solana_extra_wasm/program/spl_token_2022/
state.rs

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