@@ -2272,3 +2272,279 @@ fn test_query_blocks_range() {
22722272 )
22732273 . unwrap ( )
22742274}
2275+
2276+ async fn get_block_operations (
2277+ rosetta_client : & RosettaClient ,
2278+ network_identifier : NetworkIdentifier ,
2279+ block_index : u64 ,
2280+ ) -> Vec < rosetta_core:: objects:: Operation > {
2281+ let response = rosetta_client
2282+ . block (
2283+ network_identifier,
2284+ PartialBlockIdentifier {
2285+ index : Some ( block_index) ,
2286+ hash : None ,
2287+ } ,
2288+ )
2289+ . await
2290+ . expect ( "Failed to fetch block" ) ;
2291+ response
2292+ . block
2293+ . expect ( "Block response missing block" )
2294+ . transactions
2295+ . first ( )
2296+ . expect ( "Block has no transactions" )
2297+ . operations
2298+ . clone ( )
2299+ }
2300+
2301+ #[ test]
2302+ fn test_authorized_mint_and_burn_122 ( ) {
2303+ let rt = Runtime :: new ( ) . unwrap ( ) ;
2304+ let setup = Setup :: builder ( )
2305+ . with_custom_ledger_wasm ( icrc3_test_ledger ( ) )
2306+ . build ( ) ;
2307+
2308+ rt. block_on ( async {
2309+ let env = RosettaTestingEnvironmentBuilder :: new ( & setup) . build ( ) . await ;
2310+
2311+ let agent = get_custom_agent ( Arc :: new ( test_identity ( ) ) , setup. port ) . await ;
2312+
2313+ let account_1 = Account {
2314+ owner : PrincipalId :: new_user_test_id ( 1 ) . into ( ) ,
2315+ subaccount : None ,
2316+ } ;
2317+ let account_2 = Account {
2318+ owner : PrincipalId :: new_user_test_id ( 2 ) . into ( ) ,
2319+ subaccount : None ,
2320+ } ;
2321+ let caller = PrincipalId :: new_user_test_id ( 42 ) ;
2322+
2323+ // Block 0: regular mint to account_1 (10M)
2324+ let block0 = BlockBuilder :: new ( 0 , 0 )
2325+ . mint ( account_1, Tokens :: from ( 10_000_000_u64 ) )
2326+ . build ( ) ;
2327+ let idx = add_block ( & agent, & env. icrc1_ledger_id , & block0)
2328+ . await
2329+ . expect ( "failed to add block 0" ) ;
2330+ assert_eq ! ( idx, Nat :: from( 0_u64 ) ) ;
2331+
2332+ // Block 1: minimal authorized mint to account_2 (5M) — no caller/mthd/reason
2333+ let block1 = BlockBuilder :: new ( 1 , 1 )
2334+ . with_parent_hash ( block0. hash ( ) . to_vec ( ) )
2335+ . authorized_mint ( account_2, Tokens :: from ( 5_000_000_u64 ) )
2336+ . build ( ) ;
2337+ let idx = add_block ( & agent, & env. icrc1_ledger_id , & block1)
2338+ . await
2339+ . expect ( "failed to add block 1" ) ;
2340+ assert_eq ! ( idx, Nat :: from( 1_u64 ) ) ;
2341+
2342+ // Block 2: full authorized mint to account_1 (2M) — with caller, mthd, reason, created_at_time
2343+ let block2 = BlockBuilder :: new ( 2 , 2 )
2344+ . with_parent_hash ( block1. hash ( ) . to_vec ( ) )
2345+ . authorized_mint ( account_1, Tokens :: from ( 2_000_000_u64 ) )
2346+ . with_caller ( caller. into ( ) )
2347+ . with_mthd ( "152mint" . to_string ( ) )
2348+ . with_reason ( "funding" . to_string ( ) )
2349+ . with_created_at_time ( 1_700_000_000_000_000_000 )
2350+ . build ( ) ;
2351+ let idx = add_block ( & agent, & env. icrc1_ledger_id , & block2)
2352+ . await
2353+ . expect ( "failed to add block 2" ) ;
2354+ assert_eq ! ( idx, Nat :: from( 2_u64 ) ) ;
2355+
2356+ // Block 3: minimal authorized burn from account_1 (1M)
2357+ let block3 = BlockBuilder :: new ( 3 , 3 )
2358+ . with_parent_hash ( block2. hash ( ) . to_vec ( ) )
2359+ . authorized_burn ( account_1, Tokens :: from ( 1_000_000_u64 ) )
2360+ . build ( ) ;
2361+ let idx = add_block ( & agent, & env. icrc1_ledger_id , & block3)
2362+ . await
2363+ . expect ( "failed to add block 3" ) ;
2364+ assert_eq ! ( idx, Nat :: from( 3_u64 ) ) ;
2365+
2366+ // Block 4: full authorized burn from account_1 (3M) — with caller, mthd, reason, created_at_time
2367+ let block4 = BlockBuilder :: new ( 4 , 4 )
2368+ . with_parent_hash ( block3. hash ( ) . to_vec ( ) )
2369+ . authorized_burn ( account_1, Tokens :: from ( 3_000_000_u64 ) )
2370+ . with_caller ( caller. into ( ) )
2371+ . with_mthd ( "152burn" . to_string ( ) )
2372+ . with_reason ( "compliance" . to_string ( ) )
2373+ . with_created_at_time ( 1_700_000_001_000_000_000 )
2374+ . build ( ) ;
2375+ let idx = add_block ( & agent, & env. icrc1_ledger_id , & block4)
2376+ . await
2377+ . expect ( "failed to add block 4" ) ;
2378+ assert_eq ! ( idx, Nat :: from( 4_u64 ) ) ;
2379+
2380+ // Wait for Rosetta to sync all blocks
2381+ let synced = wait_for_rosetta_block ( & env. rosetta_client , env. network_identifier . clone ( ) , 4 )
2382+ . await
2383+ . expect ( "Failed to sync Rosetta to block 4" ) ;
2384+ assert_eq ! ( synced, 4 ) ;
2385+
2386+ // Verify balances: account_1 = 10M + 2M - 1M - 3M = 8M, account_2 = 5M
2387+ assert_rosetta_balance (
2388+ account_1,
2389+ 4 ,
2390+ 8_000_000 ,
2391+ & env. rosetta_client ,
2392+ env. network_identifier . clone ( ) ,
2393+ )
2394+ . await ;
2395+ assert_rosetta_balance (
2396+ account_2,
2397+ 4 ,
2398+ 5_000_000 ,
2399+ & env. rosetta_client ,
2400+ env. network_identifier . clone ( ) ,
2401+ )
2402+ . await ;
2403+
2404+ // Verify block 1 operations (minimal authorized mint)
2405+ let ops1 =
2406+ get_block_operations ( & env. rosetta_client , env. network_identifier . clone ( ) , 1 ) . await ;
2407+ assert_eq ! ( ops1. len( ) , 1 ) ;
2408+ assert_eq ! ( ops1[ 0 ] . type_, OperationType :: AuthorizedMint . to_string( ) ) ;
2409+ assert_eq ! (
2410+ ops1[ 0 ] . account. as_ref( ) . unwrap( ) ,
2411+ & AccountIdentifier :: from( account_2)
2412+ ) ;
2413+ assert_eq ! ( ops1[ 0 ] . amount. as_ref( ) . unwrap( ) . value, "5000000" ) ;
2414+ // Minimal block: metadata should have no caller/mthd/reason
2415+ if let Some ( ref meta1) = ops1[ 0 ] . metadata {
2416+ assert ! ( meta1. get( "caller" ) . is_none( ) || meta1. get( "caller" ) . unwrap( ) . is_null( ) ) ;
2417+ assert ! ( meta1. get( "mthd" ) . is_none( ) || meta1. get( "mthd" ) . unwrap( ) . is_null( ) ) ;
2418+ assert ! ( meta1. get( "reason" ) . is_none( ) || meta1. get( "reason" ) . unwrap( ) . is_null( ) ) ;
2419+ }
2420+
2421+ // Verify block 2 operations (full authorized mint with metadata)
2422+ let ops2 =
2423+ get_block_operations ( & env. rosetta_client , env. network_identifier . clone ( ) , 2 ) . await ;
2424+ assert_eq ! ( ops2. len( ) , 1 ) ;
2425+ assert_eq ! ( ops2[ 0 ] . type_, OperationType :: AuthorizedMint . to_string( ) ) ;
2426+ assert_eq ! (
2427+ ops2[ 0 ] . account. as_ref( ) . unwrap( ) ,
2428+ & AccountIdentifier :: from( account_1)
2429+ ) ;
2430+ assert_eq ! ( ops2[ 0 ] . amount. as_ref( ) . unwrap( ) . value, "2000000" ) ;
2431+ let meta2 = ops2[ 0 ]
2432+ . metadata
2433+ . as_ref ( )
2434+ . expect ( "Expected metadata on full authorized mint" ) ;
2435+ assert_eq ! (
2436+ meta2. get( "caller" ) . and_then( |v| v. as_str( ) ) ,
2437+ Some ( hex:: encode( caller. as_slice( ) ) . as_str( ) )
2438+ ) ;
2439+ assert_eq ! ( meta2. get( "mthd" ) . and_then( |v| v. as_str( ) ) , Some ( "152mint" ) ) ;
2440+ assert_eq ! (
2441+ meta2. get( "reason" ) . and_then( |v| v. as_str( ) ) ,
2442+ Some ( "funding" )
2443+ ) ;
2444+
2445+ // Verify block 3 operations (minimal authorized burn)
2446+ let ops3 =
2447+ get_block_operations ( & env. rosetta_client , env. network_identifier . clone ( ) , 3 ) . await ;
2448+ assert_eq ! ( ops3. len( ) , 1 ) ;
2449+ assert_eq ! ( ops3[ 0 ] . type_, OperationType :: AuthorizedBurn . to_string( ) ) ;
2450+ assert_eq ! (
2451+ ops3[ 0 ] . account. as_ref( ) . unwrap( ) ,
2452+ & AccountIdentifier :: from( account_1)
2453+ ) ;
2454+ assert_eq ! ( ops3[ 0 ] . amount. as_ref( ) . unwrap( ) . value, "-1000000" ) ;
2455+ // Minimal block: metadata should have no caller/mthd/reason
2456+ if let Some ( ref meta3) = ops3[ 0 ] . metadata {
2457+ assert ! ( meta3. get( "caller" ) . is_none( ) || meta3. get( "caller" ) . unwrap( ) . is_null( ) ) ;
2458+ assert ! ( meta3. get( "mthd" ) . is_none( ) || meta3. get( "mthd" ) . unwrap( ) . is_null( ) ) ;
2459+ assert ! ( meta3. get( "reason" ) . is_none( ) || meta3. get( "reason" ) . unwrap( ) . is_null( ) ) ;
2460+ }
2461+
2462+ // Verify block 4 operations (full authorized burn with metadata)
2463+ let ops4 =
2464+ get_block_operations ( & env. rosetta_client , env. network_identifier . clone ( ) , 4 ) . await ;
2465+ assert_eq ! ( ops4. len( ) , 1 ) ;
2466+ assert_eq ! ( ops4[ 0 ] . type_, OperationType :: AuthorizedBurn . to_string( ) ) ;
2467+ assert_eq ! (
2468+ ops4[ 0 ] . account. as_ref( ) . unwrap( ) ,
2469+ & AccountIdentifier :: from( account_1)
2470+ ) ;
2471+ assert_eq ! ( ops4[ 0 ] . amount. as_ref( ) . unwrap( ) . value, "-3000000" ) ;
2472+ let meta4 = ops4[ 0 ]
2473+ . metadata
2474+ . as_ref ( )
2475+ . expect ( "Expected metadata on full authorized burn" ) ;
2476+ assert_eq ! (
2477+ meta4. get( "caller" ) . and_then( |v| v. as_str( ) ) ,
2478+ Some ( hex:: encode( caller. as_slice( ) ) . as_str( ) )
2479+ ) ;
2480+ assert_eq ! ( meta4. get( "mthd" ) . and_then( |v| v. as_str( ) ) , Some ( "152burn" ) ) ;
2481+ assert_eq ! (
2482+ meta4. get( "reason" ) . and_then( |v| v. as_str( ) ) ,
2483+ Some ( "compliance" )
2484+ ) ;
2485+
2486+ // Verify search_transactions by account_1 (blocks 0, 2, 3, 4)
2487+ let search_req_1 = SearchTransactionsRequest {
2488+ network_identifier : env. network_identifier . clone ( ) ,
2489+ account_identifier : Some ( account_1. into ( ) ) ,
2490+ ..Default :: default ( )
2491+ } ;
2492+ let search_resp_1 = env
2493+ . rosetta_client
2494+ . search_transactions ( & search_req_1)
2495+ . await
2496+ . expect ( "Failed to search transactions for account_1" ) ;
2497+ let block_indices_1: HashSet < u64 > = search_resp_1
2498+ . transactions
2499+ . iter ( )
2500+ . map ( |t| t. block_identifier . index )
2501+ . collect ( ) ;
2502+ assert ! (
2503+ block_indices_1. contains( & 0 ) ,
2504+ "account_1 should appear in block 0"
2505+ ) ;
2506+ assert ! (
2507+ block_indices_1. contains( & 2 ) ,
2508+ "account_1 should appear in block 2"
2509+ ) ;
2510+ assert ! (
2511+ block_indices_1. contains( & 3 ) ,
2512+ "account_1 should appear in block 3"
2513+ ) ;
2514+ assert ! (
2515+ block_indices_1. contains( & 4 ) ,
2516+ "account_1 should appear in block 4"
2517+ ) ;
2518+ assert_eq ! (
2519+ block_indices_1. len( ) ,
2520+ 4 ,
2521+ "account_1 should appear in exactly 4 blocks"
2522+ ) ;
2523+
2524+ // Verify search_transactions by account_2 (block 1)
2525+ let search_req_2 = SearchTransactionsRequest {
2526+ network_identifier : env. network_identifier . clone ( ) ,
2527+ account_identifier : Some ( account_2. into ( ) ) ,
2528+ ..Default :: default ( )
2529+ } ;
2530+ let search_resp_2 = env
2531+ . rosetta_client
2532+ . search_transactions ( & search_req_2)
2533+ . await
2534+ . expect ( "Failed to search transactions for account_2" ) ;
2535+ let block_indices_2: HashSet < u64 > = search_resp_2
2536+ . transactions
2537+ . iter ( )
2538+ . map ( |t| t. block_identifier . index )
2539+ . collect ( ) ;
2540+ assert ! (
2541+ block_indices_2. contains( & 1 ) ,
2542+ "account_2 should appear in block 1"
2543+ ) ;
2544+ assert_eq ! (
2545+ block_indices_2. len( ) ,
2546+ 1 ,
2547+ "account_2 should appear in exactly 1 block"
2548+ ) ;
2549+ } ) ;
2550+ }
0 commit comments