-
Notifications
You must be signed in to change notification settings - Fork 85
refactor: compressed token sdk mint action builder #2063
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
refactor: compressed token sdk mint action builder #2063
Conversation
…ionData Add fluent builder pattern to MintActionCompressedInstructionData following the light-compressed-account pattern. Changes: - Add builder.rs with InstructionDiscriminator and LightInstructionData traits - Add two constructors: new() for existing mints, new_mint() for creating new mints - Add 8 with_* builder methods for actions (mint_to_compressed, mint_to_ctoken, update authorities, metadata operations) - Add CPI context methods (with_cpi_context, write_to_cpi_context_first/set) - Use CompressedMintWithContext to bundle merkle context parameters - new() sets create_mint to None (for existing mints only) - new_mint() requires proof and automatically sets create_mint, leaf_index=0, prove_by_index=false - All builder methods use #[must_use] attribute for safety This is part 1 of refactoring compressed token SDK to use trait-based builders.
- Create general CTokenInstruction trait for all compressed token instructions - Replace instruction-specific InvokeMintActionInstruction with CTokenInstruction - Use LightInstructionData::data() for serialization with discriminator - Add associated types for ExecuteAccounts and CpiWriteAccounts - MintActionCompressedInstructionData now implements CTokenInstruction This provides a consistent pattern that other compressed token instructions (transfer, burn, etc.) can follow.
WalkthroughRefactors mint-action flows into a builder-and-trait pattern, adds CPI-write / CPI-context support and meta-config types, introduces CTokenInstruction and TokenPool, expands account-meta configuration, adds pubkey helpers, updates Cargo features, and removes legacy create_spl_mint helpers and client paths. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as SDK / Client
participant Builder as MintActionCompressedInstructionData (builder)
participant Meta as MintActionMetaConfig / CpiWriteConfig
participant Serializer as LightInstructionData
participant Program as CompressedTokenProgram
Client->>Builder: new(...) / new_mint(...) and chain with_*() calls
Builder->>Meta: build MintActionMetaConfig (new / new_create_mint / new_cpi_context)
Meta->>Meta: analyze_actions() → account meta list
Builder->>Serializer: serialize via LightInstructionData
Serializer->>Program: assemble Instruction(program_id, accounts, data)
Program-->>Client: Instruction ready
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Areas to focus review effort:
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro 📒 Files selected for processing (3)
🧰 Additional context used🧬 Code graph analysis (1)sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs (4)
⏰ 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). (12)
🔇 Additional comments (3)
Comment |
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs
Outdated
Show resolved
Hide resolved
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs
Outdated
Show resolved
Hide resolved
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs
Outdated
Show resolved
Hide resolved
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: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
sdk-libs/token-client/src/actions/mint_action.rs (1)
137-146: Prefer structured logging overprintln!in library codeUsing
println!("params {:?}", params);inmint_action_comprehensivewill emit unstructured logs for every call, with no way for downstream users to control verbosity or target. That’s fine for local debugging but undesirable in an SDK.Consider switching to your standard logging/tracing macro (e.g.
tracing::debug!/log::debug!) and, if needed, gating behind a feature flag so this trace can be enabled without impacting production noise:- println!("params {:?}", params); + tracing::debug!(?params, "mint_action params");(or equivalent in whatever logging stack you use).
sdk-libs/token-client/src/instructions/mint_action.rs (1)
124-131: Avoidunwrap()when deserializing the compressed mint account
compressed_mint_account.data.unwrap()will panic if the RPC response contains an account without data (or the field is unexpectedlyNone). This is a hard crash instead of a controlledRpcError.Consider mapping the missing data into an error instead of panicking:
- let compressed_mint: CompressedMint = BorshDeserialize::deserialize( - &mut compressed_mint_account.data.unwrap().data.as_slice(), - ) + let account_data = compressed_mint_account.data.ok_or_else(|| { + RpcError::CustomError( + format!("Compressed mint account {:?} has no data", params.compressed_mint_address), + ) + })?; + + let compressed_mint: CompressedMint = + BorshDeserialize::deserialize(&mut account_data.data.as_slice())This keeps the client resilient to unexpected or malformed RPC responses.
-->
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (9)
program-tests/utils/src/assert_mint_action.rsis excluded by none and included by nonesdk-tests/sdk-token-test/src/ctoken_pda/mint.rsis excluded by none and included by nonesdk-tests/sdk-token-test/src/mint_compressed_tokens_cpi_write.rsis excluded by none and included by nonesdk-tests/sdk-token-test/src/pda_ctoken/mint.rsis excluded by none and included by nonesdk-tests/sdk-token-test/src/pda_ctoken/processor.rsis excluded by none and included by nonesdk-tests/sdk-token-test/tests/compress_and_close_cpi.rsis excluded by none and included by nonesdk-tests/sdk-token-test/tests/ctoken_pda.rsis excluded by none and included by nonesdk-tests/sdk-token-test/tests/decompress_full_cpi.rsis excluded by none and included by nonesdk-tests/sdk-token-test/tests/pda_ctoken.rsis excluded by none and included by none
📒 Files selected for processing (21)
program-libs/account-checks/Cargo.toml(1 hunks)program-libs/ctoken-types/src/instructions/mint_action/builder.rs(1 hunks)program-libs/ctoken-types/src/instructions/mint_action/mod.rs(1 hunks)programs/compressed-token/program/Cargo.toml(1 hunks)sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs(1 hunks)sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs(5 hunks)sdk-libs/compressed-token-sdk/src/instructions/create_spl_mint.rs(0 hunks)sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs(2 hunks)sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs(1 hunks)sdk-libs/compressed-token-sdk/src/instructions/mint_action/mod.rs(1 hunks)sdk-libs/compressed-token-sdk/src/instructions/mint_to_compressed/instruction.rs(4 hunks)sdk-libs/compressed-token-sdk/src/instructions/mod.rs(1 hunks)sdk-libs/compressed-token-sdk/src/instructions/update_compressed_mint/instruction.rs(4 hunks)sdk-libs/compressed-token-sdk/src/lib.rs(2 hunks)sdk-libs/compressed-token-sdk/src/token_pool.rs(1 hunks)sdk-libs/token-client/src/actions/create_spl_mint.rs(0 hunks)sdk-libs/token-client/src/actions/mint_action.rs(1 hunks)sdk-libs/token-client/src/actions/mod.rs(0 hunks)sdk-libs/token-client/src/instructions/create_spl_mint.rs(0 hunks)sdk-libs/token-client/src/instructions/mint_action.rs(4 hunks)sdk-libs/token-client/src/instructions/mod.rs(0 hunks)
💤 Files with no reviewable changes (5)
- sdk-libs/token-client/src/instructions/create_spl_mint.rs
- sdk-libs/compressed-token-sdk/src/instructions/create_spl_mint.rs
- sdk-libs/token-client/src/instructions/mod.rs
- sdk-libs/token-client/src/actions/mod.rs
- sdk-libs/token-client/src/actions/create_spl_mint.rs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-14T00:36:53.191Z
Learnt from: ananas-block
Repo: Lightprotocol/light-protocol PR: 1909
File: program-libs/zero-copy/src/init_mut.rs:241-249
Timestamp: 2025-08-14T00:36:53.191Z
Learning: In the light-protocol zero-copy crate, performance is prioritized over safety checks for edge cases like Vec lengths exceeding u32::MAX, even when there might be wire format inconsistencies.
Applied to files:
programs/compressed-token/program/Cargo.toml
🧬 Code graph analysis (9)
sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (3)
instruction(22-45)instruction_write_to_cpi_context_first(47-63)instruction_write_to_cpi_context_set(65-81)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (3)
sdk-libs/token-client/src/actions/mint_action.rs (1)
mint_action(25-60)sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (3)
instruction(31-34)instruction_write_to_cpi_context_first(46-49)instruction_write_to_cpi_context_set(60-63)program-libs/ctoken-types/src/instructions/mint_action/cpi_context.rs (1)
default(25-37)
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs (3)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (4)
get_mint_action_instruction_account_metas(247-385)get_mint_action_instruction_account_metas_cpi_write(399-438)new_cpi_context(160-199)new_cpi_create_mint(120-137)program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1)
new_mint(88-107)program-libs/compressed-account/src/pubkey.rs (1)
new_from_array(79-81)
sdk-libs/compressed-token-sdk/src/instructions/update_compressed_mint/instruction.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (4)
get_mint_action_instruction_account_metas(247-385)get_mint_action_instruction_account_metas_cpi_write(399-438)new(73-117)new_cpi(140-157)
sdk-libs/compressed-token-sdk/src/instructions/mint_to_compressed/instruction.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (4)
get_mint_action_instruction_account_metas(247-385)new(73-117)new_cpi_context(160-199)new_cpi(140-157)
sdk-libs/compressed-token-sdk/src/instructions/mod.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (2)
get_mint_action_instruction_account_metas(247-385)get_mint_action_instruction_account_metas_cpi_write(399-438)
sdk-libs/token-client/src/instructions/mint_action.rs (4)
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs (2)
derive_compressed_mint_address(205-214)find_spl_mint_address(224-229)sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (3)
get_mint_action_instruction_account_metas(247-385)new(73-117)new_create_mint(32-70)sdk-libs/token-client/src/actions/mint_action.rs (1)
mint_action(25-60)sdk-libs/program-test/src/logging/formatter.rs (1)
format(249-288)
program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1)
sdk-libs/token-client/src/actions/mint_action.rs (1)
mint_action(25-60)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (1)
sdk-libs/token-client/src/actions/mint_action.rs (1)
mint_action(25-60)
🪛 GitHub Actions: lint
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs
[warning] 1-1: Diff in formatting: added line breaks and wrapping in use statements.
[warning] 1-1: Diff in get_mint_action_instruction_account_metas import: reordered imports.
[warning] 1-1: Diff in program_id construction: wrapping Pubkey::new_from_array call across lines.
sdk-libs/compressed-token-sdk/src/instructions/update_compressed_mint/instruction.rs
[warning] 1-1: Diff in import blocks and builder pattern formatting within MintActionCompressedInstructionData initialization.
sdk-libs/compressed-token-sdk/src/instructions/mint_to_compressed/instruction.rs
[warning] 1-1: Diff in import: pub use vs use ordering for MintAction-related items.
🪛 GitHub Check: lint
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs
[warning] 1-1:
Diff in /home/runner/work/light-protocol/light-protocol/sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs
[warning] 116-116:
Diff in /home/runner/work/light-protocol/light-protocol/sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs
[warning] 106-106:
Diff in /home/runner/work/light-protocol/light-protocol/sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs
[warning] 191-191:
Diff in /home/runner/work/light-protocol/light-protocol/sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs
[warning] 184-184:
Diff in /home/runner/work/light-protocol/light-protocol/sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs
[warning] 95-95:
Diff in /home/runner/work/light-protocol/light-protocol/sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs
⏰ 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). (5)
- GitHub Check: Forester e2e test
- GitHub Check: Test program-libs-slow
- GitHub Check: Test batched-merkle-tree-simulate
- GitHub Check: Test program-libs-fast
- GitHub Check: stateless-js-v2
🔇 Additional comments (8)
program-libs/account-checks/Cargo.toml (1)
18-18: Consolidate std requirement within solana feature — idiomatic and sound.Making
"std"an explicit dependency of the"solana"feature is the right call here. This ensures that whenever solana-related code paths are enabled, the standard library is available, which aligns with the broader builder and account-meta enhancements described in the PR summary.The pattern mirrors the existing
"test-only"feature (line 22), maintaining consistency across the crate's feature design. Since feature flags compose idempotently, this poses no compatibility risk for users who already enable"std"in their defaults.programs/compressed-token/program/Cargo.toml (1)
75-79: Rationale for feature flag differences between dependencies.The choice to include "msg" in the main dependency (line 55) but not in dev-dependencies (lines 75-79) appears intentional—production code needs messaging support for CPI flows, while tests use the "test-only" feature. However, please confirm this design is deliberate and document if needed.
program-libs/ctoken-types/src/instructions/mint_action/mod.rs (1)
16-16: Let me verify thatMintActionCompressedInstructionDatais publicly exported:
The builder pattern is properly exposed through public methods on
MintActionCompressedInstructionData. The module doesn't need explicitpub usere-exports.The builder API is accessible to consumers through the public impl methods:
MintActionCompressedInstructionData::new(),new_mint(),with_mint_to_compressed(), etc. The builder module is an internal organizational detail—its visibility doesn't affect API accessibility. This follows standard Rust patterns: keep module structure private and expose only the public API surface (the methods).Likely an incorrect or invalid review comment.
program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1)
88-107:new_mintdefaultingcreate_mintand requiring a proof matches the documented constraintsThe
new_mintconstructor correctly enforces new‑mint semantics:
leaf_index = 0andprove_by_index = false, so it can’t be (mis)used for existing mints.proofis required and stored asSome(proof), matching the “REQUIRED for new mint” constraint.create_mint: Some(CreateMint::default())clearly distinguishes this path fromnew().This wiring looks consistent with the comments and separation between new vs. existing mints.
sdk-libs/compressed-token-sdk/src/lib.rs (1)
4-22: Re-export ofCTokenInstructionandTokenPoolaligns the public SDK surfaceAdding
pub mod ctoken_instruction;pluspub use ctoken_instruction::CTokenInstruction;andpub use token_pool::TokenPool;makes the key traits/types available from the crate root, which is exactly what downstream users expect for core SDK concepts.This looks like the right level of exposure for the new instruction abstraction and token pool type.
sdk-libs/compressed-token-sdk/src/token_pool.rs (1)
5-13:TokenPoolstruct and derives look appropriate for PDA helpers and serializationDefining
TokenPool { pubkey, bump, index }with#[derive(Debug, Clone, AnchorDeserialize, AnchorSerialize, PartialEq)]matches how the PDA helpers at the bottom of the file operate (they already compute(pubkey, bump, index)).This gives callers a simple, serializable handle to pool metadata, and the reuse of the crate-level
AnchorDeserialize/AnchorSerializealiases keeps it consistent with the rest of the SDK’s serialization story.sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (1)
1-64: CTokenInstruction trait cleanly separates execute vs CPI-write instruction constructionThe trait design is solid:
- Associated
ExecuteAccounts<'info, A>/CpiWriteAccounts<'info, A>let each instruction define its own typed account layouts while staying generic over theAccountInfoTraitimplementation.- The three methods consume
selfto build anInstruction, which fits a builder-style API and prevents accidental reuse of mutated instruction data.- Having dedicated
instruction_write_to_cpi_context_first/setmethods makes CPI batching semantics explicit at the type level instead of burying flags in opaque data.This provides a clear, reusable contract for other compressed-token instructions to implement.
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (1)
15-45: CTokenInstruction impl wiring looks soundThe
CTokenInstructionimplementation forMintActionCompressedInstructionDataaligns well with the new model:
- Correctly gates
instruction()against CPI-write modes by inspectingcpi_context.- Uses
LightInstructionData::data()for serialization.- Delegates account meta generation to
MintActionCpiAccounts::to_account_metas(false)and anchorsprogram_idtoCOMPRESSED_TOKEN_PROGRAM_ID.This keeps the instruction-building surface consistent with other instructions in the SDK and centralizes CPI concerns into the dedicated write helpers.
-->
program-libs/ctoken-types/src/instructions/mint_action/builder.rs
Outdated
Show resolved
Hide resolved
program-libs/ctoken-types/src/instructions/mint_action/builder.rs
Outdated
Show resolved
Hide resolved
program-libs/ctoken-types/src/instructions/mint_action/builder.rs
Outdated
Show resolved
Hide resolved
| &instruction_data, | ||
| input.mint_authority, | ||
| input.payer, | ||
| cpi_context_pubkey.unwrap(), | ||
| )? | ||
| } else { | ||
| // Regular CPI mode | ||
| MintActionMetaConfig::new_cpi_create_mint( | ||
| &instruction_data, | ||
| input.mint_authority, | ||
| input.payer, | ||
| input.mint_signer, | ||
| input.address_tree_pubkey, | ||
| input.output_queue, | ||
| )? | ||
| }; | ||
|
|
||
| create_mint_action_cpi( |
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.
Set CPI-context meta config fields before serializing accounts.
When cpi_context_pubkey is present we call MintActionMetaConfig::new_cpi_context(...) and immediately collect account metas. That constructor intentionally leaves tree_pubkey, output_queue, and mint_signer as placeholders that must be filled via the chainable setters (see the comments in MintActionMetaConfig::new_cpi_context). Because we never populate them, get_mint_action_instruction_account_metas emits the default Pubkey::default() for the state tree/output queue and skips the mint signer entirely, so the on-chain processor will reject this instruction. Please set these fields before deriving the metas (e.g. chain .with_mint_signer(input.mint_signer).with_tree_pubkey(input.address_tree_pubkey).with_output_queue(input.output_queue) or an equivalent sequence).
🤖 Prompt for AI Agents
In
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs
around lines 88 to 105, the MintActionMetaConfig created via
new_cpi_context(...) leaves tree_pubkey, output_queue, and mint_signer as
placeholders; before collecting account metas you must populate those fields
using the chainable setters (for example call
.with_mint_signer(input.mint_signer).with_tree_pubkey(input.address_tree_pubkey).with_output_queue(input.output_queue)
or an equivalent ordering) so the serialized account metas contain the real
Pubkeys and the mint signer is included.
| /// Helper to analyze actions and extract info | ||
| fn analyze_actions( | ||
| actions: &[light_ctoken_types::instructions::mint_action::Action], | ||
| ) -> (bool, Vec<Pubkey>) { | ||
| let mut has_mint_to_actions = false; | ||
| let ctoken_accounts = Vec::new(); | ||
|
|
||
| for action in actions { | ||
| match action { | ||
| light_ctoken_types::instructions::mint_action::Action::MintToCompressed(_) => { | ||
| has_mint_to_actions = true; | ||
| } | ||
| light_ctoken_types::instructions::mint_action::Action::MintToCToken(_) => { | ||
| // MintToCToken also requires tokens_out_queue (matches on-chain logic) | ||
| has_mint_to_actions = true; | ||
| // Extract account from action - but we can't because it's an index | ||
| // So ctoken_accounts must be provided separately by user | ||
| } | ||
| _ => {} | ||
| } | ||
| } | ||
|
|
||
| (has_mint_to_actions, ctoken_accounts) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Document the expectation to supply ctoken_accounts explicitly
analyze_actions intentionally returns an empty ctoken_accounts vector even when MintToCToken actions are present:
fn analyze_actions(
actions: &[light_ctoken_types::instructions::mint_action::Action],
) -> (bool, Vec<Pubkey>) {
let mut has_mint_to_actions = false;
let ctoken_accounts = Vec::new();
for action in actions {
match action {
Action::MintToCompressed(_) => has_mint_to_actions = true,
Action::MintToCToken(_) => {
has_mint_to_actions = true;
// ... cannot extract accounts because action uses indices
}
_ => {}
}
}
(has_mint_to_actions, ctoken_accounts)
}and with_ctoken_accounts is the only API that actually populates ctoken_accounts:
pub fn with_ctoken_accounts(mut self, accounts: Vec<Pubkey>) -> Self {
self.ctoken_accounts = accounts;
self
}This is correct given that on-chain actions only store indices, but it’s easy for callers to assume ctoken_accounts is derived automatically from actions.
I’d suggest:
- Making it explicit in the
with_ctoken_accountsdoc comment (and/or in theMintActionMetaConfigstruct comment) that callers must supply decompressed token accounts wheneverMintToCTokenactions are present, otherwiseget_mint_action_instruction_account_metaswill emit no extra accounts and the on-chain program will fail. - Optionally asserting (in higher-level helpers) that when
has_mint_to_actionsand anyMintToCTokenare present,ctoken_accountsis non-empty before constructing metas.
This will save users from subtle runtime failures when wiring MintToCToken flows.
-->
Also applies to: 213-217
🤖 Prompt for AI Agents
In sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs
around lines 213-242, callers can mistakenly expect ctoken_accounts to be
derived from Action::MintToCToken but analyze_actions intentionally returns an
empty vector; update the library by adding an explicit doc comment to
with_ctoken_accounts (and/or the MintActionMetaConfig struct) stating that
callers MUST supply decompressed token Pubkeys whenever MintToCToken actions are
present, and add a runtime check before building account metas that if
has_mint_to_actions and any MintToCToken actions exist then ctoken_accounts is
non-empty (return an Err or panic with a clear message) so we fail fast instead
of producing invalid account metas.
| /// Helper function for building CPI write instructions | ||
| #[inline(always)] | ||
| fn build_cpi_write_instruction<A: light_account_checks::AccountInfoTrait + Clone>( | ||
| instruction_data: MintActionCompressedInstructionData, | ||
| accounts: &MintActionCpiWriteAccounts<A>, | ||
| ) -> Result<Instruction> { | ||
| // Convert high-level actions to program-level actions | ||
| let mut program_actions = Vec::new(); | ||
| let create_mint = if input.create_mint { | ||
| Some(CreateMint::default()) | ||
| } else { | ||
| None | ||
| }; | ||
|
|
||
| // Check for lamports, decompressed status, and mint actions before moving | ||
| let with_lamports = false; | ||
| let spl_mint_initialized = input | ||
| .actions | ||
| .iter() | ||
| .any(|action| matches!(action, MintActionType::CreateSplMint { .. })) | ||
| || input | ||
| .compressed_mint_inputs | ||
| .mint | ||
| .metadata | ||
| .spl_mint_initialized; | ||
| let has_mint_to_actions = input.actions.iter().any(|action| { | ||
| matches!( | ||
| action, | ||
| MintActionType::MintTo { .. } | MintActionType::MintToCToken { .. } | ||
| ) | ||
| }); | ||
| // Match onchain logic: with_mint_signer = create_mint() | has_CreateSplMint_action | ||
| let with_mint_signer = create_mint.is_some() | ||
| || input | ||
| .actions | ||
| .iter() | ||
| .any(|action| matches!(action, MintActionType::CreateSplMint { .. })); | ||
|
|
||
| // Only require mint to sign when creating a new compressed mint | ||
| let mint_needs_to_sign = create_mint.is_some(); | ||
|
|
||
| // Collect decompressed accounts for account index mapping | ||
| let mut decompressed_accounts: Vec<Pubkey> = Vec::new(); | ||
| let mut decompressed_account_index = 0u8; | ||
|
|
||
| for action in input.actions { | ||
| // Check that we don't have actions that require proof or multiple accounts | ||
| // CPI write mode is limited to simple operations | ||
| for action in &instruction_data.actions { | ||
| match action { | ||
| MintActionType::CreateSplMint { mint_bump: bump } => { | ||
| program_actions.push(Action::CreateSplMint(CreateSplMintAction { | ||
| mint_bump: bump, | ||
| })); | ||
| } | ||
| MintActionType::MintTo { | ||
| recipients, | ||
| token_account_version, | ||
| } => { | ||
| // TODO: cleanup once lamports are removed. | ||
| let program_recipients: Vec<_> = recipients | ||
| .into_iter() | ||
| .map(|r| Recipient { | ||
| recipient: r.recipient.to_bytes().into(), | ||
| amount: r.amount, | ||
| }) | ||
| .collect(); | ||
|
|
||
| program_actions.push(Action::MintToCompressed(MintToCompressedAction { | ||
| token_account_version, | ||
| recipients: program_recipients, | ||
| })); | ||
| } | ||
| MintActionType::UpdateMintAuthority { new_authority } => { | ||
| program_actions.push(Action::UpdateMintAuthority(UpdateAuthority { | ||
| new_authority: new_authority.map(|auth| auth.to_bytes().into()), | ||
| })); | ||
| } | ||
| MintActionType::UpdateFreezeAuthority { new_authority } => { | ||
| program_actions.push(Action::UpdateFreezeAuthority(UpdateAuthority { | ||
| new_authority: new_authority.map(|auth| auth.to_bytes().into()), | ||
| })); | ||
| } | ||
| MintActionType::MintToCToken { account, amount } => { | ||
| use light_ctoken_types::instructions::mint_action::MintToCTokenAction; | ||
|
|
||
| // Add account to decompressed accounts list and get its index | ||
| decompressed_accounts.push(account); | ||
| let current_index = decompressed_account_index; | ||
| decompressed_account_index += 1; | ||
|
|
||
| program_actions.push(Action::MintToCToken(MintToCTokenAction { | ||
| account_index: current_index, | ||
| amount, | ||
| })); | ||
| } | ||
| MintActionType::UpdateMetadataField { | ||
| extension_index, | ||
| field_type, | ||
| key, | ||
| value, | ||
| } => { | ||
| program_actions.push(Action::UpdateMetadataField(UpdateMetadataFieldAction { | ||
| extension_index, | ||
| field_type, | ||
| key, | ||
| value, | ||
| })); | ||
| } | ||
| MintActionType::UpdateMetadataAuthority { | ||
| extension_index, | ||
| new_authority, | ||
| } => { | ||
| program_actions.push(Action::UpdateMetadataAuthority( | ||
| UpdateMetadataAuthorityAction { | ||
| extension_index, | ||
| new_authority: new_authority.to_bytes().into(), | ||
| }, | ||
| )); | ||
| Action::CreateSplMint(_) => { | ||
| msg!("CreateSplMint not supported in CPI write mode"); | ||
| return Err(TokenSdkError::CannotMintWithDecompressedInCpiWrite); | ||
| } | ||
| MintActionType::RemoveMetadataKey { | ||
| extension_index, | ||
| key, | ||
| idempotent, | ||
| } => { | ||
| program_actions.push(Action::RemoveMetadataKey(RemoveMetadataKeyAction { | ||
| extension_index, | ||
| key, | ||
| idempotent, | ||
| })); | ||
| Action::MintToCToken(_) => { | ||
| // MintToCToken is allowed but needs recipient accounts | ||
| } | ||
| _ => {} // Other actions are OK | ||
| } | ||
| } | ||
|
|
||
| // Create account meta config first (before moving compressed_mint_inputs) | ||
| let meta_config = MintActionMetaConfig { | ||
| fee_payer: Some(input.payer), | ||
| mint_signer: if with_mint_signer { | ||
| Some(input.mint_seed) | ||
| } else { | ||
| None | ||
| }, | ||
| authority: input.authority, | ||
| tree_pubkey: input.address_tree_pubkey, | ||
| input_queue: input.input_queue, | ||
| output_queue: input.output_queue, | ||
| tokens_out_queue: input.tokens_out_queue, | ||
| with_lamports, | ||
| spl_mint_initialized, | ||
| has_mint_to_actions, | ||
| with_cpi_context: cpi_context_pubkey, | ||
| create_mint: create_mint.is_some(), | ||
| with_mint_signer, | ||
| mint_needs_to_sign, | ||
| ctoken_accounts: decompressed_accounts, | ||
| }; | ||
|
|
||
| // Get account metas (before moving compressed_mint_inputs) | ||
| let accounts = | ||
| get_mint_action_instruction_account_metas(meta_config, &input.compressed_mint_inputs); | ||
| msg!("account metas {:?}", accounts); | ||
| let instruction_data = MintActionCompressedInstructionData { | ||
| create_mint, | ||
| leaf_index: input.compressed_mint_inputs.leaf_index, | ||
| prove_by_index: input.compressed_mint_inputs.prove_by_index, | ||
| root_index: input.compressed_mint_inputs.root_index, | ||
| compressed_address: input.compressed_mint_inputs.address, | ||
| mint: input.compressed_mint_inputs.mint, | ||
| token_pool_bump: input.token_pool.as_ref().map_or(0, |tp| tp.bump), | ||
| token_pool_index: input.token_pool.as_ref().map_or(0, |tp| tp.index), | ||
| actions: program_actions, | ||
| proof: input.proof, | ||
| cpi_context, | ||
| }; | ||
|
|
||
| // Serialize instruction data | ||
| let data_vec = instruction_data | ||
| .try_to_vec() | ||
| // Serialize instruction data with discriminator using LightInstructionData trait | ||
| let data = instruction_data | ||
| .data() | ||
| .map_err(|_| TokenSdkError::SerializationError)?; | ||
|
|
||
| // Build instruction | ||
| Ok(Instruction { | ||
| program_id: Pubkey::new_from_array(light_ctoken_types::COMPRESSED_TOKEN_PROGRAM_ID), | ||
| accounts, | ||
| data: [vec![MINT_ACTION_DISCRIMINATOR], data_vec].concat(), | ||
| }) | ||
| } | ||
|
|
||
| /// Creates a mint action instruction without CPI context | ||
| pub fn create_mint_action(input: MintActionInputs) -> Result<Instruction> { | ||
| create_mint_action_cpi(input, None, None) | ||
| } | ||
|
|
||
| /// Input struct for creating a mint action CPI write instruction | ||
| #[derive(Debug, Clone, AnchorDeserialize, AnchorSerialize, PartialEq)] | ||
| pub struct MintActionInputsCpiWrite { | ||
| pub compressed_mint_inputs: | ||
| light_ctoken_types::instructions::mint_action::CompressedMintWithContext, | ||
| pub mint_seed: Option<Pubkey>, // Optional - only when creating mint and when creating SPL mint | ||
| pub mint_bump: Option<u8>, // Bump seed for creating SPL mint | ||
| pub create_mint: bool, // Whether we're creating a new mint | ||
| pub authority: Pubkey, | ||
| pub payer: Pubkey, | ||
| pub actions: Vec<MintActionType>, | ||
| //pub input_queue: Option<Pubkey>, // Input queue for existing compressed mint operations | ||
| pub cpi_context: light_ctoken_types::instructions::mint_action::CpiContext, | ||
| pub cpi_context_pubkey: Pubkey, | ||
| } | ||
|
|
||
| /// Input parameters for creating a new mint in CPI write mode | ||
| #[derive(Debug, Clone)] | ||
| pub struct CreateMintCpiWriteInputs { | ||
| pub compressed_mint_inputs: | ||
| light_ctoken_types::instructions::mint_action::CompressedMintWithContext, | ||
| pub mint_seed: Pubkey, | ||
| pub authority: Pubkey, | ||
| pub payer: Pubkey, | ||
| pub cpi_context_pubkey: Pubkey, | ||
| pub first_set_context: bool, | ||
| pub address_tree_index: u8, | ||
| pub output_queue_index: u8, | ||
| pub assigned_account_index: u8, | ||
| } | ||
|
|
||
| /// Input parameters for working with an existing mint in CPI write mode | ||
| #[derive(Debug, Clone)] | ||
| pub struct WithMintCpiWriteInputs { | ||
| pub compressed_mint_inputs: | ||
| light_ctoken_types::instructions::mint_action::CompressedMintWithContext, | ||
| pub authority: Pubkey, | ||
| pub payer: Pubkey, | ||
| pub cpi_context_pubkey: Pubkey, | ||
| pub first_set_context: bool, | ||
| pub state_tree_index: u8, | ||
| pub input_queue_index: u8, | ||
| pub output_queue_index: u8, | ||
| pub assigned_account_index: u8, | ||
| } | ||
|
|
||
| impl MintActionInputsCpiWrite { | ||
| /// Create a new compressed mint for CPI write (starting point for new mints) | ||
| pub fn new_create_mint(inputs: CreateMintCpiWriteInputs) -> Self { | ||
| let cpi_context = light_ctoken_types::instructions::mint_action::CpiContext { | ||
| set_context: false, // For CPI write, we're reading from context | ||
| first_set_context: inputs.first_set_context, | ||
| in_tree_index: inputs.address_tree_index, // For create_mint, this is the address tree | ||
| in_queue_index: 0, // Not used for create_mint | ||
| out_queue_index: inputs.output_queue_index, | ||
| token_out_queue_index: 0, // Set when adding MintTo action | ||
| assigned_account_index: inputs.assigned_account_index, | ||
| read_only_address_trees: [0; 4], | ||
| address_tree_pubkey: light_ctoken_types::CMINT_ADDRESS_TREE, | ||
| }; | ||
|
|
||
| Self { | ||
| compressed_mint_inputs: inputs.compressed_mint_inputs, | ||
| mint_seed: Some(inputs.mint_seed), | ||
| mint_bump: None, | ||
| create_mint: true, | ||
| authority: inputs.authority, | ||
| payer: inputs.payer, | ||
| actions: Vec::new(), | ||
| cpi_context, | ||
| cpi_context_pubkey: inputs.cpi_context_pubkey, | ||
| } | ||
| } | ||
|
|
||
| /// Start with an existing mint for CPI write (starting point for existing mints) | ||
| pub fn new_with_mint(inputs: WithMintCpiWriteInputs) -> Self { | ||
| let cpi_context = light_ctoken_types::instructions::mint_action::CpiContext { | ||
| set_context: false, // For CPI write, we're reading from context | ||
| first_set_context: inputs.first_set_context, | ||
| in_tree_index: inputs.state_tree_index, | ||
| in_queue_index: inputs.input_queue_index, | ||
| out_queue_index: inputs.output_queue_index, | ||
| token_out_queue_index: 0, // Set when adding MintTo action | ||
| assigned_account_index: inputs.assigned_account_index, | ||
| ..Default::default() | ||
| }; | ||
|
|
||
| Self { | ||
| compressed_mint_inputs: inputs.compressed_mint_inputs, | ||
| mint_seed: None, | ||
| mint_bump: None, | ||
| create_mint: false, | ||
| authority: inputs.authority, | ||
| payer: inputs.payer, | ||
| actions: Vec::new(), | ||
| cpi_context, | ||
| cpi_context_pubkey: inputs.cpi_context_pubkey, | ||
| } | ||
| } | ||
|
|
||
| /// Add MintTo action - mint tokens to compressed token accounts | ||
| /// Returns error if mint is decompressed (no SPL mint modifications in CPI write) | ||
| pub fn add_mint_to( | ||
| mut self, | ||
| recipients: Vec<MintToRecipient>, | ||
| token_account_version: u8, | ||
| token_out_queue_index: u8, // Index for token output queue | ||
| ) -> Result<Self> { | ||
| // Cannot mint if the mint is decompressed | ||
| // In CPI write, we cannot modify SPL mint supply | ||
| if self | ||
| .compressed_mint_inputs | ||
| .mint | ||
| .metadata | ||
| .spl_mint_initialized | ||
| { | ||
| return Err(TokenSdkError::CannotMintWithDecompressedInCpiWrite); | ||
| } | ||
|
|
||
| // Set token_out_queue_index for the MintTo action | ||
| self.cpi_context.token_out_queue_index = token_out_queue_index; | ||
|
|
||
| self.actions.push(MintActionType::MintTo { | ||
| recipients, | ||
| token_account_version, | ||
| }); | ||
| Ok(self) | ||
| } | ||
|
|
||
| /// Add UpdateMintAuthority action | ||
| pub fn add_update_mint_authority(mut self, new_authority: Option<Pubkey>) -> Self { | ||
| self.actions | ||
| .push(MintActionType::UpdateMintAuthority { new_authority }); | ||
| self | ||
| } | ||
|
|
||
| /// Add UpdateFreezeAuthority action | ||
| pub fn add_update_freeze_authority(mut self, new_authority: Option<Pubkey>) -> Self { | ||
| self.actions | ||
| .push(MintActionType::UpdateFreezeAuthority { new_authority }); | ||
| self | ||
| } | ||
|
|
||
| /// Add UpdateMetadataField action | ||
| pub fn add_update_metadata_field( | ||
| mut self, | ||
| extension_index: u8, | ||
| field_type: u8, | ||
| key: Vec<u8>, | ||
| value: Vec<u8>, | ||
| ) -> Self { | ||
| self.actions.push(MintActionType::UpdateMetadataField { | ||
| extension_index, | ||
| field_type, | ||
| key, | ||
| value, | ||
| }); | ||
| self | ||
| } | ||
|
|
||
| /// Add UpdateMetadataAuthority action | ||
| pub fn add_update_metadata_authority( | ||
| mut self, | ||
| extension_index: u8, | ||
| new_authority: Pubkey, | ||
| ) -> Self { | ||
| self.actions.push(MintActionType::UpdateMetadataAuthority { | ||
| extension_index, | ||
| new_authority, | ||
| }); | ||
| self | ||
| } | ||
|
|
||
| /// Add RemoveMetadataKey action | ||
| pub fn add_remove_metadata_key( | ||
| mut self, | ||
| extension_index: u8, | ||
| key: Vec<u8>, | ||
| idempotent: u8, | ||
| ) -> Self { | ||
| self.actions.push(MintActionType::RemoveMetadataKey { | ||
| extension_index, | ||
| key, | ||
| idempotent, | ||
| }); | ||
| self | ||
| } | ||
| } | ||
|
|
||
| /// Creates a mint action CPI write instruction (for use in CPI context) | ||
| pub fn mint_action_cpi_write(input: MintActionInputsCpiWrite) -> Result<Instruction> { | ||
| use light_ctoken_types::instructions::mint_action::MintActionCompressedInstructionData; | ||
| if input | ||
| .compressed_mint_inputs | ||
| .mint | ||
| .metadata | ||
| .spl_mint_initialized | ||
| || input | ||
| .actions | ||
| .iter() | ||
| .any(|action| matches!(action, MintActionType::CreateSplMint { .. })) | ||
| { | ||
| return Err(TokenSdkError::CannotMintWithDecompressedInCpiWrite); | ||
| } | ||
| // Validate CPI context | ||
| if !input.cpi_context.first_set_context && !input.cpi_context.set_context { | ||
| return Err(TokenSdkError::InvalidAccountData); | ||
| } | ||
|
|
||
| // Convert high-level actions to program-level actions | ||
| let mut program_actions = Vec::new(); | ||
| let create_mint = if input.create_mint { | ||
| Some(CreateMint::default()) | ||
| } else { | ||
| None | ||
| }; | ||
|
|
||
| let with_mint_signer = create_mint.is_some(); | ||
|
|
||
| // Only require mint to sign when creating a new compressed mint | ||
| let mint_needs_to_sign = create_mint.is_some(); | ||
|
|
||
| for action in input.actions { | ||
| match action { | ||
| MintActionType::MintTo { | ||
| recipients, | ||
| token_account_version, | ||
| } => { | ||
| let program_recipients: Vec<_> = recipients | ||
| .into_iter() | ||
| .map( | ||
| |r| light_ctoken_types::instructions::mint_action::Recipient { | ||
| recipient: r.recipient.to_bytes().into(), | ||
| amount: r.amount, | ||
| }, | ||
| ) | ||
| .collect(); | ||
|
|
||
| program_actions.push( | ||
| light_ctoken_types::instructions::mint_action::Action::MintToCompressed( | ||
| light_ctoken_types::instructions::mint_action::MintToCompressedAction { | ||
| token_account_version, | ||
| recipients: program_recipients, | ||
| }, | ||
| ), | ||
| ); | ||
| } | ||
| MintActionType::UpdateMintAuthority { new_authority } => { | ||
| program_actions.push( | ||
| light_ctoken_types::instructions::mint_action::Action::UpdateMintAuthority( | ||
| light_ctoken_types::instructions::mint_action::UpdateAuthority { | ||
| new_authority: new_authority.map(|auth| auth.to_bytes().into()), | ||
| }, | ||
| ), | ||
| ); | ||
| } | ||
| MintActionType::UpdateFreezeAuthority { new_authority } => { | ||
| program_actions.push( | ||
| light_ctoken_types::instructions::mint_action::Action::UpdateFreezeAuthority( | ||
| light_ctoken_types::instructions::mint_action::UpdateAuthority { | ||
| new_authority: new_authority.map(|auth| auth.to_bytes().into()), | ||
| }, | ||
| ), | ||
| ); | ||
| } | ||
| MintActionType::UpdateMetadataField { | ||
| extension_index, | ||
| field_type, | ||
| key, | ||
| value, | ||
| } => { | ||
| program_actions.push( | ||
| light_ctoken_types::instructions::mint_action::Action::UpdateMetadataField( | ||
| UpdateMetadataFieldAction { | ||
| extension_index, | ||
| field_type, | ||
| key, | ||
| value, | ||
| }, | ||
| ), | ||
| ); | ||
| program_id: COMPRESSED_TOKEN_PROGRAM_ID.into(), | ||
| accounts: { | ||
| let mut account_metas = Vec::with_capacity( | ||
| 6 + accounts.recipient_token_accounts.len() | ||
| + if accounts.mint_signer.is_some() { 1 } else { 0 }, | ||
| ); | ||
|
|
||
| account_metas.push(solana_instruction::AccountMeta { | ||
| pubkey: accounts.light_system_program.key().into(), | ||
| is_writable: false, | ||
| is_signer: false, | ||
| }); | ||
|
|
||
| if let Some(mint_signer) = accounts.mint_signer { | ||
| account_metas.push(solana_instruction::AccountMeta { | ||
| pubkey: mint_signer.key().into(), | ||
| is_writable: false, | ||
| is_signer: true, | ||
| }); | ||
| } | ||
| MintActionType::UpdateMetadataAuthority { | ||
| extension_index, | ||
| new_authority, | ||
| } => { | ||
| program_actions.push( | ||
| light_ctoken_types::instructions::mint_action::Action::UpdateMetadataAuthority( | ||
| UpdateMetadataAuthorityAction { | ||
| extension_index, | ||
| new_authority: new_authority.to_bytes().into(), | ||
| }, | ||
| ), | ||
| ); | ||
| } | ||
| MintActionType::RemoveMetadataKey { | ||
| extension_index, | ||
| key, | ||
| idempotent, | ||
| } => { | ||
| program_actions.push( | ||
| light_ctoken_types::instructions::mint_action::Action::RemoveMetadataKey( | ||
| RemoveMetadataKeyAction { | ||
| extension_index, | ||
| key, | ||
| idempotent, | ||
| }, | ||
| ), | ||
| ); | ||
| } | ||
| _ => return Err(TokenSdkError::CannotMintWithDecompressedInCpiWrite), | ||
| } | ||
| } | ||
|
|
||
| let instruction_data = MintActionCompressedInstructionData { | ||
| create_mint, | ||
| leaf_index: input.compressed_mint_inputs.leaf_index, | ||
| prove_by_index: input.compressed_mint_inputs.prove_by_index, | ||
| root_index: input.compressed_mint_inputs.root_index, | ||
| compressed_address: input.compressed_mint_inputs.address, | ||
| mint: input.compressed_mint_inputs.mint, | ||
| token_pool_bump: 0, // Not used in CPI write context | ||
| token_pool_index: 0, // Not used in CPI write context | ||
| actions: program_actions, | ||
| proof: None, // No proof for CPI write context | ||
| cpi_context: Some(input.cpi_context), | ||
| }; | ||
| account_metas.push(solana_instruction::AccountMeta { | ||
| pubkey: accounts.authority.key().into(), | ||
| is_writable: false, | ||
| is_signer: true, | ||
| }); | ||
|
|
||
| account_metas.push(solana_instruction::AccountMeta { | ||
| pubkey: accounts.fee_payer.key().into(), | ||
| is_writable: true, | ||
| is_signer: true, | ||
| }); | ||
|
|
||
| account_metas.push(solana_instruction::AccountMeta { | ||
| pubkey: accounts.cpi_authority_pda.key().into(), | ||
| is_writable: false, | ||
| is_signer: false, | ||
| }); | ||
|
|
||
| account_metas.push(solana_instruction::AccountMeta { | ||
| pubkey: accounts.cpi_context.key().into(), | ||
| is_writable: true, | ||
| is_signer: false, | ||
| }); | ||
|
|
||
| for acc in &accounts.recipient_token_accounts { | ||
| account_metas.push(solana_instruction::AccountMeta { | ||
| pubkey: acc.key().into(), | ||
| is_writable: true, | ||
| is_signer: false, | ||
| }); | ||
| } | ||
|
|
||
| // Create account meta config for CPI write | ||
| let meta_config = MintActionMetaConfigCpiWrite { | ||
| fee_payer: input.payer, | ||
| mint_signer: if with_mint_signer { | ||
| input.mint_seed | ||
| } else { | ||
| None | ||
| account_metas | ||
| }, | ||
| authority: input.authority, | ||
| cpi_context: input.cpi_context_pubkey, | ||
| mint_needs_to_sign, | ||
| }; | ||
|
|
||
| // Get account metas | ||
| let accounts = get_mint_action_instruction_account_metas_cpi_write(meta_config); | ||
|
|
||
| // Serialize instruction data | ||
| let data_vec = instruction_data | ||
| .try_to_vec() | ||
| .map_err(|_| TokenSdkError::SerializationError)?; | ||
|
|
||
| Ok(Instruction { | ||
| program_id: Pubkey::new_from_array(light_ctoken_types::COMPRESSED_TOKEN_PROGRAM_ID), | ||
| accounts, | ||
| data: [vec![MINT_ACTION_DISCRIMINATOR], data_vec].concat(), | ||
| data, | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Tighten CPI-write invariants for MintToCToken and mint creation
In build_cpi_write_instruction you already reject Action::CreateSplMint in CPI-write mode, which is good. Two additional invariants would make this API harder to misuse:
- Ensure
MintToCTokenhas recipient accounts in CPI-write calls
Right now Action::MintToCToken(_) is allowed with only a comment noting it requires recipient accounts:
Action::MintToCToken(_) => {
// MintToCToken is allowed but needs recipient accounts
}If CPI-write calls with MintToCToken but an empty recipient_token_accounts vector are invalid, it’s better to fail on the client:
for action in &instruction_data.actions {
match action {
Action::CreateSplMint(_) => {
msg!("CreateSplMint not supported in CPI write mode");
return Err(TokenSdkError::CannotMintWithDecompressedInCpiWrite);
}
- Action::MintToCToken(_) => {
- // MintToCToken is allowed but needs recipient accounts
- }
+ Action::MintToCToken(_) => {
+ if accounts.recipient_token_accounts.is_empty() {
+ msg!("MintToCToken requires at least one recipient token account in CPI write mode");
+ return Err(TokenSdkError::InvalidAccountData);
+ }
+ }
_ => {}
}
}- (Optional) Enforce
mint_signerpresence when creating a compressed mint in CPI-write mode
If instruction_data.create_mint.is_some() in CPI-write mode implies a required mint_signer account, adding an explicit check before building metas would surface misconfigurations earlier, rather than relying on on-chain failures.
These checks keep CPI-write usage aligned with on-chain expectations and make mis-wiring more obvious during integration.
-->
🤖 Prompt for AI Agents
In sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs
around lines 84 to 167, add proactive validation before serializing/building the
Instruction: if any Action::MintToCToken exists in instruction_data.actions,
return an error when accounts.recipient_token_accounts.is_empty(); additionally,
if instruction_data.create_mint.is_some() then require
accounts.mint_signer.is_some() and return an appropriate TokenSdkError when
missing. Place these checks after the action loop (or immediately before
serializing data) so you fail early with clear errors instead of producing an
invalid CPI write instruction.
sdk-libs/compressed-token-sdk/src/instructions/mint_to_compressed/instruction.rs
Outdated
Show resolved
Hide resolved
| /// Required if any action is creating a mint | ||
| pub new_mint: Option<NewMint>, | ||
| } |
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 new_mint / create_spl_mint semantics in the helper API
MintActionParams::new_mint is documented as:
/// Required if any action is creating a mint
pub new_mint: Option<NewMint>,while create_comprehensive_mint_action_instruction keeps a create_spl_mint: bool parameter but now immediately errors if it is true:
if create_spl_mint {
return Err(RpcError::CustomError(
"CreateSplMint is no longer supported".to_string(),
));
}Given that:
new_mintis what actually drives the “create compressed mint” flow increate_mint_action_instruction, andCreateSplMinthas been explicitly dropped from this helper,
the current comments/parameter naming are a bit misleading for callers migrating off the old API.
I’d recommend:
- Updating the doc comment on
new_mintto explicitly say it is what enables compressed mint creation (and that SPL mint creation is no longer supported through this helper). - Optionally deprecating or clearly documenting
create_spl_mintas a no-op flag that always errors, so downstream users know to stop setting it and rely solely onnew_mint.
This will make the new behaviour obvious to SDK consumers and avoid confusion around CreateSplMint.
-->
Also applies to: 299-301, 310-313
| let mut ctoken_accounts = Vec::new(); | ||
| let mut ctoken_account_index = 0u8; | ||
|
|
||
| for action in params.actions { | ||
| instruction_data = match action { | ||
| MintActionType::MintTo { | ||
| recipients, | ||
| token_account_version, | ||
| } => { | ||
| // Convert MintToRecipient (solana_sdk::Pubkey) to Recipient ([u8; 32]) | ||
| let ctoken_recipients: Vec<Recipient> = recipients | ||
| .into_iter() | ||
| .map(|r| Recipient { | ||
| recipient: r.recipient.to_bytes().into(), | ||
| amount: r.amount, | ||
| }) | ||
| .collect(); | ||
| instruction_data.with_mint_to_compressed(MintToCompressedAction { | ||
| token_account_version, | ||
| recipients: ctoken_recipients, | ||
| }) | ||
| } | ||
| MintActionType::MintToCToken { account, amount } => { | ||
| // Add account to the list and use its index | ||
| ctoken_accounts.push(account); | ||
| let current_index = ctoken_account_index; | ||
| ctoken_account_index += 1; | ||
|
|
||
| let token_pool = if needs_token_pool { | ||
| let mint = find_spl_mint_address(¶ms.mint_seed).0; | ||
| Some(derive_token_pool(&mint, 0)) | ||
| instruction_data.with_mint_to_ctoken(MintToCTokenAction { | ||
| account_index: current_index, | ||
| amount, | ||
| }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against ctoken_account_index u8 overflow when many MintToCToken actions are present
ctoken_account_index is a u8 that is incremented for every MintToCToken action:
let mut ctoken_account_index = 0u8;
// ...
ctoken_accounts.push(account);
let current_index = ctoken_account_index;
ctoken_account_index += 1;If a caller ever supplies >255 MintToCToken actions in a single instruction, this will wrap around in release builds and produce incorrect indices, corrupting the mapping between actions and ctoken_accounts.
You can make this safe and simpler by deriving the index from the vector length with a checked conversion:
- MintActionType::MintToCToken { account, amount } => {
- // Add account to the list and use its index
- ctoken_accounts.push(account);
- let current_index = ctoken_account_index;
- ctoken_account_index += 1;
-
- instruction_data.with_mint_to_ctoken(MintToCTokenAction {
- account_index: current_index,
- amount,
- })
- }
+ MintActionType::MintToCToken { account, amount } => {
+ // Add account to the list and use its index (bounded by u8::MAX)
+ let current_index = u8::try_from(ctoken_accounts.len())
+ .map_err(|_| {
+ RpcError::CustomError(
+ "Too many MintToCToken accounts in a single instruction (max 255)"
+ .to_string(),
+ )
+ })?;
+ ctoken_accounts.push(account);
+
+ instruction_data.with_mint_to_ctoken(MintToCTokenAction {
+ account_index: current_index,
+ amount,
+ })
+ }This fails fast with a clear client-side error instead of silently generating bogus indices.
-->
🤖 Prompt for AI Agents
In sdk-libs/token-client/src/instructions/mint_action.rs around lines 171 to
203, the code uses a u8 counter ctoken_account_index that increments for each
MintToCToken action and can overflow silently; replace that manual counter by
deriving the index from ctoken_accounts.len() with a checked conversion to u8
(e.g. use u8::try_from(ctoken_accounts.len()) or similar) before pushing the new
account, return or propagate a clear error if the conversion fails (meaning >255
entries), then push the account and use the computed index for the
MintToCTokenAction; remove the manual incrementing variable to avoid wraparound.
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: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
program-libs/compressed-account/src/pubkey.rs (4)
209-214: Adding a required method toAsPubkeyis a breaking change; consider a default impl forto_light_pubkey.Introducing
fn to_light_pubkey(&self) -> Pubkey;as a required method on the publicAsPubkeytrait will break any external crates that implementAsPubkeybut don’t yet define this method.You can make this extension backward‑compatible and also remove duplication in the impls by providing a default body that reuses
to_pubkey_bytes:pub trait AsPubkey { fn to_pubkey_bytes(&self) -> [u8; 32]; #[cfg(feature = "anchor")] fn to_anchor_pubkey(&self) -> anchor_lang::prelude::Pubkey; - fn to_light_pubkey(&self) -> Pubkey; + fn to_light_pubkey(&self) -> Pubkey { + Pubkey::from(self.to_pubkey_bytes()) + } }With this, existing downstream impls keep compiling, and most of the explicit
to_light_pubkeyimpls in this file become redundant unless you want to micro‑opt specific cases.
216-227:AsPubkey for Pubkeyimpl is correct; can be simplified if a default is added.
to_light_pubkeyreturning*selfis correct and efficient sincePubkeyisCopy. If you adopt a default implementation on the trait (as suggested above), this method becomes unnecessary boilerplate and can be safely dropped for clarity.
229-242: AnchorPubkeyconversion is semantically correct and consistent.
to_light_pubkeyforanchor_lang::prelude::Pubkeyusingself.to_bytes().into()correctly funnels through the existing[u8; 32] -> Pubkeyconversion and keeps the semantics aligned withto_pubkey_bytes. If you add a defaultto_light_pubkeyon the trait, this explicit impl becomes redundant but is still fine to keep for readability.
254-265:[u8; 32]implementation is correct and leverages existingFromimpls.
to_light_pubkeydelegating toself.into()is correct, sinceFrom<&[u8; 32]> for Pubkeyis defined above. If you move to a defaultto_light_pubkeybased onto_pubkey_bytes, this method can be removed entirely without changing semantics.sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (1)
8-25: Strengthened config invariants; double‑checkspl_mint_initializedhandling for existing mintsMaking
fee_payera requiredPubkeyand droppingDefaultonMintActionMetaConfigis a solid move: it forces all constructors to supply a real payer and avoids “partially initialized” configs being used by accident.One thing to verify:
MintActionMetaConfig::newalways setsspl_mint_initialized: false, ignoringinstruction_data.mint.metadata.spl_mint_initialized, whereasnew_create_mintforwards that flag from the instruction data. Ifnewis ever used for decompressed SPL‑mint flows, you probably want to propagate the metadata flag similarly so that:if config.spl_mint_initialized { // add SPL mint + token pool + spl_token_2022 program metas }behaves consistently. If
newis strictly for “pure compressed” flows, a short doc comment onnew(or onspl_mint_initialized) stating that expectation would make this clearer to future callers.Also applies to: 69-107
♻️ Duplicate comments (5)
sdk-libs/token-client/src/instructions/mint_action.rs (2)
171-199: Guard againstctoken_account_indexu8 overflow for many MintToCToken actions
ctoken_account_indexis au8that you increment perMintToCTokenaction; with >255 such actions in one instruction this will wrap and corrupt the mapping betweenaccount_indexandctoken_accounts. Deriving the index from the current vector length with a hard cap avoids silent wraparound:- // Convert and add actions using builder pattern - // Collect decompressed token accounts for MintToCToken actions - let mut ctoken_accounts = Vec::new(); - let mut ctoken_account_index = 0u8; + // Convert and add actions using builder pattern + // Collect decompressed token accounts for MintToCToken actions + let mut ctoken_accounts = Vec::new(); @@ - MintActionType::MintToCToken { account, amount } => { - // Add account to the list and use its index - ctoken_accounts.push(account); - let current_index = ctoken_account_index; - ctoken_account_index += 1; - - instruction_data.with_mint_to_ctoken(MintToCTokenAction { - account_index: current_index, - amount, - }) - } + MintActionType::MintToCToken { account, amount } => { + // Add account to the list and use its index, bounded by u8::MAX + if ctoken_accounts.len() >= u8::MAX as usize { + return Err(RpcError::CustomError( + "Too many MintToCToken accounts in a single instruction (max 255)" + .to_string(), + )); + } + let current_index = ctoken_accounts.len() as u8; + ctoken_accounts.push(account); + + instruction_data.with_mint_to_ctoken(MintToCTokenAction { + account_index: current_index, + amount, + }) + }This fails fast with a clear client error instead of emitting an instruction with bogus indices.
48-50: Clarifynew_mintvscreate_spl_mintsemantics in the public APIWith the new builder flow,
new_mintis what actually drives compressed mint creation, whilecreate_spl_mintincreate_comprehensive_mint_action_instructionis now an always-unsupported flag that immediately errors. The current doc onnew_mint(“Required if any action is creating a mint”) plus the lingeringcreate_spl_mintparameter are confusing for SDK consumers migrating from the old API; I’d suggest explicitly documentingnew_mintas “required when creating a compressed mint via this helper (SPL mint creation is no longer supported here)” and either deprecating or clearly documentingcreate_spl_mintas a legacy parameter that always errors so callers know to rely solely onnew_mint.Also applies to: 295-308
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (1)
47-85: Consider centralizing CPI-write invariants alongsidebuild_cpi_write_instructionRight now
build_cpi_write_instructionblindly serializesinstruction_dataand uses only the CPI-write account set, without checking that the actions are compatible with CPI-write (e.g., no disallowed variants, andrecipient_token_accountspresent whenMintToCTokenactions are included). If those invariants aren’t enforced elsewhere anymore, it’s worth reintroducing lightweight validation here—right before serialization—so obviously invalid CPI-write combinations fail fast on the client instead of reaching the on-chain program.Also applies to: 88-153
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (2)
109-143: CPI context ctor leavestree_pubkey/output_queueatPubkey::default()with no safe way to set them
new_cpi_contextcurrently initializes:tree_pubkey: Pubkey::default(), // Must be set with chainable method input_queue: None, // Set with chainable method if not create_mint output_queue: Pubkey::default(), // Must be set with chainable methodbut the only chainable methods on
MintActionMetaConfigarewith_tokens_out_queueandwith_ctoken_accounts. In practice, callers either have to:
- Mutate the public fields directly, or
- Risk calling
get_mint_action_instruction_account_metaswithtree_pubkey/output_queueleft as all‑zero keys, which will produce structurally valid but semantically bogus metas.Since
tree_pubkeyandoutput_queueare always consumed later, this is an easy footgun in CPI flows.I’d strongly recommend:
- Adding explicit setters like:
pub fn with_tree_pubkey(mut self, tree: Pubkey) -> Self { self.tree_pubkey = tree; self } pub fn with_input_queue(mut self, q: Pubkey) -> Self { self.input_queue = Some(q); self } pub fn with_output_queue(mut self, q: Pubkey) -> Self { self.output_queue = q; self }and updating these comments to point to them, or
- If you prefer field mutation, change the comments to say “must be set by caller before building metas” and add a guard in
get_mint_action_instruction_account_metas(e.g. asserttree_pubkey != Pubkey::default()/output_queue != Pubkey::default()).Failing fast here is much easier to debug than chasing runtime program failures caused by zero pubkeys.
145-155: Make theMintToCTokenaccount requirements explicit and fail fast whenctoken_accountsis missing
analyze_actionsintentionally cannot populatectoken_accountsforMintToCTokenbecause the on‑chain action only stores indices:let (has_mint_to_actions, ctoken_accounts) = Self::analyze_actions(&instruction_data.actions); // ... for token_account in &config.ctoken_accounts { metas.push(AccountMeta::new(*token_account, false)); }Given
ctoken_accountsis always initialized as an emptyVechere, anyMintToCTokenflow requires the caller to invokewith_ctoken_accounts(or otherwise fillctoken_accounts) before building metas. If they forget, the instruction will still be built, but without the decompressed token accounts the program expects, leading to opaque on‑chain failures.To make this safer and more self‑documenting:
- Update the docs on
with_ctoken_accounts(and/or onMintActionMetaConfigitself) to clearly state that wheneverMintToCTokenactions are present, callers MUST provide the decompressed token pubkeys viactoken_accounts; they are not derived fromactions.- Optionally (recommended), add a guard before emitting metas, e.g.:
if config.has_mint_to_actions && instruction_data.actions.iter().any(|a| matches!(a, Action::MintToCToken(_))) && config.ctoken_accounts.is_empty() { panic!("MintToCToken actions present but no ctoken_accounts supplied"); // or return a Result from the meta builder if you prefer not to panic }so that missing accounts surface as an immediate, descriptive failure rather than a mysterious program error.
Also applies to: 157-180, 315-318
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (11)
program-tests/compressed-token-test/tests/mint/failing.rsis excluded by none and included by noneprogram-tests/compressed-token-test/tests/transfer2/compress_failing.rsis excluded by none and included by noneprogram-tests/compressed-token-test/tests/transfer2/decompress_failing.rsis excluded by none and included by noneprogram-tests/compressed-token-test/tests/transfer2/no_system_program_cpi_failing.rsis excluded by none and included by noneprogram-tests/compressed-token-test/tests/transfer2/transfer_failing.rsis excluded by none and included by nonesdk-tests/sdk-token-test/src/pda_ctoken/mint.rsis excluded by none and included by nonesdk-tests/sdk-token-test/tests/compress_and_close_cpi.rsis excluded by none and included by nonesdk-tests/sdk-token-test/tests/ctoken_pda.rsis excluded by none and included by nonesdk-tests/sdk-token-test/tests/decompress_full_cpi.rsis excluded by none and included by nonesdk-tests/sdk-token-test/tests/pda_ctoken.rsis excluded by none and included by nonesdk-tests/sdk-token-test/tests/test_compress_full_and_close.rsis excluded by none and included by none
📒 Files selected for processing (7)
program-libs/compressed-account/src/pubkey.rs(4 hunks)sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs(1 hunks)sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs(3 hunks)sdk-libs/compressed-token-sdk/src/instructions/mint_action/cpi_accounts.rs(9 hunks)sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs(1 hunks)sdk-libs/compressed-token-sdk/tests/mint_action_cpi_accounts_tests.rs(8 hunks)sdk-libs/token-client/src/instructions/mint_action.rs(4 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
sdk-libs/compressed-token-sdk/tests/mint_action_cpi_accounts_tests.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/cpi_accounts.rs (3)
try_from_account_infos(202-204)try_from_account_infos_full(78-197)create_mint(22-28)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (2)
sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (3)
instruction(30-33)instruction_write_to_cpi_context_first(45-48)instruction_write_to_cpi_context_set(59-62)program-libs/ctoken-types/src/instructions/mint_action/cpi_context.rs (1)
default(25-37)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (2)
sdk-libs/token-client/src/actions/mint_action.rs (1)
mint_action(25-60)sdk-libs/compressed-token-sdk/src/instructions/mint_action/cpi_accounts.rs (1)
create_mint(22-28)
sdk-libs/token-client/src/instructions/mint_action.rs (5)
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs (2)
derive_compressed_mint_address(217-226)find_spl_mint_address(236-241)sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (3)
get_mint_action_instruction_account_metas(185-321)new(70-107)new_create_mint(29-67)sdk-libs/token-client/src/actions/mint_action.rs (1)
mint_action(25-60)program-libs/ctoken-types/src/instructions/mint_action/builder.rs (2)
new_mint(88-107)new(43-60)program-libs/ctoken-types/src/instructions/mint_action/mint_to_compressed.rs (2)
new(14-19)new(30-35)
sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (3)
instruction(22-45)instruction_write_to_cpi_context_first(47-65)instruction_write_to_cpi_context_set(67-85)
⏰ 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). (12)
- GitHub Check: stateless-js-v1
- GitHub Check: Forester e2e test
- GitHub Check: stateless-js-v2
- GitHub Check: programs (compressed-token-and-e2e, ["cargo-test-sbf -p compressed-token-test --test v1", "cargo-...
- GitHub Check: programs (light-system-program-address, ["cargo-test-sbf -p system-test -- test_with_address", "c...
- GitHub Check: programs (account-compression-and-registry, ["cargo-test-sbf -p account-compression-test", "cargo...
- GitHub Check: programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test", "cargo test -p light-system-prog...
- GitHub Check: programs (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: Test program-libs-slow
- GitHub Check: Test program-libs-fast
- GitHub Check: Test batched-merkle-tree-simulate
🔇 Additional comments (2)
sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (1)
4-62: Well-factored CTokenInstruction trait abstractionThe trait cleanly separates execute vs CPI-write account sets via GATs and gives a uniform API for instruction construction; the bounds and documentation line up with how
MintActionCompressedInstructionDataimplements it, and I don’t see any correctness or ergonomics issues here.sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (1)
15-45: CTokenInstruction implementation for mint_action is consistent and defensiveThe implementation correctly wires
MintActionCompressedInstructionDatainto the genericCTokenInstructionsurface, and the explicit guard that rejects pre-set CPI-write flags ininstruction()before serializing helps catch misuse early on the client side. Account metas are delegated toMintActionCpiAccounts::to_account_metas, keeping execute-mode wiring centralized.
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs
Outdated
Show resolved
Hide resolved
| #[derive(Debug, Clone, Default, Copy)] | ||
| pub struct MintActionCpiAccountsConfig { | ||
| pub with_cpi_context: bool, | ||
| pub create_mint: bool, // true = address tree, false = state tree | ||
| pub mint_to_compressed: bool, // true = tokens_out_queue required | ||
| } | ||
|
|
||
| impl MintActionCpiAccountsConfig { | ||
| pub fn create_mint() -> Self { | ||
| Self { | ||
| with_cpi_context: false, | ||
| create_mint: true, | ||
| mint_to_compressed: false, | ||
| } | ||
| } | ||
|
|
||
| pub fn mint_to_compressed(self) -> Self { | ||
| Self { | ||
| with_cpi_context: self.with_cpi_context, | ||
| create_mint: self.create_mint, | ||
| mint_to_compressed: true, | ||
| } | ||
| } | ||
| } |
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
Config-driven CPI account parsing is solid; next step is deriving config from instruction data
MintActionCpiAccountsConfig cleanly controls which optional accounts are expected (mint_signer, cpi_context, in_output_queue, tokens_out_queue), and the updated try_from_account_infos_full / to_account_metas paths line up with that model and with the tests. To reduce the chance of config callsites drifting from what a given MintActionCompressedInstructionData actually requires, a natural follow-up would be a small helper that derives the appropriate MintActionCpiAccountsConfig from the instruction data (closing the existing TODO and keeping the parsing rules centralized).
Also applies to: 79-197, 199-205, 278-380
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
sdk-libs/compressed-token-sdk/tests/mint_action_cpi_accounts_tests.rs (1)
245-261: Config‑driven CPI account tests are thorough; tighten error assertionsThe new tests for “all options”,
create_mint(), explicit update config, invalid queue ownership, and helper methods exercise the main branches ofMintActionCpiAccountsConfigand the helper APIs well. The only nit is the repeated pattern:assert!(result.is_err()); assert!(result.is_err());in several failure tests, which is redundant and could instead assert on the specific error type or message to better lock in expected behaviour.
Also applies to: 337-345, 426-439, 854-861, 930-952
sdk-libs/token-client/src/instructions/mint_action.rs (1)
155-171: Avoid panics fromunwrap()when deserialising existing compressed mintsBoth
unwrap()calls in the existing mint branch (lines 166 and 185) will panic if RPC returns unexpected data:
- Line 166:
compressed_mint_account.data.unwrap()panics if the account lacks data- Line 185:
compressed_mint.try_into().unwrap()panics if the type conversion failsThis is inconsistent with your error handling pattern elsewhere in the file (e.g., line 169's
map_err()for deserialization). Since this is client-facing SDK code, panics crash the caller's application when the RPC layer returns malformed responses.Use proper error propagation instead:
let account_data = compressed_mint_account .data .as_ref() .ok_or_else(|| RpcError::CustomError("Compressed mint account missing data".to_string()))?; let mut data_slice = account_data.data.as_slice(); let compressed_mint: CompressedMint = BorshDeserialize::deserialize(&mut data_slice).map_err(|e| { RpcError::CustomError(format!("Failed to deserialize compressed mint: {}", e)) })?; let mint = compressed_mint .try_into() .map_err(|e| RpcError::CustomError(format!("Failed to convert compressed mint: {:?}", e)))?; let compressed_mint_inputs = CompressedMintWithContext { prove_by_index: rpc_proof_result.accounts[0].root_index.proof_by_index(), leaf_index: compressed_mint_account.leaf_index, root_index: rpc_proof_result.accounts[0] .root_index .root_index() .unwrap_or_default(), address: params.compressed_mint_address, mint, };Also applies to: lines 177–187
♻️ Duplicate comments (5)
sdk-libs/token-client/src/instructions/mint_action.rs (2)
88-90: Clarifynew_mint(compressed mint) semantics and de‑emphasisecreate_spl_mint
MintActionParams::new_mintis still documented only as “Required if any action is creating a mint”, whilecreate_comprehensive_mint_action_instructionnow rejectscreate_spl_mint = trueoutright and effectively only supports compressed mint creation throughnew_mint.To make this unambiguous for callers:
- Update the
new_mintdoc to say it controls compressed mint creation and that SPL mint creation is no longer supported here.- Adjust or deprecate the
create_spl_mintflag and its doc comment (which still implies it’s the driver for mint creation) so it’s clearly a legacy parameter that always errors.This prevents confusion for clients migrating from the old CreateSplMint path.
Also applies to: 323-336, 345-348
210-237: Guard againstu8overflow inMintToCTokenaccount indices
ctoken_account_indexis au8incremented for everyMintToCTokenaction. With >255 actions, this will wrap in release builds and silently produce incorrectaccount_indexvalues, breaking the mapping betweenMintToCTokenAction.account_indexandctoken_accounts.Derive the index from the vector length with a checked conversion and drop the manual counter:
- // Collect decompressed token accounts for MintToCToken actions - let mut ctoken_accounts = Vec::new(); - let mut ctoken_account_index = 0u8; + // Collect decompressed token accounts for MintToCToken actions + let mut ctoken_accounts = Vec::new(); @@ - MintActionType::MintToCToken { account, amount } => { - // Add account to the list and use its index - ctoken_accounts.push(account); - let current_index = ctoken_account_index; - ctoken_account_index += 1; - - instruction_data.with_mint_to_ctoken(MintToCTokenAction { - account_index: current_index, - amount, - }) - } + MintActionType::MintToCToken { account, amount } => { + // Derive the index from current length; enforce u8::MAX bound + let current_index = u8::try_from(ctoken_accounts.len()) + .map_err(|_| { + RpcError::CustomError( + "Too many MintToCToken accounts in a single instruction (max 255)" + .to_string(), + ) + })?; + + ctoken_accounts.push(account); + + instruction_data.with_mint_to_ctoken(MintToCTokenAction { + account_index: current_index, + amount, + }) + }This fails fast with a clear client‑side error instead of silently corrupting indices.
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (1)
47-85: Tighten CPI‑write invariants and reuse base meta constructionThe CPI‑write helpers correctly flip
cpi_context.{first_set_context,set_context}andbuild_cpi_write_instructionemits metas in the same order asget_mint_action_instruction_account_metas_cpi_write, which is good. What’s missing is any validation that the actions ininstruction_dataare compatible with the CPI‑write accounts you pass:
- If
instruction_data.actionscontainsMintToCTokenbutaccounts.recipient_token_accountsis empty, you’ll still build an instruction that only fails on‑chain.- If
instruction_data.create_mint.is_some()(or aCreateSplMintaction exists) butaccounts.mint_signerisNone, you again emit an instruction that can’t possibly succeed.To make this API safer and restore the invariants you previously had, consider:
Before serializing
instruction_data, loop overinstruction_data.actionsand:
- Reject
Action::MintToCToken(_)whenaccounts.recipient_token_accounts.is_empty()with a clearTokenSdkError::InvalidAccountData(or similar).- If you still intend to forbid
Action::CreateSplMintin CPI‑write, explicitly error out withTokenSdkError::CannotMintWithDecompressedInCpiWrite; otherwise, assert the requiredmint_signeris present.Add a separate check like:
if instruction_data.create_mint.is_some() && accounts.mint_signer.is_none() { msg!("create_mint in CPI write mode requires mint_signer account"); return Err(ProgramError::from(TokenSdkError::InvalidAccountData)); }(Optional) To avoid drift, factor the common
[light_system_program, mint_signer, authority, fee_payer, cpi_authority_pda, cpi_context]metas into a shared helper used both here and inget_mint_action_instruction_account_metas_cpi_write, then appendrecipient_token_accountson top.This way mis‑wired CPI‑write calls fail early on the client instead of only at runtime on‑chain, and your two CPI‑write meta builders stay in lock‑step.
Also applies to: 90-152
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (2)
109-143: CPI‑context config leaves critical fields as defaultPubkey::default()with no setters
new_cpi_contextinitializes:
tree_pubkeytoPubkey::default()input_queuetoNoneoutput_queuetoPubkey::default()tokens_out_queuetoNonewith comments like “Must be set with chainable method”, but there are currently no chainable setters for
tree_pubkey,input_queue, oroutput_queue. That makes it easy to accidentally build metas with0pubkeys, and the comments don’t match the API surface.I’d suggest one of:
Add explicit builders:
pub fn with_tree_pubkey(mut self, tree: Pubkey) -> Self { self.tree_pubkey = tree; self } pub fn with_input_queue(mut self, queue: Pubkey) -> Self { self.input_queue = Some(queue); self } pub fn with_output_queue(mut self, queue: Pubkey) -> Self { self.output_queue = queue; self }and update the comments to point at these methods; or
If you prefer direct field mutation, change the comments to say callers must set these public fields before calling
get_mint_action_instruction_account_metas, and add a runtime check there (e.g. error iftree_pubkey == Pubkey::default()oroutput_queue == Pubkey::default()whenwith_cpi_context.is_some()).This makes CPI‑context usage much harder to get wrong.
151-180: MakeMintToCToken/ctoken_accountscontract explicit and fail fast
analyze_actionsintentionally returns an emptyctoken_accountsvector even whenAction::MintToCTokenis present, andwith_ctoken_accountsis the only way to populate it. That’s correct given that the on‑chain action only stores indices, but:
- Callers can easily assume
ctoken_accountsis derived fromactions.- If there are
MintToCTokenactions butctoken_accountsis left empty,get_mint_action_instruction_account_metaswill emit no extra accounts (lines 315‑318) and the on‑chain program will fail in a surprising way.Two improvements would make this API clearer and safer:
Document the requirement
Expand the
with_ctoken_accountsdoc comment (and/or theMintActionMetaConfigstruct docs) to explicitly state that callers must supply decompressed token Pubkeys wheneverMintToCTokenactions are present, otherwise the constructed metas will be invalid.Optionally add a runtime check
Consider extending
analyze_actionsto also return ahas_mint_to_ctokenflag, then, before building metas, assert that when that flag is true,ctoken_accountsis non‑empty, e.g.:if has_mint_to_ctoken && config.ctoken_accounts.is_empty() { return Err(TokenSdkError::InvalidAccountData.into()); }or at least
debug_assert!with a clear message in internal helpers.This will save users from subtle runtime failures in
MintToCTokenflows.Also applies to: 315-318
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (15)
program-tests/compressed-token-test/tests/mint/edge_cases.rsis excluded by none and included by noneprogram-tests/compressed-token-test/tests/mint/failing.rsis excluded by none and included by noneprogram-tests/compressed-token-test/tests/mint/random.rsis excluded by none and included by noneprogram-tests/compressed-token-test/tests/transfer2/compress_failing.rsis excluded by none and included by noneprogram-tests/compressed-token-test/tests/transfer2/decompress_failing.rsis excluded by none and included by noneprogram-tests/compressed-token-test/tests/transfer2/no_system_program_cpi_failing.rsis excluded by none and included by noneprogram-tests/compressed-token-test/tests/transfer2/shared.rsis excluded by none and included by noneprogram-tests/compressed-token-test/tests/transfer2/transfer_failing.rsis excluded by none and included by noneprogram-tests/utils/src/assert_mint_action.rsis excluded by none and included by nonesdk-tests/sdk-token-test/src/pda_ctoken/mint.rsis excluded by none and included by nonesdk-tests/sdk-token-test/tests/compress_and_close_cpi.rsis excluded by none and included by nonesdk-tests/sdk-token-test/tests/ctoken_pda.rsis excluded by none and included by nonesdk-tests/sdk-token-test/tests/decompress_full_cpi.rsis excluded by none and included by nonesdk-tests/sdk-token-test/tests/pda_ctoken.rsis excluded by none and included by nonesdk-tests/sdk-token-test/tests/test_compress_full_and_close.rsis excluded by none and included by none
📒 Files selected for processing (11)
program-libs/compressed-account/src/pubkey.rs(4 hunks)sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs(1 hunks)sdk-libs/compressed-token-sdk/src/error.rs(2 hunks)sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs(3 hunks)sdk-libs/compressed-token-sdk/src/instructions/mint_action/cpi_accounts.rs(9 hunks)sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs(1 hunks)sdk-libs/compressed-token-sdk/src/instructions/mint_action/mod.rs(0 hunks)sdk-libs/compressed-token-sdk/src/instructions/mod.rs(1 hunks)sdk-libs/compressed-token-sdk/tests/mint_action_cpi_accounts_tests.rs(8 hunks)sdk-libs/token-client/src/actions/mint_action.rs(2 hunks)sdk-libs/token-client/src/instructions/mint_action.rs(4 hunks)
💤 Files with no reviewable changes (1)
- sdk-libs/compressed-token-sdk/src/instructions/mint_action/mod.rs
🧰 Additional context used
🧬 Code graph analysis (7)
sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (3)
instruction(22-45)instruction_write_to_cpi_context_first(47-65)instruction_write_to_cpi_context_set(67-85)
sdk-libs/token-client/src/actions/mint_action.rs (2)
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs (1)
derive_compressed_mint_address(217-226)sdk-libs/token-client/src/instructions/mint_action.rs (1)
create_mint_action_instruction(93-321)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (3)
sdk-libs/token-client/src/actions/mint_action.rs (1)
mint_action(24-59)program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1)
new(43-60)sdk-libs/compressed-token-sdk/src/instructions/mint_action/cpi_accounts.rs (1)
create_mint(22-28)
sdk-libs/compressed-token-sdk/src/instructions/mod.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (2)
get_mint_action_instruction_account_metas(185-321)get_mint_action_instruction_account_metas_cpi_write(335-374)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (2)
sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (3)
instruction(30-33)instruction_write_to_cpi_context_first(45-48)instruction_write_to_cpi_context_set(59-62)program-libs/ctoken-types/src/instructions/mint_action/cpi_context.rs (1)
default(25-37)
sdk-libs/compressed-token-sdk/tests/mint_action_cpi_accounts_tests.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/cpi_accounts.rs (3)
try_from_account_infos(202-204)try_from_account_infos_full(78-197)create_mint(22-28)
sdk-libs/token-client/src/instructions/mint_action.rs (4)
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs (2)
derive_compressed_mint_address(217-226)find_spl_mint_address(236-241)sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (3)
get_mint_action_instruction_account_metas(185-321)new(70-107)new_create_mint(29-67)program-libs/ctoken-types/src/instructions/mint_action/builder.rs (2)
new_mint(88-107)new(43-60)program-libs/ctoken-types/src/instructions/mint_action/mint_to_compressed.rs (2)
new(14-19)new(30-35)
⏰ 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). (8)
- GitHub Check: Test batched-merkle-tree-simulate
- GitHub Check: Test program-libs-fast
- GitHub Check: Test program-libs-slow
- GitHub Check: Forester e2e test
- GitHub Check: programs (system-cpi-test-v2-functional-account-infos, ["cargo-test-sbf -p system-cpi-v2-test -- ...
- GitHub Check: programs (system-cpi-test, ["cargo-test-sbf -p system-cpi-test", "cargo test -p light-system-prog...
- GitHub Check: programs (system-cpi-test-v2-functional-read-only, ["cargo-test-sbf -p system-cpi-v2-test -- func...
- GitHub Check: programs (account-compression-and-registry, ["cargo-test-sbf -p account-compression-test", "cargo...
🔇 Additional comments (15)
sdk-libs/compressed-token-sdk/src/error.rs (2)
60-63: LGTM! Clear validation error variants.The new error variants appropriately distinguish between creating a new mint (which requires
create_mintdata) and using an existing mint (wherecreate_mintmust beNone). The error messages reference specific method names, making debugging straightforward.
116-117: LGTM! Proper sequential error code assignment.The error codes 17024 and 17025 follow the existing sequence correctly, and the mappings are complete.
sdk-libs/token-client/src/actions/mint_action.rs (2)
5-5: Import consolidation looks good.The import path update aligns with the refactored SDK structure, making the API surface cleaner.
12-14: Clean import consolidation.Grouping related imports improves readability and follows idiomatic Rust style.
program-libs/compressed-account/src/pubkey.rs (5)
224-226: LGTM: Optimal implementation for Copy type.Returning
*selfis the most efficient approach sincePubkeyimplementsCopy.
239-241: LGTM: Correct conversion through bytes.The conversion chain
to_bytes() -> [u8; 32] -> Pubkeyleverages the existingFrom<[u8; 32]>implementation and is correctly feature-gated.
249-251: LGTM: Consistent conversion pattern.Uses the same conversion approach as the Anchor implementation, appropriately gated for the non-Anchor Solana use case.
262-264: LGTM: Clean conversion from byte array.The
.into()call correctly uses theFrom<&[u8; 32]>implementation defined at lines 164-168.
209-214: All AsPubkey implementations updated—concern addressed.Verification confirms the new required method
to_light_pubkey()has been added to all fourAsPubkeytrait implementations in the codebase:
Pubkey(line 224)anchor_lang::prelude::Pubkey(line 239)solana_pubkey::Pubkey(line 249)[u8; 32](line 262)The method is actively used (e.g.,
ctoken-types/src/instructions/mint_action/mint_to_compressed.rs:16). No broken implementations remain within the workspace.While adding a required method is technically a breaking change for any external crates implementing
AsPubkey, the PR has properly handled all implementations in scope here. If this is a public API, the team should follow semantic versioning practices separately.sdk-libs/compressed-token-sdk/src/instructions/mod.rs (1)
42-48: Mint action re-exports match the new builder/config API surfaceThe updated
mint_actionandmint_to_compressedre-exports cleanly expose the new meta/config types and helpers while dropping legacy inputs; this module now reflects the intended public API without leaks of the old path.sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (1)
4-63: CTokenInstruction trait cleanly separates instruction data from invocationThe trait design (separate execute/CPI account types, three explicit build modes,
self-by-value semantics) matches the new builder pattern and keeps instruction construction decoupled from how/where it’s invoked. No issues from a correctness or ergonomics standpoint.sdk-libs/compressed-token-sdk/tests/mint_action_cpi_accounts_tests.rs (1)
4-148: Minimal CPI accounts test correctly tracks new default config behaviourPulling in
MintActionCpiAccountsConfigand extending the “minimal” fixture within_output_queueplus theparsed.in_output_queue.is_some()assertion lines everything up with the default (create_mint = false) parsing rules intry_from_account_infos_full. This is a solid guard against regressions in the base config.sdk-libs/compressed-token-sdk/src/instructions/mint_action/cpi_accounts.rs (1)
14-204: Config‑based MintActionCpiAccounts parsing and metas look consistent and robust
MintActionCpiAccountsConfigplus the refactoredtry_from_account_infos_full,to_account_infos, andto_account_metasgive a clear, extensible way to control optional accounts (mint_signer, CPI context, in_output_queue, tokens_out_queue) without hard‑coding booleans. The account order and writability/signature flags into_account_metasmatch the expectations exercised in tests and the CPI write helpers, and the default config intry_from_account_infoscleanly represents the non‑create‑mint, no‑CPI‑context case.Also applies to: 223-380
sdk-libs/token-client/src/instructions/mint_action.rs (1)
95-153: Builder‑based mint action construction and meta config wiring look solidThe overall flow—branching on
new_mintfor create vs update, fetching the appropriate proof, buildingMintActionCompressedInstructionDatavia.new_mint/.newand the.with_*methods, then feeding that intoMintActionMetaConfig::new_create_mint/::newandget_mint_action_instruction_account_metas—is coherent and matches the on‑chain account/meta expectations surfaced in the SDK. Aside from the specific issues called out above, this is a clean migration away from the older MintActionInputs path.Also applies to: 195-207, 279-321
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (1)
22-45: Non‑CPIinstruction()guard on CPI context looks solidThe explicit check that rejects
cpi_contextwithset_context/first_set_contextin the genericinstruction()path keeps CPI‑write usage on the dedicated helpers and avoids silently mis‑tagged instructions. Serialization viaLightInstructionDataandaccounts.to_account_metas()wiring look consistent with the rest of the SDK.
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs
Outdated
Show resolved
Hide resolved
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
♻️ Duplicate comments (1)
program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1)
65-84: CPI-context builder helpers still duplicate flag logic from the CTokenInstruction impl
new_mint_write_to_cpi_context,write_to_cpi_context_first, andwrite_to_cpi_context_sethere manipulateCpiContext.first_set_context/set_contextin essentially the same way as theinstruction_write_to_cpi_context_first/instruction_write_to_cpi_context_setmethods onMintActionCompressedInstructionDatainsdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs. That gives you two places that must stay in sync if CPI semantics evolve.It’s not wrong, but you might want to centralize the flag-toggling into a small helper (or rely on just one of these paths) to avoid future drift between client-side builder usage and the trait implementation.
Also applies to: 134-160
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (2)
sdk-tests/sdk-compressible-test/src/instructions/create_user_record_and_game_session.rsis excluded by none and included by nonesdk-tests/sdk-token-test/src/ctoken_pda/mint.rsis excluded by none and included by none
📒 Files selected for processing (14)
program-libs/ctoken-types/src/instructions/mint_action/builder.rs(1 hunks)sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs(1 hunks)sdk-libs/compressed-token-sdk/src/error.rs(2 hunks)sdk-libs/compressed-token-sdk/src/instructions/approve/instruction.rs(0 hunks)sdk-libs/compressed-token-sdk/src/instructions/batch_compress/instruction.rs(0 hunks)sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs(5 hunks)sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs(3 hunks)sdk-libs/compressed-token-sdk/src/instructions/mint_action/cpi_accounts.rs(3 hunks)sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs(1 hunks)sdk-libs/compressed-token-sdk/src/instructions/mint_action/mod.rs(0 hunks)sdk-libs/compressed-token-sdk/src/instructions/mint_to_compressed/instruction.rs(4 hunks)sdk-libs/compressed-token-sdk/src/instructions/transfer2/instruction.rs(0 hunks)sdk-libs/compressed-token-sdk/src/instructions/update_compressed_mint/instruction.rs(4 hunks)sdk-libs/compressed-token-sdk/src/token_pool.rs(1 hunks)
💤 Files with no reviewable changes (4)
- sdk-libs/compressed-token-sdk/src/instructions/batch_compress/instruction.rs
- sdk-libs/compressed-token-sdk/src/instructions/approve/instruction.rs
- sdk-libs/compressed-token-sdk/src/instructions/transfer2/instruction.rs
- sdk-libs/compressed-token-sdk/src/instructions/mint_action/mod.rs
🧰 Additional context used
🧬 Code graph analysis (8)
sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (3)
instruction(21-41)instruction_write_to_cpi_context_first(43-60)instruction_write_to_cpi_context_set(62-79)
sdk-libs/compressed-token-sdk/src/instructions/update_compressed_mint/instruction.rs (2)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (3)
get_mint_action_instruction_account_metas(172-274)get_mint_action_instruction_account_metas_cpi_write(288-318)new(66-102)program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1)
new(25-42)
sdk-libs/compressed-token-sdk/src/instructions/mint_to_compressed/instruction.rs (3)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (3)
get_mint_action_instruction_account_metas(172-274)new(66-102)new_cpi_context(104-136)program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1)
new(25-42)program-libs/ctoken-types/src/instructions/mint_action/mint_to_compressed.rs (2)
new(14-19)new(30-35)
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs (2)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (4)
get_mint_action_instruction_account_metas(172-274)get_mint_action_instruction_account_metas_cpi_write(288-318)new_cpi_context(104-136)new_create_mint(27-64)program-libs/ctoken-types/src/instructions/mint_action/builder.rs (2)
new_mint(44-63)new_mint_write_to_cpi_context(65-84)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/cpi_accounts.rs (2)
program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1)
new(25-42)sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (1)
new(66-102)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/instruction.rs (3)
sdk-libs/compressed-token-sdk/src/ctoken_instruction.rs (3)
instruction(9-12)instruction_write_to_cpi_context_first(14-17)instruction_write_to_cpi_context_set(19-22)sdk-libs/compressed-token-sdk/src/error.rs (3)
from(81-83)from(87-89)from(93-128)program-libs/ctoken-types/src/instructions/mint_action/cpi_context.rs (1)
default(25-37)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (4)
sdk-libs/token-client/src/actions/mint_action.rs (1)
mint_action(24-59)program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1)
new(25-42)sdk-libs/compressed-token-sdk/src/token_pool.rs (1)
find_token_pool_pda_with_index(20-28)sdk-libs/compressed-token-sdk/src/error.rs (3)
from(81-83)from(87-89)from(93-128)
program-libs/ctoken-types/src/instructions/mint_action/builder.rs (1)
sdk-libs/compressed-token-sdk/src/instructions/mint_action/account_metas.rs (1)
new(66-102)
⏰ 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). (4)
- GitHub Check: programs (light-system-program-address, ["cargo-test-sbf -p system-test -- test_with_address", "c...
- GitHub Check: programs (system-cpi-test-v2-functional-account-infos, ["cargo-test-sbf -p system-cpi-v2-test -- ...
- GitHub Check: Test batched-merkle-tree-simulate
- GitHub Check: Test program-libs-fast
🔇 Additional comments (2)
sdk-libs/compressed-token-sdk/src/token_pool.rs (1)
5-12: TokenPool struct wiring matches PDA helpers
TokenPool { pubkey, bump, index }lines up cleanly withfind_token_pool_pda_with_indexandderive_token_pool, and addingAnchorDeserialize/AnchorSerialize+PartialEqshould make this easy to pass around in Anchor-compatible contexts. I don’t see correctness issues here; this is a straightforward and sensible extraction of pool metadata.sdk-libs/compressed-token-sdk/src/error.rs (1)
60-65: New MintActionMetaConfig / CPI errors and codes are consistentThe added
TokenSdkErrorvariants and their numeric mappings (17024–17026) are coherent with the existing range and clearly express the builder precondition failures aroundcreate_mintandcpi_context. Just make sure any external consumers that rely on concrete error codes (e.g., SDKs, dashboards) are updated to understand these new values.Also applies to: 118-120
| use solana_instruction::Instruction; | ||
| use solana_program_error::ProgramError; | ||
|
|
||
| pub trait CTokenInstruction: Sized { | ||
| type ExecuteAccounts<'info, A: light_account_checks::AccountInfoTrait + Clone + 'info>; | ||
|
|
||
| type CpiWriteAccounts<'info, A: light_account_checks::AccountInfoTrait + Clone + 'info>; | ||
|
|
||
| fn instruction<A: light_account_checks::AccountInfoTrait + Clone>( | ||
| self, | ||
| accounts: &Self::ExecuteAccounts<'_, A>, | ||
| ) -> Result<Instruction, ProgramError>; | ||
|
|
||
| fn instruction_write_to_cpi_context_first<A: light_account_checks::AccountInfoTrait + Clone>( | ||
| self, | ||
| accounts: &Self::CpiWriteAccounts<'_, A>, | ||
| ) -> Result<Instruction, ProgramError>; | ||
|
|
||
| fn instruction_write_to_cpi_context_set<A: light_account_checks::AccountInfoTrait + Clone>( | ||
| self, | ||
| accounts: &Self::CpiWriteAccounts<'_, A>, | ||
| ) -> Result<Instruction, ProgramError>; | ||
| } |
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
CTokenInstruction trait shape is solid; consider whether methods should consume self
Using GATs for ExecuteAccounts / CpiWriteAccounts gives you nice type-level coupling between instruction data and its account layouts, and returning Result<Instruction, ProgramError> fits Solana conventions.
One design question: all three methods take self by value, which means callers must clone the instruction data if they want to build multiple instructions (e.g., execute vs different CPI-write variants) from the same base state. If that’s intentional to enforce a “build once, send once” pattern, this is fine; otherwise, you might consider &self receivers and cloning internally in the impls for a bit more ergonomic reuse.
| let meta_config = if cpi_context_pubkey.is_some() { | ||
| MintActionMetaConfig::new_cpi_context( | ||
| &instruction_data, | ||
| input.mint_authority, | ||
| input.payer, | ||
| cpi_context_pubkey.unwrap(), | ||
| )? | ||
| } else { | ||
| MintActionMetaConfig::new_create_mint( | ||
| &instruction_data, | ||
| input.mint_authority, | ||
| input.mint_signer, | ||
| input.payer, | ||
| input.address_tree_pubkey, | ||
| input.output_queue, | ||
| )? | ||
| }; | ||
|
|
||
| create_mint_action_cpi( | ||
| mint_action_inputs, | ||
| mint_action_cpi_context, | ||
| cpi_context_pubkey, | ||
| ) | ||
| let account_metas = | ||
| get_mint_action_instruction_account_metas(meta_config, &compressed_mint_with_context); | ||
|
|
||
| let data = instruction_data | ||
| .data() | ||
| .map_err(|_| TokenSdkError::SerializationError)?; | ||
|
|
||
| Ok(Instruction { | ||
| program_id: solana_pubkey::Pubkey::new_from_array( | ||
| light_ctoken_types::COMPRESSED_TOKEN_PROGRAM_ID, | ||
| ), | ||
| accounts: account_metas, | ||
| data, | ||
| }) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CPI-context branch still emits placeholder accounts
MintActionMetaConfig::new_cpi_context intentionally seeds tree_pubkey, output_queue, tokens_out_queue, and mint_signer with placeholders, expecting the caller to fill them in. Here we take that config and immediately derive metas, so the instruction ships with all-zero tree/output queue pubkeys and omits the required mint signer altogether. On-chain this trips the first account ownership check. Please populate the config before you call get_mint_action_instruction_account_metas (e.g., assign the real mint_signer, set tree_pubkey/output_queue to the provided inputs, and reapply tokens_out_queue when has_mint_to_actions is true).
🤖 Prompt for AI Agents
In
sdk-libs/compressed-token-sdk/src/instructions/create_compressed_mint/instruction.rs
around lines 86 to 118, the CPI-context branch uses
MintActionMetaConfig::new_cpi_context which returns a config seeded with
placeholder pubkeys and a missing mint_signer; you must fill that config with
the real values from the inputs before deriving account metas: set
config.mint_signer = input.mint_signer, set config.tree_pubkey =
input.address_tree_pubkey, set config.output_queue = input.output_queue, and if
meta_config.has_mint_to_actions is true reassign config.tokens_out_queue to the
expected tokens_out_queue value; after updating those fields call
get_mint_action_instruction_account_metas(meta_config,
&compressed_mint_with_context) so the produced Instruction contains the real
accounts rather than zeroed placeholders.
| @@ -1,3 +1,4 @@ | |||
| use light_compressed_account::instruction_data::traits::LightInstructionData; | |||
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
MintAction builder usage looks correct; minor CPI-branch ergonomics
The construction
let mut instruction_data =
MintActionCompressedInstructionData::new(compressed_mint_inputs.clone(), proof)
.with_mint_to_compressed(mint_to_action);followed by optionally attaching cpi_context before feeding instruction_data into MintActionMetaConfig::new / new_cpi_context is logically consistent and lines up with the new error conditions (CreateMintMustBeNone, CpiContextRequired). The subsequent call to instruction_data.data() via LightInstructionData is also appropriate for building the final Instruction payload.
One small ergonomic improvement in the CPI branch:
let meta_config = if cpi_context_pubkey.is_some() {
MintActionMetaConfig::new_cpi_context(
&instruction_data,
mint_authority,
payer,
cpi_context_pubkey.unwrap(),
)?
} else { ... };You can avoid the is_some + unwrap pair and keep the pubkey and branch coupled more clearly with:
let meta_config = if let Some(cpi_context_pubkey) = cpi_context_pubkey {
MintActionMetaConfig::new_cpi_context(
&instruction_data,
mint_authority,
payer,
cpi_context_pubkey,
)?
} else {
MintActionMetaConfig::new(
&instruction_data,
mint_authority,
payer,
state_merkle_tree,
input_queue,
output_queue_cmint,
)?
.with_tokens_out_queue(output_queue_tokens)
};Functionally identical, but a bit harder to misuse.
Also applies to: 59-107
| TokenPool, | ||
| }; |
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.
token_pool input is unused but documented as required
The new token_pool: Option<TokenPool> field is documented as “Required if the mint is decompressed” but is immediately destructured as token_pool: _ in create_mint_to_compressed_instruction and never influences the instruction data or account metas.
If decompressed-mint flows are supposed to depend on a specific token-pool address, this requirement currently isn’t enforced or reflected in the constructed instruction. Either:
- Wire
token_poolinto the relevant instruction data / meta-config, or - Relax or clarify the doc comment to indicate it’s reserved for future use to avoid misleading callers.
As written, it’s harmless but confusing for API consumers.
Also applies to: 35-35, 56-57
| if let Some(ctx) = cpi_context { | ||
| instruction_data = instruction_data.with_cpi_context(ctx); | ||
| } | ||
|
|
||
| let meta_config = MintActionMetaConfig::new( | ||
| &instruction_data, | ||
| input.authority, | ||
| input.payer, | ||
| input.in_merkle_tree, | ||
| input.in_output_queue, | ||
| input.out_output_queue, | ||
| )?; | ||
|
|
||
| create_mint_action_cpi(mint_action_inputs, mint_action_cpi_context, None) | ||
| let account_metas = | ||
| get_mint_action_instruction_account_metas(meta_config, &input.compressed_mint_inputs); | ||
|
|
||
| let data = instruction_data | ||
| .data() | ||
| .map_err(|_| TokenSdkError::SerializationError)?; | ||
|
|
||
| Ok(Instruction { | ||
| program_id: solana_pubkey::Pubkey::new_from_array( | ||
| light_ctoken_types::COMPRESSED_TOKEN_PROGRAM_ID, | ||
| ), | ||
| accounts: account_metas, | ||
| data, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CPI context account never reaches the metas
When cpi_context is Some, we call with_cpi_context on the instruction data, but we still build the metas via MintActionMetaConfig::new(...). That constructor hardcodes with_cpi_context = None, so get_mint_action_instruction_account_metas never appends the CPI context account. The runtime then errors out with a missing-account failure as soon as the on-chain processor reads the CPI context. Please plumb the CPI context account Pubkey into the config before deriving metas (e.g., extend UpdateCompressedMintInputs with the CPI-context account key and set meta_config.with_cpi_context = Some(pubkey) ahead of the metas call).
Summary by CodeRabbit
New Features
Refactor
Removed
Tests
✏️ Tip: You can customize this high-level summary in your review settings.