1use crate::account::AccountView;
18use crate::address::Address;
19use crate::error::ProgramError;
20use crate::instruction::{InstructionAccount, InstructionView, Signer};
21use crate::ProgramResult;
22
23#[inline(always)]
39fn require_authority_signed_direct(authority: &AccountView) -> ProgramResult {
40 if authority.is_signer() {
41 Ok(())
42 } else {
43 Err(ProgramError::MissingRequiredSignature)
44 }
45}
46
47#[inline]
67pub fn require_token_authority(
68 token_account: &AccountView,
69 authority: &AccountView,
70) -> ProgramResult {
71 let data = token_account
75 .try_borrow()
76 .map_err(|_| ProgramError::AccountBorrowFailed)?;
77 if data.len() < 64 {
78 return Err(ProgramError::AccountDataTooSmall);
79 }
80 let mut owner_bytes = [0u8; 32];
81 owner_bytes.copy_from_slice(&data[32..64]);
82 let authority_bytes: [u8; 32] = *authority.address().as_array();
83 if owner_bytes == authority_bytes {
84 Ok(())
85 } else {
86 Err(ProgramError::IncorrectAuthority)
87 }
88}
89
90#[inline]
102pub fn require_token_owner_eq(
103 token_account: &AccountView,
104 expected_owner: &Address,
105) -> ProgramResult {
106 let data = token_account
107 .try_borrow()
108 .map_err(|_| ProgramError::AccountBorrowFailed)?;
109 if data.len() < 64 {
110 return Err(ProgramError::AccountDataTooSmall);
111 }
112 let mut actual = [0u8; 32];
113 actual.copy_from_slice(&data[32..64]);
114 if actual == *expected_owner.as_array() {
115 Ok(())
116 } else {
117 Err(ProgramError::IncorrectAuthority)
118 }
119}
120
121#[inline]
140pub fn require_token_mint(token_account: &AccountView, expected_mint: &Address) -> ProgramResult {
141 let data = token_account
142 .try_borrow()
143 .map_err(|_| ProgramError::AccountBorrowFailed)?;
144 if data.len() < 32 {
145 return Err(ProgramError::AccountDataTooSmall);
146 }
147 let actual: [u8; 32] = {
148 let mut out = [0u8; 32];
149 out.copy_from_slice(&data[0..32]);
150 out
151 };
152 if actual == *expected_mint.as_array() {
153 Ok(())
154 } else {
155 Err(ProgramError::InvalidAccountData)
156 }
157}
158
159#[inline]
178pub fn require_mint_authority(
179 mint_account: &AccountView,
180 expected_authority: &Address,
181) -> ProgramResult {
182 let data = mint_account
183 .try_borrow()
184 .map_err(|_| ProgramError::AccountBorrowFailed)?;
185 if data.len() < 46 {
186 return Err(ProgramError::AccountDataTooSmall);
187 }
188 let tag = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
189 if tag != 1 {
190 return Err(ProgramError::InvalidAccountData);
192 }
193 let mut actual = [0u8; 32];
194 actual.copy_from_slice(&data[4..36]);
195 if actual == *expected_authority.as_array() {
196 Ok(())
197 } else {
198 Err(ProgramError::IncorrectAuthority)
199 }
200}
201
202#[inline]
208pub fn require_mint_decimals(mint_account: &AccountView, expected: u8) -> ProgramResult {
209 let data = mint_account
210 .try_borrow()
211 .map_err(|_| ProgramError::AccountBorrowFailed)?;
212 if data.len() < 45 {
213 return Err(ProgramError::AccountDataTooSmall);
214 }
215 if data[44] == expected {
216 Ok(())
217 } else {
218 Err(ProgramError::InvalidAccountData)
219 }
220}
221
222#[inline]
230pub fn require_mint_freeze_authority(
231 mint_account: &AccountView,
232 expected_freeze: &Address,
233) -> ProgramResult {
234 let data = mint_account
235 .try_borrow()
236 .map_err(|_| ProgramError::AccountBorrowFailed)?;
237 if data.len() < 82 {
238 return Err(ProgramError::AccountDataTooSmall);
239 }
240 let tag = u32::from_le_bytes([data[46], data[47], data[48], data[49]]);
241 if tag != 1 {
242 return Err(ProgramError::InvalidAccountData);
243 }
244 let mut actual = [0u8; 32];
245 actual.copy_from_slice(&data[50..82]);
246 if actual == *expected_freeze.as_array() {
247 Ok(())
248 } else {
249 Err(ProgramError::IncorrectAuthority)
250 }
251}
252
253#[deprecated(
270 since = "0.2.0",
271 note = "use TransferChecked for Token-2022 safety (mint + decimals validation)"
272)]
273#[cfg(feature = "legacy-token-instructions")]
274pub struct Transfer<'a> {
275 pub from: &'a AccountView,
276 pub to: &'a AccountView,
277 pub authority: &'a AccountView,
278 pub amount: u64,
279}
280
281#[allow(deprecated)]
282#[cfg(feature = "legacy-token-instructions")]
283impl Transfer<'_> {
284 #[inline]
288 pub fn invoke(&self) -> ProgramResult {
289 require_authority_signed_direct(self.authority)?;
290 self.invoke_signed_unchecked(&[])
291 }
292
293 #[inline]
296 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
297 self.invoke_signed_unchecked(signers)
298 }
299
300 #[inline(always)]
301 fn invoke_signed_unchecked(&self, signers: &[Signer]) -> ProgramResult {
302 let mut data = [0u8; 9];
303 data[0] = 3;
304 data[1..9].copy_from_slice(&self.amount.to_le_bytes());
305
306 let accounts = [
307 InstructionAccount::writable(self.from.address()),
308 InstructionAccount::writable(self.to.address()),
309 InstructionAccount::readonly_signer(self.authority.address()),
310 ];
311 let views = [self.from, self.to, self.authority];
312 let instruction = InstructionView {
313 program_id: &TOKEN_PROGRAM_ID,
314 data: &data,
315 accounts: &accounts,
316 };
317
318 crate::cpi::invoke_signed(&instruction, &views, signers)
319 }
320}
321
322#[deprecated(
328 since = "0.2.0",
329 note = "use MintToChecked for Token-2022 safety (mint + decimals validation)"
330)]
331#[cfg(feature = "legacy-token-instructions")]
332pub struct MintTo<'a> {
333 pub mint: &'a AccountView,
334 pub account: &'a AccountView,
335 pub mint_authority: &'a AccountView,
336 pub amount: u64,
337}
338
339#[allow(deprecated)]
340#[cfg(feature = "legacy-token-instructions")]
341impl MintTo<'_> {
342 #[inline]
343 pub fn invoke(&self) -> ProgramResult {
344 require_authority_signed_direct(self.mint_authority)?;
345 self.invoke_signed(&[])
346 }
347
348 #[inline]
349 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
350 let mut data = [0u8; 9];
351 data[0] = 7;
352 data[1..9].copy_from_slice(&self.amount.to_le_bytes());
353
354 let accounts = [
355 InstructionAccount::writable(self.mint.address()),
356 InstructionAccount::writable(self.account.address()),
357 InstructionAccount::readonly_signer(self.mint_authority.address()),
358 ];
359 let views = [self.mint, self.account, self.mint_authority];
360 let instruction = InstructionView {
361 program_id: &TOKEN_PROGRAM_ID,
362 data: &data,
363 accounts: &accounts,
364 };
365
366 crate::cpi::invoke_signed(&instruction, &views, signers)
367 }
368}
369
370#[deprecated(
376 since = "0.2.0",
377 note = "use BurnChecked for Token-2022 safety (mint + decimals validation)"
378)]
379#[cfg(feature = "legacy-token-instructions")]
380pub struct Burn<'a> {
381 pub account: &'a AccountView,
382 pub mint: &'a AccountView,
383 pub authority: &'a AccountView,
384 pub amount: u64,
385}
386
387#[allow(deprecated)]
388#[cfg(feature = "legacy-token-instructions")]
389impl Burn<'_> {
390 #[inline]
391 pub fn invoke(&self) -> ProgramResult {
392 require_authority_signed_direct(self.authority)?;
393 self.invoke_signed(&[])
394 }
395
396 #[inline]
397 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
398 let mut data = [0u8; 9];
399 data[0] = 8;
400 data[1..9].copy_from_slice(&self.amount.to_le_bytes());
401
402 let accounts = [
403 InstructionAccount::writable(self.account.address()),
404 InstructionAccount::writable(self.mint.address()),
405 InstructionAccount::readonly_signer(self.authority.address()),
406 ];
407 let views = [self.account, self.mint, self.authority];
408 let instruction = InstructionView {
409 program_id: &TOKEN_PROGRAM_ID,
410 data: &data,
411 accounts: &accounts,
412 };
413
414 crate::cpi::invoke_signed(&instruction, &views, signers)
415 }
416}
417
418pub struct CloseAccount<'a> {
422 pub account: &'a AccountView,
423 pub destination: &'a AccountView,
424 pub authority: &'a AccountView,
425}
426
427impl CloseAccount<'_> {
428 #[inline]
429 pub fn invoke(&self) -> ProgramResult {
430 require_authority_signed_direct(self.authority)?;
431 self.invoke_signed(&[])
432 }
433
434 #[inline]
435 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
436 let data = [9u8];
437 let accounts = [
438 InstructionAccount::writable(self.account.address()),
439 InstructionAccount::writable(self.destination.address()),
440 InstructionAccount::readonly_signer(self.authority.address()),
441 ];
442 let views = [self.account, self.destination, self.authority];
443 let instruction = InstructionView {
444 program_id: &TOKEN_PROGRAM_ID,
445 data: &data,
446 accounts: &accounts,
447 };
448
449 crate::cpi::invoke_signed(&instruction, &views, signers)
450 }
451}
452
453#[deprecated(
459 since = "0.2.0",
460 note = "use ApproveChecked for Token-2022 safety (mint + decimals validation)"
461)]
462#[cfg(feature = "legacy-token-instructions")]
463pub struct Approve<'a> {
464 pub source: &'a AccountView,
465 pub delegate: &'a AccountView,
466 pub authority: &'a AccountView,
467 pub amount: u64,
468}
469
470#[allow(deprecated)]
471#[cfg(feature = "legacy-token-instructions")]
472impl Approve<'_> {
473 #[inline]
474 pub fn invoke(&self) -> ProgramResult {
475 require_authority_signed_direct(self.authority)?;
476 self.invoke_signed(&[])
477 }
478
479 #[inline]
480 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
481 let mut data = [0u8; 9];
482 data[0] = 4;
483 data[1..9].copy_from_slice(&self.amount.to_le_bytes());
484
485 let accounts = [
486 InstructionAccount::writable(self.source.address()),
487 InstructionAccount::readonly(self.delegate.address()),
488 InstructionAccount::readonly_signer(self.authority.address()),
489 ];
490 let views = [self.source, self.delegate, self.authority];
491 let instruction = InstructionView {
492 program_id: &TOKEN_PROGRAM_ID,
493 data: &data,
494 accounts: &accounts,
495 };
496
497 crate::cpi::invoke_signed(&instruction, &views, signers)
498 }
499}
500
501pub struct Revoke<'a> {
505 pub source: &'a AccountView,
506 pub authority: &'a AccountView,
507}
508
509impl Revoke<'_> {
510 #[inline]
511 pub fn invoke(&self) -> ProgramResult {
512 require_authority_signed_direct(self.authority)?;
513 self.invoke_signed(&[])
514 }
515
516 #[inline]
517 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
518 let data = [5u8];
519 let accounts = [
520 InstructionAccount::writable(self.source.address()),
521 InstructionAccount::readonly_signer(self.authority.address()),
522 ];
523 let views = [self.source, self.authority];
524 let instruction = InstructionView {
525 program_id: &TOKEN_PROGRAM_ID,
526 data: &data,
527 accounts: &accounts,
528 };
529
530 crate::cpi::invoke_signed(&instruction, &views, signers)
531 }
532}
533
534pub struct TransferChecked<'a> {
551 pub from: &'a AccountView,
552 pub mint: &'a AccountView,
553 pub to: &'a AccountView,
554 pub authority: &'a AccountView,
555 pub amount: u64,
556 pub decimals: u8,
557}
558
559impl TransferChecked<'_> {
560 #[inline]
564 pub fn invoke(&self) -> ProgramResult {
565 require_authority_signed_direct(self.authority)?;
566 self.invoke_signed_unchecked(&[])
567 }
568
569 #[inline]
581 pub fn invoke_strict(&self) -> ProgramResult {
582 require_authority_signed_direct(self.authority)?;
583 require_token_authority(self.from, self.authority)?;
584 self.invoke_signed_unchecked(&[])
585 }
586
587 #[inline]
590 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
591 self.invoke_signed_unchecked(signers)
592 }
593
594 #[inline]
598 pub fn invoke_signed_strict(&self, signers: &[Signer]) -> ProgramResult {
599 require_token_authority(self.from, self.authority)?;
600 self.invoke_signed_unchecked(signers)
601 }
602
603 #[inline(always)]
604 fn invoke_signed_unchecked(&self, signers: &[Signer]) -> ProgramResult {
605 let mut data = [0u8; 10];
606 data[0] = 12;
607 data[1..9].copy_from_slice(&self.amount.to_le_bytes());
608 data[9] = self.decimals;
609
610 let accounts = [
611 InstructionAccount::writable(self.from.address()),
612 InstructionAccount::readonly(self.mint.address()),
613 InstructionAccount::writable(self.to.address()),
614 InstructionAccount::readonly_signer(self.authority.address()),
615 ];
616 let views = [self.from, self.mint, self.to, self.authority];
617 let instruction = InstructionView {
618 program_id: &TOKEN_PROGRAM_ID,
619 data: &data,
620 accounts: &accounts,
621 };
622
623 crate::cpi::invoke_signed(&instruction, &views, signers)
624 }
625}
626
627pub struct MintToChecked<'a> {
634 pub mint: &'a AccountView,
635 pub account: &'a AccountView,
636 pub mint_authority: &'a AccountView,
637 pub amount: u64,
638 pub decimals: u8,
639}
640
641impl MintToChecked<'_> {
642 #[inline]
643 pub fn invoke(&self) -> ProgramResult {
644 require_authority_signed_direct(self.mint_authority)?;
645 self.invoke_signed_unchecked(&[])
646 }
647
648 #[inline]
649 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
650 self.invoke_signed_unchecked(signers)
651 }
652
653 #[inline(always)]
654 fn invoke_signed_unchecked(&self, signers: &[Signer]) -> ProgramResult {
655 let mut data = [0u8; 10];
656 data[0] = 14;
657 data[1..9].copy_from_slice(&self.amount.to_le_bytes());
658 data[9] = self.decimals;
659
660 let accounts = [
661 InstructionAccount::writable(self.mint.address()),
662 InstructionAccount::writable(self.account.address()),
663 InstructionAccount::readonly_signer(self.mint_authority.address()),
664 ];
665 let views = [self.mint, self.account, self.mint_authority];
666 let instruction = InstructionView {
667 program_id: &TOKEN_PROGRAM_ID,
668 data: &data,
669 accounts: &accounts,
670 };
671
672 crate::cpi::invoke_signed(&instruction, &views, signers)
673 }
674}
675
676pub struct BurnChecked<'a> {
684 pub account: &'a AccountView,
685 pub mint: &'a AccountView,
686 pub authority: &'a AccountView,
687 pub amount: u64,
688 pub decimals: u8,
689}
690
691impl BurnChecked<'_> {
692 #[inline]
693 pub fn invoke(&self) -> ProgramResult {
694 require_authority_signed_direct(self.authority)?;
695 self.invoke_signed_unchecked(&[])
696 }
697
698 #[inline]
702 pub fn invoke_strict(&self) -> ProgramResult {
703 require_authority_signed_direct(self.authority)?;
704 require_token_authority(self.account, self.authority)?;
705 self.invoke_signed_unchecked(&[])
706 }
707
708 #[inline]
709 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
710 self.invoke_signed_unchecked(signers)
711 }
712
713 #[inline]
717 pub fn invoke_signed_strict(&self, signers: &[Signer]) -> ProgramResult {
718 require_token_authority(self.account, self.authority)?;
719 self.invoke_signed_unchecked(signers)
720 }
721
722 #[inline(always)]
723 fn invoke_signed_unchecked(&self, signers: &[Signer]) -> ProgramResult {
724 let mut data = [0u8; 10];
725 data[0] = 15;
726 data[1..9].copy_from_slice(&self.amount.to_le_bytes());
727 data[9] = self.decimals;
728
729 let accounts = [
730 InstructionAccount::writable(self.account.address()),
731 InstructionAccount::writable(self.mint.address()),
732 InstructionAccount::readonly_signer(self.authority.address()),
733 ];
734 let views = [self.account, self.mint, self.authority];
735 let instruction = InstructionView {
736 program_id: &TOKEN_PROGRAM_ID,
737 data: &data,
738 accounts: &accounts,
739 };
740
741 crate::cpi::invoke_signed(&instruction, &views, signers)
742 }
743}
744
745pub struct ApproveChecked<'a> {
752 pub source: &'a AccountView,
753 pub mint: &'a AccountView,
754 pub delegate: &'a AccountView,
755 pub authority: &'a AccountView,
756 pub amount: u64,
757 pub decimals: u8,
758}
759
760impl ApproveChecked<'_> {
761 #[inline]
762 pub fn invoke(&self) -> ProgramResult {
763 require_authority_signed_direct(self.authority)?;
764 self.invoke_signed_unchecked(&[])
765 }
766
767 #[inline]
772 pub fn invoke_strict(&self) -> ProgramResult {
773 require_authority_signed_direct(self.authority)?;
774 require_token_authority(self.source, self.authority)?;
775 self.invoke_signed_unchecked(&[])
776 }
777
778 #[inline]
779 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
780 self.invoke_signed_unchecked(signers)
781 }
782
783 #[inline]
786 pub fn invoke_signed_strict(&self, signers: &[Signer]) -> ProgramResult {
787 require_token_authority(self.source, self.authority)?;
788 self.invoke_signed_unchecked(signers)
789 }
790
791 #[inline(always)]
792 fn invoke_signed_unchecked(&self, signers: &[Signer]) -> ProgramResult {
793 let mut data = [0u8; 10];
794 data[0] = 13;
795 data[1..9].copy_from_slice(&self.amount.to_le_bytes());
796 data[9] = self.decimals;
797
798 let accounts = [
799 InstructionAccount::writable(self.source.address()),
800 InstructionAccount::readonly(self.mint.address()),
801 InstructionAccount::readonly(self.delegate.address()),
802 InstructionAccount::readonly_signer(self.authority.address()),
803 ];
804 let views = [self.source, self.mint, self.delegate, self.authority];
805 let instruction = InstructionView {
806 program_id: &TOKEN_PROGRAM_ID,
807 data: &data,
808 accounts: &accounts,
809 };
810
811 crate::cpi::invoke_signed(&instruction, &views, signers)
812 }
813}
814
815pub struct InitializeAccount<'a> {
819 pub account: &'a AccountView,
820 pub mint: &'a AccountView,
821 pub owner: &'a AccountView,
822 pub rent_sysvar: &'a AccountView,
823}
824
825impl InitializeAccount<'_> {
826 #[inline]
827 pub fn invoke(&self) -> ProgramResult {
828 let data = [1u8];
829 let accounts = [
830 InstructionAccount::writable(self.account.address()),
831 InstructionAccount::readonly(self.mint.address()),
832 InstructionAccount::readonly(self.owner.address()),
833 InstructionAccount::readonly(self.rent_sysvar.address()),
834 ];
835 let views = [self.account, self.mint, self.owner, self.rent_sysvar];
836 let instruction = InstructionView {
837 program_id: &TOKEN_PROGRAM_ID,
838 data: &data,
839 accounts: &accounts,
840 };
841
842 crate::cpi::invoke(&instruction, &views)
843 }
844}
845
846pub const TOKEN_PROGRAM_ID: Address = Address::new_from_array(five8_const::decode_32_const(
848 "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
849));
850
851pub mod instructions {
853 pub use super::{
854 ApproveChecked, BurnChecked, CloseAccount, InitializeAccount, MintToChecked, Revoke,
855 TransferChecked,
856 };
857
858 #[cfg(feature = "legacy-token-instructions")]
859 #[allow(deprecated)]
860 pub use super::{Approve, Burn, MintTo, Transfer};
861}
862
863#[cfg(test)]
864mod tests {
865 use super::*;
873
874 #[test]
879 fn transfer_checked_discriminator_is_12() {
880 }
900
901 fn encode_checked(disc: u8, amount: u64, decimals: u8) -> [u8; 10] {
905 let mut data = [0u8; 10];
906 data[0] = disc;
907 data[1..9].copy_from_slice(&amount.to_le_bytes());
908 data[9] = decimals;
909 data
910 }
911
912 #[test]
913 fn transfer_checked_wire_format_is_stable() {
914 let out = encode_checked(12, 0x0102_0304_0506_0708, 9);
916 assert_eq!(out[0], 12);
917 assert_eq!(
918 &out[1..9],
919 &[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]
920 );
921 assert_eq!(out[9], 9);
922 }
923
924 #[test]
925 fn mint_to_checked_wire_format_is_stable() {
926 let out = encode_checked(14, 1000, 6);
927 assert_eq!(out[0], 14);
928 assert_eq!(u64::from_le_bytes(out[1..9].try_into().unwrap()), 1000);
929 assert_eq!(out[9], 6);
930 }
931
932 #[test]
933 fn burn_checked_wire_format_is_stable() {
934 let out = encode_checked(15, 42, 8);
935 assert_eq!(out[0], 15);
936 assert_eq!(u64::from_le_bytes(out[1..9].try_into().unwrap()), 42);
937 assert_eq!(out[9], 8);
938 }
939
940 #[test]
941 fn approve_checked_wire_format_is_stable() {
942 let out = encode_checked(13, u64::MAX, 0);
943 assert_eq!(out[0], 13);
944 assert_eq!(u64::from_le_bytes(out[1..9].try_into().unwrap()), u64::MAX);
945 assert_eq!(out[9], 0);
946 }
947
948 #[test]
949 fn checked_encoding_round_trips_decimals_range() {
950 for d in 0u8..=255 {
954 let out = encode_checked(12, 1, d);
955 assert_eq!(out[9], d);
956 }
957 }
958
959 #[test]
960 fn checked_encoding_preserves_amount_bits() {
961 for shift in 0..8 {
964 let amount = 0xABu64 << (shift * 8);
965 let out = encode_checked(12, amount, 0);
966 let decoded = u64::from_le_bytes(out[1..9].try_into().unwrap());
967 assert_eq!(decoded, amount);
968 }
969 }
970
971 fn make_token_and_authority(
979 authority_bytes: [u8; 32],
980 token_owner_bytes: [u8; 32],
981 ) -> (
982 std::vec::Vec<u8>,
983 std::vec::Vec<u8>,
984 crate::account::AccountView,
985 crate::account::AccountView,
986 ) {
987 use hopper_native::{
988 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount,
989 NOT_BORROWED,
990 };
991
992 let token_data_len = 165;
997 let mut token_backing = std::vec![0u8; RuntimeAccount::SIZE + token_data_len];
998 let token_raw = token_backing.as_mut_ptr() as *mut RuntimeAccount;
999 unsafe {
1001 token_raw.write(RuntimeAccount {
1002 borrow_state: NOT_BORROWED,
1003 is_signer: 0,
1004 is_writable: 1,
1005 executable: 0,
1006 resize_delta: 0,
1007 address: NativeAddress::new_from_array([0xAA; 32]),
1008 owner: NativeAddress::new_from_array([3; 32]),
1009 lamports: 2_039_280,
1010 data_len: token_data_len as u64,
1011 });
1012 let data_ptr = (token_raw as *mut u8).add(RuntimeAccount::SIZE);
1014 core::ptr::copy_nonoverlapping(token_owner_bytes.as_ptr(), data_ptr.add(32), 32);
1015 }
1016 let token_backend = unsafe { NativeAccountView::new_unchecked(token_raw) };
1018 let token_view = crate::account::AccountView::from_backend(token_backend);
1019
1020 let mut auth_backing = std::vec![0u8; RuntimeAccount::SIZE];
1022 let auth_raw = auth_backing.as_mut_ptr() as *mut RuntimeAccount;
1023 unsafe {
1025 auth_raw.write(RuntimeAccount {
1026 borrow_state: NOT_BORROWED,
1027 is_signer: 1,
1028 is_writable: 0,
1029 executable: 0,
1030 resize_delta: 0,
1031 address: NativeAddress::new_from_array(authority_bytes),
1032 owner: NativeAddress::new_from_array([0; 32]),
1033 lamports: 0,
1034 data_len: 0,
1035 });
1036 }
1037 let auth_backend = unsafe { NativeAccountView::new_unchecked(auth_raw) };
1039 let auth_view = crate::account::AccountView::from_backend(auth_backend);
1040
1041 (token_backing, auth_backing, token_view, auth_view)
1042 }
1043
1044 #[test]
1045 fn require_token_authority_accepts_matching_owner() {
1046 let authority = [0x42u8; 32];
1047 let (_tb, _ab, token, auth) = make_token_and_authority(authority, authority);
1048 require_token_authority(&token, &auth).unwrap();
1049 }
1050
1051 #[test]
1052 fn require_token_authority_rejects_mismatched_owner() {
1053 let authority = [0x42u8; 32];
1054 let wrong_owner = [0x77u8; 32];
1055 let (_tb, _ab, token, auth) = make_token_and_authority(authority, wrong_owner);
1056 let err = require_token_authority(&token, &auth).unwrap_err();
1057 assert!(matches!(err, ProgramError::IncorrectAuthority));
1058 }
1059
1060 #[test]
1061 fn require_token_authority_rejects_short_buffer() {
1062 use hopper_native::{
1063 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount,
1064 NOT_BORROWED,
1065 };
1066
1067 let data_len = 50;
1071 let mut backing = std::vec![0u8; RuntimeAccount::SIZE + data_len];
1072 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
1073 unsafe {
1075 raw.write(RuntimeAccount {
1076 borrow_state: NOT_BORROWED,
1077 is_signer: 0,
1078 is_writable: 1,
1079 executable: 0,
1080 resize_delta: 0,
1081 address: NativeAddress::new_from_array([0xAA; 32]),
1082 owner: NativeAddress::new_from_array([3; 32]),
1083 lamports: 0,
1084 data_len: data_len as u64,
1085 });
1086 }
1087 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
1089 let token = crate::account::AccountView::from_backend(backend);
1090
1091 let (_ab, _, _, auth) = make_token_and_authority([0x11; 32], [0x11; 32]);
1092 let err = require_token_authority(&token, &auth).unwrap_err();
1093 assert!(matches!(err, ProgramError::AccountDataTooSmall));
1094 }
1095
1096 fn make_token_with_mint_and_owner(
1108 mint_bytes: [u8; 32],
1109 owner_bytes: [u8; 32],
1110 ) -> (std::vec::Vec<u8>, crate::account::AccountView) {
1111 use hopper_native::{
1112 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount,
1113 NOT_BORROWED,
1114 };
1115
1116 let token_data_len = 165;
1117 let mut backing = std::vec![0u8; RuntimeAccount::SIZE + token_data_len];
1118 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
1119 unsafe {
1121 raw.write(RuntimeAccount {
1122 borrow_state: NOT_BORROWED,
1123 is_signer: 0,
1124 is_writable: 1,
1125 executable: 0,
1126 resize_delta: 0,
1127 address: NativeAddress::new_from_array([0xAA; 32]),
1128 owner: NativeAddress::new_from_array([3; 32]),
1129 lamports: 2_039_280,
1130 data_len: token_data_len as u64,
1131 });
1132 let data_ptr = (raw as *mut u8).add(RuntimeAccount::SIZE);
1133 core::ptr::copy_nonoverlapping(mint_bytes.as_ptr(), data_ptr, 32);
1134 core::ptr::copy_nonoverlapping(owner_bytes.as_ptr(), data_ptr.add(32), 32);
1135 }
1136 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
1138 let view = crate::account::AccountView::from_backend(backend);
1139 (backing, view)
1140 }
1141
1142 fn make_mint_with_authority_decimals(
1146 mint_authority: [u8; 32],
1147 decimals: u8,
1148 ) -> (std::vec::Vec<u8>, crate::account::AccountView) {
1149 use hopper_native::{
1150 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount,
1151 NOT_BORROWED,
1152 };
1153
1154 let mint_data_len = 82;
1155 let mut backing = std::vec![0u8; RuntimeAccount::SIZE + mint_data_len];
1156 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
1157 unsafe {
1159 raw.write(RuntimeAccount {
1160 borrow_state: NOT_BORROWED,
1161 is_signer: 0,
1162 is_writable: 0,
1163 executable: 0,
1164 resize_delta: 0,
1165 address: NativeAddress::new_from_array([0xBB; 32]),
1166 owner: NativeAddress::new_from_array([3; 32]),
1167 lamports: 1_461_600,
1168 data_len: mint_data_len as u64,
1169 });
1170 let data_ptr = (raw as *mut u8).add(RuntimeAccount::SIZE);
1171 let some_tag: [u8; 4] = 1u32.to_le_bytes();
1173 core::ptr::copy_nonoverlapping(some_tag.as_ptr(), data_ptr, 4);
1174 core::ptr::copy_nonoverlapping(mint_authority.as_ptr(), data_ptr.add(4), 32);
1175 *data_ptr.add(44) = decimals;
1178 *data_ptr.add(45) = 1;
1180 }
1182 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
1184 let view = crate::account::AccountView::from_backend(backend);
1185 (backing, view)
1186 }
1187
1188 #[test]
1189 fn require_token_mint_accepts_matching_mint() {
1190 let mint = [0xABu8; 32];
1191 let (_b, view) = make_token_with_mint_and_owner(mint, [0; 32]);
1192 let expected = crate::address::Address::new_from_array(mint);
1193 require_token_mint(&view, &expected).unwrap();
1194 }
1195
1196 #[test]
1197 fn require_token_mint_rejects_mismatched_mint() {
1198 let mint = [0xABu8; 32];
1199 let (_b, view) = make_token_with_mint_and_owner(mint, [0; 32]);
1200 let wrong = crate::address::Address::new_from_array([0xCDu8; 32]);
1201 let err = require_token_mint(&view, &wrong).unwrap_err();
1202 assert!(matches!(err, ProgramError::InvalidAccountData));
1203 }
1204
1205 #[test]
1206 fn require_token_owner_eq_matches() {
1207 let owner = [0x77u8; 32];
1208 let (_b, view) = make_token_with_mint_and_owner([0; 32], owner);
1209 let expected = crate::address::Address::new_from_array(owner);
1210 require_token_owner_eq(&view, &expected).unwrap();
1211 }
1212
1213 #[test]
1214 fn require_token_owner_eq_rejects_mismatch() {
1215 let owner = [0x77u8; 32];
1216 let (_b, view) = make_token_with_mint_and_owner([0; 32], owner);
1217 let wrong = crate::address::Address::new_from_array([0x88u8; 32]);
1218 let err = require_token_owner_eq(&view, &wrong).unwrap_err();
1219 assert!(matches!(err, ProgramError::IncorrectAuthority));
1220 }
1221
1222 #[test]
1223 fn require_mint_authority_accepts_matching() {
1224 let auth = [0x99u8; 32];
1225 let (_b, view) = make_mint_with_authority_decimals(auth, 6);
1226 let expected = crate::address::Address::new_from_array(auth);
1227 require_mint_authority(&view, &expected).unwrap();
1228 }
1229
1230 #[test]
1231 fn require_mint_authority_rejects_mismatched() {
1232 let auth = [0x99u8; 32];
1233 let (_b, view) = make_mint_with_authority_decimals(auth, 6);
1234 let wrong = crate::address::Address::new_from_array([0x00u8; 32]);
1235 let err = require_mint_authority(&view, &wrong).unwrap_err();
1236 assert!(matches!(err, ProgramError::IncorrectAuthority));
1237 }
1238
1239 #[test]
1240 fn require_mint_decimals_matches() {
1241 let (_b, view) = make_mint_with_authority_decimals([1u8; 32], 9);
1242 require_mint_decimals(&view, 9).unwrap();
1243 }
1244
1245 #[test]
1246 fn require_mint_decimals_rejects_mismatch() {
1247 let (_b, view) = make_mint_with_authority_decimals([1u8; 32], 9);
1248 let err = require_mint_decimals(&view, 6).unwrap_err();
1249 assert!(matches!(err, ProgramError::InvalidAccountData));
1250 }
1251
1252 #[test]
1253 fn require_mint_freeze_authority_rejects_none_tag() {
1254 let (_b, view) = make_mint_with_authority_decimals([1u8; 32], 9);
1260 let expected = crate::address::Address::new_from_array([2u8; 32]);
1261 let err = require_mint_freeze_authority(&view, &expected).unwrap_err();
1262 assert!(matches!(err, ProgramError::InvalidAccountData));
1263 }
1264}