mpl_token_vault/
utils.rs

1use {
2    crate::{
3        error::VaultError,
4        state::{Key, Vault},
5    },
6    borsh::BorshDeserialize,
7    solana_program::{
8        account_info::AccountInfo,
9        borsh::try_from_slice_unchecked,
10        entrypoint::ProgramResult,
11        msg,
12        program::{invoke, invoke_signed},
13        program_error::ProgramError,
14        program_pack::{IsInitialized, Pack},
15        pubkey::Pubkey,
16        system_instruction,
17        sysvar::{rent::Rent, Sysvar},
18    },
19    std::convert::TryInto,
20};
21
22/// assert initialized account
23pub fn assert_initialized<T: Pack + IsInitialized>(
24    account_info: &AccountInfo,
25) -> Result<T, ProgramError> {
26    let account: T = T::unpack_unchecked(&account_info.data.borrow())?;
27    if !account.is_initialized() {
28        Err(VaultError::Uninitialized.into())
29    } else {
30        Ok(account)
31    }
32}
33
34pub fn assert_rent_exempt(rent: &Rent, account_info: &AccountInfo) -> ProgramResult {
35    if !rent.is_exempt(account_info.lamports(), account_info.data_len()) {
36        Err(VaultError::NotRentExempt.into())
37    } else {
38        Ok(())
39    }
40}
41
42pub fn assert_owned_by(account: &AccountInfo, owner: &Pubkey) -> ProgramResult {
43    if account.owner != owner {
44        Err(VaultError::IncorrectOwner.into())
45    } else {
46        Ok(())
47    }
48}
49
50pub fn assert_token_matching(vault: &Vault, token: &AccountInfo) -> ProgramResult {
51    if vault.token_program != *token.key {
52        return Err(VaultError::TokenProgramProvidedDoesNotMatchVault.into());
53    }
54    Ok(())
55}
56
57pub fn assert_vault_authority_correct(
58    vault: &Vault,
59    vault_authority_info: &AccountInfo,
60) -> ProgramResult {
61    if !vault_authority_info.is_signer {
62        return Err(VaultError::AuthorityIsNotSigner.into());
63    }
64
65    if *vault_authority_info.key != vault.authority {
66        return Err(VaultError::AuthorityDoesNotMatch.into());
67    }
68    Ok(())
69}
70
71pub fn assert_token_program_matches_package(token_program_info: &AccountInfo) -> ProgramResult {
72    if *token_program_info.key != spl_token::id() {
73        return Err(VaultError::InvalidTokenProgram.into());
74    }
75
76    Ok(())
77}
78
79/// Create account almost from scratch, lifted from
80/// https://github.com/solana-labs/solana-program-library/blob/7d4873c61721aca25464d42cc5ef651a7923ca79/associated-token-account/program/src/processor.rs#L51-L98
81#[inline(always)]
82pub fn create_or_allocate_account_raw<'a>(
83    program_id: Pubkey,
84    new_account_info: &AccountInfo<'a>,
85    rent_sysvar_info: &AccountInfo<'a>,
86    system_program_info: &AccountInfo<'a>,
87    payer_info: &AccountInfo<'a>,
88    size: usize,
89    signer_seeds: &[&[u8]],
90) -> Result<(), ProgramError> {
91    let rent = &Rent::from_account_info(rent_sysvar_info)?;
92    let required_lamports = rent
93        .minimum_balance(size)
94        .max(1)
95        .saturating_sub(new_account_info.lamports());
96
97    if required_lamports > 0 {
98        msg!("Transfer {} lamports to the new account", required_lamports);
99        invoke(
100            &system_instruction::transfer(payer_info.key, new_account_info.key, required_lamports),
101            &[
102                payer_info.clone(),
103                new_account_info.clone(),
104                system_program_info.clone(),
105            ],
106        )?;
107    }
108
109    msg!("Allocate space for the account");
110    invoke_signed(
111        &system_instruction::allocate(new_account_info.key, size.try_into().unwrap()),
112        &[new_account_info.clone(), system_program_info.clone()],
113        &[signer_seeds],
114    )?;
115
116    msg!("Assign the account to the owning program");
117    invoke_signed(
118        &system_instruction::assign(new_account_info.key, &program_id),
119        &[new_account_info.clone(), system_program_info.clone()],
120        &[signer_seeds],
121    )?;
122    msg!("Completed assignation!");
123
124    Ok(())
125}
126
127/// Issue a spl_token `Transfer` instruction.
128#[inline(always)]
129pub fn spl_token_transfer(params: TokenTransferParams<'_, '_>) -> ProgramResult {
130    let TokenTransferParams {
131        source,
132        destination,
133        authority,
134        token_program,
135        amount,
136        authority_signer_seeds,
137    } = params;
138    let result = invoke_signed(
139        &spl_token::instruction::transfer(
140            token_program.key,
141            source.key,
142            destination.key,
143            authority.key,
144            &[],
145            amount,
146        )?,
147        &[source, destination, authority, token_program],
148        &[authority_signer_seeds],
149    );
150    result.map_err(|_| VaultError::TokenTransferFailed.into())
151}
152
153/// Issue a spl_token `MintTo` instruction.
154pub fn spl_token_mint_to(params: TokenMintToParams<'_, '_>) -> ProgramResult {
155    let TokenMintToParams {
156        mint,
157        destination,
158        authority,
159        token_program,
160        amount,
161        authority_signer_seeds,
162    } = params;
163    let result = invoke_signed(
164        &spl_token::instruction::mint_to(
165            token_program.key,
166            mint.key,
167            destination.key,
168            authority.key,
169            &[],
170            amount,
171        )?,
172        &[mint, destination, authority, token_program],
173        &[authority_signer_seeds],
174    );
175    result.map_err(|_| VaultError::TokenMintToFailed.into())
176}
177
178/// Issue a spl_token `Burn` instruction.
179#[inline(always)]
180pub fn spl_token_burn(params: TokenBurnParams<'_, '_>) -> ProgramResult {
181    let TokenBurnParams {
182        mint,
183        source,
184        authority,
185        token_program,
186        amount,
187        authority_signer_seeds,
188    } = params;
189    let result = invoke_signed(
190        &spl_token::instruction::burn(
191            token_program.key,
192            source.key,
193            mint.key,
194            authority.key,
195            &[],
196            amount,
197        )?,
198        &[source, mint, authority, token_program],
199        &[authority_signer_seeds],
200    );
201    result.map_err(|_| VaultError::TokenBurnFailed.into())
202}
203
204///TokenTransferParams
205pub struct TokenTransferParams<'a: 'b, 'b> {
206    /// source
207    pub source: AccountInfo<'a>,
208    /// destination
209    pub destination: AccountInfo<'a>,
210    /// amount
211    pub amount: u64,
212    /// authority
213    pub authority: AccountInfo<'a>,
214    /// authority_signer_seeds
215    pub authority_signer_seeds: &'b [&'b [u8]],
216    /// token_program
217    pub token_program: AccountInfo<'a>,
218}
219/// TokenMintToParams
220pub struct TokenMintToParams<'a: 'b, 'b> {
221    /// mint
222    pub mint: AccountInfo<'a>,
223    /// destination
224    pub destination: AccountInfo<'a>,
225    /// amount
226    pub amount: u64,
227    /// authority
228    pub authority: AccountInfo<'a>,
229    /// authority_signer_seeds
230    pub authority_signer_seeds: &'b [&'b [u8]],
231    /// token_program
232    pub token_program: AccountInfo<'a>,
233}
234/// TokenBurnParams
235pub struct TokenBurnParams<'a: 'b, 'b> {
236    /// mint
237    pub mint: AccountInfo<'a>,
238    /// source
239    pub source: AccountInfo<'a>,
240    /// amount
241    pub amount: u64,
242    /// authority
243    pub authority: AccountInfo<'a>,
244    /// authority_signer_seeds
245    pub authority_signer_seeds: &'b [&'b [u8]],
246    /// token_program
247    pub token_program: AccountInfo<'a>,
248}
249
250pub fn try_from_slice_checked<T: BorshDeserialize>(
251    data: &[u8],
252    data_type: Key,
253    data_size: usize,
254) -> Result<T, ProgramError> {
255    if (data[0] != data_type as u8 && data[0] != Key::Uninitialized as u8)
256        || data.len() != data_size
257    {
258        return Err(VaultError::DataTypeMismatch.into());
259    }
260
261    let result: T = try_from_slice_unchecked(data)?;
262
263    Ok(result)
264}
265
266pub fn assert_derivation(
267    program_id: &Pubkey,
268    account: &AccountInfo,
269    path: &[&[u8]],
270) -> Result<u8, ProgramError> {
271    let (key, bump) = Pubkey::find_program_address(path, program_id);
272    if key != *account.key {
273        return Err(VaultError::DerivedKeyInvalid.into());
274    }
275    Ok(bump)
276}