1use crate::account::AccountView;
33use crate::address::Address;
34use crate::borrow::Ref;
35use crate::error::ProgramError;
36use crate::foreign::{ExplainExternal, ExternalAccount, ExternalExplainSink, ExternalZeroCopy};
37use crate::instruction::{InstructionAccount, InstructionView, Signer};
38use crate::ProgramResult;
39use core::mem::MaybeUninit;
40
41pub use crate::token_admin::{
42 return_data_string, return_data_u64, AmountToUiAmount, GetAccountDataSize,
43 InitializeImmutableOwner, InitializeMint, InitializeMultisig, InitializeMultisig2,
44 UiAmountToAmount, UnwrapLamports, WithdrawExcessLamports, MAX_UI_AMOUNT_LEN,
45};
46pub use crate::token_batch::TokenBatch;
47pub use crate::token_mint::{InitializeMint2, MintConfig, MintPlan, MintProgram};
48
49pub const TOKEN_2022_PROGRAM_ID: Address = Address::new_from_array(crate::__decode_base58_32(
51 "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
52));
53
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub enum TokenProgram {
60 Legacy,
62 Token2022,
64}
65
66impl TokenProgram {
67 #[inline(always)]
69 pub const fn address(self) -> &'static Address {
70 match self {
71 Self::Legacy => &TOKEN_PROGRAM_ID,
72 Self::Token2022 => &TOKEN_2022_PROGRAM_ID,
73 }
74 }
75
76 #[inline]
78 pub fn from_address(address: &Address) -> Option<Self> {
79 if address == &TOKEN_PROGRAM_ID {
80 Some(Self::Legacy)
81 } else if address == &TOKEN_2022_PROGRAM_ID {
82 Some(Self::Token2022)
83 } else {
84 None
85 }
86 }
87
88 #[inline]
93 pub fn owning(account: &AccountView<'_>) -> Result<Self, ProgramError> {
94 if account.owned_by(&TOKEN_PROGRAM_ID) {
95 Ok(Self::Legacy)
96 } else if account.owned_by(&TOKEN_2022_PROGRAM_ID) {
97 Ok(Self::Token2022)
98 } else {
99 Err(ProgramError::IncorrectProgramId)
100 }
101 }
102
103 #[inline]
106 pub fn from_program_account(program: &AccountView<'_>) -> Result<Self, ProgramError> {
107 Self::from_address(program.address()).ok_or(ProgramError::IncorrectProgramId)
108 }
109}
110
111#[derive(Clone, Copy)]
115pub struct Trailing<'s, 'a> {
116 pub views: &'s [&'a AccountView<'a>],
117 pub writable: bool,
118 pub signer: bool,
119}
120
121impl<'s, 'a> Trailing<'s, 'a> {
122 #[inline(always)]
124 pub const fn signers(views: &'s [&'a AccountView<'a>]) -> Self {
125 Self {
126 views,
127 writable: false,
128 signer: true,
129 }
130 }
131
132 #[inline(always)]
134 pub const fn readonly(views: &'s [&'a AccountView<'a>]) -> Self {
135 Self {
136 views,
137 writable: false,
138 signer: false,
139 }
140 }
141
142 #[inline(always)]
144 pub const fn writable(views: &'s [&'a AccountView<'a>]) -> Self {
145 Self {
146 views,
147 writable: true,
148 signer: false,
149 }
150 }
151}
152
153pub trait TokenSink<'a> {
159 fn emit<const N: usize>(
163 &mut self,
164 data: &[u8],
165 accounts: [InstructionAccount<'a>; N],
166 views: [&'a AccountView<'a>; N],
167 trailing: &[Trailing<'_, 'a>],
168 ) -> ProgramResult;
169}
170
171pub trait TokenInstruction<'a> {
178 fn emit(
179 &self,
180 multisig_signers: &[&'a AccountView<'a>],
181 sink: &mut impl TokenSink<'a>,
182 ) -> ProgramResult;
183}
184
185pub(crate) struct Invoke<'p, 's, 'x, 'y> {
188 pub(crate) program: &'p Address,
189 pub(crate) signers: &'s [Signer<'x, 'y>],
190}
191
192impl<'s, 'x, 'y> Invoke<'static, 's, 'x, 'y> {
193 #[inline(always)]
195 pub(crate) const fn legacy(signers: &'s [Signer<'x, 'y>]) -> Self {
196 Self {
197 program: &TOKEN_PROGRAM_ID,
198 signers,
199 }
200 }
201
202 #[inline(always)]
204 pub(crate) const fn token_2022(signers: &'s [Signer<'x, 'y>]) -> Self {
205 Self {
206 program: &TOKEN_2022_PROGRAM_ID,
207 signers,
208 }
209 }
210}
211
212impl<'a> TokenSink<'a> for Invoke<'_, '_, '_, '_> {
213 #[inline(always)]
214 fn emit<const N: usize>(
215 &mut self,
216 data: &[u8],
217 accounts: [InstructionAccount<'a>; N],
218 views: [&'a AccountView<'a>; N],
219 trailing: &[Trailing<'_, 'a>],
220 ) -> ProgramResult {
221 if trailing.iter().all(|run| run.views.is_empty()) {
227 let instruction = InstructionView {
228 program_id: self.program,
229 data,
230 accounts: &accounts,
231 };
232 return crate::cpi::invoke_signed_builder_distinct(&instruction, &views, self.signers);
233 }
234 invoke_token_signed(self.program, data, accounts, views, trailing, self.signers)
235 }
236}
237
238macro_rules! token_program_methods {
243 ($name:ident, owner = $owner:ident $(, authority = $auth:ident)?) => {
244 impl $name<'_> {
245 #[inline]
251 pub fn invoke_on(
252 &self,
253 program: TokenProgram,
254 multisig_signers: &[&AccountView<'_>],
255 signers: &[Signer<'_, '_>],
256 ) -> ProgramResult {
257 $(
258 if signers.is_empty() {
259 if multisig_signers.is_empty() {
260 require_authority_signed_direct(self.$auth)?;
261 } else {
262 require_multisig_signers_direct(multisig_signers)?;
263 }
264 }
265 )?
266 self.emit(
267 multisig_signers,
268 &mut Invoke {
269 program: program.address(),
270 signers,
271 },
272 )
273 }
274
275 #[doc = concat!("`", stringify!($owner), "`")]
277 #[inline]
280 pub fn invoke_for_owner(
281 &self,
282 multisig_signers: &[&AccountView<'_>],
283 signers: &[Signer<'_, '_>],
284 ) -> ProgramResult {
285 self.invoke_on(TokenProgram::owning(self.$owner)?, multisig_signers, signers)
286 }
287 }
288 };
289}
290pub(crate) use token_program_methods;
291
292pub const MAX_TOKEN_MULTISIG_SIGNERS: usize = 11;
294
295#[inline(always)]
310pub(crate) fn require_authority_signed_direct(authority: &AccountView<'_>) -> ProgramResult {
311 if authority.is_signer() {
312 Ok(())
313 } else {
314 Err(ProgramError::MissingRequiredSignature)
315 }
316}
317
318#[inline(always)]
319pub(crate) fn authority_meta<'a>(
320 authority: &'a AccountView<'a>,
321 multisig_signers: &[&'a AccountView<'a>],
322) -> InstructionAccount<'a> {
323 if multisig_signers.is_empty() {
324 InstructionAccount::readonly_signer(authority.address())
325 } else {
326 InstructionAccount::readonly(authority.address())
327 }
328}
329
330#[inline]
331pub(crate) fn require_multisig_signers_direct(
332 multisig_signers: &[&AccountView<'_>],
333) -> ProgramResult {
334 if multisig_signers.len() > MAX_TOKEN_MULTISIG_SIGNERS {
335 return Err(ProgramError::InvalidArgument);
336 }
337 for signer in multisig_signers {
338 require_authority_signed_direct(signer)?;
339 }
340 Ok(())
341}
342
343#[doc(hidden)]
368pub mod encoders {
369 #[inline(always)]
373 fn amount_ix(disc: u8, amount: u64) -> [u8; 9] {
374 let mut data = [0u8; 9];
375 data[0] = disc;
376 data[1..9].copy_from_slice(&amount.to_le_bytes());
377 data
378 }
379
380 #[inline(always)]
384 fn amount_checked_ix(disc: u8, amount: u64, decimals: u8) -> [u8; 10] {
385 let mut data = [0u8; 10];
386 data[0] = disc;
387 data[1..9].copy_from_slice(&amount.to_le_bytes());
388 data[9] = decimals;
389 data
390 }
391
392 #[inline(always)]
394 pub fn encode_transfer(amount: u64) -> [u8; 9] {
395 amount_ix(3, amount)
396 }
397
398 #[inline(always)]
400 pub fn encode_approve(amount: u64) -> [u8; 9] {
401 amount_ix(4, amount)
402 }
403
404 #[inline(always)]
406 pub fn encode_mint_to(amount: u64) -> [u8; 9] {
407 amount_ix(7, amount)
408 }
409
410 #[inline(always)]
412 pub fn encode_burn(amount: u64) -> [u8; 9] {
413 amount_ix(8, amount)
414 }
415
416 #[inline(always)]
419 pub fn encode_transfer_checked(amount: u64, decimals: u8) -> [u8; 10] {
420 amount_checked_ix(12, amount, decimals)
421 }
422
423 #[inline(always)]
426 pub fn encode_approve_checked(amount: u64, decimals: u8) -> [u8; 10] {
427 amount_checked_ix(13, amount, decimals)
428 }
429
430 #[inline(always)]
433 pub fn encode_mint_to_checked(amount: u64, decimals: u8) -> [u8; 10] {
434 amount_checked_ix(14, amount, decimals)
435 }
436
437 #[inline(always)]
440 pub fn encode_burn_checked(amount: u64, decimals: u8) -> [u8; 10] {
441 amount_checked_ix(15, amount, decimals)
442 }
443
444 #[inline(always)]
446 pub fn encode_revoke() -> [u8; 1] {
447 [5]
448 }
449
450 #[inline(always)]
452 pub fn encode_close_account() -> [u8; 1] {
453 [9]
454 }
455
456 #[inline(always)]
458 pub fn encode_freeze_account() -> [u8; 1] {
459 [10]
460 }
461
462 #[inline(always)]
464 pub fn encode_thaw_account() -> [u8; 1] {
465 [11]
466 }
467
468 #[inline(always)]
470 pub fn encode_sync_native() -> [u8; 1] {
471 [17]
472 }
473
474 #[inline(always)]
477 pub fn encode_initialize_account() -> [u8; 1] {
478 [1]
479 }
480
481 #[inline(always)]
485 pub fn encode_initialize_account_with_owner(discriminator: u8, owner: &[u8; 32]) -> [u8; 33] {
486 let mut data = [0u8; 33];
487 data[0] = discriminator;
488 data[1..33].copy_from_slice(owner);
489 data
490 }
491
492 #[inline(always)]
499 pub fn encode_set_authority(
500 authority_type: u8,
501 new_authority: Option<&[u8; 32]>,
502 ) -> ([u8; 35], usize) {
503 let mut data = [0u8; 35];
504 data[0] = 6;
505 data[1] = authority_type;
506 match new_authority {
507 Some(key) => {
508 data[2] = 1;
509 data[3..35].copy_from_slice(key);
510 (data, 35)
511 }
512 None => {
513 data[2] = 0;
514 (data, 3)
515 }
516 }
517 }
518
519 #[inline(always)]
523 pub fn encode_initialize_mint(
524 decimals: u8,
525 mint_authority: &[u8; 32],
526 freeze_authority: Option<&[u8; 32]>,
527 ) -> ([u8; 67], usize) {
528 let mut data = [0u8; 67];
529 data[0] = 0;
530 data[1] = decimals;
531 data[2..34].copy_from_slice(mint_authority);
532 match freeze_authority {
533 Some(key) => {
534 data[34] = 1;
535 data[35..67].copy_from_slice(key);
536 (data, 67)
537 }
538 None => (data, 35),
539 }
540 }
541
542 #[inline(always)]
544 pub fn encode_initialize_multisig(m: u8) -> [u8; 2] {
545 [2, m]
546 }
547
548 #[inline(always)]
550 pub fn encode_initialize_multisig2(m: u8) -> [u8; 2] {
551 [19, m]
552 }
553
554 #[inline(always)]
556 pub fn encode_initialize_immutable_owner() -> [u8; 1] {
557 [22]
558 }
559
560 pub const MAX_EXTENSION_TYPES: usize = 32;
563
564 #[inline(always)]
569 pub fn encode_extension_types(
570 disc: u8,
571 extension_types: &[u16],
572 ) -> Option<([u8; 1 + 2 * MAX_EXTENSION_TYPES], usize)> {
573 if extension_types.len() > MAX_EXTENSION_TYPES {
574 return None;
575 }
576 let mut data = [0u8; 1 + 2 * MAX_EXTENSION_TYPES];
577 data[0] = disc;
578 let mut at = 1;
579 for ext in extension_types {
580 data[at..at + 2].copy_from_slice(&ext.to_le_bytes());
581 at += 2;
582 }
583 Some((data, at))
584 }
585
586 #[inline(always)]
588 pub fn encode_withdraw_excess_lamports() -> [u8; 1] {
589 [38]
590 }
591
592 #[inline(always)]
594 pub fn encode_amount_to_ui_amount(amount: u64) -> [u8; 9] {
595 amount_ix(23, amount)
596 }
597
598 pub const MAX_UI_AMOUNT_LEN: usize = 254;
602
603 #[inline(always)]
606 pub fn encode_ui_amount_to_amount(
607 ui_amount: &str,
608 ) -> Option<([u8; 1 + MAX_UI_AMOUNT_LEN], usize)> {
609 let bytes = ui_amount.as_bytes();
610 if bytes.len() > MAX_UI_AMOUNT_LEN {
611 return None;
612 }
613 let mut data = [0u8; 1 + MAX_UI_AMOUNT_LEN];
614 data[0] = 24;
615 data[1..1 + bytes.len()].copy_from_slice(bytes);
616 Some((data, 1 + bytes.len()))
617 }
618
619 #[inline(always)]
622 pub fn encode_unwrap_lamports(amount: Option<u64>) -> ([u8; 10], usize) {
623 let mut data = [0u8; 10];
624 data[0] = 45;
625 match amount {
626 Some(amount) => {
627 data[1] = 1;
628 data[2..10].copy_from_slice(&amount.to_le_bytes());
629 (data, 10)
630 }
631 None => (data, 2),
632 }
633 }
634}
635
636#[inline]
637pub(crate) fn invoke_token_signed<'a, const FIXED: usize>(
638 program: &Address,
639 data: &[u8],
640 fixed_accounts: [InstructionAccount<'a>; FIXED],
641 fixed_views: [&'a AccountView<'a>; FIXED],
642 trailing: &[Trailing<'_, 'a>],
643 signer_seeds: &[Signer<'_, '_>],
644) -> ProgramResult {
645 let mut total = FIXED;
646 for run in trailing {
647 if run.signer && run.views.len() > MAX_TOKEN_MULTISIG_SIGNERS {
648 return Err(ProgramError::InvalidArgument);
649 }
650 total = total
651 .checked_add(run.views.len())
652 .ok_or(ProgramError::ArithmeticOverflow)?;
653 }
654 if total > crate::cpi::MAX_STATIC_CPI_ACCOUNTS {
655 return Err(ProgramError::InvalidArgument);
656 }
657
658 let mut accounts: [MaybeUninit<InstructionAccount<'a>>; crate::cpi::MAX_STATIC_CPI_ACCOUNTS] =
659 [MaybeUninit::uninit(); crate::cpi::MAX_STATIC_CPI_ACCOUNTS];
660 let mut views: [MaybeUninit<&'a AccountView<'a>>; crate::cpi::MAX_STATIC_CPI_ACCOUNTS] =
661 [MaybeUninit::uninit(); crate::cpi::MAX_STATIC_CPI_ACCOUNTS];
662
663 let mut index = 0;
664 while index < FIXED {
665 accounts[index].write(fixed_accounts[index]);
666 views[index].write(fixed_views[index]);
667 index += 1;
668 }
669 for run in trailing {
670 for view in run.views {
671 accounts[index].write(InstructionAccount::new(
672 view.address(),
673 run.writable,
674 run.signer,
675 ));
676 views[index].write(*view);
677 index += 1;
678 }
679 }
680
681 let accounts = unsafe {
684 core::slice::from_raw_parts(accounts.as_ptr() as *const InstructionAccount<'a>, total)
685 };
686 let views =
688 unsafe { core::slice::from_raw_parts(views.as_ptr() as *const &'a AccountView<'a>, total) };
689
690 let instruction = InstructionView {
691 program_id: program,
692 data,
693 accounts,
694 };
695 crate::cpi::invoke_signed_with_bounds::<{ crate::cpi::MAX_STATIC_CPI_ACCOUNTS }>(
696 &instruction,
697 views,
698 signer_seeds,
699 )
700}
701
702#[inline]
722pub fn require_token_authority(
723 token_account: &AccountView<'_>,
724 authority: &AccountView<'_>,
725) -> ProgramResult {
726 let data = token_account
730 .try_borrow()
731 .map_err(|_| ProgramError::AccountBorrowFailed)?;
732 if data.len() < 64 {
733 return Err(ProgramError::AccountDataTooSmall);
734 }
735 if crate::address::keys_eq_bytes(&data[32..64], authority.address().as_array()) {
737 Ok(())
738 } else {
739 Err(ProgramError::IncorrectAuthority)
740 }
741}
742
743#[inline]
755pub fn require_token_owner_eq(
756 token_account: &AccountView<'_>,
757 expected_owner: &Address,
758) -> ProgramResult {
759 let data = token_account
760 .try_borrow()
761 .map_err(|_| ProgramError::AccountBorrowFailed)?;
762 if data.len() < 64 {
763 return Err(ProgramError::AccountDataTooSmall);
764 }
765 if crate::address::keys_eq_bytes(&data[32..64], expected_owner.as_array()) {
767 Ok(())
768 } else {
769 Err(ProgramError::IncorrectAuthority)
770 }
771}
772
773#[inline]
792pub fn require_token_mint(
793 token_account: &AccountView<'_>,
794 expected_mint: &Address,
795) -> ProgramResult {
796 let data = token_account
797 .try_borrow()
798 .map_err(|_| ProgramError::AccountBorrowFailed)?;
799 if data.len() < 32 {
800 return Err(ProgramError::AccountDataTooSmall);
801 }
802 if crate::address::keys_eq_bytes(&data[0..32], expected_mint.as_array()) {
804 Ok(())
805 } else {
806 Err(ProgramError::InvalidAccountData)
807 }
808}
809
810#[inline]
829pub fn require_mint_authority(
830 mint_account: &AccountView<'_>,
831 expected_authority: &Address,
832) -> ProgramResult {
833 let data = mint_account
834 .try_borrow()
835 .map_err(|_| ProgramError::AccountBorrowFailed)?;
836 if data.len() < 46 {
837 return Err(ProgramError::AccountDataTooSmall);
838 }
839 let tag = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
840 if tag != 1 {
841 return Err(ProgramError::InvalidAccountData);
843 }
844 if crate::address::keys_eq_bytes(&data[4..36], expected_authority.as_array()) {
846 Ok(())
847 } else {
848 Err(ProgramError::IncorrectAuthority)
849 }
850}
851
852#[inline]
858pub fn require_mint_decimals(mint_account: &AccountView<'_>, expected: u8) -> ProgramResult {
859 let data = mint_account
860 .try_borrow()
861 .map_err(|_| ProgramError::AccountBorrowFailed)?;
862 if data.len() < 45 {
863 return Err(ProgramError::AccountDataTooSmall);
864 }
865 if data[44] == expected {
866 Ok(())
867 } else {
868 Err(ProgramError::InvalidAccountData)
869 }
870}
871
872#[inline]
880pub fn require_mint_freeze_authority(
881 mint_account: &AccountView<'_>,
882 expected_freeze: &Address,
883) -> ProgramResult {
884 let data = mint_account
885 .try_borrow()
886 .map_err(|_| ProgramError::AccountBorrowFailed)?;
887 if data.len() < 82 {
888 return Err(ProgramError::AccountDataTooSmall);
889 }
890 let tag = u32::from_le_bytes([data[46], data[47], data[48], data[49]]);
891 if tag != 1 {
892 return Err(ProgramError::InvalidAccountData);
893 }
894 if crate::address::keys_eq_bytes(&data[50..82], expected_freeze.as_array()) {
896 Ok(())
897 } else {
898 Err(ProgramError::IncorrectAuthority)
899 }
900}
901
902#[deprecated(
913 since = "0.2.0",
914 note = "use TransferChecked for explicit classic SPL mint and decimals validation"
915)]
916#[cfg(feature = "legacy-token-instructions")]
917pub struct Transfer<'a> {
918 pub from: &'a AccountView<'a>,
919 pub to: &'a AccountView<'a>,
920 pub authority: &'a AccountView<'a>,
921 pub amount: u64,
922}
923
924#[allow(deprecated)]
925#[cfg(feature = "legacy-token-instructions")]
926impl Transfer<'_> {
927 #[inline]
931 pub fn invoke(&self) -> ProgramResult {
932 require_authority_signed_direct(self.authority)?;
933 self.emit(&[], &mut Invoke::legacy(&[]))
934 }
935
936 #[inline]
939 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
940 self.emit(&[], &mut Invoke::legacy(signers))
941 }
942}
943
944#[allow(deprecated)]
945#[cfg(feature = "legacy-token-instructions")]
946impl<'a, 'x: 'a> TokenInstruction<'a> for Transfer<'x> {
947 #[inline(always)]
948 fn emit(
949 &self,
950 multisig_signers: &[&'a AccountView<'a>],
951 sink: &mut impl TokenSink<'a>,
952 ) -> ProgramResult {
953 let data = encoders::encode_transfer(self.amount);
954
955 let accounts = [
956 InstructionAccount::writable(self.from.address()),
957 InstructionAccount::writable(self.to.address()),
958 InstructionAccount::readonly_signer(self.authority.address()),
959 ];
960 let views = [self.from, self.to, self.authority];
961 sink.emit(
962 &data,
963 accounts,
964 views,
965 &[Trailing::signers(multisig_signers)],
966 )
967 }
968}
969
970#[allow(deprecated)]
971#[cfg(feature = "legacy-token-instructions")]
972token_program_methods!(Transfer, owner = from, authority = authority);
973
974#[deprecated(
980 since = "0.2.0",
981 note = "use MintToChecked for explicit classic SPL mint and decimals validation"
982)]
983#[cfg(feature = "legacy-token-instructions")]
984pub struct MintTo<'a> {
985 pub mint: &'a AccountView<'a>,
986 pub account: &'a AccountView<'a>,
987 pub mint_authority: &'a AccountView<'a>,
988 pub amount: u64,
989}
990
991#[allow(deprecated)]
992#[cfg(feature = "legacy-token-instructions")]
993impl MintTo<'_> {
994 #[inline]
995 pub fn invoke(&self) -> ProgramResult {
996 require_authority_signed_direct(self.mint_authority)?;
997 self.emit(&[], &mut Invoke::legacy(&[]))
998 }
999
1000 #[inline]
1001 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1002 self.emit(&[], &mut Invoke::legacy(signers))
1003 }
1004}
1005
1006#[allow(deprecated)]
1007#[cfg(feature = "legacy-token-instructions")]
1008impl<'a, 'x: 'a> TokenInstruction<'a> for MintTo<'x> {
1009 #[inline(always)]
1010 fn emit(
1011 &self,
1012 multisig_signers: &[&'a AccountView<'a>],
1013 sink: &mut impl TokenSink<'a>,
1014 ) -> ProgramResult {
1015 let data = encoders::encode_mint_to(self.amount);
1016
1017 let accounts = [
1018 InstructionAccount::writable(self.mint.address()),
1019 InstructionAccount::writable(self.account.address()),
1020 InstructionAccount::readonly_signer(self.mint_authority.address()),
1021 ];
1022 let views = [self.mint, self.account, self.mint_authority];
1023 sink.emit(
1024 &data,
1025 accounts,
1026 views,
1027 &[Trailing::signers(multisig_signers)],
1028 )
1029 }
1030}
1031
1032#[allow(deprecated)]
1033#[cfg(feature = "legacy-token-instructions")]
1034token_program_methods!(MintTo, owner = mint, authority = mint_authority);
1035
1036#[deprecated(
1042 since = "0.2.0",
1043 note = "use BurnChecked for explicit classic SPL mint and decimals validation"
1044)]
1045#[cfg(feature = "legacy-token-instructions")]
1046pub struct Burn<'a> {
1047 pub account: &'a AccountView<'a>,
1048 pub mint: &'a AccountView<'a>,
1049 pub authority: &'a AccountView<'a>,
1050 pub amount: u64,
1051}
1052
1053#[allow(deprecated)]
1054#[cfg(feature = "legacy-token-instructions")]
1055impl Burn<'_> {
1056 #[inline]
1057 pub fn invoke(&self) -> ProgramResult {
1058 require_authority_signed_direct(self.authority)?;
1059 self.emit(&[], &mut Invoke::legacy(&[]))
1060 }
1061
1062 #[inline]
1063 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1064 self.emit(&[], &mut Invoke::legacy(signers))
1065 }
1066}
1067
1068#[allow(deprecated)]
1069#[cfg(feature = "legacy-token-instructions")]
1070impl<'a, 'x: 'a> TokenInstruction<'a> for Burn<'x> {
1071 #[inline(always)]
1072 fn emit(
1073 &self,
1074 multisig_signers: &[&'a AccountView<'a>],
1075 sink: &mut impl TokenSink<'a>,
1076 ) -> ProgramResult {
1077 let data = encoders::encode_burn(self.amount);
1078
1079 let accounts = [
1080 InstructionAccount::writable(self.account.address()),
1081 InstructionAccount::writable(self.mint.address()),
1082 InstructionAccount::readonly_signer(self.authority.address()),
1083 ];
1084 let views = [self.account, self.mint, self.authority];
1085 sink.emit(
1086 &data,
1087 accounts,
1088 views,
1089 &[Trailing::signers(multisig_signers)],
1090 )
1091 }
1092}
1093
1094#[allow(deprecated)]
1095#[cfg(feature = "legacy-token-instructions")]
1096token_program_methods!(Burn, owner = account, authority = authority);
1097
1098pub struct CloseAccount<'a> {
1102 pub account: &'a AccountView<'a>,
1103 pub destination: &'a AccountView<'a>,
1104 pub authority: &'a AccountView<'a>,
1105}
1106
1107impl CloseAccount<'_> {
1108 #[inline]
1109 pub fn invoke(&self) -> ProgramResult {
1110 require_authority_signed_direct(self.authority)?;
1111 self.invoke_signed(&[])
1112 }
1113
1114 #[inline]
1115 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1116 self.emit(&[], &mut Invoke::legacy(signers))
1117 }
1118
1119 #[inline]
1120 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
1121 require_multisig_signers_direct(multisig_signers)?;
1122 self.invoke_signed_multisig(multisig_signers, &[])
1123 }
1124
1125 #[inline]
1126 pub fn invoke_signed_multisig(
1127 &self,
1128 multisig_signers: &[&AccountView<'_>],
1129 signers: &[Signer<'_, '_>],
1130 ) -> ProgramResult {
1131 self.emit(multisig_signers, &mut Invoke::legacy(signers))
1132 }
1133}
1134
1135impl<'a, 'x: 'a> TokenInstruction<'a> for CloseAccount<'x> {
1136 #[inline(always)]
1137 fn emit(
1138 &self,
1139 multisig_signers: &[&'a AccountView<'a>],
1140 sink: &mut impl TokenSink<'a>,
1141 ) -> ProgramResult {
1142 let data = encoders::encode_close_account();
1143 let accounts = [
1144 InstructionAccount::writable(self.account.address()),
1145 InstructionAccount::writable(self.destination.address()),
1146 authority_meta(self.authority, multisig_signers),
1147 ];
1148 let views = [self.account, self.destination, self.authority];
1149 sink.emit(
1150 &data,
1151 accounts,
1152 views,
1153 &[Trailing::signers(multisig_signers)],
1154 )
1155 }
1156}
1157
1158token_program_methods!(CloseAccount, owner = account, authority = authority);
1159
1160#[deprecated(
1166 since = "0.2.0",
1167 note = "use ApproveChecked for explicit classic SPL mint and decimals validation"
1168)]
1169#[cfg(feature = "legacy-token-instructions")]
1170pub struct Approve<'a> {
1171 pub source: &'a AccountView<'a>,
1172 pub delegate: &'a AccountView<'a>,
1173 pub authority: &'a AccountView<'a>,
1174 pub amount: u64,
1175}
1176
1177#[allow(deprecated)]
1178#[cfg(feature = "legacy-token-instructions")]
1179impl Approve<'_> {
1180 #[inline]
1181 pub fn invoke(&self) -> ProgramResult {
1182 require_authority_signed_direct(self.authority)?;
1183 self.emit(&[], &mut Invoke::legacy(&[]))
1184 }
1185
1186 #[inline]
1187 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1188 self.emit(&[], &mut Invoke::legacy(signers))
1189 }
1190}
1191
1192#[allow(deprecated)]
1193#[cfg(feature = "legacy-token-instructions")]
1194impl<'a, 'x: 'a> TokenInstruction<'a> for Approve<'x> {
1195 #[inline(always)]
1196 fn emit(
1197 &self,
1198 multisig_signers: &[&'a AccountView<'a>],
1199 sink: &mut impl TokenSink<'a>,
1200 ) -> ProgramResult {
1201 let data = encoders::encode_approve(self.amount);
1202
1203 let accounts = [
1204 InstructionAccount::writable(self.source.address()),
1205 InstructionAccount::readonly(self.delegate.address()),
1206 InstructionAccount::readonly_signer(self.authority.address()),
1207 ];
1208 let views = [self.source, self.delegate, self.authority];
1209 sink.emit(
1210 &data,
1211 accounts,
1212 views,
1213 &[Trailing::signers(multisig_signers)],
1214 )
1215 }
1216}
1217
1218#[allow(deprecated)]
1219#[cfg(feature = "legacy-token-instructions")]
1220token_program_methods!(Approve, owner = source, authority = authority);
1221
1222pub struct Revoke<'a> {
1226 pub source: &'a AccountView<'a>,
1227 pub authority: &'a AccountView<'a>,
1228}
1229
1230impl Revoke<'_> {
1231 #[inline]
1232 pub fn invoke(&self) -> ProgramResult {
1233 require_authority_signed_direct(self.authority)?;
1234 self.invoke_signed(&[])
1235 }
1236
1237 #[inline]
1238 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1239 self.emit(&[], &mut Invoke::legacy(signers))
1240 }
1241
1242 #[inline]
1243 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
1244 require_multisig_signers_direct(multisig_signers)?;
1245 self.invoke_signed_multisig(multisig_signers, &[])
1246 }
1247
1248 #[inline]
1249 pub fn invoke_signed_multisig(
1250 &self,
1251 multisig_signers: &[&AccountView<'_>],
1252 signers: &[Signer<'_, '_>],
1253 ) -> ProgramResult {
1254 self.emit(multisig_signers, &mut Invoke::legacy(signers))
1255 }
1256}
1257
1258impl<'a, 'x: 'a> TokenInstruction<'a> for Revoke<'x> {
1259 #[inline(always)]
1260 fn emit(
1261 &self,
1262 multisig_signers: &[&'a AccountView<'a>],
1263 sink: &mut impl TokenSink<'a>,
1264 ) -> ProgramResult {
1265 let data = encoders::encode_revoke();
1266 let accounts = [
1267 InstructionAccount::writable(self.source.address()),
1268 authority_meta(self.authority, multisig_signers),
1269 ];
1270 let views = [self.source, self.authority];
1271 sink.emit(
1272 &data,
1273 accounts,
1274 views,
1275 &[Trailing::signers(multisig_signers)],
1276 )
1277 }
1278}
1279
1280token_program_methods!(Revoke, owner = source, authority = authority);
1281
1282pub struct TransferChecked<'a> {
1291 pub from: &'a AccountView<'a>,
1292 pub mint: &'a AccountView<'a>,
1293 pub to: &'a AccountView<'a>,
1294 pub authority: &'a AccountView<'a>,
1295 pub amount: u64,
1296 pub decimals: u8,
1297}
1298
1299impl TransferChecked<'_> {
1300 #[inline]
1304 pub fn invoke(&self) -> ProgramResult {
1305 require_authority_signed_direct(self.authority)?;
1306 self.invoke_signed_unchecked(&[])
1307 }
1308
1309 #[inline]
1318 pub fn invoke_strict(&self) -> ProgramResult {
1319 require_authority_signed_direct(self.authority)?;
1320 require_token_authority(self.from, self.authority)?;
1321 self.invoke_signed_unchecked(&[])
1322 }
1323
1324 #[inline]
1327 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1328 self.invoke_signed_unchecked(signers)
1329 }
1330
1331 #[inline]
1334 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
1335 require_multisig_signers_direct(multisig_signers)?;
1336 self.invoke_signed_multisig(multisig_signers, &[])
1337 }
1338
1339 #[inline]
1341 pub fn invoke_signed_multisig(
1342 &self,
1343 multisig_signers: &[&AccountView<'_>],
1344 signers: &[Signer<'_, '_>],
1345 ) -> ProgramResult {
1346 self.emit(multisig_signers, &mut Invoke::legacy(signers))
1347 }
1348
1349 #[inline]
1353 pub fn invoke_signed_strict(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1354 require_token_authority(self.from, self.authority)?;
1355 self.invoke_signed_unchecked(signers)
1356 }
1357
1358 #[inline(always)]
1359 fn invoke_signed_unchecked(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1360 self.emit(&[], &mut Invoke::legacy(signers))
1361 }
1362}
1363
1364impl<'a, 'x: 'a> TokenInstruction<'a> for TransferChecked<'x> {
1365 #[inline(always)]
1366 fn emit(
1367 &self,
1368 multisig_signers: &[&'a AccountView<'a>],
1369 sink: &mut impl TokenSink<'a>,
1370 ) -> ProgramResult {
1371 let data = encoders::encode_transfer_checked(self.amount, self.decimals);
1372
1373 let accounts = [
1374 InstructionAccount::writable(self.from.address()),
1375 InstructionAccount::readonly(self.mint.address()),
1376 InstructionAccount::writable(self.to.address()),
1377 authority_meta(self.authority, multisig_signers),
1378 ];
1379 let views = [self.from, self.mint, self.to, self.authority];
1380 sink.emit(
1381 &data,
1382 accounts,
1383 views,
1384 &[Trailing::signers(multisig_signers)],
1385 )
1386 }
1387}
1388
1389token_program_methods!(TransferChecked, owner = from, authority = authority);
1390
1391pub struct MintToChecked<'a> {
1398 pub mint: &'a AccountView<'a>,
1399 pub account: &'a AccountView<'a>,
1400 pub mint_authority: &'a AccountView<'a>,
1401 pub amount: u64,
1402 pub decimals: u8,
1403}
1404
1405impl MintToChecked<'_> {
1406 #[inline]
1407 pub fn invoke(&self) -> ProgramResult {
1408 require_authority_signed_direct(self.mint_authority)?;
1409 self.invoke_signed_unchecked(&[])
1410 }
1411
1412 #[inline]
1413 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1414 self.invoke_signed_unchecked(signers)
1415 }
1416
1417 #[inline]
1418 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
1419 require_multisig_signers_direct(multisig_signers)?;
1420 self.invoke_signed_multisig(multisig_signers, &[])
1421 }
1422
1423 #[inline]
1424 pub fn invoke_signed_multisig(
1425 &self,
1426 multisig_signers: &[&AccountView<'_>],
1427 signers: &[Signer<'_, '_>],
1428 ) -> ProgramResult {
1429 self.emit(multisig_signers, &mut Invoke::legacy(signers))
1430 }
1431
1432 #[inline(always)]
1433 fn invoke_signed_unchecked(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1434 self.emit(&[], &mut Invoke::legacy(signers))
1435 }
1436}
1437
1438impl<'a, 'x: 'a> TokenInstruction<'a> for MintToChecked<'x> {
1439 #[inline(always)]
1440 fn emit(
1441 &self,
1442 multisig_signers: &[&'a AccountView<'a>],
1443 sink: &mut impl TokenSink<'a>,
1444 ) -> ProgramResult {
1445 let data = encoders::encode_mint_to_checked(self.amount, self.decimals);
1446
1447 let accounts = [
1448 InstructionAccount::writable(self.mint.address()),
1449 InstructionAccount::writable(self.account.address()),
1450 authority_meta(self.mint_authority, multisig_signers),
1451 ];
1452 let views = [self.mint, self.account, self.mint_authority];
1453 sink.emit(
1454 &data,
1455 accounts,
1456 views,
1457 &[Trailing::signers(multisig_signers)],
1458 )
1459 }
1460}
1461
1462token_program_methods!(MintToChecked, owner = mint, authority = mint_authority);
1463
1464pub struct BurnChecked<'a> {
1472 pub account: &'a AccountView<'a>,
1473 pub mint: &'a AccountView<'a>,
1474 pub authority: &'a AccountView<'a>,
1475 pub amount: u64,
1476 pub decimals: u8,
1477}
1478
1479impl BurnChecked<'_> {
1480 #[inline]
1481 pub fn invoke(&self) -> ProgramResult {
1482 require_authority_signed_direct(self.authority)?;
1483 self.invoke_signed_unchecked(&[])
1484 }
1485
1486 #[inline]
1490 pub fn invoke_strict(&self) -> ProgramResult {
1491 require_authority_signed_direct(self.authority)?;
1492 require_token_authority(self.account, self.authority)?;
1493 self.invoke_signed_unchecked(&[])
1494 }
1495
1496 #[inline]
1497 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1498 self.invoke_signed_unchecked(signers)
1499 }
1500
1501 #[inline]
1502 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
1503 require_multisig_signers_direct(multisig_signers)?;
1504 self.invoke_signed_multisig(multisig_signers, &[])
1505 }
1506
1507 #[inline]
1508 pub fn invoke_signed_multisig(
1509 &self,
1510 multisig_signers: &[&AccountView<'_>],
1511 signers: &[Signer<'_, '_>],
1512 ) -> ProgramResult {
1513 self.emit(multisig_signers, &mut Invoke::legacy(signers))
1514 }
1515
1516 #[inline]
1520 pub fn invoke_signed_strict(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1521 require_token_authority(self.account, self.authority)?;
1522 self.invoke_signed_unchecked(signers)
1523 }
1524
1525 #[inline(always)]
1526 fn invoke_signed_unchecked(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1527 self.emit(&[], &mut Invoke::legacy(signers))
1528 }
1529}
1530
1531impl<'a, 'x: 'a> TokenInstruction<'a> for BurnChecked<'x> {
1532 #[inline(always)]
1533 fn emit(
1534 &self,
1535 multisig_signers: &[&'a AccountView<'a>],
1536 sink: &mut impl TokenSink<'a>,
1537 ) -> ProgramResult {
1538 let data = encoders::encode_burn_checked(self.amount, self.decimals);
1539
1540 let accounts = [
1541 InstructionAccount::writable(self.account.address()),
1542 InstructionAccount::writable(self.mint.address()),
1543 authority_meta(self.authority, multisig_signers),
1544 ];
1545 let views = [self.account, self.mint, self.authority];
1546 sink.emit(
1547 &data,
1548 accounts,
1549 views,
1550 &[Trailing::signers(multisig_signers)],
1551 )
1552 }
1553}
1554
1555token_program_methods!(BurnChecked, owner = account, authority = authority);
1556
1557pub struct ApproveChecked<'a> {
1564 pub source: &'a AccountView<'a>,
1565 pub mint: &'a AccountView<'a>,
1566 pub delegate: &'a AccountView<'a>,
1567 pub authority: &'a AccountView<'a>,
1568 pub amount: u64,
1569 pub decimals: u8,
1570}
1571
1572impl ApproveChecked<'_> {
1573 #[inline]
1574 pub fn invoke(&self) -> ProgramResult {
1575 require_authority_signed_direct(self.authority)?;
1576 self.invoke_signed_unchecked(&[])
1577 }
1578
1579 #[inline]
1584 pub fn invoke_strict(&self) -> ProgramResult {
1585 require_authority_signed_direct(self.authority)?;
1586 require_token_authority(self.source, self.authority)?;
1587 self.invoke_signed_unchecked(&[])
1588 }
1589
1590 #[inline]
1591 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1592 self.invoke_signed_unchecked(signers)
1593 }
1594
1595 #[inline]
1596 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
1597 require_multisig_signers_direct(multisig_signers)?;
1598 self.invoke_signed_multisig(multisig_signers, &[])
1599 }
1600
1601 #[inline]
1602 pub fn invoke_signed_multisig(
1603 &self,
1604 multisig_signers: &[&AccountView<'_>],
1605 signers: &[Signer<'_, '_>],
1606 ) -> ProgramResult {
1607 self.emit(multisig_signers, &mut Invoke::legacy(signers))
1608 }
1609
1610 #[inline]
1613 pub fn invoke_signed_strict(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1614 require_token_authority(self.source, self.authority)?;
1615 self.invoke_signed_unchecked(signers)
1616 }
1617
1618 #[inline(always)]
1619 fn invoke_signed_unchecked(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1620 self.emit(&[], &mut Invoke::legacy(signers))
1621 }
1622}
1623
1624impl<'a, 'x: 'a> TokenInstruction<'a> for ApproveChecked<'x> {
1625 #[inline(always)]
1626 fn emit(
1627 &self,
1628 multisig_signers: &[&'a AccountView<'a>],
1629 sink: &mut impl TokenSink<'a>,
1630 ) -> ProgramResult {
1631 let data = encoders::encode_approve_checked(self.amount, self.decimals);
1632
1633 let accounts = [
1634 InstructionAccount::writable(self.source.address()),
1635 InstructionAccount::readonly(self.mint.address()),
1636 InstructionAccount::readonly(self.delegate.address()),
1637 authority_meta(self.authority, multisig_signers),
1638 ];
1639 let views = [self.source, self.mint, self.delegate, self.authority];
1640 sink.emit(
1641 &data,
1642 accounts,
1643 views,
1644 &[Trailing::signers(multisig_signers)],
1645 )
1646 }
1647}
1648
1649token_program_methods!(ApproveChecked, owner = source, authority = authority);
1650
1651#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1655#[repr(u8)]
1656pub enum TokenAuthorityType {
1657 MintTokens = 0,
1658 FreezeAccount = 1,
1659 AccountOwner = 2,
1660 CloseAccount = 3,
1661}
1662
1663pub struct SetAuthority<'a> {
1665 pub account: &'a AccountView<'a>,
1666 pub current_authority: &'a AccountView<'a>,
1667 pub authority_type: TokenAuthorityType,
1668 pub new_authority: Option<&'a Address>,
1669}
1670
1671impl SetAuthority<'_> {
1672 #[inline]
1673 pub fn invoke(&self) -> ProgramResult {
1674 require_authority_signed_direct(self.current_authority)?;
1675 self.emit(&[], &mut Invoke::legacy(&[]))
1676 }
1677
1678 #[inline]
1679 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1680 self.emit(&[], &mut Invoke::legacy(signers))
1681 }
1682
1683 #[inline]
1684 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
1685 require_multisig_signers_direct(multisig_signers)?;
1686 self.invoke_signed_multisig(multisig_signers, &[])
1687 }
1688
1689 #[inline]
1690 pub fn invoke_signed_multisig(
1691 &self,
1692 multisig_signers: &[&AccountView<'_>],
1693 signers: &[Signer<'_, '_>],
1694 ) -> ProgramResult {
1695 self.emit(multisig_signers, &mut Invoke::legacy(signers))
1696 }
1697}
1698
1699impl<'a, 'x: 'a> TokenInstruction<'a> for SetAuthority<'x> {
1700 #[inline(always)]
1701 fn emit(
1702 &self,
1703 multisig_signers: &[&'a AccountView<'a>],
1704 sink: &mut impl TokenSink<'a>,
1705 ) -> ProgramResult {
1706 let (data, len) = encoders::encode_set_authority(
1707 self.authority_type as u8,
1708 self.new_authority.map(|a| a.as_array()),
1709 );
1710 let accounts = [
1711 InstructionAccount::writable(self.account.address()),
1712 authority_meta(self.current_authority, multisig_signers),
1713 ];
1714 let views = [self.account, self.current_authority];
1715 sink.emit(
1716 &data[..len],
1717 accounts,
1718 views,
1719 &[Trailing::signers(multisig_signers)],
1720 )
1721 }
1722}
1723
1724token_program_methods!(SetAuthority, owner = account, authority = current_authority);
1725
1726pub struct FreezeAccount<'a> {
1730 pub account: &'a AccountView<'a>,
1731 pub mint: &'a AccountView<'a>,
1732 pub freeze_authority: &'a AccountView<'a>,
1733}
1734
1735impl FreezeAccount<'_> {
1736 #[inline]
1737 pub fn invoke(&self) -> ProgramResult {
1738 require_authority_signed_direct(self.freeze_authority)?;
1739 self.emit(&[], &mut Invoke::legacy(&[]))
1740 }
1741
1742 #[inline]
1743 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1744 self.emit(&[], &mut Invoke::legacy(signers))
1745 }
1746
1747 #[inline]
1748 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
1749 require_multisig_signers_direct(multisig_signers)?;
1750 self.invoke_signed_multisig(multisig_signers, &[])
1751 }
1752
1753 #[inline]
1754 pub fn invoke_signed_multisig(
1755 &self,
1756 multisig_signers: &[&AccountView<'_>],
1757 signers: &[Signer<'_, '_>],
1758 ) -> ProgramResult {
1759 self.emit(multisig_signers, &mut Invoke::legacy(signers))
1760 }
1761}
1762
1763impl<'a, 'x: 'a> TokenInstruction<'a> for FreezeAccount<'x> {
1764 #[inline(always)]
1765 fn emit(
1766 &self,
1767 multisig_signers: &[&'a AccountView<'a>],
1768 sink: &mut impl TokenSink<'a>,
1769 ) -> ProgramResult {
1770 let data = encoders::encode_freeze_account();
1771 let accounts = [
1772 InstructionAccount::writable(self.account.address()),
1773 InstructionAccount::readonly(self.mint.address()),
1774 authority_meta(self.freeze_authority, multisig_signers),
1775 ];
1776 let views = [self.account, self.mint, self.freeze_authority];
1777 sink.emit(
1778 &data,
1779 accounts,
1780 views,
1781 &[Trailing::signers(multisig_signers)],
1782 )
1783 }
1784}
1785
1786token_program_methods!(FreezeAccount, owner = account, authority = freeze_authority);
1787
1788pub struct ThawAccount<'a> {
1790 pub account: &'a AccountView<'a>,
1791 pub mint: &'a AccountView<'a>,
1792 pub freeze_authority: &'a AccountView<'a>,
1793}
1794
1795impl ThawAccount<'_> {
1796 #[inline]
1797 pub fn invoke(&self) -> ProgramResult {
1798 require_authority_signed_direct(self.freeze_authority)?;
1799 self.emit(&[], &mut Invoke::legacy(&[]))
1800 }
1801
1802 #[inline]
1803 pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
1804 self.emit(&[], &mut Invoke::legacy(signers))
1805 }
1806
1807 #[inline]
1808 pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
1809 require_multisig_signers_direct(multisig_signers)?;
1810 self.invoke_signed_multisig(multisig_signers, &[])
1811 }
1812
1813 #[inline]
1814 pub fn invoke_signed_multisig(
1815 &self,
1816 multisig_signers: &[&AccountView<'_>],
1817 signers: &[Signer<'_, '_>],
1818 ) -> ProgramResult {
1819 self.emit(multisig_signers, &mut Invoke::legacy(signers))
1820 }
1821}
1822
1823impl<'a, 'x: 'a> TokenInstruction<'a> for ThawAccount<'x> {
1824 #[inline(always)]
1825 fn emit(
1826 &self,
1827 multisig_signers: &[&'a AccountView<'a>],
1828 sink: &mut impl TokenSink<'a>,
1829 ) -> ProgramResult {
1830 let data = encoders::encode_thaw_account();
1831 let accounts = [
1832 InstructionAccount::writable(self.account.address()),
1833 InstructionAccount::readonly(self.mint.address()),
1834 authority_meta(self.freeze_authority, multisig_signers),
1835 ];
1836 let views = [self.account, self.mint, self.freeze_authority];
1837 sink.emit(
1838 &data,
1839 accounts,
1840 views,
1841 &[Trailing::signers(multisig_signers)],
1842 )
1843 }
1844}
1845
1846token_program_methods!(ThawAccount, owner = account, authority = freeze_authority);
1847
1848pub struct SyncNative<'a> {
1852 pub account: &'a AccountView<'a>,
1853}
1854
1855impl SyncNative<'_> {
1856 #[inline]
1857 pub fn invoke(&self) -> ProgramResult {
1858 self.emit(&[], &mut Invoke::legacy(&[]))
1859 }
1860}
1861
1862impl<'a, 'x: 'a> TokenInstruction<'a> for SyncNative<'x> {
1863 #[inline(always)]
1864 fn emit(
1865 &self,
1866 multisig_signers: &[&'a AccountView<'a>],
1867 sink: &mut impl TokenSink<'a>,
1868 ) -> ProgramResult {
1869 let data = encoders::encode_sync_native();
1870 let accounts = [InstructionAccount::writable(self.account.address())];
1871 let views = [self.account];
1872 sink.emit(
1873 &data,
1874 accounts,
1875 views,
1876 &[Trailing::signers(multisig_signers)],
1877 )
1878 }
1879}
1880
1881token_program_methods!(SyncNative, owner = account);
1882
1883pub struct InitializeAccount<'a> {
1887 pub account: &'a AccountView<'a>,
1888 pub mint: &'a AccountView<'a>,
1889 pub owner: &'a AccountView<'a>,
1890 pub rent_sysvar: &'a AccountView<'a>,
1891}
1892
1893impl InitializeAccount<'_> {
1894 #[inline]
1895 pub fn invoke(&self) -> ProgramResult {
1896 self.emit(&[], &mut Invoke::legacy(&[]))
1897 }
1898}
1899
1900impl<'a, 'x: 'a> TokenInstruction<'a> for InitializeAccount<'x> {
1901 #[inline(always)]
1902 fn emit(
1903 &self,
1904 multisig_signers: &[&'a AccountView<'a>],
1905 sink: &mut impl TokenSink<'a>,
1906 ) -> ProgramResult {
1907 let data = encoders::encode_initialize_account();
1908 let accounts = [
1909 InstructionAccount::writable(self.account.address()),
1910 InstructionAccount::readonly(self.mint.address()),
1911 InstructionAccount::readonly(self.owner.address()),
1912 InstructionAccount::readonly(self.rent_sysvar.address()),
1913 ];
1914 let views = [self.account, self.mint, self.owner, self.rent_sysvar];
1915 sink.emit(
1916 &data,
1917 accounts,
1918 views,
1919 &[Trailing::signers(multisig_signers)],
1920 )
1921 }
1922}
1923
1924token_program_methods!(InitializeAccount, owner = account);
1925
1926pub struct InitializeAccount2<'a> {
1928 pub account: &'a AccountView<'a>,
1929 pub mint: &'a AccountView<'a>,
1930 pub owner: &'a Address,
1931 pub rent_sysvar: &'a AccountView<'a>,
1932}
1933
1934impl InitializeAccount2<'_> {
1935 #[inline]
1936 pub fn invoke(&self) -> ProgramResult {
1937 self.emit(&[], &mut Invoke::legacy(&[]))
1938 }
1939}
1940
1941impl<'a, 'x: 'a> TokenInstruction<'a> for InitializeAccount2<'x> {
1942 #[inline(always)]
1943 fn emit(
1944 &self,
1945 multisig_signers: &[&'a AccountView<'a>],
1946 sink: &mut impl TokenSink<'a>,
1947 ) -> ProgramResult {
1948 let data = encoders::encode_initialize_account_with_owner(16, self.owner.as_array());
1949 let accounts = [
1950 InstructionAccount::writable(self.account.address()),
1951 InstructionAccount::readonly(self.mint.address()),
1952 InstructionAccount::readonly(self.rent_sysvar.address()),
1953 ];
1954 let views = [self.account, self.mint, self.rent_sysvar];
1955 sink.emit(
1956 &data,
1957 accounts,
1958 views,
1959 &[Trailing::signers(multisig_signers)],
1960 )
1961 }
1962}
1963
1964token_program_methods!(InitializeAccount2, owner = account);
1965
1966pub struct InitializeAccount3<'a> {
1968 pub account: &'a AccountView<'a>,
1969 pub mint: &'a AccountView<'a>,
1970 pub owner: &'a Address,
1971}
1972
1973impl InitializeAccount3<'_> {
1974 #[inline]
1975 pub fn invoke(&self) -> ProgramResult {
1976 self.emit(&[], &mut Invoke::legacy(&[]))
1977 }
1978}
1979
1980impl<'a, 'x: 'a> TokenInstruction<'a> for InitializeAccount3<'x> {
1981 #[inline(always)]
1982 fn emit(
1983 &self,
1984 multisig_signers: &[&'a AccountView<'a>],
1985 sink: &mut impl TokenSink<'a>,
1986 ) -> ProgramResult {
1987 let data = encoders::encode_initialize_account_with_owner(18, self.owner.as_array());
1988 let accounts = [
1989 InstructionAccount::writable(self.account.address()),
1990 InstructionAccount::readonly(self.mint.address()),
1991 ];
1992 let views = [self.account, self.mint];
1993 sink.emit(
1994 &data,
1995 accounts,
1996 views,
1997 &[Trailing::signers(multisig_signers)],
1998 )
1999 }
2000}
2001
2002token_program_methods!(InitializeAccount3, owner = account);
2003
2004pub const TOKEN_PROGRAM_ID: Address = Address::new_from_array(crate::__decode_base58_32(
2006 "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
2007));
2008
2009pub const SPL_TOKEN_ACCOUNT_LEN: usize = 165;
2012pub const SPL_MINT_LEN: usize = 82;
2013
2014const TOKEN_ACCOUNT_MINT_OFFSET: usize = 0;
2015const TOKEN_ACCOUNT_AUTHORITY_OFFSET: usize = 32;
2016const TOKEN_ACCOUNT_AMOUNT_OFFSET: usize = 64;
2017const TOKEN_ACCOUNT_STATE_OFFSET: usize = 108;
2018
2019const MINT_AUTHORITY_TAG_OFFSET: usize = 0;
2020const MINT_AUTHORITY_OFFSET: usize = 4;
2021const MINT_SUPPLY_OFFSET: usize = 36;
2022const MINT_DECIMALS_OFFSET: usize = 44;
2023const MINT_INITIALIZED_OFFSET: usize = 45;
2024const MINT_FREEZE_AUTHORITY_TAG_OFFSET: usize = 46;
2025const MINT_FREEZE_AUTHORITY_OFFSET: usize = 50;
2026
2027pub struct SplTokenAccount;
2029
2030pub struct SplTokenAccountView<'a> {
2032 data: Ref<'a, [u8]>,
2033}
2034
2035impl SplTokenAccountView<'_> {
2036 #[inline(always)]
2037 pub fn mint(&self) -> Address {
2038 read_address_unchecked(&self.data, TOKEN_ACCOUNT_MINT_OFFSET)
2039 }
2040
2041 #[inline(always)]
2042 pub fn authority(&self) -> Address {
2043 read_address_unchecked(&self.data, TOKEN_ACCOUNT_AUTHORITY_OFFSET)
2044 }
2045
2046 #[inline(always)]
2047 pub fn amount(&self) -> u64 {
2048 read_u64_unchecked(&self.data, TOKEN_ACCOUNT_AMOUNT_OFFSET)
2049 }
2050
2051 #[inline(always)]
2052 pub fn state(&self) -> u8 {
2053 self.data[TOKEN_ACCOUNT_STATE_OFFSET]
2054 }
2055
2056 #[inline(always)]
2057 pub fn is_initialized(&self) -> bool {
2058 self.state() != 0
2059 }
2060}
2061
2062impl ExternalZeroCopy for SplTokenAccount {
2063 type View<'a> = SplTokenAccountView<'a>;
2064
2065 const OWNER: Option<Address> = Some(TOKEN_PROGRAM_ID);
2066 const MIN_LEN: usize = SPL_TOKEN_ACCOUNT_LEN;
2067
2068 #[inline]
2069 fn view<'a>(data: Ref<'a, [u8]>) -> Result<Self::View<'a>, ProgramError> {
2070 Ok(SplTokenAccountView { data })
2071 }
2072}
2073
2074impl ExplainExternal for SplTokenAccount {
2075 fn explain<S: ExternalExplainSink>(account: &AccountView<'_>, sink: &mut S) -> ProgramResult {
2076 let account = ExternalAccount::<SplTokenAccount>::try_new(account)?;
2077 account.with_view(|token| {
2078 sink.field_str("adapter", "SplTokenAccount")?;
2079 sink.field_address("mint", &token.mint())?;
2080 sink.field_address("authority", &token.authority())?;
2081 sink.field_u64("amount", token.amount())?;
2082 sink.field_bool("initialized", token.is_initialized())
2083 })
2084 }
2085}
2086
2087pub struct SplMint;
2089
2090pub struct SplMintView<'a> {
2092 data: Ref<'a, [u8]>,
2093}
2094
2095impl SplMintView<'_> {
2096 #[inline(always)]
2097 pub fn mint_authority(&self) -> Option<Address> {
2098 read_coption_address(&self.data, MINT_AUTHORITY_TAG_OFFSET, MINT_AUTHORITY_OFFSET)
2099 }
2100
2101 #[inline(always)]
2102 pub fn supply(&self) -> u64 {
2103 read_u64_unchecked(&self.data, MINT_SUPPLY_OFFSET)
2104 }
2105
2106 #[inline(always)]
2107 pub fn decimals(&self) -> u8 {
2108 self.data[MINT_DECIMALS_OFFSET]
2109 }
2110
2111 #[inline(always)]
2112 pub fn is_initialized(&self) -> bool {
2113 self.data[MINT_INITIALIZED_OFFSET] != 0
2114 }
2115
2116 #[inline(always)]
2117 pub fn freeze_authority(&self) -> Option<Address> {
2118 read_coption_address(
2119 &self.data,
2120 MINT_FREEZE_AUTHORITY_TAG_OFFSET,
2121 MINT_FREEZE_AUTHORITY_OFFSET,
2122 )
2123 }
2124}
2125
2126impl ExternalZeroCopy for SplMint {
2127 type View<'a> = SplMintView<'a>;
2128
2129 const OWNER: Option<Address> = Some(TOKEN_PROGRAM_ID);
2130 const MIN_LEN: usize = SPL_MINT_LEN;
2131
2132 #[inline]
2133 fn view<'a>(data: Ref<'a, [u8]>) -> Result<Self::View<'a>, ProgramError> {
2134 Ok(SplMintView { data })
2135 }
2136}
2137
2138impl ExplainExternal for SplMint {
2139 fn explain<S: ExternalExplainSink>(account: &AccountView<'_>, sink: &mut S) -> ProgramResult {
2140 let account = ExternalAccount::<SplMint>::try_new(account)?;
2141 account.with_view(|mint| {
2142 sink.field_str("adapter", "SplMint")?;
2143 sink.field_u64("supply", mint.supply())?;
2144 sink.field_u64("decimals", mint.decimals() as u64)?;
2145 sink.field_bool("initialized", mint.is_initialized())
2146 })
2147 }
2148}
2149
2150#[derive(Debug)]
2152pub struct CheckedTokenMint<'info> {
2153 account: ExternalAccount<'info, SplTokenAccount>,
2154 mint: Address,
2155}
2156
2157impl<'info> CheckedTokenMint<'info> {
2158 #[inline(always)]
2159 pub const fn account(&self) -> ExternalAccount<'info, SplTokenAccount> {
2160 self.account
2161 }
2162
2163 #[inline(always)]
2164 pub const fn mint(&self) -> Address {
2165 self.mint
2166 }
2167}
2168
2169#[derive(Debug)]
2171pub struct CheckedTokenAuthority<'info> {
2172 account: ExternalAccount<'info, SplTokenAccount>,
2173 authority: Address,
2174}
2175
2176impl<'info> CheckedTokenAuthority<'info> {
2177 #[inline(always)]
2178 pub const fn account(&self) -> ExternalAccount<'info, SplTokenAccount> {
2179 self.account
2180 }
2181
2182 #[inline(always)]
2183 pub const fn authority(&self) -> Address {
2184 self.authority
2185 }
2186}
2187
2188#[derive(Debug)]
2190pub struct CheckedMintDecimals<'info> {
2191 account: ExternalAccount<'info, SplMint>,
2192 decimals: u8,
2193}
2194
2195impl<'info> CheckedMintDecimals<'info> {
2196 #[inline(always)]
2197 pub const fn account(&self) -> ExternalAccount<'info, SplMint> {
2198 self.account
2199 }
2200
2201 #[inline(always)]
2202 pub const fn decimals(&self) -> u8 {
2203 self.decimals
2204 }
2205}
2206
2207#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2209pub struct TokenAmountSnapshot {
2210 amount: u64,
2211}
2212
2213impl TokenAmountSnapshot {
2214 #[inline(always)]
2215 pub const fn amount(self) -> u64 {
2216 self.amount
2217 }
2218}
2219
2220impl<'info> ExternalAccount<'info, SplTokenAccount> {
2221 #[inline]
2222 pub fn token_amount(&self) -> Result<u64, ProgramError> {
2223 Ok(self.view()?.amount())
2224 }
2225
2226 #[inline]
2227 pub fn checked_mint(
2228 &self,
2229 expected_mint: &Address,
2230 ) -> Result<CheckedTokenMint<'info>, ProgramError> {
2231 let mint = self.view()?.mint();
2232 if &mint == expected_mint {
2233 Ok(CheckedTokenMint {
2234 account: *self,
2235 mint,
2236 })
2237 } else {
2238 Err(ProgramError::InvalidAccountData)
2239 }
2240 }
2241
2242 #[inline]
2243 pub fn checked_authority(
2244 &self,
2245 expected_authority: &Address,
2246 ) -> Result<CheckedTokenAuthority<'info>, ProgramError> {
2247 let authority = self.view()?.authority();
2248 if &authority == expected_authority {
2249 Ok(CheckedTokenAuthority {
2250 account: *self,
2251 authority,
2252 })
2253 } else {
2254 Err(ProgramError::IncorrectAuthority)
2255 }
2256 }
2257
2258 #[inline]
2259 pub fn amount_snapshot(&self) -> Result<TokenAmountSnapshot, ProgramError> {
2260 Ok(TokenAmountSnapshot {
2261 amount: self.token_amount()?,
2262 })
2263 }
2264
2265 #[inline]
2266 pub fn assert_amount_delta(
2267 &self,
2268 before: TokenAmountSnapshot,
2269 expected_delta: i128,
2270 ) -> ProgramResult {
2271 let after = self.token_amount()? as i128;
2272 let expected = (before.amount as i128)
2273 .checked_add(expected_delta)
2274 .ok_or(ProgramError::ArithmeticOverflow)?;
2275 if expected < 0 || expected > u64::MAX as i128 {
2276 return Err(ProgramError::ArithmeticOverflow);
2277 }
2278 if after == expected {
2279 Ok(())
2280 } else {
2281 Err(ProgramError::InvalidAccountData)
2282 }
2283 }
2284
2285 #[inline]
2286 pub fn assert_amount_unchanged(&self, before: TokenAmountSnapshot) -> ProgramResult {
2287 self.assert_amount_delta(before, 0)
2288 }
2289}
2290
2291impl<'info> ExternalAccount<'info, SplMint> {
2292 #[inline]
2293 pub fn checked_decimals(
2294 &self,
2295 expected: u8,
2296 ) -> Result<CheckedMintDecimals<'info>, ProgramError> {
2297 let decimals = self.view()?.decimals();
2298 if decimals == expected {
2299 Ok(CheckedMintDecimals {
2300 account: *self,
2301 decimals,
2302 })
2303 } else {
2304 Err(ProgramError::InvalidAccountData)
2305 }
2306 }
2307}
2308
2309#[inline(always)]
2310fn read_address_unchecked(data: &[u8], offset: usize) -> Address {
2311 let mut bytes = [0u8; 32];
2312 bytes.copy_from_slice(&data[offset..offset + 32]);
2313 Address::new_from_array(bytes)
2314}
2315
2316#[inline(always)]
2317fn read_u64_unchecked(data: &[u8], offset: usize) -> u64 {
2318 u64::from_le_bytes([
2319 data[offset],
2320 data[offset + 1],
2321 data[offset + 2],
2322 data[offset + 3],
2323 data[offset + 4],
2324 data[offset + 5],
2325 data[offset + 6],
2326 data[offset + 7],
2327 ])
2328}
2329
2330#[inline(always)]
2331fn read_u32_unchecked(data: &[u8], offset: usize) -> u32 {
2332 u32::from_le_bytes([
2333 data[offset],
2334 data[offset + 1],
2335 data[offset + 2],
2336 data[offset + 3],
2337 ])
2338}
2339
2340#[inline(always)]
2341fn read_coption_address(data: &[u8], tag_offset: usize, address_offset: usize) -> Option<Address> {
2342 match read_u32_unchecked(data, tag_offset) {
2343 1 => Some(read_address_unchecked(data, address_offset)),
2344 _ => None,
2345 }
2346}
2347
2348pub mod instructions {
2350 pub use super::{
2351 AmountToUiAmount, ApproveChecked, BurnChecked, CloseAccount, FreezeAccount,
2352 GetAccountDataSize, InitializeAccount, InitializeAccount2, InitializeAccount3,
2353 InitializeImmutableOwner, InitializeMint, InitializeMultisig, InitializeMultisig2,
2354 MintToChecked, Revoke, SetAuthority, SyncNative, ThawAccount, TokenAuthorityType,
2355 TokenBatch, TokenInstruction, TokenProgram, TransferChecked, UiAmountToAmount,
2356 UnwrapLamports, WithdrawExcessLamports,
2357 };
2358
2359 #[cfg(feature = "legacy-token-instructions")]
2360 #[allow(deprecated)]
2361 pub use super::{Approve, Burn, MintTo, Transfer};
2362}
2363
2364#[cfg(test)]
2365mod tests {
2366 use super::*;
2374 use hopper_native::{
2375 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED,
2376 };
2377 fn make_account(owner: Address, data: &[u8]) -> (std::vec::Vec<u64>, AccountView<'static>) {
2378 let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + data.len()).div_ceil(8)];
2379 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
2380 unsafe {
2383 raw.write(RuntimeAccount {
2384 borrow_state: NOT_BORROWED,
2385 is_signer: 0,
2386 is_writable: 1,
2387 executable: 0,
2388 resize_delta: 0,
2389 address: NativeAddress::new_from_array([7; 32]),
2390 owner: NativeAddress::new_from_array(owner.to_bytes()),
2391 lamports: 1,
2392 data_len: data.len() as u64,
2393 });
2394 let data_ptr = (backing.as_mut_ptr() as *mut u8).add(RuntimeAccount::SIZE);
2395 core::ptr::copy_nonoverlapping(data.as_ptr(), data_ptr, data.len());
2396 }
2397 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
2399 (backing, AccountView::from_backend(backend))
2400 }
2401 fn token_account_data(
2402 mint: Address,
2403 authority: Address,
2404 amount: u64,
2405 ) -> [u8; SPL_TOKEN_ACCOUNT_LEN] {
2406 let mut data = [0u8; SPL_TOKEN_ACCOUNT_LEN];
2407 data[0..32].copy_from_slice(mint.as_bytes());
2408 data[32..64].copy_from_slice(authority.as_bytes());
2409 data[64..72].copy_from_slice(&amount.to_le_bytes());
2410 data[108] = 1;
2411 data
2412 }
2413 fn mint_data(authority: Address, supply: u64, decimals: u8) -> [u8; SPL_MINT_LEN] {
2414 let mut data = [0u8; SPL_MINT_LEN];
2415 data[0..4].copy_from_slice(&1u32.to_le_bytes());
2416 data[4..36].copy_from_slice(authority.as_bytes());
2417 data[36..44].copy_from_slice(&supply.to_le_bytes());
2418 data[44] = decimals;
2419 data[45] = 1;
2420 data
2421 }
2422
2423 #[test]
2428 fn transfer_checked_discriminator_is_12() {
2429 }
2449 #[test]
2450 fn spl_external_token_account_view_proofs_and_amount_delta() {
2451 let mint = Address::new_from_array([2; 32]);
2452 let authority = Address::new_from_array([3; 32]);
2453 let data = token_account_data(mint, authority, 100);
2454 let (mut backing, account) = make_account(TOKEN_PROGRAM_ID, &data);
2455
2456 let token = ExternalAccount::<SplTokenAccount>::try_new(&account).unwrap();
2457 let view = token.view().unwrap();
2458 assert_eq!(view.mint(), mint);
2459 assert_eq!(view.authority(), authority);
2460 assert_eq!(view.amount(), 100);
2461 assert!(view.is_initialized());
2462 assert_eq!(token.checked_mint(&mint).unwrap().mint(), mint);
2463 assert_eq!(
2464 token.checked_authority(&authority).unwrap().authority(),
2465 authority
2466 );
2467 assert_eq!(
2468 token
2469 .checked_mint(&Address::new_from_array([9; 32]))
2470 .unwrap_err(),
2471 ProgramError::InvalidAccountData
2472 );
2473
2474 let before = token.amount_snapshot().unwrap();
2475 let backing_bytes = unsafe {
2479 core::slice::from_raw_parts_mut(backing.as_mut_ptr() as *mut u8, backing.len() * 8)
2480 };
2481 backing_bytes[RuntimeAccount::SIZE + 64..RuntimeAccount::SIZE + 72]
2482 .copy_from_slice(&150u64.to_le_bytes());
2483 token.assert_amount_delta(before, 50).unwrap();
2484 assert_eq!(
2485 token.assert_amount_delta(before, 49).unwrap_err(),
2486 ProgramError::InvalidAccountData
2487 );
2488 }
2489 #[test]
2490 fn spl_external_mint_view_and_decimals_proof() {
2491 let authority = Address::new_from_array([4; 32]);
2492 let data = mint_data(authority, 1_000_000, 6);
2493 let (_backing, account) = make_account(TOKEN_PROGRAM_ID, &data);
2494
2495 let mint = ExternalAccount::<SplMint>::try_new(&account).unwrap();
2496 let view = mint.view().unwrap();
2497 assert_eq!(view.mint_authority(), Some(authority));
2498 assert_eq!(view.supply(), 1_000_000);
2499 assert_eq!(view.decimals(), 6);
2500 assert!(view.is_initialized());
2501 assert_eq!(mint.checked_decimals(6).unwrap().decimals(), 6);
2502 assert_eq!(
2503 mint.checked_decimals(9).unwrap_err(),
2504 ProgramError::InvalidAccountData
2505 );
2506 }
2507
2508 fn encode_checked(disc: u8, amount: u64, decimals: u8) -> [u8; 10] {
2512 let mut data = [0u8; 10];
2513 data[0] = disc;
2514 data[1..9].copy_from_slice(&amount.to_le_bytes());
2515 data[9] = decimals;
2516 data
2517 }
2518
2519 #[test]
2520 fn transfer_checked_wire_format_is_stable() {
2521 let out = encode_checked(12, 0x0102_0304_0506_0708, 9);
2523 assert_eq!(out[0], 12);
2524 assert_eq!(
2525 &out[1..9],
2526 &[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]
2527 );
2528 assert_eq!(out[9], 9);
2529 }
2530
2531 #[test]
2532 fn mint_to_checked_wire_format_is_stable() {
2533 let out = encode_checked(14, 1000, 6);
2534 assert_eq!(out[0], 14);
2535 assert_eq!(u64::from_le_bytes(out[1..9].try_into().unwrap()), 1000);
2536 assert_eq!(out[9], 6);
2537 }
2538
2539 #[test]
2540 fn burn_checked_wire_format_is_stable() {
2541 let out = encode_checked(15, 42, 8);
2542 assert_eq!(out[0], 15);
2543 assert_eq!(u64::from_le_bytes(out[1..9].try_into().unwrap()), 42);
2544 assert_eq!(out[9], 8);
2545 }
2546
2547 #[test]
2548 fn approve_checked_wire_format_is_stable() {
2549 let out = encode_checked(13, u64::MAX, 0);
2550 assert_eq!(out[0], 13);
2551 assert_eq!(u64::from_le_bytes(out[1..9].try_into().unwrap()), u64::MAX);
2552 assert_eq!(out[9], 0);
2553 }
2554
2555 #[test]
2556 fn checked_encoding_round_trips_decimals_range() {
2557 for d in 0u8..=255 {
2561 let out = encode_checked(12, 1, d);
2562 assert_eq!(out[9], d);
2563 }
2564 }
2565
2566 #[test]
2567 fn checked_encoding_preserves_amount_bits() {
2568 for shift in 0..8 {
2571 let amount = 0xABu64 << (shift * 8);
2572 let out = encode_checked(12, amount, 0);
2573 let decoded = u64::from_le_bytes(out[1..9].try_into().unwrap());
2574 assert_eq!(decoded, amount);
2575 }
2576 }
2577
2578 #[test]
2579 fn authority_and_initialize_encodings_match_spl_token_wire_format() {
2580 let authority = Address::new_from_array([9; 32]);
2581 let (set_authority, len) = encoders::encode_set_authority(
2582 TokenAuthorityType::AccountOwner as u8,
2583 Some(authority.as_array()),
2584 );
2585 assert_eq!(len, 35);
2586 assert_eq!(set_authority[0], 6);
2587 assert_eq!(set_authority[1], 2);
2588 assert_eq!(set_authority[2], 1);
2589 assert_eq!(&set_authority[3..35], authority.as_bytes());
2590
2591 let (set_authority, len) =
2592 encoders::encode_set_authority(TokenAuthorityType::CloseAccount as u8, None);
2593 assert_eq!(len, 3);
2594 assert_eq!(&set_authority[..3], &[6, 3, 0]);
2595
2596 let init2 = encoders::encode_initialize_account_with_owner(16, authority.as_array());
2597 let init3 = encoders::encode_initialize_account_with_owner(18, authority.as_array());
2598 assert_eq!(init2[0], 16);
2599 assert_eq!(init3[0], 18);
2600 assert_eq!(&init2[1..33], authority.as_bytes());
2601 assert_eq!(&init3[1..33], authority.as_bytes());
2602 }
2603
2604 #[test]
2609 fn shipped_encoders_match_pre_refactor_golden_bytes() {
2610 assert_eq!(encoders::encode_transfer(1), [3, 1, 0, 0, 0, 0, 0, 0, 0]);
2612 assert_eq!(encoders::encode_approve(1), [4, 1, 0, 0, 0, 0, 0, 0, 0]);
2613 assert_eq!(encoders::encode_mint_to(1), [7, 1, 0, 0, 0, 0, 0, 0, 0]);
2614 assert_eq!(encoders::encode_burn(1), [8, 1, 0, 0, 0, 0, 0, 0, 0]);
2615 assert_eq!(
2616 encoders::encode_transfer_checked(1, 9),
2617 [12, 1, 0, 0, 0, 0, 0, 0, 0, 9]
2618 );
2619 assert_eq!(
2620 encoders::encode_approve_checked(1, 9),
2621 [13, 1, 0, 0, 0, 0, 0, 0, 0, 9]
2622 );
2623 assert_eq!(
2624 encoders::encode_mint_to_checked(1, 9),
2625 [14, 1, 0, 0, 0, 0, 0, 0, 0, 9]
2626 );
2627 assert_eq!(
2628 encoders::encode_burn_checked(1, 9),
2629 [15, 1, 0, 0, 0, 0, 0, 0, 0, 9]
2630 );
2631 assert_eq!(encoders::encode_revoke(), [5]);
2632 assert_eq!(encoders::encode_close_account(), [9]);
2633 assert_eq!(encoders::encode_freeze_account(), [10]);
2634 assert_eq!(encoders::encode_thaw_account(), [11]);
2635 assert_eq!(encoders::encode_sync_native(), [17]);
2636 assert_eq!(encoders::encode_initialize_account(), [1]);
2637
2638 let owner = [
2639 0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
2640 24, 25, 26, 27, 28, 29, 30, 31,
2641 ];
2642 let init2 = encoders::encode_initialize_account_with_owner(16, &owner);
2643 assert_eq!(init2[0], 16);
2644 assert_eq!(&init2[1..33], &owner);
2645 let init3 = encoders::encode_initialize_account_with_owner(18, &owner);
2646 assert_eq!(init3[0], 18);
2647 assert_eq!(&init3[1..33], &owner);
2648
2649 let (sa_some, some_len) = encoders::encode_set_authority(2, Some(&owner));
2650 assert_eq!(some_len, 35);
2651 assert_eq!(sa_some[0], 6);
2652 assert_eq!(sa_some[1], 2);
2653 assert_eq!(sa_some[2], 1);
2654 assert_eq!(&sa_some[3..35], &owner);
2655 let (sa_none, none_len) = encoders::encode_set_authority(3, None);
2656 assert_eq!(none_len, 3);
2657 assert_eq!(&sa_none[..3], &[6, 3, 0]);
2658 }
2659
2660 fn make_token_and_authority(
2668 authority_bytes: [u8; 32],
2669 token_owner_bytes: [u8; 32],
2670 ) -> (
2671 std::vec::Vec<u64>,
2672 std::vec::Vec<u64>,
2673 crate::account::AccountView<'static>,
2674 crate::account::AccountView<'static>,
2675 ) {
2676 use hopper_native::{
2677 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount,
2678 NOT_BORROWED,
2679 };
2680
2681 let token_data_len = 165;
2686 let mut token_backing =
2687 std::vec![0u64; (RuntimeAccount::SIZE + token_data_len).div_ceil(8)];
2688 let token_raw = token_backing.as_mut_ptr() as *mut RuntimeAccount;
2689 unsafe {
2691 token_raw.write(RuntimeAccount {
2692 borrow_state: NOT_BORROWED,
2693 is_signer: 0,
2694 is_writable: 1,
2695 executable: 0,
2696 resize_delta: 0,
2697 address: NativeAddress::new_from_array([0xAA; 32]),
2698 owner: NativeAddress::new_from_array([3; 32]),
2699 lamports: 2_039_280,
2700 data_len: token_data_len as u64,
2701 });
2702 let data_ptr = (token_raw as *mut u8).add(RuntimeAccount::SIZE);
2704 core::ptr::copy_nonoverlapping(token_owner_bytes.as_ptr(), data_ptr.add(32), 32);
2705 }
2706 let token_backend = unsafe { NativeAccountView::new_unchecked(token_raw) };
2708 let token_view = crate::account::AccountView::from_backend(token_backend);
2709
2710 let mut auth_backing = std::vec![0u64; (RuntimeAccount::SIZE).div_ceil(8)];
2712 let auth_raw = auth_backing.as_mut_ptr() as *mut RuntimeAccount;
2713 unsafe {
2715 auth_raw.write(RuntimeAccount {
2716 borrow_state: NOT_BORROWED,
2717 is_signer: 1,
2718 is_writable: 0,
2719 executable: 0,
2720 resize_delta: 0,
2721 address: NativeAddress::new_from_array(authority_bytes),
2722 owner: NativeAddress::new_from_array([0; 32]),
2723 lamports: 0,
2724 data_len: 0,
2725 });
2726 }
2727 let auth_backend = unsafe { NativeAccountView::new_unchecked(auth_raw) };
2729 let auth_view = crate::account::AccountView::from_backend(auth_backend);
2730
2731 (token_backing, auth_backing, token_view, auth_view)
2732 }
2733
2734 #[test]
2735 fn require_token_authority_accepts_matching_owner() {
2736 let authority = [0x42u8; 32];
2737 let (_tb, _ab, token, auth) = make_token_and_authority(authority, authority);
2738 require_token_authority(&token, &auth).unwrap();
2739 }
2740
2741 #[test]
2742 fn require_token_authority_rejects_mismatched_owner() {
2743 let authority = [0x42u8; 32];
2744 let wrong_owner = [0x77u8; 32];
2745 let (_tb, _ab, token, auth) = make_token_and_authority(authority, wrong_owner);
2746 let err = require_token_authority(&token, &auth).unwrap_err();
2747 assert!(matches!(err, ProgramError::IncorrectAuthority));
2748 }
2749
2750 #[test]
2751 fn require_token_authority_rejects_short_buffer() {
2752 use hopper_native::{
2753 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount,
2754 NOT_BORROWED,
2755 };
2756
2757 let data_len = 50;
2761 let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + data_len).div_ceil(8)];
2762 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
2763 unsafe {
2765 raw.write(RuntimeAccount {
2766 borrow_state: NOT_BORROWED,
2767 is_signer: 0,
2768 is_writable: 1,
2769 executable: 0,
2770 resize_delta: 0,
2771 address: NativeAddress::new_from_array([0xAA; 32]),
2772 owner: NativeAddress::new_from_array([3; 32]),
2773 lamports: 0,
2774 data_len: data_len as u64,
2775 });
2776 }
2777 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
2779 let token = crate::account::AccountView::from_backend(backend);
2780
2781 let (_ab, _, _, auth) = make_token_and_authority([0x11; 32], [0x11; 32]);
2782 let err = require_token_authority(&token, &auth).unwrap_err();
2783 assert!(matches!(err, ProgramError::AccountDataTooSmall));
2784 }
2785
2786 fn make_token_with_mint_and_owner(
2798 mint_bytes: [u8; 32],
2799 owner_bytes: [u8; 32],
2800 ) -> (std::vec::Vec<u64>, crate::account::AccountView<'static>) {
2801 use hopper_native::{
2802 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount,
2803 NOT_BORROWED,
2804 };
2805
2806 let token_data_len = 165;
2807 let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + token_data_len).div_ceil(8)];
2808 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
2809 unsafe {
2811 raw.write(RuntimeAccount {
2812 borrow_state: NOT_BORROWED,
2813 is_signer: 0,
2814 is_writable: 1,
2815 executable: 0,
2816 resize_delta: 0,
2817 address: NativeAddress::new_from_array([0xAA; 32]),
2818 owner: NativeAddress::new_from_array([3; 32]),
2819 lamports: 2_039_280,
2820 data_len: token_data_len as u64,
2821 });
2822 let data_ptr = (raw as *mut u8).add(RuntimeAccount::SIZE);
2823 core::ptr::copy_nonoverlapping(mint_bytes.as_ptr(), data_ptr, 32);
2824 core::ptr::copy_nonoverlapping(owner_bytes.as_ptr(), data_ptr.add(32), 32);
2825 }
2826 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
2828 let view = crate::account::AccountView::from_backend(backend);
2829 (backing, view)
2830 }
2831
2832 fn make_mint_with_authority_decimals(
2836 mint_authority: [u8; 32],
2837 decimals: u8,
2838 ) -> (std::vec::Vec<u64>, crate::account::AccountView<'static>) {
2839 use hopper_native::{
2840 AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount,
2841 NOT_BORROWED,
2842 };
2843
2844 let mint_data_len = 82;
2845 let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + mint_data_len).div_ceil(8)];
2846 let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
2847 unsafe {
2849 raw.write(RuntimeAccount {
2850 borrow_state: NOT_BORROWED,
2851 is_signer: 0,
2852 is_writable: 0,
2853 executable: 0,
2854 resize_delta: 0,
2855 address: NativeAddress::new_from_array([0xBB; 32]),
2856 owner: NativeAddress::new_from_array([3; 32]),
2857 lamports: 1_461_600,
2858 data_len: mint_data_len as u64,
2859 });
2860 let data_ptr = (raw as *mut u8).add(RuntimeAccount::SIZE);
2861 let some_tag: [u8; 4] = 1u32.to_le_bytes();
2863 core::ptr::copy_nonoverlapping(some_tag.as_ptr(), data_ptr, 4);
2864 core::ptr::copy_nonoverlapping(mint_authority.as_ptr(), data_ptr.add(4), 32);
2865 *data_ptr.add(44) = decimals;
2868 *data_ptr.add(45) = 1;
2870 }
2872 let backend = unsafe { NativeAccountView::new_unchecked(raw) };
2874 let view = crate::account::AccountView::from_backend(backend);
2875 (backing, view)
2876 }
2877
2878 #[test]
2879 fn require_token_mint_accepts_matching_mint() {
2880 let mint = [0xABu8; 32];
2881 let (_b, view) = make_token_with_mint_and_owner(mint, [0; 32]);
2882 let expected = crate::address::Address::new_from_array(mint);
2883 require_token_mint(&view, &expected).unwrap();
2884 }
2885
2886 #[test]
2887 fn require_token_mint_rejects_mismatched_mint() {
2888 let mint = [0xABu8; 32];
2889 let (_b, view) = make_token_with_mint_and_owner(mint, [0; 32]);
2890 let wrong = crate::address::Address::new_from_array([0xCDu8; 32]);
2891 let err = require_token_mint(&view, &wrong).unwrap_err();
2892 assert!(matches!(err, ProgramError::InvalidAccountData));
2893 }
2894
2895 #[test]
2896 fn require_token_owner_eq_matches() {
2897 let owner = [0x77u8; 32];
2898 let (_b, view) = make_token_with_mint_and_owner([0; 32], owner);
2899 let expected = crate::address::Address::new_from_array(owner);
2900 require_token_owner_eq(&view, &expected).unwrap();
2901 }
2902
2903 #[test]
2904 fn require_token_owner_eq_rejects_mismatch() {
2905 let owner = [0x77u8; 32];
2906 let (_b, view) = make_token_with_mint_and_owner([0; 32], owner);
2907 let wrong = crate::address::Address::new_from_array([0x88u8; 32]);
2908 let err = require_token_owner_eq(&view, &wrong).unwrap_err();
2909 assert!(matches!(err, ProgramError::IncorrectAuthority));
2910 }
2911
2912 #[test]
2913 fn require_mint_authority_accepts_matching() {
2914 let auth = [0x99u8; 32];
2915 let (_b, view) = make_mint_with_authority_decimals(auth, 6);
2916 let expected = crate::address::Address::new_from_array(auth);
2917 require_mint_authority(&view, &expected).unwrap();
2918 }
2919
2920 #[test]
2921 fn require_mint_authority_rejects_mismatched() {
2922 let auth = [0x99u8; 32];
2923 let (_b, view) = make_mint_with_authority_decimals(auth, 6);
2924 let wrong = crate::address::Address::new_from_array([0x00u8; 32]);
2925 let err = require_mint_authority(&view, &wrong).unwrap_err();
2926 assert!(matches!(err, ProgramError::IncorrectAuthority));
2927 }
2928
2929 #[test]
2930 fn require_mint_decimals_matches() {
2931 let (_b, view) = make_mint_with_authority_decimals([1u8; 32], 9);
2932 require_mint_decimals(&view, 9).unwrap();
2933 }
2934
2935 #[test]
2936 fn require_mint_decimals_rejects_mismatch() {
2937 let (_b, view) = make_mint_with_authority_decimals([1u8; 32], 9);
2938 let err = require_mint_decimals(&view, 6).unwrap_err();
2939 assert!(matches!(err, ProgramError::InvalidAccountData));
2940 }
2941
2942 #[test]
2943 fn require_mint_freeze_authority_rejects_none_tag() {
2944 let (_b, view) = make_mint_with_authority_decimals([1u8; 32], 9);
2950 let expected = crate::address::Address::new_from_array([2u8; 32]);
2951 let err = require_mint_freeze_authority(&view, &expected).unwrap_err();
2952 assert!(matches!(err, ProgramError::InvalidAccountData));
2953 }
2954}