revm/db/states/
bundle_account.rs

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