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