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