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::{FoundryTransactionBuilder, 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 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                self.executor.deploy_create2_deployer()?;
55            }
56        }
57
58        let sender_nonce = script_config.sender_nonce;
59        self.executor.set_nonce(self.evm_opts.sender, sender_nonce)?;
60
61        // We max out their balance so that they can deploy and make calls.
62        self.executor.set_balance(CALLER, U256::MAX)?;
63
64        let mut library_transactions = VecDeque::new();
65        let mut traces = Traces::default();
66
67        // Deploy libraries
68        match libraries {
69            ScriptPredeployLibraries::Default(libraries) => libraries.iter().for_each(|code| {
70                let result = self
71                    .executor
72                    .deploy(self.evm_opts.sender, code.clone(), U256::ZERO, None)
73                    .expect("couldn't deploy library")
74                    .raw;
75
76                if let Some(deploy_traces) = result.traces {
77                    traces.push((TraceKind::Deployment, deploy_traces));
78                }
79
80                let mut tx_req = TransactionRequestFor::<FEN>::default()
81                    .with_from(self.evm_opts.sender)
82                    .with_input(code.clone())
83                    .with_nonce(sender_nonce + library_transactions.len() as u64);
84
85                if let Some(fee_token) = script_config.fee_token {
86                    tx_req.set_fee_token(fee_token);
87                }
88
89                library_transactions.push_back(BroadcastableTransaction {
90                    rpc: self.evm_opts.fork_url.clone(),
91                    transaction: TransactionMaybeSigned::new(tx_req),
92                })
93            }),
94            ScriptPredeployLibraries::Create2(libraries, salt) => {
95                let create2_deployer = self.executor.create2_deployer();
96                for library in libraries {
97                    let address = create2_deployer.create2_from_code(salt, library.as_ref());
98                    // Skip if already deployed
99                    if !self.executor.is_empty_code(address)? {
100                        continue;
101                    }
102                    let calldata = [salt.as_ref(), library.as_ref()].concat();
103                    let result = self
104                        .executor
105                        .transact_raw(
106                            self.evm_opts.sender,
107                            create2_deployer,
108                            calldata.clone().into(),
109                            U256::from(0),
110                        )
111                        .expect("couldn't deploy library");
112
113                    if let Some(deploy_traces) = result.traces {
114                        traces.push((TraceKind::Deployment, deploy_traces));
115                    }
116
117                    let mut tx_req = TransactionRequestFor::<FEN>::default()
118                        .with_from(self.evm_opts.sender)
119                        .with_input(calldata)
120                        .with_nonce(sender_nonce + library_transactions.len() as u64)
121                        .with_to(create2_deployer);
122
123                    if let Some(fee_token) = script_config.fee_token {
124                        tx_req.set_fee_token(fee_token);
125                    }
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, .. }) = 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                address: Some(address),
296                ..Default::default()
297            })
298        }
299    }
300
301    /// Executes the call
302    ///
303    /// This will commit the changes if `commit` is true.
304    ///
305    /// This will return _estimated_ gas instead of the precise gas the call would consume, so it
306    /// can be used as `gas_limit`.
307    fn call(
308        &mut self,
309        from: Address,
310        to: Address,
311        calldata: Bytes,
312        value: U256,
313        authorization_list: Option<Vec<SignedAuthorization>>,
314        commit: bool,
315    ) -> Result<ScriptResult<FEN::Network>> {
316        let mut res = if let Some(authorization_list) = &authorization_list {
317            self.executor.call_raw_with_authorization(
318                from,
319                to,
320                calldata.clone(),
321                value,
322                authorization_list.clone(),
323            )?
324        } else {
325            self.executor.call_raw(from, to, calldata.clone(), value)?
326        };
327        let mut gas_used = res.gas_used;
328
329        // We should only need to calculate realistic gas costs when preparing to broadcast
330        // something. This happens during the onchain simulation stage, where we commit each
331        // collected transactions.
332        //
333        // Otherwise don't re-execute, or some usecases might be broken: https://github.com/foundry-rs/foundry/issues/3921
334        if commit {
335            gas_used = self.search_optimal_gas_usage(&res, from, to, &calldata, value)?;
336            res = if let Some(authorization_list) = authorization_list {
337                self.executor.transact_raw_with_authorization(
338                    from,
339                    to,
340                    calldata,
341                    value,
342                    authorization_list,
343                )?
344            } else {
345                self.executor.transact_raw(from, to, calldata, value)?
346            }
347        }
348
349        let RawCallResult { result, reverted, logs, traces, labels, transactions, .. } = res;
350        let breakpoints = res.cheatcodes.map(|cheats| cheats.breakpoints).unwrap_or_default();
351
352        Ok(ScriptResult {
353            returned: result,
354            success: !reverted,
355            gas_used,
356            logs,
357            traces: traces
358                .map(|traces| {
359                    // Manually adjust gas for the trace to add back the stipend/real used gas
360
361                    vec![(TraceKind::Execution, traces)]
362                })
363                .unwrap_or_default(),
364            labeled_addresses: labels,
365            transactions,
366            address: None,
367            breakpoints,
368        })
369    }
370
371    /// The executor will return the _exact_ gas value this transaction consumed, setting this value
372    /// as gas limit will result in `OutOfGas` so to come up with a better estimate we search over a
373    /// possible range we pick a higher gas limit 3x of a succeeded call should be safe.
374    ///
375    /// This might result in executing the same script multiple times. Depending on the user's goal,
376    /// it might be problematic when using `ffi`.
377    fn search_optimal_gas_usage(
378        &mut self,
379        res: &RawCallResult<FEN>,
380        from: Address,
381        to: Address,
382        calldata: &Bytes,
383        value: U256,
384    ) -> Result<u64> {
385        let mut gas_used = res.gas_used;
386        if matches!(res.exit_reason, Some(return_ok!())) {
387            // Store the current gas limit and reset it later.
388            let init_gas_limit = self.executor.tx_env().gas_limit();
389
390            let mut highest_gas_limit = gas_used * 3;
391            let mut lowest_gas_limit = gas_used;
392            let mut last_highest_gas_limit = highest_gas_limit;
393            while (highest_gas_limit - lowest_gas_limit) > 1 {
394                let mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2;
395                self.executor.tx_env_mut().set_gas_limit(mid_gas_limit);
396                let res = self.executor.call_raw(from, to, calldata.0.clone().into(), value)?;
397                match res.exit_reason {
398                    Some(
399                        InstructionResult::Revert
400                        | InstructionResult::OutOfGas
401                        | InstructionResult::OutOfFunds,
402                    ) => {
403                        lowest_gas_limit = mid_gas_limit;
404                    }
405                    _ => {
406                        highest_gas_limit = mid_gas_limit;
407                        // if last two successful estimations only vary by 10%, we consider this to
408                        // sufficiently accurate
409                        const ACCURACY: u64 = 10;
410                        if (last_highest_gas_limit - highest_gas_limit) * ACCURACY
411                            / last_highest_gas_limit
412                            < 1
413                        {
414                            // update the gas
415                            gas_used = highest_gas_limit;
416                            break;
417                        }
418                        last_highest_gas_limit = highest_gas_limit;
419                    }
420                }
421            }
422            // Reset gas limit in the executor.
423            self.executor.tx_env_mut().set_gas_limit(init_gas_limit);
424        }
425        Ok(gas_used)
426    }
427}