@@ -2,14 +2,15 @@ use candid::{Decode, Encode, Nat};
2
2
use ic_base_types:: { CanisterId , PrincipalId } ;
3
3
use ic_icrc1_index_ng:: {
4
4
GetAccountTransactionsArgs , GetAccountTransactionsResponse , GetAccountTransactionsResult ,
5
- GetBlocksResponse , IndexArg , InitArg as IndexInitArg , TransactionWithId ,
5
+ GetBlocksResponse , IndexArg , InitArg as IndexInitArg , ListSubaccountsArgs , TransactionWithId ,
6
+ DEFAULT_MAX_BLOCKS_PER_RESPONSE ,
6
7
} ;
7
8
use ic_icrc1_ledger:: { InitArgs as LedgerInitArgs , LedgerArgument } ;
8
9
use ic_icrc1_test_utils:: { valid_transactions_strategy, CallerTransferArg } ;
9
10
use ic_ledger_canister_core:: archive:: ArchiveOptions ;
10
11
use ic_state_machine_tests:: StateMachine ;
11
12
use icrc_ledger_types:: icrc:: generic_metadata_value:: MetadataValue as Value ;
12
- use icrc_ledger_types:: icrc1:: account:: Account ;
13
+ use icrc_ledger_types:: icrc1:: account:: { Account , Subaccount } ;
13
14
use icrc_ledger_types:: icrc1:: transfer:: { BlockIndex , TransferArg , TransferError } ;
14
15
use icrc_ledger_types:: icrc3:: blocks:: { BlockRange , GenericBlock , GetBlocksRequest } ;
15
16
use icrc_ledger_types:: icrc3:: transactions:: { Mint , Transaction , Transfer } ;
@@ -229,6 +230,30 @@ fn get_account_transactions(
229
230
. expect ( "Failed to perform GetAccountTransactionsArgs" )
230
231
}
231
232
233
+ fn list_subaccounts (
234
+ env : & StateMachine ,
235
+ index : CanisterId ,
236
+ principal : PrincipalId ,
237
+ start : Option < Subaccount > ,
238
+ ) -> Vec < Subaccount > {
239
+ Decode ! (
240
+ & env. execute_ingress_as(
241
+ principal,
242
+ index,
243
+ "list_subaccounts" ,
244
+ Encode !( & ListSubaccountsArgs {
245
+ owner: principal. into( ) ,
246
+ start,
247
+ } )
248
+ . unwrap( )
249
+ )
250
+ . expect( "failed to list_subaccounts" )
251
+ . bytes( ) ,
252
+ Vec <Subaccount >
253
+ )
254
+ . expect ( "failed to decode list_subaccounts response" )
255
+ }
256
+
232
257
// Assert that the index canister contains the same blocks as the ledger
233
258
fn assert_ledger_index_parity ( env : & StateMachine , ledger_id : CanisterId , index_id : CanisterId ) {
234
259
let ledger_blocks = icrc1_get_blocks ( env, ledger_id) ;
@@ -617,3 +642,95 @@ fn test_icrc1_balance_of() {
617
642
)
618
643
. unwrap ( ) ;
619
644
}
645
+
646
+ #[ test]
647
+ fn test_list_subaccounts ( ) {
648
+ // For this test, we add minting operations for some principals:
649
+ // - The principal 1 has one account with the last possible
650
+ // subaccount.
651
+ // - The principal 2 has a number of subaccounts equals to
652
+ // two times the DEFAULT_MAX_BLOCKS_PER_RESPONSE. Therefore fetching
653
+ // its subaccounts will trigger pagination.
654
+ // - The principal 3 has one account with the first possible
655
+ // subaccount.
656
+ // - The principal 4 has one account with the default subaccount,
657
+ // which should map to [0;32] in the index.
658
+
659
+ let account_1 = Account {
660
+ owner : PrincipalId :: new_user_test_id ( 1 ) . into ( ) ,
661
+ subaccount : Some ( [ u8:: MAX ; 32 ] ) ,
662
+ } ;
663
+ let accounts_2: Vec < _ > = ( 0 ..( DEFAULT_MAX_BLOCKS_PER_RESPONSE * 2 ) )
664
+ . map ( |i| account ( 2 , i as u128 ) )
665
+ . collect ( ) ;
666
+ let account_3 = account ( 3 , 0 ) ;
667
+ let account_4 = Account {
668
+ owner : PrincipalId :: new_user_test_id ( 4 ) . into ( ) ,
669
+ subaccount : None ,
670
+ } ;
671
+
672
+ let mut initial_balances: Vec < _ > = vec ! [
673
+ ( account_1, 10_000 ) ,
674
+ ( account_3, 10_000 ) ,
675
+ ( account_4, 40_000 ) ,
676
+ ] ;
677
+ initial_balances. extend ( accounts_2. iter ( ) . map ( |account| ( * account, 10_000 ) ) ) ;
678
+
679
+ let env = & StateMachine :: new ( ) ;
680
+ let ledger_id = install_ledger ( env, initial_balances, default_archive_options ( ) ) ;
681
+ let index_id = install_index ( env, ledger_id) ;
682
+
683
+ trigger_heartbeat ( env) ;
684
+
685
+ // list account_1.owner subaccounts when no starting subaccount is specified
686
+ assert_eq ! (
687
+ vec![ * account_1. effective_subaccount( ) ] ,
688
+ list_subaccounts( env, index_id, PrincipalId ( account_1. owner) , None )
689
+ ) ;
690
+
691
+ // list account_3.owner subaccounts when no starting subaccount is specified
692
+ assert_eq ! (
693
+ vec![ * account_3. effective_subaccount( ) ] ,
694
+ list_subaccounts( env, index_id, PrincipalId ( account_3. owner) , None )
695
+ ) ;
696
+
697
+ // list account_3.owner subaccounts when an existing starting subaccount is specified but no subaccount is in that range
698
+ assert ! ( list_subaccounts(
699
+ env,
700
+ index_id,
701
+ PrincipalId ( account_3. owner) ,
702
+ Some ( * account( 3 , 1 ) . effective_subaccount( ) )
703
+ )
704
+ . is_empty( ) ) ;
705
+
706
+ // list acccount_4.owner subaccounts should return the default subaccount
707
+ // mapped to [0;32]
708
+ assert_eq ! (
709
+ vec![ [ 0 ; 32 ] ] ,
710
+ list_subaccounts( env, index_id, PrincipalId ( account_4. owner) , None )
711
+ ) ;
712
+
713
+ // account_2.owner should have two batches of subaccounts
714
+ let principal_2 = accounts_2. get ( 0 ) . unwrap ( ) . owner ;
715
+ let batch_1 = list_subaccounts ( env, index_id, PrincipalId ( principal_2) , None ) ;
716
+ let expected_batch_1: Vec < _ > = accounts_2
717
+ . iter ( )
718
+ . take ( DEFAULT_MAX_BLOCKS_PER_RESPONSE as usize )
719
+ . map ( |account| * account. effective_subaccount ( ) )
720
+ . collect ( ) ;
721
+ assert_eq ! ( expected_batch_1, batch_1) ;
722
+
723
+ let batch_2 = list_subaccounts (
724
+ env,
725
+ index_id,
726
+ PrincipalId ( principal_2) ,
727
+ Some ( * batch_1. last ( ) . unwrap ( ) ) ,
728
+ ) ;
729
+ let expected_batch_2: Vec < _ > = accounts_2
730
+ . iter ( )
731
+ . skip ( DEFAULT_MAX_BLOCKS_PER_RESPONSE as usize )
732
+ . take ( DEFAULT_MAX_BLOCKS_PER_RESPONSE as usize )
733
+ . map ( |account| * account. effective_subaccount ( ) )
734
+ . collect ( ) ;
735
+ assert_eq ! ( expected_batch_2, batch_2) ;
736
+ }
0 commit comments