1#![recursion_limit = "256"]
6#![cfg_attr(not(test), warn(unused_crate_dependencies))]
7#![cfg_attr(docsrs, feature(doc_cfg))]
8
9#[macro_use]
10extern crate foundry_common;
11
12#[macro_use]
13extern crate tracing;
14
15use crate::{broadcast::BundledState, runner::ScriptRunner, simulate::PreSimulationState};
16use alloy_json_abi::{Function, JsonAbi};
17use alloy_network::Network;
18use alloy_primitives::{
19 Address, B256, Bytes, Log, U256, hex,
20 map::{AddressHashMap, HashMap},
21};
22use alloy_signer::Signer;
23use build::PreprocessedState;
24use clap::{Parser, ValueHint, builder::RangedU64ValueParser};
25use dialoguer::Confirm;
26use eyre::{ContextCompat, Result};
27use forge_script_sequence::{AdditionalContract, NestedValue};
28use forge_verify::{RetryArgs, VerifierArgs};
29use foundry_cli::{
30 opts::{BuildOpts, EvmArgs, GlobalArgs, TempoOpts, TracingArgs},
31 utils::LoadConfig,
32};
33use foundry_common::{
34 ContractsByArtifact, SELECTOR_LEN,
35 abi::{encode_function_args, get_func},
36 compile::ContractSizeLimits,
37 shell,
38};
39use foundry_compilers::{ArtifactId, artifacts::output_selection::ContractOutputSelection};
40use foundry_config::{
41 Config, Eip1559FeeEstimatePreset, FoundryHardfork, figment,
42 figment::{
43 Metadata, Profile, Provider,
44 value::{Dict, Map},
45 },
46};
47use foundry_evm::{
48 backend::Backend,
49 core::{
50 Breakpoints, FoundryTransaction,
51 evm::{EthEvmNetwork, EvmEnvFor, FoundryEvmNetwork, SpecFor, TempoEvmNetwork, TxEnvFor},
52 },
53 executors::ExecutorBuilder,
54 hardforks::ExecutionSpec,
55 inspectors::{
56 CheatsConfig,
57 cheatcodes::{BroadcastableTransactions, Wallets},
58 },
59 opts::{EvmOpts, ExecutionSpecContext, ForkContext, resolve_execution_spec},
60 revm::interpreter::InstructionResult,
61 traces::{InternalTraceMode, TraceRequirements, Traces},
62};
63use foundry_evm_networks::{NetworkConfigs, NetworkVariant};
64use foundry_wallets::{MultiWalletOpts, wallet_multi::MultiWallet};
65use serde::Serialize;
66use std::path::PathBuf;
67
68#[cfg(feature = "base")]
69use foundry_evm::core::evm::BaseEvmNetwork;
70
71#[cfg(feature = "monad")]
72use foundry_evm::core::evm::MonadEvmNetwork;
73
74#[cfg(feature = "optimism")]
75use foundry_evm::core::evm::OpEvmNetwork;
76
77mod broadcast;
78mod build;
79mod execute;
80mod library_deployments;
81mod multi_sequence;
82mod progress;
83mod providers;
84mod receipts;
85mod recovery;
86mod runner;
87mod sequence;
88mod session;
89mod simulate;
90mod transaction;
91mod verify;
92mod wallet_session;
93
94pub use wallet_session::ScriptWalletSessionArgs;
95
96foundry_config::merge_impl_figment_convert!(ScriptArgs, build, evm);
98
99const DEFAULT_CONFIRMATIONS: u64 = 1;
100
101enum PreparedScript<FEN: FoundryEvmNetwork> {
103 Resume(Box<BundledState<FEN>>),
104 Simulate(Box<PreSimulationState<FEN>>),
105}
106
107#[derive(Clone, Debug, Default, Parser)]
109pub struct ScriptArgs {
110 #[command(flatten)]
112 pub global: GlobalArgs,
113
114 #[arg(value_hint = ValueHint::FilePath)]
119 pub path: String,
120
121 pub args: Vec<String>,
123
124 #[arg(long, visible_alias = "tc", value_name = "CONTRACT_NAME")]
126 pub target_contract: Option<String>,
127
128 #[arg(long, short, default_value = "run")]
130 pub sig: String,
131
132 #[arg(
134 long,
135 env = "ETH_PRIORITY_GAS_PRICE",
136 value_parser = foundry_cli::utils::parse_ether_value,
137 value_name = "PRICE"
138 )]
139 pub priority_gas_price: Option<U256>,
140
141 #[arg(long)]
145 pub legacy: bool,
146
147 #[arg(long = "estimate", value_name = "PRESET")]
155 pub eip1559_fee_estimate: Option<Eip1559FeeEstimatePreset>,
156
157 #[arg(long)]
159 pub broadcast: bool,
160
161 #[arg(long)]
167 pub batch: bool,
168
169 #[command(flatten)]
171 pub tempo: TempoOpts,
172
173 #[command(flatten)]
175 pub wallet_session: ScriptWalletSessionArgs,
176
177 #[arg(long)]
179 pub skip_simulation: bool,
180
181 #[arg(long, short, default_value = "130")]
183 pub gas_estimate_multiplier: u64,
184
185 #[arg(
187 long,
188 value_name = "NONCE",
189 value_parser = RangedU64ValueParser::<u64>::new().range(..u64::MAX),
190 )]
191 pub sender_nonce: Option<u64>,
192
193 #[arg(
195 long,
196 conflicts_with_all = &["private_key", "private_keys", "ledger", "trezor", "aws", "gcp", "turnkey", "browser"],
197 )]
198 pub unlocked: bool,
199
200 #[arg(long)]
207 pub resume: bool,
208
209 #[arg(long, requires = "resume")]
211 pub resume_attempt: Option<B256>,
212
213 #[arg(
215 long,
216 requires_all = ["resume", "resume_attempt"],
217 conflicts_with = "resume_retry"
218 )]
219 pub resume_tx_hash: Option<B256>,
220
221 #[arg(
223 long,
224 requires_all = ["resume", "resume_attempt"],
225 conflicts_with = "resume_tx_hash"
226 )]
227 pub resume_retry: bool,
228
229 #[arg(long, requires = "broadcast", conflicts_with = "resume")]
234 pub start_fresh: bool,
235
236 #[arg(long)]
238 pub multi: bool,
239
240 #[arg(long)]
244 pub debug: bool,
245
246 #[arg(
251 long,
252 requires = "debug",
253 value_hint = ValueHint::FilePath,
254 value_name = "PATH"
255 )]
256 pub dump: Option<PathBuf>,
257
258 #[arg(long)]
267 pub slow: bool,
268
269 #[arg(
271 long,
272 default_value_t = DEFAULT_CONFIRMATIONS,
273 value_parser = RangedU64ValueParser::<u64>::new().range(1..),
274 )]
275 pub confirmations: u64,
276
277 #[arg(long)]
281 pub non_interactive: bool,
282
283 #[arg(long)]
285 pub disable_code_size_limit: bool,
286
287 #[command(flatten)]
288 pub tracing: TracingArgs,
289
290 #[arg(long, env = "ETHERSCAN_API_KEY", value_name = "KEY")]
292 pub etherscan_api_key: Option<String>,
293
294 #[arg(long, requires = "broadcast")]
296 pub verify: bool,
297
298 #[arg(long, requires = "verify", conflicts_with_all = ["skip_simulation", "offline"])]
300 pub verify_external: bool,
301
302 #[arg(
307 long,
308 env = "ETH_GAS_PRICE",
309 value_parser = foundry_cli::utils::parse_ether_value,
310 value_name = "PRICE",
311 )]
312 pub with_gas_price: Option<U256>,
313
314 #[arg(long, env = "ETH_TIMEOUT")]
316 pub timeout: Option<u64>,
317
318 #[command(flatten)]
319 pub build: BuildOpts,
320
321 #[command(flatten)]
322 pub wallets: MultiWalletOpts,
323
324 #[command(flatten)]
325 pub evm: EvmArgs,
326
327 #[command(flatten)]
328 pub verifier: VerifierArgs,
329
330 #[command(flatten)]
331 pub retry: RetryArgs,
332}
333
334impl ScriptArgs {
335 fn has_tempo_session(&self) -> Result<bool> {
336 Ok(self.tempo.session_id()?.is_some())
337 }
338
339 async fn resolved_evm_opts(
341 &self,
342 mut config: Config,
343 mut evm_opts: EvmOpts,
344 ) -> Result<(Config, EvmOpts)> {
345 if self.tempo.is_tempo() || self.has_tempo_session()? {
346 if evm_opts.networks.has_network_selection() {
347 let network = evm_opts.networks.execution_network();
348 eyre::ensure!(
349 network.is_tempo(),
350 "Tempo transaction options conflict with configured network `{}`",
351 evm_opts.networks.execution_profile_name()
352 );
353 }
354 evm_opts.networks = NetworkConfigs::with_tempo();
356 }
357 evm_opts.infer_network_from_fork().await?;
360 config.networks = evm_opts.networks;
361
362 Ok((config, evm_opts))
363 }
364
365 async fn preprocess<FEN: FoundryEvmNetwork>(
366 self,
367 mut config: Config,
368 mut evm_opts: EvmOpts,
369 executor_builder: ExecutorBuilder<FEN>,
370 ) -> Result<PreprocessedState<FEN>> {
371 let args = self;
372 let mut tempo = args.tempo.clone();
373
374 let session_sender = if args.resume {
375 None
376 } else {
377 tempo.session_sender_for_multi_wallet(&args.wallets, args.evm.sender)?
380 };
381
382 let (script_wallets, browser_wallet) = if args.resume {
384 (Wallets::new(MultiWallet::default(), args.evm.sender), None)
385 } else {
386 (
387 Wallets::new(args.wallets.get_multi_wallet().await?, args.evm.sender),
388 args.wallets.browser_signer::<FEN::Network>().await?,
389 )
390 };
391
392 if let Some(sender) = session_sender {
393 evm_opts.sender = sender;
394 } else if !args.resume
395 && args.evm.sender.is_none()
396 && let Some(sender) = args.maybe_load_private_key()?
397 {
398 evm_opts.sender = sender;
399 } else if !args.resume && args.evm.sender.is_none() {
400 let mut addresses = script_wallets.addresses();
404 if addresses.is_empty() && browser_wallet.is_none() {
405 addresses = script_wallets.signers().map_err(|err| {
408 eyre::eyre!("{err}").wrap_err(
409 "failed to unlock the wallet to find the script sender; pass `--sender`, \
410 or a keystore password file with `--password-file` or `ETH_PASSWORD`",
411 )
412 })?;
413 }
414 if addresses.len() == 1 {
415 evm_opts.sender = addresses[0];
416 } else if let Some(signer) = browser_wallet.as_ref().map(|b| b.address()) {
417 evm_opts.sender = signer
418 }
419 }
420
421 tempo.resolve_expires();
422 config.tracing = args.tracing.resolve(&config.tracing, evm_opts.verbosity);
423 if args.debug {
424 if !config.extra_output.contains(&ContractOutputSelection::StorageLayout) {
425 config.extra_output.push(ContractOutputSelection::StorageLayout);
426 }
427 if let Err(error) = config.enable_debug_build_info() {
430 let _ = sh_warn!(
431 "the debugger may show source files that differ from the compiled ones: {error:#}"
432 );
433 }
434 }
435
436 let script_config = ScriptConfig::new(
437 config,
438 evm_opts,
439 executor_builder,
440 args.batch,
441 tempo,
442 args.sender_nonce,
443 )
444 .await?;
445 Ok(PreprocessedState { args, script_config, script_wallets, browser_wallet })
446 }
447
448 #[allow(clippy::large_stack_frames)]
450 pub async fn run_script(self) -> Result<()> {
451 trace!(target: "script", "executing script command");
452
453 if self.tempo.sponsor_url.is_some() {
454 eyre::bail!(
455 "--sponsor-url is not supported by forge script; use --tempo.sponsor with \
456 --tempo.sponsor-signer or --tempo.sponsor-sig"
457 );
458 }
459
460 if self.wallet_session.enabled {
461 return self.run_wallet_session_wrapper();
462 }
463
464 let (mut config, evm_opts) = self.load_config_and_evm_opts()?;
465 self.install_missing_dependencies(&mut config)?;
466 let (config, evm_opts) = self.resolved_evm_opts(config, evm_opts).await?;
467
468 let is_tempo = evm_opts.networks.is_tempo();
469
470 if self.batch && !is_tempo {
471 eyre::bail!("--batch mode is only supported on Tempo networks");
472 }
473
474 if self.unlocked && self.has_tempo_session()? {
475 eyre::bail!("--tempo.session/TEMPO_SESSION_ID cannot be combined with --unlocked");
476 }
477
478 match evm_opts.networks.execution_network() {
481 NetworkVariant::Tempo => {
482 let batch = self.batch;
483 Box::pin(async move {
484 let bundled = match self
485 .prepare_bundled::<TempoEvmNetwork>(
486 config,
487 evm_opts,
488 ExecutorBuilder::<TempoEvmNetwork>::new(),
489 )
490 .await?
491 {
492 Some(bundled) => bundled,
493 None => return Ok(()),
494 };
495 let bundled = if batch { bundled } else { bundled.wait_for_pending().await? };
499 let broadcasted = if batch {
500 bundled.broadcast_batch().await?
501 } else {
502 bundled.broadcast().await?
503 };
504 if broadcasted.args.verify {
505 broadcasted.verify().await?;
506 }
507 Ok(())
508 })
509 .await
510 }
511 #[cfg(feature = "base")]
512 NetworkVariant::Base => {
513 Box::pin(self.run_generic_script::<BaseEvmNetwork>(
514 config,
515 evm_opts,
516 ExecutorBuilder::<BaseEvmNetwork>::new(),
517 ))
518 .await
519 }
520 #[cfg(feature = "monad")]
521 NetworkVariant::Monad => {
522 Box::pin(async move {
523 let Some(prepared) = self
524 .prepare_script(config, evm_opts, ExecutorBuilder::<MonadEvmNetwork>::new())
525 .await?
526 else {
527 return Ok(());
528 };
529 let bundled = match prepared {
530 PreparedScript::Resume(bundled) => *bundled,
531 PreparedScript::Simulate(state) => {
532 state.fill_monad_metadata().await?.bundle().await?
533 }
534 };
535 let Some(bundled) = Self::finish_bundle(bundled).await? else { return Ok(()) };
536 Self::broadcast_bundle(bundled).await
537 })
538 .await
539 }
540 #[cfg(feature = "optimism")]
541 NetworkVariant::Optimism => {
542 Box::pin(self.run_generic_script::<OpEvmNetwork>(
543 config,
544 evm_opts,
545 ExecutorBuilder::<OpEvmNetwork>::new(),
546 ))
547 .await
548 }
549 NetworkVariant::Ethereum => {
550 Box::pin(self.run_generic_script::<EthEvmNetwork>(
551 config,
552 evm_opts,
553 ExecutorBuilder::<EthEvmNetwork>::new(),
554 ))
555 .await
556 }
557 }
558 }
559
560 #[allow(clippy::large_stack_frames)]
564 async fn prepare_bundled<FEN: FoundryEvmNetwork>(
565 self,
566 config: Config,
567 evm_opts: EvmOpts,
568 executor_builder: ExecutorBuilder<FEN>,
569 ) -> Result<Option<BundledState<FEN>>> {
570 let Some(prepared) = self.prepare_script(config, evm_opts, executor_builder).await? else {
571 return Ok(None);
572 };
573 let bundled = match prepared {
574 PreparedScript::Resume(bundled) => *bundled,
575 PreparedScript::Simulate(state) => {
576 state.fill_ordinary_metadata().await?.bundle().await?
577 }
578 };
579 Self::finish_bundle(bundled).await
580 }
581
582 #[allow(clippy::large_stack_frames)]
584 async fn prepare_script<FEN: FoundryEvmNetwork>(
585 self,
586 config: Config,
587 evm_opts: EvmOpts,
588 executor_builder: ExecutorBuilder<FEN>,
589 ) -> Result<Option<PreparedScript<FEN>>> {
590 let state = self.preprocess::<FEN>(config, evm_opts, executor_builder).await?;
591 let create2_deployer = state.script_config.evm_opts.create2_deployer;
592 let compiled = state.compile()?;
593
594 let prepared = if compiled.args.resume {
597 PreparedScript::Resume(Box::new(compiled.resume().await?))
598 } else {
599 let pre_simulation = compiled
601 .link()
602 .await?
603 .prepare_execution()
604 .await?
605 .execute()
606 .await?
607 .prepare_simulation()
608 .await?
609 .optimize_library_deployments()
610 .await?;
611
612 if pre_simulation.args.debug {
613 return match pre_simulation.args.dump.clone() {
614 Some(path) => pre_simulation.dump_debugger(&path).map(|_| None),
615 None => pre_simulation.run_debugger().map(|_| None),
616 };
617 }
618
619 if shell::is_json() {
620 pre_simulation.show_json().await?;
621 } else {
622 pre_simulation.show_traces().await?;
623 }
624
625 if pre_simulation
628 .execution_result
629 .transactions
630 .as_ref()
631 .is_none_or(|txs| txs.is_empty())
632 {
633 if pre_simulation.args.broadcast {
634 sh_warn!("No transactions to broadcast.")?;
635 }
636
637 return Ok(None);
638 }
639
640 if pre_simulation.execution_artifacts.rpc_data.missing_rpc {
642 if !shell::is_json() {
643 sh_println!("\nIf you wish to simulate on-chain transactions pass a RPC URL.")?;
644 }
645
646 return Ok(None);
647 }
648
649 let size_limits = pre_simulation.args.contract_size_limits::<FEN>(
650 &pre_simulation.script_config.config,
651 &pre_simulation.script_config.evm_opts,
652 pre_simulation.script_config.hardfork,
653 );
654 pre_simulation.args.check_contract_sizes(
655 size_limits,
656 &pre_simulation.execution_result,
657 &pre_simulation.build_data.known_contracts,
658 create2_deployer,
659 )?;
660
661 PreparedScript::Simulate(Box::new(pre_simulation))
662 };
663 Ok(Some(prepared))
664 }
665
666 async fn finish_bundle<FEN: FoundryEvmNetwork>(
668 bundled: BundledState<FEN>,
669 ) -> Result<Option<BundledState<FEN>>> {
670 if !bundled.args.should_broadcast() {
672 if bundled.sequence.sequences().iter().any(|sequence| {
673 sequence
674 .transactions
675 .iter()
676 .any(|tx| tx.tx().from() == Some(Config::DEFAULT_SENDER))
677 }) {
678 sh_warn!(
679 "Simulated with Foundry's default sender, so addresses and nonces may not match your deployer. Set your own --sender."
680 )?;
681 }
682 if !shell::is_json() {
683 if shell::verbosity() >= 4 {
684 sh_println!("\n=== Transactions that will be broadcast ===\n")?;
685 bundled.sequence.show_transactions()?;
686 }
687
688 sh_println!(
689 "\nSIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more."
690 )?;
691 }
692 return Ok(None);
693 }
694
695 if bundled.args.verify {
697 bundled.verify_preflight_check().await?;
698 }
699
700 Ok(Some(bundled))
701 }
702
703 async fn run_generic_script<FEN: FoundryEvmNetwork>(
704 self,
705 config: Config,
706 evm_opts: EvmOpts,
707 executor_builder: ExecutorBuilder<FEN>,
708 ) -> Result<()> {
709 let bundled = match self.prepare_bundled::<FEN>(config, evm_opts, executor_builder).await? {
710 Some(bundled) => bundled,
711 None => return Ok(()),
712 };
713
714 Self::broadcast_bundle(bundled).await
716 }
717
718 async fn broadcast_bundle<FEN: FoundryEvmNetwork>(bundled: BundledState<FEN>) -> Result<()> {
719 let broadcasted = bundled.wait_for_pending().await?.broadcast().await?;
720
721 if broadcasted.args.verify {
722 broadcasted.verify().await?;
723 }
724
725 Ok(())
726 }
727
728 fn maybe_load_private_key(&self) -> Result<Option<Address>> {
731 if let Some(turnkey_address) = self.wallets.turnkey_address() {
732 return Ok(Some(turnkey_address));
733 }
734
735 let maybe_sender = self
736 .wallets
737 .private_keys()?
738 .filter(|pks| pks.len() == 1)
739 .map(|pks| pks.first().unwrap().address());
740 Ok(maybe_sender)
741 }
742
743 fn get_method_and_calldata(&self, abi: &JsonAbi) -> Result<(Function, Bytes)> {
751 if let Ok(decoded) = hex::decode(&self.sig)
753 && let Some(selector) = decoded.get(..SELECTOR_LEN)
754 {
755 let func =
756 abi.functions().find(|func| selector == &func.selector()[..]).ok_or_else(|| {
757 eyre::eyre!(
758 "Function selector `{}` not found in the ABI",
759 hex::encode(selector)
760 )
761 })?;
762 return Ok((func.clone(), decoded.into()));
763 }
764
765 let func = if self.sig.contains('(') {
766 let func = get_func(&self.sig)?;
767 abi.functions()
768 .find(|&abi_func| abi_func.selector() == func.selector())
769 .wrap_err(format!("Function `{}` is not implemented in your script.", self.sig))?
770 } else {
771 let matching_functions =
772 abi.functions().filter(|func| func.name == self.sig).collect::<Vec<_>>();
773 match matching_functions.len() {
774 0 => {
775 eyre::bail!("Function `{}` not found in the ABI", self.sig);
776 }
777 1 => matching_functions[0],
778 2.. => {
779 eyre::bail!(
780 "Multiple functions with the same name `{}` found in the ABI",
781 self.sig
782 );
783 }
784 }
785 };
786 let data = encode_function_args(func, &self.args)?;
787
788 Ok((func.clone(), data.into()))
789 }
790
791 fn check_contract_sizes<N: Network>(
797 &self,
798 size_limits: ContractSizeLimits,
799 result: &ScriptResult<N>,
800 known_contracts: &ContractsByArtifact,
801 create2_deployer: Address,
802 ) -> Result<()> {
803 if self.disable_code_size_limit {
805 return Ok(());
806 }
807
808 let mut bytecodes: Vec<(String, &[u8], &[u8])> = vec![];
810
811 for (artifact, contract) in known_contracts.iter() {
813 let Some(bytecode) = contract.bytecode() else { continue };
814 let Some(deployed_bytecode) = contract.deployed_bytecode() else { continue };
815 bytecodes.push((artifact.name.clone(), bytecode, deployed_bytecode));
816 }
817
818 let create_nodes = result.traces.iter().flat_map(|(_, traces)| {
820 traces.nodes().iter().filter(|node| node.trace.kind.is_any_create())
821 });
822 let mut unknown_c = 0usize;
823 for node in create_nodes {
824 let init_code = &node.trace.data;
825 let deployed_code = &node.trace.output;
826 if !bytecodes.iter().any(|(_, b, _)| *b == init_code.as_ref()) {
827 bytecodes.push((format!("Unknown{unknown_c}"), init_code, deployed_code));
828 unknown_c += 1;
829 }
830 }
831
832 let mut prompt_user = false;
833 let max_size = size_limits.runtime;
834
835 for (data, to) in result.transactions.iter().flat_map(|txes| {
836 txes.iter().filter_map(|tx| {
837 tx.transaction
838 .input()
839 .filter(|data| data.len() > max_size)
840 .map(|data| (data, tx.transaction.to()))
841 })
842 }) {
843 let mut offset = 0;
844
845 if let Some(to) = to {
847 if to == create2_deployer {
848 offset = 32;
850 } else {
851 continue;
852 }
853 }
854
855 if let Some((name, _, deployed_code)) =
857 bytecodes.iter().find(|(_, init_code, _)| *init_code == &data[offset..])
858 {
859 let deployment_size = deployed_code.len();
860
861 if deployment_size > max_size {
862 prompt_user = self.should_broadcast();
863 sh_err!(
864 "`{name}` is above the contract size limit ({deployment_size} > {max_size})."
865 )?;
866 }
867 }
868 }
869
870 if prompt_user
872 && !self.non_interactive
873 && !Confirm::new().with_prompt("Do you wish to continue?".to_string()).interact()?
874 {
875 eyre::bail!("User canceled the script.");
876 }
877
878 Ok(())
879 }
880
881 fn contract_size_limits<FEN: FoundryEvmNetwork>(
882 &self,
883 config: &Config,
884 evm_opts: &EvmOpts,
885 hardfork: Option<FoundryHardfork>,
886 ) -> ContractSizeLimits {
887 self.evm
888 .env
889 .code_size_limit
890 .or(evm_opts.env.code_size_limit)
891 .or(config.code_size_limit)
892 .map(ContractSizeLimits::with_runtime_limit)
893 .or_else(|| {
894 evm_opts
895 .networks
896 .contract_size_limits()
897 .map(|limits| ContractSizeLimits::new(limits.runtime, limits.initcode))
898 })
899 .unwrap_or_else(|| {
900 let spec_id = hardfork
901 .and_then(SpecFor::<FEN>::from_foundry_hardfork)
902 .unwrap_or_else(|| config.evm_spec_id());
903 ContractSizeLimits::for_spec_id(spec_id.into())
904 })
905 }
906
907 const fn should_broadcast(&self) -> bool {
909 self.broadcast || self.resume || self.verify
910 }
911}
912
913impl Provider for ScriptArgs {
914 fn metadata(&self) -> Metadata {
915 Metadata::named("Script Args Provider")
916 }
917
918 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
919 let mut dict = Dict::default();
920
921 if let Some(etherscan_api_key) =
922 self.etherscan_api_key.as_ref().filter(|s| !s.trim().is_empty())
923 {
924 dict.insert(
925 "etherscan_api_key".to_string(),
926 figment::value::Value::from(etherscan_api_key.clone()),
927 );
928 }
929
930 if let Some(timeout) = self.timeout {
931 dict.insert("transaction_timeout".to_string(), timeout.into());
932 }
933
934 if let Some(preset) = self.eip1559_fee_estimate {
935 dict.insert(
936 "eip1559_fee_estimate".to_string(),
937 figment::value::Value::from(preset.to_string()),
938 );
939 }
940
941 Ok(Map::from([(Config::selected_profile(), dict)]))
942 }
943}
944
945#[derive(Serialize, Clone)]
946#[serde(bound = "")]
947pub struct ScriptResult<N: Network> {
948 pub success: bool,
949 #[serde(rename = "raw_logs")]
950 pub logs: Vec<Log>,
951 pub traces: Traces,
952 pub gas_used: u64,
953 pub labeled_addresses: AddressHashMap<String>,
954 #[serde(skip)]
955 pub debug_bytecodes: AddressHashMap<Bytes>,
956 #[serde(skip)]
957 pub transactions: Option<BroadcastableTransactions<N>>,
958 pub returned: Bytes,
959 #[serde(skip)]
960 pub exit_reason: Option<InstructionResult>,
961 pub address: Option<Address>,
962 #[serde(skip)]
963 pub breakpoints: Breakpoints,
964}
965
966impl<N: Network> Default for ScriptResult<N> {
967 fn default() -> Self {
968 Self {
969 success: Default::default(),
970 logs: Default::default(),
971 traces: Default::default(),
972 gas_used: Default::default(),
973 labeled_addresses: Default::default(),
974 debug_bytecodes: Default::default(),
975 transactions: Default::default(),
976 returned: Default::default(),
977 exit_reason: Default::default(),
978 address: Default::default(),
979 breakpoints: Default::default(),
980 }
981 }
982}
983
984impl<N: Network> ScriptResult<N> {
985 pub fn get_created_contracts(
986 &self,
987 known_contracts: &ContractsByArtifact,
988 ) -> Vec<AdditionalContract> {
989 self.traces
990 .iter()
991 .flat_map(|(_, traces)| {
992 let nodes = traces.nodes();
993 nodes.iter().filter_map(|node| {
994 if !node.trace.kind.is_any_create() || !node.trace.success {
995 return None;
996 }
997
998 let mut creator_code_addresses = Vec::new();
999 let mut parent = node.parent;
1000 while let Some(parent_idx) = parent {
1001 let ancestor = &nodes[parent_idx];
1002 if !ancestor.trace.success {
1003 return None;
1004 }
1005 if !ancestor.trace.kind.is_any_create()
1006 && !creator_code_addresses.contains(&ancestor.trace.address)
1007 {
1008 creator_code_addresses.push(ancestor.trace.address);
1009 }
1010 parent = ancestor.parent;
1011 }
1012
1013 let init_code = node.trace.data.clone();
1014 let artifact = known_contracts.find_by_creation_code_prefix(init_code.as_ref());
1015 let contract_name = artifact
1016 .or_else(|| known_contracts.find_by_creation_code(init_code.as_ref()))
1017 .map(|(id, _)| id.name.clone());
1018 let exact_artifact_match = artifact.is_some_and(|(id, _)| {
1019 !id.source.extension().is_some_and(|ext| ext == "vy")
1020 });
1021 Some(AdditionalContract {
1022 call_kind: node.trace.kind,
1023 address: node.trace.address,
1024 contract_name,
1025 exact_artifact_match,
1026 init_code,
1027 creator_code_addresses,
1028 })
1029 })
1030 })
1031 .collect()
1032 }
1033}
1034
1035#[derive(Serialize)]
1036#[serde(bound = "")]
1037struct JsonResult<'a, N: Network> {
1038 logs: Vec<String>,
1039 returns: &'a HashMap<String, NestedValue>,
1040 #[serde(flatten)]
1041 result: &'a ScriptResult<N>,
1042}
1043
1044#[derive(Clone, Debug)]
1045pub struct ScriptConfig<FEN: FoundryEvmNetwork> {
1046 pub config: Config,
1047 pub evm_opts: EvmOpts,
1048 pub executor_builder: ExecutorBuilder<FEN>,
1050 pub hardfork: Option<FoundryHardfork>,
1052 pub source_chain_id: Option<u64>,
1054 pub sender_nonce: u64,
1055 sender_nonce_override: Option<u64>,
1056 backend: Backend<FEN>,
1058 pub batch: bool,
1060 pub tempo: TempoOpts,
1062}
1063
1064impl<FEN: FoundryEvmNetwork> ScriptConfig<FEN> {
1065 pub(crate) async fn new(
1066 mut config: Config,
1067 mut evm_opts: EvmOpts,
1068 executor_builder: ExecutorBuilder<FEN>,
1069 batch: bool,
1070 tempo: TempoOpts,
1071 sender_nonce_override: Option<u64>,
1072 ) -> Result<Self> {
1073 prepare_script_source(&mut config, &mut evm_opts, None).await?;
1075 let backend = Backend::spawn(evm_opts.get_fork(
1076 &config,
1077 evm_opts.env.chain_id.unwrap_or_default(),
1078 None,
1079 ))?;
1080 let sender_nonce = if let Some(sender_nonce) = sender_nonce_override {
1081 sender_nonce
1082 } else if evm_opts.fork_url.is_some() {
1083 backend.transaction_count(evm_opts.sender).await?
1084 } else {
1085 1
1087 };
1088
1089 Ok(Self {
1090 config,
1091 evm_opts,
1092 executor_builder,
1093 hardfork: None,
1094 source_chain_id: None,
1095 sender_nonce,
1096 sender_nonce_override,
1097 backend,
1098 batch,
1099 tempo,
1100 })
1101 }
1102
1103 pub async fn update_sender(&mut self, sender: Address) -> Result<()> {
1104 self.sender_nonce = if let Some(sender_nonce) = self.sender_nonce_override {
1105 sender_nonce
1106 } else if self.evm_opts.fork_url.is_some() {
1107 self.backend.transaction_count(sender).await?
1108 } else {
1109 1
1111 };
1112 self.evm_opts.sender = sender;
1113 Ok(())
1114 }
1115
1116 async fn select_rpc(&mut self, fork_url: String) -> Result<()> {
1117 self.evm_opts.set_fork_url(fork_url);
1118 self.select_backend().await
1119 }
1120
1121 async fn select_backend(&mut self) -> Result<()> {
1122 if self.evm_opts.fork_url.is_none() {
1123 if self.backend.fork()?.is_some() {
1124 self.backend.replace_fork(None)?;
1125 }
1126 return Ok(());
1127 }
1128 if !self.prepare_requested_source().await? {
1129 return Ok(());
1130 }
1131 self.backend.replace_fork(self.evm_opts.get_fork(
1132 &self.config,
1133 self.evm_opts.env.chain_id.unwrap_or_default(),
1134 None,
1135 ))
1136 }
1137
1138 async fn prepare_requested_source(&mut self) -> Result<bool> {
1140 if let Some(fork) = self.backend.fork()? {
1141 if fork.matches_request(&self.evm_opts) {
1142 return Ok(false);
1143 }
1144 self.evm_opts.invalidate_fork_endpoint_if_source_changed(&fork);
1145 }
1146 let active_networks = self.config.networks;
1147 prepare_script_source(&mut self.config, &mut self.evm_opts, Some(active_networks)).await?;
1148 Ok(true)
1149 }
1150
1151 async fn resolve_rpc_execution_spec(&mut self, fork_url: String) -> Result<()> {
1155 self.evm_opts.set_fork_url(fork_url);
1156 if !self.prepare_requested_source().await? {
1157 self.resolve_execution_env().await?;
1158 return Ok(());
1159 }
1160 let (mut evm_env, _, fork_context) =
1161 self.evm_opts.env_with_fork_context::<_, _, TxEnvFor<FEN>>().await?;
1162 self.apply_execution_spec(&mut evm_env, fork_context);
1163 Ok(())
1164 }
1165
1166 pub(crate) async fn update_tempo_session_sender(
1167 &mut self,
1168 wallets: &MultiWalletOpts,
1169 expected_sender: Option<Address>,
1170 ) -> Result<()> {
1171 if let Some(sender) =
1172 self.tempo.session_sender_for_multi_wallet(wallets, expected_sender)?
1173 {
1174 self.update_sender(sender).await?;
1175 }
1176 Ok(())
1177 }
1178
1179 async fn get_runner_with_cheatcodes(
1180 &mut self,
1181 known_contracts: ContractsByArtifact,
1182 script_wallets: Wallets,
1183 debug: bool,
1184 target: ArtifactId,
1185 restricted: bool,
1186 ) -> Result<ScriptRunner<FEN>> {
1187 let mut runner = self
1188 ._get_runner(Some((known_contracts, script_wallets, target)), debug, restricted)
1189 .await?;
1190
1191 if self.evm_opts.networks.is_tempo() {
1194 runner.executor.evm_env_mut().cfg_env.disable_fee_charge = true;
1195 }
1196
1197 Ok(runner)
1198 }
1199
1200 async fn _get_runner(
1201 &mut self,
1202 cheats_data: Option<(ContractsByArtifact, Wallets, ArtifactId)>,
1203 debug: bool,
1204 restricted: bool,
1205 ) -> Result<ScriptRunner<FEN>> {
1206 trace!("preparing script runner");
1207 let (evm_env, mut tx_env) = self.resolve_execution_env().await?;
1208 let db = self.backend.clone();
1209
1210 let mut builder = self
1212 .executor_builder
1213 .clone()
1214 .inspectors(|stack| {
1215 stack
1216 .logs(self.config.live_logs)
1217 .trace_requirements(script_trace_requirements(&self.config, debug))
1218 .create2_deployer(self.evm_opts.create2_deployer)
1219 })
1220 .gas_limit(self.evm_opts.gas_limit())
1221 .legacy_assertions(self.config.legacy_assertions);
1222
1223 if let Some((known_contracts, script_wallets, target)) = cheats_data {
1224 builder = builder.inspectors(|stack| {
1225 let mut cheats_config = CheatsConfig::new(
1226 &self.config,
1227 self.evm_opts.clone(),
1228 Some(known_contracts),
1229 Some(target),
1230 self.batch,
1231 );
1232 if restricted {
1233 cheats_config.blocked_cheatcodes =
1234 library_deployments::rerun_unsafe_cheatcode_selectors();
1235 }
1236 stack
1237 .cheatcodes(cheats_config.into())
1238 .wallets(script_wallets)
1239 .enable_isolation(self.evm_opts.isolate)
1240 });
1241 }
1242
1243 tx_env.set_fee_token(self.tempo.fee_token);
1246
1247 let mut runner = ScriptRunner::new(
1248 builder.build(evm_env, tx_env, db, self.evm_opts.networks),
1249 self.evm_opts.clone(),
1250 )
1251 .with_debug_bytecodes(debug);
1252
1253 if self.sender_nonce_override.is_some() {
1254 runner.executor.set_nonce(self.evm_opts.sender, self.sender_nonce)?;
1255 }
1256
1257 Ok(runner)
1258 }
1259
1260 async fn resolve_execution_env(&mut self) -> Result<(EvmEnvFor<FEN>, TxEnvFor<FEN>)> {
1262 let (mut evm_env, tx_env) = self.backend.env(&self.evm_opts).await?;
1263 let fork_context = self.backend.fork()?.as_ref().map(|fork| fork.context());
1264 self.apply_execution_spec(&mut evm_env, fork_context);
1265 Ok((evm_env, tx_env))
1266 }
1267
1268 fn apply_execution_spec(
1270 &mut self,
1271 evm_env: &mut EvmEnvFor<FEN>,
1272 fork_context: Option<ForkContext>,
1273 ) {
1274 let fork_chain_id = fork_context.map(|context| context.source_chain_id);
1275 let fork_hardfork = fork_context.and_then(|context| context.hardfork);
1276 self.source_chain_id = fork_chain_id;
1277 self.hardfork = resolve_execution_spec(
1278 self.config.evm_version,
1279 self.config.hardfork,
1280 evm_env,
1281 ExecutionSpecContext::local_or_script(fork_chain_id, fork_hardfork),
1282 None,
1283 );
1284 }
1285}
1286
1287async fn prepare_script_source(
1288 config: &mut Config,
1289 evm_opts: &mut EvmOpts,
1290 active_networks: Option<NetworkConfigs>,
1291) -> Result<()> {
1292 if evm_opts.fork_url.is_none() {
1293 return Ok(());
1294 }
1295 if evm_opts.fork_endpoint.is_none() {
1296 evm_opts.infer_network_from_fork().await?;
1297 }
1298 if let Some(active_networks) = active_networks
1299 && !active_networks.supports_fork_source(&evm_opts.networks)
1300 {
1301 eyre::bail!(
1302 "fork network `{}` is incompatible with the active EVM",
1303 evm_opts.networks.execution_network()
1304 );
1305 }
1306 config.networks = evm_opts.networks;
1307 if let Some(identity) = evm_opts.fork_endpoint.clone() {
1308 let network_is_inferred = evm_opts.fork_network_is_inferred;
1309 evm_opts.expect_fork_endpoint(identity, network_is_inferred);
1310 }
1311 Ok(())
1312}
1313
1314const fn script_trace_requirements(config: &Config, debug: bool) -> TraceRequirements {
1315 TraceRequirements::none()
1316 .with_calls(true)
1317 .with_debug_recording(debug)
1318 .with_verbosity(config.tracing.verbosity)
1319 .with_decode_internal(if config.tracing.decode_internal {
1320 InternalTraceMode::Full
1321 } else {
1322 InternalTraceMode::None
1323 })
1324}
1325
1326#[cfg(test)]
1327mod tests {
1328 use super::*;
1329 use alloy_chains::NamedChain;
1330 use alloy_network::Ethereum;
1331 use alloy_primitives::address;
1332 use alloy_provider::Provider as _;
1333 use alloy_rpc_types::TransactionRequest;
1334 use alloy_signer::SignerSync as _;
1335 use anvil::{NodeConfig, spawn};
1336 use foundry_cli::opts::TEMPO_SESSION_ID_ENV;
1337 use foundry_common::tempo::{
1338 GeneratedSessionKey, SessionAuthorizationRequest, SessionEntry, TEMPO_HOME_ENV,
1339 upsert_session_entry,
1340 };
1341 use foundry_config::UnresolvedEnvVarError;
1342 use foundry_evm::{
1343 revm::context::Block as _,
1344 traces::{
1345 CallKind, CallTrace, CallTraceArena, CallTraceNode, SparsedTraceArena, TraceKind,
1346 },
1347 };
1348 use semver::Version;
1349 use std::{
1350 fs,
1351 num::NonZeroU64,
1352 sync::{Arc, LazyLock},
1353 };
1354 use tempfile::tempdir;
1355 use tokio::sync::{Mutex, MutexGuard};
1356
1357 const SESSION_PRIVATE_KEY: &str =
1358 "0x59c6995e998f97a5a004497e5da3b5d2b2b66a87f064d39c44da0b6d6e4f8ff0";
1359 const SESSION_ROOT_PRIVATE_KEY: &str =
1360 "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
1361 const SESSION_ID_HEX: &str =
1362 "0x1111111111111111111111111111111111111111111111111111111111111111";
1363 const SESSION_ROOT_ADDRESS: &str = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266";
1364 static TEMPO_HOME_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
1365
1366 fn assert_missing_orphaned_block(error: &eyre::Report) {
1367 let message = format!("{error:#}").to_ascii_lowercase();
1368 assert!(
1369 message.contains("failed to get block hash")
1370 || message.contains("not found")
1371 || message.contains("unknown block"),
1372 "unexpected orphaned block lookup error: {error:#}"
1373 );
1374 }
1375
1376 #[tokio::test(flavor = "multi_thread")]
1377 async fn script_fork_context_is_re_resolved_for_a_new_rpc() {
1378 let (api_a, handle_a) = spawn(NodeConfig::test()).await;
1379 let (api_b, handle_b) = spawn(NodeConfig::test()).await;
1380 api_a.anvil_mine(Some(U256::ONE), None).await.unwrap();
1381 api_b.anvil_mine(Some(U256::from(3)), None).await.unwrap();
1382
1383 let evm_opts = EvmOpts {
1384 fork_url: Some(handle_a.http_endpoint()),
1385 sender: handle_a.dev_accounts().next().unwrap(),
1386 ..Default::default()
1387 };
1388 let mut config = ScriptConfig::<EthEvmNetwork>::new(
1389 Config::default(),
1390 evm_opts,
1391 ExecutorBuilder::<EthEvmNetwork>::new(),
1392 false,
1393 TempoOpts::default(),
1394 None,
1395 )
1396 .await
1397 .unwrap();
1398 let first = config.backend.fork().unwrap().unwrap();
1399 assert_eq!(first.number(), 1);
1400 assert_eq!(config.evm_opts.fork_block_number, None);
1401 assert!(first.matches_request(&config.evm_opts));
1402
1403 config._get_runner(None, false, false).await.unwrap();
1404
1405 config.select_rpc(handle_b.http_endpoint()).await.unwrap();
1406 let second = config.backend.fork().unwrap().unwrap();
1407
1408 assert_eq!(second.number(), 3);
1409 assert_eq!(config.evm_opts.fork_block_number, None);
1410 assert!(second.matches_request(&config.evm_opts));
1411 assert_ne!(first, second);
1412 }
1413
1414 #[cfg(feature = "monad")]
1415 #[tokio::test(flavor = "multi_thread")]
1416 async fn script_explicit_network_is_preserved_for_a_new_rpc() {
1417 let (api_a, handle_a) = spawn(NodeConfig::test_monad()).await;
1418 let (api_b, handle_b) = spawn(NodeConfig::test_monad()).await;
1419 api_a.anvil_mine(Some(U256::ONE), None).await.unwrap();
1420 api_b.anvil_mine(Some(U256::from(3)), None).await.unwrap();
1421
1422 let evm_opts = EvmOpts {
1423 fork_url: Some(handle_a.http_endpoint()),
1424 sender: handle_a.dev_accounts().next().unwrap(),
1425 networks: NetworkConfigs::with_ethereum(),
1426 ..Default::default()
1427 };
1428 let mut config = ScriptConfig::<EthEvmNetwork>::new(
1429 Config::default(),
1430 evm_opts,
1431 ExecutorBuilder::<EthEvmNetwork>::new(),
1432 false,
1433 TempoOpts::default(),
1434 None,
1435 )
1436 .await
1437 .unwrap();
1438 assert_eq!(config.evm_opts.networks, NetworkConfigs::with_ethereum());
1439
1440 config.select_rpc(handle_b.http_endpoint()).await.unwrap();
1441 let second = config.backend.fork().unwrap().unwrap();
1442
1443 assert_eq!(second.number(), 3);
1444 assert_eq!(second.context().network_profile, NetworkConfigs::with_monad());
1445 assert_eq!(config.evm_opts.networks, NetworkConfigs::with_ethereum());
1446 }
1447
1448 #[tokio::test(flavor = "multi_thread")]
1449 async fn script_explicit_fork_block_is_preserved_for_a_new_rpc() {
1450 let (api_a, handle_a) = spawn(NodeConfig::test()).await;
1451 let (api_b, handle_b) = spawn(NodeConfig::test()).await;
1452 api_a.anvil_mine(Some(U256::from(2)), None).await.unwrap();
1453 api_b.anvil_mine(Some(U256::from(4)), None).await.unwrap();
1454
1455 let evm_opts = EvmOpts {
1456 fork_url: Some(handle_a.http_endpoint()),
1457 fork_block_number: Some(1),
1458 sender: handle_a.dev_accounts().next().unwrap(),
1459 ..Default::default()
1460 };
1461 let mut config = ScriptConfig::<EthEvmNetwork>::new(
1462 Config::default(),
1463 evm_opts,
1464 ExecutorBuilder::<EthEvmNetwork>::new(),
1465 false,
1466 TempoOpts::default(),
1467 None,
1468 )
1469 .await
1470 .unwrap();
1471 config._get_runner(None, false, false).await.unwrap();
1472
1473 config.select_rpc(handle_b.http_endpoint()).await.unwrap();
1474 let context = config.backend.fork().unwrap().unwrap();
1475
1476 assert_eq!(context.number(), 1);
1477 assert_eq!(config.evm_opts.fork_block_number, Some(1));
1478 assert!(context.matches_request(&config.evm_opts));
1479 }
1480
1481 #[tokio::test(flavor = "multi_thread")]
1482 async fn script_explicit_fork_block_can_equal_previous_latest() {
1483 let (api_a, handle_a) = spawn(NodeConfig::test()).await;
1484 let (api_b, handle_b) = spawn(NodeConfig::test()).await;
1485 api_a.anvil_mine(Some(U256::ONE), None).await.unwrap();
1486 api_b.anvil_mine(Some(U256::ONE), None).await.unwrap();
1487
1488 let evm_opts = EvmOpts {
1489 fork_url: Some(handle_a.http_endpoint()),
1490 sender: handle_a.dev_accounts().next().unwrap(),
1491 ..Default::default()
1492 };
1493 let mut config = ScriptConfig::<EthEvmNetwork>::new(
1494 Config::default(),
1495 evm_opts,
1496 ExecutorBuilder::<EthEvmNetwork>::new(),
1497 false,
1498 TempoOpts::default(),
1499 None,
1500 )
1501 .await
1502 .unwrap();
1503 let latest = config.backend.fork().unwrap().unwrap();
1504 assert_eq!(config.evm_opts.fork_block_number, None);
1505 config._get_runner(None, false, false).await.unwrap();
1506
1507 config.select_rpc(handle_b.http_endpoint()).await.unwrap();
1508 config.evm_opts.fork_block_number = Some(1);
1509 config.select_backend().await.unwrap();
1510 let explicit = config.backend.fork().unwrap().unwrap();
1511
1512 assert_eq!(explicit.number(), 1);
1513 assert_eq!(config.evm_opts.fork_block_number, Some(1));
1514 assert!(explicit.matches_request(&config.evm_opts));
1515 assert_ne!(latest, explicit);
1516
1517 config._get_runner(None, false, false).await.unwrap();
1518 }
1519
1520 #[tokio::test(flavor = "multi_thread")]
1521 async fn script_fork_cache_distinguishes_same_height_contexts() {
1522 let (api_a, handle_a) = spawn(NodeConfig::test()).await;
1523 let (api_b, handle_b) = spawn(NodeConfig::test()).await;
1524 let state_address = address!("0000000000000000000000000000000000001337");
1525 let balance_a = U256::ONE;
1526 let balance_b = U256::from(2);
1527 api_a.anvil_set_balance(state_address, balance_a).await.unwrap();
1528 api_b.anvil_set_balance(state_address, balance_b).await.unwrap();
1529 let original_prevrandao = B256::with_last_byte(0x42);
1530 api_a.anvil_set_next_block_prevrandao(original_prevrandao).await.unwrap();
1531 api_a.anvil_mine(Some(U256::ONE), None).await.unwrap();
1532 api_b.anvil_mine(Some(U256::ONE), None).await.unwrap();
1533 let url_a = handle_a.http_endpoint();
1534 let url_b = handle_b.http_endpoint();
1535 let sender = handle_a.dev_accounts().next().unwrap();
1536
1537 let evm_opts = EvmOpts { fork_url: Some(url_a.clone()), sender, ..Default::default() };
1538 let mut config = ScriptConfig::<EthEvmNetwork>::new(
1539 Config::default(),
1540 evm_opts,
1541 ExecutorBuilder::<EthEvmNetwork>::new(),
1542 false,
1543 TempoOpts::default(),
1544 None,
1545 )
1546 .await
1547 .unwrap();
1548 let mut runner_a = config._get_runner(None, false, false).await.unwrap();
1549 assert_eq!(runner_a.executor.get_balance(state_address).unwrap(), balance_a);
1550 let first_a = config.backend.fork().unwrap().unwrap();
1551 let cache_a = runner_a.executor.backend().active_fork().unwrap().backend().data();
1552 runner_a.executor.set_balance(state_address, U256::from(3)).unwrap();
1553
1554 config.select_rpc(url_b.clone()).await.unwrap();
1555 let runner_b = config._get_runner(None, false, false).await.unwrap();
1556 assert_eq!(runner_b.executor.get_balance(state_address).unwrap(), balance_b);
1557 let context_b = config.backend.fork().unwrap().unwrap().clone();
1558 assert_eq!(context_b.number(), 1);
1559
1560 config.select_rpc(url_a.clone()).await.unwrap();
1561 let reused_a = config._get_runner(None, false, false).await.unwrap();
1562 assert_eq!(reused_a.executor.get_balance(state_address).unwrap(), balance_a);
1563 assert!(Arc::ptr_eq(
1564 &cache_a,
1565 &reused_a.executor.backend().active_fork().unwrap().backend().data()
1566 ));
1567 config.select_rpc(url_b.clone()).await.unwrap();
1568
1569 let provider_a = handle_a.http_provider();
1570 provider_a
1571 .raw_request::<_, ()>("anvil_reorg".into(), (1_u64, Vec::<serde_json::Value>::new()))
1572 .await
1573 .unwrap();
1574 let replacement = provider_a.get_block_by_number(1.into()).await.unwrap().unwrap();
1575 assert_eq!(replacement.header.number, first_a.number());
1576 assert_ne!(replacement.header.hash, first_a.hash());
1577 assert_ne!(replacement.header.mix_hash, Some(original_prevrandao));
1578
1579 config.select_rpc(url_a.clone()).await.unwrap();
1580 let second_a = config.backend.fork().unwrap().unwrap();
1581 assert_eq!(second_a.number(), first_a.number());
1582 assert_eq!(second_a.hash(), replacement.header.hash);
1583 assert_ne!(second_a.hash(), first_a.hash());
1584
1585 let runner = config._get_runner(None, false, false).await.unwrap();
1586 assert_eq!(runner.executor.get_balance(state_address).unwrap(), balance_a);
1587 assert_eq!(runner.executor.evm_env().block_env.prevrandao(), replacement.header.mix_hash);
1588 }
1589
1590 #[tokio::test(flavor = "multi_thread")]
1591 async fn script_same_endpoint_keeps_backend_block() {
1592 let (api, handle) = spawn(NodeConfig::test()).await;
1593 api.anvil_mine(Some(U256::ONE), None).await.unwrap();
1594 let url = handle.http_endpoint();
1595 let evm_opts = EvmOpts {
1596 fork_url: Some(url.clone()),
1597 sender: handle.dev_accounts().next().unwrap(),
1598 ..Default::default()
1599 };
1600 let mut config = ScriptConfig::<EthEvmNetwork>::new(
1601 Config::default(),
1602 evm_opts,
1603 ExecutorBuilder::<EthEvmNetwork>::new(),
1604 false,
1605 TempoOpts::default(),
1606 None,
1607 )
1608 .await
1609 .unwrap();
1610 let resolved = config.backend.fork().unwrap().unwrap();
1611 config._get_runner(None, false, false).await.unwrap();
1612
1613 api.anvil_mine(Some(U256::ONE), None).await.unwrap();
1614 config.select_rpc(url.clone()).await.unwrap();
1615 let runner = config._get_runner(None, false, false).await.unwrap();
1616
1617 assert_eq!(config.backend.fork().unwrap().as_ref(), Some(&resolved));
1618 assert_eq!(runner.executor.evm_env().block_env.number(), U256::ONE);
1619 assert_eq!(config.evm_opts.fork_block_number, None);
1620 }
1621
1622 #[tokio::test(flavor = "multi_thread")]
1623 async fn script_fork_changed_headers_replace_cached_backend() {
1624 let (api, handle) = spawn(NodeConfig::test()).await;
1625 api.anvil_mine(Some(U256::ONE), None).await.unwrap();
1626 let url = handle.http_endpoint();
1627 let evm_opts = EvmOpts {
1628 fork_url: Some(url.clone()),
1629 sender: handle.dev_accounts().next().unwrap(),
1630 ..Default::default()
1631 };
1632 let mut config = ScriptConfig::<EthEvmNetwork>::new(
1633 Config::default(),
1634 evm_opts,
1635 ExecutorBuilder::<EthEvmNetwork>::new(),
1636 false,
1637 TempoOpts::default(),
1638 None,
1639 )
1640 .await
1641 .unwrap();
1642 config._get_runner(None, false, false).await.unwrap();
1643 let without_headers = config.backend.fork().unwrap().unwrap().clone();
1644
1645 config.evm_opts.fork_headers = Some(vec!["x-foundry-test: resolved-fork".to_string()]);
1646 config.select_backend().await.unwrap();
1647 let with_headers = config.backend.fork().unwrap().unwrap();
1648 config._get_runner(None, false, false).await.unwrap();
1649
1650 assert_ne!(without_headers, with_headers);
1651 assert!(with_headers.matches_request(&config.evm_opts));
1652 }
1653
1654 async fn assert_same_url_fork_auth_change_re_resolves(
1655 configure_initial: impl FnOnce(&mut EvmOpts),
1656 configure_changed: impl FnOnce(&mut EvmOpts),
1657 ) {
1658 let (api, handle) = spawn(NodeConfig::test()).await;
1659 let mut evm_opts = EvmOpts {
1660 fork_url: Some(handle.http_endpoint()),
1661 fork_block_number: Some(0),
1662 networks: NetworkConfigs::with_ethereum(),
1663 ..Default::default()
1664 };
1665 evm_opts.env.chain_id = Some(42);
1666 configure_initial(&mut evm_opts);
1667
1668 let mut config = ScriptConfig::<EthEvmNetwork>::new(
1669 Config::default(),
1670 evm_opts,
1671 ExecutorBuilder::<EthEvmNetwork>::new(),
1672 false,
1673 TempoOpts::default(),
1674 None,
1675 )
1676 .await
1677 .unwrap();
1678 config._get_runner(None, false, false).await.unwrap();
1679 let first = config.backend.fork().unwrap().unwrap().clone();
1680 let first_instance = first.context().instance_id.unwrap();
1681
1682 api.anvil_reset(None).await.unwrap();
1683 assert_ne!(api.instance_id(), first_instance);
1684 configure_changed(&mut config.evm_opts);
1685
1686 config.select_backend().await.unwrap();
1687 let second = config.backend.fork().unwrap().unwrap();
1688 assert_ne!(second.context().instance_id, Some(first_instance));
1689 assert_ne!(second, first);
1690 assert_eq!(config.evm_opts.fork_block_number, Some(0));
1691 assert_eq!(config.evm_opts.networks, NetworkConfigs::with_ethereum());
1692 assert_eq!(config.evm_opts.env.chain_id, Some(42));
1693
1694 config._get_runner(None, false, false).await.unwrap();
1695 }
1696
1697 #[tokio::test(flavor = "multi_thread")]
1698 async fn script_same_url_rpc_headers_change_adopts_new_fork_identity() {
1699 assert_same_url_fork_auth_change_re_resolves(
1700 |evm_opts| evm_opts.rpc_headers = Some(vec!["x-fork-source: first".to_string()]),
1701 |evm_opts| evm_opts.rpc_headers = Some(vec!["x-fork-source: second".to_string()]),
1702 )
1703 .await;
1704 }
1705
1706 #[tokio::test(flavor = "multi_thread")]
1707 async fn script_same_url_jwt_change_adopts_new_fork_identity() {
1708 const FIRST_JWT: &str = "5c43996d0d150a81f06ae452fce38120d97a4156650aec7487b3384bfe32edae";
1709 const SECOND_JWT: &str = "cabee703106087906e50f3e75a6ddbab60809f980511d1d1548d449d52220795";
1710
1711 assert_same_url_fork_auth_change_re_resolves(
1712 |evm_opts| evm_opts.rpc_jwt = Some(FIRST_JWT.to_string()),
1713 |evm_opts| evm_opts.rpc_jwt = Some(SECOND_JWT.to_string()),
1714 )
1715 .await;
1716 }
1717
1718 #[tokio::test(flavor = "multi_thread")]
1719 async fn script_runner_env_preserves_prepared_fork_after_reorg() {
1720 let (api, handle) = spawn(NodeConfig::test()).await;
1721 let prevrandao = B256::with_last_byte(0x42);
1722 api.anvil_set_next_block_prevrandao(prevrandao).await.unwrap();
1723 api.anvil_mine(Some(U256::ONE), None).await.unwrap();
1724
1725 let evm_opts = EvmOpts {
1726 fork_url: Some(handle.http_endpoint()),
1727 sender: handle.dev_accounts().next().unwrap(),
1728 ..Default::default()
1729 };
1730 let mut config = ScriptConfig::<EthEvmNetwork>::new(
1731 Config::default(),
1732 evm_opts,
1733 ExecutorBuilder::<EthEvmNetwork>::new(),
1734 false,
1735 TempoOpts::default(),
1736 None,
1737 )
1738 .await
1739 .unwrap();
1740 assert!(
1741 config
1742 .backend
1743 .can_use_create2_deployer(config.evm_opts.create2_deployer)
1744 .await
1745 .unwrap()
1746 );
1747 config._get_runner(None, false, false).await.unwrap();
1748
1749 let provider = handle.http_provider();
1750 let pinned = config.backend.fork().unwrap().unwrap();
1751 provider
1752 .raw_request::<_, ()>("anvil_reorg".into(), (1_u64, Vec::<serde_json::Value>::new()))
1753 .await
1754 .unwrap();
1755 let replacement = provider.get_block_by_number(1.into()).await.unwrap().unwrap();
1756 assert_ne!(replacement.header.hash, pinned.hash());
1757 assert_ne!(replacement.header.mix_hash, Some(prevrandao));
1758
1759 config.select_rpc(handle.http_endpoint()).await.unwrap();
1761 match config._get_runner(None, false, false).await {
1762 Ok(runner) => {
1763 assert_eq!(runner.executor.evm_env().block_env.prevrandao(), Some(prevrandao));
1764 }
1765 Err(error) => assert_missing_orphaned_block(&error),
1766 }
1767 assert_eq!(config.backend.fork().unwrap().as_ref(), Some(&pinned));
1768 }
1769
1770 #[test]
1771 fn script_trace_requirements_honor_tracing_verbosity() {
1772 let mut config = Config::default();
1773 config.tracing.verbosity = 5;
1774
1775 let tracing = script_trace_requirements(&config, false).into_config().unwrap();
1776 assert!(tracing.record_state_diff);
1777 }
1778
1779 #[test]
1780 fn created_contracts_include_successful_creator_code_ancestry() {
1781 let root = Address::repeat_byte(0x11);
1782 let implementation = Address::repeat_byte(0x22);
1783 let helper = Address::repeat_byte(0x33);
1784 let created = Address::repeat_byte(0x44);
1785 let init_code = Bytes::from_static(&[0x60, 0x00]);
1786 let mut arena = CallTraceArena::default();
1787 arena.nodes_mut()[0].trace =
1788 CallTrace { success: true, address: root, kind: CallKind::Call, ..Default::default() };
1789 arena.nodes_mut().extend([
1790 CallTraceNode {
1791 parent: Some(0),
1792 idx: 1,
1793 trace: CallTrace {
1794 success: true,
1795 address: implementation,
1796 kind: CallKind::DelegateCall,
1797 ..Default::default()
1798 },
1799 ..Default::default()
1800 },
1801 CallTraceNode {
1802 parent: Some(1),
1803 idx: 2,
1804 trace: CallTrace {
1805 success: true,
1806 address: helper,
1807 kind: CallKind::Call,
1808 ..Default::default()
1809 },
1810 ..Default::default()
1811 },
1812 CallTraceNode {
1813 parent: Some(2),
1814 idx: 3,
1815 trace: CallTrace {
1816 success: true,
1817 address: created,
1818 kind: CallKind::Create2,
1819 data: init_code.clone(),
1820 ..Default::default()
1821 },
1822 ..Default::default()
1823 },
1824 ]);
1825 let result = ScriptResult::<Ethereum> {
1826 traces: vec![(
1827 TraceKind::Execution,
1828 SparsedTraceArena {
1829 arena,
1830 ignored: Default::default(),
1831 diagnostics: Default::default(),
1832 debug_steps: None,
1833 },
1834 )],
1835 ..Default::default()
1836 };
1837
1838 let contracts = result.get_created_contracts(&ContractsByArtifact::default());
1839 assert_eq!(contracts.len(), 1);
1840 assert_eq!(contracts[0].address, created);
1841 assert_eq!(contracts[0].init_code, init_code);
1842 assert_eq!(contracts[0].creator_code_addresses, [helper, implementation, root]);
1843 }
1844
1845 #[test]
1846 fn created_contracts_exclude_creations_rolled_back_by_an_ancestor() {
1847 let mut arena = CallTraceArena::default();
1848 arena.nodes_mut()[0].trace = CallTrace { success: true, ..Default::default() };
1849 arena.nodes_mut().extend([
1850 CallTraceNode {
1851 parent: Some(0),
1852 idx: 1,
1853 trace: CallTrace { success: false, kind: CallKind::Call, ..Default::default() },
1854 ..Default::default()
1855 },
1856 CallTraceNode {
1857 parent: Some(1),
1858 idx: 2,
1859 trace: CallTrace {
1860 success: true,
1861 kind: CallKind::Create,
1862 data: Bytes::from_static(&[0x60, 0x00]),
1863 ..Default::default()
1864 },
1865 ..Default::default()
1866 },
1867 ]);
1868 let result = ScriptResult::<Ethereum> {
1869 traces: vec![(
1870 TraceKind::Execution,
1871 SparsedTraceArena {
1872 arena,
1873 ignored: Default::default(),
1874 diagnostics: Default::default(),
1875 debug_steps: None,
1876 },
1877 )],
1878 ..Default::default()
1879 };
1880
1881 assert!(result.get_created_contracts(&ContractsByArtifact::default()).is_empty());
1882 }
1883
1884 fn active_session_entry(
1885 session_id: B256,
1886 root_account: Address,
1887 chain_id: u64,
1888 ) -> SessionEntry {
1889 let foundry_wallets::WalletSigner::Local(root) =
1890 foundry_wallets::utils::create_private_key_signer(SESSION_ROOT_PRIVATE_KEY).unwrap()
1891 else {
1892 unreachable!("a raw private key always creates a local signer")
1893 };
1894 assert_eq!(root.address(), root_account);
1895 let key = GeneratedSessionKey::from_private_key(SESSION_PRIVATE_KEY).unwrap();
1896 let prepared = SessionAuthorizationRequest {
1897 session_id,
1898 root_account,
1899 chain_id,
1900 key_address: key.address(),
1901 expiry: NonZeroU64::new(u64::MAX).unwrap(),
1902 scope: vec![tempo_primitives::transaction::CallScope {
1903 target: Address::repeat_byte(0xaa),
1904 selector_rules: vec![],
1905 }],
1906 spend_limits: vec![],
1907 }
1908 .prepare(0)
1909 .unwrap();
1910 let signature = root.sign_hash_sync(&prepared.authorization.signature_hash()).unwrap();
1911 let authorization = prepared
1912 .authorization
1913 .clone()
1914 .into_signed(tempo_primitives::transaction::PrimitiveSignature::Secp256k1(signature));
1915 prepared.into_active_entry(key, &authorization).unwrap()
1916 }
1917
1918 struct TempoHomeGuard {
1919 _guard: MutexGuard<'static, ()>,
1920 }
1921
1922 impl TempoHomeGuard {
1923 async fn set(path: &std::path::Path) -> Self {
1924 let guard = TEMPO_HOME_LOCK.lock().await;
1925 unsafe {
1927 std::env::remove_var(TEMPO_SESSION_ID_ENV);
1928 std::env::set_var(TEMPO_HOME_ENV, path);
1929 }
1930 Self { _guard: guard }
1931 }
1932 }
1933
1934 impl Drop for TempoHomeGuard {
1935 fn drop(&mut self) {
1936 unsafe {
1938 std::env::remove_var(TEMPO_HOME_ENV);
1939 std::env::remove_var(TEMPO_SESSION_ID_ENV);
1940 }
1941 }
1942 }
1943
1944 fn session_root() -> Address {
1945 SESSION_ROOT_ADDRESS.parse().unwrap()
1946 }
1947
1948 #[test]
1949 fn can_parse_sig() {
1950 let sig = "0x522bb704000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfFFb92266";
1951 let args = ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--sig", sig]);
1952 assert_eq!(args.sig, sig);
1953 }
1954
1955 #[test]
1956 fn short_hex_sig_is_resolved_as_function_name() {
1957 let abi = JsonAbi::parse(["function fade()", "function run()"]).unwrap();
1958 let method = |sig: &str| {
1959 ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--sig", sig])
1960 .get_method_and_calldata(&abi)
1961 };
1962
1963 let fade = abi.function("fade").unwrap()[0].clone();
1964 let (func, calldata) = method("fade").unwrap();
1965 assert_eq!(func, fade);
1966 assert_eq!(calldata, Bytes::from(fade.selector().to_vec()));
1967
1968 for sig in ["", "0x12"] {
1969 assert_eq!(
1970 method(sig).unwrap_err().to_string(),
1971 format!("Function `{sig}` not found in the ABI")
1972 );
1973 }
1974
1975 let run = abi.function("run").unwrap()[0].clone();
1977 let (func, calldata) = method(&hex::encode(run.selector())).unwrap();
1978 assert_eq!(func, run);
1979 assert_eq!(calldata, Bytes::from(run.selector().to_vec()));
1980 }
1981
1982 #[test]
1983 fn parses_confirmations() {
1984 let args = ScriptArgs::parse_from(["foundry-cli", "Contract.sol"]);
1985 assert_eq!(args.confirmations, DEFAULT_CONFIRMATIONS);
1986
1987 let args = ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--confirmations", "6"]);
1988 assert_eq!(args.confirmations, 6);
1989
1990 let err =
1991 ScriptArgs::try_parse_from(["foundry-cli", "Contract.sol", "--confirmations", "0"])
1992 .unwrap_err();
1993 assert_eq!(err.kind(), clap::error::ErrorKind::ValueValidation);
1994 }
1995
1996 #[test]
1997 fn verify_external_requires_verify() {
1998 let err = ScriptArgs::try_parse_from(["foundry-cli", "Contract.sol", "--verify-external"])
1999 .unwrap_err();
2000 assert_eq!(err.kind(), clap::error::ErrorKind::MissingRequiredArgument);
2001
2002 let args = ScriptArgs::try_parse_from([
2003 "foundry-cli",
2004 "Contract.sol",
2005 "--broadcast",
2006 "--verify",
2007 "--verify-external",
2008 ])
2009 .unwrap();
2010 assert!(args.verify_external);
2011 }
2012
2013 #[test]
2014 fn verify_external_requires_simulation_and_online_mode() {
2015 for conflicting_arg in ["--skip-simulation", "--offline"] {
2016 let err = ScriptArgs::try_parse_from([
2017 "foundry-cli",
2018 "Contract.sol",
2019 "--broadcast",
2020 "--verify",
2021 "--verify-external",
2022 conflicting_arg,
2023 ])
2024 .unwrap_err();
2025 assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict);
2026 }
2027 }
2028
2029 #[test]
2030 fn rejects_max_sender_nonce() {
2031 let max_valid_nonce = (u64::MAX - 1).to_string();
2032 let args = ScriptArgs::try_parse_from([
2033 "foundry-cli",
2034 "Contract.sol",
2035 "--sender-nonce",
2036 &max_valid_nonce,
2037 ])
2038 .unwrap();
2039 assert_eq!(args.sender_nonce, Some(u64::MAX - 1));
2040
2041 let max_nonce = u64::MAX.to_string();
2042 let err = ScriptArgs::try_parse_from([
2043 "foundry-cli",
2044 "Contract.sol",
2045 "--sender-nonce",
2046 &max_nonce,
2047 ])
2048 .unwrap_err();
2049 assert_eq!(err.kind(), clap::error::ErrorKind::ValueValidation);
2050 }
2051
2052 #[tokio::test(flavor = "multi_thread")]
2053 async fn update_sender_fork_does_not_read_nonce_from_replacement_block() {
2054 let (_api, handle) = spawn(NodeConfig::test()).await;
2055 let accounts = handle.dev_wallets().collect::<Vec<_>>();
2056 let original_sender = accounts[0].address();
2057 let replacement_sender = accounts[1].address();
2058 let provider = handle.http_provider();
2059 let tx = TransactionRequest::default()
2060 .from(replacement_sender)
2061 .to(original_sender)
2062 .value(U256::ONE);
2063 provider.send_transaction(tx.into()).await.unwrap().get_receipt().await.unwrap();
2064
2065 let evm_opts = EvmOpts {
2066 fork_url: Some(handle.http_endpoint()),
2067 sender: original_sender,
2068 ..Default::default()
2069 };
2070 let mut config = ScriptConfig::<EthEvmNetwork>::new(
2071 Config::default(),
2072 evm_opts,
2073 ExecutorBuilder::<EthEvmNetwork>::new(),
2074 false,
2075 TempoOpts::default(),
2076 None,
2077 )
2078 .await
2079 .unwrap();
2080 let resolved = config.backend.fork().unwrap().unwrap();
2081 assert_eq!(config.evm_opts.fork_block_number, None);
2082 assert_eq!(config.sender_nonce, 0);
2083
2084 provider
2085 .raw_request::<_, ()>("anvil_reorg".into(), (1_u64, Vec::<serde_json::Value>::new()))
2086 .await
2087 .unwrap();
2088 assert_eq!(provider.get_transaction_count(replacement_sender).number(1).await.unwrap(), 0);
2089
2090 match config.update_sender(replacement_sender).await {
2091 Ok(()) => assert_eq!(config.sender_nonce, 1),
2092 Err(error) => assert_missing_orphaned_block(&error),
2093 }
2094 assert_eq!(config.backend.fork().unwrap().as_ref(), Some(&resolved));
2095 }
2096
2097 #[tokio::test(flavor = "multi_thread")]
2098 async fn sender_nonce_override_does_not_refresh_fork() {
2099 let (_api, handle) = spawn(NodeConfig::test()).await;
2100 let sender = handle.dev_accounts().next().unwrap();
2101 let evm_opts =
2102 EvmOpts { fork_url: Some(handle.http_endpoint()), sender, ..Default::default() };
2103 let mut config = ScriptConfig::<EthEvmNetwork>::new(
2104 Config::default(),
2105 evm_opts,
2106 ExecutorBuilder::<EthEvmNetwork>::new(),
2107 false,
2108 TempoOpts::default(),
2109 Some(7),
2110 )
2111 .await
2112 .unwrap();
2113
2114 config.evm_opts.set_fork_url("http://127.0.0.1:1".to_string());
2115 config.update_sender(Address::with_last_byte(0x42)).await.unwrap();
2116 assert_eq!(config.sender_nonce, 7);
2117 }
2118
2119 #[tokio::test(flavor = "multi_thread")]
2120 #[cfg(feature = "monad")]
2121 async fn script_runner_preserves_nested_fork_source_chain() {
2122 let (_origin_api, origin_handle) = spawn(
2123 NodeConfig::test_monad()
2124 .with_chain_id(Some(NamedChain::MonadTestnet as u64))
2125 .with_hardfork(Some(foundry_evm::hardforks::MonadHardfork::MonadNine.into())),
2126 )
2127 .await;
2128 let (_fork_api, fork_handle) = spawn(
2129 NodeConfig::test_monad()
2130 .with_chain_id(Some(NamedChain::Mainnet as u64))
2131 .with_no_storage_caching(true)
2132 .with_eth_rpc_url(Some(origin_handle.http_endpoint()))
2133 .with_fork_block_number(Some(0u64)),
2134 )
2135 .await;
2136 let mut evm_opts =
2137 EvmOpts { fork_url: Some(fork_handle.http_endpoint()), ..Default::default() };
2138 evm_opts.infer_network_from_fork().await.unwrap();
2139 let config = Config { networks: evm_opts.networks, ..Default::default() };
2140 let mut script = ScriptConfig::<MonadEvmNetwork>::new(
2141 config,
2142 evm_opts,
2143 ExecutorBuilder::<MonadEvmNetwork>::new(),
2144 false,
2145 TempoOpts::default(),
2146 None,
2147 )
2148 .await
2149 .unwrap();
2150
2151 let _runner = script._get_runner(None, false, false).await.unwrap();
2152
2153 assert_eq!(script.source_chain_id, Some(NamedChain::MonadTestnet as u64));
2154 assert_eq!(
2155 script.hardfork,
2156 Some(FoundryHardfork::Monad(foundry_evm::hardforks::MonadHardfork::MonadNine))
2157 );
2158 }
2159
2160 #[tokio::test(flavor = "multi_thread")]
2161 async fn tempo_runner_fee_charge_matches_execution_context() {
2162 let (_api, handle) = spawn(NodeConfig::test_tempo()).await;
2163 let networks = NetworkConfigs::with_tempo();
2164 let evm_opts = EvmOpts {
2165 fork_url: Some(handle.http_endpoint()),
2166 sender: handle.dev_accounts().next().unwrap(),
2167 networks,
2168 ..Default::default()
2169 };
2170 let config = Config { networks, ..Default::default() };
2171 let mut script = ScriptConfig::<TempoEvmNetwork>::new(
2172 config,
2173 evm_opts,
2174 ExecutorBuilder::<TempoEvmNetwork>::new(),
2175 false,
2176 TempoOpts::default(),
2177 None,
2178 )
2179 .await
2180 .unwrap();
2181
2182 let rpc_runner = script._get_runner(None, false, false).await.unwrap();
2183 assert!(!rpc_runner.executor.evm_env().cfg_env.disable_fee_charge);
2184
2185 let target = ArtifactId {
2186 path: PathBuf::from("Script.json"),
2187 name: "Script".to_string(),
2188 source: PathBuf::from("Script.sol"),
2189 version: Version::new(0, 8, 30),
2190 build_id: String::new(),
2191 profile: "default".to_string(),
2192 };
2193 let synthetic_runner = script
2194 .get_runner_with_cheatcodes(
2195 ContractsByArtifact::default(),
2196 Wallets::new(Default::default(), None),
2197 false,
2198 target,
2199 false,
2200 )
2201 .await
2202 .unwrap();
2203 assert!(synthetic_runner.executor.evm_env().cfg_env.disable_fee_charge);
2204 }
2205
2206 #[test]
2207 fn can_parse_shared_tempo_opts() {
2208 let args = ScriptArgs::parse_from([
2209 "foundry-cli",
2210 "Contract.sol",
2211 "--tempo.fee-token",
2212 "1",
2213 "--tempo.expires",
2214 "10",
2215 ]);
2216
2217 assert_eq!(
2218 args.tempo.fee_token,
2219 Some(address!("0x20C0000000000000000000000000000000000001"))
2220 );
2221 assert_eq!(args.tempo.expires, Some(10));
2222 }
2223
2224 #[test]
2225 fn can_parse_sponsor_tempo_opts() {
2226 let args = ScriptArgs::parse_from([
2227 "foundry-cli",
2228 "Contract.sol",
2229 "--tempo.sponsor",
2230 SESSION_ROOT_ADDRESS,
2231 "--tempo.sponsor-signer",
2232 "env://TEMPO_SPONSOR_PK",
2233 ]);
2234
2235 assert_eq!(args.tempo.sponsor, Some(session_root()));
2236 assert_eq!(args.tempo.sponsor_signer.as_deref(), Some("env://TEMPO_SPONSOR_PK"));
2237 }
2238
2239 #[test]
2240 fn can_parse_full_tempo_opts() {
2241 let args =
2242 ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--tempo.nonce-key", "1"]);
2243
2244 assert_eq!(args.tempo.nonce_key, Some(U256::ONE));
2245 }
2246
2247 #[test]
2248 fn can_parse_tempo_session_opt() {
2249 let args = ScriptArgs::parse_from([
2250 "foundry-cli",
2251 "Contract.sol",
2252 "--tempo.session",
2253 SESSION_ID_HEX,
2254 ]);
2255
2256 assert_eq!(args.tempo.session, Some(B256::repeat_byte(0x11)),);
2257 }
2258
2259 #[tokio::test(flavor = "multi_thread")]
2260 async fn tempo_session_sets_script_sender_to_root_account() {
2261 let temp = tempdir().unwrap();
2262 let session_id = B256::repeat_byte(0x22);
2263 let root = session_root();
2264 let chain_id = foundry_common::DEV_CHAIN_ID;
2265
2266 let _guard = TempoHomeGuard::set(temp.path()).await;
2267 upsert_session_entry(active_session_entry(session_id, root, chain_id)).unwrap();
2268
2269 let args = ScriptArgs::parse_from([
2270 "foundry-cli",
2271 "Contract.sol",
2272 "--tempo.session",
2273 &format!("{session_id:?}"),
2274 ]);
2275 let evm_opts = EvmOpts {
2276 networks: NetworkConfigs::with_tempo(),
2277 env: foundry_evm::opts::Env { chain_id: Some(chain_id), ..Default::default() },
2278 ..Default::default()
2279 };
2280
2281 let state = args
2282 .preprocess::<TempoEvmNetwork>(
2283 Config::default(),
2284 evm_opts,
2285 ExecutorBuilder::<TempoEvmNetwork>::new(),
2286 )
2287 .await
2288 .unwrap();
2289 assert_eq!(state.script_config.evm_opts.sender, root);
2290 }
2291
2292 #[tokio::test(flavor = "multi_thread")]
2293 async fn tempo_session_resume_multi_defers_session_sender_until_reexecution() {
2294 let temp = tempdir().unwrap();
2295 let session_id = B256::repeat_byte(0x55);
2296 let root = session_root();
2297 let chain_id = 4217;
2298
2299 let _guard = TempoHomeGuard::set(temp.path()).await;
2300 upsert_session_entry(active_session_entry(session_id, root, chain_id)).unwrap();
2301
2302 let args = ScriptArgs::parse_from([
2303 "foundry-cli",
2304 "Contract.sol",
2305 "--resume",
2306 "--multi",
2307 "--tempo.session",
2308 &format!("{session_id:?}"),
2309 ]);
2310 let evm_opts = EvmOpts { networks: NetworkConfigs::with_tempo(), ..Default::default() };
2311
2312 let state = args
2313 .preprocess::<TempoEvmNetwork>(
2314 Config::default(),
2315 evm_opts,
2316 ExecutorBuilder::<TempoEvmNetwork>::new(),
2317 )
2318 .await
2319 .unwrap();
2320 assert_ne!(state.script_config.evm_opts.sender, root);
2321 }
2322
2323 #[tokio::test(flavor = "multi_thread")]
2324 async fn tempo_session_resume_defers_session_sender_until_reexecution() {
2325 let temp = tempdir().unwrap();
2326 let session_id = B256::repeat_byte(0x77);
2327 let root = session_root();
2328 let chain_id = 4217;
2329
2330 let _guard = TempoHomeGuard::set(temp.path()).await;
2331 upsert_session_entry(active_session_entry(session_id, root, chain_id)).unwrap();
2332
2333 let args = ScriptArgs::parse_from([
2334 "foundry-cli",
2335 "Contract.sol",
2336 "--resume",
2337 "--tempo.session",
2338 &format!("{session_id:?}"),
2339 ]);
2340 let evm_opts = EvmOpts { networks: NetworkConfigs::with_tempo(), ..Default::default() };
2341
2342 let state = args
2343 .preprocess::<TempoEvmNetwork>(
2344 Config::default(),
2345 evm_opts,
2346 ExecutorBuilder::<TempoEvmNetwork>::new(),
2347 )
2348 .await
2349 .unwrap();
2350 assert_ne!(state.script_config.evm_opts.sender, root);
2351 }
2352
2353 #[tokio::test(flavor = "multi_thread")]
2354 async fn tempo_session_non_resume_multi_sets_sender_without_chain_validation() {
2355 let temp = tempdir().unwrap();
2356 let session_id = B256::repeat_byte(0x66);
2357 let root = session_root();
2358 let chain_id = 4217;
2359
2360 let _guard = TempoHomeGuard::set(temp.path()).await;
2361 upsert_session_entry(active_session_entry(session_id, root, chain_id)).unwrap();
2362
2363 let args = ScriptArgs::parse_from([
2364 "foundry-cli",
2365 "Contract.sol",
2366 "--multi",
2367 "--tempo.session",
2368 &format!("{session_id:?}"),
2369 ]);
2370 let evm_opts = EvmOpts { networks: NetworkConfigs::with_tempo(), ..Default::default() };
2371
2372 let state = args
2373 .preprocess::<TempoEvmNetwork>(
2374 Config::default(),
2375 evm_opts,
2376 ExecutorBuilder::<TempoEvmNetwork>::new(),
2377 )
2378 .await
2379 .unwrap();
2380 assert_eq!(state.script_config.evm_opts.sender, root);
2381 }
2382
2383 #[tokio::test(flavor = "multi_thread")]
2384 async fn tempo_session_initial_broadcast_sets_sender_without_chain_validation() {
2385 let temp = tempdir().unwrap();
2386 let session_id = B256::repeat_byte(0x88);
2387 let root = session_root();
2388 let chain_id = 4217;
2389
2390 let _guard = TempoHomeGuard::set(temp.path()).await;
2391 upsert_session_entry(active_session_entry(session_id, root, chain_id)).unwrap();
2392
2393 let args = ScriptArgs::parse_from([
2394 "foundry-cli",
2395 "Contract.sol",
2396 "--broadcast",
2397 "--tempo.session",
2398 &format!("{session_id:?}"),
2399 ]);
2400 let evm_opts = EvmOpts { networks: NetworkConfigs::with_tempo(), ..Default::default() };
2401
2402 let state = args
2403 .preprocess::<TempoEvmNetwork>(
2404 Config::default(),
2405 evm_opts,
2406 ExecutorBuilder::<TempoEvmNetwork>::new(),
2407 )
2408 .await
2409 .unwrap();
2410 assert_eq!(state.script_config.evm_opts.sender, root);
2411 }
2412
2413 #[tokio::test]
2414 async fn tempo_options_preserve_explicit_script_network() {
2415 let temp = tempdir().unwrap();
2416 let _guard = TempoHomeGuard::set(temp.path()).await;
2417 for options in [
2418 vec!["--tempo.fee-token", "0x20c0000000000000000000000000000000000000"],
2419 vec![
2420 "--tempo.session",
2421 "0x4444444444444444444444444444444444444444444444444444444444444444",
2422 ],
2423 ] {
2424 let args =
2425 ScriptArgs::parse_from(["foundry-cli", "Contract.sol"].into_iter().chain(options));
2426 for (networks, name) in [
2427 (NetworkConfigs::with_ethereum(), "ethereum"),
2428 (NetworkConfigs::with_celo(), "celo"),
2429 #[cfg(feature = "base")]
2430 (NetworkConfigs::with_base(), "base"),
2431 ] {
2432 let evm_opts = EvmOpts { networks, ..Default::default() };
2433 let err = args.resolved_evm_opts(Config::default(), evm_opts).await.unwrap_err();
2434 assert_eq!(
2435 err.to_string(),
2436 format!("Tempo transaction options conflict with configured network `{name}`")
2437 );
2438 }
2439 }
2440 }
2441
2442 #[tokio::test]
2443 async fn tempo_session_env_selects_tempo_network() {
2444 let temp = tempdir().unwrap();
2445 let _guard = TempoHomeGuard::set(temp.path()).await;
2446 let session_id = B256::repeat_byte(0x44);
2447 unsafe { std::env::set_var(TEMPO_SESSION_ID_ENV, format!("{session_id:?}")) };
2449
2450 let args = ScriptArgs::parse_from(["foundry-cli", "Contract.sol"]);
2451 let (config, evm_opts) = args.load_config_and_evm_opts().unwrap();
2452 let (_, evm_opts) = args.resolved_evm_opts(config, evm_opts).await.unwrap();
2453
2454 assert!(evm_opts.networks.is_tempo());
2455 }
2456
2457 #[tokio::test]
2458 async fn tempo_session_rejects_explicit_script_wallet_signer() {
2459 let temp = tempdir().unwrap();
2460 let session_id = B256::repeat_byte(0x33);
2461 let root = session_root();
2462 let chain_id = foundry_common::DEV_CHAIN_ID;
2463
2464 let _guard = TempoHomeGuard::set(temp.path()).await;
2465 upsert_session_entry(active_session_entry(session_id, root, chain_id)).unwrap();
2466
2467 let args = ScriptArgs::parse_from([
2468 "foundry-cli",
2469 "Contract.sol",
2470 "--tempo.session",
2471 &format!("{session_id:?}"),
2472 "--private-key",
2473 SESSION_PRIVATE_KEY,
2474 ]);
2475 let evm_opts = EvmOpts {
2476 networks: NetworkConfigs::with_tempo(),
2477 env: foundry_evm::opts::Env { chain_id: Some(chain_id), ..Default::default() },
2478 ..Default::default()
2479 };
2480
2481 let err = match args
2482 .preprocess::<TempoEvmNetwork>(
2483 Config::default(),
2484 evm_opts,
2485 ExecutorBuilder::<TempoEvmNetwork>::new(),
2486 )
2487 .await
2488 {
2489 Ok(_) => panic!("expected --tempo.session with --private-key to fail"),
2490 Err(err) => err,
2491 };
2492 assert!(err.to_string().contains("explicit wallet signer"), "{err}");
2493 }
2494
2495 #[test]
2496 fn can_parse_unlocked() {
2497 let args = ScriptArgs::parse_from([
2498 "foundry-cli",
2499 "Contract.sol",
2500 "--sender",
2501 "0x4e59b44847b379578588920ca78fbf26c0b4956c",
2502 "--unlocked",
2503 ]);
2504 assert!(args.unlocked);
2505
2506 let key = U256::ZERO;
2507 let args = ScriptArgs::try_parse_from([
2508 "foundry-cli",
2509 "Contract.sol",
2510 "--sender",
2511 "0x4e59b44847b379578588920ca78fbf26c0b4956c",
2512 "--unlocked",
2513 "--private-key",
2514 &key.to_string(),
2515 ]);
2516 assert!(args.is_err());
2517 }
2518
2519 #[test]
2520 fn can_merge_script_config() {
2521 let args = ScriptArgs::parse_from([
2522 "foundry-cli",
2523 "Contract.sol",
2524 "--etherscan-api-key",
2525 "goerli",
2526 ]);
2527 let config = args.load_config().unwrap();
2528 assert_eq!(config.etherscan_api_key, Some("goerli".to_string()));
2529 }
2530
2531 #[test]
2532 fn can_disable_code_size_limit() {
2533 let args =
2534 ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--disable-code-size-limit"]);
2535 assert!(args.disable_code_size_limit);
2536
2537 let result = ScriptResult::<Ethereum>::default();
2538 let contracts = ContractsByArtifact::default();
2539 let create = Address::ZERO;
2540 assert!(
2541 args.check_contract_sizes(ContractSizeLimits::default(), &result, &contracts, create)
2542 .is_ok()
2543 );
2544 }
2545
2546 #[test]
2547 fn can_parse_verifier_url() {
2548 let args = ScriptArgs::parse_from([
2549 "foundry-cli",
2550 "script",
2551 "script/Test.s.sol:TestScript",
2552 "--fork-url",
2553 "http://localhost:8545",
2554 "--verifier-url",
2555 "http://localhost:3000/api/verify",
2556 "--etherscan-api-key",
2557 "blacksmith",
2558 "--broadcast",
2559 "--verify",
2560 "-vvvvv",
2561 ]);
2562 assert_eq!(
2563 args.verifier.verifier_url,
2564 Some("http://localhost:3000/api/verify".to_string())
2565 );
2566 }
2567
2568 #[test]
2569 fn can_extract_code_size_limit() {
2570 let args = ScriptArgs::parse_from([
2571 "foundry-cli",
2572 "script",
2573 "script/Test.s.sol:TestScript",
2574 "--fork-url",
2575 "http://localhost:8545",
2576 "--broadcast",
2577 "--code-size-limit",
2578 "50000",
2579 ]);
2580 assert_eq!(args.evm.env.code_size_limit, Some(50000));
2581 }
2582
2583 #[test]
2586 fn cli_code_size_limit_is_honoured_by_check() {
2587 let args = ScriptArgs::parse_from([
2588 "foundry-cli",
2589 "script",
2590 "script/Test.s.sol:TestScript",
2591 "--code-size-limit",
2592 "2147483647",
2593 ]);
2594 assert_eq!(args.evm.env.code_size_limit, Some(2147483647));
2597 }
2598
2599 #[test]
2600 #[cfg(feature = "monad")]
2601 fn contract_size_limits_use_resolved_monad_network() {
2602 let args =
2603 ScriptArgs::parse_from(["foundry-cli", "script", "script/Test.s.sol:TestScript"]);
2604 let evm_opts = EvmOpts { networks: NetworkConfigs::with_monad(), ..Default::default() };
2605
2606 let limits =
2607 args.contract_size_limits::<MonadEvmNetwork>(&Config::default(), &evm_opts, None);
2608
2609 assert!(limits.runtime > ContractSizeLimits::default().runtime);
2610 assert!(limits.initcode > ContractSizeLimits::default().initcode);
2611 }
2612
2613 #[test]
2614 #[cfg(feature = "monad")]
2615 fn contract_size_limits_prefer_cli_then_config_over_network() {
2616 let args = ScriptArgs::parse_from([
2617 "foundry-cli",
2618 "script",
2619 "script/Test.s.sol:TestScript",
2620 "--code-size-limit",
2621 "64",
2622 ]);
2623 let mut config = Config { code_size_limit: Some(128), ..Default::default() };
2624 let evm_opts = EvmOpts { networks: NetworkConfigs::with_monad(), ..Default::default() };
2625 assert_eq!(
2626 args.contract_size_limits::<MonadEvmNetwork>(&config, &evm_opts, None),
2627 ContractSizeLimits::with_runtime_limit(64)
2628 );
2629
2630 let args =
2631 ScriptArgs::parse_from(["foundry-cli", "script", "script/Test.s.sol:TestScript"]);
2632 config.code_size_limit = Some(128);
2633 assert_eq!(
2634 args.contract_size_limits::<MonadEvmNetwork>(&config, &evm_opts, None),
2635 ContractSizeLimits::with_runtime_limit(128)
2636 );
2637 }
2638
2639 #[test]
2640 fn can_extract_script_etherscan_key() {
2641 let temp = tempdir().unwrap();
2642 let root = temp.path();
2643
2644 let config = r#"
2645 [profile.default]
2646 etherscan_api_key = "amoy"
2647
2648 [etherscan]
2649 amoy = { key = "https://etherscan-amoy.com/" }
2650 "#;
2651
2652 let toml_file = root.join(Config::FILE_NAME);
2653 fs::write(toml_file, config).unwrap();
2654 let args = ScriptArgs::parse_from([
2655 "foundry-cli",
2656 "Contract.sol",
2657 "--etherscan-api-key",
2658 "amoy",
2659 "--root",
2660 root.as_os_str().to_str().unwrap(),
2661 ]);
2662
2663 let config = args.load_config().unwrap();
2664 let amoy = config.get_etherscan_api_key(Some(NamedChain::PolygonAmoy.into()));
2665 assert_eq!(amoy, Some("https://etherscan-amoy.com/".to_string()));
2666 }
2667
2668 #[test]
2669 fn can_extract_script_rpc_alias() {
2670 let temp = tempdir().unwrap();
2671 let root = temp.path();
2672
2673 let config = r#"
2674 [profile.default]
2675
2676 [rpc_endpoints]
2677 polygonAmoy = "https://polygon-amoy.g.alchemy.com/v2/${_CAN_EXTRACT_RPC_ALIAS}"
2678 "#;
2679
2680 let toml_file = root.join(Config::FILE_NAME);
2681 fs::write(toml_file, config).unwrap();
2682 let args = ScriptArgs::parse_from([
2683 "foundry-cli",
2684 "DeployV1",
2685 "--rpc-url",
2686 "polygonAmoy",
2687 "--root",
2688 root.as_os_str().to_str().unwrap(),
2689 ]);
2690
2691 let err = args.load_config_and_evm_opts().unwrap_err();
2692
2693 assert!(err.downcast::<UnresolvedEnvVarError>().is_ok());
2694
2695 unsafe {
2696 std::env::set_var("_CAN_EXTRACT_RPC_ALIAS", "123456");
2697 }
2698 let (config, evm_opts) = args.load_config_and_evm_opts().unwrap();
2699 assert_eq!(config.eth_rpc_url, Some("polygonAmoy".to_string()));
2700 assert_eq!(
2701 evm_opts.fork_url,
2702 Some("https://polygon-amoy.g.alchemy.com/v2/123456".to_string())
2703 );
2704 }
2705
2706 #[test]
2707 fn can_extract_script_rpc_and_etherscan_alias() {
2708 let temp = tempdir().unwrap();
2709 let root = temp.path();
2710
2711 let config = r#"
2712 [profile.default]
2713
2714 [rpc_endpoints]
2715 amoy = "https://polygon-amoy.g.alchemy.com/v2/${_EXTRACT_RPC_ALIAS}"
2716
2717 [etherscan]
2718 amoy = { key = "${_ETHERSCAN_API_KEY}", chain = 80002, url = "https://amoy.polygonscan.com/" }
2719 "#;
2720
2721 let toml_file = root.join(Config::FILE_NAME);
2722 fs::write(toml_file, config).unwrap();
2723 let args = ScriptArgs::parse_from([
2724 "foundry-cli",
2725 "DeployV1",
2726 "--rpc-url",
2727 "amoy",
2728 "--etherscan-api-key",
2729 "amoy",
2730 "--root",
2731 root.as_os_str().to_str().unwrap(),
2732 ]);
2733 let err = args.load_config_and_evm_opts().unwrap_err();
2734
2735 assert!(err.downcast::<UnresolvedEnvVarError>().is_ok());
2736
2737 unsafe {
2738 std::env::set_var("_EXTRACT_RPC_ALIAS", "123456");
2739 }
2740 unsafe {
2741 std::env::set_var("_ETHERSCAN_API_KEY", "etherscan_api_key");
2742 }
2743 let (config, evm_opts) = args.load_config_and_evm_opts().unwrap();
2744 assert_eq!(config.eth_rpc_url, Some("amoy".to_string()));
2745 assert_eq!(
2746 evm_opts.fork_url,
2747 Some("https://polygon-amoy.g.alchemy.com/v2/123456".to_string())
2748 );
2749 let etherscan = config.get_etherscan_api_key(Some(80002u64.into()));
2750 assert_eq!(etherscan, Some("etherscan_api_key".to_string()));
2751 let etherscan = config.get_etherscan_api_key(None);
2752 assert_eq!(etherscan, Some("etherscan_api_key".to_string()));
2753 }
2754
2755 #[test]
2756 fn can_extract_script_rpc_and_sole_etherscan_alias() {
2757 let temp = tempdir().unwrap();
2758 let root = temp.path();
2759
2760 let config = r#"
2761 [profile.default]
2762
2763 [rpc_endpoints]
2764 amoy = "https://polygon-amoy.g.alchemy.com/v2/${_SOLE_EXTRACT_RPC_ALIAS}"
2765
2766 [etherscan]
2767 amoy = { key = "${_SOLE_ETHERSCAN_API_KEY}" }
2768 "#;
2769
2770 let toml_file = root.join(Config::FILE_NAME);
2771 fs::write(toml_file, config).unwrap();
2772 let args = ScriptArgs::parse_from([
2773 "foundry-cli",
2774 "DeployV1",
2775 "--rpc-url",
2776 "amoy",
2777 "--root",
2778 root.as_os_str().to_str().unwrap(),
2779 ]);
2780 let err = args.load_config_and_evm_opts().unwrap_err();
2781
2782 assert!(err.downcast::<UnresolvedEnvVarError>().is_ok());
2783
2784 unsafe {
2785 std::env::set_var("_SOLE_EXTRACT_RPC_ALIAS", "123456");
2786 }
2787 unsafe {
2788 std::env::set_var("_SOLE_ETHERSCAN_API_KEY", "etherscan_api_key");
2789 }
2790 let (config, evm_opts) = args.load_config_and_evm_opts().unwrap();
2791 assert_eq!(
2792 evm_opts.fork_url,
2793 Some("https://polygon-amoy.g.alchemy.com/v2/123456".to_string())
2794 );
2795 let etherscan = config.get_etherscan_api_key(Some(80002u64.into()));
2796 assert_eq!(etherscan, Some("etherscan_api_key".to_string()));
2797 let etherscan = config.get_etherscan_api_key(None);
2798 assert_eq!(etherscan, Some("etherscan_api_key".to_string()));
2799 }
2800
2801 #[test]
2803 fn test_5923() {
2804 let args =
2805 ScriptArgs::parse_from(["foundry-cli", "DeployV1", "--priority-gas-price", "100"]);
2806 assert!(args.priority_gas_price.is_some());
2807 }
2808
2809 #[test]
2810 fn test_eip1559_fee_estimate() {
2811 let args = ScriptArgs::parse_from(["foundry-cli", "DeployV1"]);
2813 assert!(args.eip1559_fee_estimate.is_none());
2814
2815 let args = ScriptArgs::parse_from(["foundry-cli", "DeployV1", "--estimate", "aggressive"]);
2816 assert_eq!(args.eip1559_fee_estimate, Some(Eip1559FeeEstimatePreset::Aggressive));
2817 }
2818
2819 #[test]
2821 fn test_5910() {
2822 let args = ScriptArgs::parse_from([
2823 "foundry-cli",
2824 "--broadcast",
2825 "--with-gas-price",
2826 "0",
2827 "SolveTutorial",
2828 ]);
2829 assert!(args.with_gas_price.unwrap().is_zero());
2830 }
2831
2832 #[test]
2833 fn test_priority_gas_price_cannot_exceed_gas_price() {
2834 let args = ScriptArgs::parse_from([
2835 "foundry-cli",
2836 "--broadcast",
2837 "--with-gas-price",
2838 "100",
2839 "--priority-gas-price",
2840 "200",
2841 "Script",
2842 ]);
2843 assert!(args.priority_gas_price.unwrap() > args.with_gas_price.unwrap());
2845 }
2846
2847 #[tokio::test(flavor = "multi_thread")]
2848 async fn explicit_sender_takes_precedence_over_sole_private_key() {
2849 let temp = tempdir().unwrap();
2850 let _guard = TempoHomeGuard::set(temp.path()).await;
2851
2852 let private_key = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
2853 let key_address = address!("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266");
2854 let sender = address!("0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
2855
2856 let args = ScriptArgs::parse_from([
2857 "foundry-cli",
2858 "Contract.sol",
2859 "--private-key",
2860 private_key,
2861 "--sender",
2862 &sender.to_string(),
2863 ]);
2864 let state = args
2865 .preprocess::<EthEvmNetwork>(
2866 Config::default(),
2867 EvmOpts { sender, ..Default::default() },
2868 ExecutorBuilder::<EthEvmNetwork>::new(),
2869 )
2870 .await
2871 .unwrap();
2872 assert_eq!(state.script_config.evm_opts.sender, sender);
2873
2874 let args =
2875 ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--private-key", private_key]);
2876 let state = args
2877 .preprocess::<EthEvmNetwork>(
2878 Config::default(),
2879 EvmOpts::default(),
2880 ExecutorBuilder::<EthEvmNetwork>::new(),
2881 )
2882 .await
2883 .unwrap();
2884 assert_eq!(state.script_config.evm_opts.sender, key_address);
2885 }
2886}