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());
    }
}