---
eip: 7756
title: EOF/EVM Trace Specification
description: Updates EIP-3155 tracing to add EOF support
author: Martin Holst Swende (@holiman), Marius van der Wijden (@MariusVanDerWijden), Danno Ferrin (@shemnon)
discussions-to: https://ethereum-magicians.org/t/eip-7756-eof-evm-trace-specification/20806
status: Stagnant
type: Standards Track
category: Interface
created: 2024-08-13
requires: 3155, 4750
---

## Abstract

Updates the [EIP-3155](./eip-3155.md) JSON tracing specification to support EOF features.

## Motivation

EIP-3155 defined a tracing standard for Legacy EVM operations. However, the EVM Object
Format ([EIP-7692](./eip-7692.md)) 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:

- Add members to the trace object to support new EOF features.
- Support tracing contracts contained in an EOF container as well as uncontained "legacy" contracts
  in the same trace.
- Clarify any previous ambiguities in the EIP-3155 specification.

## Specification

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.

### Datatypes

| 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`                         |

- Clients may OPTIONALLY output Number where a Hex-Number is expected and vice-versa. Clients that
  do this SHOULD provide a CLI option to strictly observe the correct output type.
- Numbers larger than 2^53 - 1 must be represented as Hex-Numbers. This is a limitation of JSON.
- Consumers of EIP-7756 traces SHOULD be written in a way that members of type Hex-Number and 
  Number may be provided a Number or Hex-Number instead, respectively.
- Note that there is no string formatted decimal number alternative. Decimal representations are 
  always JSON numbers, and hex representations are always strings encoding hex numbers.

### Output

- The client outputs one JSON object per EVM operation executed.
- The client MUST NOT output multiple lines for the same operation execution.
- The client MUST NOT output a line for the `STOP` operation if an error occurred, or if the
  contract runs out of instructions.

#### Required Fields

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) |

- The `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.

<!-- - `stack` MAY exclude stack items that are more than any operation will need to execute, which 
is currently seven stack items. If any stack items are excluded past that point then all stack items
past that point MUST be excluded
-->

#### Recommended Fields

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.

#### Optional Fields

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](./eip-4750.md) CALLF return stack |
| `memory`        | Array of Hex-Strings | Array of all allocated values                             |
| `storage`       | Key-Value            | Array of all stored values                                |

- The `section` member must only be present when tracing a contract contained in an EOF container.
- The `immediate` field MUST NOT be present for operations without immediate data.
    - For PUSH series operations, this field is OPTIONAL as the immediate data is pushed onto the
      stack
    - For RJUMPV this would include the table length and the entire table. Clients MAY instead store
      just the table length.
    - For all other operations with immediate data the entire immediate data, including leading
      zeros, SHOULD be present.
- `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.
- If `memory` is empty an empty array (`[]`) is used instead of `null`.
- The `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"}
```

### Summary Line

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}
```

## Rationale

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.

## Backwards Compatibility

Clients emitting tracing JSON for uncontained "legacy" contracts will produce a compatible trace,
except as outlined below

### Changes from EIP-3155

- The term Client Under Test or CUT has been replaced simply with "client."
- `gas`, `gasCost`, and `gasUsed` are now Number types.
- `opcode` is now a Hex-Number type.

### Additions to EIP-3155

- The `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.
- The `section` and `functionDepth` members were added to support [EIP-4750](./eip-4750) EOF 
  Functions.
- Added clarification around where `pc` indexes when run in an EOF container.
- Added Hex-Number/Number interchangeability expectations. Clients MAY provide either types when
  one is specified and consumers SHOULD be prepared to accept either type. Clients SHOULD provide a
  flag to output members with the specified number type only.

### Clients

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.

## Test Cases

This is the trace output from the Ethereum Execution Specification Test from one of the
parameterized executions of [test_eof_functions_contract_call_succeed](https://github.com/ethereum/execution-spec-tests/blob/632d151ea8a71d09a3a0acbdb85d97fa18c8456b/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_execution_function.py#L304-L335).
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}
```

## Security Considerations

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

Copyright and related rights waived via [CC0](../LICENSE.md).
