Skip to main content

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