gmsol_store/instructions/exchange/
execute_shift.rs1use 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#[event_cpi]
24#[derive(Accounts)]
25pub struct ExecuteShift<'info> {
26 pub authority: Signer<'info>,
28 #[account(has_one = token_map)]
30 pub store: AccountLoader<'info, Store>,
31 #[account(has_one = store)]
33 pub token_map: AccountLoader<'info, TokenMapHeader>,
34 #[account(mut, has_one = store)]
36 pub oracle: AccountLoader<'info, Oracle>,
37 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 pub token_program: Program<'info, Token>,
104 pub chainlink_program: Option<Program<'info, Chainlink>>,
106}
107
108pub 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 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 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}