revm_context/journal/
inner.rs

1//! Module containing the [`JournalInner`] that is part of [`crate::Journal`].
2use super::{JournalEntryTr, JournalOutput};
3use bytecode::Bytecode;
4use context_interface::{
5    context::{SStoreResult, SelfDestructResult, StateLoad},
6    journaled_state::{AccountLoad, JournalCheckpoint, TransferError},
7};
8use core::mem;
9use database_interface::Database;
10use primitives::{
11    hardfork::{SpecId, SpecId::*},
12    hash_map::Entry,
13    Address, HashMap, HashSet, Log, B256, KECCAK_EMPTY, U256,
14};
15use state::{Account, EvmState, EvmStorageSlot, TransientStorage};
16use std::vec::Vec;
17
18/// Inner journal state that contains journal and state changes.
19///
20/// Spec Id is a essential information for the Journal.
21#[derive(Debug, Clone, PartialEq, Eq)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23pub struct JournalInner<ENTRY> {
24    /// The current state
25    pub state: EvmState,
26    /// Transient storage that is discarded after every transaction.
27    ///
28    /// See [EIP-1153](https://eips.ethereum.org/EIPS/eip-1153).
29    pub transient_storage: TransientStorage,
30    /// Emitted logs
31    pub logs: Vec<Log>,
32    /// The current call stack depth
33    pub depth: usize,
34    /// The journal of state changes, one for each call
35    pub journal: Vec<ENTRY>,
36    /// The spec ID for the EVM. Spec is required for some journal entries and needs to be set for
37    /// JournalInner to be functional.
38    ///
39    /// If spec is set it assumed that precompile addresses are set as well for this particular spec.
40    ///
41    /// This spec is used for two things:
42    ///
43    /// - [EIP-161]: Prior to this EIP, Ethereum had separate definitions for empty and non-existing accounts.
44    /// - [EIP-6780]: `SELFDESTRUCT` only in same transaction
45    ///
46    /// [EIP-161]: https://eips.ethereum.org/EIPS/eip-161
47    /// [EIP-6780]: https://eips.ethereum.org/EIPS/eip-6780
48    pub spec: SpecId,
49    /// Warm loaded addresses are used to check if loaded address
50    /// should be considered cold or warm loaded when the account
51    /// is first accessed.
52    ///
53    /// Note that this not include newly loaded accounts, account and storage
54    /// is considered warm if it is found in the `State`.
55    pub warm_preloaded_addresses: HashSet<Address>,
56    /// Precompile addresses
57    pub precompiles: HashSet<Address>,
58}
59
60impl<ENTRY: JournalEntryTr> Default for JournalInner<ENTRY> {
61    fn default() -> Self {
62        Self::new()
63    }
64}
65
66impl<ENTRY: JournalEntryTr> JournalInner<ENTRY> {
67    /// Creates new [`JournalInner`].
68    ///
69    /// `warm_preloaded_addresses` is used to determine if address is considered warm loaded.
70    /// In ordinary case this is precompile or beneficiary.
71    pub fn new() -> JournalInner<ENTRY> {
72        Self {
73            state: HashMap::default(),
74            transient_storage: TransientStorage::default(),
75            logs: Vec::new(),
76            journal: Vec::new(),
77            depth: 0,
78            spec: SpecId::default(),
79            warm_preloaded_addresses: HashSet::default(),
80            precompiles: HashSet::default(),
81        }
82    }
83
84    /// Take the [`JournalOutput`] and clears the journal by resetting it to initial state.
85    ///
86    /// Note: Precompile addresses and spec are preserved and initial state of
87    /// warm_preloaded_addresses will contain precompiles addresses.
88    /// Precompile addresses
89    #[inline]
90    pub fn clear_and_take_output(&mut self) -> JournalOutput {
91        // Clears all field from JournalInner. Doing it this way to avoid
92        // missing any field.
93        let Self {
94            state,
95            transient_storage,
96            logs,
97            depth,
98            journal,
99            spec,
100            warm_preloaded_addresses,
101            precompiles,
102        } = self;
103        // Spec is not changed. It is always set again execution.
104        let _ = spec;
105        // Load precompiles into warm_preloaded_addresses.
106        warm_preloaded_addresses.clone_from(precompiles);
107
108        let state = mem::take(state);
109        let logs = mem::take(logs);
110        transient_storage.clear();
111        journal.clear();
112        *depth = 0;
113
114        JournalOutput { state, logs }
115    }
116
117    /// Return reference to state.
118    #[inline]
119    pub fn state(&mut self) -> &mut EvmState {
120        &mut self.state
121    }
122
123    /// Sets SpecId.
124    #[inline]
125    pub fn set_spec_id(&mut self, spec: SpecId) {
126        self.spec = spec;
127    }
128
129    /// Mark account as touched as only touched accounts will be added to state.
130    /// This is especially important for state clear where touched empty accounts needs to
131    /// be removed from state.
132    #[inline]
133    pub fn touch(&mut self, address: Address) {
134        if let Some(account) = self.state.get_mut(&address) {
135            Self::touch_account(&mut self.journal, address, account);
136        }
137    }
138
139    /// Mark account as touched.
140    #[inline]
141    fn touch_account(journal: &mut Vec<ENTRY>, address: Address, account: &mut Account) {
142        if !account.is_touched() {
143            journal.push(ENTRY::account_touched(address));
144            account.mark_touch();
145        }
146    }
147
148    /// Returns the _loaded_ [Account] for the given address.
149    ///
150    /// This assumes that the account has already been loaded.
151    ///
152    /// # Panics
153    ///
154    /// Panics if the account has not been loaded and is missing from the state set.
155    #[inline]
156    pub fn account(&self, address: Address) -> &Account {
157        self.state
158            .get(&address)
159            .expect("Account expected to be loaded") // Always assume that acc is already loaded
160    }
161
162    /// Set code and its hash to the account.
163    ///
164    /// Note: Assume account is warm and that hash is calculated from code.
165    #[inline]
166    pub fn set_code_with_hash(&mut self, address: Address, code: Bytecode, hash: B256) {
167        let account = self.state.get_mut(&address).unwrap();
168        Self::touch_account(&mut self.journal, address, account);
169
170        self.journal.push(ENTRY::code_changed(address));
171
172        account.info.code_hash = hash;
173        account.info.code = Some(code);
174    }
175
176    /// Use it only if you know that acc is warm.
177    ///
178    /// Assume account is warm.
179    #[inline]
180    pub fn set_code(&mut self, address: Address, code: Bytecode) {
181        let hash = code.hash_slow();
182        self.set_code_with_hash(address, code, hash)
183    }
184
185    /// Increments the nonce of the account.
186    ///
187    /// # Returns
188    ///
189    /// Returns the new nonce if it did not overflow, otherwise returns `None`.
190    #[inline]
191    pub fn inc_nonce(&mut self, address: Address) -> Option<u64> {
192        let account = self.state.get_mut(&address).unwrap();
193        // Check if nonce is going to overflow.
194        if account.info.nonce == u64::MAX {
195            return None;
196        }
197        Self::touch_account(&mut self.journal, address, account);
198        self.journal.push(ENTRY::nonce_changed(address));
199
200        account.info.nonce += 1;
201
202        Some(account.info.nonce)
203    }
204
205    /// Transfers balance from two accounts. Returns error if sender balance is not enough.
206    #[inline]
207    pub fn transfer<DB: Database>(
208        &mut self,
209        db: &mut DB,
210        from: Address,
211        to: Address,
212        balance: U256,
213    ) -> Result<Option<TransferError>, DB::Error> {
214        if balance.is_zero() {
215            self.load_account(db, to)?;
216            let to_account = self.state.get_mut(&to).unwrap();
217            Self::touch_account(&mut self.journal, to, to_account);
218            return Ok(None);
219        }
220        // load accounts
221        self.load_account(db, from)?;
222        self.load_account(db, to)?;
223
224        // sub balance from
225        let from_account = self.state.get_mut(&from).unwrap();
226        Self::touch_account(&mut self.journal, from, from_account);
227        let from_balance = &mut from_account.info.balance;
228
229        let Some(from_balance_decr) = from_balance.checked_sub(balance) else {
230            return Ok(Some(TransferError::OutOfFunds));
231        };
232        *from_balance = from_balance_decr;
233
234        // add balance to
235        let to_account = &mut self.state.get_mut(&to).unwrap();
236        Self::touch_account(&mut self.journal, to, to_account);
237        let to_balance = &mut to_account.info.balance;
238        let Some(to_balance_incr) = to_balance.checked_add(balance) else {
239            return Ok(Some(TransferError::OverflowPayment));
240        };
241        *to_balance = to_balance_incr;
242        // Overflow of U256 balance is not possible to happen on mainnet. We don't bother to return funds from from_acc.
243
244        self.journal
245            .push(ENTRY::balance_transfer(from, to, balance));
246
247        Ok(None)
248    }
249
250    /// Creates account or returns false if collision is detected.
251    ///
252    /// There are few steps done:
253    /// 1. Make created account warm loaded (AccessList) and this should
254    ///    be done before subroutine checkpoint is created.
255    /// 2. Check if there is collision of newly created account with existing one.
256    /// 3. Mark created account as created.
257    /// 4. Add fund to created account
258    /// 5. Increment nonce of created account if SpuriousDragon is active
259    /// 6. Decrease balance of caller account.
260    ///
261    /// # Panics
262    ///
263    /// Panics if the caller is not loaded inside the EVM state.
264    /// This should have been done inside `create_inner`.
265    #[inline]
266    pub fn create_account_checkpoint(
267        &mut self,
268        caller: Address,
269        target_address: Address,
270        balance: U256,
271        spec_id: SpecId,
272    ) -> Result<JournalCheckpoint, TransferError> {
273        // Enter subroutine
274        let checkpoint = self.checkpoint();
275
276        // Fetch balance of caller.
277        let caller_balance = self.state.get(&caller).unwrap().info.balance;
278        // Check if caller has enough balance to send to the created contract.
279        if caller_balance < balance {
280            self.checkpoint_revert(checkpoint);
281            return Err(TransferError::OutOfFunds);
282        }
283
284        // Newly created account is present, as we just loaded it.
285        let target_acc = self.state.get_mut(&target_address).unwrap();
286        let last_journal = &mut self.journal;
287
288        // New account can be created if:
289        // Bytecode is not empty.
290        // Nonce is not zero
291        // Account is not precompile.
292        if target_acc.info.code_hash != KECCAK_EMPTY || target_acc.info.nonce != 0 {
293            self.checkpoint_revert(checkpoint);
294            return Err(TransferError::CreateCollision);
295        }
296
297        // set account status to create.
298        target_acc.mark_created();
299
300        // this entry will revert set nonce.
301        last_journal.push(ENTRY::account_created(target_address));
302        target_acc.info.code = None;
303        // EIP-161: State trie clearing (invariant-preserving alternative)
304        if spec_id.is_enabled_in(SPURIOUS_DRAGON) {
305            // nonce is going to be reset to zero in AccountCreated journal entry.
306            target_acc.info.nonce = 1;
307        }
308
309        // touch account. This is important as for pre SpuriousDragon account could be
310        // saved even empty.
311        Self::touch_account(last_journal, target_address, target_acc);
312
313        // Add balance to created account, as we already have target here.
314        let Some(new_balance) = target_acc.info.balance.checked_add(balance) else {
315            self.checkpoint_revert(checkpoint);
316            return Err(TransferError::OverflowPayment);
317        };
318        target_acc.info.balance = new_balance;
319
320        // safe to decrement for the caller as balance check is already done.
321        self.state.get_mut(&caller).unwrap().info.balance -= balance;
322
323        // add journal entry of transferred balance
324        last_journal.push(ENTRY::balance_transfer(caller, target_address, balance));
325
326        Ok(checkpoint)
327    }
328
329    /// Makes a checkpoint that in case of Revert can bring back state to this point.
330    #[inline]
331    pub fn checkpoint(&mut self) -> JournalCheckpoint {
332        let checkpoint = JournalCheckpoint {
333            log_i: self.logs.len(),
334            journal_i: self.journal.len(),
335        };
336        self.depth += 1;
337        checkpoint
338    }
339
340    /// Commits the checkpoint.
341    #[inline]
342    pub fn checkpoint_commit(&mut self) {
343        self.depth -= 1;
344    }
345
346    /// Reverts all changes to state until given checkpoint.
347    #[inline]
348    pub fn checkpoint_revert(&mut self, checkpoint: JournalCheckpoint) {
349        let is_spurious_dragon_enabled = self.spec.is_enabled_in(SPURIOUS_DRAGON);
350        let state = &mut self.state;
351        let transient_storage = &mut self.transient_storage;
352        self.depth -= 1;
353        self.logs.truncate(checkpoint.log_i);
354
355        // iterate over last N journals sets and revert our global state
356        self.journal
357            .drain(checkpoint.journal_i..)
358            .rev()
359            .for_each(|entry| {
360                entry.revert(state, transient_storage, is_spurious_dragon_enabled);
361            });
362    }
363
364    /// Performs selfdestruct action.
365    /// Transfers balance from address to target. Check if target exist/is_cold
366    ///
367    /// Note: Balance will be lost if address and target are the same BUT when
368    /// current spec enables Cancun, this happens only when the account associated to address
369    /// is created in the same tx
370    ///
371    /// # References:
372    ///  * <https://github.com/ethereum/go-ethereum/blob/141cd425310b503c5678e674a8c3872cf46b7086/core/vm/instructions.go#L832-L833>
373    ///  * <https://github.com/ethereum/go-ethereum/blob/141cd425310b503c5678e674a8c3872cf46b7086/core/state/statedb.go#L449>
374    ///  * <https://eips.ethereum.org/EIPS/eip-6780>
375    #[inline]
376    pub fn selfdestruct<DB: Database>(
377        &mut self,
378        db: &mut DB,
379        address: Address,
380        target: Address,
381    ) -> Result<StateLoad<SelfDestructResult>, DB::Error> {
382        let spec = self.spec;
383        let account_load = self.load_account(db, target)?;
384        let is_cold = account_load.is_cold;
385        let is_empty = account_load.state_clear_aware_is_empty(spec);
386
387        if address != target {
388            // Both accounts are loaded before this point, `address` as we execute its contract.
389            // and `target` at the beginning of the function.
390            let acc_balance = self.state.get(&address).unwrap().info.balance;
391
392            let target_account = self.state.get_mut(&target).unwrap();
393            Self::touch_account(&mut self.journal, target, target_account);
394            target_account.info.balance += acc_balance;
395        }
396
397        let acc = self.state.get_mut(&address).unwrap();
398        let balance = acc.info.balance;
399        let previously_destroyed = acc.is_selfdestructed();
400        let is_cancun_enabled = spec.is_enabled_in(CANCUN);
401
402        // EIP-6780 (Cancun hard-fork): selfdestruct only if contract is created in the same tx
403        let journal_entry = if acc.is_created() || !is_cancun_enabled {
404            acc.mark_selfdestruct();
405            acc.info.balance = U256::ZERO;
406            Some(ENTRY::account_destroyed(
407                address,
408                target,
409                previously_destroyed,
410                balance,
411            ))
412        } else if address != target {
413            acc.info.balance = U256::ZERO;
414            Some(ENTRY::balance_transfer(address, target, balance))
415        } else {
416            // State is not changed:
417            // * if we are after Cancun upgrade and
418            // * Selfdestruct account that is created in the same transaction and
419            // * Specify the target is same as selfdestructed account. The balance stays unchanged.
420            None
421        };
422
423        if let Some(entry) = journal_entry {
424            self.journal.push(entry);
425        };
426
427        Ok(StateLoad {
428            data: SelfDestructResult {
429                had_value: !balance.is_zero(),
430                target_exists: !is_empty,
431                previously_destroyed,
432            },
433            is_cold,
434        })
435    }
436
437    /// Initial load of account. This load will not be tracked inside journal
438    #[inline]
439    pub fn initial_account_load<DB: Database>(
440        &mut self,
441        db: &mut DB,
442        address: Address,
443        storage_keys: impl IntoIterator<Item = U256>,
444    ) -> Result<&mut Account, DB::Error> {
445        // load or get account.
446        let account = match self.state.entry(address) {
447            Entry::Occupied(entry) => entry.into_mut(),
448            Entry::Vacant(vac) => vac.insert(
449                db.basic(address)?
450                    .map(|i| i.into())
451                    .unwrap_or(Account::new_not_existing()),
452            ),
453        };
454        // preload storages.
455        for storage_key in storage_keys.into_iter() {
456            if let Entry::Vacant(entry) = account.storage.entry(storage_key) {
457                let storage = db.storage(address, storage_key)?;
458                entry.insert(EvmStorageSlot::new(storage));
459            }
460        }
461        Ok(account)
462    }
463
464    /// Loads account into memory. return if it is cold or warm accessed
465    #[inline]
466    pub fn load_account<DB: Database>(
467        &mut self,
468        db: &mut DB,
469        address: Address,
470    ) -> Result<StateLoad<&mut Account>, DB::Error> {
471        self.load_account_optional(db, address, false)
472    }
473
474    /// Loads account into memory. If account is EIP-7702 type it will additionally
475    /// load delegated account.
476    ///
477    /// It will mark both this and delegated account as warm loaded.
478    ///
479    /// Returns information about the account (If it is empty or cold loaded) and if present the information
480    /// about the delegated account (If it is cold loaded).
481    #[inline]
482    pub fn load_account_delegated<DB: Database>(
483        &mut self,
484        db: &mut DB,
485        address: Address,
486    ) -> Result<StateLoad<AccountLoad>, DB::Error> {
487        let spec = self.spec;
488        let is_eip7702_enabled = spec.is_enabled_in(SpecId::PRAGUE);
489        let account = self.load_account_optional(db, address, is_eip7702_enabled)?;
490        let is_empty = account.state_clear_aware_is_empty(spec);
491
492        let mut account_load = StateLoad::new(
493            AccountLoad {
494                is_delegate_account_cold: None,
495                is_empty,
496            },
497            account.is_cold,
498        );
499
500        // load delegate code if account is EIP-7702
501        if let Some(Bytecode::Eip7702(code)) = &account.info.code {
502            let address = code.address();
503            let delegate_account = self.load_account(db, address)?;
504            account_load.data.is_delegate_account_cold = Some(delegate_account.is_cold);
505        }
506
507        Ok(account_load)
508    }
509
510    /// Loads account and its code. If account is already loaded it will load its code.
511    ///
512    /// It will mark account as warm loaded. If not existing Database will be queried for data.
513    ///
514    /// In case of EIP-7702 delegated account will not be loaded,
515    /// [`Self::load_account_delegated`] should be used instead.
516    #[inline]
517    pub fn load_code<DB: Database>(
518        &mut self,
519        db: &mut DB,
520        address: Address,
521    ) -> Result<StateLoad<&mut Account>, DB::Error> {
522        self.load_account_optional(db, address, true)
523    }
524
525    /// Loads account. If account is already loaded it will be marked as warm.
526    #[inline]
527    pub fn load_account_optional<DB: Database>(
528        &mut self,
529        db: &mut DB,
530        address: Address,
531        load_code: bool,
532    ) -> Result<StateLoad<&mut Account>, DB::Error> {
533        let load = match self.state.entry(address) {
534            Entry::Occupied(entry) => {
535                let account = entry.into_mut();
536                let is_cold = account.mark_warm();
537                StateLoad {
538                    data: account,
539                    is_cold,
540                }
541            }
542            Entry::Vacant(vac) => {
543                let account = if let Some(account) = db.basic(address)? {
544                    account.into()
545                } else {
546                    Account::new_not_existing()
547                };
548
549                // Precompiles among some other account are warm loaded so we need to take that into account
550                let is_cold = !self.warm_preloaded_addresses.contains(&address);
551
552                StateLoad {
553                    data: vac.insert(account),
554                    is_cold,
555                }
556            }
557        };
558        // journal loading of cold account.
559        if load.is_cold {
560            self.journal.push(ENTRY::account_warmed(address));
561        }
562        if load_code {
563            let info = &mut load.data.info;
564            if info.code.is_none() {
565                let code = if info.code_hash == KECCAK_EMPTY {
566                    Bytecode::default()
567                } else {
568                    db.code_by_hash(info.code_hash)?
569                };
570                info.code = Some(code);
571            }
572        }
573
574        Ok(load)
575    }
576
577    /// Loads storage slot.
578    ///
579    /// # Panics
580    ///
581    /// Panics if the account is not present in the state.
582    #[inline]
583    pub fn sload<DB: Database>(
584        &mut self,
585        db: &mut DB,
586        address: Address,
587        key: U256,
588    ) -> Result<StateLoad<U256>, DB::Error> {
589        // assume acc is warm
590        let account = self.state.get_mut(&address).unwrap();
591        // only if account is created in this tx we can assume that storage is empty.
592        let is_newly_created = account.is_created();
593        let (value, is_cold) = match account.storage.entry(key) {
594            Entry::Occupied(occ) => {
595                let slot = occ.into_mut();
596                let is_cold = slot.mark_warm();
597                (slot.present_value, is_cold)
598            }
599            Entry::Vacant(vac) => {
600                // if storage was cleared, we don't need to ping db.
601                let value = if is_newly_created {
602                    U256::ZERO
603                } else {
604                    db.storage(address, key)?
605                };
606
607                vac.insert(EvmStorageSlot::new(value));
608
609                (value, true)
610            }
611        };
612
613        if is_cold {
614            // add it to journal as cold loaded.
615            self.journal.push(ENTRY::storage_warmed(address, key));
616        }
617
618        Ok(StateLoad::new(value, is_cold))
619    }
620
621    /// Stores storage slot.
622    ///
623    /// And returns (original,present,new) slot value.
624    ///
625    /// **Note**: Account should already be present in our state.
626    #[inline]
627    pub fn sstore<DB: Database>(
628        &mut self,
629        db: &mut DB,
630        address: Address,
631        key: U256,
632        new: U256,
633    ) -> Result<StateLoad<SStoreResult>, DB::Error> {
634        // assume that acc exists and load the slot.
635        let present = self.sload(db, address, key)?;
636        let acc = self.state.get_mut(&address).unwrap();
637
638        // if there is no original value in dirty return present value, that is our original.
639        let slot = acc.storage.get_mut(&key).unwrap();
640
641        // new value is same as present, we don't need to do anything
642        if present.data == new {
643            return Ok(StateLoad::new(
644                SStoreResult {
645                    original_value: slot.original_value(),
646                    present_value: present.data,
647                    new_value: new,
648                },
649                present.is_cold,
650            ));
651        }
652
653        self.journal
654            .push(ENTRY::storage_changed(address, key, present.data));
655        // insert value into present state.
656        slot.present_value = new;
657        Ok(StateLoad::new(
658            SStoreResult {
659                original_value: slot.original_value(),
660                present_value: present.data,
661                new_value: new,
662            },
663            present.is_cold,
664        ))
665    }
666
667    /// Read transient storage tied to the account.
668    ///
669    /// EIP-1153: Transient storage opcodes
670    #[inline]
671    pub fn tload(&mut self, address: Address, key: U256) -> U256 {
672        self.transient_storage
673            .get(&(address, key))
674            .copied()
675            .unwrap_or_default()
676    }
677
678    /// Store transient storage tied to the account.
679    ///
680    /// If values is different add entry to the journal
681    /// so that old state can be reverted if that action is needed.
682    ///
683    /// EIP-1153: Transient storage opcodes
684    #[inline]
685    pub fn tstore(&mut self, address: Address, key: U256, new: U256) {
686        let had_value = if new.is_zero() {
687            // if new values is zero, remove entry from transient storage.
688            // if previous values was some insert it inside journal.
689            // If it is none nothing should be inserted.
690            self.transient_storage.remove(&(address, key))
691        } else {
692            // insert values
693            let previous_value = self
694                .transient_storage
695                .insert((address, key), new)
696                .unwrap_or_default();
697
698            // check if previous value is same
699            if previous_value != new {
700                // if it is different, insert previous values inside journal.
701                Some(previous_value)
702            } else {
703                None
704            }
705        };
706
707        if let Some(had_value) = had_value {
708            // insert in journal only if value was changed.
709            self.journal
710                .push(ENTRY::transient_storage_changed(address, key, had_value));
711        }
712    }
713
714    /// Pushes log into subroutine.
715    #[inline]
716    pub fn log(&mut self, log: Log) {
717        self.logs.push(log);
718    }
719}