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
43 changes: 43 additions & 0 deletions program-tests/compressed-token-test/tests/ctoken/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -518,4 +518,47 @@ async fn test_create_compressible_token_account_failing() {
// Should fail with InvalidDiscriminator (20000) from account-checks
light_program_test::utils::assert::assert_rpc_error(result, 0, 20000).unwrap();
}

// Test 9: Non-signer custom rent payer (DoS prevention)
// Custom rent payer must be a signer to prevent setting executable accounts as rent_sponsor.
// This prevents DoS attacks where an attacker sets an executable account as rent_sponsor,
// making the token account impossible to close (lamport transfers to executable accounts fail).
// Error: 8 (MissingRequiredSignature)
{
context.token_account_keypair = Keypair::new();

// Use account compression program as custom rent payer (executable, cannot sign)
let executable_rent_payer = light_sdk::constants::ACCOUNT_COMPRESSION_PROGRAM_ID.into();

let compressible_params = CompressibleParams {
compressible_config: context.compressible_config,
rent_sponsor: executable_rent_payer, // Executable account!
pre_pay_num_epochs: 2,
lamports_per_write: Some(100),
compress_to_account_pubkey: None,
token_account_version: light_ctoken_interface::state::TokenDataVersion::ShaFlat,
};

let create_token_account_ix = CreateCTokenAccount::new(
payer_pubkey,
context.token_account_keypair.pubkey(),
context.mint_pubkey,
context.owner_keypair.pubkey(),
)
.with_compressible(compressible_params)
.instruction()
.unwrap();

let result = context
.rpc
.create_and_send_transaction(
&[create_token_account_ix],
&payer_pubkey,
&[&context.payer, &context.token_account_keypair],
)
.await;

// Should fail with MissingRequiredSignature (8)
light_program_test::utils::assert::assert_rpc_error(result, 0, 8).unwrap();
}
}
40 changes: 40 additions & 0 deletions program-tests/compressed-token-test/tests/ctoken/create_ata.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use light_sdk::constants::ACCOUNT_COMPRESSION_PROGRAM_ID;

use super::shared::*;

#[tokio::test]
Expand Down Expand Up @@ -558,6 +560,44 @@ async fn test_create_ata_failing() {
// Should fail with InvalidDiscriminator (20000) from account-checks
light_program_test::utils::assert::assert_rpc_error(result, 0, 20000).unwrap();
}

// Test 9: Non-signer custom rent payer (DoS prevention)
// Custom rent payer must be a signer to prevent setting executable accounts as rent_sponsor.
// This prevents DoS attacks where an attacker sets an executable account as rent_sponsor,
// making the token account impossible to close (lamport transfers to executable accounts fail).
// Error: 8 (MissingRequiredSignature)
{
context.mint_pubkey = solana_sdk::pubkey::Pubkey::new_unique();

// Use system program as custom rent payer (executable, cannot sign)
let executable_rent_payer = ACCOUNT_COMPRESSION_PROGRAM_ID.into();

let compressible_params = CompressibleParams {
compressible_config: context.compressible_config,
rent_sponsor: executable_rent_payer, // Executable account!
pre_pay_num_epochs: 2,
lamports_per_write: Some(100),
compress_to_account_pubkey: None,
token_account_version: light_ctoken_interface::state::TokenDataVersion::ShaFlat,
};

let create_ata_ix = CreateAssociatedCTokenAccount::new(
payer_pubkey,
context.owner_keypair.pubkey(),
context.mint_pubkey,
)
.with_compressible(compressible_params)
.instruction()
.unwrap();

let result = context
.rpc
.create_and_send_transaction(&[create_ata_ix], &payer_pubkey, &[&context.payer])
.await;

// Should fail with MissingRequiredSignature (8)
light_program_test::utils::assert::assert_rpc_error(result, 0, 8).unwrap();
}
}

#[tokio::test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,12 @@ fn process_compressible_config<'info>(
let custom_rent_payer =
*rent_payer.key() != compressible_config_account.rent_sponsor.to_bytes();

// Prevents setting executable accounts as rent_sponsor
if custom_rent_payer && !rent_payer.is_signer() {
msg!("Custom rent payer must be a signer");
return Err(ProgramError::MissingRequiredSignature);
}

let rent = compressible_config_account
.rent_config
.get_rent_with_compression_cost(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ pub fn process_create_token_account(
let custom_rent_payer =
*compressible.rent_payer.key() != config_account.rent_sponsor.to_bytes();

// Prevents setting executable accounts as rent_sponsor
if custom_rent_payer && !compressible.rent_payer.is_signer() {
msg!("Custom rent payer must be a signer");
return Err(ProgramError::MissingRequiredSignature);
}

// Build fee_payer seeds (rent_sponsor PDA or None for custom keypair)
let version_bytes = config_account.version.to_le_bytes();
let bump_seed = [config_account.rent_sponsor_bump];
Expand Down