Skip to main content

foundry_evm_core/evm/
mod.rs

1//! Shared EVM traits, associated types, and execution helpers.
2//!
3//! Each network module owns its network marker and concrete EVM implementations.
4
5use crate::{
6    FoundryBlock, FoundryChain, FoundryContextExt, FoundryInspectorExt, FoundryJournal,
7    FoundryTransaction, FromAnyRpcTransaction,
8    backend::{DatabaseExt, JournaledState},
9};
10use alloy_consensus::{SignableTransaction, Signed, transaction::SignerRecoverable};
11use alloy_evm::{Evm, EvmEnv, EvmFactory, FromRecoveredTx, precompiles::PrecompilesMap};
12use alloy_network::Network;
13use alloy_primitives::{Address, Signature, U256};
14use alloy_rlp::Decodable;
15use foundry_common::{FoundryReceiptResponse, FoundryTransactionBuilder, fmt::UIfmt};
16use foundry_config::ExecutionSpec;
17use foundry_fork_db::{DatabaseError, ForkBlockEnv};
18use revm::{
19    Database,
20    context::{
21        ContextTr, JournalTr, LocalContextTr,
22        result::{EVMError, HaltReason, ResultAndState},
23    },
24    handler::{EvmTr, FrameResult},
25    inspector::{InspectorEvmTr, InspectorHandler, NoOpInspector},
26    interpreter::{
27        CallInput, CallInputs, CallScheme, CallValue, CreateInputs, FrameInput, GasTracker,
28        InstructionResult, SharedMemory, interpreter::EthInterpreter,
29        interpreter_action::FrameInit,
30    },
31    primitives::hardfork::SpecId,
32    state::{AccountStatus, EvmState},
33};
34use serde::{Deserialize, Serialize};
35use std::{fmt::Debug, ops::DerefMut};
36
37#[cfg(feature = "base")]
38pub mod base;
39pub mod eth;
40#[cfg(feature = "monad")]
41pub mod monad;
42#[cfg(feature = "optimism")]
43pub mod op;
44pub mod tempo;
45
46pub use eth::*;
47pub use tempo::*;
48
49#[cfg(feature = "base")]
50pub use base::*;
51
52#[cfg(feature = "monad")]
53pub use monad::*;
54
55#[cfg(feature = "optimism")]
56pub use op::*;
57
58/// Foundry's compatibility trait associating a [`Network`] with a [`FoundryEvmFactory`].
59pub trait FoundryEvmNetwork: Copy + Debug + Default + 'static {
60    type Network: Network<
61            TxEnvelope: Decodable
62                            + SignerRecoverable
63                            + From<Signed<<Self::Network as Network>::UnsignedTx>>
64                            + for<'d> Deserialize<'d>
65                            + Serialize
66                            + UIfmt,
67            UnsignedTx: SignableTransaction<Signature>,
68            TransactionRequest: FoundryTransactionBuilder<Self::Network>
69                                    + for<'d> Deserialize<'d>
70                                    + Serialize,
71            ReceiptResponse: FoundryReceiptResponse,
72        >;
73    type EvmFactory: FoundryEvmFactory<Tx: FromRecoveredTx<<Self::Network as Network>::TxEnvelope>>;
74}
75
76pub trait FoundryEvmFactory:
77    EvmFactory<
78        Spec: Into<SpecId> + ExecutionSpec + Default + Copy + Unpin + Send + 'static,
79        BlockEnv: FoundryBlock + ForkBlockEnv + Default + Unpin,
80        Tx: Clone + Debug + FoundryTransaction + FromAnyRpcTransaction + Default + Send + Sync,
81        HaltReason: IntoInstructionResult,
82        Precompiles = PrecompilesMap,
83    > + Clone
84    + Debug
85    + Default
86    + 'static
87{
88    /// Chain type for EVM's context created by this factory.
89    type Chain: FoundryChain<Self::Tx>;
90
91    /// Foundry Context abstraction
92    type FoundryContext<'db>: FoundryContextExt<
93            Block = Self::BlockEnv,
94            Tx = Self::Tx,
95            Spec = Self::Spec,
96            Chain = Self::Chain,
97            Journal: FoundryJournal,
98            Db: DatabaseExt<Self>,
99        >
100    where
101        Self: 'db;
102
103    /// The Foundry-wrapped EVM type produced by this factory.
104    type FoundryEvm<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>>: Evm<
105            DB = &'db mut dyn DatabaseExt<Self>,
106            Tx = Self::Tx,
107            BlockEnv = Self::BlockEnv,
108            Spec = Self::Spec,
109            HaltReason = Self::HaltReason,
110            Precompiles = PrecompilesMap,
111        > + DerefMut<Target = Self::FoundryContext<'db>>
112    where
113        Self: 'db;
114
115    /// Creates a Foundry-wrapped EVM with the given inspector.
116    ///
117    /// Callers carrying execution context must install it through the returned context's
118    /// `chain_mut` before executing. This also preserves OP's block-derived L1 fee information.
119    fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>>(
120        &self,
121        db: &'db mut dyn DatabaseExt<Self>,
122        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
123        inspector: I,
124    ) -> Self::FoundryEvm<'db, I>;
125
126    /// Creates a Foundry-wrapped nested EVM without an inspector.
127    fn create_nested_evm<'db>(
128        &self,
129        db: &'db mut dyn DatabaseExt<Self>,
130        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
131    ) -> NestedEvmFor<'db, Self> {
132        self.create_nested_evm_with_inspector(db, evm_env, NoOpInspector)
133    }
134
135    /// Creates a Foundry-wrapped nested EVM with the given inspector.
136    /// Install inherited chain state with [`NestedEvm::chain_mut`] before executing or restoring
137    /// journal-derived state.
138    fn create_nested_evm_with_inspector<'db, I>(
139        &self,
140        db: &'db mut dyn DatabaseExt<Self>,
141        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
142        inspector: I,
143    ) -> NestedEvmFor<'db, Self>
144    where
145        I: FoundryInspectorExt<Self::FoundryContext<'db>> + 'db;
146
147    /// Updates the execution spec and gas parameters using this family's configuration.
148    /// This does not reconstruct the instruction table or precompile registry.
149    fn set_execution_spec(context: &mut Self::FoundryContext<'_>, spec: Self::Spec) {
150        context.set_spec_and_gas_params(spec);
151    }
152}
153
154/// Object-safe EVM operations used by nested execution and fork replay.
155///
156/// This abstracts over the concrete EVM type (`FoundryEvm`, future `TempoEvm`, etc.)
157/// so that cheatcode impls can build and run nested EVMs without knowing the concrete type.
158pub trait NestedEvm {
159    /// The spec type.
160    type Spec;
161    /// The block environment type.
162    type Block;
163    /// The transaction environment type.
164    type Tx: FoundryTransaction;
165    /// Chain context identifying the active transaction position.
166    type Chain: FoundryChain<Self::Tx>;
167    /// The Journal type, which may own Monad's reserve-balance-tracker state.
168    type Journal: FoundryJournal;
169    /// Returns a mutable reference to the journal inner state (`JournaledState`).
170    fn journal_inner_mut(&mut self) -> &mut JournaledState;
171
172    /// Returns a mutable reference to the transaction environment.
173    fn tx_mut(&mut self) -> &mut Self::Tx;
174
175    /// Returns a mutable reference to the chain-position context.
176    fn chain_mut(&mut self) -> &mut Self::Chain;
177
178    /// Returns the precompile map.
179    fn precompiles_mut(&mut self) -> &mut PrecompilesMap;
180
181    /// Returns a mutable reference to the Journal.
182    fn journal_mut(&mut self) -> &mut Self::Journal;
183
184    /// Runs a single execution frame (create or call) through the EVM handler loop.
185    fn run_execution(&mut self, frame: FrameInput) -> Result<FrameResult, EVMError<DatabaseError>>;
186
187    /// Executes a full transaction with the given tx env.
188    fn transact_raw(&mut self, tx: Self::Tx) -> eyre::Result<ResultAndState<HaltReason>>;
189
190    /// Replays a transaction, skipping unsupported system envelopes.
191    ///
192    /// `is_system` preserves the RPC envelope classification that conversion to `Self::Tx` may
193    /// discard. Returning `None` must not mutate the EVM, database, or inspector.
194    fn transact_replay(
195        &mut self,
196        tx: Self::Tx,
197        is_system: bool,
198    ) -> eyre::Result<Option<ResultAndState<HaltReason>>> {
199        if is_system && !tx.is_deposit() {
200            return Ok(None);
201        }
202        self.transact_raw(tx).map(Some)
203    }
204
205    fn to_evm_env(&self) -> EvmEnv<Self::Spec, Self::Block>;
206}
207
208/// Converts a network-specific halt reason into an [`InstructionResult`].
209pub trait IntoInstructionResult {
210    fn into_instruction_result(self) -> InstructionResult;
211}
212
213/// Convenience type aliases for accessing associated types through [`FoundryEvmNetwork`].
214pub type EvmFactoryFor<FEN> = <FEN as FoundryEvmNetwork>::EvmFactory;
215pub type FoundryContextFor<'db, FEN> =
216    <EvmFactoryFor<FEN> as FoundryEvmFactory>::FoundryContext<'db>;
217pub type TxEnvFor<FEN> = <EvmFactoryFor<FEN> as EvmFactory>::Tx;
218pub type HaltReasonFor<FEN> = <EvmFactoryFor<FEN> as EvmFactory>::HaltReason;
219pub type SpecFor<FEN> = <EvmFactoryFor<FEN> as EvmFactory>::Spec;
220pub type BlockEnvFor<FEN> = <EvmFactoryFor<FEN> as EvmFactory>::BlockEnv;
221pub type PrecompilesFor<FEN> = <EvmFactoryFor<FEN> as EvmFactory>::Precompiles;
222pub type EvmEnvFor<FEN> = EvmEnv<SpecFor<FEN>, BlockEnvFor<FEN>>;
223pub type NetworkFor<FEN> = <FEN as FoundryEvmNetwork>::Network;
224pub type TxEnvelopeFor<FEN> = <NetworkFor<FEN> as Network>::TxEnvelope;
225pub type TransactionRequestFor<FEN> = <NetworkFor<FEN> as Network>::TransactionRequest;
226pub type TransactionResponseFor<FEN> = <NetworkFor<FEN> as Network>::TransactionResponse;
227pub type BlockResponseFor<FEN> = <NetworkFor<FEN> as Network>::BlockResponse;
228
229pub type ChainFor<FEN> = <EvmFactoryFor<FEN> as FoundryEvmFactory>::Chain;
230
231/// Boxed nested EVM produced by a Foundry EVM factory.
232pub type NestedEvmFor<'db, F> = Box<
233    dyn NestedEvm<
234            Spec = <F as EvmFactory>::Spec,
235            Block = <F as EvmFactory>::BlockEnv,
236            Tx = <F as EvmFactory>::Tx,
237            Chain = <F as FoundryEvmFactory>::Chain,
238            Journal = <<F as FoundryEvmFactory>::FoundryContext<'db> as ContextTr>::Journal,
239        > + 'db,
240>;
241
242/// Closure type used by `CheatcodesExecutor` methods that run nested EVM operations.
243pub type NestedEvmClosure<'a, F> = &'a mut dyn for<'j> FnMut(
244    &mut dyn NestedEvm<
245        Spec = <F as EvmFactory>::Spec,
246        Block = <F as EvmFactory>::BlockEnv,
247        Tx = <F as EvmFactory>::Tx,
248        Chain = <F as FoundryEvmFactory>::Chain,
249        Journal = <<F as FoundryEvmFactory>::FoundryContext<'j> as ContextTr>::Journal,
250    >,
251)
252    -> Result<(), EVMError<DatabaseError>>;
253
254/// Nested EVM closure for a Foundry EVM network.
255pub type NestedEvmClosureFor<'a, FEN> = NestedEvmClosure<'a, EvmFactoryFor<FEN>>;
256
257/// Runs a child operation with the parent's environment, journal, and native chain state.
258///
259/// Publishes child environment, journal, and chain changes only when the operation returns `Ok`.
260/// This does not roll back database or inspector effects: callers retain their existing ownership
261/// of those effects. The outer transaction environment is not replaced.
262///
263/// Both inspector adapters use this operation so inheritance and write-back remain paired. The
264/// existing Monad journal bridge is retained here until native journal lifecycle ownership
265/// migrates.
266pub fn with_inherited_evm<F, I>(
267    ecx: &mut F::FoundryContext<'_>,
268    inspector: I,
269    f: NestedEvmClosure<'_, F>,
270) -> Result<(), EVMError<DatabaseError>>
271where
272    F: FoundryEvmFactory,
273    I: for<'db> FoundryInspectorExt<F::FoundryContext<'db>>,
274{
275    let evm_env = ecx.evm_clone();
276    let chain_context = ecx.chain().clone();
277    #[cfg(feature = "monad")]
278    let mut reserve_balance = FoundryJournal::capture_reserve_balance(ecx.journal());
279    let (evm_env, journaled_state, chain_context) = {
280        let (db, journaled_state) = ecx.db_journal_inner_mut();
281        let journaled_state = journaled_state.clone();
282        let mut evm = F::default().create_nested_evm_with_inspector(db, evm_env, inspector);
283        *evm.chain_mut() = chain_context;
284        *evm.journal_inner_mut() = journaled_state;
285        #[cfg(feature = "monad")]
286        {
287            FoundryJournal::restore_reserve_balance(evm.journal_mut(), reserve_balance);
288            refresh_nested_chain_journal(&mut *evm);
289        }
290        f(&mut *evm)?;
291        #[cfg(feature = "monad")]
292        {
293            reserve_balance = FoundryJournal::capture_reserve_balance(evm.journal_mut());
294        }
295        (evm.to_evm_env(), evm.journal_inner_mut().clone(), evm.chain_mut().clone())
296    };
297    ecx.set_journal_inner(journaled_state);
298    ecx.set_evm(evm_env);
299    *ecx.chain_mut() = chain_context;
300    #[cfg(feature = "monad")]
301    {
302        FoundryJournal::restore_reserve_balance(ecx.journal_mut(), reserve_balance);
303        refresh_chain_journal(ecx);
304    }
305    Ok(())
306}
307
308/// Prepares account state for execution across a synthetic transaction boundary.
309///
310/// Preserve account flags, including local creation, while making accounts outside the protocol
311/// warm-address set cold. All storage starts cold with its current value as the child's original
312/// value. The parent's state is unchanged.
313pub fn prepare_child_state(journal: &JournaledState) -> EvmState {
314    let mut state = journal.state.clone();
315    for (address, account) in &mut state {
316        if journal.warm_addresses.is_cold(address) {
317            account.mark_cold();
318        }
319        for slot in account.storage.values_mut() {
320            slot.is_cold = true;
321            slot.original_value = slot.present_value();
322        }
323    }
324    state
325}
326
327/// Merges a child's returned account state into its suspended parent.
328///
329/// Preserve parent warmth and original storage values, import child account flags and current
330/// values, and optionally remove parent accounts and slots absent from the child. Newly loaded
331/// accounts and slots keep their child metadata. This operates on the EVM's returned state, not on
332/// an unfiltered write set; the caller retains responsibility for execution errors and
333/// family-specific reconciliation.
334pub fn merge_child_state(parent: &mut EvmState, child: EvmState, remove_absent: bool) {
335    if remove_absent {
336        parent.retain(|address, parent_account| {
337            let Some(child_account) = child.get(address) else { return false };
338            parent_account.storage.retain(|key, _| child_account.storage.contains_key(key));
339            true
340        });
341    }
342
343    for (address, mut account) in child {
344        let Some(parent_account) = parent.get_mut(&address) else {
345            parent.insert(address, account);
346            continue;
347        };
348        if account.status.contains(AccountStatus::Cold)
349            && !parent_account.status.contains(AccountStatus::Cold)
350        {
351            account.status -= AccountStatus::Cold;
352        }
353        parent_account.info = account.info;
354        parent_account.status |= account.status;
355        for (key, slot) in account.storage {
356            let Some(parent_slot) = parent_account.storage.get_mut(&key) else {
357                parent_account.storage.insert(key, slot);
358                continue;
359            };
360            parent_slot.present_value = slot.present_value();
361            parent_slot.is_cold &= slot.is_cold;
362        }
363    }
364}
365
366/// Runs a nested frame with inspection and settles its gas into the parent frame.
367pub(crate) fn run_inspected_frame<H>(
368    evm: &mut H::Evm,
369    mut handler: H,
370    frame_input: FrameInput,
371) -> Result<FrameResult, H::Error>
372where
373    H: InspectorHandler<IT = EthInterpreter>,
374    H::Evm: InspectorEvmTr,
375{
376    let memory =
377        SharedMemory::new_with_buffer(evm.ctx_ref().local().shared_memory_buffer().clone());
378    let first_frame_input = FrameInit { depth: 0, memory, frame_input };
379    let mut frame_result = handler.inspect_run_exec_loop(evm, first_frame_input)?;
380    let mut parent_gas = GasTracker::new(
381        frame_result.gas().limit(),
382        frame_result.gas().remaining(),
383        frame_result.gas().reservoir(),
384    );
385    handler.last_frame_result(evm, &mut frame_result, &mut parent_gas)?;
386    Ok(frame_result)
387}
388
389/// Get the call inputs for the CREATE2 factory.
390pub fn get_create2_factory_call_inputs<T: JournalTr>(
391    salt: U256,
392    inputs: &CreateInputs,
393    deployer: Address,
394    journal: &mut T,
395) -> Result<CallInputs, <T::Database as Database>::Error> {
396    let calldata = [&salt.to_be_bytes::<32>()[..], &inputs.init_code()[..]].concat();
397    let account = journal.load_account_with_code(deployer)?;
398    Ok(CallInputs {
399        caller: inputs.caller(),
400        bytecode_address: deployer,
401        known_bytecode: (account.info.code_hash(), account.info.code.clone().unwrap_or_default()),
402        target_address: deployer,
403        scheme: CallScheme::Call,
404        value: CallValue::Transfer(inputs.value()),
405        input: CallInput::Bytes(calldata.into()),
406        gas_limit: inputs.gas_limit(),
407        reservoir: inputs.reservoir(),
408        is_static: false,
409        return_memory_offset: 0..0,
410        charged_new_account_state_gas: false,
411    })
412}
413
414#[cfg(test)]
415mod tests {
416    use super::*;
417    use crate::backend::Backend;
418    use alloy_evm::EthEvmFactory;
419    use revm::{
420        context::Transaction,
421        state::{Account, AccountInfo, EvmStorageSlot, TransactionId},
422    };
423
424    #[cfg(feature = "monad")]
425    use alloy_monad_evm::MonadEvmFactory;
426    #[cfg(feature = "monad")]
427    use monad_revm::{MonadHardfork, MonadJournalTr, reserve_balance::tracker::ReserveBalanceInit};
428    #[cfg(feature = "monad")]
429    use revm::context::{BlockEnv, CfgEnv};
430
431    #[test]
432    fn inherited_journal_publishes_only_after_success() {
433        let address = Address::with_last_byte(0x42);
434        for succeeds in [false, true] {
435            let mut db = Backend::<EthEvmNetwork>::spawn(None).unwrap();
436            let mut parent = EthEvmFactory::default().create_foundry_evm_with_inspector(
437                &mut db,
438                EvmEnv::default(),
439                NoOpInspector,
440            );
441            parent.journaled_state.inner.depth = 3;
442            parent
443                .journaled_state
444                .inner
445                .state
446                .insert(address, Account::from(AccountInfo::from_balance(U256::from(7))));
447            let caller = parent.tx().caller();
448            let result =
449                with_inherited_evm::<EthEvmFactory, _>(&mut parent, NoOpInspector, &mut |child| {
450                    assert_eq!(child.journal_inner_mut().depth, 3);
451                    assert_eq!(
452                        child.journal_inner_mut().state[&address].info.balance,
453                        U256::from(7)
454                    );
455                    child.journal_inner_mut().depth = 4;
456                    child.journal_inner_mut().state.get_mut(&address).unwrap().info.balance =
457                        U256::from(9);
458                    child.tx_mut().caller = address;
459                    if succeeds { Ok(()) } else { Err(EVMError::Custom("abort child".into())) }
460                });
461            assert_eq!(result.is_ok(), succeeds);
462            assert_eq!(parent.journaled_state.inner.depth, if succeeds { 4 } else { 3 });
463            assert_eq!(
464                parent.journaled_state.inner.state[&address].info.balance,
465                U256::from(if succeeds { 9 } else { 7 })
466            );
467            assert_eq!(parent.tx().caller(), caller);
468        }
469    }
470
471    #[cfg(feature = "monad")]
472    #[test]
473    fn inherited_monad_tracker_and_chain_publish_together() {
474        let sender = Address::with_last_byte(0x42);
475        for succeeds in [false, true] {
476            let mut db = Backend::<MonadEvmNetwork>::spawn(None).unwrap();
477            let mut parent = MonadEvmFactory::default().create_foundry_evm_with_inspector(
478                &mut db,
479                EvmEnv::new(CfgEnv::new_with_spec(MonadHardfork::MonadNine), BlockEnv::default()),
480                NoOpInspector,
481            );
482            let account = Account::from(AccountInfo::from_balance(U256::from(12)));
483            let chain = parent.chain().clone();
484            parent.journaled_state.reserve_balance_mut().init(ReserveBalanceInit {
485                chain: &chain,
486                spec: MonadHardfork::MonadNine,
487                sender,
488                effective_gas_price: 0,
489                gas_limit: 0,
490                sender_is_delegated: false,
491                sender_account: Some(&account),
492            });
493            parent.journaled_state.inner.state.insert(sender, account);
494            let tracker = parent.journaled_state.reserve_balance().clone();
495            let result = with_inherited_evm::<MonadEvmFactory, _>(
496                &mut parent,
497                NoOpInspector,
498                &mut |child| {
499                    assert_eq!(child.journal_mut().reserve_balance(), &tracker);
500                    child.chain_mut().parent_senders_and_authorities.insert(sender);
501                    child.journal_inner_mut().state.get_mut(&sender).unwrap().info.balance =
502                        U256::from(9);
503                    if succeeds { Ok(()) } else { Err(EVMError::Custom("abort child".into())) }
504                },
505            );
506            assert_eq!(result.is_ok(), succeeds);
507            if succeeds {
508                assert!(parent.chain().parent_senders_and_authorities.contains(&sender));
509                assert!(parent.journaled_state.reserve_balance().has_violation());
510            } else {
511                assert_eq!(parent.chain(), &chain);
512                assert_eq!(parent.journaled_state.reserve_balance(), &tracker);
513                assert_eq!(
514                    parent.journaled_state.inner.state[&sender].info.balance,
515                    U256::from(12)
516                );
517            }
518        }
519    }
520
521    #[test]
522    fn preparation_preserves_creation_and_protocol_warmth() {
523        let address = Address::with_last_byte(0x42);
524        let protocol_address = Address::with_last_byte(0x43);
525        let key = U256::ONE;
526        let mut account = Account::from(AccountInfo::default());
527        account.mark_created_locally();
528        account.mark_touch();
529        account.storage.insert(
530            key,
531            EvmStorageSlot::new_changed(U256::from(3), U256::from(7), TransactionId::ZERO),
532        );
533        let mut journal = JournaledState::default();
534        journal.state.insert(address, account.clone());
535        journal.state.insert(protocol_address, account);
536        journal.warm_addresses.set_coinbase(protocol_address);
537        let before = journal.state.clone();
538
539        let child = prepare_child_state(&journal);
540
541        assert_eq!(journal.state, before);
542        assert!(child[&address].is_created_locally());
543        assert!(child[&address].is_touched());
544        assert!(child[&address].status.contains(AccountStatus::Cold));
545        assert!(!child[&protocol_address].status.contains(AccountStatus::Cold));
546        for account in child.values() {
547            assert_eq!(account.storage[&key].original_value, U256::from(7));
548            assert_eq!(account.storage[&key].present_value, U256::from(7));
549            assert!(account.storage[&key].is_cold);
550        }
551    }
552
553    #[test]
554    fn settlement_preserves_parent_original_values_and_combines_warmth() {
555        let address = Address::with_last_byte(0x42);
556        let key = U256::ONE;
557        for parent_cold in [false, true] {
558            for child_cold in [false, true] {
559                let mut account = Account::from(AccountInfo::default());
560                account.status.set(AccountStatus::Cold, parent_cold);
561                account.mark_created_locally();
562                let mut slot =
563                    EvmStorageSlot::new_changed(U256::from(3), U256::from(7), TransactionId::ZERO);
564                slot.is_cold = parent_cold;
565                account.storage.insert(key, slot);
566                let mut parent = EvmState::from_iter([(address, account)]);
567                let mut account = Account::from(AccountInfo::from_balance(U256::from(9)));
568                account.status.set(AccountStatus::Cold, child_cold);
569                account.mark_touch();
570                let mut slot =
571                    EvmStorageSlot::new_changed(U256::from(7), U256::from(11), TransactionId::ZERO);
572                slot.is_cold = child_cold;
573                account.storage.insert(key, slot);
574
575                merge_child_state(&mut parent, EvmState::from_iter([(address, account)]), false);
576
577                let account = &parent[&address];
578                assert!(account.is_created_locally());
579                assert!(account.is_touched());
580                assert_eq!(account.info.balance, U256::from(9));
581                // Account flags are unioned; a cold parent retains its flag until journal access.
582                assert_eq!(account.status.contains(AccountStatus::Cold), parent_cold);
583                assert_eq!(account.storage[&key].original_value, U256::from(3));
584                assert_eq!(account.storage[&key].present_value, U256::from(11));
585                assert_eq!(account.storage[&key].is_cold, parent_cold && child_cold);
586            }
587        }
588    }
589
590    #[test]
591    fn settlement_only_removes_state_absent_from_child_when_requested() {
592        let retained_address = Address::with_last_byte(0x42);
593        let removed_address = Address::with_last_byte(0x43);
594        let retained_key = U256::ONE;
595        let removed_key = U256::from(2);
596        let mut retained_account = Account::from(AccountInfo::default());
597        retained_account
598            .storage
599            .insert(retained_key, EvmStorageSlot::new(U256::ONE, TransactionId::ZERO));
600        retained_account
601            .storage
602            .insert(removed_key, EvmStorageSlot::new(U256::from(2), TransactionId::ZERO));
603        let mut parent = EvmState::from_iter([
604            (retained_address, retained_account),
605            (removed_address, Account::from(AccountInfo::default())),
606        ]);
607        let mut child_account = Account::from(AccountInfo::default());
608        child_account
609            .storage
610            .insert(retained_key, EvmStorageSlot::new(U256::ONE, TransactionId::ZERO));
611
612        let child = EvmState::from_iter([(retained_address, child_account)]);
613
614        let mut retained = parent.clone();
615        merge_child_state(&mut retained, child.clone(), false);
616
617        assert!(retained.contains_key(&removed_address));
618        assert!(retained[&retained_address].storage.contains_key(&removed_key));
619
620        merge_child_state(&mut parent, child, true);
621
622        assert!(!parent.contains_key(&removed_address));
623        assert!(parent[&retained_address].storage.contains_key(&retained_key));
624        assert!(!parent[&retained_address].storage.contains_key(&removed_key));
625    }
626}