safe_token_2022/
state.rs

1//! State transition types
2
3use {
4    crate::{
5        extension::AccountType,
6        generic_token_account::{is_initialized_account, GenericTokenAccount},
7        instruction::MAX_SIGNERS,
8    },
9    arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs},
10    num_enum::{IntoPrimitive, TryFromPrimitive},
11    solana_program::{
12        program_error::ProgramError,
13        program_option::COption,
14        program_pack::{IsInitialized, Pack, Sealed},
15        pubkey::Pubkey,
16    },
17};
18
19/// Mint data.
20#[repr(C)]
21#[derive(Clone, Copy, Debug, Default, PartialEq)]
22pub struct Mint {
23    /// Optional authority used to mint new tokens. The mint authority may only be provided during
24    /// mint creation. If no mint authority is present then the mint has a fixed supply and no
25    /// further tokens may be minted.
26    pub mint_authority: COption<Pubkey>,
27    /// Total supply of tokens.
28    pub supply: u64,
29    /// Number of base 10 digits to the right of the decimal place.
30    pub decimals: u8,
31    /// Is `true` if this structure has been initialized
32    pub is_initialized: bool,
33    /// Optional authority to freeze token accounts.
34    pub freeze_authority: COption<Pubkey>,
35}
36impl Sealed for Mint {}
37impl IsInitialized for Mint {
38    fn is_initialized(&self) -> bool {
39        self.is_initialized
40    }
41}
42impl Pack for Mint {
43    const LEN: usize = 82;
44    fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
45        let src = array_ref![src, 0, 82];
46        let (mint_authority, supply, decimals, is_initialized, freeze_authority) =
47            array_refs![src, 36, 8, 1, 1, 36];
48        let mint_authority = unpack_coption_key(mint_authority)?;
49        let supply = u64::from_le_bytes(*supply);
50        let decimals = decimals[0];
51        let is_initialized = match is_initialized {
52            [0] => false,
53            [1] => true,
54            _ => return Err(ProgramError::InvalidAccountData),
55        };
56        let freeze_authority = unpack_coption_key(freeze_authority)?;
57        Ok(Mint {
58            mint_authority,
59            supply,
60            decimals,
61            is_initialized,
62            freeze_authority,
63        })
64    }
65    fn pack_into_slice(&self, dst: &mut [u8]) {
66        let dst = array_mut_ref![dst, 0, 82];
67        let (
68            mint_authority_dst,
69            supply_dst,
70            decimals_dst,
71            is_initialized_dst,
72            freeze_authority_dst,
73        ) = mut_array_refs![dst, 36, 8, 1, 1, 36];
74        let &Mint {
75            ref mint_authority,
76            supply,
77            decimals,
78            is_initialized,
79            ref freeze_authority,
80        } = self;
81        pack_coption_key(mint_authority, mint_authority_dst);
82        *supply_dst = supply.to_le_bytes();
83        decimals_dst[0] = decimals;
84        is_initialized_dst[0] = is_initialized as u8;
85        pack_coption_key(freeze_authority, freeze_authority_dst);
86    }
87}
88
89/// Account data.
90#[repr(C)]
91#[derive(Clone, Copy, Debug, Default, PartialEq)]
92pub struct Account {
93    /// The mint associated with this account
94    pub mint: Pubkey,
95    /// The owner of this account.
96    pub owner: Pubkey,
97    /// The amount of tokens this account holds.
98    pub amount: u64,
99    /// If `delegate` is `Some` then `delegated_amount` represents
100    /// the amount authorized by the delegate
101    pub delegate: COption<Pubkey>,
102    /// The account's state
103    pub state: AccountState,
104    /// If is_some, this is a native token, and the value logs the rent-exempt reserve. An Account
105    /// is required to be rent-exempt, so the value is used by the Processor to ensure that wrapped
106    /// SAFE accounts do not drop below this threshold.
107    pub is_native: COption<u64>,
108    /// The amount delegated
109    pub delegated_amount: u64,
110    /// Optional authority to close the account.
111    pub close_authority: COption<Pubkey>,
112}
113impl Account {
114    /// Checks if account is frozen
115    pub fn is_frozen(&self) -> bool {
116        self.state == AccountState::Frozen
117    }
118    /// Checks if account is native
119    pub fn is_native(&self) -> bool {
120        self.is_native.is_some()
121    }
122    /// Checks if a token Account's owner is the system_program or the incinerator
123    pub fn is_owned_by_system_program_or_incinerator(&self) -> bool {
124        solana_program::system_program::check_id(&self.owner)
125            || solana_program::incinerator::check_id(&self.owner)
126    }
127}
128impl Sealed for Account {}
129impl IsInitialized for Account {
130    fn is_initialized(&self) -> bool {
131        self.state != AccountState::Uninitialized
132    }
133}
134impl Pack for Account {
135    const LEN: usize = 165;
136    fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
137        let src = array_ref![src, 0, 165];
138        let (mint, owner, amount, delegate, state, is_native, delegated_amount, close_authority) =
139            array_refs![src, 32, 32, 8, 36, 1, 12, 8, 36];
140        Ok(Account {
141            mint: Pubkey::new_from_array(*mint),
142            owner: Pubkey::new_from_array(*owner),
143            amount: u64::from_le_bytes(*amount),
144            delegate: unpack_coption_key(delegate)?,
145            state: AccountState::try_from_primitive(state[0])
146                .or(Err(ProgramError::InvalidAccountData))?,
147            is_native: unpack_coption_u64(is_native)?,
148            delegated_amount: u64::from_le_bytes(*delegated_amount),
149            close_authority: unpack_coption_key(close_authority)?,
150        })
151    }
152    fn pack_into_slice(&self, dst: &mut [u8]) {
153        let dst = array_mut_ref![dst, 0, 165];
154        let (
155            mint_dst,
156            owner_dst,
157            amount_dst,
158            delegate_dst,
159            state_dst,
160            is_native_dst,
161            delegated_amount_dst,
162            close_authority_dst,
163        ) = mut_array_refs![dst, 32, 32, 8, 36, 1, 12, 8, 36];
164        let &Account {
165            ref mint,
166            ref owner,
167            amount,
168            ref delegate,
169            state,
170            ref is_native,
171            delegated_amount,
172            ref close_authority,
173        } = self;
174        mint_dst.copy_from_slice(mint.as_ref());
175        owner_dst.copy_from_slice(owner.as_ref());
176        *amount_dst = amount.to_le_bytes();
177        pack_coption_key(delegate, delegate_dst);
178        state_dst[0] = state as u8;
179        pack_coption_u64(is_native, is_native_dst);
180        *delegated_amount_dst = delegated_amount.to_le_bytes();
181        pack_coption_key(close_authority, close_authority_dst);
182    }
183}
184
185/// Account state.
186#[repr(u8)]
187#[derive(Clone, Copy, Debug, PartialEq, IntoPrimitive, TryFromPrimitive)]
188pub enum AccountState {
189    /// Account is not yet initialized
190    Uninitialized,
191    /// Account is initialized; the account owner and/or delegate may perform permitted operations
192    /// on this account
193    Initialized,
194    /// Account has been frozen by the mint freeze authority. Neither the account owner nor
195    /// the delegate are able to perform operations on this account.
196    Frozen,
197}
198
199impl Default for AccountState {
200    fn default() -> Self {
201        AccountState::Uninitialized
202    }
203}
204
205/// Multisignature data.
206#[repr(C)]
207#[derive(Clone, Copy, Debug, Default, PartialEq)]
208pub struct Multisig {
209    /// Number of signers required
210    pub m: u8,
211    /// Number of valid signers
212    pub n: u8,
213    /// Is `true` if this structure has been initialized
214    pub is_initialized: bool,
215    /// Signer public keys
216    pub signers: [Pubkey; MAX_SIGNERS],
217}
218impl Sealed for Multisig {}
219impl IsInitialized for Multisig {
220    fn is_initialized(&self) -> bool {
221        self.is_initialized
222    }
223}
224impl Pack for Multisig {
225    const LEN: usize = 355;
226    fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
227        let src = array_ref![src, 0, 355];
228        #[allow(clippy::ptr_offset_with_cast)]
229        let (m, n, is_initialized, signers_flat) = array_refs![src, 1, 1, 1, 32 * MAX_SIGNERS];
230        let mut result = Multisig {
231            m: m[0],
232            n: n[0],
233            is_initialized: match is_initialized {
234                [0] => false,
235                [1] => true,
236                _ => return Err(ProgramError::InvalidAccountData),
237            },
238            signers: [Pubkey::new_from_array([0u8; 32]); MAX_SIGNERS],
239        };
240        for (src, dst) in signers_flat.chunks(32).zip(result.signers.iter_mut()) {
241            *dst = Pubkey::new(src);
242        }
243        Ok(result)
244    }
245    fn pack_into_slice(&self, dst: &mut [u8]) {
246        let dst = array_mut_ref![dst, 0, 355];
247        #[allow(clippy::ptr_offset_with_cast)]
248        let (m, n, is_initialized, signers_flat) = mut_array_refs![dst, 1, 1, 1, 32 * MAX_SIGNERS];
249        *m = [self.m];
250        *n = [self.n];
251        *is_initialized = [self.is_initialized as u8];
252        for (i, src) in self.signers.iter().enumerate() {
253            let dst_array = array_mut_ref![signers_flat, 32 * i, 32];
254            dst_array.copy_from_slice(src.as_ref());
255        }
256    }
257}
258
259// Helpers
260pub(crate) fn pack_coption_key(src: &COption<Pubkey>, dst: &mut [u8; 36]) {
261    let (tag, body) = mut_array_refs![dst, 4, 32];
262    match src {
263        COption::Some(key) => {
264            *tag = [1, 0, 0, 0];
265            body.copy_from_slice(key.as_ref());
266        }
267        COption::None => {
268            *tag = [0; 4];
269        }
270    }
271}
272pub(crate) fn unpack_coption_key(src: &[u8; 36]) -> Result<COption<Pubkey>, ProgramError> {
273    let (tag, body) = array_refs![src, 4, 32];
274    match *tag {
275        [0, 0, 0, 0] => Ok(COption::None),
276        [1, 0, 0, 0] => Ok(COption::Some(Pubkey::new_from_array(*body))),
277        _ => Err(ProgramError::InvalidAccountData),
278    }
279}
280fn pack_coption_u64(src: &COption<u64>, dst: &mut [u8; 12]) {
281    let (tag, body) = mut_array_refs![dst, 4, 8];
282    match src {
283        COption::Some(amount) => {
284            *tag = [1, 0, 0, 0];
285            *body = amount.to_le_bytes();
286        }
287        COption::None => {
288            *tag = [0; 4];
289        }
290    }
291}
292fn unpack_coption_u64(src: &[u8; 12]) -> Result<COption<u64>, ProgramError> {
293    let (tag, body) = array_refs![src, 4, 8];
294    match *tag {
295        [0, 0, 0, 0] => Ok(COption::None),
296        [1, 0, 0, 0] => Ok(COption::Some(u64::from_le_bytes(*body))),
297        _ => Err(ProgramError::InvalidAccountData),
298    }
299}
300
301// `safe_token_program_2022::extension::AccountType::Account` ordinal value
302const ACCOUNTTYPE_ACCOUNT: u8 = AccountType::Account as u8;
303impl GenericTokenAccount for Account {
304    fn valid_account_data(account_data: &[u8]) -> bool {
305        // Use safe_token::state::Account::valid_account_data once possible
306        account_data.len() == Account::LEN && is_initialized_account(account_data)
307            || (account_data.len() >= Account::LEN
308                && account_data.len() != Multisig::LEN
309                && ACCOUNTTYPE_ACCOUNT
310                    == *account_data
311                        .get(safe_token::state::Account::get_packed_len())
312                        .unwrap_or(&(AccountType::Uninitialized as u8))
313                && is_initialized_account(account_data))
314    }
315}
316
317#[cfg(test)]
318pub(crate) mod test {
319    use super::*;
320    use crate::generic_token_account::ACCOUNT_INITIALIZED_INDEX;
321
322    pub const TEST_MINT: Mint = Mint {
323        mint_authority: COption::Some(Pubkey::new_from_array([1; 32])),
324        supply: 42,
325        decimals: 7,
326        is_initialized: true,
327        freeze_authority: COption::Some(Pubkey::new_from_array([2; 32])),
328    };
329    pub const TEST_MINT_SLICE: &[u8] = &[
330        1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
331        1, 1, 1, 1, 1, 1, 42, 0, 0, 0, 0, 0, 0, 0, 7, 1, 1, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
332        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
333    ];
334
335    pub const TEST_ACCOUNT: Account = Account {
336        mint: Pubkey::new_from_array([1; 32]),
337        owner: Pubkey::new_from_array([2; 32]),
338        amount: 3,
339        delegate: COption::Some(Pubkey::new_from_array([4; 32])),
340        state: AccountState::Frozen,
341        is_native: COption::Some(5),
342        delegated_amount: 6,
343        close_authority: COption::Some(Pubkey::new_from_array([7; 32])),
344    };
345    pub const TEST_ACCOUNT_SLICE: &[u8] = &[
346        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
347        1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
348        2, 2, 2, 2, 3, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
349        4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 1, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0,
350        0, 6, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
351        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
352    ];
353
354    #[test]
355    fn test_pack_unpack() {
356        // Mint
357        let check = TEST_MINT;
358        let mut packed = vec![0; Mint::get_packed_len() + 1];
359        assert_eq!(
360            Err(ProgramError::InvalidAccountData),
361            Mint::pack(check, &mut packed)
362        );
363        let mut packed = vec![0; Mint::get_packed_len() - 1];
364        assert_eq!(
365            Err(ProgramError::InvalidAccountData),
366            Mint::pack(check, &mut packed)
367        );
368        let mut packed = vec![0; Mint::get_packed_len()];
369        Mint::pack(check, &mut packed).unwrap();
370        assert_eq!(packed, TEST_MINT_SLICE);
371        let unpacked = Mint::unpack(&packed).unwrap();
372        assert_eq!(unpacked, check);
373
374        // Account
375        let check = TEST_ACCOUNT;
376        let mut packed = vec![0; Account::get_packed_len() + 1];
377        assert_eq!(
378            Err(ProgramError::InvalidAccountData),
379            Account::pack(check, &mut packed)
380        );
381        let mut packed = vec![0; Account::get_packed_len() - 1];
382        assert_eq!(
383            Err(ProgramError::InvalidAccountData),
384            Account::pack(check, &mut packed)
385        );
386        let mut packed = vec![0; Account::get_packed_len()];
387        Account::pack(check, &mut packed).unwrap();
388        let expect = TEST_ACCOUNT_SLICE;
389        assert_eq!(packed, expect);
390        let unpacked = Account::unpack(&packed).unwrap();
391        assert_eq!(unpacked, check);
392
393        // Multisig
394        let check = Multisig {
395            m: 1,
396            n: 2,
397            is_initialized: true,
398            signers: [Pubkey::new(&[3; 32]); MAX_SIGNERS],
399        };
400        let mut packed = vec![0; Multisig::get_packed_len() + 1];
401        assert_eq!(
402            Err(ProgramError::InvalidAccountData),
403            Multisig::pack(check, &mut packed)
404        );
405        let mut packed = vec![0; Multisig::get_packed_len() - 1];
406        assert_eq!(
407            Err(ProgramError::InvalidAccountData),
408            Multisig::pack(check, &mut packed)
409        );
410        let mut packed = vec![0; Multisig::get_packed_len()];
411        Multisig::pack(check, &mut packed).unwrap();
412        let expect = vec![
413            1, 2, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
414            3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
415            3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
416            3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
417            3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
418            3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
419            3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
420            3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
421            3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
422            3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
423            3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
424            3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
425            3, 3, 3, 3, 3, 3, 3,
426        ];
427        assert_eq!(packed, expect);
428        let unpacked = Multisig::unpack(&packed).unwrap();
429        assert_eq!(unpacked, check);
430    }
431
432    #[test]
433    fn test_unpack_token_owner() {
434        // Account data length < Account::LEN, unpack will not return a key
435        let src: [u8; 12] = [0; 12];
436        let result = Account::unpack_account_owner(&src);
437        assert_eq!(result, Option::None);
438
439        // The right account data size and initialized, unpack will return some key
440        let mut src: [u8; Account::LEN] = [0; Account::LEN];
441        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
442        let result = Account::unpack_account_owner(&src);
443        assert!(result.is_some());
444
445        // The right account data size and frozen, unpack will return some key
446        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
447        let result = Account::unpack_account_owner(&src);
448        assert!(result.is_some());
449
450        // Account data length > account data size, but not a valid extension,
451        // unpack will not return a key
452        let mut src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
453        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
454        let result = Account::unpack_account_owner(&src);
455        assert_eq!(result, Option::None);
456
457        // Account data length > account data size with a valid extension and initialized,
458        // expect some key returned
459        let mut src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
460        src[Account::LEN] = AccountType::Account as u8;
461        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
462        let result = Account::unpack_account_owner(&src);
463        assert!(result.is_some());
464
465        // Account data length > account data size with a valid extension but uninitialized,
466        // expect None
467        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
468        let result = Account::unpack_account_owner(&src);
469        assert!(result.is_none());
470
471        // Account data length is multi-sig data size with a valid extension and initialized,
472        // expect none
473        let mut src: [u8; Multisig::LEN] = [0; Multisig::LEN];
474        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
475        src[Account::LEN] = AccountType::Account as u8;
476        let result = Account::unpack_account_owner(&src);
477        assert!(result.is_none());
478    }
479
480    #[test]
481    fn test_unpack_token_mint() {
482        // Account data length < Account::LEN, unpack will not return a key
483        let src: [u8; 12] = [0; 12];
484        let result = Account::unpack_account_mint(&src);
485        assert_eq!(result, Option::None);
486
487        // The right account data size and initialized, unpack will return some key
488        let mut src: [u8; Account::LEN] = [0; Account::LEN];
489        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
490        let result = Account::unpack_account_mint(&src);
491        assert!(result.is_some());
492
493        // The right account data size and frozen, unpack will return some key
494        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
495        let result = Account::unpack_account_mint(&src);
496        assert!(result.is_some());
497
498        // Account data length > account data size, but not a valid extension,
499        // unpack will not return a key
500        let mut src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
501        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
502        let result = Account::unpack_account_mint(&src);
503        assert_eq!(result, Option::None);
504
505        // Account data length > account data size with a valid extension and initialized,
506        // expect some key returned
507        let mut src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
508        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
509        src[Account::LEN] = AccountType::Account as u8;
510        let result = Account::unpack_account_mint(&src);
511        assert!(result.is_some());
512
513        // Account data length > account data size with a valid extension but uninitialized,
514        // expect none
515        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
516        let result = Account::unpack_account_mint(&src);
517        assert!(result.is_none());
518
519        // Account data length is multi-sig data size with a valid extension and initialized,
520        // expect none
521        let mut src: [u8; Multisig::LEN] = [0; Multisig::LEN];
522        src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
523        src[Account::LEN] = AccountType::Account as u8;
524        let result = Account::unpack_account_mint(&src);
525        assert!(result.is_none());
526    }
527}