Skip to main content

forge_script/
runner.rs

1use super::{ScriptConfig, ScriptResult};
2use crate::build::ScriptPredeployLibraries;
3use alloy_eips::eip7702::SignedAuthorization;
4use alloy_evm::revm::context::Transaction;
5use alloy_network::TransactionBuilder;
6use alloy_primitives::{Address, Bytes, U256, map::AddressHashMap};
7use eyre::Result;
8use foundry_cheatcodes::BroadcastableTransaction;
9use foundry_common::{LIBRARY_DEPLOYER, TransactionMaybeSigned};
10use foundry_config::Config;
11use foundry_evm::{
12    constants::CALLER,
13    core::{
14        FoundryTransaction,
15        evm::{FoundryEvmNetwork, TransactionRequestFor},
16    },
17    executors::{DeployResult, EvmError, ExecutionErr, Executor, RawCallResult},
18    opts::EvmOpts,
19    revm::interpreter::{InstructionResult, return_ok},
20    traces::{TraceKind, Traces},
21};
22use std::collections::VecDeque;
23
24/// Drives script execution
25#[derive(Debug)]
26pub struct ScriptRunner<FEN: FoundryEvmNetwork> {
27    pub executor: Executor<FEN>,
28    pub evm_opts: EvmOpts,
29    collect_debug_bytecodes: bool,
30}
31
32impl<FEN: FoundryEvmNetwork> ScriptRunner<FEN> {
33    pub const fn new(executor: Executor<FEN>, evm_opts: EvmOpts) -> Self {
34        Self { executor, evm_opts, collect_debug_bytecodes: false }
35    }
36
37    pub const fn with_debug_bytecodes(mut self, collect_debug_bytecodes: bool) -> Self {
38        self.collect_debug_bytecodes = collect_debug_bytecodes;
39        self
40    }
41
42    fn maybe_debug_bytecodes(
43        &self,
44        debug_bytecodes: AddressHashMap<Bytes>,
45    ) -> AddressHashMap<Bytes> {
46        if self.collect_debug_bytecodes { debug_bytecodes } else { Default::default() }
47    }
48
49    fn extend_debug_bytecodes(
50        &self,
51        target: &mut AddressHashMap<Bytes>,
52        debug_bytecodes: AddressHashMap<Bytes>,
53    ) {
54        if self.collect_debug_bytecodes {
55            target.extend(debug_bytecodes);
56        }
57    }
58
59    fn deploy_local_libraries(
60        &mut self,
61        libraries: &[foundry_linking::LinkedLibrary],
62        debug_bytecodes: &mut AddressHashMap<Bytes>,
63    ) -> Result<()> {
64        if libraries.is_empty() {
65            return Ok(());
66        }
67        let balance = self.executor.get_balance(LIBRARY_DEPLOYER)?;
68        let nonce = self.executor.get_nonce(LIBRARY_DEPLOYER)?;
69        self.executor.set_balance(LIBRARY_DEPLOYER, U256::MAX)?;
70        self.executor.set_nonce(LIBRARY_DEPLOYER, 0)?;
71        for library in libraries {
72            let DeployResult { address, raw } = self
73                .executor
74                .deploy(LIBRARY_DEPLOYER, library.bytecode.clone(), U256::ZERO, None)
75                .map_err(|err| eyre::eyre!("couldn't deploy local library: {err}"))?;
76            eyre::ensure!(
77                library.address == address,
78                "local library deployed at an unexpected address"
79            );
80            self.extend_debug_bytecodes(debug_bytecodes, raw.debug_bytecodes);
81        }
82        self.executor.set_balance(LIBRARY_DEPLOYER, balance)?;
83        self.executor.set_nonce(LIBRARY_DEPLOYER, nonce)?;
84        Ok(())
85    }
86
87    /// Deploys the libraries and broadcast contract. Calls setUp method if requested.
88    pub fn setup(
89        &mut self,
90        libraries: &ScriptPredeployLibraries,
91        code: Bytes,
92        setup: bool,
93        script_config: &ScriptConfig<FEN>,
94        is_broadcast: bool,
95    ) -> Result<(Address, ScriptResult<FEN::Network>)> {
96        trace!(target: "script", "executing setUP()");
97
98        if !is_broadcast {
99            if self.evm_opts.sender == Config::DEFAULT_SENDER {
100                // We max out their balance so that they can deploy and make calls.
101                self.executor.set_balance(self.evm_opts.sender, U256::MAX)?;
102            }
103
104            if script_config.evm_opts.fork_url.is_none()
105                && !script_config.evm_opts.networks.is_tempo()
106            {
107                self.executor.deploy_create2_deployer()?;
108            }
109        }
110
111        let sender_nonce = script_config.sender_nonce;
112        self.executor.set_nonce(self.evm_opts.sender, sender_nonce)?;
113
114        // We max out their balance so that they can deploy and make calls.
115        self.executor.set_balance(CALLER, U256::MAX)?;
116
117        let mut library_transactions = VecDeque::new();
118        let mut traces = Traces::default();
119        let mut debug_bytecodes: AddressHashMap<Bytes> = Default::default();
120
121        // Deploy libraries
122        match libraries {
123            ScriptPredeployLibraries::Default { onchain, local } => {
124                self.deploy_local_libraries(local, &mut debug_bytecodes)?;
125                for library in onchain {
126                    let code = &library.bytecode;
127                    let RawCallResult {
128                        traces: deploy_traces,
129                        debug_bytecodes: deploy_debug_bytecodes,
130                        ..
131                    } = self
132                        .executor
133                        .deploy(self.evm_opts.sender, code.clone(), U256::ZERO, None)
134                        .map_err(|err| eyre::eyre!("couldn't deploy library: {err}"))?
135                        .raw;
136
137                    self.extend_debug_bytecodes(&mut debug_bytecodes, deploy_debug_bytecodes);
138
139                    if let Some(deploy_traces) = deploy_traces {
140                        traces.push((TraceKind::Deployment, deploy_traces));
141                    }
142
143                    let mut tx_req = TransactionRequestFor::<FEN>::default()
144                        .with_from(self.evm_opts.sender)
145                        .with_input(code.clone())
146                        .with_nonce(sender_nonce + library_transactions.len() as u64);
147
148                    script_config.tempo.apply::<FEN::Network>(&mut tx_req, None);
149
150                    library_transactions.push_back(BroadcastableTransaction {
151                        rpc: self.evm_opts.fork_url.clone(),
152                        transaction: TransactionMaybeSigned::new(tx_req),
153                    })
154                }
155            }
156            ScriptPredeployLibraries::Create2 { onchain, salt, local } => {
157                self.deploy_local_libraries(local, &mut debug_bytecodes)?;
158                let create2_deployer = self.executor.create2_deployer();
159                for library in onchain {
160                    let address =
161                        create2_deployer.create2_from_code(salt, library.bytecode.as_ref());
162                    // Skip if already deployed
163                    if !self.executor.is_empty_code(address)? {
164                        continue;
165                    }
166                    let calldata = [salt.as_ref(), library.bytecode.as_ref()].concat();
167                    let RawCallResult {
168                        traces: deploy_traces,
169                        debug_bytecodes: deploy_debug_bytecodes,
170                        ..
171                    } = self
172                        .executor
173                        .transact_raw(
174                            self.evm_opts.sender,
175                            create2_deployer,
176                            calldata.clone().into(),
177                            U256::ZERO,
178                        )
179                        .map_err(|err| eyre::eyre!("couldn't deploy library: {err}"))?;
180
181                    self.extend_debug_bytecodes(&mut debug_bytecodes, deploy_debug_bytecodes);
182
183                    if let Some(deploy_traces) = deploy_traces {
184                        traces.push((TraceKind::Deployment, deploy_traces));
185                    }
186
187                    let mut tx_req = TransactionRequestFor::<FEN>::default()
188                        .with_from(self.evm_opts.sender)
189                        .with_input(calldata)
190                        .with_nonce(sender_nonce + library_transactions.len() as u64)
191                        .with_to(create2_deployer);
192
193                    script_config.tempo.apply::<FEN::Network>(&mut tx_req, None);
194
195                    library_transactions.push_back(BroadcastableTransaction {
196                        rpc: self.evm_opts.fork_url.clone(),
197                        transaction: TransactionMaybeSigned::new(tx_req),
198                    });
199                }
200
201                // Sender nonce is not incremented when performing CALLs. We need to manually
202                // increase it.
203                self.executor.set_nonce(
204                    self.evm_opts.sender,
205                    sender_nonce + library_transactions.len() as u64,
206                )?;
207            }
208        };
209
210        let address = CALLER.create(self.executor.get_nonce(CALLER)?);
211
212        // Set the contracts initial balance before deployment, so it is available during the
213        // construction
214        self.executor.set_balance(address, self.evm_opts.initial_balance)?;
215
216        // HACK: if the current sender is the default script sender (which is a default value), we
217        // set its nonce to a very large value before deploying the script contract. This
218        // ensures that the nonce increase during this CREATE does not affect deployment
219        // addresses of contracts that are deployed in the script, Otherwise, we'd have a
220        // nonce mismatch during script execution and onchain simulation, potentially
221        // resulting in weird errors like <https://github.com/foundry-rs/foundry/issues/8960>.
222        let prev_sender_nonce = self.executor.get_nonce(self.evm_opts.sender)?;
223        if self.evm_opts.sender == CALLER {
224            self.executor.set_nonce(self.evm_opts.sender, u64::MAX / 2)?;
225        }
226
227        // Deploy an instance of the contract
228        let DeployResult {
229            address,
230            raw:
231                RawCallResult {
232                    mut logs,
233                    traces: constructor_traces,
234                    debug_bytecodes: constructor_debug_bytecodes,
235                    ..
236                },
237        } = self
238            .executor
239            .deploy(CALLER, code, U256::ZERO, None)
240            .map_err(|err| eyre::eyre!("Failed to deploy script:\n{}", err))?;
241
242        if self.evm_opts.sender == CALLER {
243            self.executor.set_nonce(self.evm_opts.sender, prev_sender_nonce)?;
244        }
245
246        // set script address to be used by execution inspector
247        if script_config.config.script_execution_protection {
248            self.executor.set_script_execution(address);
249        }
250
251        traces.extend(constructor_traces.map(|traces| (TraceKind::Deployment, traces)));
252        self.extend_debug_bytecodes(&mut debug_bytecodes, constructor_debug_bytecodes);
253
254        // Optionally call the `setUp` function
255        let (success, gas_used, labeled_addresses, transactions) = if setup {
256            let result = match self.executor.setup(Some(self.evm_opts.sender), address, None) {
257                Ok(result) => result,
258                Err(EvmError::Execution(err)) => err.raw,
259                Err(err) => return Err(err.into()),
260            };
261            let RawCallResult {
262                reverted,
263                traces: setup_traces,
264                labels,
265                logs: setup_logs,
266                gas_used,
267                debug_bytecodes: setup_debug_bytecodes,
268                transactions: setup_transactions,
269                ..
270            } = result;
271            traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces)));
272            logs.extend_from_slice(&setup_logs);
273            self.extend_debug_bytecodes(&mut debug_bytecodes, setup_debug_bytecodes);
274            if let Some(txs) = setup_transactions {
275                library_transactions.extend(txs);
276            }
277            (!reverted, gas_used, labels, Some(library_transactions))
278        } else {
279            self.executor.backend_mut().set_test_contract(address);
280            (true, 0, Default::default(), Some(library_transactions))
281        };
282
283        Ok((
284            address,
285            ScriptResult {
286                returned: Bytes::new(),
287                success,
288                gas_used,
289                labeled_addresses,
290                debug_bytecodes: self.maybe_debug_bytecodes(debug_bytecodes),
291                transactions,
292                logs,
293                traces,
294                address: None,
295                ..Default::default()
296            },
297        ))
298    }
299
300    /// Executes the method that will collect all broadcastable transactions.
301    pub fn script(
302        &mut self,
303        address: Address,
304        calldata: Bytes,
305    ) -> Result<ScriptResult<FEN::Network>> {
306        self.call(self.evm_opts.sender, address, calldata, U256::ZERO, None, false)
307    }
308
309    /// Runs a broadcastable transaction locally and persists its state.
310    pub fn simulate(
311        &mut self,
312        from: Address,
313        to: Option<Address>,
314        calldata: Option<Bytes>,
315        value: Option<U256>,
316        authorization_list: Option<Vec<SignedAuthorization>>,
317    ) -> Result<ScriptResult<FEN::Network>> {
318        if let Some(to) = to {
319            self.call(
320                from,
321                to,
322                calldata.unwrap_or_default(),
323                value.unwrap_or(U256::ZERO),
324                authorization_list,
325                true,
326            )
327        } else {
328            let res = self.executor.deploy(
329                from,
330                calldata.expect("No data for create transaction"),
331                value.unwrap_or(U256::ZERO),
332                None,
333            );
334            self.deployment_result(res)
335        }
336    }
337
338    pub(crate) fn deployment_result(
339        &self,
340        res: Result<DeployResult<FEN>, EvmError<FEN>>,
341    ) -> Result<ScriptResult<FEN::Network>> {
342        let (address, RawCallResult { gas_used, logs, traces, debug_bytecodes, exit_reason, .. }) =
343            match res {
344                Ok(DeployResult { address, raw }) => (address, raw),
345                Err(EvmError::Execution(err)) => {
346                    let ExecutionErr { raw, reason } = *err;
347                    sh_err!("Failed with `{reason}`:\n")?;
348                    (Address::ZERO, raw)
349                }
350                Err(e) => {
351                    eyre::bail!("Failed deploying contract: {e:?}");
352                }
353            };
354
355        Ok(ScriptResult {
356            returned: Bytes::new(),
357            success: !address.is_zero(),
358            gas_used,
359            logs,
360            debug_bytecodes: self.maybe_debug_bytecodes(debug_bytecodes),
361            // Manually adjust gas for the trace to add back the stipend/real used gas
362            traces: traces.map(|traces| vec![(TraceKind::Execution, traces)]).unwrap_or_default(),
363            exit_reason,
364            address: Some(address),
365            ..Default::default()
366        })
367    }
368
369    /// Executes the call
370    ///
371    /// This will commit the changes if `commit` is true.
372    ///
373    /// This will return _estimated_ gas instead of the precise gas the call would consume, so it
374    /// can be used as `gas_limit`.
375    fn call(
376        &mut self,
377        from: Address,
378        to: Address,
379        calldata: Bytes,
380        value: U256,
381        authorization_list: Option<Vec<SignedAuthorization>>,
382        commit: bool,
383    ) -> Result<ScriptResult<FEN::Network>> {
384        let mut res = if let Some(authorization_list) = &authorization_list {
385            self.executor.call_raw_with_authorization(
386                from,
387                to,
388                calldata.clone(),
389                value,
390                authorization_list.clone(),
391            )?
392        } else {
393            self.executor.call_raw(from, to, calldata.clone(), value)?
394        };
395        let mut gas_used = res.gas_used;
396
397        // We should only need to calculate realistic gas costs when preparing to broadcast
398        // something. This happens during the onchain simulation stage, where we commit each
399        // collected transactions.
400        //
401        // Otherwise don't re-execute, or some usecases might be broken: https://github.com/foundry-rs/foundry/issues/3921
402        if commit {
403            gas_used = self.search_optimal_gas_usage(&res, from, to, &calldata, value)?;
404            res = if let Some(authorization_list) = authorization_list {
405                self.executor.transact_raw_with_authorization(
406                    from,
407                    to,
408                    calldata,
409                    value,
410                    authorization_list,
411                )?
412            } else {
413                self.executor.transact_raw(from, to, calldata, value)?
414            }
415        }
416
417        Ok(self.call_result(res, gas_used))
418    }
419
420    pub(crate) fn call_result(
421        &self,
422        res: RawCallResult<FEN>,
423        gas_used: u64,
424    ) -> ScriptResult<FEN::Network> {
425        let RawCallResult {
426            result,
427            reverted,
428            logs,
429            traces,
430            labels,
431            transactions,
432            debug_bytecodes,
433            exit_reason,
434            cheatcodes,
435            ..
436        } = res;
437        let breakpoints = cheatcodes.map(|cheats| cheats.breakpoints).unwrap_or_default();
438
439        ScriptResult {
440            returned: result,
441            success: !reverted,
442            gas_used,
443            logs,
444            debug_bytecodes: self.maybe_debug_bytecodes(debug_bytecodes),
445            traces: traces
446                .map(|traces| {
447                    // Manually adjust gas for the trace to add back the stipend/real used gas
448
449                    vec![(TraceKind::Execution, traces)]
450                })
451                .unwrap_or_default(),
452            labeled_addresses: labels,
453            transactions,
454            exit_reason,
455            address: None,
456            breakpoints,
457        }
458    }
459
460    /// The executor will return the _exact_ gas value this transaction consumed, setting this value
461    /// as gas limit will result in `OutOfGas` so to come up with a better estimate we search over a
462    /// possible range we pick a higher gas limit 3x of a succeeded call should be safe.
463    ///
464    /// This might result in executing the same script multiple times. Depending on the user's goal,
465    /// it might be problematic when using `ffi`.
466    fn search_optimal_gas_usage(
467        &mut self,
468        res: &RawCallResult<FEN>,
469        from: Address,
470        to: Address,
471        calldata: &Bytes,
472        value: U256,
473    ) -> Result<u64> {
474        let mut gas_used = res.gas_used;
475        if matches!(res.exit_reason, Some(return_ok!())) {
476            // Store the current gas limit and reset it later.
477            let init_gas_limit = self.executor.tx_env().gas_limit();
478
479            let mut search = GasSearch::new(gas_used);
480            while let Some(limit) = search.next_limit() {
481                self.executor.tx_env_mut().set_gas_limit(limit);
482                let res = self.executor.call_raw(from, to, calldata.0.clone().into(), value)?;
483                search.record(limit, needs_more_gas(res.exit_reason));
484            }
485            gas_used = search.gas_used();
486            // Reset gas limit in the executor.
487            self.executor.tx_env_mut().set_gas_limit(init_gas_limit);
488        }
489        Ok(gas_used)
490    }
491}
492
493/// Gas-search arithmetic shared by ordinary and Monad simulation.
494pub(crate) struct GasSearch {
495    gas_used: u64,
496    highest: u64,
497    lowest: u64,
498    last_highest: u64,
499    done: bool,
500}
501
502impl GasSearch {
503    pub(crate) const fn new(gas_used: u64) -> Self {
504        Self {
505            gas_used,
506            highest: gas_used * 3,
507            lowest: gas_used,
508            last_highest: gas_used * 3,
509            done: false,
510        }
511    }
512
513    pub(crate) const fn next_limit(&self) -> Option<u64> {
514        if !self.done && self.highest - self.lowest > 1 {
515            Some((self.highest + self.lowest) / 2)
516        } else {
517            None
518        }
519    }
520
521    /// Records the outcome of a probe at `limit`, where `needs_more_gas` means the limit was not
522    /// enough.
523    pub(crate) const fn record(&mut self, limit: u64, needs_more_gas: bool) {
524        if needs_more_gas {
525            self.lowest = limit;
526        } else {
527            self.highest = limit;
528            // Stop when successive successful estimates differ by less than ten percent.
529            if (self.last_highest - self.highest) * 10 / self.last_highest < 1 {
530                self.gas_used = self.highest;
531                self.done = true;
532            } else {
533                self.last_highest = self.highest;
534            }
535        }
536    }
537
538    pub(crate) const fn gas_used(&self) -> u64 {
539        self.gas_used
540    }
541}
542
543/// Returns whether the gas search treats `exit_reason` as needing more gas.
544pub(crate) const fn needs_more_gas(exit_reason: Option<InstructionResult>) -> bool {
545    matches!(
546        exit_reason,
547        Some(
548            InstructionResult::Revert | InstructionResult::OutOfGas | InstructionResult::OutOfFunds
549        )
550    )
551}
552
553#[cfg(test)]
554mod gas_search_tests {
555    use super::*;
556
557    #[test]
558    fn successful_probes_keep_existing_ten_percent_stop() {
559        let mut search = GasSearch::new(100);
560        for expected in [200, 150, 125, 112, 106] {
561            assert_eq!(search.next_limit(), Some(expected));
562            search.record(expected, false);
563        }
564        assert_eq!(search.next_limit(), None);
565        assert_eq!(search.gas_used(), 106);
566    }
567
568    #[test]
569    fn unsuccessful_probes_keep_original_estimate() {
570        let mut search = GasSearch::new(100);
571        while let Some(limit) = search.next_limit() {
572            search.record(limit, true);
573        }
574        assert_eq!(search.gas_used(), 100);
575        assert_eq!(GasSearch::new(0).next_limit(), None);
576    }
577
578    #[test]
579    fn only_revert_and_running_out_mean_too_little_gas() {
580        for reason in
581            [InstructionResult::Revert, InstructionResult::OutOfGas, InstructionResult::OutOfFunds]
582        {
583            assert!(needs_more_gas(Some(reason)));
584        }
585        for reason in [
586            Some(InstructionResult::Return),
587            Some(InstructionResult::Stop),
588            Some(InstructionResult::InvalidFEOpcode),
589            None,
590        ] {
591            assert!(!needs_more_gas(reason));
592        }
593    }
594}