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, build_trace_decoder_for_context},
10 sequence::get_commit_hash,
11};
12use alloy_chains::{Chain, 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::{
23 ContractData, ContractsByArtifact, provider::fee::resolve_broadcast_eip1559_fees, shell,
24 tempo::known_fee_token_symbol,
25};
26use foundry_evm::{
27 core::{FoundryBlock, evm::FoundryEvmNetwork},
28 traces::{
29 CallTraceDecoder, Traces, debug::ContractSources, decode_trace_arena, prune_trace_depth,
30 render_trace_arena_inner,
31 },
32};
33use foundry_wallets::wallet_browser::signer::BrowserSigner;
34use futures::future::join_all;
35use parking_lot::RwLock;
36use std::{
37 collections::{BTreeMap, VecDeque},
38 mem,
39 sync::Arc,
40};
41
42#[cfg(feature = "monad")]
43mod monad;
44
45pub struct PreSimulationState<FEN: FoundryEvmNetwork> {
51 pub args: ScriptArgs,
52 pub script_config: ScriptConfig<FEN>,
53 pub script_wallets: Wallets,
54 pub browser_wallet: Option<BrowserSigner<FEN::Network>>,
55 pub build_data: LinkedBuildData,
56 pub execution_data: ExecutionData,
57 pub execution_result: ScriptResult<FEN::Network>,
58 pub execution_artifacts: ExecutionArtifacts,
59}
60
61type SimulationOutcome<N> = (String, Option<TransactionWithMetadata<N>>, bool, Traces);
62
63struct RpcSimulationContext<R> {
64 runner: RwLock<R>,
65 decoder: CallTraceDecoder,
66}
67
68enum RpcContexts<R> {
69 Simulation(Arc<HashMap<String, RpcSimulationContext<R>>>),
70 Decoding(HashMap<String, CallTraceDecoder>),
71}
72
73impl<R> RpcContexts<R> {
74 fn decoder(&self, rpc: &str) -> &CallTraceDecoder {
75 match self {
76 Self::Simulation(contexts) => &context_for_rpc(contexts, rpc).decoder,
77 Self::Decoding(decoders) => decoders.get(rpc).expect("invalid rpc url"),
78 }
79 }
80}
81
82fn context_for_rpc<'a, R>(
83 contexts: &'a HashMap<String, RpcSimulationContext<R>>,
84 rpc: &str,
85) -> &'a RpcSimulationContext<R> {
86 contexts.get(rpc).expect("invalid rpc url")
87}
88
89async fn build_rpc_simulation_context<FEN: FoundryEvmNetwork>(
90 rpc: String,
91 args: &ScriptArgs,
92 script_config: &ScriptConfig<FEN>,
93 known_contracts: &ContractsByArtifact,
94 sources: &ContractSources,
95 execution_result: &ScriptResult<FEN::Network>,
96) -> Result<(String, RpcSimulationContext<ScriptRunner<FEN>>)> {
97 let mut script_config = script_config.clone();
98 script_config.set_fork_url(rpc.clone());
99 let runner = script_config._get_runner(None, false, false).await?;
100 let decoder = build_trace_decoder_for_context(
101 args,
102 &script_config,
103 known_contracts,
104 sources,
105 execution_result,
106 script_config.source_chain_id.map(Chain::from),
107 )?;
108 Ok((rpc, RpcSimulationContext { runner: RwLock::new(runner), decoder }))
109}
110
111async fn build_rpc_decoder<FEN: FoundryEvmNetwork>(
112 rpc: String,
113 args: &ScriptArgs,
114 script_config: &ScriptConfig<FEN>,
115 known_contracts: &ContractsByArtifact,
116 sources: &ContractSources,
117 execution_result: &ScriptResult<FEN::Network>,
118) -> Result<(String, CallTraceDecoder)> {
119 let mut script_config = script_config.clone();
120 script_config.set_fork_url(rpc.clone());
121 let _ = script_config.resolve_execution_env().await?;
122 let decoder = build_trace_decoder_for_context(
123 args,
124 &script_config,
125 known_contracts,
126 sources,
127 execution_result,
128 script_config.source_chain_id.map(Chain::from),
129 )?;
130 Ok((rpc, decoder))
131}
132
133impl<FEN: FoundryEvmNetwork> PreSimulationState<FEN> {
134 pub(crate) async fn fill_ordinary_metadata(self) -> Result<FilledTransactionsState<FEN>> {
140 if self.args.skip_simulation {
141 return self.fill_without_simulation().await;
142 }
143
144 let contexts = Arc::new(self.build_runners().await?.into_iter().collect::<HashMap<_, _>>());
145 let transactions =
146 self.transaction_metadata(&RpcContexts::Simulation(Arc::clone(&contexts)))?;
147 let transactions = self.simulate_and_fill_with_contexts(transactions, contexts).await?;
148 Ok(self.into_filled(transactions))
149 }
150
151 pub(crate) async fn fill_without_simulation(self) -> Result<FilledTransactionsState<FEN>> {
153 let contexts = RpcContexts::<ScriptRunner<FEN>>::Decoding(self.build_rpc_decoders().await?);
154 let transactions = self.transaction_metadata(&contexts)?;
155 sh_println!("\nSKIPPING ON CHAIN SIMULATION.")?;
156 Ok(self.into_filled(transactions))
157 }
158
159 fn transaction_metadata<R>(
160 &self,
161 contexts: &RpcContexts<R>,
162 ) -> Result<VecDeque<TransactionWithMetadata<FEN::Network>>> {
163 let address_to_abi = self.build_address_to_abi_map();
164 let transactions = self
165 .execution_result
166 .transactions
167 .clone()
168 .unwrap_or_default()
169 .into_iter()
170 .map(|tx| {
171 let rpc = tx.rpc.expect("missing broadcastable tx rpc url");
172 let sender = tx.transaction.from().expect("all transactions should have a sender");
173 let nonce = tx.transaction.nonce().expect("all transactions should have a nonce");
174 let to = tx.transaction.to();
175 let decoder = contexts.decoder(&rpc);
176
177 let mut builder = ScriptTransactionBuilder::new(tx.transaction, rpc);
178
179 if to.is_some() {
180 builder.set_call(
181 &address_to_abi,
182 decoder,
183 self.script_config.evm_opts.create2_deployer,
184 )?;
185 } else {
186 builder.set_create(false, sender.create(nonce), &address_to_abi)?;
187 }
188
189 Ok(builder.build())
190 })
191 .collect::<Result<VecDeque<_>>>()?;
192
193 Ok(transactions)
194 }
195
196 fn into_filled(
197 self,
198 transactions: VecDeque<TransactionWithMetadata<FEN::Network>>,
199 ) -> FilledTransactionsState<FEN> {
200 FilledTransactionsState {
201 args: self.args,
202 script_config: self.script_config,
203 script_wallets: self.script_wallets,
204 browser_wallet: self.browser_wallet,
205 build_data: self.build_data,
206 execution_artifacts: self.execution_artifacts,
207 transactions,
208 }
209 }
210
211 async fn simulate_and_fill_with_contexts(
214 &self,
215 transactions: VecDeque<TransactionWithMetadata<FEN::Network>>,
216 contexts: Arc<HashMap<String, RpcSimulationContext<ScriptRunner<FEN>>>>,
217 ) -> Result<VecDeque<TransactionWithMetadata<FEN::Network>>> {
218 trace!(target: "script", "executing onchain simulation");
219
220 let futs = transactions
222 .into_iter()
223 .map(|mut transaction| async {
224 let rpc = transaction.rpc.clone();
225 let context = context_for_rpc(&contexts, &rpc);
226 let mut runner = context.runner.write();
227 let tx = transaction.tx_mut();
228
229 let to = tx.to();
230 let result = runner
231 .simulate(
232 tx.from()
233 .expect("transaction doesn't have a `from` address at execution time"),
234 to,
235 tx.input().cloned(),
236 tx.value(),
237 tx.authorization_list(),
238 )
239 .wrap_err("Internal EVM error during simulation")?;
240
241 if !result.success {
242 return Ok((rpc, None, false, result.traces));
243 }
244
245 if self.args.slow {
247 let block_number = runner.executor.evm_env().block_env.number() + U256::from(1);
248 runner.executor.evm_env_mut().block_env.set_number(block_number);
249 }
250
251 let is_noop_tx = if let Some(to) = to {
252 runner.executor.is_empty_code(to)? && tx.value().unwrap_or_default().is_zero()
253 } else {
254 false
255 };
256
257 let transaction = ScriptTransactionBuilder::from(transaction)
258 .with_execution_result(
259 &result,
260 self.args.gas_estimate_multiplier,
261 &self.build_data,
262 )
263 .build();
264
265 eyre::Ok((rpc, Some(transaction), is_noop_tx, result.traces))
266 })
267 .collect::<Vec<_>>();
268
269 self.show_simulation_header()?;
270 self.collect_simulation_results(join_all(futs).await, &contexts).await
271 }
272
273 fn show_simulation_header(&self) -> Result<()> {
274 if !shell::is_json() && self.script_config.config.tracing.verbosity > 3 {
275 sh_println!("==========================")?;
276 sh_println!("Simulated On-chain Traces:\n")?;
277 }
278 Ok(())
279 }
280
281 async fn collect_simulation_results<R>(
282 &self,
283 results: Vec<Result<SimulationOutcome<FEN::Network>>>,
284 contexts: &HashMap<String, RpcSimulationContext<R>>,
285 ) -> Result<VecDeque<TransactionWithMetadata<FEN::Network>>> {
286 let mut final_txs = VecDeque::new();
287 let tracing = &self.script_config.config.tracing;
288
289 let mut abort = false;
290 for res in results {
291 let (rpc, tx, is_noop_tx, mut traces) = res?;
292
293 if !shell::is_json() && (tx.is_none() || tracing.verbosity > 3) {
295 let decoder = &context_for_rpc(contexts, &rpc).decoder;
296 for (_, trace) in &mut traces {
297 decode_trace_arena(trace, decoder).await;
298 if let Some(trace_depth) = tracing.trace_depth {
299 prune_trace_depth(trace, trace_depth);
300 }
301 sh_println!(
302 "{}",
303 render_trace_arena_inner(trace, false, tracing.verbosity > 4)
304 )?;
305 }
306 }
307
308 if let Some(tx) = tx {
309 if is_noop_tx {
310 let to = tx.contract_address.unwrap();
311 sh_warn!(
312 "Script contains a transaction to {to} which does not contain any code."
313 )?;
314
315 if self.args.should_broadcast()
317 && !self.args.non_interactive
318 && !Confirm::new()
319 .with_prompt("Do you wish to continue?".to_string())
320 .interact()?
321 {
322 eyre::bail!("User canceled the script.");
323 }
324 }
325
326 final_txs.push_back(tx);
327 } else {
328 abort = true;
329 }
330 }
331
332 if abort {
333 eyre::bail!("Simulated execution failed.");
334 }
335
336 Ok(final_txs)
337 }
338
339 fn build_address_to_abi_map(&self) -> BTreeMap<Address, &ContractData> {
341 self.execution_artifacts
342 .decoder
343 .contracts
344 .iter()
345 .filter_map(move |(addr, contract_id)| {
346 if let Ok(Some((_, data))) =
347 self.build_data.known_contracts.find_by_name_or_identifier(contract_id)
348 {
349 return Some((*addr, data));
350 }
351 None
352 })
353 .collect()
354 }
355
356 async fn build_runners(
358 &self,
359 ) -> Result<Vec<(String, RpcSimulationContext<ScriptRunner<FEN>>)>> {
360 let rpcs = &self.execution_artifacts.rpc_data.total_rpcs;
361
362 if !shell::is_json() {
363 let n = rpcs.len();
364 let s = if n == 1 { "" } else { "s" };
365 sh_println!("\n## Setting up {n} EVM{s}.")?;
366 }
367
368 let mut contexts = Vec::with_capacity(rpcs.len());
371 for rpc in rpcs.iter().cloned() {
372 contexts.push(
373 build_rpc_simulation_context(
374 rpc,
375 &self.args,
376 &self.script_config,
377 &self.build_data.known_contracts,
378 &self.build_data.sources,
379 &self.execution_result,
380 )
381 .await?,
382 );
383 }
384 Ok(contexts)
385 }
386
387 async fn build_rpc_decoders(&self) -> Result<HashMap<String, CallTraceDecoder>> {
389 let rpcs = &self.execution_artifacts.rpc_data.total_rpcs;
390 let mut decoders = HashMap::default();
392 for rpc in rpcs.iter().cloned() {
393 let (rpc, decoder) = build_rpc_decoder(
394 rpc,
395 &self.args,
396 &self.script_config,
397 &self.build_data.known_contracts,
398 &self.build_data.sources,
399 &self.execution_result,
400 )
401 .await?;
402 decoders.insert(rpc, decoder);
403 }
404 Ok(decoders)
405 }
406}
407
408#[cfg(all(test, feature = "monad"))]
409mod tests {
410 use super::*;
411 use alloy_primitives::address;
412 use anvil::{NodeConfig, spawn};
413 use foundry_cli::opts::TempoOpts;
414 use foundry_config::Config;
415 use foundry_evm::{
416 core::{evm::MonadEvmNetwork, opts::EvmOpts},
417 executors::ExecutorBuilder,
418 hardforks::MonadHardfork,
419 };
420 use foundry_evm_networks::NetworkConfigs;
421
422 const RESERVE_BALANCE_ADDRESS: Address = address!("0000000000000000000000000000000000001001");
423
424 #[tokio::test(flavor = "multi_thread")]
425 async fn multi_rpc_fork_selects_trace_decoder_for_source_hardfork() {
426 let (monad_eight_api, monad_eight) = spawn(
427 NodeConfig::test_monad()
428 .with_chain_id(Some(NamedChain::Monad as u64))
429 .with_hardfork(Some(MonadHardfork::MonadEight.into())),
430 )
431 .await;
432 let (monad_nine_api, monad_nine) = spawn(
433 NodeConfig::test_monad()
434 .with_chain_id(Some(NamedChain::Monad as u64))
435 .with_hardfork(Some(MonadHardfork::MonadNine.into())),
436 )
437 .await;
438 monad_eight_api.mine_one().await.unwrap();
439 monad_nine_api.mine_one().await.unwrap();
440 let monad_eight_rpc = monad_eight.http_endpoint();
441 let monad_nine_rpc = monad_nine.http_endpoint();
442
443 let mut evm_opts = EvmOpts {
444 fork_url: Some(monad_eight_rpc.clone()),
445 fork_block_number: Some(0),
446 networks: NetworkConfigs::with_monad(),
447 ..Default::default()
448 };
449 evm_opts.env.chain_id = Some(42);
450 let script_config = ScriptConfig::<MonadEvmNetwork>::new(
451 Config::default(),
452 evm_opts,
453 ExecutorBuilder::<MonadEvmNetwork>::new(),
454 false,
455 TempoOpts::default(),
456 Some(0),
457 )
458 .await
459 .unwrap();
460 let args = ScriptArgs::default();
461 let known_contracts = ContractsByArtifact::default();
462 let sources = ContractSources::default();
463 let execution_result = ScriptResult::default();
464 let contexts = [
465 build_rpc_simulation_context(
466 monad_eight_rpc.clone(),
467 &args,
468 &script_config,
469 &known_contracts,
470 &sources,
471 &execution_result,
472 )
473 .await
474 .unwrap(),
475 build_rpc_simulation_context(
476 monad_nine_rpc.clone(),
477 &args,
478 &script_config,
479 &known_contracts,
480 &sources,
481 &execution_result,
482 )
483 .await
484 .unwrap(),
485 ]
486 .into_iter()
487 .collect::<HashMap<_, _>>();
488
489 let monad_eight = context_for_rpc(&contexts, &monad_eight_rpc);
490 let monad_eight_runner = monad_eight.runner.read();
491 assert_eq!(monad_eight_runner.executor.evm_env().cfg_env.chain_id, 42);
492 assert_eq!(monad_eight_runner.executor.evm_env().block_env.number(), U256::ZERO);
493 assert_eq!(monad_eight_runner.evm_opts.fork_block_number, Some(0));
494 assert!(!monad_eight_runner.evm_opts.fork_block_number_is_inferred);
495 assert_eq!(monad_eight_runner.evm_opts.networks, NetworkConfigs::with_monad());
496 assert!(!monad_eight_runner.evm_opts.fork_network_is_inferred);
497 assert_eq!(monad_eight.decoder.chain_id, Some(NamedChain::Monad as u64));
498 assert_eq!(monad_eight.decoder.hardfork(), Some(MonadHardfork::MonadEight.into()));
499 assert!(!monad_eight.decoder.precompile_labels().contains_key(&RESERVE_BALANCE_ADDRESS));
500
501 let monad_nine = context_for_rpc(&contexts, &monad_nine_rpc);
502 let monad_nine_runner = monad_nine.runner.read();
503 assert_eq!(monad_nine_runner.executor.evm_env().cfg_env.chain_id, 42);
504 assert_eq!(monad_nine_runner.executor.evm_env().block_env.number(), U256::ZERO);
505 assert_eq!(monad_nine_runner.evm_opts.fork_block_number, Some(0));
506 assert!(!monad_nine_runner.evm_opts.fork_block_number_is_inferred);
507 assert_eq!(monad_nine_runner.evm_opts.networks, NetworkConfigs::with_monad());
508 assert!(!monad_nine_runner.evm_opts.fork_network_is_inferred);
509 assert_eq!(monad_nine.decoder.chain_id, Some(NamedChain::Monad as u64));
510 assert_eq!(monad_nine.decoder.hardfork(), Some(MonadHardfork::MonadNine.into()));
511 assert_eq!(
512 monad_nine.decoder.precompile_labels().get(&RESERVE_BALANCE_ADDRESS),
513 Some(&"ReserveBalance".to_string())
514 );
515 }
516
517 #[tokio::test(flavor = "multi_thread")]
518 async fn skip_simulation_fork_builds_per_rpc_trace_decoders() {
519 let (monad_eight_api, monad_eight) = spawn(
520 NodeConfig::test_monad()
521 .with_chain_id(Some(NamedChain::Monad as u64))
522 .with_hardfork(Some(MonadHardfork::MonadEight.into())),
523 )
524 .await;
525 let (monad_nine_api, monad_nine) = spawn(
526 NodeConfig::test_monad()
527 .with_chain_id(Some(NamedChain::Monad as u64))
528 .with_hardfork(Some(MonadHardfork::MonadNine.into())),
529 )
530 .await;
531 monad_eight_api.mine_one().await.unwrap();
532 monad_nine_api.mine_one().await.unwrap();
533 let monad_eight_rpc = monad_eight.http_endpoint();
534 let monad_nine_rpc = monad_nine.http_endpoint();
535
536 let script_config = ScriptConfig::<MonadEvmNetwork>::new(
537 Config::default(),
538 EvmOpts {
539 fork_url: Some(monad_eight_rpc.clone()),
540 fork_block_number: Some(0),
541 networks: NetworkConfigs::with_monad(),
542 ..Default::default()
543 },
544 ExecutorBuilder::<MonadEvmNetwork>::new(),
545 false,
546 TempoOpts::default(),
547 Some(0),
548 )
549 .await
550 .unwrap();
551 let args = ScriptArgs { skip_simulation: true, ..Default::default() };
552 let known_contracts = ContractsByArtifact::default();
553 let sources = ContractSources::default();
554 let execution_result = ScriptResult::default();
555 let decoders = [
556 build_rpc_decoder(
557 monad_eight_rpc.clone(),
558 &args,
559 &script_config,
560 &known_contracts,
561 &sources,
562 &execution_result,
563 )
564 .await
565 .unwrap(),
566 build_rpc_decoder(
567 monad_nine_rpc.clone(),
568 &args,
569 &script_config,
570 &known_contracts,
571 &sources,
572 &execution_result,
573 )
574 .await
575 .unwrap(),
576 ]
577 .into_iter()
578 .collect::<HashMap<_, _>>();
579
580 let monad_eight = decoders.get(&monad_eight_rpc).unwrap();
581 assert_eq!(monad_eight.hardfork(), Some(MonadHardfork::MonadEight.into()));
582 assert!(!monad_eight.precompile_labels().contains_key(&RESERVE_BALANCE_ADDRESS));
583
584 let monad_nine = decoders.get(&monad_nine_rpc).unwrap();
585 assert_eq!(monad_nine.hardfork(), Some(MonadHardfork::MonadNine.into()));
586 assert_eq!(
587 monad_nine.precompile_labels().get(&RESERVE_BALANCE_ADDRESS),
588 Some(&"ReserveBalance".to_string())
589 );
590 }
591
592 #[tokio::test(flavor = "multi_thread")]
593 async fn multi_rpc_fork_rejects_inferred_network_change() {
594 let (_monad_api, monad) = spawn(NodeConfig::test_monad()).await;
595 let (_ethereum_api, ethereum) = spawn(NodeConfig::test()).await;
596 let ethereum_rpc = ethereum.http_endpoint();
597 let script_config = ScriptConfig::<MonadEvmNetwork>::new(
598 Config::default(),
599 EvmOpts { fork_url: Some(monad.http_endpoint()), ..Default::default() },
600 ExecutorBuilder::<MonadEvmNetwork>::new(),
601 false,
602 TempoOpts::default(),
603 Some(0),
604 )
605 .await
606 .unwrap();
607 assert!(script_config.evm_opts.fork_network_is_inferred);
608
609 let result = build_rpc_simulation_context(
610 ethereum_rpc.clone(),
611 &ScriptArgs::default(),
612 &script_config,
613 &ContractsByArtifact::default(),
614 &ContractSources::default(),
615 &ScriptResult::default(),
616 )
617 .await;
618 let Err(error) = result else { panic!("inferred cross-network fork should be rejected") };
619 assert!(
620 error
621 .to_string()
622 .contains("fork network `ethereum` is incompatible with the active EVM"),
623 "{error}"
624 );
625
626 let result = build_rpc_decoder(
627 ethereum_rpc,
628 &ScriptArgs { skip_simulation: true, ..Default::default() },
629 &script_config,
630 &ContractsByArtifact::default(),
631 &ContractSources::default(),
632 &ScriptResult::default(),
633 )
634 .await;
635 let Err(error) = result else {
636 panic!("skip-simulation decoder should reject an inferred cross-network fork")
637 };
638 assert!(
639 error
640 .to_string()
641 .contains("fork network `ethereum` is incompatible with the active EVM"),
642 "{error}"
643 );
644 }
645}
646
647pub struct FilledTransactionsState<FEN: FoundryEvmNetwork> {
651 pub args: ScriptArgs,
652 pub script_config: ScriptConfig<FEN>,
653 pub script_wallets: Wallets,
654 pub browser_wallet: Option<BrowserSigner<FEN::Network>>,
655 pub build_data: LinkedBuildData,
656 pub execution_artifacts: ExecutionArtifacts,
657 pub transactions: VecDeque<TransactionWithMetadata<FEN::Network>>,
658}
659
660impl<FEN: FoundryEvmNetwork> FilledTransactionsState<FEN> {
661 pub async fn bundle(mut self) -> Result<BundledState<FEN>> {
667 let is_multi_deployment = self.execution_artifacts.rpc_data.total_rpcs.len() > 1;
668
669 if is_multi_deployment && !self.build_data.libraries.is_empty() {
670 eyre::bail!("Multi-chain deployment is not supported with libraries.");
671 }
672
673 let mut total_gas_per_rpc: HashMap<String, u128> = HashMap::default();
674
675 let mut new_sequence = VecDeque::new();
677 let mut manager = ProvidersManager::<FEN::Network>::default();
678 let mut sequences = vec![];
679
680 let mut txes_iter = mem::take(&mut self.transactions).into_iter().peekable();
683
684 while let Some(mut tx) = txes_iter.next() {
685 let tx_rpc = tx.rpc.clone();
686 let provider_info = manager
687 .get_or_init_provider(
688 &tx.rpc,
689 self.execution_artifacts.rpc_data.chain_ids.get(&tx.rpc).copied(),
690 self.args.legacy,
691 self.script_config.config.eip1559_fee_estimate,
692 &self.script_config.config,
693 )
694 .await?;
695
696 if let Some(tx) = tx.tx_mut().as_unsigned_mut() {
697 tx.set_chain_id(provider_info.chain);
699 }
700
701 if !self.args.skip_simulation {
702 let tx = tx.tx_mut();
703
704 if has_different_gas_calc(provider_info.chain) {
705 if let Some(tx) = tx.as_unsigned_mut() {
707 trace!("estimating with different gas calculation");
708 let gas = tx.gas_limit().expect("gas is set by simulation.");
709
710 if let Err(err) = estimate_gas(
722 tx,
723 &provider_info.provider,
724 self.args.gas_estimate_multiplier,
725 false,
726 )
727 .await
728 {
729 trace!("gas estimation failed: {err}");
730
731 tx.set_gas_limit(gas);
733 }
734 }
735 }
736
737 let total_gas = total_gas_per_rpc.entry(tx_rpc.clone()).or_insert(0);
738 *total_gas += tx.gas().expect("gas is set");
739 }
740
741 new_sequence.push_back(tx);
742 if let Some(next_tx) = txes_iter.peek()
745 && next_tx.rpc == tx_rpc
746 {
747 continue;
748 }
749
750 let sequence =
751 self.create_sequence(is_multi_deployment, provider_info.chain, new_sequence)?;
752
753 sequences.push(sequence);
754
755 new_sequence = VecDeque::new();
756 }
757
758 if !self.args.skip_simulation {
759 for (rpc, total_gas) in total_gas_per_rpc {
761 let provider_info = manager.get(&rpc).expect("provider is set.");
762
763 let token_symbol = if self.script_config.evm_opts.networks.is_tempo() {
764 self.args.tempo.fee_token.map_or_else(
765 || "TIP-20".to_string(),
766 |fee_token| {
767 known_fee_token_symbol(fee_token)
768 .map(str::to_string)
769 .unwrap_or_else(|| fee_token.to_string())
770 },
771 )
772 } else {
773 NamedChain::try_from(provider_info.chain)
774 .unwrap_or_default()
775 .native_currency_symbol()
776 .unwrap_or("ETH")
777 .to_string()
778 };
779
780 let resolved_eip1559_fees = if self.args.with_gas_price.is_none() {
787 if let Some(fees) = provider_info.eip1559_fees().copied() {
788 let browser_suggested_tip =
791 if !self.args.batch && self.browser_wallet.is_some() {
792 provider_info.provider.get_max_priority_fee_per_gas().await.ok()
793 } else {
794 None
795 };
796 Some(resolve_broadcast_eip1559_fees(
797 fees,
798 None,
799 self.args.priority_gas_price.map(|p| p.to()),
800 browser_suggested_tip,
801 )?)
802 } else {
803 None
804 }
805 } else {
806 None
807 };
808
809 let per_gas = if let Some(gas_price) = self.args.with_gas_price {
813 gas_price.to()
814 } else if let Some(fees) = &resolved_eip1559_fees {
815 fees.max_fee_per_gas
816 } else {
817 provider_info.gas_price()?
818 };
819
820 let fmt_gwei = |wei: u128| {
822 let raw = format_units(wei, 9)
823 .unwrap_or_else(|_| "[Could not calculate]".to_string());
824 raw.trim_end_matches('0').trim_end_matches('.').to_string()
825 };
826
827 let estimated_gas_price = fmt_gwei(per_gas);
828
829 let fee_breakdown = resolved_eip1559_fees.as_ref().map(|fees| {
831 (fmt_gwei(fees.base_fee_per_gas), fmt_gwei(fees.max_priority_fee_per_gas))
832 });
833
834 let estimated_amount_raw = format_units(total_gas.saturating_mul(per_gas), 18)
835 .unwrap_or_else(|_| "[Could not calculate]".to_string());
836 let estimated_amount = estimated_amount_raw.trim_end_matches('0');
837
838 if shell::is_json() {
839 let mut json = serde_json::json!({
840 "chain": provider_info.chain,
841 "estimated_gas_price": estimated_gas_price,
842 "estimated_total_gas_used": total_gas,
843 "estimated_amount_required": estimated_amount,
844 "token_symbol": token_symbol,
845 });
846 if let Some((base_fee, priority_fee)) = &fee_breakdown {
847 json["estimated_max_fee_per_gas"] =
848 serde_json::Value::from(estimated_gas_price);
849 json["estimated_base_fee_per_gas"] =
850 serde_json::Value::from(base_fee.clone());
851 json["estimated_max_priority_fee_per_gas"] =
852 serde_json::Value::from(priority_fee.clone());
853 }
854 sh_println!("{}", json)?;
855 } else {
856 sh_println!("\n==========================")?;
857 sh_println!("\nChain {}", provider_info.chain)?;
858
859 if let Some((base_fee, priority_fee)) = &fee_breakdown {
860 sh_println!("\nEstimated max fee per gas: {estimated_gas_price} gwei")?;
861 sh_println!("Estimated base fee per gas: {base_fee} gwei")?;
862 sh_println!("Estimated max priority fee per gas: {priority_fee} gwei")?;
863 } else {
864 sh_println!("\nEstimated gas price: {estimated_gas_price} gwei")?;
865 }
866 sh_println!("\nEstimated total gas used for script: {total_gas}")?;
867 sh_println!("\nEstimated amount required: {estimated_amount} {token_symbol}")?;
868 sh_println!("\n==========================")?;
869 }
870 }
871 }
872
873 let sequence = if sequences.len() == 1 {
874 ScriptSequenceKind::Single(sequences.pop().expect("empty sequences"))
875 } else {
876 ScriptSequenceKind::Multi(MultiChainSequence::new(
877 sequences,
878 &self.args.sig,
879 &self.build_data.build_data.target,
880 &self.script_config.config,
881 !self.args.broadcast,
882 )?)
883 };
884
885 Ok(BundledState {
886 args: self.args,
887 script_config: self.script_config,
888 script_wallets: self.script_wallets,
889 browser_wallet: self.browser_wallet,
890 build_data: self.build_data,
891 sequence,
892 })
893 }
894
895 fn create_sequence(
897 &self,
898 multi: bool,
899 chain: u64,
900 transactions: VecDeque<TransactionWithMetadata<FEN::Network>>,
901 ) -> Result<ScriptSequence<FEN::Network>> {
902 let paths = if multi {
905 None
906 } else {
907 Some(ScriptSequence::<FEN::Network>::get_paths(
908 &self.script_config.config,
909 &self.args.sig,
910 &self.build_data.build_data.target,
911 chain,
912 !self.args.broadcast,
913 )?)
914 };
915
916 let commit = get_commit_hash(&self.script_config.config.root);
917
918 let local_addresses = match &self.build_data.predeploy_libraries {
919 crate::build::ScriptPredeployLibraries::Default { local, .. }
920 | crate::build::ScriptPredeployLibraries::Create2 { local, .. } => local.as_slice(),
921 };
922 let local_addresses = local_addresses
923 .iter()
924 .map(|library| library.address.to_checksum(None))
925 .collect::<Vec<_>>();
926 let libraries = self
927 .build_data
928 .libraries
929 .libs
930 .iter()
931 .flat_map(|(file, libs)| {
932 libs.iter()
933 .filter(|(_, address)| !local_addresses.contains(address))
934 .map(|(name, address)| format!("{}:{name}:{address}", file.to_string_lossy()))
935 })
936 .collect();
937
938 let sequence = ScriptSequence {
939 transactions,
940 returns: self.execution_artifacts.returns.clone(),
941 receipts: vec![],
942 pending: vec![],
943 paths,
944 timestamp: now().as_millis(),
945 libraries,
946 chain,
947 commit,
948 };
949 Ok(sequence)
950 }
951}