gmsol_treasury/instructions/
swap.rs

1use anchor_lang::prelude::*;
2use anchor_spl::{
3    associated_token::AssociatedToken,
4    token::{Mint, Token, TokenAccount},
5};
6use gmsol_store::{
7    cpi::{
8        accounts::{CloseOrder, CreateOrder, PrepareUser},
9        close_order, create_order, prepare_user,
10    },
11    ops::order::CreateOrderParams,
12    program::GmsolStore,
13    states::{common::action::Action, order::OrderKind, NonceBytes, Order},
14    utils::{CpiAuthentication, WithStore},
15    CoreError,
16};
17
18use crate::{
19    constants,
20    states::{config::ReceiverSigner, Config, TreasuryVaultConfig},
21};
22
23/// The accounts definition for [`create_swap`](crate::gmsol_treasury::create_swap).
24#[derive(Accounts)]
25pub struct CreateSwap<'info> {
26    /// Authority.
27    #[account(mut)]
28    pub authority: Signer<'info>,
29    /// Store.
30    /// CHECK: check by CPI.
31    pub store: UncheckedAccount<'info>,
32    /// Config.
33    #[account(
34        has_one = store,
35        // Only allow using the authorized treasury vault config.
36        constraint = config.load()?.treasury_vault_config() == Some(&treasury_vault_config.key()) @ CoreError::InvalidArgument,
37    )]
38    pub config: AccountLoader<'info, Config>,
39    /// Treasury Config.
40    #[account(
41        has_one = config,
42        constraint = !treasury_vault_config.load()?.is_deposit_allowed(&swap_in_token.key()).unwrap_or(false) @ CoreError::InvalidArgument,
43        constraint = treasury_vault_config.load()?.is_deposit_allowed(&swap_out_token.key())? @ CoreError::InvalidArgument,
44    )]
45    pub treasury_vault_config: AccountLoader<'info, TreasuryVaultConfig>,
46    /// Swap in token.
47    pub swap_in_token: Account<'info, Mint>,
48    /// Swap out token.
49    #[account(constraint = swap_in_token.key() != swap_out_token.key() @ CoreError::InvalidArgument)]
50    pub swap_out_token: Account<'info, Mint>,
51    /// Swap in token receiver vault.
52    #[account(
53        mut,
54        associated_token::authority = receiver,
55        associated_token::mint = swap_in_token,
56    )]
57    pub swap_in_token_receiver_vault: Account<'info, TokenAccount>,
58    /// Market.
59    /// CHECK: check by CPI.
60    #[account(mut)]
61    pub market: UncheckedAccount<'info>,
62    /// Swap order owner (the receiver).
63    #[account(
64        mut,
65        seeds = [constants::RECEIVER_SEED, config.key().as_ref()],
66        bump,
67    )]
68    pub receiver: SystemAccount<'info>,
69    /// The user account for `receiver`.
70    /// CHECK: check by CPI.
71    #[account(mut)]
72    pub user: UncheckedAccount<'info>,
73    /// The escrow account for swap in token.
74    /// CHECK: check by CPI.
75    #[account(mut)]
76    pub swap_in_token_escrow: UncheckedAccount<'info>,
77    /// The escrow account for swap out token.
78    /// CHECK: check by CPI.
79    #[account(mut)]
80    pub swap_out_token_escrow: UncheckedAccount<'info>,
81    /// The order account.
82    /// CHECK: check by CPI.
83    #[account(mut)]
84    pub order: UncheckedAccount<'info>,
85    /// Store program.
86    pub store_program: Program<'info, GmsolStore>,
87    /// The token program.
88    pub token_program: Program<'info, Token>,
89    /// Associated token program.
90    pub associated_token_program: Program<'info, AssociatedToken>,
91    /// The system program.
92    pub system_program: Program<'info, System>,
93}
94
95/// Create a swap with the store program.
96/// # CHECK
97/// Only [`TREASURY_KEEPER`](crate::roles::TREASURY_KEEPER) is allowed to use.
98pub(crate) fn unchecked_create_swap<'info>(
99    ctx: Context<'_, '_, 'info, 'info, CreateSwap<'info>>,
100    nonce: NonceBytes,
101    swap_path_length: u8,
102    swap_in_amount: u64,
103    min_swap_out_amount: Option<u64>,
104) -> Result<()> {
105    let signer = ReceiverSigner::new(ctx.accounts.config.key(), ctx.bumps.receiver);
106
107    // Prepare user.
108    let cpi_ctx = ctx.accounts.prepare_user_ctx();
109    prepare_user(cpi_ctx.with_signer(&[&signer.as_seeds()]))?;
110
111    // Create order.
112    let cpi_ctx = ctx.accounts.create_order_ctx();
113    let params = CreateOrderParams {
114        kind: OrderKind::MarketSwap,
115        decrease_position_swap_type: None,
116        execution_lamports: Order::MIN_EXECUTION_LAMPORTS,
117        swap_path_length,
118        initial_collateral_delta_amount: swap_in_amount,
119        size_delta_value: 0,
120        is_long: true,
121        is_collateral_long: true,
122        min_output: min_swap_out_amount.map(u128::from),
123        trigger_price: None,
124        acceptable_price: None,
125        should_unwrap_native_token: false,
126        valid_from_ts: None,
127    };
128    create_order(
129        cpi_ctx
130            .with_signer(&[&signer.as_seeds()])
131            .with_remaining_accounts(ctx.remaining_accounts.to_vec()),
132        nonce,
133        params,
134    )?;
135    Ok(())
136}
137
138impl<'info> WithStore<'info> for CreateSwap<'info> {
139    fn store_program(&self) -> AccountInfo<'info> {
140        self.store_program.to_account_info()
141    }
142
143    fn store(&self) -> AccountInfo<'info> {
144        self.store.to_account_info()
145    }
146}
147
148impl<'info> CpiAuthentication<'info> for CreateSwap<'info> {
149    fn authority(&self) -> AccountInfo<'info> {
150        self.authority.to_account_info()
151    }
152
153    fn on_error(&self) -> Result<()> {
154        err!(CoreError::PermissionDenied)
155    }
156}
157
158impl<'info> CreateSwap<'info> {
159    fn prepare_user_ctx(&self) -> CpiContext<'_, '_, '_, 'info, PrepareUser<'info>> {
160        CpiContext::new(
161            self.store_program.to_account_info(),
162            PrepareUser {
163                owner: self.receiver.to_account_info(),
164                store: self.store.to_account_info(),
165                user: self.user.to_account_info(),
166                system_program: self.system_program.to_account_info(),
167            },
168        )
169    }
170
171    fn create_order_ctx(&self) -> CpiContext<'_, '_, '_, 'info, CreateOrder<'info>> {
172        CpiContext::new(
173            self.store_program.to_account_info(),
174            CreateOrder {
175                owner: self.receiver.to_account_info(),
176                receiver: self.receiver.to_account_info(),
177                store: self.store.to_account_info(),
178                market: self.market.to_account_info(),
179                user: self.user.to_account_info(),
180                order: self.order.to_account_info(),
181                position: None,
182                initial_collateral_token: Some(self.swap_in_token.to_account_info()),
183                final_output_token: self.swap_out_token.to_account_info(),
184                long_token: None,
185                short_token: None,
186                initial_collateral_token_escrow: Some(self.swap_in_token_escrow.to_account_info()),
187                final_output_token_escrow: Some(self.swap_out_token_escrow.to_account_info()),
188                long_token_escrow: None,
189                short_token_escrow: None,
190                initial_collateral_token_source: Some(
191                    self.swap_in_token_receiver_vault.to_account_info(),
192                ),
193                system_program: self.system_program.to_account_info(),
194                token_program: self.token_program.to_account_info(),
195                associated_token_program: self.associated_token_program.to_account_info(),
196            },
197        )
198    }
199}
200
201/// The accounts definition for [`cancel_swap`](crate::gmsol_treasury::cancel_swap).
202#[derive(Accounts)]
203pub struct CancelSwap<'info> {
204    /// Authority.
205    #[account(mut)]
206    pub authority: Signer<'info>,
207    /// Store.
208    /// CHECK: check by CPI.
209    #[account(mut)]
210    pub store: UncheckedAccount<'info>,
211    /// Store Wallet.
212    /// CHECK: check by CPI.
213    #[account(mut)]
214    pub store_wallet: UncheckedAccount<'info>,
215    #[account(
216        has_one = store,
217    )]
218    pub config: AccountLoader<'info, Config>,
219    /// Swap order owner (the receiver).
220    #[account(
221        mut,
222        seeds = [constants::RECEIVER_SEED, config.key().as_ref()],
223        bump,
224    )]
225    pub receiver: SystemAccount<'info>,
226    /// The user account for `owner`.
227    /// CHECK: check by CPI.
228    #[account(mut)]
229    pub user: UncheckedAccount<'info>,
230    /// Swap in token.
231    /// CHECK: check by CPI.
232    pub swap_in_token: UncheckedAccount<'info>,
233    /// Swap out token.
234    /// CHECK: check by CPI.
235    pub swap_out_token: UncheckedAccount<'info>,
236    /// Swap in token receiver vault.
237    /// CHECK: check by CPI.
238    #[account(mut)]
239    pub swap_in_token_receiver_vault: UncheckedAccount<'info>,
240    /// Swap out token receiver vault.
241    /// CHECK: check by CPI.
242    #[account(mut)]
243    pub swap_out_token_receiver_vault: UncheckedAccount<'info>,
244    /// The escrow account for swap in token.
245    /// CHECK: check by CPI.
246    #[account(mut)]
247    pub swap_in_token_escrow: UncheckedAccount<'info>,
248    /// The escrow account for swap out token.
249    /// CHECK: check by CPI.
250    #[account(mut)]
251    pub swap_out_token_escrow: UncheckedAccount<'info>,
252    /// The order account.
253    /// CHECK: check by CPI.
254    #[account(mut)]
255    pub order: UncheckedAccount<'info>,
256    /// Event authority.
257    /// CHECK: check by CPI.
258    pub event_authority: UncheckedAccount<'info>,
259    /// Store program.
260    pub store_program: Program<'info, GmsolStore>,
261    /// The token program.
262    pub token_program: Program<'info, Token>,
263    /// Associated token program.
264    pub associated_token_program: Program<'info, AssociatedToken>,
265    /// The system program.
266    pub system_program: Program<'info, System>,
267}
268
269/// Cancel a swap with the store program.
270/// # CHECK
271/// Only [`TREASURY_KEEPER`](crate::roles::TREASURY_KEEPER) is allowed to use.
272pub(crate) fn unchecked_cancel_swap(ctx: Context<CancelSwap>) -> Result<()> {
273    let signer = ReceiverSigner::new(ctx.accounts.config.key(), ctx.bumps.receiver);
274    let cpi_ctx = ctx.accounts.close_order_ctx();
275    close_order(
276        cpi_ctx.with_signer(&[&signer.as_seeds()]),
277        "cancel".to_string(),
278    )?;
279    Ok(())
280}
281
282impl<'info> WithStore<'info> for CancelSwap<'info> {
283    fn store_program(&self) -> AccountInfo<'info> {
284        self.store_program.to_account_info()
285    }
286
287    fn store(&self) -> AccountInfo<'info> {
288        self.store.to_account_info()
289    }
290}
291
292impl<'info> CpiAuthentication<'info> for CancelSwap<'info> {
293    fn authority(&self) -> AccountInfo<'info> {
294        self.authority.to_account_info()
295    }
296
297    fn on_error(&self) -> Result<()> {
298        err!(CoreError::PermissionDenied)
299    }
300}
301
302impl<'info> CancelSwap<'info> {
303    fn close_order_ctx(&self) -> CpiContext<'_, '_, '_, 'info, CloseOrder<'info>> {
304        CpiContext::new(
305            self.store_program.to_account_info(),
306            CloseOrder {
307                executor: self.receiver.to_account_info(),
308                store: self.store.to_account_info(),
309                store_wallet: self.store_wallet.to_account_info(),
310                owner: self.receiver.to_account_info(),
311                receiver: self.receiver.to_account_info(),
312                rent_receiver: self.receiver.to_account_info(),
313                user: self.user.to_account_info(),
314                referrer_user: None,
315                order: self.order.to_account_info(),
316                initial_collateral_token: Some(self.swap_in_token.to_account_info()),
317                final_output_token: Some(self.swap_out_token.to_account_info()),
318                long_token: None,
319                short_token: None,
320                initial_collateral_token_escrow: Some(self.swap_in_token_escrow.to_account_info()),
321                final_output_token_escrow: Some(self.swap_out_token_escrow.to_account_info()),
322                long_token_escrow: None,
323                short_token_escrow: None,
324                initial_collateral_token_ata: Some(
325                    self.swap_in_token_receiver_vault.to_account_info(),
326                ),
327                final_output_token_ata: Some(self.swap_out_token_receiver_vault.to_account_info()),
328                long_token_ata: None,
329                short_token_ata: None,
330                system_program: self.system_program.to_account_info(),
331                token_program: self.token_program.to_account_info(),
332                associated_token_program: self.associated_token_program.to_account_info(),
333                event_authority: self.event_authority.to_account_info(),
334                program: self.store_program.to_account_info(),
335            },
336        )
337    }
338}