1#![recursion_limit = "256"]
6#![cfg_attr(not(test), warn(unused_crate_dependencies))]
7#![cfg_attr(docsrs, feature(doc_cfg))]
8
9#[macro_use]
10extern crate foundry_common;
11
12#[macro_use]
13extern crate tracing;
14
15use crate::{broadcast::BundledState, runner::ScriptRunner};
16use alloy_json_abi::{Function, JsonAbi};
17use alloy_network::Network;
18use alloy_primitives::{
19 Address, Bytes, Log, U256, hex,
20 map::{AddressHashMap, HashMap},
21};
22use alloy_signer::Signer;
23use broadcast::next_nonce;
24use build::PreprocessedState;
25use clap::{Parser, ValueHint, builder::RangedU64ValueParser};
26use dialoguer::Confirm;
27use eyre::{ContextCompat, Result};
28use forge_script_sequence::{AdditionalContract, NestedValue};
29use forge_verify::{RetryArgs, VerifierArgs};
30use foundry_cli::{
31 opts::{BuildOpts, EvmArgs, GlobalArgs, TempoOpts},
32 utils::LoadConfig,
33};
34use foundry_common::{
35 ContractsByArtifact, SELECTOR_LEN,
36 abi::{encode_function_args, get_func},
37 compile::ContractSizeLimits,
38 shell,
39};
40use foundry_compilers::ArtifactId;
41use foundry_config::{
42 Config, Eip1559FeeEstimatePreset, figment,
43 figment::{
44 Metadata, Profile, Provider,
45 value::{Dict, Map},
46 },
47};
48use foundry_debugger::DebuggerLayout;
49#[cfg(feature = "optimism")]
50use foundry_evm::core::evm::OpEvmNetwork;
51use foundry_evm::{
52 backend::Backend,
53 core::{
54 Breakpoints, FoundryTransaction,
55 evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork, TxEnvFor},
56 },
57 executors::ExecutorBuilder,
58 inspectors::{
59 CheatsConfig,
60 cheatcodes::{BroadcastableTransactions, Wallets},
61 },
62 opts::EvmOpts,
63 revm::interpreter::InstructionResult,
64 traces::{TraceRequirements, Traces},
65};
66use foundry_evm_networks::NetworkConfigs;
67use foundry_wallets::MultiWalletOpts;
68use serde::Serialize;
69use std::path::PathBuf;
70
71mod broadcast;
72mod build;
73mod execute;
74mod multi_sequence;
75mod progress;
76mod providers;
77mod receipts;
78mod runner;
79mod sequence;
80mod session;
81mod simulate;
82mod transaction;
83mod verify;
84mod wallet_session;
85
86pub use wallet_session::ScriptWalletSessionArgs;
87
88foundry_config::merge_impl_figment_convert!(ScriptArgs, build, evm);
90
91#[derive(Clone, Debug, Default, Parser)]
93pub struct ScriptArgs {
94 #[command(flatten)]
96 pub global: GlobalArgs,
97
98 #[arg(value_hint = ValueHint::FilePath)]
103 pub path: String,
104
105 pub args: Vec<String>,
107
108 #[arg(long, visible_alias = "tc", value_name = "CONTRACT_NAME")]
110 pub target_contract: Option<String>,
111
112 #[arg(long, short, default_value = "run")]
114 pub sig: String,
115
116 #[arg(
118 long,
119 env = "ETH_PRIORITY_GAS_PRICE",
120 value_parser = foundry_cli::utils::parse_ether_value,
121 value_name = "PRICE"
122 )]
123 pub priority_gas_price: Option<U256>,
124
125 #[arg(long)]
129 pub legacy: bool,
130
131 #[arg(long = "estimate", value_name = "PRESET")]
139 pub eip1559_fee_estimate: Option<Eip1559FeeEstimatePreset>,
140
141 #[arg(long)]
143 pub broadcast: bool,
144
145 #[arg(long)]
151 pub batch: bool,
152
153 #[command(flatten)]
155 pub tempo: TempoOpts,
156
157 #[command(flatten)]
159 pub wallet_session: ScriptWalletSessionArgs,
160
161 #[arg(long)]
163 pub skip_simulation: bool,
164
165 #[arg(long, short, default_value = "130")]
167 pub gas_estimate_multiplier: u64,
168
169 #[arg(
171 long,
172 value_name = "NONCE",
173 value_parser = RangedU64ValueParser::<u64>::new().range(..u64::MAX),
174 )]
175 pub sender_nonce: Option<u64>,
176
177 #[arg(
179 long,
180 conflicts_with_all = &["private_key", "private_keys", "ledger", "trezor", "aws", "browser"],
181 )]
182 pub unlocked: bool,
183
184 #[arg(long)]
191 pub resume: bool,
192
193 #[arg(long)]
195 pub multi: bool,
196
197 #[arg(long)]
201 pub debug: bool,
202
203 #[arg(long = "debug-layout", requires = "debug", value_enum)]
205 pub debug_layout: Option<DebuggerLayout>,
206
207 #[arg(
209 long,
210 requires = "debug",
211 value_hint = ValueHint::FilePath,
212 value_name = "PATH"
213 )]
214 pub dump: Option<PathBuf>,
215
216 #[arg(long)]
219 pub slow: bool,
220
221 #[arg(long)]
225 pub non_interactive: bool,
226
227 #[arg(long)]
229 pub disable_code_size_limit: bool,
230
231 #[arg(long)]
233 pub disable_labels: bool,
234
235 #[arg(long, env = "ETHERSCAN_API_KEY", value_name = "KEY")]
237 pub etherscan_api_key: Option<String>,
238
239 #[arg(long, requires = "broadcast")]
241 pub verify: bool,
242
243 #[arg(
248 long,
249 env = "ETH_GAS_PRICE",
250 value_parser = foundry_cli::utils::parse_ether_value,
251 value_name = "PRICE",
252 )]
253 pub with_gas_price: Option<U256>,
254
255 #[arg(long, env = "ETH_TIMEOUT")]
257 pub timeout: Option<u64>,
258
259 #[command(flatten)]
260 pub build: BuildOpts,
261
262 #[command(flatten)]
263 pub wallets: MultiWalletOpts,
264
265 #[command(flatten)]
266 pub evm: EvmArgs,
267
268 #[command(flatten)]
269 pub verifier: VerifierArgs,
270
271 #[command(flatten)]
272 pub retry: RetryArgs,
273}
274
275impl ScriptArgs {
276 fn has_tempo_session(&self) -> Result<bool> {
277 Ok(self.tempo.session_id()?.is_some())
278 }
279
280 async fn resolved_evm_opts(&self) -> Result<(Config, EvmOpts)> {
282 let (config, mut evm_opts) = self.load_config_and_evm_opts()?;
283
284 if self.tempo.is_tempo() || self.has_tempo_session()? {
285 evm_opts.networks = NetworkConfigs::with_tempo();
287 } else {
288 evm_opts.infer_network_from_fork().await;
290 }
291
292 Ok((config, evm_opts))
293 }
294
295 async fn preprocess<FEN: FoundryEvmNetwork>(
296 self,
297 config: Config,
298 mut evm_opts: EvmOpts,
299 ) -> Result<PreprocessedState<FEN>> {
300 let args = self;
301 let mut tempo = args.tempo.clone();
302
303 let session_sender = if args.resume {
304 None
305 } else {
306 tempo.session_sender_for_multi_wallet(&args.wallets, args.evm.sender)?
309 };
310
311 let script_wallets = Wallets::new(args.wallets.get_multi_wallet().await?, args.evm.sender);
312 let browser_wallet = args.wallets.browser_signer::<FEN::Network>().await?;
313
314 if let Some(sender) = session_sender {
315 evm_opts.sender = sender;
316 } else if let Some(sender) = args.maybe_load_private_key()? {
317 evm_opts.sender = sender;
318 } else if args.evm.sender.is_none() {
319 let addresses = script_wallets.addresses();
323 if addresses.len() == 1 {
324 evm_opts.sender = addresses[0];
325 } else if let Some(signer) = browser_wallet.as_ref().map(|b| b.address()) {
326 evm_opts.sender = signer
327 }
328 }
329
330 tempo.resolve_expires();
331
332 let script_config =
333 ScriptConfig::new(config, evm_opts, args.batch, tempo, args.sender_nonce).await?;
334 Ok(PreprocessedState { args, script_config, script_wallets, browser_wallet })
335 }
336
337 #[allow(clippy::large_stack_frames)]
339 pub async fn run_script(self) -> Result<()> {
340 trace!(target: "script", "executing script command");
341
342 if self.wallet_session.enabled {
343 return self.run_wallet_session_wrapper();
344 }
345
346 let (config, evm_opts) = self.resolved_evm_opts().await?;
347
348 let is_tempo = evm_opts.networks.is_tempo();
349
350 if self.batch && !is_tempo {
351 eyre::bail!("--batch mode is only supported on Tempo networks");
352 }
353
354 if self.unlocked && self.has_tempo_session()? {
355 eyre::bail!("--tempo.session/TEMPO_SESSION_ID cannot be combined with --unlocked");
356 }
357
358 if is_tempo {
361 let batch = self.batch;
362 return Box::pin(async move {
363 let bundled =
364 match self.prepare_bundled::<TempoEvmNetwork>(config, evm_opts).await? {
365 Some(bundled) => bundled,
366 None => return Ok(()),
367 };
368 let bundled = if batch { bundled } else { bundled.wait_for_pending().await? };
372 let broadcasted = if batch {
373 bundled.broadcast_batch().await?
374 } else {
375 bundled.broadcast().await?
376 };
377 if broadcasted.args.verify {
378 broadcasted.verify().await?;
379 }
380 Ok(())
381 })
382 .await;
383 }
384
385 #[cfg(feature = "optimism")]
386 if evm_opts.networks.is_optimism() {
387 return Box::pin(self.run_generic_script::<OpEvmNetwork>(config, evm_opts)).await;
388 }
389
390 Box::pin(self.run_generic_script::<EthEvmNetwork>(config, evm_opts)).await
391 }
392
393 #[allow(clippy::large_stack_frames)]
397 async fn prepare_bundled<FEN: FoundryEvmNetwork>(
398 self,
399 config: Config,
400 evm_opts: EvmOpts,
401 ) -> Result<Option<BundledState<FEN>>> {
402 let state = self.preprocess::<FEN>(config, evm_opts).await?;
403 let create2_deployer = state.script_config.evm_opts.create2_deployer;
404 let compiled = state.compile()?;
405
406 let bundled = if compiled.args.resume {
409 compiled.resume().await?
410 } else {
411 let pre_simulation = compiled
413 .link()
414 .await?
415 .prepare_execution()
416 .await?
417 .execute()
418 .await?
419 .prepare_simulation()
420 .await?;
421
422 if pre_simulation.args.debug {
423 return match pre_simulation.args.dump.clone() {
424 Some(path) => pre_simulation.dump_debugger(&path).map(|_| None),
425 None => pre_simulation.run_debugger().map(|_| None),
426 };
427 }
428
429 if shell::is_json() {
430 pre_simulation.show_json().await?;
431 } else {
432 pre_simulation.show_traces().await?;
433 }
434
435 if pre_simulation
438 .execution_result
439 .transactions
440 .as_ref()
441 .is_none_or(|txs| txs.is_empty())
442 {
443 if pre_simulation.args.broadcast {
444 sh_warn!("No transactions to broadcast.")?;
445 }
446
447 return Ok(None);
448 }
449
450 if pre_simulation.execution_artifacts.rpc_data.missing_rpc {
452 if !shell::is_json() {
453 sh_println!("\nIf you wish to simulate on-chain transactions pass a RPC URL.")?;
454 }
455
456 return Ok(None);
457 }
458
459 let size_limits = pre_simulation
460 .script_config
461 .evm_opts
462 .env
463 .code_size_limit
464 .or(pre_simulation.script_config.config.code_size_limit)
465 .map(ContractSizeLimits::with_runtime_limit)
466 .unwrap_or_default();
467 pre_simulation.args.check_contract_sizes(
468 size_limits,
469 &pre_simulation.execution_result,
470 &pre_simulation.build_data.known_contracts,
471 create2_deployer,
472 )?;
473
474 pre_simulation.fill_metadata().await?.bundle().await?
475 };
476
477 if !bundled.args.should_broadcast() {
479 if !shell::is_json() {
480 if shell::verbosity() >= 4 {
481 sh_println!("\n=== Transactions that will be broadcast ===\n")?;
482 bundled.sequence.show_transactions()?;
483 }
484
485 sh_println!(
486 "\nSIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more."
487 )?;
488 }
489 return Ok(None);
490 }
491
492 if bundled.args.verify {
494 bundled.verify_preflight_check().await?;
495 }
496
497 Ok(Some(bundled))
498 }
499
500 async fn run_generic_script<FEN: FoundryEvmNetwork>(
501 self,
502 config: Config,
503 evm_opts: EvmOpts,
504 ) -> Result<()> {
505 let bundled = match self.prepare_bundled::<FEN>(config, evm_opts).await? {
506 Some(bundled) => bundled,
507 None => return Ok(()),
508 };
509
510 let broadcasted = bundled.wait_for_pending().await?.broadcast().await?;
512
513 if broadcasted.args.verify {
514 broadcasted.verify().await?;
515 }
516
517 Ok(())
518 }
519
520 fn maybe_load_private_key(&self) -> Result<Option<Address>> {
523 if let Some(turnkey_address) = self.wallets.turnkey_address() {
524 return Ok(Some(turnkey_address));
525 }
526
527 let maybe_sender = self
528 .wallets
529 .private_keys()?
530 .filter(|pks| pks.len() == 1)
531 .map(|pks| pks.first().unwrap().address());
532 Ok(maybe_sender)
533 }
534
535 fn get_method_and_calldata(&self, abi: &JsonAbi) -> Result<(Function, Bytes)> {
543 if let Ok(decoded) = hex::decode(&self.sig) {
544 let selector = &decoded[..SELECTOR_LEN];
545 let func =
546 abi.functions().find(|func| selector == &func.selector()[..]).ok_or_else(|| {
547 eyre::eyre!(
548 "Function selector `{}` not found in the ABI",
549 hex::encode(selector)
550 )
551 })?;
552 return Ok((func.clone(), decoded.into()));
553 }
554
555 let func = if self.sig.contains('(') {
556 let func = get_func(&self.sig)?;
557 abi.functions()
558 .find(|&abi_func| abi_func.selector() == func.selector())
559 .wrap_err(format!("Function `{}` is not implemented in your script.", self.sig))?
560 } else {
561 let matching_functions =
562 abi.functions().filter(|func| func.name == self.sig).collect::<Vec<_>>();
563 match matching_functions.len() {
564 0 => {
565 eyre::bail!("Function `{}` not found in the ABI", self.sig);
566 }
567 1 => matching_functions[0],
568 2.. => {
569 eyre::bail!(
570 "Multiple functions with the same name `{}` found in the ABI",
571 self.sig
572 );
573 }
574 }
575 };
576 let data = encode_function_args(func, &self.args)?;
577
578 Ok((func.clone(), data.into()))
579 }
580
581 fn check_contract_sizes<N: Network>(
587 &self,
588 size_limits: ContractSizeLimits,
589 result: &ScriptResult<N>,
590 known_contracts: &ContractsByArtifact,
591 create2_deployer: Address,
592 ) -> Result<()> {
593 if self.disable_code_size_limit {
595 return Ok(());
596 }
597
598 let mut bytecodes: Vec<(String, &[u8], &[u8])> = vec![];
600
601 for (artifact, contract) in known_contracts.iter() {
603 let Some(bytecode) = contract.bytecode() else { continue };
604 let Some(deployed_bytecode) = contract.deployed_bytecode() else { continue };
605 bytecodes.push((artifact.name.clone(), bytecode, deployed_bytecode));
606 }
607
608 let create_nodes = result.traces.iter().flat_map(|(_, traces)| {
610 traces.nodes().iter().filter(|node| node.trace.kind.is_any_create())
611 });
612 let mut unknown_c = 0usize;
613 for node in create_nodes {
614 let init_code = &node.trace.data;
615 let deployed_code = &node.trace.output;
616 if !bytecodes.iter().any(|(_, b, _)| *b == init_code.as_ref()) {
617 bytecodes.push((format!("Unknown{unknown_c}"), init_code, deployed_code));
618 unknown_c += 1;
619 }
620 }
621
622 let mut prompt_user = false;
623 let max_size = size_limits.runtime;
624
625 for (data, to) in result.transactions.iter().flat_map(|txes| {
626 txes.iter().filter_map(|tx| {
627 tx.transaction
628 .input()
629 .filter(|data| data.len() > max_size)
630 .map(|data| (data, tx.transaction.to()))
631 })
632 }) {
633 let mut offset = 0;
634
635 if let Some(to) = to {
637 if to == create2_deployer {
638 offset = 32;
640 } else {
641 continue;
642 }
643 }
644
645 if let Some((name, _, deployed_code)) =
647 bytecodes.iter().find(|(_, init_code, _)| *init_code == &data[offset..])
648 {
649 let deployment_size = deployed_code.len();
650
651 if deployment_size > max_size {
652 prompt_user = self.should_broadcast();
653 sh_err!(
654 "`{name}` is above the contract size limit ({deployment_size} > {max_size})."
655 )?;
656 }
657 }
658 }
659
660 if prompt_user
662 && !self.non_interactive
663 && !Confirm::new().with_prompt("Do you wish to continue?".to_string()).interact()?
664 {
665 eyre::bail!("User canceled the script.");
666 }
667
668 Ok(())
669 }
670
671 const fn should_broadcast(&self) -> bool {
673 self.broadcast || self.resume || self.verify
674 }
675}
676
677impl Provider for ScriptArgs {
678 fn metadata(&self) -> Metadata {
679 Metadata::named("Script Args Provider")
680 }
681
682 fn data(&self) -> Result<Map<Profile, Dict>, figment::Error> {
683 let mut dict = Dict::default();
684
685 if let Some(etherscan_api_key) =
686 self.etherscan_api_key.as_ref().filter(|s| !s.trim().is_empty())
687 {
688 dict.insert(
689 "etherscan_api_key".to_string(),
690 figment::value::Value::from(etherscan_api_key.clone()),
691 );
692 }
693
694 if let Some(timeout) = self.timeout {
695 dict.insert("transaction_timeout".to_string(), timeout.into());
696 }
697
698 if let Some(preset) = self.eip1559_fee_estimate {
699 dict.insert(
700 "eip1559_fee_estimate".to_string(),
701 figment::value::Value::from(preset.to_string()),
702 );
703 }
704
705 Ok(Map::from([(Config::selected_profile(), dict)]))
706 }
707}
708
709#[derive(Serialize, Clone)]
710#[serde(bound = "")]
711pub struct ScriptResult<N: Network> {
712 pub success: bool,
713 #[serde(rename = "raw_logs")]
714 pub logs: Vec<Log>,
715 pub traces: Traces,
716 pub gas_used: u64,
717 pub labeled_addresses: AddressHashMap<String>,
718 #[serde(skip)]
719 pub debug_bytecodes: AddressHashMap<Bytes>,
720 #[serde(skip)]
721 pub transactions: Option<BroadcastableTransactions<N>>,
722 pub returned: Bytes,
723 #[serde(skip)]
724 pub exit_reason: Option<InstructionResult>,
725 pub address: Option<Address>,
726 #[serde(skip)]
727 pub breakpoints: Breakpoints,
728}
729
730impl<N: Network> Default for ScriptResult<N> {
731 fn default() -> Self {
732 Self {
733 success: Default::default(),
734 logs: Default::default(),
735 traces: Default::default(),
736 gas_used: Default::default(),
737 labeled_addresses: Default::default(),
738 debug_bytecodes: Default::default(),
739 transactions: Default::default(),
740 returned: Default::default(),
741 exit_reason: Default::default(),
742 address: Default::default(),
743 breakpoints: Default::default(),
744 }
745 }
746}
747
748impl<N: Network> ScriptResult<N> {
749 pub fn get_created_contracts(
750 &self,
751 known_contracts: &ContractsByArtifact,
752 ) -> Vec<AdditionalContract> {
753 self.traces
754 .iter()
755 .flat_map(|(_, traces)| {
756 traces.nodes().iter().filter_map(|node| {
757 if node.trace.kind.is_any_create() {
758 let init_code = node.trace.data.clone();
759 let contract_name = known_contracts
760 .find_by_creation_code(init_code.as_ref())
761 .map(|artifact| artifact.0.name.clone());
762 return Some(AdditionalContract {
763 call_kind: node.trace.kind,
764 address: node.trace.address,
765 contract_name,
766 init_code,
767 });
768 }
769 None
770 })
771 })
772 .collect()
773 }
774}
775
776#[derive(Serialize)]
777#[serde(bound = "")]
778struct JsonResult<'a, N: Network> {
779 logs: Vec<String>,
780 returns: &'a HashMap<String, NestedValue>,
781 #[serde(flatten)]
782 result: &'a ScriptResult<N>,
783}
784
785#[derive(Clone, Debug)]
786pub struct ScriptConfig<FEN: FoundryEvmNetwork> {
787 pub config: Config,
788 pub evm_opts: EvmOpts,
789 pub sender_nonce: u64,
790 sender_nonce_override: Option<u64>,
791 pub backends: HashMap<String, Backend<FEN>>,
793 pub batch: bool,
795 pub tempo: TempoOpts,
797}
798
799impl<FEN: FoundryEvmNetwork> ScriptConfig<FEN> {
800 pub async fn new(
801 config: Config,
802 evm_opts: EvmOpts,
803 batch: bool,
804 tempo: TempoOpts,
805 sender_nonce_override: Option<u64>,
806 ) -> Result<Self> {
807 let sender_nonce = if let Some(sender_nonce) = sender_nonce_override {
808 sender_nonce
809 } else if let Some(fork_url) = evm_opts.fork_url.as_ref() {
810 next_nonce(evm_opts.sender, fork_url, evm_opts.fork_block_number).await?
811 } else {
812 1
814 };
815
816 Ok(Self {
817 config,
818 evm_opts,
819 sender_nonce,
820 sender_nonce_override,
821 backends: HashMap::default(),
822 batch,
823 tempo,
824 })
825 }
826
827 pub async fn update_sender(&mut self, sender: Address) -> Result<()> {
828 self.sender_nonce = if let Some(sender_nonce) = self.sender_nonce_override {
829 sender_nonce
830 } else if let Some(fork_url) = self.evm_opts.fork_url.as_ref() {
831 next_nonce(sender, fork_url, None).await?
832 } else {
833 1
835 };
836 self.evm_opts.sender = sender;
837 Ok(())
838 }
839
840 pub(crate) async fn update_tempo_session_sender(
841 &mut self,
842 wallets: &MultiWalletOpts,
843 expected_sender: Option<Address>,
844 ) -> Result<()> {
845 if let Some(sender) =
846 self.tempo.session_sender_for_multi_wallet(wallets, expected_sender)?
847 {
848 self.update_sender(sender).await?;
849 }
850 Ok(())
851 }
852
853 async fn get_runner(&mut self) -> Result<ScriptRunner<FEN>> {
854 self._get_runner(None, false).await
855 }
856
857 async fn get_runner_with_cheatcodes(
858 &mut self,
859 known_contracts: ContractsByArtifact,
860 script_wallets: Wallets,
861 debug: bool,
862 target: ArtifactId,
863 ) -> Result<ScriptRunner<FEN>> {
864 self._get_runner(Some((known_contracts, script_wallets, target)), debug).await
865 }
866
867 async fn _get_runner(
868 &mut self,
869 cheats_data: Option<(ContractsByArtifact, Wallets, ArtifactId)>,
870 debug: bool,
871 ) -> Result<ScriptRunner<FEN>> {
872 trace!("preparing script runner");
873 let (evm_env, mut tx_env, fork_block) = self.evm_opts.env::<_, _, TxEnvFor<FEN>>().await?;
874
875 let db = if let Some(fork_url) = self.evm_opts.fork_url.as_ref() {
876 match self.backends.get(fork_url) {
877 Some(db) => db.clone(),
878 None => {
879 let fork =
880 self.evm_opts.get_fork(&self.config, evm_env.cfg_env.chain_id, fork_block);
881 let backend = Backend::spawn(fork)?;
882 self.backends.insert(fork_url.clone(), backend.clone());
883 backend
884 }
885 }
886 } else {
887 Backend::spawn(None)?
891 };
892
893 let mut builder = ExecutorBuilder::default()
895 .inspectors(|stack| {
896 stack
897 .logs(self.config.live_logs)
898 .trace_requirements(
899 TraceRequirements::none()
900 .with_calls(true)
901 .with_debug(debug)
902 .with_verbosity(self.evm_opts.verbosity),
903 )
904 .networks(self.evm_opts.networks)
905 .create2_deployer(self.evm_opts.create2_deployer)
906 })
907 .spec_id(self.config.evm_spec_id())
908 .gas_limit(self.evm_opts.gas_limit())
909 .legacy_assertions(self.config.legacy_assertions);
910
911 if let Some((known_contracts, script_wallets, target)) = cheats_data {
912 builder = builder.inspectors(|stack| {
913 stack
914 .cheatcodes(
915 CheatsConfig::new(
916 &self.config,
917 self.evm_opts.clone(),
918 Some(known_contracts),
919 Some(target),
920 self.tempo.fee_token,
921 self.batch,
922 )
923 .into(),
924 )
925 .wallets(script_wallets)
926 .enable_isolation(self.evm_opts.isolate)
927 });
928 }
929
930 tx_env.set_fee_token(self.tempo.fee_token);
933
934 let mut runner =
935 ScriptRunner::new(builder.build(evm_env, tx_env, db), self.evm_opts.clone())
936 .with_debug_bytecodes(debug);
937
938 if self.sender_nonce_override.is_some() {
939 runner.executor.set_nonce(self.evm_opts.sender, self.sender_nonce)?;
940 }
941
942 Ok(runner)
943 }
944}
945
946#[cfg(test)]
947mod tests {
948 use super::*;
949 use alloy_chains::NamedChain;
950 use alloy_network::Ethereum;
951 use alloy_primitives::{B256, address};
952 use foundry_cli::opts::TEMPO_SESSION_ID_ENV;
953 use foundry_common::tempo::{
954 KeyType, SessionEntry, SessionKeyMaterial, SessionStatus, TEMPO_HOME_ENV,
955 upsert_session_entry,
956 };
957 use foundry_config::UnresolvedEnvVarError;
958 use std::{fs, sync::LazyLock};
959 use tempfile::tempdir;
960 use tokio::sync::{Mutex, MutexGuard};
961
962 const SESSION_PRIVATE_KEY: &str =
963 "0x59c6995e998f97a5a004497e5da3b5d2b2b66a87f064d39c44da0b6d6e4f8ff0";
964 const SESSION_ID_HEX: &str =
965 "0x1111111111111111111111111111111111111111111111111111111111111111";
966 const SESSION_ROOT_ADDRESS: &str = "0x1111111111111111111111111111111111111111";
967 static TEMPO_HOME_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
968
969 fn active_session_entry(
970 session_id: B256,
971 root_account: Address,
972 chain_id: u64,
973 ) -> SessionEntry {
974 let key = foundry_wallets::utils::create_private_key_signer(SESSION_PRIVATE_KEY).unwrap();
975 SessionEntry {
976 session_id,
977 root_account,
978 chain_id,
979 key_address: key.address(),
980 expiry: u64::MAX,
981 scope: None,
982 limits: None,
983 status: SessionStatus::Active,
984 key: Some(SessionKeyMaterial {
985 key_type: KeyType::Secp256k1,
986 key: SESSION_PRIVATE_KEY.to_string(),
987 key_authorization: None,
988 }),
989 }
990 }
991
992 struct TempoHomeGuard {
993 _guard: MutexGuard<'static, ()>,
994 }
995
996 impl TempoHomeGuard {
997 async fn set(path: &std::path::Path) -> Self {
998 let guard = TEMPO_HOME_LOCK.lock().await;
999 unsafe {
1001 std::env::remove_var(TEMPO_SESSION_ID_ENV);
1002 std::env::set_var(TEMPO_HOME_ENV, path);
1003 }
1004 Self { _guard: guard }
1005 }
1006 }
1007
1008 impl Drop for TempoHomeGuard {
1009 fn drop(&mut self) {
1010 unsafe {
1012 std::env::remove_var(TEMPO_HOME_ENV);
1013 std::env::remove_var(TEMPO_SESSION_ID_ENV);
1014 }
1015 }
1016 }
1017
1018 fn session_root() -> Address {
1019 SESSION_ROOT_ADDRESS.parse().unwrap()
1020 }
1021
1022 #[test]
1023 fn can_parse_sig() {
1024 let sig = "0x522bb704000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfFFb92266";
1025 let args = ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--sig", sig]);
1026 assert_eq!(args.sig, sig);
1027 }
1028
1029 #[test]
1030 fn rejects_max_sender_nonce() {
1031 let max_valid_nonce = (u64::MAX - 1).to_string();
1032 let args = ScriptArgs::try_parse_from([
1033 "foundry-cli",
1034 "Contract.sol",
1035 "--sender-nonce",
1036 &max_valid_nonce,
1037 ])
1038 .unwrap();
1039 assert_eq!(args.sender_nonce, Some(u64::MAX - 1));
1040
1041 let max_nonce = u64::MAX.to_string();
1042 let err = ScriptArgs::try_parse_from([
1043 "foundry-cli",
1044 "Contract.sol",
1045 "--sender-nonce",
1046 &max_nonce,
1047 ])
1048 .unwrap_err();
1049 assert_eq!(err.kind(), clap::error::ErrorKind::ValueValidation);
1050 }
1051
1052 #[test]
1053 fn can_parse_shared_tempo_opts() {
1054 let args = ScriptArgs::parse_from([
1055 "foundry-cli",
1056 "Contract.sol",
1057 "--tempo.fee-token",
1058 "1",
1059 "--tempo.expires",
1060 "10",
1061 ]);
1062
1063 assert_eq!(
1064 args.tempo.fee_token,
1065 Some(address!("0x20C0000000000000000000000000000000000001"))
1066 );
1067 assert_eq!(args.tempo.expires, Some(10));
1068 }
1069
1070 #[test]
1071 fn can_parse_sponsor_tempo_opts() {
1072 let args = ScriptArgs::parse_from([
1073 "foundry-cli",
1074 "Contract.sol",
1075 "--tempo.sponsor",
1076 SESSION_ROOT_ADDRESS,
1077 "--tempo.sponsor-signer",
1078 "env://TEMPO_SPONSOR_PK",
1079 ]);
1080
1081 assert_eq!(args.tempo.sponsor, Some(session_root()));
1082 assert_eq!(args.tempo.sponsor_signer.as_deref(), Some("env://TEMPO_SPONSOR_PK"));
1083 }
1084
1085 #[test]
1086 fn can_parse_full_tempo_opts() {
1087 let args =
1088 ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--tempo.nonce-key", "1"]);
1089
1090 assert_eq!(args.tempo.nonce_key, Some(U256::from(1)));
1091 }
1092
1093 #[test]
1094 fn can_parse_tempo_session_opt() {
1095 let args = ScriptArgs::parse_from([
1096 "foundry-cli",
1097 "Contract.sol",
1098 "--tempo.session",
1099 SESSION_ID_HEX,
1100 ]);
1101
1102 assert_eq!(args.tempo.session, Some(B256::from([0x11; 32])),);
1103 }
1104
1105 #[tokio::test]
1106 async fn tempo_session_sets_script_sender_to_root_account() {
1107 let temp = tempdir().unwrap();
1108 let session_id = B256::from([0x22; 32]);
1109 let root = session_root();
1110 let chain_id = foundry_common::DEV_CHAIN_ID;
1111
1112 let _guard = TempoHomeGuard::set(temp.path()).await;
1113 upsert_session_entry(active_session_entry(session_id, root, chain_id)).unwrap();
1114
1115 let args = ScriptArgs::parse_from([
1116 "foundry-cli",
1117 "Contract.sol",
1118 "--tempo.session",
1119 &format!("{session_id:?}"),
1120 ]);
1121 let evm_opts = EvmOpts {
1122 networks: NetworkConfigs::with_tempo(),
1123 env: foundry_evm::opts::Env { chain_id: Some(chain_id), ..Default::default() },
1124 ..Default::default()
1125 };
1126
1127 let state = args.preprocess::<TempoEvmNetwork>(Config::default(), evm_opts).await.unwrap();
1128 assert_eq!(state.script_config.evm_opts.sender, root);
1129 }
1130
1131 #[tokio::test]
1132 async fn tempo_session_resume_multi_defers_session_sender_until_reexecution() {
1133 let temp = tempdir().unwrap();
1134 let session_id = B256::from([0x55; 32]);
1135 let root = session_root();
1136 let chain_id = 4217;
1137
1138 let _guard = TempoHomeGuard::set(temp.path()).await;
1139 upsert_session_entry(active_session_entry(session_id, root, chain_id)).unwrap();
1140
1141 let args = ScriptArgs::parse_from([
1142 "foundry-cli",
1143 "Contract.sol",
1144 "--resume",
1145 "--multi",
1146 "--tempo.session",
1147 &format!("{session_id:?}"),
1148 ]);
1149 let evm_opts = EvmOpts { networks: NetworkConfigs::with_tempo(), ..Default::default() };
1150
1151 let state = args.preprocess::<TempoEvmNetwork>(Config::default(), evm_opts).await.unwrap();
1152 assert_ne!(state.script_config.evm_opts.sender, root);
1153 }
1154
1155 #[tokio::test]
1156 async fn tempo_session_resume_defers_session_sender_until_reexecution() {
1157 let temp = tempdir().unwrap();
1158 let session_id = B256::from([0x77; 32]);
1159 let root = session_root();
1160 let chain_id = 4217;
1161
1162 let _guard = TempoHomeGuard::set(temp.path()).await;
1163 upsert_session_entry(active_session_entry(session_id, root, chain_id)).unwrap();
1164
1165 let args = ScriptArgs::parse_from([
1166 "foundry-cli",
1167 "Contract.sol",
1168 "--resume",
1169 "--tempo.session",
1170 &format!("{session_id:?}"),
1171 ]);
1172 let evm_opts = EvmOpts { networks: NetworkConfigs::with_tempo(), ..Default::default() };
1173
1174 let state = args.preprocess::<TempoEvmNetwork>(Config::default(), evm_opts).await.unwrap();
1175 assert_ne!(state.script_config.evm_opts.sender, root);
1176 }
1177
1178 #[tokio::test]
1179 async fn tempo_session_non_resume_multi_sets_sender_without_chain_validation() {
1180 let temp = tempdir().unwrap();
1181 let session_id = B256::from([0x66; 32]);
1182 let root = session_root();
1183 let chain_id = 4217;
1184
1185 let _guard = TempoHomeGuard::set(temp.path()).await;
1186 upsert_session_entry(active_session_entry(session_id, root, chain_id)).unwrap();
1187
1188 let args = ScriptArgs::parse_from([
1189 "foundry-cli",
1190 "Contract.sol",
1191 "--multi",
1192 "--tempo.session",
1193 &format!("{session_id:?}"),
1194 ]);
1195 let evm_opts = EvmOpts { networks: NetworkConfigs::with_tempo(), ..Default::default() };
1196
1197 let state = args.preprocess::<TempoEvmNetwork>(Config::default(), evm_opts).await.unwrap();
1198 assert_eq!(state.script_config.evm_opts.sender, root);
1199 }
1200
1201 #[tokio::test]
1202 async fn tempo_session_initial_broadcast_sets_sender_without_chain_validation() {
1203 let temp = tempdir().unwrap();
1204 let session_id = B256::from([0x88; 32]);
1205 let root = session_root();
1206 let chain_id = 4217;
1207
1208 let _guard = TempoHomeGuard::set(temp.path()).await;
1209 upsert_session_entry(active_session_entry(session_id, root, chain_id)).unwrap();
1210
1211 let args = ScriptArgs::parse_from([
1212 "foundry-cli",
1213 "Contract.sol",
1214 "--broadcast",
1215 "--tempo.session",
1216 &format!("{session_id:?}"),
1217 ]);
1218 let evm_opts = EvmOpts { networks: NetworkConfigs::with_tempo(), ..Default::default() };
1219
1220 let state = args.preprocess::<TempoEvmNetwork>(Config::default(), evm_opts).await.unwrap();
1221 assert_eq!(state.script_config.evm_opts.sender, root);
1222 }
1223
1224 #[tokio::test]
1225 async fn tempo_session_env_selects_tempo_network() {
1226 let temp = tempdir().unwrap();
1227 let _guard = TempoHomeGuard::set(temp.path()).await;
1228 let session_id = B256::from([0x44; 32]);
1229 unsafe { std::env::set_var(TEMPO_SESSION_ID_ENV, format!("{session_id:?}")) };
1231
1232 let args = ScriptArgs::parse_from(["foundry-cli", "Contract.sol"]);
1233 let (_, evm_opts) = args.resolved_evm_opts().await.unwrap();
1234
1235 assert!(evm_opts.networks.is_tempo());
1236 }
1237
1238 #[tokio::test]
1239 async fn tempo_session_rejects_explicit_script_wallet_signer() {
1240 let temp = tempdir().unwrap();
1241 let session_id = B256::from([0x33; 32]);
1242 let root = session_root();
1243 let chain_id = foundry_common::DEV_CHAIN_ID;
1244
1245 let _guard = TempoHomeGuard::set(temp.path()).await;
1246 upsert_session_entry(active_session_entry(session_id, root, chain_id)).unwrap();
1247
1248 let args = ScriptArgs::parse_from([
1249 "foundry-cli",
1250 "Contract.sol",
1251 "--tempo.session",
1252 &format!("{session_id:?}"),
1253 "--private-key",
1254 SESSION_PRIVATE_KEY,
1255 ]);
1256 let evm_opts = EvmOpts {
1257 networks: NetworkConfigs::with_tempo(),
1258 env: foundry_evm::opts::Env { chain_id: Some(chain_id), ..Default::default() },
1259 ..Default::default()
1260 };
1261
1262 let err = match args.preprocess::<TempoEvmNetwork>(Config::default(), evm_opts).await {
1263 Ok(_) => panic!("expected --tempo.session with --private-key to fail"),
1264 Err(err) => err,
1265 };
1266 assert!(err.to_string().contains("explicit wallet signer"), "{err}");
1267 }
1268
1269 #[test]
1270 fn can_parse_unlocked() {
1271 let args = ScriptArgs::parse_from([
1272 "foundry-cli",
1273 "Contract.sol",
1274 "--sender",
1275 "0x4e59b44847b379578588920ca78fbf26c0b4956c",
1276 "--unlocked",
1277 ]);
1278 assert!(args.unlocked);
1279
1280 let key = U256::ZERO;
1281 let args = ScriptArgs::try_parse_from([
1282 "foundry-cli",
1283 "Contract.sol",
1284 "--sender",
1285 "0x4e59b44847b379578588920ca78fbf26c0b4956c",
1286 "--unlocked",
1287 "--private-key",
1288 &key.to_string(),
1289 ]);
1290 assert!(args.is_err());
1291 }
1292
1293 #[test]
1294 fn can_merge_script_config() {
1295 let args = ScriptArgs::parse_from([
1296 "foundry-cli",
1297 "Contract.sol",
1298 "--etherscan-api-key",
1299 "goerli",
1300 ]);
1301 let config = args.load_config().unwrap();
1302 assert_eq!(config.etherscan_api_key, Some("goerli".to_string()));
1303 }
1304
1305 #[test]
1306 fn can_disable_code_size_limit() {
1307 let args =
1308 ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--disable-code-size-limit"]);
1309 assert!(args.disable_code_size_limit);
1310
1311 let result = ScriptResult::<Ethereum>::default();
1312 let contracts = ContractsByArtifact::default();
1313 let create = Address::ZERO;
1314 assert!(
1315 args.check_contract_sizes(ContractSizeLimits::default(), &result, &contracts, create)
1316 .is_ok()
1317 );
1318 }
1319
1320 #[test]
1321 fn can_parse_verifier_url() {
1322 let args = ScriptArgs::parse_from([
1323 "foundry-cli",
1324 "script",
1325 "script/Test.s.sol:TestScript",
1326 "--fork-url",
1327 "http://localhost:8545",
1328 "--verifier-url",
1329 "http://localhost:3000/api/verify",
1330 "--etherscan-api-key",
1331 "blacksmith",
1332 "--broadcast",
1333 "--verify",
1334 "-vvvvv",
1335 ]);
1336 assert_eq!(
1337 args.verifier.verifier_url,
1338 Some("http://localhost:3000/api/verify".to_string())
1339 );
1340 }
1341
1342 #[test]
1343 fn can_extract_code_size_limit() {
1344 let args = ScriptArgs::parse_from([
1345 "foundry-cli",
1346 "script",
1347 "script/Test.s.sol:TestScript",
1348 "--fork-url",
1349 "http://localhost:8545",
1350 "--broadcast",
1351 "--code-size-limit",
1352 "50000",
1353 ]);
1354 assert_eq!(args.evm.env.code_size_limit, Some(50000));
1355 }
1356
1357 #[test]
1360 fn cli_code_size_limit_is_honoured_by_check() {
1361 let args = ScriptArgs::parse_from([
1362 "foundry-cli",
1363 "script",
1364 "script/Test.s.sol:TestScript",
1365 "--code-size-limit",
1366 "2147483647",
1367 ]);
1368 assert_eq!(args.evm.env.code_size_limit, Some(2147483647));
1371 }
1372
1373 #[test]
1374 fn can_extract_script_etherscan_key() {
1375 let temp = tempdir().unwrap();
1376 let root = temp.path();
1377
1378 let config = r#"
1379 [profile.default]
1380 etherscan_api_key = "amoy"
1381
1382 [etherscan]
1383 amoy = { key = "https://etherscan-amoy.com/" }
1384 "#;
1385
1386 let toml_file = root.join(Config::FILE_NAME);
1387 fs::write(toml_file, config).unwrap();
1388 let args = ScriptArgs::parse_from([
1389 "foundry-cli",
1390 "Contract.sol",
1391 "--etherscan-api-key",
1392 "amoy",
1393 "--root",
1394 root.as_os_str().to_str().unwrap(),
1395 ]);
1396
1397 let config = args.load_config().unwrap();
1398 let amoy = config.get_etherscan_api_key(Some(NamedChain::PolygonAmoy.into()));
1399 assert_eq!(amoy, Some("https://etherscan-amoy.com/".to_string()));
1400 }
1401
1402 #[test]
1403 fn can_extract_script_rpc_alias() {
1404 let temp = tempdir().unwrap();
1405 let root = temp.path();
1406
1407 let config = r#"
1408 [profile.default]
1409
1410 [rpc_endpoints]
1411 polygonAmoy = "https://polygon-amoy.g.alchemy.com/v2/${_CAN_EXTRACT_RPC_ALIAS}"
1412 "#;
1413
1414 let toml_file = root.join(Config::FILE_NAME);
1415 fs::write(toml_file, config).unwrap();
1416 let args = ScriptArgs::parse_from([
1417 "foundry-cli",
1418 "DeployV1",
1419 "--rpc-url",
1420 "polygonAmoy",
1421 "--root",
1422 root.as_os_str().to_str().unwrap(),
1423 ]);
1424
1425 let err = args.load_config_and_evm_opts().unwrap_err();
1426
1427 assert!(err.downcast::<UnresolvedEnvVarError>().is_ok());
1428
1429 unsafe {
1430 std::env::set_var("_CAN_EXTRACT_RPC_ALIAS", "123456");
1431 }
1432 let (config, evm_opts) = args.load_config_and_evm_opts().unwrap();
1433 assert_eq!(config.eth_rpc_url, Some("polygonAmoy".to_string()));
1434 assert_eq!(
1435 evm_opts.fork_url,
1436 Some("https://polygon-amoy.g.alchemy.com/v2/123456".to_string())
1437 );
1438 }
1439
1440 #[test]
1441 fn can_extract_script_rpc_and_etherscan_alias() {
1442 let temp = tempdir().unwrap();
1443 let root = temp.path();
1444
1445 let config = r#"
1446 [profile.default]
1447
1448 [rpc_endpoints]
1449 amoy = "https://polygon-amoy.g.alchemy.com/v2/${_EXTRACT_RPC_ALIAS}"
1450
1451 [etherscan]
1452 amoy = { key = "${_ETHERSCAN_API_KEY}", chain = 80002, url = "https://amoy.polygonscan.com/" }
1453 "#;
1454
1455 let toml_file = root.join(Config::FILE_NAME);
1456 fs::write(toml_file, config).unwrap();
1457 let args = ScriptArgs::parse_from([
1458 "foundry-cli",
1459 "DeployV1",
1460 "--rpc-url",
1461 "amoy",
1462 "--etherscan-api-key",
1463 "amoy",
1464 "--root",
1465 root.as_os_str().to_str().unwrap(),
1466 ]);
1467 let err = args.load_config_and_evm_opts().unwrap_err();
1468
1469 assert!(err.downcast::<UnresolvedEnvVarError>().is_ok());
1470
1471 unsafe {
1472 std::env::set_var("_EXTRACT_RPC_ALIAS", "123456");
1473 }
1474 unsafe {
1475 std::env::set_var("_ETHERSCAN_API_KEY", "etherscan_api_key");
1476 }
1477 let (config, evm_opts) = args.load_config_and_evm_opts().unwrap();
1478 assert_eq!(config.eth_rpc_url, Some("amoy".to_string()));
1479 assert_eq!(
1480 evm_opts.fork_url,
1481 Some("https://polygon-amoy.g.alchemy.com/v2/123456".to_string())
1482 );
1483 let etherscan = config.get_etherscan_api_key(Some(80002u64.into()));
1484 assert_eq!(etherscan, Some("etherscan_api_key".to_string()));
1485 let etherscan = config.get_etherscan_api_key(None);
1486 assert_eq!(etherscan, Some("etherscan_api_key".to_string()));
1487 }
1488
1489 #[test]
1490 fn can_extract_script_rpc_and_sole_etherscan_alias() {
1491 let temp = tempdir().unwrap();
1492 let root = temp.path();
1493
1494 let config = r#"
1495 [profile.default]
1496
1497 [rpc_endpoints]
1498 amoy = "https://polygon-amoy.g.alchemy.com/v2/${_SOLE_EXTRACT_RPC_ALIAS}"
1499
1500 [etherscan]
1501 amoy = { key = "${_SOLE_ETHERSCAN_API_KEY}" }
1502 "#;
1503
1504 let toml_file = root.join(Config::FILE_NAME);
1505 fs::write(toml_file, config).unwrap();
1506 let args = ScriptArgs::parse_from([
1507 "foundry-cli",
1508 "DeployV1",
1509 "--rpc-url",
1510 "amoy",
1511 "--root",
1512 root.as_os_str().to_str().unwrap(),
1513 ]);
1514 let err = args.load_config_and_evm_opts().unwrap_err();
1515
1516 assert!(err.downcast::<UnresolvedEnvVarError>().is_ok());
1517
1518 unsafe {
1519 std::env::set_var("_SOLE_EXTRACT_RPC_ALIAS", "123456");
1520 }
1521 unsafe {
1522 std::env::set_var("_SOLE_ETHERSCAN_API_KEY", "etherscan_api_key");
1523 }
1524 let (config, evm_opts) = args.load_config_and_evm_opts().unwrap();
1525 assert_eq!(
1526 evm_opts.fork_url,
1527 Some("https://polygon-amoy.g.alchemy.com/v2/123456".to_string())
1528 );
1529 let etherscan = config.get_etherscan_api_key(Some(80002u64.into()));
1530 assert_eq!(etherscan, Some("etherscan_api_key".to_string()));
1531 let etherscan = config.get_etherscan_api_key(None);
1532 assert_eq!(etherscan, Some("etherscan_api_key".to_string()));
1533 }
1534
1535 #[test]
1537 fn test_5923() {
1538 let args =
1539 ScriptArgs::parse_from(["foundry-cli", "DeployV1", "--priority-gas-price", "100"]);
1540 assert!(args.priority_gas_price.is_some());
1541 }
1542
1543 #[test]
1544 fn test_eip1559_fee_estimate() {
1545 let args = ScriptArgs::parse_from(["foundry-cli", "DeployV1"]);
1547 assert!(args.eip1559_fee_estimate.is_none());
1548
1549 let args = ScriptArgs::parse_from(["foundry-cli", "DeployV1", "--estimate", "aggressive"]);
1550 assert_eq!(args.eip1559_fee_estimate, Some(Eip1559FeeEstimatePreset::Aggressive));
1551 }
1552
1553 #[test]
1555 fn test_5910() {
1556 let args = ScriptArgs::parse_from([
1557 "foundry-cli",
1558 "--broadcast",
1559 "--with-gas-price",
1560 "0",
1561 "SolveTutorial",
1562 ]);
1563 assert!(args.with_gas_price.unwrap().is_zero());
1564 }
1565
1566 #[test]
1567 fn test_priority_gas_price_cannot_exceed_gas_price() {
1568 let args = ScriptArgs::parse_from([
1569 "foundry-cli",
1570 "--broadcast",
1571 "--with-gas-price",
1572 "100",
1573 "--priority-gas-price",
1574 "200",
1575 "Script",
1576 ]);
1577 assert!(args.priority_gas_price.unwrap() > args.with_gas_price.unwrap());
1579 }
1580}