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