Skip to content

fix(evm): emit step event before dynamic-gas OOG exceptions#4343

Open
GiHoon1123 wants to merge 1 commit into
ethereumjs:masterfrom
GiHoon1123:fix-dynamic-gas-oog-step-event
Open

fix(evm): emit step event before dynamic-gas OOG exceptions#4343
GiHoon1123 wants to merge 1 commit into
ethereumjs:masterfrom
GiHoon1123:fix-dynamic-gas-oog-step-event

Conversation

@GiHoon1123

@GiHoon1123 GiHoon1123 commented Jul 9, 2026

Copy link
Copy Markdown

Summary

Fixes #4313.

The step event (used by debuggers/tracers to observe each instruction before it executes) was inconsistently emitted when an instruction ran out of gas:

  • Opcodes with a static gas fee always got a step event before the OUT_OF_GAS exception, because useGas() (which throws it) runs after the step hook in Interpreter.runStep().
  • Opcodes with a dynamic gas handler (e.g. CALL) could throw OUT_OF_GAS from inside the handler itself (e.g. during memory-expansion cost calculation), before the step hook ever ran -- so no step event was emitted for that instruction at all.

Fix

In Interpreter.runStep(), wrap the dynamic gas handler call in a try/catch. If it throws OUT_OF_GAS, emit the step event for the current opcode (mirroring what the static-gas path already does), then rethrow the original error unchanged. Gas accounting and the resulting error are untouched -- this only makes step-event emission consistent between the two paths.

Test plan

Locally:

  • New test (packages/evm/test/runCall.spec.ts): a CALL with a small gas limit and a large outLength (forcing memory-expansion cost to exceed the limit during dynamic gas calculation) now shows CALL as the last opcode observed via the step event, and confirms gas consumption / error type (OUT_OF_GAS) are unchanged.
  • Confirmed the new test is actually discriminating: it fails (expected 'PUSH1' to equal 'CALL') against the code before this fix, and passes with it.
  • packages/evm full test suite: 1178 passed. The 2 failing suites are unrelated (missing ethereum-tests git submodule fixtures) and fail identically on unmodified master.
  • biome check clean on changed files.

If an opcode has a dynamic gas handler (e.g. CALL) and that handler
itself throws out-of-gas, runStep() never reached the step-event hook,
so debuggers/tracers silently lost the last instruction executed.
Static-gas opcodes didn't have this problem, since useGas() (where
their OOG is thrown) already runs after the step hook.

Catch OUT_OF_GAS errors thrown by the dynamic gas handler, emit the
step event for the current opcode (matching what already happens for
static-gas OOG), then rethrow the original error unchanged. Gas
consumption and the resulting error are untouched -- this only makes
step-event emission consistent.
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.

Inconsistent behavior for "step" event for out-of-gas exceptions depending on instruction

1 participant