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