gmsol_programs/model/
market.rs

1use std::{
2    borrow::Borrow,
3    ops::{Deref, DerefMut},
4    sync::Arc,
5};
6
7use anchor_lang::prelude::Pubkey;
8use bitmaps::Bitmap;
9use bytemuck::Zeroable;
10use gmsol_model::{
11    params::{
12        fee::{
13            BorrowingFeeKinkModelParams, BorrowingFeeKinkModelParamsForOneSide, BorrowingFeeParams,
14            FundingFeeParams, LiquidationFeeParams,
15        },
16        position::PositionImpactDistributionParams,
17        FeeParams, PositionParams, PriceImpactParams,
18    },
19    PoolKind,
20};
21
22use crate::{
23    constants,
24    gmsol_store::{
25        accounts::{Market, Position},
26        types::{MarketConfig, MarketMeta, Pool, PoolStorage, Pools},
27    },
28    model::{position::PositionKind, PositionModel},
29};
30
31use super::clock::{AsClock, AsClockMut};
32
33impl MarketMeta {
34    /// Get token side.
35    pub fn token_side(&self, token: &Pubkey) -> gmsol_model::Result<bool> {
36        if *token == self.long_token_mint {
37            Ok(true)
38        } else if *token == self.short_token_mint {
39            Ok(false)
40        } else {
41            Err(gmsol_model::Error::InvalidArgument("not a pool token"))
42        }
43    }
44}
45
46impl Pools {
47    fn get(&self, kind: PoolKind) -> Option<&PoolStorage> {
48        let pool = match kind {
49            PoolKind::Primary => &self.primary,
50            PoolKind::SwapImpact => &self.swap_impact,
51            PoolKind::ClaimableFee => &self.claimable_fee,
52            PoolKind::OpenInterestForLong => &self.open_interest_for_long,
53            PoolKind::OpenInterestForShort => &self.open_interest_for_short,
54            PoolKind::OpenInterestInTokensForLong => &self.open_interest_in_tokens_for_long,
55            PoolKind::OpenInterestInTokensForShort => &self.open_interest_in_tokens_for_short,
56            PoolKind::PositionImpact => &self.position_impact,
57            PoolKind::BorrowingFactor => &self.borrowing_factor,
58            PoolKind::FundingAmountPerSizeForLong => &self.funding_amount_per_size_for_long,
59            PoolKind::FundingAmountPerSizeForShort => &self.funding_amount_per_size_for_short,
60            PoolKind::ClaimableFundingAmountPerSizeForLong => {
61                &self.claimable_funding_amount_per_size_for_long
62            }
63            PoolKind::ClaimableFundingAmountPerSizeForShort => {
64                &self.claimable_funding_amount_per_size_for_short
65            }
66            PoolKind::CollateralSumForLong => &self.collateral_sum_for_long,
67            PoolKind::CollateralSumForShort => &self.collateral_sum_for_short,
68            PoolKind::TotalBorrowing => &self.total_borrowing,
69            _ => return None,
70        };
71        Some(pool)
72    }
73
74    fn get_mut(&mut self, kind: PoolKind) -> Option<&mut PoolStorage> {
75        let pool = match kind {
76            PoolKind::Primary => &mut self.primary,
77            PoolKind::SwapImpact => &mut self.swap_impact,
78            PoolKind::ClaimableFee => &mut self.claimable_fee,
79            PoolKind::OpenInterestForLong => &mut self.open_interest_for_long,
80            PoolKind::OpenInterestForShort => &mut self.open_interest_for_short,
81            PoolKind::OpenInterestInTokensForLong => &mut self.open_interest_in_tokens_for_long,
82            PoolKind::OpenInterestInTokensForShort => &mut self.open_interest_in_tokens_for_short,
83            PoolKind::PositionImpact => &mut self.position_impact,
84            PoolKind::BorrowingFactor => &mut self.borrowing_factor,
85            PoolKind::FundingAmountPerSizeForLong => &mut self.funding_amount_per_size_for_long,
86            PoolKind::FundingAmountPerSizeForShort => &mut self.funding_amount_per_size_for_short,
87            PoolKind::ClaimableFundingAmountPerSizeForLong => {
88                &mut self.claimable_funding_amount_per_size_for_long
89            }
90            PoolKind::ClaimableFundingAmountPerSizeForShort => {
91                &mut self.claimable_funding_amount_per_size_for_short
92            }
93            PoolKind::CollateralSumForLong => &mut self.collateral_sum_for_long,
94            PoolKind::CollateralSumForShort => &mut self.collateral_sum_for_short,
95            PoolKind::TotalBorrowing => &mut self.total_borrowing,
96            _ => return None,
97        };
98        Some(pool)
99    }
100}
101
102#[repr(u8)]
103enum MarketConfigFlag {
104    SkipBorrowingFeeForSmallerSide,
105    IgnoreOpenInterestForUsageFactor,
106    EnableMarketClosedParams,
107    MarketClosedSkipBorrowingFeeForSmallerSide,
108}
109
110type MarketConfigFlags = Bitmap<{ constants::NUM_MARKET_CONFIG_FLAGS }>;
111
112impl MarketConfig {
113    fn flag(&self, flag: MarketConfigFlag) -> bool {
114        MarketConfigFlags::from_value(self.flag.value).get(flag as usize)
115    }
116
117    fn use_market_closed_params(&self, is_market_closed: bool) -> bool {
118        is_market_closed && self.flag(MarketConfigFlag::EnableMarketClosedParams)
119    }
120
121    fn min_collateral_factor_for_liquidation(&self, is_market_closed: bool) -> Option<u128> {
122        let factor = if self.use_market_closed_params(is_market_closed) {
123            self.market_closed_min_collateral_factor_for_liquidation
124        } else {
125            self.min_collateral_factor_for_liquidation
126        };
127        if factor == 0 {
128            None
129        } else {
130            Some(factor)
131        }
132    }
133
134    fn skip_borrowing_fee_for_smaller_side(&self, is_market_closed: bool) -> bool {
135        if self.use_market_closed_params(is_market_closed) {
136            self.flag(MarketConfigFlag::MarketClosedSkipBorrowingFeeForSmallerSide)
137        } else {
138            self.flag(MarketConfigFlag::SkipBorrowingFeeForSmallerSide)
139        }
140    }
141
142    fn borrowing_fee_base_factor(&self, for_long: bool, is_market_closed: bool) -> u128 {
143        match (self.use_market_closed_params(is_market_closed), for_long) {
144            (true, _) => self.market_closed_borrowing_fee_base_factor,
145            (false, true) => self.borrowing_fee_base_factor_for_long,
146            (false, false) => self.borrowing_fee_base_factor_for_short,
147        }
148    }
149
150    /// Returns above optimal usage borrowing fee factor.
151    fn borrowing_fee_above_optimal_usage_factor(
152        &self,
153        for_long: bool,
154        is_market_closed: bool,
155    ) -> u128 {
156        match (self.use_market_closed_params(is_market_closed), for_long) {
157            (true, _) => self.market_closed_borrowing_fee_above_optimal_usage_factor,
158            (false, true) => self.borrowing_fee_above_optimal_usage_factor_for_long,
159            (false, false) => self.borrowing_fee_above_optimal_usage_factor_for_short,
160        }
161    }
162}
163
164#[repr(u8)]
165#[allow(dead_code)]
166enum MarketFlag {
167    Enabled,
168    Pure,
169    AutoDeleveragingEnabledForLong,
170    AutoDeleveragingEnabledForShort,
171    GTEnabled,
172    Closed,
173}
174
175type MarketFlags = Bitmap<{ constants::NUM_MARKET_FLAGS }>;
176
177impl Market {
178    fn try_pool(&self, kind: PoolKind) -> gmsol_model::Result<&Pool> {
179        Ok(&self
180            .state
181            .pools
182            .get(kind)
183            .ok_or(gmsol_model::Error::MissingPoolKind(kind))?
184            .pool)
185    }
186
187    fn try_pool_mut(&mut self, kind: PoolKind) -> gmsol_model::Result<&mut Pool> {
188        Ok(&mut self
189            .state
190            .pools
191            .get_mut(kind)
192            .ok_or(gmsol_model::Error::MissingPoolKind(kind))?
193            .pool)
194    }
195
196    fn flag(&self, flag: MarketFlag) -> bool {
197        MarketFlags::from_value(self.flags.value).get(flag as usize)
198    }
199
200    fn is_closed(&self) -> bool {
201        self.flag(MarketFlag::Closed)
202    }
203}
204
205/// Market Model.
206#[derive(Debug, Clone)]
207pub struct MarketModel {
208    market: Arc<Market>,
209    supply: u64,
210}
211
212impl Deref for MarketModel {
213    type Target = Market;
214
215    fn deref(&self) -> &Self::Target {
216        &self.market
217    }
218}
219
220impl MarketModel {
221    /// Create from parts.
222    pub fn from_parts(market: Arc<Market>, supply: u64) -> Self {
223        Self { market, supply }
224    }
225
226    /// Get whether it is a pure market.
227    pub fn is_pure(&self) -> bool {
228        self.market.flag(MarketFlag::Pure)
229    }
230
231    /// Record transferred in.
232    fn record_transferred_in(
233        &mut self,
234        is_long_token: bool,
235        amount: u64,
236    ) -> gmsol_model::Result<()> {
237        let is_pure = self.market.flag(MarketFlag::Pure);
238        let other = &self.market.state.other;
239
240        if is_pure || is_long_token {
241            self.make_market_mut().state.other.long_token_balance =
242                other.long_token_balance.checked_add(amount).ok_or(
243                    gmsol_model::Error::Computation("increasing long token balance"),
244                )?;
245        } else {
246            self.make_market_mut().state.other.short_token_balance =
247                other.short_token_balance.checked_add(amount).ok_or(
248                    gmsol_model::Error::Computation("increasing short token balance"),
249                )?;
250        }
251
252        Ok(())
253    }
254
255    /// Record transferred out.
256    fn record_transferred_out(
257        &mut self,
258        is_long_token: bool,
259        amount: u64,
260    ) -> gmsol_model::Result<()> {
261        let is_pure = self.market.flag(MarketFlag::Pure);
262        let other = &self.market.state.other;
263
264        if is_pure || is_long_token {
265            self.make_market_mut().state.other.long_token_balance =
266                other.long_token_balance.checked_sub(amount).ok_or(
267                    gmsol_model::Error::Computation("decreasing long token balance"),
268                )?;
269        } else {
270            self.make_market_mut().state.other.short_token_balance =
271                other.short_token_balance.checked_sub(amount).ok_or(
272                    gmsol_model::Error::Computation("decreasing long token balance"),
273                )?;
274        }
275
276        Ok(())
277    }
278
279    fn balance_for_token(&self, is_long_token: bool) -> u64 {
280        let other = &self.state.other;
281        if is_long_token || self.market.flag(MarketFlag::Pure) {
282            other.long_token_balance
283        } else {
284            other.short_token_balance
285        }
286    }
287
288    fn make_market_mut(&mut self) -> &mut Market {
289        Arc::make_mut(&mut self.market)
290    }
291
292    /// Returns the time in seconds since last funding fee state update.
293    pub fn passed_in_seconds_for_funding(&self) -> gmsol_model::Result<u64> {
294        AsClock::from(&self.state.clocks.funding).passed_in_seconds()
295    }
296
297    /// Convert into an empty position model.
298    ///
299    /// # Notes
300    /// - All position parameters unrelated to the model,
301    ///   such as `owner` and `bump`, use zeroed values.
302    pub fn into_empty_position(
303        self,
304        is_long: bool,
305        collateral_token: Pubkey,
306    ) -> gmsol_model::Result<PositionModel> {
307        self.into_empty_position_opts(is_long, collateral_token, Default::default())
308    }
309
310    /// Convert into an empty position model with options.
311    pub fn into_empty_position_opts(
312        self,
313        is_long: bool,
314        collateral_token: Pubkey,
315        options: PositionOptions,
316    ) -> gmsol_model::Result<PositionModel> {
317        const POSITION_SEED: &[u8] = b"position";
318
319        if !(self.meta.long_token_mint == collateral_token
320            || self.meta.short_token_mint == collateral_token)
321        {
322            return Err(gmsol_model::Error::InvalidArgument(
323                "invalid `collateral_token`",
324            ));
325        }
326
327        let owner = options.owner.unwrap_or_default();
328        let store = &self.store;
329        let market_token = &self.meta.market_token_mint;
330        let kind = if is_long {
331            PositionKind::Long
332        } else {
333            PositionKind::Short
334        } as u8;
335
336        let bump = if options.generate_bump {
337            Pubkey::find_program_address(
338                &[
339                    POSITION_SEED,
340                    store.as_ref(),
341                    owner.as_ref(),
342                    market_token.as_ref(),
343                    collateral_token.as_ref(),
344                    &[kind],
345                ],
346                &options.store_program_id,
347            )
348            .1
349        } else {
350            0
351        };
352
353        let position = Position {
354            version: 0,
355            bump,
356            store: *store,
357            kind,
358            padding_0: Zeroable::zeroed(),
359            created_at: options.created_at,
360            owner,
361            market_token: *market_token,
362            collateral_token,
363            state: Zeroable::zeroed(),
364            reserved: Zeroable::zeroed(),
365        };
366        PositionModel::new(self, Arc::new(position))
367    }
368}
369
370/// Options for creating a position model.
371#[derive(Debug, Clone)]
372pub struct PositionOptions {
373    /// The owner of the position.
374    ///
375    /// If set to `None`, the `owner` will use the default pubkey.
376    pub owner: Option<Pubkey>,
377    /// The timestamp of the position creation.
378    pub created_at: i64,
379    /// Whether to generate a bump seed.
380    ///
381    /// If set `false`, the `bump` will be fixed to `0`.
382    pub generate_bump: bool,
383    /// The store program ID used to generate the bump seed.
384    pub store_program_id: Pubkey,
385}
386
387impl Default for PositionOptions {
388    fn default() -> Self {
389        Self {
390            owner: None,
391            created_at: 0,
392            generate_bump: false,
393            store_program_id: crate::gmsol_store::ID,
394        }
395    }
396}
397
398impl gmsol_model::BaseMarket<{ constants::MARKET_DECIMALS }> for MarketModel {
399    type Num = u128;
400
401    type Signed = i128;
402
403    type Pool = Pool;
404
405    fn liquidity_pool(&self) -> gmsol_model::Result<&Self::Pool> {
406        self.try_pool(PoolKind::Primary)
407    }
408
409    fn claimable_fee_pool(&self) -> gmsol_model::Result<&Self::Pool> {
410        self.try_pool(PoolKind::ClaimableFee)
411    }
412
413    fn swap_impact_pool(&self) -> gmsol_model::Result<&Self::Pool> {
414        self.try_pool(PoolKind::SwapImpact)
415    }
416
417    fn open_interest_pool(&self, is_long: bool) -> gmsol_model::Result<&Self::Pool> {
418        self.try_pool(if is_long {
419            PoolKind::OpenInterestForLong
420        } else {
421            PoolKind::OpenInterestForShort
422        })
423    }
424
425    fn open_interest_in_tokens_pool(&self, is_long: bool) -> gmsol_model::Result<&Self::Pool> {
426        self.try_pool(if is_long {
427            PoolKind::OpenInterestInTokensForLong
428        } else {
429            PoolKind::OpenInterestInTokensForShort
430        })
431    }
432
433    fn collateral_sum_pool(&self, is_long: bool) -> gmsol_model::Result<&Self::Pool> {
434        let kind = if is_long {
435            PoolKind::CollateralSumForLong
436        } else {
437            PoolKind::CollateralSumForShort
438        };
439        self.try_pool(kind)
440    }
441
442    fn virtual_inventory_for_swaps_pool(
443        &self,
444    ) -> gmsol_model::Result<Option<impl Deref<Target = Self::Pool>>> {
445        Ok(None::<&Self::Pool>)
446    }
447
448    fn virtual_inventory_for_positions_pool(
449        &self,
450    ) -> gmsol_model::Result<Option<impl Deref<Target = Self::Pool>>> {
451        Ok(None::<&Self::Pool>)
452    }
453
454    fn usd_to_amount_divisor(&self) -> Self::Num {
455        constants::MARKET_USD_TO_AMOUNT_DIVISOR
456    }
457
458    fn max_pool_amount(&self, is_long_token: bool) -> gmsol_model::Result<Self::Num> {
459        if is_long_token {
460            Ok(self.config.max_pool_amount_for_long_token)
461        } else {
462            Ok(self.config.max_pool_amount_for_short_token)
463        }
464    }
465
466    fn pnl_factor_config(
467        &self,
468        kind: gmsol_model::PnlFactorKind,
469        is_long: bool,
470    ) -> gmsol_model::Result<Self::Num> {
471        use gmsol_model::PnlFactorKind;
472
473        match (kind, is_long) {
474            (PnlFactorKind::MaxAfterDeposit, true) => {
475                Ok(self.config.max_pnl_factor_for_long_deposit)
476            }
477            (PnlFactorKind::MaxAfterDeposit, false) => {
478                Ok(self.config.max_pnl_factor_for_short_deposit)
479            }
480            (PnlFactorKind::MaxAfterWithdrawal, true) => {
481                Ok(self.config.max_pnl_factor_for_long_withdrawal)
482            }
483            (PnlFactorKind::MaxAfterWithdrawal, false) => {
484                Ok(self.config.max_pnl_factor_for_short_withdrawal)
485            }
486            (PnlFactorKind::MaxForTrader, true) => Ok(self.config.max_pnl_factor_for_long_trader),
487            (PnlFactorKind::MaxForTrader, false) => Ok(self.config.max_pnl_factor_for_short_trader),
488            (PnlFactorKind::ForAdl, true) => Ok(self.config.max_pnl_factor_for_long_adl),
489            (PnlFactorKind::ForAdl, false) => Ok(self.config.max_pnl_factor_for_short_adl),
490            (PnlFactorKind::MinAfterAdl, true) => Ok(self.config.min_pnl_factor_after_long_adl),
491            (PnlFactorKind::MinAfterAdl, false) => Ok(self.config.min_pnl_factor_after_short_adl),
492            _ => Err(gmsol_model::Error::InvalidArgument("missing pnl factor")),
493        }
494    }
495
496    fn reserve_factor(&self) -> gmsol_model::Result<Self::Num> {
497        Ok(self.config.reserve_factor)
498    }
499
500    fn open_interest_reserve_factor(&self) -> gmsol_model::Result<Self::Num> {
501        Ok(self.config.open_interest_reserve_factor)
502    }
503
504    fn max_open_interest(&self, is_long: bool) -> gmsol_model::Result<Self::Num> {
505        if is_long {
506            Ok(self.config.max_open_interest_for_long)
507        } else {
508            Ok(self.config.max_open_interest_for_short)
509        }
510    }
511
512    fn ignore_open_interest_for_usage_factor(&self) -> gmsol_model::Result<bool> {
513        Ok(self
514            .config
515            .flag(MarketConfigFlag::IgnoreOpenInterestForUsageFactor))
516    }
517}
518
519impl gmsol_model::SwapMarket<{ constants::MARKET_DECIMALS }> for MarketModel {
520    fn swap_impact_params(&self) -> gmsol_model::Result<PriceImpactParams<Self::Num>> {
521        Ok(PriceImpactParams::builder()
522            .exponent(self.config.swap_impact_exponent)
523            .positive_factor(self.config.swap_impact_positive_factor)
524            .negative_factor(self.config.swap_impact_negative_factor)
525            .build())
526    }
527
528    fn swap_fee_params(&self) -> gmsol_model::Result<FeeParams<Self::Num>> {
529        Ok(FeeParams::builder()
530            .fee_receiver_factor(self.config.swap_fee_receiver_factor)
531            .positive_impact_fee_factor(self.config.swap_fee_factor_for_positive_impact)
532            .negative_impact_fee_factor(self.config.swap_fee_factor_for_negative_impact)
533            .build())
534    }
535}
536
537impl gmsol_model::PositionImpactMarket<{ constants::MARKET_DECIMALS }> for MarketModel {
538    fn position_impact_pool(&self) -> gmsol_model::Result<&Self::Pool> {
539        self.try_pool(PoolKind::PositionImpact)
540    }
541
542    fn position_impact_params(&self) -> gmsol_model::Result<PriceImpactParams<Self::Num>> {
543        let config = &self.config;
544        Ok(PriceImpactParams::builder()
545            .exponent(config.position_impact_exponent)
546            .positive_factor(config.position_impact_positive_factor)
547            .negative_factor(config.position_impact_negative_factor)
548            .build())
549    }
550
551    fn position_impact_distribution_params(
552        &self,
553    ) -> gmsol_model::Result<PositionImpactDistributionParams<Self::Num>> {
554        let config = &self.config;
555        Ok(PositionImpactDistributionParams::builder()
556            .distribute_factor(config.position_impact_distribute_factor)
557            .min_position_impact_pool_amount(config.min_position_impact_pool_amount)
558            .build())
559    }
560
561    fn passed_in_seconds_for_position_impact_distribution(&self) -> gmsol_model::Result<u64> {
562        AsClock::from(&self.state.clocks.price_impact_distribution).passed_in_seconds()
563    }
564}
565
566impl gmsol_model::BorrowingFeeMarket<{ constants::MARKET_DECIMALS }> for MarketModel {
567    fn borrowing_factor_pool(&self) -> gmsol_model::Result<&Self::Pool> {
568        self.try_pool(PoolKind::BorrowingFactor)
569    }
570
571    fn total_borrowing_pool(&self) -> gmsol_model::Result<&Self::Pool> {
572        self.try_pool(PoolKind::TotalBorrowing)
573    }
574
575    fn borrowing_fee_params(&self) -> gmsol_model::Result<BorrowingFeeParams<Self::Num>> {
576        Ok(BorrowingFeeParams::builder()
577            .receiver_factor(self.config.borrowing_fee_receiver_factor)
578            .factor_for_long(self.config.borrowing_fee_factor_for_long)
579            .factor_for_short(self.config.borrowing_fee_factor_for_short)
580            .exponent_for_long(self.config.borrowing_fee_exponent_for_long)
581            .exponent_for_short(self.config.borrowing_fee_exponent_for_short)
582            .skip_borrowing_fee_for_smaller_side(
583                self.config
584                    .skip_borrowing_fee_for_smaller_side(self.is_closed()),
585            )
586            .build())
587    }
588
589    fn passed_in_seconds_for_borrowing(&self) -> gmsol_model::Result<u64> {
590        AsClock::from(&self.state.clocks.borrowing).passed_in_seconds()
591    }
592
593    fn borrowing_fee_kink_model_params(
594        &self,
595    ) -> gmsol_model::Result<BorrowingFeeKinkModelParams<Self::Num>> {
596        let is_closed = self.is_closed();
597        Ok(BorrowingFeeKinkModelParams::builder()
598            .long(
599                BorrowingFeeKinkModelParamsForOneSide::builder()
600                    .optimal_usage_factor(self.config.borrowing_fee_optimal_usage_factor_for_long)
601                    .base_borrowing_factor(self.config.borrowing_fee_base_factor(true, is_closed))
602                    .above_optimal_usage_borrowing_factor(
603                        self.config
604                            .borrowing_fee_above_optimal_usage_factor(true, is_closed),
605                    )
606                    .build(),
607            )
608            .short(
609                BorrowingFeeKinkModelParamsForOneSide::builder()
610                    .optimal_usage_factor(self.config.borrowing_fee_optimal_usage_factor_for_short)
611                    .base_borrowing_factor(self.config.borrowing_fee_base_factor(false, is_closed))
612                    .above_optimal_usage_borrowing_factor(
613                        self.config
614                            .borrowing_fee_above_optimal_usage_factor(false, is_closed),
615                    )
616                    .build(),
617            )
618            .build())
619    }
620}
621
622impl gmsol_model::PerpMarket<{ constants::MARKET_DECIMALS }> for MarketModel {
623    fn funding_factor_per_second(&self) -> &Self::Signed {
624        &self.state.other.funding_factor_per_second
625    }
626
627    fn funding_amount_per_size_pool(&self, is_long: bool) -> gmsol_model::Result<&Self::Pool> {
628        let kind = if is_long {
629            PoolKind::FundingAmountPerSizeForLong
630        } else {
631            PoolKind::FundingAmountPerSizeForShort
632        };
633        self.try_pool(kind)
634    }
635
636    fn claimable_funding_amount_per_size_pool(
637        &self,
638        is_long: bool,
639    ) -> gmsol_model::Result<&Self::Pool> {
640        let kind = if is_long {
641            PoolKind::ClaimableFundingAmountPerSizeForLong
642        } else {
643            PoolKind::ClaimableFundingAmountPerSizeForShort
644        };
645        self.try_pool(kind)
646    }
647
648    fn funding_amount_per_size_adjustment(&self) -> Self::Num {
649        constants::FUNDING_AMOUNT_PER_SIZE_ADJUSTMENT
650    }
651
652    fn funding_fee_params(&self) -> gmsol_model::Result<FundingFeeParams<Self::Num>> {
653        Ok(FundingFeeParams::builder()
654            .exponent(self.config.funding_fee_exponent)
655            .funding_factor(self.config.funding_fee_factor)
656            .max_factor_per_second(self.config.funding_fee_max_factor_per_second)
657            .min_factor_per_second(self.config.funding_fee_min_factor_per_second)
658            .increase_factor_per_second(self.config.funding_fee_increase_factor_per_second)
659            .decrease_factor_per_second(self.config.funding_fee_decrease_factor_per_second)
660            .threshold_for_stable_funding(self.config.funding_fee_threshold_for_stable_funding)
661            .threshold_for_decrease_funding(self.config.funding_fee_threshold_for_decrease_funding)
662            .build())
663    }
664
665    fn position_params(&self) -> gmsol_model::Result<PositionParams<Self::Num>> {
666        Ok(PositionParams::builder()
667            .min_position_size_usd(self.config.min_position_size_usd)
668            .min_collateral_value(self.config.min_collateral_value)
669            .min_collateral_factor(self.config.min_collateral_factor)
670            .max_positive_position_impact_factor(self.config.max_positive_position_impact_factor)
671            .max_negative_position_impact_factor(self.config.max_negative_position_impact_factor)
672            .max_position_impact_factor_for_liquidations(
673                self.config.max_position_impact_factor_for_liquidations,
674            )
675            .min_collateral_factor_for_liquidation(
676                self.config
677                    .min_collateral_factor_for_liquidation(self.is_closed()),
678            )
679            .build())
680    }
681
682    fn order_fee_params(&self) -> gmsol_model::Result<FeeParams<Self::Num>> {
683        Ok(FeeParams::builder()
684            .fee_receiver_factor(self.config.order_fee_receiver_factor)
685            .positive_impact_fee_factor(self.config.order_fee_factor_for_positive_impact)
686            .negative_impact_fee_factor(self.config.order_fee_factor_for_negative_impact)
687            .build())
688    }
689
690    fn min_collateral_factor_for_open_interest_multiplier(
691        &self,
692        is_long: bool,
693    ) -> gmsol_model::Result<Self::Num> {
694        if is_long {
695            Ok(self
696                .config
697                .min_collateral_factor_for_open_interest_multiplier_for_long)
698        } else {
699            Ok(self
700                .config
701                .min_collateral_factor_for_open_interest_multiplier_for_short)
702        }
703    }
704
705    fn liquidation_fee_params(&self) -> gmsol_model::Result<LiquidationFeeParams<Self::Num>> {
706        Ok(LiquidationFeeParams::builder()
707            .factor(self.config.liquidation_fee_factor)
708            .receiver_factor(self.config.liquidation_fee_receiver_factor)
709            .build())
710    }
711}
712
713impl gmsol_model::LiquidityMarket<{ constants::MARKET_DECIMALS }> for MarketModel {
714    fn total_supply(&self) -> Self::Num {
715        u128::from(self.supply)
716    }
717
718    fn max_pool_value_for_deposit(&self, is_long_token: bool) -> gmsol_model::Result<Self::Num> {
719        if is_long_token {
720            Ok(self.config.max_pool_value_for_deposit_for_long_token)
721        } else {
722            Ok(self.config.max_pool_value_for_deposit_for_short_token)
723        }
724    }
725}
726
727impl gmsol_model::Bank<Pubkey> for MarketModel {
728    type Num = u64;
729
730    fn record_transferred_in_by_token<Q: ?Sized + Borrow<Pubkey>>(
731        &mut self,
732        token: &Q,
733        amount: &Self::Num,
734    ) -> gmsol_model::Result<()> {
735        let is_long_token = self.market.meta.token_side(token.borrow())?;
736        self.record_transferred_in(is_long_token, *amount)?;
737        Ok(())
738    }
739
740    fn record_transferred_out_by_token<Q: ?Sized + Borrow<Pubkey>>(
741        &mut self,
742        token: &Q,
743        amount: &Self::Num,
744    ) -> gmsol_model::Result<()> {
745        let is_long_token = self.market.meta.token_side(token.borrow())?;
746        self.record_transferred_out(is_long_token, *amount)?;
747        Ok(())
748    }
749
750    fn balance<Q: Borrow<Pubkey> + ?Sized>(&self, token: &Q) -> gmsol_model::Result<Self::Num> {
751        let side = self.market.meta.token_side(token.borrow())?;
752        Ok(self.balance_for_token(side))
753    }
754}
755
756impl gmsol_model::BaseMarketMut<{ constants::MARKET_DECIMALS }> for MarketModel {
757    fn liquidity_pool_mut(&mut self) -> gmsol_model::Result<&mut Self::Pool> {
758        self.make_market_mut().try_pool_mut(PoolKind::Primary)
759    }
760
761    fn claimable_fee_pool_mut(&mut self) -> gmsol_model::Result<&mut Self::Pool> {
762        self.make_market_mut().try_pool_mut(PoolKind::ClaimableFee)
763    }
764
765    fn virtual_inventory_for_swaps_pool_mut(
766        &mut self,
767    ) -> gmsol_model::Result<Option<impl DerefMut<Target = Self::Pool>>> {
768        Ok(None::<&mut Self::Pool>)
769    }
770}
771
772impl gmsol_model::SwapMarketMut<{ constants::MARKET_DECIMALS }> for MarketModel {
773    fn swap_impact_pool_mut(&mut self) -> gmsol_model::Result<&mut Self::Pool> {
774        self.make_market_mut().try_pool_mut(PoolKind::SwapImpact)
775    }
776}
777
778impl gmsol_model::PositionImpactMarketMut<{ constants::MARKET_DECIMALS }> for MarketModel {
779    fn position_impact_pool_mut(&mut self) -> gmsol_model::Result<&mut Self::Pool> {
780        self.make_market_mut()
781            .try_pool_mut(PoolKind::PositionImpact)
782    }
783
784    fn just_passed_in_seconds_for_position_impact_distribution(
785        &mut self,
786    ) -> gmsol_model::Result<u64> {
787        AsClockMut::from(
788            &mut self
789                .make_market_mut()
790                .state
791                .clocks
792                .price_impact_distribution,
793        )
794        .just_passed_in_seconds()
795    }
796}
797
798impl gmsol_model::PerpMarketMut<{ constants::MARKET_DECIMALS }> for MarketModel {
799    fn just_passed_in_seconds_for_funding(&mut self) -> gmsol_model::Result<u64> {
800        AsClockMut::from(&mut self.make_market_mut().state.clocks.funding).just_passed_in_seconds()
801    }
802
803    fn funding_factor_per_second_mut(&mut self) -> &mut Self::Signed {
804        &mut self.make_market_mut().state.other.funding_factor_per_second
805    }
806
807    fn open_interest_pool_mut(&mut self, is_long: bool) -> gmsol_model::Result<&mut Self::Pool> {
808        self.make_market_mut().try_pool_mut(if is_long {
809            PoolKind::OpenInterestForLong
810        } else {
811            PoolKind::OpenInterestForShort
812        })
813    }
814
815    fn open_interest_in_tokens_pool_mut(
816        &mut self,
817        is_long: bool,
818    ) -> gmsol_model::Result<&mut Self::Pool> {
819        self.make_market_mut().try_pool_mut(if is_long {
820            PoolKind::OpenInterestInTokensForLong
821        } else {
822            PoolKind::OpenInterestInTokensForShort
823        })
824    }
825
826    fn funding_amount_per_size_pool_mut(
827        &mut self,
828        is_long: bool,
829    ) -> gmsol_model::Result<&mut Self::Pool> {
830        self.make_market_mut().try_pool_mut(if is_long {
831            PoolKind::FundingAmountPerSizeForLong
832        } else {
833            PoolKind::FundingAmountPerSizeForShort
834        })
835    }
836
837    fn claimable_funding_amount_per_size_pool_mut(
838        &mut self,
839        is_long: bool,
840    ) -> gmsol_model::Result<&mut Self::Pool> {
841        self.make_market_mut().try_pool_mut(if is_long {
842            PoolKind::ClaimableFundingAmountPerSizeForLong
843        } else {
844            PoolKind::ClaimableFundingAmountPerSizeForShort
845        })
846    }
847
848    fn collateral_sum_pool_mut(&mut self, is_long: bool) -> gmsol_model::Result<&mut Self::Pool> {
849        self.make_market_mut().try_pool_mut(if is_long {
850            PoolKind::CollateralSumForLong
851        } else {
852            PoolKind::CollateralSumForShort
853        })
854    }
855
856    fn total_borrowing_pool_mut(&mut self) -> gmsol_model::Result<&mut Self::Pool> {
857        self.make_market_mut()
858            .try_pool_mut(PoolKind::TotalBorrowing)
859    }
860
861    fn virtual_inventory_for_positions_pool_mut(
862        &mut self,
863    ) -> gmsol_model::Result<Option<impl DerefMut<Target = Self::Pool>>> {
864        Ok(None::<&mut Self::Pool>)
865    }
866}
867
868impl gmsol_model::LiquidityMarketMut<{ constants::MARKET_DECIMALS }> for MarketModel {
869    fn mint(&mut self, amount: &Self::Num) -> gmsol_model::Result<()> {
870        let new_mint: u64 = (*amount)
871            .try_into()
872            .map_err(|_| gmsol_model::Error::Overflow)?;
873        let new_supply = self
874            .supply
875            .checked_add(new_mint)
876            .ok_or(gmsol_model::Error::Overflow)?;
877        self.supply = new_supply;
878        Ok(())
879    }
880
881    fn burn(&mut self, amount: &Self::Num) -> gmsol_model::Result<()> {
882        let new_burn: u64 = (*amount)
883            .try_into()
884            .map_err(|_| gmsol_model::Error::Overflow)?;
885        let new_supply = self
886            .supply
887            .checked_sub(new_burn)
888            .ok_or(gmsol_model::Error::Overflow)?;
889        self.supply = new_supply;
890        Ok(())
891    }
892}