Skip to main content

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