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 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#[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 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 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 pub fn transaction(&self, index: usize) -> ChainFor<FEN> {
266 ChainFor::<FEN>::for_block(&self.grandparent, &self.parent, &self.current, index)
267 }
268
269 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 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 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, ¤t, index)
294 }
295
296 pub fn record_transaction(&mut self, tx: TxEnvFor<FEN>) {
298 self.current.push(tx);
299 }
300
301 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
342pub 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
348pub type MonadBlockParticipants = HashSet<Address>;
350
351pub 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
362pub 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#[derive(Clone, Debug)]
384pub struct ProtocolSystemCall {
385 pub caller: Address,
387 pub contract: Address,
389 pub data: Bytes,
391 pub nonce: u64,
393 pub chain_id: Option<u64>,
395 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
452pub 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
588pub 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 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 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 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 ¤t,
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(¤t_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(¤t_sender));
1249 assert!(context.parent_senders_and_authorities.contains(¤t_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}