EIP-8037 + EIP-7778: state gas reservoir and refund-free block gas accounting - #2876
Open
anvacaru wants to merge 11 commits into
Open
EIP-8037 + EIP-7778: state gas reservoir and refund-free block gas accounting#2876anvacaru wants to merge 11 commits into
anvacaru wants to merge 11 commits into
Conversation
… (const part) AMSTERDAM overrides Gsstoreset/Gnewaccount/Gcreate/Gtxcreate/Gcallvalue per the state-creation-cost repricing. Gnewaccount's new value (183600) would otherwise leak into the existing EIP-7702 "new account" refund rule (<refund> += Gnewaccount - Gauthbase), so that rule is now gated by a new Ghasauthbaserefund flag (true through Osaka, false at AMSTERDAM) until Wave 1b's state-gas AUTH_BASE charge replaces it outright. Docs record two scoping findings from implementing this and EIP-7954: Amsterdam's full state-gas reservoir mechanism (which most devnet-7 fixtures exercise via tx gas limits above today's Gmaxtxgaslimit) is not const-only and is re-scoped into its own "Wave 1b" plan item, with the mapping doc gaining a fully-cited mechanics section for it.
Ghasauthbaserefund was added without a DEFAULT-schedule case, triggering a non-exhaustive-match warning from kompile on every build. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ate gas reservoir mechanism (Wave 1b) Amsterdam splits gas into a regular and a state dimension. A per-tx reservoir (nonzero only when tx.gas exceeds Gmaxtxgaslimit) is drawn down first by state-gas-priced operations -- SSTORE zero-to-nonzero, new-account creation on CALL/CREATE/SELFDESTRUCT, EIP-7702 authority processing, and code-deposit -- spilling into regular gas once exhausted. Two new per-frame cells (<stateGasReservoir>, <stateGasSpilled>) carry this through the call stack for free via the existing #pushCallStack/#popCallStack snapshot-restore, avoiding a fuller EELS-style running-total mirror. Two new block-level cells (<gasUsedRegular>, <gasUsedState>) track the split separately; <gasUsed> remains the single source of truth, updated to max(regular, state) at #finalizeTx so the existing header-check rule needs no changes. Tx admission replaces the old flat Gmaxtxgaslimit cap with dual per-block budget checks, one per dimension. Also folds in the adjacent Amsterdam code-deposit repricing (flat Gcodedeposit/byte cost becomes a keccak-per-word regular cost, plus a new per-byte state-gas charge), since CREATE needed touching for the state-gas charge anyway and every CREATE fixture needs both pieces correct together. Verified against the full 5051-fixture Osaka+Prague regression suite (0 failures) and spot-checked against real Amsterdam EIP-8037 conformance fixtures (SSTORE, CREATE, and CALL-family cases). A wider for_amsterdam sweep surfaced 8 remaining edge-case fixtures (extreme-boundary gas accounting on authority overspend, and a system-contract calling-convention issue) documented as follow-up in the plan doc rather than blocking this commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…7/8038) The EIP-8037 work (commit 3ff7af55a) added the SSTORE state-gas half (STORAGE_SET -> reservoir) but left the regular half in its Prague shape, which charges the storage-access surcharge (Gcoldsload) AND a separate Gsload operation cost. Amsterdam charges the access as cold XOR warm (Gcoldsload=3000 if cold, else Gwarmstorageread=100, mutually exclusive) plus STORAGE_WRITE (Gsstoreset=10000) only on the first real change, with a simplified refund (a restore always refunds STORAGE_WRITE). Net effect of the old shape: a cold no-op/dirty SSTORE was over-charged by 100 and a warm first-change was under-charged by 100; both were masked wherever the state-gas dimension dominated gasUsed=max(regular,state). Restructure Csstore/Rsstore and the SSTORE #gasAccess surcharge with Amsterdam-gated (Ghasstategas) variants matching the EELS reference; the state-gas halves (SstoreStateGas/SstoreStateCredit) were already correct. Unmask the two SLOTNUM sub-cases (value/slot_zero, gas_cost/out_of_gas) that this fixes. Verified: osaka/prague sweep unchanged (5 fail / 5122 pass); Amsterdam SSTORE suites show the identical 9 pre-existing failures before and after (no regression). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nd top-frame state-gas refill on create-tx failure On the CREATE/CREATE2 opcode path the new-account state-gas charge is lifted out of #create into #chargeCreateNewAccount, run before #create, so an OOG there halts the calling frame cleanly instead of leaving a dangling #codeDeposit that popped a never-pushed call frame. The charge uses #chargeStateGasForCreate, whose out-of-gas rule drops the trailing handler. On create-transaction failure (init-code halt, code-deposit halt, and oversized/invalid deployed code) #refillTopFrameStateGas discards the top frame's refillable state gas (reservoir reset to the tx reservoir, spilled folded into the burned regular gas), matching EELS refill_frame_state_gas. It is gated to the top create-tx frame (callDepth -1, empty 'to') and is inert on non-state-gas forks. Unmasks the now-passing state_gas_create fixtures in failing.llvm. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…efore the 63/64 split EELS generic_create charges NEW_ACCOUNT state gas before computing create_message_gas = max_message_call_gas(gas_left), so the charge draws from the caller's full remaining gas. KEVM ran #allocateCreateGas first (from NEW_ACCOUNT into regular gas hit a spurious OOG and burned the whole transaction. Wrap the charge in #unallocateCreateGas / #allocateCreateGas so the 63/64 split is taken after it, matching the reference. The round trip is exact (#allBut64th(G) +Int G /Int 64 ==Int G) and the charge is 0 without Ghasstategas, so pre-Amsterdam behavior is unchanged; the early allocation is kept because the #checkCreate failure paths refund <callGas>. Also refill the top-frame state gas when a create transaction reverts: the EVMC_REVERT #finishTx rule returned the spilled gas but left <stateGasSpilled> set, so a reverted create kept its NEW_ACCOUNT charge in the state dimension. The reference treats the prepare_dispatch charges as refillable -- they pay for state that rolls back with the dispatched frame. Unmask the four inner_create_succeeds_code_deposit_state_gas sub-cases, which now report exactly 2 * 183600 + 1 * 1530 = 368730 state gas. The remaining failed_create_tx_refills_top_frame_new_account[revert] sub-case stays masked: its state gas is correctly 0 and the residual 256 gas is the calldata floor (23320) versus the v7.1.0 fixture's pre-floor 23064 -- the known floor placement drift, not a state-gas defect. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…7702 auth charges, and CALL/SELFDESTRUCT charge sites with EELS Per-frame spill: <stateGasSpilled> now resets at child-frame entry (#startFrameStateGas) and #restoreStateGas accumulates the child's own spill into the parent on success, so revert refunds (GAVAIL + spill) and LIFO state-gas credits stop double-counting ancestor spill in nested chains. Child-failure refunds: parent-side NEW_ACCOUNT charges (CALL value to dead account, CREATE/CREATE2) are recorded in a new <newAccountCharged> callState flag and credited back via #creditChargedNewAccount on every child failure path (return.exception/revert, exceptional), matching EELS incorporate_child_on_error + credit_state_gas_refund. Dispatch charges in-frame: the tx-level (callDepth -1) NEW_ACCOUNT charges for call and create dispatch moved after #pushCallStack, so #finishTx's pop restores the post-auth fold baseline exactly like EELS refill_frame_state_gas; #refillTopFrameStateGas is now redundant and removed. The delegated-recipient dispatch access charge (#chargeDispatchDelegationAccess, Amsterdam-only) implements EELS prepare_dispatch. Auth charges: #loadAuthorities/#setDelegation/#addAuthority now thread EELS set_delegation's written-accounts / no-auth-base / touched sets; nonexistent authorities are charged Gnewaccount (state), first-writes Gaccountwrite (regular, sender and value-bearing recipient exempt), and net-new delegations Gauthbase (state, once per authority, pre-tx delegation exempt). Auth processing is bracketed by now have OOG rules) rolls back applied delegations and burns exactly tx.gas. Charge ordering: CALL charges extra regular gas, then NEW_ACCOUNT state gas, then computes the 63/64 allocation from the remaining gas (#allocateCallGasStateGas), and SELFDESTRUCT charges its state gas in the gas phase, so an OOG in either kills the calling frame instead of being mis-caught by the inner #return pop or sticking on Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ion on pre-state-gas forks remaining (BLOCK_GAS_LIMIT - <gasUsed>) on schedules without Ghasstategas, matching EELS check_transaction's gas_available test on every fork; the Amsterdam two-dimensional admission branch is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…no-ops
Proof specs that omit or leave open <callState> get the three EIP-8037
cells framed as fresh symbolic variables, so `0 ~> #chargeStateGas`
nondeterministically branched across the charge/spill/OOG rules (the
`A <=Gas R` guard is undecidable for unconstrained R:Gas) and SSTORE
claims died on a spurious EVMC_OUT_OF_GAS. SstoreStateGas/Credit are
[concrete]-only, so symbolic storage arguments also wedged as stuck
gas terms even though both provably evaluate to 0 on pre-state-gas
schedules (Gstorageset is 0 there).
Add priority(40) no-op rules for literal-0 charges to #chargeStateGas,
#creditStateGas, #chargeStateGasForCreate, #chargeStateGasIntoCallGas,
and [simplification] rules reducing SstoreStateGas/SstoreStateCredit
to 0 when notBool Ghasstategas << SCHED >>.
Validated: STORAGE-SPEC.{myBool,setMyBool} and ERC20-SPEC.approve.success
(the two genuine prove-suite failures) now pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements EIP-8037 : state-creation costs move to a per-transaction state-gas reservoir; execution gas and state gas are tracked separately, and
gasUsedis their max. The new#finalizeTxrules also implement EIP-7778: block gas is accumulated from pre-refund totals while sender/miner settlement still applies the refund. Aligned against the EELS reference implementation.EIP-8038 is excluded for now until it is confirmed to be included in the Glamsterdam hardfork; so Amsterdam keeps Osaka's cold-access costs and
Gsstorereset/Rsstoreclearvalues. Fixtures that assume the combined 8037+8038 pricing stay masked until EIP-8038 lands.Changes:
<callState>cells (stateGasReservoir,stateGasSpilled,newAccountCharged) with frame push/pop, CALL/CREATE/SELFDESTRUCT charge sites, and refill on create-tx failure.tests/specs/closed-form<callState>configs gain the three cells.