Skip to content

Update EIP-8037: clarify state gas behavior on frame failure (child vs top-level)#11468

Closed
qu0b wants to merge 3 commits into
ethereum:masterfrom
qu0b:qu0b/clarify-eip8037-state-gas-refund
Closed

Update EIP-8037: clarify state gas behavior on frame failure (child vs top-level)#11468
qu0b wants to merge 3 commits into
ethereum:masterfrom
qu0b:qu0b/clarify-eip8037-state-gas-refund

Conversation

@qu0b

@qu0b qu0b commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Clarify that on child frame failure, execution_state_gas_used is restored to the parent's reservoir and not accumulated into the parent's counter
  • Clarify that on top-level exceptional halt, execution_state_gas_used is not reset and still counts toward block_state_gas_used
  • Rename "Revert behavior for state gas" → "State gas on frame failure" and reconcile with the reservoir model description

Motivation

The current text has two statements that appear contradictory:

  1. Reservoir model (bullet 5): "State gas is fully preserved on failure because state changes are reverted, so no state was actually grown"
  2. Revert behavior section: "State gas charged for account creation... is consumed even if the frame reverts — state changes are rolled back but gas is not refunded"

Both are correct but apply to different scopes — (1) describes child frame behavior where state gas is returned to the parent's reservoir, while (2) describes the top-level perspective where consumed state gas has no parent to reclaim it. This distinction was not explicit, causing implementer confusion about whether initcode state gas should count in block_state_gas_used when a CREATE transaction fails at code deposit.

Discovered during bal-devnet-3 testing where a nethermind chain split was traced to this ambiguity.

Line-by-line explanation

Change 1: Bullet 5 (child frame behavior)

Old:

On child success, the remaining state_gas_reservoir is returned to the parent.

New:

On child success, the remaining state_gas_reservoir is returned to the parent and the child's execution_state_gas_used is accumulated into the parent's counter.

The original only mentioned the reservoir being returned on success. It didn't say what happens to execution_state_gas_used. Added: the parent adopts the child's state gas usage counter (EELS: incorporate_child_on_success does evm.state_gas_used += child_evm.state_gas_used).


Old:

...is restored to the parent's reservoir.

New:

...is restored to the parent's state_gas_reservoir; the child's execution_state_gas_used is not accumulated into the parent's counter, because state changes are rolled back and no state was actually grown.

This is the key addition. The original said state gas is "restored to the parent's reservoir" but never mentioned execution_state_gas_used. An implementer reading this could think: the reservoir is restored AND the used counter is accumulated (like the success path). The new text explicitly says the used counter is NOT accumulated — the parent doesn't inherit the child's state gas consumption on failure.


Old:

State gas is fully preserved on failure because state changes are reverted, so no state was actually grown.

Removed. This sentence was the main source of confusion. "Fully preserved" could mean the used counter is preserved (i.e., still counted), when the actual intent is that the gas is preserved in the sense of being returned to the parent's reservoir. The same meaning is now conveyed by the explicit "not accumulated" language above.


Change 2: Bullet 6 (top-level behavior)

Old:

On exceptional halt, remaining gas_left...

New:

On exceptional halt at the top level (no parent frame), remaining gas_left...

The original didn't specify this was about the top level. Since bullet 5 also discusses exceptional halt (child frames), a reader could think this bullet applies to all frames. Added "at the top level (no parent frame)" to make the scope unambiguous.


Old:

The state_gas_reservoir is not consumed, it is returned to the parent frame or preserved at the top level for refund, consistent with the principle that state gas pays for long-term state growth which does not occur on failure.

New:

The state_gas_reservoir is preserved (not consumed) for the sender refund calculation. Unlike child frames, execution_state_gas_used is not reset at the top level and still counts toward block_state_gas_used, because there is no parent frame to absorb the refund.

The original said "returned to parent frame or preserved at top level for refund" — mixing child and top-level behavior in one sentence. But they behave differently: child frames return everything (reservoir + used) to the parent; top level only preserves the reservoir. The new text focuses on the top level only (child is covered in bullet 5) and explicitly states the critical difference: execution_state_gas_used is NOT reset, so it counts in block accounting. This is the exact behavior that caused the Nethermind chain split.


Change 3: Section rename and rewrite

Old title: "Revert behavior for state gas"
New title: "State gas on frame failure"

The old title implied this only covers REVERT. It actually covers both revert and exceptional halt, so the broader name is more accurate.


Old:

State gas charged for account creation... is consumed even if the frame reverts — state changes are rolled back but gas is not refunded.

New:

State gas is deducted at the point of the operation... If the frame subsequently reverts or halts, the deduction is not undone within that frame — but the parent reclaims the child's consumed state gas via the reservoir restoration described above. At the top level, consumed state gas has no parent to reclaim it and counts toward block_state_gas_used.

The old text said "gas is not refunded" — full stop. But for child frames, the gas IS refunded (to the parent's reservoir). The contradiction confused implementers. The new text explains both cases:

  1. Within the frame: deduction stands (not undone)
  2. At the parent: child's state gas is reclaimed via reservoir restoration
  3. At the top level: no parent to reclaim → counts in block

This resolves the apparent contradiction without changing actual behavior.

🤖 Generated with Claude Code

Resolve ambiguity in the reservoir model description around what
happens to execution_state_gas_used on child frame failure vs
top-level transaction failure:

- Child frames: state gas is restored to parent's reservoir and
  NOT accumulated into parent's execution_state_gas_used
- Top level: execution_state_gas_used is NOT reset and counts
  toward block_state_gas_used (no parent to absorb the refund)

The previous text said "State gas is fully preserved on failure"
and "gas is not refunded" in different sections, which appeared
contradictory. Both statements are correct but apply to different
scopes — this change makes the distinction explicit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@qu0b qu0b requested a review from eth-bot as a code owner March 30, 2026 21:34
@github-actions github-actions Bot added c-update Modifies an existing proposal s-draft This EIP is a Draft t-core labels Mar 30, 2026
@eth-bot

eth-bot commented Mar 30, 2026

Copy link
Copy Markdown
Collaborator

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 Mar 30, 2026
@eth-bot eth-bot changed the title EIP-8037: clarify state gas behavior on frame failure (child vs top-level) Update EIP-8037: clarify state gas behavior on frame failure (child vs top-level) Mar 30, 2026
qu0b and others added 2 commits March 30, 2026 21:47
SSTORE was not in the original list — the section is specifically
about account creation operations, not all state gas operations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rakita

rakita commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

I think this is an incorrect clarification, and it should be considered a spec code bug.

@misilva73

Copy link
Copy Markdown
Contributor

The general principle we tried to follow when setting the logic to state_gas refills on failure cases was "state gas pays for long-term state growth which does not occur on failure." This principle is applied consistently for child frames (state gas restored to parent on revert/halt). However, the current spec (and this proposal of updating the EIP) does not apply this at the top level, which is not an obvious choice.

Arguments for restoring state gas on topmost revert/halt:

  • Consistency with child frame behavior. The asymmetry is hard to justify — why does the same revert logic differ based on call depth? At depth 1, state gas is fully reclaimed by the parent. At depth 0, it's lost. This creates a surprising edge case.
  • block_state_gas_used should reflect reality. If it's meant to track how much state the block actually grew, counting reverted state operations inflates it. A block that reverted all its state-touching transactions would hit the state gas limit despite growing zero state.

Arguments against restoring state gas on topmost revert/halt:

  • Pre-EIP-8037 precedent. GAS_NEW_ACCOUNT (25,000 gas) is consumed on revert today. The EIP's "Revert behavior" section explicitly preserves this behavior.

My first intuition is that restoring the state gas in this case would make more sense. am I missing som edge case where this behavior would not make sense in the topmost frame? Why was GAS_NEW_ACCOUNT originally spent in cases where no account was created?

@qu0b

qu0b commented Mar 31, 2026

Copy link
Copy Markdown
Contributor Author

@misilva73 This PR should just add clarifications regarding parent and child frames it does not aim to change any of the logic already inherint in the EIP.

State gas charged for account creation (CREATE, CALL to new account, and EOA delegation) is consumed even if the frame reverts — state changes are rolled back but gas is not refunded

The EIP seems to currently be contradicting itself to some extent. We need to make it clearer depending what we decide is better.

@qu0b

qu0b commented Apr 15, 2026

Copy link
Copy Markdown
Contributor Author

Closed in favor of #11476

@qu0b qu0b closed this Apr 15, 2026
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.

6 participants