Skip to content

Commit 43ed279

Browse files
authored
refactor(script): isolate Tempo session signer handling (foundry-rs#15089)
1 parent 7fcce27 commit 43ed279

7 files changed

Lines changed: 370 additions & 273 deletions

File tree

crates/cast/tests/cli/keychain.rs

Lines changed: 121 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@ use anvil::NodeConfig;
44
use foundry_evm::core::tempo::PATH_USD_ADDRESS;
55
use foundry_test_utils::{TestCommand, util::OutputExt};
66
use path_slash::PathExt;
7-
use std::{
8-
fs,
9-
path::{Path, PathBuf},
10-
};
7+
use std::{fs, path::Path};
118

129
/// Anvil test accounts (standard mnemonic).
1310
mod accounts {
@@ -24,20 +21,21 @@ fn path_usd() -> String {
2421
const MISSING_SESSION_ID: &str =
2522
"0x5555555555555555555555555555555555555555555555555555555555555555";
2623

27-
fn cast_bin() -> PathBuf {
28-
std::env::current_exe()
29-
.expect("current test executable")
30-
.parent()
31-
.expect("deps dir")
32-
.parent()
33-
.expect("target debug dir")
34-
.join(format!("cast{}", std::env::consts::EXE_SUFFIX))
35-
}
36-
3724
fn batch_send_transfer_call(path_usd: &str) -> String {
3825
format!("{path_usd}::transfer(address,uint256):{},0", accounts::ADDR3)
3926
}
4027

28+
fn cast_send_session_script(path_usd: &str) -> String {
29+
format!(
30+
r#"#!/bin/sh
31+
set -eu
32+
test -n "${{TEMPO_SESSION_ID:-}}"
33+
"${{CAST_BIN}}" send "{path_usd}" 'transfer(address,uint256)' "{recipient}" 0 --rpc-url "${{RPC_URL}}" --tempo.fee-token "{path_usd}" --async
34+
"#,
35+
recipient = accounts::ADDR3,
36+
)
37+
}
38+
4139
fn create_session(cmd: &mut TestCommand, tempo_home: &Path, chain_id: &str) -> (String, String) {
4240
let path_usd = path_usd();
4341

@@ -475,34 +473,20 @@ printf '%s\n' "${TEMPO_SESSION_ID}" > "$1"
475473
}
476474
);
477475

478-
casttest!(wallet_session_run_for_cast_send_submits_with_session_key, async |_prj, cmd| {
476+
casttest!(wallet_session_run_for_cast_send_submits_with_session_key, async |prj, cmd| {
479477
let (_, handle) = anvil::spawn(NodeConfig::test_tempo()).await;
480478
let rpc = handle.http_endpoint();
481479
let tempo_home = tempfile::tempdir().unwrap();
482480
let child_dir = tempfile::tempdir().unwrap();
483481
let child_script = child_dir.path().join("session-cast-send.sh");
484482
let path_usd = path_usd();
485-
let cast_bin = cast_bin();
486-
fs::write(
487-
&child_script,
488-
format!(
489-
r#"#!/bin/sh
490-
set -eu
491-
test -n "${{TEMPO_SESSION_ID:-}}"
492-
"${{CAST_BIN}}" send "{}" 'transfer(address,uint256)' "{}" 0 --rpc-url "${{RPC_URL}}" --tempo.fee-token "{}" --async
493-
"#,
494-
path_usd,
495-
accounts::ADDR3,
496-
path_usd,
497-
),
498-
)
499-
.expect("write child script");
483+
fs::write(&child_script, cast_send_session_script(&path_usd)).expect("write child script");
500484

501485
let for_command = format!("sh {}", child_script.to_slash_lossy());
502486

503487
cmd.cast_fuse();
504488
cmd.env("TEMPO_HOME", tempo_home.path());
505-
cmd.env("CAST_BIN", &cast_bin);
489+
cmd.env("CAST_BIN", prj.foundry_bin_path("cast"));
506490
cmd.env("RPC_URL", &rpc);
507491
let assertion = cmd
508492
.args([
@@ -524,24 +508,22 @@ test -n "${{TEMPO_SESSION_ID:-}}"
524508
&rpc,
525509
])
526510
.assert_failure();
527-
let output = assertion.get_output();
528-
let stdout = output.stdout_lossy();
529-
let stderr = output.stderr_lossy();
511+
let stdout = assertion.get_output().stdout_lossy();
512+
let stderr = assertion.get_output().stderr_lossy();
530513

531514
assert_async_tx_hash(&stdout, "child cast send");
532515
assert_session_cleanup_failure(&stderr);
533516
assert_session_file_status_without_key(tempo_home.path(), "failed");
534517
});
535518

536-
casttest!(wallet_session_run_for_batch_send_submits_with_session_key, async |_prj, cmd| {
519+
casttest!(wallet_session_run_for_batch_send_submits_with_session_key, async |prj, cmd| {
537520
let (_, handle) = anvil::spawn(NodeConfig::test_tempo()).await;
538521
let rpc = handle.http_endpoint();
539522
let tempo_home = tempfile::tempdir().unwrap();
540523
let child_dir = tempfile::tempdir().unwrap();
541524
let child_script = child_dir.path().join("session-batch-send.sh");
542525
let path_usd = path_usd();
543526
let call = batch_send_transfer_call(&path_usd);
544-
let cast_bin = cast_bin();
545527
fs::write(
546528
&child_script,
547529
format!(
@@ -558,7 +540,7 @@ test -n "${{TEMPO_SESSION_ID:-}}"
558540

559541
cmd.cast_fuse();
560542
cmd.env("TEMPO_HOME", tempo_home.path());
561-
cmd.env("CAST_BIN", &cast_bin);
543+
cmd.env("CAST_BIN", prj.foundry_bin_path("cast"));
562544
cmd.env("RPC_URL", &rpc);
563545
let assertion = cmd
564546
.args([
@@ -580,15 +562,108 @@ test -n "${{TEMPO_SESSION_ID:-}}"
580562
&rpc,
581563
])
582564
.assert_failure();
583-
let output = assertion.get_output();
584-
let stdout = output.stdout_lossy();
585-
let stderr = output.stderr_lossy();
565+
let stdout = assertion.get_output().stdout_lossy();
566+
let stderr = assertion.get_output().stderr_lossy();
586567

587568
assert_async_tx_hash(&stdout, "child cast batch-send");
588569
assert_session_cleanup_failure(&stderr);
589570
assert_session_file_status_without_key(tempo_home.path(), "failed");
590571
});
591572

573+
casttest!(wallet_session_run_for_forge_script_submits_with_session_key, async |prj, cmd| {
574+
let (_, handle) = anvil::spawn(NodeConfig::test_tempo()).await;
575+
let rpc = handle.http_endpoint();
576+
let tempo_home = tempfile::tempdir().unwrap();
577+
let path_usd = path_usd();
578+
579+
foundry_test_utils::util::initialize(prj.root());
580+
let script = prj.add_script(
581+
"SessionForgeScript.s.sol",
582+
&format!(
583+
r#"
584+
import "forge-std/Script.sol";
585+
586+
interface PathUsdLike {{
587+
function transfer(address to, uint256 amount) external returns (bool);
588+
}}
589+
590+
contract SessionForgeScript is Script {{
591+
function run() external {{
592+
vm.startBroadcast();
593+
PathUsdLike({path_usd}).transfer({recipient}, 0);
594+
vm.stopBroadcast();
595+
}}
596+
}}
597+
"#,
598+
recipient = accounts::ADDR3,
599+
),
600+
);
601+
602+
let for_command = format!(
603+
"{} script {} --tc SessionForgeScript --broadcast --timeout 1 --rpc-url {} --root {}",
604+
prj.ensure_foundry_bin("forge").to_slash_lossy(),
605+
script.to_slash_lossy(),
606+
rpc,
607+
prj.root().to_slash_lossy(),
608+
);
609+
610+
cmd.cast_fuse();
611+
cmd.env("TEMPO_HOME", tempo_home.path());
612+
let assertion = cmd
613+
.args([
614+
"wallet",
615+
"session",
616+
"--root",
617+
accounts::ADDR1,
618+
"--expires",
619+
"10m",
620+
"--target",
621+
&path_usd,
622+
"--selector",
623+
"transfer(address,uint256)",
624+
"--spend-limit",
625+
"PathUSD=0",
626+
"--for",
627+
&for_command,
628+
"--private-key",
629+
accounts::PK1,
630+
"--rpc-url",
631+
&rpc,
632+
])
633+
.assert_failure();
634+
let stdout = assertion.get_output().stdout_lossy();
635+
let stderr = assertion.get_output().stderr_lossy();
636+
637+
assert!(
638+
stdout.contains("ONCHAIN EXECUTION COMPLETE & SUCCESSFUL."),
639+
"expected forge script broadcast completion\nstdout:\n{stdout}\nstderr:\n{stderr}"
640+
);
641+
642+
let run_latest = foundry_common::fs::json_files(&prj.root().join("broadcast"))
643+
.find(|path| path.ends_with("run-latest.json"))
644+
.unwrap_or_else(|| {
645+
panic!("expected forge broadcast artifact\nstdout:\n{stdout}\nstderr:\n{stderr}")
646+
});
647+
let broadcast = fs::read_to_string(&run_latest).expect("read forge broadcast artifact");
648+
let broadcast: serde_json::Value =
649+
serde_json::from_str(&broadcast).expect("forge broadcast artifact is valid JSON");
650+
let tx = &broadcast["transactions"][0];
651+
652+
assert_eq!(tx["transactionType"], "CALL", "unexpected forge broadcast tx: {tx}");
653+
assert_eq!(tx["function"], "transfer(address,uint256)", "unexpected forge broadcast tx: {tx}");
654+
assert_eq!(
655+
tx["contractAddress"].as_str().map(str::to_ascii_lowercase),
656+
Some(path_usd.to_ascii_lowercase()),
657+
"unexpected forge broadcast tx: {tx}"
658+
);
659+
assert!(
660+
tx["hash"].as_str().is_some_and(|hash| hash.starts_with("0x")),
661+
"forge broadcast tx should have a submitted hash: {tx}"
662+
);
663+
assert_session_cleanup_failure(&stderr);
664+
assert_session_file_status_without_key(tempo_home.path(), "failed");
665+
});
666+
592667
casttest!(batch_send_uses_tempo_session_id_env, async |_prj, cmd| {
593668
let (_, handle) = anvil::spawn(NodeConfig::test_tempo()).await;
594669
let rpc = handle.http_endpoint();
@@ -618,15 +693,14 @@ casttest!(batch_send_uses_tempo_session_id_env, async |_prj, cmd| {
618693
assert_async_tx_hash(&stdout, "cast batch-send");
619694
});
620695

621-
casttest!(wallet_session_run_for_grandchild_cast_send_inherits_session_key, async |_prj, cmd| {
696+
casttest!(wallet_session_run_for_grandchild_cast_send_inherits_session_key, async |prj, cmd| {
622697
let (_, handle) = anvil::spawn(NodeConfig::test_tempo()).await;
623698
let rpc = handle.http_endpoint();
624699
let tempo_home = tempfile::tempdir().unwrap();
625700
let child_dir = tempfile::tempdir().unwrap();
626701
let child_script = child_dir.path().join("session-child.sh");
627702
let grandchild_script = child_dir.path().join("session-grandchild-cast-send.sh");
628703
let path_usd = path_usd();
629-
let cast_bin = cast_bin();
630704

631705
fs::write(
632706
&child_script,
@@ -638,25 +712,15 @@ sh "$1"
638712
)
639713
.expect("write child script");
640714

641-
fs::write(
642-
&grandchild_script,
643-
format!(
644-
r#"#!/bin/sh
645-
set -eu
646-
test -n "${{TEMPO_SESSION_ID:-}}"
647-
"${{CAST_BIN}}" send "{}" 'transfer(address,uint256)' "{}" 0 --rpc-url "${{RPC_URL}}" --tempo.fee-token "{}" --async
648-
"#,
649-
path_usd, accounts::ADDR3, path_usd,
650-
),
651-
)
652-
.expect("write grandchild script");
715+
fs::write(&grandchild_script, cast_send_session_script(&path_usd))
716+
.expect("write grandchild script");
653717

654718
let for_command =
655719
format!("sh {} {}", child_script.to_slash_lossy(), grandchild_script.to_slash_lossy());
656720

657721
cmd.cast_fuse();
658722
cmd.env("TEMPO_HOME", tempo_home.path());
659-
cmd.env("CAST_BIN", &cast_bin);
723+
cmd.env("CAST_BIN", prj.foundry_bin_path("cast"));
660724
cmd.env("RPC_URL", &rpc);
661725
let assertion = cmd
662726
.args([
@@ -678,9 +742,8 @@ test -n "${{TEMPO_SESSION_ID:-}}"
678742
&rpc,
679743
])
680744
.assert_failure();
681-
let output = assertion.get_output();
682-
let stdout = output.stdout_lossy();
683-
let stderr = output.stderr_lossy();
745+
let stdout = assertion.get_output().stdout_lossy();
746+
let stderr = assertion.get_output().stderr_lossy();
684747

685748
assert_async_tx_hash(&stdout, "grandchild cast send");
686749
assert_session_cleanup_failure(&stderr);

0 commit comments

Comments
 (0)