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_chain_journal<CTX: FoundryContextExt>(context: &mut CTX) {
344 let chain = context.chain().clone();
345 chain.refresh_journal(context.journal_mut());
346}
347
348pub 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
354pub type MonadBlockParticipants = HashSet<Address>;
356
357pub 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
368pub 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#[derive(Clone, Debug)]
390pub struct ProtocolSystemCall {
391 pub caller: Address,
393 pub contract: Address,
395 pub data: Bytes,
397 pub nonce: u64,
399 pub chain_id: Option<u64>,
401 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
458pub 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
594pub 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 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 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 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 ¤t,
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(¤t_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(¤t_sender));
1255 assert!(context.parent_senders_and_authorities.contains(¤t_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}