Skip to content

Instantly share code, notes, and snippets.

@edg-l
Last active May 13, 2026 15:46
Show Gist options
  • Select an option

  • Save edg-l/f4178058d86c75a9cb4a47b377c88a5a to your computer and use it in GitHub Desktop.

Select an option

Save edg-l/f4178058d86c75a9cb4a47b377c88a5a to your computer and use it in GitHub Desktop.
Gas Tracer
title Structured Gas Trace Format
description JSON-lines trace events for individual gas charges, refunds, and dimensional transfers
author Edgar Luque (@edgl)
discussions-to https://ethereum-magicians.org/t/<REPLACE-with-real-thread-id>
status Draft
type Standards Track
category Interface
created 2026-05-13
requires 3155, 7778, 8037

Abstract

A JSON-lines event format, emitted alongside an EIP-3155 trace, that records every individual gas charge, gas refund, and dimensional transfer performed during transaction execution. The format makes the internal steps of two-dimensional gas accounting (EIP-7778, EIP-8037) observable, so that two implementations producing different gasUsed values can be aligned event-by-event to find the first divergence.

Motivation

EIP-3155 records one entry per opcode. The gasCost field is the total charged for that opcode and hides the individual charges that produced it. A single CALL can charge cold-account access, value transfer, memory expansion, state-gas, and forwarded subcall budget; an EIP-3155 trace shows only the sum.

Two-dimensional gas accounting introduces a second metering channel (state_gas) with its own reservoir, spill behavior, refund routing, and intrinsic-refund channels. A client that miscounts by one NEW_ACCOUNT_state_gas charge in a SELFDESTRUCT path produces the same gasUsed difference as a client that correctly charges but forgets to refund. EIP-3155 cannot distinguish these.

Today the debugging workflow is to add printf statements to both the client and the reference specification, align the two textual logs by hand, and find the first divergence. This EIP defines a stable, line-oriented event format so the same alignment can be done by a tool.

Specification

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.

Wire format

Each event is one line of JSON terminated by \n. Event lines MAY be interleaved with EIP-3155 opcode lines on the same stream. Consumers MUST distinguish events by the type field; all event types defined here are namespaced under gas..

Gas values are unsigned 64-bit integers. Addresses are 20-byte hex strings with 0x prefix. The frame field is the call-stack depth at the time of emission; the top-level transaction frame is frame: 0.

Event types

type Triggered by Required fields
gas.intrinsic After intrinsic-gas calculation, before execution regular, state, floor, reservoir_initial
gas.charge.regular Any decrement of regular gas amount, frame, opcode, pc, reason, gas_remaining_after
gas.charge.state Any charge against the state-gas reservoir amount, frame, opcode, pc, reason, from_reservoir, spill_to_regular, reservoir_after, state_gas_used_after
gas.refund.state Any credit to the state-gas refund channel amount, frame, opcode, pc, reason, applied_to_reservoir, applied_to_pending
gas.refund.regular Any addition to the EIP-3529 refund counter amount, frame, opcode, pc, reason, refund_counter_after
gas.transfer.subcall A child frame is entered (CALL, CREATE) regular_to_child, state_to_child, parent_regular_after, parent_state_after
gas.transfer.return A child frame returns regular_from_child, state_from_child, pending_from_child, parent_regular_after, parent_state_after
gas.intrinsic.refund Transaction-end intrinsic state-gas refunds amount, channel, reason
gas.summary Once per transaction, after all refunds applied regular_used, state_used_net, state_refund, reservoir_final, sender_debit, coinbase_credit

channel in gas.intrinsic.refund is one of reservoir or state_refund.

The reason field is a short string drawn from a registry colocated with this EIP. Examples: op_create.new_account, op_sstore.storage_set, op_call.value_to_empty, op_selfdestruct.beneficiary_new_account, tx_create.collision_refund, eip7702.auth_base_refill. Unknown reasons MUST be passed through unchanged; new charge sites added in future forks add new reasons without requiring an EIP per change.

Invariants

Implementations MUST satisfy the following for every traced transaction:

  • The sum of gas.charge.regular.amount minus the sum of gas.refund.regular.amount (after EIP-3529 capping) equals gas.summary.regular_used.
  • The sum of gas.charge.state.amount minus the sum of gas.refund.state.amount minus the sum of gas.intrinsic.refund.amount (where channel = state_refund) equals gas.summary.state_used_net.
  • For each gas.charge.state, from_reservoir + spill_to_regular == amount.
  • gas.summary.sender_debit == (regular_used + Σ gas.charge.state.spill_to_regular) × effective_gas_price.
  • Every gas.transfer.subcall is paired with exactly one gas.transfer.return. On error in the child, the return event has an additional error field; the pairing requirement is unchanged.

Activation

This EIP defines a debug surface; there is no consensus impact and no protocol activation. Clients SHOULD emit the format when a debug flag (for example --trace-gas) is set. Reference specifications (EELS, py-evm) SHOULD emit the format under their existing trace flag.

Rationale

Why not extend EIP-3155?

EIP-3155 emits one event per opcode. Multiple charges fire within a single opcode and the order matters for spill-and-clamp behavior. Adding a list of sub-charges to each EIP-3155 entry would change the shape of an established format and break existing consumers.

Why JSON-lines

This is a debug surface, not a consensus surface. Existing EIP-3155 consumers already use line-oriented JSON; a compact binary encoding can be standardized later if I/O cost becomes a bottleneck.

Why reason is a string and not derived from opcode + pc

Many charges fire in helper code where the responsible opcode is ambiguous or absent (frame entry/exit accounting, EIP-7702 authorization processing, end-of-transaction SELFDESTRUCT settlement). A stable string lets a diff tool produce actionable error messages without re-deriving the spec.

Backwards Compatibility

The format is additive. Consumers of EIP-3155 traces ignore lines with unknown top-level fields. There is no change to consensus, RPC, or any persisted format.

Test Cases

A reference event stream is provided in assets/eip-####/sd-create-sstore.jsonl. The fixture is a single transaction that:

  1. CREATEs an inner contract.
  2. The inner contract SELFDESTRUCTs to itself.
  3. The outer frame CALLs the inner with value = 1.
  4. The outer frame SSTOREs the CALL result.

Under EIP-8037 with the same-transaction SELFDESTRUCT state-gas refund removed, the stream contains exactly one gas.charge.state with reason = op_create.new_account and exactly one with reason = op_sstore.storage_set. Any additional gas.charge.state with the same reason, or any gas.refund.state with reason = tx_end.sd_refund, indicates non-conformance with the v7.1.0 spec.

Security Considerations

The trace contains gas-accounting internals and reveals nothing not already derivable from a full EIP-3155 trace plus the post-state.

Tracing is expensive. Clients MUST NOT emit traces on production endpoints by default. Emission should be gated on a build feature and on an explicit flag or environment variable.

Copyright

Copyright and related rights waived via CC0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment