Conversation
Entire-Checkpoint: 3b001a578b94
|
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (1)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAdds on-chain rent_sponsor plumbing and a 50,000-lamport MINT_CREATION_FEE collected via CPI for compressed-mint creation; threads Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Instruction Builder
participant Program as Mint Action Processor
participant FeePayer as Fee Payer (payer account)
participant RentSponsor as Rent Sponsor PDA
participant CPI as System/CPI
Client->>Program: submit create_compressed_mint(with compressible_config, rent_sponsor, create_mint)
Program->>Program: parse AccountsConfig (needs_rent_sponsor -> true)
alt CPI write context set
Program-->>Client: error (CpiContextSetNotUsable / validation)
else not CPI write
Program->>Program: validate rent_sponsor PDA == config or RENT_SPONSOR_V1
Program->>CPI: invoke transfer_lamports_via_cpi(FeePayer -> RentSponsor, MINT_CREATION_FEE)
CPI-->>RentSponsor: lamports transferred
Program->>Program: create/init compressed mint accounts
Program-->>Client: success
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
sdk-libs/compressed-token-sdk/src/compressed_token/v2/create_compressed_mint/instruction.rs (1)
75-91:⚠️ Potential issue | 🟠 Major
create_compressed_mint_cpi_writein this file is permanently broken by the program guard.The companion function
create_compressed_mint_cpi_write(lines 123-174) enforces thatfirst_set_context || set_contextistruebefore building the instruction. Because the program now rejects anycreate_mintinstruction wherewrite_to_cpi_context = true, every instruction produced bycreate_compressed_mint_cpi_writewill be rejected at runtime withCpiContextSetNotUsable.This function should be deprecated (with a
#[deprecated]attribute and a clear explanation) or removed in this PR to avoid shipping dead, misleading API surface.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk-libs/compressed-token-sdk/src/compressed_token/v2/create_compressed_mint/instruction.rs` around lines 75 - 91, The create_compressed_mint_cpi_write function is permanently broken by the program guard that rejects instructions with write_to_cpi_context=true; remove or deprecate this dead API: add a #[deprecated] attribute to create_compressed_mint_cpi_write with a clear message explaining it always triggers CpiContextSetNotUsable and suggest the proper alternative (e.g., create_compressed_mint or the non-CPI helper), update the public API/export list to avoid re-exporting it if necessary, and remove or mark any related tests/docs referencing create_compressed_mint_cpi_write to prevent confusion.sdk-libs/compressed-token-sdk/src/compressed_token/v2/mint_action/cpi_accounts.rs (1)
202-246: 🧹 Nitpick | 🔵 Trivial
to_account_infos()andto_account_metas()ordering is consistent and correct.Both methods place
rent_sponsorafterauthorityand beforefee_payer, which aligns with the program's account iterator order forcreate_mint. The capacity hint (20 + ctoken_accounts.len()) should be bumped to21to account for the new optionalrent_sponsorslot, avoiding a reallocation in the commoncreate_mintpath.✏️ Suggested fix
- let mut accounts = Vec::with_capacity(20 + self.ctoken_accounts.len()); + let mut accounts = Vec::with_capacity(21 + self.ctoken_accounts.len());🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk-libs/compressed-token-sdk/src/compressed_token/v2/mint_action/cpi_accounts.rs` around lines 202 - 246, The capacity hint for the accounts Vec is off by one because the optional rent_sponsor adds an extra slot; update the allocation in to_account_infos (and the sibling to_account_metas) from Vec::with_capacity(20 + self.ctoken_accounts.len()) to Vec::with_capacity(21 + self.ctoken_accounts.len()) so the common create_mint path avoids a reallocation while preserving the existing account order (mint_signer, authority, optional rent_sponsor, then fee_payer, ...).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@programs/compressed-token/program/src/compressed_token/mint_action/accounts.rs`:
- Around line 113-114: The parsed optional account rent_sponsor (obtained via
iter.next_option_mut("rent_sponsor", config.needs_rent_sponsor())) is not being
validated and can be an arbitrary writable account; add an on-chain check that
the account.key() equals the expected protocol PDA constant
(LIGHT_TOKEN_RENT_SPONSOR) before proceeding with any fee transfer or
rent-sponsor logic, and return an appropriate error if it does not; apply the
same check in the other occurrence handling CMint create/close (the block that
mirrors this logic around the other use site).
- Around line 460-467: The function create_compressed_mint_cpi_write produces
instructions that will always hit the guard in accounts.rs (write_to_cpi_context
&& parsed_instruction_data.create_mint.is_some()) and thus always return
ErrorCode::CpiContextSetNotUsable; remove create_compressed_mint_cpi_write (and
the helper new_mint_write_to_cpi_context if unused) or mark
create_compressed_mint_cpi_write #[deprecated] and change its implementation to
return a clear compile/runtime error (or None) so SDK consumers cannot produce
broken instructions—ensure you update/remove any references and add a short doc
comment explaining the deprecation decision.
In
`@programs/compressed-token/program/src/compressed_token/mint_action/processor.rs`:
- Around line 53-56: The code currently uses
ErrorCode::MintActionMissingExecutingAccounts for both executing being None and
rent_sponsor being None; add a new distinct error variant (e.g.,
MintCreationFeeRentSponsorMissing) to the ErrorCode enum and replace the second
.ok_or(...) on rent_sponsor with
.ok_or(ErrorCode::MintCreationFeeRentSponsorMissing) so the rent fee-recipient
missing case is reported separately (keep the existing
.ok_or(ErrorCode::MintActionMissingExecutingAccounts) for executing).
In `@programs/compressed-token/program/src/lib.rs`:
- Line 39: Add a doc comment above the MINT_CREATION_FEE constant explaining why
it is set to 50_000 lamports (e.g., what costs or policy it covers and how it
relates to related constants like COMPRESSION_COST and COMPRESSION_INCENTIVE);
update the declaration for MINT_CREATION_FEE with a concise justification that
references those related constants (COMPRESSION_COST = 10_000,
COMPRESSION_INCENTIVE = 1_000) so maintainers/auditors can understand the fee
basis and any assumptions used to derive the 50_000 value.
In
`@sdk-libs/compressed-token-sdk/src/compressed_token/v2/mint_action/cpi_accounts.rs`:
- Line 92: The rent_sponsor account is only checked for writability via
next_option_mut, so update the parsing in try_from_account_infos_full (the block
that assigns let rent_sponsor = iter.next_option_mut("rent_sponsor",
config.create_mint)?) to validate the account's public key against the constant
LIGHT_TOKEN_RENT_SPONSOR when present; if the optional account exists and its
key does not equal LIGHT_TOKEN_RENT_SPONSOR return a descriptive error (e.g.,
InvalidAccountKey or a custom error) instead of accepting any writable account.
Ensure you use the same iterator/variable names (rent_sponsor, iter, config) and
keep the optional semantics (only validate when Some) so callers get an early,
descriptive failure.
In `@sdk-libs/token-sdk/tests/mint_action_cpi_accounts_tests.rs`:
- Around line 289-297: Add a negative test that ensures AccountsConfig::new()
rejects create_mint when the CPI context set flag is used: construct a
ZMintActionCompressedInstructionData (use the SDK helper
new_mint_write_to_cpi_context) with create_mint populated and
cpi_context.first_set_context/set_context = true and then call
AccountsConfig::new(...); assert it returns Err(CpiContextSetNotUsable). Place
this alongside the existing mint tests (referencing LIGHT_TOKEN_RENT_SPONSOR in
the test setup) to cover the new guard and prevent regressions.
---
Outside diff comments:
In
`@sdk-libs/compressed-token-sdk/src/compressed_token/v2/create_compressed_mint/instruction.rs`:
- Around line 75-91: The create_compressed_mint_cpi_write function is
permanently broken by the program guard that rejects instructions with
write_to_cpi_context=true; remove or deprecate this dead API: add a
#[deprecated] attribute to create_compressed_mint_cpi_write with a clear message
explaining it always triggers CpiContextSetNotUsable and suggest the proper
alternative (e.g., create_compressed_mint or the non-CPI helper), update the
public API/export list to avoid re-exporting it if necessary, and remove or mark
any related tests/docs referencing create_compressed_mint_cpi_write to prevent
confusion.
In
`@sdk-libs/compressed-token-sdk/src/compressed_token/v2/mint_action/cpi_accounts.rs`:
- Around line 202-246: The capacity hint for the accounts Vec is off by one
because the optional rent_sponsor adds an extra slot; update the allocation in
to_account_infos (and the sibling to_account_metas) from Vec::with_capacity(20 +
self.ctoken_accounts.len()) to Vec::with_capacity(21 +
self.ctoken_accounts.len()) so the common create_mint path avoids a reallocation
while preserving the existing account order (mint_signer, authority, optional
rent_sponsor, then fee_payer, ...).
---
Duplicate comments:
In
`@programs/compressed-token/program/src/compressed_token/mint_action/processor.rs`:
- Around line 49-59: The transfer currently sends MINT_CREATION_FEE to whichever
writable account is passed as executing.rent_sponsor without validating its
identity; before calling transfer_lamports_via_cpi in the block guarded by
accounts_config.create_mint, verify the rent_sponsor is the expected account
(e.g., compare executing.rent_sponsor pubkey against the validated/expected rent
sponsor from parsed accounts or require it is a signer/authority similar to the
check in accounts.rs) and return ErrorCode::MintActionMissingExecutingAccounts
(or a new appropriate error) if the identity check fails; perform this
validation immediately after fetching executing and rent_sponsor and only then
call transfer_lamports_via_cpi(MINT_CREATION_FEE, executing.system.fee_payer,
rent_sponsor).
| // Parse rent_sponsor when creating mint (fee recipient) or when creating/closing CMint | ||
| let rent_sponsor = iter.next_option_mut("rent_sponsor", config.needs_rent_sponsor())?; |
There was a problem hiding this comment.
rent_sponsor not validated against the expected protocol PDA — fee bypass vulnerability.
iter.next_option_mut("rent_sponsor", config.needs_rent_sponsor()) accepts any writable account. A caller can pass their own wallet as rent_sponsor, causing MINT_CREATION_FEE to transfer from fee_payer back to a caller-controlled address. Net effect: zero cost to the caller and zero revenue for the protocol.
Since LIGHT_TOKEN_RENT_SPONSOR is the only valid recipient, the program must verify the key on-chain before the fee transfer:
🛡️ Proposed fix — add key check after parsing
// Parse rent_sponsor when creating mint (fee recipient) or when creating/closing CMint
let rent_sponsor = iter.next_option_mut("rent_sponsor", config.needs_rent_sponsor())?;
+if config.create_mint {
+ if let Some(sponsor) = rent_sponsor {
+ if sponsor.key() != &light_sdk_types::LIGHT_TOKEN_RENT_SPONSOR {
+ msg!("Invalid rent_sponsor: expected LIGHT_TOKEN_RENT_SPONSOR");
+ return Err(ErrorCode::InvalidRentSponsor.into());
+ }
+ }
+}Also applies to: 379-384
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@programs/compressed-token/program/src/compressed_token/mint_action/accounts.rs`
around lines 113 - 114, The parsed optional account rent_sponsor (obtained via
iter.next_option_mut("rent_sponsor", config.needs_rent_sponsor())) is not being
validated and can be an arbitrary writable account; add an on-chain check that
the account.key() equals the expected protocol PDA constant
(LIGHT_TOKEN_RENT_SPONSOR) before proceeding with any fee transfer or
rent-sponsor logic, and return an appropriate error if it does not; apply the
same check in the other occurrence handling CMint create/close (the block that
mirrors this logic around the other use site).
programs/compressed-token/program/src/compressed_token/mint_action/accounts.rs
Outdated
Show resolved
Hide resolved
| .ok_or(ErrorCode::MintActionMissingExecutingAccounts)?; | ||
| let rent_sponsor = executing | ||
| .rent_sponsor | ||
| .ok_or(ErrorCode::MintActionMissingExecutingAccounts)?; |
There was a problem hiding this comment.
Two distinct failure modes share the same error code.
ErrorCode::MintActionMissingExecutingAccounts is used for both:
executingbeingNone(shouldn't happen given the CPI guard)rent_sponsorbeingNone(missing required fee-recipient account)
Using a dedicated error code (e.g., MintCreationFeeRentSponsorMissing) for case 2 makes on-chain failures immediately actionable for SDK consumers.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@programs/compressed-token/program/src/compressed_token/mint_action/processor.rs`
around lines 53 - 56, The code currently uses
ErrorCode::MintActionMissingExecutingAccounts for both executing being None and
rent_sponsor being None; add a new distinct error variant (e.g.,
MintCreationFeeRentSponsorMissing) to the ErrorCode enum and replace the second
.ok_or(...) on rent_sponsor with
.ok_or(ErrorCode::MintCreationFeeRentSponsorMissing) so the rent fee-recipient
missing case is reported separately (keep the existing
.ok_or(ErrorCode::MintActionMissingExecutingAccounts) for executing).
| derive_light_cpi_signer!("cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m"); | ||
|
|
||
| pub const MAX_ACCOUNTS: usize = 30; | ||
| pub const MINT_CREATION_FEE: u64 = 50_000; |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider documenting the rationale for the 50_000 lamport value.
The constant is clean, but given that related cost constants exist elsewhere (e.g., COMPRESSION_COST = 10_000, COMPRESSION_INCENTIVE = 1_000), a brief doc comment explaining the basis for 50_000 would help future maintainers and auditors understand the fee policy.
✏️ Suggested addition
+/// Mint creation fee charged to the fee_payer and transferred to the rent_sponsor PDA.
+/// Set at 50,000 lamports (5× COMPRESSION_COST) to cover protocol operational overhead.
pub const MINT_CREATION_FEE: u64 = 50_000;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| pub const MINT_CREATION_FEE: u64 = 50_000; | |
| /// Mint creation fee charged to the fee_payer and transferred to the rent_sponsor PDA. | |
| /// Set at 50,000 lamports (5× COMPRESSION_COST) to cover protocol operational overhead. | |
| pub const MINT_CREATION_FEE: u64 = 50_000; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@programs/compressed-token/program/src/lib.rs` at line 39, Add a doc comment
above the MINT_CREATION_FEE constant explaining why it is set to 50_000 lamports
(e.g., what costs or policy it covers and how it relates to related constants
like COMPRESSION_COST and COMPRESSION_INCENTIVE); update the declaration for
MINT_CREATION_FEE with a concise justification that references those related
constants (COMPRESSION_COST = 10_000, COMPRESSION_INCENTIVE = 1_000) so
maintainers/auditors can understand the fee basis and any assumptions used to
derive the 50_000 value.
| return Err(AccountError::InvalidSigner.into()); | ||
| } | ||
|
|
||
| let rent_sponsor = iter.next_option_mut("rent_sponsor", config.create_mint)?; |
There was a problem hiding this comment.
rent_sponsor accepted without key validation against LIGHT_TOKEN_RENT_SPONSOR.
next_option_mut only checks writability. A CPI caller parsing inbound accounts via try_from_account_infos_full will accept any writable account as rent_sponsor, masking a mismatch before the transaction hits the program. Adding the key check here gives callers early, descriptive failures:
🛡️ Proposed fix
let rent_sponsor = iter.next_option_mut("rent_sponsor", config.create_mint)?;
+if config.create_mint {
+ if let Some(sponsor) = rent_sponsor {
+ if sponsor.key() != light_sdk_types::LIGHT_TOKEN_RENT_SPONSOR {
+ msg!("rent_sponsor must be LIGHT_TOKEN_RENT_SPONSOR");
+ return Err(AccountError::InvalidProgramId.into());
+ }
+ }
+}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@sdk-libs/compressed-token-sdk/src/compressed_token/v2/mint_action/cpi_accounts.rs`
at line 92, The rent_sponsor account is only checked for writability via
next_option_mut, so update the parsing in try_from_account_infos_full (the block
that assigns let rent_sponsor = iter.next_option_mut("rent_sponsor",
config.create_mint)?) to validate the account's public key against the constant
LIGHT_TOKEN_RENT_SPONSOR when present; if the optional account exists and its
key does not equal LIGHT_TOKEN_RENT_SPONSOR return a descriptive error (e.g.,
InvalidAccountKey or a custom error) instead of accepting any writable account.
Ensure you use the same iterator/variable names (rent_sponsor, iter, config) and
keep the optional semantics (only validate when Some) so callers get an early,
descriptive failure.
| // Rent sponsor (required for create_mint, known PDA) | ||
| create_test_account( | ||
| LIGHT_TOKEN_RENT_SPONSOR, | ||
| [0u8; 32], | ||
| false, | ||
| true, | ||
| false, | ||
| vec![], | ||
| ), |
There was a problem hiding this comment.
Happy-path test for rent_sponsor is correct; missing negative test for the new CPI-context guard.
The new LIGHT_TOKEN_RENT_SPONSOR account and the assert!(parsed.rent_sponsor.is_some()) assertion are correct. However, AccountsConfig::new() in the program now rejects create_mint when write_to_cpi_context = true — that invariant has no test coverage. A failing test case like the one below would prevent regressions:
✅ Suggested negative test
#[test]
fn test_create_mint_rejected_when_write_to_cpi_context() {
// Build a ZMintActionCompressedInstructionData with create_mint set
// AND cpi_context.first_set_context = true / set_context = true.
// AccountsConfig::new() must return Err(CpiContextSetNotUsable).
//
// (Use the SDK's new_mint_write_to_cpi_context builder to construct the data.)
}Also applies to: 354-354
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@sdk-libs/token-sdk/tests/mint_action_cpi_accounts_tests.rs` around lines 289
- 297, Add a negative test that ensures AccountsConfig::new() rejects
create_mint when the CPI context set flag is used: construct a
ZMintActionCompressedInstructionData (use the SDK helper
new_mint_write_to_cpi_context) with create_mint populated and
cpi_context.first_set_context/set_context = true and then call
AccountsConfig::new(...); assert it returns Err(CpiContextSetNotUsable). Place
this alongside the existing mint tests (referencing LIGHT_TOKEN_RENT_SPONSOR in
the test setup) to cover the new guard and prevent regressions.
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
sdk-libs/token-sdk/src/instruction/create_mints.rs (1)
282-307:⚠️ Potential issue | 🔴 CriticalFix
invoke_multiple_mints: The multi-mint CPI write path is broken by the new guard.
invoke_cpi_writeconstructs each write instruction viaMintActionCompressedInstructionData::new_mint_write_to_cpi_context, which sets bothcreate_mint: Some(CreateMint::default())andcpi_context: Some(cpi_context). This means thewrite_to_cpi_contextflag (derived fromcpi_context.first_set_context() || cpi_context.set_context()) will always be true. The guard inAccountsConfig::newat lines 468–474 ofaccounts.rsexplicitly rejects any instruction where both conditions hold, returningCpiContextSetNotUsable. Every call toinvoke_cpi_writein the N>1 loop (lines 293–295) will therefore fail immediately at accounts validation, makinginvoke_multiple_mints— and thusCreateMintsCpiwith N≥2 or nonzerocpi_context_offset— unreachable. The module docstring still advertises the N>1 path, so callers will silently hit this hard error.
invoke_single_mintandinvoke_executeare unaffected because they useinvoke_execute, which does not setwrite_to_cpi_context(flags are both false). The guard must either be removed for write operations orinvoke_cpi_writemust be redesigned to avoid the incompatibility.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@sdk-libs/token-sdk/src/instruction/create_mints.rs` around lines 282 - 307, invoke_multiple_mints fails because invoke_cpi_write builds instructions that set both create_mint (Some) and cpi_context (Some), which the new AccountsConfig guard rejects; fix by changing invoke_cpi_write so the CPI write instructions do not set create_mint at all (i.e., build the MintActionCompressedInstructionData for a pure "write to CPI context" path with create_mint = None and only the cpi_context set), or use/introduce an alternative constructor that produces a write-only instruction; update invoke_cpi_write (and any helper constructor calls it uses) to avoid the simultaneous create_mint + cpi_context combination so the accounts validation no longer returns CpiContextSetNotUsable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@programs/compressed-token/program/src/compressed_token/mint_action/accounts.rs`:
- Around line 454-458: Replace the reused
ErrorCode::CompressAndCloseCMintMustBeOnlyAction with a new, distinct error
variant to represent the separate invariant that create_mint (top-level) cannot
be combined with CompressAndCloseCMint: add a new enum variant e.g.,
CannotCombineCreateMintWithCompressAndClose to the ErrorCode definition, give it
a clear error message, and update the check in accounts.rs that currently
references ErrorCode::CompressAndCloseCMintMustBeOnlyAction (the block using
has_compress_and_close_cmint_action &&
parsed_instruction_data.create_mint.is_some()) to return the new variant
instead; keep the original CompressAndCloseCMintMustBeOnlyAction for the
existing actions-only case.
In `@programs/compressed-token/program/tests/mint_action.rs`:
- Around line 348-362: The variable is_creating_mint is misnamed:
instruction_data.mint.is_none() actually represents the
compressed-mint-decompressed flag (cmint_decompressed) in production; rename
is_creating_mint to cmint_decompressed to match accounts.rs semantics and avoid
confusion, and if you intended to detect a true "creating new mint" case
instead, replace that expression with the correct condition
(create_mint.is_some() && mint.is_none() or create_mint.is_some() &&
mint.is_some() per your creation logic) and update the combined error check that
references is_creating_mint accordingly.
---
Outside diff comments:
In `@sdk-libs/token-sdk/src/instruction/create_mints.rs`:
- Around line 282-307: invoke_multiple_mints fails because invoke_cpi_write
builds instructions that set both create_mint (Some) and cpi_context (Some),
which the new AccountsConfig guard rejects; fix by changing invoke_cpi_write so
the CPI write instructions do not set create_mint at all (i.e., build the
MintActionCompressedInstructionData for a pure "write to CPI context" path with
create_mint = None and only the cpi_context set), or use/introduce an
alternative constructor that produces a write-only instruction; update
invoke_cpi_write (and any helper constructor calls it uses) to avoid the
simultaneous create_mint + cpi_context combination so the accounts validation no
longer returns CpiContextSetNotUsable.
---
Duplicate comments:
In
`@programs/compressed-token/program/src/compressed_token/mint_action/accounts.rs`:
- Around line 114-115: The rent_sponsor account returned by
iter.next_option_mut("rent_sponsor", config.needs_rent_sponsor()) is not being
validated and can be replaced by an attacker to receive MINT_CREATION_FEE;
derive the expected protocol PDA for LIGHT_TOKEN_RENT_SPONSOR (using the same
seeds/constants used elsewhere in this crate and the program id, e.g., via
Pubkey::find_program_address) and compare it to the provided rent_sponsor.key
(and error if missing or mismatched), so that the processor only transfers
MINT_CREATION_FEE to the canonical LIGHT_TOKEN_RENT_SPONSOR PDA; update the
validation near where rent_sponsor is parsed (the code around
iter.next_option_mut, config.needs_rent_sponsor(), and the MINT_CREATION_FEE
transfer) to perform this check and return an appropriate error if the PDA does
not match.
In
`@programs/compressed-token/program/src/compressed_token/mint_action/processor.rs`:
- Around line 49-58: The code is using a single error variant
MintActionMissingExecutingAccounts for three different failure modes; update the
error enum to add distinct variants (e.g., MintActionMissingExecutingAccounts,
MintActionMissingRentSponsor, MintActionIdempotentExit) and replace the
.ok_or(...) calls accordingly: leave
validated_accounts.executing.ok_or(MintActionMissingExecutingAccounts) for the
missing executing accounts case, use
rent_sponsor.ok_or(MintActionMissingRentSponsor) for the missing rent_sponsor
case, and change the idempotent-exit usage (the other spot that currently
returns MintActionMissingExecutingAccounts) to return MintActionIdempotentExit;
update any matching error handling or docs to reflect the new variants so
callers can differentiate the three failure reasons.
In
`@sdk-libs/compressed-token-sdk/src/compressed_token/v2/create_compressed_mint/instruction.rs`:
- Around line 123-174: The create_compressed_mint_cpi_write function builds an
instruction using new_mint_write_to_cpi_context which always sets create_mint =
Some(...), causing write_to_cpi_context=true + create_mint populated (and thus
CpiContextSetNotUsable) — fix by using an instruction constructor that does not
set create_mint (or explicitly set create_mint = None) when building
MintActionCompressedInstructionData for CPI write, or switch to the correct
factory (e.g., a non-creating "write only" constructor) so create_mint is not
populated; update create_compressed_mint_cpi_write to call that alternative
constructor (or clear the create_mint field) and ensure cpi_context flags remain
consistent with the chosen constructor.
| // Validation: Cannot combine create_mint with CompressAndCloseCMint | ||
| if has_compress_and_close_cmint_action && parsed_instruction_data.create_mint.is_some() { | ||
| msg!("Cannot combine create_mint with CompressAndCloseCMint"); | ||
| return Err(ErrorCode::CompressAndCloseCMintMustBeOnlyAction.into()); | ||
| } |
There was a problem hiding this comment.
CompressAndCloseCMintMustBeOnlyAction reused for a semantically distinct condition.
Lines 448–452 already use this error for "CompressAndCloseCMint must be the only action in actions." Lines 454–458 reuse it for "cannot combine create_mint (a separate top-level field) with CompressAndCloseCMint," which is a different invariant. When SDK consumers receive this error, there is no way to distinguish between the two causes. A dedicated variant (e.g., CannotCombineCreateMintWithCompressAndClose) would make failures immediately actionable.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@programs/compressed-token/program/src/compressed_token/mint_action/accounts.rs`
around lines 454 - 458, Replace the reused
ErrorCode::CompressAndCloseCMintMustBeOnlyAction with a new, distinct error
variant to represent the separate invariant that create_mint (top-level) cannot
be combined with CompressAndCloseCMint: add a new enum variant e.g.,
CannotCombineCreateMintWithCompressAndClose to the ErrorCode definition, give it
a clear error message, and update the check in accounts.rs that currently
references ErrorCode::CompressAndCloseCMintMustBeOnlyAction (the block using
has_compress_and_close_cmint_action &&
parsed_instruction_data.create_mint.is_some()) to return the new variant
instead; keep the original CompressAndCloseCMintMustBeOnlyAction for the
existing actions-only case.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@sdk-libs/compressed-token-sdk/src/compressed_token/v2/mint_action/account_metas.rs`:
- Around line 132-137: The with_rent_sponsor setter allows setting rent_sponsor
without compressible_config which can reorder account metas and break
create_mint/compressible parsing; update the implementation to guard against
this by either (A) making with_rent_sponsor also require/attach a
compressible_config (or call into with_compressible_mint) or (B) add a
validation step in the build/validate method that checks if rent_sponsor is
Some(...) while compressible_config is None and then return/error/panic with a
clear message; also update the doc comment on with_rent_sponsor to state it must
be paired with compressible_config (or use with_compressible_mint) and reference
the compressible_config field and with_compressible_mint method for callers.
---
Duplicate comments:
In
`@programs/compressed-token/program/src/compressed_token/mint_action/accounts.rs`:
- Around line 456-460: The current branch that checks
has_compress_and_close_cmint_action &&
parsed_instruction_data.create_mint.is_some() reuses
CompressAndCloseCMintMustBeOnlyAction which is misleading; add a new error
variant (e.g. CreateMintWithCompressAndCloseCMintNotAllowed) to the ErrorCode
enum and replace the Err(...) return in this if-block with the new variant, and
update the msg! invocation to a clear, specific message referencing create_mint
+ CompressAndCloseCMint; also update any error docs/tests that assert the old
variant.
- Around line 469-476: The current guard blocks any instruction with
parsed_instruction_data.create_mint when write_to_cpi_context is true, which
makes the helper create_compressed_mint_cpi_write produce unusable instructions;
remove or deprecate the create_compressed_mint_cpi_write helper (and any SDK
bindings that call it) so callers cannot generate instructions that will always
fail due to this guard (referencing write_to_cpi_context,
parsed_instruction_data.create_mint and the ErrorCode::CpiContextSetNotUsable
check), or alternatively change the helper to never set create_mint when
targeting CPI-write contexts so the guard no longer triggers.
In
`@programs/compressed-token/program/src/compressed_token/mint_action/processor.rs`:
- Around line 48-68: Change the generic error mapping for a missing rent_sponsor
to a distinct error variant: instead of mapping rent_sponsor absence to
ErrorCode::MintActionMissingExecutingAccounts, add a new ErrorCode variant
(e.g., ErrorCode::MintActionMissingRentSponsor) and return that when
executing.rent_sponsor is None; update the check in the mint creation block (the
code using accounts_config.create_mint, validated_accounts.executing, let
rent_sponsor = executing.rent_sponsor.ok_or(...)? and the subsequent
rent_sponsor.key() comparison) to use the new ErrorCode and adjust any tests or
match arms that expect the old error.
In
`@sdk-libs/compressed-token-sdk/src/compressed_token/v2/mint_action/cpi_accounts.rs`:
- Around line 94-96: After reading rent_sponsor via
iter.next_option_mut("rent_sponsor", config.create_mint) validate that
rent_sponsor.key() equals the expected PDA before proceeding: derive the
expected PDA using the same seeds/program-id logic used when the rent sponsor
PDA is originally created (use config.create_mint and the program id/seeds used
by your program), compare it to rent_sponsor.key(), and return a clear error if
it does not match; place this check immediately after the rent_sponsor binding
in the same function so callers fail fast with a readable message.
| /// Set only the rent_sponsor account (without compressible_config or mint/cmint). | ||
| /// Required for create_mint operations to receive the mint creation fee. | ||
| pub fn with_rent_sponsor(mut self, rent_sponsor: Pubkey) -> Self { | ||
| self.rent_sponsor = Some(rent_sponsor); | ||
| self | ||
| } |
There was a problem hiding this comment.
Guard against using with_rent_sponsor without compressible_config.
Because account metas place compressible_config before rent_sponsor, setting only rent_sponsor can shift account ordering and break parsing for create_mint/compressible flows. Consider documenting that this must be paired with compressible_config (or use with_compressible_mint).
✍️ Suggested doc tweak
- /// Set only the rent_sponsor account (without compressible_config or mint/cmint).
- /// Required for create_mint operations to receive the mint creation fee.
+ /// Set the rent_sponsor account. Must be paired with `compressible_config`
+ /// (use `new_create_mint` / `with_compressible_mint`) to preserve account ordering.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@sdk-libs/compressed-token-sdk/src/compressed_token/v2/mint_action/account_metas.rs`
around lines 132 - 137, The with_rent_sponsor setter allows setting rent_sponsor
without compressible_config which can reorder account metas and break
create_mint/compressible parsing; update the implementation to guard against
this by either (A) making with_rent_sponsor also require/attach a
compressible_config (or call into with_compressible_mint) or (B) add a
validation step in the build/validate method that checks if rent_sponsor is
Some(...) while compressible_config is None and then return/error/panic with a
clear message; also update the doc comment on with_rent_sponsor to state it must
be paired with compressible_config (or use with_compressible_mint) and reference
the compressible_config field and with_compressible_mint method for callers.
Removes the restriction that blocked create_mint when write_to_cpi_context is true. This enables multi-mint creation (N-1 write-mode CPI calls + 1 execute-mode call). In write mode, the mint creation fee is charged by validating rent_sponsor against the hardcoded RENT_SPONSOR_V1 constant (no compressible_config account needed). The system program is included as a trailing account to enable the fee transfer CPI. Entire-Checkpoint: 3c0618ad006f
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@programs/compressed-token/program/src/compressed_token/mint_action/accounts.rs`:
- Around line 91-102: The rent_sponsor account must be parsed as mutable when
config.create_mint is true because fee transfer writes lamports; replace the
call to iter.next_account("rent_sponsor")? with the mut-validated iterator call
(e.g., iter.next_mut("rent_sponsor")?) so write_mode_rent_sponsor is obtained as
a mutable account; keep the surrounding conditional (config.create_mint) and
leave the rest (CpiContextLightSystemAccounts::new(...) and the system_program
handling) unchanged.
In
`@programs/compressed-token/program/src/compressed_token/mint_action/processor.rs`:
- Around line 184-190: The current idempotent early-exit branch uses
ErrorCode::MintActionMissingExecutingAccounts which is misleading for
create_mint; add a new error variant (e.g., CreateMintIdempotentNotAllowed) to
the ErrorCode enum and return that when is_idempotent_early_exit(err) is true
and accounts_config.create_mint is set; update any error display/serialization
as needed and replace the existing return
Err(ErrorCode::MintActionMissingExecutingAccounts.into()) in the create_mint
branch with Err(ErrorCode::CreateMintIdempotentNotAllowed.into()) so the failure
reason is explicit (refer to is_idempotent_early_exit,
accounts_config.create_mint, and the create_mint code path).
---
Duplicate comments:
In
`@programs/compressed-token/program/src/compressed_token/mint_action/accounts.rs`:
- Around line 476-480: The check that blocks combining create_mint with
CompressAndCloseCMint uses the generic
ErrorCode::CompressAndCloseCMintMustBeOnlyAction which is ambiguous; add a new
dedicated error variant (e.g.,
ErrorCode::CreateMintCannotCombineWithCompressAndCloseCMint) to your ErrorCode
enum and return that here instead of CompressAndCloseCMintMustBeOnlyAction,
updating the msg! string accordingly and ensuring the new error is
documented/converted to ProgramError where needed; change the conditional in the
block that references has_compress_and_close_cmint_action and
parsed_instruction_data.create_mint to return the new error.
In
`@programs/compressed-token/program/src/compressed_token/mint_action/processor.rs`:
- Around line 50-58: The code uses the same ErrorCode variant
(MintActionMissingExecutingAccounts) for multiple missing-option failures; add
distinct error variants (e.g., MintActionMissingRentSponsor and
MintActionMissingCompressibleConfig) to the ErrorCode enum and update the
processor code around validated_accounts.executing: keep the existing check that
returns MintActionMissingExecutingAccounts when executing is None, but change
the rent_sponsor unwrap to return MintActionMissingRentSponsor when
executing.rent_sponsor is None and the compressible_config unwrap to return
MintActionMissingCompressibleConfig when executing.compressible_config is None;
also update any tests or match arms that rely on the old variant.
| let write_mode_rent_sponsor = if config.create_mint { | ||
| Some(iter.next_account("rent_sponsor")?) | ||
| } else { | ||
| None | ||
| }; | ||
| let write_to_cpi_context_system = CpiContextLightSystemAccounts::new(&mut iter)?; | ||
| // System program is needed for the fee transfer CPI when creating mint in write mode. | ||
| // It's placed after all parsed accounts - the account iterator consumes it here, | ||
| // but it's available for the system program CPI via the transaction accounts. | ||
| if config.create_mint { | ||
| let _system_program = iter.next_account("system_program")?; | ||
| } |
There was a problem hiding this comment.
Require rent_sponsor to be mutable in write mode.
The fee transfer credits lamports to this account, so it must be writable; using next_account doesn’t enforce that. Prefer a mut-validated iterator call.
Suggested fix
- let write_mode_rent_sponsor = if config.create_mint {
- Some(iter.next_account("rent_sponsor")?)
- } else {
- None
- };
+ let write_mode_rent_sponsor = if config.create_mint {
+ Some(iter.next_mut("rent_sponsor")?)
+ } else {
+ None
+ };Based on learnings: "Use specialized AccountIterator methods (next_signer, next_mut, next_signer_mut, etc.) instead of manually calling next_account() followed by separate validation functions".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let write_mode_rent_sponsor = if config.create_mint { | |
| Some(iter.next_account("rent_sponsor")?) | |
| } else { | |
| None | |
| }; | |
| let write_to_cpi_context_system = CpiContextLightSystemAccounts::new(&mut iter)?; | |
| // System program is needed for the fee transfer CPI when creating mint in write mode. | |
| // It's placed after all parsed accounts - the account iterator consumes it here, | |
| // but it's available for the system program CPI via the transaction accounts. | |
| if config.create_mint { | |
| let _system_program = iter.next_account("system_program")?; | |
| } | |
| let write_mode_rent_sponsor = if config.create_mint { | |
| Some(iter.next_mut("rent_sponsor")?) | |
| } else { | |
| None | |
| }; | |
| let write_to_cpi_context_system = CpiContextLightSystemAccounts::new(&mut iter)?; | |
| // System program is needed for the fee transfer CPI when creating mint in write mode. | |
| // It's placed after all parsed accounts - the account iterator consumes it here, | |
| // but it's available for the system program CPI via the transaction accounts. | |
| if config.create_mint { | |
| let _system_program = iter.next_account("system_program")?; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@programs/compressed-token/program/src/compressed_token/mint_action/accounts.rs`
around lines 91 - 102, The rent_sponsor account must be parsed as mutable when
config.create_mint is true because fee transfer writes lamports; replace the
call to iter.next_account("rent_sponsor")? with the mut-validated iterator call
(e.g., iter.next_mut("rent_sponsor")?) so write_mode_rent_sponsor is obtained as
a mutable account; keep the surrounding conditional (config.create_mint) and
leave the rest (CpiContextLightSystemAccounts::new(...) and the system_program
handling) unchanged.
| // Check for idempotent early exit - skip CPI and return success. | ||
| // create_mint must never use idempotent early exit (fee already charged). | ||
| if let Err(ref err) = result { | ||
| if is_idempotent_early_exit(err) { | ||
| if accounts_config.create_mint { | ||
| return Err(ErrorCode::MintActionMissingExecutingAccounts.into()); | ||
| } |
There was a problem hiding this comment.
Use a clearer error for create_mint idempotent early-exit.
Returning MintActionMissingExecutingAccounts here is misleading; a dedicated error variant (e.g., CreateMintIdempotentNotAllowed) would make failure causes obvious.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@programs/compressed-token/program/src/compressed_token/mint_action/processor.rs`
around lines 184 - 190, The current idempotent early-exit branch uses
ErrorCode::MintActionMissingExecutingAccounts which is misleading for
create_mint; add a new error variant (e.g., CreateMintIdempotentNotAllowed) to
the ErrorCode enum and return that when is_idempotent_early_exit(err) is true
and accounts_config.create_mint is set; update any error display/serialization
as needed and replace the existing return
Err(ErrorCode::MintActionMissingExecutingAccounts.into()) in the create_mint
branch with Err(ErrorCode::CreateMintIdempotentNotAllowed.into()) so the failure
reason is explicit (refer to is_idempotent_early_exit,
accounts_config.create_mint, and the create_mint code path).
The wrapper program doesn't include rent_sponsor in its CPI, so the error changes from 6035 (CpiContextSetNotUsable) to 20009 (account iterator parse failure).
Summary by CodeRabbit
New Features
Bug Fixes / Validation