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