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};
10use alloy_primitives::{
11 Address, Bytes,
12 map::{HashMap, HashSet},
13};
14use alloy_provider::Provider;
15use eyre::{OptionExt, Result};
16use foundry_cheatcodes::Wallets;
17use foundry_cli::utils::{ensure_clean_constructor, needs_setup};
18use foundry_common::{
19 ContractsByArtifact,
20 fmt::{format_token, format_token_raw},
21 provider::ProviderBuilder,
22};
23use foundry_config::{Chain, NamedChain};
24use foundry_debugger::Debugger;
25use foundry_evm::{
26 core::evm::FoundryEvmNetwork,
27 decode::decode_console_logs,
28 inspectors::cheatcodes::BroadcastableTransactions,
29 traces::{
30 CallTraceDecoder, CallTraceDecoderBuilder, DebugTraceIdentifier, TraceKind,
31 debug::ContractSources,
32 decode_trace_arena,
33 identifier::{SignaturesIdentifier, TraceIdentifiers},
34 prune_trace_depth, render_trace_arena_inner, trace_arena_at_depth,
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
43pub 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#[derive(Debug)]
55pub struct ExecutionData {
56 pub func: Function,
58 pub calldata: Bytes,
60 pub bytecode: Bytes,
62 pub abi: JsonAbi,
64}
65
66impl<FEN: FoundryEvmNetwork> LinkedState<FEN> {
67 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#[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 pub async fn execute(self) -> Result<ExecutedState<FEN>> {
111 self.execute_inner(false).await
112 }
113
114 pub(crate) async fn execute_restricted(self) -> Result<ExecutedState<FEN>> {
116 self.execute_inner(true).await
117 }
118
119 async fn execute_inner(mut self, restricted: bool) -> Result<ExecutedState<FEN>> {
120 let mut runner = self
121 .script_config
122 .get_runner_with_cheatcodes(
123 self.build_data.known_contracts.clone(),
124 self.script_wallets.clone(),
125 self.args.debug,
126 self.build_data.build_data.target.clone(),
127 restricted,
128 )
129 .await?;
130 let result = self.execute_with_runner(&mut runner).await?;
131
132 if let Some(new_sender) = self.maybe_new_sender(result.transactions.as_ref())? {
135 self.script_config.update_sender(new_sender).await?;
136
137 let state = CompiledState {
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.build_data,
144 };
145
146 return Box::pin(
147 state.link().await?.prepare_execution().await?.execute_inner(restricted),
148 )
149 .await;
150 }
151
152 Ok(ExecutedState {
153 args: self.args,
154 script_config: self.script_config,
155 script_wallets: self.script_wallets,
156 browser_wallet: self.browser_wallet,
157 build_data: self.build_data,
158 execution_data: self.execution_data,
159 execution_result: result,
160 })
161 }
162
163 pub async fn execute_with_runner(
165 &self,
166 runner: &mut ScriptRunner<FEN>,
167 ) -> Result<ScriptResult<FEN::Network>> {
168 let (address, mut setup_result) = runner.setup(
169 &self.build_data.predeploy_libraries,
170 self.execution_data.bytecode.clone(),
171 needs_setup(&self.execution_data.abi),
172 &self.script_config,
173 self.args.broadcast,
174 )?;
175
176 if setup_result.success {
177 let script_result = runner.script(address, self.execution_data.calldata.clone())?;
178
179 setup_result.success &= script_result.success;
180 setup_result.gas_used = script_result.gas_used;
181 setup_result.logs.extend(script_result.logs);
182 setup_result.traces.extend(script_result.traces);
183 setup_result.labeled_addresses.extend(script_result.labeled_addresses);
184 setup_result.debug_bytecodes.extend(script_result.debug_bytecodes);
185 setup_result.returned = script_result.returned;
186 setup_result.exit_reason = script_result.exit_reason;
187 setup_result.breakpoints = script_result.breakpoints;
188
189 match (&mut setup_result.transactions, script_result.transactions) {
190 (Some(txs), Some(new_txs)) => {
191 txs.extend(new_txs);
192 }
193 (None, Some(new_txs)) => {
194 setup_result.transactions = Some(new_txs);
195 }
196 _ => {}
197 }
198 }
199
200 Ok(setup_result)
201 }
202
203 fn maybe_new_sender(
208 &self,
209 transactions: Option<&BroadcastableTransactions<FEN::Network>>,
210 ) -> Result<Option<Address>> {
211 let mut new_sender = None;
212
213 if let Some(txs) = transactions {
214 if self.build_data.predeploy_libraries.libraries_count() > 0
216 && self.args.evm.sender.is_none()
217 {
218 for tx in txs {
219 if tx.transaction.to().is_none() {
220 let sender = tx.transaction.from().expect("no sender");
221 if let Some(ns) = new_sender {
222 if sender != ns {
223 sh_warn!(
224 "You have more than one deployer who could predeploy libraries. Using `--sender` instead."
225 )?;
226 return Ok(None);
227 }
228 } else if sender != self.script_config.evm_opts.sender {
229 new_sender = Some(sender);
230 }
231 }
232 }
233 }
234 }
235 Ok(new_sender)
236 }
237}
238
239pub struct RpcData {
241 pub total_rpcs: HashSet<String>,
243 pub missing_rpc: bool,
245 pub(crate) chain_ids: HashMap<String, u64>,
247}
248
249impl RpcData {
250 fn from_transactions<N: Network>(txs: &BroadcastableTransactions<N>) -> Self {
252 let missing_rpc = txs.iter().any(|tx| tx.rpc.is_none());
253 let total_rpcs = txs.iter().filter_map(|tx| tx.rpc.clone()).collect::<HashSet<_>>();
254
255 Self { total_rpcs, missing_rpc, chain_ids: HashMap::default() }
256 }
257
258 pub fn is_multi_chain(&self) -> bool {
261 self.total_rpcs.len() > 1 || (self.missing_rpc && !self.total_rpcs.is_empty())
262 }
263
264 async fn check_shanghai_support(&mut self) -> Result<()> {
266 let chain_ids =
267 self.total_rpcs.iter().filter(|rpc| !self.chain_ids.contains_key(*rpc)).map(
268 |rpc| async move {
269 let provider = ProviderBuilder::<AnyNetwork>::new(rpc).build().ok()?;
270 Some((rpc.clone(), provider.get_chain_id().await.ok()?))
271 },
272 );
273
274 self.chain_ids.extend(join_all(chain_ids).await.into_iter().flatten());
275 let iter = self
276 .chain_ids
277 .values()
278 .filter_map(|id| NamedChain::try_from(*id).ok())
279 .map(|chain| (chain.supports_shanghai(), chain));
280 if iter.clone().any(|(s, _)| !s) {
281 let msg = format!(
282 "\
283EIP-3855 is not supported in one or more of the RPCs used.
284Unsupported Chain IDs: {}.
285Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly.
286For more information, please see https://eips.ethereum.org/EIPS/eip-3855",
287 iter.filter(|(supported, _)| !supported)
288 .map(|(_, chain)| chain as u64)
289 .format(", ")
290 );
291 sh_warn!("{msg}")?;
292 }
293 Ok(())
294 }
295}
296
297pub struct ExecutionArtifacts {
299 pub decoder: CallTraceDecoder,
301 pub returns: HashMap<String, NestedValue>,
303 pub rpc_data: RpcData,
305}
306
307pub struct ExecutedState<FEN: FoundryEvmNetwork> {
309 pub args: ScriptArgs,
310 pub script_config: ScriptConfig<FEN>,
311 pub script_wallets: Wallets,
312 pub browser_wallet: Option<BrowserSigner<FEN::Network>>,
313 pub build_data: LinkedBuildData,
314 pub execution_data: ExecutionData,
315 pub execution_result: ScriptResult<FEN::Network>,
316}
317
318impl<FEN: FoundryEvmNetwork> ExecutedState<FEN> {
319 pub async fn prepare_simulation(self) -> Result<PreSimulationState<FEN>> {
321 self.prepare_simulation_inner(false).await
322 }
323
324 pub(crate) async fn prepare_simulation_silent(self) -> Result<PreSimulationState<FEN>> {
327 self.prepare_simulation_inner(true).await
328 }
329
330 async fn prepare_simulation_inner(self, silent: bool) -> Result<PreSimulationState<FEN>> {
331 let returns = self.get_returns()?;
332
333 let mut rpc_data = RpcData::from_transactions(
334 self.execution_result.transactions.as_ref().unwrap_or(&Default::default()),
335 );
336 if let Some(identity) = &self.script_config.evm_opts.fork_endpoint
337 && rpc_data.total_rpcs.contains(&identity.endpoint)
338 {
339 rpc_data.chain_ids.insert(identity.endpoint.clone(), identity.execution_chain_id);
340 }
341
342 if rpc_data.is_multi_chain() && !silent {
343 sh_warn!("Multi chain deployment is still under development. Use with caution.")?;
344 if !self.build_data.libraries.is_empty() {
345 eyre::bail!(
346 "Multi chain deployment does not support library linking at the moment."
347 );
348 }
349 }
350 if !silent {
351 rpc_data.check_shanghai_support().await?;
352 }
353
354 let decoder = self.build_trace_decoder(&rpc_data).await?;
355
356 Ok(PreSimulationState {
357 args: self.args,
358 script_config: self.script_config,
359 script_wallets: self.script_wallets,
360 browser_wallet: self.browser_wallet,
361 build_data: self.build_data,
362 execution_data: self.execution_data,
363 execution_result: self.execution_result,
364 execution_artifacts: ExecutionArtifacts { decoder, returns, rpc_data },
365 })
366 }
367
368 async fn build_trace_decoder(&self, rpc_data: &RpcData) -> Result<CallTraceDecoder> {
370 let chain_id = self.script_config.source_chain_id.map(Chain::from).or_else(|| {
371 self.script_config
372 .evm_opts
373 .fork_url
374 .as_ref()
375 .and_then(|url| rpc_data.chain_ids.get(url))
376 .map(|chain_id| (*chain_id).into())
377 });
378 let chain_id = match chain_id {
379 Some(chain_id) => Some(chain_id),
380 None => self.script_config.evm_opts.get_remote_chain_id().await,
381 };
382 build_trace_decoder_for_context(
383 &self.args,
384 &self.script_config,
385 &self.build_data.known_contracts,
386 &self.build_data.sources,
387 &self.execution_result,
388 chain_id,
389 )
390 }
391
392 fn get_returns(&self) -> Result<HashMap<String, NestedValue>> {
394 let mut returns = HashMap::default();
395 let returned = &self.execution_result.returned;
396 let func = &self.execution_data.func;
397
398 match func.abi_decode_output(returned) {
399 Ok(decoded) => {
400 for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() {
401 let internal_type =
402 output.internal_type.clone().unwrap_or(InternalType::Other {
403 contract: None,
404 ty: "unknown".to_string(),
405 });
406
407 let label = if output.name.is_empty() {
408 index.to_string()
409 } else {
410 output.name.clone()
411 };
412
413 returns.insert(
414 label,
415 NestedValue {
416 internal_type: internal_type.to_string(),
417 value: format_token_raw(token),
418 },
419 );
420 }
421 }
422 Err(_) => {
423 sh_err!("Failed to decode return value: {:x?}", returned)?;
424 }
425 }
426
427 Ok(returns)
428 }
429}
430
431pub(crate) fn build_trace_decoder_for_context<FEN: FoundryEvmNetwork>(
433 args: &ScriptArgs,
434 script_config: &ScriptConfig<FEN>,
435 known_contracts: &ContractsByArtifact,
436 sources: &ContractSources,
437 execution_result: &ScriptResult<FEN::Network>,
438 chain_id: Option<Chain>,
439) -> Result<CallTraceDecoder> {
440 let resolved_hardfork = script_config.hardfork;
441 let mut tracing = script_config.config.tracing.clone();
442 tracing.labels.extend(execution_result.labeled_addresses.clone());
443
444 let builder = CallTraceDecoderBuilder::new()
445 .with_tracing_config(&tracing)
446 .with_known_contracts(known_contracts)
447 .with_signature_identifier(SignaturesIdentifier::from_config(&script_config.config)?)
448 .with_networks(script_config.config.networks)
449 .with_chain_id(chain_id.map(|chain| chain.id()))
450 .with_hardfork(resolved_hardfork);
451 let mut decoder = builder.build();
452
453 if tracing.decode_internal && !args.debug {
455 decoder.debug_identifier = Some(DebugTraceIdentifier::new(sources.clone()));
456 }
457
458 let use_debug_bytecodes = args.debug && !execution_result.debug_bytecodes.is_empty();
459 let mut identifier = if use_debug_bytecodes {
460 TraceIdentifiers::new()
461 .with_local_and_bytecodes(known_contracts, &execution_result.debug_bytecodes)
462 } else {
463 TraceIdentifiers::new().with_local(known_contracts)
464 }
465 .with_external(&script_config.config, chain_id)?;
466
467 for (_, trace) in &execution_result.traces {
468 decoder.identify(trace, &mut identifier);
469 }
470
471 Ok(decoder)
472}
473
474impl<FEN: FoundryEvmNetwork> PreSimulationState<FEN> {
475 pub async fn show_json(&self) -> Result<()> {
476 let mut result = self.execution_result.clone();
477 let trace_depth = self.script_config.config.tracing.trace_depth;
478
479 for (_, trace) in &mut result.traces {
480 decode_trace_arena(trace, &self.execution_artifacts.decoder).await;
481 if let Some(trace_depth) = trace_depth {
482 *trace = trace_arena_at_depth(trace, trace_depth);
483 }
484 }
485
486 let json_result = JsonResult {
487 logs: decode_console_logs(&result.logs),
488 returns: &self.execution_artifacts.returns,
489 result: &result,
490 };
491 let json = serde_json::to_string(&json_result)?;
492
493 sh_println!("{json}")?;
494
495 if !self.execution_result.success {
496 return Err(eyre::eyre!(
497 "script failed: {}",
498 &self
499 .execution_artifacts
500 .decoder
501 .revert_decoder
502 .decode(&result.returned[..], result.exit_reason)
503 ));
504 }
505
506 Ok(())
507 }
508
509 pub async fn show_traces(&self) -> Result<()> {
510 let tracing = &self.script_config.config.tracing;
511 let verbosity = tracing.verbosity;
512 let func = &self.execution_data.func;
513 let result = &self.execution_result;
514 let decoder = &self.execution_artifacts.decoder;
515
516 if !result.success || verbosity > 3 {
517 if result.traces.is_empty() {
518 warn!(verbosity, "no traces");
519 }
520
521 sh_println!("Traces:")?;
522 for (kind, trace) in &result.traces {
523 let should_include = match kind {
524 TraceKind::Setup => verbosity >= 5,
525 TraceKind::Execution => verbosity > 3,
526 _ => false,
527 } || !result.success;
528
529 if should_include {
530 let mut trace = trace.clone();
531 decode_trace_arena(&mut trace, decoder).await;
532 if let Some(trace_depth) = tracing.trace_depth {
533 prune_trace_depth(&mut trace, trace_depth);
534 }
535 sh_println!("{}", render_trace_arena_inner(&trace, false, verbosity > 4))?;
536 }
537 }
538 sh_println!()?;
539 }
540
541 if result.success {
542 sh_println!("{}", "Script ran successfully.".green())?;
543 }
544
545 if self.script_config.evm_opts.fork_url.is_none() {
546 sh_println!("Gas used: {}", result.gas_used)?;
547 }
548
549 if result.success && !result.returned.is_empty() {
550 sh_println!("\n== Return ==")?;
551 match func.abi_decode_output(&result.returned) {
552 Ok(decoded) => {
553 for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() {
554 let internal_type =
555 output.internal_type.clone().unwrap_or(InternalType::Other {
556 contract: None,
557 ty: "unknown".to_string(),
558 });
559
560 let label = if output.name.is_empty() {
561 index.to_string()
562 } else {
563 output.name.clone()
564 };
565 sh_println!(
566 "{label}: {internal_type} {value}",
567 label = label.trim_end(),
568 value = format_token(token)
569 )?;
570 }
571 }
572 Err(_) => {
573 sh_err!("{:x?}", (&result.returned))?;
574 }
575 }
576 }
577
578 let console_logs = decode_console_logs(&result.logs);
579 if !console_logs.is_empty() {
580 sh_println!("\n== Logs ==")?;
581 for log in console_logs {
582 sh_println!(" {log}")?;
583 }
584 }
585
586 if !result.success {
587 return Err(eyre::eyre!(
588 "script failed: {}",
589 &self
590 .execution_artifacts
591 .decoder
592 .revert_decoder
593 .decode(&result.returned[..], result.exit_reason)
594 ));
595 }
596
597 Ok(())
598 }
599
600 pub fn run_debugger(self) -> Result<()> {
601 self.create_debugger().try_run_tui()?;
602 Ok(())
603 }
604
605 pub fn dump_debugger(self, path: &Path) -> Result<()> {
606 self.create_debugger().dump_to_file(path)?;
607 Ok(())
608 }
609
610 fn create_debugger(self) -> Debugger {
611 Debugger::builder()
612 .traces(
613 self.execution_result
614 .traces
615 .into_iter()
616 .filter(|(t, _)| t.is_execution())
617 .collect(),
618 )
619 .decoder(&self.execution_artifacts.decoder)
620 .known_contracts(&self.build_data.known_contracts)
621 .sources(self.build_data.sources)
622 .breakpoints(self.execution_result.breakpoints)
623 .layout(self.args.debug_layout.unwrap_or_default())
624 .build()
625 }
626}