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]
143pub fn require_token_mint(
144 token_account: &AccountView,
145 expected_mint: &Address,
146) -> ProgramResult {
147 let data = token_account
148 .try_borrow()
149 .map_err(|_| ProgramError::AccountBorrowFailed)?;
150 if data.len() < 32 {
151 return Err(ProgramError::AccountDataTooSmall);
152 }
153 let actual: [u8; 32] = {
154 let mut out = [0u8; 32];
155 out.copy_from_slice(&data[0..32]);
156 out
157 };
158 if actual == *expected_mint.as_array() {
159 Ok(())
160 } else {
161 Err(ProgramError::InvalidAccountData)
162 }
163}
164
165#[inline]
184pub fn require_mint_authority(
185 mint_account: &AccountView,
186 expected_authority: &Address,
187) -> ProgramResult {
188 let data = mint_account
189 .try_borrow()
190 .map_err(|_| ProgramError::AccountBorrowFailed)?;
191 if data.len() < 46 {
192 return Err(ProgramError::AccountDataTooSmall);
193 }
194 let tag = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
195 if tag != 1 {
196 return Err(ProgramError::InvalidAccountData);
198 }
199 let mut actual = [0u8; 32];
200 actual.copy_from_slice(&data[4..36]);
201 if actual == *expected_authority.as_array() {
202 Ok(())
203 } else {
204 Err(ProgramError::IncorrectAuthority)
205 }
206}
207
208#[inline]
214pub fn require_mint_decimals(mint_account: &AccountView, expected: u8) -> ProgramResult {
215 let data = mint_account
216 .try_borrow()
217 .map_err(|_| ProgramError::AccountBorrowFailed)?;
218 if data.len() < 45 {
219 return Err(ProgramError::AccountDataTooSmall);
220 }
221 if data[44] == expected {
222 Ok(())
223 } else {
224 Err(ProgramError::InvalidAccountData)
225 }
226}
227
228#[inline]
236pub fn require_mint_freeze_authority(
237 mint_account: &AccountView,
238 expected_freeze: &Address,
239) -> ProgramResult {
240 let data = mint_account
241 .try_borrow()
242 .map_err(|_| ProgramError::AccountBorrowFailed)?;
243 if data.len() < 82 {
244 return Err(ProgramError::AccountDataTooSmall);
245 }
246 let tag = u32::from_le_bytes([data[46], data[47], data[48], data[49]]);
247 if tag != 1 {
248 return Err(ProgramError::InvalidAccountData);
249 }
250 let mut actual = [0u8; 32];
251 actual.copy_from_slice(&data[50..82]);
252 if actual == *expected_freeze.as_array() {
253 Ok(())
254 } else {
255 Err(ProgramError::IncorrectAuthority)
256 }
257}
258
259#[deprecated(
276 since = "0.2.0",
277 note = "use TransferChecked for Token-2022 safety (mint + decimals validation)"
278)]
279#[cfg(feature = "legacy-token-instructions")]
280pub struct Transfer<'a> {
281 pub from: &'a AccountView,
282 pub to: &'a AccountView,
283 pub authority: &'a AccountView,
284 pub amount: u64,
285}
286
287#[allow(deprecated)]
288#[cfg(feature = "legacy-token-instructions")]
289impl Transfer<'_> {
290 #[inline]
294 pub fn invoke(&self) -> ProgramResult {
295 require_authority_signed_direct(self.authority)?;
296 self.invoke_signed_unchecked(&[])
297 }
298
299 #[inline]
302 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
303 self.invoke_signed_unchecked(signers)
304 }
305
306 #[inline(always)]
307 fn invoke_signed_unchecked(&self, signers: &[Signer]) -> ProgramResult {
308 let mut data = [0u8; 9];
309 data[0] = 3;
310 data[1..9].copy_from_slice(&self.amount.to_le_bytes());
311
312 let accounts = [
313 InstructionAccount::writable(self.from.address()),
314 InstructionAccount::writable(self.to.address()),
315 InstructionAccount::readonly_signer(self.authority.address()),
316 ];
317 let views = [self.from, self.to, self.authority];
318 let instruction = InstructionView {
319 program_id: &TOKEN_PROGRAM_ID,
320 data: &data,
321 accounts: &accounts,
322 };
323
324 crate::cpi::invoke_signed(&instruction, &views, signers)
325 }
326}
327
328#[deprecated(
334 since = "0.2.0",
335 note = "use MintToChecked for Token-2022 safety (mint + decimals validation)"
336)]
337#[cfg(feature = "legacy-token-instructions")]
338pub struct MintTo<'a> {
339 pub mint: &'a AccountView,
340 pub account: &'a AccountView,
341 pub mint_authority: &'a AccountView,
342 pub amount: u64,
343}
344
345#[allow(deprecated)]
346#[cfg(feature = "legacy-token-instructions")]
347impl MintTo<'_> {
348 #[inline]
349 pub fn invoke(&self) -> ProgramResult {
350 require_authority_signed_direct(self.mint_authority)?;
351 self.invoke_signed(&[])
352 }
353
354 #[inline]
355 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
356 let mut data = [0u8; 9];
357 data[0] = 7;
358 data[1..9].copy_from_slice(&self.amount.to_le_bytes());
359
360 let accounts = [
361 InstructionAccount::writable(self.mint.address()),
362 InstructionAccount::writable(self.account.address()),
363 InstructionAccount::readonly_signer(self.mint_authority.address()),
364 ];
365 let views = [self.mint, self.account, self.mint_authority];
366 let instruction = InstructionView {
367 program_id: &TOKEN_PROGRAM_ID,
368 data: &data,
369 accounts: &accounts,
370 };
371
372 crate::cpi::invoke_signed(&instruction, &views, signers)
373 }
374}
375
376#[deprecated(
382 since = "0.2.0",
383 note = "use BurnChecked for Token-2022 safety (mint + decimals validation)"
384)]
385#[cfg(feature = "legacy-token-instructions")]
386pub struct Burn<'a> {
387 pub account: &'a AccountView,
388 pub mint: &'a AccountView,
389 pub authority: &'a AccountView,
390 pub amount: u64,
391}
392
393#[allow(deprecated)]
394#[cfg(feature = "legacy-token-instructions")]
395impl Burn<'_> {
396 #[inline]
397 pub fn invoke(&self) -> ProgramResult {
398 require_authority_signed_direct(self.authority)?;
399 self.invoke_signed(&[])
400 }
401
402 #[inline]
403 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
404 let mut data = [0u8; 9];
405 data[0] = 8;
406 data[1..9].copy_from_slice(&self.amount.to_le_bytes());
407
408 let accounts = [
409 InstructionAccount::writable(self.account.address()),
410 InstructionAccount::writable(self.mint.address()),
411 InstructionAccount::readonly_signer(self.authority.address()),
412 ];
413 let views = [self.account, self.mint, self.authority];
414 let instruction = InstructionView {
415 program_id: &TOKEN_PROGRAM_ID,
416 data: &data,
417 accounts: &accounts,
418 };
419
420 crate::cpi::invoke_signed(&instruction, &views, signers)
421 }
422}
423
424pub struct CloseAccount<'a> {
428 pub account: &'a AccountView,
429 pub destination: &'a AccountView,
430 pub authority: &'a AccountView,
431}
432
433impl CloseAccount<'_> {
434 #[inline]
435 pub fn invoke(&self) -> ProgramResult {
436 require_authority_signed_direct(self.authority)?;
437 self.invoke_signed(&[])
438 }
439
440 #[inline]
441 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
442 let data = [9u8];
443 let accounts = [
444 InstructionAccount::writable(self.account.address()),
445 InstructionAccount::writable(self.destination.address()),
446 InstructionAccount::readonly_signer(self.authority.address()),
447 ];
448 let views = [self.account, self.destination, self.authority];
449 let instruction = InstructionView {
450 program_id: &TOKEN_PROGRAM_ID,
451 data: &data,
452 accounts: &accounts,
453 };
454
455 crate::cpi::invoke_signed(&instruction, &views, signers)
456 }
457}
458
459#[deprecated(
465 since = "0.2.0",
466 note = "use ApproveChecked for Token-2022 safety (mint + decimals validation)"
467)]
468#[cfg(feature = "legacy-token-instructions")]
469pub struct Approve<'a> {
470 pub source: &'a AccountView,
471 pub delegate: &'a AccountView,
472 pub authority: &'a AccountView,
473 pub amount: u64,
474}
475
476#[allow(deprecated)]
477#[cfg(feature = "legacy-token-instructions")]
478impl Approve<'_> {
479 #[inline]
480 pub fn invoke(&self) -> ProgramResult {
481 require_authority_signed_direct(self.authority)?;
482 self.invoke_signed(&[])
483 }
484
485 #[inline]
486 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
487 let mut data = [0u8; 9];
488 data[0] = 4;
489 data[1..9].copy_from_slice(&self.amount.to_le_bytes());
490
491 let accounts = [
492 InstructionAccount::writable(self.source.address()),
493 InstructionAccount::readonly(self.delegate.address()),
494 InstructionAccount::readonly_signer(self.authority.address()),
495 ];
496 let views = [self.source, self.delegate, self.authority];
497 let instruction = InstructionView {
498 program_id: &TOKEN_PROGRAM_ID,
499 data: &data,
500 accounts: &accounts,
501 };
502
503 crate::cpi::invoke_signed(&instruction, &views, signers)
504 }
505}
506
507pub struct Revoke<'a> {
511 pub source: &'a AccountView,
512 pub authority: &'a AccountView,
513}
514
515impl Revoke<'_> {
516 #[inline]
517 pub fn invoke(&self) -> ProgramResult {
518 require_authority_signed_direct(self.authority)?;
519 self.invoke_signed(&[])
520 }
521
522 #[inline]
523 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
524 let data = [5u8];
525 let accounts = [
526 InstructionAccount::writable(self.source.address()),
527 InstructionAccount::readonly_signer(self.authority.address()),
528 ];
529 let views = [self.source, self.authority];
530 let instruction = InstructionView {
531 program_id: &TOKEN_PROGRAM_ID,
532 data: &data,
533 accounts: &accounts,
534 };
535
536 crate::cpi::invoke_signed(&instruction, &views, signers)
537 }
538}
539
540pub struct TransferChecked<'a> {
557 pub from: &'a AccountView,
558 pub mint: &'a AccountView,
559 pub to: &'a AccountView,
560 pub authority: &'a AccountView,
561 pub amount: u64,
562 pub decimals: u8,
563}
564
565impl TransferChecked<'_> {
566 #[inline]
570 pub fn invoke(&self) -> ProgramResult {
571 require_authority_signed_direct(self.authority)?;
572 self.invoke_signed_unchecked(&[])
573 }
574
575 #[inline]
587 pub fn invoke_strict(&self) -> ProgramResult {
588 require_authority_signed_direct(self.authority)?;
589 require_token_authority(self.from, self.authority)?;
590 self.invoke_signed_unchecked(&[])
591 }
592
593 #[inline]
596 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
597 self.invoke_signed_unchecked(signers)
598 }
599
600 #[inline]
604 pub fn invoke_signed_strict(&self, signers: &[Signer]) -> ProgramResult {
605 require_token_authority(self.from, self.authority)?;
606 self.invoke_signed_unchecked(signers)
607 }
608
609 #[inline(always)]
610 fn invoke_signed_unchecked(&self, signers: &[Signer]) -> ProgramResult {
611 let mut data = [0u8; 10];
612 data[0] = 12;
613 data[1..9].copy_from_slice(&self.amount.to_le_bytes());
614 data[9] = self.decimals;
615
616 let accounts = [
617 InstructionAccount::writable(self.from.address()),
618 InstructionAccount::readonly(self.mint.address()),
619 InstructionAccount::writable(self.to.address()),
620 InstructionAccount::readonly_signer(self.authority.address()),
621 ];
622 let views = [self.from, self.mint, self.to, self.authority];
623 let instruction = InstructionView {
624 program_id: &TOKEN_PROGRAM_ID,
625 data: &data,
626 accounts: &accounts,
627 };
628
629 crate::cpi::invoke_signed(&instruction, &views, signers)
630 }
631}
632
633pub struct MintToChecked<'a> {
640 pub mint: &'a AccountView,
641 pub account: &'a AccountView,
642 pub mint_authority: &'a AccountView,
643 pub amount: u64,
644 pub decimals: u8,
645}
646
647impl MintToChecked<'_> {
648 #[inline]
649 pub fn invoke(&self) -> ProgramResult {
650 require_authority_signed_direct(self.mint_authority)?;
651 self.invoke_signed_unchecked(&[])
652 }
653
654 #[inline]
655 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
656 self.invoke_signed_unchecked(signers)
657 }
658
659 #[inline(always)]
660 fn invoke_signed_unchecked(&self, signers: &[Signer]) -> ProgramResult {
661 let mut data = [0u8; 10];
662 data[0] = 14;
663 data[1..9].copy_from_slice(&self.amount.to_le_bytes());
664 data[9] = self.decimals;
665
666 let accounts = [
667 InstructionAccount::writable(self.mint.address()),
668 InstructionAccount::writable(self.account.address()),
669 InstructionAccount::readonly_signer(self.mint_authority.address()),
670 ];
671 let views = [self.mint, self.account, self.mint_authority];
672 let instruction = InstructionView {
673 program_id: &TOKEN_PROGRAM_ID,
674 data: &data,
675 accounts: &accounts,
676 };
677
678 crate::cpi::invoke_signed(&instruction, &views, signers)
679 }
680}
681
682pub struct BurnChecked<'a> {
690 pub account: &'a AccountView,
691 pub mint: &'a AccountView,
692 pub authority: &'a AccountView,
693 pub amount: u64,
694 pub decimals: u8,
695}
696
697impl BurnChecked<'_> {
698 #[inline]
699 pub fn invoke(&self) -> ProgramResult {
700 require_authority_signed_direct(self.authority)?;
701 self.invoke_signed_unchecked(&[])
702 }
703
704 #[inline]
708 pub fn invoke_strict(&self) -> ProgramResult {
709 require_authority_signed_direct(self.authority)?;
710 require_token_authority(self.account, self.authority)?;
711 self.invoke_signed_unchecked(&[])
712 }
713
714 #[inline]
715 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
716 self.invoke_signed_unchecked(signers)
717 }
718
719 #[inline]
723 pub fn invoke_signed_strict(&self, signers: &[Signer]) -> ProgramResult {
724 require_token_authority(self.account, self.authority)?;
725 self.invoke_signed_unchecked(signers)
726 }
727
728 #[inline(always)]
729 fn invoke_signed_unchecked(&self, signers: &[Signer]) -> ProgramResult {
730 let mut data = [0u8; 10];
731 data[0] = 15;
732 data[1..9].copy_from_slice(&self.amount.to_le_bytes());
733 data[9] = self.decimals;
734
735 let accounts = [
736 InstructionAccount::writable(self.account.address()),
737 InstructionAccount::writable(self.mint.address()),
738 InstructionAccount::readonly_signer(self.authority.address()),
739 ];
740 let views = [self.account, self.mint, self.authority];
741 let instruction = InstructionView {
742 program_id: &TOKEN_PROGRAM_ID,
743 data: &data,
744 accounts: &accounts,
745 };
746
747 crate::cpi::invoke_signed(&instruction, &views, signers)
748 }
749}
750
751pub struct ApproveChecked<'a> {
758 pub source: &'a AccountView,
759 pub mint: &'a AccountView,
760 pub delegate: &'a AccountView,
761 pub authority: &'a AccountView,
762 pub amount: u64,
763 pub decimals: u8,
764}
765
766impl ApproveChecked<'_> {
767 #[inline]
768 pub fn invoke(&self) -> ProgramResult {
769 require_authority_signed_direct(self.authority)?;
770 self.invoke_signed_unchecked(&[])
771 }
772
773 #[inline]
778 pub fn invoke_strict(&self) -> ProgramResult {
779 require_authority_signed_direct(self.authority)?;
780 require_token_authority(self.source, self.authority)?;
781 self.invoke_signed_unchecked(&[])
782 }
783
784 #[inline]
785 pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
786 self.invoke_signed_unchecked(signers)
787 }
788
789 #[inline]
792 pub fn invoke_signed_strict(&self, signers: &[Signer]) -> ProgramResult {
793 require_token_authority(self.source, self.authority)?;
794 self.invoke_signed_unchecked(signers)
795 }
796
797 #[inline(always)]
798 fn invoke_signed_unchecked(&self, signers: &[Signer]) -> ProgramResult {
799 let mut data = [0u8; 10];
800 data[0] = 13;
801 data[1..9].copy_from_slice(&self.amount.to_le_bytes());
802 data[9] = self.decimals;
803
804 let accounts = [
805 InstructionAccount::writable(self.source.address()),
806 InstructionAccount::readonly(self.mint.address()),
807 InstructionAccount::readonly(self.delegate.address()),
808 InstructionAccount::readonly_signer(self.authority.address()),
809 ];
810 let views = [self.source, self.mint, self.delegate, self.authority];
811 let instruction = InstructionView {
812 program_id: &TOKEN_PROGRAM_ID,
813 data: &data,
814 accounts: &accounts,
815 };
816
817 crate::cpi::invoke_signed(&instruction, &views, signers)
818 }
819}
820
821pub struct InitializeAccount<'a> {
825 pub account: &'a AccountView,
826 pub mint: &'a AccountView,
827 pub owner: &'a AccountView,
828 pub rent_sysvar: &'a AccountView,
829}
830
831impl InitializeAccount<'_> {
832 #[inline]
833 pub fn invoke(&self) -> ProgramResult {
834 let data = [1u8];
835 let accounts = [
836 InstructionAccount::writable(self.account.address()),
837 InstructionAccount::readonly(self.mint.address()),
838 InstructionAccount::readonly(self.owner.address()),
839 InstructionAccount::readonly(self.rent_sysvar.address()),
840 ];
841 let views = [self.account, self.mint, self.owner, self.rent_sysvar];
842 let instruction = InstructionView {
843 program_id: &TOKEN_PROGRAM_ID,
844 data: &data,
845 accounts: &accounts,
846 };
847
848 crate::cpi::invoke(&instruction, &views)
849 }
850}
851
852pub const TOKEN_PROGRAM_ID: Address = Address::new_from_array(
854 five8_const::decode_32_const("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
855);
856
857pub mod instructions {
859 pub use super::{
860 ApproveChecked, BurnChecked, CloseAccount, InitializeAccount, MintToChecked, Revoke,
861 TransferChecked,
862 };
863
864 #[cfg(feature = "legacy-token-instructions")]
865 #[allow(deprecated)]
866 pub use super::{Approve, Burn, MintTo, Transfer};
867}
868
869#[cfg(test)]
870mod tests {
871 use super::*;
879
880 #[test]
885 fn transfer_checked_discriminator_is_12() {
886 }
906
907 fn encode_checked(disc: u8, amount: u64, decimals: u8) -> [u8; 10] {
911 let mut data = [0u8; 10];
912 data[0] = disc;
913 data[1..9].copy_from_slice(&amount.to_le_bytes());
914 data[9] = decimals;
915 data
916 }
917
918 #[test]
919 fn transfer_checked_wire_format_is_stable() {
920 let out = encode_checked(12, 0x0102_0304_0506_0708, 9);
922 assert_eq!(out[0], 12);
923 assert_eq!(
924 &out[1..9],
925 &[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]
926 );
927 assert_eq!(out[9], 9);
928 }
929
930 #[test]
931 fn mint_to_checked_wire_format_is_stable() {
932 let out = encode_checked(14, 1000, 6);
933 assert_eq!(out[0], 14);
934 assert_eq!(u64::from_le_bytes(out[1..9].try_into().unwrap()), 1000);
935 assert_eq!(out[9], 6);
936 }
937
938 #[test]
939 fn burn_checked_wire_format_is_stable() {
940 let out = encode_checked(15, 42, 8);
941 assert_eq!(out[0], 15);
942 assert_eq!(u64::from_le_bytes(out[1..9].try_into().unwrap()), 42);
943 assert_eq!(out[9], 8);
944 }
945
946 #[test]
947 fn approve_checked_wire_format_is_stable() {
948 let out = encode_checked(13, u64::MAX, 0);
949 assert_eq!(out[0], 13);
950 assert_eq!(u64::from_le_bytes(out[1..9].try_into().unwrap()), u64::MAX);
951 assert_eq!(out[9], 0);
952 }
953
954 #[test]
955 fn checked_encoding_round_trips_decimals_range() {
956 for d in 0u8..=255 {
960 let out = encode_checked(12, 1, d);
961 assert_eq!(out[9], d);
962 }
963 }
964
965 #[test]
966 fn checked_encoding_preserves_amount_bits() {
967 for shift in 0..8 {
970 let amount = 0xABu64 << (shift * 8);
971 let out = encode_checked(12, amount, 0);
972 let decoded = u64::from_le_bytes(out[1..9].try_into().unwrap());
973 assert_eq!(decoded, amount);
974 }
975 }
976
977 fn make_token_and_authority(
985 authority_bytes: [u8; 32],
986 token_owner_bytes: [u8; 32],
987 ) -> (
988 std::vec::Vec<u8>,
989 std::vec::Vec<u8>,
990 crate::account::AccountView,
991 crate::account::AccountView,
992 ) {
993 use hopper_native::{AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED};
994
995 let token_data_len = 165;
1000 let mut token_backing = std::vec![0u8; RuntimeAccount::SIZE + token_data_len];
1001 let token_raw = token_backing.as_mut_ptr() as *mut RuntimeAccount;
1002 unsafe {
1003 token_raw.write(RuntimeAccount {
1004 borrow_state: NOT_BORROWED,
1005 is_signer: 0,
1006 is_writable: 1,
1007 executable: 0,
1008 resize_delta: 0,
1009 address: NativeAddress::new_from_array([0xAA; 32]),
1010 owner: NativeAddress::new_from_array([3; 32]),
1011 lamports: 2_039_280,
1012 data_len: token_data_len as u64,
1013 });
1014 let data_ptr = (token_raw as *mut u8).add(RuntimeAccount::SIZE);
1016 core::ptr::copy_nonoverlapping(
1017 token_owner_bytes.as_ptr(),
1018 data_ptr.add(32),
1019 32,
1020 );
1021 }
1022 let token_backend = unsafe { NativeAccountView::new_unchecked(token_raw) };
1023 let token_view = crate::account::AccountView::from_backend(token_backend);
1024
1025 let mut auth_backing = std::vec![0u8; RuntimeAccount::SIZE];
1027 let auth_raw = auth_backing.as_mut_ptr() as *mut RuntimeAccount;
1028 unsafe {
1029 auth_raw.write(RuntimeAccount {
1030 borrow_state: NOT_BORROWED,
1031 is_signer: 1,
1032 is_writable: 0,
1033 executable: 0,
1034 resize_delta: 0,
1035 address: NativeAddress::new_from_array(authority_bytes),
1036 owner: NativeAddress::new_from_array([0; 32]),
1037 lamports: 0,
1038 data_len: 0,
1039 });
1040 }
1041 let auth_backend = unsafe { NativeAccountView::new_unchecked(auth_raw) };
1042 let auth_view = crate::account::AccountView::from_backend(auth_backend);
1043
1044 (token_backing, auth_backing, token_view, auth_view)
1045 }
1046
1047 #[test]
1048 fn require_token_authority_accepts_matching_owner() {
1049 let authority = [0x42u8; 32];
1050 let (_tb, _ab, token, auth) = make_token_and_authority(authority, authority);
1051 require_token_authority(&token, &auth).unwrap();
1052 }
1053
1054 #[test]
1055 fn require_token_authority_rejects_mismatched_owner() {
1056 let authority = [0x42u8; 32];
1057 let wrong_owner = [0x77u8; 32];
1058 let (_tb, _ab, token, auth) = make_token_and_authority(authority, wrong_owner);
1059 let err = require_token_authority(&token, &auth).unwrap_err();
1060 assert!(matches!(err, ProgramError::IncorrectAuthority));
1061 }
1062
1063 #[test]
1064 fn require_token_authority_rejects_short_buffer() {
1065 use hopper_native::{AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED};
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 {
1074 raw.write(RuntimeAccount {
1075 borrow_state: NOT_BORROWED,
1076 is_signer: 0,
1077 is_writable: 1,
1078 executable: 0,
1079 resize_delta: 0,
1080 address: NativeAddress::new_from_array([0xAA; 32]),
1081 owner: NativeAddress::new_from_array([3; 32]),
1082 lamports: 0,
1083 data_len: data_len as u64,
1084 });
1085 }
1086 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
1087 let token = crate::account::AccountView::from_backend(backend);
1088
1089 let (_ab, _, _, auth) = make_token_and_authority([0x11; 32], [0x11; 32]);
1090 let err = require_token_authority(&token, &auth).unwrap_err();
1091 assert!(matches!(err, ProgramError::AccountDataTooSmall));
1092 }
1093
1094 fn make_token_with_mint_and_owner(
1106 mint_bytes: [u8; 32],
1107 owner_bytes: [u8; 32],
1108 ) -> (std::vec::Vec<u8>, crate::account::AccountView) {
1109 use hopper_native::{AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED};
1110
1111 let token_data_len = 165;
1112 let mut backing = std::vec![0u8; RuntimeAccount::SIZE + token_data_len];
1113 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
1114 unsafe {
1115 raw.write(RuntimeAccount {
1116 borrow_state: NOT_BORROWED,
1117 is_signer: 0,
1118 is_writable: 1,
1119 executable: 0,
1120 resize_delta: 0,
1121 address: NativeAddress::new_from_array([0xAA; 32]),
1122 owner: NativeAddress::new_from_array([3; 32]),
1123 lamports: 2_039_280,
1124 data_len: token_data_len as u64,
1125 });
1126 let data_ptr = (raw as *mut u8).add(RuntimeAccount::SIZE);
1127 core::ptr::copy_nonoverlapping(mint_bytes.as_ptr(), data_ptr, 32);
1128 core::ptr::copy_nonoverlapping(owner_bytes.as_ptr(), data_ptr.add(32), 32);
1129 }
1130 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
1131 let view = crate::account::AccountView::from_backend(backend);
1132 (backing, view)
1133 }
1134
1135 fn make_mint_with_authority_decimals(
1139 mint_authority: [u8; 32],
1140 decimals: u8,
1141 ) -> (std::vec::Vec<u8>, crate::account::AccountView) {
1142 use hopper_native::{AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED};
1143
1144 let mint_data_len = 82;
1145 let mut backing = std::vec![0u8; RuntimeAccount::SIZE + mint_data_len];
1146 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
1147 unsafe {
1148 raw.write(RuntimeAccount {
1149 borrow_state: NOT_BORROWED,
1150 is_signer: 0,
1151 is_writable: 0,
1152 executable: 0,
1153 resize_delta: 0,
1154 address: NativeAddress::new_from_array([0xBB; 32]),
1155 owner: NativeAddress::new_from_array([3; 32]),
1156 lamports: 1_461_600,
1157 data_len: mint_data_len as u64,
1158 });
1159 let data_ptr = (raw as *mut u8).add(RuntimeAccount::SIZE);
1160 let some_tag: [u8; 4] = 1u32.to_le_bytes();
1162 core::ptr::copy_nonoverlapping(some_tag.as_ptr(), data_ptr, 4);
1163 core::ptr::copy_nonoverlapping(mint_authority.as_ptr(), data_ptr.add(4), 32);
1164 *data_ptr.add(44) = decimals;
1167 *data_ptr.add(45) = 1;
1169 }
1171 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
1172 let view = crate::account::AccountView::from_backend(backend);
1173 (backing, view)
1174 }
1175
1176 #[test]
1177 fn require_token_mint_accepts_matching_mint() {
1178 let mint = [0xABu8; 32];
1179 let (_b, view) = make_token_with_mint_and_owner(mint, [0; 32]);
1180 let expected = crate::address::Address::new_from_array(mint);
1181 require_token_mint(&view, &expected).unwrap();
1182 }
1183
1184 #[test]
1185 fn require_token_mint_rejects_mismatched_mint() {
1186 let mint = [0xABu8; 32];
1187 let (_b, view) = make_token_with_mint_and_owner(mint, [0; 32]);
1188 let wrong = crate::address::Address::new_from_array([0xCDu8; 32]);
1189 let err = require_token_mint(&view, &wrong).unwrap_err();
1190 assert!(matches!(err, ProgramError::InvalidAccountData));
1191 }
1192
1193 #[test]
1194 fn require_token_owner_eq_matches() {
1195 let owner = [0x77u8; 32];
1196 let (_b, view) = make_token_with_mint_and_owner([0; 32], owner);
1197 let expected = crate::address::Address::new_from_array(owner);
1198 require_token_owner_eq(&view, &expected).unwrap();
1199 }
1200
1201 #[test]
1202 fn require_token_owner_eq_rejects_mismatch() {
1203 let owner = [0x77u8; 32];
1204 let (_b, view) = make_token_with_mint_and_owner([0; 32], owner);
1205 let wrong = crate::address::Address::new_from_array([0x88u8; 32]);
1206 let err = require_token_owner_eq(&view, &wrong).unwrap_err();
1207 assert!(matches!(err, ProgramError::IncorrectAuthority));
1208 }
1209
1210 #[test]
1211 fn require_mint_authority_accepts_matching() {
1212 let auth = [0x99u8; 32];
1213 let (_b, view) = make_mint_with_authority_decimals(auth, 6);
1214 let expected = crate::address::Address::new_from_array(auth);
1215 require_mint_authority(&view, &expected).unwrap();
1216 }
1217
1218 #[test]
1219 fn require_mint_authority_rejects_mismatched() {
1220 let auth = [0x99u8; 32];
1221 let (_b, view) = make_mint_with_authority_decimals(auth, 6);
1222 let wrong = crate::address::Address::new_from_array([0x00u8; 32]);
1223 let err = require_mint_authority(&view, &wrong).unwrap_err();
1224 assert!(matches!(err, ProgramError::IncorrectAuthority));
1225 }
1226
1227 #[test]
1228 fn require_mint_decimals_matches() {
1229 let (_b, view) = make_mint_with_authority_decimals([1u8; 32], 9);
1230 require_mint_decimals(&view, 9).unwrap();
1231 }
1232
1233 #[test]
1234 fn require_mint_decimals_rejects_mismatch() {
1235 let (_b, view) = make_mint_with_authority_decimals([1u8; 32], 9);
1236 let err = require_mint_decimals(&view, 6).unwrap_err();
1237 assert!(matches!(err, ProgramError::InvalidAccountData));
1238 }
1239
1240 #[test]
1241 fn require_mint_freeze_authority_rejects_none_tag() {
1242 let (_b, view) = make_mint_with_authority_decimals([1u8; 32], 9);
1248 let expected = crate::address::Address::new_from_array([2u8; 32]);
1249 let err = require_mint_freeze_authority(&view, &expected).unwrap_err();
1250 assert!(matches!(err, ProgramError::InvalidAccountData));
1251 }
1252}