forge_script/
runner.rs

1use super::{ScriptConfig, ScriptResult};
2use crate::build::ScriptPredeployLibraries;
3use alloy_eips::eip7702::SignedAuthorization;
4use alloy_primitives::{Address, Bytes, TxKind, U256};
5use alloy_rpc_types::TransactionRequest;
6use eyre::Result;
7use foundry_cheatcodes::BroadcastableTransaction;
8use foundry_config::Config;
9use foundry_evm::{
10    constants::CALLER,
11    executors::{DeployResult, EvmError, ExecutionErr, Executor, RawCallResult},
12    opts::EvmOpts,
13    revm::interpreter::{return_ok, InstructionResult},
14    traces::{TraceKind, Traces},
15};
16use std::collections::VecDeque;
17
18/// Drives script execution
19#[derive(Debug)]
20pub struct ScriptRunner {
21    pub executor: Executor,
22    pub evm_opts: EvmOpts,
23}
24
25impl ScriptRunner {
26    pub fn new(executor: Executor, evm_opts: EvmOpts) -> Self {
27        Self { executor, evm_opts }
28    }
29
30    /// Deploys the libraries and broadcast contract. Calls setUp method if requested.
31    pub fn setup(
32        &mut self,
33        libraries: &ScriptPredeployLibraries,
34        code: Bytes,
35        setup: bool,
36        script_config: &ScriptConfig,
37        is_broadcast: bool,
38    ) -> Result<(Address, ScriptResult)> {
39        trace!(target: "script", "executing setUP()");
40
41        if !is_broadcast {
42            if self.evm_opts.sender == Config::DEFAULT_SENDER {
43                // We max out their balance so that they can deploy and make calls.
44                self.executor.set_balance(self.evm_opts.sender, U256::MAX)?;
45            }
46
47            if script_config.evm_opts.fork_url.is_none() {
48                self.executor.deploy_create2_deployer()?;
49            }
50        }
51
52        let sender_nonce = script_config.sender_nonce;
53        self.executor.set_nonce(self.evm_opts.sender, sender_nonce)?;
54
55        // We max out their balance so that they can deploy and make calls.
56        self.executor.set_balance(CALLER, U256::MAX)?;
57
58        let mut library_transactions = VecDeque::new();
59        let mut traces = Traces::default();
60
61        // Deploy libraries
62        match libraries {
63            ScriptPredeployLibraries::Default(libraries) => libraries.iter().for_each(|code| {
64                let result = self
65                    .executor
66                    .deploy(self.evm_opts.sender, code.clone(), U256::ZERO, None)
67                    .expect("couldn't deploy library")
68                    .raw;
69
70                if let Some(deploy_traces) = result.traces {
71                    traces.push((TraceKind::Deployment, deploy_traces));
72                }
73
74                library_transactions.push_back(BroadcastableTransaction {
75                    rpc: self.evm_opts.fork_url.clone(),
76                    transaction: TransactionRequest {
77                        from: Some(self.evm_opts.sender),
78                        input: code.clone().into(),
79                        nonce: Some(sender_nonce + library_transactions.len() as u64),
80                        ..Default::default()
81                    }
82                    .into(),
83                })
84            }),
85            ScriptPredeployLibraries::Create2(libraries, salt) => {
86                let create2_deployer = self.executor.create2_deployer();
87                for library in libraries {
88                    let address = create2_deployer.create2_from_code(salt, library.as_ref());
89                    // Skip if already deployed
90                    if !self.executor.is_empty_code(address)? {
91                        continue;
92                    }
93                    let calldata = [salt.as_ref(), library.as_ref()].concat();
94                    let result = self
95                        .executor
96                        .transact_raw(
97                            self.evm_opts.sender,
98                            create2_deployer,
99                            calldata.clone().into(),
100                            U256::from(0),
101                        )
102                        .expect("couldn't deploy library");
103
104                    if let Some(deploy_traces) = result.traces {
105                        traces.push((TraceKind::Deployment, deploy_traces));
106                    }
107
108                    library_transactions.push_back(BroadcastableTransaction {
109                        rpc: self.evm_opts.fork_url.clone(),
110                        transaction: TransactionRequest {
111                            from: Some(self.evm_opts.sender),
112                            input: calldata.into(),
113                            nonce: Some(sender_nonce + library_transactions.len() as u64),
114                            to: Some(TxKind::Call(create2_deployer)),
115                            ..Default::default()
116                        }
117                        .into(),
118                    });
119                }
120
121                // Sender nonce is not incremented when performing CALLs. We need to manually
122                // increase it.
123                self.executor.set_nonce(
124                    self.evm_opts.sender,
125                    sender_nonce + library_transactions.len() as u64,
126                )?;
127            }
128        };
129
130        let address = CALLER.create(self.executor.get_nonce(CALLER)?);
131
132        // Set the contracts initial balance before deployment, so it is available during the
133        // construction
134        self.executor.set_balance(address, self.evm_opts.initial_balance)?;
135
136        // HACK: if the current sender is the default script sender (which is a default value), we
137        // set its nonce to a very large value before deploying the script contract. This
138        // ensures that the nonce increase during this CREATE does not affect deployment
139        // addresses of contracts that are deployed in the script, Otherwise, we'd have a
140        // nonce mismatch during script execution and onchain simulation, potentially
141        // resulting in weird errors like <https://github.com/foundry-rs/foundry/issues/8960>.
142        let prev_sender_nonce = self.executor.get_nonce(self.evm_opts.sender)?;
143        if self.evm_opts.sender == CALLER {
144            self.executor.set_nonce(self.evm_opts.sender, u64::MAX / 2)?;
145        }
146
147        // Deploy an instance of the contract
148        let DeployResult {
149            address,
150            raw: RawCallResult { mut logs, traces: constructor_traces, .. },
151        } = self
152            .executor
153            .deploy(CALLER, code, U256::ZERO, None)
154            .map_err(|err| eyre::eyre!("Failed to deploy script:\n{}", err))?;
155
156        if self.evm_opts.sender == CALLER {
157            self.executor.set_nonce(self.evm_opts.sender, prev_sender_nonce)?;
158        }
159
160        // set script address to be used by execution inspector
161        if script_config.config.script_execution_protection {
162            self.executor.set_script_execution(address);
163        }
164
165        traces.extend(constructor_traces.map(|traces| (TraceKind::Deployment, traces)));
166
167        // Optionally call the `setUp` function
168        let (success, gas_used, labeled_addresses, transactions) = if !setup {
169            self.executor.backend_mut().set_test_contract(address);
170            (true, 0, Default::default(), Some(library_transactions))
171        } else {
172            match self.executor.setup(Some(self.evm_opts.sender), address, None) {
173                Ok(RawCallResult {
174                    reverted,
175                    traces: setup_traces,
176                    labels,
177                    logs: setup_logs,
178                    gas_used,
179                    transactions: setup_transactions,
180                    ..
181                }) => {
182                    traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces)));
183                    logs.extend_from_slice(&setup_logs);
184
185                    if let Some(txs) = setup_transactions {
186                        library_transactions.extend(txs);
187                    }
188
189                    (!reverted, gas_used, labels, Some(library_transactions))
190                }
191                Err(EvmError::Execution(err)) => {
192                    let RawCallResult {
193                        reverted,
194                        traces: setup_traces,
195                        labels,
196                        logs: setup_logs,
197                        gas_used,
198                        transactions,
199                        ..
200                    } = err.raw;
201                    traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces)));
202                    logs.extend_from_slice(&setup_logs);
203
204                    if let Some(txs) = transactions {
205                        library_transactions.extend(txs);
206                    }
207
208                    (!reverted, gas_used, labels, Some(library_transactions))
209                }
210                Err(e) => return Err(e.into()),
211            }
212        };
213
214        Ok((
215            address,
216            ScriptResult {
217                returned: Bytes::new(),
218                success,
219                gas_used,
220                labeled_addresses,
221                transactions,
222                logs,
223                traces,
224                address: None,
225                ..Default::default()
226            },
227        ))
228    }
229
230    /// Executes the method that will collect all broadcastable transactions.
231    pub fn script(&mut self, address: Address, calldata: Bytes) -> Result<ScriptResult> {
232        self.call(self.evm_opts.sender, address, calldata, U256::ZERO, None, false)
233    }
234
235    /// Runs a broadcastable transaction locally and persists its state.
236    pub fn simulate(
237        &mut self,
238        from: Address,
239        to: Option<Address>,
240        calldata: Option<Bytes>,
241        value: Option<U256>,
242        authorization_list: Option<Vec<SignedAuthorization>>,
243    ) -> Result<ScriptResult> {
244        if let Some(to) = to {
245            self.call(
246                from,
247                to,
248                calldata.unwrap_or_default(),
249                value.unwrap_or(U256::ZERO),
250                authorization_list,
251                true,
252            )
253        } else if to.is_none() {
254            let res = self.executor.deploy(
255                from,
256                calldata.expect("No data for create transaction"),
257                value.unwrap_or(U256::ZERO),
258                None,
259            );
260            let (address, RawCallResult { gas_used, logs, traces, .. }) = match res {
261                Ok(DeployResult { address, raw }) => (address, raw),
262                Err(EvmError::Execution(err)) => {
263                    let ExecutionErr { raw, reason } = *err;
264                    sh_err!("Failed with `{reason}`:\n")?;
265                    (Address::ZERO, raw)
266                }
267                Err(e) => eyre::bail!("Failed deploying contract: {e:?}"),
268            };
269
270            Ok(ScriptResult {
271                returned: Bytes::new(),
272                success: address != Address::ZERO,
273                gas_used,
274                logs,
275                // Manually adjust gas for the trace to add back the stipend/real used gas
276                traces: traces
277                    .map(|traces| vec![(TraceKind::Execution, traces)])
278                    .unwrap_or_default(),
279                address: Some(address),
280                ..Default::default()
281            })
282        } else {
283            eyre::bail!("ENS not supported.");
284        }
285    }
286
287    /// Executes the call
288    ///
289    /// This will commit the changes if `commit` is true.
290    ///
291    /// This will return _estimated_ gas instead of the precise gas the call would consume, so it
292    /// can be used as `gas_limit`.
293    fn call(
294        &mut self,
295        from: Address,
296        to: Address,
297        calldata: Bytes,
298        value: U256,
299        authorization_list: Option<Vec<SignedAuthorization>>,
300        commit: bool,
301    ) -> Result<ScriptResult> {
302        let mut res = if let Some(authorization_list) = authorization_list {
303            self.executor.call_raw_with_authorization(
304                from,
305                to,
306                calldata.clone(),
307                value,
308                authorization_list,
309            )?
310        } else {
311            self.executor.call_raw(from, to, calldata.clone(), value)?
312        };
313        let mut gas_used = res.gas_used;
314
315        // We should only need to calculate realistic gas costs when preparing to broadcast
316        // something. This happens during the onchain simulation stage, where we commit each
317        // collected transactions.
318        //
319        // Otherwise don't re-execute, or some usecases might be broken: https://github.com/foundry-rs/foundry/issues/3921
320        if commit {
321            gas_used = self.search_optimal_gas_usage(&res, from, to, &calldata, value)?;
322            res = self.executor.transact_raw(from, to, calldata, value)?;
323        }
324
325        let RawCallResult { result, reverted, logs, traces, labels, transactions, .. } = res;
326        let breakpoints = res.cheatcodes.map(|cheats| cheats.breakpoints).unwrap_or_default();
327
328        Ok(ScriptResult {
329            returned: result,
330            success: !reverted,
331            gas_used,
332            logs,
333            traces: traces
334                .map(|traces| {
335                    // Manually adjust gas for the trace to add back the stipend/real used gas
336
337                    vec![(TraceKind::Execution, traces)]
338                })
339                .unwrap_or_default(),
340            labeled_addresses: labels,
341            transactions,
342            address: None,
343            breakpoints,
344        })
345    }
346
347    /// The executor will return the _exact_ gas value this transaction consumed, setting this value
348    /// as gas limit will result in `OutOfGas` so to come up with a better estimate we search over a
349    /// possible range we pick a higher gas limit 3x of a succeeded call should be safe.
350    ///
351    /// This might result in executing the same script multiple times. Depending on the user's goal,
352    /// it might be problematic when using `ffi`.
353    fn search_optimal_gas_usage(
354        &mut self,
355        res: &RawCallResult,
356        from: Address,
357        to: Address,
358        calldata: &Bytes,
359        value: U256,
360    ) -> Result<u64> {
361        let mut gas_used = res.gas_used;
362        if matches!(res.exit_reason, return_ok!()) {
363            // Store the current gas limit and reset it later.
364            let init_gas_limit = self.executor.env().tx.gas_limit;
365
366            let mut highest_gas_limit = gas_used * 3;
367            let mut lowest_gas_limit = gas_used;
368            let mut last_highest_gas_limit = highest_gas_limit;
369            while (highest_gas_limit - lowest_gas_limit) > 1 {
370                let mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2;
371                self.executor.env_mut().tx.gas_limit = mid_gas_limit;
372                let res = self.executor.call_raw(from, to, calldata.0.clone().into(), value)?;
373                match res.exit_reason {
374                    InstructionResult::Revert |
375                    InstructionResult::OutOfGas |
376                    InstructionResult::OutOfFunds => {
377                        lowest_gas_limit = mid_gas_limit;
378                    }
379                    _ => {
380                        highest_gas_limit = mid_gas_limit;
381                        // if last two successful estimations only vary by 10%, we consider this to
382                        // sufficiently accurate
383                        const ACCURACY: u64 = 10;
384                        if (last_highest_gas_limit - highest_gas_limit) * ACCURACY /
385                            last_highest_gas_limit <
386                            1
387                        {
388                            // update the gas
389                            gas_used = highest_gas_limit;
390                            break;
391                        }
392                        last_highest_gas_limit = highest_gas_limit;
393                    }
394                }
395            }
396            // Reset gas limit in the executor.
397            self.executor.env_mut().tx.gas_limit = init_gas_limit;
398        }
399        Ok(gas_used)
400    }
401}