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