Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion js/compressed-token/src/v3/instructions/mint-to.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface CreateMintToInstructionParams {
amount: number | bigint;
/** Mint authority (must be signer) */
authority: PublicKey;
/** Maximum lamports for rent and top-up combined. Transaction fails if exceeded. (0 = no limit) */
/** Maximum lamports for rent and top-up combined. Transaction fails if exceeded. (u16::MAX = no limit, 0 = no top-ups allowed) */
maxTopUp?: number;
/** Optional fee payer for rent top-ups. If not provided, authority pays. */
feePayer?: PublicKey;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ impl MintActionCompressedInstructionData {
leaf_index: mint_with_context.leaf_index,
prove_by_index: mint_with_context.prove_by_index,
root_index: mint_with_context.root_index,
max_top_up: 0, // No limit by default
max_top_up: u16::MAX, // No limit by default
create_mint: None,
actions: Vec::new(),
proof,
Expand All @@ -47,7 +47,7 @@ impl MintActionCompressedInstructionData {
leaf_index: 0, // New mint has no existing leaf
prove_by_index: false, // Using address proof, not validity proof
root_index: address_merkle_tree_root_index,
max_top_up: 0, // No limit by default
max_top_up: u16::MAX, // No limit by default
create_mint: Some(CreateMint::default()),
actions: Vec::new(),
proof: Some(proof),
Expand All @@ -66,7 +66,7 @@ impl MintActionCompressedInstructionData {
leaf_index: 0, // New mint has no existing leaf
prove_by_index: false, // Using address proof, not validity proof
root_index: address_merkle_tree_root_index,
max_top_up: 0, // No limit by default
max_top_up: u16::MAX, // No limit by default
create_mint: Some(CreateMint::default()),
actions: Vec::new(),
proof: None, // Proof is verified with execution not write
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub struct MintActionCompressedInstructionData {
/// If mint already exists, root index of validity proof
/// If proof by index not used.
pub root_index: u16,
/// Maximum lamports for rent and top-up combined. Transaction fails if exceeded. (0 = no limit)
/// Maximum lamports for rent and top-up combined. Transaction fails if exceeded. (u16::MAX = no limit, 0 = no top-ups allowed)
pub max_top_up: u16,
pub create_mint: Option<CreateMint>,
pub actions: Vec<Action>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub struct CompressedTokenInstructionDataTransfer2 {
/// Placeholder currently unimplemented.
pub lamports_change_account_owner_index: u8,
pub output_queue: u8,
/// Maximum lamports for rent and top-up combined. Transaction fails if exceeded. (0 = no limit)
/// Maximum lamports for rent and top-up combined. Transaction fails if exceeded. (u16::MAX = no limit, 0 = no top-ups allowed)
pub max_top_up: u16,
pub cpi_context: Option<CompressedCpiContext>,
pub compressions: Option<Vec<Compression>>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,7 @@ async fn test_ata_decompress_with_mismatched_amount_fails() {
out_tlv: None,
compressions: Some(compressions),
cpi_context: None,
max_top_up: 0,
max_top_up: u16::MAX, // No limit on top-ups
};

// Serialize instruction data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ async fn test_transfer_requires_checked_for_restricted_extensions() {
destination: account_b_pubkey,
amount: transfer_amount,
authority: owner.pubkey(),
max_top_up: Some(0), // 0 = no limit, but includes system program for compressible
max_top_up: Some(u16::MAX), // u16::MAX = no limit, includes system program for compressible
fee_payer: None,
}
.instruction()
Expand All @@ -186,7 +186,7 @@ async fn test_transfer_requires_checked_for_restricted_extensions() {
amount: transfer_amount,
decimals: 9,
authority: owner.pubkey(),
max_top_up: Some(0), // 0 = no limit, but includes system program for compressible
max_top_up: Some(u16::MAX), // u16::MAX = no limit, includes system program for compressible
fee_payer: None,
}
.instruction()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ fn build_compressions_only_instruction(
out_tlv: None,
compressions,
cpi_context: None,
max_top_up: 0, // No limit
max_top_up: u16::MAX, // No limit
};

// Serialize instruction data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Key concepts integrated:
- `leaf_index`: u32 - Merkle tree leaf index of existing compressed mint (only used if create_mint is None)
- `prove_by_index`: bool - Use proof-by-index for existing mint validation (only used if create_mint is None)
- `root_index`: u16 - Root index for address proof (create) or validity proof (update). Not used if proof by index.
- `max_top_up`: u16 - Maximum lamports for rent and top-up combined, in units of 1,000 lamports (e.g., max_top_up=1 means 1,000 lamports, max_top_up=65535 means ~65.5M lamports). Transaction fails if exceeded. (0 = no limit)
- `max_top_up`: u16 - Maximum lamports for rent and top-up combined, in units of 1,000 lamports (e.g., max_top_up=1 means 1,000 lamports, max_top_up=65535 means ~65.5M lamports). Transaction fails if exceeded. (u16::MAX = no limit, 0 = no top-ups allowed)
- `create_mint`: Option<CreateMint> - Configuration for creating new compressed mint (None for existing mint operations)
- `actions`: Vec<Action> - Ordered list of actions to execute
- `proof`: Option<CompressedProof> - ZK proof for compressed account validation (required unless prove_by_index=true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
- `lamports_change_account_merkle_tree_index`: u8 - Merkle tree index for lamport change account (placeholder, unimplemented)
- `lamports_change_account_owner_index`: u8 - Owner index for lamport change account (placeholder, unimplemented)
- `output_queue`: u8 - Output queue index for compressed account outputs
- `max_top_up`: u16 - Maximum lamports for rent and top-up combined, in units of 1,000 lamports (e.g., max_top_up=1 means 1,000 lamports, max_top_up=65535 means ~65.5M lamports). Transaction fails if exceeded. (0 = no limit)
- `max_top_up`: u16 - Maximum lamports for rent and top-up combined, in units of 1,000 lamports (e.g., max_top_up=1 means 1,000 lamports, max_top_up=65535 means ~65.5M lamports). Transaction fails if exceeded. (u16::MAX = no limit, 0 = no top-ups allowed)
- `cpi_context`: Optional CompressedCpiContext - Required for CPI operations; write mode: set either first_set_context or set_context (not both); execute mode: provide with all flags false
- `compressions`: Optional Vec<Compression> - Compress/decompress operations
- `proof`: Optional CompressedProof - Required for ZK validation of compressed inputs; not needed for proof by index or when no compressed inputs exist
Expand Down
14 changes: 7 additions & 7 deletions programs/compressed-token/program/docs/ctoken/APPROVE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ If the CToken account has a compressible extension and requires a rent top-up, t
- **NOT SPL-compatible (system program required):** Compressible accounts that need rent top-up based on current slot

**description:**
Delegates a specified amount to a delegate authority on a decompressed ctoken account (account layout `CToken` defined in program-libs/token-interface/src/state/ctoken/ctoken_struct.rs). After the SPL approve operation, automatically tops up compressible accounts (extension layout `CompressionInfo` defined in program-libs/compressible/src/compression_info.rs) with additional lamports if needed to prevent accounts from becoming compressible during normal operations. The instruction supports a max_top_up parameter (0 = no limit) that enforces transaction failure if the calculated top-up exceeds this limit. Uses pinocchio-token-program for SPL-compatible approve semantics. Supports backwards-compatible instruction data format (8 bytes legacy vs 10 bytes with max_top_up).
Delegates a specified amount to a delegate authority on a decompressed ctoken account (account layout `CToken` defined in program-libs/token-interface/src/state/ctoken/ctoken_struct.rs). After the SPL approve operation, automatically tops up compressible accounts (extension layout `CompressionInfo` defined in program-libs/compressible/src/compression_info.rs) with additional lamports if needed to prevent accounts from becoming compressible during normal operations. The instruction supports a max_top_up parameter (u16::MAX = no limit, 0 = no top-ups allowed) that enforces transaction failure if the calculated top-up exceeds this limit. Uses pinocchio-token-program for SPL-compatible approve semantics. Supports backwards-compatible instruction data format (8 bytes legacy vs 10 bytes with max_top_up).

**Instruction data:**
Path: programs/compressed-token/program/src/ctoken/approve_revoke.rs (lines 14-15, 98-106)

- Bytes 0-7: `amount` (u64, little-endian) - Number of tokens to delegate
- Bytes 8-9 (optional): `max_top_up` (u16, little-endian) - Maximum lamports for top-up in units of 1,000 lamports (e.g., max_top_up=1 means 1,000 lamports, max_top_up=65535 means ~65.5M lamports). 0 = no limit, default for legacy format.
- Bytes 8-9 (optional): `max_top_up` (u16, little-endian) - Maximum lamports for top-up in units of 1,000 lamports (e.g., max_top_up=1 means 1,000 lamports, max_top_up=65535 means ~65.5M lamports). u16::MAX = no limit (default for legacy format), 0 = no top-ups allowed.

**Accounts:**
1. source
Expand Down Expand Up @@ -57,13 +57,13 @@ Path: programs/compressed-token/program/src/ctoken/approve_revoke.rs (lines 14-1

4. **Process compressible top-up (cold path):**
- Parse max_top_up from instruction data:
- If 8 bytes: legacy format, set max_top_up = 0 (no limit)
- If 8 bytes: legacy format, set max_top_up = u16::MAX (no limit)
- If 10 bytes: parse max_top_up from last 2 bytes
- Return InvalidInstructionData for any other length
- Read CompressionInfo directly from account bytes using bytemuck (no full CToken deserialization)
- Calculate transfer_amount using `top_up_lamports_from_account_info_unchecked`
- If transfer_amount > 0:
- If max_top_up > 0 and transfer_amount > max_top_up: return MaxTopUpExceeded
- If max_top_up != u16::MAX and transfer_amount > max_top_up: return MaxTopUpExceeded
- Get payer account (index 2), return MissingPayer if not present
- Transfer lamports from payer to source via CPI
Comment on lines 64 to 68
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Step 4 bullet omits the 1,000-lamport unit conversion present in the actual check.

Line 66 states the condition as transfer_amount > max_top_up, but the real enforcement (shown in your own code example at line 117) is transfer_amount > (max_top_up as u64).saturating_mul(1000). This mismatch between the prose and the code snippet within the same doc can mislead readers.

Consider updating the bullet to:

-     - If max_top_up != u16::MAX and transfer_amount > max_top_up: return MaxTopUpExceeded
+     - If max_top_up != u16::MAX and transfer_amount > max_top_up * 1000: return MaxTopUpExceeded

As per coding guidelines: "Verify that all function signatures, struct definitions, and behavior described in the documentation accurately match the actual implementation."

🤖 Prompt for AI Agents
In `@programs/compressed-token/program/docs/ctoken/APPROVE.md` around lines 64 -
68, Update the Step 4 bullet to match the actual check: when comparing
transfer_amount against max_top_up, include the 1,000-lamport unit conversion
used in the code (i.e., compare transfer_amount > (max_top_up as
u64).saturating_mul(1000)) and preserve the u16::MAX bypass check; reference the
same symbols top_up_lamports_from_account_info_unchecked, transfer_amount, and
max_top_up so the prose mirrors the implementation exactly.


Expand Down Expand Up @@ -114,7 +114,7 @@ let transfer_amount = top_up_lamports_from_account_info_unchecked(account, &mut

if transfer_amount > 0 {
// max_top_up is in units of 1,000 lamports (max ~65.5M lamports).
if max_top_up > 0 && transfer_amount > (max_top_up as u64).saturating_mul(1000) {
if max_top_up != u16::MAX && transfer_amount > (max_top_up as u64).saturating_mul(1000) {
return Err(CTokenError::MaxTopUpExceeded.into());
}
let payer = payer.ok_or(CTokenError::MissingPayer)?;
Expand All @@ -130,12 +130,12 @@ if transfer_amount > 0 {

Extended instruction data format (10 bytes total):
- Bytes 0-7: amount (u64)
- Bytes 8-9: max_top_up (u16, 0 = no limit)
- Bytes 8-9: max_top_up (u16, u16::MAX = no limit, 0 = no top-ups allowed)

**Enforcement**:
```rust
// max_top_up is in units of 1,000 lamports (max ~65.5M lamports).
if max_top_up > 0 && transfer_amount > (max_top_up as u64).saturating_mul(1000) {
if max_top_up != u16::MAX && transfer_amount > (max_top_up as u64).saturating_mul(1000) {
return Err(CTokenError::MaxTopUpExceeded.into());
}
```
Expand Down
8 changes: 4 additions & 4 deletions programs/compressed-token/program/docs/ctoken/BURN.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
**path:** programs/compressed-token/program/src/ctoken/burn.rs

**description:**
Burns tokens from a decompressed CToken account and decreases the CMint supply, fully compatible with SPL Token burn semantics. Account layout `CToken` is defined in `program-libs/token-interface/src/state/ctoken/ctoken_struct.rs`. Account layout `CompressedMint` (CMint) is defined in `program-libs/token-interface/src/state/mint/compressed_mint.rs`. Extension layout `CompressionInfo` is defined in `program-libs/compressible/src/compression_info.rs` and is embedded in both CToken and CMint structs. Uses pinocchio-token-program to process the burn (handles balance/supply updates, authority check, frozen check). After the burn, automatically tops up compressible accounts with additional lamports if needed. Top-up is calculated for both CMint and source CToken based on current slot and account balance. Top-up prevents accounts from becoming compressible during normal operations. Enforces max_top_up limit if provided (transaction fails if exceeded). Supports max_top_up parameter to limit rent top-up costs (0 = no limit). Instruction data is backwards-compatible: 8-byte format (legacy, no max_top_up enforcement) and 10-byte format (with max_top_up). This instruction only works with CMints (compressed mints). CMints do not support restricted Token-2022 extensions (Pausable, TransferFee, TransferHook, PermanentDelegate, DefaultAccountState) - only TokenMetadata is allowed. To burn tokens from spl or T22 mints, use Transfer2 with decompress mode to convert to SPL tokens first, then burn via SPL Token-2022.
Burns tokens from a decompressed CToken account and decreases the CMint supply, fully compatible with SPL Token burn semantics. Account layout `CToken` is defined in `program-libs/token-interface/src/state/ctoken/ctoken_struct.rs`. Account layout `CompressedMint` (CMint) is defined in `program-libs/token-interface/src/state/mint/compressed_mint.rs`. Extension layout `CompressionInfo` is defined in `program-libs/compressible/src/compression_info.rs` and is embedded in both CToken and CMint structs. Uses pinocchio-token-program to process the burn (handles balance/supply updates, authority check, frozen check). After the burn, automatically tops up compressible accounts with additional lamports if needed. Top-up is calculated for both CMint and source CToken based on current slot and account balance. Top-up prevents accounts from becoming compressible during normal operations. Enforces max_top_up limit if provided (transaction fails if exceeded). Supports max_top_up parameter to limit rent top-up costs (u16::MAX = no limit, 0 = no top-ups allowed). Instruction data is backwards-compatible: 8-byte format (legacy, no max_top_up enforcement) and 10-byte format (with max_top_up). This instruction only works with CMints (compressed mints). CMints do not support restricted Token-2022 extensions (Pausable, TransferFee, TransferHook, PermanentDelegate, DefaultAccountState) - only TokenMetadata is allowed. To burn tokens from spl or T22 mints, use Transfer2 with decompress mode to convert to SPL tokens first, then burn via SPL Token-2022.

**Instruction data:**

Expand All @@ -15,7 +15,7 @@ Format 1 (8 bytes, legacy):

Format 2 (10 bytes):
- Bytes 0-7: `amount` (u64, little-endian) - Number of tokens to burn
- Bytes 8-9: `max_top_up` (u16, little-endian) - Maximum lamports for combined CMint + CToken top-ups in units of 1,000 lamports (e.g., max_top_up=1 means 1,000 lamports, max_top_up=65535 means ~65.5M lamports). 0 = no limit.
- Bytes 8-9: `max_top_up` (u16, little-endian) - Maximum lamports for combined CMint + CToken top-ups in units of 1,000 lamports (e.g., max_top_up=1 means 1,000 lamports, max_top_up=65535 means ~65.5M lamports). u16::MAX = no limit, 0 = no top-ups allowed.

**Accounts:**
1. source CToken
Expand Down Expand Up @@ -53,7 +53,7 @@ Format 2 (10 bytes):
2. **Parse instruction data:**
- Require at least 8 bytes for amount
- Parse max_top_up:
- If instruction_data.len() == 8: max_top_up = 0 (no limit, legacy format)
- If instruction_data.len() == 8: max_top_up = u16::MAX (no limit, legacy format)
- If instruction_data.len() == 10: parse u16 from bytes 8-9 as max_top_up
- Otherwise: return InvalidInstructionData

Expand Down Expand Up @@ -95,7 +95,7 @@ Format 2 (10 bytes):
d. **Validate budget:**
- If no compressible accounts were found (current_slot == 0), exit early
- If both top-up amounts are 0, exit early
- If max_top_up != 0 and lamports_budget == 0, fail with MaxTopUpExceeded
- If max_top_up != u16::MAX and lamports_budget == 0, fail with MaxTopUpExceeded

e. **Execute transfers:**
- Fail with MissingPayer if payer account is not provided
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Format 1 (9 bytes, legacy):
Format 2 (11 bytes):
- Bytes 0-7: `amount` (u64, little-endian) - Number of tokens to burn
- Byte 8: `decimals` (u8) - Expected token decimals
- Bytes 9-10: `max_top_up` (u16, little-endian) - Maximum lamports for combined CMint + CToken top-ups in units of 1,000 lamports (e.g., max_top_up=1 means 1,000 lamports, max_top_up=65535 means ~65.5M lamports). 0 = no limit.
- Bytes 9-10: `max_top_up` (u16, little-endian) - Maximum lamports for combined CMint + CToken top-ups in units of 1,000 lamports (e.g., max_top_up=1 means 1,000 lamports, max_top_up=65535 means ~65.5M lamports). u16::MAX = no limit, 0 = no top-ups allowed.

**Accounts:**
1. source CToken
Expand Down Expand Up @@ -56,7 +56,7 @@ Format 2 (11 bytes):
2. **Parse instruction data:**
- Require at least 9 bytes (amount + decimals)
- Parse max_top_up:
- If instruction_data.len() == 9: max_top_up = 0 (no limit, legacy format)
- If instruction_data.len() == 9: max_top_up = u16::MAX (no limit, legacy format)
- If instruction_data.len() == 11: parse u16 from bytes 9-10 as max_top_up
- Otherwise: return InvalidInstructionData

Expand Down Expand Up @@ -103,7 +103,7 @@ Format 2 (11 bytes):
d. **Validate budget:**
- If no compressible accounts were found (current_slot == 0), exit early
- If both top-up amounts are 0, exit early
- If max_top_up != 0 and lamports_budget == 0, fail with MaxTopUpExceeded
- If max_top_up != u16::MAX and lamports_budget == 0, fail with MaxTopUpExceeded
- If payer is None but top-up is needed, fail with MissingPayer

e. **Execute transfers:**
Expand Down
Loading