Skip to main content

gmsol_programs/utils/
store.rs

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
10/// Referral Code Bytes.
11pub 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    /// Returns whether the pool is a pure pool.
27    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    /// Get claimable time window size.
64    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    /// Get claimable time window index for the given timestamp.
70    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    /// Get claimable time key for the given timestamp.
80    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    /// Get order fee discount factor.
89    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    /// The length of referral code.
125    pub const LEN: usize = std::mem::size_of::<ReferralCodeBytes>();
126
127    /// Decode the given code string to code bytes.
128    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    /// Encode the given code to code string.
146    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    /// Is empty.
192    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        /// Get clock value by [`ClockKind`].
209        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        /// Get name.
323        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        /// Get config by [`MarketConfigKey`].
336        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        /// Get token.
492        pub fn token(&self) -> Option<Pubkey> {
493            optional_address(&self.token).copied()
494        }
495
496        /// Get account.
497        pub fn account(&self) -> Option<Pubkey> {
498            optional_address(&self.account).copied()
499        }
500
501        /// Get token and account.
502        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        /// Get order side.
548        pub fn side(&self) -> crate::Result<order::OrderSide> {
549            self.side.try_into().map_err(crate::Error::custom)
550        }
551
552        /// Get order kind.
553        pub fn kind(&self) -> crate::Result<order::OrderKind> {
554            self.kind.try_into().map_err(crate::Error::custom)
555        }
556
557        /// Get position.
558        pub fn position(&self) -> Option<&Pubkey> {
559            optional_address(&self.position)
560        }
561
562        /// Get decrease position swap type.
563        #[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        /// Get position kind.
578        pub fn kind(&self) -> crate::Result<PositionKind> {
579            self.kind.try_into().map_err(crate::Error::custom)
580        }
581    }
582
583    impl Glv {
584        /// Get all market tokens.
585        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        /// Get [`GlvMarketConfig`] for the given market.
592        pub fn market_config(&self, market_token: &Pubkey) -> Option<&GlvMarketConfig> {
593            self.markets.get(market_token)
594        }
595
596        /// Get the total number of markets.
597        pub fn num_markets(&self) -> usize {
598            self.markets.len()
599        }
600
601        /// Create a new [`TokensCollector`].
602        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        /// Get action state.
633        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        /// Get callback kind.
639        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        /// Get trade data flag.
647        pub fn get_flag(&self, flag: TradeFlag) -> bool {
648            let map = TradeFlagContainer::from_value(self.flags);
649            map.get_flag(flag)
650        }
651
652        /// Return whether the position side is long.
653        pub fn is_long(&self) -> bool {
654            self.get_flag(TradeFlag::IsLong)
655        }
656
657        /// Return whether the collateral side is long.
658        pub fn is_collateral_long(&self) -> bool {
659            self.get_flag(TradeFlag::IsCollateralLong)
660        }
661
662        /// Create position from this event.
663        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            // Note: there's no need to provide a correct bump here for now.
680            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        /// A `u8` value indicates that this role is enabled.
692        pub const ROLE_ENABLED: u8 = u8::MAX;
693
694        /// Get the name of this role.
695        pub fn name(&self) -> crate::Result<&str> {
696            bytes_to_fixed_str(&self.name).map_err(crate::Error::custom)
697        }
698
699        /// Is enabled.
700        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        /// Returns whether the address has the give role.
721        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        /// Returns all members.
737        pub fn members(&self) -> impl Iterator<Item = Pubkey> + '_ {
738            self.members
739                .entries()
740                .map(|(key, _)| Pubkey::new_from_array(*key))
741        }
742
743        /// Returns all roles.
744        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    /// Helper function to create a Store with specific GT and factor settings for testing.
756    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        // rank = 0, is_referred = false -> should return 0
771        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        // rank = 1, is_referred = false -> should return 2.5%
781        let mut discount_factors = [0u128; 16];
782        discount_factors[1] = MARKET_USD_UNIT / 1_000 * 25; // 2.5%
783        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; // 2.5%
787        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        // rank = 0, is_referred = true -> should return 10% (referred discount only)
796        let discount_factors = [0u128; 16];
797        let referred_discount = MARKET_USD_UNIT / 100 * 10; // 10%
798        let store = create_test_store(9, discount_factors, referred_discount);
799
800        let factor = store.order_fee_discount_factor(0, true).unwrap();
801        // Formula: 1 - (1 - 0) * (1 - 0.1) = 0.1 = 10%
802        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        // rank = 1, is_referred = true -> combined discount
811        // Formula: 1 - (1 - A) * (1 - B) = A + B * (1 - A) = 0.025 + 0.10 * (1 - 0.025) = 0.025 + 0.10 * 0.975 = 0.025 + 0.0975 = 0.1225 = 12.25%
812        // A = 2.5% (rank 1), B = 10% (referred)
813        let mut discount_factors = [0u128; 16];
814        let rank_discount = MARKET_USD_UNIT / 1_000 * 25; // 2.5%
815        discount_factors[1] = rank_discount;
816        let referred_discount = MARKET_USD_UNIT / 100 * 10; // 10%
817        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; // 1 - 10% = 90%
822        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        // Test with max_rank = 9, rank = 9
832        let mut discount_factors = [0u128; 16];
833        discount_factors[9] = MARKET_USD_UNIT / 1_000 * 225; // 22.5%
834        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        // rank exceeds max_rank -> should return error
844        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        // Test with full setup matching the integration test:
857        // order_fee_discount_factors: [0, 25, 50, 75, 100, 125, 150, 175, 200, 225] * MARKET_USD_UNIT / 1_000
858        // OrderFeeDiscountForReferredUser: 10% (10 * MARKET_USD_UNIT / 100)
859        let discount_factors: [u128; 16] = [
860            0,
861            MARKET_USD_UNIT / 1_000 * 25,  // 2.5%
862            MARKET_USD_UNIT / 1_000 * 50,  // 5%
863            MARKET_USD_UNIT / 1_000 * 75,  // 7.5%
864            MARKET_USD_UNIT / 1_000 * 100, // 10%
865            MARKET_USD_UNIT / 1_000 * 125, // 12.5%
866            MARKET_USD_UNIT / 1_000 * 150, // 15%
867            MARKET_USD_UNIT / 1_000 * 175, // 17.5%
868            MARKET_USD_UNIT / 1_000 * 200, // 20%
869            MARKET_USD_UNIT / 1_000 * 225, // 22.5%
870            0,
871            0,
872            0,
873            0,
874            0,
875            0,
876        ];
877        let referred_discount = MARKET_USD_UNIT / 100 * 10; // 10%
878        let store = create_test_store(9, discount_factors, referred_discount);
879
880        // Test rank 0, no referral
881        assert_eq!(store.order_fee_discount_factor(0, false).unwrap(), 0);
882
883        // Test rank 5, no referral (12.5%)
884        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        // Test rank 5, with referral
891        // Formula: referred + rank_discount * (1 - referred) / UNIT = 10% + 12.5% * 90% = 10% + 11.25% = 21.25%
892        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; // 50%
907        let referred_discount = MARKET_USD_UNIT / 100 * 40; // 40%
908        let store = create_test_store(9, discount_factors, referred_discount);
909
910        // Combined: 1 - (1 - 0.5) * (1 - 0.4) = 1 - 0.5 * 0.6 = 1 - 0.3 = 0.7 = 70%
911        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}