-
Notifications
You must be signed in to change notification settings - Fork 87
feat: compressible mint #2136
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
feat: compressible mint #2136
Conversation
WalkthroughAdds CMint (compressible mint) support: new DecompressMint and CompressAndCloseCMint actions, CMint PDA creation/resizing/top-ups, CMint-driven mint-data flow (cmint_decompressed replaces spl_mint_initialized), extension mapping for Compressible, many SDK and test updates, and new ctoken burn/mint_to ops. Changes
Sequence Diagram(s)sequenceDiagram
actor SDK
participant Program
participant CompressibleConfig
participant RentSponsor
participant CMint as CMint_PDA
SDK->>Program: DecompressMint(action: cmint_bump, rent_payment, write_top_up)
Program->>Program: validate rent_payment (reject 0/1), bounds checks
Program->>CompressibleConfig: read compressible config & limits
Program->>RentSponsor: validate rent_sponsor matches config
Program->>Program: derive CMint PDA from mint metadata & bump
Program->>CMint: create CMint PDA account (funded by rent_sponsor)
Program->>CMint: attach Compressible extension (CompressionInfo)
Program->>Program: set cmint_decompressed = true in mint metadata
Program-->>SDK: return Success / ProgramError
sequenceDiagram
actor SDK
participant Program
participant CMint as CMint_PDA
participant RentSponsor
SDK->>Program: CompressAndCloseCMint(action: idempotent)
Program->>CMint: verify CMint exists and cmint_decompressed == true
Program->>CMint: fetch Compressible extension & is_compressible
Program->>RentSponsor: verify rent_sponsor matches extension
Program->>CMint: transfer lamports -> rent_sponsor
Program->>CMint: remove Compressible extension, resize & close CMint
Program->>Program: set cmint_decompressed = false
Program-->>SDK: return Success / ProgramError
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Areas to focus during review:
Possibly related PRs
Suggested labels
Suggested reviewers
Pre-merge checks and finishing touches✅ Passed checks (3 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: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
sdk-libs/ctoken-sdk/src/compressed_token/v2/create_compressed_mint/instruction.rs (1)
138-149: Consistent field rename with create_compressed_mint_cpi.The
cmint_decompressed: falseinitialization matches the change at line 54 and is semantically correct for a newly created compressed mint.Optional: Consider extracting metadata construction.
Lines 138-149 duplicate the metadata initialization logic from lines 48-59. If metadata construction becomes more complex in future iterations, consider extracting it into a helper function:
fn build_compressed_mint_metadata( version: u8, mint_signer: &Pubkey, ) -> light_ctoken_interface::state::CompressedMintMetadata { light_ctoken_interface::state::CompressedMintMetadata { version, mint: find_cmint_address(mint_signer).0.to_bytes().into(), cmint_decompressed: false, } }This would centralize the initialization and reduce the risk of inconsistencies if additional fields are added to the metadata structure.
programs/compressed-token/program/src/mint_action/actions/mint_to.rs (1)
23-29: Documentation describes removed functionality.The doc comment describes "SPL Mint Synchronization" behavior at lines 27-29, but the code that performed this synchronization (
handle_spl_mint_initialized_token_pool) has been removed from this function. The documentation should either be removed or updated to indicate where this synchronization now occurs (presumably in a different action likeDecompressMint)./// ## Process Steps /// 1. **Authority Validation**: Verify signer matches current mint authority from compressed mint state /// 2. **Amount Calculation**: Sum recipient amounts with overflow protection /// 3. **Lamports Calculation**: Calculate total lamports for compressed accounts (if specified) /// 4. **Supply Update**: Calculate new total supply with overflow protection -/// 5. **SPL Mint Synchronization**: For initialized SPL mints, validate accounts and mint equivalent tokens to token pool via CPI -/// 6. **Compressed Account Creation**: Create new compressed token account for each recipient -/// -/// ## SPL Mint Synchronization -/// When `compressed_mint.metadata.cmint_decompressed` is true and an SPL mint exists for this compressed mint, -/// the function maintains consistency between the compressed token supply and the underlying SPL mint supply -/// by minting equivalent tokens to a program-controlled token pool account via CPI to SPL Token 2022. +/// 5. **Compressed Account Creation**: Create new compressed token account for each recipientprograms/compressed-token/program/docs/instructions/MINT_ACTION.md (1)
10-11: Update MINT_ACTION.md to include DecompressMint and CloseCMint actions and correct the total count.The documentation currently states "9 total actions" but the Action enum in
program-libs/ctoken-interface/src/instructions/mint_action/instruction_data.rscontains 10 variants, includingDecompressMintandCloseCMintwhich are not listed in the documentation. Update the action count to 11 (1 creation + 10 enum-based actions) and add:
DecompressMint- Decompress a compressed mint to a CMint Solana accountCloseCMint- Close a CMint Solana account while preserving compressed mint stateprograms/compressed-token/program/tests/mint_action.rs (1)
116-128: Consider adding DecompressMint and CloseCMint to random action generation.The
random_actionfunction generates 8 action types (indices 0-7) but doesn't include the newDecompressMintorCloseCMintactions. While the test still validateshas_decompress_mint_action, it won't exercise randomized paths that include these new actions.fn random_action(rng: &mut StdRng) -> Action { - match rng.gen_range(0..8) { + match rng.gen_range(0..10) { 0 => Action::MintToCompressed(random_mint_to_action(rng)), 1 => Action::UpdateMintAuthority(random_update_authority_action(rng)), 2 => Action::UpdateFreezeAuthority(random_update_authority_action(rng)), 3 => Action::CreateSplMint(random_create_spl_mint_action(rng)), 4 => Action::MintToCToken(random_mint_to_decompressed_action(rng)), 5 => Action::UpdateMetadataField(random_update_metadata_field_action(rng)), 6 => Action::UpdateMetadataAuthority(random_update_metadata_authority_action(rng)), 7 => Action::RemoveMetadataKey(random_remove_metadata_key_action(rng)), + 8 => Action::DecompressMint(/* random_decompress_mint_action(rng) */), + 9 => Action::CloseCMint(/* random_close_cmint_action(rng) */), _ => unreachable!(), } }programs/compressed-token/program/src/mint_action/processor.rs (1)
3-30: Remove unused imports - CI is failing.Static analysis confirms these imports are unused in
processor.rsand are causing CI failures:use anchor_compressed_token::ErrorCode; use anchor_lang::prelude::ProgramError; -use borsh::BorshSerialize; use light_compressed_account::instruction_data::with_readonly::InstructionDataInvokeCpiWithReadOnly; -use light_compressible::rent::get_rent_exemption_lamports; use light_ctoken_interface::{ hash_cache::HashCache, instructions::mint_action::MintActionCompressedInstructionData, - state::{CompressedMint, ExtensionStruct}, + state::CompressedMint, CTokenError, }; use light_sdk::instruction::PackedMerkleContext; use light_zero_copy::{traits::ZeroCopyAt, ZeroCopyNew}; -use pinocchio::{ - account_info::AccountInfo, - sysvars::{clock::Clock, rent::Rent, Sysvar}, -}; +use pinocchio::account_info::AccountInfo; use crate::{ mint_action::{ accounts::{AccountsConfig, MintActionAccounts}, create_mint::process_create_mint_action, mint_input::create_input_compressed_mint_account, mint_output::process_output_compressed_account, queue_indices::QueueIndices, zero_copy_config::get_zero_copy_configs, }, - shared::{ - convert_program_error, cpi::execute_cpi_invoke, transfer_lamports::transfer_lamports, - }, + shared::cpi::execute_cpi_invoke, };Note:
BorshSerialize,get_rent_exemption_lamports,ExtensionStruct,Clock,Rent,Sysvar,convert_program_error, andtransfer_lamportsappear to be used inmint_output.rs(per external context), not here.sdk-libs/token-client/src/actions/mint_action.rs (1)
167-167: Remove debugprintln!before merging.This debug statement will pollute stdout in production/library usage. Consider using
log::debug!or removing entirely.- println!("params {:?}", params);
| #[msg("Missing mint signer account for mint action")] | ||
| MintActionMissingMintSigner, |
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: Duplicate enum variant will cause compilation error.
The variant MintActionMissingMintSigner is already defined at line 302 with message "Missing mint signer account for SPL mint creation". Rust does not allow duplicate enum variant names.
Apply this diff to rename the new variant:
- // CMint (decompressed compressed mint) specific errors
- #[msg("Missing mint signer account for mint action")]
- MintActionMissingMintSigner,
+ // CMint (decompressed compressed mint) specific errors
+ #[msg("Missing mint signer account for CMint decompress action")]
+ MintActionMissingCMintSigner,📝 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.
| #[msg("Missing mint signer account for mint action")] | |
| MintActionMissingMintSigner, | |
| // CMint (decompressed compressed mint) specific errors | |
| #[msg("Missing mint signer account for CMint decompress action")] | |
| MintActionMissingCMintSigner, |
🤖 Prompt for AI Agents
In programs/compressed-token/anchor/src/lib.rs around lines 432-433, the new
enum variant MintActionMissingMintSigner duplicates an existing variant defined
at line 302, causing a compile error; rename this new variant (for example to
MintActionMissingMintAuthority or MintActionMissingMintSignerForAction), update
its #[msg(...)] text if needed to remain accurate, and then update all places
that reference this new variant to use the new name so the enum has no duplicate
variant identifiers.
| // 10. Close account (assign to system program, resize to 0) | ||
| unsafe { | ||
| cmint.assign(&[0u8; 32]); | ||
| } | ||
| cmint | ||
| .resize(0) | ||
| .map_err(|e| ProgramError::Custom(u64::from(e) as 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
Magic number 6000 in error mapping—use a named constant.
The error offset 6000 appears to be a convention for pinocchio errors, but a named constant would improve clarity and maintainability.
+const PINOCCHIO_ERROR_OFFSET: u32 = 6000;
+
cmint
.resize(0)
- .map_err(|e| ProgramError::Custom(u64::from(e) as u32 + 6000))?;
+ .map_err(|e| ProgramError::Custom(u64::from(e) as u32 + PINOCCHIO_ERROR_OFFSET))?;📝 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.
| // 10. Close account (assign to system program, resize to 0) | |
| unsafe { | |
| cmint.assign(&[0u8; 32]); | |
| } | |
| cmint | |
| .resize(0) | |
| .map_err(|e| ProgramError::Custom(u64::from(e) as u32 + 6000))?; | |
| // 10. Close account (assign to system program, resize to 0) | |
| const PINOCCHIO_ERROR_OFFSET: u32 = 6000; | |
| unsafe { | |
| cmint.assign(&[0u8; 32]); | |
| } | |
| cmint | |
| .resize(0) | |
| .map_err(|e| ProgramError::Custom(u64::from(e) as u32 + PINOCCHIO_ERROR_OFFSET))?; |
🤖 Prompt for AI Agents
In programs/compressed-token/program/src/mint_action/actions/close_cmint.rs
around lines 157 to 163, the error mapping uses the magic number 6000; define a
descriptive constant (e.g., PINOCCHIO_ERROR_OFFSET: u32 = 6000) at the top of
the module or in a shared constants module and replace the literal 6000 with
that constant in the map_err call (ensuring types align when adding the
converted error value), so the offset is named and maintainable.
| unsafe { | ||
| cmint.assign(&[0u8; 32]); | ||
| } |
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
Unsafe block for assign is acceptable but warrants comment.
The unsafe block uses pinocchio's low-level account manipulation. While this is an established pattern in the codebase, a brief comment explaining why it's safe would help reviewers.
// 10. Close account (assign to system program, resize to 0)
+ // SAFETY: CMint is owned by this program and we've validated all preconditions
unsafe {
cmint.assign(&[0u8; 32]);
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In programs/compressed-token/program/src/mint_action/actions/close_cmint.rs
around lines 158 to 160, the unsafe block calling cmint.assign(&[0u8; 32]) lacks
an explanatory comment; add a short comment above the unsafe block stating why
this use of assign is safe (e.g., that cmint is a pinned account with correct
ownership and size guarantees, no concurrent aliasing occurs, and the assignment
only sets known-zeroed data of exact expected length), and reference any
invariants or checks that ensure safety so reviewers can verify the rationale
quickly.
| let rent_exemption = get_rent_exemption_lamports(num_bytes) | ||
| .map_err(|_| ErrorCode::CMintTopUpCalculationFailed)?; |
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
Two different rent exemption calculation methods used.
Line 69 uses get_rent_exemption_lamports(num_bytes) while Line 109-110 uses Rent::get()?.minimum_balance(required_size). These should produce the same result, but using two different APIs for the same concept is error-prone if implementations ever diverge.
Consider consolidating to one approach:
- let rent_exemption = get_rent_exemption_lamports(num_bytes)
- .map_err(|_| ErrorCode::CMintTopUpCalculationFailed)?;
+ let rent = Rent::get().map_err(|_| ProgramError::UnsupportedSysvar)?;
+ let rent_exemption = rent.minimum_balance(num_bytes as usize);Also applies to: 109-110
🤖 Prompt for AI Agents
In programs/compressed-token/program/src/mint_action/mint_output.rs around lines
69 and 109-110, you are calling two different APIs to compute rent exemption
(get_rent_exemption_lamports(num_bytes) at ~69 and
Rent::get()?.minimum_balance(required_size) at ~109-110); consolidate to one
consistent approach (prefer using Rent::get()?.minimum_balance(size) everywhere)
so the same implementation is used; replace the call at line 69 with the
Rent::get()?.minimum_balance(num_bytes) pattern (or create a single helper
wrapper used by both call sites) and make sure to map errors consistently
(map_err to ErrorCode::CMintTopUpCalculationFailed) so both sites behave
identically on failure.
| // Check if DecompressMint action is present | ||
| let has_decompress_mint = params | ||
| .actions | ||
| .iter() | ||
| .any(|a| matches!(a, MintActionType::DecompressMint { .. })); | ||
|
|
||
| // Check if CloseCMint action is present | ||
| let has_close_cmint = params | ||
| .actions | ||
| .iter() | ||
| .any(|a| matches!(a, MintActionType::CloseCMint { .. })); |
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 client-side validation for mutually exclusive actions.
The program correctly rejects combining DecompressMint and CloseCMint (see accounts.rs lines 407-410), but adding early validation here would provide faster feedback to SDK users rather than waiting for transaction simulation failure.
let has_close_cmint = params
.actions
.iter()
.any(|a| matches!(a, MintActionType::CloseCMint { .. }));
+
+ // Validate mutually exclusive actions early
+ if has_decompress_mint && has_close_cmint {
+ return Err(RpcError::CustomError(
+ "Cannot combine DecompressMint and CloseCMint in the same instruction".to_string(),
+ ));
+ }Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In sdk-libs/token-client/src/instructions/mint_action.rs around lines 118 to
128, add a client-side validation that checks if both has_decompress_mint and
has_close_cmint are true and immediately return an error instead of continuing;
implement this by detecting the mutual exclusivity after computing the two
booleans and returning a descriptive Err of the function's existing error/Result
type (e.g., an InvalidInput or custom error variant with a message like
"DecompressMint and CloseCMint are mutually exclusive"), so SDK callers get fast
feedback; update or add a small unit test to assert the early-return behavior.
| // SPL mint synchronization not yet implemented for cmint_decompressed | ||
| if compressed_mint.metadata.cmint_decompressed { | ||
| unimplemented!("SPL mint synchronization for decompressed CMint not yet implemented"); | ||
| } | ||
| let decompressed_mint_config: Option<DecompressedMintConfig<Pubkey>> = None; | ||
| let spl_interface_pda: Option<light_ctoken_sdk::spl_interface::SplInterfacePda> = 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
Runtime panic for unimplemented path - consider returning an error instead.
Using unimplemented!() at Line 59 will cause a runtime panic if called with a decompressed CMint. While the comment clarifies the WIP status, consider returning a descriptive error for production safety:
- // SPL mint synchronization not yet implemented for cmint_decompressed
- if compressed_mint.metadata.cmint_decompressed {
- unimplemented!("SPL mint synchronization for decompressed CMint not yet implemented");
- }
+ // SPL mint synchronization not yet implemented for cmint_decompressed
+ if compressed_mint.metadata.cmint_decompressed {
+ return Err(RpcError::CustomError(
+ "SPL mint synchronization for decompressed CMint not yet implemented".to_string()
+ ));
+ }Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In sdk-libs/token-client/src/instructions/mint_to_compressed.rs around lines 57
to 62, the code calls unimplemented!() for the decompressed CMint path which
triggers a runtime panic; instead return a descriptive error Result: replace the
unimplemented!() with an Err(...) using the crate's existing error enum (or add
a new variant like UnsupportedDecompressedCmint) and propagate it up, update the
function signature to return Result<..., YourErrorType> if it doesn't already,
and ensure callers handle that error; leave the decompressed_mint_config and
spl_interface_pda as None when returning the error or remove unreachable
assignments as appropriate.
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)
programs/compressed-token/program/docs/instructions/MINT_ACTION.md (1)
131-142: Update documentation to use "cmint_decompressed" instead of "SPL mint initialized".Line 131 references "If SPL mint initialized" but the implementation consistently uses the
cmint_decompressedflag throughout mint_action. Update the documentation to read "If cmint_decompressed: validate token pool PDA derivation" for consistency with the actual field naming.sdk-libs/token-client/src/actions/mint_action.rs (1)
167-167: Remove debug println before merging.This
println!statement appears to be leftover debug code. It should be removed or converted to a proper logging mechanism before merging to avoid cluttering consumer logs.- println!("params {:?}", params);programs/compressed-token/program/tests/mint_action.rs (1)
116-128: Test generator missing new action types.The
random_actionfunction generates actions 0-7 but doesn't includeDecompressMint(index 8) orCloseCMint(index 9). This means randomized tests won't cover paths involving these new actions.fn random_action(rng: &mut StdRng) -> Action { - match rng.gen_range(0..8) { + match rng.gen_range(0..10) { 0 => Action::MintToCompressed(random_mint_to_action(rng)), 1 => Action::UpdateMintAuthority(random_update_authority_action(rng)), 2 => Action::UpdateFreezeAuthority(random_update_authority_action(rng)), 3 => Action::CreateSplMint(random_create_spl_mint_action(rng)), 4 => Action::MintToCToken(random_mint_to_decompressed_action(rng)), 5 => Action::UpdateMetadataField(random_update_metadata_field_action(rng)), 6 => Action::UpdateMetadataAuthority(random_update_metadata_authority_action(rng)), 7 => Action::RemoveMetadataKey(random_remove_metadata_key_action(rng)), + 8 => Action::DecompressMint(random_decompress_mint_action(rng)), + 9 => Action::CloseCMint(random_close_cmint_action(rng)), _ => unreachable!(), } }You'll also need to add helper functions
random_decompress_mint_actionandrandom_close_cmint_actionfollowing the existing patterns.
| #[cfg(target_os = "solana")] | ||
| { | ||
| let is_compressible = compression_info | ||
| .is_compressible(cmint.data_len() as u64, current_slot, cmint.lamports()) | ||
| .map_err(|_| ProgramError::InvalidAccountData)?; | ||
|
|
||
| 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.
🧹 Nitpick | 🔵 Trivial
Consider extracting compressibility result for better diagnostics.
The is_compressible check only verifies is_none() but discards the actual result which could contain useful information about why the account became compressible. Consider logging the result for debugging purposes.
#[cfg(target_os = "solana")]
{
let is_compressible = compression_info
.is_compressible(cmint.data_len() as u64, current_slot, cmint.lamports())
.map_err(|_| ProgramError::InvalidAccountData)?;
if is_compressible.is_none() {
msg!("CMint is not compressible (rent not expired)");
return Err(ErrorCode::CMintNotCompressible.into());
}
+ // is_compressible returned Some(_) - account is eligible for compression
}🤖 Prompt for AI Agents
In programs/compressed-token/program/src/mint_action/actions/close_cmint.rs
around lines 120 to 130, capture and pattern-match the result of
compression_info.is_compressible into a variable instead of discarding it, then
log the detailed result when it is Some(...) for diagnostics (using msg! with a
debug display), and keep the existing behavior of logging and returning
ErrorCode::CMintNotCompressible when the result is None; ensure any map_err
stays in place to convert errors to ProgramError::InvalidAccountData.
| // 10. Close account (assign to system program, resize to 0) | ||
| unsafe { | ||
| cmint.assign(&[0u8; 32]); | ||
| } | ||
| cmint | ||
| .resize(0) | ||
| .map_err(|e| ProgramError::Custom(u64::from(e) as 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
🧩 Analysis chain
🏁 Script executed:
# Search for 6000 usage in compressed-token program
rg -n "6000" programs/compressed-token/ --type rustRepository: Lightprotocol/light-protocol
Length of output: 1445
🏁 Script executed:
# Look for error code definitions in compressed-token program
fd error -type f programs/compressed-token/ | head -20Repository: Lightprotocol/light-protocol
Length of output: 242
🏁 Script executed:
# Check the error handling pattern in the specific file
cat -n programs/compressed-token/program/src/mint_action/actions/close_cmint.rs | sed -n '150,170p'Repository: Lightprotocol/light-protocol
Length of output: 940
🏁 Script executed:
# Search for resize error handling patterns across the codebase
rg -n "\.resize\(" programs/compressed-token/ --type rust -A 1 -B 1Repository: Lightprotocol/light-protocol
Length of output: 1700
🏁 Script executed:
# Check for similar offset patterns in error mapping
rg -n "ProgramError::Custom.*\+ [0-9]{4}" programs/compressed-token/ --type rustRepository: Lightprotocol/light-protocol
Length of output: 1321
🏁 Script executed:
# Check the convert_program_error function
cat programs/compressed-token/program/src/shared/convert_program_error.rsRepository: Lightprotocol/light-protocol
Length of output: 311
🏁 Script executed:
# Look at error code definitions in the anchor error file
cat programs/compressed-token/anchor/src/lib.rs | head -100Repository: Lightprotocol/light-protocol
Length of output: 3899
🏁 Script executed:
# Check if there's documentation on the 6000 offset convention
find programs/compressed-token -name "*.md" -o -name "CLAUDE.md" | xargs cat 2>/dev/nullRepository: Lightprotocol/light-protocol
Length of output: 50386
🏁 Script executed:
# Compare with the CMintResizeFailed pattern to understand the inconsistency
rg -n "CMintResizeFailed" programs/compressed-token/ --type rust -B 2 -A 2Repository: Lightprotocol/light-protocol
Length of output: 1639
Consider using the existing convert_program_error() helper function instead of duplicating the offset logic.
The 6000 offset is a standard convention used throughout the program for converting Pinocchio errors to Anchor error codes. However, rather than inlining this offset, the code should reuse the existing convert_program_error() function (defined in src/shared/convert_program_error.rs), which already encapsulates this conversion. This improves maintainability and reduces duplication across the codebase.
Change:
cmint
.resize(0)
.map_err(|e| ProgramError::Custom(u64::from(e) as u32 + 6000))?;To:
cmint
.resize(0)
.map_err(convert_program_error)?;🤖 Prompt for AI Agents
In programs/compressed-token/program/src/mint_action/actions/close_cmint.rs
around lines 157 to 163, the code inlines the Pinocchio-to-Anchor error offset
(adding 6000) when mapping the resize error; replace the closure mapping
(map_err(|e| ProgramError::Custom(u64::from(e) as u32 + 6000))) with a direct
call to the existing convert_program_error function
(map_err(convert_program_error)) and add the appropriate use/import for
convert_program_error from src/shared/convert_program_error.rs so the symbol is
in scope.
| let compression_info = CompressionInfo { | ||
| config_account_version: config.version, | ||
| compress_to_pubkey: 0, // Not applicable for CMint | ||
| account_version: 3, // ShaFlat version | ||
| lamports_per_write: action.write_top_up.into(), | ||
| compression_authority: config.compression_authority.to_bytes(), | ||
| rent_sponsor: config.rent_sponsor.to_bytes(), | ||
| last_claimed_slot: current_slot, | ||
| rent_config: RentConfig { | ||
| base_rent: config.rent_config.base_rent, | ||
| compression_cost: config.rent_config.compression_cost, | ||
| lamports_per_byte_per_epoch: config.rent_config.lamports_per_byte_per_epoch, | ||
| max_funded_epochs: config.rent_config.max_funded_epochs, | ||
| max_top_up: config.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
Hardcoded account_version may limit future flexibility.
account_version: 3 (ShaFlat) is hardcoded. If different versions become supported, this would need to be parameterized. For now, document the assumption.
let compression_info = CompressionInfo {
config_account_version: config.version,
compress_to_pubkey: 0, // Not applicable for CMint
- account_version: 3, // ShaFlat version
+ account_version: 3, // ShaFlat version - currently the only supported version for CMint
lamports_per_write: action.write_top_up.into(),🤖 Prompt for AI Agents
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
lines 132-147: the CompressionInfo currently hardcodes account_version: 3 which
reduces future flexibility; change this to derive the account_version from a
source (preferred: add it to the config struct and use config.account_version)
or at minimum replace the magic number with a named constant and add a clear
comment documenting the assumption and supported version(s); update any relevant
types/constructors to pass the version through so new versions can be supported
without code changes.
| let fee_payer = validated_accounts | ||
| .executing | ||
| .as_ref() | ||
| .map(|exec| exec.system.fee_payer) | ||
| .ok_or_else(|| { | ||
| msg!("Fee payer required for DecompressMint action"); | ||
| ProgramError::NotEnoughAccountKeys | ||
| })?; |
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 fee_payer retrieval into a helper.
The same pattern for extracting fee_payer appears twice (lines 145-152 and 162-169). While not critical, a small helper could reduce duplication:
fn get_fee_payer<'a>(
validated_accounts: &MintActionAccounts<'a, '_>,
action_name: &str,
) -> Result<&'a AccountInfo, ProgramError> {
validated_accounts
.executing
.as_ref()
.map(|exec| exec.system.fee_payer)
.ok_or_else(|| {
msg!("Fee payer required for {} action", action_name);
ProgramError::NotEnoughAccountKeys
})
}Also applies to: 162-169
🤖 Prompt for AI Agents
In programs/compressed-token/program/src/mint_action/actions/process_actions.rs
around lines 145-152 and 162-169, the fee_payer extraction is duplicated; create
a small helper function (e.g., get_fee_payer) that accepts a reference to
validated_accounts and an action name, performs the existing map/ok_or_else
logic and returns the fee_payer Result, then replace both in-place extractions
with calls to that helper passing the appropriate action name to preserve the
contextual msg! string; ensure lifetimes and return type match the existing
usage and keep the same ProgramError on failure.
| /// Decompress the compressed mint to a CMint Solana account. | ||
| /// CMint is always compressible - rent_payment must be >= 2. | ||
| DecompressMint { | ||
| cmint_bump: u8, | ||
| /// Rent payment in epochs (prepaid). Must be >= 2. | ||
| rent_payment: u8, | ||
| /// Lamports allocated for future write operations (top-up per write). | ||
| write_top_up: u32, | ||
| }, | ||
| /// Close a CMint Solana account. The compressed mint state is preserved. | ||
| /// Authority can be compression_authority, mint_authority, or freeze_authority. | ||
| /// All require is_compressible() to return true (rent expired). | ||
| CloseCMint { | ||
| /// If true, succeed silently when CMint doesn't exist | ||
| idempotent: bool, | ||
| }, |
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
New action types for CMint lifecycle look well-structured.
The DecompressMint and CloseCMint variants are cleanly documented. However, I notice the comment states rent_payment must be >= 2, but there's no client-side validation enforcing this constraint before the instruction is built.
Consider adding validation in create_mint_action_instruction to fail fast with a clear error rather than letting it reach the on-chain program:
+ MintActionType::DecompressMint {
+ cmint_bump,
+ rent_payment,
+ write_top_up,
+ } => {
+ if rent_payment < 2 {
+ return Err(RpcError::CustomError(
+ "DecompressMint rent_payment must be >= 2".to_string(),
+ ));
+ }
+ instruction_data.with_decompress_mint(DecompressMintAction {Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In sdk-libs/token-client/src/instructions/mint_action.rs around lines 69 to 84,
add client-side validation in create_mint_action_instruction so DecompressMint
fails fast if rent_payment < 2: detect the DecompressMint variant before
building the instruction, check rent_payment >= 2 and return an Err with a clear
error (or a descriptive Result::Err string) instead of constructing the
instruction; do not modify on-chain types — just validate inputs, keep
write_top_up as-is, and add/update a small unit test asserting that providing
rent_payment < 2 returns the expected client-side error.
| // SPL mint synchronization not yet implemented for cmint_decompressed | ||
| if compressed_mint.metadata.cmint_decompressed { | ||
| unimplemented!("SPL mint synchronization for decompressed CMint not yet implemented"); | ||
| } |
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.
Return an error instead of panicking.
The unimplemented!() macro will cause a panic at runtime if cmint_decompressed is true. SDK functions exposed to users should return errors gracefully rather than panicking.
Apply this diff to return an appropriate error:
- // SPL mint synchronization not yet implemented for cmint_decompressed
- if compressed_mint.metadata.cmint_decompressed {
- unimplemented!("SPL mint synchronization for decompressed CMint not yet implemented");
- }
+ // SPL mint synchronization not yet implemented for cmint_decompressed
+ if compressed_mint.metadata.cmint_decompressed {
+ return Err(RpcError::CustomError(
+ "SPL mint synchronization for decompressed CMint not yet implemented".to_string()
+ ));
+ }Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In sdk-libs/token-client/src/instructions/mint_to_compressed.rs around lines 57
to 60, replace the panic-causing unimplemented!() with a graceful error return:
update the function to return a Result if it does not already, add (or reuse) a
clear error variant such as TokenClientError::CmintDecompressedNotSupported (or
an appropriate ProgramError/InstructionError mapping) and return Err(...) with
that variant and a descriptive message when
compressed_mint.metadata.cmint_decompressed is true; ensure any error type used
is exported to callers and update callers/tests if needed.
| let decompressed_mint_config: Option<DecompressedMintConfig<Pubkey>> = None; | ||
| let spl_interface_pda: Option<light_ctoken_sdk::spl_interface::SplInterfacePda> = 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 removing explicit type annotations.
The explicit type annotations on the None values are clear but likely unnecessary, as Rust can infer these types from the usage in MintToCompressedInputs on lines 88 and 91. However, they do document the transition phase clearly, so this is optional.
If you prefer cleaner code, apply this diff:
- let decompressed_mint_config: Option<DecompressedMintConfig<Pubkey>> = None;
- let spl_interface_pda: Option<light_ctoken_sdk::spl_interface::SplInterfacePda> = None;
+ let decompressed_mint_config = None;
+ let spl_interface_pda = None;🤖 Prompt for AI Agents
In sdk-libs/token-client/src/instructions/mint_to_compressed.rs around lines 61
to 62, remove the unnecessary explicit type annotations on the two None
initializations (decompressed_mint_config and spl_interface_pda) so they become
simple let bindings initialized to None and allow the compiler to infer their
types from later usage in MintToCompressedInputs; keep the variable names and
initial None values unchanged.
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: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
programs/compressed-token/program/src/mint_action/actions/create_mint.rs (1)
103-114: Single-extension constraint is clear, but consider documenting the supported extension type.The code enforces exactly one extension (TokenMetadata), but the validation of the extension type is deferred. A brief inline comment or debug assertion could help future maintainers understand which extension types are actually permitted here.
// Validate extensions - only TokenMetadata is supported and at most one extension allowed if let Some(extensions) = &mint.extensions { if extensions.len() != 1 { msg!( "Only one extension allowed for compressed mints, found {}", extensions.len() ); return Err(ErrorCode::MintActionUnsupportedOperation.into()); } - // Extension type validation happens during allocation/creation - // TokenMetadata is the only supported extension type + // Extension type validation (TokenMetadata only) happens during allocation/creation + // via CompressedMint::try_from() which validates the extension discriminator }sdk-libs/ctoken-sdk/src/compressed_token/v2/create_compressed_mint/instruction.rs (1)
143-147: Minor formatting: missing space after comma.Line 146 has
compressed_mint_instruction_data,input.cpi_contextwithout a space after the comma.let instruction_data = light_ctoken_interface::instructions::mint_action::MintActionCompressedInstructionData::new_mint_write_to_cpi_context( input.mint_address, input.address_merkle_tree_root_index, - compressed_mint_instruction_data,input.cpi_context + compressed_mint_instruction_data, input.cpi_context );sdk-libs/token-client/src/actions/mint_action.rs (1)
167-167: Remove debug println before merge.This debug statement should be removed for production code.
- println!("params {:?}", params); mint_action(rpc, params, authority, payer, mint_signer).awaitprograms/compressed-token/program/src/extensions/mod.rs (1)
220-220: Consider returning ArrayVec directly to avoid allocation.The function builds an
ArrayVec<_, 20>but then converts toVecvia.into_iter().collect(). Since the caller receivesVec<AdditionalMetadataConfig>, this allocation is necessary for the current signature. However, if performance is critical, consider changing the return type toArrayVec<AdditionalMetadataConfig, 20>if callers can work with it.fn build_metadata_config( metadata: &[AdditionalMetadata], actions: &[ZAction], extension_index: usize, -) -> Vec<AdditionalMetadataConfig> { +) -> arrayvec::ArrayVec<AdditionalMetadataConfig, 20> { // ... - configs.into_iter().collect() + configs }This would require updating
TokenMetadataConfig.additional_metadatato acceptArrayVecor a generic.
♻️ Duplicate comments (6)
programs/compressed-token/program/src/mint_action/actions/process_actions.rs (1)
141-167: LGTM! New action handlers properly integrated.The handlers for
DecompressMintandCompressAndCloseCMintare well-integrated:
- DecompressMint retrieves required accounts (mint_signer, fee_payer) with appropriate error handling
- CompressAndCloseCMint delegates to its processor with validated_accounts
- Error messages are descriptive
Note: The fee_payer extraction pattern at lines 145-152 is duplicated at lines 197-204. This was already flagged in a previous review as a potential refactoring opportunity to reduce duplication.
programs/compressed-token/program/src/mint_action/actions/mod.rs (1)
1-12: LGTM! Module organization reflects active actions.The addition of
compress_and_close_cmintanddecompress_mintmodules with their public exports, along with the removal ofcreate_spl_mint, aligns with the current supported action set. This is consistent with theCreateSplMintaction being explicitly disabled (returningMintActionUnsupportedOperation) inprocess_actions.rs.Note: As flagged in a previous review, the
CreateSplMintaction variant remains in the documented 9-action requirement but is unsupported. This module removal reinforces that the action is not implemented.programs/compressed-token/program/src/mint_action/actions/compress_and_close_cmint.rs (1)
118-124: Unsafe block for account closure—document the safety invariants.The
unsafeblock usingcmint.assign(&[0u8; 32])manipulates account ownership. While this is an established pattern with pinocchio, a safety comment helps reviewers confirm preconditions are met.// 7. Close account (assign to system program, resize to 0) + // SAFETY: cmint is owned by this program and we've validated it matches + // compressed_mint.metadata.mint; all lamports have been transferred out unsafe { cmint.assign(&[0u8; 32]); }sdk-libs/token-client/src/instructions/mint_to_compressed.rs (1)
56-66: Return an error instead of panicking withunimplemented!().SDK functions exposed to users should handle edge cases gracefully. The
unimplemented!()macro will cause a runtime panic ifcmint_decompressedis true, which breaks user applications rather than providing a recoverable error.if cmint_decompressed { - unimplemented!("SPL mint synchronization for decompressed CMint not yet implemented"); + return Err(RpcError::CustomError( + "SPL mint synchronization for decompressed CMint not yet implemented".to_string() + )); }sdk-libs/token-client/src/instructions/mint_action.rs (2)
69-83: New action types are well-documented with clear constraints.The
DecompressMintvariant documents thatrent_payment must be >= 2, andCompressAndCloseCMintdocuments its permissionless nature. However, as noted in a past review comment, there's no client-side validation enforcing therent_payment >= 2constraint before building the instruction.Consider adding early validation as suggested in the previous review to provide faster feedback to SDK users.
117-127: Action detection for CMint operations is correct.The code properly detects presence of
DecompressMintandCompressAndCloseCMintactions. However, as noted in a past review comment, these actions are mutually exclusive (enforced on-chain inaccounts.rslines 407-410), but there's no client-side validation here.Adding early validation would provide faster feedback than waiting for transaction simulation failure.
| ZExtensionInstructionData::Compressible(compression_info) => { | ||
| // Convert zero-copy CompressionInfo to owned CompressionInfo | ||
| Ok(ExtensionStruct::Compressible( | ||
| light_compressible::compression_info::CompressionInfo { | ||
| config_account_version: compression_info | ||
| .config_account_version | ||
| .into(), | ||
| compress_to_pubkey: compression_info.compress_to_pubkey, | ||
| account_version: compression_info.account_version, | ||
| lamports_per_write: compression_info.lamports_per_write.into(), | ||
| compression_authority: compression_info.compression_authority, | ||
| rent_sponsor: compression_info.rent_sponsor, | ||
| last_claimed_slot: compression_info.last_claimed_slot.into(), | ||
| rent_config: light_compressible::rent::RentConfig { | ||
| base_rent: compression_info.rent_config.base_rent.into(), | ||
| compression_cost: compression_info | ||
| .rent_config | ||
| .compression_cost | ||
| .into(), | ||
| lamports_per_byte_per_epoch: compression_info | ||
| .rent_config | ||
| .lamports_per_byte_per_epoch, | ||
| max_funded_epochs: compression_info | ||
| .rent_config | ||
| .max_funded_epochs, | ||
| max_top_up: compression_info.rent_config.max_top_up.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
Manual CompressionInfo field mapping is verbose but correct.
The zero-copy to owned conversion maps all fields from the ZCompressionInfo structure. This is necessary because zero-copy types can't be directly converted to owned types without explicit field copying.
However, consider whether a From or Into implementation on CompressionInfo itself would reduce duplication if this conversion pattern appears elsewhere:
// In light_compressible crate
impl From<&ZCompressionInfo<'_>> for CompressionInfo {
fn from(z: &ZCompressionInfo<'_>) -> Self { ... }
}🤖 Prompt for AI Agents
program-libs/ctoken-interface/src/instructions/mint_action/instruction_data.rs
lines 195-224: the manual field-by-field conversion from the zero-copy
ZCompressionInfo to the owned CompressionInfo is verbose and should be
refactored; add an implementation of From<&ZCompressionInfo<'_>> for
light_compressible::compression_info::CompressionInfo in the light_compressible
crate that copies each field, then replace the long inline mapping with a simple
CompressionInfo::from(&compression_info) or compression_info.into() to keep the
conversion centralized and reduce duplication.
| pub fn from_account_info_checked( | ||
| program_id: &[u8; 32], | ||
| account_info: &pinocchio::account_info::AccountInfo, | ||
| ) -> Result<Self, CTokenError> { | ||
| // 1. Check program ownership | ||
| if !account_info.is_owned_by(program_id) { | ||
| #[cfg(feature = "solana")] | ||
| msg!("CMint account has invalid owner"); | ||
| return Err(CTokenError::InvalidCMintOwner); | ||
| } | ||
|
|
||
| // 2. Borrow and deserialize account data | ||
| let data = account_info | ||
| .try_borrow_data() | ||
| .map_err(|_| CTokenError::CMintBorrowFailed)?; | ||
|
|
||
| let mint = | ||
| Self::try_from_slice(&data).map_err(|_| CTokenError::CMintDeserializationFailed)?; | ||
|
|
||
| // 3. Check is_initialized | ||
| if !mint.base.is_initialized { | ||
| #[cfg(feature = "solana")] | ||
| msg!("CMint account is not initialized"); | ||
| return Err(CTokenError::CMintNotInitialized); | ||
| } | ||
|
|
||
| Ok(mint) | ||
| } |
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 all usages of from_account_info_checked
rg -n -B3 -A3 'from_account_info_checked' --type rustRepository: Lightprotocol/light-protocol
Length of output: 1617
🏁 Script executed:
# Also check the account-checks module to understand available validation patterns
find program-libs/account-checks -type f -name "*.rs" | head -20Repository: Lightprotocol/light-protocol
Length of output: 790
🏁 Script executed:
# Check if there's a checks.rs file in account-checks
fd checks\.rs program-libs/account-checksRepository: Lightprotocol/light-protocol
Length of output: 113
🏁 Script executed:
# Get more context around the usage in processor.rs
sed -n '50,90p' programs/compressed-token/program/src/mint_action/processor.rsRepository: Lightprotocol/light-protocol
Length of output: 1952
🏁 Script executed:
# Examine the checks.rs module structure and available functions
cat program-libs/account-checks/src/checks.rsRepository: Lightprotocol/light-protocol
Length of output: 5692
🏁 Script executed:
# Check if validated_accounts has PDA validation
rg -n 'get_cmint' programs/compressed-token/program/src/mint_action/processor.rs -B10 -A5Repository: Lightprotocol/light-protocol
Length of output: 784
🏁 Script executed:
# Find the definition of validated_accounts and get_cmint
rg -n 'get_cmint' programs/compressed-token/program/src/mint_action/processor.rs -B20 | head -40Repository: Lightprotocol/light-protocol
Length of output: 1114
🏁 Script executed:
# Search for ValidatedAccounts definition and its get_cmint method
rg -n 'struct ValidatedAccounts' programs/compressed-token/program/src/ -A20Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Search more broadly for get_cmint method definition
rg -n 'fn get_cmint' programs/compressed-token/program/src/ -A10Repository: Lightprotocol/light-protocol
Length of output: 1163
🏁 Script executed:
# Get the full validate_accounts method
sed -n '304,350p' programs/compressed-token/program/src/mint_action/accounts.rsRepository: Lightprotocol/light-protocol
Length of output: 1960
🏁 Script executed:
# Search for PDA validation of cmint in validate_accounts
rg -n 'cmint' programs/compressed-token/program/src/mint_action/accounts.rs -A3 -B3 | grep -A5 -B5 'validate\|pda\|derive'Repository: Lightprotocol/light-protocol
Length of output: 1887
🏁 Script executed:
# Check if there's any PDA validation happening in the accounts setup
rg -n 'check_pda' programs/compressed-token/program/src/mint_action/accounts.rsRepository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Check the CLAUDE.md or docs for any guidance on PDA validation patterns
find program-libs/ctoken-interface -name "*.md" -type f | xargs cat 2>/dev/null | head -100Repository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# See if there are any other account validation patterns in the codebase
rg -n 'check_pda_seeds' programs/ -B3 -A3 | head -50Repository: Lightprotocol/light-protocol
Length of output: 2017
Add PDA derivation validation at the call site.
The method's three-step validation is sound (ownership, deserialization, initialization), but the assumption that callers verify PDA derivation isn't being met. The only call site in mint_action/processor.rs (line 68) retrieves the CMint account from validated_accounts.get_cmint(), which validates pubkey matching but does not derive the PDA from seeds. This creates a gap: the method documentation claims PDA verification is the caller's responsibility, but the caller doesn't actually perform it.
Use check_pda_seeds or check_pda_seeds_with_bump from the account-checks module (available in light_account_checks::checks) to validate the CMint PDA derivation before calling from_account_info_checked.
🤖 Prompt for AI Agents
program-libs/ctoken-interface/src/state/mint/compressed_mint.rs lines 79-106:
the function assumes the caller has validated the CMint PDA derivation but the
actual caller in mint_action/processor.rs does not derive/check the PDA; update
the caller (mint_action/processor.rs around line 68) to call
light_account_checks::checks::check_pda_seeds or check_pda_seeds_with_bump with
the CMint account pubkey, program_id, and the correct PDA seeds (and bump if
required) before invoking from_account_info_checked so the PDA derivation is
validated at the call site as the method documentation requires.
| // Check for duplicate keys (O(n^2) but acceptable for max 20 items) | ||
| for i in 0..token_metadata.additional_metadata.len() { | ||
| for j in (i + 1)..token_metadata.additional_metadata.len() { | ||
| if token_metadata.additional_metadata[i].key | ||
| == token_metadata.additional_metadata[j].key | ||
| { | ||
| msg!("Duplicate metadata key found at positions {} and {}", i, j); | ||
| return Err(CTokenError::DuplicateMetadataKey); | ||
| } | ||
| } | ||
| } |
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 key validation is correct but could be clearer.
The O(n²) duplicate check is acceptable for max 20 items as commented. However, the comparison item.key == item.key pattern relies on Vec<u8> equality. Consider adding a brief comment that this compares byte vectors:
// Compare key byte vectors for equality
if token_metadata.additional_metadata[i].key
== token_metadata.additional_metadata[j].key🤖 Prompt for AI Agents
programs/compressed-token/program/src/extensions/mod.rs around lines 80 to 90:
the duplicate-key check is correct but unclear because it compares Vec<u8>
values using equality; add a short comment immediately before the comparison
stating that the equality is comparing key byte vectors (Vec<u8>) element-wise,
e.g. “Compare key byte vectors for equality” so future readers understand the
intent.
| let mut mint_metadata = random_compressed_mint_metadata(rng); | ||
| if let Some(spl_init) = force_spl_initialized { | ||
| mint_metadata.spl_mint_initialized = spl_init && create_mint.is_none(); | ||
| mint_metadata.cmint_decompressed = spl_init && create_mint.is_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
Logic looks correct but variable name is misleading.
The parameter is named force_spl_initialized but it's now setting cmint_decompressed. Consider renaming for clarity:
fn generate_random_instruction_data(
rng: &mut StdRng,
force_create_mint: Option<bool>,
force_cpi_context: Option<bool>,
- force_spl_initialized: Option<bool>,
+ force_cmint_decompressed: Option<bool>,
action_count_range: std::ops::Range<usize>,
) -> MintActionCompressedInstructionData {And update the call site comments at line 289 accordingly.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
programs/compressed-token/program/tests/mint_action.rs lines 168-171: the
boolean parameter named `force_spl_initialized` is misleading because the code
sets `mint_metadata.cmint_decompressed`; rename the parameter to something like
`force_cmint_decompressed` (or `force_cmint_decompressed_flag`) and update all
usages and documentation, and adjust the comment at the call site around line
289 to reflect the new parameter name and semantics; ensure function signature
and any tests referencing the old name are updated consistently.
| // Try to deserialize the compressed mint - may be None if CMint is source of truth | ||
| let compressed_mint: Option<CompressedMint> = compressed_mint_account | ||
| .data | ||
| .as_ref() | ||
| .and_then(|d| BorshDeserialize::deserialize(&mut d.data.as_slice()).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
Graceful handling of optional mint data.
The deserialization now handles the case where compressed mint data might not be present (when CMint is source of truth). Using and_then with ok() means deserialization failures result in None rather than an error.
However, this silently swallows deserialization errors. Consider whether a warn! log would help debugging:
let compressed_mint: Option<CompressedMint> = compressed_mint_account
.data
.as_ref()
.and_then(|d| {
BorshDeserialize::deserialize(&mut d.data.as_slice())
.map_err(|e| { /* optionally log e */ })
.ok()
});🤖 Prompt for AI Agents
In sdk-libs/token-client/src/instructions/mint_action.rs around lines 194 to
198, the code currently uses and_then(... .ok()) which silently converts any
deserialization error into None; change this to explicitly handle the Result so
deserialization failures are logged (e.g., warn! with the error and some
context) before returning None, by matching or using map_err to log the error
and then calling ok() so successful deserializations yield Some(CompressedMint)
and failures yield None but with a warning to aid debugging.
| // Try to deserialize the compressed mint - may be None if CMint is source of truth | ||
| let compressed_mint: Option<CompressedMint> = compressed_mint_account | ||
| .data | ||
| .as_ref() | ||
| .and_then(|d| BorshDeserialize::deserialize(&mut d.data.as_slice()).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.
Deserialization silently swallows errors—consider logging or propagating.
Using and_then(|d| BorshDeserialize::deserialize(...).ok()) converts deserialization failures into None, which then causes unwrap_or(true) to assume the CMint is decompressed. If there's actual data but it's malformed, this could mask bugs.
Consider at least logging when deserialization fails:
let compressed_mint: Option<CompressedMint> = compressed_mint_account
.data
.as_ref()
- .and_then(|d| BorshDeserialize::deserialize(&mut d.data.as_slice()).ok());
+ .and_then(|d| {
+ match BorshDeserialize::deserialize(&mut d.data.as_slice()) {
+ Ok(mint) => Some(mint),
+ Err(e) => {
+ // Log but don't fail - CMint may be source of truth
+ solana_msg::msg!("Warning: Failed to deserialize compressed mint: {:?}", e);
+ None
+ }
+ }
+ });🤖 Prompt for AI Agents
In sdk-libs/token-client/src/instructions/mint_to_compressed.rs around lines 42
to 46, the deserialization call uses .ok() which silently swallows errors and
can mask malformed data; change it to explicitly attempt deserialization and
handle the Err case (e.g., match or map_err) so you either log the error with
context (including account pubkey or source) before treating as None or
propagate the error up as a Result; ensure the log includes the deserialization
error string and account id, or return a descriptive error instead of defaulting
to None.
| mint: compressed_mint.map(|m| m.try_into().unwrap()), | ||
| }; |
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.
Potential panic from unwrap() inside map.
If compressed_mint is Some but try_into() fails, this will panic. Consider propagating the error instead.
- mint: compressed_mint.map(|m| m.try_into().unwrap()),
+ mint: compressed_mint
+ .map(|m| m.try_into())
+ .transpose()
+ .map_err(|e| RpcError::CustomError(format!("Failed to convert mint: {:?}", e)))?,📝 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.
| mint: compressed_mint.map(|m| m.try_into().unwrap()), | |
| }; | |
| mint: compressed_mint | |
| .map(|m| m.try_into()) | |
| .transpose() | |
| .map_err(|e| RpcError::CustomError(format!("Failed to convert mint: {:?}", e)))?, | |
| }; |
🤖 Prompt for AI Agents
In sdk-libs/token-client/src/instructions/mint_to_compressed.rs around lines
77-78, the code uses unwrap() on try_into() inside a map which can panic if
conversion fails; change this to propagate the conversion error instead: perform
the conversion that yields Result within the Option (e.g. map(|m|
m.try_into())), then use transpose() (or otherwise transform Option<Result<...>>
into Result<Option<...>>) so the function returns a Result and the error is
propagated rather than unwrapped; update the surrounding function signature to
return Result if necessary and handle the propagated error.
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: 5
♻️ Duplicate comments (1)
sdk-libs/ctoken-sdk/src/ctoken/burn.rs (1)
106-106: Clarify the "10-byte format" comment.The comment states "10-byte format" but when
max_top_upis included, the total instruction data is 11 bytes (1-byte discriminator + 8-byte amount + 2-byte max_top_up), not 10. If the comment refers to the data portion excluding the discriminator (8 + 2 = 10 bytes), this should be clarified to avoid confusion.Consider rewording:
// Append max_top_up (2 bytes) if set
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (8)
Cargo.lockis excluded by!**/*.lockand included by noneCargo.tomlis excluded by none and included by noneprogram-tests/compressed-token-test/tests/mint.rsis excluded by none and included by noneprogram-tests/compressed-token-test/tests/mint/burn.rsis excluded by none and included by noneprogram-tests/compressed-token-test/tests/mint/ctoken_mint_to.rsis excluded by none and included by noneprogram-tests/utils/src/assert_ctoken_burn.rsis excluded by none and included by noneprogram-tests/utils/src/assert_ctoken_mint_to.rsis excluded by none and included by noneprogram-tests/utils/src/lib.rsis excluded by none and included by none
📒 Files selected for processing (9)
programs/compressed-token/program/src/ctoken_burn.rs(1 hunks)programs/compressed-token/program/src/ctoken_mint_to.rs(1 hunks)programs/compressed-token/program/src/ctoken_transfer.rs(1 hunks)programs/compressed-token/program/src/lib.rs(5 hunks)programs/compressed-token/program/src/shared/compressible_top_up.rs(1 hunks)programs/compressed-token/program/src/shared/mod.rs(1 hunks)sdk-libs/ctoken-sdk/src/ctoken/burn.rs(1 hunks)sdk-libs/ctoken-sdk/src/ctoken/ctoken_mint_to.rs(1 hunks)sdk-libs/ctoken-sdk/src/ctoken/mod.rs(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
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/shared/mod.rsprograms/compressed-token/program/src/shared/compressible_top_up.rsprograms/compressed-token/program/src/ctoken_burn.rsprograms/compressed-token/program/src/ctoken_transfer.rsprograms/compressed-token/program/src/ctoken_mint_to.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/burn.rssdk-libs/ctoken-sdk/src/ctoken/ctoken_mint_to.rs
programs/compressed-token/program/src/ctoken_transfer.rs
📄 CodeRabbit inference engine (programs/compressed-token/program/CLAUDE.md)
CTokenTransfer instruction (discriminator: 3) must implement SPL-compatible transfers between decompressed accounts
Files:
programs/compressed-token/program/src/ctoken_transfer.rs
🧠 Learnings (52)
📓 Common learnings
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: 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
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: 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: 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/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
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)
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: 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 PDA must be derived using seeds `[b"compressible_config", version.to_le_bytes()]` with the stored bump seed for deterministic account address generation in Light Protocol
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
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/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
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: cli/README.md:0-0
Timestamp: 2025-11-24T17:53:40.537Z
Learning: Implement CLI commands: create-mint, mint-to, transfer, compress-sol, and decompress-sol with their specified flags and required parameters
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
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
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
📚 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/shared/mod.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/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/shared/mod.rsprograms/compressed-token/program/src/shared/compressible_top_up.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/ctoken_mint_to.rssdk-libs/ctoken-sdk/src/ctoken/burn.rssdk-libs/ctoken-sdk/src/ctoken/ctoken_mint_to.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/**/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/shared/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/**/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/shared/mod.rsprograms/compressed-token/program/src/ctoken_burn.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/ctoken_mint_to.rssdk-libs/ctoken-sdk/src/ctoken/burn.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/**/traits.rs : Core trait implementations (`HasCompressionInfo`, `CompressAs`, `Compressible`) should be defined in `traits.rs`
Applied to files:
programs/compressed-token/program/src/shared/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/**/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/shared/mod.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 : 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/shared/mod.rsprograms/compressed-token/program/src/shared/compressible_top_up.rsprograms/compressed-token/program/src/ctoken_burn.rsprograms/compressed-token/program/src/ctoken_transfer.rsprograms/compressed-token/program/src/ctoken_mint_to.rssdk-libs/ctoken-sdk/src/ctoken/burn.rssdk-libs/ctoken-sdk/src/ctoken/ctoken_mint_to.rsprograms/compressed-token/program/src/lib.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:
programs/compressed-token/program/src/shared/mod.rsprograms/compressed-token/program/src/shared/compressible_top_up.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/shared/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/**/seed_providers.rs : PDA and CToken seed provider implementations with client-side seed functions should be in `seed_providers.rs`
Applied to files:
programs/compressed-token/program/src/shared/mod.rssdk-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 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:
programs/compressed-token/program/src/shared/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:
programs/compressed-token/program/src/shared/mod.rsprograms/compressed-token/program/src/ctoken_transfer.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,**/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/shared/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/error.rs : Error types must use numeric codes in the 19xxx range for CToken-specific errors
Applied to files:
programs/compressed-token/program/src/shared/mod.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/ctoken_transfer.rs : CTokenTransfer instruction (discriminator: 3) must implement SPL-compatible transfers between decompressed accounts
Applied to files:
programs/compressed-token/program/src/shared/compressible_top_up.rsprograms/compressed-token/program/src/ctoken_burn.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/ctoken_transfer.rsprograms/compressed-token/program/src/ctoken_mint_to.rssdk-libs/ctoken-sdk/src/ctoken/burn.rssdk-libs/ctoken-sdk/src/ctoken/ctoken_mint_to.rsprograms/compressed-token/program/src/lib.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/compressible_top_up.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/shared/compressible_top_up.rsprograms/compressed-token/program/src/ctoken_burn.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/ctoken_transfer.rsprograms/compressed-token/program/src/ctoken_mint_to.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/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/shared/compressible_top_up.rsprograms/compressed-token/program/src/ctoken_burn.rsprograms/compressed-token/program/src/ctoken_mint_to.rssdk-libs/ctoken-sdk/src/ctoken/burn.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_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.
Applied to files:
programs/compressed-token/program/src/shared/compressible_top_up.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/shared/compressible_top_up.rsprograms/compressed-token/program/src/ctoken_transfer.rsprograms/compressed-token/program/src/ctoken_mint_to.rssdk-libs/ctoken-sdk/src/ctoken/ctoken_mint_to.rsprograms/compressed-token/program/src/lib.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 `claimable_lamports` function to return None if account is compressible, otherwise return Some(amount) for completed epochs only (excluding current ongoing epoch).
Applied to files:
programs/compressed-token/program/src/shared/compressible_top_up.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/shared/compressible_top_up.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_rent_exemption_lamports` function to return Solana's rent-exempt balance for a given account size, with error handling for unavailable rent sysvar.
Applied to files:
programs/compressed-token/program/src/shared/compressible_top_up.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/compressible_top_up.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/compressible_top_up.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_burn.rssdk-libs/ctoken-sdk/src/ctoken/mod.rsprograms/compressed-token/program/src/ctoken_mint_to.rssdk-libs/ctoken-sdk/src/ctoken/burn.rssdk-libs/ctoken-sdk/src/ctoken/ctoken_mint_to.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/ctoken_burn.rsprograms/compressed-token/program/src/ctoken_mint_to.rssdk-libs/ctoken-sdk/src/ctoken/ctoken_mint_to.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/ctoken_burn.rsprograms/compressed-token/program/src/ctoken_transfer.rsprograms/compressed-token/program/src/ctoken_mint_to.rssdk-libs/ctoken-sdk/src/ctoken/burn.rssdk-libs/ctoken-sdk/src/ctoken/ctoken_mint_to.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/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_burn.rssdk-libs/ctoken-sdk/src/ctoken/burn.rssdk-libs/ctoken-sdk/src/ctoken/ctoken_mint_to.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:
programs/compressed-token/program/src/ctoken_burn.rsprograms/compressed-token/program/src/ctoken_mint_to.rssdk-libs/ctoken-sdk/src/ctoken/ctoken_mint_to.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/ctoken_burn.rsprograms/compressed-token/program/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:
programs/compressed-token/program/src/ctoken_burn.rssdk-libs/ctoken-sdk/src/ctoken/mod.rssdk-libs/ctoken-sdk/src/ctoken/burn.rssdk-libs/ctoken-sdk/src/ctoken/ctoken_mint_to.rsprograms/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 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/burn.rssdk-libs/ctoken-sdk/src/ctoken/ctoken_mint_to.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: 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.rssdk-libs/ctoken-sdk/src/ctoken/burn.rssdk-libs/ctoken-sdk/src/ctoken/ctoken_mint_to.rsprograms/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/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/mod.rssdk-libs/ctoken-sdk/src/ctoken/burn.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/mod.rssdk-libs/ctoken-sdk/src/ctoken/burn.rssdk-libs/ctoken-sdk/src/ctoken/ctoken_mint_to.rsprograms/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/**/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-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-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: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-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_transfer.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/ctoken_transfer.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/lib.rs : In wrapper instruction handler in `lib.rs`, load account metadata, determine work units, call `check_forester()`, then call the processing function
Applied to files:
programs/compressed-token/program/src/ctoken_transfer.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:
programs/compressed-token/program/src/ctoken_transfer.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 `account_info_init` function to initialize account with discriminator, handling `BorrowAccountDataFailed` (20003) and `AlreadyInitialized` (20006) errors
Applied to files:
programs/compressed-token/program/src/ctoken_transfer.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/burn.rssdk-libs/ctoken-sdk/src/ctoken/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: 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/burn.rssdk-libs/ctoken-sdk/src/ctoken/ctoken_mint_to.rs
📚 Learning: 2025-11-24T17:59:13.714Z
Learnt from: CR
Repo: Lightprotocol/light-protocol PR: 0
File: program-libs/account-checks/docs/ACCOUNT_ITERATOR.md:0-0
Timestamp: 2025-11-24T17:59:13.714Z
Learning: Applies to program-libs/account-checks/docs/**/*.rs : Use specialized AccountIterator methods (`next_signer`, `next_mut`, `next_signer_mut`, etc.) instead of manually calling `next_account()` followed by separate validation functions
Applied to files:
programs/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/**/*.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
🧬 Code graph analysis (3)
programs/compressed-token/program/src/shared/compressible_top_up.rs (2)
sdk-libs/sdk/src/compressible/compression_info.rs (1)
compression_info(37-37)program-libs/ctoken-interface/src/state/ctoken/zero_copy.rs (1)
zero_copy_at_checked(473-488)
programs/compressed-token/program/src/ctoken_burn.rs (3)
programs/compressed-token/anchor/src/lib.rs (1)
burn(206-211)programs/compressed-token/program/src/shared/compressible_top_up.rs (1)
calculate_and_execute_compressible_top_ups(26-125)programs/compressed-token/program/src/lib.rs (1)
from(87-102)
sdk-libs/ctoken-sdk/src/ctoken/mod.rs (1)
programs/compressed-token/anchor/src/lib.rs (1)
burn(206-211)
⏰ 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). (11)
- GitHub Check: system-programs (anchor & pinocchio, ["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -p sdk...
- GitHub Check: programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test", "cargo test -p light-system-prog...
- GitHub Check: cli-v1
- GitHub Check: programs (account-compression-and-registry, ["cargo-test-sbf -p account-compression-test", "cargo...
- 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: programs (compressed-token-and-e2e, ["cargo test -p light-compressed-token", "cargo-test-sbf -p c...
- GitHub Check: Test program-libs-fast
- GitHub Check: Test program-libs-slow
- GitHub Check: Test batched-merkle-tree-simulate
- GitHub Check: Forester e2e test
🔇 Additional comments (5)
programs/compressed-token/program/src/ctoken_transfer.rs (1)
52-52: The boolean parameterfalseis correctly applied for CTokenTransfer operations.CTokenTransfer handles SPL-compatible transfers between already-decompressed accounts. The newly added boolean parameter to
process_transfer(part of the pinocchio_token_program API update) distinguishes between standard decompressed transfers and CMint decompression operations. Passingfalseis correct here because this instruction does not involve CMint decompression—it only performs token transfers between existing decompressed accounts, consistent with its documented purpose and the SPL transfer semantics.programs/compressed-token/program/src/shared/mod.rs (1)
2-2: LGTM!Clean module declaration that follows the existing pattern. The module provides the
calculate_and_execute_compressible_top_upsfunction for CMint/CToken top-up operations, which integrates well with the broader compressible token flows being introduced.programs/compressed-token/program/src/shared/compressible_top_up.rs (2)
26-125: Well-structured compressible top-up calculation with proper budget management.The implementation correctly:
- Lazily initializes the clock sysvar only when needed
- Uses zero-copy deserialization for efficiency
- Implements budget tracking with the
+1trick allowing exact matches- Early-exits when no transfers are required
44-45: Nice trick with the +1 budget allowance.The
saturating_add(1)on the budget allows exact matches (e.g., max_top_up=100 permits total transfers of exactly 100 lamports). This is a clean way to implement the "less than or equal" semantics without additional comparisons.programs/compressed-token/program/src/lib.rs (1)
127-134: Add test for first-byte discriminator collisions between custom and Anchor instructions.While no collision currently exists between custom discriminators 7/8 (CTokenMintTo/CTokenBurn) and the Anchor-generated instructions, the dispatch logic at line 118 uses only the first byte of instruction data to route to handlers. To prevent future regressions, add a test that verifies no Anchor instruction in the program generates a SHA256-based discriminator starting with bytes matching the custom enum values (3, 7, 8, 9, 18, 100-105). This ensures that if new Anchor instructions are added, any collision would be caught early.
| } else { | ||
| // Only Compressible extensions are implemented for ctoken accounts. | ||
| return Err(CTokenError::InvalidAccountData.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 a more specific error for future extensibility.
The current logic returns InvalidAccountData when a CToken has extended data but no extensions. While the comment explains that only Compressible extensions are currently implemented, this error could be confusing if other extension types are added later.
Consider using a more descriptive error variant that clarifies the expectation:
} else {
// Only Compressible extensions are implemented for ctoken accounts.
- return Err(CTokenError::InvalidAccountData.into());
+ return Err(CTokenError::MissingCompressibleExtension.into());
}This would require adding a new error variant, but would make debugging easier when extensions evolve.
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 103 to 106, the code returns CTokenError::InvalidAccountData for accounts
that have extended data but no known extensions; add a new, specific error
variant (e.g., CTokenError::UnsupportedExtension or
CTokenError::UnexpectedExtensions) to the error enum, update any
Display/From/translation logic, and replace the InvalidAccountData return with
the new variant so that callers get a clear, specific error when unknown or
missing extensions are encountered.
| data: { | ||
| let mut data = vec![7u8]; // CTokenMintTo discriminator | ||
| data.extend_from_slice(&self.amount.to_le_bytes()); | ||
| // Include max_top_up if set (10-byte format) |
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 "10-byte format" comment.
The comment states "10-byte format" but when max_top_up is included, the total instruction data is 11 bytes (1-byte discriminator + 8-byte amount + 2-byte max_top_up), not 10. If the comment refers to the data portion excluding the discriminator (8 + 2 = 10 bytes), this should be clarified to avoid confusion.
Consider rewording:
// Include max_top_up if set (total instruction data: 11 bytes)or
// Append max_top_up (2 bytes) if set🤖 Prompt for AI Agents
sdk-libs/ctoken-sdk/src/ctoken/ctoken_mint_to.rs around line 106: the comment
"10-byte format" is ambiguous because including max_top_up adds 2 bytes to the
8-byte amount plus the 1-byte discriminator (total 11 bytes). Update the comment
to clearly state what is being counted — for example: "Append max_top_up (2
bytes) if set (total instruction data: 11 bytes including 1-byte discriminator)"
or "Append max_top_up (2 bytes); data payload (excluding discriminator) is 10
bytes".
| //! ``` | ||
| //! | ||
| mod burn; |
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
LGTM!
The new burn and ctoken_mint_to modules are properly declared and re-exported, following the established pattern. These expand the SDK's public API to support burn operations and CMint mint-to flows.
Consider updating the module-level documentation (lines 19-29) to include the new Burn functionality alongside the existing documented operations. A brief entry like - [BurnCToken] - Burn tokens from ctoken accounts would maintain documentation completeness.
Also applies to: 74-74, 82-82, 88-88
🤖 Prompt for AI Agents
In sdk-libs/ctoken-sdk/src/ctoken/mod.rs around lines 19-29 (and similarly
update lines 68, 74, 82, and 88 where module docs/exports are declared), add a
brief module-level documentation entry for the new burn functionality — e.g.,
add a bullet like "- [`BurnCToken`] - Burn tokens from ctoken accounts" to the
documented operations list, and ensure any re-exports or pub uses referencing
the burn module are reflected in those doc sections so the public API docs
include the new Burn functionality.
6cb429a to
bd00727
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: 5
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/program/src/ctoken_transfer.rs (1)
82-83: Consider clarifying the budget overflow detection logic.The budget initialization with
saturating_add(1)at line 83 and the zero-check at line 131 work together to detect whentotal_transfers > max_top_up. The +1 offset is necessary becausesaturating_subclamps to zero, making it impossible to distinguish "exactly at limit" from "exceeded limit" without the offset.While the logic is correct, the comment at line 82 could be expanded to explain the full mechanism: "Initialize budget with +1 to distinguish exact match (budget=1) from exceeded (budget=0) when using saturating_sub."
Also applies to: 131-133
♻️ Duplicate comments (19)
sdk-libs/token-client/src/instructions/mint_to_compressed.rs (3)
42-46: Deserialization error handling already flagged.The use of
.ok()to silently swallow deserialization errors has been covered in detail in past review comments. See the existing suggestion to add logging or error propagation.
56-66: unimplemented!() panic and explicit types already flagged.Both the
unimplemented!()call (lines 62-64) that causes runtime panic and the optional explicit type annotations (lines 65-66) have been extensively covered in past review comments with suggested fixes.
77-77: Potential unwrap() panic already flagged.The use of
unwrap()inside themap()that could panic has been covered in past review comments with a suggestion to usetranspose()for proper error propagation.programs/compressed-token/program/src/ctoken_burn.rs (1)
53-55: Redundant bounds checking after length validation.These
.ok_or()checks are redundant sinceaccounts.len() >= 3is already validated at line 24. Consider using direct indexing for cleaner code.Apply this diff to use direct indexing:
- let ctoken = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?; - let cmint = accounts.get(1).ok_or(ProgramError::NotEnoughAccountKeys)?; - let payer = accounts.get(2).ok_or(ProgramError::NotEnoughAccountKeys)?; + let ctoken = &accounts[0]; + let cmint = &accounts[1]; + let payer = &accounts[2];programs/compressed-token/program/src/ctoken_mint_to.rs (1)
53-55: Redundant bounds checking after length validation.Same issue as
ctoken_burn.rs: these checks are redundant after the validation at line 24.Apply this diff:
- let cmint = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?; - let ctoken = accounts.get(1).ok_or(ProgramError::NotEnoughAccountKeys)?; - let payer = accounts.get(2).ok_or(ProgramError::NotEnoughAccountKeys)?; + let cmint = &accounts[0]; + let ctoken = &accounts[1]; + let payer = &accounts[2];programs/compressed-token/program/src/shared/compressible_top_up.rs (1)
103-106: More specific error variant could improve debuggability.When a CToken has extended data but no extensions field, returning
InvalidAccountDatamay be confusing if other extension types are added later. A past review comment suggested a more specific error variant.While this is already noted in past reviews, consider adding a specific error variant like
CTokenError::MissingCompressibleExtensionorCTokenError::UnexpectedExtensionsto make debugging easier as the extension ecosystem evolves.programs/compressed-token/program/docs/instructions/MINT_ACTION.md (1)
77-88: Documentation correctly clarifies optional account usage.The updated descriptions for
mint,token_pool_pda, andtoken_programnow accurately state these are "required for SPL mint supply synchronization" rather than just "SPL mint operations." This better reflects that these accounts are only needed when synchronizing supply between the compressed mint and an SPL mint.However, there are markdown formatting issues flagged by markdownlint (unordered list indentation at lines 78-79 and incorrect numbering at lines 81, 86). These were already noted in past review comments.
sdk-libs/token-client/src/instructions/mint_action.rs (1)
194-198: Silent deserialization failure may hide issues.The
.ok()at the end converts deserialization errors intoNone, silently swallowing any issues. While this is intentional (mint data may not exist when CMint is the source of truth), it makes debugging harder if there's a genuine deserialization problem.Consider logging deserialization errors before converting to None, or at least document this behavior clearly.
Apply this diff to log deserialization failures:
- let compressed_mint: Option<CompressedMint> = compressed_mint_account - .data - .as_ref() - .and_then(|d| BorshDeserialize::deserialize(&mut d.data.as_slice()).ok()); + let compressed_mint: Option<CompressedMint> = compressed_mint_account + .data + .as_ref() + .and_then(|d| { + BorshDeserialize::deserialize(&mut d.data.as_slice()) + .map_err(|e| { + // Log but don't fail - mint data may not exist when CMint is source of truth + eprintln!("Note: Could not deserialize compressed mint data (expected if CMint is decompressed): {:?}", e); + e + }) + .ok() + });programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs (2)
101-109: Type mismatch in comparison—use explicit conversion for consistency.
action.write_top_upisu32whileconfig.rent_config.max_top_upisu16. The cast is safe but usingu32::from()is more idiomatic.- if action.write_top_up > config.rent_config.max_top_up as u32 { + if action.write_top_up > u32::from(config.rent_config.max_top_up) {
132-147: Consider extracting the magicaccount_version: 3to a named constant.The ShaFlat version is hardcoded. A named constant would improve readability and make future version changes safer.
+// At module level: +const SHAFLAT_ACCOUNT_VERSION: u8 = 3; let compression_info = CompressionInfo { config_account_version: config.version, compress_to_pubkey: 0, // Not applicable for CMint - account_version: 3, // ShaFlat version + account_version: SHAFLAT_ACCOUNT_VERSION, lamports_per_write: action.write_top_up.into(),programs/compressed-token/program/src/mint_action/actions/compress_and_close_cmint.rs (1)
118-124: Unsafe block for account assignment needs safety justification.The
unsafeblock directly assigns the system program (zeroed pubkey) as owner. While this is a valid pattern for closing accounts, a safety comment explaining why this is safe would help reviewers.// 7. Close account (assign to system program, resize to 0) + // SAFETY: cmint is owned by this program, we've validated all preconditions, + // and we're intentionally transferring ownership to system program to close it. unsafe { cmint.assign(&[0u8; 32]); }sdk-libs/ctoken-sdk/src/ctoken/ctoken_mint_to.rs (1)
103-111: Clarify the byte format comment.The comment "10-byte format" is misleading. With discriminator (1) + amount (8) + max_top_up (2), total is 11 bytes. If referring to payload excluding discriminator, make that explicit.
- // Include max_top_up if set (10-byte format) + // Append max_top_up (2 bytes) when set; total: 1 (disc) + 8 (amount) + 2 = 11 bytes if let Some(max_top_up) = self.max_top_up { data.extend_from_slice(&max_top_up.to_le_bytes()); }program-libs/ctoken-interface/src/state/mint/compressed_mint.rs (1)
79-106: PDA derivation validation remains the caller's responsibility—ensure call sites comply.This method provides solid three-step validation (ownership → borrow → initialization), but as documented at line 78, PDA derivation verification is deferred to callers. The past review identified that
mint_action/processor.rsretrieves the CMint viavalidated_accounts.get_cmint()which validates pubkey matching but doesn't derive the PDA from seeds.Verify that the validation in
MintActionAccounts::validate_and_parse(which now receivescmint_pubkey) performs proper PDA seed derivation usingcheck_pda_seedsor equivalent.#!/bin/bash # Check if PDA validation is performed for CMint in accounts validation rg -n 'check_pda_seeds|derive.*cmint|cmint.*seeds' programs/compressed-token/program/src/mint_action/accounts.rs -B3 -A5programs/compressed-token/program/tests/mint_action.rs (1)
153-171: Parameter nameforce_spl_initializedis misleading—rename toforce_cmint_decompressed.As flagged in a previous review, this parameter now controls
mint_metadata.cmint_decompressed(line 170), but the name still references the oldspl_initializedterminology. This creates confusion for future maintainers.fn generate_random_instruction_data( rng: &mut StdRng, force_create_mint: Option<bool>, force_cpi_context: Option<bool>, - force_spl_initialized: Option<bool>, + force_cmint_decompressed: Option<bool>, action_count_range: std::ops::Range<usize>, ) -> MintActionCompressedInstructionData { // ... let mut mint_metadata = random_compressed_mint_metadata(rng); - if let Some(spl_init) = force_spl_initialized { - mint_metadata.cmint_decompressed = spl_init && create_mint.is_none(); + if let Some(cmint_decompressed) = force_cmint_decompressed { + mint_metadata.cmint_decompressed = cmint_decompressed && create_mint.is_none(); }programs/compressed-token/program/src/mint_action/mint_output.rs (2)
126-132: Defensive bounds check message could indicate this is unexpected.This bounds check should be unreachable after a successful
resize. As noted in a past review, consider updating the message to indicate this is unexpected:- msg!( - "CMint account too small: {} < {}", + msg!( + "Unexpected: CMint account too small after resize: {} < {}", cmint_data.len(), serialized.len() );
66-67: Consolidate rent exemption calculation approach.As noted in a past review, this uses
get_rent_exemption_lamports(num_bytes)while lines 106-107 useRent::get()?.minimum_balance(required_size). Both should produce the same result, but using two different APIs is error-prone.Consider using the same approach consistently:
- let rent_exemption = get_rent_exemption_lamports(num_bytes) - .map_err(|_| ErrorCode::CMintTopUpCalculationFailed)?; + let rent = Rent::get().map_err(|_| ProgramError::UnsupportedSysvar)?; + let rent_exemption = rent.minimum_balance(num_bytes as usize);programs/compressed-token/program/src/extensions/mod.rs (1)
80-90: Duplicate key validation is correct.As noted in a past review, the O(n²) duplicate check is acceptable for max 20 items. Consider adding a brief comment clarifying the comparison is on
Vec<u8>byte vectors:// Compare key byte vectors (Vec<u8>) for equality if token_metadata.additional_metadata[i].key == token_metadata.additional_metadata[j].keyprogram-libs/ctoken-interface/src/instructions/mint_action/instruction_data.rs (1)
195-224: Zero-copy to owned CompressionInfo conversion is verbose but functionally correct.This field-by-field mapping is necessary because zero-copy types can't be directly converted to owned types. A past review suggested extracting this into a
Fromimplementation in thelight_compressiblecrate to reduce duplication if this pattern appears elsewhere.Consider centralizing this conversion:
// In light_compressible crate impl From<&ZCompressionInfo<'_>> for CompressionInfo { fn from(z: &ZCompressionInfo<'_>) -> Self { /* field mapping */ } }programs/compressed-token/program/src/mint_action/accounts.rs (1)
304-321: Dead parameters_token_pool_indexand_token_pool_bumpshould be removed.As noted in past reviews, these parameters are marked with
//TODO: removeand are unused. This creates confusion and technical debt.Remove the dead parameters:
pub fn validate_accounts( &self, cmint_pubkey: Option<&solana_pubkey::Pubkey>, - _token_pool_index: u8, //TODO: remove - _token_pool_bump: u8, ) -> Result<(), ProgramError> {Then update all call sites (e.g., line 159) to remove these arguments.
| // 7. Check is_compressible (rent has expired) | ||
| #[cfg(target_os = "solana")] | ||
| let current_slot = Clock::get() | ||
| .map_err(|_| ProgramError::UnsupportedSysvar)? | ||
| .slot; | ||
| #[cfg(not(target_os = "solana"))] | ||
| let _current_slot = 1u64; | ||
|
|
||
| #[cfg(target_os = "solana")] | ||
| { | ||
| let is_compressible = compression_info | ||
| .is_compressible(cmint.data_len() as u64, current_slot, cmint.lamports()) | ||
| .map_err(|_| ProgramError::InvalidAccountData)?; | ||
|
|
||
| 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.
🧹 Nitpick | 🔵 Trivial
Step numbering in comments is inconsistent with the header.
The doc header lists 9 steps, but the inline comments jump around (7, 6, 7 again). This makes the code harder to follow. Recommend renumbering to match the actual code flow.
- // 7. Check is_compressible (rent has expired)
+ // 6. Check is_compressible (rent has expired)
#[cfg(target_os = "solana")]
let current_slot = Clock::get()
...
- // 6. Transfer all lamports to rent_sponsor
+ // 7. Transfer all lamports to rent_sponsorCommittable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
programs/compressed-token/program/src/mint_action/actions/compress_and_close_cmint.rs
lines 92-110: the inline step comment numbers are inconsistent with the function
header (they jump and duplicate), so renumber the inline comments in this block
to follow the header's sequential step order (update "7. Check is_compressible"
and any surrounding step comments to the correct step number per the header),
ensuring each step comment matches its actual position and flow.
| cmint | ||
| .resize(0) | ||
| .map_err(|e| ProgramError::Custom(u64::from(e) as 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
Error conversion for resize failure uses magic offset.
The + 6000 offset for converting pinocchio errors is a magic number. Consider defining this as a constant or using a proper error conversion utility.
+const PINOCCHIO_ERROR_OFFSET: u32 = 6000;
+
cmint
.resize(0)
- .map_err(|e| ProgramError::Custom(u64::from(e) as u32 + 6000))?;
+ .map_err(|e| ProgramError::Custom(u64::from(e) as u32 + PINOCCHIO_ERROR_OFFSET))?;🤖 Prompt for AI Agents
programs/compressed-token/program/src/mint_action/actions/compress_and_close_cmint.rs
lines 122-124: the map_err uses a magic numeric offset (+ 6000) when converting
the pinocchio error into ProgramError; replace the literal with a named constant
(e.g. PINOCCHIO_ERROR_OFFSET) or call a small error-conversion helper (e.g.
map_pinocchio_to_program_error) so the offset is declared once and
documented/centralized, and use that constant/helper in the map_err call instead
of the literal.
| // Add Compressible extension to compressed_mint | ||
| let extension = ExtensionStruct::Compressible(compression_info); | ||
| if let Some(ref mut extensions) = compressed_mint.extensions { | ||
| extensions.push(extension); | ||
| } else { | ||
| compressed_mint.extensions = Some(vec![extension]); | ||
| } |
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
Extension insertion logic is correct but could use a helper.
The pattern of checking Option<Vec> and either pushing or creating a new vec is common. Consider extracting this to a helper method on CompressedMint if this pattern repeats elsewhere.
- if let Some(ref mut extensions) = compressed_mint.extensions {
- extensions.push(extension);
- } else {
- compressed_mint.extensions = Some(vec![extension]);
- }
+ compressed_mint
+ .extensions
+ .get_or_insert_with(Vec::new)
+ .push(extension);🤖 Prompt for AI Agents
programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
lines 149-155: the code manually checks compressed_mint.extensions to push or
create a vec for the new Compressible extension; extract this pattern into a
helper method on the CompressedMint type (e.g., add_extension or push_extension)
that encapsulates: create the vec if None and push the provided ExtensionStruct
otherwise, make it take self as &mut and the extension value; then replace the
inline if/else with a call to that helper to keep call sites concise and avoid
duplication.
| // Add compressible CMint accounts if DecompressMint or CompressAndCloseCMint action is present | ||
| if has_decompress_mint || has_compress_and_close_cmint { | ||
| let (cmint_pda, _) = find_cmint_address(¶ms.mint_seed); | ||
| // Get config and rent_sponsor from v1 config PDA | ||
| let config_address = CompressibleConfig::ctoken_v1_config_pda(); | ||
| let compressible_config: CompressibleConfig = rpc | ||
| .get_anchor_account(&config_address) | ||
| .await? | ||
| .ok_or_else(|| { | ||
| RpcError::CustomError(format!( | ||
| "CompressibleConfig not found at {}", | ||
| config_address | ||
| )) | ||
| })?; | ||
| config = config.with_compressible_cmint( | ||
| cmint_pda, | ||
| config_address, | ||
| compressible_config.rent_sponsor, | ||
| ); | ||
| // DecompressMint needs mint_signer even when not creating a new mint | ||
| // (for PDA derivation of CMint account) | ||
| // CompressAndCloseCMint does NOT need mint_signer - it verifies CMint via compressed_mint.metadata.mint | ||
| if has_decompress_mint && !is_creating_mint { | ||
| config = config.with_mint_signer(params.mint_seed); | ||
| } | ||
| } |
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 CompressibleConfig implementation and methods
rg -n "impl.*CompressibleConfig|fn ctoken_v1_config_pda|fn validate_active" --type rsRepository: Lightprotocol/light-protocol
Length of output: 98
🏁 Script executed:
# Find the mint_action.rs file and examine context around the specified lines
fd "mint_action.rs" --type fRepository: Lightprotocol/light-protocol
Length of output: 229
🏁 Script executed:
# Find CONFIG_ACCOUNT.md and CLAUDE.md documentation files
fd "CONFIG_ACCOUNT.md|CLAUDE.md|ERRORS.md" --type fRepository: Lightprotocol/light-protocol
Length of output: 682
🏁 Script executed:
# Search for ctoken_v1_config_pda and CompressibleConfig methods
rg -n "ctoken_v1_config_pda|validate_active|fn derive" program-libs/compressible/src/ sdk-libs/Repository: Lightprotocol/light-protocol
Length of output: 5643
🏁 Script executed:
# Find CompressibleConfig struct definition
rg -n "pub struct CompressibleConfig|impl CompressibleConfig" program-libs/compressible/src/ sdk-libs/ -A 5Repository: Lightprotocol/light-protocol
Length of output: 2533
🏁 Script executed:
# Read the mint_action.rs file to see the full context around lines 351-376
cat -n sdk-libs/token-client/src/instructions/mint_action.rs | sed -n '340,385p'Repository: Lightprotocol/light-protocol
Length of output: 2212
🏁 Script executed:
# Read the CONFIG_ACCOUNT.md documentation
cat program-libs/compressible/docs/CONFIG_ACCOUNT.mdRepository: Lightprotocol/light-protocol
Length of output: 6152
🏁 Script executed:
# Check if validate_active is called on the compressible_config after fetching
cat -n sdk-libs/token-client/src/instructions/mint_action.rs | sed -n '351,376p'Repository: Lightprotocol/light-protocol
Length of output: 1476
🏁 Script executed:
# Read the relevant parts of CompressibleConfig implementation
cat -n program-libs/compressible/src/config.rs | sed -n '64,100p'Repository: Lightprotocol/light-protocol
Length of output: 1814
🏁 Script executed:
# Check the learning requirements and error codes
cat program-libs/compressible/docs/ERRORS.md | head -100Repository: Lightprotocol/light-protocol
Length of output: 2592
🏁 Script executed:
# Check if validate methods are called on compressible_config anywhere in the file
rg -n "validate_active|validate_not_inactive" sdk-libs/token-client/src/instructions/mint_action.rsRepository: Lightprotocol/light-protocol
Length of output: 54
🏁 Script executed:
# Check the full function signature and context
cat -n sdk-libs/token-client/src/instructions/mint_action.rs | sed -n '1,50p'Repository: Lightprotocol/light-protocol
Length of output: 2069
🏁 Script executed:
# Find all usages of CompressibleConfig in this file
rg -n "CompressibleConfig" sdk-libs/token-client/src/instructions/mint_action.rs -B 2 -A 2Repository: Lightprotocol/light-protocol
Length of output: 825
🏁 Script executed:
# Check the exact operation context - what is with_compressible_cmint doing
rg -n "with_compressible_cmint" sdk-libs/token-client/src/ -B 5 -A 5Repository: Lightprotocol/light-protocol
Length of output: 1120
🏁 Script executed:
# Check what operations are valid on CompressibleConfig and if state validation is ever needed
cat -n program-libs/compressible/src/config.rs | sed -n '150,200p'Repository: Lightprotocol/light-protocol
Length of output: 1937
🏁 Script executed:
# Look for similar patterns in other files to see if state validation is called
rg -n "compressible_config.*validate_active|validate_not_inactive.*compressible_config" --type rust -lRepository: Lightprotocol/light-protocol
Length of output: 54
Add state validation for CompressibleConfig before use.
After fetching the compressibleConfig at line 356, call compressible_config.validate_not_inactive()? before using it. The CompressAndCloseCMint operation requires the config to be in a valid (non-Inactive) state. This ensures the instruction fails gracefully if the config has been deactivated rather than silently proceeding with invalid state.
The PDA derivation via ctoken_v1_config_pda() is deterministic and the error handling is adequate.
🤖 Prompt for AI Agents
In sdk-libs/token-client/src/instructions/mint_action.rs around lines 351 to
376, after retrieving compressible_config from RPC you must validate it is not
inactive before using it; call compressible_config.validate_not_inactive()?
immediately after the get_anchor_account result and before passing
compressible_config.rent_sponsor to config.with_compressible_cmint so the
instruction fails early on an inactive config; propagate the error with ? and
keep the existing error handling for the RPC fetch as-is.
fix tests refactor: remove all spl code from ctoken program test: functional test for primitive cmint creation add compressibility update tests test: functional create cmint for existing compressed mint feat: add compress and close cmint test compress and close cmint fix lint and tests stash refactor close cmint when decompressed fix lint chore: dont require mint signer as signer for decompression refactor: anyone can compress and close a cmint rename close cmint -> compress and close cmint feat: add mint to feat: ctoken burn test: burn ctoken, feat: add burn sdk test: mint to ctoken, feat: add mint to sdk fix lint fix: ts sdk naming and tests
bd00727 to
8a4a48d
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: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
sdk-libs/ctoken-sdk/src/compressed_token/v2/create_compressed_mint/instruction.rs (1)
143-147: Minor formatting: missing space after comma.Line 146 has
compressed_mint_instruction_data,input.cpi_contextwithout a space after the comma. This should be caught by rustfmt.🔎 Apply this formatting fix
let instruction_data = light_ctoken_interface::instructions::mint_action::MintActionCompressedInstructionData::new_mint_write_to_cpi_context( input.mint_address, input.address_merkle_tree_root_index, - compressed_mint_instruction_data,input.cpi_context + compressed_mint_instruction_data, input.cpi_context );sdk-libs/token-client/src/actions/mint_action.rs (2)
167-167: Remove debugprintln!before merging.This debug statement will print to stdout on every call, which is not appropriate for a library function. Remove it or convert to proper logging if debug visibility is needed.
🔎 Apply this diff:
- println!("params {:?}", params); mint_action(rpc, params, authority, payer, mint_signer).await
106-121: Local import inside function is unconventional but functional.Moving
derive_ctoken_ataimport to line 108 inside the conditional block works, but it's unusual in Rust. Consider moving it to the top-level imports for consistency with codebase conventions.programs/compressed-token/program/tests/mint_action.rs (1)
239-269: Expected config computation updated correctly.The new fields
has_decompress_mint_actionandhas_compress_and_close_cmint_actionare computed by checking action types, andcmint_decompressedis correctly sourced frommint.metadata. TheAccountsConfigconstruction now includes all required fields.Note: The
.unwrap()at line 240 is safe here becausegenerate_random_instruction_dataalways producesSome(mint), but a more defensive approach would useunwrap_or_default()or handle theNonecase explicitly.🔎 Optional: More defensive unwrap
- let cmint_decompressed = data.mint.as_ref().unwrap().metadata.cmint_decompressed; + let cmint_decompressed = data + .mint + .as_ref() + .map(|m| m.metadata.cmint_decompressed) + .unwrap_or(false);
♻️ Duplicate comments (23)
programs/compressed-token/program/src/mint_action/actions/mint_to.rs (1)
26-29: Remove the outdated SPL Mint Synchronization documentation (duplicate issue).A previous review already flagged this documentation block as inconsistent with the implementation. While the terminology was updated from
spl_mint_initializedtocmint_decompressed, the documentation still describes SPL mint synchronization functionality that no longer exists inprocess_mint_to_compressed_action. The implementation (lines 43-68) performs only authority validation, amount calculation, supply updates, and output account creation—there is no CPI to SPL Token 2022 or token pool minting.Either remove lines 26-29 entirely, or if synchronization now occurs in other actions (e.g., DecompressMint or CompressAndCloseCMint), replace this section with a brief note pointing to the correct location.
sdk-libs/token-client/src/instructions/mint_to_compressed.rs (4)
42-46: Deserialization error handling already flagged.This segment silently swallows deserialization errors by using
.ok(), which has already been identified in previous review comments. The concern is that malformed data will be masked rather than surfaced.
56-64:unimplemented!()panic already flagged; questionable default at line 60.The
unimplemented!()call that causes runtime panics has already been identified in previous reviews.However, there's a separate logic concern: at line 60,
unwrap_or(true)defaults to treating CMint as "source of truth" when deserialization fails or data is missing. This seems backwards—if there's no readable CMint data, you cannot meaningfully determine whether it's decompressed. This fallback may lead to the unimplemented path being triggered incorrectly when the account is malformed rather than intentionally decompressed.Consider returning an error if
compressed_mintisNone, or clarify the intended semantics with explicit error handling.
65-66: Explicit type annotations already flagged as optional.Previous review comments have already noted that these explicit type annotations are likely unnecessary and could be simplified.
77-77: Panic risk fromunwrap()already identified.The potential panic from calling
unwrap()ontry_into()has already been flagged in previous review comments, with suggestions to propagate the error using.transpose().sdk-libs/ctoken-sdk/src/ctoken/mod.rs (1)
68-68: LGTM! New burn and ctoken_mint_to modules properly integrated.The module declarations and re-exports follow the established pattern and correctly expose the new burn and CMint mint-to functionality. As noted in the previous review, consider updating the module-level documentation (lines 19-29) to include the new burn operations for completeness.
Also applies to: 74-74, 82-82, 88-88
programs/compressed-token/program/docs/instructions/MINT_ACTION.md (1)
78-88: Documentation wording improved, but markdown formatting issues remain.The updated descriptions ("optional, required for SPL mint supply synchronization") are clearer than referencing the field name. However, the markdown formatting violations flagged in the previous review and by static analysis remain unaddressed:
- Lines 78-79: Unordered list items indented 3 spaces instead of 0
- Line 81: Ordered list prefix should be
1.not5.- Line 86: Ordered list prefix should be
2.not6.Please apply the fix from the previous review to correct the list formatting.
programs/compressed-token/program/src/ctoken_burn.rs (1)
20-58: Solid burn instruction implementation.The function correctly:
- Validates account and instruction data requirements
- Parses
max_top_upfrom instruction data (8 bytes legacy, 10 bytes with limit)- Delegates to pinocchio's burn processor for balance/authority validation
- Executes compressible top-ups for both CMint and CToken accounts
The redundant bounds checking at lines 53-55 was already noted in a previous review.
programs/compressed-token/program/src/mint_action/actions/process_actions.rs (1)
141-160: DecompressMint action handler looks solid.The handler correctly:
- Retrieves
mint_signerwith a specific error code- Retrieves
fee_payerfrom executing accounts with a descriptive message- Passes all required parameters to the processor
One observation: the fee_payer extraction pattern (lines 145-152) is duplicated at lines 197-204 for the compressible token account top-ups. A previous review already suggested extracting this into a helper - worth considering if you're doing further cleanup.
programs/compressed-token/program/src/mint_action/actions/compress_and_close_cmint.rs (2)
92-111: Compressibility check logic is correct but step comments are inconsistent.The
is_compressiblemethod returnsSome(deficit)when the account IS compressible (rent has expired), andNonewhen it's NOT compressible (still has sufficient rent). So checkingis_none()to return an error is correct.However, the inline step comment says "7. Check is_compressible" but according to the doc header this should be step 5. A previous review already noted this numbering inconsistency.
119-125: Account closure uses unsafe block appropriately.The
unsafeblock forassignis necessary for low-level account manipulation with pinocchio. A previous review suggested adding a safety comment here. The magic number6000in the error offset was also flagged - consider defining it as a constant for clarity.programs/compressed-token/program/src/ctoken_mint_to.rs (1)
51-58: Redundant bounds checks after the early guard.Since line 24 already verified
accounts.len() < 3, theok_orcalls on lines 53-55 will never fail. Direct indexing would be cleaner.Simplify account access:
// Calculate and execute top-ups for both CMint and CToken // mint_to account order: [cmint, ctoken, authority] - let cmint = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?; - let ctoken = accounts.get(1).ok_or(ProgramError::NotEnoughAccountKeys)?; - let payer = accounts.get(2).ok_or(ProgramError::NotEnoughAccountKeys)?; + let cmint = &accounts[0]; + let ctoken = &accounts[1]; + let payer = &accounts[2]; calculate_and_execute_compressible_top_ups(cmint, ctoken, payer, max_top_up)programs/compressed-token/program/src/shared/compressible_top_up.rs (1)
105-108: Error specificity concern previously raised.The current
InvalidAccountDataerror when extended data exists but no recognized extensions is generic. A past review suggested using a more descriptive error variant likeMissingCompressibleExtensionfor clearer debugging as extensions evolve.programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs (2)
132-147: Magic number foraccount_versionpreviously flagged.The hardcoded
account_version: 3(ShaFlat) was noted in a previous review. While functional, a named constant would improve readability and maintainability as versions evolve.
154-158: Extension insertion pattern previously flagged for simplification.A past review suggested using
get_or_insert_with(Vec::new).push(extension)instead of the explicit if/else pattern. This is a minor refactor opportunity.program-libs/ctoken-interface/src/state/mint/compressed_mint.rs (1)
70-106: Documentation clarifies caller responsibility for PDA validation—ensure callers comply.The method explicitly documents that "Validation is done via owner check + PDA derivation (caller responsibility)" on line 78. This is a reasonable design choice since the method doesn't have access to the PDA seeds. However, based on the past review comment, the actual call site in
mint_action/processor.rsmay not be performing this validation.This is a duplicate of the past review concern. Ensure the processor validates PDA derivation using
check_pda_seedsor equivalent before calling this method.sdk-libs/ctoken-sdk/src/ctoken/ctoken_mint_to.rs (1)
94-113: Verify account order matches program processor.The instruction uses discriminator
7u8(matchingCTokenMintTo = 7in lib.rs) and account order[cmint, destination, authority]. According to the relevant snippet fromctoken_mint_to.rsprocessor (lines 51-53), the program expects:cmint = accounts[0],ctoken = accounts[1],payer = accounts[2]. The naming differs (destinationvsctoken,authorityvspayer), but positions match.Note: The comment at line 106 about "10-byte format" is slightly ambiguous (as noted in past review). The total instruction data is 11 bytes (1 discriminator + 8 amount + 2 max_top_up), not 10.
programs/compressed-token/program/tests/mint_action.rs (1)
168-171: Parameter name mismatch persists.The parameter
force_spl_initializedcontrolsmint_metadata.cmint_decompressed, which is semantically confusing. This was flagged in a previous review.sdk-libs/token-client/src/instructions/mint_action.rs (2)
194-198: Silent error swallowing may hide legitimate deserialization issues.When CMint is source of truth,
mintdata may be absent, so returningNoneis correct. However, this also swallows genuine deserialization failures. Consider logging atwarn!level when deserialization fails but data exists, to aid debugging without breaking the flow.
351-376: Add state validation for CompressibleConfig after fetching.After retrieving
compressible_configat line 356, you should validate its state. Per coding guidelines,CompressAndCloseCMintrequiresvalidate_not_inactive()(accepts ACTIVE or DEPRECATED), whileDecompressMinttypically requiresvalidate_active().🔎 Suggested fix:
let compressible_config: CompressibleConfig = rpc .get_anchor_account(&config_address) .await? .ok_or_else(|| { RpcError::CustomError(format!( "CompressibleConfig not found at {}", config_address )) })?; + + // Validate config state based on action type + if has_decompress_mint { + compressible_config.validate_active().map_err(|_| { + RpcError::CustomError("CompressibleConfig must be ACTIVE for DecompressMint".to_string()) + })?; + } else if has_compress_and_close_cmint { + compressible_config.validate_not_inactive().map_err(|_| { + RpcError::CustomError("CompressibleConfig must not be INACTIVE for CompressAndCloseCMint".to_string()) + })?; + }program-libs/ctoken-interface/src/instructions/mint_action/instruction_data.rs (1)
195-225: Verbose but correct zero-copy to owned conversion for Compressible extension.The field-by-field mapping is necessary because zero-copy types don't automatically convert to owned. Note that
compression_only: falseis hardcoded—this is correct for CMint since it supports both decompression and compression.Consider extracting this to
impl From<&ZCompressionInfo<'_>> for CompressionInfoin thelight_compressiblecrate to reduce duplication if this pattern appears elsewhere.programs/compressed-token/program/src/mint_action/mint_output.rs (1)
66-67: Two different rent exemption calculation methods still present.Line 66 uses
get_rent_exemption_lamports(num_bytes)while lines 107-108 useRent::get()?.minimum_balance(required_size). These should produce the same result, but using two different APIs for the same concept is error-prone if implementations ever diverge.Also applies to: 107-108
programs/compressed-token/program/src/mint_action/accounts.rs (1)
306-308: TODO parameters still present - consider cleanup.The
_token_pool_indexand_token_pool_bumpparameters are marked with//TODO: removebut remain in the signature. While past reviews flagged this, I see it's been acknowledged but not yet removed.Since this is a feature branch adding significant new functionality, consider removing these in a follow-up to avoid technical debt accumulation.
| // Call pinocchio mint_to - handles supply/balance updates, authority check, frozen check | ||
| process_mint_to(accounts, &instruction_data[..8]) | ||
| .map_err(|e| ProgramError::Custom(u64::from(e) as 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
Pinocchio error conversion needs attention.
The conversion u64::from(e) as u32 could silently truncate if pinocchio ever returns an error code > u32::MAX. While unlikely in practice, a safer approach would be explicit handling.
However, this pattern appears to be used consistently across the codebase (e.g., ctoken_burn.rs), so this is more of a codebase-wide concern than something to address here.
Consider using saturating conversion for safety:
- process_mint_to(accounts, &instruction_data[..8])
- .map_err(|e| ProgramError::Custom(u64::from(e) as u32))?;
+ process_mint_to(accounts, &instruction_data[..8])
+ .map_err(|e| ProgramError::Custom(u64::from(e).min(u32::MAX as u64) as u32))?;🤖 Prompt for AI Agents
In programs/compressed-token/program/src/ctoken_mint_to.rs around lines 47 to
49, the current conversion map_err(|e| ProgramError::Custom(u64::from(e) as
u32)) can silently truncate Pinocchio error codes > u32::MAX; change the mapping
to handle overflow explicitly — convert the Pinocchio error to u64, then either
saturate to u32::MAX on overflow or perform a checked conversion and map any
overflow to a defined fallback ProgramError::Custom value (e.g., u32::MAX) so no
silent truncation occurs; apply the same pattern consistently across similar
files.
| if !should_include_in_compressed_output(extension) { | ||
| continue; | ||
| } | ||
| match extension { | ||
| ZExtensionInstructionData::TokenMetadata(token_metadata_data) => { | ||
| ExtensionStruct::TokenMetadata(token_metadata) => { | ||
| process_token_metadata_config_with_actions( | ||
| &mut additional_mint_data_len, | ||
| &mut config_vec, | ||
| token_metadata_data, | ||
| token_metadata, | ||
| actions, | ||
| extension_index, | ||
| )? | ||
| } | ||
| _ => return Err(CTokenError::UnsupportedExtension), | ||
| } |
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
Unreachable error branch after filtering.
After the should_include_in_compressed_output check at line 38 filters out non-TokenMetadata extensions, the _ => return Err(CTokenError::UnsupportedExtension) at line 51 should never execute for filtered extensions.
However, this is actually correct defensive programming because:
- If a new extension is added that returns
truefromshould_include_in_compressed_output - But isn't handled in the match, it will properly error
Consider adding a comment to clarify this is intentional:
match extension {
ExtensionStruct::TokenMetadata(token_metadata) => {
// ... processing
}
+ // Defensive: error on any extension that passes the filter but isn't handled
_ => return Err(CTokenError::UnsupportedExtension),
}🤖 Prompt for AI Agents
programs/compressed-token/program/src/extensions/mod.rs around lines 38 to 52:
the match contains a fallback arm returning
Err(CTokenError::UnsupportedExtension) which appears unreachable due to the
prior should_include_in_compressed_output filter but is intentional defensive
coding; add a short comment above the `_ =>` arm (or above the match) stating
that the fallback is deliberate to catch any future extensions that might pass
the filter but lack explicit handling, so reviewers understand it's intentional
and not dead code.
| use ctoken_mint_to::process_ctoken_mint_to; | ||
| use ctoken_transfer::process_ctoken_transfer; | ||
| use withdraw_funding_pool::process_withdraw_funding_pool; | ||
|
|
||
| use crate::{ | ||
| convert_account_infos::convert_account_infos, mint_action::processor::process_mint_action, | ||
| convert_account_infos::convert_account_infos, ctoken_burn::process_ctoken_burn, | ||
| mint_action::processor::process_mint_action, | ||
| }; |
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
Imports are correctly structured.
The processor functions are imported from their respective modules. Note the slight asymmetry: process_ctoken_mint_to is imported directly at line 30, while process_ctoken_burn is imported via the crate path at line 35. This works but could be more consistent.
🔎 Optional: Consistent import style
use create_token_account::process_create_token_account;
use ctoken_mint_to::process_ctoken_mint_to;
+use ctoken_burn::process_ctoken_burn;
use ctoken_transfer::process_ctoken_transfer;
use withdraw_funding_pool::process_withdraw_funding_pool;
use crate::{
- convert_account_infos::convert_account_infos, ctoken_burn::process_ctoken_burn,
- mint_action::processor::process_mint_action,
+ convert_account_infos::convert_account_infos,
+ mint_action::processor::process_mint_action,
};Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In programs/compressed-token/program/src/lib.rs around lines 30 to 37, the
imports mix direct module imports (process_ctoken_mint_to) with crate-path
imports (process_ctoken_burn via crate::...), so make them consistent: either
import all processor functions via crate::... (e.g.,
crate::ctoken_mint_to::process_ctoken_mint_to) or import all directly from their
modules; update the import lines accordingly so every processor uses the same
import style and remove the asymmetry.
| #[cfg(target_os = "solana")] | ||
| let current_slot = Clock::get() | ||
| .map_err(|_| ProgramError::UnsupportedSysvar)? | ||
| .slot; | ||
| #[cfg(not(target_os = "solana"))] | ||
| let current_slot = 1u64; |
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
Hardcoded test slot may cause unexpected behavior.
The #[cfg(not(target_os = "solana"))] fallback uses current_slot = 1u64, which could cause last_claimed_slot in CompressionInfo to be unrealistically low in tests. This might lead to tests passing but production failing if slot-dependent logic behaves differently at slot 1 versus realistic slot numbers.
Consider using a more realistic default or exposing this as a test parameter.
🔎 Suggested approach
#[cfg(not(target_os = "solana"))]
- let current_slot = 1u64;
+ let current_slot = 100_000_000u64; // Realistic slot for testingOr better, inject via a test configuration mechanism.
🤖 Prompt for AI Agents
In programs/compressed-token/program/src/mint_action/actions/decompress_mint.rs
around lines 122 to 127, the non-Solana fallback sets current_slot = 1u64 which
is unrealistically low for tests; replace the hardcoded 1u64 with a
configurable/test-injectable value (for example read from a test-only env var, a
test config module, or call a helper function like get_test_current_slot() that
returns a realistic default such as 1_000_000u64) so tests can control the slot
or use a realistic default and avoid mismatches with production slot-dependent
logic.
| // When CMint was source of truth (input state BEFORE actions), use zero sentinel values | ||
| // Use cmint_decompressed directly, not cmint_is_source_of_truth(), because: | ||
| // - cmint_is_source_of_truth() tells us the OUTPUT state (after actions) | ||
| // - cmint_decompressed tells us the INPUT state (before actions) | ||
| // For CompressAndCloseCMint: input has zero values (was decompressed), output has real data | ||
| let (discriminator, input_data_hash) = if accounts_config.cmint_decompressed { | ||
| ([0u8; 8], [0u8; 32]) | ||
| } else { | ||
| // Data from instruction - compute hash | ||
| let mint_data = mint_instruction_data | ||
| .mint | ||
| .as_ref() | ||
| .ok_or(ProgramError::InvalidInstructionData)?; | ||
| let compressed_mint = CompressedMint::try_from(mint_data)?; | ||
| let bytes = compressed_mint | ||
| .try_to_vec() | ||
| .map_err(|e| ProgramError::BorshIoError(e.to_string()))?; | ||
| ( | ||
| COMPRESSED_MINT_DISCRIMINATOR, | ||
| Sha256BE::hash(bytes.as_slice())?, | ||
| ) | ||
| }; |
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
Well-documented conditional logic for CMint source-of-truth handling.
The comments at lines 27-31 clearly explain the distinction between cmint_decompressed (INPUT state) vs cmint_is_source_of_truth() (OUTPUT state). This is exactly the kind of documentation that prevents future confusion. The zero sentinel pattern for decompressed mints is consistent with the broader design.
One minor observation: The ok_or(ProgramError::InvalidInstructionData)? at line 39 could benefit from a msg!() log before returning, to aid debugging when mint data is unexpectedly missing.
🔎 Optional: Add debug logging for missing mint data
let (discriminator, input_data_hash) = if accounts_config.cmint_decompressed {
([0u8; 8], [0u8; 32])
} else {
// Data from instruction - compute hash
let mint_data = mint_instruction_data
.mint
.as_ref()
- .ok_or(ProgramError::InvalidInstructionData)?;
+ .ok_or_else(|| {
+ msg!("Mint data required when cmint not decompressed");
+ ProgramError::InvalidInstructionData
+ })?;🤖 Prompt for AI Agents
programs/compressed-token/program/src/mint_action/mint_input.rs around lines 27
to 48: the branch that extracts mint_instruction_data.mint uses
ok_or(ProgramError::InvalidInstructionData)? without logging, so add a debug
message before returning when mint data is missing; replace the direct ok_or
call with an explicit match or if-let that calls msg!("missing mint instruction
data for Compress/Close CMint") (or similar concise context) and then returns
Err(ProgramError::InvalidInstructionData) to aid debugging.
| let num_bytes = cmint_account.data_len() as u64; | ||
| let current_lamports = cmint_account.lamports(); | ||
| let rent_exemption = get_rent_exemption_lamports(num_bytes) | ||
| .map_err(|_| ErrorCode::CMintTopUpCalculationFailed)?; | ||
|
|
||
| // Calculate top-up amount | ||
| let top_up = compression_info | ||
| .info | ||
| .calculate_top_up_lamports( | ||
| num_bytes, | ||
| current_slot, | ||
| current_lamports, | ||
| rent_exemption, | ||
| ) | ||
| .map_err(|_| ErrorCode::CMintTopUpCalculationFailed)?; |
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
Top-up calculation could silently fail on edge cases.
The calculate_top_up_lamports call at lines 70-78 maps errors to CMintTopUpCalculationFailed, but the calculation depends on compression_info.info having valid state. If last_claimed_slot is far in the past or num_bytes changes significantly, the calculation could overflow or produce unexpected results.
Consider adding validation or logging before the calculation to help diagnose issues:
+ // Validate state before calculation
+ if compression_info.info.last_claimed_slot > current_slot {
+ msg!("Invalid last_claimed_slot: {} > current_slot {}",
+ compression_info.info.last_claimed_slot, current_slot);
+ return Err(ErrorCode::CMintTopUpCalculationFailed.into());
+ }
+
// Calculate top-up amount
let top_up = compression_info
.info
.calculate_top_up_lamports(🤖 Prompt for AI Agents
In programs/compressed-token/program/src/mint_action/mint_output.rs around lines
64 to 78, the call to compression_info.info.calculate_top_up_lamports is being
mapped to a generic CMintTopUpCalculationFailed and can silently fail for
invalid state (stale last_claimed_slot), large changes in num_bytes, or
arithmetic overflow; before invoking calculate_top_up_lamports validate relevant
inputs (ensure compression_info.info is initialized and last_claimed_slot is
within a sensible range, num_bytes is within expected bounds, and
current_lamports >= 0), use checked arithmetic or bounds checks to prevent
overflow, and replace the blanket map_err with a more specific error variant (or
attach context) so failures are distinguishable; also emit a debug log (or
include values in the returned error) of num_bytes, current_lamports,
rent_exemption, and last_claimed_slot when validation fails to aid diagnosis.
| let num_bytes = cmint_account.data_len() as u64; | ||
| let current_lamports = cmint_account.lamports(); | ||
| let rent_exemption = get_rent_exemption_lamports(num_bytes) | ||
| .map_err(|_| ErrorCode::CMintTopUpCalculationFailed)?; | ||
|
|
||
| // Calculate top-up amount | ||
| let top_up = compression_info | ||
| .info | ||
| .calculate_top_up_lamports( | ||
| num_bytes, | ||
| current_slot, | ||
| current_lamports, | ||
| rent_exemption, | ||
| ) | ||
| .map_err(|_| ErrorCode::CMintTopUpCalculationFailed)?; | ||
|
|
||
| if top_up > 0 { | ||
| let fee_payer = validated_accounts | ||
| .executing | ||
| .as_ref() | ||
| .map(|exec| exec.system.fee_payer) | ||
| .ok_or(ProgramError::NotEnoughAccountKeys)?; | ||
| transfer_lamports(top_up, fee_payer, cmint_account) | ||
| .map_err(convert_program_error)?; | ||
| } | ||
|
|
||
| // Update last_claimed_slot to current slot | ||
| compression_info.info.last_claimed_slot = current_slot; | ||
| } | ||
| } | ||
|
|
||
| let serialized = compressed_mint | ||
| .try_to_vec() | ||
| .map_err(|_| ErrorCode::MintActionOutputSerializationFailed)?; | ||
| let required_size = serialized.len(); | ||
|
|
||
| // Resize if needed (e.g., metadata extensions added) | ||
| if cmint_account.data_len() < required_size { | ||
| cmint_account | ||
| .resize(required_size) | ||
| .map_err(|_| ErrorCode::CMintResizeFailed)?; |
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.
Top-up calculation uses pre-resize size, potentially under-funding the account.
The top-up calculation at lines 64-78 uses cmint_account.data_len() (the current size) to calculate num_bytes. However, if the account is resized at line 102-104 due to metadata extensions being added, the compression rent calculation was based on the smaller pre-resize size.
This means the compression_info.info will have its last_claimed_slot updated to current_slot, but the top-up amount was calculated for the old size. Future rent claims will expect rent for the new, larger size.
Consider calculating the required size first and using that for the top-up calculation:
+ let serialized = compressed_mint
+ .try_to_vec()
+ .map_err(|_| ErrorCode::MintActionOutputSerializationFailed)?;
+ let required_size = serialized.len();
+
// Check if CMint has Compressible extension and handle top-up
if let Some(ref mut extensions) = compressed_mint.extensions {
if let Some(ExtensionStruct::Compressible(ref mut compression_info)) = extensions
.iter_mut()
.find(|e| matches!(e, ExtensionStruct::Compressible(_)))
{
// Get current slot for top-up calculation
let current_slot = Clock::get()
.map_err(|_| ProgramError::UnsupportedSysvar)?
.slot;
- let num_bytes = cmint_account.data_len() as u64;
+ let num_bytes = required_size.max(cmint_account.data_len()) as u64;Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In programs/compressed-token/program/src/mint_action/mint_output.rs around lines
64 to 104, the top-up calculation uses the pre-resize cmint_account.data_len(),
so if the account will be resized later the rent/top-up is under-calculated; fix
by determining the required serialized size first (serialize compressed_mint to
get required_size) and then use required_size (or max(current_len,
required_size)) when calling get_rent_exemption_lamports and calculate_top_up,
perform the resize before transferring lamports (or calculate top_up against the
final size and then transfer), and only then update last_claimed_slot to
current_slot.
| if top_up > 0 { | ||
| let fee_payer = validated_accounts | ||
| .executing | ||
| .as_ref() | ||
| .map(|exec| exec.system.fee_payer) | ||
| .ok_or(ProgramError::NotEnoughAccountKeys)?; | ||
| transfer_lamports(top_up, fee_payer, cmint_account) | ||
| .map_err(convert_program_error)?; | ||
| } |
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 fee_payer retrieval to reduce duplication.
The fee_payer extraction pattern is duplicated at lines 81-85 and 110-114:
let fee_payer = validated_accounts
.executing
.as_ref()
.map(|exec| exec.system.fee_payer)
.ok_or(ProgramError::NotEnoughAccountKeys)?;Since this function may transfer lamports in two places (top-up and resize), consider extracting early:
Suggested refactor
+ // Extract fee_payer once at the start of the CMint sync block
+ let fee_payer = validated_accounts
+ .executing
+ .as_ref()
+ .map(|exec| exec.system.fee_payer)
+ .ok_or(ProgramError::NotEnoughAccountKeys)?;
+
if let Some(cmint_account) = validated_accounts.get_cmint() {
if !accounts_config.has_compress_and_close_cmint_action {
// ... use fee_payer directly in both transfer callsAlso applies to: 110-121
| impl<'info> BurnCTokenCpi<'info> { | ||
| pub fn instruction(&self) -> Result<Instruction, ProgramError> { | ||
| BurnCToken::from(self).instruction() | ||
| } | ||
|
|
||
| pub fn invoke(self) -> Result<(), ProgramError> { | ||
| let instruction = BurnCToken::from(&self).instruction()?; | ||
| let account_infos = [self.source, self.cmint, self.authority]; | ||
| invoke(&instruction, &account_infos) |
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 borrowing self instead of consuming it in invoke().
Line 70 creates BurnCToken::from(&self) but line 69 declares invoke(self) consuming ownership. The conversion only needs a reference. If a caller wanted to inspect BurnCTokenCpi after a failed invoke, they couldn't. This is a minor ergonomic concern—the current pattern works but differs from typical CPI helpers that borrow.
🔎 Suggested change (optional):
- pub fn invoke(self) -> Result<(), ProgramError> {
- let instruction = BurnCToken::from(&self).instruction()?;
- let account_infos = [self.source, self.cmint, self.authority];
+ pub fn invoke(&self) -> Result<(), ProgramError> {
+ let instruction = BurnCToken::from(self).instruction()?;
+ let account_infos = [self.source.clone(), self.cmint.clone(), self.authority.clone()];
invoke(&instruction, &account_infos)
}🤖 Prompt for AI Agents
In sdk-libs/ctoken-sdk/src/ctoken/burn.rs around lines 64 to 72, change the
method to borrow self rather than consume it: update the signature from pub fn
invoke(self) -> Result<(), ProgramError> to pub fn invoke(&self) -> Result<(),
ProgramError>, keep creating the instruction from a reference
(BurnCToken::from(&self).instruction()?) and build the account_infos array using
the fields without moving ownership, then call invoke(&instruction,
&account_infos). This preserves ownership of the BurnCTokenCpi on failure and
matches typical CPI helper ergonomics.
| // Add CompressAndCloseCMint action if requested | ||
| if compress_and_close_cmint { | ||
| actions.push(MintActionType::CompressAndCloseCMint { idempotent: 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
Consider exposing idempotent as a parameter.
The CompressAndCloseCMint action is hardcoded with idempotent: false. If there are use cases where callers want idempotent behavior (e.g., retry-safe operations), they'd need a different code path. Consider whether this should be configurable.
🔎 Suggested change (if idempotency control is needed):
// Whether to compress and close the CMint Solana account
- compress_and_close_cmint: bool,
+ compress_and_close_cmint: Option<bool>, // Some(idempotent) to enable, None to skip
...
// Add CompressAndCloseCMint action if requested
- if compress_and_close_cmint {
- actions.push(MintActionType::CompressAndCloseCMint { idempotent: false });
+ if let Some(idempotent) = compress_and_close_cmint {
+ actions.push(MintActionType::CompressAndCloseCMint { idempotent });
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In sdk-libs/token-client/src/actions/mint_action.rs around lines 147 to 150, the
CompressAndCloseCMint action is created with idempotent hardcoded to false; make
idempotency configurable by adding an idempotent boolean parameter (or field on
the options/args struct) to the function/struct that builds actions, defaulting
to false to preserve current behavior, thread that parameter through to where
actions.push is called so you pass MintActionType::CompressAndCloseCMint {
idempotent }, and update any callers, tests and docs to accept/forward the new
parameter.
Summary by CodeRabbit
New Features
Improvements
Changes
✏️ Tip: You can customize this high-level summary in your review settings.