Skip to content

feat(spec-specs, tests): implement EIP-8253, bump nonce of zero-nonce storage accounts - #3535

Draft
jochem-brouwer wants to merge 8 commits into
ethereum:forks/amsterdamfrom
jochem-brouwer:add-eip-8253
Draft

jochem-brouwer wants to merge 8 commits into
ethereum:forks/amsterdamfrom
jochem-brouwer:add-eip-8253

Conversation

@jochem-brouwer

@jochem-brouwer jochem-brouwer commented Sep 6, 2026

Copy link
Copy Markdown
Member

Description

Implements EIP-8253 for Amsterdam, with tests.

Spec. At the start of the fork block, before the pre-execution system calls, the nonce of the 28 Mainnet accounts with empty code, zero nonce and non-empty storage is set to one. Balance, code and storage are untouched. The BAL records one NonceChange [0, 1] per account at block access index 0. The bump is unconditional, as the EIP specifies: the fixed address list selects the accounts, not their state, so a listed account is bumped even when it is absent or has empty storage.

Fork block detection. The spec derives is_fork_block from FORK_CRITERIA and the parent header. The filler passes an explicit fork_activation flag to the transition tool (--state.fork-activation for the CLI and daemon); external t8n binaries do not receive it yet. The spec's own detection is exercised when the validate-blocks CI step replays the transition fixtures through state_transition, which needs #3564.

Pre-states. The framework imposes no Mainnet-shape restriction on custom pre-states, at the listed addresses or elsewhere: the list only says which addresses are bumped. Existing tests that build codeless zero-nonce storage accounts keep running at Amsterdam. The EXTCODEHASH empty-account test gains a nonce-bearing storage variant, which is the only such shape reachable on Mainnet after Spurious Dragon.

Tests (tests/amsterdam/eip8253_..., all fork-transition; see its test_cases.md): nonce bump with full BAL and post-state checks, with the fork block at the fork timestamp or past it; CREATE collision at and after the fork block from a synthetic factory at each account's historical creator address and nonce (verified against chain data), plus CREATE failures before the target is accessed; calls to a bumped account; non-targeted and same-shape accounts untouched; listed accounts that are absent or have empty storage. BAL merging of the index-zero nonce change with reads, transfers, reverted transfers, OOG before access, EIP-2930 listings, SELFDESTRUCT, fee recipient, withdrawals, and the BAL size budget; and rejection of BALs with a missing, wrong, duplicated, split, mis-ordered, or non-canonically encoded bump. The EIP-7928 BAL size transition test budgets the 28 extra fork-block entries. The BAL modifier module gains a nonce_block_access_index scalar for the index-zero encoding test.

None of the 28 addresses is reachable by CREATE again: 26 were created by EOA transactions whose nonces have moved on, and the 2 factory-created ones came from factories that self-destructed and, under EIP-161, could never again create from nonce 0. Replaying the original creation transactions is not possible either: under EIP-8037 state gas every one of them runs out of gas within its signed gas limit.

Related Issues or PRs

Depends on #3564 for the spec-side fork-block coverage. Test cases follow the list proposed by @nerolation.

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.

Cute Animal Picture

Put a link to a cute animal picture inside the parenthesis-->

… block

Set the nonce of the 28 Mainnet accounts with empty code, a zero nonce and
non-empty storage to one at the start of the fork block, before the
pre-execution system contract calls. The change is recorded in the block
access list at block access index zero.

`execute_block` derives whether a block activates the fork from
`FORK_CRITERIA` and the parent header, and passes it to `apply_body`.
`ForkLoad` exposes the new module to the transition tool.
…vation

Add fork-transition tests for EIP-8253: the nonce bump and its block access
list entries, a replay of each account's Mainnet creation colliding at the
fork block, calls to a bumped account, untouched non-targeted accounts, and
the unconditional bump of accounts absent from the pre-state.

The blockchain filler now tells the transition tool when a block activates
its fork (`fork_activation`), which the in-process spec t8n uses to apply
the bump; the CLI and daemon accept `--state.fork-activation`.

Forks expose `zero_nonce_storage_accounts()`. Pre-states are checked
against the EIP-8253 invariants: before the bump, a listed address must
have a zero nonce and no code; from the bump on, no account may have empty
code, a zero nonce and non-empty storage. The EIP-7928 BAL size transition
test budgets the extra fork-block entries.
@codecov

codecov Bot commented Sep 6, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.30435% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.39%. Comparing base (903b48f) to head (3183156).
⚠️ Report is 15 commits behind head on forks/amsterdam.

Files with missing lines Patch % Lines
src/ethereum/forks/amsterdam/fork.py 66.66% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@                 Coverage Diff                 @@
##           forks/amsterdam    #3535      +/-   ##
===================================================
+ Coverage            94.01%   94.39%   +0.38%     
===================================================
  Files                  624      625       +1     
  Lines                36912    36949      +37     
  Branches              3326     3328       +2     
===================================================
+ Hits                 34702    34878     +176     
+ Misses                1533     1459      -74     
+ Partials               677      612      -65     
Flag Coverage Δ
unittests 94.39% <91.30%> (+0.38%) ⬆️

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.

@jochem-brouwer jochem-brouwer changed the title feat(spec-specs): Add EIP-8253 feat(spec-specs, tests): implement EIP-8253, bump nonce of zero-nonce storage accounts Sep 7, 2026
@jochem-brouwer

Copy link
Copy Markdown
Member Author

(Dumping some extra output of Claude here)

Tests capped by EIP-8253

From Amsterdam on, no account may have empty code, a zero nonce, and
non-empty storage: the fork block bumps the last such Mainnet accounts to
nonce one, and no code path has been able to create one since Spurious
Dragon. The filler now rejects pre-states of that shape at Amsterdam and
later, so the following tests carry valid_before("EIP8253"). Each one
places an account with nonce=0, no code, and non-zero storage in its
pre-state.

# File Test Capped scope
1 tests/constantinople/eip1052_extcodehash/test_extcodehash.py test_extcodehash_codeless_with_storage parameter balance only
2 tests/constantinople/eip1052_extcodehash/test_extcodehash.py test_extcodehash_empty_account_variants parameter balance-storage only
3 tests/ported_static/stNonZeroCallsTest/test_non_zero_value_suicide_to_one_storage_key_paris.py test_non_zero_value_suicide_to_one_storage_key_paris whole test
4 tests/ported_static/stNonZeroCallsTest/test_non_zero_value_transaction_call_to_one_storage_key_paris.py test_non_zero_value_transaction_call_to_one_storage_key_paris whole test
5 tests/ported_static/stNonZeroCallsTest/test_non_zero_value_transaction_cal_lwith_data_to_one_storage_key_paris.py test_non_zero_value_transaction_cal_lwith_data_to_one_storage_key_paris whole test
6 tests/ported_static/stRevertTest/test_revert_precompiled_touch_storage_paris.py test_revert_precompiled_touch_storage_paris whole test
7 tests/ported_static/stSpecialTest/test_eoa_empty_paris.py test_eoa_empty_paris whole test
8 tests/ported_static/stZeroCallsRevert/test_zero_value_call_to_one_storage_key_oog_revert_paris.py test_zero_value_call_to_one_storage_key_oog_revert_paris whole test
9 tests/ported_static/stZeroCallsRevert/test_zero_value_callcode_to_one_storage_key_oog_revert_paris.py test_zero_value_callcode_to_one_storage_key_oog_revert_paris whole test
10 tests/ported_static/stZeroCallsRevert/test_zero_value_delegatecall_to_one_storage_key_oog_revert_paris.py test_zero_value_delegatecall_to_one_storage_key_oog_revert_paris whole test
11 tests/ported_static/stZeroCallsRevert/test_zero_value_suicide_to_one_storage_key_oog_revert_paris.py test_zero_value_suicide_to_one_storage_key_oog_revert_paris whole test
12 tests/ported_static/stZeroCallsTest/test_zero_value_call_to_one_storage_key_paris.py test_zero_value_call_to_one_storage_key_paris whole test
13 tests/ported_static/stZeroCallsTest/test_zero_value_callcode_to_one_storage_key_paris.py test_zero_value_callcode_to_one_storage_key_paris whole test
14 tests/ported_static/stZeroCallsTest/test_zero_value_delegatecall_to_one_storage_key_paris.py test_zero_value_delegatecall_to_one_storage_key_paris whole test
15 tests/ported_static/stZeroCallsTest/test_zero_value_suicide_to_one_storage_key_paris.py test_zero_value_suicide_to_one_storage_key_paris whole test
16 tests/ported_static/stZeroCallsTest/test_zero_value_transaction_call_to_one_storage_key_paris.py test_zero_value_transaction_call_to_one_storage_key_paris whole test
17 tests/ported_static/stZeroCallsTest/test_zero_value_transaction_cal_lwith_data_to_one_storage_key_paris.py test_zero_value_transaction_cal_lwith_data_to_one_storage_key_paris whole test

Alternative to capping

The ported tests (3 to 17) all use the same pre-state line,
pre[addr] = Account(balance=..., storage={0: 1}). Giving that account
nonce=1 when the fork is Amsterdam or later would keep Amsterdam coverage
at the cost of diverging from the ported fixture. This was not done in the
PR; the cap keeps the ported tests byte-for-byte faithful.

@jochem-brouwer

Copy link
Copy Markdown
Member Author

@jochem-brouwer

Copy link
Copy Markdown
Member Author

Hi all, the review of this PR has two parts:

  • Verify EIP-8253 spec and tests
  • Ensure the changes to "old" tests (see: 4a5276d) are valid

These can be done in parallel 😄 👍

…e EIP-8253 case map

Add a `nonce-balance-storage` variant to the EXTCODEHASH empty-account
test: a codeless account with storage and nonce one fails the EIP-684
creation check, so its storage can never be adopted and the case is
valid at every fork.

Bring `test_cases.md` in line with the consolidated tests: the renamed
`test_targeted_accounts_without_storage`, the activation delay on the
bump test, the synthetic factory wording, and the fact that the spec's
own fork-block detection is exercised when `validate-blocks` replays
transition fixtures (ethereum#3564).
@jochem-brouwer

Copy link
Copy Markdown
Member Author

This PR is blocked by #3557 which should first be merged 😄 👍

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.

2 participants