revm_database/states/account_status.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
/// AccountStatus represents the various states an account can be in after being loaded from the database.
///
/// After account get loaded from database it can be in a lot of different states
/// while we execute multiple transaction and even blocks over account that is in memory.
/// This structure models all possible states that account can be in.
///
/// # Variants
///
/// - `LoadedNotExisting`: the account has been loaded but does not exist.
/// - `Loaded`: the account has been loaded and exists.
/// - `LoadedEmptyEIP161`: the account is loaded and empty, as per EIP-161.
/// - `InMemoryChange`: there are changes in the account that exist only in memory.
/// - `Changed`: the account has been modified.
/// - `Destroyed`: the account has been destroyed.
/// - `DestroyedChanged`: the account has been destroyed and then modified.
/// - `DestroyedAgain`: the account has been destroyed again.
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AccountStatus {
#[default]
LoadedNotExisting,
Loaded,
LoadedEmptyEIP161,
InMemoryChange,
Changed,
Destroyed,
DestroyedChanged,
DestroyedAgain,
}
impl AccountStatus {
/// Account is not modified and just loaded from database.
pub fn is_not_modified(&self) -> bool {
matches!(
self,
AccountStatus::LoadedNotExisting
| AccountStatus::Loaded
| AccountStatus::LoadedEmptyEIP161
)
}
/// Account was destroyed by calling SELFDESTRUCT.
/// This means that full account and storage are inside memory.
pub fn was_destroyed(&self) -> bool {
matches!(
self,
AccountStatus::Destroyed
| AccountStatus::DestroyedChanged
| AccountStatus::DestroyedAgain
)
}
/// This means storage is known, it can be newly created or storage got destroyed.
pub fn is_storage_known(&self) -> bool {
matches!(
self,
AccountStatus::LoadedNotExisting
| AccountStatus::InMemoryChange
| AccountStatus::Destroyed
| AccountStatus::DestroyedChanged
| AccountStatus::DestroyedAgain
)
}
/// Account is modified but not destroyed.
/// This means that some storage values can be found in both
/// memory and database.
pub fn is_modified_and_not_destroyed(&self) -> bool {
matches!(self, AccountStatus::Changed | AccountStatus::InMemoryChange)
}
/// Returns the next account status on creation.
pub fn on_created(&self) -> AccountStatus {
match self {
// if account was destroyed previously just copy new info to it.
AccountStatus::DestroyedAgain
| AccountStatus::Destroyed
| AccountStatus::DestroyedChanged => AccountStatus::DestroyedChanged,
// if account is loaded from db.
AccountStatus::LoadedNotExisting
// Loaded empty eip161 to creates is not possible as CREATE2 was added after EIP-161
| AccountStatus::LoadedEmptyEIP161
| AccountStatus::Loaded
| AccountStatus::Changed
| AccountStatus::InMemoryChange => {
// If account is loaded and not empty this means that account has some balance.
// This means that account cannot be created.
// We are assuming that EVM did necessary checks before allowing account to be created.
AccountStatus::InMemoryChange
}
}
}
/// Returns the next account status on touched empty account post state clear EIP (EIP-161).
///
/// # Panics
///
/// If current status is [AccountStatus::Loaded] or [AccountStatus::Changed].
pub fn on_touched_empty_post_eip161(&self) -> AccountStatus {
match self {
// Account can be touched but not existing. The status should remain the same.
AccountStatus::LoadedNotExisting => AccountStatus::LoadedNotExisting,
// Account can be created empty and only then touched.
AccountStatus::InMemoryChange
| AccountStatus::Destroyed
| AccountStatus::LoadedEmptyEIP161 => AccountStatus::Destroyed,
// Transition to destroy the account.
AccountStatus::DestroyedAgain | AccountStatus::DestroyedChanged => {
AccountStatus::DestroyedAgain
}
// Account statuses considered unreachable.
AccountStatus::Loaded | AccountStatus::Changed => {
unreachable!("Wrong state transition, touch empty is not possible from {self:?}");
}
}
}
/// Returns the next account status on touched or created account pre state clear EIP (EIP-161).
/// Returns `None` if the account status didn't change.
///
/// # Panics
///
/// If current status is [AccountStatus::Loaded] or [AccountStatus::Changed].
pub fn on_touched_created_pre_eip161(&self, had_no_info: bool) -> Option<AccountStatus> {
match self {
AccountStatus::LoadedEmptyEIP161 => None,
AccountStatus::DestroyedChanged => {
if had_no_info {
None
} else {
Some(AccountStatus::DestroyedChanged)
}
}
AccountStatus::Destroyed | AccountStatus::DestroyedAgain => {
Some(AccountStatus::DestroyedChanged)
}
AccountStatus::InMemoryChange | AccountStatus::LoadedNotExisting => {
Some(AccountStatus::InMemoryChange)
}
AccountStatus::Loaded | AccountStatus::Changed => {
unreachable!("Wrong state transition, touch crate is not possible from {self:?}")
}
}
}
/// Returns the next account status on change.
pub fn on_changed(&self, had_no_nonce_and_code: bool) -> AccountStatus {
match self {
// If the account was loaded as not existing, promote it to changed.
// This account was likely created by a balance transfer.
AccountStatus::LoadedNotExisting => AccountStatus::InMemoryChange,
// Change on empty account, should transfer storage if there is any.
// There is possibility that there are storage entries inside db.
// That storage is used in merkle tree calculation before state clear EIP.
AccountStatus::LoadedEmptyEIP161 => AccountStatus::InMemoryChange,
// The account was loaded as existing.
AccountStatus::Loaded => {
if had_no_nonce_and_code {
// account is fully in memory
AccountStatus::InMemoryChange
} else {
// can be contract and some of storage slots can be present inside db.
AccountStatus::Changed
}
}
// On change, the "changed" type account statuses are preserved.
// Any checks for empty accounts are done outside of this fn.
AccountStatus::Changed => AccountStatus::Changed,
AccountStatus::InMemoryChange => AccountStatus::InMemoryChange,
AccountStatus::DestroyedChanged => AccountStatus::DestroyedChanged,
// If account is destroyed and then changed this means this is
// balance transfer.
AccountStatus::Destroyed | AccountStatus::DestroyedAgain => {
AccountStatus::DestroyedChanged
}
}
}
/// Returns the next account status on selfdestruct.
pub fn on_selfdestructed(&self) -> AccountStatus {
match self {
// Non existing account can't be destroyed.
AccountStatus::LoadedNotExisting => AccountStatus::LoadedNotExisting,
// If account is created and selfdestructed in the same block, mark it as destroyed again.
// Note: there is no big difference between Destroyed and DestroyedAgain in this case,
// but was added for clarity.
AccountStatus::DestroyedChanged
| AccountStatus::DestroyedAgain
| AccountStatus::Destroyed => AccountStatus::DestroyedAgain,
// Transition to destroyed status.
_ => AccountStatus::Destroyed,
}
}
/// Transition to other state while preserving invariance of this state.
///
/// It this account was Destroyed and other account is not:
/// we should mark extended account as destroyed too.
/// and as other account had some changes, extended account
/// should be marked as DestroyedChanged.
///
/// If both account are not destroyed and if this account is in memory:
/// this means that extended account is in memory too.
///
/// Otherwise, if both are destroyed or other is destroyed:
/// set other status to extended account.
pub fn transition(&mut self, other: Self) {
*self = match (self.was_destroyed(), other.was_destroyed()) {
(true, false) => Self::DestroyedChanged,
(false, false) if *self == Self::InMemoryChange => Self::InMemoryChange,
_ => other,
};
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_account_status() {
// account not modified
assert!(AccountStatus::Loaded.is_not_modified());
assert!(AccountStatus::LoadedEmptyEIP161.is_not_modified());
assert!(AccountStatus::LoadedNotExisting.is_not_modified());
assert!(!AccountStatus::Changed.is_not_modified());
assert!(!AccountStatus::InMemoryChange.is_not_modified());
assert!(!AccountStatus::Destroyed.is_not_modified());
assert!(!AccountStatus::DestroyedChanged.is_not_modified());
assert!(!AccountStatus::DestroyedAgain.is_not_modified());
// we know full storage
assert!(!AccountStatus::LoadedEmptyEIP161.is_storage_known());
assert!(AccountStatus::LoadedNotExisting.is_storage_known());
assert!(AccountStatus::InMemoryChange.is_storage_known());
assert!(AccountStatus::Destroyed.is_storage_known());
assert!(AccountStatus::DestroyedChanged.is_storage_known());
assert!(AccountStatus::DestroyedAgain.is_storage_known());
assert!(!AccountStatus::Loaded.is_storage_known());
assert!(!AccountStatus::Changed.is_storage_known());
// account was destroyed
assert!(!AccountStatus::LoadedEmptyEIP161.was_destroyed());
assert!(!AccountStatus::LoadedNotExisting.was_destroyed());
assert!(!AccountStatus::InMemoryChange.was_destroyed());
assert!(AccountStatus::Destroyed.was_destroyed());
assert!(AccountStatus::DestroyedChanged.was_destroyed());
assert!(AccountStatus::DestroyedAgain.was_destroyed());
assert!(!AccountStatus::Loaded.was_destroyed());
assert!(!AccountStatus::Changed.was_destroyed());
// account modified but not destroyed
assert!(AccountStatus::Changed.is_modified_and_not_destroyed());
assert!(AccountStatus::InMemoryChange.is_modified_and_not_destroyed());
assert!(!AccountStatus::Loaded.is_modified_and_not_destroyed());
assert!(!AccountStatus::LoadedEmptyEIP161.is_modified_and_not_destroyed());
assert!(!AccountStatus::LoadedNotExisting.is_modified_and_not_destroyed());
assert!(!AccountStatus::Destroyed.is_modified_and_not_destroyed());
assert!(!AccountStatus::DestroyedChanged.is_modified_and_not_destroyed());
assert!(!AccountStatus::DestroyedAgain.is_modified_and_not_destroyed());
}
}