Skip to content

EIP-7778 gas fix#11794

Merged
svlachakis merged 1 commit into
masterfrom
eip7778-gas-fix
May 28, 2026
Merged

EIP-7778 gas fix#11794
svlachakis merged 1 commit into
masterfrom
eip7778-gas-fix

Conversation

@svlachakis

Copy link
Copy Markdown
Contributor

Types of changes

What types of changes does your code introduce?

  • Bugfix (a non-breaking change that fixes an issue)
  • New feature (a non-breaking change that adds functionality)
  • Breaking change (a change that causes existing functionality not to work as expected)
  • Optimization
  • Refactoring
  • Documentation update
  • Build-related changes
  • Other: Description

Testing

Requires testing

  • Yes
  • No

If yes, did you write tests?

  • Yes
  • No

Notes on testing

Added regression test.

@svlachakis svlachakis changed the title eip7778 gas fix EIP-7778 gas fix May 27, 2026
@claude

claude Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Claude finished @svlachakis's task in 3m 6s —— View job


Code Review — eip7778-gas-fix

  • Read changed files and diff
  • Analyze gas-accounting logic
  • Verify test correctness
  • Check for regressions and edge cases
  • Post findings

Summary

The fix is correct and clean. The root cause was that transaction admission under EIP-7778 used _blockCumulativeReceiptGas (sum of SpentGas, i.e. post-refund) as the gas budget gate, while header.GasUsed was accumulated with EffectiveBlockGas = Math.Max(preRefundGas, floorGas) (pre-refund). This asymmetry allowed transactions to be admitted when the pre-refund block gas would exceed the limit, which is the bug.

The fix removes the now-unnecessary _blockCumulativeReceiptGas field and collapses the admission check to always use header.GasUsed directly. Because header.GasUsed already tracks the correct basis per-spec — pre-refund under EIP-7778, post-refund without it — no spec-switch is needed at the admission site.


Gas accounting path

Spec EffectiveBlockGas header.GasUsed basis Admission basis (old) Admission basis (new)
No EIP-7778 SpentGas (post-refund) post-refund header.GasUsed header.GasUsed
EIP-7778 (no EIP-8037) Max(preRefund, floor) pre-refund _blockCumulativeReceiptGas (post-refund) ✗ header.GasUsed
EIP-8037 different path returns early, unaffected returns early, unaffected

Test analysis

The renamed test Transaction_admission_uses_pre_refund_block_gas_when_eip7778_enabled correctly inverts the assertions from the buggy behavior:

  • tx1 creates a refund gap: blockGasAfterTx1 > receiptGasAfterTx1
  • GasLimit is set to blockGasAfterTx1 + GasCostOf.Transaction - 1 — exactly 1 gas short for tx2
  • Under the old code tx2 was admitted (using post-refund basis, so there appeared to be room)
  • Under the new code tx2 is correctly rejected: admission uses header.GasUsed = blockGasAfterTx1, leaving only GasCostOf.Transaction - 1 available

One structural point worth noting: when _processor.Execute(tx2, new BlockExecutionContext(block.Header, ...), ...) is called, the extension method calls SetBlockExecutionContext internally, which resets _blockCumulativeRegularGas = 0. header.GasUsed lives on block.Header and is not reset, so the admission check still sees the pre-refund gas from tx1 — correct.


Findings

No Critical, High, or Medium issues. One low-level observation:

Low — The inline comment added at the admission site (TransactionProcessor.cs:680) correctly explains the EIP-7778 behavior, but omits that EIP-8037 takes a separate early-return path. This is very minor since the EIP-8037 guard directly above it makes this clear to a careful reader. No action required.


Verdict: approved — no blocking issues. The fix is minimal, correct, removes dead code, and is backed by a tight regression test.

@svlachakis svlachakis requested a review from flcl42 May 27, 2026 11:40
@svlachakis svlachakis marked this pull request as ready for review May 27, 2026 11:43
@claude

claude Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Claude finished @svlachakis's task in 2m 27s —— View job


Code Review — eip7778-gas-fix

  • Read changed files and diff
  • Analyze gas-accounting logic
  • Verify test correctness
  • Check for regressions and edge cases
  • Post findings

Summary

The fix is correct and clean. The root cause was that transaction admission under EIP-7778 used _blockCumulativeReceiptGas (sum of SpentGas, i.e. post-refund) as the gas budget gate, while header.GasUsed was accumulated with EffectiveBlockGas = Math.Max(preRefundGas, floorGas) (pre-refund). This asymmetry allowed transactions to be admitted when the pre-refund block gas would exceed the limit — the bug.

The fix removes the now-unnecessary _blockCumulativeReceiptGas field and simplifies the admission check to always use header.GasUsed directly. Because header.GasUsed already tracks the correct basis per-spec — pre-refund under EIP-7778, post-refund without it — no spec-switch is needed at the admission site.


Gas accounting path

Spec EffectiveBlockGas header.GasUsed basis Admission basis (before) Admission basis (after)
No EIP-7778 SpentGas (post-refund) post-refund header.GasUsed header.GasUsed
EIP-7778 (no EIP-8037) Max(preRefund, floor) pre-refund _blockCumulativeReceiptGas (post-refund) ✗ header.GasUsed
EIP-8037 different path early-returns before this check, unaffected early-returns before this check, unaffected

Test analysis

Transaction_admission_uses_pre_refund_block_gas_when_eip7778_enabled correctly inverts the assertions from the buggy behavior:

  • tx1 creates a refund gap: blockGasAfterTx1 > receiptGasAfterTx1
  • GasLimit is set to blockGasAfterTx1 + GasCostOf.Transaction - 1 — exactly 1 gas short for tx2
  • Under the old code tx2 was admitted (post-refund basis created apparent headroom)
  • Under the fixed code tx2 is correctly rejected: gasUsedForAllowance = header.GasUsed = blockGasAfterTx1, leaving only GasCostOf.Transaction - 1 available

One structural subtlety: _processor.Execute(tx2, new BlockExecutionContext(block.Header, ...), NullTxTracer.Instance) calls SetBlockExecutionContext internally (via ITransactionProcessor.cs:77), which resets _blockCumulativeRegularGas = 0. However header.GasUsed lives on block.Header and is not reset by SetBlockExecutionContext, so the admission check correctly sees the pre-refund gas from tx1. Since tx2 is rejected before the Complete: label, header.GasUsed is never modified — the third assertion block.Header.GasUsed == blockGasAfterTx1 holds.

Moving tracer.EndBlockTrace() to before tx2 is correct — tx2 is executed outside the block tracer session.


Findings

No Critical, High, or Medium issues.

Low — The inline comment at TransactionProcessor.cs:680 correctly explains the EIP-7778 basis alignment, but omits that EIP-8037 takes a separate early-return path above it. This is very minor since the guard is immediately visible above. No action required.


Verdict: approved — no blocking issues. The fix is minimal, correct, removes dead code, and is backed by a tight regression test.

@benaadams benaadams 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.

Need to also raise as test gap for EEST test so consensus is enforced on the change https://github.com/ethereum/execution-specs/issues

That not failing with the behaviour change confirms a problem

@benaadams

Copy link
Copy Markdown
Member

Need to also raise as test gap for EEST test so consensus is enforced on the change https://github.com/ethereum/execution-specs/issues

That not failing with the behaviour change confirms a problem

Test is here ethereum/execution-specs#2932

@svlachakis svlachakis merged commit eb2770f into master May 28, 2026
557 of 559 checks passed
@svlachakis svlachakis deleted the eip7778-gas-fix branch May 28, 2026 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants