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