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