gmsol_store/instructions/exchange/
withdrawal.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, WithdrawalCreated},
10    ops::withdrawal::{CreateWithdrawalOperation, CreateWithdrawalParams},
11    states::{
12        common::action::{Action, ActionExt},
13        feature::{ActionDisabledFlag, DomainDisabledFlag},
14        withdrawal::Withdrawal,
15        Market, NonceBytes, RoleKey, Seed, Store, StoreWalletSigner,
16    },
17    utils::{
18        internal,
19        token::{is_associated_token_account, is_associated_token_account_or_owner},
20    },
21    CoreError,
22};
23
24/// The accounts definition for the [`create_withdrawal`](crate::gmsol_store::create_withdrawal)
25/// instruction.
26///
27/// Remaining accounts expected by this instruction:
28///
29///   - 0..M. `[]` M market accounts, where M represents the length
30///     of the swap path for final long token.
31///   - M..M+N. `[]` N market accounts, where N represents the length
32///     of the swap path for final short token.
33#[derive(Accounts)]
34#[instruction(nonce: [u8; 32])]
35pub struct CreateWithdrawal<'info> {
36    /// The owner.
37    #[account(mut)]
38    pub owner: Signer<'info>,
39    /// The receiver of the output funds.
40    /// CHECK: only the address is used.
41    pub receiver: UncheckedAccount<'info>,
42    /// Store.
43    pub store: AccountLoader<'info, Store>,
44    /// Market.
45    #[account(mut, has_one = store)]
46    pub market: AccountLoader<'info, Market>,
47    /// The withdrawal to be created.
48    #[account(
49        init,
50        space = 8 + Withdrawal::INIT_SPACE,
51        payer = owner,
52        seeds = [Withdrawal::SEED, store.key().as_ref(), owner.key().as_ref(), &nonce],
53        bump,
54    )]
55    pub withdrawal: AccountLoader<'info, Withdrawal>,
56    /// Market token.
57    #[account(constraint = market.load()?.meta().market_token_mint == market_token.key() @ CoreError::MarketTokenMintMismatched)]
58    pub market_token: Box<Account<'info, Mint>>,
59    /// Final long token.
60    pub final_long_token: Box<Account<'info, Mint>>,
61    /// Final short token.
62    pub final_short_token: Box<Account<'info, Mint>>,
63    /// The escrow account for receving market tokens to burn.
64    #[account(
65        mut,
66        associated_token::mint = market_token,
67        associated_token::authority = withdrawal,
68    )]
69    pub market_token_escrow: Box<Account<'info, TokenAccount>>,
70    /// The escrow account for receiving withdrawed final long tokens.
71    #[account(
72        mut,
73        associated_token::mint = final_long_token,
74        associated_token::authority = withdrawal,
75    )]
76    pub final_long_token_escrow: Box<Account<'info, TokenAccount>>,
77    /// The escrow account for receiving withdrawed final short tokens.
78    #[account(
79        mut,
80        associated_token::mint = final_short_token,
81        associated_token::authority = withdrawal,
82    )]
83    pub final_short_token_escrow: Box<Account<'info, TokenAccount>>,
84    /// The source market token account.
85    #[account(
86        mut,
87        token::mint = market_token,
88    )]
89    pub market_token_source: Box<Account<'info, TokenAccount>>,
90    /// The system program.
91    pub system_program: Program<'info, System>,
92    /// The token program.
93    pub token_program: Program<'info, Token>,
94    /// The associated token program.
95    pub associated_token_program: Program<'info, AssociatedToken>,
96}
97
98impl<'info> internal::Create<'info, Withdrawal> for CreateWithdrawal<'info> {
99    type CreateParams = CreateWithdrawalParams;
100
101    fn action(&self) -> AccountInfo<'info> {
102        self.withdrawal.to_account_info()
103    }
104
105    fn payer(&self) -> AccountInfo<'info> {
106        self.owner.to_account_info()
107    }
108
109    fn system_program(&self) -> AccountInfo<'info> {
110        self.system_program.to_account_info()
111    }
112
113    fn validate(&self, _params: &Self::CreateParams) -> Result<()> {
114        self.store
115            .load()?
116            .validate_not_restarted()?
117            .validate_feature_enabled(DomainDisabledFlag::Withdrawal, ActionDisabledFlag::Create)?;
118        Ok(())
119    }
120
121    fn create_impl(
122        &mut self,
123        params: &Self::CreateParams,
124        nonce: &NonceBytes,
125        bumps: &Self::Bumps,
126        remaining_accounts: &'info [AccountInfo<'info>],
127    ) -> Result<()> {
128        self.transfer_tokens(params)?;
129        CreateWithdrawalOperation::builder()
130            .withdrawal(self.withdrawal.clone())
131            .market(self.market.clone())
132            .store(self.store.clone())
133            .owner(&self.owner)
134            .receiver(&self.receiver)
135            .nonce(nonce)
136            .bump(bumps.withdrawal)
137            .final_long_token(&self.final_long_token_escrow)
138            .final_short_token(&self.final_short_token_escrow)
139            .market_token(&self.market_token_escrow)
140            .params(params)
141            .swap_paths(remaining_accounts)
142            .build()
143            .execute()?;
144        emit!(WithdrawalCreated::new(
145            self.store.key(),
146            self.withdrawal.key(),
147        )?);
148        Ok(())
149    }
150}
151
152impl CreateWithdrawal<'_> {
153    fn transfer_tokens(&mut self, params: &CreateWithdrawalParams) -> Result<()> {
154        let amount = params.market_token_amount;
155        let source = &self.market_token_source;
156        let target = &mut self.market_token_escrow;
157        let mint = &self.market_token;
158        if amount != 0 {
159            transfer_checked(
160                CpiContext::new(
161                    self.token_program.to_account_info(),
162                    TransferChecked {
163                        from: source.to_account_info(),
164                        mint: mint.to_account_info(),
165                        to: target.to_account_info(),
166                        authority: self.owner.to_account_info(),
167                    },
168                ),
169                amount,
170                mint.decimals,
171            )?;
172            target.reload()?;
173        }
174        Ok(())
175    }
176}
177
178/// The accounts definition for the [`close_withdrawal`](crate::gmsol_store::close_withdrawal)
179/// instruction.
180#[event_cpi]
181#[derive(Accounts)]
182pub struct CloseWithdrawal<'info> {
183    /// The executor of this instruction.
184    pub executor: Signer<'info>,
185    /// The store.
186    pub store: AccountLoader<'info, Store>,
187    /// The store wallet.
188    #[account(mut, seeds = [Store::WALLET_SEED, store.key().as_ref()], bump)]
189    pub store_wallet: SystemAccount<'info>,
190    /// The owner of the withdrawal.
191    /// CHECK: only use to validate and receive input funds.
192    #[account(mut)]
193    pub owner: UncheckedAccount<'info>,
194    /// The receiver of the withdrawal.
195    /// CHECK: only use to validate and receive output funds.
196    #[account(mut)]
197    pub receiver: UncheckedAccount<'info>,
198    /// Market token.
199    #[account(
200        constraint = withdrawal.load()?.tokens.market_token() == market_token.key() @ CoreError::MarketTokenMintMismatched
201    )]
202    pub market_token: Box<Account<'info, Mint>>,
203    /// Final long token.
204    #[account(constraint = withdrawal.load()?.tokens.final_long_token() == final_long_token.key() @ CoreError::TokenMintMismatched)]
205    pub final_long_token: Box<Account<'info, Mint>>,
206    /// Final short token.
207    #[account(constraint = withdrawal.load()?.tokens.final_short_token() == final_short_token.key() @ CoreError::TokenMintMismatched)]
208    pub final_short_token: Box<Account<'info, Mint>>,
209    /// The withdrawal to close.
210    #[account(
211        mut,
212        constraint = withdrawal.load()?.header.owner == owner.key() @ CoreError::OwnerMismatched,
213        constraint = withdrawal.load()?.header.receiver() == receiver.key() @ CoreError::ReceiverMismatched,
214        // The rent receiver of a withdrawal must be the owner.
215        constraint = withdrawal.load()?.header.rent_receiver() == owner.key @ CoreError::RentReceiverMismatched,
216        constraint = withdrawal.load()?.header.store == store.key() @ CoreError::StoreMismatched,
217        constraint = withdrawal.load()?.tokens.market_token_account() == market_token_escrow.key() @ CoreError::MarketTokenAccountMismatched,
218        constraint = withdrawal.load()?.tokens.final_long_token_account() == final_long_token_escrow.key() @ CoreError::MarketTokenAccountMismatched,
219        constraint = withdrawal.load()?.tokens.final_short_token_account() == final_short_token_escrow.key() @ CoreError::MarketTokenAccountMismatched,
220    )]
221    pub withdrawal: AccountLoader<'info, Withdrawal>,
222    /// The escrow account for receving market tokens to burn.
223    #[account(
224        mut,
225        associated_token::mint = market_token,
226        associated_token::authority = withdrawal,
227    )]
228    pub market_token_escrow: Box<Account<'info, TokenAccount>>,
229    /// The escrow account for receiving withdrawed final long tokens.
230    #[account(
231        mut,
232        associated_token::mint = final_long_token,
233        associated_token::authority = withdrawal,
234    )]
235    pub final_long_token_escrow: Box<Account<'info, TokenAccount>>,
236    /// The escrow account for receiving withdrawed final short tokens.
237    #[account(
238        mut,
239        associated_token::mint = final_short_token,
240        associated_token::authority = withdrawal,
241    )]
242    pub final_short_token_escrow: Box<Account<'info, TokenAccount>>,
243    /// The ATA for market token of the owner.
244    /// CHECK: should be checked during the execution.
245    #[account(
246        mut,
247        constraint = is_associated_token_account(market_token_ata.key, owner.key, &market_token.key()) @ CoreError::NotAnATA,
248    )]
249    pub market_token_ata: UncheckedAccount<'info>,
250    /// The ATA for final long token of the receiver.
251    /// CHECK: should be checked during the execution
252    #[account(
253        mut,
254        constraint = is_associated_token_account_or_owner(final_long_token_ata.key, receiver.key, &final_long_token.key()) @ CoreError::NotAnATA,
255    )]
256    pub final_long_token_ata: UncheckedAccount<'info>,
257    /// The ATA for final short token of the receiver.
258    /// CHECK: should be checked during the execution
259    #[account(
260        mut,
261        constraint = is_associated_token_account_or_owner(final_short_token_ata.key, receiver.key, &final_short_token.key()) @ CoreError::NotAnATA,
262    )]
263    pub final_short_token_ata: UncheckedAccount<'info>,
264    /// The system program.
265    pub system_program: Program<'info, System>,
266    /// The token program.
267    pub token_program: Program<'info, Token>,
268    /// The associated token program.
269    pub associated_token_program: Program<'info, AssociatedToken>,
270}
271
272impl<'info> internal::Authentication<'info> for CloseWithdrawal<'info> {
273    fn authority(&self) -> &Signer<'info> {
274        &self.executor
275    }
276
277    fn store(&self) -> &AccountLoader<'info, Store> {
278        &self.store
279    }
280}
281
282impl<'info> internal::Close<'info, Withdrawal> for CloseWithdrawal<'info> {
283    fn expected_keeper_role(&self) -> &str {
284        RoleKey::ORDER_KEEPER
285    }
286
287    fn rent_receiver(&self) -> AccountInfo<'info> {
288        debug_assert!(
289            self.withdrawal.load().unwrap().header.rent_receiver() == self.owner.key,
290            "The rent receiver must have been checked to be the owner"
291        );
292        self.owner.to_account_info()
293    }
294
295    fn store_wallet_bump(&self, bumps: &Self::Bumps) -> u8 {
296        bumps.store_wallet
297    }
298
299    fn validate(&self) -> Result<()> {
300        let withdrawal = self.withdrawal.load()?;
301        if withdrawal.header.action_state()?.is_pending() {
302            self.store
303                .load()?
304                .validate_not_restarted()?
305                .validate_feature_enabled(
306                    DomainDisabledFlag::Withdrawal,
307                    ActionDisabledFlag::Cancel,
308                )?;
309        }
310        Ok(())
311    }
312
313    fn process(
314        &self,
315        init_if_needed: bool,
316        store_wallet_signer: &StoreWalletSigner,
317        _event_emitter: &EventEmitter<'_, 'info>,
318    ) -> Result<internal::Success> {
319        use crate::utils::token::TransferAllFromEscrowToATA;
320
321        let signer = self.withdrawal.load()?.signer();
322        let seeds = signer.as_seeds();
323
324        let builder = TransferAllFromEscrowToATA::builder()
325            .store_wallet(self.store_wallet.to_account_info())
326            .store_wallet_signer(store_wallet_signer)
327            .system_program(self.system_program.to_account_info())
328            .token_program(self.token_program.to_account_info())
329            .associated_token_program(self.associated_token_program.to_account_info())
330            .payer(self.executor.to_account_info())
331            .escrow_authority(self.withdrawal.to_account_info())
332            .escrow_authority_seeds(&seeds)
333            .init_if_needed(init_if_needed)
334            .rent_receiver(self.rent_receiver())
335            .should_unwrap_native(
336                self.withdrawal
337                    .load()?
338                    .header()
339                    .should_unwrap_native_token(),
340            );
341
342        // Transfer market tokens.
343        if !builder
344            .clone()
345            .mint(self.market_token.to_account_info())
346            .decimals(self.market_token.decimals)
347            .ata(self.market_token_ata.to_account_info())
348            .escrow(self.market_token_escrow.to_account_info())
349            .owner(self.owner.to_account_info())
350            .build()
351            .unchecked_execute()?
352        {
353            return Ok(false);
354        }
355
356        // Transfer final long tokens.
357        if !builder
358            .clone()
359            .mint(self.final_long_token.to_account_info())
360            .decimals(self.final_long_token.decimals)
361            .ata(self.final_long_token_ata.to_account_info())
362            .escrow(self.final_long_token_escrow.to_account_info())
363            .owner(self.receiver.to_account_info())
364            .build()
365            .unchecked_execute()?
366        {
367            return Ok(false);
368        }
369
370        if self.final_long_token_escrow.key() != self.final_short_token_escrow.key() {
371            // Transfer final short tokens.
372            if !builder
373                .clone()
374                .mint(self.final_short_token.to_account_info())
375                .decimals(self.final_short_token.decimals)
376                .ata(self.final_short_token_ata.to_account_info())
377                .escrow(self.final_short_token_escrow.to_account_info())
378                .owner(self.receiver.to_account_info())
379                .build()
380                .unchecked_execute()?
381            {
382                return Ok(false);
383            }
384        }
385        Ok(true)
386    }
387
388    fn event_authority(&self, bumps: &Self::Bumps) -> (AccountInfo<'info>, u8) {
389        (
390            self.event_authority.to_account_info(),
391            bumps.event_authority,
392        )
393    }
394
395    fn action(&self) -> &AccountLoader<'info, Withdrawal> {
396        &self.withdrawal
397    }
398}