meteora_stable_swap_client/
instruction.rs

1//! Instruction types
2
3#![allow(clippy::too_many_arguments)]
4
5use crate::error::SwapError;
6use crate::fees::Fees;
7use solana_program::{
8    instruction::{AccountMeta, Instruction},
9    program_error::ProgramError,
10    program_pack::Pack,
11    pubkey::Pubkey,
12};
13use std::mem::size_of;
14
15/// Initialize instruction data
16#[repr(C)]
17#[derive(Clone, Copy, Debug, PartialEq)]
18#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
19pub struct InitializeData {
20    /// Nonce used to create valid program address
21    pub nonce: u8,
22    /// Amplification coefficient (A)
23    pub amp_factor: u64,
24    /// Fees
25    pub fees: Fees,
26}
27
28/// Swap instruction data
29#[repr(C)]
30#[derive(Clone, Copy, Debug, PartialEq)]
31#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
32pub struct SwapData {
33    /// SOURCE amount to transfer, output to DESTINATION is based on the exchange rate
34    pub amount_in: u64,
35    /// Minimum amount of DESTINATION token to output, prevents excessive slippage
36    pub minimum_amount_out: u64,
37}
38
39/// Deposit instruction data
40#[repr(C)]
41#[derive(Clone, Copy, Debug, PartialEq)]
42#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
43pub struct DepositData {
44    /// Token A amount to deposit
45    pub token_a_amount: u64,
46    /// Token B amount to deposit
47    pub token_b_amount: u64,
48    /// Minimum LP tokens to mint, prevents excessive slippage
49    pub min_mint_amount: u64,
50}
51
52/// Withdraw instruction data
53#[repr(C)]
54#[derive(Clone, Copy, Debug, PartialEq)]
55#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
56pub struct WithdrawData {
57    /// Amount of pool tokens to burn. User receives an output of token a
58    /// and b based on the percentage of the pool tokens that are returned.
59    pub pool_token_amount: u64,
60    /// Minimum amount of token A to receive, prevents excessive slippage
61    pub minimum_token_a_amount: u64,
62    /// Minimum amount of token B to receive, prevents excessive slippage
63    pub minimum_token_b_amount: u64,
64}
65
66/// Withdraw instruction data
67#[repr(C)]
68#[derive(Clone, Copy, Debug, PartialEq)]
69#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
70pub struct WithdrawOneData {
71    /// Amount of pool tokens to burn. User receives an output of token a
72    /// or b based on the percentage of the pool tokens that are returned.
73    pub pool_token_amount: u64,
74    /// Minimum amount of token A or B to receive, prevents excessive slippage
75    pub minimum_token_amount: u64,
76}
77
78/// RampA instruction data
79#[repr(C)]
80#[derive(Clone, Copy, Debug, PartialEq)]
81#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
82pub struct RampAData {
83    /// Amp. Coefficient to ramp to
84    pub target_amp: u64,
85    /// Unix timestamp to stop ramp
86    pub stop_ramp_ts: i64,
87}
88
89/// Admin only instructions.
90#[repr(C)]
91#[derive(Clone, Copy, Debug, PartialEq)]
92pub enum AdminInstruction {
93    /// Starts a ramp of A to the next value.
94    ///
95    /// 0. `[writable]` StableSwap
96    /// 1. `[signer]` Admin account
97    RampA(RampAData),
98
99    /// Cancels the pending ramp of A.
100    ///
101    /// 0. `[writable]` StableSwap
102    /// 1. `[signer]` Admin account
103    StopRampA,
104
105    /// Pauses swap, deposit, and withdraw_one.
106    ///
107    /// 0. `[writable]` StableSwap
108    /// 1. `[signer]` Admin account
109    Pause,
110
111    /// Unpauses the swap.
112    ///
113    /// 0. `[writable]` StableSwap
114    /// 1. `[signer]` Admin account
115    Unpause,
116
117    /// Sets the account that receives admin fees.
118    ///
119    /// 0. `[writable]` StableSwap
120    /// 1. `[signer]` Admin account
121    /// 2. `[]` Token account to receive fees. Must have mint of Token A or Token B.
122    SetFeeAccount,
123
124    /// Finalizes the admin transfer. This is run after CommitNewAdmin.
125    ///
126    /// 0. `[writable]` StableSwap
127    /// 1. `[signer]` Admin account
128    ApplyNewAdmin,
129
130    /// Commits a new admin. The admin must accept ownership within 3 days.
131    ///
132    /// 0. `[writable]` StableSwap
133    /// 1. `[signer]` Admin account
134    /// 2. `[]` New admin account
135    CommitNewAdmin,
136
137    /// Updates the swap fees.
138    ///
139    /// 0. `[writable]` StableSwap
140    /// 1. `[signer]` Admin account
141    SetNewFees(Fees),
142}
143
144impl AdminInstruction {
145    /// Unpacks a byte buffer into a [AdminInstruction](enum.AdminInstruction.html).
146    pub fn unpack(input: &[u8]) -> Result<Option<Self>, ProgramError> {
147        let (&tag, rest) = input.split_first().ok_or(SwapError::InvalidInstruction)?;
148        Ok(match tag {
149            100 => {
150                let (target_amp, rest) = unpack_u64(rest)?;
151                let (stop_ramp_ts, _rest) = unpack_i64(rest)?;
152                Some(Self::RampA(RampAData {
153                    target_amp,
154                    stop_ramp_ts,
155                }))
156            }
157            101 => Some(Self::StopRampA),
158            102 => Some(Self::Pause),
159            103 => Some(Self::Unpause),
160            104 => Some(Self::SetFeeAccount),
161            105 => Some(Self::ApplyNewAdmin),
162            106 => Some(Self::CommitNewAdmin),
163            107 => {
164                let fees = Fees::unpack_unchecked(rest)?;
165                Some(Self::SetNewFees(fees))
166            }
167            _ => None,
168        })
169    }
170
171    /// Packs a [AdminInstruction](enum.AdminInstruction.html) into a byte buffer.
172    pub fn pack(&self) -> Vec<u8> {
173        let mut buf = Vec::with_capacity(size_of::<Self>());
174        match *self {
175            Self::RampA(RampAData {
176                target_amp,
177                stop_ramp_ts,
178            }) => {
179                buf.push(100);
180                buf.extend_from_slice(&target_amp.to_le_bytes());
181                buf.extend_from_slice(&stop_ramp_ts.to_le_bytes());
182            }
183            Self::StopRampA => buf.push(101),
184            Self::Pause => buf.push(102),
185            Self::Unpause => buf.push(103),
186            Self::SetFeeAccount => buf.push(104),
187            Self::ApplyNewAdmin => buf.push(105),
188            Self::CommitNewAdmin => buf.push(106),
189            Self::SetNewFees(fees) => {
190                buf.push(107);
191                let mut fees_slice = [0u8; Fees::LEN];
192                Pack::pack_into_slice(&fees, &mut fees_slice[..]);
193                buf.extend_from_slice(&fees_slice);
194            }
195        }
196        buf
197    }
198}
199
200/// Creates a 'ramp_a' instruction
201pub fn ramp_a(
202    swap_pubkey: &Pubkey,
203    admin_pubkey: &Pubkey,
204    target_amp: u64,
205    stop_ramp_ts: i64,
206) -> Result<Instruction, ProgramError> {
207    let data = AdminInstruction::RampA(RampAData {
208        target_amp,
209        stop_ramp_ts,
210    })
211    .pack();
212
213    let accounts = vec![
214        AccountMeta::new(*swap_pubkey, false),
215        AccountMeta::new_readonly(*admin_pubkey, true),
216    ];
217
218    Ok(Instruction {
219        program_id: crate::ID,
220        accounts,
221        data,
222    })
223}
224
225/// Creates a 'stop_ramp_a' instruction
226pub fn stop_ramp_a(
227    swap_pubkey: &Pubkey,
228    admin_pubkey: &Pubkey,
229) -> Result<Instruction, ProgramError> {
230    let data = AdminInstruction::StopRampA.pack();
231
232    let accounts = vec![
233        AccountMeta::new(*swap_pubkey, false),
234        AccountMeta::new_readonly(*admin_pubkey, true),
235    ];
236
237    Ok(Instruction {
238        program_id: crate::ID,
239        accounts,
240        data,
241    })
242}
243
244/// Creates a 'pause' instruction
245pub fn pause(swap_pubkey: &Pubkey, admin_pubkey: &Pubkey) -> Result<Instruction, ProgramError> {
246    let data = AdminInstruction::Pause.pack();
247
248    let accounts = vec![
249        AccountMeta::new(*swap_pubkey, false),
250        AccountMeta::new_readonly(*admin_pubkey, true),
251    ];
252
253    Ok(Instruction {
254        program_id: crate::ID,
255        accounts,
256        data,
257    })
258}
259
260/// Creates a 'unpause' instruction
261pub fn unpause(swap_pubkey: &Pubkey, admin_pubkey: &Pubkey) -> Result<Instruction, ProgramError> {
262    let data = AdminInstruction::Unpause.pack();
263
264    let accounts = vec![
265        AccountMeta::new(*swap_pubkey, false),
266        AccountMeta::new_readonly(*admin_pubkey, true),
267    ];
268
269    Ok(Instruction {
270        program_id: crate::ID,
271        accounts,
272        data,
273    })
274}
275
276/// Creates a 'apply_new_admin' instruction
277pub fn apply_new_admin(
278    swap_pubkey: &Pubkey,
279    admin_pubkey: &Pubkey,
280) -> Result<Instruction, ProgramError> {
281    let data = AdminInstruction::ApplyNewAdmin.pack();
282
283    let accounts = vec![
284        AccountMeta::new(*swap_pubkey, false),
285        AccountMeta::new_readonly(*admin_pubkey, true),
286    ];
287
288    Ok(Instruction {
289        program_id: crate::ID,
290        accounts,
291        data,
292    })
293}
294
295/// Creates a 'commit_new_admin' instruction
296pub fn commit_new_admin(
297    swap_pubkey: &Pubkey,
298    admin_pubkey: &Pubkey,
299    new_admin_pubkey: &Pubkey,
300) -> Result<Instruction, ProgramError> {
301    let data = AdminInstruction::CommitNewAdmin.pack();
302
303    let accounts = vec![
304        AccountMeta::new(*swap_pubkey, false),
305        AccountMeta::new_readonly(*admin_pubkey, true),
306        AccountMeta::new_readonly(*new_admin_pubkey, false),
307    ];
308
309    Ok(Instruction {
310        program_id: crate::ID,
311        accounts,
312        data,
313    })
314}
315
316/// Creates a 'set_fee_account' instruction
317pub fn set_fee_account(
318    swap_pubkey: &Pubkey,
319    admin_pubkey: &Pubkey,
320    new_fee_account_pubkey: &Pubkey,
321) -> Result<Instruction, ProgramError> {
322    let data = AdminInstruction::SetFeeAccount.pack();
323
324    let accounts = vec![
325        AccountMeta::new(*swap_pubkey, false),
326        AccountMeta::new_readonly(*admin_pubkey, true),
327        AccountMeta::new_readonly(*new_fee_account_pubkey, false),
328    ];
329
330    Ok(Instruction {
331        program_id: crate::ID,
332        accounts,
333        data,
334    })
335}
336
337/// Creates a 'set_new_fees' instruction
338pub fn set_new_fees(
339    swap_pubkey: &Pubkey,
340    admin_pubkey: &Pubkey,
341    new_fees: Fees,
342) -> Result<Instruction, ProgramError> {
343    let data = AdminInstruction::SetNewFees(new_fees).pack();
344
345    let accounts = vec![
346        AccountMeta::new(*swap_pubkey, false),
347        AccountMeta::new_readonly(*admin_pubkey, true),
348    ];
349
350    Ok(Instruction {
351        program_id: crate::ID,
352        accounts,
353        data,
354    })
355}
356
357/// Instructions supported by the SwapInfo program.
358#[repr(C)]
359#[derive(Copy, Clone, Debug, PartialEq)]
360#[cfg_attr(feature = "fuzz", derive(arbitrary::Arbitrary))]
361pub enum SwapInstruction {
362    /// Initializes a new SwapInfo.
363    ///
364    /// 0. `[writable, signer]` New StableSwap to create.
365    /// 1. `[]` $authority derived from `create_program_address(&[StableSwap account])`
366    /// 2. `[]` admin Account.
367    /// 3. `[]` admin_fee_a admin fee Account for token_a.
368    /// 4. `[]` admin_fee_b admin fee Account for token_b.
369    /// 5. `[]` token_a Account. Must be non zero, owned by $authority.
370    /// 6. `[]` token_b Account. Must be non zero, owned by $authority.
371    /// 7. `[writable]` Pool Token Mint. Must be empty, owned by $authority.
372    Initialize(InitializeData),
373
374    /// Swap the tokens in the pool.
375    ///
376    /// 0. `[]`StableSwap
377    /// 1. `[]` $authority
378    /// 2. `[signer]` User authority.
379    /// 3. `[writable]` token_(A|B) SOURCE Account, amount is transferable by $authority,
380    /// 4. `[writable]` token_(A|B) Base Account to swap INTO.  Must be the SOURCE token.
381    /// 5. `[writable]` token_(A|B) Base Account to swap FROM.  Must be the DESTINATION token.
382    /// 6. `[writable]` token_(A|B) DESTINATION Account assigned to USER as the owner.
383    /// 7. `[writable]` token_(A|B) admin fee Account. Must have same mint as DESTINATION token.
384    /// 8. `[]` Token program id
385    Swap(SwapData),
386
387    /// Deposit some tokens into the pool.  The output is a "pool" token representing ownership
388    /// into the pool. Inputs are converted to the current ratio.
389    ///
390    /// 0. `[]`StableSwap
391    /// 1. `[]` $authority
392    /// 2. `[signer]` User authority.
393    /// 3. `[writable]` token_a $authority can transfer amount,
394    /// 4. `[writable]` token_b $authority can transfer amount,
395    /// 5. `[writable]` token_a Base Account to deposit into.
396    /// 6. `[writable]` token_b Base Account to deposit into.
397    /// 7. `[writable]` Pool MINT account, $authority is the owner.
398    /// 8. `[writable]` Pool Account to deposit the generated tokens, user is the owner.
399    /// 9. `[]` Token program id
400    Deposit(DepositData),
401
402    /// Withdraw tokens from the pool at the current ratio.
403    ///
404    /// 0. `[]`StableSwap
405    /// 1. `[]` $authority
406    /// 2. `[signer]` User authority.
407    /// 3. `[writable]` Pool mint account, $authority is the owner
408    /// 4. `[writable]` SOURCE Pool account, amount is transferable by $authority.
409    /// 5. `[writable]` token_a Swap Account to withdraw FROM.
410    /// 6. `[writable]` token_b Swap Account to withdraw FROM.
411    /// 7. `[writable]` token_a user Account to credit.
412    /// 8. `[writable]` token_b user Account to credit.
413    /// 9. `[writable]` admin_fee_a admin fee Account for token_a.
414    /// 10. `[writable]` admin_fee_b admin fee Account for token_b.
415    /// 11. `[]` Token program id
416    Withdraw(WithdrawData),
417
418    /// Withdraw one token from the pool at the current ratio.
419    ///
420    /// 0. `[]`StableSwap
421    /// 1. `[]` $authority
422    /// 2. `[signer]` User authority.
423    /// 3. `[writable]` Pool mint account, $authority is the owner
424    /// 4. `[writable]` SOURCE Pool account, amount is transferable by $authority.
425    /// 5. `[writable]` token_(A|B) BASE token Swap Account to withdraw FROM.
426    /// 6. `[writable]` token_(A|B) QUOTE token Swap Account to exchange to base token.
427    /// 7. `[writable]` token_(A|B) BASE token user Account to credit.
428    /// 8. `[writable]` token_(A|B) admin fee Account. Must have same mint as BASE token.
429    /// 9. `[]` Token program id
430    WithdrawOne(WithdrawOneData),
431}
432
433impl SwapInstruction {
434    /// Unpacks a byte buffer into a [SwapInstruction](enum.SwapInstruction.html).
435    pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
436        let (&tag, rest) = input.split_first().ok_or(SwapError::InvalidInstruction)?;
437        Ok(match tag {
438            0 => {
439                let (&nonce, rest) = rest.split_first().ok_or(SwapError::InvalidInstruction)?;
440                let (amp_factor, rest) = unpack_u64(rest)?;
441                let fees = Fees::unpack_unchecked(rest)?;
442                Self::Initialize(InitializeData {
443                    nonce,
444                    amp_factor,
445                    fees,
446                })
447            }
448            1 => {
449                let (amount_in, rest) = unpack_u64(rest)?;
450                let (minimum_amount_out, _rest) = unpack_u64(rest)?;
451                Self::Swap(SwapData {
452                    amount_in,
453                    minimum_amount_out,
454                })
455            }
456            2 => {
457                let (token_a_amount, rest) = unpack_u64(rest)?;
458                let (token_b_amount, rest) = unpack_u64(rest)?;
459                let (min_mint_amount, _rest) = unpack_u64(rest)?;
460                Self::Deposit(DepositData {
461                    token_a_amount,
462                    token_b_amount,
463                    min_mint_amount,
464                })
465            }
466            3 => {
467                let (pool_token_amount, rest) = unpack_u64(rest)?;
468                let (minimum_token_a_amount, rest) = unpack_u64(rest)?;
469                let (minimum_token_b_amount, _rest) = unpack_u64(rest)?;
470                Self::Withdraw(WithdrawData {
471                    pool_token_amount,
472                    minimum_token_a_amount,
473                    minimum_token_b_amount,
474                })
475            }
476            4 => {
477                let (pool_token_amount, rest) = unpack_u64(rest)?;
478                let (minimum_token_amount, _rest) = unpack_u64(rest)?;
479                Self::WithdrawOne(WithdrawOneData {
480                    pool_token_amount,
481                    minimum_token_amount,
482                })
483            }
484            _ => return Err(SwapError::InvalidInstruction.into()),
485        })
486    }
487
488    /// Packs a [SwapInstruction](enum.SwapInstruction.html) into a byte buffer.
489    pub fn pack(&self) -> Vec<u8> {
490        let mut buf = Vec::with_capacity(size_of::<Self>());
491        match *self {
492            Self::Initialize(InitializeData {
493                nonce,
494                amp_factor,
495                fees,
496            }) => {
497                buf.push(0);
498                buf.push(nonce);
499                buf.extend_from_slice(&amp_factor.to_le_bytes());
500                let mut fees_slice = [0u8; Fees::LEN];
501                Pack::pack_into_slice(&fees, &mut fees_slice[..]);
502                buf.extend_from_slice(&fees_slice);
503            }
504            Self::Swap(SwapData {
505                amount_in,
506                minimum_amount_out,
507            }) => {
508                buf.push(1);
509                buf.extend_from_slice(&amount_in.to_le_bytes());
510                buf.extend_from_slice(&minimum_amount_out.to_le_bytes());
511            }
512            Self::Deposit(DepositData {
513                token_a_amount,
514                token_b_amount,
515                min_mint_amount,
516            }) => {
517                buf.push(2);
518                buf.extend_from_slice(&token_a_amount.to_le_bytes());
519                buf.extend_from_slice(&token_b_amount.to_le_bytes());
520                buf.extend_from_slice(&min_mint_amount.to_le_bytes());
521            }
522            Self::Withdraw(WithdrawData {
523                pool_token_amount,
524                minimum_token_a_amount,
525                minimum_token_b_amount,
526            }) => {
527                buf.push(3);
528                buf.extend_from_slice(&pool_token_amount.to_le_bytes());
529                buf.extend_from_slice(&minimum_token_a_amount.to_le_bytes());
530                buf.extend_from_slice(&minimum_token_b_amount.to_le_bytes());
531            }
532            Self::WithdrawOne(WithdrawOneData {
533                pool_token_amount,
534                minimum_token_amount,
535            }) => {
536                buf.push(4);
537                buf.extend_from_slice(&pool_token_amount.to_le_bytes());
538                buf.extend_from_slice(&minimum_token_amount.to_le_bytes());
539            }
540        }
541        buf
542    }
543}
544
545/// Creates an 'initialize' instruction.
546pub fn initialize(
547    pool_token_program_id: &Pubkey, // Token program used for the pool token
548    swap_pubkey: &Pubkey,
549    swap_authority_key: &Pubkey,
550    admin_pubkey: &Pubkey,
551    admin_fee_a_pubkey: &Pubkey,
552    admin_fee_b_pubkey: &Pubkey,
553    token_a_mint_pubkey: &Pubkey,
554    token_a_pubkey: &Pubkey,
555    token_b_mint_pubkey: &Pubkey,
556    token_b_pubkey: &Pubkey,
557    pool_mint_pubkey: &Pubkey,
558    destination_pubkey: &Pubkey, // Destination to mint pool tokens for bootstrapper
559    nonce: u8,
560    amp_factor: u64,
561    fees: Fees,
562) -> Result<Instruction, ProgramError> {
563    let data = SwapInstruction::Initialize(InitializeData {
564        nonce,
565        amp_factor,
566        fees,
567    })
568    .pack();
569
570    let accounts = vec![
571        AccountMeta::new(*swap_pubkey, true),
572        AccountMeta::new_readonly(*swap_authority_key, false),
573        AccountMeta::new_readonly(*admin_pubkey, false),
574        AccountMeta::new_readonly(*admin_fee_a_pubkey, false),
575        AccountMeta::new_readonly(*admin_fee_b_pubkey, false),
576        AccountMeta::new_readonly(*token_a_mint_pubkey, false),
577        AccountMeta::new_readonly(*token_a_pubkey, false),
578        AccountMeta::new_readonly(*token_b_mint_pubkey, false),
579        AccountMeta::new_readonly(*token_b_pubkey, false),
580        AccountMeta::new(*pool_mint_pubkey, false),
581        AccountMeta::new(*destination_pubkey, false),
582        AccountMeta::new_readonly(*pool_token_program_id, false),
583    ];
584
585    Ok(Instruction {
586        program_id: crate::ID,
587        accounts,
588        data,
589    })
590}
591
592/// Creates a 'deposit' instruction.
593#[inline(always)]
594pub fn deposit(
595    token_program_id: &Pubkey,
596    swap_pubkey: &Pubkey,
597    swap_authority_key: &Pubkey,
598    user_authority_key: &Pubkey,
599    deposit_token_a_pubkey: &Pubkey,
600    deposit_token_b_pubkey: &Pubkey,
601    swap_token_a_pubkey: &Pubkey,
602    swap_token_b_pubkey: &Pubkey,
603    pool_mint_pubkey: &Pubkey,
604    destination_pubkey: &Pubkey,
605    token_a_amount: u64,
606    token_b_amount: u64,
607    min_mint_amount: u64,
608) -> Result<Instruction, ProgramError> {
609    let data = SwapInstruction::Deposit(DepositData {
610        token_a_amount,
611        token_b_amount,
612        min_mint_amount,
613    })
614    .pack();
615
616    let accounts = vec![
617        AccountMeta::new_readonly(*swap_pubkey, false),
618        AccountMeta::new_readonly(*swap_authority_key, false),
619        AccountMeta::new_readonly(*user_authority_key, true),
620        AccountMeta::new(*deposit_token_a_pubkey, false),
621        AccountMeta::new(*deposit_token_b_pubkey, false),
622        AccountMeta::new(*swap_token_a_pubkey, false),
623        AccountMeta::new(*swap_token_b_pubkey, false),
624        AccountMeta::new(*pool_mint_pubkey, false),
625        AccountMeta::new(*destination_pubkey, false),
626        AccountMeta::new_readonly(*token_program_id, false),
627    ];
628
629    Ok(Instruction {
630        program_id: crate::ID,
631        accounts,
632        data,
633    })
634}
635
636/// Creates a 'withdraw' instruction.
637#[inline(always)]
638pub fn withdraw(
639    token_program_id: &Pubkey,
640    swap_pubkey: &Pubkey,
641    swap_authority_key: &Pubkey,
642    user_authority_key: &Pubkey,
643    pool_mint_pubkey: &Pubkey,
644    source_pubkey: &Pubkey,
645    swap_token_a_pubkey: &Pubkey,
646    swap_token_b_pubkey: &Pubkey,
647    destination_token_a_pubkey: &Pubkey,
648    destination_token_b_pubkey: &Pubkey,
649    admin_fee_a_pubkey: &Pubkey,
650    admin_fee_b_pubkey: &Pubkey,
651    pool_token_amount: u64,
652    minimum_token_a_amount: u64,
653    minimum_token_b_amount: u64,
654) -> Result<Instruction, ProgramError> {
655    let data = SwapInstruction::Withdraw(WithdrawData {
656        pool_token_amount,
657        minimum_token_a_amount,
658        minimum_token_b_amount,
659    })
660    .pack();
661
662    let accounts = vec![
663        AccountMeta::new_readonly(*swap_pubkey, false),
664        AccountMeta::new_readonly(*swap_authority_key, false),
665        AccountMeta::new_readonly(*user_authority_key, true),
666        AccountMeta::new(*pool_mint_pubkey, false),
667        AccountMeta::new(*source_pubkey, false),
668        AccountMeta::new(*swap_token_a_pubkey, false),
669        AccountMeta::new(*swap_token_b_pubkey, false),
670        AccountMeta::new(*destination_token_a_pubkey, false),
671        AccountMeta::new(*destination_token_b_pubkey, false),
672        AccountMeta::new(*admin_fee_a_pubkey, false),
673        AccountMeta::new(*admin_fee_b_pubkey, false),
674        AccountMeta::new_readonly(*token_program_id, false),
675    ];
676
677    Ok(Instruction {
678        program_id: crate::ID,
679        accounts,
680        data,
681    })
682}
683
684/// Creates a 'swap' instruction.
685#[inline(always)]
686pub fn swap(
687    token_program_id: &Pubkey,
688    swap_pubkey: &Pubkey,
689    swap_authority_key: &Pubkey,
690    user_authority_key: &Pubkey,
691    source_pubkey: &Pubkey,
692    swap_source_pubkey: &Pubkey,
693    swap_destination_pubkey: &Pubkey,
694    destination_pubkey: &Pubkey,
695    admin_fee_destination_pubkey: &Pubkey,
696    amount_in: u64,
697    minimum_amount_out: u64,
698) -> Result<Instruction, ProgramError> {
699    let data = SwapInstruction::Swap(SwapData {
700        amount_in,
701        minimum_amount_out,
702    })
703    .pack();
704
705    let accounts = vec![
706        AccountMeta::new_readonly(*swap_pubkey, false),
707        AccountMeta::new_readonly(*swap_authority_key, false),
708        AccountMeta::new_readonly(*user_authority_key, true),
709        AccountMeta::new(*source_pubkey, false),
710        AccountMeta::new(*swap_source_pubkey, false),
711        AccountMeta::new(*swap_destination_pubkey, false),
712        AccountMeta::new(*destination_pubkey, false),
713        AccountMeta::new(*admin_fee_destination_pubkey, false),
714        AccountMeta::new_readonly(*token_program_id, false),
715    ];
716
717    Ok(Instruction {
718        program_id: crate::ID,
719        accounts,
720        data,
721    })
722}
723
724/// Creates a 'withdraw_one' instruction.
725#[inline(always)]
726pub fn withdraw_one(
727    token_program_id: &Pubkey,
728    swap_pubkey: &Pubkey,
729    swap_authority_key: &Pubkey,
730    user_authority_key: &Pubkey,
731    pool_mint_pubkey: &Pubkey,
732    source_pubkey: &Pubkey,
733    swap_base_token_pubkey: &Pubkey,
734    swap_quote_token_pubkey: &Pubkey,
735    base_destination_pubkey: &Pubkey,
736    admin_fee_destination_pubkey: &Pubkey,
737    pool_token_amount: u64,
738    minimum_token_amount: u64,
739) -> Result<Instruction, ProgramError> {
740    let data = SwapInstruction::WithdrawOne(WithdrawOneData {
741        pool_token_amount,
742        minimum_token_amount,
743    })
744    .pack();
745
746    let accounts = vec![
747        AccountMeta::new_readonly(*swap_pubkey, false),
748        AccountMeta::new_readonly(*swap_authority_key, false),
749        AccountMeta::new_readonly(*user_authority_key, true),
750        AccountMeta::new(*pool_mint_pubkey, false),
751        AccountMeta::new(*source_pubkey, false),
752        AccountMeta::new(*swap_base_token_pubkey, false),
753        AccountMeta::new(*swap_quote_token_pubkey, false),
754        AccountMeta::new(*base_destination_pubkey, false),
755        AccountMeta::new(*admin_fee_destination_pubkey, false),
756        AccountMeta::new_readonly(*token_program_id, false),
757    ];
758
759    Ok(Instruction {
760        program_id: crate::ID,
761        accounts,
762        data,
763    })
764}
765
766fn unpack_i64(input: &[u8]) -> Result<(i64, &[u8]), ProgramError> {
767    if input.len() >= 8 {
768        let (amount, rest) = input.split_at(8);
769        let amount = amount
770            .get(..8)
771            .and_then(|slice| slice.try_into().ok())
772            .map(i64::from_le_bytes)
773            .ok_or(SwapError::InvalidInstruction)?;
774        Ok((amount, rest))
775    } else {
776        Err(SwapError::InvalidInstruction.into())
777    }
778}
779
780fn unpack_u64(input: &[u8]) -> Result<(u64, &[u8]), ProgramError> {
781    if input.len() >= 8 {
782        let (amount, rest) = input.split_at(8);
783        let amount = amount
784            .get(..8)
785            .and_then(|slice| slice.try_into().ok())
786            .map(u64::from_le_bytes)
787            .ok_or(SwapError::InvalidInstruction)?;
788        Ok((amount, rest))
789    } else {
790        Err(SwapError::InvalidInstruction.into())
791    }
792}
793
794#[cfg(test)]
795#[allow(clippy::unwrap_used)]
796mod tests {
797    use super::*;
798
799    #[test]
800    fn test_admin_instruction_packing() {
801        let target_amp = 100;
802        let stop_ramp_ts = i64::MAX;
803        let check = AdminInstruction::RampA(RampAData {
804            target_amp,
805            stop_ramp_ts,
806        });
807        let packed = check.pack();
808        let mut expect = vec![100_u8];
809        expect.extend_from_slice(&target_amp.to_le_bytes());
810        expect.extend_from_slice(&stop_ramp_ts.to_le_bytes());
811        assert_eq!(packed, expect);
812        let unpacked = AdminInstruction::unpack(&expect).unwrap();
813        assert_eq!(unpacked, Some(check));
814
815        let check = AdminInstruction::StopRampA;
816        let packed = check.pack();
817        let expect = vec![101_u8];
818        assert_eq!(packed, expect);
819        let unpacked = AdminInstruction::unpack(&expect).unwrap();
820        assert_eq!(unpacked, Some(check));
821
822        let check = AdminInstruction::Pause;
823        let packed = check.pack();
824        let expect = vec![102_u8];
825        assert_eq!(packed, expect);
826        let unpacked = AdminInstruction::unpack(&expect).unwrap();
827        assert_eq!(unpacked, Some(check));
828
829        let check = AdminInstruction::Unpause;
830        let packed = check.pack();
831        let expect = vec![103_u8];
832        assert_eq!(packed, expect);
833        let unpacked = AdminInstruction::unpack(&expect).unwrap();
834        assert_eq!(unpacked, Some(check));
835
836        let check = AdminInstruction::SetFeeAccount;
837        let packed = check.pack();
838        let expect = vec![104_u8];
839        assert_eq!(packed, expect);
840        let unpacked = AdminInstruction::unpack(&expect).unwrap();
841        assert_eq!(unpacked, Some(check));
842
843        let check = AdminInstruction::ApplyNewAdmin;
844        let packed = check.pack();
845        let expect = vec![105_u8];
846        assert_eq!(packed, expect);
847        let unpacked = AdminInstruction::unpack(&expect).unwrap();
848        assert_eq!(unpacked, Some(check));
849
850        let check = AdminInstruction::CommitNewAdmin;
851        let packed = check.pack();
852        let expect = vec![106_u8];
853        assert_eq!(packed, expect);
854        let unpacked = AdminInstruction::unpack(&expect).unwrap();
855        assert_eq!(unpacked, Some(check));
856
857        let new_fees = Fees {
858            admin_trade_fee_numerator: 1,
859            admin_trade_fee_denominator: 2,
860            admin_withdraw_fee_numerator: 3,
861            admin_withdraw_fee_denominator: 4,
862            trade_fee_numerator: 5,
863            trade_fee_denominator: 6,
864            withdraw_fee_numerator: 7,
865            withdraw_fee_denominator: 8,
866        };
867        let check = AdminInstruction::SetNewFees(new_fees);
868        let packed = check.pack();
869        let mut expect = vec![107_u8];
870        let mut new_fees_slice = [0u8; Fees::LEN];
871        new_fees.pack_into_slice(&mut new_fees_slice[..]);
872        expect.extend_from_slice(&new_fees_slice);
873        assert_eq!(packed, expect);
874        let unpacked = AdminInstruction::unpack(&expect).unwrap();
875        assert_eq!(unpacked, Some(check));
876    }
877
878    #[test]
879    fn test_swap_instruction_packing() {
880        let nonce: u8 = 255;
881        let amp_factor: u64 = 0;
882        let fees = Fees {
883            admin_trade_fee_numerator: 1,
884            admin_trade_fee_denominator: 2,
885            admin_withdraw_fee_numerator: 3,
886            admin_withdraw_fee_denominator: 4,
887            trade_fee_numerator: 5,
888            trade_fee_denominator: 6,
889            withdraw_fee_numerator: 7,
890            withdraw_fee_denominator: 8,
891        };
892        let check = SwapInstruction::Initialize(InitializeData {
893            nonce,
894            amp_factor,
895            fees,
896        });
897        let packed = check.pack();
898        let mut expect = vec![0_u8, nonce];
899        expect.extend_from_slice(&amp_factor.to_le_bytes());
900        let mut fees_slice = [0u8; Fees::LEN];
901        fees.pack_into_slice(&mut fees_slice[..]);
902        expect.extend_from_slice(&fees_slice);
903        assert_eq!(packed, expect);
904        let unpacked = SwapInstruction::unpack(&expect).unwrap();
905        assert_eq!(unpacked, check);
906
907        let amount_in: u64 = 2;
908        let minimum_amount_out: u64 = 10;
909        let check = SwapInstruction::Swap(SwapData {
910            amount_in,
911            minimum_amount_out,
912        });
913        let packed = check.pack();
914        let mut expect = vec![1];
915        expect.extend_from_slice(&amount_in.to_le_bytes());
916        expect.extend_from_slice(&minimum_amount_out.to_le_bytes());
917        assert_eq!(packed, expect);
918        let unpacked = SwapInstruction::unpack(&expect).unwrap();
919        assert_eq!(unpacked, check);
920
921        let token_a_amount: u64 = 10;
922        let token_b_amount: u64 = 20;
923        let min_mint_amount: u64 = 5;
924        let check = SwapInstruction::Deposit(DepositData {
925            token_a_amount,
926            token_b_amount,
927            min_mint_amount,
928        });
929        let packed = check.pack();
930        let mut expect = vec![2];
931        expect.extend_from_slice(&token_a_amount.to_le_bytes());
932        expect.extend_from_slice(&token_b_amount.to_le_bytes());
933        expect.extend_from_slice(&min_mint_amount.to_le_bytes());
934        assert_eq!(packed, expect);
935        let unpacked = SwapInstruction::unpack(&expect).unwrap();
936        assert_eq!(unpacked, check);
937
938        let pool_token_amount: u64 = 1212438012089;
939        let minimum_token_a_amount: u64 = 102198761982612;
940        let minimum_token_b_amount: u64 = 2011239855213;
941        let check = SwapInstruction::Withdraw(WithdrawData {
942            pool_token_amount,
943            minimum_token_a_amount,
944            minimum_token_b_amount,
945        });
946        let packed = check.pack();
947        let mut expect = vec![3];
948        expect.extend_from_slice(&pool_token_amount.to_le_bytes());
949        expect.extend_from_slice(&minimum_token_a_amount.to_le_bytes());
950        expect.extend_from_slice(&minimum_token_b_amount.to_le_bytes());
951        assert_eq!(packed, expect);
952        let unpacked = SwapInstruction::unpack(&expect).unwrap();
953        assert_eq!(unpacked, check);
954
955        let pool_token_amount: u64 = 1212438012089;
956        let minimum_token_amount: u64 = 102198761982612;
957        let check = SwapInstruction::WithdrawOne(WithdrawOneData {
958            pool_token_amount,
959            minimum_token_amount,
960        });
961        let packed = check.pack();
962        let mut expect = vec![4];
963        expect.extend_from_slice(&pool_token_amount.to_le_bytes());
964        expect.extend_from_slice(&minimum_token_amount.to_le_bytes());
965        assert_eq!(packed, expect);
966        let unpacked = SwapInstruction::unpack(&expect).unwrap();
967        assert_eq!(unpacked, check);
968    }
969}