1use crate::instruction::MAX_SIGNERS;
4use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
5use num_enum::TryFromPrimitive;
6use solana_program::{
7 program_error::ProgramError,
8 program_option::COption,
9 program_pack::{IsInitialized, Pack, Sealed},
10 pubkey::{Pubkey, PUBKEY_BYTES},
11};
12
13#[repr(C)]
15#[derive(Clone, Copy, Debug, Default, PartialEq)]
16pub struct Mint {
17 pub mint_authority: COption<Pubkey>,
21 pub supply: u64,
23 pub decimals: u8,
25 pub is_initialized: bool,
27 pub freeze_authority: COption<Pubkey>,
29}
30impl Sealed for Mint {}
31impl IsInitialized for Mint {
32 fn is_initialized(&self) -> bool {
33 self.is_initialized
34 }
35}
36impl Pack for Mint {
37 const LEN: usize = 82;
38 fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
39 let src = array_ref![src, 0, 82];
40 let (mint_authority, supply, decimals, is_initialized, freeze_authority) =
41 array_refs![src, 36, 8, 1, 1, 36];
42 let mint_authority = unpack_coption_key(mint_authority)?;
43 let supply = u64::from_le_bytes(*supply);
44 let decimals = decimals[0];
45 let is_initialized = match is_initialized {
46 [0] => false,
47 [1] => true,
48 _ => return Err(ProgramError::InvalidAccountData),
49 };
50 let freeze_authority = unpack_coption_key(freeze_authority)?;
51 Ok(Mint {
52 mint_authority,
53 supply,
54 decimals,
55 is_initialized,
56 freeze_authority,
57 })
58 }
59 fn pack_into_slice(&self, dst: &mut [u8]) {
60 let dst = array_mut_ref![dst, 0, 82];
61 let (
62 mint_authority_dst,
63 supply_dst,
64 decimals_dst,
65 is_initialized_dst,
66 freeze_authority_dst,
67 ) = mut_array_refs![dst, 36, 8, 1, 1, 36];
68 let &Mint {
69 ref mint_authority,
70 supply,
71 decimals,
72 is_initialized,
73 ref freeze_authority,
74 } = self;
75 pack_coption_key(mint_authority, mint_authority_dst);
76 *supply_dst = supply.to_le_bytes();
77 decimals_dst[0] = decimals;
78 is_initialized_dst[0] = is_initialized as u8;
79 pack_coption_key(freeze_authority, freeze_authority_dst);
80 }
81}
82
83#[repr(C)]
85#[derive(Clone, Copy, Debug, Default, PartialEq)]
86pub struct Account {
87 pub mint: Pubkey,
89 pub owner: Pubkey,
91 pub amount: u64,
93 pub delegate: COption<Pubkey>,
96 pub state: AccountState,
98 pub is_native: COption<u64>,
102 pub delegated_amount: u64,
104 pub close_authority: COption<Pubkey>,
106}
107impl Account {
108 pub fn is_frozen(&self) -> bool {
110 self.state == AccountState::Frozen
111 }
112 pub fn is_native(&self) -> bool {
114 self.is_native.is_some()
115 }
116 pub fn is_owned_by_system_program_or_incinerator(&self) -> bool {
118 solana_program::system_program::check_id(&self.owner)
119 || solana_program::incinerator::check_id(&self.owner)
120 }
121}
122impl Sealed for Account {}
123impl IsInitialized for Account {
124 fn is_initialized(&self) -> bool {
125 self.state != AccountState::Uninitialized
126 }
127}
128impl Pack for Account {
129 const LEN: usize = 165;
130 fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
131 let src = array_ref![src, 0, 165];
132 let (mint, owner, amount, delegate, state, is_native, delegated_amount, close_authority) =
133 array_refs![src, 32, 32, 8, 36, 1, 12, 8, 36];
134 Ok(Account {
135 mint: Pubkey::new_from_array(*mint),
136 owner: Pubkey::new_from_array(*owner),
137 amount: u64::from_le_bytes(*amount),
138 delegate: unpack_coption_key(delegate)?,
139 state: AccountState::try_from_primitive(state[0])
140 .or(Err(ProgramError::InvalidAccountData))?,
141 is_native: unpack_coption_u64(is_native)?,
142 delegated_amount: u64::from_le_bytes(*delegated_amount),
143 close_authority: unpack_coption_key(close_authority)?,
144 })
145 }
146 fn pack_into_slice(&self, dst: &mut [u8]) {
147 let dst = array_mut_ref![dst, 0, 165];
148 let (
149 mint_dst,
150 owner_dst,
151 amount_dst,
152 delegate_dst,
153 state_dst,
154 is_native_dst,
155 delegated_amount_dst,
156 close_authority_dst,
157 ) = mut_array_refs![dst, 32, 32, 8, 36, 1, 12, 8, 36];
158 let &Account {
159 ref mint,
160 ref owner,
161 amount,
162 ref delegate,
163 state,
164 ref is_native,
165 delegated_amount,
166 ref close_authority,
167 } = self;
168 mint_dst.copy_from_slice(mint.as_ref());
169 owner_dst.copy_from_slice(owner.as_ref());
170 *amount_dst = amount.to_le_bytes();
171 pack_coption_key(delegate, delegate_dst);
172 state_dst[0] = state as u8;
173 pack_coption_u64(is_native, is_native_dst);
174 *delegated_amount_dst = delegated_amount.to_le_bytes();
175 pack_coption_key(close_authority, close_authority_dst);
176 }
177}
178
179#[repr(u8)]
181#[derive(Clone, Copy, Debug, PartialEq, TryFromPrimitive)]
182pub enum AccountState {
183 Uninitialized,
185 Initialized,
188 Frozen,
191}
192
193impl Default for AccountState {
194 fn default() -> Self {
195 AccountState::Uninitialized
196 }
197}
198
199#[repr(C)]
201#[derive(Clone, Copy, Debug, Default, PartialEq)]
202pub struct Multisig {
203 pub m: u8,
205 pub n: u8,
207 pub is_initialized: bool,
209 pub signers: [Pubkey; MAX_SIGNERS],
211}
212impl Sealed for Multisig {}
213impl IsInitialized for Multisig {
214 fn is_initialized(&self) -> bool {
215 self.is_initialized
216 }
217}
218impl Pack for Multisig {
219 const LEN: usize = 355;
220 fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
221 let src = array_ref![src, 0, 355];
222 #[allow(clippy::ptr_offset_with_cast)]
223 let (m, n, is_initialized, signers_flat) = array_refs![src, 1, 1, 1, 32 * MAX_SIGNERS];
224 let mut result = Multisig {
225 m: m[0],
226 n: n[0],
227 is_initialized: match is_initialized {
228 [0] => false,
229 [1] => true,
230 _ => return Err(ProgramError::InvalidAccountData),
231 },
232 signers: [Pubkey::new_from_array([0u8; 32]); MAX_SIGNERS],
233 };
234 for (src, dst) in signers_flat.chunks(32).zip(result.signers.iter_mut()) {
235 *dst = Pubkey::new(src);
236 }
237 Ok(result)
238 }
239 fn pack_into_slice(&self, dst: &mut [u8]) {
240 let dst = array_mut_ref![dst, 0, 355];
241 #[allow(clippy::ptr_offset_with_cast)]
242 let (m, n, is_initialized, signers_flat) = mut_array_refs![dst, 1, 1, 1, 32 * MAX_SIGNERS];
243 *m = [self.m];
244 *n = [self.n];
245 *is_initialized = [self.is_initialized as u8];
246 for (i, src) in self.signers.iter().enumerate() {
247 let dst_array = array_mut_ref![signers_flat, 32 * i, 32];
248 dst_array.copy_from_slice(src.as_ref());
249 }
250 }
251}
252
253fn pack_coption_key(src: &COption<Pubkey>, dst: &mut [u8; 36]) {
255 let (tag, body) = mut_array_refs![dst, 4, 32];
256 match src {
257 COption::Some(key) => {
258 *tag = [1, 0, 0, 0];
259 body.copy_from_slice(key.as_ref());
260 }
261 COption::None => {
262 *tag = [0; 4];
263 }
264 }
265}
266fn unpack_coption_key(src: &[u8; 36]) -> Result<COption<Pubkey>, ProgramError> {
267 let (tag, body) = array_refs![src, 4, 32];
268 match *tag {
269 [0, 0, 0, 0] => Ok(COption::None),
270 [1, 0, 0, 0] => Ok(COption::Some(Pubkey::new_from_array(*body))),
271 _ => Err(ProgramError::InvalidAccountData),
272 }
273}
274fn pack_coption_u64(src: &COption<u64>, dst: &mut [u8; 12]) {
275 let (tag, body) = mut_array_refs![dst, 4, 8];
276 match src {
277 COption::Some(amount) => {
278 *tag = [1, 0, 0, 0];
279 *body = amount.to_le_bytes();
280 }
281 COption::None => {
282 *tag = [0; 4];
283 }
284 }
285}
286fn unpack_coption_u64(src: &[u8; 12]) -> Result<COption<u64>, ProgramError> {
287 let (tag, body) = array_refs![src, 4, 8];
288 match *tag {
289 [0, 0, 0, 0] => Ok(COption::None),
290 [1, 0, 0, 0] => Ok(COption::Some(u64::from_le_bytes(*body))),
291 _ => Err(ProgramError::InvalidAccountData),
292 }
293}
294
295const SAFE_TOKEN_ACCOUNT_MINT_OFFSET: usize = 0;
296const SAFE_TOKEN_ACCOUNT_OWNER_OFFSET: usize = 32;
297
298pub trait GenericTokenAccount {
301 fn valid_account_data(account_data: &[u8]) -> bool;
303
304 fn unpack_account_owner_unchecked(account_data: &[u8]) -> &Pubkey {
306 Self::unpack_pubkey_unchecked(account_data, SAFE_TOKEN_ACCOUNT_OWNER_OFFSET)
307 }
308
309 fn unpack_account_mint_unchecked(account_data: &[u8]) -> &Pubkey {
311 Self::unpack_pubkey_unchecked(account_data, SAFE_TOKEN_ACCOUNT_MINT_OFFSET)
312 }
313
314 fn unpack_pubkey_unchecked(account_data: &[u8], offset: usize) -> &Pubkey {
317 bytemuck::from_bytes(&account_data[offset..offset + PUBKEY_BYTES])
318 }
319
320 fn unpack_account_owner(account_data: &[u8]) -> Option<&Pubkey> {
322 if Self::valid_account_data(account_data) {
323 Some(Self::unpack_account_owner_unchecked(account_data))
324 } else {
325 None
326 }
327 }
328
329 fn unpack_account_mint(account_data: &[u8]) -> Option<&Pubkey> {
331 if Self::valid_account_data(account_data) {
332 Some(Self::unpack_account_mint_unchecked(account_data))
333 } else {
334 None
335 }
336 }
337}
338
339pub const ACCOUNT_INITIALIZED_INDEX: usize = 108;
341
342pub fn is_initialized_account(account_data: &[u8]) -> bool {
345 *account_data
346 .get(ACCOUNT_INITIALIZED_INDEX)
347 .unwrap_or(&(AccountState::Uninitialized as u8))
348 != AccountState::Uninitialized as u8
349}
350
351impl GenericTokenAccount for Account {
352 fn valid_account_data(account_data: &[u8]) -> bool {
353 account_data.len() == Account::LEN && is_initialized_account(account_data)
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360
361 #[test]
362 fn test_mint_unpack_from_slice() {
363 let src: [u8; 82] = [0; 82];
364 let mint = Mint::unpack_from_slice(&src).unwrap();
365 assert!(!mint.is_initialized);
366
367 let mut src: [u8; 82] = [0; 82];
368 src[45] = 2;
369 let mint = Mint::unpack_from_slice(&src).unwrap_err();
370 assert_eq!(mint, ProgramError::InvalidAccountData);
371 }
372
373 #[test]
374 fn test_account_state() {
375 let account_state = AccountState::default();
376 assert_eq!(account_state, AccountState::Uninitialized);
377 }
378
379 #[test]
380 fn test_multisig_unpack_from_slice() {
381 let src: [u8; 355] = [0; 355];
382 let multisig = Multisig::unpack_from_slice(&src).unwrap();
383 assert_eq!(multisig.m, 0);
384 assert_eq!(multisig.n, 0);
385 assert!(!multisig.is_initialized);
386
387 let mut src: [u8; 355] = [0; 355];
388 src[0] = 1;
389 src[1] = 1;
390 src[2] = 1;
391 let multisig = Multisig::unpack_from_slice(&src).unwrap();
392 assert_eq!(multisig.m, 1);
393 assert_eq!(multisig.n, 1);
394 assert!(multisig.is_initialized);
395
396 let mut src: [u8; 355] = [0; 355];
397 src[2] = 2;
398 let multisig = Multisig::unpack_from_slice(&src).unwrap_err();
399 assert_eq!(multisig, ProgramError::InvalidAccountData);
400 }
401
402 #[test]
403 fn test_unpack_coption_key() {
404 let src: [u8; 36] = [0; 36];
405 let result = unpack_coption_key(&src).unwrap();
406 assert_eq!(result, COption::None);
407
408 let mut src: [u8; 36] = [0; 36];
409 src[1] = 1;
410 let result = unpack_coption_key(&src).unwrap_err();
411 assert_eq!(result, ProgramError::InvalidAccountData);
412 }
413
414 #[test]
415 fn test_unpack_coption_u64() {
416 let src: [u8; 12] = [0; 12];
417 let result = unpack_coption_u64(&src).unwrap();
418 assert_eq!(result, COption::None);
419
420 let mut src: [u8; 12] = [0; 12];
421 src[0] = 1;
422 let result = unpack_coption_u64(&src).unwrap();
423 assert_eq!(result, COption::Some(0));
424
425 let mut src: [u8; 12] = [0; 12];
426 src[1] = 1;
427 let result = unpack_coption_u64(&src).unwrap_err();
428 assert_eq!(result, ProgramError::InvalidAccountData);
429 }
430
431 #[test]
432 fn test_unpack_token_owner() {
433 let src: [u8; 12] = [0; 12];
435 let result = Account::unpack_account_owner(&src);
436 assert_eq!(result, Option::None);
437
438 let mut src: [u8; Account::LEN] = [0; Account::LEN];
440 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
441 let result = Account::unpack_account_owner(&src);
442 assert!(result.is_some());
443
444 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
446 let result = Account::unpack_account_owner(&src);
447 assert!(result.is_some());
448
449 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
451 let result = Account::unpack_account_mint(&src);
452 assert_eq!(result, Option::None);
453
454 let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
456 let result = Account::unpack_account_owner(&src);
457 assert_eq!(result, Option::None);
458 }
459
460 #[test]
461 fn test_unpack_token_mint() {
462 let src: [u8; 12] = [0; 12];
464 let result = Account::unpack_account_mint(&src);
465 assert_eq!(result, Option::None);
466
467 let mut src: [u8; Account::LEN] = [0; Account::LEN];
469 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
470 let result = Account::unpack_account_mint(&src);
471 assert!(result.is_some());
472
473 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
475 let result = Account::unpack_account_mint(&src);
476 assert!(result.is_some());
477
478 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
480 let result = Account::unpack_account_mint(&src);
481 assert_eq!(result, Option::None);
482
483 let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
485 let result = Account::unpack_account_mint(&src);
486 assert_eq!(result, Option::None);
487 }
488}