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
15 changes: 15 additions & 0 deletions program-libs/account-checks/src/account_iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,21 @@ impl<'info, T: AccountInfoTrait> AccountIterator<'info, T> {
}
}

#[inline(always)]
#[track_caller]
pub fn next_option_signer(
&mut self,
account_name: &str,
is_some: bool,
) -> Result<Option<&'info T>, AccountError> {
if is_some {
let account_info = self.next_signer(account_name)?;
Ok(Some(account_info))
} else {
Ok(None)
}
}

#[inline(always)]
#[track_caller]
pub fn next_signer_mut(&mut self, account_name: &str) -> Result<&'info T, AccountError> {
Expand Down
63 changes: 62 additions & 1 deletion program-tests/compressed-token-test/tests/mint/failing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ use light_test_utils::{
};
use light_token_client::actions::create_mint;
use serial_test::serial;
use solana_sdk::{signature::Keypair, signer::Signer};
use solana_sdk::{
instruction::AccountMeta, signature::Keypair, signer::Signer, transaction::Transaction,
};

/// Functional and Failing tests:
/// 1. FAIL - MintToCompressed - invalid mint authority
Expand Down Expand Up @@ -797,3 +799,62 @@ async fn functional_and_failing_tests() {
.await;
}
}

/// Test that mint_signer must be a signer when creating a compressed mint
#[tokio::test]
#[serial]
async fn test_create_mint_non_signer_mint_signer() {
let mut rpc = LightProgramTest::new(ProgramTestConfig::new_v2(false, None))
.await
.unwrap();

let payer = Keypair::new();
rpc.airdrop_lamports(&payer.pubkey(), 10_000_000_000)
.await
.unwrap();

let mint_seed = Keypair::new();
let mint_authority = Keypair::new();

// Create the instruction using the helper function
let mut instruction =
light_token_client::instructions::create_mint::create_compressed_mint_instruction(
&mut rpc,
&mint_seed,
8, // decimals
mint_authority.pubkey(),
None, // freeze authority
payer.pubkey(),
None, // metadata
)
.await
.unwrap();

// Manually override the account metas to make mint_signer a non-signer
// Account ordering: [0] light_system_program, [1] mint_signer, [2] authority, ...
// Find and modify the mint_signer account meta at index 1
// The SDK creates it as AccountMeta::new_readonly(mint_signer, true)
// We want to change it to AccountMeta::new_readonly(mint_signer, false)
if let Some(mint_signer_meta) = instruction.accounts.get_mut(1) {
// Verify it's the mint_seed
assert_eq!(mint_signer_meta.pubkey, mint_seed.pubkey());
// Change is_signer from true to false to bypass runtime checks
*mint_signer_meta = AccountMeta::new_readonly(mint_seed.pubkey(), false);
}

let (blockhash, _) = rpc.get_latest_blockhash().await.unwrap();
let transaction = Transaction::new_signed_with_payer(
&[instruction],
Some(&payer.pubkey()),
&[&payer, &mint_authority], // Note: NOT signing with mint_seed
blockhash,
);

let result = rpc.process_transaction(transaction).await;

// Should fail with AccountError::InvalidSigner (error code 20009)
assert_rpc_error(
result, 0, 20009, // AccountError::InvalidSigner = 20009
)
.unwrap();
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ impl<'info> MintActionAccounts<'info> {
let mut iter = AccountIterator::new(accounts);
let light_system_program = iter.next_account("light_system_program")?;

let mint_signer = iter.next_option("mint_signer", config.with_mint_signer)?;
let mint_signer = iter.next_option_signer("mint_signer", config.with_mint_signer)?;
// Static non-CPI accounts first
// Authority is always required to sign
let authority = iter.next_signer("authority")?;
Expand Down