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, TransactionId};
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: TransactionId,
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 const 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: TransactionId,
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 if let Some(account_id) = self.account.info.account_id {
208                    self.db
209                        .storage_by_account_id(self.address, account_id, key)?
210                } else {
211                    self.db.storage(self.address, key)?
212                };
213
214                let slot = vac.insert(EvmStorageSlot::new(value, self.transaction_id));
215                (slot, is_cold)
216            }
217        };
218
219        if is_cold {
220            // add it to journal as cold loaded.
221            self.journal_entries
222                .push(ENTRY::storage_warmed(self.address, key));
223        }
224
225        Ok(StateLoad::new(slot, is_cold))
226    }
227
228    /// Stores the storage slot.
229    ///
230    /// If storage is cold and skip_cold_load is true, it will return [`JournalLoadError::ColdLoadSkipped`] error.
231    ///
232    /// Does not erase the db error.
233    #[inline]
234    pub fn sstore_concrete_error(
235        &mut self,
236        key: StorageKey,
237        new: StorageValue,
238        skip_cold_load: bool,
239    ) -> Result<StateLoad<SStoreResult>, JournalLoadError<DB::Error>> {
240        // touch the account so changes are tracked.
241        self.touch();
242
243        // assume that acc exists and load the slot.
244        let slot = self.sload_concrete_error(key, skip_cold_load)?;
245
246        let ret = Ok(StateLoad::new(
247            SStoreResult {
248                original_value: slot.original_value(),
249                present_value: slot.present_value(),
250                new_value: new,
251            },
252            slot.is_cold,
253        ));
254
255        // when new value is different from present, we need to add a journal entry and make a change.
256        if slot.present_value != new {
257            let previous_value = slot.present_value;
258            // insert value into present state.
259            slot.data.present_value = new;
260
261            // add journal entry.
262            self.journal_entries
263                .push(ENTRY::storage_changed(self.address, key, previous_value));
264        }
265
266        ret
267    }
268
269    /// Loads the code of the account. and returns it as reference.
270    ///
271    /// Does not erase the db error.
272    #[inline]
273    pub fn load_code_preserve_error(&mut self) -> Result<&Bytecode, JournalLoadError<DB::Error>> {
274        if self.account.info.code.is_none() {
275            let hash = *self.code_hash();
276            let code = if hash == KECCAK_EMPTY {
277                Bytecode::default()
278            } else {
279                self.db.code_by_hash(hash)?
280            };
281            self.account.info.code = Some(code);
282        }
283
284        Ok(self.account.info.code.as_ref().unwrap())
285    }
286
287    /// Consumes the journaled account and returns the account.
288    #[inline]
289    pub const fn into_account(self) -> &'a Account {
290        self.account
291    }
292}
293
294impl<'a, DB: Database, ENTRY: JournalEntryTr> JournaledAccountTr
295    for JournaledAccount<'a, DB, ENTRY>
296{
297    /// Returns the account.
298    fn account(&self) -> &Account {
299        self.account
300    }
301
302    /// Returns the balance of the account.
303    #[inline]
304    fn balance(&self) -> &U256 {
305        &self.account.info.balance
306    }
307
308    /// Returns the nonce of the account.
309    #[inline]
310    fn nonce(&self) -> u64 {
311        self.account.info.nonce
312    }
313
314    /// Returns the code hash of the account.
315    #[inline]
316    fn code_hash(&self) -> &B256 {
317        &self.account.info.code_hash
318    }
319
320    /// Returns the code of the account.
321    #[inline]
322    fn code(&self) -> Option<&Bytecode> {
323        self.account.info.code.as_ref()
324    }
325
326    /// Touches the account.
327    #[inline]
328    fn touch(&mut self) {
329        if !self.account.status.is_touched() {
330            self.account.mark_touch();
331            self.journal_entries
332                .push(ENTRY::account_touched(self.address));
333        }
334    }
335
336    /// Marks the account as cold without making a journal entry.
337    ///
338    /// Changing account without journal entry can be a footgun as reverting of the state change
339    /// would not happen without entry. It is the reason why this function has an `unsafe` prefix.
340    ///
341    /// If account is in access list, it would still be marked as warm if account get accessed again.
342    #[inline]
343    fn unsafe_mark_cold(&mut self) {
344        self.account.mark_cold();
345    }
346
347    /// Sets the balance of the account.
348    ///
349    /// If balance is the same, we don't add a journal entry.
350    ///
351    /// Touches the account in all cases.
352    #[inline]
353    fn set_balance(&mut self, balance: U256) {
354        self.touch();
355        if self.account.info.balance != balance {
356            self.journal_entries.push(ENTRY::balance_changed(
357                self.address,
358                self.account.info.balance,
359            ));
360            self.account.info.set_balance(balance);
361        }
362    }
363
364    /// Increments the balance of the account.
365    ///
366    /// Touches the account in all cases.
367    #[inline]
368    fn incr_balance(&mut self, balance: U256) -> bool {
369        self.touch();
370        let Some(balance) = self.account.info.balance.checked_add(balance) else {
371            return false;
372        };
373        self.set_balance(balance);
374        true
375    }
376
377    /// Decrements the balance of the account.
378    ///
379    /// Touches the account in all cases.
380    #[inline]
381    fn decr_balance(&mut self, balance: U256) -> bool {
382        self.touch();
383        let Some(balance) = self.account.info.balance.checked_sub(balance) else {
384            return false;
385        };
386        self.set_balance(balance);
387        true
388    }
389
390    /// Bumps the nonce of the account.
391    ///
392    /// Touches the account in all cases.
393    ///
394    /// Returns true if nonce was bumped, false if nonce is at the max value.
395    #[inline]
396    fn bump_nonce(&mut self) -> bool {
397        self.touch();
398        let Some(nonce) = self.account.info.nonce.checked_add(1) else {
399            return false;
400        };
401        self.account.info.set_nonce(nonce);
402        self.journal_entries.push(ENTRY::nonce_bumped(self.address));
403        true
404    }
405
406    /// Set the nonce of the account and create a journal entry.
407    ///
408    /// Touches the account in all cases.
409    #[inline]
410    fn set_nonce(&mut self, nonce: u64) {
411        self.touch();
412        let previous_nonce = self.account.info.nonce;
413        self.account.info.set_nonce(nonce);
414        self.journal_entries
415            .push(ENTRY::nonce_changed(self.address, previous_nonce));
416    }
417
418    /// Set the nonce of the account without creating a journal entry.
419    ///
420    /// Changing account without journal entry can be a footgun as reverting of the state change
421    /// would not happen without entry. It is the reason why this function has an `unsafe` prefix.
422    #[inline]
423    fn unsafe_set_nonce(&mut self, nonce: u64) {
424        self.account.info.set_nonce(nonce);
425    }
426
427    /// Sets the code of the account.
428    ///
429    /// Touches the account in all cases.
430    #[inline]
431    fn set_code(&mut self, code_hash: B256, code: Bytecode) {
432        self.touch();
433        self.account.info.set_code_and_hash(code, code_hash);
434        self.journal_entries.push(ENTRY::code_changed(self.address));
435    }
436
437    /// Sets the code of the account. Calculates hash of the code.
438    ///
439    /// Touches the account in all cases.
440    #[inline]
441    fn set_code_and_hash_slow(&mut self, code: Bytecode) {
442        let code_hash = code.hash_slow();
443        self.set_code(code_hash, code);
444    }
445
446    /// Delegates the account to another address (EIP-7702).
447    ///
448    /// This touches the account, sets the code to the delegation designation,
449    /// and bumps the nonce.
450    #[inline]
451    fn delegate(&mut self, address: Address) {
452        let (bytecode, hash) = if address.is_zero() {
453            (Bytecode::default(), KECCAK_EMPTY)
454        } else {
455            let bytecode = Bytecode::new_eip7702(address);
456            let hash = bytecode.hash_slow();
457            (bytecode, hash)
458        };
459        self.touch();
460        self.set_code(hash, bytecode);
461        self.bump_nonce();
462    }
463
464    /// Loads the storage slot.
465    #[inline]
466    fn sload(
467        &mut self,
468        key: StorageKey,
469        skip_cold_load: bool,
470    ) -> Result<StateLoad<&mut EvmStorageSlot>, JournalLoadErasedError> {
471        self.sload_concrete_error(key, skip_cold_load)
472            .map_err(|i| i.map(ErasedError::new))
473    }
474
475    /// Stores the storage slot.
476    #[inline]
477    fn sstore(
478        &mut self,
479        key: StorageKey,
480        new: StorageValue,
481        skip_cold_load: bool,
482    ) -> Result<StateLoad<SStoreResult>, JournalLoadErasedError> {
483        self.sstore_concrete_error(key, new, skip_cold_load)
484            .map_err(|i| i.map(ErasedError::new))
485    }
486
487    /// Loads the code of the account. and returns it as reference.
488    #[inline]
489    fn load_code(&mut self) -> Result<&Bytecode, JournalLoadErasedError> {
490        self.load_code_preserve_error()
491            .map_err(|i| i.map(ErasedError::new))
492    }
493}