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