gmsol_store/ops/
glv.rs

1use std::borrow::Borrow;
2
3use anchor_lang::prelude::*;
4use anchor_spl::{
5    token::{transfer_checked, Mint, TokenAccount, TransferChecked},
6    token_interface,
7};
8use gmsol_model::{price::Prices, BaseMarketExt, PnlFactorKind};
9use typed_builder::TypedBuilder;
10
11use crate::{
12    constants,
13    events::{EventEmitter, GlvPricing, GlvPricingKind},
14    states::{
15        common::{
16            action::{Action, ActionExt, ActionParams, ActionSigner},
17            swap::SwapActionParamsExt,
18        },
19        glv::{GlvShift, GlvWithdrawal},
20        market::revertible::Revertible,
21        withdrawal::WithdrawalActionParams,
22        Glv, GlvDeposit, HasMarketMeta, Market, NonceBytes, Oracle, Shift, Store,
23        ValidateOracleTime,
24    },
25    utils::internal::TransferUtils,
26    CoreError, CoreResult, ModelError,
27};
28
29use super::market::{Execute, RevertibleLiquidityMarketOperation};
30
31/// Create GLV Deposit Params.
32#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
33pub struct CreateGlvDepositParams {
34    /// Execution fee in lamports
35    pub execution_lamports: u64,
36    /// The length of the swap path for long token.
37    pub long_token_swap_length: u8,
38    /// The length of the swap path for short token.
39    pub short_token_swap_length: u8,
40    /// Initial long token amount to deposit.
41    pub initial_long_token_amount: u64,
42    /// Initial short otken amount to deposit.
43    pub initial_short_token_amount: u64,
44    /// Market token amount.
45    pub market_token_amount: u64,
46    /// Minimum acceptable maount of market tokens to be minted.
47    pub min_market_token_amount: u64,
48    /// Minimum acceptable amount of glv tokens to receive.
49    pub min_glv_token_amount: u64,
50    /// Whether to unwrap native token when sending funds back.
51    pub should_unwrap_native_token: bool,
52}
53
54impl ActionParams for CreateGlvDepositParams {
55    fn execution_lamports(&self) -> u64 {
56        self.execution_lamports
57    }
58}
59
60/// Operation for creating GLV deposit.
61#[derive(TypedBuilder)]
62pub(crate) struct CreateGlvDepositOperation<'a, 'info> {
63    glv_deposit: AccountLoader<'info, GlvDeposit>,
64    market: AccountLoader<'info, Market>,
65    store: AccountLoader<'info, Store>,
66    owner: &'a AccountInfo<'info>,
67    receiver: &'a AccountInfo<'info>,
68    nonce: &'a NonceBytes,
69    bump: u8,
70    initial_long_token: Option<&'a Account<'info, TokenAccount>>,
71    initial_short_token: Option<&'a Account<'info, TokenAccount>>,
72    market_token: &'a Account<'info, TokenAccount>,
73    glv_token: &'a InterfaceAccount<'info, token_interface::TokenAccount>,
74    params: &'a CreateGlvDepositParams,
75    swap_paths: &'info [AccountInfo<'info>],
76}
77
78impl CreateGlvDepositOperation<'_, '_> {
79    /// Execute.
80    ///
81    /// # CHECK
82    /// - The address of `glv_deposit` must be derived from the given `store`,
83    ///   `owner`, `nonce` and `bump`.
84    /// - `glv` must be owned by the given `store`.
85    /// - `glv` must contain the given `market`.
86    /// - All the token accounts must be owned by `glv_deposit`.
87    /// - The mint of `market_token` account must be the market token of the
88    ///   given `market`.
89    /// - The mint of `glv_token` account must be the glv token of the given `glv`.
90    ///
91    /// # Errors
92    /// - `market` must be initialized, enabled and owned by the given `store`.
93    /// - `initial_long_token_amount`, `initial_short_token_amount` and `market_token_amount`
94    ///   must not all be zero.
95    /// - When the token amount is not zero, the corresponding token account must be provided and
96    ///   have enough amount of tokens.
97    /// - `execution_lamports` must greater than `MIN_EXECUTION_LAMPORTS` and there must be enough lamports
98    ///   in the `glv_deposit` account.
99    /// - `glv_deposit` must be uninitialized.
100    /// - `swap_paths` must be valid.
101    pub(crate) fn unchecked_execute(self) -> Result<()> {
102        let (long_token, short_token) = self.validate_market_and_get_tokens()?;
103
104        self.validate_params_excluding_swap()?;
105
106        let id = self
107            .market
108            .load_mut()?
109            .indexer_mut()
110            .next_glv_deposit_id()?;
111
112        let mut glv_deposit = self.glv_deposit.load_init()?;
113
114        glv_deposit.header.init(
115            id,
116            self.store.key(),
117            self.market.key(),
118            self.owner.key(),
119            self.receiver.key(),
120            *self.nonce,
121            self.bump,
122            self.params.execution_lamports,
123            self.params.should_unwrap_native_token,
124        )?;
125
126        // Init tokens and token accounts.
127        let primary_token_in = if let Some(account) = self.initial_long_token {
128            glv_deposit.tokens.initial_long_token.init(account);
129            account.mint
130        } else {
131            long_token
132        };
133
134        let secondary_token_in = if let Some(account) = self.initial_short_token {
135            glv_deposit.tokens.initial_short_token.init(account);
136            account.mint
137        } else {
138            short_token
139        };
140        glv_deposit.tokens.market_token.init(self.market_token);
141        glv_deposit
142            .tokens
143            .glv_token
144            .init_with_interface(self.glv_token);
145
146        // Init params.
147        glv_deposit.params.deposit.initial_long_token_amount =
148            self.params.initial_long_token_amount;
149        glv_deposit.params.deposit.initial_short_token_amount =
150            self.params.initial_short_token_amount;
151        glv_deposit.params.deposit.min_market_token_amount = self.params.min_market_token_amount;
152
153        glv_deposit.params.market_token_amount = self.params.market_token_amount;
154        glv_deposit.params.min_glv_token_amount = self.params.min_glv_token_amount;
155
156        // Init swap paths.
157        glv_deposit.swap.validate_and_init(
158            &*self.market.load()?,
159            self.params.long_token_swap_length,
160            self.params.short_token_swap_length,
161            self.swap_paths,
162            &self.store.key(),
163            (&primary_token_in, &secondary_token_in),
164            (&long_token, &short_token),
165        )?;
166
167        Ok(())
168    }
169
170    fn validate_market_and_get_tokens(&self) -> Result<(Pubkey, Pubkey)> {
171        let market = self.market.load()?;
172        let meta = market.validated_meta(&self.store.key())?;
173        Ok((meta.long_token_mint, meta.short_token_mint))
174    }
175
176    fn validate_params_excluding_swap(&self) -> Result<()> {
177        let params = self.params;
178        require!(
179            params.initial_long_token_amount != 0
180                || params.initial_short_token_amount != 0
181                || params.market_token_amount != 0,
182            CoreError::EmptyDeposit
183        );
184
185        if params.initial_long_token_amount != 0 {
186            let Some(account) = self.initial_long_token.as_ref() else {
187                return err!(CoreError::TokenAccountNotProvided);
188            };
189            require_gte!(
190                account.amount,
191                params.initial_long_token_amount,
192                CoreError::NotEnoughTokenAmount
193            );
194        }
195
196        if params.initial_short_token_amount != 0 {
197            let Some(account) = self.initial_short_token.as_ref() else {
198                return err!(CoreError::TokenAccountNotProvided);
199            };
200            require_gte!(
201                account.amount,
202                params.initial_short_token_amount,
203                CoreError::NotEnoughTokenAmount
204            );
205        }
206
207        // If the two token accounts are actually the same, then we should check for the sum.
208        let same_initial_token_amount = self.initial_long_token.as_ref().and_then(|long| {
209            self.initial_short_token
210                .as_ref()
211                .and_then(|short| (long.key() == short.key()).then(|| long.amount))
212        });
213        if let Some(amount) = same_initial_token_amount {
214            let total_amount = params
215                .initial_long_token_amount
216                .checked_add(params.initial_short_token_amount)
217                .ok_or_else(|| error!(CoreError::TokenAmountExceedsLimit))?;
218            require_gte!(amount, total_amount, CoreError::NotEnoughTokenAmount);
219        }
220
221        ActionExt::validate_balance(&self.glv_deposit, params.execution_lamports)?;
222
223        Ok(())
224    }
225}
226
227/// Operation for executing a GLV deposit.
228#[derive(TypedBuilder)]
229pub(crate) struct ExecuteGlvDepositOperation<'a, 'info> {
230    glv_deposit: AccountLoader<'info, GlvDeposit>,
231    token_program: AccountInfo<'info>,
232    glv_token_program: AccountInfo<'info>,
233    throw_on_execution_error: bool,
234    store: AccountLoader<'info, Store>,
235    glv: AccountLoader<'info, Glv>,
236    glv_token_mint: &'a mut InterfaceAccount<'info, token_interface::Mint>,
237    glv_token_receiver: AccountInfo<'info>,
238    market: AccountLoader<'info, Market>,
239    market_token_mint: &'a mut Account<'info, Mint>,
240    market_token_source: &'a Account<'info, TokenAccount>,
241    market_token_vault: AccountInfo<'info>,
242    markets: &'info [AccountInfo<'info>],
243    market_tokens: &'info [AccountInfo<'info>],
244    oracle: &'a Oracle,
245    remaining_accounts: &'info [AccountInfo<'info>],
246    #[builder(setter(into))]
247    event_emitter: EventEmitter<'a, 'info>,
248}
249
250impl ExecuteGlvDepositOperation<'_, '_> {
251    /// Execute.
252    ///
253    /// # CHECK
254    /// - The `glv_deposit` must be owned by the `store`.
255    /// - The `glv` must be owned by the `store`, and be the GLV account of the `glv_deposit`.
256    /// - The `market_token_mint` must be the market token of the `market`.
257    /// - The `market_token_vault` must be the vault of GLV for the `market_token`.
258    /// - The lengths of `markets` and `market_tokens` must be the same as the market tokens list of the `glv`.
259    /// - The order of `markets` and `market_tokens` must be the same of the market tokens list of the `glv`.
260    /// - The required prices of tokens must have been validated and stored in the `oracle`.
261    ///
262    /// # Errors
263    /// - The `market` must be owned by the `store` and be the current market of the `glv_deposit`.
264    /// - The swap markets provided by `remaining_accounts` must be valid.
265    pub(crate) fn unchecked_execute(mut self) -> Result<bool> {
266        let throw_on_execution_error = self.throw_on_execution_error;
267        match self.validate_oracle() {
268            Ok(()) => {}
269            Err(CoreError::OracleTimestampsAreLargerThanRequired) if !throw_on_execution_error => {
270                msg!(
271                    "GLV Deposit expired at {}",
272                    self.oracle_updated_before()
273                        .ok()
274                        .flatten()
275                        .expect("must have an expiration time"),
276                );
277                return Ok(false);
278            }
279            Err(err) => {
280                return Err(error!(err));
281            }
282        }
283        let executed = match self.perform_glv_deposit() {
284            Ok(()) => true,
285            Err(err) if !throw_on_execution_error => {
286                msg!("Execute GLV deposit error: {}", err);
287                false
288            }
289            Err(err) => return Err(err),
290        };
291
292        self.validate_after_execution()?;
293
294        Ok(executed)
295    }
296
297    fn validate_oracle(&self) -> CoreResult<()> {
298        self.oracle.validate_time(self)
299    }
300
301    fn validate_before_execution(&self) -> Result<()> {
302        let market = self.market.load()?;
303        market.validate(&self.store.key())?;
304
305        let glv = self.glv.load()?;
306        let glv_deposit = self.glv_deposit.load()?;
307
308        glv_deposit.unchecked_validate_for_execution(self.glv_token_mint, &glv)?;
309
310        require_gte!(
311            self.market_token_source.amount,
312            glv_deposit.params.market_token_amount,
313            CoreError::NotEnoughTokenAmount,
314        );
315
316        Ok(())
317    }
318
319    fn validate_after_execution(&self) -> Result<()> {
320        use anchor_spl::token::accessor::amount;
321
322        let glv = self.glv.load()?;
323        let market_token = self.market_token_mint.key();
324        let vault_balance = amount(&self.market_token_vault)?;
325
326        require_gte!(
327            vault_balance,
328            glv.market_config(&market_token)
329                .ok_or_else(|| error!(CoreError::NotFound))?
330                .balance(),
331            CoreError::Internal
332        );
333
334        Ok(())
335    }
336
337    #[inline(never)]
338    fn perform_glv_deposit(&mut self) -> Result<()> {
339        use gmsol_model::utils::usd_to_market_token_amount;
340
341        self.validate_before_execution()?;
342
343        let glv_token_amount = {
344            let deposit = self.glv_deposit.load()?;
345            let mut market_token_amount = deposit.params.market_token_amount;
346
347            let mut market = RevertibleLiquidityMarketOperation::new(
348                &self.store,
349                self.oracle,
350                &self.market,
351                self.market_token_mint,
352                self.token_program.clone(),
353                Some(&deposit.swap),
354                self.remaining_accounts,
355                self.event_emitter,
356            )?;
357
358            let mut op = market.op()?;
359
360            if market_token_amount != 0 {
361                // The Max PnL validation here helps prevent a malicious user from using GLV
362                // to "withdraw" from a high-risk market, something that would normally be
363                // blocked in other paths (like withdrawals or shift-withdrawal).
364                let prices = self.oracle.market_prices(op.market())?;
365                op.market()
366                    .validate_max_pnl(
367                        &prices,
368                        PnlFactorKind::MaxAfterWithdrawal,
369                        PnlFactorKind::MaxAfterWithdrawal,
370                    )
371                    .map_err(ModelError::from)?;
372            }
373
374            if deposit.is_market_deposit_required() {
375                let executed = op.unchecked_deposit(
376                    &deposit.header().receiver(),
377                    &self.market_token_vault,
378                    &deposit.params.deposit,
379                    (
380                        deposit.tokens.initial_long_token.token(),
381                        deposit.tokens.initial_short_token.token(),
382                    ),
383                    None,
384                )?;
385
386                market_token_amount = market_token_amount
387                    .checked_add(executed.output)
388                    .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
389
390                op = executed.with_output(());
391            }
392
393            let market_token_mint = op.market().market_meta().market_token_mint;
394            let next_market_token_balance = self
395                .glv
396                .load()?
397                .market_config(&market_token_mint)
398                .ok_or_else(|| error!(CoreError::NotFound))?
399                .balance()
400                .checked_add(market_token_amount)
401                .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
402
403            // Calculate GLV token amount to mint.
404            let glv_amount = {
405                let maximize_value = true;
406                let glv_supply = self.glv_token_mint.supply;
407                let glv_value = unchecked_get_glv_value(
408                    &*self.glv.load()?,
409                    self.oracle,
410                    &op,
411                    self.markets,
412                    self.market_tokens,
413                    maximize_value,
414                )?;
415
416                let (received_value, market_pool_value, market_token_supply) = {
417                    let mut prices = self.oracle.market_prices(op.market())?;
418                    let balance = u128::from(market_token_amount);
419                    let received_value = get_glv_value_for_market_with_new_index_price(
420                        self.oracle,
421                        &mut prices,
422                        op.market(),
423                        balance,
424                        false,
425                    )?
426                    .0;
427
428                    let (_, market_pool_value, market_token_supply) =
429                        get_glv_value_for_market(&prices, op.market(), balance, true)?;
430
431                    (received_value, market_pool_value, market_token_supply)
432                };
433
434                // Validate market token balance.
435                self.glv.load()?.validate_market_token_balance(
436                    &market_token_mint,
437                    next_market_token_balance,
438                    &market_pool_value,
439                    &market_token_supply,
440                )?;
441
442                msg!(
443                    "[GLV] Calculating GLV amount with glv_supply={}, glv_value={}, received_value={}",
444                    glv_supply,
445                    glv_value,
446                    received_value,
447                );
448
449                let glv_amount = usd_to_market_token_amount(
450                    received_value,
451                    glv_value,
452                    u128::from(glv_supply),
453                    constants::MARKET_USD_TO_AMOUNT_DIVISOR,
454                )
455                .ok_or_else(|| error!(CoreError::FailedToCalculateGlvAmountToMint))?;
456                let output_amount = u64::try_from(glv_amount)
457                    .map_err(|_| error!(CoreError::TokenAmountOverflow))?;
458
459                self.event_emitter.emit_cpi(&GlvPricing {
460                    glv_token: self.glv_token_mint.key(),
461                    market_token: market_token_mint.key(),
462                    supply: glv_supply,
463                    value_maximized: maximize_value,
464                    value: glv_value,
465                    input_amount: market_token_amount,
466                    input_value: received_value,
467                    output_amount,
468                    kind: GlvPricingKind::Deposit,
469                })?;
470
471                output_amount
472            };
473
474            deposit.validate_output_amount(glv_amount)?;
475
476            // Update market token balance.
477            self.glv
478                .load_mut()?
479                .update_market_token_balance(&market_token_mint, next_market_token_balance)?;
480
481            op.commit();
482
483            glv_amount
484        };
485
486        // Invertible operations after the commitment.
487        {
488            // Complete the market tokens transfer.
489            self.transfer_market_tokens_in();
490
491            // Mint GLV token to the receiver.
492            self.mint_glv_tokens(glv_token_amount);
493        }
494
495        Ok(())
496    }
497
498    /// Mint GLV tokens to target account.
499    ///
500    /// # Panic
501    /// This is an invertible operation that will panic on error.
502    fn mint_glv_tokens(&self, glv_token_amount: u64) {
503        if glv_token_amount != 0 {
504            TransferUtils::new(
505                self.glv_token_program.clone(),
506                &self.store,
507                self.glv_token_mint.to_account_info(),
508            )
509            .mint_to(&self.glv_token_receiver, glv_token_amount)
510            .expect("failed to mint glv tokens");
511        }
512    }
513
514    /// Transfer market tokens to vault.
515    ///
516    /// # Panic
517    /// This is an invertible operation that will panic on error.
518    fn transfer_market_tokens_in(&self) {
519        use anchor_spl::token_interface::{transfer_checked, TransferChecked};
520
521        let deposit = self.glv_deposit.load().expect("must have been checked");
522        let signer = deposit.signer();
523
524        let amount = deposit.params.market_token_amount;
525        if amount != 0 {
526            let token = &*self.market_token_mint;
527            let from = &self.market_token_source;
528            let to = &self.market_token_vault;
529            let ctx = CpiContext::new(
530                self.token_program.to_account_info(),
531                TransferChecked {
532                    from: from.to_account_info(),
533                    mint: token.to_account_info(),
534                    to: to.to_account_info(),
535                    authority: self.glv_deposit.to_account_info(),
536                },
537            );
538            transfer_checked(
539                ctx.with_signer(&[&signer.as_seeds()]),
540                amount,
541                token.decimals,
542            )
543            .expect("failed to transfer market tokens");
544        }
545    }
546}
547
548impl ValidateOracleTime for ExecuteGlvDepositOperation<'_, '_> {
549    fn oracle_updated_after(&self) -> CoreResult<Option<i64>> {
550        Ok(Some(
551            self.glv_deposit
552                .load()
553                .map_err(|_| CoreError::LoadAccountError)?
554                .header
555                .updated_at,
556        ))
557    }
558
559    fn oracle_updated_before(&self) -> CoreResult<Option<i64>> {
560        let ts = self
561            .store
562            .load()
563            .map_err(|_| CoreError::LoadAccountError)?
564            .request_expiration_at(
565                self.glv_deposit
566                    .load()
567                    .map_err(|_| CoreError::LoadAccountError)?
568                    .header
569                    .updated_at,
570            )?;
571        Ok(Some(ts))
572    }
573
574    fn oracle_updated_after_slot(&self) -> CoreResult<Option<u64>> {
575        Ok(Some(
576            self.glv_deposit
577                .load()
578                .map_err(|_| CoreError::LoadAccountError)?
579                .header
580                .updated_at_slot,
581        ))
582    }
583}
584
585/// Create GLV Withdrawal Params.
586#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
587pub struct CreateGlvWithdrawalParams {
588    /// Execution fee in lamports
589    pub execution_lamports: u64,
590    /// The length of the swap path for long token.
591    pub long_token_swap_length: u8,
592    /// The length of the swap path for short token.
593    pub short_token_swap_length: u8,
594    /// The amount of glv tokens to burn.
595    pub glv_token_amount: u64,
596    /// Minimum acceptable final long token to receive.
597    pub min_final_long_token_amount: u64,
598    /// Minimum acceptable final short token to receive.
599    pub min_final_short_token_amount: u64,
600    /// Whether to unwrap native token when sending funds back.
601    pub should_unwrap_native_token: bool,
602}
603
604impl ActionParams for CreateGlvWithdrawalParams {
605    fn execution_lamports(&self) -> u64 {
606        self.execution_lamports
607    }
608}
609
610/// Operation for creating GLV withdrawal.
611#[derive(TypedBuilder)]
612pub(crate) struct CreateGlvWithdrawalOperation<'a, 'info> {
613    glv_withdrawal: AccountLoader<'info, GlvWithdrawal>,
614    market: AccountLoader<'info, Market>,
615    store: AccountLoader<'info, Store>,
616    owner: &'a AccountInfo<'info>,
617    receiver: &'a AccountInfo<'info>,
618    nonce: &'a NonceBytes,
619    bump: u8,
620    final_long_token: &'a Account<'info, TokenAccount>,
621    final_short_token: &'a Account<'info, TokenAccount>,
622    market_token: &'a Account<'info, TokenAccount>,
623    glv_token: &'a InterfaceAccount<'info, token_interface::TokenAccount>,
624    params: &'a CreateGlvWithdrawalParams,
625    swap_paths: &'info [AccountInfo<'info>],
626}
627
628impl CreateGlvWithdrawalOperation<'_, '_> {
629    /// Execute.
630    ///
631    /// # CHECK
632    ///
633    /// # Errors
634    ///
635    pub(crate) fn unchecked_execute(self) -> Result<()> {
636        let (long_token, short_token) = self.validate_market_and_get_tokens()?;
637
638        self.validate_params_excluding_swap()?;
639
640        let id = self
641            .market
642            .load_mut()?
643            .indexer_mut()
644            .next_glv_withdrawal_id()?;
645
646        let mut glv_withdrawal = self.glv_withdrawal.load_init()?;
647
648        // Init header.
649        glv_withdrawal.header.init(
650            id,
651            self.store.key(),
652            self.market.key(),
653            self.owner.key(),
654            self.receiver.key(),
655            *self.nonce,
656            self.bump,
657            self.params.execution_lamports,
658            self.params.should_unwrap_native_token,
659        )?;
660
661        // Init tokens and token accounts.
662        let tokens = &mut glv_withdrawal.tokens;
663        tokens.glv_token.init_with_interface(self.glv_token);
664        tokens.market_token.init(self.market_token);
665        tokens.final_long_token.init(self.final_long_token);
666        tokens.final_short_token.init(self.final_short_token);
667
668        // Init params.
669        let params = &mut glv_withdrawal.params;
670        params.glv_token_amount = self.params.glv_token_amount;
671        params.min_final_long_token_amount = self.params.min_final_long_token_amount;
672        params.min_final_short_token_amount = self.params.min_final_short_token_amount;
673
674        // Init swap paths.
675        glv_withdrawal.swap.validate_and_init(
676            &*self.market.load()?,
677            self.params.long_token_swap_length,
678            self.params.short_token_swap_length,
679            self.swap_paths,
680            &self.store.key(),
681            (&long_token, &short_token),
682            (&self.final_long_token.mint, &self.final_short_token.mint),
683        )?;
684
685        Ok(())
686    }
687
688    fn validate_market_and_get_tokens(&self) -> Result<(Pubkey, Pubkey)> {
689        let market = self.market.load()?;
690        let meta = market.validated_meta(&self.store.key())?;
691        Ok((meta.long_token_mint, meta.short_token_mint))
692    }
693
694    fn validate_params_excluding_swap(&self) -> Result<()> {
695        let params = self.params;
696        let amount = params.glv_token_amount;
697        require!(amount != 0, CoreError::EmptyGlvWithdrawal);
698        require_gte!(
699            self.glv_token.amount,
700            amount,
701            CoreError::NotEnoughTokenAmount
702        );
703
704        ActionExt::validate_balance(&self.glv_withdrawal, params.execution_lamports)?;
705
706        Ok(())
707    }
708}
709
710/// Operation for executing a GLV withdrawal.
711#[derive(TypedBuilder)]
712pub(crate) struct ExecuteGlvWithdrawalOperation<'a, 'info> {
713    glv_withdrawal: AccountLoader<'info, GlvWithdrawal>,
714    token_program: AccountInfo<'info>,
715    glv_token_program: AccountInfo<'info>,
716    throw_on_execution_error: bool,
717    store: AccountLoader<'info, Store>,
718    glv: &'a AccountLoader<'info, Glv>,
719    glv_token_mint: &'a mut InterfaceAccount<'info, token_interface::Mint>,
720    glv_token_account: AccountInfo<'info>,
721    market: AccountLoader<'info, Market>,
722    market_token_mint: &'a mut Account<'info, Mint>,
723    market_token_glv_vault: &'a Account<'info, TokenAccount>,
724    market_token_withdrawal_vault: AccountInfo<'info>,
725    markets: &'info [AccountInfo<'info>],
726    market_tokens: &'info [AccountInfo<'info>],
727    oracle: &'a Oracle,
728    remaining_accounts: &'info [AccountInfo<'info>],
729    #[builder(setter(into))]
730    event_emitter: EventEmitter<'a, 'info>,
731}
732
733impl ExecuteGlvWithdrawalOperation<'_, '_> {
734    /// Execute.
735    ///
736    /// # CHECK
737    ///
738    /// # Errors
739    ///
740    pub(crate) fn unchecked_execute(mut self) -> Result<Option<(u64, u64)>> {
741        let throw_on_execution_error = self.throw_on_execution_error;
742        match self.validate_oracle() {
743            Ok(()) => {}
744            Err(CoreError::OracleTimestampsAreLargerThanRequired) if !throw_on_execution_error => {
745                msg!(
746                    "GLV Withdrawal expired at {}",
747                    self.oracle_updated_before()
748                        .ok()
749                        .flatten()
750                        .expect("must have an expiration time"),
751                );
752                return Ok(None);
753            }
754            Err(err) => {
755                return Err(error!(err));
756            }
757        }
758
759        let executed = match self.perform_glv_withdrawal() {
760            Ok(amounts) => Some(amounts),
761            Err(err) if !throw_on_execution_error => {
762                msg!("Execute GLV withdrawal error: {}", err);
763                None
764            }
765            Err(err) => return Err(err),
766        };
767
768        self.validate_after_execution()?;
769
770        Ok(executed)
771    }
772
773    fn validate_oracle(&self) -> CoreResult<()> {
774        self.oracle.validate_time(self)
775    }
776
777    fn validate_market(&self) -> Result<()> {
778        self.market.load()?.validate(&self.store.key())?;
779        Ok(())
780    }
781
782    fn validate_after_execution(&self) -> Result<()> {
783        use anchor_spl::token::accessor::amount;
784
785        let glv = self.glv.load()?;
786        let market_token = self.market_token_mint.key();
787        let vault_balance = amount(&self.market_token_glv_vault.to_account_info())?;
788
789        require_gte!(
790            vault_balance,
791            glv.market_config(&market_token)
792                .ok_or_else(|| error!(CoreError::NotFound))?
793                .balance(),
794            CoreError::Internal
795        );
796
797        Ok(())
798    }
799
800    #[inline(never)]
801    fn perform_glv_withdrawal(&mut self) -> Result<(u64, u64)> {
802        use gmsol_model::utils::market_token_amount_to_usd;
803
804        self.validate_market()?;
805
806        let withdrawal_signer = self.glv_withdrawal.load()?.signer();
807
808        let (glv_token_amount, amounts) = {
809            let withdrawal = self.glv_withdrawal.load()?;
810            let glv_token_amount = withdrawal.params.glv_token_amount;
811            let market_token_mint = self.market_token_mint.to_account_info();
812            let market_token_decimals = self.market_token_mint.decimals;
813
814            let mut market = RevertibleLiquidityMarketOperation::new(
815                &self.store,
816                self.oracle,
817                &self.market,
818                self.market_token_mint,
819                self.token_program.clone(),
820                Some(&withdrawal.swap),
821                self.remaining_accounts,
822                self.event_emitter,
823            )?;
824
825            let op = market.op()?;
826
827            // Calculate market token amount to withdrawal.
828            let market_token_amount = {
829                let maximize_value = false;
830                let glv_supply = self.glv_token_mint.supply;
831                let glv_value = unchecked_get_glv_value(
832                    &*self.glv.load()?,
833                    self.oracle,
834                    &op,
835                    self.markets,
836                    self.market_tokens,
837                    maximize_value,
838                )?;
839
840                let market_token_value = market_token_amount_to_usd(
841                    &(u128::from(glv_token_amount)),
842                    &glv_value,
843                    &(u128::from(glv_supply)),
844                )
845                .ok_or_else(|| error!(CoreError::FailedToCalculateGlvValueForMarket))?;
846
847                msg!(
848                    "[GLV] Calculating GM amount with glv_supply={}, glv_value={}, market_token_value={}",
849                    glv_supply,
850                    glv_value,
851                    market_token_value,
852                );
853
854                let amount = get_market_token_amount_for_glv_value(
855                    self.oracle,
856                    op.market(),
857                    market_token_value,
858                    true,
859                )?
860                .try_into()
861                .map_err(|_| error!(CoreError::TokenAmountOverflow))?;
862
863                self.event_emitter.emit_cpi(&GlvPricing {
864                    glv_token: self.glv_token_mint.key(),
865                    market_token: market_token_mint.key(),
866                    supply: glv_supply,
867                    value_maximized: maximize_value,
868                    value: glv_value,
869                    input_amount: glv_token_amount,
870                    input_value: market_token_value,
871                    output_amount: amount,
872                    kind: GlvPricingKind::Withdrawal,
873                })?;
874
875                amount
876            };
877
878            require_gte!(
879                self.market_token_glv_vault.amount,
880                market_token_amount,
881                CoreError::NotEnoughTokenAmount,
882            );
883
884            let executed = {
885                let mut params = WithdrawalActionParams::default();
886                params.market_token_amount = market_token_amount;
887                params.min_long_token_amount = withdrawal.params.min_final_long_token_amount;
888                params.min_short_token_amount = withdrawal.params.min_final_short_token_amount;
889
890                op.unchecked_withdraw(
891                    &self.market_token_withdrawal_vault,
892                    &params,
893                    (
894                        withdrawal.tokens.final_long_token(),
895                        withdrawal.tokens.final_short_token(),
896                    ),
897                    None,
898                )?
899            };
900
901            let amounts = executed.output;
902
903            // Update market token balance.
904            let next_market_token_balance = self
905                .glv
906                .load()?
907                .market_config(market_token_mint.key)
908                .ok_or_else(|| error!(CoreError::NotFound))?
909                .balance()
910                .checked_sub(market_token_amount)
911                .ok_or_else(|| error!(CoreError::NotEnoughTokenAmount))?;
912
913            self.glv
914                .load_mut()?
915                .update_market_token_balance(market_token_mint.key, next_market_token_balance)?;
916
917            // Transfer market tokens from the GLV vault to the withdrawal vault before the commitment.
918            transfer_checked(
919                CpiContext::new(
920                    self.token_program.to_account_info(),
921                    TransferChecked {
922                        from: self.market_token_glv_vault.to_account_info(),
923                        mint: market_token_mint,
924                        to: self.market_token_withdrawal_vault.to_account_info(),
925                        authority: self.glv.to_account_info(),
926                    },
927                )
928                .with_signer(&[&self.glv.load()?.signer_seeds()]),
929                market_token_amount,
930                market_token_decimals,
931            )
932            .expect("failed to transfer market tokens");
933
934            executed.commit();
935
936            (glv_token_amount, amounts)
937        };
938
939        // Invertible operations after the commitment.
940        {
941            // Burn GLV tokens.
942            self.burn_glv_tokens(&withdrawal_signer, glv_token_amount);
943        }
944
945        Ok(amounts)
946    }
947
948    /// Burn GLV tokens from the source account.
949    ///
950    /// # Panic
951    /// This is an invertbile operation that will panic on error.
952    fn burn_glv_tokens(&self, signer: &ActionSigner, glv_token_amount: u64) {
953        use anchor_spl::token_interface::{burn, Burn};
954
955        if glv_token_amount != 0 {
956            let ctx = CpiContext::new(
957                self.glv_token_program.to_account_info(),
958                Burn {
959                    mint: self.glv_token_mint.to_account_info(),
960                    from: self.glv_token_account.clone(),
961                    authority: self.glv_withdrawal.to_account_info(),
962                },
963            );
964            burn(ctx.with_signer(&[&signer.as_seeds()]), glv_token_amount)
965                .expect("failed to burn GLV tokens");
966        }
967    }
968}
969
970impl ValidateOracleTime for ExecuteGlvWithdrawalOperation<'_, '_> {
971    fn oracle_updated_after(&self) -> CoreResult<Option<i64>> {
972        Ok(Some(
973            self.glv_withdrawal
974                .load()
975                .map_err(|_| CoreError::LoadAccountError)?
976                .header
977                .updated_at,
978        ))
979    }
980
981    fn oracle_updated_before(&self) -> CoreResult<Option<i64>> {
982        let ts = self
983            .store
984            .load()
985            .map_err(|_| CoreError::LoadAccountError)?
986            .request_expiration_at(
987                self.glv_withdrawal
988                    .load()
989                    .map_err(|_| CoreError::LoadAccountError)?
990                    .header
991                    .updated_at,
992            )?;
993        Ok(Some(ts))
994    }
995
996    fn oracle_updated_after_slot(&self) -> CoreResult<Option<u64>> {
997        Ok(Some(
998            self.glv_withdrawal
999                .load()
1000                .map_err(|_| CoreError::LoadAccountError)?
1001                .header
1002                .updated_at_slot,
1003        ))
1004    }
1005}
1006
1007/// Get total GLV value.
1008///
1009/// # CHECK
1010/// Basically one must make sure that `glv_markets` and
1011/// `glv_market_tokens` are aligned.
1012///
1013/// # Errors
1014///
1015fn unchecked_get_glv_value<'info>(
1016    glv: &Glv,
1017    oracle: &Oracle,
1018    op: &Execute<'_, 'info>,
1019    glv_markets: &'info [AccountInfo<'info>],
1020    glv_market_tokens: &'info [AccountInfo<'info>],
1021    maximize: bool,
1022) -> Result<u128> {
1023    use crate::states::market::AsLiquidityMarket;
1024
1025    let mut value = 0u128;
1026
1027    let current_market = op.market();
1028    let swap_markets = op.swap_markets();
1029
1030    let mut prices = oracle.market_prices(current_market)?;
1031
1032    for (market, market_token) in glv_markets.iter().zip(glv_market_tokens) {
1033        let key = market_token.key();
1034
1035        // Get the current balance of market tokens in the GLV vault.
1036        let balance = u128::from(
1037            glv.market_config(&key)
1038                .ok_or_else(|| error!(CoreError::NotFound))?
1039                .balance(),
1040        );
1041
1042        let value_for_market = if key == current_market.key() {
1043            let market = current_market;
1044            // Note that we should use the balance prior to the operation.
1045            get_glv_value_for_market_with_new_index_price(
1046                oracle,
1047                &mut prices,
1048                market,
1049                balance,
1050                maximize,
1051            )?
1052            .0
1053        } else if let Some(market) = swap_markets.get(&key) {
1054            let mint = Account::<Mint>::try_from(market_token)?;
1055            let market = AsLiquidityMarket::new(market, &mint);
1056            get_glv_value_for_market_with_new_index_price(
1057                oracle,
1058                &mut prices,
1059                &market,
1060                balance,
1061                maximize,
1062            )?
1063            .0
1064        } else {
1065            let market = AccountLoader::<Market>::try_from(market)?;
1066            let mint = Account::<Mint>::try_from(market_token)?;
1067            let market = market.load()?;
1068            let market = market.as_liquidity_market(&mint);
1069            get_glv_value_for_market_with_new_index_price(
1070                oracle,
1071                &mut prices,
1072                &market,
1073                balance,
1074                maximize,
1075            )?
1076            .0
1077        };
1078
1079        value = value
1080            .checked_add(value_for_market)
1081            .ok_or_else(|| error!(CoreError::ValueOverflow))?;
1082    }
1083
1084    Ok(value)
1085}
1086
1087fn get_glv_value_for_market_with_new_index_price<M>(
1088    oracle: &Oracle,
1089    prices: &mut Prices<u128>,
1090    market: &M,
1091    balance: u128,
1092    maximize: bool,
1093) -> Result<(u128, i128, u128)>
1094where
1095    M: gmsol_model::LiquidityMarket<{ constants::MARKET_DECIMALS }, Num = u128, Signed = i128>,
1096    M: HasMarketMeta,
1097{
1098    let index_token_mint = market.market_meta().index_token_mint;
1099    prices.index_token_price = oracle
1100        .get_primary_price(&index_token_mint, true)
1101        .expect("must exist");
1102
1103    get_glv_value_for_market(prices, market, balance, maximize)
1104}
1105
1106fn get_glv_value_for_market<M>(
1107    prices: &Prices<u128>,
1108    market: &M,
1109    balance: u128,
1110    maximize: bool,
1111) -> Result<(u128, i128, u128)>
1112where
1113    M: gmsol_model::LiquidityMarket<{ constants::MARKET_DECIMALS }, Num = u128, Signed = i128>,
1114{
1115    use gmsol_model::{utils, LiquidityMarketExt};
1116
1117    let value = market
1118        .pool_value(prices, PnlFactorKind::MaxAfterDeposit, maximize)
1119        .map_err(ModelError::from)?;
1120
1121    let supply = market.total_supply();
1122
1123    if balance == 0 {
1124        return Ok((0, value, supply));
1125    }
1126
1127    if value.is_negative() {
1128        return err!(CoreError::GlvNegativeMarketPoolValue);
1129    }
1130
1131    let glv_value = utils::market_token_amount_to_usd(&balance, &value.unsigned_abs(), &supply)
1132        .ok_or_else(|| error!(CoreError::FailedToCalculateGlvValueForMarket))?;
1133
1134    Ok((glv_value, value, supply))
1135}
1136
1137fn get_market_token_amount_for_glv_value<M>(
1138    oracle: &Oracle,
1139    market: &M,
1140    glv_value: u128,
1141    maximize: bool,
1142) -> Result<u128>
1143where
1144    M: gmsol_model::LiquidityMarket<{ constants::MARKET_DECIMALS }, Num = u128, Signed = i128>,
1145    M: HasMarketMeta,
1146{
1147    use gmsol_model::{utils, LiquidityMarketExt};
1148
1149    let prices = oracle.market_prices(market).expect("must exist");
1150
1151    let value = market
1152        .pool_value(&prices, PnlFactorKind::MaxAfterWithdrawal, maximize)
1153        .map_err(ModelError::from)?;
1154
1155    if value.is_negative() {
1156        return err!(CoreError::GlvNegativeMarketPoolValue);
1157    }
1158
1159    let supply = market.total_supply();
1160
1161    let market_token_amount = utils::usd_to_market_token_amount(
1162        glv_value,
1163        value.unsigned_abs(),
1164        supply,
1165        constants::MARKET_USD_TO_AMOUNT_DIVISOR,
1166    )
1167    .ok_or_else(|| error!(CoreError::FailedTOCalculateMarketTokenAmountToBurn))?;
1168
1169    Ok(market_token_amount)
1170}
1171
1172/// Operation for executing a GLV withdrawal.
1173#[derive(TypedBuilder)]
1174pub(crate) struct ExecuteGlvShiftOperation<'a, 'info> {
1175    glv_shift: &'a AccountLoader<'info, GlvShift>,
1176    token_program: AccountInfo<'info>,
1177    throw_on_execution_error: bool,
1178    store: &'a AccountLoader<'info, Store>,
1179    glv: &'a AccountLoader<'info, Glv>,
1180    from_market: &'a AccountLoader<'info, Market>,
1181    from_market_token_mint: &'a mut Account<'info, Mint>,
1182    from_market_token_glv_vault: &'a Account<'info, TokenAccount>,
1183    from_market_token_withdrawal_vault: AccountInfo<'info>,
1184    to_market: &'a AccountLoader<'info, Market>,
1185    to_market_token_mint: &'a mut Account<'info, Mint>,
1186    to_market_token_glv_vault: AccountInfo<'info>,
1187    oracle: &'a Oracle,
1188    #[builder(setter(into))]
1189    event_emitter: EventEmitter<'a, 'info>,
1190}
1191
1192impl ExecuteGlvShiftOperation<'_, '_> {
1193    /// Execute.
1194    ///
1195    /// # CHECK
1196    ///
1197    /// # Errors
1198    ///
1199    pub(crate) fn unchecked_execute(mut self) -> Result<bool> {
1200        let throw_on_execution_error = self.throw_on_execution_error;
1201        match self.validate_oracle() {
1202            Ok(()) => {}
1203            Err(CoreError::OracleTimestampsAreLargerThanRequired) if !throw_on_execution_error => {
1204                msg!(
1205                    "GLV Shift expired at {}",
1206                    self.oracle_updated_before()
1207                        .ok()
1208                        .flatten()
1209                        .expect("must have an expiration time"),
1210                );
1211                return Ok(false);
1212            }
1213            Err(err) => {
1214                return Err(error!(err));
1215            }
1216        }
1217
1218        let executed = match self.perform_glv_shift() {
1219            Ok(()) => true,
1220            Err(err) if !throw_on_execution_error => {
1221                msg!("Execute GLV shift error: {}", err);
1222                false
1223            }
1224            Err(err) => return Err(err),
1225        };
1226
1227        self.validate_after_execution()?;
1228
1229        Ok(executed)
1230    }
1231
1232    fn validate_oracle(&self) -> CoreResult<()> {
1233        self.oracle.validate_time(self)
1234    }
1235
1236    fn validate_before_execution(&self) -> Result<()> {
1237        self.glv.load()?.validate_shift_interval()?;
1238
1239        require!(
1240            self.from_market.key() != self.to_market.key(),
1241            CoreError::Internal
1242        );
1243
1244        let from_market = self.from_market.load()?;
1245        let to_market = self.to_market.load()?;
1246
1247        from_market.validate(&self.store.key())?;
1248        to_market.validate(&self.store.key())?;
1249
1250        from_market.validate_shiftable(&to_market)?;
1251
1252        let shift = self.glv_shift.load()?;
1253
1254        // Validate the vault has enough from market tokens.
1255        let amount = Borrow::<Shift>::borrow(&*shift)
1256            .params
1257            .from_market_token_amount;
1258        require_gte!(
1259            self.from_market_token_glv_vault.amount,
1260            amount,
1261            CoreError::NotEnoughTokenAmount
1262        );
1263
1264        Ok(())
1265    }
1266
1267    fn validate_after_execution(&self) -> Result<()> {
1268        use anchor_spl::token::accessor::amount;
1269
1270        let glv = self.glv.load()?;
1271
1272        let from_market_token = self.from_market_token_mint.key();
1273        let from_market_token_vault_balance =
1274            amount(&self.from_market_token_glv_vault.to_account_info())?;
1275        require_gte!(
1276            from_market_token_vault_balance,
1277            glv.market_config(&from_market_token)
1278                .ok_or_else(|| error!(CoreError::NotFound))?
1279                .balance(),
1280            CoreError::Internal
1281        );
1282
1283        let to_market_token = self.to_market_token_mint.key();
1284        let to_market_token_vault_balance =
1285            amount(&self.to_market_token_glv_vault.to_account_info())?;
1286        require_gte!(
1287            to_market_token_vault_balance,
1288            glv.market_config(&to_market_token)
1289                .ok_or_else(|| error!(CoreError::NotFound))?
1290                .balance(),
1291            CoreError::Internal
1292        );
1293
1294        Ok(())
1295    }
1296
1297    #[inline(never)]
1298    fn perform_glv_shift(&mut self) -> Result<()> {
1299        self.validate_before_execution()?;
1300
1301        let from_market_token_address = self.from_market_token_mint.key();
1302        let to_market_token_address = self.to_market_token_mint.key();
1303
1304        let from_market_token_mint = self.from_market_token_mint.to_account_info();
1305        let from_market_token_decimals = self.from_market_token_mint.decimals;
1306        let glv_shift = self.glv_shift.load()?;
1307        let shift = Borrow::<Shift>::borrow(&*glv_shift);
1308
1309        let mut from_market = RevertibleLiquidityMarketOperation::new(
1310            self.store,
1311            self.oracle,
1312            self.from_market,
1313            self.from_market_token_mint,
1314            self.token_program.clone(),
1315            None,
1316            &[],
1317            self.event_emitter,
1318        )?;
1319
1320        let mut to_market = RevertibleLiquidityMarketOperation::new(
1321            self.store,
1322            self.oracle,
1323            self.to_market,
1324            self.to_market_token_mint,
1325            self.token_program.clone(),
1326            None,
1327            &[],
1328            self.event_emitter,
1329        )?;
1330
1331        let from_market = from_market.op()?;
1332        let to_market = to_market.op()?;
1333
1334        let (from_market, to_market, received) = from_market.unchecked_shift(
1335            to_market,
1336            &shift.header().receiver(),
1337            &shift.params,
1338            &self.from_market_token_withdrawal_vault,
1339            &self.to_market_token_glv_vault,
1340        )?;
1341
1342        // Validate to market token balance.
1343        let next_to_market_token_balance = {
1344            let (_, market_pool_value, market_token_supply) = {
1345                let mut prices = self.oracle.market_prices(to_market.market())?;
1346                get_glv_value_for_market_with_new_index_price(
1347                    self.oracle,
1348                    &mut prices,
1349                    to_market.market(),
1350                    0,
1351                    true,
1352                )?
1353            };
1354            let current_balance = self
1355                .glv
1356                .load()?
1357                .market_config(&to_market_token_address)
1358                .ok_or_else(|| error!(CoreError::NotFound))?
1359                .balance();
1360            let new_balance = current_balance
1361                .checked_add(received)
1362                .ok_or_else(|| error!(CoreError::TokenAmountOverflow))?;
1363            self.glv.load()?.validate_market_token_balance(
1364                &to_market.market().market_meta().market_token_mint,
1365                new_balance,
1366                &market_pool_value,
1367                &market_token_supply,
1368            )?;
1369
1370            new_balance
1371        };
1372
1373        // Validate max price impact and min shift value.
1374        {
1375            let mut prices = self.oracle.market_prices(from_market.market())?;
1376
1377            let (from_market_token_value, _, _) = get_glv_value_for_market_with_new_index_price(
1378                self.oracle,
1379                &mut prices,
1380                from_market.market(),
1381                shift.params.from_market_token_amount().into(),
1382                true,
1383            )?;
1384
1385            self.glv
1386                .load()?
1387                .validate_shift_value(from_market_token_value)?;
1388
1389            let (to_market_token_value, _, _) = get_glv_value_for_market_with_new_index_price(
1390                self.oracle,
1391                &mut prices,
1392                to_market.market(),
1393                received.into(),
1394                true,
1395            )?;
1396
1397            self.glv
1398                .load()?
1399                .validate_shift_price_impact(from_market_token_value, to_market_token_value)?;
1400        }
1401
1402        // Transfer market tokens from the GLV vault to the withdrawal vault before the commitment.
1403        let next_from_market_token_balance = {
1404            let glv = self.glv.load()?;
1405            let seeds = glv.signer_seeds();
1406
1407            let amount = shift.params.from_market_token_amount;
1408            let next_from_market_token_balance = glv
1409                .market_config(&from_market_token_address)
1410                .ok_or_else(|| error!(CoreError::NotFound))?
1411                .balance()
1412                .checked_sub(amount)
1413                .ok_or_else(|| error!(CoreError::NotEnoughTokenAmount))?;
1414
1415            transfer_checked(
1416                CpiContext::new(
1417                    self.token_program.to_account_info(),
1418                    TransferChecked {
1419                        from: self.from_market_token_glv_vault.to_account_info(),
1420                        mint: from_market_token_mint,
1421                        to: self.from_market_token_withdrawal_vault.to_account_info(),
1422                        authority: self.glv.to_account_info(),
1423                    },
1424                )
1425                .with_signer(&[&seeds]),
1426                amount,
1427                from_market_token_decimals,
1428            )
1429            .expect("failed to transfer from market tokens");
1430
1431            next_from_market_token_balance
1432        };
1433
1434        // Commit the changes.
1435        from_market.commit();
1436        to_market.commit();
1437
1438        // Invertible operations after the commitment.
1439        {
1440            let mut glv = self.glv.load_mut().expect("must success");
1441            glv.update_shift_last_executed_ts()
1442                .expect("failed to update shift last executed ts");
1443            glv.update_market_token_balance(
1444                &from_market_token_address,
1445                next_from_market_token_balance,
1446            )
1447            .expect("failed to update from market token balance");
1448            glv.update_market_token_balance(&to_market_token_address, next_to_market_token_balance)
1449                .expect("failed to update from market token balance");
1450        }
1451
1452        Ok(())
1453    }
1454}
1455
1456impl ValidateOracleTime for ExecuteGlvShiftOperation<'_, '_> {
1457    fn oracle_updated_after(&self) -> CoreResult<Option<i64>> {
1458        Ok(Some(
1459            self.glv_shift
1460                .load()
1461                .map_err(|_| CoreError::LoadAccountError)?
1462                .header()
1463                .updated_at,
1464        ))
1465    }
1466
1467    fn oracle_updated_before(&self) -> CoreResult<Option<i64>> {
1468        let ts = self
1469            .store
1470            .load()
1471            .map_err(|_| CoreError::LoadAccountError)?
1472            .request_expiration_at(
1473                self.glv_shift
1474                    .load()
1475                    .map_err(|_| CoreError::LoadAccountError)?
1476                    .header()
1477                    .updated_at,
1478            )?;
1479        Ok(Some(ts))
1480    }
1481
1482    fn oracle_updated_after_slot(&self) -> CoreResult<Option<u64>> {
1483        Ok(Some(
1484            self.glv_shift
1485                .load()
1486                .map_err(|_| CoreError::LoadAccountError)?
1487                .header()
1488                .updated_at_slot,
1489        ))
1490    }
1491}