Skip to content

feat(tests): pin the BAL for contract creation over a storage-only account - #3588

Draft
spencer-tb wants to merge 2 commits into
ethereum:forks/amsterdamfrom
spencer-tb:tests/amsterdam/bal-create-storage-only-target
Draft

spencer-tb wants to merge 2 commits into
ethereum:forks/amsterdamfrom
spencer-tb:tests/amsterdam/bal-create-storage-only-target

Conversation

@spencer-tb

@spencer-tb spencer-tb commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Description

Stacked on #3508: only the last commit is new, the first is #3508's. The new commit changes one function in the Amsterdam BAL builder and adds the tests.

What

Three BAL tests (38 cases) that pin what the block access list shows when a contract creation lands on an account with zero nonce, no code and non-empty storage, now that #3508 makes the creation wipe that storage:

  • test_bal_create_tx_storage_only_target (12 cases) and test_bal_create_opcode_storage_only_target (24, CREATE and CREATE2). The init code leaves the wiped slots alone, reads one, rewrites one to its old value, writes a new value, reads then reverts, or selfdestructs; the target has balance 0 or 1.
  • test_bal_cross_tx_create_storage_only_target_then_call (2 cases). Tx1 creates over the account, tx2 reads or rewrites a wiped slot.

What they pin:

  • the wiped slots never appear in the BAL (the pre-state cannot enumerate them);
  • a read of a wiped slot sees 0 and is a storage_read;
  • a same-tx rewrite of the old value is a storage_change, compared against the wiped value like a rewrite by a later transaction;
  • a write of a new value is a storage_change;
  • a revert keeps the storage but the read survives;
  • a same-tx selfdestruct leaves only the balance change (or an empty entry), and the account is gone;
  • a later tx that rewrites the pre-block value is a storage_change, since the block-level pre-tx value is 0 after the wipe.

Spec change

_get_pre_tx_storage in the Amsterdam BAL builder now returns 0 for an address in the current transaction's storage_clears, as it already did (from #3508) for the block's. Before this, a slot wiped and then written in the same transaction was compared against the pre-block value, so restoring that value counted as a no-op while the same rewrite by a later transaction counted as a change. Where the wipe falls relative to a transaction boundary should not change the reference value. It is also what nethermind, reth and besu produce (table below), since their original-value snapshot is taken after the clear. No other Amsterdam expectation moves: the full EIP-7928 directory refills identically.

Verification

  • 76/76 fixtures fill for the new tests (38 cases in blockchain and engine formats); the whole tests/amsterdam/eip7928_block_level_access_lists/ directory fills 2172/2172 on the branch.
  • The same test file on forks/amsterdam without fix(spec-specs): wipe pre-existing storage on contract creation from Cancun onward #3508 fails 26/38. Only the revert and selfdestruct arms pass, since their end state is identical under keep or wipe.
  • just static clean.

Client survey

Read at today's master/main heads, not run (geth 94548a1, nethermind 0d43dbdf, reth c3f0746 with revm 43.0.2, besu 9c80643, erigon d6735e9, all 2026-09-15):

Client EIP-7610 Storage on create Wiped slots in BAL Same-tx SSTORE back to old value Later tx SLOAD of wiped slot
EELS, this PR off wiped omitted change (vs post-wipe 0) 0
geth off (#35581) kept: the account object is reused, init sees the old values n/a no-op old value
nethermind off wiped (tx and opcode paths) omitted change (vs post-wipe 0) 0
besu off (#11191) wiped omitted change (vs post-wipe 0) old value (stale flat DB read)
reth/revm off wiped in the EVM only; the DB and state root keep the old slots, so the block is rejected omitted change (vs post-wipe 0) 0
erigon off (#23706) wiped; the creation's storage prefix delete reaches the DB and the root omitted change (vs post-wipe 0) in the BAL, but the write normaliser drops the SSTORE as a no-op against the pre-block value, so the slot ends 0 0

Each column is an arm: post-state slot 2 on every non-revert arm (wipe vs keep), the strict empty storage_changes on the untouched arm (omission), the rewrite_old_value arm, and the cross-tx read_wiped_slot case.

Expected client outcomes

By code reading:

Client Expected What fails
nethermind 38 / 38 nothing
erigon 32 / 38 the same-tx rewrite cases on state root: the BAL is right but the persisted write is dropped, then the prefix delete removes the slot (the cross-tx rewrite may fail the same way; ordering not verified)
besu 37 / 38 the cross-tx read: a later tx sees the stale pre-block value, stores 2 instead of 1, wrong root
reth 12 / 38 every wipe arm on state root, since the DB keeps the old slots
geth 12 / 38 every wipe arm on state, since it never wipes

Nethermind matches EELS, and every remaining failure is an inconsistency inside that client rather than a test convention: geth reuses the existing account object so the empty-storage start never happens, reth wipes in the EVM but persists and roots the old slots, besu clears the storage but its accumulator does not consult the cleared set when reading from the flat DB, and erigon's write normaliser still assumes a creation does not wipe pre-existing storage, a comment left over from before its 7610 removal.

Two caveats. Nethermind's parallel BAL-validation path treats the clear as a no-op, which could bite the read arms under engine validation; whether it falls back to sequential execution was not traced. And the current glamsterdam-devnet-8 hive images are older than these heads: besu's and erigon's still abort on EIP-7610 there, so a hive run today would show 0 / 38 for both until their images catch up with main.

None of this is reachable on mainnet: it needs a create address colliding with one of the 28 storage-only accounts, and EIP-8253 gives those nonce 1. Whether these fixtures ship in a release is an ACDT call; as they stand they document the divergence.

Related Issues or PRs

Stacked on #3508. Related to #3417, #3425, ethereum/legacytests#18 and EIP-8253.

Checklist

  • Ran fast static checks to avoid CI fails, see Code Standards & Verifying Changes: just static
  • PR title has the form <type>(<area>): <title>, where <type> and <area> come from an appropriate C-<type>, respectively A-<area>, label. The title should match the target squash commit message.

@spencer-tb spencer-tb added C-feat Category: an improvement or new feature A-tests Area: Consensus tests. labels Sep 15, 2026
@codecov

codecov Bot commented Sep 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.44%. Comparing base (ad19524) to head (41e5543).
⚠️ Report is 51 commits behind head on forks/amsterdam.

Additional details and impacted files
@@                 Coverage Diff                 @@
##           forks/amsterdam    #3588      +/-   ##
===================================================
+ Coverage            94.01%   94.44%   +0.43%     
===================================================
  Files                  624      624              
  Lines                36905    36990      +85     
  Branches              3326     3345      +19     
===================================================
+ Hits                 34695    34937     +242     
+ Misses                1533     1450      -83     
+ Partials               677      603      -74     
Flag Coverage Δ
unittests 94.44% <100.00%> (+0.43%) ⬆️

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

☔ View full report in Codecov by Harness.
📢 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.

…count

Diff storage writes that follow a same-transaction wipe against zero in
the Amsterdam BAL builder, as it already does for a wipe by an earlier
transaction, and add three BAL tests (38 cases) for a creation landing on
an account with zero nonce, no code and non-empty storage.
@spencer-tb
spencer-tb force-pushed the tests/amsterdam/bal-create-storage-only-target branch from 0af5725 to 41e5543 Compare September 15, 2026 10:39
@spencer-tb spencer-tb added the A-spec-specs Area: Specification—The Ethereum specification itself (eg. `src/ethereum/*`) label Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-spec-specs Area: Specification—The Ethereum specification itself (eg. `src/ethereum/*`) A-tests Area: Consensus tests. C-feat Category: an improvement or new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant