manifest_jupiter/
lib.rs

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            // Gets updated on the first iter
99            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    /// ManifestLocalMarket::update should be called once before calling this method
146    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        // 1   Program
214        // 2   Market
215        // 3   Signer
216        // 4   SystemProgram
217        // 5   User Base
218        // 6   User Quote
219        // 7   Vault Base
220        // 8   Vault Quote
221        // 9   Base Token Program
222        // 10  Base Mint
223        // 11  Quote Token Program
224        // 12  Quote Mint
225        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            // Gets updated on the first iter
296            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(&quote_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            // Artificially penalize by 1 atom to be worse than the non-global version.
409            // This ensures that routes that can be filled without global accounts cause less
410            // lock contention on the global accounts, which will allow them to be included
411            // the block earlier. The UX improvement should be worth at least 1 atom.
412            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        // 1   Program
489        // 2   Market
490        // 3   Signer
491        // 4   System Program
492        // 5   User Base
493        // 6   User Quote
494        // 7   Vault Base
495        // 8   Vault Quote
496        // 9   Base Token Program
497        // 10  Base Mint
498        // 11  Quote Token Program
499        // 12  Quote Mint
500        // 13  Global
501        // 14  Global Vault
502        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                        &QUOTE_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(&QUOTE_MINT_KEY).0;
598
599        let mut quote_global_value: DynamicAccount<GlobalFixed, Vec<u8>> = GlobalValue {
600            fixed: GlobalFixed::new_empty(&QUOTE_MINT_KEY),
601            // 2 because 1 deposit, 1 seat
602            dynamic: vec![0; GLOBAL_BLOCK_SIZE * 2],
603        };
604        // Claim a seat and deposit plenty of quote atoms.
605        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        // Clone so the consumed bytes are available for the global trade
614        // accounts later when quoting.
615        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(&quote_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, &quote_mint, &MARKET_KEY),
646            // 4 because 1 extra, 1 seat, 2 orders.
647            dynamic: vec![0; MARKET_BLOCK_SIZE * 4],
648        };
649        // Claim a seat and deposit plenty on both sides.
650        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        // Bid for 10 SOL@ 150USDC/SOL global
661        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        // Ask 10 SOL @ 180USDC/SOL
677        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        // Ask for 1 SOL, Bid for 180 USDC
739        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(&quote_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(&QUOTE_MINT_KEY).0;
770
771        let mut quote_global_value: DynamicAccount<GlobalFixed, Vec<u8>> = GlobalValue {
772            fixed: GlobalFixed::new_empty(&QUOTE_MINT_KEY),
773            // 2 because 1 deposit, 1 seat
774            dynamic: vec![0; GLOBAL_BLOCK_SIZE * 2],
775        };
776        // Claim a seat and deposit plenty of quote atoms.
777        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        // Clone so the consumed bytes are available for the global trade
786        // accounts later when quoting.
787        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(&quote_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, &quote_mint, &MARKET_KEY),
811            // 4 because 1 extra, 1 seat, 2 orders.
812            dynamic: vec![0; MARKET_BLOCK_SIZE * 4],
813        };
814        // Claim a seat and deposit plenty on both sides.
815        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        // Bid for 10 SOL@ 150USDC/SOL global
826        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        // Ask 10 SOL @ 180USDC/SOL
842        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        // Ask for 1 SOL, Bid for 180 USDC
903        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(&quote_params).unwrap();
917
918            // Ignores globals.
919            match side {
920                Side::Ask => {
921                    assert_eq!(quote.out_amount, 0);
922                }
923                Side::Bid => {
924                    // Global is only on the resting bid side. No penalty.
925                    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, &quote_mint, &MARKET_KEY),
938            // 4 because 1 extra, 1 seat, 2 orders.
939            dynamic: vec![0; MARKET_BLOCK_SIZE * 4],
940        };
941        // Claim a seat and deposit plenty on both sides.
942        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        // Bid for 10 SOL@ 150USDC/SOL global
953        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        // Ask 10 SOL @ 180USDC/SOL
969        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        // Ask for 1 SOL, Bid for 180 USDC
1008        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(&quote_params).unwrap();
1022
1023            // Does not get the global single atom punishment.
1024            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, &quote_mint, &MARKET_KEY),
1042            // 4 because 1 extra, 1 seat, 2 orders.
1043            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        // Bid for 10 SOL
1056        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        // Ask 10 SOL
1072        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}