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
17pub const DEFAULT_HEARTBEAT_DURATION: u32 = 30;
19
20pub const DEFAULT_PRECISION: u8 = 4;
22
23pub 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#[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 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 pub fn space(num_configs: u8) -> usize {
159 TokenMapHeader::INIT_SPACE + (usize::from(num_configs) * TokenConfig::INIT_SPACE)
160 }
161
162 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 pub fn tokens(&self) -> impl Iterator<Item = Pubkey> + '_ {
176 self.tokens
177 .entries()
178 .map(|(k, _)| Pubkey::new_from_array(*k))
179 }
180
181 pub fn len(&self) -> usize {
183 self.tokens.len()
184 }
185
186 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
210pub struct TokenMapRef<'a> {
212 header: Ref<'a, TokenMapHeader>,
213 configs: Ref<'a, [u8]>,
214}
215
216pub struct TokenMapMut<'a> {
218 header: RefMut<'a, TokenMapHeader>,
219 configs: RefMut<'a, [u8]>,
220}
221
222pub trait TokenMapLoader<'info> {
224 fn load_token_map(&self) -> Result<TokenMapRef>;
226 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 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 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
267pub trait TokenMapAccessMut {
271 fn get_mut(&mut self, token: &Pubkey) -> Option<&mut TokenConfig>;
273
274 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#[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 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 pub fn header(&self) -> &TokenMapHeader {
357 &self.header
358 }
359
360 pub fn is_empty(&self) -> bool {
362 self.header.is_empty()
363 }
364
365 pub fn len(&self) -> usize {
367 self.header.len()
368 }
369
370 pub fn tokens(&self) -> impl Iterator<Item = Pubkey> + '_ {
372 self.header.tokens()
373 }
374
375 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}