gmsol_store/instructions/exchange/
execute_shift.rs

1use anchor_lang::prelude::*;
2use anchor_spl::token::{transfer_checked, Mint, Token, TokenAccount, TransferChecked};
3use gmsol_utils::market::ordered_tokens;
4
5use crate::{
6    constants,
7    ops::{execution_fee::PayExecutionFeeOperation, shift::ExecuteShiftOperation},
8    states::{
9        common::action::{ActionExt, ActionSigner},
10        feature::{ActionDisabledFlag, DomainDisabledFlag},
11        Chainlink, Market, Oracle, Shift, Store, TokenMapHeader,
12    },
13    utils::internal,
14    CoreError,
15};
16
17/// The accounts definition for the [`execute_shift`](crate::gmsol_store::execute_shift) instruction.
18///
19/// Remaining accounts expected by this instruction:
20///
21///   - 0..N. `[]` N feed accounts, where N represents the total number of unique tokens
22///     in the markets.
23#[event_cpi]
24#[derive(Accounts)]
25pub struct ExecuteShift<'info> {
26    /// Authority.
27    pub authority: Signer<'info>,
28    /// Store.
29    #[account(has_one = token_map)]
30    pub store: AccountLoader<'info, Store>,
31    /// Token map.
32    #[account(has_one = store)]
33    pub token_map: AccountLoader<'info, TokenMapHeader>,
34    /// Oracle buffer to use.
35    #[account(mut, has_one = store)]
36    pub oracle: AccountLoader<'info, Oracle>,
37    /// From market.
38    #[account(
39        mut,
40        has_one = store,
41        constraint = from_market.load()?.meta().market_token_mint == from_market_token.key() @ CoreError::MarketTokenMintMismatched,
42        constraint = from_market.load()?.meta().long_token_mint == shift.load()?.tokens.long_token @ CoreError::TokenMintMismatched,
43        constraint = from_market.load()?.meta().short_token_mint== shift.load()?.tokens.short_token @ CoreError::TokenMintMismatched,
44    )]
45    pub from_market: AccountLoader<'info, Market>,
46    /// To market.
47    #[account(
48        mut,
49        has_one = store,
50        constraint = to_market.load()?.meta().market_token_mint == to_market_token.key() @ CoreError::MarketTokenMintMismatched,
51        constraint = to_market.load()?.meta().long_token_mint == shift.load()?.tokens.long_token @ CoreError::TokenMintMismatched,
52        constraint = to_market.load()?.meta().short_token_mint== shift.load()?.tokens.short_token @ CoreError::TokenMintMismatched,
53    )]
54    pub to_market: AccountLoader<'info, Market>,
55    /// The shift to execute.
56    #[account(
57        mut,
58        constraint = shift.load()?.header.store == store.key() @ CoreError::StoreMismatched,
59        constraint = shift.load()?.header.market == from_market.key() @ CoreError::MarketMismatched,
60        constraint = shift.load()?.tokens.from_market_token_account() == from_market_token_escrow.key() @ CoreError::MarketTokenAccountMismatched,
61        constraint = shift.load()?.tokens.to_market_token_account() == to_market_token_escrow.key() @ CoreError::MarketTokenAccountMismatched,
62    )]
63    pub shift: AccountLoader<'info, Shift>,
64    /// From market token.
65    #[account(
66        mut,
67        constraint = shift.load()?.tokens.from_market_token() == from_market_token.key() @ CoreError::MarketTokenMintMismatched,
68    )]
69    pub from_market_token: Box<Account<'info, Mint>>,
70    /// To market token.
71    #[account(
72        mut,
73        constraint = shift.load()?.tokens.to_market_token() == to_market_token.key() @ CoreError::MarketTokenMintMismatched,
74    )]
75    pub to_market_token: Box<Account<'info, Mint>>,
76    /// The escrow account for from market tokens.
77    #[account(
78        mut,
79        associated_token::mint = from_market_token,
80        associated_token::authority = shift,
81    )]
82    pub from_market_token_escrow: Box<Account<'info, TokenAccount>>,
83    /// The escrow account for to market tokens.
84    #[account(
85        mut,
86        associated_token::mint = to_market_token,
87        associated_token::authority = shift,
88    )]
89    pub to_market_token_escrow: Box<Account<'info, TokenAccount>>,
90    /// From market token vault.
91    #[account(
92        mut,
93        token::mint = from_market_token,
94        seeds = [
95            constants::MARKET_VAULT_SEED,
96            store.key().as_ref(),
97            from_market_token_vault.mint.as_ref(),
98        ],
99        bump,
100    )]
101    pub from_market_token_vault: Box<Account<'info, TokenAccount>>,
102    /// The token program.
103    pub token_program: Program<'info, Token>,
104    /// Chainlink Program.
105    pub chainlink_program: Option<Program<'info, Chainlink>>,
106}
107
108/// CHECK: only ORDER_KEEPER is allowed to execute shift.
109pub fn unchecked_execute_shift<'info>(
110    ctx: Context<'_, '_, 'info, 'info, ExecuteShift<'info>>,
111    execution_lamports: u64,
112    throw_on_execution_error: bool,
113) -> Result<()> {
114    let accounts = ctx.accounts;
115    let remaining_accounts = ctx.remaining_accounts;
116
117    // Validate feature enabled.
118    accounts
119        .store
120        .load()?
121        .validate_feature_enabled(DomainDisabledFlag::Shift, ActionDisabledFlag::Execute)?;
122
123    let signer = accounts.shift.load()?.signer();
124
125    accounts.transfer_from_market_tokens_in(&signer)?;
126
127    let executed = accounts.perform_execution(
128        remaining_accounts,
129        throw_on_execution_error,
130        ctx.bumps.event_authority,
131    )?;
132
133    if executed {
134        accounts.shift.load_mut()?.header.completed()?;
135    } else {
136        accounts.shift.load_mut()?.header.cancelled()?;
137        accounts.transfer_from_market_tokens_out()?;
138    }
139
140    // Is must be placed at the end to be executed correctly.
141    accounts.pay_execution_fee(execution_lamports)?;
142
143    Ok(())
144}
145
146impl<'info> internal::Authentication<'info> for ExecuteShift<'info> {
147    fn authority(&self) -> &Signer<'info> {
148        &self.authority
149    }
150
151    fn store(&self) -> &AccountLoader<'info, Store> {
152        &self.store
153    }
154}
155
156impl<'info> ExecuteShift<'info> {
157    fn transfer_from_market_tokens_in(&mut self, signer: &ActionSigner) -> Result<()> {
158        let seeds = signer.as_seeds();
159
160        transfer_checked(
161            CpiContext::new(
162                self.token_program.to_account_info(),
163                TransferChecked {
164                    from: self.from_market_token_escrow.to_account_info(),
165                    mint: self.from_market_token.to_account_info(),
166                    to: self.from_market_token_vault.to_account_info(),
167                    authority: self.shift.to_account_info(),
168                },
169            )
170            .with_signer(&[&seeds]),
171            self.shift.load()?.params.from_market_token_amount(),
172            self.from_market_token.decimals,
173        )?;
174
175        Ok(())
176    }
177
178    fn transfer_from_market_tokens_out(&self) -> Result<()> {
179        use crate::internal::TransferUtils;
180
181        let amount = self.shift.load()?.params.from_market_token_amount();
182        TransferUtils::new(
183            self.token_program.to_account_info(),
184            &self.store,
185            self.from_market_token.to_account_info(),
186        )
187        .transfer_out(
188            self.from_market_token_vault.to_account_info(),
189            self.from_market_token_escrow.to_account_info(),
190            amount,
191            self.from_market_token.decimals,
192        )?;
193
194        Ok(())
195    }
196
197    #[inline(never)]
198    fn ordered_tokens(&self) -> Result<Vec<Pubkey>> {
199        let from = *self.from_market.load()?.meta();
200        let to = *self.to_market.load()?.meta();
201
202        Ok(ordered_tokens(&from, &to).into_iter().collect())
203    }
204
205    #[inline(never)]
206    fn perform_execution(
207        &mut self,
208        remaining_accounts: &'info [AccountInfo<'info>],
209        throw_on_execution_error: bool,
210        event_authority_bump: u8,
211    ) -> Result<bool> {
212        let tokens = self.ordered_tokens()?;
213
214        let ops = ExecuteShiftOperation::builder()
215            .store(&self.store)
216            .shift(&self.shift)
217            .from_market(&self.from_market)
218            .to_market(&self.to_market)
219            .from_market_token_mint(&mut self.from_market_token)
220            .to_market_token_mint(&mut self.to_market_token)
221            .from_market_token_vault(self.from_market_token_vault.to_account_info())
222            .to_market_token_account(self.to_market_token_escrow.to_account_info())
223            .throw_on_execution_error(throw_on_execution_error)
224            .token_program(self.token_program.to_account_info())
225            .event_emitter((&self.event_authority, event_authority_bump));
226
227        let executed = self.oracle.load_mut()?.with_prices(
228            &self.store,
229            &self.token_map,
230            &tokens,
231            remaining_accounts,
232            self.chainlink_program.as_ref(),
233            |oracle, _remaining_accounts| ops.oracle(oracle).build().execute(),
234        )?;
235
236        Ok(executed)
237    }
238
239    fn pay_execution_fee(&self, execution_lamports: u64) -> Result<()> {
240        let execution_lamports = self.shift.load()?.execution_lamports(execution_lamports);
241        PayExecutionFeeOperation::builder()
242            .payer(self.shift.to_account_info())
243            .receiver(self.authority.to_account_info())
244            .execution_lamports(execution_lamports)
245            .build()
246            .execute()?;
247        Ok(())
248    }
249}