Merge branch 'develop' into specify-attacker-creator-address

specify-attacker-creator-address
Nathan 5 years ago committed by GitHub
commit 8ad33dd64d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      myth
  2. 2
      mythril/__version__.py
  3. 4
      mythril/analysis/modules/deprecated_ops.py
  4. 4
      mythril/analysis/modules/external_calls.py
  5. 2
      mythril/analysis/modules/state_change_external_calls.py
  6. 2
      mythril/analysis/templates/report_as_markdown.jinja2
  7. 2
      mythril/analysis/templates/report_as_text.jinja2
  8. 543
      mythril/interfaces/old_cli.py
  9. 6
      mythril/laser/ethereum/call.py
  10. 194
      mythril/laser/ethereum/gas.py
  11. 222
      mythril/laser/ethereum/instruction_data.py
  12. 53
      mythril/laser/ethereum/instructions.py
  13. 2
      mythril/laser/ethereum/natives.py
  14. 50
      mythril/laser/ethereum/svm.py
  15. 6
      tests/cmd_line_test.py
  16. 57
      tests/instructions/extcodecopy_test.py

24
myth

@ -3,29 +3,11 @@
"""mythril.py: Bug hunting on the Ethereum blockchain
http://www.github.com/b-mueller/mythril
"""
from sys import argv, exit
from mythril.interfaces.cli import COMMAND_LIST
from sys import exit
import mythril.interfaces.cli
import mythril.interfaces.old_cli
import warnings
def format_Warning(message, category, filename, lineno, line=""):
return "Deprecation warning: {}\n\n".format(str(message))
warnings.formatwarning = format_Warning
if __name__ == "__main__":
for arg in argv:
if arg in COMMAND_LIST:
mythril.interfaces.cli.main()
exit()
if "--help" in argv or "-h" in argv:
mythril.interfaces.cli.main()
exit()
warnings.warn("The old cli arguments are deprecated, Please use 'myth -h' to view the new command line interface")
mythril.interfaces.old_cli.main()
mythril.interfaces.cli.main()
exit()

@ -4,4 +4,4 @@ This file is suitable for sourcing inside POSIX shell, e.g. bash as well
as for importing into Python.
"""
__version__ = "v0.21.17"
__version__ = "v0.21.18"

@ -58,9 +58,7 @@ class DeprecatedOperationsModule(DetectionModule):
"The smart contract retrieves the transaction origin (tx.origin) using msg.origin. "
"Use of msg.origin is deprecated and the instruction may be removed in the future. "
"Use msg.sender instead.\nSee also: "
"https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin".format(
state.environment.active_function_name
)
"https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin"
)
swc_id = DEPRECATED_FUNCTIONS_USAGE

@ -87,7 +87,7 @@ class ExternalCalls(DetectionModule):
try:
constraints = Constraints([UGT(gas, symbol_factory.BitVecVal(2300, 256))])
transaction_sequence = solver.get_transaction_sequence(
solver.get_transaction_sequence(
state, constraints + state.mstate.constraints
)
@ -100,7 +100,7 @@ class ExternalCalls(DetectionModule):
if not isinstance(tx, ContractCreationTransaction):
constraints.append(tx.caller == ACTOR_ADDRESSES["ATTACKER"])
transaction_sequence = solver.get_transaction_sequence(
solver.get_transaction_sequence(
state, constraints + state.mstate.constraints
)

@ -55,7 +55,7 @@ class StateChangeCallsAnnotation(StateAnnotation):
constraints += [to == 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF]
try:
transaction_sequence = solver.get_transaction_sequence(
solver.get_transaction_sequence(
global_state, constraints + global_state.mstate.constraints
)
except UnsatError:

@ -42,7 +42,7 @@ Account: {% if key == "0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe" %}[CREATOR]{%
{% for step in issue.tx_sequence.steps %}
{% if step == issue.tx_sequence.steps[0] and step.input != "0x" and step.origin == "0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe" %}
Caller: [CREATOR], data: {{ step.input }}, value: {{ step.value }}
Caller: [CREATOR], data: [CONTRACT_CREATION], value: {{ step.value }}
{% else %}
Caller: {% if step.origin == "0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe" %}[CREATOR]{% elif step.origin == "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" %}[ATTACKER]{% else %}[SOMEGUY]{% endif %}, function: {{ step.name }}, txdata: {{ step.input }}, value: {{ step.value }}
{% endif %}

@ -33,7 +33,7 @@ Transaction Sequence:
{% for step in issue.tx_sequence.steps %}
{% if step == issue.tx_sequence.steps[0] and step.input != "0x" and step.origin == "0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe" %}
Caller: [CREATOR], data: {{ step.input }}, value: {{ step.value }}
Caller: [CREATOR], data: [CONTRACT_CREATION], value: {{ step.value }}
{% else %}
Caller: {% if step.origin == "0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe" %}[CREATOR]{% elif step.origin == "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" %}[ATTACKER]{% else %}[SOMEGUY]{% endif %}, function: {{ step.name }}, txdata: {{ step.input }}, value: {{ step.value }}
{% endif %}

@ -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()

@ -8,7 +8,7 @@ from typing import Union, List, cast, Callable, Optional
import mythril.laser.ethereum.util as util
from mythril.laser.ethereum import natives
from mythril.laser.ethereum.gas import OPCODE_GAS
from mythril.laser.ethereum.instruction_data import calculate_native_gas
from mythril.laser.ethereum.state.account import Account
from mythril.laser.ethereum.natives import PRECOMPILE_COUNT
from mythril.laser.ethereum.state.calldata import (
@ -213,7 +213,7 @@ def get_call_data(
return ConcreteCalldata(transaction_id, calldata_from_mem)
except TypeError:
log.debug(
"Unsupported symbolic calldata offset %s size %s", memory_start, memory_size
"Unsupported symbolic memory offset %s size %s", memory_start, memory_size
)
return SymbolicCalldata(transaction_id)
@ -242,7 +242,7 @@ def native_call(
contract_list = ["ecrecover", "sha256", "ripemd160", "identity"]
call_address_int = int(callee_address, 16)
native_gas_min, native_gas_max = cast(Callable, OPCODE_GAS["NATIVE_COST"])(
native_gas_min, native_gas_max = calculate_native_gas(
global_state.mstate.calculate_extension_size(mem_out_start, mem_out_sz),
contract_list[call_address_int - 1],
)

@ -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]

@ -43,7 +43,7 @@ from mythril.laser.ethereum.evm_exceptions import (
OutOfGasException,
WriteProtection,
)
from mythril.laser.ethereum.gas import OPCODE_GAS
from mythril.laser.ethereum.instruction_data import get_opcode_gas, calculate_sha3_gas
from mythril.laser.ethereum.state.global_state import GlobalState
from mythril.laser.ethereum.transaction import (
MessageCallTransaction,
@ -159,7 +159,7 @@ class StateTransition(object):
if not self.enable_gas:
return global_state
opcode = global_state.instruction["opcode"]
min_gas, max_gas = cast(Tuple[int, int], OPCODE_GAS[opcode])
min_gas, max_gas = get_opcode_gas(opcode)
global_state.mstate.min_gas_used += min_gas
global_state.mstate.max_gas_used += max_gas
self.check_gas_usage_limit(global_state)
@ -901,9 +901,7 @@ class Instruction:
state = global_state.mstate
address = state.stack.pop()
balance = global_state.world_state.balances[
global_state.environment.active_account.address
]
balance = global_state.world_state.balances[address]
state.stack.append(balance)
return [global_state]
@ -960,7 +958,7 @@ class Instruction:
@staticmethod
def _sha3_gas_helper(global_state, length):
min_gas, max_gas = cast(Callable, OPCODE_GAS["SHA3_FUNC"])(length)
min_gas, max_gas = calculate_sha3_gas(length)
global_state.mstate.min_gas_used += min_gas
global_state.mstate.max_gas_used += max_gas
StateTransition.check_gas_usage_limit(global_state)
@ -986,7 +984,7 @@ class Instruction:
state.stack.append(
symbol_factory.BitVecSym("KECCAC_mem[" + str(op0) + "]", 256)
)
gas_tuple = cast(Tuple, OPCODE_GAS["SHA3"])
gas_tuple = get_opcode_gas("SHA3")
state.min_gas_used += gas_tuple[0]
state.max_gas_used += gas_tuple[1]
return [global_state]
@ -1525,7 +1523,7 @@ class Instruction:
new_state = copy(global_state)
# add JUMP gas cost
min_gas, max_gas = cast(Tuple[int, int], OPCODE_GAS["JUMP"])
min_gas, max_gas = get_opcode_gas("JUMP")
new_state.mstate.min_gas_used += min_gas
new_state.mstate.max_gas_used += max_gas
@ -1544,7 +1542,7 @@ class Instruction:
"""
state = global_state.mstate
disassembly = global_state.environment.code
min_gas, max_gas = cast(Tuple[int, int], OPCODE_GAS["JUMPI"])
min_gas, max_gas = get_opcode_gas("JUMPI")
states = []
op0, condition = state.stack.pop(), state.stack.pop()
@ -1677,12 +1675,24 @@ class Instruction:
code_raw = []
code_end = call_data.size
for i in range(call_data.size):
size = call_data.size
if isinstance(size, BitVec):
# This should be fine because of the below check
if size.symbolic:
size = 10 ** 5
else:
size = size.value
for i in range(size):
if call_data[i].symbolic:
code_end = i
break
code_raw.append(call_data[i].value)
if len(code_raw) < 1:
global_state.mstate.stack.append(1)
log.debug("No code found for trying to execute a create type instruction.")
return global_state
code_str = bytes.hex(bytes(code_raw))
next_transaction_id = get_next_transaction_id()
@ -1737,16 +1747,7 @@ class Instruction:
@StateTransition()
def create_post(self, global_state: GlobalState) -> List[GlobalState]:
call_value, mem_offset, mem_size = global_state.mstate.pop(3)
call_data = get_call_data(global_state, mem_offset, mem_offset + mem_size)
if global_state.last_return_data:
return_val = symbol_factory.BitVecVal(
int(global_state.last_return_data, 16), 256
)
else:
return_val = symbol_factory.BitVecVal(0, 256)
global_state.mstate.stack.append(return_val)
return [global_state]
return self._handle_create_type_post(global_state)
@StateTransition(is_state_mutation_instruction=True)
def create2_(self, global_state: GlobalState) -> List[GlobalState]:
@ -1763,11 +1764,17 @@ class Instruction:
@StateTransition()
def create2_post(self, global_state: GlobalState) -> List[GlobalState]:
call_value, mem_offset, mem_size, salt = global_state.mstate.pop(4)
call_data = get_call_data(global_state, mem_offset, mem_offset + mem_size)
return self._handle_create_type_post(global_state, opcode="create2")
@staticmethod
def _handle_create_type_post(global_state, opcode="cre ate"):
if opcode == "create2":
global_state.mstate.pop(4)
else:
global_state.mstate.pop(3)
if global_state.last_return_data:
return_val = symbol_factory.BitVecVal(
int(global_state.last_return_data), 256
int(global_state.last_return_data, 16), 256
)
else:
return_val = symbol_factory.BitVecVal(0, 256)

@ -116,9 +116,7 @@ def mod_exp(data: List[int]) -> List[int]:
return []
first_exp_bytes = extract32(bytes_data, 96 + baselen) >> (8 * max(32 - explen, 0))
bitlength = -1
while first_exp_bytes:
bitlength += 1
first_exp_bytes >>= 1
base = bytearray(baselen)

@ -10,6 +10,7 @@ from mythril.laser.ethereum.cfg import NodeFlags, Node, Edge, JumpType
from mythril.laser.ethereum.evm_exceptions import StackUnderflowException
from mythril.laser.ethereum.evm_exceptions import VmException
from mythril.laser.ethereum.instructions import Instruction
from mythril.laser.ethereum.instruction_data import get_required_stack_elements
from mythril.laser.ethereum.plugins.signals import PluginSkipWorldState, PluginSkipState
from mythril.laser.ethereum.plugins.implementations.plugin_annotations import (
MutationAnnotation,
@ -269,6 +270,25 @@ class LaserEVM:
self.open_states.append(global_state.world_state)
def handle_vm_exception(
self, global_state: GlobalState, op_code: str, error_msg: str
) -> List[GlobalState]:
transaction, return_global_state = global_state.transaction_stack.pop()
if return_global_state is None:
# In this case we don't put an unmodified world state in the open_states list Since in the case of an
# exceptional halt all changes should be discarded, and this world state would not provide us with a
# previously unseen world state
log.debug("Encountered a VmException, ending path: `{}`".format(error_msg))
new_global_states = [] # type: List[GlobalState]
else:
# First execute the post hook for the transaction ending instruction
self._execute_post_hook(op_code, [global_state])
new_global_states = self._end_message_call(
return_global_state, global_state, revert_changes=True, return_data=None
)
return new_global_states
def execute_state(
self, global_state: GlobalState
) -> Tuple[List[GlobalState], Optional[str]]:
@ -288,6 +308,18 @@ class LaserEVM:
except IndexError:
self._add_world_state(global_state)
return [], None
if len(global_state.mstate.stack) < get_required_stack_elements(op_code):
error_msg = (
"Stack Underflow Exception due to insufficient "
"stack elements for the address {}".format(
instructions[global_state.mstate.pc]["address"]
)
)
new_global_states = self.handle_vm_exception(
global_state, op_code, error_msg
)
self._execute_post_hook(op_code, new_global_states)
return new_global_states, op_code
try:
self._execute_pre_hook(op_code, global_state)
@ -301,23 +333,7 @@ class LaserEVM:
).evaluate(global_state)
except VmException as e:
transaction, return_global_state = global_state.transaction_stack.pop()
if return_global_state is None:
# In this case we don't put an unmodified world state in the open_states list Since in the case of an
# exceptional halt all changes should be discarded, and this world state would not provide us with a
# previously unseen world state
log.debug("Encountered a VmException, ending path: `{}`".format(str(e)))
new_global_states = []
else:
# First execute the post hook for the transaction ending instruction
self._execute_post_hook(op_code, [global_state])
new_global_states = self._end_message_call(
return_global_state,
global_state,
revert_changes=True,
return_data=None,
)
new_global_states = self.handle_vm_exception(global_state, op_code, str(e))
except TransactionStartSignal as start_signal:
# Setup new global state

@ -59,11 +59,11 @@ class CommandLineToolTestCase(BaseTestCase):
self.assertIn(""""success": false""", output_of(command))
def test_only_epic(self):
command = "python3 {}".format(MYTH)
self.assertIn("usage: ", output_of(command))
command = "python3 {} --epic".format(MYTH)
# Just check for crashes
output_of(command)
def test_storage(self):
solidity_file = str(TESTDATA / "input_contracts" / "origin.sol")
command = """python3 {} read-storage "438767356, 3" 0x76799f77587738bfeef09452df215b63d2cfb08a """.format(
MYTH
)

@ -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…
Cancel
Save