# 8037 Spec Breakout, Pt. 2 Notes **Date:** 2026 / 04 / 28 **Hosts:** Marius / Maria / Mario **Time:** 15:00 **Purpose:** Synchronize client implementations on ambiguous points in the state gas specification before test releases for devnet-5, to avoid client teams failing tests due to undocumented design choices. ## Topic 1: Where to Charge State Gas for Account Creation ### Resolved: Account Creation in Parent Frame **Consensus reached:** The **caller** (parent frame) is responsible for account creation costs in all of the following cases: 1. `CALL` to a non-existent account with value transfer 2. `SELFDESTRUCT` sending value to a non-existent beneficiary account 3. `CREATE` / `CREATE2` operations **Rationale:** The caller is the entity initiating account creation. Solidity's default 2300 gas stipend would otherwise be insufficient for a child frame to pay for its own creation. ### Resolved: Code Deposit Costs **Consensus reached:** Code deposit costs are charged to the **init code frame itself** (not the parent). **Rationale:** If charged to the parent, the init code could run unconstrained and consume excessive state gas without bound. Charging within the init code frame keeps it constrained by that frame's gas limit. And its in line with previous behavior. ## Topic 2: Top-Level Account Creation and Failed Transactions ### Issue Identified The "minus-one frame" concept (where transaction-level account creation, delegations, and authorizations are charged) creates ambiguity around: - **Reverted CREATE transactions:** Should the state gas pre-charged for contract creation be returned if the contract is never created? - **Potential double-charging:** State creation costs appear to be paid both in intrinsic gas and again when code performs state diffs. ### Decision **Forward direction:** Refund state gas back to the caller when a CREATE transaction reverts (nothing was created → nothing should be charged). **Implementation note:** This is awkward because it involves refunding *intrinsic* gas, but acceptable. **Longer-term fix (Maria's proposal):** EIP-2780 (Amsterdam) could be amended to remove state creation costs from intrinsic gas entirely, charging them as cold state access at the moment of creation instead. This would be conceptually cleaner. **For devnet-5:** The current approach (refund on revert, snapshot after intrinsic) is acceptable. EELS already does this; clients should align. ## Topic 3: CALLCODE Clarification **Question raised:** Why doesn't CALLCODE create accounts? **Answer:** CALLCODE executes external code in the caller's own context — the "value" parameter is essentially nominal and performs no state modification on a separate account. Since the executing account is the same as the caller, no account creation is possible. **Side note:** Discussion of whether to deprecate CALLCODE in Amsterdam (no decision made). ## Topic 4: State Gas Accounting Across Call Frames (Major Discussion) ### Core Problem When a sub-call performs an SSTORE that incurs state gas, then a deeper call halts/reverts, **how should the parent frame's state gas accounting be reconciled?** ### Pavel's Example ``` Call 1 → Call 2 (SSTORE 0→X, costs 6 state gas) → Call 3 (HALT) ``` **Question:** Should the 6 state gas charged in Call 2 be returned to the reservoir when Call 3 halts (since Call 2's state changes are reverted)? **Consensus:** Yes — state gas for changes that won't be persisted must be refilled. This is a specification requirement, regardless of implementation detail. ### The Deeper Problem: Spillover from Regular Gas to Reservoir When a transaction has gas limit < 16M, the reservoir starts at zero, so state gas is **spilled** from regular gas. The contentious question: **When state gas is "refunded" (e.g., from `X→0` clearing), where does it go?** ### Three Options Identified | Option | Behavior | Tradeoffs | |--------|----------|-----------| | **1. Disappear** | Unused state gas on revert/halt is lost | Simple; user pays more | | **2. Always to Reservoir** (current spec) | All refunds/refills go to reservoir | Easiest to implement; can result in reservoir > initial allocation | | **3. Return to source** | Track spill counter; refund regular gas if it was spilled, else reservoir | Most "correct" but requires extra counter | ### The Worked Example Starting state: regular=10, reservoir=5 (gas limit 15) | Step | Operation | Current Spec (always→reservoir) | Proposed (return to source) | |------|-----------|-------------------------------|---------------------------| | Initial | — | 10, 5 | 10, 5 | | SSTORE 0→X (cost: 1 regular + 6 state) | spill 1 from regular | 9, 0 | 9, 0 (spill counter = 1) | | ADD (cost 1) | — | 8, 0 | 8, 0 | | SSTORE X→0 (cost: 1 regular, refund 6) | refund to reservoir | 7, 6 | 7, 5 (1 returned to regular, then spent) | | **On HALT** | all regular gas consumed | **0, 6** | **0, 5** | | **On REVERT** | regular returned | **7, 6** (user net: gained 1 state gas vs Paris) | **7, 5** (matches Paris exactly) | ### The Identified Risk in Current Spec **Reservoir-only refunds can yield reservoir greater than the initial allocation**, effectively converting regular gas → state gas across the transaction. Concerns raised: - **Account abstraction griefing:** A sub-call could convert regular→state gas, leaving sibling call frames with insufficient compute gas. (Not really a problem because Call-Gas can be limited). - **Incentive distortion:** Users would be incentivized to inflate transaction gas limits past 16M to access the reservoir refilling mechanism. - **Block utilization:** May reduce average block fullness if users overprovision. ### The Composability Problem When the SSTORE 0→X and X→0 happen in **different sibling calls** (rather than nested), the simple per-frame spill counter breaks down: - After 0→X in Call A: end with 7, 0 (spilled 1) - After X→0 in Call B (sibling): without a global spill counter, the refund cannot correctly return to regular gas — ends at 6, 5 instead of an equivalent state. **Conclusion:** Properly implementing "return to source" requires either: - A **global** (transaction-level) spill counter, OR - Returning **two values** from each call frame (spent + refunded), with parent applying spill logic ### Lewis's Suggestion (Transaction Processor) Allow state gas to go **negative** during execution; only check the limit at frame end. The cost of state operations is computed continuously, with out-of-gas determined by the sum of remaining regular + state gas. **Maria's response:** This is functionally equivalent to per-frame accounting; doesn't resolve where to settle the gas at the end. ## Topic 5: Decision for devnet-5 ### Final Resolution 1. **devnet-5 ships with the current spec:** All state gas refunds/refills go to the **reservoir** (Option 2). 2. **Pawel will prototype** the "return to source" approach (Option 3) as a backup. 3. **Maria will analyze** edge cases and user incentives around reservoir manipulation. 4. **Maria will update the EIP** accordingly once the analysis converges. 5. The team will revisit asynchronously after devnet-5 launch. ### Rationale - Current spec is significantly easier to implement. - Time pressure: devnet-5 launching tonight. - The semantic difference primarily affects gas refund amounts on halt/revert, not correctness of state transitions. - Risks identified are theoretical and require more analysis. ## Action Items | Owner | Action | |-------|--------| | Mario Vega (EELS) | Generate test fixtures aligned with current spec; release to client teams | | Pawel (Erigon) | Prototype "return to source" alternative for future evaluation | | Maria | Analyze user-incentive edge cases (reservoir gaming, AA griefing); update EIP for clarity | | All client teams | Verify implementations refund state gas to reservoir on revert/halt; verify caller-pays semantics for account creation; verify init-code-pays semantics for code deposit | | All client teams | Implement state gas refund on reverted CREATE transactions (previously a known bug across most clients) | --- ## Open Questions for Future Resolution 1. Should EIP-2780 be amended to remove state creation costs from intrinsic gas? 2. Is the reservoir refilling mechanism creating perverse incentives for users to overprovision gas limits past 16M? 3. Can sibling-call spillover refunds be handled correctly without a transaction-global spill counter? 4. Are there griefing vectors in account abstraction contexts where one frame depletes another's compute budget by converting it to state gas?