Opt-in opcode-level execution tracer.
Pass tracer: true to EEVM.run/2, EEVM.transact/4, or
EEVM.Block.execute/4 and the result's trace holds a tracer with one
EEVM.Tracer.TraceStep per executed opcode, in every call frame. Read the
steps with steps/1, or render them with to_json/1 in the EIP-3155
format that geth evm --json and revm emit, so traces can be diffed
against theirs.
iex> {:ok, result} = EEVM.run(<<0x60, 1, 0x60, 2, 0x01, 0x00>>, tracer: true)
iex> for step <- EEVM.Tracer.steps(result.trace), do: {step.pc, step.op, step.stack}
[{0, :PUSH1, []}, {2, :PUSH1, [1]}, {4, :ADD, [2, 1]}, {5, :STOP, [3]}]Gas cost
A step's gas_cost is what geth's structLog reports as gasCost: the
static and dynamic gas the opcode charges, memory expansion included.
- CALL, CALLCODE, DELEGATECALL, and STATICCALL count the gas they forward
to the callee, as geth does. The callee's own steps spend it, and what
the callee leaves comes back to the caller, so the caller's next step
shows it in
gas_remaining. - CREATE and CREATE2 do not count the gas they forward to the init code, as in geth, where the forwarding happens inside the opcode.
- A step that fails reports the cost it attempted, not the remaining gas the failure burns: its static cost when its static gas or stack check fails, and what it charged, failing charge included, when it runs out of gas while running. INVALID costs 0.
So when no frame halts exceptionally and nothing is created, a
transaction's gas_used is its intrinsic gas plus the cost of all its
steps, less the gas each call forwards (the callee's steps count it again),
less the refund.
These are the semantics of geth's core/vm/interpreter.go, which reports
the constant gas plus the dynamic gas function's result, and of its
makeCallVariantGasCost, whose dynamic gas includes the forwarded gas.
Performance
Without :tracer, the interpreter skips all trace bookkeeping: no
snapshot is taken and the tracer branch is a single pattern match.
Summary
Functions
Returns an empty tracer. tracer: true is shorthand for tracer: EEVM.Tracer.new().
Returns the opcode name atom for a byte, or :UNKNOWN for an undefined one.
Returns the recorded steps in execution order.
Renders the trace as a single newline-joined JSON string.
Renders the trace as EIP-3155 / geth --json compatible JSON lines.
Types
@type snapshot() :: %{ pc: non_neg_integer(), op_byte: non_neg_integer(), op: atom(), gas: non_neg_integer(), stack: [non_neg_integer()], memory_size: non_neg_integer(), depth: non_neg_integer(), refund: integer() }
@type t() :: %EEVM.Tracer{ costs: %{required(non_neg_integer()) => non_neg_integer()}, count: non_neg_integer(), open: [{non_neg_integer(), non_neg_integer(), non_neg_integer(), boolean()}], steps: [EEVM.Tracer.TraceStep.t()] }
Functions
@spec new() :: t()
Returns an empty tracer. tracer: true is shorthand for tracer: EEVM.Tracer.new().
@spec op_name(non_neg_integer()) :: atom()
Returns the opcode name atom for a byte, or :UNKNOWN for an undefined one.
iex> EEVM.Tracer.op_name(0x01)
:ADD
iex> EEVM.Tracer.op_name(0x0C)
:UNKNOWN
@spec steps(t()) :: [EEVM.Tracer.TraceStep.t()]
Returns the recorded steps in execution order.
Renders the trace as a single newline-joined JSON string.
Renders the trace as EIP-3155 / geth --json compatible JSON lines.
Returns a list of JSON-encoded strings, one per step. Join with newlines for geth-style output.