mirror of https://github.com/ConsenSys/mythril
commit
8ad33dd64d
@ -1,543 +0,0 @@ |
||||
#!/usr/bin/env python3 |
||||
# -*- coding: utf-8 -*- |
||||
"""mythril.py: Bug hunting on the Ethereum blockchain |
||||
|
||||
http://www.github.com/ConsenSys/mythril |
||||
""" |
||||
|
||||
import argparse |
||||
import json |
||||
import logging |
||||
import os |
||||
import sys |
||||
|
||||
import coloredlogs |
||||
import traceback |
||||
|
||||
import mythril.support.signatures as sigs |
||||
from mythril.exceptions import AddressNotFoundError, CriticalError |
||||
from mythril.mythril import ( |
||||
MythrilAnalyzer, |
||||
MythrilDisassembler, |
||||
MythrilConfig, |
||||
MythrilLevelDB, |
||||
) |
||||
from mythril.__version__ import __version__ as VERSION |
||||
|
||||
log = logging.getLogger(__name__) |
||||
|
||||
|
||||
def exit_with_error(format_, message): |
||||
""" |
||||
:param format_: |
||||
:param message: |
||||
""" |
||||
if format_ == "text" or format_ == "markdown": |
||||
log.error(message) |
||||
elif format_ == "json": |
||||
result = {"success": False, "error": str(message), "issues": []} |
||||
print(json.dumps(result)) |
||||
else: |
||||
result = [ |
||||
{ |
||||
"issues": [], |
||||
"sourceType": "", |
||||
"sourceFormat": "", |
||||
"sourceList": [], |
||||
"meta": {"logs": [{"level": "error", "hidden": True, "msg": message}]}, |
||||
} |
||||
] |
||||
print(json.dumps(result)) |
||||
sys.exit() |
||||
|
||||
|
||||
def main() -> None: |
||||
"""The main CLI interface entry point.""" |
||||
parser = argparse.ArgumentParser( |
||||
description="Security analysis of Ethereum smart contracts" |
||||
) |
||||
create_parser(parser) |
||||
|
||||
# Get config values |
||||
|
||||
args = parser.parse_args() |
||||
parse_args(parser=parser, args=args) |
||||
|
||||
|
||||
def create_parser(parser: argparse.ArgumentParser) -> None: |
||||
""" |
||||
Creates the parser by setting all the possible arguments |
||||
:param parser: The parser |
||||
""" |
||||
parser.add_argument("solidity_file", nargs="*") |
||||
|
||||
commands = parser.add_argument_group("commands") |
||||
commands.add_argument("-g", "--graph", help="generate a control flow graph") |
||||
commands.add_argument( |
||||
"-V", |
||||
"--version", |
||||
action="store_true", |
||||
help="print the Mythril version number and exit", |
||||
) |
||||
commands.add_argument( |
||||
"-x", |
||||
"--fire-lasers", |
||||
action="store_true", |
||||
help="detect vulnerabilities, use with -c, -a or solidity file(s)", |
||||
) |
||||
commands.add_argument( |
||||
"--truffle", |
||||
action="store_true", |
||||
help="analyze a truffle project (run from project dir)", |
||||
) |
||||
commands.add_argument( |
||||
"-d", "--disassemble", action="store_true", help="print disassembly" |
||||
) |
||||
commands.add_argument( |
||||
"-j", |
||||
"--statespace-json", |
||||
help="dumps the statespace json", |
||||
metavar="OUTPUT_FILE", |
||||
) |
||||
|
||||
inputs = parser.add_argument_group("input arguments") |
||||
inputs.add_argument( |
||||
"-c", |
||||
"--code", |
||||
help='hex-encoded bytecode string ("6060604052...")', |
||||
metavar="BYTECODE", |
||||
) |
||||
inputs.add_argument( |
||||
"-f", |
||||
"--codefile", |
||||
help="file containing hex-encoded bytecode string", |
||||
metavar="BYTECODEFILE", |
||||
type=argparse.FileType("r"), |
||||
) |
||||
inputs.add_argument( |
||||
"-a", |
||||
"--address", |
||||
help="pull contract from the blockchain", |
||||
metavar="CONTRACT_ADDRESS", |
||||
) |
||||
inputs.add_argument( |
||||
"-l", |
||||
"--dynld", |
||||
action="store_true", |
||||
help="auto-load dependencies from the blockchain", |
||||
) |
||||
inputs.add_argument( |
||||
"--no-onchain-storage-access", |
||||
action="store_true", |
||||
help="turns off getting the data from onchain contracts", |
||||
) |
||||
inputs.add_argument( |
||||
"--bin-runtime", |
||||
action="store_true", |
||||
help="Only when -c or -f is used. Consider the input bytecode as binary runtime code, default being the contract creation bytecode.", |
||||
) |
||||
|
||||
outputs = parser.add_argument_group("output formats") |
||||
outputs.add_argument( |
||||
"-o", |
||||
"--outform", |
||||
choices=["text", "markdown", "json", "jsonv2"], |
||||
default="text", |
||||
help="report output format", |
||||
metavar="<text/markdown/json/jsonv2>", |
||||
) |
||||
|
||||
database = parser.add_argument_group("local contracts database") |
||||
database.add_argument( |
||||
"-s", "--search", help="search the contract database", metavar="EXPRESSION" |
||||
) |
||||
database.add_argument( |
||||
"--leveldb-dir", |
||||
help="specify leveldb directory for search or direct access operations", |
||||
metavar="LEVELDB_PATH", |
||||
) |
||||
|
||||
utilities = parser.add_argument_group("utilities") |
||||
utilities.add_argument( |
||||
"--hash", help="calculate function signature hash", metavar="SIGNATURE" |
||||
) |
||||
utilities.add_argument( |
||||
"--storage", |
||||
help="read state variables from storage index, use with -a", |
||||
metavar="INDEX,NUM_SLOTS,[array] / mapping,INDEX,[KEY1, KEY2...]", |
||||
) |
||||
utilities.add_argument( |
||||
"--solv", |
||||
help="specify solidity compiler version. If not present, will try to install it (Experimental)", |
||||
metavar="SOLV", |
||||
) |
||||
utilities.add_argument( |
||||
"--contract-hash-to-address", |
||||
help="returns corresponding address for a contract address hash", |
||||
metavar="SHA3_TO_LOOK_FOR", |
||||
) |
||||
|
||||
options = parser.add_argument_group("options") |
||||
options.add_argument( |
||||
"-m", |
||||
"--modules", |
||||
help="Comma-separated list of security analysis modules", |
||||
metavar="MODULES", |
||||
) |
||||
options.add_argument( |
||||
"--max-depth", |
||||
type=int, |
||||
default=50, |
||||
help="Maximum recursion depth for symbolic execution", |
||||
) |
||||
options.add_argument( |
||||
"--strategy", |
||||
choices=["dfs", "bfs", "naive-random", "weighted-random"], |
||||
default="bfs", |
||||
help="Symbolic execution strategy", |
||||
) |
||||
options.add_argument( |
||||
"-b", |
||||
"--loop-bound", |
||||
type=int, |
||||
default=4, |
||||
help="Bound loops at n iterations", |
||||
metavar="N", |
||||
) |
||||
options.add_argument( |
||||
"-t", |
||||
"--transaction-count", |
||||
type=int, |
||||
default=2, |
||||
help="Maximum number of transactions issued by laser", |
||||
) |
||||
options.add_argument( |
||||
"--solver-timeout", |
||||
type=int, |
||||
default=10000, |
||||
help="The maximum amount of time(in milli seconds) the solver spends for queries from analysis modules", |
||||
) |
||||
options.add_argument( |
||||
"--execution-timeout", |
||||
type=int, |
||||
default=86400, |
||||
help="The amount of seconds to spend on symbolic execution", |
||||
) |
||||
options.add_argument( |
||||
"--create-timeout", |
||||
type=int, |
||||
default=10, |
||||
help="The amount of seconds to spend on " "the initial contract creation", |
||||
) |
||||
options.add_argument( |
||||
"--solc-json", |
||||
help="Json for the optional 'settings' parameter of solc's standard-json input", |
||||
) |
||||
options.add_argument( |
||||
"--phrack", action="store_true", help="Phrack-style call graph" |
||||
) |
||||
options.add_argument( |
||||
"--enable-physics", action="store_true", help="enable graph physics simulation" |
||||
) |
||||
options.add_argument( |
||||
"-v", type=int, help="log level (0-5)", metavar="LOG_LEVEL", default=2 |
||||
) |
||||
options.add_argument( |
||||
"-q", |
||||
"--query-signature", |
||||
action="store_true", |
||||
help="Lookup function signatures through www.4byte.directory", |
||||
) |
||||
options.add_argument( |
||||
"--enable-iprof", action="store_true", help="enable the instruction profiler" |
||||
) |
||||
options.add_argument( |
||||
"--disable-dependency-pruning", |
||||
action="store_true", |
||||
help="Deactivate dependency-based pruning", |
||||
) |
||||
|
||||
rpc = parser.add_argument_group("RPC options") |
||||
|
||||
rpc.add_argument( |
||||
"--rpc", |
||||
help="custom RPC settings", |
||||
metavar="HOST:PORT / ganache / infura-[network_name]", |
||||
default="infura-mainnet", |
||||
) |
||||
rpc.add_argument( |
||||
"--rpctls", type=bool, default=False, help="RPC connection over TLS" |
||||
) |
||||
parser.add_argument("--epic", action="store_true", help=argparse.SUPPRESS) |
||||
|
||||
|
||||
def validate_args(parser: argparse.ArgumentParser, args: argparse.Namespace): |
||||
if not ( |
||||
args.search |
||||
or args.hash |
||||
or args.disassemble |
||||
or args.graph |
||||
or args.fire_lasers |
||||
or args.storage |
||||
or args.truffle |
||||
or args.statespace_json |
||||
or args.contract_hash_to_address |
||||
): |
||||
parser.print_help() |
||||
sys.exit() |
||||
|
||||
if args.v: |
||||
if 0 <= args.v < 6: |
||||
log_levels = [ |
||||
logging.NOTSET, |
||||
logging.CRITICAL, |
||||
logging.ERROR, |
||||
logging.WARNING, |
||||
logging.INFO, |
||||
logging.DEBUG, |
||||
] |
||||
coloredlogs.install( |
||||
fmt="%(name)s [%(levelname)s]: %(message)s", level=log_levels[args.v] |
||||
) |
||||
logging.getLogger("mythril").setLevel(log_levels[args.v]) |
||||
else: |
||||
exit_with_error( |
||||
args.outform, "Invalid -v value, you can find valid values in usage" |
||||
) |
||||
|
||||
if args.query_signature: |
||||
if sigs.ethereum_input_decoder is None: |
||||
exit_with_error( |
||||
args.outform, |
||||
"The --query-signature function requires the python package ethereum-input-decoder", |
||||
) |
||||
|
||||
if args.enable_iprof: |
||||
if args.v < 4: |
||||
exit_with_error( |
||||
args.outform, |
||||
"--enable-iprof must be used with -v LOG_LEVEL where LOG_LEVEL >= 4", |
||||
) |
||||
elif not (args.graph or args.fire_lasers or args.statespace_json): |
||||
exit_with_error( |
||||
args.outform, |
||||
"--enable-iprof must be used with one of -g, --graph, -x, --fire-lasers, -j and --statespace-json", |
||||
) |
||||
|
||||
|
||||
def quick_commands(args: argparse.Namespace): |
||||
if args.hash: |
||||
print(MythrilDisassembler.hash_for_function_signature(args.hash)) |
||||
sys.exit() |
||||
|
||||
|
||||
def set_config(args: argparse.Namespace): |
||||
config = MythrilConfig() |
||||
if args.dynld or not args.no_onchain_storage_access and not (args.rpc or args.i): |
||||
config.set_api_from_config_path() |
||||
|
||||
if args.address: |
||||
# Establish RPC connection if necessary |
||||
config.set_api_rpc(rpc=args.rpc, rpctls=args.rpctls) |
||||
elif args.search or args.contract_hash_to_address: |
||||
# Open LevelDB if necessary |
||||
config.set_api_leveldb( |
||||
config.leveldb_dir if not args.leveldb_dir else args.leveldb_dir |
||||
) |
||||
return config |
||||
|
||||
|
||||
def leveldb_search(config: MythrilConfig, args: argparse.Namespace): |
||||
if args.search or args.contract_hash_to_address: |
||||
leveldb_searcher = MythrilLevelDB(config.eth_db) |
||||
if args.search: |
||||
# Database search ops |
||||
leveldb_searcher.search_db(args.search) |
||||
|
||||
else: |
||||
# search corresponding address |
||||
try: |
||||
leveldb_searcher.contract_hash_to_address(args.contract_hash_to_address) |
||||
except AddressNotFoundError: |
||||
print("Address not found.") |
||||
|
||||
sys.exit() |
||||
|
||||
|
||||
def get_code(disassembler: MythrilDisassembler, args: argparse.Namespace): |
||||
address = None |
||||
if args.code: |
||||
# Load from bytecode |
||||
code = args.code[2:] if args.code.startswith("0x") else args.code |
||||
address, _ = disassembler.load_from_bytecode(code, args.bin_runtime) |
||||
elif args.codefile: |
||||
bytecode = "".join([l.strip() for l in args.codefile if len(l.strip()) > 0]) |
||||
bytecode = bytecode[2:] if bytecode.startswith("0x") else bytecode |
||||
address, _ = disassembler.load_from_bytecode(bytecode, args.bin_runtime) |
||||
elif args.address: |
||||
# Get bytecode from a contract address |
||||
address, _ = disassembler.load_from_address(args.address) |
||||
elif args.solidity_file: |
||||
# Compile Solidity source file(s) |
||||
if args.graph and len(args.solidity_file) > 1: |
||||
exit_with_error( |
||||
args.outform, |
||||
"Cannot generate call graphs from multiple input files. Please do it one at a time.", |
||||
) |
||||
address, _ = disassembler.load_from_solidity( |
||||
args.solidity_file |
||||
) # list of files |
||||
else: |
||||
exit_with_error( |
||||
args.outform, |
||||
"No input bytecode. Please provide EVM code via -c BYTECODE, -a ADDRESS, or -i SOLIDITY_FILES", |
||||
) |
||||
return address |
||||
|
||||
|
||||
def execute_command( |
||||
disassembler: MythrilDisassembler, |
||||
address: str, |
||||
parser: argparse.ArgumentParser, |
||||
args: argparse.Namespace, |
||||
): |
||||
|
||||
if args.storage: |
||||
if not args.address: |
||||
exit_with_error( |
||||
args.outform, |
||||
"To read storage, provide the address of a deployed contract with the -a option.", |
||||
) |
||||
|
||||
storage = disassembler.get_state_variable_from_storage( |
||||
address=address, params=[a.strip() for a in args.storage.strip().split(",")] |
||||
) |
||||
print(storage) |
||||
return |
||||
|
||||
analyzer = MythrilAnalyzer( |
||||
strategy=args.strategy, |
||||
disassembler=disassembler, |
||||
address=address, |
||||
max_depth=args.max_depth, |
||||
execution_timeout=args.execution_timeout, |
||||
loop_bound=args.loop_bound, |
||||
create_timeout=args.create_timeout, |
||||
enable_iprof=args.enable_iprof, |
||||
disable_dependency_pruning=args.disable_dependency_pruning, |
||||
onchain_storage_access=not args.no_onchain_storage_access, |
||||
solver_timeout=args.solver_timeout, |
||||
) |
||||
|
||||
if args.disassemble: |
||||
# or mythril.disassemble(mythril.contracts[0]) |
||||
|
||||
if disassembler.contracts[0].code: |
||||
print("Runtime Disassembly: \n" + disassembler.contracts[0].get_easm()) |
||||
if disassembler.contracts[0].creation_code: |
||||
print("Disassembly: \n" + disassembler.contracts[0].get_creation_easm()) |
||||
|
||||
elif args.graph or args.fire_lasers: |
||||
if not disassembler.contracts: |
||||
exit_with_error( |
||||
args.outform, "input files do not contain any valid contracts" |
||||
) |
||||
|
||||
if args.graph: |
||||
html = analyzer.graph_html( |
||||
contract=analyzer.contracts[0], |
||||
enable_physics=args.enable_physics, |
||||
phrackify=args.phrack, |
||||
transaction_count=args.transaction_count, |
||||
) |
||||
|
||||
try: |
||||
with open(args.graph, "w") as f: |
||||
f.write(html) |
||||
except Exception as e: |
||||
exit_with_error(args.outform, "Error saving graph: " + str(e)) |
||||
|
||||
else: |
||||
try: |
||||
report = analyzer.fire_lasers( |
||||
modules=[m.strip() for m in args.modules.strip().split(",")] |
||||
if args.modules |
||||
else [], |
||||
transaction_count=args.transaction_count, |
||||
) |
||||
outputs = { |
||||
"json": report.as_json(), |
||||
"jsonv2": report.as_swc_standard_format(), |
||||
"text": report.as_text(), |
||||
"markdown": report.as_markdown(), |
||||
} |
||||
print(outputs[args.outform]) |
||||
except ModuleNotFoundError as e: |
||||
exit_with_error( |
||||
args.outform, "Error loading analyis modules: " + format(e) |
||||
) |
||||
|
||||
elif args.statespace_json: |
||||
|
||||
if not analyzer.contracts: |
||||
exit_with_error( |
||||
args.outform, "input files do not contain any valid contracts" |
||||
) |
||||
|
||||
statespace = analyzer.dump_statespace(contract=analyzer.contracts[0]) |
||||
|
||||
try: |
||||
with open(args.statespace_json, "w") as f: |
||||
json.dump(statespace, f) |
||||
except Exception as e: |
||||
exit_with_error(args.outform, "Error saving json: " + str(e)) |
||||
|
||||
else: |
||||
parser.print_help() |
||||
|
||||
|
||||
def parse_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> None: |
||||
""" |
||||
Parses the arguments |
||||
:param parser: The parser |
||||
:param args: The args |
||||
""" |
||||
|
||||
if args.epic: |
||||
path = os.path.dirname(os.path.realpath(__file__)) |
||||
sys.argv.remove("--epic") |
||||
os.system(" ".join(sys.argv) + " | python3 " + path + "/epic.py") |
||||
sys.exit() |
||||
|
||||
if args.version: |
||||
if args.outform == "json": |
||||
print(json.dumps({"version_str": VERSION})) |
||||
else: |
||||
print("Mythril version {}".format(VERSION)) |
||||
sys.exit() |
||||
|
||||
# Parse cmdline args |
||||
validate_args(parser, args) |
||||
try: |
||||
quick_commands(args) |
||||
config = set_config(args) |
||||
leveldb_search(config, args) |
||||
disassembler = MythrilDisassembler( |
||||
eth=config.eth, |
||||
solc_version=args.solv, |
||||
solc_settings_json=args.solc_json, |
||||
enable_online_lookup=args.query_signature, |
||||
) |
||||
|
||||
address = get_code(disassembler, args) |
||||
execute_command( |
||||
disassembler=disassembler, address=address, parser=parser, args=args |
||||
) |
||||
except CriticalError as ce: |
||||
exit_with_error(args.outform, str(ce)) |
||||
except Exception: |
||||
exit_with_error(args.outform, traceback.format_exc()) |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
main() |
@ -1,194 +0,0 @@ |
||||
"""This module contains functions for dynamic gas calculation and a gas cost |
||||
table.""" |
||||
from ethereum import opcodes |
||||
from ethereum.utils import ceil32 |
||||
from typing import Callable, Dict, Tuple, Union |
||||
|
||||
|
||||
def calculate_native_gas(size: int, contract: str): |
||||
""" |
||||
|
||||
:param size: |
||||
:param contract: |
||||
:return: |
||||
""" |
||||
gas_value = None |
||||
word_num = ceil32(size) // 32 |
||||
if contract == "ecrecover": |
||||
gas_value = opcodes.GECRECOVER |
||||
elif contract == "sha256": |
||||
gas_value = opcodes.GSHA256BASE + word_num * opcodes.GSHA256WORD |
||||
elif contract == "ripemd160": |
||||
gas_value = opcodes.GRIPEMD160BASE + word_num * opcodes.GRIPEMD160WORD |
||||
elif contract == "identity": |
||||
gas_value = opcodes.GIDENTITYBASE + word_num * opcodes.GIDENTITYWORD |
||||
else: |
||||
raise ValueError("Unknown contract type {}".format(contract)) |
||||
return gas_value, gas_value |
||||
|
||||
|
||||
def calculate_sha3_gas(length: int): |
||||
""" |
||||
|
||||
:param length: |
||||
:return: |
||||
""" |
||||
gas_val = 30 + opcodes.GSHA3WORD * (ceil32(length) // 32) |
||||
return gas_val, gas_val |
||||
|
||||
|
||||
# opcode -> (min_gas, max_gas) |
||||
OPCODE_GAS = { |
||||
"STOP": (0, 0), |
||||
"ADD": (3, 3), |
||||
"MUL": (5, 5), |
||||
"SUB": (3, 3), |
||||
"DIV": (5, 5), |
||||
"SDIV": (5, 5), |
||||
"MOD": (5, 5), |
||||
"SMOD": (5, 5), |
||||
"ADDMOD": (8, 8), |
||||
"MULMOD": (8, 8), |
||||
"EXP": (10, 340), # exponent max 2^32 |
||||
"SIGNEXTEND": (5, 5), |
||||
"LT": (3, 3), |
||||
"GT": (3, 3), |
||||
"SLT": (3, 3), |
||||
"SGT": (3, 3), |
||||
"EQ": (3, 3), |
||||
"ISZERO": (3, 3), |
||||
"AND": (3, 3), |
||||
"OR": (3, 3), |
||||
"XOR": (3, 3), |
||||
"NOT": (3, 3), |
||||
"BYTE": (3, 3), |
||||
"SHL": (3, 3), |
||||
"SHR": (3, 3), |
||||
"SAR": (3, 3), |
||||
"SHA3": ( |
||||
30, |
||||
30 + 6 * 8, |
||||
), # max can be larger, but usually storage location with 8 words |
||||
"SHA3_FUNC": calculate_sha3_gas, |
||||
"ADDRESS": (2, 2), |
||||
"BALANCE": (400, 400), |
||||
"ORIGIN": (2, 2), |
||||
"CALLER": (2, 2), |
||||
"CALLVALUE": (2, 2), |
||||
"CALLDATALOAD": (3, 3), |
||||
"CALLDATASIZE": (2, 2), |
||||
"CALLDATACOPY": (2, 2 + 3 * 768), # https://ethereum.stackexchange.com/a/47556 |
||||
"CODESIZE": (2, 2), |
||||
"CODECOPY": (2, 2 + 3 * 768), # https://ethereum.stackexchange.com/a/47556, |
||||
"GASPRICE": (2, 2), |
||||
"EXTCODESIZE": (700, 700), |
||||
"EXTCODECOPY": (700, 700 + 3 * 768), # https://ethereum.stackexchange.com/a/47556 |
||||
"EXTCODEHASH": (400, 400), |
||||
"RETURNDATASIZE": (2, 2), |
||||
"RETURNDATACOPY": (3, 3), |
||||
"BLOCKHASH": (20, 20), |
||||
"COINBASE": (2, 2), |
||||
"TIMESTAMP": (2, 2), |
||||
"NUMBER": (2, 2), |
||||
"DIFFICULTY": (2, 2), |
||||
"GASLIMIT": (2, 2), |
||||
"POP": (2, 2), |
||||
# assume 1KB memory r/w cost as upper bound |
||||
"MLOAD": (3, 96), |
||||
"MSTORE": (3, 98), |
||||
"MSTORE8": (3, 98), |
||||
# assume 64 byte r/w cost as upper bound |
||||
"SLOAD": (400, 400), |
||||
"SSTORE": (5000, 25000), |
||||
"JUMP": (8, 8), |
||||
"JUMPI": (10, 10), |
||||
"PC": (2, 2), |
||||
"MSIZE": (2, 2), |
||||
"GAS": (2, 2), |
||||
"JUMPDEST": (1, 1), |
||||
"PUSH1": (3, 3), |
||||
"PUSH2": (3, 3), |
||||
"PUSH3": (3, 3), |
||||
"PUSH4": (3, 3), |
||||
"PUSH5": (3, 3), |
||||
"PUSH6": (3, 3), |
||||
"PUSH7": (3, 3), |
||||
"PUSH8": (3, 3), |
||||
"PUSH9": (3, 3), |
||||
"PUSH10": (3, 3), |
||||
"PUSH11": (3, 3), |
||||
"PUSH12": (3, 3), |
||||
"PUSH13": (3, 3), |
||||
"PUSH14": (3, 3), |
||||
"PUSH15": (3, 3), |
||||
"PUSH16": (3, 3), |
||||
"PUSH17": (3, 3), |
||||
"PUSH18": (3, 3), |
||||
"PUSH19": (3, 3), |
||||
"PUSH20": (3, 3), |
||||
"PUSH21": (3, 3), |
||||
"PUSH22": (3, 3), |
||||
"PUSH23": (3, 3), |
||||
"PUSH24": (3, 3), |
||||
"PUSH25": (3, 3), |
||||
"PUSH26": (3, 3), |
||||
"PUSH27": (3, 3), |
||||
"PUSH28": (3, 3), |
||||
"PUSH29": (3, 3), |
||||
"PUSH30": (3, 3), |
||||
"PUSH31": (3, 3), |
||||
"PUSH32": (3, 3), |
||||
"DUP1": (3, 3), |
||||
"DUP2": (3, 3), |
||||
"DUP3": (3, 3), |
||||
"DUP4": (3, 3), |
||||
"DUP5": (3, 3), |
||||
"DUP6": (3, 3), |
||||
"DUP7": (3, 3), |
||||
"DUP8": (3, 3), |
||||
"DUP9": (3, 3), |
||||
"DUP10": (3, 3), |
||||
"DUP11": (3, 3), |
||||
"DUP12": (3, 3), |
||||
"DUP13": (3, 3), |
||||
"DUP14": (3, 3), |
||||
"DUP15": (3, 3), |
||||
"DUP16": (3, 3), |
||||
"SWAP1": (3, 3), |
||||
"SWAP2": (3, 3), |
||||
"SWAP3": (3, 3), |
||||
"SWAP4": (3, 3), |
||||
"SWAP5": (3, 3), |
||||
"SWAP6": (3, 3), |
||||
"SWAP7": (3, 3), |
||||
"SWAP8": (3, 3), |
||||
"SWAP9": (3, 3), |
||||
"SWAP10": (3, 3), |
||||
"SWAP11": (3, 3), |
||||
"SWAP12": (3, 3), |
||||
"SWAP13": (3, 3), |
||||
"SWAP14": (3, 3), |
||||
"SWAP15": (3, 3), |
||||
"SWAP16": (3, 3), |
||||
# apparently Solidity only allows byte32 as input to the log |
||||
# function. Virtually it could be as large as the block gas limit |
||||
# allows, but let's stick to the reasonable standard here. |
||||
# https://ethereum.stackexchange.com/a/1691 |
||||
"LOG0": (375, 375 + 8 * 32), |
||||
"LOG1": (2 * 375, 2 * 375 + 8 * 32), |
||||
"LOG2": (3 * 375, 3 * 375 + 8 * 32), |
||||
"LOG3": (4 * 375, 4 * 375 + 8 * 32), |
||||
"LOG4": (5 * 375, 5 * 375 + 8 * 32), |
||||
"CREATE": (32000, 32000), |
||||
"CREATE2": (32000, 32000), # TODO: Make the gas values dynamic |
||||
"CALL": (700, 700 + 9000 + 25000), |
||||
"NATIVE_COST": calculate_native_gas, |
||||
"CALLCODE": (700, 700 + 9000 + 25000), |
||||
"RETURN": (0, 0), |
||||
"DELEGATECALL": (700, 700 + 9000 + 25000), |
||||
"STATICCALL": (700, 700 + 9000 + 25000), |
||||
"REVERT": (0, 0), |
||||
"SUICIDE": (5000, 30000), |
||||
"ASSERT_FAIL": (0, 0), |
||||
"INVALID": (0, 0), |
||||
} # type: Dict[str, Union[Tuple[int, int], Callable]] |
@ -0,0 +1,222 @@ |
||||
from ethereum import opcodes |
||||
from ethereum.utils import ceil32 |
||||
from typing import Callable, Dict, Tuple, Union |
||||
|
||||
|
||||
Z_OPERATOR_TUPLE = (0, 1) |
||||
UN_OPERATOR_TUPLE = (1, 1) |
||||
BIN_OPERATOR_TUPLE = (2, 1) |
||||
T_OPERATOR_TUPLE = (3, 1) |
||||
GAS = "gas" |
||||
STACK = "stack" |
||||
|
||||
# Gas tuple contains (min_gas, max_gas) |
||||
# stack tuple contains (no_of_elements_popped, no_of_elements_pushed) |
||||
|
||||
OPCODES = { |
||||
"STOP": {GAS: (0, 0), STACK: (0, 0)}, |
||||
"ADD": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"MUL": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE}, |
||||
"SUB": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"DIV": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE}, |
||||
"SDIV": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE}, |
||||
"MOD": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE}, |
||||
"SMOD": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE}, |
||||
"ADDMOD": {GAS: (8, 8), STACK: BIN_OPERATOR_TUPLE}, |
||||
"MULMOD": {GAS: (8, 8), STACK: T_OPERATOR_TUPLE}, |
||||
"EXP": {GAS: (10, 340), STACK: BIN_OPERATOR_TUPLE}, # exponent max 2^32 |
||||
"SIGNEXTEND": {GAS: (5, 5), STACK: BIN_OPERATOR_TUPLE}, |
||||
"LT": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"GT": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"SLT": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"SGT": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"EQ": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"AND": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"ISZERO": {GAS: (3, 3), STACK: UN_OPERATOR_TUPLE}, |
||||
"OR": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"XOR": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"NOT": {GAS: (3, 3), STACK: UN_OPERATOR_TUPLE}, |
||||
"BYTE": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"SHL": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"SHR": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"SAR": {GAS: (3, 3), STACK: BIN_OPERATOR_TUPLE}, |
||||
"SHA3": { |
||||
GAS: ( |
||||
30, |
||||
30 + 6 * 8, |
||||
), # max can be larger, but usually storage location with 8 words |
||||
STACK: BIN_OPERATOR_TUPLE, |
||||
}, |
||||
"ADDRESS": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"BALANCE": {GAS: (400, 400), STACK: UN_OPERATOR_TUPLE}, |
||||
"ORIGIN": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"CALLER": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"CALLVALUE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"CALLDATALOAD": {GAS: (3, 3), STACK: UN_OPERATOR_TUPLE}, |
||||
"CALLDATASIZE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"CALLDATACOPY": { |
||||
GAS: (2, 2 + 3 * 768), # https://ethereum.stackexchange.com/a/47556 |
||||
STACK: (3, 0), |
||||
}, |
||||
"CODESIZE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"CODECOPY": { |
||||
GAS: (2, 2 + 3 * 768), # https://ethereum.stackexchange.com/a/47556, |
||||
STACK: (3, 0), |
||||
}, |
||||
"GASPRICE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"EXTCODESIZE": {GAS: (700, 700), STACK: Z_OPERATOR_TUPLE}, |
||||
"EXTCODECOPY": { |
||||
GAS: (700, 700 + 3 * 768), # https://ethereum.stackexchange.com/a/47556 |
||||
STACK: (4, 0), |
||||
}, |
||||
"EXTCODEHASH": {GAS: (400, 400), STACK: UN_OPERATOR_TUPLE}, |
||||
"RETURNDATASIZE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"RETURNDATACOPY": {GAS: (3, 3), STACK: (3, 0)}, |
||||
"BLOCKHASH": {GAS: (20, 20), STACK: UN_OPERATOR_TUPLE}, |
||||
"COINBASE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"TIMESTAMP": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"NUMBER": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"DIFFICULTY": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"GASLIMIT": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"POP": {GAS: (2, 2), STACK: (1, 0)}, |
||||
# assume 1KB memory r/w cost as upper bound |
||||
"MLOAD": {GAS: (3, 96), STACK: UN_OPERATOR_TUPLE}, |
||||
"MSTORE": {GAS: (3, 98), STACK: (2, 0)}, |
||||
"MSTORE8": {GAS: (3, 98), STACK: (2, 0)}, |
||||
# assume 64 byte r/w cost as upper bound |
||||
"SLOAD": {GAS: (400, 400), STACK: UN_OPERATOR_TUPLE}, |
||||
"SSTORE": {GAS: (5000, 25000), STACK: (1, 0)}, |
||||
"JUMP": {GAS: (8, 8), STACK: (1, 0)}, |
||||
"JUMPI": {GAS: (10, 10), STACK: (2, 0)}, |
||||
"PC": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"MSIZE": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"GAS": {GAS: (2, 2), STACK: Z_OPERATOR_TUPLE}, |
||||
"JUMPDEST": {GAS: (1, 1), STACK: (0, 0)}, |
||||
"PUSH1": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH2": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH3": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH4": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH5": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH6": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH7": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH8": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH9": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH10": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH11": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH12": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH13": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH14": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH15": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH16": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH17": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH18": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH19": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH20": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH21": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH22": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH23": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH24": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH25": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH26": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH27": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH28": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH29": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH30": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH31": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"PUSH32": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP1": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP2": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP3": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP4": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP5": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP6": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP7": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP8": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP9": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP10": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP11": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP12": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP13": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP14": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP15": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"DUP16": {GAS: (3, 3), STACK: Z_OPERATOR_TUPLE}, |
||||
"SWAP1": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP2": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP3": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP4": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP5": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP6": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP7": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP8": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP9": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP10": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP11": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP12": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP13": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP14": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP15": {GAS: (3, 3), STACK: (0, 0)}, |
||||
"SWAP16": {GAS: (3, 3), STACK: (0, 0)}, |
||||
# apparently Solidity only allows byte32 as input to the log |
||||
# function. Virtually it could be as large as the block gas limit |
||||
# allows, but let's stick to the reasonable standard here. |
||||
# https://ethereum.stackexchange.com/a/1691 |
||||
"LOG0": {GAS: (375, 375 + 8 * 32), STACK: (2, 0)}, |
||||
"LOG1": {GAS: (2 * 375, 2 * 375 + 8 * 32), STACK: (3, 0)}, |
||||
"LOG2": {GAS: (3 * 375, 3 * 375 + 8 * 32), STACK: (4, 0)}, |
||||
"LOG3": {GAS: (4 * 375, 4 * 375 + 8 * 32), STACK: (5, 0)}, |
||||
"LOG4": {GAS: (5 * 375, 5 * 375 + 8 * 32), STACK: (6, 0)}, |
||||
"CREATE": {GAS: (32000, 32000), STACK: T_OPERATOR_TUPLE}, |
||||
"CREATE2": { |
||||
GAS: (32000, 32000), # TODO: Make the gas values dynamic |
||||
STACK: (4, 1), |
||||
}, |
||||
"CALL": {GAS: (700, 700 + 9000 + 25000), STACK: (7, 1)}, |
||||
"CALLCODE": {GAS: (700, 700 + 9000 + 25000), STACK: (7, 1)}, |
||||
"RETURN": {GAS: (0, 0), STACK: (2, 0)}, |
||||
"DELEGATECALL": {GAS: (700, 700 + 9000 + 25000), STACK: (6, 1)}, |
||||
"STATICCALL": {GAS: (700, 700 + 9000 + 25000), STACK: (6, 1)}, |
||||
"REVERT": {GAS: (0, 0), STACK: (2, 0)}, |
||||
"SUICIDE": {GAS: (5000, 30000), STACK: (1, 0)}, |
||||
"ASSERT_FAIL": {GAS: (0, 0), STACK: (0, 0)}, |
||||
"INVALID": {GAS: (0, 0), STACK: (0, 0)}, |
||||
} # type: Dict[str, Dict[str, Tuple[int, int]]] |
||||
|
||||
|
||||
def calculate_sha3_gas(length: int): |
||||
""" |
||||
|
||||
:param length: |
||||
:return: |
||||
""" |
||||
gas_val = 30 + opcodes.GSHA3WORD * (ceil32(length) // 32) |
||||
return gas_val, gas_val |
||||
|
||||
|
||||
def calculate_native_gas(size: int, contract: str): |
||||
""" |
||||
|
||||
:param size: |
||||
:param contract: |
||||
:return: |
||||
""" |
||||
gas_value = None |
||||
word_num = ceil32(size) // 32 |
||||
if contract == "ecrecover": |
||||
gas_value = opcodes.GECRECOVER |
||||
elif contract == "sha256": |
||||
gas_value = opcodes.GSHA256BASE + word_num * opcodes.GSHA256WORD |
||||
elif contract == "ripemd160": |
||||
gas_value = opcodes.GRIPEMD160BASE + word_num * opcodes.GRIPEMD160WORD |
||||
elif contract == "identity": |
||||
gas_value = opcodes.GIDENTITYBASE + word_num * opcodes.GIDENTITYWORD |
||||
else: |
||||
raise ValueError("Unknown contract type {}".format(contract)) |
||||
return gas_value, gas_value |
||||
|
||||
|
||||
def get_opcode_gas(opcode: str) -> Tuple[int, int]: |
||||
return OPCODES[opcode][GAS] |
||||
|
||||
|
||||
def get_required_stack_elements(opcode: str) -> int: |
||||
return OPCODES[opcode][STACK][0] |
@ -0,0 +1,57 @@ |
||||
from mythril.laser.smt import symbol_factory |
||||
from mythril.disassembler.disassembly import Disassembly |
||||
from mythril.laser.ethereum.state.environment import Environment |
||||
from mythril.laser.ethereum.state.machine_state import MachineState |
||||
from mythril.laser.ethereum.state.global_state import GlobalState |
||||
from mythril.laser.ethereum.state.world_state import WorldState |
||||
from mythril.laser.ethereum.instructions import Instruction |
||||
from mythril.laser.ethereum.transaction.transaction_models import MessageCallTransaction |
||||
|
||||
|
||||
def test_extcodecopy(): |
||||
# Arrange |
||||
new_world_state = WorldState() |
||||
new_account = new_world_state.create_account(balance=10, address=101) |
||||
new_account.code = Disassembly("60616240") |
||||
ext_account = new_world_state.create_account(balance=1000, address=121) |
||||
ext_account.code = Disassembly("6040404040") |
||||
|
||||
new_environment = Environment(new_account, None, None, None, None, None) |
||||
state = GlobalState( |
||||
new_world_state, new_environment, None, MachineState(gas_limit=8000000) |
||||
) |
||||
state.transaction_stack.append( |
||||
(MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None) |
||||
) |
||||
|
||||
state.mstate.stack = [3, 0, 0, 121] |
||||
instruction = Instruction("extcodecopy", dynamic_loader=None) |
||||
|
||||
# Act |
||||
new_state = instruction.evaluate(state)[0] |
||||
# Assert |
||||
assert new_state.mstate.memory[0:3] == [96, 64, 64] |
||||
|
||||
|
||||
def test_extcodecopy_fail(): |
||||
# Arrange |
||||
new_world_state = WorldState() |
||||
new_account = new_world_state.create_account(balance=10, address=101) |
||||
new_account.code = Disassembly("60616240") |
||||
new_environment = Environment(new_account, None, None, None, None, None) |
||||
state = GlobalState( |
||||
new_world_state, new_environment, None, MachineState(gas_limit=8000000) |
||||
) |
||||
state.transaction_stack.append( |
||||
(MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None) |
||||
) |
||||
|
||||
state.mstate.stack = [2, 2, 2, symbol_factory.BitVecSym("FAIL", 256)] |
||||
instruction = Instruction("extcodecopy", dynamic_loader=None) |
||||
|
||||
# Act |
||||
new_state = instruction.evaluate(state)[0] |
||||
|
||||
# Assert |
||||
assert new_state.mstate.stack == [] |
||||
assert new_state.mstate.memory._memory == state.mstate.memory._memory |
Loading…
Reference in new issue