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_network::TransactionBuilder;
14use alloy_primitives::{Address, TxKind, U256, map::HashMap, utils::format_units};
15use dialoguer::Confirm;
16use eyre::{Context, Result};
17use forge_script_sequence::{ScriptSequence, TransactionWithMetadata};
18use foundry_cheatcodes::Wallets;
19use foundry_cli::utils::{has_different_gas_calc, now};
20use foundry_common::{ContractData, shell};
21use foundry_evm::traces::{decode_trace_arena, render_trace_arena};
22use futures::future::{join_all, try_join_all};
23use parking_lot::RwLock;
24use std::{
25 collections::{BTreeMap, VecDeque},
26 mem,
27 sync::Arc,
28};
29
30pub struct PreSimulationState {
36 pub args: ScriptArgs,
37 pub script_config: ScriptConfig,
38 pub script_wallets: Wallets,
39 pub build_data: LinkedBuildData,
40 pub execution_data: ExecutionData,
41 pub execution_result: ScriptResult,
42 pub execution_artifacts: ExecutionArtifacts,
43}
44
45impl PreSimulationState {
46 pub async fn fill_metadata(self) -> Result<FilledTransactionsState> {
52 let address_to_abi = self.build_address_to_abi_map();
53
54 let mut transactions = self
55 .execution_result
56 .transactions
57 .clone()
58 .unwrap_or_default()
59 .into_iter()
60 .map(|tx| {
61 let rpc = tx.rpc.expect("missing broadcastable tx rpc url");
62 let sender = tx.transaction.from().expect("all transactions should have a sender");
63 let nonce = tx.transaction.nonce().expect("all transactions should have a sender");
64 let to = tx.transaction.to();
65
66 let mut builder = ScriptTransactionBuilder::new(tx.transaction, rpc);
67
68 if let Some(TxKind::Call(_)) = to {
69 builder.set_call(
70 &address_to_abi,
71 &self.execution_artifacts.decoder,
72 self.script_config.evm_opts.create2_deployer,
73 )?;
74 } else {
75 builder.set_create(false, sender.create(nonce), &address_to_abi)?;
76 }
77
78 Ok(builder.build())
79 })
80 .collect::<Result<VecDeque<_>>>()?;
81
82 if self.args.skip_simulation {
83 sh_println!("\nSKIPPING ON CHAIN SIMULATION.")?;
84 } else {
85 transactions = self.simulate_and_fill(transactions).await?;
86 }
87
88 Ok(FilledTransactionsState {
89 args: self.args,
90 script_config: self.script_config,
91 script_wallets: self.script_wallets,
92 build_data: self.build_data,
93 execution_artifacts: self.execution_artifacts,
94 transactions,
95 })
96 }
97
98 pub async fn simulate_and_fill(
103 &self,
104 transactions: VecDeque<TransactionWithMetadata>,
105 ) -> Result<VecDeque<TransactionWithMetadata>> {
106 trace!(target: "script", "executing onchain simulation");
107
108 let runners = Arc::new(
109 self.build_runners()
110 .await?
111 .into_iter()
112 .map(|(rpc, runner)| (rpc, Arc::new(RwLock::new(runner))))
113 .collect::<HashMap<_, _>>(),
114 );
115
116 let mut final_txs = VecDeque::new();
117
118 let futs = transactions
120 .into_iter()
121 .map(|mut transaction| async {
122 let mut runner = runners.get(&transaction.rpc).expect("invalid rpc url").write();
123 let tx = transaction.tx_mut();
124
125 let to = if let Some(TxKind::Call(to)) = tx.to() { Some(to) } else { None };
126 let result = runner
127 .simulate(
128 tx.from()
129 .expect("transaction doesn't have a `from` address at execution time"),
130 to,
131 tx.input().cloned(),
132 tx.value(),
133 tx.authorization_list(),
134 )
135 .wrap_err("Internal EVM error during simulation")?;
136
137 if !result.success {
138 return Ok((None, false, result.traces));
139 }
140
141 if self.args.slow {
143 runner.executor.env_mut().evm_env.block_env.number += U256::from(1);
144 }
145
146 let is_noop_tx = if let Some(to) = to {
147 runner.executor.is_empty_code(to)? && tx.value().unwrap_or_default().is_zero()
148 } else {
149 false
150 };
151
152 let transaction = ScriptTransactionBuilder::from(transaction)
153 .with_execution_result(
154 &result,
155 self.args.gas_estimate_multiplier,
156 &self.build_data,
157 )
158 .build();
159
160 eyre::Ok((Some(transaction), is_noop_tx, result.traces))
161 })
162 .collect::<Vec<_>>();
163
164 if !shell::is_json() && self.script_config.evm_opts.verbosity > 3 {
165 sh_println!("==========================")?;
166 sh_println!("Simulated On-chain Traces:\n")?;
167 }
168
169 let mut abort = false;
170 for res in join_all(futs).await {
171 let (tx, is_noop_tx, mut traces) = res?;
172
173 if tx.is_none() || self.script_config.evm_opts.verbosity > 3 {
175 for (_, trace) in &mut traces {
176 decode_trace_arena(trace, &self.execution_artifacts.decoder).await;
177 sh_println!("{}", render_trace_arena(trace))?;
178 }
179 }
180
181 if let Some(tx) = tx {
182 if is_noop_tx {
183 let to = tx.contract_address.unwrap();
184 sh_warn!(
185 "Script contains a transaction to {to} which does not contain any code."
186 )?;
187
188 if self.args.should_broadcast()
190 && !self.args.non_interactive
191 && !Confirm::new()
192 .with_prompt("Do you wish to continue?".to_string())
193 .interact()?
194 {
195 eyre::bail!("User canceled the script.");
196 }
197 }
198
199 final_txs.push_back(tx);
200 } else {
201 abort = true;
202 }
203 }
204
205 if abort {
206 eyre::bail!("Simulated execution failed.")
207 }
208
209 Ok(final_txs)
210 }
211
212 fn build_address_to_abi_map(&self) -> BTreeMap<Address, &ContractData> {
214 self.execution_artifacts
215 .decoder
216 .contracts
217 .iter()
218 .filter_map(move |(addr, contract_id)| {
219 if let Ok(Some((_, data))) =
220 self.build_data.known_contracts.find_by_name_or_identifier(contract_id)
221 {
222 return Some((*addr, data));
223 }
224 None
225 })
226 .collect()
227 }
228
229 async fn build_runners(&self) -> Result<Vec<(String, ScriptRunner)>> {
231 let rpcs = self.execution_artifacts.rpc_data.total_rpcs.clone();
232
233 if !shell::is_json() {
234 let n = rpcs.len();
235 let s = if n != 1 { "s" } else { "" };
236 sh_println!("\n## Setting up {n} EVM{s}.")?;
237 }
238
239 let futs = rpcs.into_iter().map(|rpc| async move {
240 let mut script_config = self.script_config.clone();
241 script_config.evm_opts.fork_url = Some(rpc.clone());
242 let runner = script_config.get_runner().await?;
243 Ok((rpc, runner))
244 });
245 try_join_all(futs).await
246 }
247}
248
249pub struct FilledTransactionsState {
253 pub args: ScriptArgs,
254 pub script_config: ScriptConfig,
255 pub script_wallets: Wallets,
256 pub build_data: LinkedBuildData,
257 pub execution_artifacts: ExecutionArtifacts,
258 pub transactions: VecDeque<TransactionWithMetadata>,
259}
260
261impl FilledTransactionsState {
262 pub async fn bundle(mut self) -> Result<BundledState> {
268 let is_multi_deployment = self.execution_artifacts.rpc_data.total_rpcs.len() > 1;
269
270 if is_multi_deployment && !self.build_data.libraries.is_empty() {
271 eyre::bail!("Multi-chain deployment is not supported with libraries.");
272 }
273
274 let mut total_gas_per_rpc: HashMap<String, u128> = HashMap::default();
275
276 let mut new_sequence = VecDeque::new();
278 let mut manager = ProvidersManager::default();
279 let mut sequences = vec![];
280
281 let mut txes_iter = mem::take(&mut self.transactions).into_iter().peekable();
284
285 while let Some(mut tx) = txes_iter.next() {
286 let tx_rpc = tx.rpc.to_owned();
287 let provider_info = manager.get_or_init_provider(&tx.rpc, self.args.legacy).await?;
288
289 if let Some(tx) = tx.tx_mut().as_unsigned_mut() {
290 tx.set_chain_id(provider_info.chain);
292 }
293
294 if !self.args.skip_simulation {
295 let tx = tx.tx_mut();
296
297 if has_different_gas_calc(provider_info.chain) {
298 if let Some(tx) = tx.as_unsigned_mut() {
300 trace!("estimating with different gas calculation");
301 let gas = tx.gas.expect("gas is set by simulation.");
302
303 if let Err(err) = estimate_gas(
315 tx,
316 &provider_info.provider,
317 self.args.gas_estimate_multiplier,
318 )
319 .await
320 {
321 trace!("gas estimation failed: {err}");
322
323 tx.set_gas_limit(gas);
325 }
326 }
327 }
328
329 let total_gas = total_gas_per_rpc.entry(tx_rpc.clone()).or_insert(0);
330 *total_gas += tx.gas().expect("gas is set");
331 }
332
333 new_sequence.push_back(tx);
334 if let Some(next_tx) = txes_iter.peek()
337 && next_tx.rpc == tx_rpc
338 {
339 continue;
340 }
341
342 let sequence =
343 self.create_sequence(is_multi_deployment, provider_info.chain, new_sequence)?;
344
345 sequences.push(sequence);
346
347 new_sequence = VecDeque::new();
348 }
349
350 if !self.args.skip_simulation {
351 for (rpc, total_gas) in total_gas_per_rpc {
353 let provider_info = manager.get(&rpc).expect("provider is set.");
354
355 let token_symbol = NamedChain::try_from(provider_info.chain)
357 .unwrap_or_default()
358 .native_currency_symbol()
359 .unwrap_or("ETH");
360
361 let per_gas = if let Some(gas_price) = self.args.with_gas_price {
364 gas_price.to()
365 } else {
366 provider_info.gas_price()?
367 };
368
369 let estimated_gas_price_raw = format_units(per_gas, 9)
370 .unwrap_or_else(|_| "[Could not calculate]".to_string());
371 let estimated_gas_price =
372 estimated_gas_price_raw.trim_end_matches('0').trim_end_matches('.');
373
374 let estimated_amount_raw = format_units(total_gas.saturating_mul(per_gas), 18)
375 .unwrap_or_else(|_| "[Could not calculate]".to_string());
376 let estimated_amount = estimated_amount_raw.trim_end_matches('0');
377
378 if !shell::is_json() {
379 sh_println!("\n==========================")?;
380 sh_println!("\nChain {}", provider_info.chain)?;
381
382 sh_println!("\nEstimated gas price: {} gwei", estimated_gas_price)?;
383 sh_println!("\nEstimated total gas used for script: {total_gas}")?;
384 sh_println!("\nEstimated amount required: {estimated_amount} {token_symbol}")?;
385 sh_println!("\n==========================")?;
386 } else {
387 sh_println!(
388 "{}",
389 serde_json::json!({
390 "chain": provider_info.chain,
391 "estimated_gas_price": estimated_gas_price,
392 "estimated_total_gas_used": total_gas,
393 "estimated_amount_required": estimated_amount,
394 "token_symbol": token_symbol,
395 })
396 )?;
397 }
398 }
399 }
400
401 let sequence = if sequences.len() == 1 {
402 ScriptSequenceKind::Single(sequences.pop().expect("empty sequences"))
403 } else {
404 ScriptSequenceKind::Multi(MultiChainSequence::new(
405 sequences,
406 &self.args.sig,
407 &self.build_data.build_data.target,
408 &self.script_config.config,
409 !self.args.broadcast,
410 )?)
411 };
412
413 Ok(BundledState {
414 args: self.args,
415 script_config: self.script_config,
416 script_wallets: self.script_wallets,
417 build_data: self.build_data,
418 sequence,
419 })
420 }
421
422 fn create_sequence(
424 &self,
425 multi: bool,
426 chain: u64,
427 transactions: VecDeque<TransactionWithMetadata>,
428 ) -> Result<ScriptSequence> {
429 let paths = if multi {
432 None
433 } else {
434 Some(ScriptSequence::get_paths(
435 &self.script_config.config,
436 &self.args.sig,
437 &self.build_data.build_data.target,
438 chain,
439 !self.args.broadcast,
440 )?)
441 };
442
443 let commit = get_commit_hash(&self.script_config.config.root);
444
445 let libraries = self
446 .build_data
447 .libraries
448 .libs
449 .iter()
450 .flat_map(|(file, libs)| {
451 libs.iter()
452 .map(|(name, address)| format!("{}:{name}:{address}", file.to_string_lossy()))
453 })
454 .collect();
455
456 let sequence = ScriptSequence {
457 transactions,
458 returns: self.execution_artifacts.returns.clone(),
459 receipts: vec![],
460 pending: vec![],
461 paths,
462 timestamp: now().as_millis(),
463 libraries,
464 chain,
465 commit,
466 };
467 Ok(sequence)
468 }
469}