Skip to content

Commit

Permalink
chore(state): move account status transitions to AccountStatus (blu…
Browse files Browse the repository at this point in the history
…ealloy#844)

* chore(state): move account status transitions to `AccountStatus`

* `is_destroyed` -> `was_destroyed`
  • Loading branch information
rkrasiuk authored Oct 27, 2023
1 parent 37ee693 commit 8779d23
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 185 deletions.
6 changes: 6 additions & 0 deletions crates/primitives/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,16 @@ impl AccountInfo {
self.balance == U256::ZERO && self.nonce == 0 && code_empty
}

/// Returns `true` if the account is not empty.
pub fn exists(&self) -> bool {
!self.is_empty()
}

/// Returns `true` if account has no nonce and code.
pub fn has_no_code_and_nonce(&self) -> bool {
self.is_empty_code_hash() && self.nonce == 0
}

/// Return bytecode hash associated with this account.
/// If account does not have code, it return's `KECCAK_EMPTY` hash.
pub fn code_hash(&self) -> B256 {
Expand Down
217 changes: 171 additions & 46 deletions crates/revm/src/db/states/account_status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,8 @@ pub enum AccountStatus {
}

impl AccountStatus {
/// 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,
};
}
/// Account is not modified and just loaded from database.
pub fn not_modified(&self) -> bool {
pub fn is_not_modified(&self) -> bool {
matches!(
self,
AccountStatus::LoadedNotExisting
Expand All @@ -56,7 +37,7 @@ impl AccountStatus {
}

/// This means storage is known, it can be newly created or storage got destroyed.
pub fn storage_known(&self) -> bool {
pub fn is_storage_known(&self) -> bool {
matches!(
self,
AccountStatus::LoadedNotExisting
Expand All @@ -70,9 +51,153 @@ impl AccountStatus {
/// Account is modified but not destroyed.
/// This means that some storage values can be found in both
/// memory and database.
pub fn modified_but_not_destroyed(&self) -> bool {
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 {
// 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)]
Expand All @@ -83,24 +208,24 @@ mod test {
#[test]
fn test_account_status() {
// account not modified
assert!(AccountStatus::Loaded.not_modified());
assert!(AccountStatus::LoadedEmptyEIP161.not_modified());
assert!(AccountStatus::LoadedNotExisting.not_modified());
assert!(!AccountStatus::Changed.not_modified());
assert!(!AccountStatus::InMemoryChange.not_modified());
assert!(!AccountStatus::Destroyed.not_modified());
assert!(!AccountStatus::DestroyedChanged.not_modified());
assert!(!AccountStatus::DestroyedAgain.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.storage_known());
assert!(AccountStatus::LoadedNotExisting.storage_known());
assert!(AccountStatus::InMemoryChange.storage_known());
assert!(AccountStatus::Destroyed.storage_known());
assert!(AccountStatus::DestroyedChanged.storage_known());
assert!(AccountStatus::DestroyedAgain.storage_known());
assert!(!AccountStatus::Loaded.storage_known());
assert!(!AccountStatus::Changed.storage_known());
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());
Expand All @@ -113,13 +238,13 @@ mod test {
assert!(!AccountStatus::Changed.was_destroyed());

// account modified but not destroyed
assert!(AccountStatus::Changed.modified_but_not_destroyed());
assert!(AccountStatus::InMemoryChange.modified_but_not_destroyed());
assert!(!AccountStatus::Loaded.modified_but_not_destroyed());
assert!(!AccountStatus::LoadedEmptyEIP161.modified_but_not_destroyed());
assert!(!AccountStatus::LoadedNotExisting.modified_but_not_destroyed());
assert!(!AccountStatus::Destroyed.modified_but_not_destroyed());
assert!(!AccountStatus::DestroyedChanged.modified_but_not_destroyed());
assert!(!AccountStatus::DestroyedAgain.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());
}
}
2 changes: 1 addition & 1 deletion crates/revm/src/db/states/bundle_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ impl BundleAccount {
let slot = self.storage.get(&slot).map(|s| s.present_value);
if slot.is_some() {
slot
} else if self.status.storage_known() {
} else if self.status.is_storage_known() {
Some(U256::ZERO)
} else {
None
Expand Down
Loading

0 comments on commit 8779d23

Please sign in to comment.