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
Original file line number Diff line number Diff line change
Expand Up @@ -988,8 +988,8 @@ async fn test_account_index_out_of_bounds() {
)
.await;

// Should fail with NotEnoughAccountKeys
assert_rpc_error(result, 0, 20014).unwrap();
// Should fail with TooManyCompressionTransfers (account index 99 >= 40)
assert_rpc_error(result, 0, 95).unwrap();
}

/// Test 16: Authority index out of bounds
Expand Down
1 change: 1 addition & 0 deletions programs/compressed-token/program/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub const LIGHT_CPI_SIGNER: CpiSigner =
derive_light_cpi_signer!("cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m");

pub const MAX_ACCOUNTS: usize = 30;
pub(crate) const MAX_PACKED_ACCOUNTS: usize = 40;

// Custom ctoken instructions start at 100 to skip spl-token program instrutions.
// When adding new instructions check anchor discriminators for collisions!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ pub fn process_mint_to_ctoken_action(
validated_accounts: &MintActionAccounts,
packed_accounts: &ProgramPackedAccounts<'_, AccountInfo>,
mint: Pubkey,
) -> Result<Option<u64>, ProgramError> {
transfer_amount: &mut u64,
) -> Result<(), ProgramError> {
check_authority(
compressed_mint.base.mint_authority,
validated_accounts.authority.key(),
Expand Down Expand Up @@ -55,7 +56,7 @@ pub fn process_mint_to_ctoken_action(
packed_accounts,
);

compress_or_decompress_ctokens(inputs)
compress_or_decompress_ctokens(inputs, transfer_amount)
}

#[profile]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,9 @@ use crate::{
convert_program_error,
transfer_lamports::{multi_transfer_lamports, Transfer},
},
MAX_PACKED_ACCOUNTS,
};

/// Maximum number of packed accounts allowed in a single instruction
const MAX_PACKED_ACCOUNTS: usize = 40;

#[allow(clippy::too_many_arguments)]
#[profile]
pub fn process_actions<'a>(
Expand Down Expand Up @@ -94,29 +92,23 @@ pub fn process_actions<'a>(
// compressed_mint.metadata.spl_mint_initialized = true;
}
ZAction::MintToCToken(mint_to_ctoken_action) => {
let transfer_amount = process_mint_to_ctoken_action(
let account_index = mint_to_ctoken_action.account_index as usize;
if account_index >= MAX_PACKED_ACCOUNTS {
msg!(
"Account index {} out of bounds, max {} allowed",
account_index,
MAX_PACKED_ACCOUNTS
);
return Err(ErrorCode::TooManyCompressionTransfers.into());
}
process_mint_to_ctoken_action(
mint_to_ctoken_action,
compressed_mint,
validated_accounts,
packed_accounts,
parsed_instruction_data.mint.metadata.mint,
&mut transfer_map[account_index],
)?;

// Accumulate transfer amount if present (deduplication happens here)
if let Some(amount) = transfer_amount {
let account_index = mint_to_ctoken_action.account_index;
if account_index as usize >= MAX_PACKED_ACCOUNTS {
msg!(
"Too many compression transfers: {}, max {} allowed",
account_index,
MAX_PACKED_ACCOUNTS
);
return Err(ErrorCode::TooManyCompressionTransfers.into());
}
transfer_map[account_index as usize] = transfer_map[account_index as usize]
.checked_add(amount)
.ok_or(ProgramError::ArithmeticOverflow)?;
}
}
ZAction::UpdateMetadataField(update_metadata_action) => {
process_update_metadata_field_action(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{
transfer2::accounts::Transfer2Accounts,
};

/// Process compress and close operation for a ctoken account
/// Process compress and close operation for a ctoken account.
#[profile]
pub fn process_compress_and_close(
authority: Option<&AccountInfo>,
Expand All @@ -27,7 +27,7 @@ pub fn process_compress_and_close(
token_account_info: &AccountInfo,
ctoken: &mut ZCompressedTokenMut,
packed_accounts: &ProgramPackedAccounts<'_, AccountInfo>,
) -> Result<Option<u64>, ProgramError> {
) -> Result<(), ProgramError> {
let authority = authority.ok_or(ErrorCode::CompressAndCloseAuthorityMissing)?;
check_signer(authority).map_err(|e| {
anchor_lang::solana_program::msg!("Authority signer check failed: {:?}", e);
Expand Down Expand Up @@ -64,7 +64,7 @@ pub fn process_compress_and_close(
}

*ctoken.amount = 0.into();
Ok(None)
Ok(())
}

/// Validate compressed token account for compress and close operation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ use spl_pod::solana_msg::msg;
use super::{compress_and_close::process_compress_and_close, inputs::CTokenCompressionInputs};
use crate::shared::owner_validation::check_ctoken_owner;

/// Perform compression/decompression on a ctoken account
/// Perform compression/decompression on a ctoken account.
#[profile]
pub fn compress_or_decompress_ctokens(
inputs: CTokenCompressionInputs,
) -> Result<Option<u64>, ProgramError> {
transfer_amount: &mut u64,
) -> Result<(), ProgramError> {
let CTokenCompressionInputs {
authority,
compress_and_close_inputs,
Expand Down Expand Up @@ -78,6 +79,7 @@ pub fn compress_or_decompress_ctokens(
ctoken.extensions.as_deref(),
token_account_info,
&mut current_slot,
transfer_amount,
)
}
ZCompressionMode::Decompress => {
Expand All @@ -92,6 +94,7 @@ pub fn compress_or_decompress_ctokens(
ctoken.extensions.as_deref(),
token_account_info,
&mut current_slot,
transfer_amount,
)
}
ZCompressionMode::CompressAndClose => process_compress_and_close(
Expand All @@ -110,7 +113,12 @@ fn process_compressible_extension(
extensions: Option<&[ZExtensionStructMut]>,
token_account_info: &AccountInfo,
current_slot: &mut u64,
) -> Result<Option<u64>, ProgramError> {
transfer_amount: &mut u64,
) -> Result<(), ProgramError> {
if *transfer_amount != 0 {
return Ok(());
}

if let Some(extensions) = extensions {
for extension in extensions.iter() {
if let ZExtensionStructMut::Compressible(compressible_extension) = extension {
Expand All @@ -119,7 +127,7 @@ fn process_compressible_extension(
.map_err(|_| CTokenError::SysvarAccessError)?
.slot;
}
let transfer_amount = compressible_extension
*transfer_amount = compressible_extension
.calculate_top_up_lamports(
token_account_info.data_len() as u64,
*current_slot,
Expand All @@ -128,9 +136,9 @@ fn process_compressible_extension(
)
.map_err(|_| CTokenError::InvalidAccountData)?;

return Ok(Some(transfer_amount));
return Ok(());
}
}
}
Ok(None)
Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ pub use compress_and_close::close_for_compress_and_close;
pub use compress_or_decompress_ctokens::compress_or_decompress_ctokens;
pub use inputs::{CTokenCompressionInputs, CompressAndCloseInputs};

/// Process compression/decompression for ctoken accounts
/// Process compression/decompression for ctoken accounts.
#[profile]
pub(super) fn process_ctoken_compressions(
inputs: &ZCompressedTokenInstructionDataTransfer2,
compression: &ZCompression,
token_account_info: &AccountInfo,
packed_accounts: &ProgramPackedAccounts<'_, AccountInfo>,
) -> Result<Option<(u8, u64)>, anchor_lang::prelude::ProgramError> {
transfer_amount: &mut u64,
) -> Result<(), anchor_lang::prelude::ProgramError> {
// Validate compression fields for the given mode
validate_compression_mode_fields(compression)?;

Expand All @@ -34,8 +35,5 @@ pub(super) fn process_ctoken_compressions(
packed_accounts,
)?;

let transfer_amount = compress_or_decompress_ctokens(compression_inputs)?;

// Return account index and amount if there's a transfer needed
Ok(transfer_amount.map(|amount| (compression.source_or_recipient, amount)))
compress_or_decompress_ctokens(compression_inputs, transfer_amount)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::{
convert_program_error,
transfer_lamports::{multi_transfer_lamports, Transfer},
},
LIGHT_CPI_SIGNER,
LIGHT_CPI_SIGNER, MAX_PACKED_ACCOUNTS,
};

pub mod ctoken;
Expand All @@ -38,21 +38,31 @@ pub fn process_token_compression(
cpi_authority: &AccountInfo,
) -> Result<(), ProgramError> {
if let Some(compressions) = inputs.compressions.as_ref() {
// Array to accumulate transfer amounts by account index (max 40 packed accounts)
let mut transfer_map = [0u64; 40];
let mut transfer_map = [0u64; MAX_PACKED_ACCOUNTS];

for compression in compressions {
let account_index = compression.source_or_recipient as usize;
if account_index >= MAX_PACKED_ACCOUNTS {
msg!(
"Account index {} out of bounds, max {} allowed",
account_index,
MAX_PACKED_ACCOUNTS
);
return Err(ErrorCode::TooManyCompressionTransfers.into());
}

let source_or_recipient = packed_accounts.get_u8(
compression.source_or_recipient,
"compression source or recipient",
)?;

let transfer = match source_or_recipient.owner() {
match source_or_recipient.owner() {
ID => ctoken::process_ctoken_compressions(
inputs,
compression,
source_or_recipient,
packed_accounts,
&mut transfer_map[account_index],
)?,
SPL_TOKEN_ID => {
spl::process_spl_compressions(
Expand All @@ -62,8 +72,6 @@ pub fn process_token_compression(
packed_accounts,
cpi_authority,
)?;
// SPL token compressions don't require lamport transfers for compressible extension´
None
}
SPL_TOKEN_2022_ID => {
spl::process_spl_compressions(
Expand All @@ -73,8 +81,6 @@ pub fn process_token_compression(
packed_accounts,
cpi_authority,
)?;
// SPL token compressions don't require lamport transfers for compressible extension´
None
}
_ => {
msg!(
Expand All @@ -88,24 +94,9 @@ pub fn process_token_compression(
return Err(ProgramError::InvalidInstructionData);
}
};

// Accumulate transfer amount if present
if let Some((account_index, amount)) = transfer {
if account_index >= 40 {
msg!(
"Too many compression transfers: {}, max 40 allowed",
account_index
);
return Err(ErrorCode::TooManyCompressionTransfers.into());
}
transfer_map[account_index as usize] = transfer_map[account_index as usize]
.checked_add(amount)
.ok_or(ProgramError::ArithmeticOverflow)?;
}
}

// Build rent_return_transfers & top up array from accumulated amounts
let transfers: ArrayVec<Transfer, 40> = transfer_map
let transfers: ArrayVec<Transfer, MAX_PACKED_ACCOUNTS> = transfer_map
.iter()
.enumerate()
.filter_map(|(index, &amount)| {
Expand All @@ -121,7 +112,7 @@ pub fn process_token_compression(
amount,
})
})
.collect::<Result<ArrayVec<Transfer, 40>, ProgramError>>()?;
.collect::<Result<ArrayVec<Transfer, MAX_PACKED_ACCOUNTS>, ProgramError>>()?;

if !transfers.is_empty() {
multi_transfer_lamports(fee_payer, &transfers).map_err(convert_program_error)?
Expand Down