Skip to content

fix(js): correct gas cost reporting by invoking step callback in step_end#396

Open
mattsse wants to merge 1 commit intomainfrom
fix/js-tracer-gas-cost
Open

fix(js): correct gas cost reporting by invoking step callback in step_end#396
mattsse wants to merge 1 commit intomainfrom
fix/js-tracer-gas-cost

Conversation

@mattsse
Copy link
Copy Markdown
Contributor

@mattsse mattsse commented Jan 20, 2026

Summary

This fixes the long-standing issue where log.getCost() in JS tracers returned incorrect gas costs (paradigmxyz/reth#10959, #200).

Problem

The JS step callback was invoked in step() (before opcode execution), but the gas cost is only known after the opcode executes. This caused the first opcode to report cost=0 and each subsequent opcode to report the previous opcode's cost.

Before (incorrect): [0, 3, 3] for PUSH1, PUSH1, STOP
After (correct): [3, 3, 0] for PUSH1, PUSH1, STOP

Solution

The key insight is that we need the pre-execution stack/memory state but the post-execution gas cost. This requires a two-phase approach:

  1. step(): Capture pre-execution snapshot (stack clone, memory snapshot, pc, op, etc.)
  2. step_end(): Compute gas delta and invoke JS callback with the snapshot

Changes

  • Add PendingStep struct to capture pre-execution state in step()
  • Add MemorySnapshot to properly snapshot memory contents (SharedMemory clone is shallow due to Rc)
  • Invoke the JS step callback in step_end() with captured state and correct gas cost
  • Update tests to expect correct gas costs

Testing

Verified against live node that the fix produces correct gas costs:

curl -X POST http://node:8545 -d '{
  "method": "debug_traceTransaction",
  "params": ["0x...", {"tracer": "{ ... log.getCost() ... }"}]
}'

All existing tests pass.

Breaking Change Note

JS tracer errors that occur on the final opcode (like STOP) can no longer revert the transaction since the opcode has already completed. The transaction succeeds but the trace may be incomplete. This matches the expected behavior since the tracer is observational.

Fixes: paradigmxyz/reth#10959
Closes: #200

@mattsse mattsse requested a review from DaniPopes as a code owner January 20, 2026 21:34
@mattsse mattsse force-pushed the fix/js-tracer-gas-cost branch from 1f43956 to f8fcf34 Compare January 20, 2026 22:04
…_end

This fixes the long-standing issue where `log.getCost()` in JS tracers
returned incorrect gas costs. The problem was that the JS step callback
was invoked in `step()` (before opcode execution), but the gas cost is
only known after the opcode executes.

Changes:
- Add `PendingStep` struct to capture pre-execution state (stack, memory,
  pc, op, etc.) in `step()`
- Invoke the JS step callback in `step_end()` with the captured state and
  the correctly computed gas cost
- Add `MemorySnapshot` to properly snapshot memory contents (SharedMemory
  clone is shallow due to Rc)
- Update test to expect `[3, 3, 0]` (correct PUSH1, PUSH1, STOP costs)
  instead of `[0, 3, 3]` (lagged costs)

The key insight is that we need the pre-execution stack/memory state but
the post-execution gas cost. This requires a two-phase approach:
1. `step()`: Capture pre-execution snapshot
2. `step_end()`: Compute gas delta and invoke JS callback

Fixes: paradigmxyz/reth#10959
Closes: #200

Co-authored-by: Delweng <delweng@gmail.com>
@mattsse mattsse force-pushed the fix/js-tracer-gas-cost branch from f8fcf34 to c8b6f72 Compare January 20, 2026 22:07
Copy link
Copy Markdown

@yongkangc yongkangc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an interesting approach, makes sense

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.

Fix gas cost for js tracer (Custom EVM tracer): Reported opcode gas cost is incorrect

2 participants