1use {
4 crate::instruction::MAX_SIGNERS,
5 arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs},
6 num_enum::TryFromPrimitive,
7 miraland_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 miraland_program::system_program::check_id(&self.owner)
124 || miraland_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)?;
238 }
239 Ok(result)
240 }
241 fn pack_into_slice(&self, dst: &mut [u8]) {
242 let dst = array_mut_ref![dst, 0, 355];
243 #[allow(clippy::ptr_offset_with_cast)]
244 let (m, n, is_initialized, signers_flat) = mut_array_refs![dst, 1, 1, 1, 32 * MAX_SIGNERS];
245 *m = [self.m];
246 *n = [self.n];
247 *is_initialized = [self.is_initialized as u8];
248 for (i, src) in self.signers.iter().enumerate() {
249 let dst_array = array_mut_ref![signers_flat, 32 * i, 32];
250 dst_array.copy_from_slice(src.as_ref());
251 }
252 }
253}
254
255fn pack_coption_key(src: &COption<Pubkey>, dst: &mut [u8; 36]) {
257 let (tag, body) = mut_array_refs![dst, 4, 32];
258 match src {
259 COption::Some(key) => {
260 *tag = [1, 0, 0, 0];
261 body.copy_from_slice(key.as_ref());
262 }
263 COption::None => {
264 *tag = [0; 4];
265 }
266 }
267}
268fn unpack_coption_key(src: &[u8; 36]) -> Result<COption<Pubkey>, ProgramError> {
269 let (tag, body) = array_refs![src, 4, 32];
270 match *tag {
271 [0, 0, 0, 0] => Ok(COption::None),
272 [1, 0, 0, 0] => Ok(COption::Some(Pubkey::new_from_array(*body))),
273 _ => Err(ProgramError::InvalidAccountData),
274 }
275}
276fn pack_coption_u64(src: &COption<u64>, dst: &mut [u8; 12]) {
277 let (tag, body) = mut_array_refs![dst, 4, 8];
278 match src {
279 COption::Some(amount) => {
280 *tag = [1, 0, 0, 0];
281 *body = amount.to_le_bytes();
282 }
283 COption::None => {
284 *tag = [0; 4];
285 }
286 }
287}
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
362#[cfg(test)]
363mod tests {
364 use super::*;
365
366 #[test]
367 fn test_mint_unpack_from_slice() {
368 let src: [u8; 82] = [0; 82];
369 let mint = Mint::unpack_from_slice(&src).unwrap();
370 assert!(!mint.is_initialized);
371
372 let mut src: [u8; 82] = [0; 82];
373 src[45] = 2;
374 let mint = Mint::unpack_from_slice(&src).unwrap_err();
375 assert_eq!(mint, ProgramError::InvalidAccountData);
376 }
377
378 #[test]
379 fn test_account_state() {
380 let account_state = AccountState::default();
381 assert_eq!(account_state, AccountState::Uninitialized);
382 }
383
384 #[test]
385 fn test_multisig_unpack_from_slice() {
386 let src: [u8; 355] = [0; 355];
387 let multisig = Multisig::unpack_from_slice(&src).unwrap();
388 assert_eq!(multisig.m, 0);
389 assert_eq!(multisig.n, 0);
390 assert!(!multisig.is_initialized);
391
392 let mut src: [u8; 355] = [0; 355];
393 src[0] = 1;
394 src[1] = 1;
395 src[2] = 1;
396 let multisig = Multisig::unpack_from_slice(&src).unwrap();
397 assert_eq!(multisig.m, 1);
398 assert_eq!(multisig.n, 1);
399 assert!(multisig.is_initialized);
400
401 let mut src: [u8; 355] = [0; 355];
402 src[2] = 2;
403 let multisig = Multisig::unpack_from_slice(&src).unwrap_err();
404 assert_eq!(multisig, ProgramError::InvalidAccountData);
405 }
406
407 #[test]
408 fn test_unpack_coption_key() {
409 let src: [u8; 36] = [0; 36];
410 let result = unpack_coption_key(&src).unwrap();
411 assert_eq!(result, COption::None);
412
413 let mut src: [u8; 36] = [0; 36];
414 src[1] = 1;
415 let result = unpack_coption_key(&src).unwrap_err();
416 assert_eq!(result, ProgramError::InvalidAccountData);
417 }
418
419 #[test]
420 fn test_unpack_coption_u64() {
421 let src: [u8; 12] = [0; 12];
422 let result = unpack_coption_u64(&src).unwrap();
423 assert_eq!(result, COption::None);
424
425 let mut src: [u8; 12] = [0; 12];
426 src[0] = 1;
427 let result = unpack_coption_u64(&src).unwrap();
428 assert_eq!(result, COption::Some(0));
429
430 let mut src: [u8; 12] = [0; 12];
431 src[1] = 1;
432 let result = unpack_coption_u64(&src).unwrap_err();
433 assert_eq!(result, ProgramError::InvalidAccountData);
434 }
435
436 #[test]
437 fn test_unpack_token_owner() {
438 let src: [u8; 12] = [0; 12];
440 let result = Account::unpack_account_owner(&src);
441 assert_eq!(result, Option::None);
442
443 let mut src: [u8; Account::LEN] = [0; Account::LEN];
445 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
446 let result = Account::unpack_account_owner(&src);
447 assert!(result.is_some());
448
449 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
451 let result = Account::unpack_account_owner(&src);
452 assert!(result.is_some());
453
454 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
456 let result = Account::unpack_account_mint(&src);
457 assert_eq!(result, Option::None);
458
459 let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
461 let result = Account::unpack_account_owner(&src);
462 assert_eq!(result, Option::None);
463 }
464
465 #[test]
466 fn test_unpack_token_mint() {
467 let src: [u8; 12] = [0; 12];
469 let result = Account::unpack_account_mint(&src);
470 assert_eq!(result, Option::None);
471
472 let mut src: [u8; Account::LEN] = [0; Account::LEN];
474 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Initialized as u8;
475 let result = Account::unpack_account_mint(&src);
476 assert!(result.is_some());
477
478 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Frozen as u8;
480 let result = Account::unpack_account_mint(&src);
481 assert!(result.is_some());
482
483 src[ACCOUNT_INITIALIZED_INDEX] = AccountState::Uninitialized as u8;
485 let result = Account::unpack_account_mint(&src);
486 assert_eq!(result, Option::None);
487
488 let src: [u8; Account::LEN + 5] = [0; Account::LEN + 5];
490 let result = Account::unpack_account_mint(&src);
491 assert_eq!(result, Option::None);
492 }
493}