gmsol_store/instructions/
token_config.rs1use anchor_lang::prelude::*;
2use anchor_spl::token::Mint;
3use gmsol_utils::token_config::TokenConfigFlag;
4
5use crate::{
6 states::{
7 PriceProviderKind, Store, TokenConfigExt, TokenMapAccess, TokenMapAccessMut,
8 TokenMapHeader, TokenMapLoader, UpdateTokenConfigParams,
9 },
10 utils::internal,
11 CoreError,
12};
13
14#[derive(Accounts)]
18pub struct InitializeTokenMap<'info> {
19 #[account(mut)]
21 pub payer: Signer<'info>,
22 pub store: AccountLoader<'info, Store>,
24 #[account(
26 init,
27 payer = payer,
28 space = 8 + TokenMapHeader::space(0),
29 )]
30 pub token_map: AccountLoader<'info, TokenMapHeader>,
31 pub system_program: Program<'info, System>,
33}
34
35pub(crate) fn initialize_token_map(ctx: Context<InitializeTokenMap>) -> Result<()> {
37 ctx.accounts.token_map.load_init()?.store = ctx.accounts.store.key();
38 Ok(())
39}
40
41#[derive(Accounts)]
43pub struct PushToTokenMap<'info> {
44 #[account(mut)]
46 pub authority: Signer<'info>,
47 pub store: AccountLoader<'info, Store>,
49 #[account(
51 mut,
52 has_one = store,
53 )]
54 pub token_map: AccountLoader<'info, TokenMapHeader>,
55 pub token: Account<'info, Mint>,
57 pub system_program: Program<'info, System>,
59}
60
61pub(crate) fn unchecked_push_to_token_map(
66 ctx: Context<PushToTokenMap>,
67 name: &str,
68 builder: UpdateTokenConfigParams,
69 enable: bool,
70 new: bool,
71) -> Result<()> {
72 let token = ctx.accounts.token.key();
73 let token_decimals = ctx.accounts.token.decimals;
74 do_push_token_map(
75 ctx.accounts.authority.to_account_info(),
76 &ctx.accounts.token_map,
77 ctx.accounts.system_program.to_account_info(),
78 false,
79 name,
80 &token,
81 token_decimals,
82 builder,
83 enable,
84 new,
85 )
86}
87
88impl<'info> internal::Authentication<'info> for PushToTokenMap<'info> {
89 fn authority(&self) -> &Signer<'info> {
90 &self.authority
91 }
92
93 fn store(&self) -> &AccountLoader<'info, Store> {
94 &self.store
95 }
96}
97
98#[derive(Accounts)]
103pub struct PushToTokenMapSynthetic<'info> {
104 #[account(mut)]
106 pub authority: Signer<'info>,
107 pub store: AccountLoader<'info, Store>,
109 #[account(mut, has_one = store)]
111 pub token_map: AccountLoader<'info, TokenMapHeader>,
112 pub system_program: Program<'info, System>,
114}
115
116pub(crate) fn unchecked_push_to_token_map_synthetic(
121 ctx: Context<PushToTokenMapSynthetic>,
122 name: &str,
123 token: Pubkey,
124 token_decimals: u8,
125 builder: UpdateTokenConfigParams,
126 enable: bool,
127 new: bool,
128) -> Result<()> {
129 do_push_token_map(
130 ctx.accounts.authority.to_account_info(),
131 &ctx.accounts.token_map,
132 ctx.accounts.system_program.to_account_info(),
133 true,
134 name,
135 &token,
136 token_decimals,
137 builder,
138 enable,
139 new,
140 )
141}
142
143impl<'info> internal::Authentication<'info> for PushToTokenMapSynthetic<'info> {
144 fn authority(&self) -> &Signer<'info> {
145 &self.authority
146 }
147
148 fn store(&self) -> &AccountLoader<'info, Store> {
149 &self.store
150 }
151}
152
153#[derive(Accounts)]
157pub struct ToggleTokenConfig<'info> {
158 pub authority: Signer<'info>,
160 pub store: AccountLoader<'info, Store>,
162 #[account(
164 mut,
165 has_one = store,
166 )]
167 pub token_map: AccountLoader<'info, TokenMapHeader>,
168}
169
170impl ToggleTokenConfig<'_> {
171 pub(crate) fn invoke_unchecked(
176 ctx: Context<Self>,
177 token: Pubkey,
178 flag: TokenConfigFlag,
179 enable: bool,
180 ) -> Result<()> {
181 require!(
183 matches!(
184 flag,
185 TokenConfigFlag::Enabled | TokenConfigFlag::AllowPriceAdjustment
186 ),
187 CoreError::Internal
188 );
189 ctx.accounts
190 .token_map
191 .load_token_map_mut()?
192 .get_mut(&token)
193 .ok_or_else(|| error!(CoreError::NotFound))?
194 .set_flag(flag, enable);
195 Ok(())
196 }
197}
198
199impl<'info> internal::Authentication<'info> for ToggleTokenConfig<'info> {
200 fn authority(&self) -> &Signer<'info> {
201 &self.authority
202 }
203
204 fn store(&self) -> &AccountLoader<'info, Store> {
205 &self.store
206 }
207}
208
209#[derive(Accounts)]
213pub struct SetExpectedProvider<'info> {
214 pub authority: Signer<'info>,
216 pub store: AccountLoader<'info, Store>,
218 #[account(mut, has_one = store)]
220 pub token_map: AccountLoader<'info, TokenMapHeader>,
221}
222
223pub(crate) fn unchecked_set_expected_provider(
228 ctx: Context<SetExpectedProvider>,
229 token: Pubkey,
230 provider: PriceProviderKind,
231) -> Result<()> {
232 let mut token_map = ctx.accounts.token_map.load_token_map_mut()?;
233
234 let config = token_map
235 .get_mut(&token)
236 .ok_or_else(|| error!(CoreError::NotFound))?;
237
238 require_neq!(
239 config.expected_provider().map_err(CoreError::from)?,
240 provider,
241 CoreError::PreconditionsAreNotMet
242 );
243
244 config.set_expected_provider(provider);
245 Ok(())
246}
247
248impl<'info> internal::Authentication<'info> for SetExpectedProvider<'info> {
249 fn authority(&self) -> &Signer<'info> {
250 &self.authority
251 }
252
253 fn store(&self) -> &AccountLoader<'info, Store> {
254 &self.store
255 }
256}
257
258#[derive(Accounts)]
262pub struct SetFeedConfig<'info> {
263 pub authority: Signer<'info>,
265 pub store: AccountLoader<'info, Store>,
267 #[account(mut, has_one = store)]
269 pub token_map: AccountLoader<'info, TokenMapHeader>,
270}
271
272impl SetFeedConfig<'_> {
273 pub(crate) fn invoke_unchecked(
278 ctx: Context<SetFeedConfig>,
279 token: Pubkey,
280 provider: &PriceProviderKind,
281 feed: Option<Pubkey>,
282 timestamp_adjustment: Option<u32>,
283 max_deviation_factor: Option<u128>,
284 ) -> Result<()> {
285 require_eq!(
286 feed.is_some() || timestamp_adjustment.is_some() || max_deviation_factor.is_some(),
287 true,
288 {
289 msg!("[Set Feed Config] empty update");
290 CoreError::InvalidArgument
291 }
292 );
293
294 let mut map = ctx.accounts.token_map.load_token_map_mut()?;
295 let feed_config = map
296 .get_mut(&token)
297 .ok_or_else(|| error!(CoreError::NotFound))?
298 .get_feed_config_mut(provider)
299 .map_err(CoreError::from)
300 .map_err(|err| error!(err))?;
301
302 let mut new_config = *feed_config;
303
304 if let Some(feed) = feed {
305 new_config = new_config.with_feed(feed);
306 }
307
308 if let Some(timestamp_adjustment) = timestamp_adjustment {
309 new_config = new_config.with_timestamp_adjustment(timestamp_adjustment);
310 }
311
312 if let Some(max_deviation_factor) = max_deviation_factor {
313 new_config = new_config
314 .with_max_deviation_factor(if max_deviation_factor == 0 {
315 None
316 } else {
317 Some(max_deviation_factor)
318 })
319 .map_err(CoreError::from)
320 .map_err(|err| error!(err))?;
321 }
322
323 *feed_config = new_config;
324
325 Ok(())
326 }
327}
328
329impl<'info> internal::Authentication<'info> for SetFeedConfig<'info> {
330 fn authority(&self) -> &Signer<'info> {
331 &self.authority
332 }
333
334 fn store(&self) -> &AccountLoader<'info, Store> {
335 &self.store
336 }
337}
338
339#[derive(Accounts)]
341pub struct ReadTokenMap<'info> {
342 pub token_map: AccountLoader<'info, TokenMapHeader>,
344}
345
346pub(crate) fn is_token_config_enabled(ctx: Context<ReadTokenMap>, token: &Pubkey) -> Result<bool> {
348 ctx.accounts
349 .token_map
350 .load_token_map()?
351 .get(token)
352 .map(|config| config.is_enabled())
353 .ok_or_else(|| error!(CoreError::NotFound))
354}
355
356pub(crate) fn token_expected_provider(
358 ctx: Context<ReadTokenMap>,
359 token: &Pubkey,
360) -> Result<PriceProviderKind> {
361 ctx.accounts
362 .token_map
363 .load_token_map()?
364 .get(token)
365 .ok_or_else(|| error!(CoreError::NotFound))?
366 .expected_provider()
367 .map_err(CoreError::from)
368 .map_err(|err| error!(err))
369}
370
371pub(crate) fn token_feed(
373 ctx: Context<ReadTokenMap>,
374 token: &Pubkey,
375 provider: &PriceProviderKind,
376) -> Result<Pubkey> {
377 ctx.accounts
378 .token_map
379 .load_token_map()?
380 .get(token)
381 .ok_or_else(|| error!(CoreError::NotFound))?
382 .get_feed(provider)
383 .map_err(CoreError::from)
384 .map_err(|err| error!(err))
385}
386
387pub(crate) fn token_timestamp_adjustment(
389 ctx: Context<ReadTokenMap>,
390 token: &Pubkey,
391 provider: &PriceProviderKind,
392) -> Result<u32> {
393 ctx.accounts
394 .token_map
395 .load_token_map()?
396 .get(token)
397 .ok_or_else(|| error!(CoreError::NotFound))?
398 .timestamp_adjustment(provider)
399 .map_err(CoreError::from)
400 .map_err(|err| error!(err))
401}
402
403pub(crate) fn token_name(ctx: Context<ReadTokenMap>, token: &Pubkey) -> Result<String> {
405 ctx.accounts
406 .token_map
407 .load_token_map()?
408 .get(token)
409 .ok_or_else(|| error!(CoreError::NotFound))?
410 .name()
411 .map(|s| s.to_owned())
412 .map_err(CoreError::from)
413 .map_err(|err| error!(err))
414}
415
416pub(crate) fn token_decimals(ctx: Context<ReadTokenMap>, token: &Pubkey) -> Result<u8> {
418 Ok(ctx
419 .accounts
420 .token_map
421 .load_token_map()?
422 .get(token)
423 .ok_or_else(|| error!(CoreError::NotFound))?
424 .token_decimals())
425}
426
427pub(crate) fn token_precision(ctx: Context<ReadTokenMap>, token: &Pubkey) -> Result<u8> {
429 Ok(ctx
430 .accounts
431 .token_map
432 .load_token_map()?
433 .get(token)
434 .ok_or_else(|| error!(CoreError::NotFound))?
435 .precision())
436}
437
438#[allow(clippy::too_many_arguments)]
439fn do_push_token_map<'info>(
440 authority: AccountInfo<'info>,
441 token_map_loader: &AccountLoader<'info, TokenMapHeader>,
442 system_program: AccountInfo<'info>,
443 synthetic: bool,
444 name: &str,
445 token: &Pubkey,
446 token_decimals: u8,
447 builder: UpdateTokenConfigParams,
448 enable: bool,
449 new: bool,
450) -> Result<()> {
451 {
457 let new_space = 8 + token_map_loader.load()?.space_after_push()?;
458 let current_space = token_map_loader.as_ref().data_len();
459 let current_lamports = token_map_loader.as_ref().lamports();
460 let new_rent_minimum = Rent::get()?.minimum_balance(new_space);
461 if new_space > current_space {
463 if current_lamports < new_rent_minimum {
464 anchor_lang::system_program::transfer(
465 CpiContext::new(
466 system_program,
467 anchor_lang::system_program::Transfer {
468 from: authority,
469 to: token_map_loader.to_account_info(),
470 },
471 ),
472 new_rent_minimum.saturating_sub(current_lamports),
473 )?;
474 }
475 token_map_loader.as_ref().realloc(new_space, false)?;
476 }
477 }
478
479 let mut token_map = token_map_loader.load_token_map_mut()?;
480 token_map.push_with(
481 token,
482 |config| config.update(name, synthetic, token_decimals, builder, enable, new),
483 new,
484 )?;
485 Ok(())
486}