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#[derive(Accounts)]
29#[instruction(nonce: [u8; 32])]
30pub struct CreateGlvShift<'info> {
31 #[account(mut)]
33 pub authority: Signer<'info>,
34 pub store: AccountLoader<'info, Store>,
36 #[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 #[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 #[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 #[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 #[account(
69 constraint = from_market_token.key() != to_market_token.key() @ CoreError::InvalidShiftMarkets,
70 )]
71 pub from_market_token: Box<Account<'info, Mint>>,
72 pub to_market_token: Box<Account<'info, Mint>>,
74 #[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 #[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 pub system_program: Program<'info, System>,
88 pub token_program: Program<'info, Token>,
90 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 {
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#[event_cpi]
175#[derive(Accounts)]
176pub struct CloseGlvShift<'info> {
177 #[account(mut)]
179 pub authority: Signer<'info>,
180 #[account(mut)]
183 pub funder: UncheckedAccount<'info>,
184 pub store: AccountLoader<'info, Store>,
186 #[account(mut, seeds = [Store::WALLET_SEED, store.key().as_ref()], bump)]
188 pub store_wallet: SystemAccount<'info>,
189 #[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 #[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 constraint = glv_shift.load()?.header().rent_receiver() == funder.key @ CoreError::RentReceiverMismatched,
203 )]
204 pub glv_shift: AccountLoader<'info, GlvShift>,
205 #[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 #[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 pub system_program: Program<'info, System>,
217 pub token_program: Program<'info, Token>,
219 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 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#[event_cpi]
294#[derive(Accounts)]
295pub struct ExecuteGlvShift<'info> {
296 pub authority: Signer<'info>,
298 #[account(has_one = token_map)]
300 pub store: AccountLoader<'info, Store>,
301 #[account(has_one = store)]
303 pub token_map: AccountLoader<'info, TokenMapHeader>,
304 #[account(mut, has_one = store)]
306 pub oracle: AccountLoader<'info, Oracle>,
307 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 pub token_program: Program<'info, Token>,
378 pub chainlink_program: Option<Program<'info, Chainlink>>,
380}
381
382pub 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 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 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}