gmsol_store/instructions/glv/
shift.rs

1use anchor_lang::prelude::*;
2use anchor_spl::{
3    associated_token::AssociatedToken,
4    token::{Mint, Token, TokenAccount},
5};
6use gmsol_utils::{market::ordered_tokens, InitSpace};
7
8use crate::{
9    constants,
10    events::EventEmitter,
11    ops::{
12        execution_fee::PayExecutionFeeOperation,
13        glv::ExecuteGlvShiftOperation,
14        shift::{CreateShiftOperation, CreateShiftParams},
15    },
16    states::{
17        common::action::{Action, ActionExt},
18        feature::{ActionDisabledFlag, DomainDisabledFlag},
19        glv::{GlvMarketFlag, GlvShift},
20        Chainlink, Glv, Market, NonceBytes, Oracle, RoleKey, Seed, Store, StoreWalletSigner,
21        TokenMapHeader,
22    },
23    utils::internal,
24    CoreError,
25};
26
27/// The accounts definition for [`create_glv_shift`](crate::create_glv_shift) instruction.
28#[derive(Accounts)]
29#[instruction(nonce: [u8; 32])]
30pub struct CreateGlvShift<'info> {
31    /// Authority.
32    #[account(mut)]
33    pub authority: Signer<'info>,
34    /// Store.
35    pub store: AccountLoader<'info, Store>,
36    /// GLV.
37    #[account(
38        mut,
39        has_one = store,
40        constraint = glv.load()?.contains(&from_market_token.key()) @ CoreError::InvalidArgument,
41        constraint = glv.load()?.contains(&to_market_token.key()) @ CoreError::InvalidArgument,
42    )]
43    pub glv: AccountLoader<'info, Glv>,
44    /// From market.
45    #[account(
46        mut,
47        has_one = store,
48        constraint = from_market.load()?.meta().market_token_mint == from_market_token.key() @ CoreError::MarketTokenMintMismatched,
49    )]
50    pub from_market: AccountLoader<'info, Market>,
51    /// To market.
52    #[account(
53        mut,
54        has_one = store,
55        constraint = to_market.load()?.meta().market_token_mint == to_market_token.key() @ CoreError::MarketTokenMintMismatched,
56    )]
57    pub to_market: AccountLoader<'info, Market>,
58    /// GLV shift.
59    #[account(
60        init,
61        payer = authority,
62        space = 8 + GlvShift::INIT_SPACE,
63        seeds = [GlvShift::SEED, store.key().as_ref(), authority.key().as_ref(), &nonce],
64        bump,
65    )]
66    pub glv_shift: AccountLoader<'info, GlvShift>,
67    /// From market token.
68    #[account(
69        constraint = from_market_token.key() != to_market_token.key() @ CoreError::InvalidShiftMarkets,
70    )]
71    pub from_market_token: Box<Account<'info, Mint>>,
72    /// To market token.
73    pub to_market_token: Box<Account<'info, Mint>>,
74    /// Vault for from market tokens.
75    #[account(
76        associated_token::mint = from_market_token,
77        associated_token::authority = glv,
78    )]
79    pub from_market_token_vault: Box<Account<'info, TokenAccount>>,
80    /// Vault for to market tokens.
81    #[account(
82        associated_token::mint = to_market_token,
83        associated_token::authority = glv,
84    )]
85    pub to_market_token_vault: Box<Account<'info, TokenAccount>>,
86    /// The system program.
87    pub system_program: Program<'info, System>,
88    /// The token program.
89    pub token_program: Program<'info, Token>,
90    /// The associated token program.
91    pub associated_token_program: Program<'info, AssociatedToken>,
92}
93
94impl<'info> internal::Create<'info, GlvShift> for CreateGlvShift<'info> {
95    type CreateParams = CreateShiftParams;
96
97    fn action(&self) -> AccountInfo<'info> {
98        self.glv_shift.to_account_info()
99    }
100
101    fn payer(&self) -> AccountInfo<'info> {
102        self.authority.to_account_info()
103    }
104
105    fn payer_seeds(&self) -> Result<Option<Vec<Vec<u8>>>> {
106        Ok(Some(self.glv.load()?.vec_signer_seeds()))
107    }
108
109    fn system_program(&self) -> AccountInfo<'info> {
110        self.system_program.to_account_info()
111    }
112
113    fn validate(&self, _params: &Self::CreateParams) -> Result<()> {
114        self.store
115            .load()?
116            .validate_not_restarted()?
117            .validate_feature_enabled(DomainDisabledFlag::GlvShift, ActionDisabledFlag::Create)?;
118        let glv = self.glv.load()?;
119        let market_token = self.to_market_token.key();
120        let is_deposit_allowed = glv
121            .market_config(&market_token)
122            .ok_or_else(|| error!(CoreError::Internal))?
123            .get_flag(GlvMarketFlag::IsDepositAllowed);
124        require!(is_deposit_allowed, CoreError::GlvDepositIsNotAllowed);
125        glv.validate_shift_interval()
126    }
127
128    fn create_impl(
129        &mut self,
130        params: &Self::CreateParams,
131        nonce: &NonceBytes,
132        bumps: &Self::Bumps,
133        _remaining_accounts: &'info [AccountInfo<'info>],
134    ) -> Result<()> {
135        CreateShiftOperation::builder()
136            .store(&self.store)
137            .owner(self.glv.as_ref())
138            .receiver(self.glv.as_ref())
139            .shift(&self.glv_shift)
140            .from_market(&self.from_market)
141            .from_market_token_account(&self.from_market_token_vault)
142            .to_market(&self.to_market)
143            .to_market_token_account(&self.to_market_token_vault)
144            .nonce(nonce)
145            .bump(bumps.glv_shift)
146            .params(params)
147            .build()
148            .execute()?;
149
150        // Set the funder of the GLV shift.
151        {
152            self.glv_shift.exit(&crate::ID)?;
153            self.glv_shift
154                .load_mut()?
155                .header_mut()
156                .set_rent_receiver(self.authority.key());
157        }
158
159        Ok(())
160    }
161}
162
163impl<'info> internal::Authentication<'info> for CreateGlvShift<'info> {
164    fn authority(&self) -> &Signer<'info> {
165        &self.authority
166    }
167
168    fn store(&self) -> &AccountLoader<'info, Store> {
169        &self.store
170    }
171}
172
173/// The accounts definition for [`close_glv_shift`](crate::close_glv_shift) instruction.
174#[event_cpi]
175#[derive(Accounts)]
176pub struct CloseGlvShift<'info> {
177    /// Authority.
178    #[account(mut)]
179    pub authority: Signer<'info>,
180    /// Funder of the GLV shift.
181    /// CHECK: only used to receive funds.
182    #[account(mut)]
183    pub funder: UncheckedAccount<'info>,
184    /// The store.
185    pub store: AccountLoader<'info, Store>,
186    /// The store wallet.
187    #[account(mut, seeds = [Store::WALLET_SEED, store.key().as_ref()], bump)]
188    pub store_wallet: SystemAccount<'info>,
189    /// GLV.
190    #[account(
191        has_one = store,
192        constraint = glv.load()?.contains(&from_market_token.key()) @ CoreError::InvalidArgument,
193        constraint = glv.load()?.contains(&to_market_token.key()) @ CoreError::InvalidArgument,
194    )]
195    pub glv: AccountLoader<'info, Glv>,
196    /// The GLV shift to close.
197    #[account(
198        mut,
199        constraint = glv_shift.load()?.header().owner == glv.key() @ CoreError::OwnerMismatched,
200        constraint = glv_shift.load()?.header().store == store.key() @ CoreError::StoreMismatched,
201        // The rent receiver of a GLV shift must be the funder.
202        constraint = glv_shift.load()?.header().rent_receiver() == funder.key @ CoreError::RentReceiverMismatched,
203    )]
204    pub glv_shift: AccountLoader<'info, GlvShift>,
205    /// From Market token.
206    #[account(
207        constraint = glv_shift.load()?.tokens().from_market_token() == from_market_token.key() @ CoreError::MarketTokenMintMismatched
208    )]
209    pub from_market_token: Box<Account<'info, Mint>>,
210    /// To Market token.
211    #[account(
212        constraint = glv_shift.load()?.tokens().to_market_token() == to_market_token.key() @ CoreError::MarketTokenMintMismatched
213    )]
214    pub to_market_token: Box<Account<'info, Mint>>,
215    /// The system program.
216    pub system_program: Program<'info, System>,
217    /// The token program.
218    pub token_program: Program<'info, Token>,
219    /// The associated token program.
220    pub associated_token_program: Program<'info, AssociatedToken>,
221}
222
223impl<'info> internal::Close<'info, GlvShift> for CloseGlvShift<'info> {
224    fn expected_keeper_role(&self) -> &str {
225        RoleKey::ORDER_KEEPER
226    }
227
228    fn rent_receiver(&self) -> AccountInfo<'info> {
229        debug_assert!(
230            self.glv_shift.load().unwrap().header().rent_receiver() == self.funder.key,
231            "The rent receiver must have been checked to be the owner"
232        );
233        self.funder.to_account_info()
234    }
235
236    fn skip_completion_check_for_keeper(&self) -> Result<bool> {
237        // Allow the funder to close the GLV shift even if it has not reached a final state.
238        Ok(*self.glv_shift.load()?.funder() == self.authority.key())
239    }
240
241    fn store_wallet_bump(&self, bumps: &Self::Bumps) -> u8 {
242        bumps.store_wallet
243    }
244
245    fn validate(&self) -> Result<()> {
246        let glv_shift = self.glv_shift.load()?;
247        if glv_shift.header().action_state()?.is_pending() {
248            self.store
249                .load()?
250                .validate_not_restarted()?
251                .validate_feature_enabled(
252                    DomainDisabledFlag::GlvShift,
253                    ActionDisabledFlag::Cancel,
254                )?;
255        }
256        Ok(())
257    }
258
259    fn process(
260        &self,
261        _init_if_needed: bool,
262        _store_wallet_signer: &StoreWalletSigner,
263        _event_emitter: &EventEmitter<'_, 'info>,
264    ) -> Result<internal::Success> {
265        Ok(true)
266    }
267
268    fn event_authority(&self, bumps: &Self::Bumps) -> (AccountInfo<'info>, u8) {
269        (self.event_authority.clone(), bumps.event_authority)
270    }
271
272    fn action(&self) -> &AccountLoader<'info, GlvShift> {
273        &self.glv_shift
274    }
275}
276
277impl<'info> internal::Authentication<'info> for CloseGlvShift<'info> {
278    fn authority(&self) -> &Signer<'info> {
279        &self.authority
280    }
281
282    fn store(&self) -> &AccountLoader<'info, Store> {
283        &self.store
284    }
285}
286
287/// The accounts definition for [`execute_glv_shift`](crate::execute_glv_shift) instruction.
288///
289/// Remaining accounts expected by this instruction:
290///
291///   - 0..M. `[]` M feed accounts, where M represents the total number of unique tokens
292///     of markets.
293#[event_cpi]
294#[derive(Accounts)]
295pub struct ExecuteGlvShift<'info> {
296    /// Authority.
297    pub authority: Signer<'info>,
298    /// Store.
299    #[account(has_one = token_map)]
300    pub store: AccountLoader<'info, Store>,
301    /// Token Map.
302    #[account(has_one = store)]
303    pub token_map: AccountLoader<'info, TokenMapHeader>,
304    /// Oracle buffer to use.
305    #[account(mut, has_one = store)]
306    pub oracle: AccountLoader<'info, Oracle>,
307    /// GLV account.
308    #[account(
309        mut,
310        has_one = store,
311        constraint = glv.load()?.contains(&from_market_token.key()) @ CoreError::InvalidArgument,
312        constraint = glv.load()?.contains(&to_market_token.key()) @ CoreError::InvalidArgument,
313    )]
314    pub glv: AccountLoader<'info, Glv>,
315    /// From Market.
316    #[account(
317        mut,
318        has_one = store,
319        constraint = from_market.load()?.meta().market_token_mint == from_market_token.key() @ CoreError::MarketTokenMintMismatched,
320    )]
321    pub from_market: AccountLoader<'info, Market>,
322    /// To Market.
323    #[account(
324        mut,
325        has_one = store,
326        constraint = to_market.load()?.meta().market_token_mint == to_market_token.key() @ CoreError::MarketTokenMintMismatched,
327    )]
328    pub to_market: AccountLoader<'info, Market>,
329    /// The GLV shift to close.
330    #[account(
331        mut,
332        constraint = glv_shift.load()?.header().owner == glv.key() @ CoreError::OwnerMismatched,
333        constraint = glv_shift.load()?.header().store == store.key() @ CoreError::StoreMismatched,
334        constraint = glv_shift.load()?.tokens().from_market_token_account() == from_market_token_glv_vault.key() @ CoreError::MarketTokenAccountMismatched,
335        constraint = glv_shift.load()?.tokens().to_market_token_account() == to_market_token_glv_vault.key() @ CoreError::MarketTokenAccountMismatched,
336    )]
337    pub glv_shift: AccountLoader<'info, GlvShift>,
338    /// From Market token.
339    #[account(
340        mut,
341        constraint = glv_shift.load()?.tokens().from_market_token() == from_market_token.key() @ CoreError::MarketTokenMintMismatched
342    )]
343    pub from_market_token: Box<Account<'info, Mint>>,
344    /// To Market token.
345    #[account(
346        mut,
347        constraint = glv_shift.load()?.tokens().to_market_token() == to_market_token.key() @ CoreError::MarketTokenMintMismatched
348    )]
349    pub to_market_token: Box<Account<'info, Mint>>,
350    /// The escrow account for from market tokens.
351    #[account(
352        mut,
353        associated_token::mint = from_market_token,
354        associated_token::authority = glv,
355    )]
356    pub from_market_token_glv_vault: Box<Account<'info, TokenAccount>>,
357    /// The escrow account for to market tokens.
358    #[account(
359        mut,
360        associated_token::mint = to_market_token,
361        associated_token::authority = glv,
362    )]
363    pub to_market_token_glv_vault: Box<Account<'info, TokenAccount>>,
364    /// From market token vault.
365    #[account(
366        mut,
367        token::mint = from_market_token,
368        seeds = [
369            constants::MARKET_VAULT_SEED,
370            store.key().as_ref(),
371            from_market_token_vault.mint.as_ref(),
372        ],
373        bump,
374    )]
375    pub from_market_token_vault: Box<Account<'info, TokenAccount>>,
376    /// The token program.
377    pub token_program: Program<'info, Token>,
378    /// Chainlink Program.
379    pub chainlink_program: Option<Program<'info, Chainlink>>,
380}
381
382/// Execute GLV shift.
383///
384/// # CHECK
385/// - Only ORDER_KEEPER is allowed to execute shift.
386pub fn unchecked_execute_glv_shift<'info>(
387    ctx: Context<'_, '_, 'info, 'info, ExecuteGlvShift<'info>>,
388    execution_lamports: u64,
389    throw_on_execution_error: bool,
390) -> Result<()> {
391    let accounts = ctx.accounts;
392    let remaining_accounts = ctx.remaining_accounts;
393
394    // Validate feature enabled.
395    accounts
396        .store
397        .load()?
398        .validate_feature_enabled(DomainDisabledFlag::GlvShift, ActionDisabledFlag::Execute)?;
399
400    let executed = accounts.perform_execution(
401        remaining_accounts,
402        throw_on_execution_error,
403        ctx.bumps.event_authority,
404    )?;
405
406    if executed {
407        accounts.glv_shift.load_mut()?.header_mut().completed()?;
408    } else {
409        accounts.glv_shift.load_mut()?.header_mut().cancelled()?;
410    }
411
412    // It must be placed at the end to be executed correctly.
413    accounts.pay_execution_fee(execution_lamports)?;
414
415    Ok(())
416}
417
418impl<'info> internal::Authentication<'info> for ExecuteGlvShift<'info> {
419    fn authority(&self) -> &Signer<'info> {
420        &self.authority
421    }
422
423    fn store(&self) -> &AccountLoader<'info, Store> {
424        &self.store
425    }
426}
427
428impl<'info> ExecuteGlvShift<'info> {
429    #[inline(never)]
430    fn pay_execution_fee(&self, execution_fee: u64) -> Result<()> {
431        let execution_lamports = self.glv_shift.load()?.execution_lamports(execution_fee);
432        PayExecutionFeeOperation::builder()
433            .payer(self.glv_shift.to_account_info())
434            .receiver(self.authority.to_account_info())
435            .execution_lamports(execution_lamports)
436            .build()
437            .execute()?;
438        Ok(())
439    }
440
441    #[inline(never)]
442    fn ordered_tokens(&self) -> Result<Vec<Pubkey>> {
443        let from = *self.from_market.load()?.meta();
444        let to = *self.to_market.load()?.meta();
445
446        Ok(ordered_tokens(&from, &to).into_iter().collect())
447    }
448
449    fn perform_execution(
450        &mut self,
451        remaining_accounts: &'info [AccountInfo<'info>],
452        throw_on_execution_error: bool,
453        event_authority_bump: u8,
454    ) -> Result<bool> {
455        let tokens = self.ordered_tokens()?;
456
457        let builder = ExecuteGlvShiftOperation::builder()
458            .glv_shift(&self.glv_shift)
459            .token_program(self.token_program.to_account_info())
460            .throw_on_execution_error(throw_on_execution_error)
461            .store(&self.store)
462            .glv(&self.glv)
463            .from_market(&self.from_market)
464            .from_market_token_mint(&mut self.from_market_token)
465            .from_market_token_glv_vault(&self.from_market_token_glv_vault)
466            .from_market_token_withdrawal_vault(self.from_market_token_vault.to_account_info())
467            .to_market(&self.to_market)
468            .to_market_token_mint(&mut self.to_market_token)
469            .to_market_token_glv_vault(self.to_market_token_glv_vault.to_account_info())
470            .event_emitter((&self.event_authority, event_authority_bump));
471
472        self.oracle.load_mut()?.with_prices(
473            &self.store,
474            &self.token_map,
475            &tokens,
476            remaining_accounts,
477            self.chainlink_program.as_ref(),
478            |oracle, _remaining_accounts| builder.oracle(oracle).build().unchecked_execute(),
479        )
480    }
481}