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 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 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#[derive(Debug, Clone, Copy, Default)]
208pub enum SwapPricingKind {
209 #[default]
211 Swap,
212 Deposit,
214 Withdrawal,
216 Shift,
218}
219
220#[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 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 pub fn is_pure(&self) -> bool {
256 self.market.flag(MarketFlag::Pure)
257 }
258
259 pub fn swap_pricing(&self) -> &SwapPricingKind {
261 &self.swap_pricing
262 }
263
264 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 }
295
296 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 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 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 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 {
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 }
411 }
412
413 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 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 }
459
460 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 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 fn has_vi_for_swaps_address(&self) -> bool {
523 self.market.virtual_inventory_for_swaps != Pubkey::default()
524 }
525
526 fn has_vi_for_positions_address(&self) -> bool {
528 self.market.virtual_inventory_for_positions != Pubkey::default()
529 }
530
531 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 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 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 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 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 pub fn set_order_fee_discount_factor(&mut self, factor: u128) {
660 self.order_fee_discount_factor = factor;
661 }
662}
663
664#[derive(Debug, Clone)]
666pub struct PositionOptions {
667 pub owner: Option<Pubkey>,
671 pub created_at: i64,
673 pub generate_bump: bool,
677 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}