forge_script/
execute.rs

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