Skip to main content

foundry_evm_core/evm/
monad.rs

1use alloy_consensus::BlockHeader;
2use alloy_evm::{Evm, EvmEnv, EvmFactory, FromRecoveredTx};
3use alloy_monad_evm::{MonadEvm, MonadEvmFactory, MonadPrecompilesMap};
4use alloy_network::{BlockResponse, Ethereum, TransactionResponse};
5use alloy_provider::Provider;
6use alloy_rpc_types::BlockTransactions;
7use alloy_sol_types::SolCall;
8use eyre::WrapErr;
9use foundry_fork_db::DatabaseError;
10use monad_revm::{
11    MonadBuilder, MonadCfgEnv, MonadChainContext, MonadContext, MonadEvm as RevmMonadEvm,
12    MonadHardfork, MonadJournal, MonadJournalTr,
13    api::block::{
14        syscall_on_epoch_change_calldata, syscall_reward_calldata, syscall_snapshot_calldata,
15    },
16    handler::MonadHandler,
17    instructions::MonadInstructions,
18    monad_context_with_db,
19    staking::{
20        STAKING_ADDRESS,
21        constants::SYSTEM_ADDRESS,
22        interface::IMonadStaking::{
23            syscallOnEpochChangeCall, syscallRewardCall, syscallSnapshotCall,
24        },
25    },
26};
27use revm::{
28    context::{
29        BlockEnv, ContextTr, Transaction, TransactionType, TxEnv,
30        journaled_state::account::JournaledAccountTr,
31        result::{EVMError, ResultAndState},
32    },
33    context_interface::{Cfg, ContextSetters, transaction::AuthorizationTr},
34    handler::{EthFrame, EvmTr, FrameResult},
35    inspector::{InspectSystemCallEvm, Inspector, InspectorHandler},
36    interpreter::FrameInput,
37    primitives::{Address, Bytes, HashSet, U256},
38};
39
40use crate::{
41    FoundryChain, FoundryContextExt, FoundryInspectorExt, FoundryJournal,
42    backend::{DatabaseExt, JournaledState},
43    evm::{
44        BlockResponseFor, ChainFor, FoundryEvmFactory, FoundryEvmNetwork, NestedEvm, NestedEvmFor,
45        TxEnvFor, run_inspected_frame,
46    },
47};
48
49#[derive(Clone, Copy, Debug, Default)]
50pub struct MonadEvmNetwork;
51impl FoundryEvmNetwork for MonadEvmNetwork {
52    type Network = Ethereum;
53    type EvmFactory = MonadEvmFactory;
54}
55
56type MonadEvmHandler<'db, I> =
57    MonadHandler<MonadRevmEvm<'db, I>, EVMError<DatabaseError>, EthFrame>;
58
59pub type MonadRevmEvm<'db, I> = RevmMonadEvm<
60    MonadContext<&'db mut dyn DatabaseExt<MonadEvmFactory>>,
61    I,
62    MonadInstructions<MonadContext<&'db mut dyn DatabaseExt<MonadEvmFactory>>>,
63    MonadPrecompilesMap,
64>;
65
66impl FoundryChain<TxEnv> for MonadChainContext {
67    fn for_transaction(tx: &TxEnv) -> Self {
68        monad_context_from_participants(
69            Default::default(),
70            Default::default(),
71            std::slice::from_ref(tx),
72            0,
73        )
74    }
75
76    fn for_block(
77        grandparent: &[TxEnv],
78        parent: &[TxEnv],
79        current: &[TxEnv],
80        current_tx_index: usize,
81    ) -> Self {
82        monad_context_from_participants(
83            monad_block_participants(grandparent),
84            monad_block_participants(parent),
85            current,
86            current_tx_index,
87        )
88    }
89
90    fn refresh_journal<J: FoundryJournal>(&self, journal: &mut J) {
91        let mut tracker = journal.capture_reserve_balance();
92        tracker.rebase(self, journal.evm_state());
93        journal.restore_reserve_balance(tracker);
94    }
95}
96
97impl FoundryEvmFactory for MonadEvmFactory {
98    type Chain = MonadChainContext;
99
100    type FoundryContext<'db> = MonadContext<&'db mut dyn DatabaseExt<Self>>;
101
102    type FoundryEvm<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>> =
103        MonadEvm<&'db mut dyn DatabaseExt<Self>, I>;
104
105    fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>>(
106        &self,
107        db: &'db mut dyn DatabaseExt<Self>,
108        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
109        inspector: I,
110    ) -> Self::FoundryEvm<'db, I> {
111        let mut monad_evm = self.create_evm_with_inspector(db, evm_env, inspector);
112        monad_evm.cfg.tx_chain_id_check = true;
113        monad_evm
114    }
115
116    fn create_nested_evm_with_inspector<'db, I>(
117        &self,
118        db: &'db mut dyn DatabaseExt<Self>,
119        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
120        inspector: I,
121    ) -> NestedEvmFor<'db, Self>
122    where
123        I: FoundryInspectorExt<Self::FoundryContext<'db>> + 'db,
124    {
125        let spec = evm_env.cfg_env.spec;
126        let monad_cfg = MonadCfgEnv::from(evm_env.cfg_env);
127        let mut evm = monad_context_with_db(db)
128            .with_block(evm_env.block_env)
129            .with_cfg(monad_cfg)
130            .build_monad_with_inspector(inspector)
131            .with_precompiles(MonadPrecompilesMap::new_with_spec(spec));
132
133        evm.0.ctx.cfg.tx_chain_id_check = true;
134        Box::new(evm)
135    }
136}
137
138impl<'db, I: FoundryInspectorExt<MonadContext<&'db mut dyn DatabaseExt<MonadEvmFactory>>>> NestedEvm
139    for MonadRevmEvm<'db, I>
140{
141    type Spec = MonadHardfork;
142    type Block = BlockEnv;
143    type Tx = TxEnv;
144    type Chain = MonadChainContext;
145    type Journal = MonadJournal<&'db mut dyn DatabaseExt<MonadEvmFactory>>;
146
147    fn tx_mut(&mut self) -> &mut Self::Tx {
148        self.ctx_mut().tx_mut()
149    }
150
151    fn journal_inner_mut(&mut self) -> &mut JournaledState {
152        &mut self.ctx_mut().journaled_state.inner
153    }
154
155    fn chain_mut(&mut self) -> &mut Self::Chain {
156        &mut self.ctx_mut().chain
157    }
158
159    fn precompiles_mut(&mut self) -> &mut alloy_evm::precompiles::PrecompilesMap {
160        &mut self.0.precompiles
161    }
162
163    fn journal_mut(&mut self) -> &mut Self::Journal {
164        &mut self.ctx_mut().journaled_state
165    }
166
167    fn run_execution(&mut self, frame: FrameInput) -> Result<FrameResult, EVMError<DatabaseError>> {
168        run_inspected_frame(self, MonadEvmHandler::<I>::new(), frame)
169    }
170
171    fn transact_raw(&mut self, tx: Self::Tx) -> eyre::Result<ResultAndState> {
172        let Some(system_call) = protocol_system_call(&tx)? else {
173            ContextSetters::set_tx(&mut self.0.ctx, tx);
174
175            let mut handler = MonadEvmHandler::<I>::new();
176            let result = handler.inspect_run(self)?;
177
178            return Ok(ResultAndState::new(
179                result,
180                self.ctx_ref().journaled_state.inner.state.clone(),
181            ));
182        };
183
184        system_call.validate_chain_id(self.ctx_ref().cfg().chain_id())?;
185        let journal = self.ctx_ref().journal_inner().clone();
186        let chain = self.ctx_ref().chain.clone();
187        let reserve_balance = self.ctx_ref().journaled_state.reserve_balance().clone();
188        let result = (|| {
189            let (db, journal) = self.0.ctx.db_journal_inner_mut();
190            system_call.apply_prestate(db, journal)?;
191            let result = self
192                .inspect_system_call_with_caller(
193                    system_call.caller,
194                    system_call.contract,
195                    system_call.data,
196                )
197                .wrap_err("failed to execute protocol system transaction")?;
198            finish_protocol_system_call(result)
199        })();
200        // Restore EVM-owned protocol state. Callers that require atomic inspector state isolate
201        // the inspector and database, as the Executor/Cow replay path does.
202        if result.is_err() {
203            self.ctx_mut().set_journal_inner(journal);
204            self.ctx_mut().chain = chain;
205            *self.ctx_mut().journaled_state.reserve_balance_mut() = reserve_balance;
206        }
207        result
208    }
209
210    fn transact_replay(
211        &mut self,
212        tx: Self::Tx,
213        is_system: bool,
214    ) -> eyre::Result<Option<ResultAndState>> {
215        if is_system && protocol_system_call(&tx)?.is_none() {
216            return Ok(None);
217        }
218        self.transact_raw(tx).map(Some)
219    }
220
221    fn to_evm_env(&self) -> EvmEnv<Self::Spec, Self::Block> {
222        self.ctx_ref().evm_clone()
223    }
224}
225
226/// Transaction metadata for an exact block and its two ancestors.
227#[derive(Clone, Debug)]
228pub struct BlockContext<FEN: FoundryEvmNetwork> {
229    grandparent: Vec<TxEnvFor<FEN>>,
230    parent: Vec<TxEnvFor<FEN>>,
231    current: Vec<TxEnvFor<FEN>>,
232}
233
234impl<FEN: FoundryEvmNetwork> BlockContext<FEN> {
235    /// Creates block context from grandparent, parent, and current block transactions.
236    pub const fn new(
237        grandparent: Vec<TxEnvFor<FEN>>,
238        parent: Vec<TxEnvFor<FEN>>,
239        current: Vec<TxEnvFor<FEN>>,
240    ) -> Self {
241        Self { grandparent, parent, current }
242    }
243
244    /// Fetches all transaction bodies needed to replay transactions in `block` exactly.
245    pub async fn fetch<P: Provider<FEN::Network>>(
246        provider: &P,
247        block: &BlockResponseFor<FEN>,
248    ) -> eyre::Result<Self> {
249        let current = transaction_envs::<FEN>(block)?;
250        let parent = fetch_parent::<FEN, P>(provider, block).await?;
251        let grandparent = if let Some(parent) = &parent {
252            fetch_parent::<FEN, P>(provider, parent).await?
253        } else {
254            None
255        };
256
257        Ok(Self::new(
258            grandparent.as_ref().map(transaction_envs::<FEN>).transpose()?.unwrap_or_default(),
259            parent.as_ref().map(transaction_envs::<FEN>).transpose()?.unwrap_or_default(),
260            current,
261        ))
262    }
263
264    /// Builds context for the transaction at `index` in the current block.
265    pub fn transaction(&self, index: usize) -> ChainFor<FEN> {
266        ChainFor::<FEN>::for_block(&self.grandparent, &self.parent, &self.current, index)
267    }
268
269    /// Returns a cursor positioned immediately before `index` in the current block.
270    pub fn before_transaction(mut self, index: usize) -> eyre::Result<Self> {
271        if index > self.current.len() {
272            eyre::bail!(
273                "transaction index {index} exceeds block transaction count {}",
274                self.current.len()
275            );
276        }
277        self.current.truncate(index);
278        Ok(self)
279    }
280
281    /// Returns a cursor positioned at the start of a child block.
282    pub fn into_child(mut self) -> Self {
283        self.grandparent = std::mem::take(&mut self.parent);
284        self.parent = std::mem::take(&mut self.current);
285        self
286    }
287
288    /// Builds context for the next transaction at the cursor's current block position.
289    pub fn next_transaction(&self, tx: &TxEnvFor<FEN>) -> ChainFor<FEN> {
290        let mut current = self.current.clone();
291        let index = current.len();
292        current.push(tx.clone());
293        ChainFor::<FEN>::for_block(&self.grandparent, &self.parent, &current, index)
294    }
295
296    /// Records a committed transaction at the cursor's current block position.
297    pub fn record_transaction(&mut self, tx: TxEnvFor<FEN>) {
298        self.current.push(tx);
299    }
300
301    /// Advances the cursor to the start of the next block.
302    pub fn advance_block(&mut self) {
303        self.grandparent = std::mem::take(&mut self.parent);
304        self.parent = std::mem::take(&mut self.current);
305    }
306}
307
308async fn fetch_parent<FEN, P>(
309    provider: &P,
310    block: &BlockResponseFor<FEN>,
311) -> eyre::Result<Option<BlockResponseFor<FEN>>>
312where
313    FEN: FoundryEvmNetwork,
314    P: Provider<FEN::Network>,
315{
316    let parent_hash = block.header().parent_hash();
317    if parent_hash.is_zero() {
318        return Ok(None);
319    }
320
321    provider
322        .get_block_by_hash(parent_hash)
323        .full()
324        .await
325        .wrap_err_with(|| format!("failed to fetch ancestor block {parent_hash}"))?
326        .map(Some)
327        .ok_or_else(|| eyre::eyre!("ancestor block {parent_hash} not found"))
328}
329
330fn transaction_envs<FEN: FoundryEvmNetwork>(
331    block: &BlockResponseFor<FEN>,
332) -> eyre::Result<Vec<TxEnvFor<FEN>>> {
333    let BlockTransactions::Full(transactions) = block.transactions() else {
334        eyre::bail!("block {} does not contain full transactions", block.header().number());
335    };
336    Ok(transactions
337        .iter()
338        .map(|tx| TxEnvFor::<FEN>::from_recovered_tx(tx.as_ref(), tx.from()))
339        .collect())
340}
341
342/// Refreshes journal state derived from a nested EVM's active Monad chain position.
343pub fn refresh_nested_chain_journal<E: NestedEvm + ?Sized>(evm: &mut E) {
344    let chain = evm.chain_mut().clone();
345    chain.refresh_journal(evm.journal_mut());
346}
347
348/// Senders and EIP-7702 authorities that participated in one Monad block.
349pub type MonadBlockParticipants = HashSet<Address>;
350
351/// Collects all senders and EIP-7702 authorities from a block's transactions.
352pub fn monad_block_participants(transactions: &[TxEnv]) -> MonadBlockParticipants {
353    transactions
354        .iter()
355        .flat_map(|tx| {
356            std::iter::once(tx.caller())
357                .chain(tx.authorization_list().filter_map(|auth| auth.authority()))
358        })
359        .collect()
360}
361
362/// Builds Monad context from cached ancestor participants and the current block transactions.
363pub fn monad_context_from_participants(
364    grandparent_senders_and_authorities: MonadBlockParticipants,
365    parent_senders_and_authorities: MonadBlockParticipants,
366    current: &[TxEnv],
367    current_tx_index: usize,
368) -> MonadChainContext {
369    MonadChainContext {
370        grandparent_senders_and_authorities,
371        parent_senders_and_authorities,
372        current_block_senders: current.iter().map(Transaction::caller).collect(),
373        current_block_authorities: current
374            .iter()
375            .map(|tx| tx.authorization_list().filter_map(|auth| auth.authority()).collect())
376            .collect(),
377        current_tx_index,
378        ..Default::default()
379    }
380}
381
382/// A canonical Monad protocol system transaction.
383#[derive(Clone, Debug)]
384pub struct ProtocolSystemCall {
385    /// Reserved caller used by the protocol.
386    pub caller: Address,
387    /// Native system contract or precompile being called.
388    pub contract: Address,
389    /// Calldata passed to the dedicated system-call entry point.
390    pub data: Bytes,
391    /// Sender nonce encoded by the canonical envelope.
392    pub nonce: u64,
393    /// Optional EIP-155 chain ID encoded by the canonical envelope.
394    pub chain_id: Option<u64>,
395    /// Optional protocol mint applied before system-call execution.
396    pub balance_increment: Option<(Address, U256)>,
397}
398
399impl ProtocolSystemCall {
400    fn validate_chain_id(&self, chain_id: u64) -> eyre::Result<()> {
401        if let Some(envelope_chain_id) = self.chain_id
402            && envelope_chain_id != chain_id
403        {
404            eyre::bail!(
405                "protocol system transaction chain ID mismatch: envelope {envelope_chain_id}, \
406                 environment {chain_id}"
407            );
408        }
409        Ok(())
410    }
411
412    fn apply_prestate<DB: alloy_evm::Database>(
413        &self,
414        db: &mut DB,
415        journal: &mut JournaledState,
416    ) -> eyre::Result<()> {
417        let next_nonce = self
418            .nonce
419            .checked_add(1)
420            .ok_or_else(|| eyre::eyre!("protocol system transaction nonce overflow"))?;
421        let caller_nonce = journal.load_account(db, self.caller)?.data.info.nonce;
422        if caller_nonce != self.nonce {
423            eyre::bail!(
424                "protocol system transaction nonce mismatch: envelope {}, state {}",
425                self.nonce,
426                caller_nonce
427            );
428        }
429
430        let balance = if let Some((address, amount)) = self.balance_increment {
431            let balance = journal
432                .load_account(db, address)?
433                .data
434                .info
435                .balance
436                .checked_add(amount)
437                .ok_or_else(|| eyre::eyre!("protocol system transaction balance overflow"))?;
438            Some((address, balance))
439        } else {
440            None
441        };
442
443        journal.load_account_mut(db, self.caller)?.data.set_nonce(next_nonce);
444        if let Some((address, balance)) = balance {
445            journal.load_account_mut(db, address)?.data.set_balance(balance);
446        }
447
448        Ok(())
449    }
450}
451
452/// Converts a canonical Monad envelope into its dedicated system call.
453///
454/// Returns an error when the transaction uses Monad's reserved protocol sender but does not
455/// satisfy the canonical envelope rules.
456pub fn protocol_system_call<T: Transaction>(tx: &T) -> eyre::Result<Option<ProtocolSystemCall>> {
457    if tx.caller() != SYSTEM_ADDRESS {
458        return Ok(None);
459    }
460
461    eyre::ensure!(
462        tx.tx_type() == TransactionType::Legacy as u8,
463        "invalid Monad protocol system transaction: transaction type must be legacy"
464    );
465    eyre::ensure!(
466        tx.kind() == revm::primitives::TxKind::Call(STAKING_ADDRESS),
467        "invalid Monad protocol system transaction: target must be the staking contract"
468    );
469    eyre::ensure!(
470        tx.gas_limit() == 0,
471        "invalid Monad protocol system transaction: gas limit must be zero"
472    );
473    eyre::ensure!(
474        tx.gas_price() == 0,
475        "invalid Monad protocol system transaction: gas price must be zero"
476    );
477    eyre::ensure!(
478        tx.max_priority_fee_per_gas().is_none(),
479        "invalid Monad protocol system transaction: priority fee must be absent"
480    );
481    eyre::ensure!(
482        tx.access_list().is_none_or(|mut list| list.next().is_none()),
483        "invalid Monad protocol system transaction: access list must be empty"
484    );
485    eyre::ensure!(
486        tx.blob_versioned_hashes().is_empty(),
487        "invalid Monad protocol system transaction: blob hashes must be empty"
488    );
489    eyre::ensure!(
490        tx.max_fee_per_blob_gas() == 0,
491        "invalid Monad protocol system transaction: blob gas fee must be zero"
492    );
493    eyre::ensure!(
494        tx.authorization_list_len() == 0,
495        "invalid Monad protocol system transaction: authorization list must be empty"
496    );
497
498    let selector: [u8; 4] = tx
499        .input()
500        .get(..4)
501        .ok_or_else(|| {
502            eyre::eyre!(
503                "invalid Monad protocol system transaction: calldata is shorter than a selector"
504            )
505        })?
506        .try_into()
507        .expect("slice has exactly four bytes");
508    let (data, balance_increment) = match selector {
509        syscallRewardCall::SELECTOR => {
510            eyre::ensure!(
511                tx.input().len() == 36,
512                "invalid Monad protocol system transaction: reward calldata must be 36 bytes"
513            );
514            let call = syscallRewardCall::abi_decode_raw(&tx.input()[4..])
515                .wrap_err("invalid Monad protocol system reward calldata")?;
516            eyre::ensure!(
517                call.abi_encode().as_slice() == tx.input(),
518                "invalid Monad protocol system reward calldata"
519            );
520            (
521                syscall_reward_calldata(call.blockAuthor, tx.value()),
522                Some((STAKING_ADDRESS, tx.value())),
523            )
524        }
525        syscallSnapshotCall::SELECTOR => {
526            eyre::ensure!(
527                tx.input().len() == 4,
528                "invalid Monad protocol system transaction: snapshot calldata must be 4 bytes"
529            );
530            eyre::ensure!(
531                tx.value().is_zero(),
532                "invalid Monad protocol system transaction: snapshot value must be zero"
533            );
534            syscallSnapshotCall::abi_decode_raw(&tx.input()[4..])
535                .wrap_err("invalid Monad protocol system snapshot calldata")?;
536            (syscall_snapshot_calldata(), None)
537        }
538        syscallOnEpochChangeCall::SELECTOR => {
539            eyre::ensure!(
540                tx.input().len() == 36,
541                "invalid Monad protocol system transaction: epoch calldata must be 36 bytes"
542            );
543            eyre::ensure!(
544                tx.value().is_zero(),
545                "invalid Monad protocol system transaction: epoch value must be zero"
546            );
547            let call = syscallOnEpochChangeCall::abi_decode_raw(&tx.input()[4..])
548                .wrap_err("invalid Monad protocol system epoch calldata")?;
549            eyre::ensure!(
550                call.abi_encode().as_slice() == tx.input(),
551                "invalid Monad protocol system epoch calldata"
552            );
553            (syscall_on_epoch_change_calldata(call.epoch), None)
554        }
555        _ => {
556            return Err(eyre::eyre!(
557                "invalid Monad protocol system transaction: unknown staking syscall selector"
558            ));
559        }
560    };
561
562    Ok(Some(ProtocolSystemCall {
563        caller: SYSTEM_ADDRESS,
564        contract: STAKING_ADDRESS,
565        data,
566        nonce: tx.nonce(),
567        chain_id: tx.chain_id(),
568        balance_increment,
569    }))
570}
571
572fn finish_protocol_system_call<H>(
573    mut result: ResultAndState<H>,
574) -> eyre::Result<ResultAndState<H>> {
575    if !result.result.is_success() {
576        eyre::bail!("protocol system transaction reverted or halted");
577    }
578
579    if let revm::context_interface::result::ExecutionResult::Success { gas, .. } =
580        &mut result.result
581    {
582        *gas = Default::default();
583    }
584
585    Ok(result)
586}
587
588/// Tries to execute a canonical Monad system transaction on an existing Monad EVM.
589pub fn try_transact_monad_system_replay<DB, I>(
590    evm: &mut MonadEvm<DB, I>,
591    tx: &TxEnv,
592) -> eyre::Result<Option<ResultAndState>>
593where
594    DB: alloy_evm::Database,
595    I: Inspector<MonadContext<DB>>,
596{
597    let Some(system_call) = protocol_system_call(tx)? else {
598        return Ok(None);
599    };
600
601    system_call.validate_chain_id(evm.chain_id())?;
602    let journal = evm.ctx().journal_inner().clone();
603    let chain = evm.ctx().chain.clone();
604    let reserve_balance = evm.ctx().journaled_state.reserve_balance().clone();
605    let result = (|| {
606        let (db, journal) = evm.ctx_mut().db_journal_inner_mut();
607        system_call.apply_prestate(db, journal)?;
608        let result = evm
609            .transact_system_call(system_call.caller, system_call.contract, system_call.data)
610            .wrap_err("failed to execute protocol system transaction")?;
611        finish_protocol_system_call(result)
612    })();
613    // Restore EVM-owned protocol state. Callers that require atomic inspector state isolate the
614    // inspector and database, as the Executor/Cow replay path does.
615    if result.is_err() {
616        evm.ctx_mut().set_journal_inner(journal);
617        evm.ctx_mut().chain = chain;
618        *evm.ctx_mut().journaled_state.reserve_balance_mut() = reserve_balance;
619    }
620    result.map(Some)
621}
622
623#[cfg(test)]
624mod tests {
625    use super::*;
626    use crate::{backend::Backend, evm::EthEvmNetwork};
627    use alloy_evm::EthEvmFactory;
628    use alloy_sol_types::SolEvent;
629    use monad_revm::{
630        reserve_balance::tracker::ReserveBalanceInit,
631        staking::{
632            constants::MON,
633            interface::IMonadStaking::ValidatorRewarded,
634            storage::{
635                consensus_view_key, global_slots, val_id_secp_key, validator_key, validator_offsets,
636            },
637        },
638    };
639    use revm::{
640        Database, DatabaseCommit,
641        context::CfgEnv,
642        context_interface::{
643            either::Either,
644            transaction::{
645                AccessListItem, Authorization, RecoveredAuthority, RecoveredAuthorization,
646            },
647        },
648        database::InMemoryDB,
649        interpreter::{CallInputs, CallOutcome},
650        primitives::{B256, TxKind, address},
651        state::{Account, AccountInfo, EvmState},
652    };
653
654    #[test]
655    fn ethereum_replay_skips_monad_system_envelopes() {
656        let tx = system_transaction(syscallSnapshotCall {}.abi_encode(), U256::ZERO);
657        let factory = EthEvmFactory::default();
658        let evm_env = EvmEnv::default();
659        let mut db = Backend::<EthEvmNetwork>::spawn(None).unwrap();
660        db.set_networks(foundry_evm_networks::NetworkConfigs::with_monad());
661        let mut nested = factory.create_nested_evm(&mut db, evm_env);
662        assert!(nested.transact_replay(tx.clone(), true).unwrap().is_none());
663        assert!(nested.journal_inner_mut().state.is_empty());
664        let error = nested.transact_raw(tx).unwrap_err();
665        assert!(format!("{error:?}").contains("gas"), "{error:?}");
666    }
667
668    #[test]
669    fn nested_replay_executes_monad_envelopes_once_and_skips_foreign_systems() {
670        let factory = MonadEvmFactory::default();
671        let evm_env =
672            EvmEnv::new(CfgEnv::new_with_spec(MonadHardfork::MonadNine), BlockEnv::default());
673        let mut db = Backend::<MonadEvmNetwork>::spawn(None).unwrap();
674        db.insert_account_info(SYSTEM_ADDRESS, AccountInfo { nonce: 3, ..Default::default() });
675        let mut evm = factory.create_nested_evm(&mut db, evm_env.clone());
676
677        // The RPC system classification must survive decoding into an ordinary TxEnv.
678        let foreign = TxEnv { caller: Address::with_last_byte(42), ..Default::default() };
679        assert!(evm.transact_replay(foreign, true).unwrap().is_none());
680        assert!(evm.journal_inner_mut().state.is_empty());
681
682        let tx = system_transaction(syscallSnapshotCall {}.abi_encode(), U256::ZERO);
683        let result = evm.transact_replay(tx.clone(), true).unwrap().unwrap();
684        assert!(result.result.is_success());
685        assert_eq!(result.result.tx_gas_used(), 0);
686        drop(evm);
687        db.commit(result.state);
688        assert_eq!(db.basic(SYSTEM_ADDRESS).unwrap().unwrap().nonce, 4);
689
690        let mut evm = factory.create_nested_evm(&mut db, evm_env);
691        assert!(evm.transact_replay(tx, true).unwrap_err().to_string().contains("nonce"));
692        assert!(evm.journal_inner_mut().state.is_empty());
693        drop(evm);
694        assert_eq!(db.basic(SYSTEM_ADDRESS).unwrap().unwrap().nonce, 4);
695    }
696
697    #[test]
698    fn nested_replay_failure_restores_monad_prestate() {
699        let initial_balance = U256::from(3) * MON;
700        let mut db = Backend::<MonadEvmNetwork>::spawn(None).unwrap();
701        db.insert_account_info(SYSTEM_ADDRESS, AccountInfo { nonce: 3, ..Default::default() });
702        db.insert_account_info(
703            STAKING_ADDRESS,
704            AccountInfo { balance: initial_balance, ..Default::default() },
705        );
706        let tx = system_transaction(
707            syscallRewardCall { blockAuthor: Address::with_last_byte(42) }.abi_encode(),
708            U256::from(25) * MON,
709        );
710        let factory = MonadEvmFactory::default();
711        let evm_env =
712            EvmEnv::new(CfgEnv::new_with_spec(MonadHardfork::MonadNine), BlockEnv::default());
713        let mut evm = factory.create_nested_evm(&mut db, evm_env);
714        let journal_before = evm.journal_inner_mut().clone();
715        let chain_before = evm.chain_mut().clone();
716        let tracker_before = evm.journal_mut().reserve_balance().clone();
717
718        let error = evm.transact_replay(tx, true).unwrap_err();
719        assert!(error.to_string().contains("reverted or halted"), "{error:?}");
720        assert_eq!(evm.journal_inner_mut().state, journal_before.state);
721        assert_eq!(evm.chain_mut(), &chain_before);
722        assert_eq!(evm.journal_mut().reserve_balance(), &tracker_before);
723        drop(evm);
724        assert_eq!(db.basic(SYSTEM_ADDRESS).unwrap().unwrap().nonce, 3);
725        assert_eq!(db.basic(STAKING_ADDRESS).unwrap().unwrap().balance, initial_balance);
726        assert_eq!(db.storage(STAKING_ADDRESS, global_slots::PROPOSER_VAL_ID).unwrap(), U256::ZERO);
727    }
728
729    #[derive(Default)]
730    struct ProtocolPrestateInspector {
731        call_count: usize,
732        staking_balance: Option<U256>,
733    }
734
735    impl Inspector<MonadContext<InMemoryDB>> for ProtocolPrestateInspector {
736        fn call(
737            &mut self,
738            context: &mut MonadContext<InMemoryDB>,
739            _inputs: &mut CallInputs,
740        ) -> Option<CallOutcome> {
741            self.call_count += 1;
742            self.staking_balance = context
743                .journaled_state
744                .inner
745                .state
746                .get(&STAKING_ADDRESS)
747                .map(|account| account.info.balance);
748            None
749        }
750    }
751
752    fn transaction(caller: Address, authority: Address) -> TxEnv {
753        let authorization = RecoveredAuthorization::new_unchecked(
754            Authorization { chain_id: U256::from(1), address: Address::ZERO, nonce: 0 },
755            RecoveredAuthority::Valid(authority),
756        );
757        TxEnv {
758            caller,
759            authorization_list: vec![Either::Right(authorization)],
760            ..Default::default()
761        }
762    }
763
764    fn system_transaction(data: Vec<u8>, value: U256) -> TxEnv {
765        TxEnv {
766            tx_type: TransactionType::Legacy as u8,
767            caller: SYSTEM_ADDRESS,
768            gas_limit: 0,
769            kind: revm::primitives::TxKind::Call(STAKING_ADDRESS),
770            data: data.into(),
771            value,
772            nonce: 3,
773            chain_id: None,
774            ..Default::default()
775        }
776    }
777
778    fn assert_invalid_system_transaction(tx: TxEnv, expected: &str) {
779        let err = protocol_system_call(&tx).unwrap_err();
780        assert!(err.to_string().contains(expected), "expected {expected:?} in error, got {err:?}");
781    }
782
783    #[test]
784    fn monad_evm_factory_implements_foundry_evm_factory() {
785        fn assert_foundry_factory<F: FoundryEvmFactory>() {}
786
787        assert_foundry_factory::<MonadEvmFactory>();
788    }
789
790    #[test]
791    fn monad_context_transition_rebases_live_tracker() {
792        let sender = Address::with_last_byte(1);
793        let old_chain = MonadChainContext::default();
794        let new_chain = MonadChainContext {
795            parent_senders_and_authorities: [sender].into_iter().collect(),
796            ..Default::default()
797        };
798        let mut account =
799            Account::from(AccountInfo { balance: U256::from(12), ..Default::default() });
800        account.info.balance = U256::from(9);
801
802        let factory = MonadEvmFactory::default();
803        let mut evm = factory.create_evm(
804            revm::database::EmptyDB::default(),
805            EvmEnv::new(
806                revm::context::CfgEnv::new_with_spec(MonadHardfork::MonadNine),
807                BlockEnv::default(),
808            ),
809        );
810        evm.ctx_mut().chain = old_chain.clone();
811        evm.ctx_mut().journaled_state.reserve_balance_mut().init(ReserveBalanceInit {
812            chain: &old_chain,
813            spec: MonadHardfork::MonadNine,
814            sender,
815            effective_gas_price: 0,
816            gas_limit: 0,
817            sender_is_delegated: false,
818            sender_account: Some(&account),
819        });
820        assert!(!evm.ctx().journaled_state.reserve_balance().has_violation());
821
822        evm.ctx_mut().chain = new_chain.clone();
823        evm.ctx_mut().journaled_state.inner.state = EvmState::from_iter([(sender, account)]);
824        crate::refresh_chain_journal(evm.ctx_mut());
825
826        assert_eq!(evm.ctx().chain, new_chain);
827        assert!(evm.ctx().journaled_state.reserve_balance().has_violation());
828    }
829
830    #[test]
831    fn monad_factory_classifies_canonical_system_envelopes() {
832        let reward = U256::from(25);
833        let reward_tx = system_transaction(
834            syscallRewardCall { blockAuthor: Address::with_last_byte(1) }.abi_encode(),
835            reward,
836        );
837        let reward_call = protocol_system_call(&reward_tx).unwrap().unwrap();
838        assert_eq!(reward_call.data.len(), 68);
839        assert_eq!(reward_call.balance_increment, Some((STAKING_ADDRESS, reward)));
840
841        let snapshot_tx = system_transaction(syscallSnapshotCall {}.abi_encode(), U256::ZERO);
842        assert!(protocol_system_call(&snapshot_tx).unwrap().is_some());
843
844        let epoch_tx =
845            system_transaction(syscallOnEpochChangeCall { epoch: 9 }.abi_encode(), U256::ZERO);
846        assert!(protocol_system_call(&epoch_tx).unwrap().is_some());
847
848        let mut unrelated = snapshot_tx;
849        unrelated.caller = Address::with_last_byte(2);
850        unrelated.tx_type = TransactionType::Eip1559 as u8;
851        assert!(protocol_system_call(&unrelated).unwrap().is_none());
852    }
853
854    #[test]
855    fn monad_replay_decline_leaves_evm_untouched() {
856        let tx = TxEnv {
857            caller: foundry_common::OPTIMISM_SYSTEM_ADDRESS,
858            kind: TxKind::Call(Address::with_last_byte(1)),
859            ..Default::default()
860        };
861        let factory = MonadEvmFactory::default();
862        let evm_env =
863            EvmEnv::new(CfgEnv::new_with_spec(MonadHardfork::MonadNine), BlockEnv::default());
864        let mut evm = factory.create_evm(InMemoryDB::default(), evm_env);
865        let tx_before = evm.tx().clone();
866        let journal_before = evm.ctx().journal_inner().clone();
867        let chain_before = evm.ctx().chain.clone();
868        let tracker_before = evm.ctx().journaled_state.reserve_balance().clone();
869
870        assert!(try_transact_monad_system_replay(&mut evm, &tx).unwrap().is_none());
871        assert_eq!(evm.tx(), &tx_before);
872        assert_eq!(evm.ctx().journal_inner().state, journal_before.state);
873        assert_eq!(evm.ctx().chain, chain_before);
874        assert_eq!(evm.ctx().journaled_state.reserve_balance(), &tracker_before);
875    }
876
877    #[test]
878    fn monad_factory_rejects_noncanonical_system_envelope_fields() {
879        let canonical = system_transaction(syscallSnapshotCall {}.abi_encode(), U256::ZERO);
880
881        let mut tx = canonical.clone();
882        tx.tx_type = TransactionType::Eip1559 as u8;
883        assert_invalid_system_transaction(tx, "transaction type must be legacy");
884
885        let mut tx = canonical.clone();
886        tx.kind = revm::primitives::TxKind::Call(Address::ZERO);
887        assert_invalid_system_transaction(tx, "target must be the staking contract");
888
889        let mut tx = canonical.clone();
890        tx.gas_limit = 1;
891        assert_invalid_system_transaction(tx, "gas limit must be zero");
892
893        let mut tx = canonical.clone();
894        tx.gas_price = 1;
895        assert_invalid_system_transaction(tx, "gas price must be zero");
896
897        let mut tx = canonical.clone();
898        tx.gas_priority_fee = Some(0);
899        assert_invalid_system_transaction(tx, "priority fee must be absent");
900
901        let mut tx = canonical.clone();
902        tx.access_list.0.push(AccessListItem::default());
903        assert_invalid_system_transaction(tx, "access list must be empty");
904
905        let mut tx = canonical.clone();
906        tx.blob_hashes.push(B256::ZERO);
907        assert_invalid_system_transaction(tx, "blob hashes must be empty");
908
909        let mut tx = canonical.clone();
910        tx.max_fee_per_blob_gas = 1;
911        assert_invalid_system_transaction(tx, "blob gas fee must be zero");
912
913        let mut tx = canonical;
914        tx.authorization_list =
915            transaction(Address::ZERO, Address::with_last_byte(1)).authorization_list;
916        assert_invalid_system_transaction(tx, "authorization list must be empty");
917    }
918
919    #[test]
920    fn monad_factory_rejects_noncanonical_system_call_data_and_value() {
921        assert_invalid_system_transaction(
922            system_transaction(Vec::new(), U256::ZERO),
923            "calldata is shorter than a selector",
924        );
925        assert_invalid_system_transaction(
926            system_transaction(vec![0xff; 4], U256::ZERO),
927            "unknown staking syscall selector",
928        );
929
930        let mut reward = syscallRewardCall { blockAuthor: Address::with_last_byte(1) }.abi_encode();
931        reward.push(0);
932        assert_invalid_system_transaction(
933            system_transaction(reward, U256::ZERO),
934            "reward calldata must be 36 bytes",
935        );
936
937        let mut malformed_reward =
938            syscallRewardCall { blockAuthor: Address::with_last_byte(1) }.abi_encode();
939        malformed_reward[4] = 1;
940        assert_invalid_system_transaction(
941            system_transaction(malformed_reward, U256::ZERO),
942            "invalid Monad protocol system reward calldata",
943        );
944
945        let mut snapshot = syscallSnapshotCall {}.abi_encode();
946        snapshot.push(0);
947        assert_invalid_system_transaction(
948            system_transaction(snapshot, U256::ZERO),
949            "snapshot calldata must be 4 bytes",
950        );
951        assert_invalid_system_transaction(
952            system_transaction(syscallSnapshotCall {}.abi_encode(), U256::from(1)),
953            "snapshot value must be zero",
954        );
955
956        let mut epoch = syscallOnEpochChangeCall { epoch: 9 }.abi_encode();
957        epoch.push(0);
958        assert_invalid_system_transaction(
959            system_transaction(epoch, U256::ZERO),
960            "epoch calldata must be 36 bytes",
961        );
962        let mut malformed_epoch = syscallOnEpochChangeCall { epoch: 9 }.abi_encode();
963        malformed_epoch[4] = 1;
964        assert_invalid_system_transaction(
965            system_transaction(malformed_epoch, U256::ZERO),
966            "invalid Monad protocol system epoch calldata",
967        );
968        assert_invalid_system_transaction(
969            system_transaction(syscallOnEpochChangeCall { epoch: 9 }.abi_encode(), U256::from(1)),
970            "epoch value must be zero",
971        );
972    }
973
974    #[test]
975    fn monad_factory_validates_system_envelope_chain_id_at_execution() {
976        let mut tx = system_transaction(syscallSnapshotCall {}.abi_encode(), U256::ZERO);
977        tx.chain_id = Some(143);
978        let system_call = protocol_system_call(&tx).unwrap().unwrap();
979
980        system_call.validate_chain_id(143).unwrap();
981        assert!(
982            system_call.validate_chain_id(1).unwrap_err().to_string().contains("chain ID mismatch")
983        );
984    }
985
986    #[test]
987    fn protocol_prestate_updates_nonce_and_balance() {
988        let caller = address!("00000000000000000000000000000000000000fe");
989        let recipient = address!("0000000000000000000000000000000000001000");
990        let mut db = InMemoryDB::default();
991        db.insert_account_info(caller, AccountInfo { nonce: 7, ..Default::default() });
992        db.insert_account_info(
993            recipient,
994            AccountInfo { balance: U256::from(10), ..Default::default() },
995        );
996        let call = ProtocolSystemCall {
997            caller,
998            contract: recipient,
999            data: Bytes::new(),
1000            nonce: 7,
1001            chain_id: None,
1002            balance_increment: Some((recipient, U256::from(25))),
1003        };
1004        let mut journal = JournaledState::default();
1005
1006        call.apply_prestate(&mut db, &mut journal).unwrap();
1007
1008        assert_eq!(journal.state[&caller].info.nonce, 8);
1009        assert_eq!(journal.state[&recipient].info.balance, U256::from(35));
1010        assert_eq!(db.basic(caller).unwrap().unwrap().nonce, 7);
1011        assert_eq!(db.basic(recipient).unwrap().unwrap().balance, U256::from(10));
1012    }
1013
1014    #[test]
1015    fn protocol_prestate_rejects_nonce_mismatch() {
1016        let caller = address!("00000000000000000000000000000000000000fe");
1017        let mut db = InMemoryDB::default();
1018        db.insert_account_info(caller, AccountInfo { nonce: 3, ..Default::default() });
1019        let call = ProtocolSystemCall {
1020            caller,
1021            contract: Address::ZERO,
1022            data: Bytes::new(),
1023            nonce: 4,
1024            chain_id: None,
1025            balance_increment: None,
1026        };
1027        let mut journal = JournaledState::default();
1028
1029        let err = call.apply_prestate(&mut db, &mut journal).unwrap_err();
1030
1031        assert!(err.to_string().contains("nonce mismatch"));
1032        assert_eq!(db.basic(caller).unwrap().unwrap().nonce, 3);
1033    }
1034
1035    #[test]
1036    fn protocol_prestate_rejects_nonce_overflow() {
1037        let caller = address!("00000000000000000000000000000000000000fe");
1038        let mut db = InMemoryDB::default();
1039        db.insert_account_info(caller, AccountInfo { nonce: u64::MAX, ..Default::default() });
1040        let call = ProtocolSystemCall {
1041            caller,
1042            contract: Address::ZERO,
1043            data: Bytes::new(),
1044            nonce: u64::MAX,
1045            chain_id: None,
1046            balance_increment: None,
1047        };
1048        let mut journal = JournaledState::default();
1049
1050        let err = call.apply_prestate(&mut db, &mut journal).unwrap_err();
1051
1052        assert!(err.to_string().contains("nonce overflow"));
1053        assert_eq!(db.basic(caller).unwrap().unwrap().nonce, u64::MAX);
1054    }
1055
1056    #[test]
1057    fn reward_envelope_replays_mint_nonce_storage_and_log() {
1058        let block_author = address!("1111111111111111111111111111111111111111");
1059        let validator_auth = address!("2222222222222222222222222222222222222222");
1060        let validator_id = 7;
1061        let reward = U256::from(25) * MON;
1062        let initial_staking_balance = U256::from(3) * MON;
1063        // Monad stores validator IDs and packed address/flags values left-aligned.
1064        let validator_id_slot = U256::from(validator_id) << 192;
1065        let address_flags_slot = U256::from_be_slice(validator_auth.as_slice()) << 96;
1066        let mut db = InMemoryDB::default();
1067        db.insert_account_info(SYSTEM_ADDRESS, AccountInfo { nonce: 11, ..Default::default() });
1068        db.insert_account_info(
1069            STAKING_ADDRESS,
1070            AccountInfo { balance: initial_staking_balance, ..Default::default() },
1071        );
1072        db.insert_account_storage(
1073            STAKING_ADDRESS,
1074            val_id_secp_key(&block_author),
1075            validator_id_slot,
1076        )
1077        .unwrap();
1078        db.insert_account_storage(
1079            STAKING_ADDRESS,
1080            consensus_view_key(validator_id, 0),
1081            U256::from(100) * MON,
1082        )
1083        .unwrap();
1084        db.insert_account_storage(STAKING_ADDRESS, consensus_view_key(validator_id, 1), U256::ZERO)
1085            .unwrap();
1086        db.insert_account_storage(
1087            STAKING_ADDRESS,
1088            validator_key(validator_id, validator_offsets::ADDRESS_FLAGS),
1089            address_flags_slot,
1090        )
1091        .unwrap();
1092
1093        let tx = TxEnv {
1094            tx_type: 0,
1095            caller: SYSTEM_ADDRESS,
1096            gas_limit: 0,
1097            kind: TxKind::Call(STAKING_ADDRESS),
1098            value: reward,
1099            data: syscallRewardCall { blockAuthor: block_author }.abi_encode().into(),
1100            nonce: 11,
1101            ..Default::default()
1102        };
1103        let factory = MonadEvmFactory::default();
1104        let evm_env =
1105            EvmEnv::new(CfgEnv::new_with_spec(MonadHardfork::MonadNine), BlockEnv::default());
1106        let mut evm =
1107            factory.create_evm_with_inspector(db, evm_env, ProtocolPrestateInspector::default());
1108
1109        let result = try_transact_monad_system_replay(&mut evm, &tx).unwrap().unwrap();
1110
1111        assert!(result.result.is_success());
1112        assert_eq!(result.result.tx_gas_used(), 0);
1113        assert!(evm.inspector().call_count > 0);
1114        assert_eq!(evm.inspector().staking_balance, Some(initial_staking_balance + reward));
1115        assert_eq!(result.result.logs().len(), 1);
1116        assert_eq!(result.result.logs()[0].address, STAKING_ADDRESS);
1117        assert_eq!(result.result.logs()[0].topics()[0], ValidatorRewarded::SIGNATURE_HASH);
1118        evm.db_mut().commit(result.state);
1119        let mut db = evm.into_db();
1120        assert_eq!(db.basic(SYSTEM_ADDRESS).unwrap().unwrap().nonce, 12);
1121        assert_eq!(
1122            db.basic(STAKING_ADDRESS).unwrap().unwrap().balance,
1123            initial_staking_balance + reward
1124        );
1125        assert_eq!(
1126            db.storage(STAKING_ADDRESS, global_slots::PROPOSER_VAL_ID).unwrap(),
1127            validator_id_slot
1128        );
1129        assert_eq!(
1130            db.storage(
1131                STAKING_ADDRESS,
1132                validator_key(validator_id, validator_offsets::UNCLAIMED_REWARDS),
1133            )
1134            .unwrap(),
1135            reward
1136        );
1137    }
1138
1139    #[test]
1140    fn failed_reward_envelope_does_not_commit_prestate() {
1141        let unknown_author = address!("1111111111111111111111111111111111111111");
1142        let reward = U256::from(25) * MON;
1143        let initial_staking_balance = U256::from(3) * MON;
1144        let mut db = InMemoryDB::default();
1145        db.insert_account_info(SYSTEM_ADDRESS, AccountInfo { nonce: 11, ..Default::default() });
1146        db.insert_account_info(
1147            STAKING_ADDRESS,
1148            AccountInfo { balance: initial_staking_balance, ..Default::default() },
1149        );
1150        let tx = TxEnv {
1151            tx_type: 0,
1152            caller: SYSTEM_ADDRESS,
1153            gas_limit: 0,
1154            kind: TxKind::Call(STAKING_ADDRESS),
1155            value: reward,
1156            data: syscallRewardCall { blockAuthor: unknown_author }.abi_encode().into(),
1157            nonce: 11,
1158            ..Default::default()
1159        };
1160        let factory = MonadEvmFactory::default();
1161        let evm_env =
1162            EvmEnv::new(CfgEnv::new_with_spec(MonadHardfork::MonadNine), BlockEnv::default());
1163        let mut evm =
1164            factory.create_evm_with_inspector(db, evm_env, ProtocolPrestateInspector::default());
1165        let journal_before = evm.ctx().journal_inner().clone();
1166        let chain_before = evm.ctx().chain.clone();
1167        let tracker_before = evm.ctx().journaled_state.reserve_balance().clone();
1168
1169        let error = try_transact_monad_system_replay(&mut evm, &tx).unwrap_err();
1170
1171        assert!(error.to_string().contains("reverted or halted"));
1172        assert!(evm.inspector().call_count > 0);
1173        assert_eq!(evm.inspector().staking_balance, Some(initial_staking_balance + reward));
1174        assert_eq!(evm.ctx().journal_inner().state, journal_before.state);
1175        assert_eq!(evm.ctx().chain, chain_before);
1176        assert_eq!(evm.ctx().journaled_state.reserve_balance(), &tracker_before);
1177        assert_eq!(evm.db_mut().basic(SYSTEM_ADDRESS).unwrap().unwrap().nonce, 11);
1178        assert_eq!(
1179            evm.db_mut().basic(STAKING_ADDRESS).unwrap().unwrap().balance,
1180            initial_staking_balance
1181        );
1182        assert_eq!(
1183            evm.db_mut().storage(STAKING_ADDRESS, global_slots::PROPOSER_VAL_ID).unwrap(),
1184            U256::ZERO
1185        );
1186    }
1187
1188    #[test]
1189    fn monad_context_tracks_senders_authorities_and_current_index() {
1190        let grandparent_sender = Address::from([1; 20]);
1191        let grandparent_authority = Address::from([2; 20]);
1192        let parent_sender = Address::from([3; 20]);
1193        let parent_authority = Address::from([4; 20]);
1194        let current_sender = Address::from([5; 20]);
1195        let current_authority = Address::from([6; 20]);
1196        let next_sender = Address::from([7; 20]);
1197        let next_authority = Address::from([8; 20]);
1198
1199        let grandparent = [transaction(grandparent_sender, grandparent_authority)];
1200        let parent = [transaction(parent_sender, parent_authority)];
1201        let current = [
1202            transaction(current_sender, current_authority),
1203            transaction(next_sender, next_authority),
1204        ];
1205
1206        let context = monad_context_from_participants(
1207            monad_block_participants(&grandparent),
1208            monad_block_participants(&parent),
1209            &current,
1210            1,
1211        );
1212
1213        assert_eq!(context.current_tx_index, 1);
1214        assert_eq!(context.grandparent_senders_and_authorities.len(), 2);
1215        assert!(context.grandparent_senders_and_authorities.contains(&grandparent_sender));
1216        assert!(context.grandparent_senders_and_authorities.contains(&grandparent_authority));
1217        assert_eq!(context.parent_senders_and_authorities.len(), 2);
1218        assert!(context.parent_senders_and_authorities.contains(&parent_sender));
1219        assert!(context.parent_senders_and_authorities.contains(&parent_authority));
1220        assert_eq!(context.current_block_senders, vec![current_sender, next_sender]);
1221        assert_eq!(context.current_block_authorities.len(), 2);
1222        assert!(context.current_block_authorities[0].contains(&current_authority));
1223        assert!(context.current_block_authorities[1].contains(&next_authority));
1224    }
1225
1226    #[test]
1227    fn child_context_advances_fork_ancestry() {
1228        let parent_sender = Address::from([1; 20]);
1229        let parent_authority = Address::from([2; 20]);
1230        let current_sender = Address::from([3; 20]);
1231        let current_authority = Address::from([4; 20]);
1232        let child_sender = Address::from([5; 20]);
1233        let child_authority = Address::from([6; 20]);
1234
1235        let context = BlockContext::<MonadEvmNetwork>::new(
1236            Vec::new(),
1237            vec![transaction(parent_sender, parent_authority)],
1238            vec![transaction(current_sender, current_authority)],
1239        )
1240        .into_child()
1241        .next_transaction(&transaction(child_sender, child_authority));
1242
1243        assert_eq!(context.current_tx_index, 0);
1244        assert_eq!(context.grandparent_senders_and_authorities.len(), 2);
1245        assert!(context.grandparent_senders_and_authorities.contains(&parent_sender));
1246        assert!(context.grandparent_senders_and_authorities.contains(&parent_authority));
1247        assert_eq!(context.parent_senders_and_authorities.len(), 2);
1248        assert!(context.parent_senders_and_authorities.contains(&current_sender));
1249        assert!(context.parent_senders_and_authorities.contains(&current_authority));
1250        assert_eq!(context.current_block_senders, vec![child_sender]);
1251        assert!(context.current_block_authorities[0].contains(&child_authority));
1252    }
1253
1254    #[test]
1255    fn transaction_cursor_replaces_target_and_excludes_future_transactions() {
1256        let preceding_sender = Address::from([1; 20]);
1257        let target_sender = Address::from([2; 20]);
1258        let future_sender = Address::from([3; 20]);
1259        let synthetic_sender = Address::from([4; 20]);
1260
1261        let cursor = BlockContext::<MonadEvmNetwork>::new(
1262            Vec::new(),
1263            Vec::new(),
1264            vec![
1265                transaction(preceding_sender, Address::ZERO),
1266                transaction(target_sender, Address::ZERO),
1267                transaction(future_sender, Address::ZERO),
1268            ],
1269        )
1270        .before_transaction(1)
1271        .unwrap();
1272        let context = cursor.next_transaction(&transaction(synthetic_sender, Address::ZERO));
1273
1274        assert_eq!(context.current_tx_index, 1);
1275        assert_eq!(context.current_block_senders, vec![preceding_sender, synthetic_sender]);
1276        assert!(!context.current_block_senders.contains(&target_sender));
1277        assert!(!context.current_block_senders.contains(&future_sender));
1278    }
1279
1280    #[test]
1281    fn transaction_cursor_accumulates_same_block_transactions() {
1282        let fork_sender = Address::from([1; 20]);
1283        let first_sender = Address::from([2; 20]);
1284        let second_sender = Address::from([3; 20]);
1285        let mut cursor = BlockContext::<MonadEvmNetwork>::new(
1286            Vec::new(),
1287            Vec::new(),
1288            vec![transaction(fork_sender, Address::ZERO)],
1289        )
1290        .into_child();
1291
1292        cursor.record_transaction(transaction(first_sender, Address::ZERO));
1293        let context = cursor.next_transaction(&transaction(second_sender, Address::ZERO));
1294
1295        assert_eq!(context.current_tx_index, 1);
1296        assert_eq!(context.current_block_senders, vec![first_sender, second_sender]);
1297        assert!(context.parent_senders_and_authorities.contains(&fork_sender));
1298    }
1299
1300    #[test]
1301    fn transaction_cursor_rotates_separate_blocks() {
1302        let fork_parent_sender = Address::from([1; 20]);
1303        let fork_sender = Address::from([2; 20]);
1304        let first_sender = Address::from([3; 20]);
1305        let second_sender = Address::from([4; 20]);
1306        let mut cursor = BlockContext::<MonadEvmNetwork>::new(
1307            Vec::new(),
1308            vec![transaction(fork_parent_sender, Address::ZERO)],
1309            vec![transaction(fork_sender, Address::ZERO)],
1310        )
1311        .into_child();
1312
1313        cursor.record_transaction(transaction(first_sender, Address::ZERO));
1314        cursor.advance_block();
1315        let context = cursor.next_transaction(&transaction(second_sender, Address::ZERO));
1316
1317        assert_eq!(context.current_tx_index, 0);
1318        assert_eq!(context.current_block_senders, vec![second_sender]);
1319        assert!(context.parent_senders_and_authorities.contains(&first_sender));
1320        assert!(context.grandparent_senders_and_authorities.contains(&fork_sender));
1321        assert!(!context.grandparent_senders_and_authorities.contains(&fork_parent_sender));
1322    }
1323}