1use std::num::NonZeroU64;
2
3use bytemuck::Zeroable;
4
5use crate::gmsol_store::{
6 accounts::{Glv, GtExchange, Market, Position, ReferralCodeV2, Store, VirtualInventory},
7 types::{ActionHeader, EventPositionState, Pool, PositionState, UpdateOrderParams},
8};
9
10pub type ReferralCodeBytes = [u8; 8];
12
13impl Default for Market {
14 fn default() -> Self {
15 Zeroable::zeroed()
16 }
17}
18
19impl Default for VirtualInventory {
20 fn default() -> Self {
21 Zeroable::zeroed()
22 }
23}
24
25impl Pool {
26 pub fn is_pure(&self) -> bool {
28 !matches!(self.is_pure, 0)
29 }
30}
31
32impl Default for Glv {
33 fn default() -> Self {
34 Zeroable::zeroed()
35 }
36}
37
38impl Default for Position {
39 fn default() -> Self {
40 Zeroable::zeroed()
41 }
42}
43
44impl AsRef<Position> for Position {
45 fn as_ref(&self) -> &Position {
46 self
47 }
48}
49
50impl Default for ActionHeader {
51 fn default() -> Self {
52 Zeroable::zeroed()
53 }
54}
55
56impl Default for GtExchange {
57 fn default() -> Self {
58 Zeroable::zeroed()
59 }
60}
61
62impl Store {
63 pub fn claimable_time_window(&self) -> crate::Result<NonZeroU64> {
65 NonZeroU64::new(self.amount.claimable_time_window)
66 .ok_or_else(|| crate::Error::custom("claimable time window cannot be zero"))
67 }
68
69 pub fn claimable_time_window_index(&self, timestamp: i64) -> crate::Result<i64> {
71 let window: i64 = self
72 .claimable_time_window()?
73 .get()
74 .try_into()
75 .map_err(crate::Error::custom)?;
76 Ok(timestamp / window)
77 }
78
79 pub fn claimable_time_key(&self, timestamp: i64) -> crate::Result<[u8; 8]> {
81 let index = self.claimable_time_window_index(timestamp)?;
82 Ok(index.to_le_bytes())
83 }
84}
85
86#[cfg(feature = "model")]
87impl Store {
88 pub fn order_fee_discount_factor(&self, rank: u8, is_referred: bool) -> crate::Result<u128> {
90 use crate::constants::{MARKET_DECIMALS, MARKET_USD_UNIT};
91 use gmsol_model::utils::apply_factor;
92
93 if (rank as u64) > self.gt.max_rank {
94 return Err(crate::Error::custom(format!(
95 "rank {rank} exceeds max_rank {}",
96 self.gt.max_rank
97 )));
98 }
99
100 let discount_factor_for_rank = self.gt.order_fee_discount_factors[rank as usize];
101
102 if is_referred {
103 let discount_factor_for_referred = self.factor.order_fee_discount_for_referred_user;
104
105 let complement_discount_factor_for_referred = MARKET_USD_UNIT
106 .checked_sub(discount_factor_for_referred)
107 .ok_or_else(|| crate::Error::custom("complement calculation overflow"))?;
108
109 let discount_factor = apply_factor::<_, { MARKET_DECIMALS }>(
110 &discount_factor_for_rank,
111 &complement_discount_factor_for_referred,
112 )
113 .and_then(|factor| discount_factor_for_referred.checked_add(factor))
114 .ok_or_else(|| crate::Error::custom("discount factor calculation overflow"))?;
115
116 Ok(discount_factor)
117 } else {
118 Ok(discount_factor_for_rank)
119 }
120 }
121}
122
123impl ReferralCodeV2 {
124 pub const LEN: usize = std::mem::size_of::<ReferralCodeBytes>();
126
127 pub fn decode(code: &str) -> crate::Result<ReferralCodeBytes> {
129 if code.is_empty() {
130 return Err(crate::Error::custom("empty code is not supported"));
131 }
132 let code = bs58::decode(code)
133 .into_vec()
134 .map_err(crate::Error::custom)?;
135 if code.len() > Self::LEN {
136 return Err(crate::Error::custom("the code is too long"));
137 }
138 let padding = Self::LEN - code.len();
139 let mut code_bytes = ReferralCodeBytes::default();
140 code_bytes[padding..].copy_from_slice(&code);
141
142 Ok(code_bytes)
143 }
144
145 pub fn encode(code: &ReferralCodeBytes, skip_leading_ones: bool) -> String {
147 let code = bs58::encode(code).into_string();
148 if skip_leading_ones {
149 code.trim_start_matches('1').to_owned()
150 } else {
151 code
152 }
153 }
154}
155
156impl From<EventPositionState> for PositionState {
157 fn from(event: EventPositionState) -> Self {
158 let EventPositionState {
159 trade_id,
160 increased_at,
161 updated_at_slot,
162 decreased_at,
163 size_in_tokens,
164 collateral_amount,
165 size_in_usd,
166 borrowing_factor,
167 funding_fee_amount_per_size,
168 long_token_claimable_funding_amount_per_size,
169 short_token_claimable_funding_amount_per_size,
170 reserved,
171 } = event;
172
173 Self {
174 trade_id,
175 increased_at,
176 updated_at_slot,
177 decreased_at,
178 size_in_tokens,
179 collateral_amount,
180 size_in_usd,
181 borrowing_factor,
182 funding_fee_amount_per_size,
183 long_token_claimable_funding_amount_per_size,
184 short_token_claimable_funding_amount_per_size,
185 reserved,
186 }
187 }
188}
189
190impl UpdateOrderParams {
191 pub fn is_empty(&self) -> bool {
193 self.size_delta_value.is_none()
194 && self.acceptable_price.is_none()
195 && self.trigger_price.is_none()
196 && self.min_output.is_none()
197 && self.valid_from_ts.is_none()
198 }
199}
200
201#[cfg(feature = "gmsol-model")]
202mod model {
203 use gmsol_model::ClockKind;
204
205 use crate::gmsol_store::types::Clocks;
206
207 impl Clocks {
208 pub fn get(&self, kind: ClockKind) -> Option<i64> {
210 let clock = match kind {
211 ClockKind::PriceImpactDistribution => self.price_impact_distribution,
212 ClockKind::Borrowing => self.borrowing,
213 ClockKind::Funding => self.funding,
214 ClockKind::AdlForLong => self.adl_for_long,
215 ClockKind::AdlForShort => self.adl_for_short,
216 _ => return None,
217 };
218 Some(clock)
219 }
220 }
221}
222
223#[cfg(feature = "gmsol-utils")]
224mod utils {
225 use anchor_lang::prelude::Pubkey;
226 use gmsol_utils::{
227 action::{ActionCallbackKind, ActionFlag, ActionState, MAX_ACTION_FLAGS},
228 fixed_str::bytes_to_fixed_str,
229 glv::{GlvMarketFlag, MAX_GLV_MARKET_FLAGS},
230 impl_fixed_map, impl_flags,
231 market::{
232 self, HasMarketMeta, MarketConfigFactor, MarketConfigFlag, MarketConfigKey, MarketFlag,
233 VirtualInventoryFlag, MAX_MARKET_CONFIG_FACTORS, MAX_MARKET_CONFIG_FLAGS,
234 MAX_MARKET_FLAGS, MAX_VIRTUAL_INVENTORY_FLAGS,
235 },
236 order::{self, OrderFlag, PositionKind, TradeFlag, TradeFlagContainer, MAX_ORDER_FLAGS},
237 pubkey::{self, optional_address},
238 swap::{self, HasSwapParams},
239 token_config::{self, TokensCollector},
240 };
241
242 use crate::gmsol_store::{
243 accounts::{Glv, Market, Position},
244 events::TradeEvent,
245 types::{
246 ActionFlagContainer, ActionHeader, GlvMarketConfig, GlvMarketFlagContainer, GlvMarkets,
247 GlvMarketsEntry, MarketConfig, MarketConfigFactorContainer, MarketConfigFlagContainer,
248 MarketFlagContainer, MarketMeta, Members, MembersEntry, OrderActionParams,
249 OrderFlagContainer, OrderKind, RoleMap, RoleMapEntry, RoleMetadata, RoleStore,
250 SwapActionParams, TokenAndAccount, Tokens, TokensEntry, UpdateTokenConfigParams,
251 VirtualInventoryFlagContainer,
252 },
253 };
254
255 const MAX_TOKENS: usize = 256;
256 const MAX_ALLOWED_NUMBER_OF_MARKETS: usize = 96;
257 const MAX_ROLES: usize = 32;
258 const MAX_MEMBERS: usize = 64;
259
260 impl_fixed_map!(RoleMap, RoleMetadata, MAX_ROLES);
261
262 impl_fixed_map!(Members, Pubkey, pubkey::to_bytes, u32, MAX_MEMBERS);
263
264 impl_fixed_map!(Tokens, Pubkey, pubkey::to_bytes, u8, MAX_TOKENS);
265 impl_fixed_map!(
266 GlvMarkets,
267 Pubkey,
268 pubkey::to_bytes,
269 GlvMarketConfig,
270 MAX_ALLOWED_NUMBER_OF_MARKETS
271 );
272
273 impl_flags!(ActionFlag, MAX_ACTION_FLAGS, u8);
274 impl_flags!(MarketFlag, MAX_MARKET_FLAGS, u8);
275 impl_flags!(GlvMarketFlag, MAX_GLV_MARKET_FLAGS, u8);
276 impl_flags!(VirtualInventoryFlag, MAX_VIRTUAL_INVENTORY_FLAGS, u8);
277 impl_flags!(MarketConfigFlag, MAX_MARKET_CONFIG_FLAGS, u128);
278 impl_flags!(MarketConfigFactor, MAX_MARKET_CONFIG_FACTORS, u128);
279 impl_flags!(OrderFlag, MAX_ORDER_FLAGS, u8);
280
281 impl From<SwapActionParams> for swap::SwapActionParams {
282 fn from(params: SwapActionParams) -> Self {
283 let SwapActionParams {
284 primary_length,
285 secondary_length,
286 num_tokens,
287 padding_0,
288 current_market_token,
289 paths,
290 tokens,
291 } = params;
292 Self {
293 primary_length,
294 secondary_length,
295 num_tokens,
296 padding_0,
297 current_market_token,
298 paths,
299 tokens,
300 }
301 }
302 }
303
304 impl From<MarketMeta> for market::MarketMeta {
305 fn from(meta: MarketMeta) -> Self {
306 let MarketMeta {
307 market_token_mint,
308 index_token_mint,
309 long_token_mint,
310 short_token_mint,
311 } = meta;
312 Self {
313 market_token_mint,
314 index_token_mint,
315 long_token_mint,
316 short_token_mint,
317 }
318 }
319 }
320
321 impl Market {
322 pub fn name(&self) -> crate::Result<&str> {
324 bytes_to_fixed_str(&self.name).map_err(crate::Error::custom)
325 }
326 }
327
328 impl HasMarketMeta for Market {
329 fn market_meta(&self) -> &market::MarketMeta {
330 bytemuck::cast_ref(&self.meta)
331 }
332 }
333
334 impl MarketConfig {
335 pub fn get(&self, key: MarketConfigKey) -> Option<&u128> {
337 let value = match key {
338 MarketConfigKey::SwapImpactExponent => &self.swap_impact_exponent,
339 MarketConfigKey::SwapImpactPositiveFactor => &self.swap_impact_positive_factor,
340 MarketConfigKey::SwapImpactNegativeFactor => &self.swap_impact_negative_factor,
341 MarketConfigKey::SwapFeeReceiverFactor => &self.swap_fee_receiver_factor,
342 MarketConfigKey::SwapFeeFactorForPositiveImpact => {
343 &self.swap_fee_factor_for_positive_impact
344 }
345 MarketConfigKey::SwapFeeFactorForNegativeImpact => {
346 &self.swap_fee_factor_for_negative_impact
347 }
348 MarketConfigKey::MinPositionSizeUsd => &self.min_position_size_usd,
349 MarketConfigKey::MinCollateralValue => &self.min_collateral_value,
350 MarketConfigKey::MinCollateralFactor => &self.min_collateral_factor,
351 MarketConfigKey::MinCollateralFactorForOpenInterestMultiplierForLong => {
352 &self.min_collateral_factor_for_open_interest_multiplier_for_long
353 }
354 MarketConfigKey::MinCollateralFactorForOpenInterestMultiplierForShort => {
355 &self.min_collateral_factor_for_open_interest_multiplier_for_short
356 }
357 MarketConfigKey::MaxPositivePositionImpactFactor => {
358 &self.max_positive_position_impact_factor
359 }
360 MarketConfigKey::MaxNegativePositionImpactFactor => {
361 &self.max_negative_position_impact_factor
362 }
363 MarketConfigKey::MaxPositionImpactFactorForLiquidations => {
364 &self.max_position_impact_factor_for_liquidations
365 }
366 MarketConfigKey::PositionImpactExponent => &self.position_impact_exponent,
367 MarketConfigKey::PositionImpactPositiveFactor => {
368 &self.position_impact_positive_factor
369 }
370 MarketConfigKey::PositionImpactNegativeFactor => {
371 &self.position_impact_negative_factor
372 }
373 MarketConfigKey::OrderFeeReceiverFactor => &self.order_fee_receiver_factor,
374 MarketConfigKey::OrderFeeFactorForPositiveImpact => {
375 &self.order_fee_factor_for_positive_impact
376 }
377 MarketConfigKey::OrderFeeFactorForNegativeImpact => {
378 &self.order_fee_factor_for_negative_impact
379 }
380 MarketConfigKey::LiquidationFeeReceiverFactor => {
381 &self.liquidation_fee_receiver_factor
382 }
383 MarketConfigKey::LiquidationFeeFactor => &self.liquidation_fee_factor,
384 MarketConfigKey::PositionImpactDistributeFactor => {
385 &self.position_impact_distribute_factor
386 }
387 MarketConfigKey::MinPositionImpactPoolAmount => {
388 &self.min_position_impact_pool_amount
389 }
390 MarketConfigKey::BorrowingFeeReceiverFactor => &self.borrowing_fee_receiver_factor,
391 MarketConfigKey::BorrowingFeeFactorForLong => &self.borrowing_fee_factor_for_long,
392 MarketConfigKey::BorrowingFeeFactorForShort => &self.borrowing_fee_factor_for_short,
393 MarketConfigKey::BorrowingFeeExponentForLong => {
394 &self.borrowing_fee_exponent_for_long
395 }
396 MarketConfigKey::BorrowingFeeExponentForShort => {
397 &self.borrowing_fee_exponent_for_short
398 }
399 MarketConfigKey::BorrowingFeeOptimalUsageFactorForLong => {
400 &self.borrowing_fee_optimal_usage_factor_for_long
401 }
402 MarketConfigKey::BorrowingFeeOptimalUsageFactorForShort => {
403 &self.borrowing_fee_optimal_usage_factor_for_short
404 }
405 MarketConfigKey::BorrowingFeeBaseFactorForLong => {
406 &self.borrowing_fee_base_factor_for_long
407 }
408 MarketConfigKey::BorrowingFeeBaseFactorForShort => {
409 &self.borrowing_fee_base_factor_for_short
410 }
411 MarketConfigKey::BorrowingFeeAboveOptimalUsageFactorForLong => {
412 &self.borrowing_fee_above_optimal_usage_factor_for_long
413 }
414 MarketConfigKey::BorrowingFeeAboveOptimalUsageFactorForShort => {
415 &self.borrowing_fee_above_optimal_usage_factor_for_short
416 }
417 MarketConfigKey::FundingFeeExponent => &self.funding_fee_exponent,
418 MarketConfigKey::FundingFeeFactor => &self.funding_fee_factor,
419 MarketConfigKey::FundingFeeMaxFactorPerSecond => {
420 &self.funding_fee_max_factor_per_second
421 }
422 MarketConfigKey::FundingFeeMinFactorPerSecond => {
423 &self.funding_fee_min_factor_per_second
424 }
425 MarketConfigKey::FundingFeeIncreaseFactorPerSecond => {
426 &self.funding_fee_increase_factor_per_second
427 }
428 MarketConfigKey::FundingFeeDecreaseFactorPerSecond => {
429 &self.funding_fee_decrease_factor_per_second
430 }
431 MarketConfigKey::FundingFeeThresholdForStableFunding => {
432 &self.funding_fee_threshold_for_stable_funding
433 }
434 MarketConfigKey::FundingFeeThresholdForDecreaseFunding => {
435 &self.funding_fee_threshold_for_decrease_funding
436 }
437 MarketConfigKey::ReserveFactor => &self.reserve_factor,
438 MarketConfigKey::OpenInterestReserveFactor => &self.open_interest_reserve_factor,
439 MarketConfigKey::MaxPnlFactorForLongDeposit => {
440 &self.max_pnl_factor_for_long_deposit
441 }
442 MarketConfigKey::MaxPnlFactorForShortDeposit => {
443 &self.max_pnl_factor_for_short_deposit
444 }
445 MarketConfigKey::MaxPnlFactorForLongWithdrawal => {
446 &self.max_pnl_factor_for_long_withdrawal
447 }
448 MarketConfigKey::MaxPnlFactorForShortWithdrawal => {
449 &self.max_pnl_factor_for_short_withdrawal
450 }
451 MarketConfigKey::MaxPnlFactorForLongTrader => &self.max_pnl_factor_for_long_trader,
452 MarketConfigKey::MaxPnlFactorForShortTrader => {
453 &self.max_pnl_factor_for_short_trader
454 }
455 MarketConfigKey::MaxPnlFactorForLongAdl => &self.max_pnl_factor_for_long_adl,
456 MarketConfigKey::MaxPnlFactorForShortAdl => &self.max_pnl_factor_for_short_adl,
457 MarketConfigKey::MinPnlFactorAfterLongAdl => &self.min_pnl_factor_after_long_adl,
458 MarketConfigKey::MinPnlFactorAfterShortAdl => &self.min_pnl_factor_after_short_adl,
459 MarketConfigKey::MaxPoolAmountForLongToken => &self.max_pool_amount_for_long_token,
460 MarketConfigKey::MaxPoolAmountForShortToken => {
461 &self.max_pool_amount_for_short_token
462 }
463 MarketConfigKey::MaxPoolValueForDepositForLongToken => {
464 &self.max_pool_value_for_deposit_for_long_token
465 }
466 MarketConfigKey::MaxPoolValueForDepositForShortToken => {
467 &self.max_pool_value_for_deposit_for_short_token
468 }
469 MarketConfigKey::MaxOpenInterestForLong => &self.max_open_interest_for_long,
470 MarketConfigKey::MaxOpenInterestForShort => &self.max_open_interest_for_short,
471 MarketConfigKey::MinTokensForFirstDeposit => &self.min_tokens_for_first_deposit,
472 MarketConfigKey::MinCollateralFactorForLiquidation => {
473 &self.min_collateral_factor_for_liquidation
474 }
475 MarketConfigKey::MarketClosedMinCollateralFactorForLiquidation => {
476 &self.market_closed_min_collateral_factor_for_liquidation
477 }
478 MarketConfigKey::MarketClosedBorrowingFeeBaseFactor => {
479 &self.market_closed_borrowing_fee_base_factor
480 }
481 MarketConfigKey::MarketClosedBorrowingFeeAboveOptimalUsageFactor => {
482 &self.market_closed_borrowing_fee_above_optimal_usage_factor
483 }
484 _ => return None,
485 };
486 Some(value)
487 }
488 }
489
490 impl TokenAndAccount {
491 pub fn token(&self) -> Option<Pubkey> {
493 optional_address(&self.token).copied()
494 }
495
496 pub fn account(&self) -> Option<Pubkey> {
498 optional_address(&self.account).copied()
499 }
500
501 pub fn token_and_account(&self) -> Option<(Pubkey, Pubkey)> {
503 let token = self.token()?;
504 let account = self.account()?;
505 Some((token, account))
506 }
507 }
508
509 impl From<OrderKind> for order::OrderKind {
510 fn from(value: OrderKind) -> Self {
511 match value {
512 OrderKind::Liquidation => Self::Liquidation,
513 OrderKind::AutoDeleveraging => Self::AutoDeleveraging,
514 OrderKind::MarketSwap => Self::MarketSwap,
515 OrderKind::MarketIncrease => Self::MarketIncrease,
516 OrderKind::MarketDecrease => Self::MarketDecrease,
517 OrderKind::LimitSwap => Self::LimitSwap,
518 OrderKind::LimitIncrease => Self::LimitIncrease,
519 OrderKind::LimitDecrease => Self::LimitDecrease,
520 OrderKind::StopLossDecrease => Self::StopLossDecrease,
521 }
522 }
523 }
524
525 impl TryFrom<order::OrderKind> for OrderKind {
526 type Error = crate::Error;
527
528 fn try_from(value: order::OrderKind) -> Result<Self, Self::Error> {
529 match value {
530 order::OrderKind::Liquidation => Ok(Self::Liquidation),
531 order::OrderKind::AutoDeleveraging => Ok(Self::AutoDeleveraging),
532 order::OrderKind::MarketSwap => Ok(Self::MarketSwap),
533 order::OrderKind::MarketIncrease => Ok(Self::MarketIncrease),
534 order::OrderKind::MarketDecrease => Ok(Self::MarketDecrease),
535 order::OrderKind::LimitSwap => Ok(Self::LimitSwap),
536 order::OrderKind::LimitIncrease => Ok(Self::LimitIncrease),
537 order::OrderKind::LimitDecrease => Ok(Self::LimitDecrease),
538 order::OrderKind::StopLossDecrease => Ok(Self::StopLossDecrease),
539 kind => Err(crate::Error::custom(format!(
540 "unsupported order kind: {kind}"
541 ))),
542 }
543 }
544 }
545
546 impl OrderActionParams {
547 pub fn side(&self) -> crate::Result<order::OrderSide> {
549 self.side.try_into().map_err(crate::Error::custom)
550 }
551
552 pub fn kind(&self) -> crate::Result<order::OrderKind> {
554 self.kind.try_into().map_err(crate::Error::custom)
555 }
556
557 pub fn position(&self) -> Option<&Pubkey> {
559 optional_address(&self.position)
560 }
561
562 #[cfg(feature = "model")]
564 pub fn decrease_position_swap_type(
565 &self,
566 ) -> crate::Result<gmsol_model::action::decrease_position::DecreasePositionSwapType>
567 {
568 let ty = self
569 .decrease_position_swap_type
570 .try_into()
571 .map_err(|_| crate::Error::custom("unknown decrease position swap type"))?;
572 Ok(ty)
573 }
574 }
575
576 impl Position {
577 pub fn kind(&self) -> crate::Result<PositionKind> {
579 self.kind.try_into().map_err(crate::Error::custom)
580 }
581 }
582
583 impl Glv {
584 pub fn market_tokens(&self) -> impl Iterator<Item = Pubkey> + '_ {
586 self.markets
587 .entries()
588 .map(|(key, _)| Pubkey::new_from_array(*key))
589 }
590
591 pub fn market_config(&self, market_token: &Pubkey) -> Option<&GlvMarketConfig> {
593 self.markets.get(market_token)
594 }
595
596 pub fn num_markets(&self) -> usize {
598 self.markets.len()
599 }
600
601 pub fn tokens_collector(&self, action: Option<&impl HasSwapParams>) -> TokensCollector {
603 let mut collector = TokensCollector::new(action, self.num_markets());
604 if action.is_none() {
605 collector.insert_token(&self.long_token);
606 collector.insert_token(&self.short_token);
607 }
608 collector
609 }
610 }
611
612 impl From<token_config::UpdateTokenConfigParams> for UpdateTokenConfigParams {
613 fn from(params: token_config::UpdateTokenConfigParams) -> Self {
614 let token_config::UpdateTokenConfigParams {
615 heartbeat_duration,
616 precision,
617 feeds,
618 timestamp_adjustments,
619 expected_provider,
620 } = params;
621 Self {
622 heartbeat_duration,
623 precision,
624 feeds,
625 timestamp_adjustments,
626 expected_provider,
627 }
628 }
629 }
630
631 impl ActionHeader {
632 pub fn action_state(&self) -> crate::Result<ActionState> {
634 ActionState::try_from(self.action_state)
635 .map_err(|_| crate::Error::custom("unknown action state"))
636 }
637
638 pub fn callback_kind(&self) -> crate::Result<ActionCallbackKind> {
640 ActionCallbackKind::try_from(self.callback_kind)
641 .map_err(|_| crate::Error::custom("unknown callback kind"))
642 }
643 }
644
645 impl TradeEvent {
646 pub fn get_flag(&self, flag: TradeFlag) -> bool {
648 let map = TradeFlagContainer::from_value(self.flags);
649 map.get_flag(flag)
650 }
651
652 pub fn is_long(&self) -> bool {
654 self.get_flag(TradeFlag::IsLong)
655 }
656
657 pub fn is_collateral_long(&self) -> bool {
659 self.get_flag(TradeFlag::IsCollateralLong)
660 }
661
662 pub fn to_position(&self, meta: &impl HasMarketMeta) -> Position {
664 let mut position = Position::default();
665
666 let kind = if self.is_long() {
667 PositionKind::Long
668 } else {
669 PositionKind::Short
670 };
671
672 let collateral_token = if self.is_collateral_long() {
673 meta.market_meta().long_token_mint
674 } else {
675 meta.market_meta().short_token_mint
676 };
677
678 position.kind = kind as u8;
679 position.bump = 0;
681 position.store = self.store;
682 position.owner = self.user;
683 position.market_token = self.market_token;
684 position.collateral_token = collateral_token;
685 position.state = self.after.into();
686 position
687 }
688 }
689
690 impl RoleMetadata {
691 pub const ROLE_ENABLED: u8 = u8::MAX;
693
694 pub fn name(&self) -> crate::Result<&str> {
696 bytes_to_fixed_str(&self.name).map_err(crate::Error::custom)
697 }
698
699 pub fn is_enabled(&self) -> bool {
701 self.enabled == Self::ROLE_ENABLED
702 }
703 }
704
705 impl RoleStore {
706 fn enabled_role_index(&self, role: &str) -> crate::Result<Option<u8>> {
707 if let Some(metadata) = self.roles.get(role) {
708 if metadata.name()? != role {
709 return Err(crate::Error::custom("invalid role store"));
710 }
711 if !metadata.is_enabled() {
712 return Err(crate::Error::custom("the given role is disabled"));
713 }
714 Ok(Some(metadata.index))
715 } else {
716 Ok(None)
717 }
718 }
719
720 pub fn has_role(&self, authority: &Pubkey, role: &str) -> crate::Result<bool> {
722 use gmsol_utils::bitmaps::Bitmap;
723 type RoleBitmap = Bitmap<MAX_ROLES>;
724
725 let value = self
726 .members
727 .get(authority)
728 .ok_or_else(|| crate::Error::custom("not a member"))?;
729 let index = self
730 .enabled_role_index(role)?
731 .ok_or_else(|| crate::Error::custom("no such role"))?;
732 let bitmap = RoleBitmap::from_value(*value);
733 Ok(bitmap.get(index as usize))
734 }
735
736 pub fn members(&self) -> impl Iterator<Item = Pubkey> + '_ {
738 self.members
739 .entries()
740 .map(|(key, _)| Pubkey::new_from_array(*key))
741 }
742
743 pub fn roles(&self) -> impl Iterator<Item = crate::Result<&str>> + '_ {
745 self.roles.entries().map(|(_, value)| value.name())
746 }
747 }
748}
749
750#[cfg(all(test, feature = "model"))]
751mod tests {
752 use super::*;
753 use crate::constants::MARKET_USD_UNIT;
754
755 fn create_test_store(
757 max_rank: u64,
758 order_fee_discount_factors: [u128; 16],
759 order_fee_discount_for_referred_user: u128,
760 ) -> Store {
761 let mut store: Store = Zeroable::zeroed();
762 store.gt.max_rank = max_rank;
763 store.gt.order_fee_discount_factors = order_fee_discount_factors;
764 store.factor.order_fee_discount_for_referred_user = order_fee_discount_for_referred_user;
765 store
766 }
767
768 #[test]
769 fn test_order_fee_discount_factor_rank_0_no_referral() {
770 let discount_factors = [0u128; 16];
772 let store = create_test_store(9, discount_factors, 0);
773
774 let factor = store.order_fee_discount_factor(0, false).unwrap();
775 assert_eq!(factor, 0, "rank 0 without referral should have 0 discount");
776 }
777
778 #[test]
779 fn test_order_fee_discount_factor_rank_1_no_referral() {
780 let mut discount_factors = [0u128; 16];
782 discount_factors[1] = MARKET_USD_UNIT / 1_000 * 25; let store = create_test_store(9, discount_factors, 0);
784
785 let factor = store.order_fee_discount_factor(1, false).unwrap();
786 let expected = MARKET_USD_UNIT / 1_000 * 25; assert_eq!(
788 factor, expected,
789 "rank 1 without referral should have 2.5% discount"
790 );
791 }
792
793 #[test]
794 fn test_order_fee_discount_factor_rank_0_with_referral() {
795 let discount_factors = [0u128; 16];
797 let referred_discount = MARKET_USD_UNIT / 100 * 10; let store = create_test_store(9, discount_factors, referred_discount);
799
800 let factor = store.order_fee_discount_factor(0, true).unwrap();
801 assert_eq!(
803 factor, referred_discount,
804 "rank 0 with referral should have 10% discount"
805 );
806 }
807
808 #[test]
809 fn test_order_fee_discount_factor_rank_1_with_referral() {
810 let mut discount_factors = [0u128; 16];
814 let rank_discount = MARKET_USD_UNIT / 1_000 * 25; discount_factors[1] = rank_discount;
816 let referred_discount = MARKET_USD_UNIT / 100 * 10; let store = create_test_store(9, discount_factors, referred_discount);
818
819 let factor = store.order_fee_discount_factor(1, true).unwrap();
820
821 let complement = MARKET_USD_UNIT - referred_discount; let expected = referred_discount + rank_discount * complement / MARKET_USD_UNIT;
823 assert_eq!(
824 factor, expected,
825 "rank 1 with referral should have combined discount (12.25%)"
826 );
827 }
828
829 #[test]
830 fn test_order_fee_discount_factor_max_rank() {
831 let mut discount_factors = [0u128; 16];
833 discount_factors[9] = MARKET_USD_UNIT / 1_000 * 225; let store = create_test_store(9, discount_factors, 0);
835
836 let factor = store.order_fee_discount_factor(9, false).unwrap();
837 let expected = MARKET_USD_UNIT / 1_000 * 225;
838 assert_eq!(factor, expected, "max rank should have 22.5% discount");
839 }
840
841 #[test]
842 fn test_order_fee_discount_factor_rank_exceeds_max() {
843 let discount_factors = [0u128; 16];
845 let store = create_test_store(9, discount_factors, 0);
846
847 let result = store.order_fee_discount_factor(10, false);
848 assert!(
849 result.is_err(),
850 "rank exceeding max_rank should return error"
851 );
852 }
853
854 #[test]
855 fn test_order_fee_discount_factor_full_setup() {
856 let discount_factors: [u128; 16] = [
860 0,
861 MARKET_USD_UNIT / 1_000 * 25, MARKET_USD_UNIT / 1_000 * 50, MARKET_USD_UNIT / 1_000 * 75, MARKET_USD_UNIT / 1_000 * 100, MARKET_USD_UNIT / 1_000 * 125, MARKET_USD_UNIT / 1_000 * 150, MARKET_USD_UNIT / 1_000 * 175, MARKET_USD_UNIT / 1_000 * 200, MARKET_USD_UNIT / 1_000 * 225, 0,
871 0,
872 0,
873 0,
874 0,
875 0,
876 ];
877 let referred_discount = MARKET_USD_UNIT / 100 * 10; let store = create_test_store(9, discount_factors, referred_discount);
879
880 assert_eq!(store.order_fee_discount_factor(0, false).unwrap(), 0);
882
883 let expected_rank_5 = MARKET_USD_UNIT / 1_000 * 125;
885 assert_eq!(
886 store.order_fee_discount_factor(5, false).unwrap(),
887 expected_rank_5
888 );
889
890 use crate::constants::MARKET_DECIMALS;
893 use gmsol_model::utils::apply_factor;
894 let complement = MARKET_USD_UNIT - referred_discount;
895 let expected_combined = referred_discount
896 + apply_factor::<_, { MARKET_DECIMALS }>(&expected_rank_5, &complement).unwrap();
897 assert_eq!(
898 store.order_fee_discount_factor(5, true).unwrap(),
899 expected_combined
900 );
901 }
902
903 #[test]
904 fn test_order_fee_discount_factor_high_discounts() {
905 let mut discount_factors = [0u128; 16];
906 discount_factors[0] = MARKET_USD_UNIT / 100 * 50; let referred_discount = MARKET_USD_UNIT / 100 * 40; let store = create_test_store(9, discount_factors, referred_discount);
909
910 let factor = store.order_fee_discount_factor(0, true).unwrap();
912 let expected = MARKET_USD_UNIT / 100 * 70;
913 assert_eq!(factor, expected, "combined high discounts should be 70%");
914 }
915}