revm_context/journal/
inner.rs

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