1#![no_std]
55
56use jiminy_core::account::{Pod, FixedLayout};
57
58#[repr(C)]
77#[derive(Clone, Copy)]
78pub struct SplTokenAccount {
79 pub mint: [u8; 32],
81 pub owner: [u8; 32],
83 pub amount: [u8; 8],
85 pub delegate_tag: [u8; 4],
87 pub delegate: [u8; 32],
89 pub state: u8,
91 pub is_native_tag: [u8; 4],
93 pub native_amount: [u8; 8],
95 pub delegated_amount: [u8; 8],
97 pub close_authority_tag: [u8; 4],
99 pub close_authority: [u8; 32],
101}
102
103unsafe impl Pod for SplTokenAccount {}
106impl FixedLayout for SplTokenAccount { const SIZE: usize = 165; }
107
108impl SplTokenAccount {
109 #[inline(always)]
111 pub fn amount(&self) -> u64 {
112 u64::from_le_bytes(self.amount)
113 }
114
115 #[inline(always)]
117 pub fn has_delegate(&self) -> bool {
118 u32::from_le_bytes(self.delegate_tag) == 1
119 }
120
121 #[inline(always)]
123 pub fn is_native(&self) -> bool {
124 u32::from_le_bytes(self.is_native_tag) == 1
125 }
126
127 #[inline(always)]
129 pub fn has_close_authority(&self) -> bool {
130 u32::from_le_bytes(self.close_authority_tag) == 1
131 }
132
133 #[inline(always)]
135 pub fn is_initialized(&self) -> bool {
136 self.state == 1
137 }
138
139 #[inline(always)]
141 pub fn is_frozen(&self) -> bool {
142 self.state == 2
143 }
144}
145
146#[repr(C)]
161#[derive(Clone, Copy)]
162pub struct SplMint {
163 pub mint_authority_tag: [u8; 4],
165 pub mint_authority: [u8; 32],
167 pub supply: [u8; 8],
169 pub decimals: u8,
171 pub is_initialized: u8,
173 pub freeze_authority_tag: [u8; 4],
175 pub freeze_authority: [u8; 32],
177}
178
179unsafe impl Pod for SplMint {}
181impl FixedLayout for SplMint { const SIZE: usize = 82; }
182
183impl SplMint {
184 #[inline(always)]
186 pub fn supply(&self) -> u64 {
187 u64::from_le_bytes(self.supply)
188 }
189
190 #[inline(always)]
192 pub fn has_mint_authority(&self) -> bool {
193 u32::from_le_bytes(self.mint_authority_tag) == 1
194 }
195
196 #[inline(always)]
198 pub fn has_freeze_authority(&self) -> bool {
199 u32::from_le_bytes(self.freeze_authority_tag) == 1
200 }
201}
202
203#[repr(C)]
215#[derive(Clone, Copy)]
216pub struct SplMultisig {
217 pub m: u8,
219 pub n: u8,
221 pub is_initialized: u8,
223 pub signers: [u8; 352],
225}
226
227unsafe impl Pod for SplMultisig {}
229impl FixedLayout for SplMultisig { const SIZE: usize = 355; }
230
231impl SplMultisig {
232 #[inline(always)]
236 pub fn signer(&self, i: usize) -> Option<&[u8; 32]> {
237 if i >= self.n as usize || i >= 11 {
238 return None;
239 }
240 let start = i * 32;
241 Some(unsafe { &*(self.signers.as_ptr().add(start) as *const [u8; 32]) })
243 }
244}
245
246#[repr(C)]
259#[derive(Clone, Copy)]
260pub struct NonceAccount {
261 pub version: [u8; 4],
263 pub state: [u8; 4],
265 pub authority: [u8; 32],
267 pub blockhash: [u8; 32],
269 pub lamports_per_signature: [u8; 8],
271}
272
273unsafe impl Pod for NonceAccount {}
276impl FixedLayout for NonceAccount { const SIZE: usize = 80; }
277
278impl NonceAccount {
279 #[inline(always)]
281 pub fn is_initialized(&self) -> bool {
282 u32::from_le_bytes(self.state) == 1
283 }
284
285 #[inline(always)]
287 pub fn lamports_per_signature(&self) -> u64 {
288 u64::from_le_bytes(self.lamports_per_signature)
289 }
290}
291
292#[repr(C)]
318#[derive(Clone, Copy)]
319pub struct StakeState {
320 pub state: [u8; 4],
323 pub rent_exempt_reserve: [u8; 8],
325 pub authorized_staker: [u8; 32],
327 pub authorized_withdrawer: [u8; 32],
329 pub lockup_timestamp: [u8; 8],
331 pub lockup_epoch: [u8; 8],
333 pub lockup_custodian: [u8; 32],
335 pub voter_pubkey: [u8; 32],
337 pub stake_amount: [u8; 8],
339 pub activation_epoch: [u8; 8],
341 pub deactivation_epoch: [u8; 8],
343 pub warmup_cooldown_rate: [u8; 8],
345 pub credits_observed: [u8; 8],
347 pub _padding: [u8; 4],
349}
350
351unsafe impl Pod for StakeState {}
354impl FixedLayout for StakeState { const SIZE: usize = 200; }
355
356impl StakeState {
357 #[inline(always)]
359 pub fn state_kind(&self) -> u32 {
360 u32::from_le_bytes(self.state)
361 }
362
363 #[inline(always)]
365 pub fn is_delegated(&self) -> bool {
366 self.state_kind() == 2
367 }
368
369 #[inline(always)]
371 pub fn stake_amount(&self) -> u64 {
372 u64::from_le_bytes(self.stake_amount)
373 }
374
375 #[inline(always)]
377 pub fn activation_epoch(&self) -> u64 {
378 u64::from_le_bytes(self.activation_epoch)
379 }
380
381 #[inline(always)]
384 pub fn deactivation_epoch(&self) -> u64 {
385 u64::from_le_bytes(self.deactivation_epoch)
386 }
387}
388
389const _: () = assert!(core::mem::size_of::<SplTokenAccount>() == 165);
392const _: () = assert!(core::mem::size_of::<SplMint>() == 82);
393const _: () = assert!(core::mem::size_of::<SplMultisig>() == 355);
394const _: () = assert!(core::mem::size_of::<NonceAccount>() == 80);
395const _: () = assert!(core::mem::size_of::<StakeState>() == 200);
396
397const _: () = assert!(core::mem::align_of::<SplTokenAccount>() == 1);
400const _: () = assert!(core::mem::align_of::<SplMint>() == 1);
401const _: () = assert!(core::mem::align_of::<SplMultisig>() == 1);
402const _: () = assert!(core::mem::align_of::<NonceAccount>() == 1);
403const _: () = assert!(core::mem::align_of::<StakeState>() == 1);
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408 use jiminy_core::account::pod_from_bytes;
409
410 fn offset_of<T, F>(base: *const T, field: *const F) -> usize {
413 (field as usize) - (base as usize)
414 }
415
416 #[test]
419 fn token_account_size() {
420 assert_eq!(SplTokenAccount::SIZE, 165);
421 assert_eq!(core::mem::size_of::<SplTokenAccount>(), 165);
422 }
423
424 #[test]
425 fn token_account_field_offsets() {
426 let t = SplTokenAccount {
427 mint: [0; 32],
428 owner: [0; 32],
429 amount: [0; 8],
430 delegate_tag: [0; 4],
431 delegate: [0; 32],
432 state: 0,
433 is_native_tag: [0; 4],
434 native_amount: [0; 8],
435 delegated_amount: [0; 8],
436 close_authority_tag: [0; 4],
437 close_authority: [0; 32],
438 };
439 let base = &t as *const SplTokenAccount;
440 assert_eq!(offset_of(base, &t.mint as *const _), 0);
441 assert_eq!(offset_of(base, &t.owner as *const _), 32);
442 assert_eq!(offset_of(base, &t.amount as *const _), 64);
443 assert_eq!(offset_of(base, &t.delegate_tag as *const _), 72);
444 assert_eq!(offset_of(base, &t.delegate as *const _), 76);
445 assert_eq!(offset_of(base, &t.state as *const _), 108);
446 assert_eq!(offset_of(base, &t.is_native_tag as *const _), 109);
447 assert_eq!(offset_of(base, &t.native_amount as *const _), 113);
448 assert_eq!(offset_of(base, &t.delegated_amount as *const _), 121);
449 assert_eq!(offset_of(base, &t.close_authority_tag as *const _), 129);
450 assert_eq!(offset_of(base, &t.close_authority as *const _), 133);
451 }
452
453 #[test]
454 fn token_account_pod_roundtrip() {
455 let mut buf = [0u8; 165];
456 buf[0..32].copy_from_slice(&[0xAA; 32]);
458 buf[64..72].copy_from_slice(&1_000_000u64.to_le_bytes());
460 buf[108] = 1;
462 buf[72..76].copy_from_slice(&1u32.to_le_bytes());
464 buf[76..108].copy_from_slice(&[0xBB; 32]);
466
467 let token = pod_from_bytes::<SplTokenAccount>(&buf).unwrap();
468 assert_eq!(token.mint, [0xAA; 32]);
469 assert_eq!(token.amount(), 1_000_000);
470 assert!(token.is_initialized());
471 assert!(!token.is_frozen());
472 assert!(token.has_delegate());
473 assert_eq!(token.delegate, [0xBB; 32]);
474 assert!(!token.is_native());
475 assert!(!token.has_close_authority());
476 }
477
478 #[test]
481 fn mint_size() {
482 assert_eq!(SplMint::SIZE, 82);
483 assert_eq!(core::mem::size_of::<SplMint>(), 82);
484 }
485
486 #[test]
487 fn mint_field_offsets() {
488 let m = SplMint {
489 mint_authority_tag: [0; 4],
490 mint_authority: [0; 32],
491 supply: [0; 8],
492 decimals: 0,
493 is_initialized: 0,
494 freeze_authority_tag: [0; 4],
495 freeze_authority: [0; 32],
496 };
497 let base = &m as *const SplMint;
498 assert_eq!(offset_of(base, &m.mint_authority_tag as *const _), 0);
499 assert_eq!(offset_of(base, &m.mint_authority as *const _), 4);
500 assert_eq!(offset_of(base, &m.supply as *const _), 36);
501 assert_eq!(offset_of(base, &m.decimals as *const _), 44);
502 assert_eq!(offset_of(base, &m.is_initialized as *const _), 45);
503 assert_eq!(offset_of(base, &m.freeze_authority_tag as *const _), 46);
504 assert_eq!(offset_of(base, &m.freeze_authority as *const _), 50);
505 }
506
507 #[test]
508 fn mint_pod_roundtrip() {
509 let mut buf = [0u8; 82];
510 buf[0..4].copy_from_slice(&1u32.to_le_bytes());
512 buf[4..36].copy_from_slice(&[0xCC; 32]);
514 buf[36..44].copy_from_slice(&1_000_000_000u64.to_le_bytes());
516 buf[44] = 9;
518 buf[45] = 1;
520
521 let mint = pod_from_bytes::<SplMint>(&buf).unwrap();
522 assert!(mint.has_mint_authority());
523 assert_eq!(mint.mint_authority, [0xCC; 32]);
524 assert_eq!(mint.supply(), 1_000_000_000);
525 assert_eq!(mint.decimals, 9);
526 assert!(!mint.has_freeze_authority());
527 }
528
529 #[test]
532 fn multisig_size() {
533 assert_eq!(SplMultisig::SIZE, 355);
534 assert_eq!(core::mem::size_of::<SplMultisig>(), 355);
535 }
536
537 #[test]
538 fn multisig_field_offsets() {
539 let ms = SplMultisig {
540 m: 0,
541 n: 0,
542 is_initialized: 0,
543 signers: [0; 352],
544 };
545 let base = &ms as *const SplMultisig;
546 assert_eq!(offset_of(base, &ms.m as *const _), 0);
547 assert_eq!(offset_of(base, &ms.n as *const _), 1);
548 assert_eq!(offset_of(base, &ms.is_initialized as *const _), 2);
549 assert_eq!(offset_of(base, &ms.signers as *const _), 3);
550 }
551
552 #[test]
553 fn multisig_signer_access() {
554 let mut ms = SplMultisig {
555 m: 2,
556 n: 3,
557 is_initialized: 1,
558 signers: [0; 352],
559 };
560 ms.signers[0..32].copy_from_slice(&[0x11; 32]);
561 ms.signers[32..64].copy_from_slice(&[0x22; 32]);
562 ms.signers[64..96].copy_from_slice(&[0x33; 32]);
563
564 assert_eq!(ms.signer(0).unwrap(), &[0x11; 32]);
565 assert_eq!(ms.signer(1).unwrap(), &[0x22; 32]);
566 assert_eq!(ms.signer(2).unwrap(), &[0x33; 32]);
567 assert!(ms.signer(3).is_none()); assert!(ms.signer(11).is_none()); }
570
571 #[test]
574 fn nonce_account_size() {
575 assert_eq!(NonceAccount::SIZE, 80);
576 assert_eq!(core::mem::size_of::<NonceAccount>(), 80);
577 }
578
579 #[test]
580 fn nonce_account_field_offsets() {
581 let n = NonceAccount {
582 version: [0; 4],
583 state: [0; 4],
584 authority: [0; 32],
585 blockhash: [0; 32],
586 lamports_per_signature: [0; 8],
587 };
588 let base = &n as *const NonceAccount;
589 assert_eq!(offset_of(base, &n.version as *const _), 0);
590 assert_eq!(offset_of(base, &n.state as *const _), 4);
591 assert_eq!(offset_of(base, &n.authority as *const _), 8);
592 assert_eq!(offset_of(base, &n.blockhash as *const _), 40);
593 assert_eq!(offset_of(base, &n.lamports_per_signature as *const _), 72);
594 }
595
596 #[test]
597 fn nonce_account_pod_roundtrip() {
598 let mut buf = [0u8; 80];
599 buf[0..4].copy_from_slice(&1u32.to_le_bytes());
601 buf[4..8].copy_from_slice(&1u32.to_le_bytes());
603 buf[8..40].copy_from_slice(&[0xDD; 32]);
605 buf[72..80].copy_from_slice(&5000u64.to_le_bytes());
607
608 let nonce = pod_from_bytes::<NonceAccount>(&buf).unwrap();
609 assert!(nonce.is_initialized());
610 assert_eq!(nonce.authority, [0xDD; 32]);
611 assert_eq!(nonce.lamports_per_signature(), 5000);
612 }
613
614 #[test]
615 fn nonce_account_initialized() {
616 let mut nonce = NonceAccount {
617 version: 1u32.to_le_bytes(),
618 state: 0u32.to_le_bytes(),
619 authority: [0; 32],
620 blockhash: [0; 32],
621 lamports_per_signature: [0; 8],
622 };
623 assert!(!nonce.is_initialized());
624 nonce.state = 1u32.to_le_bytes();
625 assert!(nonce.is_initialized());
626 }
627
628 #[test]
631 fn stake_state_size() {
632 assert_eq!(StakeState::SIZE, 200);
633 assert_eq!(core::mem::size_of::<StakeState>(), 200);
634 }
635
636 #[test]
637 fn stake_state_field_offsets() {
638 let s = StakeState {
639 state: [0; 4],
640 rent_exempt_reserve: [0; 8],
641 authorized_staker: [0; 32],
642 authorized_withdrawer: [0; 32],
643 lockup_timestamp: [0; 8],
644 lockup_epoch: [0; 8],
645 lockup_custodian: [0; 32],
646 voter_pubkey: [0; 32],
647 stake_amount: [0; 8],
648 activation_epoch: [0; 8],
649 deactivation_epoch: [0; 8],
650 warmup_cooldown_rate: [0; 8],
651 credits_observed: [0; 8],
652 _padding: [0; 4],
653 };
654 let base = &s as *const StakeState;
655 assert_eq!(offset_of(base, &s.state as *const _), 0);
656 assert_eq!(offset_of(base, &s.rent_exempt_reserve as *const _), 4);
657 assert_eq!(offset_of(base, &s.authorized_staker as *const _), 12);
658 assert_eq!(offset_of(base, &s.authorized_withdrawer as *const _), 44);
659 assert_eq!(offset_of(base, &s.lockup_timestamp as *const _), 76);
660 assert_eq!(offset_of(base, &s.lockup_epoch as *const _), 84);
661 assert_eq!(offset_of(base, &s.lockup_custodian as *const _), 92);
662 assert_eq!(offset_of(base, &s.voter_pubkey as *const _), 124);
663 assert_eq!(offset_of(base, &s.stake_amount as *const _), 156);
664 assert_eq!(offset_of(base, &s.activation_epoch as *const _), 164);
665 assert_eq!(offset_of(base, &s.deactivation_epoch as *const _), 172);
666 assert_eq!(offset_of(base, &s.warmup_cooldown_rate as *const _), 180);
667 assert_eq!(offset_of(base, &s.credits_observed as *const _), 188);
668 assert_eq!(offset_of(base, &s._padding as *const _), 196);
669 }
670
671 #[test]
672 fn stake_state_pod_roundtrip() {
673 let mut buf = [0u8; 200];
674 buf[0..4].copy_from_slice(&2u32.to_le_bytes());
676 buf[124..156].copy_from_slice(&[0xEE; 32]);
678 buf[156..164].copy_from_slice(&5_000_000u64.to_le_bytes());
680 buf[164..172].copy_from_slice(&42u64.to_le_bytes());
682 buf[172..180].copy_from_slice(&u64::MAX.to_le_bytes());
684
685 let stake = pod_from_bytes::<StakeState>(&buf).unwrap();
686 assert!(stake.is_delegated());
687 assert_eq!(stake.voter_pubkey, [0xEE; 32]);
688 assert_eq!(stake.stake_amount(), 5_000_000);
689 assert_eq!(stake.activation_epoch(), 42);
690 assert_eq!(stake.deactivation_epoch(), u64::MAX);
691 }
692
693 #[test]
694 fn stake_state_delegated() {
695 let mut stake = StakeState {
696 state: 2u32.to_le_bytes(),
697 rent_exempt_reserve: [0; 8],
698 authorized_staker: [0; 32],
699 authorized_withdrawer: [0; 32],
700 lockup_timestamp: [0; 8],
701 lockup_epoch: [0; 8],
702 lockup_custodian: [0; 32],
703 voter_pubkey: [0; 32],
704 stake_amount: 1_000_000u64.to_le_bytes(),
705 activation_epoch: 100u64.to_le_bytes(),
706 deactivation_epoch: u64::MAX.to_le_bytes(),
707 warmup_cooldown_rate: [0; 8],
708 credits_observed: [0; 8],
709 _padding: [0; 4],
710 };
711 assert!(stake.is_delegated());
712 assert_eq!(stake.stake_amount(), 1_000_000);
713 assert_eq!(stake.activation_epoch(), 100);
714 assert_eq!(stake.deactivation_epoch(), u64::MAX);
715
716 stake.state = 1u32.to_le_bytes(); assert!(!stake.is_delegated());
718 }
719
720 #[test]
723 fn token_account_bytes_match_struct() {
724 let token = SplTokenAccount {
725 mint: [1; 32],
726 owner: [2; 32],
727 amount: 42u64.to_le_bytes(),
728 delegate_tag: 0u32.to_le_bytes(),
729 delegate: [0; 32],
730 state: 1,
731 is_native_tag: 0u32.to_le_bytes(),
732 native_amount: [0; 8],
733 delegated_amount: [0; 8],
734 close_authority_tag: 0u32.to_le_bytes(),
735 close_authority: [0; 32],
736 };
737 let bytes = unsafe {
739 core::slice::from_raw_parts(
740 &token as *const SplTokenAccount as *const u8,
741 165,
742 )
743 };
744 let roundtrip = pod_from_bytes::<SplTokenAccount>(bytes).unwrap();
745 assert_eq!(roundtrip.mint, [1; 32]);
746 assert_eq!(roundtrip.owner, [2; 32]);
747 assert_eq!(roundtrip.amount(), 42);
748 assert!(roundtrip.is_initialized());
749 }
750}