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