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 hardforks::TempoHardfork,
30 inspectors::cheatcodes::BroadcastableTransactions,
31 traces::{
32 CallTraceDecoder, CallTraceDecoderBuilder, DebugTraceIdentifier, TraceKind,
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(mut self) -> Result<ExecutedState<FEN>> {
112 let mut runner = self
113 .script_config
114 .get_runner_with_cheatcodes(
115 self.build_data.known_contracts.clone(),
116 self.script_wallets.clone(),
117 self.args.debug,
118 self.build_data.build_data.target.clone(),
119 )
120 .await?;
121 let result = self.execute_with_runner(&mut runner).await?;
122
123 if let Some(new_sender) = self.maybe_new_sender(result.transactions.as_ref())? {
126 self.script_config.update_sender(new_sender).await?;
127
128 let state = CompiledState {
130 args: self.args,
131 script_config: self.script_config,
132 script_wallets: self.script_wallets,
133 browser_wallet: self.browser_wallet,
134 build_data: self.build_data.build_data,
135 };
136
137 return Box::pin(state.link().await?.prepare_execution().await?.execute()).await;
138 }
139
140 Ok(ExecutedState {
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,
146 execution_data: self.execution_data,
147 execution_result: result,
148 })
149 }
150
151 pub async fn execute_with_runner(
153 &self,
154 runner: &mut ScriptRunner<FEN>,
155 ) -> Result<ScriptResult<FEN::Network>> {
156 let (address, mut setup_result) = runner.setup(
157 &self.build_data.predeploy_libraries,
158 self.execution_data.bytecode.clone(),
159 needs_setup(&self.execution_data.abi),
160 &self.script_config,
161 self.args.broadcast,
162 )?;
163
164 if setup_result.success {
165 let script_result = runner.script(address, self.execution_data.calldata.clone())?;
166
167 setup_result.success &= script_result.success;
168 setup_result.gas_used = script_result.gas_used;
169 setup_result.logs.extend(script_result.logs);
170 setup_result.traces.extend(script_result.traces);
171 setup_result.labeled_addresses.extend(script_result.labeled_addresses);
172 setup_result.debug_bytecodes.extend(script_result.debug_bytecodes);
173 setup_result.returned = script_result.returned;
174 setup_result.exit_reason = script_result.exit_reason;
175 setup_result.breakpoints = script_result.breakpoints;
176
177 match (&mut setup_result.transactions, script_result.transactions) {
178 (Some(txs), Some(new_txs)) => {
179 txs.extend(new_txs);
180 }
181 (None, Some(new_txs)) => {
182 setup_result.transactions = Some(new_txs);
183 }
184 _ => {}
185 }
186 }
187
188 Ok(setup_result)
189 }
190
191 fn maybe_new_sender(
196 &self,
197 transactions: Option<&BroadcastableTransactions<FEN::Network>>,
198 ) -> Result<Option<Address>> {
199 let mut new_sender = None;
200
201 if let Some(txs) = transactions {
202 if self.build_data.predeploy_libraries.libraries_count() > 0
204 && self.args.evm.sender.is_none()
205 {
206 for tx in txs {
207 if tx.transaction.to().is_none() {
208 let sender = tx.transaction.from().expect("no sender");
209 if let Some(ns) = new_sender {
210 if sender != ns {
211 sh_warn!(
212 "You have more than one deployer who could predeploy libraries. Using `--sender` instead."
213 )?;
214 return Ok(None);
215 }
216 } else if sender != self.script_config.evm_opts.sender {
217 new_sender = Some(sender);
218 }
219 }
220 }
221 }
222 }
223 Ok(new_sender)
224 }
225}
226
227pub struct RpcData {
229 pub total_rpcs: HashSet<String>,
231 pub missing_rpc: bool,
233}
234
235impl RpcData {
236 fn from_transactions<N: Network>(txs: &BroadcastableTransactions<N>) -> Self {
238 let missing_rpc = txs.iter().any(|tx| tx.rpc.is_none());
239 let total_rpcs = txs.iter().filter_map(|tx| tx.rpc.clone()).collect::<HashSet<_>>();
240
241 Self { total_rpcs, missing_rpc }
242 }
243
244 pub fn is_multi_chain(&self) -> bool {
247 self.total_rpcs.len() > 1 || (self.missing_rpc && !self.total_rpcs.is_empty())
248 }
249
250 async fn check_shanghai_support(&self) -> Result<()> {
252 let chain_ids = self.total_rpcs.iter().map(|rpc| async move {
253 let provider = ProviderBuilder::<AnyNetwork>::new(rpc).build().ok()?;
254 let id = provider.get_chain_id().await.ok()?;
255 NamedChain::try_from(id).ok()
256 });
257
258 let chains = join_all(chain_ids).await;
259 let iter = chains.iter().flatten().map(|c| (c.supports_shanghai(), c));
260 if iter.clone().any(|(s, _)| !s) {
261 let msg = format!(
262 "\
263EIP-3855 is not supported in one or more of the RPCs used.
264Unsupported Chain IDs: {}.
265Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly.
266For more information, please see https://eips.ethereum.org/EIPS/eip-3855",
267 iter.filter(|(supported, _)| !supported)
268 .map(|(_, chain)| *chain as u64)
269 .format(", ")
270 );
271 sh_warn!("{msg}")?;
272 }
273 Ok(())
274 }
275}
276
277pub struct ExecutionArtifacts {
279 pub decoder: CallTraceDecoder,
281 pub returns: HashMap<String, NestedValue>,
283 pub rpc_data: RpcData,
285}
286
287pub struct ExecutedState<FEN: FoundryEvmNetwork> {
289 pub args: ScriptArgs,
290 pub script_config: ScriptConfig<FEN>,
291 pub script_wallets: Wallets,
292 pub browser_wallet: Option<BrowserSigner<FEN::Network>>,
293 pub build_data: LinkedBuildData,
294 pub execution_data: ExecutionData,
295 pub execution_result: ScriptResult<FEN::Network>,
296}
297
298impl<FEN: FoundryEvmNetwork> ExecutedState<FEN> {
299 pub async fn prepare_simulation(self) -> Result<PreSimulationState<FEN>> {
301 let returns = self.get_returns()?;
302
303 let decoder = self.build_trace_decoder(&self.build_data.known_contracts).await?;
304
305 let mut txs: BroadcastableTransactions<FEN::Network> =
306 self.execution_result.transactions.clone().unwrap_or_default();
307
308 for tx in &mut txs {
311 if let Some(req) = tx.transaction.as_unsigned_mut()
312 && let Some(input) = req.input().cloned()
313 {
314 *req = req.clone().with_input_kind(input, TransactionInputKind::Both);
315 }
316 }
317 let rpc_data = RpcData::from_transactions(&txs);
318
319 if rpc_data.is_multi_chain() {
320 sh_warn!("Multi chain deployment is still under development. Use with caution.")?;
321 if !self.build_data.libraries.is_empty() {
322 eyre::bail!(
323 "Multi chain deployment does not support library linking at the moment."
324 );
325 }
326 }
327 rpc_data.check_shanghai_support().await?;
328
329 Ok(PreSimulationState {
330 args: self.args,
331 script_config: self.script_config,
332 script_wallets: self.script_wallets,
333 browser_wallet: self.browser_wallet,
334 build_data: self.build_data,
335 execution_data: self.execution_data,
336 execution_result: self.execution_result,
337 execution_artifacts: ExecutionArtifacts { decoder, returns, rpc_data },
338 })
339 }
340
341 async fn build_trace_decoder(
343 &self,
344 known_contracts: &ContractsByArtifact,
345 ) -> Result<CallTraceDecoder> {
346 let chain_id = self.script_config.evm_opts.get_remote_chain_id().await;
347 let is_tempo = self.script_config.evm_opts.networks.is_tempo()
348 || chain_id.as_ref().is_some_and(|chain| chain.is_tempo());
349 let mut tracing = self.script_config.config.tracing.clone();
350 tracing.labels.extend(self.execution_result.labeled_addresses.clone());
351
352 let mut decoder = CallTraceDecoderBuilder::new()
353 .with_tracing_config(&tracing)
354 .with_known_contracts(known_contracts)
355 .with_signature_identifier(SignaturesIdentifier::from_config(
356 &self.script_config.config,
357 )?)
358 .with_chain_id(chain_id.map(|c| c.id()))
359 .with_tempo_hardfork(
360 is_tempo.then(|| self.script_config.config.evm_spec_id::<TempoHardfork>()),
361 )
362 .build();
363
364 if tracing.decode_internal {
365 decoder.debug_identifier =
366 Some(DebugTraceIdentifier::new(self.build_data.sources.clone()));
367 }
368
369 let use_debug_bytecodes =
370 self.args.debug && !self.execution_result.debug_bytecodes.is_empty();
371 let mut identifier = if use_debug_bytecodes {
372 TraceIdentifiers::new()
373 .with_local_and_bytecodes(known_contracts, &self.execution_result.debug_bytecodes)
374 } else {
375 TraceIdentifiers::new().with_local(known_contracts)
376 }
377 .with_external(&self.script_config.config, chain_id)?;
378
379 for (_, trace) in &self.execution_result.traces {
380 decoder.identify(trace, &mut identifier);
381 }
382
383 Ok(decoder)
384 }
385
386 fn get_returns(&self) -> Result<HashMap<String, NestedValue>> {
388 let mut returns = HashMap::default();
389 let returned = &self.execution_result.returned;
390 let func = &self.execution_data.func;
391
392 match func.abi_decode_output(returned) {
393 Ok(decoded) => {
394 for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() {
395 let internal_type =
396 output.internal_type.clone().unwrap_or(InternalType::Other {
397 contract: None,
398 ty: "unknown".to_string(),
399 });
400
401 let label = if output.name.is_empty() {
402 index.to_string()
403 } else {
404 output.name.clone()
405 };
406
407 returns.insert(
408 label,
409 NestedValue {
410 internal_type: internal_type.to_string(),
411 value: format_token_raw(token),
412 },
413 );
414 }
415 }
416 Err(_) => {
417 sh_err!("Failed to decode return value: {:x?}", returned)?;
418 }
419 }
420
421 Ok(returns)
422 }
423}
424
425impl<FEN: FoundryEvmNetwork> PreSimulationState<FEN> {
426 pub async fn show_json(&self) -> Result<()> {
427 let mut result = self.execution_result.clone();
428 let trace_depth = self.script_config.config.tracing.trace_depth;
429
430 for (_, trace) in &mut result.traces {
431 decode_trace_arena(trace, &self.execution_artifacts.decoder).await;
432 if let Some(trace_depth) = trace_depth {
433 *trace = trace_arena_at_depth(trace, trace_depth);
434 }
435 }
436
437 let json_result = JsonResult {
438 logs: decode_console_logs(&result.logs),
439 returns: &self.execution_artifacts.returns,
440 result: &result,
441 };
442 let json = serde_json::to_string(&json_result)?;
443
444 sh_println!("{json}")?;
445
446 if !self.execution_result.success {
447 return Err(eyre::eyre!(
448 "script failed: {}",
449 &self
450 .execution_artifacts
451 .decoder
452 .revert_decoder
453 .decode(&result.returned[..], result.exit_reason)
454 ));
455 }
456
457 Ok(())
458 }
459
460 pub async fn show_traces(&self) -> Result<()> {
461 let tracing = &self.script_config.config.tracing;
462 let verbosity = tracing.verbosity;
463 let func = &self.execution_data.func;
464 let result = &self.execution_result;
465 let decoder = &self.execution_artifacts.decoder;
466
467 if !result.success || verbosity > 3 {
468 if result.traces.is_empty() {
469 warn!(verbosity, "no traces");
470 }
471
472 sh_println!("Traces:")?;
473 for (kind, trace) in &result.traces {
474 let should_include = match kind {
475 TraceKind::Setup => verbosity >= 5,
476 TraceKind::Execution => verbosity > 3,
477 _ => false,
478 } || !result.success;
479
480 if should_include {
481 let mut trace = trace.clone();
482 decode_trace_arena(&mut trace, decoder).await;
483 if let Some(trace_depth) = tracing.trace_depth {
484 prune_trace_depth(&mut trace, trace_depth);
485 }
486 sh_println!("{}", render_trace_arena_inner(&trace, false, verbosity > 4))?;
487 }
488 }
489 sh_println!()?;
490 }
491
492 if result.success {
493 sh_println!("{}", "Script ran successfully.".green())?;
494 }
495
496 if self.script_config.evm_opts.fork_url.is_none() {
497 sh_println!("Gas used: {}", result.gas_used)?;
498 }
499
500 if result.success && !result.returned.is_empty() {
501 sh_println!("\n== Return ==")?;
502 match func.abi_decode_output(&result.returned) {
503 Ok(decoded) => {
504 for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() {
505 let internal_type =
506 output.internal_type.clone().unwrap_or(InternalType::Other {
507 contract: None,
508 ty: "unknown".to_string(),
509 });
510
511 let label = if output.name.is_empty() {
512 index.to_string()
513 } else {
514 output.name.clone()
515 };
516 sh_println!(
517 "{label}: {internal_type} {value}",
518 label = label.trim_end(),
519 value = format_token(token)
520 )?;
521 }
522 }
523 Err(_) => {
524 sh_err!("{:x?}", (&result.returned))?;
525 }
526 }
527 }
528
529 let console_logs = decode_console_logs(&result.logs);
530 if !console_logs.is_empty() {
531 sh_println!("\n== Logs ==")?;
532 for log in console_logs {
533 sh_println!(" {log}")?;
534 }
535 }
536
537 if !result.success {
538 return Err(eyre::eyre!(
539 "script failed: {}",
540 &self
541 .execution_artifacts
542 .decoder
543 .revert_decoder
544 .decode(&result.returned[..], result.exit_reason)
545 ));
546 }
547
548 Ok(())
549 }
550
551 pub fn run_debugger(self) -> Result<()> {
552 self.create_debugger().try_run_tui()?;
553 Ok(())
554 }
555
556 pub fn dump_debugger(self, path: &Path) -> Result<()> {
557 self.create_debugger().dump_to_file(path)?;
558 Ok(())
559 }
560
561 fn create_debugger(self) -> Debugger {
562 Debugger::builder()
563 .traces(
564 self.execution_result
565 .traces
566 .into_iter()
567 .filter(|(t, _)| t.is_execution())
568 .collect(),
569 )
570 .decoder(&self.execution_artifacts.decoder)
571 .sources(self.build_data.sources)
572 .breakpoints(self.execution_result.breakpoints)
573 .layout(self.args.debug_layout.unwrap_or_default())
574 .build()
575 }
576}