revm_database/states/
bundle_account.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
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
use super::{
    reverts::AccountInfoRevert, AccountRevert, AccountStatus, RevertToSlot, StorageSlot,
    StorageWithOriginalValues, TransitionAccount,
};
use primitives::{HashMap, U256};
use state::AccountInfo;

/// Account information focused on creating of database changesets
/// and Reverts.
///
/// Status is needed as to know from what state we are applying the TransitionAccount.
///
/// Original account info is needed to know if there was a change.
/// Same thing for storage with original value.
///
/// On selfdestruct storage original value is ignored.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BundleAccount {
    pub info: Option<AccountInfo>,
    pub original_info: Option<AccountInfo>,
    /// Contains both original and present state.
    /// When extracting changeset we compare if original value is different from present value.
    /// If it is different we add it to changeset.
    ///
    /// If Account was destroyed we ignore original value and compare present state with U256::ZERO.
    pub storage: StorageWithOriginalValues,
    /// Account status.
    pub status: AccountStatus,
}

impl BundleAccount {
    /// Create new BundleAccount.
    pub fn new(
        original_info: Option<AccountInfo>,
        present_info: Option<AccountInfo>,
        storage: StorageWithOriginalValues,
        status: AccountStatus,
    ) -> Self {
        Self {
            info: present_info,
            original_info,
            storage,
            status,
        }
    }

    /// The approximate size of changes needed to store this account.
    /// `1 + storage_len`
    pub fn size_hint(&self) -> usize {
        1 + self.storage.len()
    }

    /// Return storage slot if it exists.
    ///
    /// In case we know that account is newly created or destroyed, return `Some(U256::ZERO)`
    pub fn storage_slot(&self, slot: U256) -> Option<U256> {
        let slot = self.storage.get(&slot).map(|s| s.present_value);
        if slot.is_some() {
            slot
        } else if self.status.is_storage_known() {
            Some(U256::ZERO)
        } else {
            None
        }
    }

    /// Fetch account info if it exist.
    pub fn account_info(&self) -> Option<AccountInfo> {
        self.info.clone()
    }

    /// Was this account destroyed.
    pub fn was_destroyed(&self) -> bool {
        self.status.was_destroyed()
    }

    /// Return true of account info was changed.
    pub fn is_info_changed(&self) -> bool {
        self.info != self.original_info
    }

    /// Return true if contract was changed
    pub fn is_contract_changed(&self) -> bool {
        self.info.as_ref().map(|a| a.code_hash) != self.original_info.as_ref().map(|a| a.code_hash)
    }

    /// Revert account to previous state and return true if account can be removed.
    pub fn revert(&mut self, revert: AccountRevert) -> bool {
        self.status = revert.previous_status;

        match revert.account {
            AccountInfoRevert::DoNothing => (),
            AccountInfoRevert::DeleteIt => {
                self.info = None;
                if self.original_info.is_none() {
                    self.storage = HashMap::default();
                    return true;
                } else {
                    // set all storage to zero but preserve original values.
                    self.storage.iter_mut().for_each(|(_, v)| {
                        v.present_value = U256::ZERO;
                    });
                    return false;
                }
            }
            AccountInfoRevert::RevertTo(info) => self.info = Some(info),
        };
        // revert storage
        for (key, slot) in revert.storage {
            match slot {
                RevertToSlot::Some(value) => {
                    // Don't overwrite original values if present
                    // if storage is not present set original value as current value.
                    self.storage
                        .entry(key)
                        .or_insert(StorageSlot::new(value))
                        .present_value = value;
                }
                RevertToSlot::Destroyed => {
                    // if it was destroyed this means that storage was created and we need to remove it.
                    self.storage.remove(&key);
                }
            }
        }
        false
    }

    /// Update to new state and generate AccountRevert that if applied to new state will
    /// revert it to previous state. If no revert is present, update is noop.
    pub fn update_and_create_revert(
        &mut self,
        transition: TransitionAccount,
    ) -> Option<AccountRevert> {
        let updated_info = transition.info;
        let updated_storage = transition.storage;
        let updated_status = transition.status;

        // the helper that extends this storage but preserves original value.
        let extend_storage =
            |this_storage: &mut StorageWithOriginalValues,
             storage_update: StorageWithOriginalValues| {
                for (key, value) in storage_update {
                    this_storage.entry(key).or_insert(value).present_value = value.present_value;
                }
            };

        let previous_storage_from_update =
            |updated_storage: &StorageWithOriginalValues| -> HashMap<U256, RevertToSlot> {
                updated_storage
                    .iter()
                    .filter(|s| s.1.is_changed())
                    .map(|(key, value)| {
                        (*key, RevertToSlot::Some(value.previous_or_original_value))
                    })
                    .collect()
            };

        // Needed for some reverts.
        let info_revert = if self.info != updated_info {
            AccountInfoRevert::RevertTo(self.info.clone().unwrap_or_default())
        } else {
            AccountInfoRevert::DoNothing
        };

        let account_revert = match updated_status {
            AccountStatus::Changed => {
                let previous_storage = previous_storage_from_update(&updated_storage);
                match self.status {
                    AccountStatus::Changed | AccountStatus::Loaded => {
                        // extend the storage. original values is not used inside bundle.
                        extend_storage(&mut self.storage, updated_storage);
                    }
                    AccountStatus::LoadedEmptyEIP161 => {
                        // Do nothing.
                        // Only change that can happen from LoadedEmpty to Changed is if balance
                        // is send to account. So we are only checking account change here.
                    }
                    _ => unreachable!("Invalid state transfer to Changed from {self:?}"),
                };
                let previous_status = self.status;
                self.status = AccountStatus::Changed;
                self.info = updated_info;
                Some(AccountRevert {
                    account: info_revert,
                    storage: previous_storage,
                    previous_status,
                    wipe_storage: false,
                })
            }
            AccountStatus::InMemoryChange => {
                let previous_storage = previous_storage_from_update(&updated_storage);
                let in_memory_info_revert = match self.status {
                    AccountStatus::Loaded | AccountStatus::InMemoryChange => {
                        // from loaded (Or LoadedEmpty) to InMemoryChange can happen if there is balance change
                        // or new created account but Loaded didn't have contract.
                        extend_storage(&mut self.storage, updated_storage);
                        info_revert
                    }
                    AccountStatus::LoadedEmptyEIP161 => {
                        self.storage = updated_storage;
                        info_revert
                    }
                    AccountStatus::LoadedNotExisting => {
                        self.storage = updated_storage;
                        AccountInfoRevert::DeleteIt
                    }
                    _ => unreachable!("Invalid change to InMemoryChange from {self:?}"),
                };
                let previous_status = self.status;
                self.status = AccountStatus::InMemoryChange;
                self.info = updated_info;
                Some(AccountRevert {
                    account: in_memory_info_revert,
                    storage: previous_storage,
                    previous_status,
                    wipe_storage: false,
                })
            }
            AccountStatus::Loaded
            | AccountStatus::LoadedNotExisting
            | AccountStatus::LoadedEmptyEIP161 => {
                // No changeset, maybe just update data
                // Do nothing for now.
                None
            }
            AccountStatus::Destroyed => {
                // clear this storage and move it to the Revert.
                let this_storage = self.storage.drain().collect();
                let ret = match self.status {
                    AccountStatus::InMemoryChange | AccountStatus::Changed | AccountStatus::Loaded | AccountStatus::LoadedEmptyEIP161 => {
                        Some(AccountRevert::new_selfdestructed(self.status, info_revert, this_storage))
                    }
                    AccountStatus::LoadedNotExisting => {
                        // Do nothing as we have LoadedNotExisting -> Destroyed (It is noop)
                        None
                    }
                    _ => unreachable!("Invalid transition to Destroyed account from: {self:?} to {updated_info:?} {updated_status:?}"),
                };

                if ret.is_some() {
                    self.status = AccountStatus::Destroyed;
                    self.info = None;
                }

                // set present to destroyed.
                ret
            }
            AccountStatus::DestroyedChanged => {
                // Previous block created account or changed.
                // (It was destroyed on previous block or one before).

                // check common pre destroy paths.
                // If common path is there it will drain the storage.
                if let Some(revert_state) = AccountRevert::new_selfdestructed_from_bundle(
                    info_revert.clone(),
                    self,
                    &updated_storage,
                ) {
                    // set to destroyed and revert state.
                    self.status = AccountStatus::DestroyedChanged;
                    self.info = updated_info;
                    self.storage = updated_storage;

                    Some(revert_state)
                } else {
                    let ret = match self.status {
                        AccountStatus::Destroyed | AccountStatus::LoadedNotExisting => {
                            // from destroyed state new account is made
                            Some(AccountRevert {
                                account: AccountInfoRevert::DeleteIt,
                                storage: previous_storage_from_update(&updated_storage),
                                previous_status: self.status,
                                wipe_storage: false,
                            })
                        }
                        AccountStatus::DestroyedChanged => {
                            // Account was destroyed in this transition. So we should clear present storage
                            // and insert it inside revert.

                            let previous_storage = if transition.storage_was_destroyed {
                                let mut storage = core::mem::take(&mut self.storage)
                                    .into_iter()
                                    .map(|t| (t.0, RevertToSlot::Some(t.1.present_value)))
                                    .collect::<HashMap<_, _>>();
                                for key in updated_storage.keys() {
                                    // as it is not existing inside Destroyed storage this means
                                    // that previous values must be zero
                                    storage.entry(*key).or_insert(RevertToSlot::Destroyed);
                                }
                                storage
                            } else {
                                previous_storage_from_update(&updated_storage)
                            };

                            Some(AccountRevert {
                                account: info_revert,
                                storage: previous_storage,
                                previous_status: AccountStatus::DestroyedChanged,
                                wipe_storage: false,
                            })
                        }
                        AccountStatus::DestroyedAgain => {
                            Some(AccountRevert::new_selfdestructed_again(
                                // destroyed again will set empty account.
                                AccountStatus::DestroyedAgain,
                                AccountInfoRevert::DeleteIt,
                                HashMap::default(),
                                updated_storage.clone(),
                            ))
                        }
                        _ => unreachable!("Invalid state transfer to DestroyedNew from {self:?}"),
                    };
                    self.status = AccountStatus::DestroyedChanged;
                    self.info = updated_info;
                    // extends current storage.
                    extend_storage(&mut self.storage, updated_storage);

                    ret
                }
            }
            AccountStatus::DestroyedAgain => {
                // Previous block created account
                // (It was destroyed on previous block or one before).

                // check common pre destroy paths.
                // This will drain the storage if it is common transition.
                let ret = if let Some(revert_state) = AccountRevert::new_selfdestructed_from_bundle(
                    info_revert,
                    self,
                    &HashMap::default(),
                ) {
                    Some(revert_state)
                } else {
                    match self.status {
                        AccountStatus::Destroyed
                        | AccountStatus::DestroyedAgain
                        | AccountStatus::LoadedNotExisting => {
                            // From destroyed to destroyed again. is noop
                            //
                            // DestroyedAgain to DestroyedAgain is noop
                            //
                            // From LoadedNotExisting to DestroyedAgain
                            // is noop as account is destroyed again
                            None
                        }
                        AccountStatus::DestroyedChanged => {
                            // From destroyed changed to destroyed again.
                            Some(AccountRevert::new_selfdestructed_again(
                                // destroyed again will set empty account.
                                AccountStatus::DestroyedChanged,
                                AccountInfoRevert::RevertTo(self.info.clone().unwrap_or_default()),
                                self.storage.drain().collect(),
                                HashMap::default(),
                            ))
                        }
                        _ => unreachable!("Invalid state to DestroyedAgain from {self:?}"),
                    }
                };
                // set to destroyed and revert state.
                self.status = AccountStatus::DestroyedAgain;
                self.info = None;
                self.storage.clear();
                ret
            }
        };

        account_revert.and_then(|acc| if acc.is_empty() { None } else { Some(acc) })
    }
}