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 @@ -31,7 +31,6 @@ const CompressibleExtensionInstructionDataLayout = struct([
]);

const CreateAssociatedTokenAccountInstructionDataLayout = struct([
u8('bump'),
option(CompressibleExtensionInstructionDataLayout, 'compressibleConfig'),
]);

Expand All @@ -50,7 +49,6 @@ export interface CompressibleConfig {
}

export interface CreateAssociatedCTokenAccountParams {
bump: number;
compressibleConfig?: CompressibleConfig;
}

Expand Down Expand Up @@ -93,14 +91,14 @@ export const DEFAULT_COMPRESSIBLE_CONFIG: CompressibleConfig = {
compressToAccountPubkey: null, // Required null for ATAs
};

function getAssociatedCTokenAddressAndBump(
function getAssociatedCTokenAddress(
owner: PublicKey,
mint: PublicKey,
): [PublicKey, number] {
): PublicKey {
return PublicKey.findProgramAddressSync(
[owner.toBuffer(), CTOKEN_PROGRAM_ID.toBuffer(), mint.toBuffer()],
CTOKEN_PROGRAM_ID,
);
)[0];
}

function encodeCreateAssociatedCTokenAccountData(
Expand All @@ -110,7 +108,6 @@ function encodeCreateAssociatedCTokenAccountData(
const buffer = Buffer.alloc(2000);
const len = CreateAssociatedTokenAccountInstructionDataLayout.encode(
{
bump: params.bump,
compressibleConfig: params.compressibleConfig || null,
},
buffer,
Expand Down Expand Up @@ -152,14 +149,10 @@ export function createAssociatedCTokenAccountInstruction(
configAccount: PublicKey = LIGHT_TOKEN_CONFIG,
rentPayerPda: PublicKey = LIGHT_TOKEN_RENT_SPONSOR,
): TransactionInstruction {
const [associatedTokenAccount, bump] = getAssociatedCTokenAddressAndBump(
owner,
mint,
);
const associatedTokenAccount = getAssociatedCTokenAddress(owner, mint);

const data = encodeCreateAssociatedCTokenAccountData(
{
bump,
compressibleConfig,
},
false,
Expand Down Expand Up @@ -213,14 +206,10 @@ export function createAssociatedCTokenAccountIdempotentInstruction(
configAccount: PublicKey = LIGHT_TOKEN_CONFIG,
rentPayerPda: PublicKey = LIGHT_TOKEN_RENT_SPONSOR,
): TransactionInstruction {
const [associatedTokenAccount, bump] = getAssociatedCTokenAddressAndBump(
owner,
mint,
);
const associatedTokenAccount = getAssociatedCTokenAddress(owner, mint);

const data = encodeCreateAssociatedCTokenAccountData(
{
bump,
compressibleConfig,
},
true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use crate::{
#[repr(C)]
#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize, ZeroCopy)]
pub struct CreateAssociatedTokenAccountInstructionData {
pub bump: u8,
/// Optional compressible configuration for the token account
pub compressible_config: Option<CompressibleExtensionInstructionData>,
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@ use light_test_utils::{
},
Rpc, RpcError,
};
use light_token::instruction::{
derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount, CreateTokenAccount,
TransferFromSpl,
use light_token::{
instruction::{
derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount, CreateTokenAccount,
TransferFromSpl,
},
utils::get_associated_token_address_and_bump,
};
use light_token_interface::{
instructions::extensions::{CompressedOnlyExtensionInstructionData, ExtensionInstructionData},
Expand Down Expand Up @@ -74,7 +77,8 @@ async fn setup_ata_compressed_token(

// Create ATA with compression_only=true
let owner = Keypair::new();
let (ata_pubkey, ata_bump) = derive_token_ata(&owner.pubkey(), &mint_pubkey);
let (ata_pubkey, ata_bump) =
get_associated_token_address_and_bump(&owner.pubkey(), &mint_pubkey);

let create_ata_ix =
CreateAssociatedTokenAccount::new(payer.pubkey(), owner.pubkey(), mint_pubkey)
Expand Down Expand Up @@ -343,7 +347,7 @@ async fn test_ata_decompress_to_different_ata_fails() {
let mint2_pubkey = mint2_keypair.pubkey();

// Create ATA for same owner but different mint
let (ata2_pubkey, _ata2_bump) = derive_token_ata(&context.owner.pubkey(), &mint2_pubkey);
let ata2_pubkey = derive_token_ata(&context.owner.pubkey(), &mint2_pubkey);

let create_ata2_ix = CreateAssociatedTokenAccount::new(
context.payer.pubkey(),
Expand Down Expand Up @@ -1010,7 +1014,8 @@ async fn test_ata_multiple_compress_decompress_cycles() {

// Setup wallet owner and derive ATA
let wallet = Keypair::new();
let (ata_pubkey, ata_bump) = derive_token_ata(&wallet.pubkey(), &mint_pubkey);
let (ata_pubkey, ata_bump) =
get_associated_token_address_and_bump(&wallet.pubkey(), &mint_pubkey);

let amount1 = 100_000_000u64;
let amount2 = 200_000_000u64;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ async fn setup_burn_test() -> BurnTestContext {
let (mint_pda, _) = find_mint_address(&mint_seed.pubkey());

// Step 1: Create Light Token ATA for owner
let (ctoken_ata, _) = derive_token_ata(&owner_keypair.pubkey(), &mint_pda);
let ctoken_ata = derive_token_ata(&owner_keypair.pubkey(), &mint_pda);

let create_ata_ix =
CreateAssociatedTokenAccount::new(payer.pubkey(), owner_keypair.pubkey(), mint_pda)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,7 @@ async fn test_compress_and_close_owner_scenarios() {

// Set token balance on ATA
use light_token::instruction::derive_token_ata;
let (ata_pubkey, _bump) =
derive_token_ata(&context.owner_keypair.pubkey(), &context.mint_pubkey);
let ata_pubkey = derive_token_ata(&context.owner_keypair.pubkey(), &context.mint_pubkey);

let mut ata_account = context.rpc.get_account(ata_pubkey).await.unwrap().unwrap();

Expand Down
82 changes: 25 additions & 57 deletions program-tests/compressed-token-test/tests/light_token/create_ata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ async fn test_create_compressible_ata() {
.unwrap();

// Verify ATA was created at the expected address
let (expected_ata, _) = derive_token_ata(&owner_and_mint, &owner_and_mint);
let expected_ata = derive_token_ata(&owner_and_mint, &owner_and_mint);
let account = context.rpc.get_account(expected_ata).await.unwrap();
assert!(
account.is_some(),
Expand Down Expand Up @@ -419,8 +419,7 @@ async fn test_create_ata_failing() {

// Use different mint for this test
context.mint_pubkey = solana_sdk::pubkey::Pubkey::new_unique();
let (ata_pubkey, bump) =
derive_token_ata(&context.owner_keypair.pubkey(), &context.mint_pubkey);
let ata_pubkey = derive_token_ata(&context.owner_keypair.pubkey(), &context.mint_pubkey);

// Manually build instruction data with compress_to_account_pubkey (forbidden for ATAs)
let compress_to_pubkey = CompressToPubkey {
Expand All @@ -430,7 +429,6 @@ async fn test_create_ata_failing() {
};

let instruction_data = CreateAssociatedTokenAccountInstructionData {
bump,
compressible_config: Some(CompressibleExtensionInstructionData {
token_account_version: light_token_interface::state::TokenDataVersion::ShaFlat
as u8,
Expand Down Expand Up @@ -477,9 +475,9 @@ async fn test_create_ata_failing() {
light_program_test::utils::assert::assert_rpc_error(result, 0, 2).unwrap();
}

// Test 5: Invalid PDA derivation (wrong bump)
// ATAs must use the correct bump derived from [owner, program_id, mint]
// Error: 21 (ProgramFailedToComplete - provided seeds do not result in valid address)
// Test 5: Invalid ATA address (wrong PDA)
// Passing a wrong ATA address that doesn't match the derived PDA should fail.
// verify_pda uses find_program_address and returns InvalidAccountData (3) on mismatch.
{
use anchor_lang::prelude::borsh::BorshSerialize;
use light_token_interface::instructions::{
Expand All @@ -490,24 +488,15 @@ async fn test_create_ata_failing() {

// Use different mint for this test
context.mint_pubkey = solana_sdk::pubkey::Pubkey::new_unique();
let (ata_pubkey, correct_bump) =
derive_token_ata(&context.owner_keypair.pubkey(), &context.mint_pubkey);

// Manually build instruction data with WRONG bump
let wrong_bump = if correct_bump == 255 {
254
} else {
correct_bump + 1
};
// Use a wrong ATA address (random pubkey instead of derived)
let wrong_ata_pubkey = solana_sdk::pubkey::Pubkey::new_unique();

// Owner and mint are now passed as accounts, not in instruction data
let instruction_data = CreateAssociatedTokenAccountInstructionData {
bump: wrong_bump, // Wrong bump!
compressible_config: Some(CompressibleExtensionInstructionData {
token_account_version: light_token_interface::state::TokenDataVersion::ShaFlat
as u8,
rent_payment: 2,
compression_only: 1, // ATAs always compression_only
compression_only: 1,
write_top_up: 100,
compress_to_account_pubkey: None,
}),
Expand All @@ -516,7 +505,6 @@ async fn test_create_ata_failing() {
let mut data = vec![100]; // CreateAssociatedTokenAccount discriminator
instruction_data.serialize(&mut data).unwrap();

// Account order: owner, mint, payer, ata, system_program, config, rent_sponsor
let ix = Instruction {
program_id: light_compressed_token::ID,
accounts: vec![
Expand All @@ -526,7 +514,7 @@ async fn test_create_ata_failing() {
),
solana_sdk::instruction::AccountMeta::new_readonly(context.mint_pubkey, false),
solana_sdk::instruction::AccountMeta::new(payer_pubkey, true),
solana_sdk::instruction::AccountMeta::new(ata_pubkey, false),
solana_sdk::instruction::AccountMeta::new(wrong_ata_pubkey, false),
solana_sdk::instruction::AccountMeta::new_readonly(
solana_sdk::pubkey::Pubkey::default(),
false,
Expand All @@ -545,16 +533,8 @@ async fn test_create_ata_failing() {
.create_and_send_transaction(&[ix], &payer_pubkey, &[&context.payer])
.await;

// Wrong bump can trigger either ProgramFailedToComplete (21) or PrivilegeEscalation (19)
// depending on runtime state - accept either
let is_valid_error =
light_program_test::utils::assert::assert_rpc_error(result.clone(), 0, 21).is_ok()
|| light_program_test::utils::assert::assert_rpc_error(result, 0, 19).is_ok();

assert!(
is_valid_error,
"Expected either ProgramFailedToComplete (21) or PrivilegeEscalation (19)"
);
// Wrong ATA address is caught by verify_pda which returns InvalidAccountData (3)
light_program_test::utils::assert::assert_rpc_error(result, 0, 3).unwrap();
}

// Test 6: Invalid config account owner
Expand Down Expand Up @@ -706,11 +686,7 @@ async fn test_create_ata_failing() {

// Test 10: Arbitrary keypair address instead of correct PDA (non-IDEMPOTENT)
// Tests that providing an arbitrary address (not the correct PDA) fails.
// Currently fails with PrivilegeEscalation (19) at CreateAccount CPI because
// the program tries to sign for a PDA but the account address doesn't match.
// With proper validation (calling validate_ata_derivation in non-IDEMPOTENT mode),
// this would fail earlier with InvalidAccountData (17).
// Error: 19 (PrivilegeEscalation - CPI tries to sign for wrong address)
// verify_pda uses find_program_address and returns InvalidAccountData (3) on mismatch.
{
use anchor_lang::prelude::borsh::BorshSerialize;
use light_token_interface::instructions::create_associated_token_account::CreateAssociatedTokenAccountInstructionData;
Expand All @@ -719,18 +695,13 @@ async fn test_create_ata_failing() {
// Use different mint for this test
context.mint_pubkey = solana_sdk::pubkey::Pubkey::new_unique();

// Get the correct PDA and bump
let (_correct_ata_pubkey, correct_bump) =
derive_token_ata(&context.owner_keypair.pubkey(), &context.mint_pubkey);

// Create an arbitrary keypair (NOT the correct PDA)
let fake_ata_keypair = solana_sdk::signature::Keypair::new();
let fake_ata_pubkey = fake_ata_keypair.pubkey();

// Build instruction with correct bump but WRONG address (arbitrary keypair)
// Build instruction with WRONG address (arbitrary keypair)
// No compressible config for non-compressible ATAs
let instruction_data = CreateAssociatedTokenAccountInstructionData {
bump: correct_bump, // Correct bump for the real PDA
compressible_config: None,
};

Expand Down Expand Up @@ -766,10 +737,8 @@ async fn test_create_ata_failing() {
.create_and_send_transaction(&[ix], &payer_pubkey, &[&context.payer])
.await;

// Fails with PrivilegeEscalation (19) - program tries to invoke_signed with
// seeds that derive to the correct PDA, but the account passed is a different address.
// Solana runtime rejects this as unauthorized signer privilege escalation.
light_program_test::utils::assert::assert_rpc_error(result, 0, 19).unwrap();
// Wrong ATA address is caught by verify_pda which returns InvalidAccountData (3)
light_program_test::utils::assert::assert_rpc_error(result, 0, 3).unwrap();
}

// Test 11: Non-compressible ATA for mint with restricted extensions
Expand Down Expand Up @@ -799,11 +768,10 @@ async fn test_create_ata_failing() {
let owner = solana_sdk::pubkey::Pubkey::new_unique();

// Derive ATA address
let (ata_pubkey, bump) = derive_token_ata(&owner, &mint_with_restricted_ext);
let ata_pubkey = derive_token_ata(&owner, &mint_with_restricted_ext);

// Build instruction data with compressible_config: None (non-compressible)
let instruction_data = CreateAssociatedTokenAccountInstructionData {
bump,
compressible_config: None, // Non-compressible!
};

Expand Down Expand Up @@ -955,9 +923,9 @@ async fn test_ata_multiple_mints_same_owner() {
assert_ne!(ata2, ata3, "ATA for mint2 and mint3 should be different");

// Verify each ATA is derived correctly for its mint
let (expected_ata1, _) = derive_token_ata(&owner, &mint1);
let (expected_ata2, _) = derive_token_ata(&owner, &mint2);
let (expected_ata3, _) = derive_token_ata(&owner, &mint3);
let expected_ata1 = derive_token_ata(&owner, &mint1);
let expected_ata2 = derive_token_ata(&owner, &mint2);
let expected_ata3 = derive_token_ata(&owner, &mint3);

assert_eq!(ata1, expected_ata1, "ATA1 should match expected derivation");
assert_eq!(ata2, expected_ata2, "ATA2 should match expected derivation");
Expand Down Expand Up @@ -1010,7 +978,7 @@ async fn test_ata_multiple_owners_same_mint() {
.await
.unwrap();

let (ata1, _) = derive_token_ata(&owner1, &mint);
let ata1 = derive_token_ata(&owner1, &mint);

// Assert ATA1 was created correctly
assert_create_associated_token_account(
Expand All @@ -1033,7 +1001,7 @@ async fn test_ata_multiple_owners_same_mint() {
.await
.unwrap();

let (ata2, _) = derive_token_ata(&owner2, &mint);
let ata2 = derive_token_ata(&owner2, &mint);

// Assert ATA2 was created correctly
assert_create_associated_token_account(
Expand All @@ -1056,7 +1024,7 @@ async fn test_ata_multiple_owners_same_mint() {
.await
.unwrap();

let (ata3, _) = derive_token_ata(&owner3, &mint);
let ata3 = derive_token_ata(&owner3, &mint);

// Assert ATA3 was created correctly
assert_create_associated_token_account(
Expand All @@ -1074,9 +1042,9 @@ async fn test_ata_multiple_owners_same_mint() {
assert_ne!(ata2, ata3, "ATA for owner2 and owner3 should be different");

// Verify each ATA is derived correctly for its owner
let (expected_ata1, _) = derive_token_ata(&owner1, &mint);
let (expected_ata2, _) = derive_token_ata(&owner2, &mint);
let (expected_ata3, _) = derive_token_ata(&owner3, &mint);
let expected_ata1 = derive_token_ata(&owner1, &mint);
let expected_ata2 = derive_token_ata(&owner2, &mint);
let expected_ata3 = derive_token_ata(&owner3, &mint);

assert_eq!(ata1, expected_ata1, "ATA1 should match expected derivation");
assert_eq!(ata2, expected_ata2, "ATA2 should match expected derivation");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ async fn create_and_assert_ata2(
let payer_pubkey = context.payer.pubkey();
let owner_pubkey = context.owner_keypair.pubkey();

let (ata_pubkey, bump) = derive_token_ata(&owner_pubkey, &context.mint_pubkey);
let ata_pubkey = derive_token_ata(&owner_pubkey, &context.mint_pubkey);

let create_ata_ix = if let Some(compressible) = compressible_data.as_ref() {
let compressible_params = CompressibleParams {
Expand All @@ -37,7 +37,6 @@ async fn create_and_assert_ata2(
// Create non-compressible account
let mut builder = CreateAssociatedTokenAccount {
idempotent: false,
bump,
payer: payer_pubkey,
owner: owner_pubkey,
mint: context.mint_pubkey,
Expand Down
Loading