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