Skip to content

execution: implement EIP-8037: State Creation Gas Cost Increase#19596

Merged
yperbasis merged 136 commits into
mainfrom
eip-8037
Mar 20, 2026
Merged

execution: implement EIP-8037: State Creation Gas Cost Increase#19596
yperbasis merged 136 commits into
mainfrom
eip-8037

Conversation

@taratorio

Copy link
Copy Markdown
Member

DO-NOT-MERGE BEFORE #19595

closes #19102

yperbasis and others added 24 commits March 19, 2026 17:03
EIP-8037: "Regular gas charge MUST be applied first. If the regular gas
charge triggers an out-of-gas error, the state gas charge is not applied."

The interpreter was charging state gas (which can spill into regular)
before checking regular gas sufficiency. If state gas spilled and then
regular OOG fired, regular gas was incorrectly consumed for state.

Reorder: check regular sufficiency first, then charge state, then deduct
regular. This ensures no state gas deduction occurs when regular gas is
insufficient.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ApplyFrame called SplitIntoMdGas without checking that tx.gas covers
intrinsic_regular_gas + intrinsic_state_gas, risking a uint64 underflow.
TransitionDb already had this guard. Add the same check for consistency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add StateBytesNewAccount (112), StateBytesStorageSet (32),
  StateBytesAuthBase (23) constants to params/protocol.go and replace
  magic numbers across 7 locations in gas_table.go, operations_acl.go,
  intrinsic_gas.go, and state_transition.go.

- Add GasUsed.BlockGasUsed() helper for max(BlockRegular, BlockState)
  and use it in SetGasUsed and ExecuteBlockEphemerally.

- Extract duplicated ~35-line EIP-8037 revert/halt handling block from
  call() and create() into EVM.handleStateGasRevert().

- Remove dead MdGas.MinusStateGas() method (defined but never called).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the state gas charge in makeCallVariantGasCallEIP7702 above the
regular-gas sufficiency checks so that any spill from the state reservoir
into regular gas is reflected in availableGas for all subsequent checks.

Previously the state gas was charged after the regular-gas checks,
meaning those checks used pre-spill availableGas — they could pass even
when post-spill regular gas was insufficient, deferring the OOG to a
later check.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Accumulate directly into result.RegularGas and result.StateGas instead
of staging through a local variable that is later merged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move execution/vm/evmtypes/mdgas → execution/protocol/mdgas.
The mdgas types are used across protocol, state, tracing, and vm
packages — protocol/ is a better home than a deeply nested vm
sub-package. Package name and all qualifiers unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The MdGas refactoring changed gas behavior for trace_call and
eth_callMany paths (gasBailout=true / !refunds):

1. ReceiptGasUsed incorrectly applied Prague floor gas for gasBailout
   paths. The old code returned raw initialGas-gasRemaining without
   floor or refund adjustments.

2. blockRegularGasUsed was set to actual gas used instead of msg.Gas().
   The old code kept the default (msg.Gas()), meaning no gas was
   returned to the pool. This changed gas pool behavior for
   eth_callMany multi-call scenarios.

3. MaxGasUsed lost the Prague FloorGasCost adjustment that the old
   peakGasUsed had unconditionally.

Collapse the separate Prague/default else branches into one that
records raw gas without floor/refund, matching pre-refactor behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move intrinsic gas calculation from execution/protocol/fixedgas into
execution/protocol/mdgas and remove the fixedgas package.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The EIP-8037 refactoring collapsed the no-refund branches into a single
else, dropping the Prague-specific floor cost that trace_call and
trace_callMany rely on. Restore the else-if-Prague branch so that
ReceiptGasUsed applies max(floorGasCost, gasUsed) for gasBailout calls.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
My earlier commit 76ec271 re-added a Prague floor cost branch that
4b893b5 had intentionally removed. The old code never applied
floor/refund in the gasBailout path — raw gas used is correct here.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The EIP-8037 refactoring changed the error message from
"have X, want Y" to "have X, want regular A + state B = Y, floor Z".
This broke RPC integration tests that match on the error string.
Restore the original format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The txpool was rejecting valid transactions by checking
gas.Total() (regular + state) against TX_MAX_GAS_LIMIT.
Per EIP-8037, only max(intrinsic_regular_gas, calldata_floor)
is capped — state gas lives in a separate dimension.

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

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Merge handleStateGasRevert, RevertToSnapshot, and the exceptional-halt
regular gas burn into a single handleFrameRevert method called from both
call() and create(). Also make create() consistently pass err (not nil)
to RevertToSnapshot for debug tracing, matching call().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Block gas is max(blockRegularGasUsed, blockStateGasUsed), so both
dimensions are independently capped at the block gas limit. Track
availableStateGas alongside availableGas to avoid over-filling the
state gas dimension when selecting transactions for block building.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CallContext.escrowStateGas/settleStateGas: deduplicate the state gas
  reservoir save/restore pattern across all 6 CALL/CREATE opcodes.
- CallContext.callGas: deduplicate MdGas construction from callGasTemp
  across 4 CALL variant opcodes.
- StateTransition.calcIntrinsicGas: deduplicate the 13-line
  IntrinsicGasCalcArgs struct construction in ApplyFrame and TransitionDb.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The interpreter checked regular gas sufficiency before charging state
gas, but useMdGas(StateGas) can spill into regular gas, reducing
callContext.gas below dynamicCost.Regular. The subsequent raw
subtraction then wraps around ~2^64, giving unlimited gas.

Deduct regular gas immediately after the sufficiency check (before
state gas) so any spill operates on the already-reduced balance.

Unskips 4 reservoir-inflation spec tests that now pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@yperbasis yperbasis added this pull request to the merge queue Mar 20, 2026
Merged via the queue into main with commit c0a9bf8 Mar 20, 2026
34 checks passed
@yperbasis yperbasis deleted the eip-8037 branch March 20, 2026 12:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Glamsterdam https://eips.ethereum.org/EIPS/eip-7773

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement EIP-8037: State Creation Gas Cost Increase

3 participants