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 ) -> Result<()> {
120 self.transfer_tokens(params)?;
121 CreateShiftOperation::builder()
122 .store(&self.store)
123 .owner(&self.owner)
124 .receiver(&self.receiver)
125 .shift(&self.shift)
126 .from_market(&self.from_market)
127 .from_market_token_account(&self.from_market_token_escrow)
128 .to_market(&self.to_market)
129 .to_market_token_account(&self.to_market_token_escrow)
130 .nonce(nonce)
131 .bump(bumps.shift)
132 .params(params)
133 .build()
134 .execute()?;
135 Ok(())
136 }
137}
138
139impl CreateShift<'_> {
140 fn transfer_tokens(&mut self, params: &CreateShiftParams) -> Result<()> {
141 let amount = params.from_market_token_amount;
142 let source = &self.from_market_token_source;
143 let target = &mut self.from_market_token_escrow;
144 let mint = &self.from_market_token;
145 if amount != 0 {
146 transfer_checked(
147 CpiContext::new(
148 self.token_program.to_account_info(),
149 TransferChecked {
150 from: source.to_account_info(),
151 mint: mint.to_account_info(),
152 to: target.to_account_info(),
153 authority: self.owner.to_account_info(),
154 },
155 ),
156 amount,
157 mint.decimals,
158 )?;
159 target.reload()?;
160 }
161 Ok(())
162 }
163}
164
165#[event_cpi]
167#[derive(Accounts)]
168pub struct CloseShift<'info> {
169 pub executor: Signer<'info>,
171 pub store: AccountLoader<'info, Store>,
173 #[account(mut, seeds = [Store::WALLET_SEED, store.key().as_ref()], bump)]
175 pub store_wallet: SystemAccount<'info>,
176 #[account(mut)]
179 pub owner: UncheckedAccount<'info>,
180 #[account(mut)]
183 pub receiver: UncheckedAccount<'info>,
184 #[account(
186 mut,
187 constraint = shift.load()?.header.store == store.key() @ CoreError::StoreMismatched,
188 constraint = shift.load()?.header.owner == owner.key() @ CoreError::OwnerMismatched,
189 constraint = shift.load()?.header.receiver() == receiver.key() @ CoreError::ReceiverMismatched,
190 constraint = shift.load()?.header.rent_receiver() == owner.key @ CoreError::RentReceiverMismatched,
192 constraint = shift.load()?.tokens.from_market_token_account() == from_market_token_escrow.key() @ CoreError::MarketTokenAccountMismatched,
193 constraint = shift.load()?.tokens.to_market_token_account() == to_market_token_escrow.key() @ CoreError::MarketTokenAccountMismatched,
194 )]
195 pub shift: AccountLoader<'info, Shift>,
196 #[account(constraint = shift.load()?.tokens.from_market_token() == from_market_token.key() @ CoreError::MarketTokenMintMismatched)]
198 pub from_market_token: Box<Account<'info, Mint>>,
199 #[account(constraint = shift.load()?.tokens.to_market_token() == to_market_token.key() @ CoreError::MarketTokenMintMismatched)]
201 pub to_market_token: Box<Account<'info, Mint>>,
202 #[account(
204 mut,
205 associated_token::mint = from_market_token,
206 associated_token::authority = shift,
207 )]
208 pub from_market_token_escrow: Box<Account<'info, TokenAccount>>,
209 #[account(
211 mut,
212 associated_token::mint = to_market_token,
213 associated_token::authority = shift,
214 )]
215 pub to_market_token_escrow: Box<Account<'info, TokenAccount>>,
216 #[account(
219 mut,
220 constraint = is_associated_token_account(from_market_token_ata.key, owner.key, &from_market_token.key()) @ CoreError::NotAnATA,
221 )]
222 pub from_market_token_ata: UncheckedAccount<'info>,
223 #[account(
226 mut,
227 constraint = is_associated_token_account(to_market_token_ata.key, receiver.key, &to_market_token.key()) @ CoreError::NotAnATA,
228 )]
229 pub to_market_token_ata: UncheckedAccount<'info>,
230 pub system_program: Program<'info, System>,
232 pub token_program: Program<'info, Token>,
234 pub associated_token_program: Program<'info, AssociatedToken>,
236}
237
238impl<'info> internal::Authentication<'info> for CloseShift<'info> {
239 fn authority(&self) -> &Signer<'info> {
240 &self.executor
241 }
242
243 fn store(&self) -> &AccountLoader<'info, Store> {
244 &self.store
245 }
246}
247
248impl<'info> internal::Close<'info, Shift> for CloseShift<'info> {
249 fn expected_keeper_role(&self) -> &str {
250 RoleKey::ORDER_KEEPER
251 }
252
253 fn rent_receiver(&self) -> AccountInfo<'info> {
254 debug_assert!(
255 self.shift.load().unwrap().header.rent_receiver() == self.owner.key,
256 "The rent receiver must have been checked to be the owner"
257 );
258 self.owner.to_account_info()
259 }
260
261 fn store_wallet_bump(&self, bumps: &Self::Bumps) -> u8 {
262 bumps.store_wallet
263 }
264
265 fn validate(&self) -> Result<()> {
266 let shift = self.shift.load()?;
267 if shift.header.action_state()?.is_pending() {
268 self.store
269 .load()?
270 .validate_not_restarted()?
271 .validate_feature_enabled(DomainDisabledFlag::Shift, ActionDisabledFlag::Cancel)?;
272 }
273 Ok(())
274 }
275
276 fn process(
277 &self,
278 init_if_needed: bool,
279 store_wallet_signer: &StoreWalletSigner,
280 _event_emitter: &EventEmitter<'_, 'info>,
281 ) -> Result<internal::Success> {
282 use crate::utils::token::TransferAllFromEscrowToATA;
283
284 let signer = self.shift.load()?.signer();
285 let seeds = signer.as_seeds();
286
287 let builder = TransferAllFromEscrowToATA::builder()
288 .store_wallet(self.store_wallet.to_account_info())
289 .store_wallet_signer(store_wallet_signer)
290 .system_program(self.system_program.to_account_info())
291 .token_program(self.token_program.to_account_info())
292 .associated_token_program(self.associated_token_program.to_account_info())
293 .payer(self.executor.to_account_info())
294 .escrow_authority(self.shift.to_account_info())
295 .escrow_authority_seeds(&seeds)
296 .init_if_needed(init_if_needed)
297 .rent_receiver(self.rent_receiver())
298 .should_unwrap_native(self.shift.load()?.header().should_unwrap_native_token());
299
300 if !builder
302 .clone()
303 .mint(self.from_market_token.to_account_info())
304 .decimals(self.from_market_token.decimals)
305 .ata(self.from_market_token_ata.to_account_info())
306 .escrow(self.from_market_token_escrow.to_account_info())
307 .owner(self.owner.to_account_info())
308 .build()
309 .unchecked_execute()?
310 {
311 return Ok(false);
312 }
313
314 if !builder
316 .clone()
317 .mint(self.to_market_token.to_account_info())
318 .decimals(self.to_market_token.decimals)
319 .ata(self.to_market_token_ata.to_account_info())
320 .escrow(self.to_market_token_escrow.to_account_info())
321 .owner(self.receiver.to_account_info())
322 .build()
323 .unchecked_execute()?
324 {
325 return Ok(false);
326 }
327
328 Ok(true)
329 }
330
331 fn event_authority(&self, bumps: &Self::Bumps) -> (AccountInfo<'info>, u8) {
332 (
333 self.event_authority.to_account_info(),
334 bumps.event_authority,
335 )
336 }
337
338 fn action(&self) -> &AccountLoader<'info, Shift> {
339 &self.shift
340 }
341}