1use anyhow::{Error, Result};
2use jupiter_amm_interface::{
3 AccountMap, Amm, AmmContext, KeyedAccount, Quote, QuoteParams, Side, Swap, SwapAndAccountMetas,
4 SwapParams,
5};
6
7use hypertree::{get_helper, get_mut_helper};
8use manifest::{
9 quantities::{BaseAtoms, QuoteAtoms, WrapperU64},
10 state::{
11 DynamicAccount, GlobalFixed, GlobalValue, MarketFixed, MarketValue, GLOBAL_FIXED_SIZE,
12 },
13 validation::{
14 get_global_address, get_global_vault_address, get_vault_address,
15 loaders::GlobalTradeAccounts, ManifestAccountInfo,
16 },
17};
18use solana_program::{account_info::AccountInfo, system_program};
19use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey};
20use std::{cell::RefCell, mem::size_of, rc::Rc};
21
22macro_rules! dynamic_value_opt_to_account_info {
23 ( $name:ident, $value_opt:expr, $fixed_size:expr, $type:ident, $key:expr ) => {
24 let mut data_vec: Vec<u8> = Vec::new();
25 if $value_opt.is_some() {
26 let mut header_bytes: [u8; $fixed_size] = [0; $fixed_size];
27 *get_mut_helper::<$type>(&mut header_bytes, 0_u32) = $value_opt.as_ref().unwrap().fixed;
28 data_vec.extend_from_slice(&header_bytes);
29 data_vec.append(&mut $value_opt.as_ref().unwrap().dynamic.clone());
30 }
31
32 let mut lamports: u64 = 0;
33 let $name: AccountInfo<'_> = AccountInfo {
34 key: &$key,
35 lamports: Rc::new(RefCell::new(&mut lamports)),
36 data: Rc::new(RefCell::new(&mut data_vec[..])),
37 owner: &manifest::ID,
38 rent_epoch: 0,
39 is_signer: false,
40 is_writable: false,
41 executable: false,
42 };
43 };
44}
45
46#[derive(Clone)]
47pub struct ManifestLocalMarket {
48 market: MarketValue,
49 key: Pubkey,
50 label: String,
51 base_token_program: Pubkey,
52 quote_token_program: Pubkey,
53}
54
55impl ManifestLocalMarket {
56 pub fn get_base_mint(&self) -> Pubkey {
57 *self.market.get_base_mint()
58 }
59 pub fn get_quote_mint(&self) -> Pubkey {
60 *self.market.get_quote_mint()
61 }
62}
63
64impl Amm for ManifestLocalMarket {
65 fn label(&self) -> String {
66 self.label.clone()
67 }
68
69 fn key(&self) -> Pubkey {
70 self.key
71 }
72
73 fn program_id(&self) -> Pubkey {
74 manifest::id()
75 }
76
77 fn get_reserve_mints(&self) -> Vec<Pubkey> {
78 vec![self.get_base_mint(), self.get_quote_mint()]
79 }
80
81 fn get_accounts_to_update(&self) -> Vec<Pubkey> {
82 vec![self.key, self.get_base_mint(), self.get_quote_mint()]
83 }
84
85 fn from_keyed_account(keyed_account: &KeyedAccount, _amm_context: &AmmContext) -> Result<Self> {
86 let mut_data: &mut &[u8] = &mut keyed_account.account.data.as_slice();
87
88 let (header_bytes, dynamic_data) = mut_data.split_at(size_of::<MarketFixed>());
89 let market_fixed: &MarketFixed = get_helper::<MarketFixed>(header_bytes, 0_u32);
90
91 Ok(ManifestLocalMarket {
92 market: DynamicAccount::<MarketFixed, Vec<u8>> {
93 fixed: *market_fixed,
94 dynamic: dynamic_data.to_vec(),
95 },
96 key: keyed_account.key,
97 label: "Manifest".into(),
98 base_token_program: spl_token::id(),
100 quote_token_program: spl_token::id(),
101 })
102 }
103
104 fn update(&mut self, account_map: &AccountMap) -> Result<()> {
105 if let Some(mint) = account_map.get(&self.get_base_mint()) {
106 self.base_token_program = mint.owner;
107 };
108 if let Some(mint) = account_map.get(&self.get_quote_mint()) {
109 self.quote_token_program = mint.owner;
110 };
111
112 let market_account: &solana_sdk::account::Account = account_map.get(&self.key).unwrap();
113
114 let (header_bytes, dynamic_data) = market_account.data.split_at(size_of::<MarketFixed>());
115 let market_fixed: &MarketFixed = get_helper::<MarketFixed>(header_bytes, 0_u32);
116 self.market = DynamicAccount::<MarketFixed, Vec<u8>> {
117 fixed: *market_fixed,
118 dynamic: dynamic_data.to_vec(),
119 };
120 Ok(())
121 }
122
123 fn quote(&self, quote_params: &QuoteParams) -> Result<Quote> {
124 let market: DynamicAccount<MarketFixed, Vec<u8>> = self.market.clone();
125
126 let global_trade_accounts: &[Option<GlobalTradeAccounts>; 2] = &[None, None];
127
128 let out_amount: u64 = if quote_params.input_mint == self.get_base_mint() {
129 let in_atoms: BaseAtoms = BaseAtoms::new(quote_params.amount);
130 market
131 .impact_quote_atoms_with_slot(false, in_atoms, global_trade_accounts, u32::MAX)?
132 .as_u64()
133 } else {
134 let in_atoms: QuoteAtoms = QuoteAtoms::new(quote_params.amount);
135 market
136 .impact_base_atoms_with_slot(true, in_atoms, global_trade_accounts, u32::MAX)?
137 .as_u64()
138 };
139 Ok(Quote {
140 out_amount,
141 ..Quote::default()
142 })
143 }
144
145 fn get_swap_and_account_metas(&self, swap_params: &SwapParams) -> Result<SwapAndAccountMetas> {
147 let SwapParams {
148 destination_mint,
149 source_mint,
150 source_token_account,
151 destination_token_account,
152 token_transfer_authority,
153 ..
154 } = swap_params;
155
156 let (side, base_account, quote_account) = if source_mint == &self.get_base_mint() {
157 if destination_mint != &self.get_quote_mint() {
158 return Err(Error::msg("Invalid quote mint"));
159 }
160 (Side::Ask, source_token_account, destination_token_account)
161 } else {
162 if destination_mint != &self.get_base_mint() {
163 return Err(Error::msg("Invalid base mint"));
164 }
165 (Side::Bid, destination_token_account, source_token_account)
166 };
167
168 let (base_vault, _base_bump) = get_vault_address(&self.key, &self.get_base_mint());
169 let (quote_vault, _quote_bump) = get_vault_address(&self.key, &self.get_quote_mint());
170
171 let account_metas: Vec<AccountMeta> = vec![
172 AccountMeta::new_readonly(manifest::id(), false),
173 AccountMeta::new(*token_transfer_authority, true),
174 AccountMeta::new(self.key, false),
175 AccountMeta::new(system_program::id(), false),
176 AccountMeta::new(*base_account, false),
177 AccountMeta::new(*quote_account, false),
178 AccountMeta::new(base_vault, false),
179 AccountMeta::new(quote_vault, false),
180 AccountMeta::new_readonly(self.base_token_program, false),
181 AccountMeta::new_readonly(self.get_base_mint(), false),
182 AccountMeta::new_readonly(self.quote_token_program, false),
183 AccountMeta::new_readonly(self.get_quote_mint(), false),
184 ];
185
186 Ok(SwapAndAccountMetas {
187 swap: Swap::Openbook { side },
188 account_metas,
189 })
190 }
191
192 fn clone_amm(&self) -> Box<dyn Amm + Send + Sync> {
193 Box::new(self.clone())
194 }
195
196 fn has_dynamic_accounts(&self) -> bool {
197 false
198 }
199
200 fn get_user_setup(&self) -> Option<jupiter_amm_interface::AmmUserSetup> {
201 None
202 }
203
204 fn unidirectional(&self) -> bool {
205 false
206 }
207
208 fn program_dependencies(&self) -> Vec<(Pubkey, String)> {
209 std::vec![]
210 }
211
212 fn get_accounts_len(&self) -> usize {
213 12
226 }
227}
228
229#[derive(Clone)]
230pub struct ManifestMarket {
231 market: MarketValue,
232 key: Pubkey,
233 label: String,
234 base_global: Option<GlobalValue>,
235 quote_global: Option<GlobalValue>,
236 base_token_program: Pubkey,
237 quote_token_program: Pubkey,
238}
239
240impl ManifestMarket {
241 pub fn get_base_mint(&self) -> Pubkey {
242 *self.market.get_base_mint()
243 }
244 pub fn get_quote_mint(&self) -> Pubkey {
245 *self.market.get_quote_mint()
246 }
247 pub fn get_base_global_address(&self) -> Pubkey {
248 get_global_address(self.market.get_base_mint()).0
249 }
250 pub fn get_quote_global_address(&self) -> Pubkey {
251 get_global_address(self.market.get_quote_mint()).0
252 }
253}
254
255impl Amm for ManifestMarket {
256 fn label(&self) -> String {
257 self.label.clone()
258 }
259
260 fn key(&self) -> Pubkey {
261 self.key
262 }
263
264 fn program_id(&self) -> Pubkey {
265 manifest::id()
266 }
267
268 fn get_reserve_mints(&self) -> Vec<Pubkey> {
269 vec![self.get_base_mint(), self.get_quote_mint()]
270 }
271
272 fn get_accounts_to_update(&self) -> Vec<Pubkey> {
273 vec![
274 self.key,
275 self.get_base_mint(),
276 self.get_quote_mint(),
277 self.get_base_global_address(),
278 self.get_quote_global_address(),
279 ]
280 }
281
282 fn from_keyed_account(keyed_account: &KeyedAccount, _amm_context: &AmmContext) -> Result<Self> {
283 let mut_data: &mut &[u8] = &mut keyed_account.account.data.as_slice();
284
285 let (header_bytes, dynamic_data) = mut_data.split_at(size_of::<MarketFixed>());
286 let market_fixed: &MarketFixed = get_helper::<MarketFixed>(header_bytes, 0_u32);
287
288 Ok(ManifestMarket {
289 market: DynamicAccount::<MarketFixed, Vec<u8>> {
290 fixed: *market_fixed,
291 dynamic: dynamic_data.to_vec(),
292 },
293 key: keyed_account.key,
294 label: "Manifest".into(),
295 base_token_program: spl_token::id(),
297 quote_token_program: spl_token::id(),
298 base_global: None,
299 quote_global: None,
300 })
301 }
302
303 fn update(&mut self, account_map: &AccountMap) -> Result<()> {
304 if let Some(mint) = account_map.get(&self.get_base_mint()) {
305 self.base_token_program = mint.owner;
306 };
307 if let Some(mint) = account_map.get(&self.get_quote_mint()) {
308 self.quote_token_program = mint.owner;
309 };
310 if let Some(global) = account_map.get(&self.get_quote_global_address()) {
311 let (header_bytes, dynamic_data) = global.data.split_at(size_of::<GlobalFixed>());
312 let global_fixed: &GlobalFixed = get_helper::<GlobalFixed>(header_bytes, 0_u32);
313 self.quote_global = Some(DynamicAccount::<GlobalFixed, Vec<u8>> {
314 fixed: *global_fixed,
315 dynamic: dynamic_data.to_vec(),
316 });
317 };
318 if let Some(global) = account_map.get(&self.get_base_global_address()) {
319 let (header_bytes, dynamic_data) = global.data.split_at(size_of::<GlobalFixed>());
320 let global_fixed: &GlobalFixed = get_helper::<GlobalFixed>(header_bytes, 0_u32);
321 self.base_global = Some(DynamicAccount::<GlobalFixed, Vec<u8>> {
322 fixed: *global_fixed,
323 dynamic: dynamic_data.to_vec(),
324 });
325 };
326
327 let market_account: &solana_sdk::account::Account = account_map.get(&self.key).unwrap();
328
329 let (header_bytes, dynamic_data) = market_account.data.split_at(size_of::<MarketFixed>());
330 let market_fixed: &MarketFixed = get_helper::<MarketFixed>(header_bytes, 0_u32);
331 self.market = DynamicAccount::<MarketFixed, Vec<u8>> {
332 fixed: *market_fixed,
333 dynamic: dynamic_data.to_vec(),
334 };
335 Ok(())
336 }
337
338 fn quote(&self, quote_params: &QuoteParams) -> Result<Quote> {
339 let market: DynamicAccount<MarketFixed, Vec<u8>> = self.market.clone();
340
341 dynamic_value_opt_to_account_info!(
342 quote_global_account_info,
343 self.quote_global,
344 GLOBAL_FIXED_SIZE,
345 GlobalFixed,
346 self.get_quote_global_address()
347 );
348
349 let quote_global_trade_accounts_opt: Option<GlobalTradeAccounts> =
350 if self.quote_global.is_some() {
351 Some(GlobalTradeAccounts {
352 mint_opt: None,
353 global: ManifestAccountInfo::new("e_global_account_info).unwrap(),
354 global_vault_opt: None,
355 market_vault_opt: None,
356 token_program_opt: None,
357 system_program: None,
358 gas_payer_opt: None,
359 gas_receiver_opt: None,
360 market: self.key.clone(),
361 })
362 } else {
363 None
364 };
365
366 dynamic_value_opt_to_account_info!(
367 base_global_account_info,
368 self.base_global,
369 GLOBAL_FIXED_SIZE,
370 GlobalFixed,
371 self.get_base_global_address()
372 );
373
374 let base_global_trade_accounts_opt: Option<GlobalTradeAccounts> =
375 if self.base_global.is_some() {
376 Some(GlobalTradeAccounts {
377 mint_opt: None,
378 global: ManifestAccountInfo::new(&base_global_account_info).unwrap(),
379 global_vault_opt: None,
380 market_vault_opt: None,
381 token_program_opt: None,
382 system_program: None,
383 gas_payer_opt: None,
384 gas_receiver_opt: None,
385 market: self.key.clone(),
386 })
387 } else {
388 None
389 };
390
391 let global_trade_accounts: &[Option<GlobalTradeAccounts>; 2] = &[
392 base_global_trade_accounts_opt,
393 quote_global_trade_accounts_opt,
394 ];
395
396 let out_amount: u64 = if quote_params.input_mint == self.get_base_mint() {
397 let in_atoms: BaseAtoms = BaseAtoms::new(quote_params.amount);
398 market
399 .impact_quote_atoms_with_slot(false, in_atoms, global_trade_accounts, u32::MAX)?
400 .as_u64()
401 } else {
402 let in_atoms: QuoteAtoms = QuoteAtoms::new(quote_params.amount);
403 market
404 .impact_base_atoms_with_slot(true, in_atoms, global_trade_accounts, u32::MAX)?
405 .as_u64()
406 };
407 Ok(Quote {
408 out_amount: out_amount.saturating_sub(1),
413 ..Quote::default()
414 })
415 }
416
417 fn get_swap_and_account_metas(&self, swap_params: &SwapParams) -> Result<SwapAndAccountMetas> {
418 let SwapParams {
419 destination_mint,
420 source_mint,
421 source_token_account,
422 destination_token_account,
423 token_transfer_authority,
424 ..
425 } = swap_params;
426
427 let (side, base_account, quote_account) = if source_mint == &self.get_base_mint() {
428 if destination_mint != &self.get_quote_mint() {
429 return Err(Error::msg("Invalid quote mint"));
430 }
431 (Side::Ask, source_token_account, destination_token_account)
432 } else {
433 if destination_mint != &self.get_base_mint() {
434 return Err(Error::msg("Invalid base mint"));
435 }
436 (Side::Bid, destination_token_account, source_token_account)
437 };
438
439 let (base_vault, _base_bump) = get_vault_address(&self.key, &self.get_base_mint());
440 let (quote_vault, _quote_bump) = get_vault_address(&self.key, &self.get_quote_mint());
441 let (global, _global_bump) = get_global_address(destination_mint);
442 let (global_vault, _global_vault_bump) = get_global_vault_address(destination_mint);
443
444 let account_metas: Vec<AccountMeta> = vec![
445 AccountMeta::new_readonly(manifest::id(), false),
446 AccountMeta::new(*token_transfer_authority, true),
447 AccountMeta::new(self.key, false),
448 AccountMeta::new(system_program::id(), false),
449 AccountMeta::new(*base_account, false),
450 AccountMeta::new(*quote_account, false),
451 AccountMeta::new(base_vault, false),
452 AccountMeta::new(quote_vault, false),
453 AccountMeta::new_readonly(self.base_token_program, false),
454 AccountMeta::new_readonly(self.get_base_mint(), false),
455 AccountMeta::new_readonly(self.quote_token_program, false),
456 AccountMeta::new_readonly(self.get_quote_mint(), false),
457 AccountMeta::new(global, false),
458 AccountMeta::new(global_vault, false),
459 ];
460
461 Ok(SwapAndAccountMetas {
462 swap: Swap::Openbook { side },
463 account_metas,
464 })
465 }
466
467 fn clone_amm(&self) -> Box<dyn Amm + Send + Sync> {
468 Box::new(self.clone())
469 }
470
471 fn has_dynamic_accounts(&self) -> bool {
472 false
473 }
474
475 fn get_user_setup(&self) -> Option<jupiter_amm_interface::AmmUserSetup> {
476 None
477 }
478
479 fn unidirectional(&self) -> bool {
480 false
481 }
482
483 fn program_dependencies(&self) -> Vec<(Pubkey, String)> {
484 std::vec![]
485 }
486
487 fn get_accounts_len(&self) -> usize {
488 14
503 }
504}
505
506#[cfg(test)]
507mod test {
508 use super::*;
509 use hypertree::{get_mut_helper, DataIndex};
510 use jupiter_amm_interface::{ClockRef, SwapMode};
511 use manifest::{
512 quantities::{BaseAtoms, GlobalAtoms},
513 state::{
514 constants::NO_EXPIRATION_LAST_VALID_SLOT, AddOrderToMarketArgs, OrderType,
515 GLOBAL_BLOCK_SIZE, MARKET_BLOCK_SIZE, MARKET_FIXED_SIZE,
516 },
517 validation::{MintAccountInfo, Signer},
518 };
519 use solana_sdk::{account::Account, account_info::AccountInfo, pubkey};
520 use spl_token_2022::state::Mint;
521 use std::{cell::RefCell, collections::HashMap, rc::Rc};
522
523 const BASE_MINT_KEY: Pubkey = pubkey!("So11111111111111111111111111111111111111112");
524 const QUOTE_MINT_KEY: Pubkey = pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
525 const MARKET_KEY: Pubkey = pubkey!("GPPda3ZQZannxp3AK8bSishVqvhHAxogiWdhw1mvmoZr");
526 const TRADER_KEY: Pubkey = pubkey!("GCtjtH2ehL6BZTjismuZ8JhQnuM6U3bmtxVoFyiHMHGc");
527
528 macro_rules! mint_account_info {
529 ($name:ident, $decimals:expr) => {
530 let mut lamports: u64 = 0;
531 let $name: MintAccountInfo = MintAccountInfo {
532 mint: Mint {
533 mint_authority: None.into(),
534 supply: 0,
535 decimals: $decimals,
536 is_initialized: true,
537 freeze_authority: None.into(),
538 },
539 info: &AccountInfo {
540 key: if $decimals == 9 {
541 &BASE_MINT_KEY
542 } else {
543 "E_MINT_KEY
544 },
545 lamports: Rc::new(RefCell::new(&mut lamports)),
546 data: Rc::new(RefCell::new(&mut [])),
547 owner: &Pubkey::new_unique(),
548 rent_epoch: 0,
549 is_signer: false,
550 is_writable: false,
551 executable: false,
552 },
553 };
554 };
555 }
556
557 macro_rules! dynamic_value_to_account {
558 ( $name:ident, $value:expr, $fixed_size:expr, $type:ident ) => {
559 let mut header_bytes: [u8; $fixed_size] = [0; $fixed_size];
560 *get_mut_helper::<$type>(&mut header_bytes, 0_u32) = $value.fixed;
561
562 let mut data_vec: Vec<u8> = Vec::new();
563 data_vec.extend_from_slice(&header_bytes);
564 data_vec.append(&mut $value.dynamic);
565
566 let $name: Account = Account {
567 lamports: 0,
568 data: data_vec,
569 owner: manifest::id(),
570 executable: false,
571 rent_epoch: 0,
572 };
573 };
574 }
575
576 macro_rules! signer {
577 ( $name:ident) => {
578 let mut lamports: u64 = 1_000_000_000;
579 let account_info: AccountInfo<'_> = AccountInfo {
580 key: &TRADER_KEY,
581 lamports: Rc::new(RefCell::new(&mut lamports)),
582 data: Rc::new(RefCell::new(&mut [])),
583 owner: &manifest::ID,
584 rent_epoch: 0,
585 is_signer: true,
586 is_writable: false,
587 executable: false,
588 };
589 let $name = Signer::new(&account_info).expect("valid signer");
590 };
591 }
592
593 #[test]
594 fn test_jupiter_global_with_global_orders() {
595 mint_account_info!(base_mint, 9);
596 mint_account_info!(quote_mint, 6);
597 let quote_global_key: Pubkey = get_global_address("E_MINT_KEY).0;
598
599 let mut quote_global_value: DynamicAccount<GlobalFixed, Vec<u8>> = GlobalValue {
600 fixed: GlobalFixed::new_empty("E_MINT_KEY),
601 dynamic: vec![0; GLOBAL_BLOCK_SIZE * 2],
603 };
604 quote_global_value.global_expand().expect("global expand");
606 quote_global_value
607 .add_trader(&TRADER_KEY)
608 .expect("claim global seat");
609 quote_global_value
610 .deposit_global(&TRADER_KEY, GlobalAtoms::new(1_000_000_000))
611 .expect("deposit quote global");
612
613 dynamic_value_opt_to_account_info!(
616 quote_global_account_info,
617 Some(quote_global_value.clone()),
618 GLOBAL_FIXED_SIZE,
619 GlobalFixed,
620 quote_global_key
621 );
622 signer!(gas_payer_account_info);
623
624 let quote_global_trade_accounts: Option<GlobalTradeAccounts<'_, '_>> =
625 Some(GlobalTradeAccounts {
626 mint_opt: None,
627 global: ManifestAccountInfo::new("e_global_account_info).unwrap(),
628 global_vault_opt: None,
629 market_vault_opt: None,
630 token_program_opt: None,
631 system_program: None,
632 gas_payer_opt: Some(gas_payer_account_info),
633 gas_receiver_opt: None,
634 market: MARKET_KEY,
635 });
636
637 dynamic_value_to_account!(
638 quote_global_account,
639 quote_global_value,
640 GLOBAL_FIXED_SIZE,
641 GlobalFixed
642 );
643
644 let mut market_value: DynamicAccount<MarketFixed, Vec<u8>> = MarketValue {
645 fixed: MarketFixed::new_empty(&base_mint, "e_mint, &MARKET_KEY),
646 dynamic: vec![0; MARKET_BLOCK_SIZE * 4],
648 };
649 market_value.market_expand().unwrap();
651 market_value.claim_seat(&TRADER_KEY).unwrap();
652 let trader_index: DataIndex = market_value.get_trader_index(&TRADER_KEY);
653 market_value
654 .deposit(trader_index, 1_000_000_000_000, true)
655 .unwrap();
656 market_value
657 .deposit(trader_index, 1_000_000_000_000, false)
658 .unwrap();
659
660 market_value.market_expand().unwrap();
662 market_value
663 .place_order(AddOrderToMarketArgs {
664 market: MARKET_KEY,
665 trader_index,
666 num_base_atoms: BaseAtoms::new(10_000),
667 price: 0.150.try_into().unwrap(),
668 is_bid: true,
669 last_valid_slot: NO_EXPIRATION_LAST_VALID_SLOT,
670 order_type: OrderType::Global,
671 global_trade_accounts_opts: &[None, quote_global_trade_accounts],
672 current_slot: None,
673 })
674 .unwrap();
675
676 market_value.market_expand().unwrap();
678 market_value
679 .place_order(AddOrderToMarketArgs {
680 market: MARKET_KEY,
681 trader_index,
682 num_base_atoms: BaseAtoms::new(10_000),
683 price: 0.180.try_into().unwrap(),
684 is_bid: false,
685 last_valid_slot: NO_EXPIRATION_LAST_VALID_SLOT,
686 order_type: OrderType::Limit,
687 global_trade_accounts_opts: &[None, None],
688 current_slot: None,
689 })
690 .unwrap();
691
692 dynamic_value_to_account!(market_account, market_value, MARKET_FIXED_SIZE, MarketFixed);
693
694 let market_keyed_account: KeyedAccount = KeyedAccount {
695 key: MARKET_KEY,
696 account: market_account.clone(),
697 params: None,
698 };
699
700 let amm_context: AmmContext = AmmContext {
701 clock_ref: ClockRef::default(),
702 };
703
704 let mut manifest_market: ManifestMarket =
705 ManifestMarket::from_keyed_account(&market_keyed_account, &amm_context).unwrap();
706
707 let accounts_map: AccountMap = HashMap::from([
708 (MARKET_KEY, market_account),
709 (quote_global_key, quote_global_account),
710 (
711 BASE_MINT_KEY,
712 Account {
713 lamports: 0,
714 data: Vec::new(),
715 owner: spl_token::id(),
716 executable: false,
717 rent_epoch: 0,
718 },
719 ),
720 (
721 QUOTE_MINT_KEY,
722 Account {
723 lamports: 0,
724 data: Vec::new(),
725 owner: spl_token::id(),
726 executable: false,
727 rent_epoch: 0,
728 },
729 ),
730 ]);
731 manifest_market.update(&accounts_map).unwrap();
732
733 let (base_mint, quote_mint) = {
734 let reserves: Vec<Pubkey> = manifest_market.get_reserve_mints();
735 (reserves[0], reserves[1])
736 };
737
738 for (side, in_amount) in [(Side::Ask, 1_000_000_000), (Side::Bid, 180_000_000)] {
740 let (input_mint, output_mint) = match side {
741 Side::Ask => (base_mint, quote_mint),
742 Side::Bid => (quote_mint, base_mint),
743 };
744
745 let quote_params: QuoteParams = QuoteParams {
746 amount: in_amount,
747 swap_mode: SwapMode::ExactIn,
748 input_mint,
749 output_mint,
750 };
751
752 let quote: Quote = manifest_market.quote("e_params).unwrap();
753
754 match side {
755 Side::Ask => {
756 assert_eq!(quote.out_amount, 1_499);
757 }
758 Side::Bid => {
759 assert_eq!(quote.out_amount, 9_999);
760 }
761 };
762 }
763 }
764
765 #[test]
766 fn test_jupiter_local_with_global_orders() {
767 mint_account_info!(base_mint, 9);
768 mint_account_info!(quote_mint, 6);
769 let quote_global_key: Pubkey = get_global_address("E_MINT_KEY).0;
770
771 let mut quote_global_value: DynamicAccount<GlobalFixed, Vec<u8>> = GlobalValue {
772 fixed: GlobalFixed::new_empty("E_MINT_KEY),
773 dynamic: vec![0; GLOBAL_BLOCK_SIZE * 2],
775 };
776 quote_global_value.global_expand().expect("global expand");
778 quote_global_value
779 .add_trader(&TRADER_KEY)
780 .expect("claim global seat");
781 quote_global_value
782 .deposit_global(&TRADER_KEY, GlobalAtoms::new(1_000_000_000))
783 .expect("deposit quote global");
784
785 dynamic_value_opt_to_account_info!(
788 quote_global_account_info,
789 Some(quote_global_value.clone()),
790 GLOBAL_FIXED_SIZE,
791 GlobalFixed,
792 quote_global_key
793 );
794 signer!(gas_payer_account_info);
795
796 let quote_global_trade_accounts: Option<GlobalTradeAccounts<'_, '_>> =
797 Some(GlobalTradeAccounts {
798 mint_opt: None,
799 global: ManifestAccountInfo::new("e_global_account_info).unwrap(),
800 global_vault_opt: None,
801 market_vault_opt: None,
802 token_program_opt: None,
803 system_program: None,
804 gas_payer_opt: Some(gas_payer_account_info),
805 gas_receiver_opt: None,
806 market: MARKET_KEY,
807 });
808
809 let mut market_value: DynamicAccount<MarketFixed, Vec<u8>> = MarketValue {
810 fixed: MarketFixed::new_empty(&base_mint, "e_mint, &MARKET_KEY),
811 dynamic: vec![0; MARKET_BLOCK_SIZE * 4],
813 };
814 market_value.market_expand().unwrap();
816 market_value.claim_seat(&TRADER_KEY).unwrap();
817 let trader_index: DataIndex = market_value.get_trader_index(&TRADER_KEY);
818 market_value
819 .deposit(trader_index, 1_000_000_000_000, true)
820 .unwrap();
821 market_value
822 .deposit(trader_index, 1_000_000_000_000, false)
823 .unwrap();
824
825 market_value.market_expand().unwrap();
827 market_value
828 .place_order(AddOrderToMarketArgs {
829 market: MARKET_KEY,
830 trader_index,
831 num_base_atoms: BaseAtoms::new(10_000),
832 price: 0.150.try_into().unwrap(),
833 is_bid: true,
834 last_valid_slot: NO_EXPIRATION_LAST_VALID_SLOT,
835 order_type: OrderType::Global,
836 global_trade_accounts_opts: &[None, quote_global_trade_accounts],
837 current_slot: None,
838 })
839 .unwrap();
840
841 market_value.market_expand().unwrap();
843 market_value
844 .place_order(AddOrderToMarketArgs {
845 market: MARKET_KEY,
846 trader_index,
847 num_base_atoms: BaseAtoms::new(10_000),
848 price: 0.180.try_into().unwrap(),
849 is_bid: false,
850 last_valid_slot: NO_EXPIRATION_LAST_VALID_SLOT,
851 order_type: OrderType::Limit,
852 global_trade_accounts_opts: &[None, None],
853 current_slot: None,
854 })
855 .unwrap();
856
857 dynamic_value_to_account!(market_account, market_value, MARKET_FIXED_SIZE, MarketFixed);
858
859 let market_keyed_account: KeyedAccount = KeyedAccount {
860 key: MARKET_KEY,
861 account: market_account.clone(),
862 params: None,
863 };
864
865 let amm_context: AmmContext = AmmContext {
866 clock_ref: ClockRef::default(),
867 };
868
869 let mut manifest_market: ManifestLocalMarket =
870 ManifestLocalMarket::from_keyed_account(&market_keyed_account, &amm_context).unwrap();
871
872 let accounts_map: AccountMap = HashMap::from([
873 (MARKET_KEY, market_account),
874 (
875 BASE_MINT_KEY,
876 Account {
877 lamports: 0,
878 data: Vec::new(),
879 owner: spl_token::id(),
880 executable: false,
881 rent_epoch: 0,
882 },
883 ),
884 (
885 QUOTE_MINT_KEY,
886 Account {
887 lamports: 0,
888 data: Vec::new(),
889 owner: spl_token::id(),
890 executable: false,
891 rent_epoch: 0,
892 },
893 ),
894 ]);
895 manifest_market.update(&accounts_map).unwrap();
896
897 let (base_mint, quote_mint) = {
898 let reserves: Vec<Pubkey> = manifest_market.get_reserve_mints();
899 (reserves[0], reserves[1])
900 };
901
902 for (side, in_amount) in [(Side::Ask, 1_000_000_000), (Side::Bid, 180_000_000)] {
904 let (input_mint, output_mint) = match side {
905 Side::Ask => (base_mint, quote_mint),
906 Side::Bid => (quote_mint, base_mint),
907 };
908
909 let quote_params: QuoteParams = QuoteParams {
910 amount: in_amount,
911 swap_mode: SwapMode::ExactIn,
912 input_mint,
913 output_mint,
914 };
915
916 let quote: Quote = manifest_market.quote("e_params).unwrap();
917
918 match side {
920 Side::Ask => {
921 assert_eq!(quote.out_amount, 0);
922 }
923 Side::Bid => {
924 assert_eq!(quote.out_amount, 10_000);
926 }
927 };
928 }
929 }
930
931 #[test]
932 fn test_jupiter_local_with_local_orders() {
933 mint_account_info!(base_mint, 9);
934 mint_account_info!(quote_mint, 6);
935
936 let mut market_value: DynamicAccount<MarketFixed, Vec<u8>> = MarketValue {
937 fixed: MarketFixed::new_empty(&base_mint, "e_mint, &MARKET_KEY),
938 dynamic: vec![0; MARKET_BLOCK_SIZE * 4],
940 };
941 market_value.market_expand().unwrap();
943 market_value.claim_seat(&TRADER_KEY).unwrap();
944 let trader_index: DataIndex = market_value.get_trader_index(&TRADER_KEY);
945 market_value
946 .deposit(trader_index, 1_000_000_000_000, true)
947 .unwrap();
948 market_value
949 .deposit(trader_index, 1_000_000_000_000, false)
950 .unwrap();
951
952 market_value.market_expand().unwrap();
954 market_value
955 .place_order(AddOrderToMarketArgs {
956 market: MARKET_KEY,
957 trader_index,
958 num_base_atoms: BaseAtoms::new(10_000),
959 price: 0.150.try_into().unwrap(),
960 is_bid: true,
961 last_valid_slot: NO_EXPIRATION_LAST_VALID_SLOT,
962 order_type: OrderType::Limit,
963 global_trade_accounts_opts: &[None, None],
964 current_slot: None,
965 })
966 .unwrap();
967
968 market_value.market_expand().unwrap();
970 market_value
971 .place_order(AddOrderToMarketArgs {
972 market: MARKET_KEY,
973 trader_index,
974 num_base_atoms: BaseAtoms::new(10_000),
975 price: 0.180.try_into().unwrap(),
976 is_bid: false,
977 last_valid_slot: NO_EXPIRATION_LAST_VALID_SLOT,
978 order_type: OrderType::Limit,
979 global_trade_accounts_opts: &[None, None],
980 current_slot: None,
981 })
982 .unwrap();
983
984 dynamic_value_to_account!(market_account, market_value, MARKET_FIXED_SIZE, MarketFixed);
985
986 let market_keyed_account: KeyedAccount = KeyedAccount {
987 key: MARKET_KEY,
988 account: market_account.clone(),
989 params: None,
990 };
991
992 let amm_context: AmmContext = AmmContext {
993 clock_ref: ClockRef::default(),
994 };
995
996 let mut manifest_market: ManifestLocalMarket =
997 ManifestLocalMarket::from_keyed_account(&market_keyed_account, &amm_context).unwrap();
998
999 let accounts_map: AccountMap = HashMap::from([(MARKET_KEY, market_account)]);
1000 manifest_market.update(&accounts_map).unwrap();
1001
1002 let (base_mint, quote_mint) = {
1003 let reserves: Vec<Pubkey> = manifest_market.get_reserve_mints();
1004 (reserves[0], reserves[1])
1005 };
1006
1007 for (side, in_amount) in [(Side::Ask, 1_000_000_000), (Side::Bid, 180_000_000)] {
1009 let (input_mint, output_mint) = match side {
1010 Side::Ask => (base_mint, quote_mint),
1011 Side::Bid => (quote_mint, base_mint),
1012 };
1013
1014 let quote_params: QuoteParams = QuoteParams {
1015 amount: in_amount,
1016 swap_mode: SwapMode::ExactIn,
1017 input_mint,
1018 output_mint,
1019 };
1020
1021 let quote: Quote = manifest_market.quote("e_params).unwrap();
1022
1023 match side {
1025 Side::Ask => {
1026 assert_eq!(quote.out_amount, 1_500);
1027 }
1028 Side::Bid => {
1029 assert_eq!(quote.out_amount, 10_000);
1030 }
1031 };
1032 }
1033 }
1034
1035 #[test]
1036 fn test_jupiter_other() {
1037 mint_account_info!(base_mint, 9);
1038 mint_account_info!(quote_mint, 6);
1039
1040 let mut market_value: DynamicAccount<MarketFixed, Vec<u8>> = MarketValue {
1041 fixed: MarketFixed::new_empty(&base_mint, "e_mint, &MARKET_KEY),
1042 dynamic: vec![0; MARKET_BLOCK_SIZE * 4],
1044 };
1045 market_value.market_expand().unwrap();
1046 market_value.claim_seat(&TRADER_KEY).unwrap();
1047 let trader_index: DataIndex = market_value.get_trader_index(&TRADER_KEY);
1048 market_value
1049 .deposit(trader_index, 1_000_000_000_000, true)
1050 .unwrap();
1051 market_value
1052 .deposit(trader_index, 1_000_000_000_000, false)
1053 .unwrap();
1054
1055 market_value.market_expand().unwrap();
1057 market_value
1058 .place_order(AddOrderToMarketArgs {
1059 market: MARKET_KEY,
1060 trader_index,
1061 num_base_atoms: BaseAtoms::new(10_000),
1062 price: 0.150.try_into().unwrap(),
1063 is_bid: true,
1064 last_valid_slot: NO_EXPIRATION_LAST_VALID_SLOT,
1065 order_type: OrderType::Limit,
1066 global_trade_accounts_opts: &[None, None],
1067 current_slot: None,
1068 })
1069 .unwrap();
1070
1071 market_value.market_expand().unwrap();
1073 market_value
1074 .place_order(AddOrderToMarketArgs {
1075 market: MARKET_KEY,
1076 trader_index,
1077 num_base_atoms: BaseAtoms::new(10_000),
1078 price: 0.180.try_into().unwrap(),
1079 is_bid: false,
1080 last_valid_slot: NO_EXPIRATION_LAST_VALID_SLOT,
1081 order_type: OrderType::Limit,
1082 global_trade_accounts_opts: &[None, None],
1083 current_slot: None,
1084 })
1085 .unwrap();
1086
1087 let mut header_bytes: [u8; MARKET_FIXED_SIZE] = [0; MARKET_FIXED_SIZE];
1088 *get_mut_helper::<MarketFixed>(&mut header_bytes, 0_u32) = market_value.fixed;
1089
1090 let mut data_vec: Vec<u8> = Vec::new();
1091 data_vec.extend_from_slice(&header_bytes);
1092 data_vec.append(&mut market_value.dynamic);
1093
1094 let account: Account = Account {
1095 lamports: 0,
1096 data: data_vec,
1097 owner: manifest::id(),
1098 executable: false,
1099 rent_epoch: 0,
1100 };
1101
1102 let market_account: KeyedAccount = KeyedAccount {
1103 key: MARKET_KEY,
1104 account: account.clone(),
1105 params: None,
1106 };
1107
1108 let amm_context: AmmContext = AmmContext {
1109 clock_ref: ClockRef::default(),
1110 };
1111 let manifest_market: ManifestMarket =
1112 ManifestMarket::from_keyed_account(&market_account, &amm_context).unwrap();
1113
1114 assert_eq!(manifest_market.get_accounts_len(), 14);
1115 assert_eq!(manifest_market.label(), "Manifest");
1116 assert_eq!(manifest_market.key(), MARKET_KEY);
1117 assert_eq!(manifest_market.program_id(), manifest::id());
1118 assert_eq!(manifest_market.get_reserve_mints()[0], BASE_MINT_KEY);
1119 assert_eq!(manifest_market.get_accounts_to_update().len(), 5);
1120
1121 let swap_params: SwapParams = SwapParams {
1122 in_amount: 1,
1123 source_mint: manifest_market.get_base_mint(),
1124 destination_mint: manifest_market.get_quote_mint(),
1125 source_token_account: Pubkey::new_unique(),
1126 destination_token_account: Pubkey::new_unique(),
1127 token_transfer_authority: TRADER_KEY,
1128 missing_dynamic_accounts_as_default: false,
1129 open_order_address: None,
1130 quote_mint_to_referrer: None,
1131 out_amount: 0,
1132 jupiter_program_id: &manifest::id(),
1133 };
1134
1135 let _results_forward: SwapAndAccountMetas = manifest_market
1136 .get_swap_and_account_metas(&swap_params)
1137 .unwrap();
1138
1139 let swap_params: SwapParams = SwapParams {
1140 in_amount: 1,
1141 source_mint: manifest_market.get_quote_mint(),
1142 destination_mint: manifest_market.get_base_mint(),
1143 source_token_account: Pubkey::new_unique(),
1144 destination_token_account: Pubkey::new_unique(),
1145 token_transfer_authority: TRADER_KEY,
1146 missing_dynamic_accounts_as_default: false,
1147 open_order_address: None,
1148 quote_mint_to_referrer: None,
1149 out_amount: 0,
1150 jupiter_program_id: &manifest::id(),
1151 };
1152
1153 let _results_backward: SwapAndAccountMetas = manifest_market
1154 .get_swap_and_account_metas(&swap_params)
1155 .unwrap();
1156
1157 manifest_market.clone_amm();
1158 assert!(!manifest_market.has_dynamic_accounts());
1159 assert!(manifest_market.get_user_setup().is_none());
1160 assert!(!manifest_market.unidirectional());
1161 assert_eq!(manifest_market.program_dependencies().len(), 0);
1162
1163 let manifest_local_market: ManifestLocalMarket =
1164 ManifestLocalMarket::from_keyed_account(&market_account, &amm_context).unwrap();
1165 assert_eq!(manifest_local_market.label(), "Manifest");
1166 assert_eq!(manifest_local_market.key(), MARKET_KEY);
1167 assert_eq!(manifest_local_market.program_id(), manifest::id());
1168 assert_eq!(manifest_local_market.get_accounts_to_update().len(), 3);
1169 assert_eq!(manifest_local_market.get_accounts_len(), 12);
1170 assert_eq!(manifest_local_market.get_reserve_mints()[0], BASE_MINT_KEY);
1171 manifest_local_market.clone_amm();
1172 assert!(!manifest_local_market.has_dynamic_accounts());
1173 assert!(manifest_local_market.get_user_setup().is_none());
1174 assert!(!manifest_local_market.unidirectional());
1175 assert_eq!(manifest_local_market.program_dependencies().len(), 0);
1176
1177 let swap_params: SwapParams = SwapParams {
1178 in_amount: 1,
1179 source_mint: manifest_market.get_base_mint(),
1180 destination_mint: manifest_market.get_quote_mint(),
1181 source_token_account: Pubkey::new_unique(),
1182 destination_token_account: Pubkey::new_unique(),
1183 token_transfer_authority: TRADER_KEY,
1184 missing_dynamic_accounts_as_default: false,
1185 open_order_address: None,
1186 quote_mint_to_referrer: None,
1187 out_amount: 0,
1188 jupiter_program_id: &manifest::id(),
1189 };
1190
1191 let _results_forward: SwapAndAccountMetas = manifest_local_market
1192 .get_swap_and_account_metas(&swap_params)
1193 .unwrap();
1194
1195 let swap_params: SwapParams = SwapParams {
1196 in_amount: 1,
1197 source_mint: manifest_market.get_quote_mint(),
1198 destination_mint: manifest_market.get_base_mint(),
1199 source_token_account: Pubkey::new_unique(),
1200 destination_token_account: Pubkey::new_unique(),
1201 token_transfer_authority: TRADER_KEY,
1202 missing_dynamic_accounts_as_default: false,
1203 open_order_address: None,
1204 quote_mint_to_referrer: None,
1205 out_amount: 0,
1206 jupiter_program_id: &manifest::id(),
1207 };
1208
1209 let _results_backward: SwapAndAccountMetas = manifest_local_market
1210 .get_swap_and_account_metas(&swap_params)
1211 .unwrap();
1212 }
1213}