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