1use crate::ScriptArgs;
8use alloy_primitives::Address;
9use clap::Args;
10use eyre::{Result, WrapErr};
11use foundry_cli::{opts::TEMPO_SESSION_ID_ENV, utils::LoadConfig};
12use std::{
13 ffi::{OsStr, OsString},
14 path::{Path, PathBuf},
15 process::Command,
16};
17
18const SESSION_WRAPPER_ENV_REMOVE: &[&str] = &[
19 TEMPO_SESSION_ID_ENV,
20 "ETH_KEYSTORE",
21 "ETH_KEYSTORE_ACCOUNT",
22 "ETH_PASSWORD",
23 "TEMPO_ACCESS_KEY",
24 "TEMPO_ROOT_ACCOUNT",
25];
26
27#[derive(Clone, Debug, Default, Args)]
29pub struct ScriptWalletSessionArgs {
30 #[arg(
32 long = "session",
33 id = "wallet_session",
34 conflicts_with_all = ["tempo_session", "unlocked"]
35 )]
36 pub enabled: bool,
37
38 #[arg(
40 long = "session-root",
41 id = "wallet_session_root",
42 value_name = "ADDRESS",
43 requires = "wallet_session"
44 )]
45 pub root: Option<Address>,
46
47 #[arg(
49 long = "session-expires",
50 id = "wallet_session_expires",
51 value_name = "DURATION",
52 requires = "wallet_session"
53 )]
54 pub expires: Option<String>,
55
56 #[arg(
58 long = "session-scope",
59 id = "wallet_session_scope",
60 value_name = "SCOPE",
61 requires = "wallet_session"
62 )]
63 pub scopes: Vec<String>,
64
65 #[arg(
67 long = "session-target",
68 id = "wallet_session_target",
69 value_name = "ADDRESS",
70 requires = "wallet_session"
71 )]
72 pub target: Option<Address>,
73
74 #[arg(
76 long = "session-selector",
77 id = "wallet_session_selector",
78 value_name = "SELECTOR",
79 requires = "wallet_session"
80 )]
81 pub selectors: Vec<String>,
82
83 #[arg(
85 long = "session-spend-limit",
86 id = "wallet_session_spend_limit",
87 value_name = "LIMIT",
88 requires = "wallet_session"
89 )]
90 pub spend_limits: Vec<String>,
91
92 #[arg(
94 long = "session-interactive",
95 id = "wallet_session_interactive",
96 requires = "wallet_session"
97 )]
98 pub interactive: bool,
99
100 #[arg(
102 long = "session-private-key",
103 id = "wallet_session_private_key",
104 value_name = "RAW_PRIVATE_KEY",
105 requires = "wallet_session"
106 )]
107 pub private_key: Option<String>,
108
109 #[arg(
111 long = "session-mnemonic",
112 id = "wallet_session_mnemonic",
113 value_name = "MNEMONIC",
114 requires = "wallet_session"
115 )]
116 pub mnemonic: Option<String>,
117
118 #[arg(
120 long = "session-mnemonic-passphrase",
121 id = "wallet_session_mnemonic_passphrase",
122 value_name = "PASSPHRASE",
123 requires = "wallet_session_mnemonic"
124 )]
125 pub mnemonic_passphrase: Option<String>,
126
127 #[arg(
129 long = "session-hd-path",
130 id = "wallet_session_hd_path",
131 value_name = "PATH",
132 requires = "wallet_session"
133 )]
134 pub hd_path: Option<String>,
135
136 #[arg(
138 long = "session-mnemonic-index",
139 id = "wallet_session_mnemonic_index",
140 value_name = "INDEX",
141 requires = "wallet_session"
142 )]
143 pub mnemonic_index: Option<u32>,
144
145 #[arg(
147 long = "session-keystore",
148 id = "wallet_session_keystore",
149 value_name = "PATH",
150 requires = "wallet_session"
151 )]
152 pub keystore: Option<String>,
153
154 #[arg(
156 long = "session-account",
157 id = "wallet_session_account",
158 value_name = "ACCOUNT_NAME",
159 requires = "wallet_session"
160 )]
161 pub account: Option<String>,
162
163 #[arg(
165 long = "session-password",
166 id = "wallet_session_password",
167 value_name = "PASSWORD",
168 requires = "wallet_session"
169 )]
170 pub password: Option<String>,
171
172 #[arg(
174 long = "session-password-file",
175 id = "wallet_session_password_file",
176 value_name = "PATH",
177 requires = "wallet_session"
178 )]
179 pub password_file: Option<String>,
180
181 #[arg(long = "session-ledger", id = "wallet_session_ledger", requires = "wallet_session")]
183 pub ledger: bool,
184
185 #[arg(long = "session-trezor", id = "wallet_session_trezor", requires = "wallet_session")]
187 pub trezor: bool,
188}
189
190impl ScriptArgs {
191 pub(super) fn run_wallet_session_wrapper(&self) -> Result<()> {
196 let command = self.wallet_session_command_from_env()?;
197 let mut child = Command::new(&command.program);
198 child.args(&command.args);
199 for key in SESSION_WRAPPER_ENV_REMOVE {
202 child.env_remove(key);
203 }
204
205 let status = child.status().wrap_err_with(|| {
206 format!(
207 "failed to run `{}` for forge script wallet session",
208 command.program.to_string_lossy()
209 )
210 })?;
211
212 if status.success() {
213 Ok(())
214 } else {
215 match status.code() {
216 Some(code) => {
217 eyre::bail!("forge script wallet session exited with code {code}");
218 }
219 None => {
220 eyre::bail!("forge script wallet session terminated by a signal");
221 }
222 }
223 }
224 }
225
226 fn wallet_session_command_from_env(&self) -> Result<WalletSessionCommand> {
227 let forge = std::env::current_exe().wrap_err("failed to resolve current forge binary")?;
228 let cast = sibling_binary(&forge, "cast");
229 self.wallet_session_command_from_raw_args(std::env::args_os(), forge.into(), cast.into())
230 }
231
232 fn wallet_session_command_from_raw_args<I>(
238 &self,
239 raw_args: I,
240 forge_program: OsString,
241 cast_program: OsString,
242 ) -> Result<WalletSessionCommand>
243 where
244 I: IntoIterator<Item = OsString>,
245 {
246 self.wallet_session.validate(self)?;
247
248 let mut inner = strip_wallet_session_args(raw_args)?;
251 let Some(program) = inner.first_mut() else {
252 eyre::bail!("failed to reconstruct forge script command");
253 };
254 *program = forge_program;
255 let inner = quote_command(&inner)?;
256
257 let (config, evm_opts) = self.load_config_and_evm_opts()?;
258 let session = &self.wallet_session;
259 let mut args = vec![OsString::from("wallet"), OsString::from("session")];
260
261 if let Some(root) = session.root {
264 push_arg(&mut args, "--root", root.to_string());
265 push_arg(&mut args, "--from", root.to_string());
266 }
267 push_opt_arg(&mut args, "--expires", session.expires.as_deref());
268
269 push_repeated_args(&mut args, "--scope", &session.scopes);
270 push_opt_arg(&mut args, "--target", session.target);
271 push_repeated_args(&mut args, "--selector", &session.selectors);
272 push_repeated_args(&mut args, "--spend-limit", &session.spend_limits);
273
274 if let Some(rpc_url) = evm_opts.fork_url.as_ref() {
275 push_arg(&mut args, "--rpc-url", rpc_url);
276 }
277 push_opt_arg(&mut args, "--chain", evm_opts.env.chain_id);
278 if config.eth_rpc_accept_invalid_certs {
279 args.push("--insecure".into());
280 }
281 if config.eth_rpc_no_proxy {
282 args.push("--no-proxy".into());
283 }
284 push_opt_arg(&mut args, "--rpc-timeout", config.eth_rpc_timeout);
285
286 session.push_root_signer_args(&mut args);
287
288 push_arg(&mut args, "--for", inner);
289
290 Ok(WalletSessionCommand { program: cast_program, args })
291 }
292}
293
294impl ScriptWalletSessionArgs {
295 const STRIP_BOOL_ARGS: &[&str] =
296 &["--session", "--session-interactive", "--session-ledger", "--session-trezor"];
297
298 const STRIP_VALUE_ARGS: &[&str] = &[
299 "--session-root",
300 "--session-expires",
301 "--session-scope",
302 "--session-target",
303 "--session-selector",
304 "--session-spend-limit",
305 "--session-private-key",
306 "--session-mnemonic",
307 "--session-mnemonic-passphrase",
308 "--session-hd-path",
309 "--session-mnemonic-index",
310 "--session-keystore",
311 "--session-account",
312 "--session-password",
313 "--session-password-file",
314 ];
315
316 fn validate(&self, args: &ScriptArgs) -> Result<()> {
321 if !self.enabled {
322 return Ok(());
323 }
324 if !args.should_broadcast() {
327 eyre::bail!("forge script --session requires --broadcast or --resume");
328 }
329 if args.debug {
330 eyre::bail!("forge script --session cannot be used with --debug");
331 }
332 Ok(())
333 }
334
335 fn push_root_signer_args(&self, args: &mut Vec<OsString>) {
341 if self.interactive {
342 args.push("--interactive".into());
343 }
344 for (name, value) in [
345 ("--private-key", self.private_key.as_deref()),
346 ("--mnemonic", self.mnemonic.as_deref()),
347 ("--mnemonic-passphrase", self.mnemonic_passphrase.as_deref()),
348 ("--hd-path", self.hd_path.as_deref()),
349 ("--keystore", self.keystore.as_deref()),
350 ("--account", self.account.as_deref()),
351 ("--password", self.password.as_deref()),
352 ("--password-file", self.password_file.as_deref()),
353 ] {
354 push_opt_arg(args, name, value);
355 }
356 push_opt_arg(args, "--mnemonic-index", self.mnemonic_index);
357 if self.ledger {
358 args.push("--ledger".into());
359 }
360 if self.trezor {
361 args.push("--trezor".into());
362 }
363 }
364}
365
366#[derive(Debug)]
367struct WalletSessionCommand {
368 program: OsString,
369 args: Vec<OsString>,
370}
371
372fn push_arg(args: &mut Vec<OsString>, name: &'static str, value: impl Into<OsString>) {
373 args.push(name.into());
374 args.push(value.into());
375}
376
377fn push_opt_arg(
378 args: &mut Vec<OsString>,
379 name: &'static str,
380 value: Option<impl std::fmt::Display>,
381) {
382 if let Some(value) = value {
383 push_arg(args, name, value.to_string());
384 }
385}
386
387fn push_repeated_args(args: &mut Vec<OsString>, name: &'static str, values: &[String]) {
388 for value in values {
389 push_arg(args, name, value.as_str());
390 }
391}
392
393fn sibling_binary(current: &Path, name: &str) -> PathBuf {
395 let mut binary = current.with_file_name(name);
396 if cfg!(windows) {
397 binary.set_extension("exe");
398 }
399 if binary.exists() { binary } else { PathBuf::from(name) }
400}
401
402fn strip_wallet_session_args<I>(raw_args: I) -> Result<Vec<OsString>>
407where
408 I: IntoIterator<Item = OsString>,
409{
410 let mut out = Vec::new();
411 let mut args = raw_args.into_iter();
412 let mut after_double_dash = false;
413
414 while let Some(arg) = args.next() {
415 if after_double_dash {
416 out.push(arg);
417 continue;
418 }
419 if arg == OsStr::new("--") {
420 after_double_dash = true;
421 out.push(arg);
422 continue;
423 }
424
425 let Some(arg_str) = arg.to_str() else {
426 out.push(arg);
427 continue;
428 };
429
430 if ScriptWalletSessionArgs::STRIP_BOOL_ARGS.contains(&arg_str) {
431 continue;
432 }
433 let (flag, has_value) =
434 arg_str.split_once('=').map_or((arg_str, false), |(flag, _)| (flag, true));
435 if ScriptWalletSessionArgs::STRIP_VALUE_ARGS.contains(&flag) {
436 if has_value {
439 continue;
440 }
441 args.next().ok_or_else(|| eyre::eyre!("{arg_str} requires a value"))?;
442 continue;
443 }
444
445 out.push(arg);
446 }
447
448 Ok(out)
449}
450
451fn quote_command(args: &[OsString]) -> Result<String> {
454 args.iter().map(quote_arg).collect::<Result<Vec<_>>>().map(|args| args.join(" "))
455}
456
457fn quote_arg(arg: &OsString) -> Result<String> {
459 let arg = arg
460 .to_str()
461 .ok_or_else(|| eyre::eyre!("forge script wallet session commands must be valid UTF-8"))?;
462 if arg.is_empty() {
463 return Ok("\"\"".to_string());
464 }
465 if arg.chars().all(|ch| !ch.is_whitespace() && !matches!(ch, '"' | '\'' | '\\')) {
466 return Ok(arg.to_string());
467 }
468
469 let mut quoted = String::with_capacity(arg.len() + 2);
470 quoted.push('"');
471 for ch in arg.chars() {
472 if matches!(ch, '"' | '\\') {
473 quoted.push('\\');
474 }
475 quoted.push(ch);
476 }
477 quoted.push('"');
478 Ok(quoted)
479}
480
481#[cfg(test)]
482mod tests {
483 use super::*;
484 use clap::Parser;
485 use foundry_config::Config;
486 use std::{borrow::Cow, fs};
487 use tempfile::tempdir;
488
489 const SESSION_PRIVATE_KEY: &str =
490 "0x59c6995e998f97a5a004497e5da3b5d2b2b66a87f064d39c44da0b6d6e4f8ff0";
491 const SESSION_ROOT_ADDRESS: &str = "0x1111111111111111111111111111111111111111";
492 const SESSION_SCOPE_ADDRESS: &str = "0x2222222222222222222222222222222222222222";
493
494 fn option_value<'a>(args: &'a [Cow<'_, str>], option: &str) -> Option<&'a str> {
495 args.windows(2)
496 .find_map(|window| (window[0].as_ref() == option).then_some(window[1].as_ref()))
497 }
498
499 fn parse_script_args(args: &[&str]) -> ScriptArgs {
500 ScriptArgs::parse_from(["foundry-cli"].into_iter().chain(args.iter().copied()))
501 }
502
503 fn raw_forge_script_args<'a>(args: &'a [&'a str]) -> impl Iterator<Item = OsString> + 'a {
504 ["forge", "script"].into_iter().chain(args.iter().copied()).map(OsString::from)
505 }
506
507 fn wallet_session_command(
508 args: &ScriptArgs,
509 raw_args: &[&str],
510 ) -> Result<WalletSessionCommand> {
511 args.wallet_session_command_from_raw_args(
512 raw_forge_script_args(raw_args),
513 OsString::from("/tmp/forge"),
514 OsString::from("/tmp/cast"),
515 )
516 }
517
518 fn command_args(command: &WalletSessionCommand) -> Vec<Cow<'_, str>> {
519 command.args.iter().map(|arg| arg.to_string_lossy()).collect()
520 }
521
522 fn inner_for_command<'a>(args: &'a [Cow<'_, str>]) -> &'a str {
523 let for_pos = args.iter().position(|arg| arg.as_ref() == "--for").unwrap();
524 args[for_pos + 1].as_ref()
525 }
526
527 fn session_root() -> Address {
528 SESSION_ROOT_ADDRESS.parse().unwrap()
529 }
530
531 fn session_target() -> Address {
532 SESSION_SCOPE_ADDRESS.parse().unwrap()
533 }
534
535 #[test]
536 fn can_parse_session_wrapper() {
537 let root = session_root();
538 let target = session_target();
539 let args = ScriptArgs::parse_from([
540 "foundry-cli",
541 "Deploy.s.sol",
542 "--broadcast",
543 "--session",
544 "--session-root",
545 &root.to_string(),
546 "--session-expires",
547 "10m",
548 "--session-target",
549 &target.to_string(),
550 "--session-selector",
551 "register(address)",
552 "--session-spend-limit",
553 "PathUSD=0",
554 "--session-private-key",
555 SESSION_PRIVATE_KEY,
556 ]);
557
558 assert!(args.wallet_session.enabled);
559 assert_eq!(args.wallet_session.root, Some(root));
560 assert_eq!(args.wallet_session.expires.as_deref(), Some("10m"));
561 assert_eq!(args.wallet_session.target, Some(target));
562 assert_eq!(args.wallet_session.selectors, ["register(address)"]);
563 assert_eq!(args.wallet_session.spend_limits, ["PathUSD=0"]);
564 assert_eq!(args.wallet_session.private_key.as_deref(), Some(SESSION_PRIVATE_KEY));
565 }
566
567 #[test]
568 fn session_wrapper_conflicts_with_existing_session_id() {
569 let err = ScriptArgs::try_parse_from([
570 "foundry-cli",
571 "Deploy.s.sol",
572 "--session",
573 "--tempo.session",
574 "0x1111111111111111111111111111111111111111111111111111111111111111",
575 ])
576 .unwrap_err();
577
578 assert!(err.to_string().contains("cannot be used with"), "{err}");
579 }
580
581 #[test]
582 fn session_wrapper_rejects_dry_run() {
583 let raw_args = [
584 "Deploy.s.sol",
585 "--session",
586 "--session-root",
587 SESSION_ROOT_ADDRESS,
588 "--session-expires",
589 "10m",
590 "--session-scope",
591 SESSION_SCOPE_ADDRESS,
592 ];
593 let args = parse_script_args(&raw_args);
594
595 let err = wallet_session_command(&args, &raw_args).unwrap_err();
596
597 assert!(err.to_string().contains("requires --broadcast or --resume"), "{err}");
598 }
599
600 #[test]
601 fn session_wrapper_rejects_debug() {
602 let raw_args = [
603 "Deploy.s.sol",
604 "--broadcast",
605 "--debug",
606 "--session",
607 "--session-root",
608 SESSION_ROOT_ADDRESS,
609 "--session-expires",
610 "10m",
611 "--session-scope",
612 SESSION_SCOPE_ADDRESS,
613 ];
614 let args = parse_script_args(&raw_args);
615
616 let err = wallet_session_command(&args, &raw_args).unwrap_err();
617
618 assert!(err.to_string().contains("cannot be used with --debug"), "{err}");
619 }
620
621 #[test]
622 fn session_wrapper_rewrites_to_cast_session_command() {
623 let root = session_root();
624 let target = session_target();
625 let root_arg = root.to_string();
626 let target_arg = target.to_string();
627 let raw_args = [
628 "Deploy.s.sol",
629 "--broadcast",
630 "--rpc-url",
631 "http://127.0.0.1:8545",
632 "--chain",
633 "4217",
634 "--session",
635 "--session-root",
636 &root_arg,
637 "--session-expires",
638 "10m",
639 "--session-target",
640 &target_arg,
641 "--session-selector",
642 "register(address)",
643 "--session-private-key",
644 SESSION_PRIVATE_KEY,
645 ];
646 let args = parse_script_args(&raw_args);
647
648 let command = wallet_session_command(&args, &raw_args).unwrap();
649
650 assert_eq!(command.program, OsString::from("/tmp/cast"));
651 let command_args = command_args(&command);
652 assert_eq!(command_args[0], "wallet");
653 assert_eq!(command_args[1], "session");
654 assert!(command_args.contains(&"--root".into()));
655 assert!(command_args.contains(&root.to_string().into()));
656 assert!(command_args.contains(&"--from".into()));
657 assert!(command_args.contains(&"--target".into()));
658 assert!(command_args.contains(&target.to_string().into()));
659 assert!(command_args.contains(&"--selector".into()));
660 assert!(command_args.contains(&"register(address)".into()));
661 assert!(command_args.contains(&"--private-key".into()));
662 assert!(command_args.contains(&SESSION_PRIVATE_KEY.into()));
663
664 let inner = inner_for_command(&command_args);
665 assert!(inner.starts_with("/tmp/forge script Deploy.s.sol --broadcast"), "{inner}");
666 assert!(inner.contains("--rpc-url http://127.0.0.1:8545"), "{inner}");
667 assert!(inner.contains("--chain 4217"), "{inner}");
668 assert!(!inner.contains("--session "), "{inner}");
669 assert!(!inner.contains("--session-private-key"), "{inner}");
670 }
671
672 #[test]
673 fn session_wrapper_cleans_inherited_tempo_signer_env_for_outer_cast() {
674 assert_eq!(
675 SESSION_WRAPPER_ENV_REMOVE,
676 [
677 TEMPO_SESSION_ID_ENV,
678 "ETH_KEYSTORE",
679 "ETH_KEYSTORE_ACCOUNT",
680 "ETH_PASSWORD",
681 "TEMPO_ACCESS_KEY",
682 "TEMPO_ROOT_ACCOUNT",
683 ]
684 );
685 }
686
687 #[test]
688 fn session_wrapper_uses_project_config_for_cast_session() {
689 let temp = tempdir().unwrap();
690 let project_root = temp.path();
691 fs::write(
692 project_root.join(Config::FILE_NAME),
693 r#"
694 [profile.default]
695 eth_rpc_url = "http://127.0.0.1:8545"
696 chain_id = 4217
697 "#,
698 )
699 .unwrap();
700
701 let root = session_root();
702 let root_arg = root.to_string();
703 let project_root_arg = project_root.to_string_lossy();
704 let raw_args = [
705 "Deploy.s.sol",
706 "--root",
707 &project_root_arg,
708 "--broadcast",
709 "--session",
710 "--session-root",
711 &root_arg,
712 "--session-expires",
713 "10m",
714 "--session-scope",
715 SESSION_SCOPE_ADDRESS,
716 ];
717 let args = parse_script_args(&raw_args);
718
719 let command = wallet_session_command(&args, &raw_args).unwrap();
720
721 let command_args = command_args(&command);
722 assert_eq!(option_value(&command_args, "--rpc-url"), Some("http://127.0.0.1:8545"));
723 assert_eq!(option_value(&command_args, "--chain"), Some("4217"));
724
725 let inner = inner_for_command(&command_args);
726 assert!(inner.contains("--root "), "{inner}");
727 if !cfg!(windows) {
728 assert!(inner.contains(project_root.to_string_lossy().as_ref()), "{inner}");
729 }
730 assert!(inner.ends_with("--broadcast"), "{inner}");
731 }
732
733 #[test]
734 fn session_wrapper_forwards_rpc_transport_flags_to_outer_cast() {
735 let root = session_root();
736 let root_arg = root.to_string();
737 let raw_args = [
738 "Deploy.s.sol",
739 "--broadcast",
740 "--rpc-url",
741 "https://127.0.0.1:8545",
742 "--insecure",
743 "--no-proxy",
744 "--rpc-timeout",
745 "7",
746 "--session",
747 "--session-root",
748 &root_arg,
749 "--session-expires",
750 "10m",
751 "--session-scope",
752 SESSION_SCOPE_ADDRESS,
753 "--session-private-key",
754 SESSION_PRIVATE_KEY,
755 ];
756 let args = parse_script_args(&raw_args);
757
758 let command = wallet_session_command(&args, &raw_args).unwrap();
759
760 let command_args = command_args(&command);
761 assert!(command_args.contains(&"--insecure".into()));
762 assert!(command_args.contains(&"--no-proxy".into()));
763 assert_eq!(option_value(&command_args, "--rpc-timeout"), Some("7"));
764
765 let inner = inner_for_command(&command_args);
766 assert!(inner.contains("--insecure"), "{inner}");
767 assert!(inner.contains("--no-proxy"), "{inner}");
768 assert!(inner.contains("--rpc-timeout 7"), "{inner}");
769 }
770
771 #[test]
772 fn session_wrapper_leaves_browser_for_inner_forge_validation() {
773 let raw_args = [
774 "Deploy.s.sol",
775 "--broadcast",
776 "--session",
777 "--session-root",
778 SESSION_ROOT_ADDRESS,
779 "--session-expires",
780 "10m",
781 "--session-scope",
782 SESSION_SCOPE_ADDRESS,
783 "--browser",
784 ];
785 let args = parse_script_args(&raw_args);
786
787 let command = wallet_session_command(&args, &raw_args).unwrap();
788 let command_args = command_args(&command);
789 let inner = inner_for_command(&command_args);
790
791 assert!(inner.contains("--browser"), "{inner}");
792 }
793
794 #[test]
795 fn session_wrapper_leaves_script_wallet_signers_for_inner_forge_validation() {
796 let raw_args = [
797 "Deploy.s.sol",
798 "--broadcast",
799 "--session",
800 "--session-root",
801 SESSION_ROOT_ADDRESS,
802 "--session-expires",
803 "10m",
804 "--session-scope",
805 SESSION_SCOPE_ADDRESS,
806 "--private-key",
807 SESSION_PRIVATE_KEY,
808 ];
809 let args = parse_script_args(&raw_args);
810
811 let command = wallet_session_command(&args, &raw_args).unwrap();
812 let command_args = command_args(&command);
813 let inner = inner_for_command(&command_args);
814
815 assert!(inner.contains("--private-key"), "{inner}");
816 assert!(!inner.contains("--session-private-key"), "{inner}");
817 }
818
819 #[test]
820 fn session_wrapper_does_not_infer_root_from_sender() {
821 let raw_args = [
822 "Deploy.s.sol",
823 "--broadcast",
824 "--session",
825 "--sender",
826 SESSION_ROOT_ADDRESS,
827 "--session-expires",
828 "10m",
829 "--session-scope",
830 SESSION_SCOPE_ADDRESS,
831 ];
832 let args = parse_script_args(&raw_args);
833
834 let command = wallet_session_command(&args, &raw_args).unwrap();
835 let command_args = command_args(&command);
836 assert_eq!(option_value(&command_args, "--root"), None);
837 assert_eq!(option_value(&command_args, "--from"), None);
838
839 let inner = inner_for_command(&command_args);
840 assert!(inner.contains(&format!("--sender {SESSION_ROOT_ADDRESS}")), "{inner}");
841 }
842
843 #[test]
844 fn session_wrapper_leaves_session_policy_requirements_to_cast() {
845 let raw_args = ["Deploy.s.sol", "--broadcast", "--session"];
846 let args = parse_script_args(&raw_args);
847
848 let command = wallet_session_command(&args, &raw_args).unwrap();
849 let command_args = command_args(&command);
850
851 assert_eq!(option_value(&command_args, "--root"), None);
852 assert_eq!(option_value(&command_args, "--expires"), None);
853 assert_eq!(option_value(&command_args, "--scope"), None);
854 assert_eq!(option_value(&command_args, "--target"), None);
855 }
856}