Update EIP-8037: clarify state gas behavior on frame failure (child vs top-level)#11468
Update EIP-8037: clarify state gas behavior on frame failure (child vs top-level)#11468qu0b wants to merge 3 commits into
Conversation
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>
File
|
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>
|
I think this is an incorrect clarification, and it should be considered a spec code bug. |
|
The general principle we tried to follow when setting the logic to Arguments for restoring state gas on topmost revert/halt:
Arguments against restoring state gas on topmost revert/halt:
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 |
|
@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.
The EIP seems to currently be contradicting itself to some extent. We need to make it clearer depending what we decide is better. |
|
Closed in favor of #11476 |
Summary
execution_state_gas_usedis restored to the parent's reservoir and not accumulated into the parent's counterexecution_state_gas_usedis not reset and still counts towardblock_state_gas_usedMotivation
The current text has two statements that appear contradictory:
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_usedwhen 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:
New:
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_successdoesevm.state_gas_used += child_evm.state_gas_used).Old:
New:
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:
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:
New:
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:
New:
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_usedis 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:
New:
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:
This resolves the apparent contradiction without changing actual behavior.
🤖 Generated with Claude Code