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