1use {
4 crate::instruction::MAX_SIGNERS,
5 arch_program::{
6 program_error::ProgramError,
7 program_option::COption,
8 program_pack::{IsInitialized, Pack, Sealed},
9 pubkey::{Pubkey, PUBKEY_BYTES},
10 },
11 arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs},
12 num_enum::TryFromPrimitive,
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(&self) -> bool {
122 self.owner == arch_program::system_program::SYSTEM_PROGRAM_ID
123 }
124}
125
126impl Sealed for Account {}
127impl IsInitialized for Account {
128 fn is_initialized(&self) -> bool {
129 self.state != AccountState::Uninitialized
130 }
131}
132impl Pack for Account {
133 const LEN: usize = 165;
134 fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
135 let src = array_ref![src, 0, 165];
136 let (mint, owner, amount, delegate, state, is_native, delegated_amount, close_authority) =
137 array_refs![src, 32, 32, 8, 36, 1, 12, 8, 36];
138 Ok(Account {
139 mint: Pubkey::from_slice(mint),
140 owner: Pubkey::from_slice(owner),
141 amount: u64::from_le_bytes(*amount),
142 delegate: unpack_coption_key(delegate)?,
143 state: AccountState::try_from_primitive(state[0])
144 .or(Err(ProgramError::InvalidAccountData))?,
145 is_native: unpack_coption_u64(is_native)?,
146 delegated_amount: u64::from_le_bytes(*delegated_amount),
147 close_authority: unpack_coption_key(close_authority)?,
148 })
149 }
150 fn pack_into_slice(&self, dst: &mut [u8]) {
151 let dst = array_mut_ref![dst, 0, 165];
152 let (
153 mint_dst,
154 owner_dst,
155 amount_dst,
156 delegate_dst,
157 state_dst,
158 is_native_dst,
159 delegated_amount_dst,
160 close_authority_dst,
161 ) = mut_array_refs![dst, 32, 32, 8, 36, 1, 12, 8, 36];
162 let &Account {
163 ref mint,
164 ref owner,
165 amount,
166 ref delegate,
167 state,
168 ref is_native,
169 delegated_amount,
170 ref close_authority,
171 } = self;
172 mint_dst.copy_from_slice(mint.as_ref());
173 owner_dst.copy_from_slice(owner.as_ref());
174 *amount_dst = amount.to_le_bytes();
175 pack_coption_key(delegate, delegate_dst);
176 state_dst[0] = state as u8;
177 pack_coption_u64(is_native, is_native_dst);
178 *delegated_amount_dst = delegated_amount.to_le_bytes();
179 pack_coption_key(close_authority, close_authority_dst);
180 }
181}
182
183#[repr(u8)]
185#[derive(Clone, Copy, Debug, Default, PartialEq, TryFromPrimitive)]
186pub enum AccountState {
187 #[default]
189 Uninitialized,
190 Initialized,
193 Frozen,
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::from_slice(&[0u8; 32]); MAX_SIGNERS],
233 };
234 for (src, dst) in signers_flat.chunks(32).zip(result.signers.iter_mut()) {
235 *dst = Pubkey::from_slice(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::from_slice(body))),
271 _ => Err(ProgramError::InvalidAccountData),
272 }
273}
274#[allow(dead_code)]
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}
287#[allow(dead_code)]
288fn unpack_coption_u64(src: &[u8; 12]) -> Result<COption<u64>, ProgramError> {
289 let (tag, body) = array_refs![src, 4, 8];
290 match *tag {
291 [0, 0, 0, 0] => Ok(COption::None),
292 [1, 0, 0, 0] => Ok(COption::Some(u64::from_le_bytes(*body))),
293 _ => Err(ProgramError::InvalidAccountData),
294 }
295}
296
297const SPL_TOKEN_ACCOUNT_MINT_OFFSET: usize = 0;
298const SPL_TOKEN_ACCOUNT_OWNER_OFFSET: usize = 32;
299
300pub trait GenericTokenAccount {
303 fn valid_account_data(account_data: &[u8]) -> bool;
305
306 fn unpack_account_owner_unchecked(account_data: &[u8]) -> &Pubkey {
309 Self::unpack_pubkey_unchecked(account_data, SPL_TOKEN_ACCOUNT_OWNER_OFFSET)
310 }
311
312 fn unpack_account_mint_unchecked(account_data: &[u8]) -> &Pubkey {
315 Self::unpack_pubkey_unchecked(account_data, SPL_TOKEN_ACCOUNT_MINT_OFFSET)
316 }
317
318 fn unpack_pubkey_unchecked(account_data: &[u8], offset: usize) -> &Pubkey {
322 bytemuck::from_bytes(&account_data[offset..offset + PUBKEY_BYTES])
323 }
324
325 fn unpack_account_owner(account_data: &[u8]) -> Option<&Pubkey> {
327 if Self::valid_account_data(account_data) {
328 Some(Self::unpack_account_owner_unchecked(account_data))
329 } else {
330 None
331 }
332 }
333
334 fn unpack_account_mint(account_data: &[u8]) -> Option<&Pubkey> {
336 if Self::valid_account_data(account_data) {
337 Some(Self::unpack_account_mint_unchecked(account_data))
338 } else {
339 None
340 }
341 }
342}
343
344pub const ACCOUNT_INITIALIZED_INDEX: usize = 108;
346
347pub fn is_initialized_account(account_data: &[u8]) -> bool {
350 *account_data
351 .get(ACCOUNT_INITIALIZED_INDEX)
352 .unwrap_or(&(AccountState::Uninitialized as u8))
353 != AccountState::Uninitialized as u8
354}
355
356impl GenericTokenAccount for Account {
357 fn valid_account_data(account_data: &[u8]) -> bool {
358 account_data.len() == Account::LEN && is_initialized_account(account_data)
359 }
360}
361
362pub fn is_rent_exempt_account(lamports: u64, account_data_len: usize) -> bool {
364 lamports >= arch_program::rent::minimum_rent(account_data_len)
365}
366
367#[cfg(test)]
368mod tests {
369 use super::*;
370
371 #[test]
372 fn test_mint_unpack_from_slice() {
373 let src: [u8; 82] = [0; 82];
374 let mint = Mint::unpack_from_slice(&src).unwrap();
375 assert!(!mint.is_initialized);
376
377 let mut src: [u8; 82] = [0; 82];
378 src[45] = 2;
379 let mint = Mint::unpack_from_slice(&src).unwrap_err();
380 assert_eq!(mint, ProgramError::InvalidAccountData);
381 }
382
383 #[test]
384 fn test_account_state() {
385 let account_state = AccountState::default();
386 assert_eq!(account_state, AccountState::Uninitialized);
387 }
388
389 #[test]
390 fn test_multisig_unpack_from_slice() {
391 let src: [u8; 355] = [0; 355];
392 let multisig = Multisig::unpack_from_slice(&src).unwrap();
393 assert_eq!(multisig.m, 0);
394 assert_eq!(multisig.n, 0);
395 assert!(!multisig.is_initialized);
396
397 let mut src: [u8; 355] = [0; 355];
398 src[0] = 1;
399 src[1] = 1;
400 src[2] = 1;
401 let multisig = Multisig::unpack_from_slice(&src).unwrap();
402 assert_eq!(multisig.m, 1);
403 assert_eq!(multisig.n, 1);
404 assert!(multisig.is_initialized);
405
406 let mut src: [u8; 355] = [0; 355];
407 src[2] = 2;
408 let multisig = Multisig::unpack_from_slice(&src).unwrap_err();
409 assert_eq!(multisig, ProgramError::InvalidAccountData);
410 }
411
412 #[test]
413 fn test_unpack_coption_key() {
414 let src: [u8; 36] = [0; 36];
415 let result = unpack_coption_key(&src).unwrap();
416 assert_eq!(result, COption::None);
417
418 let mut src: [u8; 36] = [0; 36];
419 src[1] = 1;
420 let result = unpack_coption_key(&src).unwrap_err();
421 assert_eq!(result, ProgramError::InvalidAccountData);
422 }
423
424 #[test]
425 fn test_unpack_coption_u64() {
426 let src: [u8; 12] = [0; 12];
427 let result = unpack_coption_u64(&src).unwrap();
428 assert_eq!(result, COption::None);
429
430 let mut src: [u8; 12] = [0; 12];
431 src[0] = 1;
432 let result = unpack_coption_u64(&src).unwrap();
433 assert_eq!(result, COption::Some(0));
434
435 let mut src: [u8; 12] = [0; 12];
436 src[1] = 1;
437 let result = unpack_coption_u64(&src).unwrap_err();
438 assert_eq!(result, ProgramError::InvalidAccountData);
439 }
440
441 #[test]
442 fn test_unpack_token_owner() {
443 let src: [u8; 12] = [0; 12];
445 let result = Account::unpack_account_owner(&src);
446 assert_eq!(result, Option::None);
447
448 let mut src: [u8; Account::LEN] = [0; Account::LEN];
450 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
451 let result = Account::unpack_account_owner(&src);
452 assert!(result.is_some());
453
454 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
456 let result = Account::unpack_account_owner(&src);
457 assert!(result.is_some());
458
459 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
461 let result = Account::unpack_account_mint(&src);
462 assert_eq!(result, Option::None);
463
464 let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
466 let result = Account::unpack_account_owner(&src);
467 assert_eq!(result, Option::None);
468 }
469
470 #[test]
471 fn test_unpack_token_mint() {
472 let src: [u8; 12] = [0; 12];
474 let result = Account::unpack_account_mint(&src);
475 assert_eq!(result, Option::None);
476
477 let mut src: [u8; Account::LEN] = [0; Account::LEN];
479 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
480 let result = Account::unpack_account_mint(&src);
481 assert!(result.is_some());
482
483 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
485 let result = Account::unpack_account_mint(&src);
486 assert!(result.is_some());
487
488 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
490 let result = Account::unpack_account_mint(&src);
491 assert_eq!(result, Option::None);
492
493 let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
495 let result = Account::unpack_account_mint(&src);
496 assert_eq!(result, Option::None);
497 }
498}