gmsol_store/states/
token_config.rs

1use std::{
2    cell::{Ref, RefMut},
3    mem::size_of,
4};
5
6use anchor_lang::prelude::*;
7use gmsol_utils::token_config::{TokenConfigError, TokenConfigFlag};
8
9use crate::{utils::fixed_str::fixed_str_to_bytes, CoreError};
10
11use super::{InitSpace, PriceProviderKind};
12
13pub use gmsol_utils::token_config::{
14    FeedConfig, TokenConfig, TokenMapAccess, UpdateTokenConfigParams,
15};
16
17/// Default heartbeat duration for price updates.
18pub const DEFAULT_HEARTBEAT_DURATION: u32 = 30;
19
20/// Default precision for price.
21pub const DEFAULT_PRECISION: u8 = 4;
22
23/// Default timestamp adjustment.
24pub const DEFAULT_TIMESTAMP_ADJUSTMENT: u32 = 0;
25
26#[cfg(feature = "utils")]
27pub use self::utils::TokenMap;
28
29const MAX_TOKENS: usize = 256;
30
31impl From<TokenConfigError> for CoreError {
32    fn from(err: TokenConfigError) -> Self {
33        msg!("Token Config Error: {}", err);
34        match err {
35            TokenConfigError::NotFound => Self::NotFound,
36            TokenConfigError::InvalidProviderIndex => Self::InvalidProviderKindIndex,
37            TokenConfigError::FixedStr(err) => err.into(),
38            TokenConfigError::ExceedMaxLengthLimit => Self::ExceedMaxLengthLimit,
39        }
40    }
41}
42
43pub(crate) trait TokenConfigExt {
44    fn update(
45        &mut self,
46        name: &str,
47        synthetic: bool,
48        token_decimals: u8,
49        builder: UpdateTokenConfigParams,
50        enable: bool,
51        init: bool,
52    ) -> Result<()>;
53}
54
55impl TokenConfigExt for TokenConfig {
56    fn update(
57        &mut self,
58        name: &str,
59        synthetic: bool,
60        token_decimals: u8,
61        builder: UpdateTokenConfigParams,
62        enable: bool,
63        init: bool,
64    ) -> Result<()> {
65        if init {
66            require!(
67                !self.flag(TokenConfigFlag::Initialized),
68                CoreError::InvalidArgument
69            );
70            self.set_flag(TokenConfigFlag::Initialized, true);
71        } else {
72            require!(
73                self.flag(TokenConfigFlag::Initialized),
74                CoreError::InvalidArgument
75            );
76            require_eq!(
77                self.token_decimals,
78                token_decimals,
79                CoreError::TokenDecimalsChanged
80            );
81        }
82        let UpdateTokenConfigParams {
83            heartbeat_duration,
84            precision,
85            feeds,
86            timestamp_adjustments,
87            expected_provider,
88        } = builder;
89
90        require_eq!(
91            feeds.len(),
92            timestamp_adjustments.len(),
93            CoreError::InvalidArgument
94        );
95
96        self.name = fixed_str_to_bytes(name)?;
97        self.set_synthetic(synthetic);
98        self.set_enabled(enable);
99        self.token_decimals = token_decimals;
100        self.precision = precision;
101        self.feeds = feeds
102            .into_iter()
103            .zip(timestamp_adjustments.into_iter())
104            .map(|(feed, timestamp_adjustment)| {
105                FeedConfig::new(feed).with_timestamp_adjustment(timestamp_adjustment)
106            })
107            .collect::<Vec<_>>()
108            .try_into()
109            .map_err(|_| error!(CoreError::InvalidArgument))?;
110        self.expected_provider = expected_provider.unwrap_or(PriceProviderKind::default() as u8);
111        self.heartbeat_duration = heartbeat_duration;
112        Ok(())
113    }
114}
115
116gmsol_utils::fixed_map!(
117    Tokens,
118    Pubkey,
119    crate::utils::pubkey::to_bytes,
120    u8,
121    MAX_TOKENS,
122    0
123);
124
125/// Header of `TokenMap`.
126#[account(zero_copy)]
127#[cfg_attr(feature = "debug", derive(derive_more::Debug))]
128pub struct TokenMapHeader {
129    version: u8,
130    #[cfg_attr(feature = "debug", debug(skip))]
131    padding_0: [u8; 7],
132    /// The authorized store.
133    pub store: Pubkey,
134    tokens: Tokens,
135    #[cfg_attr(feature = "debug", debug(skip))]
136    reserved: [u8; 64],
137}
138
139impl InitSpace for TokenMapHeader {
140    const INIT_SPACE: usize = std::mem::size_of::<TokenMapHeader>();
141}
142
143#[cfg(feature = "display")]
144impl std::fmt::Display for TokenMapHeader {
145    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146        write!(
147            f,
148            "TokenMap: store={}, tokens={}",
149            self.store,
150            self.tokens.len(),
151        )
152    }
153}
154
155impl TokenMapHeader {
156    /// Get the space of the whole `TokenMap` required, excluding discriminator.
157    pub fn space(num_configs: u8) -> usize {
158        TokenMapHeader::INIT_SPACE + (usize::from(num_configs) * TokenConfig::INIT_SPACE)
159    }
160
161    /// Get the space after push.
162    pub fn space_after_push(&self) -> Result<usize> {
163        let num_configs: u8 = self
164            .tokens
165            .len()
166            .checked_add(1)
167            .ok_or_else(|| error!(CoreError::ExceedMaxLengthLimit))?
168            .try_into()
169            .map_err(|_| error!(CoreError::InvalidArgument))?;
170        Ok(Self::space(num_configs))
171    }
172
173    /// Get tokens.
174    pub fn tokens(&self) -> impl Iterator<Item = Pubkey> + '_ {
175        self.tokens
176            .entries()
177            .map(|(k, _)| Pubkey::new_from_array(*k))
178    }
179
180    /// Get the number of tokens.
181    pub fn len(&self) -> usize {
182        self.tokens.len()
183    }
184
185    /// Whether this token map is empty.
186    pub fn is_empty(&self) -> bool {
187        self.tokens.is_empty()
188    }
189
190    fn get_token_config_unchecked<'a>(
191        &self,
192        token: &Pubkey,
193        configs: &'a [u8],
194    ) -> Option<&'a TokenConfig> {
195        let index = usize::from(*self.tokens.get(token)?);
196        crate::utils::dynamic_access::get(configs, index)
197    }
198
199    fn get_token_config_mut_unchecked<'a>(
200        &self,
201        token: &Pubkey,
202        configs: &'a mut [u8],
203    ) -> Option<&'a mut TokenConfig> {
204        let index = usize::from(*self.tokens.get(token)?);
205        crate::utils::dynamic_access::get_mut(configs, index)
206    }
207}
208
209/// Reference to Token Map.
210pub struct TokenMapRef<'a> {
211    header: Ref<'a, TokenMapHeader>,
212    configs: Ref<'a, [u8]>,
213}
214
215/// Mutable Reference to Token Map.
216pub struct TokenMapMut<'a> {
217    header: RefMut<'a, TokenMapHeader>,
218    configs: RefMut<'a, [u8]>,
219}
220
221/// Token Map Loader.
222pub trait TokenMapLoader<'info> {
223    /// Load token map.
224    fn load_token_map(&self) -> Result<TokenMapRef>;
225    /// Load token map with mutable access.
226    fn load_token_map_mut(&self) -> Result<TokenMapMut>;
227}
228
229impl<'info> TokenMapLoader<'info> for AccountLoader<'info, TokenMapHeader> {
230    fn load_token_map(&self) -> Result<TokenMapRef> {
231        // Check the account.
232        self.load()?;
233
234        let data = self.as_ref().try_borrow_data()?;
235        let (_disc, data) = Ref::map_split(data, |d| d.split_at(8));
236        let (header, configs) = Ref::map_split(data, |d| d.split_at(size_of::<TokenMapHeader>()));
237
238        Ok(TokenMapRef {
239            header: Ref::map(header, bytemuck::from_bytes),
240            configs,
241        })
242    }
243
244    fn load_token_map_mut(&self) -> Result<TokenMapMut> {
245        // Check the account for mutablely access.
246        self.load_mut()?;
247
248        let data = self.as_ref().try_borrow_mut_data()?;
249        let (_disc, data) = RefMut::map_split(data, |d| d.split_at_mut(8));
250        let (header, configs) =
251            RefMut::map_split(data, |d| d.split_at_mut(size_of::<TokenMapHeader>()));
252
253        Ok(TokenMapMut {
254            header: RefMut::map(header, bytemuck::from_bytes_mut),
255            configs,
256        })
257    }
258}
259
260impl TokenMapAccess for TokenMapRef<'_> {
261    fn get(&self, token: &Pubkey) -> Option<&TokenConfig> {
262        self.header.get_token_config_unchecked(token, &self.configs)
263    }
264}
265
266/// Token Map Operations.
267///
268/// The token map is append-only.
269pub trait TokenMapAccessMut {
270    /// Get mutably the config of the given token.
271    fn get_mut(&mut self, token: &Pubkey) -> Option<&mut TokenConfig>;
272
273    /// Push a new token config.
274    fn push_with(
275        &mut self,
276        token: &Pubkey,
277        f: impl FnOnce(&mut TokenConfig) -> Result<()>,
278        new: bool,
279    ) -> Result<()>;
280}
281
282impl TokenMapAccessMut for TokenMapMut<'_> {
283    fn get_mut(&mut self, token: &Pubkey) -> Option<&mut TokenConfig> {
284        self.header
285            .get_token_config_mut_unchecked(token, &mut self.configs)
286    }
287
288    fn push_with(
289        &mut self,
290        token: &Pubkey,
291        f: impl FnOnce(&mut TokenConfig) -> Result<()>,
292        new: bool,
293    ) -> Result<()> {
294        let index = if new {
295            let next_index = self.header.tokens.len();
296            require!(next_index < MAX_TOKENS, CoreError::ExceedMaxLengthLimit);
297            let index = next_index
298                .try_into()
299                .map_err(|_| error!(CoreError::InvalidArgument))?;
300            self.header.tokens.insert_with_options(token, index, true)?;
301            index
302        } else {
303            *self
304                .header
305                .tokens
306                .get(token)
307                .ok_or_else(|| error!(CoreError::NotFound))?
308        };
309        let Some(dst) = crate::utils::dynamic_access::get_mut::<TokenConfig>(
310            &mut self.configs,
311            usize::from(index),
312        ) else {
313            return err!(CoreError::NotEnoughSpace);
314        };
315        (f)(dst)
316    }
317}
318
319/// Utils for using token map.
320#[cfg(feature = "utils")]
321pub mod utils {
322    use std::sync::Arc;
323
324    use anchor_lang::{prelude::Pubkey, AccountDeserialize};
325    use bytes::Bytes;
326
327    use crate::utils::de;
328
329    use super::{TokenConfig, TokenMapAccess, TokenMapHeader};
330
331    /// Token Map.
332    pub struct TokenMap {
333        header: Arc<TokenMapHeader>,
334        configs: Bytes,
335    }
336
337    #[cfg(feature = "debug")]
338    impl std::fmt::Debug for TokenMap {
339        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
340            f.debug_struct("TokenMap")
341                .field("header", &self.header)
342                .field("configs", &self.configs)
343                .finish()
344        }
345    }
346
347    impl TokenMapAccess for TokenMap {
348        fn get(&self, token: &Pubkey) -> Option<&TokenConfig> {
349            self.header.get_token_config_unchecked(token, &self.configs)
350        }
351    }
352
353    impl TokenMap {
354        /// Get the header.
355        pub fn header(&self) -> &TokenMapHeader {
356            &self.header
357        }
358
359        /// Is empty.
360        pub fn is_empty(&self) -> bool {
361            self.header.is_empty()
362        }
363
364        /// Get the number of tokens in the map.
365        pub fn len(&self) -> usize {
366            self.header.len()
367        }
368
369        /// Get all tokens.
370        pub fn tokens(&self) -> impl Iterator<Item = Pubkey> + '_ {
371            self.header.tokens()
372        }
373
374        /// Create an iterator over the entires of the map.
375        pub fn iter(&self) -> impl Iterator<Item = (Pubkey, &TokenConfig)> + '_ {
376            self.tokens()
377                .filter_map(|token| self.get(&token).map(|config| (token, config)))
378        }
379    }
380
381    impl AccountDeserialize for TokenMap {
382        fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
383            de::check_discriminator::<TokenMapHeader>(buf)?;
384            Self::try_deserialize_unchecked(buf)
385        }
386
387        fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
388            let header = Arc::new(de::try_deserailize_unchecked::<TokenMapHeader>(buf)?);
389            let (_disc, data) = buf.split_at(8);
390            let (_header, configs) = data.split_at(std::mem::size_of::<TokenMapHeader>());
391            Ok(Self {
392                header,
393                configs: Bytes::copy_from_slice(configs),
394            })
395        }
396    }
397}