Updates the EIP-3155 JSON tracing specification to support EOF features.
EIP-3155 defined a tracing standard for Legacy EVM operations. However, the EVM Object Format (EIP-7692) adds a number of features that need to be reflected in debugging traces.
The use of these traces has also moved out from state testing, including live block tracing and differential fuzzing, increasing the need to keep tracing up to date.
This EIP has multiple goals:
To promote clarity and provide a cohesive specification, the entire tracing specification will be presented with alterations in-line rather than as a set of diffs on top of EIP-3155. Differences will be highlighted in the Backwards Compatibility section.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", " RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
Type | Explanation | JSON member example |
---|---|---|
Number | JSON number | "pc":0 |
Hex-Number | Hex-encoded number | "gas":"0x2540be400" |
String | Plain string | "opName":"PUSH1" |
Hex-String | Hex-encoded string | "returnData":"0x60616203" |
Array | Array of Hex-Strings | "stack":["0x0", "0x0"] |
Key-Value | Key-Value structure with key and values encoded as hex strings | "storage":{"0x0":"0x1", "0x1":"0x1"} |
Boolean | Json bool can either be true or false | "pass": true |
STOP
operation if an error occurred, or if the
contract runs out of instructions.Each trace line MUST have these fields.
Name | Type | Explanation |
---|---|---|
pc |
Number | Program Counter |
op |
Hex-Number | OpCode |
opName |
String | Name of the operation |
gas |
Number | Gas left before executing this operation |
stack |
Array of Hex-Numbers | Array of all values on the stack |
depth |
Number | Depth of the call stack |
error |
Hex-String | Description of an error (SHOULD contain revert reason if supported) |
pc
value is zero indexed from the beginning of the contract when the contract is not in an
EOF container, or from the beginning of the container EOF container. For legacy contracts the
first execution line is for "pc":0
. For EOF contracts the zero byte corresponds to the 0xEF
magic byte. The first line of execution will not be at "pc":0
but at the first byte of the first
code section executed.opName
SHOULD be the most current name of the operation, in cases where operations have multiple
specification names (SELFDESTRUCT
and PREVRANDAO
)
If stack
is empty an empty array ([]
) is used instead of null
.
depth
starts at 1.error
SHOULD be omitted if a prior CALL series or CREATE series operation within the current
frame has not returned a revert reason and the current operation did not trigger an exceptional
halt.Each trace line SHOULD have these fields in the conditions they are indicated for.
Name | Type | Explanation |
---|---|---|
gasCost |
Number | Gas cost of this operation |
memSize |
Number | Size of memory array |
returnData |
Hex-String | Data returned by function call |
refund |
Hex-Number | Amount of global gas refunded |
gasCost
is the sum of all gas costs, including dynamic costs such as memory expansion, call
stipend, and account warming costs.memSize
is counted in 8-bit bytes, not 256-bit words.returnData
SHOULD NOT be present if a CALL series operation has not completed within the current
frame.Each trace line MAY have these fields in the conditions they are indicated for. If a field is to be omitted within a trace, it MUST always be omitted within the same trace.
Name | Type | Explanation |
---|---|---|
section |
Number | Current EOF section being executed |
immediate |
Hex-String | Immediate argument of the Opcode |
functionDepth |
Number | Depth of the EIP-4750 CALLF return stack |
memory |
Array of Hex-Strings | Array of all allocated values |
storage |
Key-Value | Array of all stored values |
section
member must only be present when tracing a contract contained in an EOF container.immediate
field MUST NOT be present for operations without immediate data.functionDepth
starts at 1, and MAY be omitted if it is 1. functionDepth
MUST NOT be present for trace lines of code not in an EOF container.memory
is empty an empty array ([]
) is used instead of null
.storage
member SHOULD only include items read or written via SSTORE
or SLOAD
, and not
the account's entire storage.Example:
{"pc":0,"op":96,"gas":"0x2540be400","gasCost":"0x3","memory":"0x","memSize":0,"stack":[],"depth":1,"error":null,"opName":"PUSH1"}
At the end of execution, the client SHOULD print summary info. This summary MUST be a single JSON object.
This info SHOULD have the following members.
Name | Type | Explanation |
---|---|---|
stateRoot |
Hex-String | Root of the state trie after executing the transaction |
output |
Hex-String | Return values of the function |
gasUsed |
Number | All gas used by the transaction |
pass |
Boolean | If the tx was successful, or if the test passed |
time |
Number | Time in nanoseconds needed to execute the transaction |
fork |
String | Name of the fork rules used for execution |
time
and fork
fields MAY be provided.Example:
{"stateRoot":"0xd4c577737f5d20207d338c360c42d3af78de54812720e3339f7b27293ef195b7","output":"","gasUsed":"0x3","pass":"true","time":141485}
This EIP is an extension of the EIP-3155 tracing features that has been in use for years. Rather than dramatically re-boot the feature, the information was added to the existing traces.
A "mini" trace was contemplated to allow for tracing to be included in tools such as
t8n
and to allow for more efficient RPC tracing calls, but that seemed sufficiently different that
it would be a stand-alone EIP rather than an EIP that adds features to the existing tracing
capabilities.
The idea of moving to a JSON Schema was rejected to ensure maximum compatibility with existing clients.
Clients emitting tracing JSON for uncontained "legacy" contracts will produce a compatible trace, except as outlined below
gas
, gasCost
, and gasUsed
are now Number types.opcode
is now a Hex-Number type.immediate
member was added to support the large number of instructions that contain
immediate operations. Without this change, users would need bytes of the contracts being executed
to rationalize the traces.section
and functionDepth
members were added to support EIP-4750 EOF
Functions.pc
indexes when run in an EOF container.Besu, evmone, EthereumJS, Geth, Nethermind, and Reth already produce these standard traces in various tools. Adding the new fields will align with work needed to support the EOF EIPs enumerated in EIP-7692.
This is the trace output from the Ethereum Execution Specification Test from one of the parameterized executions of test_eof_functions_contract_call_succeed. Memory and return data is disabled.
json lines
{"pc":0,"op":"0x60","gas":49979000,"gasCost":3,"memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":2,"op":"0x60","gas":49978997,"gasCost":3,"memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":4,"op":"0x60","gas":49978994,"gasCost":3,"memSize":0,"stack":["0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":6,"op":"0x60","gas":49978991,"gasCost":3,"memSize":0,"stack":["0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":8,"op":"0x60","gas":49978988,"gasCost":3,"memSize":0,"stack":["0x0","0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":10,"op":"0x61","gas":49978985,"gasCost":3,"memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH2"}
{"pc":13,"op":"0x5a","gas":49978982,"gasCost":2,"memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0x1000"],"depth":1,"refund":0,"opName":"GAS"}
{"pc":14,"op":"0xf1","gas":49978980,"gasCost":49198100,"memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0x1000","0x2fa9e64"],"depth":1,"refund":0,"opName":"CALL"}
{"pc":25,"section":0,"op":"0xe3","immediate":"0x0001","gas":49195500,"gasCost":5,"memSize":0,"stack":[],"depth":2,"refund":0,"opName":"CALLF"}
{"pc":29,"section":1,"op":"0xe4","gas":49195495,"gasCost":3,"memSize":0,"stack":[],"depth":2,"functionDepth":2,"refund":0,"opName":"RETF"}
{"pc":28,"section":0,"op":"0x00","gas":49195492,"gasCost":0,"memSize":0,"stack":[],"depth":2,"refund":0,"opName":"STOP"}
{"pc":15,"op":"0x60","gas":49976372,"gasCost":3,"memSize":0,"stack":["0x1"],"depth":1,"refund":0,"opName":"PUSH1"}
{"pc":17,"op":"0x55","gas":49976369,"gasCost":22100,"memSize":0,"stack":["0x1","0x0"],"depth":1,"refund":0,"opName":"SSTORE"}
{"pc":18,"op":"0x00","gas":49954269,"gasCost":0,"memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP"}
{"output":"","gasUsed":45731,"fork":"Osaka","postHash":"0x2c47b4070c1eef501d9548959c3abde2d8dc78ed1d819697c61d2b0861cc78cf","pass":true}
Clients should be aware that tracing can be expensive both in terms of CPU overhead and network bandwidth. Tracing endpoints should not be enabled by default, and when they are enabled should have access restrictions on the network level. Failure to do so could result in a client being overwhelmed with requests and, if operating as a validator, cause the client to fail to provide execution attestations in a timely manner.
Differential fuzzing is also a double-edged sword. While it allows client teams the ability to identify consensus splits, the client teams need to be prompt in fixing any issues that are discovered.
Copyright and related rights waived via CC0.