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::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    /// Deploys the libraries and broadcast contract. Calls setUp method if requested.
60    pub fn setup(
61        &mut self,
62        libraries: &ScriptPredeployLibraries,
63        code: Bytes,
64        setup: bool,
65        script_config: &ScriptConfig<FEN>,
66        is_broadcast: bool,
67    ) -> Result<(Address, ScriptResult<FEN::Network>)> {
68        trace!(target: "script", "executing setUP()");
69
70        if !is_broadcast {
71            if self.evm_opts.sender == Config::DEFAULT_SENDER {
72                // We max out their balance so that they can deploy and make calls.
73                self.executor.set_balance(self.evm_opts.sender, U256::MAX)?;
74            }
75
76            if script_config.evm_opts.fork_url.is_none()
77                && !script_config.evm_opts.networks.is_tempo()
78            {
79                self.executor.deploy_create2_deployer()?;
80            }
81        }
82
83        let sender_nonce = script_config.sender_nonce;
84        self.executor.set_nonce(self.evm_opts.sender, sender_nonce)?;
85
86        // We max out their balance so that they can deploy and make calls.
87        self.executor.set_balance(CALLER, U256::MAX)?;
88
89        let mut library_transactions = VecDeque::new();
90        let mut traces = Traces::default();
91        let mut debug_bytecodes: AddressHashMap<Bytes> = Default::default();
92
93        // Deploy libraries
94        match libraries {
95            ScriptPredeployLibraries::Default(libraries) => {
96                for code in libraries {
97                    let RawCallResult {
98                        traces: deploy_traces,
99                        debug_bytecodes: deploy_debug_bytecodes,
100                        ..
101                    } = self
102                        .executor
103                        .deploy(self.evm_opts.sender, code.clone(), U256::ZERO, None)
104                        .expect("couldn't deploy library")
105                        .raw;
106
107                    self.extend_debug_bytecodes(&mut debug_bytecodes, deploy_debug_bytecodes);
108
109                    if let Some(deploy_traces) = deploy_traces {
110                        traces.push((TraceKind::Deployment, deploy_traces));
111                    }
112
113                    let mut tx_req = TransactionRequestFor::<FEN>::default()
114                        .with_from(self.evm_opts.sender)
115                        .with_input(code.clone())
116                        .with_nonce(sender_nonce + library_transactions.len() as u64);
117
118                    script_config.tempo.apply::<FEN::Network>(&mut tx_req, None);
119
120                    library_transactions.push_back(BroadcastableTransaction {
121                        rpc: self.evm_opts.fork_url.clone(),
122                        transaction: TransactionMaybeSigned::new(tx_req),
123                    })
124                }
125            }
126            ScriptPredeployLibraries::Create2(libraries, salt) => {
127                let create2_deployer = self.executor.create2_deployer();
128                for library in libraries {
129                    let address = create2_deployer.create2_from_code(salt, library.as_ref());
130                    // Skip if already deployed
131                    if !self.executor.is_empty_code(address)? {
132                        continue;
133                    }
134                    let calldata = [salt.as_ref(), library.as_ref()].concat();
135                    let RawCallResult {
136                        traces: deploy_traces,
137                        debug_bytecodes: deploy_debug_bytecodes,
138                        ..
139                    } = self
140                        .executor
141                        .transact_raw(
142                            self.evm_opts.sender,
143                            create2_deployer,
144                            calldata.clone().into(),
145                            U256::from(0),
146                        )
147                        .expect("couldn't deploy library");
148
149                    self.extend_debug_bytecodes(&mut debug_bytecodes, deploy_debug_bytecodes);
150
151                    if let Some(deploy_traces) = deploy_traces {
152                        traces.push((TraceKind::Deployment, deploy_traces));
153                    }
154
155                    let mut tx_req = TransactionRequestFor::<FEN>::default()
156                        .with_from(self.evm_opts.sender)
157                        .with_input(calldata)
158                        .with_nonce(sender_nonce + library_transactions.len() as u64)
159                        .with_to(create2_deployer);
160
161                    script_config.tempo.apply::<FEN::Network>(&mut tx_req, None);
162
163                    library_transactions.push_back(BroadcastableTransaction {
164                        rpc: self.evm_opts.fork_url.clone(),
165                        transaction: TransactionMaybeSigned::new(tx_req),
166                    });
167                }
168
169                // Sender nonce is not incremented when performing CALLs. We need to manually
170                // increase it.
171                self.executor.set_nonce(
172                    self.evm_opts.sender,
173                    sender_nonce + library_transactions.len() as u64,
174                )?;
175            }
176        };
177
178        let address = CALLER.create(self.executor.get_nonce(CALLER)?);
179
180        // Set the contracts initial balance before deployment, so it is available during the
181        // construction
182        self.executor.set_balance(address, self.evm_opts.initial_balance)?;
183
184        // HACK: if the current sender is the default script sender (which is a default value), we
185        // set its nonce to a very large value before deploying the script contract. This
186        // ensures that the nonce increase during this CREATE does not affect deployment
187        // addresses of contracts that are deployed in the script, Otherwise, we'd have a
188        // nonce mismatch during script execution and onchain simulation, potentially
189        // resulting in weird errors like <https://github.com/foundry-rs/foundry/issues/8960>.
190        let prev_sender_nonce = self.executor.get_nonce(self.evm_opts.sender)?;
191        if self.evm_opts.sender == CALLER {
192            self.executor.set_nonce(self.evm_opts.sender, u64::MAX / 2)?;
193        }
194
195        // Deploy an instance of the contract
196        let DeployResult {
197            address,
198            raw:
199                RawCallResult {
200                    mut logs,
201                    traces: constructor_traces,
202                    debug_bytecodes: constructor_debug_bytecodes,
203                    ..
204                },
205        } = self
206            .executor
207            .deploy(CALLER, code, U256::ZERO, None)
208            .map_err(|err| eyre::eyre!("Failed to deploy script:\n{}", err))?;
209
210        if self.evm_opts.sender == CALLER {
211            self.executor.set_nonce(self.evm_opts.sender, prev_sender_nonce)?;
212        }
213
214        // set script address to be used by execution inspector
215        if script_config.config.script_execution_protection {
216            self.executor.set_script_execution(address);
217        }
218
219        traces.extend(constructor_traces.map(|traces| (TraceKind::Deployment, traces)));
220        self.extend_debug_bytecodes(&mut debug_bytecodes, constructor_debug_bytecodes);
221
222        // Optionally call the `setUp` function
223        let (success, gas_used, labeled_addresses, transactions) = if setup {
224            match self.executor.setup(Some(self.evm_opts.sender), address, None) {
225                Ok(RawCallResult {
226                    reverted,
227                    traces: setup_traces,
228                    labels,
229                    logs: setup_logs,
230                    gas_used,
231                    debug_bytecodes: setup_debug_bytecodes,
232                    transactions: setup_transactions,
233                    ..
234                }) => {
235                    traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces)));
236                    logs.extend_from_slice(&setup_logs);
237                    self.extend_debug_bytecodes(&mut debug_bytecodes, setup_debug_bytecodes);
238
239                    if let Some(txs) = setup_transactions {
240                        library_transactions.extend(txs);
241                    }
242
243                    (!reverted, gas_used, labels, Some(library_transactions))
244                }
245                Err(EvmError::Execution(err)) => {
246                    let RawCallResult {
247                        reverted,
248                        traces: setup_traces,
249                        labels,
250                        logs: setup_logs,
251                        gas_used,
252                        debug_bytecodes: setup_debug_bytecodes,
253                        transactions,
254                        ..
255                    } = err.raw;
256                    traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces)));
257                    logs.extend_from_slice(&setup_logs);
258                    self.extend_debug_bytecodes(&mut debug_bytecodes, setup_debug_bytecodes);
259
260                    if let Some(txs) = transactions {
261                        library_transactions.extend(txs);
262                    }
263
264                    (!reverted, gas_used, labels, Some(library_transactions))
265                }
266                Err(e) => return Err(e.into()),
267            }
268        } else {
269            self.executor.backend_mut().set_test_contract(address);
270            (true, 0, Default::default(), Some(library_transactions))
271        };
272
273        Ok((
274            address,
275            ScriptResult {
276                returned: Bytes::new(),
277                success,
278                gas_used,
279                labeled_addresses,
280                debug_bytecodes: self.maybe_debug_bytecodes(debug_bytecodes),
281                transactions,
282                logs,
283                traces,
284                address: None,
285                ..Default::default()
286            },
287        ))
288    }
289
290    /// Executes the method that will collect all broadcastable transactions.
291    pub fn script(
292        &mut self,
293        address: Address,
294        calldata: Bytes,
295    ) -> Result<ScriptResult<FEN::Network>> {
296        self.call(self.evm_opts.sender, address, calldata, U256::ZERO, None, false)
297    }
298
299    /// Runs a broadcastable transaction locally and persists its state.
300    pub fn simulate(
301        &mut self,
302        from: Address,
303        to: Option<Address>,
304        calldata: Option<Bytes>,
305        value: Option<U256>,
306        authorization_list: Option<Vec<SignedAuthorization>>,
307    ) -> Result<ScriptResult<FEN::Network>> {
308        if let Some(to) = to {
309            self.call(
310                from,
311                to,
312                calldata.unwrap_or_default(),
313                value.unwrap_or(U256::ZERO),
314                authorization_list,
315                true,
316            )
317        } else {
318            let res = self.executor.deploy(
319                from,
320                calldata.expect("No data for create transaction"),
321                value.unwrap_or(U256::ZERO),
322                None,
323            );
324            let (
325                address,
326                RawCallResult { gas_used, logs, traces, debug_bytecodes, exit_reason, .. },
327            ) = match res {
328                Ok(DeployResult { address, raw }) => (address, raw),
329                Err(EvmError::Execution(err)) => {
330                    let ExecutionErr { raw, reason } = *err;
331                    sh_err!("Failed with `{reason}`:\n")?;
332                    (Address::ZERO, raw)
333                }
334                Err(e) => {
335                    eyre::bail!("Failed deploying contract: {e:?}");
336                }
337            };
338
339            Ok(ScriptResult {
340                returned: Bytes::new(),
341                success: address != Address::ZERO,
342                gas_used,
343                logs,
344                debug_bytecodes: self.maybe_debug_bytecodes(debug_bytecodes),
345                // Manually adjust gas for the trace to add back the stipend/real used gas
346                traces: traces
347                    .map(|traces| vec![(TraceKind::Execution, traces)])
348                    .unwrap_or_default(),
349                exit_reason,
350                address: Some(address),
351                ..Default::default()
352            })
353        }
354    }
355
356    /// Executes the call
357    ///
358    /// This will commit the changes if `commit` is true.
359    ///
360    /// This will return _estimated_ gas instead of the precise gas the call would consume, so it
361    /// can be used as `gas_limit`.
362    fn call(
363        &mut self,
364        from: Address,
365        to: Address,
366        calldata: Bytes,
367        value: U256,
368        authorization_list: Option<Vec<SignedAuthorization>>,
369        commit: bool,
370    ) -> Result<ScriptResult<FEN::Network>> {
371        let mut res = if let Some(authorization_list) = &authorization_list {
372            self.executor.call_raw_with_authorization(
373                from,
374                to,
375                calldata.clone(),
376                value,
377                authorization_list.clone(),
378            )?
379        } else {
380            self.executor.call_raw(from, to, calldata.clone(), value)?
381        };
382        let mut gas_used = res.gas_used;
383
384        // We should only need to calculate realistic gas costs when preparing to broadcast
385        // something. This happens during the onchain simulation stage, where we commit each
386        // collected transactions.
387        //
388        // Otherwise don't re-execute, or some usecases might be broken: https://github.com/foundry-rs/foundry/issues/3921
389        if commit {
390            gas_used = self.search_optimal_gas_usage(&res, from, to, &calldata, value)?;
391            res = if let Some(authorization_list) = authorization_list {
392                self.executor.transact_raw_with_authorization(
393                    from,
394                    to,
395                    calldata,
396                    value,
397                    authorization_list,
398                )?
399            } else {
400                self.executor.transact_raw(from, to, calldata, value)?
401            }
402        }
403
404        let RawCallResult {
405            result,
406            reverted,
407            logs,
408            traces,
409            labels,
410            transactions,
411            debug_bytecodes,
412            exit_reason,
413            cheatcodes,
414            ..
415        } = res;
416        let breakpoints = cheatcodes.map(|cheats| cheats.breakpoints).unwrap_or_default();
417
418        Ok(ScriptResult {
419            returned: result,
420            success: !reverted,
421            gas_used,
422            logs,
423            debug_bytecodes: self.maybe_debug_bytecodes(debug_bytecodes),
424            traces: traces
425                .map(|traces| {
426                    // Manually adjust gas for the trace to add back the stipend/real used gas
427
428                    vec![(TraceKind::Execution, traces)]
429                })
430                .unwrap_or_default(),
431            labeled_addresses: labels,
432            transactions,
433            exit_reason,
434            address: None,
435            breakpoints,
436        })
437    }
438
439    /// The executor will return the _exact_ gas value this transaction consumed, setting this value
440    /// as gas limit will result in `OutOfGas` so to come up with a better estimate we search over a
441    /// possible range we pick a higher gas limit 3x of a succeeded call should be safe.
442    ///
443    /// This might result in executing the same script multiple times. Depending on the user's goal,
444    /// it might be problematic when using `ffi`.
445    fn search_optimal_gas_usage(
446        &mut self,
447        res: &RawCallResult<FEN>,
448        from: Address,
449        to: Address,
450        calldata: &Bytes,
451        value: U256,
452    ) -> Result<u64> {
453        let mut gas_used = res.gas_used;
454        if matches!(res.exit_reason, Some(return_ok!())) {
455            // Store the current gas limit and reset it later.
456            let init_gas_limit = self.executor.tx_env().gas_limit();
457
458            let mut highest_gas_limit = gas_used * 3;
459            let mut lowest_gas_limit = gas_used;
460            let mut last_highest_gas_limit = highest_gas_limit;
461            while (highest_gas_limit - lowest_gas_limit) > 1 {
462                let mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2;
463                self.executor.tx_env_mut().set_gas_limit(mid_gas_limit);
464                let res = self.executor.call_raw(from, to, calldata.0.clone().into(), value)?;
465                match res.exit_reason {
466                    Some(
467                        InstructionResult::Revert
468                        | InstructionResult::OutOfGas
469                        | InstructionResult::OutOfFunds,
470                    ) => {
471                        lowest_gas_limit = mid_gas_limit;
472                    }
473                    _ => {
474                        highest_gas_limit = mid_gas_limit;
475                        // if last two successful estimations only vary by 10%, we consider this to
476                        // sufficiently accurate
477                        const ACCURACY: u64 = 10;
478                        if (last_highest_gas_limit - highest_gas_limit) * ACCURACY
479                            / last_highest_gas_limit
480                            < 1
481                        {
482                            // update the gas
483                            gas_used = highest_gas_limit;
484                            break;
485                        }
486                        last_highest_gas_limit = highest_gas_limit;
487                    }
488                }
489            }
490            // Reset gas limit in the executor.
491            self.executor.tx_env_mut().set_gas_limit(init_gas_limit);
492        }
493        Ok(gas_used)
494    }
495}