1use super::{
2 multi_sequence::MultiChainSequence, providers::ProvidersManager, runner::ScriptRunner,
3 sequence::ScriptSequenceKind, transaction::ScriptTransactionBuilder,
4};
5use crate::{
6 ScriptArgs, ScriptConfig, ScriptResult,
7 broadcast::{BundledState, estimate_gas},
8 build::LinkedBuildData,
9 execute::{ExecutionArtifacts, ExecutionData},
10 sequence::get_commit_hash,
11};
12use alloy_chains::NamedChain;
13use alloy_evm::revm::context::Block;
14use alloy_network::TransactionBuilder;
15use alloy_primitives::{Address, U256, map::HashMap, utils::format_units};
16use alloy_provider::Provider;
17use dialoguer::Confirm;
18use eyre::{Context, Result};
19use forge_script_sequence::{ScriptSequence, TransactionWithMetadata};
20use foundry_cheatcodes::Wallets;
21use foundry_cli::utils::{has_different_gas_calc, now};
22use foundry_common::{ContractData, provider::fee::resolve_broadcast_eip1559_fees, shell};
23use foundry_evm::{
24 core::{FoundryBlock, evm::FoundryEvmNetwork},
25 traces::{decode_trace_arena, prune_trace_depth, render_trace_arena_inner},
26};
27use foundry_wallets::wallet_browser::signer::BrowserSigner;
28use futures::future::{join_all, try_join_all};
29use parking_lot::RwLock;
30use std::{
31 collections::{BTreeMap, VecDeque},
32 mem,
33 sync::Arc,
34};
35
36pub struct PreSimulationState<FEN: FoundryEvmNetwork> {
42 pub args: ScriptArgs,
43 pub script_config: ScriptConfig<FEN>,
44 pub script_wallets: Wallets,
45 pub browser_wallet: Option<BrowserSigner<FEN::Network>>,
46 pub build_data: LinkedBuildData,
47 pub execution_data: ExecutionData,
48 pub execution_result: ScriptResult<FEN::Network>,
49 pub execution_artifacts: ExecutionArtifacts,
50}
51
52impl<FEN: FoundryEvmNetwork> PreSimulationState<FEN> {
53 pub async fn fill_metadata(self) -> Result<FilledTransactionsState<FEN>> {
59 let address_to_abi = self.build_address_to_abi_map();
60
61 let mut transactions = self
62 .execution_result
63 .transactions
64 .clone()
65 .unwrap_or_default()
66 .into_iter()
67 .map(|tx| {
68 let rpc = tx.rpc.expect("missing broadcastable tx rpc url");
69 let sender = tx.transaction.from().expect("all transactions should have a sender");
70 let nonce = tx.transaction.nonce().expect("all transactions should have a nonce");
71 let to = tx.transaction.to();
72
73 let mut builder = ScriptTransactionBuilder::new(tx.transaction, rpc);
74
75 if to.is_some() {
76 builder.set_call(
77 &address_to_abi,
78 &self.execution_artifacts.decoder,
79 self.script_config.evm_opts.create2_deployer,
80 )?;
81 } else {
82 builder.set_create(false, sender.create(nonce), &address_to_abi)?;
83 }
84
85 Ok(builder.build())
86 })
87 .collect::<Result<VecDeque<_>>>()?;
88
89 if self.args.skip_simulation {
90 sh_println!("\nSKIPPING ON CHAIN SIMULATION.")?;
91 } else {
92 transactions = self.simulate_and_fill(transactions).await?;
93 }
94
95 Ok(FilledTransactionsState {
96 args: self.args,
97 script_config: self.script_config,
98 script_wallets: self.script_wallets,
99 browser_wallet: self.browser_wallet,
100 build_data: self.build_data,
101 execution_artifacts: self.execution_artifacts,
102 transactions,
103 })
104 }
105
106 pub async fn simulate_and_fill(
111 &self,
112 transactions: VecDeque<TransactionWithMetadata<FEN::Network>>,
113 ) -> Result<VecDeque<TransactionWithMetadata<FEN::Network>>> {
114 trace!(target: "script", "executing onchain simulation");
115
116 let runners = Arc::new(
117 self.build_runners()
118 .await?
119 .into_iter()
120 .map(|(rpc, runner)| (rpc, Arc::new(RwLock::new(runner))))
121 .collect::<HashMap<_, _>>(),
122 );
123
124 let mut final_txs = VecDeque::new();
125
126 let futs = transactions
128 .into_iter()
129 .map(|mut transaction| async {
130 let mut runner = runners.get(&transaction.rpc).expect("invalid rpc url").write();
131 let tx = transaction.tx_mut();
132
133 let to = tx.to();
134 let result = runner
135 .simulate(
136 tx.from()
137 .expect("transaction doesn't have a `from` address at execution time"),
138 to,
139 tx.input().cloned(),
140 tx.value(),
141 tx.authorization_list(),
142 )
143 .wrap_err("Internal EVM error during simulation")?;
144
145 if !result.success {
146 return Ok((None, false, result.traces));
147 }
148
149 if self.args.slow {
151 let block_number = runner.executor.evm_env().block_env.number() + U256::from(1);
152 runner.executor.evm_env_mut().block_env.set_number(block_number);
153 }
154
155 let is_noop_tx = if let Some(to) = to {
156 runner.executor.is_empty_code(to)? && tx.value().unwrap_or_default().is_zero()
157 } else {
158 false
159 };
160
161 let transaction = ScriptTransactionBuilder::from(transaction)
162 .with_execution_result(
163 &result,
164 self.args.gas_estimate_multiplier,
165 &self.build_data,
166 )
167 .build();
168
169 eyre::Ok((Some(transaction), is_noop_tx, result.traces))
170 })
171 .collect::<Vec<_>>();
172
173 let tracing = &self.script_config.config.tracing;
174 if !shell::is_json() && tracing.verbosity > 3 {
175 sh_println!("==========================")?;
176 sh_println!("Simulated On-chain Traces:\n")?;
177 }
178
179 let mut abort = false;
180 for res in join_all(futs).await {
181 let (tx, is_noop_tx, mut traces) = res?;
182
183 if !shell::is_json() && (tx.is_none() || tracing.verbosity > 3) {
185 for (_, trace) in &mut traces {
186 decode_trace_arena(trace, &self.execution_artifacts.decoder).await;
187 if let Some(trace_depth) = tracing.trace_depth {
188 prune_trace_depth(trace, trace_depth);
189 }
190 sh_println!(
191 "{}",
192 render_trace_arena_inner(trace, false, tracing.verbosity > 4)
193 )?;
194 }
195 }
196
197 if let Some(tx) = tx {
198 if is_noop_tx {
199 let to = tx.contract_address.unwrap();
200 sh_warn!(
201 "Script contains a transaction to {to} which does not contain any code."
202 )?;
203
204 if self.args.should_broadcast()
206 && !self.args.non_interactive
207 && !Confirm::new()
208 .with_prompt("Do you wish to continue?".to_string())
209 .interact()?
210 {
211 eyre::bail!("User canceled the script.");
212 }
213 }
214
215 final_txs.push_back(tx);
216 } else {
217 abort = true;
218 }
219 }
220
221 if abort {
222 eyre::bail!("Simulated execution failed.");
223 }
224
225 Ok(final_txs)
226 }
227
228 fn build_address_to_abi_map(&self) -> BTreeMap<Address, &ContractData> {
230 self.execution_artifacts
231 .decoder
232 .contracts
233 .iter()
234 .filter_map(move |(addr, contract_id)| {
235 if let Ok(Some((_, data))) =
236 self.build_data.known_contracts.find_by_name_or_identifier(contract_id)
237 {
238 return Some((*addr, data));
239 }
240 None
241 })
242 .collect()
243 }
244
245 async fn build_runners(&self) -> Result<Vec<(String, ScriptRunner<FEN>)>> {
247 let rpcs = self.execution_artifacts.rpc_data.total_rpcs.clone();
248
249 if !shell::is_json() {
250 let n = rpcs.len();
251 let s = if n == 1 { "" } else { "s" };
252 sh_println!("\n## Setting up {n} EVM{s}.")?;
253 }
254
255 let futs = rpcs.into_iter().map(|rpc| async move {
256 let mut script_config = self.script_config.clone();
257 script_config.evm_opts.fork_url = Some(rpc.clone());
258 let runner = script_config.get_runner().await?;
259 Ok((rpc, runner))
260 });
261 try_join_all(futs).await
262 }
263}
264
265pub struct FilledTransactionsState<FEN: FoundryEvmNetwork> {
269 pub args: ScriptArgs,
270 pub script_config: ScriptConfig<FEN>,
271 pub script_wallets: Wallets,
272 pub browser_wallet: Option<BrowserSigner<FEN::Network>>,
273 pub build_data: LinkedBuildData,
274 pub execution_artifacts: ExecutionArtifacts,
275 pub transactions: VecDeque<TransactionWithMetadata<FEN::Network>>,
276}
277
278impl<FEN: FoundryEvmNetwork> FilledTransactionsState<FEN> {
279 pub async fn bundle(mut self) -> Result<BundledState<FEN>> {
285 let is_multi_deployment = self.execution_artifacts.rpc_data.total_rpcs.len() > 1;
286
287 if is_multi_deployment && !self.build_data.libraries.is_empty() {
288 eyre::bail!("Multi-chain deployment is not supported with libraries.");
289 }
290
291 let mut total_gas_per_rpc: HashMap<String, u128> = HashMap::default();
292
293 let mut new_sequence = VecDeque::new();
295 let mut manager = ProvidersManager::<FEN::Network>::default();
296 let mut sequences = vec![];
297
298 let mut txes_iter = mem::take(&mut self.transactions).into_iter().peekable();
301
302 while let Some(mut tx) = txes_iter.next() {
303 let tx_rpc = tx.rpc.clone();
304 let provider_info = manager
305 .get_or_init_provider(
306 &tx.rpc,
307 self.args.legacy,
308 self.script_config.config.eip1559_fee_estimate,
309 )
310 .await?;
311
312 if let Some(tx) = tx.tx_mut().as_unsigned_mut() {
313 tx.set_chain_id(provider_info.chain);
315 }
316
317 if !self.args.skip_simulation {
318 let tx = tx.tx_mut();
319
320 if has_different_gas_calc(provider_info.chain) {
321 if let Some(tx) = tx.as_unsigned_mut() {
323 trace!("estimating with different gas calculation");
324 let gas = tx.gas_limit().expect("gas is set by simulation.");
325
326 if let Err(err) = estimate_gas(
338 tx,
339 &provider_info.provider,
340 self.args.gas_estimate_multiplier,
341 )
342 .await
343 {
344 trace!("gas estimation failed: {err}");
345
346 tx.set_gas_limit(gas);
348 }
349 }
350 }
351
352 let total_gas = total_gas_per_rpc.entry(tx_rpc.clone()).or_insert(0);
353 *total_gas += tx.gas().expect("gas is set");
354 }
355
356 new_sequence.push_back(tx);
357 if let Some(next_tx) = txes_iter.peek()
360 && next_tx.rpc == tx_rpc
361 {
362 continue;
363 }
364
365 let sequence =
366 self.create_sequence(is_multi_deployment, provider_info.chain, new_sequence)?;
367
368 sequences.push(sequence);
369
370 new_sequence = VecDeque::new();
371 }
372
373 if !self.args.skip_simulation {
374 for (rpc, total_gas) in total_gas_per_rpc {
376 let provider_info = manager.get(&rpc).expect("provider is set.");
377
378 let token_symbol = NamedChain::try_from(provider_info.chain)
380 .unwrap_or_default()
381 .native_currency_symbol()
382 .unwrap_or("ETH");
383
384 let resolved_eip1559_fees = if self.args.with_gas_price.is_none() {
391 if let Some(fees) = provider_info.eip1559_fees().copied() {
392 let browser_suggested_tip =
395 if !self.args.batch && self.browser_wallet.is_some() {
396 provider_info.provider.get_max_priority_fee_per_gas().await.ok()
397 } else {
398 None
399 };
400 Some(resolve_broadcast_eip1559_fees(
401 fees,
402 None,
403 self.args.priority_gas_price.map(|p| p.to()),
404 browser_suggested_tip,
405 )?)
406 } else {
407 None
408 }
409 } else {
410 None
411 };
412
413 let per_gas = if let Some(gas_price) = self.args.with_gas_price {
417 gas_price.to()
418 } else if let Some(fees) = &resolved_eip1559_fees {
419 fees.max_fee_per_gas
420 } else {
421 provider_info.gas_price()?
422 };
423
424 let fmt_gwei = |wei: u128| {
426 let raw = format_units(wei, 9)
427 .unwrap_or_else(|_| "[Could not calculate]".to_string());
428 raw.trim_end_matches('0').trim_end_matches('.').to_string()
429 };
430
431 let estimated_gas_price = fmt_gwei(per_gas);
432
433 let fee_breakdown = resolved_eip1559_fees.as_ref().map(|fees| {
435 (fmt_gwei(fees.base_fee_per_gas), fmt_gwei(fees.max_priority_fee_per_gas))
436 });
437
438 let estimated_amount_raw = format_units(total_gas.saturating_mul(per_gas), 18)
439 .unwrap_or_else(|_| "[Could not calculate]".to_string());
440 let estimated_amount = estimated_amount_raw.trim_end_matches('0');
441
442 if shell::is_json() {
443 let mut json = serde_json::json!({
444 "chain": provider_info.chain,
445 "estimated_gas_price": estimated_gas_price,
446 "estimated_total_gas_used": total_gas,
447 "estimated_amount_required": estimated_amount,
448 "token_symbol": token_symbol,
449 });
450 if let Some((base_fee, priority_fee)) = &fee_breakdown {
451 json["estimated_max_fee_per_gas"] =
452 serde_json::Value::from(estimated_gas_price.clone());
453 json["estimated_base_fee_per_gas"] =
454 serde_json::Value::from(base_fee.clone());
455 json["estimated_max_priority_fee_per_gas"] =
456 serde_json::Value::from(priority_fee.clone());
457 }
458 sh_println!("{}", json)?;
459 } else {
460 sh_println!("\n==========================")?;
461 sh_println!("\nChain {}", provider_info.chain)?;
462
463 if let Some((base_fee, priority_fee)) = &fee_breakdown {
464 sh_println!("\nEstimated max fee per gas: {estimated_gas_price} gwei")?;
465 sh_println!("Estimated base fee per gas: {base_fee} gwei")?;
466 sh_println!("Estimated max priority fee per gas: {priority_fee} gwei")?;
467 } else {
468 sh_println!("\nEstimated gas price: {estimated_gas_price} gwei")?;
469 }
470 sh_println!("\nEstimated total gas used for script: {total_gas}")?;
471 sh_println!("\nEstimated amount required: {estimated_amount} {token_symbol}")?;
472 sh_println!("\n==========================")?;
473 }
474 }
475 }
476
477 let sequence = if sequences.len() == 1 {
478 ScriptSequenceKind::Single(sequences.pop().expect("empty sequences"))
479 } else {
480 ScriptSequenceKind::Multi(MultiChainSequence::new(
481 sequences,
482 &self.args.sig,
483 &self.build_data.build_data.target,
484 &self.script_config.config,
485 !self.args.broadcast,
486 )?)
487 };
488
489 Ok(BundledState {
490 args: self.args,
491 script_config: self.script_config,
492 script_wallets: self.script_wallets,
493 browser_wallet: self.browser_wallet,
494 build_data: self.build_data,
495 sequence,
496 })
497 }
498
499 fn create_sequence(
501 &self,
502 multi: bool,
503 chain: u64,
504 transactions: VecDeque<TransactionWithMetadata<FEN::Network>>,
505 ) -> Result<ScriptSequence<FEN::Network>> {
506 let paths = if multi {
509 None
510 } else {
511 Some(ScriptSequence::<FEN::Network>::get_paths(
512 &self.script_config.config,
513 &self.args.sig,
514 &self.build_data.build_data.target,
515 chain,
516 !self.args.broadcast,
517 )?)
518 };
519
520 let commit = get_commit_hash(&self.script_config.config.root);
521
522 let libraries = self
523 .build_data
524 .libraries
525 .libs
526 .iter()
527 .flat_map(|(file, libs)| {
528 libs.iter()
529 .map(|(name, address)| format!("{}:{name}:{address}", file.to_string_lossy()))
530 })
531 .collect();
532
533 let sequence = ScriptSequence {
534 transactions,
535 returns: self.execution_artifacts.returns.clone(),
536 receipts: vec![],
537 pending: vec![],
538 paths,
539 timestamp: now().as_millis(),
540 libraries,
541 chain,
542 commit,
543 };
544 Ok(sequence)
545 }
546}