Skip to main content

forge_script/simulate/
monad.rs

1//! Sequential Monad simulation owns its block cursor independently of local script execution.
2
3use super::{PreSimulationState, RpcSimulationContext};
4use crate::{
5    ScriptResult,
6    runner::{GasSearch, ScriptRunner, needs_more_gas},
7    simulate::FilledTransactionsState,
8    transaction::ScriptTransactionBuilder,
9};
10use alloy_eips::eip7702::SignedAuthorization;
11use alloy_evm::EvmEnv;
12use alloy_network::Ethereum;
13use alloy_primitives::{Address, Bytes, TxKind, U256, map::HashMap};
14use eyre::{Result, WrapErr};
15use foundry_common::TransactionMaybeSigned;
16use foundry_evm::{
17    backend::DatabaseExt,
18    core::{
19        FoundryBlock, FoundryTransaction,
20        evm::{BlockContext, MonadEvmNetwork},
21    },
22    executors::{DeployResult, EvmError},
23    revm::{
24        context::{Transaction, TxEnv},
25        context_interface::result::Output,
26        interpreter::return_ok,
27    },
28};
29use monad_revm::{MonadChainContext, MonadHardfork};
30
31struct MonadSimulation {
32    runner: ScriptRunner<MonadEvmNetwork>,
33    cursor: Option<BlockContext<MonadEvmNetwork>>,
34}
35
36impl MonadSimulation {
37    fn new(runner: ScriptRunner<MonadEvmNetwork>) -> Result<Self> {
38        let cursor = runner.executor.backend().block_context_for_synthetic_transaction()?;
39        Ok(Self { runner, cursor })
40    }
41
42    fn context(&self, tx: &TxEnv) -> Result<MonadChainContext> {
43        self.cursor.as_ref().map_or_else(
44            || self.runner.executor.backend().chain_context_for_synthetic_transaction(tx),
45            |cursor| Ok(cursor.next_transaction(tx)),
46        )
47    }
48
49    fn record(&mut self, tx: TxEnv) {
50        if let Some(cursor) = &mut self.cursor {
51            cursor.record_transaction(tx);
52        }
53    }
54
55    fn prepare_call(
56        &self,
57        from: Address,
58        to: Address,
59        calldata: Bytes,
60        value: U256,
61        authorization_list: Option<Vec<SignedAuthorization>>,
62    ) -> (EvmEnv<MonadHardfork>, TxEnv) {
63        let (env, mut tx) = self.runner.executor.prepare_call_env(from, to.into(), calldata, value);
64        if let Some(authorization_list) = authorization_list {
65            tx.set_signed_authorization(authorization_list);
66            tx.set_tx_type(4);
67        }
68        (env, tx)
69    }
70
71    fn simulate(
72        &mut self,
73        transaction: &TransactionMaybeSigned<Ethereum>,
74        is_fixed_gas_limit: bool,
75    ) -> Result<ScriptResult<Ethereum>> {
76        let from = transaction.from().expect("transaction sender missing");
77        let calldata = transaction.input().cloned();
78        let value = transaction.value().unwrap_or_default();
79        let authorization_list = transaction.authorization_list();
80        let Some(to) = transaction.to() else {
81            let (env, tx) = if is_fixed_gas_limit {
82                self.runner.prepare_fixed_gas_env(transaction)
83            } else {
84                self.runner.executor.prepare_call_env(
85                    from,
86                    TxKind::Create,
87                    calldata.expect("No data for create transaction"),
88                    value,
89                )
90            };
91            let context = self.context(&tx)?;
92            let result = self
93                .runner
94                .executor
95                .transact_with_env_and_context(env, tx, context)
96                .map_err(EvmError::from)
97                .and_then(|raw| {
98                    // Record before result conversion: even a skip/revert has already committed.
99                    self.record(raw.tx_env.clone());
100                    let raw = raw.into_result(None)?;
101                    let Some(Output::Create(_, Some(address))) = raw.out else {
102                        panic!("Deployment succeeded, but no address was returned: {raw:#?}");
103                    };
104                    self.runner.executor.backend_mut().add_persistent_account(address);
105                    Ok(DeployResult { raw, address })
106                });
107            return self.runner.deployment_result(result);
108        };
109
110        if is_fixed_gas_limit {
111            let (env, tx) = self.runner.prepare_fixed_gas_env(transaction);
112            let context = self.context(&tx)?;
113            let result = self.runner.executor.transact_with_env_and_context(env, tx, context)?;
114            self.record(result.tx_env.clone());
115            let gas_used = result.gas_used;
116            return Ok(self.runner.call_result(result, gas_used));
117        }
118
119        let calldata = calldata.unwrap_or_default();
120        let (env, tx) =
121            self.prepare_call(from, to, calldata.clone(), value, authorization_list.clone());
122        let context = self.context(&tx)?;
123        let result = self.runner.executor.call_with_env_and_context(env, tx, context)?;
124        let mut gas_used = result.gas_used;
125        if matches!(result.exit_reason, Some(return_ok!())) {
126            let initial_limit = self.runner.executor.tx_env().gas_limit();
127            let mut search = GasSearch::new(gas_used);
128            while let Some(limit) = search.next_limit() {
129                // Keep the existing gas-probe preparation and authorization behavior unchanged.
130                self.runner.executor.tx_env_mut().set_gas_limit(limit);
131                let (env, tx) = self.prepare_call(from, to, calldata.clone(), value, None);
132                let context = self.context(&tx)?;
133                let result = self.runner.executor.call_with_env_and_context(env, tx, context)?;
134                search.record(limit, needs_more_gas(result.exit_reason));
135            }
136            gas_used = search.gas_used();
137            self.runner.executor.tx_env_mut().set_gas_limit(initial_limit);
138        }
139
140        let (env, tx) = self.prepare_call(from, to, calldata, value, authorization_list);
141        let context = self.context(&tx)?;
142        let result = self.runner.executor.transact_with_env_and_context(env, tx, context)?;
143        self.record(result.tx_env.clone());
144        Ok(self.runner.call_result(result, gas_used))
145    }
146
147    fn advance_block(&mut self) {
148        if let Some(cursor) = &mut self.cursor {
149            cursor.advance_block();
150        }
151        let block = &mut self.runner.executor.evm_env_mut().block_env;
152        block.set_number(block.number + U256::ONE);
153    }
154}
155
156impl PreSimulationState<MonadEvmNetwork> {
157    pub(crate) async fn fill_monad_metadata(
158        self,
159    ) -> Result<FilledTransactionsState<MonadEvmNetwork>> {
160        if self.args.skip_simulation {
161            return self.fill_without_simulation().await;
162        }
163
164        let mut contexts = HashMap::default();
165        for (rpc, context) in self.build_runners().await? {
166            contexts.insert(
167                rpc,
168                RpcSimulationContext {
169                    runner: MonadSimulation::new(context.runner)?,
170                    decoder: context.decoder,
171                },
172            );
173        }
174        let transactions =
175            self.transaction_metadata(|rpc| &contexts.get(rpc).expect("invalid rpc url").decoder)?;
176        self.show_simulation_header()?;
177        let results = transactions
178            .into_iter()
179            .map(|transaction| {
180                let rpc = transaction.rpc.clone();
181                let simulation = &mut contexts.get_mut(&rpc).expect("invalid rpc url").runner;
182
183                let tx = transaction.tx();
184                let to = tx.to();
185                let result = simulation
186                    .simulate(tx, transaction.is_fixed_gas_limit)
187                    .wrap_err("Internal EVM error during simulation")?;
188                if !result.success {
189                    return Ok((rpc, None, false, result.traces));
190                }
191                if self.args.slow {
192                    simulation.advance_block();
193                }
194                let is_noop = if let Some(to) = to {
195                    simulation.runner.executor.is_empty_code(to)?
196                        && tx.value().unwrap_or_default().is_zero()
197                } else {
198                    false
199                };
200                let transaction = ScriptTransactionBuilder::from(transaction)
201                    .with_execution_result(
202                        &result,
203                        self.args.gas_estimate_multiplier,
204                        &self.build_data,
205                    )
206                    .build();
207                eyre::Ok((rpc, Some(transaction), is_noop, result.traces))
208            })
209            .collect::<Vec<_>>();
210        let transactions = self.collect_simulation_results(results, &contexts).await?;
211        Ok(self.into_filled(transactions))
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218    use alloy_eips::eip2930::{AccessList, AccessListItem};
219    use alloy_network::TransactionBuilder;
220    use alloy_primitives::{B256, bytes};
221    use alloy_rpc_types::TransactionRequest;
222    use foundry_evm::{
223        backend::Backend,
224        executors::ExecutorBuilder,
225        opts::EvmOpts,
226        revm::{DatabaseRef, bytecode::Bytecode, interpreter::InstructionResult},
227    };
228    use foundry_evm_networks::NetworkConfigs;
229
230    fn simulation() -> MonadSimulation {
231        let mut env = EvmEnv::<MonadHardfork>::default();
232        // Match the simulation environment produced by EvmOpts.
233        env.cfg_env.disable_nonce_check = true;
234        let executor = ExecutorBuilder::<MonadEvmNetwork>::new().gas_limit(1 << 20).build(
235            env,
236            TxEnv::default(),
237            Backend::spawn(None).unwrap(),
238            NetworkConfigs::with_monad(),
239        );
240        let runner = ScriptRunner::new(executor, EvmOpts::default());
241        let mut simulation = MonadSimulation::new(runner).unwrap();
242        assert!(simulation.cursor.is_none());
243        simulation.cursor = Some(BlockContext::new(Vec::new(), Vec::new(), Vec::new()));
244        simulation
245    }
246
247    fn assert_transaction_count(simulation: &MonadSimulation, count: usize) {
248        let cursor = simulation.cursor.as_ref().unwrap();
249        assert!(cursor.clone().before_transaction(count).is_ok());
250        assert!(cursor.clone().before_transaction(count + 1).is_err());
251    }
252
253    #[test]
254    fn calls_and_gas_probes_record_only_committed_transactions() {
255        let other = simulation();
256        let mut simulation = simulation();
257        let sender = Address::with_last_byte(0x42);
258        let recipient = Address::with_last_byte(0x43);
259        simulation.runner.executor.set_balance(sender, U256::MAX).unwrap();
260        let result =
261            simulation.simulate(&transaction(sender, Some(recipient), None), false).unwrap();
262        assert!(result.success);
263        assert_transaction_count(&simulation, 1);
264        assert_eq!(simulation.runner.executor.get_nonce(sender).unwrap(), 1);
265
266        simulation.advance_block();
267        assert_transaction_count(&simulation, 0);
268        assert_eq!(simulation.runner.executor.evm_env().block_env.number, U256::ONE);
269        assert_transaction_count(&other, 0);
270    }
271
272    #[test]
273    fn deployment_and_reverted_call_both_advance_the_cursor() {
274        let mut simulation = simulation();
275        let sender = Address::with_last_byte(0x42);
276        simulation.runner.executor.set_balance(sender, U256::MAX).unwrap();
277        // Deploy runtime code that always reverts.
278        let initcode = bytes!("6005600c60003960056000f360006000fd");
279        let deployment =
280            simulation.simulate(&transaction(sender, None, Some(initcode)), false).unwrap();
281        assert!(deployment.success);
282        assert_transaction_count(&simulation, 1);
283        let address = deployment.address.unwrap();
284        assert!(!simulation.runner.executor.is_empty_code(address).unwrap());
285        let result = simulation.simulate(&transaction(sender, Some(address), None), false).unwrap();
286        assert!(!result.success);
287        assert_transaction_count(&simulation, 2);
288        assert_eq!(simulation.runner.executor.get_nonce(sender).unwrap(), 2);
289    }
290
291    #[test]
292    fn validation_failure_does_not_advance_the_cursor() {
293        let mut simulation = simulation();
294        simulation.runner.executor.set_gas_limit(1);
295        let sender = Address::with_last_byte(0x42);
296        let result = simulation
297            .simulate(&transaction(sender, Some(Address::with_last_byte(0x43)), None), false);
298        assert!(result.is_err());
299        assert_transaction_count(&simulation, 0);
300        assert_eq!(simulation.runner.executor.get_nonce(sender).unwrap(), 0);
301    }
302
303    #[test]
304    fn authorization_preparation_preserves_explicit_empty_list() {
305        let simulation = simulation();
306        let (_, tx) = simulation.prepare_call(
307            Address::with_last_byte(0x42),
308            Address::with_last_byte(0x43),
309            Bytes::new(),
310            U256::ZERO,
311            Some(Vec::new()),
312        );
313        assert_eq!(tx.tx_type(), 4);
314        assert_transaction_count(&simulation, 0);
315    }
316
317    fn transaction(
318        from: Address,
319        to: Option<Address>,
320        input: Option<Bytes>,
321    ) -> TransactionMaybeSigned<Ethereum> {
322        TransactionMaybeSigned::new(
323            TransactionRequest::default()
324                .with_from(from)
325                .with_kind(to.map_or(TxKind::Create, TxKind::Call))
326                .with_input(input.unwrap_or_default()),
327        )
328    }
329
330    #[test]
331    fn fixed_gas_calls_and_deployments_preserve_block_gas_limit() {
332        let mut simulation = simulation();
333        let sender = Address::with_last_byte(0x42);
334        let block_gas_limit = 30_000_000;
335        simulation.runner.executor.set_balance(sender, U256::MAX).unwrap();
336        simulation.runner.executor.evm_env_mut().block_env.set_gas_limit(block_gas_limit);
337        let executor_gas_limit = simulation.runner.executor.gas_limit();
338
339        // The constructor stores GASLIMIT in slot zero; the runtime returns GASLIMIT.
340        let initcode = bytes!("456000556009601060003960096000f34560005260206000f3");
341        let deployment = simulation
342            .simulate(&fixed_transaction(sender, None, Some(initcode), 100_000), true)
343            .unwrap();
344        assert!(deployment.success);
345        let address = deployment.address.unwrap();
346        assert_eq!(
347            simulation.runner.executor.backend().storage_ref(address, U256::ZERO).unwrap(),
348            U256::from(block_gas_limit),
349        );
350        assert_transaction_count(&simulation, 1);
351        assert_eq!(simulation.runner.executor.get_nonce(sender).unwrap(), 1);
352
353        let result = simulation
354            .simulate(&fixed_transaction(sender, Some(address), None, 50_000), true)
355            .unwrap();
356        assert!(result.success);
357        assert_eq!(U256::from_be_slice(&result.returned), U256::from(block_gas_limit));
358        assert_transaction_count(&simulation, 2);
359        assert_eq!(simulation.runner.executor.get_nonce(sender).unwrap(), 2);
360        assert_eq!(simulation.runner.executor.gas_limit(), executor_gas_limit);
361        assert_eq!(simulation.runner.executor.evm_env().block_env.gas_limit, block_gas_limit);
362    }
363
364    #[test]
365    fn fixed_gas_access_lists_do_not_leak_to_following_transactions() {
366        let mut simulation = simulation();
367        simulation.runner.executor.set_spec_id(MonadHardfork::MonadTen);
368        let sender = Address::with_last_byte(0x42);
369        let recipient = Address::with_last_byte(0x43);
370        simulation.runner.executor.set_balance(sender, U256::MAX).unwrap();
371        let executor_gas_limit = simulation.runner.executor.gas_limit();
372
373        // Read 32 distinct storage pages: warm reads fit the cap, while cold reads do not.
374        // MonadTen warms all 128 slots in a page together.
375        let mut code = Vec::new();
376        for page in 0..32u16 {
377            code.push(0x61);
378            code.extend_from_slice(&(page * 128).to_be_bytes());
379            code.extend_from_slice(&[0x54, 0x50]);
380        }
381        code.push(0x00);
382        simulation.runner.executor.set_code(recipient, Bytecode::new_raw(code.into())).unwrap();
383        let access_list = AccessList(vec![AccessListItem {
384            address: recipient,
385            storage_keys: (0..32).map(|page| B256::from(U256::from(page * 128))).collect(),
386        }]);
387        let mut transaction = TransactionMaybeSigned::new(
388            TransactionRequest::default()
389                .with_from(sender)
390                .with_to(recipient)
391                .with_gas_price(0)
392                .with_gas_limit(100_000)
393                .with_access_list(access_list),
394        );
395        let result = simulation.simulate(&transaction, true).unwrap();
396        assert!(result.success);
397        assert_transaction_count(&simulation, 1);
398        assert_eq!(simulation.runner.executor.get_nonce(sender).unwrap(), 1);
399
400        transaction.as_unsigned_mut().unwrap().access_list = None;
401        let result = simulation.simulate(&transaction, true).unwrap();
402        assert!(!result.success);
403        assert_eq!(result.exit_reason, Some(InstructionResult::OutOfGas));
404        assert_transaction_count(&simulation, 2);
405        assert_eq!(simulation.runner.executor.get_nonce(sender).unwrap(), 2);
406
407        let result = simulation.simulate(&transaction, false).unwrap();
408        assert!(result.success);
409        assert_transaction_count(&simulation, 3);
410        assert_eq!(simulation.runner.executor.get_nonce(sender).unwrap(), 3);
411        assert_eq!(simulation.runner.executor.gas_limit(), executor_gas_limit);
412    }
413
414    #[test]
415    fn fixed_gas_validation_failure_does_not_advance_the_cursor() {
416        let mut simulation = simulation();
417        let sender = Address::with_last_byte(0x42);
418        let recipient = Address::with_last_byte(0x43);
419        simulation.runner.executor.set_balance(sender, U256::MAX).unwrap();
420        let result =
421            simulation.simulate(&fixed_transaction(sender, Some(recipient), None, 1), true);
422        assert!(result.is_err());
423        assert_transaction_count(&simulation, 0);
424        assert_eq!(simulation.runner.executor.get_nonce(sender).unwrap(), 0);
425
426        let result = simulation
427            .simulate(&fixed_transaction(sender, Some(recipient), None, 21_000), true)
428            .unwrap();
429        assert!(result.success);
430        assert_transaction_count(&simulation, 1);
431        assert_eq!(simulation.runner.executor.get_nonce(sender).unwrap(), 1);
432    }
433
434    fn fixed_transaction(
435        from: Address,
436        to: Option<Address>,
437        input: Option<Bytes>,
438        gas_limit: u64,
439    ) -> TransactionMaybeSigned<Ethereum> {
440        let mut transaction = transaction(from, to, input);
441        transaction.as_unsigned_mut().unwrap().set_gas_limit(gas_limit);
442        transaction
443    }
444
445    #[test]
446    fn fixed_gas_failed_deployments_advance_the_cursor() {
447        let mut simulation = simulation();
448        let sender = Address::with_last_byte(0x42);
449        simulation.runner.executor.set_balance(sender, U256::MAX).unwrap();
450
451        for (index, (initcode, exit_reason)) in [
452            (bytes!("60006000fd"), InstructionResult::Revert),
453            (bytes!("5b600056"), InstructionResult::OutOfGas),
454        ]
455        .into_iter()
456        .enumerate()
457        {
458            let result = simulation
459                .simulate(&fixed_transaction(sender, None, Some(initcode), 60_000), true)
460                .unwrap();
461            assert!(!result.success);
462            assert_eq!(result.exit_reason, Some(exit_reason));
463            assert_eq!(result.address, Some(Address::ZERO));
464            assert_transaction_count(&simulation, index + 1);
465            assert_eq!(simulation.runner.executor.get_nonce(sender).unwrap(), index as u64 + 1);
466        }
467    }
468}