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 context's active Monad chain position.
343pub fn refresh_chain_journal<CTX: FoundryContextExt>(context: &mut CTX) {
344    let chain = context.chain().clone();
345    chain.refresh_journal(context.journal_mut());
346}
347
348/// Refreshes journal state derived from a nested EVM's active Monad chain position.
349pub fn refresh_nested_chain_journal<E: NestedEvm + ?Sized>(evm: &mut E) {
350    let chain = evm.chain_mut().clone();
351    chain.refresh_journal(evm.journal_mut());
352}
353
354/// Senders and EIP-7702 authorities that participated in one Monad block.
355pub type MonadBlockParticipants = HashSet<Address>;
356
357/// Collects all senders and EIP-7702 authorities from a block's transactions.
358pub fn monad_block_participants(transactions: &[TxEnv]) -> MonadBlockParticipants {
359    transactions
360        .iter()
361        .flat_map(|tx| {
362            std::iter::once(tx.caller())
363                .chain(tx.authorization_list().filter_map(|auth| auth.authority()))
364        })
365        .collect()
366}
367
368/// Builds Monad context from cached ancestor participants and the current block transactions.
369pub fn monad_context_from_participants(
370    grandparent_senders_and_authorities: MonadBlockParticipants,
371    parent_senders_and_authorities: MonadBlockParticipants,
372    current: &[TxEnv],
373    current_tx_index: usize,
374) -> MonadChainContext {
375    MonadChainContext {
376        grandparent_senders_and_authorities,
377        parent_senders_and_authorities,
378        current_block_senders: current.iter().map(Transaction::caller).collect(),
379        current_block_authorities: current
380            .iter()
381            .map(|tx| tx.authorization_list().filter_map(|auth| auth.authority()).collect())
382            .collect(),
383        current_tx_index,
384        ..Default::default()
385    }
386}
387
388/// A canonical Monad protocol system transaction.
389#[derive(Clone, Debug)]
390pub struct ProtocolSystemCall {
391    /// Reserved caller used by the protocol.
392    pub caller: Address,
393    /// Native system contract or precompile being called.
394    pub contract: Address,
395    /// Calldata passed to the dedicated system-call entry point.
396    pub data: Bytes,
397    /// Sender nonce encoded by the canonical envelope.
398    pub nonce: u64,
399    /// Optional EIP-155 chain ID encoded by the canonical envelope.
400    pub chain_id: Option<u64>,
401    /// Optional protocol mint applied before system-call execution.
402    pub balance_increment: Option<(Address, U256)>,
403}
404
405impl ProtocolSystemCall {
406    fn validate_chain_id(&self, chain_id: u64) -> eyre::Result<()> {
407        if let Some(envelope_chain_id) = self.chain_id
408            && envelope_chain_id != chain_id
409        {
410            eyre::bail!(
411                "protocol system transaction chain ID mismatch: envelope {envelope_chain_id}, \
412                 environment {chain_id}"
413            );
414        }
415        Ok(())
416    }
417
418    fn apply_prestate<DB: alloy_evm::Database>(
419        &self,
420        db: &mut DB,
421        journal: &mut JournaledState,
422    ) -> eyre::Result<()> {
423        let next_nonce = self
424            .nonce
425            .checked_add(1)
426            .ok_or_else(|| eyre::eyre!("protocol system transaction nonce overflow"))?;
427        let caller_nonce = journal.load_account(db, self.caller)?.data.info.nonce;
428        if caller_nonce != self.nonce {
429            eyre::bail!(
430                "protocol system transaction nonce mismatch: envelope {}, state {}",
431                self.nonce,
432                caller_nonce
433            );
434        }
435
436        let balance = if let Some((address, amount)) = self.balance_increment {
437            let balance = journal
438                .load_account(db, address)?
439                .data
440                .info
441                .balance
442                .checked_add(amount)
443                .ok_or_else(|| eyre::eyre!("protocol system transaction balance overflow"))?;
444            Some((address, balance))
445        } else {
446            None
447        };
448
449        journal.load_account_mut(db, self.caller)?.data.set_nonce(next_nonce);
450        if let Some((address, balance)) = balance {
451            journal.load_account_mut(db, address)?.data.set_balance(balance);
452        }
453
454        Ok(())
455    }
456}
457
458/// Converts a canonical Monad envelope into its dedicated system call.
459///
460/// Returns an error when the transaction uses Monad's reserved protocol sender but does not
461/// satisfy the canonical envelope rules.
462pub fn protocol_system_call<T: Transaction>(tx: &T) -> eyre::Result<Option<ProtocolSystemCall>> {
463    if tx.caller() != SYSTEM_ADDRESS {
464        return Ok(None);
465    }
466
467    eyre::ensure!(
468        tx.tx_type() == TransactionType::Legacy as u8,
469        "invalid Monad protocol system transaction: transaction type must be legacy"
470    );
471    eyre::ensure!(
472        tx.kind() == revm::primitives::TxKind::Call(STAKING_ADDRESS),
473        "invalid Monad protocol system transaction: target must be the staking contract"
474    );
475    eyre::ensure!(
476        tx.gas_limit() == 0,
477        "invalid Monad protocol system transaction: gas limit must be zero"
478    );
479    eyre::ensure!(
480        tx.gas_price() == 0,
481        "invalid Monad protocol system transaction: gas price must be zero"
482    );
483    eyre::ensure!(
484        tx.max_priority_fee_per_gas().is_none(),
485        "invalid Monad protocol system transaction: priority fee must be absent"
486    );
487    eyre::ensure!(
488        tx.access_list().is_none_or(|mut list| list.next().is_none()),
489        "invalid Monad protocol system transaction: access list must be empty"
490    );
491    eyre::ensure!(
492        tx.blob_versioned_hashes().is_empty(),
493        "invalid Monad protocol system transaction: blob hashes must be empty"
494    );
495    eyre::ensure!(
496        tx.max_fee_per_blob_gas() == 0,
497        "invalid Monad protocol system transaction: blob gas fee must be zero"
498    );
499    eyre::ensure!(
500        tx.authorization_list_len() == 0,
501        "invalid Monad protocol system transaction: authorization list must be empty"
502    );
503
504    let selector: [u8; 4] = tx
505        .input()
506        .get(..4)
507        .ok_or_else(|| {
508            eyre::eyre!(
509                "invalid Monad protocol system transaction: calldata is shorter than a selector"
510            )
511        })?
512        .try_into()
513        .expect("slice has exactly four bytes");
514    let (data, balance_increment) = match selector {
515        syscallRewardCall::SELECTOR => {
516            eyre::ensure!(
517                tx.input().len() == 36,
518                "invalid Monad protocol system transaction: reward calldata must be 36 bytes"
519            );
520            let call = syscallRewardCall::abi_decode_raw(&tx.input()[4..])
521                .wrap_err("invalid Monad protocol system reward calldata")?;
522            eyre::ensure!(
523                call.abi_encode().as_slice() == tx.input(),
524                "invalid Monad protocol system reward calldata"
525            );
526            (
527                syscall_reward_calldata(call.blockAuthor, tx.value()),
528                Some((STAKING_ADDRESS, tx.value())),
529            )
530        }
531        syscallSnapshotCall::SELECTOR => {
532            eyre::ensure!(
533                tx.input().len() == 4,
534                "invalid Monad protocol system transaction: snapshot calldata must be 4 bytes"
535            );
536            eyre::ensure!(
537                tx.value().is_zero(),
538                "invalid Monad protocol system transaction: snapshot value must be zero"
539            );
540            syscallSnapshotCall::abi_decode_raw(&tx.input()[4..])
541                .wrap_err("invalid Monad protocol system snapshot calldata")?;
542            (syscall_snapshot_calldata(), None)
543        }
544        syscallOnEpochChangeCall::SELECTOR => {
545            eyre::ensure!(
546                tx.input().len() == 36,
547                "invalid Monad protocol system transaction: epoch calldata must be 36 bytes"
548            );
549            eyre::ensure!(
550                tx.value().is_zero(),
551                "invalid Monad protocol system transaction: epoch value must be zero"
552            );
553            let call = syscallOnEpochChangeCall::abi_decode_raw(&tx.input()[4..])
554                .wrap_err("invalid Monad protocol system epoch calldata")?;
555            eyre::ensure!(
556                call.abi_encode().as_slice() == tx.input(),
557                "invalid Monad protocol system epoch calldata"
558            );
559            (syscall_on_epoch_change_calldata(call.epoch), None)
560        }
561        _ => {
562            return Err(eyre::eyre!(
563                "invalid Monad protocol system transaction: unknown staking syscall selector"
564            ));
565        }
566    };
567
568    Ok(Some(ProtocolSystemCall {
569        caller: SYSTEM_ADDRESS,
570        contract: STAKING_ADDRESS,
571        data,
572        nonce: tx.nonce(),
573        chain_id: tx.chain_id(),
574        balance_increment,
575    }))
576}
577
578fn finish_protocol_system_call<H>(
579    mut result: ResultAndState<H>,
580) -> eyre::Result<ResultAndState<H>> {
581    if !result.result.is_success() {
582        eyre::bail!("protocol system transaction reverted or halted");
583    }
584
585    if let revm::context_interface::result::ExecutionResult::Success { gas, .. } =
586        &mut result.result
587    {
588        *gas = Default::default();
589    }
590
591    Ok(result)
592}
593
594/// Tries to execute a canonical Monad system transaction on an existing Monad EVM.
595pub fn try_transact_monad_system_replay<DB, I>(
596    evm: &mut MonadEvm<DB, I>,
597    tx: &TxEnv,
598) -> eyre::Result<Option<ResultAndState>>
599where
600    DB: alloy_evm::Database,
601    I: Inspector<MonadContext<DB>>,
602{
603    let Some(system_call) = protocol_system_call(tx)? else {
604        return Ok(None);
605    };
606
607    system_call.validate_chain_id(evm.chain_id())?;
608    let journal = evm.ctx().journal_inner().clone();
609    let chain = evm.ctx().chain.clone();
610    let reserve_balance = evm.ctx().journaled_state.reserve_balance().clone();
611    let result = (|| {
612        let (db, journal) = evm.ctx_mut().db_journal_inner_mut();
613        system_call.apply_prestate(db, journal)?;
614        let result = evm
615            .transact_system_call(system_call.caller, system_call.contract, system_call.data)
616            .wrap_err("failed to execute protocol system transaction")?;
617        finish_protocol_system_call(result)
618    })();
619    // Restore EVM-owned protocol state. Callers that require atomic inspector state isolate the
620    // inspector and database, as the Executor/Cow replay path does.
621    if result.is_err() {
622        evm.ctx_mut().set_journal_inner(journal);
623        evm.ctx_mut().chain = chain;
624        *evm.ctx_mut().journaled_state.reserve_balance_mut() = reserve_balance;
625    }
626    result.map(Some)
627}
628
629#[cfg(test)]
630mod tests {
631    use super::*;
632    use crate::{backend::Backend, evm::EthEvmNetwork};
633    use alloy_evm::EthEvmFactory;
634    use alloy_sol_types::SolEvent;
635    use monad_revm::{
636        reserve_balance::tracker::ReserveBalanceInit,
637        staking::{
638            constants::MON,
639            interface::IMonadStaking::ValidatorRewarded,
640            storage::{
641                consensus_view_key, global_slots, val_id_secp_key, validator_key, validator_offsets,
642            },
643        },
644    };
645    use revm::{
646        Database, DatabaseCommit,
647        context::CfgEnv,
648        context_interface::{
649            either::Either,
650            transaction::{
651                AccessListItem, Authorization, RecoveredAuthority, RecoveredAuthorization,
652            },
653        },
654        database::InMemoryDB,
655        interpreter::{CallInputs, CallOutcome},
656        primitives::{B256, TxKind, address},
657        state::{Account, AccountInfo, EvmState},
658    };
659
660    #[test]
661    fn ethereum_replay_skips_monad_system_envelopes() {
662        let tx = system_transaction(syscallSnapshotCall {}.abi_encode(), U256::ZERO);
663        let factory = EthEvmFactory::default();
664        let evm_env = EvmEnv::default();
665        let mut db = Backend::<EthEvmNetwork>::spawn(None).unwrap();
666        db.set_networks(foundry_evm_networks::NetworkConfigs::with_monad());
667        let mut nested = factory.create_nested_evm(&mut db, evm_env);
668        assert!(nested.transact_replay(tx.clone(), true).unwrap().is_none());
669        assert!(nested.journal_inner_mut().state.is_empty());
670        let error = nested.transact_raw(tx).unwrap_err();
671        assert!(format!("{error:?}").contains("gas"), "{error:?}");
672    }
673
674    #[test]
675    fn nested_replay_executes_monad_envelopes_once_and_skips_foreign_systems() {
676        let factory = MonadEvmFactory::default();
677        let evm_env =
678            EvmEnv::new(CfgEnv::new_with_spec(MonadHardfork::MonadNine), BlockEnv::default());
679        let mut db = Backend::<MonadEvmNetwork>::spawn(None).unwrap();
680        db.insert_account_info(SYSTEM_ADDRESS, AccountInfo { nonce: 3, ..Default::default() });
681        let mut evm = factory.create_nested_evm(&mut db, evm_env.clone());
682
683        // The RPC system classification must survive decoding into an ordinary TxEnv.
684        let foreign = TxEnv { caller: Address::with_last_byte(42), ..Default::default() };
685        assert!(evm.transact_replay(foreign, true).unwrap().is_none());
686        assert!(evm.journal_inner_mut().state.is_empty());
687
688        let tx = system_transaction(syscallSnapshotCall {}.abi_encode(), U256::ZERO);
689        let result = evm.transact_replay(tx.clone(), true).unwrap().unwrap();
690        assert!(result.result.is_success());
691        assert_eq!(result.result.tx_gas_used(), 0);
692        drop(evm);
693        db.commit(result.state);
694        assert_eq!(db.basic(SYSTEM_ADDRESS).unwrap().unwrap().nonce, 4);
695
696        let mut evm = factory.create_nested_evm(&mut db, evm_env);
697        assert!(evm.transact_replay(tx, true).unwrap_err().to_string().contains("nonce"));
698        assert!(evm.journal_inner_mut().state.is_empty());
699        drop(evm);
700        assert_eq!(db.basic(SYSTEM_ADDRESS).unwrap().unwrap().nonce, 4);
701    }
702
703    #[test]
704    fn nested_replay_failure_restores_monad_prestate() {
705        let initial_balance = U256::from(3) * MON;
706        let mut db = Backend::<MonadEvmNetwork>::spawn(None).unwrap();
707        db.insert_account_info(SYSTEM_ADDRESS, AccountInfo { nonce: 3, ..Default::default() });
708        db.insert_account_info(
709            STAKING_ADDRESS,
710            AccountInfo { balance: initial_balance, ..Default::default() },
711        );
712        let tx = system_transaction(
713            syscallRewardCall { blockAuthor: Address::with_last_byte(42) }.abi_encode(),
714            U256::from(25) * MON,
715        );
716        let factory = MonadEvmFactory::default();
717        let evm_env =
718            EvmEnv::new(CfgEnv::new_with_spec(MonadHardfork::MonadNine), BlockEnv::default());
719        let mut evm = factory.create_nested_evm(&mut db, evm_env);
720        let journal_before = evm.journal_inner_mut().clone();
721        let chain_before = evm.chain_mut().clone();
722        let tracker_before = evm.journal_mut().reserve_balance().clone();
723
724        let error = evm.transact_replay(tx, true).unwrap_err();
725        assert!(error.to_string().contains("reverted or halted"), "{error:?}");
726        assert_eq!(evm.journal_inner_mut().state, journal_before.state);
727        assert_eq!(evm.chain_mut(), &chain_before);
728        assert_eq!(evm.journal_mut().reserve_balance(), &tracker_before);
729        drop(evm);
730        assert_eq!(db.basic(SYSTEM_ADDRESS).unwrap().unwrap().nonce, 3);
731        assert_eq!(db.basic(STAKING_ADDRESS).unwrap().unwrap().balance, initial_balance);
732        assert_eq!(db.storage(STAKING_ADDRESS, global_slots::PROPOSER_VAL_ID).unwrap(), U256::ZERO);
733    }
734
735    #[derive(Default)]
736    struct ProtocolPrestateInspector {
737        call_count: usize,
738        staking_balance: Option<U256>,
739    }
740
741    impl Inspector<MonadContext<InMemoryDB>> for ProtocolPrestateInspector {
742        fn call(
743            &mut self,
744            context: &mut MonadContext<InMemoryDB>,
745            _inputs: &mut CallInputs,
746        ) -> Option<CallOutcome> {
747            self.call_count += 1;
748            self.staking_balance = context
749                .journaled_state
750                .inner
751                .state
752                .get(&STAKING_ADDRESS)
753                .map(|account| account.info.balance);
754            None
755        }
756    }
757
758    fn transaction(caller: Address, authority: Address) -> TxEnv {
759        let authorization = RecoveredAuthorization::new_unchecked(
760            Authorization { chain_id: U256::ONE, address: Address::ZERO, nonce: 0 },
761            RecoveredAuthority::Valid(authority),
762        );
763        TxEnv {
764            caller,
765            authorization_list: vec![Either::Right(authorization)],
766            ..Default::default()
767        }
768    }
769
770    fn system_transaction(data: Vec<u8>, value: U256) -> TxEnv {
771        TxEnv {
772            tx_type: TransactionType::Legacy as u8,
773            caller: SYSTEM_ADDRESS,
774            gas_limit: 0,
775            kind: revm::primitives::TxKind::Call(STAKING_ADDRESS),
776            data: data.into(),
777            value,
778            nonce: 3,
779            chain_id: None,
780            ..Default::default()
781        }
782    }
783
784    fn assert_invalid_system_transaction(tx: TxEnv, expected: &str) {
785        let err = protocol_system_call(&tx).unwrap_err();
786        assert!(err.to_string().contains(expected), "expected {expected:?} in error, got {err:?}");
787    }
788
789    #[test]
790    fn monad_evm_factory_implements_foundry_evm_factory() {
791        fn assert_foundry_factory<F: FoundryEvmFactory>() {}
792
793        assert_foundry_factory::<MonadEvmFactory>();
794    }
795
796    #[test]
797    fn monad_context_transition_rebases_live_tracker() {
798        let sender = Address::with_last_byte(1);
799        let old_chain = MonadChainContext::default();
800        let new_chain = MonadChainContext {
801            parent_senders_and_authorities: [sender].into_iter().collect(),
802            ..Default::default()
803        };
804        let mut account =
805            Account::from(AccountInfo { balance: U256::from(12), ..Default::default() });
806        account.info.balance = U256::from(9);
807
808        let factory = MonadEvmFactory::default();
809        let mut evm = factory.create_evm(
810            revm::database::EmptyDB::default(),
811            EvmEnv::new(
812                revm::context::CfgEnv::new_with_spec(MonadHardfork::MonadNine),
813                BlockEnv::default(),
814            ),
815        );
816        evm.ctx_mut().chain = old_chain.clone();
817        evm.ctx_mut().journaled_state.reserve_balance_mut().init(ReserveBalanceInit {
818            chain: &old_chain,
819            spec: MonadHardfork::MonadNine,
820            sender,
821            effective_gas_price: 0,
822            gas_limit: 0,
823            sender_is_delegated: false,
824            sender_account: Some(&account),
825        });
826        assert!(!evm.ctx().journaled_state.reserve_balance().has_violation());
827
828        evm.ctx_mut().chain = new_chain.clone();
829        evm.ctx_mut().journaled_state.inner.state = EvmState::from_iter([(sender, account)]);
830        refresh_chain_journal(evm.ctx_mut());
831
832        assert_eq!(evm.ctx().chain, new_chain);
833        assert!(evm.ctx().journaled_state.reserve_balance().has_violation());
834    }
835
836    #[test]
837    fn monad_factory_classifies_canonical_system_envelopes() {
838        let reward = U256::from(25);
839        let reward_tx = system_transaction(
840            syscallRewardCall { blockAuthor: Address::with_last_byte(1) }.abi_encode(),
841            reward,
842        );
843        let reward_call = protocol_system_call(&reward_tx).unwrap().unwrap();
844        assert_eq!(reward_call.data.len(), 68);
845        assert_eq!(reward_call.balance_increment, Some((STAKING_ADDRESS, reward)));
846
847        let snapshot_tx = system_transaction(syscallSnapshotCall {}.abi_encode(), U256::ZERO);
848        assert!(protocol_system_call(&snapshot_tx).unwrap().is_some());
849
850        let epoch_tx =
851            system_transaction(syscallOnEpochChangeCall { epoch: 9 }.abi_encode(), U256::ZERO);
852        assert!(protocol_system_call(&epoch_tx).unwrap().is_some());
853
854        let mut unrelated = snapshot_tx;
855        unrelated.caller = Address::with_last_byte(2);
856        unrelated.tx_type = TransactionType::Eip1559 as u8;
857        assert!(protocol_system_call(&unrelated).unwrap().is_none());
858    }
859
860    #[test]
861    fn monad_replay_decline_leaves_evm_untouched() {
862        let tx = TxEnv {
863            caller: foundry_common::OPTIMISM_SYSTEM_ADDRESS,
864            kind: TxKind::Call(Address::with_last_byte(1)),
865            ..Default::default()
866        };
867        let factory = MonadEvmFactory::default();
868        let evm_env =
869            EvmEnv::new(CfgEnv::new_with_spec(MonadHardfork::MonadNine), BlockEnv::default());
870        let mut evm = factory.create_evm(InMemoryDB::default(), evm_env);
871        let tx_before = evm.tx().clone();
872        let journal_before = evm.ctx().journal_inner().clone();
873        let chain_before = evm.ctx().chain.clone();
874        let tracker_before = evm.ctx().journaled_state.reserve_balance().clone();
875
876        assert!(try_transact_monad_system_replay(&mut evm, &tx).unwrap().is_none());
877        assert_eq!(evm.tx(), &tx_before);
878        assert_eq!(evm.ctx().journal_inner().state, journal_before.state);
879        assert_eq!(evm.ctx().chain, chain_before);
880        assert_eq!(evm.ctx().journaled_state.reserve_balance(), &tracker_before);
881    }
882
883    #[test]
884    fn monad_factory_rejects_noncanonical_system_envelope_fields() {
885        let canonical = system_transaction(syscallSnapshotCall {}.abi_encode(), U256::ZERO);
886
887        let mut tx = canonical.clone();
888        tx.tx_type = TransactionType::Eip1559 as u8;
889        assert_invalid_system_transaction(tx, "transaction type must be legacy");
890
891        let mut tx = canonical.clone();
892        tx.kind = revm::primitives::TxKind::Call(Address::ZERO);
893        assert_invalid_system_transaction(tx, "target must be the staking contract");
894
895        let mut tx = canonical.clone();
896        tx.gas_limit = 1;
897        assert_invalid_system_transaction(tx, "gas limit must be zero");
898
899        let mut tx = canonical.clone();
900        tx.gas_price = 1;
901        assert_invalid_system_transaction(tx, "gas price must be zero");
902
903        let mut tx = canonical.clone();
904        tx.gas_priority_fee = Some(0);
905        assert_invalid_system_transaction(tx, "priority fee must be absent");
906
907        let mut tx = canonical.clone();
908        tx.access_list.0.push(AccessListItem::default());
909        assert_invalid_system_transaction(tx, "access list must be empty");
910
911        let mut tx = canonical.clone();
912        tx.blob_hashes.push(B256::ZERO);
913        assert_invalid_system_transaction(tx, "blob hashes must be empty");
914
915        let mut tx = canonical.clone();
916        tx.max_fee_per_blob_gas = 1;
917        assert_invalid_system_transaction(tx, "blob gas fee must be zero");
918
919        let mut tx = canonical;
920        tx.authorization_list =
921            transaction(Address::ZERO, Address::with_last_byte(1)).authorization_list;
922        assert_invalid_system_transaction(tx, "authorization list must be empty");
923    }
924
925    #[test]
926    fn monad_factory_rejects_noncanonical_system_call_data_and_value() {
927        assert_invalid_system_transaction(
928            system_transaction(Vec::new(), U256::ZERO),
929            "calldata is shorter than a selector",
930        );
931        assert_invalid_system_transaction(
932            system_transaction(vec![0xff; 4], U256::ZERO),
933            "unknown staking syscall selector",
934        );
935
936        let mut reward = syscallRewardCall { blockAuthor: Address::with_last_byte(1) }.abi_encode();
937        reward.push(0);
938        assert_invalid_system_transaction(
939            system_transaction(reward, U256::ZERO),
940            "reward calldata must be 36 bytes",
941        );
942
943        let mut malformed_reward =
944            syscallRewardCall { blockAuthor: Address::with_last_byte(1) }.abi_encode();
945        malformed_reward[4] = 1;
946        assert_invalid_system_transaction(
947            system_transaction(malformed_reward, U256::ZERO),
948            "invalid Monad protocol system reward calldata",
949        );
950
951        let mut snapshot = syscallSnapshotCall {}.abi_encode();
952        snapshot.push(0);
953        assert_invalid_system_transaction(
954            system_transaction(snapshot, U256::ZERO),
955            "snapshot calldata must be 4 bytes",
956        );
957        assert_invalid_system_transaction(
958            system_transaction(syscallSnapshotCall {}.abi_encode(), U256::ONE),
959            "snapshot value must be zero",
960        );
961
962        let mut epoch = syscallOnEpochChangeCall { epoch: 9 }.abi_encode();
963        epoch.push(0);
964        assert_invalid_system_transaction(
965            system_transaction(epoch, U256::ZERO),
966            "epoch calldata must be 36 bytes",
967        );
968        let mut malformed_epoch = syscallOnEpochChangeCall { epoch: 9 }.abi_encode();
969        malformed_epoch[4] = 1;
970        assert_invalid_system_transaction(
971            system_transaction(malformed_epoch, U256::ZERO),
972            "invalid Monad protocol system epoch calldata",
973        );
974        assert_invalid_system_transaction(
975            system_transaction(syscallOnEpochChangeCall { epoch: 9 }.abi_encode(), U256::ONE),
976            "epoch value must be zero",
977        );
978    }
979
980    #[test]
981    fn monad_factory_validates_system_envelope_chain_id_at_execution() {
982        let mut tx = system_transaction(syscallSnapshotCall {}.abi_encode(), U256::ZERO);
983        tx.chain_id = Some(143);
984        let system_call = protocol_system_call(&tx).unwrap().unwrap();
985
986        system_call.validate_chain_id(143).unwrap();
987        assert!(
988            system_call.validate_chain_id(1).unwrap_err().to_string().contains("chain ID mismatch")
989        );
990    }
991
992    #[test]
993    fn protocol_prestate_updates_nonce_and_balance() {
994        let caller = Address::with_last_byte(0xfe);
995        let recipient = address!("0000000000000000000000000000000000001000");
996        let mut db = InMemoryDB::default();
997        db.insert_account_info(caller, AccountInfo { nonce: 7, ..Default::default() });
998        db.insert_account_info(
999            recipient,
1000            AccountInfo { balance: U256::from(10), ..Default::default() },
1001        );
1002        let call = ProtocolSystemCall {
1003            caller,
1004            contract: recipient,
1005            data: Bytes::new(),
1006            nonce: 7,
1007            chain_id: None,
1008            balance_increment: Some((recipient, U256::from(25))),
1009        };
1010        let mut journal = JournaledState::default();
1011
1012        call.apply_prestate(&mut db, &mut journal).unwrap();
1013
1014        assert_eq!(journal.state[&caller].info.nonce, 8);
1015        assert_eq!(journal.state[&recipient].info.balance, U256::from(35));
1016        assert_eq!(db.basic(caller).unwrap().unwrap().nonce, 7);
1017        assert_eq!(db.basic(recipient).unwrap().unwrap().balance, U256::from(10));
1018    }
1019
1020    #[test]
1021    fn protocol_prestate_rejects_nonce_mismatch() {
1022        let caller = Address::with_last_byte(0xfe);
1023        let mut db = InMemoryDB::default();
1024        db.insert_account_info(caller, AccountInfo { nonce: 3, ..Default::default() });
1025        let call = ProtocolSystemCall {
1026            caller,
1027            contract: Address::ZERO,
1028            data: Bytes::new(),
1029            nonce: 4,
1030            chain_id: None,
1031            balance_increment: None,
1032        };
1033        let mut journal = JournaledState::default();
1034
1035        let err = call.apply_prestate(&mut db, &mut journal).unwrap_err();
1036
1037        assert!(err.to_string().contains("nonce mismatch"));
1038        assert_eq!(db.basic(caller).unwrap().unwrap().nonce, 3);
1039    }
1040
1041    #[test]
1042    fn protocol_prestate_rejects_nonce_overflow() {
1043        let caller = Address::with_last_byte(0xfe);
1044        let mut db = InMemoryDB::default();
1045        db.insert_account_info(caller, AccountInfo { nonce: u64::MAX, ..Default::default() });
1046        let call = ProtocolSystemCall {
1047            caller,
1048            contract: Address::ZERO,
1049            data: Bytes::new(),
1050            nonce: u64::MAX,
1051            chain_id: None,
1052            balance_increment: None,
1053        };
1054        let mut journal = JournaledState::default();
1055
1056        let err = call.apply_prestate(&mut db, &mut journal).unwrap_err();
1057
1058        assert!(err.to_string().contains("nonce overflow"));
1059        assert_eq!(db.basic(caller).unwrap().unwrap().nonce, u64::MAX);
1060    }
1061
1062    #[test]
1063    fn reward_envelope_replays_mint_nonce_storage_and_log() {
1064        let block_author = Address::repeat_byte(0x11);
1065        let validator_auth = Address::repeat_byte(0x22);
1066        let validator_id = 7;
1067        let reward = U256::from(25) * MON;
1068        let initial_staking_balance = U256::from(3) * MON;
1069        // Monad stores validator IDs and packed address/flags values left-aligned.
1070        let validator_id_slot = U256::from(validator_id) << 192;
1071        let address_flags_slot = U256::from_be_slice(validator_auth.as_slice()) << 96;
1072        let mut db = InMemoryDB::default();
1073        db.insert_account_info(SYSTEM_ADDRESS, AccountInfo { nonce: 11, ..Default::default() });
1074        db.insert_account_info(
1075            STAKING_ADDRESS,
1076            AccountInfo { balance: initial_staking_balance, ..Default::default() },
1077        );
1078        db.insert_account_storage(
1079            STAKING_ADDRESS,
1080            val_id_secp_key(&block_author),
1081            validator_id_slot,
1082        )
1083        .unwrap();
1084        db.insert_account_storage(
1085            STAKING_ADDRESS,
1086            consensus_view_key(validator_id, 0),
1087            U256::from(100) * MON,
1088        )
1089        .unwrap();
1090        db.insert_account_storage(STAKING_ADDRESS, consensus_view_key(validator_id, 1), U256::ZERO)
1091            .unwrap();
1092        db.insert_account_storage(
1093            STAKING_ADDRESS,
1094            validator_key(validator_id, validator_offsets::ADDRESS_FLAGS),
1095            address_flags_slot,
1096        )
1097        .unwrap();
1098
1099        let tx = TxEnv {
1100            tx_type: 0,
1101            caller: SYSTEM_ADDRESS,
1102            gas_limit: 0,
1103            kind: TxKind::Call(STAKING_ADDRESS),
1104            value: reward,
1105            data: syscallRewardCall { blockAuthor: block_author }.abi_encode().into(),
1106            nonce: 11,
1107            ..Default::default()
1108        };
1109        let factory = MonadEvmFactory::default();
1110        let evm_env =
1111            EvmEnv::new(CfgEnv::new_with_spec(MonadHardfork::MonadNine), BlockEnv::default());
1112        let mut evm =
1113            factory.create_evm_with_inspector(db, evm_env, ProtocolPrestateInspector::default());
1114
1115        let result = try_transact_monad_system_replay(&mut evm, &tx).unwrap().unwrap();
1116
1117        assert!(result.result.is_success());
1118        assert_eq!(result.result.tx_gas_used(), 0);
1119        assert!(evm.inspector().call_count > 0);
1120        assert_eq!(evm.inspector().staking_balance, Some(initial_staking_balance + reward));
1121        assert_eq!(result.result.logs().len(), 1);
1122        assert_eq!(result.result.logs()[0].address, STAKING_ADDRESS);
1123        assert_eq!(result.result.logs()[0].topics()[0], ValidatorRewarded::SIGNATURE_HASH);
1124        evm.db_mut().commit(result.state);
1125        let mut db = evm.into_db();
1126        assert_eq!(db.basic(SYSTEM_ADDRESS).unwrap().unwrap().nonce, 12);
1127        assert_eq!(
1128            db.basic(STAKING_ADDRESS).unwrap().unwrap().balance,
1129            initial_staking_balance + reward
1130        );
1131        assert_eq!(
1132            db.storage(STAKING_ADDRESS, global_slots::PROPOSER_VAL_ID).unwrap(),
1133            validator_id_slot
1134        );
1135        assert_eq!(
1136            db.storage(
1137                STAKING_ADDRESS,
1138                validator_key(validator_id, validator_offsets::UNCLAIMED_REWARDS),
1139            )
1140            .unwrap(),
1141            reward
1142        );
1143    }
1144
1145    #[test]
1146    fn failed_reward_envelope_does_not_commit_prestate() {
1147        let unknown_author = Address::repeat_byte(0x11);
1148        let reward = U256::from(25) * MON;
1149        let initial_staking_balance = U256::from(3) * MON;
1150        let mut db = InMemoryDB::default();
1151        db.insert_account_info(SYSTEM_ADDRESS, AccountInfo { nonce: 11, ..Default::default() });
1152        db.insert_account_info(
1153            STAKING_ADDRESS,
1154            AccountInfo { balance: initial_staking_balance, ..Default::default() },
1155        );
1156        let tx = TxEnv {
1157            tx_type: 0,
1158            caller: SYSTEM_ADDRESS,
1159            gas_limit: 0,
1160            kind: TxKind::Call(STAKING_ADDRESS),
1161            value: reward,
1162            data: syscallRewardCall { blockAuthor: unknown_author }.abi_encode().into(),
1163            nonce: 11,
1164            ..Default::default()
1165        };
1166        let factory = MonadEvmFactory::default();
1167        let evm_env =
1168            EvmEnv::new(CfgEnv::new_with_spec(MonadHardfork::MonadNine), BlockEnv::default());
1169        let mut evm =
1170            factory.create_evm_with_inspector(db, evm_env, ProtocolPrestateInspector::default());
1171        let journal_before = evm.ctx().journal_inner().clone();
1172        let chain_before = evm.ctx().chain.clone();
1173        let tracker_before = evm.ctx().journaled_state.reserve_balance().clone();
1174
1175        let error = try_transact_monad_system_replay(&mut evm, &tx).unwrap_err();
1176
1177        assert!(error.to_string().contains("reverted or halted"));
1178        assert!(evm.inspector().call_count > 0);
1179        assert_eq!(evm.inspector().staking_balance, Some(initial_staking_balance + reward));
1180        assert_eq!(evm.ctx().journal_inner().state, journal_before.state);
1181        assert_eq!(evm.ctx().chain, chain_before);
1182        assert_eq!(evm.ctx().journaled_state.reserve_balance(), &tracker_before);
1183        assert_eq!(evm.db_mut().basic(SYSTEM_ADDRESS).unwrap().unwrap().nonce, 11);
1184        assert_eq!(
1185            evm.db_mut().basic(STAKING_ADDRESS).unwrap().unwrap().balance,
1186            initial_staking_balance
1187        );
1188        assert_eq!(
1189            evm.db_mut().storage(STAKING_ADDRESS, global_slots::PROPOSER_VAL_ID).unwrap(),
1190            U256::ZERO
1191        );
1192    }
1193
1194    #[test]
1195    fn monad_context_tracks_senders_authorities_and_current_index() {
1196        let grandparent_sender = Address::repeat_byte(1);
1197        let grandparent_authority = Address::repeat_byte(2);
1198        let parent_sender = Address::repeat_byte(3);
1199        let parent_authority = Address::repeat_byte(4);
1200        let current_sender = Address::repeat_byte(5);
1201        let current_authority = Address::repeat_byte(6);
1202        let next_sender = Address::repeat_byte(7);
1203        let next_authority = Address::repeat_byte(8);
1204
1205        let grandparent = [transaction(grandparent_sender, grandparent_authority)];
1206        let parent = [transaction(parent_sender, parent_authority)];
1207        let current = [
1208            transaction(current_sender, current_authority),
1209            transaction(next_sender, next_authority),
1210        ];
1211
1212        let context = monad_context_from_participants(
1213            monad_block_participants(&grandparent),
1214            monad_block_participants(&parent),
1215            &current,
1216            1,
1217        );
1218
1219        assert_eq!(context.current_tx_index, 1);
1220        assert_eq!(context.grandparent_senders_and_authorities.len(), 2);
1221        assert!(context.grandparent_senders_and_authorities.contains(&grandparent_sender));
1222        assert!(context.grandparent_senders_and_authorities.contains(&grandparent_authority));
1223        assert_eq!(context.parent_senders_and_authorities.len(), 2);
1224        assert!(context.parent_senders_and_authorities.contains(&parent_sender));
1225        assert!(context.parent_senders_and_authorities.contains(&parent_authority));
1226        assert_eq!(context.current_block_senders, vec![current_sender, next_sender]);
1227        assert_eq!(context.current_block_authorities.len(), 2);
1228        assert!(context.current_block_authorities[0].contains(&current_authority));
1229        assert!(context.current_block_authorities[1].contains(&next_authority));
1230    }
1231
1232    #[test]
1233    fn child_context_advances_fork_ancestry() {
1234        let parent_sender = Address::repeat_byte(1);
1235        let parent_authority = Address::repeat_byte(2);
1236        let current_sender = Address::repeat_byte(3);
1237        let current_authority = Address::repeat_byte(4);
1238        let child_sender = Address::repeat_byte(5);
1239        let child_authority = Address::repeat_byte(6);
1240
1241        let context = BlockContext::<MonadEvmNetwork>::new(
1242            Vec::new(),
1243            vec![transaction(parent_sender, parent_authority)],
1244            vec![transaction(current_sender, current_authority)],
1245        )
1246        .into_child()
1247        .next_transaction(&transaction(child_sender, child_authority));
1248
1249        assert_eq!(context.current_tx_index, 0);
1250        assert_eq!(context.grandparent_senders_and_authorities.len(), 2);
1251        assert!(context.grandparent_senders_and_authorities.contains(&parent_sender));
1252        assert!(context.grandparent_senders_and_authorities.contains(&parent_authority));
1253        assert_eq!(context.parent_senders_and_authorities.len(), 2);
1254        assert!(context.parent_senders_and_authorities.contains(&current_sender));
1255        assert!(context.parent_senders_and_authorities.contains(&current_authority));
1256        assert_eq!(context.current_block_senders, vec![child_sender]);
1257        assert!(context.current_block_authorities[0].contains(&child_authority));
1258    }
1259
1260    #[test]
1261    fn transaction_cursor_replaces_target_and_excludes_future_transactions() {
1262        let preceding_sender = Address::repeat_byte(1);
1263        let target_sender = Address::repeat_byte(2);
1264        let future_sender = Address::repeat_byte(3);
1265        let synthetic_sender = Address::repeat_byte(4);
1266
1267        let cursor = BlockContext::<MonadEvmNetwork>::new(
1268            Vec::new(),
1269            Vec::new(),
1270            vec![
1271                transaction(preceding_sender, Address::ZERO),
1272                transaction(target_sender, Address::ZERO),
1273                transaction(future_sender, Address::ZERO),
1274            ],
1275        )
1276        .before_transaction(1)
1277        .unwrap();
1278        let context = cursor.next_transaction(&transaction(synthetic_sender, Address::ZERO));
1279
1280        assert_eq!(context.current_tx_index, 1);
1281        assert_eq!(context.current_block_senders, vec![preceding_sender, synthetic_sender]);
1282        assert!(!context.current_block_senders.contains(&target_sender));
1283        assert!(!context.current_block_senders.contains(&future_sender));
1284    }
1285
1286    #[test]
1287    fn transaction_cursor_accumulates_same_block_transactions() {
1288        let fork_sender = Address::repeat_byte(1);
1289        let first_sender = Address::repeat_byte(2);
1290        let second_sender = Address::repeat_byte(3);
1291        let mut cursor = BlockContext::<MonadEvmNetwork>::new(
1292            Vec::new(),
1293            Vec::new(),
1294            vec![transaction(fork_sender, Address::ZERO)],
1295        )
1296        .into_child();
1297
1298        cursor.record_transaction(transaction(first_sender, Address::ZERO));
1299        let context = cursor.next_transaction(&transaction(second_sender, Address::ZERO));
1300
1301        assert_eq!(context.current_tx_index, 1);
1302        assert_eq!(context.current_block_senders, vec![first_sender, second_sender]);
1303        assert!(context.parent_senders_and_authorities.contains(&fork_sender));
1304    }
1305
1306    #[test]
1307    fn transaction_cursor_rotates_separate_blocks() {
1308        let fork_parent_sender = Address::repeat_byte(1);
1309        let fork_sender = Address::repeat_byte(2);
1310        let first_sender = Address::repeat_byte(3);
1311        let second_sender = Address::repeat_byte(4);
1312        let mut cursor = BlockContext::<MonadEvmNetwork>::new(
1313            Vec::new(),
1314            vec![transaction(fork_parent_sender, Address::ZERO)],
1315            vec![transaction(fork_sender, Address::ZERO)],
1316        )
1317        .into_child();
1318
1319        cursor.record_transaction(transaction(first_sender, Address::ZERO));
1320        cursor.advance_block();
1321        let context = cursor.next_transaction(&transaction(second_sender, Address::ZERO));
1322
1323        assert_eq!(context.current_tx_index, 0);
1324        assert_eq!(context.current_block_senders, vec![second_sender]);
1325        assert!(context.parent_senders_and_authorities.contains(&first_sender));
1326        assert!(context.grandparent_senders_and_authorities.contains(&fork_sender));
1327        assert!(!context.grandparent_senders_and_authorities.contains(&fork_parent_sender));
1328    }
1329}