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 }
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#[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 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 pub fn space(num_configs: u8) -> usize {
158 TokenMapHeader::INIT_SPACE + (usize::from(num_configs) * TokenConfig::INIT_SPACE)
159 }
160
161 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 pub fn tokens(&self) -> impl Iterator<Item = Pubkey> + '_ {
175 self.tokens
176 .entries()
177 .map(|(k, _)| Pubkey::new_from_array(*k))
178 }
179
180 pub fn len(&self) -> usize {
182 self.tokens.len()
183 }
184
185 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
209pub struct TokenMapRef<'a> {
211 header: Ref<'a, TokenMapHeader>,
212 configs: Ref<'a, [u8]>,
213}
214
215pub struct TokenMapMut<'a> {
217 header: RefMut<'a, TokenMapHeader>,
218 configs: RefMut<'a, [u8]>,
219}
220
221pub trait TokenMapLoader<'info> {
223 fn load_token_map(&self) -> Result<TokenMapRef>;
225 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 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 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
266pub trait TokenMapAccessMut {
270 fn get_mut(&mut self, token: &Pubkey) -> Option<&mut TokenConfig>;
272
273 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#[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 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 pub fn header(&self) -> &TokenMapHeader {
356 &self.header
357 }
358
359 pub fn is_empty(&self) -> bool {
361 self.header.is_empty()
362 }
363
364 pub fn len(&self) -> usize {
366 self.header.len()
367 }
368
369 pub fn tokens(&self) -> impl Iterator<Item = Pubkey> + '_ {
371 self.header.tokens()
372 }
373
374 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}