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