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(
522 since = "0.2.0",
523 note = "use TransferChecked for Token-2022 safety (mint + decimals validation)"
524)]
525#[cfg(feature = "legacy-token-instructions")]
526pub struct Transfer<'a> {
527 pub from: &'a AccountView<'a>,
528 pub to: &'a AccountView<'a>,
529 pub authority: &'a AccountView<'a>,
530 pub amount: u64,
531}
532
533#[allow(deprecated)]
534#[cfg(feature = "legacy-token-instructions")]
535impl Transfer<'_> {
536 #[inline]
540 pub fn invoke(&self) -> ProgramResult {
541 require_authority_signed_direct(self.authority)?;
542 self.invoke_signed_unchecked(&[])
543 }
544
545 #[inline]
548 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
549 self.invoke_signed_unchecked(signers)
550 }
551
552 #[inline(always)]
553 fn invoke_signed_unchecked(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
554 let data = encoders::encode_transfer(self.amount);
555
556 let accounts = [
557 InstructionAccount::writable(self.from.address()),
558 InstructionAccount::writable(self.to.address()),
559 InstructionAccount::readonly_signer(self.authority.address()),
560 ];
561 let views = [self.from, self.to, self.authority];
562 let instruction = InstructionView {
563 program_id: &TOKEN_PROGRAM_ID,
564 data: &data,
565 accounts: &accounts,
566 };
567
568 crate::cpi::invoke_signed(&instruction, &views, signers)
569 }
570}
571
572#[deprecated(
578 since = "0.2.0",
579 note = "use MintToChecked for Token-2022 safety (mint + decimals validation)"
580)]
581#[cfg(feature = "legacy-token-instructions")]
582pub struct MintTo<'a> {
583 pub mint: &'a AccountView<'a>,
584 pub account: &'a AccountView<'a>,
585 pub mint_authority: &'a AccountView<'a>,
586 pub amount: u64,
587}
588
589#[allow(deprecated)]
590#[cfg(feature = "legacy-token-instructions")]
591impl MintTo<'_> {
592 #[inline]
593 pub fn invoke(&self) -> ProgramResult {
594 require_authority_signed_direct(self.mint_authority)?;
595 self.invoke_signed(&[])
596 }
597
598 #[inline]
599 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
600 let data = encoders::encode_mint_to(self.amount);
601
602 let accounts = [
603 InstructionAccount::writable(self.mint.address()),
604 InstructionAccount::writable(self.account.address()),
605 InstructionAccount::readonly_signer(self.mint_authority.address()),
606 ];
607 let views = [self.mint, self.account, self.mint_authority];
608 let instruction = InstructionView {
609 program_id: &TOKEN_PROGRAM_ID,
610 data: &data,
611 accounts: &accounts,
612 };
613
614 crate::cpi::invoke_signed(&instruction, &views, signers)
615 }
616}
617
618#[deprecated(
624 since = "0.2.0",
625 note = "use BurnChecked for Token-2022 safety (mint + decimals validation)"
626)]
627#[cfg(feature = "legacy-token-instructions")]
628pub struct Burn<'a> {
629 pub account: &'a AccountView<'a>,
630 pub mint: &'a AccountView<'a>,
631 pub authority: &'a AccountView<'a>,
632 pub amount: u64,
633}
634
635#[allow(deprecated)]
636#[cfg(feature = "legacy-token-instructions")]
637impl Burn<'_> {
638 #[inline]
639 pub fn invoke(&self) -> ProgramResult {
640 require_authority_signed_direct(self.authority)?;
641 self.invoke_signed(&[])
642 }
643
644 #[inline]
645 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
646 let data = encoders::encode_burn(self.amount);
647
648 let accounts = [
649 InstructionAccount::writable(self.account.address()),
650 InstructionAccount::writable(self.mint.address()),
651 InstructionAccount::readonly_signer(self.authority.address()),
652 ];
653 let views = [self.account, self.mint, self.authority];
654 let instruction = InstructionView {
655 program_id: &TOKEN_PROGRAM_ID,
656 data: &data,
657 accounts: &accounts,
658 };
659
660 crate::cpi::invoke_signed(&instruction, &views, signers)
661 }
662}
663
664pub struct CloseAccount<'a> {
668 pub account: &'a AccountView<'a>,
669 pub destination: &'a AccountView<'a>,
670 pub authority: &'a AccountView<'a>,
671}
672
673impl CloseAccount<'_> {
674 #[inline]
675 pub fn invoke(&self) -> ProgramResult {
676 require_authority_signed_direct(self.authority)?;
677 self.invoke_signed(&[])
678 }
679
680 #[inline]
681 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
682 self.invoke_signed_unchecked_with_multisig(&[], signers)
683 }
684
685 #[inline]
686 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
687 require_multisig_signers_direct(multisig_signers)?;
688 self.invoke_signed_multisig(multisig_signers, &[])
689 }
690
691 #[inline]
692 pub fn invoke_signed_multisig(
693 &self,
694 multisig_signers: &[&AccountView<'_>],
695 signers: &[Signer<'_, '_>],
696 ) -> ProgramResult {
697 self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
698 }
699
700 #[inline(always)]
701 fn invoke_signed_unchecked_with_multisig(
702 &self,
703 multisig_signers: &[&AccountView<'_>],
704 signers: &[Signer<'_, '_>],
705 ) -> ProgramResult {
706 let data = encoders::encode_close_account();
707 let accounts = [
708 InstructionAccount::writable(self.account.address()),
709 InstructionAccount::writable(self.destination.address()),
710 authority_meta(self.authority, multisig_signers),
711 ];
712 let views = [self.account, self.destination, self.authority];
713 invoke_token_signed(&data, accounts, views, multisig_signers, signers)
714 }
715}
716
717#[deprecated(
723 since = "0.2.0",
724 note = "use ApproveChecked for Token-2022 safety (mint + decimals validation)"
725)]
726#[cfg(feature = "legacy-token-instructions")]
727pub struct Approve<'a> {
728 pub source: &'a AccountView<'a>,
729 pub delegate: &'a AccountView<'a>,
730 pub authority: &'a AccountView<'a>,
731 pub amount: u64,
732}
733
734#[allow(deprecated)]
735#[cfg(feature = "legacy-token-instructions")]
736impl Approve<'_> {
737 #[inline]
738 pub fn invoke(&self) -> ProgramResult {
739 require_authority_signed_direct(self.authority)?;
740 self.invoke_signed(&[])
741 }
742
743 #[inline]
744 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
745 let data = encoders::encode_approve(self.amount);
746
747 let accounts = [
748 InstructionAccount::writable(self.source.address()),
749 InstructionAccount::readonly(self.delegate.address()),
750 InstructionAccount::readonly_signer(self.authority.address()),
751 ];
752 let views = [self.source, self.delegate, self.authority];
753 let instruction = InstructionView {
754 program_id: &TOKEN_PROGRAM_ID,
755 data: &data,
756 accounts: &accounts,
757 };
758
759 crate::cpi::invoke_signed(&instruction, &views, signers)
760 }
761}
762
763pub struct Revoke<'a> {
767 pub source: &'a AccountView<'a>,
768 pub authority: &'a AccountView<'a>,
769}
770
771impl Revoke<'_> {
772 #[inline]
773 pub fn invoke(&self) -> ProgramResult {
774 require_authority_signed_direct(self.authority)?;
775 self.invoke_signed(&[])
776 }
777
778 #[inline]
779 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
780 self.invoke_signed_unchecked_with_multisig(&[], signers)
781 }
782
783 #[inline]
784 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
785 require_multisig_signers_direct(multisig_signers)?;
786 self.invoke_signed_multisig(multisig_signers, &[])
787 }
788
789 #[inline]
790 pub fn invoke_signed_multisig(
791 &self,
792 multisig_signers: &[&AccountView<'_>],
793 signers: &[Signer<'_, '_>],
794 ) -> ProgramResult {
795 self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
796 }
797
798 #[inline(always)]
799 fn invoke_signed_unchecked_with_multisig(
800 &self,
801 multisig_signers: &[&AccountView<'_>],
802 signers: &[Signer<'_, '_>],
803 ) -> ProgramResult {
804 let data = encoders::encode_revoke();
805 let accounts = [
806 InstructionAccount::writable(self.source.address()),
807 authority_meta(self.authority, multisig_signers),
808 ];
809 let views = [self.source, self.authority];
810 invoke_token_signed(&data, accounts, views, multisig_signers, signers)
811 }
812}
813
814pub struct TransferChecked<'a> {
831 pub from: &'a AccountView<'a>,
832 pub mint: &'a AccountView<'a>,
833 pub to: &'a AccountView<'a>,
834 pub authority: &'a AccountView<'a>,
835 pub amount: u64,
836 pub decimals: u8,
837}
838
839impl TransferChecked<'_> {
840 #[inline]
844 pub fn invoke(&self) -> ProgramResult {
845 require_authority_signed_direct(self.authority)?;
846 self.invoke_signed_unchecked(&[])
847 }
848
849 #[inline]
861 pub fn invoke_strict(&self) -> ProgramResult {
862 require_authority_signed_direct(self.authority)?;
863 require_token_authority(self.from, self.authority)?;
864 self.invoke_signed_unchecked(&[])
865 }
866
867 #[inline]
870 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
871 self.invoke_signed_unchecked(signers)
872 }
873
874 #[inline]
877 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
878 require_multisig_signers_direct(multisig_signers)?;
879 self.invoke_signed_multisig(multisig_signers, &[])
880 }
881
882 #[inline]
884 pub fn invoke_signed_multisig(
885 &self,
886 multisig_signers: &[&AccountView<'_>],
887 signers: &[Signer<'_, '_>],
888 ) -> ProgramResult {
889 self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
890 }
891
892 #[inline]
896 pub fn invoke_signed_strict(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
897 require_token_authority(self.from, self.authority)?;
898 self.invoke_signed_unchecked(signers)
899 }
900
901 #[inline(always)]
902 fn invoke_signed_unchecked(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
903 self.invoke_signed_unchecked_with_multisig(&[], signers)
904 }
905
906 #[inline(always)]
907 fn invoke_signed_unchecked_with_multisig(
908 &self,
909 multisig_signers: &[&AccountView<'_>],
910 signers: &[Signer<'_, '_>],
911 ) -> ProgramResult {
912 let data = encoders::encode_transfer_checked(self.amount, self.decimals);
913
914 let accounts = [
915 InstructionAccount::writable(self.from.address()),
916 InstructionAccount::readonly(self.mint.address()),
917 InstructionAccount::writable(self.to.address()),
918 authority_meta(self.authority, multisig_signers),
919 ];
920 let views = [self.from, self.mint, self.to, self.authority];
921 invoke_token_signed(&data, accounts, views, multisig_signers, signers)
922 }
923}
924
925pub struct MintToChecked<'a> {
932 pub mint: &'a AccountView<'a>,
933 pub account: &'a AccountView<'a>,
934 pub mint_authority: &'a AccountView<'a>,
935 pub amount: u64,
936 pub decimals: u8,
937}
938
939impl MintToChecked<'_> {
940 #[inline]
941 pub fn invoke(&self) -> ProgramResult {
942 require_authority_signed_direct(self.mint_authority)?;
943 self.invoke_signed_unchecked(&[])
944 }
945
946 #[inline]
947 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
948 self.invoke_signed_unchecked(signers)
949 }
950
951 #[inline]
952 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
953 require_multisig_signers_direct(multisig_signers)?;
954 self.invoke_signed_multisig(multisig_signers, &[])
955 }
956
957 #[inline]
958 pub fn invoke_signed_multisig(
959 &self,
960 multisig_signers: &[&AccountView<'_>],
961 signers: &[Signer<'_, '_>],
962 ) -> ProgramResult {
963 self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
964 }
965
966 #[inline(always)]
967 fn invoke_signed_unchecked(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
968 self.invoke_signed_unchecked_with_multisig(&[], signers)
969 }
970
971 #[inline(always)]
972 fn invoke_signed_unchecked_with_multisig(
973 &self,
974 multisig_signers: &[&AccountView<'_>],
975 signers: &[Signer<'_, '_>],
976 ) -> ProgramResult {
977 let data = encoders::encode_mint_to_checked(self.amount, self.decimals);
978
979 let accounts = [
980 InstructionAccount::writable(self.mint.address()),
981 InstructionAccount::writable(self.account.address()),
982 authority_meta(self.mint_authority, multisig_signers),
983 ];
984 let views = [self.mint, self.account, self.mint_authority];
985 invoke_token_signed(&data, accounts, views, multisig_signers, signers)
986 }
987}
988
989pub struct BurnChecked<'a> {
997 pub account: &'a AccountView<'a>,
998 pub mint: &'a AccountView<'a>,
999 pub authority: &'a AccountView<'a>,
1000 pub amount: u64,
1001 pub decimals: u8,
1002}
1003
1004impl BurnChecked<'_> {
1005 #[inline]
1006 pub fn invoke(&self) -> ProgramResult {
1007 require_authority_signed_direct(self.authority)?;
1008 self.invoke_signed_unchecked(&[])
1009 }
1010
1011 #[inline]
1015 pub fn invoke_strict(&self) -> ProgramResult {
1016 require_authority_signed_direct(self.authority)?;
1017 require_token_authority(self.account, self.authority)?;
1018 self.invoke_signed_unchecked(&[])
1019 }
1020
1021 #[inline]
1022 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1023 self.invoke_signed_unchecked(signers)
1024 }
1025
1026 #[inline]
1027 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
1028 require_multisig_signers_direct(multisig_signers)?;
1029 self.invoke_signed_multisig(multisig_signers, &[])
1030 }
1031
1032 #[inline]
1033 pub fn invoke_signed_multisig(
1034 &self,
1035 multisig_signers: &[&AccountView<'_>],
1036 signers: &[Signer<'_, '_>],
1037 ) -> ProgramResult {
1038 self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
1039 }
1040
1041 #[inline]
1045 pub fn invoke_signed_strict(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1046 require_token_authority(self.account, self.authority)?;
1047 self.invoke_signed_unchecked(signers)
1048 }
1049
1050 #[inline(always)]
1051 fn invoke_signed_unchecked(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1052 self.invoke_signed_unchecked_with_multisig(&[], signers)
1053 }
1054
1055 #[inline(always)]
1056 fn invoke_signed_unchecked_with_multisig(
1057 &self,
1058 multisig_signers: &[&AccountView<'_>],
1059 signers: &[Signer<'_, '_>],
1060 ) -> ProgramResult {
1061 let data = encoders::encode_burn_checked(self.amount, self.decimals);
1062
1063 let accounts = [
1064 InstructionAccount::writable(self.account.address()),
1065 InstructionAccount::writable(self.mint.address()),
1066 authority_meta(self.authority, multisig_signers),
1067 ];
1068 let views = [self.account, self.mint, self.authority];
1069 invoke_token_signed(&data, accounts, views, multisig_signers, signers)
1070 }
1071}
1072
1073pub struct ApproveChecked<'a> {
1080 pub source: &'a AccountView<'a>,
1081 pub mint: &'a AccountView<'a>,
1082 pub delegate: &'a AccountView<'a>,
1083 pub authority: &'a AccountView<'a>,
1084 pub amount: u64,
1085 pub decimals: u8,
1086}
1087
1088impl ApproveChecked<'_> {
1089 #[inline]
1090 pub fn invoke(&self) -> ProgramResult {
1091 require_authority_signed_direct(self.authority)?;
1092 self.invoke_signed_unchecked(&[])
1093 }
1094
1095 #[inline]
1100 pub fn invoke_strict(&self) -> ProgramResult {
1101 require_authority_signed_direct(self.authority)?;
1102 require_token_authority(self.source, self.authority)?;
1103 self.invoke_signed_unchecked(&[])
1104 }
1105
1106 #[inline]
1107 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1108 self.invoke_signed_unchecked(signers)
1109 }
1110
1111 #[inline]
1112 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
1113 require_multisig_signers_direct(multisig_signers)?;
1114 self.invoke_signed_multisig(multisig_signers, &[])
1115 }
1116
1117 #[inline]
1118 pub fn invoke_signed_multisig(
1119 &self,
1120 multisig_signers: &[&AccountView<'_>],
1121 signers: &[Signer<'_, '_>],
1122 ) -> ProgramResult {
1123 self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
1124 }
1125
1126 #[inline]
1129 pub fn invoke_signed_strict(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1130 require_token_authority(self.source, self.authority)?;
1131 self.invoke_signed_unchecked(signers)
1132 }
1133
1134 #[inline(always)]
1135 fn invoke_signed_unchecked(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1136 self.invoke_signed_unchecked_with_multisig(&[], signers)
1137 }
1138
1139 #[inline(always)]
1140 fn invoke_signed_unchecked_with_multisig(
1141 &self,
1142 multisig_signers: &[&AccountView<'_>],
1143 signers: &[Signer<'_, '_>],
1144 ) -> ProgramResult {
1145 let data = encoders::encode_approve_checked(self.amount, self.decimals);
1146
1147 let accounts = [
1148 InstructionAccount::writable(self.source.address()),
1149 InstructionAccount::readonly(self.mint.address()),
1150 InstructionAccount::readonly(self.delegate.address()),
1151 authority_meta(self.authority, multisig_signers),
1152 ];
1153 let views = [self.source, self.mint, self.delegate, self.authority];
1154 invoke_token_signed(&data, accounts, views, multisig_signers, signers)
1155 }
1156}
1157
1158#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1162#[repr(u8)]
1163pub enum TokenAuthorityType {
1164 MintTokens = 0,
1165 FreezeAccount = 1,
1166 AccountOwner = 2,
1167 CloseAccount = 3,
1168}
1169
1170pub struct SetAuthority<'a> {
1172 pub account: &'a AccountView<'a>,
1173 pub current_authority: &'a AccountView<'a>,
1174 pub authority_type: TokenAuthorityType,
1175 pub new_authority: Option<&'a Address>,
1176}
1177
1178impl SetAuthority<'_> {
1179 #[inline]
1180 pub fn invoke(&self) -> ProgramResult {
1181 require_authority_signed_direct(self.current_authority)?;
1182 self.invoke_signed_unchecked_with_multisig(&[], &[])
1183 }
1184
1185 #[inline]
1186 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1187 self.invoke_signed_unchecked_with_multisig(&[], signers)
1188 }
1189
1190 #[inline]
1191 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
1192 require_multisig_signers_direct(multisig_signers)?;
1193 self.invoke_signed_multisig(multisig_signers, &[])
1194 }
1195
1196 #[inline]
1197 pub fn invoke_signed_multisig(
1198 &self,
1199 multisig_signers: &[&AccountView<'_>],
1200 signers: &[Signer<'_, '_>],
1201 ) -> ProgramResult {
1202 self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
1203 }
1204
1205 #[inline(always)]
1206 fn invoke_signed_unchecked_with_multisig(
1207 &self,
1208 multisig_signers: &[&AccountView<'_>],
1209 signers: &[Signer<'_, '_>],
1210 ) -> ProgramResult {
1211 let (data, len) = encoders::encode_set_authority(
1212 self.authority_type as u8,
1213 self.new_authority.map(|a| a.as_array()),
1214 );
1215 let accounts = [
1216 InstructionAccount::writable(self.account.address()),
1217 authority_meta(self.current_authority, multisig_signers),
1218 ];
1219 let views = [self.account, self.current_authority];
1220 invoke_token_signed(&data[..len], accounts, views, multisig_signers, signers)
1221 }
1222}
1223
1224pub struct FreezeAccount<'a> {
1228 pub account: &'a AccountView<'a>,
1229 pub mint: &'a AccountView<'a>,
1230 pub freeze_authority: &'a AccountView<'a>,
1231}
1232
1233impl FreezeAccount<'_> {
1234 #[inline]
1235 pub fn invoke(&self) -> ProgramResult {
1236 require_authority_signed_direct(self.freeze_authority)?;
1237 self.invoke_signed_unchecked_with_multisig(&[], &[])
1238 }
1239
1240 #[inline]
1241 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1242 self.invoke_signed_unchecked_with_multisig(&[], signers)
1243 }
1244
1245 #[inline]
1246 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
1247 require_multisig_signers_direct(multisig_signers)?;
1248 self.invoke_signed_multisig(multisig_signers, &[])
1249 }
1250
1251 #[inline]
1252 pub fn invoke_signed_multisig(
1253 &self,
1254 multisig_signers: &[&AccountView<'_>],
1255 signers: &[Signer<'_, '_>],
1256 ) -> ProgramResult {
1257 self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
1258 }
1259
1260 #[inline(always)]
1261 fn invoke_signed_unchecked_with_multisig(
1262 &self,
1263 multisig_signers: &[&AccountView<'_>],
1264 signers: &[Signer<'_, '_>],
1265 ) -> ProgramResult {
1266 let data = encoders::encode_freeze_account();
1267 let accounts = [
1268 InstructionAccount::writable(self.account.address()),
1269 InstructionAccount::readonly(self.mint.address()),
1270 authority_meta(self.freeze_authority, multisig_signers),
1271 ];
1272 let views = [self.account, self.mint, self.freeze_authority];
1273 invoke_token_signed(&data, accounts, views, multisig_signers, signers)
1274 }
1275}
1276
1277pub struct ThawAccount<'a> {
1279 pub account: &'a AccountView<'a>,
1280 pub mint: &'a AccountView<'a>,
1281 pub freeze_authority: &'a AccountView<'a>,
1282}
1283
1284impl ThawAccount<'_> {
1285 #[inline]
1286 pub fn invoke(&self) -> ProgramResult {
1287 require_authority_signed_direct(self.freeze_authority)?;
1288 self.invoke_signed_unchecked_with_multisig(&[], &[])
1289 }
1290
1291 #[inline]
1292 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1293 self.invoke_signed_unchecked_with_multisig(&[], signers)
1294 }
1295
1296 #[inline]
1297 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
1298 require_multisig_signers_direct(multisig_signers)?;
1299 self.invoke_signed_multisig(multisig_signers, &[])
1300 }
1301
1302 #[inline]
1303 pub fn invoke_signed_multisig(
1304 &self,
1305 multisig_signers: &[&AccountView<'_>],
1306 signers: &[Signer<'_, '_>],
1307 ) -> ProgramResult {
1308 self.invoke_signed_unchecked_with_multisig(multisig_signers, signers)
1309 }
1310
1311 #[inline(always)]
1312 fn invoke_signed_unchecked_with_multisig(
1313 &self,
1314 multisig_signers: &[&AccountView<'_>],
1315 signers: &[Signer<'_, '_>],
1316 ) -> ProgramResult {
1317 let data = encoders::encode_thaw_account();
1318 let accounts = [
1319 InstructionAccount::writable(self.account.address()),
1320 InstructionAccount::readonly(self.mint.address()),
1321 authority_meta(self.freeze_authority, multisig_signers),
1322 ];
1323 let views = [self.account, self.mint, self.freeze_authority];
1324 invoke_token_signed(&data, accounts, views, multisig_signers, signers)
1325 }
1326}
1327
1328pub struct SyncNative<'a> {
1332 pub account: &'a AccountView<'a>,
1333}
1334
1335impl SyncNative<'_> {
1336 #[inline]
1337 pub fn invoke(&self) -> ProgramResult {
1338 let data = encoders::encode_sync_native();
1339 let accounts = [InstructionAccount::writable(self.account.address())];
1340 let views = [self.account];
1341 invoke_token_signed(&data, accounts, views, &[], &[])
1342 }
1343}
1344
1345pub struct InitializeAccount<'a> {
1349 pub account: &'a AccountView<'a>,
1350 pub mint: &'a AccountView<'a>,
1351 pub owner: &'a AccountView<'a>,
1352 pub rent_sysvar: &'a AccountView<'a>,
1353}
1354
1355impl InitializeAccount<'_> {
1356 #[inline]
1357 pub fn invoke(&self) -> ProgramResult {
1358 let data = encoders::encode_initialize_account();
1359 let accounts = [
1360 InstructionAccount::writable(self.account.address()),
1361 InstructionAccount::readonly(self.mint.address()),
1362 InstructionAccount::readonly(self.owner.address()),
1363 InstructionAccount::readonly(self.rent_sysvar.address()),
1364 ];
1365 let views = [self.account, self.mint, self.owner, self.rent_sysvar];
1366 let instruction = InstructionView {
1367 program_id: &TOKEN_PROGRAM_ID,
1368 data: &data,
1369 accounts: &accounts,
1370 };
1371
1372 crate::cpi::invoke(&instruction, &views)
1373 }
1374}
1375
1376pub struct InitializeAccount2<'a> {
1378 pub account: &'a AccountView<'a>,
1379 pub mint: &'a AccountView<'a>,
1380 pub owner: &'a Address,
1381 pub rent_sysvar: &'a AccountView<'a>,
1382}
1383
1384impl InitializeAccount2<'_> {
1385 #[inline]
1386 pub fn invoke(&self) -> ProgramResult {
1387 let data = encoders::encode_initialize_account_with_owner(16, self.owner.as_array());
1388 let accounts = [
1389 InstructionAccount::writable(self.account.address()),
1390 InstructionAccount::readonly(self.mint.address()),
1391 InstructionAccount::readonly(self.rent_sysvar.address()),
1392 ];
1393 let views = [self.account, self.mint, self.rent_sysvar];
1394 invoke_token_signed(&data, accounts, views, &[], &[])
1395 }
1396}
1397
1398pub struct InitializeAccount3<'a> {
1400 pub account: &'a AccountView<'a>,
1401 pub mint: &'a AccountView<'a>,
1402 pub owner: &'a Address,
1403}
1404
1405impl InitializeAccount3<'_> {
1406 #[inline]
1407 pub fn invoke(&self) -> ProgramResult {
1408 let data = encoders::encode_initialize_account_with_owner(18, self.owner.as_array());
1409 let accounts = [
1410 InstructionAccount::writable(self.account.address()),
1411 InstructionAccount::readonly(self.mint.address()),
1412 ];
1413 let views = [self.account, self.mint];
1414 invoke_token_signed(&data, accounts, views, &[], &[])
1415 }
1416}
1417
1418pub const TOKEN_PROGRAM_ID: Address = Address::new_from_array(crate::__decode_base58_32(
1420 "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
1421));
1422
1423pub const SPL_TOKEN_ACCOUNT_LEN: usize = 165;
1426pub const SPL_MINT_LEN: usize = 82;
1427
1428const TOKEN_ACCOUNT_MINT_OFFSET: usize = 0;
1429const TOKEN_ACCOUNT_AUTHORITY_OFFSET: usize = 32;
1430const TOKEN_ACCOUNT_AMOUNT_OFFSET: usize = 64;
1431const TOKEN_ACCOUNT_STATE_OFFSET: usize = 108;
1432
1433const MINT_AUTHORITY_TAG_OFFSET: usize = 0;
1434const MINT_AUTHORITY_OFFSET: usize = 4;
1435const MINT_SUPPLY_OFFSET: usize = 36;
1436const MINT_DECIMALS_OFFSET: usize = 44;
1437const MINT_INITIALIZED_OFFSET: usize = 45;
1438const MINT_FREEZE_AUTHORITY_TAG_OFFSET: usize = 46;
1439const MINT_FREEZE_AUTHORITY_OFFSET: usize = 50;
1440
1441pub struct SplTokenAccount;
1443
1444pub struct SplTokenAccountView<'a> {
1446 data: Ref<'a, [u8]>,
1447}
1448
1449impl SplTokenAccountView<'_> {
1450 #[inline(always)]
1451 pub fn mint(&self) -> Address {
1452 read_address_unchecked(&self.data, TOKEN_ACCOUNT_MINT_OFFSET)
1453 }
1454
1455 #[inline(always)]
1456 pub fn authority(&self) -> Address {
1457 read_address_unchecked(&self.data, TOKEN_ACCOUNT_AUTHORITY_OFFSET)
1458 }
1459
1460 #[inline(always)]
1461 pub fn amount(&self) -> u64 {
1462 read_u64_unchecked(&self.data, TOKEN_ACCOUNT_AMOUNT_OFFSET)
1463 }
1464
1465 #[inline(always)]
1466 pub fn state(&self) -> u8 {
1467 self.data[TOKEN_ACCOUNT_STATE_OFFSET]
1468 }
1469
1470 #[inline(always)]
1471 pub fn is_initialized(&self) -> bool {
1472 self.state() != 0
1473 }
1474}
1475
1476impl ExternalZeroCopy for SplTokenAccount {
1477 type View<'a> = SplTokenAccountView<'a>;
1478
1479 const OWNER: Option<Address> = Some(TOKEN_PROGRAM_ID);
1480 const MIN_LEN: usize = SPL_TOKEN_ACCOUNT_LEN;
1481
1482 #[inline]
1483 fn view<'a>(data: Ref<'a, [u8]>) -> Result<Self::View<'a>, ProgramError> {
1484 Ok(SplTokenAccountView { data })
1485 }
1486}
1487
1488impl ExplainExternal for SplTokenAccount {
1489 fn explain<S: ExternalExplainSink>(account: &AccountView<'_>, sink: &mut S) -> ProgramResult {
1490 let account = ExternalAccount::<SplTokenAccount>::try_new(account)?;
1491 account.with_view(|token| {
1492 sink.field_str("adapter", "SplTokenAccount")?;
1493 sink.field_address("mint", &token.mint())?;
1494 sink.field_address("authority", &token.authority())?;
1495 sink.field_u64("amount", token.amount())?;
1496 sink.field_bool("initialized", token.is_initialized())
1497 })
1498 }
1499}
1500
1501pub struct SplMint;
1503
1504pub struct SplMintView<'a> {
1506 data: Ref<'a, [u8]>,
1507}
1508
1509impl SplMintView<'_> {
1510 #[inline(always)]
1511 pub fn mint_authority(&self) -> Option<Address> {
1512 read_coption_address(&self.data, MINT_AUTHORITY_TAG_OFFSET, MINT_AUTHORITY_OFFSET)
1513 }
1514
1515 #[inline(always)]
1516 pub fn supply(&self) -> u64 {
1517 read_u64_unchecked(&self.data, MINT_SUPPLY_OFFSET)
1518 }
1519
1520 #[inline(always)]
1521 pub fn decimals(&self) -> u8 {
1522 self.data[MINT_DECIMALS_OFFSET]
1523 }
1524
1525 #[inline(always)]
1526 pub fn is_initialized(&self) -> bool {
1527 self.data[MINT_INITIALIZED_OFFSET] != 0
1528 }
1529
1530 #[inline(always)]
1531 pub fn freeze_authority(&self) -> Option<Address> {
1532 read_coption_address(
1533 &self.data,
1534 MINT_FREEZE_AUTHORITY_TAG_OFFSET,
1535 MINT_FREEZE_AUTHORITY_OFFSET,
1536 )
1537 }
1538}
1539
1540impl ExternalZeroCopy for SplMint {
1541 type View<'a> = SplMintView<'a>;
1542
1543 const OWNER: Option<Address> = Some(TOKEN_PROGRAM_ID);
1544 const MIN_LEN: usize = SPL_MINT_LEN;
1545
1546 #[inline]
1547 fn view<'a>(data: Ref<'a, [u8]>) -> Result<Self::View<'a>, ProgramError> {
1548 Ok(SplMintView { data })
1549 }
1550}
1551
1552impl ExplainExternal for SplMint {
1553 fn explain<S: ExternalExplainSink>(account: &AccountView<'_>, sink: &mut S) -> ProgramResult {
1554 let account = ExternalAccount::<SplMint>::try_new(account)?;
1555 account.with_view(|mint| {
1556 sink.field_str("adapter", "SplMint")?;
1557 sink.field_u64("supply", mint.supply())?;
1558 sink.field_u64("decimals", mint.decimals() as u64)?;
1559 sink.field_bool("initialized", mint.is_initialized())
1560 })
1561 }
1562}
1563
1564#[derive(Debug)]
1566pub struct CheckedTokenMint<'info> {
1567 account: ExternalAccount<'info, SplTokenAccount>,
1568 mint: Address,
1569}
1570
1571impl<'info> CheckedTokenMint<'info> {
1572 #[inline(always)]
1573 pub const fn account(&self) -> ExternalAccount<'info, SplTokenAccount> {
1574 self.account
1575 }
1576
1577 #[inline(always)]
1578 pub const fn mint(&self) -> Address {
1579 self.mint
1580 }
1581}
1582
1583#[derive(Debug)]
1585pub struct CheckedTokenAuthority<'info> {
1586 account: ExternalAccount<'info, SplTokenAccount>,
1587 authority: Address,
1588}
1589
1590impl<'info> CheckedTokenAuthority<'info> {
1591 #[inline(always)]
1592 pub const fn account(&self) -> ExternalAccount<'info, SplTokenAccount> {
1593 self.account
1594 }
1595
1596 #[inline(always)]
1597 pub const fn authority(&self) -> Address {
1598 self.authority
1599 }
1600}
1601
1602#[derive(Debug)]
1604pub struct CheckedMintDecimals<'info> {
1605 account: ExternalAccount<'info, SplMint>,
1606 decimals: u8,
1607}
1608
1609impl<'info> CheckedMintDecimals<'info> {
1610 #[inline(always)]
1611 pub const fn account(&self) -> ExternalAccount<'info, SplMint> {
1612 self.account
1613 }
1614
1615 #[inline(always)]
1616 pub const fn decimals(&self) -> u8 {
1617 self.decimals
1618 }
1619}
1620
1621#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1623pub struct TokenAmountSnapshot {
1624 amount: u64,
1625}
1626
1627impl TokenAmountSnapshot {
1628 #[inline(always)]
1629 pub const fn amount(self) -> u64 {
1630 self.amount
1631 }
1632}
1633
1634impl<'info> ExternalAccount<'info, SplTokenAccount> {
1635 #[inline]
1636 pub fn token_amount(&self) -> Result<u64, ProgramError> {
1637 Ok(self.view()?.amount())
1638 }
1639
1640 #[inline]
1641 pub fn checked_mint(
1642 &self,
1643 expected_mint: &Address,
1644 ) -> Result<CheckedTokenMint<'info>, ProgramError> {
1645 let mint = self.view()?.mint();
1646 if &mint == expected_mint {
1647 Ok(CheckedTokenMint {
1648 account: *self,
1649 mint,
1650 })
1651 } else {
1652 Err(ProgramError::InvalidAccountData)
1653 }
1654 }
1655
1656 #[inline]
1657 pub fn checked_authority(
1658 &self,
1659 expected_authority: &Address,
1660 ) -> Result<CheckedTokenAuthority<'info>, ProgramError> {
1661 let authority = self.view()?.authority();
1662 if &authority == expected_authority {
1663 Ok(CheckedTokenAuthority {
1664 account: *self,
1665 authority,
1666 })
1667 } else {
1668 Err(ProgramError::IncorrectAuthority)
1669 }
1670 }
1671
1672 #[inline]
1673 pub fn amount_snapshot(&self) -> Result<TokenAmountSnapshot, ProgramError> {
1674 Ok(TokenAmountSnapshot {
1675 amount: self.token_amount()?,
1676 })
1677 }
1678
1679 #[inline]
1680 pub fn assert_amount_delta(
1681 &self,
1682 before: TokenAmountSnapshot,
1683 expected_delta: i128,
1684 ) -> ProgramResult {
1685 let after = self.token_amount()? as i128;
1686 let expected = (before.amount as i128)
1687 .checked_add(expected_delta)
1688 .ok_or(ProgramError::ArithmeticOverflow)?;
1689 if expected < 0 || expected > u64::MAX as i128 {
1690 return Err(ProgramError::ArithmeticOverflow);
1691 }
1692 if after == expected {
1693 Ok(())
1694 } else {
1695 Err(ProgramError::InvalidAccountData)
1696 }
1697 }
1698
1699 #[inline]
1700 pub fn assert_amount_unchanged(&self, before: TokenAmountSnapshot) -> ProgramResult {
1701 self.assert_amount_delta(before, 0)
1702 }
1703}
1704
1705impl<'info> ExternalAccount<'info, SplMint> {
1706 #[inline]
1707 pub fn checked_decimals(
1708 &self,
1709 expected: u8,
1710 ) -> Result<CheckedMintDecimals<'info>, ProgramError> {
1711 let decimals = self.view()?.decimals();
1712 if decimals == expected {
1713 Ok(CheckedMintDecimals {
1714 account: *self,
1715 decimals,
1716 })
1717 } else {
1718 Err(ProgramError::InvalidAccountData)
1719 }
1720 }
1721}
1722
1723#[inline(always)]
1724fn read_address_unchecked(data: &[u8], offset: usize) -> Address {
1725 let mut bytes = [0u8; 32];
1726 bytes.copy_from_slice(&data[offset..offset + 32]);
1727 Address::new_from_array(bytes)
1728}
1729
1730#[inline(always)]
1731fn read_u64_unchecked(data: &[u8], offset: usize) -> u64 {
1732 u64::from_le_bytes([
1733 data[offset],
1734 data[offset + 1],
1735 data[offset + 2],
1736 data[offset + 3],
1737 data[offset + 4],
1738 data[offset + 5],
1739 data[offset + 6],
1740 data[offset + 7],
1741 ])
1742}
1743
1744#[inline(always)]
1745fn read_u32_unchecked(data: &[u8], offset: usize) -> u32 {
1746 u32::from_le_bytes([
1747 data[offset],
1748 data[offset + 1],
1749 data[offset + 2],
1750 data[offset + 3],
1751 ])
1752}
1753
1754#[inline(always)]
1755fn read_coption_address(data: &[u8], tag_offset: usize, address_offset: usize) -> Option<Address> {
1756 match read_u32_unchecked(data, tag_offset) {
1757 1 => Some(read_address_unchecked(data, address_offset)),
1758 _ => None,
1759 }
1760}
1761
1762pub mod instructions {
1764 pub use super::{
1765 ApproveChecked, BurnChecked, CloseAccount, FreezeAccount, InitializeAccount,
1766 InitializeAccount2, InitializeAccount3, MintToChecked, Revoke, SetAuthority, SyncNative,
1767 ThawAccount, TokenAuthorityType, TransferChecked,
1768 };
1769
1770 #[cfg(feature = "legacy-token-instructions")]
1771 #[allow(deprecated)]
1772 pub use super::{Approve, Burn, MintTo, Transfer};
1773}
1774
1775#[cfg(test)]
1776mod tests {
1777 use super::*;
1785 use hopper_native::{
1786 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED,
1787 };
1788 fn make_account(owner: Address, data: &[u8]) -> (std::vec::Vec<u64>, AccountView<'static>) {
1789 let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + data.len()).div_ceil(8)];
1790 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
1791 unsafe {
1794 raw.write(RuntimeAccount {
1795 borrow_state: NOT_BORROWED,
1796 is_signer: 0,
1797 is_writable: 1,
1798 executable: 0,
1799 resize_delta: 0,
1800 address: NativeAddress::new_from_array([7; 32]),
1801 owner: NativeAddress::new_from_array(owner.to_bytes()),
1802 lamports: 1,
1803 data_len: data.len() as u64,
1804 });
1805 let data_ptr = (backing.as_mut_ptr() as *mut u8).add(RuntimeAccount::SIZE);
1806 core::ptr::copy_nonoverlapping(data.as_ptr(), data_ptr, data.len());
1807 }
1808 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
1810 (backing, AccountView::from_backend(backend))
1811 }
1812 fn token_account_data(
1813 mint: Address,
1814 authority: Address,
1815 amount: u64,
1816 ) -> [u8; SPL_TOKEN_ACCOUNT_LEN] {
1817 let mut data = [0u8; SPL_TOKEN_ACCOUNT_LEN];
1818 data[0..32].copy_from_slice(mint.as_bytes());
1819 data[32..64].copy_from_slice(authority.as_bytes());
1820 data[64..72].copy_from_slice(&amount.to_le_bytes());
1821 data[108] = 1;
1822 data
1823 }
1824 fn mint_data(authority: Address, supply: u64, decimals: u8) -> [u8; SPL_MINT_LEN] {
1825 let mut data = [0u8; SPL_MINT_LEN];
1826 data[0..4].copy_from_slice(&1u32.to_le_bytes());
1827 data[4..36].copy_from_slice(authority.as_bytes());
1828 data[36..44].copy_from_slice(&supply.to_le_bytes());
1829 data[44] = decimals;
1830 data[45] = 1;
1831 data
1832 }
1833
1834 #[test]
1839 fn transfer_checked_discriminator_is_12() {
1840 }
1860 #[test]
1861 fn spl_external_token_account_view_proofs_and_amount_delta() {
1862 let mint = Address::new_from_array([2; 32]);
1863 let authority = Address::new_from_array([3; 32]);
1864 let data = token_account_data(mint, authority, 100);
1865 let (mut backing, account) = make_account(TOKEN_PROGRAM_ID, &data);
1866
1867 let token = ExternalAccount::<SplTokenAccount>::try_new(&account).unwrap();
1868 let view = token.view().unwrap();
1869 assert_eq!(view.mint(), mint);
1870 assert_eq!(view.authority(), authority);
1871 assert_eq!(view.amount(), 100);
1872 assert!(view.is_initialized());
1873 assert_eq!(token.checked_mint(&mint).unwrap().mint(), mint);
1874 assert_eq!(
1875 token.checked_authority(&authority).unwrap().authority(),
1876 authority
1877 );
1878 assert_eq!(
1879 token
1880 .checked_mint(&Address::new_from_array([9; 32]))
1881 .unwrap_err(),
1882 ProgramError::InvalidAccountData
1883 );
1884
1885 let before = token.amount_snapshot().unwrap();
1886 let backing_bytes = unsafe {
1890 core::slice::from_raw_parts_mut(backing.as_mut_ptr() as *mut u8, backing.len() * 8)
1891 };
1892 backing_bytes[RuntimeAccount::SIZE + 64..RuntimeAccount::SIZE + 72]
1893 .copy_from_slice(&150u64.to_le_bytes());
1894 token.assert_amount_delta(before, 50).unwrap();
1895 assert_eq!(
1896 token.assert_amount_delta(before, 49).unwrap_err(),
1897 ProgramError::InvalidAccountData
1898 );
1899 }
1900 #[test]
1901 fn spl_external_mint_view_and_decimals_proof() {
1902 let authority = Address::new_from_array([4; 32]);
1903 let data = mint_data(authority, 1_000_000, 6);
1904 let (_backing, account) = make_account(TOKEN_PROGRAM_ID, &data);
1905
1906 let mint = ExternalAccount::<SplMint>::try_new(&account).unwrap();
1907 let view = mint.view().unwrap();
1908 assert_eq!(view.mint_authority(), Some(authority));
1909 assert_eq!(view.supply(), 1_000_000);
1910 assert_eq!(view.decimals(), 6);
1911 assert!(view.is_initialized());
1912 assert_eq!(mint.checked_decimals(6).unwrap().decimals(), 6);
1913 assert_eq!(
1914 mint.checked_decimals(9).unwrap_err(),
1915 ProgramError::InvalidAccountData
1916 );
1917 }
1918
1919 fn encode_checked(disc: u8, amount: u64, decimals: u8) -> [u8; 10] {
1923 let mut data = [0u8; 10];
1924 data[0] = disc;
1925 data[1..9].copy_from_slice(&amount.to_le_bytes());
1926 data[9] = decimals;
1927 data
1928 }
1929
1930 #[test]
1931 fn transfer_checked_wire_format_is_stable() {
1932 let out = encode_checked(12, 0x0102_0304_0506_0708, 9);
1934 assert_eq!(out[0], 12);
1935 assert_eq!(
1936 &out[1..9],
1937 &[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]
1938 );
1939 assert_eq!(out[9], 9);
1940 }
1941
1942 #[test]
1943 fn mint_to_checked_wire_format_is_stable() {
1944 let out = encode_checked(14, 1000, 6);
1945 assert_eq!(out[0], 14);
1946 assert_eq!(u64::from_le_bytes(out[1..9].try_into().unwrap()), 1000);
1947 assert_eq!(out[9], 6);
1948 }
1949
1950 #[test]
1951 fn burn_checked_wire_format_is_stable() {
1952 let out = encode_checked(15, 42, 8);
1953 assert_eq!(out[0], 15);
1954 assert_eq!(u64::from_le_bytes(out[1..9].try_into().unwrap()), 42);
1955 assert_eq!(out[9], 8);
1956 }
1957
1958 #[test]
1959 fn approve_checked_wire_format_is_stable() {
1960 let out = encode_checked(13, u64::MAX, 0);
1961 assert_eq!(out[0], 13);
1962 assert_eq!(u64::from_le_bytes(out[1..9].try_into().unwrap()), u64::MAX);
1963 assert_eq!(out[9], 0);
1964 }
1965
1966 #[test]
1967 fn checked_encoding_round_trips_decimals_range() {
1968 for d in 0u8..=255 {
1972 let out = encode_checked(12, 1, d);
1973 assert_eq!(out[9], d);
1974 }
1975 }
1976
1977 #[test]
1978 fn checked_encoding_preserves_amount_bits() {
1979 for shift in 0..8 {
1982 let amount = 0xABu64 << (shift * 8);
1983 let out = encode_checked(12, amount, 0);
1984 let decoded = u64::from_le_bytes(out[1..9].try_into().unwrap());
1985 assert_eq!(decoded, amount);
1986 }
1987 }
1988
1989 #[test]
1990 fn authority_and_initialize_encodings_match_spl_token_wire_format() {
1991 let authority = Address::new_from_array([9; 32]);
1992 let (set_authority, len) = encoders::encode_set_authority(
1993 TokenAuthorityType::AccountOwner as u8,
1994 Some(authority.as_array()),
1995 );
1996 assert_eq!(len, 35);
1997 assert_eq!(set_authority[0], 6);
1998 assert_eq!(set_authority[1], 2);
1999 assert_eq!(set_authority[2], 1);
2000 assert_eq!(&set_authority[3..35], authority.as_bytes());
2001
2002 let (set_authority, len) =
2003 encoders::encode_set_authority(TokenAuthorityType::CloseAccount as u8, None);
2004 assert_eq!(len, 3);
2005 assert_eq!(&set_authority[..3], &[6, 3, 0]);
2006
2007 let init2 = encoders::encode_initialize_account_with_owner(16, authority.as_array());
2008 let init3 = encoders::encode_initialize_account_with_owner(18, authority.as_array());
2009 assert_eq!(init2[0], 16);
2010 assert_eq!(init3[0], 18);
2011 assert_eq!(&init2[1..33], authority.as_bytes());
2012 assert_eq!(&init3[1..33], authority.as_bytes());
2013 }
2014
2015 #[test]
2020 fn shipped_encoders_match_pre_refactor_golden_bytes() {
2021 assert_eq!(encoders::encode_transfer(1), [3, 1, 0, 0, 0, 0, 0, 0, 0]);
2023 assert_eq!(encoders::encode_approve(1), [4, 1, 0, 0, 0, 0, 0, 0, 0]);
2024 assert_eq!(encoders::encode_mint_to(1), [7, 1, 0, 0, 0, 0, 0, 0, 0]);
2025 assert_eq!(encoders::encode_burn(1), [8, 1, 0, 0, 0, 0, 0, 0, 0]);
2026 assert_eq!(
2027 encoders::encode_transfer_checked(1, 9),
2028 [12, 1, 0, 0, 0, 0, 0, 0, 0, 9]
2029 );
2030 assert_eq!(
2031 encoders::encode_approve_checked(1, 9),
2032 [13, 1, 0, 0, 0, 0, 0, 0, 0, 9]
2033 );
2034 assert_eq!(
2035 encoders::encode_mint_to_checked(1, 9),
2036 [14, 1, 0, 0, 0, 0, 0, 0, 0, 9]
2037 );
2038 assert_eq!(
2039 encoders::encode_burn_checked(1, 9),
2040 [15, 1, 0, 0, 0, 0, 0, 0, 0, 9]
2041 );
2042 assert_eq!(encoders::encode_revoke(), [5]);
2043 assert_eq!(encoders::encode_close_account(), [9]);
2044 assert_eq!(encoders::encode_freeze_account(), [10]);
2045 assert_eq!(encoders::encode_thaw_account(), [11]);
2046 assert_eq!(encoders::encode_sync_native(), [17]);
2047 assert_eq!(encoders::encode_initialize_account(), [1]);
2048
2049 let owner = [
2050 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
2051 24, 25, 26, 27, 28, 29, 30, 31,
2052 ];
2053 let init2 = encoders::encode_initialize_account_with_owner(16, &owner);
2054 assert_eq!(init2[0], 16);
2055 assert_eq!(&init2[1..33], &owner);
2056 let init3 = encoders::encode_initialize_account_with_owner(18, &owner);
2057 assert_eq!(init3[0], 18);
2058 assert_eq!(&init3[1..33], &owner);
2059
2060 let (sa_some, some_len) = encoders::encode_set_authority(2, Some(&owner));
2061 assert_eq!(some_len, 35);
2062 assert_eq!(sa_some[0], 6);
2063 assert_eq!(sa_some[1], 2);
2064 assert_eq!(sa_some[2], 1);
2065 assert_eq!(&sa_some[3..35], &owner);
2066 let (sa_none, none_len) = encoders::encode_set_authority(3, None);
2067 assert_eq!(none_len, 3);
2068 assert_eq!(&sa_none[..3], &[6, 3, 0]);
2069 }
2070
2071 fn make_token_and_authority(
2079 authority_bytes: [u8; 32],
2080 token_owner_bytes: [u8; 32],
2081 ) -> (
2082 std::vec::Vec<u64>,
2083 std::vec::Vec<u64>,
2084 crate::account::AccountView<'static>,
2085 crate::account::AccountView<'static>,
2086 ) {
2087 use hopper_native::{
2088 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount,
2089 NOT_BORROWED,
2090 };
2091
2092 let token_data_len = 165;
2097 let mut token_backing =
2098 std::vec![0u64; (RuntimeAccount::SIZE + token_data_len).div_ceil(8)];
2099 let token_raw = token_backing.as_mut_ptr() as *mut RuntimeAccount;
2100 unsafe {
2102 token_raw.write(RuntimeAccount {
2103 borrow_state: NOT_BORROWED,
2104 is_signer: 0,
2105 is_writable: 1,
2106 executable: 0,
2107 resize_delta: 0,
2108 address: NativeAddress::new_from_array([0xAA; 32]),
2109 owner: NativeAddress::new_from_array([3; 32]),
2110 lamports: 2_039_280,
2111 data_len: token_data_len as u64,
2112 });
2113 let data_ptr = (token_raw as *mut u8).add(RuntimeAccount::SIZE);
2115 core::ptr::copy_nonoverlapping(token_owner_bytes.as_ptr(), data_ptr.add(32), 32);
2116 }
2117 let token_backend = unsafe { NativeAccountView::new_unchecked(token_raw) };
2119 let token_view = crate::account::AccountView::from_backend(token_backend);
2120
2121 let mut auth_backing = std::vec![0u64; (RuntimeAccount::SIZE).div_ceil(8)];
2123 let auth_raw = auth_backing.as_mut_ptr() as *mut RuntimeAccount;
2124 unsafe {
2126 auth_raw.write(RuntimeAccount {
2127 borrow_state: NOT_BORROWED,
2128 is_signer: 1,
2129 is_writable: 0,
2130 executable: 0,
2131 resize_delta: 0,
2132 address: NativeAddress::new_from_array(authority_bytes),
2133 owner: NativeAddress::new_from_array([0; 32]),
2134 lamports: 0,
2135 data_len: 0,
2136 });
2137 }
2138 let auth_backend = unsafe { NativeAccountView::new_unchecked(auth_raw) };
2140 let auth_view = crate::account::AccountView::from_backend(auth_backend);
2141
2142 (token_backing, auth_backing, token_view, auth_view)
2143 }
2144
2145 #[test]
2146 fn require_token_authority_accepts_matching_owner() {
2147 let authority = [0x42u8; 32];
2148 let (_tb, _ab, token, auth) = make_token_and_authority(authority, authority);
2149 require_token_authority(&token, &auth).unwrap();
2150 }
2151
2152 #[test]
2153 fn require_token_authority_rejects_mismatched_owner() {
2154 let authority = [0x42u8; 32];
2155 let wrong_owner = [0x77u8; 32];
2156 let (_tb, _ab, token, auth) = make_token_and_authority(authority, wrong_owner);
2157 let err = require_token_authority(&token, &auth).unwrap_err();
2158 assert!(matches!(err, ProgramError::IncorrectAuthority));
2159 }
2160
2161 #[test]
2162 fn require_token_authority_rejects_short_buffer() {
2163 use hopper_native::{
2164 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount,
2165 NOT_BORROWED,
2166 };
2167
2168 let data_len = 50;
2172 let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + data_len).div_ceil(8)];
2173 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
2174 unsafe {
2176 raw.write(RuntimeAccount {
2177 borrow_state: NOT_BORROWED,
2178 is_signer: 0,
2179 is_writable: 1,
2180 executable: 0,
2181 resize_delta: 0,
2182 address: NativeAddress::new_from_array([0xAA; 32]),
2183 owner: NativeAddress::new_from_array([3; 32]),
2184 lamports: 0,
2185 data_len: data_len as u64,
2186 });
2187 }
2188 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
2190 let token = crate::account::AccountView::from_backend(backend);
2191
2192 let (_ab, _, _, auth) = make_token_and_authority([0x11; 32], [0x11; 32]);
2193 let err = require_token_authority(&token, &auth).unwrap_err();
2194 assert!(matches!(err, ProgramError::AccountDataTooSmall));
2195 }
2196
2197 fn make_token_with_mint_and_owner(
2209 mint_bytes: [u8; 32],
2210 owner_bytes: [u8; 32],
2211 ) -> (std::vec::Vec<u64>, crate::account::AccountView<'static>) {
2212 use hopper_native::{
2213 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount,
2214 NOT_BORROWED,
2215 };
2216
2217 let token_data_len = 165;
2218 let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + token_data_len).div_ceil(8)];
2219 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
2220 unsafe {
2222 raw.write(RuntimeAccount {
2223 borrow_state: NOT_BORROWED,
2224 is_signer: 0,
2225 is_writable: 1,
2226 executable: 0,
2227 resize_delta: 0,
2228 address: NativeAddress::new_from_array([0xAA; 32]),
2229 owner: NativeAddress::new_from_array([3; 32]),
2230 lamports: 2_039_280,
2231 data_len: token_data_len as u64,
2232 });
2233 let data_ptr = (raw as *mut u8).add(RuntimeAccount::SIZE);
2234 core::ptr::copy_nonoverlapping(mint_bytes.as_ptr(), data_ptr, 32);
2235 core::ptr::copy_nonoverlapping(owner_bytes.as_ptr(), data_ptr.add(32), 32);
2236 }
2237 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
2239 let view = crate::account::AccountView::from_backend(backend);
2240 (backing, view)
2241 }
2242
2243 fn make_mint_with_authority_decimals(
2247 mint_authority: [u8; 32],
2248 decimals: u8,
2249 ) -> (std::vec::Vec<u64>, crate::account::AccountView<'static>) {
2250 use hopper_native::{
2251 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount,
2252 NOT_BORROWED,
2253 };
2254
2255 let mint_data_len = 82;
2256 let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + mint_data_len).div_ceil(8)];
2257 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
2258 unsafe {
2260 raw.write(RuntimeAccount {
2261 borrow_state: NOT_BORROWED,
2262 is_signer: 0,
2263 is_writable: 0,
2264 executable: 0,
2265 resize_delta: 0,
2266 address: NativeAddress::new_from_array([0xBB; 32]),
2267 owner: NativeAddress::new_from_array([3; 32]),
2268 lamports: 1_461_600,
2269 data_len: mint_data_len as u64,
2270 });
2271 let data_ptr = (raw as *mut u8).add(RuntimeAccount::SIZE);
2272 let some_tag: [u8; 4] = 1u32.to_le_bytes();
2274 core::ptr::copy_nonoverlapping(some_tag.as_ptr(), data_ptr, 4);
2275 core::ptr::copy_nonoverlapping(mint_authority.as_ptr(), data_ptr.add(4), 32);
2276 *data_ptr.add(44) = decimals;
2279 *data_ptr.add(45) = 1;
2281 }
2283 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
2285 let view = crate::account::AccountView::from_backend(backend);
2286 (backing, view)
2287 }
2288
2289 #[test]
2290 fn require_token_mint_accepts_matching_mint() {
2291 let mint = [0xABu8; 32];
2292 let (_b, view) = make_token_with_mint_and_owner(mint, [0; 32]);
2293 let expected = crate::address::Address::new_from_array(mint);
2294 require_token_mint(&view, &expected).unwrap();
2295 }
2296
2297 #[test]
2298 fn require_token_mint_rejects_mismatched_mint() {
2299 let mint = [0xABu8; 32];
2300 let (_b, view) = make_token_with_mint_and_owner(mint, [0; 32]);
2301 let wrong = crate::address::Address::new_from_array([0xCDu8; 32]);
2302 let err = require_token_mint(&view, &wrong).unwrap_err();
2303 assert!(matches!(err, ProgramError::InvalidAccountData));
2304 }
2305
2306 #[test]
2307 fn require_token_owner_eq_matches() {
2308 let owner = [0x77u8; 32];
2309 let (_b, view) = make_token_with_mint_and_owner([0; 32], owner);
2310 let expected = crate::address::Address::new_from_array(owner);
2311 require_token_owner_eq(&view, &expected).unwrap();
2312 }
2313
2314 #[test]
2315 fn require_token_owner_eq_rejects_mismatch() {
2316 let owner = [0x77u8; 32];
2317 let (_b, view) = make_token_with_mint_and_owner([0; 32], owner);
2318 let wrong = crate::address::Address::new_from_array([0x88u8; 32]);
2319 let err = require_token_owner_eq(&view, &wrong).unwrap_err();
2320 assert!(matches!(err, ProgramError::IncorrectAuthority));
2321 }
2322
2323 #[test]
2324 fn require_mint_authority_accepts_matching() {
2325 let auth = [0x99u8; 32];
2326 let (_b, view) = make_mint_with_authority_decimals(auth, 6);
2327 let expected = crate::address::Address::new_from_array(auth);
2328 require_mint_authority(&view, &expected).unwrap();
2329 }
2330
2331 #[test]
2332 fn require_mint_authority_rejects_mismatched() {
2333 let auth = [0x99u8; 32];
2334 let (_b, view) = make_mint_with_authority_decimals(auth, 6);
2335 let wrong = crate::address::Address::new_from_array([0x00u8; 32]);
2336 let err = require_mint_authority(&view, &wrong).unwrap_err();
2337 assert!(matches!(err, ProgramError::IncorrectAuthority));
2338 }
2339
2340 #[test]
2341 fn require_mint_decimals_matches() {
2342 let (_b, view) = make_mint_with_authority_decimals([1u8; 32], 9);
2343 require_mint_decimals(&view, 9).unwrap();
2344 }
2345
2346 #[test]
2347 fn require_mint_decimals_rejects_mismatch() {
2348 let (_b, view) = make_mint_with_authority_decimals([1u8; 32], 9);
2349 let err = require_mint_decimals(&view, 6).unwrap_err();
2350 assert!(matches!(err, ProgramError::InvalidAccountData));
2351 }
2352
2353 #[test]
2354 fn require_mint_freeze_authority_rejects_none_tag() {
2355 let (_b, view) = make_mint_with_authority_decimals([1u8; 32], 9);
2361 let expected = crate::address::Address::new_from_array([2u8; 32]);
2362 let err = require_mint_freeze_authority(&view, &expected).unwrap_err();
2363 assert!(matches!(err, ProgramError::InvalidAccountData));
2364 }
2365}