gmsol_store/instructions/glv/
management.rs

1use std::collections::BTreeSet;
2
3use anchor_lang::prelude::*;
4use anchor_spl::{
5    associated_token::AssociatedToken,
6    token_2022::Token2022,
7    token_interface::{self, Mint},
8};
9use gmsol_utils::InitSpace;
10
11use crate::{
12    constants,
13    states::{
14        glv::{Glv, UpdateGlvParams},
15        Market, Seed, Store,
16    },
17    utils::{internal, token::is_associated_token_account_with_program_id},
18    CoreError,
19};
20
21/// The accounts definition for [`initialize_glv`](crate::gmsol_store::initialize_glv) instruction.
22///
23/// Remaining accounts expected by this instruction:
24///
25///   - 0..N. `[]` N unique market accounts.
26///   - N..2N. `[]` N corresponding market token accounts. Should be sorted by addresses.
27///   - 2N..3N. `[writable]` N corresponding market token vault accounts,
28///     which will be initialized as the associated token accounts of the GLV.
29///     This vault accounts should be sorted by market token addresses.
30#[derive(Accounts)]
31#[instruction(index: u16)]
32pub struct InitializeGlv<'info> {
33    /// Authority.
34    #[account(mut)]
35    pub authority: Signer<'info>,
36    /// Store.
37    pub store: AccountLoader<'info, Store>,
38    /// Glv token.
39    #[account(
40        init,
41        payer = authority,
42        mint::authority = store,
43        mint::decimals = constants::MARKET_TOKEN_DECIMALS,
44        seeds = [
45            Glv::GLV_TOKEN_SEED,
46            store.key().as_ref(),
47            &index.to_le_bytes(),
48        ],
49        bump,
50        owner = token_program.key(),
51    )]
52    pub glv_token: InterfaceAccount<'info, Mint>,
53    /// Glv account.
54    #[account(
55        init,
56        payer = authority,
57        space = 8 + Glv::INIT_SPACE,
58        seeds = [
59            Glv::SEED,
60            glv_token.key().as_ref(),
61        ],
62        bump,
63    )]
64    pub glv: AccountLoader<'info, Glv>,
65    pub system_program: Program<'info, System>,
66    pub token_program: Program<'info, Token2022>,
67    pub market_token_program: Interface<'info, token_interface::TokenInterface>,
68    pub associated_token_program: Program<'info, AssociatedToken>,
69}
70
71/// Initialize GLV token and account.
72///
73/// # CHECK
74/// - Only MARKET_KEEPER is allowed to call this function.
75pub(crate) fn unchecked_initialize_glv<'info>(
76    ctx: Context<'_, '_, 'info, 'info, InitializeGlv<'info>>,
77    index: u16,
78    length: usize,
79) -> Result<()> {
80    let remaining_accounts = ctx.remaining_accounts;
81
82    require_gte!(
83        Glv::MAX_ALLOWED_NUMBER_OF_MARKETS,
84        length,
85        CoreError::ExceedMaxLengthLimit
86    );
87
88    require_gt!(length, 0, CoreError::InvalidArgument);
89
90    let markets_end = length;
91    let market_tokens_end = markets_end + length;
92    let vaults_end = market_tokens_end + length;
93
94    require_gte!(
95        remaining_accounts.len(),
96        vaults_end,
97        ErrorCode::AccountNotEnoughKeys
98    );
99
100    let markets = &remaining_accounts[0..markets_end];
101    let market_tokens = &remaining_accounts[markets_end..market_tokens_end];
102    let vaults = &remaining_accounts[market_tokens_end..vaults_end];
103
104    let store = ctx.accounts.store.key();
105    let (long_token, short_token, expected_market_tokens) =
106        Glv::process_and_validate_markets_for_init(markets, &store)?;
107
108    ctx.accounts
109        .initialize_vaults(&expected_market_tokens, market_tokens, vaults)?;
110
111    ctx.accounts.glv.load_init()?.unchecked_init(
112        ctx.bumps.glv,
113        index,
114        &store,
115        &ctx.accounts.glv_token.key(),
116        &long_token,
117        &short_token,
118        &expected_market_tokens,
119    )?;
120
121    Ok(())
122}
123
124impl<'info> internal::Authentication<'info> for InitializeGlv<'info> {
125    fn authority(&self) -> &Signer<'info> {
126        &self.authority
127    }
128
129    fn store(&self) -> &AccountLoader<'info, Store> {
130        &self.store
131    }
132}
133
134impl<'info> InitializeGlv<'info> {
135    fn initialize_vaults(
136        &self,
137        expected_market_tokens: &BTreeSet<Pubkey>,
138        market_tokens: &'info [AccountInfo<'info>],
139        vaults: &'info [AccountInfo<'info>],
140    ) -> Result<()> {
141        use anchor_spl::associated_token::{create_idempotent, Create};
142
143        require_eq!(
144            expected_market_tokens.len(),
145            vaults.len(),
146            CoreError::Internal
147        );
148
149        require_eq!(
150            expected_market_tokens.len(),
151            market_tokens.len(),
152            CoreError::Internal
153        );
154
155        let glv = self.glv.key();
156        let token_program_id = self.market_token_program.key;
157
158        for ((expected_market_token, market_token), vault) in
159            expected_market_tokens.iter().zip(market_tokens).zip(vaults)
160        {
161            require!(
162                is_associated_token_account_with_program_id(
163                    vault.key,
164                    &glv,
165                    expected_market_token,
166                    token_program_id,
167                ),
168                ErrorCode::AccountNotAssociatedTokenAccount
169            );
170
171            create_idempotent(CpiContext::new(
172                self.associated_token_program.to_account_info(),
173                Create {
174                    payer: self.authority.to_account_info(),
175                    associated_token: vault.clone(),
176                    authority: self.glv.to_account_info(),
177                    mint: market_token.clone(),
178                    system_program: self.system_program.to_account_info(),
179                    token_program: self.market_token_program.to_account_info(),
180                },
181            ))?;
182        }
183
184        Ok(())
185    }
186}
187
188/// The accounts definition for [`update_glv_market_config`](crate::gmsol_store::update_glv_market_config) instruction.
189#[derive(Accounts)]
190pub struct UpdateGlvMarketConfig<'info> {
191    /// Authority.
192    pub authority: Signer<'info>,
193    /// Store.
194    pub store: AccountLoader<'info, Store>,
195    /// GLV.
196    #[account(
197        mut,
198        has_one = store,
199        constraint = glv.load()?.contains(&market_token.key()) @ CoreError::InvalidArgument,
200    )]
201    pub glv: AccountLoader<'info, Glv>,
202    /// Market token.
203    pub market_token: Box<Account<'info, anchor_spl::token::Mint>>,
204}
205
206impl<'info> internal::Authentication<'info> for UpdateGlvMarketConfig<'info> {
207    fn authority(&self) -> &Signer<'info> {
208        &self.authority
209    }
210
211    fn store(&self) -> &AccountLoader<'info, Store> {
212        &self.store
213    }
214}
215
216/// Update the config for the given market.
217///
218/// # CHECK
219/// - Only MARKET_KEEPER is allowed to call this function.
220pub(crate) fn unchecked_update_glv_market_config(
221    ctx: Context<UpdateGlvMarketConfig>,
222    max_amount: Option<u64>,
223    max_value: Option<u128>,
224) -> Result<()> {
225    require!(
226        max_amount.is_some() || max_value.is_some(),
227        CoreError::InvalidArgument
228    );
229    let mut glv = ctx.accounts.glv.load_mut()?;
230    glv.update_market_config(&ctx.accounts.market_token.key(), max_amount, max_value)?;
231    Ok(())
232}
233
234/// Toggle flag of the given market.
235///
236/// # CHECK
237/// - Only MARKET_KEEPER is allowed to call this function.
238pub(crate) fn unchecked_toggle_glv_market_flag(
239    ctx: Context<UpdateGlvMarketConfig>,
240    flag: &str,
241    enable: bool,
242) -> Result<()> {
243    let flag = flag
244        .parse()
245        .map_err(|_| error!(CoreError::InvalidArgument))?;
246    let mut glv = ctx.accounts.glv.load_mut()?;
247    let market_token = ctx.accounts.market_token.key();
248    let previous = glv.toggle_market_config_flag(&market_token, flag, enable)?;
249    msg!(
250        "[GLV] toggled market flag {}: {} -> {}",
251        flag,
252        previous,
253        enable
254    );
255    Ok(())
256}
257
258/// The accounts definition for [`update_glv_config`](crate::gmsol_store::update_glv_config) instruction.
259#[derive(Accounts)]
260pub struct UpdateGlvConfig<'info> {
261    /// Authority.
262    pub authority: Signer<'info>,
263    /// Store.
264    pub store: AccountLoader<'info, Store>,
265    /// GLV to update.
266    #[account(mut, has_one = store)]
267    pub glv: AccountLoader<'info, Glv>,
268}
269
270/// Update thte config of GLV.
271///
272/// # CHECK
273/// - Only MARKET_KEEPER can use.
274pub(crate) fn unchecked_update_glv(
275    ctx: Context<UpdateGlvConfig>,
276    params: &UpdateGlvParams,
277) -> Result<()> {
278    params.validate()?;
279    ctx.accounts.glv.load_mut()?.update_config(params)?;
280    Ok(())
281}
282
283impl<'info> internal::Authentication<'info> for UpdateGlvConfig<'info> {
284    fn authority(&self) -> &Signer<'info> {
285        &self.authority
286    }
287
288    fn store(&self) -> &AccountLoader<'info, Store> {
289        &self.store
290    }
291}
292
293/// The accounts definition for [`insert_glv_market`](crate::gmsol_store::insert_glv_market) instruction.
294#[derive(Accounts)]
295pub struct InsertGlvMarket<'info> {
296    /// Authority.
297    #[account(mut)]
298    pub authority: Signer<'info>,
299    /// Store.
300    pub store: AccountLoader<'info, Store>,
301    /// GLV to modify.
302    #[account(
303        mut,
304        has_one = store,
305    )]
306    pub glv: AccountLoader<'info, Glv>,
307    /// Market token.
308    #[account(
309        mint::authority = store,
310        mint::token_program = token_program,
311        constraint = !glv.load()?.contains(&market_token.key()) @ CoreError::PreconditionsAreNotMet,
312    )]
313    pub market_token: InterfaceAccount<'info, token_interface::Mint>,
314    /// Market.
315    #[account(
316        has_one = store,
317        constraint = market.load()?.meta().market_token_mint == market_token.key() @ CoreError::MarketTokenMintMismatched,
318    )]
319    pub market: AccountLoader<'info, Market>,
320    /// Vault.
321    #[account(
322        init_if_needed,
323        payer = authority,
324        associated_token::mint = market_token,
325        associated_token::authority = glv,
326        associated_token::token_program = token_program,
327    )]
328    pub vault: InterfaceAccount<'info, token_interface::TokenAccount>,
329    /// System program.
330    pub system_program: Program<'info, System>,
331    /// Token program for market token.
332    pub token_program: Interface<'info, token_interface::TokenInterface>,
333    /// Associated token program.
334    pub associated_token_program: Program<'info, AssociatedToken>,
335}
336
337/// Insert a new market to the GLV.
338///
339/// # CHECK
340/// - Only MARKET_KEEPER can use.
341pub(crate) fn unchecked_insert_glv_market(ctx: Context<InsertGlvMarket>) -> Result<()> {
342    let mut glv = ctx.accounts.glv.load_mut()?;
343    let market = ctx.accounts.market.load()?;
344
345    glv.insert_market(&ctx.accounts.store.key(), &market)?;
346
347    Ok(())
348}
349
350impl<'info> internal::Authentication<'info> for InsertGlvMarket<'info> {
351    fn authority(&self) -> &Signer<'info> {
352        &self.authority
353    }
354
355    fn store(&self) -> &AccountLoader<'info, Store> {
356        &self.store
357    }
358}
359
360/// The accounts definition for [`remove_glv_market`](crate::gmsol_store::remove_glv_market) instruction.
361#[derive(Accounts)]
362pub struct RemoveGlvMarket<'info> {
363    /// Authority.
364    #[account(mut)]
365    pub authority: Signer<'info>,
366    /// Store.
367    pub store: AccountLoader<'info, Store>,
368    /// The store wallet.
369    #[account(mut, seeds = [Store::WALLET_SEED, store.key().as_ref()], bump)]
370    pub store_wallet: SystemAccount<'info>,
371    /// GLV to modify.
372    #[account(
373        mut,
374        has_one = store,
375    )]
376    pub glv: AccountLoader<'info, Glv>,
377    /// Market token.
378    #[account(
379        mint::authority = store,
380        constraint = glv.load()?.contains(&market_token.key()) @ CoreError::PreconditionsAreNotMet,
381    )]
382    pub market_token: InterfaceAccount<'info, token_interface::Mint>,
383    /// Vault.
384    #[account(
385        mut,
386        associated_token::mint = market_token,
387        associated_token::authority = glv,
388    )]
389    pub vault: InterfaceAccount<'info, token_interface::TokenAccount>,
390    /// Store wallet ATA.
391    #[account(
392        init_if_needed,
393        payer = authority,
394        associated_token::mint = market_token,
395        associated_token::authority = store_wallet,
396    )]
397    pub store_wallet_ata: InterfaceAccount<'info, token_interface::TokenAccount>,
398    /// Token program.
399    pub token_program: Interface<'info, token_interface::TokenInterface>,
400    /// Associated token program.
401    pub associated_token_program: Program<'info, AssociatedToken>,
402    /// System program.
403    pub system_program: Program<'info, System>,
404}
405
406/// Remove a new market from the GLV.
407///
408/// # CHECK
409/// - Only MARKET_KEEPER can use.
410pub(crate) fn unchecked_remove_glv_market(ctx: Context<RemoveGlvMarket>) -> Result<()> {
411    use anchor_spl::token_interface::{close_account, CloseAccount};
412
413    let market_token = ctx.accounts.market_token.key();
414    require_eq!(
415        ctx.accounts
416            .glv
417            .load()?
418            .market_config(&market_token)
419            .ok_or_else(|| error!(CoreError::NotFound))?
420            .balance(),
421        0,
422        CoreError::PreconditionsAreNotMet
423    );
424    ctx.accounts
425        .glv
426        .load_mut()?
427        .unchecked_remove_market(&market_token)?;
428
429    {
430        let glv = ctx.accounts.glv.load()?;
431        let signer = glv.signer_seeds();
432
433        let amount = ctx.accounts.vault.amount;
434
435        if amount != 0 {
436            let decimals = ctx.accounts.market_token.decimals;
437            token_interface::transfer_checked(
438                CpiContext::new(
439                    ctx.accounts.token_program.to_account_info(),
440                    token_interface::TransferChecked {
441                        from: ctx.accounts.vault.to_account_info(),
442                        mint: ctx.accounts.market_token.to_account_info(),
443                        to: ctx.accounts.store_wallet_ata.to_account_info(),
444                        authority: ctx.accounts.glv.to_account_info(),
445                    },
446                )
447                .with_signer(&[&signer]),
448                amount,
449                decimals,
450            )?;
451        }
452
453        close_account(
454            CpiContext::new(
455                ctx.accounts.token_program.to_account_info(),
456                CloseAccount {
457                    account: ctx.accounts.vault.to_account_info(),
458                    destination: ctx.accounts.store_wallet.to_account_info(),
459                    authority: ctx.accounts.glv.to_account_info(),
460                },
461            )
462            .with_signer(&[&signer]),
463        )?;
464    }
465
466    Ok(())
467}
468
469impl<'info> internal::Authentication<'info> for RemoveGlvMarket<'info> {
470    fn authority(&self) -> &Signer<'info> {
471        &self.authority
472    }
473
474    fn store(&self) -> &AccountLoader<'info, Store> {
475        &self.store
476    }
477}