Skip to content
Open
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
54 changes: 52 additions & 2 deletions spl/src/token_2022_extensions/cpi_guard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub fn cpi_guard_enable<'info>(ctx: CpiContext<'_, '_, '_, 'info, CpiGuard<'info
let ix = spl_token_2022::extension::cpi_guard::instruction::enable_cpi_guard(
ctx.accounts.token_program_id.key,
ctx.accounts.account.key,
ctx.accounts.account.owner,
ctx.accounts.owner.key,
&[],
)?;
anchor_lang::solana_program::program::invoke_signed(
Expand All @@ -29,7 +29,7 @@ pub fn cpi_guard_disable<'info>(ctx: CpiContext<'_, '_, '_, 'info, CpiGuard<'inf
let ix = spl_token_2022::extension::cpi_guard::instruction::disable_cpi_guard(
ctx.accounts.token_program_id.key,
ctx.accounts.account.key,
ctx.accounts.account.owner,
ctx.accounts.owner.key,
&[],
)?;

Expand All @@ -51,3 +51,53 @@ pub struct CpiGuard<'info> {
pub account: AccountInfo<'info>,
pub owner: AccountInfo<'info>,
}

#[cfg(test)]
mod tests {
use anchor_lang::solana_program::pubkey::Pubkey;
use spl_token_2022_interface as spl_token_2022;

/// Verifies enable_cpi_guard instruction uses the correct owner key (token authority),
/// not account.owner (the program that owns the account data).
///
/// The bug: the original code passed `ctx.accounts.account.owner` which is the
/// program ID that owns the account, not the token account authority.
#[test]
fn test_enable_cpi_guard_correct_accounts() {
let token_program_id = Pubkey::new_unique();
let account_pubkey = Pubkey::new_unique();
let owner_pubkey = Pubkey::new_unique();

let ix = spl_token_2022::extension::cpi_guard::instruction::enable_cpi_guard(
&token_program_id,
&account_pubkey,
&owner_pubkey,
&[],
)
.expect("enable_cpi_guard instruction construction should succeed");

assert_eq!(ix.accounts.len(), 3, "enable_cpi_guard expects 3 account metas: program, account, owner");
assert_eq!(ix.accounts[1].pubkey, account_pubkey, "account meta at index 1 should be the token account");
assert_eq!(ix.accounts[2].pubkey, owner_pubkey, "account meta at index 2 should be the owner/authority, not the program owner");
}

/// Verifies disable_cpi_guard has the same correct account structure.
#[test]
fn test_disable_cpi_guard_correct_accounts() {
let token_program_id = Pubkey::new_unique();
let account_pubkey = Pubkey::new_unique();
let owner_pubkey = Pubkey::new_unique();

let ix = spl_token_2022::extension::cpi_guard::instruction::disable_cpi_guard(
&token_program_id,
&account_pubkey,
&owner_pubkey,
&[],
)
.expect("disable_cpi_guard instruction construction should succeed");

assert_eq!(ix.accounts.len(), 3);
assert_eq!(ix.accounts[1].pubkey, account_pubkey);
assert_eq!(ix.accounts[2].pubkey, owner_pubkey);
}
}
60 changes: 59 additions & 1 deletion spl/src/token_2022_extensions/group_pointer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ pub fn group_pointer_update<'info>(
)?;
anchor_lang::solana_program::program::invoke_signed(
&ix,
&[ctx.accounts.token_program_id, ctx.accounts.mint],
&[
ctx.accounts.token_program_id,
ctx.accounts.mint,
ctx.accounts.authority,
],
ctx.signer_seeds,
)
.map_err(Into::into)
Expand All @@ -56,3 +60,57 @@ pub struct GroupPointerUpdate<'info> {
pub mint: AccountInfo<'info>,
pub authority: AccountInfo<'info>,
}

#[cfg(test)]
mod tests {
use anchor_lang::solana_program::pubkey::Pubkey;
use spl_token_2022_interface as spl_token_2022;

/// Verifies group_pointer_initialize instruction construction succeeds.
#[test]
fn test_group_pointer_initialize_instruction() {
let token_program_id = Pubkey::new_unique();
let mint_pubkey = Pubkey::new_unique();
let authority = Pubkey::new_unique();
let group_address = Pubkey::new_unique();

let ix = spl_token_2022::extension::group_pointer::instruction::initialize(
&token_program_id,
&mint_pubkey,
Some(authority),
Some(group_address),
)
.expect("group_pointer initialize should build correctly");

// initialize takes 2 accounts: token_program and mint
assert_eq!(ix.accounts.len(), 2, "initialize needs token_program and mint accounts");
assert_eq!(ix.accounts[1].pubkey, mint_pubkey);
}

/// Verifies group_pointer_update instruction includes the authority account.
///
/// The bug: the original code only passed [token_program_id, mint] to invoke_signed,
/// missing the authority account. The authority is needed as a signer to authorize
/// the update. Without it, the CPI would fail at runtime.
#[test]
fn test_group_pointer_update_includes_authority() {
let token_program_id = Pubkey::new_unique();
let mint_pubkey = Pubkey::new_unique();
let authority_pubkey = Pubkey::new_unique();
let group_address = Pubkey::new_unique();

let ix = spl_token_2022::extension::group_pointer::instruction::update(
&token_program_id,
&mint_pubkey,
&authority_pubkey,
&[&authority_pubkey],
Some(group_address),
)
.expect("group_pointer update should build correctly");

// update takes 3 accounts: token_program, mint, authority
assert_eq!(ix.accounts.len(), 3, "update needs token_program, mint, and authority accounts");
assert_eq!(ix.accounts[1].pubkey, mint_pubkey);
assert_eq!(ix.accounts[2].pubkey, authority_pubkey, "authority must be included in accounts for signer validation");
}
}