Skip to content

Update EIP-8037: clarify reservoir mechanics#11328

Draft
spencer-tb wants to merge 3 commits intoethereum:masterfrom
spencer-tb:eip8037-clarifications
Draft

Update EIP-8037: clarify reservoir mechanics#11328
spencer-tb wants to merge 3 commits intoethereum:masterfrom
spencer-tb:eip8037-clarifications

Conversation

@spencer-tb
Copy link
Contributor

@spencer-tb spencer-tb commented Feb 17, 2026

Clarify EIP-8037 reservoir mechanics and fix minor issues identified during EELS implementation.

Remove stray <-TODO->, document calldata floor interaction with refunds, clarify refund_counter is a single undimensioned counter and that EIP-7702 authorization refund goes to state_gas_reservoir.

ethereum/execution-specs#2181

@github-actions github-actions bot added c-update Modifies an existing proposal s-draft This EIP is a Draft t-core labels Feb 17, 2026
@eth-bot
Copy link
Collaborator

eth-bot commented Feb 17, 2026

File EIPS/eip-8037.md

Requires 1 more reviewers from @adietrichs, @anderselowsson, @CPerezz, @fradamt, @jochem-brouwer, @LukaszRozmej, @misilva73

@eth-bot eth-bot added the a-review Waiting on author to review label Feb 17, 2026
@eth-bot eth-bot changed the title chore(eip-8037): clarify reservoir mechanics Update EIP-8037: clarify reservoir mechanics Feb 17, 2026
EIPS/eip-8037.md Outdated
- The `GAS` opcode returns `gas_left` only (excluding the reservoir).
- The reservoir is passed **in full** to child frames (no 63/64 rule). Unused reservoir is returned to the parent on child completion.
- On **exceptional halt**, both `gas_left` and `state_gas_reservoir` are set to zero (all gas consumed), consistent with existing EVM out-of-gas semantics. This is not applied to system transactions.
- The reservoir is passed **in full** to child frames (no 63/64 rule). On child **revert**, unused `state_gas_reservoir` is returned to the parent. On child **exceptional halt**, `state_gas_reservoir` is zeroed and nothing is returned.
Copy link
Contributor

@misilva73 misilva73 Feb 18, 2026

Choose a reason for hiding this comment

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

@rakita had a comment on this that I think we should think through.

When a CALL (or DELEGATECALL/CALLCODE) is made, the caller specifies a single gas value g. With multidim metereing, we need to figure out three things:

  1. Forwarding: How much of gas_left and reservoir does the subcall receive?
  2. Success: How are unused portions returned?
  3. Failure: What happens on REVERT vs. exceptional halt (OOG)?

Here is a map of some options and their pros and cons.

Option 1: Forward all reservoir; return reservoir on any failure (this is the proposal from @rakita )

Mechanics:

  • Subcall receives: call_gas_left = min(g, 63/64 * caller.gas_left), call_reservoir = caller.reservoir (all of it)
  • On success: return unused call_gas_left and unused call_reservoir to caller
  • On REVERT: return unused call_gas_left and unused call_reservoir to caller
  • On exceptional halt: consume call_gas_left (zeroed), return unused call_reservoir to caller

Rationale for returning reservoir on halt: State changes are reverted on failure, so no state was actually grown — the state gas wasn't "consumed" in any economic sense.

Pros:

  • Subcalls have full access to state gas — large contract deployments via factories work without issues
  • Reservoir is never "wasted" on failures, which is consistent with the principle that state gas pays for long-term state growth (which didn't happen)
  • Simple forwarding logic (just pass it all through)
  • Enables the full benefit of uncapped state gas in nested calls

Cons:

  • The gas limit g no longer bounds total spending — a subcall to an untrusted contract can consume unbounded state gas from the caller's reservoir
  • Breaks composability expectations: callers cannot limit how much state gas a subcall uses
  • Existing patterns that use gas limits for economic protection (e.g., limiting gas sent to an external callback) become insufficient
  • A malicious or buggy callee can drain the caller's entire reservoir even though the caller only intended to send g gas

Option 2: Forward all reservoir; consume reservoir on exceptional halt (this is the current proposal)

Mechanics:

  • Subcall receives: call_gas_left = min(g, 63/64 * caller.gas_left), call_reservoir = caller.reservoir
  • On success: return unused call_gas_left and unused call_reservoir
  • On REVERT: return unused call_gas_left and unused call_reservoir
  • On exceptional halt: consume both call_gas_left and call_reservoir (zeroed)

Pros:

  • Consistent with current EVM behavior where exceptional halts consume all forwarded gas
  • Provides a strong penalty for OOG, discouraging underestimation of gas
  • Full reservoir access for subcalls

Cons:

  • Extremely punishing — a single OOG in any subcall wipes the entire transaction's remaining state budget
  • Makes calling untrusted contracts very risky (they can force-burn your reservoir by triggering OOG)
  • Same composability problem as Option 1 (caller can't bound subcall state gas usage)
  • The punishment is arguably disproportionate since no state was actually grown

Option 3: Forward reservoir proportional to gas fraction; return reservoir on failure

Mechanics:

  • Subcall receives: call_gas_left = min(g, 63/64 * caller.gas_left), call_reservoir = caller.reservoir * (call_gas_left / caller.gas_left)
  • On success: return unused portions of both
  • On REVERT: return unused portions of both
  • On exceptional halt: consume call_gas_left, return unused call_reservoir

Pros:

  • The gas parameter g provides proportional control over both dimensions — sending half your gas sends half your reservoir
  • Better composability: callers retain reservoir proportional to gas they didn't forward
  • More predictable total spending (bounded by ~2× the gas fraction sent)
  • Callers can limit exposure to untrusted contracts

Cons:

  • State-heavy subcalls may be starved of reservoir (e.g., a factory deploying a 24kB contract needs a high gas limit even if regular gas usage is low)
  • The proportional split may not match actual needs — some calls are 90% state, others 0%
  • Adds calculation complexity and potential rounding issues
  • Nested calls compound the problem: at depth N, the available reservoir may be heavily fragmented

Option 4: Forward reservoir capped at g; return reservoir on failure

Mechanics:

  • Subcall receives: call_gas_left = min(g, 63/64 * caller.gas_left), call_reservoir = min(g, caller.reservoir)
  • On success: return unused portions of both
  • On REVERT: return unused portions of both
  • On exceptional halt: consume call_gas_left, return unused call_reservoir

Pros:

  • The gas parameter g sets a clear bound: total spending in the subcall ≤ 2g (at most g from each pool)
  • Symmetric and intuitive — "I'm sending g worth of resources in each dimension"
  • Callers retain excess reservoir beyond g
  • Reasonable composability: gas limit still gives meaningful spending control
  • Subcalls get enough reservoir for most use cases (the gas limit is usually set high enough to cover the call's needs)

Cons:

  • A subcall can still spend up to 2× the gas parameter g (potentially surprising)
  • For very state-heavy operations, the cap may be too restrictive — e.g., deploying a large contract from a factory may need reservoir >> regular gas
  • Somewhat arbitrary choice to cap at exactly g (why not 2g? or g/2?)
  • Doesn't solve the problem of deploying contracts larger than TX_MAX_GAS_LIMIT worth of state gas from within a subcall

Summary

Reservoir forwarded Spending bound Factory/nested state ops Composability
Option 1: Forward all, return on fail All Unbounded Full support Poor
Option 2: Forward all, consume on halt All Unbounded Full support Very poor
Option 3: Proportional forward Proportional ~2× fraction Partial Good
Option 4: Capped at g min(g, reservoir) ≤ 2g Partial Good

The fundamental tension is between reservoir accessibility in subcalls (needed for factory patterns and nested contract deployments) and caller control over spending (needed for composability with untrusted code). Options 1-2 maximize accessibility but sacrifice control while Options 3-4 attempt a middle ground. Either way, I think option 2 is too punishing and agree with @rakita that option 1 > option 2. The question is whether we want to add more control to caller gas spending by having option 3 or 4.

A future option to consider is introduce an explicit two-parameter call through a new opcode.

Mechanics:

  • Introduce new CALL variants (e.g., CALL2D) or extend EOF call instructions to take two gas parameters: regular_gas and state_gas
  • Subcall receives: call_gas_left = regular_gas, call_reservoir = state_gas
  • Legacy CALL opcodes use a default policy (e.g., Option 3 or 4) for backward compatibility
  • On success/REVERT/halt: handle each dimension independently per the chosen policy

Pros:

  • Most explicit and flexible — callers have full control over both dimensions
  • No ambiguity about behavior; no surprising interactions
  • Can be introduced alongside EOF without breaking legacy contracts
  • Future-proof: works cleanly if more dimensions are added later (per EIP-8011)

Cons:

  • Requires new opcodes or changes to the EVM instruction set
  • Legacy contracts cannot benefit — only new contracts using the new opcodes
  • Increases EVM complexity (more opcodes, more edge cases)
  • EOF adoption timeline is uncertain; legacy contracts may dominate for years
  • Two gas parameters add UX complexity for developers and tooling

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From the testing perspective I do prefer Option 1 over 3/4, even with the greifing vector. Its a simple forward everything, return everything on failure which makes correctness easy to verify across success/revert paths, i.e a minimal test matrix. 3/4 seems more complex to implement and harder to catch all edge cases, more likely to introduce bugs. We could always introduce 3/4 in a future EIP if the need arises.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, sounds good. Let's go with option 1 for now and try to think about potential issues with this design. We can come back to this decision if I find significant issues.

Copy link
Contributor

Choose a reason for hiding this comment

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

@spencer-tb, can you update the description to align with option 1?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added just now. Updated in the EELS PR as well. It helps with some of the test fails 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a-review Waiting on author to review c-update Modifies an existing proposal s-draft This EIP is a Draft t-core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants