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_primitives::{
10 Address, Bytes,
11 map::{HashMap, HashSet},
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 ContractsByArtifact,
20 fmt::{format_token, format_token_raw},
21 provider::get_http_provider,
22};
23use foundry_config::NamedChain;
24use foundry_debugger::Debugger;
25use foundry_evm::{
26 decode::decode_console_logs,
27 inspectors::cheatcodes::BroadcastableTransactions,
28 traces::{
29 CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, decode_trace_arena,
30 identifier::{SignaturesIdentifier, TraceIdentifiers},
31 render_trace_arena,
32 },
33};
34use futures::future::join_all;
35use itertools::Itertools;
36use std::path::Path;
37use yansi::Paint;
38
39pub struct LinkedState {
42 pub args: ScriptArgs,
43 pub script_config: ScriptConfig,
44 pub script_wallets: Wallets,
45 pub build_data: LinkedBuildData,
46}
47
48#[derive(Debug)]
50pub struct ExecutionData {
51 pub func: Function,
53 pub calldata: Bytes,
55 pub bytecode: Bytes,
57 pub abi: JsonAbi,
59}
60
61impl LinkedState {
62 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#[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 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 let Some(new_sender) = self.maybe_new_sender(result.transactions.as_ref())? {
118 self.script_config.update_sender(new_sender).await?;
119
120 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 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,
148 self.args.broadcast,
149 )?;
150
151 if setup_result.success {
152 let script_result = runner.script(address, self.execution_data.calldata.clone())?;
153
154 setup_result.success &= script_result.success;
155 setup_result.gas_used = script_result.gas_used;
156 setup_result.logs.extend(script_result.logs);
157 setup_result.traces.extend(script_result.traces);
158 setup_result.labeled_addresses.extend(script_result.labeled_addresses);
159 setup_result.returned = script_result.returned;
160 setup_result.breakpoints = script_result.breakpoints;
161
162 match (&mut setup_result.transactions, script_result.transactions) {
163 (Some(txs), Some(new_txs)) => {
164 txs.extend(new_txs);
165 }
166 (None, Some(new_txs)) => {
167 setup_result.transactions = Some(new_txs);
168 }
169 _ => {}
170 }
171 }
172
173 Ok(setup_result)
174 }
175
176 fn maybe_new_sender(
181 &self,
182 transactions: Option<&BroadcastableTransactions>,
183 ) -> Result<Option<Address>> {
184 let mut new_sender = None;
185
186 if let Some(txs) = transactions {
187 if self.build_data.predeploy_libraries.libraries_count() > 0
189 && self.args.evm.sender.is_none()
190 {
191 for tx in txs {
192 if tx.transaction.to().is_none() {
193 let sender = tx.transaction.from().expect("no sender");
194 if let Some(ns) = new_sender {
195 if sender != ns {
196 sh_warn!(
197 "You have more than one deployer who could predeploy libraries. Using `--sender` instead."
198 )?;
199 return Ok(None);
200 }
201 } else if sender != self.script_config.evm_opts.sender {
202 new_sender = Some(sender);
203 }
204 }
205 }
206 }
207 }
208 Ok(new_sender)
209 }
210}
211
212pub struct RpcData {
214 pub total_rpcs: HashSet<String>,
216 pub missing_rpc: bool,
218}
219
220impl RpcData {
221 fn from_transactions(txs: &BroadcastableTransactions) -> Self {
223 let missing_rpc = txs.iter().any(|tx| tx.rpc.is_none());
224 let total_rpcs =
225 txs.iter().filter_map(|tx| tx.rpc.as_ref().cloned()).collect::<HashSet<_>>();
226
227 Self { total_rpcs, missing_rpc }
228 }
229
230 pub fn is_multi_chain(&self) -> bool {
233 self.total_rpcs.len() > 1 || (self.missing_rpc && !self.total_rpcs.is_empty())
234 }
235
236 async fn check_shanghai_support(&self) -> Result<()> {
238 let chain_ids = self.total_rpcs.iter().map(|rpc| async move {
239 let provider = get_http_provider(rpc);
240 let id = provider.get_chain_id().await.ok()?;
241 NamedChain::try_from(id).ok()
242 });
243
244 let chains = join_all(chain_ids).await;
245 let iter = chains.iter().flatten().map(|c| (c.supports_shanghai(), c));
246 if iter.clone().any(|(s, _)| !s) {
247 let msg = format!(
248 "\
249EIP-3855 is not supported in one or more of the RPCs used.
250Unsupported Chain IDs: {}.
251Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly.
252For more information, please see https://eips.ethereum.org/EIPS/eip-3855",
253 iter.filter(|(supported, _)| !supported)
254 .map(|(_, chain)| *chain as u64)
255 .format(", ")
256 );
257 sh_warn!("{msg}")?;
258 }
259 Ok(())
260 }
261}
262
263pub struct ExecutionArtifacts {
265 pub decoder: CallTraceDecoder,
267 pub returns: HashMap<String, NestedValue>,
269 pub rpc_data: RpcData,
271}
272
273pub struct ExecutedState {
275 pub args: ScriptArgs,
276 pub script_config: ScriptConfig,
277 pub script_wallets: Wallets,
278 pub build_data: LinkedBuildData,
279 pub execution_data: ExecutionData,
280 pub execution_result: ScriptResult,
281}
282
283impl ExecutedState {
284 pub async fn prepare_simulation(self) -> Result<PreSimulationState> {
286 let returns = self.get_returns()?;
287
288 let decoder = self.build_trace_decoder(&self.build_data.known_contracts).await?;
289
290 let mut txs = self.execution_result.transactions.clone().unwrap_or_default();
291
292 for tx in &mut txs {
295 if let Some(req) = tx.transaction.as_unsigned_mut() {
296 req.input =
297 TransactionInput::maybe_both(std::mem::take(&mut req.input).into_input());
298 }
299 }
300 let rpc_data = RpcData::from_transactions(&txs);
301
302 if rpc_data.is_multi_chain() {
303 sh_warn!("Multi chain deployment is still under development. Use with caution.")?;
304 if !self.build_data.libraries.is_empty() {
305 eyre::bail!(
306 "Multi chain deployment does not support library linking at the moment."
307 )
308 }
309 }
310 rpc_data.check_shanghai_support().await?;
311
312 Ok(PreSimulationState {
313 args: self.args,
314 script_config: self.script_config,
315 script_wallets: self.script_wallets,
316 build_data: self.build_data,
317 execution_data: self.execution_data,
318 execution_result: self.execution_result,
319 execution_artifacts: ExecutionArtifacts { decoder, returns, rpc_data },
320 })
321 }
322
323 async fn build_trace_decoder(
325 &self,
326 known_contracts: &ContractsByArtifact,
327 ) -> Result<CallTraceDecoder> {
328 let mut decoder = CallTraceDecoderBuilder::new()
329 .with_labels(self.execution_result.labeled_addresses.clone())
330 .with_verbosity(self.script_config.evm_opts.verbosity)
331 .with_known_contracts(known_contracts)
332 .with_signature_identifier(SignaturesIdentifier::from_config(
333 &self.script_config.config,
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 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) {
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 async fn show_json(&self) -> Result<()> {
390 let mut result = self.execution_result.clone();
391
392 for (_, trace) in &mut result.traces {
393 decode_trace_arena(trace, &self.execution_artifacts.decoder).await;
394 }
395
396 let json_result = JsonResult {
397 logs: decode_console_logs(&result.logs),
398 returns: &self.execution_artifacts.returns,
399 result: &result,
400 };
401 let json = serde_json::to_string(&json_result)?;
402
403 sh_println!("{json}")?;
404
405 if !self.execution_result.success {
406 return Err(eyre::eyre!(
407 "script failed: {}",
408 &self.execution_artifacts.decoder.revert_decoder.decode(&result.returned[..], None)
409 ));
410 }
411
412 Ok(())
413 }
414
415 pub async fn show_traces(&self) -> Result<()> {
416 let verbosity = self.script_config.evm_opts.verbosity;
417 let func = &self.execution_data.func;
418 let result = &self.execution_result;
419 let decoder = &self.execution_artifacts.decoder;
420
421 if !result.success || verbosity > 3 {
422 if result.traces.is_empty() {
423 warn!(verbosity, "no traces");
424 }
425
426 sh_println!("Traces:")?;
427 for (kind, trace) in &result.traces {
428 let should_include = match kind {
429 TraceKind::Setup => verbosity >= 5,
430 TraceKind::Execution => verbosity > 3,
431 _ => false,
432 } || !result.success;
433
434 if should_include {
435 let mut trace = trace.clone();
436 decode_trace_arena(&mut trace, decoder).await;
437 sh_println!("{}", render_trace_arena(&trace))?;
438 }
439 }
440 sh_println!()?;
441 }
442
443 if result.success {
444 sh_println!("{}", "Script ran successfully.".green())?;
445 }
446
447 if self.script_config.evm_opts.fork_url.is_none() {
448 sh_println!("Gas used: {}", result.gas_used)?;
449 }
450
451 if result.success && !result.returned.is_empty() {
452 sh_println!("\n== Return ==")?;
453 match func.abi_decode_output(&result.returned) {
454 Ok(decoded) => {
455 for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() {
456 let internal_type =
457 output.internal_type.clone().unwrap_or(InternalType::Other {
458 contract: None,
459 ty: "unknown".to_string(),
460 });
461
462 let label = if !output.name.is_empty() {
463 output.name.to_string()
464 } else {
465 index.to_string()
466 };
467 sh_println!(
468 "{label}: {internal_type} {value}",
469 label = label.trim_end(),
470 value = format_token(token)
471 )?;
472 }
473 }
474 Err(_) => {
475 sh_err!("{:x?}", (&result.returned))?;
476 }
477 }
478 }
479
480 let console_logs = decode_console_logs(&result.logs);
481 if !console_logs.is_empty() {
482 sh_println!("\n== Logs ==")?;
483 for log in console_logs {
484 sh_println!(" {log}")?;
485 }
486 }
487
488 if !result.success {
489 return Err(eyre::eyre!(
490 "script failed: {}",
491 &self.execution_artifacts.decoder.revert_decoder.decode(&result.returned[..], None)
492 ));
493 }
494
495 Ok(())
496 }
497
498 pub fn run_debugger(self) -> Result<()> {
499 self.create_debugger().try_run_tui()?;
500 Ok(())
501 }
502
503 pub fn dump_debugger(self, path: &Path) -> Result<()> {
504 self.create_debugger().dump_to_file(path)?;
505 Ok(())
506 }
507
508 fn create_debugger(self) -> Debugger {
509 Debugger::builder()
510 .traces(
511 self.execution_result
512 .traces
513 .into_iter()
514 .filter(|(t, _)| t.is_execution())
515 .collect(),
516 )
517 .decoder(&self.execution_artifacts.decoder)
518 .sources(self.build_data.sources)
519 .breakpoints(self.execution_result.breakpoints)
520 .build()
521 }
522}