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::{self, *},
12    hash_map::Entry,
13    Address, HashMap, HashSet, Log, StorageKey, StorageValue, 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    ///
180    /// In case of EIP-7702 code with zero address, the bytecode will be erased.
181    #[inline]
182    pub fn set_code(&mut self, address: Address, code: Bytecode) {
183        if let Bytecode::Eip7702(eip7702_bytecode) = &code {
184            if eip7702_bytecode.address().is_zero() {
185                self.set_code_with_hash(address, Bytecode::default(), KECCAK_EMPTY);
186                return;
187            }
188        }
189
190        let hash = code.hash_slow();
191        self.set_code_with_hash(address, code, hash)
192    }
193
194    /// Increments the nonce of the account.
195    ///
196    /// # Returns
197    ///
198    /// Returns the new nonce if it did not overflow, otherwise returns `None`.
199    #[inline]
200    pub fn inc_nonce(&mut self, address: Address) -> Option<u64> {
201        let account = self.state.get_mut(&address).unwrap();
202        // Check if nonce is going to overflow.
203        if account.info.nonce == u64::MAX {
204            return None;
205        }
206        Self::touch_account(&mut self.journal, address, account);
207        self.journal.push(ENTRY::nonce_changed(address));
208
209        account.info.nonce += 1;
210
211        Some(account.info.nonce)
212    }
213
214    /// Transfers balance from two accounts. Returns error if sender balance is not enough.
215    #[inline]
216    pub fn transfer<DB: Database>(
217        &mut self,
218        db: &mut DB,
219        from: Address,
220        to: Address,
221        balance: U256,
222    ) -> Result<Option<TransferError>, DB::Error> {
223        if balance.is_zero() {
224            self.load_account(db, to)?;
225            let to_account = self.state.get_mut(&to).unwrap();
226            Self::touch_account(&mut self.journal, to, to_account);
227            return Ok(None);
228        }
229        // load accounts
230        self.load_account(db, from)?;
231        self.load_account(db, to)?;
232
233        // sub balance from
234        let from_account = self.state.get_mut(&from).unwrap();
235        Self::touch_account(&mut self.journal, from, from_account);
236        let from_balance = &mut from_account.info.balance;
237
238        let Some(from_balance_decr) = from_balance.checked_sub(balance) else {
239            return Ok(Some(TransferError::OutOfFunds));
240        };
241        *from_balance = from_balance_decr;
242
243        // add balance to
244        let to_account = &mut self.state.get_mut(&to).unwrap();
245        Self::touch_account(&mut self.journal, to, to_account);
246        let to_balance = &mut to_account.info.balance;
247        let Some(to_balance_incr) = to_balance.checked_add(balance) else {
248            return Ok(Some(TransferError::OverflowPayment));
249        };
250        *to_balance = to_balance_incr;
251        // Overflow of U256 balance is not possible to happen on mainnet. We don't bother to return funds from from_acc.
252
253        self.journal
254            .push(ENTRY::balance_transfer(from, to, balance));
255
256        Ok(None)
257    }
258
259    /// Creates account or returns false if collision is detected.
260    ///
261    /// There are few steps done:
262    /// 1. Make created account warm loaded (AccessList) and this should
263    ///    be done before subroutine checkpoint is created.
264    /// 2. Check if there is collision of newly created account with existing one.
265    /// 3. Mark created account as created.
266    /// 4. Add fund to created account
267    /// 5. Increment nonce of created account if SpuriousDragon is active
268    /// 6. Decrease balance of caller account.
269    ///
270    /// # Panics
271    ///
272    /// Panics if the caller is not loaded inside the EVM state.
273    /// This should have been done inside `create_inner`.
274    #[inline]
275    pub fn create_account_checkpoint(
276        &mut self,
277        caller: Address,
278        target_address: Address,
279        balance: U256,
280        spec_id: SpecId,
281    ) -> Result<JournalCheckpoint, TransferError> {
282        // Enter subroutine
283        let checkpoint = self.checkpoint();
284
285        // Fetch balance of caller.
286        let caller_balance = self.state.get(&caller).unwrap().info.balance;
287        // Check if caller has enough balance to send to the created contract.
288        if caller_balance < balance {
289            self.checkpoint_revert(checkpoint);
290            return Err(TransferError::OutOfFunds);
291        }
292
293        // Newly created account is present, as we just loaded it.
294        let target_acc = self.state.get_mut(&target_address).unwrap();
295        let last_journal = &mut self.journal;
296
297        // New account can be created if:
298        // Bytecode is not empty.
299        // Nonce is not zero
300        // Account is not precompile.
301        if target_acc.info.code_hash != KECCAK_EMPTY || target_acc.info.nonce != 0 {
302            self.checkpoint_revert(checkpoint);
303            return Err(TransferError::CreateCollision);
304        }
305
306        // set account status to create.
307        target_acc.mark_created();
308
309        // this entry will revert set nonce.
310        last_journal.push(ENTRY::account_created(target_address));
311        target_acc.info.code = None;
312        // EIP-161: State trie clearing (invariant-preserving alternative)
313        if spec_id.is_enabled_in(SPURIOUS_DRAGON) {
314            // nonce is going to be reset to zero in AccountCreated journal entry.
315            target_acc.info.nonce = 1;
316        }
317
318        // touch account. This is important as for pre SpuriousDragon account could be
319        // saved even empty.
320        Self::touch_account(last_journal, target_address, target_acc);
321
322        // Add balance to created account, as we already have target here.
323        let Some(new_balance) = target_acc.info.balance.checked_add(balance) else {
324            self.checkpoint_revert(checkpoint);
325            return Err(TransferError::OverflowPayment);
326        };
327        target_acc.info.balance = new_balance;
328
329        // safe to decrement for the caller as balance check is already done.
330        self.state.get_mut(&caller).unwrap().info.balance -= balance;
331
332        // add journal entry of transferred balance
333        last_journal.push(ENTRY::balance_transfer(caller, target_address, balance));
334
335        Ok(checkpoint)
336    }
337
338    /// Makes a checkpoint that in case of Revert can bring back state to this point.
339    #[inline]
340    pub fn checkpoint(&mut self) -> JournalCheckpoint {
341        let checkpoint = JournalCheckpoint {
342            log_i: self.logs.len(),
343            journal_i: self.journal.len(),
344        };
345        self.depth += 1;
346        checkpoint
347    }
348
349    /// Commits the checkpoint.
350    #[inline]
351    pub fn checkpoint_commit(&mut self) {
352        self.depth -= 1;
353    }
354
355    /// Reverts all changes to state until given checkpoint.
356    #[inline]
357    pub fn checkpoint_revert(&mut self, checkpoint: JournalCheckpoint) {
358        let is_spurious_dragon_enabled = self.spec.is_enabled_in(SPURIOUS_DRAGON);
359        let state = &mut self.state;
360        let transient_storage = &mut self.transient_storage;
361        self.depth -= 1;
362        self.logs.truncate(checkpoint.log_i);
363
364        // iterate over last N journals sets and revert our global state
365        self.journal
366            .drain(checkpoint.journal_i..)
367            .rev()
368            .for_each(|entry| {
369                entry.revert(state, transient_storage, is_spurious_dragon_enabled);
370            });
371    }
372
373    /// Performs selfdestruct action.
374    /// Transfers balance from address to target. Check if target exist/is_cold
375    ///
376    /// Note: Balance will be lost if address and target are the same BUT when
377    /// current spec enables Cancun, this happens only when the account associated to address
378    /// is created in the same tx
379    ///
380    /// # References:
381    ///  * <https://github.com/ethereum/go-ethereum/blob/141cd425310b503c5678e674a8c3872cf46b7086/core/vm/instructions.go#L832-L833>
382    ///  * <https://github.com/ethereum/go-ethereum/blob/141cd425310b503c5678e674a8c3872cf46b7086/core/state/statedb.go#L449>
383    ///  * <https://eips.ethereum.org/EIPS/eip-6780>
384    #[inline]
385    pub fn selfdestruct<DB: Database>(
386        &mut self,
387        db: &mut DB,
388        address: Address,
389        target: Address,
390    ) -> Result<StateLoad<SelfDestructResult>, DB::Error> {
391        let spec = self.spec;
392        let account_load = self.load_account(db, target)?;
393        let is_cold = account_load.is_cold;
394        let is_empty = account_load.state_clear_aware_is_empty(spec);
395
396        if address != target {
397            // Both accounts are loaded before this point, `address` as we execute its contract.
398            // and `target` at the beginning of the function.
399            let acc_balance = self.state.get(&address).unwrap().info.balance;
400
401            let target_account = self.state.get_mut(&target).unwrap();
402            Self::touch_account(&mut self.journal, target, target_account);
403            target_account.info.balance += acc_balance;
404        }
405
406        let acc = self.state.get_mut(&address).unwrap();
407        let balance = acc.info.balance;
408        let previously_destroyed = acc.is_selfdestructed();
409        let is_cancun_enabled = spec.is_enabled_in(CANCUN);
410
411        // EIP-6780 (Cancun hard-fork): selfdestruct only if contract is created in the same tx
412        let journal_entry = if acc.is_created() || !is_cancun_enabled {
413            acc.mark_selfdestruct();
414            acc.info.balance = U256::ZERO;
415            Some(ENTRY::account_destroyed(
416                address,
417                target,
418                previously_destroyed,
419                balance,
420            ))
421        } else if address != target {
422            acc.info.balance = U256::ZERO;
423            Some(ENTRY::balance_transfer(address, target, balance))
424        } else {
425            // State is not changed:
426            // * if we are after Cancun upgrade and
427            // * Selfdestruct account that is created in the same transaction and
428            // * Specify the target is same as selfdestructed account. The balance stays unchanged.
429            None
430        };
431
432        if let Some(entry) = journal_entry {
433            self.journal.push(entry);
434        };
435
436        Ok(StateLoad {
437            data: SelfDestructResult {
438                had_value: !balance.is_zero(),
439                target_exists: !is_empty,
440                previously_destroyed,
441            },
442            is_cold,
443        })
444    }
445
446    /// Initial load of account. This load will not be tracked inside journal
447    #[inline]
448    pub fn initial_account_load<DB: Database>(
449        &mut self,
450        db: &mut DB,
451        address: Address,
452        storage_keys: impl IntoIterator<Item = StorageKey>,
453    ) -> Result<&mut Account, DB::Error> {
454        // load or get account.
455        let account = match self.state.entry(address) {
456            Entry::Occupied(entry) => entry.into_mut(),
457            Entry::Vacant(vac) => vac.insert(
458                db.basic(address)?
459                    .map(|i| i.into())
460                    .unwrap_or(Account::new_not_existing()),
461            ),
462        };
463        // preload storages.
464        for storage_key in storage_keys.into_iter() {
465            if let Entry::Vacant(entry) = account.storage.entry(storage_key) {
466                let storage = db.storage(address, storage_key)?;
467                entry.insert(EvmStorageSlot::new(storage));
468            }
469        }
470        Ok(account)
471    }
472
473    /// Loads account into memory. return if it is cold or warm accessed
474    #[inline]
475    pub fn load_account<DB: Database>(
476        &mut self,
477        db: &mut DB,
478        address: Address,
479    ) -> Result<StateLoad<&mut Account>, DB::Error> {
480        self.load_account_optional(db, address, false)
481    }
482
483    /// Loads account into memory. If account is EIP-7702 type it will additionally
484    /// load delegated account.
485    ///
486    /// It will mark both this and delegated account as warm loaded.
487    ///
488    /// Returns information about the account (If it is empty or cold loaded) and if present the information
489    /// about the delegated account (If it is cold loaded).
490    #[inline]
491    pub fn load_account_delegated<DB: Database>(
492        &mut self,
493        db: &mut DB,
494        address: Address,
495    ) -> Result<StateLoad<AccountLoad>, DB::Error> {
496        let spec = self.spec;
497        let is_eip7702_enabled = spec.is_enabled_in(SpecId::PRAGUE);
498        let account = self.load_account_optional(db, address, is_eip7702_enabled)?;
499        let is_empty = account.state_clear_aware_is_empty(spec);
500
501        let mut account_load = StateLoad::new(
502            AccountLoad {
503                is_delegate_account_cold: None,
504                is_empty,
505            },
506            account.is_cold,
507        );
508
509        // load delegate code if account is EIP-7702
510        if let Some(Bytecode::Eip7702(code)) = &account.info.code {
511            let address = code.address();
512            let delegate_account = self.load_account(db, address)?;
513            account_load.data.is_delegate_account_cold = Some(delegate_account.is_cold);
514        }
515
516        Ok(account_load)
517    }
518
519    /// Loads account and its code. If account is already loaded it will load its code.
520    ///
521    /// It will mark account as warm loaded. If not existing Database will be queried for data.
522    ///
523    /// In case of EIP-7702 delegated account will not be loaded,
524    /// [`Self::load_account_delegated`] should be used instead.
525    #[inline]
526    pub fn load_code<DB: Database>(
527        &mut self,
528        db: &mut DB,
529        address: Address,
530    ) -> Result<StateLoad<&mut Account>, DB::Error> {
531        self.load_account_optional(db, address, true)
532    }
533
534    /// Loads account. If account is already loaded it will be marked as warm.
535    #[inline]
536    pub fn load_account_optional<DB: Database>(
537        &mut self,
538        db: &mut DB,
539        address: Address,
540        load_code: bool,
541    ) -> Result<StateLoad<&mut Account>, DB::Error> {
542        let load = match self.state.entry(address) {
543            Entry::Occupied(entry) => {
544                let account = entry.into_mut();
545                let is_cold = account.mark_warm();
546                StateLoad {
547                    data: account,
548                    is_cold,
549                }
550            }
551            Entry::Vacant(vac) => {
552                let account = if let Some(account) = db.basic(address)? {
553                    account.into()
554                } else {
555                    Account::new_not_existing()
556                };
557
558                // Precompiles among some other account are warm loaded so we need to take that into account
559                let is_cold = !self.warm_preloaded_addresses.contains(&address);
560
561                StateLoad {
562                    data: vac.insert(account),
563                    is_cold,
564                }
565            }
566        };
567        // journal loading of cold account.
568        if load.is_cold {
569            self.journal.push(ENTRY::account_warmed(address));
570        }
571        if load_code {
572            let info = &mut load.data.info;
573            if info.code.is_none() {
574                let code = if info.code_hash == KECCAK_EMPTY {
575                    Bytecode::default()
576                } else {
577                    db.code_by_hash(info.code_hash)?
578                };
579                info.code = Some(code);
580            }
581        }
582
583        Ok(load)
584    }
585
586    /// Loads storage slot.
587    ///
588    /// # Panics
589    ///
590    /// Panics if the account is not present in the state.
591    #[inline]
592    pub fn sload<DB: Database>(
593        &mut self,
594        db: &mut DB,
595        address: Address,
596        key: StorageKey,
597    ) -> Result<StateLoad<StorageValue>, DB::Error> {
598        // assume acc is warm
599        let account = self.state.get_mut(&address).unwrap();
600        // only if account is created in this tx we can assume that storage is empty.
601        let is_newly_created = account.is_created();
602        let (value, is_cold) = match account.storage.entry(key) {
603            Entry::Occupied(occ) => {
604                let slot = occ.into_mut();
605                let is_cold = slot.mark_warm();
606                (slot.present_value, is_cold)
607            }
608            Entry::Vacant(vac) => {
609                // if storage was cleared, we don't need to ping db.
610                let value = if is_newly_created {
611                    StorageValue::ZERO
612                } else {
613                    db.storage(address, key)?
614                };
615
616                vac.insert(EvmStorageSlot::new(value));
617
618                (value, true)
619            }
620        };
621
622        if is_cold {
623            // add it to journal as cold loaded.
624            self.journal.push(ENTRY::storage_warmed(address, key));
625        }
626
627        Ok(StateLoad::new(value, is_cold))
628    }
629
630    /// Stores storage slot.
631    ///
632    /// And returns (original,present,new) slot value.
633    ///
634    /// **Note**: Account should already be present in our state.
635    #[inline]
636    pub fn sstore<DB: Database>(
637        &mut self,
638        db: &mut DB,
639        address: Address,
640        key: StorageKey,
641        new: StorageValue,
642    ) -> Result<StateLoad<SStoreResult>, DB::Error> {
643        // assume that acc exists and load the slot.
644        let present = self.sload(db, address, key)?;
645        let acc = self.state.get_mut(&address).unwrap();
646
647        // if there is no original value in dirty return present value, that is our original.
648        let slot = acc.storage.get_mut(&key).unwrap();
649
650        // new value is same as present, we don't need to do anything
651        if present.data == new {
652            return Ok(StateLoad::new(
653                SStoreResult {
654                    original_value: slot.original_value(),
655                    present_value: present.data,
656                    new_value: new,
657                },
658                present.is_cold,
659            ));
660        }
661
662        self.journal
663            .push(ENTRY::storage_changed(address, key, present.data));
664        // insert value into present state.
665        slot.present_value = new;
666        Ok(StateLoad::new(
667            SStoreResult {
668                original_value: slot.original_value(),
669                present_value: present.data,
670                new_value: new,
671            },
672            present.is_cold,
673        ))
674    }
675
676    /// Read transient storage tied to the account.
677    ///
678    /// EIP-1153: Transient storage opcodes
679    #[inline]
680    pub fn tload(&mut self, address: Address, key: StorageKey) -> StorageValue {
681        self.transient_storage
682            .get(&(address, key))
683            .copied()
684            .unwrap_or_default()
685    }
686
687    /// Store transient storage tied to the account.
688    ///
689    /// If values is different add entry to the journal
690    /// so that old state can be reverted if that action is needed.
691    ///
692    /// EIP-1153: Transient storage opcodes
693    #[inline]
694    pub fn tstore(&mut self, address: Address, key: StorageKey, new: StorageValue) {
695        let had_value = if new.is_zero() {
696            // if new values is zero, remove entry from transient storage.
697            // if previous values was some insert it inside journal.
698            // If it is none nothing should be inserted.
699            self.transient_storage.remove(&(address, key))
700        } else {
701            // insert values
702            let previous_value = self
703                .transient_storage
704                .insert((address, key), new)
705                .unwrap_or_default();
706
707            // check if previous value is same
708            if previous_value != new {
709                // if it is different, insert previous values inside journal.
710                Some(previous_value)
711            } else {
712                None
713            }
714        };
715
716        if let Some(had_value) = had_value {
717            // insert in journal only if value was changed.
718            self.journal
719                .push(ENTRY::transient_storage_changed(address, key, had_value));
720        }
721    }
722
723    /// Pushes log into subroutine.
724    #[inline]
725    pub fn log(&mut self, log: Log) {
726        self.logs.push(log);
727    }
728}