light_compressed_token/instructions/
create_token_pool.rs

1use account_compression::utils::constants::CPI_AUTHORITY_PDA_SEED;
2use anchor_lang::prelude::*;
3use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};
4use spl_token_2022::{
5    extension::{BaseStateWithExtensions, ExtensionType, PodStateWithExtensions},
6    pod::PodMint,
7};
8
9use crate::{
10    constants::{NUM_MAX_POOL_ACCOUNTS, POOL_SEED},
11    spl_compression::is_valid_token_pool_pda,
12};
13
14/// Creates an SPL or token-2022 token pool account, which is owned by the token authority PDA.
15#[derive(Accounts)]
16pub struct CreateTokenPoolInstruction<'info> {
17    /// UNCHECKED: only pays fees.
18    #[account(mut)]
19    pub fee_payer: Signer<'info>,
20    #[account(
21        init,
22        seeds = [
23        POOL_SEED, &mint.key().to_bytes(),
24        ],
25        bump,
26        payer = fee_payer,
27          token::mint = mint,
28          token::authority = cpi_authority_pda,
29    )]
30    pub token_pool_pda: InterfaceAccount<'info, TokenAccount>,
31    pub system_program: Program<'info, System>,
32    /// CHECK: is mint account.
33    #[account(mut)]
34    pub mint: InterfaceAccount<'info, Mint>,
35    pub token_program: Interface<'info, TokenInterface>,
36    /// CHECK: (seeds anchor constraint).
37    #[account(seeds = [CPI_AUTHORITY_PDA_SEED], bump)]
38    pub cpi_authority_pda: AccountInfo<'info>,
39}
40
41pub fn get_token_pool_pda(mint: &Pubkey) -> Pubkey {
42    get_token_pool_pda_with_index(mint, 0)
43}
44
45pub fn find_token_pool_pda_with_index(mint: &Pubkey, token_pool_index: u8) -> (Pubkey, u8) {
46    let seeds = &[POOL_SEED, mint.as_ref(), &[token_pool_index]];
47    let seeds = if token_pool_index == 0 {
48        &seeds[..2]
49    } else {
50        &seeds[..]
51    };
52    Pubkey::find_program_address(seeds, &crate::ID)
53}
54
55pub fn get_token_pool_pda_with_index(mint: &Pubkey, token_pool_index: u8) -> Pubkey {
56    find_token_pool_pda_with_index(mint, token_pool_index).0
57}
58
59const ALLOWED_EXTENSION_TYPES: [ExtensionType; 7] = [
60    ExtensionType::MetadataPointer,
61    ExtensionType::TokenMetadata,
62    ExtensionType::InterestBearingConfig,
63    ExtensionType::GroupPointer,
64    ExtensionType::GroupMemberPointer,
65    ExtensionType::TokenGroup,
66    ExtensionType::TokenGroupMember,
67];
68
69pub fn assert_mint_extensions(account_data: &[u8]) -> Result<()> {
70    let mint = PodStateWithExtensions::<PodMint>::unpack(account_data).unwrap();
71    let mint_extensions = mint.get_extension_types().unwrap();
72    if !mint_extensions
73        .iter()
74        .all(|item| ALLOWED_EXTENSION_TYPES.contains(item))
75    {
76        return err!(crate::ErrorCode::MintWithInvalidExtension);
77    }
78    Ok(())
79}
80
81/// Creates an SPL or token-2022 token pool account, which is owned by the token authority PDA.
82#[derive(Accounts)]
83#[instruction(token_pool_index: u8)]
84pub struct AddTokenPoolInstruction<'info> {
85    /// UNCHECKED: only pays fees.
86    #[account(mut)]
87    pub fee_payer: Signer<'info>,
88    #[account(
89        init,
90        seeds = [
91        POOL_SEED, &mint.key().to_bytes(), &[token_pool_index],
92        ],
93        bump,
94        payer = fee_payer,
95          token::mint = mint,
96          token::authority = cpi_authority_pda,
97    )]
98    pub token_pool_pda: InterfaceAccount<'info, TokenAccount>,
99    pub existing_token_pool_pda: InterfaceAccount<'info, TokenAccount>,
100    pub system_program: Program<'info, System>,
101    /// CHECK: is mint account.
102    #[account(mut)]
103    pub mint: InterfaceAccount<'info, Mint>,
104    pub token_program: Interface<'info, TokenInterface>,
105    /// CHECK: (seeds anchor constraint).
106    #[account(seeds = [CPI_AUTHORITY_PDA_SEED], bump)]
107    pub cpi_authority_pda: AccountInfo<'info>,
108}
109
110/// Checks if the token pool PDA is valid.
111/// Iterates over all possible bump seeds to check if the token pool PDA is valid.
112#[inline(always)]
113pub fn check_spl_token_pool_derivation(token_pool_pda: &Pubkey, mint: &Pubkey) -> Result<()> {
114    let mint_bytes = mint.to_bytes();
115    let is_valid = (0..NUM_MAX_POOL_ACCOUNTS).any(|i| {
116        is_valid_token_pool_pda(mint_bytes.as_slice(), token_pool_pda, &[i], None).unwrap_or(false)
117    });
118    if !is_valid {
119        err!(crate::ErrorCode::InvalidTokenPoolPda)
120    } else {
121        Ok(())
122    }
123}
124
125#[inline(always)]
126pub fn check_spl_token_pool_derivation_with_index(
127    token_pool_pda: &Pubkey,
128    mint: &Pubkey,
129    index: u8,
130    bump: Option<u8>,
131) -> Result<()> {
132    let mint_bytes = mint.to_bytes();
133    let is_valid = is_valid_token_pool_pda(mint_bytes.as_slice(), token_pool_pda, &[index], bump)?;
134    if !is_valid {
135        err!(crate::ErrorCode::InvalidTokenPoolPda)
136    } else {
137        Ok(())
138    }
139}
140
141#[cfg(test)]
142mod test {
143    use super::*;
144
145    /// Test:
146    /// 1. Functional: test_check_spl_token_pool_derivation
147    /// 2. Failing: test_check_spl_token_pool_derivation_invalid_derivation
148    /// 3. Failing: test_check_spl_token_pool_derivation_bump_seed_equal_to_num_max_accounts
149    /// 4. Failing: test_check_spl_token_pool_derivation_bump_seed_larger_than_num_max_accounts
150    #[test]
151    fn test_check_spl_token_pool_derivation() {
152        // 1. Functional: test_check_spl_token_pool_derivation_valid
153        let mint = Pubkey::new_unique();
154        for i in 0..NUM_MAX_POOL_ACCOUNTS {
155            let valid_pda = get_token_pool_pda_with_index(&mint, i);
156            assert!(check_spl_token_pool_derivation(&valid_pda, &mint).is_ok());
157        }
158
159        // 2. Failing: test_check_spl_token_pool_derivation_invalid_derivation
160        let mint = Pubkey::new_unique();
161        let invalid_pda = Pubkey::new_unique();
162        assert!(check_spl_token_pool_derivation(&invalid_pda, &mint).is_err());
163
164        // 3. Failing: test_check_spl_token_pool_derivation_bump_seed_equal_to_num_max_accounts
165        let mint = Pubkey::new_unique();
166        let invalid_pda = get_token_pool_pda_with_index(&mint, NUM_MAX_POOL_ACCOUNTS);
167        assert!(check_spl_token_pool_derivation(&invalid_pda, &mint).is_err());
168
169        // 4. Failing: test_check_spl_token_pool_derivation_bump_seed_larger_than_num_max_accounts
170        let mint = Pubkey::new_unique();
171        let invalid_pda = get_token_pool_pda_with_index(&mint, NUM_MAX_POOL_ACCOUNTS + 1);
172        assert!(check_spl_token_pool_derivation(&invalid_pda, &mint).is_err());
173    }
174}