# `EEVM.Tracer`
[🔗](https://github.com/mw2000/eevm/blob/v0.1.0/lib/eevm/tracer.ex#L1)

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.

# `snapshot`

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

```elixir
@type t() :: %EEVM.Tracer{
  costs: %{required(non_neg_integer()) =&gt; non_neg_integer()},
  count: non_neg_integer(),
  open: [{non_neg_integer(), non_neg_integer(), non_neg_integer(), boolean()}],
  steps: [EEVM.Tracer.TraceStep.t()]
}
```

# `new`

```elixir
@spec new() :: t()
```

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

# `op_name`

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

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

Returns the recorded steps in execution order.

# `to_json`

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

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

# `to_json_lines`

```elixir
@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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
