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