Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(state): move account status transitions to AccountStatus #844

Merged
merged 2 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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