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