Skip to main content

hopper_runtime/
token_2022_ix.rs

1//! Token-2022 instruction builders that have no SPL Token counterpart:
2//! the native-mint and non-transferable-mint initializers, `Reallocate`,
3//! and every extension family's instructions (transfer fee, default
4//! account state, memo transfer, interest-bearing, CPI guard, permanent
5//! delegate, transfer hook, metadata / group / group-member pointers,
6//! scaled UI amount, pausable, permissioned burn, mint close authority).
7//!
8//! Each builder encodes its bytes once in a [`TokenInstruction::emit`]
9//! impl, exactly as the shared builders in [`crate::token`] do, so a
10//! [`crate::token::TokenBatch`] can carry them too. `invoke()` always
11//! targets Token-2022. Builders that initialize a mint extension run
12//! before `InitializeMint2`, which is what [`crate::token_mint::MintPlan`]
13//! sequences for the fixed-size extensions; the builders here are the
14//! same bytes for callers that drive the sequence themselves or that need
15//! the post-initialization updates.
16//!
17//! The wire formats follow the Token-2022 program's instruction enum: a
18//! family discriminator (25 for mint close authority, 26 transfer fee, 28
19//! default account state, 30 memo transfer, 33 interest bearing, 34 CPI
20//! guard, 35 permanent delegate, 36 transfer hook, 39 metadata pointer,
21//! 40 group pointer, 41 group member pointer, 43 scaled UI amount, 44
22//! pausable, 46 permissioned burn) followed, where the family has more
23//! than one instruction, by a sub-discriminator. Optional addresses are
24//! either `COption` (`[0]` or `[1][32 bytes]`) or nullable (32 zero bytes
25//! mean "none"); each encoder's documentation says which.
26
27use crate::account::AccountView;
28use crate::address::Address;
29use crate::error::ProgramError;
30use crate::instruction::{InstructionAccount, Signer};
31use crate::token::{
32    authority_meta, encoders as token_encoders, require_authority_signed_direct,
33    require_multisig_signers_direct, Invoke, TokenInstruction, TokenSink, Trailing,
34};
35use crate::ProgramResult;
36
37/// A bounded, stack-resident instruction-data buffer.
38#[derive(Clone, Copy)]
39pub struct Bytes<const N: usize> {
40    buf: [u8; N],
41    len: usize,
42}
43
44impl<const N: usize> Bytes<N> {
45    #[inline(always)]
46    const fn new() -> Self {
47        Self {
48            buf: [0; N],
49            len: 0,
50        }
51    }
52
53    #[inline(always)]
54    fn push(&mut self, bytes: &[u8]) {
55        self.buf[self.len..self.len + bytes.len()].copy_from_slice(bytes);
56        self.len += bytes.len();
57    }
58
59    /// `[1][address]` when present, `[0]` when absent.
60    #[inline(always)]
61    fn coption(&mut self, address: Option<&Address>) {
62        match address {
63            Some(address) => {
64                self.push(&[1]);
65                self.push(address.as_array());
66            }
67            None => self.push(&[0]),
68        }
69    }
70
71    /// The address, or 32 zero bytes when absent.
72    #[inline(always)]
73    fn nullable(&mut self, address: Option<&Address>) {
74        match address {
75            Some(address) => self.push(address.as_array()),
76            None => self.push(&[0; 32]),
77        }
78    }
79
80    /// The encoded bytes.
81    #[inline(always)]
82    pub fn as_slice(&self) -> &[u8] {
83        &self.buf[..self.len]
84    }
85}
86
87/// Refuse a "present" optional address that is all zeros: Token-2022 stores
88/// these as nullable addresses, so a zero address would silently mean
89/// "none" on chain.
90#[inline(always)]
91fn non_zero(address: Option<&Address>) -> ProgramResult {
92    if address.is_some_and(|a| a.as_array() == &[0; 32]) {
93        return Err(ProgramError::InvalidArgument);
94    }
95    Ok(())
96}
97
98/// Byte-exact encoders for the Token-2022-only instructions.
99///
100/// Public for the same reason as [`crate::token::encoders`]: the
101/// builders call them, and so do the layout proofs and
102/// [`crate::token_mint::MintExtension`], so there is one source of the
103/// bytes.
104pub mod encoders {
105    use super::{non_zero, Bytes};
106    use crate::address::Address;
107    use crate::error::ProgramError;
108    use crate::ProgramResult;
109
110    pub const IX_INITIALIZE_MINT_CLOSE_AUTHORITY: u8 = 25;
111    pub const IX_TRANSFER_FEE: u8 = 26;
112    pub const IX_DEFAULT_ACCOUNT_STATE: u8 = 28;
113    pub const IX_REALLOCATE: u8 = 29;
114    pub const IX_MEMO_TRANSFER: u8 = 30;
115    pub const IX_CREATE_NATIVE_MINT: u8 = 31;
116    pub const IX_INITIALIZE_NON_TRANSFERABLE_MINT: u8 = 32;
117    pub const IX_INTEREST_BEARING_MINT: u8 = 33;
118    pub const IX_CPI_GUARD: u8 = 34;
119    pub const IX_INITIALIZE_PERMANENT_DELEGATE: u8 = 35;
120    pub const IX_TRANSFER_HOOK: u8 = 36;
121    pub const IX_METADATA_POINTER: u8 = 39;
122    pub const IX_GROUP_POINTER: u8 = 40;
123    pub const IX_GROUP_MEMBER_POINTER: u8 = 41;
124    pub const IX_SCALED_UI_AMOUNT: u8 = 43;
125    pub const IX_PAUSABLE: u8 = 44;
126    pub const IX_PERMISSIONED_BURN: u8 = 46;
127
128    /// Token-2022 `AccountState::Initialized`.
129    pub const ACCOUNT_STATE_INITIALIZED: u8 = 1;
130    /// Token-2022 `AccountState::Frozen`.
131    pub const ACCOUNT_STATE_FROZEN: u8 = 2;
132
133    /// The largest transfer fee, 100% in basis points.
134    pub const MAX_FEE_BASIS_POINTS: u16 = 10_000;
135
136    /// `[31]`.
137    #[inline(always)]
138    pub fn encode_create_native_mint() -> [u8; 1] {
139        [IX_CREATE_NATIVE_MINT]
140    }
141
142    /// `[32]`.
143    #[inline(always)]
144    pub fn encode_initialize_non_transferable_mint() -> [u8; 1] {
145        [IX_INITIALIZE_NON_TRANSFERABLE_MINT]
146    }
147
148    /// `[25][COption<close_authority>]`.
149    #[inline(always)]
150    pub fn encode_initialize_mint_close_authority(close_authority: Option<&Address>) -> Bytes<34> {
151        let mut out = Bytes::new();
152        out.push(&[IX_INITIALIZE_MINT_CLOSE_AUTHORITY]);
153        out.coption(close_authority);
154        out
155    }
156
157    /// `[26][0][COption<config_authority>][COption<withdraw_authority>][basis_points: u16 LE][maximum_fee: u64 LE]`.
158    /// Refuses a fee above 100%.
159    #[inline(always)]
160    pub fn encode_initialize_transfer_fee_config(
161        transfer_fee_config_authority: Option<&Address>,
162        withdraw_withheld_authority: Option<&Address>,
163        transfer_fee_basis_points: u16,
164        maximum_fee: u64,
165    ) -> Result<Bytes<78>, ProgramError> {
166        if transfer_fee_basis_points > MAX_FEE_BASIS_POINTS {
167            return Err(ProgramError::InvalidArgument);
168        }
169        let mut out = Bytes::new();
170        out.push(&[IX_TRANSFER_FEE, 0]);
171        out.coption(transfer_fee_config_authority);
172        out.coption(withdraw_withheld_authority);
173        out.push(&transfer_fee_basis_points.to_le_bytes());
174        out.push(&maximum_fee.to_le_bytes());
175        Ok(out)
176    }
177
178    /// `[26][1][amount: u64 LE][decimals][fee: u64 LE]`.
179    #[inline(always)]
180    pub fn encode_transfer_checked_with_fee(amount: u64, decimals: u8, fee: u64) -> [u8; 19] {
181        let mut data = [0u8; 19];
182        data[0] = IX_TRANSFER_FEE;
183        data[1] = 1;
184        data[2..10].copy_from_slice(&amount.to_le_bytes());
185        data[10] = decimals;
186        data[11..19].copy_from_slice(&fee.to_le_bytes());
187        data
188    }
189
190    /// `[26][2]`.
191    #[inline(always)]
192    pub fn encode_withdraw_withheld_tokens_from_mint() -> [u8; 2] {
193        [IX_TRANSFER_FEE, 2]
194    }
195
196    /// `[26][3][num_token_accounts]`.
197    #[inline(always)]
198    pub fn encode_withdraw_withheld_tokens_from_accounts(num_token_accounts: u8) -> [u8; 3] {
199        [IX_TRANSFER_FEE, 3, num_token_accounts]
200    }
201
202    /// `[26][4]`.
203    #[inline(always)]
204    pub fn encode_harvest_withheld_tokens_to_mint() -> [u8; 2] {
205        [IX_TRANSFER_FEE, 4]
206    }
207
208    /// `[26][5][basis_points: u16 LE][maximum_fee: u64 LE]`. Refuses a fee
209    /// above 100%.
210    #[inline(always)]
211    pub fn encode_set_transfer_fee(
212        transfer_fee_basis_points: u16,
213        maximum_fee: u64,
214    ) -> Result<[u8; 12], ProgramError> {
215        if transfer_fee_basis_points > MAX_FEE_BASIS_POINTS {
216            return Err(ProgramError::InvalidArgument);
217        }
218        let mut data = [0u8; 12];
219        data[0] = IX_TRANSFER_FEE;
220        data[1] = 5;
221        data[2..4].copy_from_slice(&transfer_fee_basis_points.to_le_bytes());
222        data[4..12].copy_from_slice(&maximum_fee.to_le_bytes());
223        Ok(data)
224    }
225
226    #[inline(always)]
227    fn check_account_state(state: u8) -> ProgramResult {
228        if state == ACCOUNT_STATE_INITIALIZED || state == ACCOUNT_STATE_FROZEN {
229            Ok(())
230        } else {
231            Err(ProgramError::InvalidArgument)
232        }
233    }
234
235    /// `[28][0][state]`; `state` is 1 (initialized) or 2 (frozen).
236    #[inline(always)]
237    pub fn encode_initialize_default_account_state(state: u8) -> Result<[u8; 3], ProgramError> {
238        check_account_state(state)?;
239        Ok([IX_DEFAULT_ACCOUNT_STATE, 0, state])
240    }
241
242    /// `[28][1][state]`; `state` is 1 (initialized) or 2 (frozen).
243    #[inline(always)]
244    pub fn encode_update_default_account_state(state: u8) -> Result<[u8; 3], ProgramError> {
245        check_account_state(state)?;
246        Ok([IX_DEFAULT_ACCOUNT_STATE, 1, state])
247    }
248
249    /// `[30][0]` (enable) or `[30][1]` (disable).
250    #[inline(always)]
251    pub fn encode_memo_transfer(enable: bool) -> [u8; 2] {
252        [IX_MEMO_TRANSFER, u8::from(!enable)]
253    }
254
255    /// `[33][0][rate_authority: nullable 32][rate: i16 LE]`.
256    #[inline(always)]
257    pub fn encode_initialize_interest_bearing_mint(
258        rate_authority: Option<&Address>,
259        rate: i16,
260    ) -> Result<Bytes<36>, ProgramError> {
261        non_zero(rate_authority)?;
262        let mut out = Bytes::new();
263        out.push(&[IX_INTEREST_BEARING_MINT, 0]);
264        out.nullable(rate_authority);
265        out.push(&rate.to_le_bytes());
266        Ok(out)
267    }
268
269    /// `[33][1][rate: i16 LE]`.
270    #[inline(always)]
271    pub fn encode_update_interest_rate(rate: i16) -> [u8; 4] {
272        let rate = rate.to_le_bytes();
273        [IX_INTEREST_BEARING_MINT, 1, rate[0], rate[1]]
274    }
275
276    /// `[34][0]` (enable) or `[34][1]` (disable).
277    #[inline(always)]
278    pub fn encode_cpi_guard(enable: bool) -> [u8; 2] {
279        [IX_CPI_GUARD, u8::from(!enable)]
280    }
281
282    /// `[35][delegate: 32]`.
283    #[inline(always)]
284    pub fn encode_initialize_permanent_delegate(delegate: &Address) -> Bytes<33> {
285        let mut out = Bytes::new();
286        out.push(&[IX_INITIALIZE_PERMANENT_DELEGATE]);
287        out.push(delegate.as_array());
288        out
289    }
290
291    /// `[family][0][authority: nullable 32][target: nullable 32]`, the
292    /// shape shared by the transfer hook (36), metadata pointer (39),
293    /// group pointer (40), and group member pointer (41) initializers.
294    #[inline(always)]
295    pub fn encode_initialize_pointer(
296        family: u8,
297        authority: Option<&Address>,
298        target: Option<&Address>,
299    ) -> Result<Bytes<66>, ProgramError> {
300        non_zero(authority)?;
301        non_zero(target)?;
302        let mut out = Bytes::new();
303        out.push(&[family, 0]);
304        out.nullable(authority);
305        out.nullable(target);
306        Ok(out)
307    }
308
309    /// `[family][1][target: nullable 32]`, the shape shared by the transfer
310    /// hook, metadata pointer, group pointer, and group member pointer
311    /// updates.
312    #[inline(always)]
313    pub fn encode_update_pointer(
314        family: u8,
315        target: Option<&Address>,
316    ) -> Result<Bytes<34>, ProgramError> {
317        non_zero(target)?;
318        let mut out = Bytes::new();
319        out.push(&[family, 1]);
320        out.nullable(target);
321        Ok(out)
322    }
323
324    /// Whether an `f64` multiplier is positive and finite, decided on its
325    /// bit pattern so that no soft-float comparison is linked on chain.
326    #[inline(always)]
327    pub const fn multiplier_is_valid(multiplier: f64) -> bool {
328        let bits = multiplier.to_bits();
329        let exponent = (bits >> 52) & 0x7ff;
330        bits >> 63 == 0 && exponent != 0x7ff && bits != 0
331    }
332
333    /// `[43][0][authority: nullable 32][multiplier: f64 LE]`. The multiplier
334    /// must be positive and finite.
335    #[inline(always)]
336    pub fn encode_initialize_scaled_ui_amount(
337        authority: Option<&Address>,
338        multiplier: f64,
339    ) -> Result<Bytes<42>, ProgramError> {
340        non_zero(authority)?;
341        if !multiplier_is_valid(multiplier) {
342            return Err(ProgramError::InvalidArgument);
343        }
344        let mut out = Bytes::new();
345        out.push(&[IX_SCALED_UI_AMOUNT, 0]);
346        out.nullable(authority);
347        out.push(&multiplier.to_le_bytes());
348        Ok(out)
349    }
350
351    /// `[43][1][multiplier: f64 LE][effective_timestamp: i64 LE]`.
352    #[inline(always)]
353    pub fn encode_update_scaled_ui_amount_multiplier(
354        multiplier: f64,
355        effective_timestamp: i64,
356    ) -> Result<[u8; 18], ProgramError> {
357        if !multiplier_is_valid(multiplier) {
358            return Err(ProgramError::InvalidArgument);
359        }
360        let mut data = [0u8; 18];
361        data[0] = IX_SCALED_UI_AMOUNT;
362        data[1] = 1;
363        data[2..10].copy_from_slice(&multiplier.to_le_bytes());
364        data[10..18].copy_from_slice(&effective_timestamp.to_le_bytes());
365        Ok(data)
366    }
367
368    /// `[family][0][authority: 32]`, the shape shared by the pausable (44)
369    /// and permissioned burn (46) initializers. The authority must not be
370    /// the zero address.
371    #[inline(always)]
372    pub fn encode_initialize_authority(
373        family: u8,
374        authority: &Address,
375    ) -> Result<Bytes<34>, ProgramError> {
376        non_zero(Some(authority))?;
377        let mut out = Bytes::new();
378        out.push(&[family, 0]);
379        out.push(authority.as_array());
380        Ok(out)
381    }
382
383    /// `[44][1]` (pause) or `[44][2]` (resume).
384    #[inline(always)]
385    pub fn encode_pausable(pause: bool) -> [u8; 2] {
386        [IX_PAUSABLE, if pause { 1 } else { 2 }]
387    }
388
389    /// `[46][1][amount: u64 LE]`.
390    #[inline(always)]
391    pub fn encode_permissioned_burn(amount: u64) -> [u8; 10] {
392        let mut data = [0u8; 10];
393        data[0] = IX_PERMISSIONED_BURN;
394        data[1] = 1;
395        data[2..10].copy_from_slice(&amount.to_le_bytes());
396        data
397    }
398
399    /// `[46][2][amount: u64 LE][decimals]`.
400    #[inline(always)]
401    pub fn encode_permissioned_burn_checked(amount: u64, decimals: u8) -> [u8; 11] {
402        let mut data = [0u8; 11];
403        data[0] = IX_PERMISSIONED_BURN;
404        data[1] = 2;
405        data[2..10].copy_from_slice(&amount.to_le_bytes());
406        data[10] = decimals;
407        data
408    }
409}
410
411use encoders::*;
412
413/// The Token-2022 entry points: `invoke` (authority signed directly),
414/// `invoke_signed` (PDA seeds), and for builders with an authority the
415/// multisig forms.
416macro_rules! t22_methods {
417    ($name:ident) => {
418        impl $name<'_> {
419            /// Send this instruction to Token-2022.
420            #[inline]
421            pub fn invoke(&self) -> ProgramResult {
422                self.emit(&[], &mut Invoke::token_2022(&[]))
423            }
424
425            /// Send this instruction to Token-2022 with PDA signers.
426            #[inline]
427            pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
428                self.emit(&[], &mut Invoke::token_2022(signers))
429            }
430        }
431    };
432    ($name:ident, authority = $auth:ident) => {
433        impl $name<'_> {
434            /// Send this instruction to Token-2022 with the authority
435            /// signed directly. Fails with `MissingRequiredSignature`
436            /// before the CPI if it is not.
437            #[inline]
438            pub fn invoke(&self) -> ProgramResult {
439                require_authority_signed_direct(self.$auth)?;
440                self.emit(&[], &mut Invoke::token_2022(&[]))
441            }
442
443            /// Send this instruction to Token-2022 with PDA signers.
444            #[inline]
445            pub fn invoke_signed(&self, signers: &[Signer<'_, '_>]) -> ProgramResult {
446                self.emit(&[], &mut Invoke::token_2022(signers))
447            }
448
449            /// Send this instruction to Token-2022 with a multisig
450            /// authority whose signers signed directly.
451            #[inline]
452            pub fn invoke_multisig(&self, multisig_signers: &[&AccountView<'_>]) -> ProgramResult {
453                require_multisig_signers_direct(multisig_signers)?;
454                self.emit(multisig_signers, &mut Invoke::token_2022(&[]))
455            }
456
457            /// Send this instruction to Token-2022 with a multisig
458            /// authority and PDA signers.
459            #[inline]
460            pub fn invoke_signed_multisig(
461                &self,
462                multisig_signers: &[&AccountView<'_>],
463                signers: &[Signer<'_, '_>],
464            ) -> ProgramResult {
465                self.emit(multisig_signers, &mut Invoke::token_2022(signers))
466            }
467        }
468    };
469}
470
471/// A builder whose only account is a writable mint and whose data is fixed
472/// at construction: the extension initializers.
473macro_rules! mint_initializer {
474    ($(#[$doc:meta])* $name:ident { $($field:ident : $ty:ty),* $(,)? } data = |$s:ident| $data:expr;) => {
475        $(#[$doc])*
476        pub struct $name<'a> {
477            pub mint: &'a AccountView<'a>,
478            $(pub $field: $ty,)*
479        }
480
481        impl<'a, 'x: 'a> TokenInstruction<'a> for $name<'x> {
482            #[inline(always)]
483            fn emit(
484                &self,
485                multisig_signers: &[&'a AccountView<'a>],
486                sink: &mut impl TokenSink<'a>,
487            ) -> ProgramResult {
488                let $s = self;
489                let data = $data;
490                let accounts = [InstructionAccount::writable(self.mint.address())];
491                let views = [self.mint];
492                sink.emit(data.as_ref(), accounts, views, &[Trailing::signers(multisig_signers)])
493            }
494        }
495
496        t22_methods!($name);
497    };
498}
499
500/// A builder over a writable target account and an authority (single key
501/// or multisig) with fixed data: the extension updates and toggles.
502macro_rules! authority_update {
503    ($(#[$doc:meta])* $name:ident { target = $target:ident, authority = $auth:ident $(, $field:ident : $ty:ty)* $(,)? } data = |$s:ident| $data:expr;) => {
504        $(#[$doc])*
505        pub struct $name<'a> {
506            pub $target: &'a AccountView<'a>,
507            pub $auth: &'a AccountView<'a>,
508            $(pub $field: $ty,)*
509        }
510
511        impl<'a, 'x: 'a> TokenInstruction<'a> for $name<'x> {
512            #[inline(always)]
513            fn emit(
514                &self,
515                multisig_signers: &[&'a AccountView<'a>],
516                sink: &mut impl TokenSink<'a>,
517            ) -> ProgramResult {
518                let $s = self;
519                let data = $data;
520                let accounts = [
521                    InstructionAccount::writable(self.$target.address()),
522                    authority_meta(self.$auth, multisig_signers),
523                ];
524                let views = [self.$target, self.$auth];
525                sink.emit(data.as_ref(), accounts, views, &[Trailing::signers(multisig_signers)])
526            }
527        }
528
529        t22_methods!($name, authority = $auth);
530    };
531}
532
533impl<const N: usize> AsRef<[u8]> for Bytes<N> {
534    #[inline(always)]
535    fn as_ref(&self) -> &[u8] {
536        self.as_slice()
537    }
538}
539
540// ---------------------------------------------------------------------
541// Program-level instructions
542
543/// `CreateNativeMint` (31): create the Token-2022 wrapped-SOL mint
544/// (`9pan9bMn5HatX4EJdBwg9VgCa7Uz5HL8N1m5D3NdXejP`). `payer` funds the
545/// rent and signs.
546pub struct CreateNativeMint<'a> {
547    pub payer: &'a AccountView<'a>,
548    pub native_mint: &'a AccountView<'a>,
549    pub system_program: &'a AccountView<'a>,
550}
551
552impl<'a, 'x: 'a> TokenInstruction<'a> for CreateNativeMint<'x> {
553    #[inline(always)]
554    fn emit(
555        &self,
556        multisig_signers: &[&'a AccountView<'a>],
557        sink: &mut impl TokenSink<'a>,
558    ) -> ProgramResult {
559        let data = encode_create_native_mint();
560        let accounts = [
561            InstructionAccount::writable_signer(self.payer.address()),
562            InstructionAccount::writable(self.native_mint.address()),
563            InstructionAccount::readonly(self.system_program.address()),
564        ];
565        let views = [self.payer, self.native_mint, self.system_program];
566        sink.emit(
567            &data,
568            accounts,
569            views,
570            &[Trailing::signers(multisig_signers)],
571        )
572    }
573}
574
575t22_methods!(CreateNativeMint);
576
577mint_initializer! {
578    /// `InitializeNonTransferableMint` (32): mark a not-yet-initialized
579    /// mint so that its tokens can never be transferred. Runs before
580    /// `InitializeMint2`.
581    InitializeNonTransferableMint {}
582    data = |_s| encode_initialize_non_transferable_mint();
583}
584
585/// `Reallocate` (29): grow an initialized token account so that it can
586/// hold `extension_types` (Token-2022 TLV type numbers); `payer` funds the
587/// rent difference and `owner` authorizes, directly or through multisig
588/// signers.
589pub struct Reallocate<'a> {
590    pub account: &'a AccountView<'a>,
591    pub payer: &'a AccountView<'a>,
592    pub system_program: &'a AccountView<'a>,
593    pub owner: &'a AccountView<'a>,
594    pub extension_types: &'a [u16],
595}
596
597impl<'a, 'x: 'a> TokenInstruction<'a> for Reallocate<'x> {
598    #[inline(always)]
599    fn emit(
600        &self,
601        multisig_signers: &[&'a AccountView<'a>],
602        sink: &mut impl TokenSink<'a>,
603    ) -> ProgramResult {
604        let (data, len) =
605            token_encoders::encode_extension_types(IX_REALLOCATE, self.extension_types)
606                .ok_or(ProgramError::InvalidArgument)?;
607        let accounts = [
608            InstructionAccount::writable(self.account.address()),
609            InstructionAccount::writable_signer(self.payer.address()),
610            InstructionAccount::readonly(self.system_program.address()),
611            authority_meta(self.owner, multisig_signers),
612        ];
613        let views = [self.account, self.payer, self.system_program, self.owner];
614        sink.emit(
615            &data[..len],
616            accounts,
617            views,
618            &[Trailing::signers(multisig_signers)],
619        )
620    }
621}
622
623t22_methods!(Reallocate, authority = owner);
624
625// ---------------------------------------------------------------------
626// Mint extension initializers (before InitializeMint2)
627
628mint_initializer! {
629    /// `InitializeMintCloseAuthority` (25): let `close_authority` close the
630    /// mint once its supply is zero.
631    InitializeMintCloseAuthority { close_authority: Option<&'a Address> }
632    data = |s| encode_initialize_mint_close_authority(s.close_authority);
633}
634
635mint_initializer! {
636    /// `TransferFeeExtension::InitializeTransferFeeConfig` (26/0): charge
637    /// `transfer_fee_basis_points` of every transfer, capped at
638    /// `maximum_fee`, withheld in the destination account.
639    InitializeTransferFeeConfig {
640        transfer_fee_config_authority: Option<&'a Address>,
641        withdraw_withheld_authority: Option<&'a Address>,
642        transfer_fee_basis_points: u16,
643        maximum_fee: u64,
644    }
645    data = |s| encode_initialize_transfer_fee_config(
646        s.transfer_fee_config_authority,
647        s.withdraw_withheld_authority,
648        s.transfer_fee_basis_points,
649        s.maximum_fee,
650    )?;
651}
652
653mint_initializer! {
654    /// `DefaultAccountStateExtension::Initialize` (28/0): new token
655    /// accounts of this mint start in `state` (1 initialized, 2 frozen).
656    InitializeDefaultAccountState { state: u8 }
657    data = |s| encode_initialize_default_account_state(s.state)?;
658}
659
660mint_initializer! {
661    /// `InterestBearingMintExtension::Initialize` (33/0): display balances
662    /// with continuously compounding interest at `rate` basis points.
663    InitializeInterestBearingMint { rate_authority: Option<&'a Address>, rate: i16 }
664    data = |s| encode_initialize_interest_bearing_mint(s.rate_authority, s.rate)?;
665}
666
667mint_initializer! {
668    /// `InitializePermanentDelegate` (35): `delegate` may transfer or burn
669    /// from any account of this mint, forever.
670    InitializePermanentDelegate { delegate: &'a Address }
671    data = |s| encode_initialize_permanent_delegate(s.delegate);
672}
673
674mint_initializer! {
675    /// `TransferHookExtension::Initialize` (36/0): every transfer of this
676    /// mint calls `program_id`; `authority` may change it later.
677    InitializeTransferHook { authority: Option<&'a Address>, program_id: Option<&'a Address> }
678    data = |s| encode_initialize_pointer(IX_TRANSFER_HOOK, s.authority, s.program_id)?;
679}
680
681mint_initializer! {
682    /// `MetadataPointerExtension::Initialize` (39/0).
683    InitializeMetadataPointer { authority: Option<&'a Address>, metadata_address: Option<&'a Address> }
684    data = |s| encode_initialize_pointer(IX_METADATA_POINTER, s.authority, s.metadata_address)?;
685}
686
687mint_initializer! {
688    /// `GroupPointerExtension::Initialize` (40/0).
689    InitializeGroupPointer { authority: Option<&'a Address>, group_address: Option<&'a Address> }
690    data = |s| encode_initialize_pointer(IX_GROUP_POINTER, s.authority, s.group_address)?;
691}
692
693mint_initializer! {
694    /// `GroupMemberPointerExtension::Initialize` (41/0).
695    InitializeGroupMemberPointer { authority: Option<&'a Address>, member_address: Option<&'a Address> }
696    data = |s| encode_initialize_pointer(IX_GROUP_MEMBER_POINTER, s.authority, s.member_address)?;
697}
698
699mint_initializer! {
700    /// `ScaledUiAmountExtension::Initialize` (43/0): display balances
701    /// multiplied by `multiplier` (positive and finite).
702    InitializeScaledUiAmount { authority: Option<&'a Address>, multiplier: f64 }
703    data = |s| encode_initialize_scaled_ui_amount(s.authority, s.multiplier)?;
704}
705
706mint_initializer! {
707    /// `PausableExtension::Initialize` (44/0): `authority` may pause and
708    /// resume every transfer, mint, and burn of this mint.
709    InitializePausable { authority: &'a Address }
710    data = |s| encode_initialize_authority(IX_PAUSABLE, s.authority)?;
711}
712
713mint_initializer! {
714    /// `PermissionedBurnExtension::Initialize` (46/0): burns of this mint
715    /// need `authority`'s signature next to the holder's.
716    InitializePermissionedBurn { authority: &'a Address }
717    data = |s| encode_initialize_authority(IX_PERMISSIONED_BURN, s.authority)?;
718}
719
720// ---------------------------------------------------------------------
721// Transfer fee operations
722
723/// `TransferFeeExtension::TransferCheckedWithFee` (26/1): a
724/// `TransferChecked` that also states the fee the caller expects, which
725/// the program refuses to exceed.
726pub struct TransferCheckedWithFee<'a> {
727    pub source: &'a AccountView<'a>,
728    pub mint: &'a AccountView<'a>,
729    pub destination: &'a AccountView<'a>,
730    pub authority: &'a AccountView<'a>,
731    pub amount: u64,
732    pub decimals: u8,
733    pub fee: u64,
734}
735
736impl<'a, 'x: 'a> TokenInstruction<'a> for TransferCheckedWithFee<'x> {
737    #[inline(always)]
738    fn emit(
739        &self,
740        multisig_signers: &[&'a AccountView<'a>],
741        sink: &mut impl TokenSink<'a>,
742    ) -> ProgramResult {
743        let data = encode_transfer_checked_with_fee(self.amount, self.decimals, self.fee);
744        let accounts = [
745            InstructionAccount::writable(self.source.address()),
746            InstructionAccount::readonly(self.mint.address()),
747            InstructionAccount::writable(self.destination.address()),
748            authority_meta(self.authority, multisig_signers),
749        ];
750        let views = [self.source, self.mint, self.destination, self.authority];
751        sink.emit(
752            &data,
753            accounts,
754            views,
755            &[Trailing::signers(multisig_signers)],
756        )
757    }
758}
759
760t22_methods!(TransferCheckedWithFee, authority = authority);
761
762/// `TransferFeeExtension::WithdrawWithheldTokensFromMint` (26/2): move
763/// the fees harvested into the mint to `destination`.
764pub struct WithdrawWithheldTokensFromMint<'a> {
765    pub mint: &'a AccountView<'a>,
766    pub destination: &'a AccountView<'a>,
767    pub withdraw_withheld_authority: &'a AccountView<'a>,
768}
769
770impl<'a, 'x: 'a> TokenInstruction<'a> for WithdrawWithheldTokensFromMint<'x> {
771    #[inline(always)]
772    fn emit(
773        &self,
774        multisig_signers: &[&'a AccountView<'a>],
775        sink: &mut impl TokenSink<'a>,
776    ) -> ProgramResult {
777        let data = encode_withdraw_withheld_tokens_from_mint();
778        let accounts = [
779            InstructionAccount::writable(self.mint.address()),
780            InstructionAccount::writable(self.destination.address()),
781            authority_meta(self.withdraw_withheld_authority, multisig_signers),
782        ];
783        let views = [
784            self.mint,
785            self.destination,
786            self.withdraw_withheld_authority,
787        ];
788        sink.emit(
789            &data,
790            accounts,
791            views,
792            &[Trailing::signers(multisig_signers)],
793        )
794    }
795}
796
797t22_methods!(
798    WithdrawWithheldTokensFromMint,
799    authority = withdraw_withheld_authority
800);
801
802/// `TransferFeeExtension::WithdrawWithheldTokensFromAccounts` (26/3): move
803/// the fees withheld in `sources` (writable token accounts of `mint`) to
804/// `destination`.
805pub struct WithdrawWithheldTokensFromAccounts<'a> {
806    pub mint: &'a AccountView<'a>,
807    pub destination: &'a AccountView<'a>,
808    pub withdraw_withheld_authority: &'a AccountView<'a>,
809    pub sources: &'a [&'a AccountView<'a>],
810}
811
812impl<'a, 'x: 'a> TokenInstruction<'a> for WithdrawWithheldTokensFromAccounts<'x> {
813    #[inline(always)]
814    fn emit(
815        &self,
816        multisig_signers: &[&'a AccountView<'a>],
817        sink: &mut impl TokenSink<'a>,
818    ) -> ProgramResult {
819        let count = u8::try_from(self.sources.len()).map_err(|_| ProgramError::InvalidArgument)?;
820        let data = encode_withdraw_withheld_tokens_from_accounts(count);
821        let accounts = [
822            InstructionAccount::readonly(self.mint.address()),
823            InstructionAccount::writable(self.destination.address()),
824            authority_meta(self.withdraw_withheld_authority, multisig_signers),
825        ];
826        let views = [
827            self.mint,
828            self.destination,
829            self.withdraw_withheld_authority,
830        ];
831        sink.emit(
832            &data,
833            accounts,
834            views,
835            &[
836                Trailing::signers(multisig_signers),
837                Trailing::writable(self.sources),
838            ],
839        )
840    }
841}
842
843t22_methods!(
844    WithdrawWithheldTokensFromAccounts,
845    authority = withdraw_withheld_authority
846);
847
848/// `TransferFeeExtension::HarvestWithheldTokensToMint` (26/4): move the
849/// fees withheld in `sources` into the mint; anyone may call it.
850pub struct HarvestWithheldTokensToMint<'a> {
851    pub mint: &'a AccountView<'a>,
852    pub sources: &'a [&'a AccountView<'a>],
853}
854
855impl<'a, 'x: 'a> TokenInstruction<'a> for HarvestWithheldTokensToMint<'x> {
856    #[inline(always)]
857    fn emit(
858        &self,
859        _multisig_signers: &[&'a AccountView<'a>],
860        sink: &mut impl TokenSink<'a>,
861    ) -> ProgramResult {
862        let data = encode_harvest_withheld_tokens_to_mint();
863        let accounts = [InstructionAccount::writable(self.mint.address())];
864        let views = [self.mint];
865        sink.emit(&data, accounts, views, &[Trailing::writable(self.sources)])
866    }
867}
868
869t22_methods!(HarvestWithheldTokensToMint);
870
871authority_update! {
872    /// `TransferFeeExtension::SetTransferFee` (26/5): change the fee; it
873    /// takes effect two epochs later.
874    SetTransferFee { target = mint, authority = transfer_fee_config_authority, transfer_fee_basis_points: u16, maximum_fee: u64 }
875    data = |s| encode_set_transfer_fee(s.transfer_fee_basis_points, s.maximum_fee)?;
876}
877
878// ---------------------------------------------------------------------
879// Mint and account updates
880
881authority_update! {
882    /// `DefaultAccountStateExtension::Update` (28/1): change the state new
883    /// accounts start in; signed by the mint's freeze authority.
884    UpdateDefaultAccountState { target = mint, authority = freeze_authority, state: u8 }
885    data = |s| encode_update_default_account_state(s.state)?;
886}
887
888authority_update! {
889    /// `MemoTransferExtension::Enable` (30/0): the account refuses incoming
890    /// transfers that carry no memo.
891    EnableMemoTransfer { target = account, authority = owner }
892    data = |_s| encode_memo_transfer(true);
893}
894
895authority_update! {
896    /// `MemoTransferExtension::Disable` (30/1).
897    DisableMemoTransfer { target = account, authority = owner }
898    data = |_s| encode_memo_transfer(false);
899}
900
901authority_update! {
902    /// `InterestBearingMintExtension::UpdateRate` (33/1).
903    UpdateInterestRate { target = mint, authority = rate_authority, rate: i16 }
904    data = |s| encode_update_interest_rate(s.rate);
905}
906
907authority_update! {
908    /// `CpiGuardExtension::Enable` (34/0): the account refuses transfers,
909    /// approvals, burns, and close from inside a CPI unless a PDA of the
910    /// calling program owns it.
911    EnableCpiGuard { target = account, authority = owner }
912    data = |_s| encode_cpi_guard(true);
913}
914
915authority_update! {
916    /// `CpiGuardExtension::Disable` (34/1).
917    DisableCpiGuard { target = account, authority = owner }
918    data = |_s| encode_cpi_guard(false);
919}
920
921authority_update! {
922    /// `TransferHookExtension::Update` (36/1): point the mint at another
923    /// hook program, or at none.
924    UpdateTransferHook { target = mint, authority = authority, program_id: Option<&'a Address> }
925    data = |s| encode_update_pointer(IX_TRANSFER_HOOK, s.program_id)?;
926}
927
928authority_update! {
929    /// `MetadataPointerExtension::Update` (39/1).
930    UpdateMetadataPointer { target = mint, authority = authority, metadata_address: Option<&'a Address> }
931    data = |s| encode_update_pointer(IX_METADATA_POINTER, s.metadata_address)?;
932}
933
934authority_update! {
935    /// `GroupPointerExtension::Update` (40/1).
936    UpdateGroupPointer { target = mint, authority = authority, group_address: Option<&'a Address> }
937    data = |s| encode_update_pointer(IX_GROUP_POINTER, s.group_address)?;
938}
939
940authority_update! {
941    /// `GroupMemberPointerExtension::Update` (41/1).
942    UpdateGroupMemberPointer { target = mint, authority = authority, member_address: Option<&'a Address> }
943    data = |s| encode_update_pointer(IX_GROUP_MEMBER_POINTER, s.member_address)?;
944}
945
946authority_update! {
947    /// `ScaledUiAmountExtension::UpdateMultiplier` (43/1): a new
948    /// multiplier that takes effect at `effective_timestamp` (0 for now).
949    UpdateScaledUiAmountMultiplier { target = mint, authority = authority, multiplier: f64, effective_timestamp: i64 }
950    data = |s| encode_update_scaled_ui_amount_multiplier(s.multiplier, s.effective_timestamp)?;
951}
952
953authority_update! {
954    /// `PausableExtension::Pause` (44/1): stop every transfer, mint, and
955    /// burn of the mint.
956    Pause { target = mint, authority = authority }
957    data = |_s| encode_pausable(true);
958}
959
960authority_update! {
961    /// `PausableExtension::Resume` (44/2).
962    Resume { target = mint, authority = authority }
963    data = |_s| encode_pausable(false);
964}
965
966// ---------------------------------------------------------------------
967// Permissioned burn
968
969/// `PermissionedBurnExtension::Burn` (46/1): burn from `account` with the
970/// holder's `authority` and the mint's `permissioned_burn_authority` both
971/// signing.
972pub struct PermissionedBurn<'a> {
973    pub account: &'a AccountView<'a>,
974    pub mint: &'a AccountView<'a>,
975    pub permissioned_burn_authority: &'a AccountView<'a>,
976    pub authority: &'a AccountView<'a>,
977    pub amount: u64,
978}
979
980impl<'a, 'x: 'a> TokenInstruction<'a> for PermissionedBurn<'x> {
981    #[inline(always)]
982    fn emit(
983        &self,
984        multisig_signers: &[&'a AccountView<'a>],
985        sink: &mut impl TokenSink<'a>,
986    ) -> ProgramResult {
987        let data = encode_permissioned_burn(self.amount);
988        let accounts = [
989            InstructionAccount::writable(self.account.address()),
990            InstructionAccount::writable(self.mint.address()),
991            InstructionAccount::readonly_signer(self.permissioned_burn_authority.address()),
992            authority_meta(self.authority, multisig_signers),
993        ];
994        let views = [
995            self.account,
996            self.mint,
997            self.permissioned_burn_authority,
998            self.authority,
999        ];
1000        sink.emit(
1001            &data,
1002            accounts,
1003            views,
1004            &[Trailing::signers(multisig_signers)],
1005        )
1006    }
1007}
1008
1009t22_methods!(PermissionedBurn, authority = authority);
1010
1011/// `PermissionedBurnExtension::BurnChecked` (46/2): [`PermissionedBurn`]
1012/// with the mint's decimals stated.
1013pub struct PermissionedBurnChecked<'a> {
1014    pub account: &'a AccountView<'a>,
1015    pub mint: &'a AccountView<'a>,
1016    pub permissioned_burn_authority: &'a AccountView<'a>,
1017    pub authority: &'a AccountView<'a>,
1018    pub amount: u64,
1019    pub decimals: u8,
1020}
1021
1022impl<'a, 'x: 'a> TokenInstruction<'a> for PermissionedBurnChecked<'x> {
1023    #[inline(always)]
1024    fn emit(
1025        &self,
1026        multisig_signers: &[&'a AccountView<'a>],
1027        sink: &mut impl TokenSink<'a>,
1028    ) -> ProgramResult {
1029        let data = encode_permissioned_burn_checked(self.amount, self.decimals);
1030        let accounts = [
1031            InstructionAccount::writable(self.account.address()),
1032            InstructionAccount::writable(self.mint.address()),
1033            InstructionAccount::readonly_signer(self.permissioned_burn_authority.address()),
1034            authority_meta(self.authority, multisig_signers),
1035        ];
1036        let views = [
1037            self.account,
1038            self.mint,
1039            self.permissioned_burn_authority,
1040            self.authority,
1041        ];
1042        sink.emit(
1043            &data,
1044            accounts,
1045            views,
1046            &[Trailing::signers(multisig_signers)],
1047        )
1048    }
1049}
1050
1051t22_methods!(PermissionedBurnChecked, authority = authority);
1052
1053#[cfg(test)]
1054mod tests {
1055    use super::encoders::*;
1056    use crate::address::Address;
1057
1058    fn addr(byte: u8) -> Address {
1059        Address::new_from_array([byte; 32])
1060    }
1061
1062    #[test]
1063    fn fixed_encoders_match_the_token_2022_wire_format() {
1064        assert_eq!(encode_create_native_mint(), [31]);
1065        assert_eq!(encode_initialize_non_transferable_mint(), [32]);
1066        assert_eq!(encode_withdraw_withheld_tokens_from_mint(), [26, 2]);
1067        assert_eq!(encode_withdraw_withheld_tokens_from_accounts(3), [26, 3, 3]);
1068        assert_eq!(encode_harvest_withheld_tokens_to_mint(), [26, 4]);
1069        assert_eq!(encode_memo_transfer(true), [30, 0]);
1070        assert_eq!(encode_memo_transfer(false), [30, 1]);
1071        assert_eq!(encode_cpi_guard(true), [34, 0]);
1072        assert_eq!(encode_cpi_guard(false), [34, 1]);
1073        assert_eq!(encode_pausable(true), [44, 1]);
1074        assert_eq!(encode_pausable(false), [44, 2]);
1075        assert_eq!(encode_update_interest_rate(-2), [33, 1, 0xfe, 0xff]);
1076        assert_eq!(
1077            encode_transfer_checked_with_fee(1, 9, 2),
1078            [26, 1, 1, 0, 0, 0, 0, 0, 0, 0, 9, 2, 0, 0, 0, 0, 0, 0, 0]
1079        );
1080        assert_eq!(
1081            encode_set_transfer_fee(100, 5).unwrap(),
1082            [26, 5, 100, 0, 5, 0, 0, 0, 0, 0, 0, 0]
1083        );
1084        assert!(encode_set_transfer_fee(10_001, 5).is_err());
1085        assert_eq!(
1086            encode_initialize_default_account_state(2).unwrap(),
1087            [28, 0, 2]
1088        );
1089        assert_eq!(encode_update_default_account_state(1).unwrap(), [28, 1, 1]);
1090        assert!(encode_initialize_default_account_state(0).is_err());
1091        assert!(encode_update_default_account_state(3).is_err());
1092        assert_eq!(encode_permissioned_burn(1), [46, 1, 1, 0, 0, 0, 0, 0, 0, 0]);
1093        assert_eq!(
1094            encode_permissioned_burn_checked(1, 6),
1095            [46, 2, 1, 0, 0, 0, 0, 0, 0, 0, 6]
1096        );
1097    }
1098
1099    #[test]
1100    fn optional_address_encoders_distinguish_coption_from_nullable() {
1101        let a = addr(1);
1102        let b = addr(2);
1103
1104        let some = encode_initialize_mint_close_authority(Some(&a));
1105        assert_eq!(some.as_slice().len(), 34);
1106        assert_eq!(&some.as_slice()[..2], &[25, 1]);
1107        assert_eq!(&some.as_slice()[2..], a.as_bytes());
1108        let none = encode_initialize_mint_close_authority(None);
1109        assert_eq!(none.as_slice(), &[25, 0]);
1110
1111        let fee = encode_initialize_transfer_fee_config(Some(&a), None, 250, 7).unwrap();
1112        let bytes = fee.as_slice();
1113        assert_eq!(bytes.len(), 2 + 33 + 1 + 2 + 8);
1114        assert_eq!(&bytes[..3], &[26, 0, 1]);
1115        assert_eq!(&bytes[3..35], a.as_bytes());
1116        assert_eq!(bytes[35], 0);
1117        assert_eq!(&bytes[36..38], &250u16.to_le_bytes());
1118        assert_eq!(&bytes[38..46], &7u64.to_le_bytes());
1119        assert!(encode_initialize_transfer_fee_config(None, None, 10_001, 0).is_err());
1120
1121        let hook = encode_initialize_pointer(36, Some(&a), Some(&b)).unwrap();
1122        assert_eq!(hook.as_slice().len(), 66);
1123        assert_eq!(&hook.as_slice()[..2], &[36, 0]);
1124        assert_eq!(&hook.as_slice()[2..34], a.as_bytes());
1125        assert_eq!(&hook.as_slice()[34..66], b.as_bytes());
1126        let bare = encode_initialize_pointer(39, None, None).unwrap();
1127        assert_eq!(&bare.as_slice()[2..], &[0u8; 64]);
1128        assert!(encode_initialize_pointer(40, Some(&Address::default()), None).is_err());
1129
1130        let update = encode_update_pointer(41, None).unwrap();
1131        assert_eq!(update.as_slice().len(), 34);
1132        assert_eq!(&update.as_slice()[..2], &[41, 1]);
1133
1134        let interest = encode_initialize_interest_bearing_mint(None, 300).unwrap();
1135        assert_eq!(interest.as_slice().len(), 36);
1136        assert_eq!(&interest.as_slice()[34..], &300i16.to_le_bytes());
1137
1138        let delegate = encode_initialize_permanent_delegate(&a);
1139        assert_eq!(delegate.as_slice()[0], 35);
1140        assert_eq!(&delegate.as_slice()[1..], a.as_bytes());
1141
1142        let pausable = encode_initialize_authority(44, &a).unwrap();
1143        assert_eq!(&pausable.as_slice()[..2], &[44, 0]);
1144        assert_eq!(&pausable.as_slice()[2..], a.as_bytes());
1145        assert!(encode_initialize_authority(46, &Address::default()).is_err());
1146    }
1147
1148    #[test]
1149    fn scaled_ui_amount_multiplier_is_validated_on_bits() {
1150        assert!(multiplier_is_valid(1.0));
1151        assert!(multiplier_is_valid(0.5));
1152        assert!(multiplier_is_valid(f64::MIN_POSITIVE));
1153        assert!(!multiplier_is_valid(0.0));
1154        assert!(!multiplier_is_valid(-0.0));
1155        assert!(!multiplier_is_valid(-1.0));
1156        assert!(!multiplier_is_valid(f64::INFINITY));
1157        assert!(!multiplier_is_valid(f64::NAN));
1158
1159        let init = encode_initialize_scaled_ui_amount(None, 2.0).unwrap();
1160        assert_eq!(init.as_slice().len(), 42);
1161        assert_eq!(&init.as_slice()[..2], &[43, 0]);
1162        assert_eq!(&init.as_slice()[34..], &2.0f64.to_le_bytes());
1163        assert!(encode_initialize_scaled_ui_amount(None, 0.0).is_err());
1164
1165        let update = encode_update_scaled_ui_amount_multiplier(3.0, 17).unwrap();
1166        assert_eq!(&update[..2], &[43, 1]);
1167        assert_eq!(&update[2..10], &3.0f64.to_le_bytes());
1168        assert_eq!(&update[10..18], &17i64.to_le_bytes());
1169    }
1170}