EEVM.Tracer (eevm v0.1.0)

Copy Markdown View Source

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

snapshot()

@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()
}

t()

@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

new()

@spec new() :: t()

Returns an empty tracer. tracer: true is shorthand for tracer: EEVM.Tracer.new().

op_name(arg1)

@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

steps(tracer)

@spec steps(t()) :: [EEVM.Tracer.TraceStep.t()]

Returns the recorded steps in execution order.

to_json(tracer)

@spec to_json(t()) :: String.t()

Renders the trace as a single newline-joined JSON string.

to_json_lines(tracer)

@spec to_json_lines(t()) :: [String.t()]

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.