Skip to content

fix(evm): rescue gas before inspector callback to preserve remaining gas#171

Open
krabat-l wants to merge 2 commits intomainfrom
krabat/fix/inspector-rescue-gas
Open

fix(evm): rescue gas before inspector callback to preserve remaining gas#171
krabat-l wants to merge 2 commits intomainfrom
krabat/fix/inspector-rescue-gas

Conversation

@krabat-l
Copy link

@krabat-l krabat-l commented Feb 27, 2026

Summary

Move rescue_gas to happen before the inspector's frame_end callback instead of saving/restoring gas around it.
This prevents inspectors like TracerEip3155 (which calls Gas::spend_all() on error results in call_end) from zeroing gas.remaining() before rescue_gas captures it.

Changes

  • Add try_rescue_gas method on AdditionalLimit that checks check_limit() and rescues gas if a TX-level limit was exceeded
  • Call try_rescue_gas in after_frame_run (covers frame_run and inspect_frame_run paths) and in frame_init (before_frame_init early-return path)
  • Remove rescue_gas from before_frame_return_result — just mark the result as exceeding limit
  • Remove the save/restore gas pattern from inspect_frame_run and inspect_frame_init
  • Add GasSpendingInspector test inspector and two tests covering both inspect_frame_init and inspect_frame_run paths

…ue_gas

GasInspector::call_end (revm) calls gas.spend_all() on error results
such as OutOfGas. This is called from frame_end in inspect_frame_run
and inspect_frame_init, before frame_return_result has a chance to
call rescue_gas(). The rescue_gas mechanism saves the remaining gas
of a frame halted by MegaETH's additional limits (e.g. state growth
limit) so it can be refunded to the sender in last_frame_result.

With the inspector path, spend_all() zeroes gas.remaining() first,
so rescue_gas() reads 0 and the refund is lost. This causes different
gas_used values compared to the non-inspector path, resulting in a
receipts root mismatch when validating blocks.

Fix: save gas.remaining() before calling frame_end, then restore it
afterwards if the inspector callback reduced it. The inspector still
receives the unmodified (pre-spend_all) gas value for its callbacks,
which is fine since GasInspector computes last_gas_cost from the
delta between step() and step_end() readings rather than from the
final remaining value.
@krabat-l krabat-l requested review from Troublor and flyq February 27, 2026 10:16
Move rescue_gas to happen before the inspector's frame_end callback
instead of saving/restoring gas around it. This is structurally correct
because TX-level limit exceeds are always detected during execution,
never newly discovered in before_frame_return_result.

- Add try_rescue_gas method on AdditionalLimit that checks check_limit()
- Call try_rescue_gas in after_frame_run and frame_init (before_frame_init
  early-return path)
- Remove rescue_gas from before_frame_return_result
- Remove save/restore gas pattern from inspect_frame_run and
  inspect_frame_init
- Add tests with GasSpendingInspector covering both inspect_frame_init
  and inspect_frame_run paths
@Troublor Troublor changed the title fix(evm): restore frame gas after inspector callback to preserve rescue_gas fix(evm): rescue gas before inspector callback to preserve remaining gas Feb 27, 2026
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.

3 participants