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