From 5b83fb34fe8598f9507698b46c7968337e907b87 Mon Sep 17 00:00:00 2001 From: msheth-circle Date: Wed, 11 Feb 2026 19:27:45 +0000 Subject: [PATCH 1/6] =?UTF-8?q?fix(database):=20allow=20Loaded=20=E2=86=92?= =?UTF-8?q?=20Destroyed=20in=20on=5Ftouched=5Fempty=5Fpost=5Feip161?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- crates/database/src/states/account_status.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/crates/database/src/states/account_status.rs b/crates/database/src/states/account_status.rs index 8b3bfcebf7..5fcac37540 100644 --- a/crates/database/src/states/account_status.rs +++ b/crates/database/src/states/account_status.rs @@ -99,7 +99,7 @@ impl AccountStatus { /// /// # Panics /// - /// If current status is [AccountStatus::Loaded] or [AccountStatus::Changed]. + /// If current status is [AccountStatus::Changed]. pub fn on_touched_empty_post_eip161(&self) -> Self { match self { // Account can be touched but not existing. The status should remain the same. @@ -108,9 +108,11 @@ impl AccountStatus { Self::InMemoryChange | Self::Destroyed | Self::LoadedEmptyEIP161 => Self::Destroyed, // Transition to destroy the account. Self::DestroyedAgain | Self::DestroyedChanged => Self::DestroyedAgain, - // Account statuses considered unreachable. - Self::Loaded | Self::Changed => { - unreachable!("Wrong state transition, touch empty is not possible from {self:?}"); + // Account can be loaded and become empty. + Self::Loaded => Self::Destroyed, + // Changed account cannot be empty. + Self::Changed => { + panic!("Wrong state transition, touch empty is not possible from Changed"); } } } @@ -319,6 +321,16 @@ mod test { AccountStatus::DestroyedChanged.on_touched_empty_post_eip161(), AccountStatus::DestroyedAgain ); + assert_eq!( + AccountStatus::Loaded.on_touched_empty_post_eip161(), + AccountStatus::Destroyed + ); + } + + #[test] + #[should_panic] + fn test_on_touched_empty_post_eip161_changed() { + AccountStatus::Changed.on_touched_empty_post_eip161(); } #[test] From 874aef5a1b95ab3f4b5520cbd41eda5f02c6c763 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Thu, 12 Feb 2026 22:08:18 +0400 Subject: [PATCH 2/6] Apply suggestion from @msheth-circle --- crates/database/src/states/account_status.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/database/src/states/account_status.rs b/crates/database/src/states/account_status.rs index 5fcac37540..7be31c2818 100644 --- a/crates/database/src/states/account_status.rs +++ b/crates/database/src/states/account_status.rs @@ -112,7 +112,7 @@ impl AccountStatus { Self::Loaded => Self::Destroyed, // Changed account cannot be empty. Self::Changed => { - panic!("Wrong state transition, touch empty is not possible from Changed"); + unreachable!("Wrong state transition, touch empty is not possible from {self:?}"); } } } From c896a859cb726107b167dc706c2daab1f12e072a Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Tue, 17 Feb 2026 18:43:01 +0400 Subject: [PATCH 3/6] handle Changed status in the same manner --- crates/database/src/states/account_status.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/database/src/states/account_status.rs b/crates/database/src/states/account_status.rs index 7be31c2818..d93cdcece4 100644 --- a/crates/database/src/states/account_status.rs +++ b/crates/database/src/states/account_status.rs @@ -98,7 +98,7 @@ impl AccountStatus { /// Returns the next account status on touched empty account post state clear EIP (EIP-161). /// /// # Panics - /// +/// Returns the next account status on touched empty account post state clear EIP (EIP-161). /// If current status is [AccountStatus::Changed]. pub fn on_touched_empty_post_eip161(&self) -> Self { match self { @@ -108,12 +108,8 @@ impl AccountStatus { Self::InMemoryChange | Self::Destroyed | Self::LoadedEmptyEIP161 => Self::Destroyed, // Transition to destroy the account. Self::DestroyedAgain | Self::DestroyedChanged => Self::DestroyedAgain, - // Account can be loaded and become empty. - Self::Loaded => Self::Destroyed, - // Changed account cannot be empty. - Self::Changed => { - unreachable!("Wrong state transition, touch empty is not possible from {self:?}"); - } + // Account can become empty. + Self::Changed | Self::Loaded => Self::Destroyed, } } @@ -325,6 +321,10 @@ mod test { AccountStatus::Loaded.on_touched_empty_post_eip161(), AccountStatus::Destroyed ); + assert_eq!( + AccountStatus::Changed.on_touched_empty_post_eip161(), + AccountStatus::Destroyed + ); } #[test] From 138f9c208f5a023fd4cbe81833c4afa617323593 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Tue, 17 Feb 2026 18:47:01 +0400 Subject: [PATCH 4/6] Apply suggestion from @msheth-circle --- crates/database/src/states/account_status.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/database/src/states/account_status.rs b/crates/database/src/states/account_status.rs index d93cdcece4..110efb6bb1 100644 --- a/crates/database/src/states/account_status.rs +++ b/crates/database/src/states/account_status.rs @@ -327,12 +327,6 @@ mod test { ); } - #[test] - #[should_panic] - fn test_on_touched_empty_post_eip161_changed() { - AccountStatus::Changed.on_touched_empty_post_eip161(); - } - #[test] fn test_on_touched_created_pre_eip161() { assert_eq!( From 422d3bdd8d7c568ec7c4e5780904937135840bcb Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Tue, 17 Feb 2026 18:48:00 +0400 Subject: [PATCH 5/6] Apply suggestion from @msheth-circle --- crates/database/src/states/account_status.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/database/src/states/account_status.rs b/crates/database/src/states/account_status.rs index 110efb6bb1..e51a8b7545 100644 --- a/crates/database/src/states/account_status.rs +++ b/crates/database/src/states/account_status.rs @@ -96,8 +96,6 @@ impl AccountStatus { } /// Returns the next account status on touched empty account post state clear EIP (EIP-161). - /// - /// # Panics /// Returns the next account status on touched empty account post state clear EIP (EIP-161). /// If current status is [AccountStatus::Changed]. pub fn on_touched_empty_post_eip161(&self) -> Self { From 17e4f0f4854ddcd624a92fa2f8031caadbf68273 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Tue, 17 Feb 2026 18:49:21 +0400 Subject: [PATCH 6/6] Apply suggestion from @msheth-circle --- crates/database/src/states/account_status.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/database/src/states/account_status.rs b/crates/database/src/states/account_status.rs index e51a8b7545..6e0a0dbcce 100644 --- a/crates/database/src/states/account_status.rs +++ b/crates/database/src/states/account_status.rs @@ -96,8 +96,6 @@ impl AccountStatus { } /// Returns the next account status on touched empty account post state clear EIP (EIP-161). -/// Returns the next account status on touched empty account post state clear EIP (EIP-161). - /// If current status is [AccountStatus::Changed]. pub fn on_touched_empty_post_eip161(&self) -> Self { match self { // Account can be touched but not existing. The status should remain the same.