revm_context/journal/
inner.rs

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