Skip to content

Commit 993b51b

Browse files
bogwarclaude
andcommitted
test(ic-icrc-rosetta): add ICRC-122 system test for authorized mint/burn
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a78e0fe commit 993b51b

File tree

1 file changed

+276
-0
lines changed

1 file changed

+276
-0
lines changed

rs/rosetta-api/icrc1/tests/system_tests.rs

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)