revm_context_interface/journaled_state/
account.rs

1//! This module contains [`JournaledAccount`] struct a wrapper around account and journal entries that
2//! allow updates to the account and journal entries.
3//!
4//! Useful to encapsulate account and journal entries together. So when account gets changed, we can add a journal entry for it.
5
6use crate::{
7    context::{SStoreResult, StateLoad},
8    journaled_state::{entry::JournalEntry, JournalLoadErasedError, JournalLoadError},
9    ErasedError,
10};
11
12use super::entry::JournalEntryTr;
13use auto_impl::auto_impl;
14use database_interface::Database;
15use primitives::{
16    hash_map::Entry, Address, HashMap, HashSet, StorageKey, StorageValue, B256, KECCAK_EMPTY, U256,
17};
18use state::{Account, Bytecode, EvmStorageSlot};
19use std::vec::Vec;
20
21/// Trait that contains database and journal of all changes that were made to the account.
22#[auto_impl(&mut, Box)]
23pub trait JournaledAccountTr {
24    /// Returns the account.
25    fn account(&self) -> &Account;
26
27    /// Sloads the storage slot and returns its mutable reference
28    fn sload(
29        &mut self,
30        key: StorageKey,
31        skip_cold_load: bool,
32    ) -> Result<StateLoad<&mut EvmStorageSlot>, JournalLoadErasedError>;
33
34    /// Loads the storage slot and stores the new value
35    fn sstore(
36        &mut self,
37        key: StorageKey,
38        new: StorageValue,
39        skip_cold_load: bool,
40    ) -> Result<StateLoad<SStoreResult>, JournalLoadErasedError>;
41
42    /// Loads the code of the account. and returns it as reference.
43    fn load_code(&mut self) -> Result<&Bytecode, JournalLoadErasedError>;
44
45    /// Returns the balance of the account.
46    fn balance(&self) -> &U256;
47
48    /// Returns the nonce of the account.
49    fn nonce(&self) -> u64;
50
51    /// Returns the code hash of the account.
52    fn code_hash(&self) -> &B256;
53
54    /// Returns the code of the account.
55    fn code(&self) -> Option<&Bytecode>;
56
57    /// Touches the account.
58    fn touch(&mut self);
59
60    /// Marks the account as cold without making a journal entry.
61    ///
62    /// Changing account without journal entry can be a footgun as reverting of the state change
63    /// would not happen without entry. It is the reason why this function has an `unsafe` prefix.
64    ///
65    /// If account is in access list, it would still be marked as warm if account get accessed again.
66    fn unsafe_mark_cold(&mut self);
67
68    /// Sets the balance of the account.
69    ///
70    /// If balance is the same, we don't add a journal entry.
71    ///
72    /// Touches the account in all cases.
73    fn set_balance(&mut self, balance: U256);
74
75    /// Increments the balance of the account.
76    ///
77    /// Touches the account in all cases.
78    fn incr_balance(&mut self, balance: U256) -> bool;
79
80    /// Decrements the balance of the account.
81    ///
82    /// Touches the account in all cases.
83    fn decr_balance(&mut self, balance: U256) -> bool;
84
85    /// Bumps the nonce of the account.
86    ///
87    /// Touches the account in all cases.
88    ///
89    /// Returns true if nonce was bumped, false if nonce is at the max value.
90    fn bump_nonce(&mut self) -> bool;
91
92    /// Set the nonce of the account and create a journal entry.
93    ///
94    /// Touches the account in all cases.
95    fn set_nonce(&mut self, nonce: u64);
96
97    /// Set the nonce of the account without creating a journal entry.
98    ///
99    /// Changing account without journal entry can be a footgun as reverting of the state change
100    /// would not happen without entry. It is the reason why this function has an `unsafe` prefix.
101    fn unsafe_set_nonce(&mut self, nonce: u64);
102
103    /// Sets the code of the account.
104    ///
105    /// Touches the account in all cases.
106    fn set_code(&mut self, code_hash: B256, code: Bytecode);
107
108    /// Sets the code of the account. Calculates hash of the code.
109    ///
110    /// Touches the account in all cases.
111    fn set_code_and_hash_slow(&mut self, code: Bytecode);
112
113    /// Delegates the account to another address (EIP-7702).
114    ///
115    /// This touches the account, sets the code to the delegation designation,
116    /// and bumps the nonce.
117    fn delegate(&mut self, address: Address);
118}
119
120/// Journaled account contains both mutable account and journal entries.
121///
122/// Useful to encapsulate account and journal entries together. So when account gets changed, we can add a journal entry for it.
123#[derive(Debug, PartialEq, Eq)]
124pub struct JournaledAccount<'a, DB, ENTRY: JournalEntryTr = JournalEntry> {
125    /// Address of the account.
126    address: Address,
127    /// Mutable account.
128    account: &'a mut Account,
129    /// Journal entries.
130    journal_entries: &'a mut Vec<ENTRY>,
131    /// Access list.
132    access_list: &'a HashMap<Address, HashSet<StorageKey>>,
133    /// Transaction ID.
134    transaction_id: usize,
135    /// Database used to load storage.
136    db: &'a mut DB,
137}
138
139impl<'a, DB: Database, ENTRY: JournalEntryTr> JournaledAccount<'a, DB, ENTRY> {
140    /// Creates new JournaledAccount
141    #[inline]
142    pub fn new(
143        address: Address,
144        account: &'a mut Account,
145        journal_entries: &'a mut Vec<ENTRY>,
146        db: &'a mut DB,
147        access_list: &'a HashMap<Address, HashSet<StorageKey>>,
148        transaction_id: usize,
149    ) -> Self {
150        Self {
151            address,
152            account,
153            journal_entries,
154            access_list,
155            transaction_id,
156            db,
157        }
158    }
159
160    /// Loads the storage slot.
161    ///
162    /// If storage is cold and skip_cold_load is true, it will return [`JournalLoadError::ColdLoadSkipped`] error.
163    ///
164    /// Does not erase the db error.
165    #[inline(never)]
166    pub fn sload_concrete_error(
167        &mut self,
168        key: StorageKey,
169        skip_cold_load: bool,
170    ) -> Result<StateLoad<&mut EvmStorageSlot>, JournalLoadError<DB::Error>> {
171        let is_newly_created = self.account.is_created();
172        let (slot, is_cold) = match self.account.storage.entry(key) {
173            Entry::Occupied(occ) => {
174                let slot = occ.into_mut();
175                // skip load if account is cold.
176                let is_cold = slot.is_cold_transaction_id(self.transaction_id);
177                if is_cold && skip_cold_load {
178                    return Err(JournalLoadError::ColdLoadSkipped);
179                }
180                slot.mark_warm_with_transaction_id(self.transaction_id);
181                (slot, is_cold)
182            }
183            Entry::Vacant(vac) => {
184                // is storage cold
185                let is_cold = self
186                    .access_list
187                    .get(&self.address)
188                    .and_then(|v| v.get(&key))
189                    .is_none();
190
191                if is_cold && skip_cold_load {
192                    return Err(JournalLoadError::ColdLoadSkipped);
193                }
194                // if storage was cleared, we don't need to ping db.
195                let value = if is_newly_created {
196                    StorageValue::ZERO
197                } else {
198                    self.db.storage(self.address, key)?
199                };
200
201                let slot = vac.insert(EvmStorageSlot::new(value, self.transaction_id));
202                (slot, is_cold)
203            }
204        };
205
206        if is_cold {
207            // add it to journal as cold loaded.
208            self.journal_entries
209                .push(ENTRY::storage_warmed(self.address, key));
210        }
211
212        Ok(StateLoad::new(slot, is_cold))
213    }
214
215    /// Stores the storage slot.
216    ///
217    /// If storage is cold and skip_cold_load is true, it will return [`JournalLoadError::ColdLoadSkipped`] error.
218    ///
219    /// Does not erase the db error.
220    #[inline]
221    pub fn sstore_concrete_error(
222        &mut self,
223        key: StorageKey,
224        new: StorageValue,
225        skip_cold_load: bool,
226    ) -> Result<StateLoad<SStoreResult>, JournalLoadError<DB::Error>> {
227        // touch the account so changes are tracked.
228        self.touch();
229
230        // assume that acc exists and load the slot.
231        let slot = self.sload_concrete_error(key, skip_cold_load)?;
232
233        let ret = Ok(StateLoad::new(
234            SStoreResult {
235                original_value: slot.original_value(),
236                present_value: slot.present_value(),
237                new_value: new,
238            },
239            slot.is_cold,
240        ));
241
242        // when new value is different from present, we need to add a journal entry and make a change.
243        if slot.present_value != new {
244            let previous_value = slot.present_value;
245            // insert value into present state.
246            slot.data.present_value = new;
247
248            // add journal entry.
249            self.journal_entries
250                .push(ENTRY::storage_changed(self.address, key, previous_value));
251        }
252
253        ret
254    }
255
256    /// Loads the code of the account. and returns it as reference.
257    ///
258    /// Does not erase the db error.
259    #[inline]
260    pub fn load_code_preserve_error(&mut self) -> Result<&Bytecode, JournalLoadError<DB::Error>> {
261        if self.account.info.code.is_none() {
262            let hash = *self.code_hash();
263            let code = if hash == KECCAK_EMPTY {
264                Bytecode::default()
265            } else {
266                self.db.code_by_hash(hash)?
267            };
268            self.account.info.code = Some(code);
269        }
270
271        Ok(self.account.info.code.as_ref().unwrap())
272    }
273
274    /// Consumes the journaled account and returns the account.
275    #[inline]
276    pub fn into_account(self) -> &'a Account {
277        self.account
278    }
279}
280
281impl<'a, DB: Database, ENTRY: JournalEntryTr> JournaledAccountTr
282    for JournaledAccount<'a, DB, ENTRY>
283{
284    /// Returns the account.
285    fn account(&self) -> &Account {
286        self.account
287    }
288
289    /// Returns the balance of the account.
290    #[inline]
291    fn balance(&self) -> &U256 {
292        &self.account.info.balance
293    }
294
295    /// Returns the nonce of the account.
296    #[inline]
297    fn nonce(&self) -> u64 {
298        self.account.info.nonce
299    }
300
301    /// Returns the code hash of the account.
302    #[inline]
303    fn code_hash(&self) -> &B256 {
304        &self.account.info.code_hash
305    }
306
307    /// Returns the code of the account.
308    #[inline]
309    fn code(&self) -> Option<&Bytecode> {
310        self.account.info.code.as_ref()
311    }
312
313    /// Touches the account.
314    #[inline]
315    fn touch(&mut self) {
316        if !self.account.status.is_touched() {
317            self.account.mark_touch();
318            self.journal_entries
319                .push(ENTRY::account_touched(self.address));
320        }
321    }
322
323    /// Marks the account as cold without making a journal entry.
324    ///
325    /// Changing account without journal entry can be a footgun as reverting of the state change
326    /// would not happen without entry. It is the reason why this function has an `unsafe` prefix.
327    ///
328    /// If account is in access list, it would still be marked as warm if account get accessed again.
329    #[inline]
330    fn unsafe_mark_cold(&mut self) {
331        self.account.mark_cold();
332    }
333
334    /// Sets the balance of the account.
335    ///
336    /// If balance is the same, we don't add a journal entry.
337    ///
338    /// Touches the account in all cases.
339    #[inline]
340    fn set_balance(&mut self, balance: U256) {
341        self.touch();
342        if self.account.info.balance != balance {
343            self.journal_entries.push(ENTRY::balance_changed(
344                self.address,
345                self.account.info.balance,
346            ));
347            self.account.info.set_balance(balance);
348        }
349    }
350
351    /// Increments the balance of the account.
352    ///
353    /// Touches the account in all cases.
354    #[inline]
355    fn incr_balance(&mut self, balance: U256) -> bool {
356        self.touch();
357        let Some(balance) = self.account.info.balance.checked_add(balance) else {
358            return false;
359        };
360        self.set_balance(balance);
361        true
362    }
363
364    /// Decrements the balance of the account.
365    ///
366    /// Touches the account in all cases.
367    #[inline]
368    fn decr_balance(&mut self, balance: U256) -> bool {
369        self.touch();
370        let Some(balance) = self.account.info.balance.checked_sub(balance) else {
371            return false;
372        };
373        self.set_balance(balance);
374        true
375    }
376
377    /// Bumps the nonce of the account.
378    ///
379    /// Touches the account in all cases.
380    ///
381    /// Returns true if nonce was bumped, false if nonce is at the max value.
382    #[inline]
383    fn bump_nonce(&mut self) -> bool {
384        self.touch();
385        let Some(nonce) = self.account.info.nonce.checked_add(1) else {
386            return false;
387        };
388        self.account.info.set_nonce(nonce);
389        self.journal_entries.push(ENTRY::nonce_bumped(self.address));
390        true
391    }
392
393    /// Set the nonce of the account and create a journal entry.
394    ///
395    /// Touches the account in all cases.
396    #[inline]
397    fn set_nonce(&mut self, nonce: u64) {
398        self.touch();
399        let previous_nonce = self.account.info.nonce;
400        self.account.info.set_nonce(nonce);
401        self.journal_entries
402            .push(ENTRY::nonce_changed(self.address, previous_nonce));
403    }
404
405    /// Set the nonce of the account without creating a journal entry.
406    ///
407    /// Changing account without journal entry can be a footgun as reverting of the state change
408    /// would not happen without entry. It is the reason why this function has an `unsafe` prefix.
409    #[inline]
410    fn unsafe_set_nonce(&mut self, nonce: u64) {
411        self.account.info.set_nonce(nonce);
412    }
413
414    /// Sets the code of the account.
415    ///
416    /// Touches the account in all cases.
417    #[inline]
418    fn set_code(&mut self, code_hash: B256, code: Bytecode) {
419        self.touch();
420        self.account.info.set_code_hash(code_hash);
421        self.account.info.set_code(code);
422        self.journal_entries.push(ENTRY::code_changed(self.address));
423    }
424
425    /// Sets the code of the account. Calculates hash of the code.
426    ///
427    /// Touches the account in all cases.
428    #[inline]
429    fn set_code_and_hash_slow(&mut self, code: Bytecode) {
430        let code_hash = code.hash_slow();
431        self.set_code(code_hash, code);
432    }
433
434    /// Delegates the account to another address (EIP-7702).
435    ///
436    /// This touches the account, sets the code to the delegation designation,
437    /// and bumps the nonce.
438    #[inline]
439    fn delegate(&mut self, address: Address) {
440        let (bytecode, hash) = if address.is_zero() {
441            (Bytecode::default(), KECCAK_EMPTY)
442        } else {
443            let bytecode = Bytecode::new_eip7702(address);
444            let hash = bytecode.hash_slow();
445            (bytecode, hash)
446        };
447        self.touch();
448        self.set_code(hash, bytecode);
449        self.bump_nonce();
450    }
451
452    /// Loads the storage slot.
453    #[inline]
454    fn sload(
455        &mut self,
456        key: StorageKey,
457        skip_cold_load: bool,
458    ) -> Result<StateLoad<&mut EvmStorageSlot>, JournalLoadErasedError> {
459        self.sload_concrete_error(key, skip_cold_load)
460            .map_err(|i| i.map(ErasedError::new))
461    }
462
463    /// Stores the storage slot.
464    #[inline]
465    fn sstore(
466        &mut self,
467        key: StorageKey,
468        new: StorageValue,
469        skip_cold_load: bool,
470    ) -> Result<StateLoad<SStoreResult>, JournalLoadErasedError> {
471        self.sstore_concrete_error(key, new, skip_cold_load)
472            .map_err(|i| i.map(ErasedError::new))
473    }
474
475    /// Loads the code of the account. and returns it as reference.
476    #[inline]
477    fn load_code(&mut self) -> Result<&Bytecode, JournalLoadErasedError> {
478        self.load_code_preserve_error()
479            .map_err(|i| i.map(ErasedError::new))
480    }
481}