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