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            let (
357                address,
358                RawCallResult { gas_used, logs, traces, debug_bytecodes, exit_reason, .. },
359            ) = match res {
360                Ok(DeployResult { address, raw }) => (address, raw),
361                Err(EvmError::Execution(err)) => {
362                    let ExecutionErr { raw, reason } = *err;
363                    sh_err!("Failed with `{reason}`:\n")?;
364                    (Address::ZERO, raw)
365                }
366                Err(e) => {
367                    eyre::bail!("Failed deploying contract: {e:?}");
368                }
369            };
370
371            Ok(ScriptResult {
372                returned: Bytes::new(),
373                success: address != Address::ZERO,
374                gas_used,
375                logs,
376                debug_bytecodes: self.maybe_debug_bytecodes(debug_bytecodes),
377                // Manually adjust gas for the trace to add back the stipend/real used gas
378                traces: traces
379                    .map(|traces| vec![(TraceKind::Execution, traces)])
380                    .unwrap_or_default(),
381                exit_reason,
382                address: Some(address),
383                ..Default::default()
384            })
385        }
386    }
387
388    /// Executes the call
389    ///
390    /// This will commit the changes if `commit` is true.
391    ///
392    /// This will return _estimated_ gas instead of the precise gas the call would consume, so it
393    /// can be used as `gas_limit`.
394    fn call(
395        &mut self,
396        from: Address,
397        to: Address,
398        calldata: Bytes,
399        value: U256,
400        authorization_list: Option<Vec<SignedAuthorization>>,
401        commit: bool,
402    ) -> Result<ScriptResult<FEN::Network>> {
403        let mut res = if let Some(authorization_list) = &authorization_list {
404            self.executor.call_raw_with_authorization(
405                from,
406                to,
407                calldata.clone(),
408                value,
409                authorization_list.clone(),
410            )?
411        } else {
412            self.executor.call_raw(from, to, calldata.clone(), value)?
413        };
414        let mut gas_used = res.gas_used;
415
416        // We should only need to calculate realistic gas costs when preparing to broadcast
417        // something. This happens during the onchain simulation stage, where we commit each
418        // collected transactions.
419        //
420        // Otherwise don't re-execute, or some usecases might be broken: https://github.com/foundry-rs/foundry/issues/3921
421        if commit {
422            gas_used = self.search_optimal_gas_usage(&res, from, to, &calldata, value)?;
423            res = if let Some(authorization_list) = authorization_list {
424                self.executor.transact_raw_with_authorization(
425                    from,
426                    to,
427                    calldata,
428                    value,
429                    authorization_list,
430                )?
431            } else {
432                self.executor.transact_raw(from, to, calldata, value)?
433            }
434        }
435
436        let RawCallResult {
437            result,
438            reverted,
439            logs,
440            traces,
441            labels,
442            transactions,
443            debug_bytecodes,
444            exit_reason,
445            cheatcodes,
446            ..
447        } = res;
448        let breakpoints = cheatcodes.map(|cheats| cheats.breakpoints).unwrap_or_default();
449
450        Ok(ScriptResult {
451            returned: result,
452            success: !reverted,
453            gas_used,
454            logs,
455            debug_bytecodes: self.maybe_debug_bytecodes(debug_bytecodes),
456            traces: traces
457                .map(|traces| {
458                    // Manually adjust gas for the trace to add back the stipend/real used gas
459
460                    vec![(TraceKind::Execution, traces)]
461                })
462                .unwrap_or_default(),
463            labeled_addresses: labels,
464            transactions,
465            exit_reason,
466            address: None,
467            breakpoints,
468        })
469    }
470
471    /// The executor will return the _exact_ gas value this transaction consumed, setting this value
472    /// as gas limit will result in `OutOfGas` so to come up with a better estimate we search over a
473    /// possible range we pick a higher gas limit 3x of a succeeded call should be safe.
474    ///
475    /// This might result in executing the same script multiple times. Depending on the user's goal,
476    /// it might be problematic when using `ffi`.
477    fn search_optimal_gas_usage(
478        &mut self,
479        res: &RawCallResult<FEN>,
480        from: Address,
481        to: Address,
482        calldata: &Bytes,
483        value: U256,
484    ) -> Result<u64> {
485        let mut gas_used = res.gas_used;
486        if matches!(res.exit_reason, Some(return_ok!())) {
487            // Store the current gas limit and reset it later.
488            let init_gas_limit = self.executor.tx_env().gas_limit();
489
490            let mut highest_gas_limit = gas_used * 3;
491            let mut lowest_gas_limit = gas_used;
492            let mut last_highest_gas_limit = highest_gas_limit;
493            while (highest_gas_limit - lowest_gas_limit) > 1 {
494                let mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2;
495                self.executor.tx_env_mut().set_gas_limit(mid_gas_limit);
496                let res = self.executor.call_raw(from, to, calldata.0.clone().into(), value)?;
497                match res.exit_reason {
498                    Some(
499                        InstructionResult::Revert
500                        | InstructionResult::OutOfGas
501                        | InstructionResult::OutOfFunds,
502                    ) => {
503                        lowest_gas_limit = mid_gas_limit;
504                    }
505                    _ => {
506                        highest_gas_limit = mid_gas_limit;
507                        // if last two successful estimations only vary by 10%, we consider this to
508                        // sufficiently accurate
509                        const ACCURACY: u64 = 10;
510                        if (last_highest_gas_limit - highest_gas_limit) * ACCURACY
511                            / last_highest_gas_limit
512                            < 1
513                        {
514                            // update the gas
515                            gas_used = highest_gas_limit;
516                            break;
517                        }
518                        last_highest_gas_limit = highest_gas_limit;
519                    }
520                }
521            }
522            // Reset gas limit in the executor.
523            self.executor.tx_env_mut().set_gas_limit(init_gas_limit);
524        }
525        Ok(gas_used)
526    }
527}