forge_script/
execute.rs

1use super::{runner::ScriptRunner, JsonResult, NestedValue, ScriptResult};
2use crate::{
3    build::{CompiledState, LinkedBuildData},
4    simulate::PreSimulationState,
5    ScriptArgs, ScriptConfig,
6};
7use alloy_dyn_abi::FunctionExt;
8use alloy_json_abi::{Function, InternalType, JsonAbi};
9use alloy_primitives::{
10    map::{HashMap, HashSet},
11    Address, Bytes,
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    fmt::{format_token, format_token_raw},
20    provider::get_http_provider,
21    ContractsByArtifact,
22};
23use foundry_config::NamedChain;
24use foundry_debugger::Debugger;
25use foundry_evm::{
26    decode::decode_console_logs,
27    inspectors::cheatcodes::BroadcastableTransactions,
28    traces::{
29        decode_trace_arena,
30        identifier::{SignaturesIdentifier, TraceIdentifiers},
31        render_trace_arena, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind,
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!("You have more than one deployer who could predeploy libraries. Using `--sender` instead.")?;
197                                return Ok(None);
198                            }
199                        } else if sender != self.script_config.evm_opts.sender {
200                            new_sender = Some(sender);
201                        }
202                    }
203                }
204            }
205        }
206        Ok(new_sender)
207    }
208}
209
210/// Container for information about RPC-endpoints used during script execution.
211pub struct RpcData {
212    /// Unique list of rpc urls present.
213    pub total_rpcs: HashSet<String>,
214    /// If true, one of the transactions did not have a rpc.
215    pub missing_rpc: bool,
216}
217
218impl RpcData {
219    /// Iterates over script transactions and collects RPC urls.
220    fn from_transactions(txs: &BroadcastableTransactions) -> Self {
221        let missing_rpc = txs.iter().any(|tx| tx.rpc.is_none());
222        let total_rpcs =
223            txs.iter().filter_map(|tx| tx.rpc.as_ref().cloned()).collect::<HashSet<_>>();
224
225        Self { total_rpcs, missing_rpc }
226    }
227
228    /// Returns true if script might be multi-chain.
229    /// Returns false positive in case when missing rpc is the same as the only rpc present.
230    pub fn is_multi_chain(&self) -> bool {
231        self.total_rpcs.len() > 1 || (self.missing_rpc && !self.total_rpcs.is_empty())
232    }
233
234    /// Checks if all RPCs support EIP-3855. Prints a warning if not.
235    async fn check_shanghai_support(&self) -> Result<()> {
236        let chain_ids = self.total_rpcs.iter().map(|rpc| async move {
237            let provider = get_http_provider(rpc);
238            let id = provider.get_chain_id().await.ok()?;
239            NamedChain::try_from(id).ok()
240        });
241
242        let chains = join_all(chain_ids).await;
243        let iter = chains.iter().flatten().map(|c| (c.supports_shanghai(), c));
244        if iter.clone().any(|(s, _)| !s) {
245            let msg = format!(
246                "\
247EIP-3855 is not supported in one or more of the RPCs used.
248Unsupported Chain IDs: {}.
249Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly.
250For more information, please see https://eips.ethereum.org/EIPS/eip-3855",
251                iter.filter(|(supported, _)| !supported)
252                    .map(|(_, chain)| *chain as u64)
253                    .format(", ")
254            );
255            sh_warn!("{msg}")?;
256        }
257        Ok(())
258    }
259}
260
261/// Container for data being collected after execution.
262pub struct ExecutionArtifacts {
263    /// Trace decoder used to decode traces.
264    pub decoder: CallTraceDecoder,
265    /// Return values from the execution result.
266    pub returns: HashMap<String, NestedValue>,
267    /// Information about RPC endpoints used during script execution.
268    pub rpc_data: RpcData,
269}
270
271/// State after the script has been executed.
272pub struct ExecutedState {
273    pub args: ScriptArgs,
274    pub script_config: ScriptConfig,
275    pub script_wallets: Wallets,
276    pub build_data: LinkedBuildData,
277    pub execution_data: ExecutionData,
278    pub execution_result: ScriptResult,
279}
280
281impl ExecutedState {
282    /// Collects the data we need for simulation and various post-execution tasks.
283    pub async fn prepare_simulation(self) -> Result<PreSimulationState> {
284        let returns = self.get_returns()?;
285
286        let decoder = self.build_trace_decoder(&self.build_data.known_contracts).await?;
287
288        let mut txs = self.execution_result.transactions.clone().unwrap_or_default();
289
290        // Ensure that unsigned transactions have both `data` and `input` populated to avoid
291        // issues with eth_estimateGas and eth_sendTransaction requests.
292        for tx in &mut txs {
293            if let Some(req) = tx.transaction.as_unsigned_mut() {
294                req.input =
295                    TransactionInput::maybe_both(std::mem::take(&mut req.input).into_input());
296            }
297        }
298        let rpc_data = RpcData::from_transactions(&txs);
299
300        if rpc_data.is_multi_chain() {
301            sh_warn!("Multi chain deployment is still under development. Use with caution.")?;
302            if !self.build_data.libraries.is_empty() {
303                eyre::bail!(
304                    "Multi chain deployment does not support library linking at the moment."
305                )
306            }
307        }
308        rpc_data.check_shanghai_support().await?;
309
310        Ok(PreSimulationState {
311            args: self.args,
312            script_config: self.script_config,
313            script_wallets: self.script_wallets,
314            build_data: self.build_data,
315            execution_data: self.execution_data,
316            execution_result: self.execution_result,
317            execution_artifacts: ExecutionArtifacts { decoder, returns, rpc_data },
318        })
319    }
320
321    /// Builds [CallTraceDecoder] from the execution result and known contracts.
322    async fn build_trace_decoder(
323        &self,
324        known_contracts: &ContractsByArtifact,
325    ) -> Result<CallTraceDecoder> {
326        let mut decoder = CallTraceDecoderBuilder::new()
327            .with_labels(self.execution_result.labeled_addresses.clone())
328            .with_verbosity(self.script_config.evm_opts.verbosity)
329            .with_known_contracts(known_contracts)
330            .with_signature_identifier(SignaturesIdentifier::from_config(
331                &self.script_config.config,
332            )?)
333            .build();
334
335        let mut identifier = TraceIdentifiers::new().with_local(known_contracts).with_etherscan(
336            &self.script_config.config,
337            self.script_config.evm_opts.get_remote_chain_id().await,
338        )?;
339
340        for (_, trace) in &self.execution_result.traces {
341            decoder.identify(trace, &mut identifier);
342        }
343
344        Ok(decoder)
345    }
346
347    /// Collects the return values from the execution result.
348    fn get_returns(&self) -> Result<HashMap<String, NestedValue>> {
349        let mut returns = HashMap::default();
350        let returned = &self.execution_result.returned;
351        let func = &self.execution_data.func;
352
353        match func.abi_decode_output(returned) {
354            Ok(decoded) => {
355                for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() {
356                    let internal_type =
357                        output.internal_type.clone().unwrap_or(InternalType::Other {
358                            contract: None,
359                            ty: "unknown".to_string(),
360                        });
361
362                    let label = if !output.name.is_empty() {
363                        output.name.to_string()
364                    } else {
365                        index.to_string()
366                    };
367
368                    returns.insert(
369                        label,
370                        NestedValue {
371                            internal_type: internal_type.to_string(),
372                            value: format_token_raw(token),
373                        },
374                    );
375                }
376            }
377            Err(_) => {
378                sh_err!("Failed to decode return value: {:x?}", returned)?;
379            }
380        }
381
382        Ok(returns)
383    }
384}
385
386impl PreSimulationState {
387    pub async fn show_json(&self) -> Result<()> {
388        let mut result = self.execution_result.clone();
389
390        for (_, trace) in &mut result.traces {
391            decode_trace_arena(trace, &self.execution_artifacts.decoder).await;
392        }
393
394        let json_result = JsonResult {
395            logs: decode_console_logs(&result.logs),
396            returns: &self.execution_artifacts.returns,
397            result: &result,
398        };
399        let json = serde_json::to_string(&json_result)?;
400
401        sh_println!("{json}")?;
402
403        if !self.execution_result.success {
404            return Err(eyre::eyre!(
405                "script failed: {}",
406                &self.execution_artifacts.decoder.revert_decoder.decode(&result.returned[..], None)
407            ));
408        }
409
410        Ok(())
411    }
412
413    pub async fn show_traces(&self) -> Result<()> {
414        let verbosity = self.script_config.evm_opts.verbosity;
415        let func = &self.execution_data.func;
416        let result = &self.execution_result;
417        let decoder = &self.execution_artifacts.decoder;
418
419        if !result.success || verbosity > 3 {
420            if result.traces.is_empty() {
421                warn!(verbosity, "no traces");
422            }
423
424            sh_println!("Traces:")?;
425            for (kind, trace) in &result.traces {
426                let should_include = match kind {
427                    TraceKind::Setup => verbosity >= 5,
428                    TraceKind::Execution => verbosity > 3,
429                    _ => false,
430                } || !result.success;
431
432                if should_include {
433                    let mut trace = trace.clone();
434                    decode_trace_arena(&mut trace, decoder).await;
435                    sh_println!("{}", render_trace_arena(&trace))?;
436                }
437            }
438            sh_println!()?;
439        }
440
441        if result.success {
442            sh_println!("{}", "Script ran successfully.".green())?;
443        }
444
445        if self.script_config.evm_opts.fork_url.is_none() {
446            sh_println!("Gas used: {}", result.gas_used)?;
447        }
448
449        if result.success && !result.returned.is_empty() {
450            sh_println!("\n== Return ==")?;
451            match func.abi_decode_output(&result.returned) {
452                Ok(decoded) => {
453                    for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() {
454                        let internal_type =
455                            output.internal_type.clone().unwrap_or(InternalType::Other {
456                                contract: None,
457                                ty: "unknown".to_string(),
458                            });
459
460                        let label = if !output.name.is_empty() {
461                            output.name.to_string()
462                        } else {
463                            index.to_string()
464                        };
465                        sh_println!(
466                            "{label}: {internal_type} {value}",
467                            label = label.trim_end(),
468                            value = format_token(token)
469                        )?;
470                    }
471                }
472                Err(_) => {
473                    sh_err!("{:x?}", (&result.returned))?;
474                }
475            }
476        }
477
478        let console_logs = decode_console_logs(&result.logs);
479        if !console_logs.is_empty() {
480            sh_println!("\n== Logs ==")?;
481            for log in console_logs {
482                sh_println!("  {log}")?;
483            }
484        }
485
486        if !result.success {
487            return Err(eyre::eyre!(
488                "script failed: {}",
489                &self.execution_artifacts.decoder.revert_decoder.decode(&result.returned[..], None)
490            ));
491        }
492
493        Ok(())
494    }
495
496    pub fn run_debugger(self) -> Result<()> {
497        self.create_debugger().try_run_tui()?;
498        Ok(())
499    }
500
501    pub fn dump_debugger(self, path: &Path) -> Result<()> {
502        self.create_debugger().dump_to_file(path)?;
503        Ok(())
504    }
505
506    fn create_debugger(self) -> Debugger {
507        Debugger::builder()
508            .traces(
509                self.execution_result
510                    .traces
511                    .into_iter()
512                    .filter(|(t, _)| t.is_execution())
513                    .collect(),
514            )
515            .decoder(&self.execution_artifacts.decoder)
516            .sources(self.build_data.sources)
517            .breakpoints(self.execution_result.breakpoints)
518            .build()
519    }
520}