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}
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 SPL_TOKEN_ACCOUNT_MINT_OFFSET: usize = 0;
296const SPL_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 {
307 Self::unpack_pubkey_unchecked(account_data, SPL_TOKEN_ACCOUNT_OWNER_OFFSET)
308 }
309
310 fn unpack_account_mint_unchecked(account_data: &[u8]) -> &Pubkey {
313 Self::unpack_pubkey_unchecked(account_data, SPL_TOKEN_ACCOUNT_MINT_OFFSET)
314 }
315
316 fn unpack_pubkey_unchecked(account_data: &[u8], offset: usize) -> &Pubkey {
320 bytemuck::from_bytes(&account_data[offset..offset + PUBKEY_BYTES])
321 }
322
323 fn unpack_account_owner(account_data: &[u8]) -> Option<&Pubkey> {
325 if Self::valid_account_data(account_data) {
326 Some(Self::unpack_account_owner_unchecked(account_data))
327 } else {
328 None
329 }
330 }
331
332 fn unpack_account_mint(account_data: &[u8]) -> Option<&Pubkey> {
334 if Self::valid_account_data(account_data) {
335 Some(Self::unpack_account_mint_unchecked(account_data))
336 } else {
337 None
338 }
339 }
340}
341
342pub const ACCOUNT_INITIALIZED_INDEX: usize = 108;
344
345pub fn is_initialized_account(account_data: &[u8]) -> bool {
348 *account_data
349 .get(ACCOUNT_INITIALIZED_INDEX)
350 .unwrap_or(&(AccountState::Uninitialized as u8))
351 != AccountState::Uninitialized as u8
352}
353
354impl GenericTokenAccount for Account {
355 fn valid_account_data(account_data: &[u8]) -> bool {
356 account_data.len() == Account::LEN && is_initialized_account(account_data)
357 }
358}
359
360pub fn is_rent_exempt_account(lamports: u64, account_data_len: usize) -> bool {
362 lamports >= arch_program::rent::minimum_rent(account_data_len)
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368
369 #[test]
370 fn test_mint_unpack_from_slice() {
371 let src: [u8; 82] = [0; 82];
372 let mint = Mint::unpack_from_slice(&src).unwrap();
373 assert!(!mint.is_initialized);
374
375 let mut src: [u8; 82] = [0; 82];
376 src[45] = 2;
377 let mint = Mint::unpack_from_slice(&src).unwrap_err();
378 assert_eq!(mint, ProgramError::InvalidAccountData);
379 }
380
381 #[test]
382 fn test_account_state() {
383 let account_state = AccountState::default();
384 assert_eq!(account_state, AccountState::Uninitialized);
385 }
386
387 #[test]
388 fn test_multisig_unpack_from_slice() {
389 let src: [u8; 355] = [0; 355];
390 let multisig = Multisig::unpack_from_slice(&src).unwrap();
391 assert_eq!(multisig.m, 0);
392 assert_eq!(multisig.n, 0);
393 assert!(!multisig.is_initialized);
394
395 let mut src: [u8; 355] = [0; 355];
396 src[0] = 1;
397 src[1] = 1;
398 src[2] = 1;
399 let multisig = Multisig::unpack_from_slice(&src).unwrap();
400 assert_eq!(multisig.m, 1);
401 assert_eq!(multisig.n, 1);
402 assert!(multisig.is_initialized);
403
404 let mut src: [u8; 355] = [0; 355];
405 src[2] = 2;
406 let multisig = Multisig::unpack_from_slice(&src).unwrap_err();
407 assert_eq!(multisig, ProgramError::InvalidAccountData);
408 }
409
410 #[test]
411 fn test_unpack_coption_key() {
412 let src: [u8; 36] = [0; 36];
413 let result = unpack_coption_key(&src).unwrap();
414 assert_eq!(result, COption::None);
415
416 let mut src: [u8; 36] = [0; 36];
417 src[1] = 1;
418 let result = unpack_coption_key(&src).unwrap_err();
419 assert_eq!(result, ProgramError::InvalidAccountData);
420 }
421
422 #[test]
423 fn test_unpack_coption_u64() {
424 let src: [u8; 12] = [0; 12];
425 let result = unpack_coption_u64(&src).unwrap();
426 assert_eq!(result, COption::None);
427
428 let mut src: [u8; 12] = [0; 12];
429 src[0] = 1;
430 let result = unpack_coption_u64(&src).unwrap();
431 assert_eq!(result, COption::Some(0));
432
433 let mut src: [u8; 12] = [0; 12];
434 src[1] = 1;
435 let result = unpack_coption_u64(&src).unwrap_err();
436 assert_eq!(result, ProgramError::InvalidAccountData);
437 }
438
439 #[test]
440 fn test_unpack_token_owner() {
441 let src: [u8; 12] = [0; 12];
443 let result = Account::unpack_account_owner(&src);
444 assert_eq!(result, Option::None);
445
446 let mut src: [u8; Account::LEN] = [0; Account::LEN];
448 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
449 let result = Account::unpack_account_owner(&src);
450 assert!(result.is_some());
451
452 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
454 let result = Account::unpack_account_owner(&src);
455 assert!(result.is_some());
456
457 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
459 let result = Account::unpack_account_mint(&src);
460 assert_eq!(result, Option::None);
461
462 let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
464 let result = Account::unpack_account_owner(&src);
465 assert_eq!(result, Option::None);
466 }
467
468 #[test]
469 fn test_unpack_token_mint() {
470 let src: [u8; 12] = [0; 12];
472 let result = Account::unpack_account_mint(&src);
473 assert_eq!(result, Option::None);
474
475 let mut src: [u8; Account::LEN] = [0; Account::LEN];
477 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
478 let result = Account::unpack_account_mint(&src);
479 assert!(result.is_some());
480
481 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
483 let result = Account::unpack_account_mint(&src);
484 assert!(result.is_some());
485
486 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
488 let result = Account::unpack_account_mint(&src);
489 assert_eq!(result, Option::None);
490
491 let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
493 let result = Account::unpack_account_mint(&src);
494 assert_eq!(result, Option::None);
495 }
496}