@@ -4,10 +4,7 @@ use anvil::NodeConfig;
44use foundry_evm:: core:: tempo:: PATH_USD_ADDRESS ;
55use foundry_test_utils:: { TestCommand , util:: OutputExt } ;
66use 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).
1310mod accounts {
@@ -24,20 +21,21 @@ fn path_usd() -> String {
2421const 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-
3724fn 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+
4139fn 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\n stdout:\n {stdout}\n stderr:\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\n stdout:\n {stdout}\n stderr:\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+
592667casttest ! ( 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