solana_extra_wasm/program/spl_token/
state.rs

1//! State transition types
2
3use super::instruction::MAX_SIGNERS;
4use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
5use num_enum::TryFromPrimitive;
6use solana_sdk::{
7    incinerator,
8    program_error::ProgramError,
9    program_option::COption,
10    program_pack::{IsInitialized, Pack, Sealed},
11    pubkey::{Pubkey, PUBKEY_BYTES},
12    system_program,
13};
14
15/// Mint data.
16#[repr(C)]
17#[derive(Clone, Copy, Debug, Default, PartialEq)]
18pub struct Mint {
19    /// Optional authority used to mint new tokens. The mint authority may only be provided during
20    /// mint creation. If no mint authority is present then the mint has a fixed supply and no
21    /// further tokens may be minted.
22    pub mint_authority: COption<Pubkey>,
23    /// Total supply of tokens.
24    pub supply: u64,
25    /// Number of base 10 digits to the right of the decimal place.
26    pub decimals: u8,
27    /// Is `true` if this structure has been initialized
28    pub is_initialized: bool,
29    /// Optional authority to freeze token accounts.
30    pub freeze_authority: COption<Pubkey>,
31}
32impl Sealed for Mint {}
33impl IsInitialized for Mint {
34    fn is_initialized(&self) -> bool {
35        self.is_initialized
36    }
37}
38impl Pack for Mint {
39    const LEN: usize = 82;
40    fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
41        let src = array_ref![src, 0, 82];
42        let (mint_authority, supply, decimals, is_initialized, freeze_authority) =
43            array_refs![src, 36, 8, 1, 1, 36];
44        let mint_authority = unpack_coption_key(mint_authority)?;
45        let supply = u64::from_le_bytes(*supply);
46        let decimals = decimals[0];
47        let is_initialized = match is_initialized {
48            [0] => false,
49            [1] => true,
50            _ => return Err(ProgramError::InvalidAccountData),
51        };
52        let freeze_authority = unpack_coption_key(freeze_authority)?;
53        Ok(Mint {
54            mint_authority,
55            supply,
56            decimals,
57            is_initialized,
58            freeze_authority,
59        })
60    }
61    fn pack_into_slice(&self, dst: &mut [u8]) {
62        let dst = array_mut_ref![dst, 0, 82];
63        let (
64            mint_authority_dst,
65            supply_dst,
66            decimals_dst,
67            is_initialized_dst,
68            freeze_authority_dst,
69        ) = mut_array_refs![dst, 36, 8, 1, 1, 36];
70        let &Mint {
71            ref mint_authority,
72            supply,
73            decimals,
74            is_initialized,
75            ref freeze_authority,
76        } = self;
77        pack_coption_key(mint_authority, mint_authority_dst);
78        *supply_dst = supply.to_le_bytes();
79        decimals_dst[0] = decimals;
80        is_initialized_dst[0] = is_initialized as u8;
81        pack_coption_key(freeze_authority, freeze_authority_dst);
82    }
83}
84
85/// Account data.
86#[repr(C)]
87#[derive(Clone, Copy, Debug, Default, PartialEq)]
88pub struct Account {
89    /// The mint associated with this account
90    pub mint: Pubkey,
91    /// The owner of this account.
92    pub owner: Pubkey,
93    /// The amount of tokens this account holds.
94    pub amount: u64,
95    /// If `delegate` is `Some` then `delegated_amount` represents
96    /// the amount authorized by the delegate
97    pub delegate: COption<Pubkey>,
98    /// The account's state
99    pub state: AccountState,
100    /// If is_native.is_some, this is a native token, and the value logs the rent-exempt reserve. An
101    /// Account is required to be rent-exempt, so the value is used by the Processor to ensure that
102    /// wrapped SOL accounts do not drop below this threshold.
103    pub is_native: COption<u64>,
104    /// The amount delegated
105    pub delegated_amount: u64,
106    /// Optional authority to close the account.
107    pub close_authority: COption<Pubkey>,
108}
109impl Account {
110    /// Checks if account is frozen
111    pub fn is_frozen(&self) -> bool {
112        self.state == AccountState::Frozen
113    }
114    /// Checks if account is native
115    pub fn is_native(&self) -> bool {
116        self.is_native.is_some()
117    }
118    /// Checks if a token Account's owner is the system_program or the incinerator
119    pub fn is_owned_by_system_program_or_incinerator(&self) -> bool {
120        system_program::check_id(&self.owner) || incinerator::check_id(&self.owner)
121    }
122}
123impl Sealed for Account {}
124impl IsInitialized for Account {
125    fn is_initialized(&self) -> bool {
126        self.state != AccountState::Uninitialized
127    }
128}
129impl Pack for Account {
130    const LEN: usize = 165;
131    fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
132        let src = array_ref![src, 0, 165];
133        let (mint, owner, amount, delegate, state, is_native, delegated_amount, close_authority) =
134            array_refs![src, 32, 32, 8, 36, 1, 12, 8, 36];
135        Ok(Account {
136            mint: Pubkey::new_from_array(*mint),
137            owner: Pubkey::new_from_array(*owner),
138            amount: u64::from_le_bytes(*amount),
139            delegate: unpack_coption_key(delegate)?,
140            state: AccountState::try_from_primitive(state[0])
141                .or(Err(ProgramError::InvalidAccountData))?,
142            is_native: unpack_coption_u64(is_native)?,
143            delegated_amount: u64::from_le_bytes(*delegated_amount),
144            close_authority: unpack_coption_key(close_authority)?,
145        })
146    }
147    fn pack_into_slice(&self, dst: &mut [u8]) {
148        let dst = array_mut_ref![dst, 0, 165];
149        let (
150            mint_dst,
151            owner_dst,
152            amount_dst,
153            delegate_dst,
154            state_dst,
155            is_native_dst,
156            delegated_amount_dst,
157            close_authority_dst,
158        ) = mut_array_refs![dst, 32, 32, 8, 36, 1, 12, 8, 36];
159        let &Account {
160            ref mint,
161            ref owner,
162            amount,
163            ref delegate,
164            state,
165            ref is_native,
166            delegated_amount,
167            ref close_authority,
168        } = self;
169        mint_dst.copy_from_slice(mint.as_ref());
170        owner_dst.copy_from_slice(owner.as_ref());
171        *amount_dst = amount.to_le_bytes();
172        pack_coption_key(delegate, delegate_dst);
173        state_dst[0] = state as u8;
174        pack_coption_u64(is_native, is_native_dst);
175        *delegated_amount_dst = delegated_amount.to_le_bytes();
176        pack_coption_key(close_authority, close_authority_dst);
177    }
178}
179
180/// Account state.
181#[repr(u8)]
182#[derive(Clone, Copy, Debug, PartialEq, Default, TryFromPrimitive)]
183pub enum AccountState {
184    /// Account is not yet initialized
185    #[default]
186    Uninitialized,
187    /// Account is initialized; the account owner and/or delegate may perform permitted operations
188    /// on this account
189    Initialized,
190    /// Account has been frozen by the mint freeze authority. Neither the account owner nor
191    /// the delegate are able to perform operations on this account.
192    Frozen,
193}
194
195/// Multisignature data.
196#[repr(C)]
197#[derive(Clone, Copy, Debug, Default, PartialEq)]
198pub struct Multisig {
199    /// Number of signers required
200    pub m: u8,
201    /// Number of valid signers
202    pub n: u8,
203    /// Is `true` if this structure has been initialized
204    pub is_initialized: bool,
205    /// Signer public keys
206    pub signers: [Pubkey; MAX_SIGNERS],
207}
208impl Sealed for Multisig {}
209impl IsInitialized for Multisig {
210    fn is_initialized(&self) -> bool {
211        self.is_initialized
212    }
213}
214impl Pack for Multisig {
215    const LEN: usize = 355;
216    fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
217        let src = array_ref![src, 0, 355];
218        #[allow(clippy::ptr_offset_with_cast)]
219        let (m, n, is_initialized, signers_flat) = array_refs![src, 1, 1, 1, 32 * MAX_SIGNERS];
220        let mut result = Multisig {
221            m: m[0],
222            n: n[0],
223            is_initialized: match is_initialized {
224                [0] => false,
225                [1] => true,
226                _ => return Err(ProgramError::InvalidAccountData),
227            },
228            signers: [Pubkey::new_from_array([0u8; 32]); MAX_SIGNERS],
229        };
230        for (src, dst) in signers_flat.chunks(32).zip(result.signers.iter_mut()) {
231            *dst = Pubkey::try_from(src).unwrap();
232        }
233        Ok(result)
234    }
235    fn pack_into_slice(&self, dst: &mut [u8]) {
236        let dst = array_mut_ref![dst, 0, 355];
237        #[allow(clippy::ptr_offset_with_cast)]
238        let (m, n, is_initialized, signers_flat) = mut_array_refs![dst, 1, 1, 1, 32 * MAX_SIGNERS];
239        *m = [self.m];
240        *n = [self.n];
241        *is_initialized = [self.is_initialized as u8];
242        for (i, src) in self.signers.iter().enumerate() {
243            let dst_array = array_mut_ref![signers_flat, 32 * i, 32];
244            dst_array.copy_from_slice(src.as_ref());
245        }
246    }
247}
248
249// Helpers
250fn pack_coption_key(src: &COption<Pubkey>, dst: &mut [u8; 36]) {
251    let (tag, body) = mut_array_refs![dst, 4, 32];
252    match src {
253        COption::Some(key) => {
254            *tag = [1, 0, 0, 0];
255            body.copy_from_slice(key.as_ref());
256        }
257        COption::None => {
258            *tag = [0; 4];
259        }
260    }
261}
262fn unpack_coption_key(src: &[u8; 36]) -> Result<COption<Pubkey>, ProgramError> {
263    let (tag, body) = array_refs![src, 4, 32];
264    match *tag {
265        [0, 0, 0, 0] => Ok(COption::None),
266        [1, 0, 0, 0] => Ok(COption::Some(Pubkey::new_from_array(*body))),
267        _ => Err(ProgramError::InvalidAccountData),
268    }
269}
270fn pack_coption_u64(src: &COption<u64>, dst: &mut [u8; 12]) {
271    let (tag, body) = mut_array_refs![dst, 4, 8];
272    match src {
273        COption::Some(amount) => {
274            *tag = [1, 0, 0, 0];
275            *body = amount.to_le_bytes();
276        }
277        COption::None => {
278            *tag = [0; 4];
279        }
280    }
281}
282fn unpack_coption_u64(src: &[u8; 12]) -> Result<COption<u64>, ProgramError> {
283    let (tag, body) = array_refs![src, 4, 8];
284    match *tag {
285        [0, 0, 0, 0] => Ok(COption::None),
286        [1, 0, 0, 0] => Ok(COption::Some(u64::from_le_bytes(*body))),
287        _ => Err(ProgramError::InvalidAccountData),
288    }
289}
290
291const SPL_TOKEN_ACCOUNT_MINT_OFFSET: usize = 0;
292const SPL_TOKEN_ACCOUNT_OWNER_OFFSET: usize = 32;
293
294/// A trait for token Account structs to enable efficiently unpacking various fields
295/// without unpacking the complete state.
296pub trait GenericTokenAccount {
297    /// Check if the account data is a valid token account
298    fn valid_account_data(account_data: &[u8]) -> bool;
299
300    /// Call after account length has already been verified to unpack the account owner
301    fn unpack_account_owner_unchecked(account_data: &[u8]) -> &Pubkey {
302        Self::unpack_pubkey_unchecked(account_data, SPL_TOKEN_ACCOUNT_OWNER_OFFSET)
303    }
304
305    /// Call after account length has already been verified to unpack the account mint
306    fn unpack_account_mint_unchecked(account_data: &[u8]) -> &Pubkey {
307        Self::unpack_pubkey_unchecked(account_data, SPL_TOKEN_ACCOUNT_MINT_OFFSET)
308    }
309
310    /// Call after account length has already been verified to unpack a Pubkey at
311    /// the specified offset. Panics if `account_data.len()` is less than `PUBKEY_BYTES`
312    fn unpack_pubkey_unchecked(account_data: &[u8], offset: usize) -> &Pubkey {
313        bytemuck::from_bytes(&account_data[offset..offset + PUBKEY_BYTES])
314    }
315
316    /// Unpacks an account's owner from opaque account data.
317    fn unpack_account_owner(account_data: &[u8]) -> Option<&Pubkey> {
318        if Self::valid_account_data(account_data) {
319            Some(Self::unpack_account_owner_unchecked(account_data))
320        } else {
321            None
322        }
323    }
324
325    /// Unpacks an account's mint from opaque account data.
326    fn unpack_account_mint(account_data: &[u8]) -> Option<&Pubkey> {
327        if Self::valid_account_data(account_data) {
328            Some(Self::unpack_account_mint_unchecked(account_data))
329        } else {
330            None
331        }
332    }
333}
334
335/// The offset of state field in Account's C representation
336pub const ACCOUNT_INITIALIZED_INDEX: usize = 108;
337
338/// Check if the account data buffer represents an initialized account.
339/// This is checking the `state` (AccountState) field of an Account object.
340pub fn is_initialized_account(account_data: &[u8]) -> bool {
341    *account_data
342        .get(ACCOUNT_INITIALIZED_INDEX)
343        .unwrap_or(&(AccountState::Uninitialized as u8))
344        != AccountState::Uninitialized as u8
345}
346
347impl GenericTokenAccount for Account {
348    fn valid_account_data(account_data: &[u8]) -> bool {
349        account_data.len() == Account::LEN && is_initialized_account(account_data)
350    }
351}