Skip to content

core/vm: fix EIP-8037 CALL state gas ordering#33972

Open
qu0b wants to merge 2 commits intoethereum:bal-devnet-3from
qu0b:qu0b/fix/eip8037-gas-accounting
Open

core/vm: fix EIP-8037 CALL state gas ordering#33972
qu0b wants to merge 2 commits intoethereum:bal-devnet-3from
qu0b:qu0b/fix/eip8037-gas-accounting

Conversation

@qu0b
Copy link
Contributor

@qu0b qu0b commented Mar 6, 2026

Summary

  • Charge new-account state gas (112 × cpsb = 131,488) before the 63/64 child gas allocation in CALL, not after
  • Without this fix, when state gas spills from an empty reservoir to regular gas, the spillover exceeds the 1/64 remainder after child allocation, causing Underflow → OOG on CALLs that should succeed

Details

In makeCallVariantGasCall (operations_acl.go), the EIP-8037 state gas for new account creation was returned in the GasCosts struct and charged by the interpreter's UseGas/Sub after callGas() had already computed the 63/64 child gas allocation using the full (pre-state-gas) contract.Gas.RegularGas.

When the state gas reservoir is empty (common case — reservoir only has gas when tx.gasLimit exceeds TX_MAX_GAS_LIMIT - intrinsic), state gas spills to regular gas. The spill amount (131,488) far exceeds the 1/64 retained gas (~15,600 at 1M gas), causing an underflow/OOG.

The fix charges state gas directly before callGas() so the 63/64 calculation uses the reduced regular gas. This matches nethermind's implementation (EvmInstructions.Call.cs:187-213) and besu's approach.

Test plan

  • geth+besu: 159 blocks in sync with spamoor (eoatx + evm-fuzz), zero errors
  • geth+nethermind: 50+ post-Amsterdam blocks in sync (no spamoor — nethermind has separate BAL validation bug)
  • Verified nethermind charges state gas before 63/64 (ConsumeNewAccountCreation at line 189, callGas at line 212)

🤖 Generated with Claude Code

Charge new-account state gas BEFORE the 63/64 child gas allocation
rather than after. When state gas spills from an empty reservoir to
regular gas, it must reduce the gas available for callGasTemp.
Otherwise the spillover exceeds the 1/64 remainder after child gas
allocation, causing Underflow → OOG on CALLs that should succeed.

This matches nethermind's implementation which calls
ConsumeNewAccountCreation() before the 63/64 calculation
(EvmInstructions.Call.cs:187-213).

Verified: geth+besu in sync through 159 blocks with spamoor load,
geth+nethermind in sync through 50+ post-Amsterdam blocks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@qu0b qu0b requested a review from rjl493456442 as a code owner March 6, 2026 21:23
When CREATE init code produces valid code but UseGas fails for code
storage, TotalStateGasCharged was incremented by the full code storage
state gas (potentially millions) without consuming any actual gas.
This inflated TSC propagated up through RefundGas and caused
blockGasUsed() to underflow (execRegularUsed = totalExecUsed - TSC
wraps uint64), resulting in incorrect gas pool accounting and state
root mismatches when validating blocks from other clients.

The fix removes the TSC increment when UseGas fails for code storage.
Since the contract creation failed and state was reverted, the state
gas demand didn't materialize and shouldn't be tracked.

Also removes the ErrCodeStoreOutOfGas case from isCodeValidation since
TSC is no longer inflated in that code path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant