gmsol_store/instructions/
token_config.rs

1use anchor_lang::prelude::*;
2use anchor_spl::token::Mint;
3
4use crate::{
5    states::{
6        FeedConfig, PriceProviderKind, Store, TokenConfigExt, TokenMapAccess, TokenMapAccessMut,
7        TokenMapHeader, TokenMapLoader, UpdateTokenConfigParams,
8    },
9    utils::internal,
10    CoreError,
11};
12
13/// The accounts definition for [`initialize_token_map`](crate::gmsol_store::initialize_token_map).
14///
15/// [*See also the documentation for the instruction.*](crate::gmsol_store::initialize_token_map)
16#[derive(Accounts)]
17pub struct InitializeTokenMap<'info> {
18    /// The payer.
19    #[account(mut)]
20    pub payer: Signer<'info>,
21    /// The store account for the token map.
22    pub store: AccountLoader<'info, Store>,
23    /// The token map account to be initialized.
24    #[account(
25        init,
26        payer = payer,
27        space = 8 + TokenMapHeader::space(0),
28    )]
29    pub token_map: AccountLoader<'info, TokenMapHeader>,
30    /// The system program.
31    pub system_program: Program<'info, System>,
32}
33
34/// Initialize a new token map.
35pub(crate) fn initialize_token_map(ctx: Context<InitializeTokenMap>) -> Result<()> {
36    ctx.accounts.token_map.load_init()?.store = ctx.accounts.store.key();
37    Ok(())
38}
39
40/// The accounts definition for [`push_to_token_map`](crate::gmsol_store::push_to_token_map).
41#[derive(Accounts)]
42pub struct PushToTokenMap<'info> {
43    /// The authority of the instruction.
44    #[account(mut)]
45    pub authority: Signer<'info>,
46    /// The store that owns the token map.
47    pub store: AccountLoader<'info, Store>,
48    /// The token map to push config to.
49    #[account(
50        mut,
51        has_one = store,
52    )]
53    pub token_map: AccountLoader<'info, TokenMapHeader>,
54    /// The token to push config for.
55    pub token: Account<'info, Mint>,
56    /// The system program.
57    pub system_program: Program<'info, System>,
58}
59
60/// Push a new token config to the token map.
61///
62/// ## CHECK
63/// - Only [`MARKET_KEEPER`](crate::states::RoleKey::MARKET_KEEPER) can perform this action.
64pub(crate) fn unchecked_push_to_token_map(
65    ctx: Context<PushToTokenMap>,
66    name: &str,
67    builder: UpdateTokenConfigParams,
68    enable: bool,
69    new: bool,
70) -> Result<()> {
71    let token = ctx.accounts.token.key();
72    let token_decimals = ctx.accounts.token.decimals;
73    do_push_token_map(
74        ctx.accounts.authority.to_account_info(),
75        &ctx.accounts.token_map,
76        ctx.accounts.system_program.to_account_info(),
77        false,
78        name,
79        &token,
80        token_decimals,
81        builder,
82        enable,
83        new,
84    )
85}
86
87impl<'info> internal::Authentication<'info> for PushToTokenMap<'info> {
88    fn authority(&self) -> &Signer<'info> {
89        &self.authority
90    }
91
92    fn store(&self) -> &AccountLoader<'info, Store> {
93        &self.store
94    }
95}
96
97/// The accounts definition for
98/// [`push_to_token_map_synthetic`](crate::gmsol_store::push_to_token_map_synthetic).
99///
100/// [*See also the documentation for the instruction.*](crate::gmsol_store::push_to_token_map_synthetic)
101#[derive(Accounts)]
102pub struct PushToTokenMapSynthetic<'info> {
103    /// The authority of the instruction.
104    #[account(mut)]
105    pub authority: Signer<'info>,
106    /// The store that owns the token map.
107    pub store: AccountLoader<'info, Store>,
108    /// The token map to push config to.
109    #[account(mut, has_one = store)]
110    pub token_map: AccountLoader<'info, TokenMapHeader>,
111    /// The system program.
112    pub system_program: Program<'info, System>,
113}
114
115/// Push a new synthetic token config to the token map.
116///
117/// ## CHECK
118/// - Only [`MARKET_KEEPER`](crate::states::RoleKey::MARKET_KEEPER) can perform this action.
119pub(crate) fn unchecked_push_to_token_map_synthetic(
120    ctx: Context<PushToTokenMapSynthetic>,
121    name: &str,
122    token: Pubkey,
123    token_decimals: u8,
124    builder: UpdateTokenConfigParams,
125    enable: bool,
126    new: bool,
127) -> Result<()> {
128    do_push_token_map(
129        ctx.accounts.authority.to_account_info(),
130        &ctx.accounts.token_map,
131        ctx.accounts.system_program.to_account_info(),
132        true,
133        name,
134        &token,
135        token_decimals,
136        builder,
137        enable,
138        new,
139    )
140}
141
142impl<'info> internal::Authentication<'info> for PushToTokenMapSynthetic<'info> {
143    fn authority(&self) -> &Signer<'info> {
144        &self.authority
145    }
146
147    fn store(&self) -> &AccountLoader<'info, Store> {
148        &self.store
149    }
150}
151
152/// The accounts definition for [`toggle_token_config`](crate::gmsol_store::toggle_token_config).
153///
154/// [*See also the documentation for the instruction.*](crate::gmsol_store::toggle_token_config)
155#[derive(Accounts)]
156pub struct ToggleTokenConfig<'info> {
157    /// The authority of the instruction.
158    pub authority: Signer<'info>,
159    /// The store that owns the token map.
160    pub store: AccountLoader<'info, Store>,
161    /// The token map to update.
162    #[account(
163        mut,
164        has_one = store,
165    )]
166    pub token_map: AccountLoader<'info, TokenMapHeader>,
167}
168
169/// Toggle the config for the given token.
170///
171/// ## CHECK
172/// - Only [`MARKET_KEEPER`](crate::states::RoleKey::MARKET_KEEPER) can perform this action.
173pub(crate) fn unchecked_toggle_token_config(
174    ctx: Context<ToggleTokenConfig>,
175    token: Pubkey,
176    enable: bool,
177) -> Result<()> {
178    ctx.accounts
179        .token_map
180        .load_token_map_mut()?
181        .get_mut(&token)
182        .ok_or_else(|| error!(CoreError::NotFound))?
183        .set_enabled(enable);
184    Ok(())
185}
186
187impl<'info> internal::Authentication<'info> for ToggleTokenConfig<'info> {
188    fn authority(&self) -> &Signer<'info> {
189        &self.authority
190    }
191
192    fn store(&self) -> &AccountLoader<'info, Store> {
193        &self.store
194    }
195}
196
197/// The accounts definition for [`set_expected_provider`](crate::gmsol_store::set_expected_provider).
198///
199/// [*See also the documentation for the instruction.*](crate::gmsol_store::set_expected_provider)
200#[derive(Accounts)]
201pub struct SetExpectedProvider<'info> {
202    /// The authority of the instruction.
203    pub authority: Signer<'info>,
204    /// The store that owns the token map.
205    pub store: AccountLoader<'info, Store>,
206    /// The token map to update.
207    #[account(mut, has_one = store)]
208    pub token_map: AccountLoader<'info, TokenMapHeader>,
209}
210
211/// Set the expected provider for the given token.
212///
213/// ## CHECK
214/// - Only [`MARKET_KEEPER`](crate::states::RoleKey::MARKET_KEEPER) can perform this action.
215pub(crate) fn unchecked_set_expected_provider(
216    ctx: Context<SetExpectedProvider>,
217    token: Pubkey,
218    provider: PriceProviderKind,
219) -> Result<()> {
220    let mut token_map = ctx.accounts.token_map.load_token_map_mut()?;
221
222    let config = token_map
223        .get_mut(&token)
224        .ok_or_else(|| error!(CoreError::NotFound))?;
225
226    require_neq!(
227        config.expected_provider().map_err(CoreError::from)?,
228        provider,
229        CoreError::PreconditionsAreNotMet
230    );
231
232    config.set_expected_provider(provider);
233    Ok(())
234}
235
236impl<'info> internal::Authentication<'info> for SetExpectedProvider<'info> {
237    fn authority(&self) -> &Signer<'info> {
238        &self.authority
239    }
240
241    fn store(&self) -> &AccountLoader<'info, Store> {
242        &self.store
243    }
244}
245
246/// The accounts definition for [`set_feed_config`](crate::gmsol_store::set_feed_config).
247///
248/// [*See also the documentation for the instruction.*](crate::gmsol_store::set_feed_config)
249#[derive(Accounts)]
250pub struct SetFeedConfig<'info> {
251    /// The authority of the instruction.
252    pub authority: Signer<'info>,
253    /// The store that owns the token map.
254    pub store: AccountLoader<'info, Store>,
255    /// The token map to update.
256    #[account(mut, has_one = store)]
257    pub token_map: AccountLoader<'info, TokenMapHeader>,
258}
259
260/// Set feed config for the given token.
261///
262/// ## CHECK
263/// - Only [`MARKET_KEEPER`](crate::states::RoleKey::MARKET_KEEPER) can perform this action.
264pub(crate) fn unchecked_set_feed_config(
265    ctx: Context<SetFeedConfig>,
266    token: Pubkey,
267    provider: &PriceProviderKind,
268    feed: Pubkey,
269    timestamp_adjustment: u32,
270) -> Result<()> {
271    ctx.accounts
272        .token_map
273        .load_token_map_mut()?
274        .get_mut(&token)
275        .ok_or_else(|| error!(CoreError::NotFound))?
276        .set_feed_config(
277            provider,
278            FeedConfig::new(feed).with_timestamp_adjustment(timestamp_adjustment),
279        )
280        .map_err(CoreError::from)
281        .map_err(|err| error!(err))
282}
283
284impl<'info> internal::Authentication<'info> for SetFeedConfig<'info> {
285    fn authority(&self) -> &Signer<'info> {
286        &self.authority
287    }
288
289    fn store(&self) -> &AccountLoader<'info, Store> {
290        &self.store
291    }
292}
293
294/// The accounts definition of the instructions to read token map.
295#[derive(Accounts)]
296pub struct ReadTokenMap<'info> {
297    /// Token map.
298    pub token_map: AccountLoader<'info, TokenMapHeader>,
299}
300
301/// Check if the config of the given token is enabled.
302pub(crate) fn is_token_config_enabled(ctx: Context<ReadTokenMap>, token: &Pubkey) -> Result<bool> {
303    ctx.accounts
304        .token_map
305        .load_token_map()?
306        .get(token)
307        .map(|config| config.is_enabled())
308        .ok_or_else(|| error!(CoreError::NotFound))
309}
310
311/// Get expected provider for the given token.
312pub(crate) fn token_expected_provider(
313    ctx: Context<ReadTokenMap>,
314    token: &Pubkey,
315) -> Result<PriceProviderKind> {
316    ctx.accounts
317        .token_map
318        .load_token_map()?
319        .get(token)
320        .ok_or_else(|| error!(CoreError::NotFound))?
321        .expected_provider()
322        .map_err(CoreError::from)
323        .map_err(|err| error!(err))
324}
325
326/// Get feed address of the price provider of the given token.
327pub(crate) fn token_feed(
328    ctx: Context<ReadTokenMap>,
329    token: &Pubkey,
330    provider: &PriceProviderKind,
331) -> Result<Pubkey> {
332    ctx.accounts
333        .token_map
334        .load_token_map()?
335        .get(token)
336        .ok_or_else(|| error!(CoreError::NotFound))?
337        .get_feed(provider)
338        .map_err(CoreError::from)
339        .map_err(|err| error!(err))
340}
341
342/// Get timestamp adjustemnt of the given token.
343pub(crate) fn token_timestamp_adjustment(
344    ctx: Context<ReadTokenMap>,
345    token: &Pubkey,
346    provider: &PriceProviderKind,
347) -> Result<u32> {
348    ctx.accounts
349        .token_map
350        .load_token_map()?
351        .get(token)
352        .ok_or_else(|| error!(CoreError::NotFound))?
353        .timestamp_adjustment(provider)
354        .map_err(CoreError::from)
355        .map_err(|err| error!(err))
356}
357
358/// Get the name of the given token.
359pub(crate) fn token_name(ctx: Context<ReadTokenMap>, token: &Pubkey) -> Result<String> {
360    ctx.accounts
361        .token_map
362        .load_token_map()?
363        .get(token)
364        .ok_or_else(|| error!(CoreError::NotFound))?
365        .name()
366        .map(|s| s.to_owned())
367        .map_err(CoreError::from)
368        .map_err(|err| error!(err))
369}
370
371/// Get the decimals of the given token.
372pub(crate) fn token_decimals(ctx: Context<ReadTokenMap>, token: &Pubkey) -> Result<u8> {
373    Ok(ctx
374        .accounts
375        .token_map
376        .load_token_map()?
377        .get(token)
378        .ok_or_else(|| error!(CoreError::NotFound))?
379        .token_decimals())
380}
381
382/// Get the price precision of the given token.
383pub(crate) fn token_precision(ctx: Context<ReadTokenMap>, token: &Pubkey) -> Result<u8> {
384    Ok(ctx
385        .accounts
386        .token_map
387        .load_token_map()?
388        .get(token)
389        .ok_or_else(|| error!(CoreError::NotFound))?
390        .precision())
391}
392
393#[allow(clippy::too_many_arguments)]
394fn do_push_token_map<'info>(
395    authority: AccountInfo<'info>,
396    token_map_loader: &AccountLoader<'info, TokenMapHeader>,
397    system_program: AccountInfo<'info>,
398    synthetic: bool,
399    name: &str,
400    token: &Pubkey,
401    token_decimals: u8,
402    builder: UpdateTokenConfigParams,
403    enable: bool,
404    new: bool,
405) -> Result<()> {
406    // Note: We have to do the realloc manually because the current implementation of
407    // the `realloc` constraint group will throw an error on the following statement:
408    // `realloc = 8 + token_map.load()?.space_after_push()?`.
409    // The cause of the error is that the generated code directly inserts the above statement
410    // into the realloc method, leading to an `already borrowed` error.
411    {
412        let new_space = 8 + token_map_loader.load()?.space_after_push()?;
413        let current_space = token_map_loader.as_ref().data_len();
414        let current_lamports = token_map_loader.as_ref().lamports();
415        let new_rent_minimum = Rent::get()?.minimum_balance(new_space);
416        // Only realloc when we need more space.
417        if new_space > current_space {
418            if current_lamports < new_rent_minimum {
419                anchor_lang::system_program::transfer(
420                    CpiContext::new(
421                        system_program,
422                        anchor_lang::system_program::Transfer {
423                            from: authority,
424                            to: token_map_loader.to_account_info(),
425                        },
426                    ),
427                    new_rent_minimum.saturating_sub(current_lamports),
428                )?;
429            }
430            token_map_loader.as_ref().realloc(new_space, false)?;
431        }
432    }
433
434    let mut token_map = token_map_loader.load_token_map_mut()?;
435    token_map.push_with(
436        token,
437        |config| config.update(name, synthetic, token_decimals, builder, enable, new),
438        new,
439    )?;
440    Ok(())
441}