1#![allow(clippy::too_many_arguments)]
4
5use crate::curve::{base::SwapCurve, fees::Fees};
6use crate::error::SwapError;
7use solana_program::{
8 instruction::{AccountMeta, Instruction},
9 program_error::ProgramError,
10 program_pack::Pack,
11 pubkey::Pubkey,
12};
13use std::convert::TryInto;
14use std::mem::size_of;
15
16#[cfg(feature = "fuzz")]
17use arbitrary::Arbitrary;
18
19#[repr(C)]
21#[derive(Debug, PartialEq)]
22pub struct Initialize {
23 pub fees: Fees,
25 pub swap_curve: SwapCurve,
28}
29
30#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
32#[repr(C)]
33#[derive(Clone, Debug, PartialEq)]
34pub struct Swap {
35 pub amount_in: u64,
37 pub minimum_amount_out: u64,
39}
40
41#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
43#[repr(C)]
44#[derive(Clone, Debug, PartialEq)]
45pub struct DepositAllTokenTypes {
46 pub pool_token_amount: u64,
49 pub maximum_token_a_amount: u64,
51 pub maximum_token_b_amount: u64,
53}
54
55#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
57#[repr(C)]
58#[derive(Clone, Debug, PartialEq)]
59pub struct WithdrawAllTokenTypes {
60 pub pool_token_amount: u64,
63 pub minimum_token_a_amount: u64,
65 pub minimum_token_b_amount: u64,
67}
68
69#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
71#[repr(C)]
72#[derive(Clone, Debug, PartialEq)]
73pub struct DepositSingleTokenTypeExactAmountIn {
74 pub source_token_amount: u64,
76 pub minimum_pool_token_amount: u64,
79}
80
81#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
83#[repr(C)]
84#[derive(Clone, Debug, PartialEq)]
85pub struct WithdrawSingleTokenTypeExactAmountOut {
86 pub destination_token_amount: u64,
88 pub maximum_pool_token_amount: u64,
91}
92
93#[repr(C)]
95#[derive(Debug, PartialEq)]
96pub enum SwapInstruction {
97 Initialize(Initialize),
110
111 Swap(Swap),
125
126 DepositAllTokenTypes(DepositAllTokenTypes),
141
142 WithdrawAllTokenTypes(WithdrawAllTokenTypes),
158
159 DepositSingleTokenTypeExactAmountIn(DepositSingleTokenTypeExactAmountIn),
173
174 WithdrawSingleTokenTypeExactAmountOut(WithdrawSingleTokenTypeExactAmountOut),
188}
189
190impl SwapInstruction {
191 pub fn unpack(input: &[u8]) -> Result<Self, ProgramError> {
193 let (&tag, rest) = input.split_first().ok_or(SwapError::InvalidInstruction)?;
194 Ok(match tag {
195 0 => {
196 if rest.len() >= Fees::LEN {
197 let (fees, rest) = rest.split_at(Fees::LEN);
198 let fees = Fees::unpack_unchecked(fees)?;
199 let swap_curve = SwapCurve::unpack_unchecked(rest)?;
200 Self::Initialize(Initialize { fees, swap_curve })
201 } else {
202 return Err(SwapError::InvalidInstruction.into());
203 }
204 }
205 1 => {
206 let (amount_in, rest) = Self::unpack_u64(rest)?;
207 let (minimum_amount_out, _rest) = Self::unpack_u64(rest)?;
208 Self::Swap(Swap {
209 amount_in,
210 minimum_amount_out,
211 })
212 }
213 2 => {
214 let (pool_token_amount, rest) = Self::unpack_u64(rest)?;
215 let (maximum_token_a_amount, rest) = Self::unpack_u64(rest)?;
216 let (maximum_token_b_amount, _rest) = Self::unpack_u64(rest)?;
217 Self::DepositAllTokenTypes(DepositAllTokenTypes {
218 pool_token_amount,
219 maximum_token_a_amount,
220 maximum_token_b_amount,
221 })
222 }
223 3 => {
224 let (pool_token_amount, rest) = Self::unpack_u64(rest)?;
225 let (minimum_token_a_amount, rest) = Self::unpack_u64(rest)?;
226 let (minimum_token_b_amount, _rest) = Self::unpack_u64(rest)?;
227 Self::WithdrawAllTokenTypes(WithdrawAllTokenTypes {
228 pool_token_amount,
229 minimum_token_a_amount,
230 minimum_token_b_amount,
231 })
232 }
233 4 => {
234 let (source_token_amount, rest) = Self::unpack_u64(rest)?;
235 let (minimum_pool_token_amount, _rest) = Self::unpack_u64(rest)?;
236 Self::DepositSingleTokenTypeExactAmountIn(DepositSingleTokenTypeExactAmountIn {
237 source_token_amount,
238 minimum_pool_token_amount,
239 })
240 }
241 5 => {
242 let (destination_token_amount, rest) = Self::unpack_u64(rest)?;
243 let (maximum_pool_token_amount, _rest) = Self::unpack_u64(rest)?;
244 Self::WithdrawSingleTokenTypeExactAmountOut(WithdrawSingleTokenTypeExactAmountOut {
245 destination_token_amount,
246 maximum_pool_token_amount,
247 })
248 }
249 _ => return Err(SwapError::InvalidInstruction.into()),
250 })
251 }
252
253 fn unpack_u64(input: &[u8]) -> Result<(u64, &[u8]), ProgramError> {
254 if input.len() >= 8 {
255 let (amount, rest) = input.split_at(8);
256 let amount = amount
257 .get(..8)
258 .and_then(|slice| slice.try_into().ok())
259 .map(u64::from_le_bytes)
260 .ok_or(SwapError::InvalidInstruction)?;
261 Ok((amount, rest))
262 } else {
263 Err(SwapError::InvalidInstruction.into())
264 }
265 }
266
267 pub fn pack(&self) -> Vec<u8> {
269 let mut buf = Vec::with_capacity(size_of::<Self>());
270 match &*self {
271 Self::Initialize(Initialize { fees, swap_curve }) => {
272 buf.push(0);
273 let mut fees_slice = [0u8; Fees::LEN];
274 Pack::pack_into_slice(fees, &mut fees_slice[..]);
275 buf.extend_from_slice(&fees_slice);
276 let mut swap_curve_slice = [0u8; SwapCurve::LEN];
277 Pack::pack_into_slice(swap_curve, &mut swap_curve_slice[..]);
278 buf.extend_from_slice(&swap_curve_slice);
279 }
280 Self::Swap(Swap {
281 amount_in,
282 minimum_amount_out,
283 }) => {
284 buf.push(1);
285 buf.extend_from_slice(&amount_in.to_le_bytes());
286 buf.extend_from_slice(&minimum_amount_out.to_le_bytes());
287 }
288 Self::DepositAllTokenTypes(DepositAllTokenTypes {
289 pool_token_amount,
290 maximum_token_a_amount,
291 maximum_token_b_amount,
292 }) => {
293 buf.push(2);
294 buf.extend_from_slice(&pool_token_amount.to_le_bytes());
295 buf.extend_from_slice(&maximum_token_a_amount.to_le_bytes());
296 buf.extend_from_slice(&maximum_token_b_amount.to_le_bytes());
297 }
298 Self::WithdrawAllTokenTypes(WithdrawAllTokenTypes {
299 pool_token_amount,
300 minimum_token_a_amount,
301 minimum_token_b_amount,
302 }) => {
303 buf.push(3);
304 buf.extend_from_slice(&pool_token_amount.to_le_bytes());
305 buf.extend_from_slice(&minimum_token_a_amount.to_le_bytes());
306 buf.extend_from_slice(&minimum_token_b_amount.to_le_bytes());
307 }
308 Self::DepositSingleTokenTypeExactAmountIn(DepositSingleTokenTypeExactAmountIn {
309 source_token_amount,
310 minimum_pool_token_amount,
311 }) => {
312 buf.push(4);
313 buf.extend_from_slice(&source_token_amount.to_le_bytes());
314 buf.extend_from_slice(&minimum_pool_token_amount.to_le_bytes());
315 }
316 Self::WithdrawSingleTokenTypeExactAmountOut(
317 WithdrawSingleTokenTypeExactAmountOut {
318 destination_token_amount,
319 maximum_pool_token_amount,
320 },
321 ) => {
322 buf.push(5);
323 buf.extend_from_slice(&destination_token_amount.to_le_bytes());
324 buf.extend_from_slice(&maximum_pool_token_amount.to_le_bytes());
325 }
326 }
327 buf
328 }
329}
330
331pub fn initialize(
333 program_id: &Pubkey,
334 token_program_id: &Pubkey,
335 swap_pubkey: &Pubkey,
336 authority_pubkey: &Pubkey,
337 token_a_pubkey: &Pubkey,
338 token_b_pubkey: &Pubkey,
339 pool_pubkey: &Pubkey,
340 fee_pubkey: &Pubkey,
341 destination_pubkey: &Pubkey,
342 fees: Fees,
343 swap_curve: SwapCurve,
344) -> Result<Instruction, ProgramError> {
345 let init_data = SwapInstruction::Initialize(Initialize { fees, swap_curve });
346 let data = init_data.pack();
347
348 let accounts = vec![
349 AccountMeta::new(*swap_pubkey, true),
350 AccountMeta::new_readonly(*authority_pubkey, false),
351 AccountMeta::new_readonly(*token_a_pubkey, false),
352 AccountMeta::new_readonly(*token_b_pubkey, false),
353 AccountMeta::new(*pool_pubkey, false),
354 AccountMeta::new_readonly(*fee_pubkey, false),
355 AccountMeta::new(*destination_pubkey, false),
356 AccountMeta::new_readonly(*token_program_id, false),
357 ];
358
359 Ok(Instruction {
360 program_id: *program_id,
361 accounts,
362 data,
363 })
364}
365
366pub fn deposit_all_token_types(
368 program_id: &Pubkey,
369 token_program_id: &Pubkey,
370 swap_pubkey: &Pubkey,
371 authority_pubkey: &Pubkey,
372 user_transfer_authority_pubkey: &Pubkey,
373 deposit_token_a_pubkey: &Pubkey,
374 deposit_token_b_pubkey: &Pubkey,
375 swap_token_a_pubkey: &Pubkey,
376 swap_token_b_pubkey: &Pubkey,
377 pool_mint_pubkey: &Pubkey,
378 destination_pubkey: &Pubkey,
379 instruction: DepositAllTokenTypes,
380) -> Result<Instruction, ProgramError> {
381 let data = SwapInstruction::DepositAllTokenTypes(instruction).pack();
382
383 let accounts = vec![
384 AccountMeta::new_readonly(*swap_pubkey, false),
385 AccountMeta::new_readonly(*authority_pubkey, false),
386 AccountMeta::new_readonly(*user_transfer_authority_pubkey, true),
387 AccountMeta::new(*deposit_token_a_pubkey, false),
388 AccountMeta::new(*deposit_token_b_pubkey, false),
389 AccountMeta::new(*swap_token_a_pubkey, false),
390 AccountMeta::new(*swap_token_b_pubkey, false),
391 AccountMeta::new(*pool_mint_pubkey, false),
392 AccountMeta::new(*destination_pubkey, false),
393 AccountMeta::new_readonly(*token_program_id, false),
394 ];
395
396 Ok(Instruction {
397 program_id: *program_id,
398 accounts,
399 data,
400 })
401}
402
403pub fn withdraw_all_token_types(
405 program_id: &Pubkey,
406 token_program_id: &Pubkey,
407 swap_pubkey: &Pubkey,
408 authority_pubkey: &Pubkey,
409 user_transfer_authority_pubkey: &Pubkey,
410 pool_mint_pubkey: &Pubkey,
411 fee_account_pubkey: &Pubkey,
412 source_pubkey: &Pubkey,
413 swap_token_a_pubkey: &Pubkey,
414 swap_token_b_pubkey: &Pubkey,
415 destination_token_a_pubkey: &Pubkey,
416 destination_token_b_pubkey: &Pubkey,
417 instruction: WithdrawAllTokenTypes,
418) -> Result<Instruction, ProgramError> {
419 let data = SwapInstruction::WithdrawAllTokenTypes(instruction).pack();
420
421 let accounts = vec![
422 AccountMeta::new_readonly(*swap_pubkey, false),
423 AccountMeta::new_readonly(*authority_pubkey, false),
424 AccountMeta::new_readonly(*user_transfer_authority_pubkey, true),
425 AccountMeta::new(*pool_mint_pubkey, false),
426 AccountMeta::new(*source_pubkey, false),
427 AccountMeta::new(*swap_token_a_pubkey, false),
428 AccountMeta::new(*swap_token_b_pubkey, false),
429 AccountMeta::new(*destination_token_a_pubkey, false),
430 AccountMeta::new(*destination_token_b_pubkey, false),
431 AccountMeta::new(*fee_account_pubkey, false),
432 AccountMeta::new_readonly(*token_program_id, false),
433 ];
434
435 Ok(Instruction {
436 program_id: *program_id,
437 accounts,
438 data,
439 })
440}
441
442pub fn deposit_single_token_type_exact_amount_in(
444 program_id: &Pubkey,
445 token_program_id: &Pubkey,
446 swap_pubkey: &Pubkey,
447 authority_pubkey: &Pubkey,
448 user_transfer_authority_pubkey: &Pubkey,
449 source_token_pubkey: &Pubkey,
450 swap_token_a_pubkey: &Pubkey,
451 swap_token_b_pubkey: &Pubkey,
452 pool_mint_pubkey: &Pubkey,
453 destination_pubkey: &Pubkey,
454 instruction: DepositSingleTokenTypeExactAmountIn,
455) -> Result<Instruction, ProgramError> {
456 let data = SwapInstruction::DepositSingleTokenTypeExactAmountIn(instruction).pack();
457
458 let accounts = vec![
459 AccountMeta::new_readonly(*swap_pubkey, false),
460 AccountMeta::new_readonly(*authority_pubkey, false),
461 AccountMeta::new_readonly(*user_transfer_authority_pubkey, true),
462 AccountMeta::new(*source_token_pubkey, false),
463 AccountMeta::new(*swap_token_a_pubkey, false),
464 AccountMeta::new(*swap_token_b_pubkey, false),
465 AccountMeta::new(*pool_mint_pubkey, false),
466 AccountMeta::new(*destination_pubkey, false),
467 AccountMeta::new_readonly(*token_program_id, false),
468 ];
469
470 Ok(Instruction {
471 program_id: *program_id,
472 accounts,
473 data,
474 })
475}
476
477pub fn withdraw_single_token_type_exact_amount_out(
479 program_id: &Pubkey,
480 token_program_id: &Pubkey,
481 swap_pubkey: &Pubkey,
482 authority_pubkey: &Pubkey,
483 user_transfer_authority_pubkey: &Pubkey,
484 pool_mint_pubkey: &Pubkey,
485 fee_account_pubkey: &Pubkey,
486 pool_token_source_pubkey: &Pubkey,
487 swap_token_a_pubkey: &Pubkey,
488 swap_token_b_pubkey: &Pubkey,
489 destination_pubkey: &Pubkey,
490 instruction: WithdrawSingleTokenTypeExactAmountOut,
491) -> Result<Instruction, ProgramError> {
492 let data = SwapInstruction::WithdrawSingleTokenTypeExactAmountOut(instruction).pack();
493
494 let accounts = vec![
495 AccountMeta::new_readonly(*swap_pubkey, false),
496 AccountMeta::new_readonly(*authority_pubkey, false),
497 AccountMeta::new_readonly(*user_transfer_authority_pubkey, true),
498 AccountMeta::new(*pool_mint_pubkey, false),
499 AccountMeta::new(*pool_token_source_pubkey, false),
500 AccountMeta::new(*swap_token_a_pubkey, false),
501 AccountMeta::new(*swap_token_b_pubkey, false),
502 AccountMeta::new(*destination_pubkey, false),
503 AccountMeta::new(*fee_account_pubkey, false),
504 AccountMeta::new_readonly(*token_program_id, false),
505 ];
506
507 Ok(Instruction {
508 program_id: *program_id,
509 accounts,
510 data,
511 })
512}
513
514pub fn swap(
516 program_id: &Pubkey,
517 token_program_id: &Pubkey,
518 swap_pubkey: &Pubkey,
519 authority_pubkey: &Pubkey,
520 user_transfer_authority_pubkey: &Pubkey,
521 source_pubkey: &Pubkey,
522 swap_source_pubkey: &Pubkey,
523 swap_destination_pubkey: &Pubkey,
524 destination_pubkey: &Pubkey,
525 pool_mint_pubkey: &Pubkey,
526 pool_fee_pubkey: &Pubkey,
527 host_fee_pubkey: Option<&Pubkey>,
528 instruction: Swap,
529) -> Result<Instruction, ProgramError> {
530 let data = SwapInstruction::Swap(instruction).pack();
531
532 let mut accounts = vec![
533 AccountMeta::new_readonly(*swap_pubkey, false),
534 AccountMeta::new_readonly(*authority_pubkey, false),
535 AccountMeta::new_readonly(*user_transfer_authority_pubkey, true),
536 AccountMeta::new(*source_pubkey, false),
537 AccountMeta::new(*swap_source_pubkey, false),
538 AccountMeta::new(*swap_destination_pubkey, false),
539 AccountMeta::new(*destination_pubkey, false),
540 AccountMeta::new(*pool_mint_pubkey, false),
541 AccountMeta::new(*pool_fee_pubkey, false),
542 AccountMeta::new_readonly(*token_program_id, false),
543 ];
544 if let Some(host_fee_pubkey) = host_fee_pubkey {
545 accounts.push(AccountMeta::new(*host_fee_pubkey, false));
546 }
547
548 Ok(Instruction {
549 program_id: *program_id,
550 accounts,
551 data,
552 })
553}
554
555pub fn unpack<T>(input: &[u8]) -> Result<&T, ProgramError> {
558 if input.len() < size_of::<u8>() + size_of::<T>() {
559 return Err(ProgramError::InvalidAccountData);
560 }
561 #[allow(clippy::cast_ptr_alignment)]
562 let val: &T = unsafe { &*(&input[1] as *const u8 as *const T) };
563 Ok(val)
564}
565
566#[cfg(test)]
567mod tests {
568 use super::*;
569 use crate::curve::{base::CurveType, stable::StableCurve};
570 use std::sync::Arc;
571
572 #[test]
573 fn pack_intialize() {
574 let trade_fee_numerator: u64 = 1;
575 let trade_fee_denominator: u64 = 4;
576 let owner_trade_fee_numerator: u64 = 2;
577 let owner_trade_fee_denominator: u64 = 5;
578 let owner_withdraw_fee_numerator: u64 = 1;
579 let owner_withdraw_fee_denominator: u64 = 3;
580 let host_fee_numerator: u64 = 5;
581 let host_fee_denominator: u64 = 20;
582 let fees = Fees {
583 trade_fee_numerator,
584 trade_fee_denominator,
585 owner_trade_fee_numerator,
586 owner_trade_fee_denominator,
587 owner_withdraw_fee_numerator,
588 owner_withdraw_fee_denominator,
589 host_fee_numerator,
590 host_fee_denominator,
591 };
592 let amp: u64 = 1;
593 let curve_type = CurveType::Stable;
594 let calculator = Arc::new(StableCurve { amp });
595 let swap_curve = SwapCurve {
596 curve_type,
597 calculator,
598 };
599 let check = SwapInstruction::Initialize(Initialize { fees, swap_curve });
600 let packed = check.pack();
601 let mut expect = vec![0u8];
602 expect.extend_from_slice(&trade_fee_numerator.to_le_bytes());
603 expect.extend_from_slice(&trade_fee_denominator.to_le_bytes());
604 expect.extend_from_slice(&owner_trade_fee_numerator.to_le_bytes());
605 expect.extend_from_slice(&owner_trade_fee_denominator.to_le_bytes());
606 expect.extend_from_slice(&owner_withdraw_fee_numerator.to_le_bytes());
607 expect.extend_from_slice(&owner_withdraw_fee_denominator.to_le_bytes());
608 expect.extend_from_slice(&host_fee_numerator.to_le_bytes());
609 expect.extend_from_slice(&host_fee_denominator.to_le_bytes());
610 expect.push(curve_type as u8);
611 expect.extend_from_slice(&.to_le_bytes());
612 expect.extend_from_slice(&[0u8; 24]);
613 assert_eq!(packed, expect);
614 let unpacked = SwapInstruction::unpack(&expect).unwrap();
615 assert_eq!(unpacked, check);
616 }
617
618 #[test]
619 fn pack_swap() {
620 let amount_in: u64 = 2;
621 let minimum_amount_out: u64 = 10;
622 let check = SwapInstruction::Swap(Swap {
623 amount_in,
624 minimum_amount_out,
625 });
626 let packed = check.pack();
627 let mut expect = vec![1];
628 expect.extend_from_slice(&amount_in.to_le_bytes());
629 expect.extend_from_slice(&minimum_amount_out.to_le_bytes());
630 assert_eq!(packed, expect);
631 let unpacked = SwapInstruction::unpack(&expect).unwrap();
632 assert_eq!(unpacked, check);
633 }
634
635 #[test]
636 fn pack_deposit() {
637 let pool_token_amount: u64 = 5;
638 let maximum_token_a_amount: u64 = 10;
639 let maximum_token_b_amount: u64 = 20;
640 let check = SwapInstruction::DepositAllTokenTypes(DepositAllTokenTypes {
641 pool_token_amount,
642 maximum_token_a_amount,
643 maximum_token_b_amount,
644 });
645 let packed = check.pack();
646 let mut expect = vec![2];
647 expect.extend_from_slice(&pool_token_amount.to_le_bytes());
648 expect.extend_from_slice(&maximum_token_a_amount.to_le_bytes());
649 expect.extend_from_slice(&maximum_token_b_amount.to_le_bytes());
650 assert_eq!(packed, expect);
651 let unpacked = SwapInstruction::unpack(&expect).unwrap();
652 assert_eq!(unpacked, check);
653 }
654
655 #[test]
656 fn pack_withdraw() {
657 let pool_token_amount: u64 = 1212438012089;
658 let minimum_token_a_amount: u64 = 102198761982612;
659 let minimum_token_b_amount: u64 = 2011239855213;
660 let check = SwapInstruction::WithdrawAllTokenTypes(WithdrawAllTokenTypes {
661 pool_token_amount,
662 minimum_token_a_amount,
663 minimum_token_b_amount,
664 });
665 let packed = check.pack();
666 let mut expect = vec![3];
667 expect.extend_from_slice(&pool_token_amount.to_le_bytes());
668 expect.extend_from_slice(&minimum_token_a_amount.to_le_bytes());
669 expect.extend_from_slice(&minimum_token_b_amount.to_le_bytes());
670 assert_eq!(packed, expect);
671 let unpacked = SwapInstruction::unpack(&expect).unwrap();
672 assert_eq!(unpacked, check);
673 }
674
675 #[test]
676 fn pack_deposit_one_exact_in() {
677 let source_token_amount: u64 = 10;
678 let minimum_pool_token_amount: u64 = 5;
679 let check = SwapInstruction::DepositSingleTokenTypeExactAmountIn(
680 DepositSingleTokenTypeExactAmountIn {
681 source_token_amount,
682 minimum_pool_token_amount,
683 },
684 );
685 let packed = check.pack();
686 let mut expect = vec![4];
687 expect.extend_from_slice(&source_token_amount.to_le_bytes());
688 expect.extend_from_slice(&minimum_pool_token_amount.to_le_bytes());
689 assert_eq!(packed, expect);
690 let unpacked = SwapInstruction::unpack(&expect).unwrap();
691 assert_eq!(unpacked, check);
692 }
693
694 #[test]
695 fn pack_withdraw_one_exact_out() {
696 let destination_token_amount: u64 = 102198761982612;
697 let maximum_pool_token_amount: u64 = 1212438012089;
698 let check = SwapInstruction::WithdrawSingleTokenTypeExactAmountOut(
699 WithdrawSingleTokenTypeExactAmountOut {
700 destination_token_amount,
701 maximum_pool_token_amount,
702 },
703 );
704 let packed = check.pack();
705 let mut expect = vec![5];
706 expect.extend_from_slice(&destination_token_amount.to_le_bytes());
707 expect.extend_from_slice(&maximum_pool_token_amount.to_le_bytes());
708 assert_eq!(packed, expect);
709 let unpacked = SwapInstruction::unpack(&expect).unwrap();
710 assert_eq!(unpacked, check);
711 }
712}