Skip to main content

revm_database/states/
cache_account.rs

1use super::{
2    plain_account::PlainStorage, AccountStatus, BundleAccount, PlainAccount,
3    StorageWithOriginalValues, TransitionAccount,
4};
5use primitives::{HashMap, StorageKey, StorageValue, U256};
6use state::AccountInfo;
7
8/// Cache account contains plain state that gets updated
9/// at every transaction when evm output is applied to CacheState.
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct CacheAccount {
12    /// Account information and storage, if account exists.
13    pub account: Option<PlainAccount>,
14    /// Account status flags.
15    pub status: AccountStatus,
16}
17
18impl From<BundleAccount> for CacheAccount {
19    fn from(account: BundleAccount) -> Self {
20        CacheAccount::from(&account)
21    }
22}
23
24impl From<&BundleAccount> for CacheAccount {
25    fn from(account: &BundleAccount) -> Self {
26        let storage = account
27            .storage
28            .iter()
29            .map(|(k, v)| (*k, v.present_value))
30            .collect();
31        let plain_account = account
32            .account_info()
33            .map(|info| PlainAccount { info, storage });
34        Self {
35            account: plain_account,
36            status: account.status,
37        }
38    }
39}
40
41impl CacheAccount {
42    /// Creates new account that is loaded from database.
43    pub fn new_loaded(info: AccountInfo, storage: PlainStorage) -> Self {
44        Self {
45            account: Some(PlainAccount { info, storage }),
46            status: AccountStatus::Loaded,
47        }
48    }
49
50    /// Creates new account that is loaded empty from database.
51    pub fn new_loaded_empty_eip161(storage: PlainStorage) -> Self {
52        Self {
53            account: Some(PlainAccount::new_empty_with_storage(storage)),
54            status: AccountStatus::LoadedEmptyEIP161,
55        }
56    }
57
58    /// Loaded not existing account.
59    pub fn new_loaded_not_existing() -> Self {
60        Self {
61            account: None,
62            status: AccountStatus::LoadedNotExisting,
63        }
64    }
65
66    /// Creates new account that is newly created.
67    pub fn new_newly_created(info: AccountInfo, storage: PlainStorage) -> Self {
68        Self {
69            account: Some(PlainAccount { info, storage }),
70            status: AccountStatus::InMemoryChange,
71        }
72    }
73
74    /// Creates account that is destroyed.
75    pub fn new_destroyed() -> Self {
76        Self {
77            account: None,
78            status: AccountStatus::Destroyed,
79        }
80    }
81
82    /// Creates changed account.
83    pub fn new_changed(info: AccountInfo, storage: PlainStorage) -> Self {
84        Self {
85            account: Some(PlainAccount { info, storage }),
86            status: AccountStatus::Changed,
87        }
88    }
89
90    /// Returns true if account is some.
91    pub fn is_some(&self) -> bool {
92        matches!(
93            self.status,
94            AccountStatus::Changed
95                | AccountStatus::InMemoryChange
96                | AccountStatus::DestroyedChanged
97                | AccountStatus::Loaded
98                | AccountStatus::LoadedEmptyEIP161
99        )
100    }
101
102    /// Returns storage slot if it exists.
103    pub fn storage_slot(&self, slot: StorageKey) -> Option<StorageValue> {
104        self.account
105            .as_ref()
106            .and_then(|a| a.storage.get(&slot).cloned())
107    }
108
109    /// Fetches account info if it exists.
110    pub fn account_info(&self) -> Option<AccountInfo> {
111        self.account.as_ref().map(|a| a.info.clone())
112    }
113
114    /// Dissolves account into components.
115    pub fn into_components(self) -> (Option<(AccountInfo, PlainStorage)>, AccountStatus) {
116        (self.account.map(|a| a.into_components()), self.status)
117    }
118
119    /// Touch empty account, related to EIP-161 state clear.
120    ///
121    /// This account returns the Transition that is used to create the BundleState.
122    pub fn touch_empty_eip161(&mut self) -> Option<TransitionAccount> {
123        let previous_status = self.status;
124
125        // Set account to None.
126        let previous_info = self.account.take().map(|acc| acc.info);
127
128        // Set account state to Destroyed as we need to clear the storage if it exist.
129        self.status = self.status.on_touched_empty_post_eip161();
130
131        if matches!(
132            previous_status,
133            AccountStatus::LoadedNotExisting
134                | AccountStatus::Destroyed
135                | AccountStatus::DestroyedAgain
136        ) {
137            None
138        } else {
139            Some(TransitionAccount {
140                info: None,
141                status: self.status,
142                previous_info,
143                previous_status,
144                storage: HashMap::default(),
145                storage_was_destroyed: true,
146            })
147        }
148    }
149
150    /// Consumes self and make account as destroyed.
151    ///
152    /// Sets account as None and set status to Destroyer or DestroyedAgain.
153    pub fn selfdestruct(&mut self) -> Option<TransitionAccount> {
154        // Account should be None after selfdestruct so we can take it.
155        let previous_info = self.account.take().map(|a| a.info);
156        let previous_status = self.status;
157
158        self.status = self.status.on_selfdestructed();
159
160        if previous_status == AccountStatus::LoadedNotExisting {
161            None
162        } else {
163            Some(TransitionAccount {
164                info: None,
165                status: self.status,
166                previous_info,
167                previous_status,
168                storage: HashMap::default(),
169                storage_was_destroyed: true,
170            })
171        }
172    }
173
174    /// Newly created account.
175    pub fn newly_created(
176        &mut self,
177        new_info: AccountInfo,
178        new_storage: StorageWithOriginalValues,
179    ) -> TransitionAccount {
180        let previous_status = self.status;
181        let previous_info = self.account.take().map(|a| a.info);
182
183        let new_bundle_storage = new_storage
184            .iter()
185            .map(|(k, s)| (*k, s.present_value))
186            .collect();
187
188        self.status = self.status.on_created();
189        let transition_account = TransitionAccount {
190            info: Some(new_info.clone()),
191            status: self.status,
192            previous_status,
193            previous_info,
194            storage: new_storage,
195            storage_was_destroyed: false,
196        };
197        self.account = Some(PlainAccount {
198            info: new_info,
199            storage: new_bundle_storage,
200        });
201        transition_account
202    }
203
204    /// Increment balance by `balance` amount. Assume that balance will not
205    /// overflow or be zero.
206    ///
207    /// Note: Only if balance is zero we would return None as no transition would be made.
208    pub fn increment_balance(&mut self, balance: u128) -> Option<TransitionAccount> {
209        if balance == 0 {
210            return None;
211        }
212        let (_, transition) = self.account_info_change(|info| {
213            info.balance = info.balance.saturating_add(U256::from(balance));
214        });
215        Some(transition)
216    }
217
218    fn account_info_change<T, F: FnOnce(&mut AccountInfo) -> T>(
219        &mut self,
220        change: F,
221    ) -> (T, TransitionAccount) {
222        let previous_status = self.status;
223        let previous_info = self.account_info();
224        let mut account = self.account.take().unwrap_or_default();
225        let output = change(&mut account.info);
226        self.account = Some(account);
227
228        let had_no_nonce_and_code = previous_info
229            .as_ref()
230            .map(AccountInfo::has_no_code_and_nonce)
231            .unwrap_or_default();
232        self.status = self.status.on_changed(had_no_nonce_and_code);
233
234        (
235            output,
236            TransitionAccount {
237                info: self.account_info(),
238                status: self.status,
239                previous_info,
240                previous_status,
241                storage: HashMap::default(),
242                storage_was_destroyed: false,
243            },
244        )
245    }
246
247    /// Drain balance from account and return drained amount and transition.
248    ///
249    /// Used for DAO hardfork transition.
250    pub fn drain_balance(&mut self) -> (u128, TransitionAccount) {
251        self.account_info_change(|info| {
252            let output = info.balance;
253            info.balance = U256::ZERO;
254            output.try_into().unwrap()
255        })
256    }
257
258    /// Updates the account with new information and storage changes.
259    ///
260    /// Merges the provided storage values with the existing storage and updates the account status.
261    pub fn change(
262        &mut self,
263        new: AccountInfo,
264        storage: StorageWithOriginalValues,
265    ) -> TransitionAccount {
266        let previous_status = self.status;
267        let (previous_info, mut this_storage) = if let Some(account) = self.account.take() {
268            (Some(account.info), account.storage)
269        } else {
270            (None, Default::default())
271        };
272
273        this_storage.extend(storage.iter().map(|(k, s)| (*k, s.present_value)));
274        let changed_account = PlainAccount {
275            info: new,
276            storage: this_storage,
277        };
278
279        let had_no_nonce_and_code = previous_info
280            .as_ref()
281            .map(AccountInfo::has_no_code_and_nonce)
282            .unwrap_or_default();
283        self.status = self.status.on_changed(had_no_nonce_and_code);
284        self.account = Some(changed_account);
285
286        TransitionAccount {
287            info: self.account.as_ref().map(|a| a.info.clone()),
288            status: self.status,
289            previous_info,
290            previous_status,
291            storage,
292            storage_was_destroyed: false,
293        }
294    }
295}