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            .with_label_disabled(self.args.disable_labels)
336            .build();
337
338        let mut identifier = TraceIdentifiers::new().with_local(known_contracts).with_etherscan(
339            &self.script_config.config,
340            self.script_config.evm_opts.get_remote_chain_id().await,
341        )?;
342
343        for (_, trace) in &self.execution_result.traces {
344            decoder.identify(trace, &mut identifier);
345        }
346
347        Ok(decoder)
348    }
349
350    /// Collects the return values from the execution result.
351    fn get_returns(&self) -> Result<HashMap<String, NestedValue>> {
352        let mut returns = HashMap::default();
353        let returned = &self.execution_result.returned;
354        let func = &self.execution_data.func;
355
356        match func.abi_decode_output(returned) {
357            Ok(decoded) => {
358                for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() {
359                    let internal_type =
360                        output.internal_type.clone().unwrap_or(InternalType::Other {
361                            contract: None,
362                            ty: "unknown".to_string(),
363                        });
364
365                    let label = if !output.name.is_empty() {
366                        output.name.to_string()
367                    } else {
368                        index.to_string()
369                    };
370
371                    returns.insert(
372                        label,
373                        NestedValue {
374                            internal_type: internal_type.to_string(),
375                            value: format_token_raw(token),
376                        },
377                    );
378                }
379            }
380            Err(_) => {
381                sh_err!("Failed to decode return value: {:x?}", returned)?;
382            }
383        }
384
385        Ok(returns)
386    }
387}
388
389impl PreSimulationState {
390    pub async fn show_json(&self) -> Result<()> {
391        let mut result = self.execution_result.clone();
392
393        for (_, trace) in &mut result.traces {
394            decode_trace_arena(trace, &self.execution_artifacts.decoder).await;
395        }
396
397        let json_result = JsonResult {
398            logs: decode_console_logs(&result.logs),
399            returns: &self.execution_artifacts.returns,
400            result: &result,
401        };
402        let json = serde_json::to_string(&json_result)?;
403
404        sh_println!("{json}")?;
405
406        if !self.execution_result.success {
407            return Err(eyre::eyre!(
408                "script failed: {}",
409                &self.execution_artifacts.decoder.revert_decoder.decode(&result.returned[..], None)
410            ));
411        }
412
413        Ok(())
414    }
415
416    pub async fn show_traces(&self) -> Result<()> {
417        let verbosity = self.script_config.evm_opts.verbosity;
418        let func = &self.execution_data.func;
419        let result = &self.execution_result;
420        let decoder = &self.execution_artifacts.decoder;
421
422        if !result.success || verbosity > 3 {
423            if result.traces.is_empty() {
424                warn!(verbosity, "no traces");
425            }
426
427            sh_println!("Traces:")?;
428            for (kind, trace) in &result.traces {
429                let should_include = match kind {
430                    TraceKind::Setup => verbosity >= 5,
431                    TraceKind::Execution => verbosity > 3,
432                    _ => false,
433                } || !result.success;
434
435                if should_include {
436                    let mut trace = trace.clone();
437                    decode_trace_arena(&mut trace, decoder).await;
438                    sh_println!("{}", render_trace_arena(&trace))?;
439                }
440            }
441            sh_println!()?;
442        }
443
444        if result.success {
445            sh_println!("{}", "Script ran successfully.".green())?;
446        }
447
448        if self.script_config.evm_opts.fork_url.is_none() {
449            sh_println!("Gas used: {}", result.gas_used)?;
450        }
451
452        if result.success && !result.returned.is_empty() {
453            sh_println!("\n== Return ==")?;
454            match func.abi_decode_output(&result.returned) {
455                Ok(decoded) => {
456                    for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() {
457                        let internal_type =
458                            output.internal_type.clone().unwrap_or(InternalType::Other {
459                                contract: None,
460                                ty: "unknown".to_string(),
461                            });
462
463                        let label = if !output.name.is_empty() {
464                            output.name.to_string()
465                        } else {
466                            index.to_string()
467                        };
468                        sh_println!(
469                            "{label}: {internal_type} {value}",
470                            label = label.trim_end(),
471                            value = format_token(token)
472                        )?;
473                    }
474                }
475                Err(_) => {
476                    sh_err!("{:x?}", (&result.returned))?;
477                }
478            }
479        }
480
481        let console_logs = decode_console_logs(&result.logs);
482        if !console_logs.is_empty() {
483            sh_println!("\n== Logs ==")?;
484            for log in console_logs {
485                sh_println!("  {log}")?;
486            }
487        }
488
489        if !result.success {
490            return Err(eyre::eyre!(
491                "script failed: {}",
492                &self.execution_artifacts.decoder.revert_decoder.decode(&result.returned[..], None)
493            ));
494        }
495
496        Ok(())
497    }
498
499    pub fn run_debugger(self) -> Result<()> {
500        self.create_debugger().try_run_tui()?;
501        Ok(())
502    }
503
504    pub fn dump_debugger(self, path: &Path) -> Result<()> {
505        self.create_debugger().dump_to_file(path)?;
506        Ok(())
507    }
508
509    fn create_debugger(self) -> Debugger {
510        Debugger::builder()
511            .traces(
512                self.execution_result
513                    .traces
514                    .into_iter()
515                    .filter(|(t, _)| t.is_execution())
516                    .collect(),
517            )
518            .decoder(&self.execution_artifacts.decoder)
519            .sources(self.build_data.sources)
520            .breakpoints(self.execution_result.breakpoints)
521            .build()
522    }
523}