Skip to main content

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        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/// The accounts definition for the [`close_shift`](crate::gmsol_store::close_shift) instruction.
171#[event_cpi]
172#[derive(Accounts)]
173pub struct CloseShift<'info> {
174    /// The executor of this instruction.
175    pub executor: Signer<'info>,
176    /// The store.
177    pub store: AccountLoader<'info, Store>,
178    /// The store wallet.
179    #[account(mut, seeds = [Store::WALLET_SEED, store.key().as_ref()], bump)]
180    pub store_wallet: SystemAccount<'info>,
181    /// The owner of the shift.
182    /// CHECK: only use to validate and receive input funds.
183    #[account(mut)]
184    pub owner: UncheckedAccount<'info>,
185    /// The receiver of the shift.
186    /// CHECK: only use to validate and receive output funds.
187    #[account(mut)]
188    pub receiver: UncheckedAccount<'info>,
189    /// The shift to close.
190    #[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        // The rent receiver of a shift must be the owner.
196        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    /// From market token.
202    #[account(constraint = shift.load()?.tokens.from_market_token() == from_market_token.key() @ CoreError::MarketTokenMintMismatched)]
203    pub from_market_token: Box<Account<'info, Mint>>,
204    /// To market token.
205    #[account(constraint = shift.load()?.tokens.to_market_token() == to_market_token.key() @ CoreError::MarketTokenMintMismatched)]
206    pub to_market_token: Box<Account<'info, Mint>>,
207    /// The escrow account for the from market tokens.
208    #[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    /// The escrow account for the to market tokens.
215    #[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    /// The ATA for from market token of the owner.
222    /// CHECK: should be checked during the execution.
223    #[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    /// The ATA for to market token of the receiver.
229    /// CHECK: should be checked during the execution.
230    #[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    /// The system program.
236    pub system_program: Program<'info, System>,
237    /// The token program.
238    pub token_program: Program<'info, Token>,
239    /// The associated token program.
240    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        // Transfer from_market tokens.
306        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        // Transfer to_market tokens.
320        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}