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