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