gmsol_store/instructions/exchange/
deposit.rs

1use anchor_lang::prelude::*;
2use anchor_spl::{
3    associated_token::AssociatedToken,
4    token::{Mint, Token, TokenAccount},
5};
6use gmsol_utils::InitSpace;
7
8use crate::{
9    events::{DepositCreated, EventEmitter},
10    ops::deposit::{CreateDepositOperation, CreateDepositParams},
11    states::{
12        common::action::{Action, ActionExt},
13        feature::{ActionDisabledFlag, DomainDisabledFlag},
14        Deposit, Market, NonceBytes, RoleKey, Seed, Store, StoreWalletSigner,
15    },
16    utils::{
17        internal,
18        token::{is_associated_token_account, is_associated_token_account_or_owner},
19    },
20    CoreError,
21};
22
23/// The accounts definition for the [`create_deposit`](crate::gmsol_store::create_deposit)
24/// instruction.
25///
26/// Remaining accounts expected by this instruction:
27///
28///   - 0..M. `[]` M market accounts, where M represents the length
29///     of the swap path for initial long token.
30///   - M..M+N. `[]` N market accounts, where N represents the length
31///     of the swap path for initial short token.
32#[derive(Accounts)]
33#[instruction(nonce: [u8; 32])]
34pub struct CreateDeposit<'info> {
35    /// The owner of the deposit.
36    #[account(mut)]
37    pub owner: Signer<'info>,
38    /// The receiver of the output funds.
39    /// CHECK: only the address is used.
40    pub receiver: UncheckedAccount<'info>,
41    /// Store.
42    pub store: AccountLoader<'info, Store>,
43    /// Market.
44    #[account(mut, has_one = store)]
45    pub market: AccountLoader<'info, Market>,
46    /// The deposit to be created.
47    #[account(
48        init,
49        space = 8 + Deposit::INIT_SPACE,
50        payer = owner,
51        seeds = [Deposit::SEED, store.key().as_ref(), owner.key().as_ref(), &nonce],
52        bump,
53    )]
54    pub deposit: AccountLoader<'info, Deposit>,
55    /// Market token.
56    #[account(constraint = market.load()?.meta().market_token_mint == market_token.key() @ CoreError::MarketTokenMintMismatched)]
57    pub market_token: Box<Account<'info, Mint>>,
58    /// Initial long token.
59    pub initial_long_token: Option<Box<Account<'info, Mint>>>,
60    /// initial short token.
61    pub initial_short_token: Option<Box<Account<'info, Mint>>>,
62    /// The escrow account for receving market tokens.
63    #[account(
64        mut,
65        associated_token::mint = market_token,
66        associated_token::authority = deposit,
67    )]
68    pub market_token_escrow: Box<Account<'info, TokenAccount>>,
69    /// The escrow account for receiving initial long token for deposit.
70    #[account(
71        mut,
72        associated_token::mint = initial_long_token,
73        associated_token::authority = deposit,
74    )]
75    pub initial_long_token_escrow: Option<Box<Account<'info, TokenAccount>>>,
76    /// The escrow account for receiving initial short token for deposit.
77    #[account(
78        mut,
79        associated_token::mint = initial_short_token,
80        associated_token::authority = deposit,
81    )]
82    pub initial_short_token_escrow: Option<Box<Account<'info, TokenAccount>>>,
83    /// The ATA of the owner for receving market tokens.
84    #[account(
85        init_if_needed,
86        payer = owner,
87        associated_token::mint = market_token,
88        associated_token::authority = receiver,
89    )]
90    pub market_token_ata: Box<Account<'info, TokenAccount>>,
91    /// The source initial long token account.
92    #[account(mut, token::mint = initial_long_token)]
93    pub initial_long_token_source: Option<Box<Account<'info, TokenAccount>>>,
94    /// The source initial short token account.
95    #[account(mut, token::mint = initial_short_token)]
96    pub initial_short_token_source: Option<Box<Account<'info, TokenAccount>>>,
97    /// The system program.
98    pub system_program: Program<'info, System>,
99    /// The token program.
100    pub token_program: Program<'info, Token>,
101    /// The associated token program.
102    pub associated_token_program: Program<'info, AssociatedToken>,
103}
104
105impl<'info> internal::Create<'info, Deposit> for CreateDeposit<'info> {
106    type CreateParams = CreateDepositParams;
107
108    fn action(&self) -> AccountInfo<'info> {
109        self.deposit.to_account_info()
110    }
111
112    fn payer(&self) -> AccountInfo<'info> {
113        self.owner.to_account_info()
114    }
115
116    fn system_program(&self) -> AccountInfo<'info> {
117        self.system_program.to_account_info()
118    }
119
120    fn validate(&self, _params: &Self::CreateParams) -> Result<()> {
121        self.store
122            .load()?
123            .validate_not_restarted()?
124            .validate_feature_enabled(DomainDisabledFlag::Deposit, ActionDisabledFlag::Create)?;
125        Ok(())
126    }
127
128    fn create_impl(
129        &mut self,
130        params: &Self::CreateParams,
131        nonce: &NonceBytes,
132        bumps: &Self::Bumps,
133        remaining_accounts: &'info [AccountInfo<'info>],
134    ) -> Result<()> {
135        self.transfer_tokens(params)?;
136        CreateDepositOperation::builder()
137            .deposit(self.deposit.clone())
138            .market(self.market.clone())
139            .store(self.store.clone())
140            .owner(&self.owner)
141            .receiver(&self.receiver)
142            .nonce(nonce)
143            .bump(bumps.deposit)
144            .initial_long_token(self.initial_long_token_escrow.as_deref())
145            .initial_short_token(self.initial_short_token_escrow.as_deref())
146            .market_token(&self.market_token_escrow)
147            .params(params)
148            .swap_paths(remaining_accounts)
149            .build()
150            .execute()?;
151        emit!(DepositCreated::new(self.store.key(), self.deposit.key())?);
152        Ok(())
153    }
154}
155
156impl CreateDeposit<'_> {
157    fn transfer_tokens(&mut self, params: &CreateDepositParams) -> Result<()> {
158        use anchor_spl::token::{transfer_checked, TransferChecked};
159
160        let amount = params.initial_long_token_amount;
161        if amount != 0 {
162            let Some(source) = self.initial_long_token_source.as_ref() else {
163                return err!(CoreError::TokenAccountNotProvided);
164            };
165            let Some(target) = self.initial_long_token_escrow.as_mut() else {
166                return err!(CoreError::TokenAccountNotProvided);
167            };
168            let Some(mint) = self.initial_long_token.as_ref() else {
169                return err!(CoreError::MintAccountNotProvided);
170            };
171            transfer_checked(
172                CpiContext::new(
173                    self.token_program.to_account_info(),
174                    TransferChecked {
175                        from: source.to_account_info(),
176                        mint: mint.to_account_info(),
177                        to: target.to_account_info(),
178                        authority: self.owner.to_account_info(),
179                    },
180                ),
181                amount,
182                mint.decimals,
183            )?;
184        }
185
186        let amount = params.initial_short_token_amount;
187        if amount != 0 {
188            let Some(source) = self.initial_short_token_source.as_ref() else {
189                return err!(CoreError::TokenAccountNotProvided);
190            };
191            let Some(target) = self.initial_short_token_escrow.as_mut() else {
192                return err!(CoreError::TokenAccountNotProvided);
193            };
194            let Some(mint) = self.initial_short_token.as_ref() else {
195                return err!(CoreError::MintAccountNotProvided);
196            };
197            transfer_checked(
198                CpiContext::new(
199                    self.token_program.to_account_info(),
200                    TransferChecked {
201                        from: source.to_account_info(),
202                        mint: mint.to_account_info(),
203                        to: target.to_account_info(),
204                        authority: self.owner.to_account_info(),
205                    },
206                ),
207                amount,
208                mint.decimals,
209            )?;
210        }
211
212        // Make sure the data for escrow accounts is up-to-date.
213        for escrow in self
214            .initial_long_token_escrow
215            .as_mut()
216            .into_iter()
217            .chain(self.initial_short_token_escrow.as_mut())
218        {
219            escrow.reload()?;
220        }
221        Ok(())
222    }
223}
224
225/// The accounts definition for [`close_deposit`](crate::gmsol_store::close_deposit)
226/// instruction.
227#[event_cpi]
228#[derive(Accounts)]
229pub struct CloseDeposit<'info> {
230    /// The executor of this instruction.
231    pub executor: Signer<'info>,
232    /// The store.
233    pub store: AccountLoader<'info, Store>,
234    /// The store wallet.
235    #[account(mut, seeds = [Store::WALLET_SEED, store.key().as_ref()], bump)]
236    pub store_wallet: SystemAccount<'info>,
237    /// The owner of the deposit.
238    /// CHECK: only use to validate and receive the input funds.
239    #[account(mut)]
240    pub owner: UncheckedAccount<'info>,
241    /// The receiver of the deposit.
242    /// CHECK: only use to validate and receive the output funds.
243    #[account(mut)]
244    pub receiver: UncheckedAccount<'info>,
245    /// Market token.
246    #[account(
247        constraint = deposit.load()?.tokens.market_token.token().expect("must exist") == market_token.key() @ CoreError::MarketTokenMintMismatched
248    )]
249    pub market_token: Box<Account<'info, Mint>>,
250    /// Initial long token.
251    #[account(
252        constraint = deposit.load()?.tokens.initial_long_token.token().map(|token| initial_long_token.key() == token).unwrap_or(true) @ CoreError::TokenMintMismatched
253    )]
254    pub initial_long_token: Option<Box<Account<'info, Mint>>>,
255    /// Initial short token.
256    #[account(
257        constraint = deposit.load()?.tokens.initial_short_token.token().map(|token| initial_short_token.key() == token).unwrap_or(true) @ CoreError::TokenMintMismatched
258    )]
259    pub initial_short_token: Option<Box<Account<'info, Mint>>>,
260    /// The deposit to close.
261    #[account(
262        mut,
263        constraint = deposit.load()?.header.owner == owner.key() @ CoreError::OwnerMismatched,
264        constraint = deposit.load()?.header.receiver() == receiver.key() @ CoreError::ReceiverMismatched,
265        // The rent receiver of a deposit must be the owner.
266        constraint = deposit.load()?.header.rent_receiver() == owner.key @ CoreError::RentReceiverMismatched,
267        constraint = deposit.load()?.header.store == store.key() @ CoreError::StoreMismatched,
268        constraint = deposit.load()?.tokens.market_token.account().expect("must exist") == market_token_escrow.key() @ CoreError::MarketTokenAccountMismatched,
269        constraint = deposit.load()?.tokens.initial_long_token.account() == initial_long_token_escrow.as_ref().map(|a| a.key()) @ CoreError::TokenAccountMismatched,
270        constraint = deposit.load()?.tokens.initial_short_token.account() == initial_short_token_escrow.as_ref().map(|a| a.key()) @ CoreError::TokenAccountMismatched,
271        seeds = [Deposit::SEED, store.key().as_ref(), owner.key().as_ref(), &deposit.load()?.header.nonce],
272        bump = deposit.load()?.header.bump,
273    )]
274    pub deposit: AccountLoader<'info, Deposit>,
275    /// The escrow account for receving market tokens.
276    #[account(
277        mut,
278        associated_token::mint = market_token,
279        associated_token::authority = deposit,
280    )]
281    pub market_token_escrow: Box<Account<'info, TokenAccount>>,
282    /// The escrow account for receiving initial long token for deposit.
283    #[account(
284        mut,
285        associated_token::mint = initial_long_token,
286        associated_token::authority = deposit,
287    )]
288    pub initial_long_token_escrow: Option<Box<Account<'info, TokenAccount>>>,
289    /// The escrow account for receiving initial short token for deposit.
290    #[account(
291        mut,
292        associated_token::mint = initial_short_token,
293        associated_token::authority = deposit,
294    )]
295    pub initial_short_token_escrow: Option<Box<Account<'info, TokenAccount>>>,
296    /// The ATA for market token of the receiver.
297    /// CHECK: should be checked during the execution.
298    #[account(
299        mut,
300        constraint = is_associated_token_account(market_token_ata.key, receiver.key, &market_token.key()) @ CoreError::NotAnATA,
301    )]
302    pub market_token_ata: UncheckedAccount<'info>,
303    /// The ATA for initial long token of the owner.
304    /// CHECK: should be checked during the execution
305    #[account(
306        mut,
307        constraint = is_associated_token_account_or_owner(initial_long_token_ata.key, owner.key, &initial_long_token.as_ref().expect("must provided").key()) @ CoreError::NotAnATA,
308    )]
309    pub initial_long_token_ata: Option<UncheckedAccount<'info>>,
310    /// The ATA for initial short token of the owner.
311    /// CHECK: should be checked during the execution
312    #[account(
313        mut,
314        constraint = is_associated_token_account_or_owner(initial_short_token_ata.key, owner.key, &initial_short_token.as_ref().expect("must provided").key()) @ CoreError::NotAnATA,
315    )]
316    pub initial_short_token_ata: Option<UncheckedAccount<'info>>,
317    /// The system program.
318    pub system_program: Program<'info, System>,
319    /// The token program.
320    pub token_program: Program<'info, Token>,
321    /// The associated token program.
322    pub associated_token_program: Program<'info, AssociatedToken>,
323}
324
325impl<'info> internal::Authentication<'info> for CloseDeposit<'info> {
326    fn authority(&self) -> &Signer<'info> {
327        &self.executor
328    }
329
330    fn store(&self) -> &AccountLoader<'info, Store> {
331        &self.store
332    }
333}
334
335impl<'info> internal::Close<'info, Deposit> for CloseDeposit<'info> {
336    fn expected_keeper_role(&self) -> &str {
337        RoleKey::ORDER_KEEPER
338    }
339
340    fn rent_receiver(&self) -> AccountInfo<'info> {
341        debug_assert!(
342            self.deposit.load().unwrap().header.rent_receiver() == self.owner.key,
343            "The rent receiver must have been checked to be the owner"
344        );
345        self.owner.to_account_info()
346    }
347
348    fn store_wallet_bump(&self, bumps: &Self::Bumps) -> u8 {
349        bumps.store_wallet
350    }
351
352    fn validate(&self) -> Result<()> {
353        let deposit = self.deposit.load()?;
354        if deposit.header.action_state()?.is_pending() {
355            self.store
356                .load()?
357                .validate_not_restarted()?
358                .validate_feature_enabled(
359                    DomainDisabledFlag::Deposit,
360                    ActionDisabledFlag::Cancel,
361                )?;
362        }
363        Ok(())
364    }
365
366    fn process(
367        &self,
368        init_if_needed: bool,
369        store_wallet_signer: &StoreWalletSigner,
370        _event_emitter: &EventEmitter<'_, 'info>,
371    ) -> Result<internal::Success> {
372        use crate::utils::token::TransferAllFromEscrowToATA;
373
374        // Prepare signer seeds.
375        let signer = self.deposit.load()?.signer();
376        let seeds = signer.as_seeds();
377
378        let builder = TransferAllFromEscrowToATA::builder()
379            .store_wallet(self.store_wallet.to_account_info())
380            .store_wallet_signer(store_wallet_signer)
381            .system_program(self.system_program.to_account_info())
382            .token_program(self.token_program.to_account_info())
383            .associated_token_program(self.associated_token_program.to_account_info())
384            .payer(self.executor.to_account_info())
385            .escrow_authority(self.deposit.to_account_info())
386            .escrow_authority_seeds(&seeds)
387            .init_if_needed(init_if_needed)
388            .rent_receiver(self.rent_receiver())
389            .should_unwrap_native(self.deposit.load()?.header().should_unwrap_native_token());
390
391        // Transfer market tokens.
392        if !builder
393            .clone()
394            .mint(self.market_token.to_account_info())
395            .decimals(self.market_token.decimals)
396            .ata(self.market_token_ata.to_account_info())
397            .escrow(self.market_token_escrow.to_account_info())
398            .owner(self.receiver.to_account_info())
399            .build()
400            .unchecked_execute()?
401        {
402            return Ok(false);
403        }
404
405        // Prevent closing the same token accounts.
406        let (initial_long_token_escrow, initial_short_token_escrow) =
407            if self.initial_long_token_escrow.as_ref().map(|a| a.key())
408                == self.initial_short_token_escrow.as_ref().map(|a| a.key())
409            {
410                (self.initial_long_token_escrow.as_ref(), None)
411            } else {
412                (
413                    self.initial_long_token_escrow.as_ref(),
414                    self.initial_short_token_escrow.as_ref(),
415                )
416            };
417
418        // Transfer initial long tokens.
419        if let Some(escrow) = initial_long_token_escrow.as_ref() {
420            let Some(ata) = self.initial_long_token_ata.as_ref() else {
421                return err!(CoreError::TokenAccountNotProvided);
422            };
423            let Some(mint) = self.initial_long_token.as_ref() else {
424                return err!(CoreError::MintAccountNotProvided);
425            };
426            if !builder
427                .clone()
428                .mint(mint.to_account_info())
429                .decimals(mint.decimals)
430                .ata(ata.to_account_info())
431                .escrow(escrow.to_account_info())
432                .owner(self.owner.to_account_info())
433                .build()
434                .unchecked_execute()?
435            {
436                return Ok(false);
437            }
438        }
439
440        // Transfer initial short tokens.
441        if let Some(escrow) = initial_short_token_escrow.as_ref() {
442            let Some(ata) = self.initial_short_token_ata.as_ref() else {
443                return err!(CoreError::TokenAccountNotProvided);
444            };
445            let Some(mint) = self.initial_short_token.as_ref() else {
446                return err!(CoreError::MintAccountNotProvided);
447            };
448            if !builder
449                .clone()
450                .mint(mint.to_account_info())
451                .decimals(mint.decimals)
452                .ata(ata.to_account_info())
453                .escrow(escrow.to_account_info())
454                .owner(self.owner.to_account_info())
455                .build()
456                .unchecked_execute()?
457            {
458                return Ok(false);
459            }
460        }
461
462        Ok(true)
463    }
464
465    fn event_authority(&self, bumps: &Self::Bumps) -> (AccountInfo<'info>, u8) {
466        (
467            self.event_authority.to_account_info(),
468            bumps.event_authority,
469        )
470    }
471
472    fn action(&self) -> &AccountLoader<'info, Deposit> {
473        &self.deposit
474    }
475}