gmsol_store/instructions/exchange/
shift.rs1use anchor_lang::prelude::*;
2use anchor_spl::{
3 associated_token::AssociatedToken,
4 token::{transfer_checked, Mint, Token, TokenAccount, TransferChecked},
5};
6use gmsol_utils::InitSpace;
7
8use crate::{
9 events::EventEmitter,
10 ops::shift::{CreateShiftOperation, CreateShiftParams},
11 states::{
12 common::action::{Action, ActionExt},
13 feature::{ActionDisabledFlag, DomainDisabledFlag},
14 Market, NonceBytes, RoleKey, Seed, Shift, Store, StoreWalletSigner,
15 },
16 utils::{internal, token::is_associated_token_account},
17 CoreError,
18};
19
20#[derive(Accounts)]
22#[instruction(nonce: [u8; 32])]
23pub struct CreateShift<'info> {
24 #[account(mut)]
26 pub owner: Signer<'info>,
27 pub receiver: UncheckedAccount<'info>,
30 pub store: AccountLoader<'info, Store>,
32 #[account(mut, has_one = store)]
34 pub from_market: AccountLoader<'info, Market>,
35 #[account(
37 has_one = store,
38 constraint = from_market.load()?.validate_shiftable(&*to_market.load()?).is_ok() @ CoreError::TokenMintMismatched,
39 )]
40 pub to_market: AccountLoader<'info, Market>,
41 #[account(
43 init,
44 space = 8 + Shift::INIT_SPACE,
45 payer = owner,
46 seeds = [Shift::SEED, store.key().as_ref(), owner.key().as_ref(), &nonce],
47 bump,
48 )]
49 pub shift: AccountLoader<'info, Shift>,
50 #[account(constraint = from_market.load()?.meta().market_token_mint == from_market_token.key() @ CoreError::MarketTokenMintMismatched)]
52 pub from_market_token: Box<Account<'info, Mint>>,
53 #[account(constraint = from_market.load()?.meta().market_token_mint == from_market_token.key() @ CoreError::MarketTokenMintMismatched)]
55 pub to_market_token: Box<Account<'info, Mint>>,
56 #[account(
58 mut,
59 associated_token::mint = from_market_token,
60 associated_token::authority = shift,
61 )]
62 pub from_market_token_escrow: Box<Account<'info, TokenAccount>>,
63 #[account(
65 mut,
66 associated_token::mint = to_market_token,
67 associated_token::authority = shift,
68 )]
69 pub to_market_token_escrow: Box<Account<'info, TokenAccount>>,
70 #[account(
72 mut,
73 token::mint = from_market_token,
74 )]
75 pub from_market_token_source: Box<Account<'info, TokenAccount>>,
76 #[account(
78 associated_token::mint = to_market_token,
79 associated_token::authority = receiver,
80 )]
81 pub to_market_token_ata: Box<Account<'info, TokenAccount>>,
82 pub system_program: Program<'info, System>,
84 pub token_program: Program<'info, Token>,
86 pub associated_token_program: Program<'info, AssociatedToken>,
88}
89
90impl<'info> internal::Create<'info, Shift> for CreateShift<'info> {
91 type CreateParams = CreateShiftParams;
92
93 fn action(&self) -> AccountInfo<'info> {
94 self.shift.to_account_info()
95 }
96
97 fn payer(&self) -> AccountInfo<'info> {
98 self.owner.to_account_info()
99 }
100
101 fn system_program(&self) -> AccountInfo<'info> {
102 self.system_program.to_account_info()
103 }
104
105 fn validate(&self, _params: &Self::CreateParams) -> Result<()> {
106 self.store
107 .load()?
108 .validate_not_restarted()?
109 .validate_feature_enabled(DomainDisabledFlag::Shift, ActionDisabledFlag::Create)?;
110 Ok(())
111 }
112
113 fn create_impl(
114 &mut self,
115 params: &Self::CreateParams,
116 nonce: &NonceBytes,
117 bumps: &Self::Bumps,
118 _remaining_accounts: &'info [AccountInfo<'info>],
119 callback_version: Option<u8>,
120 ) -> Result<()> {
121 require_eq!(callback_version.is_none(), true, {
122 msg!("[Callback] callback is not supported by this instruction.");
123 CoreError::InvalidArgument
124 });
125 self.transfer_tokens(params)?;
126 CreateShiftOperation::builder()
127 .store(&self.store)
128 .owner(&self.owner)
129 .receiver(&self.receiver)
130 .shift(&self.shift)
131 .from_market(&self.from_market)
132 .from_market_token_account(&self.from_market_token_escrow)
133 .to_market(&self.to_market)
134 .to_market_token_account(&self.to_market_token_escrow)
135 .nonce(nonce)
136 .bump(bumps.shift)
137 .params(params)
138 .build()
139 .execute()?;
140 Ok(())
141 }
142}
143
144impl CreateShift<'_> {
145 fn transfer_tokens(&mut self, params: &CreateShiftParams) -> Result<()> {
146 let amount = params.from_market_token_amount;
147 let source = &self.from_market_token_source;
148 let target = &mut self.from_market_token_escrow;
149 let mint = &self.from_market_token;
150 if amount != 0 {
151 transfer_checked(
152 CpiContext::new(
153 self.token_program.to_account_info(),
154 TransferChecked {
155 from: source.to_account_info(),
156 mint: mint.to_account_info(),
157 to: target.to_account_info(),
158 authority: self.owner.to_account_info(),
159 },
160 ),
161 amount,
162 mint.decimals,
163 )?;
164 target.reload()?;
165 }
166 Ok(())
167 }
168}
169
170#[event_cpi]
172#[derive(Accounts)]
173pub struct CloseShift<'info> {
174 pub executor: Signer<'info>,
176 pub store: AccountLoader<'info, Store>,
178 #[account(mut, seeds = [Store::WALLET_SEED, store.key().as_ref()], bump)]
180 pub store_wallet: SystemAccount<'info>,
181 #[account(mut)]
184 pub owner: UncheckedAccount<'info>,
185 #[account(mut)]
188 pub receiver: UncheckedAccount<'info>,
189 #[account(
191 mut,
192 constraint = shift.load()?.header.store == store.key() @ CoreError::StoreMismatched,
193 constraint = shift.load()?.header.owner == owner.key() @ CoreError::OwnerMismatched,
194 constraint = shift.load()?.header.receiver() == receiver.key() @ CoreError::ReceiverMismatched,
195 constraint = shift.load()?.header.rent_receiver() == owner.key @ CoreError::RentReceiverMismatched,
197 constraint = shift.load()?.tokens.from_market_token_account() == from_market_token_escrow.key() @ CoreError::MarketTokenAccountMismatched,
198 constraint = shift.load()?.tokens.to_market_token_account() == to_market_token_escrow.key() @ CoreError::MarketTokenAccountMismatched,
199 )]
200 pub shift: AccountLoader<'info, Shift>,
201 #[account(constraint = shift.load()?.tokens.from_market_token() == from_market_token.key() @ CoreError::MarketTokenMintMismatched)]
203 pub from_market_token: Box<Account<'info, Mint>>,
204 #[account(constraint = shift.load()?.tokens.to_market_token() == to_market_token.key() @ CoreError::MarketTokenMintMismatched)]
206 pub to_market_token: Box<Account<'info, Mint>>,
207 #[account(
209 mut,
210 associated_token::mint = from_market_token,
211 associated_token::authority = shift,
212 )]
213 pub from_market_token_escrow: Box<Account<'info, TokenAccount>>,
214 #[account(
216 mut,
217 associated_token::mint = to_market_token,
218 associated_token::authority = shift,
219 )]
220 pub to_market_token_escrow: Box<Account<'info, TokenAccount>>,
221 #[account(
224 mut,
225 constraint = is_associated_token_account(from_market_token_ata.key, owner.key, &from_market_token.key()) @ CoreError::NotAnATA,
226 )]
227 pub from_market_token_ata: UncheckedAccount<'info>,
228 #[account(
231 mut,
232 constraint = is_associated_token_account(to_market_token_ata.key, receiver.key, &to_market_token.key()) @ CoreError::NotAnATA,
233 )]
234 pub to_market_token_ata: UncheckedAccount<'info>,
235 pub system_program: Program<'info, System>,
237 pub token_program: Program<'info, Token>,
239 pub associated_token_program: Program<'info, AssociatedToken>,
241}
242
243impl<'info> internal::Authentication<'info> for CloseShift<'info> {
244 fn authority(&self) -> &Signer<'info> {
245 &self.executor
246 }
247
248 fn store(&self) -> &AccountLoader<'info, Store> {
249 &self.store
250 }
251}
252
253impl<'info> internal::Close<'info, Shift> for CloseShift<'info> {
254 fn expected_keeper_role(&self) -> &str {
255 RoleKey::ORDER_KEEPER
256 }
257
258 fn rent_receiver(&self) -> AccountInfo<'info> {
259 debug_assert!(
260 self.shift.load().unwrap().header.rent_receiver() == self.owner.key,
261 "The rent receiver must have been checked to be the owner"
262 );
263 self.owner.to_account_info()
264 }
265
266 fn store_wallet_bump(&self, bumps: &Self::Bumps) -> u8 {
267 bumps.store_wallet
268 }
269
270 fn validate(&self) -> Result<()> {
271 let shift = self.shift.load()?;
272 if shift.header.action_state()?.is_pending() {
273 self.store
274 .load()?
275 .validate_not_restarted()?
276 .validate_feature_enabled(DomainDisabledFlag::Shift, ActionDisabledFlag::Cancel)?;
277 }
278 Ok(())
279 }
280
281 fn process(
282 &self,
283 init_if_needed: bool,
284 store_wallet_signer: &StoreWalletSigner,
285 _event_emitter: &EventEmitter<'_, 'info>,
286 ) -> Result<internal::Success> {
287 use crate::utils::token::TransferAllFromEscrowToATA;
288
289 let signer = self.shift.load()?.signer();
290 let seeds = signer.as_seeds();
291
292 let builder = TransferAllFromEscrowToATA::builder()
293 .store_wallet(self.store_wallet.as_ref())
294 .store_wallet_signer(store_wallet_signer)
295 .system_program(self.system_program.to_account_info())
296 .token_program(self.token_program.to_account_info())
297 .associated_token_program(self.associated_token_program.to_account_info())
298 .payer(self.executor.to_account_info())
299 .escrow_authority(self.shift.to_account_info())
300 .escrow_authority_seeds(&seeds)
301 .init_if_needed(init_if_needed)
302 .rent_receiver(self.rent_receiver())
303 .should_unwrap_native(self.shift.load()?.header().should_unwrap_native_token());
304
305 if !builder
307 .clone()
308 .mint(self.from_market_token.to_account_info())
309 .decimals(self.from_market_token.decimals)
310 .ata(self.from_market_token_ata.to_account_info())
311 .escrow(self.from_market_token_escrow.to_account_info())
312 .owner(self.owner.to_account_info())
313 .build()
314 .unchecked_execute()?
315 {
316 return Ok(false);
317 }
318
319 if !builder
321 .clone()
322 .mint(self.to_market_token.to_account_info())
323 .decimals(self.to_market_token.decimals)
324 .ata(self.to_market_token_ata.to_account_info())
325 .escrow(self.to_market_token_escrow.to_account_info())
326 .owner(self.receiver.to_account_info())
327 .build()
328 .unchecked_execute()?
329 {
330 return Ok(false);
331 }
332
333 Ok(true)
334 }
335
336 fn event_authority(&self, bumps: &Self::Bumps) -> (AccountInfo<'info>, u8) {
337 (
338 self.event_authority.to_account_info(),
339 bumps.event_authority,
340 )
341 }
342
343 fn action(&self) -> &AccountLoader<'info, Shift> {
344 &self.shift
345 }
346}