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, RpcContexts, RpcSimulationContext, context_for_rpc};
4use crate::{
5    ScriptResult,
6    runner::{GasSearch, ScriptRunner},
7    simulate::FilledTransactionsState,
8    transaction::ScriptTransactionBuilder,
9};
10use alloy_eips::eip7702::SignedAuthorization;
11use alloy_network::Ethereum;
12use alloy_primitives::{Address, Bytes, TxKind, U256, map::HashMap};
13use eyre::{Result, WrapErr};
14use foundry_evm::{
15    backend::DatabaseExt,
16    core::{
17        FoundryBlock, FoundryTransaction,
18        evm::{BlockContext, ChainFor, EvmEnvFor, MonadEvmNetwork, TxEnvFor},
19    },
20    executors::{DeployResult, EvmError},
21    revm::{context::Transaction, context_interface::result::Output, interpreter::return_ok},
22};
23use futures::future::join_all;
24use parking_lot::RwLock;
25use std::sync::Arc;
26
27struct MonadSimulation {
28    runner: ScriptRunner<MonadEvmNetwork>,
29    cursor: Option<BlockContext<MonadEvmNetwork>>,
30}
31
32impl MonadSimulation {
33    fn new(runner: ScriptRunner<MonadEvmNetwork>) -> Result<Self> {
34        let cursor = runner.executor.backend().block_context_for_synthetic_transaction()?;
35        Ok(Self { runner, cursor })
36    }
37
38    fn context(&self, tx: &TxEnvFor<MonadEvmNetwork>) -> Result<ChainFor<MonadEvmNetwork>> {
39        self.cursor.as_ref().map_or_else(
40            || self.runner.executor.backend().chain_context_for_synthetic_transaction(tx),
41            |cursor| Ok(cursor.next_transaction(tx)),
42        )
43    }
44
45    fn record(&mut self, tx: TxEnvFor<MonadEvmNetwork>) {
46        if let Some(cursor) = &mut self.cursor {
47            cursor.record_transaction(tx);
48        }
49    }
50
51    fn prepare_call(
52        &self,
53        from: Address,
54        to: Address,
55        calldata: Bytes,
56        value: U256,
57        authorization_list: Option<Vec<SignedAuthorization>>,
58    ) -> (EvmEnvFor<MonadEvmNetwork>, TxEnvFor<MonadEvmNetwork>) {
59        let (env, mut tx) = self.runner.executor.prepare_call_env(from, to.into(), calldata, value);
60        if let Some(authorization_list) = authorization_list {
61            tx.set_signed_authorization(authorization_list);
62            tx.set_tx_type(4);
63        }
64        (env, tx)
65    }
66
67    fn simulate(
68        &mut self,
69        from: Address,
70        to: Option<Address>,
71        calldata: Option<Bytes>,
72        value: Option<U256>,
73        authorization_list: Option<Vec<SignedAuthorization>>,
74    ) -> Result<ScriptResult<Ethereum>> {
75        let value = value.unwrap_or_default();
76        let Some(to) = to else {
77            let (env, tx) = self.runner.executor.prepare_call_env(
78                from,
79                TxKind::Create,
80                calldata.expect("No data for create transaction"),
81                value,
82            );
83            let context = self.context(&tx)?;
84            let result = self
85                .runner
86                .executor
87                .transact_with_env_and_context(env, tx, context)
88                .map_err(EvmError::from)
89                .and_then(|raw| {
90                    // Record before result conversion: even a skip/revert has already committed.
91                    self.record(raw.tx_env.clone());
92                    let raw = raw.into_result(None)?;
93                    let Some(Output::Create(_, Some(address))) = raw.out else {
94                        panic!("Deployment succeeded, but no address was returned: {raw:#?}");
95                    };
96                    self.runner.executor.backend_mut().add_persistent_account(address);
97                    Ok(DeployResult { raw, address })
98                });
99            return self.runner.deployment_result(result);
100        };
101
102        let calldata = calldata.unwrap_or_default();
103        let (env, tx) =
104            self.prepare_call(from, to, calldata.clone(), value, authorization_list.clone());
105        let context = self.context(&tx)?;
106        let result = self.runner.executor.call_with_env_and_context(env, tx, context)?;
107        let mut gas_used = result.gas_used;
108        if matches!(result.exit_reason, Some(return_ok!())) {
109            let initial_limit = self.runner.executor.tx_env().gas_limit();
110            let mut search = GasSearch::new(gas_used);
111            while let Some(limit) = search.next_limit() {
112                // Keep the existing gas-probe preparation and authorization behavior unchanged.
113                self.runner.executor.tx_env_mut().set_gas_limit(limit);
114                let (env, tx) = self.prepare_call(from, to, calldata.clone(), value, None);
115                let context = self.context(&tx)?;
116                let result = self.runner.executor.call_with_env_and_context(env, tx, context)?;
117                search.record(limit, result.exit_reason);
118            }
119            gas_used = search.gas_used();
120            self.runner.executor.tx_env_mut().set_gas_limit(initial_limit);
121        }
122
123        let (env, tx) = self.prepare_call(from, to, calldata, value, authorization_list);
124        let context = self.context(&tx)?;
125        let result = self.runner.executor.transact_with_env_and_context(env, tx, context)?;
126        self.record(result.tx_env.clone());
127        Ok(self.runner.call_result(result, gas_used))
128    }
129
130    fn advance_block(&mut self) {
131        if let Some(cursor) = &mut self.cursor {
132            cursor.advance_block();
133        }
134        let block = &mut self.runner.executor.evm_env_mut().block_env;
135        block.set_number(block.number + U256::from(1));
136    }
137}
138
139impl PreSimulationState<MonadEvmNetwork> {
140    pub(crate) async fn fill_monad_metadata(
141        self,
142    ) -> Result<FilledTransactionsState<MonadEvmNetwork>> {
143        if self.args.skip_simulation {
144            return self.fill_without_simulation().await;
145        }
146
147        let mut contexts = HashMap::default();
148        for (rpc, context) in self.build_runners().await? {
149            contexts.insert(
150                rpc,
151                RpcSimulationContext {
152                    runner: RwLock::new(MonadSimulation::new(context.runner.into_inner())?),
153                    decoder: context.decoder,
154                },
155            );
156        }
157        let contexts = Arc::new(contexts);
158        let transactions =
159            self.transaction_metadata(&RpcContexts::Simulation(Arc::clone(&contexts)))?;
160        let futs = transactions
161            .into_iter()
162            .map(|mut transaction| async {
163                let rpc = transaction.rpc.clone();
164                let context = context_for_rpc(&contexts, &rpc);
165                let mut simulation = context.runner.write();
166                let tx = transaction.tx_mut();
167                let to = tx.to();
168                let result = simulation
169                    .simulate(
170                        tx.from()
171                            .expect("transaction doesn't have a `from` address at execution time"),
172                        to,
173                        tx.input().cloned(),
174                        tx.value(),
175                        tx.authorization_list(),
176                    )
177                    .wrap_err("Internal EVM error during simulation")?;
178                if !result.success {
179                    return Ok((rpc, None, false, result.traces));
180                }
181                if self.args.slow {
182                    simulation.advance_block();
183                }
184                let is_noop = if let Some(to) = to {
185                    simulation.runner.executor.is_empty_code(to)?
186                        && tx.value().unwrap_or_default().is_zero()
187                } else {
188                    false
189                };
190                let transaction = ScriptTransactionBuilder::from(transaction)
191                    .with_execution_result(
192                        &result,
193                        self.args.gas_estimate_multiplier,
194                        &self.build_data,
195                    )
196                    .build();
197                eyre::Ok((rpc, Some(transaction), is_noop, result.traces))
198            })
199            .collect::<Vec<_>>();
200        self.show_simulation_header()?;
201        let transactions = self.collect_simulation_results(join_all(futs).await, &contexts).await?;
202        Ok(self.into_filled(transactions))
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209    use alloy_primitives::hex;
210    use foundry_evm::{backend::Backend, executors::ExecutorBuilder, opts::EvmOpts};
211    use foundry_evm_networks::NetworkConfigs;
212
213    fn simulation() -> MonadSimulation {
214        let mut env = EvmEnvFor::<MonadEvmNetwork>::default();
215        // Match the simulation environment produced by EvmOpts.
216        env.cfg_env.disable_nonce_check = true;
217        let executor = ExecutorBuilder::<MonadEvmNetwork>::new().gas_limit(1 << 20).build(
218            env,
219            TxEnvFor::<MonadEvmNetwork>::default(),
220            Backend::spawn(None).unwrap(),
221            NetworkConfigs::with_monad(),
222        );
223        let runner = ScriptRunner::new(executor, EvmOpts::default());
224        let mut simulation = MonadSimulation::new(runner).unwrap();
225        assert!(simulation.cursor.is_none());
226        simulation.cursor = Some(BlockContext::new(Vec::new(), Vec::new(), Vec::new()));
227        simulation
228    }
229
230    fn assert_transaction_count(simulation: &MonadSimulation, count: usize) {
231        let cursor = simulation.cursor.as_ref().unwrap();
232        assert!(cursor.clone().before_transaction(count).is_ok());
233        assert!(cursor.clone().before_transaction(count + 1).is_err());
234    }
235
236    #[test]
237    fn calls_and_gas_probes_record_only_committed_transactions() {
238        let other = simulation();
239        let mut simulation = simulation();
240        let sender = Address::with_last_byte(0x42);
241        let recipient = Address::with_last_byte(0x43);
242        simulation.runner.executor.set_balance(sender, U256::MAX).unwrap();
243        let result = simulation.simulate(sender, Some(recipient), None, None, None).unwrap();
244        assert!(result.success);
245        assert_transaction_count(&simulation, 1);
246        assert_eq!(simulation.runner.executor.get_nonce(sender).unwrap(), 1);
247
248        simulation.advance_block();
249        assert_transaction_count(&simulation, 0);
250        assert_eq!(simulation.runner.executor.evm_env().block_env.number, U256::from(1));
251        assert_transaction_count(&other, 0);
252    }
253
254    #[test]
255    fn deployment_and_reverted_call_both_advance_the_cursor() {
256        let mut simulation = simulation();
257        let sender = Address::with_last_byte(0x42);
258        simulation.runner.executor.set_balance(sender, U256::MAX).unwrap();
259        // Deploy runtime code that always reverts.
260        let initcode = Bytes::from_static(&hex!("6005600c60003960056000f360006000fd"));
261        let deployment = simulation.simulate(sender, None, Some(initcode), None, None).unwrap();
262        assert!(deployment.success);
263        assert_transaction_count(&simulation, 1);
264        let address = deployment.address.unwrap();
265        assert!(!simulation.runner.executor.is_empty_code(address).unwrap());
266        let result = simulation.simulate(sender, Some(address), None, None, None).unwrap();
267        assert!(!result.success);
268        assert_transaction_count(&simulation, 2);
269        assert_eq!(simulation.runner.executor.get_nonce(sender).unwrap(), 2);
270    }
271
272    #[test]
273    fn validation_failure_does_not_advance_the_cursor() {
274        let mut simulation = simulation();
275        simulation.runner.executor.set_gas_limit(1);
276        let sender = Address::with_last_byte(0x42);
277        let result =
278            simulation.simulate(sender, Some(Address::with_last_byte(0x43)), None, None, None);
279        assert!(result.is_err());
280        assert_transaction_count(&simulation, 0);
281        assert_eq!(simulation.runner.executor.get_nonce(sender).unwrap(), 0);
282    }
283
284    #[test]
285    fn authorization_preparation_preserves_explicit_empty_list() {
286        let simulation = simulation();
287        let (_, tx) = simulation.prepare_call(
288            Address::with_last_byte(0x42),
289            Address::with_last_byte(0x43),
290            Bytes::new(),
291            U256::ZERO,
292            Some(Vec::new()),
293        );
294        assert_eq!(tx.tx_type(), 4);
295        assert_transaction_count(&simulation, 0);
296    }
297}