forge_script/
runner.rs

1use super::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        sender_nonce: u64,
37        is_broadcast: bool,
38        need_create2_deployer: bool,
39    ) -> Result<(Address, ScriptResult)> {
40        trace!(target: "script", "executing setUP()");
41
42        if !is_broadcast {
43            if self.evm_opts.sender == Config::DEFAULT_SENDER {
44                // We max out their balance so that they can deploy and make calls.
45                self.executor.set_balance(self.evm_opts.sender, U256::MAX)?;
46            }
47
48            if need_create2_deployer {
49                self.executor.deploy_create2_deployer()?;
50            }
51        }
52
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        traces.extend(constructor_traces.map(|traces| (TraceKind::Deployment, traces)));
161
162        // Optionally call the `setUp` function
163        let (success, gas_used, labeled_addresses, transactions) = if !setup {
164            self.executor.backend_mut().set_test_contract(address);
165            (true, 0, Default::default(), Some(library_transactions))
166        } else {
167            match self.executor.setup(Some(self.evm_opts.sender), address, None) {
168                Ok(RawCallResult {
169                    reverted,
170                    traces: setup_traces,
171                    labels,
172                    logs: setup_logs,
173                    gas_used,
174                    transactions: setup_transactions,
175                    ..
176                }) => {
177                    traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces)));
178                    logs.extend_from_slice(&setup_logs);
179
180                    if let Some(txs) = setup_transactions {
181                        library_transactions.extend(txs);
182                    }
183
184                    (!reverted, gas_used, labels, Some(library_transactions))
185                }
186                Err(EvmError::Execution(err)) => {
187                    let RawCallResult {
188                        reverted,
189                        traces: setup_traces,
190                        labels,
191                        logs: setup_logs,
192                        gas_used,
193                        transactions,
194                        ..
195                    } = err.raw;
196                    traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces)));
197                    logs.extend_from_slice(&setup_logs);
198
199                    if let Some(txs) = transactions {
200                        library_transactions.extend(txs);
201                    }
202
203                    (!reverted, gas_used, labels, Some(library_transactions))
204                }
205                Err(e) => return Err(e.into()),
206            }
207        };
208
209        Ok((
210            address,
211            ScriptResult {
212                returned: Bytes::new(),
213                success,
214                gas_used,
215                labeled_addresses,
216                transactions,
217                logs,
218                traces,
219                address: None,
220                ..Default::default()
221            },
222        ))
223    }
224
225    /// Executes the method that will collect all broadcastable transactions.
226    pub fn script(&mut self, address: Address, calldata: Bytes) -> Result<ScriptResult> {
227        self.call(self.evm_opts.sender, address, calldata, U256::ZERO, None, false)
228    }
229
230    /// Runs a broadcastable transaction locally and persists its state.
231    pub fn simulate(
232        &mut self,
233        from: Address,
234        to: Option<Address>,
235        calldata: Option<Bytes>,
236        value: Option<U256>,
237        authorization_list: Option<Vec<SignedAuthorization>>,
238    ) -> Result<ScriptResult> {
239        if let Some(to) = to {
240            self.call(
241                from,
242                to,
243                calldata.unwrap_or_default(),
244                value.unwrap_or(U256::ZERO),
245                authorization_list,
246                true,
247            )
248        } else if to.is_none() {
249            let res = self.executor.deploy(
250                from,
251                calldata.expect("No data for create transaction"),
252                value.unwrap_or(U256::ZERO),
253                None,
254            );
255            let (address, RawCallResult { gas_used, logs, traces, .. }) = match res {
256                Ok(DeployResult { address, raw }) => (address, raw),
257                Err(EvmError::Execution(err)) => {
258                    let ExecutionErr { raw, reason } = *err;
259                    sh_err!("Failed with `{reason}`:\n")?;
260                    (Address::ZERO, raw)
261                }
262                Err(e) => eyre::bail!("Failed deploying contract: {e:?}"),
263            };
264
265            Ok(ScriptResult {
266                returned: Bytes::new(),
267                success: address != Address::ZERO,
268                gas_used,
269                logs,
270                // Manually adjust gas for the trace to add back the stipend/real used gas
271                traces: traces
272                    .map(|traces| vec![(TraceKind::Execution, traces)])
273                    .unwrap_or_default(),
274                address: Some(address),
275                ..Default::default()
276            })
277        } else {
278            eyre::bail!("ENS not supported.");
279        }
280    }
281
282    /// Executes the call
283    ///
284    /// This will commit the changes if `commit` is true.
285    ///
286    /// This will return _estimated_ gas instead of the precise gas the call would consume, so it
287    /// can be used as `gas_limit`.
288    fn call(
289        &mut self,
290        from: Address,
291        to: Address,
292        calldata: Bytes,
293        value: U256,
294        authorization_list: Option<Vec<SignedAuthorization>>,
295        commit: bool,
296    ) -> Result<ScriptResult> {
297        let mut res = if let Some(authorization_list) = authorization_list {
298            self.executor.call_raw_with_authorization(
299                from,
300                to,
301                calldata.clone(),
302                value,
303                authorization_list,
304            )?
305        } else {
306            self.executor.call_raw(from, to, calldata.clone(), value)?
307        };
308        let mut gas_used = res.gas_used;
309
310        // We should only need to calculate realistic gas costs when preparing to broadcast
311        // something. This happens during the onchain simulation stage, where we commit each
312        // collected transactions.
313        //
314        // Otherwise don't re-execute, or some usecases might be broken: https://github.com/foundry-rs/foundry/issues/3921
315        if commit {
316            gas_used = self.search_optimal_gas_usage(&res, from, to, &calldata, value)?;
317            res = self.executor.transact_raw(from, to, calldata, value)?;
318        }
319
320        let RawCallResult { result, reverted, logs, traces, labels, transactions, .. } = res;
321        let breakpoints = res.cheatcodes.map(|cheats| cheats.breakpoints).unwrap_or_default();
322
323        Ok(ScriptResult {
324            returned: result,
325            success: !reverted,
326            gas_used,
327            logs,
328            traces: traces
329                .map(|traces| {
330                    // Manually adjust gas for the trace to add back the stipend/real used gas
331
332                    vec![(TraceKind::Execution, traces)]
333                })
334                .unwrap_or_default(),
335            labeled_addresses: labels,
336            transactions,
337            address: None,
338            breakpoints,
339        })
340    }
341
342    /// The executor will return the _exact_ gas value this transaction consumed, setting this value
343    /// as gas limit will result in `OutOfGas` so to come up with a better estimate we search over a
344    /// possible range we pick a higher gas limit 3x of a succeeded call should be safe.
345    ///
346    /// This might result in executing the same script multiple times. Depending on the user's goal,
347    /// it might be problematic when using `ffi`.
348    fn search_optimal_gas_usage(
349        &mut self,
350        res: &RawCallResult,
351        from: Address,
352        to: Address,
353        calldata: &Bytes,
354        value: U256,
355    ) -> Result<u64> {
356        let mut gas_used = res.gas_used;
357        if matches!(res.exit_reason, return_ok!()) {
358            // Store the current gas limit and reset it later.
359            let init_gas_limit = self.executor.env().tx.gas_limit;
360
361            let mut highest_gas_limit = gas_used * 3;
362            let mut lowest_gas_limit = gas_used;
363            let mut last_highest_gas_limit = highest_gas_limit;
364            while (highest_gas_limit - lowest_gas_limit) > 1 {
365                let mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2;
366                self.executor.env_mut().tx.gas_limit = mid_gas_limit;
367                let res = self.executor.call_raw(from, to, calldata.0.clone().into(), value)?;
368                match res.exit_reason {
369                    InstructionResult::Revert |
370                    InstructionResult::OutOfGas |
371                    InstructionResult::OutOfFunds => {
372                        lowest_gas_limit = mid_gas_limit;
373                    }
374                    _ => {
375                        highest_gas_limit = mid_gas_limit;
376                        // if last two successful estimations only vary by 10%, we consider this to
377                        // sufficiently accurate
378                        const ACCURACY: u64 = 10;
379                        if (last_highest_gas_limit - highest_gas_limit) * ACCURACY /
380                            last_highest_gas_limit <
381                            1
382                        {
383                            // update the gas
384                            gas_used = highest_gas_limit;
385                            break;
386                        }
387                        last_highest_gas_limit = highest_gas_limit;
388                    }
389                }
390            }
391            // Reset gas limit in the executor.
392            self.executor.env_mut().tx.gas_limit = init_gas_limit;
393        }
394        Ok(gas_used)
395    }
396}