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 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 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#[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 pub fn from_parts(market: Arc<Market>, supply: u64) -> Self {
223 Self { market, supply }
224 }
225
226 pub fn is_pure(&self) -> bool {
228 self.market.flag(MarketFlag::Pure)
229 }
230
231 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 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 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 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 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#[derive(Debug, Clone)]
372pub struct PositionOptions {
373 pub owner: Option<Pubkey>,
377 pub created_at: i64,
379 pub generate_bump: bool,
383 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}