feat(spec-specs, tests): EIP-8037 - SELFDESTRUCT same-tx refunds state gas at end of tx#2707
Merged
spencer-tb merged 2 commits intoApr 19, 2026
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## eips/amsterdam/eip-8037 #2707 +/- ##
==========================================================
Coverage ? 88.18%
==========================================================
Files ? 524
Lines ? 31120
Branches ? 3036
==========================================================
Hits ? 27444
Misses ? 3161
Partials ? 515
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
4 tasks
3e1d7c4 to
44b47cc
Compare
17d34fe to
1ad4edc
Compare
1ad4edc to
1495372
Compare
b8defe9
into
ethereum:eips/amsterdam/eip-8037
11 of 16 checks passed
4 tasks
spencer-tb
added a commit
to spencer-tb/execution-specs
that referenced
this pull request
Apr 19, 2026
…efund EIP-11532 (merged as PR ethereum#2707) refunds the CREATE's `GAS_NEW_ACCOUNT` at end-of-tx when the created account is also destroyed in the same transaction. This broke three tests in `test_state_gas_call.py` (added by PR ethereum#2646 before ethereum#2707 merged) that asserted `header.gas_used == new_account_state_gas`; after the refund `block_state_gas_used` is zero and the header reports `block_regular` instead. Affected tests (20 variants): test_call_value_to_self_destructed_header_gas_used test_call_value_to_self_destructed_burns_value test_call_zero_value_to_self_destructed_same_tx_account The original discriminator intent (no spurious GAS_NEW_ACCOUNT charge on the CALL) still holds: a buggy extra charge would push `state_gas_used` to 131,488 and bump the header above `new_account_state_gas`. Pin the expected `header.gas_used` to the empirical `block_regular` per variant, with a build-time `assert expected < new_account_state_gas` so the spurious-charge discriminator stays sharp.
spencer-tb
added a commit
to spencer-tb/execution-specs
that referenced
this pull request
Apr 19, 2026
Ports three tests from the closed PR ethereum#2639 that cover reservoir behavior paths not exercised by the merged ethereum#2689/ethereum#2704/ethereum#2707 tests. test_top_level_halt_preserves_restored_reservoir (parametrized reservoir_delta in {-1, 0, 1} x child_termination in {revert, halt}) Regression test for the bal-devnet-3 Besu bug (ethereum#2644). Child runs an SSTORE then fails, restoring state gas to the parent. Parent then INVALIDs, triggering the top-level failure refund. Expected `header.gas_used = gas_limit_cap + min(reservoir_delta, 0)` so the reservoir (including any spill-restore) is preserved across the halt. test_callcode_value_no_new_account_state_gas CALLCODE transfers value to the caller, not to the target, so no new-account state gas is ever charged regardless of whether the target exists. The reservoir stays intact for a subsequent SSTORE. test_create_oog_during_state_gas_charge Parent CALLs an inner with only 20k gas forwarded. The inner's CREATE charges GAS_NEW_ACCOUNT which exceeds the forwarded budget, OOGing before any state gas lands. Per PR ethereum#2704 the refund restores the parent's reservoir and the parent's subsequent SSTORE succeeds from it.
spencer-tb
added a commit
to spencer-tb/execution-specs
that referenced
this pull request
Apr 19, 2026
Two tests that exercise state-gas paths the merged PRs don't
cover directly: both involve a CREATION tx (to=None) whose
initcode interacts with nested CREATE / SELFDESTRUCT semantics.
test_selfdestruct_in_create_tx_initcode
Creation tx whose initcode SELFDESTRUCTs to a new beneficiary.
The outer contract is in `tx_state.created_accounts` and
`accounts_to_delete`, so PR ethereum#2707 refunds its GAS_NEW_ACCOUNT
end-of-tx. The beneficiary's new-account charge is NOT
refunded (beneficiary is not in `created_accounts`), but it
equals the refund amount, so `state_gas_used` nets to zero.
Only the outer intrinsic_state remains in the header.
test_inner_create_succeeds_code_deposit_state_gas
(parametrized `outer_outcome` in {succeeds, reverts, halts} x
`create_opcode` in {CREATE, CREATE2})
Creation tx whose initcode does an inner CREATE that succeeds
and deploys 1 byte of code. The outer then terminates normally,
reverts, or halts.
* outer_succeeds: inner GAS_NEW_ACCOUNT + code-deposit
accumulate via `incorporate_child_on_success`. Block state
= 2 * GAS_NEW_ACCOUNT + inner code deposit.
* outer_reverts / outer_halts: top-level failure refund (PR
ethereum#2689) zeroes execution state gas. Only the outer intrinsic
remains.
Both tests complete the coverage gap between ethereum#2707/ethereum#2704/ethereum#2689
single-scenario tests for creation-tx initcode compositions.
4 tasks
spencer-tb
added a commit
to spencer-tb/execution-specs
that referenced
this pull request
Apr 19, 2026
The three regression-fix tests in commit 4828ae6 used hardcoded empirical `block_regular` dicts (per CREATE/CREATE2 x self/external variant) to discriminate a spurious `GAS_NEW_ACCOUNT` charge on the CALL. The dicts are brittle to any regular-gas constant change and the spurious-charge discriminator is redundant: PR ethereum#2707's own tests (`test_create_selfdestruct_*`) already exercise the refund path. Drop `header_verify` from: test_call_value_to_self_destructed_header_gas_used test_call_value_to_self_destructed_burns_value test_call_zero_value_to_self_destructed_same_tx_account The tests still verify runtime behavior: NONEXISTENT created address and orchestrator balance burned to zero. Also adds a cross-over test for the ethereum#2704 + ethereum#2689 refund composition that PR ethereum#2704 does not exercise directly: test_inner_create_fail_refunds_in_creation_tx (parametrized `outer_outcome` in {succeeds, reverts}, `num_inner_ops` in {1, 3}, `create_opcode` in {CREATE, CREATE2}) Creation tx with `num_inner_ops` inner CREATE/CREATE2 calls whose initcode REVERTs. Each inner CREATE's GAS_NEW_ACCOUNT is refunded by PR ethereum#2704. Outer then succeeds or reverts. block_state == outer intrinsic in both cases; a client that regressed to pre-ethereum#2704 "gas persists" behavior would inflate it by `num_inner_ops * GAS_NEW_ACCOUNT`. Rewrites the inverted-premise test from the closed PR ethereum#2639.
spencer-tb
added a commit
to spencer-tb/execution-specs
that referenced
this pull request
Apr 20, 2026
…efund EIP-11532 (merged as PR ethereum#2707) refunds the CREATE's `GAS_NEW_ACCOUNT` at end-of-tx when the created account is also destroyed in the same transaction. This broke three tests in `test_state_gas_call.py` (added by PR ethereum#2646 before ethereum#2707 merged) that asserted `header.gas_used == new_account_state_gas`; after the refund `block_state_gas_used` is zero and the header reports `block_regular` instead. Affected tests (20 variants): test_call_value_to_self_destructed_header_gas_used test_call_value_to_self_destructed_burns_value test_call_zero_value_to_self_destructed_same_tx_account The original discriminator intent (no spurious GAS_NEW_ACCOUNT charge on the CALL) still holds: a buggy extra charge would push `state_gas_used` to 131,488 and bump the header above `new_account_state_gas`. Pin the expected `header.gas_used` to the empirical `block_regular` per variant, with a build-time `assert expected < new_account_state_gas` so the spurious-charge discriminator stays sharp.
4 tasks
spencer-tb
added a commit
to spencer-tb/execution-specs
that referenced
this pull request
Apr 20, 2026
EIP-11532 (merged as ethereum#2707) refunds the CREATE's `GAS_NEW_ACCOUNT` at end-of-tx when the created account is also destroyed in the same transaction. Three tests in `test_state_gas_call.py` (added by ethereum#2646 before ethereum#2707 merged) asserted `header.gas_used == new_account_state_gas`; after the refund `block_state_gas_used` is zero, so the header reports `block_regular` instead and those assertions fail. Drop the brittle `header_verify` from the three tests. Remaining post-state checks still validate the semantics each test cares about. Affected (20 variants across `blockchain_test` and `blockchain_test_engine`): test_call_value_to_self_destructed_header_gas_used test_call_value_to_self_destructed_burns_value test_call_zero_value_to_self_destructed_same_tx_account
spencer-tb
added a commit
to spencer-tb/execution-specs
that referenced
this pull request
Apr 20, 2026
Ports three tests from the closed PR ethereum#2639 that cover reservoir behavior paths not exercised by the merged ethereum#2689/ethereum#2704/ethereum#2707 tests. test_top_level_halt_preserves_restored_reservoir (parametrized reservoir_delta in {-1, 0, 1} x child_termination in {revert, halt}) Regression test for the bal-devnet-3 Besu bug (ethereum#2644). Child runs an SSTORE then fails, restoring state gas to the parent. Parent then INVALIDs, triggering the top-level failure refund. Expected `header.gas_used = gas_limit_cap + min(reservoir_delta, 0)` so the reservoir (including any spill-restore) is preserved across the halt. test_callcode_value_no_new_account_state_gas CALLCODE transfers value to the caller, not to the target, so no new-account state gas is ever charged regardless of whether the target exists. The reservoir stays intact for a subsequent SSTORE. test_create_oog_during_state_gas_charge Parent CALLs an inner with only 20k gas forwarded. The inner's CREATE charges GAS_NEW_ACCOUNT which exceeds the forwarded budget, OOGing before any state gas lands. Per PR ethereum#2704 the refund restores the parent's reservoir and the parent's subsequent SSTORE succeeds from it.
spencer-tb
added a commit
to spencer-tb/execution-specs
that referenced
this pull request
Apr 20, 2026
Two tests that exercise state-gas paths the merged PRs don't
cover directly: both involve a CREATION tx (to=None) whose
initcode interacts with nested CREATE / SELFDESTRUCT semantics.
test_selfdestruct_in_create_tx_initcode
Creation tx whose initcode SELFDESTRUCTs to a new beneficiary.
The outer contract is in `tx_state.created_accounts` and
`accounts_to_delete`, so PR ethereum#2707 refunds its GAS_NEW_ACCOUNT
end-of-tx. The beneficiary's new-account charge is NOT
refunded (beneficiary is not in `created_accounts`), but it
equals the refund amount, so `state_gas_used` nets to zero.
Only the outer intrinsic_state remains in the header.
test_inner_create_succeeds_code_deposit_state_gas
(parametrized `outer_outcome` in {succeeds, reverts, halts} x
`create_opcode` in {CREATE, CREATE2})
Creation tx whose initcode does an inner CREATE that succeeds
and deploys 1 byte of code. The outer then terminates normally,
reverts, or halts.
* outer_succeeds: inner GAS_NEW_ACCOUNT + code-deposit
accumulate via `incorporate_child_on_success`. Block state
= 2 * GAS_NEW_ACCOUNT + inner code deposit.
* outer_reverts / outer_halts: top-level failure refund (PR
ethereum#2689) zeroes execution state gas. Only the outer intrinsic
remains.
Both tests complete the coverage gap between ethereum#2707/ethereum#2704/ethereum#2689
single-scenario tests for creation-tx initcode compositions.
spencer-tb
added a commit
that referenced
this pull request
Apr 20, 2026
…e gas at end of tx (#2707)
4 tasks
spencer-tb
added a commit
to spencer-tb/execution-specs
that referenced
this pull request
Apr 21, 2026
…e gas at end of tx (ethereum#2707)
spencer-tb
added a commit
to spencer-tb/execution-specs
that referenced
this pull request
Apr 21, 2026
…e gas at end of tx (ethereum#2707)
spencer-tb
added a commit
that referenced
this pull request
Apr 21, 2026
…e gas at end of tx (#2707)
9 tasks
spencer-tb
added a commit
to spencer-tb/execution-specs
that referenced
this pull request
May 22, 2026
…e gas at end of tx (ethereum#2707)
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.
🗒️ Description
Implements SELFDESTRUCT same tx state gas refund: when an account is created AND self-destructed in the same transaction (EIP-6780), refund its state gas to
state_gas_reservoirat the end of the transaction.EIP text change: ethereum/EIPs#11532
Spec change: 0e1c131
After
process_message_callreturns, iteratetx_output.accounts_to_delete. For each address also intx_state.created_accounts, refund:STATE_BYTES_PER_NEW_ACCOUNT * cost_per_state_byte(account)STATE_BYTES_PER_STORAGE_SET * cost_per_state_byteper non-zero final storage slotlen(code) * cost_per_state_byte(code deposit)Clamped to
state_gas_used(cumulative across multiple accounts becausestate_gas_used -= refundeach iteration). Applied beforetx_gas_used_before_refundis computed soblock_state_gas_usedreflects the refund.The refund loop runs only when
tx_output.error is None— composes cleanly with the top-level failure refund path from #2689 which zerosstate_gas_usedon top-level revert/halt (and whereaccounts_to_deleteis already empty perinterpreter.py:157).account.code_hashstill points at deployed code here because EIP-6780 defers actual account/storage/code removal to tx-end; a comment in the spec calls this out so readers don't wonder whyget_codedoesn't return empty for a selfdestructed account.Tests: 1495372
Four tests in
test_state_gas_selfdestruct.py, each isolating one term of the refund formula or one guard condition. All strictheader_verifydiscriminators — reverting the spec change fails every variant.test_create_selfdestruct_refunds_account_and_storage, parametrizednum_slotsin {0, 1, 5} andcreate_opcodein {CREATE, CREATE2}. Initcode writes N cold SSTOREs then SELFDESTRUCTs. Asserts refund coversGAS_NEW_ACCOUNT + N * sstore_state_gas.test_create_selfdestruct_refunds_code_deposit_state_gas, parametrized(beneficiary_type, code_size)across {(self, 2), (self, 100), (external, 100)}. Factory CREATEs a contract deployingcode_sizebytes then CALLs it to trigger SELFDESTRUCT. UsesOp.CALL(gas=Op.GAS, address=Op.CREATE(...))so the created address flows via the stack — no hard-coded gas or magic memory slot.externalvariant verifies the refund targets the created account, not the ETH destination.test_create_selfdestruct_no_double_refund_with_sstore_restoration. Initcode doesSSTORE(0, 1)thenSSTORE(0, 0)then SELFDESTRUCT. The 0 to x to 0 restoration refunds the slot inline (via the mechanism from feat(spec-specs, tests): EIP-8037 - 0 to x to 0 SSTORE refunds to state gas #2698); the end-of-tx refund then skips it because the final value is zero. Asserts the end-of-tx refund is account-only.test_selfdestruct_pre_existing_account_no_refund. A contract deployed inpre(NOT same-tx-created) is destroyed by the tx.accounts_to_deletecontains it butcreated_accountsdoes not, so theif address in tx_state.created_accountsguard skips the refund. Blockgas_usedreflects full regular tx cost. Per EIP-6780, the victim account persists post-tx (SELFDESTRUCT only deletes same-tx-created accounts), which the post-state asserts.Shared
init_code_at_high_byteshelper is moved tospec.pyso it can also be used by #2704 / future PRs.🔗 Related Issues or PRs
✅ Checklist
just statictype(scope):.