Skip to main content

gmsol_programs/model/
market.rs

1use std::{
2    borrow::Borrow,
3    collections::BTreeMap,
4    ops::{Deref, DerefMut},
5    sync::Arc,
6};
7
8use anchor_lang::prelude::Pubkey;
9use bitmaps::Bitmap;
10use bytemuck::Zeroable;
11use gmsol_model::{
12    params::{
13        fee::{
14            BorrowingFeeKinkModelParams, BorrowingFeeKinkModelParamsForOneSide, BorrowingFeeParams,
15            FundingFeeParams, LiquidationFeeParams,
16        },
17        position::PositionImpactDistributionParams,
18        FeeParams, PositionParams, PriceImpactParams,
19    },
20    PoolKind,
21};
22
23use crate::{
24    constants,
25    gmsol_store::{
26        accounts::{Market, Position},
27        types::{MarketConfig, MarketMeta, Pool, PoolStorage, Pools},
28    },
29    model::{position::PositionKind, PositionModel, VirtualInventoryModel},
30};
31
32use super::clock::{AsClock, AsClockMut};
33
34impl MarketMeta {
35    /// Get token side.
36    pub fn token_side(&self, token: &Pubkey) -> gmsol_model::Result<bool> {
37        if *token == self.long_token_mint {
38            Ok(true)
39        } else if *token == self.short_token_mint {
40            Ok(false)
41        } else {
42            Err(gmsol_model::Error::InvalidArgument("not a pool token"))
43        }
44    }
45}
46
47impl Pools {
48    fn get(&self, kind: PoolKind) -> Option<&PoolStorage> {
49        let pool = match kind {
50            PoolKind::Primary => &self.primary,
51            PoolKind::SwapImpact => &self.swap_impact,
52            PoolKind::ClaimableFee => &self.claimable_fee,
53            PoolKind::OpenInterestForLong => &self.open_interest_for_long,
54            PoolKind::OpenInterestForShort => &self.open_interest_for_short,
55            PoolKind::OpenInterestInTokensForLong => &self.open_interest_in_tokens_for_long,
56            PoolKind::OpenInterestInTokensForShort => &self.open_interest_in_tokens_for_short,
57            PoolKind::PositionImpact => &self.position_impact,
58            PoolKind::BorrowingFactor => &self.borrowing_factor,
59            PoolKind::FundingAmountPerSizeForLong => &self.funding_amount_per_size_for_long,
60            PoolKind::FundingAmountPerSizeForShort => &self.funding_amount_per_size_for_short,
61            PoolKind::ClaimableFundingAmountPerSizeForLong => {
62                &self.claimable_funding_amount_per_size_for_long
63            }
64            PoolKind::ClaimableFundingAmountPerSizeForShort => {
65                &self.claimable_funding_amount_per_size_for_short
66            }
67            PoolKind::CollateralSumForLong => &self.collateral_sum_for_long,
68            PoolKind::CollateralSumForShort => &self.collateral_sum_for_short,
69            PoolKind::TotalBorrowing => &self.total_borrowing,
70            _ => return None,
71        };
72        Some(pool)
73    }
74
75    fn get_mut(&mut self, kind: PoolKind) -> Option<&mut PoolStorage> {
76        let pool = match kind {
77            PoolKind::Primary => &mut self.primary,
78            PoolKind::SwapImpact => &mut self.swap_impact,
79            PoolKind::ClaimableFee => &mut self.claimable_fee,
80            PoolKind::OpenInterestForLong => &mut self.open_interest_for_long,
81            PoolKind::OpenInterestForShort => &mut self.open_interest_for_short,
82            PoolKind::OpenInterestInTokensForLong => &mut self.open_interest_in_tokens_for_long,
83            PoolKind::OpenInterestInTokensForShort => &mut self.open_interest_in_tokens_for_short,
84            PoolKind::PositionImpact => &mut self.position_impact,
85            PoolKind::BorrowingFactor => &mut self.borrowing_factor,
86            PoolKind::FundingAmountPerSizeForLong => &mut self.funding_amount_per_size_for_long,
87            PoolKind::FundingAmountPerSizeForShort => &mut self.funding_amount_per_size_for_short,
88            PoolKind::ClaimableFundingAmountPerSizeForLong => {
89                &mut self.claimable_funding_amount_per_size_for_long
90            }
91            PoolKind::ClaimableFundingAmountPerSizeForShort => {
92                &mut self.claimable_funding_amount_per_size_for_short
93            }
94            PoolKind::CollateralSumForLong => &mut self.collateral_sum_for_long,
95            PoolKind::CollateralSumForShort => &mut self.collateral_sum_for_short,
96            PoolKind::TotalBorrowing => &mut self.total_borrowing,
97            _ => return None,
98        };
99        Some(pool)
100    }
101}
102
103#[repr(u8)]
104enum MarketConfigFlag {
105    SkipBorrowingFeeForSmallerSide,
106    IgnoreOpenInterestForUsageFactor,
107    EnableMarketClosedParams,
108    MarketClosedSkipBorrowingFeeForSmallerSide,
109}
110
111type MarketConfigFlags = Bitmap<{ constants::NUM_MARKET_CONFIG_FLAGS }>;
112
113impl MarketConfig {
114    fn flag(&self, flag: MarketConfigFlag) -> bool {
115        MarketConfigFlags::from_value(self.flag.value).get(flag as usize)
116    }
117
118    fn use_market_closed_params(&self, is_market_closed: bool) -> bool {
119        is_market_closed && self.flag(MarketConfigFlag::EnableMarketClosedParams)
120    }
121
122    fn min_collateral_factor_for_liquidation(&self, is_market_closed: bool) -> Option<u128> {
123        let factor = if self.use_market_closed_params(is_market_closed) {
124            self.market_closed_min_collateral_factor_for_liquidation
125        } else {
126            self.min_collateral_factor_for_liquidation
127        };
128        if factor == 0 {
129            None
130        } else {
131            Some(factor)
132        }
133    }
134
135    fn skip_borrowing_fee_for_smaller_side(&self, is_market_closed: bool) -> bool {
136        if self.use_market_closed_params(is_market_closed) {
137            self.flag(MarketConfigFlag::MarketClosedSkipBorrowingFeeForSmallerSide)
138        } else {
139            self.flag(MarketConfigFlag::SkipBorrowingFeeForSmallerSide)
140        }
141    }
142
143    fn borrowing_fee_base_factor(&self, for_long: bool, is_market_closed: bool) -> u128 {
144        match (self.use_market_closed_params(is_market_closed), for_long) {
145            (true, _) => self.market_closed_borrowing_fee_base_factor,
146            (false, true) => self.borrowing_fee_base_factor_for_long,
147            (false, false) => self.borrowing_fee_base_factor_for_short,
148        }
149    }
150
151    /// Returns above optimal usage borrowing fee factor.
152    fn borrowing_fee_above_optimal_usage_factor(
153        &self,
154        for_long: bool,
155        is_market_closed: bool,
156    ) -> u128 {
157        match (self.use_market_closed_params(is_market_closed), for_long) {
158            (true, _) => self.market_closed_borrowing_fee_above_optimal_usage_factor,
159            (false, true) => self.borrowing_fee_above_optimal_usage_factor_for_long,
160            (false, false) => self.borrowing_fee_above_optimal_usage_factor_for_short,
161        }
162    }
163}
164
165#[repr(u8)]
166#[allow(dead_code)]
167enum MarketFlag {
168    Enabled,
169    Pure,
170    AutoDeleveragingEnabledForLong,
171    AutoDeleveragingEnabledForShort,
172    GTEnabled,
173    Closed,
174}
175
176type MarketFlags = Bitmap<{ constants::NUM_MARKET_FLAGS }>;
177
178impl Market {
179    fn try_pool(&self, kind: PoolKind) -> gmsol_model::Result<&Pool> {
180        Ok(&self
181            .state
182            .pools
183            .get(kind)
184            .ok_or(gmsol_model::Error::MissingPoolKind(kind))?
185            .pool)
186    }
187
188    fn try_pool_mut(&mut self, kind: PoolKind) -> gmsol_model::Result<&mut Pool> {
189        Ok(&mut self
190            .state
191            .pools
192            .get_mut(kind)
193            .ok_or(gmsol_model::Error::MissingPoolKind(kind))?
194            .pool)
195    }
196
197    fn flag(&self, flag: MarketFlag) -> bool {
198        MarketFlags::from_value(self.flags.value).get(flag as usize)
199    }
200
201    fn is_closed(&self) -> bool {
202        self.flag(MarketFlag::Closed)
203    }
204}
205
206/// Swap Pricing Kind.
207#[derive(Debug, Clone, Copy, Default)]
208pub enum SwapPricingKind {
209    /// Swap.
210    #[default]
211    Swap,
212    /// Deposit.
213    Deposit,
214    /// Withdrawal.
215    Withdrawal,
216    /// Shift.
217    Shift,
218}
219
220/// Market Model.
221#[derive(Debug, Clone)]
222pub struct MarketModel {
223    market: Arc<Market>,
224    supply: u64,
225    swap_pricing: SwapPricingKind,
226    vi_for_swaps: Option<VirtualInventoryModel>,
227    vi_for_positions: Option<VirtualInventoryModel>,
228    disable_vis: bool,
229    order_fee_discount_factor: u128,
230}
231
232impl Deref for MarketModel {
233    type Target = Market;
234
235    fn deref(&self) -> &Self::Target {
236        &self.market
237    }
238}
239
240impl MarketModel {
241    /// Create from parts.
242    pub fn from_parts(market: Arc<Market>, supply: u64) -> Self {
243        Self {
244            market,
245            supply,
246            swap_pricing: Default::default(),
247            vi_for_swaps: None,
248            vi_for_positions: None,
249            disable_vis: false,
250            order_fee_discount_factor: 0,
251        }
252    }
253
254    /// Get whether it is a pure market.
255    pub fn is_pure(&self) -> bool {
256        self.market.flag(MarketFlag::Pure)
257    }
258
259    /// Get swap pricing kind.
260    pub fn swap_pricing(&self) -> &SwapPricingKind {
261        &self.swap_pricing
262    }
263
264    /// Execute a function with the specified swap pricing kind.
265    ///
266    /// # Panic Safety
267    /// This method uses RAII to ensure state is restored even if the closure panics.
268    pub fn with_swap_pricing<T>(
269        &mut self,
270        swap_pricing: SwapPricingKind,
271        f: impl FnOnce(&mut Self) -> T,
272    ) -> T {
273        struct SwapPricingGuard<'a> {
274            model: &'a mut MarketModel,
275            original_swap_pricing: SwapPricingKind,
276        }
277
278        impl Drop for SwapPricingGuard<'_> {
279            fn drop(&mut self) {
280                self.model.swap_pricing = self.original_swap_pricing;
281            }
282        }
283
284        let original_swap_pricing = self.swap_pricing;
285        self.swap_pricing = swap_pricing;
286
287        let guard = SwapPricingGuard {
288            model: self,
289            original_swap_pricing,
290        };
291
292        (f)(guard.model)
293        // guard is automatically dropped at the end of scope, restoring original state
294    }
295
296    /// Execute a function with virtual inventory models from a map.
297    ///
298    /// This method temporarily replaces the virtual inventory models
299    /// (for swaps and positions) of the `MarketModel` with models from the provided map,
300    /// executes the provided function, and then restores the original values.
301    ///
302    /// The virtual inventory models are looked up from the map using the market's
303    /// `virtual_inventory_for_swaps` and `virtual_inventory_for_positions` addresses.
304    ///
305    /// # Arguments
306    /// * `vi_map` - A mutable reference to a map of Pubkey to VirtualInventoryModel
307    /// * `f` - Function to execute with the temporary VI models
308    ///
309    /// # Returns
310    /// The return value of the function `f`
311    ///
312    /// # Note
313    /// The virtual inventory models are passed via a mutable reference to a map,
314    /// allowing the caller to maintain access to the models and observe any
315    /// state changes made during the function execution.
316    ///
317    /// # Panic Safety
318    /// This method uses RAII to ensure state is restored even if the closure panics.
319    ///
320    /// # Notes
321    /// - If the `MarketModel` already has virtual inventory models attached
322    ///   (i.e. `vi_for_swaps` / `vi_for_positions` are `Some`), this function
323    ///   will not load VI models from `vi_map` and will not write any changes
324    ///   back to `vi_map`. In that case, only the existing in-model VI
325    ///   instances are used and mutated.
326    pub fn with_vi_models<T>(
327        &mut self,
328        vi_map: &mut BTreeMap<Pubkey, VirtualInventoryModel>,
329        f: impl FnOnce(&mut Self) -> T,
330    ) -> T {
331        struct ViModelsGuard<'a> {
332            model: &'a mut MarketModel,
333            vi_map: &'a mut BTreeMap<Pubkey, VirtualInventoryModel>,
334            vi_for_swaps_key: Option<Pubkey>,
335            vi_for_positions_key: Option<Pubkey>,
336            loaded_from_map_for_swaps: bool,
337            loaded_from_map_for_positions: bool,
338        }
339
340        impl Drop for ViModelsGuard<'_> {
341            fn drop(&mut self) {
342                // All cleanup work must be done in Drop to ensure panic safety.
343                // This includes:
344                // 1. Moving VI models back to vi_map
345                // 2. Setting vi_for_* to None in the model (via take())
346                if self.loaded_from_map_for_swaps {
347                    if let (Some(key), Some(vi_for_swaps_model)) =
348                        (self.vi_for_swaps_key, self.model.vi_for_swaps.take())
349                    {
350                        self.vi_map.insert(key, vi_for_swaps_model);
351                    }
352                }
353                if self.loaded_from_map_for_positions {
354                    if let (Some(key), Some(vi_for_positions_model)) = (
355                        self.vi_for_positions_key,
356                        self.model.vi_for_positions.take(),
357                    ) {
358                        self.vi_map.insert(key, vi_for_positions_model);
359                    }
360                }
361            }
362        }
363
364        // Determine the VI addresses, if any.
365        let vi_for_swaps_key = (self.market.virtual_inventory_for_swaps != Pubkey::default())
366            .then_some(self.market.virtual_inventory_for_swaps);
367        let vi_for_positions_key = (self.market.virtual_inventory_for_positions
368            != Pubkey::default())
369        .then_some(self.market.virtual_inventory_for_positions);
370
371        // Attach VI models from the map to the MarketModel *only if* the model
372        // does not already have VI models attached. This ensures that nested
373        // calls to `with_vi_models` reuse the same VI instances instead of
374        // creating independent copies.
375        let mut loaded_from_map_for_swaps = false;
376        if self.vi_for_swaps.is_none() {
377            if let Some(key) = vi_for_swaps_key {
378                if let Some(vi_model) = vi_map.remove(&key) {
379                    self.vi_for_swaps = Some(vi_model);
380                    loaded_from_map_for_swaps = true;
381                }
382            }
383        }
384
385        let mut loaded_from_map_for_positions = false;
386        if self.vi_for_positions.is_none() {
387            if let Some(key) = vi_for_positions_key {
388                if let Some(vi_model) = vi_map.remove(&key) {
389                    self.vi_for_positions = Some(vi_model);
390                    loaded_from_map_for_positions = true;
391                }
392            }
393        }
394
395        // Use a scope block to limit guard's lifetime to f's execution only.
396        // This ensures:
397        // 1. Panic safety: guard drop will restore VI models even if f panics
398        // 2. vi_map is released immediately after f completes, allowing future access
399        {
400            let guard = ViModelsGuard {
401                model: self,
402                vi_map,
403                vi_for_swaps_key,
404                vi_for_positions_key,
405                loaded_from_map_for_swaps,
406                loaded_from_map_for_positions,
407            };
408            (f)(guard.model)
409            // guard is dropped here, restoring VI models to vi_map
410        }
411    }
412
413    /// Execute a function with or without virtual inventories depending on the given map.
414    ///
415    /// - If `vi_map` is `Some`, this will attach VI models from the map via [`Self::with_vi_models`].
416    /// - If `vi_map` is `None`, this will temporarily disable VIs via [`Self::with_vis_disabled`].
417    ///
418    /// This is a small utility to unify the entry point of VI enable/disable logic so that
419    /// callers do not need to duplicate branching between `with_vi_models` and
420    /// `with_vis_disabled`.
421    pub fn with_vis_if<T>(
422        &mut self,
423        vi_map: Option<&mut BTreeMap<Pubkey, VirtualInventoryModel>>,
424        f: impl FnOnce(&mut Self) -> T,
425    ) -> T {
426        match vi_map {
427            Some(vi_map) => self.with_vi_models(vi_map, f),
428            None => self.with_vis_disabled(f),
429        }
430    }
431
432    /// Execute a function with virtual inventories disabled.
433    ///
434    /// # Panic Safety
435    /// This method uses RAII to ensure state is restored even if the closure panics.
436    pub fn with_vis_disabled<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
437        struct DisableVisGuard<'a> {
438            model: &'a mut MarketModel,
439            original_disable_vis: bool,
440        }
441
442        impl Drop for DisableVisGuard<'_> {
443            fn drop(&mut self) {
444                self.model.disable_vis = self.original_disable_vis;
445            }
446        }
447
448        let original_disable_vis = self.disable_vis;
449        self.disable_vis = true;
450
451        let guard = DisableVisGuard {
452            model: self,
453            original_disable_vis,
454        };
455
456        (f)(guard.model)
457        // guard is automatically dropped at the end of scope, restoring original state
458    }
459
460    /// Record transferred in.
461    fn record_transferred_in(
462        &mut self,
463        is_long_token: bool,
464        amount: u64,
465    ) -> gmsol_model::Result<()> {
466        let is_pure = self.market.flag(MarketFlag::Pure);
467        let other = &self.market.state.other;
468
469        if is_pure || is_long_token {
470            self.make_market_mut().state.other.long_token_balance =
471                other.long_token_balance.checked_add(amount).ok_or(
472                    gmsol_model::Error::Computation("increasing long token balance"),
473                )?;
474        } else {
475            self.make_market_mut().state.other.short_token_balance =
476                other.short_token_balance.checked_add(amount).ok_or(
477                    gmsol_model::Error::Computation("increasing short token balance"),
478                )?;
479        }
480
481        Ok(())
482    }
483
484    /// Record transferred out.
485    fn record_transferred_out(
486        &mut self,
487        is_long_token: bool,
488        amount: u64,
489    ) -> gmsol_model::Result<()> {
490        let is_pure = self.market.flag(MarketFlag::Pure);
491        let other = &self.market.state.other;
492
493        if is_pure || is_long_token {
494            self.make_market_mut().state.other.long_token_balance =
495                other.long_token_balance.checked_sub(amount).ok_or(
496                    gmsol_model::Error::Computation("decreasing long token balance"),
497                )?;
498        } else {
499            self.make_market_mut().state.other.short_token_balance =
500                other.short_token_balance.checked_sub(amount).ok_or(
501                    gmsol_model::Error::Computation("decreasing long token balance"),
502                )?;
503        }
504
505        Ok(())
506    }
507
508    fn balance_for_token(&self, is_long_token: bool) -> u64 {
509        let other = &self.state.other;
510        if is_long_token || self.market.flag(MarketFlag::Pure) {
511            other.long_token_balance
512        } else {
513            other.short_token_balance
514        }
515    }
516
517    fn make_market_mut(&mut self) -> &mut Market {
518        Arc::make_mut(&mut self.market)
519    }
520
521    /// Check if the market has a virtual inventory address for swaps.
522    fn has_vi_for_swaps_address(&self) -> bool {
523        self.market.virtual_inventory_for_swaps != Pubkey::default()
524    }
525
526    /// Check if the market has a virtual inventory address for positions.
527    fn has_vi_for_positions_address(&self) -> bool {
528        self.market.virtual_inventory_for_positions != Pubkey::default()
529    }
530
531    /// Validate virtual inventory consistency for swaps.
532    ///
533    /// # Note
534    /// We do not validate PDA address matching here because:
535    /// - The assumption that the provided VI address must match the calculated PDA is too strong
536    /// - PDA address calculation has high computational cost
537    fn validate_vi_for_swaps(&self) -> gmsol_model::Result<()> {
538        if self.disable_vis {
539            return Ok(());
540        }
541
542        let market_has_vi = self.has_vi_for_swaps_address();
543        let model_has_vi = self.vi_for_swaps.is_some();
544
545        match (market_has_vi, model_has_vi) {
546            (true, false) => Err(gmsol_model::Error::InvalidArgument(
547                "virtual inventory for swaps should be present but is missing",
548            )),
549            (false, true) => Err(gmsol_model::Error::InvalidArgument(
550                "virtual inventory for swaps should not be present but is provided",
551            )),
552            _ => Ok(()),
553        }
554    }
555
556    /// Validate virtual inventory consistency for positions.
557    ///
558    /// # Note
559    /// We do not validate PDA address matching here because:
560    /// - The assumption that the provided VI address must match the calculated PDA is too strong
561    /// - PDA address calculation has high computational cost
562    fn validate_vi_for_positions(&self) -> gmsol_model::Result<()> {
563        if self.disable_vis {
564            return Ok(());
565        }
566
567        let market_has_vi = self.has_vi_for_positions_address();
568        let model_has_vi = self.vi_for_positions.is_some();
569
570        match (market_has_vi, model_has_vi) {
571            (true, false) => Err(gmsol_model::Error::InvalidArgument(
572                "virtual inventory for positions should be present but is missing",
573            )),
574            (false, true) => Err(gmsol_model::Error::InvalidArgument(
575                "virtual inventory for positions should not be present but is provided",
576            )),
577            _ => Ok(()),
578        }
579    }
580
581    /// Returns the time in seconds since last funding fee state update.
582    pub fn passed_in_seconds_for_funding(&self) -> gmsol_model::Result<u64> {
583        AsClock::from(&self.state.clocks.funding).passed_in_seconds()
584    }
585
586    /// Convert into an empty position model.
587    ///
588    /// # Notes
589    /// - All position parameters unrelated to the model,
590    ///   such as `owner` and `bump`, use zeroed values.
591    pub fn into_empty_position(
592        self,
593        is_long: bool,
594        collateral_token: Pubkey,
595    ) -> gmsol_model::Result<PositionModel> {
596        self.into_empty_position_opts(is_long, collateral_token, Default::default())
597    }
598
599    /// Convert into an empty position model with options.
600    pub fn into_empty_position_opts(
601        self,
602        is_long: bool,
603        collateral_token: Pubkey,
604        options: PositionOptions,
605    ) -> gmsol_model::Result<PositionModel> {
606        const POSITION_SEED: &[u8] = b"position";
607
608        if !(self.meta.long_token_mint == collateral_token
609            || self.meta.short_token_mint == collateral_token)
610        {
611            return Err(gmsol_model::Error::InvalidArgument(
612                "invalid `collateral_token`",
613            ));
614        }
615
616        let owner = options.owner.unwrap_or_default();
617        let store = &self.store;
618        let market_token = &self.meta.market_token_mint;
619        let kind = if is_long {
620            PositionKind::Long
621        } else {
622            PositionKind::Short
623        } as u8;
624
625        let bump = if options.generate_bump {
626            Pubkey::find_program_address(
627                &[
628                    POSITION_SEED,
629                    store.as_ref(),
630                    owner.as_ref(),
631                    market_token.as_ref(),
632                    collateral_token.as_ref(),
633                    &[kind],
634                ],
635                &options.store_program_id,
636            )
637            .1
638        } else {
639            0
640        };
641
642        let position = Position {
643            version: 0,
644            bump,
645            store: *store,
646            kind,
647            padding_0: Zeroable::zeroed(),
648            created_at: options.created_at,
649            owner,
650            market_token: *market_token,
651            collateral_token,
652            state: Zeroable::zeroed(),
653            reserved: Zeroable::zeroed(),
654        };
655        PositionModel::new(self, Arc::new(position))
656    }
657
658    /// Set order fee discount factor.
659    pub fn set_order_fee_discount_factor(&mut self, factor: u128) {
660        self.order_fee_discount_factor = factor;
661    }
662}
663
664/// Options for creating a position model.
665#[derive(Debug, Clone)]
666pub struct PositionOptions {
667    /// The owner of the position.
668    ///
669    /// If set to `None`, the `owner` will use the default pubkey.
670    pub owner: Option<Pubkey>,
671    /// The timestamp of the position creation.
672    pub created_at: i64,
673    /// Whether to generate a bump seed.
674    ///
675    /// If set `false`, the `bump` will be fixed to `0`.
676    pub generate_bump: bool,
677    /// The store program ID used to generate the bump seed.
678    pub store_program_id: Pubkey,
679}
680
681impl Default for PositionOptions {
682    fn default() -> Self {
683        Self {
684            owner: None,
685            created_at: 0,
686            generate_bump: false,
687            store_program_id: crate::gmsol_store::ID,
688        }
689    }
690}
691
692impl gmsol_model::BaseMarket<{ constants::MARKET_DECIMALS }> for MarketModel {
693    type Num = u128;
694
695    type Signed = i128;
696
697    type Pool = Pool;
698
699    fn liquidity_pool(&self) -> gmsol_model::Result<&Self::Pool> {
700        self.try_pool(PoolKind::Primary)
701    }
702
703    fn claimable_fee_pool(&self) -> gmsol_model::Result<&Self::Pool> {
704        self.try_pool(PoolKind::ClaimableFee)
705    }
706
707    fn swap_impact_pool(&self) -> gmsol_model::Result<&Self::Pool> {
708        self.try_pool(PoolKind::SwapImpact)
709    }
710
711    fn open_interest_pool(&self, is_long: bool) -> gmsol_model::Result<&Self::Pool> {
712        self.try_pool(if is_long {
713            PoolKind::OpenInterestForLong
714        } else {
715            PoolKind::OpenInterestForShort
716        })
717    }
718
719    fn open_interest_in_tokens_pool(&self, is_long: bool) -> gmsol_model::Result<&Self::Pool> {
720        self.try_pool(if is_long {
721            PoolKind::OpenInterestInTokensForLong
722        } else {
723            PoolKind::OpenInterestInTokensForShort
724        })
725    }
726
727    fn collateral_sum_pool(&self, is_long: bool) -> gmsol_model::Result<&Self::Pool> {
728        let kind = if is_long {
729            PoolKind::CollateralSumForLong
730        } else {
731            PoolKind::CollateralSumForShort
732        };
733        self.try_pool(kind)
734    }
735
736    fn virtual_inventory_for_swaps_pool(
737        &self,
738    ) -> gmsol_model::Result<Option<impl Deref<Target = Self::Pool>>> {
739        if self.disable_vis {
740            return Ok(None);
741        }
742        self.validate_vi_for_swaps()?;
743        Ok(self.vi_for_swaps.as_ref().map(|vi| vi.pool()))
744    }
745
746    fn virtual_inventory_for_positions_pool(
747        &self,
748    ) -> gmsol_model::Result<Option<impl Deref<Target = Self::Pool>>> {
749        if self.disable_vis {
750            return Ok(None);
751        }
752        self.validate_vi_for_positions()?;
753        Ok(self.vi_for_positions.as_ref().map(|vi| vi.pool()))
754    }
755
756    fn usd_to_amount_divisor(&self) -> Self::Num {
757        constants::MARKET_USD_TO_AMOUNT_DIVISOR
758    }
759
760    fn max_pool_amount(&self, is_long_token: bool) -> gmsol_model::Result<Self::Num> {
761        if is_long_token {
762            Ok(self.config.max_pool_amount_for_long_token)
763        } else {
764            Ok(self.config.max_pool_amount_for_short_token)
765        }
766    }
767
768    fn pnl_factor_config(
769        &self,
770        kind: gmsol_model::PnlFactorKind,
771        is_long: bool,
772    ) -> gmsol_model::Result<Self::Num> {
773        use gmsol_model::PnlFactorKind;
774
775        match (kind, is_long) {
776            (PnlFactorKind::MaxAfterDeposit, true) => {
777                Ok(self.config.max_pnl_factor_for_long_deposit)
778            }
779            (PnlFactorKind::MaxAfterDeposit, false) => {
780                Ok(self.config.max_pnl_factor_for_short_deposit)
781            }
782            (PnlFactorKind::MaxAfterWithdrawal, true) => {
783                Ok(self.config.max_pnl_factor_for_long_withdrawal)
784            }
785            (PnlFactorKind::MaxAfterWithdrawal, false) => {
786                Ok(self.config.max_pnl_factor_for_short_withdrawal)
787            }
788            (PnlFactorKind::MaxForTrader, true) => Ok(self.config.max_pnl_factor_for_long_trader),
789            (PnlFactorKind::MaxForTrader, false) => Ok(self.config.max_pnl_factor_for_short_trader),
790            (PnlFactorKind::ForAdl, true) => Ok(self.config.max_pnl_factor_for_long_adl),
791            (PnlFactorKind::ForAdl, false) => Ok(self.config.max_pnl_factor_for_short_adl),
792            (PnlFactorKind::MinAfterAdl, true) => Ok(self.config.min_pnl_factor_after_long_adl),
793            (PnlFactorKind::MinAfterAdl, false) => Ok(self.config.min_pnl_factor_after_short_adl),
794            _ => Err(gmsol_model::Error::InvalidArgument("missing pnl factor")),
795        }
796    }
797
798    fn reserve_factor(&self) -> gmsol_model::Result<Self::Num> {
799        Ok(self.config.reserve_factor)
800    }
801
802    fn open_interest_reserve_factor(&self) -> gmsol_model::Result<Self::Num> {
803        Ok(self.config.open_interest_reserve_factor)
804    }
805
806    fn max_open_interest(&self, is_long: bool) -> gmsol_model::Result<Self::Num> {
807        if is_long {
808            Ok(self.config.max_open_interest_for_long)
809        } else {
810            Ok(self.config.max_open_interest_for_short)
811        }
812    }
813
814    fn ignore_open_interest_for_usage_factor(&self) -> gmsol_model::Result<bool> {
815        Ok(self
816            .config
817            .flag(MarketConfigFlag::IgnoreOpenInterestForUsageFactor))
818    }
819}
820
821impl gmsol_model::SwapMarket<{ constants::MARKET_DECIMALS }> for MarketModel {
822    fn swap_impact_params(&self) -> gmsol_model::Result<PriceImpactParams<Self::Num>> {
823        Ok(PriceImpactParams::builder()
824            .exponent(self.config.swap_impact_exponent)
825            .positive_factor(self.config.swap_impact_positive_factor)
826            .negative_factor(self.config.swap_impact_negative_factor)
827            .build())
828    }
829
830    fn swap_fee_params(&self) -> gmsol_model::Result<FeeParams<Self::Num>> {
831        let params = match self.swap_pricing {
832            SwapPricingKind::Shift => FeeParams::builder()
833                .fee_receiver_factor(self.config.swap_fee_receiver_factor)
834                .positive_impact_fee_factor(0)
835                .negative_impact_fee_factor(0)
836                .build(),
837            SwapPricingKind::Swap | SwapPricingKind::Deposit | SwapPricingKind::Withdrawal => {
838                FeeParams::builder()
839                    .fee_receiver_factor(self.config.swap_fee_receiver_factor)
840                    .positive_impact_fee_factor(self.config.swap_fee_factor_for_positive_impact)
841                    .negative_impact_fee_factor(self.config.swap_fee_factor_for_negative_impact)
842                    .build()
843            }
844        };
845
846        Ok(params)
847    }
848}
849
850impl gmsol_model::PositionImpactMarket<{ constants::MARKET_DECIMALS }> for MarketModel {
851    fn position_impact_pool(&self) -> gmsol_model::Result<&Self::Pool> {
852        self.try_pool(PoolKind::PositionImpact)
853    }
854
855    fn position_impact_params(&self) -> gmsol_model::Result<PriceImpactParams<Self::Num>> {
856        let config = &self.config;
857        Ok(PriceImpactParams::builder()
858            .exponent(config.position_impact_exponent)
859            .positive_factor(config.position_impact_positive_factor)
860            .negative_factor(config.position_impact_negative_factor)
861            .build())
862    }
863
864    fn position_impact_distribution_params(
865        &self,
866    ) -> gmsol_model::Result<PositionImpactDistributionParams<Self::Num>> {
867        let config = &self.config;
868        Ok(PositionImpactDistributionParams::builder()
869            .distribute_factor(config.position_impact_distribute_factor)
870            .min_position_impact_pool_amount(config.min_position_impact_pool_amount)
871            .build())
872    }
873
874    fn passed_in_seconds_for_position_impact_distribution(&self) -> gmsol_model::Result<u64> {
875        AsClock::from(&self.state.clocks.price_impact_distribution).passed_in_seconds()
876    }
877}
878
879impl gmsol_model::BorrowingFeeMarket<{ constants::MARKET_DECIMALS }> for MarketModel {
880    fn borrowing_factor_pool(&self) -> gmsol_model::Result<&Self::Pool> {
881        self.try_pool(PoolKind::BorrowingFactor)
882    }
883
884    fn total_borrowing_pool(&self) -> gmsol_model::Result<&Self::Pool> {
885        self.try_pool(PoolKind::TotalBorrowing)
886    }
887
888    fn borrowing_fee_params(&self) -> gmsol_model::Result<BorrowingFeeParams<Self::Num>> {
889        Ok(BorrowingFeeParams::builder()
890            .receiver_factor(self.config.borrowing_fee_receiver_factor)
891            .factor_for_long(self.config.borrowing_fee_factor_for_long)
892            .factor_for_short(self.config.borrowing_fee_factor_for_short)
893            .exponent_for_long(self.config.borrowing_fee_exponent_for_long)
894            .exponent_for_short(self.config.borrowing_fee_exponent_for_short)
895            .skip_borrowing_fee_for_smaller_side(
896                self.config
897                    .skip_borrowing_fee_for_smaller_side(self.is_closed()),
898            )
899            .build())
900    }
901
902    fn passed_in_seconds_for_borrowing(&self) -> gmsol_model::Result<u64> {
903        AsClock::from(&self.state.clocks.borrowing).passed_in_seconds()
904    }
905
906    fn borrowing_fee_kink_model_params(
907        &self,
908    ) -> gmsol_model::Result<BorrowingFeeKinkModelParams<Self::Num>> {
909        let is_closed = self.is_closed();
910        Ok(BorrowingFeeKinkModelParams::builder()
911            .long(
912                BorrowingFeeKinkModelParamsForOneSide::builder()
913                    .optimal_usage_factor(self.config.borrowing_fee_optimal_usage_factor_for_long)
914                    .base_borrowing_factor(self.config.borrowing_fee_base_factor(true, is_closed))
915                    .above_optimal_usage_borrowing_factor(
916                        self.config
917                            .borrowing_fee_above_optimal_usage_factor(true, is_closed),
918                    )
919                    .build(),
920            )
921            .short(
922                BorrowingFeeKinkModelParamsForOneSide::builder()
923                    .optimal_usage_factor(self.config.borrowing_fee_optimal_usage_factor_for_short)
924                    .base_borrowing_factor(self.config.borrowing_fee_base_factor(false, is_closed))
925                    .above_optimal_usage_borrowing_factor(
926                        self.config
927                            .borrowing_fee_above_optimal_usage_factor(false, is_closed),
928                    )
929                    .build(),
930            )
931            .build())
932    }
933}
934
935impl gmsol_model::PerpMarket<{ constants::MARKET_DECIMALS }> for MarketModel {
936    fn funding_factor_per_second(&self) -> &Self::Signed {
937        &self.state.other.funding_factor_per_second
938    }
939
940    fn funding_amount_per_size_pool(&self, is_long: bool) -> gmsol_model::Result<&Self::Pool> {
941        let kind = if is_long {
942            PoolKind::FundingAmountPerSizeForLong
943        } else {
944            PoolKind::FundingAmountPerSizeForShort
945        };
946        self.try_pool(kind)
947    }
948
949    fn claimable_funding_amount_per_size_pool(
950        &self,
951        is_long: bool,
952    ) -> gmsol_model::Result<&Self::Pool> {
953        let kind = if is_long {
954            PoolKind::ClaimableFundingAmountPerSizeForLong
955        } else {
956            PoolKind::ClaimableFundingAmountPerSizeForShort
957        };
958        self.try_pool(kind)
959    }
960
961    fn funding_amount_per_size_adjustment(&self) -> Self::Num {
962        constants::FUNDING_AMOUNT_PER_SIZE_ADJUSTMENT
963    }
964
965    fn funding_fee_params(&self) -> gmsol_model::Result<FundingFeeParams<Self::Num>> {
966        Ok(FundingFeeParams::builder()
967            .exponent(self.config.funding_fee_exponent)
968            .funding_factor(self.config.funding_fee_factor)
969            .max_factor_per_second(self.config.funding_fee_max_factor_per_second)
970            .min_factor_per_second(self.config.funding_fee_min_factor_per_second)
971            .increase_factor_per_second(self.config.funding_fee_increase_factor_per_second)
972            .decrease_factor_per_second(self.config.funding_fee_decrease_factor_per_second)
973            .threshold_for_stable_funding(self.config.funding_fee_threshold_for_stable_funding)
974            .threshold_for_decrease_funding(self.config.funding_fee_threshold_for_decrease_funding)
975            .build())
976    }
977
978    fn position_params(&self) -> gmsol_model::Result<PositionParams<Self::Num>> {
979        Ok(PositionParams::builder()
980            .min_position_size_usd(self.config.min_position_size_usd)
981            .min_collateral_value(self.config.min_collateral_value)
982            .min_collateral_factor(self.config.min_collateral_factor)
983            .max_positive_position_impact_factor(self.config.max_positive_position_impact_factor)
984            .max_negative_position_impact_factor(self.config.max_negative_position_impact_factor)
985            .max_position_impact_factor_for_liquidations(
986                self.config.max_position_impact_factor_for_liquidations,
987            )
988            .min_collateral_factor_for_liquidation(
989                self.config
990                    .min_collateral_factor_for_liquidation(self.is_closed()),
991            )
992            .build())
993    }
994
995    fn order_fee_params(&self) -> gmsol_model::Result<FeeParams<Self::Num>> {
996        Ok(FeeParams::builder()
997            .fee_receiver_factor(self.config.order_fee_receiver_factor)
998            .positive_impact_fee_factor(self.config.order_fee_factor_for_positive_impact)
999            .negative_impact_fee_factor(self.config.order_fee_factor_for_negative_impact)
1000            .build()
1001            .with_discount_factor(self.order_fee_discount_factor))
1002    }
1003
1004    fn min_collateral_factor_for_open_interest_multiplier(
1005        &self,
1006        is_long: bool,
1007    ) -> gmsol_model::Result<Self::Num> {
1008        if is_long {
1009            Ok(self
1010                .config
1011                .min_collateral_factor_for_open_interest_multiplier_for_long)
1012        } else {
1013            Ok(self
1014                .config
1015                .min_collateral_factor_for_open_interest_multiplier_for_short)
1016        }
1017    }
1018
1019    fn liquidation_fee_params(&self) -> gmsol_model::Result<LiquidationFeeParams<Self::Num>> {
1020        Ok(LiquidationFeeParams::builder()
1021            .factor(self.config.liquidation_fee_factor)
1022            .receiver_factor(self.config.liquidation_fee_receiver_factor)
1023            .build())
1024    }
1025}
1026
1027impl gmsol_model::LiquidityMarket<{ constants::MARKET_DECIMALS }> for MarketModel {
1028    fn total_supply(&self) -> Self::Num {
1029        u128::from(self.supply)
1030    }
1031
1032    fn max_pool_value_for_deposit(&self, is_long_token: bool) -> gmsol_model::Result<Self::Num> {
1033        if is_long_token {
1034            Ok(self.config.max_pool_value_for_deposit_for_long_token)
1035        } else {
1036            Ok(self.config.max_pool_value_for_deposit_for_short_token)
1037        }
1038    }
1039}
1040
1041impl gmsol_model::Bank<Pubkey> for MarketModel {
1042    type Num = u64;
1043
1044    fn record_transferred_in_by_token<Q: ?Sized + Borrow<Pubkey>>(
1045        &mut self,
1046        token: &Q,
1047        amount: &Self::Num,
1048    ) -> gmsol_model::Result<()> {
1049        let is_long_token = self.market.meta.token_side(token.borrow())?;
1050        self.record_transferred_in(is_long_token, *amount)?;
1051        Ok(())
1052    }
1053
1054    fn record_transferred_out_by_token<Q: ?Sized + Borrow<Pubkey>>(
1055        &mut self,
1056        token: &Q,
1057        amount: &Self::Num,
1058    ) -> gmsol_model::Result<()> {
1059        let is_long_token = self.market.meta.token_side(token.borrow())?;
1060        self.record_transferred_out(is_long_token, *amount)?;
1061        Ok(())
1062    }
1063
1064    fn balance<Q: Borrow<Pubkey> + ?Sized>(&self, token: &Q) -> gmsol_model::Result<Self::Num> {
1065        let side = self.market.meta.token_side(token.borrow())?;
1066        Ok(self.balance_for_token(side))
1067    }
1068}
1069
1070impl gmsol_model::BaseMarketMut<{ constants::MARKET_DECIMALS }> for MarketModel {
1071    fn liquidity_pool_mut(&mut self) -> gmsol_model::Result<&mut Self::Pool> {
1072        self.make_market_mut().try_pool_mut(PoolKind::Primary)
1073    }
1074
1075    fn claimable_fee_pool_mut(&mut self) -> gmsol_model::Result<&mut Self::Pool> {
1076        self.make_market_mut().try_pool_mut(PoolKind::ClaimableFee)
1077    }
1078
1079    fn virtual_inventory_for_swaps_pool_mut(
1080        &mut self,
1081    ) -> gmsol_model::Result<Option<impl DerefMut<Target = Self::Pool>>> {
1082        if self.disable_vis {
1083            return Ok(None);
1084        }
1085        self.validate_vi_for_swaps()?;
1086        Ok(self.vi_for_swaps.as_mut().map(|vi| vi.pool_mut()))
1087    }
1088}
1089
1090impl gmsol_model::SwapMarketMut<{ constants::MARKET_DECIMALS }> for MarketModel {
1091    fn swap_impact_pool_mut(&mut self) -> gmsol_model::Result<&mut Self::Pool> {
1092        self.make_market_mut().try_pool_mut(PoolKind::SwapImpact)
1093    }
1094}
1095
1096impl gmsol_model::PositionImpactMarketMut<{ constants::MARKET_DECIMALS }> for MarketModel {
1097    fn position_impact_pool_mut(&mut self) -> gmsol_model::Result<&mut Self::Pool> {
1098        self.make_market_mut()
1099            .try_pool_mut(PoolKind::PositionImpact)
1100    }
1101
1102    fn just_passed_in_seconds_for_position_impact_distribution(
1103        &mut self,
1104    ) -> gmsol_model::Result<u64> {
1105        AsClockMut::from(
1106            &mut self
1107                .make_market_mut()
1108                .state
1109                .clocks
1110                .price_impact_distribution,
1111        )
1112        .just_passed_in_seconds()
1113    }
1114}
1115
1116impl gmsol_model::PerpMarketMut<{ constants::MARKET_DECIMALS }> for MarketModel {
1117    fn just_passed_in_seconds_for_funding(&mut self) -> gmsol_model::Result<u64> {
1118        AsClockMut::from(&mut self.make_market_mut().state.clocks.funding).just_passed_in_seconds()
1119    }
1120
1121    fn funding_factor_per_second_mut(&mut self) -> &mut Self::Signed {
1122        &mut self.make_market_mut().state.other.funding_factor_per_second
1123    }
1124
1125    fn open_interest_pool_mut(&mut self, is_long: bool) -> gmsol_model::Result<&mut Self::Pool> {
1126        self.make_market_mut().try_pool_mut(if is_long {
1127            PoolKind::OpenInterestForLong
1128        } else {
1129            PoolKind::OpenInterestForShort
1130        })
1131    }
1132
1133    fn open_interest_in_tokens_pool_mut(
1134        &mut self,
1135        is_long: bool,
1136    ) -> gmsol_model::Result<&mut Self::Pool> {
1137        self.make_market_mut().try_pool_mut(if is_long {
1138            PoolKind::OpenInterestInTokensForLong
1139        } else {
1140            PoolKind::OpenInterestInTokensForShort
1141        })
1142    }
1143
1144    fn funding_amount_per_size_pool_mut(
1145        &mut self,
1146        is_long: bool,
1147    ) -> gmsol_model::Result<&mut Self::Pool> {
1148        self.make_market_mut().try_pool_mut(if is_long {
1149            PoolKind::FundingAmountPerSizeForLong
1150        } else {
1151            PoolKind::FundingAmountPerSizeForShort
1152        })
1153    }
1154
1155    fn claimable_funding_amount_per_size_pool_mut(
1156        &mut self,
1157        is_long: bool,
1158    ) -> gmsol_model::Result<&mut Self::Pool> {
1159        self.make_market_mut().try_pool_mut(if is_long {
1160            PoolKind::ClaimableFundingAmountPerSizeForLong
1161        } else {
1162            PoolKind::ClaimableFundingAmountPerSizeForShort
1163        })
1164    }
1165
1166    fn collateral_sum_pool_mut(&mut self, is_long: bool) -> gmsol_model::Result<&mut Self::Pool> {
1167        self.make_market_mut().try_pool_mut(if is_long {
1168            PoolKind::CollateralSumForLong
1169        } else {
1170            PoolKind::CollateralSumForShort
1171        })
1172    }
1173
1174    fn total_borrowing_pool_mut(&mut self) -> gmsol_model::Result<&mut Self::Pool> {
1175        self.make_market_mut()
1176            .try_pool_mut(PoolKind::TotalBorrowing)
1177    }
1178
1179    fn virtual_inventory_for_positions_pool_mut(
1180        &mut self,
1181    ) -> gmsol_model::Result<Option<impl DerefMut<Target = Self::Pool>>> {
1182        if self.disable_vis {
1183            return Ok(None);
1184        }
1185        self.validate_vi_for_positions()?;
1186        Ok(self.vi_for_positions.as_mut().map(|vi| vi.pool_mut()))
1187    }
1188}
1189
1190impl gmsol_model::LiquidityMarketMut<{ constants::MARKET_DECIMALS }> for MarketModel {
1191    fn mint(&mut self, amount: &Self::Num) -> gmsol_model::Result<()> {
1192        let new_mint: u64 = (*amount)
1193            .try_into()
1194            .map_err(|_| gmsol_model::Error::Overflow)?;
1195        let new_supply = self
1196            .supply
1197            .checked_add(new_mint)
1198            .ok_or(gmsol_model::Error::Overflow)?;
1199        self.supply = new_supply;
1200        Ok(())
1201    }
1202
1203    fn burn(&mut self, amount: &Self::Num) -> gmsol_model::Result<()> {
1204        let new_burn: u64 = (*amount)
1205            .try_into()
1206            .map_err(|_| gmsol_model::Error::Overflow)?;
1207        let new_supply = self
1208            .supply
1209            .checked_sub(new_burn)
1210            .ok_or(gmsol_model::Error::Overflow)?;
1211        self.supply = new_supply;
1212        Ok(())
1213    }
1214}