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::NamedChain;
25use foundry_debugger::Debugger;
26use foundry_evm::{
27    core::evm::FoundryEvmNetwork,
28    decode::decode_console_logs,
29    hardforks::TempoHardfork,
30    inspectors::cheatcodes::BroadcastableTransactions,
31    traces::{
32        CallTraceDecoder, CallTraceDecoderBuilder, DebugTraceIdentifier, TraceKind,
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(mut self) -> Result<ExecutedState<FEN>> {
112        let mut runner = self
113            .script_config
114            .get_runner_with_cheatcodes(
115                self.build_data.known_contracts.clone(),
116                self.script_wallets.clone(),
117                self.args.debug,
118                self.build_data.build_data.target.clone(),
119            )
120            .await?;
121        let result = self.execute_with_runner(&mut runner).await?;
122
123        // If we have a new sender from execution, we need to use it to deploy libraries and relink
124        // contracts.
125        if let Some(new_sender) = self.maybe_new_sender(result.transactions.as_ref())? {
126            self.script_config.update_sender(new_sender).await?;
127
128            // Rollback to rerun linking with the new sender.
129            let state = CompiledState {
130                args: self.args,
131                script_config: self.script_config,
132                script_wallets: self.script_wallets,
133                browser_wallet: self.browser_wallet,
134                build_data: self.build_data.build_data,
135            };
136
137            return Box::pin(state.link().await?.prepare_execution().await?.execute()).await;
138        }
139
140        Ok(ExecutedState {
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,
146            execution_data: self.execution_data,
147            execution_result: result,
148        })
149    }
150
151    /// Executes the script using the provided runner and returns the [ScriptResult].
152    pub async fn execute_with_runner(
153        &self,
154        runner: &mut ScriptRunner<FEN>,
155    ) -> Result<ScriptResult<FEN::Network>> {
156        let (address, mut setup_result) = runner.setup(
157            &self.build_data.predeploy_libraries,
158            self.execution_data.bytecode.clone(),
159            needs_setup(&self.execution_data.abi),
160            &self.script_config,
161            self.args.broadcast,
162        )?;
163
164        if setup_result.success {
165            let script_result = runner.script(address, self.execution_data.calldata.clone())?;
166
167            setup_result.success &= script_result.success;
168            setup_result.gas_used = script_result.gas_used;
169            setup_result.logs.extend(script_result.logs);
170            setup_result.traces.extend(script_result.traces);
171            setup_result.labeled_addresses.extend(script_result.labeled_addresses);
172            setup_result.debug_bytecodes.extend(script_result.debug_bytecodes);
173            setup_result.returned = script_result.returned;
174            setup_result.exit_reason = script_result.exit_reason;
175            setup_result.breakpoints = script_result.breakpoints;
176
177            match (&mut setup_result.transactions, script_result.transactions) {
178                (Some(txs), Some(new_txs)) => {
179                    txs.extend(new_txs);
180                }
181                (None, Some(new_txs)) => {
182                    setup_result.transactions = Some(new_txs);
183                }
184                _ => {}
185            }
186        }
187
188        Ok(setup_result)
189    }
190
191    /// It finds the deployer from the running script and uses it to predeploy libraries.
192    ///
193    /// If there are multiple candidate addresses, it skips everything and lets `--sender` deploy
194    /// them instead.
195    fn maybe_new_sender(
196        &self,
197        transactions: Option<&BroadcastableTransactions<FEN::Network>>,
198    ) -> Result<Option<Address>> {
199        let mut new_sender = None;
200
201        if let Some(txs) = transactions {
202            // If the user passed a `--sender` don't check anything.
203            if self.build_data.predeploy_libraries.libraries_count() > 0
204                && self.args.evm.sender.is_none()
205            {
206                for tx in txs {
207                    if tx.transaction.to().is_none() {
208                        let sender = tx.transaction.from().expect("no sender");
209                        if let Some(ns) = new_sender {
210                            if sender != ns {
211                                sh_warn!(
212                                    "You have more than one deployer who could predeploy libraries. Using `--sender` instead."
213                                )?;
214                                return Ok(None);
215                            }
216                        } else if sender != self.script_config.evm_opts.sender {
217                            new_sender = Some(sender);
218                        }
219                    }
220                }
221            }
222        }
223        Ok(new_sender)
224    }
225}
226
227/// Container for information about RPC-endpoints used during script execution.
228pub struct RpcData {
229    /// Unique list of rpc urls present.
230    pub total_rpcs: HashSet<String>,
231    /// If true, one of the transactions did not have a rpc.
232    pub missing_rpc: bool,
233}
234
235impl RpcData {
236    /// Iterates over script transactions and collects RPC urls.
237    fn from_transactions<N: Network>(txs: &BroadcastableTransactions<N>) -> Self {
238        let missing_rpc = txs.iter().any(|tx| tx.rpc.is_none());
239        let total_rpcs = txs.iter().filter_map(|tx| tx.rpc.clone()).collect::<HashSet<_>>();
240
241        Self { total_rpcs, missing_rpc }
242    }
243
244    /// Returns true if script might be multi-chain.
245    /// Returns false positive in case when missing rpc is the same as the only rpc present.
246    pub fn is_multi_chain(&self) -> bool {
247        self.total_rpcs.len() > 1 || (self.missing_rpc && !self.total_rpcs.is_empty())
248    }
249
250    /// Checks if all RPCs support EIP-3855. Prints a warning if not.
251    async fn check_shanghai_support(&self) -> Result<()> {
252        let chain_ids = self.total_rpcs.iter().map(|rpc| async move {
253            let provider = ProviderBuilder::<AnyNetwork>::new(rpc).build().ok()?;
254            let id = provider.get_chain_id().await.ok()?;
255            NamedChain::try_from(id).ok()
256        });
257
258        let chains = join_all(chain_ids).await;
259        let iter = chains.iter().flatten().map(|c| (c.supports_shanghai(), c));
260        if iter.clone().any(|(s, _)| !s) {
261            let msg = format!(
262                "\
263EIP-3855 is not supported in one or more of the RPCs used.
264Unsupported Chain IDs: {}.
265Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly.
266For more information, please see https://eips.ethereum.org/EIPS/eip-3855",
267                iter.filter(|(supported, _)| !supported)
268                    .map(|(_, chain)| *chain as u64)
269                    .format(", ")
270            );
271            sh_warn!("{msg}")?;
272        }
273        Ok(())
274    }
275}
276
277/// Container for data being collected after execution.
278pub struct ExecutionArtifacts {
279    /// Trace decoder used to decode traces.
280    pub decoder: CallTraceDecoder,
281    /// Return values from the execution result.
282    pub returns: HashMap<String, NestedValue>,
283    /// Information about RPC endpoints used during script execution.
284    pub rpc_data: RpcData,
285}
286
287/// State after the script has been executed.
288pub struct ExecutedState<FEN: FoundryEvmNetwork> {
289    pub args: ScriptArgs,
290    pub script_config: ScriptConfig<FEN>,
291    pub script_wallets: Wallets,
292    pub browser_wallet: Option<BrowserSigner<FEN::Network>>,
293    pub build_data: LinkedBuildData,
294    pub execution_data: ExecutionData,
295    pub execution_result: ScriptResult<FEN::Network>,
296}
297
298impl<FEN: FoundryEvmNetwork> ExecutedState<FEN> {
299    /// Collects the data we need for simulation and various post-execution tasks.
300    pub async fn prepare_simulation(self) -> Result<PreSimulationState<FEN>> {
301        let returns = self.get_returns()?;
302
303        let decoder = self.build_trace_decoder(&self.build_data.known_contracts).await?;
304
305        let mut txs: BroadcastableTransactions<FEN::Network> =
306            self.execution_result.transactions.clone().unwrap_or_default();
307
308        // Ensure that unsigned transactions have both `data` and `input` populated to avoid
309        // issues with eth_estimateGas and eth_sendTransaction requests.
310        for tx in &mut txs {
311            if let Some(req) = tx.transaction.as_unsigned_mut()
312                && let Some(input) = req.input().cloned()
313            {
314                *req = req.clone().with_input_kind(input, TransactionInputKind::Both);
315            }
316        }
317        let rpc_data = RpcData::from_transactions(&txs);
318
319        if rpc_data.is_multi_chain() {
320            sh_warn!("Multi chain deployment is still under development. Use with caution.")?;
321            if !self.build_data.libraries.is_empty() {
322                eyre::bail!(
323                    "Multi chain deployment does not support library linking at the moment."
324                );
325            }
326        }
327        rpc_data.check_shanghai_support().await?;
328
329        Ok(PreSimulationState {
330            args: self.args,
331            script_config: self.script_config,
332            script_wallets: self.script_wallets,
333            browser_wallet: self.browser_wallet,
334            build_data: self.build_data,
335            execution_data: self.execution_data,
336            execution_result: self.execution_result,
337            execution_artifacts: ExecutionArtifacts { decoder, returns, rpc_data },
338        })
339    }
340
341    /// Builds [CallTraceDecoder] from the execution result and known contracts.
342    async fn build_trace_decoder(
343        &self,
344        known_contracts: &ContractsByArtifact,
345    ) -> Result<CallTraceDecoder> {
346        let chain_id = self.script_config.evm_opts.get_remote_chain_id().await;
347        let is_tempo = self.script_config.evm_opts.networks.is_tempo()
348            || chain_id.as_ref().is_some_and(|chain| chain.is_tempo());
349        let mut tracing = self.script_config.config.tracing.clone();
350        tracing.labels.extend(self.execution_result.labeled_addresses.clone());
351
352        let mut decoder = CallTraceDecoderBuilder::new()
353            .with_tracing_config(&tracing)
354            .with_known_contracts(known_contracts)
355            .with_signature_identifier(SignaturesIdentifier::from_config(
356                &self.script_config.config,
357            )?)
358            .with_chain_id(chain_id.map(|c| c.id()))
359            .with_tempo_hardfork(
360                is_tempo.then(|| self.script_config.config.evm_spec_id::<TempoHardfork>()),
361            )
362            .build();
363
364        if tracing.decode_internal {
365            decoder.debug_identifier =
366                Some(DebugTraceIdentifier::new(self.build_data.sources.clone()));
367        }
368
369        let use_debug_bytecodes =
370            self.args.debug && !self.execution_result.debug_bytecodes.is_empty();
371        let mut identifier = if use_debug_bytecodes {
372            TraceIdentifiers::new()
373                .with_local_and_bytecodes(known_contracts, &self.execution_result.debug_bytecodes)
374        } else {
375            TraceIdentifiers::new().with_local(known_contracts)
376        }
377        .with_external(&self.script_config.config, chain_id)?;
378
379        for (_, trace) in &self.execution_result.traces {
380            decoder.identify(trace, &mut identifier);
381        }
382
383        Ok(decoder)
384    }
385
386    /// Collects the return values from the execution result.
387    fn get_returns(&self) -> Result<HashMap<String, NestedValue>> {
388        let mut returns = HashMap::default();
389        let returned = &self.execution_result.returned;
390        let func = &self.execution_data.func;
391
392        match func.abi_decode_output(returned) {
393            Ok(decoded) => {
394                for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() {
395                    let internal_type =
396                        output.internal_type.clone().unwrap_or(InternalType::Other {
397                            contract: None,
398                            ty: "unknown".to_string(),
399                        });
400
401                    let label = if output.name.is_empty() {
402                        index.to_string()
403                    } else {
404                        output.name.clone()
405                    };
406
407                    returns.insert(
408                        label,
409                        NestedValue {
410                            internal_type: internal_type.to_string(),
411                            value: format_token_raw(token),
412                        },
413                    );
414                }
415            }
416            Err(_) => {
417                sh_err!("Failed to decode return value: {:x?}", returned)?;
418            }
419        }
420
421        Ok(returns)
422    }
423}
424
425impl<FEN: FoundryEvmNetwork> PreSimulationState<FEN> {
426    pub async fn show_json(&self) -> Result<()> {
427        let mut result = self.execution_result.clone();
428        let trace_depth = self.script_config.config.tracing.trace_depth;
429
430        for (_, trace) in &mut result.traces {
431            decode_trace_arena(trace, &self.execution_artifacts.decoder).await;
432            if let Some(trace_depth) = trace_depth {
433                *trace = trace_arena_at_depth(trace, trace_depth);
434            }
435        }
436
437        let json_result = JsonResult {
438            logs: decode_console_logs(&result.logs),
439            returns: &self.execution_artifacts.returns,
440            result: &result,
441        };
442        let json = serde_json::to_string(&json_result)?;
443
444        sh_println!("{json}")?;
445
446        if !self.execution_result.success {
447            return Err(eyre::eyre!(
448                "script failed: {}",
449                &self
450                    .execution_artifacts
451                    .decoder
452                    .revert_decoder
453                    .decode(&result.returned[..], result.exit_reason)
454            ));
455        }
456
457        Ok(())
458    }
459
460    pub async fn show_traces(&self) -> Result<()> {
461        let tracing = &self.script_config.config.tracing;
462        let verbosity = tracing.verbosity;
463        let func = &self.execution_data.func;
464        let result = &self.execution_result;
465        let decoder = &self.execution_artifacts.decoder;
466
467        if !result.success || verbosity > 3 {
468            if result.traces.is_empty() {
469                warn!(verbosity, "no traces");
470            }
471
472            sh_println!("Traces:")?;
473            for (kind, trace) in &result.traces {
474                let should_include = match kind {
475                    TraceKind::Setup => verbosity >= 5,
476                    TraceKind::Execution => verbosity > 3,
477                    _ => false,
478                } || !result.success;
479
480                if should_include {
481                    let mut trace = trace.clone();
482                    decode_trace_arena(&mut trace, decoder).await;
483                    if let Some(trace_depth) = tracing.trace_depth {
484                        prune_trace_depth(&mut trace, trace_depth);
485                    }
486                    sh_println!("{}", render_trace_arena_inner(&trace, false, verbosity > 4))?;
487                }
488            }
489            sh_println!()?;
490        }
491
492        if result.success {
493            sh_println!("{}", "Script ran successfully.".green())?;
494        }
495
496        if self.script_config.evm_opts.fork_url.is_none() {
497            sh_println!("Gas used: {}", result.gas_used)?;
498        }
499
500        if result.success && !result.returned.is_empty() {
501            sh_println!("\n== Return ==")?;
502            match func.abi_decode_output(&result.returned) {
503                Ok(decoded) => {
504                    for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() {
505                        let internal_type =
506                            output.internal_type.clone().unwrap_or(InternalType::Other {
507                                contract: None,
508                                ty: "unknown".to_string(),
509                            });
510
511                        let label = if output.name.is_empty() {
512                            index.to_string()
513                        } else {
514                            output.name.clone()
515                        };
516                        sh_println!(
517                            "{label}: {internal_type} {value}",
518                            label = label.trim_end(),
519                            value = format_token(token)
520                        )?;
521                    }
522                }
523                Err(_) => {
524                    sh_err!("{:x?}", (&result.returned))?;
525                }
526            }
527        }
528
529        let console_logs = decode_console_logs(&result.logs);
530        if !console_logs.is_empty() {
531            sh_println!("\n== Logs ==")?;
532            for log in console_logs {
533                sh_println!("  {log}")?;
534            }
535        }
536
537        if !result.success {
538            return Err(eyre::eyre!(
539                "script failed: {}",
540                &self
541                    .execution_artifacts
542                    .decoder
543                    .revert_decoder
544                    .decode(&result.returned[..], result.exit_reason)
545            ));
546        }
547
548        Ok(())
549    }
550
551    pub fn run_debugger(self) -> Result<()> {
552        self.create_debugger().try_run_tui()?;
553        Ok(())
554    }
555
556    pub fn dump_debugger(self, path: &Path) -> Result<()> {
557        self.create_debugger().dump_to_file(path)?;
558        Ok(())
559    }
560
561    fn create_debugger(self) -> Debugger {
562        Debugger::builder()
563            .traces(
564                self.execution_result
565                    .traces
566                    .into_iter()
567                    .filter(|(t, _)| t.is_execution())
568                    .collect(),
569            )
570            .decoder(&self.execution_artifacts.decoder)
571            .sources(self.build_data.sources)
572            .breakpoints(self.execution_result.breakpoints)
573            .layout(self.args.debug_layout.unwrap_or_default())
574            .build()
575    }
576}