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::from(0),
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            match self.executor.setup(Some(self.evm_opts.sender), address, None) {
257                Ok(RawCallResult {
258                    reverted,
259                    traces: setup_traces,
260                    labels,
261                    logs: setup_logs,
262                    gas_used,
263                    debug_bytecodes: setup_debug_bytecodes,
264                    transactions: setup_transactions,
265                    ..
266                }) => {
267                    traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces)));
268                    logs.extend_from_slice(&setup_logs);
269                    self.extend_debug_bytecodes(&mut debug_bytecodes, setup_debug_bytecodes);
270
271                    if let Some(txs) = setup_transactions {
272                        library_transactions.extend(txs);
273                    }
274
275                    (!reverted, gas_used, labels, Some(library_transactions))
276                }
277                Err(EvmError::Execution(err)) => {
278                    let RawCallResult {
279                        reverted,
280                        traces: setup_traces,
281                        labels,
282                        logs: setup_logs,
283                        gas_used,
284                        debug_bytecodes: setup_debug_bytecodes,
285                        transactions,
286                        ..
287                    } = err.raw;
288                    traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces)));
289                    logs.extend_from_slice(&setup_logs);
290                    self.extend_debug_bytecodes(&mut debug_bytecodes, setup_debug_bytecodes);
291
292                    if let Some(txs) = transactions {
293                        library_transactions.extend(txs);
294                    }
295
296                    (!reverted, gas_used, labels, Some(library_transactions))
297                }
298                Err(e) => return Err(e.into()),
299            }
300        } else {
301            self.executor.backend_mut().set_test_contract(address);
302            (true, 0, Default::default(), Some(library_transactions))
303        };
304
305        Ok((
306            address,
307            ScriptResult {
308                returned: Bytes::new(),
309                success,
310                gas_used,
311                labeled_addresses,
312                debug_bytecodes: self.maybe_debug_bytecodes(debug_bytecodes),
313                transactions,
314                logs,
315                traces,
316                address: None,
317                ..Default::default()
318            },
319        ))
320    }
321
322    /// Executes the method that will collect all broadcastable transactions.
323    pub fn script(
324        &mut self,
325        address: Address,
326        calldata: Bytes,
327    ) -> Result<ScriptResult<FEN::Network>> {
328        self.call(self.evm_opts.sender, address, calldata, U256::ZERO, None, false)
329    }
330
331    /// Runs a broadcastable transaction locally and persists its state.
332    pub fn simulate(
333        &mut self,
334        from: Address,
335        to: Option<Address>,
336        calldata: Option<Bytes>,
337        value: Option<U256>,
338        authorization_list: Option<Vec<SignedAuthorization>>,
339    ) -> Result<ScriptResult<FEN::Network>> {
340        if let Some(to) = to {
341            self.call(
342                from,
343                to,
344                calldata.unwrap_or_default(),
345                value.unwrap_or(U256::ZERO),
346                authorization_list,
347                true,
348            )
349        } else {
350            let res = self.executor.deploy(
351                from,
352                calldata.expect("No data for create transaction"),
353                value.unwrap_or(U256::ZERO),
354                None,
355            );
356            self.deployment_result(res)
357        }
358    }
359
360    pub(crate) fn deployment_result(
361        &self,
362        res: Result<DeployResult<FEN>, EvmError<FEN>>,
363    ) -> Result<ScriptResult<FEN::Network>> {
364        let (address, RawCallResult { gas_used, logs, traces, debug_bytecodes, exit_reason, .. }) =
365            match res {
366                Ok(DeployResult { address, raw }) => (address, raw),
367                Err(EvmError::Execution(err)) => {
368                    let ExecutionErr { raw, reason } = *err;
369                    sh_err!("Failed with `{reason}`:\n")?;
370                    (Address::ZERO, raw)
371                }
372                Err(e) => {
373                    eyre::bail!("Failed deploying contract: {e:?}");
374                }
375            };
376
377        Ok(ScriptResult {
378            returned: Bytes::new(),
379            success: address != Address::ZERO,
380            gas_used,
381            logs,
382            debug_bytecodes: self.maybe_debug_bytecodes(debug_bytecodes),
383            // Manually adjust gas for the trace to add back the stipend/real used gas
384            traces: traces.map(|traces| vec![(TraceKind::Execution, traces)]).unwrap_or_default(),
385            exit_reason,
386            address: Some(address),
387            ..Default::default()
388        })
389    }
390
391    /// Executes the call
392    ///
393    /// This will commit the changes if `commit` is true.
394    ///
395    /// This will return _estimated_ gas instead of the precise gas the call would consume, so it
396    /// can be used as `gas_limit`.
397    fn call(
398        &mut self,
399        from: Address,
400        to: Address,
401        calldata: Bytes,
402        value: U256,
403        authorization_list: Option<Vec<SignedAuthorization>>,
404        commit: bool,
405    ) -> Result<ScriptResult<FEN::Network>> {
406        let mut res = if let Some(authorization_list) = &authorization_list {
407            self.executor.call_raw_with_authorization(
408                from,
409                to,
410                calldata.clone(),
411                value,
412                authorization_list.clone(),
413            )?
414        } else {
415            self.executor.call_raw(from, to, calldata.clone(), value)?
416        };
417        let mut gas_used = res.gas_used;
418
419        // We should only need to calculate realistic gas costs when preparing to broadcast
420        // something. This happens during the onchain simulation stage, where we commit each
421        // collected transactions.
422        //
423        // Otherwise don't re-execute, or some usecases might be broken: https://github.com/foundry-rs/foundry/issues/3921
424        if commit {
425            gas_used = self.search_optimal_gas_usage(&res, from, to, &calldata, value)?;
426            res = if let Some(authorization_list) = authorization_list {
427                self.executor.transact_raw_with_authorization(
428                    from,
429                    to,
430                    calldata,
431                    value,
432                    authorization_list,
433                )?
434            } else {
435                self.executor.transact_raw(from, to, calldata, value)?
436            }
437        }
438
439        Ok(self.call_result(res, gas_used))
440    }
441
442    pub(crate) fn call_result(
443        &self,
444        res: RawCallResult<FEN>,
445        gas_used: u64,
446    ) -> ScriptResult<FEN::Network> {
447        let RawCallResult {
448            result,
449            reverted,
450            logs,
451            traces,
452            labels,
453            transactions,
454            debug_bytecodes,
455            exit_reason,
456            cheatcodes,
457            ..
458        } = res;
459        let breakpoints = cheatcodes.map(|cheats| cheats.breakpoints).unwrap_or_default();
460
461        ScriptResult {
462            returned: result,
463            success: !reverted,
464            gas_used,
465            logs,
466            debug_bytecodes: self.maybe_debug_bytecodes(debug_bytecodes),
467            traces: traces
468                .map(|traces| {
469                    // Manually adjust gas for the trace to add back the stipend/real used gas
470
471                    vec![(TraceKind::Execution, traces)]
472                })
473                .unwrap_or_default(),
474            labeled_addresses: labels,
475            transactions,
476            exit_reason,
477            address: None,
478            breakpoints,
479        }
480    }
481
482    /// The executor will return the _exact_ gas value this transaction consumed, setting this value
483    /// as gas limit will result in `OutOfGas` so to come up with a better estimate we search over a
484    /// possible range we pick a higher gas limit 3x of a succeeded call should be safe.
485    ///
486    /// This might result in executing the same script multiple times. Depending on the user's goal,
487    /// it might be problematic when using `ffi`.
488    fn search_optimal_gas_usage(
489        &mut self,
490        res: &RawCallResult<FEN>,
491        from: Address,
492        to: Address,
493        calldata: &Bytes,
494        value: U256,
495    ) -> Result<u64> {
496        let mut gas_used = res.gas_used;
497        if matches!(res.exit_reason, Some(return_ok!())) {
498            // Store the current gas limit and reset it later.
499            let init_gas_limit = self.executor.tx_env().gas_limit();
500
501            let mut search = GasSearch::new(gas_used);
502            while let Some(limit) = search.next_limit() {
503                self.executor.tx_env_mut().set_gas_limit(limit);
504                let res = self.executor.call_raw(from, to, calldata.0.clone().into(), value)?;
505                search.record(limit, res.exit_reason);
506            }
507            gas_used = search.gas_used();
508            // Reset gas limit in the executor.
509            self.executor.tx_env_mut().set_gas_limit(init_gas_limit);
510        }
511        Ok(gas_used)
512    }
513}
514
515/// Gas-search arithmetic shared by ordinary and Monad simulation.
516pub(crate) struct GasSearch {
517    gas_used: u64,
518    highest: u64,
519    lowest: u64,
520    last_highest: u64,
521    done: bool,
522}
523
524impl GasSearch {
525    pub(crate) const fn new(gas_used: u64) -> Self {
526        Self {
527            gas_used,
528            highest: gas_used * 3,
529            lowest: gas_used,
530            last_highest: gas_used * 3,
531            done: false,
532        }
533    }
534
535    pub(crate) const fn next_limit(&self) -> Option<u64> {
536        if !self.done && self.highest - self.lowest > 1 {
537            Some((self.highest + self.lowest) / 2)
538        } else {
539            None
540        }
541    }
542
543    pub(crate) const fn record(&mut self, limit: u64, exit_reason: Option<InstructionResult>) {
544        match exit_reason {
545            Some(
546                InstructionResult::Revert
547                | InstructionResult::OutOfGas
548                | InstructionResult::OutOfFunds,
549            ) => {
550                self.lowest = limit;
551            }
552            _ => {
553                self.highest = limit;
554                // Stop when successive successful estimates differ by less than ten percent.
555                if (self.last_highest - self.highest) * 10 / self.last_highest < 1 {
556                    self.gas_used = self.highest;
557                    self.done = true;
558                } else {
559                    self.last_highest = self.highest;
560                }
561            }
562        }
563    }
564
565    pub(crate) const fn gas_used(&self) -> u64 {
566        self.gas_used
567    }
568}
569
570#[cfg(test)]
571mod gas_search_tests {
572    use super::*;
573
574    #[test]
575    fn successful_probes_keep_existing_ten_percent_stop() {
576        let mut search = GasSearch::new(100);
577        for expected in [200, 150, 125, 112, 106] {
578            assert_eq!(search.next_limit(), Some(expected));
579            search.record(expected, Some(InstructionResult::Return));
580        }
581        assert_eq!(search.next_limit(), None);
582        assert_eq!(search.gas_used(), 106);
583    }
584
585    #[test]
586    fn unsuccessful_probes_keep_original_estimate() {
587        let mut search = GasSearch::new(100);
588        while let Some(limit) = search.next_limit() {
589            search.record(limit, Some(InstructionResult::OutOfGas));
590        }
591        assert_eq!(search.gas_used(), 100);
592        assert_eq!(GasSearch::new(0).next_limit(), None);
593    }
594}