use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::AssociatedToken,
token::{Mint, Token, TokenAccount},
};
use gmsol_utils::{market::ordered_tokens, InitSpace};
use crate::{
constants,
events::EventEmitter,
ops::{
execution_fee::PayExecutionFeeOperation,
glv::ExecuteGlvShiftOperation,
shift::{CreateShiftOperation, CreateShiftParams},
},
states::{
common::action::{Action, ActionExt},
feature::{ActionDisabledFlag, DomainDisabledFlag},
glv::{GlvMarketFlag, GlvShift},
Chainlink, Glv, Market, NonceBytes, Oracle, RoleKey, Seed, Store, StoreWalletSigner,
TokenMapHeader,
},
utils::internal,
CoreError,
};
#[derive(Accounts)]
#[instruction(nonce: [u8; 32])]
pub struct CreateGlvShift<'info> {
#[account(mut)]
pub authority: Signer<'info>,
pub store: AccountLoader<'info, Store>,
#[account(
mut,
has_one = store,
constraint = glv.load()?.contains(&from_market_token.key()) @ CoreError::InvalidArgument,
constraint = glv.load()?.contains(&to_market_token.key()) @ CoreError::InvalidArgument,
)]
pub glv: AccountLoader<'info, Glv>,
#[account(
mut,
has_one = store,
constraint = from_market.load()?.meta().market_token_mint == from_market_token.key() @ CoreError::MarketTokenMintMismatched,
)]
pub from_market: AccountLoader<'info, Market>,
#[account(
mut,
has_one = store,
constraint = to_market.load()?.meta().market_token_mint == to_market_token.key() @ CoreError::MarketTokenMintMismatched,
)]
pub to_market: AccountLoader<'info, Market>,
#[account(
init,
payer = authority,
space = 8 + GlvShift::INIT_SPACE,
seeds = [GlvShift::SEED, store.key().as_ref(), authority.key().as_ref(), &nonce],
bump,
)]
pub glv_shift: AccountLoader<'info, GlvShift>,
#[account(
constraint = from_market_token.key() != to_market_token.key() @ CoreError::InvalidShiftMarkets,
)]
pub from_market_token: Box<Account<'info, Mint>>,
pub to_market_token: Box<Account<'info, Mint>>,
#[account(
associated_token::mint = from_market_token,
associated_token::authority = glv,
)]
pub from_market_token_vault: Box<Account<'info, TokenAccount>>,
#[account(
associated_token::mint = to_market_token,
associated_token::authority = glv,
)]
pub to_market_token_vault: Box<Account<'info, TokenAccount>>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
}
impl<'info> internal::Create<'info, GlvShift> for CreateGlvShift<'info> {
type CreateParams = CreateShiftParams;
fn action(&self) -> AccountInfo<'info> {
self.glv_shift.to_account_info()
}
fn payer(&self) -> AccountInfo<'info> {
self.authority.to_account_info()
}
fn payer_seeds(&self) -> Result<Option<Vec<Vec<u8>>>> {
Ok(Some(self.glv.load()?.vec_signer_seeds()))
}
fn system_program(&self) -> AccountInfo<'info> {
self.system_program.to_account_info()
}
fn validate(&self, _params: &Self::CreateParams) -> Result<()> {
self.store
.load()?
.validate_not_restarted()?
.validate_feature_enabled(DomainDisabledFlag::GlvShift, ActionDisabledFlag::Create)?;
let glv = self.glv.load()?;
let market_token = self.to_market_token.key();
let is_deposit_allowed = glv
.market_config(&market_token)
.ok_or_else(|| error!(CoreError::Internal))?
.get_flag(GlvMarketFlag::IsDepositAllowed);
require!(is_deposit_allowed, CoreError::GlvDepositIsNotAllowed);
glv.validate_shift_interval()
}
fn create_impl(
&mut self,
params: &Self::CreateParams,
nonce: &NonceBytes,
bumps: &Self::Bumps,
_remaining_accounts: &'info [AccountInfo<'info>],
callback_version: Option<u8>,
) -> Result<()> {
require_eq!(callback_version.is_none(), true, {
msg!("[Callback] callback is not supported by this instruction.");
CoreError::InvalidArgument
});
CreateShiftOperation::builder()
.store(&self.store)
.owner(self.glv.as_ref())
.receiver(self.glv.as_ref())
.shift(&self.glv_shift)
.from_market(&self.from_market)
.from_market_token_account(&self.from_market_token_vault)
.to_market(&self.to_market)
.to_market_token_account(&self.to_market_token_vault)
.nonce(nonce)
.bump(bumps.glv_shift)
.params(params)
.build()
.execute()?;
{
self.glv_shift.exit(&crate::ID)?;
self.glv_shift
.load_mut()?
.header_mut()
.set_rent_receiver(self.authority.key());
}
Ok(())
}
}
impl<'info> internal::Authentication<'info> for CreateGlvShift<'info> {
fn authority(&self) -> &Signer<'info> {
&self.authority
}
fn store(&self) -> &AccountLoader<'info, Store> {
&self.store
}
}
#[event_cpi]
#[derive(Accounts)]
pub struct CloseGlvShift<'info> {
#[account(mut)]
pub authority: Signer<'info>,
#[account(mut)]
pub funder: UncheckedAccount<'info>,
pub store: AccountLoader<'info, Store>,
#[account(mut, seeds = [Store::WALLET_SEED, store.key().as_ref()], bump)]
pub store_wallet: SystemAccount<'info>,
#[account(
has_one = store,
constraint = glv.load()?.contains(&from_market_token.key()) @ CoreError::InvalidArgument,
constraint = glv.load()?.contains(&to_market_token.key()) @ CoreError::InvalidArgument,
)]
pub glv: AccountLoader<'info, Glv>,
#[account(
mut,
constraint = glv_shift.load()?.header().owner == glv.key() @ CoreError::OwnerMismatched,
constraint = glv_shift.load()?.header().store == store.key() @ CoreError::StoreMismatched,
// The rent receiver of a GLV shift must be the funder.
constraint = glv_shift.load()?.header().rent_receiver() == funder.key @ CoreError::RentReceiverMismatched,
)]
pub glv_shift: AccountLoader<'info, GlvShift>,
#[account(
constraint = glv_shift.load()?.tokens().from_market_token() == from_market_token.key() @ CoreError::MarketTokenMintMismatched
)]
pub from_market_token: Box<Account<'info, Mint>>,
#[account(
constraint = glv_shift.load()?.tokens().to_market_token() == to_market_token.key() @ CoreError::MarketTokenMintMismatched
)]
pub to_market_token: Box<Account<'info, Mint>>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
}
impl<'info> internal::Close<'info, GlvShift> for CloseGlvShift<'info> {
fn expected_keeper_role(&self) -> &str {
RoleKey::ORDER_KEEPER
}
fn rent_receiver(&self) -> AccountInfo<'info> {
debug_assert!(
self.glv_shift.load().unwrap().header().rent_receiver() == self.funder.key,
"The rent receiver must have been checked to be the owner"
);
self.funder.to_account_info()
}
fn skip_completion_check_for_keeper(&self) -> Result<bool> {
Ok(*self.glv_shift.load()?.funder() == self.authority.key())
}
fn store_wallet_bump(&self, bumps: &Self::Bumps) -> u8 {
bumps.store_wallet
}
fn validate(&self) -> Result<()> {
let glv_shift = self.glv_shift.load()?;
if glv_shift.header().action_state()?.is_pending() {
self.store
.load()?
.validate_not_restarted()?
.validate_feature_enabled(
DomainDisabledFlag::GlvShift,
ActionDisabledFlag::Cancel,
)?;
}
Ok(())
}
fn process(
&self,
_init_if_needed: bool,
_store_wallet_signer: &StoreWalletSigner,
_event_emitter: &EventEmitter<'_, 'info>,
) -> Result<internal::Success> {
Ok(true)
}
fn event_authority(&self, bumps: &Self::Bumps) -> (AccountInfo<'info>, u8) {
(self.event_authority.clone(), bumps.event_authority)
}
fn action(&self) -> &AccountLoader<'info, GlvShift> {
&self.glv_shift
}
}
impl<'info> internal::Authentication<'info> for CloseGlvShift<'info> {
fn authority(&self) -> &Signer<'info> {
&self.authority
}
fn store(&self) -> &AccountLoader<'info, Store> {
&self.store
}
}
#[event_cpi]
#[derive(Accounts)]
pub struct ExecuteGlvShift<'info> {
pub authority: Signer<'info>,
#[account(has_one = token_map)]
pub store: AccountLoader<'info, Store>,
#[account(has_one = store)]
pub token_map: AccountLoader<'info, TokenMapHeader>,
#[account(mut, has_one = store)]
pub oracle: AccountLoader<'info, Oracle>,
#[account(
mut,
has_one = store,
constraint = glv.load()?.contains(&from_market_token.key()) @ CoreError::InvalidArgument,
constraint = glv.load()?.contains(&to_market_token.key()) @ CoreError::InvalidArgument,
)]
pub glv: AccountLoader<'info, Glv>,
#[account(
mut,
has_one = store,
constraint = from_market.load()?.meta().market_token_mint == from_market_token.key() @ CoreError::MarketTokenMintMismatched,
)]
pub from_market: AccountLoader<'info, Market>,
#[account(
mut,
has_one = store,
constraint = to_market.load()?.meta().market_token_mint == to_market_token.key() @ CoreError::MarketTokenMintMismatched,
)]
pub to_market: AccountLoader<'info, Market>,
#[account(
mut,
constraint = glv_shift.load()?.header().owner == glv.key() @ CoreError::OwnerMismatched,
constraint = glv_shift.load()?.header().store == store.key() @ CoreError::StoreMismatched,
constraint = glv_shift.load()?.tokens().from_market_token_account() == from_market_token_glv_vault.key() @ CoreError::MarketTokenAccountMismatched,
constraint = glv_shift.load()?.tokens().to_market_token_account() == to_market_token_glv_vault.key() @ CoreError::MarketTokenAccountMismatched,
)]
pub glv_shift: AccountLoader<'info, GlvShift>,
#[account(
mut,
constraint = glv_shift.load()?.tokens().from_market_token() == from_market_token.key() @ CoreError::MarketTokenMintMismatched
)]
pub from_market_token: Box<Account<'info, Mint>>,
#[account(
mut,
constraint = glv_shift.load()?.tokens().to_market_token() == to_market_token.key() @ CoreError::MarketTokenMintMismatched
)]
pub to_market_token: Box<Account<'info, Mint>>,
#[account(
mut,
associated_token::mint = from_market_token,
associated_token::authority = glv,
)]
pub from_market_token_glv_vault: Box<Account<'info, TokenAccount>>,
#[account(
mut,
associated_token::mint = to_market_token,
associated_token::authority = glv,
)]
pub to_market_token_glv_vault: Box<Account<'info, TokenAccount>>,
#[account(
mut,
token::mint = from_market_token,
seeds = [
constants::MARKET_VAULT_SEED,
store.key().as_ref(),
from_market_token_vault.mint.as_ref(),
],
bump,
)]
pub from_market_token_vault: Box<Account<'info, TokenAccount>>,
pub token_program: Program<'info, Token>,
pub chainlink_program: Option<Program<'info, Chainlink>>,
}
pub fn unchecked_execute_glv_shift<'info>(
ctx: Context<'_, '_, 'info, 'info, ExecuteGlvShift<'info>>,
execution_lamports: u64,
throw_on_execution_error: bool,
) -> Result<()> {
let accounts = ctx.accounts;
let remaining_accounts = ctx.remaining_accounts;
accounts
.store
.load()?
.validate_feature_enabled(DomainDisabledFlag::GlvShift, ActionDisabledFlag::Execute)?;
let executed = accounts.perform_execution(
remaining_accounts,
throw_on_execution_error,
ctx.bumps.event_authority,
)?;
if executed {
accounts.glv_shift.load_mut()?.header_mut().completed()?;
} else {
accounts.glv_shift.load_mut()?.header_mut().cancelled()?;
}
accounts.pay_execution_fee(execution_lamports)?;
Ok(())
}
impl<'info> internal::Authentication<'info> for ExecuteGlvShift<'info> {
fn authority(&self) -> &Signer<'info> {
&self.authority
}
fn store(&self) -> &AccountLoader<'info, Store> {
&self.store
}
}
impl<'info> ExecuteGlvShift<'info> {
#[inline(never)]
fn pay_execution_fee(&self, execution_fee: u64) -> Result<()> {
let execution_lamports = self.glv_shift.load()?.execution_lamports(execution_fee);
PayExecutionFeeOperation::builder()
.payer(self.glv_shift.to_account_info())
.receiver(self.authority.to_account_info())
.execution_lamports(execution_lamports)
.build()
.execute()?;
Ok(())
}
#[inline(never)]
fn ordered_tokens(&self) -> Result<Vec<Pubkey>> {
let from = *self.from_market.load()?.meta();
let to = *self.to_market.load()?.meta();
Ok(ordered_tokens(&from, &to).into_iter().collect())
}
fn perform_execution(
&mut self,
remaining_accounts: &'info [AccountInfo<'info>],
throw_on_execution_error: bool,
event_authority_bump: u8,
) -> Result<bool> {
let tokens = self.ordered_tokens()?;
let builder = ExecuteGlvShiftOperation::builder()
.glv_shift(&self.glv_shift)
.token_program(self.token_program.to_account_info())
.throw_on_execution_error(throw_on_execution_error)
.store(&self.store)
.glv(&self.glv)
.from_market(&self.from_market)
.from_market_token_mint(&mut self.from_market_token)
.from_market_token_glv_vault(&self.from_market_token_glv_vault)
.from_market_token_withdrawal_vault(self.from_market_token_vault.to_account_info())
.to_market(&self.to_market)
.to_market_token_mint(&mut self.to_market_token)
.to_market_token_glv_vault(self.to_market_token_glv_vault.to_account_info())
.event_emitter((&self.event_authority, event_authority_bump));
self.oracle.load_mut()?.with_prices(
&self.store,
&self.token_map,
&tokens,
remaining_accounts,
|oracle, _remaining_accounts| builder.oracle(oracle).build().unchecked_execute(),
)
}
}