trace

Documentation for eth_defi.trace Python module.

Symbolic transaction tracing and human-readable Solidity stack traces.

  • This code is very preliminary and has not been througly tested with different smart contracts, so patches welcome

  • Internally use evm-trace library from Ape

  • Currently only works with Anvil (eth_defi.anvil) backend

Functions

assert_call_success_with_explanation(func[, ...])

Make a Web3.call and if it fails get the Solidity stack trace.

assert_transaction_success_with_explanation(...)

Checks if a transaction succeeds and give a verbose explanation why not..

print_symbolic_trace(contract_registry, calltree)

Print a symbolic trace of an Ethereum transaction.

trace_evm_call(web3, tx[, trace_method, ...])

Trace a Solidity function call.

trace_evm_transaction(web3, tx_hash[, ...])

Trace a (failed) transaction.

Classes

SymbolicTreeRepresentation

A EVM trace tree that can resolve contract names and functions.

TraceMethod

What kind of transaction tracing method we use.

Exceptions

TraceNotEnabled

Tracing is not enabled on the backend.

TransactionAssertionError

Exception thrown when unit test transaction asset fails.

exception TraceNotEnabled

Bases: Exception

Tracing is not enabled on the backend.

__init__(*args, **kwargs)
__new__(**kwargs)
add_note()

Exception.add_note(note) – add a note to the exception

with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

exception TransactionAssertionError

Bases: AssertionError

Exception thrown when unit test transaction asset fails.

See assert_transaction_success_with_explanation().

__init__(message, revert_reason='', solidity_stack_trace='')
Parameters
  • revert_reason (str) –

  • solidity_stack_trace (str) –

__new__(**kwargs)
add_note()

Exception.add_note(note) – add a note to the exception

with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

class TraceMethod

Bases: enum.Enum

What kind of transaction tracing method we use.

geth = 'geth'

Use debug_traceTransaction

parity = 'parity'

Use trace_transaction

trace_evm_transaction(web3, tx_hash, trace_method=TraceMethod.parity)

Trace a (failed) transaction.

  • See print_symbolic_trace() for usage

  • Extract an EVM transaction stack trace from a node, using GoEthereum compatible debug_traceTransaction

  • Currently only works with Anvil backend and if steps_trace=True

Parameters
  • web3 (web3.main.Web3) – Anvil connection

  • tx_hash (hexbytes.main.HexBytes | str) – Transaction to trace

  • trace_method (eth_defi.trace.TraceMethod) –

    How to trace.

    Choose between debug_traceTransaction and trace_transaction RPCs.

Return type

evm_trace.base.CallTreeNode

trace_evm_call(web3, tx, trace_method=TraceMethod.parity, block_reference='latest')

Trace a Solidity function call.

  • See print_symbolic_trace() for usage

  • Extract an EVM transaction stack trace from a node, using GoEthereum compatible debug_traceTransaction

  • Currently only works with Anvil backend and if steps_trace=True

Warning

Currently not implemented. Anvil does not support trace_call RPC yet.

Parameters
  • web3 (web3.main.Web3) – Anvil connection

  • tx (dict) – Transaction object for the call

  • trace_method (eth_defi.trace.TraceMethod) –

    How to trace.

    Choose between debug_traceTransaction and trace_transaction RPCs.

Return type

evm_trace.base.CallTreeNode

print_symbolic_trace(contract_registry, calltree)

Print a symbolic trace of an Ethereum transaction.

  • Contracts by name

  • Functions by name

Notes about tracing:

  • Currently only works with Anvil backend and if steps_trace=True

  • Transaction must have its gas parameter set, otherwise transaction is never broadcasted because it fails in estimate gas phase

Example output:

E           AssertionError: Transaction failed: AttributeDict({'hash': HexBytes('0xaa70b2f76ad9f32f7c722390535d5a806b4d815f3d8d460e5d18cdba3b1c8c2d'), 'nonce': 2, 'blockHash': HexBytes('0x1d2a1d36185bebb373639e1eb4ddbe9f7f3347fa6dd7bcbbe5e5905fe6a1f4ed'), 'blockNumber': 3, 'transactionIndex': 0, 'from': '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', 'to': '0x5FbDB2315678afecb367f032d93F642f64180aa3', 'value': 0, 'gasPrice': 768647811, 'gas': 500000, 'input': '0x25ad8c83000000000000000000000000e7f1725e7734ce288f8367e1bb143e90bb3f0512', 'v': 1, 'r': HexBytes('0x43336f08be93aec7ecf456c724d3c29c6cebc589ab3fe6199ee783a627bbcda8'), 's': HexBytes('0x74002a6cdd84b81932e36ac0725591460b09eaaa6b0dd615c0c5d43171467c8a'), 'type': 2, 'accessList': [], 'maxPriorityFeePerGas': 0, 'maxFeePerGas': 1768647811, 'chainId': 31337})
E           Revert reason: execution reverted: Big bada boom
E           Solidity stack trace:
E           CALL: RevertTest.revert2(second=0xe7f1725e7734ce288f8367e1bb143e90bb3f0512) [3284 gas]
E           └── CALL: RevertTest2.boom() [230 gas]

See also

  • eth_defi.anvil.launch_anvil().

Usage example:

reverter = deploy_contract(web3, "RevertTest.json", deployer)

tx_hash = reverter.functions.revert1().transact({"from": deployer, "gas": 500_000})
receipt = web3.eth.wait_for_transaction_receipt(tx_hash)
assert receipt["status"] == 0  # Tx failed

# Get the debug trace from the node and transform it to a list of call items
trace_data = trace_evm_transaction(web3, tx_hash)

# Transform the list of call items to a human-readable output,
# use ABI data from deployed contracts to enrich the output
trace_output = print_symbolic_trace(get_or_create_contract_registry(web3), trace_data)

assert trace_output == "CALL: [reverted] RevertTest.<revert1> [500000 gas]"
Parameters
  • contract_registry (Dict[str, web3.contract.contract.Contract]) –

    The registered contracts for which we have symbolic information available.

    See eth_defi.deploy.deploy_contract() for registering. All contracts deployed using this function should be registered by default.

  • calltree (evm_trace.base.CallTreeNode) –

    Call tree output.

    From trace_evm_transaction().

Returns

Unicode print output

assert_transaction_success_with_explanation(web3, tx_hash, RaisedException=<class 'eth_defi.trace.TransactionAssertionError'>, tracing=False, func=None, timeout=120.0)

Checks if a transaction succeeds and give a verbose explanation why not..

Designed to be used on Anvil backend based tests.

If it’s a failure then print

  • The revert reason string

  • Solidity stack trace where the transaction reverted

Example usage:

tx_hash = contract.functions.myFunction().transact({"from": fund_owner, "gas": 1_000_000})
assert_transaction_success_with_explaination(web3, tx_hash)

Example output:

E           AssertionError: Transaction failed: AttributeDict({'hash': HexBytes('0xaa70b2f76ad9f32f7c722390535d5a806b4d815f3d8d460e5d18cdba3b1c8c2d'), 'nonce': 2, 'blockHash': HexBytes('0x1d2a1d36185bebb373639e1eb4ddbe9f7f3347fa6dd7bcbbe5e5905fe6a1f4ed'), 'blockNumber': 3, 'transactionIndex': 0, 'from': '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', 'to': '0x5FbDB2315678afecb367f032d93F642f64180aa3', 'value': 0, 'gasPrice': 768647811, 'gas': 500000, 'input': '0x25ad8c83000000000000000000000000e7f1725e7734ce288f8367e1bb143e90bb3f0512', 'v': 1, 'r': HexBytes('0x43336f08be93aec7ecf456c724d3c29c6cebc589ab3fe6199ee783a627bbcda8'), 's': HexBytes('0x74002a6cdd84b81932e36ac0725591460b09eaaa6b0dd615c0c5d43171467c8a'), 'type': 2, 'accessList': [], 'maxPriorityFeePerGas': 0, 'maxFeePerGas': 1768647811, 'chainId': 31337})
E           Revert reason: execution reverted: Big bada boom
E           Solidity stack trace:
E           CALL: RevertTest.revert2(second=0xe7f1725e7734ce288f8367e1bb143e90bb3f0512) [3284 gas]
E           └── CALL: RevertTest2.boom() [230 gas]

You can also pass Web3 ContractFunction instance for more information:

# Settle deposit queue 9 USDC -> 0 USDC
 settle_func = vault.settle_via_trading_strategy_module()
 tx_hash = settle_func.transact({
     "from": asset_manager,
     "gas": 1_000_000,
 })
 assert_transaction_success_with_explanation(web3, tx_hash, func=settle_func)

See also print_symbolic_trace().

Note

TODO: Currently does not work with failed contract deployment transactions.

Parameters
  • web3 (web3.main.Web3) – Web3 instance

  • tx_hash (hexbytes.main.HexBytes | str) –

    A transaction (mined/not mined) we want to make sure has succeeded.

    Gas limit must have been set for this transaction.

  • RaisedException – Raise a custom exception instead of TransactionAssertionError.

  • tracing (bool) – Force turn on transaction tracing to use in e.g testing.

  • func (web3.contract.contract.ContractFunction) –

    Bound ContractFunction instance with arguments.

    Used for diagnostics and exception messages only.

  • timeout (float) – How long to wait for the transaction receipt, seconds.

Raises

TransactionAssertionError – Outputs a verbose AssertionError on what went wrong.

Return tx_receipt

Output transaction receipt if no error is raised

Return type

web3.types.TxReceipt

assert_call_success_with_explanation(func, transaction=None)

Make a Web3.call and if it fails get the Solidity stack trace.

  • We do debug_traceCall first to see if the call fails

  • If it does not fail we do the actual eth_call

Note

Because Anvil does not support trace_call yet, we just do this as sending the transaction. We assume the call does not change any state. See notes in trace_evm_call().

If not gas given, assume 1,000,000 gas units.

Parameters
  • func (web3.contract.contract.ContractFunction) – Prepared ContractFunction call.

  • transaction (Optional[web3.types.TxParams]) – Transactional parameters for the call, like gas limit and sender.

Raises

TransactionAssertionError – Outputs a verbose AssertionError on what went wrong.

Returns

Same results as you would have with func.call(transaction)

Return type

Any

class SymbolicTreeRepresentation

Bases: object

A EVM trace tree that can resolve contract names and functions.

Lifted from eth_trace.display module.

See print_symbolic_trace() for more information.

__init__(contract_registry, call, parent=None, is_last=False)
Parameters