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