gmsol_store/instructions/exchange/
shift.rs

1use 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/// The accounts definition for the [`create_shift`](crate::gmsol_store::create_shift) instruction.
21#[derive(Accounts)]
22#[instruction(nonce: [u8; 32])]
23pub struct CreateShift<'info> {
24    /// The owner.
25    #[account(mut)]
26    pub owner: Signer<'info>,
27    /// The receiver of the output funds.
28    /// CHECK: only the address is used.
29    pub receiver: UncheckedAccount<'info>,
30    /// Store.
31    pub store: AccountLoader<'info, Store>,
32    /// From market.
33    #[account(mut, has_one = store)]
34    pub from_market: AccountLoader<'info, Market>,
35    /// To market.
36    #[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    /// Shift.
42    #[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    /// From market token.
51    #[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    /// To market token.
54    #[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    /// The escrow account for the from market tokens.
57    #[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    /// The escrow account for the to market tokens.
64    #[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    /// The source from market token account.
71    #[account(
72        mut,
73        token::mint = from_market_token,
74    )]
75    pub from_market_token_source: Box<Account<'info, TokenAccount>>,
76    /// The ATA for receiving to market tokens.
77    #[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    /// The system program.
83    pub system_program: Program<'info, System>,
84    /// The token program.
85    pub token_program: Program<'info, Token>,
86    /// The associated token program.
87    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/// The accounts definition for the [`close_shift`](crate::gmsol_store::close_shift) instruction.
166#[event_cpi]
167#[derive(Accounts)]
168pub struct CloseShift<'info> {
169    /// The executor of this instruction.
170    pub executor: Signer<'info>,
171    /// The store.
172    pub store: AccountLoader<'info, Store>,
173    /// The store wallet.
174    #[account(mut, seeds = [Store::WALLET_SEED, store.key().as_ref()], bump)]
175    pub store_wallet: SystemAccount<'info>,
176    /// The owenr of the shift.
177    /// CHECK: only use to validate and receive input funds.
178    #[account(mut)]
179    pub owner: UncheckedAccount<'info>,
180    /// The receiver of the shift.
181    /// CHECK: only use to validate and receive output funds.
182    #[account(mut)]
183    pub receiver: UncheckedAccount<'info>,
184    /// The shift to close.
185    #[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        // The rent receiver of a shift must be the owner.
191        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    /// From market token.
197    #[account(constraint = shift.load()?.tokens.from_market_token() == from_market_token.key() @ CoreError::MarketTokenMintMismatched)]
198    pub from_market_token: Box<Account<'info, Mint>>,
199    /// To market token.
200    #[account(constraint = shift.load()?.tokens.to_market_token() == to_market_token.key() @ CoreError::MarketTokenMintMismatched)]
201    pub to_market_token: Box<Account<'info, Mint>>,
202    /// The escrow account for the from market tokens.
203    #[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    /// The escrow account for the to market tokens.
210    #[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    /// The ATA for from market token of the owner.
217    /// CHECK: should be checked during the execution.
218    #[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    /// The ATA for to market token of the receiver.
224    /// CHECK: should be checked during the execution.
225    #[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    /// The system program.
231    pub system_program: Program<'info, System>,
232    /// The token program.
233    pub token_program: Program<'info, Token>,
234    /// The associated token program.
235    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        // Transfer from_market tokens.
301        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        // Transfer to_market tokens.
315        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}