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