Skip to content

feat(tests): EIP-7928 self destruct to system address with 0 value#2893

Merged
fselmo merged 3 commits into
ethereum:forks/amsterdamfrom
taratorio:bal-selfdestruct-to-system-address-0-value
May 22, 2026
Merged

feat(tests): EIP-7928 self destruct to system address with 0 value#2893
fselmo merged 3 commits into
ethereum:forks/amsterdamfrom
taratorio:bal-selfdestruct-to-system-address-0-value

Conversation

@taratorio

Copy link
Copy Markdown
Contributor

Summary

Adds test_bal_selfdestruct_to_system_address_zero_balance to tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4788.py.

The test covers the intersection of two BAL rules that previously had no joint coverage:

  • EIP-7928 inclusion list: "Beneficiary addresses for SELFDESTRUCT" MUST appear in the BAL — regardless of value transfer.
  • EIP-7928 SystemAddress carve-out: SYSTEM_ADDRESS (0xff...fffe) "MUST NOT be included unless it experiences state access itself" — and a SELFDESTRUCT to it is such an access, so the exception fires.

Scenario

A single CREATE transaction whose init code is Op.SELFDESTRUCT(SYSTEM_ADDRESS), with the new contract's balance still zero (the CREATE tx carries no value). Per EIP-6780 the contract is actually deleted because creation and destruction happen in the same transaction. The resulting BAL must contain the SYSTEM_ADDRESS entry with every change-set empty — the SELFDESTRUCT is the only access on SYSTEM_ADDRESS in this block.

Motivation

This case was identified via a real-world client divergence on a Block Access List devnet — one client dropped the SYSTEM_ADDRESS entry under this combination of conditions, producing a BAL hash mismatch with other clients. Existing BAL SELFDESTRUCT tests in this directory all use either a beacon-root / history-storage / withdrawal-queue / consolidation-queue beneficiary or an ordinary EOA — none of them target SYSTEM_ADDRESS itself, so the carve-out's "unless it experiences state access" clause was never exercised end-to-end.

Test plan

  • uv run fill tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists_eip4788.py::test_bal_selfdestruct_to_system_address_zero_balance --fork Amsterdam — fills against EELS t8n.
  • Generated blockchain_test fixture executes successfully against a spec-correct client.
  • Generated blockchain_test fixture deterministically reproduces the divergence against a non-spec-correct client (BAL hash mismatch, missing SYSTEM_ADDRESS entry).

pull Bot pushed a commit to Dustin4444/erigon that referenced this pull request May 22, 2026
…alue transfer (erigontech#21333)

added a test for this to EEST
ethereum/execution-specs#2893

---

## Summary

`makeSelfdestructGasFn` in `execution/vm/operations_acl.go` previously
gated `MarkAddressAccess(beneficiary, false)` on the destructing
contract having a non-zero balance, and on the zero-balance path
additionally marked the gas-calc `Empty()` reads internal so the
beneficiary would not appear in the BAL purely from that read.

Per EIP-7928, "Beneficiary addresses for `SELFDESTRUCT`" are in scope
for inclusion unconditionally — the SELFDESTRUCT is itself a state
access on the beneficiary, independent of any value transfer. The
pre-fix gate caused a BAL hash mismatch in the specific case where a
SELFDESTRUCT targets `SYSTEM_ADDRESS` (`0xff…fffe`) with the destructing
contract at zero balance: `versionedio.go::AsBlockAccessList`'s "drop
SystemAddress unless it experienced state access itself" filter then
dropped the entry because no non-revertable access mark was ever
recorded, while other clients (which mark the beneficiary
unconditionally) kept it. Result: BAL hash mismatch, block rejected,
sync stall.

## Fix

`execution/vm/operations_acl.go::makeSelfdestructGasFn`: record the
beneficiary access unconditionally:

```go
// Per EIP-7928, SELFDESTRUCT is a state access on the beneficiary
// independently of any value transfer, so record it unconditionally.
evm.IntraBlockState().MarkAddressAccess(address, false)
```

The pre-existing `SnapshotVersionedReadKeys` / `MarkNewReadsInternal`
helpers in `execution/state/intra_block_state.go` were only used by the
now-removed zero-balance branch and are dropped as dead code (−35
lines).

`execution/state/versionedio.go::AsBlockAccessList`'s SystemAddress
filter is left unchanged — its `&& !account.nonRevertableUserAccess`
clause is now correctly satisfied by the unconditional mark above.

## Test coverage

New file `execution/execmodule/bal_selfdestruct_systemaddress_test.go`
adds three regression tests via `blockgen.GenerateChain` against
`chain.AllProtocolChanges` (Amsterdam active at genesis):

- `TestBALIncludesSystemAddressOnSelfdestructToItWithZeroBalance` — the
bug scenario: CREATE tx with init code `PUSH20 SYSTEM_ADDRESS;
SELFDESTRUCT` and zero contract balance. Asserts the resulting BAL
contains a SystemAddress entry with every change-set empty.
- `TestBALIncludesSystemAddressOnSelfdestructToItWithNonZeroBalance` —
guard for the non-zero balance variant.
- `TestBALIncludesOrdinaryBeneficiaryOnSelfdestructWithZeroBalance` —
guard that ordinary EOA beneficiaries (where the SystemAddress filter
doesn't apply) are unaffected.

## Test plan

- [x] `go test -count=1 -run='TestBALIncludes' ./execution/execmodule/`
— 3/3 pass.
- [x] TDD verified: with the pre-fix `if !balance.IsZero()` guard
restored,
`TestBALIncludesSystemAddressOnSelfdestructToItWithZeroBalance` fails
with "BAL must include a SystemAddress entry"; the other two still pass
(confirming the bug is specific to SystemAddress + zero balance).
- [x] `make lint` — 0 issues.
- [x] `make eest-spec-statetests-devnet` — 65,202 / 65,202 tests pass.
- [x] `make eest-spec-blocktests-devnet` — 82,941 / 82,941 tests pass
(parallel exec).
- [x] Fresh resync from block 0 against the BAL devnet through
previously-divergent blocks: 0 BAL mismatches.
- [x] EEST upstream coverage: matching test
`test_bal_selfdestruct_to_system_address_zero_balance` opened as a PR
against `ethereum/execution-specs devnets/bal/7` (link to follow).

---------

Co-authored-by: Alex Sharov <AskAlexSharov@gmail.com>
@fselmo fselmo changed the base branch from devnets/bal/7 to forks/amsterdam May 22, 2026 16:38
@fselmo fselmo force-pushed the bal-selfdestruct-to-system-address-0-value branch from 4ec7f8a to 4c63727 Compare May 22, 2026 16:39
@fselmo

fselmo commented May 22, 2026

Copy link
Copy Markdown
Contributor

Thanks @taratorio. I pointed this to forks/amsterdam as this is our default branch at the moment... and it can trickle back up to devnet branches (that's the force push here). I also cleaned it up to use dynamic gas limit and added this to test_cases.md. Will wait for CI.

@fselmo fselmo force-pushed the bal-selfdestruct-to-system-address-0-value branch from 4c63727 to 59436f8 Compare May 22, 2026 16:45

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

lgtm, will wait for CI 👍🏼

@codecov

codecov Bot commented May 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.44%. Comparing base (55d774b) to head (9eb02f6).
⚠️ Report is 2 commits behind head on forks/amsterdam.

Additional details and impacted files
@@               Coverage Diff                @@
##           forks/amsterdam    #2893   +/-   ##
================================================
  Coverage            90.44%   90.44%           
================================================
  Files                  535      535           
  Lines                32439    32439           
  Branches              3012     3012           
================================================
  Hits                 29338    29338           
  Misses                2573     2573           
  Partials               528      528           
Flag Coverage Δ
unittests 90.44% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@fselmo fselmo merged commit 810c184 into ethereum:forks/amsterdam May 22, 2026
17 checks passed
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.

3 participants