-
Notifications
You must be signed in to change notification settings - Fork 87
feat: more ctoken extensions rebased #2141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughThis PR comprehensively refactors the compressed token (ctoken) architecture by embedding compression metadata directly within token accounts instead of relying on extensions, introducing TLV-based extension handling for Token 2022 compatibility (pausable, permanent delegate, transfer fee, transfer hook), redesigning the zero-copy token model, and replacing Compressible extension patterns with embedded CompressionInfo fields throughout the codebase. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~70 minutes This is a large, deeply interconnected architectural refactor affecting core compressed token state representation, compression metadata storage, extension handling, and transfer logic. The changes span ~100+ files across program logic, SDK, and tests with significant logic density in state deserialization, compression info embedding, TLV handling, and function signature updates. While individual changes are often straightforward (field additions, type replacements), their interdependencies demand careful verification that all callsites properly thread new fields, that zero-copy layouts remain correct, and that error paths align with the new extension model. Possibly related PRs
Suggested labels
Suggested reviewers
Key Technical ObservationsReading through this refactor, I can identify the core architectural shift: compression metadata is being lifted out of optional extensions and embedded directly into the token account structure. This is a substantial quality-of-life improvement because:
What to watch during review:
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 36
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (11)
sdk-libs/ctoken-sdk/src/compressed_token/v2/account2.rs (1)
252-284: Missing#[allow(clippy::too_many_arguments)]ondecompress_spl.The
compress_splmethod received the clippy annotation (line 193), butdecompress_splalso has 7 parameters now and will trigger the same warning. Consider adding the annotation here for consistency.🔎 Suggested fix:
#[profile] +#[allow(clippy::too_many_arguments)] pub fn decompress_spl( &mut self, amount: u64, source_index: u8, pool_account_index: u8, pool_index: u8, bump: u8, decimals: u8, ) -> Result<(), CTokenSdkError> {sdk-libs/program-test/src/forester/compress_and_close_forester.rs (2)
50-50: Hardcoded epoch value may not reflect actual on-chain state.The
current_epochis hardcoded to 0, which will not work correctly if the forester epoch has progressed beyond 0. Consider retrieving the current epoch from on-chain data or accepting it as a function parameter.
56-62: Remove unused config variable with default values.The
configvariable is created with default pubkeys but never meaningfully used. Thecompression_authorityextracted from it (line 61) is immediately overridden by the authority found in the account loop (lines 130-135). This creates confusion about the actual authority source.🔎 Remove the unused config variable:
- let config = CompressibleConfig::ctoken_v1(Pubkey::default(), Pubkey::default()); - let compressible_config = CompressibleConfig::derive_v1_config_pda(®istry_program_id).0; - // Derive compression_authority PDA (uses u16 version) - let compression_authority = config.compression_authority; - println!("config compression_authority {:?}", compression_authority); -sdk-libs/token-client/src/actions/transfer2/compress.rs (1)
25-37: Consider decimals validation consistency across the Transfer2 API.The
compressfunction accepts decimals as a parameter without validating it against the mint's actual decimals. While the TransferChecked instruction checks the token mint and decimals value by the caller, this is a design choice that appears intentional—all Transfer2 operations (compress, decompress, spl_to_ctoken, ctoken_to_spl) follow this pattern, delegating validation to the caller. If you want to add validation here, ensure it's done consistently across all similar operations in the Transfer2 API, not justcompress. This would likely require an architectural discussion about whether the SDK should query the mint account for validation or continue delegating this responsibility to callers.sdk-libs/token-client/src/actions/transfer2/spl_to_ctoken.rs (1)
17-25: Add decimals validation by querying the mint account.The
decimalsparameter is passed directly to the SPLtransfer_checkedoperation without verification against the mint's actual decimals. The transfer_checked instruction asserts the token mint and decimals, so mismatched values cause runtime failures that are difficult to debug. Since the mint account is already available, extract and verify its decimals field before constructing the instruction—similar validation patterns should also be applied tocompress.rsandtransfer_ctoken_to_spl.rsfor consistency.sdk-libs/token-client/src/actions/transfer2/ctoken_to_spl.rs (1)
15-24: Decimals parameter addition is correct, but consider future refactoring.The addition of the
decimalsparameter is necessary for proper token transfer handling. While 8 parameters triggers a Clippy warning, the current design is acceptable. However, for future iterations, consider grouping related parameters (e.g., source account, destination account, amount, decimals) into aTransferParamsstruct to improve maintainability.programs/compressed-token/program/src/shared/owner_validation.rs (1)
66-96: Account delegate authorization appears to be removed from compression checks.The function now only authorizes the owner and permanent delegate from mint, but the original account-level delegate (
compressed_token.delegate) is no longer checked. Was this intentional?Previously, if a user delegated tokens to someone, that delegate could authorize operations. Now only the permanent delegate (a mint-level authority) can act alongside the owner.
If this is intentional (permanent delegate replaces account-level delegate for compression), please confirm. Otherwise, you may need:
🔎 Restore account delegate check
// Check if authority is the owner if *authority_key == owner_key { return Ok(()); // Owner can always compress } + // Check if authority is the account's delegate + if let Some(delegate) = compressed_token.delegate.as_deref() { + if *authority_key == delegate.to_bytes() { + return Ok(()); // Account delegate can compress delegated amount + } + } + // Check if authority is the permanent delegate from the mintAlso,
_compression_amountis unused—was this parameter intended for checking delegated amount limits?programs/compressed-token/program/src/transfer2/compression/spl.rs (1)
70-73:unimplemented!()will panic at runtime.If
CompressAndCloseis a valid mode that can be passed to this function, theunimplemented!()macro will cause a runtime panic rather than returning a graceful error. Consider returning aProgramErrorinstead to avoid unexpected program crashes.🔎 Suggested fix:
ZCompressionMode::CompressAndClose => { - msg!("CompressAndClose is unimplemented for spl token accounts"); - unimplemented!() + msg!("CompressAndClose is unimplemented for spl token accounts"); + return Err(ProgramError::InvalidArgument); }sdk-libs/ctoken-sdk/src/ctoken/transfer_spl_ctoken.rs (2)
40-52: Documentation example is stale.The doc example at lines 17-39 doesn't include the new
decimalsfield. Callers following the example will get compilation errors.🔎 Update the doc example
/// let instruction = TransferSplToCtoken { /// amount: 100, /// spl_interface_pda_bump: 255, +/// decimals: 9, /// source_spl_token_account, /// destination_ctoken_account,
66-80: CPI struct documentation example is also stale.The
TransferSplToCtokenCpidoc example (lines 54-79) is missing both the newdecimalsandsystem_programfields.programs/compressed-token/program/src/create_associated_token_account.rs (1)
75-82: Potential issue:Nonepassed for mint_account prevents extension detection.When
process_create_associated_token_account_inneris called from the non-idempotent path,mint_accountisNone, causinghas_mint_extensionsto return default (all false). If the mint actually has extensions likePausableorPermanentDelegate, the created token account will be undersized.The comment says "No mint account available in create_ata (owner/mint passed as bytes)" - but
mintis right there atowner_and_mint[1]. Should this passSome(mint)instead?🔎 Consider passing the mint account for extension detection:
process_create_associated_token_account_inner::<IDEMPOTENT>( remaining_accounts, owner.key(), mint.key(), instruction_inputs.bump, instruction_inputs.compressible_config, - None, // No mint account available in create_ata (owner/mint passed as bytes) + Some(mint), // Pass mint for extension detection )
program-libs/ctoken-interface/src/instructions/extensions/compressible.rs
Outdated
Show resolved
Hide resolved
program-libs/ctoken-interface/src/instructions/transfer2/instruction_data.rs
Show resolved
Hide resolved
| 32 => { | ||
| // Compressible variant (index 32 to avoid Token-2022 overlap) | ||
| let (compressible_ext, remaining_bytes) = | ||
| CompressionInfo::zero_copy_at_mut(remaining_data)?; | ||
| CompressibleExtension::zero_copy_at_mut(remaining_data)?; | ||
| Ok(( | ||
| ZExtensionStructMut::Compressible(compressible_ext), | ||
| remaining_bytes, | ||
| )) | ||
| } | ||
| 27 => { | ||
| // PausableAccount variant (marker extension, no data) | ||
| let (pausable_ext, remaining_bytes) = | ||
| PausableAccountExtension::zero_copy_at_mut(remaining_data)?; | ||
| Ok(( | ||
| ZExtensionStructMut::PausableAccount(pausable_ext), | ||
| remaining_bytes, | ||
| )) | ||
| } | ||
| 28 => { | ||
| // PermanentDelegateAccount variant (marker extension, no data) | ||
| let (permanent_delegate_ext, remaining_bytes) = | ||
| PermanentDelegateAccountExtension::zero_copy_at_mut(remaining_data)?; | ||
| Ok(( | ||
| ZExtensionStructMut::PermanentDelegateAccount(permanent_delegate_ext), | ||
| remaining_bytes, | ||
| )) | ||
| } | ||
| 29 => { | ||
| // TransferFeeAccount variant | ||
| let (transfer_fee_ext, remaining_bytes) = | ||
| TransferFeeAccountExtension::zero_copy_at_mut(remaining_data)?; | ||
| Ok(( | ||
| ZExtensionStructMut::TransferFeeAccount(transfer_fee_ext), | ||
| remaining_bytes, | ||
| )) | ||
| } | ||
| 30 => { | ||
| // TransferHookAccount variant | ||
| let (transfer_hook_ext, remaining_bytes) = | ||
| TransferHookAccountExtension::zero_copy_at_mut(remaining_data)?; | ||
| Ok(( | ||
| ZExtensionStructMut::TransferHookAccount(transfer_hook_ext), | ||
| remaining_bytes, | ||
| )) | ||
| } | ||
| 31 => { | ||
| // CompressedOnly variant | ||
| let (compressed_only_ext, remaining_bytes) = | ||
| CompressedOnlyExtension::zero_copy_at_mut(remaining_data)?; | ||
| Ok(( | ||
| ZExtensionStructMut::CompressedOnly(compressed_only_ext), | ||
| remaining_bytes, | ||
| )) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for discriminant-related code and constants in ctoken-interface
find program-libs/ctoken-interface -type f -name "*.rs" | head -20Repository: Lightprotocol/light-protocol
Length of output: 1518
🏁 Script executed:
#!/bin/bash
# Look for the extension_struct.rs file and surrounding context
find program-libs -name "extension_struct.rs" -o -name "CLAUDE.md" -o -name "DISCRIMINATOR.md" | head -20Repository: Lightprotocol/light-protocol
Length of output: 398
🏁 Script executed:
#!/bin/bash
# Check for discriminant or enum values in the file
rg "enum.*Extension|discriminant|DISCRIMINATOR" program-libs/ctoken-interface/src -B 3 -A 3 --type rust | head -100Repository: Lightprotocol/light-protocol
Length of output: 10251
🏁 Script executed:
#!/bin/bash
# Find and examine the extension_struct.rs file
find program-libs/ctoken-interface -name "extension_struct.rs" -exec cat {} \; | head -300Repository: Lightprotocol/light-protocol
Length of output: 11264
🏁 Script executed:
#!/bin/bash
# Look for CLAUDE.md documentation files
find program-libs/ctoken-interface -name "CLAUDE.md" -o -name "*.md" | head -10Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
#!/bin/bash
# Search for discriminant constants and enum definitions in ctoken-interface
rg "const.*DISCRIMIN|enum.*Extension" program-libs/ctoken-interface -B 2 -A 5 --type rust | head -150Repository: Lightprotocol/light-protocol
Length of output: 4655
🏁 Script executed:
#!/bin/bash
# Search for discriminant constants in ctoken-interface
rg "const.*27|const.*28|const.*29|const.*30|const.*31|const.*32" program-libs/ctoken-interface/src --type rust | head -50Repository: Lightprotocol/light-protocol
Length of output: 917
🏁 Script executed:
#!/bin/bash
# Check if extension discriminants should be extracted to constants
grep -n "bytes\[0\] = " program-libs/ctoken-interface/src/state/extensions/extension_struct.rsRepository: Lightprotocol/light-protocol
Length of output: 330
🏁 Script executed:
#!/bin/bash
# Look for any documentation or README in ctoken-interface
find program-libs/ctoken-interface -name "README.md" -o -name "*.md" | head -10Repository: Lightprotocol/light-protocol
Length of output: 54
Extract discriminants to named constants and document the serialization contract.
The discriminant mapping is intentionally decoupled from enum indices for Token-2022 compatibility (extensions 26-31 are reserved). However, the discriminant values (19, 27-32) are currently hardcoded as literals in multiple match arms and write operations, violating DRY principles. Extract these to named constants (e.g., const TOKEN_METADATA_DISCRIMINANT: u8 = 19;) and create a CLAUDE.md file documenting the complete discriminant-to-extension mapping as part of the serialization contract.
🤖 Prompt for AI Agents
In program-libs/ctoken-interface/src/state/extensions/extension_struct.rs around
lines 167 to 220, hardcoded discriminant literals (e.g., 19, 27..32) are used
directly in match arms and write operations; extract each discriminant into
clearly named u8 constants (for example const TOKEN_METADATA_DISCRIMINANT: u8 =
19;, const PAUSABLE_ACCOUNT_DISCRIMINANT: u8 = 27;, etc.), replace all literal
usages in match arms, serialization writes and any other places with those
constants to avoid duplication, and add a new CLAUDE.md at the repo root that
documents the complete discriminant -> extension mapping and the serialization
contract so future changes remain consistent and discoverable.
| has_top_up: 1, | ||
| compression_only: if config.compression_only { 1 } else { 0 }, | ||
| write_top_up: config.lamports_per_write.unwrap_or(0), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find and read the create_ata.rs file
echo "=== File: sdk-libs/ctoken-sdk/src/ctoken/create_ata.rs ==="
find . -name "create_ata.rs" -type f | head -5
# Search for CompressibleExtensionInstructionData struct definition
echo -e "\n=== Searching for CompressibleExtensionInstructionData struct ==="
rg "struct CompressibleExtensionInstructionData" -A 10 --type rust
# Search for has_top_up field definition/usage
echo -e "\n=== Searching for has_top_up field ==="
rg "has_top_up" -B 2 -A 2 --type rust | head -80
# Look for CLAUDE.md or documentation about compressible extension
echo -e "\n=== Looking for documentation ==="
find . -name "*.md" -path "*compressible*" -o -name "CLAUDE.md" -path "*ctoken*" | head -10Repository: Lightprotocol/light-protocol
Length of output: 4889
🏁 Script executed:
#!/bin/bash
# Read the create_ata.rs file content around lines 95-105
wc -l sdk-libs/ctoken-sdk/src/ctoken/create_ata.rs
head -250 sdk-libs/ctoken-sdk/src/ctoken/create_ata.rs | tail -160Repository: Lightprotocol/light-protocol
Length of output: 6017
🏁 Script executed:
#!/bin/bash
# Read the create_ata.rs file
cat -n sdk-libs/ctoken-sdk/src/ctoken/create_ata.rs | sed -n '85,110p'
# Find the CompressibleExtensionInstructionData struct
echo -e "\n\n=== CompressibleExtensionInstructionData struct ==="
rg -A 15 "struct CompressibleExtensionInstructionData" --type rust
# Find documentation about the fields
echo -e "\n\n=== Documentation about has_top_up and write_top_up ==="
rg -B 3 -A 3 "has_top_up|write_top_up" --type rust | head -100Repository: Lightprotocol/light-protocol
Length of output: 10711
Make has_top_up conditional on write_top_up being provided.
Currently, has_top_up is hardcoded to 1 when a compressible config is provided, even when write_top_up is 0 (via unwrap_or(0) when config.lamports_per_write is None). This deviates from the pattern in create_associated_token_account.rs and create.rs, which only set has_top_up: 1 when lamports_per_write.is_some(). To stay consistent with those builders, change line 100 to:
has_top_up: if config.lamports_per_write.is_some() { 1 } else { 0 },
🤖 Prompt for AI Agents
In sdk-libs/ctoken-sdk/src/ctoken/create_ata.rs around lines 100 to 102,
has_top_up is currently hardcoded to 1 even when config.lamports_per_write is
None (because write_top_up uses unwrap_or(0)); change it so has_top_up is
conditional on lamports_per_write being Some by setting has_top_up to 1 when
config.lamports_per_write.is_some() and 0 otherwise, matching the pattern used
in create_associated_token_account.rs and create.rs.
| #[allow(clippy::too_many_arguments)] | ||
| pub fn new( | ||
| amount: u64, | ||
| decimals: u8, | ||
| source_account: AccountInfo<'info>, | ||
| destination_account: AccountInfo<'info>, | ||
| authority: AccountInfo<'info>, | ||
| payer: AccountInfo<'info>, | ||
| compressed_token_program_authority: AccountInfo<'info>, | ||
| system_program: AccountInfo<'info>, | ||
| ) -> Self { | ||
| Self { | ||
| source_account, | ||
| destination_account, | ||
| authority, | ||
| amount, | ||
| decimals, | ||
| payer, | ||
| compressed_token_program_authority, | ||
| spl_interface: None, | ||
| system_program, | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider builder pattern for constructor ergonomics.
With 8 parameters, the constructor is becoming unwieldy. The with_spl_interface method already demonstrates a builder-like approach. Consider extending this pattern for optional parameters like system_program to improve call-site readability:
TransferInterfaceCpi::new(amount, decimals, source, dest, authority, payer, ctp_authority)
.with_system_program(system_program)
.with_spl_interface(...)This is a minor ergonomic improvement—the current approach is functional.
| // System program (index 6) - needed for compressible account lamport top-ups | ||
| AccountMeta::new_readonly(Pubkey::default(), false), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find and display the transfer_spl_ctoken.rs file content around lines 170-171
fd -t f transfer_spl_ctoken.rs --exec head -n 200 {} \; | tail -n +150Repository: Lightprotocol/light-protocol
Length of output: 2141
🏁 Script executed:
#!/bin/bash
# Get more lines from the file to see how packed_accounts is used
fd -t f transfer_spl_ctoken.rs --exec head -n 230 {} \; | tail -n +160Repository: Lightprotocol/light-protocol
Length of output: 2426
🏁 Script executed:
#!/bin/bash
# Search for Transfer2 instruction processing and system program handling
rg -n "Transfer2" sdk-libs/ctoken-sdk/src --type rust -A 3 -B 1 | head -50Repository: Lightprotocol/light-protocol
Length of output: 4604
🏁 Script executed:
#!/bin/bash
# Search for Transfer2 processing in program-libs (on-chain code)
fd -t f -path "*/program-libs/*" | grep -i transfer | head -20Repository: Lightprotocol/light-protocol
Length of output: 240
🏁 Script executed:
#!/bin/bash
# Search for Transfer2 processing and system program usage
find program-libs -name "*.rs" -path "*transfer*" 2>/dev/null | head -20Repository: Lightprotocol/light-protocol
Length of output: 491
🏁 Script executed:
#!/bin/bash
# Check how Transfer2 processes accounts, especially for lamport transfers
rg -n "system_program|System.*Program|Pubkey::default" program-libs/ctoken-interface/src/instructions/transfer2/ --type rust -B 2 -A 2 | head -80Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
#!/bin/bash
# Search in the ctoken program itself for transfer2 processing
rg -n "transfer2|Transfer2" programs/compressed-token/program/src --type rust -l | head -5Repository: Lightprotocol/light-protocol
Length of output: 369
🏁 Script executed:
#!/bin/bash
# Check the transfer2 mod.rs file to see how accounts are processed
head -n 300 programs/compressed-token/program/src/transfer2/mod.rs | tail -n 150Repository: Lightprotocol/light-protocol
Length of output: 248
🏁 Script executed:
#!/bin/bash
# Check transfer2 processor to understand account handling
rg -n "packed_accounts|system_program|Pubkey::default" programs/compressed-token/program/src/transfer2 --type rust -B 3 -A 3 | head -100Repository: Lightprotocol/light-protocol
Length of output: 10049
🏁 Script executed:
#!/bin/bash
# Search for lamport processing in transfer2
rg -n "lamport|invoke|cpi" programs/compressed-token/program/src/transfer2/processor.rs --type rust -B 2 -A 2 | head -120Repository: Lightprotocol/light-protocol
Length of output: 4682
🏁 Script executed:
#!/bin/bash
# Check what directory transfer_spl_ctoken.rs is in to confirm it's sdk-libs
find . -name "transfer_spl_ctoken.rs" -type f 2>/dev/nullRepository: Lightprotocol/light-protocol
Length of output: 127
🏁 Script executed:
#!/bin/bash
# Verify that in_lamports and out_lamports are indeed unimplemented in all transfer2 code
rg -n "in_lamports|out_lamports" programs/compressed-token/program/src/transfer2 --type rust | head -30Repository: Lightprotocol/light-protocol
Length of output: 692
🏁 Script executed:
#!/bin/bash
# Check if there's a SYSTEM_PROGRAM_ID constant defined
rg -n "SYSTEM_PROGRAM_ID|system_program::ID" --type rust | head -10Repository: Lightprotocol/light-protocol
Length of output: 1223
Replace Pubkey::default() with the actual System Program ID if lamport transfers will be implemented, or remove this account if it's unused.
The comment indicates this should be the System Program for lamport top-ups, but the System Program address is 11111111111111111111111111111111, not the zero address. Additionally, on-chain Transfer2 processing currently rejects any lamport inputs or outputs as unimplemented. Either wire up the correct System Program ID (available via solana_program::system_program::ID) if this feature is planned, or omit this account and update the documentation.
🤖 Prompt for AI Agents
In sdk-libs/ctoken-sdk/src/ctoken/transfer_spl_ctoken.rs around lines 170-171,
the AccountMeta is using Pubkey::default() for the System Program placeholder
which is incorrect for lamport transfers; either replace Pubkey::default() with
the real System Program ID (solana_program::system_program::ID) if you intend to
support lamport top-ups, or remove this AccountMeta entry entirely and adjust
the surrounding comment/docs to reflect that lamport transfers are not
supported. Ensure the chosen action matches on-chain Transfer2 behavior (wire up
the proper system program ID when implementing lamport transfers, otherwise
remove the unused account and update the comment).
| // Get delegate if present | ||
| let delegate_index = if let Some(delegate_bytes) = ctoken_account.delegate.as_ref() { | ||
| let delegate_pubkey = Pubkey::from(delegate_bytes.to_bytes()); | ||
| packed_accounts.insert_or_get(delegate_pubkey) | ||
| } else { | ||
| 0 // 0 means no delegate | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sentinel value 0 for "no delegate" can collide with actual index 0.
Using 0 to represent "no delegate" is problematic because if a delegate account is inserted as the first account in packed_accounts, it will receive index 0, creating ambiguity. The instruction handler won't be able to distinguish between "no delegate" and "delegate at index 0".
Consider one of these approaches:
- Use
Option<usize>fordelegate_indexinCompressAndCloseIndices(preferred) - Use a sentinel value that cannot collide, such as
usize::MAX - Reserve index 0 by inserting a dummy account first (not recommended)
🔎 Suggested approach using a non-colliding sentinel:
// Get delegate if present
- let delegate_index = if let Some(delegate_bytes) = ctoken_account.delegate.as_ref() {
+ let delegate_index = if let Some(delegate_bytes) = ctoken_account.delegate.as_ref() {
let delegate_pubkey = Pubkey::from(delegate_bytes.to_bytes());
packed_accounts.insert_or_get(delegate_pubkey)
} else {
- 0 // 0 means no delegate
+ usize::MAX // usize::MAX means no delegate
};Note: This assumes CompressAndCloseIndices and the instruction handler recognize usize::MAX as the sentinel. Verify this convention is documented and consistently applied across the codebase.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In sdk-libs/program-test/src/forester/compress_and_close_forester.rs around
lines 148 to 154, using 0 as a sentinel for "no delegate" collides with a real
delegate that could be at index 0; change the representation so it cannot
collide—preferably make delegate_index an Option<usize> in
CompressAndCloseIndices (update struct/type and all call sites to use
Some(index)/None), or if changing the type is infeasible use a guaranteed
impossible sentinel like usize::MAX and ensure CompressAndCloseIndices and the
instruction handler treat usize::MAX as "no delegate" consistently across the
codebase.
| mint_index, | ||
| owner_index, | ||
| rent_sponsor_index, | ||
| delegate_index, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for CompressAndCloseIndices struct definition and delegate_index usage
echo "=== Finding CompressAndCloseIndices struct definition ==="
fd -e rs | xargs rg "struct CompressAndCloseIndices" -B2 -A10
echo -e "\n=== Finding delegate_index field usage ==="
fd -e rs | xargs rg "delegate_index" -B2 -A2
echo -e "\n=== Checking file path context ==="
fd "compress_and_close_forester.rs"Repository: Lightprotocol/light-protocol
Length of output: 21957
🏁 Script executed:
#!/bin/bash
# Get more context around line 161 in the forester file and look for the else branch
echo "=== Full function context around delegate_index in compress_and_close_forester.rs ==="
sed -n '140,180p' sdk-libs/program-test/src/forester/compress_and_close_forester.rs
echo -e "\n=== Check what happens when delegate is None ==="
sed -n '150,175p' sdk-libs/program-test/src/forester/compress_and_close_forester.rs
echo -e "\n=== Look for how delegate_index is used after assignment ==="
rg -A5 "let delegate_index =" sdk-libs/program-test/src/forester/compress_and_close_forester.rsRepository: Lightprotocol/light-protocol
Length of output: 2952
🏁 Script executed:
#!/bin/bash
# Check how the instruction handler uses delegate_index in the registry program
echo "=== Handler code using delegate_index from CompressAndCloseIndices ==="
sed -n '80,140p' programs/registry/src/compressible/compressed_token/compress_and_close.rs
echo -e "\n=== Check if there's any validation of sentinel value ==="
rg -B3 -A3 "delegate.*idx.delegate_index" programs/registry/src/compressible/compressed_token/compress_and_close.rsRepository: Lightprotocol/light-protocol
Length of output: 3161
Address sentinel value collision in delegate_index.
The current implementation uses 0 as a sentinel to indicate "no delegate" in CompressAndCloseIndices, but packed_accounts.insert_or_get() can also return 0 if the delegate account happens to be the first inserted. This creates ambiguity: a delegate_index of 0 could mean either "no delegate" or "delegate is the first packed account."
While the handler mitigates this by reading has_delegate from the actual CToken account data (not the instruction), this indirect safety mechanism masks a design flaw. Align with the pattern used elsewhere in the codebase (in transfer.rs, freeze.rs, etc.) by changing delegate_index to Option<u8> to make the intent explicit and type-safe.
🤖 Prompt for AI Agents
In sdk-libs/program-test/src/forester/compress_and_close_forester.rs around line
161, change the CompressAndCloseIndices.delegate_index from a raw u8 sentinel to
Option<u8> so presence/absence of a delegate is explicit and type-safe; update
the instruction struct, its serialization/deserialization (encode/decode) to
represent Option<u8> (e.g., write a presence flag then the value), update all
call sites that build the instruction to convert
packed_accounts.insert_or_get(...) into Some(index as u8) when a delegate exists
or None when not, and update any unpacking/handler logic to use the Option
directly (and update tests/mocks accordingly).
87b7a1e to
7113d51
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (10)
sdk-libs/ctoken-sdk/src/compressed_token/v2/account2.rs (1)
252-284: Add clippy suppression for consistency withcompress_spl.This method now has 7 arguments (clippy's default threshold), and
compress_splalready has#[allow(clippy::too_many_arguments)]. For consistency and to prevent future lint noise if another parameter is added, consider adding the same annotation here.🔎 Suggested fix
#[profile] +#[allow(clippy::too_many_arguments)] pub fn decompress_spl( &mut self, amount: u64, source_index: u8, pool_account_index: u8, pool_index: u8, bump: u8, decimals: u8, ) -> Result<(), CTokenSdkError> {sdk-libs/ctoken-sdk/src/ctoken/transfer_ctoken_spl.rs (3)
17-52: Update the documentation example to include the newdecimalsfield.The example at lines 28-38 will fail to compile because it doesn't initialize the new required
decimalsfield. Users copying this example will encounter a compilation error.🔎 Apply this diff to fix the documentation example:
let instruction = TransferCTokenToSpl { source_ctoken_account, destination_spl_token_account, amount: 100, authority, mint, payer, spl_interface_pda, spl_interface_pda_bump: 255, + decimals: 9, // Token decimals (e.g., 9 for typical SPL tokens) spl_token_program, }.instruction()?;
54-93: Update the documentation example to include the newdecimalsfield.The example at lines 66-77 will fail to compile because it doesn't initialize the new required
decimalsfield.🔎 Apply this diff to fix the documentation example:
TransferCTokenToSplCpi { source_ctoken_account, destination_spl_token_account, amount: 100, authority, mint, payer, spl_interface_pda, spl_interface_pda_bump: 255, + decimals: 9, // Token decimals (e.g., 9 for typical SPL tokens) spl_token_program, compressed_token_program_authority, }
133-148: Decimals parameter relies on caller accuracy—consider documenting or validating.The
decimalsfield is accepted as-is from the caller without verification against the mint account. Since the mint account is available in the accounts list, the SDK could optionally validate that the provideddecimalsmatches the mint's actual decimals to catch caller errors early. Alternatively, document explicitly that the caller is responsible for ensuring decimals correctness, as incorrect values would cause decompression to behave incorrectly.sdk-libs/ctoken-sdk/src/ctoken/transfer_spl_ctoken.rs (2)
40-52: Doc example is now out of sync with struct fields.The example at lines 27-37 doesn't include the new
decimalsfield, which will cause compilation errors for anyone copying the example.🔎 Update the doc example to include decimals:
/// let instruction = TransferSplToCtoken { /// amount: 100, /// spl_interface_pda_bump: 255, +/// decimals: 9, /// source_spl_token_account, /// destination_ctoken_account,
81-96: Doc example for CPI struct is incomplete.The example at lines 66-78 is missing the new
decimalsandsystem_programfields required byTransferSplToCtokenCpi.🔎 Update the CPI doc example:
/// TransferSplToCtokenCpi { /// amount: 100, /// spl_interface_pda_bump: 255, +/// decimals: 9, /// source_spl_token_account, /// ... /// compressed_token_program_authority, +/// system_program, /// }sdk-libs/token-client/src/actions/transfer2/decompress.rs (1)
14-34: Documentation should include the newdecimalsparameter.The function signature now includes
decimals: u8, but the doc comment doesn't describe this parameter. Consider adding:/// * `decimals` - The decimal places for the token mintThis helps callers understand what value to provide.
programs/compressed-token/program/src/transfer2/compression/spl.rs (1)
161-170: Consider preserving original CPI error for debugging.Mapping all CPI errors to
ProgramError::InvalidArgumentloses diagnostic information. While this is consistent with the existing pattern, preserving the original error would aid debugging:🔎 Potential improvement
match signers { Some(signers) => { pinocchio::cpi::slice_invoke_signed(&instruction, account_infos, signers) - .map_err(|_| ProgramError::InvalidArgument)?; + .map_err(ProgramError::from)?; } None => { pinocchio::cpi::slice_invoke(&instruction, account_infos) - .map_err(|_| ProgramError::InvalidArgument)?; + .map_err(ProgramError::from)?; } }program-libs/ctoken-interface/src/state/extensions/extension_type.rs (1)
54-68: TryFrom implementation correctly handles new variants.The
TryFrom<u8>implementation properly maps discriminants 27-31 to the new extension types. The error handling for unsupported values returnsCTokenError::UnsupportedExtension.One observation: placeholders 0-26 (except 19) are not handled in
TryFromand will returnUnsupportedExtension. This is intentional since they're reserved/unused, but consider adding a comment explaining this design choice for future maintainers.sdk-libs/token-client/src/instructions/transfer2.rs (1)
197-201: Consider more graceful error handling for RPC calls.The chain
.await.unwrap().valuewill panic on RPC failures. While this is SDK code (not on-chain), users of this function might prefer to receive a proper error rather than a panic.🔎 Suggested improvement:
let rpc_proof_result = rpc .get_validity_proof(hashes, vec![], None) .await - .unwrap() + .map_err(|e| CTokenSdkError::RpcError(e.to_string()))? .value;Note: This pattern appears in several other places in this file (lines 275-280, 354-359, 525-529). A consistent error handling approach would improve the SDK's usability.
♻️ Duplicate comments (35)
program-libs/zero-copy-derive/src/shared/utils.rs (1)
24-28: UnitStruct variant addition is well-structured.The
InputTypeenum extension cleanly separates unit structs from named-field structs. The comment on line 26 clearly documents what this variant represents (struct Foo;syntax).The documentation mismatch with coding guidelines ("Structs must have at least one field for zero-copy serialization; empty structs are not supported") was already flagged in a previous review. Please ensure the README and guidelines are updated to reflect that unit structs are now intentionally supported.
program-libs/zero-copy-derive/src/zero_copy.rs (1)
303-333: Unit struct immutable zero-copy implementation is correct.The static instance pattern at line 322 (
static UNIT: #name = #name;) is the right approach for immutable access—the static is created once at compile time, and returning&'a UNITsafely provides a reference with arbitrary lifetime since the static lives for the entire program duration.This contrasts appropriately with the mutable version in
zero_copy_mut.rswhich must useBox::leaksince you can't safely hand out multiple&'static mutreferences to the same static.Tests and documentation updates for unit struct support were flagged in a previous review. Please add integration tests under
program-libs/zero-copy-derive/tests/and update the README.sdk-libs/token-client/src/actions/create_compressible_token_account.rs (1)
64-64: Document thecompression_onlyfield and its usage here.This hardcodes
compression_only: truewithout any explanation of whytrueis the appropriate value for compressible token account creation. The underlying field definition also lacks documentation (see past review comments onsdk-libs/ctoken-sdk/src/ctoken/compressible.rs).sdk-libs/ctoken-sdk/src/ctoken/compressible.rs (2)
37-37: Documentation forcompression_onlyis still missing.This new public field was already flagged in previous review comments for lacking documentation. The struct-level docstring and field-level comments still don't explain what
compression_onlycontrols or when to usetruevsfalse.Also applies to: 49-49
96-96: Builder method and documentation still needed.As noted in previous review comments, the
compression_onlyfield lacks documentation and there's no builder method likewith_compression_only()to set it fluently.Also applies to: 114-114
sdk-libs/ctoken-sdk/src/ctoken/create_ata.rs (1)
100-102: Makehas_top_upconditional onwrite_top_upbeing provided.Currently,
has_top_upis hardcoded to 1 when a compressible config is provided, even whenwrite_top_upis 0 (viaunwrap_or(0)on line 102). This deviates from the pattern increate_associated_token_account.rsandcreate.rs, which only sethas_top_up: 1whenlamports_per_write.is_some().🔎 Apply this diff to make has_top_up conditional:
has_top_up: if config.lamports_per_write.is_some() { 1 } else { 0 }, compression_only: if config.compression_only { 1 } else { 0 }, write_top_up: config.lamports_per_write.unwrap_or(0),programs/compressed-token/program/src/transfer2/cpi.rs (1)
54-59: Fix TLV boolean flag and use safe type conversion.Two issues remain from the previous review:
Line 56: The TLV boolean is hardcoded to
trueeven whentlv_configis empty. This causes incorrect byte length calculations. Use!tlv_config.is_empty()to reflect whether TLV data actually exists, matching the pattern inshared/token_output.rs.Line 59: The unchecked
as u32cast should be replaced with.try_into()for explicit overflow handling and defensive programming.🔎 Apply this diff to fix both issues:
let token_config = TokenDataConfig { delegate: (has_delegate, ()), - tlv: (true, tlv_config), + tlv: (!tlv_config.is_empty(), tlv_config), }; TokenData::byte_len(&token_config).map_err(|_| ProgramError::InvalidAccountData)? - as u32 + .try_into() + .map_err(|_| ProgramError::InvalidAccountData)?program-libs/ctoken-interface/src/state/extensions/compressed_only.rs (1)
1-31: COMPRESSED_ONLY_EXTENSION_SIZE constant needs updating to 16 bytes.The
CompressedOnlyExtensionstruct correctly defines twou64fields (delegated_amountandwithheld_transfer_fee) totaling 16 bytes, and theLENconstant properly usessize_of::<Self>(). However, theCOMPRESSED_ONLY_EXTENSION_SIZEconstant inconstants.rsis outdated at 8 bytes.Update that constant to 16, or better yet, set it to
CompressedOnlyExtension::LENto keep it automatically in sync with the struct definition. Also update the constant's documentation to reflect the two-field (16-byte) layout.#!/bin/bash # Verify the current value of COMPRESSED_ONLY_EXTENSION_SIZE rg -n "COMPRESSED_ONLY_EXTENSION_SIZE" --type rust -B 2 -A 2programs/compressed-token/program/src/shared/owner_validation.rs (1)
40-60: Verbose failure logging adds compute overhead.Multiple
msg!calls on authorization failure increase compute cost. This concern was raised in a previous review. Consider consolidating into a single message or gating behind a feature flag if this is a hot path.sdk-libs/ctoken-sdk/src/ctoken/transfer_spl_ctoken.rs (1)
170-171:Pubkey::default()is incorrect for the System Program.
Pubkey::default()produces the null address[0; 32], not the System Program ID (11111111111111111111111111111111). This will fail on-chain validation when the program checks the system program account.🔎 Use the actual System Program ID:
- // System program (index 6) - needed for compressible account lamport top-ups - AccountMeta::new_readonly(Pubkey::default(), false), + // System program (index 6) - needed for compressible account lamport top-ups + AccountMeta::new_readonly(solana_system_interface::program::ID, false),sdk-libs/ctoken-sdk/src/ctoken/create_associated_token_account.rs (4)
16-20: Discriminators are magic numbers without documentation.These discriminator values (100, 102, 106, 107) should reference the canonical on-chain instruction enum or include comments explaining their meaning.
206-210: System program pubkey is null, not the actual System Program ID.
Pubkey::new_from_array([0; 32])creates a null pubkey. The System Program ID is11111111111111111111111111111111(base58) or the constant fromsolana_system_interface::program::ID.🔎 Fix the system program pubkey:
- solana_instruction::AccountMeta::new_readonly(Pubkey::new_from_array([0; 32]), false), // system_program + solana_instruction::AccountMeta::new_readonly(solana_system_interface::program::ID, false), // system_program
383-389: Same null system program issue in ATA2 instruction builder.🔎 Apply the same fix:
- solana_instruction::AccountMeta::new_readonly(Pubkey::new_from_array([0; 32]), false), + solana_instruction::AccountMeta::new_readonly(solana_system_interface::program::ID, false),
408-451: CPI wrapper passesauthorityaccount but instruction doesn't expect it.The
create_associated_ctoken_accountfunction passesauthorityin the CPI accounts (line 448), butcreate_compressible_associated_token_account_with_bumpdoesn't include authority in its accounts list (lines 206-222). This account mismatch will cause the CPI to fail.sdk-libs/client/src/indexer/types.rs (1)
1-1: Type evolution is correct, but error handling and duplication concerns remain.The change from
Option<Vec<u8>>to deserializing intoVec<ExtensionStruct>properly reflects the typed extension model introduced in this PR. The TLV data is now strongly typed, which is a good design evolution.However, the past review correctly identified two improvement opportunities:
Generic error mapping loses debugging context: Both base64 decode and Borsh deserialization errors map to
InvalidResponseData, making it difficult to diagnose whether the issue is data corruption (base64) or schema mismatch (Borsh).Logic duplication: The identical TLV parsing appears in both
TokenAccount(lines 890-896) andTokenAccountV2(931-937) implementations.The functional change is sound—the TLV data is now properly typed. The error handling and duplication concerns noted in the past review remain valid suggestions for future improvement, but don't block this PR since the core functionality is correct.
Also applies to: 890-896, 931-937
program-libs/ctoken-interface/src/instructions/extensions/compressible.rs (1)
21-25: Consider adding validation or clarifying the design choice.The addition of
has_top_up: u8creates potential for inconsistency with the existingwrite_top_up: u32field. Ifhas_top_up == 0butwrite_top_up != 0, which takes precedence?Options to consider:
- Remove the flag: Use
write_top_up != 0as the presence indicator- Add validation: Ensure
write_top_up == 0whenhas_top_up == 0at deserialization/construction sites- Document the design: If the flag is required (e.g., to distinguish "no top-up specified" from "explicit zero top-up"), add a comment explaining this
Without validation or documentation, this creates a potential correctness issue where the two fields could contradict each other.
sdk-libs/program-test/src/forester/compress_and_close_forester.rs (1)
148-154: Address sentinel value collision for delegate_index.Using
0as a sentinel for "no delegate" creates ambiguity becausepacked_accounts.insert_or_get()can legitimately return0if the delegate account is inserted first. This makes it impossible to distinguish between "no delegate" and "delegate at index 0".Consider using
Option<usize>fordelegate_indexinCompressAndCloseIndices(update the struct definition, serialization, and all call sites), or use a non-colliding sentinel likeusize::MAXif changing the type is not feasible.forester/src/compressible/compressor.rs (1)
155-163: Address sentinel value collision for delegate_index.Using
0as the default when no delegate is present creates the same ambiguity as in the compress_and_close_forester: if the delegate is the first account inserted intopacked_accounts, it receives index0, making it indistinguishable from "no delegate."Change
delegate_indextoOption<usize>inCompressAndCloseIndicesor useusize::MAXas a non-colliding sentinel.programs/compressed-token/program/tests/token_input.rs (1)
115-132: Test correctly adapts to unified API signature.The
mint_cacheinitialization withMintExtensionChecks::default()at index 0 is appropriate for this test since the token data usesmint: 0(line 67). The updatedset_input_compressed_accountcall properly passes all required parameters.As noted in a previous review,
tlv_data: Nonemeans TLV extension paths remain untested here. That's acceptable for this test's scope (validating basic input account creation), but dedicated TLV tests should exist elsewhere.sdk-libs/ctoken-sdk/src/ctoken/transfer_interface.rs (1)
41-63: Constructor expansion is reasonable given always-required parameters.Since
decimalsandsystem_programare required for the CPI paths (unlike optional SPL interface), including them in the constructor makes sense. The#[allow(clippy::too_many_arguments)]is appropriate.The builder pattern suggestion from previous reviews remains valid for future ergonomic improvements, but isn't blocking.
programs/compressed-token/program/src/transfer2/compression/ctoken/compress_or_decompress_ctokens.rs (1)
217-220: Frozen state restoration uses magic number.Line 219 uses
2forAccountState::Frozen. This relates to the earlier suggestion about extracting constants for account states.programs/compressed-token/program/src/transfer2/compression/ctoken/compress_and_close.rs (1)
252-264: Magic number forAccountState::Frozen- already flagged in past review.This uses
*ctoken.state == 2for frozen state check. A past review comment already flagged this for extraction to a constant.programs/compressed-token/program/src/ctoken_transfer.rs (1)
68-93: Consider a more specific error for extension mismatch.
InvalidInstructionDatais generic. A dedicated error likeExtensionMismatchwould help callers diagnose why the transfer failed.🔎 Suggested improvement
fn check_t22_extensions(&self, other: &Self) -> Result<(), ProgramError> { if !self.t22_extensions_eq(other) { - Err(ProgramError::InvalidInstructionData) + Err(CTokenError::ExtensionMismatch.into()) } else { Ok(()) } }This would require adding
ExtensionMismatchtoCTokenError.forester/src/compressible/state.rs (1)
33-34: UsingRent::default()may not reflect actual on-chain rent parameters.
Rent::default()returns hardcoded mainnet values. In test environments or if Solana changes rent parameters, this could produce incorrect compressible slot calculations. Consider accepting theRentsysvar as a parameter or fetching it via RPC in the calling context for accuracy.program-libs/ctoken-interface/src/instructions/transfer2/instruction_data.rs (1)
34-37: TLV type change is a breaking serialization change—verify migration strategy.The fields
in_tlvandout_tlvchanged fromOption<Vec<Vec<u8>>>toOption<Vec<Vec<ExtensionInstructionData>>>. This alters the on-chain serialization format.A previous review flagged this concern. Ensure that:
- SDK versions are coordinated with program deployment
- There's a clear migration path for existing transactions
- Error messages guide users if format mismatches occur
programs/compressed-token/program/src/create_token_account.rs (1)
177-190: Restricted extension enforcement is correct.The validation correctly identifies restricted extensions and enforces
compression_onlymode when present. This prevents creating decompressible accounts for mints with complex extension behaviors.Consider extracting
has_restricted_extensions()as a method onMintExtensionFlagsto reduce duplication with similar checks elsewhere.programs/compressed-token/program/src/shared/initialize_ctoken_account.rs (1)
132-133: Extract extension discriminators to named constants.The discriminators (27, 28, 29, 30, 32) are hardcoded magic numbers. Consider defining them as constants for maintainability and to ensure consistency with
ExtensionStructenum indices:const PAUSABLE_ACCOUNT_DISCRIMINATOR: u8 = 27; const PERMANENT_DELEGATE_DISCRIMINATOR: u8 = 28; const TRANSFER_FEE_DISCRIMINATOR: u8 = 29; const TRANSFER_HOOK_DISCRIMINATOR: u8 = 30; const COMPRESSIBLE_DISCRIMINATOR: u8 = 32;Also applies to: 163-166, 174-177, 185-188, 196-199
programs/compressed-token/program/tests/token_output.rs (1)
124-124: Address the TODO for frozen flag randomization.The frozen flag is hardcoded to
false. For comprehensive test coverage, consider randomizing this similar to other test parameters.🔎 Suggested fix:
- is_frozen: false, // TODO: make random + is_frozen: rng.gen_bool(0.2), // 20% chance of frozenprograms/compressed-token/program/src/create_associated_token_account.rs (1)
118-126: Account sizing depends on extension detection - verify fallback is safe.When
mint_accountisNone,MintExtensionFlags::default()is used (all extensions false). This means the token account size calculation at line 126 will use the minimum size.If the mint actually has extensions that require larger accounts (like
TransferFeewhich needs 9 extra bytes per the relevant snippet), accounts created without the mint reference will be too small and potentially fail initialization or later operations.programs/compressed-token/program/src/shared/token_output.rs (1)
103-106: Empty vec allocation when no TLV config.When
tlv_configisNoneor empty, an emptyvec![]is still created. This is fine for correctness but could be avoided by using a default or sentinel value if performance matters here.programs/compressed-token/program/src/extensions/check_mint_extensions.rs (2)
91-100:unwrap_or_default()silently swallows extension type enumeration errors.If
get_extension_types()fails, this returns an empty vector andhas_restricted_extensionsbecomes false—potentially allowing restricted mints to bypass validation.
182-183: Sameunwrap_or_default()pattern inhas_mint_extensions.This has the same issue as in
check_mint_extensions- ifget_extension_types()fails, the function silently returns default flags instead of surfacing the error. This could allow mints with parsing errors to be treated as having no extensions.🔎 Suggested fix:
- let extension_types = mint_state.get_extension_types().unwrap_or_default(); + let extension_types = mint_state.get_extension_types().map_err(|e| { + msg!("Failed to get extension types: {:?}", e); + ProgramError::InvalidAccountData + })?;program-libs/ctoken-interface/src/state/extensions/extension_struct.rs (1)
158-225: Extract discriminant values to named constants.The discriminant values (19, 27, 28, 29, 30, 31, 32) appear in multiple places:
zero_copy_at_mutmatch arms (lines 161, 169, 178, 187, 196, 205, 214)new_zero_copywrite operations (lines 284, 301, 318, 335, 352, 369, 386)Extracting these to named constants improves maintainability and makes the serialization contract explicit:
🔎 Suggested constant definitions
// Extension discriminant values for serialization // Reserved ranges: 0-18 (placeholders), 20-26 (placeholders) // Token-2022 compatible range: 27-31 pub const TOKEN_METADATA_DISCRIMINANT: u8 = 19; pub const PAUSABLE_ACCOUNT_DISCRIMINANT: u8 = 27; pub const PERMANENT_DELEGATE_ACCOUNT_DISCRIMINANT: u8 = 28; pub const TRANSFER_FEE_ACCOUNT_DISCRIMINANT: u8 = 29; pub const TRANSFER_HOOK_ACCOUNT_DISCRIMINANT: u8 = 30; pub const COMPRESSED_ONLY_DISCRIMINANT: u8 = 31; pub const COMPRESSIBLE_DISCRIMINANT: u8 = 32;Then use these constants in all match arms and write operations.
programs/compressed-token/anchor/src/instructions/create_token_pool.rs (2)
105-135: Add runtime validation for documented extension constraints.The comments document that
ConfidentialTransferMint,ConfidentialMintBurn, andConfidentialTransferFeeConfighave specific constraints, butassert_mint_extensions()only validatesTransferFeeConfigandTransferHook. This creates a gap between documented behavior and actual enforcement.At minimum, consider validating:
ConfidentialTransferFeeConfig: fees must be zero (similar toTransferFeeConfig)DefaultAccountState: document which states are acceptable
137-173: Complete the extension validation logic.The validation for
TransferFeeConfigandTransferHookis solid. However, several documented constraints are not enforced:
- ConfidentialTransferFeeConfig: Should validate fees are zero
- DefaultAccountState: No validation present despite being in the allowed list
- ConfidentialTransferMint/ConfidentialMintBurn: Comments say "initialized but not enabled" but no code enforces this
If these extensions truly don't need runtime checks (e.g., the "initialized but not enabled" state is acceptable), consider clarifying the comments to indicate validation is intentionally deferred or unnecessary.
program-libs/ctoken-interface/src/state/extensions/transfer_fee.rs
Outdated
Show resolved
Hide resolved
| use light_ctoken_interface::{ | ||
| state::calculate_ctoken_account_size, BASE_TOKEN_ACCOUNT_SIZE, | ||
| COMPRESSIBLE_PAUSABLE_TOKEN_ACCOUNT_SIZE, COMPRESSIBLE_TOKEN_ACCOUNT_SIZE, | ||
| }; | ||
|
|
||
| #[test] | ||
| fn test_ctoken_account_size_calculation() { | ||
| // Base only (no extensions) | ||
| assert_eq!( | ||
| calculate_ctoken_account_size(false, false, false, false, false), | ||
| BASE_TOKEN_ACCOUNT_SIZE | ||
| ); | ||
|
|
||
| // With compressible only | ||
| assert_eq!( | ||
| calculate_ctoken_account_size(true, false, false, false, false), | ||
| COMPRESSIBLE_TOKEN_ACCOUNT_SIZE | ||
| ); | ||
|
|
||
| // With compressible + pausable | ||
| assert_eq!( | ||
| calculate_ctoken_account_size(true, true, false, false, false), | ||
| COMPRESSIBLE_PAUSABLE_TOKEN_ACCOUNT_SIZE | ||
| ); | ||
|
|
||
| // With compressible + pausable + permanent_delegate (262 + 1 = 263) | ||
| assert_eq!( | ||
| calculate_ctoken_account_size(true, true, true, false, false), | ||
| 263 | ||
| ); | ||
|
|
||
| // With pausable only (165 + 1 = 166) | ||
| assert_eq!( | ||
| calculate_ctoken_account_size(false, true, false, false, false), | ||
| 166 | ||
| ); | ||
|
|
||
| // With permanent_delegate only (165 + 1 = 166) | ||
| assert_eq!( | ||
| calculate_ctoken_account_size(false, false, true, false, false), | ||
| 166 | ||
| ); | ||
|
|
||
| // With pausable + permanent_delegate (165 + 1 + 1 = 167) | ||
| assert_eq!( | ||
| calculate_ctoken_account_size(false, true, true, false, false), | ||
| 167 | ||
| ); | ||
|
|
||
| // With compressible + permanent_delegate (261 + 1 = 262) | ||
| assert_eq!( | ||
| calculate_ctoken_account_size(true, false, true, false, false), | ||
| 262 | ||
| ); | ||
|
|
||
| // With transfer_fee only (165 + 9 = 174) | ||
| assert_eq!( | ||
| calculate_ctoken_account_size(false, false, false, true, false), | ||
| 174 | ||
| ); | ||
|
|
||
| // With compressible + transfer_fee (261 + 9 = 270) | ||
| assert_eq!( | ||
| calculate_ctoken_account_size(true, false, false, true, false), | ||
| 270 | ||
| ); | ||
|
|
||
| // With 4 extensions (261 + 1 + 1 + 9 = 272) | ||
| assert_eq!( | ||
| calculate_ctoken_account_size(true, true, true, true, false), | ||
| 272 | ||
| ); | ||
|
|
||
| // With all 5 extensions (261 + 1 + 1 + 9 + 2 = 274) | ||
| assert_eq!( | ||
| calculate_ctoken_account_size(true, true, true, true, true), | ||
| 274 | ||
| ); | ||
|
|
||
| // With transfer_hook only (165 + 2 = 167) | ||
| assert_eq!( | ||
| calculate_ctoken_account_size(false, false, false, false, true), | ||
| 167 | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Comprehensive test coverage with room for improvement.
The test thoroughly exercises various extension combinations, which is excellent. However, several assertions use hard-coded numeric values (e.g., 263, 166, 167) while others reference named constants (BASE_TOKEN_ACCOUNT_SIZE, COMPRESSIBLE_TOKEN_ACCOUNT_SIZE).
💡 Consider defining constants for all expected sizes:
+const COMPRESSIBLE_PERMANENT_DELEGATE_SIZE: u64 = 262;
+const PAUSABLE_ONLY_SIZE: u64 = 166;
+const PERMANENT_DELEGATE_ONLY_SIZE: u64 = 166;
+// ... etc.
// With compressible + pausable + permanent_delegate
assert_eq!(
calculate_ctoken_account_size(true, true, true, false, false),
- 263
+ COMPRESSIBLE_PAUSABLE_PERMANENT_DELEGATE_SIZE
);This approach:
- Makes the test intent clearer (e.g., "COMPRESSIBLE_PERMANENT_DELEGATE_SIZE" vs "262")
- Centralizes size definitions for easier maintenance
- Reduces risk of typos in hard-coded values
That said, the current implementation works correctly and the hard-coded values do document the exact expected sizes clearly.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
program-libs/ctoken-interface/tests/ctoken/size.rs lines 1-85: Several
assertions use hard-coded numeric sizes (e.g., 263, 166, 167, 262, 174, 270,
272, 274) instead of named constants; define descriptive constants (or import
existing ones) for each expected size variant (e.g.,
COMPRESSIBLE_PERMANENT_DELEGATE_SIZE, PAUSABLE_SIZE,
PAUSABLE_PERMANENT_DELEGATE_SIZE, COMPRESSIBLE_PERMANENT_DELEGATE_SIZE,
TRANSFER_FEE_SIZE, COMPRESSIBLE_TRANSFER_FEE_SIZE, FOUR_EXTENSIONS_SIZE,
ALL_EXTENSIONS_SIZE, TRANSFER_HOOK_SIZE) by composing the known base constants
(BASE_TOKEN_ACCOUNT_SIZE, COMPRESSIBLE_TOKEN_ACCOUNT_SIZE,
COMPRESSIBLE_PAUSABLE_TOKEN_ACCOUNT_SIZE, and the extension deltas) and replace
the hard-coded literals in the assertions with those named constants so tests
remain clear and maintainable.
| /// Initializes a token account via CPI to the token program. | ||
| pub fn initialize_token_account<'info>( | ||
| token_account: &AccountInfo<'info>, | ||
| mint: &AccountInfo<'info>, | ||
| authority: &AccountInfo<'info>, | ||
| token_program: &AccountInfo<'info>, | ||
| ) -> Result<()> { | ||
| let ix = spl_token_2022::instruction::initialize_account3( | ||
| token_program.key, | ||
| token_account.key, | ||
| mint.key, | ||
| authority.key, | ||
| )?; | ||
| anchor_lang::solana_program::program::invoke( | ||
| &ix, | ||
| &[ | ||
| token_account.clone(), | ||
| mint.clone(), | ||
| authority.clone(), | ||
| token_program.clone(), | ||
| ], | ||
| )?; | ||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider validating token_program is a known SPL program.
The CPI to initialize_account3 looks correct. However, token_program.key is used directly without validation that it's either SPL Token or Token-2022. While the Anchor Interface<'info, TokenInterface> constraint should enforce this elsewhere, this helper function could be called with an arbitrary account.
🔎 Optional: Add program ID validation
pub fn initialize_token_account<'info>(
token_account: &AccountInfo<'info>,
mint: &AccountInfo<'info>,
authority: &AccountInfo<'info>,
token_program: &AccountInfo<'info>,
) -> Result<()> {
+ // Validate token program is SPL Token or Token-2022
+ if *token_program.key != spl_token::id() && *token_program.key != spl_token_2022::id() {
+ return Err(crate::ErrorCode::InvalidTokenProgram.into());
+ }
let ix = spl_token_2022::instruction::initialize_account3(Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In programs/compressed-token/anchor/src/instructions/create_token_pool.rs around
lines 62-85, the CPI uses token_program.key directly without verifying it is the
official SPL Token or Token-2022 program; add a guard before building the
instruction that checks token_program.key equals spl_token::id() or
spl_token_2022::id() and return an appropriate error if it does not match.
Implement the check using the canonical id() helpers from those crates and
return a ProgramError::IncorrectProgramId (or convert into the function Result
type) when the key is not one of the expected program IDs, then proceed to
construct and invoke the CPI only after validation succeeds.
| let is_compress_and_close_with_rent_sponsor = mode == ZCompressionMode::CompressAndClose | ||
| && compress_and_close_inputs | ||
| .as_ref() | ||
| .map(|inputs| inputs.rent_sponsor_is_signer_flag) | ||
| .unwrap_or(false); | ||
| if *ctoken.state == 2 && !is_compress_and_close_with_rent_sponsor { | ||
| msg!("Cannot modify frozen account"); | ||
| return Err(ErrorCode::AccountFrozen.into()); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider extracting magic number 2 for AccountState::Frozen into a constant.
The frozen state check *ctoken.state == 2 appears here and later at line 219. Defining a constant like const ACCOUNT_STATE_FROZEN: u8 = 2; would improve readability and reduce the risk of inconsistencies.
🔎 Suggested improvement:
// At module level or in a constants file
const ACCOUNT_STATE_FROZEN: u8 = 2;
const ACCOUNT_STATE_INITIALIZED: u8 = 1;Then use these constants instead of magic numbers throughout.
🤖 Prompt for AI Agents
In
programs/compressed-token/program/src/transfer2/compression/ctoken/compress_or_decompress_ctokens.rs
around lines 67–75, the code uses the magic number 2 to check for the frozen
account state; define a module-level constant (e.g., const ACCOUNT_STATE_FROZEN:
u8 = 2;) and replace the literal 2 here and at the other occurrence (around line
219) with that constant; optionally add other state constants (e.g.,
INITIALIZED) in the same place to keep state values consistent and improve
readability.
programs/compressed-token/program/src/transfer2/compression/ctoken/inputs.rs
Show resolved
Hide resolved
| let mut withheld_transfer_fee: u64 = 0; | ||
| let delegated_amount: u64 = (*ctoken.delegated_amount).into(); | ||
| // AccountState::Frozen = 2 in CToken | ||
| let is_frozen = ctoken.state == 2; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Verify the frozen state constant.
The code assumes ctoken.state == 2 represents AccountState::Frozen. While this appears correct based on SPL Token conventions, consider adding a constant or comment referencing the state enum to make this more maintainable and discoverable.
🔎 Consider adding clarity:
+ // AccountState: Uninitialized=0, Initialized=1, Frozen=2
let is_frozen = ctoken.state == 2;Or define a constant:
const ACCOUNT_STATE_FROZEN: u8 = 2;
// ...
let is_frozen = ctoken.state == ACCOUNT_STATE_FROZEN;🤖 Prompt for AI Agents
In programs/registry/src/compressible/compressed_token/compress_and_close.rs
around line 111, the code uses a magic number to check frozen state with
`ctoken.state == 2`; replace this with a named constant or a clear comment
referencing the SPL Token AccountState enum to improve readability and
maintainability. Add a module-level constant like ACCOUNT_STATE_FROZEN: u8 = 2
(or import/alias the enum if available) and use it in the comparison, or add a
one-line comment that documents that 2 corresponds to AccountState::Frozen and
reference the SPL Token docs/enum.
| pub fn create_associated_ctoken_account_idempotent<'info>( | ||
| payer: AccountInfo<'info>, | ||
| associated_token_account: AccountInfo<'info>, | ||
| system_program: AccountInfo<'info>, | ||
| compressible_config: AccountInfo<'info>, | ||
| rent_sponsor: AccountInfo<'info>, | ||
| authority: Pubkey, | ||
| mint: Pubkey, | ||
| bump: u8, | ||
| pre_pay_num_epochs: Option<u8>, | ||
| lamports_per_write: Option<u32>, | ||
| ) -> std::result::Result<(), solana_program_error::ProgramError> { | ||
| let inputs = CreateCompressibleAssociatedTokenAccountInputs { | ||
| payer: *payer.key, | ||
| owner: authority, | ||
| mint, | ||
| compressible_config: *compressible_config.key, | ||
| rent_sponsor: *rent_sponsor.key, | ||
| pre_pay_num_epochs: pre_pay_num_epochs.unwrap_or(2), | ||
| lamports_per_write, | ||
| token_account_version: TokenDataVersion::ShaFlat, | ||
| }; | ||
|
|
||
| let ix = create_compressible_associated_token_account_with_bump_and_mode::<true>( | ||
| inputs, | ||
| *associated_token_account.key, | ||
| bump, | ||
| )?; | ||
|
|
||
| solana_cpi::invoke( | ||
| &ix, | ||
| &[ | ||
| payer, | ||
| associated_token_account, | ||
| system_program, | ||
| compressible_config, | ||
| rent_sponsor, | ||
| ], | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent authority handling between CPI wrappers.
create_associated_ctoken_account takes authority: AccountInfo and passes it in invoke, while create_associated_ctoken_account_idempotent takes authority: Pubkey and doesn't include it in the invoke accounts. This inconsistency is confusing and one of them is likely wrong.
If the on-chain program doesn't require authority as a separate account, remove it from both functions. If it does, add it to both consistently.
🤖 Prompt for AI Agents
In sdk-libs/ctoken-sdk/src/ctoken/create_associated_token_account.rs around
lines 456-495, the idempotent wrapper currently accepts authority: Pubkey and
does not include an authority AccountInfo in the CPI account list while the
non-idempotent wrapper accepts authority: AccountInfo and passes it to invoke;
make them consistent by changing this function to accept authority:
AccountInfo<'info>, set inputs.owner = *authority.key (instead of the Pubkey
param), and add the authority AccountInfo to the list passed to
solana_cpi::invoke so the CPI receives the authority account like the other
wrapper.
7113d51 to
5c9e4e8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (12)
sdk-libs/program-test/src/compressible.rs (1)
32-32: Consider removing redundantpubkeyfield.The
pubkeyis stored both as the HashMap key (line 27) and as a field in the struct. This creates unnecessary data duplication. Since the HashMap already provides the key when iterating, consider removing thepubkeyfield from the struct.🔎 Apply this diff to remove the redundant field:
#[derive(Eq, Hash, PartialEq)] pub struct StoredCompressibleAccount { - pub pubkey: Pubkey, pub last_paid_slot: u64, pub account: CToken, }Update the insertion site (around line 113-119):
stored_compressible_accounts.insert( account.0, StoredCompressibleAccount { - pubkey: account.0, last_paid_slot: last_funded_slot, account: des_account.clone(), }, );sdk-libs/token-client/src/actions/create_compressible_token_account.rs (1)
8-16: Consider makingcompression_onlyconfigurable.The
compression_onlyflag is currently hardcoded totruein the function body. If there are use cases where callers need to create compressible token accounts that are NOT compression-only, consider adding acompression_only: boolfield to this input struct.🔎 Proposed refactor to make compression_only configurable:
pub struct CreateCompressibleTokenAccountInputs<'a> { pub owner: Pubkey, pub mint: Pubkey, pub num_prepaid_epochs: u8, pub payer: &'a Keypair, pub token_account_keypair: Option<&'a Keypair>, pub lamports_per_write: Option<u32>, pub token_account_version: TokenDataVersion, + pub compression_only: bool, }Then update line 64 to use the input parameter:
let compressible_params = CompressibleParams { compressible_config, rent_sponsor, pre_pay_num_epochs: num_prepaid_epochs, lamports_per_write, compress_to_account_pubkey: None, token_account_version, - compression_only: true, + compression_only: inputs.compression_only, };forester/src/compressible/compressor.rs (1)
97-103: Hardcoded tree info should be addressed before production.The
FIXMEcomment and hardcodedTreeInfovalues indicate this is temporary. The commented-out code shows the intended pattern usingget_latest_active_state_trees(). Ensure this is tracked for resolution before mainnet deployment.Would you like me to open an issue to track replacing these hardcoded values with dynamic tree lookup?
sdk-libs/ctoken-sdk/src/compressed_token/v2/transfer2/instruction.rs (2)
156-191: Remove commented-out dead code.This commented-out
transfer2function appears to be legacy code. Dead code reduces readability and can cause confusion during maintenance. Consider removing it entirely or, if it's intended for future use, track it in an issue instead.🔎 Remove the dead code block
-/* -/// Create a multi-transfer instruction -pub fn transfer2(inputs: create_transfer2_instruction) -> Result<Instruction> { - let create_transfer2_instruction { - fee_payer, - authority, - validity_proof, - token_accounts, - tree_pubkeys, - config, - } = inputs; - - // Validate that no token account has been used - for token_account in &token_accounts { - if token_account.method_used { - return Err(CTokenSdkError::MethodUsed); - } - } - - let config = config.unwrap_or_default(); - let meta_config = Transfer2AccountsMetaConfig::new(fee_payer, authority) - .with_sol_pool( - config.sol_pool_pda.unwrap_or_default(), - config.sol_decompression_recipient.unwrap_or_default(), - ) - .with_cpi_context(); - - create_transfer2_instruction( - token_accounts, - validity_proof, - config, - meta_config, - tree_pubkeys, - ) -} -*/
64-79: Improve validation ergonomics forin_tlvfield.The documentation for
in_tlvis clear and theOption<Vec<Vec<...>>>design maintains good backward compatibility. However, consider adding SDK-level validation increate_transfer2_instruction()that checksin_tlv.len()matchestoken_accounts.len()before serialization. While the program validates this and returnsCTokenError::InvalidInstructionDatawith a descriptive message, early SDK-level validation would catch mismatches during instruction building rather than on-chain, providing better developer experience with synchronous error feedback.sdk-libs/token-client/src/actions/transfer2/decompress.rs (1)
14-34: Update doc comment to includedecimalsparameter.The function signature now includes
decimals: u8, but the doc comment doesn't document it. For API clarity, add a description of this parameter.🔎 Add decimals to documentation
/// # Arguments /// * `rpc` - RPC client with indexer capabilities /// * `compressed_token_account` - Slice of compressed token accounts to decompress /// * `decompress_amount` - Amount of tokens to decompress /// * `solana_token_account` - The SPL token account to receive the decompressed tokens /// * `authority` - Authority that can spend from the compressed token account /// * `payer` - Transaction fee payer keypair +/// * `decimals` - Token decimals (required for SPL transfer_checked)program-libs/ctoken-interface/src/constants.rs (1)
1-43: COMPRESSED_ONLY_EXTENSION_SIZE constant is incorrect and needs fixing.The
COMPRESSED_ONLY_EXTENSION_SIZEconstant is set to 8 bytes, but theCompressedOnlyExtensionstruct contains two u64 fields (delegated_amount and withheld_transfer_fee), totaling 16 bytes. The constant should be updated to 16, and the comment should reflect both fields instead of only mentioning delegated_amount.The other extension constants are correct:
TRANSFER_FEE_ACCOUNT_EXTENSION_LEN= 9 (1 discriminant + 8 withheld_amount) ✓TRANSFER_HOOK_ACCOUNT_EXTENSION_LEN= 2 (1 discriminant + 1 transferring) ✓TRANSFER2= 101 ✓The removal of
COMPRESSIBLE_TOKEN_RENT_EXEMPTIONis safe—no code references this constant anywhere in the codebase.programs/compressed-token/program/src/shared/owner_validation.rs (1)
66-96: Fix misleading comment that incorrectly describes delegate support.The comment at line 64 claims this function "Allows owner, account delegate, or permanent delegate" but the code only checks owner and permanent delegate—account delegates are intentionally excluded. The error message at line 95 repeats this inaccuracy. Update the comment to reflect the actual implementation: "Allows owner or permanent delegate (from mint) to authorize compression operations."
The unused
_compression_amountparameter is intentional (underscore suppresses warnings), but consider adding a clarifying comment like// TODO: Reserve for future transfer fee calculationsto document the design intent for future maintainers.programs/compressed-token/program/src/transfer2/compression/spl.rs (1)
70-73: Consider returning an error instead ofunimplemented!().While the
msg!provides context,unimplemented!()causes a hard panic. Returning a proper error would provide a cleaner failure mode and better error handling for callers.🔎 Suggested fix:
ZCompressionMode::CompressAndClose => { - msg!("CompressAndClose is unimplemented for spl token accounts"); - unimplemented!() + msg!("CompressAndClose is not supported for SPL token accounts"); + return Err(ProgramError::InvalidInstructionData); }program-libs/ctoken-interface/src/instructions/extensions/mod.rs (1)
46-55: Fix position misalignment between ExtensionInstructionData and ExtensionStruct enums.Both enums use
#[repr(C)]with theZeroCopyderive for serialization. Currently,ExtensionInstructionDatahas variants at positions 27, 28, and 31, butExtensionStructplaces the same variants at positions 33, 34, and 37 respectively. Only position 32 (Compressible) is correctly aligned. This mismatch will cause binary incompatibility and data corruption during serialization/deserialization. Align all variant positions between the two enums.sdk-libs/ctoken-sdk/src/ctoken/transfer_spl_ctoken.rs (2)
27-37: Doc example is missing the newdecimalsfield.The example code won't compile since
TransferSplToCtokennow requiresdecimals: u8. This applies to both doc examples (lines 27-37 and 66-78).🔎 Apply this diff to fix the doc example:
let instruction = TransferSplToCtoken { amount: 100, spl_interface_pda_bump: 255, + decimals: 9, source_spl_token_account, destination_ctoken_account, authority,
66-78: Second doc example also missingdecimalsandsystem_program.The
TransferSplToCtokenCpiexample needs both new fields.🔎 Apply this diff:
TransferSplToCtokenCpi { amount: 100, spl_interface_pda_bump: 255, + decimals: 9, source_spl_token_account, destination_ctoken_account, authority, mint, payer, spl_interface_pda, spl_token_program, compressed_token_program_authority, + system_program, }
♻️ Duplicate comments (40)
program-libs/zero-copy-derive/src/zero_copy.rs (1)
303-333: Immutable unit-struct path is technically sound.The approach differs appropriately from the mutable path:
- Immutable: Returns
&'areference to astatic UNITinstance—safe because shared references to statics are allowed.- Mutable (in
zero_copy_mut.rs): UsesBox::leakbecause returning&'a mutto a static would allow multiple mutable references, which is unsound.The initialization
static UNIT: #name = #name;correctly instantiates a unit struct (e.g.,static UNIT: Foo = Foo;).A previous review flagged that this unit-struct support contradicts the coding guidelines stating "Structs must have at least one field for zero-copy serialization; empty structs are not supported." That comment was marked as addressed. Ensure the README and guidelines are updated to document unit struct support as intentional for marker/phantom use cases.
program-libs/zero-copy-derive/src/shared/utils.rs (1)
24-28: UnitStruct variant cleanly extends the InputType enum.Adding
UnitStructas a distinct variant (rather than trying to represent it asStructwith empty fields) is the right design choice. It makes the handling explicit in each derive implementation and avoids ambiguity about whether "no fields" means unit struct or an error.Per the past review, documentation updates are needed to reflect that unit structs are now intentionally supported. The coding guidelines still state "Structs must have at least one field for zero-copy serialization."
programs/compressed-token/program/src/transfer2/compression/ctoken/compress_or_decompress_ctokens.rs (2)
64-75: Logic is sound; magic number already flagged.The frozen account handling correctly allows
CompressAndClosewith rent sponsor authority—this is the intended behavior for compressing expired frozen accounts. The check on line 72 using*ctoken.state == 2for frozen state was already noted in a previous review suggesting a named constant.
198-220: LGTM on fee and frozen state handling; magic number already flagged.The
withheld_transfer_feehandling correctly iterates through extensions to findTransferFeeAccountand adds the fee amount. The error path when no extension is found is appropriate since having a fee without the extension is an invalid state.Line 219 uses
*ctoken.state = 2for frozen—this is the same magic number flagged in the past review comment. When that constant is introduced, update this location as well.programs/compressed-token/anchor/src/instructions/create_token_pool.rs (1)
62-85: Past suggestion remains unaddressed: validate token_program ID.The previous review comment (lines 62-85) suggested adding validation that
token_program.keymatches eitherspl_token::id()orspl_token_2022::id()before constructing the CPI. While theInterface<'info, TokenInterface>constraint provides some protection at the instruction level, adding explicit validation here would provide defense in depth.🔎 Apply this diff to add token program validation:
pub fn initialize_token_account<'info>( token_account: &AccountInfo<'info>, mint: &AccountInfo<'info>, authority: &AccountInfo<'info>, token_program: &AccountInfo<'info>, ) -> Result<()> { + // Validate token program is SPL Token or Token-2022 + if *token_program.key != spl_token::id() && *token_program.key != spl_token_2022::id() { + return Err(crate::ErrorCode::InvalidTokenProgram.into()); + } let ix = spl_token_2022::instruction::initialize_account3(forester/src/compressible/state.rs (2)
33-34:Rent::default()uses hardcoded mainnet values.This returns Solana mainnet rent parameters. In test environments or if rent parameters change, this could produce incorrect compressible slot calculations. For production forester code targeting mainnet, this is acceptable, but be aware of this limitation.
127-137: Silent skip on calculation failure may hide issues.When
calculate_compressible_slotfails, the account is silently skipped with only a warning. This prevents one bad account from blocking updates, but these accounts won't be tracked at all.programs/registry/src/compressible/compressed_token/compress_and_close.rs (2)
100-104: Use a more appropriate error variant for CToken parsing failure.
RegistryError::InvalidSignerdoesn't accurately describe a CToken parsing failure. This should useRegistryError::InvalidTokenAccountDatafor consistency with line 97-98.
111-111: Document the frozen state constant for maintainability.The magic number
2representsAccountState::Frozenfrom SPL Token. Consider adding a comment or constant to make this discoverable.sdk-libs/program-test/src/forester/compress_and_close_forester.rs (1)
148-154: Sentinel value 0 for "no delegate" can collide with actual index 0.Using
0to represent "no delegate" is problematic because if a delegate account is the first inserted intopacked_accounts, it will receive index0, creating ambiguity.The instruction handler mitigates this by reading
has_delegatefrom actual CToken data, but this indirect safety mechanism masks a design flaw.programs/compressed-token/program/src/transfer2/compression/ctoken/compress_and_close.rs (1)
72-74: Magic number for AccountState::Initialized.Similar to the existing comment about
AccountState::Frozen(line 255, 268), line 74 uses the magic number1forAccountState::Initialized. Consider defining constants for both states:const ACCOUNT_STATE_INITIALIZED: u8 = 1; const ACCOUNT_STATE_FROZEN: u8 = 2;Then use these throughout the file for consistency and clarity.
programs/compressed-token/program/tests/multi_sum_check.rs (1)
1-493: Test location concern already flagged in previous review.A previous review comment already noted that integration tests should be moved to
program-tests/per coding guidelines. This applies to this test file.sdk-libs/client/src/indexer/types.rs (1)
890-896: TLV deserialization concerns already flagged in previous review.A previous review comment comprehensively identified issues with this TLV deserialization logic:
- Duplicated implementation in both TokenAccount and TokenAccountV2
- Generic error handling loses base64 vs. Borsh failure context
- Missing integration test coverage in sdk-tests/
- Should extract into shared helper function
These concerns remain unaddressed.
Also applies to: 931-937
programs/compressed-token/program/src/ctoken_transfer.rs (1)
86-92: Consider a more specific error for extension mismatch.
ProgramError::InvalidInstructionDatais generic. A dedicated error likeCTokenError::ExtensionMismatchwould help callers diagnose why the transfer failed.program-libs/ctoken-interface/src/instructions/transfer2/instruction_data.rs (1)
34-37: Breaking serialization change for TLV fields.This type change from
Vec<Vec<u8>>toVec<Vec<ExtensionInstructionData>>alters the Borsh wire format. Older SDK-generated transactions will fail deserialization.This was flagged in a previous review. Ensure the SDK and program are deployed atomically, or consider a versioned approach if gradual rollout is needed.
programs/compressed-token/program/src/shared/token_input.rs (1)
131-144: Acknowledge prior suggestion: TLV filtering comment.A previous review correctly noted that only
CompressedOnlyis mapped here while other extension variants (Pausable, PermanentDelegate, TransferFee, TransferHook) are intentionally filtered out. These are marker extensions that reference mint-level configuration rather than carrying per-account state. The suggested comment would help future maintainers understand this design choice.program-libs/ctoken-interface/src/instructions/extensions/compressible.rs (1)
21-25: Acknowledge prior concern: Potential flag/value inconsistency.A previous review correctly identified that
has_top_up(line 21) andwrite_top_up(line 25) could become inconsistent—e.g.,has_top_up=0butwrite_top_up>0, or vice versa. This creates two sources of truth for the same semantic information. Consider either:
- Removing
has_top_upand treatingwrite_top_up != 0as the presence indicator, OR- Adding validation logic where this struct is created/parsed to ensure
(has_top_up == 0) == (write_top_up == 0)and reject inconsistent combinationsWithout one of these approaches, consumers must guess which field takes precedence.
sdk-libs/ctoken-sdk/src/ctoken/compressible.rs (2)
37-50: Document the purpose and behavior ofcompression_only.This field lacks documentation explaining what behavior it controls, when it should be
truevsfalse, and its implications for compressible accounts. Additionally, the struct's docstring (lines 14-18) lists default values but doesn't include this field.
96-114: Add documentation and consider a builder method.The same documentation issue applies: this field lacks explanation of its purpose and behavior. Additionally, unlike
compress_to_account_pubkeywhich has awith_compress_to_pubkeybuilder method (line 118), there's no method to configurecompression_only. If users need to set this value, consider adding:pub fn with_compression_only(mut self, compression_only: bool) -> Self { self.compression_only = compression_only; self }sdk-libs/ctoken-sdk/src/ctoken/create_ata.rs (1)
100-102: Makehas_top_upconditional onwrite_top_upbeing provided.Currently,
has_top_upis hardcoded to 1 when a compressible config is provided, even whenwrite_top_upis 0 (viaunwrap_or(0)whenconfig.lamports_per_writeis None). This deviates from the pattern increate_associated_token_account.rsandcreate.rs, which only sethas_top_up: 1whenlamports_per_write.is_some(). To stay consistent:- has_top_up: 1, + has_top_up: if config.lamports_per_write.is_some() { 1 } else { 0 },programs/compressed-token/program/tests/token_input.rs (1)
115-132: Test correctly adapts to the new unified API signature.The
mint_cacheinitialization and updatedset_input_compressed_accountcall are correct. Passing the same slice for bothpacked_accountsandall_accountsis valid for this test scenario where they're identical.One observation: the test always passes
tlv_data: None, so TLV/extension data flows aren't exercised here. Consider adding a follow-up test case that providesSome(tlv_data)withCompressedOnlyextension data to validate the TLV population path inZTokenDataMut::set.program-libs/ctoken-interface/src/state/extensions/transfer_fee.rs (1)
27-40: Safe arithmetic with checked operations.The
add_withheld_amountmethod correctly useschecked_addto prevent overflow, which is critical for fee accumulation. The implementation pattern (get → checked_add → set) is idiomatic for zero-copy mutable references.The
ArithmeticOverflowerror type is minimal but sufficient. As noted in a previous review, addingstd::fmt::Displaywould improve error messages when converting toProgramError, though the current definition works forno_stdcontexts.programs/compressed-token/program/src/transfer2/cpi.rs (1)
54-59: Fix TLV boolean flag and improve type conversion safety.The
tlvtuple inTokenDataConfighas two issues:
The boolean should reflect whether TLV data exists: use
!tlv_config.is_empty()instead of hardcodingtrue. Other parts of the codebase (e.g.,shared/token_output.rsand tests) correctly set this conditionally—hardcodingtruewhen the config is empty causes incorrect byte length calculations.Replace the
as u32cast with.try_into()for explicit overflow handling. While Solana's 10MB account limit makes overflow unlikely, usingtry_into()makes the conversion bounds explicit and is more defensive.🔎 Apply these fixes:
let token_config = TokenDataConfig { delegate: (has_delegate, ()), - tlv: (true, tlv_config), + tlv: (!tlv_config.is_empty(), tlv_config), }; -TokenData::byte_len(&token_config).map_err(|_| ProgramError::InvalidAccountData)? - as u32 +TokenData::byte_len(&token_config) + .map_err(|_| ProgramError::InvalidAccountData)? + .try_into() + .map_err(|_| ProgramError::InvalidAccountData)?programs/compressed-token/program/src/shared/owner_validation.rs (1)
40-60: Verbose logging concern already noted.The multiple
msg!calls on authorization failure add compute overhead on the error path. This was flagged in a previous review.programs/compressed-token/program/src/transfer2/check_extensions.rs (1)
96-99: Compress mode validation remains commented out.The documentation at line 71 states "Compress: NOT allowed (mints with restricted extensions must not be compressed)", but the validation is still commented out. This contradicts the documented design intent and was flagged in a previous review.
programs/compressed-token/program/src/create_token_account.rs (1)
177-194: Mint extension validation and account sizing are well-implemented.The logic correctly:
- Checks mint extensions once via
has_mint_extensions- Enforces
compression_onlywhen restricted extensions exist- Calculates account size dynamically based on extension flags
However, as noted in a previous review, the
has_restricted_extensionscheck duplicates logic fromcheck_mint_extensions.rs. Consider extractingMintExtensionFlags::has_restricted_extensions()as a shared helper.programs/compressed-token/program/src/ctoken_approve_revoke.rs (2)
113-117: Clarify thesaturating_add(1)sentinel value pattern.The
+1creates a sentinel allowing differentiation between "exactly at limit" and "exceeded", but this isn't immediately obvious. Since line 132 performs a direct comparison (transfer_amount > max_top_up), the budget mechanism becomes redundant.Consider simplifying by removing
saturating_add(1)and relying solely on the direct comparison, or adding a comment explaining the sentinel pattern if both checks are intentional.
130-137: Redundant max_top_up enforcement.The direct comparison at line 132 (
transfer_amount > max_top_up as u64) makes thelamports_budgetmechanism on lines 113-117 redundant. Consider either:
- Using only the direct comparison (simpler), or
- Using only the budget mechanism (check
lamports_budget == 0afterprocess_compressible_extension)programs/compressed-token/program/src/create_associated_token_account.rs (1)
118-126: Account sizing falls back to minimum when mint_account is unavailable.When
mint_accountisNone(as in thecreate_atapath at line 81),MintExtensionFlags::default()produces all-false flags. If the mint actually has extensions likeTransferFee(which requires extra bytes per the PR), the created account will be undersized.The comment on lines 94-95 mentions
create_ata2uses this—verify that callers who need extension-aware sizing always provide the mint account.#!/bin/bash # Check how create_ata2 calls this function rg -n "process_create_associated_token_account_inner" --type rust -B 3 -A 10sdk-libs/ctoken-sdk/src/ctoken/transfer_spl_ctoken.rs (1)
170-171:Pubkey::default()is not the System Program ID.The System Program address is
11111111111111111111111111111111(all ones), not the zero address. If lamport top-ups are intended to work, usesolana_program::system_program::ID. If this feature isn't implemented yet, consider removing this account or adding a// TODO: not yet implementedcomment.programs/compressed-token/program/tests/token_output.rs (1)
124-124: TODO: Randomize frozen flag.The frozen state path should be exercised. Consider implementing as suggested in the past review:
is_frozen: rng.gen_bool(0.2), // 20% chance of frozenprograms/compressed-token/program/src/transfer2/compression/ctoken/inputs.rs (1)
107-116: Delegate lookup silently swallows errors.When
has_delegate()returns true butpacked_accounts.get_u8()fails, the error is silently converted toNonevia.ok(). This could mask configuration errors. Consider logging or propagating the error.program-libs/ctoken-interface/src/state/extensions/extension_struct.rs (2)
160-225: Discriminant handling is correct but should use constants.The discriminant-to-variant mapping (19→TokenMetadata, 27→PausableAccount, 28→PermanentDelegateAccount, 29→TransferFeeAccount, 30→TransferHookAccount, 31→CompressedOnly, 32→Compressible) is intentionally decoupled from enum indices for Token-2022 compatibility.
However, as noted in previous reviews, these magic numbers should be extracted to named constants and documented as part of the serialization contract.
🔎 Define constants at module level:
/// Discriminant values for ExtensionStruct variants /// These are intentionally decoupled from enum indices for Token-2022 compatibility pub const TOKEN_METADATA_DISCRIMINANT: u8 = 19; pub const PAUSABLE_ACCOUNT_DISCRIMINANT: u8 = 27; pub const PERMANENT_DELEGATE_ACCOUNT_DISCRIMINANT: u8 = 28; pub const TRANSFER_FEE_ACCOUNT_DISCRIMINANT: u8 = 29; pub const TRANSFER_HOOK_ACCOUNT_DISCRIMINANT: u8 = 30; pub const COMPRESSED_ONLY_DISCRIMINANT: u8 = 31; pub const COMPRESSIBLE_DISCRIMINANT: u8 = 32;
378-394: Inconsistent size validation pattern for CompressedOnly.The
CompressedOnlycase validates buffer size explicitly (line 380) before writing, while other cases rely onnew_zero_copyto fail if the buffer is too small. This inconsistency was noted in previous reviews.For consistency, either add explicit pre-checks to all variants or rely uniformly on
new_zero_copyfor validation.programs/compressed-token/program/src/shared/initialize_ctoken_account.rs (1)
116-133: Extract discriminator values to named constants.The discriminators (27, 28, 29, 30, 32) are hardcoded magic numbers. As flagged in previous reviews, these should be extracted to named constants for maintainability and to ensure consistency with the
ExtensionStructenum inextension_struct.rs.🔎 Suggested constants:
// At module level or in a shared constants module const PAUSABLE_ACCOUNT_DISCRIMINATOR: u8 = 27; const PERMANENT_DELEGATE_DISCRIMINATOR: u8 = 28; const TRANSFER_FEE_DISCRIMINATOR: u8 = 29; const TRANSFER_HOOK_DISCRIMINATOR: u8 = 30; const COMPRESSIBLE_DISCRIMINATOR: u8 = 32;programs/compressed-token/program/src/extensions/check_mint_extensions.rs (2)
91-100:unwrap_or_default()silently swallows extension type enumeration errors.If
get_extension_types()fails, this returns an empty vector andhas_restricted_extensionsbecomesfalse—potentially allowing restricted mints to bypass validation.🔎 Handle the error explicitly:
- let extension_types = mint_state.get_extension_types().unwrap_or_default(); + let extension_types = mint_state.get_extension_types().map_err(|e| { + msg!("Failed to get extension types: {:?}", e); + ProgramError::InvalidAccountData + })?;
182-191: Sameunwrap_or_default()issue inhas_mint_extensions.Line 183 has the same silent error swallowing issue. If extension type enumeration fails, the function returns default flags instead of propagating the error, which could lead to accounts being created with incorrect extension configurations.
🔎 Propagate the error:
- let extension_types = mint_state.get_extension_types().unwrap_or_default(); + let extension_types = mint_state.get_extension_types().map_err(|e| { + msg!("Failed to get extension types: {:?}", e); + ProgramError::InvalidAccountData + })?;sdk-libs/ctoken-sdk/src/ctoken/create_associated_token_account.rs (3)
206-210: System program pubkey is incorrect - will cause CPI failures.Using
Pubkey::new_from_array([0; 32])creates a null pubkey (all zeros), not the system program ID. This will cause the instruction to fail on-chain when the program validates the system program account.🔎 Fix the system program pubkey:
- solana_instruction::AccountMeta::new_readonly(Pubkey::new_from_array([0; 32]), false), // system_program + solana_instruction::AccountMeta::new_readonly(solana_program::system_program::ID, false), // system_programOr if using
solana_system_interface:+use solana_system_interface::program::ID as SYSTEM_PROGRAM_ID; // ... - solana_instruction::AccountMeta::new_readonly(Pubkey::new_from_array([0; 32]), false), // system_program + solana_instruction::AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false), // system_program
383-389: Same system program issue in v2 instruction builder.The
[0; 32]placeholder is used here as well. This needs the same fix to use the actual system program ID.🔎 Apply the same fix:
- solana_instruction::AccountMeta::new_readonly(Pubkey::new_from_array([0; 32]), false), + solana_instruction::AccountMeta::new_readonly(solana_program::system_program::ID, false),
410-451: Inconsistent authority handling between CPI wrappers.
create_associated_ctoken_account(line 416) takesauthority: AccountInfoand passes it in the invoke accounts (line 448), whilecreate_associated_ctoken_account_idempotent(line 462) takesauthority: Pubkeyand doesn't include it in the invoke accounts.This inconsistency is confusing and one of them is likely incorrect. If the on-chain program expects the authority as a separate account for validation, both should pass it. If not, neither should.
Additionally, the non-idempotent wrapper passes 6 accounts to invoke but the instruction built by
create_compressible_associated_token_account_with_bumponly includes 5 accounts in its AccountMeta list (lines 206-222), which will cause a CPI mismatch.Also applies to: 456-495
| // Single assert comparing full account state | ||
| assert_eq!(ctoken, expected_ctoken, "CToken account should match expected"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider documenting additional test patterns beyond the single assertion approach.
The section demonstrates a comprehensive single-assert pattern, which is excellent for full account validation. However, consider adding guidance for scenarios where tests need to: (a) assert on specific fields only (partial validation), (b) handle variable extension ordering, or (c) test failed account creation paths. A follow-up section with these patterns would strengthen the testing guidance.
🤖 Prompt for AI Agents
In CLAUDE.md around lines 245-246, expand the testing section by adding a
follow-up subsection that covers three patterns: (a) partial validation — show
how to assert specific fields rather than the entire account (e.g., compare
selected fields or use helper matchers to ignore irrelevant fields), (b)
extension-order resilience — describe comparing extensions in an
order-independent way (e.g., sort or compare by key/ID or convert to a map/set
before asserting), and (c) negative/failure paths — demonstrate testing failed
account creation by asserting error kinds/messages and any preserved state. Keep
each pattern short with a one-paragraph explanation and a small example outline
for the assertion strategy and test setup.
| // Build TLV extensions for this output if marker extensions are present | ||
| if has_marker_extensions { | ||
| out_tlv.push(vec![ExtensionInstructionData::CompressedOnly( | ||
| CompressedOnlyExtensionInstructionData { | ||
| delegated_amount, | ||
| withheld_transfer_fee, | ||
| is_frozen, | ||
| }, | ||
| )]); | ||
| } else { | ||
| out_tlv.push(vec![]); | ||
| } | ||
|
|
||
| // Create one output account per compression operation | ||
| output_accounts.push(MultiTokenTransferOutputData { | ||
| owner: idx.owner_index, | ||
| amount, | ||
| delegate: 0, | ||
| delegate: idx.delegate_index, | ||
| mint: idx.mint_index, | ||
| version: 3, // Shaflat | ||
| has_delegate: false, | ||
| has_delegate: delegated_amount > 0, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -name "compress_and_close.rs" -type fRepository: Lightprotocol/light-protocol
Length of output: 497
🏁 Script executed:
cat -n ./programs/registry/src/compressible/compressed_token/compress_and_close.rs | sed -n '130,170p'Repository: Lightprotocol/light-protocol
Length of output: 1867
🏁 Script executed:
cat -n ./programs/registry/src/compressible/compressed_token/compress_and_close.rs | sed -n '100,140p'Repository: Lightprotocol/light-protocol
Length of output: 2169
🏁 Script executed:
cat -n ./programs/registry/src/compressible/compressed_token/compress_and_close.rs | sed -n '1,50p'Repository: Lightprotocol/light-protocol
Length of output: 2518
🏁 Script executed:
# Let me check what happens downstream with this data - look for where MultiTokenTransferOutputData is used
rg -n "MultiTokenTransferOutputData" --type rust -A 5 programs/registry/src/ | head -40Repository: Lightprotocol/light-protocol
Length of output: 1368
🏁 Script executed:
# Check if CompressedOnly extension is the only way to preserve delegated_amount downstream
rg -n "delegated_amount" --type rust -B 2 -A 2 programs/compressed-token/ | head -60Repository: Lightprotocol/light-protocol
Length of output: 5326
🏁 Script executed:
# Check how the transfer2 instruction processes the TLV and output data
rg -n "transfer2\|Transfer2" --type rust -l programs/compressed-token/program/src/ | head -5Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Let's find how the CompressedOnly extension is processed
rg -n "CompressedOnly" --type rust -B 2 -A 5 programs/compressed-token/program/src/ | head -80Repository: Lightprotocol/light-protocol
Length of output: 7940
🏁 Script executed:
# Check the ACCOUNTS.md documentation for account layout specifications
cat ./programs/compressed-token/program/docs/ACCOUNTS.md | head -100Repository: Lightprotocol/light-protocol
Length of output: 3165
🏁 Script executed:
# Let's verify if the MultiTokenTransferOutputData.delegate index is used to reconstruct delegated_amount
rg -n "delegate.*index\|delegate_index" --type rust -B 2 -A 2 programs/compressed-token/program/src/transfer2/token_outputs.rs | head -40Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
cat -n ./programs/compressed-token/program/src/transfer2/token_outputs.rs | sed -n '1,150p'Repository: Lightprotocol/light-protocol
Length of output: 4526
🏁 Script executed:
cat -n ./programs/compressed-token/program/src/shared/token_output.rs | sed -n '1,150p'Repository: Lightprotocol/light-protocol
Length of output: 6314
🏁 Script executed:
# Check if delegated_amount can be recovered from anywhere else besides CompressedOnly
rg -n "delegated_amount\|CompressedOnly" --type rust programs/compressed-token/program/src/shared/token_output.rs -A 3 -B 3Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Check the TokenData structure and how it handles delegated_amount
rg -n "delegated_amount\|struct TokenData" --type rust -A 5 program-libs/ctoken-types/src/state/ | head -80Repository: Lightprotocol/light-protocol
Length of output: 148
🏁 Script executed:
find . -name "*token_data*" -type f | grep -E "\.rs$"Repository: Lightprotocol/light-protocol
Length of output: 601
🏁 Script executed:
rg -n "delegated_amount\|pub struct TokenData" --type rust -B 2 -A 10 program-libs/ctoken-interface/src/state/compressed_token/token_data.rs | head -100Repository: Lightprotocol/light-protocol
Length of output: 54
Preserve delegated_amount in CompressedOnly extension when delegate exists, even without marker extensions.
The code only creates the CompressedOnly extension when marker extensions are present (frozen state, transfer fees, pausable, permanent delegate, transfer hook, or compressible extension). However, when an account has a delegate with a delegated_amount but none of these marker extensions, the delegated_amount value is lost—the delegate index only stores the delegate's pubkey, not the spending limit.
The compressed output will have has_delegate: true but no way to recover the original delegated_amount because it's only preserved in CompressedOnly. To fix this, create CompressedOnly whenever delegated_amount > 0, not just when marker extensions are present.
🤖 Prompt for AI Agents
In programs/registry/src/compressible/compressed_token/compress_and_close.rs
around lines 141 to 162, the CompressedOnly extension is only added when
has_marker_extensions is true, which drops delegated_amount when a delegate
exists but no marker extensions are present; change the condition to add the
CompressedOnly extension whenever has_marker_extensions || delegated_amount > 0
so delegated_amount is preserved in the TLV, keeping the same
CompressedOnlyExtensionInstructionData fields (delegated_amount,
withheld_transfer_fee, is_frozen) and leaving the output_accounts construction
(including has_delegate: delegated_amount > 0) unchanged.
| .unwrap_or(0), | ||
| mint: packed_accounts.insert_or_get(token.mint), | ||
| version, | ||
| version: if tlv.is_some() { 3 } else { 2 }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider checking for non-empty TLV, not just presence.
The version is set to 3 when tlv.is_some(), but this would also apply if tlv = Some(vec![]). In token_inputs.rs (line 47), the check uses is_some_and(|v| !v.is_empty()) for validation. Consider aligning this logic:
🔎 Suggested fix:
- version: if tlv.is_some() { 3 } else { 2 },
+ version: if tlv.as_ref().is_some_and(|v| !v.is_empty()) { 3 } else { 2 },📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| version: if tlv.is_some() { 3 } else { 2 }, | |
| version: if tlv.as_ref().is_some_and(|v| !v.is_empty()) { 3 } else { 2 }, |
🤖 Prompt for AI Agents
In sdk-libs/ctoken-sdk/src/compressed_token/v2/decompress_full.rs around line
174, the code sets version based on tlv.is_some(), which treats Some(empty_vec)
the same as a non-empty TLV; change the condition to check for a non-empty TLV
(e.g., use is_some_and(|v| !v.is_empty())) so version is set to 3 only when tlv
contains data, matching the validation in token_inputs.rs.
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
Outdated
Show resolved
Hide resolved
| use solana_signer::Signer; | ||
|
|
||
| /// Transfer tokens from a compressed token account to an SPL token account | ||
| #[allow(clippy::too_many_arguments)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Decimals parameter added; consider builder pattern for parameter-heavy functions.
The decimals parameter is correctly propagated to the TransferCTokenToSpl instruction. The #[allow(clippy::too_many_arguments)] attribute indicates the function now has 8 parameters. While acceptable for this incremental change, consider refactoring to a builder pattern in future work if additional parameters are anticipated.
Also applies to: 24-24, 38-38
🤖 Prompt for AI Agents
In sdk-libs/token-client/src/actions/transfer2/ctoken_to_spl.rs around lines 15,
24 and 38, the function now takes eight parameters (triggering
#[allow(clippy::too_many_arguments)]) after adding decimals; refactor to a
builder pattern: introduce a TransferCTokenToSplParams struct containing all
current parameters (including decimals) and implement a fluent builder (new() +
setter methods for each field and a build() that validates required fields),
change the instruction function signature to accept a single params struct (or
the built params), update all call sites to construct the params via the
builder, remove the clippy allow attribute, and add minimal validation in
build() for required invariants.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
sdk-libs/ctoken-sdk/src/compressed_token/v2/decompress_full.rs (1)
63-87: TLV collection logic is correct but could be simplified.The logic correctly collects TLV data and tracks whether any TLV exists. However, the conditional push could be simplified:
🔎 Optional simplification
- // Collect TLV data for this input - if let Some(tlv) = &idx.tlv { - has_any_tlv = true; - in_tlv_data.push(tlv.clone()); - } else { - in_tlv_data.push(Vec::new()); - } + // Collect TLV data for this input + let tlv = idx.tlv.clone().unwrap_or_default(); + if !tlv.is_empty() { + has_any_tlv = true; + } + in_tlv_data.push(tlv);
♻️ Duplicate comments (5)
program-libs/ctoken-interface/src/state/extensions/extension_struct.rs (1)
364-380: CompressedOnly has explicit size validation – good defensive programming.The explicit length check at line 366 before writing the discriminant is more defensive than other variants. As previously noted, consider making this consistent across all variants.
programs/compressed-token/program/src/transfer2/compression/ctoken/compress_or_decompress_ctokens.rs (1)
65-70: Magic number for Frozen state – extract to constant.The literal
2forAccountState::Frozenappears here (and was previously flagged). Consider defining a constant likeconst ACCOUNT_STATE_FROZEN: u8 = 2;to improve readability and prevent inconsistencies.programs/compressed-token/program/src/transfer2/compression/ctoken/inputs.rs (1)
87-117: Delegate lookup silently swallows errors – previously flagged.At line 108,
.ok()converts any error frompacked_accounts.get_u8()toNone, which can mask issues whenhas_delegate()returns true but the delegate index is invalid. Consider propagating or logging the error.🔎 Suggested improvement:
let input_delegate = matching_input_index.and_then(|idx| { let input = inputs.in_token_data.get(idx)?; if input.has_delegate() { - packed_accounts + match packed_accounts .get_u8(input.delegate, "input delegate") - .ok() + { + Ok(delegate) => Some(delegate), + Err(e) => { + solana_msg::msg!("Failed to resolve input delegate: {:?}", e); + None + } + } } else { None } });sdk-libs/ctoken-sdk/src/ctoken/compressible.rs (2)
37-37: Documentation still missing forcompression_onlyfield.The
compression_onlyfield lacks any doc comment explaining its purpose, when to set it, or its implications for compressible account behavior. The struct-level docstring (lines 14-18) lists default values but omits this field.
96-96: Documentation and builder method still missing.The
compression_onlyfield inCompressibleParamsCpilacks documentation, and unlikecompress_to_account_pubkey(which has a builder method at line 118), there's no way to configure this field through a builder pattern.
| // Reject uninitialized accounts (state == 0) | ||
| if *ctoken.state == 0 { | ||
| msg!("Account is uninitialized"); | ||
| return Err(CTokenError::InvalidAccountState.into()); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Redundant uninitialized account check.
This check duplicates the validation already performed by CToken::zero_copy_at_mut_checked at line 48, which returns InvalidAccountState when bytes[108] == 0. The double-check is defensive but adds unnecessary code.
🔎 Consider removing the redundant check:
let (mut ctoken, _) = CToken::zero_copy_at_mut_checked(&mut token_account_data)?;
- // Reject uninitialized accounts (state == 0)
- if *ctoken.state == 0 {
- msg!("Account is uninitialized");
- return Err(CTokenError::InvalidAccountState.into());
- }
if ctoken.mint.to_bytes() != mint {Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
programs/compressed-token/program/src/transfer2/compression/ctoken/compress_or_decompress_ctokens.rs
around lines 50 to 54: the explicit uninitialized-account check (if
*ctoken.state == 0 { ... return Err(...) }) duplicates the validation already
performed by CToken::zero_copy_at_mut_checked at line 48; remove that entire
if-block (lines 50–54) so the function relies on the prior checked constructor,
and run/adjust tests or callers if they depended on the duplicated log message.
a6748f8 to
1156039
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
program-libs/ctoken-interface/src/instructions/extensions/compressible.rs (1)
56-70: Update comment to match actual implementation.The comment describing modifications (line 59) states that the function returns
CTokenError::InvalidAccountDatawhenseeds.len() > MAX_SEEDS, but the actual implementation (line 68-69) checksseeds.len() >= MAX_SEEDSand returnsCTokenError::TooManySeeds(MAX_SEEDS - 1).🔎 Proposed fix
// Taken from pinocchio 0.9.2. // Modifications: // - seeds: &[&[u8]; N], -> seeds: &[&[u8]], -// - if seeds.len() > MAX_SEEDS CTokenError::InvalidAccountData +// - if seeds.len() >= MAX_SEEDS returns CTokenError::TooManySeeds(MAX_SEEDS - 1) pub fn derive_address(sdk-libs/ctoken-sdk/src/compressed_token/v2/account2.rs (2)
253-284: Document thedecimalsparameter.Similar to
compress_spl, the newdecimals: u8parameter indecompress_spllacks documentation explaining its purpose and expected values.Add documentation clarifying the parameter's role in SPL token decompression and its relationship to the token mint's decimal precision.
193-223: Add inline documentation for thedecimalsparameter in compression/decompression methods.The
decimals: u8parameter incompress_splanddecompress_spllacks explanation. Whilecompress_fullincludes an inline comment clarifying when decimals are used (// Not used for ctoken compression), these methods need similar documentation.Decimals are passed through to SPL token program's
transfer_checkedinstruction where the on-chain program validates they match the mint's actual decimal count. Add a comment like:/// decimals - Mint decimal precision for SPL token operations (validated by transfer_checked)This clarifies the parameter's purpose and documents that validation happens at the SPL program layer, consistent with the pattern already established in
compress_full.
♻️ Duplicate comments (7)
sdk-libs/ctoken-sdk/src/ctoken/transfer_spl_ctoken.rs (1)
174-175: ReplacePubkey::default()with actual System Program ID.Line 175 uses
Pubkey::default()(zero address) for the System program placeholder. The actual System Program ID is11111111111111111111111111111111.Since the comment indicates this account is "needed for compressible account lamport top-ups," either:
- Use the correct System Program ID constant (e.g., from
solana_program::system_program::IDor define it locally)- Or remove this account entirely if lamport transfers remain unimplemented
Note: The on-chain Transfer2 processor currently rejects
in_lamportsandout_lamportsas unimplemented (see lines 305-306 in TRANSFER2.md), so this account may not be actively used yet.🔎 Proposed fix using system program constant
- // System program (index 6) - needed for compressible account lamport top-ups - AccountMeta::new_readonly(Pubkey::default(), false), + // System program (index 6) - needed for compressible account lamport top-ups + AccountMeta::new_readonly(solana_program::system_program::ID, false),program-libs/ctoken-interface/src/error.rs (1)
151-152: Consider improving error message clarity.The error messages could be more specific:
Line 151-152: The message "CompressedOnly tokens cannot have compressed outputs - must decompress only" mixes prohibition and directive. Consider:
"CompressedOnly extension prohibits compressed outputs"or"CompressedOnly tokens can only be decompressed, not compressed"Line 154-155: The term "Output TLV data count must match number of compressed outputs" doesn't clearly indicate what's mismatched. Consider:
"TLV output count mismatch: provided {provided} but expected {expected}"or"Number of TLV entries must match number of compressed outputs"Clear error messages help developers quickly diagnose issues during debugging.
Also applies to: 154-155
sdk-libs/ctoken-sdk/src/ctoken/create_associated_token_account.rs (4)
205-209: System program pubkey is incorrect.
Pubkey::new_from_array([0; 32])creates a null pubkey, not the system program ID. This will cause on-chain validation to fail when the program checks for the system program account.🔎 Proposed fix
- solana_instruction::AccountMeta::new_readonly(Pubkey::new_from_array([0; 32]), false), // system_program + solana_instruction::AccountMeta::new_readonly(solana_system_interface::program::ID, false), // system_programYou'll need to add the import:
use solana_system_interface::program::ID as SYSTEM_PROGRAM_ID;
381-387: Same system program issue in v2 instruction builder.This uses the same
[0; 32]placeholder which will fail on-chain validation.🔎 Proposed fix
- solana_instruction::AccountMeta::new_readonly(Pubkey::new_from_array([0; 32]), false), + solana_instruction::AccountMeta::new_readonly(solana_system_interface::program::ID, false),
406-449: CPI wrapper passes authority but instruction doesn't expect it.The
create_associated_ctoken_accountfunction takesauthority: AccountInfo(line 414) and passes it in the invoke accounts (line 446), but the instruction built bycreate_compressible_associated_token_account_with_bumponly expects 5 accounts:[payer, ata, system_program, compressible_config, rent_sponsor]. This account count mismatch will cause the CPI to fail.Either remove
authorityfrom the invoke accounts if it's not needed by the on-chain program, or add it to the instruction's account list if it is required.
454-493: Inconsistent authority handling between CPI wrappers.
create_associated_ctoken_accounttakesauthority: AccountInfoand passes it in invoke (though incorrectly), whilecreate_associated_ctoken_account_idempotenttakesauthority: Pubkeyand doesn't include it in invoke accounts. This inconsistency is confusing and one implementation is likely wrong.Harmonize the signatures and account handling:
- If authority is needed: both should take
AccountInfoand include it in invoke- If authority is not needed: both should just use
Pubkeyfor owner derivation onlyprograms/compressed-token/program/src/transfer2/check_extensions.rs (1)
94-97: Uncomment Compress mode validation for restricted extensionsThe documented behavior states "Compress: NOT allowed (mints with restricted extensions must not be compressed)" (line 69), but the validation is commented out, silently allowing restricted mints to compress. CompressAndClose mode has equivalent validation enabled (lines 73-92). Restore the validation to match the documented design intent.
🔎 Restore the commented validation
ZCompressionMode::Compress => { - // msg!("Mints with restricted extensions cannot be compressed"); - // return Err(ErrorCode::MintHasRestrictedExtensions.into()); + msg!("Mints with restricted extensions cannot be compressed"); + return Err(ErrorCode::MintHasRestrictedExtensions.into()); }
| // This is the compression-only hot path (no compressed inputs/outputs). | ||
| // Extension checks are skipped because balance must be restored immediately | ||
| // (compress + decompress in same tx) or sum check will fail. | ||
| process_token_compression( | ||
| fee_payer, | ||
| inputs, | ||
| &validated_accounts.packed_accounts, | ||
| cpi_authority_pda, | ||
| inputs.max_top_up.get(), | ||
| mint_cache, | ||
| )?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider clarifying the extension check comment
The comment states "Extension checks are skipped" while still passing mint_cache to process_token_compression. While this is likely correct (the cache is required by the function signature, and specific checks may be relaxed downstream), consider clarifying what "skipped" means—perhaps "certain extension checks are relaxed" or "balance-restoration checks are deferred."
The logic itself is sound for the compression-only hot path.
🤖 Prompt for AI Agents
In programs/compressed-token/program/src/transfer2/processor.rs around lines 199
to 209, the comment "Extension checks are skipped" is ambiguous while the code
still passes mint_cache into process_token_compression; update the comment to
explicitly state that only certain extension checks are relaxed or deferred
(e.g., balance-restoration/extension validation deferred because
compress+decompress occur in the same tx) and note that mint_cache is still
required by the function signature for any remaining validations; keep the
current call and behavior unchanged, just make the comment precise about which
checks are skipped versus which are still performed.
| let compressible_extension = if COMPRESSIBLE { | ||
| if let Some((pre_pay_num_epochs, lamports_per_write, _, _, token_account_version)) = | ||
| compressible_config | ||
| { | ||
| Some(CompressibleExtensionInstructionData { | ||
| token_account_version: token_account_version as u8, | ||
| rent_payment: pre_pay_num_epochs, | ||
| compression_only: 0, | ||
| write_top_up: lamports_per_write.unwrap_or(0), | ||
| compress_to_account_pubkey: None, // Not used for ATA creation | ||
| }) | ||
| } else { | ||
| return Err(TokenSdkError::InvalidAccountData); | ||
| } | ||
| } else { | ||
| None | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
compression_only is hardcoded to 0, inconsistent with other builders.
In create.rs and create_ata.rs, compression_only is derived from config.compression_only. Here it's always set to 0. If the CreateCompressibleAssociatedTokenAccountInputs struct should support this field, it needs to be added and propagated.
🔎 Proposed fix
Add the field to the inputs struct:
pub struct CreateCompressibleAssociatedTokenAccountInputs {
// ... existing fields ...
pub token_account_version: TokenDataVersion,
+ /// Whether the account is compression-only (cannot be transferred while decompressed)
+ pub compression_only: bool,
}Then propagate it in the unified builder:
- compression_only: 0,
+ compression_only: if compressible_config.5 { 1 } else { 0 },Note: You'll need to update the tuple type and all call sites accordingly.
Committable suggestion skipped: line range outside the PR's diff.
| pub fn derive_ctoken_ata(owner: &Pubkey, mint: &Pubkey) -> (Pubkey, u8) { | ||
| Pubkey::find_program_address( | ||
| &[ | ||
| owner.as_ref(), | ||
| light_ctoken_types::COMPRESSED_TOKEN_PROGRAM_ID.as_ref(), | ||
| mint.as_ref(), | ||
| ], | ||
| &Pubkey::from(light_ctoken_types::COMPRESSED_TOKEN_PROGRAM_ID), | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
derive_ctoken_ata is duplicated from create_ata.rs.
This function is identical to the one in sdk-libs/ctoken-sdk/src/ctoken/create_ata.rs (lines 16-25 per relevant_code_snippets). Consider extracting it to a shared module to avoid drift.
You could move this to a common utilities module (e.g., ctoken/mod.rs or a new ctoken/utils.rs) and re-export it from both locations for backward compatibility.
🤖 Prompt for AI Agents
In sdk-libs/ctoken-sdk/src/ctoken/create_associated_token_account.rs around
lines 230 to 239, the derive_ctoken_ata function is duplicated from
create_ata.rs; extract this helper into a shared module (e.g.,
sdk-libs/ctoken-sdk/src/ctoken/utils.rs or
sdk-libs/ctoken-sdk/src/ctoken/mod.rs), move the derive_ctoken_ata
implementation there, update both create_associated_token_account.rs and
create_ata.rs to import/re-export the function (pub use
crate::ctoken::utils::derive_ctoken_ata or pub use
super::utils::derive_ctoken_ata as appropriate) and remove the duplicate
implementation, then run a build to ensure all references compile.
| let compressible_extension = if COMPRESSIBLE { | ||
| if let Some((pre_pay_num_epochs, lamports_per_write, _, _, token_account_version)) = | ||
| compressible_config | ||
| { | ||
| Some(CompressibleExtensionInstructionData { | ||
| token_account_version: token_account_version as u8, | ||
| rent_payment: pre_pay_num_epochs, | ||
| compression_only: 0, | ||
| write_top_up: lamports_per_write.unwrap_or(0), | ||
| compress_to_account_pubkey: None, | ||
| }) | ||
| } else { | ||
| return Err(TokenSdkError::InvalidAccountData); | ||
| } | ||
| } else { | ||
| None | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same compression_only hardcoding issue in v2 builder.
This also hardcodes compression_only: 0 instead of deriving from config, creating the same inconsistency with create.rs and create_ata.rs.
🤖 Prompt for AI Agents
In sdk-libs/ctoken-sdk/src/ctoken/create_associated_token_account.rs around
lines 352 to 368, the CompressibleExtensionInstructionData currently hardcodes
compression_only: 0; update the code to read compression_only from the
compressible_config tuple (the appropriate element in the tuple that represents
compression_only), cast/convert it to the expected u8 type, and assign that
value to compression_only instead of 0 while keeping the same None/error
handling when compressible_config is missing.
| // TODO: switch to wrapper ixn using accounts instead of ixdata. | ||
| let ix = create_compressible_associated_token_account_with_bump( | ||
| inputs, | ||
| *associated_token_account.key, | ||
| bump, | ||
| )?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Address TODO or track it.
The TODO comment indicates this should switch to a wrapper instruction using accounts instead of instruction data. Consider opening an issue to track this work if it's not being addressed in this PR.
Would you like me to open an issue to track this technical debt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
programs/compressed-token/anchor/src/lib.rs (1)
543-552: Consider adding doc comments for clarity.The
check_cpi_contextfunction correctly validates that CPI context isn't in write mode (set_contextorfirst_set_context), which would be incompatible with operations like burn, compression, or decompression.However, adding doc comments would improve maintainability by explaining:
- Which operations this constraint applies to (burn, compress, decompress)
- Why these flags are incompatible with those operations
- What
set_contextandfirst_set_contextsignify📝 Suggested documentation addition
+/// Checks if CPI context usage is valid for the current instruction. +/// +/// Returns an error if the CPI context has `set_context` or `first_set_context` set to true, +/// which indicates the context is being written/modified. This is incompatible with +/// burn, compression (in transfer), and decompression operations. +/// +/// # Arguments +/// * `cpi_context` - Optional CPI context to validate +/// +/// # Errors +/// Returns `ErrorCode::CpiContextSetNotUsable` if context write flags are set pub fn check_cpi_context(cpi_context: &Option<CompressedCpiContext>) -> Result<()> {
♻️ Duplicate comments (9)
programs/compressed-token/program/src/transfer2/compression/ctoken/compress_or_decompress_ctokens.rs (2)
50-54: Redundant uninitialized account check (previously flagged).This check duplicates the validation already performed by
CToken::zero_copy_at_mut_checkedat line 48. As noted in the previous review, this defensive double-check adds unnecessary code.
67-67: Magic number for frozen state (previously flagged).Lines 67 and 211 use the literal
2to check and set the frozen account state. As noted in the previous review, defining constants likeconst ACCOUNT_STATE_FROZEN: u8 = 2;would improve readability and maintainability.Also applies to: 211-211
programs/compressed-token/program/tests/token_input.rs (1)
115-132: Test correctly adapts to the expanded API signature.The
mint_cacheinitialization withMintExtensionChecks::default()for mint index 0 is appropriate since this test focuses on basic input account creation, not extension-specific behavior. The unified call path with theis_frozenparameter simplifies the test structure.As noted in previous review, the test always passes
tlv_data: None, so TLV population paths inZTokenDataMut::setremain unexercised. Consider adding a dedicated test case withSome(tlv_data)containingCompressedOnlyextension data to cover that code path.programs/compressed-token/program/src/shared/token_input.rs (1)
95-108: Add explanatory comment for TLV extension filtering.The conversion correctly filters to only
CompressedOnlyextensions since other variants (Pausable, PermanentDelegate, TransferHook) are marker extensions referencing mint configuration rather than account-specific state data. A brief comment would help future readers understand this intentional filtering.🔎 Suggested comment addition
+ // Only CompressedOnly carries account-specific state data (delegated_amount, + // withheld_transfer_fee); other TLV variants are mint-level marker extensions. let tlv: Option<Vec<ExtensionStruct>> = tlv_data.map(|exts| { exts.iter() .filter_map(|ext| match ext {programs/compressed-token/program/src/transfer2/check_extensions.rs (2)
24-28: Consider extracting version 3 as a named constant.The magic number
3would be clearer as a named constant likeVERSION_SHA_FLATto improve readability and reduce the risk of inconsistencies if this version check appears elsewhere.🔎 Suggested improvement
+const VERSION_SHA_FLAT: u8 = 3; + // Validate TLV is only used with version 3 (ShaFlat) -if tlv_data.is_some_and(|v| !v.is_empty() && version != 3) { +if tlv_data.is_some_and(|v| !v.is_empty() && version != VERSION_SHA_FLAT) {
98-134: Uncomment Compress mode validation for restricted extensions.The documentation at line 100 clearly states "Compress: NOT allowed (mints with restricted extensions must not be compressed)", but the validation code at lines 125-128 is commented out. This silently allows restricted mints to compress when they shouldn't be allowed.
The
CompressAndClosemode correctly validates that outputs haveCompressedOnlyextension (lines 104-123), but theCompressarm does nothing. This inconsistency creates a security gap where restricted mints could be compressed without the required extension.🔎 Recommended fix
ZCompressionMode::Compress => { - // msg!("Mints with restricted extensions cannot be compressed"); - // return Err(ErrorCode::MintHasRestrictedExtensions.into()); + msg!("Mints with restricted extensions cannot be compressed"); + return Err(ErrorCode::MintHasRestrictedExtensions.into()); }programs/compressed-token/program/src/shared/token_output.rs (1)
44-54: TLV config extraction intentionally filters to CompressedOnly.This filtering is correct—only
CompressedOnlycarries account-specific state data (delegated_amount, withheld_transfer_fee). Other TLV variants like Pausable, PermanentDelegate, and TransferHook are mint-level marker extensions that don't need to be stored in individual account TLV.A brief inline comment explaining this would improve clarity for future maintainers.
programs/compressed-token/program/src/extensions/check_mint_extensions.rs (2)
121-131: Silent error swallowing inget_extension_types()should be addressed.As flagged in a past review,
unwrap_or_default()at line 123 silently treats extension type enumeration failures as "no extensions". This could allow a malformed mint to bypass restricted extension validation.Consider propagating the error:
🔎 Suggested fix:
- let extension_types = mint_state.get_extension_types().unwrap_or_default(); + let extension_types = mint_state.get_extension_types().map_err(|e| { + msg!("Failed to get extension types: {:?}", e); + ProgramError::InvalidAccountData + })?;
205-229: Sameunwrap_or_default()concern inhas_mint_extensions.Line 207 has the same pattern as line 123 — silently treating
get_extension_types()failures as empty. For account creation, this could result in undersized accounts if extension types cannot be determined.🔎 Suggested fix:
- let extension_types = mint_state.get_extension_types().unwrap_or_default(); + let extension_types = mint_state.get_extension_types().map_err(|e| { + msg!("Failed to get extension types: {:?}", e); + ProgramError::InvalidAccountData + })?;
.../compressed-token/program/src/transfer2/compression/ctoken/compress_or_decompress_ctokens.rs
Show resolved
Hide resolved
| version, | ||
| tlv_slice, | ||
| false, // Not frozen in tests | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test passes hardcoded is_frozen: false but generates random frozen flags in TLV.
At line 124, is_frozen is randomly set to 20% true in the TLV data, but at line 180, is_frozen: false is always passed to set_output_compressed_account. This means:
- The TLV data contains a potentially non-zero
is_frozenfield - But the function is told the account is never frozen
- This creates a mismatch between the TLV state and the actual frozen state
The test will still pass because the expected data is built with the same hardcoded false, but this doesn't test the frozen state handling path properly.
🔎 Suggested fix to properly test frozen state
+ // Track frozen state from TLV data
+ let mut is_frozen_flags = Vec::new();
+
for i in 0..num_outputs {
if tlv_flags[i] {
+ let frozen = rng.gen_bool(0.2);
+ is_frozen_flags.push(frozen);
let ext = ExtensionInstructionData::CompressedOnly(
CompressedOnlyExtensionInstructionData {
delegated_amount: tlv_delegated_amounts[i],
withheld_transfer_fee: tlv_withheld_fees[i],
- is_frozen: rng.gen_bool(0.2), // 20% chance of frozen
+ is_frozen: frozen,
},
);
// ...
} else {
+ is_frozen_flags.push(false);
// ...
}
}
// Later in the loop:
+ let is_frozen = is_frozen_flags[index];
set_output_compressed_account(
// ...
- false, // Not frozen in tests
+ is_frozen,
)Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In programs/compressed-token/program/tests/token_output.rs around lines 124 and
178–181, the test randomly sets the TLV is_frozen flag (~20% true) at line 124
but then always passes a hardcoded false to set_output_compressed_account at
lines 178–181, creating a mismatch; fix by capturing the generated is_frozen
value into a local variable when building the TLV and pass that variable (not
false) into set_output_compressed_account, and update any expected/verification
data to use the same variable so the test exercises both frozen and unfrozen
paths correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
forester/tests/e2e_test.rs (1)
264-277: Indexer disabled but test requires it to run.With
enable_indexer: false, the test validator skips indexer startup via--skip-indexer, yet the test immediately makes indexer calls that will fail:
wait_for_indexer()at lines 1006, 1121, 1163, 1333get_compressed_token_accounts_by_owner()at line 1010get_validity_proof()at lines 1039, 1167, 1337get_compressed_accounts_by_owner()at line 1125The RPC is configured via
LightClientConfig::local()withindexer_url: "http://127.0.0.1:8784", but the indexer process is never started. All indexer method calls use.unwrap(), so test execution will panic on connection errors.Per the coding guidelines, local validator mode should automatically spawn the indexer. Change
enable_indexer: falsetoenable_indexer: trueand consider adding explicit indexer startup likespawn_prover()if needed.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (2)
forester/tests/e2e_test.rs(1 hunks)sdk-libs/token-client/src/actions/create_compressible_token_account.rs(2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
forester/**/*e2e*.rs
📄 CodeRabbit inference engine (forester/README.md)
forester/**/*e2e*.rs: Test environment variables should useTEST_MODEto specify 'local' or 'devnet' mode (default: devnet)
Test feature flagsTEST_V1_STATE,TEST_V2_STATE,TEST_V1_ADDRESS, andTEST_V2_ADDRESSshould default totrueand control which test scenarios run
Devnet mode tests requirePHOTON_RPC_URL,PHOTON_WSS_RPC_URL,PHOTON_INDEXER_URL,PHOTON_PROVER_URL, andPHOTON_API_KEYenvironment variables
Local validator mode tests should automatically spawn a local validator, start a local prover service, and use predefined local URLs (localhost:8899 for RPC, localhost:8784 for indexer)
Tests should automatically skip minting tokens, executing transactions, and root verification for disabled test types
Files:
forester/tests/e2e_test.rs
sdk-libs/**/*.rs
📄 CodeRabbit inference engine (CLAUDE.md)
Unit tests in sdk-libs must not depend on light-test-utils; integration tests must be located in sdk-tests/
Files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
🧠 Learnings (34)
📓 Common learnings
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Use compressible token account extensions that allow accounts to be compressed back into compressed state with rent payment mechanisms
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/RENT.md:0-0
Timestamp: 2025-11-24T18:00:36.663Z
Learning: Applies to program-libs/compressible/docs/**/*rent*.rs : Implement rent constants with values: BASE_RENT = 1,220 lamports, RENT_PER_BYTE = 10 lamports, SLOTS_PER_EPOCH = 432,000.
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: When working with compressible extension (rent management), must read and comply with documentation in program-libs/compressible/docs/ including RENT.md, CONFIG_ACCOUNT.md, and SOLANA_RENT.md
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/transfer2/*.rs : Transfer2 instruction (discriminator: 101) supports batch operations including Compress, Decompress, and CompressAndClose with multi-mint support and sum checks
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Compressible extension rent management must follow the specifications in program-libs/compressible/docs/RENT.md including: rent authority can only compress when is_compressible() returns true, lamport distribution on close (rent → rent_sponsor, unutilized → destination), and compression incentive for foresters
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/RENT.md:0-0
Timestamp: 2025-11-24T18:00:36.663Z
Learning: Applies to program-libs/compressible/docs/**/*rent*.rs : RentConfig struct must be exactly 8 bytes and contain: base_rent (u16), compression_cost (u16), lamports_per_byte_per_epoch (u8), and 3 bytes of padding.
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: The codebase provides rent calculation algorithms and account structure definitions used across light-compressed-token, light-registry, compressed-token-sdk, and related programs
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: Applies to program-libs/compressible/src/rent.rs : Implement rent calculation functions including `rent_curve_per_epoch`, `calculate_rent_and_balance`, `claimable_lamports`, and `calculate_close_lamports` in rent.rs
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/SOLANA_RENT.md:0-0
Timestamp: 2025-11-24T18:00:48.449Z
Learning: Applies to program-libs/compressible/docs/**/*.rs : Light Protocol rent is calculated per epoch as: 1220 minimum + (data_bytes * 10) lamports per epoch
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/RENT.md:0-0
Timestamp: 2025-11-24T18:00:36.663Z
Learning: Applies to program-libs/compressible/docs/**/*rent*.rs : Implement `calculate_close_lamports` function to return (u64, u64) tuple splitting lamports between rent recipient and user, with rent recipient receiving completed epochs and user receiving partial epoch remainder.
📚 Learning: 2025-11-24T17:53:53.971Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: forester/README.md:0-0
Timestamp: 2025-11-24T17:53:53.971Z
Learning: Applies to forester/**/*e2e*.rs : Local validator mode tests should automatically spawn a local validator, start a local prover service, and use predefined local URLs (localhost:8899 for RPC, localhost:8784 for indexer)
Applied to files:
forester/tests/e2e_test.rs
📚 Learning: 2025-11-24T17:53:53.971Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: forester/README.md:0-0
Timestamp: 2025-11-24T17:53:53.971Z
Learning: Applies to forester/**/*e2e*.rs : Test feature flags `TEST_V1_STATE`, `TEST_V2_STATE`, `TEST_V1_ADDRESS`, and `TEST_V2_ADDRESS` should default to `true` and control which test scenarios run
Applied to files:
forester/tests/e2e_test.rs
📚 Learning: 2025-11-24T17:53:53.971Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: forester/README.md:0-0
Timestamp: 2025-11-24T17:53:53.971Z
Learning: Applies to forester/**/*e2e*.rs : Tests should automatically skip minting tokens, executing transactions, and root verification for disabled test types
Applied to files:
forester/tests/e2e_test.rs
📚 Learning: 2025-11-24T17:53:53.971Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: forester/README.md:0-0
Timestamp: 2025-11-24T17:53:53.971Z
Learning: Applies to forester/**/*e2e*.rs : Test environment variables should use `TEST_MODE` to specify 'local' or 'devnet' mode (default: devnet)
Applied to files:
forester/tests/e2e_test.rs
📚 Learning: 2025-11-24T17:53:53.971Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: forester/README.md:0-0
Timestamp: 2025-11-24T17:53:53.971Z
Learning: Applies to forester/**/*e2e*.rs : Devnet mode tests require `PHOTON_RPC_URL`, `PHOTON_WSS_RPC_URL`, `PHOTON_INDEXER_URL`, `PHOTON_PROVER_URL`, and `PHOTON_API_KEY` environment variables
Applied to files:
forester/tests/e2e_test.rs
📚 Learning: 2025-11-24T17:53:53.971Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: forester/README.md:0-0
Timestamp: 2025-11-24T17:53:53.971Z
Learning: Applies to forester/src/main.rs : Required configuration must include: `--rpc-url`, `--ws-rpc-url`, `--indexer-url`, `--prover-url`, `--payer`, and `--derivation` parameters
Applied to files:
forester/tests/e2e_test.rs
📚 Learning: 2025-12-06T00:50:31.314Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-06T00:50:31.314Z
Learning: Forester end-to-end tests must run with TEST_MODE=local environment variable
Applied to files:
forester/tests/e2e_test.rs
📚 Learning: 2025-11-24T17:53:40.537Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: cli/README.md:0-0
Timestamp: 2025-11-24T17:53:40.537Z
Learning: The CLI should start the Light test validator as a background process with Solana test-validator, Light system programs, prover server, and Photon indexer
Applied to files:
forester/tests/e2e_test.rs
📚 Learning: 2025-12-07T03:17:42.217Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-12-07T03:17:42.217Z
Learning: Applies to program-libs/account-checks/**/account_info/test_account_info.rs : Provide mock TestAccountInfo implementation (feature: `test-only`) for unit testing account validation logic
Applied to files:
forester/tests/e2e_test.rs
📚 Learning: 2025-12-07T03:17:28.803Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Use compressible token account extensions that allow accounts to be compressed back into compressed state with rent payment mechanisms
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-11-24T17:55:17.323Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-libs/macros/src/compressible/README.md:0-0
Timestamp: 2025-11-24T17:55:17.323Z
Learning: Applies to sdk-libs/macros/src/compressible/**/decompress_context.rs : Decompression trait implementation (`DecompressContext`) with account accessors, PDA/token separation logic, and token processing delegation should be in `decompress_context.rs`
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-11-24T17:55:17.323Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-libs/macros/src/compressible/README.md:0-0
Timestamp: 2025-11-24T17:55:17.323Z
Learning: Applies to sdk-libs/macros/src/compressible/**/variant_enum.rs : Account variant enum (`CompressedAccountVariant`) generation and `CompressedAccountData` wrapper struct should be implemented in `variant_enum.rs`
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-11-24T18:00:48.449Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/SOLANA_RENT.md:0-0
Timestamp: 2025-11-24T18:00:48.449Z
Learning: Applies to program-libs/compressible/docs/**/*.rs : Implement account compressibility states in the following order: Funded (rent for current + 1 epoch) → Compressible (lacks rent for current + 1 epoch) → Claimable (funded but past epochs unclaimed)
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-11-24T17:55:17.323Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-libs/macros/src/compressible/README.md:0-0
Timestamp: 2025-11-24T17:55:17.323Z
Learning: Applies to sdk-libs/macros/src/compressible/**/instructions.rs : Compress/decompress instruction handlers and context struct generation should be implemented in `instructions.rs`, with compress using PDA-only and decompress supporting full PDA + ctoken
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-12-07T18:09:57.240Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/CONFIG_ACCOUNT.md:0-0
Timestamp: 2025-12-07T18:09:57.240Z
Learning: Applies to program-libs/compressible/docs/**/*.rs : CompressibleConfig account state in Solana programs must validate active state using `validate_active()` method before allowing new compressible token account creation
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-12-06T00:49:21.983Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: Applies to program-libs/compressible/src/config.rs : Implement state validation methods (`validate_active`, `validate_not_inactive`) on CompressibleConfig account structure
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-11-24T18:02:15.670Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/registry/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:02:15.670Z
Learning: Applies to programs/registry/src/account_compression_cpi/mod.rs : Export new wrapper modules in `account_compression_cpi/mod.rs` using `pub mod new_operation;` and `pub use new_operation::*;`, then import in `lib.rs`
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-11-24T17:55:17.323Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-libs/macros/src/compressible/README.md:0-0
Timestamp: 2025-11-24T17:55:17.323Z
Learning: Applies to sdk-libs/macros/src/compressible/**/traits.rs : Core trait implementations (`HasCompressionInfo`, `CompressAs`, `Compressible`) should be defined in `traits.rs`
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/close_token_account.rs : Close token account instruction must return rent exemption to rent recipient if compressible and remaining lamports to destination account
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/docs/ACCOUNTS.md : All account documentation must include: description, discriminator, state layout, serialization example, hashing (for compressed accounts only), derivation (for PDAs only), and associated instructions (create, update, close)
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-11-24T18:00:48.449Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/SOLANA_RENT.md:0-0
Timestamp: 2025-11-24T18:00:48.449Z
Learning: Applies to program-libs/compressible/docs/**/*.rs : Light Protocol accounts must satisfy both Solana rent exemption and Light Protocol rent requirements, plus an 11,000 lamport compression incentive during creation
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-12-07T18:09:57.240Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/CONFIG_ACCOUNT.md:0-0
Timestamp: 2025-12-07T18:09:57.240Z
Learning: Applies to program-libs/compressible/docs/**/*.rs : CompressibleConfig account size must be exactly 256 bytes including the 8-byte discriminator to maintain compatibility with Light Protocol Solana programs
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-12-06T00:50:17.433Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: DOCS.md:0-0
Timestamp: 2025-12-06T00:50:17.433Z
Learning: Applies to **/docs/*accounts* : Account documentation must include: description (what account represents and role), state layout (path to struct and field descriptions), associated instructions (list with discriminators), discriminator (8-byte value if applicable), size (in bytes), ownership (expected owner), serialization (zero-copy for programs and Borsh for clients with code snippets). For PDAs include derivation (seeds) and bump (stored or derived). For compressed accounts include version, hashing (method and discriminator), and data layout.
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-11-24T18:01:54.689Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/docs/instructions/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:01:54.689Z
Learning: Applies to programs/compressed-token/program/docs/instructions/instructions/CREATE_TOKEN_ACCOUNT.md : Create Token Account Instructions documentation must cover creation of regular and associated ctoken accounts
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-11-24T17:54:38.537Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/README.md:0-0
Timestamp: 2025-11-24T17:54:38.537Z
Learning: Ensure compressed token program documentation is maintained at the official documentation site (https://zkcompression.com)
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-11-24T17:54:38.537Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/README.md:0-0
Timestamp: 2025-11-24T17:54:38.537Z
Learning: Implement compressed token program interfaces for third-party token creation and usage on Solana using ZK Compression
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/create_token_account.rs : Create token account instructions (CreateTokenAccount, CreateAssociatedCTokenAccount, CreateAssociatedTokenAccountIdempotent) require ACTIVE config validation only
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/ctoken_transfer.rs : CTokenTransfer instruction (discriminator: 3) must implement SPL-compatible transfers between decompressed accounts
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-11-24T17:54:33.614Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/anchor/README.md:0-0
Timestamp: 2025-11-24T17:54:33.614Z
Learning: Implement the Compressed Token Program interface for creating and using compressed tokens on Solana with ZK Compression
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-11-24T17:57:39.230Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/batched-merkle-tree/docs/QUEUE_ACCOUNT.md:0-0
Timestamp: 2025-11-24T17:57:39.230Z
Learning: Applies to program-libs/batched-merkle-tree/docs/src/queue.rs : Validate account ownership by Light account compression program using `check_owner` from `light-account-checks` when deserializing `BatchedQueueAccount` with `output_from_account_info`
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-12-07T03:17:28.803Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Implement all 8 compressed token instructions: create_cmint, mint_to_ctoken, create_token_account_invoke, create_token_account_invoke_signed, create_ata_invoke, create_ata_invoke_signed, transfer_interface_invoke, and transfer_interface_invoke_signed
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: All compressed token account implementations must reference and comply with the account layout specifications in programs/compressed-token/program/docs/ACCOUNTS.md
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
📚 Learning: 2025-11-24T18:02:15.670Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/registry/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:02:15.670Z
Learning: Applies to programs/registry/src/account_compression_cpi/*.rs : Create wrapper instruction module at `src/account_compression_cpi/new_operation.rs` with `NewOperationContext` struct defining required accounts
Applied to files:
sdk-libs/token-client/src/actions/create_compressible_token_account.rs
🧬 Code graph analysis (1)
sdk-libs/token-client/src/actions/create_compressible_token_account.rs (3)
programs/compressed-token/anchor/src/instructions/create_token_pool.rs (1)
ExtensionType(58-58)program-libs/ctoken-interface/src/state/compressed_token/token_data.rs (1)
state(53-55)forester/tests/e2e_test.rs (2)
rpc(584-585)rpc(604-605)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (21)
- GitHub Check: programs (system-cpi-test-v2-functional-account-infos, ["cargo-test-sbf -p system-cpi-v2-test -- ...
- GitHub Check: lint
- GitHub Check: cli-v1
- GitHub Check: programs (compressed-token-batched-tree, ["cargo-test-sbf -p compressed-token-test -- test_transf...
- GitHub Check: programs (account-compression-and-registry, ["cargo-test-sbf -p account-compression-test", "cargo...
- GitHub Check: programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test", "cargo test -p light-system-prog...
- GitHub Check: programs (light-system-program-address, ["cargo-test-sbf -p system-test -- test_with_address", "c...
- GitHub Check: programs (light-system-program-compression, ["cargo-test-sbf -p system-test -- test_with_compress...
- GitHub Check: programs (system-cpi-test-v2-functional-read-only, ["cargo-test-sbf -p system-cpi-v2-test -- func...
- GitHub Check: programs (compressed-token-and-e2e, ["cargo test -p light-compressed-token", "cargo-test-sbf -p c...
- GitHub Check: Forester e2e test
- GitHub Check: Test program-libs-fast
- GitHub Check: Test program-libs-slow
- GitHub Check: Test batched-merkle-tree-simulate
- GitHub Check: system-programs (token test, ["cargo-test-sbf -p sdk-token-test"])
- GitHub Check: system-programs (native, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v1-native-t...
- GitHub Check: system-programs (anchor & pinocchio, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -p sdk...
- GitHub Check: cli-v2
- GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client light-ctoke...
- GitHub Check: stateless-js-v1
- GitHub Check: stateless-js-v2
🔇 Additional comments (2)
sdk-libs/token-client/src/actions/create_compressible_token_account.rs (2)
10-16: Solid selection of restricted extensions.These four extension types make sense for requiring
compression_onlymode—each represents a mint-level control mechanism that free decompression would bypass:
- Pausable: Decompression would circumvent pause state
- PermanentDelegate: Bypass delegate authority
- TransferFeeConfig: Skip transfer fees
- TransferHook: Evade custom transfer logic
Consider adding a brief inline comment noting why these specific four were chosen, and whether future extensions (e.g.,
ConfidentialTransferMint) might need similar treatment as the Token 2022 extension set evolves.
86-94: Good improvement: dynamiccompression_onlybased on mint extensions.The dynamic determination of
compression_onlyfrom mint extension data is a significant improvement over hardcoding. This ensures accounts are created with appropriate restrictions based on the actual mint configuration.The flow now correctly:
- Inspects the mint for restricted extensions
- Sets
compression_onlyaccordingly inCompressibleParams- Passes through to
with_compressible()for proper account creationThis addresses the concern from the previous review about documenting the
compression_only: truedesign—the behavior is now self-documenting through code.
| /// Check if a mint has any restricted extensions that require compression_only mode. | ||
| fn mint_has_restricted_extensions(mint_data: &[u8]) -> bool { | ||
| let Ok(mint_state) = StateWithExtensions::<Mint>::unpack(mint_data) else { | ||
| return false; | ||
| }; | ||
| let Ok(extension_types) = mint_state.get_extension_types() else { | ||
| return false; | ||
| }; | ||
| extension_types | ||
| .iter() | ||
| .any(|ext| RESTRICTED_EXTENSIONS.contains(ext)) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Silent failure returns false, which may not be the safest default.
When StateWithExtensions::<Mint>::unpack or get_extension_types() fails, this function returns false (meaning "no restricted extensions"). If the mint account data is malformed or not actually a Token-2022 mint, this silently treats it as unrestricted.
For a Token-2022 mint with corrupted data, defaulting to compression_only = false could create an account with incorrect permissions. Consider whether this should:
- Return
true(fail-safe, requiring compression_only when uncertain), or - Return a
Resultso the caller can decide how to handle malformed data
If you intentionally want this behavior (e.g., to gracefully handle legacy SPL mints), a comment clarifying that rationale would help future maintainers.
🔎 Option: Fail-safe approach
fn mint_has_restricted_extensions(mint_data: &[u8]) -> bool {
let Ok(mint_state) = StateWithExtensions::<Mint>::unpack(mint_data) else {
- return false;
+ // Cannot parse mint data; assume unrestricted for legacy SPL mints
+ // Token-2022 mints should always unpack successfully
+ return false; // or return true for fail-safe
};
let Ok(extension_types) = mint_state.get_extension_types() else {
- return false;
+ // Extension types unavailable; assume no restrictions
+ return false;
};| // Check if mint has restricted extensions that require compression_only mode | ||
| let compression_only = match rpc.get_account(mint).await { | ||
| Ok(Some(mint_account)) => mint_has_restricted_extensions(&mint_account.data), | ||
| _ => false, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RPC failure silently defaults to compression_only = false—potential correctness risk.
If rpc.get_account(mint) fails due to network issues or returns None (mint doesn't exist), the code proceeds with compression_only = false. This has two concerning scenarios:
- Transient network failure: A real Token-2022 mint with
TransferFeeConfigwould get a non-compression-only account created due to RPC hiccup - Non-existent mint: The function continues and will fail later when building the instruction, but not with a clear error about the mint
Consider propagating the error or at minimum logging the failure:
🔎 Proposed fix: Propagate RPC errors
- // Check if mint has restricted extensions that require compression_only mode
- let compression_only = match rpc.get_account(mint).await {
- Ok(Some(mint_account)) => mint_has_restricted_extensions(&mint_account.data),
- _ => false,
- };
+ // Check if mint has restricted extensions that require compression_only mode
+ let mint_account = rpc
+ .get_account(mint)
+ .await?
+ .ok_or_else(|| RpcError::CustomError(format!("Mint account not found: {}", mint)))?;
+ let compression_only = mint_has_restricted_extensions(&mint_account.data);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Check if mint has restricted extensions that require compression_only mode | |
| let compression_only = match rpc.get_account(mint).await { | |
| Ok(Some(mint_account)) => mint_has_restricted_extensions(&mint_account.data), | |
| _ => false, | |
| }; | |
| // Check if mint has restricted extensions that require compression_only mode | |
| let mint_account = rpc | |
| .get_account(mint) | |
| .await? | |
| .ok_or_else(|| RpcError::CustomError(format!("Mint account not found: {}", mint)))?; | |
| let compression_only = mint_has_restricted_extensions(&mint_account.data); |
🤖 Prompt for AI Agents
In sdk-libs/token-client/src/actions/create_compressible_token_account.rs around
lines 80 to 84, the current match silently treats RPC errors or missing mint as
compression_only = false; change this so RPC failures are propagated and a
missing mint returns a clear error instead of defaulting to false. Specifically,
await rpc.get_account(mint) and if it returns an Err, return Err(...) from this
function with context (e.g., "failed to fetch mint account") so callers surface
network issues; if it returns Ok(None), return an explicit error like "mint
account not found"; only set compression_only when you have
Ok(Some(mint_account)) and compute
mint_has_restricted_extensions(&mint_account.data).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
sdk-libs/ctoken-sdk/src/ctoken/mod.rs (1)
87-109: Re-exports are consistent with the module's public API design.The glob re-exports (
pub use approve::*, etc.) align with the existing pattern used for other operations likeburn,close, andcreate. This maintains a uniform public API surface.Consider updating the module-level documentation (lines 1-66) to include the new operations (Approve, Revoke, Freeze, Thaw) under appropriate sections for discoverability.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (24)
program-tests/compressed-token-test/tests/ctoken/approve_revoke.rsis excluded by none and included by noneprogram-tests/compressed-token-test/tests/ctoken/freeze_thaw.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/src/approve.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/src/burn.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/src/create_ata2.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/src/ctoken_mint_to.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/src/decompress_cmint.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/src/freeze.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/src/lib.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/src/revoke.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/src/thaw.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/tests/scenario_cmint.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/tests/shared.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/tests/test_approve_revoke.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/tests/test_burn.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/tests/test_close.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/tests/test_create_ata.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/tests/test_create_ata_v2.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/tests/test_create_token_account.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/tests/test_ctoken_mint_to.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/tests/test_decompress_cmint.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/tests/test_freeze_thaw.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/tests/test_mint_to_ctoken.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/tests/test_transfer.rsis excluded by none and included by none
📒 Files selected for processing (9)
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs(3 hunks)sdk-libs/ctoken-sdk/src/ctoken/approve.rs(1 hunks)sdk-libs/ctoken-sdk/src/ctoken/create_cmint.rs(1 hunks)sdk-libs/ctoken-sdk/src/ctoken/decompress_cmint.rs(1 hunks)sdk-libs/ctoken-sdk/src/ctoken/freeze.rs(1 hunks)sdk-libs/ctoken-sdk/src/ctoken/mint_to.rs(1 hunks)sdk-libs/ctoken-sdk/src/ctoken/mod.rs(4 hunks)sdk-libs/ctoken-sdk/src/ctoken/revoke.rs(1 hunks)sdk-libs/ctoken-sdk/src/ctoken/thaw.rs(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
sdk-libs/**/*.rs
📄 CodeRabbit inference engine (CLAUDE.md)
Unit tests in sdk-libs must not depend on light-test-utils; integration tests must be located in sdk-tests/
Files:
sdk-libs/ctoken-sdk/src/ctoken/create_cmint.rssdk-libs/ctoken-sdk/src/ctoken/mint_to.rssdk-libs/ctoken-sdk/src/ctoken/approve.rssdk-libs/ctoken-sdk/src/ctoken/revoke.rssdk-libs/ctoken-sdk/src/ctoken/freeze.rssdk-libs/ctoken-sdk/src/ctoken/thaw.rssdk-libs/ctoken-sdk/src/ctoken/mod.rssdk-libs/ctoken-sdk/src/ctoken/decompress_cmint.rs
programs/**/*.rs
📄 CodeRabbit inference engine (CLAUDE.md)
Unit tests in programs must not depend on light-test-utils; integration tests must be located in program-tests/
Files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
🧠 Learnings (66)
📓 Common learnings
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: When working with compressible extension (rent management), must read and comply with documentation in program-libs/compressible/docs/ including RENT.md, CONFIG_ACCOUNT.md, and SOLANA_RENT.md
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Compressible extension rent management must follow the specifications in program-libs/compressible/docs/RENT.md including: rent authority can only compress when is_compressible() returns true, lamport distribution on close (rent → rent_sponsor, unutilized → destination), and compression incentive for foresters
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/RENT.md:0-0
Timestamp: 2025-11-24T18:00:36.663Z
Learning: Applies to program-libs/compressible/docs/**/*rent*.rs : Implement rent constants with values: BASE_RENT = 1,220 lamports, RENT_PER_BYTE = 10 lamports, SLOTS_PER_EPOCH = 432,000.
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Use compressible token account extensions that allow accounts to be compressed back into compressed state with rent payment mechanisms
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: The codebase provides rent calculation algorithms and account structure definitions used across light-compressed-token, light-registry, compressed-token-sdk, and related programs
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: Applies to program-libs/compressible/src/rent.rs : Implement rent calculation functions including `rent_curve_per_epoch`, `calculate_rent_and_balance`, `claimable_lamports`, and `calculate_close_lamports` in rent.rs
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/RENT.md:0-0
Timestamp: 2025-11-24T18:00:36.663Z
Learning: Applies to program-libs/compressible/docs/**/*rent*.rs : RentConfig struct must be exactly 8 bytes and contain: base_rent (u16), compression_cost (u16), lamports_per_byte_per_epoch (u8), and 3 bytes of padding.
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/SOLANA_RENT.md:0-0
Timestamp: 2025-11-24T18:00:48.449Z
Learning: Applies to program-libs/compressible/docs/**/*.rs : Light Protocol rent is calculated per epoch as: 1220 minimum + (data_bytes * 10) lamports per epoch
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/transfer2/*.rs : Transfer2 instruction (discriminator: 101) supports batch operations including Compress, Decompress, and CompressAndClose with multi-mint support and sum checks
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/RENT.md:0-0
Timestamp: 2025-11-24T18:00:36.663Z
Learning: Applies to program-libs/compressible/docs/**/*rent*.rs : Implement `rent_curve_per_epoch` function with formula: base_rent + (num_bytes * lamports_per_byte_per_epoch).
📚 Learning: 2025-11-24T17:55:17.323Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-libs/macros/src/compressible/README.md:0-0
Timestamp: 2025-11-24T17:55:17.323Z
Learning: Applies to sdk-libs/macros/src/compressible/**/instructions.rs : Compress/decompress instruction handlers and context struct generation should be implemented in `instructions.rs`, with compress using PDA-only and decompress supporting full PDA + ctoken
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/create_cmint.rssdk-libs/ctoken-sdk/src/ctoken/mint_to.rssdk-libs/ctoken-sdk/src/ctoken/freeze.rssdk-libs/ctoken-sdk/src/ctoken/mod.rssdk-libs/ctoken-sdk/src/ctoken/decompress_cmint.rs
📚 Learning: 2025-11-24T17:55:17.323Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-libs/macros/src/compressible/README.md:0-0
Timestamp: 2025-11-24T17:55:17.323Z
Learning: Applies to sdk-libs/macros/src/compressible/**/decompress_context.rs : Decompression trait implementation (`DecompressContext`) with account accessors, PDA/token separation logic, and token processing delegation should be in `decompress_context.rs`
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/create_cmint.rssdk-libs/ctoken-sdk/src/ctoken/mint_to.rssdk-libs/ctoken-sdk/src/ctoken/mod.rssdk-libs/ctoken-sdk/src/ctoken/decompress_cmint.rs
📚 Learning: 2025-12-06T00:49:21.983Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: Applies to program-libs/compressible/src/config.rs : Implement default initialization for CToken V1 config in CompressibleConfig structure
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/create_cmint.rssdk-libs/ctoken-sdk/src/ctoken/mint_to.rssdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-11-24T18:01:54.689Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/docs/instructions/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:01:54.689Z
Learning: Applies to programs/compressed-token/program/docs/instructions/instructions/MINT_ACTION.md : MintAction documentation must cover batch instruction for compressed mint management supporting 9 actions: CreateCompressedMint, MintTo, UpdateMintAuthority, UpdateFreezeAuthority, CreateSplMint, MintToCToken, UpdateMetadataField, UpdateMetadataAuthority, RemoveMetadataKey
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/create_cmint.rssdk-libs/ctoken-sdk/src/ctoken/mint_to.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/mint_action/actions/decompress_mint.rssdk-libs/ctoken-sdk/src/ctoken/decompress_cmint.rs
📚 Learning: 2025-11-24T18:02:15.670Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/registry/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:02:15.670Z
Learning: Applies to programs/registry/src/account_compression_cpi/*.rs : Create wrapper instruction module at `src/account_compression_cpi/new_operation.rs` with `NewOperationContext` struct defining required accounts
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/create_cmint.rssdk-libs/ctoken-sdk/src/ctoken/mint_to.rssdk-libs/ctoken-sdk/src/ctoken/approve.rssdk-libs/ctoken-sdk/src/ctoken/revoke.rssdk-libs/ctoken-sdk/src/ctoken/freeze.rssdk-libs/ctoken-sdk/src/ctoken/thaw.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/mint_action/actions/decompress_mint.rssdk-libs/ctoken-sdk/src/ctoken/decompress_cmint.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/mint_action/*.rs : MintAction instruction (discriminator: 103) must support 9 action types: CreateCompressedMint, MintTo, UpdateMintAuthority, UpdateFreezeAuthority, CreateSplMint, MintToCToken, UpdateMetadataField, UpdateMetadataAuthority, RemoveMetadataKey
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/create_cmint.rssdk-libs/ctoken-sdk/src/ctoken/mint_to.rssdk-libs/ctoken-sdk/src/ctoken/freeze.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/mint_action/actions/decompress_mint.rssdk-libs/ctoken-sdk/src/ctoken/decompress_cmint.rs
📚 Learning: 2025-11-24T18:02:15.670Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/registry/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:02:15.670Z
Learning: Applies to programs/registry/src/account_compression_cpi/mod.rs : Export new wrapper modules in `account_compression_cpi/mod.rs` using `pub mod new_operation;` and `pub use new_operation::*;`, then import in `lib.rs`
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/create_cmint.rssdk-libs/ctoken-sdk/src/ctoken/approve.rssdk-libs/ctoken-sdk/src/ctoken/revoke.rssdk-libs/ctoken-sdk/src/ctoken/freeze.rssdk-libs/ctoken-sdk/src/ctoken/thaw.rssdk-libs/ctoken-sdk/src/ctoken/mod.rssdk-libs/ctoken-sdk/src/ctoken/decompress_cmint.rs
📚 Learning: 2025-11-24T17:55:17.323Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-libs/macros/src/compressible/README.md:0-0
Timestamp: 2025-11-24T17:55:17.323Z
Learning: Applies to sdk-libs/macros/src/compressible/**/seed_providers.rs : PDA and CToken seed provider implementations with client-side seed functions should be in `seed_providers.rs`
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/create_cmint.rssdk-libs/ctoken-sdk/src/ctoken/mint_to.rssdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-11-24T17:55:17.323Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-libs/macros/src/compressible/README.md:0-0
Timestamp: 2025-11-24T17:55:17.323Z
Learning: Applies to sdk-libs/macros/src/compressible/**/variant_enum.rs : Account variant enum (`CompressedAccountVariant`) generation and `CompressedAccountData` wrapper struct should be implemented in `variant_enum.rs`
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/create_cmint.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/mint_action/actions/decompress_mint.rssdk-libs/ctoken-sdk/src/ctoken/decompress_cmint.rs
📚 Learning: 2025-12-06T00:49:21.983Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: Applies to program-libs/compressible/src/error.rs : Error types must use numeric codes in the 19xxx range for CToken-specific errors
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/create_cmint.rssdk-libs/ctoken-sdk/src/ctoken/mint_to.rs
📚 Learning: 2025-12-07T03:17:28.803Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Implement all 8 compressed token instructions: create_cmint, mint_to_ctoken, create_token_account_invoke, create_token_account_invoke_signed, create_ata_invoke, create_ata_invoke_signed, transfer_interface_invoke, and transfer_interface_invoke_signed
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/create_cmint.rssdk-libs/ctoken-sdk/src/ctoken/mint_to.rssdk-libs/ctoken-sdk/src/ctoken/approve.rssdk-libs/ctoken-sdk/src/ctoken/revoke.rssdk-libs/ctoken-sdk/src/ctoken/freeze.rssdk-libs/ctoken-sdk/src/ctoken/thaw.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/mint_action/actions/decompress_mint.rssdk-libs/ctoken-sdk/src/ctoken/decompress_cmint.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/create_token_account.rs : Create token account instructions (CreateTokenAccount, CreateAssociatedCTokenAccount, CreateAssociatedTokenAccountIdempotent) require ACTIVE config validation only
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/create_cmint.rssdk-libs/ctoken-sdk/src/ctoken/mint_to.rssdk-libs/ctoken-sdk/src/ctoken/approve.rssdk-libs/ctoken-sdk/src/ctoken/revoke.rssdk-libs/ctoken-sdk/src/ctoken/freeze.rssdk-libs/ctoken-sdk/src/ctoken/thaw.rs
📚 Learning: 2025-11-24T18:01:30.012Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-tests/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:01:30.012Z
Learning: Run compressed token core tests using `cargo test-sbf -p compressed-token-test --test ctoken`, `--test v1`, `--test mint`, and `--test transfer2`
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/create_cmint.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-11-24T18:01:54.689Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/docs/instructions/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:01:54.689Z
Learning: Applies to programs/compressed-token/program/docs/instructions/instructions/CREATE_TOKEN_ACCOUNT.md : Create Token Account Instructions documentation must cover creation of regular and associated ctoken accounts
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/create_cmint.rssdk-libs/ctoken-sdk/src/ctoken/mint_to.rssdk-libs/ctoken-sdk/src/ctoken/approve.rssdk-libs/ctoken-sdk/src/ctoken/revoke.rssdk-libs/ctoken-sdk/src/ctoken/freeze.rssdk-libs/ctoken-sdk/src/ctoken/thaw.rs
📚 Learning: 2025-12-06T00:49:21.983Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: Applies to program-libs/compressible/src/config.rs : CompressibleConfig account structure must support serialization via Anchor, Pinocchio, and Borsh formats for Light Registry program integration
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/create_cmint.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: All compressed token account implementations must reference and comply with the account layout specifications in programs/compressed-token/program/docs/ACCOUNTS.md
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/create_cmint.rssdk-libs/ctoken-sdk/src/ctoken/mint_to.rs
📚 Learning: 2025-12-06T00:49:57.458Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-token-test/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:57.458Z
Learning: Applies to sdk-tests/sdk-token-test/**/*test.rs : Tests should use light-ctoken-sdk functions from sdk-libs/compressed-token-sdk for testing ctoken instructions
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mint_to.rssdk-libs/ctoken-sdk/src/ctoken/approve.rssdk-libs/ctoken-sdk/src/ctoken/revoke.rssdk-libs/ctoken-sdk/src/ctoken/freeze.rssdk-libs/ctoken-sdk/src/ctoken/thaw.rssdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-12-07T03:17:28.803Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Applies to sdk-tests/sdk-ctoken-test/**/{lib,main}.rs : Use the builder pattern from `light-ctoken-sdk::ctoken` module for CPI operations instead of manual instruction building
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mint_to.rssdk-libs/ctoken-sdk/src/ctoken/approve.rssdk-libs/ctoken-sdk/src/ctoken/revoke.rssdk-libs/ctoken-sdk/src/ctoken/freeze.rssdk-libs/ctoken-sdk/src/ctoken/thaw.rssdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-11-24T17:55:17.323Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-libs/macros/src/compressible/README.md:0-0
Timestamp: 2025-11-24T17:55:17.323Z
Learning: Applies to sdk-libs/macros/src/compressible/**/pack_unpack.rs : Pubkey compression logic and `PackedXxx` struct generation with Pack/Unpack trait implementations should be in `pack_unpack.rs`
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mint_to.rssdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/ctoken_transfer.rs : CTokenTransfer instruction (discriminator: 3) must implement SPL-compatible transfers between decompressed accounts
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mint_to.rssdk-libs/ctoken-sdk/src/ctoken/approve.rssdk-libs/ctoken-sdk/src/ctoken/revoke.rssdk-libs/ctoken-sdk/src/ctoken/freeze.rssdk-libs/ctoken-sdk/src/ctoken/thaw.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/mint_action/actions/decompress_mint.rssdk-libs/ctoken-sdk/src/ctoken/decompress_cmint.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/close_token_account.rs : Close token account instruction must return rent exemption to rent recipient if compressible and remaining lamports to destination account
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mint_to.rssdk-libs/ctoken-sdk/src/ctoken/thaw.rsprograms/compressed-token/program/src/mint_action/actions/decompress_mint.rssdk-libs/ctoken-sdk/src/ctoken/decompress_cmint.rs
📚 Learning: 2025-12-07T03:17:28.803Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Applies to sdk-tests/sdk-ctoken-test/**/{lib,main}.rs : Use `invoke_signed()` method from builder pattern for PDA-signed CPI calls, deriving PDA with `Pubkey::find_program_address()` before invocation
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/approve.rssdk-libs/ctoken-sdk/src/ctoken/revoke.rssdk-libs/ctoken-sdk/src/ctoken/freeze.rssdk-libs/ctoken-sdk/src/ctoken/thaw.rs
📚 Learning: 2025-12-07T03:17:28.803Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Applies to sdk-tests/sdk-ctoken-test/**/{lib,main}.rs : Use `invoke()` method from builder pattern for regular CPI calls where the program acts as authority
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/approve.rssdk-libs/ctoken-sdk/src/ctoken/revoke.rssdk-libs/ctoken-sdk/src/ctoken/freeze.rssdk-libs/ctoken-sdk/src/ctoken/thaw.rs
📚 Learning: 2025-12-07T03:17:28.803Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Applies to sdk-tests/sdk-ctoken-test/**/{lib,main}.rs : Clone AccountInfo structs when building CPI builder pattern account structs to avoid borrow checker issues
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/approve.rssdk-libs/ctoken-sdk/src/ctoken/revoke.rssdk-libs/ctoken-sdk/src/ctoken/freeze.rssdk-libs/ctoken-sdk/src/ctoken/thaw.rssdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-12-07T03:17:28.803Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Applies to sdk-tests/sdk-ctoken-test/src/{lib,main}.rs : Structure Solana BPF projects with src/lib.rs containing the program entrypoint and instruction handlers
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/approve.rs
📚 Learning: 2025-11-24T17:59:54.233Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/PACKED_ACCOUNTS.md:0-0
Timestamp: 2025-11-24T17:59:54.233Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/**/*.rs : Provide descriptive names in ProgramPackedAccounts error messages (e.g., 'token_mint' instead of 'account')
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/approve.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: State and instruction data structures must be defined in the separate 'light-ctoken-interface' crate (program-libs/ctoken-types/) to allow SDKs to import types without pulling in program dependencies
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/approve.rssdk-libs/ctoken-sdk/src/ctoken/revoke.rssdk-libs/ctoken-sdk/src/ctoken/freeze.rssdk-libs/ctoken-sdk/src/ctoken/thaw.rssdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-11-24T18:02:15.670Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/registry/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:02:15.670Z
Learning: Applies to programs/registry/src/account_compression_cpi/*.rs : Implement `process_new_operation()` function in wrapper module to handle PDA signer setup, account mapping, and CPI execution
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/revoke.rssdk-libs/ctoken-sdk/src/ctoken/freeze.rssdk-libs/ctoken-sdk/src/ctoken/thaw.rssdk-libs/ctoken-sdk/src/ctoken/decompress_cmint.rs
📚 Learning: 2025-12-07T03:17:42.217Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-12-07T03:17:42.217Z
Learning: Applies to program-libs/account-checks/**/checks.rs : Provide account ownership validation functions (`check_owner`, `check_program`) in checks module
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/revoke.rs
📚 Learning: 2025-11-24T17:55:17.323Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-libs/macros/src/compressible/README.md:0-0
Timestamp: 2025-11-24T17:55:17.323Z
Learning: Applies to sdk-libs/macros/src/compressible/**/mod.rs : Module declaration should be kept in `mod.rs`
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-11-24T18:00:48.449Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/SOLANA_RENT.md:0-0
Timestamp: 2025-11-24T18:00:48.449Z
Learning: Applies to program-libs/compressible/docs/**/*.rs : Implement account compressibility states in the following order: Funded (rent for current + 1 epoch) → Compressible (lacks rent for current + 1 epoch) → Claimable (funded but past epochs unclaimed)
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/mint_action/actions/decompress_mint.rssdk-libs/ctoken-sdk/src/ctoken/decompress_cmint.rs
📚 Learning: 2025-11-24T17:55:17.323Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-libs/macros/src/compressible/README.md:0-0
Timestamp: 2025-11-24T17:55:17.323Z
Learning: Applies to sdk-libs/macros/src/compressible/**/traits.rs : Core trait implementations (`HasCompressionInfo`, `CompressAs`, `Compressible`) should be defined in `traits.rs`
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-12-06T00:49:21.983Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: Applies to program-libs/compressible/src/config.rs : Implement state validation methods (`validate_active`, `validate_not_inactive`) on CompressibleConfig account structure
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/transfer2/*.rs : Transfer2 instruction (discriminator: 101) supports batch operations including Compress, Decompress, and CompressAndClose with multi-mint support and sum checks
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/mint_action/actions/decompress_mint.rssdk-libs/ctoken-sdk/src/ctoken/decompress_cmint.rs
📚 Learning: 2025-11-24T17:54:38.537Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/README.md:0-0
Timestamp: 2025-11-24T17:54:38.537Z
Learning: Implement compressed token program interfaces for third-party token creation and usage on Solana using ZK Compression
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-11-24T17:56:00.229Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/batched-merkle-tree/docs/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:56:00.229Z
Learning: Applies to program-libs/batched-merkle-tree/docs/**/Cargo.toml : Depend on light-compressed-account crate for compressed account types and utilities
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-11-24T17:59:03.485Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_INFO_TRAIT.md:0-0
Timestamp: 2025-11-24T17:59:03.485Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/account_info/{solana,pinocchio}.rs : Implement AccountInfoTrait with SDK-specific Pubkey types: `solana_pubkey::Pubkey` for solana-program feature and `[u8; 32]` raw bytes for pinocchio feature
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-11-24T17:59:03.485Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_INFO_TRAIT.md:0-0
Timestamp: 2025-11-24T17:59:03.485Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/account_info/solana.rs : Use `std::cell::Ref` and `std::cell::RefMut` for DataRef and DataRefMut associated types when implementing AccountInfoTrait for solana-program SDK
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-11-24T17:56:50.011Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/batched-merkle-tree/docs/INITIALIZE_STATE_TREE.md:0-0
Timestamp: 2025-11-24T17:56:50.011Z
Learning: Applies to program-libs/batched-merkle-tree/docs/src/initialize_state_tree.rs : Use `check_account_balance_is_rent_exempt` function from `light-account-checks` to verify rent exemption for both queue_account and merkle_tree_account during initialization
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-11-24T17:58:50.237Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_CHECKS.md:0-0
Timestamp: 2025-11-24T17:58:50.237Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/checks.rs : Use generic `AccountInfoTrait` parameter for all account validation functions to enable compatibility with both Solana and Pinocchio runtimes
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-11-24T17:55:17.323Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-libs/macros/src/compressible/README.md:0-0
Timestamp: 2025-11-24T17:55:17.323Z
Learning: Use procedural macros for generating rent-free account types and their hooks in Solana programs
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-12-07T03:17:42.217Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-12-07T03:17:42.217Z
Learning: Applies to program-libs/account-checks/**/*.rs : Use AccountInfoTrait abstraction for unified account handling across both solana-program and pinocchio SDKs
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-11-24T18:00:36.663Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/RENT.md:0-0
Timestamp: 2025-11-24T18:00:36.663Z
Learning: Applies to program-libs/compressible/docs/**/*rent*.rs : Implement `get_last_funded_epoch` function to determine the last epoch number covered by rent payments.
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/{src/claim/*.rs,src/withdraw_funding_pool.rs} : Rent management instructions (Claim, WithdrawFundingPool) require validation that config is not INACTIVE (accepts ACTIVE or DEPRECATED states)
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-11-24T18:00:36.663Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/RENT.md:0-0
Timestamp: 2025-11-24T18:00:36.663Z
Learning: Applies to program-libs/compressible/docs/**/*rent*.rs : Implement `calculate_rent_and_balance` function to determine compressibility by checking if account balance covers required rent for epochs since last claim, returning (bool, u64) tuple.
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-11-24T18:00:48.449Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/SOLANA_RENT.md:0-0
Timestamp: 2025-11-24T18:00:48.449Z
Learning: Applies to program-libs/compressible/docs/**/*.rs : Account closure must distribute lamports according to: Solana rent exemption → returned to user, completed epoch rent → rent recipient, partial epoch rent → user, compression incentive → forester node
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-11-24T18:00:36.663Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/RENT.md:0-0
Timestamp: 2025-11-24T18:00:36.663Z
Learning: Applies to program-libs/compressible/docs/**/*rent*.rs : RentConfig struct must be exactly 8 bytes and contain: base_rent (u16), compression_cost (u16), lamports_per_byte_per_epoch (u8), and 3 bytes of padding.
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-11-24T18:00:36.663Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/RENT.md:0-0
Timestamp: 2025-11-24T18:00:36.663Z
Learning: Applies to program-libs/compressible/docs/**/*rent*.rs : Implement rent constants with values: BASE_RENT = 1,220 lamports, RENT_PER_BYTE = 10 lamports, SLOTS_PER_EPOCH = 432,000.
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-11-24T18:00:48.449Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/SOLANA_RENT.md:0-0
Timestamp: 2025-11-24T18:00:48.449Z
Learning: Applies to program-libs/compressible/docs/**/*.rs : Light Protocol accounts must satisfy both Solana rent exemption and Light Protocol rent requirements, plus an 11,000 lamport compression incentive during creation
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-12-07T03:17:28.803Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Use compressible token account extensions that allow accounts to be compressed back into compressed state with rent payment mechanisms
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-11-24T18:00:21.501Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/ERRORS.md:0-0
Timestamp: 2025-11-24T18:00:21.501Z
Learning: Applies to program-libs/compressible/docs/program-libs/compressible/src/**/*.rs : InvalidState (Error Code 19002): For account creation, ensure CompressibleConfig state is `Active` (1). For other operations (claim, withdraw, compress & close), ensure config state is not `Inactive` (0). Validate config state using `config.validate_active()` for creation and `config.validate_not_inactive()` for other operations.
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: When working with compressible extension (rent management), must read and comply with documentation in program-libs/compressible/docs/ including RENT.md, CONFIG_ACCOUNT.md, and SOLANA_RENT.md
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-12-07T18:09:57.240Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/CONFIG_ACCOUNT.md:0-0
Timestamp: 2025-12-07T18:09:57.240Z
Learning: Applies to program-libs/compressible/docs/**/*.rs : CompressibleConfig account state in Solana programs must validate active state using `validate_active()` method before allowing new compressible token account creation
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-11-24T17:56:50.011Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/batched-merkle-tree/docs/INITIALIZE_STATE_TREE.md:0-0
Timestamp: 2025-11-24T17:56:50.011Z
Learning: Applies to program-libs/batched-merkle-tree/docs/src/initialize_state_tree.rs : Return error `AccountError::AccountNotRentExempt` (error code 12011) when account balance is insufficient for rent exemption at calculated size
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-12-06T00:49:21.983Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: Applies to program-libs/compressible/src/rent.rs : Implement rent calculation functions including `rent_curve_per_epoch`, `calculate_rent_and_balance`, `claimable_lamports`, and `calculate_close_lamports` in rent.rs
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-11-24T17:58:50.237Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_CHECKS.md:0-0
Timestamp: 2025-11-24T17:58:50.237Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/checks.rs : Implement `check_account_balance_is_rent_exempt` function to verify account size matches expected_size and account balance meets rent exemption requirement, returning rent exemption amount or errors `InvalidAccountSize` (20004), `InvalidAccountBalance` (20007), or `FailedBorrowRentSysvar` (20008)
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-11-24T18:00:21.501Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/ERRORS.md:0-0
Timestamp: 2025-11-24T18:00:21.501Z
Learning: Applies to program-libs/compressible/docs/program-libs/compressible/src/error.rs : FailedBorrowRentSysvar (Error Code 19001): Ensure the rent sysvar is properly initialized in test environments and verify the sysvar is accessible for on-chain programs. Use `solana_program::rent::Rent::get()` with proper error handling for borrowing rent sysvar.
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-11-24T17:56:20.711Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/batched-merkle-tree/docs/INITIALIZE_ADDRESS_TREE.md:0-0
Timestamp: 2025-11-24T17:56:20.711Z
Learning: Applies to program-libs/batched-merkle-tree/docs/src/initialize_address_tree.rs : Verify merkle_tree_account balance is rent-exempt in `src/initialize_address_tree.rs`. Use `check_account_balance_is_rent_exempt` from `light-account-checks` library. Return error `AccountError::AccountNotRentExempt` (error code: 12011) if account balance is insufficient for rent exemption at calculated size.
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-12-07T03:17:42.217Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-12-07T03:17:42.217Z
Learning: Applies to program-libs/account-checks/**/checks.rs : Implement rent exemption checks (`check_account_balance_is_rent_exempt`) in checks module
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-11-24T17:58:50.237Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_CHECKS.md:0-0
Timestamp: 2025-11-24T17:58:50.237Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/checks.rs : Implement `check_account_info_mut` function to validate account is writable, owned by program_id, and discriminator matches type T
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-11-24T17:58:50.237Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_CHECKS.md:0-0
Timestamp: 2025-11-24T17:58:50.237Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/checks.rs : Implement `check_account_info_non_mut` function to validate account is NOT writable, owned by program_id, and discriminator matches type T
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-11-24T17:57:24.369Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/batched-merkle-tree/docs/INSERT_OUTPUT_QUEUE.md:0-0
Timestamp: 2025-11-24T17:57:24.369Z
Learning: Applies to program-libs/batched-merkle-tree/docs/src/queue.rs : Insert Into Output Queue: Inserts a compressed account hash into the output queue's currently processing batch by calling `BatchedQueueAccount::insert_into_current_batch` with hash_chain_value and current_slot parameters, updating value vec, hash chain, and managing batch state transitions in src/queue.rs
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-12-07T18:09:57.240Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/CONFIG_ACCOUNT.md:0-0
Timestamp: 2025-12-07T18:09:57.240Z
Learning: Applies to program-libs/compressible/docs/**/*.rs : When deserializing CompressibleConfig accounts, skip the 8-byte discriminator `[180, 4, 231, 26, 220, 144, 55, 168]` before deserialization in zero-copy implementations using bytemuck::pod_from_bytes
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-12-06T00:49:21.983Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: Applies to program-libs/compressible/src/config.rs : Implement PDA derivation methods (`derive_pda`, `derive_v1_config_pda`) on CompressibleConfig for account discovery
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
📚 Learning: 2025-11-24T17:57:14.561Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/batched-merkle-tree/docs/INSERT_INPUT_QUEUE.md:0-0
Timestamp: 2025-11-24T17:57:14.561Z
Learning: Applies to program-libs/batched-merkle-tree/docs/src/merkle_tree.rs : In `BatchedMerkleTreeAccount::insert_nullifier_into_queue`, insert compressed_account_hash into bloom filter (NOT the nullifier) for non-inclusion checks in subsequent transactions
Applied to files:
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
🧬 Code graph analysis (4)
sdk-libs/ctoken-sdk/src/ctoken/approve.rs (3)
sdk-libs/ctoken-sdk/src/ctoken/freeze.rs (5)
invoke(57-61)invoke_signed(63-67)instruction(53-55)instruction(81-91)from(71-77)sdk-libs/ctoken-sdk/src/ctoken/revoke.rs (5)
invoke(53-57)invoke_signed(59-63)instruction(49-51)instruction(76-86)from(67-72)sdk-libs/ctoken-sdk/src/ctoken/thaw.rs (5)
invoke(57-61)invoke_signed(63-67)instruction(53-55)instruction(81-91)from(71-77)
sdk-libs/ctoken-sdk/src/ctoken/freeze.rs (3)
sdk-libs/ctoken-sdk/src/ctoken/approve.rs (5)
invoke(65-74)invoke_signed(76-85)instruction(61-63)instruction(100-114)from(89-96)sdk-libs/ctoken-sdk/src/ctoken/revoke.rs (5)
invoke(53-57)invoke_signed(59-63)instruction(49-51)instruction(76-86)from(67-72)sdk-libs/ctoken-sdk/src/ctoken/thaw.rs (5)
invoke(57-61)invoke_signed(63-67)instruction(53-55)instruction(81-91)from(71-77)
sdk-libs/ctoken-sdk/src/ctoken/thaw.rs (3)
sdk-libs/ctoken-sdk/src/ctoken/approve.rs (5)
invoke(65-74)invoke_signed(76-85)instruction(61-63)instruction(100-114)from(89-96)sdk-libs/ctoken-sdk/src/ctoken/freeze.rs (5)
invoke(57-61)invoke_signed(63-67)instruction(53-55)instruction(81-91)from(71-77)sdk-libs/ctoken-sdk/src/ctoken/revoke.rs (5)
invoke(53-57)invoke_signed(59-63)instruction(49-51)instruction(76-86)from(67-72)
sdk-libs/ctoken-sdk/src/ctoken/mod.rs (4)
programs/compressed-token/anchor/src/lib.rs (7)
approve(181-186)freeze(199-205)freeze(204-204)freeze(214-214)mint_to(95-109)revoke(190-195)thaw(209-215)sdk-libs/ctoken-sdk/src/compressed_token/v1/account.rs (1)
approve(101-134)sdk-libs/ctoken-sdk/src/compressed_token/v1/approve/instruction.rs (1)
approve(86-88)sdk-libs/token-client/src/actions/ctoken_transfer.rs (1)
transfer_ctoken(20-38)
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
Show resolved
Hide resolved
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
Show resolved
Hide resolved
| #[derive(Debug, Clone)] | ||
| pub struct DecompressCMint { | ||
| /// Mint seed pubkey (used to derive CMint PDA) | ||
| pub mint_seed_pubkey: Pubkey, | ||
| /// Fee payer | ||
| pub payer: Pubkey, | ||
| /// Mint authority (must sign) | ||
| pub authority: Pubkey, | ||
| /// State tree for the compressed mint | ||
| pub state_tree: Pubkey, | ||
| /// Input queue for reading compressed mint | ||
| pub input_queue: Pubkey, | ||
| /// Output queue for updated compressed mint | ||
| pub output_queue: Pubkey, | ||
| /// Compressed mint with context (from indexer) | ||
| pub compressed_mint_with_context: CompressedMintWithContext, | ||
| /// Validity proof for the compressed mint | ||
| pub proof: ValidityProof, | ||
| /// Rent payment in epochs (must be >= 2) | ||
| pub rent_payment: u8, | ||
| /// Lamports for future write operations | ||
| pub write_top_up: u32, | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider validating rent_payment >= 2 at construction or instruction time.
The doc comment states rent_payment "must be >= 2", but there's no enforcement in this SDK code. While the on-chain program likely validates this, failing early in the SDK provides better developer experience.
Based on learnings about rent management in program-libs/compressible/docs/RENT.md, the rent calculation is critical for proper account compressibility states.
🔎 Optional validation helper
impl DecompressCMint {
pub fn instruction(self) -> Result<Instruction, ProgramError> {
+ if self.rent_payment < 2 {
+ return Err(ProgramError::InvalidArgument);
+ }
+
// Derive CMint PDA
let (cmint_pda, cmint_bump) = find_cmint_address(&self.mint_seed_pubkey);Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In sdk-libs/ctoken-sdk/src/ctoken/decompress_cmint.rs around lines 38 to 60, the
struct documents that rent_payment must be >= 2 but there is no enforcement; add
a validation to enforce rent_payment >= 2 at SDK construction/initializer or
before building the instruction (e.g., in constructors, builder::new, or a
validate() method) so callers get a clear early error instead of relying solely
on on-chain checks; return a Result/error when invalid, update any factory
functions that create DecompressCMint to call this validation, and add/adjust
unit tests and the doc comment to reflect the new client-side validation.
| impl<'info> TryFrom<&DecompressCMintCpi<'info>> for DecompressCMint { | ||
| type Error = ProgramError; | ||
|
|
||
| fn try_from(cpi: &DecompressCMintCpi<'info>) -> Result<Self, Self::Error> { | ||
| Ok(Self { | ||
| mint_seed_pubkey: *cpi.mint_seed.key, | ||
| payer: *cpi.payer.key, | ||
| authority: *cpi.authority.key, | ||
| state_tree: *cpi.state_tree.key, | ||
| input_queue: *cpi.input_queue.key, | ||
| output_queue: *cpi.output_queue.key, | ||
| compressed_mint_with_context: cpi.compressed_mint_with_context.clone(), | ||
| proof: cpi.proof, | ||
| rent_payment: cpi.rent_payment, | ||
| write_top_up: cpi.write_top_up, | ||
| }) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
TryFrom is infallible—consider using From instead.
The TryFrom implementation always returns Ok(...) and never produces an error. Using From would be more idiomatic and removes the need for ? at call sites.
🔎 Proposed simplification
-impl<'info> TryFrom<&DecompressCMintCpi<'info>> for DecompressCMint {
- type Error = ProgramError;
-
- fn try_from(cpi: &DecompressCMintCpi<'info>) -> Result<Self, Self::Error> {
- Ok(Self {
+impl<'info> From<&DecompressCMintCpi<'info>> for DecompressCMint {
+ fn from(cpi: &DecompressCMintCpi<'info>) -> Self {
+ Self {
mint_seed_pubkey: *cpi.mint_seed.key,
payer: *cpi.payer.key,
authority: *cpi.authority.key,
state_tree: *cpi.state_tree.key,
input_queue: *cpi.input_queue.key,
output_queue: *cpi.output_queue.key,
compressed_mint_with_context: cpi.compressed_mint_with_context.clone(),
proof: cpi.proof,
rent_payment: cpi.rent_payment,
write_top_up: cpi.write_top_up,
- })
+ }
}
}Then update line 168:
pub fn instruction(&self) -> Result<Instruction, ProgramError> {
- DecompressCMint::try_from(self)?.instruction()
+ DecompressCMint::from(self).instruction()
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In sdk-libs/ctoken-sdk/src/ctoken/decompress_cmint.rs around lines 221–238, the
TryFrom<&DecompressCMintCpi<'info>> impl always returns Ok and never errors;
replace it with a From<&DecompressCMintCpi<'info>> implementation that returns
DecompressCMint directly (no Result or associated Error type), i.e. implement
From<&DecompressCMintCpi<'info>> for DecompressCMint and construct/return Self
with the same field mappings; then update call sites (e.g., places using
TryFrom::try_from or .try_from(...) with a trailing ? around line ~168) to use
From::from or direct conversion (DecompressCMint::from(&cpi)) and remove the
now-unnecessary ? error propagation.
| MintActionMetaConfigCpiWrite, | ||
| }; | ||
|
|
||
| // TODO: move to compressed_token. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Clarify the TODO and consider adding an issue reference.
The comment "move to compressed_token" lacks specificity about the target module and intended timeline. To improve maintainability and tracking, consider either:
- Expanding the comment with the full target path (e.g.,
// TODO: move to crate::compressed_token::mint_action) - Adding a GitHub issue reference (e.g.,
// TODO (#1234): move to compressed_token)
This will help future maintainers understand the refactoring intent and track the work.
🤖 Prompt for AI Agents
In sdk-libs/ctoken-sdk/src/ctoken/mint_to.rs around line 17, the TODO comment
"move to compressed_token." is too vague; update it to specify the exact target
path and/or a tracking issue by replacing it with a clearer comment such as "//
TODO (#<issue>): move to crate::compressed_token::mint_action" (or the actual
module/function name) so maintainers know where it should go and can track the
work; ensure the issue number and full module path are accurate before
committing.
| accounts: vec![ | ||
| AccountMeta::new(self.token_account, false), | ||
| AccountMeta::new(self.owner, true), | ||
| AccountMeta::new_readonly(Pubkey::default(), false), | ||
| ], | ||
| data: vec![5u8], // CTokenRevoke discriminator | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find the revoke.rs file in the sdk
find . -name "revoke.rs" -type f | head -20Repository: Lightprotocol/light-protocol
Length of output: 114
🏁 Script executed:
#!/bin/bash
# Read the revoke.rs file to understand the implementation
cat -n ./sdk-libs/ctoken-sdk/src/ctoken/revoke.rs
# Also search for the program-side handler that processes revoke instructions (discriminator 5)
echo "=== Searching for CTokenRevoke or revoke handler in program ==="
find ./programs -name "*.rs" -type f | xargs grep -l "revoke\|discriminator.*5" | head -10Repository: Lightprotocol/light-protocol
Length of output: 4026
🏁 Script executed:
#!/bin/bash
# Examine the program-side revoke handler
cat -n ./programs/compressed-token/program/src/ctoken_approve_revoke.rs | head -150
# Also check if there's documentation about the revoke instruction
find ./programs/compressed-token -name "*.md" -type f | xargs grep -l "revoke\|Revoke" 2>/dev/nullRepository: Lightprotocol/light-protocol
Length of output: 5883
🏁 Script executed:
#!/bin/bash
# Check approve instruction in SDK for comparison
cat -n ./sdk-libs/ctoken-sdk/src/ctoken/approve.rs | grep -A 30 "pub fn instruction"
# Also check what pinocchio's revoke expects
find ./programs -name "*.rs" -type f | xargs grep -l "process_revoke" | head -5Repository: Lightprotocol/light-protocol
Length of output: 2198
🏁 Script executed:
#!/bin/bash
# Check pinocchio's revoke implementation signature
find . -path ./target -prune -o -name "*.rs" -type f -print | xargs grep -l "fn process_revoke" | head -5
# Get more details on what approve vs revoke actually need
echo "=== Checking if system_program is used in process_compressible_top_up ==="
grep -A 50 "fn process_compressible_top_up" ./programs/compressed-token/program/src/ctoken_approve_revoke.rs | grep -i "system"
# Check account indices more carefully
echo "=== Checking CLAUDE.md for revoke instruction documentation ==="
find ./programs/compressed-token -name "*.md" -type f -exec grep -l "revoke\|Revoke" {} \;Repository: Lightprotocol/light-protocol
Length of output: 262
🏁 Script executed:
#!/bin/bash
# Let's check what pinocchio_token_program expects for revoke
find . -path ./target -prune -o -name "*.rs" -type f -print | xargs grep -B 5 -A 10 "pub.*fn process_revoke" 2>/dev/null | head -50
# Also check documentation about the account layout
echo "=== Looking for revoke instruction account docs ==="
find ./programs/compressed-token -name "*.md" -type f -exec cat {} \; 2>/dev/null | grep -A 20 -i "revoke"Repository: Lightprotocol/light-protocol
Length of output: 1707
🏁 Script executed:
#!/bin/bash
# Check the delegate account difference between approve and revoke
echo "=== Comparing approve vs revoke account structure ==="
echo "APPROVE accounts:"
cat -n ./sdk-libs/ctoken-sdk/src/ctoken/approve.rs | grep -A 15 "pub fn instruction"
echo ""
echo "REVOKE accounts:"
cat -n ./sdk-libs/ctoken-sdk/src/ctoken/revoke.rs | grep -A 8 "pub fn instruction"
# Check if pinocchio's revoke is part of spl_token standards
echo ""
echo "=== Checking pinocchio token program ==="
find . -path ./target -prune -o -name "*.rs" -type f -print | xargs grep -l "pinocchio_token_program" | head -3Repository: Lightprotocol/light-protocol
Length of output: 2699
🏁 Script executed:
#!/bin/bash
# Let's verify the exact mismatch by checking line-by-line what invoke() passes
echo "=== REVOKE: instruction vs invoke mismatch ==="
echo "Instruction accounts (from instruction()):"
sed -n '79,82p' ./sdk-libs/ctoken-sdk/src/ctoken/revoke.rs
echo ""
echo "Account infos passed to invoke() (from invoke()):"
sed -n '53,56p' ./sdk-libs/ctoken-sdk/src/ctoken/revoke.rs
# Check if approve has same pattern
echo ""
echo "=== APPROVE: instruction vs invoke ==="
echo "Instruction accounts (from instruction()):"
sed -n '106,111p' ./sdk-libs/ctoken-sdk/src/ctoken/approve.rs
echo ""
echo "Account infos passed to invoke() (from invoke()):"
sed -n '67,72p' ./sdk-libs/ctoken-sdk/src/ctoken/approve.rs
# Check how the program handler actually uses these accounts
echo ""
echo "=== Program handler account usage for REVOKE ==="
sed -n '62,87p' ./programs/compressed-token/program/src/ctoken_approve_revoke.rsRepository: Lightprotocol/light-protocol
Length of output: 2246
🏁 Script executed:
#!/bin/bash
# Let's verify if the pinocchio process_revoke actually uses account index 2
# Check what accounts the SPL token revoke instruction needs
echo "=== Checking SPL Token revoke standard ==="
# Search for any reference to what pinocchio expects
find . -path ./target -prune -o -name "*.rs" -type f -print | xargs grep -B 2 -A 15 "process_revoke.*accounts" 2>/dev/null | grep -A 15 "pinocchio"
# Also check the SDK tests to see how revoke is actually used
echo ""
echo "=== Checking SDK tests for revoke usage ==="
find ./sdk-tests -name "*.rs" -type f -exec grep -l "revoke\|Revoke" {} \;Repository: Lightprotocol/light-protocol
Length of output: 159
🏁 Script executed:
#!/bin/bash
# Let's check the transfer2.rs file to understand how system_program is used in compressible extension
echo "=== Checking how system_program is used in transfer ==="
grep -n "system_program\|transfer_lamports_via_cpi" ./programs/compressed-token/program/src/ctoken_approve_revoke.rs
# Let's look at the actual account consumption in the handler
echo ""
echo "=== Full process_compressible_top_up implementation ==="
sed -n '90,140p' ./programs/compressed-token/program/src/ctoken_approve_revoke.rs
# Check what accounts process_revoke from pinocchio actually needs
echo ""
echo "=== Check if there are any tests ==="
find ./programs/compressed-token -name "*test*" -type f | head -5
# Check what Pubkey::default() is being used for in similar SDK wrappers
echo ""
echo "=== Check other SDK instruction wrappers ==="
find ./sdk-libs/ctoken-sdk/src -name "*.rs" -type f | xargs grep "Pubkey::default()" | head -10Repository: Lightprotocol/light-protocol
Length of output: 2975
🏁 Script executed:
#!/bin/bash
# The key question: does pinocchio's process_revoke actually need 3 accounts?
# Let's check the code flow and what transfer_lamports_via_cpi does
echo "=== Checking transfer_lamports_via_cpi implementation ==="
find ./programs/compressed-token -name "*.rs" -type f | xargs grep -B 3 -A 15 "fn transfer_lamports_via_cpi"
# Also verify: in the actual instruction, which accounts are actually used by process_revoke?
echo ""
echo "=== Verifying account counts ==="
sed -n '55,87p' ./programs/compressed-token/program/src/ctoken_approve_revoke.rs | grep -E "accounts.get|process_revoke"Repository: Lightprotocol/light-protocol
Length of output: 1974
🏁 Script executed:
#!/bin/bash
# Critical check: Does the Pubkey::default() placeholder actually work with invoke()?
# Let's examine how invoke() validates accounts
echo "=== Looking at actual usage patterns ==="
grep -B 5 -A 5 "invoke(&instruction" ./sdk-libs/ctoken-sdk/src/ctoken/revoke.rs
# Compare with what accounts the program actually uses
echo ""
echo "=== Program uses these account indices ==="
echo "REVOKE_ACCOUNT_SOURCE = 0"
echo "REVOKE_ACCOUNT_OWNER = 1"
grep "const REVOKE" ./programs/compressed-token/program/src/ctoken_approve_revoke.rs
# Check if system_program is actually needed
echo ""
echo "=== Does process_revoke from pinocchio access accounts beyond [0,1]? ==="
# The handler calls: process_revoke(accounts)
# Which expects to access token_account and owner only
echo "Program handler passes full accounts array to process_revoke(accounts)"
echo "But only extracts and uses accounts[0] and accounts[1]"
# Check instruction creation - is this correct?
echo ""
echo "=== Checking if Pubkey::default() matches actual pubkey passed ==="
sed -n '76,86p' ./sdk-libs/ctoken-sdk/src/ctoken/revoke.rsRepository: Lightprotocol/light-protocol
Length of output: 1538
🏁 Script executed:
#!/bin/bash
# Final verification: Check if system_program is actually needed/used
echo "=== Verify if system_program is actually needed ==="
# Check what pinocchio's revoke signature expects
grep -r "pinocchio_token_program::processor::revoke" ./programs/compressed-token
# Check if revoke actually modifies any system accounts
echo ""
echo "=== Check if account index 2 is ever accessed ==="
sed -n '62,88p' ./programs/compressed-token/program/src/ctoken_approve_revoke.rs | grep -E "accounts\[2\]|get\(2"
# Look at how transfer_lamports_via_cpi works - does it need system_program passed?
echo ""
echo "=== Full transfer_lamports_via_cpi ==="
cat ./programs/compressed-token/program/src/shared/transfer_lamports.rs | head -30Repository: Lightprotocol/light-protocol
Length of output: 1160
🏁 Script executed:
#!/bin/bash
# Final check: verify this is in sdk-libs as specified in guidelines
echo "=== Verify file location ==="
ls -la ./sdk-libs/ctoken-sdk/src/ctoken/revoke.rs
# Check if there are any unit tests for revoke
echo ""
echo "=== Check for unit tests ==="
find ./sdk-libs -name "*test*" -type f | xargs grep -l "revoke\|Revoke" 2>/dev/nullRepository: Lightprotocol/light-protocol
Length of output: 224
Remove unused system_program account from RevokeCTokenCpi invoke methods.
The instruction metadata declares Pubkey::default() as the third account, but both invoke() and invoke_signed() pass system_program which the program handler never uses. The handler only accesses accounts[0] (token_account) and accounts[1] (owner). Unlike ApproveCToken which requires the delegate account, revoke only needs two accounts—remove the system_program from the account_infos array at lines 55 and 61.
🤖 Prompt for AI Agents
sdk-libs/ctoken-sdk/src/ctoken/revoke.rs around lines 79–85: the CPI instruction
includes a third account (Pubkey::default()) but both invoke() and
invoke_signed() currently pass the runtime system_program AccountInfo which the
on-chain handler never reads; remove the system_program entry from the
account_infos arrays at the call sites (lines ~55 and ~61) so the
invoke/invoke_signed calls pass only the two AccountInfos (token_account and
owner), and adjust any corresponding slices/signers for invoke_signed
accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
programs/compressed-token/program/src/lib.rs (1)
143-145: CTokenTransfer logging is commented out—intentional?Line 144 has
// msg!("CTokenTransfer");commented out while all other instruction types log. This may be intentional for performance on a hot path, but inconsistent. Consider documenting the rationale or uncommenting for consistency.programs/compressed-token/program/src/shared/owner_validation.rs (1)
80-114: Add account delegate check to match documented behavior.The function comment states it "Allows owner, account delegate, or permanent delegate" but the implementation only validates owner and permanent delegate. Account delegates (set via the approve instruction) should also be able to authorize compression operations. Add a delegate field check matching the pattern in
verify_owner_or_delegate_signerto ensure delegates can authorize transfers up to their delegated amount.
♻️ Duplicate comments (2)
programs/compressed-token/program/src/shared/owner_validation.rs (1)
57-77: Verbose failure logging adds compute overhead.This concern was raised in a previous review. The multiple
msg!calls on authorization failure are helpful for debugging but consume compute units on every failure path. Consider consolidating into a single message or gating behind a feature flag for production deployments.programs/compressed-token/program/src/transfer/shared.rs (1)
18-44: Clean design for extension tracking.The
AccountExtensionInfostruct provides a sensible way to track extension flags detected during a single deserialization pass. I appreciate thathas_compressibleis correctly excluded fromt22_extensions_eqsince compressible is a Light Protocol extension, not a T22 extension marker.Regarding lines 37-43: the generic
ProgramError::InvalidInstructionDataerror was already flagged in a previous review. A dedicatedExtensionMismatcherror would improve diagnostics.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (4)
sdk-tests/sdk-ctoken-test/src/lib.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/src/transfer_checked.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/tests/scenario_spl.rsis excluded by none and included by nonesdk-tests/sdk-ctoken-test/tests/test_transfer_checked.rsis excluded by none and included by none
📒 Files selected for processing (9)
programs/compressed-token/program/src/ctoken_freeze_thaw.rs(1 hunks)programs/compressed-token/program/src/lib.rs(6 hunks)programs/compressed-token/program/src/shared/owner_validation.rs(2 hunks)programs/compressed-token/program/src/transfer/checked.rs(1 hunks)programs/compressed-token/program/src/transfer/default.rs(1 hunks)programs/compressed-token/program/src/transfer/mod.rs(1 hunks)programs/compressed-token/program/src/transfer/shared.rs(1 hunks)sdk-libs/ctoken-sdk/src/ctoken/mod.rs(4 hunks)sdk-libs/ctoken-sdk/src/ctoken/transfer_ctoken_checked.rs(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
programs/**/*.rs
📄 CodeRabbit inference engine (CLAUDE.md)
Unit tests in programs must not depend on light-test-utils; integration tests must be located in program-tests/
Files:
programs/compressed-token/program/src/transfer/mod.rsprograms/compressed-token/program/src/transfer/shared.rsprograms/compressed-token/program/src/ctoken_freeze_thaw.rsprograms/compressed-token/program/src/transfer/default.rsprograms/compressed-token/program/src/shared/owner_validation.rsprograms/compressed-token/program/src/transfer/checked.rsprograms/compressed-token/program/src/lib.rs
sdk-libs/**/*.rs
📄 CodeRabbit inference engine (CLAUDE.md)
Unit tests in sdk-libs must not depend on light-test-utils; integration tests must be located in sdk-tests/
Files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rssdk-libs/ctoken-sdk/src/ctoken/transfer_ctoken_checked.rs
🧠 Learnings (83)
📓 Common learnings
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/transfer2/*.rs : Transfer2 instruction (discriminator: 101) supports batch operations including Compress, Decompress, and CompressAndClose with multi-mint support and sum checks
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/docs/instructions/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:01:54.689Z
Learning: Applies to programs/compressed-token/program/docs/instructions/instructions/MINT_ACTION.md : MintAction documentation must cover batch instruction for compressed mint management supporting 9 actions: CreateCompressedMint, MintTo, UpdateMintAuthority, UpdateFreezeAuthority, CreateSplMint, MintToCToken, UpdateMetadataField, UpdateMetadataAuthority, RemoveMetadataKey
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/ctoken_transfer.rs : CTokenTransfer instruction (discriminator: 3) must implement SPL-compatible transfers between decompressed accounts
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: When working with compressible extension (rent management), must read and comply with documentation in program-libs/compressible/docs/ including RENT.md, CONFIG_ACCOUNT.md, and SOLANA_RENT.md
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Compressible extension rent management must follow the specifications in program-libs/compressible/docs/RENT.md including: rent authority can only compress when is_compressible() returns true, lamport distribution on close (rent → rent_sponsor, unutilized → destination), and compression incentive for foresters
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Use compressible token account extensions that allow accounts to be compressed back into compressed state with rent payment mechanisms
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/RENT.md:0-0
Timestamp: 2025-11-24T18:00:36.663Z
Learning: Applies to program-libs/compressible/docs/**/*rent*.rs : Implement rent constants with values: BASE_RENT = 1,220 lamports, RENT_PER_BYTE = 10 lamports, SLOTS_PER_EPOCH = 432,000.
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: The codebase provides rent calculation algorithms and account structure definitions used across light-compressed-token, light-registry, compressed-token-sdk, and related programs
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: Applies to program-libs/compressible/src/rent.rs : Implement rent calculation functions including `rent_curve_per_epoch`, `calculate_rent_and_balance`, `claimable_lamports`, and `calculate_close_lamports` in rent.rs
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/RENT.md:0-0
Timestamp: 2025-11-24T18:00:36.663Z
Learning: Applies to program-libs/compressible/docs/**/*rent*.rs : RentConfig struct must be exactly 8 bytes and contain: base_rent (u16), compression_cost (u16), lamports_per_byte_per_epoch (u8), and 3 bytes of padding.
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/SOLANA_RENT.md:0-0
Timestamp: 2025-11-24T18:00:48.449Z
Learning: Applies to program-libs/compressible/docs/**/*.rs : Light Protocol rent is calculated per epoch as: 1220 minimum + (data_bytes * 10) lamports per epoch
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/RENT.md:0-0
Timestamp: 2025-11-24T18:00:36.663Z
Learning: Applies to program-libs/compressible/docs/**/*rent*.rs : Implement `rent_curve_per_epoch` function with formula: base_rent + (num_bytes * lamports_per_byte_per_epoch).
📚 Learning: 2025-11-24T18:02:15.670Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/registry/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:02:15.670Z
Learning: Applies to programs/registry/src/account_compression_cpi/mod.rs : Export new wrapper modules in `account_compression_cpi/mod.rs` using `pub mod new_operation;` and `pub use new_operation::*;`, then import in `lib.rs`
Applied to files:
programs/compressed-token/program/src/transfer/mod.rsprograms/compressed-token/program/src/transfer/shared.rsprograms/compressed-token/program/src/ctoken_freeze_thaw.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/lib.rs
📚 Learning: 2025-11-24T17:55:17.323Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-libs/macros/src/compressible/README.md:0-0
Timestamp: 2025-11-24T17:55:17.323Z
Learning: Applies to sdk-libs/macros/src/compressible/**/mod.rs : Module declaration should be kept in `mod.rs`
Applied to files:
programs/compressed-token/program/src/transfer/mod.rssdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/transfer2/*.rs : Transfer2 instruction (discriminator: 101) supports batch operations including Compress, Decompress, and CompressAndClose with multi-mint support and sum checks
Applied to files:
programs/compressed-token/program/src/transfer/mod.rsprograms/compressed-token/program/src/transfer/shared.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/transfer/default.rsprograms/compressed-token/program/src/transfer/checked.rsprograms/compressed-token/program/src/lib.rssdk-libs/ctoken-sdk/src/ctoken/transfer_ctoken_checked.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/ctoken_transfer.rs : CTokenTransfer instruction (discriminator: 3) must implement SPL-compatible transfers between decompressed accounts
Applied to files:
programs/compressed-token/program/src/transfer/mod.rsprograms/compressed-token/program/src/transfer/shared.rsprograms/compressed-token/program/src/ctoken_freeze_thaw.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/transfer/default.rsprograms/compressed-token/program/src/shared/owner_validation.rsprograms/compressed-token/program/src/transfer/checked.rsprograms/compressed-token/program/src/lib.rssdk-libs/ctoken-sdk/src/ctoken/transfer_ctoken_checked.rs
📚 Learning: 2025-11-24T17:55:17.323Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-libs/macros/src/compressible/README.md:0-0
Timestamp: 2025-11-24T17:55:17.323Z
Learning: Applies to sdk-libs/macros/src/compressible/**/pack_unpack.rs : Pubkey compression logic and `PackedXxx` struct generation with Pack/Unpack trait implementations should be in `pack_unpack.rs`
Applied to files:
programs/compressed-token/program/src/transfer/mod.rsprograms/compressed-token/program/src/transfer/shared.rssdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-12-07T03:17:42.217Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-12-07T03:17:42.217Z
Learning: Applies to program-libs/account-checks/**/checks.rs : Provide account ownership validation functions (`check_owner`, `check_program`) in checks module
Applied to files:
programs/compressed-token/program/src/transfer/mod.rsprograms/compressed-token/program/src/shared/owner_validation.rsprograms/compressed-token/program/src/transfer/checked.rs
📚 Learning: 2025-12-07T03:17:42.217Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-12-07T03:17:42.217Z
Learning: Applies to program-libs/account-checks/**/checks.rs : Include PDA validation functions (`check_pda_seeds`, `check_pda_seeds_with_bump`) in checks module
Applied to files:
programs/compressed-token/program/src/transfer/mod.rs
📚 Learning: 2025-11-24T18:02:15.670Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/registry/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:02:15.670Z
Learning: Applies to programs/registry/src/account_compression_cpi/*.rs : Create wrapper instruction module at `src/account_compression_cpi/new_operation.rs` with `NewOperationContext` struct defining required accounts
Applied to files:
programs/compressed-token/program/src/transfer/mod.rsprograms/compressed-token/program/src/transfer/shared.rsprograms/compressed-token/program/src/ctoken_freeze_thaw.rsprograms/compressed-token/program/src/transfer/default.rsprograms/compressed-token/program/src/lib.rs
📚 Learning: 2025-11-24T17:59:23.357Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:59:23.357Z
Learning: Applies to program-libs/account-checks/docs/src/**/checks.rs : Implement comprehensive validation functions in the checks module covering ownership validation, permission checks, discriminator handling, and PDA verification
Applied to files:
programs/compressed-token/program/src/transfer/mod.rsprograms/compressed-token/program/src/transfer/shared.rsprograms/compressed-token/program/src/shared/owner_validation.rsprograms/compressed-token/program/src/transfer/checked.rssdk-libs/ctoken-sdk/src/ctoken/transfer_ctoken_checked.rs
📚 Learning: 2025-11-24T17:59:54.233Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/PACKED_ACCOUNTS.md:0-0
Timestamp: 2025-11-24T17:59:54.233Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/**/*.rs : Provide descriptive names in ProgramPackedAccounts error messages (e.g., 'token_mint' instead of 'account')
Applied to files:
programs/compressed-token/program/src/transfer/mod.rsprograms/compressed-token/program/src/transfer/shared.rsprograms/compressed-token/program/src/ctoken_freeze_thaw.rsprograms/compressed-token/program/src/transfer/default.rsprograms/compressed-token/program/src/shared/owner_validation.rsprograms/compressed-token/program/src/transfer/checked.rsprograms/compressed-token/program/src/lib.rssdk-libs/ctoken-sdk/src/ctoken/transfer_ctoken_checked.rs
📚 Learning: 2025-12-07T03:17:42.217Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-12-07T03:17:42.217Z
Learning: Applies to program-libs/account-checks/**/checks.rs : Provide discriminator validation and setting functions (`check_discriminator`, `set_discriminator`) in checks module
Applied to files:
programs/compressed-token/program/src/transfer/mod.rs
📚 Learning: 2025-12-07T03:17:42.217Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-12-07T03:17:42.217Z
Learning: Applies to program-libs/account-checks/**/checks.rs : Provide combined validators (`check_account_info_mut`, `check_account_info_non_mut`) for complete account validation
Applied to files:
programs/compressed-token/program/src/transfer/mod.rs
📚 Learning: 2025-11-24T17:59:54.233Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/PACKED_ACCOUNTS.md:0-0
Timestamp: 2025-11-24T17:59:54.233Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/**/*.rs : Implement `get_checked()` method for validated account retrieval with discriminator checking in ProgramPackedAccounts extensions
Applied to files:
programs/compressed-token/program/src/transfer/mod.rsprograms/compressed-token/program/src/transfer/shared.rsprograms/compressed-token/program/src/transfer/checked.rs
📚 Learning: 2025-11-24T17:59:54.233Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/PACKED_ACCOUNTS.md:0-0
Timestamp: 2025-11-24T17:59:54.233Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/**/*.rs : Use `ProgramPackedAccounts` for index-based access to dynamically sized account arrays in Rust programs
Applied to files:
programs/compressed-token/program/src/transfer/mod.rs
📚 Learning: 2025-11-24T17:59:23.357Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:59:23.357Z
Learning: Applies to program-libs/account-checks/docs/src/**/packed_accounts.rs : Implement index-based dynamic account access with bounds checking for handling variable account sets (mint, owner, delegate accounts)
Applied to files:
programs/compressed-token/program/src/transfer/mod.rs
📚 Learning: 2025-12-07T03:17:42.217Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-12-07T03:17:42.217Z
Learning: Applies to program-libs/account-checks/**/checks.rs : Implement permission check functions (`check_mut`, `check_non_mut`, `check_signer`) for account state validation
Applied to files:
programs/compressed-token/program/src/transfer/mod.rsprograms/compressed-token/program/src/shared/owner_validation.rsprograms/compressed-token/program/src/transfer/checked.rs
📚 Learning: 2025-11-24T18:01:54.689Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/docs/instructions/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:01:54.689Z
Learning: Applies to programs/compressed-token/program/docs/instructions/instructions/DECOMPRESSED_TRANSFER.md : Decompressed Transfer documentation must cover SPL-compatible transfers between decompressed accounts
Applied to files:
programs/compressed-token/program/src/transfer/shared.rsprograms/compressed-token/program/src/transfer/default.rsprograms/compressed-token/program/src/transfer/checked.rs
📚 Learning: 2025-12-07T03:17:28.803Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Use compressible token account extensions that allow accounts to be compressed back into compressed state with rent payment mechanisms
Applied to files:
programs/compressed-token/program/src/transfer/shared.rsprograms/compressed-token/program/src/shared/owner_validation.rs
📚 Learning: 2025-11-24T17:55:17.323Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-libs/macros/src/compressible/README.md:0-0
Timestamp: 2025-11-24T17:55:17.323Z
Learning: Applies to sdk-libs/macros/src/compressible/**/decompress_context.rs : Decompression trait implementation (`DecompressContext`) with account accessors, PDA/token separation logic, and token processing delegation should be in `decompress_context.rs`
Applied to files:
programs/compressed-token/program/src/transfer/shared.rsprograms/compressed-token/program/src/ctoken_freeze_thaw.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/shared/owner_validation.rs
📚 Learning: 2025-11-24T17:55:17.323Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-libs/macros/src/compressible/README.md:0-0
Timestamp: 2025-11-24T17:55:17.323Z
Learning: Applies to sdk-libs/macros/src/compressible/**/variant_enum.rs : Account variant enum (`CompressedAccountVariant`) generation and `CompressedAccountData` wrapper struct should be implemented in `variant_enum.rs`
Applied to files:
programs/compressed-token/program/src/transfer/shared.rssdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: All compressed token account implementations must reference and comply with the account layout specifications in programs/compressed-token/program/docs/ACCOUNTS.md
Applied to files:
programs/compressed-token/program/src/transfer/shared.rsprograms/compressed-token/program/src/shared/owner_validation.rsprograms/compressed-token/program/src/lib.rs
📚 Learning: 2025-12-06T00:50:17.433Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: DOCS.md:0-0
Timestamp: 2025-12-06T00:50:17.433Z
Learning: Applies to **/docs/*instructions* : Error documentation must use format: `ErrorType::Variant` (error code: N) - Description with actual numeric codes. For standard Solana ProgramError variants use: InvalidInstructionData (3), InvalidAccountData (4), InsufficientFunds (6), MissingRequiredSignature (8), NotEnoughAccountKeys (11), InvalidSeeds (14). For custom errors show u32 value from logs. For external crate errors show directly without wrapping.
Applied to files:
programs/compressed-token/program/src/transfer/shared.rsprograms/compressed-token/program/src/lib.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/programs/compressed-token/anchor/src/lib.rs : Custom error codes must be defined in programs/compressed-token/anchor/src/lib.rs as part of the anchor_compressed_token::ErrorCode enum and returned as ProgramError::Custom(error_code as u32)
Applied to files:
programs/compressed-token/program/src/transfer/shared.rsprograms/compressed-token/program/src/ctoken_freeze_thaw.rsprograms/compressed-token/program/src/shared/owner_validation.rs
📚 Learning: 2025-12-06T00:49:21.983Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: Applies to program-libs/compressible/src/error.rs : Error types must use numeric codes in the 19xxx range for CToken-specific errors
Applied to files:
programs/compressed-token/program/src/transfer/shared.rsprograms/compressed-token/program/src/lib.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/mint_action/*.rs : MintAction instruction (discriminator: 103) must support 9 action types: CreateCompressedMint, MintTo, UpdateMintAuthority, UpdateFreezeAuthority, CreateSplMint, MintToCToken, UpdateMetadataField, UpdateMetadataAuthority, RemoveMetadataKey
Applied to files:
programs/compressed-token/program/src/transfer/shared.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/lib.rs
📚 Learning: 2025-11-24T17:55:17.323Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-libs/macros/src/compressible/README.md:0-0
Timestamp: 2025-11-24T17:55:17.323Z
Learning: Applies to sdk-libs/macros/src/compressible/**/instructions.rs : Compress/decompress instruction handlers and context struct generation should be implemented in `instructions.rs`, with compress using PDA-only and decompress supporting full PDA + ctoken
Applied to files:
programs/compressed-token/program/src/transfer/shared.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/lib.rs
📚 Learning: 2025-12-07T03:17:42.217Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-12-07T03:17:42.217Z
Learning: Applies to program-libs/account-checks/**/error.rs : Implement automatic error conversions from AccountError to ProgramError for both solana-program and pinocchio SDKs
Applied to files:
programs/compressed-token/program/src/transfer/shared.rsprograms/compressed-token/program/src/ctoken_freeze_thaw.rsprograms/compressed-token/program/src/transfer/default.rsprograms/compressed-token/program/src/transfer/checked.rs
📚 Learning: 2025-12-06T00:49:21.983Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: Applies to program-libs/compressible/src/error.rs : Support ProgramError conversions for Anchor, Pinocchio, and Solana frameworks in error implementation
Applied to files:
programs/compressed-token/program/src/transfer/shared.rs
📚 Learning: 2025-11-24T17:59:36.701Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/DISCRIMINATOR.md:0-0
Timestamp: 2025-11-24T17:59:36.701Z
Learning: Applies to program-libs/account-checks/docs/**/account-checks/**/*.rs : Document discriminator values in code comments to make account types discoverable and maintainable
Applied to files:
programs/compressed-token/program/src/transfer/shared.rs
📚 Learning: 2025-11-24T17:59:36.701Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/DISCRIMINATOR.md:0-0
Timestamp: 2025-11-24T17:59:36.701Z
Learning: Applies to program-libs/account-checks/docs/**/account-checks/**/*.rs : Use unique discriminators across all account types to prevent account type collision attacks
Applied to files:
programs/compressed-token/program/src/transfer/shared.rs
📚 Learning: 2025-11-24T17:59:36.701Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/DISCRIMINATOR.md:0-0
Timestamp: 2025-11-24T17:59:36.701Z
Learning: Applies to program-libs/account-checks/docs/**/account-checks/**/*.rs : Use human-readable ASCII string discriminators or hash-derived values, documented with byte values in comments
Applied to files:
programs/compressed-token/program/src/transfer/shared.rs
📚 Learning: 2025-11-24T17:56:00.229Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/batched-merkle-tree/docs/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:56:00.229Z
Learning: Applies to program-libs/batched-merkle-tree/docs/**/*.rs : Error handling must use BatchedMerkleTreeError enum from errors.rs with u32 error codes in the 14301-14312 range
Applied to files:
programs/compressed-token/program/src/transfer/shared.rs
📚 Learning: 2025-11-24T17:58:50.237Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_CHECKS.md:0-0
Timestamp: 2025-11-24T17:58:50.237Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/checks.rs : Use generic `AccountInfoTrait` parameter for all account validation functions to enable compatibility with both Solana and Pinocchio runtimes
Applied to files:
programs/compressed-token/program/src/transfer/shared.rsprograms/compressed-token/program/src/ctoken_freeze_thaw.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/transfer/default.rsprograms/compressed-token/program/src/transfer/checked.rssdk-libs/ctoken-sdk/src/ctoken/transfer_ctoken_checked.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/close_token_account.rs : Close token account instruction must return rent exemption to rent recipient if compressible and remaining lamports to destination account
Applied to files:
programs/compressed-token/program/src/transfer/shared.rsprograms/compressed-token/program/src/transfer/default.rsprograms/compressed-token/program/src/shared/owner_validation.rsprograms/compressed-token/program/src/lib.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/create_token_account.rs : Create token account instructions (CreateTokenAccount, CreateAssociatedCTokenAccount, CreateAssociatedTokenAccountIdempotent) require ACTIVE config validation only
Applied to files:
programs/compressed-token/program/src/transfer/shared.rsprograms/compressed-token/program/src/ctoken_freeze_thaw.rsprograms/compressed-token/program/src/transfer/default.rsprograms/compressed-token/program/src/transfer/checked.rsprograms/compressed-token/program/src/lib.rssdk-libs/ctoken-sdk/src/ctoken/transfer_ctoken_checked.rs
📚 Learning: 2025-11-24T17:59:03.485Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_INFO_TRAIT.md:0-0
Timestamp: 2025-11-24T17:59:03.485Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/account_info/pinocchio.rs : Pinocchio AccountInfo implementations should use native on-chain implementations for PDA functions when available, with fallback to solana_pubkey off-chain
Applied to files:
programs/compressed-token/program/src/transfer/shared.rsprograms/compressed-token/program/src/ctoken_freeze_thaw.rs
📚 Learning: 2025-12-07T03:17:42.217Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-12-07T03:17:42.217Z
Learning: Applies to program-libs/account-checks/**/*.rs : Use AccountInfoTrait abstraction for unified account handling across both solana-program and pinocchio SDKs
Applied to files:
programs/compressed-token/program/src/transfer/shared.rssdk-libs/ctoken-sdk/src/ctoken/mod.rssdk-libs/ctoken-sdk/src/ctoken/transfer_ctoken_checked.rs
📚 Learning: 2025-11-24T17:59:36.701Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/DISCRIMINATOR.md:0-0
Timestamp: 2025-11-24T17:59:36.701Z
Learning: Applies to program-libs/account-checks/docs/**/account-checks/**/*.rs : Use check_account_info<T> function for full account validation including discriminator and ownership checks
Applied to files:
programs/compressed-token/program/src/transfer/shared.rsprograms/compressed-token/program/src/shared/owner_validation.rsprograms/compressed-token/program/src/transfer/checked.rs
📚 Learning: 2025-11-24T18:02:15.670Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/registry/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:02:15.670Z
Learning: Applies to programs/registry/src/account_compression_cpi/*.rs : Implement `process_new_operation()` function in wrapper module to handle PDA signer setup, account mapping, and CPI execution
Applied to files:
programs/compressed-token/program/src/ctoken_freeze_thaw.rsprograms/compressed-token/program/src/transfer/default.rs
📚 Learning: 2025-12-07T03:17:42.217Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-12-07T03:17:42.217Z
Learning: Applies to program-libs/account-checks/**/account_info/{pinocchio,solana}.rs : Implement feature-gated AccountInfo adapters for pinocchio (feature: `pinocchio`) and solana (feature: `solana`)
Applied to files:
programs/compressed-token/program/src/ctoken_freeze_thaw.rs
📚 Learning: 2025-12-07T03:17:28.803Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Applies to sdk-tests/sdk-ctoken-test/**/{lib,main}.rs : Clone AccountInfo structs when building CPI builder pattern account structs to avoid borrow checker issues
Applied to files:
programs/compressed-token/program/src/ctoken_freeze_thaw.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/lib.rssdk-libs/ctoken-sdk/src/ctoken/transfer_ctoken_checked.rs
📚 Learning: 2025-12-07T03:17:42.217Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-12-07T03:17:42.217Z
Learning: Applies to program-libs/account-checks/{**/error.rs,**/account_info/pinocchio.rs} : Map Pinocchio ProgramError with standard codes 1-11 and handle BorrowError conversions for safe data access
Applied to files:
programs/compressed-token/program/src/ctoken_freeze_thaw.rs
📚 Learning: 2025-12-07T03:17:28.803Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Implement all 8 compressed token instructions: create_cmint, mint_to_ctoken, create_token_account_invoke, create_token_account_invoke_signed, create_ata_invoke, create_ata_invoke_signed, transfer_interface_invoke, and transfer_interface_invoke_signed
Applied to files:
programs/compressed-token/program/src/ctoken_freeze_thaw.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/transfer/default.rsprograms/compressed-token/program/src/transfer/checked.rsprograms/compressed-token/program/src/lib.rssdk-libs/ctoken-sdk/src/ctoken/transfer_ctoken_checked.rs
📚 Learning: 2025-11-24T17:59:46.693Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ERRORS.md:0-0
Timestamp: 2025-11-24T17:59:46.693Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/error.rs : Map Pinocchio standard ProgramError variants (InvalidArgument, InvalidInstructionData, InvalidAccountData, AccountDataTooSmall, InsufficientFunds, IncorrectProgramId, MissingRequiredSignature, AccountAlreadyInitialized, UninitializedAccount, NotEnoughAccountKeys, AccountBorrowFailed) to AccountError::PinocchioProgramError with codes 1-11
Applied to files:
programs/compressed-token/program/src/ctoken_freeze_thaw.rs
📚 Learning: 2025-11-24T18:01:54.689Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/docs/instructions/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:01:54.689Z
Learning: Applies to programs/compressed-token/program/docs/instructions/instructions/CREATE_TOKEN_ACCOUNT.md : Create Token Account Instructions documentation must cover creation of regular and associated ctoken accounts
Applied to files:
programs/compressed-token/program/src/ctoken_freeze_thaw.rsprograms/compressed-token/program/src/transfer/default.rsprograms/compressed-token/program/src/lib.rssdk-libs/ctoken-sdk/src/ctoken/transfer_ctoken_checked.rs
📚 Learning: 2025-12-06T00:49:57.458Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-token-test/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:57.458Z
Learning: Applies to sdk-tests/sdk-token-test/**/*test.rs : Tests should use light-ctoken-sdk functions from sdk-libs/compressed-token-sdk for testing ctoken instructions
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/transfer/default.rsprograms/compressed-token/program/src/lib.rssdk-libs/ctoken-sdk/src/ctoken/transfer_ctoken_checked.rs
📚 Learning: 2025-12-07T03:17:28.803Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Applies to sdk-tests/sdk-ctoken-test/**/{lib,main}.rs : Use the builder pattern from `light-ctoken-sdk::ctoken` module for CPI operations instead of manual instruction building
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rssdk-libs/ctoken-sdk/src/ctoken/transfer_ctoken_checked.rs
📚 Learning: 2025-11-24T17:55:17.323Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-libs/macros/src/compressible/README.md:0-0
Timestamp: 2025-11-24T17:55:17.323Z
Learning: Applies to sdk-libs/macros/src/compressible/**/seed_providers.rs : PDA and CToken seed provider implementations with client-side seed functions should be in `seed_providers.rs`
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-12-07T03:17:28.803Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Applies to sdk-tests/sdk-ctoken-test/**/Cargo.toml : Use path references in Cargo.toml dependencies pointing to `/Users/ananas/dev/light-protocol2/sdk-libs/` for light-ctoken-sdk, light-ctoken-types, light-sdk, light-sdk-types, and light-program-test
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: State and instruction data structures must be defined in the separate 'light-ctoken-interface' crate (program-libs/ctoken-types/) to allow SDKs to import types without pulling in program dependencies
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-11-24T18:00:48.449Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/SOLANA_RENT.md:0-0
Timestamp: 2025-11-24T18:00:48.449Z
Learning: Applies to program-libs/compressible/docs/**/*.rs : Implement account compressibility states in the following order: Funded (rent for current + 1 epoch) → Compressible (lacks rent for current + 1 epoch) → Claimable (funded but past epochs unclaimed)
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/shared/owner_validation.rs
📚 Learning: 2025-11-24T17:55:17.323Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-libs/macros/src/compressible/README.md:0-0
Timestamp: 2025-11-24T17:55:17.323Z
Learning: Applies to sdk-libs/macros/src/compressible/**/traits.rs : Core trait implementations (`HasCompressionInfo`, `CompressAs`, `Compressible`) should be defined in `traits.rs`
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-12-06T00:49:21.983Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: Applies to program-libs/compressible/src/config.rs : Implement state validation methods (`validate_active`, `validate_not_inactive`) on CompressibleConfig account structure
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-12-06T00:49:21.983Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: Applies to program-libs/compressible/src/config.rs : CompressibleConfig account structure must support serialization via Anchor, Pinocchio, and Borsh formats for Light Registry program integration
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-11-24T18:01:30.012Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-tests/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:01:30.012Z
Learning: Run compressed token core tests using `cargo test-sbf -p compressed-token-test --test ctoken`, `--test v1`, `--test mint`, and `--test transfer2`
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/lib.rs
📚 Learning: 2025-11-24T18:01:54.689Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/docs/instructions/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:01:54.689Z
Learning: Applies to programs/compressed-token/program/docs/instructions/instructions/MINT_ACTION.md : MintAction documentation must cover batch instruction for compressed mint management supporting 9 actions: CreateCompressedMint, MintTo, UpdateMintAuthority, UpdateFreezeAuthority, CreateSplMint, MintToCToken, UpdateMetadataField, UpdateMetadataAuthority, RemoveMetadataKey
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/lib.rs
📚 Learning: 2025-11-24T17:54:38.537Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/README.md:0-0
Timestamp: 2025-11-24T17:54:38.537Z
Learning: Implement compressed token program interfaces for third-party token creation and usage on Solana using ZK Compression
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-12-06T00:49:21.983Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: Applies to program-libs/compressible/src/config.rs : Implement default initialization for CToken V1 config in CompressibleConfig structure
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-11-24T17:56:00.229Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/batched-merkle-tree/docs/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:56:00.229Z
Learning: Applies to program-libs/batched-merkle-tree/docs/**/Cargo.toml : Depend on light-compressed-account crate for compressed account types and utilities
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-11-24T17:59:03.485Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_INFO_TRAIT.md:0-0
Timestamp: 2025-11-24T17:59:03.485Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/account_info/{solana,pinocchio}.rs : Implement AccountInfoTrait with SDK-specific Pubkey types: `solana_pubkey::Pubkey` for solana-program feature and `[u8; 32]` raw bytes for pinocchio feature
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-11-24T17:59:03.485Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_INFO_TRAIT.md:0-0
Timestamp: 2025-11-24T17:59:03.485Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/account_info/solana.rs : Use `std::cell::Ref` and `std::cell::RefMut` for DataRef and DataRefMut associated types when implementing AccountInfoTrait for solana-program SDK
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/mod.rs
📚 Learning: 2025-11-24T18:01:54.689Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/docs/instructions/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:01:54.689Z
Learning: Applies to programs/compressed-token/program/docs/instructions/instructions/TRANSFER2.md : Transfer2 documentation must cover batch transfer instruction supporting compress/decompress/transfer operations
Applied to files:
programs/compressed-token/program/src/transfer/default.rs
📚 Learning: 2025-11-24T17:58:50.237Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_CHECKS.md:0-0
Timestamp: 2025-11-24T17:58:50.237Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/checks.rs : Implement `check_owner` function to verify account is owned by specified program, returning `AccountOwnedByWrongProgram` (20001) error on mismatch
Applied to files:
programs/compressed-token/program/src/shared/owner_validation.rs
📚 Learning: 2025-11-24T17:57:39.230Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/batched-merkle-tree/docs/QUEUE_ACCOUNT.md:0-0
Timestamp: 2025-11-24T17:57:39.230Z
Learning: Applies to program-libs/batched-merkle-tree/docs/src/queue.rs : Validate account ownership by Light account compression program using `check_owner` from `light-account-checks` when deserializing `BatchedQueueAccount` with `output_from_account_info`
Applied to files:
programs/compressed-token/program/src/shared/owner_validation.rs
📚 Learning: 2025-11-24T17:59:46.693Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ERRORS.md:0-0
Timestamp: 2025-11-24T17:59:46.693Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/checks/**/*.rs : Validation functions (check_owner, check_signer, check_discriminator, check_pda_seeds) must return specific AccountError variants as documented: check_owner returns AccountOwnedByWrongProgram (20001), check_signer returns InvalidSigner (20009), check_discriminator returns InvalidDiscriminator (20000) or InvalidAccountSize (20004), check_pda_seeds returns InvalidSeeds (20010)
Applied to files:
programs/compressed-token/program/src/shared/owner_validation.rs
📚 Learning: 2025-11-24T17:57:53.312Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/batched-merkle-tree/docs/TREE_ACCOUNT.md:0-0
Timestamp: 2025-11-24T17:57:53.312Z
Learning: Applies to program-libs/batched-merkle-tree/docs/**/*.rs : Account validation for state trees must check: (1) account ownership by Light account compression program, (2) account discriminator is `BatchMta`, and (3) tree type is `STATE_MERKLE_TREE_TYPE_V2` (4)
Applied to files:
programs/compressed-token/program/src/shared/owner_validation.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/{src/claim/*.rs,src/withdraw_funding_pool.rs} : Rent management instructions (Claim, WithdrawFundingPool) require validation that config is not INACTIVE (accepts ACTIVE or DEPRECATED states)
Applied to files:
programs/compressed-token/program/src/shared/owner_validation.rsprograms/compressed-token/program/src/lib.rs
📚 Learning: 2025-11-24T17:58:50.237Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_CHECKS.md:0-0
Timestamp: 2025-11-24T17:58:50.237Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/checks.rs : Implement `check_account_info` function to validate account ownership by program_id and verify discriminator matches type T
Applied to files:
programs/compressed-token/program/src/shared/owner_validation.rsprograms/compressed-token/program/src/transfer/checked.rs
📚 Learning: 2025-12-07T18:09:57.240Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/CONFIG_ACCOUNT.md:0-0
Timestamp: 2025-12-07T18:09:57.240Z
Learning: Applies to program-libs/compressible/docs/**/*.rs : CompressibleConfig account state in Solana programs must validate active state using `validate_active()` method before allowing new compressible token account creation
Applied to files:
programs/compressed-token/program/src/shared/owner_validation.rs
📚 Learning: 2025-11-24T18:00:36.663Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/RENT.md:0-0
Timestamp: 2025-11-24T18:00:36.663Z
Learning: Applies to program-libs/compressible/docs/**/*rent*.rs : Implement `calculate_rent_and_balance` function to determine compressibility by checking if account balance covers required rent for epochs since last claim, returning (bool, u64) tuple.
Applied to files:
programs/compressed-token/program/src/shared/owner_validation.rs
📚 Learning: 2025-11-24T18:00:48.449Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/SOLANA_RENT.md:0-0
Timestamp: 2025-11-24T18:00:48.449Z
Learning: Applies to program-libs/compressible/docs/**/*.rs : Account closure must distribute lamports according to: Solana rent exemption → returned to user, completed epoch rent → rent recipient, partial epoch rent → user, compression incentive → forester node
Applied to files:
programs/compressed-token/program/src/shared/owner_validation.rs
📚 Learning: 2025-11-24T18:00:48.449Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/SOLANA_RENT.md:0-0
Timestamp: 2025-11-24T18:00:48.449Z
Learning: Applies to program-libs/compressible/docs/**/*.rs : Light Protocol accounts must satisfy both Solana rent exemption and Light Protocol rent requirements, plus an 11,000 lamport compression incentive during creation
Applied to files:
programs/compressed-token/program/src/shared/owner_validation.rs
📚 Learning: 2025-12-07T18:09:57.240Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/CONFIG_ACCOUNT.md:0-0
Timestamp: 2025-12-07T18:09:57.240Z
Learning: Applies to program-libs/compressible/docs/**/*.rs : CompressibleConfig rent_sponsor PDA and compression_authority PDA must be derived separately using `get_rent_sponsor_seeds()` and `get_compression_authority_seeds()` helper methods with version-specific seeds
Applied to files:
programs/compressed-token/program/src/shared/owner_validation.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Compressible extension rent management must follow the specifications in program-libs/compressible/docs/RENT.md including: rent authority can only compress when is_compressible() returns true, lamport distribution on close (rent → rent_sponsor, unutilized → destination), and compression incentive for foresters
Applied to files:
programs/compressed-token/program/src/shared/owner_validation.rs
📚 Learning: 2025-11-24T17:59:54.233Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/PACKED_ACCOUNTS.md:0-0
Timestamp: 2025-11-24T17:59:54.233Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/**/*.rs : Always validate retrieved accounts after retrieval from ProgramPackedAccounts using appropriate check methods
Applied to files:
programs/compressed-token/program/src/transfer/checked.rs
📚 Learning: 2025-11-24T17:58:50.237Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_CHECKS.md:0-0
Timestamp: 2025-11-24T17:58:50.237Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/checks.rs : Implement `check_program` function to verify account key matches program_id AND is marked executable, returning `InvalidProgramId` (20011) or `ProgramNotExecutable` (20012) errors
Applied to files:
programs/compressed-token/program/src/transfer/checked.rs
📚 Learning: 2025-11-24T17:59:46.693Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ERRORS.md:0-0
Timestamp: 2025-11-24T17:59:46.693Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/error.rs : All AccountError variants must convert automatically to `ProgramError::Custom(u32)` for both solana-program and pinocchio SDKs using From trait implementations
Applied to files:
programs/compressed-token/program/src/transfer/checked.rs
📚 Learning: 2025-11-24T18:01:54.689Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/docs/instructions/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:01:54.689Z
Learning: Applies to programs/compressed-token/program/docs/instructions/instructions/**/*.md : Instruction documentation must include the following sections: path, description (including accounts and state layout), instruction_data, Accounts (in order with checks), instruction logic and checks, and Errors (with descriptions of causes)
Applied to files:
programs/compressed-token/program/src/lib.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/docs/instructions/*.md : All instruction documentation must include: path to instruction code, description of what the instruction does including accounts used and state layouts, instruction_data paths, accounts list with checks, instruction logic and checks, and possible errors with descriptions
Applied to files:
programs/compressed-token/program/src/lib.rs
📚 Learning: 2025-12-06T00:50:17.433Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: DOCS.md:0-0
Timestamp: 2025-12-06T00:50:17.433Z
Learning: Applies to **/docs/*instructions* : Every instruction must include in documentation: discriminator (instruction value), enum (variant name), path (to processor code), description (high-level overview with integrated key concepts, NOT separate sections), instruction_data (path to struct with field descriptions), Accounts (ordered list with name, type, signer/writable requirements, validation checks, purpose), instruction logic and checks (step-by-step: input validation, deserialization, business logic, state updates, CPIs), and Errors (comprehensive list with ErrorType::Variant format and numeric codes).
Applied to files:
programs/compressed-token/program/src/lib.rs
📚 Learning: 2025-12-07T03:17:28.803Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Applies to sdk-tests/sdk-ctoken-test/**/{lib,main}.rs : Use `invoke_signed()` method from builder pattern for PDA-signed CPI calls, deriving PDA with `Pubkey::find_program_address()` before invocation
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/transfer_ctoken_checked.rs
📚 Learning: 2025-12-07T03:17:28.803Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Applies to sdk-tests/sdk-ctoken-test/**/{lib,main}.rs : Use `invoke()` method from builder pattern for regular CPI calls where the program acts as authority
Applied to files:
sdk-libs/ctoken-sdk/src/ctoken/transfer_ctoken_checked.rs
🧬 Code graph analysis (8)
programs/compressed-token/program/src/transfer/mod.rs (1)
sdk-libs/ctoken-sdk/src/ctoken/mod.rs (1)
default(163-174)
programs/compressed-token/program/src/transfer/shared.rs (3)
program-libs/array-map/src/lib.rs (1)
pubkey_eq(171-181)programs/compressed-token/program/src/extensions/check_mint_extensions.rs (1)
check_mint_extensions(104-180)program-libs/ctoken-interface/src/state/ctoken/zero_copy.rs (1)
zero_copy_at_mut_checked(506-522)
programs/compressed-token/program/src/ctoken_freeze_thaw.rs (1)
programs/compressed-token/program/src/shared/owner_validation.rs (1)
check_token_program_owner(15-25)
sdk-libs/ctoken-sdk/src/ctoken/mod.rs (4)
programs/compressed-token/anchor/src/lib.rs (7)
approve(181-186)freeze(199-205)freeze(204-204)freeze(214-214)mint_to(95-109)revoke(190-195)thaw(209-215)sdk-libs/ctoken-sdk/src/compressed_token/v1/account.rs (1)
approve(101-134)sdk-libs/ctoken-sdk/src/compressed_token/v1/approve/instruction.rs (1)
approve(86-88)sdk-libs/token-client/src/actions/ctoken_transfer.rs (1)
transfer_ctoken(20-38)
programs/compressed-token/program/src/transfer/default.rs (1)
programs/compressed-token/program/src/transfer/shared.rs (1)
process_transfer_extensions(67-90)
programs/compressed-token/program/src/shared/owner_validation.rs (3)
program-libs/array-map/src/lib.rs (1)
pubkey_eq(171-181)program-libs/account-checks/src/checks.rs (1)
check_signer(121-126)program-libs/compressed-account/src/pubkey.rs (1)
new_from_array(79-81)
programs/compressed-token/program/src/transfer/checked.rs (3)
programs/compressed-token/program/src/transfer/shared.rs (1)
process_transfer_extensions(67-90)programs/compressed-token/program/src/shared/owner_validation.rs (1)
check_token_program_owner(15-25)sdk-libs/ctoken-sdk/src/ctoken/transfer_ctoken_checked.rs (1)
from(89-99)
sdk-libs/ctoken-sdk/src/ctoken/transfer_ctoken_checked.rs (1)
programs/compressed-token/program/src/lib.rs (1)
from(102-122)
| /// Process account extensions with mutable access. | ||
| /// Performs extension detection and compressible top-up calculation. | ||
| #[inline(always)] | ||
| #[profile] | ||
| fn process_account_extensions( | ||
| account: &AccountInfo, | ||
| current_slot: &mut u64, | ||
| ) -> Result<AccountExtensionInfo, ProgramError> { | ||
| // Fast path: base account with no extensions | ||
| if account.data_len() == light_ctoken_interface::BASE_TOKEN_ACCOUNT_SIZE as usize { | ||
| return Ok(AccountExtensionInfo::default()); | ||
| } | ||
|
|
||
| let mut account_data = account | ||
| .try_borrow_mut_data() | ||
| .map_err(convert_program_error)?; | ||
| let (token, remaining) = CToken::zero_copy_at_mut_checked(&mut account_data)?; | ||
| if !remaining.is_empty() { | ||
| return Err(ProgramError::InvalidAccountData); | ||
| } | ||
|
|
||
| let extensions = token.extensions.ok_or(CTokenError::InvalidAccountData)?; | ||
|
|
||
| let mut info = AccountExtensionInfo::default(); | ||
|
|
||
| for extension in extensions { | ||
| match extension { | ||
| ZExtensionStructMut::Compressible(compressible_extension) => { | ||
| info.has_compressible = true; | ||
| // Get current slot for compressible top-up calculation | ||
| use pinocchio::sysvars::{clock::Clock, rent::Rent, Sysvar}; | ||
| if *current_slot == 0 { | ||
| *current_slot = Clock::get() | ||
| .map_err(|_| CTokenError::SysvarAccessError)? | ||
| .slot; | ||
| } | ||
|
|
||
| let rent_exemption = Rent::get() | ||
| .map_err(|_| CTokenError::SysvarAccessError)? | ||
| .minimum_balance(account.data_len()); | ||
|
|
||
| info.top_up_amount = compressible_extension | ||
| .info | ||
| .calculate_top_up_lamports( | ||
| account.data_len() as u64, | ||
| *current_slot, | ||
| account.lamports(), | ||
| rent_exemption, | ||
| ) | ||
| .map_err(|_| CTokenError::InvalidAccountData)?; | ||
| } | ||
| ZExtensionStructMut::PausableAccount(_) => { | ||
| info.has_pausable = true; | ||
| } | ||
| ZExtensionStructMut::PermanentDelegateAccount(_) => { | ||
| info.has_permanent_delegate = true; | ||
| } | ||
| ZExtensionStructMut::TransferFeeAccount(_transfer_fee_ext) => { | ||
| info.has_transfer_fee = true; | ||
| // Note: Non-zero transfer fees are rejected by check_mint_extensions, | ||
| // so no fee withholding is needed here. | ||
| } | ||
| ZExtensionStructMut::TransferHookAccount(_) => { | ||
| info.has_transfer_hook = true; | ||
| // No runtime logic needed - we only support nil program_id | ||
| } | ||
| // Placeholder and TokenMetadata variants are not valid for CToken accounts | ||
| _ => { | ||
| return Err(CTokenError::InvalidAccountData.into()); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Ok(info) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Solid extension processing with good defensive checks.
The implementation follows a sound pattern:
- Fast path for base accounts (no extensions) avoids unnecessary deserialization
zero_copy_at_mut_checkedvalidates account state before processing- Trailing data rejection (
remaining.is_empty()) prevents malformed accounts - Lazy
Clock::get()via the sentinel pattern avoids redundant syscalls
The catch-all at line 244 with the comment on line 243 addresses the previous review concern about documenting intentionally rejected variants.
One potential optimization: Rent::get() is called inside the compressible extension handler (line 214). If processing multiple accounts with compressible extensions, this syscall is repeated. Consider caching rent similar to how current_slot is cached.
🔎 Optional: Cache Rent sysvar like Clock
fn process_account_extensions(
account: &AccountInfo,
current_slot: &mut u64,
+ cached_rent: &mut Option<pinocchio::sysvars::rent::Rent>,
) -> Result<AccountExtensionInfo, ProgramError> {
// ... inside Compressible handler:
- let rent_exemption = Rent::get()
- .map_err(|_| CTokenError::SysvarAccessError)?
- .minimum_balance(account.data_len());
+ let rent = cached_rent.get_or_insert_with(|| {
+ Rent::get().expect("rent sysvar")
+ });
+ let rent_exemption = rent.minimum_balance(account.data_len());This would require threading the cached rent through validate_sender and validate_recipient.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In programs/compressed-token/program/src/transfer/shared.rs around lines
177–251, the code calls Rent::get() inside the loop for each Compressible
extension which can repeat the sysvar syscall; cache the Rent like current_slot
to avoid repeated sysvar accesses by retrieving Rent::get() once when first
needed (or passing in a cached Rent to this function), store its minimum_balance
result, and thread the cached rent/minimum_balance through downstream calls
(e.g., validate_sender/validate_recipient) so calculate_top_up_lamports uses the
cached value instead of calling Rent::get() repeatedly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 15
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
forester/src/compressible/subscriber.rs (1)
57-62: Remove the DataSize filter or update it to handle variable-sized accounts with extensions.The
RpcFilterType::DataSize(BASE_TOKEN_ACCOUNT_SIZE)filter at line 62 only matches accounts of exactly 258 bytes. Since the codebase now supports compressible token accounts with extensions (pausable, permanent_delegate, transfer_fee, transfer_hook, compressed_only), and those accounts are larger (263–275 bytes per test expectations), the forester will fail to subscribe to and track any extension-enabled accounts. This breaks rent collection and state management for extended accounts.Either remove the size filter entirely and validate account structure in the handler, or use a different approach (e.g., memcmp on the account type discriminator at a fixed position).
programs/compressed-token/program/tests/mint.rs (1)
438-457: Incomplete test coverage: additional_metadata round-trip not verified.Line 450 sets
additional_metadata: vec![]with comment "Simplified for test", which means the borsh/zero-copy round-trip compatibility ofadditional_metadatais not verified in this test.While
test_rnd_create_compressed_mint_accountdoes test with random additional_metadata, it doesn't explicitly verify the reconstruction from zero-copy like this test does. Consider either:
- Reconstructing actual additional_metadata from the zero-copy structure, or
- Adding a comment explaining why this simplification is acceptable (e.g., "additional_metadata uses standard Vec serialization, tested separately").
💡 Proposed fix to test additional_metadata round-trip
ZExtensionStruct::TokenMetadata(z_metadata) => { ExtensionStruct::TokenMetadata(TokenMetadata { update_authority: z_metadata.update_authority, mint: z_metadata.mint, name: z_metadata.name.to_vec(), symbol: z_metadata.symbol.to_vec(), uri: z_metadata.uri.to_vec(), - additional_metadata: vec![], // Simplified for test + additional_metadata: z_metadata + .additional_metadata + .as_ref() + .map(|am| { + am.iter() + .map(|item| AdditionalMetadata { + key: item.key.to_vec(), + value: item.value.to_vec(), + }) + .collect() + }) + .unwrap_or_default(), }) }programs/compressed-token/program/src/shared/compressible_top_up.rs (2)
50-73: CMint always initializes sysvars, making CToken's lazy init dead code.The CMint block (lines 51-74) unconditionally fetches Clock and Rent. The CToken block's check at lines 81-86 (
if current_slot == 0) will never trigger since CMint runs first. Similarly, the early exit at lines 102-104 is now dead code.Consider simplifying by removing the conditional checks in the CToken block:
🔎 Simplify by fetching sysvars once upfront
let mut transfers = [...]; - let mut current_slot = 0; - let mut rent: Option<Rent> = None; + let current_slot = Clock::get() + .map_err(|_| CTokenError::SysvarAccessError)? + .slot; + let rent = Rent::get().map_err(|_| CTokenError::SysvarAccessError)?; // Initialize budget: +1 allows exact match (total == max_top_up) let mut lamports_budget = (max_top_up as u64).saturating_add(1); // Calculate CMint top-up using zero-copy { let cmint_data = cmint.try_borrow_data().map_err(convert_program_error)?; let (mint, _) = CompressedMint::zero_copy_at(&cmint_data) .map_err(|_| CTokenError::CMintDeserializationFailed)?; // Access compression info directly from meta (all cmints now have compression embedded) - if current_slot == 0 { - current_slot = Clock::get() - .map_err(|_| CTokenError::SysvarAccessError)? - .slot; - rent = Some(Rent::get().map_err(|_| CTokenError::SysvarAccessError)?); - } - let rent_exemption = rent.as_ref().unwrap().minimum_balance(cmint.data_len()); + let rent_exemption = rent.minimum_balance(cmint.data_len());
101-104: Dead code: Early exit unreachable with new embedded compression model.Since both CMint and CToken now have embedded compression info that's always accessed,
current_slotis always set to a non-zero value before reaching this check. This early exit is no longer reachable.🔎 Remove dead code
- // Exit early if no compressible accounts - if current_slot == 0 { - return Ok(()); - } - if transfers[0].amount == 0 && transfers[1].amount == 0 { return Ok(()); }programs/compressed-token/program/src/mint_action/actions/compress_and_close_cmint.rs (1)
79-111: Step numbering in comments is inconsistent.The comments indicate steps 7 (line 79), then 6 (line 99), then 7 again (line 105). This appears to be a copy-paste artifact. Consider renumbering for clarity:
- // 7. Check is_compressible (rent has expired) + // 6. Check is_compressible (rent has expired) ... - // 6. Transfer all lamports to rent_sponsor + // 7. Transfer all lamports to rent_sponsor ... - // 7. Close account (assign to system program, resize to 0) + // 8. Close account (assign to system program, resize to 0)program-libs/ctoken-interface/tests/compressed_mint.rs (1)
104-114: Consider adding test iterations with extensions enabled.Currently the loop only tests
with_extensions: false. To fully exercise the TokenMetadata extension path and validate extensions roundtrip correctly, consider adding iterations with extensions enabled.🔎 Suggested improvement
for i in 0..100 { - let original_mint = generate_random_compressed_mint(&mut rng, false); + let with_ext = i % 2 == 0; // Alternate between with/without extensions + let original_mint = generate_random_compressed_mint(&mut rng, with_ext); let borsh_bytes = original_mint.try_to_vec().unwrap();Note: You'd also need to update the
CompressedMintConfigto include extensions whenwith_extis true.
♻️ Duplicate comments (23)
programs/compressed-token/program/src/transfer/shared.rs (3)
39-46: Extension mismatch returns a generic error.As noted in a previous review,
ProgramError::InvalidInstructionDatais generic. A dedicatedCTokenError::ExtensionMismatchvariant would provide clearer diagnostics for callers investigating transfer failures.
104-126: Budget enforcement is correct; minor optimization opportunity noted previously.The
saturating_addprevents overflow, and the "0 = no limit" semantics are implemented correctly. As mentioned in a prior review, thetransfersarray always includes both accounts even when one has a zero amount—filtering could avoid unnecessary work ifmulti_transfer_lamportsdoesn't already short-circuit zeros.
225-238: Rent sysvar is fetched on every account processed.As noted in a prior review,
Rent::get()is called inside this function for each account. When processing both sender and recipient accounts, this results in two syscalls. Consider caching the rent value similarly to howcurrent_slotis cached—pass a&mut Option<Rent>and populate it lazily.programs/compressed-token/program/src/shared/owner_validation.rs (1)
57-77: Verbose logging on authorization failure impacts compute budget.Multiple
msg!calls on failure add compute overhead on hot paths. This was flagged in a previous review. Consider consolidating into a single log message or gating behind a feature flag for debugging.program-libs/ctoken-interface/tests/ctoken/size.rs (1)
6-57: Good test coverage for extension size combinations.The test exercises:
- Base size (no extensions)
- Single extensions (pausable, permanent_delegate, transfer_fee, transfer_hook)
- Pairs (pausable + permanent_delegate)
- All four extensions
The comments clearly document the expected size math. While named constants could improve maintainability, the current approach is readable.
programs/compressed-token/program/src/transfer2/compression/ctoken/compress_and_close.rs (2)
241-253: Consider extracting the frozen state constant.Lines 244 and 257 use the magic number
2forAccountState::Frozen. While the inline comment explains this, a named constant would improve clarity and reduce duplication.
270-276: Redundant delegate check can be simplified.The
has_delegate()check on line 271 already validates the delegate option prefix. The subsequent checkcompressed_token_account.delegate != 0on line 274 is redundant sincehas_delegate()returns true only when the delegate is set.programs/compressed-token/program/src/extensions/check_mint_extensions.rs (1)
134-136:unwrap_or_default()silently swallows extension enumeration errors.If
get_extension_types()fails, this returns an empty vector, causinghas_restricted_extensionsto be false. A malformed mint could bypass restricted extension checks.🔎 Suggested fix
- let extension_types = mint_state.get_extension_types().unwrap_or_default(); + let extension_types = mint_state.get_extension_types().map_err(|e| { + msg!("Failed to get extension types: {:?}", e); + ProgramError::InvalidAccountData + })?;forester/src/compressible/compressor.rs (1)
140-156: Index collision issue withdelegate_indexsentinel value.Using
unwrap_or(0)as a sentinel for "no delegate" collides with valid packed account index 0. When the owner is the first account inserted,insert_or_get()returns 0. Downstream code cannot distinguish:
- No delegate (sentinel 0)
- Delegate equals owner (legitimate index 0)
🔎 Suggested fix
Change
delegate_indexinCompressAndCloseIndicestoOption<u8>:- let delegate_index = account_state - .account - .delegate - .map(|delegate| { - let delegate_pubkey = Pubkey::new_from_array(delegate.to_bytes()); - packed_accounts.insert_or_get(delegate_pubkey) - }) - .unwrap_or(0); + let delegate_index = account_state.account.delegate.map(|delegate| { + let delegate_pubkey = Pubkey::new_from_array(delegate.to_bytes()); + packed_accounts.insert_or_get(delegate_pubkey) + });Then update
CompressAndCloseIndicesstruct and all consumers.forester/src/compressible/state.rs (2)
14-38: Dynamic rent calculation is an improvement.The refactor to accept
account_sizeand computerent_exemptiondynamically viaRent::default().minimum_balance(account_size)is better than hardcoded values. The embedded compression access viaaccount.compression.get_last_funded_epoch()aligns with the PR's migration.Note: As flagged in a previous review,
Rent::default()returns hardcoded mainnet values. For production accuracy, consider fetching the actual rent sysvar via RPC in calling contexts.
112-122: Silent skip on calculation failure preserved.The error handling logs a warning and returns
Ok(())whencalculate_compressible_slotfails. This was flagged in a previous review as potentially hiding issues. While it prevents one bad account from blocking updates, consider tracking failed accounts for later investigation or using a sentinel value.programs/registry/src/compressible/compressed_token/compress_and_close.rs (4)
100-104: Use consistent error variant for CToken parsing failure.The error
RegistryError::InvalidSignerdoesn't describe a CToken parsing failure. For consistency with line 97-98, useRegistryError::InvalidTokenAccountData.🔎 Suggested fix
let (ctoken, _) = CToken::zero_copy_at(&account_data).map_err(|e| { anchor_lang::prelude::msg!("Failed to parse CToken: {:?}", e); - RegistryError::InvalidSigner + RegistryError::InvalidTokenAccountData })?;
109-111: Document the frozen state constant.The magic number
2forAccountState::Frozenshould be documented. Per relevant code snippets,is_frozen()methods inzero_copy.rsalso usestate == 2.+ // AccountState: Uninitialized=0, Initialized=1, Frozen=2 let is_frozen = ctoken.state == 2;
137-148: Critical: delegated_amount lost when no marker extensions present.When an account has a delegate with
delegated_amount > 0but no marker extensions (not frozen, no transfer fees, etc.), theCompressedOnlyextension isn't created, anddelegated_amountis permanently lost. The output setshas_delegate: trueat line 157, but the compressed account has no way to recover the original spending limit.The condition should include
delegated_amount > 0:🔎 Suggested fix
- if has_marker_extensions { + if has_marker_extensions || delegated_amount > 0 { out_tlv.push(vec![ExtensionInstructionData::CompressedOnly( CompressedOnlyExtensionInstructionData { delegated_amount, withheld_transfer_fee, is_frozen, }, )]); } else { out_tlv.push(vec![]); }
37-37: Clarify delegate_index sentinel semantics.The comment says "0 if no delegate", but 0 is a valid index in
packed_accounts. At line 157,has_delegateis derived fromdelegated_amount > 0which provides a safety net, but the index semantics remain ambiguous.Consider using
u8::MAX(255) as the sentinel, or document that index 0 is reserved/cannot be used for delegates.programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs (2)
28-28: Documentation still doesn't match validation logic.The comment states "rent_payment must be 0 or >= 2" but the validation at lines 62-64 only rejects
rent_payment == 1. Consider revising to: "rent_payment must not be 1" to accurately reflect the code behavior.
61-65: Consider adding upper bound validation for rent_payment.The validation only rejects
rent_payment == 1but allows unbounded values. Sincemax_funded_epochsexists in the config, consider adding:if action.rent_payment > config.rent_config.max_funded_epochs as u32 { return Err(ErrorCode::RentPaymentExceedsMaximum.into()); }This prevents excessively large epoch values from causing large lamport transfers.
programs/compressed-token/program/src/ctoken_approve_revoke.rs (2)
113-117: Thesaturating_add(1)on lamports_budget is unclear.When
max_top_up != 0, you setlamports_budget = (max_top_up as u64).saturating_add(1). The+1appears to be a sentinel value, but this interacts confusingly with the explicit check at line 132. Consider documenting why this is needed or simplifying to just use the explicit check.
130-137: Redundant max_top_up enforcement.The explicit
transfer_amount > max_top_upcheck makes thelamports_budgetmechanism somewhat redundant for enforcement purposes. The budget is passed toprocess_compression_top_upbut doesn't seem to be the authoritative check here.Consider either:
- Removing the explicit check and relying on budget exhaustion, or
- Clarifying with a comment why both checks exist.
program-libs/ctoken-interface/src/state/extensions/extension_struct.rs (1)
122-177: Zero-copy deserialization handles all new discriminants correctly.Each discriminant (27-31) correctly maps to its corresponding extension type and returns the appropriate
ZExtensionStructMutvariant. The pattern is consistent across all cases.Note: The hardcoded discriminant literals (19, 27, 28, 29, 30, 31) would benefit from being extracted to named constants as noted in a past review. This would improve maintainability.
programs/compressed-token/program/src/transfer2/compression/ctoken/compress_or_decompress_ctokens.rs (3)
50-54: Redundant uninitialized account check (previously flagged).This check duplicates validation already performed by
CToken::zero_copy_at_mut_checkedat line 48. The_checkedvariant returns an error whenstate == 0(viaInvalidAccountState). The explicit re-check here adds defensive depth but is functionally redundant.
64-70: Consider extracting magic number2forAccountState::Frozen.The frozen state check uses the literal
2for the frozen state. A named constant would improve readability:const ACCOUNT_STATE_FROZEN: u8 = 2;This was noted in a previous review and remains a minor readability improvement.
157-192: Missing delegate validation when CToken has existing delegate (previously flagged).The delegate validation logic still has the gap identified in the prior review:
Scenario:
- CToken has delegate A (
ctoken.delegate()returnsSome(A))- Input compressed account has
delegated_amount > 0- But
input_delegateparameter isNoneCurrent flow:
- Line 161: enters
if let Some(ctoken_delegate)block- Line 163:
if let Some(input_del)is false (input_delegate is None)- Falls through to line 173 comment
- Line 184-191: adds
delegated_amountwithout validating it came from the same delegateRisk: Delegated amounts from potentially different delegates are merged without validation.
🔎 Proposed fix
// Validate delegate compatibility if let Some(ctoken_delegate) = ctoken.delegate() { // CToken has a delegate - check if it matches the input delegate if let Some(input_del) = input_delegate_pubkey.as_ref() { if ctoken_delegate.to_bytes() != input_del.to_bytes() { msg!( "Decompress delegate mismatch: CToken delegate {:?} != input delegate {:?}", ctoken_delegate.to_bytes(), input_del.to_bytes() ); return Err(ErrorCode::DecompressDelegateMismatch.into()); } + } else if delegated_amount > 0 { + // CToken has delegate but input has delegated_amount without delegate info + msg!("Decompress: CToken has delegate but input has delegated_amount without delegate pubkey"); + return Err(CTokenError::InvalidAccountData.into()); } // Delegates match (or input has no delegated_amount) - continue
programs/compressed-token/program/src/extensions/check_mint_extensions.rs
Outdated
Show resolved
Hide resolved
programs/compressed-token/program/src/extensions/check_mint_extensions.rs
Outdated
Show resolved
Hide resolved
| // Reconstruct CompressionInfo from zero-copy fields | ||
| let compression = { | ||
| let zc = &zc_mint.meta.compression; | ||
| CompressionInfo { | ||
| config_account_version: u16::from(zc.config_account_version), | ||
| compress_to_pubkey: zc.compress_to_pubkey, | ||
| account_version: zc.account_version, | ||
| lamports_per_write: u32::from(zc.lamports_per_write), | ||
| compression_authority: zc.compression_authority, | ||
| rent_sponsor: zc.rent_sponsor, | ||
| last_claimed_slot: u64::from(zc.last_claimed_slot), | ||
| rent_config: light_compressible::rent::RentConfig { | ||
| base_rent: u16::from(zc.rent_config.base_rent), | ||
| compression_cost: u16::from(zc.rent_config.compression_cost), | ||
| lamports_per_byte_per_epoch: zc.rent_config.lamports_per_byte_per_epoch, | ||
| max_funded_epochs: zc.rent_config.max_funded_epochs, | ||
| max_top_up: u16::from(zc.rent_config.max_top_up), | ||
| }, | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider adding a helper method or From trait for CompressionInfo reconstruction.
This manual reconstruction of CompressionInfo from zero-copy fields is verbose and creates maintenance burden. If the structure changes, this code must be updated in lockstep.
💡 Suggested refactoring approach
Consider implementing a From<&ZCompressionInfo> trait or a .to_owned() method on the zero-copy type to eliminate this boilerplate. Example:
// In the zero-copy CompressionInfo implementation
impl<'a> From<&ZCompressionInfo<'a>> for CompressionInfo {
fn from(zc: &ZCompressionInfo<'a>) -> Self {
CompressionInfo {
config_account_version: u16::from(zc.config_account_version),
compress_to_pubkey: zc.compress_to_pubkey,
// ... remaining fields
}
}
}Then this test code becomes:
- let compression = {
- let zc = &zc_mint.meta.compression;
- CompressionInfo {
- config_account_version: u16::from(zc.config_account_version),
- // ... 15+ lines of manual field mapping
- }
- };
+ let compression = CompressionInfo::from(&zc_mint.meta.compression);Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In programs/compressed-token/program/tests/mint.rs around lines 401 to 420, the
test manually reconstructs a CompressionInfo from zero-copy fields; implement a
conversion to remove this boilerplate by adding a From<&ZCompressionInfo> (or a
to_owned) implementation on the zero-copy type that maps each field (including
numeric casts like u16::from/u32::from/u64::from and reconstructing the
RentConfig sub-struct) into a CompressionInfo, then update the test to use
CompressionInfo::from(&zc_mint.meta.compression) or .into() instead of the
manual field-by-field reconstruction.
23e48a4 to
d293dc3
Compare
| ); | ||
| return Err(ErrorCode::RentPaymentExceedsMax.into()); | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed misguided check, max_funded_epochs should only regulate top ups not the initial rent payment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 20
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
programs/compressed-token/program/tests/compress_and_close.rs (1)
73-84: Move this test to program-tests/ directory.Unit tests in programs must be located in program-tests/ for integration testing, not in programs/*/tests/. The decimals value of 9 is correct for this test scenario (standard for Solana tokens), but the file location violates the project's testing structure guidelines.
program-libs/ctoken-interface/src/instructions/mint_action/instruction_data.rs (1)
117-152: Normalize empty extension lists toNonefor symmetry and cleaner encoding.In
TryFrom<CompressedMint> for CompressedMintInstructionDatayou always returnextensions: Some(extension_list), even when there were no extensions on the mint andextension_listis empty. The reverse conversion treats an empty list asNone, so the two paths are no longer symmetric and you emit a different Borsh shape (Some([])vsNone) for “no extensions”.Consider only using
Somewhen there is at least one extension:Suggested change
- Ok(Self { - supply: mint.base.supply, - decimals: mint.base.decimals, - metadata: mint.metadata, - mint_authority: mint.base.mint_authority, - freeze_authority: mint.base.freeze_authority, - extensions: Some(extension_list), - }) + let extensions = if extension_list.is_empty() { + None + } else { + Some(extension_list) + }; + + Ok(Self { + supply: mint.base.supply, + decimals: mint.base.decimals, + metadata: mint.metadata, + mint_authority: mint.base.mint_authority, + freeze_authority: mint.base.freeze_authority, + extensions, + })This keeps the InstructionData encoding consistent with how you deserialize it back into
CompressedMint.programs/compressed-token/program/src/shared/compressible_top_up.rs (1)
45-99: Consider extracting duplicate sysvar initialization logic.The code correctly migrates from extension-scanning to embedded compression metadata access. However, the sysvar initialization logic (Lines 56-61 and 81-86) is duplicated between CMint and CToken sections.
Since both blocks perform identical operations:
if current_slot == 0 { current_slot = Clock::get()...; rent = Some(Rent::get()...); }Consider extracting this into a helper function or initializing once before both sections:
🔎 Suggested refactor
+ // Initialize sysvars once for both calculations + let current_slot = Clock::get() + .map_err(|_| CTokenError::SysvarAccessError)? + .slot; + let rent = Rent::get().map_err(|_| CTokenError::SysvarAccessError)?; + // Calculate CMint top-up using zero-copy { let cmint_data = cmint.try_borrow_data().map_err(convert_program_error)?; let (mint, _) = CompressedMint::zero_copy_at(&cmint_data) .map_err(|_| CTokenError::CMintDeserializationFailed)?; - if current_slot == 0 { - current_slot = Clock::get()...; - rent = Some(Rent::get()...); - } - let rent_exemption = rent.as_ref().unwrap().minimum_balance(cmint.data_len()); + let rent_exemption = rent.minimum_balance(cmint.data_len());This eliminates the lazy initialization complexity and duplicate code.
programs/compressed-token/program/src/mint_action/actions/compress_and_close_cmint.rs (1)
79-97: Minor: Comment step numbering is inconsistent.The doc comments at the top list steps 1-9, but the inline comments jump: 4→5→7→6→7→8→9. Consider renumbering for clarity.
🔎 Suggested numbering fix
- // 7. Check is_compressible (rent has expired) + // 6. Check is_compressible (rent has expired) #[cfg(target_os = "solana")] let current_slot = Clock::get() .map_err(|_| ProgramError::UnsupportedSysvar)? .slot;And subsequent steps should follow sequentially.
program-libs/ctoken-interface/tests/compressed_mint.rs (1)
104-114: Test currently only exercises non-extension path.Line 105 hardcodes
with_extensions: false. Consider adding a separate test iteration or test case that exerciseswith_extensions: trueto ensure the TokenMetadata extension path is validated.🔎 Consider adding extension coverage
// After the existing loop, add: for i in 0..20 { let original_mint = generate_random_compressed_mint(&mut rng, true); // ... similar assertions for extension case }programs/compressed-token/program/src/shared/owner_validation.rs (1)
83-114: Unused_compression_amountparameter—remove or document intent.The
_compression_amountparameter is accepted but never used. If it's reserved for future use, consider adding a brief doc comment explaining the intent. Otherwise, remove it to avoid confusion.🔎 If removing:
pub fn check_ctoken_owner( compressed_token: &mut ZCTokenMut, authority_account: &AccountInfo, mint_checks: Option<&MintExtensionChecks>, - _compression_amount: u64, ) -> Result<(), ProgramError> {Update all call sites accordingly.
♻️ Duplicate comments (44)
programs/compressed-token/program/src/mint_action/mint_input.rs (1)
40-41: Comment remains misleading.This issue was previously flagged: the comment suggests returning
CompressedMintto avoid double deserialization, but the function returnsOk(()). Either mark it as a TODO if you plan to refactor the signature later, or remove the comment to keep the code and documentation consistent.programs/compressed-token/program/src/transfer/checked.rs (1)
81-100: Two transfer paths are correct, but rationale could be clearer.The function correctly handles two scenarios:
- Extension decimals present: Validates decimals match, uses 3-account slice (source, destination, authority), calls
process_transferwithNonefor expected_decimals- No extension decimals: Validates mint ownership, uses all 4 accounts including mint, passes
Some(decimals)The different account layouts are necessary because the pinocchio transfer logic expects different account sets depending on whether decimals validation is needed. However, it would help to add a comment explaining why the mint is excluded in the extension path.
📝 Suggested clarification
if let Some(extension_decimals) = extension_decimals { if extension_decimals != decimals { msg!("extension_decimals != decimals"); return Err(ProgramError::InvalidInstructionData); } - // Create accounts slice without mint: [source, destination, authority] - // pinocchio expects 3 accounts when expected_decimals is None + // Extension decimals path: mint is excluded because decimals already validated + // against extension. The underlying SPL transfer validates the mint internally + // and doesn't require it in the accounts slice when expected_decimals is None. let transfer_accounts = [*source, *destination, *authority];programs/compressed-token/program/src/transfer/shared.rs (3)
189-227: Extension processing with good defensive checks.Key strengths:
zero_copy_at_mut_checkedvalidates account state before processing- Trailing data rejection (line 203-205) prevents malformed accounts
- Lazy Clock caching via sentinel pattern (line 219)
- Mint field validation when mint account is provided (lines 208-212)
The
Rent::get()call inside this function will repeat for each account processed. Consider caching rent similar to the clock pattern if this becomes a hot path.
39-46: Generic error for extension mismatch.
ProgramError::InvalidInstructionDatais used when T22 extensions don't match between sender and recipient. A more specific error likeCTokenError::ExtensionMismatchwould help callers diagnose failures.
97-126: Budget enforcement is correct; zero-amount optimization opportunity.The
saturating_addprevents overflow, and "0 = no limit" semantics are properly implemented. The transfers array always includes both accounts even when one has zero amount.program-libs/ctoken-interface/src/instructions/create_ctoken_account.rs (1)
100-119: Non-Solana target panics instead of returning error.The
unreachable!()on non-Solana targets will cause a panic in unit tests or off-chain tooling. Since this function returnsResult, it would be more consistent to return an error variant.🔎 Suggested fix
#[cfg(not(target_os = "solana"))] - unreachable!("deriving a pda is only available on target `solana`"); + { + // Off-chain: use solana_pubkey's software implementation + let mut seeds_with_bump: Vec<&[u8]> = seeds.to_vec(); + seeds_with_bump.push(&[bump]); + solana_pubkey::Pubkey::create_program_address(&seeds_with_bump, &solana_pubkey::Pubkey::new_from_array(*program_id)) + .map(|p| p.to_bytes()) + .map_err(|_| CTokenError::InvalidAccountData) + }This enables off-chain testing without panics.
programs/compressed-token/program/tests/token_output.rs (1)
51-68: Propagate and assert the frozen flag instead of hardcodingis_frozen: false.You randomize
is_frozeninCompressedOnlyExtensionInstructionData(Line 124) but always passfalseintoset_output_compressed_account(Line 180) and always setstate: AccountState::Initializedin the expectedAnchorTokenData(Line 212). That means:
- The frozen-state path in
set_output_compressed_account(state selection + frozen hashing) is never exercised.- The TLV
is_frozenbit can conceptually diverge from the ctoken account state without any test catching it.Consider tracking a per-output
is_frozenflag and using it consistently:
- Record
is_frozenwhen building the TLV instruction data.- Pass that flag into
set_output_compressed_account.- Drive the expected
token_data.statefrom the same flag (e.g.,AccountState::Frozenwhen true).For example:
Suggested test adjustments
- let mut merkle_tree_indices = Vec::new(); - let mut tlv_flags = Vec::new(); - let mut tlv_delegated_amounts = Vec::new(); - let mut tlv_withheld_fees = Vec::new(); + let mut merkle_tree_indices = Vec::new(); + let mut tlv_flags = Vec::new(); + let mut tlv_delegated_amounts = Vec::new(); + let mut tlv_withheld_fees = Vec::new(); + let mut is_frozen_flags = Vec::new(); @@ - for i in 0..num_outputs { + for i in 0..num_outputs { if tlv_flags[i] { - let ext = ExtensionInstructionData::CompressedOnly( - CompressedOnlyExtensionInstructionData { - delegated_amount: tlv_delegated_amounts[i], - withheld_transfer_fee: tlv_withheld_fees[i], - is_frozen: rng.gen_bool(0.2), // 20% chance of frozen - }, - ); + let is_frozen = rng.gen_bool(0.2); // 20% chance of frozen + let ext = ExtensionInstructionData::CompressedOnly( + CompressedOnlyExtensionInstructionData { + delegated_amount: tlv_delegated_amounts[i], + withheld_transfer_fee: tlv_withheld_fees[i], + is_frozen, + }, + ); + is_frozen_flags.push(is_frozen); @@ - } else { - tlv_instruction_data_vecs.push(vec![]); + } else { + tlv_instruction_data_vecs.push(vec![]); + is_frozen_flags.push(false); @@ - set_output_compressed_account( + set_output_compressed_account( output_account, &mut hash_cache, owner_pubkeys[index], output_delegate, amounts[index], lamports.as_ref().and_then(|l| l[index]), mint_pubkey, merkle_tree_indices[index], version, tlv_slice, - false, // Not frozen in tests + is_frozen_flags[index], ) @@ - let token_data = AnchorTokenData { + let is_frozen = is_frozen_flags[i]; + let token_data = AnchorTokenData { mint: mint_pubkey, owner: owner_pubkeys[i], amount: amounts[i], delegate: token_delegate, - state: AccountState::Initialized as u8, + state: if is_frozen { + AccountState::Frozen as u8 + } else { + AccountState::Initialized as u8 + }, tlv: tlv.clone(), };This keeps TLV
is_frozen, token state, and hashing all in sync and actually tests the frozen path.Also applies to: 114-135, 137-145, 159-181, 191-215
programs/compressed-token/anchor/src/lib.rs (1)
311-312: UpdateUnsupportedTlvExtensionTypeerror message to reflect all supported extensions.The message still claims “only CompressedOnly is currently implemented”, but the interface now supports multiple TLV extensions (TokenMetadata, CompressedOnly, PausableAccount, PermanentDelegateAccount, TransferFeeAccount, TransferHookAccount, etc.). Consider either listing the supported set or using a generic “Unsupported TLV extension type” message so it stays accurate as support evolves.
In the current light-ctoken-interface crate, which TLV extension variants are defined in `ExtensionStruct` and `ZExtensionInstructionData`, and which of them are actually supported by the compressed-token program?programs/compressed-token/program/tests/multi_sum_check.rs (1)
92-92: Decimals field updated but remains unvalidated.The
decimals: 9values are now set in theCompressionstructs (previously 0), butsum_check_multi_mintstill performs no validation on this field—it only checks balance arithmetic. If the decimals field is meant to align with mint metadata, consider adding a separate validation step. Otherwise, document why this field is included but not checked.Also, per previous review feedback: this test file should be moved from
programs/compressed-token/program/tests/toprogram-tests/to comply with coding guidelines for integration tests.Also applies to: 357-357
sdk-libs/client/src/indexer/types.rs (1)
887-893: Extract duplicated TLV deserialization and improve error handling.The TLV deserialization logic is duplicated identically in both
TokenAccount(lines 887-893) andTokenAccountV2(lines 928-934). Additionally, both base64 decode and Borsh deserialization errors are mapped to the genericInvalidResponseData, making debugging difficult.Recommend:
- Extract into a shared helper function:
parse_indexer_tlv(tlv: &str) -> Result<Vec<ExtensionStruct>, IndexerError>- Add specific error variants (
InvalidBase64Data,BorshDeserializationError) to preserve failure context- Add integration tests in
sdk-tests/covering valid/malformed TLVs- Document why indexer returns Borsh-serialized
Vec<ExtensionStruct>and SDK/indexer version coordinationAlso applies to: 928-934
program-libs/ctoken-interface/src/state/ctoken/size.rs (1)
18-31:⚠️ Silent error handling could lead to undersized allocations.The function uses
unwrap_or(0)on Line 25 when calculating extension sizes. IfExtensionStruct::byte_len(ext)returns an error, treating it as zero bytes could cause account allocation to be undersized, leading to buffer overflows or data truncation during serialization.Consider propagating errors explicitly or at minimum using
.expect()with a descriptive message if errors are truly impossible for valid configs.🔎 Recommended approach
-pub fn calculate_ctoken_account_size(extensions: Option<&[ExtensionStructConfig]>) -> usize { +pub fn calculate_ctoken_account_size(extensions: Option<&[ExtensionStructConfig]>) -> Result<usize, light_zero_copy::errors::ZeroCopyError> { let mut size = BASE_TOKEN_ACCOUNT_SIZE as usize; if let Some(exts) = extensions { if !exts.is_empty() { size += 4; // Vec length prefix for ext in exts { - size += ExtensionStruct::byte_len(ext).unwrap_or(0); + size += ExtensionStruct::byte_len(ext)?; } } } - size + Ok(size) }Then update callers to handle the Result.
program-libs/ctoken-interface/tests/ctoken/size.rs (1)
1-57: Test coverage is comprehensive; consider named constants for clarity.The test thoroughly exercises various extension combinations (base, pausable, permanent_delegate, transfer_fee, transfer_hook, and all combinations). However, hard-coded numeric literals (263, 264, 271, 275) could be replaced with named constants for better maintainability and clearer test intent.
For example:
const PAUSABLE_ONLY_SIZE: usize = 263; const PAUSABLE_PERMANENT_DELEGATE_SIZE: usize = 264;That said, the current implementation works correctly and documents the exact expected sizes.
forester/src/compressible/compressor.rs (1)
140-148: Index collision issue: using 0 as sentinel for "no delegate" is ambiguous.The
unwrap_or(0)pattern creates ambiguity—index 0 is returned both when no delegate exists (sentinel) and when the delegate is the first packed account (the owner). Downstream handlers cannot distinguish between these cases.Consider using
Option<u8>inCompressAndCloseIndicesto represent the delegate index, whereNoneclearly indicates no delegate.programs/compressed-token/program/tests/mint.rs (1)
401-420: CompressionInfo reconstruction is correct but verbose.The manual field-by-field reconstruction works but creates maintenance overhead. A previous review already suggested implementing
From<&ZCompressionInfo>forCompressionInfoto reduce this boilerplate—that remains a good refactoring target.program-libs/ctoken-interface/tests/cross_deserialization.rs (1)
83-98: Solid regression tests for account type discrimination.These tests effectively verify that the discriminator at byte 165 prevents type confusion attacks. The test structure covers both zero-copy and Borsh deserialization paths.
Note: A previous review suggested deriving
ACCOUNT_TYPE_OFFSETprogrammatically to catch layout changes earlier—that suggestion still applies here.forester/src/compressible/state.rs (1)
21-22:Rent::default()uses hardcoded mainnet values.As flagged in a prior review,
Rent::default()returns hardcoded mainnet parameters. In test environments or if Solana updates rent parameters, this could produce incorrect compressible slot calculations. Consider acceptingRentas a parameter or fetching it via RPC.programs/compressed-token/program/src/transfer2/cpi.rs (1)
54-59: TLV boolean flag and cast concerns remain from prior review.A previous review flagged two issues here:
- Line 56: The
tlvboolean is hardcoded totrue, but should be!tlv_config.is_empty()to correctly signal when TLV data exists.- Line 58-59: The
as u32cast is unchecked; prefer.try_into().map_err(...)for explicit overflow handling.🔎 Suggested fix
let token_config = TokenDataConfig { delegate: (has_delegate, ()), - tlv: (true, tlv_config), + tlv: (!tlv_config.is_empty(), tlv_config), }; - TokenData::byte_len(&token_config).map_err(|_| ProgramError::InvalidAccountData)? - as u32 + TokenData::byte_len(&token_config) + .map_err(|_| ProgramError::InvalidAccountData)? + .try_into() + .map_err(|_| ProgramError::InvalidAccountData)?programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs (2)
28-28: Documentation still doesn't match validation logic.The comment says "rent_payment must be 0 or >= 2" but the validation only rejects
rent_payment == 1. This was flagged in a previous review. The code accepts values like 1000 epochs without checking againstmax_funded_epochs.
61-65: Missing upper bound validation for rent_payment.As noted in a previous review, there's no check against
config.rent_config.max_funded_epochs. Large values could cause excessive lamport transfers or arithmetic issues in downstream rent calculations.programs/registry/src/compressible/compressed_token/compress_and_close.rs (4)
100-104: Use appropriate error variant for CToken parsing failure.
RegistryError::InvalidSignerdoesn't describe a CToken parsing failure. This was noted in a previous review—consider usingRegistryError::InvalidTokenAccountDatafor consistency with line 97-98.
109-111: Document the AccountState frozen constant.Using
ctoken.state == 2as a magic number was flagged before. Add a comment or constant:// AccountState: Uninitialized=0, Initialized=1, Frozen=2 let is_frozen = ctoken.state == 2;
137-148: Preserve delegated_amount when delegate exists without marker extensions.This was flagged as critical in a previous review: when an account has
delegated_amount > 0but no marker extensions, the CompressedOnly extension isn't created and delegated_amount is lost.Fix: create CompressedOnly whenever
has_marker_extensions || delegated_amount > 0.🔎 Proposed fix
- if has_marker_extensions { + if has_marker_extensions || delegated_amount > 0 { out_tlv.push(vec![ExtensionInstructionData::CompressedOnly(
36-38: Sentinel value for delegate_index is ambiguous.Index 0 is a valid packed_accounts index, yet the comment says "0 if no delegate". The actual safety net is
has_delegate: delegated_amount > 0at line 157, but this indirect approach is fragile.Consider using
u8::MAX(255) as the sentinel value, or document that index 0 cannot be used for delegates.program-libs/ctoken-interface/src/state/ctoken/borsh.rs (1)
148-158: Inconsistent error handling between optional field reads and CompressionInfo deserialization.The deserialization uses
let _ = buf.read_exact(...)to silently ignore errors foraccount_type,decimals, andcompression_only(backward compatibility with shorter legacy accounts). However,CompressionInfo::deserialize_reader(buf)?on line 154 propagates errors.This creates a subtle inconsistency:
- If reading legacy data where bytes exist for
account_type/decimals/compression_onlybut not forCompressionInfo, the function will fail- If reading even older data where none of these fields exist, it will also fail on
CompressionInfoConsider making
CompressionInfodeserialization consistent with the tolerant pattern:🔎 Suggested fix for consistent fallback behavior
- // Read compression (CompressionInfo) - let compression = CompressionInfo::deserialize_reader(buf)?; + // Read compression (CompressionInfo) - use default if not present for backward compatibility + let compression = CompressionInfo::deserialize_reader(buf).unwrap_or_default();This assumes
CompressionInfoimplementsDefault. If not, you may need to define a sensible default value.programs/compressed-token/program/src/shared/owner_validation.rs (1)
57-77: Verbose failure logging already flagged.program-libs/ctoken-interface/src/instructions/transfer2/instruction_data.rs (1)
34-37: TLV type change already flagged as breaking.programs/compressed-token/program/src/transfer2/check_extensions.rs (1)
125-128: Uncomment Compress mode validation—already flagged.programs/compressed-token/program/src/ctoken_approve_revoke.rs (2)
111-117:saturating_add(1)andcurrent_slotusage already flagged.
130-137: Redundant max_top_up enforcement already flagged.programs/compressed-token/program/src/shared/token_input.rs (1)
95-108: Add explanatory comment for TLV extension filtering.The conversion correctly filters to only
CompressedOnlyextension since other variants (Pausable, PermanentDelegate, TransferHook) are marker extensions referencing mint configuration rather than account-specific state data. Consider adding a brief comment to clarify this intentional filtering for future maintainers:// Only CompressedOnly carries account state data; marker extensions are metadata-onlyprograms/compressed-token/program/src/close_token_account/processor.rs (2)
115-120: State validation uses raw u8 comparison instead of enum match.The pattern
state if state == AccountState::Initialized as u8works but is fragile. Consider adding a comment documenting the discriminant mapping:// AccountState discriminants: Uninitialized=0, Initialized=1, Frozen=2
198-209: Compression authority incentive extraction could underflow in edge cases.When
authority == compression_authority, thechecked_subcorrectly prevents underflow. Consider adding a more descriptive error message for debugging:lamports_to_rent_sponsor = lamports_to_rent_sponsor .checked_sub(compression_cost) - .ok_or(ProgramError::InsufficientFunds)?; + .ok_or_else(|| { + msg!("Insufficient lamports for compression incentive"); + ProgramError::InsufficientFunds + })?;programs/compressed-token/program/src/transfer2/compression/ctoken/inputs.rs (2)
103-112: Delegate lookup silently swallows errors via.ok().When
has_delegate()returns true butpacked_accounts.get_u8()fails, the error is silently converted toNone. This could mask legitimate account resolution failures. Consider propagating or logging the error:🔎 Suggested improvement
let input_delegate = matching_input_index.and_then(|idx| { let input = inputs.in_token_data.get(idx)?; if input.has_delegate() { - packed_accounts + match packed_accounts .get_u8(input.delegate, "input delegate") - .ok() + { + Ok(delegate) => Some(delegate), + Err(e) => { + msg!("Failed to resolve input delegate at index {}: {:?}", input.delegate, e); + None + } + } } else { None } });
88-94: Clarify that mint comparison uses packed account indices.The matching logic
input.mint == compression.mintcompares packed account indices, not actual mint pubkeys. Adding a brief comment would help future readers understand this:// Match by mint index (both are packed account indices, not pubkeys)programs/compressed-token/program/src/transfer2/compression/ctoken/compress_or_decompress_ctokens.rs (3)
50-54: Redundant uninitialized account check.This check duplicates validation already performed by
CToken::zero_copy_at_mut_checkedat line 48. The defensive check adds code without preventing any new failure mode.
67-70: Magic number 2 for frozen state should be a constant.The frozen state check
ctoken.meta.state == 2uses a magic number. Define a constant for clarity:const ACCOUNT_STATE_FROZEN: u8 = 2;Then use
ctoken.meta.state == ACCOUNT_STATE_FROZENhere and elsewhere.
161-181: Delegate validation gap remains when CToken has delegate but input has delegated_amount without delegate pubkey.When CToken has a delegate and the input has
delegated_amount > 0butinput_delegateisNone, the code at line 172 falls through without validation. The check at line 177-180 only catches this when CToken has no delegate.🔎 Proposed fix
// Validate delegate compatibility if let Some(ctoken_delegate) = ctoken.delegate() { // CToken has a delegate - check if it matches the input delegate if let Some(input_del) = input_delegate_pubkey.as_ref() { if ctoken_delegate.to_bytes() != input_del.to_bytes() { msg!( "Decompress delegate mismatch: CToken delegate {:?} != input delegate {:?}", ctoken_delegate.to_bytes(), input_del.to_bytes() ); return Err(ErrorCode::DecompressDelegateMismatch.into()); } + } else if delegated_amount > 0 { + // CToken has delegate but input has delegated_amount without delegate info + msg!("Decompress: CToken has delegate but input has delegated_amount without delegate pubkey"); + return Err(CTokenError::InvalidAccountData.into()); } // Delegates match - add to delegated_amountprogram-libs/ctoken-interface/tests/ctoken/spl_compat.rs (1)
87-92: Use constant instead of magic number for consistency.Line 91 uses the literal
2while line 406 usesACCOUNT_TYPE_TOKEN_ACCOUNT. For maintainability, use the constant consistently throughout the file.🔎 Suggested fix
// Set account_type byte at position 165 to ACCOUNT_TYPE_TOKEN_ACCOUNT (2) - account_data[165] = 2; + account_data[165] = ACCOUNT_TYPE_TOKEN_ACCOUNT;program-libs/ctoken-interface/src/state/extensions/extension_struct.rs (1)
122-177: Discriminant values still hardcoded as magic numbers.The discriminant literals (19, 27, 28, 29, 30, 31) are used in both
zero_copy_at_mutandnew_zero_copymatch arms. This duplicates values across multiple locations, making future changes error-prone.Consider extracting to named constants:
const TOKEN_METADATA_DISCRIMINANT: u8 = 19; const PAUSABLE_ACCOUNT_DISCRIMINANT: u8 = 27; const PERMANENT_DELEGATE_ACCOUNT_DISCRIMINANT: u8 = 28; const TRANSFER_FEE_ACCOUNT_DISCRIMINANT: u8 = 29; const TRANSFER_HOOK_ACCOUNT_DISCRIMINANT: u8 = 30; const COMPRESSED_ONLY_DISCRIMINANT: u8 = 31;programs/compressed-token/anchor/src/instructions/create_token_pool.rs (1)
62-85: Consider validating token_program is a known SPL program.The CPI to
initialize_account3usestoken_program.keydirectly. While Anchor'sInterface<'info, TokenInterface>constraint should enforce this in the context struct, this helper function could theoretically be called with an arbitrary account.🔎 Optional: Add program ID validation
pub fn initialize_token_account<'info>( token_account: &AccountInfo<'info>, mint: &AccountInfo<'info>, authority: &AccountInfo<'info>, token_program: &AccountInfo<'info>, ) -> Result<()> { + // Validate token program is SPL Token or Token-2022 + if *token_program.key != spl_token::id() && *token_program.key != spl_token_2022::id() { + return Err(crate::ErrorCode::InvalidTokenProgram.into()); + } let ix = spl_token_2022::instruction::initialize_account3(program-libs/ctoken-interface/src/state/mint/zero_copy.rs (1)
359-370: Validation order inconsistency between checked methods.
zero_copy_at_checked(lines 330-337) checks:
is_cmint_account()firstis_initialized()second
zero_copy_at_mut_checked(lines 362-367) checks:
is_initialized()firstis_cmint_account()secondWhile both checks must pass, consistent ordering improves maintainability.
🔎 Suggested fix
let (mint, remaining) = CompressedMint::zero_copy_at_mut(bytes) .map_err(|_| CTokenError::CMintDeserializationFailed)?; - if !mint.is_initialized() { - return Err(CTokenError::CMintNotInitialized); - } if !mint.is_cmint_account() { return Err(CTokenError::InvalidAccountType); } + if !mint.is_initialized() { + return Err(CTokenError::CMintNotInitialized); + } Ok((mint, remaining))programs/compressed-token/program/src/extensions/check_mint_extensions.rs (3)
134-136:unwrap_or_default()silently swallows extension type enumeration errors.If
get_extension_types()fails, this returns an empty vector andhas_restricted_extensionsbecomesfalse—potentially allowing restricted mints to bypass validation.🔎 Suggested improvement
- let extension_types = mint_state.get_extension_types().unwrap_or_default(); + let extension_types = mint_state.get_extension_types().map_err(|e| { + msg!("Failed to get extension types: {:?}", e); + ProgramError::InvalidAccountData + })?;
218-219: Sameunwrap_or_default()issue inhas_mint_extensions.This duplicates the error-swallowing pattern. Consider propagating the error to prevent silently treating parse failures as "no extensions."
73-93: Heap allocation incalculate_account_sizecould be avoided.
Vec::new()allocates on every call. Since you're only counting extensions, consider accumulating size directly without building a vector.🔎 Suggested refactor using stack allocation
pub fn calculate_account_size(&self) -> u64 { - let mut extensions = Vec::new(); - if self.has_pausable { - extensions.push(ExtensionStructConfig::PausableAccount(())); - } - // ... more pushes ... - let exts = if extensions.is_empty() { - None - } else { - Some(extensions.as_slice()) - }; - light_ctoken_interface::state::calculate_ctoken_account_size(exts) as u64 + // Use fixed-size stack array + let mut extensions: [ExtensionStructConfig; 4] = [ + ExtensionStructConfig::PausableAccount(()), + ExtensionStructConfig::PermanentDelegateAccount(()), + ExtensionStructConfig::TransferFeeAccount(()), + ExtensionStructConfig::TransferHookAccount(()), + ]; + let mut count = 0usize; + if self.has_pausable { count += 1; } + if self.has_permanent_delegate { count += 1; } + if self.has_transfer_fee { count += 1; } + if self.has_transfer_hook { count += 1; } + // Note: This approach needs refinement to only include active extensions + // Consider direct size calculation instead + light_ctoken_interface::state::calculate_ctoken_account_size( + if count == 0 { None } else { Some(&extensions[..count]) } + ) as u64 }
| // Check if account is a valid CToken account (account_type == 2) | ||
| if !ctoken.is_ctoken_account() { | ||
| debug!("Skipping account {} without compressible config", pubkey); | ||
| return Ok(false); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Log message may be misleading — the check is for account_type, not "compressible config".
The is_ctoken_account() method checks whether account_type == 2 (i.e., a CToken account type). The debug message "without compressible config" could confuse future maintainers since the actual validation is about account type, not configuration presence.
Consider updating the message to better reflect what's being checked:
🔎 Suggested fix
// Check if account is a valid CToken account (account_type == 2)
if !ctoken.is_ctoken_account() {
- debug!("Skipping account {} without compressible config", pubkey);
+ debug!("Skipping account {} - not a CToken account (account_type != 2)", pubkey);
return Ok(false);
}🤖 Prompt for AI Agents
In forester/src/compressible/bootstrap.rs around lines 116 to 120, the debug
message incorrectly says "without compressible config" even though
is_ctoken_account() is checking account_type == 2; change the log to accurately
reflect the account-type check (e.g., "Skipping account {}: not a CToken
account" or "Skipping account {}: account_type != CToken (2)") so the message
matches the actual condition being evaluated.
program-libs/ctoken-interface/src/instructions/create_associated_token_account.rs
Show resolved
Hide resolved
| /// decimals for spl token Compression/Decompression (used in transfer_checked) | ||
| /// rent_sponsor_is_signer flag for CompressAndClose (non-zero = true) | ||
| pub decimals: u8, | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Document the dual-purpose semantics more prominently.
The decimals field serves two different purposes depending on CompressionMode:
- For
Compress/Decompress: actual SPL token decimals - For
CompressAndClose: boolean flag (rent_sponsor_is_signer)
This overloading is documented in comments but could cause confusion. Consider either:
- Adding a helper method like
rent_sponsor_is_signer(&self) -> boolfor the CompressAndClose case - Using a union-style comment pattern at the struct level
🤖 Prompt for AI Agents
In program-libs/ctoken-interface/src/instructions/transfer2/compression.rs
around lines 71 to 74, the struct's `decimals: u8` field is dual-purpose (actual
SPL decimals for Compress/Decompress and a boolean `rent_sponsor_is_signer` flag
for CompressAndClose); add a clear, small fix: implement a helper method on the
struct like `fn rent_sponsor_is_signer(&self) -> bool` that returns
`self.decimals != 0` for the CompressAndClose case and update the doc comment to
call out the helper, or alternatively expand the struct-level comment to use a
union-style description that explicitly documents the two semantic
interpretations and references the helper to avoid confusion.
| #[profile] | ||
| #[inline(always)] | ||
| pub fn build_mint_extension_cache<'a>( | ||
| inputs: &ZCompressedTokenInstructionDataTransfer2, | ||
| packed_accounts: &'a ProgramPackedAccounts<'a, AccountInfo>, | ||
| deny_restricted_extensions: bool, // true if has_output_compressed_accounts | ||
| ) -> Result<MintExtensionCache, ProgramError> { | ||
| let mut cache: MintExtensionCache = ArrayMap::new(); | ||
|
|
||
| // Collect mints from input token data | ||
| for input in inputs.in_token_data.iter() { | ||
| let mint_index = input.mint; | ||
| if cache.get_by_key(&mint_index).is_none() { | ||
| let mint_account = packed_accounts.get_u8(mint_index, "mint cache: input")?; | ||
| let checks = check_mint_extensions(mint_account, deny_restricted_extensions)?; | ||
| cache.insert(mint_index, checks, ErrorCode::MintCacheCapacityExceeded)?; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider early-exit optimization for empty inputs.
If in_token_data and compressions are both empty, the function still allocates an empty cache. This is minor, but an early return could save a few cycles.
🔎 Optional early exit:
pub fn build_mint_extension_cache<'a>(
inputs: &ZCompressedTokenInstructionDataTransfer2,
packed_accounts: &'a ProgramPackedAccounts<'a, AccountInfo>,
deny_restricted_extensions: bool,
) -> Result<MintExtensionCache, ProgramError> {
+ // Fast path: no mints to check
+ if inputs.in_token_data.is_empty() && inputs.compressions.as_ref().map_or(true, |c| c.is_empty()) {
+ return Ok(ArrayMap::new());
+ }
+
let mut cache: MintExtensionCache = ArrayMap::new();🤖 Prompt for AI Agents
In programs/compressed-token/program/src/transfer2/check_extensions.rs around
lines 63 to 80, add an early-exit: before allocating the MintExtensionCache,
detect if inputs.in_token_data.is_empty() AND inputs.compressions.is_empty() and
immediately return Ok(empty cache) to avoid unnecessary allocation and work;
implement this by checking those two collections and returning the appropriate
empty MintExtensionCache wrapped in Ok instead of continuing to build and
iterate.
| // Collect mints from compressions | ||
| if let Some(compressions) = inputs.compressions.as_ref() { | ||
| for compression in compressions.iter() { | ||
| let mint_index = compression.mint; | ||
|
|
||
| if cache.get_by_key(&mint_index).is_none() { | ||
| let mint_account = packed_accounts.get_u8(mint_index, "mint cache: compression")?; | ||
| let checks = if compression.mode == ZCompressionMode::CompressAndClose { | ||
| check_mint_extensions( | ||
| mint_account, | ||
| false, // Allow restricted extensions, also if instruction has has_output_compressed_accounts | ||
| )? | ||
| } else { | ||
| check_mint_extensions(mint_account, deny_restricted_extensions)? | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Compression mode branching affects deny_restricted_extensions flag.
For CompressAndClose mode, you pass false to allow restricted extensions (with subsequent CompressedOnly requirement), while other modes use the caller's flag. This asymmetry is intentional but worth a quick doc comment to make the rationale obvious to future maintainers.
🔎 Suggested clarification:
if cache.get_by_key(&mint_index).is_none() {
let mint_account = packed_accounts.get_u8(mint_index, "mint cache: compression")?;
let checks = if compression.mode == ZCompressionMode::CompressAndClose {
+ // CompressAndClose allows restricted extensions but requires CompressedOnly output
check_mint_extensions(
mint_account,
false, // Allow restricted extensions, also if instruction has has_output_compressed_accounts
)?🤖 Prompt for AI Agents
programs/compressed-token/program/src/transfer2/check_extensions.rs around lines
82-96: the branch passes false for CompressAndClose to allow restricted
extensions (while other modes use deny_restricted_extensions), which is
intentional but not obvious; add a concise doc comment above this block
explaining that CompressAndClose purposely allows restricted extensions here
because those will be later enforced as CompressedOnly (or by downstream
checks), while other modes should respect the caller's
deny_restricted_extensions flag, and mention any invariant or post-check that
enforces the CompressedOnly requirement so future maintainers understand the
rationale.
| let mint_config_with_ext = CompressedMintConfig { | ||
| base: (), | ||
| metadata: (), | ||
| extensions: (true, extensions_config.clone()), | ||
| extensions: Some(extensions_config.clone()), | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Unnecessary .clone() on extensions_config.
The extensions_config vector is moved into mint_config_with_ext and isn't used afterward in this test. The clone is redundant.
🔎 Suggested fix
let mint_config_with_ext = CompressedMintConfig {
- extensions: Some(extensions_config.clone()),
+ extensions: Some(extensions_config),
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let mint_config_with_ext = CompressedMintConfig { | |
| base: (), | |
| metadata: (), | |
| extensions: (true, extensions_config.clone()), | |
| extensions: Some(extensions_config.clone()), | |
| }; | |
| let mint_config_with_ext = CompressedMintConfig { | |
| extensions: Some(extensions_config), | |
| }; |
🤖 Prompt for AI Agents
In programs/compressed-token/program/tests/allocation_test.rs around lines 43 to
45, the extensions_config vector is unnecessarily cloned when constructing
CompressedMintConfig; remove the .clone() and pass extensions_config directly
into extensions: Some(extensions_config) so the value is moved (since it isn't
used later in the test).
| // Get compression info from meta | ||
| let compression = &ctoken_account.meta.compression; | ||
| let authority_index = packed_accounts.insert_or_get_config( | ||
| Pubkey::from(compression.compression_authority), | ||
| true, | ||
| true, | ||
| ); | ||
| let rent_sponsor_index = packed_accounts.insert_or_get(Pubkey::from(compression.rent_sponsor)); | ||
| // When compression authority closes, everything goes to rent sponsor | ||
| let destination_index = rent_sponsor_index; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Direct field access to meta.compression risks panic if None.
Lines 50-58 access ctoken_account.meta.compression directly without checking if compression metadata exists. The relevant snippet from account2.rs shows compression() returns Option<&Compression>, indicating this field can be absent. If compression is None, this code will panic at runtime.
Additionally, setting destination_index = rent_sponsor_index (line 58) routes all funds to the rent sponsor. Based on learnings about lamport distribution on close, unutilized lamports should go to the user's destination account, not the rent_sponsor. This conflates user funds with rent recovery.
Finally, marking the compression authority as writable (line 54, is_writable: true) appears unnecessary—the authority typically only signs but doesn't require mutable access.
🔎 Proposed fixes
- // Get compression info from meta
- let compression = &ctoken_account.meta.compression;
+ // Get compression info from meta - validate it exists
+ let compression = ctoken_account.meta.compression.as_ref()
+ .ok_or(CTokenSdkError::MissingCompressionMetadata)?;
+
let authority_index = packed_accounts.insert_or_get_config(
Pubkey::from(compression.compression_authority),
true,
- true,
+ false, // Authority only needs to sign, not be writable
);
let rent_sponsor_index = packed_accounts.insert_or_get(Pubkey::from(compression.rent_sponsor));
- // When compression authority closes, everything goes to rent sponsor
- let destination_index = rent_sponsor_index;
+ // User funds should go to owner, not rent_sponsor
+ let destination_index = owner_index; // or derive from closure contextNote: You'll need to add MissingCompressionMetadata to the CTokenSdkError enum.
Committable suggestion skipped: line range outside the PR's diff.
| // Get compression info from meta | ||
| let compression = &compressed_token.meta.compression; | ||
| let authority = Pubkey::from(compression.compression_authority); | ||
| let rent_sponsor = Pubkey::from(compression.rent_sponsor); | ||
|
|
||
| let destination_pubkey = if with_compression_authority { | ||
| actual_rent_sponsor | ||
| } else { | ||
| owner_pubkey | ||
| }; | ||
| // When compression authority closes, everything goes to rent sponsor | ||
| let destination_pubkey = rent_sponsor; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Same direct access and fund routing issues as lines 49-58.
This code segment has identical problems to the earlier segment:
-
Direct field access (line 274): Accesses
compressed_token.meta.compressionwithout validating it exists, risking a panic. -
Incorrect fund routing (line 279): Setting
destination_pubkey = rent_sponsorroutes all closure proceeds to the rent sponsor instead of separating user funds from rent recovery. Per the learnings on account closure, "unutilized lamports → destination account" (for the user), while "completed epoch rent → rent recipient."
🔎 Proposed fix
- // Get compression info from meta
- let compression = &compressed_token.meta.compression;
+ // Get compression info from meta - validate it exists
+ let compression = compressed_token.meta.compression.as_ref()
+ .ok_or(CTokenSdkError::MissingCompressionMetadata)?;
+
let authority = Pubkey::from(compression.compression_authority);
let rent_sponsor = Pubkey::from(compression.rent_sponsor);
- // When compression authority closes, everything goes to rent sponsor
- let destination_pubkey = rent_sponsor;
+ // User funds should be returned to the owner, not conflated with rent
+ let destination_pubkey = owner_pubkey;Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In sdk-libs/ctoken-sdk/src/compressed_token/v2/compress_and_close.rs around
lines 273-279, the code unsafely accesses compressed_token.meta.compression and
routes all closure proceeds to the rent_sponsor; instead, first validate that
compressed_token.meta and its compression field exist (use a match or as_ref()
and return a clear error if missing) and extract compression_authority and
rent_sponsor from that validated compression; then set the closure destination
for unutilized lamports to the token owner (e.g., compressed_token.owner) and
reserve rent recovery/distribution to rent_sponsor (do not set
destination_pubkey = rent_sponsor), ensuring you assign destination_pubkey to
the token owner and use a separate rent_recipient variable for rent_sponsor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (1)
program-libs/ctoken-interface/tests/ctoken/failing.rs
🧰 Additional context used
📓 Path-based instructions (1)
program-libs/**/*.rs
📄 CodeRabbit inference engine (CLAUDE.md)
Unit tests in program-libs must not depend on light-test-utils; tests requiring light-test-utils must be located in program-tests/
Files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
🧠 Learnings (37)
📓 Common learnings
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Use compressible token account extensions that allow accounts to be compressed back into compressed state with rent payment mechanisms
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: All compressed token account implementations must reference and comply with the account layout specifications in programs/compressed-token/program/docs/ACCOUNTS.md
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Implement all 8 compressed token instructions: create_cmint, mint_to_ctoken, create_token_account_invoke, create_token_account_invoke_signed, create_ata_invoke, create_ata_invoke_signed, transfer_interface_invoke, and transfer_interface_invoke_signed
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/README.md:0-0
Timestamp: 2025-11-24T17:54:38.537Z
Learning: Implement compressed token program interfaces for third-party token creation and usage on Solana using ZK Compression
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/transfer2/*.rs : Transfer2 instruction (discriminator: 101) supports batch operations including Compress, Decompress, and CompressAndClose with multi-mint support and sum checks
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/docs/instructions/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:01:54.689Z
Learning: Applies to programs/compressed-token/program/docs/instructions/instructions/MINT_ACTION.md : MintAction documentation must cover batch instruction for compressed mint management supporting 9 actions: CreateCompressedMint, MintTo, UpdateMintAuthority, UpdateFreezeAuthority, CreateSplMint, MintToCToken, UpdateMetadataField, UpdateMetadataAuthority, RemoveMetadataKey
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/anchor/README.md:0-0
Timestamp: 2025-11-24T17:54:33.614Z
Learning: Implement the Compressed Token Program interface for creating and using compressed tokens on Solana with ZK Compression
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-tests/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:01:30.012Z
Learning: Run compressed token core tests using `cargo test-sbf -p compressed-token-test --test ctoken`, `--test v1`, `--test mint`, and `--test transfer2`
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/mint_action/*.rs : MintAction instruction (discriminator: 103) must support 9 action types: CreateCompressedMint, MintTo, UpdateMintAuthority, UpdateFreezeAuthority, CreateSplMint, MintToCToken, UpdateMetadataField, UpdateMetadataAuthority, RemoveMetadataKey
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: The codebase provides rent calculation algorithms and account structure definitions used across light-compressed-token, light-registry, compressed-token-sdk, and related programs
📚 Learning: 2025-12-06T00:49:57.458Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-token-test/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:57.458Z
Learning: Applies to sdk-tests/sdk-token-test/**/*test.rs : Tests should use light-ctoken-sdk functions from sdk-libs/compressed-token-sdk for testing ctoken instructions
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-12-07T03:17:28.803Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Applies to sdk-tests/sdk-ctoken-test/**/{lib,main}.rs : Clone AccountInfo structs when building CPI builder pattern account structs to avoid borrow checker issues
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-12-07T03:17:28.803Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Applies to sdk-tests/sdk-ctoken-test/**/{lib,main}.rs : Use the builder pattern from `light-ctoken-sdk::ctoken` module for CPI operations instead of manual instruction building
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-11-24T18:01:30.012Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-tests/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:01:30.012Z
Learning: Run compressed token core tests using `cargo test-sbf -p compressed-token-test --test ctoken`, `--test v1`, `--test mint`, and `--test transfer2`
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-12-07T18:09:57.240Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/CONFIG_ACCOUNT.md:0-0
Timestamp: 2025-12-07T18:09:57.240Z
Learning: Applies to program-libs/compressible/docs/**/*.rs : CompressibleConfig account size must be exactly 256 bytes including the 8-byte discriminator to maintain compatibility with Light Protocol Solana programs
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-11-24T18:01:30.012Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-tests/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:01:30.012Z
Learning: Run Light system program compression tests using `cargo test-sbf -p system-test -- test_with_compression` and `cargo test-sbf -p system-test --test test_re_init_cpi_account` to test compressed account operations
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-12-07T03:17:28.803Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: sdk-tests/sdk-ctoken-test/README.md:0-0
Timestamp: 2025-12-07T03:17:28.803Z
Learning: Applies to sdk-tests/sdk-ctoken-test/**/Cargo.toml : Use path references in Cargo.toml dependencies pointing to `/Users/ananas/dev/light-protocol2/sdk-libs/` for light-ctoken-sdk, light-ctoken-types, light-sdk, light-sdk-types, and light-program-test
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-11-24T18:02:15.670Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/registry/CLAUDE.md:0-0
Timestamp: 2025-11-24T18:02:15.670Z
Learning: Applies to programs/registry/src/account_compression_cpi/mod.rs : Export new wrapper modules in `account_compression_cpi/mod.rs` using `pub mod new_operation;` and `pub use new_operation::*;`, then import in `lib.rs`
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-12-06T00:49:21.983Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: Applies to program-libs/compressible/src/config.rs : CompressibleConfig account structure must support serialization via Anchor, Pinocchio, and Borsh formats for Light Registry program integration
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-11-24T17:56:00.229Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/batched-merkle-tree/docs/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:56:00.229Z
Learning: Applies to program-libs/batched-merkle-tree/docs/**/Cargo.toml : Depend on light-compressed-account crate for compressed account types and utilities
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-12-06T00:49:21.983Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: Applies to program-libs/compressible/src/config.rs : Implement default initialization for CToken V1 config in CompressibleConfig structure
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: All compressed token account implementations must reference and comply with the account layout specifications in programs/compressed-token/program/docs/ACCOUNTS.md
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-11-24T17:56:50.011Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/batched-merkle-tree/docs/INITIALIZE_STATE_TREE.md:0-0
Timestamp: 2025-11-24T17:56:50.011Z
Learning: Applies to program-libs/batched-merkle-tree/docs/src/initialize_state_tree.rs : Return error `AccountError::InvalidAccountSize` (error code 12006) when account data length doesn't match calculated size requirements
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/create_token_account.rs : Create token account instructions (CreateTokenAccount, CreateAssociatedCTokenAccount, CreateAssociatedTokenAccountIdempotent) require ACTIVE config validation only
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-12-07T18:09:57.240Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/CONFIG_ACCOUNT.md:0-0
Timestamp: 2025-12-07T18:09:57.240Z
Learning: Applies to program-libs/compressible/docs/**/*.rs : CompressibleConfig account state in Solana programs must validate active state using `validate_active()` method before allowing new compressible token account creation
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-12-06T00:49:21.983Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/CLAUDE.md:0-0
Timestamp: 2025-12-06T00:49:21.983Z
Learning: Applies to program-libs/compressible/src/config.rs : Implement state validation methods (`validate_active`, `validate_not_inactive`) on CompressibleConfig account structure
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-12-07T18:09:57.240Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/compressible/docs/CONFIG_ACCOUNT.md:0-0
Timestamp: 2025-12-07T18:09:57.240Z
Learning: Applies to program-libs/compressible/docs/**/*.rs : Anchor deserialization of CompressibleConfig must use `AccountDeserialize::try_deserialize()` which automatically validates the discriminator to prevent invalid account type mismatches
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-11-24T17:58:50.237Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_CHECKS.md:0-0
Timestamp: 2025-11-24T17:58:50.237Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/checks.rs : Implement `check_data_is_zeroed` function to verify first N bytes are zero (uninitialized), returning `AccountNotZeroed` (20013) error if data is initialized
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-11-24T17:56:50.011Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/batched-merkle-tree/docs/INITIALIZE_STATE_TREE.md:0-0
Timestamp: 2025-11-24T17:56:50.011Z
Learning: Applies to program-libs/batched-merkle-tree/docs/src/initialize_state_tree.rs : Return error `AccountError::AccountNotRentExempt` (error code 12011) when account balance is insufficient for rent exemption at calculated size
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-11-24T17:56:20.711Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/batched-merkle-tree/docs/INITIALIZE_ADDRESS_TREE.md:0-0
Timestamp: 2025-11-24T17:56:20.711Z
Learning: Applies to program-libs/batched-merkle-tree/docs/src/initialize_address_tree.rs : Verify merkle_tree_account data length matches calculated size in `src/initialize_address_tree.rs`. Return error `AccountError::InvalidAccountSize` (error code: 12006) if account data length doesn't match calculated size requirements.
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-11-24T17:58:50.237Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_CHECKS.md:0-0
Timestamp: 2025-11-24T17:58:50.237Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/checks.rs : Implement `set_discriminator` function to set 8-byte discriminator on uninitialized account, returning `AlreadyInitialized` (20006) if discriminator is non-zero
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-11-24T17:56:50.011Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/batched-merkle-tree/docs/INITIALIZE_STATE_TREE.md:0-0
Timestamp: 2025-11-24T17:56:50.011Z
Learning: Applies to program-libs/batched-merkle-tree/docs/src/initialize_state_tree.rs : Use `check_account_balance_is_rent_exempt` function from `light-account-checks` to verify rent exemption for both queue_account and merkle_tree_account during initialization
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-12-07T03:17:42.217Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-12-07T03:17:42.217Z
Learning: Applies to program-libs/account-checks/**/packed_accounts.rs : Implement index-based account access in PackedAccounts with bounds-checked retrieval
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-11-24T17:57:53.312Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/batched-merkle-tree/docs/TREE_ACCOUNT.md:0-0
Timestamp: 2025-11-24T17:57:53.312Z
Learning: Applies to program-libs/batched-merkle-tree/docs/**/*.rs : State trees must initialize with zero bytes root for the specified height and have a separate BatchedQueueAccount for output operations
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-11-24T17:59:03.485Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_INFO_TRAIT.md:0-0
Timestamp: 2025-11-24T17:59:03.485Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/account_info/test_account_info.rs : Create a test-only AccountInfo mock implementation without external dependencies for unit testing AccountInfoTrait implementations
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-12-07T03:17:42.217Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-12-07T03:17:42.217Z
Learning: Applies to program-libs/account-checks/**/checks.rs : Implement permission check functions (`check_mut`, `check_non_mut`, `check_signer`) for account state validation
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-12-07T03:17:42.217Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/CLAUDE.md:0-0
Timestamp: 2025-12-07T03:17:42.217Z
Learning: Applies to program-libs/account-checks/**/account_info/test_account_info.rs : Provide mock TestAccountInfo implementation (feature: `test-only`) for unit testing account validation logic
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-11-24T17:58:50.237Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_CHECKS.md:0-0
Timestamp: 2025-11-24T17:58:50.237Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/checks.rs : Implement `check_mut` function to verify account is writable, returning `AccountNotMutable` (20002) error if read-only
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-11-24T17:58:50.237Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_CHECKS.md:0-0
Timestamp: 2025-11-24T17:58:50.237Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/checks.rs : Implement `check_discriminator` function to verify first 8 bytes match expected discriminator for type T, returning `InvalidAccountSize` (20004) if < 8 bytes or `InvalidDiscriminator` (20000) if mismatch
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-11-24T17:58:50.237Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_CHECKS.md:0-0
Timestamp: 2025-11-24T17:58:50.237Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/checks.rs : Implement `check_account_balance_is_rent_exempt` function to verify account size matches expected_size and account balance meets rent exemption requirement, returning rent exemption amount or errors `InvalidAccountSize` (20004), `InvalidAccountBalance` (20007), or `FailedBorrowRentSysvar` (20008)
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-11-24T17:56:20.711Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/batched-merkle-tree/docs/INITIALIZE_ADDRESS_TREE.md:0-0
Timestamp: 2025-11-24T17:56:20.711Z
Learning: Applies to program-libs/batched-merkle-tree/docs/src/initialize_address_tree.rs : Verify merkle_tree_account balance is rent-exempt in `src/initialize_address_tree.rs`. Use `check_account_balance_is_rent_exempt` from `light-account-checks` library. Return error `AccountError::AccountNotRentExempt` (error code: 12011) if account balance is insufficient for rent exemption at calculated size.
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-11-24T17:54:20.982Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/zero-copy-derive/README.md:0-0
Timestamp: 2025-11-24T17:54:20.982Z
Learning: Applies to program-libs/zero-copy-derive/**/*.rs : Custom types in zero-copy structs must implement the ZeroCopyAt trait
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-12-07T18:10:14.606Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: programs/compressed-token/program/CLAUDE.md:0-0
Timestamp: 2025-12-07T18:10:14.606Z
Learning: Applies to programs/compressed-token/program/src/ctoken_transfer.rs : CTokenTransfer instruction (discriminator: 3) must implement SPL-compatible transfers between decompressed accounts
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-11-24T17:58:50.237Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_CHECKS.md:0-0
Timestamp: 2025-11-24T17:58:50.237Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/checks.rs : Implement `check_account_info_non_mut` function to validate account is NOT writable, owned by program_id, and discriminator matches type T
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-11-24T17:58:50.237Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_CHECKS.md:0-0
Timestamp: 2025-11-24T17:58:50.237Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/checks.rs : Implement `check_account_info_mut` function to validate account is writable, owned by program_id, and discriminator matches type T
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
📚 Learning: 2025-11-24T17:58:50.237Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_CHECKS.md:0-0
Timestamp: 2025-11-24T17:58:50.237Z
Learning: Applies to program-libs/account-checks/docs/program-libs/account-checks/src/checks.rs : Implement `check_account_info` function to validate account ownership by program_id and verify discriminator matches type T
Applied to files:
program-libs/ctoken-interface/tests/ctoken/failing.rs
🧬 Code graph analysis (1)
program-libs/ctoken-interface/tests/ctoken/failing.rs (3)
program-libs/ctoken-interface/tests/ctoken/zero_copy_new.rs (1)
default_config(35-43)program-libs/ctoken-interface/tests/ctoken/spl_compat.rs (1)
default_config(27-35)program-libs/ctoken-interface/src/state/ctoken/zero_copy.rs (1)
zero_copy_at_checked(409-422)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
- GitHub Check: Test batched-merkle-tree-simulate
- GitHub Check: Test program-libs-slow
- GitHub Check: Test program-libs-fast
- GitHub Check: system-programs (sdk-libs, light-sdk-macros light-sdk light-program-test light-client light-ctoke...
- GitHub Check: system-programs (anchor & pinocchio, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -p sdk...
- GitHub Check: system-programs (native, ["cargo-test-sbf -p sdk-native-test", "cargo-test-sbf -p sdk-v1-native-t...
- GitHub Check: system-programs (token test, ["cargo-test-sbf -p sdk-token-test"])
- GitHub Check: programs (compressed-token-and-e2e, ["cargo test -p light-compressed-token", "cargo-test-sbf -p c...
- GitHub Check: programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test", "cargo test -p light-system-prog...
- GitHub Check: programs (account-compression-and-registry, ["cargo-test-sbf -p account-compression-test", "cargo...
- GitHub Check: programs (light-system-program-address, ["cargo-test-sbf -p system-test -- test_with_address", "c...
- GitHub Check: programs (system-cpi-test-v2-functional-account-infos, ["cargo-test-sbf -p system-cpi-v2-test -- ...
- GitHub Check: programs (system-cpi-test-v2-functional-read-only, ["cargo-test-sbf -p system-cpi-v2-test -- func...
- GitHub Check: cli-v1
- GitHub Check: stateless-js-v1
- GitHub Check: lint
- GitHub Check: cli-v2
- GitHub Check: stateless-js-v2
🔇 Additional comments (6)
program-libs/ctoken-interface/tests/ctoken/failing.rs (6)
1-6: LGTM! Clean import additions.The new imports (
Pubkey,BASE_TOKEN_ACCOUNT_SIZE) support the test refactoring and comply with the guideline that program-libs tests must not depend on light-test-utils.
19-28: LGTM! Clear test setup.Using
default_config()andBASE_TOKEN_ACCOUNT_SIZEmakes this test more maintainable and the intent clearer.
31-40: LGTM! Correct validation test.The test correctly validates that
zero_copy_at_checkedrejects uninitialized accounts (state byte = 0). UsingBASE_TOKEN_ACCOUNT_SIZEimproves maintainability.
43-52: LGTM! Test validates mutable variant correctly.This test appropriately mirrors
test_zero_copy_at_checked_uninitialized_accountfor the mutable API.Note: The AI summary indicates this test was removed, but it's present in the final code. This is just an inconsistency in the summary—the test itself is correct.
55-64: LGTM! Correct error expectation.The change to expect
ZeroCopyErroris correct—when the buffer is too small,zero_copy_atfails before the checked validation can execute. The comment clearly explains this ordering.
67-76: LGTM! Mutable variant test correct.Appropriately mirrors the immutable test with the correct error expectation for undersized buffers.
| fn default_config() -> CompressedTokenConfig { | ||
| CompressedTokenConfig { | ||
| mint: Pubkey::default(), | ||
| owner: Pubkey::default(), | ||
| state: 1, | ||
| compression_only: false, | ||
| extensions: None, | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider extracting the duplicated helper to a shared test module.
This default_config() helper is duplicated in at least three test files (zero_copy_new.rs, spl_compat.rs, and this file). Extract it to a common test utilities module (e.g., tests/common/mod.rs) to reduce duplication and simplify maintenance.
Example structure
Create tests/common/mod.rs:
use light_compressed_account::Pubkey;
use light_ctoken_interface::state::CompressedTokenConfig;
pub fn default_config() -> CompressedTokenConfig {
CompressedTokenConfig {
mint: Pubkey::default(),
owner: Pubkey::default(),
state: 1,
compression_only: false,
extensions: None,
}
}Then import in test files:
mod common;
use common::default_config;🤖 Prompt for AI Agents
In program-libs/ctoken-interface/tests/ctoken/failing.rs around lines 8 to 16,
the default_config() helper is duplicated across multiple test files; extract it
into a shared test utilities module (e.g., tests/common/mod.rs) and update the
tests to import and use that shared helper. Create tests/common/mod.rs that
exposes pub fn default_config() -> CompressedTokenConfig with the same body and
required imports (Pubkey and CompressedTokenConfig), then in failing.rs (and the
other test files) add mod common; and use common::default_config; (or use
crate::tests::common if using integration-style tests) and remove the local
duplicate function. Ensure module visibility and paths are correct so tests
compile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
programs/compressed-token/program/src/shared/compressible_top_up.rs (1)
100-103: Early exit condition may be unreachable.Since the CMint block (lines 51-74) always sets
current_slotat line 57-59, the conditioncurrent_slot == 0at line 101 will never be true when execution reaches this point. This early exit appears to be dead code.If this check was intended for a scenario where neither account has compression info, that's no longer possible with embedded compression in all accounts. Consider removing this block or updating the comment to clarify its purpose.
programs/compressed-token/program/src/shared/owner_validation.rs (1)
80-113: Update docstring to accurately reflect authorization checks.The implementation only validates owner and permanent delegate authority, not account delegate. While this is the correct design for mint-level compression operations, the docstring (line 81) and error message comment (line 111) are misleading—they mention account delegate support that isn't implemented. Update line 81 to read: "Allows owner or permanent delegate (from mint) to authorize compression operations."
♻️ Duplicate comments (28)
programs/compressed-token/program/src/ctoken_approve_revoke.rs (3)
107-111: Clarify the purpose ofsaturating_add(1)in budget calculation.The
+1added tomax_top_upat line 110 is unclear. Does this account for some off-by-one behavior inprocess_compression_top_up, or is it unnecessary? If the budget is meant to be exactlymax_top_uplamports, document why the extra lamport is needed. If it's not needed, remove it.🔎 Suggested clarification
Either remove the
+1:let mut lamports_budget = if max_top_up == 0 { u64::MAX } else { - (max_top_up as u64).saturating_add(1) + max_top_up as u64 };Or add a comment explaining its purpose:
let mut lamports_budget = if max_top_up == 0 { u64::MAX } else { + // +1 allows exact max_top_up after saturating_sub in process_compression_top_up (max_top_up as u64).saturating_add(1) };
113-119: Document or eliminate the unusedcurrent_slotoutput.The
&mut 0passed ascurrent_slottoprocess_compression_top_upis never read afterward. If this is required by the function signature but the value isn't needed here, make the intent explicit by renaming to_current_slotor adding a brief comment.🔎 Suggested cleanup
+ // current_slot output is unused here but required by function signature process_compression_top_up( &ctoken.base.compression, account, &mut 0, &mut transfer_amount, &mut lamports_budget, )?;Or use a named binding:
+ let mut _current_slot = 0; process_compression_top_up( &ctoken.base.compression, account, - &mut 0, + &mut _current_slot, &mut transfer_amount, &mut lamports_budget, )?;
124-133: Fix the budget enforcement logic.The condition at line 125 has a subtle bug:
lamports_budget != 0 && transfer_amount > lamports_budgetWhen
max_top_up == 0(unlimited),lamports_budgetis set tou64::MAX. The checklamports_budget != 0is true, so we comparetransfer_amount > u64::MAX, which can never be true. This works but is convoluted.More critically, the condition
lamports_budget != 0means if the budget were exhausted (becomes 0), we would skip the check instead of failing. This seems backwards.The cleaner approach is to directly check against
max_top_upwithout the budget indirection:🔎 Proposed fix
// Drop borrow before CPI drop(account_data); if transfer_amount > 0 { - if lamports_budget != 0 && transfer_amount > lamports_budget { + if max_top_up != 0 && transfer_amount > max_top_up as u64 { return Err(CTokenError::MaxTopUpExceeded.into()); } transfer_lamports_via_cpi(transfer_amount, payer, account) .map_err(convert_program_error)?; }This directly enforces the limit: when
max_top_upis non-zero, fail if transfer exceeds it. Whenmax_top_upis 0 (unlimited), the check is skipped cleanly.programs/compressed-token/program/src/transfer/shared.rs (1)
217-219: Optimize by caching the Rent sysvar to avoid redundant syscalls.
Rent::get()is called every timeprocess_account_extensionsexecutes. Since this function is invoked twice per transfer (sender + recipient), you're making two syscalls for the same immutable sysvar data. Cache the rent value similar to howcurrent_slotis cached with a sentinel pattern.🔎 Suggested optimization
Thread a cached
Option<Rent>through the call chain:fn process_account_extensions( account: &AccountInfo, current_slot: &mut u64, + cached_rent: &mut Option<Rent>, mint: Option<&AccountInfo>, ) -> Result<AccountExtensionInfo, ProgramError> { // ... existing code ... - let rent_exemption = Rent::get() - .map_err(|_| CTokenError::SysvarAccessError)? - .minimum_balance(account.data_len()); + let rent = cached_rent.get_or_insert_with(|| { + Rent::get().expect("rent sysvar") + }); + let rent_exemption = rent.minimum_balance(account.data_len());Update callers in
validate_senderandvalidate_recipientto pass the cached rent reference.Note: This aligns with the previous review feedback on caching sysvars for performance.
program-libs/ctoken-interface/tests/ctoken/size.rs (1)
6-62: Comprehensive test coverage for account size calculations.The test methodically covers:
- Base case (no extensions)
- Single extensions (pausable, permanent_delegate, transfer_fee, transfer_hook)
- Combined extensions
The inline comments explaining the arithmetic (e.g., "258 + 4 metadata + 1 discriminant = 263") effectively document the expected values. While named constants could improve maintainability, the current approach clearly shows how each size is derived.
Consider defining constants for frequently referenced sizes if these values are needed elsewhere in the codebase.
programs/registry/src/compressible/compressed_token/compress_and_close.rs (2)
100-104: Use a more appropriate error variant for CToken parsing failure.
RegistryError::InvalidSignerdoesn't accurately describe a CToken parsing failure. For consistency and clarity (see line 97-98), useRegistryError::InvalidTokenAccountData.🔎 Suggested fix
let (ctoken, _) = CToken::zero_copy_at(&account_data).map_err(|e| { anchor_lang::prelude::msg!("Failed to parse CToken: {:?}", e); - RegistryError::InvalidSigner + RegistryError::InvalidTokenAccountData })?;
137-148: Preservedelegated_amountin CompressedOnly extension even without marker extensions.When a CToken has a delegate with
delegated_amount > 0but none of the marker extension flags are set, the code pushes an empty TLV vector. This causesdelegated_amountto be lost—the compressed output will havehas_delegate: true(line 157) but no way to recover the original spending limit since it's only stored in CompressedOnly.🔎 Proposed fix
- // Build TLV extensions for this output if marker extensions are present - if has_marker_extensions { + // Build TLV extensions for this output if marker extensions are present + // OR if delegated_amount > 0 (to preserve the spending limit) + if has_marker_extensions || delegated_amount > 0 { out_tlv.push(vec![ExtensionInstructionData::CompressedOnly( CompressedOnlyExtensionInstructionData { delegated_amount, withheld_transfer_fee, is_frozen, }, )]); } else { out_tlv.push(vec![]); }programs/compressed-token/program/src/transfer2/compression/ctoken/compress_or_decompress_ctokens.rs (2)
74-80: Consider extracting the frozen state constant.The code uses magic number
2forAccountState::Frozen. This appears multiple times in the codebase. Defining a constant would improve readability.// At module level const ACCOUNT_STATE_FROZEN: u8 = 2;
167-202: Missing delegate validation when CToken has existing delegate but input lacks delegate pubkey.There's a gap in the validation logic. When:
- CToken already has a delegate
- Input compressed account has
delegated_amount > 0- But
input_delegateisNoneThe code falls through line 182 and proceeds to add
delegated_amountat line 194-200. This merges delegated amounts that may belong to different delegates.🔎 Proposed fix
// Validate delegate compatibility if let Some(ctoken_delegate) = ctoken.delegate() { // CToken has a delegate - check if it matches the input delegate if let Some(input_del) = input_delegate_pubkey.as_ref() { if ctoken_delegate.to_bytes() != input_del.to_bytes() { msg!( "Decompress delegate mismatch: CToken delegate {:?} != input delegate {:?}", ctoken_delegate.to_bytes(), input_del.to_bytes() ); return Err(ErrorCode::DecompressDelegateMismatch.into()); } + } else if delegated_amount > 0 { + // CToken has delegate but input has delegated_amount without delegate info + msg!("Decompress: CToken has delegate but input has delegated_amount without delegate pubkey"); + return Err(CTokenError::InvalidAccountData.into()); } // Delegates match - add to delegated_amountprogram-libs/ctoken-interface/tests/mint_borsh_zero_copy.rs (1)
169-172: Compression field round-trip validation uses default values.As noted in a prior review, the reconstruction always uses
CompressionInfo::default(). While this tests the default case thoroughly, it doesn't verify that non-defaultCompressionInfovalues survive the Borsh ↔ zero-copy round-trip. Consider enhancinggenerate_random_mint()to randomize compression fields for complete coverage.programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs (1)
30-30: Documentation doesn't match validation logic.The comment states "rent_payment must be 0 or >= 2" but the code at line 64 only rejects
rent_payment == 1. Any value ≥ 2 is accepted without upper bound, as is 0. Consider updating to: "rent_payment must not be 1" or clarify the actual constraint.programs/compressed-token/program/tests/mint.rs (1)
401-420: Manual CompressionInfo reconstruction is verbose but functional.This reconstruction pattern works correctly, but as noted in a previous review, consider implementing a
From<&ZCompressionInfo>trait or.to_owned()method on the zero-copy type to reduce boilerplate. The current approach requires updating multiple locations if the structure changes.programs/compressed-token/program/src/shared/owner_validation.rs (1)
57-77: Verbose logging on authorization failure path.This was previously flagged: emitting multiple
msg!calls on every authorization failure adds compute overhead. Since this is the error path, the cost is acceptable for debugging, but consider consolidating into a single structured message for production paths if this becomes a hot path.programs/compressed-token/anchor/src/lib.rs (2)
311-312: Error message is inaccurate—multiple TLV extension types are now supported.The message claims "only CompressedOnly is currently implemented," but based on the PR context and codebase, multiple extension types are supported: CompressedOnly, TokenMetadata, PausableAccount, PermanentDelegateAccount, TransferFeeAccount, and TransferHookAccount. Update the message to reflect reality or use a generic phrasing like "Unsupported TLV extension type."
🔎 Suggested fix
- #[msg("Unsupported TLV extension type - only CompressedOnly is currently implemented")] + #[msg("Unsupported TLV extension type")] UnsupportedTlvExtensionType,
526-529: Document unimplemented features with tracking references.
InLamportsUnimplementedandOutLamportsUnimplementedindicate incomplete functionality. Consider adding a brief comment or TODO with an issue tracker reference so developers understand the roadmap.programs/compressed-token/program/src/transfer2/compression/ctoken/compress_and_close.rs (2)
239-251: Consider extracting magic number for AccountState::Frozen.The value
2is used to representAccountState::Frozenon lines 242 and 255. While the comment on line 240-241 explains this, extracting to a constant would improve clarity:const ACCOUNT_STATE_FROZEN: u8 = 2;This was previously flagged as a nitpick.
268-274: Redundant delegate checks in non-CompressedOnly branch.Lines 269-274 check both
has_delegate()(line 269) anddelegate != 0(line 272). Thehas_delegate()method already checks the underlyingdelegate_option_prefixfield. If the field values are consistent, the second check is redundant.🔎 Suggested fix—remove redundant check
// Delegate should be None if compressed_token_account.has_delegate() { return Err(ErrorCode::CompressAndCloseDelegateNotAllowed.into()); } - if compressed_token_account.delegate != 0 { - return Err(ErrorCode::CompressAndCloseDelegateNotAllowed.into()); - }programs/compressed-token/anchor/src/instructions/create_token_pool.rs (2)
63-86: Consider validating token_program is a known SPL program.The
initialize_token_accountfunction invokes CPI usingtoken_program.keydirectly. While the AnchorInterface<'info, TokenInterface>constraint should enforce this at the instruction level, this helper function could theoretically be called with an arbitrary account.🔎 Optional: Add program ID validation
pub fn initialize_token_account<'info>( token_account: &AccountInfo<'info>, mint: &AccountInfo<'info>, authority: &AccountInfo<'info>, token_program: &AccountInfo<'info>, ) -> Result<()> { + // Validate token program is SPL Token or Token-2022 + if *token_program.key != spl_token::id() && *token_program.key != spl_token_2022::id() { + return Err(crate::ErrorCode::InvalidTokenProgram.into()); + } let ix = spl_token_2022::instruction::initialize_account3(
106-142: Missing validation for confidential extension runtime states.The documentation in this codebase (and previous reviews) indicates that certain confidential extensions have runtime constraints:
ConfidentialTransferMint: must be initialized but not enabledConfidentialMintBurn: must be initialized but not enabledConfidentialTransferFeeConfig: fees must be zeroCurrently,
assert_mint_extensionsvalidatesTransferFeeConfig(lines 120-130) andTransferHook(lines 133-139), but the three confidential extensions mentioned above are not validated. If these extensions are enabled, they alter token behavior and should be rejected during pool creation.#!/bin/bash # Check if confidential extension validation exists elsewhere in the codebase rg -n "ConfidentialTransferMint|ConfidentialMintBurn|ConfidentialTransferFeeConfig" programs/compressed-token/ --type rust -B 2 -A 5 | head -100programs/compressed-token/program/src/transfer2/compression/ctoken/inputs.rs (1)
87-123: TODO indicates known inefficiency in input matching - address before release.The matching logic iterates through all inputs to find one matching by mint index, which the TODO at lines 89-92 correctly identifies as inefficient and potentially problematic. The comment suggests passing the input index directly in instruction data.
Additionally, the delegate lookup at lines 107-118 uses
.ok()?which silently swallows errors. If the delegate index is invalid buthas_delegate()returns true, this will returnNoneinstead of surfacing the error.Consider:
- Pass the input compressed account index explicitly in instruction data to avoid O(n) search
- Handle the delegate lookup error explicitly:
🔎 Suggested fix for delegate lookup
let input_delegate = matching_input_index.and_then(|idx| { let input = inputs.in_token_data.get(idx)?; if input.has_delegate() { - Some( - packed_accounts - .get_u8(input.delegate, "input delegate") - .ok()?, - ) + packed_accounts + .get_u8(input.delegate, "input delegate") + .ok() // Consider logging if this fails unexpectedly } else { None } });programs/compressed-token/program/src/close_token_account/processor.rs (1)
176-177: Non-Solana build has hardcoded slot of 0.The
#[cfg(not(target_os = "solana"))]block setscurrent_slot = 0, which will cause incorrect rent calculations in tests or off-chain simulations. Consider documenting this limitation or providing a way to inject a slot for testing.#[cfg(not(target_os = "solana"))] let current_slot = 0; // Note: Off-chain simulations will have incorrect rent calculationsprogram-libs/ctoken-interface/src/state/extensions/extension_struct.rs (1)
205-208: CompressedOnly uses LEN constant instead of byte_len method.The
CompressedOnlyvariant usesCompressedOnlyExtension::LENdirectly instead of callingbyte_len()like other variants. This was flagged in a previous review. IfCompressedOnlyExtensionhas a fixed size (whichLENsuggests), this is acceptable but should be documented.🔎 Consider adding a comment for consistency
ExtensionStructConfig::CompressedOnly(_) => { - // 1 byte for discriminant + 16 bytes for CompressedOnlyExtension (2 * u64) + // 1 byte for discriminant + 16 bytes for CompressedOnlyExtension (2 * u64) + // Uses LEN constant as CompressedOnlyExtension has a fixed size 1 + CompressedOnlyExtension::LEN }program-libs/ctoken-interface/tests/ctoken/spl_compat.rs (1)
87-92: Magic number 2 at line 91 should use ACCOUNT_TYPE_TOKEN_ACCOUNT constant.The past review comment noted this should use the constant for consistency with line 406. While the commit history suggests this was addressed, the current code still shows a literal
2at line 91.🔎 Use constant instead of magic number
// Set account_type byte at position 165 to ACCOUNT_TYPE_TOKEN_ACCOUNT (2) - account_data[165] = 2; + account_data[165] = ACCOUNT_TYPE_TOKEN_ACCOUNT;program-libs/ctoken-interface/src/state/mint/zero_copy.rs (1)
319-340: Validation order inconsistency between checked methods persists.The past review noted that
zero_copy_at_checkedchecksis_cmint_account()first thenis_initialized(), whilezero_copy_at_mut_checkeddoes the opposite. While functionally equivalent (both must pass), consistent ordering improves maintainability.🔎 Suggested consistency fix
pub fn zero_copy_at_mut_checked( bytes: &mut [u8], ) -> Result<(ZCompressedMintMut<'_>, &mut [u8]), CTokenError> { // ... let (mint, remaining) = CompressedMint::zero_copy_at_mut(bytes) .map_err(|_| CTokenError::CMintDeserializationFailed)?; - if !mint.is_initialized() { - return Err(CTokenError::CMintNotInitialized); - } if !mint.is_cmint_account() { return Err(CTokenError::InvalidAccountType); } + if !mint.is_initialized() { + return Err(CTokenError::CMintNotInitialized); + } Ok((mint, remaining)) }Also applies to: 347-370
programs/compressed-token/program/src/shared/token_input.rs (1)
96-119: TLV conversion only handles CompressedOnly extension type.The conversion correctly filters to
ZExtensionInstructionData::CompressedOnlyand returnsUnsupportedTlvExtensionTypefor other variants. Based on the past review comment, this is intentional because other variants (Pausable, PermanentDelegate, TransferHook) are marker extensions referencing mint configuration rather than account-specific state data.Consider adding a brief comment explaining this design decision for future maintainers:
🔎 Add clarifying comment
// Convert instruction TLV data to state TLV + // Note: Only CompressedOnly carries account-specific state data; + // marker extensions (Pausable, PermanentDelegate, TransferHook) + // reference mint configuration and are not stored in token data. let tlv: Option<Vec<ExtensionStruct>> = match tlv_data {program-libs/ctoken-interface/src/state/ctoken/zero_copy.rs (3)
128-128: Assign boolean value directly instead of casting to u8.The
compression_onlyfield is declared asboolin the struct (line 57), but you're assigningconfig.compression_only as u8. The zero-copy derive macros handle the byte-level representation automatically. Assign the boolean directly:base.compression_only = config.compression_only;This maintains type correctness and is more idiomatic.
🔎 Proposed fix
- base.compression_only = config.compression_only as u8; + base.compression_only = config.compression_only;Based on past review feedback flagging the same pattern.
133-133: Assigntruedirectly to boolean field instead of numeric literal.The
has_extensionsfield is declared asbool(line 59), so assign the boolean value directly:*base.has_extensions = true;The zero-copy machinery handles the u8 representation internally.
🔎 Proposed fix
- *base.has_extensions = 1u8; + *base.has_extensions = true;Based on past review feedback flagging the same pattern.
147-154: Extensions written but not reflected in returned struct.The function writes extensions directly to storage but returns
extensions: None. This design choice could confuse callers who expect the returnedZCTokenMutto reflect the actual account state. Consider either:
- Populating the extensions field in the returned struct (preferred for consistency), or
- Adding explicit documentation explaining why extensions aren't tracked in the returned wrapper.
Based on past review feedback noting this inconsistency.
| /// Check if an extension type is a restricted extension. | ||
| #[inline(always)] | ||
| pub const fn is_restricted_extension(ext: &ExtensionType) -> bool { | ||
| matches!( | ||
| ext, | ||
| ExtensionType::Pausable | ||
| | ExtensionType::PermanentDelegate | ||
| | ExtensionType::TransferFeeConfig | ||
| | ExtensionType::TransferHook | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider using RESTRICTED_EXTENSION_TYPES in is_restricted_extension().
The is_restricted_extension() function duplicates the logic of RESTRICTED_EXTENSION_TYPES using a matches! expression. While this works, changes to one require updating the other.
🔎 Alternative using the constant array
pub const fn is_restricted_extension(ext: &ExtensionType) -> bool {
- matches!(
- ext,
- ExtensionType::Pausable
- | ExtensionType::PermanentDelegate
- | ExtensionType::TransferFeeConfig
- | ExtensionType::TransferHook
- )
+ // Note: Can't use contains() in const fn, so matches! is appropriate here
+ // Just ensure this stays in sync with RESTRICTED_EXTENSION_TYPES
+ matches!(
+ ext,
+ ExtensionType::Pausable
+ | ExtensionType::PermanentDelegate
+ | ExtensionType::TransferFeeConfig
+ | ExtensionType::TransferHook
+ )
}The current implementation is correct for a const fn since array iteration isn't available. Consider adding a comment noting the synchronization requirement with RESTRICTED_EXTENSION_TYPES.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /// Check if an extension type is a restricted extension. | |
| #[inline(always)] | |
| pub const fn is_restricted_extension(ext: &ExtensionType) -> bool { | |
| matches!( | |
| ext, | |
| ExtensionType::Pausable | |
| | ExtensionType::PermanentDelegate | |
| | ExtensionType::TransferFeeConfig | |
| | ExtensionType::TransferHook | |
| ) | |
| } | |
| /// Check if an extension type is a restricted extension. | |
| #[inline(always)] | |
| pub const fn is_restricted_extension(ext: &ExtensionType) -> bool { | |
| // Note: Can't use contains() in const fn, so matches! is appropriate here | |
| // Just ensure this stays in sync with RESTRICTED_EXTENSION_TYPES | |
| matches!( | |
| ext, | |
| ExtensionType::Pausable | |
| | ExtensionType::PermanentDelegate | |
| | ExtensionType::TransferFeeConfig | |
| | ExtensionType::TransferHook | |
| ) | |
| } |
🤖 Prompt for AI Agents
In program-libs/ctoken-interface/src/token_2022_extensions.rs around lines 45 to
55, the is_restricted_extension() function duplicates the logic of the
RESTRICTED_EXTENSION_TYPES constant which risks them getting out of sync; update
the file to either (preferred) replace the function with a call to the shared
constant (if you can make a non-const/const-compatible contains check) or (if
const constraints prevent that) add a clear comment above
is_restricted_extension() stating it must be kept in sync with
RESTRICTED_EXTENSION_TYPES (include the constant name and its location), and add
a unit test/assert that validates both representations match to prevent drift.
| pub fn calculate_account_size(&self) -> Result<u64, ZeroCopyError> { | ||
| // Use stack-allocated array to avoid heap allocation | ||
| // Maximum 4 extensions: pausable, permanent_delegate, transfer_fee, transfer_hook | ||
| let mut extensions: [ExtensionStructConfig; 4] = [ | ||
| ExtensionStructConfig::Placeholder0, | ||
| ExtensionStructConfig::Placeholder0, | ||
| ExtensionStructConfig::Placeholder0, | ||
| ExtensionStructConfig::Placeholder0, | ||
| ]; | ||
| let mut count = 0; | ||
|
|
||
| if self.has_pausable { | ||
| extensions[count] = ExtensionStructConfig::PausableAccount(()); | ||
| count += 1; | ||
| } | ||
| if self.has_permanent_delegate { | ||
| extensions[count] = ExtensionStructConfig::PermanentDelegateAccount(()); | ||
| count += 1; | ||
| } | ||
| if self.has_transfer_fee { | ||
| extensions[count] = ExtensionStructConfig::TransferFeeAccount(()); | ||
| count += 1; | ||
| } | ||
| if self.has_transfer_hook { | ||
| extensions[count] = ExtensionStructConfig::TransferHookAccount(()); | ||
| count += 1; | ||
| } | ||
|
|
||
| let exts = if count == 0 { | ||
| None | ||
| } else { | ||
| Some(&extensions[..count]) | ||
| }; | ||
| crate::state::calculate_ctoken_account_size(exts).map(|size| size as u64) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find ExtensionStructConfig definition and Placeholder0 usage
echo "=== Searching for ExtensionStructConfig enum definition ==="
rg -n "enum ExtensionStructConfig" --type=rust -A 20
echo ""
echo "=== Searching for Placeholder0 references ==="
rg -n "Placeholder0" --type=rust -B 2 -A 2
echo ""
echo "=== Locating calculate_ctoken_account_size function ==="
rg -n "fn calculate_ctoken_account_size" --type=rust -A 30Repository: Lightprotocol/light-protocol
Length of output: 6833
🏁 Script executed:
#!/bin/bash
# Find ExtensionStruct::byte_len implementation
echo "=== Searching for byte_len implementation ==="
rg -n "fn byte_len" --type=rust -B 2 -A 40 | head -80
echo ""
echo "=== Looking for match/match statement handling Placeholder0 ==="
rg -n "Placeholder0.*=>" --type=rustRepository: Lightprotocol/light-protocol
Length of output: 7573
🏁 Script executed:
#!/bin/bash
# Search for ExtensionStruct definition and its byte_len implementation
echo "=== Finding ExtensionStruct enum definition ==="
rg -n "pub enum ExtensionStruct" --type=rust -A 50 | head -60
echo ""
echo "=== Searching for impl.*ExtensionStruct with byte_len ==="
rg -n "impl.*ExtensionStruct" --type=rust -A 100 | head -150Repository: Lightprotocol/light-protocol
Length of output: 23880
Placeholder0 variants will cause an error if passed to byte_len().
The catch-all _ case in ExtensionStruct::byte_len() returns ZeroCopyError::InvalidConversion for any unmatched variants, including Placeholder0. While the current code works because only the first count elements are passed to calculate_ctoken_account_size(), this relies on correct count tracking. If count is miscalculated, Placeholder0 variants beyond the slice would cause a runtime error instead of contributing 0 bytes. Consider explicitly handling placeholder variants in the match statement to fail fast during development, or document that the count must always stay in bounds.
| // Check if DefaultAccountState is set to Frozen | ||
| // AccountState::Frozen as u8 = 2, ext.state is PodAccountState (u8) | ||
| let default_account_state_frozen = if has_default_account_state { | ||
| mint_state | ||
| .get_extension::<DefaultAccountState>() | ||
| .map(|ext| ext.state == AccountState::Frozen as u8) | ||
| .unwrap_or(false) | ||
| } else { | ||
| false | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
DefaultAccountState frozen detection is correct but could use unwrap_or_else for consistency.
The pattern unwrap_or(false) at line 172 is fine, but consider using unwrap_or_default() or adding a comment explaining why the fallback to false is safe here (extension exists but failed to parse).
🔎 Minor clarification suggestion
let default_account_state_frozen = if has_default_account_state {
mint_state
.get_extension::<DefaultAccountState>()
.map(|ext| ext.state == AccountState::Frozen as u8)
- .unwrap_or(false)
+ .unwrap_or(false) // Safe: if extension parse fails, treat as not frozen
} else {
false
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Check if DefaultAccountState is set to Frozen | |
| // AccountState::Frozen as u8 = 2, ext.state is PodAccountState (u8) | |
| let default_account_state_frozen = if has_default_account_state { | |
| mint_state | |
| .get_extension::<DefaultAccountState>() | |
| .map(|ext| ext.state == AccountState::Frozen as u8) | |
| .unwrap_or(false) | |
| } else { | |
| false | |
| }; | |
| // Check if DefaultAccountState is set to Frozen | |
| // AccountState::Frozen as u8 = 2, ext.state is PodAccountState (u8) | |
| let default_account_state_frozen = if has_default_account_state { | |
| mint_state | |
| .get_extension::<DefaultAccountState>() | |
| .map(|ext| ext.state == AccountState::Frozen as u8) | |
| .unwrap_or(false) // Safe: if extension parse fails, treat as not frozen | |
| } else { | |
| false | |
| }; |
🤖 Prompt for AI Agents
In programs/compressed-token/program/src/extensions/check_mint_extensions.rs
around lines 166 to 175, the code uses unwrap_or(false) when converting the
DefaultAccountState extension to a boolean which is safe but inconsistent with
other patterns; replace unwrap_or(false) with unwrap_or_default() (or
unwrap_or_else(|| false)) so the fallback is idiomatic, or add a short comment
clarifying that returning false is safe when parsing fails; ensure the chosen
approach matches surrounding code style.
| let is_compressible = match compression_info.is_compressible( | ||
| cmint.data_len() as u64, | ||
| current_slot, | ||
| cmint.lamports(), | ||
| ) { | ||
| Ok(is_compressible) => is_compressible, | ||
| Err(_) => { | ||
| if action.idempotent != 1 { | ||
| return Ok(()); | ||
| } else { | ||
| msg!("CMint is not compressible (rent not expired)"); | ||
| return Err(ErrorCode::CMintNotCompressible.into()); | ||
| } | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| // 6. Transfer all lamports to rent_sponsor | ||
| let cmint_lamports = cmint.lamports(); | ||
| if cmint_lamports > 0 { | ||
| transfer_lamports(cmint_lamports, cmint, rent_sponsor).map_err(convert_program_error)?; | ||
| if is_compressible.is_none() { | ||
| msg!("CMint is not compressible (rent not expired)"); | ||
| return Err(ErrorCode::CMintNotCompressible.into()); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Inverted idempotent logic will cause incorrect behavior.
The conditional at lines 93-98 has inverted logic. When is_compressible returns an error:
- If
action.idempotent != 1(meaning NOT idempotent), the code returnsOk(())— silently succeeding. - If
action.idempotent == 1(meaning idempotent), it returns an error.
This is backwards. Idempotent mode should succeed silently on "already done" or "cannot proceed" conditions, not the non-idempotent mode.
🔎 Proposed fix
let is_compressible = match compression_info.is_compressible(
cmint.data_len() as u64,
current_slot,
cmint.lamports(),
) {
Ok(is_compressible) => is_compressible,
Err(_) => {
- if action.idempotent != 1 {
+ if action.idempotent == 1 {
return Ok(());
} else {
msg!("CMint is not compressible (rent not expired)");
return Err(ErrorCode::CMintNotCompressible.into());
}
}
};🤖 Prompt for AI Agents
In
programs/compressed-token/program/src/mint_action/actions/compress_and_close_cmint.rs
around lines 86 to 105, the idempotent branch is inverted when
compression_info.is_compressible() returns Err: currently it returns Ok(()) for
non-idempotent actions and Err for idempotent ones; flip the logic so that if
is_compressible() returns Err and action.idempotent == 1 you return Ok(())
(silent success), otherwise log the "CMint is not compressible (rent not
expired)" message and return Err(ErrorCode::CMintNotCompressible.into()); keep
the later check for is_compressible.is_none() unchanged.
| // Calculate CToken top-up | ||
| { | ||
| let account_data = ctoken.try_borrow_data().map_err(convert_program_error)?; | ||
| let (token, _) = CToken::zero_copy_at_checked(&account_data)?; | ||
| if let Some(ref extensions) = token.extensions { | ||
| for extension in extensions.iter() { | ||
| if let ZExtensionStruct::Compressible(compressible_ext) = extension { | ||
| if current_slot == 0 { | ||
| use pinocchio::sysvars::{clock::Clock, Sysvar}; | ||
| current_slot = Clock::get() | ||
| .map_err(|_| CTokenError::SysvarAccessError)? | ||
| .slot; | ||
| } | ||
| transfers[1].amount = compressible_ext | ||
| .info | ||
| .calculate_top_up_lamports( | ||
| ctoken.data_len() as u64, | ||
| current_slot, | ||
| ctoken.lamports(), | ||
| COMPRESSIBLE_TOKEN_RENT_EXEMPTION, | ||
| ) | ||
| .map_err(|_| CTokenError::InvalidAccountData)?; | ||
| lamports_budget = lamports_budget.saturating_sub(transfers[1].amount); | ||
| break; | ||
| } | ||
| } | ||
| } else { | ||
| // Only Compressible extensions are implemented for ctoken accounts. | ||
| return Err(CTokenError::InvalidAccountData.into()); | ||
| // Access compression info directly from meta (all ctokens now have compression embedded) | ||
| if current_slot == 0 { | ||
| current_slot = Clock::get() | ||
| .map_err(|_| CTokenError::SysvarAccessError)? | ||
| .slot; | ||
| rent = Some(Rent::get().map_err(|_| CTokenError::SysvarAccessError)?); | ||
| } | ||
| let rent_exemption = rent.as_ref().unwrap().minimum_balance(ctoken.data_len()); | ||
| transfers[1].amount = token | ||
| .compression | ||
| .calculate_top_up_lamports( | ||
| ctoken.data_len() as u64, | ||
| current_slot, | ||
| ctoken.lamports(), | ||
| rent_exemption, | ||
| ) | ||
| .map_err(|_| CTokenError::InvalidAccountData)?; | ||
| lamports_budget = lamports_budget.saturating_sub(transfers[1].amount); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Duplicate lazy initialization code could be consolidated.
Lines 81-86 duplicate the lazy initialization pattern from lines 56-61. Since the CMint block always executes first and sets current_slot and rent, the condition at line 81 (if current_slot == 0) will never be true.
Consider moving the sysvar initialization before both blocks to simplify:
let mut current_slot = 0;
let mut rent: Option<Rent> = None;
+
+ // Initialize sysvars once
+ current_slot = Clock::get()
+ .map_err(|_| CTokenError::SysvarAccessError)?
+ .slot;
+ rent = Some(Rent::get().map_err(|_| CTokenError::SysvarAccessError)?);
+
// Initialize budget: +1 allows exact match (total == max_top_up)
let mut lamports_budget = (max_top_up as u64).saturating_add(1);
// Calculate CMint top-up using zero-copy
{
let cmint_data = cmint.try_borrow_data().map_err(convert_program_error)?;
let (mint, _) = CompressedMint::zero_copy_at(&cmint_data)
.map_err(|_| CTokenError::CMintDeserializationFailed)?;
- if current_slot == 0 {
- current_slot = Clock::get()
- .map_err(|_| CTokenError::SysvarAccessError)?
- .slot;
- rent = Some(Rent::get().map_err(|_| CTokenError::SysvarAccessError)?);
- }
let rent_exemption = rent.as_ref().unwrap().minimum_balance(cmint.data_len());Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In programs/compressed-token/program/src/shared/compressible_top_up.rs around
lines 76 to 98, the lazy sysvar initialization (Clock::get and Rent::get) is
duplicated and the second check (if current_slot == 0) is redundant because the
CMint block runs first; move the sysvar initialization (populate current_slot
and rent with the same error handling) to a single spot before both the CMint
and CToken blocks, remove the duplicate if/current_slot check and its body in
the CToken block, and replace the subsequent rent.as_ref().unwrap() usage with
the already-initialized rent reference so both blocks use the shared values and
no duplicate sysvar calls remain.
| impl AccountExtensionInfo { | ||
| #[inline(always)] | ||
| fn check_t22_extensions(&self, other: &Self) -> Result<(), ProgramError> { | ||
| if self.flags.has_pausable != other.flags.has_pausable | ||
| || self.flags.has_permanent_delegate != other.flags.has_permanent_delegate | ||
| || self.flags.has_transfer_fee != other.flags.has_transfer_fee | ||
| || self.flags.has_transfer_hook != other.flags.has_transfer_hook | ||
| { | ||
| Err(ProgramError::InvalidInstructionData) | ||
| } else { | ||
| Ok(()) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider implementing PartialEq on MintExtensionFlags to simplify comparison.
The manual field-by-field comparison of extension flags could be replaced with a simple equality check if MintExtensionFlags implements PartialEq. This would be more maintainable when additional extension flags are added.
🔎 Suggested simplification
If MintExtensionFlags implements PartialEq:
fn check_t22_extensions(&self, other: &Self) -> Result<(), ProgramError> {
- if self.flags.has_pausable != other.flags.has_pausable
- || self.flags.has_permanent_delegate != other.flags.has_permanent_delegate
- || self.flags.has_transfer_fee != other.flags.has_transfer_fee
- || self.flags.has_transfer_hook != other.flags.has_transfer_hook
- {
+ if self.flags != other.flags {
Err(ProgramError::InvalidInstructionData)
} else {
Ok(())
}
}Otherwise, verify whether this manual comparison is necessary.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In programs/compressed-token/program/src/transfer/shared.rs around lines 30 to
43, the code manually compares each field of the extension flags which is
fragile and verbose; implement or derive PartialEq (and Eq if appropriate) for
the MintExtensionFlags/MintExtensionFlags-like type and replace the
field-by-field checks with a single equality comparison (e.g., if self.flags !=
other.flags then return Err(...)); if there is a deliberate reason to compare
fields individually, add a comment explaining why and keep the explicit checks.
| // Anchor custom error codes start at offset 6000 | ||
| const ANCHOR_ERROR_OFFSET: u32 = 6000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider importing ERROR_CODE_OFFSET from the anchor crate.
The constant ANCHOR_ERROR_OFFSET duplicates the ERROR_CODE_OFFSET defined in programs/compressed-token/anchor/src/lib.rs (lines 539-541). If the offset changes in the source, this test will silently use an outdated value.
🔎 Proposed fix
+use anchor_compressed_token::ERROR_CODE_OFFSET;
+
-// Anchor custom error codes start at offset 6000
-const ANCHOR_ERROR_OFFSET: u32 = 6000;Then update line 146:
- ANCHOR_ERROR_OFFSET + ErrorCode::CompressAndCloseDuplicateOutput as u32,
+ ERROR_CODE_OFFSET + ErrorCode::CompressAndCloseDuplicateOutput as u32,Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In programs/compressed-token/program/tests/compress_and_close.rs around lines
17-18, the local ANCHOR_ERROR_OFFSET constant duplicates the value defined in
the anchor crate and can drift; remove that local const and import the canonical
ERROR_CODE_OFFSET from the anchor crate (e.g. use anchor::ERROR_CODE_OFFSET;)
then update the test usage at line 146 to reference ERROR_CODE_OFFSET instead of
the hard-coded ANCHOR_ERROR_OFFSET so the test always uses the source-of-truth
value.
Typescript changes:
Summary by CodeRabbit
Release Notes
New Features
Improvements
✏️ Tip: You can customize this high-level summary in your review settings.