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