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 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 evm::Breakpoints,
36 shell,
37};
38use foundry_compilers::ArtifactId;
39use foundry_config::{
40 Config, figment,
41 figment::{
42 Metadata, Profile, Provider,
43 value::{Dict, Map},
44 },
45};
46use foundry_evm::{
47 backend::Backend,
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)]
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 || (compiled.args.verify && !compiled.args.broadcast)
254 {
255 compiled.resume().await?
256 } else {
257 let pre_simulation = compiled
259 .link()
260 .await?
261 .prepare_execution()
262 .await?
263 .execute()
264 .await?
265 .prepare_simulation()
266 .await?;
267
268 if pre_simulation.args.debug {
269 return match pre_simulation.args.dump.clone() {
270 Some(path) => pre_simulation.dump_debugger(&path),
271 None => pre_simulation.run_debugger(),
272 };
273 }
274
275 if shell::is_json() {
276 pre_simulation.show_json().await?;
277 } else {
278 pre_simulation.show_traces().await?;
279 }
280
281 if pre_simulation
284 .execution_result
285 .transactions
286 .as_ref()
287 .is_none_or(|txs| txs.is_empty())
288 {
289 if pre_simulation.args.broadcast {
290 sh_warn!("No transactions to broadcast.")?;
291 }
292
293 return Ok(());
294 }
295
296 if pre_simulation.execution_artifacts.rpc_data.missing_rpc {
298 if !shell::is_json() {
299 sh_println!("\nIf you wish to simulate on-chain transactions pass a RPC URL.")?;
300 }
301
302 return Ok(());
303 }
304
305 pre_simulation.args.check_contract_sizes(
306 &pre_simulation.execution_result,
307 &pre_simulation.build_data.known_contracts,
308 create2_deployer,
309 )?;
310
311 pre_simulation.fill_metadata().await?.bundle().await?
312 };
313
314 if !bundled.args.should_broadcast() {
316 if !shell::is_json() {
317 if shell::verbosity() >= 4 {
318 sh_println!("\n=== Transactions that will be broadcast ===\n")?;
319 bundled.sequence.show_transactions()?;
320 }
321
322 sh_println!(
323 "\nSIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more."
324 )?;
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
506 if let Some(ref etherscan_api_key) =
507 self.etherscan_api_key.as_ref().filter(|s| !s.trim().is_empty())
508 {
509 dict.insert(
510 "etherscan_api_key".to_string(),
511 figment::value::Value::from(etherscan_api_key.to_string()),
512 );
513 }
514
515 if let Some(timeout) = self.timeout {
516 dict.insert("transaction_timeout".to_string(), timeout.into());
517 }
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(
541 &self,
542 known_contracts: &ContractsByArtifact,
543 ) -> Vec<AdditionalContract> {
544 self.traces
545 .iter()
546 .flat_map(|(_, traces)| {
547 traces.nodes().iter().filter_map(|node| {
548 if node.trace.kind.is_any_create() {
549 let init_code = node.trace.data.clone();
550 let contract_name = known_contracts
551 .find_by_creation_code(init_code.as_ref())
552 .map(|artifact| artifact.0.name.clone());
553 return Some(AdditionalContract {
554 opcode: node.trace.kind,
555 address: node.trace.address,
556 contract_name,
557 init_code,
558 });
559 }
560 None
561 })
562 })
563 .collect()
564 }
565}
566
567#[derive(Serialize)]
568struct JsonResult<'a> {
569 logs: Vec<String>,
570 returns: &'a HashMap<String, NestedValue>,
571 #[serde(flatten)]
572 result: &'a ScriptResult,
573}
574
575#[derive(Clone, Debug)]
576pub struct ScriptConfig {
577 pub config: Config,
578 pub evm_opts: EvmOpts,
579 pub sender_nonce: u64,
580 pub backends: HashMap<String, Backend>,
582}
583
584impl ScriptConfig {
585 pub async fn new(config: Config, evm_opts: EvmOpts) -> Result<Self> {
586 let sender_nonce = if let Some(fork_url) = evm_opts.fork_url.as_ref() {
587 next_nonce(evm_opts.sender, fork_url, evm_opts.fork_block_number).await?
588 } else {
589 1
591 };
592
593 Ok(Self { config, evm_opts, sender_nonce, backends: HashMap::default() })
594 }
595
596 pub async fn update_sender(&mut self, sender: Address) -> Result<()> {
597 self.sender_nonce = if let Some(fork_url) = self.evm_opts.fork_url.as_ref() {
598 next_nonce(sender, fork_url, None).await?
599 } else {
600 1
602 };
603 self.evm_opts.sender = sender;
604 Ok(())
605 }
606
607 async fn get_runner(&mut self) -> Result<ScriptRunner> {
608 self._get_runner(None, false).await
609 }
610
611 async fn get_runner_with_cheatcodes(
612 &mut self,
613 known_contracts: ContractsByArtifact,
614 script_wallets: Wallets,
615 debug: bool,
616 target: ArtifactId,
617 ) -> Result<ScriptRunner> {
618 self._get_runner(Some((known_contracts, script_wallets, target)), debug).await
619 }
620
621 async fn _get_runner(
622 &mut self,
623 cheats_data: Option<(ContractsByArtifact, Wallets, ArtifactId)>,
624 debug: bool,
625 ) -> Result<ScriptRunner> {
626 trace!("preparing script runner");
627 let env = self.evm_opts.evm_env().await?;
628
629 let db = if let Some(fork_url) = self.evm_opts.fork_url.as_ref() {
630 match self.backends.get(fork_url) {
631 Some(db) => db.clone(),
632 None => {
633 let fork = self.evm_opts.get_fork(&self.config, env.clone());
634 let backend = Backend::spawn(fork)?;
635 self.backends.insert(fork_url.clone(), backend.clone());
636 backend
637 }
638 }
639 } else {
640 Backend::spawn(None)?
644 };
645
646 let mut builder = ExecutorBuilder::new()
648 .inspectors(|stack| {
649 stack
650 .trace_mode(if debug { TraceMode::Debug } else { TraceMode::Call })
651 .networks(self.evm_opts.networks)
652 .create2_deployer(self.evm_opts.create2_deployer)
653 })
654 .spec_id(self.config.evm_spec_id())
655 .gas_limit(self.evm_opts.gas_limit())
656 .legacy_assertions(self.config.legacy_assertions);
657
658 if let Some((known_contracts, script_wallets, target)) = cheats_data {
659 builder = builder.inspectors(|stack| {
660 stack
661 .cheatcodes(
662 CheatsConfig::new(
663 &self.config,
664 self.evm_opts.clone(),
665 Some(known_contracts),
666 Some(target),
667 )
668 .into(),
669 )
670 .wallets(script_wallets)
671 .enable_isolation(self.evm_opts.isolate)
672 });
673 }
674
675 Ok(ScriptRunner::new(builder.build(env, db), self.evm_opts.clone()))
676 }
677}
678
679#[cfg(test)]
680mod tests {
681 use super::*;
682 use foundry_config::{NamedChain, UnresolvedEnvVarError};
683 use std::fs;
684 use tempfile::tempdir;
685
686 #[test]
687 fn can_parse_sig() {
688 let sig = "0x522bb704000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfFFb92266";
689 let args = ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--sig", sig]);
690 assert_eq!(args.sig, sig);
691 }
692
693 #[test]
694 fn can_parse_unlocked() {
695 let args = ScriptArgs::parse_from([
696 "foundry-cli",
697 "Contract.sol",
698 "--sender",
699 "0x4e59b44847b379578588920ca78fbf26c0b4956c",
700 "--unlocked",
701 ]);
702 assert!(args.unlocked);
703
704 let key = U256::ZERO;
705 let args = ScriptArgs::try_parse_from([
706 "foundry-cli",
707 "Contract.sol",
708 "--sender",
709 "0x4e59b44847b379578588920ca78fbf26c0b4956c",
710 "--unlocked",
711 "--private-key",
712 key.to_string().as_str(),
713 ]);
714 assert!(args.is_err());
715 }
716
717 #[test]
718 fn can_merge_script_config() {
719 let args = ScriptArgs::parse_from([
720 "foundry-cli",
721 "Contract.sol",
722 "--etherscan-api-key",
723 "goerli",
724 ]);
725 let config = args.load_config().unwrap();
726 assert_eq!(config.etherscan_api_key, Some("goerli".to_string()));
727 }
728
729 #[test]
730 fn can_disable_code_size_limit() {
731 let args =
732 ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--disable-code-size-limit"]);
733 assert!(args.disable_code_size_limit);
734
735 let result = ScriptResult::default();
736 let contracts = ContractsByArtifact::default();
737 let create = Address::ZERO;
738 assert!(args.check_contract_sizes(&result, &contracts, create).is_ok());
739 }
740
741 #[test]
742 fn can_parse_verifier_url() {
743 let args = ScriptArgs::parse_from([
744 "foundry-cli",
745 "script",
746 "script/Test.s.sol:TestScript",
747 "--fork-url",
748 "http://localhost:8545",
749 "--verifier-url",
750 "http://localhost:3000/api/verify",
751 "--etherscan-api-key",
752 "blacksmith",
753 "--broadcast",
754 "--verify",
755 "-vvvvv",
756 ]);
757 assert_eq!(
758 args.verifier.verifier_url,
759 Some("http://localhost:3000/api/verify".to_string())
760 );
761 }
762
763 #[test]
764 fn can_extract_code_size_limit() {
765 let args = ScriptArgs::parse_from([
766 "foundry-cli",
767 "script",
768 "script/Test.s.sol:TestScript",
769 "--fork-url",
770 "http://localhost:8545",
771 "--broadcast",
772 "--code-size-limit",
773 "50000",
774 ]);
775 assert_eq!(args.evm.env.code_size_limit, Some(50000));
776 }
777
778 #[test]
779 fn can_extract_script_etherscan_key() {
780 let temp = tempdir().unwrap();
781 let root = temp.path();
782
783 let config = r#"
784 [profile.default]
785 etherscan_api_key = "amoy"
786
787 [etherscan]
788 amoy = { key = "https://etherscan-amoy.com/" }
789 "#;
790
791 let toml_file = root.join(Config::FILE_NAME);
792 fs::write(toml_file, config).unwrap();
793 let args = ScriptArgs::parse_from([
794 "foundry-cli",
795 "Contract.sol",
796 "--etherscan-api-key",
797 "amoy",
798 "--root",
799 root.as_os_str().to_str().unwrap(),
800 ]);
801
802 let config = args.load_config().unwrap();
803 let amoy = config.get_etherscan_api_key(Some(NamedChain::PolygonAmoy.into()));
804 assert_eq!(amoy, Some("https://etherscan-amoy.com/".to_string()));
805 }
806
807 #[test]
808 fn can_extract_script_rpc_alias() {
809 let temp = tempdir().unwrap();
810 let root = temp.path();
811
812 let config = r#"
813 [profile.default]
814
815 [rpc_endpoints]
816 polygonAmoy = "https://polygon-amoy.g.alchemy.com/v2/${_CAN_EXTRACT_RPC_ALIAS}"
817 "#;
818
819 let toml_file = root.join(Config::FILE_NAME);
820 fs::write(toml_file, config).unwrap();
821 let args = ScriptArgs::parse_from([
822 "foundry-cli",
823 "DeployV1",
824 "--rpc-url",
825 "polygonAmoy",
826 "--root",
827 root.as_os_str().to_str().unwrap(),
828 ]);
829
830 let err = args.load_config_and_evm_opts().unwrap_err();
831
832 assert!(err.downcast::<UnresolvedEnvVarError>().is_ok());
833
834 unsafe {
835 std::env::set_var("_CAN_EXTRACT_RPC_ALIAS", "123456");
836 }
837 let (config, evm_opts) = args.load_config_and_evm_opts().unwrap();
838 assert_eq!(config.eth_rpc_url, Some("polygonAmoy".to_string()));
839 assert_eq!(
840 evm_opts.fork_url,
841 Some("https://polygon-amoy.g.alchemy.com/v2/123456".to_string())
842 );
843 }
844
845 #[test]
846 fn can_extract_script_rpc_and_etherscan_alias() {
847 let temp = tempdir().unwrap();
848 let root = temp.path();
849
850 let config = r#"
851 [profile.default]
852
853 [rpc_endpoints]
854 amoy = "https://polygon-amoy.g.alchemy.com/v2/${_EXTRACT_RPC_ALIAS}"
855
856 [etherscan]
857 amoy = { key = "${_ETHERSCAN_API_KEY}", chain = 80002, url = "https://amoy.polygonscan.com/" }
858 "#;
859
860 let toml_file = root.join(Config::FILE_NAME);
861 fs::write(toml_file, config).unwrap();
862 let args = ScriptArgs::parse_from([
863 "foundry-cli",
864 "DeployV1",
865 "--rpc-url",
866 "amoy",
867 "--etherscan-api-key",
868 "amoy",
869 "--root",
870 root.as_os_str().to_str().unwrap(),
871 ]);
872 let err = args.load_config_and_evm_opts().unwrap_err();
873
874 assert!(err.downcast::<UnresolvedEnvVarError>().is_ok());
875
876 unsafe {
877 std::env::set_var("_EXTRACT_RPC_ALIAS", "123456");
878 }
879 unsafe {
880 std::env::set_var("_ETHERSCAN_API_KEY", "etherscan_api_key");
881 }
882 let (config, evm_opts) = args.load_config_and_evm_opts().unwrap();
883 assert_eq!(config.eth_rpc_url, Some("amoy".to_string()));
884 assert_eq!(
885 evm_opts.fork_url,
886 Some("https://polygon-amoy.g.alchemy.com/v2/123456".to_string())
887 );
888 let etherscan = config.get_etherscan_api_key(Some(80002u64.into()));
889 assert_eq!(etherscan, Some("etherscan_api_key".to_string()));
890 let etherscan = config.get_etherscan_api_key(None);
891 assert_eq!(etherscan, Some("etherscan_api_key".to_string()));
892 }
893
894 #[test]
895 fn can_extract_script_rpc_and_sole_etherscan_alias() {
896 let temp = tempdir().unwrap();
897 let root = temp.path();
898
899 let config = r#"
900 [profile.default]
901
902 [rpc_endpoints]
903 amoy = "https://polygon-amoy.g.alchemy.com/v2/${_SOLE_EXTRACT_RPC_ALIAS}"
904
905 [etherscan]
906 amoy = { key = "${_SOLE_ETHERSCAN_API_KEY}" }
907 "#;
908
909 let toml_file = root.join(Config::FILE_NAME);
910 fs::write(toml_file, config).unwrap();
911 let args = ScriptArgs::parse_from([
912 "foundry-cli",
913 "DeployV1",
914 "--rpc-url",
915 "amoy",
916 "--root",
917 root.as_os_str().to_str().unwrap(),
918 ]);
919 let err = args.load_config_and_evm_opts().unwrap_err();
920
921 assert!(err.downcast::<UnresolvedEnvVarError>().is_ok());
922
923 unsafe {
924 std::env::set_var("_SOLE_EXTRACT_RPC_ALIAS", "123456");
925 }
926 unsafe {
927 std::env::set_var("_SOLE_ETHERSCAN_API_KEY", "etherscan_api_key");
928 }
929 let (config, evm_opts) = args.load_config_and_evm_opts().unwrap();
930 assert_eq!(
931 evm_opts.fork_url,
932 Some("https://polygon-amoy.g.alchemy.com/v2/123456".to_string())
933 );
934 let etherscan = config.get_etherscan_api_key(Some(80002u64.into()));
935 assert_eq!(etherscan, Some("etherscan_api_key".to_string()));
936 let etherscan = config.get_etherscan_api_key(None);
937 assert_eq!(etherscan, Some("etherscan_api_key".to_string()));
938 }
939
940 #[test]
942 fn test_5923() {
943 let args =
944 ScriptArgs::parse_from(["foundry-cli", "DeployV1", "--priority-gas-price", "100"]);
945 assert!(args.priority_gas_price.is_some());
946 }
947
948 #[test]
950 fn test_5910() {
951 let args = ScriptArgs::parse_from([
952 "foundry-cli",
953 "--broadcast",
954 "--with-gas-price",
955 "0",
956 "SolveTutorial",
957 ]);
958 assert!(args.with_gas_price.unwrap().is_zero());
959 }
960}