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 mut is_cold = false;
177                if slot.is_cold_transaction_id(self.transaction_id) {
178                    // is storage cold
179                    is_cold = self
180                        .access_list
181                        .get(&self.address)
182                        .and_then(|v| v.get(&key))
183                        .is_none();
184
185                    if is_cold && skip_cold_load {
186                        return Err(JournalLoadError::ColdLoadSkipped);
187                    }
188                }
189                slot.mark_warm_with_transaction_id(self.transaction_id);
190                (slot, is_cold)
191            }
192            Entry::Vacant(vac) => {
193                // is storage cold
194                let is_cold = self
195                    .access_list
196                    .get(&self.address)
197                    .and_then(|v| v.get(&key))
198                    .is_none();
199
200                if is_cold && skip_cold_load {
201                    return Err(JournalLoadError::ColdLoadSkipped);
202                }
203                // if storage was cleared, we don't need to ping db.
204                let value = if is_newly_created {
205                    StorageValue::ZERO
206                } else {
207                    self.db.storage(self.address, key)?
208                };
209
210                let slot = vac.insert(EvmStorageSlot::new(value, self.transaction_id));
211                (slot, is_cold)
212            }
213        };
214
215        if is_cold {
216            // add it to journal as cold loaded.
217            self.journal_entries
218                .push(ENTRY::storage_warmed(self.address, key));
219        }
220
221        Ok(StateLoad::new(slot, is_cold))
222    }
223
224    /// Stores the storage slot.
225    ///
226    /// If storage is cold and skip_cold_load is true, it will return [`JournalLoadError::ColdLoadSkipped`] error.
227    ///
228    /// Does not erase the db error.
229    #[inline]
230    pub fn sstore_concrete_error(
231        &mut self,
232        key: StorageKey,
233        new: StorageValue,
234        skip_cold_load: bool,
235    ) -> Result<StateLoad<SStoreResult>, JournalLoadError<DB::Error>> {
236        // touch the account so changes are tracked.
237        self.touch();
238
239        // assume that acc exists and load the slot.
240        let slot = self.sload_concrete_error(key, skip_cold_load)?;
241
242        let ret = Ok(StateLoad::new(
243            SStoreResult {
244                original_value: slot.original_value(),
245                present_value: slot.present_value(),
246                new_value: new,
247            },
248            slot.is_cold,
249        ));
250
251        // when new value is different from present, we need to add a journal entry and make a change.
252        if slot.present_value != new {
253            let previous_value = slot.present_value;
254            // insert value into present state.
255            slot.data.present_value = new;
256
257            // add journal entry.
258            self.journal_entries
259                .push(ENTRY::storage_changed(self.address, key, previous_value));
260        }
261
262        ret
263    }
264
265    /// Loads the code of the account. and returns it as reference.
266    ///
267    /// Does not erase the db error.
268    #[inline]
269    pub fn load_code_preserve_error(&mut self) -> Result<&Bytecode, JournalLoadError<DB::Error>> {
270        if self.account.info.code.is_none() {
271            let hash = *self.code_hash();
272            let code = if hash == KECCAK_EMPTY {
273                Bytecode::default()
274            } else {
275                self.db.code_by_hash(hash)?
276            };
277            self.account.info.code = Some(code);
278        }
279
280        Ok(self.account.info.code.as_ref().unwrap())
281    }
282
283    /// Consumes the journaled account and returns the account.
284    #[inline]
285    pub fn into_account(self) -> &'a Account {
286        self.account
287    }
288}
289
290impl<'a, DB: Database, ENTRY: JournalEntryTr> JournaledAccountTr
291    for JournaledAccount<'a, DB, ENTRY>
292{
293    /// Returns the account.
294    fn account(&self) -> &Account {
295        self.account
296    }
297
298    /// Returns the balance of the account.
299    #[inline]
300    fn balance(&self) -> &U256 {
301        &self.account.info.balance
302    }
303
304    /// Returns the nonce of the account.
305    #[inline]
306    fn nonce(&self) -> u64 {
307        self.account.info.nonce
308    }
309
310    /// Returns the code hash of the account.
311    #[inline]
312    fn code_hash(&self) -> &B256 {
313        &self.account.info.code_hash
314    }
315
316    /// Returns the code of the account.
317    #[inline]
318    fn code(&self) -> Option<&Bytecode> {
319        self.account.info.code.as_ref()
320    }
321
322    /// Touches the account.
323    #[inline]
324    fn touch(&mut self) {
325        if !self.account.status.is_touched() {
326            self.account.mark_touch();
327            self.journal_entries
328                .push(ENTRY::account_touched(self.address));
329        }
330    }
331
332    /// Marks the account as cold without making a journal entry.
333    ///
334    /// Changing account without journal entry can be a footgun as reverting of the state change
335    /// would not happen without entry. It is the reason why this function has an `unsafe` prefix.
336    ///
337    /// If account is in access list, it would still be marked as warm if account get accessed again.
338    #[inline]
339    fn unsafe_mark_cold(&mut self) {
340        self.account.mark_cold();
341    }
342
343    /// Sets the balance of the account.
344    ///
345    /// If balance is the same, we don't add a journal entry.
346    ///
347    /// Touches the account in all cases.
348    #[inline]
349    fn set_balance(&mut self, balance: U256) {
350        self.touch();
351        if self.account.info.balance != balance {
352            self.journal_entries.push(ENTRY::balance_changed(
353                self.address,
354                self.account.info.balance,
355            ));
356            self.account.info.set_balance(balance);
357        }
358    }
359
360    /// Increments the balance of the account.
361    ///
362    /// Touches the account in all cases.
363    #[inline]
364    fn incr_balance(&mut self, balance: U256) -> bool {
365        self.touch();
366        let Some(balance) = self.account.info.balance.checked_add(balance) else {
367            return false;
368        };
369        self.set_balance(balance);
370        true
371    }
372
373    /// Decrements the balance of the account.
374    ///
375    /// Touches the account in all cases.
376    #[inline]
377    fn decr_balance(&mut self, balance: U256) -> bool {
378        self.touch();
379        let Some(balance) = self.account.info.balance.checked_sub(balance) else {
380            return false;
381        };
382        self.set_balance(balance);
383        true
384    }
385
386    /// Bumps the nonce of the account.
387    ///
388    /// Touches the account in all cases.
389    ///
390    /// Returns true if nonce was bumped, false if nonce is at the max value.
391    #[inline]
392    fn bump_nonce(&mut self) -> bool {
393        self.touch();
394        let Some(nonce) = self.account.info.nonce.checked_add(1) else {
395            return false;
396        };
397        self.account.info.set_nonce(nonce);
398        self.journal_entries.push(ENTRY::nonce_bumped(self.address));
399        true
400    }
401
402    /// Set the nonce of the account and create a journal entry.
403    ///
404    /// Touches the account in all cases.
405    #[inline]
406    fn set_nonce(&mut self, nonce: u64) {
407        self.touch();
408        let previous_nonce = self.account.info.nonce;
409        self.account.info.set_nonce(nonce);
410        self.journal_entries
411            .push(ENTRY::nonce_changed(self.address, previous_nonce));
412    }
413
414    /// Set the nonce of the account without creating a journal entry.
415    ///
416    /// Changing account without journal entry can be a footgun as reverting of the state change
417    /// would not happen without entry. It is the reason why this function has an `unsafe` prefix.
418    #[inline]
419    fn unsafe_set_nonce(&mut self, nonce: u64) {
420        self.account.info.set_nonce(nonce);
421    }
422
423    /// Sets the code of the account.
424    ///
425    /// Touches the account in all cases.
426    #[inline]
427    fn set_code(&mut self, code_hash: B256, code: Bytecode) {
428        self.touch();
429        self.account.info.set_code_hash(code_hash);
430        self.account.info.set_code(code);
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}