tensor_toolbox/token_2022/
token.rs

1use anchor_lang::{
2    context::CpiContext,
3    error::ErrorCode,
4    solana_program::{
5        account_info::AccountInfo, msg, program_pack::IsInitialized, rent::Rent, sysvar::Sysvar,
6    },
7    system_program::create_account,
8    Result,
9};
10use anchor_spl::{
11    token_2022::InitializeAccount3,
12    token_interface::{
13        initialize_account3,
14        spl_token_2022::{
15            extension::{BaseStateWithExtensions, StateWithExtensions},
16            state::{Account, Mint},
17        },
18    },
19};
20
21use super::extension::{get_extension_types, IExtensionType};
22
23/// Struct that holds the accounts required for initializing a token account.
24pub struct InitializeTokenAccount<'a, 'b, 'info> {
25    pub token_info: &'a AccountInfo<'info>,
26    pub mint: &'a AccountInfo<'info>,
27    pub authority: &'a AccountInfo<'info>,
28    pub payer: &'a AccountInfo<'info>,
29    pub system_program: &'a AccountInfo<'info>,
30    pub token_program: &'a AccountInfo<'info>,
31    pub signer_seeds: &'a [&'b [u8]],
32}
33
34/// Initializes a token account without checking for the mint extensions.
35///
36/// This function is useful when the mint is known to have any extensions, which might not be
37/// supported by the current version of the spl-token-2022 create. Trying to deserialize the
38/// extension types will result in an error. For this reason, this function uses an "internal"
39/// `IExtensionType` to safely calculate the length of the account.
40///
41/// When `allow_existing` is true, the function will not try to create the account; otherwise, it will
42/// fail if the account already exists. This provides the same functionality as `init_if_needed`.
43pub fn safe_initialize_token_account(
44    input: InitializeTokenAccount<'_, '_, '_>,
45    allow_existing: bool,
46) -> Result<()> {
47    let mint_data = &input.mint.data.borrow();
48    let mint = StateWithExtensions::<Mint>::unpack(mint_data)?;
49    // Get the token extensions required for the mint.
50    let extensions = IExtensionType::get_required_init_account_extensions(&get_extension_types(
51        mint.get_tlv_data(),
52    )?);
53
54    // Check if the token account is already initialized.
55    if input.token_info.owner == &anchor_lang::solana_program::system_program::ID
56        && input.token_info.lamports() == 0
57    {
58        // Determine the size of the account. We cannot deserialize the mint since it might
59        // have extensions that are not currently on the version of the spl-token crate being used.
60
61        let required_length = IExtensionType::try_calculate_account_len::<Account>(&extensions)?;
62
63        let lamports = Rent::get()?.minimum_balance(required_length);
64        let cpi_accounts = anchor_lang::system_program::CreateAccount {
65            from: input.payer.clone(),
66            to: input.token_info.clone(),
67        };
68
69        // Create the account.
70        let cpi_context = CpiContext::new(input.system_program.clone(), cpi_accounts);
71        create_account(
72            cpi_context.with_signer(&[input.signer_seeds]),
73            lamports,
74            required_length as u64,
75            input.token_program.key,
76        )?;
77
78        // Initialize the token account.
79        let cpi_ctx = anchor_lang::context::CpiContext::new(
80            input.token_program.clone(),
81            InitializeAccount3 {
82                account: input.token_info.clone(),
83                mint: input.mint.clone(),
84                authority: input.authority.clone(),
85            },
86        );
87        initialize_account3(cpi_ctx)?;
88    } else if allow_existing {
89        // Validate that we got the expected token account.
90        if input.token_info.owner != input.token_program.key {
91            msg!("Invalid token account owner");
92            return Err(ErrorCode::AccountOwnedByWrongProgram.into());
93        }
94
95        let token_data = &input.token_info.data.borrow();
96        let token = StateWithExtensions::<Account>::unpack(token_data)?;
97
98        if !token.base.is_initialized() {
99            msg!("Token account is not initialized");
100            return Err(ErrorCode::AccountNotInitialized.into());
101        }
102
103        if token.base.mint != *input.mint.key {
104            msg!("Invalid mint on token account");
105            return Err(ErrorCode::ConstraintTokenMint.into());
106        }
107
108        if token.base.owner != *input.authority.key {
109            msg!("Invalid token owner");
110            return Err(ErrorCode::ConstraintOwner.into());
111        }
112    } else {
113        // the account already exists but `allow_existing` is false
114        msg!("Token account already exists (reinitialization not allowed)");
115        return Err(ErrorCode::ConstraintState.into());
116    }
117
118    Ok(())
119}