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#[derive(Accounts)]
31#[instruction(index: u16)]
32pub struct InitializeGlv<'info> {
33 #[account(mut)]
35 pub authority: Signer<'info>,
36 pub store: AccountLoader<'info, Store>,
38 #[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 #[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
71pub(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#[derive(Accounts)]
190pub struct UpdateGlvMarketConfig<'info> {
191 pub authority: Signer<'info>,
193 pub store: AccountLoader<'info, Store>,
195 #[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 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
216pub(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
234pub(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#[derive(Accounts)]
260pub struct UpdateGlvConfig<'info> {
261 pub authority: Signer<'info>,
263 pub store: AccountLoader<'info, Store>,
265 #[account(mut, has_one = store)]
267 pub glv: AccountLoader<'info, Glv>,
268}
269
270pub(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#[derive(Accounts)]
295pub struct InsertGlvMarket<'info> {
296 #[account(mut)]
298 pub authority: Signer<'info>,
299 pub store: AccountLoader<'info, Store>,
301 #[account(
303 mut,
304 has_one = store,
305 )]
306 pub glv: AccountLoader<'info, Glv>,
307 #[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 #[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 #[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 pub system_program: Program<'info, System>,
331 pub token_program: Interface<'info, token_interface::TokenInterface>,
333 pub associated_token_program: Program<'info, AssociatedToken>,
335}
336
337pub(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#[derive(Accounts)]
362pub struct RemoveGlvMarket<'info> {
363 #[account(mut)]
365 pub authority: Signer<'info>,
366 pub store: AccountLoader<'info, Store>,
368 #[account(mut, seeds = [Store::WALLET_SEED, store.key().as_ref()], bump)]
370 pub store_wallet: SystemAccount<'info>,
371 #[account(
373 mut,
374 has_one = store,
375 )]
376 pub glv: AccountLoader<'info, Glv>,
377 #[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 #[account(
385 mut,
386 associated_token::mint = market_token,
387 associated_token::authority = glv,
388 )]
389 pub vault: InterfaceAccount<'info, token_interface::TokenAccount>,
390 #[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 pub token_program: Interface<'info, token_interface::TokenInterface>,
400 pub associated_token_program: Program<'info, AssociatedToken>,
402 pub system_program: Program<'info, System>,
404}
405
406pub(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}