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 delegated_amount: u64,
103 pub close_authority: COption<Pubkey>,
105}
106impl Account {
107 pub fn is_frozen(&self) -> bool {
109 self.state == AccountState::Frozen
110 }
111 pub fn is_owned_by_system_program_or_incinerator_(&self) -> bool {
114 true
117 }
118}
119impl Sealed for Account {}
120impl IsInitialized for Account {
121 fn is_initialized(&self) -> bool {
122 self.state != AccountState::Uninitialized
123 }
124}
125impl Pack for Account {
126 const LEN: usize = 164;
127 fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
128 let src = array_ref![src, 0, 164];
129 let (mint, owner, amount, delegate, state, delegated_amount, close_authority) =
130 array_refs![src, 32, 32, 8, 36, 12, 8, 36];
131 Ok(Account {
132 mint: Pubkey::from_slice(mint),
133 owner: Pubkey::from_slice(owner),
134 amount: u64::from_le_bytes(*amount),
135 delegate: unpack_coption_key(delegate)?,
136 state: AccountState::try_from_primitive(state[0])
137 .or(Err(ProgramError::InvalidAccountData))?,
138 delegated_amount: u64::from_le_bytes(*delegated_amount),
139 close_authority: unpack_coption_key(close_authority)?,
140 })
141 }
142 fn pack_into_slice(&self, dst: &mut [u8]) {
143 let dst = array_mut_ref![dst, 0, 164];
144 let (
145 mint_dst,
146 owner_dst,
147 amount_dst,
148 delegate_dst,
149 state_dst,
150 delegated_amount_dst,
151 close_authority_dst,
152 ) = mut_array_refs![dst, 32, 32, 8, 36, 12, 8, 36];
153 let &Account {
154 ref mint,
155 ref owner,
156 amount,
157 ref delegate,
158 state,
159 delegated_amount,
160 ref close_authority,
161 } = self;
162 mint_dst.copy_from_slice(mint.as_ref());
163 owner_dst.copy_from_slice(owner.as_ref());
164 *amount_dst = amount.to_le_bytes();
165 pack_coption_key(delegate, delegate_dst);
166 state_dst[0] = state as u8;
167 *delegated_amount_dst = delegated_amount.to_le_bytes();
168 pack_coption_key(close_authority, close_authority_dst);
169 }
170}
171
172#[repr(u8)]
174#[derive(Clone, Copy, Debug, Default, PartialEq, TryFromPrimitive)]
175pub enum AccountState {
176 #[default]
178 Uninitialized,
179 Initialized,
182 Frozen,
186}
187
188#[repr(C)]
190#[derive(Clone, Copy, Debug, Default, PartialEq)]
191pub struct Multisig {
192 pub m: u8,
194 pub n: u8,
196 pub is_initialized: bool,
198 pub signers: [Pubkey; MAX_SIGNERS],
200}
201impl Sealed for Multisig {}
202impl IsInitialized for Multisig {
203 fn is_initialized(&self) -> bool {
204 self.is_initialized
205 }
206}
207impl Pack for Multisig {
208 const LEN: usize = 355;
209 fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
210 let src = array_ref![src, 0, 355];
211 #[allow(clippy::ptr_offset_with_cast)]
212 let (m, n, is_initialized, signers_flat) = array_refs![src, 1, 1, 1, 32 * MAX_SIGNERS];
213 let mut result = Multisig {
214 m: m[0],
215 n: n[0],
216 is_initialized: match is_initialized {
217 [0] => false,
218 [1] => true,
219 _ => return Err(ProgramError::InvalidAccountData),
220 },
221 signers: [Pubkey::from_slice(&[0u8; 32]); MAX_SIGNERS],
222 };
223 for (src, dst) in signers_flat.chunks(32).zip(result.signers.iter_mut()) {
224 *dst = Pubkey::from_slice(src);
225 }
226 Ok(result)
227 }
228 fn pack_into_slice(&self, dst: &mut [u8]) {
229 let dst = array_mut_ref![dst, 0, 355];
230 #[allow(clippy::ptr_offset_with_cast)]
231 let (m, n, is_initialized, signers_flat) = mut_array_refs![dst, 1, 1, 1, 32 * MAX_SIGNERS];
232 *m = [self.m];
233 *n = [self.n];
234 *is_initialized = [self.is_initialized as u8];
235 for (i, src) in self.signers.iter().enumerate() {
236 let dst_array = array_mut_ref![signers_flat, 32 * i, 32];
237 dst_array.copy_from_slice(src.as_ref());
238 }
239 }
240}
241
242fn pack_coption_key(src: &COption<Pubkey>, dst: &mut [u8; 36]) {
244 let (tag, body) = mut_array_refs![dst, 4, 32];
245 match src {
246 COption::Some(key) => {
247 *tag = [1, 0, 0, 0];
248 body.copy_from_slice(key.as_ref());
249 }
250 COption::None => {
251 *tag = [0; 4];
252 }
253 }
254}
255fn unpack_coption_key(src: &[u8; 36]) -> Result<COption<Pubkey>, ProgramError> {
256 let (tag, body) = array_refs![src, 4, 32];
257 match *tag {
258 [0, 0, 0, 0] => Ok(COption::None),
259 [1, 0, 0, 0] => Ok(COption::Some(Pubkey::from_slice(body))),
260 _ => Err(ProgramError::InvalidAccountData),
261 }
262}
263#[allow(dead_code)]
264fn pack_coption_u64(src: &COption<u64>, dst: &mut [u8; 12]) {
265 let (tag, body) = mut_array_refs![dst, 4, 8];
266 match src {
267 COption::Some(amount) => {
268 *tag = [1, 0, 0, 0];
269 *body = amount.to_le_bytes();
270 }
271 COption::None => {
272 *tag = [0; 4];
273 }
274 }
275}
276#[allow(dead_code)]
277fn unpack_coption_u64(src: &[u8; 12]) -> Result<COption<u64>, ProgramError> {
278 let (tag, body) = array_refs![src, 4, 8];
279 match *tag {
280 [0, 0, 0, 0] => Ok(COption::None),
281 [1, 0, 0, 0] => Ok(COption::Some(u64::from_le_bytes(*body))),
282 _ => Err(ProgramError::InvalidAccountData),
283 }
284}
285
286const SPL_TOKEN_ACCOUNT_MINT_OFFSET: usize = 0;
287const SPL_TOKEN_ACCOUNT_OWNER_OFFSET: usize = 32;
288
289pub trait GenericTokenAccount {
292 fn valid_account_data(account_data: &[u8]) -> bool;
294
295 fn unpack_account_owner_unchecked(account_data: &[u8]) -> &Pubkey {
298 Self::unpack_pubkey_unchecked(account_data, SPL_TOKEN_ACCOUNT_OWNER_OFFSET)
299 }
300
301 fn unpack_account_mint_unchecked(account_data: &[u8]) -> &Pubkey {
304 Self::unpack_pubkey_unchecked(account_data, SPL_TOKEN_ACCOUNT_MINT_OFFSET)
305 }
306
307 fn unpack_pubkey_unchecked(account_data: &[u8], offset: usize) -> &Pubkey {
311 bytemuck::from_bytes(&account_data[offset..offset + PUBKEY_BYTES])
312 }
313
314 fn unpack_account_owner(account_data: &[u8]) -> Option<&Pubkey> {
316 if Self::valid_account_data(account_data) {
317 Some(Self::unpack_account_owner_unchecked(account_data))
318 } else {
319 None
320 }
321 }
322
323 fn unpack_account_mint(account_data: &[u8]) -> Option<&Pubkey> {
325 if Self::valid_account_data(account_data) {
326 Some(Self::unpack_account_mint_unchecked(account_data))
327 } else {
328 None
329 }
330 }
331}
332
333pub const ACCOUNT_INITIALIZED_INDEX: usize = 108;
335
336pub fn is_initialized_account(account_data: &[u8]) -> bool {
339 *account_data
340 .get(ACCOUNT_INITIALIZED_INDEX)
341 .unwrap_or(&(AccountState::Uninitialized as u8))
342 != AccountState::Uninitialized as u8
343}
344
345impl GenericTokenAccount for Account {
346 fn valid_account_data(account_data: &[u8]) -> bool {
347 account_data.len() == Account::LEN && is_initialized_account(account_data)
348 }
349}
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354
355 #[test]
356 fn test_mint_unpack_from_slice() {
357 let src: [u8; 82] = [0; 82];
358 let mint = Mint::unpack_from_slice(&src).unwrap();
359 assert!(!mint.is_initialized);
360
361 let mut src: [u8; 82] = [0; 82];
362 src[45] = 2;
363 let mint = Mint::unpack_from_slice(&src).unwrap_err();
364 assert_eq!(mint, ProgramError::InvalidAccountData);
365 }
366
367 #[test]
368 fn test_account_state() {
369 let account_state = AccountState::default();
370 assert_eq!(account_state, AccountState::Uninitialized);
371 }
372
373 #[test]
374 fn test_multisig_unpack_from_slice() {
375 let src: [u8; 355] = [0; 355];
376 let multisig = Multisig::unpack_from_slice(&src).unwrap();
377 assert_eq!(multisig.m, 0);
378 assert_eq!(multisig.n, 0);
379 assert!(!multisig.is_initialized);
380
381 let mut src: [u8; 355] = [0; 355];
382 src[0] = 1;
383 src[1] = 1;
384 src[2] = 1;
385 let multisig = Multisig::unpack_from_slice(&src).unwrap();
386 assert_eq!(multisig.m, 1);
387 assert_eq!(multisig.n, 1);
388 assert!(multisig.is_initialized);
389
390 let mut src: [u8; 355] = [0; 355];
391 src[2] = 2;
392 let multisig = Multisig::unpack_from_slice(&src).unwrap_err();
393 assert_eq!(multisig, ProgramError::InvalidAccountData);
394 }
395
396 #[test]
397 fn test_unpack_coption_key() {
398 let src: [u8; 36] = [0; 36];
399 let result = unpack_coption_key(&src).unwrap();
400 assert_eq!(result, COption::None);
401
402 let mut src: [u8; 36] = [0; 36];
403 src[1] = 1;
404 let result = unpack_coption_key(&src).unwrap_err();
405 assert_eq!(result, ProgramError::InvalidAccountData);
406 }
407
408 #[test]
409 fn test_unpack_coption_u64() {
410 let src: [u8; 12] = [0; 12];
411 let result = unpack_coption_u64(&src).unwrap();
412 assert_eq!(result, COption::None);
413
414 let mut src: [u8; 12] = [0; 12];
415 src[0] = 1;
416 let result = unpack_coption_u64(&src).unwrap();
417 assert_eq!(result, COption::Some(0));
418
419 let mut src: [u8; 12] = [0; 12];
420 src[1] = 1;
421 let result = unpack_coption_u64(&src).unwrap_err();
422 assert_eq!(result, ProgramError::InvalidAccountData);
423 }
424
425 #[test]
426 fn test_unpack_token_owner() {
427 let src: [u8; 12] = [0; 12];
429 let result = Account::unpack_account_owner(&src);
430 assert_eq!(result, Option::None);
431
432 let mut src: [u8; Account::LEN] = [0; Account::LEN];
434 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
435 let result = Account::unpack_account_owner(&src);
436 assert!(result.is_some());
437
438 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
440 let result = Account::unpack_account_owner(&src);
441 assert!(result.is_some());
442
443 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
445 let result = Account::unpack_account_mint(&src);
446 assert_eq!(result, Option::None);
447
448 let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
450 let result = Account::unpack_account_owner(&src);
451 assert_eq!(result, Option::None);
452 }
453
454 #[test]
455 fn test_unpack_token_mint() {
456 let src: [u8; 12] = [0; 12];
458 let result = Account::unpack_account_mint(&src);
459 assert_eq!(result, Option::None);
460
461 let mut src: [u8; Account::LEN] = [0; Account::LEN];
463 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
464 let result = Account::unpack_account_mint(&src);
465 assert!(result.is_some());
466
467 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
469 let result = Account::unpack_account_mint(&src);
470 assert!(result.is_some());
471
472 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
474 let result = Account::unpack_account_mint(&src);
475 assert_eq!(result, Option::None);
476
477 let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
479 let result = Account::unpack_account_mint(&src);
480 assert_eq!(result, Option::None);
481 }
482}