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