Skip to main content

revm_database/states/
bundle_account.rs

1use super::{
2    reverts::AccountInfoRevert, AccountRevert, AccountStatus, RevertToSlot, StorageSlot,
3    StorageWithOriginalValues, TransitionAccount,
4};
5use primitives::{HashMap, StorageKey, StorageKeyMap, StorageValue};
6use state::AccountInfo;
7
8/// Account information focused on creating of database changesets
9/// and Reverts.
10///
11/// Status is needed as to know from what state we are applying the TransitionAccount.
12///
13/// Original account info is needed to know if there was a change.
14///
15/// Same thing for storage with original value.
16///
17/// On selfdestruct storage original value is ignored.
18#[derive(Clone, Debug, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20pub struct BundleAccount {
21    /// Current account information.
22    pub info: Option<AccountInfo>,
23    /// Original account information before modifications.
24    pub original_info: Option<AccountInfo>,
25    /// Contains both original and present state.
26    /// When extracting changeset we compare if original value is different from present value.
27    /// If it is different we add it to changeset.
28    ///
29    /// If Account was destroyed we ignore original value and compare present state with StorageValue::ZERO.
30    pub storage: StorageWithOriginalValues,
31    /// Account status.
32    pub status: AccountStatus,
33}
34
35impl BundleAccount {
36    /// Create new BundleAccount.
37    pub fn new(
38        original_info: Option<AccountInfo>,
39        present_info: Option<AccountInfo>,
40        storage: StorageWithOriginalValues,
41        status: AccountStatus,
42    ) -> Self {
43        Self {
44            info: present_info,
45            original_info,
46            storage,
47            status,
48        }
49    }
50
51    /// The approximate size of changes needed to store this account.
52    ///
53    /// `1 + storage_len`
54    pub fn size_hint(&self) -> usize {
55        1 + self.storage.len()
56    }
57
58    /// Return storage slot if it exists.
59    ///
60    /// In case we know that account is newly created or destroyed, return `Some(StorageValue::ZERO)`
61    pub fn storage_slot(&self, slot: StorageKey) -> Option<StorageValue> {
62        let slot = self.storage.get(&slot).map(|s| s.present_value);
63        if slot.is_some() {
64            slot
65        } else if self.status.is_storage_known() {
66            Some(StorageValue::ZERO)
67        } else {
68            None
69        }
70    }
71
72    /// Fetch account info if it exists.
73    pub fn account_info(&self) -> Option<AccountInfo> {
74        self.info.clone()
75    }
76
77    /// Was this account destroyed.
78    pub fn was_destroyed(&self) -> bool {
79        self.status.was_destroyed()
80    }
81
82    /// Return true of account info was changed.
83    pub fn is_info_changed(&self) -> bool {
84        self.info != self.original_info
85    }
86
87    /// Return true if contract was changed
88    pub fn is_contract_changed(&self) -> bool {
89        self.info.as_ref().map(|a| a.code_hash) != self.original_info.as_ref().map(|a| a.code_hash)
90    }
91
92    /// Revert account to previous state and return true if account can be removed.
93    pub fn revert(&mut self, revert: AccountRevert) -> bool {
94        self.status = revert.previous_status;
95
96        match revert.account {
97            AccountInfoRevert::DoNothing => (),
98            AccountInfoRevert::DeleteIt => {
99                self.info = None;
100                if self.original_info.is_none() {
101                    self.storage = HashMap::default();
102                    return true;
103                } else {
104                    // Set all storage to zero but preserve original values.
105                    self.storage.iter_mut().for_each(|(_, v)| {
106                        v.present_value = StorageValue::ZERO;
107                    });
108                    return false;
109                }
110            }
111            AccountInfoRevert::RevertTo(info) => self.info = Some(info),
112        };
113        // Revert storage
114        for (key, slot) in revert.storage {
115            match slot {
116                RevertToSlot::Some(value) => {
117                    // Don't overwrite original values if present
118                    // if storage is not present set original value as current value.
119                    self.storage
120                        .entry(key)
121                        .or_insert_with(|| StorageSlot::new(value))
122                        .present_value = value;
123                }
124                RevertToSlot::Destroyed => {
125                    // If it was destroyed this means that storage was created and we need to remove it.
126                    self.storage.remove(&key);
127                }
128            }
129        }
130        false
131    }
132
133    /// Update to new state and generate AccountRevert that if applied to new state will
134    /// revert it to previous state.
135    ///
136    /// If no revert is present, update is noop.
137    pub fn update_and_create_revert(
138        &mut self,
139        transition: TransitionAccount,
140    ) -> Option<AccountRevert> {
141        let updated_info = transition.info;
142        let updated_storage = transition.storage;
143        let updated_status = transition.status;
144
145        // The helper that extends this storage but preserves original value.
146        let extend_storage =
147            |this_storage: &mut StorageWithOriginalValues,
148             storage_update: StorageWithOriginalValues| {
149                this_storage.reserve(storage_update.len());
150                for (key, value) in storage_update {
151                    this_storage.entry(key).or_insert(value).present_value = value.present_value;
152                }
153            };
154
155        let previous_storage_from_update =
156            |updated_storage: &StorageWithOriginalValues| -> StorageKeyMap<RevertToSlot> {
157                updated_storage
158                    .iter()
159                    .filter(|s| s.1.is_changed())
160                    .map(|(key, value)| {
161                        (*key, RevertToSlot::Some(value.previous_or_original_value))
162                    })
163                    .collect()
164            };
165
166        // Needed for some reverts.
167        let info_revert = if self.info != updated_info {
168            AccountInfoRevert::RevertTo(self.info.clone().unwrap_or_default())
169        } else {
170            AccountInfoRevert::DoNothing
171        };
172
173        let account_revert = match updated_status {
174            AccountStatus::Changed => {
175                let previous_storage = previous_storage_from_update(&updated_storage);
176                match self.status {
177                    AccountStatus::Changed | AccountStatus::Loaded => {
178                        // Extend the storage. original values is not used inside bundle.
179                        extend_storage(&mut self.storage, updated_storage);
180                    }
181                    AccountStatus::LoadedEmptyEIP161 => {
182                        // Do nothing.
183                        // Only change that can happen from LoadedEmpty to Changed is if balance
184                        // is send to account. So we are only checking account change here.
185                    }
186                    _ => unreachable!("Invalid state transfer to Changed from {self:?}"),
187                };
188                let previous_status = self.status;
189                self.status = AccountStatus::Changed;
190                self.info = updated_info;
191                Some(AccountRevert {
192                    account: info_revert,
193                    storage: previous_storage,
194                    previous_status,
195                    wipe_storage: false,
196                })
197            }
198            AccountStatus::InMemoryChange => {
199                let previous_storage = previous_storage_from_update(&updated_storage);
200                let in_memory_info_revert = match self.status {
201                    AccountStatus::Loaded | AccountStatus::InMemoryChange => {
202                        // From loaded (Or LoadedEmpty) to InMemoryChange can happen if there is balance change
203                        // or new created account but Loaded didn't have contract.
204                        extend_storage(&mut self.storage, updated_storage);
205                        info_revert
206                    }
207                    AccountStatus::LoadedEmptyEIP161 => {
208                        self.storage = updated_storage;
209                        info_revert
210                    }
211                    AccountStatus::LoadedNotExisting => {
212                        self.storage = updated_storage;
213                        AccountInfoRevert::DeleteIt
214                    }
215                    _ => unreachable!("Invalid change to InMemoryChange from {self:?}"),
216                };
217                let previous_status = self.status;
218                self.status = AccountStatus::InMemoryChange;
219                self.info = updated_info;
220                Some(AccountRevert {
221                    account: in_memory_info_revert,
222                    storage: previous_storage,
223                    previous_status,
224                    wipe_storage: false,
225                })
226            }
227            AccountStatus::Loaded
228            | AccountStatus::LoadedNotExisting
229            | AccountStatus::LoadedEmptyEIP161 => {
230                // No changeset, maybe just update data
231                // Do nothing for now.
232                None
233            }
234            AccountStatus::Destroyed => {
235                // Clear this storage and move it to the Revert.
236                let this_storage = core::mem::take(&mut self.storage);
237                let ret = match self.status {
238                    AccountStatus::InMemoryChange | AccountStatus::Changed | AccountStatus::Loaded | AccountStatus::LoadedEmptyEIP161 => {
239                        Some(AccountRevert::new_selfdestructed(self.status, info_revert, this_storage))
240                    }
241                    AccountStatus::LoadedNotExisting => {
242                        // Do nothing as we have LoadedNotExisting -> Destroyed (It is noop)
243                        None
244                    }
245                    _ => unreachable!("Invalid transition to Destroyed account from: {self:?} to {updated_info:?} {updated_status:?}"),
246                };
247
248                if ret.is_some() {
249                    self.status = AccountStatus::Destroyed;
250                    self.info = None;
251                }
252
253                // Set present to destroyed.
254                ret
255            }
256            AccountStatus::DestroyedChanged => {
257                // Previous block created account or changed.
258                // (It was destroyed on previous block or one before).
259
260                // Check common pre destroy paths.
261                // If common path is there it will drain the storage.
262                if let Some(revert_state) = AccountRevert::new_selfdestructed_from_bundle(
263                    info_revert.clone(),
264                    self,
265                    &updated_storage,
266                ) {
267                    // Set to destroyed and revert state.
268                    self.status = AccountStatus::DestroyedChanged;
269                    self.info = updated_info;
270                    self.storage = updated_storage;
271
272                    Some(revert_state)
273                } else {
274                    let ret = match self.status {
275                        AccountStatus::Destroyed | AccountStatus::LoadedNotExisting => {
276                            // From destroyed state new account is made
277                            Some(AccountRevert {
278                                account: AccountInfoRevert::DeleteIt,
279                                storage: previous_storage_from_update(&updated_storage),
280                                previous_status: self.status,
281                                wipe_storage: false,
282                            })
283                        }
284                        AccountStatus::DestroyedChanged => {
285                            // Account was destroyed in this transition. So we should clear present storage
286                            // and insert it inside revert.
287
288                            let previous_storage = if transition.storage_was_destroyed {
289                                let mut storage = core::mem::take(&mut self.storage)
290                                    .into_iter()
291                                    .map(|t| (t.0, RevertToSlot::Some(t.1.present_value)))
292                                    .collect::<StorageKeyMap<_>>();
293                                for key in updated_storage.keys() {
294                                    // As it is not existing inside Destroyed storage this means
295                                    // that previous values must be zero
296                                    storage.entry(*key).or_insert(RevertToSlot::Destroyed);
297                                }
298                                storage
299                            } else {
300                                previous_storage_from_update(&updated_storage)
301                            };
302
303                            Some(AccountRevert {
304                                account: info_revert,
305                                storage: previous_storage,
306                                previous_status: AccountStatus::DestroyedChanged,
307                                wipe_storage: false,
308                            })
309                        }
310                        AccountStatus::DestroyedAgain => {
311                            Some(AccountRevert::new_selfdestructed_again(
312                                // Destroyed again will set empty account.
313                                AccountStatus::DestroyedAgain,
314                                AccountInfoRevert::DeleteIt,
315                                HashMap::default(),
316                                updated_storage.clone(),
317                            ))
318                        }
319                        _ => unreachable!("Invalid state transfer to DestroyedNew from {self:?}"),
320                    };
321                    self.status = AccountStatus::DestroyedChanged;
322                    self.info = updated_info;
323                    // Extends current storage.
324                    extend_storage(&mut self.storage, updated_storage);
325
326                    ret
327                }
328            }
329            AccountStatus::DestroyedAgain => {
330                // Previous block created account
331                // (It was destroyed on previous block or one before).
332
333                // Check common pre destroy paths.
334                // This will drain the storage if it is common transition.
335                let ret = if let Some(revert_state) = AccountRevert::new_selfdestructed_from_bundle(
336                    info_revert,
337                    self,
338                    &HashMap::default(),
339                ) {
340                    Some(revert_state)
341                } else {
342                    match self.status {
343                        AccountStatus::Destroyed
344                        | AccountStatus::DestroyedAgain
345                        | AccountStatus::LoadedNotExisting => {
346                            // From destroyed to destroyed again is noop
347                            //
348                            // From DestroyedAgain to DestroyedAgain is noop
349                            //
350                            // From LoadedNotExisting to DestroyedAgain is noop
351                            // as account is destroyed again
352                            None
353                        }
354                        AccountStatus::DestroyedChanged => {
355                            // From destroyed changed to destroyed again.
356                            Some(AccountRevert::new_selfdestructed_again(
357                                // Destroyed again will set empty account.
358                                AccountStatus::DestroyedChanged,
359                                AccountInfoRevert::RevertTo(self.info.clone().unwrap_or_default()),
360                                core::mem::take(&mut self.storage),
361                                HashMap::default(),
362                            ))
363                        }
364                        _ => unreachable!("Invalid state to DestroyedAgain from {self:?}"),
365                    }
366                };
367                // Set to destroyed and revert state.
368                self.status = AccountStatus::DestroyedAgain;
369                self.info = None;
370                self.storage.clear();
371                ret
372            }
373        };
374
375        account_revert.and_then(|acc| if acc.is_empty() { None } else { Some(acc) })
376    }
377}