1use super::types::TradeDirection;
2use crate::constants::{
3 ATA_PROGRAM_ID, DEX_PROGRAM_ID, SYSTEM_PROGRAM_ID, TOKEN_2022_PROGRAM_ID,
4};
5use crate::math::amm::{self, QuoteError};
6use crate::utils::{
7 DeriveTradeArgs, TradeAccounts, TradeMode, TradeStateFields,
8 TradeUserContext, derive_trade_accounts, partner_account,
9};
10use solana_address::Address;
11
12#[derive(Clone, Debug)]
13pub struct BuyExactInParams<'a> {
14 pub user: &'a Address,
15 pub payer: Option<&'a Address>,
16 pub base_mint: &'a Address,
17 pub quote_mint: &'a Address,
18 pub base_reserves: u64,
19 pub quote_reserves: u64,
20 pub fee_bps: u16,
21 pub slippage_bps: u16,
22 pub partner: &'a Address,
23 pub platform_config: &'a Address,
24 pub quote_token_program: &'a Address,
25 pub quote_fee: Option<amm::MintFee>,
28 pub base_fee: Option<amm::MintFee>,
30 pub quote_amount: u64,
31}
32
33pub fn buy_exact_in(
34 p: &BuyExactInParams<'_>,
35) -> Result<(solana_instruction::Instruction, amm::BuyQuote), QuoteError> {
36 let q = amm::buy_exact_in_with_fees(amm::BuyArgs {
37 amm: amm::AmmInput {
38 quote_reserves: p.quote_reserves,
39 base_reserves: p.base_reserves,
40 amount: p.quote_amount,
41 fee_bps: p.fee_bps,
42 },
43 quote_fee: p.quote_fee,
44 base_fee: p.base_fee,
45 base_reserve_cap: None,
46 })?;
47 let min_base_out =
49 amm::calculate_slippage_down(q.base_to_user, p.slippage_bps)?;
50 let ix = build_buy_exact_in_instruction(&BuildBuyExactInParams {
51 user: p.user,
52 payer: p.payer,
53 base_mint: p.base_mint,
54 quote_mint: p.quote_mint,
55 partner: p.partner,
56 platform_config: p.platform_config,
57 quote_token_program: p.quote_token_program,
58 amount_in: p.quote_amount,
59 min_amount_out: min_base_out,
60 });
61 Ok((ix, q))
62}
63
64#[derive(Clone, Debug)]
65pub struct BuyExactOutParams<'a> {
66 pub user: &'a Address,
67 pub payer: Option<&'a Address>,
68 pub base_mint: &'a Address,
69 pub quote_mint: &'a Address,
70 pub base_reserves: u64,
71 pub quote_reserves: u64,
72 pub fee_bps: u16,
73 pub slippage_bps: u16,
74 pub partner: &'a Address,
75 pub platform_config: &'a Address,
76 pub quote_token_program: &'a Address,
77 pub quote_fee: Option<amm::MintFee>,
80 pub base_fee: Option<amm::MintFee>,
82 pub base_amount: u64,
83}
84
85pub fn buy_exact_out(
86 p: &BuyExactOutParams<'_>,
87) -> Result<(solana_instruction::Instruction, amm::BuyQuote), QuoteError> {
88 let q = amm::buy_exact_out_with_fees(amm::BuyArgs {
89 amm: amm::AmmInput {
90 quote_reserves: p.quote_reserves,
91 base_reserves: p.base_reserves,
92 amount: p.base_amount,
93 fee_bps: p.fee_bps,
94 },
95 quote_fee: p.quote_fee,
96 base_fee: p.base_fee,
97 base_reserve_cap: None,
98 })?;
99 let max_quote_in =
102 amm::calculate_slippage_up(q.quote_from_user, p.slippage_bps)?;
103 let ix = build_buy_exact_out_instruction(&BuildBuyExactOutParams {
104 user: p.user,
105 payer: p.payer,
106 base_mint: p.base_mint,
107 quote_mint: p.quote_mint,
108 partner: p.partner,
109 platform_config: p.platform_config,
110 quote_token_program: p.quote_token_program,
111 amount_out: p.base_amount,
112 max_amount_in: max_quote_in,
113 });
114 Ok((ix, q))
115}
116
117#[derive(Clone, Debug)]
118pub struct SellExactInParams<'a> {
119 pub user: &'a Address,
120 pub payer: Option<&'a Address>,
121 pub base_mint: &'a Address,
122 pub quote_mint: &'a Address,
123 pub base_reserves: u64,
124 pub quote_reserves: u64,
125 pub fee_bps: u16,
126 pub slippage_bps: u16,
127 pub partner: &'a Address,
128 pub platform_config: &'a Address,
129 pub quote_token_program: &'a Address,
130 pub quote_fee: Option<amm::MintFee>,
133 pub base_fee: Option<amm::MintFee>,
135 pub base_amount: u64,
136}
137
138pub fn sell_exact_in(
139 p: &SellExactInParams<'_>,
140) -> Result<(solana_instruction::Instruction, amm::SellQuote), QuoteError> {
141 let q = amm::sell_exact_in_with_fees(amm::SellArgs {
142 amm: amm::AmmInput {
143 quote_reserves: p.quote_reserves,
144 base_reserves: p.base_reserves,
145 amount: p.base_amount,
146 fee_bps: p.fee_bps,
147 },
148 quote_fee: p.quote_fee,
149 base_fee: p.base_fee,
150 })?;
151 let min_quote_out =
154 amm::calculate_slippage_down(q.quote_to_user, p.slippage_bps)?;
155 let ix = build_sell_exact_in_instruction(&BuildSellExactInParams {
156 user: p.user,
157 payer: p.payer,
158 base_mint: p.base_mint,
159 quote_mint: p.quote_mint,
160 partner: p.partner,
161 platform_config: p.platform_config,
162 quote_token_program: p.quote_token_program,
163 amount_in: p.base_amount,
164 min_amount_out: min_quote_out,
165 });
166 Ok((ix, q))
167}
168
169#[derive(Clone, Debug)]
170pub struct SellExactOutParams<'a> {
171 pub user: &'a Address,
172 pub payer: Option<&'a Address>,
173 pub base_mint: &'a Address,
174 pub quote_mint: &'a Address,
175 pub base_reserves: u64,
176 pub quote_reserves: u64,
177 pub fee_bps: u16,
178 pub slippage_bps: u16,
179 pub partner: &'a Address,
180 pub platform_config: &'a Address,
181 pub quote_token_program: &'a Address,
182 pub quote_fee: Option<amm::MintFee>,
185 pub base_fee: Option<amm::MintFee>,
187 pub quote_amount: u64,
188}
189
190pub fn sell_exact_out(
191 p: &SellExactOutParams<'_>,
192) -> Result<(solana_instruction::Instruction, amm::SellQuote), QuoteError> {
193 let q = amm::sell_exact_out_with_fees(amm::SellArgs {
194 amm: amm::AmmInput {
195 quote_reserves: p.quote_reserves,
196 base_reserves: p.base_reserves,
197 amount: p.quote_amount,
198 fee_bps: p.fee_bps,
199 },
200 quote_fee: p.quote_fee,
201 base_fee: p.base_fee,
202 })?;
203 let max_base_in =
206 amm::calculate_slippage_up(q.base_from_user, p.slippage_bps)?;
207 let ix = build_sell_exact_out_instruction(&BuildSellExactOutParams {
208 user: p.user,
209 payer: p.payer,
210 base_mint: p.base_mint,
211 quote_mint: p.quote_mint,
212 partner: p.partner,
213 platform_config: p.platform_config,
214 quote_token_program: p.quote_token_program,
215 amount_out: p.quote_amount,
216 max_amount_in: max_base_in,
217 });
218 Ok((ix, q))
219}
220
221#[derive(Clone, Debug)]
222pub struct BuildBuyExactInParams<'a> {
223 pub user: &'a Address,
224 pub payer: Option<&'a Address>,
225 pub base_mint: &'a Address,
226 pub quote_mint: &'a Address,
227 pub partner: &'a Address,
228 pub platform_config: &'a Address,
229 pub quote_token_program: &'a Address,
230 pub amount_in: u64,
231 pub min_amount_out: u64,
232}
233
234#[must_use]
235pub fn build_buy_exact_in_instruction(
236 p: &BuildBuyExactInParams<'_>,
237) -> solana_instruction::Instruction {
238 dex_trade_accounts!(
239 super::instructions::BuyExactIn,
240 &derived_trade_accounts!(p)
241 )
242 .instruction(super::instructions::BuyExactInInstructionArgs {
243 amount_in: p.amount_in,
244 min_amount_out: p.min_amount_out,
245 })
246}
247
248#[derive(Clone, Debug)]
249pub struct BuildBuyExactOutParams<'a> {
250 pub user: &'a Address,
251 pub payer: Option<&'a Address>,
252 pub base_mint: &'a Address,
253 pub quote_mint: &'a Address,
254 pub partner: &'a Address,
255 pub platform_config: &'a Address,
256 pub quote_token_program: &'a Address,
257 pub amount_out: u64,
258 pub max_amount_in: u64,
259}
260
261#[must_use]
262pub fn build_buy_exact_out_instruction(
263 p: &BuildBuyExactOutParams<'_>,
264) -> solana_instruction::Instruction {
265 dex_trade_accounts!(
266 super::instructions::BuyExactOut,
267 &derived_trade_accounts!(p)
268 )
269 .instruction(super::instructions::BuyExactOutInstructionArgs {
270 amount_out: p.amount_out,
271 max_amount_in: p.max_amount_in,
272 })
273}
274
275#[derive(Clone, Debug)]
276pub struct BuildSellExactInParams<'a> {
277 pub user: &'a Address,
278 pub payer: Option<&'a Address>,
279 pub base_mint: &'a Address,
280 pub quote_mint: &'a Address,
281 pub partner: &'a Address,
282 pub platform_config: &'a Address,
283 pub quote_token_program: &'a Address,
284 pub amount_in: u64,
285 pub min_amount_out: u64,
286}
287
288#[must_use]
289pub fn build_sell_exact_in_instruction(
290 p: &BuildSellExactInParams<'_>,
291) -> solana_instruction::Instruction {
292 dex_trade_accounts!(
293 super::instructions::SellExactIn,
294 &derived_trade_accounts!(p)
295 )
296 .instruction(super::instructions::SellExactInInstructionArgs {
297 amount_in: p.amount_in,
298 min_amount_out: p.min_amount_out,
299 })
300}
301
302#[derive(Clone, Debug)]
303pub struct BuildSellExactOutParams<'a> {
304 pub user: &'a Address,
305 pub payer: Option<&'a Address>,
306 pub base_mint: &'a Address,
307 pub quote_mint: &'a Address,
308 pub partner: &'a Address,
309 pub platform_config: &'a Address,
310 pub quote_token_program: &'a Address,
311 pub amount_out: u64,
312 pub max_amount_in: u64,
313}
314
315#[must_use]
316pub fn build_sell_exact_out_instruction(
317 p: &BuildSellExactOutParams<'_>,
318) -> solana_instruction::Instruction {
319 dex_trade_accounts!(
320 super::instructions::SellExactOut,
321 &derived_trade_accounts!(p)
322 )
323 .instruction(super::instructions::SellExactOutInstructionArgs {
324 amount_out: p.amount_out,
325 max_amount_in: p.max_amount_in,
326 })
327}
328
329#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
330pub struct TradeArgs {
331 pub direction: TradeDirection,
334 pub mode: TradeMode,
335 pub amount: u64,
337 pub limit: u64,
339}
340
341#[must_use]
342pub fn trade_instruction(
343 accounts: &TradeAccounts,
344 args: TradeArgs,
345) -> solana_instruction::Instruction {
346 let TradeArgs {
347 direction,
348 mode,
349 amount,
350 limit,
351 } = args;
352 match (direction, mode) {
353 (TradeDirection::Buy, TradeMode::ExactIn) => {
354 dex_trade_accounts!(super::instructions::BuyExactIn, accounts)
355 .instruction(super::instructions::BuyExactInInstructionArgs {
356 amount_in: amount,
357 min_amount_out: limit,
358 })
359 }
360 (TradeDirection::Buy, TradeMode::ExactOut) => {
361 dex_trade_accounts!(super::instructions::BuyExactOut, accounts)
362 .instruction(super::instructions::BuyExactOutInstructionArgs {
363 amount_out: amount,
364 max_amount_in: limit,
365 })
366 }
367 (TradeDirection::Sell, TradeMode::ExactIn) => {
368 dex_trade_accounts!(super::instructions::SellExactIn, accounts)
369 .instruction(super::instructions::SellExactInInstructionArgs {
370 amount_in: amount,
371 min_amount_out: limit,
372 })
373 }
374 (TradeDirection::Sell, TradeMode::ExactOut) => {
375 dex_trade_accounts!(super::instructions::SellExactOut, accounts)
376 .instruction(super::instructions::SellExactOutInstructionArgs {
377 amount_out: amount,
378 max_amount_in: limit,
379 })
380 }
381 }
382}
383
384#[must_use]
385pub fn trade_account_metas(
386 accounts: &TradeAccounts,
387) -> Vec<solana_instruction::AccountMeta> {
388 dex_trade_accounts!(super::instructions::BuyExactIn, accounts)
389 .instruction(super::instructions::BuyExactInInstructionArgs {
390 amount_in: 0,
391 min_amount_out: 0,
392 })
393 .accounts
394}
395
396#[must_use]
397pub fn trade_data(args: TradeArgs) -> Vec<u8> {
398 trade_instruction(&TradeAccounts::default(), args).data
400}
401
402macro_rules! dex_trade_accounts {
403 ($ty:path, $accounts:expr) => {{
404 let a: &TradeAccounts = $accounts;
405 $ty {
406 user: a.user,
407 payer: a.payer,
408 pool: a.market,
409 base_mint: a.base_mint,
410 quote_mint: a.quote_mint,
411 base_vault: a.base_vault,
412 quote_vault: a.quote_vault,
413 user_base_account: a.user_base_account,
414 user_quote_account: a.user_quote_account,
415 partner: partner_account(&a.partner),
416 partner_config: a.partner_config,
417 base_token_program: TOKEN_2022_PROGRAM_ID,
418 quote_token_program: a.quote_token_program,
419 associated_token_program: ATA_PROGRAM_ID,
420 system_program: SYSTEM_PROGRAM_ID,
421 event_authority: super::pda::EVENT_AUTHORITY_ADDRESS,
422 program: DEX_PROGRAM_ID,
423 }
424 }};
425}
426use dex_trade_accounts;
427
428macro_rules! derived_trade_accounts {
429 ($p:expr) => {
430 derive(
431 &TradeStateFields {
432 base_mint: $p.base_mint,
433 quote_mint: $p.quote_mint,
434 platform_config: $p.platform_config,
435 },
436 TradeUserContext {
437 user: $p.user,
438 partner: $p.partner,
439 quote_token_program: $p.quote_token_program,
440 },
441 $p.payer,
442 )
443 };
444}
445use derived_trade_accounts;
446
447fn derive(
448 state: &TradeStateFields<'_>,
449 ctx: TradeUserContext<'_>,
450 payer: Option<&Address>,
451) -> TradeAccounts {
452 let (pool, _) =
453 super::pda::find_pool_pda(state.base_mint, state.quote_mint);
454 let (base_vault, _) = crate::utils::find_associated_token_pda(
456 &pool,
457 state.base_mint,
458 &TOKEN_2022_PROGRAM_ID,
459 );
460 let (quote_vault, _) = crate::utils::find_associated_token_pda(
461 &pool,
462 state.quote_mint,
463 ctx.quote_token_program,
464 );
465 let user = *ctx.user;
466 let payer = *payer.unwrap_or(ctx.user);
467 let partner = *ctx.partner;
468 let quote_token_program = *ctx.quote_token_program;
469 let trade = derive_trade_accounts(DeriveTradeArgs { state, ctx });
470 TradeAccounts {
471 user,
472 payer,
473 market: pool,
474 base_mint: *state.base_mint,
475 quote_mint: *state.quote_mint,
476 base_vault,
477 quote_vault,
478 user_base_account: trade.user_base_account,
479 user_quote_account: trade.user_quote_account,
480 partner,
481 partner_config: trade.partner_config,
482 quote_token_program,
483 }
484}
485
486#[cfg(test)]
487mod tests {
488 use solana_address::Address;
489
490 use super::{
491 BuildBuyExactInParams, BuildBuyExactOutParams, BuildSellExactInParams,
492 BuildSellExactOutParams, SellExactInParams, TradeArgs,
493 build_buy_exact_in_instruction, build_buy_exact_out_instruction,
494 build_sell_exact_in_instruction, build_sell_exact_out_instruction,
495 sell_exact_in, trade_account_metas, trade_data, trade_instruction,
496 };
497 use crate::constants::{
498 ATA_PROGRAM_ID, DEFAULT_PARTNER, DEX_PROGRAM_ID, SYSTEM_PROGRAM_ID,
499 TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, WSOL_MINT,
500 };
501 use crate::dex::events::TradeEvent;
502 use crate::dex::types::TradeDirection;
503 use crate::utils::{TradeAccounts, TradeMode};
504 use solana_instruction::AccountMeta;
505
506 const POOL_INDEX: usize = 2;
507 const BASE_VAULT_INDEX: usize = 5;
508 const QUOTE_VAULT_INDEX: usize = 6;
509 const PARTNER_INDEX: usize = 9;
510 const PARTNER_CONFIG_INDEX: usize = 10;
511
512 const USER: Address = Address::new_from_array([1u8; 32]);
513 const BASE_MINT: Address = Address::new_from_array([2u8; 32]);
514 const PARTNER: Address = Address::new_from_array([3u8; 32]);
515 const PLATFORM_CONFIG: Address = Address::new_from_array([4u8; 32]);
516
517 fn assert_derived_market_accounts(
518 ix: &solana_instruction::Instruction,
519 quote_mint: &Address,
520 quote_token_program: &Address,
521 ) {
522 let (pool, _) = crate::dex::pda::find_pool_pda(&BASE_MINT, quote_mint);
523 let (base_vault, _) = crate::utils::find_associated_token_pda(
524 &pool,
525 &BASE_MINT,
526 &TOKEN_2022_PROGRAM_ID,
527 );
528 let (quote_vault, _) = crate::utils::find_associated_token_pda(
529 &pool,
530 quote_mint,
531 quote_token_program,
532 );
533 assert_eq!(ix.accounts.len(), 17);
534 assert_eq!(ix.accounts[POOL_INDEX].pubkey, pool);
535 assert_eq!(ix.accounts[BASE_VAULT_INDEX].pubkey, base_vault);
536 assert_eq!(ix.accounts[QUOTE_VAULT_INDEX].pubkey, quote_vault);
537 assert_eq!(ix.accounts[PARTNER_INDEX].pubkey, PARTNER);
538 assert!(ix.accounts[PARTNER_INDEX].is_signer);
539 }
540
541 #[test]
542 fn build_buy_exact_in_derives_the_market_accounts() {
543 let ix = build_buy_exact_in_instruction(&BuildBuyExactInParams {
544 user: &USER,
545 payer: None,
546 base_mint: &BASE_MINT,
547 quote_mint: &WSOL_MINT,
548 partner: &PARTNER,
549 platform_config: &PLATFORM_CONFIG,
550 quote_token_program: &TOKEN_PROGRAM_ID,
551 amount_in: 1_000,
552 min_amount_out: 900,
553 });
554 assert_derived_market_accounts(&ix, &WSOL_MINT, &TOKEN_PROGRAM_ID);
555 }
556
557 #[test]
558 fn build_buy_exact_out_derives_the_market_accounts() {
559 let ix = build_buy_exact_out_instruction(&BuildBuyExactOutParams {
560 user: &USER,
561 payer: None,
562 base_mint: &BASE_MINT,
563 quote_mint: &WSOL_MINT,
564 partner: &PARTNER,
565 platform_config: &PLATFORM_CONFIG,
566 quote_token_program: &TOKEN_PROGRAM_ID,
567 amount_out: 1_000,
568 max_amount_in: 1_100,
569 });
570 assert_derived_market_accounts(&ix, &WSOL_MINT, &TOKEN_PROGRAM_ID);
571 }
572
573 #[test]
574 fn build_sell_exact_in_derives_the_market_accounts() {
575 let ix = build_sell_exact_in_instruction(&BuildSellExactInParams {
576 user: &USER,
577 payer: None,
578 base_mint: &BASE_MINT,
579 quote_mint: &WSOL_MINT,
580 partner: &PARTNER,
581 platform_config: &PLATFORM_CONFIG,
582 quote_token_program: &TOKEN_PROGRAM_ID,
583 amount_in: 1_000,
584 min_amount_out: 900,
585 });
586 assert_derived_market_accounts(&ix, &WSOL_MINT, &TOKEN_PROGRAM_ID);
587 }
588
589 #[test]
590 fn build_sell_exact_out_derives_the_market_accounts() {
591 let ix = build_sell_exact_out_instruction(&BuildSellExactOutParams {
592 user: &USER,
593 payer: None,
594 base_mint: &BASE_MINT,
595 quote_mint: &WSOL_MINT,
596 partner: &PARTNER,
597 platform_config: &PLATFORM_CONFIG,
598 quote_token_program: &TOKEN_PROGRAM_ID,
599 amount_out: 1_000,
600 max_amount_in: 1_100,
601 });
602 assert_derived_market_accounts(&ix, &WSOL_MINT, &TOKEN_PROGRAM_ID);
603 }
604
605 #[test]
608 fn the_quote_vault_follows_the_quote_token_program() {
609 let quote_mint = Address::new_from_array([5u8; 32]);
610 let ix = build_sell_exact_in_instruction(&BuildSellExactInParams {
611 user: &USER,
612 payer: None,
613 base_mint: &BASE_MINT,
614 quote_mint: "e_mint,
615 partner: &PARTNER,
616 platform_config: &PLATFORM_CONFIG,
617 quote_token_program: &TOKEN_2022_PROGRAM_ID,
618 amount_in: 1_000,
619 min_amount_out: 900,
620 });
621 assert_derived_market_accounts(
622 &ix,
623 "e_mint,
624 &TOKEN_2022_PROGRAM_ID,
625 );
626 }
627
628 #[test]
630 fn sell_exact_in_builds_from_a_trade_event() {
631 let (pool, _) = crate::dex::pda::find_pool_pda(&BASE_MINT, &WSOL_MINT);
632 let event = TradeEvent {
633 pool,
634 base_mint: BASE_MINT,
635 quote_mint: WSOL_MINT,
636 user: Address::new_from_array([6u8; 32]),
637 coin_creator: Address::new_from_array([7u8; 32]),
638 padding0: [0; 4],
639 platform_config: PLATFORM_CONFIG,
640 base_amount: 0,
641 quote_amount_gross: 0,
642 quote_amount_net: 0,
643 trade_direction: TradeDirection::Buy,
644 partner: Address::new_from_array([9u8; 32]),
645 is_partner: false,
646 protocol_fee: 0,
647 lp_fee: 0,
648 creator_fee: 0,
649 base_transfer_fee: 0,
650 quote_transfer_fee: 0,
651 base_reserves: 400_000_000_000,
652 quote_reserves: 25_000_000_000,
653 timestamp: 0,
654 creator_fee_mode: 0,
655 creator_fee_bps: 0,
656 };
657
658 let (ix, quote) = sell_exact_in(&SellExactInParams {
659 user: &USER,
660 payer: None,
661 base_mint: &event.base_mint,
662 quote_mint: &event.quote_mint,
663 base_reserves: event.base_reserves,
664 quote_reserves: event.quote_reserves,
665 fee_bps: 100,
666 slippage_bps: 50,
667 partner: &PARTNER,
668 platform_config: &event.platform_config,
669 quote_token_program: &TOKEN_PROGRAM_ID,
670 quote_fee: None,
671 base_fee: None,
672 base_amount: 10_000_000_000,
673 })
674 .unwrap();
675
676 assert_eq!(quote.base_from_user, 10_000_000_000);
677 assert_eq!(quote.fee, 6_097_561);
678 assert_eq!(quote.quote_to_user, 603_658_536);
679
680 assert_derived_market_accounts(&ix, &WSOL_MINT, &TOKEN_PROGRAM_ID);
681 assert_eq!(ix.accounts[POOL_INDEX].pubkey, event.pool);
682 let (partner_config, _) = crate::nexus::pda::find_partner_config_pda(
683 &event.platform_config,
684 &PARTNER,
685 );
686 assert_eq!(ix.accounts[PARTNER_CONFIG_INDEX].pubkey, partner_config);
687
688 assert_eq!(
689 ix.data[..8],
690 crate::dex::instructions::SELL_EXACT_IN_DISCRIMINATOR
691 );
692 let args = <crate::dex::instructions::SellExactInInstructionArgs as borsh::BorshDeserialize>::try_from_slice(&ix.data[8..]).unwrap();
693 assert_eq!(args.amount_in, 10_000_000_000);
694 assert_eq!(args.min_amount_out, 600_640_243);
696 }
697
698 const AMOUNT: u64 = 1_234_567;
699 const LIMIT: u64 = 7_654_321;
700
701 const ACCOUNTS: TradeAccounts = TradeAccounts {
703 user: Address::new_from_array([11u8; 32]),
704 payer: Address::new_from_array([12u8; 32]),
705 market: Address::new_from_array([13u8; 32]),
706 base_mint: Address::new_from_array([14u8; 32]),
707 quote_mint: Address::new_from_array([15u8; 32]),
708 base_vault: Address::new_from_array([16u8; 32]),
709 quote_vault: Address::new_from_array([17u8; 32]),
710 user_base_account: Address::new_from_array([18u8; 32]),
711 user_quote_account: Address::new_from_array([19u8; 32]),
712 partner: Address::new_from_array([20u8; 32]),
713 partner_config: Address::new_from_array([21u8; 32]),
714 quote_token_program: Address::new_from_array([22u8; 32]),
715 };
716
717 const TRADES: [(TradeDirection, TradeMode); 4] = [
718 (TradeDirection::Buy, TradeMode::ExactIn),
719 (TradeDirection::Buy, TradeMode::ExactOut),
720 (TradeDirection::Sell, TradeMode::ExactIn),
721 (TradeDirection::Sell, TradeMode::ExactOut),
722 ];
723
724 const fn trade_args(
725 direction: TradeDirection,
726 mode: TradeMode,
727 ) -> TradeArgs {
728 TradeArgs {
729 direction,
730 mode,
731 amount: AMOUNT,
732 limit: LIMIT,
733 }
734 }
735
736 macro_rules! by_hand {
737 ($ty:ident) => {
738 crate::dex::instructions::$ty {
739 user: ACCOUNTS.user,
740 payer: ACCOUNTS.payer,
741 pool: ACCOUNTS.market,
742 base_mint: ACCOUNTS.base_mint,
743 quote_mint: ACCOUNTS.quote_mint,
744 base_vault: ACCOUNTS.base_vault,
745 quote_vault: ACCOUNTS.quote_vault,
746 user_base_account: ACCOUNTS.user_base_account,
747 user_quote_account: ACCOUNTS.user_quote_account,
748 partner: (ACCOUNTS.partner, true),
749 partner_config: ACCOUNTS.partner_config,
750 base_token_program: TOKEN_2022_PROGRAM_ID,
751 quote_token_program: ACCOUNTS.quote_token_program,
752 associated_token_program: ATA_PROGRAM_ID,
753 system_program: SYSTEM_PROGRAM_ID,
754 event_authority: crate::dex::pda::EVENT_AUTHORITY_ADDRESS,
755 program: DEX_PROGRAM_ID,
756 }
757 };
758 }
759
760 #[test]
761 fn trade_instruction_maps_every_account_onto_each_builder() {
762 use crate::dex::instructions::{
763 BuyExactInInstructionArgs, BuyExactOutInstructionArgs,
764 SellExactInInstructionArgs, SellExactOutInstructionArgs,
765 };
766 let expected = [
767 by_hand!(BuyExactIn).instruction(BuyExactInInstructionArgs {
768 amount_in: AMOUNT,
769 min_amount_out: LIMIT,
770 }),
771 by_hand!(BuyExactOut).instruction(BuyExactOutInstructionArgs {
772 amount_out: AMOUNT,
773 max_amount_in: LIMIT,
774 }),
775 by_hand!(SellExactIn).instruction(SellExactInInstructionArgs {
776 amount_in: AMOUNT,
777 min_amount_out: LIMIT,
778 }),
779 by_hand!(SellExactOut).instruction(SellExactOutInstructionArgs {
780 amount_out: AMOUNT,
781 max_amount_in: LIMIT,
782 }),
783 ];
784 for ((direction, mode), expected) in TRADES.into_iter().zip(expected) {
785 assert_eq!(
786 trade_instruction(&ACCOUNTS, trade_args(direction, mode)),
787 expected,
788 "{direction:?} {mode:?}"
789 );
790 }
791 }
792
793 #[test]
794 fn all_four_trades_share_the_account_metas() {
795 for (direction, mode) in TRADES {
796 assert_eq!(
797 trade_account_metas(&ACCOUNTS),
798 trade_instruction(&ACCOUNTS, trade_args(direction, mode))
799 .accounts,
800 "{direction:?} {mode:?}"
801 );
802 }
803 }
804
805 #[test]
806 fn trade_data_selects_each_trades_discriminator() {
807 use crate::dex::instructions::{
808 BUY_EXACT_IN_DISCRIMINATOR, BUY_EXACT_OUT_DISCRIMINATOR,
809 SELL_EXACT_IN_DISCRIMINATOR, SELL_EXACT_OUT_DISCRIMINATOR,
810 };
811 let discriminators = [
812 BUY_EXACT_IN_DISCRIMINATOR,
813 BUY_EXACT_OUT_DISCRIMINATOR,
814 SELL_EXACT_IN_DISCRIMINATOR,
815 SELL_EXACT_OUT_DISCRIMINATOR,
816 ];
817 for ((direction, mode), discriminator) in
818 TRADES.into_iter().zip(discriminators)
819 {
820 assert_eq!(
821 trade_data(trade_args(direction, mode)),
822 [discriminator, AMOUNT.to_le_bytes(), LIMIT.to_le_bytes()]
823 .concat(),
824 "{direction:?} {mode:?}"
825 );
826 }
827 }
828
829 #[test]
830 fn trade_data_ignores_the_accounts() {
831 for (direction, mode) in TRADES {
832 let args = trade_args(direction, mode);
833 assert_eq!(
834 trade_data(args),
835 trade_instruction(&ACCOUNTS, args).data
836 );
837 }
838 }
839
840 #[test]
842 fn only_a_partner_other_than_the_default_signs() {
843 let unpartnered = TradeAccounts {
844 partner: DEFAULT_PARTNER,
845 ..ACCOUNTS
846 };
847 assert_eq!(
848 trade_account_metas(&unpartnered)[PARTNER_INDEX],
849 AccountMeta::new_readonly(DEFAULT_PARTNER, false)
850 );
851 assert_eq!(
852 trade_account_metas(&ACCOUNTS)[PARTNER_INDEX],
853 AccountMeta::new_readonly(ACCOUNTS.partner, true)
854 );
855 }
856}