Skip to content

Commit 805371c

Browse files
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 <cursoragent@cursor.com> 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 f2da063. 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
1 parent 291933b commit 805371c

32 files changed

Lines changed: 846 additions & 3387 deletions

forester/tests/test_indexer_interface.rs

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
/// 4. Compressible token accounts - on-chain accounts that can be compressed
1212
use std::collections::HashMap;
1313

14-
use anchor_lang::Discriminator;
14+
use anchor_lang::{AnchorDeserialize, Discriminator};
1515
use borsh::BorshSerialize;
1616
use create_address_test_program::create_invoke_cpi_instruction;
1717
use light_client::{
@@ -368,6 +368,13 @@ async fn test_indexer_interface_scenarios() {
368368
compressed_mint_pda, create_mint_sig
369369
);
370370

371+
// Warp forward so rent expires - required before CompressAndCloseMint
372+
let current_slot = rpc.get_slot().await.unwrap();
373+
let target_slot = current_slot + light_compressible::rent::SLOTS_PER_EPOCH * 30;
374+
rpc.warp_to_slot(target_slot)
375+
.await
376+
.expect("warp_to_slot so mint rent expires");
377+
371378
// Now compress and close the mint to make it fully compressed
372379
println!("Compressing mint via CompressAndCloseMint...");
373380

@@ -483,36 +490,41 @@ async fn test_indexer_interface_scenarios() {
483490
);
484491
println!(" PASSED: Compressible account resolved from on-chain");
485492

486-
// ============ Test 2: getTokenAccountInterface with compressible token account (on-chain) ============
487-
println!("\nTest 2: getTokenAccountInterface with compressible token account (on-chain)...");
488-
let compressible_token_interface = photon_indexer
489-
.get_token_account_interface(&compressible_token_account, None)
493+
println!("\nTest 2: getAccountInterface for compressible token account (on-chain)...");
494+
let compressible_token_interface = rpc
495+
.get_account_interface(&compressible_token_account, None)
490496
.await
491-
.expect("getTokenAccountInterface should not error")
497+
.expect("getAccountInterface should not error")
492498
.value
493-
.expect("Compressible token account should be found via token interface");
499+
.expect("Compressible token account should be found");
494500

495501
assert!(
496-
compressible_token_interface.account.is_hot(),
502+
compressible_token_interface.is_hot(),
497503
"Token account should be hot (on-chain)"
498504
);
499505
assert!(
500-
compressible_token_interface.account.cold.is_none(),
506+
compressible_token_interface.cold.is_none(),
501507
"On-chain token account should not have cold context"
502508
);
503509
assert_eq!(
504-
compressible_token_interface.account.key, compressible_token_account,
510+
compressible_token_interface.key, compressible_token_account,
505511
"Token account key should match"
506512
);
507-
assert_eq!(
508-
compressible_token_interface.token.mint, decompressed_mint_pda,
509-
"Token mint should match decompressed mint"
510-
);
511-
assert_eq!(
512-
compressible_token_interface.token.owner,
513-
compressible_owner.pubkey(),
514-
"Token owner should match compressible owner"
515-
);
513+
{
514+
let token = light_token_interface::state::Token::deserialize(
515+
&mut &compressible_token_interface.account.data[..],
516+
)
517+
.expect("parse token account");
518+
assert_eq!(
519+
token.mint, decompressed_mint_pda,
520+
"Token mint should match decompressed mint"
521+
);
522+
assert_eq!(
523+
token.owner,
524+
compressible_owner.pubkey(),
525+
"Token owner should match compressible owner"
526+
);
527+
}
516528
println!(" PASSED: Token account interface resolved with correct token data");
517529

518530
// ============ Test 3: getMultipleAccountInterfaces batch lookup ============

sdk-libs/client/src/indexer/photon_indexer.rs

Lines changed: 1 addition & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use tracing::{error, trace, warn};
99

1010
use super::types::{
1111
AccountInterface, CompressedAccount, CompressedTokenAccount, OwnerBalance,
12-
SignatureWithMetadata, TokenAccountInterface, TokenBalance,
12+
SignatureWithMetadata, TokenBalance,
1313
};
1414
use crate::indexer::{
1515
base58::Base58Conversions,
@@ -1761,46 +1761,6 @@ impl PhotonIndexer {
17611761
.await
17621762
}
17631763

1764-
pub async fn get_token_account_interface(
1765-
&self,
1766-
address: &Pubkey,
1767-
config: Option<IndexerRpcConfig>,
1768-
) -> Result<Response<Option<TokenAccountInterface>>, IndexerError> {
1769-
let response = self.get_account_interface(address, config).await?;
1770-
let value = match response.value {
1771-
Some(ai) => {
1772-
let token = parse_token_data_from_indexer_account(&ai)?;
1773-
Some(TokenAccountInterface { account: ai, token })
1774-
}
1775-
None => None,
1776-
};
1777-
Ok(Response {
1778-
context: response.context,
1779-
value,
1780-
})
1781-
}
1782-
1783-
pub async fn get_associated_token_account_interface(
1784-
&self,
1785-
owner: &Pubkey,
1786-
mint: &Pubkey,
1787-
config: Option<IndexerRpcConfig>,
1788-
) -> Result<Response<Option<TokenAccountInterface>>, IndexerError> {
1789-
let ata_address = light_token::instruction::get_associated_token_address(owner, mint);
1790-
let response = self.get_account_interface(&ata_address, config).await?;
1791-
let value = match response.value {
1792-
Some(ai) => {
1793-
let token = parse_token_data_from_indexer_account(&ai)?;
1794-
Some(TokenAccountInterface { account: ai, token })
1795-
}
1796-
None => None,
1797-
};
1798-
Ok(Response {
1799-
context: response.context,
1800-
value,
1801-
})
1802-
}
1803-
18041764
pub async fn get_multiple_account_interfaces(
18051765
&self,
18061766
addresses: Vec<&Pubkey>,
@@ -1853,19 +1813,3 @@ impl PhotonIndexer {
18531813
.await
18541814
}
18551815
}
1856-
1857-
/// Parse token data from an indexer AccountInterface.
1858-
/// For compressed (cold) accounts: borsh-deserializes TokenData from the cold data bytes.
1859-
/// For on-chain (hot) accounts: returns default TokenData (downstream conversion re-parses from SPL layout).
1860-
fn parse_token_data_from_indexer_account(
1861-
ai: &AccountInterface,
1862-
) -> Result<light_token::compat::TokenData, IndexerError> {
1863-
match &ai.cold {
1864-
Some(cold) => borsh::BorshDeserialize::deserialize(&mut cold.data.data.as_slice())
1865-
.map_err(|e| IndexerError::decode_error("token_data", e)),
1866-
None => {
1867-
// Hot account — downstream will re-parse from SPL account data directly
1868-
Ok(light_token::compat::TokenData::default())
1869-
}
1870-
}
1871-
}

0 commit comments

Comments
 (0)