Skip to content

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

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

feat(spec-specs, tests): implement EIP-8253, bump nonce of zero-nonce storage accounts#3535
jochem-brouwer wants to merge 2 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: test chains stand in for Mainnet.

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.

Pre-state invariants. Since test chains model Mainnet, filling now rejects pre-states that contradict the EIP: before the bump, a listed address must have zero nonce and no code; from the bump on, no account may have empty code, zero nonce and non-empty storage. The 17 existing tests that build that shape are capped with valid_before("EIP8253") (2 EXTCODEHASH cases, 15 ported static tests).

Tests (tests/amsterdam/eip8253_..., all fork-transition): nonce bump with full BAL and post-state checks; CREATE collision at the fork block by replaying each account's Mainnet creation (original creator address and nonce, verified against chain data); calls to a bumped account; non-targeted accounts untouched; bump of accounts absent from the pre-state. The EIP-7928 BAL size transition test budgets the 28 extra fork-block entries.

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.

Related Issues or PRs

N/A.

Checklist

  • Add the tests to be ported with the new EIP-8253 guard
  • 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 60.86957% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.63%. Comparing base (903b48f) to head (2457146).
⚠️ Report is 1 commits behind head on forks/amsterdam.

Files with missing lines Patch % Lines
...eum/forks/amsterdam/zero_nonce_storage_accounts.py 58.82% 7 Missing ⚠️
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%   93.63%   -0.38%     
===================================================
  Files                  624      625       +1     
  Lines                36912    36934      +22     
  Branches              3326     3328       +2     
===================================================
- Hits                 34702    34583     -119     
- Misses                1533     1624      +91     
- Partials               677      727      +50     
Flag Coverage Δ
unittests 93.63% <60.86%> (-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

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.

1 participant