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