Skip to content

feat(tracer): opt-in opcode-level execution tracing (EIP-3155)#107

Merged
mw2000 merged 1 commit intomainfrom
issue-89
Apr 23, 2026
Merged

feat(tracer): opt-in opcode-level execution tracing (EIP-3155)#107
mw2000 merged 1 commit intomainfrom
issue-89

Conversation

@mw2000
Copy link
Copy Markdown
Owner

@mw2000 mw2000 commented Apr 21, 2026

Summary

  • add EEVM.Tracer — collects one TraceStep per opcode with PC, stack, memory size, gas, depth, refund; steps captured pre-execution so CALL appears before the child's opcodes in chronological order
  • thread tracer through CALL/CREATE frame boundaries so nested depth is recorded correctly
  • add EEVM.trace/2 entry point (returns {state, tracer}); EEVM.Tracer.to_json/1 renders EIP-3155 / geth --json lines for diffing against reference implementations

Closes #89.

Design notes

  • The tracer is nil by default. The executor's trace hooks pattern-match on tracer: nil and return the state unchanged — no snapshot work on the hot path.
  • gas_cost is recorded as the static cost known pre-execution. Dynamic portions for CALL, SSTORE, EXP, SHA3, etc. are a known limitation of this first cut; ordering and all other fields are accurate.
  • Child frames inherit the parent's tracer and write their steps into the same chronological stream; when the child returns, the updated tracer is pulled back into the parent state on both success and failure paths (for CREATE, this includes the size-exceeded / EIP-3541 / insufficient-deposit-gas branches).

Test plan

  • mix test — 547 tests, 0 failures (12 new)
  • mix format --check-formatted
  • mix credo --strict — no issues
  • mix compile --warnings-as-errors

Trace tests cover: opt-in default (nil), per-step pc/gas/stack/depth/op, out-of-gas and stack-underflow error capture, nested call depth, JSON shape and error field.

🤖 Generated with Claude Code

Closes #89.

EEVM.Tracer records one TraceStep per opcode with pre-step PC, stack,
memory size, gas, depth, and refund. Output renders as EIP-3155 / geth
--json lines for diffing against reference runs. Steps are captured
before each opcode so CALL appears before the child's opcodes in the
trace, and the tracer is threaded through CALL/CREATE frame boundaries
to keep depth correct.

The tracer is nil by default; when unset, the executor's trace hooks
are pattern-matched away with no snapshot work. Opt in via
EEVM.trace/2 or pass tracer: EEVM.Tracer.new() to MachineState.new/2.

gas_cost is recorded as the static cost known pre-execution; dynamic
portions (CALL/SSTORE/EXP etc.) are a known limitation of this first cut.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@mw2000 mw2000 marked this pull request as ready for review April 23, 2026 06:54
@mw2000 mw2000 merged commit e542721 into main Apr 23, 2026
2 checks passed
@mw2000 mw2000 deleted the issue-89 branch April 23, 2026 06:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add opcode-level execution tracing

1 participant