1use crate::constants::{
2 EVENT_AUTHORITY, FEE_PROGRAM, GLOBAL_CONFIG, GLOBAL_VOLUME_ACCUMULATOR, PUMP_CREATOR_VAULT,
3 PUMP_SWAP_PROGRAM_ID, PUMPFUN_EVENT_AUTHORITY, PUMPFUN_PROGRAM, WRAPPED_SOL_MINT,
4};
5use crate::state::PoolInfo;
6use crate::util::{
7 calc_lp_mint_pda, calc_user_pool_token_account, fee_config_pda, find_coin_creator_vault_ata,
8 find_coin_creator_vault_authority, find_user_vol_accumulator, pick_buyback_fee_recipient,
9 pick_protocol_fee_recipient, pool_v2_pda, user_volume_accumulator_quote_ata,
10};
11use anyhow::Result;
12use bytemuck::{Pod, Zeroable};
13use solana_sdk::instruction::{AccountMeta, Instruction};
14use solana_sdk::pubkey::Pubkey;
15use solana_sdk::system_program;
16
17pub trait ToInstructionBytes {
19 fn to_vec(&self) -> Vec<u8>;
20}
21
22impl<T: Pod> ToInstructionBytes for T {
23 fn to_vec(&self) -> Vec<u8> {
24 bytemuck::bytes_of(self).to_vec()
25 }
26}
27
28#[derive(Clone, Copy, Debug, Default, PartialEq)]
34pub struct BuyInstruction {
35 pub base_amount_out: u64,
36 pub max_quote_amount_in: u64,
37 pub track_volume: bool,
40}
41
42impl BuyInstruction {
43 pub const DISCRIMINATOR: [u8; 8] = [102, 6, 61, 18, 1, 218, 235, 234];
44
45 pub fn new(base_amount_out: u64, max_quote_amount_in: u64, track_volume: bool) -> Self {
46 Self {
47 base_amount_out,
48 max_quote_amount_in,
49 track_volume,
50 }
51 }
52
53 pub fn to_vec(&self) -> Vec<u8> {
54 let mut buf = Vec::with_capacity(25);
55 buf.extend_from_slice(&Self::DISCRIMINATOR);
56 buf.extend_from_slice(&self.base_amount_out.to_le_bytes());
57 buf.extend_from_slice(&self.max_quote_amount_in.to_le_bytes());
58 buf.push(self.track_volume as u8);
59 buf
60 }
61}
62
63#[derive(Clone, Copy, Debug, Default, PartialEq)]
69pub struct BuyExactQuoteInInstruction {
70 pub spendable_quote_in: u64,
71 pub min_base_amount_out: u64,
72 pub track_volume: bool,
73}
74
75impl BuyExactQuoteInInstruction {
76 pub const DISCRIMINATOR: [u8; 8] = [198, 46, 21, 82, 180, 217, 232, 112];
77
78 pub fn new(spendable_quote_in: u64, min_base_amount_out: u64, track_volume: bool) -> Self {
79 Self {
80 spendable_quote_in,
81 min_base_amount_out,
82 track_volume,
83 }
84 }
85
86 pub fn to_vec(&self) -> Vec<u8> {
87 let mut buf = Vec::with_capacity(25);
88 buf.extend_from_slice(&Self::DISCRIMINATOR);
89 buf.extend_from_slice(&self.spendable_quote_in.to_le_bytes());
90 buf.extend_from_slice(&self.min_base_amount_out.to_le_bytes());
91 buf.push(self.track_volume as u8);
92 buf
93 }
94}
95
96#[repr(C)]
97#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
98pub struct SellInstruction {
99 pub discriminator: [u8; 8],
100 pub base_amount_in: u64,
101 pub min_quote_amount_out: u64,
102}
103
104impl SellInstruction {
105 pub fn new(base_amount_in: u64, min_quote_amount_out: u64) -> Self {
106 Self {
107 discriminator: [51, 230, 133, 164, 1, 127, 131, 173],
108 base_amount_in,
109 min_quote_amount_out,
110 }
111 }
112}
113
114#[repr(C)]
118#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
119pub struct DepositInstruction {
120 pub discriminator: [u8; 8],
121 pub lp_token_amount_out: u64,
122 pub max_base_amount_in: u64,
123 pub max_quote_amount_in: u64,
124}
125
126impl DepositInstruction {
127 pub fn new(
128 lp_token_amount_out: u64,
129 max_base_amount_in: u64,
130 max_quote_amount_in: u64,
131 ) -> Self {
132 Self {
133 discriminator: [242, 35, 198, 137, 82, 225, 242, 182],
134 lp_token_amount_out,
135 max_base_amount_in,
136 max_quote_amount_in,
137 }
138 }
139}
140
141#[derive(Clone, Copy, Debug, Default, PartialEq)]
144pub struct ClaimCashbackInstruction;
145
146impl ClaimCashbackInstruction {
147 pub const DISCRIMINATOR: [u8; 8] = [37, 58, 35, 126, 190, 53, 228, 197];
148
149 pub fn to_vec(&self) -> Vec<u8> {
150 Self::DISCRIMINATOR.to_vec()
151 }
152}
153
154#[repr(C)]
155#[derive(Clone, Copy, Debug, Default, PartialEq, Zeroable)]
156pub struct CreatePoolInstruction {
157 pub discriminator: [u8; 8],
158 pub index: u16,
159 pub base_amount_in: u64,
160 pub quote_amount_in: u64,
161 pub coin_creator: Pubkey,
162}
163
164impl CreatePoolInstruction {
165 pub fn new(base_amount_in: u64, quote_amount_in: u64, coin_creator: Pubkey) -> Self {
166 Self {
167 discriminator: [233, 146, 209, 142, 207, 104, 64, 188],
168 index: 0,
169 base_amount_in,
170 quote_amount_in,
171 coin_creator,
172 }
173 }
174
175 #[allow(clippy::wrong_self_convention)]
176 fn to_vec(&self) -> Vec<u8> {
177 let mut buf: Vec<u8> = Vec::with_capacity(size_of::<Self>());
178 buf.extend_from_slice(&self.discriminator);
179 buf.extend_from_slice(&self.index.to_le_bytes());
180 buf.extend_from_slice(&self.base_amount_in.to_le_bytes());
181 buf.extend_from_slice(&self.quote_amount_in.to_le_bytes());
182 buf.extend_from_slice(&self.coin_creator.to_bytes());
183 buf
184 }
185}
186
187#[repr(C)]
188#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
189pub struct WithdrawInstruction {
190 pub discriminator: [u8; 8],
191 pub lp_token_amount_in: u64,
192 pub min_base_amount_out: u64,
193 pub min_quote_amount_out: u64,
194}
195
196impl WithdrawInstruction {
197 pub fn new(
198 lp_token_amount_in: u64,
199 min_base_amount_out: u64,
200 min_quote_amount_out: u64,
201 ) -> Self {
202 Self {
203 discriminator: [183, 18, 70, 156, 148, 109, 161, 34],
204 lp_token_amount_in,
205 min_base_amount_out,
206 min_quote_amount_out,
207 }
208 }
209}
210
211pub fn make_buy_instruction(
228 base_amount_out: u64,
229 max_quote_amount_in: u64,
230 track_volume: bool,
231 pool_info: &PoolInfo,
232 user: &Pubkey,
233 user_base_token_account: &Pubkey,
234 user_quote_token_account: &Pubkey,
235) -> Result<Instruction> {
236 let data = BuyInstruction::new(base_amount_out, max_quote_amount_in, track_volume).to_vec();
237 Ok(Instruction {
238 program_id: PUMP_SWAP_PROGRAM_ID,
239 accounts: swap_accounts(
240 pool_info,
241 user,
242 user_base_token_account,
243 user_quote_token_account,
244 SwapKind::Buy,
245 ),
246 data,
247 })
248}
249
250pub fn make_buy_exact_quote_in_instruction(
255 spendable_quote_in: u64,
256 min_base_amount_out: u64,
257 track_volume: bool,
258 pool_info: &PoolInfo,
259 user: &Pubkey,
260 user_base_token_account: &Pubkey,
261 user_quote_token_account: &Pubkey,
262) -> Result<Instruction> {
263 let data =
264 BuyExactQuoteInInstruction::new(spendable_quote_in, min_base_amount_out, track_volume)
265 .to_vec();
266 Ok(Instruction {
267 program_id: PUMP_SWAP_PROGRAM_ID,
268 accounts: swap_accounts(
269 pool_info,
270 user,
271 user_base_token_account,
272 user_quote_token_account,
273 SwapKind::Buy,
274 ),
275 data,
276 })
277}
278
279#[derive(Clone, Copy)]
280enum SwapKind {
281 Buy,
282 Sell,
283}
284
285fn swap_accounts(
288 pool_info: &PoolInfo,
289 user: &Pubkey,
290 user_base_token_account: &Pubkey,
291 user_quote_token_account: &Pubkey,
292 kind: SwapKind,
293) -> Vec<AccountMeta> {
294 let protocol_fee_recipient = pick_protocol_fee_recipient();
295 let protocol_fee_recipient_ta =
296 spl_associated_token_account::get_associated_token_address_with_program_id(
297 &protocol_fee_recipient,
298 &pool_info.quote_mint,
299 &pool_info.quote_token_program,
300 );
301 let creator_vault_authority = find_coin_creator_vault_authority(&pool_info.coin_creator);
302 let creator_vault_ata = find_coin_creator_vault_ata(
303 &creator_vault_authority,
304 &pool_info.quote_token_program,
305 &pool_info.quote_mint,
306 );
307 let fee_config = fee_config_pda();
308
309 let mut accounts = vec![
310 AccountMeta::new(pool_info.pool, false),
311 AccountMeta::new(*user, true),
312 AccountMeta::new_readonly(GLOBAL_CONFIG, false),
313 AccountMeta::new_readonly(pool_info.base_mint, false),
314 AccountMeta::new_readonly(pool_info.quote_mint, false),
315 AccountMeta::new(*user_base_token_account, false),
316 AccountMeta::new(*user_quote_token_account, false),
317 AccountMeta::new(pool_info.pool_base_token_account, false),
318 AccountMeta::new(pool_info.pool_quote_token_account, false),
319 AccountMeta::new_readonly(protocol_fee_recipient, false),
320 AccountMeta::new(protocol_fee_recipient_ta, false),
321 AccountMeta::new_readonly(pool_info.base_token_program, false),
322 AccountMeta::new_readonly(pool_info.quote_token_program, false),
323 AccountMeta::new_readonly(system_program::ID, false),
324 AccountMeta::new_readonly(spl_associated_token_account::ID, false),
325 AccountMeta::new_readonly(EVENT_AUTHORITY, false),
326 AccountMeta::new_readonly(PUMP_SWAP_PROGRAM_ID, false),
327 AccountMeta::new(creator_vault_ata, false),
328 AccountMeta::new_readonly(creator_vault_authority, false),
329 ];
330
331 if matches!(kind, SwapKind::Buy) {
332 accounts.push(AccountMeta::new_readonly(GLOBAL_VOLUME_ACCUMULATOR, false));
333 accounts.push(AccountMeta::new(find_user_vol_accumulator(user), false));
334 }
335
336 accounts.push(AccountMeta::new_readonly(fee_config, false));
337 accounts.push(AccountMeta::new_readonly(FEE_PROGRAM, false));
338
339 let is_sell = matches!(kind, SwapKind::Sell);
340 append_swap_remaining_accounts(&mut accounts, pool_info, user, is_sell);
341 accounts
342}
343
344pub fn make_sell_instruction(
350 base_amount_in: u64,
351 min_quote_amount_out: u64,
352 pool_info: &PoolInfo,
353 user: &Pubkey,
354 user_base_token_account: &Pubkey,
355 user_quote_token_account: &Pubkey,
356) -> Result<Instruction> {
357 let data = SellInstruction::new(base_amount_in, min_quote_amount_out).to_vec();
358 Ok(Instruction {
359 program_id: PUMP_SWAP_PROGRAM_ID,
360 accounts: swap_accounts(
361 pool_info,
362 user,
363 user_base_token_account,
364 user_quote_token_account,
365 SwapKind::Sell,
366 ),
367 data,
368 })
369}
370
371#[allow(clippy::too_many_arguments)]
378pub fn make_deposit_instruction(
379 lp_token_amount_out: u64,
380 max_base_amount_in: u64,
381 max_quote_amount_in: u64,
382 pool_info: &PoolInfo,
383 user: &Pubkey,
384 user_base_token_account: &Pubkey,
385 user_quote_token_account: &Pubkey,
386 user_pool_token_account: &Pubkey,
387) -> Result<Instruction> {
388 let data =
389 DepositInstruction::new(lp_token_amount_out, max_base_amount_in, max_quote_amount_in)
390 .to_vec();
391
392 let accounts = vec![
393 AccountMeta::new(pool_info.pool, false),
394 AccountMeta::new_readonly(GLOBAL_CONFIG, false),
395 AccountMeta::new_readonly(*user, true),
396 AccountMeta::new_readonly(pool_info.base_mint, false),
397 AccountMeta::new_readonly(pool_info.quote_mint, false),
398 AccountMeta::new(pool_info.lp_mint, false),
399 AccountMeta::new(*user_base_token_account, false),
400 AccountMeta::new(*user_quote_token_account, false),
401 AccountMeta::new(*user_pool_token_account, false),
402 AccountMeta::new(pool_info.pool_base_token_account, false),
403 AccountMeta::new(pool_info.pool_quote_token_account, false),
404 AccountMeta::new_readonly(spl_token::ID, false),
405 AccountMeta::new_readonly(spl_token_2022::ID, false),
406 AccountMeta::new_readonly(EVENT_AUTHORITY, false),
407 AccountMeta::new_readonly(PUMP_SWAP_PROGRAM_ID, false),
408 ];
409
410 Ok(Instruction {
411 program_id: PUMP_SWAP_PROGRAM_ID,
412 accounts,
413 data,
414 })
415}
416
417pub fn make_claim_cashback_instruction(
425 user: &Pubkey,
426 quote_mint: &Pubkey,
427 quote_token_program: &Pubkey,
428) -> Result<Instruction> {
429 let user_volume_accumulator = find_user_vol_accumulator(user);
430 let user_volume_accumulator_quote_ta =
431 spl_associated_token_account::get_associated_token_address_with_program_id(
432 &user_volume_accumulator,
433 quote_mint,
434 quote_token_program,
435 );
436 let user_quote_ta = spl_associated_token_account::get_associated_token_address_with_program_id(
437 user,
438 quote_mint,
439 quote_token_program,
440 );
441
442 let accounts = vec![
446 AccountMeta::new(*user, false),
447 AccountMeta::new(user_volume_accumulator, false),
448 AccountMeta::new_readonly(*quote_mint, false),
449 AccountMeta::new_readonly(*quote_token_program, false),
450 AccountMeta::new(user_volume_accumulator_quote_ta, false),
451 AccountMeta::new(user_quote_ta, false),
452 AccountMeta::new_readonly(system_program::ID, false),
453 AccountMeta::new_readonly(EVENT_AUTHORITY, false),
454 AccountMeta::new_readonly(PUMP_SWAP_PROGRAM_ID, false),
455 ];
456
457 Ok(Instruction {
458 program_id: PUMP_SWAP_PROGRAM_ID,
459 accounts,
460 data: ClaimCashbackInstruction.to_vec(),
461 })
462}
463
464fn append_swap_remaining_accounts(
474 accounts: &mut Vec<AccountMeta>,
475 pool_info: &PoolInfo,
476 user: &Pubkey,
477 is_sell: bool,
478) {
479 if pool_info.is_cashback_coin {
480 let cashback_ata = user_volume_accumulator_quote_ata(
481 user,
482 &pool_info.quote_mint,
483 &pool_info.quote_token_program,
484 );
485 accounts.push(AccountMeta::new(cashback_ata, false));
486 if is_sell {
487 accounts.push(AccountMeta::new(find_user_vol_accumulator(user), false));
488 }
489 }
490
491 if pool_info.coin_creator != Pubkey::default() {
492 accounts.push(AccountMeta::new_readonly(
493 pool_v2_pda(&pool_info.base_mint),
494 false,
495 ));
496 }
497
498 let buyback_recipient = pick_buyback_fee_recipient();
499 let buyback_recipient_ta =
500 spl_associated_token_account::get_associated_token_address_with_program_id(
501 &buyback_recipient,
502 &pool_info.quote_mint,
503 &pool_info.quote_token_program,
504 );
505 accounts.push(AccountMeta::new_readonly(buyback_recipient, false));
506 accounts.push(AccountMeta::new(buyback_recipient_ta, false));
507}
508
509#[allow(clippy::too_many_arguments)]
511pub fn create_pool_instruction(
512 base_amount_in: u64,
513 quote_amount_in: u64,
514 pool: &Pubkey,
515 creator: &Pubkey,
516 coin_creator: &Pubkey,
517 base_mint: &Pubkey,
518 quote_mint: &Pubkey,
519 user_base_token_account: &Pubkey,
520 user_quote_token_account: &Pubkey,
521 pool_base_token_account: &Pubkey,
522 pool_quote_token_account: &Pubkey,
523) -> Result<Instruction> {
524 let data = CreatePoolInstruction::new(base_amount_in, quote_amount_in, *coin_creator).to_vec();
525
526 let lp_mint = calc_lp_mint_pda(pool).0;
527
528 let accounts = vec![
529 AccountMeta::new(*pool, false),
530 AccountMeta::new_readonly(GLOBAL_CONFIG, false),
531 AccountMeta::new(*creator, true),
532 AccountMeta::new_readonly(*base_mint, false),
533 AccountMeta::new_readonly(*quote_mint, false),
534 AccountMeta::new(lp_mint, false),
535 AccountMeta::new(*user_base_token_account, false),
536 AccountMeta::new(*user_quote_token_account, false),
537 AccountMeta::new(calc_user_pool_token_account(creator, &lp_mint).0, false),
538 AccountMeta::new(*pool_base_token_account, false),
539 AccountMeta::new(*pool_quote_token_account, false),
540 AccountMeta::new_readonly(system_program::ID, false),
541 AccountMeta::new_readonly(spl_token_2022::ID, false),
542 AccountMeta::new_readonly(spl_token::ID, false),
543 AccountMeta::new_readonly(spl_token::ID, false),
544 AccountMeta::new_readonly(spl_associated_token_account::ID, false),
545 AccountMeta::new_readonly(EVENT_AUTHORITY, false),
546 AccountMeta::new_readonly(PUMP_SWAP_PROGRAM_ID, false),
547 ];
548
549 Ok(Instruction {
550 program_id: PUMP_SWAP_PROGRAM_ID,
551 accounts,
552 data,
553 })
554}
555
556#[allow(clippy::too_many_arguments)]
558pub fn withdraw_instruction(
559 pool: &Pubkey,
560 user: &Pubkey,
561 base_mint: &Pubkey,
562 base_ata: &Pubkey,
563 quote_ata: &Pubkey,
564 lp_token_amount_in: u64,
565 min_base_amount_out: u64,
566 min_quote_amount_out: u64,
567) -> Result<Instruction> {
568 let data = WithdrawInstruction::new(
569 lp_token_amount_in,
570 min_base_amount_out,
571 min_quote_amount_out,
572 )
573 .to_vec();
574
575 let lp_mint = calc_lp_mint_pda(pool).0;
576 let user_pool_token_account = calc_user_pool_token_account(user, &lp_mint).0;
577
578 let pool_base_ata = spl_associated_token_account::get_associated_token_address(pool, base_mint);
579 let pool_quote_ata =
580 spl_associated_token_account::get_associated_token_address(pool, &WRAPPED_SOL_MINT);
581
582 let accounts = vec![
583 AccountMeta::new(*pool, false),
584 AccountMeta::new_readonly(GLOBAL_CONFIG, false),
585 AccountMeta::new_readonly(*user, true),
586 AccountMeta::new_readonly(*base_mint, false),
587 AccountMeta::new_readonly(WRAPPED_SOL_MINT, false),
588 AccountMeta::new(lp_mint, false),
589 AccountMeta::new(*base_ata, false),
590 AccountMeta::new(*quote_ata, false),
591 AccountMeta::new(user_pool_token_account, false),
592 AccountMeta::new(pool_base_ata, false),
593 AccountMeta::new(pool_quote_ata, false),
594 AccountMeta::new_readonly(spl_token::ID, false),
595 AccountMeta::new_readonly(spl_token_2022::ID, false),
596 AccountMeta::new_readonly(EVENT_AUTHORITY, false),
597 AccountMeta::new_readonly(PUMP_SWAP_PROGRAM_ID, false),
598 ];
599
600 Ok(Instruction {
601 program_id: PUMP_SWAP_PROGRAM_ID,
602 accounts,
603 data,
604 })
605}
606
607pub fn transfer_creator_fees_to_pump_instruction(coin_creator: &Pubkey) -> Result<Instruction> {
611 let coin_creator_vault_authority = find_coin_creator_vault_authority(coin_creator);
612 let coin_creator_vault_ata = find_coin_creator_vault_ata(
613 &coin_creator_vault_authority,
614 &spl_token::ID,
615 &WRAPPED_SOL_MINT,
616 );
617
618 let accounts = vec![
619 AccountMeta::new_readonly(WRAPPED_SOL_MINT, false),
620 AccountMeta::new_readonly(spl_token::ID, false),
621 AccountMeta::new_readonly(system_program::ID, false),
622 AccountMeta::new_readonly(spl_associated_token_account::ID, false),
623 AccountMeta::new_readonly(*coin_creator, false),
624 AccountMeta::new(coin_creator_vault_authority, false),
625 AccountMeta::new(coin_creator_vault_ata, false),
626 AccountMeta::new(PUMP_CREATOR_VAULT, false),
627 AccountMeta::new_readonly(EVENT_AUTHORITY, false),
628 AccountMeta::new_readonly(PUMP_SWAP_PROGRAM_ID, false),
629 ];
630
631 Ok(Instruction {
632 program_id: PUMP_SWAP_PROGRAM_ID,
633 accounts,
634 data: vec![139, 52, 134, 85, 228, 229, 108, 241],
635 })
636}
637
638pub fn distribute_creator_fees_instruction(
641 mint: &Pubkey,
642 bonding_curve: &Pubkey,
643 sharing_config: &Pubkey,
644 admin_account: &Pubkey,
645) -> Result<Instruction> {
646 let accounts = vec![
647 AccountMeta::new_readonly(*mint, false),
648 AccountMeta::new_readonly(*bonding_curve, false),
649 AccountMeta::new_readonly(*sharing_config, false),
650 AccountMeta::new(PUMP_CREATOR_VAULT, false),
651 AccountMeta::new_readonly(system_program::ID, false),
652 AccountMeta::new_readonly(PUMPFUN_EVENT_AUTHORITY, false),
653 AccountMeta::new_readonly(PUMPFUN_PROGRAM, false),
654 AccountMeta::new(*admin_account, true),
655 ];
656
657 Ok(Instruction {
658 program_id: PUMPFUN_PROGRAM,
659 accounts,
660 data: vec![165, 114, 103, 0, 121, 206, 247, 81],
661 })
662}
663
664#[cfg(test)]
665mod tests {
666 use super::*;
667
668 fn pk(byte: u8) -> Pubkey {
669 Pubkey::new_from_array([byte; 32])
670 }
671
672 fn pool_info(coin_creator: Pubkey, is_cashback_coin: bool) -> PoolInfo {
673 PoolInfo {
674 pool: pk(1),
675 base_mint: pk(2),
676 quote_mint: WRAPPED_SOL_MINT,
677 lp_mint: pk(3),
678 pool_base_token_account: pk(4),
679 pool_quote_token_account: pk(5),
680 creator: pk(6),
681 coin_creator,
682 is_cashback_coin,
683 base_token_program: spl_token::ID,
684 quote_token_program: spl_token::ID,
685 }
686 }
687
688 #[test]
689 fn swap_instruction_account_counts_include_required_remaining_accounts() {
690 let user = pk(7);
691 let user_base_ata = pk(8);
692 let user_quote_ata = pk(9);
693
694 let classic = pool_info(Pubkey::default(), false);
695 assert_eq!(
696 make_buy_instruction(1, 2, true, &classic, &user, &user_base_ata, &user_quote_ata)
697 .unwrap()
698 .accounts
699 .len(),
700 25
701 );
702 assert_eq!(
703 make_buy_exact_quote_in_instruction(
704 1,
705 2,
706 true,
707 &classic,
708 &user,
709 &user_base_ata,
710 &user_quote_ata,
711 )
712 .unwrap()
713 .accounts
714 .len(),
715 25
716 );
717 assert_eq!(
718 make_sell_instruction(1, 2, &classic, &user, &user_base_ata, &user_quote_ata)
719 .unwrap()
720 .accounts
721 .len(),
722 23
723 );
724
725 let creator_pool = pool_info(pk(10), false);
726 assert_eq!(
727 make_buy_instruction(
728 1,
729 2,
730 true,
731 &creator_pool,
732 &user,
733 &user_base_ata,
734 &user_quote_ata
735 )
736 .unwrap()
737 .accounts
738 .len(),
739 26
740 );
741 assert_eq!(
742 make_sell_instruction(1, 2, &creator_pool, &user, &user_base_ata, &user_quote_ata)
743 .unwrap()
744 .accounts
745 .len(),
746 24
747 );
748
749 let cashback_creator_pool = pool_info(pk(10), true);
750 assert_eq!(
751 make_buy_instruction(
752 1,
753 2,
754 true,
755 &cashback_creator_pool,
756 &user,
757 &user_base_ata,
758 &user_quote_ata,
759 )
760 .unwrap()
761 .accounts
762 .len(),
763 27
764 );
765 assert_eq!(
766 make_sell_instruction(
767 1,
768 2,
769 &cashback_creator_pool,
770 &user,
771 &user_base_ata,
772 &user_quote_ata,
773 )
774 .unwrap()
775 .accounts
776 .len(),
777 26
778 );
779 }
780
781 #[test]
782 fn buy_instruction_data_layout_matches_idl() {
783 let bytes = BuyInstruction::new(50_000_000, 564_953_706, true).to_vec();
784 assert_eq!(bytes.len(), 25, "data must be exactly 25 bytes");
785 assert_eq!(&bytes[0..8], &BuyInstruction::DISCRIMINATOR);
786 assert_eq!(
787 u64::from_le_bytes(bytes[8..16].try_into().unwrap()),
788 50_000_000
789 );
790 assert_eq!(
791 u64::from_le_bytes(bytes[16..24].try_into().unwrap()),
792 564_953_706
793 );
794 assert_eq!(bytes[24], 1);
795
796 let bytes_off = BuyInstruction::new(1, 2, false).to_vec();
797 assert_eq!(bytes_off[24], 0);
798 }
799
800 #[test]
801 fn buy_exact_quote_in_data_layout_matches_idl() {
802 let bytes = BuyExactQuoteInInstruction::new(50_000_000, 564_953_706, true).to_vec();
805 assert_eq!(bytes.len(), 25);
806 assert_eq!(&bytes[0..8], &BuyExactQuoteInInstruction::DISCRIMINATOR);
807 assert_eq!(
808 u64::from_le_bytes(bytes[8..16].try_into().unwrap()),
809 50_000_000
810 );
811 assert_eq!(
812 u64::from_le_bytes(bytes[16..24].try_into().unwrap()),
813 564_953_706
814 );
815 assert_eq!(bytes[24], 1);
816 }
817
818 #[test]
819 fn deposit_instruction_account_count() {
820 let pool = pool_info(Pubkey::default(), false);
821 let user = pk(7);
822 assert_eq!(
823 make_deposit_instruction(1, 2, 3, &pool, &user, &pk(8), &pk(9), &pk(10))
824 .unwrap()
825 .accounts
826 .len(),
827 15
828 );
829 }
830
831 #[test]
832 fn claim_cashback_instruction_account_count() {
833 let user = pk(7);
834 assert_eq!(
835 make_claim_cashback_instruction(&user, &WRAPPED_SOL_MINT, &spl_token::ID)
836 .unwrap()
837 .accounts
838 .len(),
839 9
840 );
841 }
842}