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