From 805371c319b77db611fdeafc432c9cdd1d3ef8f2 Mon Sep 17 00:00:00 2001 From: Swenschaeferjohann Date: Mon, 9 Feb 2026 17:00:20 +0000 Subject: [PATCH 1/9] refactor trait refactor remove fetch-accounts renaming, simplify trait fomat excl photon-api submodule fix: multi-pass cold account lookup in test indexer RPC Align get_account_interface and get_multiple_account_interfaces with Photon's lookup strategy: search compressed_accounts by onchain_pubkey, then by PDA seed derivation, then token_compressed_accounts, then by token_data.owner. Also fix as_mint() to accept ColdContext::Account since Photon returns mints as generic compressed accounts. Co-authored-by: Cursor lint fix lint fix: reject rent sponsor self-referencing the token account (#2257) * fix: reject rent sponsor self-referencing the token account Audit issue #9 (INFO): The rent payer could be the same account as the target token account being created. Add a check that rejects this self-reference to prevent accounting issues. * test: add failing test rent sponsor self reference fix: process metadata add/remove actions in sequential order (#2256) * fix: process metadata add/remove actions in sequential order Audit issue #16 (LOW): should_add_key checked for any add and any remove independently, ignoring action ordering. An add-remove-add sequence would incorrectly remove the key. Process actions sequentially so the final state reflects the actual order. * chore: format * test: add randomized test for metadata action processing Validates that process_extensions_config_with_actions produces correct AdditionalMetadataConfig for random sequences of UpdateMetadataField and RemoveMetadataKey actions, covering the add-remove-add bug from audit issue #16. * test: add integration test for audit issue #13 (no double rent charge) Verifies that two compress operations targeting the same compressible CToken account in a single Transfer2 instruction do not double-charge the rent top-up budget. * chore: format extensions_metadata test fix: validate authority on self-transfer early return (#2252) * fix: handle self-transfer in ctoken transfer and transfer_checked Validate that the authority is a signer and is the owner or delegate before allowing self-transfer early return. Previously the self-transfer path returned Ok(()) without any authority validation. * fix: simplify map_or to is_some_and per clippy * fix: use pubkey_eq for self-transfer check * refactor: extract self-transfer validation into shared function Extract duplicate self-transfer check from default.rs and checked.rs into validate_self_transfer() in shared.rs with cold path for authority validation. * chore: format * fix: deduplicate random metadata keys in test_random_mint_action Random key generation could produce duplicate keys, causing DuplicateMetadataKey error (18040) with certain seeds. fix: enforce mint extension checks in cToken-to-cToken decompress (#2246) * fix: enforce mint extension checks in cToken-to-cToken decompress hot path Add enforce_extension_state() to MintExtensionChecks and call it in the Decompress branch when decompress_inputs is None (hot-path, not CompressedOnly restore). This prevents cToken-to-cToken transfers from bypassing pause, transfer fee, and transfer hook restrictions. * fix test chore: reject compress for mints with restricted extensions in build_mint_extension_cache (#2240) * chore: reject compress for mints with restricted extensions in mint check * Update programs/compressed-token/program/src/compressed_token/transfer2/check_extensions.rs Co-authored-by: 0xa5df-c <172008956+0xa5df-c@users.noreply.github.com> * fix: format else-if condition for lint --------- Co-authored-by: 0xa5df-c <172008956+0xa5df-c@users.noreply.github.com> fix: token-pool index 0 check (#2239) fix(programs): add MintCloseAuthority as restricted extension (M-03) (#2263) * fix: add MintCloseAuthority as restricted extension (M-03) A mint with MintCloseAuthority can be closed and re-opened with different extensions. Treating it as restricted ensures compressed tokens from such mints require CompressedOnly mode. * test: add MintCloseAuthority compression_only requirement tests Add test coverage for MintCloseAuthority requiring compression_only mode, complementing the fix in f2da063df. refactor: light program pinocchio macro (#2247) * refactor: light program pinocchio macro * fix: address CodeRabbit review comments on macro codegen - Fix account order bug in process_update_config (config=0, authority=1) - Use backend-provided serialize/deserialize derives in LightAccountData - Remove redundant is_pinocchio() branch for has_le_bytes unpack fields - DRY doc attribute generation across 4 struct generation methods - Unify unpack_data path using account_crate re-export for both backends chore(libs): bump versions (#2272) fix(programs): allow account-level delegate to compress CToken (M-02) (#2262) * fix: allow account-level delegate to compress tokens from CToken (M-02) check_ctoken_owner() only checked owner and permanent delegate. An account-level delegate (approved via CTokenApprove) could not compress tokens. Added delegate check after permanent delegate. * test: compress by delegate fix: accumulate delegated amount at decompression (#2242) * fix: accumulate delegated amount at decompression * fix lint * refactor: simplify apply_delegate to single accumulation path * fix: ignore delegated_amount without delegate * restore decompress amount check fix programtest, wallet owner tracking for ata fmt and lint upd amm test simplify client usage, remove unnecessary endpoints clean cleanup lint --- forester/tests/test_indexer_interface.rs | 50 +- sdk-libs/client/src/indexer/photon_indexer.rs | 58 +- .../client/src/interface/account_interface.rs | 280 ++-- .../client/src/interface/decompress_mint.rs | 180 +-- .../src/interface/light_program_interface.rs | 148 +-- .../client/src/interface/load_accounts.rs | 44 +- sdk-libs/client/src/interface/mod.rs | 10 +- sdk-libs/client/src/rpc/client.rs | 189 +-- sdk-libs/client/src/rpc/rpc_trait.rs | 90 +- sdk-libs/program-test/src/compressible.rs | 24 + .../program-test/src/indexer/test_indexer.rs | 5 + sdk-libs/program-test/src/program_test/rpc.rs | 333 ++--- .../tests/stress_test.rs | 42 +- .../tests/test_create_all.rs | 42 +- .../tests/test_create_ata.rs | 5 +- .../tests/test_create_mint.rs | 9 +- .../tests/test_create_token_vault.rs | 18 +- .../tests/test_create_two_mints.rs | 17 +- .../src/lib.rs | 496 ++----- .../tests/coverage.md | 623 --------- .../tests/trait_tests.rs | 1184 ++--------------- .../tests/amm_stress_test.rs | 106 +- .../tests/amm_test.rs | 51 +- .../tests/basic_test.rs | 76 +- .../tests/d10_token_accounts_test.rs | 9 +- .../tests/integration_tests.rs | 27 +- .../tests/stress_test.rs | 34 +- .../tests/test_create_all.rs | 34 +- .../tests/test_create_ata.rs | 5 +- .../tests/test_create_mint.rs | 9 +- .../tests/test_create_token_vault.rs | 18 +- .../tests/test_create_two_mints.rs | 17 +- 32 files changed, 846 insertions(+), 3387 deletions(-) delete mode 100644 sdk-tests/csdk-anchor-full-derived-test-sdk/tests/coverage.md diff --git a/forester/tests/test_indexer_interface.rs b/forester/tests/test_indexer_interface.rs index d3cc02b0f4..1675a42dd5 100644 --- a/forester/tests/test_indexer_interface.rs +++ b/forester/tests/test_indexer_interface.rs @@ -11,7 +11,7 @@ /// 4. Compressible token accounts - on-chain accounts that can be compressed use std::collections::HashMap; -use anchor_lang::Discriminator; +use anchor_lang::{AnchorDeserialize, Discriminator}; use borsh::BorshSerialize; use create_address_test_program::create_invoke_cpi_instruction; use light_client::{ @@ -368,6 +368,13 @@ async fn test_indexer_interface_scenarios() { compressed_mint_pda, create_mint_sig ); + // Warp forward so rent expires - required before CompressAndCloseMint + let current_slot = rpc.get_slot().await.unwrap(); + let target_slot = current_slot + light_compressible::rent::SLOTS_PER_EPOCH * 30; + rpc.warp_to_slot(target_slot) + .await + .expect("warp_to_slot so mint rent expires"); + // Now compress and close the mint to make it fully compressed println!("Compressing mint via CompressAndCloseMint..."); @@ -483,36 +490,41 @@ async fn test_indexer_interface_scenarios() { ); println!(" PASSED: Compressible account resolved from on-chain"); - // ============ Test 2: getTokenAccountInterface with compressible token account (on-chain) ============ - println!("\nTest 2: getTokenAccountInterface with compressible token account (on-chain)..."); - let compressible_token_interface = photon_indexer - .get_token_account_interface(&compressible_token_account, None) + println!("\nTest 2: getAccountInterface for compressible token account (on-chain)..."); + let compressible_token_interface = rpc + .get_account_interface(&compressible_token_account, None) .await - .expect("getTokenAccountInterface should not error") + .expect("getAccountInterface should not error") .value - .expect("Compressible token account should be found via token interface"); + .expect("Compressible token account should be found"); assert!( - compressible_token_interface.account.is_hot(), + compressible_token_interface.is_hot(), "Token account should be hot (on-chain)" ); assert!( - compressible_token_interface.account.cold.is_none(), + compressible_token_interface.cold.is_none(), "On-chain token account should not have cold context" ); assert_eq!( - compressible_token_interface.account.key, compressible_token_account, + compressible_token_interface.key, compressible_token_account, "Token account key should match" ); - assert_eq!( - compressible_token_interface.token.mint, decompressed_mint_pda, - "Token mint should match decompressed mint" - ); - assert_eq!( - compressible_token_interface.token.owner, - compressible_owner.pubkey(), - "Token owner should match compressible owner" - ); + { + let token = light_token_interface::state::Token::deserialize( + &mut &compressible_token_interface.account.data[..], + ) + .expect("parse token account"); + assert_eq!( + token.mint, decompressed_mint_pda, + "Token mint should match decompressed mint" + ); + assert_eq!( + token.owner, + compressible_owner.pubkey(), + "Token owner should match compressible owner" + ); + } println!(" PASSED: Token account interface resolved with correct token data"); // ============ Test 3: getMultipleAccountInterfaces batch lookup ============ diff --git a/sdk-libs/client/src/indexer/photon_indexer.rs b/sdk-libs/client/src/indexer/photon_indexer.rs index 26d16ae235..7aed81762a 100644 --- a/sdk-libs/client/src/indexer/photon_indexer.rs +++ b/sdk-libs/client/src/indexer/photon_indexer.rs @@ -9,7 +9,7 @@ use tracing::{error, trace, warn}; use super::types::{ AccountInterface, CompressedAccount, CompressedTokenAccount, OwnerBalance, - SignatureWithMetadata, TokenAccountInterface, TokenBalance, + SignatureWithMetadata, TokenBalance, }; use crate::indexer::{ base58::Base58Conversions, @@ -1761,46 +1761,6 @@ impl PhotonIndexer { .await } - pub async fn get_token_account_interface( - &self, - address: &Pubkey, - config: Option, - ) -> Result>, IndexerError> { - let response = self.get_account_interface(address, config).await?; - let value = match response.value { - Some(ai) => { - let token = parse_token_data_from_indexer_account(&ai)?; - Some(TokenAccountInterface { account: ai, token }) - } - None => None, - }; - Ok(Response { - context: response.context, - value, - }) - } - - pub async fn get_associated_token_account_interface( - &self, - owner: &Pubkey, - mint: &Pubkey, - config: Option, - ) -> Result>, IndexerError> { - let ata_address = light_token::instruction::get_associated_token_address(owner, mint); - let response = self.get_account_interface(&ata_address, config).await?; - let value = match response.value { - Some(ai) => { - let token = parse_token_data_from_indexer_account(&ai)?; - Some(TokenAccountInterface { account: ai, token }) - } - None => None, - }; - Ok(Response { - context: response.context, - value, - }) - } - pub async fn get_multiple_account_interfaces( &self, addresses: Vec<&Pubkey>, @@ -1853,19 +1813,3 @@ impl PhotonIndexer { .await } } - -/// Parse token data from an indexer AccountInterface. -/// For compressed (cold) accounts: borsh-deserializes TokenData from the cold data bytes. -/// For on-chain (hot) accounts: returns default TokenData (downstream conversion re-parses from SPL layout). -fn parse_token_data_from_indexer_account( - ai: &AccountInterface, -) -> Result { - match &ai.cold { - Some(cold) => borsh::BorshDeserialize::deserialize(&mut cold.data.data.as_slice()) - .map_err(|e| IndexerError::decode_error("token_data", e)), - None => { - // Hot account — downstream will re-parse from SPL account data directly - Ok(light_token::compat::TokenData::default()) - } - } -} diff --git a/sdk-libs/client/src/interface/account_interface.rs b/sdk-libs/client/src/interface/account_interface.rs index 22488a2511..ae54132347 100644 --- a/sdk-libs/client/src/interface/account_interface.rs +++ b/sdk-libs/client/src/interface/account_interface.rs @@ -1,21 +1,15 @@ //! Unified account interfaces for hot/cold account handling. //! -//! Core types: -//! - `AccountInterface` - Generic account (PDAs, mints) -//! - `TokenAccountInterface` - Token accounts (ATAs, program-owned vaults) +//! Core type: `AccountInterface` - Generic account (PDAs, mints, ATAs). +//! Consumers parse `account.data` (SPL layout) for hot or cold. //! //! All interfaces use standard Solana/SPL types: //! - `solana_account::Account` for raw account data //! - `spl_token_2022_interface::pod::PodAccount` for parsed token data -use light_token::utils::get_associated_token_address_and_bump; -use light_token_interface::state::ExtensionStruct; use solana_account::Account; use solana_pubkey::Pubkey; -use spl_pod::{ - bytemuck::{pod_bytes_of, pod_from_bytes, pod_get_packed_len}, - primitives::PodU64, -}; +use spl_pod::{bytemuck::pod_bytes_of, primitives::PodU64}; use spl_token_2022_interface::{ pod::{PodAccount, PodCOption}, state::AccountState, @@ -24,6 +18,22 @@ use thiserror::Error; use crate::indexer::{CompressedAccount, CompressedTokenAccount, TreeInfo}; +/// Context for cold accounts. +/// +/// Three variants based on data structure: +/// - `Account` - Generic PDA +/// - `Token` - Token account +/// - `Mint` - Compressed mint +#[derive(Clone, Debug, PartialEq)] +pub enum ColdContext { + /// Generic PDA + Account(CompressedAccount), + /// Token account + Token(CompressedTokenAccount), + /// Compressed mint + Mint(CompressedAccount), +} + /// Error type for account interface operations. #[derive(Debug, Error)] pub enum AccountInterfaceError { @@ -48,8 +58,8 @@ pub struct AccountInterface { pub key: Pubkey, /// Standard Solana Account (lamports, data, owner, executable, rent_epoch). pub account: Account, - /// Compressed account data (only present when cold). - pub cold: Option, + /// Cold context (only present when cold). + pub cold: Option, } impl AccountInterface { @@ -83,128 +93,22 @@ impl AccountInterface { executable: false, rent_epoch: 0, }, - cold: Some(compressed), - } - } - - /// Whether this account is cold. - #[inline] - pub fn is_cold(&self) -> bool { - self.cold.is_some() - } - - /// Whether this account is hot. - #[inline] - pub fn is_hot(&self) -> bool { - self.cold.is_none() - } - - /// Get data bytes. - #[inline] - pub fn data(&self) -> &[u8] { - &self.account.data - } - - /// Get the account hash if cold. - pub fn hash(&self) -> Option<[u8; 32]> { - self.cold.as_ref().map(|c| c.hash) - } - - /// Get tree info if cold. - pub fn tree_info(&self) -> Option<&TreeInfo> { - self.cold.as_ref().map(|c| &c.tree_info) - } - - /// Get leaf index if cold. - pub fn leaf_index(&self) -> Option { - self.cold.as_ref().map(|c| c.leaf_index) - } - - /// Get as CompressedAccount if cold. - pub fn as_compressed_account(&self) -> Option<&CompressedAccount> { - self.cold.as_ref() - } - - /// Try to parse as Mint. Returns None if not a mint or parse fails. - pub fn as_mint(&self) -> Option { - let ca = self.cold.as_ref()?; - let data = ca.data.as_ref()?; - borsh::BorshDeserialize::deserialize(&mut data.data.as_slice()).ok() - } - - /// Get mint signer if this is a cold mint. - pub fn mint_signer(&self) -> Option<[u8; 32]> { - self.as_mint().map(|m| m.metadata.mint_signer) - } - - /// Get mint compressed address if this is a cold mint. - pub fn mint_compressed_address(&self) -> Option<[u8; 32]> { - self.as_mint().map(|m| m.metadata.compressed_address()) - } -} - -/// Token account interface with both raw and parsed data. -/// -/// Uses standard types: -/// - `solana_account::Account` for raw bytes -/// - `spl_token_2022_interface::pod::PodAccount` for parsed token data -/// -/// For ATAs: `parsed.owner` is the wallet owner (set from fetch params). -/// For program-owned: `parsed.owner` is the PDA. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct TokenAccountInterface { - /// The token account's public key. - pub key: Pubkey, - /// Standard Solana Account (lamports, data, owner, executable, rent_epoch). - pub account: Account, - /// Parsed SPL Token Account (POD format). - pub parsed: PodAccount, - /// Compressed token account data (only present when cold). - pub cold: Option, - /// Optional TLV extension data. - pub extensions: Option>, -} - -impl TokenAccountInterface { - /// Create a hot (on-chain) token account interface. - pub fn hot(key: Pubkey, account: Account) -> Result { - let pod_len = pod_get_packed_len::(); - if account.data.len() < pod_len { - return Err(AccountInterfaceError::InvalidData); + cold: Some(ColdContext::Account(compressed)), } - - let parsed: &PodAccount = pod_from_bytes(&account.data[..pod_len]) - .map_err(|e| AccountInterfaceError::ParseError(e.to_string()))?; - - Ok(Self { - key, - parsed: *parsed, - account, - cold: None, - extensions: None, - }) } - /// Create a cold token account interface. - /// - /// # Arguments - /// * `key` - The token account address - /// * `compressed` - The cold token account from indexer - /// * `owner_override` - For ATAs, pass the wallet owner. For program-owned, pass the PDA. - /// * `program_owner` - The program that owns this account (usually LIGHT_TOKEN_PROGRAM_ID) - pub fn cold( + /// Create a cold account interface for a token account. + pub fn cold_token( key: Pubkey, compressed: CompressedTokenAccount, - owner_override: Pubkey, - program_owner: Pubkey, + wallet_owner: Pubkey, ) -> Self { use light_token::compat::AccountState as LightAccountState; let token = &compressed.token; - let parsed = PodAccount { mint: token.mint, - owner: owner_override, + owner: wallet_owner, amount: PodU64::from(token.amount), delegate: match token.delegate { Some(pk) => PodCOption::some(pk), @@ -218,25 +122,18 @@ impl TokenAccountInterface { delegated_amount: PodU64::from(0u64), close_authority: PodCOption::none(), }; - let data = pod_bytes_of(&parsed).to_vec(); - let extensions = token.tlv.clone(); - - let account = Account { - lamports: compressed.account.lamports, - data, - owner: program_owner, - executable: false, - rent_epoch: 0, - }; - Self { key, - account, - parsed, - cold: Some(compressed), - extensions, + account: Account { + lamports: compressed.account.lamports, + data, + owner: light_token::instruction::LIGHT_TOKEN_PROGRAM_ID, + executable: false, + rent_epoch: 0, + }, + cold: Some(ColdContext::Token(compressed)), } } @@ -252,82 +149,77 @@ impl TokenAccountInterface { self.cold.is_none() } - /// Get the CompressedTokenAccount if cold. - pub fn compressed(&self) -> Option<&CompressedTokenAccount> { - self.cold.as_ref() - } - - /// Get amount. - #[inline] - pub fn amount(&self) -> u64 { - u64::from(self.parsed.amount) - } - - /// Get delegate. - #[inline] - pub fn delegate(&self) -> Option { - if self.parsed.delegate.is_some() { - Some(self.parsed.delegate.value) - } else { - None - } - } - - /// Get mint. - #[inline] - pub fn mint(&self) -> Pubkey { - self.parsed.mint - } - - /// Get owner (wallet for ATAs, PDA for program-owned). - #[inline] - pub fn owner(&self) -> Pubkey { - self.parsed.owner - } - - /// Check if frozen. + /// Get data bytes. #[inline] - pub fn is_frozen(&self) -> bool { - self.parsed.state == AccountState::Frozen as u8 + pub fn data(&self) -> &[u8] { + &self.account.data } /// Get the account hash if cold. - #[inline] pub fn hash(&self) -> Option<[u8; 32]> { - self.compressed().map(|c| c.account.hash) + match &self.cold { + Some(ColdContext::Account(c)) => Some(c.hash), + Some(ColdContext::Token(c)) => Some(c.account.hash), + Some(ColdContext::Mint(c)) => Some(c.hash), + None => None, + } } /// Get tree info if cold. - #[inline] pub fn tree_info(&self) -> Option<&TreeInfo> { - self.compressed().map(|c| &c.account.tree_info) + match &self.cold { + Some(ColdContext::Account(c)) => Some(&c.tree_info), + Some(ColdContext::Token(c)) => Some(&c.account.tree_info), + Some(ColdContext::Mint(c)) => Some(&c.tree_info), + None => None, + } } /// Get leaf index if cold. - #[inline] pub fn leaf_index(&self) -> Option { - self.compressed().map(|c| c.account.leaf_index) + match &self.cold { + Some(ColdContext::Account(c)) => Some(c.leaf_index), + Some(ColdContext::Token(c)) => Some(c.account.leaf_index), + Some(ColdContext::Mint(c)) => Some(c.leaf_index), + None => None, + } } - /// Get ATA bump if this is an ATA. Returns None if not a valid ATA derivation. - pub fn ata_bump(&self) -> Option { - let (derived_ata, bump) = - get_associated_token_address_and_bump(&self.parsed.owner, &self.parsed.mint); - (derived_ata == self.key).then_some(bump) + /// Get as CompressedAccount if cold account or mint type. + pub fn as_compressed_account(&self) -> Option<&CompressedAccount> { + match &self.cold { + Some(ColdContext::Account(c)) => Some(c), + Some(ColdContext::Mint(c)) => Some(c), + _ => None, + } } - /// Check if this token account is an ATA (derivation matches). - pub fn is_ata(&self) -> bool { - self.ata_bump().is_some() + /// Get as CompressedTokenAccount if cold token type. + pub fn as_compressed_token(&self) -> Option<&CompressedTokenAccount> { + match &self.cold { + Some(ColdContext::Token(c)) => Some(c), + _ => None, + } } -} -impl From for AccountInterface { - fn from(tai: TokenAccountInterface) -> Self { - Self { - key: tai.key, - account: tai.account, - cold: tai.cold.map(|ct| ct.account), + /// Try to parse as Mint. Returns None if not a mint or parse fails. + pub fn as_mint(&self) -> Option { + match &self.cold { + Some(ColdContext::Mint(ca)) | Some(ColdContext::Account(ca)) => { + let data = ca.data.as_ref()?; + borsh::BorshDeserialize::deserialize(&mut data.data.as_slice()).ok() + } + _ => None, } } + + /// Get mint signer if this is a cold mint. + pub fn mint_signer(&self) -> Option<[u8; 32]> { + self.as_mint().map(|m| m.metadata.mint_signer) + } + + /// Get mint compressed address if this is a cold mint. + pub fn mint_compressed_address(&self) -> Option<[u8; 32]> { + self.as_mint().map(|m| m.metadata.compressed_address()) + } } diff --git a/sdk-libs/client/src/interface/decompress_mint.rs b/sdk-libs/client/src/interface/decompress_mint.rs index 9c117e2545..6aac9e1aa0 100644 --- a/sdk-libs/client/src/interface/decompress_mint.rs +++ b/sdk-libs/client/src/interface/decompress_mint.rs @@ -1,4 +1,4 @@ -//! Mint interface types for hot/cold handling. +//! Mint decompression for hot/cold handling. use borsh::BorshDeserialize; use light_compressed_account::{ @@ -10,13 +10,12 @@ use light_token_interface::{ state::Mint, MINT_ADDRESS_TREE, }; -use solana_account::Account; use solana_instruction::Instruction; use solana_pubkey::Pubkey; use thiserror::Error; use super::AccountInterface; -use crate::indexer::{CompressedAccount, Indexer, ValidityProofWithContext}; +use crate::indexer::{Indexer, ValidityProofWithContext}; /// Error type for mint load operations. #[derive(Debug, Error)] @@ -27,6 +26,9 @@ pub enum DecompressMintError { #[error("Missing mint data in cold account")] MissingMintData, + #[error("Invalid mint account owner: expected Light Token Program")] + InvalidMintOwner, + #[error("Program error: {0}")] ProgramError(#[from] solana_program_error::ProgramError), @@ -40,138 +42,29 @@ pub enum DecompressMintError { IndexerError(#[from] crate::indexer::IndexerError), } -/// Mint state: hot (on-chain), cold (compressed), or none. -#[derive(Debug, Clone, PartialEq, Default)] -#[allow(clippy::large_enum_variant)] -pub enum MintState { - /// On-chain. - Hot { account: Account }, - /// Compressed. - Cold { - compressed: CompressedAccount, - mint_data: Mint, - }, - /// Doesn't exist. - #[default] - None, -} - -/// Mint interface for hot/cold handling. -#[derive(Debug, Clone, PartialEq, Default)] -pub struct MintInterface { - pub mint: Pubkey, - pub address_tree: Pubkey, - pub compressed_address: [u8; 32], - pub state: MintState, -} - -impl MintInterface { - #[inline] - pub fn is_cold(&self) -> bool { - matches!(self.state, MintState::Cold { .. }) - } - - #[inline] - pub fn is_hot(&self) -> bool { - matches!(self.state, MintState::Hot { .. }) - } - - pub fn hash(&self) -> Option<[u8; 32]> { - match &self.state { - MintState::Cold { compressed, .. } => Some(compressed.hash), - _ => None, - } - } - - pub fn account(&self) -> Option<&Account> { - match &self.state { - MintState::Hot { account } => Some(account), - _ => None, - } - } - - pub fn compressed(&self) -> Option<(&CompressedAccount, &Mint)> { - match &self.state { - MintState::Cold { - compressed, - mint_data, - } => Some((compressed, mint_data)), - _ => None, - } - } -} - -impl From for AccountInterface { - fn from(mi: MintInterface) -> Self { - match mi.state { - MintState::Hot { account } => Self { - key: mi.mint, - account, - cold: None, - }, - MintState::Cold { - compressed, - mint_data: _, - } => { - let data = compressed - .data - .as_ref() - .map(|d| { - let mut buf = d.discriminator.to_vec(); - buf.extend_from_slice(&d.data); - buf - }) - .unwrap_or_default(); - - Self { - key: mi.mint, - account: Account { - lamports: compressed.lamports, - data, - owner: Pubkey::new_from_array( - light_token_interface::LIGHT_TOKEN_PROGRAM_ID, - ), - executable: false, - rent_epoch: 0, - }, - cold: Some(compressed), - } - } - MintState::None => Self { - key: mi.mint, - account: Account::default(), - cold: None, - }, - } - } -} - pub const DEFAULT_RENT_PAYMENT: u8 = 2; pub const DEFAULT_WRITE_TOP_UP: u32 = 0; /// Builds load instruction for a cold mint. Returns empty vec if already hot. pub fn build_decompress_mint( - mint: &MintInterface, + mint: &AccountInterface, fee_payer: Pubkey, validity_proof: Option, rent_payment: Option, write_top_up: Option, ) -> Result, DecompressMintError> { - // Fast exit if hot - let mint_data = match &mint.state { - MintState::Hot { .. } | MintState::None => return Ok(vec![]), - MintState::Cold { mint_data, .. } => mint_data, - }; - - // Check if already decompressed flag is set - return empty vec (idempotent) + if mint.is_hot() { + return Ok(vec![]); + } + let mint_data = mint.as_mint().ok_or(DecompressMintError::ProofRequired)?; if mint_data.metadata.mint_decompressed { return Ok(vec![]); } + let compressed_address = mint + .mint_compressed_address() + .ok_or(DecompressMintError::ProofRequired)?; - // Proof required for cold mint let proof_result = validity_proof.ok_or(DecompressMintError::ProofRequired)?; - - // Extract tree info from proof result let account_info = &proof_result.accounts[0]; let state_tree = account_info.tree_info.tree; let input_queue = account_info.tree_info.queue; @@ -182,7 +75,6 @@ pub fn build_decompress_mint( .map(|next| next.queue) .unwrap_or(input_queue); - // Build MintWithContext let mint_instruction_data = MintInstructionData::try_from(mint_data.clone()) .map_err(|_| DecompressMintError::MissingMintData)?; @@ -190,14 +82,13 @@ pub fn build_decompress_mint( leaf_index: account_info.leaf_index as u32, prove_by_index: account_info.root_index.proof_by_index(), root_index: account_info.root_index.root_index().unwrap_or_default(), - address: mint.compressed_address, + address: compressed_address, mint: Some(mint_instruction_data), }; - // Build DecompressMint instruction let decompress = DecompressMint { payer: fee_payer, - authority: fee_payer, // Permissionless - any signer works + authority: fee_payer, state_tree, input_queue, output_queue, @@ -215,30 +106,25 @@ pub fn build_decompress_mint( /// Load (decompress) a pre-fetched mint. Returns empty vec if already hot. pub async fn decompress_mint( - mint: &MintInterface, + mint: &AccountInterface, fee_payer: Pubkey, indexer: &I, ) -> Result, DecompressMintError> { - // Fast exit if hot or doesn't exist let hash = match mint.hash() { Some(h) => h, None => return Ok(vec![]), }; - - // Check decompressed flag before fetching proof - if let Some((_, mint_data)) = mint.compressed() { + if let Some(mint_data) = mint.as_mint() { if mint_data.metadata.mint_decompressed { return Ok(vec![]); } } - // Get validity proof let proof = indexer .get_validity_proof(vec![hash], vec![], None) .await? .value; - // Build instruction (sync) build_decompress_mint(mint, fee_payer, Some(proof), None, None) } @@ -364,35 +250,3 @@ pub async fn decompress_mint_idempotent( .map_err(DecompressMintError::from)?; Ok(vec![ix]) } - -/// Create MintInterface from mint address and state data. -pub fn create_mint_interface( - address: Pubkey, - address_tree: Pubkey, - onchain_account: Option, - compressed: Option<(CompressedAccount, Mint)>, -) -> MintInterface { - let compressed_address = light_compressed_account::address::derive_address( - &address.to_bytes(), - &address_tree.to_bytes(), - &light_token_interface::LIGHT_TOKEN_PROGRAM_ID, - ); - - let state = if let Some(account) = onchain_account { - MintState::Hot { account } - } else if let Some((compressed, mint_data)) = compressed { - MintState::Cold { - compressed, - mint_data, - } - } else { - MintState::None - }; - - MintInterface { - mint: address, - address_tree, - compressed_address, - state, - } -} diff --git a/sdk-libs/client/src/interface/light_program_interface.rs b/sdk-libs/client/src/interface/light_program_interface.rs index a128d20151..12b78f0705 100644 --- a/sdk-libs/client/src/interface/light_program_interface.rs +++ b/sdk-libs/client/src/interface/light_program_interface.rs @@ -1,4 +1,4 @@ -//! LightProgramInterface trait and supporting types for client-side SDK patterns. +//! LightProgramInterface trait and supporting types for client-side cold account handling. //! //! Core types: //! - `PdaSpec` - Spec for PDA loading with typed variant @@ -8,56 +8,10 @@ use std::fmt::Debug; use light_account::Pack; -use light_token::instruction::derive_token_ata; use solana_pubkey::Pubkey; -use super::{AccountInterface, TokenAccountInterface}; -use crate::indexer::CompressedAccount; - -/// Account descriptor for fetching. Routes to the correct indexer endpoint. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum AccountToFetch { - /// PDA account - uses `get_account_interface(address, program_id)` - Pda { address: Pubkey, program_id: Pubkey }, - /// Token account (program-owned) - uses `get_token_account_interface(address)` - Token { address: Pubkey }, - /// ATA - uses `get_associated_token_account_interface(wallet_owner, mint)` - Ata { wallet_owner: Pubkey, mint: Pubkey }, - /// Light mint - uses `get_account_interface(address)` (clients parse mint data) - Mint { address: Pubkey }, -} - -impl AccountToFetch { - pub fn pda(address: Pubkey, program_id: Pubkey) -> Self { - Self::Pda { - address, - program_id, - } - } - - pub fn token(address: Pubkey) -> Self { - Self::Token { address } - } - - pub fn ata(wallet_owner: Pubkey, mint: Pubkey) -> Self { - Self::Ata { wallet_owner, mint } - } - - pub fn mint(address: Pubkey) -> Self { - Self::Mint { address } - } - - /// Returns the primary pubkey for this fetch request. - #[must_use] - pub fn pubkey(&self) -> Pubkey { - match self { - Self::Pda { address, .. } => *address, - Self::Token { address } => *address, - Self::Ata { wallet_owner, mint } => derive_token_ata(wallet_owner, mint), - Self::Mint { address } => *address, - } - } -} +use super::{AccountInterface, ColdContext}; +use crate::indexer::{CompressedAccount, CompressedTokenAccount}; /// Specification for a program-owned PDA with typed variant. /// @@ -114,7 +68,24 @@ impl PdaSpec { /// Get the compressed account if cold. #[must_use] pub fn compressed(&self) -> Option<&CompressedAccount> { - self.interface.cold.as_ref() + match &self.interface.cold { + Some(ColdContext::Account(c)) => Some(c), + Some(ColdContext::Token(c)) => Some(&c.account), + Some(ColdContext::Mint(c)) => Some(c), + None => None, + } + } + + /// Get the compressed token account if this is a cold token PDA. + #[must_use] + pub fn compressed_token(&self) -> Option<&CompressedTokenAccount> { + self.interface.as_compressed_token() + } + + /// Whether this spec is for a token PDA (cold context is Token variant). + #[must_use] + pub fn is_token_pda(&self) -> bool { + self.interface.as_compressed_token().is_some() } /// Get the cold account hash. @@ -137,7 +108,7 @@ pub enum AccountSpec { /// Program-owned PDA with typed variant. Pda(PdaSpec), /// Associated token account - Ata(Box), + Ata(AccountInterface), /// Light token mint Mint(AccountInterface), } @@ -175,12 +146,6 @@ impl From> for AccountSpec { } } -impl From for AccountSpec<()> { - fn from(interface: TokenAccountInterface) -> Self { - Self::Ata(Box::new(interface)) - } -} - impl From for AccountSpec<()> { fn from(interface: AccountInterface) -> Self { Self::Mint(interface) @@ -201,65 +166,38 @@ pub fn all_hot(specs: &[AccountSpec]) -> bool { specs.iter().all(|s| s.is_hot()) } -/// Trait for programs to give clients a unified API to load cold program accounts. +/// Trait for program SDKs to produce load specs for cold accounts. +/// +/// Implementors hold parsed program state (e.g., pool config, vault addresses, +/// seed values). The trait provides two methods: +/// - `instruction_accounts`: which pubkeys does this instruction reference? +/// - `load_specs`: given cold AccountInterfaces, build AccountSpec with variants. +/// +/// The caller handles construction, caching, and cold detection. +/// The trait only maps cold accounts to their variants for `create_load_instructions`. pub trait LightProgramInterface: Sized { - /// The program's interface account variant enum. + /// The program's account variant enum (macro-generated, carries PDA seeds). type Variant: Pack + Clone + Debug; /// Program-specific instruction enum. type Instruction; - /// Error type for SDK operations. - type Error: std::error::Error; - /// The program ID. - #[must_use] - fn program_id(&self) -> Pubkey; + fn program_id() -> Pubkey; - /// Construct SDK from root account(s). - fn from_keyed_accounts(accounts: &[AccountInterface]) -> Result; - - /// Returns pubkeys of accounts needed for an instruction. + /// Which compressible account pubkeys does this instruction reference? + /// Used by callers to check which accounts might need loading. #[must_use] - fn get_accounts_to_update(&self, ix: &Self::Instruction) -> Vec; - - /// Update internal cache with fetched account data. - fn update(&mut self, accounts: &[AccountInterface]) -> Result<(), Self::Error>; + fn instruction_accounts(&self, ix: &Self::Instruction) -> Vec; - /// Get all cached specs. - #[must_use] - fn get_all_specs(&self) -> Vec>; - - /// Get specs filtered for a specific instruction. - #[must_use] - fn get_specs_for_instruction(&self, ix: &Self::Instruction) -> Vec>; - - /// Get only cold specs from all cached specs. - #[must_use] - fn get_cold_specs(&self) -> Vec> { - self.get_all_specs() - .into_iter() - .filter(|s| s.is_cold()) - .collect() - } - - /// Get only cold specs for a specific instruction. - #[must_use] - fn get_cold_specs_for_instruction( + /// Build AccountSpec for cold accounts. + /// Matches each AccountInterface by pubkey, constructs the variant (seeds) + /// from internal parsed state, wraps in PdaSpec/AccountSpec. + /// Only called on the cold path. + fn load_specs( &self, - ix: &Self::Instruction, - ) -> Vec> { - self.get_specs_for_instruction(ix) - .into_iter() - .filter(|s| s.is_cold()) - .collect() - } - - /// Check if any accounts for this instruction are cold. - #[must_use] - fn needs_loading(&self, ix: &Self::Instruction) -> bool { - any_cold(&self.get_specs_for_instruction(ix)) - } + cold_accounts: &[AccountInterface], + ) -> Result>, Box>; } /// Extract 8-byte discriminator from account data. diff --git a/sdk-libs/client/src/interface/load_accounts.rs b/sdk-libs/client/src/interface/load_accounts.rs index a73ecafa9f..5754062dd6 100644 --- a/sdk-libs/client/src/interface/load_accounts.rs +++ b/sdk-libs/client/src/interface/load_accounts.rs @@ -27,13 +27,15 @@ use light_token_interface::{ }; use solana_instruction::Instruction; use solana_pubkey::Pubkey; +use spl_pod::bytemuck::{pod_from_bytes, pod_get_packed_len}; +use spl_token_2022_interface::pod::PodAccount; use thiserror::Error; use super::{ decompress_mint::{DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP}, instructions::{self, DECOMPRESS_ACCOUNTS_IDEMPOTENT_DISCRIMINATOR}, light_program_interface::{AccountSpec, PdaSpec}, - AccountInterface, TokenAccountInterface, + AccountInterface, }; use crate::indexer::{ CompressedAccount, CompressedTokenAccount, Indexer, IndexerError, ValidityProofWithContext, @@ -100,7 +102,7 @@ where let cold_atas: Vec<_> = specs .iter() .filter_map(|s| match s { - AccountSpec::Ata(a) if a.is_cold() => Some(a.as_ref()), + AccountSpec::Ata(a) if a.is_cold() => Some(a), _ => None, }) .collect(); @@ -166,9 +168,7 @@ fn collect_pda_hashes(specs: &[&PdaSpec]) -> Result, LoadAcc .collect() } -fn collect_ata_hashes( - ifaces: &[&TokenAccountInterface], -) -> Result, LoadAccountsError> { +fn collect_ata_hashes(ifaces: &[&AccountInterface]) -> Result, LoadAccountsError> { ifaces .iter() .enumerate() @@ -289,32 +289,46 @@ struct AtaContext<'a> { impl<'a> AtaContext<'a> { fn from_interface( - iface: &'a TokenAccountInterface, + iface: &'a AccountInterface, index: usize, ) -> Result { - let compressed = iface - .compressed() + let compressed = + iface + .as_compressed_token() + .ok_or(LoadAccountsError::MissingAtaContext { + index, + pubkey: iface.key, + })?; + let pod_len = pod_get_packed_len::(); + let parsed: &PodAccount = iface + .account + .data + .get(..pod_len) + .and_then(|d| pod_from_bytes(d).ok()) .ok_or(LoadAccountsError::MissingAtaContext { index, pubkey: iface.key, })?; - let bump = iface - .ata_bump() - .ok_or(LoadAccountsError::MissingAtaContext { + let wallet_owner = parsed.owner; + let mint = parsed.mint; + let (derived_ata, bump) = derive_token_ata(&wallet_owner, &mint); + if derived_ata != iface.key { + return Err(LoadAccountsError::MissingAtaContext { index, pubkey: iface.key, - })?; + }); + } Ok(Self { compressed, - wallet_owner: iface.owner(), - mint: iface.mint(), + wallet_owner, + mint, bump, }) } } fn build_ata_load( - ifaces: &[&TokenAccountInterface], + ifaces: &[&AccountInterface], proof: ValidityProofWithContext, fee_payer: Pubkey, ) -> Result, LoadAccountsError> { diff --git a/sdk-libs/client/src/interface/mod.rs b/sdk-libs/client/src/interface/mod.rs index 5a587556f4..31ccd583d8 100644 --- a/sdk-libs/client/src/interface/mod.rs +++ b/sdk-libs/client/src/interface/mod.rs @@ -10,19 +10,17 @@ pub mod load_accounts; pub mod pack; pub mod tx_size; -pub use account_interface::{AccountInterface, AccountInterfaceError, TokenAccountInterface}; +pub use account_interface::{AccountInterface, AccountInterfaceError, ColdContext}; pub use create_accounts_proof::{ get_create_accounts_proof, CreateAccountsProofError, CreateAccountsProofInput, CreateAccountsProofResult, }; -pub use decompress_mint::{ - DecompressMintError, MintInterface, MintState, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, -}; +pub use decompress_mint::{DecompressMintError, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP}; pub use initialize_config::InitializeRentFreeConfig; pub use light_account::LightConfig; pub use light_program_interface::{ - all_hot, any_cold, discriminator, matches_discriminator, AccountSpec, AccountToFetch, - LightProgramInterface, PdaSpec, + all_hot, any_cold, discriminator, matches_discriminator, AccountSpec, LightProgramInterface, + PdaSpec, }; pub use light_sdk_types::interface::CreateAccountsProof; pub use light_token::compat::TokenData; diff --git a/sdk-libs/client/src/rpc/client.rs b/sdk-libs/client/src/rpc/client.rs index 82d543c002..aa9a8704af 100644 --- a/sdk-libs/client/src/rpc/client.rs +++ b/sdk-libs/client/src/rpc/client.rs @@ -37,10 +37,9 @@ use crate::rpc::get_light_state_tree_infos::{ use crate::{ indexer::{ photon_indexer::PhotonIndexer, AccountInterface as IndexerAccountInterface, Indexer, - IndexerRpcConfig, Response, TokenAccountInterface as IndexerTokenAccountInterface, - TreeInfo, + IndexerRpcConfig, Response, TreeInfo, }, - interface::{AccountInterface, MintInterface, MintState, TokenAccountInterface}, + interface::AccountInterface, rpc::{errors::RpcError, merkle_tree::MerkleTreeExt, Rpc}, }; @@ -565,44 +564,6 @@ fn convert_account_interface( } } -fn convert_token_account_interface( - indexer_tai: IndexerTokenAccountInterface, -) -> Result { - use crate::indexer::CompressedTokenAccount; - - let account = Account { - lamports: indexer_tai.account.account.lamports, - data: indexer_tai.account.account.data.clone(), - owner: indexer_tai.account.account.owner, - executable: indexer_tai.account.account.executable, - rent_epoch: indexer_tai.account.account.rent_epoch, - }; - - match indexer_tai.account.cold { - None => TokenAccountInterface::hot(indexer_tai.account.key, account) - .map_err(|e| RpcError::CustomError(format!("parse error: {}", e))), - Some(cold) => { - let compressed_account = cold_context_to_compressed_account( - &cold, - indexer_tai.account.account.lamports, - indexer_tai.account.account.owner, - ); - // Extract token owner before moving token into CompressedTokenAccount - let token_owner = indexer_tai.token.owner; - let compressed_token = CompressedTokenAccount { - token: indexer_tai.token, - account: compressed_account, - }; - Ok(TokenAccountInterface::cold( - indexer_tai.account.key, - compressed_token, - token_owner, // owner_override: use token owner, not account key - indexer_tai.account.account.owner, - )) - } - } -} - #[async_trait] impl Rpc for LightClient { async fn new(config: LightClientConfig) -> Result @@ -1067,57 +1028,6 @@ impl Rpc for LightClient { }) } - async fn get_token_account_interface( - &self, - address: &Pubkey, - config: Option, - ) -> Result>, RpcError> { - let indexer = self - .indexer - .as_ref() - .ok_or(RpcError::IndexerNotInitialized)?; - let resp = indexer - .get_token_account_interface(address, config) - .await - .map_err(|e| RpcError::CustomError(format!("Indexer error: {e}")))?; - - let value = match resp.value { - Some(tai) => Some(convert_token_account_interface(tai)?), - None => None, - }; - - Ok(Response { - context: resp.context, - value, - }) - } - - async fn get_associated_token_account_interface( - &self, - owner: &Pubkey, - mint: &Pubkey, - config: Option, - ) -> Result>, RpcError> { - let indexer = self - .indexer - .as_ref() - .ok_or(RpcError::IndexerNotInitialized)?; - let resp = indexer - .get_associated_token_account_interface(owner, mint, config) - .await - .map_err(|e| RpcError::CustomError(format!("Indexer error: {e}")))?; - - let value = match resp.value { - Some(tai) => Some(convert_token_account_interface(tai)?), - None => None, - }; - - Ok(Response { - context: resp.context, - value, - }) - } - async fn get_multiple_account_interfaces( &self, addresses: Vec<&Pubkey>, @@ -1143,101 +1053,6 @@ impl Rpc for LightClient { value: value?, }) } - - async fn get_mint_interface( - &self, - address: &Pubkey, - config: Option, - ) -> Result>, RpcError> { - use light_compressed_account::address::derive_address; - use light_token_interface::{state::Mint, MINT_ADDRESS_TREE}; - - let address_tree = Pubkey::new_from_array(MINT_ADDRESS_TREE); - let compressed_address = derive_address( - &address.to_bytes(), - &address_tree.to_bytes(), - &light_token_interface::LIGHT_TOKEN_PROGRAM_ID, - ); - - let indexer = self - .indexer - .as_ref() - .ok_or(RpcError::IndexerNotInitialized)?; - - // Use get_account_interface to check hot/cold (Photon handles derived address fallback) - let resp = indexer - .get_account_interface(address, config.clone()) - .await - .map_err(|e| RpcError::CustomError(format!("Indexer error: {e}")))?; - - let value = match resp.value { - Some(ai) => { - let state = if ai.is_cold() { - let cold = ai.cold.as_ref().ok_or_else(|| { - RpcError::CustomError("Cold mint missing cold context".into()) - })?; - - // Build CompressedAccount from indexer ColdContext - let mut compressed = cold_context_to_compressed_account( - cold, - ai.account.lamports, - ai.account.owner, - ); - - if compressed.address.is_none() { - compressed.address = Some(compressed_address); - } - - // Parse mint data from cold data bytes - let mint_data = if cold.data.data.is_empty() { - None - } else { - Mint::try_from_slice(&cold.data.data).ok() - } - .ok_or_else(|| { - RpcError::CustomError( - "Missing or invalid mint data in compressed account".into(), - ) - })?; - - MintState::Cold { - compressed, - mint_data, - } - } else { - let expected_owner = - Pubkey::new_from_array(light_token_interface::LIGHT_TOKEN_PROGRAM_ID); - if ai.account.owner != expected_owner { - return Err(RpcError::CustomError(format!( - "Invalid mint account owner: expected {}, got {}", - expected_owner, ai.account.owner, - ))); - } - Mint::try_from_slice(&ai.account.data).map_err(|e| { - RpcError::CustomError(format!( - "Failed to deserialize hot mint account: {e}" - )) - })?; - MintState::Hot { - account: ai.account, - } - }; - - Some(MintInterface { - mint: *address, - address_tree, - compressed_address, - state, - }) - } - None => None, - }; - - Ok(Response { - context: resp.context, - value, - }) - } } impl MerkleTreeExt for LightClient {} diff --git a/sdk-libs/client/src/rpc/rpc_trait.rs b/sdk-libs/client/src/rpc/rpc_trait.rs index 0c3d61caa8..add9e16063 100644 --- a/sdk-libs/client/src/rpc/rpc_trait.rs +++ b/sdk-libs/client/src/rpc/rpc_trait.rs @@ -19,7 +19,7 @@ use solana_transaction_status_client_types::TransactionStatus; use super::client::RpcUrl; use crate::{ indexer::{Indexer, IndexerRpcConfig, Response, TreeInfo}, - interface::{AccountInterface, AccountToFetch, MintInterface, TokenAccountInterface}, + interface::AccountInterface, rpc::errors::RpcError, }; @@ -246,98 +246,10 @@ pub trait Rpc: Send + Sync + Debug + 'static { config: Option, ) -> Result>, RpcError>; - /// Get token account data from either on-chain or compressed sources. - async fn get_token_account_interface( - &self, - address: &Pubkey, - config: Option, - ) -> Result>, RpcError>; - - /// Get ATA data from either on-chain or compressed sources. - async fn get_associated_token_account_interface( - &self, - owner: &Pubkey, - mint: &Pubkey, - config: Option, - ) -> Result>, RpcError>; - /// Get multiple account interfaces in a batch. async fn get_multiple_account_interfaces( &self, addresses: Vec<&Pubkey>, config: Option, ) -> Result>>, RpcError>; - - /// Get mint interface from either on-chain or compressed sources. - /// - /// This method: - /// 1. First checks if the mint exists on-chain (hot) - /// 2. Falls back to compressed account lookup (cold) using derived address - /// 3. Parses mint data locally from the account data - async fn get_mint_interface( - &self, - address: &Pubkey, - config: Option, - ) -> Result>, RpcError>; - - /// Fetch multiple accounts using `AccountToFetch` descriptors. - /// - /// Routes each account to the correct method based on its variant: - /// - `Pda` -> `get_account_interface` - /// - `Token` -> `get_token_account_interface` - /// - `Ata` -> `get_associated_token_account_interface` - /// - `Mint` -> `get_mint_interface` - async fn fetch_accounts( - &self, - accounts: &[AccountToFetch], - config: Option, - ) -> Result, RpcError> { - let mut results = Vec::with_capacity(accounts.len()); - for account in accounts { - let interface = match account { - AccountToFetch::Pda { address, .. } => self - .get_account_interface(address, config.clone()) - .await? - .value - .ok_or_else(|| { - RpcError::CustomError(format!("PDA account not found: {}", address)) - })?, - AccountToFetch::Token { address } => { - let tai = self - .get_token_account_interface(address, config.clone()) - .await? - .value - .ok_or_else(|| { - RpcError::CustomError(format!("Token account not found: {}", address)) - })?; - tai.into() - } - AccountToFetch::Ata { wallet_owner, mint } => { - let tai = self - .get_associated_token_account_interface(wallet_owner, mint, config.clone()) - .await? - .value - .ok_or_else(|| { - RpcError::CustomError(format!( - "ATA not found for owner {} mint {}", - wallet_owner, mint - )) - })?; - tai.into() - } - AccountToFetch::Mint { address } => { - let mi = self - .get_mint_interface(address, config.clone()) - .await? - .value - .ok_or_else(|| { - RpcError::CustomError(format!("Mint not found: {}", address)) - })?; - mi.into() - } - }; - results.push(interface); - } - Ok(results) - } } diff --git a/sdk-libs/program-test/src/compressible.rs b/sdk-libs/program-test/src/compressible.rs index 00138b5d7b..00c424f469 100644 --- a/sdk-libs/program-test/src/compressible.rs +++ b/sdk-libs/program-test/src/compressible.rs @@ -228,6 +228,30 @@ pub async fn claim_and_compress( } } + // Track ATA wallet owners before compression closes the on-chain accounts. + // For ATAs, the compressed token owner is the ATA pubkey (not the wallet owner), + // so we need to record the wallet owner for cold ATA lookups during decompression. + { + use light_zero_copy::traits::ZeroCopyAt; + for pubkey in compress_accounts_compression_only + .iter() + .chain(compress_accounts_normal.iter()) + { + if let Some(account) = rpc.context.get_account(pubkey) { + if let Ok((ctoken, _)) = Token::zero_copy_at(&account.data) { + if let Some(ext) = ctoken.get_compressible_extension() { + if ext.is_ata != 0 { + let wallet_owner = Pubkey::from(ctoken.owner.to_bytes()); + if let Some(indexer) = rpc.indexer.as_mut() { + indexer.ata_owner_map.insert(*pubkey, wallet_owner); + } + } + } + } + } + } + } + // Process claimable accounts in batches for token_accounts in claim_accounts.as_slice().chunks(20) { claim_forester(rpc, token_accounts, &forester_keypair, &payer).await?; diff --git a/sdk-libs/program-test/src/indexer/test_indexer.rs b/sdk-libs/program-test/src/indexer/test_indexer.rs index 32249ab81b..deff79f9ea 100644 --- a/sdk-libs/program-test/src/indexer/test_indexer.rs +++ b/sdk-libs/program-test/src/indexer/test_indexer.rs @@ -108,6 +108,9 @@ pub struct TestIndexer { pub events: Vec, /// Index mapping onchain_pubkey to compressed account index. pub onchain_pubkey_index: HashMap<[u8; 32], usize>, + /// Maps ATA pubkey -> wallet owner for compressed ATAs. + /// Populated during compression so that cold ATA lookups can resolve the wallet owner. + pub ata_owner_map: HashMap, } impl Clone for TestIndexer { @@ -123,6 +126,7 @@ impl Clone for TestIndexer { token_nullified_compressed_accounts: self.token_nullified_compressed_accounts.clone(), events: self.events.clone(), onchain_pubkey_index: self.onchain_pubkey_index.clone(), + ata_owner_map: self.ata_owner_map.clone(), } } } @@ -1353,6 +1357,7 @@ impl TestIndexer { token_nullified_compressed_accounts: vec![], group_pda, onchain_pubkey_index: HashMap::new(), + ata_owner_map: HashMap::new(), } } diff --git a/sdk-libs/program-test/src/program_test/rpc.rs b/sdk-libs/program-test/src/program_test/rpc.rs index 76d1151eb7..f8d44f862e 100644 --- a/sdk-libs/program-test/src/program_test/rpc.rs +++ b/sdk-libs/program-test/src/program_test/rpc.rs @@ -5,7 +5,7 @@ use async_trait::async_trait; use borsh::BorshDeserialize; use light_client::{ indexer::{CompressedAccount, CompressedTokenAccount, Context, Indexer, Response, TreeInfo}, - interface::{AccountInterface, MintInterface, MintState, TokenAccountInterface}, + interface::AccountInterface, rpc::{LightClientConfig, Rpc, RpcError}, }; use light_compressed_account::TreeType; @@ -426,53 +426,14 @@ impl Rpc for LightProgramTest { value: Some(AccountInterface::cold(*address, compressed, owner)), }); } - } - - Ok(Response { - context: Context { slot }, - value: None, - }) - } - async fn get_token_account_interface( - &self, - address: &Pubkey, - _config: Option, - ) -> Result>, RpcError> { - use light_sdk::constants::LIGHT_TOKEN_PROGRAM_ID; - - let light_token_program_id: Pubkey = LIGHT_TOKEN_PROGRAM_ID.into(); - let slot = self.context.get_sysvar::().slot; - - // Hot: check on-chain first (must be owned by LIGHT_TOKEN_PROGRAM_ID) - if let Some(account) = self.context.get_account(address) { - if account.lamports > 0 && account.owner == light_token_program_id { - match TokenAccountInterface::hot(*address, account) { - Ok(iface) => { - return Ok(Response { - context: Context { slot }, - value: Some(iface), - }); - } - Err(_) => { - // Fall through to cold lookup if parsing failed - } - } - } - } - - // Cold: check TestIndexer by onchain_pubkey, PDA seed, or token_data.owner - if let Some(indexer) = self.indexer.as_ref() { - // First try: lookup by onchain_pubkey (for accounts with DECOMPRESSED_PDA_DISCRIMINATOR) + // Third try: lookup in token_compressed_accounts + // Try by onchain_pubkey discriminator, PDA seed, then by token_data.owner let token_acc = indexer .find_token_account_by_onchain_pubkey(&address.to_bytes()) - .or_else(|| { - // Second try: lookup by PDA seed (for accounts whose address was derived from this pubkey) - indexer.find_token_account_by_pda_seed(&address.to_bytes()) - }); + .or_else(|| indexer.find_token_account_by_pda_seed(&address.to_bytes())); if let Some(token_acc) = token_acc { - // Convert to CompressedTokenAccount let compressed_account: CompressedAccount = token_acc .compressed_account .clone() @@ -484,123 +445,44 @@ impl Rpc for LightProgramTest { account: compressed_account, }; + // For ATAs, use the wallet owner from the ata_owner_map + let wallet_owner = indexer + .ata_owner_map + .get(address) + .copied() + .unwrap_or(*address); + return Ok(Response { context: Context { slot }, - value: Some(TokenAccountInterface::cold( + value: Some(AccountInterface::cold_token( *address, compressed_token, - *address, // owner = hot address for program-owned tokens - light_token_program_id, + wallet_owner, )), }); } - // Third try: lookup by token_data.owner (for tokens where owner == address) + // Fourth try: lookup by token_data.owner (for program-owned tokens where owner == pubkey) let result = indexer .get_compressed_token_accounts_by_owner(address, None, None) .await .map_err(|e| RpcError::CustomError(format!("indexer error: {}", e)))?; let items = result.value.items; - if items.len() > 1 { - return Err(RpcError::CustomError(format!( - "Ambiguous lookup: found {} compressed token accounts for address {}. \ - Use get_compressed_token_accounts_by_owner for multiple accounts.", - items.len(), - address - ))); - } - - if let Some(token_acc) = items.into_iter().next() { - let key = token_acc - .account - .address - .map(Pubkey::new_from_array) + if items.len() == 1 { + let token_acc = items.into_iter().next().unwrap(); + // For ATAs, use the wallet owner from the ata_owner_map + let wallet_owner = indexer + .ata_owner_map + .get(address) + .copied() .unwrap_or(*address); return Ok(Response { context: Context { slot }, - value: Some(TokenAccountInterface::cold( - key, - token_acc, - *address, // owner = hot address for program-owned tokens - light_token_program_id, - )), - }); - } - } - - Ok(Response { - context: Context { slot }, - value: None, - }) - } - - async fn get_associated_token_account_interface( - &self, - owner: &Pubkey, - mint: &Pubkey, - _config: Option, - ) -> Result>, RpcError> { - use light_client::indexer::GetCompressedTokenAccountsByOwnerOrDelegateOptions; - use light_sdk::constants::LIGHT_TOKEN_PROGRAM_ID; - use light_token::instruction::derive_token_ata; - - let ata = derive_token_ata(owner, mint); - let light_token_program_id: Pubkey = LIGHT_TOKEN_PROGRAM_ID.into(); - let slot = self.context.get_sysvar::().slot; - - // First try: on-chain (hot) lookup - // We handle this directly instead of using get_token_account_interface - // because we need to control owner_override for ata_bump() to work - if let Some(account) = self.context.get_account(&ata) { - if account.lamports > 0 && account.owner == light_token_program_id { - match TokenAccountInterface::hot(ata, account) { - Ok(iface) => { - return Ok(Response { - context: Context { slot }, - value: Some(iface), - }); - } - Err(_) => { - // Fall through to cold lookup if parsing failed - } - } - } - } - - // Cold: search compressed tokens by ata_pubkey + mint - // In Light Protocol, token_data.owner is the token account pubkey (ATA), not wallet owner - // But we need to pass the wallet owner for TokenAccountInterface::cold so ata_bump() works - if let Some(indexer) = self.indexer.as_ref() { - let options = Some(GetCompressedTokenAccountsByOwnerOrDelegateOptions { - mint: Some(*mint), - ..Default::default() - }); - let result = indexer - .get_compressed_token_accounts_by_owner(&ata, options, None) - .await - .map_err(|e| RpcError::CustomError(format!("indexer error: {}", e)))?; - - let items = result.value.items; - if items.len() > 1 { - return Err(RpcError::CustomError(format!( - "Ambiguous lookup: found {} compressed token accounts for ATA {} (owner: {}, mint: {}). \ - Use get_compressed_token_accounts_by_owner for multiple accounts.", - items.len(), - ata, - owner, - mint - ))); - } - - if let Some(token_acc) = items.into_iter().next() { - return Ok(Response { - context: Context { slot }, - value: Some(TokenAccountInterface::cold( - ata, // key = ATA pubkey (derived, so we use it directly) + value: Some(AccountInterface::cold_token( + *address, token_acc, - *owner, // owner_override = wallet owner (for ata_bump() to work) - light_token_program_id, + wallet_owner, )), }); } @@ -650,9 +532,14 @@ impl Rpc for LightProgramTest { // Batch lookup cold accounts from TestIndexer if !cold_lookup_pubkeys.is_empty() { if let Some(indexer) = self.indexer.as_ref() { + // First pass: search in compressed_accounts (PDAs, mints) let cold_results = indexer .find_multiple_compressed_accounts_by_onchain_pubkeys(&cold_lookup_pubkeys); + // Track which addresses still need PDA seed or token lookup + let mut pda_seed_lookup_indices: Vec = Vec::new(); + let mut pda_seed_lookup_pubkeys: Vec<[u8; 32]> = Vec::new(); + for (lookup_idx, maybe_compressed) in cold_results.into_iter().enumerate() { let original_idx = cold_lookup_indices[lookup_idx]; if let Some(compressed_with_ctx) = maybe_compressed { @@ -667,95 +554,103 @@ impl Rpc for LightProgramTest { compressed, owner, )); + } else { + pda_seed_lookup_indices.push(original_idx); + pda_seed_lookup_pubkeys.push(cold_lookup_pubkeys[lookup_idx]); } } - } - } - Ok(Response { - context: Context { slot }, - value: results, - }) - } + // Second pass: try PDA seed derivation for accounts not found by onchain_pubkey + let mut token_lookup_indices: Vec = Vec::new(); + let mut token_lookup_pubkeys: Vec<[u8; 32]> = Vec::new(); - async fn get_mint_interface( - &self, - address: &Pubkey, - config: Option, - ) -> Result>, RpcError> { - use borsh::BorshDeserialize; - use light_compressed_account::address::derive_address; - use light_token_interface::{state::Mint, MINT_ADDRESS_TREE}; + for (i, pubkey) in pda_seed_lookup_pubkeys.iter().enumerate() { + let original_idx = pda_seed_lookup_indices[i]; + if let Some(compressed_with_ctx) = + indexer.find_compressed_account_by_pda_seed(pubkey) + { + let owner: Pubkey = compressed_with_ctx.compressed_account.owner.into(); + let compressed: CompressedAccount = + compressed_with_ctx.clone().try_into().map_err(|e| { + RpcError::CustomError(format!("conversion error: {:?}", e)) + })?; - let slot = self.context.get_sysvar::().slot; - let address_tree = Pubkey::new_from_array(MINT_ADDRESS_TREE); - let compressed_address = derive_address( - &address.to_bytes(), - &address_tree.to_bytes(), - &light_token_interface::LIGHT_TOKEN_PROGRAM_ID, - ); + results[original_idx] = Some(AccountInterface::cold( + *addresses[original_idx], + compressed, + owner, + )); + } else { + token_lookup_indices.push(original_idx); + token_lookup_pubkeys.push(*pubkey); + } + } - // 1. Try hot (on-chain) first - if let Some(account) = self.context.get_account(address) { - if account.lamports > 0 { - return Ok(Response { - context: Context { slot }, - value: Some(MintInterface { - mint: *address, - address_tree, - compressed_address, - state: MintState::Hot { account }, - }), - }); - } - } + // Third pass: search in token_compressed_accounts + // Track addresses that still need owner-based lookup + let mut owner_lookup_indices: Vec = Vec::new(); + let mut owner_lookup_pubkeys: Vec = Vec::new(); + + for (i, pubkey) in token_lookup_pubkeys.iter().enumerate() { + let original_idx = token_lookup_indices[i]; + let token_acc = indexer + .find_token_account_by_onchain_pubkey(pubkey) + .or_else(|| indexer.find_token_account_by_pda_seed(pubkey)); + + if let Some(token_acc) = token_acc { + let compressed_account: CompressedAccount = token_acc + .compressed_account + .clone() + .try_into() + .map_err(|e| { + RpcError::CustomError(format!("conversion error: {:?}", e)) + })?; - // 2. Fall back to cold (compressed) via indexer - let indexer = self - .indexer - .as_ref() - .ok_or_else(|| RpcError::CustomError("Indexer not initialized".to_string()))?; + let compressed_token = CompressedTokenAccount { + token: token_acc.token_data.clone(), + account: compressed_account, + }; + + // For ATAs, use the wallet owner from the ata_owner_map + let addr = addresses[original_idx]; + let wallet_owner = + indexer.ata_owner_map.get(addr).copied().unwrap_or(*addr); + results[original_idx] = Some(AccountInterface::cold_token( + *addr, + compressed_token, + wallet_owner, + )); + } else { + owner_lookup_indices.push(original_idx); + owner_lookup_pubkeys.push(*addresses[original_idx]); + } + } - let resp = indexer - .get_compressed_account(compressed_address, config) - .await - .map_err(|e| RpcError::CustomError(format!("Indexer error: {e}")))?; - - let value = match resp.value { - Some(compressed) => { - // Parse mint data from compressed account - let mint_data = compressed - .data - .as_ref() - .and_then(|d| { - if d.data.is_empty() { - None - } else { - Mint::try_from_slice(&d.data).ok() - } - }) - .ok_or_else(|| { - RpcError::CustomError( - "Missing or invalid mint data in compressed account".into(), - ) - })?; - - Some(MintInterface { - mint: *address, - address_tree, - compressed_address, - state: MintState::Cold { - compressed, - mint_data, - }, - }) + // Fourth pass: search by token_data.owner (for program-owned tokens) + for (i, pubkey) in owner_lookup_pubkeys.iter().enumerate() { + let original_idx = owner_lookup_indices[i]; + let result = indexer + .get_compressed_token_accounts_by_owner(pubkey, None, None) + .await + .map_err(|e| RpcError::CustomError(format!("indexer error: {}", e)))?; + + let items = result.value.items; + if items.len() == 1 { + let token_acc = items.into_iter().next().unwrap(); + // For ATAs, use the wallet owner from the ata_owner_map + let addr = addresses[original_idx]; + let wallet_owner = + indexer.ata_owner_map.get(addr).copied().unwrap_or(*addr); + results[original_idx] = + Some(AccountInterface::cold_token(*addr, token_acc, wallet_owner)); + } + } } - None => None, - }; + } Ok(Response { context: Context { slot }, - value, + value: results, }) } } diff --git a/sdk-tests/anchor-semi-manual-test/tests/stress_test.rs b/sdk-tests/anchor-semi-manual-test/tests/stress_test.rs index 340726eb6e..483139ad7a 100644 --- a/sdk-tests/anchor-semi-manual-test/tests/stress_test.rs +++ b/sdk-tests/anchor-semi-manual-test/tests/stress_test.rs @@ -18,8 +18,8 @@ use light_batched_merkle_tree::{ initialize_state_tree::InitStateTreeAccountsInstructionData, }; use light_client::interface::{ - create_load_instructions, get_create_accounts_proof, AccountInterface, AccountSpec, - CreateAccountsProofInput, PdaSpec, + create_load_instructions, get_create_accounts_proof, AccountSpec, CreateAccountsProofInput, + PdaSpec, }; use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{ @@ -276,19 +276,19 @@ async fn decompress_all(ctx: &mut StressTestContext, pdas: &TestPdas, cached: &C let zc_spec = PdaSpec::new(zc_interface, zc_variant, ctx.program_id); // ATA + let ata = light_token::instruction::derive_token_ata(&pdas.ata_owner, &pdas.ata_mint).0; let ata_interface = ctx .rpc - .get_associated_token_account_interface(&pdas.ata_owner, &pdas.ata_mint, None) + .get_account_interface(&ata, None) .await .expect("failed to get ATA interface") .value .expect("ATA interface should exist"); assert!(ata_interface.is_cold(), "ATA should be cold"); - // Token PDA: Vault let vault_iface = ctx .rpc - .get_token_account_interface(&pdas.vault, None) + .get_account_interface(&pdas.vault, None) .await .expect("failed to get vault interface") .value @@ -304,42 +304,34 @@ async fn decompress_all(ctx: &mut StressTestContext, pdas: &TestPdas, cached: &C }, token_data: vault_token_data, }); - let vault_compressed = vault_iface - .compressed() - .expect("cold vault must have compressed data"); - let vault_interface = AccountInterface { - key: vault_iface.key, - account: vault_iface.account.clone(), - cold: Some(vault_compressed.account.clone()), - }; - let vault_spec = PdaSpec::new(vault_interface, vault_variant, ctx.program_id); + assert!( + vault_iface.as_compressed_token().is_some(), + "cold vault must have compressed data" + ); + let vault_spec = PdaSpec::new(vault_iface.clone(), vault_variant, ctx.program_id); - // Mint A - let mint_a_iface = ctx + let mint_a_ai = ctx .rpc - .get_mint_interface(&pdas.mint_a, None) + .get_account_interface(&pdas.mint_a, None) .await .expect("failed to get mint A interface") .value .expect("mint A interface should exist"); - assert!(mint_a_iface.is_cold(), "Mint A should be cold"); - let mint_a_ai = AccountInterface::from(mint_a_iface); + assert!(mint_a_ai.is_cold(), "Mint A should be cold"); - // Mint B - let mint_b_iface = ctx + let mint_b_ai = ctx .rpc - .get_mint_interface(&pdas.mint_b, None) + .get_account_interface(&pdas.mint_b, None) .await .expect("failed to get mint B interface") .value .expect("mint B interface should exist"); - assert!(mint_b_iface.is_cold(), "Mint B should be cold"); - let mint_b_ai = AccountInterface::from(mint_b_iface); + assert!(mint_b_ai.is_cold(), "Mint B should be cold"); let specs: Vec> = vec![ AccountSpec::Pda(record_spec), AccountSpec::Pda(zc_spec), - AccountSpec::Ata(Box::new(ata_interface)), + AccountSpec::Ata(ata_interface), AccountSpec::Pda(vault_spec), AccountSpec::Mint(mint_a_ai), AccountSpec::Mint(mint_b_ai), diff --git a/sdk-tests/anchor-semi-manual-test/tests/test_create_all.rs b/sdk-tests/anchor-semi-manual-test/tests/test_create_all.rs index b4c91db9c4..3c66a5b816 100644 --- a/sdk-tests/anchor-semi-manual-test/tests/test_create_all.rs +++ b/sdk-tests/anchor-semi-manual-test/tests/test_create_all.rs @@ -6,8 +6,8 @@ use anchor_semi_manual_test::{ MINT_SIGNER_SEED_B, RECORD_SEED, VAULT_AUTH_SEED, VAULT_SEED, }; use light_client::interface::{ - create_load_instructions, get_create_accounts_proof, AccountInterface, AccountSpec, - CreateAccountsProofInput, PdaSpec, + create_load_instructions, get_create_accounts_proof, AccountSpec, CreateAccountsProofInput, + PdaSpec, }; use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{program_test::TestRpc, Rpc}; @@ -252,37 +252,33 @@ async fn test_create_all_derive() { let zc_spec = PdaSpec::new(zc_interface, zc_variant, program_id); // ATA + let ata = light_token::instruction::derive_token_ata(&ata_owner, &ata_mint).0; let ata_interface = rpc - .get_associated_token_account_interface(&ata_owner, &ata_mint, None) + .get_account_interface(&ata, None) .await .expect("failed to get ATA interface") .value .expect("ATA interface should exist"); assert!(ata_interface.is_cold(), "ATA should be cold"); - // Mint A - let mint_a_iface = rpc - .get_mint_interface(&mint_a_pda, None) + let mint_a_ai = rpc + .get_account_interface(&mint_a_pda, None) .await .expect("failed to get mint A interface") .value .expect("mint A interface should exist"); - assert!(mint_a_iface.is_cold(), "Mint A should be cold"); - let mint_a_ai = AccountInterface::from(mint_a_iface); + assert!(mint_a_ai.is_cold(), "Mint A should be cold"); - // Mint B - let mint_b_iface = rpc - .get_mint_interface(&mint_b_pda, None) + let mint_b_ai = rpc + .get_account_interface(&mint_b_pda, None) .await .expect("failed to get mint B interface") .value .expect("mint B interface should exist"); - assert!(mint_b_iface.is_cold(), "Mint B should be cold"); - let mint_b_ai = AccountInterface::from(mint_b_iface); + assert!(mint_b_ai.is_cold(), "Mint B should be cold"); - // Token PDA: Vault let vault_iface = rpc - .get_token_account_interface(&vault, None) + .get_account_interface(&vault, None) .await .expect("failed to get vault interface") .value @@ -296,20 +292,16 @@ async fn test_create_all_derive() { seeds: VaultSeeds { mint: vault_mint }, token_data: vault_token_data, }); - let vault_compressed = vault_iface - .compressed() - .expect("cold vault must have compressed data"); - let vault_interface = AccountInterface { - key: vault_iface.key, - account: vault_iface.account.clone(), - cold: Some(vault_compressed.account.clone()), - }; - let vault_spec = PdaSpec::new(vault_interface, vault_variant, program_id); + assert!( + vault_iface.as_compressed_token().is_some(), + "cold vault must have compressed data" + ); + let vault_spec = PdaSpec::new(vault_iface.clone(), vault_variant, program_id); let specs: Vec> = vec![ AccountSpec::Pda(record_spec), AccountSpec::Pda(zc_spec), - AccountSpec::Ata(Box::new(ata_interface)), + AccountSpec::Ata(ata_interface), AccountSpec::Pda(vault_spec), AccountSpec::Mint(mint_a_ai), AccountSpec::Mint(mint_b_ai), diff --git a/sdk-tests/anchor-semi-manual-test/tests/test_create_ata.rs b/sdk-tests/anchor-semi-manual-test/tests/test_create_ata.rs index 5953b31e71..b799163785 100644 --- a/sdk-tests/anchor-semi-manual-test/tests/test_create_ata.rs +++ b/sdk-tests/anchor-semi-manual-test/tests/test_create_ata.rs @@ -94,15 +94,14 @@ async fn test_create_ata_derive() { use anchor_semi_manual_test::LightAccountVariant; let ata_interface = rpc - .get_associated_token_account_interface(&ata_owner, &mint, None) + .get_account_interface(&ata, None) .await .expect("failed to get ATA interface") .value .expect("ATA interface should exist"); assert!(ata_interface.is_cold(), "ATA should be cold"); - let specs: Vec> = - vec![AccountSpec::Ata(Box::new(ata_interface))]; + let specs: Vec> = vec![AccountSpec::Ata(ata_interface)]; let ixs = create_load_instructions(&specs, payer.pubkey(), env.config_pda, &rpc) .await diff --git a/sdk-tests/anchor-semi-manual-test/tests/test_create_mint.rs b/sdk-tests/anchor-semi-manual-test/tests/test_create_mint.rs index 41291a984e..625e9a642a 100644 --- a/sdk-tests/anchor-semi-manual-test/tests/test_create_mint.rs +++ b/sdk-tests/anchor-semi-manual-test/tests/test_create_mint.rs @@ -3,8 +3,7 @@ mod shared; use anchor_lang::{InstructionData, ToAccountMetas}; use anchor_semi_manual_test::{CreateMintParams, MINT_SIGNER_SEED_A}; use light_client::interface::{ - create_load_instructions, get_create_accounts_proof, AccountInterface, AccountSpec, - CreateAccountsProofInput, + create_load_instructions, get_create_accounts_proof, AccountSpec, CreateAccountsProofInput, }; use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{program_test::TestRpc, Rpc}; @@ -99,16 +98,14 @@ async fn test_create_mint_derive() { use anchor_semi_manual_test::LightAccountVariant; let mint_interface = rpc - .get_mint_interface(&mint_pda, None) + .get_account_interface(&mint_pda, None) .await .expect("failed to get mint interface") .value .expect("mint interface should exist"); assert!(mint_interface.is_cold(), "Mint should be cold"); - let mint_account_interface = AccountInterface::from(mint_interface); - let specs: Vec> = - vec![AccountSpec::Mint(mint_account_interface)]; + let specs: Vec> = vec![AccountSpec::Mint(mint_interface)]; let ixs = create_load_instructions(&specs, payer.pubkey(), env.config_pda, &rpc) .await diff --git a/sdk-tests/anchor-semi-manual-test/tests/test_create_token_vault.rs b/sdk-tests/anchor-semi-manual-test/tests/test_create_token_vault.rs index b642ca7357..6a030385f7 100644 --- a/sdk-tests/anchor-semi-manual-test/tests/test_create_token_vault.rs +++ b/sdk-tests/anchor-semi-manual-test/tests/test_create_token_vault.rs @@ -5,7 +5,7 @@ use anchor_semi_manual_test::{ CreateTokenVaultParams, LightAccountVariant, VaultSeeds, VAULT_AUTH_SEED, VAULT_SEED, }; use light_client::interface::{ - create_load_instructions, get_create_accounts_proof, AccountInterface, AccountSpec, PdaSpec, + create_load_instructions, get_create_accounts_proof, AccountSpec, PdaSpec, }; use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{program_test::TestRpc, Rpc}; @@ -102,7 +102,7 @@ async fn test_create_token_vault_derive() { // PHASE 3: Decompress vault let vault_iface = rpc - .get_token_account_interface(&vault, None) + .get_account_interface(&vault, None) .await .expect("failed to get vault interface") .value @@ -116,15 +116,11 @@ async fn test_create_token_vault_derive() { seeds: VaultSeeds { mint }, token_data, }); - let vault_compressed = vault_iface - .compressed() - .expect("cold vault must have compressed data"); - let vault_interface = AccountInterface { - key: vault_iface.key, - account: vault_iface.account.clone(), - cold: Some(vault_compressed.account.clone()), - }; - let vault_spec = PdaSpec::new(vault_interface, vault_variant, program_id); + assert!( + vault_iface.as_compressed_token().is_some(), + "cold vault must have compressed data" + ); + let vault_spec = PdaSpec::new(vault_iface.clone(), vault_variant, program_id); let specs: Vec> = vec![AccountSpec::Pda(vault_spec)]; diff --git a/sdk-tests/anchor-semi-manual-test/tests/test_create_two_mints.rs b/sdk-tests/anchor-semi-manual-test/tests/test_create_two_mints.rs index e9489b081a..7c2eca17f1 100644 --- a/sdk-tests/anchor-semi-manual-test/tests/test_create_two_mints.rs +++ b/sdk-tests/anchor-semi-manual-test/tests/test_create_two_mints.rs @@ -3,8 +3,7 @@ mod shared; use anchor_lang::{InstructionData, ToAccountMetas}; use anchor_semi_manual_test::{CreateTwoMintsParams, MINT_SIGNER_SEED_A, MINT_SIGNER_SEED_B}; use light_client::interface::{ - create_load_instructions, get_create_accounts_proof, AccountInterface, AccountSpec, - CreateAccountsProofInput, + create_load_instructions, get_create_accounts_proof, AccountSpec, CreateAccountsProofInput, }; use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{program_test::TestRpc, Rpc}; @@ -123,23 +122,21 @@ async fn test_create_two_mints_derive() { // PHASE 3: Decompress both mints via create_load_instructions use anchor_semi_manual_test::LightAccountVariant; - let mint_a_interface = rpc - .get_mint_interface(&mint_a_pda, None) + let mint_a_ai = rpc + .get_account_interface(&mint_a_pda, None) .await .expect("failed to get mint A interface") .value .expect("mint A interface should exist"); - assert!(mint_a_interface.is_cold(), "Mint A should be cold"); - let mint_a_ai = AccountInterface::from(mint_a_interface); + assert!(mint_a_ai.is_cold(), "Mint A should be cold"); - let mint_b_interface = rpc - .get_mint_interface(&mint_b_pda, None) + let mint_b_ai = rpc + .get_account_interface(&mint_b_pda, None) .await .expect("failed to get mint B interface") .value .expect("mint B interface should exist"); - assert!(mint_b_interface.is_cold(), "Mint B should be cold"); - let mint_b_ai = AccountInterface::from(mint_b_interface); + assert!(mint_b_ai.is_cold(), "Mint B should be cold"); let specs: Vec> = vec![AccountSpec::Mint(mint_a_ai), AccountSpec::Mint(mint_b_ai)]; diff --git a/sdk-tests/csdk-anchor-full-derived-test-sdk/src/lib.rs b/sdk-tests/csdk-anchor-full-derived-test-sdk/src/lib.rs index 81dc07dda2..caf912682a 100644 --- a/sdk-tests/csdk-anchor-full-derived-test-sdk/src/lib.rs +++ b/sdk-tests/csdk-anchor-full-derived-test-sdk/src/lib.rs @@ -1,9 +1,6 @@ //! Client SDK for the AMM test program. //! -//! Implements the `LightProgramInterface` trait to provide a Jupiter-style -//! interface for clients to build decompression instructions. - -use std::collections::HashMap; +//! Implements the `LightProgramInterface` trait to produce load specs for cold accounts. use anchor_lang::AnchorDeserialize; use csdk_anchor_full_derived_test::{ @@ -13,41 +10,12 @@ use csdk_anchor_full_derived_test::{ Token1VaultSeeds, }, }; -use light_client::interface::{ - matches_discriminator, AccountInterface, AccountSpec, AccountToFetch, LightProgramInterface, - PdaSpec, -}; -use light_sdk::LightDiscriminator; +use light_client::interface::{AccountInterface, AccountSpec, LightProgramInterface, PdaSpec}; use solana_pubkey::Pubkey; /// Program ID for the AMM test program. pub const PROGRAM_ID: Pubkey = csdk_anchor_full_derived_test::ID; -/// Map of account pubkeys to program-owned specs. -pub type PdaSpecMap = HashMap, ahash::RandomState>; - -/// Map of account pubkeys to mint interfaces. -pub type MintInterfaceMap = HashMap; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum AccountKind { - Pda, - Token, - Mint, -} - -#[derive(Debug, Clone, Copy)] -pub struct AccountRequirement { - pub pubkey: Option, - pub kind: AccountKind, -} - -impl AccountRequirement { - fn new(pubkey: Option, kind: AccountKind) -> Self { - Self { pubkey, kind } - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum AmmInstruction { Swap, @@ -58,391 +26,167 @@ pub enum AmmInstruction { #[derive(Debug, Clone)] pub enum AmmSdkError { ParseError(String), - UnknownDiscriminator([u8; 8]), - MissingField(&'static str), - PoolStateNotParsed, } impl std::fmt::Display for AmmSdkError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::ParseError(msg) => write!(f, "Parse error: {}", msg), - Self::UnknownDiscriminator(disc) => write!(f, "Unknown discriminator: {:?}", disc), - Self::MissingField(field) => write!(f, "Missing field: {}", field), - Self::PoolStateNotParsed => write!(f, "Pool state must be parsed first"), } } } impl std::error::Error for AmmSdkError {} +/// Flat SDK struct. All fields populated at construction from pool state data. +/// No Options, no HashMaps. Seeds and variants built on the fly in `load_specs`. #[derive(Debug)] pub struct AmmSdk { - pool_state_pubkey: Option, - amm_config: Option, - token_0_mint: Option, - token_1_mint: Option, - token_0_vault: Option, - token_1_vault: Option, - lp_mint: Option, - observation_key: Option, - authority: Option, - lp_mint_signer: Option, - program_owned_specs: PdaSpecMap, - mint_specs: MintInterfaceMap, -} - -impl Default for AmmSdk { - fn default() -> Self { - Self::new() - } + pub pool_state_pubkey: Pubkey, + pub amm_config: Pubkey, + pub token_0_mint: Pubkey, + pub token_1_mint: Pubkey, + pub token_0_vault: Pubkey, + pub token_1_vault: Pubkey, + pub lp_mint: Pubkey, + pub observation_key: Pubkey, + pub authority: Pubkey, + pub lp_mint_signer: Pubkey, } impl AmmSdk { - pub fn new() -> Self { - Self { - pool_state_pubkey: None, - amm_config: None, - token_0_mint: None, - token_1_mint: None, - token_0_vault: None, - token_1_vault: None, - lp_mint: None, - observation_key: None, - authority: None, - lp_mint_signer: None, - program_owned_specs: HashMap::with_hasher(ahash::RandomState::new()), - mint_specs: HashMap::with_hasher(ahash::RandomState::new()), - } - } - - pub fn pool_state_pubkey(&self) -> Option { - self.pool_state_pubkey - } - - pub fn lp_mint(&self) -> Option { - self.lp_mint - } - - pub fn lp_mint_signer(&self) -> Option { - self.lp_mint_signer - } - - fn parse_pool_state(&mut self, account: &AccountInterface) -> Result<(), AmmSdkError> { - let pool = PoolState::deserialize(&mut &account.data()[8..]) + /// Construct from pool state pubkey and its account data. + /// Parses PoolState once, extracts all dependent addresses. + pub fn new(pool_state_pubkey: Pubkey, pool_data: &[u8]) -> Result { + let pool = PoolState::deserialize(&mut &pool_data[8..]) .map_err(|e| AmmSdkError::ParseError(e.to_string()))?; - self.pool_state_pubkey = Some(account.key); - - self.amm_config = Some(pool.amm_config); - self.token_0_mint = Some(pool.token_0_mint); - self.token_1_mint = Some(pool.token_1_mint); - self.token_0_vault = Some(pool.token_0_vault); - self.token_1_vault = Some(pool.token_1_vault); - self.lp_mint = Some(pool.lp_mint); - self.observation_key = Some(pool.observation_key); - let (authority, _) = Pubkey::find_program_address(&[AUTH_SEED.as_bytes()], &PROGRAM_ID); - self.authority = Some(authority); - let (lp_mint_signer, _) = Pubkey::find_program_address( - &[POOL_LP_MINT_SIGNER_SEED, account.key.as_ref()], + &[POOL_LP_MINT_SIGNER_SEED, pool_state_pubkey.as_ref()], &PROGRAM_ID, ); - self.lp_mint_signer = Some(lp_mint_signer); - - let variant = LightAccountVariant::PoolState { - seeds: PoolStateSeeds { - amm_config: self.amm_config.unwrap(), - token_0_mint: self.token_0_mint.unwrap(), - token_1_mint: self.token_1_mint.unwrap(), - }, - data: pool, - }; - - let spec = PdaSpec::new(account.clone(), variant, PROGRAM_ID); - self.program_owned_specs.insert(account.key, spec); - - Ok(()) - } - - fn parse_observation_state(&mut self, account: &AccountInterface) -> Result<(), AmmSdkError> { - let pool_state = self - .pool_state_pubkey - .ok_or(AmmSdkError::PoolStateNotParsed)?; - - let observation = ObservationState::deserialize(&mut &account.data()[8..]) - .map_err(|e| AmmSdkError::ParseError(e.to_string()))?; - - let variant = LightAccountVariant::ObservationState { - seeds: ObservationStateSeeds { pool_state }, - data: observation, - }; - - let spec = PdaSpec::new(account.clone(), variant, PROGRAM_ID); - self.program_owned_specs.insert(account.key, spec); - - Ok(()) - } - fn parse_token_vault( - &mut self, - account: &AccountInterface, - is_vault_0: bool, - ) -> Result<(), AmmSdkError> { - use light_account::{token::TokenDataWithSeeds, Token}; - - let pool_state = self - .pool_state_pubkey - .ok_or(AmmSdkError::PoolStateNotParsed)?; - - let token: Token = Token::deserialize(&mut &account.data()[..]) - .map_err(|e| AmmSdkError::ParseError(e.to_string()))?; - - let variant = if is_vault_0 { - let token_0_mint = self - .token_0_mint - .ok_or(AmmSdkError::MissingField("token_0_mint"))?; - LightAccountVariant::Token0Vault(TokenDataWithSeeds { - seeds: Token0VaultSeeds { - pool_state, - token_0_mint, - }, - token_data: token, - }) - } else { - let token_1_mint = self - .token_1_mint - .ok_or(AmmSdkError::MissingField("token_1_mint"))?; - LightAccountVariant::Token1Vault(TokenDataWithSeeds { - seeds: Token1VaultSeeds { - pool_state, - token_1_mint, - }, - token_data: token, - }) - }; - - // For token vaults, the cold field already contains the CompressedAccount directly - let interface = if account.is_cold() { - let compressed_account = account - .cold - .as_ref() - .ok_or(AmmSdkError::MissingField("cold_context"))? - .clone(); - AccountInterface { - key: account.key, - account: account.account.clone(), // Keep original owner (SPL Token) - cold: Some(compressed_account), - } - } else { - account.clone() - }; - - // Decompression goes to PROGRAM_ID (AMM), not interface.account.owner (SPL/Light Token) - let spec = PdaSpec::new(interface, variant, PROGRAM_ID); - self.program_owned_specs.insert(account.key, spec); - - Ok(()) - } - - fn parse_account(&mut self, account: &AccountInterface) -> Result<(), AmmSdkError> { - if Some(account.key) == self.token_0_vault { - return self.parse_token_vault(account, true); - } - if Some(account.key) == self.token_1_vault { - return self.parse_token_vault(account, false); - } - - if matches_discriminator(account.data(), &PoolState::LIGHT_DISCRIMINATOR) { - return self.parse_pool_state(account); - } - if matches_discriminator(account.data(), &ObservationState::LIGHT_DISCRIMINATOR) { - return self.parse_observation_state(account); - } - - // Check if this is an LP mint by matching the signer - if let Some(lp_mint_signer) = self.lp_mint_signer { - if let Some(mint_signer) = account.mint_signer() { - if Pubkey::new_from_array(mint_signer) == lp_mint_signer { - return self.parse_mint(account); - } - } - } - - Ok(()) - } - - fn parse_mint(&mut self, account: &AccountInterface) -> Result<(), AmmSdkError> { - // Store AccountInterface directly - mints are just accounts with special data - self.mint_specs.insert(account.key, account.clone()); - Ok(()) - } - - pub fn derive_lp_mint_compressed_address(&self, address_tree: &Pubkey) -> Option<[u8; 32]> { - self.lp_mint_signer.map(|signer| { - light_compressed_token_sdk::compressed_token::create_compressed_mint::derive_mint_compressed_address( - &signer, - address_tree, - ) + Ok(Self { + pool_state_pubkey, + amm_config: pool.amm_config, + token_0_mint: pool.token_0_mint, + token_1_mint: pool.token_1_mint, + token_0_vault: pool.token_0_vault, + token_1_vault: pool.token_1_vault, + lp_mint: pool.lp_mint, + observation_key: pool.observation_key, + authority, + lp_mint_signer, }) } - fn account_requirements(&self, ix: &AmmInstruction) -> Vec { - let vault_0_req = AccountRequirement::new(self.token_0_vault, AccountKind::Token); - let vault_1_req = AccountRequirement::new(self.token_1_vault, AccountKind::Token); - - match ix { - AmmInstruction::Swap => { - vec![ - AccountRequirement::new(self.pool_state_pubkey, AccountKind::Pda), - vault_0_req, - vault_1_req, - AccountRequirement::new(self.observation_key, AccountKind::Pda), - ] - } - AmmInstruction::Deposit | AmmInstruction::Withdraw => { - vec![ - AccountRequirement::new(self.pool_state_pubkey, AccountKind::Pda), - vault_0_req, - vault_1_req, - AccountRequirement::new(self.observation_key, AccountKind::Pda), - AccountRequirement::new(self.lp_mint, AccountKind::Mint), - ] - } - } + pub fn derive_lp_mint_compressed_address(&self, address_tree: &Pubkey) -> [u8; 32] { + light_compressed_token_sdk::compressed_token::create_compressed_mint::derive_mint_compressed_address( + &self.lp_mint_signer, + address_tree, + ) } } impl LightProgramInterface for AmmSdk { type Variant = LightAccountVariant; type Instruction = AmmInstruction; - type Error = AmmSdkError; - fn program_id(&self) -> Pubkey { + fn program_id() -> Pubkey { PROGRAM_ID } - fn from_keyed_accounts(accounts: &[AccountInterface]) -> Result { - let mut sdk = Self::new(); - - for account in accounts { - // Parse pool_state first (needed for other accounts), then remaining - if matches_discriminator(account.data(), &PoolState::LIGHT_DISCRIMINATOR) { - sdk.parse_pool_state(account)?; - } else { - sdk.parse_account(account)?; - } - } - - Ok(sdk) - } - - fn get_accounts_to_update(&self, ix: &Self::Instruction) -> Vec { - self.account_requirements(ix) - .into_iter() - .filter_map(|req| match req.kind { - AccountKind::Pda => req - .pubkey - .map(|pubkey| AccountToFetch::pda(pubkey, PROGRAM_ID)), - AccountKind::Token => req.pubkey.map(AccountToFetch::token), - AccountKind::Mint => req.pubkey.map(AccountToFetch::mint), - }) - .collect() - } - - fn update(&mut self, accounts: &[AccountInterface]) -> Result<(), Self::Error> { - for account in accounts { - self.parse_account(account)?; + fn instruction_accounts(&self, ix: &Self::Instruction) -> Vec { + match ix { + AmmInstruction::Swap => vec![ + self.pool_state_pubkey, + self.token_0_vault, + self.token_1_vault, + self.observation_key, + ], + AmmInstruction::Deposit | AmmInstruction::Withdraw => vec![ + self.pool_state_pubkey, + self.token_0_vault, + self.token_1_vault, + self.observation_key, + self.lp_mint, + ], } - Ok(()) } - fn get_all_specs(&self) -> Vec> { - let mut specs = Vec::new(); - specs.extend( - self.program_owned_specs - .values() - .cloned() - .map(AccountSpec::Pda), - ); - specs.extend(self.mint_specs.values().cloned().map(AccountSpec::Mint)); - specs - } + fn load_specs( + &self, + cold_accounts: &[AccountInterface], + ) -> Result>, Box> { + use light_account::{token::TokenDataWithSeeds, Token}; - fn get_specs_for_instruction(&self, ix: &Self::Instruction) -> Vec> { - let requirements = self.account_requirements(ix); let mut specs = Vec::new(); - - for req in &requirements { - match req.kind { - AccountKind::Pda | AccountKind::Token => { - if let Some(pubkey) = req.pubkey { - if let Some(spec) = self.program_owned_specs.get(&pubkey) { - specs.push(AccountSpec::Pda(spec.clone())); - } - } - } - AccountKind::Mint => { - if let Some(mint_pubkey) = req.pubkey { - if let Some(spec) = self.mint_specs.get(&mint_pubkey) { - specs.push(AccountSpec::Mint(spec.clone())); - } - } - } + for account in cold_accounts { + if account.key == self.pool_state_pubkey { + let pool = PoolState::deserialize(&mut &account.data()[8..]) + .map_err(|e| AmmSdkError::ParseError(e.to_string()))?; + let variant = LightAccountVariant::PoolState { + seeds: PoolStateSeeds { + amm_config: self.amm_config, + token_0_mint: self.token_0_mint, + token_1_mint: self.token_1_mint, + }, + data: pool, + }; + specs.push(AccountSpec::Pda(PdaSpec::new( + account.clone(), + variant, + PROGRAM_ID, + ))); + } else if account.key == self.observation_key { + let observation = ObservationState::deserialize(&mut &account.data()[8..]) + .map_err(|e| AmmSdkError::ParseError(e.to_string()))?; + let variant = LightAccountVariant::ObservationState { + seeds: ObservationStateSeeds { + pool_state: self.pool_state_pubkey, + }, + data: observation, + }; + specs.push(AccountSpec::Pda(PdaSpec::new( + account.clone(), + variant, + PROGRAM_ID, + ))); + } else if account.key == self.token_0_vault { + let token: Token = Token::deserialize(&mut &account.data()[..]) + .map_err(|e| AmmSdkError::ParseError(e.to_string()))?; + let variant = LightAccountVariant::Token0Vault(TokenDataWithSeeds { + seeds: Token0VaultSeeds { + pool_state: self.pool_state_pubkey, + token_0_mint: self.token_0_mint, + }, + token_data: token, + }); + specs.push(AccountSpec::Pda(PdaSpec::new( + account.clone(), + variant, + PROGRAM_ID, + ))); + } else if account.key == self.token_1_vault { + let token: Token = Token::deserialize(&mut &account.data()[..]) + .map_err(|e| AmmSdkError::ParseError(e.to_string()))?; + let variant = LightAccountVariant::Token1Vault(TokenDataWithSeeds { + seeds: Token1VaultSeeds { + pool_state: self.pool_state_pubkey, + token_1_mint: self.token_1_mint, + }, + token_data: token, + }); + specs.push(AccountSpec::Pda(PdaSpec::new( + account.clone(), + variant, + PROGRAM_ID, + ))); + } else if account.key == self.lp_mint { + specs.push(AccountSpec::Mint(account.clone())); } } - - specs - } -} - -impl AmmSdk { - pub fn program_id(&self) -> Pubkey { - PROGRAM_ID - } - - pub fn pool_state_seeds(&self) -> Result { - Ok(PoolStateSeeds { - amm_config: self - .amm_config - .ok_or(AmmSdkError::MissingField("amm_config"))?, - token_0_mint: self - .token_0_mint - .ok_or(AmmSdkError::MissingField("token_0_mint"))?, - token_1_mint: self - .token_1_mint - .ok_or(AmmSdkError::MissingField("token_1_mint"))?, - }) - } - - pub fn observation_state_seeds(&self) -> Result { - Ok(ObservationStateSeeds { - pool_state: self - .pool_state_pubkey - .ok_or(AmmSdkError::PoolStateNotParsed)?, - }) - } - - pub fn token_0_vault_seeds(&self) -> Result { - Ok(Token0VaultSeeds { - pool_state: self - .pool_state_pubkey - .ok_or(AmmSdkError::PoolStateNotParsed)?, - token_0_mint: self - .token_0_mint - .ok_or(AmmSdkError::MissingField("token_0_mint"))?, - }) - } - - pub fn token_1_vault_seeds(&self) -> Result { - Ok(Token1VaultSeeds { - pool_state: self - .pool_state_pubkey - .ok_or(AmmSdkError::PoolStateNotParsed)?, - token_1_mint: self - .token_1_mint - .ok_or(AmmSdkError::MissingField("token_1_mint"))?, - }) + Ok(specs) } } diff --git a/sdk-tests/csdk-anchor-full-derived-test-sdk/tests/coverage.md b/sdk-tests/csdk-anchor-full-derived-test-sdk/tests/coverage.md deleted file mode 100644 index 18175cbaa5..0000000000 --- a/sdk-tests/csdk-anchor-full-derived-test-sdk/tests/coverage.md +++ /dev/null @@ -1,623 +0,0 @@ -# LightProgramInterface Trait Test Coverage Plan - -## Overview - -Comprehensive test coverage for the `LightProgramInterface` trait to ensure robust SDK implementations. - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ TEST COVERAGE ARCHITECTURE │ -├─────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ UNIT TESTS │ │ INTEGRATION │ │ PROPERTY │ │ -│ │ (Trait Methods)│ │ (Multi-Op) │ │ (Invariants) │ │ -│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ -│ │ │ │ │ -│ v v v │ -│ ┌────────────────────────────────────────────────────────────┐ │ -│ │ LightProgramInterface Trait │ │ -│ │ ┌──────────────────┐ ┌──────────────────┐ │ │ -│ │ │from_keyed_accounts│ │get_accounts_to_ │ │ │ -│ │ │ │ │update │ │ │ -│ │ └──────────────────┘ └──────────────────┘ │ │ -│ │ ┌──────────────────┐ ┌──────────────────┐ │ │ -│ │ │update │ │get_all_specs │ │ │ -│ │ │ │ │ │ │ │ -│ │ └──────────────────┘ └──────────────────┘ │ │ -│ │ ┌──────────────────┐ │ │ -│ │ │get_specs_for_ │ │ │ -│ │ │operation │ │ │ -│ │ └──────────────────┘ │ │ -│ └────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────────┘ -``` - ---- - -## 1. Core Trait Method Tests - -### 1.1 `from_keyed_accounts()` Tests - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ from_keyed_accounts() Test Matrix │ -├─────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ INPUT │ EXPECTED │ TEST NAME │ -│ ───────────────────────────────────────────────────────────────────────── │ -│ Empty accounts [] │ Err or empty SDK │ empty_accounts │ -│ Single root (PoolState) │ SDK with extracted pubkeys│ single_root │ -│ Multiple roots │ SDK with merged state │ multiple_roots │ -│ Wrong discriminator │ Skip or error │ wrong_disc │ -│ Truncated data │ ParseError │ truncated_data │ -│ Hot root account │ SDK (no cold_context) │ hot_root │ -│ Cold root account │ SDK with cold_context │ cold_root │ -│ Missing required fields │ ParseError │ missing_fields │ -│ │ -└─────────────────────────────────────────────────────────────────────────────┘ -``` - -| Test ID | Test Name | Description | Priority | -|---------|-----------|-------------|----------| -| T1.1.1 | `test_from_keyed_empty_accounts` | Empty array returns error/empty SDK | HIGH | -| T1.1.2 | `test_from_keyed_single_root` | Single PoolState parses all pubkeys | HIGH | -| T1.1.3 | `test_from_keyed_cold_root` | Cold root sets up cold_context correctly | HIGH | -| T1.1.4 | `test_from_keyed_hot_root` | Hot root works without cold_context | HIGH | -| T1.1.5 | `test_from_keyed_wrong_discriminator` | Unknown discriminator handled gracefully | MEDIUM | -| T1.1.6 | `test_from_keyed_truncated_data` | Insufficient data returns ParseError | HIGH | -| T1.1.7 | `test_from_keyed_zero_length_data` | Zero-length data handled | MEDIUM | -| T1.1.8 | `test_from_keyed_multiple_roots` | Multiple root accounts merged correctly | MEDIUM | - -### 1.2 `get_accounts_to_update()` Tests - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ Operation -> Accounts Mapping Test Matrix │ -├─────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ OPERATION │ EXPECTED ACCOUNTS │ OVERLAP WITH OTHERS │ -│ ──────────────────────────────────────────────────────────────────────────│ -│ Swap │ [vault_0, vault_1] │ Subset of Deposit │ -│ Deposit │ [vault_0, vault_1, obs, │ Superset of Swap │ -│ │ lp_mint] │ │ -│ Withdraw │ [vault_0, vault_1, obs, │ Same as Deposit │ -│ │ lp_mint] │ │ -│ │ -│ EDGE CASES: │ -│ - Before pool_state parsed → returns [] │ -│ - Pool has no vaults → returns [] for Swap │ -│ - Pool has no LP mint → Deposit excludes it │ -│ │ -└─────────────────────────────────────────────────────────────────────────────┘ -``` - -| Test ID | Test Name | Description | Priority | -|---------|-----------|-------------|----------| -| T1.2.1 | `test_get_accounts_swap` | Swap returns correct vaults | HIGH | -| T1.2.2 | `test_get_accounts_deposit` | Deposit returns vaults+obs+mint | HIGH | -| T1.2.3 | `test_get_accounts_withdraw` | Withdraw matches Deposit | HIGH | -| T1.2.4 | `test_get_accounts_before_init` | Returns empty before pool parsed | HIGH | -| T1.2.5 | `test_get_accounts_overlap` | Verify overlapping accounts deduplicated | MEDIUM | -| T1.2.6 | `test_get_accounts_partial_state` | Missing some optional fields | MEDIUM | - -### 1.3 `update()` Tests - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ update() State Transitions │ -├─────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ INITIAL STATE INPUT FINAL STATE │ -│ ──────────────────────────────────────────────────────────────────────────│ -│ [PoolState parsed] + [vault_0] → specs: {pool, vault_0} │ -│ specs: {pool} │ -│ │ -│ [PoolState parsed] + [vault_0, → specs: {pool, vault_0, │ -│ specs: {pool} vault_1] vault_1} │ -│ │ -│ specs: {pool, v0} + [vault_0] → specs: {pool, v0} (updated) │ -│ (already has v0) (re-update) IDEMPOTENT │ -│ │ -│ specs: {} + [vault_0] → ERROR (pool not parsed) │ -│ (no pool yet) │ -│ │ -│ specs: {pool} + [unknown] → specs: {pool} (skipped) │ -│ (unrecognized) │ -│ │ -└─────────────────────────────────────────────────────────────────────────────┘ -``` - -| Test ID | Test Name | Description | Priority | -|---------|-----------|-------------|----------| -| T1.3.1 | `test_update_single_account` | Single vault updates correctly | HIGH | -| T1.3.2 | `test_update_multiple_accounts` | Multiple accounts batch | HIGH | -| T1.3.3 | `test_update_idempotent` | Same account twice is idempotent | HIGH | -| T1.3.4 | `test_update_before_root` | Error if updating before root parsed | HIGH | -| T1.3.5 | `test_update_unknown_account` | Unknown accounts skipped | MEDIUM | -| T1.3.6 | `test_update_mixed_hot_cold` | Mix of hot and cold accounts | HIGH | -| T1.3.7 | `test_update_overwrites_old` | Re-updating changes is_cold status | HIGH | -| T1.3.8 | `test_update_token_context` | Token accounts use token_context | HIGH | -| T1.3.9 | `test_update_pda_context` | PDA accounts use pda_context | HIGH | - -### 1.4 `get_all_specs()` Tests - -| Test ID | Test Name | Description | Priority | -|---------|-----------|-------------|----------| -| T1.4.1 | `test_get_all_empty` | Empty SDK returns empty specs | HIGH | -| T1.4.2 | `test_get_all_complete` | All parsed accounts returned | HIGH | -| T1.4.3 | `test_get_all_preserves_cold` | Cold status preserved in specs | HIGH | -| T1.4.4 | `test_get_all_categories` | Correct categorization (pda/ata/mint) | HIGH | - -### 1.5 `get_specs_for_operation()` Tests - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ Operation-Filtered Specs Visual │ -├─────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ALL SPECS: │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ pool_state │ vault_0 │ vault_1 │ observation │ lp_mint │ │ -│ └──────────────────────────────────────────────────────────┘ │ -│ │ -│ SWAP FILTER: │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ pool_state │ vault_0 │ vault_1 │░░░░░░░░░░░░│░░░░░░░░░│ │ -│ └──────────────────────────────────────────────────────────┘ │ -│ ↑ INCLUDED ↑ EXCLUDED │ -│ │ -│ DEPOSIT FILTER: │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ pool_state │ vault_0 │ vault_1 │ observation │ lp_mint │ │ -│ └──────────────────────────────────────────────────────────┘ │ -│ ↑ ALL INCLUDED │ -│ │ -└─────────────────────────────────────────────────────────────────────────────┘ -``` - -| Test ID | Test Name | Description | Priority | -|---------|-----------|-------------|----------| -| T1.5.1 | `test_specs_for_swap` | Swap returns vaults only | HIGH | -| T1.5.2 | `test_specs_for_deposit` | Deposit includes all | HIGH | -| T1.5.3 | `test_specs_for_operation_cold_filter` | Only cold accounts have context | HIGH | -| T1.5.4 | `test_specs_for_operation_missing_accounts` | Missing accounts not in specs | MEDIUM | - ---- - -## 2. Error Handling Tests - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ ERROR SCENARIOS MATRIX │ -├─────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ERROR TYPE │ SCENARIO │ EXPECTED MESSAGE │ -│ ──────────────────────────────────────────────────────────────────────────│ -│ ParseError │ Invalid account data │ "Parse error: ..." │ -│ UnknownDiscriminator │ Unrecognized disc │ "Unknown disc: [..]"│ -│ MissingField │ Required field null │ "Missing: field_x" │ -│ PoolStateNotParsed │ Update before init │ "Pool state must..."│ -│ MissingContext │ Cold without context │ "Missing context" │ -│ │ -│ RECOVERY SCENARIOS: │ -│ - Partial parse failure → previously parsed state preserved │ -│ - Unknown account → skip silently, continue │ -│ - Hot account missing context → OK (no context needed) │ -│ │ -└─────────────────────────────────────────────────────────────────────────────┘ -``` - -| Test ID | Test Name | Description | Priority | -|---------|-----------|-------------|----------| -| T2.1 | `test_error_parse_invalid_data` | ParseError on invalid data | HIGH | -| T2.2 | `test_error_missing_field` | MissingField with field name | HIGH | -| T2.3 | `test_error_pool_not_parsed` | PoolStateNotParsed meaningful msg | HIGH | -| T2.4 | `test_error_display_impl` | All errors have Display impl | HIGH | -| T2.5 | `test_error_recovery_partial` | Partial failure preserves state | MEDIUM | -| T2.6 | `test_error_cold_without_context` | Cold account without context errors | HIGH | - ---- - -## 3. Multi-Operation Scenarios (Overlapping/Divergent Accounts) - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ MULTI-OPERATION ACCOUNT OVERLAP SCENARIOS │ -├─────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ SCENARIO: Sequential Operations with Shared Accounts │ -│ │ -│ Timeline: │ -│ ─────────────────────────────────────────────────────────────────────────│ -│ T0: Initialize SDK with PoolState │ -│ └── specs: {pool_state} │ -│ │ -│ T1: get_accounts_to_update(Swap) → [vault_0, vault_1] │ -│ └── Fetch and update vaults │ -│ └── specs: {pool_state, vault_0, vault_1} │ -│ │ -│ T2: get_specs_for_operation(Swap) → {pool, v0, v1} │ -│ └── Execute Swap with these specs │ -│ │ -│ T3: get_accounts_to_update(Deposit) → [vault_0, vault_1, obs, lp_mint] │ -│ └── Already have vaults! Only need obs + lp_mint │ -│ └── Fetch obs + lp_mint, update │ -│ └── specs: {pool_state, vault_0, vault_1, obs, lp_mint} │ -│ │ -│ T4: get_specs_for_operation(Deposit) → {pool, v0, v1, obs, lp_mint} │ -│ │ -│ KEY INVARIANT: Shared accounts (vaults) use SAME spec instance │ -│ │ -└─────────────────────────────────────────────────────────────────────────────┘ -``` - -| Test ID | Test Name | Description | Priority | -|---------|-----------|-------------|----------| -| T3.1 | `test_multi_op_swap_then_deposit` | Specs preserved across ops | HIGH | -| T3.2 | `test_multi_op_shared_accounts` | Shared accounts not duplicated | HIGH | -| T3.3 | `test_multi_op_incremental_fetch` | Can skip already-fetched accounts | HIGH | -| T3.4 | `test_multi_op_state_refresh` | Re-fetching updates cold→hot | HIGH | -| T3.5 | `test_multi_op_interleaved` | Alternating ops work correctly | MEDIUM | - ---- - -## 4. Account Naming / Aliasing Tests - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ ACCOUNT NAMING EDGE CASES │ -├─────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ PROBLEM: Same account address, different instruction names │ -│ │ -│ Example: │ -│ ┌─────────────────────────────────────────────────────────────────────┐ │ -│ │ Instruction: initialize │ │ -│ │ accounts: │ │ -│ │ - token_vault_0: CYLaS4pMLTb1gTrxf9YnMNkF6ta7vMopKgST5kDAWdU2 │ │ -│ │ - pool_state: 8qitTUf7KWgEwgsLnSfrt52GfTAcUmFRci4h5RdnJh5m │ │ -│ └─────────────────────────────────────────────────────────────────────┘ │ -│ ┌─────────────────────────────────────────────────────────────────────┐ │ -│ │ Instruction: swap │ │ -│ │ accounts: │ │ -│ │ - source_vault: CYLaS4pMLTb1gTrxf9YnMNkF6ta7vMopKgST5kDAWdU2 │ <── SAME! -│ │ - amm_pool: 8qitTUf7KWgEwgsLnSfrt52GfTAcUmFRci4h5RdnJh5m │ <── SAME! -│ └─────────────────────────────────────────────────────────────────────┘ │ -│ │ -│ SOLUTION: SDK keyed by PUBKEY, not name │ -│ - HashMap ensures same address = same spec │ -│ - Variant enum contains canonical data, not instruction-specific names │ -│ │ -└─────────────────────────────────────────────────────────────────────────────┘ -``` - -| Test ID | Test Name | Description | Priority | -|---------|-----------|-------------|----------| -| T4.1 | `test_same_address_different_name` | Same pubkey = same spec | HIGH | -| T4.2 | `test_spec_keyed_by_pubkey` | HashMap uses pubkey not name | HIGH | -| T4.3 | `test_variant_canonical_data` | Variant has canonical seeds | HIGH | -| T4.4 | `test_instruction_agnostic` | Works regardless of ix context | MEDIUM | - ---- - -## 5. Exhaustive Coverage Requirements - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ EXHAUSTIVE IMPLEMENTATION REQUIREMENTS │ -├─────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ A valid LightProgramInterface implementation MUST: │ -│ │ -│ 1. VARIANT COMPLETENESS │ -│ □ LightAccountVariant covers ALL #[light_account] accounts │ -│ □ TokenAccountVariant covers ALL #[rentfree_token] accounts │ -│ □ No rentfree account left unrepresented │ -│ │ -│ 2. OPERATION COMPLETENESS │ -│ □ Operation enum covers all instruction types │ -│ □ Each operation returns correct account set │ -│ □ get_specs_for_operation returns superset of get_accounts_to_update │ -│ │ -│ 3. SEED VALUE COMPLETENESS │ -│ □ All seed fields populated from parsed state │ -│ □ Variant constructor includes all seed values │ -│ □ Seeds match what macros expect for address derivation │ -│ │ -│ 4. CONTEXT COMPLETENESS │ -│ □ Cold accounts have appropriate context (Pda/Token/Mint) │ -│ □ Hot accounts have no context (or empty) │ -│ □ Context types match account types │ -│ │ -│ VALIDATION CHECKS TO IMPLEMENT: │ -│ │ -│ ┌───────────────────────────────────────────────────────────────────┐ │ -│ │ fn validate_implementation() { │ │ -│ │ // 1. Create SDK from known root │ │ -│ │ // 2. For each Operation: │ │ -│ │ // - get_accounts_to_update returns non-empty │ │ -│ │ // - After update, get_specs_for_operation non-empty │ │ -│ │ // - All specs have valid variants │ │ -│ │ // 3. get_all_specs covers all accounts from all ops │ │ -│ │ } │ │ -│ └───────────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────────┘ -``` - -| Test ID | Test Name | Description | Priority | -|---------|-----------|-------------|----------| -| T5.1 | `test_variant_covers_all_rentfree` | No rentfree account missing from variant | HIGH | -| T5.2 | `test_operation_covers_all_instructions` | All ix types have operation | HIGH | -| T5.3 | `test_seeds_complete` | All seed values populated | HIGH | -| T5.4 | `test_context_type_matches` | PDA→PdaContext, Token→TokenContext | HIGH | -| T5.5 | `test_all_specs_superset` | get_all_specs ⊇ union of all get_specs_for_op | HIGH | -| T5.6 | `test_no_orphan_accounts` | Every program account reachable via some op | MEDIUM | - ---- - -## 6. Property-Based / Invariant Tests - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ SDK INVARIANTS │ -├─────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ INVARIANT 1: Idempotency │ -│ ─────────────────────────────────────────────────────────────────────────│ -│ ∀ accounts a: update(a); update(a) ≡ update(a) │ -│ (updating with same data twice has same effect as once) │ -│ │ -│ INVARIANT 2: Commutativity │ -│ ─────────────────────────────────────────────────────────────────────────│ -│ update([a, b]) ≡ update([a]); update([b]) ≡ update([b]); update([a]) │ -│ (order of updates doesn't matter for final state) │ -│ │ -│ INVARIANT 3: Spec Consistency │ -│ ─────────────────────────────────────────────────────────────────────────│ -│ ∀ op: get_accounts_to_update(op) ⊆ keys(get_specs_for_operation(op)) │ -│ (all accounts to update should appear in specs after update) │ -│ │ -│ INVARIANT 4: Address Uniqueness │ -│ ─────────────────────────────────────────────────────────────────────────│ -│ ∀ specs: |specs.addresses| = |unique(specs.addresses)| │ -│ (no duplicate addresses in specs) │ -│ │ -│ INVARIANT 5: Cold Context Presence │ -│ ─────────────────────────────────────────────────────────────────────────│ -│ ∀ spec: spec.is_cold ⟹ spec.cold_context.is_some() │ -│ (cold specs must have context) │ -│ │ -└─────────────────────────────────────────────────────────────────────────────┘ -``` - -| Test ID | Test Name | Description | Priority | -|---------|-----------|-------------|----------| -| T6.1 | `test_invariant_idempotent` | update(a);update(a) = update(a) | HIGH | -| T6.2 | `test_invariant_commutative` | Order doesn't matter | HIGH | -| T6.3 | `test_invariant_spec_consistency` | Accounts in specs after update | HIGH | -| T6.4 | `test_invariant_no_duplicates` | No duplicate addresses | HIGH | -| T6.5 | `test_invariant_cold_has_context` | Cold specs have context | HIGH | -| T6.6 | `test_invariant_hot_no_context_needed` | Hot specs work without context | MEDIUM | - ---- - -## 7. State Transition Tests - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ STATE TRANSITION DIAGRAM │ -├─────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌──────────────┐ │ -│ │ EMPTY │ │ -│ │ SDK │ │ -│ └──────┬───────┘ │ -│ │ │ -│ │ from_keyed_accounts([pool]) │ -│ │ (parses root) │ -│ v │ -│ ┌──────────────┐ │ -│ │ ROOT PARSED │ │ -│ │ (pool only) │ │ -│ └──────┬───────┘ │ -│ │ │ -│ ┌──────────────────┼──────────────────┐ │ -│ │ │ │ │ -│ │ update([vaults]) │ update([obs]) │ update([mint]) │ -│ v v v │ -│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ -│ │ SWAP READY │ │ PARTIAL │ │ MINT READY │ │ -│ │ (vaults) │ │ (vaults+obs) │ │ (mint) │ │ -│ └──────────────┘ └──────────────┘ └──────────────┘ │ -│ │ │ │ │ -│ └──────────────────┼──────────────────┘ │ -│ │ update([remaining]) │ -│ v │ -│ ┌──────────────┐ │ -│ │ COMPLETE │ │ -│ │ (all specs) │ │ -│ └──────────────┘ │ -│ │ -│ TRANSITIONS: │ -│ - Any state → COMPLETE (by updating remaining accounts) │ -│ - Hot → Cold (account compressed externally, re-fetch) │ -│ - Cold → Hot (account decompressed, re-fetch) │ -│ │ -└─────────────────────────────────────────────────────────────────────────────┘ -``` - -| Test ID | Test Name | Description | Priority | -|---------|-----------|-------------|----------| -| T7.1 | `test_state_empty_to_root` | Empty → Root parsed | HIGH | -| T7.2 | `test_state_root_to_swap_ready` | Root → Swap ready (vaults) | HIGH | -| T7.3 | `test_state_incremental_to_complete` | Incremental updates to complete | HIGH | -| T7.4 | `test_state_hot_to_cold_refetch` | Re-fetch changes hot→cold | HIGH | -| T7.5 | `test_state_cold_to_hot_refetch` | Re-fetch changes cold→hot | HIGH | - ---- - -## 8. Edge Case Tests - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ EDGE CASES MATRIX │ -├─────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ SCENARIO │ EXPECTED BEHAVIOR │ TEST │ -│ ──────────────────────────────────────────────────────────────────────────│ -│ Pool with zero vaults │ Swap returns empty │ zero_vaults │ -│ Pool without LP mint │ Deposit excludes mint │ no_lp_mint │ -│ All accounts hot │ all_hot() = true │ all_hot │ -│ All accounts cold │ has_cold() = true │ all_cold │ -│ Mixed hot/cold │ correct filtering │ mixed_state │ -│ Very large state data │ Handles without OOM │ large_data │ -│ Concurrent updates │ No race conditions │ concurrent │ -│ Null pubkeys in state │ Graceful handling │ null_pubkeys │ -│ Duplicate accounts in update │ Deduplicated │ duplicate_accts │ -│ │ -└─────────────────────────────────────────────────────────────────────────────┘ -``` - -| Test ID | Test Name | Description | Priority | -|---------|-----------|-------------|----------| -| T8.1 | `test_edge_zero_vaults` | Pool with no vaults | MEDIUM | -| T8.2 | `test_edge_no_lp_mint` | Pool without LP mint | MEDIUM | -| T8.3 | `test_edge_all_hot` | all_hot() works correctly | HIGH | -| T8.4 | `test_edge_all_cold` | has_cold() works correctly | HIGH | -| T8.5 | `test_edge_mixed_hot_cold` | Mixed state handled | HIGH | -| T8.6 | `test_edge_duplicate_accounts` | Duplicates deduplicated | MEDIUM | - ---- - -## 9. Same Type Different Instance Tests (CRITICAL) - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ SAME TYPE, DIFFERENT INSTANCE - SPEC SEPARATION │ -├─────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ SCENARIO: vault_0 and vault_1 are BOTH TokenVault type │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────────┐ │ -│ │ VAULT_0 VAULT_1 │ │ -│ │ ───────────────────────────────────────────────────────────────── │ │ -│ │ pubkey: 0xAAAA... pubkey: 0xBBBB... │ │ -│ │ type: Token0Vault type: Token1Vault │ │ -│ │ seeds: [pool, mint_0] seeds: [pool, mint_1] │ │ -│ │ │ │ -│ │ ↓ DIFFERENT PUBKEYS = DIFFERENT SPECS ↓ │ │ -│ │ │ │ -│ │ ┌───────────────────────────────────────────────────────────┐ │ │ -│ │ │ HashMap │ │ │ -│ │ │ ──────────────────────────────────────────────────────── │ │ │ -│ │ │ 0xAAAA... → Spec { variant: Token0Vault, ... } │ │ │ -│ │ │ 0xBBBB... → Spec { variant: Token1Vault, ... } │ │ │ -│ │ └───────────────────────────────────────────────────────────┘ │ │ -│ └─────────────────────────────────────────────────────────────────────┘ │ -│ │ -│ KEY INVARIANTS: │ -│ 1. Pubkey is globally unique → HashMap key guarantees no mingling │ -│ 2. Variant enum encodes WHICH account via type + seed values │ -│ 3. Field name (vault_0, vault_1) unique across ALL instructions │ -│ 4. Updating vault_0 does NOT affect vault_1 │ -│ 5. get_specs_for_operation returns ALL required instances │ -│ │ -│ CROSS-INSTRUCTION NAMING: │ -│ ┌─────────────────────────────────────────────────────────────────────┐ │ -│ │ initialize.token_0_vault ──────┐ │ │ -│ │ ├──→ SAME pubkey = SAME spec │ │ -│ │ swap.input_vault ──────────────┘ │ │ -│ │ │ │ -│ │ SDK keys by PUBKEY, not field name, so same account │ │ -│ │ referenced by different names = single spec entry │ │ -│ └─────────────────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────────┘ -``` - -| Test ID | Test Name | Description | Priority | -|---------|-----------|-------------|----------| -| T9.1 | `test_same_type_different_pubkey_separate_specs` | Two vaults with different pubkeys = two specs | CRITICAL | -| T9.2 | `test_variant_seed_values_distinguish_instances` | Variants contain different seed values | CRITICAL | -| T9.3 | `test_specs_contain_all_vaults_not_merged` | Specs returns BOTH vaults, not merged | CRITICAL | -| T9.4 | `test_field_name_uniqueness_across_instructions` | Same pubkey from different names = single spec | CRITICAL | -| T9.5 | `test_updating_vault_0_does_not_affect_vault_1` | Update isolation between vaults | CRITICAL | -| T9.6 | `test_operation_returns_all_required_instances` | Operation returns ALL needed instances | CRITICAL | -| T9.7 | `test_hashmap_keying_prevents_spec_mingling` | HashMap prevents mingling | CRITICAL | - ---- - -## Test Implementation Summary - -### Total Tests by Category - -| Category | Count | Priority HIGH | Priority CRITICAL | -|----------|-------|---------------|-------------------| -| 1. Core Methods | 22 | 18 | 0 | -| 2. Error Handling | 6 | 5 | 0 | -| 3. Multi-Operation | 5 | 4 | 0 | -| 4. Account Naming | 4 | 3 | 0 | -| 5. Exhaustive Coverage | 6 | 5 | 0 | -| 6. Invariants | 6 | 5 | 0 | -| 7. State Transitions | 5 | 5 | 0 | -| 8. Edge Cases | 6 | 3 | 0 | -| 9. Same Type Different Instance | 7 | 0 | **7** | -| **TOTAL** | **67** | **48** | **7** | - -### Currently Implemented Tests: **31 PASSING** - -``` -test test_all_specs_helpers ... ok -test test_edge_all_hot_check ... ok -test test_error_missing_field_names_field ... ok -test test_error_display_impl ... ok -test test_edge_duplicate_accounts_in_update ... ok -test test_error_parse_error_contains_cause ... ok -test test_field_name_uniqueness_across_instructions ... ok [T9.4] -test test_from_keyed_empty_accounts ... ok -test test_from_keyed_truncated_data ... ok -test test_from_keyed_wrong_discriminator ... ok -test test_from_keyed_zero_length_data ... ok -test test_get_accounts_before_init ... ok -test test_get_accounts_swap_vs_deposit ... ok -test test_get_accounts_to_update_typed_categories ... ok -test test_get_accounts_to_update_typed_empty ... ok -test test_get_all_empty ... ok -test test_hashmap_keying_prevents_spec_mingling ... ok [T9.7] -test test_invariant_cold_has_context ... ok -test test_invariant_hot_context_optional ... ok -test test_invariant_no_duplicate_addresses ... ok -test test_multi_op_deposit_superset_of_swap ... ok -test test_multi_op_withdraw_equals_deposit ... ok -test test_operation_returns_all_required_instances ... ok [T9.6] -test test_same_pubkey_same_spec ... ok -test test_same_type_different_pubkey_separate_specs ... ok [T9.1] -test test_specs_contain_all_vaults_not_merged ... ok [T9.3] -test test_update_idempotent ... ok -test test_update_before_root_errors ... ok -test test_update_unknown_account_skipped ... ok -test test_updating_vault_0_does_not_affect_vault_1 ... ok [T9.5] -test test_variant_seed_values_distinguish_instances ... ok [T9.2] -``` - -### Implementation Priority Order - -1. **Phase 0 (CRITICAL)**: T9.* (Same Type Different Instance - ALL IMPLEMENTED) -2. **Phase 1 (HIGH)**: T1.1.*, T1.3.*, T2.*, T6.* (Core + Error + Invariants) -3. **Phase 2 (IMPORTANT)**: T1.2.*, T1.4.*, T1.5.*, T3.*, T5.* (Ops + Multi-op) -4. **Phase 3 (ROBUSTNESS)**: T4.*, T7.*, T8.* (Naming + State + Edge) - ---- - -## File Structure - -``` -sdk-tests/csdk-anchor-full-derived-test-sdk/ -├── src/ -│ └── lib.rs # AmmSdk implementation -└── tests/ - └── trait_tests.rs # All trait unit tests (31 tests) -``` diff --git a/sdk-tests/csdk-anchor-full-derived-test-sdk/tests/trait_tests.rs b/sdk-tests/csdk-anchor-full-derived-test-sdk/tests/trait_tests.rs index 4dcb0a8267..637c48e11a 100644 --- a/sdk-tests/csdk-anchor-full-derived-test-sdk/tests/trait_tests.rs +++ b/sdk-tests/csdk-anchor-full-derived-test-sdk/tests/trait_tests.rs @@ -1,429 +1,189 @@ //! LightProgramInterface trait unit tests for AmmSdk. //! //! Tests cover: -//! - Core trait methods (from_keyed_accounts, update, get_specs_for_instruction) -//! - Error handling and meaningful error messages -//! - Multi-operation scenarios with overlapping/divergent accounts -//! - Invariants (idempotency, commutativity, spec consistency) -//! - Edge cases (hot/cold mixed, missing accounts, etc.) +//! - instruction_accounts returns correct pubkeys per instruction type +//! - load_specs builds correct variants from cold accounts +//! - Helper functions (all_hot, any_cold) +//! - Invariants (no duplicate addresses, variant seed distinction) use std::collections::HashSet; -use csdk_anchor_full_derived_test::{ - amm_test::{ObservationState, PoolState}, - csdk_anchor_full_derived_test::{LightAccountVariant, ObservationStateSeeds}, +use csdk_anchor_full_derived_test::csdk_anchor_full_derived_test::{ + LightAccountVariant, ObservationStateSeeds, }; -use csdk_anchor_full_derived_test_sdk::{AmmInstruction, AmmSdk, AmmSdkError}; +use csdk_anchor_full_derived_test_sdk::{AmmInstruction, AmmSdk, AmmSdkError, PROGRAM_ID}; use light_client::interface::{ all_hot, any_cold, Account, AccountInterface, AccountSpec, LightProgramInterface, PdaSpec, }; -use light_sdk::LightDiscriminator; use solana_pubkey::Pubkey; // ============================================================================= // TEST HELPERS // ============================================================================= -/// Create a hot AccountInterface from data. -fn keyed_hot(pubkey: Pubkey, data: Vec) -> AccountInterface { - AccountInterface::hot( - pubkey, - Account { - lamports: 0, - data, - owner: csdk_anchor_full_derived_test_sdk::PROGRAM_ID, - executable: false, - rent_epoch: 0, - }, - ) +/// Build an AmmSdk with known pubkeys for unit testing (no deserialization). +fn test_sdk() -> AmmSdk { + AmmSdk { + pool_state_pubkey: Pubkey::new_unique(), + amm_config: Pubkey::new_unique(), + token_0_mint: Pubkey::new_unique(), + token_1_mint: Pubkey::new_unique(), + token_0_vault: Pubkey::new_unique(), + token_1_vault: Pubkey::new_unique(), + lp_mint: Pubkey::new_unique(), + observation_key: Pubkey::new_unique(), + authority: Pubkey::new_unique(), + lp_mint_signer: Pubkey::new_unique(), + } } // ============================================================================= -// 1. CORE TRAIT METHOD TESTS: from_keyed_accounts +// 1. PROGRAM_ID // ============================================================================= #[test] -fn test_from_keyed_empty_accounts() { - // T1.1.1: Empty array should create empty SDK (no error, just no state) - let result = AmmSdk::from_keyed_accounts(&[]); - assert!(result.is_ok(), "Empty accounts should not error"); - - let sdk = result.unwrap(); - assert!( - sdk.pool_state_pubkey().is_none(), - "No pool state parsed from empty" - ); -} - -#[test] -fn test_from_keyed_wrong_discriminator() { - // T1.1.5: Unknown discriminator should be skipped - let mut data = vec![0u8; 100]; - data[..8].copy_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]); - - let keyed = keyed_hot(Pubkey::new_unique(), data); - let result = AmmSdk::from_keyed_accounts(&[keyed]); - - assert!(result.is_ok(), "Unknown discriminator should not error"); - let sdk = result.unwrap(); - assert!( - sdk.pool_state_pubkey().is_none(), - "Unknown disc should be skipped" - ); -} - -#[test] -fn test_from_keyed_truncated_data() { - // T1.1.6: Truncated data should error on parse - let mut data = Vec::new(); - data.extend_from_slice(&PoolState::LIGHT_DISCRIMINATOR); - data.extend_from_slice(&[0u8; 10]); // Way too short - - let keyed = keyed_hot(Pubkey::new_unique(), data); - let result = AmmSdk::from_keyed_accounts(&[keyed]); - - // Should either skip or error depending on implementation - // Current impl: errors on parse - assert!( - result.is_err() || result.as_ref().unwrap().pool_state_pubkey().is_none(), - "Truncated data should error or skip" - ); -} - -#[test] -fn test_from_keyed_zero_length_data() { - // T1.1.7: Zero-length data should be skipped - let keyed = keyed_hot(Pubkey::new_unique(), vec![]); - let result = AmmSdk::from_keyed_accounts(&[keyed]); - - assert!(result.is_ok(), "Zero-length should not error"); - let sdk = result.unwrap(); - assert!( - sdk.pool_state_pubkey().is_none(), - "Zero-length should be skipped" - ); +fn test_program_id() { + assert_eq!(AmmSdk::program_id(), PROGRAM_ID); } // ============================================================================= -// 2. CORE TRAIT METHOD TESTS: get_accounts_to_update +// 2. INSTRUCTION_ACCOUNTS // ============================================================================= #[test] -fn test_get_accounts_before_init() { - // T1.2.4: Returns empty before pool parsed - let sdk = AmmSdk::new(); - - let swap_accounts = sdk.get_accounts_to_update(&AmmInstruction::Swap); - let deposit_accounts = sdk.get_accounts_to_update(&AmmInstruction::Deposit); +fn test_swap_instruction_accounts() { + let sdk = test_sdk(); + let accounts = sdk.instruction_accounts(&AmmInstruction::Swap); - assert!( - swap_accounts.is_empty(), - "Swap should return empty before init" - ); - assert!( - deposit_accounts.is_empty(), - "Deposit should return empty before init" + assert_eq!( + accounts.len(), + 4, + "Swap: pool_state, vault_0, vault_1, observation" ); + assert!(accounts.contains(&sdk.pool_state_pubkey)); + assert!(accounts.contains(&sdk.token_0_vault)); + assert!(accounts.contains(&sdk.token_1_vault)); + assert!(accounts.contains(&sdk.observation_key)); + // Swap does not include lp_mint + assert!(!accounts.contains(&sdk.lp_mint)); } #[test] -fn test_get_accounts_swap_vs_deposit() { - // T1.2.1, T1.2.2: Compare Swap vs Deposit accounts - // Note: This test would need a properly parsed SDK - // For now, verify the behavior contract - - let sdk = AmmSdk::new(); - // Without pool state, both return empty - let _swap_accounts = sdk.get_accounts_to_update(&AmmInstruction::Swap); - let deposit_accounts = sdk.get_accounts_to_update(&AmmInstruction::Deposit); - let withdraw_accounts = sdk.get_accounts_to_update(&AmmInstruction::Withdraw); +fn test_deposit_instruction_accounts() { + let sdk = test_sdk(); + let accounts = sdk.instruction_accounts(&AmmInstruction::Deposit); - // Verify Deposit and Withdraw have same requirements assert_eq!( - deposit_accounts, withdraw_accounts, - "Deposit and Withdraw should have same account requirements" + accounts.len(), + 5, + "Deposit: pool_state, vault_0, vault_1, observation, lp_mint" ); + assert!(accounts.contains(&sdk.pool_state_pubkey)); + assert!(accounts.contains(&sdk.token_0_vault)); + assert!(accounts.contains(&sdk.token_1_vault)); + assert!(accounts.contains(&sdk.observation_key)); + assert!(accounts.contains(&sdk.lp_mint)); } -// ============================================================================= -// 3. CORE TRAIT METHOD TESTS: update -// ============================================================================= - #[test] -fn test_update_before_root_errors() { - // T1.3.4: Update before root parsed should error for accounts that need root - let mut sdk = AmmSdk::new(); - - // Try to update with a vault before pool state is parsed - let vault_data = vec![0u8; 165]; // TokenData size - let vault_keyed = keyed_hot(Pubkey::new_unique(), vault_data); - - // This should either error or skip (depending on implementation) - let result = sdk.update(&[vault_keyed]); - - // Current impl: skips unknown accounts, doesn't error - assert!(result.is_ok(), "Update with unknown should skip, not error"); -} - -#[test] -fn test_update_idempotent() { - // T1.3.3, T6.1: Same account twice should be idempotent - let mut sdk = AmmSdk::new(); - - let data = vec![0u8; 100]; - let keyed = keyed_hot(Pubkey::new_unique(), data.clone()); - - // Update twice with same data - let _ = sdk.update(std::slice::from_ref(&keyed)); - let specs_after_first = sdk.get_all_specs(); - - let _ = sdk.update(std::slice::from_ref(&keyed)); - let specs_after_second = sdk.get_all_specs(); - - // Should be same +fn test_withdraw_equals_deposit() { + let sdk = test_sdk(); + let deposit = sdk.instruction_accounts(&AmmInstruction::Deposit); + let withdraw = sdk.instruction_accounts(&AmmInstruction::Withdraw); assert_eq!( - specs_after_first.len(), - specs_after_second.len(), - "Idempotent: same spec count" + deposit, withdraw, + "Deposit and Withdraw have identical account sets" ); } #[test] -fn test_update_unknown_account_skipped() { - // T1.3.5: Unknown accounts should be skipped - let mut sdk = AmmSdk::new(); - - let unknown_data = vec![0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x00, 0x00, 0x00]; - let keyed = keyed_hot(Pubkey::new_unique(), unknown_data); - - let result = sdk.update(&[keyed]); - assert!(result.is_ok(), "Unknown account should be skipped"); - - let specs = sdk.get_all_specs(); - assert!(specs.is_empty(), "Unknown should not add spec"); -} - -// ============================================================================= -// 4. CORE TRAIT METHOD TESTS: get_all_specs / get_specs_for_instruction -// ============================================================================= - -#[test] -fn test_get_all_empty() { - // T1.4.1: Empty SDK returns empty specs - let sdk = AmmSdk::new(); - let specs = sdk.get_all_specs(); - - assert!(specs.is_empty()); - assert!(all_hot(&specs), "Empty specs should report all_hot"); -} - -#[test] -fn test_all_specs_helpers() { - // Test all_hot() and any_cold() helpers - let specs: Vec> = vec![]; - - assert!(all_hot(&specs), "Empty is all hot"); - assert!(!any_cold(&specs), "Empty has no cold"); -} - -// ============================================================================= -// 5. ERROR HANDLING TESTS -// ============================================================================= - -#[test] -fn test_error_display_impl() { - // T2.4: All errors have Display impl with meaningful messages - let errors = vec![ - AmmSdkError::ParseError("test parse".to_string()), - AmmSdkError::UnknownDiscriminator([0u8; 8]), - AmmSdkError::MissingField("test_field"), - AmmSdkError::PoolStateNotParsed, - ]; - - for err in errors { - let msg = format!("{}", err); - assert!(!msg.is_empty(), "Error should have display message"); - println!("Error display: {}", msg); - } -} - -#[test] -fn test_error_parse_error_contains_cause() { - let err = AmmSdkError::ParseError("deserialization failed".to_string()); - let msg = format!("{}", err); - assert!( - msg.contains("deserialization"), - "ParseError should include cause" - ); -} +fn test_deposit_superset_of_swap() { + let sdk = test_sdk(); + let swap: HashSet = sdk + .instruction_accounts(&AmmInstruction::Swap) + .into_iter() + .collect(); + let deposit: HashSet = sdk + .instruction_accounts(&AmmInstruction::Deposit) + .into_iter() + .collect(); -#[test] -fn test_error_missing_field_names_field() { - let err = AmmSdkError::MissingField("amm_config"); - let msg = format!("{}", err); assert!( - msg.contains("amm_config"), - "MissingField should name the field" - ); -} - -// ============================================================================= -// 6. INVARIANT TESTS -// ============================================================================= - -#[test] -fn test_invariant_no_duplicate_addresses() { - // T6.4: No duplicate addresses in specs - let sdk = AmmSdk::new(); - let specs = sdk.get_all_specs(); - - let addresses: Vec = specs.iter().map(|s| s.pubkey()).collect(); - let unique: HashSet = addresses.iter().copied().collect(); - - assert_eq!( - addresses.len(), - unique.len(), - "No duplicate addresses allowed" + swap.is_subset(&deposit), + "Swap accounts must be a subset of Deposit accounts" ); } #[test] -fn test_invariant_cold_has_context() { - // T6.5: Cold specs must have compressed data - let sdk = AmmSdk::new(); - let specs = sdk.get_all_specs(); - - for spec in &specs { - if spec.is_cold() { - match spec { - AccountSpec::Pda(s) => { - assert!( - s.compressed().is_some(), - "Cold PDA must have compressed: {}", - s.address() - ); - } - AccountSpec::Ata(s) => { - assert!( - s.compressed().is_some(), - "Cold ATA must have compressed: {}", - s.key - ); - } - AccountSpec::Mint(s) => { - assert!( - s.cold.is_some() && s.as_mint().is_some(), - "Cold mint must have cold context + mint_data: {}", - s.key - ); - } - } - } - } -} - -#[test] -fn test_invariant_hot_context_optional() { - // T6.6: Hot specs don't need compressed data (can be None) - let sdk = AmmSdk::new(); - let specs = sdk.get_all_specs(); - - for spec in &specs { - if !spec.is_cold() { - // Hot compressed can be None - this is valid - // Just verify the spec is accessible - let _ = spec.pubkey(); - } +fn test_no_duplicate_pubkeys_in_instruction_accounts() { + let sdk = test_sdk(); + for ix in [ + AmmInstruction::Swap, + AmmInstruction::Deposit, + AmmInstruction::Withdraw, + ] { + let accounts = sdk.instruction_accounts(&ix); + let unique: HashSet = accounts.iter().copied().collect(); + assert_eq!( + accounts.len(), + unique.len(), + "No duplicate pubkeys for {:?}", + ix + ); } } // ============================================================================= -// 7. MULTI-OPERATION TESTS +// 3. LOAD_SPECS (empty input) // ============================================================================= #[test] -fn test_multi_op_deposit_superset_of_swap() { - // T3.1: Deposit accounts should be superset of Swap - let sdk = AmmSdk::new(); - - let swap_accounts: HashSet = sdk - .get_accounts_to_update(&AmmInstruction::Swap) - .into_iter() - .map(|a| a.pubkey()) - .collect(); - let deposit_accounts: HashSet = sdk - .get_accounts_to_update(&AmmInstruction::Deposit) - .into_iter() - .map(|a| a.pubkey()) - .collect(); - - // All swap accounts should be in deposit - for acc in &swap_accounts { - assert!( - deposit_accounts.contains(acc), - "Deposit should include all Swap accounts" - ); - } +fn test_load_specs_empty_input() { + let sdk = test_sdk(); + let specs = sdk.load_specs(&[]).expect("empty input should succeed"); + assert!(specs.is_empty()); } #[test] -fn test_multi_op_withdraw_equals_deposit() { - // T3.1: Withdraw should have same accounts as Deposit - let sdk = AmmSdk::new(); - - let deposit_accounts = sdk.get_accounts_to_update(&AmmInstruction::Deposit); - let withdraw_accounts = sdk.get_accounts_to_update(&AmmInstruction::Withdraw); - - assert_eq!( - deposit_accounts, withdraw_accounts, - "Deposit and Withdraw should have identical account requirements" +fn test_load_specs_unknown_pubkey_skipped() { + let sdk = test_sdk(); + let unknown = AccountInterface::hot( + Pubkey::new_unique(), + Account { + lamports: 0, + data: vec![0; 100], + owner: PROGRAM_ID, + executable: false, + rent_epoch: 0, + }, ); + let specs = sdk + .load_specs(&[unknown]) + .expect("unknown pubkey should be skipped"); + assert!(specs.is_empty()); } // ============================================================================= -// 8. ACCOUNT NAMING TESTS +// 4. HELPER FUNCTIONS // ============================================================================= #[test] -fn test_same_pubkey_same_spec() { - // T4.1, T4.2: Same pubkey should always map to same spec - // Regardless of what name the instruction calls it - - let mut sdk = AmmSdk::new(); - let pubkey = Pubkey::new_unique(); - let data = vec![0u8; 100]; - - // Update with same pubkey twice (simulating different instruction contexts) - let keyed1 = keyed_hot(pubkey, data.clone()); - let keyed2 = keyed_hot(pubkey, data.clone()); - - let _ = sdk.update(&[keyed1]); - let specs_after_first = sdk.get_all_specs(); - - let _ = sdk.update(&[keyed2]); - let specs_after_second = sdk.get_all_specs(); - - // Should have same count (not doubled) - assert_eq!( - specs_after_first.len(), - specs_after_second.len(), - "Same pubkey should not create duplicate specs" - ); +fn test_all_hot_empty() { + let specs: Vec> = vec![]; + assert!(all_hot(&specs), "Empty is all hot"); + assert!(!any_cold(&specs), "Empty has no cold"); } -// ============================================================================= -// 9. EDGE CASE TESTS -// ============================================================================= - #[test] -fn test_edge_all_hot_check() { - // T8.3: all_hot() returns true when all specs are hot +fn test_all_hot_with_hot_spec() { + use csdk_anchor_full_derived_test::amm_test::ObservationState; + let hot_interface = AccountInterface::hot( Pubkey::new_unique(), Account { lamports: 0, data: vec![0; 100], - owner: csdk_anchor_full_derived_test_sdk::PROGRAM_ID, + owner: PROGRAM_ID, executable: false, rent_epoch: 0, }, @@ -436,117 +196,34 @@ fn test_edge_all_hot_check() { }, data: ObservationState::default(), }, - csdk_anchor_full_derived_test_sdk::PROGRAM_ID, + PROGRAM_ID, ); let specs: Vec> = vec![AccountSpec::Pda(hot_spec)]; - assert!( - all_hot(&specs), - "All hot specs should return all_hot() = true" - ); - assert!( - !any_cold(&specs), - "All hot specs should return any_cold() = false" - ); -} - -#[test] -fn test_edge_duplicate_accounts_in_update() { - // T8.6: Duplicate accounts in single update should be deduplicated - let mut sdk = AmmSdk::new(); - let pubkey = Pubkey::new_unique(); - let data = vec![0u8; 100]; - - let keyed = keyed_hot(pubkey, data); - - // Update with same account twice in same call - let _ = sdk.update(&[keyed.clone(), keyed.clone()]); - - // Should not have duplicates in specs - let specs = sdk.get_all_specs(); - let addresses: Vec = specs.iter().map(|s| s.pubkey()).collect(); - let unique: HashSet = addresses.iter().copied().collect(); - - assert_eq!( - addresses.len(), - unique.len(), - "Duplicates should be deduplicated" - ); + assert!(all_hot(&specs)); + assert!(!any_cold(&specs)); } // ============================================================================= -// 10. TYPED FETCH HELPER TESTS +// 5. ERROR DISPLAY // ============================================================================= #[test] -fn test_get_accounts_to_update_empty() { - // get_accounts_to_update should return empty for uninitialized SDK - let sdk = AmmSdk::new(); - - let typed = sdk.get_accounts_to_update(&AmmInstruction::Swap); - assert!(typed.is_empty(), "Typed should be empty before init"); -} - -#[test] -fn test_get_accounts_to_update_categories() { - // Verify typed accounts have correct categories - use light_client::interface::AccountToFetch; - - let sdk = AmmSdk::new(); - let typed = sdk.get_accounts_to_update(&AmmInstruction::Deposit); - - // All should be one of Pda, Token, Ata, or Mint - for acc in &typed { - match acc { - AccountToFetch::Pda { .. } => {} - AccountToFetch::Token { .. } => {} - AccountToFetch::Ata { .. } => {} - AccountToFetch::Mint { .. } => {} - } - } +fn test_error_display() { + let err = AmmSdkError::ParseError("deserialization failed".to_string()); + let msg = format!("{}", err); + assert!( + msg.contains("deserialization"), + "ParseError should include cause" + ); } // ============================================================================= -// 11. SAME TYPE DIFFERENT INSTANCE TESTS +// 6. VARIANT SEED DISTINCTION // ============================================================================= -// Critical tests for ensuring vault_0 and vault_1 (same type, different seeds/values) -// are handled as separate specs and not mingled together. - -#[test] -fn test_same_type_different_pubkey_separate_specs() { - // CRITICAL: Two accounts of same type but different pubkeys must be stored separately. - // This is the case for vault_0 and vault_1 which are both token vaults - // but with different mints and therefore different pubkeys. - - // Create two different pubkeys (simulating vault_0 and vault_1) - let vault_0_pubkey = Pubkey::new_unique(); - let vault_1_pubkey = Pubkey::new_unique(); - - assert_ne!( - vault_0_pubkey, vault_1_pubkey, - "Vaults must have different pubkeys" - ); - - // In the SDK, these would be keyed by pubkey in HashMap - // Verify the design: each pubkey gets its own entry - let mut pubkey_set: HashSet = HashSet::new(); - pubkey_set.insert(vault_0_pubkey); - pubkey_set.insert(vault_1_pubkey); - - assert_eq!( - pubkey_set.len(), - 2, - "Two different pubkeys must create two entries" - ); -} #[test] fn test_variant_seed_values_distinguish_instances() { - // CRITICAL: Even if variants have same type name, the seed VALUES must differ. - // Example: Token0Vault{pool_state: A, token_0_mint: B} vs Token1Vault{pool_state: A, token_1_mint: C} - // - // The variant enum encodes WHICH account this is via the variant name AND seed values. - use csdk_anchor_full_derived_test::csdk_anchor_full_derived_test::{ Token0VaultSeeds, Token1VaultSeeds, }; @@ -583,8 +260,6 @@ fn test_variant_seed_values_distinguish_instances() { token_data: default_token, }); - // These are different enum variants (type-level distinction) - // Even if they were the same variant type, the seed values differ match (&variant_0, &variant_1) { (LightAccountVariant::Token0Vault(data_0), LightAccountVariant::Token1Vault(data_1)) => { assert_ne!( @@ -596,612 +271,37 @@ fn test_variant_seed_values_distinguish_instances() { } } -#[test] -fn test_specs_contain_all_vaults_not_merged() { - // CRITICAL: When getting specs for Swap, we must get BOTH vault_0 AND vault_1, - // not have them merged into a single spec. - - // The SDK stores specs in HashMap - // This test verifies the invariant that different pubkeys = different specs - - let sdk = AmmSdk::new(); - - // Before init, specs are empty - let specs = sdk.get_specs_for_instruction(&AmmInstruction::Swap); - - // Count of specs should match number of unique accounts - // When SDK is properly initialized with pool_state and vaults, - // Swap should return pool_state + vault_0 + vault_1 = 3 specs - - // For now, verify the empty case works correctly - assert_eq!(specs.len(), 0, "Uninitialized SDK should have 0 specs"); - - // The invariant we're testing: no duplicate addresses - let addresses: HashSet = specs.iter().map(|s| s.pubkey()).collect(); - assert_eq!( - specs.len(), - addresses.len(), - "Each spec must have unique address" - ); -} - -#[test] -fn test_field_name_uniqueness_across_instructions() { - // CRITICAL: Field names like "token_0_vault" must be globally unique across ALL instructions. - // The macros enforce this - same field name = same account = same spec. - // - // This test documents the design contract: - // - In initialize: token_0_vault refers to account at pubkey A - // - In swap: source_vault (if it's the same account) MUST have pubkey A - // - The SDK keys by pubkey, so same pubkey = same spec regardless of field name in instruction - - // Two instructions can call the same account different names: - // initialize.token_0_vault and swap.input_vault could be the SAME account - // The SDK correctly handles this by keying on pubkey, not field name - - let shared_pubkey = Pubkey::new_unique(); - - // If two instructions reference the same pubkey, they're the same account - // The SDK stores ONE spec for this pubkey, not two - let mut seen_pubkeys: HashSet = HashSet::new(); - - // "initialize.token_0_vault" -> shared_pubkey - seen_pubkeys.insert(shared_pubkey); - - // "swap.input_vault" -> shared_pubkey (same account, different name) - seen_pubkeys.insert(shared_pubkey); - - assert_eq!( - seen_pubkeys.len(), - 1, - "Same pubkey from different field names = single spec" - ); -} - -#[test] -fn test_updating_vault_0_does_not_affect_vault_1() { - // CRITICAL: Updating vault_0's spec must NOT affect vault_1's spec. - // They are independent entries in the HashMap. - - let mut sdk = AmmSdk::new(); - - // Create two different "vault" accounts - let vault_0_pubkey = Pubkey::new_unique(); - let vault_1_pubkey = Pubkey::new_unique(); - - let vault_0_data = vec![0xAAu8; 100]; - let vault_1_data = vec![0xBBu8; 100]; - - let vault_0_keyed = keyed_hot(vault_0_pubkey, vault_0_data); - let vault_1_keyed = keyed_hot(vault_1_pubkey, vault_1_data); - - // Update with both - let _ = sdk.update(&[vault_0_keyed.clone(), vault_1_keyed.clone()]); - - // Now update vault_0 again with different data - let vault_0_updated = keyed_hot(vault_0_pubkey, vec![0xCCu8; 100]); - let _ = sdk.update(&[vault_0_updated]); - - // Verify: vault_1 should still have its original data (if tracked) - // The key point: updating by pubkey only affects that specific entry - let specs = sdk.get_all_specs(); - - // Verify both are still separate entries (if they were recognized) - let addresses: HashSet = specs.iter().map(|s| s.pubkey()).collect(); - - // No duplicates - assert_eq!( - specs.len(), - addresses.len(), - "Each vault must remain separate" - ); -} - -#[test] -fn test_operation_returns_all_required_instances() { - // CRITICAL: get_specs_for_instruction(Swap) must return BOTH vault_0 AND vault_1, - // not just one of them. - - // Document the expected behavior: - // Swap operation needs: pool_state, vault_0, vault_1 - // Deposit operation needs: pool_state, vault_0, vault_1, observation, lp_mint - - let sdk = AmmSdk::new(); - - // Get accounts needed for Swap - let swap_accounts = sdk.get_accounts_to_update(&AmmInstruction::Swap); - - // Without pool state, this is empty, but document the contract: - // When properly initialized, Swap should request both vaults - // The SDK implementation does: vec![token_0_vault, token_1_vault].into_iter().flatten() - - // This confirms the design: BOTH vaults are requested, not just one - // Each vault is a separate entry, not merged - - // Verify Deposit requests more accounts than Swap - let deposit_accounts = sdk.get_accounts_to_update(&AmmInstruction::Deposit); - - // Even when empty, the contract holds: - // len(deposit_accounts) >= len(swap_accounts) because Deposit is a superset - assert!( - deposit_accounts.len() >= swap_accounts.len(), - "Deposit must request at least as many accounts as Swap" - ); -} - -#[test] -fn test_hashmap_keying_prevents_spec_mingling() { - // CRITICAL: The SDK uses HashMap which naturally prevents mingling. - // This test verifies the data structure choice is correct. - - use std::collections::HashMap; - - let vault_0_pubkey = Pubkey::new_unique(); - let vault_1_pubkey = Pubkey::new_unique(); - - // Simulate the SDK's internal storage - let mut specs: HashMap = HashMap::new(); - - // Insert vault_0 spec - specs.insert(vault_0_pubkey, "vault_0_spec".to_string()); - - // Insert vault_1 spec - specs.insert(vault_1_pubkey, "vault_1_spec".to_string()); - - // Verify: both are stored separately - assert_eq!(specs.len(), 2, "Two vaults = two entries"); - assert_eq!( - specs.get(&vault_0_pubkey), - Some(&"vault_0_spec".to_string()) - ); - assert_eq!( - specs.get(&vault_1_pubkey), - Some(&"vault_1_spec".to_string()) - ); - - // Updating vault_0 doesn't affect vault_1 - specs.insert(vault_0_pubkey, "vault_0_updated".to_string()); - assert_eq!( - specs.get(&vault_1_pubkey), - Some(&"vault_1_spec".to_string()), - "vault_1 must be unaffected" - ); -} - // ============================================================================= -// 8. DIVERGENT NAMING TESTS: input_vault/output_vault vs token_0_vault/token_1_vault +// 7. DIRECTION-AGNOSTIC DESIGN // ============================================================================= #[test] -fn test_swap_returns_both_vaults_regardless_of_role() { - // CRITICAL: Swap instruction uses input_vault/output_vault names, - // but they are aliases for token_0_vault/token_1_vault. - // The SDK must return BOTH vaults for Swap, regardless of trade direction. - - let sdk = AmmSdk::new(); +fn test_swap_returns_both_vaults() { + let sdk = test_sdk(); + let accounts = sdk.instruction_accounts(&AmmInstruction::Swap); - let swap_accounts = sdk.get_accounts_to_update(&AmmInstruction::Swap); - - // Without pool state initialized, this is empty, but the contract is: - // When pool_state has token_0_vault and token_1_vault set, - // get_accounts_to_update(Swap) returns BOTH. - // - // This is because the SDK doesn't know which vault will be "input" vs "output" - // at runtime - that depends on trade direction chosen by the user. - - // Document: accounts returned are keyed by CANONICAL pubkeys (token_0_vault, token_1_vault) - // NOT by instruction field names (input_vault, output_vault) - - // The Swap instruction's input_vault/output_vault are just ALIASES - // that map to the same underlying accounts. assert!( - swap_accounts.is_empty(), - "SDK without pool state returns empty - but contract is to return both vaults when populated" - ); -} - -#[test] -fn test_directional_alias_same_pubkey_same_spec() { - // CRITICAL: input_vault and output_vault in Swap instruction point to - // the same underlying accounts (token_0_vault or token_1_vault). - // - // When ZeroForOne: input_vault = token_0_vault, output_vault = token_1_vault - // When OneForZero: input_vault = token_1_vault, output_vault = token_0_vault - // - // The SDK stores specs by PUBKEY, so the "role" (input/output) doesn't matter. - // The spec for token_0_vault is the same whether it's used as input or output. - - use std::collections::HashMap; - - // Simulate pool state with two vaults - let token_0_vault = Pubkey::new_unique(); - let token_1_vault = Pubkey::new_unique(); - - // Simulate SDK's HashMap - let mut specs: HashMap = HashMap::new(); - specs.insert(token_0_vault, "token_0_vault_spec"); - specs.insert(token_1_vault, "token_1_vault_spec"); - - // Swap ZeroForOne: input=token_0, output=token_1 - let input_vault_zero_for_one = token_0_vault; - let output_vault_zero_for_one = token_1_vault; - - // Swap OneForZero: input=token_1, output=token_0 - let input_vault_one_for_zero = token_1_vault; - let output_vault_one_for_zero = token_0_vault; - - // Regardless of direction, lookup by pubkey returns the same spec - assert_eq!( - specs.get(&input_vault_zero_for_one), - specs.get(&output_vault_one_for_zero), - "Same pubkey = same spec regardless of role" - ); - - assert_eq!( - specs.get(&output_vault_zero_for_one), - specs.get(&input_vault_one_for_zero), - "Same pubkey = same spec regardless of role" + accounts.contains(&sdk.token_0_vault), + "Swap must include vault_0" ); -} - -#[test] -fn test_sdk_doesnt_need_trade_direction() { - // The SDK is DIRECTION-AGNOSTIC. - // It doesn't need to know if the user is swapping ZeroForOne or OneForZero. - // It just returns all necessary accounts and lets the client decide. - - let sdk = AmmSdk::new(); - - // Both directions use the same set of accounts from get_accounts_to_update - let accounts = sdk.get_accounts_to_update(&AmmInstruction::Swap); - - // The SDK's contract: return [token_0_vault, token_1_vault] for Swap - // The client then passes them to the instruction as input_vault/output_vault - // based on the desired trade direction. - - // This is the key insight: decompression is role-agnostic. - // We decompress the account regardless of how it will be used in the swap. - - // Direction independence: same accounts returned regardless of future use - // (accounts is empty for uninitialized SDK, non-empty when populated) - let _ = accounts; -} - -#[test] -fn test_decompression_instruction_role_agnostic() { - // Decompression doesn't care about instruction-level roles. - // When we build a decompression instruction, we specify: - // - The account pubkey - // - The seeds (for PDA verification) - // - The compressed account data - // - // We do NOT specify: - // - Whether it's an "input" or "output" vault - // - Which instruction will use it - // - What role it will play - // - // The decompression instruction is purely about restoring the account to on-chain state. - - // This test documents the separation of concerns: - // 1. SDK: returns specs keyed by canonical pubkey - // 2. Client: builds decompression instructions from specs - // 3. Program: uses decompressed accounts in any role - - // The SDK never sees "input_vault" or "output_vault" - only token_0_vault, token_1_vault - // The program's Swap instruction uses aliases, but that's transparent to the SDK. - - let sdk = AmmSdk::new(); - let specs = sdk.get_all_specs(); - - // All specs are keyed by pubkey, not by instruction field name - for spec in &specs { - // spec.pubkey() is the canonical pubkey - // There's no "role" field because roles are instruction-specific - assert!( - !spec.pubkey().to_bytes().iter().all(|&b| b == 0), - "Valid pubkey, no role information" - ); - } -} - -#[test] -fn test_swap_and_deposit_share_vault_specs() { - // Swap uses vaults as input/output - // Deposit also uses vaults (for receiving tokens) - // Both operations use the SAME underlying accounts, just different roles. - // - // The SDK must return the same specs for these shared accounts. - - let sdk = AmmSdk::new(); - - let swap_accounts = sdk.get_accounts_to_update(&AmmInstruction::Swap); - let deposit_accounts = sdk.get_accounts_to_update(&AmmInstruction::Deposit); - - // Swap: [token_0_vault, token_1_vault] - // Deposit: [token_0_vault, token_1_vault, observation, lp_mint] - // - // The vault pubkeys in swap_accounts should be a subset of deposit_accounts - // (when both are populated) - - // Verify the relationship contract assert!( - deposit_accounts.len() >= swap_accounts.len(), - "Deposit accounts should be superset of Swap accounts" - ); -} - -#[test] -fn test_canonical_variant_independent_of_alias() { - // The LightAccountVariant enum uses CANONICAL names: - // - Token0Vault { pool_state, token_0_mint } - // - Token1Vault { pool_state, token_1_mint } - // - // NOT aliased names: - // - InputVault (NO - this would be instruction-specific) - // - OutputVault (NO - this would be instruction-specific) - // - // The variant encodes the TRUE identity of the account, - // not how it's used in a particular instruction. - - // Document the design principle: - // Variants are based on SEEDS (which are constant per account) - // NOT based on instruction roles (which vary per operation) - - // For example, token_0_vault always has these seeds: - // [POOL_VAULT_SEED, pool_state.key(), token_0_mint.key()] - // - // Whether it's used as input_vault or output_vault in Swap, - // the seeds are the same. The variant is Token0Vault, always. - - let sdk = AmmSdk::new(); - - // Get specs - let specs = sdk.get_specs_for_instruction(&AmmInstruction::Swap); - - // All specs should have canonical variants - for spec in &specs { - if let AccountSpec::Pda(pda) = spec { - match &pda.variant { - LightAccountVariant::PoolState { .. } => { - // Canonical: PoolState - } - LightAccountVariant::ObservationState { .. } => { - // Canonical: ObservationState - } - LightAccountVariant::Token0Vault(_) => { - // Canonical: Token0Vault - } - LightAccountVariant::Token1Vault(_) => { - // Canonical: Token1Vault - } - _ => { - // Other variants from the program (not AMM-related) - } - } - } - // No "InputVault" or "OutputVault" variants exist - by design - } -} - -#[test] -fn test_swap_loads_decompresses_before_execution() { - // The correct flow for Swap with cold vaults: - // - // 1. Client: Get accounts to load for Swap - // 2. SDK returns: [token_0_vault, token_1_vault] - // 3. Client: Build decompression transactions - // 4. Client: Execute decompression (vaults now on-chain) - // 5. Client: Build Swap instruction with: - // - input_vault = token_0_vault (for ZeroForOne) - // - output_vault = token_1_vault - // OR - // - input_vault = token_1_vault (for OneForZero) - // - output_vault = token_0_vault - // 6. Client: Execute Swap - // - // The decompression step (3-4) doesn't know about step 5's direction. - // It just decompresses both vaults. - - // This test documents the expected flow - let sdk = AmmSdk::new(); - - // Step 1-2: Get accounts - let _accounts = sdk.get_accounts_to_update(&AmmInstruction::Swap); - - // Step 3-4: Decompression (direction-agnostic) - // Both vaults decompressed regardless of which is input/output - - // Step 5-6: Swap execution (direction chosen here) - // The SDK has no involvement in determining direction -} - -#[test] -fn test_multiple_operations_same_underlying_account() { - // Multiple operations can reference the same account with different field names: - // - // | Operation | Field Name | Underlying Account | - // |-----------|----------------|-------------------| - // | Initialize| token_0_vault | 0xAAAA | - // | Deposit | token_0_vault | 0xAAAA | - // | Withdraw | token_0_vault | 0xAAAA | - // | Swap | input_vault | 0xAAAA (if ZeroForOne) | - // | Swap | output_vault | 0xAAAA (if OneForZero) | - // - // The SDK stores ONE spec for pubkey 0xAAAA, used by all operations. - - use std::collections::HashMap; - - let underlying_pubkey = Pubkey::new_unique(); - - // Simulate field name -> pubkey mapping - let field_mappings: HashMap<&str, Pubkey> = [ - ("token_0_vault", underlying_pubkey), // Initialize, Deposit, Withdraw - ("input_vault_zero_for_one", underlying_pubkey), // Swap ZeroForOne - ("output_vault_one_for_zero", underlying_pubkey), // Swap OneForZero - ] - .into_iter() - .collect(); - - // All map to the same pubkey - assert_eq!( - field_mappings.get("token_0_vault"), - field_mappings.get("input_vault_zero_for_one"), - "Different names, same account" - ); - assert_eq!( - field_mappings.get("token_0_vault"), - field_mappings.get("output_vault_one_for_zero"), - "Different names, same account" + accounts.contains(&sdk.token_1_vault), + "Swap must include vault_1" ); - - // The SDK stores by pubkey, so ONE spec serves all aliases - let mut specs: HashMap = HashMap::new(); - specs.insert(underlying_pubkey, "the_one_and_only_spec"); - - assert_eq!( - specs.len(), - 1, - "One pubkey = one spec regardless of aliases" - ); -} - -// ============================================================================= -// 9. SINGLE SOURCE OF TRUTH INVARIANT TESTS -// ============================================================================= - -#[test] -fn test_invariant_get_accounts_subset_of_specs() { - // INVARIANT: For all operations, get_accounts_to_update() pubkeys - // must be a subset of get_specs_for_instruction() addresses. - // - // This catches bugs where one method was updated but not the other. - - let sdk = AmmSdk::new(); - - for op in [ - AmmInstruction::Swap, - AmmInstruction::Deposit, - AmmInstruction::Withdraw, - ] { - let update_keys: HashSet<_> = sdk - .get_accounts_to_update(&op) - .into_iter() - .map(|a| a.pubkey()) - .collect(); - let spec_keys: HashSet<_> = sdk - .get_specs_for_instruction(&op) - .iter() - .map(|s| s.pubkey()) - .collect(); - - // When SDK is empty, both should be empty - assert!( - update_keys.is_subset(&spec_keys) || (update_keys.is_empty() && spec_keys.is_empty()), - "get_accounts_to_update must return subset of get_specs_for_instruction for {:?}\n update_keys: {:?}\n spec_keys: {:?}", - op, update_keys, spec_keys - ); - } -} - -#[test] -fn test_invariant_typed_matches_untyped_pubkeys() { - // INVARIANT: get_accounts_to_update() must return the same pubkeys - // as get_accounts_to_update(), just with type information. - // (Now they're the same method, so this test is essentially a no-op) - - let sdk = AmmSdk::new(); - - for op in [ - AmmInstruction::Swap, - AmmInstruction::Deposit, - AmmInstruction::Withdraw, - ] { - let untyped: HashSet<_> = sdk - .get_accounts_to_update(&op) - .into_iter() - .map(|a| a.pubkey()) - .collect(); - let typed: HashSet<_> = sdk - .get_accounts_to_update(&op) - .iter() - .map(|a| a.pubkey()) - .collect(); - - assert_eq!( - untyped, typed, - "Typed and untyped must return same pubkeys for {:?}", - op - ); - } } #[test] -fn test_invariant_all_methods_derive_from_account_requirements() { - // DESIGN INVARIANT: All three methods must derive from account_requirements() - // - // get_accounts_to_update() -> account_requirements().map(pubkey) - // get_accounts_to_update() -> account_requirements().map(to_fetch) - // get_specs_for_instruction() -> account_requirements().filter_map(spec_lookup) - // - // This ensures they can NEVER drift out of sync. - - // Verify by code inspection: - // 1. get_accounts_to_update() calls self.account_requirements(op) - // 2. get_accounts_to_update() calls self.account_requirements(op) - // 3. get_specs_for_instruction() calls self.account_requirements(op) - // - // All derive from the SAME source. - - let sdk = AmmSdk::new(); +fn test_canonical_variant_not_aliased() { + // The LightAccountVariant enum uses canonical names (Token0Vault, Token1Vault), + // not instruction aliases (InputVault, OutputVault). + // The SDK is direction-agnostic: both vaults are always returned. + let sdk = test_sdk(); + let swap_accounts = sdk.instruction_accounts(&AmmInstruction::Swap); + let deposit_accounts = sdk.instruction_accounts(&AmmInstruction::Deposit); - // Sanity check: all operations return consistent empty results - for op in [ - AmmInstruction::Swap, - AmmInstruction::Deposit, - AmmInstruction::Withdraw, - ] { - let pubkeys = sdk.get_accounts_to_update(&op); - let typed = sdk.get_accounts_to_update(&op); - let specs = sdk.get_specs_for_instruction(&op); - - // All should be empty for uninitialized SDK - assert!(pubkeys.is_empty(), "Empty SDK should return no pubkeys"); - assert!( - typed.is_empty(), - "Empty SDK should return no typed accounts" - ); - assert!(specs.is_empty(), "Empty SDK should return no specs"); + // Both vaults appear in both Swap and Deposit + for vault in [&sdk.token_0_vault, &sdk.token_1_vault] { + assert!(swap_accounts.contains(vault)); + assert!(deposit_accounts.contains(vault)); } } - -#[test] -fn test_swap_observation_included_after_refactor() { - // Regression test: Swap must include observation after the single-source-of-truth refactor. - // - // Before fix: get_accounts_to_update(Swap) returned [vault_0, vault_1] - MISSING observation! - // After fix: get_accounts_to_update(Swap) returns [pool_state, vault_0, vault_1, observation] - - // Create a mock initialized SDK state - // We can't fully initialize without real data, but we can verify the count - - let sdk = AmmSdk::new(); - - // For an uninitialized SDK, both return empty - let swap_accounts = sdk.get_accounts_to_update(&AmmInstruction::Swap); - let deposit_accounts = sdk.get_accounts_to_update(&AmmInstruction::Deposit); - - // The key invariant: Swap and Deposit should now have the same number of - // non-mint accounts when pool_state is set (pool_state, vault_0, vault_1, observation) - // The only difference is Deposit has lp_mint. - - // When empty, both are empty - assert_eq!( - swap_accounts.len(), - deposit_accounts.len(), - "Both empty when uninitialized" - ); - - // Document the expected counts when initialized: - // Swap: pool_state, vault_0, vault_1, observation = 4 - // Deposit: pool_state, vault_0, vault_1, observation, lp_mint_signer = 5 -} diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/amm_stress_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/amm_stress_test.rs index 1bd04c0cbc..b857496e9d 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/amm_stress_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/amm_stress_test.rs @@ -19,8 +19,8 @@ use light_batched_merkle_tree::{ initialize_state_tree::InitStateTreeAccountsInstructionData, }; use light_client::interface::{ - create_load_instructions, get_create_accounts_proof, AccountInterface, AccountSpec, - CreateAccountsProofInput, InitializeRentFreeConfig, LightProgramInterface, + create_load_instructions, get_create_accounts_proof, AccountSpec, CreateAccountsProofInput, + InitializeRentFreeConfig, LightProgramInterface, }; use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{ @@ -493,69 +493,67 @@ async fn decompress_all(ctx: &mut AmmTestContext, pdas: &AmmPdas) { .expect("pool_state should exist"); assert!(pool_interface.is_cold(), "pool_state should be cold"); - let mut sdk = AmmSdk::from_keyed_accounts(&[pool_interface]) - .expect("AmmSdk::from_keyed_accounts should succeed"); + let sdk = + AmmSdk::new(pdas.pool_state, pool_interface.data()).expect("AmmSdk::new should succeed"); - let accounts_to_fetch = sdk.get_accounts_to_update(&AmmInstruction::Deposit); + let mut pubkeys: Vec<_> = sdk.instruction_accounts(&AmmInstruction::Deposit); + pubkeys.push(pdas.creator_lp_token); + pubkeys.push(ctx.creator_token_0); + pubkeys.push(ctx.creator_token_1); + pubkeys.push(ctx.token_0_mint); + pubkeys.push(ctx.token_1_mint); - let keyed_accounts = ctx + let account_interfaces = ctx .rpc - .fetch_accounts(&accounts_to_fetch, None) + .get_multiple_account_interfaces(pubkeys.iter().collect(), None) .await - .expect("fetch_accounts should succeed"); + .expect("get_multiple_account_interfaces should succeed"); - sdk.update(&keyed_accounts) - .expect("sdk.update should succeed"); - - let specs = sdk.get_specs_for_instruction(&AmmInstruction::Deposit); - - let creator_lp_interface = ctx - .rpc - .get_associated_token_account_interface(&ctx.creator.pubkey(), &pdas.lp_mint, None) - .await - .expect("failed to get creator_lp_token") - .value - .expect("creator_lp_token should exist"); - - // Creator's token_0 and token_1 ATAs also get compressed during epoch warp - let creator_token_0_interface = ctx - .rpc - .get_associated_token_account_interface(&ctx.creator.pubkey(), &ctx.token_0_mint, None) - .await - .expect("failed to get creator_token_0") + let interfaces: Vec<_> = account_interfaces .value - .expect("creator_token_0 should exist"); + .into_iter() + .zip(pubkeys.iter()) + .filter_map(|(opt, &pk)| opt.map(|ai| (pk, ai))) + .collect(); + + let cold_accounts: Vec<_> = interfaces + .iter() + .filter(|(_, a)| a.is_cold()) + .map(|(_, a)| a.clone()) + .collect(); + + let specs = sdk + .load_specs(&cold_accounts) + .expect("load_specs should succeed"); + + let find_interface = |pk: Pubkey| { + interfaces + .iter() + .find(|(p, _)| *p == pk) + .map(|(_, ai)| ai.clone()) + .expect("account should exist") + }; - let creator_token_1_interface = ctx - .rpc - .get_associated_token_account_interface(&ctx.creator.pubkey(), &ctx.token_1_mint, None) - .await - .expect("failed to get creator_token_1") - .value - .expect("creator_token_1 should exist"); + let creator_lp_interface = find_interface(pdas.creator_lp_token); + let creator_token_0_interface = find_interface(ctx.creator_token_0); + let creator_token_1_interface = find_interface(ctx.creator_token_1); - let mint_0_account_iface = AccountInterface::from( - ctx.rpc - .get_mint_interface(&ctx.token_0_mint, None) - .await - .expect("failed to get token_0_mint") - .value - .expect("token_0_mint should exist"), - ); + let mint_0_account_iface = interfaces + .iter() + .find(|(p, _)| *p == ctx.token_0_mint) + .map(|(_, ai)| ai.clone()) + .expect("token_0_mint should exist"); - let mint_1_account_iface = AccountInterface::from( - ctx.rpc - .get_mint_interface(&ctx.token_1_mint, None) - .await - .expect("failed to get token_1_mint") - .value - .expect("token_1_mint should exist"), - ); + let mint_1_account_iface = interfaces + .iter() + .find(|(p, _)| *p == ctx.token_1_mint) + .map(|(_, ai)| ai.clone()) + .expect("token_1_mint should exist"); let mut all_specs = specs; - all_specs.push(AccountSpec::Ata(Box::new(creator_lp_interface))); - all_specs.push(AccountSpec::Ata(Box::new(creator_token_0_interface))); - all_specs.push(AccountSpec::Ata(Box::new(creator_token_1_interface))); + all_specs.push(AccountSpec::Ata(creator_lp_interface)); + all_specs.push(AccountSpec::Ata(creator_token_0_interface)); + all_specs.push(AccountSpec::Ata(creator_token_1_interface)); all_specs.push(AccountSpec::Mint(mint_0_account_iface)); all_specs.push(AccountSpec::Mint(mint_1_account_iface)); diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/amm_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/amm_test.rs index c30ab6369a..986b6641d2 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/amm_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/amm_test.rs @@ -635,35 +635,42 @@ async fn test_amm_full_lifecycle() { .expect("pool_state should exist"); assert!(pool_interface.is_cold(), "pool_state should be cold"); - // Create Program Interface SDK. - let mut sdk = AmmSdk::from_keyed_accounts(&[pool_interface]) - .expect("ProgrammSdk::from_keyed_accounts should succeed"); + // Create SDK from pool state data. + let sdk = + AmmSdk::new(pdas.pool_state, pool_interface.data()).expect("AmmSdk::new should succeed"); - let accounts_to_fetch = sdk.get_accounts_to_update(&AmmInstruction::Deposit); - - let keyed_accounts = ctx - .rpc - .fetch_accounts(&accounts_to_fetch, None) - .await - .expect("fetch_accounts should succeed"); - - sdk.update(&keyed_accounts) - .expect("sdk.update should succeed"); - - let specs = sdk.get_specs_for_instruction(&AmmInstruction::Deposit); - - let creator_lp_interface = ctx + let mut pubkeys: Vec<_> = sdk.instruction_accounts(&AmmInstruction::Deposit); + pubkeys.push(pdas.creator_lp_token); + let account_interfaces = ctx .rpc - .get_associated_token_account_interface(&ctx.creator.pubkey(), &pdas.lp_mint, None) + .get_multiple_account_interfaces(pubkeys.iter().collect(), None) .await - .expect("failed to get creator_lp_token") + .expect("get_multiple_account_interfaces should succeed"); + let interfaces: Vec<_> = account_interfaces .value + .into_iter() + .zip(pubkeys.iter()) + .filter_map(|(opt, &pk)| opt.map(|ai| (pk, ai))) + .collect(); + + let cold_accounts: Vec<_> = interfaces + .iter() + .filter(|(_, a)| a.is_cold()) + .map(|(_, a)| a.clone()) + .collect(); + + let mut all_specs = sdk + .load_specs(&cold_accounts) + .expect("load_specs should succeed"); + + let creator_lp_interface = interfaces + .iter() + .find(|(pk, _)| *pk == pdas.creator_lp_token) + .map(|(_, ai)| ai.clone()) .expect("creator_lp_token should exist"); - // add ata use light_client::interface::AccountSpec; - let mut all_specs = specs; - all_specs.push(AccountSpec::Ata(Box::new(creator_lp_interface))); + all_specs.push(AccountSpec::Ata(creator_lp_interface)); let decompress_ixs = create_load_instructions(&all_specs, ctx.payer.pubkey(), ctx.config_pda, &ctx.rpc) diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs index 747c75ce32..82844bae47 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs @@ -352,9 +352,7 @@ async fn test_create_pdas_and_mint_auto() { GameSession as GameSessionState, UserRecord, }; use light_account::TokenDataWithSeeds; - use light_client::interface::{ - create_load_instructions, AccountInterface, AccountSpec, PdaSpec, - }; + use light_client::interface::{create_load_instructions, AccountSpec, PdaSpec}; // Fetch unified interfaces (hot/cold transparent) let user_interface = rpc @@ -374,13 +372,20 @@ async fn test_create_pdas_and_mint_auto() { assert!(game_interface.is_cold(), "GameSession should be cold"); let vault_interface = rpc - .get_token_account_interface(&vault_pda, None) + .get_account_interface(&vault_pda, None) .await .expect("failed to get vault") .value .expect("vault should exist"); assert!(vault_interface.is_cold(), "Vault should be cold"); - assert_eq!(vault_interface.amount(), vault_mint_amount); + let vault_amount = { + let token = light_token_interface::state::Token::deserialize( + &mut &vault_interface.account.data[..], + ) + .expect("parse vault"); + token.amount + }; + assert_eq!(vault_amount, vault_mint_amount); // Build PdaSpec for UserRecord let user_data = UserRecord::deserialize(&mut &user_interface.account.data[8..]) @@ -409,8 +414,8 @@ async fn test_create_pdas_and_mint_auto() { }; let game_spec = PdaSpec::new(game_interface.clone(), game_variant, program_id); - // Build PdaSpec for Vault (CToken) - // Vault is fetched as token account but decompressed as PDA, so convert cold context + // Build PdaSpec for Vault (CToken). PdaSpec accepts ColdContext::Token; + // load_accounts uses compressed() which returns inner CompressedAccount. let token = light_token_interface::state::Token::deserialize(&mut &vault_interface.account.data[..]) .expect("Failed to parse Token"); @@ -418,53 +423,40 @@ async fn test_create_pdas_and_mint_auto() { seeds: VaultSeeds { mint: mint_pda }, token_data: token, }); - let vault_compressed = vault_interface - .compressed() - .expect("cold vault must have compressed data"); - // Convert TokenAccountInterface to AccountInterface with compressed account - let vault_interface_for_pda = AccountInterface { - key: vault_interface.key, - account: vault_interface.account.clone(), - cold: Some(vault_compressed.account.clone()), - }; - let vault_spec = PdaSpec::new(vault_interface_for_pda, vault_variant, program_id); + let vault_spec = PdaSpec::new(vault_interface.clone(), vault_variant, program_id); - // get_associated_token_account_interface: fetches ATA with unified handling using standard SPL types + let ata = light_token::instruction::derive_token_ata(&payer.pubkey(), &mint_pda).0; let ata_interface = rpc - .get_associated_token_account_interface(&payer.pubkey(), &mint_pda, None) + .get_account_interface(&ata, None) .await - .expect("get_associated_token_account_interface should succeed") + .expect("get_account_interface for ATA should succeed") .value .expect("ATA should exist"); assert!(ata_interface.is_cold(), "ATA should be cold after warp"); - assert_eq!(ata_interface.amount(), user_ata_mint_amount); - assert_eq!(ata_interface.mint(), mint_pda); - // After fix: parsed.owner = wallet_owner (payer), not ATA address - assert_eq!(ata_interface.owner(), payer.pubkey()); - - // Use TokenAccountInterface directly for ATA - // (no separate AtaSpec needed - TokenAccountInterface has all the data) - - // Fetch mint via get_mint_interface - let mint_account_interface = light_client::interface::AccountInterface::from( - rpc.get_mint_interface(&mint_pda, None) - .await - .expect("get_mint_interface should succeed") - .value - .expect("Mint should exist"), - ); - assert!( - mint_account_interface.is_cold(), - "Mint should be cold after warp" - ); + { + let token = + light_token_interface::state::Token::deserialize(&mut &ata_interface.account.data[..]) + .expect("parse ATA"); + assert_eq!(token.amount, user_ata_mint_amount); + assert_eq!(token.mint, mint_pda); + assert_eq!(token.owner, payer.pubkey()); + } + + let mint_interface = rpc + .get_account_interface(&mint_pda, None) + .await + .expect("get_account_interface for mint should succeed") + .value + .expect("Mint should exist"); + assert!(mint_interface.is_cold(), "Mint should be cold after warp"); // Build AccountSpec slice for all accounts let specs: Vec> = vec![ AccountSpec::Pda(user_spec), AccountSpec::Pda(game_spec), AccountSpec::Pda(vault_spec), - AccountSpec::Ata(Box::new(ata_interface.clone())), - AccountSpec::Mint(mint_account_interface), + AccountSpec::Ata(ata_interface.clone()), + AccountSpec::Mint(mint_interface), ]; // Load all accounts with single call diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/d10_token_accounts_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/d10_token_accounts_test.rs index fd04ba17db..1a85193aac 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/d10_token_accounts_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/d10_token_accounts_test.rs @@ -527,12 +527,12 @@ async fn test_d10_single_ata_markonly_lifecycle() { shared::assert_onchain_closed(&mut ctx.rpc, &d10_markonly_ata, "d10_markonly_ata").await; // PHASE 3: Decompress ATA using create_load_instructions - // ATAs use get_associated_token_account_interface which fetches the compressed token data + let ata = light_token::instruction::derive_token_ata(&ata_owner, &mint).0; let ata_interface = ctx .rpc - .get_associated_token_account_interface(&ata_owner, &mint, None) + .get_account_interface(&ata, None) .await - .expect("get_associated_token_account_interface should succeed") + .expect("get_account_interface for ATA should succeed") .value .expect("ata interface should exist"); assert!( @@ -541,8 +541,7 @@ async fn test_d10_single_ata_markonly_lifecycle() { ); // Build AccountSpec for ATA decompression - let specs: Vec> = - vec![AccountSpec::Ata(Box::new(ata_interface))]; + let specs: Vec> = vec![AccountSpec::Ata(ata_interface)]; // Create decompression instructions let decompress_instructions = diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/integration_tests.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/integration_tests.rs index 5cb8b82b1c..ca589bb8f9 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/integration_tests.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/integration_tests.rs @@ -237,41 +237,30 @@ impl TestContext { expected_owner: Pubkey, expected_amount: u64, ) { - use light_client::interface::AccountInterface; - // Fetch token account interface let vault_interface = self .rpc - .get_token_account_interface(vault_pda, None) + .get_account_interface(vault_pda, None) .await - .expect("get_token_account_interface should succeed") + .expect("get_account_interface for vault should succeed") .value .expect("token account interface should exist"); assert!(vault_interface.is_cold(), "Token vault should be cold"); - // Deserialize token data let token = light_token_interface::state::Token::deserialize( &mut &vault_interface.account.data[..], ) .expect("Failed to parse Token"); - // Build variant using provided closure let vault_variant = build_variant(token); - // Get compressed context - let vault_compressed = vault_interface - .compressed() - .expect("cold vault must have compressed data"); - - // Convert to AccountInterface with compressed account - let vault_interface_for_pda = AccountInterface { - key: vault_interface.key, - account: vault_interface.account.clone(), - cold: Some(vault_compressed.account.clone()), - }; + assert!( + vault_interface.as_compressed_token().is_some(), + "cold vault must have compressed data" + ); - // Create PdaSpec and decompress - let vault_spec = PdaSpec::new(vault_interface_for_pda, vault_variant, self.program_id); + // Create PdaSpec and decompress. PdaSpec accepts ColdContext::Token directly. + let vault_spec = PdaSpec::new(vault_interface.clone(), vault_variant, self.program_id); let specs: Vec> = vec![AccountSpec::Pda(vault_spec)]; let decompress_instructions = diff --git a/sdk-tests/pinocchio-light-program-test/tests/stress_test.rs b/sdk-tests/pinocchio-light-program-test/tests/stress_test.rs index 3779e20a5a..bad59d6bcf 100644 --- a/sdk-tests/pinocchio-light-program-test/tests/stress_test.rs +++ b/sdk-tests/pinocchio-light-program-test/tests/stress_test.rs @@ -13,8 +13,8 @@ use light_batched_merkle_tree::{ initialize_state_tree::InitStateTreeAccountsInstructionData, }; use light_client::interface::{ - create_load_instructions, get_create_accounts_proof, AccountInterface, AccountSpec, - CreateAccountsProofInput, PdaSpec, + create_load_instructions, get_create_accounts_proof, AccountSpec, CreateAccountsProofInput, + PdaSpec, }; use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{ @@ -265,19 +265,19 @@ async fn decompress_all(ctx: &mut StressTestContext, pdas: &TestPdas, cached: &C let zc_spec = PdaSpec::new(zc_interface, zc_variant, ctx.program_id); // ATA + let ata = light_token::instruction::derive_token_ata(&pdas.ata_owner, &pdas.mint).0; let ata_interface = ctx .rpc - .get_associated_token_account_interface(&pdas.ata_owner, &pdas.mint, None) + .get_account_interface(&ata, None) .await .expect("failed to get ATA interface") .value .expect("ATA interface should exist"); assert!(ata_interface.is_cold(), "ATA should be cold"); - // Token PDA: Vault let vault_iface = ctx .rpc - .get_token_account_interface(&pdas.vault, None) + .get_account_interface(&pdas.vault, None) .await .expect("failed to get vault interface") .value @@ -293,31 +293,25 @@ async fn decompress_all(ctx: &mut StressTestContext, pdas: &TestPdas, cached: &C }, token_data: vault_token_data, }); - let vault_compressed = vault_iface - .compressed() - .expect("cold vault must have compressed data"); - let vault_interface = AccountInterface { - key: vault_iface.key, - account: vault_iface.account.clone(), - cold: Some(vault_compressed.account.clone()), - }; - let vault_spec = PdaSpec::new(vault_interface, vault_variant, ctx.program_id); + assert!( + vault_iface.as_compressed_token().is_some(), + "cold vault must have compressed data" + ); + let vault_spec = PdaSpec::new(vault_iface.clone(), vault_variant, ctx.program_id); - // Mint - let mint_iface = ctx + let mint_ai = ctx .rpc - .get_mint_interface(&pdas.mint, None) + .get_account_interface(&pdas.mint, None) .await .expect("failed to get mint interface") .value .expect("mint interface should exist"); - assert!(mint_iface.is_cold(), "Mint should be cold"); - let mint_ai = AccountInterface::from(mint_iface); + assert!(mint_ai.is_cold(), "Mint should be cold"); let specs: Vec> = vec![ AccountSpec::Pda(record_spec), AccountSpec::Pda(zc_spec), - AccountSpec::Ata(Box::new(ata_interface)), + AccountSpec::Ata(ata_interface), AccountSpec::Pda(vault_spec), AccountSpec::Mint(mint_ai), ]; diff --git a/sdk-tests/pinocchio-light-program-test/tests/test_create_all.rs b/sdk-tests/pinocchio-light-program-test/tests/test_create_all.rs index c0b9742f13..00660d3e58 100644 --- a/sdk-tests/pinocchio-light-program-test/tests/test_create_all.rs +++ b/sdk-tests/pinocchio-light-program-test/tests/test_create_all.rs @@ -2,8 +2,8 @@ mod shared; use light_account_pinocchio::token::TokenDataWithSeeds; use light_client::interface::{ - create_load_instructions, get_create_accounts_proof, AccountInterface, AccountSpec, - CreateAccountsProofInput, PdaSpec, + create_load_instructions, get_create_accounts_proof, AccountSpec, CreateAccountsProofInput, + PdaSpec, }; use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{program_test::TestRpc, Rpc}; @@ -245,17 +245,17 @@ async fn test_create_all_derive() { let zc_spec = PdaSpec::new(zc_interface, zc_variant, program_id); // ATA + let ata = light_token::instruction::derive_token_ata(&ata_owner, &mint_pda).0; let ata_interface = rpc - .get_associated_token_account_interface(&ata_owner, &mint_pda, None) + .get_account_interface(&ata, None) .await .expect("failed to get ATA interface") .value .expect("ATA interface should exist"); assert!(ata_interface.is_cold(), "ATA should be cold"); - // Token PDA: Vault let vault_iface = rpc - .get_token_account_interface(&vault, None) + .get_account_interface(&vault, None) .await .expect("failed to get vault interface") .value @@ -271,30 +271,24 @@ async fn test_create_all_derive() { }, token_data: vault_token_data, }); - let vault_compressed = vault_iface - .compressed() - .expect("cold vault must have compressed data"); - let vault_interface = AccountInterface { - key: vault_iface.key, - account: vault_iface.account.clone(), - cold: Some(vault_compressed.account.clone()), - }; - let vault_spec = PdaSpec::new(vault_interface, vault_variant, program_id); + assert!( + vault_iface.as_compressed_token().is_some(), + "cold vault must have compressed data" + ); + let vault_spec = PdaSpec::new(vault_iface.clone(), vault_variant, program_id); - // Mint - let mint_iface = rpc - .get_mint_interface(&mint_pda, None) + let mint_ai = rpc + .get_account_interface(&mint_pda, None) .await .expect("failed to get mint interface") .value .expect("mint interface should exist"); - assert!(mint_iface.is_cold(), "Mint should be cold"); - let mint_ai = AccountInterface::from(mint_iface); + assert!(mint_ai.is_cold(), "Mint should be cold"); let specs: Vec> = vec![ AccountSpec::Pda(record_spec), AccountSpec::Pda(zc_spec), - AccountSpec::Ata(Box::new(ata_interface)), + AccountSpec::Ata(ata_interface), AccountSpec::Pda(vault_spec), AccountSpec::Mint(mint_ai), ]; diff --git a/sdk-tests/pinocchio-light-program-test/tests/test_create_ata.rs b/sdk-tests/pinocchio-light-program-test/tests/test_create_ata.rs index 229bde9dac..5974e182bf 100644 --- a/sdk-tests/pinocchio-light-program-test/tests/test_create_ata.rs +++ b/sdk-tests/pinocchio-light-program-test/tests/test_create_ata.rs @@ -85,15 +85,14 @@ async fn test_create_ata_derive() { // PHASE 3: Decompress via create_load_instructions let ata_interface = rpc - .get_associated_token_account_interface(&ata_owner, &mint, None) + .get_account_interface(&ata, None) .await .expect("failed to get ATA interface") .value .expect("ATA interface should exist"); assert!(ata_interface.is_cold(), "ATA should be cold"); - let specs: Vec> = - vec![AccountSpec::Ata(Box::new(ata_interface))]; + let specs: Vec> = vec![AccountSpec::Ata(ata_interface)]; let ixs = create_load_instructions(&specs, payer.pubkey(), env.config_pda, &rpc) .await diff --git a/sdk-tests/pinocchio-light-program-test/tests/test_create_mint.rs b/sdk-tests/pinocchio-light-program-test/tests/test_create_mint.rs index 20233200c4..41bfcbb9ec 100644 --- a/sdk-tests/pinocchio-light-program-test/tests/test_create_mint.rs +++ b/sdk-tests/pinocchio-light-program-test/tests/test_create_mint.rs @@ -1,8 +1,7 @@ mod shared; use light_client::interface::{ - create_load_instructions, get_create_accounts_proof, AccountInterface, AccountSpec, - CreateAccountsProofInput, + create_load_instructions, get_create_accounts_proof, AccountSpec, CreateAccountsProofInput, }; use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{program_test::TestRpc, Rpc}; @@ -101,16 +100,14 @@ async fn test_create_mint_derive() { // PHASE 3: Decompress via create_load_instructions let mint_interface = rpc - .get_mint_interface(&mint_pda, None) + .get_account_interface(&mint_pda, None) .await .expect("failed to get mint interface") .value .expect("mint interface should exist"); assert!(mint_interface.is_cold(), "Mint should be cold"); - let mint_account_interface = AccountInterface::from(mint_interface); - let specs: Vec> = - vec![AccountSpec::Mint(mint_account_interface)]; + let specs: Vec> = vec![AccountSpec::Mint(mint_interface)]; let ixs = create_load_instructions(&specs, payer.pubkey(), env.config_pda, &rpc) .await diff --git a/sdk-tests/pinocchio-light-program-test/tests/test_create_token_vault.rs b/sdk-tests/pinocchio-light-program-test/tests/test_create_token_vault.rs index 3e83ba60fc..4118723797 100644 --- a/sdk-tests/pinocchio-light-program-test/tests/test_create_token_vault.rs +++ b/sdk-tests/pinocchio-light-program-test/tests/test_create_token_vault.rs @@ -1,7 +1,7 @@ mod shared; use light_client::interface::{ - create_load_instructions, get_create_accounts_proof, AccountInterface, AccountSpec, PdaSpec, + create_load_instructions, get_create_accounts_proof, AccountSpec, PdaSpec, }; use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{program_test::TestRpc, Rpc}; @@ -91,7 +91,7 @@ async fn test_create_token_vault_derive() { // PHASE 3: Decompress vault let vault_iface = rpc - .get_token_account_interface(&vault, None) + .get_account_interface(&vault, None) .await .expect("failed to get vault interface") .value @@ -108,15 +108,11 @@ async fn test_create_token_vault_derive() { }, token_data, }); - let vault_compressed = vault_iface - .compressed() - .expect("cold vault must have compressed data"); - let vault_interface = AccountInterface { - key: vault_iface.key, - account: vault_iface.account.clone(), - cold: Some(vault_compressed.account.clone()), - }; - let vault_spec = PdaSpec::new(vault_interface, vault_variant, program_id); + assert!( + vault_iface.as_compressed_token().is_some(), + "cold vault must have compressed data" + ); + let vault_spec = PdaSpec::new(vault_iface.clone(), vault_variant, program_id); let specs: Vec> = vec![AccountSpec::Pda(vault_spec)]; diff --git a/sdk-tests/pinocchio-light-program-test/tests/test_create_two_mints.rs b/sdk-tests/pinocchio-light-program-test/tests/test_create_two_mints.rs index ebb294fbd9..e6e5f121d3 100644 --- a/sdk-tests/pinocchio-light-program-test/tests/test_create_two_mints.rs +++ b/sdk-tests/pinocchio-light-program-test/tests/test_create_two_mints.rs @@ -1,8 +1,7 @@ mod shared; use light_client::interface::{ - create_load_instructions, get_create_accounts_proof, AccountInterface, AccountSpec, - CreateAccountsProofInput, + create_load_instructions, get_create_accounts_proof, AccountSpec, CreateAccountsProofInput, }; use light_compressible::rent::SLOTS_PER_EPOCH; use light_program_test::{program_test::TestRpc, Rpc}; @@ -128,23 +127,21 @@ async fn test_create_two_mints_derive() { shared::assert_onchain_closed(&mut rpc, &mint_b_pda, "MintB").await; // PHASE 3: Decompress both mints via create_load_instructions - let mint_a_interface = rpc - .get_mint_interface(&mint_a_pda, None) + let mint_a_ai = rpc + .get_account_interface(&mint_a_pda, None) .await .expect("failed to get mint A interface") .value .expect("mint A interface should exist"); - assert!(mint_a_interface.is_cold(), "Mint A should be cold"); - let mint_a_ai = AccountInterface::from(mint_a_interface); + assert!(mint_a_ai.is_cold(), "Mint A should be cold"); - let mint_b_interface = rpc - .get_mint_interface(&mint_b_pda, None) + let mint_b_ai = rpc + .get_account_interface(&mint_b_pda, None) .await .expect("failed to get mint B interface") .value .expect("mint B interface should exist"); - assert!(mint_b_interface.is_cold(), "Mint B should be cold"); - let mint_b_ai = AccountInterface::from(mint_b_interface); + assert!(mint_b_ai.is_cold(), "Mint B should be cold"); let specs: Vec> = vec![AccountSpec::Mint(mint_a_ai), AccountSpec::Mint(mint_b_ai)]; From 2bf1ecec6ad2dc71ce4375c509de5686fdcb723d Mon Sep 17 00:00:00 2001 From: Swenschaeferjohann Date: Thu, 12 Feb 2026 00:09:30 +0000 Subject: [PATCH 2/9] clean --- forester/tests/test_indexer_interface.rs | 6 ------ sdk-libs/client/src/indexer/mod.rs | 2 +- sdk-libs/client/src/indexer/types/interface.rs | 10 ---------- sdk-libs/client/src/indexer/types/mod.rs | 1 - sdk-libs/client/src/interface/load_accounts.rs | 5 +++-- 5 files changed, 4 insertions(+), 20 deletions(-) diff --git a/forester/tests/test_indexer_interface.rs b/forester/tests/test_indexer_interface.rs index 1675a42dd5..3b5927be72 100644 --- a/forester/tests/test_indexer_interface.rs +++ b/forester/tests/test_indexer_interface.rs @@ -3,12 +3,6 @@ /// This test creates various account types for testing the indexer's interface racing logic. /// After running, use `cargo xtask export-photon-test-data --test-name indexer_interface` /// to export transactions to the indexer's test snapshot directory. -/// -/// Scenarios covered: -/// 1. Light Token Mint - mint for token operations -/// 2. Token accounts (via light-token-client MintTo) - for getTokenAccountInterface -/// 3. Registered v2 address in batched address tree - for address tree verification -/// 4. Compressible token accounts - on-chain accounts that can be compressed use std::collections::HashMap; use anchor_lang::{AnchorDeserialize, Discriminator}; diff --git a/sdk-libs/client/src/indexer/mod.rs b/sdk-libs/client/src/indexer/mod.rs index cc3167459c..9b3c903566 100644 --- a/sdk-libs/client/src/indexer/mod.rs +++ b/sdk-libs/client/src/indexer/mod.rs @@ -20,7 +20,7 @@ pub use types::{ MerkleProofWithContext, NewAddressProofWithContext, NextTreeInfo, OutputQueueData, OwnerBalance, ProofOfLeaf, QueueElementsResult, QueueInfo, QueueInfoResult, RootIndex, SignatureWithMetadata, SolanaAccountData, StateMerkleTreeAccounts, StateQueueData, - TokenAccountInterface, TokenBalance, TreeInfo, ValidityProofWithContext, + TokenBalance, TreeInfo, ValidityProofWithContext, }; mod options; pub use options::*; diff --git a/sdk-libs/client/src/indexer/types/interface.rs b/sdk-libs/client/src/indexer/types/interface.rs index 61d8d109aa..bac5540dab 100644 --- a/sdk-libs/client/src/indexer/types/interface.rs +++ b/sdk-libs/client/src/indexer/types/interface.rs @@ -1,5 +1,4 @@ use light_compressed_account::TreeType; -use light_token::compat::TokenData; use solana_account::Account; use solana_pubkey::Pubkey; @@ -159,12 +158,3 @@ impl TryFrom<&photon_api::types::AccountInterface> for AccountInterface { convert_account_interface(ai) } } - -/// Token account interface with parsed token data -#[derive(Clone, Debug, PartialEq)] -pub struct TokenAccountInterface { - /// Base account interface data - pub account: AccountInterface, - /// Parsed token data (same as CompressedTokenAccount.token) - pub token: TokenData, -} diff --git a/sdk-libs/client/src/indexer/types/mod.rs b/sdk-libs/client/src/indexer/types/mod.rs index f91504c8e3..9ab14798f4 100644 --- a/sdk-libs/client/src/indexer/types/mod.rs +++ b/sdk-libs/client/src/indexer/types/mod.rs @@ -9,7 +9,6 @@ mod tree; pub use account::CompressedAccount; pub use interface::{ AccountInterface, ColdContext, ColdData, InterfaceTreeInfo, SolanaAccountData, - TokenAccountInterface, }; pub use proof::{ AccountProofInputs, AddressProofInputs, AddressWithTree, MerkleProof, MerkleProofWithContext, diff --git a/sdk-libs/client/src/interface/load_accounts.rs b/sdk-libs/client/src/interface/load_accounts.rs index 5754062dd6..fdc363fbe4 100644 --- a/sdk-libs/client/src/interface/load_accounts.rs +++ b/sdk-libs/client/src/interface/load_accounts.rs @@ -14,7 +14,8 @@ use light_sdk::instruction::PackedAccounts; use light_token::{ compat::AccountState, instruction::{ - derive_token_ata, CreateAssociatedTokenAccount, DecompressMint, LIGHT_TOKEN_PROGRAM_ID, + derive_token_ata, get_associated_token_address_and_bump, CreateAssociatedTokenAccount, + DecompressMint, LIGHT_TOKEN_PROGRAM_ID, }, }; use light_token_interface::{ @@ -311,7 +312,7 @@ impl<'a> AtaContext<'a> { })?; let wallet_owner = parsed.owner; let mint = parsed.mint; - let (derived_ata, bump) = derive_token_ata(&wallet_owner, &mint); + let (derived_ata, bump) = get_associated_token_address_and_bump(&wallet_owner, &mint); if derived_ata != iface.key { return Err(LoadAccountsError::MissingAtaContext { index, From 14a7b76316c60ded37d09be205d6311f36b25799 Mon Sep 17 00:00:00 2001 From: Swenschaeferjohann Date: Thu, 12 Feb 2026 00:21:05 +0000 Subject: [PATCH 3/9] use get_associated_token_address --- .../tests/compress_only/ata_decompress.rs | 6 ++--- .../tests/light_token/burn.rs | 6 +++-- .../tests/light_token/compress_and_close.rs | 4 ++-- .../tests/light_token/create_ata.rs | 24 +++++++++---------- .../tests/light_token/create_ata2.rs | 2 +- .../tests/light_token/functional_ata.rs | 4 ++-- .../tests/light_token/shared.rs | 4 ++-- .../compressed-token-test/tests/mint/burn.rs | 4 ++-- .../tests/mint/cmint_resize.rs | 8 +++---- .../tests/mint/edge_cases.rs | 2 +- .../tests/mint/failing.rs | 4 ++-- .../tests/mint/functional.rs | 12 +++++----- .../tests/mint/mint_to.rs | 4 ++-- .../tests/mint/random.rs | 2 +- .../tests/transfer2/compress_failing.rs | 8 +++---- .../tests/transfer2/compress_spl_failing.rs | 4 ++-- .../tests/transfer2/decompress_failing.rs | 4 ++-- .../no_system_program_cpi_failing.rs | 10 ++++---- .../tests/transfer2/shared.rs | 2 +- .../tests/transfer2/spl_ctoken.rs | 6 ++--- .../registry-test/tests/compressible.rs | 4 ++-- .../utils/src/actions/legacy/mint_action.rs | 4 ++-- .../utils/src/assert_create_token_account.rs | 4 ++-- .../client/src/interface/load_accounts.rs | 6 ++--- sdk-libs/token-sdk/src/instruction/mod.rs | 5 ++-- .../tests/stress_test.rs | 4 ++-- .../tests/test_create_all.rs | 4 ++-- .../tests/test_create_ata.rs | 2 +- .../tests/basic_test.rs | 2 +- .../tests/d10_token_accounts_test.rs | 10 ++++---- .../tests/d11_zero_copy_test.rs | 2 +- .../tests/shared.rs | 4 ++-- .../tests/stress_test.rs | 4 ++-- .../tests/test_create_all.rs | 4 ++-- .../tests/test_create_ata.rs | 2 +- .../tests/shared/mod.rs | 12 +++++----- .../tests/test_create_ata.rs | 8 +++---- .../tests/test_ctoken_mint_to.rs | 2 +- .../tests/test_transfer_checked.rs | 10 ++++---- .../tests/test_transfer_interface.rs | 18 +++++++------- .../tests/test_transfer_spl_ctoken.rs | 10 ++++---- .../tests/scenario_spl.rs | 4 ++-- .../tests/scenario_spl_restricted_ext.rs | 4 ++-- .../sdk-light-token-test/tests/shared.rs | 12 +++++----- .../tests/test_create_ata.rs | 8 +++---- .../tests/test_ctoken_mint_to.rs | 2 +- .../tests/test_transfer_checked.rs | 10 ++++---- .../tests/test_transfer_interface.rs | 18 +++++++------- .../tests/test_transfer_spl_ctoken.rs | 10 ++++---- .../tests/decompress_full_cpi.rs | 4 ++-- sdk-tests/sdk-token-test/tests/pda_ctoken.rs | 4 ++-- .../sdk-token-test/tests/test_4_transfer2.rs | 2 +- .../tests/test_compress_full_and_close.rs | 4 ++-- sdk-tests/single-ata-test/tests/test.rs | 2 +- .../tests/test_approve_revoke.rs | 14 +++++------ .../token-client-test/tests/test_transfer.rs | 10 ++++---- .../tests/test_transfer_checked.rs | 10 ++++---- .../tests/test_transfer_interface.rs | 10 ++++---- .../tests/test_wrap_unwrap.rs | 10 ++++---- 59 files changed, 190 insertions(+), 189 deletions(-) diff --git a/program-tests/compressed-token-test/tests/compress_only/ata_decompress.rs b/program-tests/compressed-token-test/tests/compress_only/ata_decompress.rs index 541d6f14d1..d1e020427e 100644 --- a/program-tests/compressed-token-test/tests/compress_only/ata_decompress.rs +++ b/program-tests/compressed-token-test/tests/compress_only/ata_decompress.rs @@ -20,8 +20,8 @@ use light_test_utils::{ }; use light_token::{ instruction::{ - derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount, CreateTokenAccount, - TransferFromSpl, + get_associated_token_address, CompressibleParams, CreateAssociatedTokenAccount, + CreateTokenAccount, TransferFromSpl, }, utils::get_associated_token_address_and_bump, }; @@ -347,7 +347,7 @@ async fn test_ata_decompress_to_different_ata_fails() { let mint2_pubkey = mint2_keypair.pubkey(); // Create ATA for same owner but different mint - let ata2_pubkey = derive_token_ata(&context.owner.pubkey(), &mint2_pubkey); + let ata2_pubkey = get_associated_token_address(&context.owner.pubkey(), &mint2_pubkey); let create_ata2_ix = CreateAssociatedTokenAccount::new( context.payer.pubkey(), diff --git a/program-tests/compressed-token-test/tests/light_token/burn.rs b/program-tests/compressed-token-test/tests/light_token/burn.rs index ccc43cbbdd..35c057324a 100644 --- a/program-tests/compressed-token-test/tests/light_token/burn.rs +++ b/program-tests/compressed-token-test/tests/light_token/burn.rs @@ -25,7 +25,9 @@ use light_test_utils::{ actions::legacy::instructions::mint_action::DecompressMintParams, assert_ctoken_burn::assert_ctoken_burn, }; -use light_token::instruction::{derive_token_ata, Burn, CreateAssociatedTokenAccount, MintTo}; +use light_token::instruction::{ + get_associated_token_address, Burn, CreateAssociatedTokenAccount, MintTo, +}; use super::shared::*; @@ -336,7 +338,7 @@ async fn setup_burn_test() -> BurnTestContext { let (mint_pda, _) = find_mint_address(&mint_seed.pubkey()); // Step 1: Create Light Token ATA for owner - let ctoken_ata = derive_token_ata(&owner_keypair.pubkey(), &mint_pda); + let ctoken_ata = get_associated_token_address(&owner_keypair.pubkey(), &mint_pda); let create_ata_ix = CreateAssociatedTokenAccount::new(payer.pubkey(), owner_keypair.pubkey(), mint_pda) diff --git a/program-tests/compressed-token-test/tests/light_token/compress_and_close.rs b/program-tests/compressed-token-test/tests/light_token/compress_and_close.rs index ca1d6e2616..088af681d8 100644 --- a/program-tests/compressed-token-test/tests/light_token/compress_and_close.rs +++ b/program-tests/compressed-token-test/tests/light_token/compress_and_close.rs @@ -163,8 +163,8 @@ async fn test_compress_and_close_owner_scenarios() { .await; // Set token balance on ATA - use light_token::instruction::derive_token_ata; - let ata_pubkey = derive_token_ata(&context.owner_keypair.pubkey(), &context.mint_pubkey); + use light_token::instruction::get_associated_token_address; + let ata_pubkey = get_associated_token_address(&context.owner_keypair.pubkey(), &context.mint_pubkey); let mut ata_account = context.rpc.get_account(ata_pubkey).await.unwrap().unwrap(); diff --git a/program-tests/compressed-token-test/tests/light_token/create_ata.rs b/program-tests/compressed-token-test/tests/light_token/create_ata.rs index 00862f3d2f..f3edeede33 100644 --- a/program-tests/compressed-token-test/tests/light_token/create_ata.rs +++ b/program-tests/compressed-token-test/tests/light_token/create_ata.rs @@ -184,7 +184,7 @@ async fn test_create_compressible_ata() { .unwrap(); // Verify ATA was created at the expected address - let expected_ata = derive_token_ata(&owner_and_mint, &owner_and_mint); + let expected_ata = get_associated_token_address(&owner_and_mint, &owner_and_mint); let account = context.rpc.get_account(expected_ata).await.unwrap(); assert!( account.is_some(), @@ -419,7 +419,7 @@ async fn test_create_ata_failing() { // Use different mint for this test context.mint_pubkey = solana_sdk::pubkey::Pubkey::new_unique(); - let ata_pubkey = derive_token_ata(&context.owner_keypair.pubkey(), &context.mint_pubkey); + let ata_pubkey = get_associated_token_address(&context.owner_keypair.pubkey(), &context.mint_pubkey); // Manually build instruction data with compress_to_account_pubkey (forbidden for ATAs) let compress_to_pubkey = CompressToPubkey { @@ -768,7 +768,7 @@ async fn test_create_ata_failing() { let owner = solana_sdk::pubkey::Pubkey::new_unique(); // Derive ATA address - let ata_pubkey = derive_token_ata(&owner, &mint_with_restricted_ext); + let ata_pubkey = get_associated_token_address(&owner, &mint_with_restricted_ext); // Build instruction data with compressible_config: None (non-compressible) let instruction_data = CreateAssociatedTokenAccountInstructionData { @@ -923,9 +923,9 @@ async fn test_ata_multiple_mints_same_owner() { assert_ne!(ata2, ata3, "ATA for mint2 and mint3 should be different"); // Verify each ATA is derived correctly for its mint - let expected_ata1 = derive_token_ata(&owner, &mint1); - let expected_ata2 = derive_token_ata(&owner, &mint2); - let expected_ata3 = derive_token_ata(&owner, &mint3); + let expected_ata1 = get_associated_token_address(&owner, &mint1); + let expected_ata2 = get_associated_token_address(&owner, &mint2); + let expected_ata3 = get_associated_token_address(&owner, &mint3); assert_eq!(ata1, expected_ata1, "ATA1 should match expected derivation"); assert_eq!(ata2, expected_ata2, "ATA2 should match expected derivation"); @@ -978,7 +978,7 @@ async fn test_ata_multiple_owners_same_mint() { .await .unwrap(); - let ata1 = derive_token_ata(&owner1, &mint); + let ata1 = get_associated_token_address(&owner1, &mint); // Assert ATA1 was created correctly assert_create_associated_token_account( @@ -1001,7 +1001,7 @@ async fn test_ata_multiple_owners_same_mint() { .await .unwrap(); - let ata2 = derive_token_ata(&owner2, &mint); + let ata2 = get_associated_token_address(&owner2, &mint); // Assert ATA2 was created correctly assert_create_associated_token_account( @@ -1024,7 +1024,7 @@ async fn test_ata_multiple_owners_same_mint() { .await .unwrap(); - let ata3 = derive_token_ata(&owner3, &mint); + let ata3 = get_associated_token_address(&owner3, &mint); // Assert ATA3 was created correctly assert_create_associated_token_account( @@ -1042,9 +1042,9 @@ async fn test_ata_multiple_owners_same_mint() { assert_ne!(ata2, ata3, "ATA for owner2 and owner3 should be different"); // Verify each ATA is derived correctly for its owner - let expected_ata1 = derive_token_ata(&owner1, &mint); - let expected_ata2 = derive_token_ata(&owner2, &mint); - let expected_ata3 = derive_token_ata(&owner3, &mint); + let expected_ata1 = get_associated_token_address(&owner1, &mint); + let expected_ata2 = get_associated_token_address(&owner2, &mint); + let expected_ata3 = get_associated_token_address(&owner3, &mint); assert_eq!(ata1, expected_ata1, "ATA1 should match expected derivation"); assert_eq!(ata2, expected_ata2, "ATA2 should match expected derivation"); diff --git a/program-tests/compressed-token-test/tests/light_token/create_ata2.rs b/program-tests/compressed-token-test/tests/light_token/create_ata2.rs index f547ccc448..33322bb441 100644 --- a/program-tests/compressed-token-test/tests/light_token/create_ata2.rs +++ b/program-tests/compressed-token-test/tests/light_token/create_ata2.rs @@ -11,7 +11,7 @@ async fn create_and_assert_ata2( let payer_pubkey = context.payer.pubkey(); let owner_pubkey = context.owner_keypair.pubkey(); - let ata_pubkey = derive_token_ata(&owner_pubkey, &context.mint_pubkey); + let ata_pubkey = get_associated_token_address(&owner_pubkey, &context.mint_pubkey); let create_ata_ix = if let Some(compressible) = compressible_data.as_ref() { let compressible_params = CompressibleParams { diff --git a/program-tests/compressed-token-test/tests/light_token/functional_ata.rs b/program-tests/compressed-token-test/tests/light_token/functional_ata.rs index fbb11d2d16..2b5fddda59 100644 --- a/program-tests/compressed-token-test/tests/light_token/functional_ata.rs +++ b/program-tests/compressed-token-test/tests/light_token/functional_ata.rs @@ -117,7 +117,7 @@ async fn test_associated_token_account_operations() { // Test closing compressible ATA let compressible_ata_pubkey = - derive_token_ata(&compressible_owner_pubkey, &context.mint_pubkey); + get_associated_token_address(&compressible_owner_pubkey, &context.mint_pubkey); // Create a separate destination account let destination = Keypair::new(); @@ -272,7 +272,7 @@ async fn test_create_ata_with_prefunded_lamports() { let owner_pubkey = context.owner_keypair.pubkey(); // Derive ATA address - let ata = derive_token_ata(&owner_pubkey, &context.mint_pubkey); + let ata = get_associated_token_address(&owner_pubkey, &context.mint_pubkey); // Pre-fund the ATA address with lamports (simulating attacker donation DoS attempt) let prefund_amount = 1_000; // 1000 lamports diff --git a/program-tests/compressed-token-test/tests/light_token/shared.rs b/program-tests/compressed-token-test/tests/light_token/shared.rs index d2085a66a3..28b7685803 100644 --- a/program-tests/compressed-token-test/tests/light_token/shared.rs +++ b/program-tests/compressed-token-test/tests/light_token/shared.rs @@ -16,7 +16,7 @@ pub use light_test_utils::{ Rpc, RpcError, }; pub use light_token::instruction::{ - derive_token_ata, Approve, CloseAccount, CompressibleParams, CreateAssociatedTokenAccount, + get_associated_token_address, Approve, CloseAccount, CompressibleParams, CreateAssociatedTokenAccount, CreateTokenAccount, Revoke, }; pub use serial_test::serial; @@ -384,7 +384,7 @@ pub async fn create_and_assert_ata( let owner_pubkey = context.owner_keypair.pubkey(); // Derive ATA address - let ata_pubkey = derive_token_ata(&owner_pubkey, &context.mint_pubkey); + let ata_pubkey = get_associated_token_address(&owner_pubkey, &context.mint_pubkey); // Build instruction based on whether it's compressible let create_ata_ix = if let Some(compressible) = compressible_data.as_ref() { diff --git a/program-tests/compressed-token-test/tests/mint/burn.rs b/program-tests/compressed-token-test/tests/mint/burn.rs index 18b9511808..a5dbae6336 100644 --- a/program-tests/compressed-token-test/tests/mint/burn.rs +++ b/program-tests/compressed-token-test/tests/mint/burn.rs @@ -4,7 +4,7 @@ use light_test_utils::{ actions::legacy::instructions::mint_action::DecompressMintParams, assert_ctoken_burn::assert_ctoken_burn, Rpc, }; -use light_token::instruction::{derive_token_ata, Burn, CreateAssociatedTokenAccount}; +use light_token::instruction::{get_associated_token_address, Burn, CreateAssociatedTokenAccount}; use light_token_interface::instructions::mint_action::Recipient; use serial_test::serial; use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; @@ -40,7 +40,7 @@ async fn setup_burn_test(mint_amount: u64) -> BurnTestContext { let (mint_pda, _) = find_mint_address(&mint_seed.pubkey()); // Step 1: Create Light Token ATA for owner first (needed before minting) - let ctoken_ata = derive_token_ata(&owner_keypair.pubkey(), &mint_pda); + let ctoken_ata = get_associated_token_address(&owner_keypair.pubkey(), &mint_pda); let create_ata_ix = CreateAssociatedTokenAccount::new(payer.pubkey(), owner_keypair.pubkey(), mint_pda) diff --git a/program-tests/compressed-token-test/tests/mint/cmint_resize.rs b/program-tests/compressed-token-test/tests/mint/cmint_resize.rs index e153976cb6..b35aba6767 100644 --- a/program-tests/compressed-token-test/tests/mint/cmint_resize.rs +++ b/program-tests/compressed-token-test/tests/mint/cmint_resize.rs @@ -27,7 +27,7 @@ use light_test_utils::{ Rpc, }; use light_token::instruction::{ - derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount, + get_associated_token_address, CompressibleParams, CreateAssociatedTokenAccount, }; use light_token_interface::{ instructions::extensions::token_metadata::TokenMetadataInstructionData, @@ -566,7 +566,7 @@ async fn test_cmint_all_operations() { }, // MintToCToken (decompressed recipient) MintActionType::MintToCToken { - account: derive_token_ata(&recipient.pubkey(), &spl_mint_pda), + account: get_associated_token_address(&recipient.pubkey(), &spl_mint_pda), amount: 2000, }, // UpdateMintAuthority @@ -967,7 +967,7 @@ async fn test_decompress_with_mint_to_ctoken() { write_top_up: 0, }, MintActionType::MintToCToken { - account: derive_token_ata(&recipient.pubkey(), &spl_mint_pda), + account: get_associated_token_address(&recipient.pubkey(), &spl_mint_pda), amount: 5000, }, ]; @@ -1103,7 +1103,7 @@ async fn test_decompress_with_all_operations() { }, // MintToCToken (decompressed recipient) MintActionType::MintToCToken { - account: derive_token_ata(&recipient.pubkey(), &spl_mint_pda), + account: get_associated_token_address(&recipient.pubkey(), &spl_mint_pda), amount: 2000, }, // UpdateMintAuthority diff --git a/program-tests/compressed-token-test/tests/mint/edge_cases.rs b/program-tests/compressed-token-test/tests/mint/edge_cases.rs index 577c7a06d0..08b25b6b08 100644 --- a/program-tests/compressed-token-test/tests/mint/edge_cases.rs +++ b/program-tests/compressed-token-test/tests/mint/edge_cases.rs @@ -182,7 +182,7 @@ async fn functional_all_in_one_instruction() { }, // 2. MintToCToken - mint to decompressed account MintActionType::MintToCToken { - account: light_token::instruction::derive_token_ata(&recipient.pubkey(), &spl_mint_pda), + account: light_token::instruction::get_associated_token_address(&recipient.pubkey(), &spl_mint_pda), amount: 2000u64, }, // 3. UpdateMintAuthority diff --git a/program-tests/compressed-token-test/tests/mint/failing.rs b/program-tests/compressed-token-test/tests/mint/failing.rs index e0ffb9f073..22ee732f0f 100644 --- a/program-tests/compressed-token-test/tests/mint/failing.rs +++ b/program-tests/compressed-token-test/tests/mint/failing.rs @@ -470,7 +470,7 @@ async fn functional_and_failing_tests() { .unwrap(); let recipient_ata = - light_token::instruction::derive_token_ata(&recipient2.pubkey(), &spl_mint_pda); + light_token::instruction::get_associated_token_address(&recipient2.pubkey(), &spl_mint_pda); // Try to mint with valid NEW authority (since we updated it) let result = light_test_utils::actions::mint_action_comprehensive( @@ -883,7 +883,7 @@ async fn test_mint_to_ctoken_max_top_up_exceeded() { .await .unwrap(); - let ctoken_ata = light_token::instruction::derive_token_ata(&recipient.pubkey(), &spl_mint_pda); + let ctoken_ata = light_token::instruction::get_associated_token_address(&recipient.pubkey(), &spl_mint_pda); // 3. Build MintToCToken instruction with max_top_up = 1 (too low) // Get current compressed mint state diff --git a/program-tests/compressed-token-test/tests/mint/functional.rs b/program-tests/compressed-token-test/tests/mint/functional.rs index 1695ecaa88..6fa144bcb6 100644 --- a/program-tests/compressed-token-test/tests/mint/functional.rs +++ b/program-tests/compressed-token-test/tests/mint/functional.rs @@ -28,7 +28,7 @@ use light_test_utils::{ Rpc, }; use light_token::instruction::{ - derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount, + get_associated_token_address, CompressibleParams, CreateAssociatedTokenAccount, }; use light_token_interface::{ instructions::{ @@ -217,7 +217,7 @@ async fn test_create_compressed_mint() { // 5. Decompress compressed tokens to ctokens // Create non-compressible token associated token account for decompression - let ctoken_ata_pubkey = derive_token_ata(&new_recipient, &spl_mint_pda); + let ctoken_ata_pubkey = get_associated_token_address(&new_recipient, &spl_mint_pda); let create_ata_instruction = CreateAssociatedTokenAccount { idempotent: false, payer: payer.pubkey(), @@ -418,11 +418,11 @@ async fn test_create_compressed_mint() { let compress_from_spl_recipient = Keypair::new(); // Create SPL token account for compression source - let compress_source_ata = derive_token_ata(&new_recipient, &spl_mint_pda); + let compress_source_ata = get_associated_token_address(&new_recipient, &spl_mint_pda); // This already exists from our previous test // Create non-compressible SPL token account for decompression destination - let decompress_dest_ata = derive_token_ata(&decompress_recipient.pubkey(), &spl_mint_pda); + let decompress_dest_ata = get_associated_token_address(&decompress_recipient.pubkey(), &spl_mint_pda); let create_decompress_ata_instruction = CreateAssociatedTokenAccount { idempotent: false, payer: payer.pubkey(), @@ -686,7 +686,7 @@ async fn test_ctoken_transfer() { let (spl_mint_pda, _) = find_mint_address(&mint_seed.pubkey()); // Create compressed token ATA for recipient - let recipient_ata = derive_token_ata(&recipient_keypair.pubkey(), &spl_mint_pda); + let recipient_ata = get_associated_token_address(&recipient_keypair.pubkey(), &spl_mint_pda); let compressible_params = CompressibleParams { compressible_config: rpc .test_accounts @@ -759,7 +759,7 @@ async fn test_ctoken_transfer() { // === CREATE SECOND RECIPIENT FOR TRANSFER TEST === let second_recipient_keypair = Keypair::new(); - let second_recipient_ata = derive_token_ata(&second_recipient_keypair.pubkey(), &spl_mint_pda); + let second_recipient_ata = get_associated_token_address(&second_recipient_keypair.pubkey(), &spl_mint_pda); rpc.airdrop_lamports(&second_recipient_keypair.pubkey(), 10_000_000_000) .await diff --git a/program-tests/compressed-token-test/tests/mint/mint_to.rs b/program-tests/compressed-token-test/tests/mint/mint_to.rs index 855fdbac79..9fcbdaafa9 100644 --- a/program-tests/compressed-token-test/tests/mint/mint_to.rs +++ b/program-tests/compressed-token-test/tests/mint/mint_to.rs @@ -4,7 +4,7 @@ use light_test_utils::{ actions::legacy::instructions::mint_action::DecompressMintParams, assert_ctoken_mint_to::assert_ctoken_mint_to, Rpc, }; -use light_token::instruction::{derive_token_ata, CreateAssociatedTokenAccount, MintTo}; +use light_token::instruction::{get_associated_token_address, CreateAssociatedTokenAccount, MintTo}; use serial_test::serial; use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; @@ -38,7 +38,7 @@ async fn setup_mint_to_test() -> MintToTestContext { let (mint_pda, _) = find_mint_address(&mint_seed.pubkey()); // Step 1: Create Light Token ATA for owner first - let ctoken_ata = derive_token_ata(&owner_keypair.pubkey(), &mint_pda); + let ctoken_ata = get_associated_token_address(&owner_keypair.pubkey(), &mint_pda); let create_ata_ix = CreateAssociatedTokenAccount::new(payer.pubkey(), owner_keypair.pubkey(), mint_pda) diff --git a/program-tests/compressed-token-test/tests/mint/random.rs b/program-tests/compressed-token-test/tests/mint/random.rs index 534e513e4b..a3cf0c62f7 100644 --- a/program-tests/compressed-token-test/tests/mint/random.rs +++ b/program-tests/compressed-token-test/tests/mint/random.rs @@ -143,7 +143,7 @@ async fn test_random_mint_action() { .await .unwrap(); - let ata = light_token::instruction::derive_token_ata(&recipient.pubkey(), &spl_mint_pda); + let ata = light_token::instruction::get_associated_token_address(&recipient.pubkey(), &spl_mint_pda); ctoken_atas.push(ata); } diff --git a/program-tests/compressed-token-test/tests/transfer2/compress_failing.rs b/program-tests/compressed-token-test/tests/transfer2/compress_failing.rs index 9cf8114e7c..fd09e1653f 100644 --- a/program-tests/compressed-token-test/tests/transfer2/compress_failing.rs +++ b/program-tests/compressed-token-test/tests/transfer2/compress_failing.rs @@ -49,7 +49,7 @@ use light_program_test::{ use light_sdk::instruction::PackedAccounts; use light_test_utils::RpcError; use light_token::{ - instruction::{derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount}, + instruction::{get_associated_token_address, CompressibleParams, CreateAssociatedTokenAccount}, ValidityProof, }; use light_token_interface::{ @@ -89,7 +89,7 @@ async fn setup_compression_test(token_amount: u64) -> Result Result<(), RpcError> { // Derive mint and ATA addresses let (mint, _) = find_mint_address(&mint_seed.pubkey()); - let ctoken_ata = derive_token_ata(&owner.pubkey(), &mint); + let ctoken_ata = get_associated_token_address(&owner.pubkey(), &mint); // Create compressible Light Token ATA with pre_pay_num_epochs = 0 (NO prepaid rent) // This means any write operation will require immediate rent top-up @@ -732,7 +732,7 @@ async fn test_compression_duplicate_account_no_double_charge_top_up() -> Result< // Derive mint and ATA addresses let (mint, _) = find_mint_address(&mint_seed.pubkey()); - let ctoken_ata = derive_token_ata(&owner.pubkey(), &mint); + let ctoken_ata = get_associated_token_address(&owner.pubkey(), &mint); // Create compressible Light Token ATA with pre_pay_num_epochs = 0 (NO prepaid rent) let compressible_params = CompressibleParams { diff --git a/program-tests/compressed-token-test/tests/transfer2/compress_spl_failing.rs b/program-tests/compressed-token-test/tests/transfer2/compress_spl_failing.rs index c640dc8990..639c189cc8 100644 --- a/program-tests/compressed-token-test/tests/transfer2/compress_spl_failing.rs +++ b/program-tests/compressed-token-test/tests/transfer2/compress_spl_failing.rs @@ -49,7 +49,7 @@ use light_test_utils::{ Rpc, RpcError, }; use light_token::{ - instruction::{derive_token_ata, CreateAssociatedTokenAccount}, + instruction::{get_associated_token_address, CreateAssociatedTokenAccount}, ValidityProof, }; use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; @@ -113,7 +113,7 @@ async fn setup_spl_compression_test( rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) .await?; - let ctoken_ata = derive_token_ata(&recipient.pubkey(), &mint); + let ctoken_ata = get_associated_token_address(&recipient.pubkey(), &mint); // Get output queue for compression (for system_accounts_offset calculation only) let output_queue = rpc diff --git a/program-tests/compressed-token-test/tests/transfer2/decompress_failing.rs b/program-tests/compressed-token-test/tests/transfer2/decompress_failing.rs index fe23a0348f..0303ac22e0 100644 --- a/program-tests/compressed-token-test/tests/transfer2/decompress_failing.rs +++ b/program-tests/compressed-token-test/tests/transfer2/decompress_failing.rs @@ -46,7 +46,7 @@ use light_program_test::{ use light_sdk::instruction::PackedAccounts; use light_test_utils::RpcError; use light_token::{ - instruction::{derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount}, + instruction::{get_associated_token_address, CompressibleParams, CreateAssociatedTokenAccount}, ValidityProof, }; use light_token_interface::{ @@ -89,7 +89,7 @@ async fn setup_decompression_test( // Derive mint and ATA addresses let (mint, _) = find_mint_address(&mint_seed.pubkey()); - let ctoken_ata = derive_token_ata(&owner.pubkey(), &mint); + let ctoken_ata = get_associated_token_address(&owner.pubkey(), &mint); // Create compressible Light Token ATA for owner (recipient of decompression) let compressible_params = CompressibleParams { diff --git a/program-tests/compressed-token-test/tests/transfer2/no_system_program_cpi_failing.rs b/program-tests/compressed-token-test/tests/transfer2/no_system_program_cpi_failing.rs index 7cbda9c766..e6e42b1c3a 100644 --- a/program-tests/compressed-token-test/tests/transfer2/no_system_program_cpi_failing.rs +++ b/program-tests/compressed-token-test/tests/transfer2/no_system_program_cpi_failing.rs @@ -50,7 +50,7 @@ use light_program_test::{ use light_sdk::instruction::PackedAccounts; use light_test_utils::{airdrop_lamports, RpcError}; use light_token::{ - instruction::{derive_token_ata, CreateAssociatedTokenAccount}, + instruction::{get_associated_token_address, CreateAssociatedTokenAccount}, ValidityProof, }; use light_token_interface::instructions::{mint_action::Recipient, transfer2::Compression}; @@ -102,8 +102,8 @@ async fn setup_no_system_program_cpi_test( // Create compressed mint seed let mint_seed = Keypair::new(); let (mint, _) = find_mint_address(&mint_seed.pubkey()); - let source_ata = derive_token_ata(&owner.pubkey(), &mint); - let recipient_ata = derive_token_ata(&recipient.pubkey(), &mint); + let source_ata = get_associated_token_address(&owner.pubkey(), &mint); + let recipient_ata = get_associated_token_address(&recipient.pubkey(), &mint); // Create Light Token ATA for owner (source) let instruction = CreateAssociatedTokenAccount::new(payer.pubkey(), owner.pubkey(), mint) @@ -709,8 +709,8 @@ async fn test_too_many_mints() { // Create new mint seed let mint_seed = Keypair::new(); let (mint, _) = find_mint_address(&mint_seed.pubkey()); - let source_ata = derive_token_ata(&context.owner.pubkey(), &mint); - let recipient_ata = derive_token_ata(&context.recipient.pubkey(), &mint); + let source_ata = get_associated_token_address(&context.owner.pubkey(), &mint); + let recipient_ata = get_associated_token_address(&context.recipient.pubkey(), &mint); // Create source ATA let instruction = diff --git a/program-tests/compressed-token-test/tests/transfer2/shared.rs b/program-tests/compressed-token-test/tests/transfer2/shared.rs index 0596494a1f..40592fcdcf 100644 --- a/program-tests/compressed-token-test/tests/transfer2/shared.rs +++ b/program-tests/compressed-token-test/tests/transfer2/shared.rs @@ -445,7 +445,7 @@ impl TestContext { .unwrap_or(&false); // Create Light Token ATA (compressible or regular based on requirements) - let ata = light_token::instruction::derive_token_ata(&signer.pubkey(), &mint); + let ata = light_token::instruction::get_associated_token_address(&signer.pubkey(), &mint); let create_ata_ix = if is_compressible { println!( diff --git a/program-tests/compressed-token-test/tests/transfer2/spl_ctoken.rs b/program-tests/compressed-token-test/tests/transfer2/spl_ctoken.rs index eb3d7f94b4..db8d0fb78c 100644 --- a/program-tests/compressed-token-test/tests/transfer2/spl_ctoken.rs +++ b/program-tests/compressed-token-test/tests/transfer2/spl_ctoken.rs @@ -22,7 +22,7 @@ pub use light_test_utils::{ Rpc, RpcError, }; pub use light_token::instruction::{ - derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount, + get_associated_token_address, CompressibleParams, CreateAssociatedTokenAccount, }; use light_token::ValidityProof; use light_token_interface::instructions::transfer2::{Compression, MultiTokenTransferOutputData}; @@ -78,7 +78,7 @@ async fn test_spl_to_ctoken_transfer() { rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) .await .unwrap(); - let associated_token_account = derive_token_ata(&recipient.pubkey(), &mint); + let associated_token_account = get_associated_token_address(&recipient.pubkey(), &mint); // Get initial SPL token balance let spl_account_data = rpc @@ -243,7 +243,7 @@ async fn test_failing_ctoken_to_spl_with_compress_and_close() { .unwrap(); // Create compressible token ATA for recipient (ATAs require compression_only=true) - let associated_token_account = derive_token_ata(&recipient.pubkey(), &mint); + let associated_token_account = get_associated_token_address(&recipient.pubkey(), &mint); let instruction = CreateAssociatedTokenAccount { idempotent: false, payer: payer.pubkey(), diff --git a/program-tests/registry-test/tests/compressible.rs b/program-tests/registry-test/tests/compressible.rs index ee65152021..19a120e618 100644 --- a/program-tests/registry-test/tests/compressible.rs +++ b/program-tests/registry-test/tests/compressible.rs @@ -37,7 +37,7 @@ use light_test_utils::{ Rpc, RpcError, }; use light_token::instruction::{ - derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount, MintTo, + get_associated_token_address, CompressibleParams, CreateAssociatedTokenAccount, MintTo, }; use solana_sdk::{ instruction::Instruction, @@ -850,7 +850,7 @@ async fn test_deprecate_compressible_config_with_valid_authority() -> Result<(), // Test 3: CAN claim rent with deprecated config let forester_keypair = rpc.test_accounts.protocol.forester.insecure_clone(); - let ata_pubkey = derive_token_ata(&token_account_keypair.pubkey(), &mint); + let ata_pubkey = get_associated_token_address(&token_account_keypair.pubkey(), &mint); // Claim from the account we created earlier let claim_result = claim_forester(&mut rpc, &[ata_pubkey], &forester_keypair, &payer).await; diff --git a/program-tests/utils/src/actions/legacy/mint_action.rs b/program-tests/utils/src/actions/legacy/mint_action.rs index 995eb5e5c1..26a1088d6b 100644 --- a/program-tests/utils/src/actions/legacy/mint_action.rs +++ b/program-tests/utils/src/actions/legacy/mint_action.rs @@ -105,13 +105,13 @@ pub async fn mint_action_comprehensive( } if !mint_to_decompressed_recipients.is_empty() { - use light_token::instruction::derive_token_ata; + use light_token::instruction::get_associated_token_address; let (spl_mint_pda, _) = find_mint_address(&mint_seed.pubkey()); for recipient in mint_to_decompressed_recipients { let recipient_pubkey = solana_pubkey::Pubkey::from(recipient.recipient.to_bytes()); - let ata_address = derive_token_ata(&recipient_pubkey, &spl_mint_pda); + let ata_address = get_associated_token_address(&recipient_pubkey, &spl_mint_pda); actions.push(MintActionType::MintToCToken { account: ata_address, diff --git a/program-tests/utils/src/assert_create_token_account.rs b/program-tests/utils/src/assert_create_token_account.rs index 0e5912c040..58e08e2846 100644 --- a/program-tests/utils/src/assert_create_token_account.rs +++ b/program-tests/utils/src/assert_create_token_account.rs @@ -1,7 +1,7 @@ use light_client::rpc::Rpc; use light_compressible::{compression_info::CompressionInfo, rent::RentConfig}; use light_program_test::LightProgramTest; -use light_token::instruction::derive_token_ata; +use light_token::instruction::get_associated_token_address; use light_token_interface::{ state::{ extensions::CompressibleExtension, token::Token, AccountState, ExtensionStruct, @@ -404,7 +404,7 @@ pub async fn assert_create_associated_token_account( expected_extensions: Option>, ) { // Derive the associated token account address - let ata_pubkey = derive_token_ata(&owner_pubkey, &mint_pubkey); + let ata_pubkey = get_associated_token_address(&owner_pubkey, &mint_pubkey); // Verify the account exists at the derived address let account = rpc diff --git a/sdk-libs/client/src/interface/load_accounts.rs b/sdk-libs/client/src/interface/load_accounts.rs index fdc363fbe4..3dbb2ba993 100644 --- a/sdk-libs/client/src/interface/load_accounts.rs +++ b/sdk-libs/client/src/interface/load_accounts.rs @@ -14,8 +14,8 @@ use light_sdk::instruction::PackedAccounts; use light_token::{ compat::AccountState, instruction::{ - derive_token_ata, get_associated_token_address_and_bump, CreateAssociatedTokenAccount, - DecompressMint, LIGHT_TOKEN_PROGRAM_ID, + get_associated_token_address, get_associated_token_address_and_bump, + CreateAssociatedTokenAccount, DecompressMint, LIGHT_TOKEN_PROGRAM_ID, }, }; use light_token_interface::{ @@ -379,7 +379,7 @@ fn build_transfer2( )?; let owner_idx = packed.insert_or_get_config(ctx.wallet_owner, true, false); - let ata_idx = packed.insert_or_get(derive_token_ata(&ctx.wallet_owner, &ctx.mint)); + let ata_idx = packed.insert_or_get(get_associated_token_address(&ctx.wallet_owner, &ctx.mint)); let mint_idx = packed.insert_or_get(token.mint); let delegate_idx = token.delegate.map(|d| packed.insert_or_get(d)).unwrap_or(0); diff --git a/sdk-libs/token-sdk/src/instruction/mod.rs b/sdk-libs/token-sdk/src/instruction/mod.rs index 7e1f7d2971..c8dc82b9e7 100644 --- a/sdk-libs/token-sdk/src/instruction/mod.rs +++ b/sdk-libs/token-sdk/src/instruction/mod.rs @@ -122,9 +122,8 @@ pub use close::{CloseAccount, CloseAccountCpi}; pub use compressible::{CompressibleParams, CompressibleParamsCpi}; pub use create::*; pub use create_ata::{ - derive_associated_token_account, derive_associated_token_account as derive_token_ata, - CreateAssociatedTokenAccount, CreateTokenAtaCpi as CreateAssociatedAccountCpi, - CreateTokenAtaCpi, + derive_associated_token_account, CreateAssociatedTokenAccount, + CreateTokenAtaCpi as CreateAssociatedAccountCpi, CreateTokenAtaCpi, }; pub use create_mint::*; pub use create_mints::*; diff --git a/sdk-tests/anchor-semi-manual-test/tests/stress_test.rs b/sdk-tests/anchor-semi-manual-test/tests/stress_test.rs index 483139ad7a..cfb3ec4030 100644 --- a/sdk-tests/anchor-semi-manual-test/tests/stress_test.rs +++ b/sdk-tests/anchor-semi-manual-test/tests/stress_test.rs @@ -112,7 +112,7 @@ async fn setup() -> (StressTestContext, TestPdas) { let (zc_record_pda, _) = Pubkey::find_program_address(&[RECORD_SEED, owner.as_ref()], &program_id); let ata_owner = payer.pubkey(); - let ata = light_token::instruction::derive_token_ata(&ata_owner, &ata_mint); + let ata = light_token::instruction::get_associated_token_address(&ata_owner, &ata_mint); let (vault_authority, _) = Pubkey::find_program_address(&[VAULT_AUTH_SEED], &program_id); let (vault, vault_bump) = Pubkey::find_program_address(&[VAULT_SEED, vault_mint.as_ref()], &program_id); @@ -276,7 +276,7 @@ async fn decompress_all(ctx: &mut StressTestContext, pdas: &TestPdas, cached: &C let zc_spec = PdaSpec::new(zc_interface, zc_variant, ctx.program_id); // ATA - let ata = light_token::instruction::derive_token_ata(&pdas.ata_owner, &pdas.ata_mint).0; + let ata = light_token::instruction::get_associated_token_address(&pdas.ata_owner, &pdas.ata_mint); let ata_interface = ctx .rpc .get_account_interface(&ata, None) diff --git a/sdk-tests/anchor-semi-manual-test/tests/test_create_all.rs b/sdk-tests/anchor-semi-manual-test/tests/test_create_all.rs index 3c66a5b816..03e8db6fdd 100644 --- a/sdk-tests/anchor-semi-manual-test/tests/test_create_all.rs +++ b/sdk-tests/anchor-semi-manual-test/tests/test_create_all.rs @@ -43,7 +43,7 @@ async fn test_create_all_derive() { // ATA let ata_owner = payer.pubkey(); - let ata = light_token::instruction::derive_token_ata(&ata_owner, &ata_mint); + let ata = light_token::instruction::get_associated_token_address(&ata_owner, &ata_mint); // Token vault let (vault_authority, _) = Pubkey::find_program_address(&[VAULT_AUTH_SEED], &program_id); @@ -252,7 +252,7 @@ async fn test_create_all_derive() { let zc_spec = PdaSpec::new(zc_interface, zc_variant, program_id); // ATA - let ata = light_token::instruction::derive_token_ata(&ata_owner, &ata_mint).0; + let ata = light_token::instruction::get_associated_token_address(&ata_owner, &ata_mint); let ata_interface = rpc .get_account_interface(&ata, None) .await diff --git a/sdk-tests/anchor-semi-manual-test/tests/test_create_ata.rs b/sdk-tests/anchor-semi-manual-test/tests/test_create_ata.rs index b799163785..267dacaed3 100644 --- a/sdk-tests/anchor-semi-manual-test/tests/test_create_ata.rs +++ b/sdk-tests/anchor-semi-manual-test/tests/test_create_ata.rs @@ -20,7 +20,7 @@ async fn test_create_ata_derive() { let (mint, _mint_seed) = shared::setup_create_mint(&mut rpc, &payer, payer.pubkey(), 9).await; let ata_owner = payer.pubkey(); - let ata = light_token::instruction::derive_token_ata(&ata_owner, &mint); + let ata = light_token::instruction::get_associated_token_address(&ata_owner, &mint); let proof_result = get_create_accounts_proof(&rpc, &program_id, vec![]) .await diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs index 82844bae47..35c57aa5d3 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/basic_test.rs @@ -425,7 +425,7 @@ async fn test_create_pdas_and_mint_auto() { }); let vault_spec = PdaSpec::new(vault_interface.clone(), vault_variant, program_id); - let ata = light_token::instruction::derive_token_ata(&payer.pubkey(), &mint_pda).0; + let ata = light_token::instruction::get_associated_token_address(&payer.pubkey(), &mint_pda); let ata_interface = rpc .get_account_interface(&ata, None) .await diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/d10_token_accounts_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/d10_token_accounts_test.rs index 1a85193aac..217784a236 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/d10_token_accounts_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/d10_token_accounts_test.rs @@ -183,7 +183,7 @@ async fn test_d10_single_ata() { let ata_owner = ctx.payer.pubkey(); // Derive the ATA address using Light Token SDK's derivation - let d10_single_ata = light_token::instruction::derive_token_ata(&ata_owner, &mint); + let d10_single_ata = light_token::instruction::get_associated_token_address(&ata_owner, &mint); // Get proof (no PDA accounts for ATA-only instruction) let proof_result = get_create_accounts_proof(&ctx.rpc, &ctx.program_id, vec![]) @@ -266,7 +266,7 @@ async fn test_d10_single_ata_idempotent_creation() { let ata_owner = ctx.payer.pubkey(); // Derive the ATA address - let d10_single_ata = light_token::instruction::derive_token_ata(&ata_owner, &mint); + let d10_single_ata = light_token::instruction::get_associated_token_address(&ata_owner, &mint); // Get proof for first creation let proof_result = get_create_accounts_proof(&ctx.rpc, &ctx.program_id, vec![]) @@ -381,7 +381,7 @@ async fn test_d10_single_ata_markonly() { let ata_owner = Keypair::new().pubkey(); // Derive the ATA address using Light Token SDK's derivation - let d10_markonly_ata = light_token::instruction::derive_token_ata(&ata_owner, &mint); + let d10_markonly_ata = light_token::instruction::get_associated_token_address(&ata_owner, &mint); // Get proof (no PDA accounts for ATA-only instruction) let proof_result = get_create_accounts_proof(&ctx.rpc, &ctx.program_id, vec![]) @@ -446,7 +446,7 @@ async fn test_d10_single_ata_markonly_lifecycle() { let ata_owner = ata_owner_keypair.pubkey(); // Derive the ATA address - let d10_markonly_ata = light_token::instruction::derive_token_ata(&ata_owner, &mint); + let d10_markonly_ata = light_token::instruction::get_associated_token_address(&ata_owner, &mint); // PHASE 1: Create ATA let proof_result = get_create_accounts_proof(&ctx.rpc, &ctx.program_id, vec![]) @@ -527,7 +527,7 @@ async fn test_d10_single_ata_markonly_lifecycle() { shared::assert_onchain_closed(&mut ctx.rpc, &d10_markonly_ata, "d10_markonly_ata").await; // PHASE 3: Decompress ATA using create_load_instructions - let ata = light_token::instruction::derive_token_ata(&ata_owner, &mint).0; + let ata = light_token::instruction::get_associated_token_address(&ata_owner, &mint); let ata_interface = ctx .rpc .get_account_interface(&ata, None) diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/d11_zero_copy_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/d11_zero_copy_test.rs index 7950bbee7a..0f7e34cd54 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/d11_zero_copy_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/d11_zero_copy_test.rs @@ -323,7 +323,7 @@ async fn test_d11_zc_with_ata() { // Derive PDAs let (zc_pda, _) = Pubkey::find_program_address(&[D11_ZC_ATA_RECORD_SEED, owner.as_ref()], &ctx.program_id); - let ata_pda = light_token::instruction::derive_token_ata(&ata_owner, &mint); + let ata_pda = light_token::instruction::get_associated_token_address(&ata_owner, &mint); // Get proof for PDA let proof_result = get_create_accounts_proof( diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/shared.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/shared.rs index 5ccd85b84f..95f62ca3dc 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/shared.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/shared.rs @@ -311,12 +311,12 @@ pub async fn setup_create_mint( } // Create ATAs for each recipient - use light_token::instruction::derive_token_ata; + use light_token::instruction::get_associated_token_address; let mut ata_pubkeys = Vec::with_capacity(recipients.len()); for (_amount, owner) in &recipients { - let ata_address = derive_token_ata(owner, &mint); + let ata_address = get_associated_token_address(owner, &mint); ata_pubkeys.push(ata_address); let create_ata = CreateAssociatedTokenAccount::new(payer.pubkey(), *owner, mint); diff --git a/sdk-tests/pinocchio-light-program-test/tests/stress_test.rs b/sdk-tests/pinocchio-light-program-test/tests/stress_test.rs index bad59d6bcf..bdb0ad7143 100644 --- a/sdk-tests/pinocchio-light-program-test/tests/stress_test.rs +++ b/sdk-tests/pinocchio-light-program-test/tests/stress_test.rs @@ -123,7 +123,7 @@ async fn setup() -> (StressTestContext, TestPdas) { // ATA (uses the mint we're creating) let ata_owner = payer.pubkey(); - let ata = light_token::instruction::derive_token_ata(&ata_owner, &mint_pda); + let ata = light_token::instruction::get_associated_token_address(&ata_owner, &mint_pda); // Create all accounts in one instruction let proof_result = get_create_accounts_proof( @@ -265,7 +265,7 @@ async fn decompress_all(ctx: &mut StressTestContext, pdas: &TestPdas, cached: &C let zc_spec = PdaSpec::new(zc_interface, zc_variant, ctx.program_id); // ATA - let ata = light_token::instruction::derive_token_ata(&pdas.ata_owner, &pdas.mint).0; + let ata = light_token::instruction::get_associated_token_address(&pdas.ata_owner, &pdas.mint); let ata_interface = ctx .rpc .get_account_interface(&ata, None) diff --git a/sdk-tests/pinocchio-light-program-test/tests/test_create_all.rs b/sdk-tests/pinocchio-light-program-test/tests/test_create_all.rs index 00660d3e58..3b9f732813 100644 --- a/sdk-tests/pinocchio-light-program-test/tests/test_create_all.rs +++ b/sdk-tests/pinocchio-light-program-test/tests/test_create_all.rs @@ -52,7 +52,7 @@ async fn test_create_all_derive() { // ATA (uses the mint we're creating) let ata_owner = payer.pubkey(); - let ata = light_token::instruction::derive_token_ata(&ata_owner, &mint_pda); + let ata = light_token::instruction::get_associated_token_address(&ata_owner, &mint_pda); // Build proof inputs for PDA accounts and the mint let proof_result = get_create_accounts_proof( @@ -245,7 +245,7 @@ async fn test_create_all_derive() { let zc_spec = PdaSpec::new(zc_interface, zc_variant, program_id); // ATA - let ata = light_token::instruction::derive_token_ata(&ata_owner, &mint_pda).0; + let ata = light_token::instruction::get_associated_token_address(&ata_owner, &mint_pda); let ata_interface = rpc .get_account_interface(&ata, None) .await diff --git a/sdk-tests/pinocchio-light-program-test/tests/test_create_ata.rs b/sdk-tests/pinocchio-light-program-test/tests/test_create_ata.rs index 5974e182bf..8f9b10511d 100644 --- a/sdk-tests/pinocchio-light-program-test/tests/test_create_ata.rs +++ b/sdk-tests/pinocchio-light-program-test/tests/test_create_ata.rs @@ -21,7 +21,7 @@ async fn test_create_ata_derive() { let (mint, _mint_seed) = shared::setup_create_mint(&mut rpc, &payer, payer.pubkey(), 9).await; let ata_owner = payer.pubkey(); - let ata = light_token::instruction::derive_token_ata(&ata_owner, &mint); + let ata = light_token::instruction::get_associated_token_address(&ata_owner, &mint); let proof_result = get_create_accounts_proof(&rpc, &program_id, vec![]) .await diff --git a/sdk-tests/sdk-light-token-pinocchio/tests/shared/mod.rs b/sdk-tests/sdk-light-token-pinocchio/tests/shared/mod.rs index 7e35bdd140..4c2760096e 100644 --- a/sdk-tests/sdk-light-token-pinocchio/tests/shared/mod.rs +++ b/sdk-tests/sdk-light-token-pinocchio/tests/shared/mod.rs @@ -97,12 +97,12 @@ pub async fn setup_create_mint( } // Create ATAs for each recipient - use light_token::instruction::derive_token_ata; + use light_token::instruction::get_associated_token_address; let mut ata_pubkeys = Vec::with_capacity(recipients.len()); for (_amount, owner) in &recipients { - let ata_address = derive_token_ata(owner, &mint); + let ata_address = get_associated_token_address(owner, &mint); ata_pubkeys.push(ata_address); let create_ata = CreateAssociatedTokenAccount::new(payer.pubkey(), *owner, mint); @@ -218,12 +218,12 @@ pub async fn setup_create_mint_with_freeze_authority( } // Create ATAs for each recipient - use light_token::instruction::derive_token_ata; + use light_token::instruction::get_associated_token_address; let mut ata_pubkeys = Vec::with_capacity(recipients.len()); for (_amount, owner) in &recipients { - let ata_address = derive_token_ata(owner, &mint); + let ata_address = get_associated_token_address(owner, &mint); ata_pubkeys.push(ata_address); let create_ata = CreateAssociatedTokenAccount::new(payer.pubkey(), *owner, mint); @@ -351,7 +351,7 @@ pub async fn setup_create_mint_with_compression_only( } // Create ATAs for each recipient with custom compression_only setting - use light_token::instruction::derive_token_ata; + use light_token::instruction::get_associated_token_address; let mut ata_pubkeys = Vec::with_capacity(recipients.len()); @@ -362,7 +362,7 @@ pub async fn setup_create_mint_with_compression_only( }; for (_amount, owner) in &recipients { - let ata_address = derive_token_ata(owner, &mint); + let ata_address = get_associated_token_address(owner, &mint); ata_pubkeys.push(ata_address); let create_ata = CreateAssociatedTokenAccount::new(payer.pubkey(), *owner, mint) 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..09f802b4b4 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 @@ -33,8 +33,8 @@ async fn test_create_ata_invoke() { // Derive the ATA address let owner = payer.pubkey(); - use light_token::instruction::derive_token_ata; - let ata_address = derive_token_ata(&owner, &mint_pda); + use light_token::instruction::get_associated_token_address; + let ata_address = get_associated_token_address(&owner, &mint_pda); // Build CreateAtaData (owner and mint are passed as accounts) let create_ata_data = CreateAtaData { @@ -118,8 +118,8 @@ async fn test_create_ata_invoke_signed() { .unwrap(); // Derive the ATA address for the PDA owner - use light_token::instruction::derive_token_ata; - let ata_address = derive_token_ata(&pda_owner, &mint_pda); + use light_token::instruction::get_associated_token_address; + let ata_address = get_associated_token_address(&pda_owner, &mint_pda); // Build CreateAtaData with PDA as owner (owner and mint are passed as accounts) let create_ata_data = CreateAtaData { diff --git a/sdk-tests/sdk-light-token-pinocchio/tests/test_ctoken_mint_to.rs b/sdk-tests/sdk-light-token-pinocchio/tests/test_ctoken_mint_to.rs index 7c29d030b1..85ee1af230 100644 --- a/sdk-tests/sdk-light-token-pinocchio/tests/test_ctoken_mint_to.rs +++ b/sdk-tests/sdk-light-token-pinocchio/tests/test_ctoken_mint_to.rs @@ -202,7 +202,7 @@ async fn test_ctoken_mint_to_invoke_signed() { // Step 2: Create ATA for payer (CreateMint now auto-decompresses) let ata = { - let ata_address = light_token::instruction::derive_token_ata(&payer.pubkey(), &mint_pda); + let ata_address = light_token::instruction::get_associated_token_address(&payer.pubkey(), &mint_pda); let create_ata = CreateAssociatedTokenAccount::new(payer.pubkey(), payer.pubkey(), mint_pda); let ata_instruction = create_ata.instruction().unwrap(); diff --git a/sdk-tests/sdk-light-token-pinocchio/tests/test_transfer_checked.rs b/sdk-tests/sdk-light-token-pinocchio/tests/test_transfer_checked.rs index 6282b96d4e..1d335c3f85 100644 --- a/sdk-tests/sdk-light-token-pinocchio/tests/test_transfer_checked.rs +++ b/sdk-tests/sdk-light-token-pinocchio/tests/test_transfer_checked.rs @@ -10,7 +10,7 @@ use light_test_utils::{ spl::{create_token_account, mint_spl_tokens}, }; use light_token::{ - instruction::{derive_token_ata, CreateAssociatedTokenAccount, TransferFromSpl}, + instruction::{get_associated_token_address, CreateAssociatedTokenAccount, TransferFromSpl}, spl_interface::{find_spl_interface_pda_with_index, CreateSplInterfacePda}, }; use light_token_interface::state::Token; @@ -99,8 +99,8 @@ async fn test_ctoken_transfer_checked_spl_mint() { let source_owner = payer.pubkey(); let dest_owner = Pubkey::new_unique(); - let source_ata = derive_token_ata(&source_owner, &mint); - let dest_ata = derive_token_ata(&dest_owner, &mint); + let source_ata = get_associated_token_address(&source_owner, &mint); + let dest_ata = get_associated_token_address(&dest_owner, &mint); let create_source_ata = CreateAssociatedTokenAccount::new(payer.pubkey(), source_owner, mint) .instruction() @@ -200,8 +200,8 @@ async fn test_ctoken_transfer_checked_t22_mint() { let source_owner = payer.pubkey(); let dest_owner = Pubkey::new_unique(); - let source_ata = derive_token_ata(&source_owner, &mint); - let dest_ata = derive_token_ata(&dest_owner, &mint); + let source_ata = get_associated_token_address(&source_owner, &mint); + let dest_ata = get_associated_token_address(&dest_owner, &mint); use light_token::instruction::CompressibleParams; let compressible_params = CompressibleParams { diff --git a/sdk-tests/sdk-light-token-pinocchio/tests/test_transfer_interface.rs b/sdk-tests/sdk-light-token-pinocchio/tests/test_transfer_interface.rs index 3a5d82b608..2637d14531 100644 --- a/sdk-tests/sdk-light-token-pinocchio/tests/test_transfer_interface.rs +++ b/sdk-tests/sdk-light-token-pinocchio/tests/test_transfer_interface.rs @@ -9,7 +9,7 @@ use light_test_utils::spl::{ create_mint_helper, create_token_2022_account, mint_spl_tokens, CREATE_MINT_HELPER_DECIMALS, }; use light_token::{ - instruction::{derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount}, + instruction::{get_associated_token_address, CompressibleParams, CreateAssociatedTokenAccount}, spl_interface::find_spl_interface_pda_with_index, }; use light_token_types::CPI_AUTHORITY_PDA; @@ -75,7 +75,7 @@ async fn test_transfer_interface_spl_to_ctoken_invoke() { rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) .await .unwrap(); - let ctoken_account = derive_token_ata(&recipient.pubkey(), &mint); + let ctoken_account = get_associated_token_address(&recipient.pubkey(), &mint); // Get token pool PDA let (spl_interface_pda, spl_interface_pda_bump) = @@ -168,7 +168,7 @@ async fn test_transfer_interface_ctoken_to_spl_invoke() { rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) .await .unwrap(); - let ctoken_account = derive_token_ata(&owner.pubkey(), &mint); + let ctoken_account = get_associated_token_address(&owner.pubkey(), &mint); // Fund Light Token via temporary SPL account let temp_spl_keypair = Keypair::new(); @@ -304,7 +304,7 @@ async fn test_transfer_interface_ctoken_to_ctoken_invoke() { rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) .await .unwrap(); - let sender_ctoken = derive_token_ata(&sender.pubkey(), &mint); + let sender_ctoken = get_associated_token_address(&sender.pubkey(), &mint); // Create recipient Light Token ATA let instruction = CreateAssociatedTokenAccount::new(payer.pubkey(), recipient.pubkey(), mint) @@ -313,7 +313,7 @@ async fn test_transfer_interface_ctoken_to_ctoken_invoke() { rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) .await .unwrap(); - let recipient_ctoken = derive_token_ata(&recipient.pubkey(), &mint); + let recipient_ctoken = get_associated_token_address(&recipient.pubkey(), &mint); // Fund sender Light Token via SPL let temp_spl_keypair = Keypair::new(); @@ -478,7 +478,7 @@ async fn test_transfer_interface_spl_to_ctoken_invoke_signed() { rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) .await .unwrap(); - let ctoken_account = derive_token_ata(&recipient.pubkey(), &mint); + let ctoken_account = get_associated_token_address(&recipient.pubkey(), &mint); let (spl_interface_pda, spl_interface_pda_bump) = find_spl_interface_pda_with_index(&mint, 0, false); @@ -569,7 +569,7 @@ async fn test_transfer_interface_ctoken_to_spl_invoke_signed() { .unwrap(); // Create Light Token ATA owned by PDA - let ctoken_account = derive_token_ata(&authority_pda, &mint); + let ctoken_account = get_associated_token_address(&authority_pda, &mint); let instruction = CreateAssociatedTokenAccount { idempotent: false, payer: payer.pubkey(), @@ -713,7 +713,7 @@ async fn test_transfer_interface_ctoken_to_ctoken_invoke_signed() { let transfer_amount = 5000u64; // Create source Light Token ATA owned by PDA - let source_ctoken = derive_token_ata(&authority_pda, &mint); + let source_ctoken = get_associated_token_address(&authority_pda, &mint); let instruction = CreateAssociatedTokenAccount { idempotent: false, payer: payer.pubkey(), @@ -739,7 +739,7 @@ async fn test_transfer_interface_ctoken_to_ctoken_invoke_signed() { rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) .await .unwrap(); - let dest_ctoken = derive_token_ata(&recipient.pubkey(), &mint); + let dest_ctoken = get_associated_token_address(&recipient.pubkey(), &mint); // Fund source Light Token via temporary SPL account let temp_spl_keypair = Keypair::new(); diff --git a/sdk-tests/sdk-light-token-pinocchio/tests/test_transfer_spl_ctoken.rs b/sdk-tests/sdk-light-token-pinocchio/tests/test_transfer_spl_ctoken.rs index 2197b6428c..aebd08db96 100644 --- a/sdk-tests/sdk-light-token-pinocchio/tests/test_transfer_spl_ctoken.rs +++ b/sdk-tests/sdk-light-token-pinocchio/tests/test_transfer_spl_ctoken.rs @@ -9,7 +9,7 @@ use light_test_utils::spl::{ create_mint_helper, create_token_2022_account, mint_spl_tokens, CREATE_MINT_HELPER_DECIMALS, }; use light_token::{ - instruction::{derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount}, + instruction::{get_associated_token_address, CompressibleParams, CreateAssociatedTokenAccount}, spl_interface::find_spl_interface_pda_with_index, }; use light_token_types::CPI_AUTHORITY_PDA; @@ -74,7 +74,7 @@ async fn test_spl_to_ctoken_invoke() { rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) .await .unwrap(); - let ctoken_account = derive_token_ata(&recipient.pubkey(), &mint); + let ctoken_account = get_associated_token_address(&recipient.pubkey(), &mint); // Get initial balances use spl_token_2022::pod::PodAccount; @@ -196,7 +196,7 @@ async fn test_ctoken_to_spl_invoke() { rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) .await .unwrap(); - let ctoken_account = derive_token_ata(&owner.pubkey(), &mint); + let ctoken_account = get_associated_token_address(&owner.pubkey(), &mint); // Create a temporary SPL account to mint tokens then transfer to ctoken let temp_spl_account_keypair = Keypair::new(); @@ -386,7 +386,7 @@ async fn test_spl_to_ctoken_invoke_signed() { rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) .await .unwrap(); - let ctoken_account = derive_token_ata(&recipient.pubkey(), &mint); + let ctoken_account = get_associated_token_address(&recipient.pubkey(), &mint); // Get SPL interface PDA let (spl_interface_pda, spl_interface_pda_bump) = @@ -485,7 +485,7 @@ async fn test_ctoken_to_spl_invoke_signed() { .unwrap(); // Create ctoken ATA owned by the PDA - let ctoken_account = derive_token_ata(&authority_pda, &mint); + let ctoken_account = get_associated_token_address(&authority_pda, &mint); let instruction = CreateAssociatedTokenAccount { idempotent: false, payer: payer.pubkey(), diff --git a/sdk-tests/sdk-light-token-test/tests/scenario_spl.rs b/sdk-tests/sdk-light-token-test/tests/scenario_spl.rs index 9efa3e2eda..d5a013797f 100644 --- a/sdk-tests/sdk-light-token-test/tests/scenario_spl.rs +++ b/sdk-tests/sdk-light-token-test/tests/scenario_spl.rs @@ -22,7 +22,7 @@ use light_program_test::{program_test::TestRpc, LightProgramTest, ProgramTestCon use light_test_utils::spl::{create_token_account, mint_spl_tokens}; use light_token::{ instruction::{ - derive_token_ata, CreateAssociatedTokenAccount, Decompress, Freeze, Thaw, TransferFromSpl, + get_associated_token_address, CreateAssociatedTokenAccount, Decompress, Freeze, Thaw, TransferFromSpl, }, spl_interface::{find_spl_interface_pda_with_index, CreateSplInterfacePda}, }; @@ -131,7 +131,7 @@ async fn test_spl_to_ctoken_scenario() { .await .unwrap(); - let ctoken_ata = derive_token_ata(&ctoken_recipient.pubkey(), &mint); + let ctoken_ata = get_associated_token_address(&ctoken_recipient.pubkey(), &mint); let create_ata_instruction = CreateAssociatedTokenAccount::new(payer.pubkey(), ctoken_recipient.pubkey(), mint) .instruction() diff --git a/sdk-tests/sdk-light-token-test/tests/scenario_spl_restricted_ext.rs b/sdk-tests/sdk-light-token-test/tests/scenario_spl_restricted_ext.rs index e9db3c3c41..7f77711b22 100644 --- a/sdk-tests/sdk-light-token-test/tests/scenario_spl_restricted_ext.rs +++ b/sdk-tests/sdk-light-token-test/tests/scenario_spl_restricted_ext.rs @@ -20,7 +20,7 @@ use light_test_utils::mint_2022::{ }; use light_token::{ instruction::{ - derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount, Decompress, + get_associated_token_address, CompressibleParams, CreateAssociatedTokenAccount, Decompress, TransferFromSpl, }, spl_interface::find_spl_interface_pda_with_index, @@ -75,7 +75,7 @@ async fn test_t22_restricted_to_ctoken_scenario() { .await .unwrap(); - let ctoken_ata = derive_token_ata(&ctoken_recipient.pubkey(), &mint); + let ctoken_ata = get_associated_token_address(&ctoken_recipient.pubkey(), &mint); let compressible_params = CompressibleParams { compression_only: true, ..Default::default() diff --git a/sdk-tests/sdk-light-token-test/tests/shared.rs b/sdk-tests/sdk-light-token-test/tests/shared.rs index d3046d227c..32d4af99dd 100644 --- a/sdk-tests/sdk-light-token-test/tests/shared.rs +++ b/sdk-tests/sdk-light-token-test/tests/shared.rs @@ -93,12 +93,12 @@ pub async fn setup_create_mint( } // Create ATAs for each recipient - use light_token::instruction::derive_token_ata; + use light_token::instruction::get_associated_token_address; let mut ata_pubkeys = Vec::with_capacity(recipients.len()); for (_amount, owner) in &recipients { - let ata_address = derive_token_ata(owner, &mint); + let ata_address = get_associated_token_address(owner, &mint); ata_pubkeys.push(ata_address); let create_ata = CreateAssociatedTokenAccount::new(payer.pubkey(), *owner, mint); @@ -214,12 +214,12 @@ pub async fn setup_create_mint_with_freeze_authority( } // Create ATAs for each recipient - use light_token::instruction::derive_token_ata; + use light_token::instruction::get_associated_token_address; let mut ata_pubkeys = Vec::with_capacity(recipients.len()); for (_amount, owner) in &recipients { - let ata_address = derive_token_ata(owner, &mint); + let ata_address = get_associated_token_address(owner, &mint); ata_pubkeys.push(ata_address); let create_ata = CreateAssociatedTokenAccount::new(payer.pubkey(), *owner, mint); @@ -347,7 +347,7 @@ pub async fn setup_create_mint_with_compression_only( } // Create ATAs for each recipient with custom compression_only setting - use light_token::instruction::derive_token_ata; + use light_token::instruction::get_associated_token_address; let mut ata_pubkeys = Vec::with_capacity(recipients.len()); @@ -358,7 +358,7 @@ pub async fn setup_create_mint_with_compression_only( }; for (_amount, owner) in &recipients { - let ata_address = derive_token_ata(owner, &mint); + let ata_address = get_associated_token_address(owner, &mint); ata_pubkeys.push(ata_address); let create_ata = CreateAssociatedTokenAccount::new(payer.pubkey(), *owner, mint) diff --git a/sdk-tests/sdk-light-token-test/tests/test_create_ata.rs b/sdk-tests/sdk-light-token-test/tests/test_create_ata.rs index fa283be243..771f06fd1a 100644 --- a/sdk-tests/sdk-light-token-test/tests/test_create_ata.rs +++ b/sdk-tests/sdk-light-token-test/tests/test_create_ata.rs @@ -33,8 +33,8 @@ async fn test_create_ata_invoke() { // Derive the ATA address let owner = payer.pubkey(); - use light_token::instruction::derive_token_ata; - let ata_address = derive_token_ata(&owner, &mint_pda); + use light_token::instruction::get_associated_token_address; + let ata_address = get_associated_token_address(&owner, &mint_pda); // Build CreateAtaData (owner and mint are passed as accounts) let create_ata_data = CreateAtaData { @@ -118,8 +118,8 @@ async fn test_create_ata_invoke_signed() { .unwrap(); // Derive the ATA address for the PDA owner - use light_token::instruction::derive_token_ata; - let ata_address = derive_token_ata(&pda_owner, &mint_pda); + use light_token::instruction::get_associated_token_address; + let ata_address = get_associated_token_address(&pda_owner, &mint_pda); // Build CreateAtaData with PDA as owner (owner and mint are passed as accounts) let create_ata_data = CreateAtaData { diff --git a/sdk-tests/sdk-light-token-test/tests/test_ctoken_mint_to.rs b/sdk-tests/sdk-light-token-test/tests/test_ctoken_mint_to.rs index 69b6e822e7..3278314ee9 100644 --- a/sdk-tests/sdk-light-token-test/tests/test_ctoken_mint_to.rs +++ b/sdk-tests/sdk-light-token-test/tests/test_ctoken_mint_to.rs @@ -196,7 +196,7 @@ async fn test_ctoken_mint_to_invoke_signed() { // Step 2: Create ATA for payer (CreateMint now auto-decompresses) let ata = { - let ata_address = light_token::instruction::derive_token_ata(&payer.pubkey(), &mint_pda); + let ata_address = light_token::instruction::get_associated_token_address(&payer.pubkey(), &mint_pda); let create_ata = CreateAssociatedTokenAccount::new(payer.pubkey(), payer.pubkey(), mint_pda); let ata_instruction = create_ata.instruction().unwrap(); diff --git a/sdk-tests/sdk-light-token-test/tests/test_transfer_checked.rs b/sdk-tests/sdk-light-token-test/tests/test_transfer_checked.rs index 9730f0eecb..c6b74dd7b8 100644 --- a/sdk-tests/sdk-light-token-test/tests/test_transfer_checked.rs +++ b/sdk-tests/sdk-light-token-test/tests/test_transfer_checked.rs @@ -10,7 +10,7 @@ use light_test_utils::{ spl::{create_token_account, mint_spl_tokens}, }; use light_token::{ - instruction::{derive_token_ata, CreateAssociatedTokenAccount, TransferFromSpl}, + instruction::{get_associated_token_address, CreateAssociatedTokenAccount, TransferFromSpl}, spl_interface::{find_spl_interface_pda_with_index, CreateSplInterfacePda}, }; use light_token_interface::state::Token; @@ -96,8 +96,8 @@ async fn test_ctoken_transfer_checked_spl_mint() { let source_owner = payer.pubkey(); let dest_owner = Pubkey::new_unique(); - let source_ata = derive_token_ata(&source_owner, &mint); - let dest_ata = derive_token_ata(&dest_owner, &mint); + let source_ata = get_associated_token_address(&source_owner, &mint); + let dest_ata = get_associated_token_address(&dest_owner, &mint); let create_source_ata = CreateAssociatedTokenAccount::new(payer.pubkey(), source_owner, mint) .instruction() @@ -194,8 +194,8 @@ async fn test_ctoken_transfer_checked_t22_mint() { let source_owner = payer.pubkey(); let dest_owner = Pubkey::new_unique(); - let source_ata = derive_token_ata(&source_owner, &mint); - let dest_ata = derive_token_ata(&dest_owner, &mint); + let source_ata = get_associated_token_address(&source_owner, &mint); + let dest_ata = get_associated_token_address(&dest_owner, &mint); use light_token::instruction::CompressibleParams; let compressible_params = CompressibleParams { diff --git a/sdk-tests/sdk-light-token-test/tests/test_transfer_interface.rs b/sdk-tests/sdk-light-token-test/tests/test_transfer_interface.rs index 7347ddf7f7..07ed334d46 100644 --- a/sdk-tests/sdk-light-token-test/tests/test_transfer_interface.rs +++ b/sdk-tests/sdk-light-token-test/tests/test_transfer_interface.rs @@ -9,7 +9,7 @@ use light_test_utils::spl::{ create_mint_helper, create_token_2022_account, mint_spl_tokens, CREATE_MINT_HELPER_DECIMALS, }; use light_token::{ - instruction::{derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount}, + instruction::{get_associated_token_address, CompressibleParams, CreateAssociatedTokenAccount}, spl_interface::find_spl_interface_pda_with_index, }; use light_token_types::CPI_AUTHORITY_PDA; @@ -74,7 +74,7 @@ async fn test_transfer_interface_spl_to_ctoken_invoke() { rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) .await .unwrap(); - let ctoken_account = derive_token_ata(&recipient.pubkey(), &mint); + let ctoken_account = get_associated_token_address(&recipient.pubkey(), &mint); // Get token pool PDA let (spl_interface_pda, spl_interface_pda_bump) = @@ -167,7 +167,7 @@ async fn test_transfer_interface_ctoken_to_spl_invoke() { rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) .await .unwrap(); - let ctoken_account = derive_token_ata(&owner.pubkey(), &mint); + let ctoken_account = get_associated_token_address(&owner.pubkey(), &mint); // Fund Light Token via temporary SPL account let temp_spl_keypair = Keypair::new(); @@ -303,7 +303,7 @@ async fn test_transfer_interface_ctoken_to_ctoken_invoke() { rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) .await .unwrap(); - let sender_ctoken = derive_token_ata(&sender.pubkey(), &mint); + let sender_ctoken = get_associated_token_address(&sender.pubkey(), &mint); // Create recipient Light Token ATA let instruction = CreateAssociatedTokenAccount::new(payer.pubkey(), recipient.pubkey(), mint) @@ -312,7 +312,7 @@ async fn test_transfer_interface_ctoken_to_ctoken_invoke() { rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) .await .unwrap(); - let recipient_ctoken = derive_token_ata(&recipient.pubkey(), &mint); + let recipient_ctoken = get_associated_token_address(&recipient.pubkey(), &mint); // Fund sender Light Token via SPL let temp_spl_keypair = Keypair::new(); @@ -477,7 +477,7 @@ async fn test_transfer_interface_spl_to_ctoken_invoke_signed() { rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) .await .unwrap(); - let ctoken_account = derive_token_ata(&recipient.pubkey(), &mint); + let ctoken_account = get_associated_token_address(&recipient.pubkey(), &mint); let (spl_interface_pda, spl_interface_pda_bump) = find_spl_interface_pda_with_index(&mint, 0, false); @@ -568,7 +568,7 @@ async fn test_transfer_interface_ctoken_to_spl_invoke_signed() { .unwrap(); // Create Light Token ATA owned by PDA - let ctoken_account = derive_token_ata(&authority_pda, &mint); + let ctoken_account = get_associated_token_address(&authority_pda, &mint); let instruction = CreateAssociatedTokenAccount { idempotent: false, payer: payer.pubkey(), @@ -712,7 +712,7 @@ async fn test_transfer_interface_ctoken_to_ctoken_invoke_signed() { let transfer_amount = 5000u64; // Create source Light Token ATA owned by PDA - let source_ctoken = derive_token_ata(&authority_pda, &mint); + let source_ctoken = get_associated_token_address(&authority_pda, &mint); let instruction = CreateAssociatedTokenAccount { idempotent: false, payer: payer.pubkey(), @@ -738,7 +738,7 @@ async fn test_transfer_interface_ctoken_to_ctoken_invoke_signed() { rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) .await .unwrap(); - let dest_ctoken = derive_token_ata(&recipient.pubkey(), &mint); + let dest_ctoken = get_associated_token_address(&recipient.pubkey(), &mint); // Fund source Light Token via temporary SPL account let temp_spl_keypair = Keypair::new(); diff --git a/sdk-tests/sdk-light-token-test/tests/test_transfer_spl_ctoken.rs b/sdk-tests/sdk-light-token-test/tests/test_transfer_spl_ctoken.rs index e07f125e58..93d02cc458 100644 --- a/sdk-tests/sdk-light-token-test/tests/test_transfer_spl_ctoken.rs +++ b/sdk-tests/sdk-light-token-test/tests/test_transfer_spl_ctoken.rs @@ -9,7 +9,7 @@ use light_test_utils::spl::{ create_mint_helper, create_token_2022_account, mint_spl_tokens, CREATE_MINT_HELPER_DECIMALS, }; use light_token::{ - instruction::{derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount}, + instruction::{get_associated_token_address, CompressibleParams, CreateAssociatedTokenAccount}, spl_interface::find_spl_interface_pda_with_index, }; use light_token_types::CPI_AUTHORITY_PDA; @@ -73,7 +73,7 @@ async fn test_spl_to_ctoken_invoke() { rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) .await .unwrap(); - let ctoken_account = derive_token_ata(&recipient.pubkey(), &mint); + let ctoken_account = get_associated_token_address(&recipient.pubkey(), &mint); // Get initial balances use spl_token_2022::pod::PodAccount; @@ -195,7 +195,7 @@ async fn test_ctoken_to_spl_invoke() { rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) .await .unwrap(); - let ctoken_account = derive_token_ata(&owner.pubkey(), &mint); + let ctoken_account = get_associated_token_address(&owner.pubkey(), &mint); // Create a temporary SPL account to mint tokens then transfer to ctoken let temp_spl_account_keypair = Keypair::new(); @@ -385,7 +385,7 @@ async fn test_spl_to_ctoken_invoke_signed() { rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) .await .unwrap(); - let ctoken_account = derive_token_ata(&recipient.pubkey(), &mint); + let ctoken_account = get_associated_token_address(&recipient.pubkey(), &mint); // Get SPL interface PDA let (spl_interface_pda, spl_interface_pda_bump) = @@ -484,7 +484,7 @@ async fn test_ctoken_to_spl_invoke_signed() { .unwrap(); // Create ctoken ATA owned by the PDA - let ctoken_account = derive_token_ata(&authority_pda, &mint); + let ctoken_account = get_associated_token_address(&authority_pda, &mint); let instruction = CreateAssociatedTokenAccount { idempotent: false, payer: payer.pubkey(), diff --git a/sdk-tests/sdk-token-test/tests/decompress_full_cpi.rs b/sdk-tests/sdk-token-test/tests/decompress_full_cpi.rs index 9cc64620c9..8ad9cfcfe6 100644 --- a/sdk-tests/sdk-token-test/tests/decompress_full_cpi.rs +++ b/sdk-tests/sdk-token-test/tests/decompress_full_cpi.rs @@ -57,7 +57,7 @@ async fn setup_decompress_full_test(num_inputs: usize) -> (LightProgramTest, Tes .unwrap(); use light_token::instruction::{ - derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount, + get_associated_token_address, CompressibleParams, CreateAssociatedTokenAccount, }; let mut destination_accounts = Vec::with_capacity(num_inputs); @@ -73,7 +73,7 @@ async fn setup_decompress_full_test(num_inputs: usize) -> (LightProgramTest, Tes additional_owner.pubkey() }; - let destination_account = derive_token_ata(&destination_owner, &mint_pubkey); + let destination_account = get_associated_token_address(&destination_owner, &mint_pubkey); let compressible_params = CompressibleParams { compressible_config: rpc diff --git a/sdk-tests/sdk-token-test/tests/pda_ctoken.rs b/sdk-tests/sdk-token-test/tests/pda_ctoken.rs index 990743a5a1..34178d2820 100644 --- a/sdk-tests/sdk-token-test/tests/pda_ctoken.rs +++ b/sdk-tests/sdk-token-test/tests/pda_ctoken.rs @@ -10,7 +10,7 @@ use light_compressed_token_sdk::compressed_token::create_compressed_mint::{ use light_program_test::{LightProgramTest, ProgramTestConfig, Rpc, RpcError}; use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; use light_token::instruction::{ - derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount, + get_associated_token_address, CompressibleParams, CreateAssociatedTokenAccount, }; use light_token_interface::{ instructions::{ @@ -200,7 +200,7 @@ pub async fn create_mint( let (mint, mint_bump) = find_mint_address(&mint_seed.pubkey()); // Create compressed token associated token account for the mint authority - let token_account = derive_token_ata(&mint_authority.pubkey(), &mint); + let token_account = get_associated_token_address(&mint_authority.pubkey(), &mint); println!("Created token_account (ATA): {:?}", token_account); let compressible_params = CompressibleParams { diff --git a/sdk-tests/sdk-token-test/tests/test_4_transfer2.rs b/sdk-tests/sdk-token-test/tests/test_4_transfer2.rs index 488a63a35e..3a0902f447 100644 --- a/sdk-tests/sdk-token-test/tests/test_4_transfer2.rs +++ b/sdk-tests/sdk-token-test/tests/test_4_transfer2.rs @@ -97,7 +97,7 @@ async fn create_compressed_mints_and_tokens( // Create associated token account for mint1 decompression let token_account1_pubkey = - light_token::instruction::derive_token_ata(&payer.pubkey(), &mint1_pda); + light_token::instruction::get_associated_token_address(&payer.pubkey(), &mint1_pda); let create_ata_instruction = CreateAssociatedTokenAccount::new(payer.pubkey(), payer.pubkey(), mint1_pda) .instruction() diff --git a/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs b/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs index c2d64eeaad..f0ba5551d0 100644 --- a/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs +++ b/sdk-tests/sdk-token-test/tests/test_compress_full_and_close.rs @@ -10,7 +10,7 @@ use light_program_test::{Indexer, LightProgramTest, ProgramTestConfig, Rpc}; use light_sdk::instruction::{PackedAccounts, SystemAccountMetaConfig}; use light_test_utils::actions::legacy::instructions::transfer2::create_decompress_instruction; use light_token::instruction::{ - config_pda, derive_token_ata, rent_sponsor_pda, CompressibleParams, + config_pda, get_associated_token_address, rent_sponsor_pda, CompressibleParams, CreateAssociatedTokenAccount, }; use light_token_interface::{ @@ -176,7 +176,7 @@ async fn test_compress_full_and_close() { println!("✅ Minted {} compressed tokens to recipient", mint_amount); // Step 4: Create compressible associated token account for decompression - let ctoken_ata_pubkey = derive_token_ata(&recipient, &mint_pda); + let ctoken_ata_pubkey = get_associated_token_address(&recipient, &mint_pda); let compressible_params = CompressibleParams { token_account_version: TokenDataVersion::ShaFlat, pre_pay_num_epochs: 2, diff --git a/sdk-tests/single-ata-test/tests/test.rs b/sdk-tests/single-ata-test/tests/test.rs index 0196b17a23..8e0fbd8d36 100644 --- a/sdk-tests/single-ata-test/tests/test.rs +++ b/sdk-tests/single-ata-test/tests/test.rs @@ -122,7 +122,7 @@ async fn test_create_single_ata() { let ata_owner = payer.pubkey(); // Derive the ATA address using Light Token SDK's derivation - let ata = light_token::instruction::derive_token_ata(&ata_owner, &mint); + let ata = light_token::instruction::get_associated_token_address(&ata_owner, &mint); // Get proof (no PDA accounts for ATA-only instruction) let proof_result = get_create_accounts_proof(&rpc, &program_id, vec![]) diff --git a/sdk-tests/token-client-test/tests/test_approve_revoke.rs b/sdk-tests/token-client-test/tests/test_approve_revoke.rs index d5269a4061..9671b4ac36 100644 --- a/sdk-tests/token-client-test/tests/test_approve_revoke.rs +++ b/sdk-tests/token-client-test/tests/test_approve_revoke.rs @@ -3,7 +3,7 @@ use borsh::BorshDeserialize; use light_client::rpc::Rpc; use light_program_test::{LightProgramTest, ProgramTestConfig}; -use light_token::instruction::derive_token_ata; +use light_token::instruction::get_associated_token_address; use light_token_client::actions::{Approve, CreateAta, CreateMint, MintTo, Revoke, Transfer}; use light_token_interface::state::{AccountState, Token}; use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; @@ -50,7 +50,7 @@ async fn test_approve_basic() { // Create ATA for payer let owner = payer.pubkey(); - let token_account = derive_token_ata(&owner, &mint); + let token_account = get_associated_token_address(&owner, &mint); CreateAta { mint, @@ -120,7 +120,7 @@ async fn test_approve_with_separate_owner() { // Create ATA for a different owner let owner = Keypair::new(); - let token_account = derive_token_ata(&owner.pubkey(), &mint); + let token_account = get_associated_token_address(&owner.pubkey(), &mint); CreateAta { mint, @@ -190,7 +190,7 @@ async fn test_revoke_basic() { // Create ATA let owner = payer.pubkey(); - let token_account = derive_token_ata(&owner, &mint); + let token_account = get_associated_token_address(&owner, &mint); CreateAta { mint, @@ -275,7 +275,7 @@ async fn test_revoke_with_separate_owner() { // Create ATA for a different owner let owner = Keypair::new(); - let token_account = derive_token_ata(&owner.pubkey(), &mint); + let token_account = get_associated_token_address(&owner.pubkey(), &mint); CreateAta { mint, @@ -345,7 +345,7 @@ async fn test_approve_and_delegate_transfer() { // Create source ATA let source_owner = payer.pubkey(); - let source_ata = derive_token_ata(&source_owner, &mint); + let source_ata = get_associated_token_address(&source_owner, &mint); CreateAta { mint, @@ -358,7 +358,7 @@ async fn test_approve_and_delegate_transfer() { // Create destination ATA let dest_owner = Pubkey::new_unique(); - let dest_ata = derive_token_ata(&dest_owner, &mint); + let dest_ata = get_associated_token_address(&dest_owner, &mint); CreateAta { mint, diff --git a/sdk-tests/token-client-test/tests/test_transfer.rs b/sdk-tests/token-client-test/tests/test_transfer.rs index 7a0bda6f27..6e905070d8 100644 --- a/sdk-tests/token-client-test/tests/test_transfer.rs +++ b/sdk-tests/token-client-test/tests/test_transfer.rs @@ -3,7 +3,7 @@ use borsh::BorshDeserialize; use light_client::rpc::Rpc; use light_program_test::{LightProgramTest, ProgramTestConfig}; -use light_token::instruction::derive_token_ata; +use light_token::instruction::get_associated_token_address; use light_token_client::actions::{CreateAta, CreateMint, MintTo, Transfer}; use light_token_interface::state::{AccountState, Token}; use solana_sdk::{pubkey::Pubkey, signer::Signer}; @@ -52,8 +52,8 @@ async fn test_transfer_basic() { let source_owner = payer.pubkey(); let dest_owner = Pubkey::new_unique(); - let source_ata = derive_token_ata(&source_owner, &mint); - let dest_ata = derive_token_ata(&dest_owner, &mint); + let source_ata = get_associated_token_address(&source_owner, &mint); + let dest_ata = get_associated_token_address(&dest_owner, &mint); CreateAta { mint, @@ -137,8 +137,8 @@ async fn test_transfer_full_balance() { let source_owner = payer.pubkey(); let dest_owner = Pubkey::new_unique(); - let source_ata = derive_token_ata(&source_owner, &mint); - let dest_ata = derive_token_ata(&dest_owner, &mint); + let source_ata = get_associated_token_address(&source_owner, &mint); + let dest_ata = get_associated_token_address(&dest_owner, &mint); CreateAta { mint, diff --git a/sdk-tests/token-client-test/tests/test_transfer_checked.rs b/sdk-tests/token-client-test/tests/test_transfer_checked.rs index 02273ea4de..1bfae10b55 100644 --- a/sdk-tests/token-client-test/tests/test_transfer_checked.rs +++ b/sdk-tests/token-client-test/tests/test_transfer_checked.rs @@ -3,7 +3,7 @@ use borsh::BorshDeserialize; use light_client::rpc::Rpc; use light_program_test::{LightProgramTest, ProgramTestConfig}; -use light_token::instruction::derive_token_ata; +use light_token::instruction::get_associated_token_address; use light_token_client::actions::{CreateAta, CreateMint, MintTo, TransferChecked}; use light_token_interface::state::{AccountState, Token}; use solana_sdk::{pubkey::Pubkey, signer::Signer}; @@ -52,8 +52,8 @@ async fn test_transfer_checked_basic() { let source_owner = payer.pubkey(); let dest_owner = Pubkey::new_unique(); - let source_ata = derive_token_ata(&source_owner, &mint); - let dest_ata = derive_token_ata(&dest_owner, &mint); + let source_ata = get_associated_token_address(&source_owner, &mint); + let dest_ata = get_associated_token_address(&dest_owner, &mint); CreateAta { mint, @@ -140,8 +140,8 @@ async fn test_transfer_checked_different_decimals() { let source_owner = payer.pubkey(); let dest_owner = Pubkey::new_unique(); - let source_ata = derive_token_ata(&source_owner, &mint); - let dest_ata = derive_token_ata(&dest_owner, &mint); + let source_ata = get_associated_token_address(&source_owner, &mint); + let dest_ata = get_associated_token_address(&dest_owner, &mint); CreateAta { mint, diff --git a/sdk-tests/token-client-test/tests/test_transfer_interface.rs b/sdk-tests/token-client-test/tests/test_transfer_interface.rs index 3061c8ff0d..fbdcbaef33 100644 --- a/sdk-tests/token-client-test/tests/test_transfer_interface.rs +++ b/sdk-tests/token-client-test/tests/test_transfer_interface.rs @@ -3,7 +3,7 @@ use borsh::BorshDeserialize; use light_client::rpc::Rpc; use light_program_test::{LightProgramTest, ProgramTestConfig}; -use light_token::instruction::derive_token_ata; +use light_token::instruction::get_associated_token_address; use light_token_client::actions::{CreateAta, CreateMint, MintTo, TransferInterface}; use light_token_interface::state::{AccountState, Token}; use solana_sdk::{pubkey::Pubkey, signer::Signer}; @@ -52,8 +52,8 @@ async fn test_transfer_interface_light_to_light() { let source_owner = payer.pubkey(); let dest_owner = Pubkey::new_unique(); - let source_ata = derive_token_ata(&source_owner, &mint); - let dest_ata = derive_token_ata(&dest_owner, &mint); + let source_ata = get_associated_token_address(&source_owner, &mint); + let dest_ata = get_associated_token_address(&dest_owner, &mint); CreateAta { mint, @@ -141,8 +141,8 @@ async fn test_transfer_interface_multiple_transfers() { let source_owner = payer.pubkey(); let dest_owner = Pubkey::new_unique(); - let source_ata = derive_token_ata(&source_owner, &mint); - let dest_ata = derive_token_ata(&dest_owner, &mint); + let source_ata = get_associated_token_address(&source_owner, &mint); + let dest_ata = get_associated_token_address(&dest_owner, &mint); CreateAta { mint, diff --git a/sdk-tests/token-client-test/tests/test_wrap_unwrap.rs b/sdk-tests/token-client-test/tests/test_wrap_unwrap.rs index 64eca8894d..5a134c459b 100644 --- a/sdk-tests/token-client-test/tests/test_wrap_unwrap.rs +++ b/sdk-tests/token-client-test/tests/test_wrap_unwrap.rs @@ -10,7 +10,7 @@ use light_program_test::{LightProgramTest, ProgramTestConfig}; use light_test_utils::spl::{ create_mint_helper, create_token_account, mint_spl_tokens, CREATE_MINT_HELPER_DECIMALS, }; -use light_token::instruction::derive_token_ata; +use light_token::instruction::get_associated_token_address; use light_token_client::actions::{CreateAta, Unwrap, Wrap}; use light_token_interface::state::Token; use solana_sdk::{program_pack::Pack, signature::Keypair, signer::Signer}; @@ -50,7 +50,7 @@ async fn test_wrap_basic() { // Create Light Token ATA for destination let owner = payer.pubkey(); - let light_token_ata = derive_token_ata(&owner, &mint); + let light_token_ata = get_associated_token_address(&owner, &mint); CreateAta { mint, @@ -123,7 +123,7 @@ async fn test_unwrap_basic() { // Create Light Token ATA let owner = payer.pubkey(); - let light_token_ata = derive_token_ata(&owner, &mint); + let light_token_ata = get_associated_token_address(&owner, &mint); CreateAta { mint, @@ -213,7 +213,7 @@ async fn test_wrap_unwrap_round_trip() { // Create Light Token ATA let owner = payer.pubkey(); - let light_token_ata = derive_token_ata(&owner, &mint); + let light_token_ata = get_associated_token_address(&owner, &mint); CreateAta { mint, @@ -297,7 +297,7 @@ async fn test_wrap_large_amount() { // Create Light Token ATA let owner = payer.pubkey(); - let light_token_ata = derive_token_ata(&owner, &mint); + let light_token_ata = get_associated_token_address(&owner, &mint); CreateAta { mint, From 84d8415a7af781aa5d4423f168787cf98e60a797 Mon Sep 17 00:00:00 2001 From: Swenschaeferjohann Date: Thu, 12 Feb 2026 01:13:00 +0000 Subject: [PATCH 4/9] typed err in trait --- Cargo.lock | 2 +- .../tests/light_token/compress_and_close.rs | 3 ++- .../tests/light_token/create_ata.rs | 3 ++- .../tests/light_token/shared.rs | 4 ++-- .../tests/mint/edge_cases.rs | 5 +++- .../tests/mint/failing.rs | 9 ++++--- .../tests/mint/functional.rs | 6 +++-- .../tests/mint/mint_to.rs | 4 +++- .../tests/mint/random.rs | 5 +++- .../tests/transfer2/shared.rs | 3 ++- .../src/interface/light_program_interface.rs | 5 +++- .../client/src/interface/load_accounts.rs | 24 +++++++++++-------- .../tests/stress_test.rs | 3 ++- .../src/lib.rs | 3 ++- .../src/amm_test/initialize.rs | 3 ++- .../tests/d10_token_accounts_test.rs | 6 +++-- .../tests/test_ctoken_mint_to.rs | 3 ++- .../tests/scenario_spl.rs | 3 ++- .../tests/test_ctoken_mint_to.rs | 3 ++- 19 files changed, 64 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6633f1db1..ae16a0270b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "Inflector" diff --git a/program-tests/compressed-token-test/tests/light_token/compress_and_close.rs b/program-tests/compressed-token-test/tests/light_token/compress_and_close.rs index 088af681d8..3854530884 100644 --- a/program-tests/compressed-token-test/tests/light_token/compress_and_close.rs +++ b/program-tests/compressed-token-test/tests/light_token/compress_and_close.rs @@ -164,7 +164,8 @@ async fn test_compress_and_close_owner_scenarios() { // Set token balance on ATA use light_token::instruction::get_associated_token_address; - let ata_pubkey = get_associated_token_address(&context.owner_keypair.pubkey(), &context.mint_pubkey); + let ata_pubkey = + get_associated_token_address(&context.owner_keypair.pubkey(), &context.mint_pubkey); let mut ata_account = context.rpc.get_account(ata_pubkey).await.unwrap().unwrap(); diff --git a/program-tests/compressed-token-test/tests/light_token/create_ata.rs b/program-tests/compressed-token-test/tests/light_token/create_ata.rs index f3edeede33..848395bc8c 100644 --- a/program-tests/compressed-token-test/tests/light_token/create_ata.rs +++ b/program-tests/compressed-token-test/tests/light_token/create_ata.rs @@ -419,7 +419,8 @@ async fn test_create_ata_failing() { // Use different mint for this test context.mint_pubkey = solana_sdk::pubkey::Pubkey::new_unique(); - let ata_pubkey = get_associated_token_address(&context.owner_keypair.pubkey(), &context.mint_pubkey); + let ata_pubkey = + get_associated_token_address(&context.owner_keypair.pubkey(), &context.mint_pubkey); // Manually build instruction data with compress_to_account_pubkey (forbidden for ATAs) let compress_to_pubkey = CompressToPubkey { diff --git a/program-tests/compressed-token-test/tests/light_token/shared.rs b/program-tests/compressed-token-test/tests/light_token/shared.rs index 28b7685803..5171081228 100644 --- a/program-tests/compressed-token-test/tests/light_token/shared.rs +++ b/program-tests/compressed-token-test/tests/light_token/shared.rs @@ -16,8 +16,8 @@ pub use light_test_utils::{ Rpc, RpcError, }; pub use light_token::instruction::{ - get_associated_token_address, Approve, CloseAccount, CompressibleParams, CreateAssociatedTokenAccount, - CreateTokenAccount, Revoke, + get_associated_token_address, Approve, CloseAccount, CompressibleParams, + CreateAssociatedTokenAccount, CreateTokenAccount, Revoke, }; pub use serial_test::serial; pub use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; diff --git a/program-tests/compressed-token-test/tests/mint/edge_cases.rs b/program-tests/compressed-token-test/tests/mint/edge_cases.rs index 08b25b6b08..b421eaf6b0 100644 --- a/program-tests/compressed-token-test/tests/mint/edge_cases.rs +++ b/program-tests/compressed-token-test/tests/mint/edge_cases.rs @@ -182,7 +182,10 @@ async fn functional_all_in_one_instruction() { }, // 2. MintToCToken - mint to decompressed account MintActionType::MintToCToken { - account: light_token::instruction::get_associated_token_address(&recipient.pubkey(), &spl_mint_pda), + account: light_token::instruction::get_associated_token_address( + &recipient.pubkey(), + &spl_mint_pda, + ), amount: 2000u64, }, // 3. UpdateMintAuthority diff --git a/program-tests/compressed-token-test/tests/mint/failing.rs b/program-tests/compressed-token-test/tests/mint/failing.rs index 22ee732f0f..98fdadebf4 100644 --- a/program-tests/compressed-token-test/tests/mint/failing.rs +++ b/program-tests/compressed-token-test/tests/mint/failing.rs @@ -469,8 +469,10 @@ async fn functional_and_failing_tests() { .await .unwrap(); - let recipient_ata = - light_token::instruction::get_associated_token_address(&recipient2.pubkey(), &spl_mint_pda); + let recipient_ata = light_token::instruction::get_associated_token_address( + &recipient2.pubkey(), + &spl_mint_pda, + ); // Try to mint with valid NEW authority (since we updated it) let result = light_test_utils::actions::mint_action_comprehensive( @@ -883,7 +885,8 @@ async fn test_mint_to_ctoken_max_top_up_exceeded() { .await .unwrap(); - let ctoken_ata = light_token::instruction::get_associated_token_address(&recipient.pubkey(), &spl_mint_pda); + let ctoken_ata = + light_token::instruction::get_associated_token_address(&recipient.pubkey(), &spl_mint_pda); // 3. Build MintToCToken instruction with max_top_up = 1 (too low) // Get current compressed mint state diff --git a/program-tests/compressed-token-test/tests/mint/functional.rs b/program-tests/compressed-token-test/tests/mint/functional.rs index 6fa144bcb6..c416b76a19 100644 --- a/program-tests/compressed-token-test/tests/mint/functional.rs +++ b/program-tests/compressed-token-test/tests/mint/functional.rs @@ -422,7 +422,8 @@ async fn test_create_compressed_mint() { // This already exists from our previous test // Create non-compressible SPL token account for decompression destination - let decompress_dest_ata = get_associated_token_address(&decompress_recipient.pubkey(), &spl_mint_pda); + let decompress_dest_ata = + get_associated_token_address(&decompress_recipient.pubkey(), &spl_mint_pda); let create_decompress_ata_instruction = CreateAssociatedTokenAccount { idempotent: false, payer: payer.pubkey(), @@ -759,7 +760,8 @@ async fn test_ctoken_transfer() { // === CREATE SECOND RECIPIENT FOR TRANSFER TEST === let second_recipient_keypair = Keypair::new(); - let second_recipient_ata = get_associated_token_address(&second_recipient_keypair.pubkey(), &spl_mint_pda); + let second_recipient_ata = + get_associated_token_address(&second_recipient_keypair.pubkey(), &spl_mint_pda); rpc.airdrop_lamports(&second_recipient_keypair.pubkey(), 10_000_000_000) .await diff --git a/program-tests/compressed-token-test/tests/mint/mint_to.rs b/program-tests/compressed-token-test/tests/mint/mint_to.rs index 9fcbdaafa9..50bbcc74ef 100644 --- a/program-tests/compressed-token-test/tests/mint/mint_to.rs +++ b/program-tests/compressed-token-test/tests/mint/mint_to.rs @@ -4,7 +4,9 @@ use light_test_utils::{ actions::legacy::instructions::mint_action::DecompressMintParams, assert_ctoken_mint_to::assert_ctoken_mint_to, Rpc, }; -use light_token::instruction::{get_associated_token_address, CreateAssociatedTokenAccount, MintTo}; +use light_token::instruction::{ + get_associated_token_address, CreateAssociatedTokenAccount, MintTo, +}; use serial_test::serial; use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; diff --git a/program-tests/compressed-token-test/tests/mint/random.rs b/program-tests/compressed-token-test/tests/mint/random.rs index a3cf0c62f7..5e240b1572 100644 --- a/program-tests/compressed-token-test/tests/mint/random.rs +++ b/program-tests/compressed-token-test/tests/mint/random.rs @@ -143,7 +143,10 @@ async fn test_random_mint_action() { .await .unwrap(); - let ata = light_token::instruction::get_associated_token_address(&recipient.pubkey(), &spl_mint_pda); + let ata = light_token::instruction::get_associated_token_address( + &recipient.pubkey(), + &spl_mint_pda, + ); ctoken_atas.push(ata); } diff --git a/program-tests/compressed-token-test/tests/transfer2/shared.rs b/program-tests/compressed-token-test/tests/transfer2/shared.rs index 40592fcdcf..886bf8333a 100644 --- a/program-tests/compressed-token-test/tests/transfer2/shared.rs +++ b/program-tests/compressed-token-test/tests/transfer2/shared.rs @@ -445,7 +445,8 @@ impl TestContext { .unwrap_or(&false); // Create Light Token ATA (compressible or regular based on requirements) - let ata = light_token::instruction::get_associated_token_address(&signer.pubkey(), &mint); + let ata = + light_token::instruction::get_associated_token_address(&signer.pubkey(), &mint); let create_ata_ix = if is_compressible { println!( diff --git a/sdk-libs/client/src/interface/light_program_interface.rs b/sdk-libs/client/src/interface/light_program_interface.rs index 12b78f0705..b9efb3707d 100644 --- a/sdk-libs/client/src/interface/light_program_interface.rs +++ b/sdk-libs/client/src/interface/light_program_interface.rs @@ -182,6 +182,9 @@ pub trait LightProgramInterface: Sized { /// Program-specific instruction enum. type Instruction; + /// Error type returned by load_specs. + type Error: std::error::Error + Send + Sync + 'static; + /// The program ID. fn program_id() -> Pubkey; @@ -197,7 +200,7 @@ pub trait LightProgramInterface: Sized { fn load_specs( &self, cold_accounts: &[AccountInterface], - ) -> Result>, Box>; + ) -> Result>, Self::Error>; } /// Extract 8-byte discriminator from account data. diff --git a/sdk-libs/client/src/interface/load_accounts.rs b/sdk-libs/client/src/interface/load_accounts.rs index 3dbb2ba993..e332c7ccab 100644 --- a/sdk-libs/client/src/interface/load_accounts.rs +++ b/sdk-libs/client/src/interface/load_accounts.rs @@ -62,8 +62,14 @@ pub enum LoadAccountsError { #[error("Cold mint at index {index} (mint {mint}) missing hash")] MissingMintHash { index: usize, mint: Pubkey }, - #[error("ATA at index {index} (pubkey {pubkey}) missing compressed data or ATA bump")] - MissingAtaContext { index: usize, pubkey: Pubkey }, + #[error("ATA at index {index} (pubkey {pubkey}) not a compressed token account")] + MissingAtaCompressedData { index: usize, pubkey: Pubkey }, + + #[error("ATA at index {index} (pubkey {pubkey}) invalid POD account data")] + InvalidAtaPodData { index: usize, pubkey: Pubkey }, + + #[error("ATA at index {index} (pubkey {pubkey}) derivation mismatch")] + AtaDerivationMismatch { index: usize, pubkey: Pubkey }, #[error("Tree info index {index} out of bounds (len {len})")] TreeInfoIndexOutOfBounds { index: usize, len: usize }, @@ -249,7 +255,6 @@ where .unwrap_or(false) }); - // Derive rent sponsor PDA from program_id let program_id = specs.first().map(|s| s.program_id()).unwrap_or_default(); let (rent_sponsor, _) = derive_rent_sponsor_pda(&program_id); @@ -268,8 +273,6 @@ where }) .collect(); - let program_id = specs.first().map(|s| s.program_id()).unwrap_or_default(); - instructions::create_decompress_accounts_idempotent_instruction( &program_id, &DECOMPRESS_ACCOUNTS_IDEMPOTENT_DISCRIMINATOR, @@ -296,7 +299,7 @@ impl<'a> AtaContext<'a> { let compressed = iface .as_compressed_token() - .ok_or(LoadAccountsError::MissingAtaContext { + .ok_or(LoadAccountsError::MissingAtaCompressedData { index, pubkey: iface.key, })?; @@ -306,7 +309,7 @@ impl<'a> AtaContext<'a> { .data .get(..pod_len) .and_then(|d| pod_from_bytes(d).ok()) - .ok_or(LoadAccountsError::MissingAtaContext { + .ok_or(LoadAccountsError::InvalidAtaPodData { index, pubkey: iface.key, })?; @@ -314,7 +317,7 @@ impl<'a> AtaContext<'a> { let mint = parsed.mint; let (derived_ata, bump) = get_associated_token_address_and_bump(&wallet_owner, &mint); if derived_ata != iface.key { - return Err(LoadAccountsError::MissingAtaContext { + return Err(LoadAccountsError::AtaDerivationMismatch { index, pubkey: iface.key, }); @@ -379,7 +382,8 @@ fn build_transfer2( )?; let owner_idx = packed.insert_or_get_config(ctx.wallet_owner, true, false); - let ata_idx = packed.insert_or_get(get_associated_token_address(&ctx.wallet_owner, &ctx.mint)); + let ata_idx = + packed.insert_or_get(get_associated_token_address(&ctx.wallet_owner, &ctx.mint)); let mint_idx = packed.insert_or_get(token.mint); let delegate_idx = token.delegate.map(|d| packed.insert_or_get(d)).unwrap_or(0); @@ -476,7 +480,7 @@ fn build_mint_load( .mint_compressed_address() .ok_or_else(|| LoadAccountsError::BuildInstruction("missing compressed_address".into()))?; let mint_ix_data = MintInstructionData::try_from(mint_data) - .map_err(|_| LoadAccountsError::BuildInstruction("invalid mint data".into()))?; + .map_err(|e| LoadAccountsError::BuildInstruction(format!("invalid mint data: {}", e)))?; DecompressMint { payer: fee_payer, diff --git a/sdk-tests/anchor-semi-manual-test/tests/stress_test.rs b/sdk-tests/anchor-semi-manual-test/tests/stress_test.rs index cfb3ec4030..7edeb4ce84 100644 --- a/sdk-tests/anchor-semi-manual-test/tests/stress_test.rs +++ b/sdk-tests/anchor-semi-manual-test/tests/stress_test.rs @@ -276,7 +276,8 @@ async fn decompress_all(ctx: &mut StressTestContext, pdas: &TestPdas, cached: &C let zc_spec = PdaSpec::new(zc_interface, zc_variant, ctx.program_id); // ATA - let ata = light_token::instruction::get_associated_token_address(&pdas.ata_owner, &pdas.ata_mint); + let ata = + light_token::instruction::get_associated_token_address(&pdas.ata_owner, &pdas.ata_mint); let ata_interface = ctx .rpc .get_account_interface(&ata, None) diff --git a/sdk-tests/csdk-anchor-full-derived-test-sdk/src/lib.rs b/sdk-tests/csdk-anchor-full-derived-test-sdk/src/lib.rs index caf912682a..b8c5028611 100644 --- a/sdk-tests/csdk-anchor-full-derived-test-sdk/src/lib.rs +++ b/sdk-tests/csdk-anchor-full-derived-test-sdk/src/lib.rs @@ -92,6 +92,7 @@ impl AmmSdk { impl LightProgramInterface for AmmSdk { type Variant = LightAccountVariant; type Instruction = AmmInstruction; + type Error = AmmSdkError; fn program_id() -> Pubkey { PROGRAM_ID @@ -118,7 +119,7 @@ impl LightProgramInterface for AmmSdk { fn load_specs( &self, cold_accounts: &[AccountInterface], - ) -> Result>, Box> { + ) -> Result>, Self::Error> { use light_account::{token::TokenDataWithSeeds, Token}; let mut specs = Vec::new(); diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/initialize.rs b/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/initialize.rs index 979530e4b6..e4a2c0dc4b 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/initialize.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/initialize.rs @@ -8,11 +8,12 @@ //! - MintToCpi use anchor_lang::prelude::*; +use anchor_spl::token_interface::Mint; use light_account::{ CreateAccountsProof, CreateTokenAccountCpi, CreateTokenAtaCpi, LightAccounts, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR, }; -use light_anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; +use light_anchor_spl::token_interface::{TokenAccount, TokenInterface}; use light_token::instruction::MintToCpi; use super::states::*; diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/d10_token_accounts_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/d10_token_accounts_test.rs index 217784a236..41b5873561 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/d10_token_accounts_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/d10_token_accounts_test.rs @@ -381,7 +381,8 @@ async fn test_d10_single_ata_markonly() { let ata_owner = Keypair::new().pubkey(); // Derive the ATA address using Light Token SDK's derivation - let d10_markonly_ata = light_token::instruction::get_associated_token_address(&ata_owner, &mint); + let d10_markonly_ata = + light_token::instruction::get_associated_token_address(&ata_owner, &mint); // Get proof (no PDA accounts for ATA-only instruction) let proof_result = get_create_accounts_proof(&ctx.rpc, &ctx.program_id, vec![]) @@ -446,7 +447,8 @@ async fn test_d10_single_ata_markonly_lifecycle() { let ata_owner = ata_owner_keypair.pubkey(); // Derive the ATA address - let d10_markonly_ata = light_token::instruction::get_associated_token_address(&ata_owner, &mint); + let d10_markonly_ata = + light_token::instruction::get_associated_token_address(&ata_owner, &mint); // PHASE 1: Create ATA let proof_result = get_create_accounts_proof(&ctx.rpc, &ctx.program_id, vec![]) diff --git a/sdk-tests/sdk-light-token-pinocchio/tests/test_ctoken_mint_to.rs b/sdk-tests/sdk-light-token-pinocchio/tests/test_ctoken_mint_to.rs index 85ee1af230..1eb470c909 100644 --- a/sdk-tests/sdk-light-token-pinocchio/tests/test_ctoken_mint_to.rs +++ b/sdk-tests/sdk-light-token-pinocchio/tests/test_ctoken_mint_to.rs @@ -202,7 +202,8 @@ async fn test_ctoken_mint_to_invoke_signed() { // Step 2: Create ATA for payer (CreateMint now auto-decompresses) let ata = { - let ata_address = light_token::instruction::get_associated_token_address(&payer.pubkey(), &mint_pda); + let ata_address = + light_token::instruction::get_associated_token_address(&payer.pubkey(), &mint_pda); let create_ata = CreateAssociatedTokenAccount::new(payer.pubkey(), payer.pubkey(), mint_pda); let ata_instruction = create_ata.instruction().unwrap(); diff --git a/sdk-tests/sdk-light-token-test/tests/scenario_spl.rs b/sdk-tests/sdk-light-token-test/tests/scenario_spl.rs index d5a013797f..d037e26f2b 100644 --- a/sdk-tests/sdk-light-token-test/tests/scenario_spl.rs +++ b/sdk-tests/sdk-light-token-test/tests/scenario_spl.rs @@ -22,7 +22,8 @@ use light_program_test::{program_test::TestRpc, LightProgramTest, ProgramTestCon use light_test_utils::spl::{create_token_account, mint_spl_tokens}; use light_token::{ instruction::{ - get_associated_token_address, CreateAssociatedTokenAccount, Decompress, Freeze, Thaw, TransferFromSpl, + get_associated_token_address, CreateAssociatedTokenAccount, Decompress, Freeze, Thaw, + TransferFromSpl, }, spl_interface::{find_spl_interface_pda_with_index, CreateSplInterfacePda}, }; diff --git a/sdk-tests/sdk-light-token-test/tests/test_ctoken_mint_to.rs b/sdk-tests/sdk-light-token-test/tests/test_ctoken_mint_to.rs index 3278314ee9..12282bbe75 100644 --- a/sdk-tests/sdk-light-token-test/tests/test_ctoken_mint_to.rs +++ b/sdk-tests/sdk-light-token-test/tests/test_ctoken_mint_to.rs @@ -196,7 +196,8 @@ async fn test_ctoken_mint_to_invoke_signed() { // Step 2: Create ATA for payer (CreateMint now auto-decompresses) let ata = { - let ata_address = light_token::instruction::get_associated_token_address(&payer.pubkey(), &mint_pda); + let ata_address = + light_token::instruction::get_associated_token_address(&payer.pubkey(), &mint_pda); let create_ata = CreateAssociatedTokenAccount::new(payer.pubkey(), payer.pubkey(), mint_pda); let ata_instruction = create_ata.instruction().unwrap(); From bfceb67b0f189db75e2e47ad2e616123718315fa Mon Sep 17 00:00:00 2001 From: Swenschaeferjohann Date: Thu, 12 Feb 2026 01:21:17 +0000 Subject: [PATCH 5/9] lint --- .../csdk-anchor-full-derived-test/src/amm_test/initialize.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/initialize.rs b/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/initialize.rs index e4a2c0dc4b..979530e4b6 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/initialize.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/src/amm_test/initialize.rs @@ -8,12 +8,11 @@ //! - MintToCpi use anchor_lang::prelude::*; -use anchor_spl::token_interface::Mint; use light_account::{ CreateAccountsProof, CreateTokenAccountCpi, CreateTokenAtaCpi, LightAccounts, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_RENT_SPONSOR, }; -use light_anchor_spl::token_interface::{TokenAccount, TokenInterface}; +use light_anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; use light_token::instruction::MintToCpi; use super::states::*; From 5bbf937fe957dde96590ba84e1ef97fddaf2412f Mon Sep 17 00:00:00 2001 From: Swenschaeferjohann Date: Thu, 12 Feb 2026 02:41:10 +0000 Subject: [PATCH 6/9] fix client --- sdk-libs/client/src/indexer/mod.rs | 13 +- sdk-libs/client/src/indexer/photon_indexer.rs | 40 ++- .../client/src/indexer/types/interface.rs | 160 ----------- sdk-libs/client/src/indexer/types/mod.rs | 4 - .../client/src/interface/account_interface.rs | 207 +++++---------- .../src/interface/light_program_interface.rs | 19 +- .../client/src/interface/load_accounts.rs | 11 +- sdk-libs/client/src/interface/mod.rs | 2 +- sdk-libs/client/src/rpc/client.rs | 93 +------ sdk-libs/program-test/src/program_test/rpc.rs | 181 +++++++++---- .../tests/amm_test.rs | 248 ++++++++++-------- 11 files changed, 387 insertions(+), 591 deletions(-) delete mode 100644 sdk-libs/client/src/indexer/types/interface.rs diff --git a/sdk-libs/client/src/indexer/mod.rs b/sdk-libs/client/src/indexer/mod.rs index 9b3c903566..ec58233f2e 100644 --- a/sdk-libs/client/src/indexer/mod.rs +++ b/sdk-libs/client/src/indexer/mod.rs @@ -14,13 +14,12 @@ pub use error::IndexerError; pub use indexer_trait::Indexer; pub use response::{Context, Items, ItemsWithCursor, Response}; pub use types::{ - AccountInterface, AccountProofInputs, Address, AddressMerkleTreeAccounts, AddressProofInputs, - AddressQueueData, AddressWithTree, ColdContext, ColdData, CompressedAccount, - CompressedTokenAccount, Hash, InputQueueData, InterfaceTreeInfo, MerkleProof, - MerkleProofWithContext, NewAddressProofWithContext, NextTreeInfo, OutputQueueData, - OwnerBalance, ProofOfLeaf, QueueElementsResult, QueueInfo, QueueInfoResult, RootIndex, - SignatureWithMetadata, SolanaAccountData, StateMerkleTreeAccounts, StateQueueData, - TokenBalance, TreeInfo, ValidityProofWithContext, + AccountProofInputs, Address, AddressMerkleTreeAccounts, AddressProofInputs, AddressQueueData, + AddressWithTree, CompressedAccount, CompressedTokenAccount, Hash, InputQueueData, MerkleProof, + MerkleProofWithContext, NewAddressProofWithContext, NextTreeInfo, OutputQueueData, OwnerBalance, + ProofOfLeaf, QueueElementsResult, QueueInfo, QueueInfoResult, RootIndex, + SignatureWithMetadata, StateMerkleTreeAccounts, StateQueueData, TokenBalance, TreeInfo, + ValidityProofWithContext, }; mod options; pub use options::*; diff --git a/sdk-libs/client/src/indexer/photon_indexer.rs b/sdk-libs/client/src/indexer/photon_indexer.rs index 7aed81762a..eedd0cb790 100644 --- a/sdk-libs/client/src/indexer/photon_indexer.rs +++ b/sdk-libs/client/src/indexer/photon_indexer.rs @@ -8,9 +8,9 @@ use solana_pubkey::Pubkey; use tracing::{error, trace, warn}; use super::types::{ - AccountInterface, CompressedAccount, CompressedTokenAccount, OwnerBalance, - SignatureWithMetadata, TokenBalance, + CompressedAccount, CompressedTokenAccount, OwnerBalance, SignatureWithMetadata, TokenBalance, }; +use crate::interface::AccountInterface; use crate::indexer::{ base58::Base58Conversions, config::RetryConfig, @@ -1747,7 +1747,7 @@ impl PhotonIndexer { } let account = match api_response.value { - Some(ref ai) => Some(AccountInterface::try_from(ai)?), + Some(ref ai) => Some(convert_photon_account_interface(ai)?), None => None, }; @@ -1798,7 +1798,7 @@ impl PhotonIndexer { .into_iter() .map(|maybe_acc| { maybe_acc - .map(|ai| AccountInterface::try_from(&ai)) + .map(|ai| convert_photon_account_interface(&ai)) .transpose() }) .collect(); @@ -1813,3 +1813,35 @@ impl PhotonIndexer { .await } } + +/// Convert a photon_api AccountInterface directly to the consumer AccountInterface. +fn convert_photon_account_interface( + ai: &photon_api::types::AccountInterface, +) -> Result { + use super::base58::decode_base58_to_fixed_array; + use solana_account::Account; + + let key = Pubkey::new_from_array(decode_base58_to_fixed_array(&ai.key)?); + let data = base64::decode_config(&*ai.account.data, base64::STANDARD_NO_PAD) + .map_err(|e| IndexerError::decode_error("account.data", e))?; + let account = Account { + lamports: *ai.account.lamports, + data, + owner: Pubkey::new_from_array(decode_base58_to_fixed_array(&ai.account.owner)?), + executable: ai.account.executable, + rent_epoch: *ai.account.rent_epoch, + }; + + let cold = ai + .cold + .as_ref() + .map(|entries| { + entries + .iter() + .map(CompressedAccount::try_from) + .collect::, _>>() + }) + .transpose()?; + + Ok(AccountInterface { key, account, cold }) +} diff --git a/sdk-libs/client/src/indexer/types/interface.rs b/sdk-libs/client/src/indexer/types/interface.rs deleted file mode 100644 index bac5540dab..0000000000 --- a/sdk-libs/client/src/indexer/types/interface.rs +++ /dev/null @@ -1,160 +0,0 @@ -use light_compressed_account::TreeType; -use solana_account::Account; -use solana_pubkey::Pubkey; - -use super::super::{base58::decode_base58_to_fixed_array, IndexerError}; - -/// Re-export solana Account for interface types. -pub type SolanaAccountData = Account; - -/// Merkle tree info for compressed accounts -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct InterfaceTreeInfo { - pub tree: Pubkey, - pub queue: Pubkey, - pub tree_type: TreeType, - pub seq: Option, - /// Slot when the account was created/compressed - pub slot_created: u64, -} - -/// Structured compressed account data (discriminator separated) -#[derive(Clone, Debug, PartialEq)] -pub struct ColdData { - pub discriminator: [u8; 8], - pub data: Vec, - pub data_hash: [u8; 32], -} - -/// Compressed account context — present when account is in compressed state. -#[derive(Clone, Debug, PartialEq)] -pub struct ColdContext { - pub hash: [u8; 32], - pub leaf_index: u64, - pub tree_info: InterfaceTreeInfo, - pub data: ColdData, - pub address: Option<[u8; 32]>, - pub prove_by_index: bool, -} - -/// Decode tree info from photon_api AccountV2 format -fn decode_tree_info_v2( - merkle_ctx: &photon_api::types::MerkleContextV2, - seq: Option, - slot_created: u64, -) -> Result { - let tree = Pubkey::new_from_array(decode_base58_to_fixed_array(&merkle_ctx.tree)?); - let queue = Pubkey::new_from_array(decode_base58_to_fixed_array(&merkle_ctx.queue)?); - let tree_type = TreeType::from(merkle_ctx.tree_type as u64); - Ok(InterfaceTreeInfo { - tree, - queue, - tree_type, - seq, - slot_created, - }) -} - -/// Decode cold data from photon_api AccountData format. -fn decode_account_data(data: &photon_api::types::AccountData) -> Result { - let disc_val = *data.discriminator; - let discriminator = disc_val.to_le_bytes(); - Ok(ColdData { - discriminator, - data: base64::decode_config(&*data.data, base64::STANDARD_NO_PAD) - .map_err(|e| IndexerError::decode_error("data", e))?, - data_hash: decode_base58_to_fixed_array(&data.data_hash)?, - }) -} - -/// Convert a photon_api AccountV2 to a client ColdContext. -fn convert_account_v2(av2: &photon_api::types::AccountV2) -> Result { - let tree_info = decode_tree_info_v2( - &av2.merkle_context, - av2.seq.as_ref().map(|s| **s), - *av2.slot_created, - )?; - - let data = match &av2.data { - Some(d) => decode_account_data(d)?, - None => ColdData { - discriminator: [0u8; 8], - data: Vec::new(), - data_hash: [0u8; 32], - }, - }; - - let address = av2 - .address - .as_ref() - .map(|a| decode_base58_to_fixed_array(a)) - .transpose()?; - - Ok(ColdContext { - hash: decode_base58_to_fixed_array(&av2.hash)?, - leaf_index: *av2.leaf_index, - tree_info, - data, - address, - prove_by_index: av2.prove_by_index, - }) -} - -/// Unified account interface — works for both on-chain and compressed accounts -#[derive(Clone, Debug, PartialEq)] -pub struct AccountInterface { - /// The on-chain Solana pubkey - pub key: Pubkey, - /// Standard Solana account fields - pub account: SolanaAccountData, - /// Compressed context — None if on-chain, Some if compressed - pub cold: Option, -} - -impl AccountInterface { - /// Returns true if this account is on-chain (hot) - pub fn is_hot(&self) -> bool { - self.cold.is_none() - } - - /// Returns true if this account is compressed (cold) - pub fn is_cold(&self) -> bool { - self.cold.is_some() - } -} - -/// Helper to convert photon_api AccountInterface to client AccountInterface -fn convert_account_interface( - ai: &photon_api::types::AccountInterface, -) -> Result { - // Take the first compressed account entry if present - let cold = ai - .cold - .as_ref() - .and_then(|entries| entries.first()) - .map(convert_account_v2) - .transpose()?; - - let data = base64::decode_config(&*ai.account.data, base64::STANDARD_NO_PAD) - .map_err(|e| IndexerError::decode_error("account.data", e))?; - - Ok(AccountInterface { - key: Pubkey::new_from_array(decode_base58_to_fixed_array(&ai.key)?), - account: Account { - lamports: *ai.account.lamports, - data, - owner: Pubkey::new_from_array(decode_base58_to_fixed_array(&ai.account.owner)?), - executable: ai.account.executable, - rent_epoch: *ai.account.rent_epoch, - }, - cold, - }) -} - -impl TryFrom<&photon_api::types::AccountInterface> for AccountInterface { - type Error = IndexerError; - - fn try_from(ai: &photon_api::types::AccountInterface) -> Result { - convert_account_interface(ai) - } -} diff --git a/sdk-libs/client/src/indexer/types/mod.rs b/sdk-libs/client/src/indexer/types/mod.rs index 9ab14798f4..b8c0fa8dc7 100644 --- a/sdk-libs/client/src/indexer/types/mod.rs +++ b/sdk-libs/client/src/indexer/types/mod.rs @@ -1,5 +1,4 @@ mod account; -mod interface; mod proof; mod queue; mod signature; @@ -7,9 +6,6 @@ mod token; mod tree; pub use account::CompressedAccount; -pub use interface::{ - AccountInterface, ColdContext, ColdData, InterfaceTreeInfo, SolanaAccountData, -}; pub use proof::{ AccountProofInputs, AddressProofInputs, AddressWithTree, MerkleProof, MerkleProofWithContext, NewAddressProofWithContext, RootIndex, ValidityProofWithContext, diff --git a/sdk-libs/client/src/interface/account_interface.rs b/sdk-libs/client/src/interface/account_interface.rs index ae54132347..0fd45afc2b 100644 --- a/sdk-libs/client/src/interface/account_interface.rs +++ b/sdk-libs/client/src/interface/account_interface.rs @@ -1,65 +1,36 @@ -//! Unified account interfaces for hot/cold account handling. +//! Unified account interface for hot/cold account handling. //! -//! Core type: `AccountInterface` - Generic account (PDAs, mints, ATAs). -//! Consumers parse `account.data` (SPL layout) for hot or cold. -//! -//! All interfaces use standard Solana/SPL types: -//! - `solana_account::Account` for raw account data -//! - `spl_token_2022_interface::pod::PodAccount` for parsed token data +//! Single type: `AccountInterface` - works for PDAs, mints, ATAs. +//! For hot accounts: real on-chain data. +//! For cold accounts: synthetic data from Photon + compressed account metadata. +use borsh::BorshDeserialize; +use light_sdk_types::TOKEN_COMPRESSED_ACCOUNT_DISCRIMINATOR; use solana_account::Account; use solana_pubkey::Pubkey; -use spl_pod::{bytemuck::pod_bytes_of, primitives::PodU64}; -use spl_token_2022_interface::{ - pod::{PodAccount, PodCOption}, - state::AccountState, -}; -use thiserror::Error; use crate::indexer::{CompressedAccount, CompressedTokenAccount, TreeInfo}; -/// Context for cold accounts. -/// -/// Three variants based on data structure: -/// - `Account` - Generic PDA -/// - `Token` - Token account -/// - `Mint` - Compressed mint -#[derive(Clone, Debug, PartialEq)] -pub enum ColdContext { - /// Generic PDA - Account(CompressedAccount), - /// Token account - Token(CompressedTokenAccount), - /// Compressed mint - Mint(CompressedAccount), -} - -/// Error type for account interface operations. -#[derive(Debug, Error)] -pub enum AccountInterfaceError { - #[error("Account not found")] - NotFound, - - #[error("Invalid account data")] - InvalidData, - - #[error("Parse error: {0}")] - ParseError(String), -} +/// C_TOKEN_DISCRIMINATOR_V2: batched Merkle trees. +const C_TOKEN_V2: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 3]; +/// C_TOKEN_DISCRIMINATOR_V3: SHA256 flat hash with TLV extensions. +const C_TOKEN_V3: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 4]; /// Unified account interface for PDAs, mints, and tokens. /// -/// Uses standard `solana_account::Account` for raw data. -/// For hot accounts: actual on-chain bytes. -/// For cold accounts: synthetic bytes from cold data. +/// `account` contains usable data bytes in both hot and cold cases: +/// - Hot: actual on-chain bytes +/// - Cold: synthetic bytes from Photon (SPL layout for tokens, disc+data for PDAs) +/// +/// `cold` contains the raw compressed account(s) when cold, needed for proof generation. #[derive(Debug, Clone, PartialEq, Default)] pub struct AccountInterface { /// The account's public key. pub key: Pubkey, /// Standard Solana Account (lamports, data, owner, executable, rent_epoch). pub account: Account, - /// Cold context (only present when cold). - pub cold: Option, + /// Compressed accounts when cold (None = hot). + pub cold: Option>, } impl AccountInterface { @@ -72,68 +43,12 @@ impl AccountInterface { } } - /// Create a cold account interface for a PDA/mint. - pub fn cold(key: Pubkey, compressed: CompressedAccount, owner: Pubkey) -> Self { - let data = compressed - .data - .as_ref() - .map(|d| { - let mut buf = d.discriminator.to_vec(); - buf.extend_from_slice(&d.data); - buf - }) - .unwrap_or_default(); - - Self { - key, - account: Account { - lamports: compressed.lamports, - data, - owner, - executable: false, - rent_epoch: 0, - }, - cold: Some(ColdContext::Account(compressed)), - } - } - - /// Create a cold account interface for a token account. - pub fn cold_token( - key: Pubkey, - compressed: CompressedTokenAccount, - wallet_owner: Pubkey, - ) -> Self { - use light_token::compat::AccountState as LightAccountState; - - let token = &compressed.token; - let parsed = PodAccount { - mint: token.mint, - owner: wallet_owner, - amount: PodU64::from(token.amount), - delegate: match token.delegate { - Some(pk) => PodCOption::some(pk), - None => PodCOption::none(), - }, - state: match token.state { - LightAccountState::Frozen => AccountState::Frozen as u8, - _ => AccountState::Initialized as u8, - }, - is_native: PodCOption::none(), - delegated_amount: PodU64::from(0u64), - close_authority: PodCOption::none(), - }; - let data = pod_bytes_of(&parsed).to_vec(); - + /// Create a cold account interface from compressed accounts and synthetic account data. + pub fn cold(key: Pubkey, account: Account, compressed: Vec) -> Self { Self { key, - account: Account { - lamports: compressed.account.lamports, - data, - owner: light_token::instruction::LIGHT_TOKEN_PROGRAM_ID, - executable: false, - rent_epoch: 0, - }, - cold: Some(ColdContext::Token(compressed)), + account, + cold: Some(compressed), } } @@ -149,68 +64,65 @@ impl AccountInterface { self.cold.is_none() } - /// Get data bytes. + /// Get data bytes (works for both hot and cold). #[inline] pub fn data(&self) -> &[u8] { &self.account.data } + /// Get the primary compressed account (first in the cold vec). + pub fn compressed(&self) -> Option<&CompressedAccount> { + self.cold.as_ref().and_then(|v| v.first()) + } + + /// Get all compressed accounts. + pub fn compressed_accounts(&self) -> Option<&[CompressedAccount]> { + self.cold.as_deref() + } + /// Get the account hash if cold. pub fn hash(&self) -> Option<[u8; 32]> { - match &self.cold { - Some(ColdContext::Account(c)) => Some(c.hash), - Some(ColdContext::Token(c)) => Some(c.account.hash), - Some(ColdContext::Mint(c)) => Some(c.hash), - None => None, - } + self.compressed().map(|c| c.hash) } /// Get tree info if cold. pub fn tree_info(&self) -> Option<&TreeInfo> { - match &self.cold { - Some(ColdContext::Account(c)) => Some(&c.tree_info), - Some(ColdContext::Token(c)) => Some(&c.account.tree_info), - Some(ColdContext::Mint(c)) => Some(&c.tree_info), - None => None, - } + self.compressed().map(|c| &c.tree_info) } /// Get leaf index if cold. pub fn leaf_index(&self) -> Option { - match &self.cold { - Some(ColdContext::Account(c)) => Some(c.leaf_index), - Some(ColdContext::Token(c)) => Some(c.account.leaf_index), - Some(ColdContext::Mint(c)) => Some(c.leaf_index), - None => None, - } + self.compressed().map(|c| c.leaf_index) } - /// Get as CompressedAccount if cold account or mint type. - pub fn as_compressed_account(&self) -> Option<&CompressedAccount> { - match &self.cold { - Some(ColdContext::Account(c)) => Some(c), - Some(ColdContext::Mint(c)) => Some(c), - _ => None, - } - } + /// Parse as CompressedTokenAccount if the primary compressed account is a token. + /// + /// Token detection: owner == LIGHT_TOKEN_PROGRAM_ID and c_token discriminator. + /// Token data is borsh-deserialized from the compressed account data. + pub fn as_compressed_token(&self) -> Option { + let compressed = self.compressed()?; + let data = compressed.data.as_ref()?; - /// Get as CompressedTokenAccount if cold token type. - pub fn as_compressed_token(&self) -> Option<&CompressedTokenAccount> { - match &self.cold { - Some(ColdContext::Token(c)) => Some(c), - _ => None, + if compressed.owner != light_token::instruction::LIGHT_TOKEN_PROGRAM_ID { + return None; } + if !is_c_token_discriminator(&data.discriminator) { + return None; + } + + let token = + light_token::compat::TokenData::deserialize(&mut data.data.as_slice()).ok()?; + Some(CompressedTokenAccount { + token, + account: compressed.clone(), + }) } /// Try to parse as Mint. Returns None if not a mint or parse fails. pub fn as_mint(&self) -> Option { - match &self.cold { - Some(ColdContext::Mint(ca)) | Some(ColdContext::Account(ca)) => { - let data = ca.data.as_ref()?; - borsh::BorshDeserialize::deserialize(&mut data.data.as_slice()).ok() - } - _ => None, - } + let compressed = self.compressed()?; + let data = compressed.data.as_ref()?; + BorshDeserialize::deserialize(&mut data.data.as_slice()).ok() } /// Get mint signer if this is a cold mint. @@ -223,3 +135,8 @@ impl AccountInterface { self.as_mint().map(|m| m.metadata.compressed_address()) } } + +/// Check if a discriminator is a c_token discriminator (V1, V2, or V3). +fn is_c_token_discriminator(disc: &[u8; 8]) -> bool { + *disc == TOKEN_COMPRESSED_ACCOUNT_DISCRIMINATOR || *disc == C_TOKEN_V2 || *disc == C_TOKEN_V3 +} diff --git a/sdk-libs/client/src/interface/light_program_interface.rs b/sdk-libs/client/src/interface/light_program_interface.rs index b9efb3707d..43afdb95e4 100644 --- a/sdk-libs/client/src/interface/light_program_interface.rs +++ b/sdk-libs/client/src/interface/light_program_interface.rs @@ -10,8 +10,8 @@ use std::fmt::Debug; use light_account::Pack; use solana_pubkey::Pubkey; -use super::{AccountInterface, ColdContext}; -use crate::indexer::{CompressedAccount, CompressedTokenAccount}; +use super::AccountInterface; +use crate::indexer::CompressedAccount; /// Specification for a program-owned PDA with typed variant. /// @@ -68,21 +68,10 @@ impl PdaSpec { /// Get the compressed account if cold. #[must_use] pub fn compressed(&self) -> Option<&CompressedAccount> { - match &self.interface.cold { - Some(ColdContext::Account(c)) => Some(c), - Some(ColdContext::Token(c)) => Some(&c.account), - Some(ColdContext::Mint(c)) => Some(c), - None => None, - } - } - - /// Get the compressed token account if this is a cold token PDA. - #[must_use] - pub fn compressed_token(&self) -> Option<&CompressedTokenAccount> { - self.interface.as_compressed_token() + self.interface.compressed() } - /// Whether this spec is for a token PDA (cold context is Token variant). + /// Whether this spec is for a token PDA (compressed account is a token). #[must_use] pub fn is_token_pda(&self) -> bool { self.interface.as_compressed_token().is_some() diff --git a/sdk-libs/client/src/interface/load_accounts.rs b/sdk-libs/client/src/interface/load_accounts.rs index e332c7ccab..04a03c85c5 100644 --- a/sdk-libs/client/src/interface/load_accounts.rs +++ b/sdk-libs/client/src/interface/load_accounts.rs @@ -284,18 +284,15 @@ where .map_err(|e| LoadAccountsError::BuildInstruction(e.to_string())) } -struct AtaContext<'a> { - compressed: &'a CompressedTokenAccount, +struct AtaContext { + compressed: CompressedTokenAccount, wallet_owner: Pubkey, mint: Pubkey, bump: u8, } -impl<'a> AtaContext<'a> { - fn from_interface( - iface: &'a AccountInterface, - index: usize, - ) -> Result { +impl AtaContext { + fn from_interface(iface: &AccountInterface, index: usize) -> Result { let compressed = iface .as_compressed_token() diff --git a/sdk-libs/client/src/interface/mod.rs b/sdk-libs/client/src/interface/mod.rs index 31ccd583d8..15323ae6da 100644 --- a/sdk-libs/client/src/interface/mod.rs +++ b/sdk-libs/client/src/interface/mod.rs @@ -10,7 +10,7 @@ pub mod load_accounts; pub mod pack; pub mod tx_size; -pub use account_interface::{AccountInterface, AccountInterfaceError, ColdContext}; +pub use account_interface::AccountInterface; pub use create_accounts_proof::{ get_create_accounts_proof, CreateAccountsProofError, CreateAccountsProofInput, CreateAccountsProofResult, diff --git a/sdk-libs/client/src/rpc/client.rs b/sdk-libs/client/src/rpc/client.rs index aa9a8704af..4fcab68150 100644 --- a/sdk-libs/client/src/rpc/client.rs +++ b/sdk-libs/client/src/rpc/client.rs @@ -35,10 +35,7 @@ use crate::rpc::get_light_state_tree_infos::{ default_state_tree_lookup_tables, get_light_state_tree_infos, }; use crate::{ - indexer::{ - photon_indexer::PhotonIndexer, AccountInterface as IndexerAccountInterface, Indexer, - IndexerRpcConfig, Response, TreeInfo, - }, + indexer::{photon_indexer::PhotonIndexer, Indexer, IndexerRpcConfig, Response, TreeInfo}, interface::AccountInterface, rpc::{errors::RpcError, merkle_tree::MerkleTreeExt, Rpc}, }; @@ -501,69 +498,6 @@ impl LightClient { } } -// Conversion helpers from indexer types to interface types - -use crate::indexer::ColdContext as IndexerColdContext; - -fn cold_context_to_compressed_account( - cold: &IndexerColdContext, - lamports: u64, - owner: Pubkey, -) -> crate::indexer::CompressedAccount { - use light_compressed_account::compressed_account::CompressedAccountData; - - crate::indexer::CompressedAccount { - address: cold.address, - data: Some(CompressedAccountData { - discriminator: cold.data.discriminator, - data: cold.data.data.clone(), - data_hash: cold.data.data_hash, - }), - hash: cold.hash, - lamports, - leaf_index: cold.leaf_index as u32, - owner, - prove_by_index: cold.prove_by_index, - seq: cold.tree_info.seq, - slot_created: cold.tree_info.slot_created, - tree_info: TreeInfo { - tree: cold.tree_info.tree, - queue: cold.tree_info.queue, - cpi_context: None, - next_tree_info: None, - tree_type: cold.tree_info.tree_type, - }, - } -} - -fn convert_account_interface( - indexer_ai: IndexerAccountInterface, -) -> Result { - let account = Account { - lamports: indexer_ai.account.lamports, - data: indexer_ai.account.data, - owner: indexer_ai.account.owner, - executable: indexer_ai.account.executable, - rent_epoch: indexer_ai.account.rent_epoch, - }; - - match indexer_ai.cold { - None => Ok(AccountInterface::hot(indexer_ai.key, account)), - Some(cold) => { - let compressed = cold_context_to_compressed_account( - &cold, - indexer_ai.account.lamports, - indexer_ai.account.owner, - ); - Ok(AccountInterface::cold( - indexer_ai.key, - compressed, - indexer_ai.account.owner, - )) - } - } -} - #[async_trait] impl Rpc for LightClient { async fn new(config: LightClientConfig) -> Result @@ -1016,16 +950,10 @@ impl Rpc for LightClient { .indexer .as_ref() .ok_or(RpcError::IndexerNotInitialized)?; - let resp = indexer + indexer .get_account_interface(address, config) .await - .map_err(|e| RpcError::CustomError(format!("Indexer error: {e}")))?; - - let value = resp.value.map(convert_account_interface).transpose()?; - Ok(Response { - context: resp.context, - value, - }) + .map_err(|e| RpcError::CustomError(format!("Indexer error: {e}"))) } async fn get_multiple_account_interfaces( @@ -1037,21 +965,10 @@ impl Rpc for LightClient { .indexer .as_ref() .ok_or(RpcError::IndexerNotInitialized)?; - let resp = indexer + indexer .get_multiple_account_interfaces(addresses, config) .await - .map_err(|e| RpcError::CustomError(format!("Indexer error: {e}")))?; - - let value: Result>, RpcError> = resp - .value - .into_iter() - .map(|opt| opt.map(convert_account_interface).transpose()) - .collect(); - - Ok(Response { - context: resp.context, - value: value?, - }) + .map_err(|e| RpcError::CustomError(format!("Indexer error: {e}"))) } } diff --git a/sdk-libs/program-test/src/program_test/rpc.rs b/sdk-libs/program-test/src/program_test/rpc.rs index f8d44f862e..af9381ae47 100644 --- a/sdk-libs/program-test/src/program_test/rpc.rs +++ b/sdk-libs/program-test/src/program_test/rpc.rs @@ -4,7 +4,7 @@ use anchor_lang::pubkey; use async_trait::async_trait; use borsh::BorshDeserialize; use light_client::{ - indexer::{CompressedAccount, CompressedTokenAccount, Context, Indexer, Response, TreeInfo}, + indexer::{CompressedAccount, Context, Indexer, Response, TreeInfo}, interface::AccountInterface, rpc::{LightClientConfig, Rpc, RpcError}, }; @@ -385,13 +385,12 @@ impl Rpc for LightProgramTest { } } - // Cold: check TestIndexer by onchain pubkey (mirrors Photon behavior) + // Cold: check TestIndexer (mirrors Photon cold_lookup behavior) if let Some(indexer) = self.indexer.as_ref() { // First try: lookup by onchain_pubkey (for accounts with DECOMPRESSED_PDA_DISCRIMINATOR) if let Some(compressed_with_ctx) = indexer.find_compressed_account_by_onchain_pubkey(&address.to_bytes()) { - let owner: Pubkey = compressed_with_ctx.compressed_account.owner.into(); let compressed: CompressedAccount = compressed_with_ctx.clone().try_into().map_err( |e| { RpcError::CustomError(format!( @@ -400,10 +399,14 @@ impl Rpc for LightProgramTest { )) }, )?; - + let synthetic = synthesize_pda_account(&compressed); return Ok(Response { context: Context { slot }, - value: Some(AccountInterface::cold(*address, compressed, owner)), + value: Some(AccountInterface::cold( + *address, + synthetic, + vec![compressed], + )), }); } @@ -411,7 +414,6 @@ impl Rpc for LightProgramTest { if let Some(compressed_with_ctx) = indexer.find_compressed_account_by_pda_seed(&address.to_bytes()) { - let owner: Pubkey = compressed_with_ctx.compressed_account.owner.into(); let compressed: CompressedAccount = compressed_with_ctx.clone().try_into().map_err( |e| { RpcError::CustomError(format!( @@ -420,44 +422,44 @@ impl Rpc for LightProgramTest { )) }, )?; - + let synthetic = synthesize_pda_account(&compressed); return Ok(Response { context: Context { slot }, - value: Some(AccountInterface::cold(*address, compressed, owner)), + value: Some(AccountInterface::cold( + *address, + synthetic, + vec![compressed], + )), }); } // Third try: lookup in token_compressed_accounts - // Try by onchain_pubkey discriminator, PDA seed, then by token_data.owner let token_acc = indexer .find_token_account_by_onchain_pubkey(&address.to_bytes()) .or_else(|| indexer.find_token_account_by_pda_seed(&address.to_bytes())); if let Some(token_acc) = token_acc { - let compressed_account: CompressedAccount = token_acc + let compressed: CompressedAccount = token_acc .compressed_account .clone() .try_into() .map_err(|e| RpcError::CustomError(format!("conversion error: {:?}", e)))?; - - let compressed_token = CompressedTokenAccount { - token: token_acc.token_data.clone(), - account: compressed_account, - }; - - // For ATAs, use the wallet owner from the ata_owner_map let wallet_owner = indexer .ata_owner_map .get(address) .copied() .unwrap_or(*address); - + let synthetic = synthesize_token_account( + &token_acc.token_data, + &wallet_owner, + compressed.lamports, + ); return Ok(Response { context: Context { slot }, - value: Some(AccountInterface::cold_token( + value: Some(AccountInterface::cold( *address, - compressed_token, - wallet_owner, + synthetic, + vec![compressed], )), }); } @@ -471,18 +473,20 @@ impl Rpc for LightProgramTest { let items = result.value.items; if items.len() == 1 { let token_acc = items.into_iter().next().unwrap(); - // For ATAs, use the wallet owner from the ata_owner_map let wallet_owner = indexer .ata_owner_map .get(address) .copied() .unwrap_or(*address); + let lamports = token_acc.account.lamports; + let compressed = token_acc.account; + let synthetic = synthesize_token_account(&token_acc.token, &wallet_owner, lamports); return Ok(Response { context: Context { slot }, - value: Some(AccountInterface::cold_token( + value: Some(AccountInterface::cold( *address, - token_acc, - wallet_owner, + synthetic, + vec![compressed], )), }); } @@ -524,7 +528,6 @@ impl Rpc for LightProgramTest { continue; } } - // Not found on-chain or has 0 lamports, need cold lookup cold_lookup_indices.push(i); cold_lookup_pubkeys.push(address.to_bytes()); } @@ -532,27 +535,25 @@ impl Rpc for LightProgramTest { // Batch lookup cold accounts from TestIndexer if !cold_lookup_pubkeys.is_empty() { if let Some(indexer) = self.indexer.as_ref() { - // First pass: search in compressed_accounts (PDAs, mints) + // First pass: search by onchain_pubkey let cold_results = indexer .find_multiple_compressed_accounts_by_onchain_pubkeys(&cold_lookup_pubkeys); - // Track which addresses still need PDA seed or token lookup let mut pda_seed_lookup_indices: Vec = Vec::new(); let mut pda_seed_lookup_pubkeys: Vec<[u8; 32]> = Vec::new(); for (lookup_idx, maybe_compressed) in cold_results.into_iter().enumerate() { let original_idx = cold_lookup_indices[lookup_idx]; if let Some(compressed_with_ctx) = maybe_compressed { - let owner: Pubkey = compressed_with_ctx.compressed_account.owner.into(); let compressed: CompressedAccount = compressed_with_ctx.clone().try_into().map_err(|e| { RpcError::CustomError(format!("conversion error: {:?}", e)) })?; - + let synthetic = synthesize_pda_account(&compressed); results[original_idx] = Some(AccountInterface::cold( *addresses[original_idx], - compressed, - owner, + synthetic, + vec![compressed], )); } else { pda_seed_lookup_indices.push(original_idx); @@ -560,7 +561,7 @@ impl Rpc for LightProgramTest { } } - // Second pass: try PDA seed derivation for accounts not found by onchain_pubkey + // Second pass: try PDA seed derivation let mut token_lookup_indices: Vec = Vec::new(); let mut token_lookup_pubkeys: Vec<[u8; 32]> = Vec::new(); @@ -569,16 +570,15 @@ impl Rpc for LightProgramTest { if let Some(compressed_with_ctx) = indexer.find_compressed_account_by_pda_seed(pubkey) { - let owner: Pubkey = compressed_with_ctx.compressed_account.owner.into(); let compressed: CompressedAccount = compressed_with_ctx.clone().try_into().map_err(|e| { RpcError::CustomError(format!("conversion error: {:?}", e)) })?; - + let synthetic = synthesize_pda_account(&compressed); results[original_idx] = Some(AccountInterface::cold( *addresses[original_idx], - compressed, - owner, + synthetic, + vec![compressed], )); } else { token_lookup_indices.push(original_idx); @@ -587,7 +587,6 @@ impl Rpc for LightProgramTest { } // Third pass: search in token_compressed_accounts - // Track addresses that still need owner-based lookup let mut owner_lookup_indices: Vec = Vec::new(); let mut owner_lookup_pubkeys: Vec = Vec::new(); @@ -598,35 +597,30 @@ impl Rpc for LightProgramTest { .or_else(|| indexer.find_token_account_by_pda_seed(pubkey)); if let Some(token_acc) = token_acc { - let compressed_account: CompressedAccount = token_acc + let compressed: CompressedAccount = token_acc .compressed_account .clone() .try_into() .map_err(|e| { RpcError::CustomError(format!("conversion error: {:?}", e)) })?; - - let compressed_token = CompressedTokenAccount { - token: token_acc.token_data.clone(), - account: compressed_account, - }; - - // For ATAs, use the wallet owner from the ata_owner_map let addr = addresses[original_idx]; let wallet_owner = indexer.ata_owner_map.get(addr).copied().unwrap_or(*addr); - results[original_idx] = Some(AccountInterface::cold_token( - *addr, - compressed_token, - wallet_owner, - )); + let synthetic = synthesize_token_account( + &token_acc.token_data, + &wallet_owner, + compressed.lamports, + ); + results[original_idx] = + Some(AccountInterface::cold(*addr, synthetic, vec![compressed])); } else { owner_lookup_indices.push(original_idx); owner_lookup_pubkeys.push(*addresses[original_idx]); } } - // Fourth pass: search by token_data.owner (for program-owned tokens) + // Fourth pass: search by token_data.owner for (i, pubkey) in owner_lookup_pubkeys.iter().enumerate() { let original_idx = owner_lookup_indices[i]; let result = indexer @@ -637,12 +631,15 @@ impl Rpc for LightProgramTest { let items = result.value.items; if items.len() == 1 { let token_acc = items.into_iter().next().unwrap(); - // For ATAs, use the wallet owner from the ata_owner_map let addr = addresses[original_idx]; let wallet_owner = indexer.ata_owner_map.get(addr).copied().unwrap_or(*addr); + let lamports = token_acc.account.lamports; + let compressed = token_acc.account; + let synthetic = + synthesize_token_account(&token_acc.token, &wallet_owner, lamports); results[original_idx] = - Some(AccountInterface::cold_token(*addr, token_acc, wallet_owner)); + Some(AccountInterface::cold(*addr, synthetic, vec![compressed])); } } } @@ -655,6 +652,82 @@ impl Rpc for LightProgramTest { } } +/// Synthesize an on-chain Account from a compressed PDA/mint account. +/// Mirrors Photon's `cold_to_synthetic_account_data` fallback path: +/// discriminator(8) || data bytes, with owner = compressed account owner. +fn synthesize_pda_account(compressed: &CompressedAccount) -> Account { + let data = match &compressed.data { + Some(d) => { + let mut bytes = Vec::with_capacity(8 + d.data.len()); + bytes.extend_from_slice(&d.discriminator); + bytes.extend_from_slice(&d.data); + bytes + } + None => Vec::new(), + }; + Account { + lamports: compressed.lamports, + data, + owner: compressed.owner, + executable: false, + rent_epoch: u64::MAX, + } +} + +/// Synthesize an on-chain Account from compressed token data. +/// Mirrors Photon's `build_spl_token_account_bytes`: +/// 165-byte SPL Token account layout with wallet_owner as the owner field. +fn synthesize_token_account( + token: &light_token::compat::TokenData, + wallet_owner: &Pubkey, + lamports: u64, +) -> Account { + const SPL_TOKEN_ACCOUNT_LEN: usize = 165; + let mut buf = Vec::with_capacity(SPL_TOKEN_ACCOUNT_LEN); + + // [0..32] mint + buf.extend_from_slice(&token.mint.to_bytes()); + // [32..64] owner (wallet owner, NOT compressed token owner) + buf.extend_from_slice(&wallet_owner.to_bytes()); + // [64..72] amount + buf.extend_from_slice(&token.amount.to_le_bytes()); + // [72..108] delegate: COption + match &token.delegate { + Some(delegate) => { + buf.extend_from_slice(&1u32.to_le_bytes()); + buf.extend_from_slice(&delegate.to_bytes()); + } + None => { + buf.extend_from_slice(&0u32.to_le_bytes()); + buf.extend_from_slice(&[0u8; 32]); + } + } + // [108] state: Initialized(0) -> SPL 1, Frozen(1) -> SPL 2 + let spl_state: u8 = match token.state { + light_token::compat::AccountState::Initialized => 1, + light_token::compat::AccountState::Frozen => 2, + }; + buf.push(spl_state); + // [109..121] is_native: COption = None + buf.extend_from_slice(&0u32.to_le_bytes()); + buf.extend_from_slice(&0u64.to_le_bytes()); + // [121..129] delegated_amount = 0 + buf.extend_from_slice(&0u64.to_le_bytes()); + // [129..165] close_authority: COption = None + buf.extend_from_slice(&0u32.to_le_bytes()); + buf.extend_from_slice(&[0u8; 32]); + + debug_assert_eq!(buf.len(), SPL_TOKEN_ACCOUNT_LEN); + + Account { + lamports, + data: buf, + owner: light_token::instruction::LIGHT_TOKEN_PROGRAM_ID, + executable: false, + rent_epoch: u64::MAX, + } +} + impl LightProgramTest { fn maybe_print_logs(&self, logs: impl std::fmt::Display) { // Use enhanced logging if enabled and RUST_BACKTRACE is set diff --git a/sdk-tests/csdk-anchor-full-derived-test/tests/amm_test.rs b/sdk-tests/csdk-anchor-full-derived-test/tests/amm_test.rs index 986b6641d2..05f39e2572 100644 --- a/sdk-tests/csdk-anchor-full-derived-test/tests/amm_test.rs +++ b/sdk-tests/csdk-anchor-full-derived-test/tests/amm_test.rs @@ -25,6 +25,26 @@ use light_program_test::{ program_test::{setup_mock_program_data, LightProgramTest, TestRpc}, Indexer, ProgramTestConfig, Rpc, }; + +async fn assert_hot(rpc: &LightProgramTest, address: &Pubkey, name: &str) { + let ai = rpc + .get_account_interface(address, None) + .await + .unwrap() + .value + .unwrap_or_else(|| panic!("{} should exist", name)); + assert!(ai.is_hot(), "{} ({}) should be hot", name, address); +} + +async fn assert_cold(rpc: &LightProgramTest, address: &Pubkey, name: &str) { + let ai = rpc + .get_account_interface(address, None) + .await + .unwrap() + .value + .unwrap_or_else(|| panic!("{} should exist", name)); + assert!(ai.is_cold(), "{} ({}) should be cold", name, address); +} use light_token::instruction::{ find_mint_address, get_associated_token_address_and_bump, LIGHT_TOKEN_CONFIG, LIGHT_TOKEN_CPI_AUTHORITY, LIGHT_TOKEN_PROGRAM_ID, LIGHT_TOKEN_RENT_SPONSOR, @@ -325,21 +345,21 @@ async fn test_amm_full_lifecycle() { .await .expect("Initialize pool should succeed"); - shared::assert_onchain_exists(&mut ctx.rpc, &pdas.pool_state, "pool state").await; - shared::assert_onchain_exists(&mut ctx.rpc, &pdas.observation_state, "observation state").await; - shared::assert_onchain_exists(&mut ctx.rpc, &pdas.lp_mint, "LP mint").await; - shared::assert_onchain_exists(&mut ctx.rpc, &pdas.token_0_vault, "token 0 vault").await; - shared::assert_onchain_exists(&mut ctx.rpc, &pdas.token_1_vault, "token 1 vault").await; - shared::assert_onchain_exists(&mut ctx.rpc, &pdas.creator_lp_token, "creator LP token").await; + assert_hot(&ctx.rpc, &pdas.pool_state, "pool state").await; + assert_hot(&ctx.rpc, &pdas.observation_state, "observation state").await; + assert_hot(&ctx.rpc, &pdas.lp_mint, "LP mint").await; + assert_hot(&ctx.rpc, &pdas.token_0_vault, "token 0 vault").await; + assert_hot(&ctx.rpc, &pdas.token_1_vault, "token 1 vault").await; + assert_hot(&ctx.rpc, &pdas.creator_lp_token, "creator LP token").await; - let lp_token_data = parse_token( - &ctx.rpc - .get_account(pdas.creator_lp_token) - .await - .unwrap() - .unwrap() - .data, - ); + let creator_lp_interface = ctx + .rpc + .get_account_interface(&pdas.creator_lp_token, None) + .await + .unwrap() + .value + .expect("creator_lp_token should exist"); + let lp_token_data = parse_token(creator_lp_interface.data()); let initial_lp_balance = lp_token_data.amount; assert!( initial_lp_balance > 0, @@ -351,9 +371,16 @@ async fn test_amm_full_lifecycle() { use csdk_anchor_full_derived_test::amm_test::{ Observation, ObservationState, PoolState, OBSERVATION_NUM, }; - let pool_account = ctx.rpc.get_account(pdas.pool_state).await.unwrap().unwrap(); + let pool_interface = ctx + .rpc + .get_account_interface(&pdas.pool_state, None) + .await + .unwrap() + .value + .expect("pool_state should exist"); + let mut pool_data = pool_interface.data(); let pool_state: PoolState = - anchor_lang::AccountDeserialize::try_deserialize(&mut &pool_account.data[..]).unwrap(); + anchor_lang::AccountDeserialize::try_deserialize(&mut pool_data).unwrap(); let expected_pool = PoolState { compression_info: shared::expected_compression_info(&pool_state.compression_info), amm_config: ctx.amm_config.pubkey(), @@ -386,14 +413,16 @@ async fn test_amm_full_lifecycle() { ); // ObservationState assertion - let obs_account = ctx + let obs_interface = ctx .rpc - .get_account(pdas.observation_state) + .get_account_interface(&pdas.observation_state, None) .await .unwrap() - .unwrap(); + .value + .expect("observation_state should exist"); + let mut obs_data = obs_interface.data(); let obs_state: ObservationState = - anchor_lang::AccountDeserialize::try_deserialize(&mut &obs_account.data[..]).unwrap(); + anchor_lang::AccountDeserialize::try_deserialize(&mut obs_data).unwrap(); let expected_obs = ObservationState { compression_info: shared::expected_compression_info(&obs_state.compression_info), initialized: false, @@ -410,14 +439,14 @@ async fn test_amm_full_lifecycle() { // Full-struct Token assertions after init { - let token_0_vault_data = parse_token( - &ctx.rpc - .get_account(pdas.token_0_vault) - .await - .unwrap() - .unwrap() - .data, - ); + let token_0_interface = ctx + .rpc + .get_account_interface(&pdas.token_0_vault, None) + .await + .unwrap() + .value + .expect("token_0_vault should exist"); + let token_0_vault_data = parse_token(token_0_interface.data()); let expected_token_0 = Token { mint: ctx.token_0_mint.into(), owner: pdas.authority.into(), @@ -435,14 +464,14 @@ async fn test_amm_full_lifecycle() { "token_0_vault should match after init" ); - let token_1_vault_data = parse_token( - &ctx.rpc - .get_account(pdas.token_1_vault) - .await - .unwrap() - .unwrap() - .data, - ); + let token_1_interface = ctx + .rpc + .get_account_interface(&pdas.token_1_vault, None) + .await + .unwrap() + .value + .expect("token_1_vault should exist"); + let token_1_vault_data = parse_token(token_1_interface.data()); let expected_token_1 = Token { mint: ctx.token_1_mint.into(), owner: pdas.authority.into(), @@ -501,14 +530,14 @@ async fn test_amm_full_lifecycle() { .expect("Deposit should succeed"); // Verify LP balance after deposit - let lp_token_data_after_deposit = parse_token( - &ctx.rpc - .get_account(pdas.creator_lp_token) - .await - .unwrap() - .unwrap() - .data, - ); + let lp_interface_after_deposit = ctx + .rpc + .get_account_interface(&pdas.creator_lp_token, None) + .await + .unwrap() + .value + .expect("creator_lp_token should exist"); + let lp_token_data_after_deposit = parse_token(lp_interface_after_deposit.data()); let expected_balance_after_deposit = initial_lp_balance + deposit_amount; assert_eq!( lp_token_data_after_deposit.amount, expected_balance_after_deposit, @@ -554,14 +583,14 @@ async fn test_amm_full_lifecycle() { .await .expect("Withdraw should succeed"); - let lp_token_data_after_withdraw = parse_token( - &ctx.rpc - .get_account(pdas.creator_lp_token) - .await - .unwrap() - .unwrap() - .data, - ); + let lp_interface_after_withdraw = ctx + .rpc + .get_account_interface(&pdas.creator_lp_token, None) + .await + .unwrap() + .value + .expect("creator_lp_token should exist"); + let lp_token_data_after_withdraw = parse_token(lp_interface_after_withdraw.data()); let expected_balance_after_withdraw = expected_balance_after_deposit - withdraw_amount; assert_eq!( lp_token_data_after_withdraw.amount, expected_balance_after_withdraw, @@ -593,13 +622,13 @@ async fn test_amm_full_lifecycle() { &address_tree_pubkey, ); - // Assert compression (assert_after_compression) - shared::assert_onchain_closed(&mut ctx.rpc, &pdas.pool_state, "pool_state").await; - shared::assert_onchain_closed(&mut ctx.rpc, &pdas.observation_state, "observation_state").await; - shared::assert_onchain_closed(&mut ctx.rpc, &pdas.lp_mint, "lp_mint").await; - shared::assert_onchain_closed(&mut ctx.rpc, &pdas.token_0_vault, "token_0_vault").await; - shared::assert_onchain_closed(&mut ctx.rpc, &pdas.token_1_vault, "token_1_vault").await; - shared::assert_onchain_closed(&mut ctx.rpc, &pdas.creator_lp_token, "creator_lp_token").await; + // Assert compression (accounts should be cold) + assert_cold(&ctx.rpc, &pdas.pool_state, "pool_state").await; + assert_cold(&ctx.rpc, &pdas.observation_state, "observation_state").await; + assert_cold(&ctx.rpc, &pdas.lp_mint, "lp_mint").await; + assert_cold(&ctx.rpc, &pdas.token_0_vault, "token_0_vault").await; + assert_cold(&ctx.rpc, &pdas.token_1_vault, "token_1_vault").await; + assert_cold(&ctx.rpc, &pdas.creator_lp_token, "creator_lp_token").await; // Verify compressed accounts exist with non-empty data shared::assert_compressed_exists_with_data(&mut ctx.rpc, pool_compressed_address, "pool_state") @@ -633,7 +662,6 @@ async fn test_amm_full_lifecycle() { .expect("failed to get pool_state") .value .expect("pool_state should exist"); - assert!(pool_interface.is_cold(), "pool_state should be cold"); // Create SDK from pool state data. let sdk = @@ -686,21 +714,28 @@ async fn test_amm_full_lifecycle() { .await .expect("Decompression should succeed"); - shared::assert_onchain_exists(&mut ctx.rpc, &pdas.pool_state, "pool_state").await; - shared::assert_onchain_exists(&mut ctx.rpc, &pdas.observation_state, "observation_state").await; - shared::assert_onchain_exists(&mut ctx.rpc, &pdas.lp_mint, "lp_mint").await; - shared::assert_onchain_exists(&mut ctx.rpc, &pdas.token_0_vault, "token_0_vault").await; - shared::assert_onchain_exists(&mut ctx.rpc, &pdas.token_1_vault, "token_1_vault").await; - shared::assert_onchain_exists(&mut ctx.rpc, &pdas.creator_lp_token, "creator_lp_token").await; + assert_hot(&ctx.rpc, &pdas.pool_state, "pool_state").await; + assert_hot(&ctx.rpc, &pdas.observation_state, "observation_state").await; + assert_hot(&ctx.rpc, &pdas.lp_mint, "lp_mint").await; + assert_hot(&ctx.rpc, &pdas.token_0_vault, "token_0_vault").await; + assert_hot(&ctx.rpc, &pdas.token_1_vault, "token_1_vault").await; + assert_hot(&ctx.rpc, &pdas.creator_lp_token, "creator_lp_token").await; // Full-struct assertion for PoolState after decompression { use csdk_anchor_full_derived_test::amm_test::{ Observation, ObservationState, PoolState, OBSERVATION_NUM, }; - let pool_account = ctx.rpc.get_account(pdas.pool_state).await.unwrap().unwrap(); + let pool_interface = ctx + .rpc + .get_account_interface(&pdas.pool_state, None) + .await + .unwrap() + .value + .expect("pool_state should exist"); + let mut pool_data = pool_interface.data(); let pool_state: PoolState = - anchor_lang::AccountDeserialize::try_deserialize(&mut &pool_account.data[..]).unwrap(); + anchor_lang::AccountDeserialize::try_deserialize(&mut pool_data).unwrap(); let expected_pool = PoolState { compression_info: shared::expected_compression_info(&pool_state.compression_info), amm_config: ctx.amm_config.pubkey(), @@ -732,14 +767,16 @@ async fn test_amm_full_lifecycle() { "PoolState should match after decompression" ); - let obs_account = ctx + let obs_interface = ctx .rpc - .get_account(pdas.observation_state) + .get_account_interface(&pdas.observation_state, None) .await .unwrap() - .unwrap(); + .value + .expect("observation_state should exist"); + let mut obs_data = obs_interface.data(); let obs_state: ObservationState = - anchor_lang::AccountDeserialize::try_deserialize(&mut &obs_account.data[..]).unwrap(); + anchor_lang::AccountDeserialize::try_deserialize(&mut obs_data).unwrap(); let expected_obs = ObservationState { compression_info: shared::expected_compression_info(&obs_state.compression_info), initialized: false, @@ -755,28 +792,28 @@ async fn test_amm_full_lifecycle() { } // Verify LP token balance - let lp_token_after_decompression = parse_token( - &ctx.rpc - .get_account(pdas.creator_lp_token) - .await - .unwrap() - .unwrap() - .data, - ); + let lp_interface_after_decompression = ctx + .rpc + .get_account_interface(&pdas.creator_lp_token, None) + .await + .unwrap() + .value + .expect("creator_lp_token should exist"); + let lp_token_after_decompression = parse_token(lp_interface_after_decompression.data()); assert_eq!( lp_token_after_decompression.amount, expected_balance_after_withdraw, "LP token balance should be preserved after decompression" ); // Verify token account owners after decompression using full struct comparison - let token_0_vault_data = parse_token( - &ctx.rpc - .get_account(pdas.token_0_vault) - .await - .unwrap() - .unwrap() - .data, - ); + let token_0_interface = ctx + .rpc + .get_account_interface(&pdas.token_0_vault, None) + .await + .unwrap() + .value + .expect("token_0_vault should exist"); + let token_0_vault_data = parse_token(token_0_interface.data()); let expected_token_0_vault = Token { mint: ctx.token_0_mint.into(), owner: pdas.authority.into(), @@ -794,14 +831,14 @@ async fn test_amm_full_lifecycle() { "token_0_vault should match expected after decompression" ); - let token_1_vault_data = parse_token( - &ctx.rpc - .get_account(pdas.token_1_vault) - .await - .unwrap() - .unwrap() - .data, - ); + let token_1_interface = ctx + .rpc + .get_account_interface(&pdas.token_1_vault, None) + .await + .unwrap() + .value + .expect("token_1_vault should exist"); + let token_1_vault_data = parse_token(token_1_interface.data()); let expected_token_1_vault = Token { mint: ctx.token_1_mint.into(), owner: pdas.authority.into(), @@ -836,17 +873,16 @@ async fn test_amm_full_lifecycle() { "creator_lp_token should match expected after decompression" ); - // Verify compressed token accounts - let remaining_vault_0 = ctx - .rpc - .get_compressed_token_accounts_by_owner(&pdas.token_0_vault, None, None) - .await - .unwrap() - .value - .items; + // Verify token_0_vault is hot (decompressed) after load assert!( - remaining_vault_0.is_empty(), - "Compressed token_0_vault should be consumed" + ctx.rpc + .get_account_interface(&pdas.token_0_vault, None) + .await + .unwrap() + .value + .unwrap() + .is_hot(), + "token_0_vault should be hot after decompression" ); let remaining_vault_1 = ctx From bdb7f7253dab0091b49eb8181a6c50889b47c7b1 Mon Sep 17 00:00:00 2001 From: Swenschaeferjohann Date: Thu, 12 Feb 2026 02:47:20 +0000 Subject: [PATCH 7/9] format --- sdk-libs/client/src/indexer/mod.rs | 4 ++-- sdk-libs/client/src/indexer/photon_indexer.rs | 21 +++++++++++-------- .../client/src/interface/account_interface.rs | 3 +-- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/sdk-libs/client/src/indexer/mod.rs b/sdk-libs/client/src/indexer/mod.rs index ec58233f2e..fa03606dfe 100644 --- a/sdk-libs/client/src/indexer/mod.rs +++ b/sdk-libs/client/src/indexer/mod.rs @@ -16,8 +16,8 @@ pub use response::{Context, Items, ItemsWithCursor, Response}; pub use types::{ AccountProofInputs, Address, AddressMerkleTreeAccounts, AddressProofInputs, AddressQueueData, AddressWithTree, CompressedAccount, CompressedTokenAccount, Hash, InputQueueData, MerkleProof, - MerkleProofWithContext, NewAddressProofWithContext, NextTreeInfo, OutputQueueData, OwnerBalance, - ProofOfLeaf, QueueElementsResult, QueueInfo, QueueInfoResult, RootIndex, + MerkleProofWithContext, NewAddressProofWithContext, NextTreeInfo, OutputQueueData, + OwnerBalance, ProofOfLeaf, QueueElementsResult, QueueInfo, QueueInfoResult, RootIndex, SignatureWithMetadata, StateMerkleTreeAccounts, StateQueueData, TokenBalance, TreeInfo, ValidityProofWithContext, }; diff --git a/sdk-libs/client/src/indexer/photon_indexer.rs b/sdk-libs/client/src/indexer/photon_indexer.rs index eedd0cb790..6b9f4a7de5 100644 --- a/sdk-libs/client/src/indexer/photon_indexer.rs +++ b/sdk-libs/client/src/indexer/photon_indexer.rs @@ -10,14 +10,16 @@ use tracing::{error, trace, warn}; use super::types::{ CompressedAccount, CompressedTokenAccount, OwnerBalance, SignatureWithMetadata, TokenBalance, }; -use crate::interface::AccountInterface; -use crate::indexer::{ - base58::Base58Conversions, - config::RetryConfig, - response::{Context, Items, ItemsWithCursor, Response}, - Address, AddressWithTree, GetCompressedAccountsByOwnerConfig, - GetCompressedTokenAccountsByOwnerOrDelegateOptions, Hash, Indexer, IndexerError, - IndexerRpcConfig, MerkleProof, NewAddressProofWithContext, PaginatedOptions, +use crate::{ + indexer::{ + base58::Base58Conversions, + config::RetryConfig, + response::{Context, Items, ItemsWithCursor, Response}, + Address, AddressWithTree, GetCompressedAccountsByOwnerConfig, + GetCompressedTokenAccountsByOwnerOrDelegateOptions, Hash, Indexer, IndexerError, + IndexerRpcConfig, MerkleProof, NewAddressProofWithContext, PaginatedOptions, + }, + interface::AccountInterface, }; // Tests are in program-tests/client-test/tests/light-client.rs @@ -1818,9 +1820,10 @@ impl PhotonIndexer { fn convert_photon_account_interface( ai: &photon_api::types::AccountInterface, ) -> Result { - use super::base58::decode_base58_to_fixed_array; use solana_account::Account; + use super::base58::decode_base58_to_fixed_array; + let key = Pubkey::new_from_array(decode_base58_to_fixed_array(&ai.key)?); let data = base64::decode_config(&*ai.account.data, base64::STANDARD_NO_PAD) .map_err(|e| IndexerError::decode_error("account.data", e))?; diff --git a/sdk-libs/client/src/interface/account_interface.rs b/sdk-libs/client/src/interface/account_interface.rs index 0fd45afc2b..1998885197 100644 --- a/sdk-libs/client/src/interface/account_interface.rs +++ b/sdk-libs/client/src/interface/account_interface.rs @@ -110,8 +110,7 @@ impl AccountInterface { return None; } - let token = - light_token::compat::TokenData::deserialize(&mut data.data.as_slice()).ok()?; + let token = light_token::compat::TokenData::deserialize(&mut data.data.as_slice()).ok()?; Some(CompressedTokenAccount { token, account: compressed.clone(), From 36a8a94264c337868a5cc2e7119013fc2558e297 Mon Sep 17 00:00:00 2001 From: Swenschaeferjohann Date: Thu, 12 Feb 2026 15:19:57 +0000 Subject: [PATCH 8/9] simplify: use consistent CompressedAccount type in client-side, avoid conversions with CompressedAccountWithMerkleContext --- Cargo.lock | 4 + .../compressed-token-test/tests/v1.rs | 700 +++++++++++------- program-tests/registry-test/tests/tests.rs | 25 +- program-tests/system-cpi-test/tests/test.rs | 64 +- .../tests/invoke_cpi_with_read_only.rs | 76 +- program-tests/system-test/tests/test.rs | 41 +- .../utils/src/assert_compressed_tx.rs | 33 +- program-tests/utils/src/assert_token_tx.rs | 63 +- program-tests/utils/src/e2e_test_env.rs | 115 ++- program-tests/utils/src/spl.rs | 76 +- .../program-test/src/indexer/extensions.rs | 14 +- .../program-test/src/indexer/test_indexer.rs | 200 ++--- .../src/program_test/extensions.rs | 14 +- sdk-libs/program-test/src/program_test/rpc.rs | 61 +- .../programs/sdk-anchor-test/tests/test.rs | 7 +- sdk-tests/sdk-native-test/Cargo.toml | 1 + sdk-tests/sdk-native-test/tests/test.rs | 22 +- sdk-tests/sdk-pinocchio-v1-test/Cargo.toml | 1 + sdk-tests/sdk-pinocchio-v1-test/tests/test.rs | 18 +- sdk-tests/sdk-pinocchio-v2-test/Cargo.toml | 1 + sdk-tests/sdk-pinocchio-v2-test/tests/test.rs | 22 +- sdk-tests/sdk-v1-native-test/Cargo.toml | 1 + sdk-tests/sdk-v1-native-test/tests/test.rs | 18 +- 23 files changed, 788 insertions(+), 789 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae16a0270b..7fe39acc6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6556,6 +6556,7 @@ name = "sdk-native-test" version = "1.0.0" dependencies = [ "borsh 0.10.4", + "light-client", "light-compressed-account", "light-hasher", "light-macros", @@ -6573,6 +6574,7 @@ name = "sdk-pinocchio-v1-test" version = "1.0.0" dependencies = [ "borsh 0.10.4", + "light-client", "light-compressed-account", "light-hasher", "light-macros", @@ -6590,6 +6592,7 @@ name = "sdk-pinocchio-v2-test" version = "1.0.0" dependencies = [ "borsh 0.10.4", + "light-client", "light-compressed-account", "light-hasher", "light-macros", @@ -6641,6 +6644,7 @@ name = "sdk-v1-native-test" version = "1.0.0" dependencies = [ "borsh 0.10.4", + "light-client", "light-compressed-account", "light-hasher", "light-macros", diff --git a/program-tests/compressed-token-test/tests/v1.rs b/program-tests/compressed-token-test/tests/v1.rs index 81c01c82fd..3765f725d8 100644 --- a/program-tests/compressed-token-test/tests/v1.rs +++ b/program-tests/compressed-token-test/tests/v1.rs @@ -12,7 +12,7 @@ use anchor_spl::{ }; use forester_utils::utils::airdrop_lamports; use light_client::{ - indexer::Indexer, + indexer::{CompressedTokenAccount, Indexer}, local_test_validator::{spawn_validator, LightValidatorConfig}, rpc::LightClientConfig, }; @@ -149,11 +149,12 @@ async fn test_wrapped_sol() { None, ) .await; - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&payer.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; decompress_test( &payer, &mut rpc, @@ -861,11 +862,12 @@ async fn perform_transfer_22_test( for _ in 0..outputs { recipients.push(Pubkey::new_unique()); } - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let equal_amount = (amount * inputs as u64) / outputs as u64; let rest_amount = (amount * inputs as u64) % outputs as u64; let mut output_amounts = vec![equal_amount; outputs - 1]; @@ -942,7 +944,8 @@ async fn test_decompression() { .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; decompress_test( &sender, &mut context, @@ -1077,14 +1080,15 @@ async fn test_mint_to_and_burn_from_all_token_pools() { iterator }; for i in iterator { - let accounts: Vec = test_indexer + let accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&payer.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let input_compressed_account = accounts[0].clone(); let change_account_merkle_tree = input_compressed_account - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -1172,14 +1176,15 @@ async fn test_multiple_decompression() { let mut iterator = vec![0, 1, 2, 3, 4]; iterator.shuffle(rng); for i in iterator { - let accounts: Vec = test_indexer + let accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let input_compressed_account = accounts .iter() - .filter(|x| x.token_data.amount != 0) + .filter(|x| x.token.amount != 0) .collect::>()[0] .clone(); println!("i = {}", i); @@ -1224,15 +1229,16 @@ async fn test_multiple_decompression() { // Decompress from all token pools { - let all_accounts: Vec = test_indexer + let all_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let input_compressed_accounts = all_accounts[0..4].to_vec(); let amount = input_compressed_accounts .iter() - .map(|x| x.token_data.amount) + .map(|x| x.token.amount) .sum(); let mut add_token_pool_accounts = (0..4) .map(|x| get_token_pool_pda_with_index(&mint, x)) @@ -1252,17 +1258,18 @@ async fn test_multiple_decompression() { Some(add_token_pool_accounts.clone()), ) .await; - let all_accounts: Vec = test_indexer + let all_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let input_compressed_accounts = all_accounts .iter() - .filter(|x| x.token_data.amount != 0) + .filter(|x| x.token.amount != 0) .collect::>()[0] .clone(); - let amount = input_compressed_accounts.token_data.amount; + let amount = input_compressed_accounts.token.amount; decompress_test( &sender, &mut context, @@ -1321,13 +1328,14 @@ async fn test_delegation( .await; // 1. Delegate tokens { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let delegated_compressed_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -1349,16 +1357,17 @@ async fn test_delegation( let recipient = Pubkey::new_unique(); // 2. Transfer partial delegated amount { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let input_compressed_accounts = input_compressed_accounts .iter() - .filter(|x| x.token_data.delegate.is_some()) + .filter(|x| x.token.delegate.is_some()) .cloned() - .collect::>(); + .collect::>(); compressed_transfer_test( &delegate, &mut rpc, @@ -1378,16 +1387,17 @@ async fn test_delegation( } // 3. Transfer full delegated amount { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let input_compressed_accounts = input_compressed_accounts .iter() - .filter(|x| x.token_data.delegate.is_some()) + .filter(|x| x.token.delegate.is_some()) .cloned() - .collect::>(); + .collect::>(); compressed_transfer_test( &delegate, &mut rpc, @@ -1459,13 +1469,14 @@ async fn test_delegation_mixed() { .await; // 1. Delegate tokens { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let delegated_compressed_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -1487,30 +1498,32 @@ async fn test_delegation_mixed() { let recipient = Pubkey::new_unique(); // 2. Transfer partial delegated amount with delegate change account { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let mut input_compressed_accounts = input_compressed_accounts .iter() - .filter(|x| x.token_data.delegate.is_some()) + .filter(|x| x.token.delegate.is_some()) .cloned() - .collect::>(); - let delegate_input_compressed_accounts: Vec = test_indexer + .collect::>(); + let delegate_input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&delegate.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; input_compressed_accounts .extend_from_slice(&[delegate_input_compressed_accounts[0].clone()]); let delegate_lamports = delegate_input_compressed_accounts[0] - .compressed_account - .compressed_account + .account + .account .lamports; let delegate_input_amount = input_compressed_accounts .iter() - .map(|x| x.token_data.amount) + .map(|x| x.token.amount) .sum::(); compressed_transfer_test( &delegate, @@ -1532,31 +1545,33 @@ async fn test_delegation_mixed() { let recipient = Pubkey::new_unique(); // 3. Transfer partial delegated amount without delegate change account { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let mut input_compressed_accounts = input_compressed_accounts .iter() - .filter(|x| x.token_data.delegate.is_some()) + .filter(|x| x.token.delegate.is_some()) .cloned() - .collect::>(); - let delegate_input_compressed_accounts: Vec = test_indexer + .collect::>(); + let delegate_input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&delegate.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; input_compressed_accounts .extend_from_slice(&[delegate_input_compressed_accounts[0].clone()]); let delegate_input_amount = input_compressed_accounts .iter() - .map(|x| x.token_data.amount) + .map(|x| x.token.amount) .sum::(); let lamports_output_amount = input_compressed_accounts .iter() - .map(|x| x.compressed_account.compressed_account.lamports) + .map(|x| x.account.lamports) .sum::() - 100; compressed_transfer_test( @@ -1579,26 +1594,28 @@ async fn test_delegation_mixed() { } // 3. Transfer full delegated amount { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let mut input_compressed_accounts = input_compressed_accounts .iter() - .filter(|x| x.token_data.delegate.is_some()) + .filter(|x| x.token.delegate.is_some()) .cloned() - .collect::>(); - let delegate_input_compressed_accounts: Vec = test_indexer + .collect::>(); + let delegate_input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&delegate.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; input_compressed_accounts.extend_from_slice(&delegate_input_compressed_accounts); let input_amount = input_compressed_accounts .iter() - .map(|x| x.token_data.amount) + .map(|x| x.token.amount) .sum::(); compressed_transfer_test( &delegate, @@ -1690,28 +1707,29 @@ async fn test_approve_failing() { ) .await; - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let delegated_amount = 1000u64; let delegated_compressed_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); let input_compressed_account_hashes = input_compressed_accounts .iter() - .map(|x| x.compressed_account.hash().unwrap()) + .map(|x| x.account.hash) .collect::>(); let proof_rpc_result = test_indexer .get_validity_proof(input_compressed_account_hashes, Vec::new(), None) .await .unwrap(); - let mint = input_compressed_accounts[0].token_data.mint; + let mint = input_compressed_accounts[0].token.mint; // 1. Invalid delegated compressed account Merkle tree. { @@ -1722,17 +1740,23 @@ async fn test_approve_failing() { authority: sender.pubkey(), input_merkle_contexts: input_compressed_accounts .iter() - .map(|x| x.compressed_account.merkle_context) + .map(|x| x.account + .tree_info + .to_light_merkle_context(x.account.leaf_index, x.account.prove_by_index)) .collect(), input_token_data: input_compressed_accounts .iter() - .map(|x| x.token_data.clone()) + .map(|x| x.token.clone()) .map(sdk_to_program_token_data) .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.compressed_account.compressed_account) - .cloned() + .map(|x| { + light_compressed_account::compressed_account::CompressedAccountWithMerkleContext::from( + x.account.clone(), + ) + .compressed_account + }) .collect::>(), mint, delegated_amount, @@ -1768,17 +1792,23 @@ async fn test_approve_failing() { authority: sender.pubkey(), input_merkle_contexts: input_compressed_accounts .iter() - .map(|x| x.compressed_account.merkle_context) + .map(|x| x.account + .tree_info + .to_light_merkle_context(x.account.leaf_index, x.account.prove_by_index)) .collect(), input_token_data: input_compressed_accounts .iter() - .map(|x| x.token_data.clone()) + .map(|x| x.token.clone()) .map(sdk_to_program_token_data) .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.compressed_account.compressed_account) - .cloned() + .map(|x| { + light_compressed_account::compressed_account::CompressedAccountWithMerkleContext::from( + x.account.clone(), + ) + .compressed_account + }) .collect::>(), mint, delegated_amount, @@ -1818,17 +1848,23 @@ async fn test_approve_failing() { authority: sender.pubkey(), input_merkle_contexts: input_compressed_accounts .iter() - .map(|x| x.compressed_account.merkle_context) + .map(|x| x.account + .tree_info + .to_light_merkle_context(x.account.leaf_index, x.account.prove_by_index)) .collect(), input_token_data: input_compressed_accounts .iter() - .map(|x| x.token_data.clone()) + .map(|x| x.token.clone()) .map(sdk_to_program_token_data) .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.compressed_account.compressed_account) - .cloned() + .map(|x| { + light_compressed_account::compressed_account::CompressedAccountWithMerkleContext::from( + x.account.clone(), + ) + .compressed_account + }) .collect::>(), mint, delegated_amount, @@ -1864,17 +1900,23 @@ async fn test_approve_failing() { authority: sender.pubkey(), input_merkle_contexts: input_compressed_accounts .iter() - .map(|x| x.compressed_account.merkle_context) + .map(|x| x.account + .tree_info + .to_light_merkle_context(x.account.leaf_index, x.account.prove_by_index)) .collect(), input_token_data: input_compressed_accounts .iter() - .map(|x| x.token_data.clone()) + .map(|x| x.token.clone()) .map(sdk_to_program_token_data) .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.compressed_account.compressed_account) - .cloned() + .map(|x| { + light_compressed_account::compressed_account::CompressedAccountWithMerkleContext::from( + x.account.clone(), + ) + .compressed_account + }) .collect::>(), mint: invalid_mint.pubkey(), delegated_amount, @@ -1905,7 +1947,7 @@ async fn test_approve_failing() { { let sum_inputs = input_compressed_accounts .iter() - .map(|x| x.token_data.amount) + .map(|x| x.token.amount) .sum::(); let delegated_amount = sum_inputs + 1; let inputs = CreateApproveInstructionInputs { @@ -1913,17 +1955,23 @@ async fn test_approve_failing() { authority: sender.pubkey(), input_merkle_contexts: input_compressed_accounts .iter() - .map(|x| x.compressed_account.merkle_context) + .map(|x| x.account + .tree_info + .to_light_merkle_context(x.account.leaf_index, x.account.prove_by_index)) .collect(), input_token_data: input_compressed_accounts .iter() - .map(|x| x.token_data.clone()) + .map(|x| x.token.clone()) .map(sdk_to_program_token_data) .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.compressed_account.compressed_account) - .cloned() + .map(|x| { + light_compressed_account::compressed_account::CompressedAccountWithMerkleContext::from( + x.account.clone(), + ) + .compressed_account + }) .collect::>(), mint, delegated_amount, @@ -1980,15 +2028,16 @@ async fn test_revoke(num_inputs: usize, mint_amount: u64, delegated_amount: u64) .await; // 1. Delegate tokens { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; for input in input_compressed_accounts.iter() { let input_compressed_accounts = vec![input.clone()]; let delegated_compressed_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -2009,7 +2058,7 @@ async fn test_revoke(num_inputs: usize, mint_amount: u64, delegated_amount: u64) } // 2. Revoke { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() @@ -2018,14 +2067,14 @@ async fn test_revoke(num_inputs: usize, mint_amount: u64, delegated_amount: u64) .iter() .filter(|x| x.token.delegate.is_some()) .map(|x| x.clone().into()) - .collect::>(); + .collect::>(); let input_compressed_accounts = input_compressed_accounts .iter() - .filter(|x| x.token_data.delegate.is_some()) + .filter(|x| x.token.delegate.is_some()) .cloned() - .collect::>(); + .collect::>(); let delegated_compressed_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -2104,14 +2153,15 @@ async fn test_revoke_failing() { .await; // Delegate tokens { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let delegated_amount = 1000u64; let delegated_compressed_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -2130,20 +2180,21 @@ async fn test_revoke_failing() { .await; } - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let input_compressed_accounts = input_compressed_accounts .iter() - .filter(|x| x.token_data.delegate.is_some()) + .filter(|x| x.token.delegate.is_some()) .cloned() - .collect::>(); + .collect::>(); let input_compressed_account_hashes = input_compressed_accounts .iter() - .map(|x| x.compressed_account.hash().unwrap()) + .map(|x| x.account.hash) .collect::>(); let proof_rpc_result = test_indexer .get_validity_proof(input_compressed_account_hashes, Vec::new(), None) @@ -2159,17 +2210,23 @@ async fn test_revoke_failing() { authority: sender.pubkey(), input_merkle_contexts: input_compressed_accounts .iter() - .map(|x| x.compressed_account.merkle_context) + .map(|x| x.account + .tree_info + .to_light_merkle_context(x.account.leaf_index, x.account.prove_by_index)) .collect(), input_token_data: input_compressed_accounts .iter() - .map(|x| x.token_data.clone()) + .map(|x| x.token.clone()) .map(sdk_to_program_token_data) .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.compressed_account.compressed_account) - .cloned() + .map(|x| { + light_compressed_account::compressed_account::CompressedAccountWithMerkleContext::from( + x.account.clone(), + ) + .compressed_account + }) .collect::>(), mint, output_account_merkle_tree: merkle_tree_pubkey, @@ -2201,17 +2258,23 @@ async fn test_revoke_failing() { authority: sender.pubkey(), input_merkle_contexts: input_compressed_accounts .iter() - .map(|x| x.compressed_account.merkle_context) + .map(|x| x.account + .tree_info + .to_light_merkle_context(x.account.leaf_index, x.account.prove_by_index)) .collect(), input_token_data: input_compressed_accounts .iter() - .map(|x| x.token_data.clone()) + .map(|x| x.token.clone()) .map(sdk_to_program_token_data) .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.compressed_account.compressed_account) - .cloned() + .map(|x| { + light_compressed_account::compressed_account::CompressedAccountWithMerkleContext::from( + x.account.clone(), + ) + .compressed_account + }) .collect::>(), mint, output_account_merkle_tree: invalid_merkle_tree.pubkey(), @@ -2244,17 +2307,23 @@ async fn test_revoke_failing() { authority: sender.pubkey(), input_merkle_contexts: input_compressed_accounts .iter() - .map(|x| x.compressed_account.merkle_context) + .map(|x| x.account + .tree_info + .to_light_merkle_context(x.account.leaf_index, x.account.prove_by_index)) .collect(), input_token_data: input_compressed_accounts .iter() - .map(|x| x.token_data.clone()) + .map(|x| x.token.clone()) .map(sdk_to_program_token_data) .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.compressed_account.compressed_account) - .cloned() + .map(|x| { + light_compressed_account::compressed_account::CompressedAccountWithMerkleContext::from( + x.account.clone(), + ) + .compressed_account + }) .collect::>(), mint: invalid_mint.pubkey(), output_account_merkle_tree: merkle_tree_pubkey, @@ -2327,14 +2396,15 @@ async fn test_burn() { .await; // 1. Burn tokens { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let burn_amount = 1000u64; let change_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -2354,14 +2424,15 @@ async fn test_burn() { } // 2. Delegate tokens { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let delegated_amount = 1000u64; let delegated_compressed_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -2381,19 +2452,20 @@ async fn test_burn() { } // 3. Burn delegated tokens { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let input_compressed_accounts = input_compressed_accounts .iter() - .filter(|x| x.token_data.delegate.is_some()) + .filter(|x| x.token.delegate.is_some()) .cloned() - .collect::>(); + .collect::>(); let burn_amount = 100; let change_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -2413,22 +2485,23 @@ async fn test_burn() { } // 3. Burn all delegated tokens { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let input_compressed_accounts = input_compressed_accounts .iter() - .filter(|x| x.token_data.delegate.is_some()) + .filter(|x| x.token.delegate.is_some()) .cloned() - .collect::>(); + .collect::>(); let burn_amount = input_compressed_accounts .iter() - .map(|x| x.token_data.amount) + .map(|x| x.token.amount) .sum::(); let change_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -2462,7 +2535,7 @@ async fn test_burn() { ) .await .unwrap(); - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() @@ -2475,10 +2548,10 @@ async fn test_burn() { .to_vec(); let burn_amount = input_compressed_accounts .iter() - .map(|x| x.token_data.amount) + .map(|x| x.token.amount) .sum(); let invalid_change_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .queue_pubkey .into(); @@ -2513,7 +2586,7 @@ async fn test_burn() { .unwrap(); let slot = rpc.get_slot().await.unwrap(); test_indexer.add_event_and_compressed_accounts(slot, &event); - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() @@ -2525,7 +2598,7 @@ async fn test_burn() { .collect::>(); let burn_amount = input_compressed_accounts .iter() - .map(|x| x.token_data.amount) + .map(|x| x.token.amount) .sum(); additional_token_pool_accounts.shuffle(rng); @@ -2595,14 +2668,15 @@ async fn failing_tests_burn() { .await; // Delegate tokens { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let delegated_amount = 1000u64; let delegated_compressed_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -2622,14 +2696,15 @@ async fn failing_tests_burn() { } // 1. invalid proof { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let burn_amount = 1; let change_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -2655,14 +2730,15 @@ async fn failing_tests_burn() { } // 2. Signer is delegate but token data has no delegate. { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let burn_amount = 1; let change_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -2691,19 +2767,20 @@ async fn failing_tests_burn() { } // 3. Signer is delegate but token data has no delegate. { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let input_compressed_accounts = input_compressed_accounts .iter() - .filter(|x| x.token_data.delegate.is_some()) + .filter(|x| x.token.delegate.is_some()) .cloned() - .collect::>(); + .collect::>(); let burn_amount = 1; let change_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -2728,14 +2805,15 @@ async fn failing_tests_burn() { } // 4. invalid authority (use delegate as authority) { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let burn_amount = 1; let change_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -2764,14 +2842,15 @@ async fn failing_tests_burn() { } // 5. invalid mint { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let burn_amount = 1; let change_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -2801,14 +2880,15 @@ async fn failing_tests_burn() { } // 6. invalid change merkle tree { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let burn_amount = 1; let invalid_change_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .queue_pubkey .into(); @@ -2838,14 +2918,15 @@ async fn failing_tests_burn() { } // 6. invalid token pool (not initialized) { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let burn_amount = 1; let invalid_change_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .queue_pubkey .into(); @@ -2870,14 +2951,15 @@ async fn failing_tests_burn() { } // 7. invalid token pool (invalid mint) { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let burn_amount = 1; let invalid_change_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .queue_pubkey .into(); @@ -2949,13 +3031,14 @@ async fn test_freeze_and_thaw(mint_amount: u64, delegated_amount: u64) { .await; // 1. Freeze tokens { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let output_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -2972,18 +3055,19 @@ async fn test_freeze_and_thaw(mint_amount: u64, delegated_amount: u64) { } // 2. Thaw tokens { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let input_compressed_accounts = input_compressed_accounts .iter() - .filter(|x| x.token_data.state == AccountState::Frozen) + .filter(|x| x.token.state == AccountState::Frozen) .cloned() - .collect::>(); + .collect::>(); let output_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -2999,13 +3083,14 @@ async fn test_freeze_and_thaw(mint_amount: u64, delegated_amount: u64) { } // 3. Delegate tokens { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let delegated_compressed_account_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -3025,13 +3110,14 @@ async fn test_freeze_and_thaw(mint_amount: u64, delegated_amount: u64) { } // 4. Freeze delegated tokens { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let output_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -3048,18 +3134,19 @@ async fn test_freeze_and_thaw(mint_amount: u64, delegated_amount: u64) { } // 5. Thaw delegated tokens { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let input_compressed_accounts = input_compressed_accounts .iter() - .filter(|x| x.token_data.state == AccountState::Frozen) + .filter(|x| x.token.state == AccountState::Frozen) .cloned() - .collect::>(); + .collect::>(); let output_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -3133,7 +3220,7 @@ async fn test_failing_freeze() { ) .await; - let input_compressed_accounts: Vec = vec![test_indexer + let input_compressed_accounts: Vec = vec![test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() @@ -3142,14 +3229,14 @@ async fn test_failing_freeze() { .clone() .into()]; let outputs_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); let input_compressed_account_hashes = input_compressed_accounts .iter() - .map(|x| x.compressed_account.hash().unwrap()) + .map(|x| x.account.hash) .collect::>(); let proof_rpc_result = test_indexer .get_validity_proof(input_compressed_account_hashes, Vec::new(), None) @@ -3166,16 +3253,20 @@ async fn test_failing_freeze() { authority: invalid_authority.pubkey(), input_merkle_contexts: input_compressed_accounts .iter() - .map(|x| x.compressed_account.merkle_context) + .map(|x| { + x.account + .tree_info + .to_light_merkle_context(x.account.leaf_index, x.account.prove_by_index) + }) .collect(), input_token_data: input_compressed_accounts .iter() - .map(|x| x.token_data.clone()) + .map(|x| x.token.clone()) .map(sdk_to_program_token_data) .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.compressed_account.compressed_account) + .map(|x| &x.account.account) .cloned() .collect::>(), outputs_merkle_tree, @@ -3201,16 +3292,20 @@ async fn test_failing_freeze() { authority: payer.pubkey(), input_merkle_contexts: input_compressed_accounts .iter() - .map(|x| x.compressed_account.merkle_context) + .map(|x| { + x.account + .tree_info + .to_light_merkle_context(x.account.leaf_index, x.account.prove_by_index) + }) .collect(), input_token_data: input_compressed_accounts .iter() - .map(|x| x.token_data.clone()) + .map(|x| x.token.clone()) .map(sdk_to_program_token_data) .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.compressed_account.compressed_account) + .map(|x| &x.account.account) .cloned() .collect::>(), outputs_merkle_tree: invalid_merkle_tree.pubkey(), @@ -3245,16 +3340,20 @@ async fn test_failing_freeze() { authority: payer.pubkey(), input_merkle_contexts: input_compressed_accounts .iter() - .map(|x| x.compressed_account.merkle_context) + .map(|x| { + x.account + .tree_info + .to_light_merkle_context(x.account.leaf_index, x.account.prove_by_index) + }) .collect(), input_token_data: input_compressed_accounts .iter() - .map(|x| x.token_data.clone()) + .map(|x| x.token.clone()) .map(sdk_to_program_token_data) .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.compressed_account.compressed_account) + .map(|x| &x.account.account) .cloned() .collect::>(), outputs_merkle_tree, @@ -3297,17 +3396,17 @@ async fn test_failing_freeze() { .filter(|x| x.token.state == AccountState::Frozen) .cloned() .collect::>(); - let input_compressed_accounts: Vec = + let input_compressed_accounts: Vec = vec![accounts[0].clone().into()]; let outputs_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); let input_compressed_account_hashes = input_compressed_accounts .iter() - .map(|x| x.compressed_account.hash().unwrap()) + .map(|x| x.account.hash) .collect::>(); let proof_rpc_result = test_indexer .get_validity_proof(input_compressed_account_hashes, Vec::new(), None) @@ -3318,16 +3417,20 @@ async fn test_failing_freeze() { authority: payer.pubkey(), input_merkle_contexts: input_compressed_accounts .iter() - .map(|x| x.compressed_account.merkle_context) + .map(|x| { + x.account + .tree_info + .to_light_merkle_context(x.account.leaf_index, x.account.prove_by_index) + }) .collect(), input_token_data: input_compressed_accounts .iter() - .map(|x| x.token_data.clone()) + .map(|x| x.token.clone()) .map(sdk_to_program_token_data) .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.compressed_account.compressed_account) + .map(|x| &x.account.account) .cloned() .collect::>(), outputs_merkle_tree, @@ -3398,7 +3501,7 @@ async fn test_failing_thaw() { // Freeze tokens { - let input_compressed_accounts: Vec = vec![test_indexer + let input_compressed_accounts: Vec = vec![test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() @@ -3407,7 +3510,7 @@ async fn test_failing_thaw() { .clone() .into()]; let output_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey .into(); @@ -3423,24 +3526,25 @@ async fn test_failing_thaw() { .await; } - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let input_compressed_accounts = input_compressed_accounts .iter() - .filter(|x| x.token_data.state == AccountState::Frozen) + .filter(|x| x.token.state == AccountState::Frozen) .cloned() - .collect::>(); + .collect::>(); let outputs_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey; let input_compressed_account_hashes = input_compressed_accounts .iter() - .map(|x| x.compressed_account.hash().unwrap()) + .map(|x| x.account.hash) .collect::>(); let proof_rpc_result = test_indexer .get_validity_proof(input_compressed_account_hashes, Vec::new(), None) @@ -3457,16 +3561,20 @@ async fn test_failing_thaw() { authority: invalid_authority.pubkey(), input_merkle_contexts: input_compressed_accounts .iter() - .map(|x| x.compressed_account.merkle_context) + .map(|x| { + x.account + .tree_info + .to_light_merkle_context(x.account.leaf_index, x.account.prove_by_index) + }) .collect(), input_token_data: input_compressed_accounts .iter() - .map(|x| x.token_data.clone()) + .map(|x| x.token.clone()) .map(sdk_to_program_token_data) .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.compressed_account.compressed_account) + .map(|x| &x.account.account) .cloned() .collect::>(), outputs_merkle_tree: outputs_merkle_tree.into(), @@ -3492,16 +3600,20 @@ async fn test_failing_thaw() { authority: payer.pubkey(), input_merkle_contexts: input_compressed_accounts .iter() - .map(|x| x.compressed_account.merkle_context) + .map(|x| { + x.account + .tree_info + .to_light_merkle_context(x.account.leaf_index, x.account.prove_by_index) + }) .collect(), input_token_data: input_compressed_accounts .iter() - .map(|x| x.token_data.clone()) + .map(|x| x.token.clone()) .map(sdk_to_program_token_data) .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.compressed_account.compressed_account) + .map(|x| &x.account.account) .cloned() .collect::>(), outputs_merkle_tree: invalid_merkle_tree.pubkey(), @@ -3536,16 +3648,20 @@ async fn test_failing_thaw() { authority: payer.pubkey(), input_merkle_contexts: input_compressed_accounts .iter() - .map(|x| x.compressed_account.merkle_context) + .map(|x| { + x.account + .tree_info + .to_light_merkle_context(x.account.leaf_index, x.account.prove_by_index) + }) .collect(), input_token_data: input_compressed_accounts .iter() - .map(|x| x.token_data.clone()) + .map(|x| x.token.clone()) .map(sdk_to_program_token_data) .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.compressed_account.compressed_account) + .map(|x| &x.account.account) .cloned() .collect::>(), outputs_merkle_tree: outputs_merkle_tree.into(), @@ -3569,24 +3685,25 @@ async fn test_failing_thaw() { } // 4. thaw compressed account which is not frozen { - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let input_compressed_accounts = input_compressed_accounts .iter() - .filter(|x| x.token_data.state == AccountState::Initialized) + .filter(|x| x.token.state == AccountState::Initialized) .cloned() - .collect::>(); + .collect::>(); let outputs_merkle_tree = input_compressed_accounts[0] - .compressed_account + .account .merkle_context .merkle_tree_pubkey; let input_compressed_account_hashes = input_compressed_accounts .iter() - .map(|x| x.compressed_account.hash().unwrap()) + .map(|x| x.account.hash) .collect::>(); let proof_rpc_result = test_indexer .get_validity_proof(input_compressed_account_hashes, Vec::new(), None) @@ -3597,16 +3714,20 @@ async fn test_failing_thaw() { authority: payer.pubkey(), input_merkle_contexts: input_compressed_accounts .iter() - .map(|x| x.compressed_account.merkle_context) + .map(|x| { + x.account + .tree_info + .to_light_merkle_context(x.account.leaf_index, x.account.prove_by_index) + }) .collect(), input_token_data: input_compressed_accounts .iter() - .map(|x| x.token_data.clone()) + .map(|x| x.token.clone()) .map(sdk_to_program_token_data) .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.compressed_account.compressed_account) + .map(|x| &x.account.account) .cloned() .collect::>(), outputs_merkle_tree: outputs_merkle_tree.into(), @@ -3701,11 +3822,12 @@ async fn test_failing_decompression() { ) .await .unwrap(); - let input_compressed_account: Vec = test_indexer + let input_compressed_account: Vec = test_indexer .get_compressed_token_accounts_by_owner(&sender.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let decompress_amount = amount - 1000; // Test 1: invalid decompress account { @@ -4156,7 +4278,7 @@ async fn test_failing_decompression() { pub async fn failing_compress_decompress( payer: &Keypair, rpc: &mut R, - input_compressed_accounts: Vec, + input_compressed_accounts: Vec, amount: u64, output_merkle_tree_pubkey: &Pubkey, compression_amount: u64, @@ -4170,7 +4292,7 @@ pub async fn failing_compress_decompress( ) -> Result<(), RpcError> { let max_amount: u64 = input_compressed_accounts .iter() - .map(|x| x.token_data.amount) + .map(|x| x.token.amount) .sum(); let change_out_compressed_account = if !is_compress { TokenTransferOutputData { @@ -4190,7 +4312,7 @@ pub async fn failing_compress_decompress( let input_compressed_account_hashes = input_compressed_accounts .iter() - .map(|x| x.compressed_account.hash().unwrap()) + .map(|x| x.account.hash) .collect::>(); let (root_indices, proof) = if !input_compressed_account_hashes.is_empty() { @@ -4213,20 +4335,24 @@ pub async fn failing_compress_decompress( &payer.pubkey(), &input_compressed_accounts .iter() - .map(|x| x.compressed_account.merkle_context) + .map(|x| { + x.account + .tree_info + .to_light_merkle_context(x.account.leaf_index, x.account.prove_by_index) + }) .collect::>(), &[change_out_compressed_account], &root_indices, &_proof, input_compressed_accounts .iter() - .map(|x| x.token_data.clone()) + .map(|x| x.token.clone()) .map(sdk_to_program_token_data) .collect::>() .as_slice(), &input_compressed_accounts .iter() - .map(|x| &x.compressed_account.compressed_account) + .map(|x| &x.account.account) .cloned() .collect::>(), *mint, @@ -4331,16 +4457,16 @@ async fn test_invalid_inputs() { let payer = recipient_keypair.insecure_clone(); let transfer_recipient_keypair = Keypair::new(); let input_compressed_account_token_data = - test_indexer.token_compressed_accounts[0].token_data.clone(); - let input_compressed_accounts = vec![test_indexer.token_compressed_accounts[0] - .compressed_account - .clone()]; + test_indexer.token_compressed_accounts[0].token.clone(); + let input_compressed_accounts = vec![test_indexer.token_compressed_accounts[0].account.clone()]; + let input_compressed_accounts_program: Vec = + input_compressed_accounts + .iter() + .cloned() + .map(Into::into) + .collect(); let proof_rpc_result = test_indexer - .get_validity_proof( - vec![input_compressed_accounts[0].hash().unwrap()], - Vec::new(), - None, - ) + .get_validity_proof(vec![input_compressed_accounts[0].hash], Vec::new(), None) .await .unwrap(); let proof = proof_rpc_result.value.proof.0; @@ -4372,7 +4498,7 @@ async fn test_invalid_inputs() { &recipient_keypair, &proof_rpc_result.value.proof.0, proof_rpc_result.value.get_root_indices().as_slice(), - &input_compressed_accounts, + &input_compressed_accounts_program, false, ) .await; @@ -4397,7 +4523,7 @@ async fn test_invalid_inputs() { &recipient_keypair, &proof_rpc_result.value.proof.0, proof_rpc_result.value.get_root_indices().as_slice(), - &input_compressed_accounts, + &input_compressed_accounts_program, false, ) .await; @@ -4422,7 +4548,7 @@ async fn test_invalid_inputs() { &recipient_keypair, &proof, proof_rpc_result.value.get_root_indices().as_slice(), - &input_compressed_accounts, + &input_compressed_accounts_program, false, ) .await; @@ -4446,7 +4572,7 @@ async fn test_invalid_inputs() { &recipient_keypair, &proof_rpc_result.value.proof.0, proof_rpc_result.value.get_root_indices().as_slice(), - &input_compressed_accounts, + &input_compressed_accounts_program, false, ) .await; @@ -4480,22 +4606,15 @@ async fn test_invalid_inputs() { // Test 5: invalid input token data amount (0) { let mut input_compressed_account_token_data_invalid_amount = - test_indexer.token_compressed_accounts[0].token_data.clone(); + test_indexer.token_compressed_accounts[0].token.clone(); input_compressed_account_token_data_invalid_amount.amount = 0; let input_compressed_account_token_data_invalid_amount = sdk_to_program_token_data(input_compressed_account_token_data_invalid_amount); - let mut input_compressed_accounts = vec![test_indexer.token_compressed_accounts[0] - .compressed_account - .clone()]; + let mut input_compressed_accounts = + vec![test_indexer.token_compressed_accounts[0].account.clone()]; crate::TokenData::serialize( &input_compressed_account_token_data_invalid_amount, - &mut input_compressed_accounts[0] - .compressed_account - .data - .as_mut() - .unwrap() - .data - .as_mut_slice(), + &mut input_compressed_accounts[0].data.as_mut().unwrap().data[..], ) .unwrap(); let change_out_compressed_account_0 = TokenTransferOutputData { @@ -4520,7 +4639,12 @@ async fn test_invalid_inputs() { &recipient_keypair, &proof, proof_rpc_result.value.get_root_indices().as_slice(), - input_compressed_accounts.as_slice(), + input_compressed_accounts + .iter() + .cloned() + .map(Into::into) + .collect::>() + .as_slice(), false, ) .await; @@ -4530,23 +4654,22 @@ async fn test_invalid_inputs() { // Test 6: invalid delegate { let mut input_compressed_account_token_data = - test_indexer.token_compressed_accounts[0].token_data.clone(); + test_indexer.token_compressed_accounts[0].token.clone(); input_compressed_account_token_data.delegate = Some(Pubkey::new_unique()); - let mut input_compressed_accounts = vec![test_indexer.token_compressed_accounts[0] - .compressed_account - .clone()]; + let mut input_compressed_accounts = + vec![test_indexer.token_compressed_accounts[0].account.clone()]; let input_compressed_account_token_data = sdk_to_program_token_data(input_compressed_account_token_data); let mut vec = Vec::new(); crate::TokenData::serialize(&input_compressed_account_token_data, &mut vec).unwrap(); - input_compressed_accounts[0] - .compressed_account - .data - .as_mut() - .unwrap() - .data = vec; + input_compressed_accounts[0].data.as_mut().unwrap().data = vec; + let input_for_test: Vec = input_compressed_accounts + .iter() + .cloned() + .map(Into::into) + .collect(); let res = perform_transfer_failing_test( &mut rpc, change_out_compressed_account_0, @@ -4556,7 +4679,7 @@ async fn test_invalid_inputs() { &recipient_keypair, &proof, proof_rpc_result.value.get_root_indices().as_slice(), - &input_compressed_accounts, + &input_for_test, false, ) .await; @@ -4578,7 +4701,7 @@ async fn test_invalid_inputs() { &invalid_payer, &proof, proof_rpc_result.value.get_root_indices().as_slice(), - &input_compressed_accounts, + &input_compressed_accounts_program, false, ) .await; @@ -4602,7 +4725,7 @@ async fn test_invalid_inputs() { &payer, &proof, &root_indices, - &input_compressed_accounts, + &input_compressed_accounts_program, false, ) .await; @@ -4623,7 +4746,7 @@ async fn test_invalid_inputs() { &payer, &proof, proof_rpc_result.value.get_root_indices().as_slice(), - &input_compressed_accounts, + &input_compressed_accounts_program, true, ) .await; @@ -4644,7 +4767,7 @@ async fn test_invalid_inputs() { &payer, &proof, proof_rpc_result.value.get_root_indices().as_slice(), - &input_compressed_accounts, + &input_compressed_accounts_program, false, ) .await; @@ -4815,7 +4938,7 @@ async fn test_transfer_with_transaction_hash() { { let payer = recipient_keypair.insecure_clone(); let input_compressed_account_token_data = - test_indexer.token_compressed_accounts[0].token_data.clone(); + test_indexer.token_compressed_accounts[0].token.clone(); let input_compressed_accounts = vec![test_indexer.token_compressed_accounts[0].clone()]; let change_out_compressed_account_0 = TokenTransferOutputData { @@ -4830,20 +4953,24 @@ async fn test_transfer_with_transaction_hash() { &payer.pubkey(), &input_compressed_accounts .iter() - .map(|x| x.compressed_account.merkle_context) + .map(|x| { + x.account + .tree_info + .to_light_merkle_context(x.account.leaf_index, x.account.prove_by_index) + }) .collect::>(), &[change_out_compressed_account_0], &[None], &None, input_compressed_accounts .iter() - .map(|x| x.token_data.clone()) + .map(|x| x.token.clone()) .map(sdk_to_program_token_data) .collect::>() .as_slice(), &input_compressed_accounts .iter() - .map(|x| &x.compressed_account.compressed_account) + .map(|x| &x.account.account) .cloned() .collect::>(), mint, @@ -4955,11 +5082,12 @@ async fn test_transfer_with_photon_and_batched_tree() { Pubkey::from_str("24fLJv6tHmsxQg5vDD7XWy85TMhFzJdkqZ9Ta3LtVReU").unwrap(), ]; println!("recipients {:?}", recipients); - let input_compressed_accounts: Vec = test_indexer + let input_compressed_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&payer.pubkey(), None, None) .await .unwrap() - .into(); + .value + .items; let equal_amount = (amount * inputs as u64) / outputs as u64; let rest_amount = (amount * inputs as u64) % outputs as u64; let mut output_amounts = vec![equal_amount; outputs - 1]; @@ -5095,11 +5223,12 @@ async fn batch_compress_with_batched_tree() { test_indexer.add_compressed_accounts_with_token_data(slot, &event); for i in 0..num_recipients { - let recipient_compressed_token_accounts: Vec = test_indexer + let recipient_compressed_token_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(&recipients[i as usize], None, None) .await .unwrap() - .into(); + .value + .items; assert_eq!(recipient_compressed_token_accounts.len(), 1); let recipient_compressed_token_account = &recipient_compressed_token_accounts[0]; let expected_token_data = light_token::compat::TokenData { @@ -5111,7 +5240,7 @@ async fn batch_compress_with_batched_tree() { tlv: None, }; assert_eq!( - recipient_compressed_token_account.token_data, + recipient_compressed_token_account.token, expected_token_data ); } @@ -5162,11 +5291,12 @@ async fn batch_compress_with_batched_tree() { test_indexer.add_compressed_accounts_with_token_data(slot, &event); for recipient in &recipients { - let recipient_compressed_token_accounts: Vec = test_indexer + let recipient_compressed_token_accounts: Vec = test_indexer .get_compressed_token_accounts_by_owner(recipient, None, None) .await .unwrap() - .into(); + .value + .items; assert_eq!(recipient_compressed_token_accounts.len(), 1); let recipient_compressed_token_account = &recipient_compressed_token_accounts[0]; let expected_token_data = light_token::compat::TokenData { @@ -5178,7 +5308,7 @@ async fn batch_compress_with_batched_tree() { tlv: None, }; assert_eq!( - recipient_compressed_token_account.token_data, + recipient_compressed_token_account.token, expected_token_data ); } diff --git a/program-tests/registry-test/tests/tests.rs b/program-tests/registry-test/tests/tests.rs index 399435372c..d97dac5577 100644 --- a/program-tests/registry-test/tests/tests.rs +++ b/program-tests/registry-test/tests/tests.rs @@ -1484,20 +1484,16 @@ async fn test_migrate_state() { .unwrap(); let compressed_account = &test_indexer.get_compressed_accounts_with_merkle_context_by_owner(&payer.pubkey())[0]; - let hash = compressed_account.hash().unwrap(); + let hash = compressed_account.hash; let bundle = &test_indexer .get_state_merkle_trees() .iter() .find(|b| { - b.accounts.merkle_tree.to_bytes() - == compressed_account - .merkle_context - .merkle_tree_pubkey - .to_bytes() + b.accounts.merkle_tree.to_bytes() == compressed_account.tree_info.tree.to_bytes() }) .unwrap(); assert_eq!(merkle_tree.root(), bundle.merkle_tree.root()); - let leaf_index = compressed_account.merkle_context.leaf_index as u64; + let leaf_index = compressed_account.leaf_index as u64; let merkle_proof = bundle .merkle_tree .get_proof_of_leaf(leaf_index as usize, false) @@ -1543,10 +1539,7 @@ async fn test_migrate_state() { .iter_mut() .find(|b| { b.accounts.merkle_tree.to_bytes() - == compressed_account - .merkle_context - .merkle_tree_pubkey - .to_bytes() + == compressed_account.tree_info.tree.to_bytes() }) .unwrap(); bundle @@ -1577,20 +1570,16 @@ async fn test_migrate_state() { .unwrap(); let compressed_account = &test_indexer.get_compressed_accounts_with_merkle_context_by_owner(&payer.pubkey())[1]; - let hash = compressed_account.hash().unwrap(); + let hash = compressed_account.hash; let bundle = &test_indexer .get_state_merkle_trees() .iter() .find(|b| { - b.accounts.merkle_tree.to_bytes() - == compressed_account - .merkle_context - .merkle_tree_pubkey - .to_bytes() + b.accounts.merkle_tree.to_bytes() == compressed_account.tree_info.tree.to_bytes() }) .unwrap(); assert_eq!(merkle_tree.root(), bundle.merkle_tree.root()); - let leaf_index = compressed_account.merkle_context.leaf_index as u64; + let leaf_index = compressed_account.leaf_index as u64; let merkle_proof = bundle .merkle_tree .get_proof_of_leaf(leaf_index as usize, false) diff --git a/program-tests/system-cpi-test/tests/test.rs b/program-tests/system-cpi-test/tests/test.rs index ec6d61721f..1dca221fa7 100644 --- a/program-tests/system-cpi-test/tests/test.rs +++ b/program-tests/system-cpi-test/tests/test.rs @@ -2,7 +2,8 @@ use anchor_lang::{AnchorDeserialize, AnchorSerialize}; use light_account_checks::error::AccountError; use light_batched_merkle_tree::initialize_state_tree::InitStateTreeAccountsInstructionData; use light_client::indexer::{ - AddressMerkleTreeAccounts, AddressWithTree, Indexer, StateMerkleTreeAccounts, + AddressMerkleTreeAccounts, AddressWithTree, CompressedAccount as ClientCompressedAccount, + Indexer, StateMerkleTreeAccounts, }; use light_compressed_account::{ address::{derive_address, derive_address_legacy}, @@ -217,9 +218,8 @@ async fn test_read_only_accounts() { .get_compressed_accounts_with_merkle_context_by_owner(&ID) .iter() .find(|x| { - x.merkle_context.leaf_index == 101 - && x.merkle_context.merkle_tree_pubkey.to_bytes() - == env.v2_state_trees[0].merkle_tree.to_bytes() + x.leaf_index == 101 + && x.tree_info.tree.to_bytes() == env.v2_state_trees[0].merkle_tree.to_bytes() }) .unwrap() .clone(); @@ -229,9 +229,8 @@ async fn test_read_only_accounts() { .get_compressed_accounts_with_merkle_context_by_owner(&ID) .iter() .find(|x| { - x.merkle_context.leaf_index == 1 - && x.merkle_context.merkle_tree_pubkey.to_bytes() - == env.v2_state_trees[0].merkle_tree.to_bytes() + x.leaf_index == 1 + && x.tree_info.tree.to_bytes() == env.v2_state_trees[0].merkle_tree.to_bytes() }) .unwrap() .clone(); @@ -327,10 +326,7 @@ async fn test_read_only_accounts() { .indexer .get_compressed_accounts_with_merkle_context_by_owner(&ID) .iter() - .find(|x| { - x.merkle_context.merkle_tree_pubkey.to_bytes() - == env.v1_state_trees[0].merkle_tree.to_bytes() - }) + .find(|x| x.tree_info.tree.to_bytes() == env.v1_state_trees[0].merkle_tree.to_bytes()) .unwrap() .clone(); let result = perform_create_pda_with_event( @@ -615,13 +611,9 @@ async fn test_read_only_accounts() { .get_compressed_accounts_with_merkle_context_by_owner(&ID) .iter() .find(|x| { - x.merkle_context.leaf_index == 2 - && x.merkle_context.merkle_tree_pubkey.to_bytes() - == env.v2_state_trees[0].merkle_tree.to_bytes() - && x.merkle_context.leaf_index - != account_not_in_value_array_and_in_mt - .merkle_context - .leaf_index + x.leaf_index == 2 + && x.tree_info.tree.to_bytes() == env.v2_state_trees[0].merkle_tree.to_bytes() + && x.leaf_index != account_not_in_value_array_and_in_mt.leaf_index }) .unwrap() .clone(); @@ -1013,8 +1005,7 @@ async fn only_test_create_pda() { .value .items[0] .account - .clone() - .into(); + .clone(); println!("only_test_create_pda 8"); // Failing 4 input account that is not owned by signer ---------------------------------------------- @@ -1146,7 +1137,7 @@ async fn only_test_create_pda() { &mut rpc, &mut test_indexer, &keypair, - &[compressed_account], + &[compressed_account.clone().into()], &[Pubkey::new_unique()], &[env.v1_state_trees[0].merkle_tree], None, @@ -1631,8 +1622,8 @@ pub async fn perform_create_pda_with_event>, - read_only_accounts: Option>, + input_accounts: Option>, + read_only_accounts: Option>, mode: CreatePdaMode, ) -> Result<(), RpcError> { let payer_pubkey = payer.pubkey(); @@ -1678,10 +1669,14 @@ async fn perform_create_pda( data: &[u8; 31], payer_pubkey: Pubkey, owner_program: &Pubkey, - input_accounts: Option>, - read_only_accounts: Option>, + input_accounts: Option>, + read_only_accounts: Option>, mode: CreatePdaMode, ) -> solana_sdk::instruction::Instruction { + let input_accounts: Option> = + input_accounts.map(|v| v.into_iter().map(Into::into).collect()); + let read_only_accounts: Option> = + read_only_accounts.map(|v| v.into_iter().map(Into::into).collect()); let output_compressed_account_merkle_tree_pubkey = if mode == CreatePdaMode::BatchFunctional { &env.v2_state_trees[0].output_queue } else { @@ -1881,19 +1876,9 @@ pub async fn assert_created_pda( let compressed_escrow_pda = test_indexer.get_compressed_accounts_with_merkle_context_by_owner(&ID)[0].clone(); let address = derive_address_legacy(&env.v1_address_trees[0].merkle_tree.into(), seed).unwrap(); - assert_eq!( - compressed_escrow_pda.compressed_account.address.unwrap(), - address - ); - assert_eq!( - compressed_escrow_pda.compressed_account.owner.to_bytes(), - ID.to_bytes() - ); - let compressed_escrow_pda_deserialized = compressed_escrow_pda - .compressed_account - .data - .as_ref() - .unwrap(); + assert_eq!(compressed_escrow_pda.address.unwrap(), address); + assert_eq!(compressed_escrow_pda.owner.to_bytes(), ID.to_bytes()); + let compressed_escrow_pda_deserialized = compressed_escrow_pda.data.as_ref().unwrap(); let compressed_escrow_pda_data = RegisteredUser::deserialize_reader(&mut &compressed_escrow_pda_deserialized.data[..]) .unwrap(); @@ -1919,11 +1904,12 @@ pub async fn perform_with_input_accounts, - compressed_account: &CompressedAccountWithMerkleContext, + compressed_account: &ClientCompressedAccount, token_account: Option, expected_error_code: u32, mode: WithInputAccountsMode, ) -> Result<(), RpcError> { + let compressed_account: CompressedAccountWithMerkleContext = compressed_account.clone().into(); let payer_pubkey = payer.pubkey(); let hash = compressed_account.hash().unwrap(); let mut hashes = vec![hash]; diff --git a/program-tests/system-cpi-v2-test/tests/invoke_cpi_with_read_only.rs b/program-tests/system-cpi-v2-test/tests/invoke_cpi_with_read_only.rs index f6d52b1d2e..f880ce256b 100644 --- a/program-tests/system-cpi-v2-test/tests/invoke_cpi_with_read_only.rs +++ b/program-tests/system-cpi-v2-test/tests/invoke_cpi_with_read_only.rs @@ -196,14 +196,17 @@ async fn functional_read_only() { let account = accounts .get(accounts.len().saturating_sub(i as usize)) .unwrap(); - println!("leaf index {}", account.merkle_context.leaf_index); - let mut merkle_context = account.merkle_context; + println!("leaf index {}", account.leaf_index); + let mut merkle_context = account.tree_info.to_light_merkle_context( + account.leaf_index, + account.prove_by_index, + ); if batched { merkle_context.prove_by_index = true; } ReadOnlyCompressedAccount { merkle_context, - account_hash: account.hash().unwrap(), + account_hash: account.hash, root_index: 0, } }) @@ -495,14 +498,17 @@ async fn functional_account_infos() { let account = accounts .get(accounts.len().saturating_sub(i as usize)) .unwrap(); - println!("leaf index {}", account.merkle_context.leaf_index); - let mut merkle_context = account.merkle_context; + println!("leaf index {}", account.leaf_index); + let mut merkle_context = account.tree_info.to_light_merkle_context( + account.leaf_index, + account.prove_by_index, + ); if batched { merkle_context.prove_by_index = true; } ReadOnlyCompressedAccount { merkle_context, - account_hash: account.hash().unwrap(), + account_hash: account.hash, root_index: 0, } }) @@ -1940,7 +1946,6 @@ async fn compress_sol_with_account_info() { .get_compressed_accounts_with_merkle_context_by_owner( &create_address_test_program::ID, )[0] - .compressed_account .lamports; assert_eq!(output_account_balance, compression_lamports); } @@ -2294,10 +2299,7 @@ async fn cpi_context_with_read_only() { &create_address_test_program::ID, ); assert_eq!(output_account_balance.len(), 1); - assert_eq!( - output_account_balance[0].compressed_account.address, - Some(address) - ); + assert_eq!(output_account_balance[0].address, Some(address)); let account = test_indexer .get_compressed_account(address1, None) @@ -2308,13 +2310,10 @@ async fn cpi_context_with_read_only() { assert_eq!(account.owner, owner_account1); let output_account_balance = test_indexer.get_compressed_accounts_with_merkle_context_by_owner(&owner_account1); - assert_eq!( - output_account_balance[0].compressed_account.address, - Some(address1) - ); + assert_eq!(output_account_balance[0].address, Some(address1)); let output_account_balance = test_indexer.get_compressed_accounts_with_merkle_context_by_owner(&owner_account2); - assert_eq!(output_account_balance[0].compressed_account.address, None); + assert_eq!(output_account_balance[0].address, None); } } } @@ -2619,22 +2618,12 @@ async fn cpi_context_with_account_info() { .get_compressed_accounts_with_merkle_context_by_owner( &create_address_test_program::ID, ); - output_account_balance.sort_by(|a, b| { - a.merkle_context - .leaf_index - .cmp(&b.merkle_context.leaf_index) - }); + output_account_balance.sort_by(|a, b| a.leaf_index.cmp(&b.leaf_index)); assert_eq!(output_account_balance.len(), 4); - assert_eq!( - output_account_balance[2].compressed_account.address, - Some(address) - ); - assert_eq!(output_account_balance[1].compressed_account.address, None); - assert_eq!( - output_account_balance[0].compressed_account.address, - Some(address1) - ); - assert_eq!(output_account_balance[3].compressed_account.address, None); + assert_eq!(output_account_balance[2].address, Some(address)); + assert_eq!(output_account_balance[1].address, None); + assert_eq!(output_account_balance[0].address, Some(address1)); + assert_eq!(output_account_balance[3].address, None); } } } @@ -2741,7 +2730,6 @@ async fn compress_sol_with_read_only() { .get_compressed_accounts_with_merkle_context_by_owner( &create_address_test_program::ID, )[0] - .compressed_account .lamports; assert_eq!(output_account_balance, compression_lamports); } @@ -2891,7 +2879,7 @@ async fn test_duplicate_account_in_inputs_and_read_only() { let compressed_account = test_indexer.compressed_accounts[0].clone(); let read_only_account = ReadOnlyCompressedAccount { - account_hash: compressed_account.hash().unwrap(), + account_hash: compressed_account.hash, merkle_context: MerkleContext { merkle_tree_pubkey: tree.into(), queue_pubkey: queue.into(), @@ -2904,7 +2892,7 @@ async fn test_duplicate_account_in_inputs_and_read_only() { // Get validity proof for the input account let rpc_result = rpc - .get_validity_proof(vec![compressed_account.hash().unwrap()], Vec::new(), None) + .get_validity_proof(vec![compressed_account.hash], Vec::new(), None) .await .unwrap(); @@ -2913,16 +2901,16 @@ async fn test_duplicate_account_in_inputs_and_read_only() { &mut rpc, &mut test_indexer, &payer, - vec![compressed_account], // input_accounts - vec![], // output_accounts - vec![], // new_addresses - rpc_result.value.proof.0, // proof - None, // sol_compression_recipient - None, // account_infos - false, // v2_ix - false, // with_transaction_hash - vec![read_only_account], // read_only_accounts - Vec::new(), // read_only_addresses + vec![compressed_account.clone().into()], // input_accounts + vec![], // output_accounts + vec![], // new_addresses + rpc_result.value.proof.0, // proof + None, // sol_compression_recipient + None, // account_infos + false, // v2_ix + false, // with_transaction_hash + vec![read_only_account], // read_only_accounts + Vec::new(), // read_only_addresses queue, tree, false, // is_compress diff --git a/program-tests/system-test/tests/test.rs b/program-tests/system-test/tests/test.rs index 5768a9a66f..8dd80580aa 100644 --- a/program-tests/system-test/tests/test.rs +++ b/program-tests/system-test/tests/test.rs @@ -200,9 +200,12 @@ pub async fn failing_transaction_inputs( } let (mut new_address_params, derived_addresses) = create_address_test_inputs(&env, num_addresses); - let input_compressed_accounts = rpc + let input_compressed_accounts: Vec = rpc .get_compressed_accounts_with_merkle_context_by_owner(&payer.pubkey())[0..num_inputs] - .to_vec(); + .to_vec() + .into_iter() + .map(Into::into) + .collect(); let hashes = input_compressed_accounts .iter() .map(|x| x.hash().unwrap()) @@ -1330,7 +1333,9 @@ async fn test_with_address() { // transfer with address println!("transfer with address-------------------------"); - let compressed_account_with_context = rpc.indexer().unwrap().compressed_accounts[0].clone(); + let compressed_account = rpc.indexer().unwrap().compressed_accounts[0].clone(); + let compressed_account_with_context: CompressedAccountWithMerkleContext = + compressed_account.clone().into(); let recipient_pubkey = Keypair::new().pubkey(); let mut indexer = rpc.clone_indexer().unwrap(); transfer_compressed_sol_test( @@ -1339,10 +1344,7 @@ async fn test_with_address() { &payer, std::slice::from_ref(&compressed_account_with_context), &[recipient_pubkey], - &[compressed_account_with_context - .merkle_context - .merkle_tree_pubkey - .into()], + &[compressed_account.tree_info.tree], None, ) .await @@ -1350,17 +1352,11 @@ async fn test_with_address() { assert_eq!(indexer.compressed_accounts.len(), 1); assert_eq!( - indexer.compressed_accounts[0] - .compressed_account - .address - .unwrap(), + indexer.compressed_accounts[0].address.unwrap(), derived_address ); assert_eq!( - indexer.compressed_accounts[0] - .compressed_account - .owner - .to_bytes(), + indexer.compressed_accounts[0].owner.to_bytes(), recipient_pubkey.to_bytes() ); (*rpc.indexer_mut().unwrap()) = indexer; @@ -1432,10 +1428,13 @@ async fn test_with_address() { (4, 2), ]; for (n_input_compressed_accounts, n_new_addresses) in test_inputs { - let compressed_input_accounts = rpc + let compressed_input_accounts: Vec = rpc .get_compressed_accounts_with_merkle_context_by_owner(&payer_pubkey) [0..n_input_compressed_accounts] - .to_vec(); + .to_vec() + .into_iter() + .map(Into::into) + .collect(); let mut address_vec = Vec::new(); // creates multiple seeds by taking the number of input accounts and zeroing out the jth byte @@ -1625,15 +1624,17 @@ async fn test_with_compression() { assert_custom_error_or_program_error(result, SystemProgramError::SumCheckFailed.into()) .unwrap(); - let compressed_account_with_context = - rpc.get_compressed_accounts_with_merkle_context_by_owner(&payer_pubkey)[0].clone(); + let compressed_account_with_context: CompressedAccountWithMerkleContext = rpc + .get_compressed_accounts_with_merkle_context_by_owner(&payer_pubkey)[0] + .clone() + .into(); let mut test_indexer = (*rpc.indexer().unwrap()).clone(); decompress_sol_test( &mut rpc, &mut test_indexer, &payer, - &vec![compressed_account_with_context], + &[compressed_account_with_context], &recipient_pubkey, compress_amount, &env.v1_state_trees[0].merkle_tree, diff --git a/program-tests/utils/src/assert_compressed_tx.rs b/program-tests/utils/src/assert_compressed_tx.rs index 8d6c2ced9d..a923169da0 100644 --- a/program-tests/utils/src/assert_compressed_tx.rs +++ b/program-tests/utils/src/assert_compressed_tx.rs @@ -8,13 +8,10 @@ use light_batched_merkle_tree::{ batch::Batch, merkle_tree::BatchedMerkleTreeAccount, queue::BatchedQueueMetadata, }; use light_client::{ - indexer::{Indexer, StateMerkleTreeAccounts}, + indexer::{CompressedAccount as ClientCompressedAccount, Indexer, StateMerkleTreeAccounts}, rpc::Rpc, }; -use light_compressed_account::{ - compressed_account::{CompressedAccount, CompressedAccountWithMerkleContext}, - TreeType, -}; +use light_compressed_account::{compressed_account::CompressedAccount, TreeType}; use light_event::event::{MerkleTreeSequenceNumber, PublicTransactionEvent}; use light_hasher::Poseidon; use light_program_test::indexer::TestIndexerExtensions; @@ -28,7 +25,7 @@ pub struct AssertCompressedTransactionInputs<'a, R: Rpc, I: Indexer + TestIndexe pub rpc: &'a mut R, pub test_indexer: &'a mut I, pub output_compressed_accounts: &'a [CompressedAccount], - pub created_output_compressed_accounts: &'a [CompressedAccountWithMerkleContext], + pub created_output_compressed_accounts: &'a [ClientCompressedAccount], pub input_compressed_account_hashes: &'a [[u8; 32]], pub output_merkle_tree_snapshots: &'a [MerkleTreeTestSnapShot], pub input_merkle_tree_snapshots: &'a [MerkleTreeTestSnapShot], @@ -103,7 +100,7 @@ pub async fn assert_compressed_transaction>(), input.compress_or_decompress_lamports, input.is_compress, @@ -226,17 +223,21 @@ pub async fn assert_addresses_exist_in_hash_sets( pub fn assert_created_compressed_accounts( output_compressed_accounts: &[CompressedAccount], output_merkle_tree_pubkeys: &[Pubkey], - created_out_compressed_accounts: &[CompressedAccountWithMerkleContext], + created_out_compressed_accounts: &[ClientCompressedAccount], ) { for output_account in created_out_compressed_accounts.iter() { - assert!(output_compressed_accounts.iter().any(|x| x.lamports - == output_account.compressed_account.lamports - && x.owner == output_account.compressed_account.owner - && x.data == output_account.compressed_account.data - && x.address == output_account.compressed_account.address),); - assert!(output_merkle_tree_pubkeys.iter().any(|x| *x - == output_account.merkle_context.merkle_tree_pubkey.into() - || *x == output_account.merkle_context.queue_pubkey.into()),); + assert!(output_compressed_accounts.iter().any(|x| { + x.lamports == output_account.lamports + && x.owner.to_bytes() == output_account.owner.to_bytes() + && x.data == output_account.data + && x.address == output_account.address + })); + assert!( + output_merkle_tree_pubkeys.iter().any(|x| { + *x == output_account.tree_info.tree || *x == output_account.tree_info.queue + }), + "tree/queue pubkey match failed for created account" + ); } } diff --git a/program-tests/utils/src/assert_token_tx.rs b/program-tests/utils/src/assert_token_tx.rs index dec0921dc7..a95a531a35 100644 --- a/program-tests/utils/src/assert_token_tx.rs +++ b/program-tests/utils/src/assert_token_tx.rs @@ -1,10 +1,11 @@ use anchor_lang::AnchorSerialize; -use light_client::{indexer::Indexer, rpc::Rpc}; -use light_compressed_account::compressed_account::CompressedAccountWithMerkleContext; +use light_client::{ + indexer::{CompressedAccount, Indexer}, + rpc::Rpc, +}; use light_compressed_token::process_transfer::{get_cpi_authority_pda, TokenTransferOutputData}; use light_event::event::PublicTransactionEvent; use light_program_test::indexer::TestIndexerExtensions; -use light_token::compat::TokenDataWithMerkleContext; use solana_sdk::{program_pack::Pack, pubkey::Pubkey}; use crate::assert_compressed_tx::{ @@ -25,7 +26,7 @@ pub async fn assert_transfer( context: &mut R, test_indexer: &mut I, out_compressed_accounts: &[TokenTransferOutputData], - created_output_compressed_accounts: &[CompressedAccountWithMerkleContext], + created_output_compressed_accounts: &[CompressedAccount], lamports: Option>>, input_compressed_account_hashes: &[[u8; 32]], output_merkle_tree_snapshots: &[MerkleTreeTestSnapShot], @@ -69,7 +70,7 @@ pub async fn assert_transfer( .as_slice(), &created_output_compressed_accounts .iter() - .map(|x| x.merkle_context.leaf_index) + .map(|x| x.leaf_index) .collect::>(), None, false, @@ -102,59 +103,47 @@ pub fn assert_compressed_token_accounts( mint: Pubkey, amounts: &[u64], snapshots: &[MerkleTreeTestSnapShot], - created_token_accounts: &[TokenDataWithMerkleContext], + created_token_accounts: &[light_client::indexer::CompressedTokenAccount], previous_mint_supply: u64, previous_sol_pool_amount: u64, token_pool_pda: Pubkey, @@ -206,10 +191,10 @@ pub async fn assert_mint_to( let pos = created_token_accounts .iter() .position(|x| { - x.token_data.owner == *recipient - && x.token_data.amount == *amount - && x.token_data.mint == mint - && x.token_data.delegate.is_none() + x.token.owner == *recipient + && x.token.amount == *amount + && x.token.mint == mint + && x.token.delegate.is_none() }) .expect("Mint to failed to create expected compressed token account."); created_token_accounts.remove(pos); diff --git a/program-tests/utils/src/e2e_test_env.rs b/program-tests/utils/src/e2e_test_env.rs index f5ecfdb405..9d89c8ae54 100644 --- a/program-tests/utils/src/e2e_test_env.rs +++ b/program-tests/utils/src/e2e_test_env.rs @@ -154,7 +154,7 @@ use light_sdk::{ use light_sparse_merkle_tree::{ changelog::ChangelogEntry, indexed_changelog::IndexedChangelogEntry, SparseMerkleTree, }; -use light_token::compat::{AccountState, TokenDataWithMerkleContext}; +use light_token::compat::AccountState; use log::info; use num_bigint::{BigUint, RandBigInt}; use num_traits::Num; @@ -1907,7 +1907,7 @@ where println!("Recipients: {:?}", recipients.len()); let max_amount = token_accounts .iter() - .map(|token_account| token_account.token_data.amount) + .map(|token_account| token_account.token.amount) .sum::(); let amount = Self::safe_gen_range(&mut self.rng, 1000..max_amount, max_amount / 2); let equal_amount = amount / recipients.len() as u64; @@ -1980,7 +1980,7 @@ where let delegate = self.users[rnd_user_index].keypair.pubkey(); let max_amount = token_accounts .iter() - .map(|token_account| token_account.token_data.amount) + .map(|token_account| token_account.token.amount) .sum::(); let delegate_amount = Self::safe_gen_range(&mut self.rng, 0..max_amount, max_amount / 2); let num_output_compressed_accounts = if delegate_amount != max_amount { 2 } else { 1 }; @@ -2084,7 +2084,7 @@ where } let max_amount = token_accounts .iter() - .map(|token_account| token_account.token_data.amount) + .map(|token_account| token_account.token.amount) .sum::(); let burn_amount = Self::safe_gen_range(&mut self.rng, 0..max_amount, max_amount / 2); let num_output_compressed_accounts = if burn_amount != max_amount { 1 } else { 0 }; @@ -2315,7 +2315,7 @@ where let output_merkle_tree_account = self.get_merkle_tree_pubkeys(1); let max_amount = token_accounts .iter() - .map(|token_account| token_account.token_data.amount) + .map(|token_account| token_account.token.amount) .sum::(); let amount = Self::safe_gen_range(&mut self.rng, 1000..max_amount, max_amount / 2); let transaction_paramets = if self.keypair_action_config.fee_assert { @@ -2498,9 +2498,7 @@ where } accounts.iter().for_each(|account| { - let hash = account.hash().unwrap(); - - proof_input_accounts.push((hash, account.merkle_context.merkle_tree_pubkey)); + proof_input_accounts.push((account.hash, account.tree_info.tree)); }); accounts @@ -2528,14 +2526,17 @@ where accounts .iter() .map(|account| { - let account_hash = account.hash().unwrap(); - proof_input_accounts - .push((account_hash, account.merkle_context.merkle_tree_pubkey)); + let acc_program: CompressedAccountWithMerkleContext = account.clone().into(); + let account_hash = acc_program.hash().unwrap(); + proof_input_accounts.push(( + account_hash, + acc_program.merkle_context.merkle_tree_pubkey.into(), + )); ReadOnlyCompressedAccount { account_hash, - merkle_context: account.merkle_context, - root_index: 0, // set after proof generation + merkle_context: acc_program.merkle_context, + root_index: 0, } }) .collect::>() @@ -2724,8 +2725,10 @@ where let packed_new_address_params = pack_new_address_params(new_address_params.as_slice(), &mut remaining_accounts); + let input_accounts_program: Vec = + input_accounts.iter().cloned().map(Into::into).collect(); let packed_inputs = pack_compressed_accounts( - input_accounts.as_slice(), + input_accounts_program.as_slice(), root_indices.as_slice(), &mut remaining_accounts, ); @@ -2895,49 +2898,46 @@ where &mut self, user_index: usize, ) -> Vec { - let input_compressed_accounts = self + let input_compressed_accounts: Vec = self .indexer .get_compressed_accounts_with_merkle_context_by_owner( &self.users[user_index].keypair.pubkey(), - ) - .into_iter() - .collect::>(); + ); if input_compressed_accounts.is_empty() { return vec![]; } let index = Self::safe_gen_range(&mut self.rng, 0..input_compressed_accounts.len(), 0); - // pick random first account to decompress let first_account = &input_compressed_accounts[index]; let first_mt = self .indexer .get_state_merkle_trees() .iter() - .find(|x| { - x.accounts.merkle_tree.to_bytes() - == first_account.merkle_context.merkle_tree_pubkey.to_bytes() - }) + .find(|x| x.accounts.merkle_tree.to_bytes() == first_account.tree_info.tree.to_bytes()) .unwrap() .tree_type; - let input_compressed_accounts_with_same_version = input_compressed_accounts + let input_compressed_accounts_with_same_version: Vec< + light_client::indexer::CompressedAccount, + > = input_compressed_accounts .iter() .filter(|x| { self.indexer .get_state_merkle_trees() .iter() - .find(|y| { - y.accounts.merkle_tree.to_bytes() - == x.merkle_context.merkle_tree_pubkey.to_bytes() - }) + .find(|y| y.accounts.merkle_tree.to_bytes() == x.tree_info.tree.to_bytes()) .unwrap() .tree_type == first_mt }) .cloned() - .collect::>(); + .collect(); let range = std::cmp::min(input_compressed_accounts_with_same_version.len(), 4); let number_of_compressed_accounts = Self::safe_gen_range(&mut self.rng, 0..=range, 0); - input_compressed_accounts_with_same_version[0..number_of_compressed_accounts].to_vec() + input_compressed_accounts_with_same_version[0..number_of_compressed_accounts] + .iter() + .cloned() + .map(Into::into) + .collect() } pub fn get_compressed_sol_accounts( @@ -2947,6 +2947,7 @@ where self.indexer .get_compressed_accounts_with_merkle_context_by_owner(pubkey) .into_iter() + .map(Into::into) .collect() } @@ -3036,7 +3037,7 @@ where pub async fn select_random_compressed_token_accounts( &mut self, user: &Pubkey, - ) -> (Pubkey, Vec) { + ) -> (Pubkey, Vec) { self.select_random_compressed_token_accounts_delegated(user, false, None, false) .await } @@ -3044,7 +3045,7 @@ where pub async fn select_random_compressed_token_accounts_frozen( &mut self, user: &Pubkey, - ) -> (Pubkey, Vec) { + ) -> (Pubkey, Vec) { self.select_random_compressed_token_accounts_delegated(user, false, None, true) .await } @@ -3055,7 +3056,7 @@ where delegated: bool, delegate: Option, frozen: bool, - ) -> (Pubkey, Vec) { + ) -> (Pubkey, Vec) { let user_token_accounts = &mut self .indexer .get_compressed_token_accounts_by_owner(user, None, None) @@ -3066,14 +3067,14 @@ where .value .items .retain(|t| t.token.amount > 1000); - let mut token_accounts_with_mint: Vec; + let mut token_accounts_with_mint: Vec; let mint; let tree_version; if user_token_accounts.value.items.is_empty() { mint = self.indexer.get_token_compressed_accounts()[self .rng .gen_range(0..self.indexer.get_token_compressed_accounts().len())] - .token_data + .token .mint; let number_of_compressed_accounts = Self::safe_gen_range(&mut self.rng, 1..8, 1); let bundle = &self.indexer.get_state_merkle_trees()[0]; @@ -3112,8 +3113,7 @@ where token_account.token.mint == mint && tree_version == version }) .cloned() - .map(|account| account.into()) - .collect::>(); + .collect::>(); } else { let token_account = &user_token_accounts.value.items [Self::safe_gen_range(&mut self.rng, 0..user_token_accounts.value.items.len(), 0)]; @@ -3140,15 +3140,15 @@ where .tree_type; token_account.token.mint == mint && tree_version == version }) - .map(|token_account| (*token_account).clone().into()) - .collect::>(); + .cloned() + .collect::>(); } if delegated { token_accounts_with_mint = token_accounts_with_mint .iter() - .filter(|token_account| token_account.token_data.delegate.is_some()) - .map(|token_account| (*token_account).clone()) - .collect::>(); + .filter(|token_account| token_account.token.delegate.is_some()) + .cloned() + .collect::>(); if token_accounts_with_mint.is_empty() { return (mint, Vec::new()); } @@ -3156,25 +3156,25 @@ where if let Some(delegate) = delegate { token_accounts_with_mint = token_accounts_with_mint .iter() - .filter(|token_account| token_account.token_data.delegate.unwrap() == delegate) - .map(|token_account| (*token_account).clone()) - .collect::>(); + .filter(|token_account| token_account.token.delegate.unwrap() == delegate) + .cloned() + .collect::>(); } if frozen { token_accounts_with_mint = token_accounts_with_mint .iter() - .filter(|token_account| token_account.token_data.state == AccountState::Frozen) - .map(|token_account| (*token_account).clone()) - .collect::>(); + .filter(|token_account| token_account.token.state == AccountState::Frozen) + .cloned() + .collect::>(); if token_accounts_with_mint.is_empty() { return (mint, Vec::new()); } } else { token_accounts_with_mint = token_accounts_with_mint .iter() - .filter(|token_account| token_account.token_data.state == AccountState::Initialized) - .map(|token_account| (*token_account).clone()) - .collect::>(); + .filter(|token_account| token_account.token.state == AccountState::Initialized) + .cloned() + .collect::>(); } let range_end = if token_accounts_with_mint.len() == 1 { 1 @@ -3188,16 +3188,11 @@ where token_accounts_with_mint[0..range_end].to_vec(); // Sorting input and output Merkle tree pubkeys the same way so the pubkey indices do not get out of order get_random_subset_of_token_accounts.sort_by(|a, b| { - a.compressed_account - .merkle_context - .merkle_tree_pubkey + a.account + .tree_info + .tree .to_bytes() - .cmp( - &b.compressed_account - .merkle_context - .merkle_tree_pubkey - .to_bytes(), - ) + .cmp(&b.account.tree_info.tree.to_bytes()) }); (mint, get_random_subset_of_token_accounts) } diff --git a/program-tests/utils/src/spl.rs b/program-tests/utils/src/spl.rs index a44ebc3374..deadf9ab20 100644 --- a/program-tests/utils/src/spl.rs +++ b/program-tests/utils/src/spl.rs @@ -5,7 +5,7 @@ pub const CREATE_MINT_HELPER_DECIMALS: u8 = 2; use forester_utils::instructions::create_account::create_account_instruction; use light_client::{ fee::TransactionParams, - indexer::Indexer, + indexer::{CompressedTokenAccount, Indexer}, rpc::{errors::RpcError, Rpc}, }; use light_compressed_account::{ @@ -30,7 +30,6 @@ use light_compressed_token::{ }; use light_hasher::Poseidon; use light_program_test::{indexer::TestIndexerExtensions, program_test::TestRpc}; -use light_token::compat::TokenDataWithMerkleContext; use light_token_interface::state::{CompressedTokenAccountState, TokenData}; use solana_banks_client::BanksClientError; use solana_sdk::{ @@ -508,7 +507,7 @@ pub async fn compressed_transfer_test< recipients: &[Pubkey], amounts: &[u64], lamports: Option>>, - input_compressed_accounts: &[TokenDataWithMerkleContext], + input_compressed_accounts: &[CompressedTokenAccount], output_merkle_tree_pubkeys: &[Pubkey], delegate_change_account_index: Option, delegate_is_signer: bool, @@ -546,13 +545,19 @@ pub async fn compressed_transfer_22_test< recipients: &[Pubkey], amounts: &[u64], mut lamports: Option>>, - input_compressed_accounts: &[TokenDataWithMerkleContext], + input_compressed_accounts: &[CompressedTokenAccount], output_merkle_tree_pubkeys: &[Pubkey], delegate_change_account_index: Option, delegate_is_signer: bool, transaction_params: Option, token_22: bool, ) { + let input_compressed_accounts: Vec = + input_compressed_accounts + .iter() + .cloned() + .map(Into::into) + .collect(); if recipients.len() != amounts.len() && amounts.len() != output_merkle_tree_pubkeys.len() { println!("{:?}", recipients); println!("{:?}", amounts); @@ -563,7 +568,7 @@ pub async fn compressed_transfer_22_test< let mut input_compressed_account_token_data = Vec::new(); let mut input_compressed_account_hashes = Vec::new(); let mut sum_input_amounts = 0; - for account in input_compressed_accounts { + for account in &input_compressed_accounts { let leaf_index = account.compressed_account.merkle_context.leaf_index; input_compressed_account_token_data.push(account.token_data.clone()); input_compressed_account_hashes.push(account.compressed_account.hash().unwrap()); @@ -715,7 +720,7 @@ pub async fn compressed_transfer_22_test< }; let mut created_output_accounts = Vec::new(); created_token_output_accounts.iter().for_each(|x| { - created_output_accounts.push(x.compressed_account.clone()); + created_output_accounts.push(x.account.clone()); }); created_change_output_account.iter().for_each(|x| { created_output_accounts.push(x.clone()); @@ -743,7 +748,7 @@ pub async fn decompress_test, + input_compressed_accounts: Vec, amount: u64, output_merkle_tree_pubkey: &Pubkey, recipient_token_account: &Pubkey, @@ -752,6 +757,11 @@ pub async fn decompress_test>, ) { + let input_compressed_accounts: Vec = + input_compressed_accounts + .into_iter() + .map(Into::into) + .collect(); let max_amount: u64 = input_compressed_accounts .iter() .map(|x| x.token_data.amount) @@ -890,7 +900,7 @@ pub async fn decompress_test>() .as_slice(), None, @@ -1103,7 +1113,7 @@ pub async fn compress_test>() .as_slice(), None, @@ -1134,7 +1144,7 @@ pub async fn approve_test, + input_compressed_accounts: Vec, delegated_amount: u64, delegate_lamports: Option, delegate: &Pubkey, @@ -1142,6 +1152,11 @@ pub async fn approve_test, ) { + let input_compressed_accounts: Vec = + input_compressed_accounts + .into_iter() + .map(Into::into) + .collect(); let input_compressed_account_hashes = input_compressed_accounts .iter() .map(|x| x.compressed_account.hash().unwrap()) @@ -1264,7 +1279,7 @@ pub async fn approve_test>() .as_slice(), change_lamports, @@ -1312,10 +1327,15 @@ pub async fn revoke_test, + input_compressed_accounts: Vec, output_account_merkle_tree: &Pubkey, transaction_params: Option, ) { + let input_compressed_accounts: Vec = + input_compressed_accounts + .into_iter() + .map(Into::into) + .collect(); let input_compressed_account_hashes = input_compressed_accounts .iter() .map(|x| x.compressed_account.hash().unwrap()) @@ -1400,7 +1420,7 @@ pub async fn revoke_test>() .as_slice(), change_lamports, @@ -1436,7 +1456,7 @@ pub async fn freeze_test, + input_compressed_accounts: Vec, outputs_merkle_tree: &Pubkey, transaction_params: Option, ) { @@ -1455,7 +1475,7 @@ pub async fn thaw_test, + input_compressed_accounts: Vec, outputs_merkle_tree: &Pubkey, transaction_params: Option, ) { @@ -1478,10 +1498,15 @@ pub async fn freeze_or_thaw_test< authority: &Keypair, rpc: &mut R, test_indexer: &mut I, - input_compressed_accounts: Vec, + input_compressed_accounts: Vec, outputs_merkle_tree: &Pubkey, transaction_params: Option, ) { + let input_compressed_accounts: Vec = + input_compressed_accounts + .into_iter() + .map(Into::into) + .collect(); let input_compressed_account_hashes = input_compressed_accounts .iter() .map(|x| x.compressed_account.hash().unwrap()) @@ -1602,7 +1627,7 @@ pub async fn freeze_or_thaw_test< expected_compressed_output_accounts.as_slice(), created_output_accounts .iter() - .map(|x| x.compressed_account.clone()) + .map(|x| x.account.clone()) .collect::>() .as_slice(), change_lamports, @@ -1620,7 +1645,7 @@ pub async fn burn_test, + input_compressed_accounts: Vec, change_account_merkle_tree: &Pubkey, burn_amount: u64, signer_is_delegate: bool, @@ -1628,6 +1653,11 @@ pub async fn burn_test = + input_compressed_accounts + .into_iter() + .map(Into::into) + .collect(); let ( input_compressed_account_hashes, input_merkle_tree_pubkeys, @@ -1727,7 +1757,7 @@ pub async fn burn_test>() .as_slice(), change_lamports, @@ -1764,7 +1794,7 @@ pub async fn create_burn_test_instruction &mut Vec; - fn get_token_compressed_accounts(&self) -> &Vec; + fn get_token_compressed_accounts(&self) -> &Vec; fn get_group_pda(&self) -> &Pubkey; @@ -55,7 +54,7 @@ pub trait TestIndexerExtensions { fn get_compressed_accounts_with_merkle_context_by_owner( &self, owner: &Pubkey, - ) -> Vec; + ) -> Vec; fn add_state_bundle(&mut self, state_bundle: StateMerkleTreeBundle); @@ -63,10 +62,7 @@ pub trait TestIndexerExtensions { &mut self, slot: u64, event: &PublicTransactionEvent, - ) -> ( - Vec, - Vec, - ); + ) -> (Vec, Vec); fn get_proof_by_index(&mut self, merkle_tree_pubkey: Pubkey, index: u64) -> MerkleProof; diff --git a/sdk-libs/program-test/src/indexer/test_indexer.rs b/sdk-libs/program-test/src/indexer/test_indexer.rs index deff79f9ea..d870e358ff 100644 --- a/sdk-libs/program-test/src/indexer/test_indexer.rs +++ b/sdk-libs/program-test/src/indexer/test_indexer.rs @@ -33,11 +33,8 @@ use light_client::{ }, }; use light_compressed_account::{ - compressed_account::{CompressedAccountWithMerkleContext, MerkleContext}, - hash_chain::create_hash_chain_from_slice, - instruction_data::compressed_proof::CompressedProof, - tx_hash::create_tx_hash, - TreeType, + compressed_account::MerkleContext, hash_chain::create_hash_chain_from_slice, + instruction_data::compressed_proof::CompressedProof, tx_hash::create_tx_hash, TreeType, }; /// Discriminator for compressible accounts that store onchain_pubkey in the first 32 bytes of data. /// Re-exported from light_compressible for convenience. @@ -69,7 +66,7 @@ use light_prover_client::{ }, }; use light_sdk::light_hasher::Hash; -use light_token::compat::{TokenData, TokenDataWithMerkleContext}; +use light_token::compat::TokenData; use log::info; use num_bigint::{BigInt, BigUint}; use num_traits::FromBytes; @@ -101,10 +98,10 @@ pub struct TestIndexer { pub address_merkle_trees: Vec, pub payer: Keypair, pub group_pda: Pubkey, - pub compressed_accounts: Vec, - pub nullified_compressed_accounts: Vec, - pub token_compressed_accounts: Vec, - pub token_nullified_compressed_accounts: Vec, + pub compressed_accounts: Vec, + pub nullified_compressed_accounts: Vec, + pub token_compressed_accounts: Vec, + pub token_nullified_compressed_accounts: Vec, pub events: Vec, /// Index mapping onchain_pubkey to compressed account index. pub onchain_pubkey_index: HashMap<[u8; 32], usize>, @@ -178,18 +175,14 @@ impl Indexer for TestIndexer { _options: Option, _config: Option, ) -> Result>, IndexerError> { - let accounts_with_context = ::get_compressed_accounts_with_merkle_context_by_owner(self, owner); - let accounts: Result, IndexerError> = accounts_with_context - .into_iter() - .map(|acc| acc.try_into()) - .collect(); + let accounts = ::get_compressed_accounts_with_merkle_context_by_owner(self, owner); Ok(Response { context: Context { slot: self.get_current_slot(), }, value: ItemsWithCursor { - items: accounts?, + items: accounts, cursor: None, }, }) @@ -203,12 +196,9 @@ impl Indexer for TestIndexer { let account = self .compressed_accounts .iter() - .find(|acc| acc.compressed_account.address == Some(address)); + .find(|acc| acc.address == Some(address)); - let account_data = match account { - Some(acc) => Some(acc.clone().try_into()?), - None => None, - }; + let account_data = account.cloned(); Ok(Response { context: Context { @@ -223,25 +213,15 @@ impl Indexer for TestIndexer { hash: Hash, _config: Option, ) -> Result>, IndexerError> { - let res = self - .compressed_accounts - .iter() - .find(|acc| acc.hash() == Ok(hash)); + let res = self.compressed_accounts.iter().find(|acc| acc.hash == hash); - // TODO: unify token accounts with compressed accounts. - let account = if res.is_none() { - let res = self - .token_compressed_accounts - .iter() - .find(|acc| acc.compressed_account.hash() == Ok(hash)); - res.map(|x| &x.compressed_account) + let account_data = if res.is_some() { + res.cloned() } else { - res - }; - - let account_data = match account { - Some(acc) => Some(acc.clone().try_into()?), - None => None, + self.token_compressed_accounts + .iter() + .find(|acc| acc.account.hash == hash) + .map(|x| x.account.clone()) }; Ok(Response { @@ -259,15 +239,12 @@ impl Indexer for TestIndexer { _config: Option, ) -> Result>, IndexerError> { let mint = options.as_ref().and_then(|opts| opts.mint); - let token_accounts: Result, IndexerError> = self + let token_accounts: Vec = self .token_compressed_accounts .iter() - .filter(|acc| { - acc.token_data.owner == *owner && mint.is_none_or(|m| acc.token_data.mint == m) - }) - .map(|acc| CompressedTokenAccount::try_from(acc.clone())) + .filter(|acc| acc.token.owner == *owner && mint.is_none_or(|m| acc.token.mint == m)) + .cloned() .collect(); - let token_accounts = token_accounts?; let token_accounts = if let Some(options) = options { if let Some(limit) = options.limit { token_accounts.into_iter().take(limit as usize).collect() @@ -325,11 +302,11 @@ impl Indexer for TestIndexer { (Some(address), _) => self .token_compressed_accounts .iter() - .find(|acc| acc.compressed_account.compressed_account.address == Some(address)), + .find(|acc| acc.account.address == Some(address)), (_, Some(hash)) => self .token_compressed_accounts .iter() - .find(|acc| acc.compressed_account.hash() == Ok(hash)), + .find(|acc| acc.account.hash == hash), (None, None) => { return Err(IndexerError::InvalidParameters( "Either address or hash must be provided".to_string(), @@ -338,7 +315,7 @@ impl Indexer for TestIndexer { }; let amount = account - .map(|acc| acc.token_data.amount) + .map(|acc| acc.token.amount) .ok_or(IndexerError::AccountNotFound)?; Ok(Response { @@ -357,39 +334,37 @@ impl Indexer for TestIndexer { ) -> Result>>, IndexerError> { match (addresses, hashes) { (Some(addresses), _) => { - let accounts: Result>, IndexerError> = addresses + let accounts: Vec> = addresses .iter() .map(|addr| { self.compressed_accounts .iter() - .find(|acc| acc.compressed_account.address == Some(*addr)) - .map(|acc| acc.clone().try_into()) - .transpose() + .find(|acc| acc.address == Some(*addr)) + .cloned() }) .collect(); Ok(Response { context: Context { slot: self.get_current_slot(), }, - value: Items { items: accounts? }, + value: Items { items: accounts }, }) } (_, Some(hashes)) => { - let accounts: Result>, IndexerError> = hashes + let accounts: Vec> = hashes .iter() .map(|hash| { self.compressed_accounts .iter() - .find(|acc| acc.hash() == Ok(*hash)) - .map(|acc| acc.clone().try_into()) - .transpose() + .find(|acc| acc.hash == *hash) + .cloned() }) .collect(); Ok(Response { context: Context { slot: self.get_current_slot(), }, - value: Items { items: accounts? }, + value: Items { items: accounts }, }) } (None, None) => Err(IndexerError::InvalidParameters( @@ -408,11 +383,9 @@ impl Indexer for TestIndexer { let balances: Vec = self .token_compressed_accounts .iter() - .filter(|acc| { - acc.token_data.owner == *owner && mint.is_none_or(|m| acc.token_data.mint == m) - }) + .filter(|acc| acc.token.owner == *owner && mint.is_none_or(|m| acc.token.mint == m)) .fold(std::collections::HashMap::new(), |mut map, acc| { - *map.entry(acc.token_data.mint).or_insert(0) += acc.token_data.amount; + *map.entry(acc.token.mint).or_insert(0) += acc.token.amount; map }) .into_iter() @@ -1109,7 +1082,7 @@ impl TestIndexerExtensions for TestIndexer { &mut self.address_merkle_trees } - fn get_token_compressed_accounts(&self) -> &Vec { + fn get_token_compressed_accounts(&self) -> &Vec { &self.token_compressed_accounts } @@ -1140,10 +1113,10 @@ impl TestIndexerExtensions for TestIndexer { fn get_compressed_accounts_with_merkle_context_by_owner( &self, owner: &Pubkey, - ) -> Vec { + ) -> Vec { self.compressed_accounts .iter() - .filter(|x| x.compressed_account.owner.to_bytes() == owner.to_bytes()) + .filter(|x| x.owner.to_bytes() == owner.to_bytes()) .cloned() .collect() } @@ -1156,10 +1129,7 @@ impl TestIndexerExtensions for TestIndexer { &mut self, slot: u64, event: &PublicTransactionEvent, - ) -> ( - Vec, - Vec, - ) { + ) -> (Vec, Vec) { let mut compressed_accounts = Vec::new(); let mut token_compressed_accounts = Vec::new(); let event_inputs_len = event.input_compressed_account_hashes.len(); @@ -1381,13 +1351,12 @@ impl TestIndexer { pub fn find_compressed_account_by_onchain_pubkey( &self, onchain_pubkey: &[u8; 32], - ) -> Option<&CompressedAccountWithMerkleContext> { + ) -> Option<&CompressedAccount> { let matches: Vec<_> = self .compressed_accounts .iter() .filter(|acc| { - Self::extract_onchain_pubkey_from_data(acc.compressed_account.data.as_ref()) - .as_ref() + Self::extract_onchain_pubkey_from_data(acc.data.as_ref()).as_ref() == Some(onchain_pubkey) }) .collect(); @@ -1405,7 +1374,7 @@ impl TestIndexer { pub fn find_multiple_compressed_accounts_by_onchain_pubkeys( &self, onchain_pubkeys: &[[u8; 32]], - ) -> Vec> { + ) -> Vec> { onchain_pubkeys .iter() .map(|pubkey| self.find_compressed_account_by_onchain_pubkey(pubkey)) @@ -1416,15 +1385,12 @@ impl TestIndexer { pub fn find_token_account_by_onchain_pubkey( &self, onchain_pubkey: &[u8; 32], - ) -> Option<&TokenDataWithMerkleContext> { + ) -> Option<&CompressedTokenAccount> { let matches: Vec<_> = self .token_compressed_accounts .iter() .filter(|acc| { - Self::extract_onchain_pubkey_from_data( - acc.compressed_account.compressed_account.data.as_ref(), - ) - .as_ref() + Self::extract_onchain_pubkey_from_data(acc.account.data.as_ref()).as_ref() == Some(onchain_pubkey) }) .collect(); @@ -1442,16 +1408,16 @@ impl TestIndexer { pub fn find_compressed_account_by_pda_seed( &self, pda_pubkey: &[u8; 32], - ) -> Option<&CompressedAccountWithMerkleContext> { + ) -> Option<&CompressedAccount> { // Try each address tree to find an account whose address matches for address_tree in &self.address_merkle_trees { let tree_pubkey = address_tree.accounts.merkle_tree.to_bytes(); // For each compressed account with an address, check if it was derived from this seed for acc in &self.compressed_accounts { - if let Some(address) = acc.compressed_account.address { + if let Some(address) = acc.address { // Try deriving with this tree and the account's owner as program_id - let owner_bytes = acc.compressed_account.owner.to_bytes(); + let owner_bytes = acc.owner.to_bytes(); let derived = light_compressed_account::address::derive_address( pda_pubkey, &tree_pubkey, @@ -1471,16 +1437,16 @@ impl TestIndexer { pub fn find_token_account_by_pda_seed( &self, pda_pubkey: &[u8; 32], - ) -> Option<&TokenDataWithMerkleContext> { + ) -> Option<&CompressedTokenAccount> { // Try each address tree to find an account whose address matches for address_tree in &self.address_merkle_trees { let tree_pubkey = address_tree.accounts.merkle_tree.to_bytes(); // For each token compressed account with an address, check if it was derived from this seed for acc in &self.token_compressed_accounts { - if let Some(address) = acc.compressed_account.compressed_account.address { + if let Some(address) = acc.account.address { // Try deriving with this tree and the account's owner as program_id - let owner_bytes = acc.compressed_account.compressed_account.owner.to_bytes(); + let owner_bytes = acc.account.owner.to_bytes(); let derived = light_compressed_account::address::derive_address( pda_pubkey, &tree_pubkey, @@ -1742,8 +1708,8 @@ impl TestIndexer { pub fn get_compressed_balance(&self, owner: &Pubkey) -> u64 { self.compressed_accounts .iter() - .filter(|x| x.compressed_account.owner.to_bytes() == owner.to_bytes()) - .map(|x| x.compressed_account.lamports) + .filter(|x| x.owner.to_bytes() == owner.to_bytes()) + .map(|x| x.lamports) .sum() } @@ -1751,11 +1717,8 @@ impl TestIndexer { pub fn get_compressed_token_balance(&self, owner: &Pubkey, mint: &Pubkey) -> u64 { self.token_compressed_accounts .iter() - .filter(|x| { - x.compressed_account.compressed_account.owner.to_bytes() == owner.to_bytes() - && x.token_data.mint == *mint - }) - .map(|x| x.token_data.amount) + .filter(|x| x.account.owner.to_bytes() == owner.to_bytes() && x.token.mint == *mint) + .map(|x| x.token.amount) .sum() } @@ -1764,8 +1727,8 @@ impl TestIndexer { slot: u64, event: &PublicTransactionEvent, i: usize, - token_compressed_accounts: &mut Vec, - compressed_accounts: &mut Vec, + token_compressed_accounts: &mut Vec, + compressed_accounts: &mut Vec, ) { let mut input_addresses = vec![]; let mut new_addresses = vec![]; @@ -1819,9 +1782,9 @@ impl TestIndexer { && (is_v1_token || is_v2_token || is_v3_token) { if let Ok(token_data) = TokenData::deserialize(&mut data.data.as_slice()) { - let token_account = TokenDataWithMerkleContext { + let token_account_program = light_token::compat::TokenDataWithMerkleContext { token_data, - compressed_account: CompressedAccountWithMerkleContext { + compressed_account: light_compressed_account::compressed_account::CompressedAccountWithMerkleContext { compressed_account: compressed_account .compressed_account .clone(), @@ -1834,11 +1797,13 @@ impl TestIndexer { }, }, }; + let token_account = CompressedTokenAccount::try_from(token_account_program) + .expect("TokenDataWithMerkleContext to CompressedTokenAccount conversion failed"); token_compressed_accounts.push(token_account.clone()); self.token_compressed_accounts.insert(0, token_account); } } else { - let compressed_account = CompressedAccountWithMerkleContext { + let acc_program = light_compressed_account::compressed_account::CompressedAccountWithMerkleContext { compressed_account: compressed_account.compressed_account.clone(), merkle_context: MerkleContext { leaf_index: event.output_leaf_indices[i], @@ -1848,12 +1813,15 @@ impl TestIndexer { tree_type: merkle_tree.tree_type, }, }; - compressed_accounts.push(compressed_account.clone()); - self.compressed_accounts.insert(0, compressed_account); + let acc: CompressedAccount = acc_program + .try_into() + .expect("CompressedAccountWithMerkleContext to CompressedAccount conversion failed"); + compressed_accounts.push(acc.clone()); + self.compressed_accounts.insert(0, acc); } } None => { - let compressed_account = CompressedAccountWithMerkleContext { + let acc_program = light_compressed_account::compressed_account::CompressedAccountWithMerkleContext { compressed_account: compressed_account.compressed_account.clone(), merkle_context: MerkleContext { leaf_index: event.output_leaf_indices[i], @@ -1863,8 +1831,11 @@ impl TestIndexer { tree_type: merkle_tree.tree_type, }, }; - compressed_accounts.push(compressed_account.clone()); - self.compressed_accounts.insert(0, compressed_account); + let acc: CompressedAccount = acc_program.try_into().expect( + "CompressedAccountWithMerkleContext to CompressedAccount conversion failed", + ); + compressed_accounts.push(acc.clone()); + self.compressed_accounts.insert(0, acc); } }; let merkle_tree = &mut self.state_merkle_trees.iter_mut().find(|x| { @@ -1919,37 +1890,28 @@ impl TestIndexer { ) .unwrap(); let hash = event.input_compressed_account_hashes[i]; - let index = self - .compressed_accounts - .iter() - .position(|x| x.hash().unwrap() == hash); + let index = self.compressed_accounts.iter().position(|x| x.hash == hash); let (leaf_index, merkle_tree_pubkey) = if let Some(index) = index { self.nullified_compressed_accounts .push(self.compressed_accounts[index].clone()); - let leaf_index = self.compressed_accounts[index].merkle_context.leaf_index; - let merkle_tree_pubkey = self.compressed_accounts[index] - .merkle_context - .merkle_tree_pubkey; - if let Some(address) = self.compressed_accounts[index].compressed_account.address { - input_addresses.push(address); + let acc = &self.compressed_accounts[index]; + let leaf_index = acc.leaf_index; + let merkle_tree_pubkey = acc.tree_info.tree; + if let Some(addr) = acc.address { + input_addresses.push(addr); } self.compressed_accounts.remove(index); (Some(leaf_index), Some(merkle_tree_pubkey)) } else if let Some(index) = self .token_compressed_accounts .iter() - .position(|x| x.compressed_account.hash().unwrap() == hash) + .position(|x| x.account.hash == hash) { self.token_nullified_compressed_accounts .push(self.token_compressed_accounts[index].clone()); - let leaf_index = self.token_compressed_accounts[index] - .compressed_account - .merkle_context - .leaf_index; - let merkle_tree_pubkey = self.token_compressed_accounts[index] - .compressed_account - .merkle_context - .merkle_tree_pubkey; + let acc = &self.token_compressed_accounts[index].account; + let leaf_index = acc.leaf_index; + let merkle_tree_pubkey = acc.tree_info.tree; self.token_compressed_accounts.remove(index); (Some(leaf_index), Some(merkle_tree_pubkey)) } else { diff --git a/sdk-libs/program-test/src/program_test/extensions.rs b/sdk-libs/program-test/src/program_test/extensions.rs index 8615d1b644..97dfc1a87d 100644 --- a/sdk-libs/program-test/src/program_test/extensions.rs +++ b/sdk-libs/program-test/src/program_test/extensions.rs @@ -1,11 +1,10 @@ use anchor_lang::solana_program::pubkey::Pubkey; use async_trait::async_trait; use light_client::indexer::{ - AddressMerkleTreeAccounts, MerkleProof, NewAddressProofWithContext, StateMerkleTreeAccounts, + AddressMerkleTreeAccounts, CompressedAccount, CompressedTokenAccount, MerkleProof, + NewAddressProofWithContext, StateMerkleTreeAccounts, }; -use light_compressed_account::compressed_account::CompressedAccountWithMerkleContext; use light_event::event::PublicTransactionEvent; -use light_token::compat::TokenDataWithMerkleContext; use solana_sdk::signature::Keypair; use crate::{ @@ -83,7 +82,7 @@ impl TestIndexerExtensions for LightProgramTest { .get_address_merkle_trees_mut() } - fn get_token_compressed_accounts(&self) -> &Vec { + fn get_token_compressed_accounts(&self) -> &Vec { self.indexer() .expect("Indexer not initialized") .get_token_compressed_accounts() @@ -109,7 +108,7 @@ impl TestIndexerExtensions for LightProgramTest { fn get_compressed_accounts_with_merkle_context_by_owner( &self, owner: &Pubkey, - ) -> Vec { + ) -> Vec { self.indexer() .expect("Indexer not initialized") .get_compressed_accounts_with_merkle_context_by_owner(owner) @@ -125,10 +124,7 @@ impl TestIndexerExtensions for LightProgramTest { &mut self, slot: u64, event: &PublicTransactionEvent, - ) -> ( - Vec, - Vec, - ) { + ) -> (Vec, Vec) { self.indexer_mut() .expect("Indexer not initialized") .add_event_and_compressed_accounts(slot, event) diff --git a/sdk-libs/program-test/src/program_test/rpc.rs b/sdk-libs/program-test/src/program_test/rpc.rs index af9381ae47..cf26d98738 100644 --- a/sdk-libs/program-test/src/program_test/rpc.rs +++ b/sdk-libs/program-test/src/program_test/rpc.rs @@ -388,17 +388,10 @@ impl Rpc for LightProgramTest { // Cold: check TestIndexer (mirrors Photon cold_lookup behavior) if let Some(indexer) = self.indexer.as_ref() { // First try: lookup by onchain_pubkey (for accounts with DECOMPRESSED_PDA_DISCRIMINATOR) - if let Some(compressed_with_ctx) = + if let Some(compressed) = indexer.find_compressed_account_by_onchain_pubkey(&address.to_bytes()) { - let compressed: CompressedAccount = compressed_with_ctx.clone().try_into().map_err( - |e| { - RpcError::CustomError(format!( - "CompressedAccountWithMerkleContext conversion failed for address {}: {:?}", - address, e - )) - }, - )?; + let compressed = compressed.clone(); let synthetic = synthesize_pda_account(&compressed); return Ok(Response { context: Context { slot }, @@ -411,17 +404,10 @@ impl Rpc for LightProgramTest { } // Second try: lookup by PDA seed (for accounts whose address was derived from this pubkey) - if let Some(compressed_with_ctx) = + if let Some(compressed) = indexer.find_compressed_account_by_pda_seed(&address.to_bytes()) { - let compressed: CompressedAccount = compressed_with_ctx.clone().try_into().map_err( - |e| { - RpcError::CustomError(format!( - "CompressedAccountWithMerkleContext conversion failed for PDA seed {}: {:?}", - address, e - )) - }, - )?; + let compressed = compressed.clone(); let synthetic = synthesize_pda_account(&compressed); return Ok(Response { context: Context { slot }, @@ -439,21 +425,14 @@ impl Rpc for LightProgramTest { .or_else(|| indexer.find_token_account_by_pda_seed(&address.to_bytes())); if let Some(token_acc) = token_acc { - let compressed: CompressedAccount = token_acc - .compressed_account - .clone() - .try_into() - .map_err(|e| RpcError::CustomError(format!("conversion error: {:?}", e)))?; + let compressed = token_acc.account.clone(); let wallet_owner = indexer .ata_owner_map .get(address) .copied() .unwrap_or(*address); - let synthetic = synthesize_token_account( - &token_acc.token_data, - &wallet_owner, - compressed.lamports, - ); + let synthetic = + synthesize_token_account(&token_acc.token, &wallet_owner, compressed.lamports); return Ok(Response { context: Context { slot }, value: Some(AccountInterface::cold( @@ -544,11 +523,8 @@ impl Rpc for LightProgramTest { for (lookup_idx, maybe_compressed) in cold_results.into_iter().enumerate() { let original_idx = cold_lookup_indices[lookup_idx]; - if let Some(compressed_with_ctx) = maybe_compressed { - let compressed: CompressedAccount = - compressed_with_ctx.clone().try_into().map_err(|e| { - RpcError::CustomError(format!("conversion error: {:?}", e)) - })?; + if let Some(compressed) = maybe_compressed { + let compressed = compressed.clone(); let synthetic = synthesize_pda_account(&compressed); results[original_idx] = Some(AccountInterface::cold( *addresses[original_idx], @@ -567,13 +543,8 @@ impl Rpc for LightProgramTest { for (i, pubkey) in pda_seed_lookup_pubkeys.iter().enumerate() { let original_idx = pda_seed_lookup_indices[i]; - if let Some(compressed_with_ctx) = - indexer.find_compressed_account_by_pda_seed(pubkey) - { - let compressed: CompressedAccount = - compressed_with_ctx.clone().try_into().map_err(|e| { - RpcError::CustomError(format!("conversion error: {:?}", e)) - })?; + if let Some(compressed) = indexer.find_compressed_account_by_pda_seed(pubkey) { + let compressed = compressed.clone(); let synthetic = synthesize_pda_account(&compressed); results[original_idx] = Some(AccountInterface::cold( *addresses[original_idx], @@ -597,18 +568,12 @@ impl Rpc for LightProgramTest { .or_else(|| indexer.find_token_account_by_pda_seed(pubkey)); if let Some(token_acc) = token_acc { - let compressed: CompressedAccount = token_acc - .compressed_account - .clone() - .try_into() - .map_err(|e| { - RpcError::CustomError(format!("conversion error: {:?}", e)) - })?; + let compressed = token_acc.account.clone(); let addr = addresses[original_idx]; let wallet_owner = indexer.ata_owner_map.get(addr).copied().unwrap_or(*addr); let synthetic = synthesize_token_account( - &token_acc.token_data, + &token_acc.token, &wallet_owner, compressed.lamports, ); diff --git a/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/test.rs b/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/test.rs index e19d0742de..748ac8f842 100644 --- a/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/test.rs +++ b/sdk-tests/sdk-anchor-test/programs/sdk-anchor-test/tests/test.rs @@ -78,12 +78,7 @@ async fn test_anchor_sdk_test() { rpc.get_compressed_accounts_with_merkle_context_by_owner(&sdk_anchor_test::ID); assert_eq!(compressed_accounts.len(), 1); let compressed_account = &compressed_accounts[0]; - let record = &compressed_account - .compressed_account - .data - .as_ref() - .unwrap() - .data; + let record = &compressed_account.data.as_ref().unwrap().data; let record = MyCompressedAccount::deserialize(&mut &record[..]).unwrap(); assert_eq!(record.nested.one, 2); diff --git a/sdk-tests/sdk-native-test/Cargo.toml b/sdk-tests/sdk-native-test/Cargo.toml index 398f4e19ec..e854710c41 100644 --- a/sdk-tests/sdk-native-test/Cargo.toml +++ b/sdk-tests/sdk-native-test/Cargo.toml @@ -30,6 +30,7 @@ light-compressed-account = { workspace = true, features = ["solana"] } light-zero-copy = { workspace = true } [dev-dependencies] +light-client = { workspace = true } light-program-test = { workspace = true, features = ["devenv"] } tokio = { workspace = true } solana-sdk = { workspace = true } diff --git a/sdk-tests/sdk-native-test/tests/test.rs b/sdk-tests/sdk-native-test/tests/test.rs index 30d792487f..12fd2fec77 100644 --- a/sdk-tests/sdk-native-test/tests/test.rs +++ b/sdk-tests/sdk-native-test/tests/test.rs @@ -1,10 +1,8 @@ #![cfg(feature = "test-sbf")] use borsh::BorshSerialize; -use light_compressed_account::{ - address::derive_address, compressed_account::CompressedAccountWithMerkleContext, - hashv_to_bn254_field_size_be, -}; +use light_client::indexer::CompressedAccount as ClientCompressedAccount; +use light_compressed_account::{address::derive_address, hashv_to_bn254_field_size_be}; use light_program_test::{ program_test::LightProgramTest, AddressWithTree, Indexer, ProgramTestConfig, Rpc, RpcError, }; @@ -70,7 +68,7 @@ async fn test_sdk_native_test() { .unwrap(); assert_eq!(compressed_pda.address.unwrap(), address); - update_pda(&payer, &mut rpc, [2u8; ARRAY_LEN], compressed_pda.into()) + update_pda(&payer, &mut rpc, [2u8; ARRAY_LEN], compressed_pda) .await .unwrap(); } @@ -131,7 +129,7 @@ pub async fn update_pda( payer: &Keypair, rpc: &mut LightProgramTest, new_account_data: [u8; ARRAY_LEN], - compressed_account: CompressedAccountWithMerkleContext, + compressed_account: ClientCompressedAccount, ) -> Result<(), RpcError> { let system_account_meta_config = SystemAccountMetaConfig::new(sdk_v1_native_test::ID); let mut accounts = PackedAccounts::default(); @@ -141,7 +139,7 @@ pub async fn update_pda( .unwrap(); let rpc_result = rpc - .get_validity_proof(vec![compressed_account.hash().unwrap()], vec![], None) + .get_validity_proof(vec![compressed_account.hash], vec![], None) .await? .value; @@ -152,7 +150,7 @@ pub async fn update_pda( let meta = CompressedAccountMeta { tree_info: packed_accounts.packed_tree_infos[0], - address: compressed_account.compressed_account.address.unwrap(), + address: compressed_account.address.unwrap(), output_state_tree_index: packed_accounts.output_tree_index, }; @@ -160,13 +158,7 @@ pub async fn update_pda( let instruction_data = UpdatePdaInstructionData { my_compressed_account: UpdateMyCompressedAccount { meta, - data: compressed_account - .compressed_account - .data - .unwrap() - .data - .try_into() - .unwrap(), + data: compressed_account.data.unwrap().data.try_into().unwrap(), }, proof: rpc_result.proof, new_data: new_account_data, diff --git a/sdk-tests/sdk-pinocchio-v1-test/Cargo.toml b/sdk-tests/sdk-pinocchio-v1-test/Cargo.toml index 420a029148..e2d4bc1b3f 100644 --- a/sdk-tests/sdk-pinocchio-v1-test/Cargo.toml +++ b/sdk-tests/sdk-pinocchio-v1-test/Cargo.toml @@ -28,6 +28,7 @@ light-macros = { workspace = true } borsh = { workspace = true } [dev-dependencies] +light-client = { workspace = true } light-program-test = { workspace = true, features = ["devenv"] } tokio = { workspace = true } solana-sdk = { workspace = true } diff --git a/sdk-tests/sdk-pinocchio-v1-test/tests/test.rs b/sdk-tests/sdk-pinocchio-v1-test/tests/test.rs index 0ae7f5c029..b8c1007ea1 100644 --- a/sdk-tests/sdk-pinocchio-v1-test/tests/test.rs +++ b/sdk-tests/sdk-pinocchio-v1-test/tests/test.rs @@ -1,7 +1,7 @@ #![cfg(feature = "test-sbf")] use borsh::BorshSerialize; -use light_compressed_account::compressed_account::CompressedAccountWithMerkleContext; +use light_client::indexer::CompressedAccount as ClientCompressedAccount; use light_program_test::{ program_test::LightProgramTest, AddressWithTree, Indexer, ProgramTestConfig, Rpc, RpcError, }; @@ -67,7 +67,7 @@ async fn test_pinocchio_sdk_test() { .clone(); assert_eq!(compressed_pda.address.unwrap(), address); - update_pda(&payer, &mut rpc, [2u8; 31], compressed_pda.into()) + update_pda(&payer, &mut rpc, [2u8; 31], compressed_pda) .await .unwrap(); } @@ -128,7 +128,7 @@ pub async fn update_pda( payer: &Keypair, rpc: &mut LightProgramTest, new_account_data: [u8; 31], - compressed_account: CompressedAccountWithMerkleContext, + compressed_account: ClientCompressedAccount, ) -> Result<(), RpcError> { let system_account_meta_config = SystemAccountMetaConfig::new(Pubkey::new_from_array(sdk_pinocchio_v1_test::ID)); @@ -139,7 +139,7 @@ pub async fn update_pda( .unwrap(); let rpc_result = rpc - .get_validity_proof(vec![compressed_account.hash().unwrap()], vec![], None) + .get_validity_proof(vec![compressed_account.hash], vec![], None) .await? .value; @@ -150,7 +150,7 @@ pub async fn update_pda( let light_sdk_meta = CompressedAccountMeta { tree_info: packed_accounts.packed_tree_infos[0], - address: compressed_account.compressed_account.address.unwrap(), + address: compressed_account.address.unwrap(), output_state_tree_index: packed_accounts.output_tree_index, }; @@ -171,13 +171,7 @@ pub async fn update_pda( let instruction_data = UpdatePdaInstructionData { my_compressed_account: UpdateMyCompressedAccount { meta, - data: compressed_account - .compressed_account - .data - .unwrap() - .data - .try_into() - .unwrap(), + data: compressed_account.data.unwrap().data.try_into().unwrap(), }, proof: rpc_result.proof, new_data: new_account_data, diff --git a/sdk-tests/sdk-pinocchio-v2-test/Cargo.toml b/sdk-tests/sdk-pinocchio-v2-test/Cargo.toml index 499a6a7920..d6a3e9f700 100644 --- a/sdk-tests/sdk-pinocchio-v2-test/Cargo.toml +++ b/sdk-tests/sdk-pinocchio-v2-test/Cargo.toml @@ -28,6 +28,7 @@ light-macros = { workspace = true } borsh = { workspace = true } [dev-dependencies] +light-client = { workspace = true } light-program-test = { workspace = true, features = ["devenv"] } tokio = { workspace = true } solana-sdk = { workspace = true } diff --git a/sdk-tests/sdk-pinocchio-v2-test/tests/test.rs b/sdk-tests/sdk-pinocchio-v2-test/tests/test.rs index 59a0562c63..b8e71021ab 100644 --- a/sdk-tests/sdk-pinocchio-v2-test/tests/test.rs +++ b/sdk-tests/sdk-pinocchio-v2-test/tests/test.rs @@ -1,10 +1,8 @@ #![cfg(feature = "test-sbf")] use borsh::BorshSerialize; -use light_compressed_account::{ - address::derive_address, compressed_account::CompressedAccountWithMerkleContext, - hashv_to_bn254_field_size_be, -}; +use light_client::indexer::CompressedAccount as ClientCompressedAccount; +use light_compressed_account::{address::derive_address, hashv_to_bn254_field_size_be}; use light_program_test::{ program_test::LightProgramTest, AddressWithTree, Indexer, ProgramTestConfig, Rpc, RpcError, }; @@ -77,7 +75,7 @@ async fn test_pinocchio_sdk_test() { .clone(); assert_eq!(compressed_pda.address.unwrap(), address); - update_pda(&payer, &mut rpc, [2u8; 31], compressed_pda.into()) + update_pda(&payer, &mut rpc, [2u8; 31], compressed_pda) .await .unwrap(); } @@ -138,7 +136,7 @@ pub async fn update_pda( payer: &Keypair, rpc: &mut LightProgramTest, new_account_data: [u8; 31], - compressed_account: CompressedAccountWithMerkleContext, + compressed_account: ClientCompressedAccount, ) -> Result<(), RpcError> { let system_account_meta_config = SystemAccountMetaConfig::new(Pubkey::new_from_array(sdk_pinocchio_v2_test::ID)); @@ -149,7 +147,7 @@ pub async fn update_pda( .unwrap(); let rpc_result = rpc - .get_validity_proof(vec![compressed_account.hash().unwrap()], vec![], None) + .get_validity_proof(vec![compressed_account.hash], vec![], None) .await? .value; @@ -160,7 +158,7 @@ pub async fn update_pda( let light_sdk_meta = CompressedAccountMeta { tree_info: packed_accounts.packed_tree_infos[0], - address: compressed_account.compressed_account.address.unwrap(), + address: compressed_account.address.unwrap(), output_state_tree_index: packed_accounts.output_tree_index, }; @@ -181,13 +179,7 @@ pub async fn update_pda( let instruction_data = UpdatePdaInstructionData { my_compressed_account: UpdateMyCompressedAccount { meta, - data: compressed_account - .compressed_account - .data - .unwrap() - .data - .try_into() - .unwrap(), + data: compressed_account.data.unwrap().data.try_into().unwrap(), }, proof: light_sdk_pinocchio::instruction::ValidityProof(None), new_data: new_account_data, diff --git a/sdk-tests/sdk-v1-native-test/Cargo.toml b/sdk-tests/sdk-v1-native-test/Cargo.toml index 766daf2e5c..3efb603f51 100644 --- a/sdk-tests/sdk-v1-native-test/Cargo.toml +++ b/sdk-tests/sdk-v1-native-test/Cargo.toml @@ -30,6 +30,7 @@ light-compressed-account = { workspace = true, features = ["solana"] } light-zero-copy = { workspace = true } [dev-dependencies] +light-client = { workspace = true } light-program-test = { workspace = true, features = ["devenv"] } tokio = { workspace = true } solana-sdk = { workspace = true } diff --git a/sdk-tests/sdk-v1-native-test/tests/test.rs b/sdk-tests/sdk-v1-native-test/tests/test.rs index a93beab599..6987bd0440 100644 --- a/sdk-tests/sdk-v1-native-test/tests/test.rs +++ b/sdk-tests/sdk-v1-native-test/tests/test.rs @@ -1,7 +1,7 @@ //#![cfg(feature = "test-sbf")] use borsh::BorshSerialize; -use light_compressed_account::compressed_account::CompressedAccountWithMerkleContext; +use light_client::indexer::CompressedAccount as ClientCompressedAccount; use light_program_test::{ program_test::LightProgramTest, AddressWithTree, Indexer, ProgramTestConfig, Rpc, RpcError, }; @@ -61,7 +61,7 @@ async fn test_sdk_native_test() { .unwrap(); assert_eq!(compressed_pda.address.unwrap(), address); - update_pda(&payer, &mut rpc, [2u8; ARRAY_LEN], compressed_pda.into()) + update_pda(&payer, &mut rpc, [2u8; ARRAY_LEN], compressed_pda) .await .unwrap(); } @@ -122,7 +122,7 @@ pub async fn update_pda( payer: &Keypair, rpc: &mut LightProgramTest, new_account_data: [u8; ARRAY_LEN], - compressed_account: CompressedAccountWithMerkleContext, + compressed_account: ClientCompressedAccount, ) -> Result<(), RpcError> { let system_account_meta_config = SystemAccountMetaConfig::new(sdk_v1_native_test::ID); let mut accounts = PackedAccounts::default(); @@ -132,7 +132,7 @@ pub async fn update_pda( .unwrap(); let rpc_result = rpc - .get_validity_proof(vec![compressed_account.hash().unwrap()], vec![], None) + .get_validity_proof(vec![compressed_account.hash], vec![], None) .await? .value; @@ -143,7 +143,7 @@ pub async fn update_pda( let meta = CompressedAccountMeta { tree_info: packed_accounts.packed_tree_infos[0], - address: compressed_account.compressed_account.address.unwrap(), + address: compressed_account.address.unwrap(), output_state_tree_index: packed_accounts.output_tree_index, }; @@ -151,13 +151,7 @@ pub async fn update_pda( let instruction_data = UpdatePdaInstructionData { my_compressed_account: UpdateMyCompressedAccount { meta, - data: compressed_account - .compressed_account - .data - .unwrap() - .data - .try_into() - .unwrap(), + data: compressed_account.data.unwrap().data.try_into().unwrap(), }, proof: rpc_result.proof, new_data: new_account_data, From a86c9860c7d28848b534fddbc6dd5e2d4a5d8430 Mon Sep 17 00:00:00 2001 From: Swenschaeferjohann Date: Thu, 12 Feb 2026 16:00:49 +0000 Subject: [PATCH 9/9] fix lint --- .../compressed-token-test/tests/v1.rs | 311 +++++++----------- program-tests/system-test/tests/test.rs | 111 ++++--- program-tests/utils/src/spl.rs | 18 +- 3 files changed, 187 insertions(+), 253 deletions(-) diff --git a/program-tests/compressed-token-test/tests/v1.rs b/program-tests/compressed-token-test/tests/v1.rs index 3765f725d8..685ce1ff18 100644 --- a/program-tests/compressed-token-test/tests/v1.rs +++ b/program-tests/compressed-token-test/tests/v1.rs @@ -60,7 +60,7 @@ use light_test_utils::{ }, LightClient, Rpc, RpcError, }; -use light_token::compat::{AccountState, TokenDataWithMerkleContext}; +use light_token::compat::AccountState; use rand::{seq::SliceRandom, thread_rng, Rng}; use serial_test::serial; #[allow(deprecated)] @@ -1087,11 +1087,7 @@ async fn test_mint_to_and_burn_from_all_token_pools() { .value .items; let input_compressed_account = accounts[0].clone(); - let change_account_merkle_tree = input_compressed_account - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let change_account_merkle_tree = input_compressed_account.account.tree_info.tree; burn_test( &payer, &mut rpc, @@ -1334,11 +1330,8 @@ async fn test_delegation( .unwrap() .value .items; - let delegated_compressed_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let delegated_compressed_account_merkle_tree = + input_compressed_accounts[0].account.tree_info.tree; approve_test( &sender, &mut rpc, @@ -1475,11 +1468,8 @@ async fn test_delegation_mixed() { .unwrap() .value .items; - let delegated_compressed_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let delegated_compressed_account_merkle_tree = + input_compressed_accounts[0].account.tree_info.tree; approve_test( &sender, &mut rpc, @@ -1517,10 +1507,7 @@ async fn test_delegation_mixed() { .items; input_compressed_accounts .extend_from_slice(&[delegate_input_compressed_accounts[0].clone()]); - let delegate_lamports = delegate_input_compressed_accounts[0] - .account - .account - .lamports; + let delegate_lamports = delegate_input_compressed_accounts[0].account.lamports; let delegate_input_amount = input_compressed_accounts .iter() .map(|x| x.token.amount) @@ -1714,11 +1701,8 @@ async fn test_approve_failing() { .value .items; let delegated_amount = 1000u64; - let delegated_compressed_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let delegated_compressed_account_merkle_tree = + input_compressed_accounts[0].account.tree_info.tree; let input_compressed_account_hashes = input_compressed_accounts .iter() @@ -2036,11 +2020,8 @@ async fn test_revoke(num_inputs: usize, mint_amount: u64, delegated_amount: u64) .items; for input in input_compressed_accounts.iter() { let input_compressed_accounts = vec![input.clone()]; - let delegated_compressed_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let delegated_compressed_account_merkle_tree = + input_compressed_accounts[0].account.tree_info.tree; approve_test( &sender, &mut rpc, @@ -2066,18 +2047,15 @@ async fn test_revoke(num_inputs: usize, mint_amount: u64, delegated_amount: u64) .items .iter() .filter(|x| x.token.delegate.is_some()) - .map(|x| x.clone().into()) + .cloned() .collect::>(); let input_compressed_accounts = input_compressed_accounts .iter() .filter(|x| x.token.delegate.is_some()) .cloned() .collect::>(); - let delegated_compressed_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let delegated_compressed_account_merkle_tree = + input_compressed_accounts[0].account.tree_info.tree; revoke_test( &sender, &mut rpc, @@ -2160,11 +2138,8 @@ async fn test_revoke_failing() { .value .items; let delegated_amount = 1000u64; - let delegated_compressed_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let delegated_compressed_account_merkle_tree = + input_compressed_accounts[0].account.tree_info.tree; approve_test( &sender, &mut rpc, @@ -2403,11 +2378,7 @@ async fn test_burn() { .value .items; let burn_amount = 1000u64; - let change_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let change_account_merkle_tree = input_compressed_accounts[0].account.tree_info.tree; burn_test( &sender, &mut rpc, @@ -2431,11 +2402,8 @@ async fn test_burn() { .value .items; let delegated_amount = 1000u64; - let delegated_compressed_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let delegated_compressed_account_merkle_tree = + input_compressed_accounts[0].account.tree_info.tree; approve_test( &sender, &mut rpc, @@ -2464,11 +2432,7 @@ async fn test_burn() { .cloned() .collect::>(); let burn_amount = 100; - let change_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let change_account_merkle_tree = input_compressed_accounts[0].account.tree_info.tree; burn_test( &delegate, &mut rpc, @@ -2500,11 +2464,7 @@ async fn test_burn() { .iter() .map(|x| x.token.amount) .sum::(); - let change_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let change_account_merkle_tree = input_compressed_accounts[0].account.tree_info.tree; burn_test( &delegate, &mut rpc, @@ -2543,18 +2503,15 @@ async fn test_burn() { .items .iter() .filter(|x| x.token.amount != 0) - .map(|x| x.clone().into()) + .cloned() .collect::>()[0..4] .to_vec(); let burn_amount = input_compressed_accounts .iter() .map(|x| x.token.amount) .sum(); - let invalid_change_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .queue_pubkey - .into(); + let invalid_change_account_merkle_tree = + input_compressed_accounts[0].account.tree_info.queue; let mut additional_token_pool_accounts = (0..4) .map(|x| get_token_pool_pda_with_index(&mint, x)) .collect::>(); @@ -2594,7 +2551,7 @@ async fn test_burn() { .items .iter() .filter(|x| x.token.amount != 0) - .map(|x| x.clone().into()) + .cloned() .collect::>(); let burn_amount = input_compressed_accounts .iter() @@ -2675,11 +2632,8 @@ async fn failing_tests_burn() { .value .items; let delegated_amount = 1000u64; - let delegated_compressed_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let delegated_compressed_account_merkle_tree = + input_compressed_accounts[0].account.tree_info.tree; approve_test( &sender, &mut rpc, @@ -2703,11 +2657,7 @@ async fn failing_tests_burn() { .value .items; let burn_amount = 1; - let change_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let change_account_merkle_tree = input_compressed_accounts[0].account.tree_info.tree; let (_, _, _, _, instruction) = create_burn_test_instruction( &sender, &mut rpc, @@ -2737,11 +2687,7 @@ async fn failing_tests_burn() { .value .items; let burn_amount = 1; - let change_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let change_account_merkle_tree = input_compressed_accounts[0].account.tree_info.tree; let (_, _, _, _, instruction) = create_burn_test_instruction( &delegate, &mut rpc, @@ -2779,11 +2725,7 @@ async fn failing_tests_burn() { .cloned() .collect::>(); let burn_amount = 1; - let change_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let change_account_merkle_tree = input_compressed_accounts[0].account.tree_info.tree; let (_, _, _, _, instruction) = create_burn_test_instruction( &sender, &mut rpc, @@ -2812,11 +2754,7 @@ async fn failing_tests_burn() { .value .items; let burn_amount = 1; - let change_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let change_account_merkle_tree = input_compressed_accounts[0].account.tree_info.tree; let (_, _, _, _, instruction) = create_burn_test_instruction( &delegate, &mut rpc, @@ -2849,11 +2787,7 @@ async fn failing_tests_burn() { .value .items; let burn_amount = 1; - let change_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let change_account_merkle_tree = input_compressed_accounts[0].account.tree_info.tree; let (_, _, _, _, instruction) = create_burn_test_instruction( &sender, &mut rpc, @@ -2887,11 +2821,8 @@ async fn failing_tests_burn() { .value .items; let burn_amount = 1; - let invalid_change_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .queue_pubkey - .into(); + let invalid_change_account_merkle_tree = + input_compressed_accounts[0].account.tree_info.queue; let (_, _, _, _, instruction) = create_burn_test_instruction( &sender, &mut rpc, @@ -2925,11 +2856,8 @@ async fn failing_tests_burn() { .value .items; let burn_amount = 1; - let invalid_change_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .queue_pubkey - .into(); + let invalid_change_account_merkle_tree = + input_compressed_accounts[0].account.tree_info.queue; let (_, _, _, _, instruction) = create_burn_test_instruction( &sender, &mut rpc, @@ -2958,11 +2886,8 @@ async fn failing_tests_burn() { .value .items; let burn_amount = 1; - let invalid_change_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .queue_pubkey - .into(); + let invalid_change_account_merkle_tree = + input_compressed_accounts[0].account.tree_info.queue; let (_, _, _, _, mut instruction) = create_burn_test_instruction( &sender, &mut rpc, @@ -3037,11 +2962,7 @@ async fn test_freeze_and_thaw(mint_amount: u64, delegated_amount: u64) { .unwrap() .value .items; - let output_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let output_merkle_tree = input_compressed_accounts[0].account.tree_info.tree; freeze_test( &payer, @@ -3066,11 +2987,7 @@ async fn test_freeze_and_thaw(mint_amount: u64, delegated_amount: u64) { .filter(|x| x.token.state == AccountState::Frozen) .cloned() .collect::>(); - let output_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let output_merkle_tree = input_compressed_accounts[0].account.tree_info.tree; thaw_test( &payer, &mut rpc, @@ -3089,11 +3006,8 @@ async fn test_freeze_and_thaw(mint_amount: u64, delegated_amount: u64) { .unwrap() .value .items; - let delegated_compressed_account_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let delegated_compressed_account_merkle_tree = + input_compressed_accounts[0].account.tree_info.tree; approve_test( &sender, &mut rpc, @@ -3116,11 +3030,7 @@ async fn test_freeze_and_thaw(mint_amount: u64, delegated_amount: u64) { .unwrap() .value .items; - let output_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let output_merkle_tree = input_compressed_accounts[0].account.tree_info.tree; freeze_test( &payer, @@ -3145,11 +3055,7 @@ async fn test_freeze_and_thaw(mint_amount: u64, delegated_amount: u64) { .filter(|x| x.token.state == AccountState::Frozen) .cloned() .collect::>(); - let output_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let output_merkle_tree = input_compressed_accounts[0].account.tree_info.tree; thaw_test( &payer, @@ -3226,13 +3132,8 @@ async fn test_failing_freeze() { .unwrap() .value .items[0] - .clone() - .into()]; - let outputs_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + .clone()]; + let outputs_merkle_tree = input_compressed_accounts[0].account.tree_info.tree; let input_compressed_account_hashes = input_compressed_accounts .iter() @@ -3266,8 +3167,12 @@ async fn test_failing_freeze() { .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.account.account) - .cloned() + .map(|x| { + light_compressed_account::compressed_account::CompressedAccountWithMerkleContext::from( + x.account.clone(), + ) + .compressed_account + }) .collect::>(), outputs_merkle_tree, root_indices: proof_rpc_result.value.get_root_indices().clone(), @@ -3305,8 +3210,12 @@ async fn test_failing_freeze() { .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.account.account) - .cloned() + .map(|x| { + light_compressed_account::compressed_account::CompressedAccountWithMerkleContext::from( + x.account.clone(), + ) + .compressed_account + }) .collect::>(), outputs_merkle_tree: invalid_merkle_tree.pubkey(), root_indices: proof_rpc_result.value.get_root_indices().clone(), @@ -3353,8 +3262,12 @@ async fn test_failing_freeze() { .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.account.account) - .cloned() + .map(|x| { + light_compressed_account::compressed_account::CompressedAccountWithMerkleContext::from( + x.account.clone(), + ) + .compressed_account + }) .collect::>(), outputs_merkle_tree, root_indices: proof_rpc_result.value.get_root_indices().clone(), @@ -3396,13 +3309,8 @@ async fn test_failing_freeze() { .filter(|x| x.token.state == AccountState::Frozen) .cloned() .collect::>(); - let input_compressed_accounts: Vec = - vec![accounts[0].clone().into()]; - let outputs_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + let input_compressed_accounts: Vec = vec![accounts[0].clone()]; + let outputs_merkle_tree = input_compressed_accounts[0].account.tree_info.tree; let input_compressed_account_hashes = input_compressed_accounts .iter() @@ -3430,8 +3338,12 @@ async fn test_failing_freeze() { .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.account.account) - .cloned() + .map(|x| { + light_compressed_account::compressed_account::CompressedAccountWithMerkleContext::from( + x.account.clone(), + ) + .compressed_account + }) .collect::>(), outputs_merkle_tree, root_indices: proof_rpc_result.value.get_root_indices().clone(), @@ -3507,13 +3419,8 @@ async fn test_failing_thaw() { .unwrap() .value .items[0] - .clone() - .into()]; - let output_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey - .into(); + .clone()]; + let output_merkle_tree = input_compressed_accounts[0].account.tree_info.tree; freeze_test( &payer, @@ -3537,10 +3444,7 @@ async fn test_failing_thaw() { .filter(|x| x.token.state == AccountState::Frozen) .cloned() .collect::>(); - let outputs_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey; + let outputs_merkle_tree = input_compressed_accounts[0].account.tree_info.tree; let input_compressed_account_hashes = input_compressed_accounts .iter() @@ -3574,10 +3478,14 @@ async fn test_failing_thaw() { .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.account.account) - .cloned() + .map(|x| { + light_compressed_account::compressed_account::CompressedAccountWithMerkleContext::from( + x.account.clone(), + ) + .compressed_account + }) .collect::>(), - outputs_merkle_tree: outputs_merkle_tree.into(), + outputs_merkle_tree, root_indices: proof_rpc_result.value.get_root_indices().clone(), proof: proof_rpc_result.value.proof.0.unwrap(), }; @@ -3613,8 +3521,12 @@ async fn test_failing_thaw() { .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.account.account) - .cloned() + .map(|x| { + light_compressed_account::compressed_account::CompressedAccountWithMerkleContext::from( + x.account.clone(), + ) + .compressed_account + }) .collect::>(), outputs_merkle_tree: invalid_merkle_tree.pubkey(), root_indices: proof_rpc_result.value.get_root_indices().clone(), @@ -3661,10 +3573,14 @@ async fn test_failing_thaw() { .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.account.account) - .cloned() + .map(|x| { + light_compressed_account::compressed_account::CompressedAccountWithMerkleContext::from( + x.account.clone(), + ) + .compressed_account + }) .collect::>(), - outputs_merkle_tree: outputs_merkle_tree.into(), + outputs_merkle_tree, root_indices: proof_rpc_result.value.get_root_indices().clone(), proof: invalid_proof, }; @@ -3696,10 +3612,7 @@ async fn test_failing_thaw() { .filter(|x| x.token.state == AccountState::Initialized) .cloned() .collect::>(); - let outputs_merkle_tree = input_compressed_accounts[0] - .account - .merkle_context - .merkle_tree_pubkey; + let outputs_merkle_tree = input_compressed_accounts[0].account.tree_info.tree; let input_compressed_account_hashes = input_compressed_accounts .iter() @@ -3727,10 +3640,14 @@ async fn test_failing_thaw() { .collect(), input_compressed_accounts: input_compressed_accounts .iter() - .map(|x| &x.account.account) - .cloned() + .map(|x| { + light_compressed_account::compressed_account::CompressedAccountWithMerkleContext::from( + x.account.clone(), + ) + .compressed_account + }) .collect::>(), - outputs_merkle_tree: outputs_merkle_tree.into(), + outputs_merkle_tree, root_indices: proof_rpc_result.value.get_root_indices().clone(), proof: proof_rpc_result.value.proof.0.unwrap(), }; @@ -4352,8 +4269,12 @@ pub async fn failing_compress_decompress( .as_slice(), &input_compressed_accounts .iter() - .map(|x| &x.account.account) - .cloned() + .map(|x| { + light_compressed_account::compressed_account::CompressedAccountWithMerkleContext::from( + x.account.clone(), + ) + .compressed_account + }) .collect::>(), *mint, None, @@ -4596,7 +4517,7 @@ async fn test_invalid_inputs() { &recipient_keypair, &proof_rpc_result.value.proof.0, proof_rpc_result.value.get_root_indices().as_slice(), - &input_compressed_accounts, + &input_compressed_accounts_program, false, ) .await; @@ -4612,11 +4533,13 @@ async fn test_invalid_inputs() { sdk_to_program_token_data(input_compressed_account_token_data_invalid_amount); let mut input_compressed_accounts = vec![test_indexer.token_compressed_accounts[0].account.clone()]; + let mut vec = Vec::new(); crate::TokenData::serialize( &input_compressed_account_token_data_invalid_amount, - &mut input_compressed_accounts[0].data.as_mut().unwrap().data[..], + &mut vec, ) .unwrap(); + input_compressed_accounts[0].data.as_mut().unwrap().data = vec; let change_out_compressed_account_0 = TokenTransferOutputData { amount: input_compressed_account_token_data.amount - 1000, owner: recipient_keypair.pubkey(), @@ -4970,8 +4893,12 @@ async fn test_transfer_with_transaction_hash() { .as_slice(), &input_compressed_accounts .iter() - .map(|x| &x.account.account) - .cloned() + .map(|x| { + light_compressed_account::compressed_account::CompressedAccountWithMerkleContext::from( + x.account.clone(), + ) + .compressed_account + }) .collect::>(), mint, None, diff --git a/program-tests/system-test/tests/test.rs b/program-tests/system-test/tests/test.rs index 8dd80580aa..7d1b419ca9 100644 --- a/program-tests/system-test/tests/test.rs +++ b/program-tests/system-test/tests/test.rs @@ -1027,15 +1027,14 @@ async fn invoke_test() { let compressed_account_with_context = rpc.indexer.as_ref().unwrap().compressed_accounts[0].clone(); let proof_rpc_res = rpc - .get_validity_proof( - vec![compressed_account_with_context.hash().unwrap()], - vec![], - None, - ) + .get_validity_proof(vec![compressed_account_with_context.hash], vec![], None) .await .unwrap(); let proof = proof_rpc_res.value.proof.0.unwrap(); - let input_compressed_accounts = vec![compressed_account_with_context.compressed_account]; + let input_compressed_accounts = vec![ + CompressedAccountWithMerkleContext::from(compressed_account_with_context.clone()) + .compressed_account, + ]; let instruction = create_invoke_instruction( &payer_pubkey, @@ -1567,16 +1566,14 @@ async fn test_with_compression() { .unwrap() .clone(); let proof_rpc_res = rpc - .get_validity_proof( - vec![compressed_account_with_context.hash().unwrap()], - vec![], - None, - ) + .get_validity_proof(vec![compressed_account_with_context.hash], vec![], None) .await .unwrap(); let proof = proof_rpc_res.value.proof.0.unwrap(); - let input_compressed_accounts = - vec![compressed_account_with_context.clone().compressed_account]; + let input_compressed_accounts = vec![ + CompressedAccountWithMerkleContext::from(compressed_account_with_context.clone()) + .compressed_account, + ]; let recipient_pubkey = Keypair::new().pubkey(); let output_compressed_accounts = vec![CompressedAccount { lamports: 0, @@ -1989,13 +1986,9 @@ async fn batch_invoke_test() { "compressed_account_with_context {:?}", compressed_account_with_context ); - println!("hash {:?}", compressed_account_with_context.hash()); + println!("hash {:?}", compressed_account_with_context.hash); let proof_rpc_result = rpc - .get_validity_proof( - vec![compressed_account_with_context.hash().unwrap()], - vec![], - None, - ) + .get_validity_proof(vec![compressed_account_with_context.hash], vec![], None) .await .unwrap(); // No proof since value is in output queue @@ -2005,7 +1998,10 @@ async fn batch_invoke_test() { .root_index .proof_by_index()); - let input_compressed_accounts = vec![compressed_account_with_context.compressed_account]; + let input_compressed_accounts = vec![ + CompressedAccountWithMerkleContext::from(compressed_account_with_context.clone()) + .compressed_account, + ]; let instruction = create_invoke_instruction( &payer_pubkey, @@ -2014,7 +2010,7 @@ async fn batch_invoke_test() { &output_compressed_accounts, &[MerkleContext { merkle_tree_pubkey: merkle_tree_pubkey.into(), - leaf_index: compressed_account_with_context.merkle_context.leaf_index, + leaf_index: compressed_account_with_context.leaf_index, queue_pubkey: output_queue_pubkey.into(), prove_by_index: true, tree_type: TreeType::StateV2, @@ -2093,7 +2089,7 @@ async fn batch_invoke_test() { let input_compressed_account = rpc .get_compressed_accounts_with_merkle_context_by_owner(&payer_pubkey) .iter() - .filter(|x| x.merkle_context.queue_pubkey.to_bytes() == output_queue_pubkey.to_bytes()) + .filter(|x| x.tree_info.queue.to_bytes() == output_queue_pubkey.to_bytes()) .next_back() .unwrap() .clone(); @@ -2103,14 +2099,17 @@ async fn batch_invoke_test() { data: None, address: None, }]; + let input_compressed_account_program = + CompressedAccountWithMerkleContext::from(input_compressed_account.clone()) + .compressed_account; let instruction = create_invoke_instruction( &payer_pubkey, &payer_pubkey, - &[input_compressed_account.compressed_account], + &[input_compressed_account_program], &output_compressed_accounts, &[MerkleContext { merkle_tree_pubkey: merkle_tree_pubkey.into(), - leaf_index: input_compressed_account.merkle_context.leaf_index - 1, + leaf_index: input_compressed_account.leaf_index - 1, queue_pubkey: output_queue_pubkey.into(), prove_by_index: true, tree_type: TreeType::StateV2, @@ -2151,8 +2150,8 @@ async fn batch_invoke_test() { .compressed_accounts .iter() .filter(|x| { - x.compressed_account.owner.to_bytes() == payer_pubkey.to_bytes() - && x.merkle_context.queue_pubkey.to_bytes() == output_queue_pubkey.to_bytes() + x.owner.to_bytes() == payer_pubkey.to_bytes() + && x.tree_info.queue.to_bytes() == output_queue_pubkey.to_bytes() }) .cloned() .collect::>() @@ -2167,8 +2166,8 @@ async fn batch_invoke_test() { .compressed_accounts .iter() .filter(|x| { - x.compressed_account.owner.to_bytes() == payer_pubkey.to_bytes() - && x.merkle_context.queue_pubkey.to_bytes() + x.owner.to_bytes() == payer_pubkey.to_bytes() + && x.tree_info.queue.to_bytes() == env.v1_state_trees[0].nullifier_queue.to_bytes() }) .collect::>()[0] @@ -2176,8 +2175,8 @@ async fn batch_invoke_test() { let proof_rpc_result = rpc .get_validity_proof( vec![ - compressed_account_with_context_1.hash().unwrap(), - compressed_account_with_context_2.hash().unwrap(), + compressed_account_with_context_1.hash, + compressed_account_with_context_2.hash, ], vec![], None, @@ -2187,15 +2186,16 @@ async fn batch_invoke_test() { let proof = proof_rpc_result.value.proof.0.unwrap(); + let ctx_1 = + CompressedAccountWithMerkleContext::from(compressed_account_with_context_1.clone()); + let ctx_2 = + CompressedAccountWithMerkleContext::from(compressed_account_with_context_2.clone()); let input_compressed_accounts = vec![ - compressed_account_with_context_1.compressed_account, - compressed_account_with_context_2.compressed_account, + ctx_1.compressed_account.clone(), + ctx_2.compressed_account.clone(), ]; - let merkle_context = vec![ - compressed_account_with_context_1.merkle_context, - compressed_account_with_context_2.merkle_context, - ]; + let merkle_context = vec![ctx_1.merkle_context, ctx_2.merkle_context]; let output_compressed_accounts = vec![ CompressedAccount { lamports: 0, @@ -2210,8 +2210,8 @@ async fn batch_invoke_test() { address: None, }, ]; - let merkle_context_1 = compressed_account_with_context_1.merkle_context; - let merkle_context_2 = compressed_account_with_context_2.merkle_context; + let merkle_context_1 = ctx_1.merkle_context; + let merkle_context_2 = ctx_2.merkle_context; let instruction = create_invoke_instruction( &payer_pubkey, &payer_pubkey, @@ -2257,8 +2257,8 @@ async fn batch_invoke_test() { .compressed_accounts .iter() .filter(|x| { - x.compressed_account.owner.to_bytes() == payer_pubkey.to_bytes() - && x.merkle_context.queue_pubkey.to_bytes() == output_queue_pubkey.to_bytes() + x.owner.to_bytes() == payer_pubkey.to_bytes() + && x.tree_info.queue.to_bytes() == output_queue_pubkey.to_bytes() }) .next_back() .unwrap() @@ -2267,7 +2267,7 @@ async fn batch_invoke_test() { &mut rpc, &payer, TestMode::ByZkpThenIndex, - compressed_account_with_context_1.clone(), + compressed_account_with_context_1.clone().into(), ) .await; assert_rpc_error( @@ -2290,8 +2290,8 @@ async fn batch_invoke_test() { .compressed_accounts .iter() .filter(|x| { - x.compressed_account.owner.to_bytes() == payer_pubkey.to_bytes() - && x.merkle_context.queue_pubkey.to_bytes() == output_queue_pubkey.to_bytes() + x.owner.to_bytes() == payer_pubkey.to_bytes() + && x.tree_info.queue.to_bytes() == output_queue_pubkey.to_bytes() }) .next_back() .unwrap() @@ -2300,7 +2300,7 @@ async fn batch_invoke_test() { &mut rpc, &payer, TestMode::ByIndexThenZkp, - compressed_account_with_context_1.clone(), + compressed_account_with_context_1.clone().into(), ) .await; assert_rpc_error( @@ -2323,8 +2323,8 @@ async fn batch_invoke_test() { .compressed_accounts .iter() .filter(|x| { - x.compressed_account.owner.to_bytes() == payer_pubkey.to_bytes() - && x.merkle_context.queue_pubkey.to_bytes() == output_queue_pubkey.to_bytes() + x.owner.to_bytes() == payer_pubkey.to_bytes() + && x.tree_info.queue.to_bytes() == output_queue_pubkey.to_bytes() }) .next_back() .unwrap() @@ -2333,7 +2333,7 @@ async fn batch_invoke_test() { &mut rpc, &payer, TestMode::ByIndexThenIndex, - compressed_account_with_context_1.clone(), + compressed_account_with_context_1.clone().into(), ) .await; assert_rpc_error( @@ -2356,8 +2356,8 @@ async fn batch_invoke_test() { .compressed_accounts .iter() .filter(|x| { - x.compressed_account.owner.to_bytes() == payer_pubkey.to_bytes() - && x.merkle_context.queue_pubkey.to_bytes() == output_queue_pubkey.to_bytes() + x.owner.to_bytes() == payer_pubkey.to_bytes() + && x.tree_info.queue.to_bytes() == output_queue_pubkey.to_bytes() }) .next_back() .unwrap() @@ -2366,7 +2366,7 @@ async fn batch_invoke_test() { &mut rpc, &payer, TestMode::ByZkpThenZkp, - compressed_account_with_context_1.clone(), + compressed_account_with_context_1.clone().into(), ) .await; assert_rpc_error( @@ -2444,14 +2444,19 @@ async fn batch_invoke_test() { .compressed_accounts .iter() .filter(|x| { - x.compressed_account.owner.to_bytes() == payer_pubkey.to_bytes() - && x.merkle_context.queue_pubkey.to_bytes() != output_queue_pubkey.to_bytes() + x.owner.to_bytes() == payer_pubkey.to_bytes() + && x.tree_info.queue.to_bytes() != output_queue_pubkey.to_bytes() }) .next_back() .unwrap() .clone(); - let mut merkle_context = compressed_account_with_context_1.merkle_context; + let mut merkle_context = compressed_account_with_context_1 + .tree_info + .to_light_merkle_context( + compressed_account_with_context_1.leaf_index, + compressed_account_with_context_1.prove_by_index, + ); merkle_context.prove_by_index = true; let instruction = create_invoke_instruction( &payer_pubkey, diff --git a/program-tests/utils/src/spl.rs b/program-tests/utils/src/spl.rs index deadf9ab20..f02b59083b 100644 --- a/program-tests/utils/src/spl.rs +++ b/program-tests/utils/src/spl.rs @@ -1653,11 +1653,6 @@ pub async fn burn_test = - input_compressed_accounts - .into_iter() - .map(Into::into) - .collect(); let ( input_compressed_account_hashes, input_merkle_tree_pubkeys, @@ -1727,7 +1722,7 @@ pub async fn burn_test 0 { let expected_token_data = TokenData { mint: mint.into(), - owner: input_compressed_accounts[0].token_data.owner.into(), + owner: input_compressed_accounts[0].token.owner.into(), amount: output_amount, delegate: delegate.map(|d| d.into()), state: CompressedTokenAccountState::Initialized as u8, @@ -1744,7 +1739,7 @@ pub async fn burn_test(); let change_lamports = if sum_inputs > 0 { Some(vec![Some(sum_inputs)]) @@ -1794,7 +1789,7 @@ pub async fn create_burn_test_instruction>, ) -> (Vec<[u8; 32]>, Vec, Pubkey, u64, Instruction) { + let input_with_context: Vec = + input_compressed_accounts + .iter() + .cloned() + .map(Into::into) + .collect(); + let input_compressed_accounts = &input_with_context[..]; let input_compressed_account_hashes = input_compressed_accounts .iter() .map(|x| x.compressed_account.hash().unwrap())