From b6c4ad1cbd609e6ae0aa4e218dff40e843fd7696 Mon Sep 17 00:00:00 2001 From: Nathan Date: Wed, 14 Nov 2018 07:05:29 -0500 Subject: [PATCH 1/5] Refactor state.py into multiple files (#698) --- mythril/analysis/analysis_utils.py | 2 +- mythril/analysis/symbolic.py | 2 +- mythril/laser/ethereum/call.py | 4 +- mythril/laser/ethereum/instructions.py | 3 +- mythril/laser/ethereum/natives.py | 2 +- mythril/laser/ethereum/state.py | 571 ------------------ mythril/laser/ethereum/state/__init__.py | 1 + mythril/laser/ethereum/state/account.py | 105 ++++ mythril/laser/ethereum/state/calldata.py | 104 ++++ mythril/laser/ethereum/state/constraints.py | 35 ++ mythril/laser/ethereum/state/environment.py | 55 ++ mythril/laser/ethereum/state/global_state.py | 75 +++ mythril/laser/ethereum/state/machine_state.py | 137 +++++ mythril/laser/ethereum/state/world_state.py | 78 +++ mythril/laser/ethereum/strategy/basic.py | 2 +- mythril/laser/ethereum/svm.py | 5 +- mythril/laser/ethereum/taint_analysis.py | 3 +- .../laser/ethereum/transaction/concolic.py | 13 +- .../laser/ethereum/transaction/symbolic.py | 3 +- .../transaction/transaction_models.py | 12 +- tests/analysis/test_delegatecall.py | 6 +- tests/instructions/codecopy_test.py | 5 +- tests/laser/evm_testsuite/evm_test.py | 2 +- tests/laser/state/calldata_test.py | 2 +- tests/laser/state/mstack_test.py | 2 +- tests/laser/state/mstate_test.py | 2 +- tests/laser/state/storage_test.py | 2 +- tests/laser/test_transaction.py | 2 +- tests/laser/transaction/symbolic_test.py | 3 +- tests/native_test.py | 4 +- tests/svm_test.py | 4 +- tests/taint_result_test.py | 2 +- tests/taint_runner_test.py | 5 +- 33 files changed, 643 insertions(+), 610 deletions(-) delete mode 100644 mythril/laser/ethereum/state.py create mode 100644 mythril/laser/ethereum/state/__init__.py create mode 100644 mythril/laser/ethereum/state/account.py create mode 100644 mythril/laser/ethereum/state/calldata.py create mode 100644 mythril/laser/ethereum/state/constraints.py create mode 100644 mythril/laser/ethereum/state/environment.py create mode 100644 mythril/laser/ethereum/state/global_state.py create mode 100644 mythril/laser/ethereum/state/machine_state.py create mode 100644 mythril/laser/ethereum/state/world_state.py diff --git a/mythril/analysis/analysis_utils.py b/mythril/analysis/analysis_utils.py index 8caac0b7..d013f074 100644 --- a/mythril/analysis/analysis_utils.py +++ b/mythril/analysis/analysis_utils.py @@ -2,7 +2,7 @@ import re from typing import List from z3 import * from mythril.laser.ethereum.transaction import ContractCreationTransaction -from mythril.laser.ethereum.state import GlobalState +from mythril.laser.ethereum.state.global_state import GlobalState def get_non_creator_constraints(state: GlobalState) -> (List, bool): diff --git a/mythril/analysis/symbolic.py b/mythril/analysis/symbolic.py index 5bd88896..4b01afc3 100644 --- a/mythril/analysis/symbolic.py +++ b/mythril/analysis/symbolic.py @@ -1,5 +1,5 @@ from mythril.laser.ethereum import svm -from mythril.laser.ethereum.state import Account +from mythril.laser.ethereum.state.account import Account from mythril.ether.soliditycontract import SolidityContract, ETHContract import copy import logging diff --git a/mythril/laser/ethereum/call.py b/mythril/laser/ethereum/call.py index 92d5c0d5..cc567b89 100644 --- a/mythril/laser/ethereum/call.py +++ b/mythril/laser/ethereum/call.py @@ -2,7 +2,9 @@ import logging from typing import Union from z3 import simplify, ExprRef, Extract import mythril.laser.ethereum.util as util -from mythril.laser.ethereum.state import Account, CalldataType, GlobalState, Calldata +from mythril.laser.ethereum.state.account import Account +from mythril.laser.ethereum.state.calldata import CalldataType, Calldata +from mythril.laser.ethereum.state.global_state import GlobalState from mythril.support.loader import DynLoader import re diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 27f44414..51675f6e 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -39,7 +39,8 @@ from mythril.laser.ethereum.evm_exceptions import ( InvalidInstruction, ) from mythril.laser.ethereum.keccak import KeccakFunctionManager -from mythril.laser.ethereum.state import GlobalState, CalldataType, Calldata +from mythril.laser.ethereum.state.calldata import CalldataType, Calldata +from mythril.laser.ethereum.state.global_state import GlobalState from mythril.laser.ethereum.transaction import ( MessageCallTransaction, TransactionStartSignal, diff --git a/mythril/laser/ethereum/natives.py b/mythril/laser/ethereum/natives.py index 85e643e1..ed8a0c4c 100644 --- a/mythril/laser/ethereum/natives.py +++ b/mythril/laser/ethereum/natives.py @@ -8,7 +8,7 @@ from ethereum.utils import ecrecover_to_pub from py_ecc.secp256k1 import N as secp256k1n from rlp.utils import ALL_BYTES -from mythril.laser.ethereum.state import Calldata +from mythril.laser.ethereum.state.calldata import Calldata from mythril.laser.ethereum.util import bytearray_to_int, sha3, get_concrete_int from z3 import Concat, simplify diff --git a/mythril/laser/ethereum/state.py b/mythril/laser/ethereum/state.py deleted file mode 100644 index 92a5aaf2..00000000 --- a/mythril/laser/ethereum/state.py +++ /dev/null @@ -1,571 +0,0 @@ -import struct -from z3 import ( - BitVec, - BitVecVal, - BitVecRef, - BitVecSort, - ExprRef, - Concat, - sat, - simplify, - Array, - ForAll, - Implies, - UGE, - UGT, -) -from z3.z3types import Z3Exception -from mythril.disassembler.disassembly import Disassembly -from mythril.laser.ethereum.cfg import Node -from copy import copy, deepcopy -from enum import Enum -from random import randint -from typing import KeysView, Dict, List, Union, Any, Sequence -from mythril.laser.ethereum.util import get_concrete_int - -from mythril.laser.ethereum.evm_exceptions import ( - StackOverflowException, - StackUnderflowException, -) - - -class CalldataType(Enum): - CONCRETE = 1 - SYMBOLIC = 2 - - -class Calldata: - """ - Calldata class representing the calldata of a transaction - """ - - def __init__(self, tx_id, starting_calldata=None): - """ - Constructor for Calldata - :param tx_id: unique value representing the transaction the calldata is for - :param starting_calldata: byte array representing the concrete calldata of a transaction - """ - self.tx_id = tx_id - if starting_calldata is not None: - self._calldata = [] - self.calldatasize = BitVecVal(len(starting_calldata), 256) - self.concrete = True - else: - self._calldata = Array( - "{}_calldata".format(self.tx_id), BitVecSort(256), BitVecSort(8) - ) - self.calldatasize = BitVec("{}_calldatasize".format(self.tx_id), 256) - self.concrete = False - - if self.concrete: - for calldata_byte in starting_calldata: - if type(calldata_byte) == int: - self._calldata.append(BitVecVal(calldata_byte, 8)) - else: - self._calldata.append(calldata_byte) - - def concretized(self, model): - result = [] - for i in range( - get_concrete_int(model.eval(self.calldatasize, model_completion=True)) - ): - result.append( - get_concrete_int(model.eval(self._calldata[i], model_completion=True)) - ) - - return result - - def get_word_at(self, index: int): - return self[index : index + 32] - - def __getitem__(self, item: Union[int, slice]) -> Any: - if isinstance(item, slice): - start, step, stop = item.start, item.step, item.stop - try: - if start is None: - start = 0 - if step is None: - step = 1 - if stop is None: - stop = self.calldatasize - current_index = ( - start if isinstance(start, BitVecRef) else BitVecVal(start, 256) - ) - dataparts = [] - while simplify(current_index != stop): - dataparts.append(self[current_index]) - current_index = simplify(current_index + step) - except Z3Exception: - raise IndexError("Invalid Calldata Slice") - - values, constraints = zip(*dataparts) - result_constraints = [] - for c in constraints: - result_constraints.extend(c) - return simplify(Concat(values)), result_constraints - - if self.concrete: - try: - return self._calldata[get_concrete_int(item)], () - except IndexError: - return BitVecVal(0, 8), () - else: - constraints = [ - Implies(self._calldata[item] != 0, UGT(self.calldatasize, item)) - ] - - return self._calldata[item], constraints - - -class Storage: - """ - Storage class represents the storage of an Account - """ - - def __init__(self, concrete=False, address=None, dynamic_loader=None): - """ - Constructor for Storage - :param concrete: bool indicating whether to interpret uninitialized storage as concrete versus symbolic - """ - self._storage = {} - self.concrete = concrete - self.dynld = dynamic_loader - self.address = address - - def __getitem__(self, item: Union[int, slice]) -> Any: - try: - return self._storage[item] - except KeyError: - if ( - self.address - and int(self.address[2:], 16) != 0 - and (self.dynld and self.dynld.storage_loading) - ): - try: - self._storage[item] = int( - self.dynld.read_storage( - contract_address=self.address, index=int(item) - ), - 16, - ) - return self._storage[item] - except ValueError: - pass - if self.concrete: - return 0 - self._storage[item] = BitVec("storage_" + str(item), 256) - return self._storage[item] - - def __setitem__(self, key: str, value: ExprRef) -> None: - self._storage[key] = value - - def keys(self) -> KeysView: - return self._storage.keys() - - -class Account: - """ - Account class representing ethereum accounts - """ - - def __init__( - self, - address: str, - code=None, - contract_name="unknown", - balance=None, - concrete_storage=False, - dynamic_loader=None, - ): - """ - Constructor for account - :param address: Address of the account - :param code: The contract code of the account - :param contract_name: The name associated with the account - :param balance: The balance for the account - :param concrete_storage: Interpret storage as concrete - """ - self.nonce = 0 - self.code = code or Disassembly("") - self.balance = balance if balance else BitVec("balance", 256) - self.storage = Storage( - concrete_storage, address=address, dynamic_loader=dynamic_loader - ) - - # Metadata - self.address = address - self.contract_name = contract_name - - self.deleted = False - - def __str__(self) -> str: - return str(self.as_dict) - - def set_balance(self, balance: ExprRef) -> None: - self.balance = balance - - def add_balance(self, balance: ExprRef) -> None: - self.balance += balance - - @property - def as_dict(self) -> Dict: - return { - "nonce": self.nonce, - "code": self.code, - "balance": self.balance, - "storage": self.storage, - } - - -class Environment: - """ - The environment class represents the current execution environment for the symbolic executor - """ - - def __init__( - self, - active_account: Account, - sender: ExprRef, - calldata: Calldata, - gasprice: ExprRef, - callvalue: ExprRef, - origin: ExprRef, - code=None, - calldata_type=CalldataType.SYMBOLIC, - ): - # Metadata - - self.active_account = active_account - self.active_function_name = "" - - self.address = BitVecVal(int(active_account.address, 16), 256) - - # Ib - self.code = active_account.code if code is None else code - - self.sender = sender - self.calldata = calldata - self.calldata_type = calldata_type - self.gasprice = gasprice - self.origin = origin - self.callvalue = callvalue - - def __str__(self) -> str: - return str(self.as_dict) - - @property - def as_dict(self) -> Dict: - return dict( - active_account=self.active_account, - sender=self.sender, - calldata=self.calldata, - gasprice=self.gasprice, - callvalue=self.callvalue, - origin=self.origin, - calldata_type=self.calldata_type, - ) - - -class Constraints(list): - """ - This class should maintain a solver and it's constraints, This class tries to make the Constraints() object - as a simple list of constraints with some background processing. - TODO: add the solver to this class after callback refactor - """ - - def __init__(self, constraint_list=None, solver=None, possibility=None): - super(Constraints, self).__init__(constraint_list or []) - self.solver = solver - self.__possibility = possibility - - def check_possibility(self): - return True - - def append(self, constraint): - super(Constraints, self).append(constraint) - - def pop(self, index=-1): - raise NotImplementedError - - def __copy__(self): - constraint_list = super(Constraints, self).copy() - return Constraints(constraint_list) - - def __deepcopy__(self, memodict=None): - return self.__copy__() - - def __add__(self, constraints): - constraints_list = super(Constraints, self).__add__(constraints) - return Constraints(constraint_list=constraints_list) - - def __iadd__(self, constraints): - super(Constraints, self).__iadd__(constraints) - return self - - -class MachineStack(list): - """ - Defines EVM stack, overrides the default list to handle overflows - """ - - STACK_LIMIT = 1024 - - def __init__(self, default_list=None): - if default_list is None: - default_list = [] - super(MachineStack, self).__init__(default_list) - - def append(self, element: BitVec) -> None: - """ - :param element: element to be appended to the list - :function: appends the element to list if the size is less than STACK_LIMIT, else throws an error - """ - if super(MachineStack, self).__len__() >= self.STACK_LIMIT: - raise StackOverflowException( - "Reached the EVM stack limit of {}, you can't append more " - "elements".format(self.STACK_LIMIT) - ) - super(MachineStack, self).append(element) - - def pop(self, index=-1) -> BitVec: - """ - :param index:index to be popped, same as the list() class. - :returns popped value - :function: same as list() class but throws StackUnderflowException for popping from an empty list - """ - - try: - return super(MachineStack, self).pop(index) - except IndexError: - raise StackUnderflowException("Trying to pop from an empty stack") - - def __getitem__(self, item: Union[int, slice]) -> Any: - try: - return super(MachineStack, self).__getitem__(item) - except IndexError: - raise StackUnderflowException( - "Trying to access a stack element which doesn't exist" - ) - - def __add__(self, other): - """ - Implement list concatenation if needed - """ - raise NotImplementedError("Implement this if needed") - - def __iadd__(self, other): - """ - Implement list concatenation if needed - """ - raise NotImplementedError("Implement this if needed") - - -class MachineState: - """ - MachineState represents current machine state also referenced to as \mu - """ - - def __init__( - self, gas: int, pc=0, stack=None, memory=None, constraints=None, depth=0 - ): - """ Constructor for machineState """ - self.pc = pc - self.stack = MachineStack(stack) - self.memory = memory or [] - self.gas = gas - self.constraints = constraints or Constraints() - self.depth = depth - - def mem_extend(self, start: int, size: int) -> None: - """ - Extends the memory of this machine state - :param start: Start of memory extension - :param size: Size of memory extension - """ - if self.memory_size > start + size: - return - m_extend = start + size - self.memory_size - self.memory.extend(bytearray(m_extend)) - - def memory_write(self, offset: int, data: List[int]) -> None: - """ Writes data to memory starting at offset """ - self.mem_extend(offset, len(data)) - self.memory[offset : offset + len(data)] = data - - def pop(self, amount=1) -> Union[BitVec, List[BitVec]]: - """ Pops amount elements from the stack""" - if amount > len(self.stack): - raise StackUnderflowException - values = self.stack[-amount:][::-1] - del self.stack[-amount:] - - return values[0] if amount == 1 else values - - def __deepcopy__(self, memodict=None): - memodict = {} if memodict is None else memodict - return MachineState( - gas=self.gas, - pc=self.pc, - stack=copy(self.stack), - memory=copy(self.memory), - constraints=copy(self.constraints), - depth=self.depth, - ) - - def __str__(self): - return str(self.as_dict) - - @property - def memory_size(self) -> int: - return len(self.memory) - - @property - def as_dict(self) -> Dict: - return dict( - pc=self.pc, - stack=self.stack, - memory=self.memory, - memsize=self.memory_size, - gas=self.gas, - ) - - -class GlobalState: - """ - GlobalState represents the current globalstate - """ - - def __init__( - self, - world_state: "WorldState", - environment: Environment, - node: Node, - machine_state=None, - transaction_stack=None, - last_return_data=None, - ): - """ Constructor for GlobalState""" - self.node = node - self.world_state = world_state - self.environment = environment - self.mstate = machine_state if machine_state else MachineState(gas=10000000) - self.transaction_stack = transaction_stack if transaction_stack else [] - self.op_code = "" - self.last_return_data = last_return_data - - def __copy__(self) -> "GlobalState": - world_state = copy(self.world_state) - environment = copy(self.environment) - mstate = deepcopy(self.mstate) - transaction_stack = copy(self.transaction_stack) - return GlobalState( - world_state, - environment, - self.node, - mstate, - transaction_stack=transaction_stack, - last_return_data=self.last_return_data, - ) - - @property - def accounts(self) -> Dict: - return self.world_state.accounts - - # TODO: remove this, as two instructions are confusing - def get_current_instruction(self) -> Dict: - """ Gets the current instruction for this GlobalState""" - - instructions = self.environment.code.instruction_list - return instructions[self.mstate.pc] - - @property - def current_transaction( - self - ) -> Union["MessageCallTransaction", "ContractCreationTransaction", None]: - # TODO: Remove circular to transaction package to import Transaction classes - try: - return self.transaction_stack[-1][0] - except IndexError: - return None - - @property - def instruction(self) -> Dict: - return self.get_current_instruction() - - def new_bitvec(self, name: str, size=256) -> BitVec: - transaction_id = self.current_transaction.id - return BitVec("{}_{}".format(transaction_id, name), size) - - -class WorldState: - """ - The WorldState class represents the world state as described in the yellow paper - """ - - def __init__(self, transaction_sequence=None): - """ - Constructor for the world state. Initializes the accounts record - """ - self.accounts = {} - self.node = None - self.transaction_sequence = transaction_sequence or [] - - def __getitem__(self, item: str) -> Account: - """ - Gets an account from the worldstate using item as key - :param item: Address of the account to get - :return: Account associated with the address - """ - return self.accounts[item] - - def __copy__(self) -> "WorldState": - new_world_state = WorldState(transaction_sequence=self.transaction_sequence[:]) - new_world_state.accounts = copy(self.accounts) - new_world_state.node = self.node - return new_world_state - - def create_account( - self, balance=0, address=None, concrete_storage=False, dynamic_loader=None - ) -> Account: - """ - Create non-contract account - :param address: The account's address - :param balance: Initial balance for the account - :param concrete_storage: Interpret account storage as concrete - :param dynamic_loader: used for dynamically loading storage from the block chain - :return: The new account - """ - address = address if address else self._generate_new_address() - new_account = Account( - address, - balance=balance, - dynamic_loader=dynamic_loader, - concrete_storage=concrete_storage, - ) - self._put_account(new_account) - return new_account - - def create_initialized_contract_account(self, contract_code, storage) -> None: - """ - Creates a new contract account, based on the contract code and storage provided - The contract code only includes the runtime contract bytecode - :param contract_code: Runtime bytecode for the contract - :param storage: Initial storage for the contract - :return: The new account - """ - # TODO: Add type hints - new_account = Account( - self._generate_new_address(), code=contract_code, balance=0 - ) - new_account.storage = storage - self._put_account(new_account) - - def _generate_new_address(self) -> str: - """ Generates a new address for the global state""" - while True: - address = "0x" + "".join([str(hex(randint(0, 16)))[-1] for _ in range(20)]) - if address not in self.accounts.keys(): - return address - - def _put_account(self, account: Account) -> None: - self.accounts[account.address] = account diff --git a/mythril/laser/ethereum/state/__init__.py b/mythril/laser/ethereum/state/__init__.py new file mode 100644 index 00000000..9e9938db --- /dev/null +++ b/mythril/laser/ethereum/state/__init__.py @@ -0,0 +1 @@ +# Hello! diff --git a/mythril/laser/ethereum/state/account.py b/mythril/laser/ethereum/state/account.py new file mode 100644 index 00000000..6a5f11cc --- /dev/null +++ b/mythril/laser/ethereum/state/account.py @@ -0,0 +1,105 @@ +from typing import Dict, Union, Any, KeysView + +from z3 import BitVec, ExprRef + +from mythril.disassembler.disassembly import Disassembly + + +class Storage: + """ + Storage class represents the storage of an Account + """ + + def __init__(self, concrete=False, address=None, dynamic_loader=None): + """ + Constructor for Storage + :param concrete: bool indicating whether to interpret uninitialized storage as concrete versus symbolic + """ + self._storage = {} + self.concrete = concrete + self.dynld = dynamic_loader + self.address = address + + def __getitem__(self, item: Union[int, slice]) -> Any: + try: + return self._storage[item] + except KeyError: + if ( + self.address + and int(self.address[2:], 16) != 0 + and (self.dynld and self.dynld.storage_loading) + ): + try: + self._storage[item] = int( + self.dynld.read_storage( + contract_address=self.address, index=int(item) + ), + 16, + ) + return self._storage[item] + except ValueError: + pass + if self.concrete: + return 0 + self._storage[item] = BitVec("storage_" + str(item), 256) + return self._storage[item] + + def __setitem__(self, key: str, value: ExprRef) -> None: + self._storage[key] = value + + def keys(self) -> KeysView: + return self._storage.keys() + + +class Account: + """ + Account class representing ethereum accounts + """ + + def __init__( + self, + address: str, + code=None, + contract_name="unknown", + balance=None, + concrete_storage=False, + dynamic_loader=None, + ): + """ + Constructor for account + :param address: Address of the account + :param code: The contract code of the account + :param contract_name: The name associated with the account + :param balance: The balance for the account + :param concrete_storage: Interpret storage as concrete + """ + self.nonce = 0 + self.code = code or Disassembly("") + self.balance = balance if balance else BitVec("balance", 256) + self.storage = Storage( + concrete_storage, address=address, dynamic_loader=dynamic_loader + ) + + # Metadata + self.address = address + self.contract_name = contract_name + + self.deleted = False + + def __str__(self) -> str: + return str(self.as_dict) + + def set_balance(self, balance: ExprRef) -> None: + self.balance = balance + + def add_balance(self, balance: ExprRef) -> None: + self.balance += balance + + @property + def as_dict(self) -> Dict: + return { + "nonce": self.nonce, + "code": self.code, + "balance": self.balance, + "storage": self.storage, + } diff --git a/mythril/laser/ethereum/state/calldata.py b/mythril/laser/ethereum/state/calldata.py new file mode 100644 index 00000000..1fd83316 --- /dev/null +++ b/mythril/laser/ethereum/state/calldata.py @@ -0,0 +1,104 @@ +from enum import Enum +from typing import Union, Any +from z3 import ( + BitVecVal, + BitVecRef, + BitVecSort, + BitVec, + Implies, + simplify, + Concat, + UGT, + Array, +) +from z3.z3types import Z3Exception + +from mythril.laser.ethereum.util import get_concrete_int + + +class CalldataType(Enum): + CONCRETE = 1 + SYMBOLIC = 2 + + +class Calldata: + """ + Calldata class representing the calldata of a transaction + """ + + def __init__(self, tx_id, starting_calldata=None): + """ + Constructor for Calldata + :param tx_id: unique value representing the transaction the calldata is for + :param starting_calldata: byte array representing the concrete calldata of a transaction + """ + self.tx_id = tx_id + if starting_calldata is not None: + self._calldata = [] + self.calldatasize = BitVecVal(len(starting_calldata), 256) + self.concrete = True + else: + self._calldata = Array( + "{}_calldata".format(self.tx_id), BitVecSort(256), BitVecSort(8) + ) + self.calldatasize = BitVec("{}_calldatasize".format(self.tx_id), 256) + self.concrete = False + + if self.concrete: + for calldata_byte in starting_calldata: + if type(calldata_byte) == int: + self._calldata.append(BitVecVal(calldata_byte, 8)) + else: + self._calldata.append(calldata_byte) + + def concretized(self, model): + result = [] + for i in range( + get_concrete_int(model.eval(self.calldatasize, model_completion=True)) + ): + result.append( + get_concrete_int(model.eval(self._calldata[i], model_completion=True)) + ) + + return result + + def get_word_at(self, index: int): + return self[index : index + 32] + + def __getitem__(self, item: Union[int, slice]) -> Any: + if isinstance(item, slice): + start, step, stop = item.start, item.step, item.stop + try: + if start is None: + start = 0 + if step is None: + step = 1 + if stop is None: + stop = self.calldatasize + current_index = ( + start if isinstance(start, BitVecRef) else BitVecVal(start, 256) + ) + dataparts = [] + while simplify(current_index != stop): + dataparts.append(self[current_index]) + current_index = simplify(current_index + step) + except Z3Exception: + raise IndexError("Invalid Calldata Slice") + + values, constraints = zip(*dataparts) + result_constraints = [] + for c in constraints: + result_constraints.extend(c) + return simplify(Concat(values)), result_constraints + + if self.concrete: + try: + return self._calldata[get_concrete_int(item)], () + except IndexError: + return BitVecVal(0, 8), () + else: + constraints = [ + Implies(self._calldata[item] != 0, UGT(self.calldatasize, item)) + ] + + return self._calldata[item], constraints diff --git a/mythril/laser/ethereum/state/constraints.py b/mythril/laser/ethereum/state/constraints.py new file mode 100644 index 00000000..ed676521 --- /dev/null +++ b/mythril/laser/ethereum/state/constraints.py @@ -0,0 +1,35 @@ +class Constraints(list): + """ + This class should maintain a solver and it's constraints, This class tries to make the Constraints() object + as a simple list of constraints with some background processing. + TODO: add the solver to this class after callback refactor + """ + + def __init__(self, constraint_list=None, solver=None, possibility=None): + super(Constraints, self).__init__(constraint_list or []) + self.solver = solver + self.__possibility = possibility + + def check_possibility(self): + return True + + def append(self, constraint): + super(Constraints, self).append(constraint) + + def pop(self, index=-1): + raise NotImplementedError + + def __copy__(self): + constraint_list = super(Constraints, self).copy() + return Constraints(constraint_list) + + def __deepcopy__(self, memodict=None): + return self.__copy__() + + def __add__(self, constraints): + constraints_list = super(Constraints, self).__add__(constraints) + return Constraints(constraint_list=constraints_list) + + def __iadd__(self, constraints): + super(Constraints, self).__iadd__(constraints) + return self diff --git a/mythril/laser/ethereum/state/environment.py b/mythril/laser/ethereum/state/environment.py new file mode 100644 index 00000000..91c67d5a --- /dev/null +++ b/mythril/laser/ethereum/state/environment.py @@ -0,0 +1,55 @@ +from typing import Dict + +from z3 import ExprRef, BitVecVal + +from mythril.laser.ethereum.state.account import Account +from mythril.laser.ethereum.state.calldata import Calldata, CalldataType + + +class Environment: + """ + The environment class represents the current execution environment for the symbolic executor + """ + + def __init__( + self, + active_account: Account, + sender: ExprRef, + calldata: Calldata, + gasprice: ExprRef, + callvalue: ExprRef, + origin: ExprRef, + code=None, + calldata_type=CalldataType.SYMBOLIC, + ): + # Metadata + + self.active_account = active_account + self.active_function_name = "" + + self.address = BitVecVal(int(active_account.address, 16), 256) + + # Ib + self.code = active_account.code if code is None else code + + self.sender = sender + self.calldata = calldata + self.calldata_type = calldata_type + self.gasprice = gasprice + self.origin = origin + self.callvalue = callvalue + + def __str__(self) -> str: + return str(self.as_dict) + + @property + def as_dict(self) -> Dict: + return dict( + active_account=self.active_account, + sender=self.sender, + calldata=self.calldata, + gasprice=self.gasprice, + callvalue=self.callvalue, + origin=self.origin, + calldata_type=self.calldata_type, + ) diff --git a/mythril/laser/ethereum/state/global_state.py b/mythril/laser/ethereum/state/global_state.py new file mode 100644 index 00000000..5641f49a --- /dev/null +++ b/mythril/laser/ethereum/state/global_state.py @@ -0,0 +1,75 @@ +from typing import Dict, Union + +from copy import copy, deepcopy +from z3 import BitVec + +from mythril.laser.ethereum.cfg import Node +from mythril.laser.ethereum.state.environment import Environment +from mythril.laser.ethereum.state.machine_state import MachineState + + +class GlobalState: + """ + GlobalState represents the current globalstate + """ + + def __init__( + self, + world_state: "WorldState", + environment: Environment, + node: Node, + machine_state=None, + transaction_stack=None, + last_return_data=None, + ): + """ Constructor for GlobalState""" + self.node = node + self.world_state = world_state + self.environment = environment + self.mstate = machine_state if machine_state else MachineState(gas=10000000) + self.transaction_stack = transaction_stack if transaction_stack else [] + self.op_code = "" + self.last_return_data = last_return_data + + def __copy__(self) -> "GlobalState": + world_state = copy(self.world_state) + environment = copy(self.environment) + mstate = deepcopy(self.mstate) + transaction_stack = copy(self.transaction_stack) + return GlobalState( + world_state, + environment, + self.node, + mstate, + transaction_stack=transaction_stack, + last_return_data=self.last_return_data, + ) + + @property + def accounts(self) -> Dict: + return self.world_state.accounts + + # TODO: remove this, as two instructions are confusing + def get_current_instruction(self) -> Dict: + """ Gets the current instruction for this GlobalState""" + + instructions = self.environment.code.instruction_list + return instructions[self.mstate.pc] + + @property + def current_transaction( + self + ) -> Union["MessageCallTransaction", "ContractCreationTransaction", None]: + # TODO: Remove circular to transaction package to import Transaction classes + try: + return self.transaction_stack[-1][0] + except IndexError: + return None + + @property + def instruction(self) -> Dict: + return self.get_current_instruction() + + def new_bitvec(self, name: str, size=256) -> BitVec: + transaction_id = self.current_transaction.id + return BitVec("{}_{}".format(transaction_id, name), size) diff --git a/mythril/laser/ethereum/state/machine_state.py b/mythril/laser/ethereum/state/machine_state.py new file mode 100644 index 00000000..286979ea --- /dev/null +++ b/mythril/laser/ethereum/state/machine_state.py @@ -0,0 +1,137 @@ +from copy import copy +from typing import Union, Any, List, Dict + +from z3 import BitVec + +from mythril.laser.ethereum.evm_exceptions import ( + StackOverflowException, + StackUnderflowException, +) +from mythril.laser.ethereum.state.constraints import Constraints + + +class MachineStack(list): + """ + Defines EVM stack, overrides the default list to handle overflows + """ + + STACK_LIMIT = 1024 + + def __init__(self, default_list=None): + if default_list is None: + default_list = [] + super(MachineStack, self).__init__(default_list) + + def append(self, element: BitVec) -> None: + """ + :param element: element to be appended to the list + :function: appends the element to list if the size is less than STACK_LIMIT, else throws an error + """ + if super(MachineStack, self).__len__() >= self.STACK_LIMIT: + raise StackOverflowException( + "Reached the EVM stack limit of {}, you can't append more " + "elements".format(self.STACK_LIMIT) + ) + super(MachineStack, self).append(element) + + def pop(self, index=-1) -> BitVec: + """ + :param index:index to be popped, same as the list() class. + :returns popped value + :function: same as list() class but throws StackUnderflowException for popping from an empty list + """ + + try: + return super(MachineStack, self).pop(index) + except IndexError: + raise StackUnderflowException("Trying to pop from an empty stack") + + def __getitem__(self, item: Union[int, slice]) -> Any: + try: + return super(MachineStack, self).__getitem__(item) + except IndexError: + raise StackUnderflowException( + "Trying to access a stack element which doesn't exist" + ) + + def __add__(self, other): + """ + Implement list concatenation if needed + """ + raise NotImplementedError("Implement this if needed") + + def __iadd__(self, other): + """ + Implement list concatenation if needed + """ + raise NotImplementedError("Implement this if needed") + + +class MachineState: + """ + MachineState represents current machine state also referenced to as \mu + """ + + def __init__( + self, gas: int, pc=0, stack=None, memory=None, constraints=None, depth=0 + ): + """ Constructor for machineState """ + self.pc = pc + self.stack = MachineStack(stack) + self.memory = memory or [] + self.gas = gas + self.constraints = constraints or Constraints() + self.depth = depth + + def mem_extend(self, start: int, size: int) -> None: + """ + Extends the memory of this machine state + :param start: Start of memory extension + :param size: Size of memory extension + """ + if self.memory_size > start + size: + return + m_extend = start + size - self.memory_size + self.memory.extend(bytearray(m_extend)) + + def memory_write(self, offset: int, data: List[int]) -> None: + """ Writes data to memory starting at offset """ + self.mem_extend(offset, len(data)) + self.memory[offset : offset + len(data)] = data + + def pop(self, amount=1) -> Union[BitVec, List[BitVec]]: + """ Pops amount elements from the stack""" + if amount > len(self.stack): + raise StackUnderflowException + values = self.stack[-amount:][::-1] + del self.stack[-amount:] + + return values[0] if amount == 1 else values + + def __deepcopy__(self, memodict=None): + memodict = {} if memodict is None else memodict + return MachineState( + gas=self.gas, + pc=self.pc, + stack=copy(self.stack), + memory=copy(self.memory), + constraints=copy(self.constraints), + depth=self.depth, + ) + + def __str__(self): + return str(self.as_dict) + + @property + def memory_size(self) -> int: + return len(self.memory) + + @property + def as_dict(self) -> Dict: + return dict( + pc=self.pc, + stack=self.stack, + memory=self.memory, + memsize=self.memory_size, + gas=self.gas, + ) diff --git a/mythril/laser/ethereum/state/world_state.py b/mythril/laser/ethereum/state/world_state.py new file mode 100644 index 00000000..ea61d96d --- /dev/null +++ b/mythril/laser/ethereum/state/world_state.py @@ -0,0 +1,78 @@ +from copy import copy +from random import randint + +from mythril.laser.ethereum.state.account import Account + + +class WorldState: + """ + The WorldState class represents the world state as described in the yellow paper + """ + + def __init__(self, transaction_sequence=None): + """ + Constructor for the world state. Initializes the accounts record + """ + self.accounts = {} + self.node = None + self.transaction_sequence = transaction_sequence or [] + + def __getitem__(self, item: str) -> Account: + """ + Gets an account from the worldstate using item as key + :param item: Address of the account to get + :return: Account associated with the address + """ + return self.accounts[item] + + def __copy__(self) -> "WorldState": + new_world_state = WorldState(transaction_sequence=self.transaction_sequence[:]) + new_world_state.accounts = copy(self.accounts) + new_world_state.node = self.node + return new_world_state + + def create_account( + self, balance=0, address=None, concrete_storage=False, dynamic_loader=None + ) -> Account: + """ + Create non-contract account + :param address: The account's address + :param balance: Initial balance for the account + :param concrete_storage: Interpret account storage as concrete + :param dynamic_loader: used for dynamically loading storage from the block chain + :return: The new account + """ + address = address if address else self._generate_new_address() + new_account = Account( + address, + balance=balance, + dynamic_loader=dynamic_loader, + concrete_storage=concrete_storage, + ) + self._put_account(new_account) + return new_account + + def create_initialized_contract_account(self, contract_code, storage) -> None: + """ + Creates a new contract account, based on the contract code and storage provided + The contract code only includes the runtime contract bytecode + :param contract_code: Runtime bytecode for the contract + :param storage: Initial storage for the contract + :return: The new account + """ + # TODO: Add type hints + new_account = Account( + self._generate_new_address(), code=contract_code, balance=0 + ) + new_account.storage = storage + self._put_account(new_account) + + def _generate_new_address(self) -> str: + """ Generates a new address for the global state""" + while True: + address = "0x" + "".join([str(hex(randint(0, 16)))[-1] for _ in range(20)]) + if address not in self.accounts.keys(): + return address + + def _put_account(self, account: Account) -> None: + self.accounts[account.address] = account diff --git a/mythril/laser/ethereum/strategy/basic.py b/mythril/laser/ethereum/strategy/basic.py index 232ac19b..85d08824 100644 --- a/mythril/laser/ethereum/strategy/basic.py +++ b/mythril/laser/ethereum/strategy/basic.py @@ -1,7 +1,7 @@ """ This module implements basic symbolic execution search strategies """ -from ..state import GlobalState +from mythril.laser.ethereum.state.global_state import GlobalState from typing import List from random import randrange from . import BasicSearchStrategy diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index 9c0a4e36..73aa18db 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -1,7 +1,9 @@ import logging from typing import List, Tuple, Union, Callable, Dict from mythril.disassembler.disassembly import Disassembly -from mythril.laser.ethereum.state import WorldState, GlobalState +from mythril.laser.ethereum.state.account import Account +from mythril.laser.ethereum.state.world_state import WorldState +from mythril.laser.ethereum.state.global_state import GlobalState from mythril.laser.ethereum.transaction import ( TransactionStartSignal, TransactionEndSignal, @@ -10,7 +12,6 @@ from mythril.laser.ethereum.transaction import ( from mythril.laser.ethereum.evm_exceptions import StackUnderflowException from mythril.laser.ethereum.instructions import Instruction from mythril.laser.ethereum.cfg import NodeFlags, Node, Edge, JumpType -from mythril.laser.ethereum.state import Account from mythril.laser.ethereum.strategy.basic import DepthFirstSearchStrategy from datetime import datetime, timedelta from copy import copy diff --git a/mythril/laser/ethereum/taint_analysis.py b/mythril/laser/ethereum/taint_analysis.py index ff4da314..0af7f205 100644 --- a/mythril/laser/ethereum/taint_analysis.py +++ b/mythril/laser/ethereum/taint_analysis.py @@ -4,7 +4,8 @@ from typing import Union, List, Tuple from z3 import ExprRef import mythril.laser.ethereum.util as helper from mythril.laser.ethereum.cfg import JumpType, Node -from mythril.laser.ethereum.state import GlobalState, Environment +from mythril.laser.ethereum.state.environment import Environment +from mythril.laser.ethereum.state.global_state import GlobalState from mythril.analysis.symbolic import SymExecWrapper diff --git a/mythril/laser/ethereum/transaction/concolic.py b/mythril/laser/ethereum/transaction/concolic.py index db3f0116..a379f716 100644 --- a/mythril/laser/ethereum/transaction/concolic.py +++ b/mythril/laser/ethereum/transaction/concolic.py @@ -4,14 +4,11 @@ from mythril.laser.ethereum.transaction.transaction_models import ( get_next_transaction_id, ) from z3 import BitVec -from mythril.laser.ethereum.state import ( - GlobalState, - Environment, - CalldataType, - Account, - WorldState, - Calldata, -) +from mythril.laser.ethereum.state.environment import Environment +from mythril.laser.ethereum.state.calldata import Calldata, CalldataType +from mythril.laser.ethereum.state.account import Account +from mythril.laser.ethereum.state.world_state import WorldState +from mythril.laser.ethereum.state.global_state import GlobalState from mythril.disassembler.disassembly import Disassembly from mythril.laser.ethereum.cfg import Node, Edge, JumpType diff --git a/mythril/laser/ethereum/transaction/symbolic.py b/mythril/laser/ethereum/transaction/symbolic.py index de7b9477..baa4dc8e 100644 --- a/mythril/laser/ethereum/transaction/symbolic.py +++ b/mythril/laser/ethereum/transaction/symbolic.py @@ -3,7 +3,8 @@ from logging import debug from mythril.disassembler.disassembly import Disassembly from mythril.laser.ethereum.cfg import Node, Edge, JumpType -from mythril.laser.ethereum.state import CalldataType, Account, Calldata +from mythril.laser.ethereum.state.calldata import CalldataType, Calldata +from mythril.laser.ethereum.state.account import Account from mythril.laser.ethereum.transaction.transaction_models import ( MessageCallTransaction, ContractCreationTransaction, diff --git a/mythril/laser/ethereum/transaction/transaction_models.py b/mythril/laser/ethereum/transaction/transaction_models.py index 3f2e2e03..d2acb156 100644 --- a/mythril/laser/ethereum/transaction/transaction_models.py +++ b/mythril/laser/ethereum/transaction/transaction_models.py @@ -1,13 +1,11 @@ import logging from typing import Union from mythril.disassembler.disassembly import Disassembly -from mythril.laser.ethereum.state import ( - GlobalState, - Environment, - WorldState, - Account, - Calldata, -) +from mythril.laser.ethereum.state.environment import Environment +from mythril.laser.ethereum.state.calldata import Calldata +from mythril.laser.ethereum.state.account import Account +from mythril.laser.ethereum.state.world_state import WorldState +from mythril.laser.ethereum.state.global_state import GlobalState from z3 import BitVec, ExprRef import array diff --git a/tests/analysis/test_delegatecall.py b/tests/analysis/test_delegatecall.py index 40816cfb..d6fa2f2c 100644 --- a/tests/analysis/test_delegatecall.py +++ b/tests/analysis/test_delegatecall.py @@ -6,7 +6,9 @@ from mythril.analysis.modules.delegatecall import ( from mythril.analysis.ops import Call, Variable, VarType from mythril.analysis.symbolic import SymExecWrapper from mythril.laser.ethereum.cfg import Node -from mythril.laser.ethereum.state import GlobalState, Environment, Account +from mythril.laser.ethereum.state.environment import Environment +from mythril.laser.ethereum.state.account import Account +from mythril.laser.ethereum.state.global_state import GlobalState import pytest from unittest.mock import MagicMock, patch import pytest_mock @@ -181,7 +183,7 @@ def test_symbolic_call_calldata_to(mocker): ) -@patch("mythril.laser.ethereum.state.GlobalState.get_current_instruction") +@patch("mythril.laser.ethereum.state.global_state.GlobalState.get_current_instruction") @patch("mythril.analysis.modules.delegatecall._concrete_call") @patch("mythril.analysis.modules.delegatecall._symbolic_call") def test_delegate_call(sym_mock, concrete_mock, curr_instruction): diff --git a/tests/instructions/codecopy_test.py b/tests/instructions/codecopy_test.py index 8c43f2f5..0316dacd 100644 --- a/tests/instructions/codecopy_test.py +++ b/tests/instructions/codecopy_test.py @@ -1,5 +1,8 @@ from mythril.disassembler.disassembly import Disassembly -from mythril.laser.ethereum.state import MachineState, GlobalState, Environment, Account +from mythril.laser.ethereum.state.environment import Environment +from mythril.laser.ethereum.state.account import Account +from mythril.laser.ethereum.state.machine_state import MachineState +from mythril.laser.ethereum.state.global_state import GlobalState from mythril.laser.ethereum.instructions import Instruction diff --git a/tests/laser/evm_testsuite/evm_test.py b/tests/laser/evm_testsuite/evm_test.py index 7da95af5..1294a682 100644 --- a/tests/laser/evm_testsuite/evm_test.py +++ b/tests/laser/evm_testsuite/evm_test.py @@ -1,5 +1,5 @@ from mythril.laser.ethereum.svm import LaserEVM -from mythril.laser.ethereum.state import Account +from mythril.laser.ethereum.state.account import Account from mythril.disassembler.disassembly import Disassembly from mythril.laser.ethereum.transaction.concolic import execute_message_call from mythril.analysis.solver import get_model diff --git a/tests/laser/state/calldata_test.py b/tests/laser/state/calldata_test.py index e93799c3..9bce4a50 100644 --- a/tests/laser/state/calldata_test.py +++ b/tests/laser/state/calldata_test.py @@ -1,5 +1,5 @@ import pytest -from mythril.laser.ethereum.state import Calldata +from mythril.laser.ethereum.state.calldata import Calldata from z3 import Solver, simplify from z3.z3types import Z3Exception from mock import MagicMock diff --git a/tests/laser/state/mstack_test.py b/tests/laser/state/mstack_test.py index 03787075..2a3cfb01 100644 --- a/tests/laser/state/mstack_test.py +++ b/tests/laser/state/mstack_test.py @@ -1,6 +1,6 @@ import pytest -from mythril.laser.ethereum.state import MachineStack +from mythril.laser.ethereum.state.machine_state import MachineStack from mythril.laser.ethereum.evm_exceptions import * from tests import BaseTestCase diff --git a/tests/laser/state/mstate_test.py b/tests/laser/state/mstate_test.py index bdf0280f..8a7bb29b 100644 --- a/tests/laser/state/mstate_test.py +++ b/tests/laser/state/mstate_test.py @@ -1,5 +1,5 @@ import pytest -from mythril.laser.ethereum.state import MachineState +from mythril.laser.ethereum.state.machine_state import MachineState from mythril.laser.ethereum.evm_exceptions import StackUnderflowException memory_extension_test_data = [(0, 0, 10), (0, 30, 10), (100, 22, 8)] diff --git a/tests/laser/state/storage_test.py b/tests/laser/state/storage_test.py index 55e8f75b..8e33af66 100644 --- a/tests/laser/state/storage_test.py +++ b/tests/laser/state/storage_test.py @@ -1,5 +1,5 @@ import pytest -from mythril.laser.ethereum.state import Storage +from mythril.laser.ethereum.state.account import Storage from z3 import ExprRef storage_uninitialized_test_data = [({}, 1), ({1: 5}, 2), ({1: 5, 3: 10}, 2)] diff --git a/tests/laser/test_transaction.py b/tests/laser/test_transaction.py index 1aca7090..8e9ac415 100644 --- a/tests/laser/test_transaction.py +++ b/tests/laser/test_transaction.py @@ -1,6 +1,6 @@ from mythril.disassembler.disassembly import Disassembly from mythril.laser.ethereum import svm -from mythril.laser.ethereum.state import Account +from mythril.laser.ethereum.state.account import Account import mythril.laser.ethereum.cfg as cfg diff --git a/tests/laser/transaction/symbolic_test.py b/tests/laser/transaction/symbolic_test.py index 2ca12f63..f3939528 100644 --- a/tests/laser/transaction/symbolic_test.py +++ b/tests/laser/transaction/symbolic_test.py @@ -7,7 +7,8 @@ from mythril.laser.ethereum.transaction import ( ContractCreationTransaction, ) from mythril.laser.ethereum.svm import LaserEVM -from mythril.laser.ethereum.state import WorldState, Account +from mythril.laser.ethereum.state.account import Account +from mythril.laser.ethereum.state.world_state import WorldState import unittest.mock as mock from unittest.mock import MagicMock diff --git a/tests/native_test.py b/tests/native_test.py index de96a181..74f7344b 100644 --- a/tests/native_test.py +++ b/tests/native_test.py @@ -1,7 +1,9 @@ import json from mythril.ether.soliditycontract import SolidityContract -from mythril.laser.ethereum.state import GlobalState, MachineState, Account +from mythril.laser.ethereum.state.account import Account +from mythril.laser.ethereum.state.machine_state import MachineState +from mythril.laser.ethereum.state.global_state import GlobalState from mythril.laser.ethereum import svm from tests import * diff --git a/tests/svm_test.py b/tests/svm_test.py index 7d23832a..d0509da2 100644 --- a/tests/svm_test.py +++ b/tests/svm_test.py @@ -4,7 +4,9 @@ from mythril.analysis.callgraph import generate_graph from mythril.ether.ethcontract import ETHContract from mythril.ether.soliditycontract import SolidityContract -from mythril.laser.ethereum.state import GlobalState, MachineState, Account +from mythril.laser.ethereum.state.account import Account +from mythril.laser.ethereum.state.machine_state import MachineState +from mythril.laser.ethereum.state.global_state import GlobalState from mythril.laser.ethereum import svm from tests import * diff --git a/tests/taint_result_test.py b/tests/taint_result_test.py index c692ea80..6670f228 100644 --- a/tests/taint_result_test.py +++ b/tests/taint_result_test.py @@ -1,5 +1,5 @@ from mythril.laser.ethereum.taint_analysis import * -from mythril.laser.ethereum.state import GlobalState +from mythril.laser.ethereum.state.global_state import GlobalState def test_result_state(): diff --git a/tests/taint_runner_test.py b/tests/taint_runner_test.py index 51208bc1..82c523d9 100644 --- a/tests/taint_runner_test.py +++ b/tests/taint_runner_test.py @@ -3,7 +3,10 @@ import pytest from pytest_mock import mocker from mythril.laser.ethereum.taint_analysis import * from mythril.laser.ethereum.cfg import Node, Edge -from mythril.laser.ethereum.state import MachineState, Account, Environment, GlobalState +from mythril.laser.ethereum.state.account import Account +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.svm import LaserEVM From 1fdd07a254483a0d02df631ef6b1f511405727c0 Mon Sep 17 00:00:00 2001 From: Dominik Muhs Date: Thu, 15 Nov 2018 12:54:41 +0100 Subject: [PATCH 2/5] Add gas simulation to LASER (#660) --- mythril/analysis/modules/delegatecall.py | 2 + .../modules/dependence_on_predictable_vars.py | 12 + mythril/analysis/modules/deprecated_ops.py | 1 + mythril/analysis/modules/ether_thief.py | 1 + mythril/analysis/modules/exceptions.py | 4 + mythril/analysis/modules/external_calls.py | 6 + mythril/analysis/modules/integer.py | 2 + mythril/analysis/modules/multiple_sends.py | 4 + mythril/analysis/modules/suicide.py | 1 + .../modules/transaction_order_dependence.py | 4 + mythril/analysis/modules/unchecked_retval.py | 5 + mythril/analysis/report.py | 4 + .../templates/report_as_markdown.jinja2 | 1 + .../analysis/templates/report_as_text.jinja2 | 1 + mythril/laser/ethereum/evm_exceptions.py | 4 + mythril/laser/ethereum/gas.py | 175 ++++++++++ mythril/laser/ethereum/instructions.py | 143 +++++--- mythril/laser/ethereum/state/global_state.py | 4 +- mythril/laser/ethereum/state/machine_state.py | 56 ++- mythril/laser/ethereum/svm.py | 13 +- .../laser/ethereum/transaction/concolic.py | 21 +- .../laser/ethereum/transaction/symbolic.py | 30 +- .../transaction/transaction_models.py | 145 +++----- tests/instructions/codecopy_test.py | 7 +- ...w.json => calldatacopyUnderFlowerror.json} | 2 +- .../VMTests/vmPushDupSwapTest/push33.json | 2 +- .../{sha3_3.json => sha3_3oog.json} | 4 +- .../{sha3_4.json => sha3_4oog.json} | 4 +- .../{sha3_5.json => sha3_5oog.json} | 4 +- .../{sha3_6.json => sha3_6oog.json} | 4 +- .../VMTests/vmSha3Test/sha3_bigOffset2.json | 2 +- ..._bigOffset.json => sha3_bigOffsetoog.json} | 4 +- ...sha3_bigSize.json => sha3_bigSizeoog.json} | 4 +- tests/laser/evm_testsuite/evm_test.py | 104 ++++-- tests/laser/state/mstate_test.py | 12 +- tests/taint_runner_test.py | 6 +- .../outputs_expected/calls.sol.o.json | 127 ++++++- .../outputs_expected/calls.sol.o.markdown | 10 + .../outputs_expected/calls.sol.o.text | 10 + .../outputs_expected/environments.sol.o.json | 37 +- .../outputs_expected/ether_send.sol.o.json | 31 +- .../ether_send.sol.o.markdown | 2 + .../outputs_expected/ether_send.sol.o.text | 2 + .../outputs_expected/exceptions.sol.o.json | 55 ++- .../exceptions.sol.o.markdown | 4 + .../outputs_expected/exceptions.sol.o.text | 4 + .../kinds_of_calls.sol.o.json | 55 ++- .../kinds_of_calls.sol.o.markdown | 4 + .../kinds_of_calls.sol.o.text | 4 + .../outputs_expected/metacoin.sol.o.json | 6 +- .../multi_contracts.sol.o.json | 19 +- .../multi_contracts.sol.o.markdown | 1 + .../multi_contracts.sol.o.text | 1 + .../outputs_expected/nonascii.sol.o.json | 6 +- .../outputs_expected/origin.sol.o.json | 19 +- .../outputs_expected/origin.sol.o.markdown | 1 + .../outputs_expected/origin.sol.o.text | 1 + .../outputs_expected/overflow.sol.o.json | 43 ++- .../outputs_expected/overflow.sol.o.markdown | 3 + .../outputs_expected/overflow.sol.o.text | 3 + .../outputs_expected/returnvalue.sol.o.json | 43 ++- .../returnvalue.sol.o.markdown | 3 + .../outputs_expected/returnvalue.sol.o.text | 3 + .../outputs_expected/rubixi.sol.o.json | 330 +++++++++--------- .../outputs_expected/suicide.sol.o.json | 19 +- .../outputs_expected/suicide.sol.o.markdown | 1 + .../outputs_expected/suicide.sol.o.text | 1 + .../outputs_expected/underflow.sol.o.json | 43 ++- .../outputs_expected/underflow.sol.o.markdown | 3 + .../outputs_expected/underflow.sol.o.text | 3 + .../outputs_expected/weak_random.sol.o.json | 90 ++--- 71 files changed, 1334 insertions(+), 451 deletions(-) create mode 100644 mythril/laser/ethereum/gas.py rename tests/laser/evm_testsuite/VMTests/vmEnvironmentalInfo/{calldatacopyUnderFlow.json => calldatacopyUnderFlowerror.json} (97%) rename tests/laser/evm_testsuite/VMTests/vmSha3Test/{sha3_3.json => sha3_3oog.json} (98%) rename tests/laser/evm_testsuite/VMTests/vmSha3Test/{sha3_4.json => sha3_4oog.json} (98%) rename tests/laser/evm_testsuite/VMTests/vmSha3Test/{sha3_5.json => sha3_5oog.json} (98%) rename tests/laser/evm_testsuite/VMTests/vmSha3Test/{sha3_6.json => sha3_6oog.json} (98%) rename tests/laser/evm_testsuite/VMTests/vmSha3Test/{sha3_bigOffset.json => sha3_bigOffsetoog.json} (98%) rename tests/laser/evm_testsuite/VMTests/vmSha3Test/{sha3_bigSize.json => sha3_bigSizeoog.json} (98%) diff --git a/mythril/analysis/modules/delegatecall.py b/mythril/analysis/modules/delegatecall.py index 30b850ef..728a0451 100644 --- a/mythril/analysis/modules/delegatecall.py +++ b/mythril/analysis/modules/delegatecall.py @@ -52,6 +52,7 @@ def _concrete_call(call, state, address, meminstart): bytecode=state.environment.code.bytecode, title="Call data forwarded with delegatecall()", _type="Informational", + gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), ) issue.description = ( @@ -74,6 +75,7 @@ def _symbolic_call(call, state, address, statespace): swc_id=DELEGATECALL_TO_UNTRUSTED_CONTRACT, bytecode=state.environment.code.bytecode, title=call.type + " to a user-supplied address", + gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), ) if "calldata" in str(call.to): diff --git a/mythril/analysis/modules/dependence_on_predictable_vars.py b/mythril/analysis/modules/dependence_on_predictable_vars.py index 3ec5efa8..96faac22 100644 --- a/mythril/analysis/modules/dependence_on_predictable_vars.py +++ b/mythril/analysis/modules/dependence_on_predictable_vars.py @@ -70,6 +70,10 @@ def execute(statespace): title="Dependence on predictable environment variable", _type="Warning", description=description, + gas_used=( + call.state.mstate.min_gas_used, + call.state.mstate.max_gas_used, + ), ) issues.append(issue) @@ -119,6 +123,10 @@ def execute(statespace): _type="Warning", description=description, swc_id=PREDICTABLE_VARS_DEPENDENCE, + gas_used=( + call.state.mstate.min_gas_used, + call.state.mstate.max_gas_used, + ), ) issues.append(issue) break @@ -149,6 +157,10 @@ def execute(statespace): _type="Informational", description=description, swc_id=PREDICTABLE_VARS_DEPENDENCE, + gas_used=( + call.state.mstate.min_gas_used, + call.state.mstate.max_gas_used, + ), ) issues.append(issue) break diff --git a/mythril/analysis/modules/deprecated_ops.py b/mythril/analysis/modules/deprecated_ops.py index a5ffea2f..b090770f 100644 --- a/mythril/analysis/modules/deprecated_ops.py +++ b/mythril/analysis/modules/deprecated_ops.py @@ -41,6 +41,7 @@ def execute(statespace): _type="Warning", swc_id=TX_ORIGIN_USAGE, description=description, + gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), ) issues.append(issue) diff --git a/mythril/analysis/modules/ether_thief.py b/mythril/analysis/modules/ether_thief.py index 40d7f00e..0eea01ff 100644 --- a/mythril/analysis/modules/ether_thief.py +++ b/mythril/analysis/modules/ether_thief.py @@ -85,6 +85,7 @@ def _analyze_state(state, node): description="Users other than the contract creator can withdraw ETH from the contract account" + " without previously having sent any ETH to it. This is likely to be vulnerability.", debug=debug, + gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), ) issues.append(issue) except UnsatError: diff --git a/mythril/analysis/modules/exceptions.py b/mythril/analysis/modules/exceptions.py index af07a162..ad2ad495 100644 --- a/mythril/analysis/modules/exceptions.py +++ b/mythril/analysis/modules/exceptions.py @@ -55,6 +55,10 @@ def execute(statespace): description=description, bytecode=state.environment.code.bytecode, debug=debug, + gas_used=( + state.mstate.min_gas_used, + state.mstate.max_gas_used, + ), ) ) diff --git a/mythril/analysis/modules/external_calls.py b/mythril/analysis/modules/external_calls.py index 00a79ca8..879cdbc2 100644 --- a/mythril/analysis/modules/external_calls.py +++ b/mythril/analysis/modules/external_calls.py @@ -126,6 +126,7 @@ def execute(statespace): description=description, bytecode=state.environment.code.bytecode, swc_id=REENTRANCY, + gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), ) else: @@ -141,6 +142,7 @@ def execute(statespace): description=description, bytecode=state.environment.code.bytecode, swc_id=REENTRANCY, + gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), ) issues.append(issue) @@ -186,6 +188,10 @@ def execute(statespace): description=description, bytecode=state.environment.code.bytecode, swc_id=REENTRANCY, + gas_used=( + state.mstate.min_gas_used, + state.mstate.max_gas_used, + ), ) issues.append(issue) diff --git a/mythril/analysis/modules/integer.py b/mythril/analysis/modules/integer.py index c493d249..0144baa6 100644 --- a/mythril/analysis/modules/integer.py +++ b/mythril/analysis/modules/integer.py @@ -102,6 +102,7 @@ def _check_integer_overflow(statespace, state, node): bytecode=state.environment.code.bytecode, title="Integer Overflow", _type="Warning", + gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), ) issue.description = "This binary {} operation can result in integer overflow.\n".format( @@ -215,6 +216,7 @@ def _check_integer_underflow(statespace, state, node): bytecode=state.environment.code.bytecode, title="Integer Underflow", _type="Warning", + gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), ) issue.description = ( diff --git a/mythril/analysis/modules/multiple_sends.py b/mythril/analysis/modules/multiple_sends.py index e26ddb90..ec8b0fa9 100644 --- a/mythril/analysis/modules/multiple_sends.py +++ b/mythril/analysis/modules/multiple_sends.py @@ -30,6 +30,10 @@ def execute(statespace): bytecode=call.state.environment.code.bytecode, title="Multiple Calls", _type="Informational", + gas_used=( + call.state.mstate.min_gas_used, + call.state.mstate.max_gas_used, + ), ) issue.description = ( diff --git a/mythril/analysis/modules/suicide.py b/mythril/analysis/modules/suicide.py index f147cf91..b8dbf343 100644 --- a/mythril/analysis/modules/suicide.py +++ b/mythril/analysis/modules/suicide.py @@ -78,6 +78,7 @@ def _analyze_state(state, node): _type="Warning", description=description, debug=debug, + gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), ) issues.append(issue) except UnsatError: diff --git a/mythril/analysis/modules/transaction_order_dependence.py b/mythril/analysis/modules/transaction_order_dependence.py index 707799b4..768bdc22 100644 --- a/mythril/analysis/modules/transaction_order_dependence.py +++ b/mythril/analysis/modules/transaction_order_dependence.py @@ -41,6 +41,10 @@ def execute(statespace): bytecode=call.state.environment.code.bytecode, swc_id=TX_ORDER_DEPENDENCE, _type="Warning", + gas_used=( + call.state.mstate.min_gas_used, + call.state.mstate.max_gas_used, + ), ) issue.description = ( diff --git a/mythril/analysis/modules/unchecked_retval.py b/mythril/analysis/modules/unchecked_retval.py index 3cbd800a..78e34028 100644 --- a/mythril/analysis/modules/unchecked_retval.py +++ b/mythril/analysis/modules/unchecked_retval.py @@ -59,6 +59,7 @@ def execute(statespace): bytecode=state.environment.code.bytecode, title="Unchecked CALL return value", swc_id=UNCHECKED_RET_VAL, + gas_used=(state.mstate.min_gas_used, state.mstate.max_gas_used), ) issue.description = ( @@ -108,6 +109,10 @@ def execute(statespace): address=address, title="Unchecked CALL return value", swc_id=UNCHECKED_RET_VAL, + gas_used=( + state.mstate.min_gas_used, + state.mstate.max_gas_used, + ), ) issue.description = ( diff --git a/mythril/analysis/report.py b/mythril/analysis/report.py index 93f13fe9..97ca0790 100644 --- a/mythril/analysis/report.py +++ b/mythril/analysis/report.py @@ -15,6 +15,7 @@ class Issue: swc_id, title, bytecode, + gas_used=(None, None), _type="Informational", description="", debug="", @@ -28,6 +29,7 @@ class Issue: self.type = _type self.debug = debug self.swc_id = swc_id + self.min_gas_used, self.max_gas_used = gas_used self.filename = None self.code = None self.lineno = None @@ -54,6 +56,8 @@ class Issue: "type": self.type, "address": self.address, "debug": self.debug, + "min_gas_used": self.min_gas_used, + "max_gas_used": self.max_gas_used, } if self.filename and self.lineno: diff --git a/mythril/analysis/templates/report_as_markdown.jinja2 b/mythril/analysis/templates/report_as_markdown.jinja2 index 8035bd4f..b3efd687 100644 --- a/mythril/analysis/templates/report_as_markdown.jinja2 +++ b/mythril/analysis/templates/report_as_markdown.jinja2 @@ -8,6 +8,7 @@ - Contract: {{ issue.contract | default("Unknown") }} - Function name: `{{ issue.function }}` - PC address: {{ issue.address }} +- Estimated Gas Usage: {{ issue.min_gas_used }} - {{ issue.max_gas_used }} ### Description diff --git a/mythril/analysis/templates/report_as_text.jinja2 b/mythril/analysis/templates/report_as_text.jinja2 index 6c4d77bc..05adda1b 100644 --- a/mythril/analysis/templates/report_as_text.jinja2 +++ b/mythril/analysis/templates/report_as_text.jinja2 @@ -6,6 +6,7 @@ Type: {{ issue.type }} Contract: {{ issue.contract | default("Unknown") }} Function name: {{ issue.function }} PC address: {{ issue.address }} +Estimated Gas Usage: {{ issue.min_gas_used }} - {{ issue.max_gas_used }} {{ issue.description }} -------------------- {% if issue.filename and issue.lineno %} diff --git a/mythril/laser/ethereum/evm_exceptions.py b/mythril/laser/ethereum/evm_exceptions.py index 80223762..0201dac3 100644 --- a/mythril/laser/ethereum/evm_exceptions.py +++ b/mythril/laser/ethereum/evm_exceptions.py @@ -16,3 +16,7 @@ class InvalidJumpDestination(VmException): class InvalidInstruction(VmException): pass + + +class OutOfGasException(VmException): + pass diff --git a/mythril/laser/ethereum/gas.py b/mythril/laser/ethereum/gas.py new file mode 100644 index 00000000..83456fe5 --- /dev/null +++ b/mythril/laser/ethereum/gas.py @@ -0,0 +1,175 @@ +from ethereum import opcodes +from ethereum.utils import ceil32 + + +def calculate_native_gas(size: int, contract: str): + 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): + 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), + "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 + "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), + "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), +} diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 51675f6e..477af48c 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -37,7 +37,9 @@ from mythril.laser.ethereum.evm_exceptions import ( StackUnderflowException, InvalidJumpDestination, InvalidInstruction, + OutOfGasException, ) +from mythril.laser.ethereum.gas import OPCODE_GAS from mythril.laser.ethereum.keccak import KeccakFunctionManager from mythril.laser.ethereum.state.calldata import CalldataType, Calldata from mythril.laser.ethereum.state.global_state import GlobalState @@ -63,8 +65,9 @@ class StateTransition(object): incremented if `increment_pc=True`. """ - def __init__(self, increment_pc=True): + def __init__(self, increment_pc=True, enable_gas=True): self.increment_pc = increment_pc + self.enable_gas = enable_gas @staticmethod def call_on_state_copy(func: Callable, func_obj: "Instruction", state: GlobalState): @@ -77,11 +80,32 @@ class StateTransition(object): state.mstate.pc += 1 return states + @staticmethod + def check_gas_usage_limit(global_state: GlobalState): + global_state.mstate.check_gas() + if ( + global_state.mstate.min_gas_used + >= global_state.current_transaction.gas_limit + ): + raise OutOfGasException() + + def accumulate_gas(self, global_state: GlobalState): + if not self.enable_gas: + return global_state + opcode = global_state.instruction["opcode"] + min_gas, max_gas = OPCODE_GAS[opcode] + global_state.mstate.min_gas_used += min_gas + global_state.mstate.max_gas_used += max_gas + return global_state + def __call__(self, func: Callable) -> Callable: def wrapper( func_obj: "Instruction", global_state: GlobalState ) -> List[GlobalState]: new_global_states = self.call_on_state_copy(func, func_obj, global_state) + new_global_states = [ + self.accumulate_gas(state) for state in new_global_states + ] return self.increment_states_pc(new_global_states) return wrapper @@ -94,7 +118,7 @@ class Instruction: def __init__(self, op_code: str, dynamic_loader: DynLoader): self.dynamic_loader = dynamic_loader - self.op_code = op_code + self.op_code = op_code.upper() def evaluate(self, global_state: GlobalState, post=False) -> List[GlobalState]: """ Performs the mutation for this instruction """ @@ -326,6 +350,7 @@ class Instruction: ) ) else: + state.stack.append(pow(base.as_long(), exponent.as_long(), 2 ** 256)) return [global_state] @@ -570,7 +595,7 @@ class Instruction: state.stack.append(len(disassembly.bytecode) // 2) return [global_state] - @StateTransition() + @StateTransition(enable_gas=False) def sha3_(self, global_state: GlobalState) -> List[GlobalState]: global keccak_function_manager @@ -584,8 +609,15 @@ class Instruction: if is_expr(op0): op0 = simplify(op0) state.stack.append(BitVec("KECCAC_mem[" + str(op0) + "]", 256)) + state.min_gas_used += OPCODE_GAS["SHA3"][0] + state.max_gas_used += OPCODE_GAS["SHA3"][1] return [global_state] + min_gas, max_gas = OPCODE_GAS["SHA3_FUNC"](length) + state.min_gas_used += min_gas + state.max_gas_used += max_gas + StateTransition.check_gas_usage_limit(global_state) + try: state.mem_extend(index, length) data = b"".join( @@ -629,8 +661,9 @@ class Instruction: return [global_state] try: - concrete_size = helper.get_concrete_int(size) - global_state.mstate.mem_extend(concrete_memory_offset, concrete_size) + size = helper.get_concrete_int(size) + global_state.mstate.mem_extend(concrete_memory_offset, size) + except TypeError: # except both attribute error and Exception global_state.mstate.mem_extend(concrete_memory_offset, 1) @@ -648,8 +681,8 @@ class Instruction: concrete_code_offset = helper.get_concrete_int(code_offset) except TypeError: logging.debug("Unsupported symbolic code offset in CODECOPY") - global_state.mstate.mem_extend(concrete_memory_offset, concrete_size) - for i in range(concrete_size): + global_state.mstate.mem_extend(concrete_memory_offset, size) + for i in range(size): global_state.mstate.memory[ concrete_memory_offset + i ] = global_state.new_bitvec( @@ -662,7 +695,7 @@ class Instruction: bytecode = global_state.environment.code.bytecode - if concrete_size == 0 and isinstance( + if size == 0 and isinstance( global_state.current_transaction, ContractCreationTransaction ): if concrete_code_offset >= len(global_state.environment.code.bytecode) // 2: @@ -677,7 +710,7 @@ class Instruction: ) return [global_state] - for i in range(concrete_size): + for i in range(size): if 2 * (concrete_code_offset + i + 1) <= len(bytecode): global_state.mstate.memory[concrete_memory_offset + i] = int( bytecode[ @@ -731,6 +764,7 @@ class Instruction: state = global_state.mstate addr = state.stack.pop() start, s2, size = state.stack.pop(), state.stack.pop(), state.stack.pop() + return [global_state] @StateTransition() @@ -771,7 +805,7 @@ class Instruction: @StateTransition() def gaslimit_(self, global_state: GlobalState) -> List[GlobalState]: - global_state.mstate.stack.append(global_state.new_bitvec("block_gaslimit", 256)) + global_state.mstate.stack.append(global_state.mstate.gas_limit) return [global_state] # Memory operations @@ -990,7 +1024,7 @@ class Instruction: return [global_state] - @StateTransition(increment_pc=False) + @StateTransition(increment_pc=False, enable_gas=False) def jump_(self, global_state: GlobalState) -> List[GlobalState]: state = global_state.mstate disassembly = global_state.environment.code @@ -1013,15 +1047,22 @@ class Instruction: ) new_state = copy(global_state) + # add JUMP gas cost + min_gas, max_gas = OPCODE_GAS["JUMP"] + new_state.mstate.min_gas_used += min_gas + new_state.mstate.max_gas_used += max_gas + + # manually set PC to destination new_state.mstate.pc = index new_state.mstate.depth += 1 return [new_state] - @StateTransition(increment_pc=False) + @StateTransition(increment_pc=False, enable_gas=False) def jumpi_(self, global_state: GlobalState) -> List[GlobalState]: state = global_state.mstate disassembly = global_state.environment.code + min_gas, max_gas = OPCODE_GAS["JUMPI"] states = [] op0, condition = state.stack.pop(), state.stack.pop() @@ -1031,6 +1072,8 @@ class Instruction: except TypeError: logging.debug("Skipping JUMPI to invalid destination.") global_state.mstate.pc += 1 + global_state.mstate.min_gas_used += min_gas + global_state.mstate.max_gas_used += max_gas return [global_state] # False case @@ -1042,6 +1085,11 @@ class Instruction: type(negated) == BoolRef and not is_false(negated) ): new_state = copy(global_state) + # add JUMPI gas cost + new_state.mstate.min_gas_used += min_gas + new_state.mstate.max_gas_used += max_gas + + # manually increment PC new_state.mstate.depth += 1 new_state.mstate.pc += 1 new_state.mstate.constraints.append(negated) @@ -1065,6 +1113,11 @@ class Instruction: type(condi) == BoolRef and not is_false(condi) ): new_state = copy(global_state) + # add JUMPI gas cost + new_state.mstate.min_gas_used += min_gas + new_state.mstate.max_gas_used += max_gas + + # manually set PC to destination new_state.mstate.pc = index new_state.mstate.depth += 1 new_state.mstate.constraints.append(condi) @@ -1095,7 +1148,7 @@ class Instruction: state = global_state.mstate dpth = int(self.op_code[3:]) state.stack.pop(), state.stack.pop() - [state.stack.pop() for _ in range(dpth)] + log_data = [state.stack.pop() for _ in range(dpth)] # Not supported return [global_state] @@ -1124,7 +1177,7 @@ class Instruction: @StateTransition() def suicide_(self, global_state: GlobalState): target = global_state.mstate.stack.pop() - + account_created = False # Often the target of the suicide instruction will be symbolic # If it isn't then well transfer the balance to the indicated contract if isinstance(target, BitVecNumRef): @@ -1139,10 +1192,10 @@ class Instruction: address=target, balance=global_state.environment.active_account.balance, ) + account_created = True global_state.environment.active_account.balance = 0 global_state.environment.active_account.deleted = True - global_state.current_transaction.end(global_state) @StateTransition() @@ -1211,12 +1264,18 @@ class Instruction: logging.debug("CALL with symbolic start or offset not supported") return [global_state] - global_state.mstate.mem_extend(mem_out_start, mem_out_sz) + contract_list = ["ecrecover", "sha256", "ripemd160", "identity"] call_address_int = int(callee_address, 16) + native_gas_min, native_gas_max = OPCODE_GAS["NATIVE_COST"]( + global_state.mstate.calculate_extension_size(mem_out_start, mem_out_sz), + contract_list[call_address_int - 1], + ) + global_state.mstate.min_gas_used += native_gas_min + global_state.mstate.max_gas_used += native_gas_max + global_state.mstate.mem_extend(mem_out_start, mem_out_sz) try: data = natives.native_contracts(call_address_int, call_data) except natives.NativeContractException: - contract_list = ["ecerecover", "sha256", "ripemd160", "identity"] for i in range(mem_out_sz): global_state.mstate.memory[ mem_out_start + i @@ -1227,7 +1286,6 @@ class Instruction: + ")", 256, ) - return [global_state] for i in range( @@ -1239,14 +1297,15 @@ class Instruction: return [global_state] transaction = MessageCallTransaction( - global_state.world_state, - callee_account, - BitVecVal(int(environment.active_account.address, 16), 256), - call_data=call_data, + world_state=global_state.world_state, gas_price=environment.gasprice, - call_value=value, + gas_limit=gas, origin=environment.origin, + caller=BitVecVal(int(environment.active_account.address, 16), 256), + callee_account=callee_account, + call_data=call_data, call_data_type=call_data_type, + call_value=value, ) raise TransactionStartSignal(transaction, self.op_code) @@ -1255,7 +1314,7 @@ class Instruction: instr = global_state.get_current_instruction() try: - _, _, _, _, _, _, memory_out_offset, memory_out_size = get_call_parameters( + callee_address, _, _, value, _, _, memory_out_offset, memory_out_size = get_call_parameters( global_state, self.dynamic_loader, True ) except ValueError as e: @@ -1333,15 +1392,16 @@ class Instruction: return [global_state] transaction = MessageCallTransaction( - global_state.world_state, - environment.active_account, - environment.address, - call_data=call_data, + world_state=global_state.world_state, gas_price=environment.gasprice, - call_value=value, + gas_limit=gas, origin=environment.origin, - call_data_type=call_data_type, code=callee_account.code, + caller=environment.address, + callee_account=environment.active_account, + call_data=call_data, + call_data_type=call_data_type, + call_value=value, ) raise TransactionStartSignal(transaction, self.op_code) @@ -1350,7 +1410,7 @@ class Instruction: instr = global_state.get_current_instruction() try: - _, _, _, _, _, _, memory_out_offset, memory_out_size = get_call_parameters( + callee_address, _, _, value, _, _, memory_out_offset, memory_out_size = get_call_parameters( global_state, self.dynamic_loader, True ) except ValueError as e: @@ -1371,7 +1431,6 @@ class Instruction: ) global_state.mstate.stack.append(return_value) global_state.mstate.constraints.append(return_value == 0) - return [global_state] try: @@ -1404,7 +1463,6 @@ class Instruction: return_value = global_state.new_bitvec("retval_" + str(instr["address"]), 256) global_state.mstate.stack.append(return_value) global_state.mstate.constraints.append(return_value == 1) - return [global_state] @StateTransition() @@ -1413,7 +1471,7 @@ class Instruction: environment = global_state.environment try: - callee_address, callee_account, call_data, _, call_data_type, gas, _, _ = get_call_parameters( + callee_address, callee_account, call_data, value, call_data_type, gas, _, _ = get_call_parameters( global_state, self.dynamic_loader ) except ValueError as e: @@ -1428,15 +1486,16 @@ class Instruction: return [global_state] transaction = MessageCallTransaction( - global_state.world_state, - environment.active_account, - environment.sender, - call_data, + world_state=global_state.world_state, gas_price=environment.gasprice, - call_value=environment.callvalue, + gas_limit=gas, origin=environment.origin, - call_data_type=call_data_type, code=callee_account.code, + caller=environment.sender, + callee_account=environment.active_account, + call_data=call_data, + call_data_type=call_data_type, + call_value=environment.callvalue, ) raise TransactionStartSignal(transaction, self.op_code) @@ -1445,7 +1504,7 @@ class Instruction: instr = global_state.get_current_instruction() try: - _, _, _, _, _, _, memory_out_offset, memory_out_size = get_call_parameters( + callee_address, _, _, value, _, _, memory_out_offset, memory_out_size = get_call_parameters( global_state, self.dynamic_loader ) except ValueError as e: @@ -1466,7 +1525,6 @@ class Instruction: ) global_state.mstate.stack.append(return_value) global_state.mstate.constraints.append(return_value == 0) - return [global_state] try: @@ -1499,7 +1557,6 @@ class Instruction: return_value = global_state.new_bitvec("retval_" + str(instr["address"]), 256) global_state.mstate.stack.append(return_value) global_state.mstate.constraints.append(return_value == 1) - return [global_state] @StateTransition() diff --git a/mythril/laser/ethereum/state/global_state.py b/mythril/laser/ethereum/state/global_state.py index 5641f49a..b8dad5d7 100644 --- a/mythril/laser/ethereum/state/global_state.py +++ b/mythril/laser/ethereum/state/global_state.py @@ -26,7 +26,9 @@ class GlobalState: self.node = node self.world_state = world_state self.environment = environment - self.mstate = machine_state if machine_state else MachineState(gas=10000000) + self.mstate = ( + machine_state if machine_state else MachineState(gas_limit=1000000000) + ) self.transaction_stack = transaction_stack if transaction_stack else [] self.op_code = "" self.last_return_data = last_return_data diff --git a/mythril/laser/ethereum/state/machine_state.py b/mythril/laser/ethereum/state/machine_state.py index 286979ea..ff747d4e 100644 --- a/mythril/laser/ethereum/state/machine_state.py +++ b/mythril/laser/ethereum/state/machine_state.py @@ -3,9 +3,11 @@ from typing import Union, Any, List, Dict from z3 import BitVec +from ethereum import opcodes, utils from mythril.laser.ethereum.evm_exceptions import ( StackOverflowException, StackUnderflowException, + OutOfGasException, ) from mythril.laser.ethereum.state.constraints import Constraints @@ -73,26 +75,60 @@ class MachineState: """ def __init__( - self, gas: int, pc=0, stack=None, memory=None, constraints=None, depth=0 + self, + gas_limit: int, + pc=0, + stack=None, + memory=None, + constraints=None, + depth=0, + max_gas_used=0, + min_gas_used=0, ): """ Constructor for machineState """ self.pc = pc self.stack = MachineStack(stack) self.memory = memory or [] - self.gas = gas + self.gas_limit = gas_limit + self.min_gas_used = min_gas_used # lower gas usage bound + self.max_gas_used = max_gas_used # upper gas usage bound self.constraints = constraints or Constraints() self.depth = depth + def calculate_extension_size(self, start: int, size: int) -> int: + if self.memory_size > start + size: + return 0 + return start + size - self.memory_size + + def calculate_memory_gas(self, start: int, size: int): + # https://github.com/ethereum/pyethereum/blob/develop/ethereum/vm.py#L148 + oldsize = self.memory_size // 32 + old_totalfee = ( + oldsize * opcodes.GMEMORY + oldsize ** 2 // opcodes.GQUADRATICMEMDENOM + ) + newsize = utils.ceil32(start + size) // 32 + new_totalfee = ( + newsize * opcodes.GMEMORY + newsize ** 2 // opcodes.GQUADRATICMEMDENOM + ) + return new_totalfee - old_totalfee + + def check_gas(self): + if self.min_gas_used > self.gas_limit: + raise OutOfGasException() + def mem_extend(self, start: int, size: int) -> None: """ Extends the memory of this machine state :param start: Start of memory extension :param size: Size of memory extension """ - if self.memory_size > start + size: - return - m_extend = start + size - self.memory_size - self.memory.extend(bytearray(m_extend)) + m_extend = self.calculate_extension_size(start, size) + if m_extend: + extend_gas = self.calculate_memory_gas(start, size) + self.min_gas_used += extend_gas + self.max_gas_used += extend_gas + self.check_gas() + self.memory.extend(bytearray(m_extend)) def memory_write(self, offset: int, data: List[int]) -> None: """ Writes data to memory starting at offset """ @@ -111,7 +147,9 @@ class MachineState: def __deepcopy__(self, memodict=None): memodict = {} if memodict is None else memodict return MachineState( - gas=self.gas, + gas_limit=self.gas_limit, + max_gas_used=self.max_gas_used, + min_gas_used=self.min_gas_used, pc=self.pc, stack=copy(self.stack), memory=copy(self.memory), @@ -133,5 +171,7 @@ class MachineState: stack=self.stack, memory=self.memory, memsize=self.memory_size, - gas=self.gas, + gas=self.gas_limit, + max_gas_used=self.max_gas_used, + min_gas_used=self.min_gas_used, ) diff --git a/mythril/laser/ethereum/svm.py b/mythril/laser/ethereum/svm.py index 73aa18db..9d28a8fe 100644 --- a/mythril/laser/ethereum/svm.py +++ b/mythril/laser/ethereum/svm.py @@ -155,17 +155,18 @@ class LaserEVM: ) return total_covered_instructions - def exec(self, create=False) -> None: + def exec(self, create=False, track_gas=False) -> Union[List[GlobalState], None]: + final_states = [] for global_state in self.strategy: if self.execution_timeout and not create: if ( self.time + timedelta(seconds=self.execution_timeout) <= datetime.now() ): - return + return final_states + [global_state] if track_gas else None elif self.create_timeout and create: if self.time + timedelta(seconds=self.create_timeout) <= datetime.now(): - return + return final_states + [global_state] if track_gas else None try: new_states, op_code = self.execute_state(global_state) @@ -174,8 +175,12 @@ class LaserEVM: continue self.manage_cfg(op_code, new_states) - self.work_list += new_states + if new_states: + self.work_list += new_states + elif track_gas: + final_states.append(global_state) self.total_states += len(new_states) + return final_states if track_gas else None def execute_state( self, global_state: GlobalState diff --git a/mythril/laser/ethereum/transaction/concolic.py b/mythril/laser/ethereum/transaction/concolic.py index a379f716..0ec88794 100644 --- a/mythril/laser/ethereum/transaction/concolic.py +++ b/mythril/laser/ethereum/transaction/concolic.py @@ -1,3 +1,4 @@ +from typing import List, Union from mythril.laser.ethereum.transaction.transaction_models import ( MessageCallTransaction, ContractCreationTransaction, @@ -20,10 +21,11 @@ def execute_message_call( origin_address, code, data, - gas, + gas_limit, gas_price, value, -) -> None: + track_gas=False, +) -> Union[None, List[GlobalState]]: """ Executes a message call transaction from all open states """ # TODO: Resolve circular import between .transaction and ..svm to import LaserEVM here open_states = laser_evm.open_states[:] @@ -32,21 +34,22 @@ def execute_message_call( for open_world_state in open_states: next_transaction_id = get_next_transaction_id() transaction = MessageCallTransaction( - identifier=next_transaction_id, world_state=open_world_state, - callee_account=open_world_state[callee_address], - caller=caller_address, - call_data=Calldata(next_transaction_id, data), + identifier=next_transaction_id, gas_price=gas_price, - call_value=value, + gas_limit=gas_limit, origin=origin_address, - call_data_type=CalldataType.SYMBOLIC, code=Disassembly(code), + caller=caller_address, + callee_account=open_world_state[callee_address], + call_data=Calldata(next_transaction_id, data), + call_data_type=CalldataType.SYMBOLIC, + call_value=value, ) _setup_global_state_for_execution(laser_evm, transaction) - laser_evm.exec() + return laser_evm.exec(track_gas=track_gas) def _setup_global_state_for_execution(laser_evm, transaction) -> None: diff --git a/mythril/laser/ethereum/transaction/symbolic.py b/mythril/laser/ethereum/transaction/symbolic.py index baa4dc8e..88e16780 100644 --- a/mythril/laser/ethereum/transaction/symbolic.py +++ b/mythril/laser/ethereum/transaction/symbolic.py @@ -26,14 +26,15 @@ def execute_message_call(laser_evm, callee_address: str) -> None: next_transaction_id = get_next_transaction_id() transaction = MessageCallTransaction( world_state=open_world_state, - callee_account=open_world_state[callee_address], - caller=BitVec("caller{}".format(next_transaction_id), 256), identifier=next_transaction_id, - call_data=Calldata(next_transaction_id), gas_price=BitVec("gas_price{}".format(next_transaction_id), 256), - call_value=BitVec("call_value{}".format(next_transaction_id), 256), + gas_limit=8000000, # block gas limit origin=BitVec("origin{}".format(next_transaction_id), 256), + caller=BitVec("caller{}".format(next_transaction_id), 256), + callee_account=open_world_state[callee_address], + call_data=Calldata(next_transaction_id), call_data_type=CalldataType.SYMBOLIC, + call_value=BitVec("call_value{}".format(next_transaction_id), 256), ) _setup_global_state_for_execution(laser_evm, transaction) @@ -57,16 +58,17 @@ def execute_contract_creation( for open_world_state in open_states: next_transaction_id = get_next_transaction_id() transaction = ContractCreationTransaction( - open_world_state, - BitVec("creator{}".format(next_transaction_id), 256), - next_transaction_id, - new_account, - Disassembly(contract_initialization_code), - [], - BitVec("gas_price{}".format(next_transaction_id), 256), - BitVec("call_value{}".format(next_transaction_id), 256), - BitVec("origin{}".format(next_transaction_id), 256), - CalldataType.SYMBOLIC, + world_state=open_world_state, + identifier=next_transaction_id, + gas_price=BitVec("gas_price{}".format(next_transaction_id), 256), + gas_limit=8000000, # block gas limit + origin=BitVec("origin{}".format(next_transaction_id), 256), + code=Disassembly(contract_initialization_code), + caller=BitVec("creator{}".format(next_transaction_id), 256), + callee_account=new_account, + call_data=[], + call_data_type=CalldataType.SYMBOLIC, + call_value=BitVec("call_value{}".format(next_transaction_id), 256), ) _setup_global_state_for_execution(laser_evm, transaction) laser_evm.exec(True) diff --git a/mythril/laser/ethereum/transaction/transaction_models.py b/mythril/laser/ethereum/transaction/transaction_models.py index d2acb156..bef83a2a 100644 --- a/mythril/laser/ethereum/transaction/transaction_models.py +++ b/mythril/laser/ethereum/transaction/transaction_models.py @@ -38,53 +38,72 @@ class TransactionStartSignal(Exception): self.op_code = op_code -class MessageCallTransaction: - """ Transaction object models an transaction""" +class BaseTransaction: + """Basic transaction class holding common data.""" def __init__( self, world_state: WorldState, - callee_account: Account, - caller: ExprRef, + callee_account: Account = None, + caller: ExprRef = None, call_data=None, identifier=None, gas_price=None, - call_value=None, + gas_limit=None, origin=None, - call_data_type=None, code=None, + call_data_type=None, + call_value=None, + init_call_data=True, ): assert isinstance(world_state, WorldState) - self.id = identifier or get_next_transaction_id() self.world_state = world_state - self.callee_account = callee_account - self.caller = caller - self.call_data = ( - Calldata(self.id, call_data) - if not isinstance(call_data, Calldata) - else call_data - ) + self.id = identifier or get_next_transaction_id() + self.gas_price = ( - BitVec("gasprice{}".format(identifier), 256) - if gas_price is None - else gas_price - ) - self.call_value = ( - BitVec("callvalue{}".format(identifier), 256) - if call_value is None - else call_value + gas_price + if gas_price is not None + else BitVec("gasprice{}".format(identifier), 256) ) + self.gas_limit = gas_limit + self.origin = ( - BitVec("origin{}".format(identifier), 256) if origin is None else origin + origin if origin is not None else BitVec("origin{}".format(identifier), 256) ) + self.code = code + + self.caller = caller + self.callee_account = callee_account + if call_data is None and init_call_data: + self.call_data = Calldata(self.id, call_data) + else: + self.call_data = call_data if isinstance(call_data, Calldata) else None self.call_data_type = ( - BitVec("call_data_type{}".format(identifier), 256) - if call_data_type is None - else call_data_type + call_data_type + if call_data_type is not None + else BitVec("call_data_type{}".format(identifier), 256) ) - self.code = code + self.call_value = ( + call_value + if call_value is not None + else BitVec("callvalue{}".format(identifier), 256) + ) + self.return_data = None + def initial_global_state_from_environment(self, environment, active_function): + # Initialize the execution environment + global_state = GlobalState(self.world_state, environment, None) + global_state.environment.active_function_name = active_function + return global_state + + +class MessageCallTransaction(BaseTransaction): + """ Transaction object models an transaction""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + def initial_global_state(self) -> GlobalState: """Initialize the execution environment""" environment = Environment( @@ -97,73 +116,25 @@ class MessageCallTransaction: code=self.code or self.callee_account.code, calldata_type=self.call_data_type, ) - - global_state = GlobalState(self.world_state, environment, None) - global_state.environment.active_function_name = "fallback" - - return global_state + return super().initial_global_state_from_environment( + environment, active_function="fallback" + ) def end(self, global_state: GlobalState, return_data=None, revert=False) -> None: self.return_data = return_data raise TransactionEndSignal(global_state, revert) -class ContractCreationTransaction: +class ContractCreationTransaction(BaseTransaction): """ Transaction object models an transaction""" - def __init__( - self, - world_state: WorldState, - caller: ExprRef, - identifier=None, - callee_account=None, - code=None, - call_data=None, - gas_price=None, - call_value=None, - origin=None, - call_data_type=None, - ): - assert isinstance(world_state, WorldState) - self.id = identifier or get_next_transaction_id() - self.world_state = world_state + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs, init_call_data=False) # TODO: set correct balance for new account - self.callee_account = ( - callee_account - if callee_account - else world_state.create_account(0, concrete_storage=True) - ) - - self.caller = caller - - self.gas_price = ( - BitVec("gasprice{}".format(identifier), 256) - if gas_price is None - else gas_price - ) - self.call_value = ( - BitVec("callvalue{}".format(identifier), 256) - if call_value is None - else call_value - ) - self.origin = ( - BitVec("origin{}".format(identifier), 256) if origin is None else origin - ) - self.call_data_type = ( - BitVec("call_data_type{}".format(identifier), 256) - if call_data_type is None - else call_data_type + self.callee_account = self.callee_account or self.world_state.create_account( + 0, concrete_storage=True ) - self.call_data = ( - Calldata(self.id, call_data) - if not isinstance(call_data, Calldata) - else call_data - ) - self.origin = origin - self.code = code - self.return_data = None - def initial_global_state(self) -> GlobalState: """Initialize the execution environment""" environment = Environment( @@ -176,11 +147,9 @@ class ContractCreationTransaction: self.code, calldata_type=self.call_data_type, ) - - global_state = GlobalState(self.world_state, environment, None) - global_state.environment.active_function_name = "constructor" - - return global_state + return super().initial_global_state_from_environment( + environment, active_function="constructor" + ) def end(self, global_state: GlobalState, return_data=None, revert=False): diff --git a/tests/instructions/codecopy_test.py b/tests/instructions/codecopy_test.py index 0316dacd..9d63efa8 100644 --- a/tests/instructions/codecopy_test.py +++ b/tests/instructions/codecopy_test.py @@ -3,14 +3,19 @@ from mythril.laser.ethereum.state.environment import Environment from mythril.laser.ethereum.state.account import Account 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_codecopy_concrete(): # Arrange active_account = Account("0x0", code=Disassembly("60606040")) environment = Environment(active_account, None, None, None, None, None) - og_state = GlobalState(None, environment, None, MachineState(gas=10000000)) + og_state = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) + og_state.transaction_stack.append( + (MessageCallTransaction(world_state=WorldState(), gas_limit=8000000), None) + ) og_state.mstate.stack = [2, 2, 2] instruction = Instruction("codecopy", dynamic_loader=None) diff --git a/tests/laser/evm_testsuite/VMTests/vmEnvironmentalInfo/calldatacopyUnderFlow.json b/tests/laser/evm_testsuite/VMTests/vmEnvironmentalInfo/calldatacopyUnderFlowerror.json similarity index 97% rename from tests/laser/evm_testsuite/VMTests/vmEnvironmentalInfo/calldatacopyUnderFlow.json rename to tests/laser/evm_testsuite/VMTests/vmEnvironmentalInfo/calldatacopyUnderFlowerror.json index 9ac02250..8055ecf5 100644 --- a/tests/laser/evm_testsuite/VMTests/vmEnvironmentalInfo/calldatacopyUnderFlow.json +++ b/tests/laser/evm_testsuite/VMTests/vmEnvironmentalInfo/calldatacopyUnderFlowerror.json @@ -1,5 +1,5 @@ { - "calldatacopyUnderFlow" : { + "calldatacopyUnderFlowerror" : { "_info" : { "comment" : "", "filledwith" : "testeth 1.5.0.dev2-52+commit.d419e0a2", diff --git a/tests/laser/evm_testsuite/VMTests/vmPushDupSwapTest/push33.json b/tests/laser/evm_testsuite/VMTests/vmPushDupSwapTest/push33.json index 29832402..15ed3eae 100644 --- a/tests/laser/evm_testsuite/VMTests/vmPushDupSwapTest/push33.json +++ b/tests/laser/evm_testsuite/VMTests/vmPushDupSwapTest/push33.json @@ -34,4 +34,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_3.json b/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_3oog.json similarity index 98% rename from tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_3.json rename to tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_3oog.json index 447b0cd0..bf375559 100644 --- a/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_3.json +++ b/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_3oog.json @@ -1,5 +1,5 @@ { - "sha3_3" : { + "sha3_3oog" : { "_info" : { "comment" : "", "filledwith" : "cpp-1.3.0+commit.6e0ce939.Linux.g++", @@ -34,4 +34,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_4.json b/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_4oog.json similarity index 98% rename from tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_4.json rename to tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_4oog.json index 265cbbfa..80634b57 100644 --- a/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_4.json +++ b/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_4oog.json @@ -1,5 +1,5 @@ { - "sha3_4" : { + "sha3_4oog" : { "_info" : { "comment" : "", "filledwith" : "cpp-1.3.0+commit.6e0ce939.Linux.g++", @@ -34,4 +34,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_5.json b/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_5oog.json similarity index 98% rename from tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_5.json rename to tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_5oog.json index 86d87942..b339bb65 100644 --- a/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_5.json +++ b/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_5oog.json @@ -1,5 +1,5 @@ { - "sha3_5" : { + "sha3_5oog" : { "_info" : { "comment" : "", "filledwith" : "cpp-1.3.0+commit.6e0ce939.Linux.g++", @@ -34,4 +34,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_6.json b/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_6oog.json similarity index 98% rename from tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_6.json rename to tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_6oog.json index 97a6788e..1e75bd65 100644 --- a/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_6.json +++ b/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_6oog.json @@ -1,5 +1,5 @@ { - "sha3_6" : { + "sha3_6oog" : { "_info" : { "comment" : "", "filledwith" : "cpp-1.3.0+commit.6e0ce939.Linux.g++", @@ -34,4 +34,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_bigOffset2.json b/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_bigOffset2.json index d5ab8ebc..d4f1b53f 100644 --- a/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_bigOffset2.json +++ b/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_bigOffset2.json @@ -49,4 +49,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_bigOffset.json b/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_bigOffsetoog.json similarity index 98% rename from tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_bigOffset.json rename to tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_bigOffsetoog.json index 940cff9b..ea45fe75 100644 --- a/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_bigOffset.json +++ b/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_bigOffsetoog.json @@ -1,5 +1,5 @@ { - "sha3_bigOffset" : { + "sha3_bigOffsetoog" : { "_info" : { "comment" : "", "filledwith" : "cpp-1.3.0+commit.6e0ce939.Linux.g++", @@ -34,4 +34,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_bigSize.json b/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_bigSizeoog.json similarity index 98% rename from tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_bigSize.json rename to tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_bigSizeoog.json index b859b929..3f68f109 100644 --- a/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_bigSize.json +++ b/tests/laser/evm_testsuite/VMTests/vmSha3Test/sha3_bigSizeoog.json @@ -1,5 +1,5 @@ { - "sha3_bigSize" : { + "sha3_bigSizeoog" : { "_info" : { "comment" : "", "filledwith" : "cpp-1.3.0+commit.6e0ce939.Linux.g++", @@ -34,4 +34,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/laser/evm_testsuite/evm_test.py b/tests/laser/evm_testsuite/evm_test.py index 1294a682..94887b32 100644 --- a/tests/laser/evm_testsuite/evm_test.py +++ b/tests/laser/evm_testsuite/evm_test.py @@ -18,8 +18,8 @@ test_types = [ "vmBitwiseLogicOperation", "vmEnvironmentalInfo", "vmPushDupSwapTest", - "vmSha3Test", "vmTests", + "vmSha3Test", ] @@ -35,21 +35,42 @@ def load_test_data(designations): pre_condition = data["pre"] action = data["exec"] + gas_before = int(action["gas"], 16) + gas_after = data.get("gas") + gas_used = ( + gas_before - int(gas_after, 16) + if gas_after is not None + else None + ) post_condition = data.get("post", {}) + environment = data.get("env") return_data.append( - (test_name, pre_condition, action, post_condition) + ( + test_name, + environment, + pre_condition, + action, + gas_used, + post_condition, + ) ) return return_data @pytest.mark.parametrize( - "test_name, pre_condition, action, post_condition", load_test_data(test_types) + "test_name, environment, pre_condition, action, gas_used, post_condition", + load_test_data(test_types), ) def test_vmtest( - test_name: str, pre_condition: dict, action: dict, post_condition: dict + test_name: str, + environment: dict, + pre_condition: dict, + action: dict, + gas_used: int, + post_condition: dict, ) -> None: # Arrange if test_name == "gasprice": @@ -68,46 +89,57 @@ def test_vmtest( # Act laser_evm.time = datetime.now() - # TODO: move this line below and check for VmExceptions when gas has been implemented - if post_condition == {}: - return - - execute_message_call( + final_states = execute_message_call( laser_evm, callee_address=action["address"], caller_address=action["caller"], origin_address=binascii.a2b_hex(action["origin"][2:]), code=action["code"][2:], - gas=action["gas"], + gas_limit=int(action["gas"], 16), data=binascii.a2b_hex(action["data"][2:]), gas_price=int(action["gasPrice"], 16), value=int(action["value"], 16), + track_gas=True, ) # Assert - - assert len(laser_evm.open_states) == 1 - - world_state = laser_evm.open_states[0] - model = get_model(next(iter(laser_evm.nodes.values())).states[0].mstate.constraints) - - for address, details in post_condition.items(): - account = world_state[address] - - assert account.nonce == int(details["nonce"], 16) - assert account.code.bytecode == details["code"][2:] - - for index, value in details["storage"].items(): - expected = int(value, 16) - actual = account.storage[int(index, 16)] - if isinstance(actual, ExprRef): - actual = model.eval(actual) - actual = ( - 1 if actual == True else 0 if actual == False else actual - ) # Comparisons should be done with == than 'is' here as actual can be a BoolRef - else: - if type(actual) == bytes: - actual = int(binascii.b2a_hex(actual), 16) - elif type(actual) == str: - actual = int(actual, 16) - assert actual == expected + if gas_used is not None and gas_used < int(environment["currentGasLimit"], 16): + # avoid gas usage larger than block gas limit + # this currently exceeds our estimations + gas_min_max = [ + (s.mstate.min_gas_used, s.mstate.max_gas_used) for s in final_states + ] + gas_ranges = [g[0] <= gas_used for g in gas_min_max] + assert all(map(lambda g: g[0] <= g[1], gas_min_max)) + assert any(gas_ranges) + + if any((v in test_name for v in ["error", "oog"])) and post_condition == {}: + # no more work to do if error happens or out of gas + assert len(laser_evm.open_states) == 0 + else: + assert len(laser_evm.open_states) == 1 + world_state = laser_evm.open_states[0] + model = get_model( + next(iter(laser_evm.nodes.values())).states[0].mstate.constraints + ) + + for address, details in post_condition.items(): + account = world_state[address] + + assert account.nonce == int(details["nonce"], 16) + assert account.code.bytecode == details["code"][2:] + + for index, value in details["storage"].items(): + expected = int(value, 16) + actual = account.storage[int(index, 16)] + if isinstance(actual, ExprRef): + actual = model.eval(actual) + actual = ( + 1 if actual == True else 0 if actual == False else actual + ) # Comparisons should be done with == than 'is' here as actual can be a BoolRef + else: + if type(actual) == bytes: + actual = int(binascii.b2a_hex(actual), 16) + elif type(actual) == str: + actual = int(actual, 16) + assert actual == expected diff --git a/tests/laser/state/mstate_test.py b/tests/laser/state/mstate_test.py index 8a7bb29b..10f5b5f3 100644 --- a/tests/laser/state/mstate_test.py +++ b/tests/laser/state/mstate_test.py @@ -10,7 +10,7 @@ memory_extension_test_data = [(0, 0, 10), (0, 30, 10), (100, 22, 8)] ) def test_memory_extension(initial_size, start, extension_size): # Arrange - machine_state = MachineState(0) + machine_state = MachineState(gas_limit=8000000) machine_state.memory = [0] * initial_size # Act @@ -27,7 +27,7 @@ stack_pop_too_many_test_data = [(0, 1), (0, 2), (5, 1), (5, 10)] @pytest.mark.parametrize("initial_size,overflow", stack_pop_too_many_test_data) def test_stack_pop_too_many(initial_size, overflow): # Arrange - machine_state = MachineState(0) + machine_state = MachineState(8000000) machine_state.stack = [42] * initial_size # Act + Assert @@ -44,7 +44,7 @@ stack_pop_test_data = [ @pytest.mark.parametrize("initial_stack,amount,expected", stack_pop_test_data) def test_stack_multiple_pop(initial_stack, amount, expected): # Arrange - machine_state = MachineState(0) + machine_state = MachineState(8000000) machine_state.stack = initial_stack[:] # Act @@ -58,7 +58,7 @@ def test_stack_multiple_pop(initial_stack, amount, expected): def test_stack_multiple_pop_(): # Arrange - machine_state = MachineState(0) + machine_state = MachineState(8000000) machine_state.stack = [1, 2, 3] # Act @@ -71,7 +71,7 @@ def test_stack_multiple_pop_(): def test_stack_single_pop(): # Arrange - machine_state = MachineState(0) + machine_state = MachineState(8000000) machine_state.stack = [1, 2, 3] # Act @@ -87,7 +87,7 @@ memory_write_test_data = [(5, 10, [1, 2, 3]), (0, 0, [3, 4]), (20, 1, [2, 4, 10] @pytest.mark.parametrize("initial_size, memory_offset, data", memory_write_test_data) def test_memory_write(initial_size, memory_offset, data): # Arrange - machine_state = MachineState(0) + machine_state = MachineState(8000000) machine_state.memory = [0] * initial_size # Act diff --git a/tests/taint_runner_test.py b/tests/taint_runner_test.py index 82c523d9..a4f40bbc 100644 --- a/tests/taint_runner_test.py +++ b/tests/taint_runner_test.py @@ -61,12 +61,12 @@ def test_execute_node(mocker): def test_execute(mocker): active_account = Account("0x00") environment = Environment(active_account, None, None, None, None, None) - state_1 = GlobalState(None, environment, None, MachineState(gas=10000000)) + state_1 = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) state_1.mstate.stack = [1, 2] mocker.patch.object(state_1, "get_current_instruction") state_1.get_current_instruction.return_value = {"opcode": "PUSH"} - state_2 = GlobalState(None, environment, None, MachineState(gas=10000000)) + state_2 = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) state_2.mstate.stack = [1, 2, 3] mocker.patch.object(state_2, "get_current_instruction") state_2.get_current_instruction.return_value = {"opcode": "ADD"} @@ -74,7 +74,7 @@ def test_execute(mocker): node_1 = Node("Test contract") node_1.states = [state_1, state_2] - state_3 = GlobalState(None, environment, None, MachineState(gas=10000000)) + state_3 = GlobalState(None, environment, None, MachineState(gas_limit=8000000)) state_3.mstate.stack = [1, 2] mocker.patch.object(state_3, "get_current_instruction") state_3.get_current_instruction.return_value = {"opcode": "ADD"} diff --git a/tests/testdata/outputs_expected/calls.sol.o.json b/tests/testdata/outputs_expected/calls.sol.o.json index 54b5f607..df92da22 100644 --- a/tests/testdata/outputs_expected/calls.sol.o.json +++ b/tests/testdata/outputs_expected/calls.sol.o.json @@ -1 +1,126 @@ -{"error": null, "issues": [{"address": 661, "contract": "Unknown", "debug": "", "description": "This contract executes a message call to to another contract. Make sure that the called contract is trusted and does not execute user-supplied code.", "function": "_function_0x5a6814ec", "swc-id": "107", "title": "Message call to external contract", "type": "Informational"}, {"address": 666, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0x5a6814ec", "swc-id": "104", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 779, "contract": "Unknown", "debug": "", "description": "This contract executes a message call to an address found at storage slot 1. This storage slot can be written to by calling the function `_function_0x2776b163`. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state.", "function": "_function_0xd24b08cc", "swc-id": "107", "title": "Message call to external contract", "type": "Warning"}, {"address": 779, "contract": "Unknown", "debug": "", "description": "Possible transaction order dependence vulnerability: The value or direction of the call statement is determined from a tainted storage location", "function": "_function_0xd24b08cc", "swc-id": "114", "title": "Transaction order dependence", "type": "Warning"}, {"address": 784, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xd24b08cc", "swc-id": "104", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 858, "contract": "Unknown", "debug": "", "description": "This contract executes a message call to to another contract. Make sure that the called contract is trusted and does not execute user-supplied code.", "function": "_function_0xe11f493e", "swc-id": "107", "title": "Message call to external contract", "type": "Informational"}, {"address": 869, "contract": "Unknown", "debug": "", "description": "The contract account state is changed after an external call. Consider that the called contract could re-enter the function before this state change takes place. This can lead to business logic vulnerabilities.", "function": "_function_0xe11f493e", "swc-id": "107", "title": "State change after external call", "type": "Warning"}, {"address": 871, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xe11f493e", "swc-id": "104", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 912, "contract": "Unknown", "debug": "", "description": "This contract executes a message call to an address provided as a function argument. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state.", "function": "_function_0xe1d10f79", "swc-id": "107", "title": "Message call to external contract", "type": "Warning"}, {"address": 918, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xe1d10f79", "swc-id": "104", "title": "Unchecked CALL return value", "type": "Informational"}], "success": true} \ No newline at end of file +{ + "error": null, + "issues": [ + { + "address": 661, + "contract": "Unknown", + "debug": "", + "description": "This contract executes a message call to to another contract. Make sure that the called contract is trusted and does not execute user-supplied code.", + "function": "_function_0x5a6814ec", + "swc-id": "107", + "min_gas_used": 643, + "max_gas_used": 1254, + "title": "Message call to external contract", + "type": "Informational" + }, + { + "address": 666, + "contract": "Unknown", + "debug": "", + "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", + "function": "_function_0x5a6814ec", + "swc-id": "104", + "min_gas_used": 1352, + "max_gas_used": 35963, + "title": "Unchecked CALL return value", + "type": "Informational" + }, + { + "address": 779, + "contract": "Unknown", + "debug": "", + "description": "This contract executes a message call to an address found at storage slot 1. This storage slot can be written to by calling the function `_function_0x2776b163`. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state.", + "function": "_function_0xd24b08cc", + "swc-id": "107", + "min_gas_used": 687, + "max_gas_used": 1298, + "title": "Message call to external contract", + "type": "Warning" + }, + { + "address": 779, + "contract": "Unknown", + "debug": "", + "description": "Possible transaction order dependence vulnerability: The value or direction of the call statement is determined from a tainted storage location", + "function": "_function_0xd24b08cc", + "swc-id": "114", + "min_gas_used": 687, + "max_gas_used": 1298, + "title": "Transaction order dependence", + "type": "Warning" + }, + { + "address": 784, + "contract": "Unknown", + "debug": "", + "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", + "function": "_function_0xd24b08cc", + "swc-id": "104", + "min_gas_used": 1396, + "max_gas_used": 36007, + "title": "Unchecked CALL return value", + "type": "Informational" + }, + { + "address": 858, + "contract": "Unknown", + "debug": "", + "description": "This contract executes a message call to to another contract. Make sure that the called contract is trusted and does not execute user-supplied code.", + "function": "_function_0xe11f493e", + "swc-id": "107", + "min_gas_used": 709, + "max_gas_used": 1320, + "title": "Message call to external contract", + "type": "Informational" + }, + { + "address": 869, + "contract": "Unknown", + "debug": "", + "description": "The contract account state is changed after an external call. Consider that the called contract could re-enter the function before this state change takes place. This can lead to business logic vulnerabilities.", + "function": "_function_0xe11f493e", + "swc-id": "107", + "min_gas_used": 709, + "max_gas_used": 1320, + "title": "State change after external call", + "type": "Warning" + }, + { + "address": 871, + "contract": "Unknown", + "debug": "", + "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", + "function": "_function_0xe11f493e", + "swc-id": "104", + "min_gas_used": 6432, + "max_gas_used": 61043, + "title": "Unchecked CALL return value", + "type": "Informational" + }, + { + "address": 912, + "contract": "Unknown", + "debug": "", + "description": "This contract executes a message call to an address provided as a function argument. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state.", + "function": "_function_0xe1d10f79", + "swc-id": "107", + "min_gas_used": 335, + "max_gas_used": 616, + "title": "Message call to external contract", + "type": "Warning" + }, + { + "address": 918, + "contract": "Unknown", + "debug": "", + "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", + "function": "_function_0xe1d10f79", + "swc-id": "104", + "min_gas_used": 1046, + "max_gas_used": 35327, + "title": "Unchecked CALL return value", + "type": "Informational" + } + ], + "success": true +} diff --git a/tests/testdata/outputs_expected/calls.sol.o.markdown b/tests/testdata/outputs_expected/calls.sol.o.markdown index f19dbaef..7898217b 100644 --- a/tests/testdata/outputs_expected/calls.sol.o.markdown +++ b/tests/testdata/outputs_expected/calls.sol.o.markdown @@ -6,6 +6,7 @@ - Contract: Unknown - Function name: `_function_0x5a6814ec` - PC address: 661 +- Estimated Gas Usage: 643 - 1254 ### Description @@ -17,6 +18,7 @@ This contract executes a message call to to another contract. Make sure that the - Contract: Unknown - Function name: `_function_0x5a6814ec` - PC address: 666 +- Estimated Gas Usage: 1352 - 35963 ### Description @@ -28,6 +30,7 @@ The return value of an external call is not checked. Note that execution continu - Contract: Unknown - Function name: `_function_0xd24b08cc` - PC address: 779 +- Estimated Gas Usage: 687 - 1298 ### Description @@ -39,6 +42,7 @@ This contract executes a message call to an address found at storage slot 1. Thi - Contract: Unknown - Function name: `_function_0xd24b08cc` - PC address: 779 +- Estimated Gas Usage: 687 - 1298 ### Description @@ -50,6 +54,7 @@ Possible transaction order dependence vulnerability: The value or direction of t - Contract: Unknown - Function name: `_function_0xd24b08cc` - PC address: 784 +- Estimated Gas Usage: 1396 - 36007 ### Description @@ -61,6 +66,7 @@ The return value of an external call is not checked. Note that execution continu - Contract: Unknown - Function name: `_function_0xe11f493e` - PC address: 858 +- Estimated Gas Usage: 709 - 1320 ### Description @@ -72,6 +78,7 @@ This contract executes a message call to to another contract. Make sure that the - Contract: Unknown - Function name: `_function_0xe11f493e` - PC address: 869 +- Estimated Gas Usage: 709 - 1320 ### Description @@ -83,6 +90,7 @@ The contract account state is changed after an external call. Consider that the - Contract: Unknown - Function name: `_function_0xe11f493e` - PC address: 871 +- Estimated Gas Usage: 6432 - 61043 ### Description @@ -94,6 +102,7 @@ The return value of an external call is not checked. Note that execution continu - Contract: Unknown - Function name: `_function_0xe1d10f79` - PC address: 912 +- Estimated Gas Usage: 335 - 616 ### Description @@ -105,6 +114,7 @@ This contract executes a message call to an address provided as a function argum - Contract: Unknown - Function name: `_function_0xe1d10f79` - PC address: 918 +- Estimated Gas Usage: 1046 - 35327 ### Description diff --git a/tests/testdata/outputs_expected/calls.sol.o.text b/tests/testdata/outputs_expected/calls.sol.o.text index c65b9fc7..9292f0a6 100644 --- a/tests/testdata/outputs_expected/calls.sol.o.text +++ b/tests/testdata/outputs_expected/calls.sol.o.text @@ -4,6 +4,7 @@ Type: Informational Contract: Unknown Function name: _function_0x5a6814ec PC address: 661 +Estimated Gas Usage: 643 - 1254 This contract executes a message call to to another contract. Make sure that the called contract is trusted and does not execute user-supplied code. -------------------- @@ -13,6 +14,7 @@ Type: Informational Contract: Unknown Function name: _function_0x5a6814ec PC address: 666 +Estimated Gas Usage: 1352 - 35963 The return value of an external call is not checked. Note that execution continue even if the called contract throws. -------------------- @@ -22,6 +24,7 @@ Type: Warning Contract: Unknown Function name: _function_0xd24b08cc PC address: 779 +Estimated Gas Usage: 687 - 1298 This contract executes a message call to an address found at storage slot 1. This storage slot can be written to by calling the function `_function_0x2776b163`. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state. -------------------- @@ -31,6 +34,7 @@ Type: Warning Contract: Unknown Function name: _function_0xd24b08cc PC address: 779 +Estimated Gas Usage: 687 - 1298 Possible transaction order dependence vulnerability: The value or direction of the call statement is determined from a tainted storage location -------------------- @@ -40,6 +44,7 @@ Type: Informational Contract: Unknown Function name: _function_0xd24b08cc PC address: 784 +Estimated Gas Usage: 1396 - 36007 The return value of an external call is not checked. Note that execution continue even if the called contract throws. -------------------- @@ -49,6 +54,7 @@ Type: Informational Contract: Unknown Function name: _function_0xe11f493e PC address: 858 +Estimated Gas Usage: 709 - 1320 This contract executes a message call to to another contract. Make sure that the called contract is trusted and does not execute user-supplied code. -------------------- @@ -58,6 +64,7 @@ Type: Warning Contract: Unknown Function name: _function_0xe11f493e PC address: 869 +Estimated Gas Usage: 709 - 1320 The contract account state is changed after an external call. Consider that the called contract could re-enter the function before this state change takes place. This can lead to business logic vulnerabilities. -------------------- @@ -67,6 +74,7 @@ Type: Informational Contract: Unknown Function name: _function_0xe11f493e PC address: 871 +Estimated Gas Usage: 6432 - 61043 The return value of an external call is not checked. Note that execution continue even if the called contract throws. -------------------- @@ -76,6 +84,7 @@ Type: Warning Contract: Unknown Function name: _function_0xe1d10f79 PC address: 912 +Estimated Gas Usage: 335 - 616 This contract executes a message call to an address provided as a function argument. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state. -------------------- @@ -85,6 +94,7 @@ Type: Informational Contract: Unknown Function name: _function_0xe1d10f79 PC address: 918 +Estimated Gas Usage: 1046 - 35327 The return value of an external call is not checked. Note that execution continue even if the called contract throws. -------------------- diff --git a/tests/testdata/outputs_expected/environments.sol.o.json b/tests/testdata/outputs_expected/environments.sol.o.json index d8659fe1..c5a4b1cd 100644 --- a/tests/testdata/outputs_expected/environments.sol.o.json +++ b/tests/testdata/outputs_expected/environments.sol.o.json @@ -1 +1,36 @@ -{"error": null, "issues": [{"address": 158, "contract": "Unknown", "debug": "", "description": "The arithmetic operation can result in integer overflow.\n", "function": "_function_0x83f12fec", "swc-id": "101", "title": "Integer Overflow", "type": "Warning"}, {"address": 278, "contract": "Unknown", "debug": "", "description": "The arithmetic operation can result in integer overflow.\n", "function": "_function_0x83f12fec", "swc-id": "101", "title": "Integer Overflow", "type": "Warning"}, {"address": 378, "contract": "Unknown", "debug": "", "description": "The substraction can result in an integer underflow.\n", "function": "_function_0x83f12fec", "swc-id": "101", "title": "Integer Underflow", "type": "Warning"}], "success": true} \ No newline at end of file +{ + "error": null, + "issues": [ + { + "address": 158, + "contract": "Unknown", + "debug": "", + "description": "The arithmetic operation can result in integer overflow.\n", + "function": "_function_0x83f12fec", + "swc-id": "101", + "title": "Integer Overflow", + "type": "Warning" + }, + { + "address": 278, + "contract": "Unknown", + "debug": "", + "description": "The arithmetic operation can result in integer overflow.\n", + "function": "_function_0x83f12fec", + "swc-id": "101", + "title": "Integer Overflow", + "type": "Warning" + }, + { + "address": 378, + "contract": "Unknown", + "debug": "", + "description": "The substraction can result in an integer underflow.\n", + "function": "_function_0x83f12fec", + "swc-id": "101", + "title": "Integer Underflow", + "type": "Warning" + } + ], + "success": true +} diff --git a/tests/testdata/outputs_expected/ether_send.sol.o.json b/tests/testdata/outputs_expected/ether_send.sol.o.json index 4b8390be..d229381b 100644 --- a/tests/testdata/outputs_expected/ether_send.sol.o.json +++ b/tests/testdata/outputs_expected/ether_send.sol.o.json @@ -1 +1,30 @@ -{"error": null, "issues": [{"address": 722, "contract": "Unknown", "debug": "", "description": "Users other than the contract creator can withdraw ETH from the contract account without previously having sent any ETH to it. This is likely to be vulnerability.", "function": "withdrawfunds()", "swc-id": "105", "title": "Ether thief", "type": "Warning"}, {"address": 883, "contract": "Unknown", "debug": "", "description": "This binary add operation can result in integer overflow.\n", "function": "invest()", "swc-id": "101", "title": "Integer Overflow", "type": "Warning"}], "success": true} \ No newline at end of file +{ + "error": null, + "issues": [ + { + "address": 722, + "contract": "Unknown", + "debug": "", + "description": "Users other than the contract creator can withdraw ETH from the contract account without previously having sent any ETH to it. This is likely to be vulnerability.", + "function": "withdrawfunds()", + "swc-id": "105", + "min_gas_used": 1138, + "max_gas_used": 1749, + "title": "Ether thief", + "type": "Warning" + }, + { + "address": 883, + "contract": "Unknown", + "debug": "", + "description": "This binary add operation can result in integer overflow.\n", + "function": "invest()", + "swc-id": "101", + "min_gas_used": 1571, + "max_gas_used": 1856, + "title": "Integer Overflow", + "type": "Warning" + } + ], + "success": true +} diff --git a/tests/testdata/outputs_expected/ether_send.sol.o.markdown b/tests/testdata/outputs_expected/ether_send.sol.o.markdown index a7c315b2..f5594b1d 100644 --- a/tests/testdata/outputs_expected/ether_send.sol.o.markdown +++ b/tests/testdata/outputs_expected/ether_send.sol.o.markdown @@ -6,6 +6,7 @@ - Contract: Unknown - Function name: `withdrawfunds()` - PC address: 722 +- Estimated Gas Usage: 1138 - 1749 ### Description @@ -17,6 +18,7 @@ Users other than the contract creator can withdraw ETH from the contract account - Contract: Unknown - Function name: `invest()` - PC address: 883 +- Estimated Gas Usage: 1571 - 1856 ### Description diff --git a/tests/testdata/outputs_expected/ether_send.sol.o.text b/tests/testdata/outputs_expected/ether_send.sol.o.text index 25d44d6c..4dc09766 100644 --- a/tests/testdata/outputs_expected/ether_send.sol.o.text +++ b/tests/testdata/outputs_expected/ether_send.sol.o.text @@ -4,6 +4,7 @@ Type: Warning Contract: Unknown Function name: withdrawfunds() PC address: 722 +Estimated Gas Usage: 1138 - 1749 Users other than the contract creator can withdraw ETH from the contract account without previously having sent any ETH to it. This is likely to be vulnerability. -------------------- @@ -13,6 +14,7 @@ Type: Warning Contract: Unknown Function name: invest() PC address: 883 +Estimated Gas Usage: 1571 - 1856 This binary add operation can result in integer overflow. -------------------- diff --git a/tests/testdata/outputs_expected/exceptions.sol.o.json b/tests/testdata/outputs_expected/exceptions.sol.o.json index cacc15a2..258cedb2 100644 --- a/tests/testdata/outputs_expected/exceptions.sol.o.json +++ b/tests/testdata/outputs_expected/exceptions.sol.o.json @@ -1 +1,54 @@ -{"error": null, "issues": [{"address": 446, "contract": "Unknown", "debug": "", "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", "function": "_function_0x546455b5", "swc-id": "110", "title": "Exception state", "type": "Informational"}, {"address": 484, "contract": "Unknown", "debug": "", "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", "function": "_function_0x92dd38ea", "swc-id": "110", "title": "Exception state", "type": "Informational"}, {"address": 506, "contract": "Unknown", "debug": "", "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", "function": "_function_0xa08299f1", "swc-id": "110", "title": "Exception state", "type": "Informational"}, {"address": 531, "contract": "Unknown", "debug": "", "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", "function": "_function_0xb34c3610", "swc-id": "110", "title": "Exception state", "type": "Informational"}], "success": true} \ No newline at end of file +{ + "error": null, + "issues": [ + { + "address": 446, + "contract": "Unknown", + "debug": "", + "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", + "function": "_function_0x546455b5", + "swc-id": "110", + "min_gas_used": 206, + "max_gas_used": 301, + "title": "Exception state", + "type": "Informational" + }, + { + "address": 484, + "contract": "Unknown", + "debug": "", + "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", + "function": "_function_0x92dd38ea", + "swc-id": "110", + "min_gas_used": 256, + "max_gas_used": 351, + "title": "Exception state", + "type": "Informational" + }, + { + "address": 506, + "contract": "Unknown", + "debug": "", + "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", + "function": "_function_0xa08299f1", + "swc-id": "110", + "min_gas_used": 272, + "max_gas_used": 367, + "title": "Exception state", + "type": "Informational" + }, + { + "address": 531, + "contract": "Unknown", + "debug": "", + "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", + "function": "_function_0xb34c3610", + "swc-id": "110", + "min_gas_used": 268, + "max_gas_used": 363, + "title": "Exception state", + "type": "Informational" + } + ], + "success": true +} diff --git a/tests/testdata/outputs_expected/exceptions.sol.o.markdown b/tests/testdata/outputs_expected/exceptions.sol.o.markdown index 2ed81d76..21e6b539 100644 --- a/tests/testdata/outputs_expected/exceptions.sol.o.markdown +++ b/tests/testdata/outputs_expected/exceptions.sol.o.markdown @@ -6,6 +6,7 @@ - Contract: Unknown - Function name: `_function_0x546455b5` - PC address: 446 +- Estimated Gas Usage: 206 - 301 ### Description @@ -17,6 +18,7 @@ A reachable exception (opcode 0xfe) has been detected. This can be caused by typ - Contract: Unknown - Function name: `_function_0x92dd38ea` - PC address: 484 +- Estimated Gas Usage: 256 - 351 ### Description @@ -28,6 +30,7 @@ A reachable exception (opcode 0xfe) has been detected. This can be caused by typ - Contract: Unknown - Function name: `_function_0xa08299f1` - PC address: 506 +- Estimated Gas Usage: 272 - 367 ### Description @@ -39,6 +42,7 @@ A reachable exception (opcode 0xfe) has been detected. This can be caused by typ - Contract: Unknown - Function name: `_function_0xb34c3610` - PC address: 531 +- Estimated Gas Usage: 268 - 363 ### Description diff --git a/tests/testdata/outputs_expected/exceptions.sol.o.text b/tests/testdata/outputs_expected/exceptions.sol.o.text index a9ac2d73..c7ccf53e 100644 --- a/tests/testdata/outputs_expected/exceptions.sol.o.text +++ b/tests/testdata/outputs_expected/exceptions.sol.o.text @@ -4,6 +4,7 @@ Type: Informational Contract: Unknown Function name: _function_0x546455b5 PC address: 446 +Estimated Gas Usage: 206 - 301 A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking. -------------------- @@ -13,6 +14,7 @@ Type: Informational Contract: Unknown Function name: _function_0x92dd38ea PC address: 484 +Estimated Gas Usage: 256 - 351 A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking. -------------------- @@ -22,6 +24,7 @@ Type: Informational Contract: Unknown Function name: _function_0xa08299f1 PC address: 506 +Estimated Gas Usage: 272 - 367 A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking. -------------------- @@ -31,6 +34,7 @@ Type: Informational Contract: Unknown Function name: _function_0xb34c3610 PC address: 531 +Estimated Gas Usage: 268 - 363 A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. Note that explicit `assert()` should only be used to check invariants. Use `require()` for regular input checking. -------------------- diff --git a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json index 62d4e9fc..3674dde9 100644 --- a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json +++ b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.json @@ -1 +1,54 @@ -{"error": null, "issues": [{"address": 626, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0x141f32ff", "swc-id": "104", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 857, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0x9b58bc26", "swc-id": "104", "title": "Unchecked CALL return value", "type": "Informational"}, {"address": 1038, "contract": "Unknown", "debug": "", "description": "This contract executes a message call to an address provided as a function argument. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state.", "function": "_function_0xeea4c864", "swc-id": "107", "title": "Message call to external contract", "type": "Warning"}, {"address": 1046, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xeea4c864", "swc-id": "104", "title": "Unchecked CALL return value", "type": "Informational"}], "success": true} \ No newline at end of file +{ + "error": null, + "issues": [ + { + "address": 626, + "contract": "Unknown", + "debug": "", + "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", + "function": "_function_0x141f32ff", + "swc-id": "104", + "min_gas_used": 1104, + "max_gas_used": 35856, + "title": "Unchecked CALL return value", + "type": "Informational" + }, + { + "address": 857, + "contract": "Unknown", + "debug": "", + "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", + "function": "_function_0x9b58bc26", + "swc-id": "104", + "min_gas_used": 1167, + "max_gas_used": 35919, + "title": "Unchecked CALL return value", + "type": "Informational" + }, + { + "address": 1038, + "contract": "Unknown", + "debug": "", + "description": "This contract executes a message call to an address provided as a function argument. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state.", + "function": "_function_0xeea4c864", + "swc-id": "107", + "min_gas_used": 477, + "max_gas_used": 1229, + "title": "Message call to external contract", + "type": "Warning" + }, + { + "address": 1046, + "contract": "Unknown", + "debug": "", + "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", + "function": "_function_0xeea4c864", + "swc-id": "104", + "min_gas_used": 1192, + "max_gas_used": 35944, + "title": "Unchecked CALL return value", + "type": "Informational" + } + ], + "success": true +} diff --git a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.markdown b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.markdown index 7208086f..98f6e0b2 100644 --- a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.markdown +++ b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.markdown @@ -6,6 +6,7 @@ - Contract: Unknown - Function name: `_function_0x141f32ff` - PC address: 626 +- Estimated Gas Usage: 1104 - 35856 ### Description @@ -17,6 +18,7 @@ The return value of an external call is not checked. Note that execution continu - Contract: Unknown - Function name: `_function_0x9b58bc26` - PC address: 857 +- Estimated Gas Usage: 1167 - 35919 ### Description @@ -28,6 +30,7 @@ The return value of an external call is not checked. Note that execution continu - Contract: Unknown - Function name: `_function_0xeea4c864` - PC address: 1038 +- Estimated Gas Usage: 477 - 1229 ### Description @@ -39,6 +42,7 @@ This contract executes a message call to an address provided as a function argum - Contract: Unknown - Function name: `_function_0xeea4c864` - PC address: 1046 +- Estimated Gas Usage: 1192 - 35944 ### Description diff --git a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.text b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.text index 46f49440..9a70b216 100644 --- a/tests/testdata/outputs_expected/kinds_of_calls.sol.o.text +++ b/tests/testdata/outputs_expected/kinds_of_calls.sol.o.text @@ -4,6 +4,7 @@ Type: Informational Contract: Unknown Function name: _function_0x141f32ff PC address: 626 +Estimated Gas Usage: 1104 - 35856 The return value of an external call is not checked. Note that execution continue even if the called contract throws. -------------------- @@ -13,6 +14,7 @@ Type: Informational Contract: Unknown Function name: _function_0x9b58bc26 PC address: 857 +Estimated Gas Usage: 1167 - 35919 The return value of an external call is not checked. Note that execution continue even if the called contract throws. -------------------- @@ -22,6 +24,7 @@ Type: Warning Contract: Unknown Function name: _function_0xeea4c864 PC address: 1038 +Estimated Gas Usage: 477 - 1229 This contract executes a message call to an address provided as a function argument. Generally, it is not recommended to call user-supplied addresses using Solidity's call() construct. Note that attackers might leverage reentrancy attacks to exploit race conditions or manipulate this contract's state. -------------------- @@ -31,6 +34,7 @@ Type: Informational Contract: Unknown Function name: _function_0xeea4c864 PC address: 1046 +Estimated Gas Usage: 1192 - 35944 The return value of an external call is not checked. Note that execution continue even if the called contract throws. -------------------- diff --git a/tests/testdata/outputs_expected/metacoin.sol.o.json b/tests/testdata/outputs_expected/metacoin.sol.o.json index 237b1c1e..179ed787 100644 --- a/tests/testdata/outputs_expected/metacoin.sol.o.json +++ b/tests/testdata/outputs_expected/metacoin.sol.o.json @@ -1 +1,5 @@ -{"error": null, "issues": [], "success": true} \ No newline at end of file +{ + "error": null, + "issues": [], + "success": true +} diff --git a/tests/testdata/outputs_expected/multi_contracts.sol.o.json b/tests/testdata/outputs_expected/multi_contracts.sol.o.json index 143f62bb..944358a0 100644 --- a/tests/testdata/outputs_expected/multi_contracts.sol.o.json +++ b/tests/testdata/outputs_expected/multi_contracts.sol.o.json @@ -1 +1,18 @@ -{"error": null, "issues": [{"address": 142, "contract": "Unknown", "debug": "", "description": "Users other than the contract creator can withdraw ETH from the contract account without previously having sent any ETH to it. This is likely to be vulnerability.", "function": "_function_0x8a4068dd", "swc-id": "105", "title": "Ether thief", "type": "Warning"}], "success": true} \ No newline at end of file +{ + "error": null, + "issues": [ + { + "address": 142, + "contract": "Unknown", + "debug": "", + "description": "Users other than the contract creator can withdraw ETH from the contract account without previously having sent any ETH to it. This is likely to be vulnerability.", + "function": "_function_0x8a4068dd", + "swc-id": "105", + "min_gas_used": 186, + "max_gas_used": 467, + "title": "Ether thief", + "type": "Warning" + } + ], + "success": true +} diff --git a/tests/testdata/outputs_expected/multi_contracts.sol.o.markdown b/tests/testdata/outputs_expected/multi_contracts.sol.o.markdown index cefc646f..9f992c47 100644 --- a/tests/testdata/outputs_expected/multi_contracts.sol.o.markdown +++ b/tests/testdata/outputs_expected/multi_contracts.sol.o.markdown @@ -6,6 +6,7 @@ - Contract: Unknown - Function name: `_function_0x8a4068dd` - PC address: 142 +- Estimated Gas Usage: 186 - 467 ### Description diff --git a/tests/testdata/outputs_expected/multi_contracts.sol.o.text b/tests/testdata/outputs_expected/multi_contracts.sol.o.text index c4a161ec..44760ba0 100644 --- a/tests/testdata/outputs_expected/multi_contracts.sol.o.text +++ b/tests/testdata/outputs_expected/multi_contracts.sol.o.text @@ -4,6 +4,7 @@ Type: Warning Contract: Unknown Function name: _function_0x8a4068dd PC address: 142 +Estimated Gas Usage: 186 - 467 Users other than the contract creator can withdraw ETH from the contract account without previously having sent any ETH to it. This is likely to be vulnerability. -------------------- diff --git a/tests/testdata/outputs_expected/nonascii.sol.o.json b/tests/testdata/outputs_expected/nonascii.sol.o.json index 237b1c1e..179ed787 100644 --- a/tests/testdata/outputs_expected/nonascii.sol.o.json +++ b/tests/testdata/outputs_expected/nonascii.sol.o.json @@ -1 +1,5 @@ -{"error": null, "issues": [], "success": true} \ No newline at end of file +{ + "error": null, + "issues": [], + "success": true +} diff --git a/tests/testdata/outputs_expected/origin.sol.o.json b/tests/testdata/outputs_expected/origin.sol.o.json index 94ff06d5..bcfd5e84 100644 --- a/tests/testdata/outputs_expected/origin.sol.o.json +++ b/tests/testdata/outputs_expected/origin.sol.o.json @@ -1 +1,18 @@ -{"error": null, "issues": [{"address": 317, "contract": "Unknown", "debug": "", "description": "The function `transferOwnership(address)` retrieves the transaction origin (tx.origin) using the ORIGIN opcode. Use msg.sender instead.\nSee also: https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin", "function": "transferOwnership(address)", "swc-id": "115", "title": "Use of tx.origin", "type": "Warning"}], "success": true} \ No newline at end of file +{ + "error": null, + "issues": [ + { + "address": 317, + "contract": "Unknown", + "debug": "", + "description": "The function `transferOwnership(address)` retrieves the transaction origin (tx.origin) using the ORIGIN opcode. Use msg.sender instead.\nSee also: https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin", + "function": "transferOwnership(address)", + "swc-id": "115", + "min_gas_used": 626, + "max_gas_used": 1051, + "title": "Use of tx.origin", + "type": "Warning" + } + ], + "success": true +} diff --git a/tests/testdata/outputs_expected/origin.sol.o.markdown b/tests/testdata/outputs_expected/origin.sol.o.markdown index 1e9d6d8b..07b2d60e 100644 --- a/tests/testdata/outputs_expected/origin.sol.o.markdown +++ b/tests/testdata/outputs_expected/origin.sol.o.markdown @@ -6,6 +6,7 @@ - Contract: Unknown - Function name: `transferOwnership(address)` - PC address: 317 +- Estimated Gas Usage: 626 - 1051 ### Description diff --git a/tests/testdata/outputs_expected/origin.sol.o.text b/tests/testdata/outputs_expected/origin.sol.o.text index b71422be..919d1f18 100644 --- a/tests/testdata/outputs_expected/origin.sol.o.text +++ b/tests/testdata/outputs_expected/origin.sol.o.text @@ -4,6 +4,7 @@ Type: Warning Contract: Unknown Function name: transferOwnership(address) PC address: 317 +Estimated Gas Usage: 626 - 1051 The function `transferOwnership(address)` retrieves the transaction origin (tx.origin) using the ORIGIN opcode. Use msg.sender instead. See also: https://solidity.readthedocs.io/en/develop/security-considerations.html#tx-origin -------------------- diff --git a/tests/testdata/outputs_expected/overflow.sol.o.json b/tests/testdata/outputs_expected/overflow.sol.o.json index 7e33e34c..44a01d77 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.json +++ b/tests/testdata/outputs_expected/overflow.sol.o.json @@ -1 +1,42 @@ -{"error":null,"issues":[{"address":567,"contract":"Unknown","debug":"","description":"The subtraction can result in an integer underflow.\n","function":"sendeth(address,uint256)","swc-id":"101","title":"Integer Underflow","type":"Warning"},{"address":649,"contract":"Unknown","debug":"","description":"The subtraction can result in an integer underflow.\n","function":"sendeth(address,uint256)","swc-id":"101","title":"Integer Underflow","type":"Warning"},{"address":725,"contract":"Unknown","debug":"","description":"This binary add operation can result in integer overflow.\n","function":"sendeth(address,uint256)","swc-id":"101","title":"Integer Overflow","type":"Warning"}],"success":true} \ No newline at end of file +{ + "error": null, + "issues": [ + { + "address": 567, + "contract": "Unknown", + "debug": "", + "description": "The subtraction can result in an integer underflow.\n", + "function": "sendeth(address,uint256)", + "swc-id": "101", + "min_gas_used": 750, + "max_gas_used": 1035, + "title": "Integer Underflow", + "type": "Warning" + }, + { + "address": 649, + "contract": "Unknown", + "debug": "", + "description": "The subtraction can result in an integer underflow.\n", + "function": "sendeth(address,uint256)", + "swc-id": "101", + "min_gas_used": 1283, + "max_gas_used": 1758, + "title": "Integer Underflow", + "type": "Warning" + }, + { + "address": 725, + "contract": "Unknown", + "debug": "", + "description": "This binary add operation can result in integer overflow.\n", + "function": "sendeth(address,uint256)", + "swc-id": "101", + "min_gas_used": 6806, + "max_gas_used": 27471, + "title": "Integer Overflow", + "type": "Warning" + } + ], + "success": true +} diff --git a/tests/testdata/outputs_expected/overflow.sol.o.markdown b/tests/testdata/outputs_expected/overflow.sol.o.markdown index 50d2e435..b3043279 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.markdown +++ b/tests/testdata/outputs_expected/overflow.sol.o.markdown @@ -6,6 +6,7 @@ - Contract: Unknown - Function name: `sendeth(address,uint256)` - PC address: 567 +- Estimated Gas Usage: 750 - 1035 ### Description @@ -17,6 +18,7 @@ The subtraction can result in an integer underflow. - Contract: Unknown - Function name: `sendeth(address,uint256)` - PC address: 649 +- Estimated Gas Usage: 1283 - 1758 ### Description @@ -28,6 +30,7 @@ The subtraction can result in an integer underflow. - Contract: Unknown - Function name: `sendeth(address,uint256)` - PC address: 725 +- Estimated Gas Usage: 6806 - 27471 ### Description diff --git a/tests/testdata/outputs_expected/overflow.sol.o.text b/tests/testdata/outputs_expected/overflow.sol.o.text index ebbdabd3..3e1b5154 100644 --- a/tests/testdata/outputs_expected/overflow.sol.o.text +++ b/tests/testdata/outputs_expected/overflow.sol.o.text @@ -4,6 +4,7 @@ Type: Warning Contract: Unknown Function name: sendeth(address,uint256) PC address: 567 +Estimated Gas Usage: 750 - 1035 The subtraction can result in an integer underflow. -------------------- @@ -14,6 +15,7 @@ Type: Warning Contract: Unknown Function name: sendeth(address,uint256) PC address: 649 +Estimated Gas Usage: 1283 - 1758 The subtraction can result in an integer underflow. -------------------- @@ -24,6 +26,7 @@ Type: Warning Contract: Unknown Function name: sendeth(address,uint256) PC address: 725 +Estimated Gas Usage: 6806 - 27471 This binary add operation can result in integer overflow. -------------------- diff --git a/tests/testdata/outputs_expected/returnvalue.sol.o.json b/tests/testdata/outputs_expected/returnvalue.sol.o.json index e8017c9a..a682d477 100644 --- a/tests/testdata/outputs_expected/returnvalue.sol.o.json +++ b/tests/testdata/outputs_expected/returnvalue.sol.o.json @@ -1 +1,42 @@ -{"error": null, "issues": [{"address": 196, "contract": "Unknown", "debug": "", "description": "This contract executes a message call to to another contract. Make sure that the called contract is trusted and does not execute user-supplied code.", "function": "_function_0x633ab5e0", "swc-id": "107", "title": "Message call to external contract", "type": "Informational"}, {"address": 285, "contract": "Unknown", "debug": "", "description": "This contract executes a message call to to another contract. Make sure that the called contract is trusted and does not execute user-supplied code.", "function": "_function_0xe3bea282", "swc-id": "107", "title": "Message call to external contract", "type": "Informational"}, {"address": 290, "contract": "Unknown", "debug": "", "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", "function": "_function_0xe3bea282", "swc-id": "104", "title": "Unchecked CALL return value", "type": "Informational"}], "success": true} \ No newline at end of file +{ + "error": null, + "issues": [ + { + "address": 196, + "contract": "Unknown", + "debug": "", + "description": "This contract executes a message call to to another contract. Make sure that the called contract is trusted and does not execute user-supplied code.", + "function": "_function_0x633ab5e0", + "swc-id": "107", + "min_gas_used": 599, + "max_gas_used": 1210, + "title": "Message call to external contract", + "type": "Informational" + }, + { + "address": 285, + "contract": "Unknown", + "debug": "", + "description": "This contract executes a message call to to another contract. Make sure that the called contract is trusted and does not execute user-supplied code.", + "function": "_function_0xe3bea282", + "swc-id": "107", + "min_gas_used": 621, + "max_gas_used": 1232, + "title": "Message call to external contract", + "type": "Informational" + }, + { + "address": 290, + "contract": "Unknown", + "debug": "", + "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", + "function": "_function_0xe3bea282", + "swc-id": "104", + "min_gas_used": 1330, + "max_gas_used": 35941, + "title": "Unchecked CALL return value", + "type": "Informational" + } + ], + "success": true +} diff --git a/tests/testdata/outputs_expected/returnvalue.sol.o.markdown b/tests/testdata/outputs_expected/returnvalue.sol.o.markdown index f3a058bd..c6772820 100644 --- a/tests/testdata/outputs_expected/returnvalue.sol.o.markdown +++ b/tests/testdata/outputs_expected/returnvalue.sol.o.markdown @@ -6,6 +6,7 @@ - Contract: Unknown - Function name: `_function_0x633ab5e0` - PC address: 196 +- Estimated Gas Usage: 599 - 1210 ### Description @@ -17,6 +18,7 @@ This contract executes a message call to to another contract. Make sure that the - Contract: Unknown - Function name: `_function_0xe3bea282` - PC address: 285 +- Estimated Gas Usage: 621 - 1232 ### Description @@ -28,6 +30,7 @@ This contract executes a message call to to another contract. Make sure that the - Contract: Unknown - Function name: `_function_0xe3bea282` - PC address: 290 +- Estimated Gas Usage: 1330 - 35941 ### Description diff --git a/tests/testdata/outputs_expected/returnvalue.sol.o.text b/tests/testdata/outputs_expected/returnvalue.sol.o.text index 2a8b1470..0eca628d 100644 --- a/tests/testdata/outputs_expected/returnvalue.sol.o.text +++ b/tests/testdata/outputs_expected/returnvalue.sol.o.text @@ -4,6 +4,7 @@ Type: Informational Contract: Unknown Function name: _function_0x633ab5e0 PC address: 196 +Estimated Gas Usage: 599 - 1210 This contract executes a message call to to another contract. Make sure that the called contract is trusted and does not execute user-supplied code. -------------------- @@ -13,6 +14,7 @@ Type: Informational Contract: Unknown Function name: _function_0xe3bea282 PC address: 285 +Estimated Gas Usage: 621 - 1232 This contract executes a message call to to another contract. Make sure that the called contract is trusted and does not execute user-supplied code. -------------------- @@ -22,6 +24,7 @@ Type: Informational Contract: Unknown Function name: _function_0xe3bea282 PC address: 290 +Estimated Gas Usage: 1330 - 35941 The return value of an external call is not checked. Note that execution continue even if the called contract throws. -------------------- diff --git a/tests/testdata/outputs_expected/rubixi.sol.o.json b/tests/testdata/outputs_expected/rubixi.sol.o.json index d328ade0..a135559e 100644 --- a/tests/testdata/outputs_expected/rubixi.sol.o.json +++ b/tests/testdata/outputs_expected/rubixi.sol.o.json @@ -1,166 +1,166 @@ { - "success": true, - "error": null, - "issues": [ - { - "title": "Ether send", - "description": "In the function `_function_0x4229616d` a non-zero amount of Ether is sent to an address taken from storage slot 5.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\n\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.", - "function": "_function_0x4229616d", - "type": "Warning", - "address": 1599, - "debug": "" - }, - { - "title": "Ether send", - "description": "In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\n\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.", - "function": "_function_0xb4022950", - "type": "Warning", - "address": 1940, - "debug": "" - }, - { - "title": "Ether send", - "description": "In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\n\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.", - "function": "_function_0xb4022950", - "type": "Warning", - "address": 2582, - "debug": "" - }, - { - "title": "Exception state", - "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", - "function": "_function_0x57d4021b", - "type": "Informational", - "address": 1653, - "debug": "" - }, - { - "title": "Exception state", - "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", - "function": "_function_0x9dbc4f9b", - "type": "Informational", - "address": 2085, - "debug": "" - }, - { - "title": "Invariant branch condition", - "description": "Found a conditional jump which always follows the same branch", - "function": "fallback", - "type": "Informational", - "address": 3111, - "debug": "" - }, - { - "title": "Invariant branch condition", - "description": "Found a conditional jump which always follows the same branch", - "function": "fallback", - "type": "Informational", - "address": 3140, - "debug": "" - }, - { - "title": "Invariant branch condition", - "description": "Found a conditional jump which always follows the same branch", - "function": "fallback", - "type": "Informational", - "address": 2950, - "debug": "" - }, - { - "title": "Invariant branch condition", - "description": "Found a conditional jump which always follows the same branch", - "function": "fallback", - "type": "Informational", - "address": 1268, - "debug": "" - }, - { - "title": "Invariant branch condition", - "description": "Found a conditional jump which always follows the same branch", - "function": "_function_0x09dfdc71", - "type": "Informational", - "address": 310, - "debug": "" - }, - { - "title": "Invariant branch condition", - "description": "Found a conditional jump which always follows the same branch", - "function": "_function_0x09dfdc71", - "type": "Informational", - "address": 1316, - "debug": "" - }, - { - "title": "Invariant branch condition", - "description": "Found a conditional jump which always follows the same branch", - "function": "_function_0x253459e3", - "type": "Informational", - "address": 1375, - "debug": "" - }, - { - "title": "Invariant branch condition", - "description": "Found a conditional jump which always follows the same branch", - "function": "_function_0x4229616d", - "type": "Informational", - "address": 1511, - "debug": "" - }, - { - "title": "Invariant branch condition", - "description": "Found a conditional jump which always follows the same branch", - "function": "_function_0x57d4021b", - "type": "Informational", - "address": 1679, - "debug": "" - }, - { - "title": "Invariant branch condition", - "description": "Found a conditional jump which always follows the same branch", - "function": "_function_0x6fbaaa1e", - "type": "Informational", - "address": 618, - "debug": "" - }, - { - "title": "Invariant branch condition", - "description": "Found a conditional jump which always follows the same branch", - "function": "_function_0x8a5fb3ca", - "type": "Informational", - "address": 805, - "debug": "" - }, - { - "title": "Invariant branch condition", - "description": "Found a conditional jump which always follows the same branch", - "function": "_function_0x9dbc4f9b", - "type": "Informational", - "address": 2187, - "debug": "" - }, - { - "title": "Unchecked CALL return value", - "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", - "function": "_function_0x4229616d", - "type": "Informational", - "address": 1599, - "debug": "" - }, - { - "title": "Unchecked CALL return value", - "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", - "function": "_function_0xb4022950", - "type": "Informational", - "address": 1940, - "debug": "" - }, - { - "title": "Unchecked CALL return value", - "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", - "function": "_function_0xb4022950", - "type": "Informational", - "address": 2582, - "debug": "" - } - ] -} \ No newline at end of file + "success": true, + "error": null, + "issues": [ + { + "title": "Ether send", + "description": "In the function `_function_0x4229616d` a non-zero amount of Ether is sent to an address taken from storage slot 5.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\n\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.", + "function": "_function_0x4229616d", + "type": "Warning", + "address": 1599, + "debug": "" + }, + { + "title": "Ether send", + "description": "In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\n\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.", + "function": "_function_0xb4022950", + "type": "Warning", + "address": 1940, + "debug": "" + }, + { + "title": "Ether send", + "description": "In the function `_function_0xb4022950` a non-zero amount of Ether is sent to an address taken from storage slot 5.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\n\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.\nThere is a check on storage index 5. This storage slot can be written to by calling the function `_function_0x67f809e9`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.", + "function": "_function_0xb4022950", + "type": "Warning", + "address": 2582, + "debug": "" + }, + { + "title": "Exception state", + "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", + "function": "_function_0x57d4021b", + "type": "Informational", + "address": 1653, + "debug": "" + }, + { + "title": "Exception state", + "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", + "function": "_function_0x9dbc4f9b", + "type": "Informational", + "address": 2085, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "fallback", + "type": "Informational", + "address": 3111, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "fallback", + "type": "Informational", + "address": 3140, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "fallback", + "type": "Informational", + "address": 2950, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "fallback", + "type": "Informational", + "address": 1268, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "_function_0x09dfdc71", + "type": "Informational", + "address": 310, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "_function_0x09dfdc71", + "type": "Informational", + "address": 1316, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "_function_0x253459e3", + "type": "Informational", + "address": 1375, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "_function_0x4229616d", + "type": "Informational", + "address": 1511, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "_function_0x57d4021b", + "type": "Informational", + "address": 1679, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "_function_0x6fbaaa1e", + "type": "Informational", + "address": 618, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "_function_0x8a5fb3ca", + "type": "Informational", + "address": 805, + "debug": "" + }, + { + "title": "Invariant branch condition", + "description": "Found a conditional jump which always follows the same branch", + "function": "_function_0x9dbc4f9b", + "type": "Informational", + "address": 2187, + "debug": "" + }, + { + "title": "Unchecked CALL return value", + "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", + "function": "_function_0x4229616d", + "type": "Informational", + "address": 1599, + "debug": "" + }, + { + "title": "Unchecked CALL return value", + "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", + "function": "_function_0xb4022950", + "type": "Informational", + "address": 1940, + "debug": "" + }, + { + "title": "Unchecked CALL return value", + "description": "The return value of an external call is not checked. Note that execution continue even if the called contract throws.", + "function": "_function_0xb4022950", + "type": "Informational", + "address": 2582, + "debug": "" + } + ] +} diff --git a/tests/testdata/outputs_expected/suicide.sol.o.json b/tests/testdata/outputs_expected/suicide.sol.o.json index 7d9a87c9..7505adcf 100644 --- a/tests/testdata/outputs_expected/suicide.sol.o.json +++ b/tests/testdata/outputs_expected/suicide.sol.o.json @@ -1 +1,18 @@ -{"error": null, "issues": [{"address": 146, "contract": "Unknown", "debug": "", "description": "A reachable SUICIDE instruction was detected. The remaining Ether is sent to an address provided as a function argument.\n", "function": "_function_0xcbf0b0c0", "swc-id": "106", "title": "Unchecked SUICIDE", "type": "Warning"}], "success": true} \ No newline at end of file +{ + "error": null, + "issues": [ + { + "address": 146, + "contract": "Unknown", + "debug": "", + "description": "A reachable SUICIDE instruction was detected. The remaining Ether is sent to an address provided as a function argument.\n", + "function": "_function_0xcbf0b0c0", + "swc-id": "106", + "min_gas_used": 168, + "max_gas_used": 263, + "title": "Unchecked SUICIDE", + "type": "Warning" + } + ], + "success": true +} diff --git a/tests/testdata/outputs_expected/suicide.sol.o.markdown b/tests/testdata/outputs_expected/suicide.sol.o.markdown index 6d24f84d..985ab74c 100644 --- a/tests/testdata/outputs_expected/suicide.sol.o.markdown +++ b/tests/testdata/outputs_expected/suicide.sol.o.markdown @@ -6,6 +6,7 @@ - Contract: Unknown - Function name: `_function_0xcbf0b0c0` - PC address: 146 +- Estimated Gas Usage: 168 - 263 ### Description diff --git a/tests/testdata/outputs_expected/suicide.sol.o.text b/tests/testdata/outputs_expected/suicide.sol.o.text index 138bf06b..3a283520 100644 --- a/tests/testdata/outputs_expected/suicide.sol.o.text +++ b/tests/testdata/outputs_expected/suicide.sol.o.text @@ -4,6 +4,7 @@ Type: Warning Contract: Unknown Function name: _function_0xcbf0b0c0 PC address: 146 +Estimated Gas Usage: 168 - 263 A reachable SUICIDE instruction was detected. The remaining Ether is sent to an address provided as a function argument. -------------------- diff --git a/tests/testdata/outputs_expected/underflow.sol.o.json b/tests/testdata/outputs_expected/underflow.sol.o.json index b4285da8..44a01d77 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.json +++ b/tests/testdata/outputs_expected/underflow.sol.o.json @@ -1 +1,42 @@ -{"error": null, "issues": [{"address": 567, "contract": "Unknown", "debug": "", "description": "The subtraction can result in an integer underflow.\n", "function": "sendeth(address,uint256)", "swc-id": "101", "title": "Integer Underflow", "type": "Warning"}, {"address": 649, "contract": "Unknown", "debug": "", "description": "The subtraction can result in an integer underflow.\n", "function": "sendeth(address,uint256)", "swc-id": "101", "title": "Integer Underflow", "type": "Warning"}, {"address": 725, "contract": "Unknown", "debug": "", "description": "This binary add operation can result in integer overflow.\n", "function": "sendeth(address,uint256)", "swc-id": "101", "title": "Integer Overflow", "type": "Warning"}], "success": true} \ No newline at end of file +{ + "error": null, + "issues": [ + { + "address": 567, + "contract": "Unknown", + "debug": "", + "description": "The subtraction can result in an integer underflow.\n", + "function": "sendeth(address,uint256)", + "swc-id": "101", + "min_gas_used": 750, + "max_gas_used": 1035, + "title": "Integer Underflow", + "type": "Warning" + }, + { + "address": 649, + "contract": "Unknown", + "debug": "", + "description": "The subtraction can result in an integer underflow.\n", + "function": "sendeth(address,uint256)", + "swc-id": "101", + "min_gas_used": 1283, + "max_gas_used": 1758, + "title": "Integer Underflow", + "type": "Warning" + }, + { + "address": 725, + "contract": "Unknown", + "debug": "", + "description": "This binary add operation can result in integer overflow.\n", + "function": "sendeth(address,uint256)", + "swc-id": "101", + "min_gas_used": 6806, + "max_gas_used": 27471, + "title": "Integer Overflow", + "type": "Warning" + } + ], + "success": true +} diff --git a/tests/testdata/outputs_expected/underflow.sol.o.markdown b/tests/testdata/outputs_expected/underflow.sol.o.markdown index 50d2e435..b3043279 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.markdown +++ b/tests/testdata/outputs_expected/underflow.sol.o.markdown @@ -6,6 +6,7 @@ - Contract: Unknown - Function name: `sendeth(address,uint256)` - PC address: 567 +- Estimated Gas Usage: 750 - 1035 ### Description @@ -17,6 +18,7 @@ The subtraction can result in an integer underflow. - Contract: Unknown - Function name: `sendeth(address,uint256)` - PC address: 649 +- Estimated Gas Usage: 1283 - 1758 ### Description @@ -28,6 +30,7 @@ The subtraction can result in an integer underflow. - Contract: Unknown - Function name: `sendeth(address,uint256)` - PC address: 725 +- Estimated Gas Usage: 6806 - 27471 ### Description diff --git a/tests/testdata/outputs_expected/underflow.sol.o.text b/tests/testdata/outputs_expected/underflow.sol.o.text index ebbdabd3..3e1b5154 100644 --- a/tests/testdata/outputs_expected/underflow.sol.o.text +++ b/tests/testdata/outputs_expected/underflow.sol.o.text @@ -4,6 +4,7 @@ Type: Warning Contract: Unknown Function name: sendeth(address,uint256) PC address: 567 +Estimated Gas Usage: 750 - 1035 The subtraction can result in an integer underflow. -------------------- @@ -14,6 +15,7 @@ Type: Warning Contract: Unknown Function name: sendeth(address,uint256) PC address: 649 +Estimated Gas Usage: 1283 - 1758 The subtraction can result in an integer underflow. -------------------- @@ -24,6 +26,7 @@ Type: Warning Contract: Unknown Function name: sendeth(address,uint256) PC address: 725 +Estimated Gas Usage: 6806 - 27471 This binary add operation can result in integer overflow. -------------------- diff --git a/tests/testdata/outputs_expected/weak_random.sol.o.json b/tests/testdata/outputs_expected/weak_random.sol.o.json index 2338b178..630fdb0d 100644 --- a/tests/testdata/outputs_expected/weak_random.sol.o.json +++ b/tests/testdata/outputs_expected/weak_random.sol.o.json @@ -1,46 +1,46 @@ { - "success": true, - "error": null, - "issues": [ - { - "title": "Dependence on predictable environment variable", - "description": "In the function `_function_0xe9874106` the following predictable state variables are used to determine Ether recipient:\n- block.coinbase\n", - "function": "_function_0xe9874106", - "type": "Warning", - "address": 1285, - "debug": "" - }, - { - "title": "Ether send", - "description": "In the function `_function_0xe9874106` a non-zero amount of Ether is sent to an address taken from storage slot 0.\nThere is a check on storage index 0. This storage slot can be written to by calling the function `fallback`.\n\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.", - "function": "_function_0xe9874106", - "type": "Warning", - "address": 1285, - "debug": "" - }, - { - "title": "Exception state", - "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", - "function": "fallback", - "type": "Informational", - "address": 356, - "debug": "" - }, - { - "title": "Exception state", - "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", - "function": "_function_0xe9874106", - "type": "Informational", - "address": 146, - "debug": "" - }, - { - "title": "Transaction order dependence", - "description": "A possible transaction order independence vulnerability exists in function _function_0xe9874106. The value or direction of the call statement is determined from a tainted storage location", - "function": "_function_0xe9874106", - "type": "Warning", - "address": 1285, - "debug": "" - } - ] -} \ No newline at end of file + "success": true, + "error": null, + "issues": [ + { + "title": "Dependence on predictable environment variable", + "description": "In the function `_function_0xe9874106` the following predictable state variables are used to determine Ether recipient:\n- block.coinbase\n", + "function": "_function_0xe9874106", + "type": "Warning", + "address": 1285, + "debug": "" + }, + { + "title": "Ether send", + "description": "In the function `_function_0xe9874106` a non-zero amount of Ether is sent to an address taken from storage slot 0.\nThere is a check on storage index 0. This storage slot can be written to by calling the function `fallback`.\n\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.\nThere is a check on storage index 1. This storage slot can be written to by calling the function `fallback`.", + "function": "_function_0xe9874106", + "type": "Warning", + "address": 1285, + "debug": "" + }, + { + "title": "Exception state", + "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", + "function": "fallback", + "type": "Informational", + "address": 356, + "debug": "" + }, + { + "title": "Exception state", + "description": "A reachable exception (opcode 0xfe) has been detected. This can be caused by type errors, division by zero, out-of-bounds array access, or assert violations. This is acceptable in most situations. Note however that `assert()` should only be used to check invariants. Use `require()` for regular input checking. ", + "function": "_function_0xe9874106", + "type": "Informational", + "address": 146, + "debug": "" + }, + { + "title": "Transaction order dependence", + "description": "A possible transaction order independence vulnerability exists in function _function_0xe9874106. The value or direction of the call statement is determined from a tainted storage location", + "function": "_function_0xe9874106", + "type": "Warning", + "address": 1285, + "debug": "" + } + ] +} From 7de54686ce2890ec6d7b82f1b5c1018bfbc657ae Mon Sep 17 00:00:00 2001 From: JoranHonig Date: Thu, 15 Nov 2018 16:46:45 +0100 Subject: [PATCH 3/5] Use z3 overflow check (#702) --- mythril/analysis/modules/integer.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mythril/analysis/modules/integer.py b/mythril/analysis/modules/integer.py index 0144baa6..4f58b067 100644 --- a/mythril/analysis/modules/integer.py +++ b/mythril/analysis/modules/integer.py @@ -74,13 +74,12 @@ def _check_integer_overflow(statespace, state, node): if instruction["opcode"] == "ADD": operator = "add" expr = op0 + op1 - # constraint = Not(BVAddNoOverflow(op0, op1, signed=False)) + constraint = Not(BVAddNoOverflow(op0, op1, signed=False)) else: operator = "multiply" expr = op1 * op0 - # constraint = Not(BVMulNoOverflow(op0, op1, signed=False)) + constraint = Not(BVMulNoOverflow(op0, op1, signed=False)) - constraint = Or(And(ULT(expr, op0), op1 != 0), And(ULT(expr, op1), op0 != 0)) # Check satisfiable model = _try_constraints(node.constraints, [constraint]) From 33c0a6c7aba799b27a37a54e66b1c058914010fc Mon Sep 17 00:00:00 2001 From: Luca Daniel Date: Fri, 16 Nov 2018 14:48:49 +0200 Subject: [PATCH 4/5] Change gitter links and references to discord (#708) --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b7d14f38..38039960 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,12 +5,12 @@ Hi, if you are reading this that means that you probably want to contribute to M If you have found a problem with Mythril or want to propose a new feature then you can do this using GitHub issues. We already created some templates to make this process easier, but if your issue/feature request does not fit within the template then feel free to deviate. -If you have a small question or aren't sure if you should create an issue for your problem/suggestion then you can always hop by on our [Gitter channel](https://gitter.im/ConsenSys/mythril). +If you have a small question or aren't sure if you should create an issue for your problem/suggestion then you can always hop by on our [Discord server](https://discord.gg/FGMkcU2). # Coding If you want to help out with the development of Mythril then you can take a look at our issues or [Waffle board](https://waffle.io/ConsenSys/mythril). -Before you start working on an issue pkease stop by on Gitter to message a collaborator, this way we can assign you to the issue making sure nobody does double work. We can also provide you with support through Gitter if there are any questions during the development process. +Before you start working on an issue pkease stop by on Discord to message a collaborator, this way we can assign you to the issue making sure nobody does double work. We can also provide you with support through Discord if there are any questions during the development process. ## New ideas Before you start working on a new idea, it's useful to create an issue on GitHub, that way we know what you want to implement and that you are working on it. Additionally, it might happen that your feature does not fit with our roadmap, in which case it would be unfortunate if you have already spent some time working on it. From 9da1ef59fbcfdbe17ae894c49a703d9ce0a1ee68 Mon Sep 17 00:00:00 2001 From: Bernhard Mueller Date: Sat, 17 Nov 2018 01:14:00 +0700 Subject: [PATCH 5/5] Deepcopy account state before deletion on selfdestuct (#704) --- mythril/laser/ethereum/instructions.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mythril/laser/ethereum/instructions.py b/mythril/laser/ethereum/instructions.py index 477af48c..c7dddc9b 100644 --- a/mythril/laser/ethereum/instructions.py +++ b/mythril/laser/ethereum/instructions.py @@ -1194,6 +1194,13 @@ class Instruction: ) account_created = True + global_state.environment.active_account = deepcopy( + global_state.environment.active_account + ) + global_state.accounts[ + global_state.environment.active_account.address + ] = global_state.environment.active_account + global_state.environment.active_account.balance = 0 global_state.environment.active_account.deleted = True global_state.current_transaction.end(global_state)