Skip to content

execution: implement 8037 changes for 7.2.0 fixtures#21275

Merged
taratorio merged 19 commits into
mainfrom
worktree-eest-spec-v7.2.0
May 20, 2026
Merged

execution: implement 8037 changes for 7.2.0 fixtures#21275
taratorio merged 19 commits into
mainfrom
worktree-eest-spec-v7.2.0

Conversation

@taratorio

Copy link
Copy Markdown
Member

Summary

Bumps the EEST devnet fixtures from tests-bal@v7.1.1 to tests-bal@v7.2.0 and adapts Erigon to the EIP-8037 spec change that ships with it.

What changed in EEST v7.2.0

The only behavioural change in the new fixtures is execution-specs PR #2863 — "EIP-8037 SSTORE/collision clear dynamics":

  • Inline state_gas refunds (SSTORE original-zero reset, CREATE collision/revert) now credit the local frame's state_gas_left immediately, instead of being clamped to a per-frame state_gas_used counter and bubbled up as a pending credit.
  • Evm.state_gas_used becomes a signed counter — it goes negative when refunds in a frame outweigh the frame's own charges (typical for DELEGATECALL/CALLCODE callees that clear a slot an ancestor set).
  • incorporate_child_on_success no longer propagates a pending refund; incorporate_child_on_error restores the parent's reservoir via the state_gas_left + state_gas_used invariant.
  • process_transaction computes block state gas as Uint(max(0, intrinsic_state + state_gas_used − state_refund)).

Bulk of the rest of the v7.2.0 diff is additional BAL test coverage synchronised from forks/amsterdam.

Erigon changes

  • test-fixtures.json: bump eest_devnet to tests-bal@v7.2.0 (sha256 fc1d9ae1…, 611 743 632 bytes).
  • execution/protocol/mdgas/md_gas.go: MdGasUsage keeps Regular uint64 but State becomes int64. Adds MdGasUsage.PlusIntrinsic(MdGas), MdGasUsage.StateClamped() (= uint64(max(0, State))), and MdGasUsage.Total() = Regular + StateClamped(). MdGas (reservoirs / leftover) keeps both fields uint64.
  • execution/vm/interpreter.go: CallContext.frameStateUsed is now int64. refundCreditPending is removed. creditStateGasRefund drops the clamp — it does stateGas += amount; frameStateUsed -= int64(amount) directly.
  • execution/vm/instructions.go: op{Call,CallCode,DelegateCall,StaticCall,execCreate} drop the PendingStateGasCredit absorption; success path is just scope.frameStateUsed += childUsed.State (signed).
  • execution/vm/evm.go:
    • evm.call / evm.create use a named return gasRemaining (renamed from leftOverGas) and gas as the parameter; mutations are visible in the body.
    • handleFrameRevert(gasRemaining *MdGas, …, stateGasUsed int64) takes the signed state usage and restores the parent reservoir via gasRemaining.State + stateGasUsed. Includes a panic on the invariant violation state_gas_left + state_gas_used < 0 so any future regression in the useMdGas/creditStateGasRefund pairing surfaces loudly rather than silently wrapping the uint64 cast.
    • The depth-0 defers fold the spec's error semantics into gasUsed.State: CALL error resets it to 0; CREATE error sets it to -StateGasNewAccount (mirrors the spec's state_refund += STATE_BYTES_PER_NEW_ACCOUNT * COST_PER_STATE_BYTE on CREATE-tx failure). TxnExecutor then reads gasUsed.State directly without an error-path branch.
    • Extracts deriveFrameRegularGasUsed(inputTotal, gasRemainingTotal uint64, stateGasUsed int64) uint64Regular = (input − leftover) − state in signed int64 so a refund-heavy frame whose gasRemaining.State grew above the input still yields the correct positive regular-ops count (a guarded uint64 subtraction would mis-skip and return 0).
  • execution/protocol/txn_executor.go: Amsterdam branches collapse the spec's tx_state_gas = intrinsic_state + state_gas_used + block_state_gas_used += max(0, tx_state_gas) into combined := gasUsed.PlusIntrinsic(imdGas); st.blockStateGasUsed = combined.StateClamped(); st.txnGasUsedB4Refunds = combined.Total().

Tests

  • execution/vm/evm_test.go (new): TestDeriveFrameRegularGasUsed with four sub-cases — charges-only, with-spillover, refunds-exceed-charges (the edge case the old guarded subtraction returned 0 for), and pure-refund. Worked numeric examples are documented inline.

Test plan

  • make lint clean.
  • go test ./execution/vm/... ./execution/protocol/... — unit tests green.
  • EEST shards on the new fixtures, 0 failures:
    • statetests-devnet (65 202 tests)
    • blocktests-devnet (82 941 tests)
    • blocktests-devnet-race-amsterdam (21 368 tests)
  • Regression check against stable fixtures, 0 failures:
    • statetests-stable (63 556 tests)
    • blocktests-stable-{sequential,parallel} (69 256 tests each)
    • enginextests-stable-{sequential,parallel} (63 920 tests each)
  • The originally failing variants now pass: tests/amsterdam/eip8037_state_creation_gas_cost_increase/test_state_gas_sstore.py::test_sstore_restoration_refund_credits_local_reservoir[fork_Amsterdam-state_test-depth_{1,3,10}-refund_funds_create].

@yperbasis yperbasis requested a review from Copilot May 19, 2026 11:25
@yperbasis yperbasis added the Glamsterdam https://eips.ethereum.org/EIPS/eip-7773 label May 19, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates Erigon to consume EEST tests-bal@v7.2.0 fixtures and implements the corresponding EIP-8037 spec adjustment ("SSTORE/collision clear dynamics"), in which inline state-gas refunds are credited to the local frame's reservoir immediately and the per-frame state-gas counter becomes signed.

Changes:

  • Make MdGasUsage.State a signed int64 and add PlusIntrinsic/StateClamped/Total helpers; remove the PendingStateGasCredit plumbing.
  • Reshape the EVM call/create flow: frameStateUsed is signed, creditStateGasRefund is now unclamped, and handleFrameRevert restores the parent reservoir via the signed state_gas_left + state_gas_used invariant (panicking if it goes negative). A new deriveFrameRegularGasUsed handles refund-heavy frames in signed arithmetic.
  • Collapse the Amsterdam branches in TxnExecutor.Execute to a single PlusIntrinsic/StateClamped/Total form, bump the eest_devnet fixture pin to tests-bal@v7.2.0, and add TestDeriveFrameRegularGasUsed.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
test-fixtures.json Pin eest_devnet to tests-bal@v7.2.0 (new URL, sha256, size).
execution/protocol/mdgas/md_gas.go Signed MdGasUsage.State; add PlusIntrinsic, StateClamped, Total; drop PendingStateGasCredit.
execution/vm/interpreter.go CallContext.frameStateUsed becomes int64; remove refundCreditPending; unclamped creditStateGasRefund; rename leftOver to gasRemaining in Run.
execution/vm/instructions.go Drop PendingStateGasCredit absorption in CALL/CALLCODE/DELEGATECALL/STATICCALL/CREATE success paths; fold signed childUsed.State into parent.
execution/vm/evm.go Rename leftOverGasgasRemaining; signed handleFrameRevert with invariant panic; new deriveFrameRegularGasUsed; depth-0 error semantics fold into gasUsed.State.
execution/protocol/txn_executor.go Replace Amsterdam refund/no-refund branches with combined := gasUsed.PlusIntrinsic(imdGas) and use StateClamped/Total.
execution/vm/evm_test.go New TestDeriveFrameRegularGasUsed covering charges-only, spillover, refund-exceeds-charges, and pure-refund cases.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@yperbasis yperbasis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TL;DR: Design is solid and largely matches what a faithful port of execution-specs PR #2863 should look like. Two CI checks fail (race-tests / execution-other, sonar) on the same root cause: an int64(uint64) cast in handleFrameRevert (and deriveFrameRegularGasUsed) wraps for the very-large state reservoir that runtime.Execute produces from GasLimit = MaxUint64. This needs to be fixed before merge. The PR description's claim "go test ./execution/vm/... ./execution/protocol/... — unit tests green" is incorrect — TestEip2929Cases panics locally too.

Blocking — int64(uint64) wrap

TestEip2929Cases (and any caller of runtime.Execute with GasLimit = MaxUint64) panics:

panic: EIP-8037 invariant violated: state_gas_left (18446744073692774399) + child state_gas_used (0) = -16777217 < 0

18446744073692774399 = MaxUint64 - MaxTxnGasLimit (2^24)SplitTxnGasLimit allocates the entire MaxUint64 - 16777216 overflow to the state reservoir. int64(gasRemaining.State) reinterprets that as -16777217, and the + 0 check panics on a value that is mathematically ~2^64 ≥ 0.

Two callsites have the same hazard:

  1. handleFrameRevertint64(gasRemaining.State) + stateGasUsed.
  2. deriveFrameRegularGasUsedint64(inputTotal) - int64(gasRemainingTotal). Less likely to actually wrap (the result is then converted back to uint64 anyway), but still a foot-gun if inputTotal2^63.

Suggested fix — keep the spec invariant but do it in sign-aware uint64:

if evm.chainRules.IsAmsterdam && depth > 0 {
    if stateGasUsed >= 0 {
        gasRemaining.State += uint64(stateGasUsed)
    } else {
        absUsed := uint64(-stateGasUsed)
        if absUsed > gasRemaining.State {
            panic(fmt.Sprintf("EIP-8037 invariant violated: state_gas_left (%d) < |child state_gas_used| (%d)",
                gasRemaining.State, absUsed))
        }
        gasRemaining.State -= absUsed
    }
}

Same shape works for deriveFrameRegularGasUsed (split on sign of stateGasUsed, then operate on inputTotal/gasRemainingTotal in uint64). The existing unit-test cases pass through unchanged; add one with inputTotal and gasRemainingTotal near MaxUint64 to lock in the regression.

Minor

  • PR description test plan is stale on go test ./execution/vm/... — re-run with the fix and update.
  • Worth verifying the panic message in handleFrameRevert matches the corrected check (printing |child state_gas_used| rather than the signed value avoids the misleading "-16777217" in the message you'd see for a wrapped value).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

@taratorio taratorio enabled auto-merge May 20, 2026 03:56
@taratorio taratorio added this pull request to the merge queue May 20, 2026
Merged via the queue into main with commit d17d224 May 20, 2026
68 checks passed
@taratorio taratorio deleted the worktree-eest-spec-v7.2.0 branch May 20, 2026 05:07
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.

3 participants