1use crate::account::AccountView;
14use crate::address::Address;
15use crate::borrow::Ref;
16use crate::error::ProgramError;
17use crate::foreign::{ExplainExternal, ExternalAccount, ExternalExplainSink, ExternalZeroCopy};
18use crate::instruction::{InstructionAccount, InstructionView, Signer};
19use crate::ProgramResult;
20use core::mem::MaybeUninit;
21
22pub const MAX_TOKEN_MULTISIG_SIGNERS: usize = 11;
24
25#[inline(always)]
40fn require_authority_signed_direct(authority: &AccountView<'_>) -> ProgramResult {
41 if authority.is_signer() {
42 Ok(())
43 } else {
44 Err(ProgramError::MissingRequiredSignature)
45 }
46}
47
48#[inline(always)]
49fn authority_meta<'a>(
50 authority: &'a AccountView<'a>,
51 multisig_signers: &[&'a AccountView<'a>],
52) -> InstructionAccount<'a> {
53 if multisig_signers.is_empty() {
54 InstructionAccount::readonly_signer(authority.address())
55 } else {
56 InstructionAccount::readonly(authority.address())
57 }
58}
59
60#[inline]
61fn require_multisig_signers_direct(multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
62 if multisig_signers.len() > MAX_TOKEN_MULTISIG_SIGNERS {
63 return Err(ProgramError::InvalidArgument);
64 }
65 for signer in multisig_signers {
66 require_authority_signed_direct(signer)?;
67 }
68 Ok(())
69}
70
71#[doc(hidden)]
96pub mod encoders {
97 #[inline(always)]
101 fn amount_ix(disc: u8, amount: u64) -> [u8; 9] {
102 let mut data = [0u8; 9];
103 data[0] = disc;
104 data[1..9].copy_from_slice(&amount.to_le_bytes());
105 data
106 }
107
108 #[inline(always)]
112 fn amount_checked_ix(disc: u8, amount: u64, decimals: u8) -> [u8; 10] {
113 let mut data = [0u8; 10];
114 data[0] = disc;
115 data[1..9].copy_from_slice(&amount.to_le_bytes());
116 data[9] = decimals;
117 data
118 }
119
120 #[inline(always)]
122 pub fn encode_transfer(amount: u64) -> [u8; 9] {
123 amount_ix(3, amount)
124 }
125
126 #[inline(always)]
128 pub fn encode_approve(amount: u64) -> [u8; 9] {
129 amount_ix(4, amount)
130 }
131
132 #[inline(always)]
134 pub fn encode_mint_to(amount: u64) -> [u8; 9] {
135 amount_ix(7, amount)
136 }
137
138 #[inline(always)]
140 pub fn encode_burn(amount: u64) -> [u8; 9] {
141 amount_ix(8, amount)
142 }
143
144 #[inline(always)]
147 pub fn encode_transfer_checked(amount: u64, decimals: u8) -> [u8; 10] {
148 amount_checked_ix(12, amount, decimals)
149 }
150
151 #[inline(always)]
154 pub fn encode_approve_checked(amount: u64, decimals: u8) -> [u8; 10] {
155 amount_checked_ix(13, amount, decimals)
156 }
157
158 #[inline(always)]
161 pub fn encode_mint_to_checked(amount: u64, decimals: u8) -> [u8; 10] {
162 amount_checked_ix(14, amount, decimals)
163 }
164
165 #[inline(always)]
168 pub fn encode_burn_checked(amount: u64, decimals: u8) -> [u8; 10] {
169 amount_checked_ix(15, amount, decimals)
170 }
171
172 #[inline(always)]
174 pub fn encode_revoke() -> [u8; 1] {
175 [5]
176 }
177
178 #[inline(always)]
180 pub fn encode_close_account() -> [u8; 1] {
181 [9]
182 }
183
184 #[inline(always)]
186 pub fn encode_freeze_account() -> [u8; 1] {
187 [10]
188 }
189
190 #[inline(always)]
192 pub fn encode_thaw_account() -> [u8; 1] {
193 [11]
194 }
195
196 #[inline(always)]
198 pub fn encode_sync_native() -> [u8; 1] {
199 [17]
200 }
201
202 #[inline(always)]
205 pub fn encode_initialize_account() -> [u8; 1] {
206 [1]
207 }
208
209 #[inline(always)]
213 pub fn encode_initialize_account_with_owner(discriminator: u8, owner: &[u8; 32]) -> [u8; 33] {
214 let mut data = [0u8; 33];
215 data[0] = discriminator;
216 data[1..33].copy_from_slice(owner);
217 data
218 }
219
220 #[inline(always)]
227 pub fn encode_set_authority(
228 authority_type: u8,
229 new_authority: Option<&[u8; 32]>,
230 ) -> ([u8; 35], usize) {
231 let mut data = [0u8; 35];
232 data[0] = 6;
233 data[1] = authority_type;
234 match new_authority {
235 Some(key) => {
236 data[2] = 1;
237 data[3..35].copy_from_slice(key);
238 (data, 35)
239 }
240 None => {
241 data[2] = 0;
242 (data, 3)
243 }
244 }
245 }
246}
247
248#[inline]
249fn invoke_token_signed<'a, const FIXED: usize>(
250 data: &[u8],
251 fixed_accounts: [InstructionAccount<'a>; FIXED],
252 fixed_views: [&'a AccountView<'a>; FIXED],
253 multisig_signers: &[&'a AccountView<'a>],
254 signer_seeds: &[Signer<'_, '_>],
255) -> ProgramResult {
256 let total = FIXED
257 .checked_add(multisig_signers.len())
258 .ok_or(ProgramError::ArithmeticOverflow)?;
259 if multisig_signers.len() > MAX_TOKEN_MULTISIG_SIGNERS
260 || total > crate::cpi::MAX_STATIC_CPI_ACCOUNTS
261 {
262 return Err(ProgramError::InvalidArgument);
263 }
264
265 let mut accounts: [MaybeUninit<InstructionAccount<'a>>; crate::cpi::MAX_STATIC_CPI_ACCOUNTS] =
266 [MaybeUninit::uninit(); crate::cpi::MAX_STATIC_CPI_ACCOUNTS];
267 let mut views: [MaybeUninit<&'a AccountView<'a>>; crate::cpi::MAX_STATIC_CPI_ACCOUNTS] =
268 [MaybeUninit::uninit(); crate::cpi::MAX_STATIC_CPI_ACCOUNTS];
269
270 let mut index = 0;
271 while index < FIXED {
272 accounts[index].write(fixed_accounts[index]);
273 views[index].write(fixed_views[index]);
274 index += 1;
275 }
276 for signer in multisig_signers {
277 accounts[index].write(InstructionAccount::readonly_signer(signer.address()));
278 views[index].write(*signer);
279 index += 1;
280 }
281
282 let accounts = unsafe {
285 core::slice::from_raw_parts(accounts.as_ptr() as *const InstructionAccount<'a>, total)
286 };
287 let views =
289 unsafe { core::slice::from_raw_parts(views.as_ptr() as *const &'a AccountView<'a>, total) };
290
291 let instruction = InstructionView {
292 program_id: &TOKEN_PROGRAM_ID,
293 data,
294 accounts,
295 };
296 crate::cpi::invoke_signed_with_bounds::<{ crate::cpi::MAX_STATIC_CPI_ACCOUNTS }>(
297 &instruction,
298 views,
299 signer_seeds,
300 )
301}
302
303#[inline]
323pub fn require_token_authority(
324 token_account: &AccountView<'_>,
325 authority: &AccountView<'_>,
326) -> ProgramResult {
327 let data = token_account
331 .try_borrow()
332 .map_err(|_| ProgramError::AccountBorrowFailed)?;
333 if data.len() < 64 {
334 return Err(ProgramError::AccountDataTooSmall);
335 }
336 if crate::address::keys_eq_bytes(&data[32..64], authority.address().as_array()) {
338 Ok(())
339 } else {
340 Err(ProgramError::IncorrectAuthority)
341 }
342}
343
344#[inline]
356pub fn require_token_owner_eq(
357 token_account: &AccountView<'_>,
358 expected_owner: &Address,
359) -> ProgramResult {
360 let data = token_account
361 .try_borrow()
362 .map_err(|_| ProgramError::AccountBorrowFailed)?;
363 if data.len() < 64 {
364 return Err(ProgramError::AccountDataTooSmall);
365 }
366 if crate::address::keys_eq_bytes(&data[32..64], expected_owner.as_array()) {
368 Ok(())
369 } else {
370 Err(ProgramError::IncorrectAuthority)
371 }
372}
373
374#[inline]
393pub fn require_token_mint(
394 token_account: &AccountView<'_>,
395 expected_mint: &Address,
396) -> ProgramResult {
397 let data = token_account
398 .try_borrow()
399 .map_err(|_| ProgramError::AccountBorrowFailed)?;
400 if data.len() < 32 {
401 return Err(ProgramError::AccountDataTooSmall);
402 }
403 if crate::address::keys_eq_bytes(&data[0..32], expected_mint.as_array()) {
405 Ok(())
406 } else {
407 Err(ProgramError::InvalidAccountData)
408 }
409}
410
411#[inline]
430pub fn require_mint_authority(
431 mint_account: &AccountView<'_>,
432 expected_authority: &Address,
433) -> ProgramResult {
434 let data = mint_account
435 .try_borrow()
436 .map_err(|_| ProgramError::AccountBorrowFailed)?;
437 if data.len() < 46 {
438 return Err(ProgramError::AccountDataTooSmall);
439 }
440 let tag = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
441 if tag != 1 {
442 return Err(ProgramError::InvalidAccountData);
444 }
445 if crate::address::keys_eq_bytes(&data[4..36], expected_authority.as_array()) {
447 Ok(())
448 } else {
449 Err(ProgramError::IncorrectAuthority)
450 }
451}
452
453#[inline]
459pub fn require_mint_decimals(mint_account: &AccountView<'_>, expected: u8) -> ProgramResult {
460 let data = mint_account
461 .try_borrow()
462 .map_err(|_| ProgramError::AccountBorrowFailed)?;
463 if data.len() < 45 {
464 return Err(ProgramError::AccountDataTooSmall);
465 }
466 if data[44] == expected {
467 Ok(())
468 } else {
469 Err(ProgramError::InvalidAccountData)
470 }
471}
472
473#[inline]
481pub fn require_mint_freeze_authority(
482 mint_account: &AccountView<'_>,
483 expected_freeze: &Address,
484) -> ProgramResult {
485 let data = mint_account
486 .try_borrow()
487 .map_err(|_| ProgramError::AccountBorrowFailed)?;
488 if data.len() < 82 {
489 return Err(ProgramError::AccountDataTooSmall);
490 }
491 let tag = u32::from_le_bytes([data[46], data[47], data[48], data[49]]);
492 if tag != 1 {
493 return Err(ProgramError::InvalidAccountData);
494 }
495 if crate::address::keys_eq_bytes(&data[50..82], expected_freeze.as_array()) {
497 Ok(())
498 } else {
499 Err(ProgramError::IncorrectAuthority)
500 }
501}
502
503#[deprecated(
520 since = "0.2.0",
521 note = "use TransferChecked for Token-2022 safety (mint + decimals validation)"
522)]
523#[cfg(feature = "legacy-token-instructions")]
524pub struct Transfer<'a> {
525 pub from: &'a AccountView<'a>,
526 pub to: &'a AccountView<'a>,
527 pub authority: &'a AccountView<'a>,
528 pub amount: u64,
529}
530
531#[allow(deprecated)]
532#[cfg(feature = "legacy-token-instructions")]
533impl Transfer<'_> {
534 #[inline]
538 pub fn invoke(&self) -> ProgramResult {
539 require_authority_signed_direct(self.authority)?;
540 self.invoke_signed_unchecked(&[])
541 }
542
543 #[inline]
546 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
547 self.invoke_signed_unchecked(signers)
548 }
549
550 #[inline(always)]
551 fn invoke_signed_unchecked(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
552 let data = encoders::encode_transfer(self.amount);
553
554 let accounts = [
555 InstructionAccount::writable(self.from.address()),
556 InstructionAccount::writable(self.to.address()),
557 InstructionAccount::readonly_signer(self.authority.address()),
558 ];
559 let views = [self.from, self.to, self.authority];
560 let instruction = InstructionView {
561 program_id: &TOKEN_PROGRAM_ID,
562 data: &data,
563 accounts: &accounts,
564 };
565
566 crate::cpi::invoke_signed(&instruction, &views, signers)
567 }
568}
569
570#[deprecated(
576 since = "0.2.0",
577 note = "use MintToChecked for Token-2022 safety (mint + decimals validation)"
578)]
579#[cfg(feature = "legacy-token-instructions")]
580pub struct MintTo<'a> {
581 pub mint: &'a AccountView<'a>,
582 pub account: &'a AccountView<'a>,
583 pub mint_authority: &'a AccountView<'a>,
584 pub amount: u64,
585}
586
587#[allow(deprecated)]
588#[cfg(feature = "legacy-token-instructions")]
589impl MintTo<'_> {
590 #[inline]
591 pub fn invoke(&self) -> ProgramResult {
592 require_authority_signed_direct(self.mint_authority)?;
593 self.invoke_signed(&[])
594 }
595
596 #[inline]
597 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
598 let data = encoders::encode_mint_to(self.amount);
599
600 let accounts = [
601 InstructionAccount::writable(self.mint.address()),
602 InstructionAccount::writable(self.account.address()),
603 InstructionAccount::readonly_signer(self.mint_authority.address()),
604 ];
605 let views = [self.mint, self.account, self.mint_authority];
606 let instruction = InstructionView {
607 program_id: &TOKEN_PROGRAM_ID,
608 data: &data,
609 accounts: &accounts,
610 };
611
612 crate::cpi::invoke_signed(&instruction, &views, signers)
613 }
614}
615
616#[deprecated(
622 since = "0.2.0",
623 note = "use BurnChecked for Token-2022 safety (mint + decimals validation)"
624)]
625#[cfg(feature = "legacy-token-instructions")]
626pub struct Burn<'a> {
627 pub account: &'a AccountView<'a>,
628 pub mint: &'a AccountView<'a>,
629 pub authority: &'a AccountView<'a>,
630 pub amount: u64,
631}
632
633#[allow(deprecated)]
634#[cfg(feature = "legacy-token-instructions")]
635impl Burn<'_> {
636 #[inline]
637 pub fn invoke(&self) -> ProgramResult {
638 require_authority_signed_direct(self.authority)?;
639 self.invoke_signed(&[])
640 }
641
642 #[inline]
643 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
644 let data = encoders::encode_burn(self.amount);
645
646 let accounts = [
647 InstructionAccount::writable(self.account.address()),
648 InstructionAccount::writable(self.mint.address()),
649 InstructionAccount::readonly_signer(self.authority.address()),
650 ];
651 let views = [self.account, self.mint, self.authority];
652 let instruction = InstructionView {
653 program_id: &TOKEN_PROGRAM_ID,
654 data: &data,
655 accounts: &accounts,
656 };
657
658 crate::cpi::invoke_signed(&instruction, &views, signers)
659 }
660}
661
662pub struct CloseAccount<'a> {
666 pub account: &'a AccountView<'a>,
667 pub destination: &'a AccountView<'a>,
668 pub authority: &'a AccountView<'a>,
669}
670
671impl CloseAccount<'_> {
672 #[inline]
673 pub fn invoke(&self) -> ProgramResult {
674 require_authority_signed_direct(self.authority)?;
675 self.invoke_signed(&[])
676 }
677
678 #[inline]
679 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
680 self.invoke_signed_unchecked_with_multisig(&[], signers)
681 }
682
683 #[inline]
684 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
685 require_multisig_signers_direct(multisig_signers)?;
686 self.invoke_signed_multisig(multisig_signers, &[])
687 }
688
689 #[inline]
690 pub fn invoke_signed_multisig(
691 &self,
692 multisig_signers: &[&AccountView<'_>],
693 signers: &[Signer<'_, '_>],
694 ) -> ProgramResult {
695 self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
696 }
697
698 #[inline(always)]
699 fn invoke_signed_unchecked_with_multisig(
700 &self,
701 multisig_signers: &[&AccountView<'_>],
702 signers: &[Signer<'_, '_>],
703 ) -> ProgramResult {
704 let data = encoders::encode_close_account();
705 let accounts = [
706 InstructionAccount::writable(self.account.address()),
707 InstructionAccount::writable(self.destination.address()),
708 authority_meta(self.authority, multisig_signers),
709 ];
710 let views = [self.account, self.destination, self.authority];
711 invoke_token_signed(&data, accounts, views, multisig_signers, signers)
712 }
713}
714
715#[deprecated(
721 since = "0.2.0",
722 note = "use ApproveChecked for Token-2022 safety (mint + decimals validation)"
723)]
724#[cfg(feature = "legacy-token-instructions")]
725pub struct Approve<'a> {
726 pub source: &'a AccountView<'a>,
727 pub delegate: &'a AccountView<'a>,
728 pub authority: &'a AccountView<'a>,
729 pub amount: u64,
730}
731
732#[allow(deprecated)]
733#[cfg(feature = "legacy-token-instructions")]
734impl Approve<'_> {
735 #[inline]
736 pub fn invoke(&self) -> ProgramResult {
737 require_authority_signed_direct(self.authority)?;
738 self.invoke_signed(&[])
739 }
740
741 #[inline]
742 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
743 let data = encoders::encode_approve(self.amount);
744
745 let accounts = [
746 InstructionAccount::writable(self.source.address()),
747 InstructionAccount::readonly(self.delegate.address()),
748 InstructionAccount::readonly_signer(self.authority.address()),
749 ];
750 let views = [self.source, self.delegate, self.authority];
751 let instruction = InstructionView {
752 program_id: &TOKEN_PROGRAM_ID,
753 data: &data,
754 accounts: &accounts,
755 };
756
757 crate::cpi::invoke_signed(&instruction, &views, signers)
758 }
759}
760
761pub struct Revoke<'a> {
765 pub source: &'a AccountView<'a>,
766 pub authority: &'a AccountView<'a>,
767}
768
769impl Revoke<'_> {
770 #[inline]
771 pub fn invoke(&self) -> ProgramResult {
772 require_authority_signed_direct(self.authority)?;
773 self.invoke_signed(&[])
774 }
775
776 #[inline]
777 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
778 self.invoke_signed_unchecked_with_multisig(&[], signers)
779 }
780
781 #[inline]
782 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
783 require_multisig_signers_direct(multisig_signers)?;
784 self.invoke_signed_multisig(multisig_signers, &[])
785 }
786
787 #[inline]
788 pub fn invoke_signed_multisig(
789 &self,
790 multisig_signers: &[&AccountView<'_>],
791 signers: &[Signer<'_, '_>],
792 ) -> ProgramResult {
793 self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
794 }
795
796 #[inline(always)]
797 fn invoke_signed_unchecked_with_multisig(
798 &self,
799 multisig_signers: &[&AccountView<'_>],
800 signers: &[Signer<'_, '_>],
801 ) -> ProgramResult {
802 let data = encoders::encode_revoke();
803 let accounts = [
804 InstructionAccount::writable(self.source.address()),
805 authority_meta(self.authority, multisig_signers),
806 ];
807 let views = [self.source, self.authority];
808 invoke_token_signed(&data, accounts, views, multisig_signers, signers)
809 }
810}
811
812pub struct TransferChecked<'a> {
829 pub from: &'a AccountView<'a>,
830 pub mint: &'a AccountView<'a>,
831 pub to: &'a AccountView<'a>,
832 pub authority: &'a AccountView<'a>,
833 pub amount: u64,
834 pub decimals: u8,
835}
836
837impl TransferChecked<'_> {
838 #[inline]
842 pub fn invoke(&self) -> ProgramResult {
843 require_authority_signed_direct(self.authority)?;
844 self.invoke_signed_unchecked(&[])
845 }
846
847 #[inline]
859 pub fn invoke_strict(&self) -> ProgramResult {
860 require_authority_signed_direct(self.authority)?;
861 require_token_authority(self.from, self.authority)?;
862 self.invoke_signed_unchecked(&[])
863 }
864
865 #[inline]
868 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
869 self.invoke_signed_unchecked(signers)
870 }
871
872 #[inline]
875 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
876 require_multisig_signers_direct(multisig_signers)?;
877 self.invoke_signed_multisig(multisig_signers, &[])
878 }
879
880 #[inline]
882 pub fn invoke_signed_multisig(
883 &self,
884 multisig_signers: &[&AccountView<'_>],
885 signers: &[Signer<'_, '_>],
886 ) -> ProgramResult {
887 self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
888 }
889
890 #[inline]
894 pub fn invoke_signed_strict(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
895 require_token_authority(self.from, self.authority)?;
896 self.invoke_signed_unchecked(signers)
897 }
898
899 #[inline(always)]
900 fn invoke_signed_unchecked(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
901 self.invoke_signed_unchecked_with_multisig(&[], signers)
902 }
903
904 #[inline(always)]
905 fn invoke_signed_unchecked_with_multisig(
906 &self,
907 multisig_signers: &[&AccountView<'_>],
908 signers: &[Signer<'_, '_>],
909 ) -> ProgramResult {
910 let data = encoders::encode_transfer_checked(self.amount, self.decimals);
911
912 let accounts = [
913 InstructionAccount::writable(self.from.address()),
914 InstructionAccount::readonly(self.mint.address()),
915 InstructionAccount::writable(self.to.address()),
916 authority_meta(self.authority, multisig_signers),
917 ];
918 let views = [self.from, self.mint, self.to, self.authority];
919 invoke_token_signed(&data, accounts, views, multisig_signers, signers)
920 }
921}
922
923pub struct MintToChecked<'a> {
930 pub mint: &'a AccountView<'a>,
931 pub account: &'a AccountView<'a>,
932 pub mint_authority: &'a AccountView<'a>,
933 pub amount: u64,
934 pub decimals: u8,
935}
936
937impl MintToChecked<'_> {
938 #[inline]
939 pub fn invoke(&self) -> ProgramResult {
940 require_authority_signed_direct(self.mint_authority)?;
941 self.invoke_signed_unchecked(&[])
942 }
943
944 #[inline]
945 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
946 self.invoke_signed_unchecked(signers)
947 }
948
949 #[inline]
950 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
951 require_multisig_signers_direct(multisig_signers)?;
952 self.invoke_signed_multisig(multisig_signers, &[])
953 }
954
955 #[inline]
956 pub fn invoke_signed_multisig(
957 &self,
958 multisig_signers: &[&AccountView<'_>],
959 signers: &[Signer<'_, '_>],
960 ) -> ProgramResult {
961 self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
962 }
963
964 #[inline(always)]
965 fn invoke_signed_unchecked(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
966 self.invoke_signed_unchecked_with_multisig(&[], signers)
967 }
968
969 #[inline(always)]
970 fn invoke_signed_unchecked_with_multisig(
971 &self,
972 multisig_signers: &[&AccountView<'_>],
973 signers: &[Signer<'_, '_>],
974 ) -> ProgramResult {
975 let data = encoders::encode_mint_to_checked(self.amount, self.decimals);
976
977 let accounts = [
978 InstructionAccount::writable(self.mint.address()),
979 InstructionAccount::writable(self.account.address()),
980 authority_meta(self.mint_authority, multisig_signers),
981 ];
982 let views = [self.mint, self.account, self.mint_authority];
983 invoke_token_signed(&data, accounts, views, multisig_signers, signers)
984 }
985}
986
987pub struct BurnChecked<'a> {
995 pub account: &'a AccountView<'a>,
996 pub mint: &'a AccountView<'a>,
997 pub authority: &'a AccountView<'a>,
998 pub amount: u64,
999 pub decimals: u8,
1000}
1001
1002impl BurnChecked<'_> {
1003 #[inline]
1004 pub fn invoke(&self) -> ProgramResult {
1005 require_authority_signed_direct(self.authority)?;
1006 self.invoke_signed_unchecked(&[])
1007 }
1008
1009 #[inline]
1013 pub fn invoke_strict(&self) -> ProgramResult {
1014 require_authority_signed_direct(self.authority)?;
1015 require_token_authority(self.account, self.authority)?;
1016 self.invoke_signed_unchecked(&[])
1017 }
1018
1019 #[inline]
1020 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1021 self.invoke_signed_unchecked(signers)
1022 }
1023
1024 #[inline]
1025 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
1026 require_multisig_signers_direct(multisig_signers)?;
1027 self.invoke_signed_multisig(multisig_signers, &[])
1028 }
1029
1030 #[inline]
1031 pub fn invoke_signed_multisig(
1032 &self,
1033 multisig_signers: &[&AccountView<'_>],
1034 signers: &[Signer<'_, '_>],
1035 ) -> ProgramResult {
1036 self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
1037 }
1038
1039 #[inline]
1043 pub fn invoke_signed_strict(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1044 require_token_authority(self.account, self.authority)?;
1045 self.invoke_signed_unchecked(signers)
1046 }
1047
1048 #[inline(always)]
1049 fn invoke_signed_unchecked(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1050 self.invoke_signed_unchecked_with_multisig(&[], signers)
1051 }
1052
1053 #[inline(always)]
1054 fn invoke_signed_unchecked_with_multisig(
1055 &self,
1056 multisig_signers: &[&AccountView<'_>],
1057 signers: &[Signer<'_, '_>],
1058 ) -> ProgramResult {
1059 let data = encoders::encode_burn_checked(self.amount, self.decimals);
1060
1061 let accounts = [
1062 InstructionAccount::writable(self.account.address()),
1063 InstructionAccount::writable(self.mint.address()),
1064 authority_meta(self.authority, multisig_signers),
1065 ];
1066 let views = [self.account, self.mint, self.authority];
1067 invoke_token_signed(&data, accounts, views, multisig_signers, signers)
1068 }
1069}
1070
1071pub struct ApproveChecked<'a> {
1078 pub source: &'a AccountView<'a>,
1079 pub mint: &'a AccountView<'a>,
1080 pub delegate: &'a AccountView<'a>,
1081 pub authority: &'a AccountView<'a>,
1082 pub amount: u64,
1083 pub decimals: u8,
1084}
1085
1086impl ApproveChecked<'_> {
1087 #[inline]
1088 pub fn invoke(&self) -> ProgramResult {
1089 require_authority_signed_direct(self.authority)?;
1090 self.invoke_signed_unchecked(&[])
1091 }
1092
1093 #[inline]
1098 pub fn invoke_strict(&self) -> ProgramResult {
1099 require_authority_signed_direct(self.authority)?;
1100 require_token_authority(self.source, self.authority)?;
1101 self.invoke_signed_unchecked(&[])
1102 }
1103
1104 #[inline]
1105 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1106 self.invoke_signed_unchecked(signers)
1107 }
1108
1109 #[inline]
1110 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
1111 require_multisig_signers_direct(multisig_signers)?;
1112 self.invoke_signed_multisig(multisig_signers, &[])
1113 }
1114
1115 #[inline]
1116 pub fn invoke_signed_multisig(
1117 &self,
1118 multisig_signers: &[&AccountView<'_>],
1119 signers: &[Signer<'_, '_>],
1120 ) -> ProgramResult {
1121 self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
1122 }
1123
1124 #[inline]
1127 pub fn invoke_signed_strict(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1128 require_token_authority(self.source, self.authority)?;
1129 self.invoke_signed_unchecked(signers)
1130 }
1131
1132 #[inline(always)]
1133 fn invoke_signed_unchecked(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1134 self.invoke_signed_unchecked_with_multisig(&[], signers)
1135 }
1136
1137 #[inline(always)]
1138 fn invoke_signed_unchecked_with_multisig(
1139 &self,
1140 multisig_signers: &[&AccountView<'_>],
1141 signers: &[Signer<'_, '_>],
1142 ) -> ProgramResult {
1143 let data = encoders::encode_approve_checked(self.amount, self.decimals);
1144
1145 let accounts = [
1146 InstructionAccount::writable(self.source.address()),
1147 InstructionAccount::readonly(self.mint.address()),
1148 InstructionAccount::readonly(self.delegate.address()),
1149 authority_meta(self.authority, multisig_signers),
1150 ];
1151 let views = [self.source, self.mint, self.delegate, self.authority];
1152 invoke_token_signed(&data, accounts, views, multisig_signers, signers)
1153 }
1154}
1155
1156#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1160#[repr(u8)]
1161pub enum TokenAuthorityType {
1162 MintTokens = 0,
1163 FreezeAccount = 1,
1164 AccountOwner = 2,
1165 CloseAccount = 3,
1166}
1167
1168pub struct SetAuthority<'a> {
1170 pub account: &'a AccountView<'a>,
1171 pub current_authority: &'a AccountView<'a>,
1172 pub authority_type: TokenAuthorityType,
1173 pub new_authority: Option<&'a Address>,
1174}
1175
1176impl SetAuthority<'_> {
1177 #[inline]
1178 pub fn invoke(&self) -> ProgramResult {
1179 require_authority_signed_direct(self.current_authority)?;
1180 self.invoke_signed_unchecked_with_multisig(&[], &[])
1181 }
1182
1183 #[inline]
1184 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1185 self.invoke_signed_unchecked_with_multisig(&[], signers)
1186 }
1187
1188 #[inline]
1189 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
1190 require_multisig_signers_direct(multisig_signers)?;
1191 self.invoke_signed_multisig(multisig_signers, &[])
1192 }
1193
1194 #[inline]
1195 pub fn invoke_signed_multisig(
1196 &self,
1197 multisig_signers: &[&AccountView<'_>],
1198 signers: &[Signer<'_, '_>],
1199 ) -> ProgramResult {
1200 self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
1201 }
1202
1203 #[inline(always)]
1204 fn invoke_signed_unchecked_with_multisig(
1205 &self,
1206 multisig_signers: &[&AccountView<'_>],
1207 signers: &[Signer<'_, '_>],
1208 ) -> ProgramResult {
1209 let (data, len) = encoders::encode_set_authority(
1210 self.authority_type as u8,
1211 self.new_authority.map(|a| a.as_array()),
1212 );
1213 let accounts = [
1214 InstructionAccount::writable(self.account.address()),
1215 authority_meta(self.current_authority, multisig_signers),
1216 ];
1217 let views = [self.account, self.current_authority];
1218 invoke_token_signed(&data[..len], accounts, views, multisig_signers, signers)
1219 }
1220}
1221
1222pub struct FreezeAccount<'a> {
1226 pub account: &'a AccountView<'a>,
1227 pub mint: &'a AccountView<'a>,
1228 pub freeze_authority: &'a AccountView<'a>,
1229}
1230
1231impl FreezeAccount<'_> {
1232 #[inline]
1233 pub fn invoke(&self) -> ProgramResult {
1234 require_authority_signed_direct(self.freeze_authority)?;
1235 self.invoke_signed_unchecked_with_multisig(&[], &[])
1236 }
1237
1238 #[inline]
1239 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1240 self.invoke_signed_unchecked_with_multisig(&[], signers)
1241 }
1242
1243 #[inline]
1244 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
1245 require_multisig_signers_direct(multisig_signers)?;
1246 self.invoke_signed_multisig(multisig_signers, &[])
1247 }
1248
1249 #[inline]
1250 pub fn invoke_signed_multisig(
1251 &self,
1252 multisig_signers: &[&AccountView<'_>],
1253 signers: &[Signer<'_, '_>],
1254 ) -> ProgramResult {
1255 self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
1256 }
1257
1258 #[inline(always)]
1259 fn invoke_signed_unchecked_with_multisig(
1260 &self,
1261 multisig_signers: &[&AccountView<'_>],
1262 signers: &[Signer<'_, '_>],
1263 ) -> ProgramResult {
1264 let data = encoders::encode_freeze_account();
1265 let accounts = [
1266 InstructionAccount::writable(self.account.address()),
1267 InstructionAccount::readonly(self.mint.address()),
1268 authority_meta(self.freeze_authority, multisig_signers),
1269 ];
1270 let views = [self.account, self.mint, self.freeze_authority];
1271 invoke_token_signed(&data, accounts, views, multisig_signers, signers)
1272 }
1273}
1274
1275pub struct ThawAccount<'a> {
1277 pub account: &'a AccountView<'a>,
1278 pub mint: &'a AccountView<'a>,
1279 pub freeze_authority: &'a AccountView<'a>,
1280}
1281
1282impl ThawAccount<'_> {
1283 #[inline]
1284 pub fn invoke(&self) -> ProgramResult {
1285 require_authority_signed_direct(self.freeze_authority)?;
1286 self.invoke_signed_unchecked_with_multisig(&[], &[])
1287 }
1288
1289 #[inline]
1290 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1291 self.invoke_signed_unchecked_with_multisig(&[], signers)
1292 }
1293
1294 #[inline]
1295 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
1296 require_multisig_signers_direct(multisig_signers)?;
1297 self.invoke_signed_multisig(multisig_signers, &[])
1298 }
1299
1300 #[inline]
1301 pub fn invoke_signed_multisig(
1302 &self,
1303 multisig_signers: &[&AccountView<'_>],
1304 signers: &[Signer<'_, '_>],
1305 ) -> ProgramResult {
1306 self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
1307 }
1308
1309 #[inline(always)]
1310 fn invoke_signed_unchecked_with_multisig(
1311 &self,
1312 multisig_signers: &[&AccountView<'_>],
1313 signers: &[Signer<'_, '_>],
1314 ) -> ProgramResult {
1315 let data = encoders::encode_thaw_account();
1316 let accounts = [
1317 InstructionAccount::writable(self.account.address()),
1318 InstructionAccount::readonly(self.mint.address()),
1319 authority_meta(self.freeze_authority, multisig_signers),
1320 ];
1321 let views = [self.account, self.mint, self.freeze_authority];
1322 invoke_token_signed(&data, accounts, views, multisig_signers, signers)
1323 }
1324}
1325
1326pub struct SyncNative<'a> {
1330 pub account: &'a AccountView<'a>,
1331}
1332
1333impl SyncNative<'_> {
1334 #[inline]
1335 pub fn invoke(&self) -> ProgramResult {
1336 let data = encoders::encode_sync_native();
1337 let accounts = [InstructionAccount::writable(self.account.address())];
1338 let views = [self.account];
1339 invoke_token_signed(&data, accounts, views, &[], &[])
1340 }
1341}
1342
1343pub struct InitializeAccount<'a> {
1347 pub account: &'a AccountView<'a>,
1348 pub mint: &'a AccountView<'a>,
1349 pub owner: &'a AccountView<'a>,
1350 pub rent_sysvar: &'a AccountView<'a>,
1351}
1352
1353impl InitializeAccount<'_> {
1354 #[inline]
1355 pub fn invoke(&self) -> ProgramResult {
1356 let data = encoders::encode_initialize_account();
1357 let accounts = [
1358 InstructionAccount::writable(self.account.address()),
1359 InstructionAccount::readonly(self.mint.address()),
1360 InstructionAccount::readonly(self.owner.address()),
1361 InstructionAccount::readonly(self.rent_sysvar.address()),
1362 ];
1363 let views = [self.account, self.mint, self.owner, self.rent_sysvar];
1364 let instruction = InstructionView {
1365 program_id: &TOKEN_PROGRAM_ID,
1366 data: &data,
1367 accounts: &accounts,
1368 };
1369
1370 crate::cpi::invoke(&instruction, &views)
1371 }
1372}
1373
1374pub struct InitializeAccount2<'a> {
1376 pub account: &'a AccountView<'a>,
1377 pub mint: &'a AccountView<'a>,
1378 pub owner: &'a Address,
1379 pub rent_sysvar: &'a AccountView<'a>,
1380}
1381
1382impl InitializeAccount2<'_> {
1383 #[inline]
1384 pub fn invoke(&self) -> ProgramResult {
1385 let data = encoders::encode_initialize_account_with_owner(16, self.owner.as_array());
1386 let accounts = [
1387 InstructionAccount::writable(self.account.address()),
1388 InstructionAccount::readonly(self.mint.address()),
1389 InstructionAccount::readonly(self.rent_sysvar.address()),
1390 ];
1391 let views = [self.account, self.mint, self.rent_sysvar];
1392 invoke_token_signed(&data, accounts, views, &[], &[])
1393 }
1394}
1395
1396pub struct InitializeAccount3<'a> {
1398 pub account: &'a AccountView<'a>,
1399 pub mint: &'a AccountView<'a>,
1400 pub owner: &'a Address,
1401}
1402
1403impl InitializeAccount3<'_> {
1404 #[inline]
1405 pub fn invoke(&self) -> ProgramResult {
1406 let data = encoders::encode_initialize_account_with_owner(18, self.owner.as_array());
1407 let accounts = [
1408 InstructionAccount::writable(self.account.address()),
1409 InstructionAccount::readonly(self.mint.address()),
1410 ];
1411 let views = [self.account, self.mint];
1412 invoke_token_signed(&data, accounts, views, &[], &[])
1413 }
1414}
1415
1416pub const TOKEN_PROGRAM_ID: Address = Address::new_from_array(crate::__decode_base58_32(
1418 "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
1419));
1420
1421pub const SPL_TOKEN_ACCOUNT_LEN: usize = 165;
1424pub const SPL_MINT_LEN: usize = 82;
1425
1426const TOKEN_ACCOUNT_MINT_OFFSET: usize = 0;
1427const TOKEN_ACCOUNT_AUTHORITY_OFFSET: usize = 32;
1428const TOKEN_ACCOUNT_AMOUNT_OFFSET: usize = 64;
1429const TOKEN_ACCOUNT_STATE_OFFSET: usize = 108;
1430
1431const MINT_AUTHORITY_TAG_OFFSET: usize = 0;
1432const MINT_AUTHORITY_OFFSET: usize = 4;
1433const MINT_SUPPLY_OFFSET: usize = 36;
1434const MINT_DECIMALS_OFFSET: usize = 44;
1435const MINT_INITIALIZED_OFFSET: usize = 45;
1436const MINT_FREEZE_AUTHORITY_TAG_OFFSET: usize = 46;
1437const MINT_FREEZE_AUTHORITY_OFFSET: usize = 50;
1438
1439pub struct SplTokenAccount;
1441
1442pub struct SplTokenAccountView<'a> {
1444 data: Ref<'a, [u8]>,
1445}
1446
1447impl SplTokenAccountView<'_> {
1448 #[inline(always)]
1449 pub fn mint(&self) -> Address {
1450 read_address_unchecked(&self.data, TOKEN_ACCOUNT_MINT_OFFSET)
1451 }
1452
1453 #[inline(always)]
1454 pub fn authority(&self) -> Address {
1455 read_address_unchecked(&self.data, TOKEN_ACCOUNT_AUTHORITY_OFFSET)
1456 }
1457
1458 #[inline(always)]
1459 pub fn amount(&self) -> u64 {
1460 read_u64_unchecked(&self.data, TOKEN_ACCOUNT_AMOUNT_OFFSET)
1461 }
1462
1463 #[inline(always)]
1464 pub fn state(&self) -> u8 {
1465 self.data[TOKEN_ACCOUNT_STATE_OFFSET]
1466 }
1467
1468 #[inline(always)]
1469 pub fn is_initialized(&self) -> bool {
1470 self.state() != 0
1471 }
1472}
1473
1474impl ExternalZeroCopy for SplTokenAccount {
1475 type View<'a> = SplTokenAccountView<'a>;
1476
1477 const OWNER: Option<Address> = Some(TOKEN_PROGRAM_ID);
1478 const MIN_LEN: usize = SPL_TOKEN_ACCOUNT_LEN;
1479
1480 #[inline]
1481 fn view<'a>(data: Ref<'a, [u8]>) -> Result<Self::View<'a>, ProgramError> {
1482 Ok(SplTokenAccountView { data })
1483 }
1484}
1485
1486impl ExplainExternal for SplTokenAccount {
1487 fn explain<S: ExternalExplainSink>(account: &AccountView<'_>, sink: &mut S) -> ProgramResult {
1488 let account = ExternalAccount::<SplTokenAccount>::try_new(account)?;
1489 account.with_view(|token| {
1490 sink.field_str("adapter", "SplTokenAccount")?;
1491 sink.field_address("mint", &token.mint())?;
1492 sink.field_address("authority", &token.authority())?;
1493 sink.field_u64("amount", token.amount())?;
1494 sink.field_bool("initialized", token.is_initialized())
1495 })
1496 }
1497}
1498
1499pub struct SplMint;
1501
1502pub struct SplMintView<'a> {
1504 data: Ref<'a, [u8]>,
1505}
1506
1507impl SplMintView<'_> {
1508 #[inline(always)]
1509 pub fn mint_authority(&self) -> Option<Address> {
1510 read_coption_address(&self.data, MINT_AUTHORITY_TAG_OFFSET, MINT_AUTHORITY_OFFSET)
1511 }
1512
1513 #[inline(always)]
1514 pub fn supply(&self) -> u64 {
1515 read_u64_unchecked(&self.data, MINT_SUPPLY_OFFSET)
1516 }
1517
1518 #[inline(always)]
1519 pub fn decimals(&self) -> u8 {
1520 self.data[MINT_DECIMALS_OFFSET]
1521 }
1522
1523 #[inline(always)]
1524 pub fn is_initialized(&self) -> bool {
1525 self.data[MINT_INITIALIZED_OFFSET] != 0
1526 }
1527
1528 #[inline(always)]
1529 pub fn freeze_authority(&self) -> Option<Address> {
1530 read_coption_address(
1531 &self.data,
1532 MINT_FREEZE_AUTHORITY_TAG_OFFSET,
1533 MINT_FREEZE_AUTHORITY_OFFSET,
1534 )
1535 }
1536}
1537
1538impl ExternalZeroCopy for SplMint {
1539 type View<'a> = SplMintView<'a>;
1540
1541 const OWNER: Option<Address> = Some(TOKEN_PROGRAM_ID);
1542 const MIN_LEN: usize = SPL_MINT_LEN;
1543
1544 #[inline]
1545 fn view<'a>(data: Ref<'a, [u8]>) -> Result<Self::View<'a>, ProgramError> {
1546 Ok(SplMintView { data })
1547 }
1548}
1549
1550impl ExplainExternal for SplMint {
1551 fn explain<S: ExternalExplainSink>(account: &AccountView<'_>, sink: &mut S) -> ProgramResult {
1552 let account = ExternalAccount::<SplMint>::try_new(account)?;
1553 account.with_view(|mint| {
1554 sink.field_str("adapter", "SplMint")?;
1555 sink.field_u64("supply", mint.supply())?;
1556 sink.field_u64("decimals", mint.decimals() as u64)?;
1557 sink.field_bool("initialized", mint.is_initialized())
1558 })
1559 }
1560}
1561
1562#[derive(Debug)]
1564pub struct CheckedTokenMint<'info> {
1565 account: ExternalAccount<'info, SplTokenAccount>,
1566 mint: Address,
1567}
1568
1569impl<'info> CheckedTokenMint<'info> {
1570 #[inline(always)]
1571 pub const fn account(&self) -> ExternalAccount<'info, SplTokenAccount> {
1572 self.account
1573 }
1574
1575 #[inline(always)]
1576 pub const fn mint(&self) -> Address {
1577 self.mint
1578 }
1579}
1580
1581#[derive(Debug)]
1583pub struct CheckedTokenAuthority<'info> {
1584 account: ExternalAccount<'info, SplTokenAccount>,
1585 authority: Address,
1586}
1587
1588impl<'info> CheckedTokenAuthority<'info> {
1589 #[inline(always)]
1590 pub const fn account(&self) -> ExternalAccount<'info, SplTokenAccount> {
1591 self.account
1592 }
1593
1594 #[inline(always)]
1595 pub const fn authority(&self) -> Address {
1596 self.authority
1597 }
1598}
1599
1600#[derive(Debug)]
1602pub struct CheckedMintDecimals<'info> {
1603 account: ExternalAccount<'info, SplMint>,
1604 decimals: u8,
1605}
1606
1607impl<'info> CheckedMintDecimals<'info> {
1608 #[inline(always)]
1609 pub const fn account(&self) -> ExternalAccount<'info, SplMint> {
1610 self.account
1611 }
1612
1613 #[inline(always)]
1614 pub const fn decimals(&self) -> u8 {
1615 self.decimals
1616 }
1617}
1618
1619#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1621pub struct TokenAmountSnapshot {
1622 amount: u64,
1623}
1624
1625impl TokenAmountSnapshot {
1626 #[inline(always)]
1627 pub const fn amount(self) -> u64 {
1628 self.amount
1629 }
1630}
1631
1632impl<'info> ExternalAccount<'info, SplTokenAccount> {
1633 #[inline]
1634 pub fn token_amount(&self) -> Result<u64, ProgramError> {
1635 Ok(self.view()?.amount())
1636 }
1637
1638 #[inline]
1639 pub fn checked_mint(
1640 &self,
1641 expected_mint: &Address,
1642 ) -> Result<CheckedTokenMint<'info>, ProgramError> {
1643 let mint = self.view()?.mint();
1644 if &mint == expected_mint {
1645 Ok(CheckedTokenMint {
1646 account: *self,
1647 mint,
1648 })
1649 } else {
1650 Err(ProgramError::InvalidAccountData)
1651 }
1652 }
1653
1654 #[inline]
1655 pub fn checked_authority(
1656 &self,
1657 expected_authority: &Address,
1658 ) -> Result<CheckedTokenAuthority<'info>, ProgramError> {
1659 let authority = self.view()?.authority();
1660 if &authority == expected_authority {
1661 Ok(CheckedTokenAuthority {
1662 account: *self,
1663 authority,
1664 })
1665 } else {
1666 Err(ProgramError::IncorrectAuthority)
1667 }
1668 }
1669
1670 #[inline]
1671 pub fn amount_snapshot(&self) -> Result<TokenAmountSnapshot, ProgramError> {
1672 Ok(TokenAmountSnapshot {
1673 amount: self.token_amount()?,
1674 })
1675 }
1676
1677 #[inline]
1678 pub fn assert_amount_delta(
1679 &self,
1680 before: TokenAmountSnapshot,
1681 expected_delta: i128,
1682 ) -> ProgramResult {
1683 let after = self.token_amount()? as i128;
1684 let expected = (before.amount as i128)
1685 .checked_add(expected_delta)
1686 .ok_or(ProgramError::ArithmeticOverflow)?;
1687 if expected < 0 || expected > u64::MAX as i128 {
1688 return Err(ProgramError::ArithmeticOverflow);
1689 }
1690 if after == expected {
1691 Ok(())
1692 } else {
1693 Err(ProgramError::InvalidAccountData)
1694 }
1695 }
1696
1697 #[inline]
1698 pub fn assert_amount_unchanged(&self, before: TokenAmountSnapshot) -> ProgramResult {
1699 self.assert_amount_delta(before, 0)
1700 }
1701}
1702
1703impl<'info> ExternalAccount<'info, SplMint> {
1704 #[inline]
1705 pub fn checked_decimals(
1706 &self,
1707 expected: u8,
1708 ) -> Result<CheckedMintDecimals<'info>, ProgramError> {
1709 let decimals = self.view()?.decimals();
1710 if decimals == expected {
1711 Ok(CheckedMintDecimals {
1712 account: *self,
1713 decimals,
1714 })
1715 } else {
1716 Err(ProgramError::InvalidAccountData)
1717 }
1718 }
1719}
1720
1721#[inline(always)]
1722fn read_address_unchecked(data: &[u8], offset: usize) -> Address {
1723 let mut bytes = [0u8; 32];
1724 bytes.copy_from_slice(&data[offset..offset + 32]);
1725 Address::new_from_array(bytes)
1726}
1727
1728#[inline(always)]
1729fn read_u64_unchecked(data: &[u8], offset: usize) -> u64 {
1730 u64::from_le_bytes([
1731 data[offset],
1732 data[offset + 1],
1733 data[offset + 2],
1734 data[offset + 3],
1735 data[offset + 4],
1736 data[offset + 5],
1737 data[offset + 6],
1738 data[offset + 7],
1739 ])
1740}
1741
1742#[inline(always)]
1743fn read_u32_unchecked(data: &[u8], offset: usize) -> u32 {
1744 u32::from_le_bytes([
1745 data[offset],
1746 data[offset + 1],
1747 data[offset + 2],
1748 data[offset + 3],
1749 ])
1750}
1751
1752#[inline(always)]
1753fn read_coption_address(data: &[u8], tag_offset: usize, address_offset: usize) -> Option<Address> {
1754 match read_u32_unchecked(data, tag_offset) {
1755 1 => Some(read_address_unchecked(data, address_offset)),
1756 _ => None,
1757 }
1758}
1759
1760pub mod instructions {
1762 pub use super::{
1763 ApproveChecked, BurnChecked, CloseAccount, FreezeAccount, InitializeAccount,
1764 InitializeAccount2, InitializeAccount3, MintToChecked, Revoke, SetAuthority, SyncNative,
1765 ThawAccount, TokenAuthorityType, TransferChecked,
1766 };
1767
1768 #[cfg(feature = "legacy-token-instructions")]
1769 #[allow(deprecated)]
1770 pub use super::{Approve, Burn, MintTo, Transfer};
1771}
1772
1773#[cfg(test)]
1774mod tests {
1775 use super::*;
1783 use hopper_native::{
1784 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED,
1785 };
1786 fn make_account(owner: Address, data: &[u8]) -> (std::vec::Vec<u64>, AccountView<'static>) {
1787 let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + data.len()).div_ceil(8)];
1788 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
1789 unsafe {
1792 raw.write(RuntimeAccount {
1793 borrow_state: NOT_BORROWED,
1794 is_signer: 0,
1795 is_writable: 1,
1796 executable: 0,
1797 resize_delta: 0,
1798 address: NativeAddress::new_from_array([7; 32]),
1799 owner: NativeAddress::new_from_array(owner.to_bytes()),
1800 lamports: 1,
1801 data_len: data.len() as u64,
1802 });
1803 let data_ptr = (backing.as_mut_ptr() as *mut u8).add(RuntimeAccount::SIZE);
1804 core::ptr::copy_nonoverlapping(data.as_ptr(), data_ptr, data.len());
1805 }
1806 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
1808 (backing, AccountView::from_backend(backend))
1809 }
1810 fn token_account_data(
1811 mint: Address,
1812 authority: Address,
1813 amount: u64,
1814 ) -> [u8; SPL_TOKEN_ACCOUNT_LEN] {
1815 let mut data = [0u8; SPL_TOKEN_ACCOUNT_LEN];
1816 data[0..32].copy_from_slice(mint.as_bytes());
1817 data[32..64].copy_from_slice(authority.as_bytes());
1818 data[64..72].copy_from_slice(&amount.to_le_bytes());
1819 data[108] = 1;
1820 data
1821 }
1822 fn mint_data(authority: Address, supply: u64, decimals: u8) -> [u8; SPL_MINT_LEN] {
1823 let mut data = [0u8; SPL_MINT_LEN];
1824 data[0..4].copy_from_slice(&1u32.to_le_bytes());
1825 data[4..36].copy_from_slice(authority.as_bytes());
1826 data[36..44].copy_from_slice(&supply.to_le_bytes());
1827 data[44] = decimals;
1828 data[45] = 1;
1829 data
1830 }
1831
1832 #[test]
1837 fn transfer_checked_discriminator_is_12() {
1838 }
1858 #[test]
1859 fn spl_external_token_account_view_proofs_and_amount_delta() {
1860 let mint = Address::new_from_array([2; 32]);
1861 let authority = Address::new_from_array([3; 32]);
1862 let data = token_account_data(mint, authority, 100);
1863 let (mut backing, account) = make_account(TOKEN_PROGRAM_ID, &data);
1864
1865 let token = ExternalAccount::<SplTokenAccount>::try_new(&account).unwrap();
1866 let view = token.view().unwrap();
1867 assert_eq!(view.mint(), mint);
1868 assert_eq!(view.authority(), authority);
1869 assert_eq!(view.amount(), 100);
1870 assert!(view.is_initialized());
1871 assert_eq!(token.checked_mint(&mint).unwrap().mint(), mint);
1872 assert_eq!(
1873 token.checked_authority(&authority).unwrap().authority(),
1874 authority
1875 );
1876 assert_eq!(
1877 token
1878 .checked_mint(&Address::new_from_array([9; 32]))
1879 .unwrap_err(),
1880 ProgramError::InvalidAccountData
1881 );
1882
1883 let before = token.amount_snapshot().unwrap();
1884 let backing_bytes = unsafe {
1888 core::slice::from_raw_parts_mut(backing.as_mut_ptr() as *mut u8, backing.len() * 8)
1889 };
1890 backing_bytes[RuntimeAccount::SIZE + 64..RuntimeAccount::SIZE + 72]
1891 .copy_from_slice(&150u64.to_le_bytes());
1892 token.assert_amount_delta(before, 50).unwrap();
1893 assert_eq!(
1894 token.assert_amount_delta(before, 49).unwrap_err(),
1895 ProgramError::InvalidAccountData
1896 );
1897 }
1898 #[test]
1899 fn spl_external_mint_view_and_decimals_proof() {
1900 let authority = Address::new_from_array([4; 32]);
1901 let data = mint_data(authority, 1_000_000, 6);
1902 let (_backing, account) = make_account(TOKEN_PROGRAM_ID, &data);
1903
1904 let mint = ExternalAccount::<SplMint>::try_new(&account).unwrap();
1905 let view = mint.view().unwrap();
1906 assert_eq!(view.mint_authority(), Some(authority));
1907 assert_eq!(view.supply(), 1_000_000);
1908 assert_eq!(view.decimals(), 6);
1909 assert!(view.is_initialized());
1910 assert_eq!(mint.checked_decimals(6).unwrap().decimals(), 6);
1911 assert_eq!(
1912 mint.checked_decimals(9).unwrap_err(),
1913 ProgramError::InvalidAccountData
1914 );
1915 }
1916
1917 fn encode_checked(disc: u8, amount: u64, decimals: u8) -> [u8; 10] {
1921 let mut data = [0u8; 10];
1922 data[0] = disc;
1923 data[1..9].copy_from_slice(&amount.to_le_bytes());
1924 data[9] = decimals;
1925 data
1926 }
1927
1928 #[test]
1929 fn transfer_checked_wire_format_is_stable() {
1930 let out = encode_checked(12, 0x0102_0304_0506_0708, 9);
1932 assert_eq!(out[0], 12);
1933 assert_eq!(
1934 &out[1..9],
1935 &[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]
1936 );
1937 assert_eq!(out[9], 9);
1938 }
1939
1940 #[test]
1941 fn mint_to_checked_wire_format_is_stable() {
1942 let out = encode_checked(14, 1000, 6);
1943 assert_eq!(out[0], 14);
1944 assert_eq!(u64::from_le_bytes(out[1..9].try_into().unwrap()), 1000);
1945 assert_eq!(out[9], 6);
1946 }
1947
1948 #[test]
1949 fn burn_checked_wire_format_is_stable() {
1950 let out = encode_checked(15, 42, 8);
1951 assert_eq!(out[0], 15);
1952 assert_eq!(u64::from_le_bytes(out[1..9].try_into().unwrap()), 42);
1953 assert_eq!(out[9], 8);
1954 }
1955
1956 #[test]
1957 fn approve_checked_wire_format_is_stable() {
1958 let out = encode_checked(13, u64::MAX, 0);
1959 assert_eq!(out[0], 13);
1960 assert_eq!(u64::from_le_bytes(out[1..9].try_into().unwrap()), u64::MAX);
1961 assert_eq!(out[9], 0);
1962 }
1963
1964 #[test]
1965 fn checked_encoding_round_trips_decimals_range() {
1966 for d in 0u8..=255 {
1970 let out = encode_checked(12, 1, d);
1971 assert_eq!(out[9], d);
1972 }
1973 }
1974
1975 #[test]
1976 fn checked_encoding_preserves_amount_bits() {
1977 for shift in 0..8 {
1980 let amount = 0xABu64 << (shift * 8);
1981 let out = encode_checked(12, amount, 0);
1982 let decoded = u64::from_le_bytes(out[1..9].try_into().unwrap());
1983 assert_eq!(decoded, amount);
1984 }
1985 }
1986
1987 #[test]
1988 fn authority_and_initialize_encodings_match_spl_token_wire_format() {
1989 let authority = Address::new_from_array([9; 32]);
1990 let (set_authority, len) = encoders::encode_set_authority(
1991 TokenAuthorityType::AccountOwner as u8,
1992 Some(authority.as_array()),
1993 );
1994 assert_eq!(len, 35);
1995 assert_eq!(set_authority[0], 6);
1996 assert_eq!(set_authority[1], 2);
1997 assert_eq!(set_authority[2], 1);
1998 assert_eq!(&set_authority[3..35], authority.as_bytes());
1999
2000 let (set_authority, len) =
2001 encoders::encode_set_authority(TokenAuthorityType::CloseAccount as u8, None);
2002 assert_eq!(len, 3);
2003 assert_eq!(&set_authority[..3], &[6, 3, 0]);
2004
2005 let init2 = encoders::encode_initialize_account_with_owner(16, authority.as_array());
2006 let init3 = encoders::encode_initialize_account_with_owner(18, authority.as_array());
2007 assert_eq!(init2[0], 16);
2008 assert_eq!(init3[0], 18);
2009 assert_eq!(&init2[1..33], authority.as_bytes());
2010 assert_eq!(&init3[1..33], authority.as_bytes());
2011 }
2012
2013 #[test]
2018 fn shipped_encoders_match_pre_refactor_golden_bytes() {
2019 assert_eq!(encoders::encode_transfer(1), [3, 1, 0, 0, 0, 0, 0, 0, 0]);
2021 assert_eq!(encoders::encode_approve(1), [4, 1, 0, 0, 0, 0, 0, 0, 0]);
2022 assert_eq!(encoders::encode_mint_to(1), [7, 1, 0, 0, 0, 0, 0, 0, 0]);
2023 assert_eq!(encoders::encode_burn(1), [8, 1, 0, 0, 0, 0, 0, 0, 0]);
2024 assert_eq!(
2025 encoders::encode_transfer_checked(1, 9),
2026 [12, 1, 0, 0, 0, 0, 0, 0, 0, 9]
2027 );
2028 assert_eq!(
2029 encoders::encode_approve_checked(1, 9),
2030 [13, 1, 0, 0, 0, 0, 0, 0, 0, 9]
2031 );
2032 assert_eq!(
2033 encoders::encode_mint_to_checked(1, 9),
2034 [14, 1, 0, 0, 0, 0, 0, 0, 0, 9]
2035 );
2036 assert_eq!(
2037 encoders::encode_burn_checked(1, 9),
2038 [15, 1, 0, 0, 0, 0, 0, 0, 0, 9]
2039 );
2040 assert_eq!(encoders::encode_revoke(), [5]);
2041 assert_eq!(encoders::encode_close_account(), [9]);
2042 assert_eq!(encoders::encode_freeze_account(), [10]);
2043 assert_eq!(encoders::encode_thaw_account(), [11]);
2044 assert_eq!(encoders::encode_sync_native(), [17]);
2045 assert_eq!(encoders::encode_initialize_account(), [1]);
2046
2047 let owner = [
2048 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
2049 24, 25, 26, 27, 28, 29, 30, 31,
2050 ];
2051 let init2 = encoders::encode_initialize_account_with_owner(16, &owner);
2052 assert_eq!(init2[0], 16);
2053 assert_eq!(&init2[1..33], &owner);
2054 let init3 = encoders::encode_initialize_account_with_owner(18, &owner);
2055 assert_eq!(init3[0], 18);
2056 assert_eq!(&init3[1..33], &owner);
2057
2058 let (sa_some, some_len) = encoders::encode_set_authority(2, Some(&owner));
2059 assert_eq!(some_len, 35);
2060 assert_eq!(sa_some[0], 6);
2061 assert_eq!(sa_some[1], 2);
2062 assert_eq!(sa_some[2], 1);
2063 assert_eq!(&sa_some[3..35], &owner);
2064 let (sa_none, none_len) = encoders::encode_set_authority(3, None);
2065 assert_eq!(none_len, 3);
2066 assert_eq!(&sa_none[..3], &[6, 3, 0]);
2067 }
2068
2069 fn make_token_and_authority(
2077 authority_bytes: [u8; 32],
2078 token_owner_bytes: [u8; 32],
2079 ) -> (
2080 std::vec::Vec<u64>,
2081 std::vec::Vec<u64>,
2082 crate::account::AccountView<'static>,
2083 crate::account::AccountView<'static>,
2084 ) {
2085 use hopper_native::{
2086 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount,
2087 NOT_BORROWED,
2088 };
2089
2090 let token_data_len = 165;
2095 let mut token_backing =
2096 std::vec![0u64; (RuntimeAccount::SIZE + token_data_len).div_ceil(8)];
2097 let token_raw = token_backing.as_mut_ptr() as *mut RuntimeAccount;
2098 unsafe {
2100 token_raw.write(RuntimeAccount {
2101 borrow_state: NOT_BORROWED,
2102 is_signer: 0,
2103 is_writable: 1,
2104 executable: 0,
2105 resize_delta: 0,
2106 address: NativeAddress::new_from_array([0xAA; 32]),
2107 owner: NativeAddress::new_from_array([3; 32]),
2108 lamports: 2_039_280,
2109 data_len: token_data_len as u64,
2110 });
2111 let data_ptr = (token_raw as *mut u8).add(RuntimeAccount::SIZE);
2113 core::ptr::copy_nonoverlapping(token_owner_bytes.as_ptr(), data_ptr.add(32), 32);
2114 }
2115 let token_backend = unsafe { NativeAccountView::new_unchecked(token_raw) };
2117 let token_view = crate::account::AccountView::from_backend(token_backend);
2118
2119 let mut auth_backing = std::vec![0u64; (RuntimeAccount::SIZE).div_ceil(8)];
2121 let auth_raw = auth_backing.as_mut_ptr() as *mut RuntimeAccount;
2122 unsafe {
2124 auth_raw.write(RuntimeAccount {
2125 borrow_state: NOT_BORROWED,
2126 is_signer: 1,
2127 is_writable: 0,
2128 executable: 0,
2129 resize_delta: 0,
2130 address: NativeAddress::new_from_array(authority_bytes),
2131 owner: NativeAddress::new_from_array([0; 32]),
2132 lamports: 0,
2133 data_len: 0,
2134 });
2135 }
2136 let auth_backend = unsafe { NativeAccountView::new_unchecked(auth_raw) };
2138 let auth_view = crate::account::AccountView::from_backend(auth_backend);
2139
2140 (token_backing, auth_backing, token_view, auth_view)
2141 }
2142
2143 #[test]
2144 fn require_token_authority_accepts_matching_owner() {
2145 let authority = [0x42u8; 32];
2146 let (_tb, _ab, token, auth) = make_token_and_authority(authority, authority);
2147 require_token_authority(&token, &auth).unwrap();
2148 }
2149
2150 #[test]
2151 fn require_token_authority_rejects_mismatched_owner() {
2152 let authority = [0x42u8; 32];
2153 let wrong_owner = [0x77u8; 32];
2154 let (_tb, _ab, token, auth) = make_token_and_authority(authority, wrong_owner);
2155 let err = require_token_authority(&token, &auth).unwrap_err();
2156 assert!(matches!(err, ProgramError::IncorrectAuthority));
2157 }
2158
2159 #[test]
2160 fn require_token_authority_rejects_short_buffer() {
2161 use hopper_native::{
2162 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount,
2163 NOT_BORROWED,
2164 };
2165
2166 let data_len = 50;
2170 let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + data_len).div_ceil(8)];
2171 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
2172 unsafe {
2174 raw.write(RuntimeAccount {
2175 borrow_state: NOT_BORROWED,
2176 is_signer: 0,
2177 is_writable: 1,
2178 executable: 0,
2179 resize_delta: 0,
2180 address: NativeAddress::new_from_array([0xAA; 32]),
2181 owner: NativeAddress::new_from_array([3; 32]),
2182 lamports: 0,
2183 data_len: data_len as u64,
2184 });
2185 }
2186 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
2188 let token = crate::account::AccountView::from_backend(backend);
2189
2190 let (_ab, _, _, auth) = make_token_and_authority([0x11; 32], [0x11; 32]);
2191 let err = require_token_authority(&token, &auth).unwrap_err();
2192 assert!(matches!(err, ProgramError::AccountDataTooSmall));
2193 }
2194
2195 fn make_token_with_mint_and_owner(
2207 mint_bytes: [u8; 32],
2208 owner_bytes: [u8; 32],
2209 ) -> (std::vec::Vec<u64>, crate::account::AccountView<'static>) {
2210 use hopper_native::{
2211 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount,
2212 NOT_BORROWED,
2213 };
2214
2215 let token_data_len = 165;
2216 let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + token_data_len).div_ceil(8)];
2217 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
2218 unsafe {
2220 raw.write(RuntimeAccount {
2221 borrow_state: NOT_BORROWED,
2222 is_signer: 0,
2223 is_writable: 1,
2224 executable: 0,
2225 resize_delta: 0,
2226 address: NativeAddress::new_from_array([0xAA; 32]),
2227 owner: NativeAddress::new_from_array([3; 32]),
2228 lamports: 2_039_280,
2229 data_len: token_data_len as u64,
2230 });
2231 let data_ptr = (raw as *mut u8).add(RuntimeAccount::SIZE);
2232 core::ptr::copy_nonoverlapping(mint_bytes.as_ptr(), data_ptr, 32);
2233 core::ptr::copy_nonoverlapping(owner_bytes.as_ptr(), data_ptr.add(32), 32);
2234 }
2235 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
2237 let view = crate::account::AccountView::from_backend(backend);
2238 (backing, view)
2239 }
2240
2241 fn make_mint_with_authority_decimals(
2245 mint_authority: [u8; 32],
2246 decimals: u8,
2247 ) -> (std::vec::Vec<u64>, crate::account::AccountView<'static>) {
2248 use hopper_native::{
2249 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount,
2250 NOT_BORROWED,
2251 };
2252
2253 let mint_data_len = 82;
2254 let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + mint_data_len).div_ceil(8)];
2255 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
2256 unsafe {
2258 raw.write(RuntimeAccount {
2259 borrow_state: NOT_BORROWED,
2260 is_signer: 0,
2261 is_writable: 0,
2262 executable: 0,
2263 resize_delta: 0,
2264 address: NativeAddress::new_from_array([0xBB; 32]),
2265 owner: NativeAddress::new_from_array([3; 32]),
2266 lamports: 1_461_600,
2267 data_len: mint_data_len as u64,
2268 });
2269 let data_ptr = (raw as *mut u8).add(RuntimeAccount::SIZE);
2270 let some_tag: [u8; 4] = 1u32.to_le_bytes();
2272 core::ptr::copy_nonoverlapping(some_tag.as_ptr(), data_ptr, 4);
2273 core::ptr::copy_nonoverlapping(mint_authority.as_ptr(), data_ptr.add(4), 32);
2274 *data_ptr.add(44) = decimals;
2277 *data_ptr.add(45) = 1;
2279 }
2281 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
2283 let view = crate::account::AccountView::from_backend(backend);
2284 (backing, view)
2285 }
2286
2287 #[test]
2288 fn require_token_mint_accepts_matching_mint() {
2289 let mint = [0xABu8; 32];
2290 let (_b, view) = make_token_with_mint_and_owner(mint, [0; 32]);
2291 let expected = crate::address::Address::new_from_array(mint);
2292 require_token_mint(&view, &expected).unwrap();
2293 }
2294
2295 #[test]
2296 fn require_token_mint_rejects_mismatched_mint() {
2297 let mint = [0xABu8; 32];
2298 let (_b, view) = make_token_with_mint_and_owner(mint, [0; 32]);
2299 let wrong = crate::address::Address::new_from_array([0xCDu8; 32]);
2300 let err = require_token_mint(&view, &wrong).unwrap_err();
2301 assert!(matches!(err, ProgramError::InvalidAccountData));
2302 }
2303
2304 #[test]
2305 fn require_token_owner_eq_matches() {
2306 let owner = [0x77u8; 32];
2307 let (_b, view) = make_token_with_mint_and_owner([0; 32], owner);
2308 let expected = crate::address::Address::new_from_array(owner);
2309 require_token_owner_eq(&view, &expected).unwrap();
2310 }
2311
2312 #[test]
2313 fn require_token_owner_eq_rejects_mismatch() {
2314 let owner = [0x77u8; 32];
2315 let (_b, view) = make_token_with_mint_and_owner([0; 32], owner);
2316 let wrong = crate::address::Address::new_from_array([0x88u8; 32]);
2317 let err = require_token_owner_eq(&view, &wrong).unwrap_err();
2318 assert!(matches!(err, ProgramError::IncorrectAuthority));
2319 }
2320
2321 #[test]
2322 fn require_mint_authority_accepts_matching() {
2323 let auth = [0x99u8; 32];
2324 let (_b, view) = make_mint_with_authority_decimals(auth, 6);
2325 let expected = crate::address::Address::new_from_array(auth);
2326 require_mint_authority(&view, &expected).unwrap();
2327 }
2328
2329 #[test]
2330 fn require_mint_authority_rejects_mismatched() {
2331 let auth = [0x99u8; 32];
2332 let (_b, view) = make_mint_with_authority_decimals(auth, 6);
2333 let wrong = crate::address::Address::new_from_array([0x00u8; 32]);
2334 let err = require_mint_authority(&view, &wrong).unwrap_err();
2335 assert!(matches!(err, ProgramError::IncorrectAuthority));
2336 }
2337
2338 #[test]
2339 fn require_mint_decimals_matches() {
2340 let (_b, view) = make_mint_with_authority_decimals([1u8; 32], 9);
2341 require_mint_decimals(&view, 9).unwrap();
2342 }
2343
2344 #[test]
2345 fn require_mint_decimals_rejects_mismatch() {
2346 let (_b, view) = make_mint_with_authority_decimals([1u8; 32], 9);
2347 let err = require_mint_decimals(&view, 6).unwrap_err();
2348 assert!(matches!(err, ProgramError::InvalidAccountData));
2349 }
2350
2351 #[test]
2352 fn require_mint_freeze_authority_rejects_none_tag() {
2353 let (_b, view) = make_mint_with_authority_decimals([1u8; 32], 9);
2359 let expected = crate::address::Address::new_from_array([2u8; 32]);
2360 let err = require_mint_freeze_authority(&view, &expected).unwrap_err();
2361 assert!(matches!(err, ProgramError::InvalidAccountData));
2362 }
2363}