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_cli::{
30 opts::{BuildOpts, GlobalArgs},
31 utils::LoadConfig,
32};
33use foundry_common::{
34 abi::{encode_function_args, get_func},
35 evm::{Breakpoints, EvmArgs},
36 shell, ContractsByArtifact, CONTRACT_MAX_SIZE, SELECTOR_LEN,
37};
38use foundry_compilers::ArtifactId;
39use foundry_config::{
40 figment,
41 figment::{
42 value::{Dict, Map},
43 Metadata, Profile, Provider,
44 },
45 Config,
46};
47use foundry_evm::{
48 backend::Backend,
49 executors::ExecutorBuilder,
50 inspectors::{
51 cheatcodes::{BroadcastableTransactions, Wallets},
52 CheatsConfig,
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", "froms", "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, env = "ETHERSCAN_API_KEY", value_name = "KEY")]
183 pub etherscan_api_key: Option<String>,
184
185 #[arg(long)]
187 pub verify: bool,
188
189 #[arg(
194 long,
195 env = "ETH_GAS_PRICE",
196 value_parser = foundry_cli::utils::parse_ether_value,
197 value_name = "PRICE",
198 )]
199 pub with_gas_price: Option<U256>,
200
201 #[arg(long, env = "ETH_TIMEOUT")]
203 pub timeout: Option<u64>,
204
205 #[command(flatten)]
206 pub build: BuildOpts,
207
208 #[command(flatten)]
209 pub wallets: MultiWalletOpts,
210
211 #[command(flatten)]
212 pub evm: EvmArgs,
213
214 #[command(flatten)]
215 pub verifier: VerifierArgs,
216
217 #[command(flatten)]
218 pub retry: RetryArgs,
219}
220
221impl ScriptArgs {
222 pub async fn preprocess(self) -> Result<PreprocessedState> {
223 let script_wallets = Wallets::new(self.wallets.get_multi_wallet().await?, self.evm.sender);
224
225 let (config, mut evm_opts) = self.load_config_and_evm_opts()?;
226
227 if let Some(sender) = self.maybe_load_private_key()? {
228 evm_opts.sender = sender;
229 }
230
231 let script_config = ScriptConfig::new(config, evm_opts).await?;
232
233 Ok(PreprocessedState { args: self, script_config, script_wallets })
234 }
235
236 pub async fn run_script(self) -> Result<()> {
238 trace!(target: "script", "executing script command");
239
240 let state = self.preprocess().await?;
241 let create2_deployer = state.script_config.evm_opts.create2_deployer;
242 let compiled = state.compile()?;
243
244 let bundled = if compiled.args.resume || (compiled.args.verify && !compiled.args.broadcast)
247 {
248 compiled.resume().await?
249 } else {
250 let pre_simulation = compiled
252 .link()
253 .await?
254 .prepare_execution()
255 .await?
256 .execute()
257 .await?
258 .prepare_simulation()
259 .await?;
260
261 if pre_simulation.args.debug {
262 return match pre_simulation.args.dump.clone() {
263 Some(path) => pre_simulation.dump_debugger(&path),
264 None => pre_simulation.run_debugger(),
265 };
266 }
267
268 if shell::is_json() {
269 pre_simulation.show_json()?;
270 } else {
271 pre_simulation.show_traces().await?;
272 }
273
274 if pre_simulation
277 .execution_result
278 .transactions
279 .as_ref()
280 .is_none_or(|txs| txs.is_empty())
281 {
282 return Ok(());
283 }
284
285 if pre_simulation.execution_artifacts.rpc_data.missing_rpc {
287 if !shell::is_json() {
288 sh_println!("\nIf you wish to simulate on-chain transactions pass a RPC URL.")?;
289 }
290
291 return Ok(());
292 }
293
294 pre_simulation.args.check_contract_sizes(
295 &pre_simulation.execution_result,
296 &pre_simulation.build_data.known_contracts,
297 create2_deployer,
298 )?;
299
300 pre_simulation.fill_metadata().await?.bundle().await?
301 };
302
303 if !bundled.args.should_broadcast() {
305 if !shell::is_json() {
306 if shell::verbosity() >= 4 {
307 sh_println!("\n=== Transactions that will be broadcast ===\n")?;
308 bundled.sequence.show_transactions()?;
309 }
310
311 sh_println!("\nSIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more.")?;
312 }
313 return Ok(());
314 }
315
316 if bundled.args.verify {
318 bundled.verify_preflight_check()?;
319 }
320
321 let broadcasted = bundled.wait_for_pending().await?.broadcast().await?;
323
324 if broadcasted.args.verify {
325 broadcasted.verify().await?;
326 }
327
328 Ok(())
329 }
330
331 fn maybe_load_private_key(&self) -> Result<Option<Address>> {
334 let maybe_sender = self
335 .wallets
336 .private_keys()?
337 .filter(|pks| pks.len() == 1)
338 .map(|pks| pks.first().unwrap().address());
339 Ok(maybe_sender)
340 }
341
342 fn get_method_and_calldata(&self, abi: &JsonAbi) -> Result<(Function, Bytes)> {
350 if let Ok(decoded) = hex::decode(&self.sig) {
351 let selector = &decoded[..SELECTOR_LEN];
352 let func =
353 abi.functions().find(|func| selector == &func.selector()[..]).ok_or_else(|| {
354 eyre::eyre!(
355 "Function selector `{}` not found in the ABI",
356 hex::encode(selector)
357 )
358 })?;
359 return Ok((func.clone(), decoded.into()));
360 }
361
362 let func = if self.sig.contains('(') {
363 let func = get_func(&self.sig)?;
364 abi.functions()
365 .find(|&abi_func| abi_func.selector() == func.selector())
366 .wrap_err(format!("Function `{}` is not implemented in your script.", self.sig))?
367 } else {
368 let matching_functions =
369 abi.functions().filter(|func| func.name == self.sig).collect::<Vec<_>>();
370 match matching_functions.len() {
371 0 => eyre::bail!("Function `{}` not found in the ABI", self.sig),
372 1 => matching_functions[0],
373 2.. => eyre::bail!(
374 "Multiple functions with the same name `{}` found in the ABI",
375 self.sig
376 ),
377 }
378 };
379 let data = encode_function_args(func, &self.args)?;
380
381 Ok((func.clone(), data.into()))
382 }
383
384 fn check_contract_sizes(
390 &self,
391 result: &ScriptResult,
392 known_contracts: &ContractsByArtifact,
393 create2_deployer: Address,
394 ) -> Result<()> {
395 let mut bytecodes: Vec<(String, &[u8], &[u8])> = vec![];
397
398 for (artifact, contract) in known_contracts.iter() {
400 let Some(bytecode) = contract.bytecode() else { continue };
401 let Some(deployed_bytecode) = contract.deployed_bytecode() else { continue };
402 bytecodes.push((artifact.name.clone(), bytecode, deployed_bytecode));
403 }
404
405 let create_nodes = result.traces.iter().flat_map(|(_, traces)| {
407 traces.nodes().iter().filter(|node| node.trace.kind.is_any_create())
408 });
409 let mut unknown_c = 0usize;
410 for node in create_nodes {
411 let init_code = &node.trace.data;
412 let deployed_code = &node.trace.output;
413 if !bytecodes.iter().any(|(_, b, _)| *b == init_code.as_ref()) {
414 bytecodes.push((format!("Unknown{unknown_c}"), init_code, deployed_code));
415 unknown_c += 1;
416 }
417 continue;
418 }
419
420 let mut prompt_user = false;
421 let max_size = match self.evm.env.code_size_limit {
422 Some(size) => size,
423 None => CONTRACT_MAX_SIZE,
424 };
425
426 for (data, to) in result.transactions.iter().flat_map(|txes| {
427 txes.iter().filter_map(|tx| {
428 tx.transaction
429 .input()
430 .filter(|data| data.len() > max_size)
431 .map(|data| (data, tx.transaction.to()))
432 })
433 }) {
434 let mut offset = 0;
435
436 if let Some(TxKind::Call(to)) = to {
438 if to == create2_deployer {
439 offset = 32;
441 } else {
442 continue;
443 }
444 } else if let Some(TxKind::Create) = to {
445 }
447
448 if let Some((name, _, deployed_code)) =
450 bytecodes.iter().find(|(_, init_code, _)| *init_code == &data[offset..])
451 {
452 let deployment_size = deployed_code.len();
453
454 if deployment_size > max_size {
455 prompt_user = self.should_broadcast();
456 sh_err!(
457 "`{name}` is above the contract size limit ({deployment_size} > {max_size})."
458 )?;
459 }
460 }
461 }
462
463 if prompt_user &&
465 !self.non_interactive &&
466 !Confirm::new().with_prompt("Do you wish to continue?".to_string()).interact()?
467 {
468 eyre::bail!("User canceled the script.");
469 }
470
471 Ok(())
472 }
473
474 fn should_broadcast(&self) -> bool {
476 self.broadcast || self.resume
477 }
478}
479
480impl Provider for ScriptArgs {
481 fn metadata(&self) -> Metadata {
482 Metadata::named("Script Args Provider")
483 }
484
485 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
486 let mut dict = Dict::default();
487 if let Some(ref etherscan_api_key) =
488 self.etherscan_api_key.as_ref().filter(|s| !s.trim().is_empty())
489 {
490 dict.insert(
491 "etherscan_api_key".to_string(),
492 figment::value::Value::from(etherscan_api_key.to_string()),
493 );
494 }
495 if let Some(timeout) = self.timeout {
496 dict.insert("transaction_timeout".to_string(), timeout.into());
497 }
498 Ok(Map::from([(Config::selected_profile(), dict)]))
499 }
500}
501
502#[derive(Default, Serialize)]
503pub struct ScriptResult {
504 pub success: bool,
505 #[serde(rename = "raw_logs")]
506 pub logs: Vec<Log>,
507 pub traces: Traces,
508 pub gas_used: u64,
509 pub labeled_addresses: AddressHashMap<String>,
510 #[serde(skip)]
511 pub transactions: Option<BroadcastableTransactions>,
512 pub returned: Bytes,
513 pub address: Option<Address>,
514 #[serde(skip)]
515 pub breakpoints: Breakpoints,
516}
517
518impl ScriptResult {
519 pub fn get_created_contracts(&self) -> Vec<AdditionalContract> {
520 self.traces
521 .iter()
522 .flat_map(|(_, traces)| {
523 traces.nodes().iter().filter_map(|node| {
524 if node.trace.kind.is_any_create() {
525 return Some(AdditionalContract {
526 opcode: node.trace.kind,
527 address: node.trace.address,
528 init_code: node.trace.data.clone(),
529 });
530 }
531 None
532 })
533 })
534 .collect()
535 }
536}
537
538#[derive(Serialize)]
539struct JsonResult<'a> {
540 logs: Vec<String>,
541 returns: &'a HashMap<String, NestedValue>,
542 #[serde(flatten)]
543 result: &'a ScriptResult,
544}
545
546#[derive(Clone, Debug)]
547pub struct ScriptConfig {
548 pub config: Config,
549 pub evm_opts: EvmOpts,
550 pub sender_nonce: u64,
551 pub backends: HashMap<String, Backend>,
553}
554
555impl ScriptConfig {
556 pub async fn new(config: Config, evm_opts: EvmOpts) -> Result<Self> {
557 let sender_nonce = if let Some(fork_url) = evm_opts.fork_url.as_ref() {
558 next_nonce(evm_opts.sender, fork_url, evm_opts.fork_block_number).await?
559 } else {
560 1
562 };
563
564 Ok(Self { config, evm_opts, sender_nonce, backends: HashMap::default() })
565 }
566
567 pub async fn update_sender(&mut self, sender: Address) -> Result<()> {
568 self.sender_nonce = if let Some(fork_url) = self.evm_opts.fork_url.as_ref() {
569 next_nonce(sender, fork_url, None).await?
570 } else {
571 1
573 };
574 self.evm_opts.sender = sender;
575 Ok(())
576 }
577
578 async fn get_runner(&mut self) -> Result<ScriptRunner> {
579 self._get_runner(None, false).await
580 }
581
582 async fn get_runner_with_cheatcodes(
583 &mut self,
584 known_contracts: ContractsByArtifact,
585 script_wallets: Wallets,
586 debug: bool,
587 target: ArtifactId,
588 ) -> Result<ScriptRunner> {
589 self._get_runner(Some((known_contracts, script_wallets, target)), debug).await
590 }
591
592 async fn _get_runner(
593 &mut self,
594 cheats_data: Option<(ContractsByArtifact, Wallets, ArtifactId)>,
595 debug: bool,
596 ) -> Result<ScriptRunner> {
597 trace!("preparing script runner");
598 let env = self.evm_opts.evm_env().await?;
599
600 let db = if let Some(fork_url) = self.evm_opts.fork_url.as_ref() {
601 match self.backends.get(fork_url) {
602 Some(db) => db.clone(),
603 None => {
604 let fork = self.evm_opts.get_fork(&self.config, env.clone());
605 let backend = Backend::spawn(fork)?;
606 self.backends.insert(fork_url.clone(), backend.clone());
607 backend
608 }
609 }
610 } else {
611 Backend::spawn(None)?
615 };
616
617 let mut builder = ExecutorBuilder::new()
619 .inspectors(|stack| {
620 stack
621 .trace_mode(if debug { TraceMode::Debug } else { TraceMode::Call })
622 .odyssey(self.evm_opts.odyssey)
623 .create2_deployer(self.evm_opts.create2_deployer)
624 })
625 .spec_id(self.config.evm_spec_id())
626 .gas_limit(self.evm_opts.gas_limit())
627 .legacy_assertions(self.config.legacy_assertions);
628
629 if let Some((known_contracts, script_wallets, target)) = cheats_data {
630 builder = builder.inspectors(|stack| {
631 stack
632 .cheatcodes(
633 CheatsConfig::new(
634 &self.config,
635 self.evm_opts.clone(),
636 Some(known_contracts),
637 Some(target),
638 )
639 .into(),
640 )
641 .wallets(script_wallets)
642 .enable_isolation(self.evm_opts.isolate)
643 });
644 }
645
646 Ok(ScriptRunner::new(builder.build(env, db), self.evm_opts.clone()))
647 }
648}
649
650#[cfg(test)]
651mod tests {
652 use super::*;
653 use foundry_config::{NamedChain, UnresolvedEnvVarError};
654 use std::fs;
655 use tempfile::tempdir;
656
657 #[test]
658 fn can_parse_sig() {
659 let sig = "0x522bb704000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfFFb92266";
660 let args = ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--sig", sig]);
661 assert_eq!(args.sig, sig);
662 }
663
664 #[test]
665 fn can_parse_unlocked() {
666 let args = ScriptArgs::parse_from([
667 "foundry-cli",
668 "Contract.sol",
669 "--sender",
670 "0x4e59b44847b379578588920ca78fbf26c0b4956c",
671 "--unlocked",
672 ]);
673 assert!(args.unlocked);
674
675 let key = U256::ZERO;
676 let args = ScriptArgs::try_parse_from([
677 "foundry-cli",
678 "Contract.sol",
679 "--sender",
680 "0x4e59b44847b379578588920ca78fbf26c0b4956c",
681 "--unlocked",
682 "--private-key",
683 key.to_string().as_str(),
684 ]);
685 assert!(args.is_err());
686 }
687
688 #[test]
689 fn can_merge_script_config() {
690 let args = ScriptArgs::parse_from([
691 "foundry-cli",
692 "Contract.sol",
693 "--etherscan-api-key",
694 "goerli",
695 ]);
696 let config = args.load_config().unwrap();
697 assert_eq!(config.etherscan_api_key, Some("goerli".to_string()));
698 }
699
700 #[test]
701 fn can_parse_verifier_url() {
702 let args = ScriptArgs::parse_from([
703 "foundry-cli",
704 "script",
705 "script/Test.s.sol:TestScript",
706 "--fork-url",
707 "http://localhost:8545",
708 "--verifier-url",
709 "http://localhost:3000/api/verify",
710 "--etherscan-api-key",
711 "blacksmith",
712 "--broadcast",
713 "--verify",
714 "-vvvvv",
715 ]);
716 assert_eq!(
717 args.verifier.verifier_url,
718 Some("http://localhost:3000/api/verify".to_string())
719 );
720 }
721
722 #[test]
723 fn can_extract_code_size_limit() {
724 let args = ScriptArgs::parse_from([
725 "foundry-cli",
726 "script",
727 "script/Test.s.sol:TestScript",
728 "--fork-url",
729 "http://localhost:8545",
730 "--broadcast",
731 "--code-size-limit",
732 "50000",
733 ]);
734 assert_eq!(args.evm.env.code_size_limit, Some(50000));
735 }
736
737 #[test]
738 fn can_extract_script_etherscan_key() {
739 let temp = tempdir().unwrap();
740 let root = temp.path();
741
742 let config = r#"
743 [profile.default]
744 etherscan_api_key = "mumbai"
745
746 [etherscan]
747 mumbai = { key = "https://etherscan-mumbai.com/" }
748 "#;
749
750 let toml_file = root.join(Config::FILE_NAME);
751 fs::write(toml_file, config).unwrap();
752 let args = ScriptArgs::parse_from([
753 "foundry-cli",
754 "Contract.sol",
755 "--etherscan-api-key",
756 "mumbai",
757 "--root",
758 root.as_os_str().to_str().unwrap(),
759 ]);
760
761 let config = args.load_config().unwrap();
762 let mumbai = config.get_etherscan_api_key(Some(NamedChain::PolygonMumbai.into()));
763 assert_eq!(mumbai, Some("https://etherscan-mumbai.com/".to_string()));
764 }
765
766 #[test]
767 fn can_extract_script_rpc_alias() {
768 let temp = tempdir().unwrap();
769 let root = temp.path();
770
771 let config = r#"
772 [profile.default]
773
774 [rpc_endpoints]
775 polygonMumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_CAN_EXTRACT_RPC_ALIAS}"
776 "#;
777
778 let toml_file = root.join(Config::FILE_NAME);
779 fs::write(toml_file, config).unwrap();
780 let args = ScriptArgs::parse_from([
781 "foundry-cli",
782 "DeployV1",
783 "--rpc-url",
784 "polygonMumbai",
785 "--root",
786 root.as_os_str().to_str().unwrap(),
787 ]);
788
789 let err = args.load_config_and_evm_opts().unwrap_err();
790
791 assert!(err.downcast::<UnresolvedEnvVarError>().is_ok());
792
793 std::env::set_var("_CAN_EXTRACT_RPC_ALIAS", "123456");
794 let (config, evm_opts) = args.load_config_and_evm_opts().unwrap();
795 assert_eq!(config.eth_rpc_url, Some("polygonMumbai".to_string()));
796 assert_eq!(
797 evm_opts.fork_url,
798 Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string())
799 );
800 }
801
802 #[test]
803 fn can_extract_script_rpc_and_etherscan_alias() {
804 let temp = tempdir().unwrap();
805 let root = temp.path();
806
807 let config = r#"
808 [profile.default]
809
810 [rpc_endpoints]
811 mumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_EXTRACT_RPC_ALIAS}"
812
813 [etherscan]
814 mumbai = { key = "${_POLYSCAN_API_KEY}", chain = 80001, url = "https://api-testnet.polygonscan.com/" }
815 "#;
816
817 let toml_file = root.join(Config::FILE_NAME);
818 fs::write(toml_file, config).unwrap();
819 let args = ScriptArgs::parse_from([
820 "foundry-cli",
821 "DeployV1",
822 "--rpc-url",
823 "mumbai",
824 "--etherscan-api-key",
825 "mumbai",
826 "--root",
827 root.as_os_str().to_str().unwrap(),
828 ]);
829 let err = args.load_config_and_evm_opts().unwrap_err();
830
831 assert!(err.downcast::<UnresolvedEnvVarError>().is_ok());
832
833 std::env::set_var("_EXTRACT_RPC_ALIAS", "123456");
834 std::env::set_var("_POLYSCAN_API_KEY", "polygonkey");
835 let (config, evm_opts) = args.load_config_and_evm_opts().unwrap();
836 assert_eq!(config.eth_rpc_url, Some("mumbai".to_string()));
837 assert_eq!(
838 evm_opts.fork_url,
839 Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string())
840 );
841 let etherscan = config.get_etherscan_api_key(Some(80001u64.into()));
842 assert_eq!(etherscan, Some("polygonkey".to_string()));
843 let etherscan = config.get_etherscan_api_key(None);
844 assert_eq!(etherscan, Some("polygonkey".to_string()));
845 }
846
847 #[test]
848 fn can_extract_script_rpc_and_sole_etherscan_alias() {
849 let temp = tempdir().unwrap();
850 let root = temp.path();
851
852 let config = r#"
853 [profile.default]
854
855 [rpc_endpoints]
856 mumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_SOLE_EXTRACT_RPC_ALIAS}"
857
858 [etherscan]
859 mumbai = { key = "${_SOLE_POLYSCAN_API_KEY}" }
860 "#;
861
862 let toml_file = root.join(Config::FILE_NAME);
863 fs::write(toml_file, config).unwrap();
864 let args = ScriptArgs::parse_from([
865 "foundry-cli",
866 "DeployV1",
867 "--rpc-url",
868 "mumbai",
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 std::env::set_var("_SOLE_EXTRACT_RPC_ALIAS", "123456");
877 std::env::set_var("_SOLE_POLYSCAN_API_KEY", "polygonkey");
878 let (config, evm_opts) = args.load_config_and_evm_opts().unwrap();
879 assert_eq!(
880 evm_opts.fork_url,
881 Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string())
882 );
883 let etherscan = config.get_etherscan_api_key(Some(80001u64.into()));
884 assert_eq!(etherscan, Some("polygonkey".to_string()));
885 let etherscan = config.get_etherscan_api_key(None);
886 assert_eq!(etherscan, Some("polygonkey".to_string()));
887 }
888
889 #[test]
891 fn test_5923() {
892 let args =
893 ScriptArgs::parse_from(["foundry-cli", "DeployV1", "--priority-gas-price", "100"]);
894 assert!(args.priority_gas_price.is_some());
895 }
896
897 #[test]
899 fn test_5910() {
900 let args = ScriptArgs::parse_from([
901 "foundry-cli",
902 "--broadcast",
903 "--with-gas-price",
904 "0",
905 "SolveTutorial",
906 ]);
907 assert!(args.with_gas_price.unwrap().is_zero());
908 }
909}