pinocchio_token/state/
token.rs

1use super::AccountState;
2use pinocchio::{
3    account_info::{AccountInfo, Ref},
4    program_error::ProgramError,
5    pubkey::Pubkey,
6};
7
8use crate::ID;
9
10/// Token account data.
11#[repr(C)]
12pub struct TokenAccount {
13    /// The mint associated with this account
14    mint: Pubkey,
15
16    /// The owner of this account.
17    owner: Pubkey,
18
19    /// The amount of tokens this account holds.
20    amount: [u8; 8],
21
22    /// Indicates whether the delegate is present or not.
23    delegate_flag: [u8; 4],
24
25    /// If `delegate` is `Some` then `delegated_amount` represents
26    /// the amount authorized by the delegate.
27    delegate: Pubkey,
28
29    /// The account's state.
30    state: u8,
31
32    /// Indicates whether this account represents a native token or not.
33    is_native: [u8; 4],
34
35    /// When `is_native.is_some()` is `true`, this is a native token, and the
36    /// value logs the rent-exempt reserve. An Account is required to be rent-exempt,
37    /// so the value is used by the Processor to ensure that wrapped SOL
38    /// accounts do not drop below this threshold.
39    native_amount: [u8; 8],
40
41    /// The amount delegated.
42    delegated_amount: [u8; 8],
43
44    /// Indicates whether the close authority is present or not.
45    close_authority_flag: [u8; 4],
46
47    /// Optional authority to close the account.
48    close_authority: Pubkey,
49}
50
51impl TokenAccount {
52    pub const LEN: usize = core::mem::size_of::<TokenAccount>();
53
54    /// Return a `TokenAccount` from the given account info.
55    ///
56    /// This method performs owner and length validation on `AccountInfo`, safe borrowing
57    /// the account data.
58    #[inline]
59    pub fn from_account_info(
60        account_info: &AccountInfo,
61    ) -> Result<Ref<TokenAccount>, ProgramError> {
62        if account_info.data_len() != Self::LEN {
63            return Err(ProgramError::InvalidAccountData);
64        }
65        if !account_info.is_owned_by(&ID) {
66            return Err(ProgramError::InvalidAccountData);
67        }
68        Ok(Ref::map(account_info.try_borrow_data()?, |data| unsafe {
69            Self::from_bytes_unchecked(data)
70        }))
71    }
72
73    /// Return a `TokenAccount` from the given account info.
74    ///
75    /// This method performs owner and length validation on `AccountInfo`, but does not
76    /// perform the borrow check.
77    ///
78    /// # Safety
79    ///
80    /// The caller must ensure that it is safe to borrow the account data (e.g., there are
81    /// no mutable borrows of the account data).
82    #[inline]
83    pub unsafe fn from_account_info_unchecked(
84        account_info: &AccountInfo,
85    ) -> Result<&TokenAccount, ProgramError> {
86        if account_info.data_len() != Self::LEN {
87            return Err(ProgramError::InvalidAccountData);
88        }
89        if account_info.owner() != &ID {
90            return Err(ProgramError::InvalidAccountData);
91        }
92        Ok(Self::from_bytes_unchecked(
93            account_info.borrow_data_unchecked(),
94        ))
95    }
96
97    /// Return a `TokenAccount` from the given bytes.
98    ///
99    /// # Safety
100    ///
101    /// The caller must ensure that `bytes` contains a valid representation of `TokenAccount`, and
102    /// it is properly aligned to be interpreted as an instance of `TokenAccount`.
103    /// At the moment `TokenAccount` has an alignment of 1 byte.
104    /// This method does not perform a length validation.
105    #[inline(always)]
106    pub unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self {
107        &*(bytes.as_ptr() as *const TokenAccount)
108    }
109
110    pub fn mint(&self) -> &Pubkey {
111        &self.mint
112    }
113
114    pub fn owner(&self) -> &Pubkey {
115        &self.owner
116    }
117
118    pub fn amount(&self) -> u64 {
119        u64::from_le_bytes(self.amount)
120    }
121
122    #[inline(always)]
123    pub fn has_delegate(&self) -> bool {
124        self.delegate_flag[0] == 1
125    }
126
127    pub fn delegate(&self) -> Option<&Pubkey> {
128        if self.has_delegate() {
129            Some(self.delegate_unchecked())
130        } else {
131            None
132        }
133    }
134
135    /// Use this when you know the account will have a delegate and want to skip the `Option` check.
136    #[inline(always)]
137    pub fn delegate_unchecked(&self) -> &Pubkey {
138        &self.delegate
139    }
140
141    #[inline(always)]
142    pub fn state(&self) -> AccountState {
143        self.state.into()
144    }
145
146    #[inline(always)]
147    pub fn is_native(&self) -> bool {
148        self.is_native[0] == 1
149    }
150
151    pub fn native_amount(&self) -> Option<u64> {
152        if self.is_native() {
153            Some(self.native_amount_unchecked())
154        } else {
155            None
156        }
157    }
158
159    /// Return the native amount.
160    ///
161    /// This method should be used when the caller knows that the token is native since it
162    /// skips the `Option` check.
163    #[inline(always)]
164    pub fn native_amount_unchecked(&self) -> u64 {
165        u64::from_le_bytes(self.native_amount)
166    }
167
168    pub fn delegated_amount(&self) -> u64 {
169        u64::from_le_bytes(self.delegated_amount)
170    }
171
172    #[inline(always)]
173    pub fn has_close_authority(&self) -> bool {
174        self.close_authority_flag[0] == 1
175    }
176
177    pub fn close_authority(&self) -> Option<&Pubkey> {
178        if self.has_close_authority() {
179            Some(self.close_authority_unchecked())
180        } else {
181            None
182        }
183    }
184
185    /// Return the close authority.
186    ///
187    /// This method should be used when the caller knows that the token will have a close
188    /// authority set since it skips the `Option` check.
189    #[inline(always)]
190    pub fn close_authority_unchecked(&self) -> &Pubkey {
191        &self.close_authority
192    }
193
194    #[inline(always)]
195    pub fn is_initialized(&self) -> bool {
196        self.state != AccountState::Uninitialized as u8
197    }
198
199    #[inline(always)]
200    pub fn is_frozen(&self) -> bool {
201        self.state == AccountState::Frozen as u8
202    }
203}