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