Skip to content

Comments

fix(database): allow EIP161 state clear for empty Loaded and Changed accounts#3421

Merged
rakita merged 6 commits intobluealloy:mainfrom
msheth-circle:fix/eip161-loaded-account
Feb 23, 2026
Merged

fix(database): allow EIP161 state clear for empty Loaded and Changed accounts#3421
rakita merged 6 commits intobluealloy:mainfrom
msheth-circle:fix/eip161-loaded-account

Conversation

@msheth-circle
Copy link
Contributor

@msheth-circle msheth-circle commented Feb 11, 2026

Summary

  • Fix on_touched_empty_post_eip161() panic when called on a Loaded AccountStatus
  • Allow Loaded and ChangedDestroyed transition unconditionally

On EVM chains where the native token is an ERC20, an EOA can hold the native token (balance > 0, nonce=0, no code) and spend it all as an ERC20 via a meta-transaction (relayer pays gas, no nonce change on the EOA), making the account empty. In this scenario, the account was loaded from DB with Loaded status, and since the balance change is tracked in the Journal — not through CacheAccount::change() — the CacheAccount status remains Loaded when touch_empty_eip161 is called.

The fix allows LoadedDestroyed unconditionally. This is safe because touch_empty_eip161 is only reachable when account.is_empty() is true on the Journal output, which requires no code (code_hash == KECCAK_EMPTY). A contract in Loaded status cannot reach this path — SELFDESTRUCT is the only way to remove code, and it's handled by the is_selfdestructed() early return before this point. This is consistent with geth, which has no status distinction and simply deletes any dirty empty account in Finalise().

Test plan

  • Added unit test for LoadedDestroyed transition in on_touched_empty_post_eip161
  • Added #[should_panic] test confirming Changed still panics
  • cargo test -p revm-database — 31 passed
  • cargo nextest run --workspace — 369 passed, 0 failed
  • cargo clippy --workspace --all-targets --all-features — 0 warnings

Previously, on_touched_empty_post_eip161() panicked with unreachable!()
when called on a Loaded AccountStatus. This is reachable when an EOA
with balance is loaded from DB, then its balance is drained to zero
via a meta-transaction (relayer pays gas, no nonce change). The balance
change is tracked in the Journal but CacheAccount::change() is never
called, so the CacheAccount status remains Loaded.

Allow Loaded → Destroyed unconditionally. This is safe because
touch_empty_eip161 is only reachable when account.is_empty() is true,
which requires code_hash == KECCAK_EMPTY. A contract in Loaded status
cannot reach this path since SELFDESTRUCT (the only way to remove code)
is handled by the is_selfdestructed() early return before this point.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@msheth-circle msheth-circle changed the title fix(database): allow Loaded → Destroyed in on_touched_empty_post_eip161 fix(database): allow EIP161 state clear for empty Loaded accounts Feb 11, 2026
@codspeed-hq
Copy link

codspeed-hq bot commented Feb 16, 2026

Merging this PR will not alter performance

✅ 176 untouched benchmarks


Comparing msheth-circle:fix/eip161-loaded-account (17e4f0f) with main (3bccc97)

Open in CodSpeed

@msheth-circle msheth-circle changed the title fix(database): allow EIP161 state clear for empty Loaded accounts fix(database): allow EIP161 state clear for empty Loaded and Changed accounts Feb 17, 2026
@msheth-circle
Copy link
Contributor Author

@rakita Mind taking another look?

@rakita
Copy link
Member

rakita commented Feb 23, 2026

Merging after CI

@msheth-circle
Copy link
Contributor Author

msheth-circle commented Feb 23, 2026

Thanks @rakita! CI is looking good

@rakita rakita merged commit cab55fd into bluealloy:main Feb 23, 2026
31 checks passed
@rakita
Copy link
Member

rakita commented Feb 23, 2026

Will make release soon, in next few days

@github-actions github-actions bot mentioned this pull request Feb 23, 2026
@msheth-circle msheth-circle deleted the fix/eip161-loaded-account branch February 23, 2026 18:55
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