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},
7    types::ActionHeader,
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 Glv {
20    fn default() -> Self {
21        Zeroable::zeroed()
22    }
23}
24
25impl Default for Position {
26    fn default() -> Self {
27        Zeroable::zeroed()
28    }
29}
30
31impl Default for ActionHeader {
32    fn default() -> Self {
33        Zeroable::zeroed()
34    }
35}
36
37impl Default for GtExchange {
38    fn default() -> Self {
39        Zeroable::zeroed()
40    }
41}
42
43impl Store {
44    /// Get claimable time window size.
45    pub fn claimable_time_window(&self) -> crate::Result<NonZeroU64> {
46        NonZeroU64::new(self.amount.claimable_time_window)
47            .ok_or_else(|| crate::Error::custom("claimable time window cannot be zero"))
48    }
49
50    /// Get claimable time window index for the given timestamp.
51    pub fn claimable_time_window_index(&self, timestamp: i64) -> crate::Result<i64> {
52        let window: i64 = self
53            .claimable_time_window()?
54            .get()
55            .try_into()
56            .map_err(crate::Error::custom)?;
57        Ok(timestamp / window)
58    }
59
60    /// Get claimable time key for the given timestamp.
61    pub fn claimable_time_key(&self, timestamp: i64) -> crate::Result<[u8; 8]> {
62        let index = self.claimable_time_window_index(timestamp)?;
63        Ok(index.to_le_bytes())
64    }
65}
66
67impl ReferralCodeV2 {
68    /// The length of referral code.
69    pub const LEN: usize = std::mem::size_of::<ReferralCodeBytes>();
70
71    /// Decode the given code string to code bytes.
72    pub fn decode(code: &str) -> crate::Result<ReferralCodeBytes> {
73        if code.is_empty() {
74            return Err(crate::Error::custom("empty code is not supported"));
75        }
76        let code = bs58::decode(code)
77            .into_vec()
78            .map_err(crate::Error::custom)?;
79        if code.len() > Self::LEN {
80            return Err(crate::Error::custom("the code is too long"));
81        }
82        let padding = Self::LEN - code.len();
83        let mut code_bytes = ReferralCodeBytes::default();
84        code_bytes[padding..].copy_from_slice(&code);
85
86        Ok(code_bytes)
87    }
88
89    /// Encode the given code to code string.
90    pub fn encode(code: &ReferralCodeBytes, skip_leading_ones: bool) -> String {
91        let code = bs58::encode(code).into_string();
92        if skip_leading_ones {
93            code.trim_start_matches('1').to_owned()
94        } else {
95            code
96        }
97    }
98}
99
100#[cfg(feature = "gmsol-utils")]
101mod utils {
102    use anchor_lang::prelude::Pubkey;
103    use gmsol_utils::{
104        action::{ActionFlag, MAX_ACTION_FLAGS},
105        impl_fixed_map, impl_flags, market,
106        order::{self, PositionKind},
107        pubkey::{self, optional_address},
108        swap::{self, HasSwapParams},
109        token_config::{self, TokensCollector},
110    };
111
112    use crate::gmsol_store::{
113        accounts::{Glv, Position},
114        types::{
115            ActionFlagContainer, GlvMarketConfig, GlvMarkets, GlvMarketsEntry, MarketMeta,
116            OrderActionParams, OrderKind, SwapActionParams, TokenAndAccount, Tokens, TokensEntry,
117            UpdateTokenConfigParams,
118        },
119    };
120
121    const MAX_TOKENS: usize = 256;
122    const MAX_ALLOWED_NUMBER_OF_MARKETS: usize = 96;
123
124    impl_fixed_map!(Tokens, Pubkey, pubkey::to_bytes, u8, MAX_TOKENS);
125    impl_fixed_map!(
126        GlvMarkets,
127        Pubkey,
128        pubkey::to_bytes,
129        GlvMarketConfig,
130        MAX_ALLOWED_NUMBER_OF_MARKETS
131    );
132
133    impl_flags!(ActionFlag, MAX_ACTION_FLAGS, u8);
134
135    impl From<SwapActionParams> for swap::SwapActionParams {
136        fn from(params: SwapActionParams) -> Self {
137            let SwapActionParams {
138                primary_length,
139                secondary_length,
140                num_tokens,
141                padding_0,
142                current_market_token,
143                paths,
144                tokens,
145            } = params;
146            Self {
147                primary_length,
148                secondary_length,
149                num_tokens,
150                padding_0,
151                current_market_token,
152                paths,
153                tokens,
154            }
155        }
156    }
157
158    impl From<MarketMeta> for market::MarketMeta {
159        fn from(meta: MarketMeta) -> Self {
160            let MarketMeta {
161                market_token_mint,
162                index_token_mint,
163                long_token_mint,
164                short_token_mint,
165            } = meta;
166            Self {
167                market_token_mint,
168                index_token_mint,
169                long_token_mint,
170                short_token_mint,
171            }
172        }
173    }
174
175    impl TokenAndAccount {
176        /// Get token.
177        pub fn token(&self) -> Option<Pubkey> {
178            optional_address(&self.token).copied()
179        }
180
181        /// Get account.
182        pub fn account(&self) -> Option<Pubkey> {
183            optional_address(&self.account).copied()
184        }
185
186        /// Get token and account.
187        pub fn token_and_account(&self) -> Option<(Pubkey, Pubkey)> {
188            let token = self.token()?;
189            let account = self.account()?;
190            Some((token, account))
191        }
192    }
193
194    impl From<OrderKind> for order::OrderKind {
195        fn from(value: OrderKind) -> Self {
196            match value {
197                OrderKind::Liquidation => Self::Liquidation,
198                OrderKind::AutoDeleveraging => Self::AutoDeleveraging,
199                OrderKind::MarketSwap => Self::MarketSwap,
200                OrderKind::MarketIncrease => Self::MarketIncrease,
201                OrderKind::MarketDecrease => Self::MarketDecrease,
202                OrderKind::LimitSwap => Self::LimitSwap,
203                OrderKind::LimitIncrease => Self::LimitIncrease,
204                OrderKind::LimitDecrease => Self::LimitDecrease,
205                OrderKind::StopLossDecrease => Self::StopLossDecrease,
206            }
207        }
208    }
209
210    impl TryFrom<order::OrderKind> for OrderKind {
211        type Error = crate::Error;
212
213        fn try_from(value: order::OrderKind) -> Result<Self, Self::Error> {
214            match value {
215                order::OrderKind::Liquidation => Ok(Self::Liquidation),
216                order::OrderKind::AutoDeleveraging => Ok(Self::AutoDeleveraging),
217                order::OrderKind::MarketSwap => Ok(Self::MarketSwap),
218                order::OrderKind::MarketIncrease => Ok(Self::MarketIncrease),
219                order::OrderKind::MarketDecrease => Ok(Self::MarketDecrease),
220                order::OrderKind::LimitSwap => Ok(Self::LimitSwap),
221                order::OrderKind::LimitIncrease => Ok(Self::LimitIncrease),
222                order::OrderKind::LimitDecrease => Ok(Self::LimitDecrease),
223                order::OrderKind::StopLossDecrease => Ok(Self::StopLossDecrease),
224                kind => Err(crate::Error::custom(format!(
225                    "unsupported order kind: {kind}"
226                ))),
227            }
228        }
229    }
230
231    impl OrderActionParams {
232        /// Get order side.
233        pub fn side(&self) -> crate::Result<order::OrderSide> {
234            self.side.try_into().map_err(crate::Error::custom)
235        }
236
237        /// Get order kind.
238        pub fn kind(&self) -> crate::Result<order::OrderKind> {
239            self.kind.try_into().map_err(crate::Error::custom)
240        }
241
242        /// Get position.
243        pub fn position(&self) -> Option<&Pubkey> {
244            optional_address(&self.position)
245        }
246    }
247
248    impl Position {
249        /// Get position kind.
250        pub fn kind(&self) -> crate::Result<PositionKind> {
251            self.kind.try_into().map_err(crate::Error::custom)
252        }
253    }
254
255    impl Glv {
256        /// Get all market tokens.
257        pub fn market_tokens(&self) -> impl Iterator<Item = Pubkey> + '_ {
258            self.markets
259                .entries()
260                .map(|(key, _)| Pubkey::new_from_array(*key))
261        }
262
263        /// Get [`GlvMarketConfig`] for the given market.
264        pub fn market_config(&self, market_token: &Pubkey) -> Option<&GlvMarketConfig> {
265            self.markets.get(market_token)
266        }
267
268        /// Get the total number of markets.
269        pub fn num_markets(&self) -> usize {
270            self.markets.len()
271        }
272
273        /// Create a new [`TokensCollector`].
274        pub fn tokens_collector(&self, action: Option<&impl HasSwapParams>) -> TokensCollector {
275            TokensCollector::new(action, self.num_markets())
276        }
277    }
278
279    impl From<token_config::UpdateTokenConfigParams> for UpdateTokenConfigParams {
280        fn from(params: token_config::UpdateTokenConfigParams) -> Self {
281            let token_config::UpdateTokenConfigParams {
282                heartbeat_duration,
283                precision,
284                feeds,
285                timestamp_adjustments,
286                expected_provider,
287            } = params;
288            Self {
289                heartbeat_duration,
290                precision,
291                feeds,
292                timestamp_adjustments,
293                expected_provider,
294            }
295        }
296    }
297}