@@ -21,15 +21,16 @@ use tokio::{
2121} ;
2222use tracing:: { info, instrument} ;
2323
24- use ethexe_common:: gear:: CodeState ;
24+ use ethexe_common:: { gear:: CodeState , injected :: Promise } ;
2525use ethexe_ethereum:: { TryGetReceipt , abi:: IMirror } ;
2626use ethexe_sdk:: VaraEthApi ;
2727use gear_core:: ids:: prelude:: CodeIdExt as _;
28- use gprimitives:: { ActorId , CodeId , H256 , U256 } ;
28+ use gprimitives:: { ActorId , CodeId , H256 , MessageId , U256 } ;
2929
3030const VFT_WASM_PATH : & str = "../target/wasm32-gear/release/extended_vft.opt.wasm" ;
3131const CODE_VALIDATION_TIMEOUT : Duration = Duration :: from_secs ( 180 ) ;
3232const EVENT_TIMEOUT : Duration = Duration :: from_secs ( 60 ) ;
33+ const INJECTED_PROMISE_TIMEOUT : Duration = Duration :: from_secs ( 120 ) ;
3334// 5 Vara
3435const TOP_UP_AMOUNT : u128 = 5 * 1_000_000_000_000 ;
3536const BURST_TOP_UP_AMOUNT : u128 = 25 * 1_000_000_000_000 ;
@@ -461,6 +462,22 @@ async fn mirror_mint_with_state_change(
461462 wait_for_state_hash_change ( & ctx. api , ctx. program_id , previous_state_hash) . await
462463}
463464
465+ async fn injected_watch_with_timeout (
466+ ctx : & TestVftContext ,
467+ payload : Vec < u8 > ,
468+ label : & str ,
469+ ) -> Result < ( MessageId , Promise ) > {
470+ timeout (
471+ INJECTED_PROMISE_TIMEOUT ,
472+ ctx. api
473+ . mirror ( ctx. program_id )
474+ . send_message_injected_and_watch ( payload, 0 ) ,
475+ )
476+ . await
477+ . with_context ( || format ! ( "timed out while waiting for {label} injected promise" ) ) ?
478+ . with_context ( || format ! ( "failed to send {label} injected message" ) )
479+ }
480+
464481async fn assert_initial_queries ( ctx : & TestVftContext ) -> Result < ( ) > {
465482 let metadata = query_metadata ( & ctx. api , ctx. program_id ) . await ?;
466483 assert_metadata ( ctx, & metadata) ;
@@ -798,6 +815,121 @@ async fn vft_full_lifecycle_on_testnet() -> Result<()> {
798815 assert_final_state ( & ctx. api , ctx. program_id , ctx. owner_actor_id , scenario. recipient ) . await
799816}
800817
818+ #[ tokio:: test]
819+ async fn vft_injected_transfer_then_second_mint_on_testnet ( ) -> Result < ( ) > {
820+ init_tracing ( ) ;
821+ log_step ( "Setup" ) ;
822+ let ctx = setup_vft_program ( ) . await ?;
823+ let mirror = ctx. api . mirror ( ctx. program_id ) ;
824+ let recipient = ActorId :: from ( 123_u64 ) ;
825+ let nonce_before_sequence = mirror. nonce ( ) . await ?;
826+
827+ log_step ( "Mirror Mint" ) ;
828+ let mut state_hash = mirror_mint_with_state_change ( & ctx, MIRROR_MINT_AMOUNT ) . await ?;
829+ let nonce_after_mirror_mint = mirror. nonce ( ) . await ?;
830+
831+ let owner_balance_after_mirror_mint =
832+ balance_of ( & ctx. api , ctx. program_id , ctx. owner_actor_id ) . await ?;
833+ let recipient_balance_after_mirror_mint = balance_of ( & ctx. api , ctx. program_id , recipient) . await ?;
834+ let supply_after_mirror_mint = total_supply ( & ctx. api , ctx. program_id ) . await ?;
835+
836+ assert_eq ! ( owner_balance_after_mirror_mint, MIRROR_MINT_AMOUNT ) ;
837+ assert_eq ! ( recipient_balance_after_mirror_mint, 0 ) ;
838+ assert_eq ! ( supply_after_mirror_mint, MIRROR_MINT_AMOUNT ) ;
839+ assert ! (
840+ nonce_after_mirror_mint > nonce_before_sequence,
841+ "mirror mint should advance mirror nonce before injected-only checks"
842+ ) ;
843+
844+ log_step ( "Injected Transfer" ) ;
845+ let ( _transfer_message_id, transfer_promise) = injected_watch_with_timeout (
846+ & ctx,
847+ vft_payload (
848+ "Transfer" ,
849+ ( recipient, INJECTED_TRANSFER_AMOUNT . to_string ( ) ) ,
850+ ) ,
851+ "first injected transfer" ,
852+ )
853+ . await ?;
854+
855+ assert_manual_success ( & transfer_promise. reply . code . to_bytes ( ) , "first injected transfer" ) ;
856+ assert ! ( decode_sails_reply:: <bool >(
857+ & transfer_promise. reply. payload,
858+ "Vft" ,
859+ "Transfer" ,
860+ ) ?) ;
861+ info ! (
862+ reply_code = ?transfer_promise. reply. code. to_bytes( ) ,
863+ "First injected transfer promise received"
864+ ) ;
865+
866+ state_hash = wait_for_state_hash_change ( & ctx. api , ctx. program_id , state_hash) . await ?;
867+
868+ let owner_balance_after_transfer = balance_of ( & ctx. api , ctx. program_id , ctx. owner_actor_id ) . await ?;
869+ let recipient_balance_after_transfer = balance_of ( & ctx. api , ctx. program_id , recipient) . await ?;
870+ let supply_after_transfer = total_supply ( & ctx. api , ctx. program_id ) . await ?;
871+
872+ assert_eq ! (
873+ owner_balance_after_transfer,
874+ MIRROR_MINT_AMOUNT - INJECTED_TRANSFER_AMOUNT
875+ ) ;
876+ assert_eq ! ( recipient_balance_after_transfer, INJECTED_TRANSFER_AMOUNT ) ;
877+ assert_eq ! ( supply_after_transfer, MIRROR_MINT_AMOUNT ) ;
878+
879+ log_step ( "Second Injected Mint" ) ;
880+ let ( _second_mint_message_id, second_mint_promise) = injected_watch_with_timeout (
881+ & ctx,
882+ vft_payload (
883+ "Mint" ,
884+ ( ctx. owner_actor_id , MIRROR_MINT_AMOUNT . to_string ( ) ) ,
885+ ) ,
886+ "second injected mint" ,
887+ )
888+ . await ?;
889+
890+ assert_manual_success ( & second_mint_promise. reply . code . to_bytes ( ) , "second injected mint" ) ;
891+ assert ! ( decode_sails_reply:: <bool >(
892+ & second_mint_promise. reply. payload,
893+ "Vft" ,
894+ "Mint" ,
895+ ) ?) ;
896+ info ! (
897+ reply_code = ?second_mint_promise. reply. code. to_bytes( ) ,
898+ "Second injected mint promise received"
899+ ) ;
900+
901+ state_hash = wait_for_state_hash_change ( & ctx. api , ctx. program_id , state_hash) . await ?;
902+
903+ let owner_balance_final = balance_of ( & ctx. api , ctx. program_id , ctx. owner_actor_id ) . await ?;
904+ let recipient_balance_final = balance_of ( & ctx. api , ctx. program_id , recipient) . await ?;
905+ let supply_final = total_supply ( & ctx. api , ctx. program_id ) . await ?;
906+ let nonce_after_sequence = mirror. nonce ( ) . await ?;
907+
908+ assert_eq ! (
909+ owner_balance_final,
910+ MIRROR_MINT_AMOUNT - INJECTED_TRANSFER_AMOUNT + MIRROR_MINT_AMOUNT
911+ ) ;
912+ assert_eq ! ( recipient_balance_final, INJECTED_TRANSFER_AMOUNT ) ;
913+ assert_eq ! ( supply_final, MIRROR_MINT_AMOUNT + MIRROR_MINT_AMOUNT ) ;
914+ assert_eq ! (
915+ nonce_after_sequence,
916+ nonce_after_mirror_mint,
917+ "injected-only sequence should not advance mirror nonce beyond the mirror mint"
918+ ) ;
919+ info ! (
920+ %state_hash,
921+ owner_balance_final,
922+ recipient_balance_final,
923+ supply_final,
924+ nonce_before_sequence = %nonce_before_sequence,
925+ nonce_after_mirror_mint = %nonce_after_mirror_mint,
926+ nonce_after_sequence = %nonce_after_sequence,
927+ "Injected transfer then second mint sequence reached expected final state"
928+ ) ;
929+
930+ Ok ( ( ) )
931+ }
932+
801933#[ tokio:: test]
802934async fn vft_mirror_parallel_burst_on_testnet ( ) -> Result < ( ) > {
803935 init_tracing ( ) ;
0 commit comments