diff --git a/manticore/platforms/evm.py b/manticore/platforms/evm.py index b3e024001..23e8e00ce 100644 --- a/manticore/platforms/evm.py +++ b/manticore/platforms/evm.py @@ -745,6 +745,7 @@ def extend_with_zeroes(b): self._on_transaction = False # for @transact self._checkpoint_data = None self._published_pre_instruction_events = False + self._return_data = b"" # Used calldata size self._used_calldata_size = 0 @@ -799,6 +800,7 @@ def __getstate__(self): state["_used_calldata_size"] = self._used_calldata_size state["_valid_jumpdests"] = self._valid_jumpdests state["_check_jumpdest"] = self._check_jumpdest + state["_return_data"] = self._return_data return state def __setstate__(self, state): @@ -823,6 +825,7 @@ def __setstate__(self, state): self._used_calldata_size = state["_used_calldata_size"] self._valid_jumpdests = state["_valid_jumpdests"] self._check_jumpdest = state["_check_jumpdest"] + self._return_data = state["_return_data"] super().__setstate__(state) def _get_memfee(self, address, size=1): @@ -1798,7 +1801,7 @@ def RETURNDATACOPY_gas(self, mem_offset, return_offset, size): return self._get_memfee(mem_offset, size) def RETURNDATACOPY(self, mem_offset, return_offset, size): - return_data = self.world.last_transaction.return_data + return_data = self._return_data self._allocate(mem_offset, size) for i in range(size): @@ -1808,7 +1811,7 @@ def RETURNDATACOPY(self, mem_offset, return_offset, size): self._store(mem_offset + i, 0) def RETURNDATASIZE(self): - return len(self.world.last_transaction.return_data) + return len(self._return_data) ############################################################################ # Block Information @@ -2028,15 +2031,16 @@ def CALL(self, gas, address, value, in_offset, in_size, out_offset, out_size): ) raise StartTx() + def __pos_call(self, out_offset, out_size): + data = self._return_data + data_size = len(data) + size = Operators.ITEBV(256, Operators.ULT(out_size, data_size), out_size, data_size) + self.write_buffer(out_offset, data[:size]) + return self.world.last_transaction.return_value + @CALL.pos # type: ignore def CALL(self, gas, address, value, in_offset, in_size, out_offset, out_size): - data = self.world.last_transaction.return_data - if data is not None: - data_size = len(data) - size = Operators.ITEBV(256, Operators.ULT(out_size, data_size), out_size, data_size) - self.write_buffer(out_offset, data[:size]) - - return self.world.last_transaction.return_value + return self.__pos_call(out_offset, out_size) def CALLCODE_gas(self, gas, address, value, in_offset, in_size, out_offset, out_size): return self._get_memfee(in_offset, in_size) @@ -2057,13 +2061,7 @@ def CALLCODE(self, gas, _ignored_, value, in_offset, in_size, out_offset, out_si @CALLCODE.pos # type: ignore def CALLCODE(self, gas, address, value, in_offset, in_size, out_offset, out_size): - data = self.world.last_transaction.return_data - if data is not None: - data_size = len(data) - size = Operators.ITEBV(256, Operators.ULT(out_size, data_size), out_size, data_size) - self.write_buffer(out_offset, data[:size]) - - return self.world.last_transaction.return_value + return self.__pos_call(out_offset, out_size) def RETURN_gas(self, offset, size): return self._get_memfee(offset, size) @@ -2093,13 +2091,7 @@ def DELEGATECALL(self, gas, address, in_offset, in_size, out_offset, out_size): @DELEGATECALL.pos # type: ignore def DELEGATECALL(self, gas, address, in_offset, in_size, out_offset, out_size): - data = self.world.last_transaction.return_data - if data is not None: - data_size = len(data) - size = Operators.ITEBV(256, Operators.ULT(out_size, data_size), out_size, data_size) - self.write_buffer(out_offset, data[:size]) - - return self.world.last_transaction.return_value + return self.__pos_call(out_offset, out_size) def STATICCALL_gas(self, gas, address, in_offset, in_size, out_offset, out_size): return self._get_memfee(in_offset, in_size) @@ -2120,13 +2112,7 @@ def STATICCALL(self, gas, address, in_offset, in_size, out_offset, out_size): @STATICCALL.pos # type: ignore def STATICCALL(self, gas, address, in_offset, in_size, out_offset, out_size): - data = self.world.last_transaction.return_data - if data is not None: - data_size = len(data) - size = Operators.ITEBV(256, Operators.ULT(out_size, data_size), out_size, data_size) - self.write_buffer(out_offset, data[:size]) - - return self.world.last_transaction.return_value + return self.__pos_call(out_offset, out_size) def REVERT_gas(self, offset, size): return self._get_memfee(offset, size) @@ -2394,11 +2380,16 @@ def constraints(self, constraints): self.current_vm.constraints = constraints def _open_transaction(self, sort, address, price, bytecode_or_data, caller, value, gas=2300): + assert price is not None if self.depth > 0: origin = self.tx_origin() else: origin = caller - assert price is not None + + # If not a human tx, reset returndata + # https://github.com/ethereum/EIPs/blob/master/EIPS/eip-211.md + if self.current_vm: + self.current_vm._return_data = b"" tx = Transaction( sort, address, price, bytecode_or_data, caller, value, depth=self.depth, gas=gas @@ -2433,13 +2424,17 @@ def _close_transaction(self, result, data=None, rollback=False): assert self.constraints == vm.constraints # Keep constraints gathered in the last vm self.constraints = vm.constraints + + # https://github.com/ethereum/EIPs/blob/master/EIPS/eip-211.md + if data is not None and self.current_vm is not None: + self.current_vm._return_data = data + if rollback: self._set_storage(vm.address, account_storage) self._logs = logs self.send_funds(tx.address, tx.caller, tx.value) else: self._deleted_accounts = deleted_accounts - # FIXME: BUG: a CREATE can be successful and still return an empty contract :shrug: if not issymbolic(tx.caller) and ( tx.sort == "CREATE" or not self._world_state[tx.caller]["code"]