Skip to main content

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