revm_context/journal/
inner.rs

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