revm_context/journal/
inner.rs

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