mpl_utils/token/
utils.rs

1use arrayref::{array_ref, array_refs};
2use solana_program::program_pack::Pack;
3use solana_program::{
4    account_info::AccountInfo, program_error::ProgramError, program_option::COption, pubkey::Pubkey,
5};
6use spl_token_2022::extension::{BaseState, StateWithExtensions};
7
8pub fn unpack<S: BaseState + Pack>(
9    account_data: &[u8],
10) -> Result<StateWithExtensions<'_, S>, ProgramError> {
11    StateWithExtensions::<S>::unpack(account_data)
12}
13
14pub fn unpack_initialized<S: BaseState + Pack>(
15    account_data: &[u8],
16    error: impl Into<ProgramError>,
17) -> Result<StateWithExtensions<'_, S>, ProgramError> {
18    let unpacked = unpack::<S>(account_data)?;
19
20    if unpacked.base.is_initialized() {
21        Ok(unpacked)
22    } else {
23        Err(error.into())
24    }
25}
26
27/// Unpacks COption from a slice, taken from token program
28fn unpack_coption_key(src: &[u8; 36]) -> Result<COption<Pubkey>, ProgramError> {
29    let (tag, body) = array_refs![src, 4, 32];
30    match *tag {
31        [0, 0, 0, 0] => Ok(COption::None),
32        [1, 0, 0, 0] => Ok(COption::Some(Pubkey::new_from_array(*body))),
33        _ => Err(ProgramError::InvalidAccountData),
34    }
35}
36
37/// Cheap method to just grab owner Pubkey from token account, instead of deserializing entire thing
38pub fn get_owner_from_token_account(
39    token_account_info: &AccountInfo,
40) -> Result<Pubkey, ProgramError> {
41    // TokenAccount layout:   mint(32), owner(32), ...
42    let data = token_account_info.try_borrow_data()?;
43    let owner_data = array_ref![data, 32, 32];
44    Ok(Pubkey::new_from_array(*owner_data))
45}
46
47pub fn get_mint_authority(account_info: &AccountInfo) -> Result<COption<Pubkey>, ProgramError> {
48    // In token program, 36, 8, 1, 1 is the layout, where the first 36 is mint_authority
49    // so we start at 0.
50    let data = account_info.try_borrow_data().unwrap();
51    let authority_bytes = array_ref![data, 0, 36];
52
53    unpack_coption_key(authority_bytes)
54}
55
56pub fn get_mint_freeze_authority(
57    account_info: &AccountInfo,
58) -> Result<COption<Pubkey>, ProgramError> {
59    let data = account_info.try_borrow_data().unwrap();
60    let authority_bytes = array_ref![data, 36 + 8 + 1 + 1, 36];
61
62    unpack_coption_key(authority_bytes)
63}
64
65/// cheap method to just get supply off a mint without unpacking whole object
66pub fn get_mint_supply(account_info: &AccountInfo) -> Result<u64, ProgramError> {
67    // In token program, 36, 8, 1, 1 is the layout, where the first 8 is supply u64.
68    // so we start at 36.
69    let data = account_info.try_borrow_data()?;
70
71    // If we don't check this and an empty account is passed in, we get a panic when
72    // the array_ref! macro tries to index into the data.
73    if data.is_empty() {
74        return Err(ProgramError::InvalidAccountData);
75    }
76
77    let bytes = array_ref![data, 36, 8];
78
79    Ok(u64::from_le_bytes(*bytes))
80}
81
82/// cheap method to just get supply off a mint without unpacking whole object
83pub fn get_mint_decimals(account_info: &AccountInfo) -> Result<u8, ProgramError> {
84    // In token program, 36, 8, 1, 1, is the layout, where the first 1 is decimals u8.
85    // so we start at 36.
86    let data = account_info.try_borrow_data()?;
87
88    // If we don't check this and an empty account is passed in, we get a panic when
89    // we try to index into the data.
90    if data.is_empty() {
91        return Err(ProgramError::InvalidAccountData);
92    }
93
94    Ok(data[44])
95}