EIP-8455: RESERVOIR Opcode

Discussion topic EIP-8455

Summary

EIP-8037 splits gas into execution gas and state gas, and lets state gas be paid from a reservoir that GAS does not report. EIP-8141 frame transactions have a similar per-frame state_gas_left. As a result, contracts can no longer see their full remaining budget, and measuring a sub-call with two gasleft() readings under-counts any state gas the reservoir paid for.

This EIP adds one opcode, RESERVOIR (0x4d), which pushes the state gas available to the current frame that GAS does not include:

  • for reservoir-model transactions (EIP-8037), the frame’s state_gas_reservoir
  • inside an EIP-8141 frame transaction, the frame’s state_gas_left

GAS and RESERVOIR together let a contract read its whole remaining budget and measure the full cost of a code region or call, state gas included.

Motivation

Protocols that meter sub-calls by subtracting two gasleft() readings, such as the ERC-4337 EntryPoint, under-count state gas whenever the reservoir pays for it. A call that creates one storage slot looks 97,920 gas cheaper when the reservoir funds the slot than when gas_left does. EIP-8037 points this out and asks such protocols to account for state gas explicitly, but no instruction currently exposes the value they need. RESERVOIR exposes it.

Design notes

  • Opcode 0x4d. It sits in the block/transaction environment range (0x40–0x4f) and is currently unassigned. There is no free byte next to GAS (0x5a).
  • GAS is not redefined. Making GAS return gas_left + reservoir would break its main use as the CALL gas argument, which is capped at 63/64 of gas_left. It would also change behavior for deployed contracts.
  • Separate instruction rather than a combined one. One opcode returning both pools was rejected to keep the interface aligned with GAS. A CALL variant taking a state-gas limit is a larger change that could later build on RESERVOIR.
  • EIP-7702. Authorization charges are applied before the first frame and are not refilled by a top-level revert, so delegated code cannot observe an inflated value.

Security considerations

  • RESERVOIR depends on the inclusion context the same way GAS does. ERC-7562 validation rules restrict GAS during ERC-4337 validation and should restrict RESERVOIR in the same way.
  • Contracts whose success depends on RESERVOIR can make the set of sufficient tx.gas values non-monotonic, as with GAS, which affects gas estimation.
  • State gas prices and the execution/state split may change in later forks, so contracts should measure consumption rather than hard-code expected amounts.

Reference implementation & tests

The assets include patches for go-ethereum and execution-specs, plus test fillers covering both the reservoir model and frame transactions.

Update Log

External Reviews

None as of 2026-10-28.

Outstanding Issues

None as of 2026-10-28.

Feedback is especially welcome on:

  • the choice of 0x4d versus other free bytes, given pending proposals for 0x4c, 0x4e and 0x4f
  • whether ERC-4337 / ERC-7562 maintainers agree on the validation-time restriction
  • whether returning the reservoir alone is enough, or a combined view would be useful too

Two notes from the 4337 side.

ERC-7562 doesn’t block GAS outright: OP-012 allows it when it’s immediately followed by a *CALL, since that’s its real use as a gas argument. RESERVOIR has no use like that, and its value starts as whatever the bundler put in tx.gas above the EIP-7825 cap, so the same UserOp can read different values in simulation and in the bundle. I’d list it in OP-011 next to SLOTNUM rather than mirror GAS. Today 0x4d is caught by OP-013 as an unassigned opcode, and it drops out of that rule the day it’s assigned.

On reservoir alone vs a combined view, worth saying in the spec that a measurement has to diff GAS + RESERVOIR as one sum. When a child call succeeds, the parent moves min(state_gas_reservoir, state_gas_from_gas_left) from the reservoir back into gas_left (the step 8037 has for refills that land in a different frame than their charge), so either value diffed alone can misreport the call’s cost, while the sum stays put.

Thanks @Zealynx, both points are spot on. I’ve updated the draft and the EELS test suite.

On ERC-7562: agreed. GAS only gets a pass under OP-012 when feeding a *CALL, and RESERVOIR has no equivalent use. It’s also less bounded than GAS since the full reservoir is forwarded to every frame regardless of the validation gas limit, and its starting value just reflects whatever tx.gas the bundler set above the EIP-7825 cap (plus any ops that ran earlier in the bundle). I updated Security Considerations to place RESERVOIR under OP-011 next to SLOTNUM, with a note that this needs to land by activation since 0x4d drops out of OP-013 once assigned. Happy to open the ERC-7562 PR when this gets closer to being scheduled.

On diffing the sum vs. individual counters: good catch. The draft defined GAS + RESERVOIR as the frame’s remaining budget, but didn’t explicitly warn against diffing either reading on its own across a call. I added a paragraph under Relationship to GAS pointing to the EIP-8037 merge step (min(state_gas_reservoir, state_gas_from_gas_left)) as the reason. While I was in there, I also tightened Security Considerations: a drop in RESERVOIR across a call still shows it drew on the reservoir, without implying that an unchanged value means zero state gas was charged.

I also added your cross-frame refill scenario (first child creates a slot, second child clears it) to the spec and the EELS fillers:

  • With R = 500,000, the refill stays in RESERVOIR: the caller reads R again, and GAS only drops by the execution cost of the call.
  • With tx.gas <= TX_MAX_GAS_LIMIT, the clearing child sees RESERVOIR = 97,920, then the merge step shifts it back into gas_left, leaving the caller at RESERVOIR = 0 while its GAS rises.

In both runs GAS + RESERVOIR goes up across the call as expected (also sanity-checked the filler by disabling the merge step in EELS, which breaks the second case).