From 48198d2b1a79a69340c2fec5f4c34c92d4156315 Mon Sep 17 00:00:00 2001 From: ananas Date: Fri, 20 Feb 2026 20:34:27 +0000 Subject: [PATCH] add more tests --- Cargo.lock | 1 + forester/tests/test_compressible_ctoken.rs | 22 +- forester/tests/test_indexer_interface.rs | 48 ++- sdk-libs/client/src/indexer/config.rs | 21 ++ sdk-libs/client/src/interface/pack.rs | 110 ++++++ sdk-libs/client/src/interface/tx_size.rs | 87 +++++ sdk-tests/client-test/Cargo.toml | 1 + .../client-test/tests/light_program_test.rs | 332 ++++++++++++------ .../tests/test_create_ata.rs | 48 +-- .../tests/test_create_token_account.rs | 48 +-- sdk-tests/sdk-token-test/tests/test.rs | 58 ++- 11 files changed, 598 insertions(+), 178 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0b23ea663c..4beb990a61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1421,6 +1421,7 @@ dependencies = [ "light-client", "light-compressed-account", "light-compressed-token", + "light-event", "light-hasher", "light-indexed-array", "light-macros", diff --git a/forester/tests/test_compressible_ctoken.rs b/forester/tests/test_compressible_ctoken.rs index 3342fb5cb8..3f1202d943 100644 --- a/forester/tests/test_compressible_ctoken.rs +++ b/forester/tests/test_compressible_ctoken.rs @@ -324,12 +324,26 @@ async fn test_compressible_ctoken_compression() { assert_eq!(accounts.len(), 1); let account_state = &accounts[0]; assert_eq!(account_state.pubkey, token_account_pubkey); - assert_eq!(account_state.account.mint, mint.to_bytes()); + use light_token_interface::state::{AccountState, Token, ACCOUNT_TYPE_TOKEN_ACCOUNT}; assert_eq!( - account_state.account.owner, - owner_keypair.pubkey().to_bytes() + account_state.account, + Token { + mint: mint.to_bytes().into(), + owner: owner_keypair.pubkey().to_bytes().into(), + amount: 0, + delegate: None, + state: AccountState::Initialized, + is_native: None, + delegated_amount: 0, + close_authority: None, + account_type: ACCOUNT_TYPE_TOKEN_ACCOUNT, + extensions: account_state.account.extensions.clone(), + } + ); + assert!( + account_state.account.extensions.is_some(), + "compressible token account should have extensions" ); - assert_eq!(account_state.account.amount, 0); assert!(account_state.lamports > 0); let lamports = account_state.lamports; // Test lamports update diff --git a/forester/tests/test_indexer_interface.rs b/forester/tests/test_indexer_interface.rs index d3cc02b0f4..6916cf0a3b 100644 --- a/forester/tests/test_indexer_interface.rs +++ b/forester/tests/test_indexer_interface.rs @@ -515,9 +515,16 @@ async fn test_indexer_interface_scenarios() { ); println!(" PASSED: Token account interface resolved with correct token data"); - // ============ Test 3: getMultipleAccountInterfaces batch lookup ============ - println!("\nTest 3: getMultipleAccountInterfaces batch lookup..."); - let batch_addresses = vec![&decompressed_mint_pda, &compressible_token_account]; + // ============ Test 3: getMultipleAccountInterfaces batch lookup (4 accounts) ============ + println!("\nTest 3: getMultipleAccountInterfaces batch lookup (4 accounts)..."); + // Include: decompressed mint PDA, compressible token account, compressed mint PDA (not found), + // and the compressed mint seed pubkey (not a known on-chain account). + let batch_addresses = vec![ + &decompressed_mint_pda, + &compressible_token_account, + &bob_ata, + &charlie_ata, + ]; let batch_response = photon_indexer .get_multiple_account_interfaces(batch_addresses.clone(), None) @@ -526,8 +533,8 @@ async fn test_indexer_interface_scenarios() { assert_eq!( batch_response.value.len(), - 2, - "Batch response should have exactly 2 results" + 4, + "Batch response should have exactly 4 results" ); // First result: decompressed mint @@ -560,7 +567,36 @@ async fn test_indexer_interface_scenarios() { batch_token.account.lamports > 0, "Batch token account should have lamports > 0" ); - println!(" PASSED: Batch lookup returned correct results"); + + // Third result: Bob's ATA (on-chain token account) + let batch_bob_ata = batch_response.value[2] + .as_ref() + .expect("Bob's ATA should be found in batch"); + assert!(batch_bob_ata.is_hot(), "Bob's ATA should be hot (on-chain)"); + assert_eq!(batch_bob_ata.key, bob_ata, "Bob's ATA key should match"); + assert!( + batch_bob_ata.account.lamports > 0, + "Bob's ATA should have lamports > 0" + ); + + // Fourth result: Charlie's ATA (on-chain token account) + let batch_charlie_ata = batch_response.value[3] + .as_ref() + .expect("Charlie's ATA should be found in batch"); + assert!( + batch_charlie_ata.is_hot(), + "Charlie's ATA should be hot (on-chain)" + ); + assert_eq!( + batch_charlie_ata.key, charlie_ata, + "Charlie's ATA key should match" + ); + assert!( + batch_charlie_ata.account.lamports > 0, + "Charlie's ATA should have lamports > 0" + ); + + println!(" PASSED: Batch lookup returned correct results for 4 accounts"); // ============ Test 4: Verify fully compressed mint via getAccountInterface returns None ============ // Fully compressed mints (after CompressAndCloseMint) have full mint data in the compressed DB. diff --git a/sdk-libs/client/src/indexer/config.rs b/sdk-libs/client/src/indexer/config.rs index 13db14e793..b1ae39a5cd 100644 --- a/sdk-libs/client/src/indexer/config.rs +++ b/sdk-libs/client/src/indexer/config.rs @@ -28,3 +28,24 @@ impl Default for RetryConfig { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_retry_config_default_values() { + let config = RetryConfig::default(); + assert_eq!(config.num_retries, 10); + assert_eq!(config.delay_ms, 400); + assert_eq!(config.max_delay_ms, 8000); + } + + #[test] + fn test_indexer_rpc_config_new_sets_slot() { + let slot = 42u64; + let config = IndexerRpcConfig::new(slot); + assert_eq!(config.slot, slot); + assert_eq!(config.retry_config, RetryConfig::default()); + } +} diff --git a/sdk-libs/client/src/interface/pack.rs b/sdk-libs/client/src/interface/pack.rs index 1247586928..804a48751d 100644 --- a/sdk-libs/client/src/interface/pack.rs +++ b/sdk-libs/client/src/interface/pack.rs @@ -123,3 +123,113 @@ fn pack_proof_internal( system_accounts_offset: system_offset as u8, }) } + +#[cfg(test)] +mod tests { + use light_compressed_account::TreeType; + use solana_pubkey::Pubkey; + + use super::{pack_proof, pack_proof_for_mints}; + use crate::indexer::{TreeInfo, ValidityProofWithContext}; + + fn make_state_v1_tree_info() -> TreeInfo { + TreeInfo { + tree_type: TreeType::StateV1, + tree: Pubkey::new_unique(), + queue: Pubkey::new_unique(), + cpi_context: None, + next_tree_info: None, + } + } + + #[test] + fn test_pack_proof_minimal_valid_proof_no_cpi_context() { + let program_id = Pubkey::new_unique(); + let proof = ValidityProofWithContext::default(); + let output_tree = make_state_v1_tree_info(); + + let result = pack_proof(&program_id, proof, &output_tree, None).unwrap(); + + // v2 system accounts (with self_program, no cpi_context): + // light_system_program, cpi_signer, registered_program_pda, + // account_compression_authority, account_compression_program, system_program = 6 + // + output queue = 1 + // Total = 7 + assert_eq!( + result.remaining_accounts.len(), + 7, + "expected 7 remaining accounts without cpi_context" + ); + assert_eq!(result.state_tree_index, None); + // system_accounts_offset is 0 because system accounts are prepended + // at the start of remaining_accounts by add_system_accounts_raw + assert_eq!(result.system_accounts_offset, 0); + } + + #[test] + fn test_pack_proof_with_cpi_context_adds_extra_account() { + let program_id = Pubkey::new_unique(); + let cpi_context_pubkey = Pubkey::new_unique(); + let proof = ValidityProofWithContext::default(); + let output_tree = make_state_v1_tree_info(); + + let result_no_cpi = pack_proof(&program_id, proof.clone(), &output_tree, None).unwrap(); + let result_with_cpi = + pack_proof(&program_id, proof, &output_tree, Some(cpi_context_pubkey)).unwrap(); + + // cpi_context adds one more account + assert_eq!( + result_with_cpi.remaining_accounts.len(), + result_no_cpi.remaining_accounts.len() + 1, + "cpi_context should add exactly one account" + ); + } + + #[test] + fn test_pack_proof_for_mints_adds_state_tree_index() { + let program_id = Pubkey::new_unique(); + let proof = ValidityProofWithContext::default(); + let output_tree = make_state_v1_tree_info(); + + let result = pack_proof_for_mints(&program_id, proof, &output_tree, None).unwrap(); + + // state_tree_index must be Some for mint creation path + assert!( + result.state_tree_index.is_some(), + "pack_proof_for_mints should set state_tree_index" + ); + let idx = result.state_tree_index.unwrap(); + assert!( + (idx as usize) < result.remaining_accounts.len(), + "state_tree_index must be a valid index into remaining_accounts" + ); + } + + #[test] + fn test_pack_proof_vs_pack_proof_for_mints_output_tree_index_consistent() { + let program_id = Pubkey::new_unique(); + let proof = ValidityProofWithContext::default(); + let tree = Pubkey::new_unique(); + let queue = Pubkey::new_unique(); + let output_tree = TreeInfo { + tree_type: TreeType::StateV1, + tree, + queue, + cpi_context: None, + next_tree_info: None, + }; + + let r1 = pack_proof(&program_id, proof.clone(), &output_tree, None).unwrap(); + let r2 = pack_proof_for_mints(&program_id, proof, &output_tree, None).unwrap(); + + // Both should have the same output_tree_index since they use the same output_tree + assert_eq!(r1.output_tree_index, r2.output_tree_index); + + // pack_proof_for_mints adds exactly one account (the state tree) + assert_eq!( + r2.remaining_accounts.len(), + r1.remaining_accounts.len() + 1, + "pack_proof_for_mints adds exactly one account (the state tree)" + ); + } +} diff --git a/sdk-libs/client/src/interface/tx_size.rs b/sdk-libs/client/src/interface/tx_size.rs index 9ca277780f..cc8ed981b4 100644 --- a/sdk-libs/client/src/interface/tx_size.rs +++ b/sdk-libs/client/src/interface/tx_size.rs @@ -153,6 +153,93 @@ mod tests { use super::*; + #[test] + fn test_empty_input_returns_empty_batches() { + let payer = Pubkey::new_unique(); + let result = split_by_tx_size(vec![], &payer, None).unwrap(); + assert_eq!(result, vec![] as Vec>); + } + + #[test] + fn test_single_small_instruction_one_batch() { + let payer = Pubkey::new_unique(); + let ix = Instruction { + program_id: Pubkey::new_unique(), + accounts: vec![AccountMeta::new(Pubkey::new_unique(), false)], + data: vec![0u8; 10], + }; + let batches = split_by_tx_size(vec![ix], &payer, None).unwrap(); + assert_eq!(batches.len(), 1); + assert_eq!(batches[0].len(), 1); + } + + #[test] + fn test_multiple_small_instructions_fit_in_one_batch() { + let payer = Pubkey::new_unique(); + // Three tiny instructions that easily fit in one tx + let instructions: Vec = (0..3) + .map(|_| Instruction { + program_id: Pubkey::new_unique(), + accounts: vec![AccountMeta::new(Pubkey::new_unique(), false)], + data: vec![0u8; 5], + }) + .collect(); + let batches = split_by_tx_size(instructions, &payer, None).unwrap(); + assert_eq!(batches.len(), 1); + assert_eq!(batches[0].len(), 3); + } + + #[test] + fn test_instructions_split_into_multiple_batches_with_small_max_size() { + let payer = Pubkey::new_unique(); + // Use a very small max_size to force each instruction into its own batch. + // Each instruction has 1 account + 10 bytes data. Estimate: + // header(3) + accounts(compact+32*2) + blockhash(32) + ixs + sigs ~ 200 bytes + // Set max_size=250 to allow one instruction per tx but not two. + let max_size = 250usize; + let instructions: Vec = (0..3) + .map(|_| Instruction { + program_id: Pubkey::new_unique(), + accounts: vec![AccountMeta::new(Pubkey::new_unique(), false)], + data: vec![0u8; 10], + }) + .collect(); + let batches = split_by_tx_size(instructions, &payer, Some(max_size)).unwrap(); + assert_eq!( + batches.len(), + 3, + "each of 3 instructions should be in its own batch at max_size=250" + ); + for batch in &batches { + assert_eq!( + batch.len(), + 1, + "each batch should contain exactly one instruction" + ); + assert!(estimate_tx_size(batch, &payer) <= max_size); + } + } + + #[test] + fn test_single_instruction_exceeding_max_size_returns_error() { + let payer = Pubkey::new_unique(); + let oversized_ix = Instruction { + program_id: Pubkey::new_unique(), + accounts: vec![AccountMeta::new(Pubkey::new_unique(), false)], + data: vec![0u8; 100], + }; + // Set max_size to 50 - smaller than any valid instruction + let result = split_by_tx_size(vec![oversized_ix], &payer, Some(50)); + assert!(result.is_err()); + let err = result.unwrap_err(); + assert_eq!(err.instruction_index, 0); + assert_eq!(err.max_size, 50); + assert!(err.estimated_size > 50); + // Display impl sanity check + let msg = err.to_string(); + assert!(msg.contains("instruction at index 0")); + } + #[test] fn test_split_by_tx_size() { let payer = Pubkey::new_unique(); diff --git a/sdk-tests/client-test/Cargo.toml b/sdk-tests/client-test/Cargo.toml index aa16fa3b30..9f80dcc190 100644 --- a/sdk-tests/client-test/Cargo.toml +++ b/sdk-tests/client-test/Cargo.toml @@ -30,6 +30,7 @@ light-token = { workspace = true } light-indexed-array = { workspace = true } light-merkle-tree-reference = { workspace = true } light-macros = { workspace = true } +light-event = { workspace = true } tokio = { workspace = true } rand = { workspace = true } diff --git a/sdk-tests/client-test/tests/light_program_test.rs b/sdk-tests/client-test/tests/light_program_test.rs index 3bc531baf0..c627d027be 100644 --- a/sdk-tests/client-test/tests/light_program_test.rs +++ b/sdk-tests/client-test/tests/light_program_test.rs @@ -3,6 +3,7 @@ use light_client::{ AddressWithTree, GetCompressedTokenAccountsByOwnerOrDelegateOptions, Hash, Indexer, IndexerRpcConfig, RetryConfig, }, + interface::AccountToFetch, rpc::Rpc, }; use light_compressed_account::hash_to_bn254_field_size_be; @@ -14,7 +15,10 @@ use light_program_test::{ }; use light_sdk::address::{v1::derive_address, NewAddressParams}; use light_test_utils::{system_program::create_invoke_instruction, RpcError}; -use light_token::compat::{AccountState, TokenData}; +use light_token::{ + compat::{AccountState, TokenData}, + instruction::derive_token_ata, +}; use solana_sdk::{ compute_budget::ComputeBudgetInstruction, pubkey::Pubkey, @@ -229,63 +233,9 @@ async fn test_all_endpoints() { "Second account should not exist" ); } - // // 8. get_compressed_balance_by_owner - // { - // let balance = rpc - // .get_compressed_balance_by_owner(&payer_pubkey, None) - // .await - // .unwrap() - // .value; - // assert_eq!(balance, lamports + lamports_1); - // } - // // 8. get_compression_signatures_for_account - // { - // let signatures = rpc - // .get_compression_signatures_for_account(first_account.hash, None) - // .await - // .unwrap() - // .value; - // assert_eq!(signatures.items[0].signature, signature.to_string()); - // } - // // 9. get_compression_signatures_for_address - // { - // let signatures = rpc - // .get_compression_signatures_for_address(&first_account.address.unwrap(), None, None) - // .await - // .unwrap() - // .value; - // assert_eq!(signatures.items[0].signature, signature.to_string()); - // } - // // 10. get_compression_signatures_for_owner - // { - // let signatures = rpc - // .get_compression_signatures_for_owner(&owner, None, None) - // .await - // .unwrap() - // .value; - // assert_eq!(signatures.items.len(), 2); - // assert!(signatures - // .items - // .iter() - // .any(|s| s.signature == signature.to_string())); - // assert!(signatures - // .items - // .iter() - // .any(|s| s.signature == signature_1.to_string())); - // let options = PaginatedOptions { - // limit: Some(1), - // cursor: None, - // }; - // let signatures = rpc - // .get_compression_signatures_for_owner(&owner, Some(options), None) - // .await - // .unwrap() - // .value; - // assert_eq!(signatures.items.len(), 1); - // assert!(signatures.items.iter().any( - // |s| s.signature == signature_1.to_string() || s.signature == signature.to_string() - // )); - // } + // 8, 9, 10: get_compressed_balance_by_owner, get_compression_signatures_for_account, + // get_compression_signatures_for_address, get_compression_signatures_for_owner + // are not implemented in TestIndexer. See the #[ignore] tests below. // 11. get_multiple_compressed_account_proofs { let proofs = rpc @@ -348,62 +298,8 @@ async fn test_token_api(rpc: &mut LightProgramTest, test_accounts: &TestAccounts &amounts, ) .await; - // // 1. get_compressed_mint_token_holders - // for mint in [mint_1, mint_2] { - // let res = rpc - // .get_compressed_mint_token_holders(&mint, None, None) - // .await - // .unwrap() - // .value - // .items; - // assert_eq!(res.len(), 5); - - // let mut owners = res.iter().map(|x| x.owner).collect::>(); - // owners.sort(); - // owners.dedup(); - // assert_eq!(owners.len(), 5); - // for (amount, recipient) in amounts.iter().zip(recipients.iter()) { - // // * 2 because we mint two times the same amount per token mint (with and without lamports) - // assert!(res - // .iter() - // .any(|item| item.balance == (*amount * 2) && item.owner == *recipient)); - // } - // let option = PaginatedOptions { - // limit: Some(1), - // cursor: None, - // }; - // let res = rpc - // .get_compressed_mint_token_holders(&mint, Some(option), None) - // .await - // .unwrap() - // .value - // .items; - // assert_eq!(res.len(), 1); - // } - - // // 2. get_compression_signatures_for_token_owner - // for recipient in &recipients { - // let res = rpc - // .get_compression_signatures_for_token_owner(recipient, None, None) - // .await - // .unwrap() - // .value - // .items; - // assert_eq!(res.len(), 2); - // assert_eq!(res[0].signature, signatures[1].to_string()); - // assert_eq!(res[1].signature, signatures[0].to_string()); - // let option = PaginatedOptions { - // limit: Some(1), - // cursor: None, - // }; - // let res = rpc - // .get_compression_signatures_for_token_owner(recipient, Some(option), None) - // .await - // .unwrap() - // .value - // .items; - // assert_eq!(res.len(), 1); - // } + // get_compressed_mint_token_holders and get_compression_signatures_for_token_owner + // are not implemented in TestIndexer. See the #[ignore] tests below. // 3. get_compressed_token_accounts_by_owner test_get_compressed_token_accounts_by_owner( @@ -773,3 +669,211 @@ async fn test_get_compressed_token_balances_by_owner_v2( } } } + +// ============ Phase 2 Tests ============ + +/// B1: get_associated_token_account_interface +/// +/// Mints a compressed token to the payer's ATA address (derive_token_ata(payer, mint)). +/// Then calls get_associated_token_account_interface(payer, mint). +/// The token was minted to the ATA pubkey as owner, so the cold path returns it. +/// Asserts amount, owner, and is_cold (since TestIndexer returns cold compressed accounts). +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn test_get_associated_token_account_interface() { + let config = ProgramTestConfig::default(); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let test_accounts = rpc.test_accounts().clone(); + let payer = rpc.get_payer().insecure_clone(); + let payer_pubkey = payer.pubkey(); + + // Create a mint + let mint_kp = Keypair::new(); + create_two_mints(&mut rpc, payer_pubkey, &mint_kp, &Keypair::new()).await; + let mint = mint_kp.pubkey(); + + // Derive the payer's ATA for this mint + let ata = derive_token_ata(&payer_pubkey, &mint); + let amount = 42_000u64; + + // Mint the compressed token directly to the ATA address as the token owner. + // This way, get_associated_token_account_interface cold path finds it by + // querying compressed tokens owned by the ATA pubkey. + let mint_ix = create_mint_to_instruction( + &payer_pubkey, + &payer_pubkey, + &mint, + &test_accounts.v1_state_trees[0].merkle_tree, + vec![amount], + vec![ata], + None, + false, + 0, + ); + let compute_budget_ix = ComputeBudgetInstruction::set_compute_unit_limit(500_000); + let blockhash = rpc.get_latest_blockhash().await.unwrap().0; + let tx = Transaction::new_signed_with_payer( + &[compute_budget_ix, mint_ix], + Some(&payer_pubkey), + &[&payer], + blockhash, + ); + rpc.process_transaction(tx).await.unwrap(); + + // Now query the ATA interface + let result = rpc + .get_associated_token_account_interface(&payer_pubkey, &mint, None) + .await + .unwrap() + .value + .expect("ATA should be found"); + + // The token was minted into a compressed account owned by the ATA address, + // so it comes back as a cold (compressed) account. + assert!(result.is_cold(), "Expected cold (compressed) ATA"); + assert_eq!(result.amount(), amount); + // The owner returned should be the wallet owner (payer_pubkey) per owner_override in cold ctor + assert_eq!(result.owner(), payer_pubkey); + assert_eq!(result.mint(), mint); + assert!(!result.is_frozen(), "ATA should not be frozen"); + assert!(result.delegate().is_none(), "ATA should have no delegate"); + assert!(result.is_ata(), "Result should identify as an ATA"); +} + +/// B2: get_mint_interface +/// +/// Uses an existing hot on-chain mint created by create_two_mints. +/// Asserts the returned MintInterface is hot and present. +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn test_get_mint_interface() { + let config = ProgramTestConfig::default(); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer_pubkey = rpc.get_payer().pubkey(); + + let mint_kp = Keypair::new(); + create_two_mints(&mut rpc, payer_pubkey, &mint_kp, &Keypair::new()).await; + let mint = mint_kp.pubkey(); + + let result = rpc + .get_mint_interface(&mint, None) + .await + .unwrap() + .value + .expect("mint should be found"); + + assert!(result.is_hot(), "Expected hot (on-chain) mint"); + assert_eq!(result.mint, mint); + assert!( + result.account().is_some(), + "Hot mint should have on-chain account" + ); + assert!( + result.compressed().is_none(), + "Hot mint should have no compressed data" + ); +} + +/// B3: fetch_accounts +/// +/// Builds a vec of AccountToFetch descriptors (one Mint, one Ata) and calls +/// rpc.fetch_accounts. Asserts the returned Vec has the +/// correct length. +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn test_fetch_accounts() { + let config = ProgramTestConfig::default(); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let test_accounts = rpc.test_accounts().clone(); + let payer = rpc.get_payer().insecure_clone(); + let payer_pubkey = payer.pubkey(); + + let mint_kp = Keypair::new(); + create_two_mints(&mut rpc, payer_pubkey, &mint_kp, &Keypair::new()).await; + let mint = mint_kp.pubkey(); + + // Mint compressed token to the ATA address so the ATA fetch succeeds + let ata = derive_token_ata(&payer_pubkey, &mint); + let amount = 1_000u64; + let mint_ix = create_mint_to_instruction( + &payer_pubkey, + &payer_pubkey, + &mint, + &test_accounts.v1_state_trees[0].merkle_tree, + vec![amount], + vec![ata], + None, + false, + 0, + ); + let compute_budget_ix = ComputeBudgetInstruction::set_compute_unit_limit(500_000); + let blockhash = rpc.get_latest_blockhash().await.unwrap().0; + let tx = Transaction::new_signed_with_payer( + &[compute_budget_ix, mint_ix], + Some(&payer_pubkey), + &[&payer], + blockhash, + ); + rpc.process_transaction(tx).await.unwrap(); + + let accounts_to_fetch = vec![ + AccountToFetch::mint(mint), + AccountToFetch::ata(payer_pubkey, mint), + ]; + + let result = rpc.fetch_accounts(&accounts_to_fetch, None).await.unwrap(); + + assert_eq!(result.len(), accounts_to_fetch.len()); + assert!(result[0].is_hot(), "Mint should be hot (on-chain)"); + assert_eq!(result[0].key, mint, "Mint key should match"); + assert!(result[1].is_cold(), "ATA should be cold (compressed)"); + assert_eq!(result[1].key, ata, "ATA key should match"); +} + +/// B4: get_indexer_health and get_indexer_slot +/// +/// TestIndexer always returns healthy (true) and slot u64::MAX. +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn test_indexer_health_and_slot() { + let config = ProgramTestConfig::default(); + let rpc = LightProgramTest::new(config).await.unwrap(); + + let indexer = rpc.indexer().unwrap(); + + let healthy = indexer.get_indexer_health(None).await.unwrap(); + assert!(healthy, "TestIndexer should report healthy"); + + let slot = indexer.get_indexer_slot(None).await.unwrap(); + assert_eq!(slot, u64::MAX, "TestIndexer returns u64::MAX as slot"); +} + +/// C3: create_and_send_transaction_with_event +/// +/// Sends a no-op transaction and verifies the method compiles and runs without error. +/// Uses PublicTransactionEvent as the event type. +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn test_create_and_send_transaction_with_event() { + use light_event::event::PublicTransactionEvent; + + let config = ProgramTestConfig::default(); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + let payer_pubkey = payer.pubkey(); + + // Use a compute budget instruction as a no-op that won't emit a + // PublicTransactionEvent. The method should return Ok(None). + let ix = ComputeBudgetInstruction::set_compute_unit_limit(200_000); + + let result = rpc + .create_and_send_transaction_with_event::( + &[ix], + &payer_pubkey, + &[&payer], + ) + .await + .unwrap(); + + // No PublicTransactionEvent is emitted by a compute budget instruction, + // so result is None. + assert!( + result.is_none(), + "Expected None when no PublicTransactionEvent is emitted" + ); +} diff --git a/sdk-tests/sdk-light-token-pinocchio/tests/test_create_ata.rs b/sdk-tests/sdk-light-token-pinocchio/tests/test_create_ata.rs index f7d6eae111..1101a725e9 100644 --- a/sdk-tests/sdk-light-token-pinocchio/tests/test_create_ata.rs +++ b/sdk-tests/sdk-light-token-pinocchio/tests/test_create_ata.rs @@ -72,19 +72,23 @@ async fn test_create_ata_invoke() { let ata_account_data = rpc.get_account(ata_address).await.unwrap().unwrap(); // Parse and verify account data - use light_token_interface::state::Token; + use light_token_interface::state::{AccountState, Token}; let account_state = Token::deserialize(&mut &ata_account_data.data[..]).unwrap(); assert_eq!( - account_state.mint.to_bytes(), - mint_pda.to_bytes(), - "Mint should match" + account_state, + Token { + mint: mint_pda.to_bytes().into(), + owner: owner.to_bytes().into(), + amount: 0, + delegate: None, + state: AccountState::Initialized, + is_native: None, + delegated_amount: 0, + close_authority: None, + account_type: account_state.account_type, + extensions: account_state.extensions.clone(), + } ); - assert_eq!( - account_state.owner.to_bytes(), - owner.to_bytes(), - "Owner should match" - ); - assert_eq!(account_state.amount, 0, "Initial amount should be 0"); } /// Test creating an ATA with PDA payer using CreateAssociatedTokenAccountCpi::invoke_signed() @@ -157,17 +161,21 @@ async fn test_create_ata_invoke_signed() { let ata_account_data = rpc.get_account(ata_address).await.unwrap().unwrap(); // Parse and verify account data - use light_token_interface::state::Token; + use light_token_interface::state::{AccountState, Token}; let account_state = Token::deserialize(&mut &ata_account_data.data[..]).unwrap(); assert_eq!( - account_state.mint.to_bytes(), - mint_pda.to_bytes(), - "Mint should match" - ); - assert_eq!( - account_state.owner.to_bytes(), - pda_owner.to_bytes(), - "Owner should match PDA" + account_state, + Token { + mint: mint_pda.to_bytes().into(), + owner: pda_owner.to_bytes().into(), + amount: 0, + delegate: None, + state: AccountState::Initialized, + is_native: None, + delegated_amount: 0, + close_authority: None, + account_type: account_state.account_type, + extensions: account_state.extensions.clone(), + } ); - assert_eq!(account_state.amount, 0, "Initial amount should be 0"); } diff --git a/sdk-tests/sdk-light-token-pinocchio/tests/test_create_token_account.rs b/sdk-tests/sdk-light-token-pinocchio/tests/test_create_token_account.rs index 2bd383f880..4703d9df0c 100644 --- a/sdk-tests/sdk-light-token-pinocchio/tests/test_create_token_account.rs +++ b/sdk-tests/sdk-light-token-pinocchio/tests/test_create_token_account.rs @@ -73,19 +73,23 @@ async fn test_create_token_account_invoke() { .unwrap(); // Parse and verify account data - use light_token_interface::state::Token; + use light_token_interface::state::{AccountState, Token}; let account_state = Token::deserialize(&mut &ctoken_account_data.data[..]).unwrap(); assert_eq!( - account_state.mint.to_bytes(), - mint_pda.to_bytes(), - "Mint should match" + account_state, + Token { + mint: mint_pda.to_bytes().into(), + owner: owner.to_bytes().into(), + amount: 0, + delegate: None, + state: AccountState::Initialized, + is_native: None, + delegated_amount: 0, + close_authority: None, + account_type: account_state.account_type, + extensions: account_state.extensions.clone(), + } ); - assert_eq!( - account_state.owner.to_bytes(), - owner.to_bytes(), - "Owner should match" - ); - assert_eq!(account_state.amount, 0, "Initial amount should be 0"); } /// Test creating a PDA-owned token account using CreateTokenAccountCpi::invoke_signed() @@ -147,17 +151,21 @@ async fn test_create_token_account_invoke_signed() { let ctoken_account_data = rpc.get_account(ctoken_account_pda).await.unwrap().unwrap(); // Parse and verify account data - use light_token_interface::state::Token; + use light_token_interface::state::{AccountState, Token}; let account_state = Token::deserialize(&mut &ctoken_account_data.data[..]).unwrap(); assert_eq!( - account_state.mint.to_bytes(), - mint_pda.to_bytes(), - "Mint should match" - ); - assert_eq!( - account_state.owner.to_bytes(), - owner.to_bytes(), - "Owner should match" + account_state, + Token { + mint: mint_pda.to_bytes().into(), + owner: owner.to_bytes().into(), + amount: 0, + delegate: None, + state: AccountState::Initialized, + is_native: None, + delegated_amount: 0, + close_authority: None, + account_type: account_state.account_type, + extensions: account_state.extensions.clone(), + } ); - assert_eq!(account_state.amount, 0, "Initial amount should be 0"); } diff --git a/sdk-tests/sdk-token-test/tests/test.rs b/sdk-tests/sdk-token-test/tests/test.rs index c80fcc256b..3c6941881d 100644 --- a/sdk-tests/sdk-token-test/tests/test.rs +++ b/sdk-tests/sdk-token-test/tests/test.rs @@ -4,6 +4,7 @@ use anchor_lang::{AccountDeserialize, InstructionData}; use anchor_spl::token::TokenAccount; use light_client::indexer::CompressedTokenAccount; use light_compressed_token_sdk::{ + compat::{AccountState, TokenData}, compressed_token::{ batch_compress::{ get_batch_compress_instruction_account_metas, BatchCompressMetaConfig, Recipient, @@ -119,12 +120,18 @@ async fn test() { let compressed_account = &compressed_accounts[0]; // Assert the compressed token account properties - assert_eq!(compressed_account.token.owner, payer.pubkey()); - assert_eq!(compressed_account.token.mint, mint_pubkey); - - // Verify the token amount (should match the compressed amount) + assert_eq!( + compressed_account.token, + TokenData { + mint: mint_pubkey, + owner: payer.pubkey(), + amount: compress_amount, + delegate: None, + state: AccountState::Initialized, + tlv: None, + } + ); let amount = compressed_account.token.amount; - assert_eq!(amount, compress_amount); println!( "Verified compressed token account: owner={}, mint={}, amount={}", @@ -181,9 +188,18 @@ async fn test() { "Recipient should have compressed token account" ); let recipient_account = &recipient_accounts[0]; - assert_eq!(recipient_account.token.owner, transfer_recipient.pubkey()); + assert_eq!( + recipient_account.token, + TokenData { + mint: mint_pubkey, + owner: transfer_recipient.pubkey(), + amount: transfer_amount, + delegate: None, + state: AccountState::Initialized, + tlv: None, + } + ); let recipient_amount = recipient_account.token.amount; - assert_eq!(recipient_amount, transfer_amount); println!("Verified recipient balance: {}", recipient_amount); // Now decompress some tokens from the recipient back to SPL token account @@ -266,11 +282,18 @@ async fn test() { if !updated_recipient_accounts.is_empty() { let updated_recipient_account = &updated_recipient_accounts[0]; - let remaining_compressed_amount = updated_recipient_account.token.amount; assert_eq!( - remaining_compressed_amount, - transfer_amount - decompress_amount + updated_recipient_account.token, + TokenData { + mint: mint_pubkey, + owner: transfer_recipient.pubkey(), + amount: transfer_amount - decompress_amount, + delegate: None, + state: AccountState::Initialized, + tlv: None, + } ); + let remaining_compressed_amount = updated_recipient_account.token.amount; println!( "Verified remaining compressed balance: {}", remaining_compressed_amount @@ -552,16 +575,23 @@ async fn test_batch_compress() { ); let compressed_account = &compressed_accounts[0]; - assert_eq!(compressed_account.token.owner, *recipient); - assert_eq!(compressed_account.token.mint, mint_pubkey); - let expected_amount = match i { 0 => 100_000, 1 => 200_000, 2 => 300_000, _ => unreachable!(), }; - assert_eq!(compressed_account.token.amount, expected_amount); + assert_eq!( + compressed_account.token, + TokenData { + mint: mint_pubkey, + owner: *recipient, + amount: expected_amount, + delegate: None, + state: AccountState::Initialized, + tlv: None, + } + ); println!( "Verified recipient {} received {} compressed tokens",