@@ -10518,3 +10518,164 @@ fn parallel_callbacks() {
1051810518 assert_eq ! ( err. code( ) , ErrorCode :: CanisterCalledTrap ) ;
1051910519 assert ! ( err. description( ) . contains( "trap in second callback" ) ) ;
1052010520}
10521+
10522+ const REPLY_REJECT_CLEANUP_CALLBACK_WAT : & str = r#"
10523+ (module
10524+ (import "ic0" "call_new"
10525+ (func $ic0_call_new
10526+ (param i32 i32)
10527+ (param $method_name_src i32) (param $method_name_len i32)
10528+ (param $reply_fun i32) (param $reply_env i32)
10529+ (param $reject_fun i32) (param $reject_env i32)
10530+ ))
10531+ (import "ic0" "call_perform" (func $ic0_call_perform (result i32)))
10532+
10533+ (import "ic0" "trap" (func $ic0_trap (param i32) (param i32)))
10534+ (import "ic0" "call_on_cleanup" (func $ic0_call_on_cleanup (param i32) (param i32)))
10535+ (import "ic0" "msg_reject_code" (func $ic0_msg_reject_code (result i32)))
10536+
10537+ (import "ic0" "msg_reply" (func $msg_reply))
10538+ (import "ic0" "msg_reply_data_append"
10539+ (func $msg_reply_data_append (param i32) (param i32)))
10540+
10541+ (func $dummy
10542+ (call $msg_reply_data_append
10543+ (i32.const 300) (i32.const 1)) ;; refers to 9 on the heap
10544+ (call $msg_reply)
10545+ )
10546+
10547+
10548+ (func $test
10549+ (call $ic0_call_new
10550+ (i32.const 100) (i32.const 10) ;; callee canister id = 0
10551+ (i32.const 0) (i32.const 4) ;; refers to "test" on the heap
10552+ (i32.const 0) (i32.const 200) ;; on_reply closure at table index 0
10553+ (i32.const 1) (i32.const 200)) ;; on_reject closure at table index 1
10554+ (call $ic0_call_on_cleanup
10555+ (i32.const 2) (i32.const 200)) ;; cleanup closure at table index 2
10556+ (drop (call $ic0_call_perform))
10557+ )
10558+
10559+ (func $on_reply (param i32)
10560+ (call $ic0_trap
10561+ (i32.const 200) (i32.const 12)) ;; reply callback traps
10562+ )
10563+
10564+ (func $on_reject (param i32)
10565+ (call $ic0_trap
10566+ (i32.const 200) (i32.const 12)) ;; reject callback traps
10567+ )
10568+
10569+ (func $on_cleanup (param i32)
10570+ (i32.store8 ;; cleanup can't reply, so
10571+ (i32.const 300) (call $ic0_msg_reject_code)) ;; we write cleanup code to memory and retrieve it via $dummy
10572+ )
10573+
10574+ (export "canister_update test" (func $test))
10575+ (export "canister_update dummy" (func $dummy))
10576+ (memory $memory 1)
10577+ (export "memory" (memory $memory))
10578+ (data (i32.const 0) "test")
10579+ (data (i32.const 100) "\00\00\00\00\00\00\00\00\01\01") ;; cansister_id of the installed canister is 0
10580+ (data (i32.const 200) "trap message")
10581+ (data (i32.const 300) "\09")
10582+ (table 3 3 funcref)
10583+ (elem (i32.const 0) $on_reply $on_reject $on_cleanup)
10584+ )
10585+ "# ;
10586+
10587+ #[ test]
10588+ fn can_access_reject_code_in_cleanup_call_rejected ( ) {
10589+ const CANISTER_SIMPLE_WAT : & str = r#"
10590+ (module
10591+ (import "ic0" "msg_reject"
10592+ (func $msg_reject (param $msg_reject_src i32) (param $msg_reject_size i32)))
10593+ (func $test
10594+ (call $msg_reject
10595+ (i32.const 0) (i32.const 26)) ;; refers to "explictly_rejected_message" on the heap
10596+ )
10597+ (export "canister_update test" (func $test))
10598+ (memory $memory 1)
10599+ (export "memory" (memory $memory))
10600+ (data (i32.const 0) "explictly_rejected_message")
10601+ )"# ;
10602+
10603+ let mut test = ExecutionTestBuilder :: new ( ) . build ( ) ;
10604+ let canister_id = test. create_canister ( Cycles :: new ( 1_000_000_000_000 ) ) ;
10605+
10606+ // assert that the hardcoded canister_id in REPLY_REJECT_CLEANUP_CALLBACK_WAT
10607+ // matches the one from instantiation
10608+ assert_eq ! ( canister_id, CanisterId :: from_u64( 0 ) ) ;
10609+
10610+ test. install_canister ( canister_id, wat:: parse_str ( CANISTER_SIMPLE_WAT ) . unwrap ( ) )
10611+ . unwrap ( ) ;
10612+
10613+ let main_canister_id = test. create_canister ( Cycles :: new ( 1_000_000_000_000 ) ) ;
10614+ test. install_canister (
10615+ main_canister_id,
10616+ wat:: parse_str ( REPLY_REJECT_CLEANUP_CALLBACK_WAT ) . unwrap ( ) ,
10617+ )
10618+ . unwrap ( ) ;
10619+
10620+ // Initial value at the cleanup reference is 9
10621+ let result = test. ingress ( main_canister_id, "dummy" , vec ! [ ] ) . unwrap ( ) ;
10622+ assert_eq ! ( WasmResult :: Reply ( vec![ 9 ] ) , result) ;
10623+
10624+ // Reject callback traps
10625+ let result = test. ingress ( main_canister_id, "test" , vec ! [ ] ) ;
10626+ assert ! ( result. is_err( ) ) ;
10627+
10628+ // msg_reject_code should be written to memory now via cleanup callback
10629+ // reject code is 4 when a canister explicitly rejects
10630+ let result = test. ingress ( main_canister_id, "dummy" , vec ! [ ] ) . unwrap ( ) ;
10631+ assert_eq ! ( WasmResult :: Reply ( vec![ 4 ] ) , result) ;
10632+ }
10633+
10634+ #[ test]
10635+ fn can_access_reject_code_in_cleanup_call_replied ( ) {
10636+ const CANISTER_SIMPLE_WAT : & str = r#"
10637+ (module
10638+ (import "ic0" "msg_reply" (func $msg_reply))
10639+ (import "ic0" "msg_reply_data_append"
10640+ (func $msg_reply_data_append (param i32) (param i32)))
10641+ (func $test
10642+ (call $msg_reply_data_append
10643+ (i32.const 0) (i32.const 26)) ;; refers to "explictly_approved_message" on the heap
10644+ (call $msg_reply)
10645+ )
10646+ (export "canister_update test" (func $test))
10647+ (memory $memory 1)
10648+ (export "memory" (memory $memory))
10649+ (data (i32.const 0) "explictly_approved_message")
10650+ )"# ;
10651+
10652+ let mut test = ExecutionTestBuilder :: new ( ) . build ( ) ;
10653+ let canister_id = test. create_canister ( Cycles :: new ( 1_000_000_000_000 ) ) ;
10654+
10655+ // assert that the hardcoded canister id in REPLY_REJECT_CLEANUP_CALLBACK_WAT
10656+ // matches the one from instantiation
10657+ assert_eq ! ( canister_id, CanisterId :: from_u64( 0 ) ) ;
10658+
10659+ test. install_canister ( canister_id, wat:: parse_str ( CANISTER_SIMPLE_WAT ) . unwrap ( ) )
10660+ . unwrap ( ) ;
10661+
10662+ let main_canister_id = test. create_canister ( Cycles :: new ( 1_000_000_000_000 ) ) ;
10663+ test. install_canister (
10664+ main_canister_id,
10665+ wat:: parse_str ( REPLY_REJECT_CLEANUP_CALLBACK_WAT ) . unwrap ( ) ,
10666+ )
10667+ . unwrap ( ) ;
10668+
10669+ // Initial value at the cleanup reference is 9
10670+ let result = test. ingress ( main_canister_id, "dummy" , vec ! [ ] ) . unwrap ( ) ;
10671+ assert_eq ! ( WasmResult :: Reply ( vec![ 9 ] ) , result) ;
10672+
10673+ // Reply callback traps
10674+ let result = test. ingress ( main_canister_id, "test" , vec ! [ ] ) ;
10675+ assert ! ( result. is_err( ) ) ;
10676+
10677+ // msg_reject_code should be written to memory now via cleanup callback
10678+ // canister replied would be reject code 0
10679+ let result = test. ingress ( main_canister_id, "dummy" , vec ! [ ] ) . unwrap ( ) ;
10680+ assert_eq ! ( WasmResult :: Reply ( vec![ 0 ] ) , result) ;
10681+ }
0 commit comments