def _reverted_trace(self, trace: Sequence) -> None: self._modified_state = False # get events from trace self._events = _decode_trace(trace) if self._revert_msg is not None: return # get revert message step = next(i for i in trace if i["op"] in ("REVERT", "INVALID")) if step["op"] == "REVERT" and int(step["stack"][-2], 16): # get returned error string from stack data = _get_memory(step, -1)[4:] self._revert_msg = decode_abi(["string"], data)[0] return # check for dev revert string using program counter self._revert_msg = build._get_dev_revert(step["pc"]) if self._revert_msg is not None: return # if none is found, expand the trace and get it from the pcMap self._expand_trace() try: pc_map = _find_contract(step["address"])._build["pcMap"] # if this is the function selector revert, check for a jump if "first_revert" in pc_map[step["pc"]]: i = trace.index(step) - 4 if trace[i]["pc"] != step["pc"] - 4: step = trace[i] self._revert_msg = pc_map[step["pc"]]["dev"] except KeyError: self._revert_msg = "invalid opcode" if step[ "op"] == "INVALID" else ""
def _reverted_trace(self, trace: Sequence) -> None: self._modified_state = False # get events from trace self._events = _decode_trace( trace, str(self.receiver or self.contract_address)) if self.contract_address: step = next((i for i in trace if i["op"] == "CODECOPY"), None) if step is not None and int(step["stack"][-3], 16) > 24577: self._revert_msg = "exceeds EIP-170 size limit" if self._revert_msg is not None: return # iterate over revert instructions in reverse to find revert message for step in (i for i in trace[::-1] if i["op"] in ("REVERT", "INVALID")): if step["op"] == "REVERT" and int(step["stack"][-2], 16): # get returned error string from stack data = _get_memory(step, -1)[4:] self._revert_msg = decode_abi(["string"], data)[0] return if self.contract_address: self._revert_msg = "invalid opcode" if step[ "op"] == "INVALID" else "" return # check for dev revert string using program counter self._revert_msg = build._get_dev_revert(step["pc"]) if self._revert_msg is not None: return # if none is found, expand the trace and get it from the pcMap self._expand_trace() try: pc_map = state._find_contract(step["address"])._build["pcMap"] # if this is the function selector revert, check for a jump if "first_revert" in pc_map[step["pc"]]: i = trace.index(step) - 4 if trace[i]["pc"] != step["pc"] - 4: step = trace[i] self._revert_msg = pc_map[step["pc"]]["dev"] return except (KeyError, AttributeError, TypeError): pass step = next(i for i in trace[::-1] if i["op"] in ("REVERT", "INVALID")) self._revert_msg = "invalid opcode" if step["op"] == "INVALID" else ""
def __init__( self, txid: Union[str, bytes], sender: Any = None, silent: bool = False, name: str = "", revert_data: Optional[Tuple] = None, ) -> None: """Instantiates a new TransactionReceipt object. Args: txid: hexstring transaction ID sender: sender as a hex string or Account object silent: toggles console verbosity name: contract function being called revert_data: (revert string, program counter, revert type) """ if isinstance(txid, bytes): txid = txid.hex() if not silent: print( f"{color['key']}Transaction sent{color}: {color['value']}{txid}{color}" ) history._add_tx(self) self._raw_trace = None self._trace = None self._events = None self._return_value = None self._revert_msg = None self._modified_state = None self._confirmed = threading.Event() self.sender = sender self.status = -1 self.txid = txid self.fn_name = name if name and "." in name: self.contract_name, self.fn_name = name.split(".", maxsplit=1) # avoid querying the trace to get the revert string if possible revert_msg, self._revert_pc, revert_type = revert_data or (None, None, None) if revert_msg: # revert message was returned self._revert_msg = revert_msg elif revert_type in ("revert", "invalid opcode"): # check for dev revert string as a comment self._revert_msg = build._get_dev_revert(self._revert_pc) else: self._revert_msg = revert_type # threaded to allow impatient users to ctrl-c to stop waiting in the console confirm_thread = threading.Thread(target=self._await_confirmation, args=(silent, ), daemon=True) confirm_thread.start() try: confirm_thread.join() if ARGV["cli"] == "console": return # if coverage evaluation is active, evaluate the trace if ARGV["coverage"] and not coverage._check_cached( self.coverage_hash) and self.trace: self._expand_trace() if not self.status: if self._revert_msg is None: # no revert message and unable to check dev string - have to get trace self._expand_trace() # raise from a new function to reduce pytest traceback length _raise( self._revert_msg or "", self._traceback_string() if ARGV["revert"] else self._error_string(1), ) except KeyboardInterrupt: if ARGV["cli"] != "console": raise