1#![cfg_attr(not(test), warn(unused_crate_dependencies))]
6#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
7
8#[macro_use]
9extern crate foundry_common;
10
11#[macro_use]
12extern crate tracing;
13
14use crate::runner::ScriptRunner;
15use alloy_json_abi::{Function, JsonAbi};
16use alloy_primitives::{
17 hex,
18 map::{AddressHashMap, HashMap},
19 Address, Bytes, Log, TxKind, U256,
20};
21use alloy_signer::Signer;
22use broadcast::next_nonce;
23use build::PreprocessedState;
24use clap::{Parser, ValueHint};
25use dialoguer::Confirm;
26use eyre::{ContextCompat, Result};
27use forge_script_sequence::{AdditionalContract, NestedValue};
28use forge_verify::{RetryArgs, VerifierArgs};
29use foundry_block_explorers::EtherscanApiVersion;
30use foundry_cli::{
31 opts::{BuildOpts, GlobalArgs},
32 utils::LoadConfig,
33};
34use foundry_common::{
35 abi::{encode_function_args, get_func},
36 evm::{Breakpoints, EvmArgs},
37 shell, ContractsByArtifact, CONTRACT_MAX_SIZE, SELECTOR_LEN,
38};
39use foundry_compilers::ArtifactId;
40use foundry_config::{
41 figment,
42 figment::{
43 value::{Dict, Map},
44 Metadata, Profile, Provider,
45 },
46 Config,
47};
48use foundry_evm::{
49 backend::Backend,
50 executors::ExecutorBuilder,
51 inspectors::{
52 cheatcodes::{BroadcastableTransactions, Wallets},
53 CheatsConfig,
54 },
55 opts::EvmOpts,
56 traces::{TraceMode, Traces},
57};
58use foundry_wallets::MultiWalletOpts;
59use serde::Serialize;
60use std::path::PathBuf;
61
62mod broadcast;
63mod build;
64mod execute;
65mod multi_sequence;
66mod progress;
67mod providers;
68mod receipts;
69mod runner;
70mod sequence;
71mod simulate;
72mod transaction;
73mod verify;
74
75foundry_config::merge_impl_figment_convert!(ScriptArgs, build, evm);
77
78#[derive(Clone, Debug, Default, Parser)]
80pub struct ScriptArgs {
81 #[command(flatten)]
83 pub global: GlobalArgs,
84
85 #[arg(value_hint = ValueHint::FilePath)]
90 pub path: String,
91
92 pub args: Vec<String>,
94
95 #[arg(long, visible_alias = "tc", value_name = "CONTRACT_NAME")]
97 pub target_contract: Option<String>,
98
99 #[arg(long, short, default_value = "run()")]
101 pub sig: String,
102
103 #[arg(
105 long,
106 env = "ETH_PRIORITY_GAS_PRICE",
107 value_parser = foundry_cli::utils::parse_ether_value,
108 value_name = "PRICE"
109 )]
110 pub priority_gas_price: Option<U256>,
111
112 #[arg(long)]
116 pub legacy: bool,
117
118 #[arg(long)]
120 pub broadcast: bool,
121
122 #[arg(long, default_value = "100")]
126 pub batch_size: usize,
127
128 #[arg(long)]
130 pub skip_simulation: bool,
131
132 #[arg(long, short, default_value = "130")]
134 pub gas_estimate_multiplier: u64,
135
136 #[arg(
138 long,
139 conflicts_with_all = &["private_key", "private_keys", "froms", "ledger", "trezor", "aws"],
140 )]
141 pub unlocked: bool,
142
143 #[arg(long)]
150 pub resume: bool,
151
152 #[arg(long)]
154 pub multi: bool,
155
156 #[arg(long)]
160 pub debug: bool,
161
162 #[arg(
164 long,
165 requires = "debug",
166 value_hint = ValueHint::FilePath,
167 value_name = "PATH"
168 )]
169 pub dump: Option<PathBuf>,
170
171 #[arg(long)]
174 pub slow: bool,
175
176 #[arg(long)]
180 pub non_interactive: bool,
181
182 #[arg(long)]
184 pub disable_code_size_limit: bool,
185
186 #[arg(long, env = "ETHERSCAN_API_KEY", value_name = "KEY")]
188 pub etherscan_api_key: Option<String>,
189
190 #[arg(long, env = "ETHERSCAN_API_VERSION", value_name = "VERSION")]
192 pub etherscan_api_version: Option<EtherscanApiVersion>,
193
194 #[arg(long)]
196 pub verify: bool,
197
198 #[arg(
203 long,
204 env = "ETH_GAS_PRICE",
205 value_parser = foundry_cli::utils::parse_ether_value,
206 value_name = "PRICE",
207 )]
208 pub with_gas_price: Option<U256>,
209
210 #[arg(long, env = "ETH_TIMEOUT")]
212 pub timeout: Option<u64>,
213
214 #[command(flatten)]
215 pub build: BuildOpts,
216
217 #[command(flatten)]
218 pub wallets: MultiWalletOpts,
219
220 #[command(flatten)]
221 pub evm: EvmArgs,
222
223 #[command(flatten)]
224 pub verifier: VerifierArgs,
225
226 #[command(flatten)]
227 pub retry: RetryArgs,
228}
229
230impl ScriptArgs {
231 pub async fn preprocess(self) -> Result<PreprocessedState> {
232 let script_wallets = Wallets::new(self.wallets.get_multi_wallet().await?, self.evm.sender);
233
234 let (config, mut evm_opts) = self.load_config_and_evm_opts()?;
235
236 if let Some(sender) = self.maybe_load_private_key()? {
237 evm_opts.sender = sender;
238 }
239
240 let script_config = ScriptConfig::new(config, evm_opts).await?;
241
242 Ok(PreprocessedState { args: self, script_config, script_wallets })
243 }
244
245 pub async fn run_script(self) -> Result<()> {
247 trace!(target: "script", "executing script command");
248
249 let state = self.preprocess().await?;
250 let create2_deployer = state.script_config.evm_opts.create2_deployer;
251 let compiled = state.compile()?;
252
253 let bundled = if compiled.args.resume || (compiled.args.verify && !compiled.args.broadcast)
256 {
257 compiled.resume().await?
258 } else {
259 let pre_simulation = compiled
261 .link()
262 .await?
263 .prepare_execution()
264 .await?
265 .execute()
266 .await?
267 .prepare_simulation()
268 .await?;
269
270 if pre_simulation.args.debug {
271 return match pre_simulation.args.dump.clone() {
272 Some(path) => pre_simulation.dump_debugger(&path),
273 None => pre_simulation.run_debugger(),
274 };
275 }
276
277 if shell::is_json() {
278 pre_simulation.show_json().await?;
279 } else {
280 pre_simulation.show_traces().await?;
281 }
282
283 if pre_simulation
286 .execution_result
287 .transactions
288 .as_ref()
289 .is_none_or(|txs| txs.is_empty())
290 {
291 if pre_simulation.args.broadcast {
292 sh_warn!("No transactions to broadcast.")?;
293 }
294
295 return Ok(());
296 }
297
298 if pre_simulation.execution_artifacts.rpc_data.missing_rpc {
300 if !shell::is_json() {
301 sh_println!("\nIf you wish to simulate on-chain transactions pass a RPC URL.")?;
302 }
303
304 return Ok(());
305 }
306
307 pre_simulation.args.check_contract_sizes(
308 &pre_simulation.execution_result,
309 &pre_simulation.build_data.known_contracts,
310 create2_deployer,
311 )?;
312
313 pre_simulation.fill_metadata().await?.bundle().await?
314 };
315
316 if !bundled.args.should_broadcast() {
318 if !shell::is_json() {
319 if shell::verbosity() >= 4 {
320 sh_println!("\n=== Transactions that will be broadcast ===\n")?;
321 bundled.sequence.show_transactions()?;
322 }
323
324 sh_println!("\nSIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more.")?;
325 }
326 return Ok(());
327 }
328
329 if bundled.args.verify {
331 bundled.verify_preflight_check()?;
332 }
333
334 let broadcasted = bundled.wait_for_pending().await?.broadcast().await?;
336
337 if broadcasted.args.verify {
338 broadcasted.verify().await?;
339 }
340
341 Ok(())
342 }
343
344 fn maybe_load_private_key(&self) -> Result<Option<Address>> {
347 let maybe_sender = self
348 .wallets
349 .private_keys()?
350 .filter(|pks| pks.len() == 1)
351 .map(|pks| pks.first().unwrap().address());
352 Ok(maybe_sender)
353 }
354
355 fn get_method_and_calldata(&self, abi: &JsonAbi) -> Result<(Function, Bytes)> {
363 if let Ok(decoded) = hex::decode(&self.sig) {
364 let selector = &decoded[..SELECTOR_LEN];
365 let func =
366 abi.functions().find(|func| selector == &func.selector()[..]).ok_or_else(|| {
367 eyre::eyre!(
368 "Function selector `{}` not found in the ABI",
369 hex::encode(selector)
370 )
371 })?;
372 return Ok((func.clone(), decoded.into()));
373 }
374
375 let func = if self.sig.contains('(') {
376 let func = get_func(&self.sig)?;
377 abi.functions()
378 .find(|&abi_func| abi_func.selector() == func.selector())
379 .wrap_err(format!("Function `{}` is not implemented in your script.", self.sig))?
380 } else {
381 let matching_functions =
382 abi.functions().filter(|func| func.name == self.sig).collect::<Vec<_>>();
383 match matching_functions.len() {
384 0 => eyre::bail!("Function `{}` not found in the ABI", self.sig),
385 1 => matching_functions[0],
386 2.. => eyre::bail!(
387 "Multiple functions with the same name `{}` found in the ABI",
388 self.sig
389 ),
390 }
391 };
392 let data = encode_function_args(func, &self.args)?;
393
394 Ok((func.clone(), data.into()))
395 }
396
397 fn check_contract_sizes(
403 &self,
404 result: &ScriptResult,
405 known_contracts: &ContractsByArtifact,
406 create2_deployer: Address,
407 ) -> Result<()> {
408 if self.disable_code_size_limit {
410 return Ok(())
411 }
412
413 let mut bytecodes: Vec<(String, &[u8], &[u8])> = vec![];
415
416 for (artifact, contract) in known_contracts.iter() {
418 let Some(bytecode) = contract.bytecode() else { continue };
419 let Some(deployed_bytecode) = contract.deployed_bytecode() else { continue };
420 bytecodes.push((artifact.name.clone(), bytecode, deployed_bytecode));
421 }
422
423 let create_nodes = result.traces.iter().flat_map(|(_, traces)| {
425 traces.nodes().iter().filter(|node| node.trace.kind.is_any_create())
426 });
427 let mut unknown_c = 0usize;
428 for node in create_nodes {
429 let init_code = &node.trace.data;
430 let deployed_code = &node.trace.output;
431 if !bytecodes.iter().any(|(_, b, _)| *b == init_code.as_ref()) {
432 bytecodes.push((format!("Unknown{unknown_c}"), init_code, deployed_code));
433 unknown_c += 1;
434 }
435 continue;
436 }
437
438 let mut prompt_user = false;
439 let max_size = match self.evm.env.code_size_limit {
440 Some(size) => size,
441 None => CONTRACT_MAX_SIZE,
442 };
443
444 for (data, to) in result.transactions.iter().flat_map(|txes| {
445 txes.iter().filter_map(|tx| {
446 tx.transaction
447 .input()
448 .filter(|data| data.len() > max_size)
449 .map(|data| (data, tx.transaction.to()))
450 })
451 }) {
452 let mut offset = 0;
453
454 if let Some(TxKind::Call(to)) = to {
456 if to == create2_deployer {
457 offset = 32;
459 } else {
460 continue;
461 }
462 } else if let Some(TxKind::Create) = to {
463 }
465
466 if let Some((name, _, deployed_code)) =
468 bytecodes.iter().find(|(_, init_code, _)| *init_code == &data[offset..])
469 {
470 let deployment_size = deployed_code.len();
471
472 if deployment_size > max_size {
473 prompt_user = self.should_broadcast();
474 sh_err!(
475 "`{name}` is above the contract size limit ({deployment_size} > {max_size})."
476 )?;
477 }
478 }
479 }
480
481 if prompt_user &&
483 !self.non_interactive &&
484 !Confirm::new().with_prompt("Do you wish to continue?".to_string()).interact()?
485 {
486 eyre::bail!("User canceled the script.");
487 }
488
489 Ok(())
490 }
491
492 fn should_broadcast(&self) -> bool {
494 self.broadcast || self.resume
495 }
496}
497
498impl Provider for ScriptArgs {
499 fn metadata(&self) -> Metadata {
500 Metadata::named("Script Args Provider")
501 }
502
503 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
504 let mut dict = Dict::default();
505 if let Some(ref etherscan_api_key) =
506 self.etherscan_api_key.as_ref().filter(|s| !s.trim().is_empty())
507 {
508 dict.insert(
509 "etherscan_api_key".to_string(),
510 figment::value::Value::from(etherscan_api_key.to_string()),
511 );
512 }
513 if let Some(api_version) = &self.etherscan_api_version {
514 dict.insert("etherscan_api_version".to_string(), api_version.to_string().into());
515 }
516 if let Some(timeout) = self.timeout {
517 dict.insert("transaction_timeout".to_string(), timeout.into());
518 }
519 Ok(Map::from([(Config::selected_profile(), dict)]))
520 }
521}
522
523#[derive(Default, Serialize, Clone)]
524pub struct ScriptResult {
525 pub success: bool,
526 #[serde(rename = "raw_logs")]
527 pub logs: Vec<Log>,
528 pub traces: Traces,
529 pub gas_used: u64,
530 pub labeled_addresses: AddressHashMap<String>,
531 #[serde(skip)]
532 pub transactions: Option<BroadcastableTransactions>,
533 pub returned: Bytes,
534 pub address: Option<Address>,
535 #[serde(skip)]
536 pub breakpoints: Breakpoints,
537}
538
539impl ScriptResult {
540 pub fn get_created_contracts(&self) -> Vec<AdditionalContract> {
541 self.traces
542 .iter()
543 .flat_map(|(_, traces)| {
544 traces.nodes().iter().filter_map(|node| {
545 if node.trace.kind.is_any_create() {
546 return Some(AdditionalContract {
547 opcode: node.trace.kind,
548 address: node.trace.address,
549 init_code: node.trace.data.clone(),
550 });
551 }
552 None
553 })
554 })
555 .collect()
556 }
557}
558
559#[derive(Serialize)]
560struct JsonResult<'a> {
561 logs: Vec<String>,
562 returns: &'a HashMap<String, NestedValue>,
563 #[serde(flatten)]
564 result: &'a ScriptResult,
565}
566
567#[derive(Clone, Debug)]
568pub struct ScriptConfig {
569 pub config: Config,
570 pub evm_opts: EvmOpts,
571 pub sender_nonce: u64,
572 pub backends: HashMap<String, Backend>,
574}
575
576impl ScriptConfig {
577 pub async fn new(config: Config, evm_opts: EvmOpts) -> Result<Self> {
578 let sender_nonce = if let Some(fork_url) = evm_opts.fork_url.as_ref() {
579 next_nonce(evm_opts.sender, fork_url, evm_opts.fork_block_number).await?
580 } else {
581 1
583 };
584
585 Ok(Self { config, evm_opts, sender_nonce, backends: HashMap::default() })
586 }
587
588 pub async fn update_sender(&mut self, sender: Address) -> Result<()> {
589 self.sender_nonce = if let Some(fork_url) = self.evm_opts.fork_url.as_ref() {
590 next_nonce(sender, fork_url, None).await?
591 } else {
592 1
594 };
595 self.evm_opts.sender = sender;
596 Ok(())
597 }
598
599 async fn get_runner(&mut self) -> Result<ScriptRunner> {
600 self._get_runner(None, false).await
601 }
602
603 async fn get_runner_with_cheatcodes(
604 &mut self,
605 known_contracts: ContractsByArtifact,
606 script_wallets: Wallets,
607 debug: bool,
608 target: ArtifactId,
609 ) -> Result<ScriptRunner> {
610 self._get_runner(Some((known_contracts, script_wallets, target)), debug).await
611 }
612
613 async fn _get_runner(
614 &mut self,
615 cheats_data: Option<(ContractsByArtifact, Wallets, ArtifactId)>,
616 debug: bool,
617 ) -> Result<ScriptRunner> {
618 trace!("preparing script runner");
619 let env = self.evm_opts.evm_env().await?;
620
621 let db = if let Some(fork_url) = self.evm_opts.fork_url.as_ref() {
622 match self.backends.get(fork_url) {
623 Some(db) => db.clone(),
624 None => {
625 let fork = self.evm_opts.get_fork(&self.config, env.clone());
626 let backend = Backend::spawn(fork)?;
627 self.backends.insert(fork_url.clone(), backend.clone());
628 backend
629 }
630 }
631 } else {
632 Backend::spawn(None)?
636 };
637
638 let mut builder = ExecutorBuilder::new()
640 .inspectors(|stack| {
641 stack
642 .trace_mode(if debug { TraceMode::Debug } else { TraceMode::Call })
643 .odyssey(self.evm_opts.odyssey)
644 .create2_deployer(self.evm_opts.create2_deployer)
645 })
646 .spec_id(self.config.evm_spec_id())
647 .gas_limit(self.evm_opts.gas_limit())
648 .legacy_assertions(self.config.legacy_assertions);
649
650 if let Some((known_contracts, script_wallets, target)) = cheats_data {
651 builder = builder.inspectors(|stack| {
652 stack
653 .cheatcodes(
654 CheatsConfig::new(
655 &self.config,
656 self.evm_opts.clone(),
657 Some(known_contracts),
658 Some(target),
659 )
660 .into(),
661 )
662 .wallets(script_wallets)
663 .enable_isolation(self.evm_opts.isolate)
664 });
665 }
666
667 Ok(ScriptRunner::new(builder.build(env, db), self.evm_opts.clone()))
668 }
669}
670
671#[cfg(test)]
672mod tests {
673 use super::*;
674 use foundry_config::{NamedChain, UnresolvedEnvVarError};
675 use std::fs;
676 use tempfile::tempdir;
677
678 #[test]
679 fn can_parse_sig() {
680 let sig = "0x522bb704000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfFFb92266";
681 let args = ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--sig", sig]);
682 assert_eq!(args.sig, sig);
683 }
684
685 #[test]
686 fn can_parse_unlocked() {
687 let args = ScriptArgs::parse_from([
688 "foundry-cli",
689 "Contract.sol",
690 "--sender",
691 "0x4e59b44847b379578588920ca78fbf26c0b4956c",
692 "--unlocked",
693 ]);
694 assert!(args.unlocked);
695
696 let key = U256::ZERO;
697 let args = ScriptArgs::try_parse_from([
698 "foundry-cli",
699 "Contract.sol",
700 "--sender",
701 "0x4e59b44847b379578588920ca78fbf26c0b4956c",
702 "--unlocked",
703 "--private-key",
704 key.to_string().as_str(),
705 ]);
706 assert!(args.is_err());
707 }
708
709 #[test]
710 fn can_merge_script_config() {
711 let args = ScriptArgs::parse_from([
712 "foundry-cli",
713 "Contract.sol",
714 "--etherscan-api-key",
715 "goerli",
716 ]);
717 let config = args.load_config().unwrap();
718 assert_eq!(config.etherscan_api_key, Some("goerli".to_string()));
719 }
720
721 #[test]
722 fn can_disable_code_size_limit() {
723 let args =
724 ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--disable-code-size-limit"]);
725 assert!(args.disable_code_size_limit);
726
727 let result = ScriptResult::default();
728 let contracts = ContractsByArtifact::default();
729 let create = Address::ZERO;
730 assert!(args.check_contract_sizes(&result, &contracts, create).is_ok());
731 }
732
733 #[test]
734 fn can_parse_verifier_url() {
735 let args = ScriptArgs::parse_from([
736 "foundry-cli",
737 "script",
738 "script/Test.s.sol:TestScript",
739 "--fork-url",
740 "http://localhost:8545",
741 "--verifier-url",
742 "http://localhost:3000/api/verify",
743 "--etherscan-api-key",
744 "blacksmith",
745 "--broadcast",
746 "--verify",
747 "-vvvvv",
748 ]);
749 assert_eq!(
750 args.verifier.verifier_url,
751 Some("http://localhost:3000/api/verify".to_string())
752 );
753 }
754
755 #[test]
756 fn can_extract_code_size_limit() {
757 let args = ScriptArgs::parse_from([
758 "foundry-cli",
759 "script",
760 "script/Test.s.sol:TestScript",
761 "--fork-url",
762 "http://localhost:8545",
763 "--broadcast",
764 "--code-size-limit",
765 "50000",
766 ]);
767 assert_eq!(args.evm.env.code_size_limit, Some(50000));
768 }
769
770 #[test]
771 fn can_extract_script_etherscan_key() {
772 let temp = tempdir().unwrap();
773 let root = temp.path();
774
775 let config = r#"
776 [profile.default]
777 etherscan_api_key = "mumbai"
778
779 [etherscan]
780 mumbai = { key = "https://etherscan-mumbai.com/" }
781 "#;
782
783 let toml_file = root.join(Config::FILE_NAME);
784 fs::write(toml_file, config).unwrap();
785 let args = ScriptArgs::parse_from([
786 "foundry-cli",
787 "Contract.sol",
788 "--etherscan-api-key",
789 "mumbai",
790 "--root",
791 root.as_os_str().to_str().unwrap(),
792 ]);
793
794 let config = args.load_config().unwrap();
795 let mumbai = config.get_etherscan_api_key(Some(NamedChain::PolygonMumbai.into()));
796 assert_eq!(mumbai, Some("https://etherscan-mumbai.com/".to_string()));
797 }
798
799 #[test]
800 fn can_extract_script_rpc_alias() {
801 let temp = tempdir().unwrap();
802 let root = temp.path();
803
804 let config = r#"
805 [profile.default]
806
807 [rpc_endpoints]
808 polygonMumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_CAN_EXTRACT_RPC_ALIAS}"
809 "#;
810
811 let toml_file = root.join(Config::FILE_NAME);
812 fs::write(toml_file, config).unwrap();
813 let args = ScriptArgs::parse_from([
814 "foundry-cli",
815 "DeployV1",
816 "--rpc-url",
817 "polygonMumbai",
818 "--root",
819 root.as_os_str().to_str().unwrap(),
820 ]);
821
822 let err = args.load_config_and_evm_opts().unwrap_err();
823
824 assert!(err.downcast::<UnresolvedEnvVarError>().is_ok());
825
826 std::env::set_var("_CAN_EXTRACT_RPC_ALIAS", "123456");
827 let (config, evm_opts) = args.load_config_and_evm_opts().unwrap();
828 assert_eq!(config.eth_rpc_url, Some("polygonMumbai".to_string()));
829 assert_eq!(
830 evm_opts.fork_url,
831 Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string())
832 );
833 }
834
835 #[test]
836 fn can_extract_script_rpc_and_etherscan_alias() {
837 let temp = tempdir().unwrap();
838 let root = temp.path();
839
840 let config = r#"
841 [profile.default]
842
843 [rpc_endpoints]
844 mumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_EXTRACT_RPC_ALIAS}"
845
846 [etherscan]
847 mumbai = { key = "${_POLYSCAN_API_KEY}", chain = 80001, url = "https://api-testnet.polygonscan.com/" }
848 "#;
849
850 let toml_file = root.join(Config::FILE_NAME);
851 fs::write(toml_file, config).unwrap();
852 let args = ScriptArgs::parse_from([
853 "foundry-cli",
854 "DeployV1",
855 "--rpc-url",
856 "mumbai",
857 "--etherscan-api-key",
858 "mumbai",
859 "--root",
860 root.as_os_str().to_str().unwrap(),
861 ]);
862 let err = args.load_config_and_evm_opts().unwrap_err();
863
864 assert!(err.downcast::<UnresolvedEnvVarError>().is_ok());
865
866 std::env::set_var("_EXTRACT_RPC_ALIAS", "123456");
867 std::env::set_var("_POLYSCAN_API_KEY", "polygonkey");
868 let (config, evm_opts) = args.load_config_and_evm_opts().unwrap();
869 assert_eq!(config.eth_rpc_url, Some("mumbai".to_string()));
870 assert_eq!(
871 evm_opts.fork_url,
872 Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string())
873 );
874 let etherscan = config.get_etherscan_api_key(Some(80001u64.into()));
875 assert_eq!(etherscan, Some("polygonkey".to_string()));
876 let etherscan = config.get_etherscan_api_key(None);
877 assert_eq!(etherscan, Some("polygonkey".to_string()));
878 }
879
880 #[test]
881 fn can_extract_script_rpc_and_sole_etherscan_alias() {
882 let temp = tempdir().unwrap();
883 let root = temp.path();
884
885 let config = r#"
886 [profile.default]
887
888 [rpc_endpoints]
889 mumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_SOLE_EXTRACT_RPC_ALIAS}"
890
891 [etherscan]
892 mumbai = { key = "${_SOLE_POLYSCAN_API_KEY}" }
893 "#;
894
895 let toml_file = root.join(Config::FILE_NAME);
896 fs::write(toml_file, config).unwrap();
897 let args = ScriptArgs::parse_from([
898 "foundry-cli",
899 "DeployV1",
900 "--rpc-url",
901 "mumbai",
902 "--root",
903 root.as_os_str().to_str().unwrap(),
904 ]);
905 let err = args.load_config_and_evm_opts().unwrap_err();
906
907 assert!(err.downcast::<UnresolvedEnvVarError>().is_ok());
908
909 std::env::set_var("_SOLE_EXTRACT_RPC_ALIAS", "123456");
910 std::env::set_var("_SOLE_POLYSCAN_API_KEY", "polygonkey");
911 let (config, evm_opts) = args.load_config_and_evm_opts().unwrap();
912 assert_eq!(
913 evm_opts.fork_url,
914 Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string())
915 );
916 let etherscan = config.get_etherscan_api_key(Some(80001u64.into()));
917 assert_eq!(etherscan, Some("polygonkey".to_string()));
918 let etherscan = config.get_etherscan_api_key(None);
919 assert_eq!(etherscan, Some("polygonkey".to_string()));
920 }
921
922 #[test]
924 fn test_5923() {
925 let args =
926 ScriptArgs::parse_from(["foundry-cli", "DeployV1", "--priority-gas-price", "100"]);
927 assert!(args.priority_gas_price.is_some());
928 }
929
930 #[test]
932 fn test_5910() {
933 let args = ScriptArgs::parse_from([
934 "foundry-cli",
935 "--broadcast",
936 "--with-gas-price",
937 "0",
938 "SolveTutorial",
939 ]);
940 assert!(args.with_gas_price.unwrap().is_zero());
941 }
942}