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) => eyre::bail!("forge script wallet session exited with code {code}"),
217 None => eyre::bail!("forge script wallet session terminated by a signal"),
218 }
219 }
220 }
221
222 fn wallet_session_command_from_env(&self) -> Result<WalletSessionCommand> {
223 let forge = std::env::current_exe().wrap_err("failed to resolve current forge binary")?;
224 let cast = sibling_binary(&forge, "cast");
225 self.wallet_session_command_from_raw_args(std::env::args_os(), forge.into(), cast.into())
226 }
227
228 fn wallet_session_command_from_raw_args<I>(
234 &self,
235 raw_args: I,
236 forge_program: OsString,
237 cast_program: OsString,
238 ) -> Result<WalletSessionCommand>
239 where
240 I: IntoIterator<Item = OsString>,
241 {
242 self.wallet_session.validate(self)?;
243
244 let mut inner = strip_wallet_session_args(raw_args)?;
247 let Some(program) = inner.first_mut() else {
248 eyre::bail!("failed to reconstruct forge script command");
249 };
250 *program = forge_program;
251 let inner = quote_command(&inner)?;
252
253 let (config, evm_opts) = self.load_config_and_evm_opts()?;
254 let session = &self.wallet_session;
255 let mut args = vec![OsString::from("wallet"), OsString::from("session")];
256
257 if let Some(root) = session.root {
260 push_arg(&mut args, "--root", root.to_string());
261 push_arg(&mut args, "--from", root.to_string());
262 }
263 push_opt_arg(&mut args, "--expires", session.expires.as_deref());
264
265 push_repeated_args(&mut args, "--scope", &session.scopes);
266 push_opt_arg(&mut args, "--target", session.target);
267 push_repeated_args(&mut args, "--selector", &session.selectors);
268 push_repeated_args(&mut args, "--spend-limit", &session.spend_limits);
269
270 if let Some(rpc_url) = evm_opts.fork_url.as_ref() {
271 push_arg(&mut args, "--rpc-url", rpc_url);
272 }
273 push_opt_arg(&mut args, "--chain", evm_opts.env.chain_id);
274 if config.eth_rpc_accept_invalid_certs {
275 args.push("--insecure".into());
276 }
277 if config.eth_rpc_no_proxy {
278 args.push("--no-proxy".into());
279 }
280 push_opt_arg(&mut args, "--rpc-timeout", config.eth_rpc_timeout);
281
282 session.push_root_signer_args(&mut args);
283
284 push_arg(&mut args, "--for", inner);
285
286 Ok(WalletSessionCommand { program: cast_program, args })
287 }
288}
289
290impl ScriptWalletSessionArgs {
291 const STRIP_BOOL_ARGS: &[&str] =
292 &["--session", "--session-interactive", "--session-ledger", "--session-trezor"];
293
294 const STRIP_VALUE_ARGS: &[&str] = &[
295 "--session-root",
296 "--session-expires",
297 "--session-scope",
298 "--session-target",
299 "--session-selector",
300 "--session-spend-limit",
301 "--session-private-key",
302 "--session-mnemonic",
303 "--session-mnemonic-passphrase",
304 "--session-hd-path",
305 "--session-mnemonic-index",
306 "--session-keystore",
307 "--session-account",
308 "--session-password",
309 "--session-password-file",
310 ];
311
312 fn validate(&self, args: &ScriptArgs) -> Result<()> {
317 if !self.enabled {
318 return Ok(());
319 }
320 if !args.should_broadcast() {
323 eyre::bail!("forge script --session requires --broadcast or --resume");
324 }
325 if args.debug {
326 eyre::bail!("forge script --session cannot be used with --debug");
327 }
328 Ok(())
329 }
330
331 fn push_root_signer_args(&self, args: &mut Vec<OsString>) {
337 if self.interactive {
338 args.push("--interactive".into());
339 }
340 for (name, value) in [
341 ("--private-key", self.private_key.as_deref()),
342 ("--mnemonic", self.mnemonic.as_deref()),
343 ("--mnemonic-passphrase", self.mnemonic_passphrase.as_deref()),
344 ("--hd-path", self.hd_path.as_deref()),
345 ("--keystore", self.keystore.as_deref()),
346 ("--account", self.account.as_deref()),
347 ("--password", self.password.as_deref()),
348 ("--password-file", self.password_file.as_deref()),
349 ] {
350 push_opt_arg(args, name, value);
351 }
352 push_opt_arg(args, "--mnemonic-index", self.mnemonic_index);
353 if self.ledger {
354 args.push("--ledger".into());
355 }
356 if self.trezor {
357 args.push("--trezor".into());
358 }
359 }
360}
361
362#[derive(Debug)]
363struct WalletSessionCommand {
364 program: OsString,
365 args: Vec<OsString>,
366}
367
368fn push_arg(args: &mut Vec<OsString>, name: &'static str, value: impl Into<OsString>) {
369 args.push(name.into());
370 args.push(value.into());
371}
372
373fn push_opt_arg(
374 args: &mut Vec<OsString>,
375 name: &'static str,
376 value: Option<impl std::fmt::Display>,
377) {
378 if let Some(value) = value {
379 push_arg(args, name, value.to_string());
380 }
381}
382
383fn push_repeated_args(args: &mut Vec<OsString>, name: &'static str, values: &[String]) {
384 for value in values {
385 push_arg(args, name, value.as_str());
386 }
387}
388
389fn sibling_binary(current: &Path, name: &str) -> PathBuf {
391 let mut binary = current.with_file_name(name);
392 if cfg!(windows) {
393 binary.set_extension("exe");
394 }
395 if binary.exists() { binary } else { PathBuf::from(name) }
396}
397
398fn strip_wallet_session_args<I>(raw_args: I) -> Result<Vec<OsString>>
403where
404 I: IntoIterator<Item = OsString>,
405{
406 let mut out = Vec::new();
407 let mut args = raw_args.into_iter();
408 let mut after_double_dash = false;
409
410 while let Some(arg) = args.next() {
411 if after_double_dash {
412 out.push(arg);
413 continue;
414 }
415 if arg == OsStr::new("--") {
416 after_double_dash = true;
417 out.push(arg);
418 continue;
419 }
420
421 let Some(arg_str) = arg.to_str() else {
422 out.push(arg);
423 continue;
424 };
425
426 if ScriptWalletSessionArgs::STRIP_BOOL_ARGS.contains(&arg_str) {
427 continue;
428 }
429 let (flag, has_value) =
430 arg_str.split_once('=').map_or((arg_str, false), |(flag, _)| (flag, true));
431 if ScriptWalletSessionArgs::STRIP_VALUE_ARGS.contains(&flag) {
432 if has_value {
435 continue;
436 }
437 args.next().ok_or_else(|| eyre::eyre!("{arg_str} requires a value"))?;
438 continue;
439 }
440
441 out.push(arg);
442 }
443
444 Ok(out)
445}
446
447fn quote_command(args: &[OsString]) -> Result<String> {
450 args.iter().map(quote_arg).collect::<Result<Vec<_>>>().map(|args| args.join(" "))
451}
452
453fn quote_arg(arg: &OsString) -> Result<String> {
455 let arg = arg
456 .to_str()
457 .ok_or_else(|| eyre::eyre!("forge script wallet session commands must be valid UTF-8"))?;
458 if arg.is_empty() {
459 return Ok("\"\"".to_string());
460 }
461 if arg.chars().all(|ch| !ch.is_whitespace() && !matches!(ch, '"' | '\'' | '\\')) {
462 return Ok(arg.to_string());
463 }
464
465 let mut quoted = String::with_capacity(arg.len() + 2);
466 quoted.push('"');
467 for ch in arg.chars() {
468 if matches!(ch, '"' | '\\') {
469 quoted.push('\\');
470 }
471 quoted.push(ch);
472 }
473 quoted.push('"');
474 Ok(quoted)
475}
476
477#[cfg(test)]
478mod tests {
479 use super::*;
480 use clap::Parser;
481 use foundry_config::Config;
482 use std::{borrow::Cow, fs};
483 use tempfile::tempdir;
484
485 const SESSION_PRIVATE_KEY: &str =
486 "0x59c6995e998f97a5a004497e5da3b5d2b2b66a87f064d39c44da0b6d6e4f8ff0";
487 const SESSION_ROOT_ADDRESS: &str = "0x1111111111111111111111111111111111111111";
488 const SESSION_SCOPE_ADDRESS: &str = "0x2222222222222222222222222222222222222222";
489
490 fn option_value<'a>(args: &'a [Cow<'_, str>], option: &str) -> Option<&'a str> {
491 args.windows(2)
492 .find_map(|window| (window[0].as_ref() == option).then_some(window[1].as_ref()))
493 }
494
495 fn parse_script_args(args: &[&str]) -> ScriptArgs {
496 ScriptArgs::parse_from(["foundry-cli"].into_iter().chain(args.iter().copied()))
497 }
498
499 fn raw_forge_script_args<'a>(args: &'a [&'a str]) -> impl Iterator<Item = OsString> + 'a {
500 ["forge", "script"].into_iter().chain(args.iter().copied()).map(OsString::from)
501 }
502
503 fn wallet_session_command(
504 args: &ScriptArgs,
505 raw_args: &[&str],
506 ) -> Result<WalletSessionCommand> {
507 args.wallet_session_command_from_raw_args(
508 raw_forge_script_args(raw_args),
509 OsString::from("/tmp/forge"),
510 OsString::from("/tmp/cast"),
511 )
512 }
513
514 fn command_args(command: &WalletSessionCommand) -> Vec<Cow<'_, str>> {
515 command.args.iter().map(|arg| arg.to_string_lossy()).collect()
516 }
517
518 fn inner_for_command<'a>(args: &'a [Cow<'_, str>]) -> &'a str {
519 let for_pos = args.iter().position(|arg| arg.as_ref() == "--for").unwrap();
520 args[for_pos + 1].as_ref()
521 }
522
523 fn session_root() -> Address {
524 SESSION_ROOT_ADDRESS.parse().unwrap()
525 }
526
527 fn session_target() -> Address {
528 SESSION_SCOPE_ADDRESS.parse().unwrap()
529 }
530
531 #[test]
532 fn can_parse_session_wrapper() {
533 let root = session_root();
534 let target = session_target();
535 let args = ScriptArgs::parse_from([
536 "foundry-cli",
537 "Deploy.s.sol",
538 "--broadcast",
539 "--session",
540 "--session-root",
541 &root.to_string(),
542 "--session-expires",
543 "10m",
544 "--session-target",
545 &target.to_string(),
546 "--session-selector",
547 "register(address)",
548 "--session-spend-limit",
549 "PathUSD=0",
550 "--session-private-key",
551 SESSION_PRIVATE_KEY,
552 ]);
553
554 assert!(args.wallet_session.enabled);
555 assert_eq!(args.wallet_session.root, Some(root));
556 assert_eq!(args.wallet_session.expires.as_deref(), Some("10m"));
557 assert_eq!(args.wallet_session.target, Some(target));
558 assert_eq!(args.wallet_session.selectors, ["register(address)"]);
559 assert_eq!(args.wallet_session.spend_limits, ["PathUSD=0"]);
560 assert_eq!(args.wallet_session.private_key.as_deref(), Some(SESSION_PRIVATE_KEY));
561 }
562
563 #[test]
564 fn session_wrapper_conflicts_with_existing_session_id() {
565 let err = ScriptArgs::try_parse_from([
566 "foundry-cli",
567 "Deploy.s.sol",
568 "--session",
569 "--tempo.session",
570 "0x1111111111111111111111111111111111111111111111111111111111111111",
571 ])
572 .unwrap_err();
573
574 assert!(err.to_string().contains("cannot be used with"), "{err}");
575 }
576
577 #[test]
578 fn session_wrapper_rejects_dry_run() {
579 let raw_args = [
580 "Deploy.s.sol",
581 "--session",
582 "--session-root",
583 SESSION_ROOT_ADDRESS,
584 "--session-expires",
585 "10m",
586 "--session-scope",
587 SESSION_SCOPE_ADDRESS,
588 ];
589 let args = parse_script_args(&raw_args);
590
591 let err = wallet_session_command(&args, &raw_args).unwrap_err();
592
593 assert!(err.to_string().contains("requires --broadcast or --resume"), "{err}");
594 }
595
596 #[test]
597 fn session_wrapper_rejects_debug() {
598 let raw_args = [
599 "Deploy.s.sol",
600 "--broadcast",
601 "--debug",
602 "--session",
603 "--session-root",
604 SESSION_ROOT_ADDRESS,
605 "--session-expires",
606 "10m",
607 "--session-scope",
608 SESSION_SCOPE_ADDRESS,
609 ];
610 let args = parse_script_args(&raw_args);
611
612 let err = wallet_session_command(&args, &raw_args).unwrap_err();
613
614 assert!(err.to_string().contains("cannot be used with --debug"), "{err}");
615 }
616
617 #[test]
618 fn session_wrapper_rewrites_to_cast_session_command() {
619 let root = session_root();
620 let target = session_target();
621 let root_arg = root.to_string();
622 let target_arg = target.to_string();
623 let raw_args = [
624 "Deploy.s.sol",
625 "--broadcast",
626 "--rpc-url",
627 "http://127.0.0.1:8545",
628 "--chain",
629 "4217",
630 "--session",
631 "--session-root",
632 &root_arg,
633 "--session-expires",
634 "10m",
635 "--session-target",
636 &target_arg,
637 "--session-selector",
638 "register(address)",
639 "--session-private-key",
640 SESSION_PRIVATE_KEY,
641 ];
642 let args = parse_script_args(&raw_args);
643
644 let command = wallet_session_command(&args, &raw_args).unwrap();
645
646 assert_eq!(command.program, OsString::from("/tmp/cast"));
647 let command_args = command_args(&command);
648 assert_eq!(command_args[0], "wallet");
649 assert_eq!(command_args[1], "session");
650 assert!(command_args.contains(&"--root".into()));
651 assert!(command_args.contains(&root.to_string().into()));
652 assert!(command_args.contains(&"--from".into()));
653 assert!(command_args.contains(&"--target".into()));
654 assert!(command_args.contains(&target.to_string().into()));
655 assert!(command_args.contains(&"--selector".into()));
656 assert!(command_args.contains(&"register(address)".into()));
657 assert!(command_args.contains(&"--private-key".into()));
658 assert!(command_args.contains(&SESSION_PRIVATE_KEY.into()));
659
660 let inner = inner_for_command(&command_args);
661 assert!(inner.starts_with("/tmp/forge script Deploy.s.sol --broadcast"), "{inner}");
662 assert!(inner.contains("--rpc-url http://127.0.0.1:8545"), "{inner}");
663 assert!(inner.contains("--chain 4217"), "{inner}");
664 assert!(!inner.contains("--session "), "{inner}");
665 assert!(!inner.contains("--session-private-key"), "{inner}");
666 }
667
668 #[test]
669 fn session_wrapper_cleans_inherited_tempo_signer_env_for_outer_cast() {
670 assert_eq!(
671 SESSION_WRAPPER_ENV_REMOVE,
672 [
673 TEMPO_SESSION_ID_ENV,
674 "ETH_KEYSTORE",
675 "ETH_KEYSTORE_ACCOUNT",
676 "ETH_PASSWORD",
677 "TEMPO_ACCESS_KEY",
678 "TEMPO_ROOT_ACCOUNT",
679 ]
680 );
681 }
682
683 #[test]
684 fn session_wrapper_uses_project_config_for_cast_session() {
685 let temp = tempdir().unwrap();
686 let project_root = temp.path();
687 fs::write(
688 project_root.join(Config::FILE_NAME),
689 r#"
690 [profile.default]
691 eth_rpc_url = "http://127.0.0.1:8545"
692 chain_id = 4217
693 "#,
694 )
695 .unwrap();
696
697 let root = session_root();
698 let root_arg = root.to_string();
699 let project_root_arg = project_root.to_string_lossy();
700 let raw_args = [
701 "Deploy.s.sol",
702 "--root",
703 &project_root_arg,
704 "--broadcast",
705 "--session",
706 "--session-root",
707 &root_arg,
708 "--session-expires",
709 "10m",
710 "--session-scope",
711 SESSION_SCOPE_ADDRESS,
712 ];
713 let args = parse_script_args(&raw_args);
714
715 let command = wallet_session_command(&args, &raw_args).unwrap();
716
717 let command_args = command_args(&command);
718 assert_eq!(option_value(&command_args, "--rpc-url"), Some("http://127.0.0.1:8545"));
719 assert_eq!(option_value(&command_args, "--chain"), Some("4217"));
720
721 let inner = inner_for_command(&command_args);
722 assert!(inner.contains("--root "), "{inner}");
723 if !cfg!(windows) {
724 assert!(inner.contains(project_root.to_string_lossy().as_ref()), "{inner}");
725 }
726 assert!(inner.ends_with("--broadcast"), "{inner}");
727 }
728
729 #[test]
730 fn session_wrapper_forwards_rpc_transport_flags_to_outer_cast() {
731 let root = session_root();
732 let root_arg = root.to_string();
733 let raw_args = [
734 "Deploy.s.sol",
735 "--broadcast",
736 "--rpc-url",
737 "https://127.0.0.1:8545",
738 "--insecure",
739 "--no-proxy",
740 "--rpc-timeout",
741 "7",
742 "--session",
743 "--session-root",
744 &root_arg,
745 "--session-expires",
746 "10m",
747 "--session-scope",
748 SESSION_SCOPE_ADDRESS,
749 "--session-private-key",
750 SESSION_PRIVATE_KEY,
751 ];
752 let args = parse_script_args(&raw_args);
753
754 let command = wallet_session_command(&args, &raw_args).unwrap();
755
756 let command_args = command_args(&command);
757 assert!(command_args.contains(&"--insecure".into()));
758 assert!(command_args.contains(&"--no-proxy".into()));
759 assert_eq!(option_value(&command_args, "--rpc-timeout"), Some("7"));
760
761 let inner = inner_for_command(&command_args);
762 assert!(inner.contains("--insecure"), "{inner}");
763 assert!(inner.contains("--no-proxy"), "{inner}");
764 assert!(inner.contains("--rpc-timeout 7"), "{inner}");
765 }
766
767 #[test]
768 fn session_wrapper_leaves_browser_for_inner_forge_validation() {
769 let raw_args = [
770 "Deploy.s.sol",
771 "--broadcast",
772 "--session",
773 "--session-root",
774 SESSION_ROOT_ADDRESS,
775 "--session-expires",
776 "10m",
777 "--session-scope",
778 SESSION_SCOPE_ADDRESS,
779 "--browser",
780 ];
781 let args = parse_script_args(&raw_args);
782
783 let command = wallet_session_command(&args, &raw_args).unwrap();
784 let command_args = command_args(&command);
785 let inner = inner_for_command(&command_args);
786
787 assert!(inner.contains("--browser"), "{inner}");
788 }
789
790 #[test]
791 fn session_wrapper_leaves_script_wallet_signers_for_inner_forge_validation() {
792 let raw_args = [
793 "Deploy.s.sol",
794 "--broadcast",
795 "--session",
796 "--session-root",
797 SESSION_ROOT_ADDRESS,
798 "--session-expires",
799 "10m",
800 "--session-scope",
801 SESSION_SCOPE_ADDRESS,
802 "--private-key",
803 SESSION_PRIVATE_KEY,
804 ];
805 let args = parse_script_args(&raw_args);
806
807 let command = wallet_session_command(&args, &raw_args).unwrap();
808 let command_args = command_args(&command);
809 let inner = inner_for_command(&command_args);
810
811 assert!(inner.contains("--private-key"), "{inner}");
812 assert!(!inner.contains("--session-private-key"), "{inner}");
813 }
814
815 #[test]
816 fn session_wrapper_does_not_infer_root_from_sender() {
817 let raw_args = [
818 "Deploy.s.sol",
819 "--broadcast",
820 "--session",
821 "--sender",
822 SESSION_ROOT_ADDRESS,
823 "--session-expires",
824 "10m",
825 "--session-scope",
826 SESSION_SCOPE_ADDRESS,
827 ];
828 let args = parse_script_args(&raw_args);
829
830 let command = wallet_session_command(&args, &raw_args).unwrap();
831 let command_args = command_args(&command);
832 assert_eq!(option_value(&command_args, "--root"), None);
833 assert_eq!(option_value(&command_args, "--from"), None);
834
835 let inner = inner_for_command(&command_args);
836 assert!(inner.contains(&format!("--sender {SESSION_ROOT_ADDRESS}")), "{inner}");
837 }
838
839 #[test]
840 fn session_wrapper_leaves_session_policy_requirements_to_cast() {
841 let raw_args = ["Deploy.s.sol", "--broadcast", "--session"];
842 let args = parse_script_args(&raw_args);
843
844 let command = wallet_session_command(&args, &raw_args).unwrap();
845 let command_args = command_args(&command);
846
847 assert_eq!(option_value(&command_args, "--root"), None);
848 assert_eq!(option_value(&command_args, "--expires"), None);
849 assert_eq!(option_value(&command_args, "--scope"), None);
850 assert_eq!(option_value(&command_args, "--target"), None);
851 }
852}