pinocchio_token/state/
token.rs

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