Skip to main content

gmsol_store/instructions/
token_config.rs

1use 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/// The accounts definition for [`initialize_token_map`](crate::gmsol_store::initialize_token_map).
15///
16/// [*See also the documentation for the instruction.*](crate::gmsol_store::initialize_token_map)
17#[derive(Accounts)]
18pub struct InitializeTokenMap<'info> {
19    /// The payer.
20    #[account(mut)]
21    pub payer: Signer<'info>,
22    /// The store account for the token map.
23    pub store: AccountLoader<'info, Store>,
24    /// The token map account to be initialized.
25    #[account(
26        init,
27        payer = payer,
28        space = 8 + TokenMapHeader::space(0),
29    )]
30    pub token_map: AccountLoader<'info, TokenMapHeader>,
31    /// The system program.
32    pub system_program: Program<'info, System>,
33}
34
35/// Initialize a new token map.
36pub(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/// The accounts definition for [`push_to_token_map`](crate::gmsol_store::push_to_token_map).
42#[derive(Accounts)]
43pub struct PushToTokenMap<'info> {
44    /// The authority of the instruction.
45    #[account(mut)]
46    pub authority: Signer<'info>,
47    /// The store that owns the token map.
48    pub store: AccountLoader<'info, Store>,
49    /// The token map to push config to.
50    #[account(
51        mut,
52        has_one = store,
53    )]
54    pub token_map: AccountLoader<'info, TokenMapHeader>,
55    /// The token to push config for.
56    pub token: Account<'info, Mint>,
57    /// The system program.
58    pub system_program: Program<'info, System>,
59}
60
61/// Push a new token config to the token map.
62///
63/// ## CHECK
64/// - Only [`MARKET_KEEPER`](crate::states::RoleKey::MARKET_KEEPER) can perform this action.
65pub(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/// The accounts definition for
99/// [`push_to_token_map_synthetic`](crate::gmsol_store::push_to_token_map_synthetic).
100///
101/// [*See also the documentation for the instruction.*](crate::gmsol_store::push_to_token_map_synthetic)
102#[derive(Accounts)]
103pub struct PushToTokenMapSynthetic<'info> {
104    /// The authority of the instruction.
105    #[account(mut)]
106    pub authority: Signer<'info>,
107    /// The store that owns the token map.
108    pub store: AccountLoader<'info, Store>,
109    /// The token map to push config to.
110    #[account(mut, has_one = store)]
111    pub token_map: AccountLoader<'info, TokenMapHeader>,
112    /// The system program.
113    pub system_program: Program<'info, System>,
114}
115
116/// Push a new synthetic token config to the token map.
117///
118/// ## CHECK
119/// - Only [`MARKET_KEEPER`](crate::states::RoleKey::MARKET_KEEPER) can perform this action.
120pub(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/// The accounts definition for [`toggle_token_config`](crate::gmsol_store::toggle_token_config).
154///
155/// [*See also the documentation for the instruction.*](crate::gmsol_store::toggle_token_config)
156#[derive(Accounts)]
157pub struct ToggleTokenConfig<'info> {
158    /// The authority of the instruction.
159    pub authority: Signer<'info>,
160    /// The store that owns the token map.
161    pub store: AccountLoader<'info, Store>,
162    /// The token map to update.
163    #[account(
164        mut,
165        has_one = store,
166    )]
167    pub token_map: AccountLoader<'info, TokenMapHeader>,
168}
169
170impl ToggleTokenConfig<'_> {
171    /// Toggle the config for the given token.
172    ///
173    /// ## CHECK
174    /// - Only [`MARKET_KEEPER`](crate::states::RoleKey::MARKET_KEEPER) can perform this action.
175    pub(crate) fn invoke_unchecked(
176        ctx: Context<Self>,
177        token: Pubkey,
178        flag: TokenConfigFlag,
179        enable: bool,
180    ) -> Result<()> {
181        // Only these flags are permitted to change.
182        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/// The accounts definition for [`set_expected_provider`](crate::gmsol_store::set_expected_provider).
210///
211/// [*See also the documentation for the instruction.*](crate::gmsol_store::set_expected_provider)
212#[derive(Accounts)]
213pub struct SetExpectedProvider<'info> {
214    /// The authority of the instruction.
215    pub authority: Signer<'info>,
216    /// The store that owns the token map.
217    pub store: AccountLoader<'info, Store>,
218    /// The token map to update.
219    #[account(mut, has_one = store)]
220    pub token_map: AccountLoader<'info, TokenMapHeader>,
221}
222
223/// Set the expected provider for the given token.
224///
225/// ## CHECK
226/// - Only [`MARKET_KEEPER`](crate::states::RoleKey::MARKET_KEEPER) can perform this action.
227pub(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/// The accounts definition for [`set_feed_config_v2`](crate::gmsol_store::set_feed_config_v2).
259///
260/// [*See also the documentation for the instruction.*](crate::gmsol_store::set_feed_config_v2)
261#[derive(Accounts)]
262pub struct SetFeedConfig<'info> {
263    /// The authority of the instruction.
264    pub authority: Signer<'info>,
265    /// The store that owns the token map.
266    pub store: AccountLoader<'info, Store>,
267    /// The token map to update.
268    #[account(mut, has_one = store)]
269    pub token_map: AccountLoader<'info, TokenMapHeader>,
270}
271
272impl SetFeedConfig<'_> {
273    /// Set feed config for the given token.
274    ///
275    /// ## CHECK
276    /// - Only [`MARKET_KEEPER`](crate::states::RoleKey::MARKET_KEEPER) can perform this action.
277    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/// The accounts definition of the instructions to read token map.
340#[derive(Accounts)]
341pub struct ReadTokenMap<'info> {
342    /// Token map.
343    pub token_map: AccountLoader<'info, TokenMapHeader>,
344}
345
346/// Check if the config of the given token is enabled.
347pub(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
356/// Get expected provider for the given token.
357pub(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
371/// Get feed address of the price provider of the given token.
372pub(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
387/// Get timestamp adjustment of the given token.
388pub(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
403/// Get the name of the given token.
404pub(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
416/// Get the decimals of the given token.
417pub(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
427/// Get the price precision of the given token.
428pub(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    // Note: We have to do the realloc manually because the current implementation of
452    // the `realloc` constraint group will throw an error on the following statement:
453    // `realloc = 8 + token_map.load()?.space_after_push()?`.
454    // The cause of the error is that the generated code directly inserts the above statement
455    // into the realloc method, leading to an `already borrowed` error.
456    {
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        // Only realloc when we need more space.
462        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}