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
41 changes: 41 additions & 0 deletions program-libs/ctoken-types/src/state/ctoken/zero_copy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,47 @@ impl<'a> ZeroCopyAt<'a> for CToken {
}
}

impl CToken {
/// Zero-copy deserialization with initialization check.
/// Returns an error if the account is not initialized (byte 108 must be 1).
#[profile]
pub fn zero_copy_at_checked(
bytes: &[u8],
) -> Result<(ZCToken<'_>, &[u8]), crate::error::CTokenError> {
// Check minimum size for state field at byte 108
if bytes.len() < 109 {
return Err(crate::error::CTokenError::InvalidAccountData);
}

// Verify account is initialized (state byte at offset 108 must be 1)
if bytes[108] != 1 {
return Err(crate::error::CTokenError::InvalidAccountState);
}

// Proceed with normal deserialization
Ok(CToken::zero_copy_at(bytes)?)
}

/// Mutable zero-copy deserialization with initialization check.
/// Returns an error if the account is not initialized (byte 108 must be 1).
#[profile]
pub fn zero_copy_at_mut_checked(
bytes: &mut [u8],
) -> Result<(ZCompressedTokenMut<'_>, &mut [u8]), crate::error::CTokenError> {
// Check minimum size for state field at byte 108
if bytes.len() < 109 {
return Err(crate::error::CTokenError::InvalidAccountData);
}

// Verify account is initialized (state byte at offset 108 must be 1)
if bytes[108] != 1 {
return Err(crate::error::CTokenError::InvalidAccountState);
}

Ok(CToken::zero_copy_at_mut(bytes)?)
}
}

impl<'a> ZeroCopyAtMut<'a> for CToken {
type ZeroCopyAtMut = ZCompressedTokenMut<'a>;

Expand Down
79 changes: 78 additions & 1 deletion program-libs/ctoken-types/tests/ctoken/failing.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use light_ctoken_types::state::{CToken, CompressedTokenConfig};
use light_ctoken_types::{
error::CTokenError,
state::{CToken, CompressedTokenConfig},
};
use light_zero_copy::ZeroCopyNew;

#[test]
Expand All @@ -17,3 +20,77 @@ fn test_compressed_token_new_zero_copy_buffer_too_small() {
// Should fail with size error
assert!(result.is_err());
}

#[test]
fn test_zero_copy_at_checked_uninitialized_account() {
// Create a 165-byte buffer with all zeros (byte 108 = 0, uninitialized)
let buffer = vec![0u8; 165];

// This should fail because byte 108 is 0 (not initialized)
let result = CToken::zero_copy_at_checked(&buffer);

// Assert it returns InvalidAccountState error
assert!(matches!(result, Err(CTokenError::InvalidAccountState)));
}

#[test]
fn test_zero_copy_at_mut_checked_uninitialized_account() {
// Create a 165-byte mutable buffer with all zeros
let mut buffer = vec![0u8; 165];

// This should fail because byte 108 is 0 (not initialized)
let result = CToken::zero_copy_at_mut_checked(&mut buffer);

// Assert it returns InvalidAccountState error
assert!(matches!(result, Err(CTokenError::InvalidAccountState)));
}

#[test]
fn test_zero_copy_at_checked_frozen_account() {
// Create a 165-byte buffer with byte 108 = 2 (AccountState::Frozen)
let mut buffer = vec![0u8; 165];
buffer[108] = 2; // AccountState::Frozen

// This should fail because byte 108 is 2 (frozen, not initialized)
let result = CToken::zero_copy_at_checked(&buffer);

// Assert it returns InvalidAccountState error
assert!(matches!(result, Err(CTokenError::InvalidAccountState)));
}

#[test]
fn test_zero_copy_at_mut_checked_frozen_account() {
// Create a 165-byte mutable buffer with byte 108 = 2
let mut buffer = vec![0u8; 165];
buffer[108] = 2; // AccountState::Frozen

// This should fail because byte 108 is 2 (frozen, not initialized)
let result = CToken::zero_copy_at_mut_checked(&mut buffer);

// Assert it returns InvalidAccountState error
assert!(matches!(result, Err(CTokenError::InvalidAccountState)));
}

#[test]
fn test_zero_copy_at_checked_buffer_too_small() {
// Create a 100-byte buffer (less than 109 bytes minimum)
let buffer = vec![0u8; 100];

// This should fail because buffer is too small
let result = CToken::zero_copy_at_checked(&buffer);

// Assert it returns InvalidAccountData error
assert!(matches!(result, Err(CTokenError::InvalidAccountData)));
}

#[test]
fn test_zero_copy_at_mut_checked_buffer_too_small() {
// Create a 100-byte mutable buffer
let mut buffer = vec![0u8; 100];

// This should fail because buffer is too small
let result = CToken::zero_copy_at_mut_checked(&mut buffer);

// Assert it returns InvalidAccountData error
assert!(matches!(result, Err(CTokenError::InvalidAccountData)));
}
4 changes: 2 additions & 2 deletions program-tests/compressed-token-test/tests/ctoken/close.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ async fn test_close_token_account_fails() {
&owner_keypair,
Some(rent_sponsor),
"uninitialized_account",
10, // ProgramError::UninitializedAccount
18036, // CTokenError::InvalidAccountState
)
.await;
}
Expand Down Expand Up @@ -317,7 +317,7 @@ async fn test_close_token_account_fails() {
&owner_keypair,
Some(rent_sponsor),
"frozen_account",
76, // ErrorCode::AccountFrozen
18036, // CTokenError::InvalidAccountState
)
.await;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -840,8 +840,8 @@ async fn test_compress_and_close_output_validation_errors() {
.await;

// Assert that the transaction failed with account frozen error
// Error: AccountFrozen (76 = 0x4c)
light_program_test::utils::assert::assert_rpc_error(result, 0, 76).unwrap();
// Error: InvalidAccountState (18036)
light_program_test::utils::assert::assert_rpc_error(result, 0, 18036).unwrap();
}
}

Expand Down
8 changes: 5 additions & 3 deletions programs/compressed-token/program/src/claim.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
use anchor_compressed_token::ErrorCode;
use anchor_lang::prelude::ProgramError;
use light_account_checks::{AccountInfoTrait, AccountIterator};
use light_account_checks::{checks::check_owner, AccountInfoTrait, AccountIterator};
use light_compressible::{compression_info::ClaimAndUpdate, config::CompressibleConfig};
use light_ctoken_types::state::{CToken, ZExtensionStructMut};
use light_program_profiler::profile;
use light_zero_copy::traits::ZeroCopyAtMut;
use pinocchio::{account_info::AccountInfo, sysvars::Sysvar};
use spl_pod::solana_msg::msg;

Expand Down Expand Up @@ -96,13 +95,16 @@ fn validate_and_claim(
token_account: &AccountInfo,
current_slot: u64,
) -> Result<Option<u64>, ProgramError> {
// Verify the token account is owned by the compressed token program
check_owner(&crate::LIGHT_CPI_SIGNER.program_id, token_account)?;

// Get current lamports balance
let current_lamports = AccountInfoTrait::lamports(token_account);
// Claim rent for completed epochs
let bytes = token_account.data_len() as u64;
// Parse and process the token account
let mut token_account_data = AccountInfoTrait::try_borrow_mut_data(token_account)?;
let (mut compressed_token, _) = CToken::zero_copy_at_mut(&mut token_account_data)?;
let (mut compressed_token, _) = CToken::zero_copy_at_mut_checked(&mut token_account_data)?;

// Find compressible extension
if let Some(extensions) = compressed_token.extensions.as_mut() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use light_account_checks::{checks::check_signer, AccountInfoTrait};
use light_compressible::rent::{get_rent_exemption_lamports, AccountRentState};
use light_ctoken_types::state::{CToken, ZCompressedTokenMut, ZExtensionStructMut};
use light_program_profiler::profile;
use light_zero_copy::traits::{ZeroCopyAt, ZeroCopyAtMut};
use pinocchio::account_info::AccountInfo;
#[cfg(target_os = "solana")]
use pinocchio::sysvars::Sysvar;
Expand All @@ -26,7 +25,7 @@ pub fn process_close_token_account(
// Try to parse as CToken using zero-copy deserialization
let token_account_data =
&mut AccountInfoTrait::try_borrow_mut_data(accounts.token_account)?;
let (ctoken, _) = CToken::zero_copy_at_mut(token_account_data)?;
let (ctoken, _) = CToken::zero_copy_at_mut_checked(token_account_data)?;
validate_token_account_close_instruction(&accounts, &ctoken)?;
}
close_token_account(&accounts)?;
Expand Down Expand Up @@ -156,7 +155,7 @@ pub fn distribute_lamports(accounts: &CloseTokenAccountAccounts<'_>) -> Result<(
// Check for compressible extension and handle lamport distribution

let token_account_data = AccountInfoTrait::try_borrow_data(accounts.token_account)?;
let (ctoken, _) = CToken::zero_copy_at(&token_account_data)?;
let (ctoken, _) = CToken::zero_copy_at_checked(&token_account_data)?;

if let Some(extensions) = ctoken.extensions.as_ref() {
for extension in extensions {
Expand Down
3 changes: 1 addition & 2 deletions programs/compressed-token/program/src/ctoken_transfer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use light_ctoken_types::{
CTokenError,
};
use light_program_profiler::profile;
use light_zero_copy::traits::ZeroCopyAt;
use pinocchio::account_info::AccountInfo;
use pinocchio_token_program::processor::transfer::process_transfer;

Expand Down Expand Up @@ -60,7 +59,7 @@ fn calculate_and_execute_top_up_transfers(
.account
.try_borrow_data()
.map_err(convert_program_error)?;
let (token, _) = CToken::zero_copy_at(&account_data)?;
let (token, _) = CToken::zero_copy_at_checked(&account_data)?;
if let Some(extensions) = token.extensions.as_ref() {
for extension in extensions.iter() {
if let ZExtensionStruct::Compressible(compressible_extension) = extension {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ use light_ctoken_types::{
CTokenError,
};
use light_program_profiler::profile;
use light_zero_copy::traits::ZeroCopyAtMut;
use pinocchio::{
account_info::AccountInfo,
sysvars::{clock::Clock, Sysvar},
Expand Down Expand Up @@ -37,8 +36,7 @@ pub fn compress_or_decompress_ctokens(
.try_borrow_mut_data()
.map_err(|_| ProgramError::AccountBorrowFailed)?;

let (mut ctoken, _) = CToken::zero_copy_at_mut(&mut token_account_data)
.map_err(|_| ProgramError::InvalidAccountData)?;
let (mut ctoken, _) = CToken::zero_copy_at_mut_checked(&mut token_account_data)?;

if ctoken.mint.to_bytes() != mint {
msg!(
Expand Down
Loading