forge_script/
execute.rs

1use super::{JsonResult, NestedValue, ScriptResult, runner::ScriptRunner};
2use crate::{
3    ScriptArgs, ScriptConfig,
4    build::{CompiledState, LinkedBuildData},
5    simulate::PreSimulationState,
6};
7use alloy_dyn_abi::FunctionExt;
8use alloy_json_abi::{Function, InternalType, JsonAbi};
9use alloy_primitives::{
10    Address, Bytes,
11    map::{HashMap, HashSet},
12};
13use alloy_provider::Provider;
14use alloy_rpc_types::TransactionInput;
15use eyre::{OptionExt, Result};
16use foundry_cheatcodes::Wallets;
17use foundry_cli::utils::{ensure_clean_constructor, needs_setup};
18use foundry_common::{
19    ContractsByArtifact,
20    fmt::{format_token, format_token_raw},
21    provider::get_http_provider,
22};
23use foundry_config::NamedChain;
24use foundry_debugger::Debugger;
25use foundry_evm::{
26    decode::decode_console_logs,
27    inspectors::cheatcodes::BroadcastableTransactions,
28    traces::{
29        CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, decode_trace_arena,
30        identifier::{SignaturesIdentifier, TraceIdentifiers},
31        render_trace_arena,
32    },
33};
34use futures::future::join_all;
35use itertools::Itertools;
36use std::path::Path;
37use yansi::Paint;
38
39/// State after linking, contains the linked build data along with library addresses and optional
40/// array of libraries that need to be predeployed.
41pub struct LinkedState {
42    pub args: ScriptArgs,
43    pub script_config: ScriptConfig,
44    pub script_wallets: Wallets,
45    pub build_data: LinkedBuildData,
46}
47
48/// Container for data we need for execution which can only be obtained after linking stage.
49#[derive(Debug)]
50pub struct ExecutionData {
51    /// Function to call.
52    pub func: Function,
53    /// Calldata to pass to the target contract.
54    pub calldata: Bytes,
55    /// Bytecode of the target contract.
56    pub bytecode: Bytes,
57    /// ABI of the target contract.
58    pub abi: JsonAbi,
59}
60
61impl LinkedState {
62    /// Given linked and compiled artifacts, prepares data we need for execution.
63    /// This includes the function to call and the calldata to pass to it.
64    pub async fn prepare_execution(self) -> Result<PreExecutionState> {
65        let Self { args, script_config, script_wallets, build_data } = self;
66
67        let target_contract = build_data.get_target_contract()?;
68
69        let bytecode = target_contract.bytecode().ok_or_eyre("target contract has no bytecode")?;
70
71        let (func, calldata) = args.get_method_and_calldata(&target_contract.abi)?;
72
73        ensure_clean_constructor(&target_contract.abi)?;
74
75        Ok(PreExecutionState {
76            args,
77            script_config,
78            script_wallets,
79            execution_data: ExecutionData {
80                func,
81                calldata,
82                bytecode: bytecode.clone(),
83                abi: target_contract.abi.clone(),
84            },
85            build_data,
86        })
87    }
88}
89
90/// Same as [LinkedState], but also contains [ExecutionData].
91#[derive(Debug)]
92pub struct PreExecutionState {
93    pub args: ScriptArgs,
94    pub script_config: ScriptConfig,
95    pub script_wallets: Wallets,
96    pub build_data: LinkedBuildData,
97    pub execution_data: ExecutionData,
98}
99
100impl PreExecutionState {
101    /// Executes the script and returns the state after execution.
102    /// Might require executing script twice in cases when we determine sender from execution.
103    pub async fn execute(mut self) -> Result<ExecutedState> {
104        let mut runner = self
105            .script_config
106            .get_runner_with_cheatcodes(
107                self.build_data.known_contracts.clone(),
108                self.script_wallets.clone(),
109                self.args.debug,
110                self.build_data.build_data.target.clone(),
111            )
112            .await?;
113        let result = self.execute_with_runner(&mut runner).await?;
114
115        // If we have a new sender from execution, we need to use it to deploy libraries and relink
116        // contracts.
117        if let Some(new_sender) = self.maybe_new_sender(result.transactions.as_ref())? {
118            self.script_config.update_sender(new_sender).await?;
119
120            // Rollback to rerun linking with the new sender.
121            let state = CompiledState {
122                args: self.args,
123                script_config: self.script_config,
124                script_wallets: self.script_wallets,
125                build_data: self.build_data.build_data,
126            };
127
128            return Box::pin(state.link().await?.prepare_execution().await?.execute()).await;
129        }
130
131        Ok(ExecutedState {
132            args: self.args,
133            script_config: self.script_config,
134            script_wallets: self.script_wallets,
135            build_data: self.build_data,
136            execution_data: self.execution_data,
137            execution_result: result,
138        })
139    }
140
141    /// Executes the script using the provided runner and returns the [ScriptResult].
142    pub async fn execute_with_runner(&self, runner: &mut ScriptRunner) -> Result<ScriptResult> {
143        let (address, mut setup_result) = runner.setup(
144            &self.build_data.predeploy_libraries,
145            self.execution_data.bytecode.clone(),
146            needs_setup(&self.execution_data.abi),
147            &self.script_config,
148            self.args.broadcast,
149        )?;
150
151        if setup_result.success {
152            let script_result = runner.script(address, self.execution_data.calldata.clone())?;
153
154            setup_result.success &= script_result.success;
155            setup_result.gas_used = script_result.gas_used;
156            setup_result.logs.extend(script_result.logs);
157            setup_result.traces.extend(script_result.traces);
158            setup_result.labeled_addresses.extend(script_result.labeled_addresses);
159            setup_result.returned = script_result.returned;
160            setup_result.breakpoints = script_result.breakpoints;
161
162            match (&mut setup_result.transactions, script_result.transactions) {
163                (Some(txs), Some(new_txs)) => {
164                    txs.extend(new_txs);
165                }
166                (None, Some(new_txs)) => {
167                    setup_result.transactions = Some(new_txs);
168                }
169                _ => {}
170            }
171        }
172
173        Ok(setup_result)
174    }
175
176    /// It finds the deployer from the running script and uses it to predeploy libraries.
177    ///
178    /// If there are multiple candidate addresses, it skips everything and lets `--sender` deploy
179    /// them instead.
180    fn maybe_new_sender(
181        &self,
182        transactions: Option<&BroadcastableTransactions>,
183    ) -> Result<Option<Address>> {
184        let mut new_sender = None;
185
186        if let Some(txs) = transactions {
187            // If the user passed a `--sender` don't check anything.
188            if self.build_data.predeploy_libraries.libraries_count() > 0
189                && self.args.evm.sender.is_none()
190            {
191                for tx in txs {
192                    if tx.transaction.to().is_none() {
193                        let sender = tx.transaction.from().expect("no sender");
194                        if let Some(ns) = new_sender {
195                            if sender != ns {
196                                sh_warn!(
197                                    "You have more than one deployer who could predeploy libraries. Using `--sender` instead."
198                                )?;
199                                return Ok(None);
200                            }
201                        } else if sender != self.script_config.evm_opts.sender {
202                            new_sender = Some(sender);
203                        }
204                    }
205                }
206            }
207        }
208        Ok(new_sender)
209    }
210}
211
212/// Container for information about RPC-endpoints used during script execution.
213pub struct RpcData {
214    /// Unique list of rpc urls present.
215    pub total_rpcs: HashSet<String>,
216    /// If true, one of the transactions did not have a rpc.
217    pub missing_rpc: bool,
218}
219
220impl RpcData {
221    /// Iterates over script transactions and collects RPC urls.
222    fn from_transactions(txs: &BroadcastableTransactions) -> Self {
223        let missing_rpc = txs.iter().any(|tx| tx.rpc.is_none());
224        let total_rpcs =
225            txs.iter().filter_map(|tx| tx.rpc.as_ref().cloned()).collect::<HashSet<_>>();
226
227        Self { total_rpcs, missing_rpc }
228    }
229
230    /// Returns true if script might be multi-chain.
231    /// Returns false positive in case when missing rpc is the same as the only rpc present.
232    pub fn is_multi_chain(&self) -> bool {
233        self.total_rpcs.len() > 1 || (self.missing_rpc && !self.total_rpcs.is_empty())
234    }
235
236    /// Checks if all RPCs support EIP-3855. Prints a warning if not.
237    async fn check_shanghai_support(&self) -> Result<()> {
238        let chain_ids = self.total_rpcs.iter().map(|rpc| async move {
239            let provider = get_http_provider(rpc);
240            let id = provider.get_chain_id().await.ok()?;
241            NamedChain::try_from(id).ok()
242        });
243
244        let chains = join_all(chain_ids).await;
245        let iter = chains.iter().flatten().map(|c| (c.supports_shanghai(), c));
246        if iter.clone().any(|(s, _)| !s) {
247            let msg = format!(
248                "\
249EIP-3855 is not supported in one or more of the RPCs used.
250Unsupported Chain IDs: {}.
251Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly.
252For more information, please see https://eips.ethereum.org/EIPS/eip-3855",
253                iter.filter(|(supported, _)| !supported)
254                    .map(|(_, chain)| *chain as u64)
255                    .format(", ")
256            );
257            sh_warn!("{msg}")?;
258        }
259        Ok(())
260    }
261}
262
263/// Container for data being collected after execution.
264pub struct ExecutionArtifacts {
265    /// Trace decoder used to decode traces.
266    pub decoder: CallTraceDecoder,
267    /// Return values from the execution result.
268    pub returns: HashMap<String, NestedValue>,
269    /// Information about RPC endpoints used during script execution.
270    pub rpc_data: RpcData,
271}
272
273/// State after the script has been executed.
274pub struct ExecutedState {
275    pub args: ScriptArgs,
276    pub script_config: ScriptConfig,
277    pub script_wallets: Wallets,
278    pub build_data: LinkedBuildData,
279    pub execution_data: ExecutionData,
280    pub execution_result: ScriptResult,
281}
282
283impl ExecutedState {
284    /// Collects the data we need for simulation and various post-execution tasks.
285    pub async fn prepare_simulation(self) -> Result<PreSimulationState> {
286        let returns = self.get_returns()?;
287
288        let decoder = self.build_trace_decoder(&self.build_data.known_contracts).await?;
289
290        let mut txs = self.execution_result.transactions.clone().unwrap_or_default();
291
292        // Ensure that unsigned transactions have both `data` and `input` populated to avoid
293        // issues with eth_estimateGas and eth_sendTransaction requests.
294        for tx in &mut txs {
295            if let Some(req) = tx.transaction.as_unsigned_mut() {
296                req.input =
297                    TransactionInput::maybe_both(std::mem::take(&mut req.input).into_input());
298            }
299        }
300        let rpc_data = RpcData::from_transactions(&txs);
301
302        if rpc_data.is_multi_chain() {
303            sh_warn!("Multi chain deployment is still under development. Use with caution.")?;
304            if !self.build_data.libraries.is_empty() {
305                eyre::bail!(
306                    "Multi chain deployment does not support library linking at the moment."
307                )
308            }
309        }
310        rpc_data.check_shanghai_support().await?;
311
312        Ok(PreSimulationState {
313            args: self.args,
314            script_config: self.script_config,
315            script_wallets: self.script_wallets,
316            build_data: self.build_data,
317            execution_data: self.execution_data,
318            execution_result: self.execution_result,
319            execution_artifacts: ExecutionArtifacts { decoder, returns, rpc_data },
320        })
321    }
322
323    /// Builds [CallTraceDecoder] from the execution result and known contracts.
324    async fn build_trace_decoder(
325        &self,
326        known_contracts: &ContractsByArtifact,
327    ) -> Result<CallTraceDecoder> {
328        let mut decoder = CallTraceDecoderBuilder::new()
329            .with_labels(self.execution_result.labeled_addresses.clone())
330            .with_verbosity(self.script_config.evm_opts.verbosity)
331            .with_known_contracts(known_contracts)
332            .with_signature_identifier(SignaturesIdentifier::from_config(
333                &self.script_config.config,
334            )?)
335            .build();
336
337        let mut identifier = TraceIdentifiers::new().with_local(known_contracts).with_etherscan(
338            &self.script_config.config,
339            self.script_config.evm_opts.get_remote_chain_id().await,
340        )?;
341
342        for (_, trace) in &self.execution_result.traces {
343            decoder.identify(trace, &mut identifier);
344        }
345
346        Ok(decoder)
347    }
348
349    /// Collects the return values from the execution result.
350    fn get_returns(&self) -> Result<HashMap<String, NestedValue>> {
351        let mut returns = HashMap::default();
352        let returned = &self.execution_result.returned;
353        let func = &self.execution_data.func;
354
355        match func.abi_decode_output(returned) {
356            Ok(decoded) => {
357                for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() {
358                    let internal_type =
359                        output.internal_type.clone().unwrap_or(InternalType::Other {
360                            contract: None,
361                            ty: "unknown".to_string(),
362                        });
363
364                    let label = if !output.name.is_empty() {
365                        output.name.to_string()
366                    } else {
367                        index.to_string()
368                    };
369
370                    returns.insert(
371                        label,
372                        NestedValue {
373                            internal_type: internal_type.to_string(),
374                            value: format_token_raw(token),
375                        },
376                    );
377                }
378            }
379            Err(_) => {
380                sh_err!("Failed to decode return value: {:x?}", returned)?;
381            }
382        }
383
384        Ok(returns)
385    }
386}
387
388impl PreSimulationState {
389    pub async fn show_json(&self) -> Result<()> {
390        let mut result = self.execution_result.clone();
391
392        for (_, trace) in &mut result.traces {
393            decode_trace_arena(trace, &self.execution_artifacts.decoder).await;
394        }
395
396        let json_result = JsonResult {
397            logs: decode_console_logs(&result.logs),
398            returns: &self.execution_artifacts.returns,
399            result: &result,
400        };
401        let json = serde_json::to_string(&json_result)?;
402
403        sh_println!("{json}")?;
404
405        if !self.execution_result.success {
406            return Err(eyre::eyre!(
407                "script failed: {}",
408                &self.execution_artifacts.decoder.revert_decoder.decode(&result.returned[..], None)
409            ));
410        }
411
412        Ok(())
413    }
414
415    pub async fn show_traces(&self) -> Result<()> {
416        let verbosity = self.script_config.evm_opts.verbosity;
417        let func = &self.execution_data.func;
418        let result = &self.execution_result;
419        let decoder = &self.execution_artifacts.decoder;
420
421        if !result.success || verbosity > 3 {
422            if result.traces.is_empty() {
423                warn!(verbosity, "no traces");
424            }
425
426            sh_println!("Traces:")?;
427            for (kind, trace) in &result.traces {
428                let should_include = match kind {
429                    TraceKind::Setup => verbosity >= 5,
430                    TraceKind::Execution => verbosity > 3,
431                    _ => false,
432                } || !result.success;
433
434                if should_include {
435                    let mut trace = trace.clone();
436                    decode_trace_arena(&mut trace, decoder).await;
437                    sh_println!("{}", render_trace_arena(&trace))?;
438                }
439            }
440            sh_println!()?;
441        }
442
443        if result.success {
444            sh_println!("{}", "Script ran successfully.".green())?;
445        }
446
447        if self.script_config.evm_opts.fork_url.is_none() {
448            sh_println!("Gas used: {}", result.gas_used)?;
449        }
450
451        if result.success && !result.returned.is_empty() {
452            sh_println!("\n== Return ==")?;
453            match func.abi_decode_output(&result.returned) {
454                Ok(decoded) => {
455                    for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() {
456                        let internal_type =
457                            output.internal_type.clone().unwrap_or(InternalType::Other {
458                                contract: None,
459                                ty: "unknown".to_string(),
460                            });
461
462                        let label = if !output.name.is_empty() {
463                            output.name.to_string()
464                        } else {
465                            index.to_string()
466                        };
467                        sh_println!(
468                            "{label}: {internal_type} {value}",
469                            label = label.trim_end(),
470                            value = format_token(token)
471                        )?;
472                    }
473                }
474                Err(_) => {
475                    sh_err!("{:x?}", (&result.returned))?;
476                }
477            }
478        }
479
480        let console_logs = decode_console_logs(&result.logs);
481        if !console_logs.is_empty() {
482            sh_println!("\n== Logs ==")?;
483            for log in console_logs {
484                sh_println!("  {log}")?;
485            }
486        }
487
488        if !result.success {
489            return Err(eyre::eyre!(
490                "script failed: {}",
491                &self.execution_artifacts.decoder.revert_decoder.decode(&result.returned[..], None)
492            ));
493        }
494
495        Ok(())
496    }
497
498    pub fn run_debugger(self) -> Result<()> {
499        self.create_debugger().try_run_tui()?;
500        Ok(())
501    }
502
503    pub fn dump_debugger(self, path: &Path) -> Result<()> {
504        self.create_debugger().dump_to_file(path)?;
505        Ok(())
506    }
507
508    fn create_debugger(self) -> Debugger {
509        Debugger::builder()
510            .traces(
511                self.execution_result
512                    .traces
513                    .into_iter()
514                    .filter(|(t, _)| t.is_execution())
515                    .collect(),
516            )
517            .decoder(&self.execution_artifacts.decoder)
518            .sources(self.build_data.sources)
519            .breakpoints(self.execution_result.breakpoints)
520            .build()
521    }
522}