gmsol_store/ops/
withdrawal.rs

1use anchor_lang::prelude::*;
2use anchor_spl::token::{Mint, TokenAccount};
3use typed_builder::TypedBuilder;
4
5use crate::{
6    events::EventEmitter,
7    states::{
8        common::{
9            action::{Action, ActionParams},
10            swap::SwapActionParamsExt,
11        },
12        market::revertible::Revertible,
13        withdrawal::Withdrawal,
14        Market, NonceBytes, Oracle, Store, ValidateOracleTime,
15    },
16    CoreError, CoreResult,
17};
18
19use super::market::RevertibleLiquidityMarketOperation;
20
21/// Create Withdrawal Params.
22#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
23pub struct CreateWithdrawalParams {
24    /// Execution fee in lamports.
25    pub execution_lamports: u64,
26    /// The length of the swap path for long token.
27    pub long_token_swap_path_length: u8,
28    /// The length of the swap path for short token.
29    pub short_token_swap_path_length: u8,
30    /// Market token amount to burn.
31    pub market_token_amount: u64,
32    /// The minimum acceptable final long token amount to receive.
33    pub min_long_token_amount: u64,
34    /// The minimum acceptable final short token amount to receive.
35    pub min_short_token_amount: u64,
36    /// Whether to unwrap native token when sending funds back.
37    pub should_unwrap_native_token: bool,
38}
39
40impl ActionParams for CreateWithdrawalParams {
41    fn execution_lamports(&self) -> u64 {
42        self.execution_lamports
43    }
44}
45
46/// Operation for creating a withdrawal.
47#[derive(TypedBuilder)]
48pub(crate) struct CreateWithdrawalOperation<'a, 'info> {
49    withdrawal: AccountLoader<'info, Withdrawal>,
50    market: AccountLoader<'info, Market>,
51    store: AccountLoader<'info, Store>,
52    owner: &'a AccountInfo<'info>,
53    receiver: &'a AccountInfo<'info>,
54    nonce: &'a NonceBytes,
55    bump: u8,
56    final_long_token: &'a Account<'info, TokenAccount>,
57    final_short_token: &'a Account<'info, TokenAccount>,
58    market_token: &'a Account<'info, TokenAccount>,
59    params: &'a CreateWithdrawalParams,
60    swap_paths: &'info [AccountInfo<'info>],
61}
62
63impl CreateWithdrawalOperation<'_, '_> {
64    /// Execute.
65    pub(crate) fn execute(self) -> Result<()> {
66        self.market.load()?.validate(&self.store.key())?;
67        self.validate_params_excluding_swap()?;
68
69        let Self {
70            withdrawal,
71            market,
72            store,
73            owner,
74            receiver,
75            nonce,
76            bump,
77            final_long_token,
78            final_short_token,
79            market_token,
80            params,
81            swap_paths,
82        } = self;
83
84        let id = market.load_mut()?.indexer_mut().next_withdrawal_id()?;
85
86        let mut withdrawal = withdrawal.load_init()?;
87
88        // Initialize header.
89        withdrawal.header.init(
90            id,
91            store.key(),
92            market.key(),
93            owner.key(),
94            receiver.key(),
95            *nonce,
96            bump,
97            params.execution_lamports,
98            params.should_unwrap_native_token,
99        )?;
100
101        // Initialize tokens.
102        withdrawal.tokens.market_token.init(market_token);
103        withdrawal.tokens.final_long_token.init(final_long_token);
104        withdrawal.tokens.final_short_token.init(final_short_token);
105
106        // Initialize params.
107        withdrawal.params.market_token_amount = params.market_token_amount;
108        withdrawal.params.min_long_token_amount = params.min_long_token_amount;
109        withdrawal.params.min_short_token_amount = params.min_short_token_amount;
110
111        // Initialize swap paths.
112        let market = market.load()?;
113        let meta = market.meta();
114        withdrawal.swap.validate_and_init(
115            &*market,
116            params.long_token_swap_path_length,
117            params.short_token_swap_path_length,
118            swap_paths,
119            &store.key(),
120            (&meta.long_token_mint, &meta.short_token_mint),
121            (&final_long_token.mint, &final_short_token.mint),
122        )?;
123
124        Ok(())
125    }
126
127    fn validate_params_excluding_swap(&self) -> Result<()> {
128        let params = &self.params;
129        require!(params.market_token_amount != 0, CoreError::EmptyWithdrawal);
130        require_gte!(
131            self.market_token.amount,
132            params.market_token_amount,
133            CoreError::NotEnoughTokenAmount,
134        );
135
136        Ok(())
137    }
138}
139
140/// Operation for executing a withdrawal.
141#[derive(TypedBuilder)]
142pub(crate) struct ExecuteWithdrawalOperation<'a, 'info> {
143    store: &'a AccountLoader<'info, Store>,
144    market: &'a AccountLoader<'info, Market>,
145    market_token_mint: &'a mut Account<'info, Mint>,
146    market_token_vault: AccountInfo<'info>,
147    withdrawal: &'a AccountLoader<'info, Withdrawal>,
148    oracle: &'a Oracle,
149    remaining_accounts: &'info [AccountInfo<'info>],
150    throw_on_execution_error: bool,
151    token_program: AccountInfo<'info>,
152    #[builder(setter(into))]
153    event_emitter: EventEmitter<'a, 'info>,
154}
155
156impl ExecuteWithdrawalOperation<'_, '_> {
157    pub(crate) fn execute(self) -> Result<Option<(u64, u64)>> {
158        let throw_on_execution_error = self.throw_on_execution_error;
159        match self.validate_oracle() {
160            Ok(()) => {}
161            Err(CoreError::OracleTimestampsAreLargerThanRequired) if !throw_on_execution_error => {
162                msg!(
163                    "Withdrawal expired at {}",
164                    self.oracle_updated_before()
165                        .ok()
166                        .flatten()
167                        .expect("must have an expiration time"),
168                );
169                return Ok(None);
170            }
171            Err(err) => {
172                return Err(error!(err));
173            }
174        }
175        match self.perform_withdrawal() {
176            Ok(res) => Ok(Some(res)),
177            Err(err) if !throw_on_execution_error => {
178                msg!("Execute withdrawal error: {}", err);
179                Ok(None)
180            }
181            Err(err) => Err(err),
182        }
183    }
184
185    fn validate_oracle(&self) -> CoreResult<()> {
186        self.oracle.validate_time(self)
187    }
188
189    #[inline(never)]
190    fn perform_withdrawal(self) -> Result<(u64, u64)> {
191        self.market.load()?.validate(&self.store.key())?;
192
193        let withdrawal = self.withdrawal.load()?;
194
195        let mut market = RevertibleLiquidityMarketOperation::new(
196            self.store,
197            self.oracle,
198            self.market,
199            self.market_token_mint,
200            self.token_program,
201            Some(&withdrawal.swap),
202            self.remaining_accounts,
203            self.event_emitter,
204        )?;
205
206        let executed = market.op()?.unchecked_withdraw(
207            &self.market_token_vault,
208            &withdrawal.params,
209            (
210                withdrawal.tokens.final_long_token(),
211                withdrawal.tokens.final_short_token(),
212            ),
213            None,
214        )?;
215
216        let final_output_amounts = executed.output;
217
218        executed.commit();
219
220        Ok(final_output_amounts)
221    }
222}
223
224impl ValidateOracleTime for ExecuteWithdrawalOperation<'_, '_> {
225    fn oracle_updated_after(&self) -> CoreResult<Option<i64>> {
226        Ok(Some(
227            self.withdrawal
228                .load()
229                .map_err(|_| CoreError::LoadAccountError)?
230                .header()
231                .updated_at,
232        ))
233    }
234
235    fn oracle_updated_before(&self) -> CoreResult<Option<i64>> {
236        let ts = self
237            .store
238            .load()
239            .map_err(|_| CoreError::LoadAccountError)?
240            .request_expiration_at(
241                self.withdrawal
242                    .load()
243                    .map_err(|_| CoreError::LoadAccountError)?
244                    .header()
245                    .updated_at,
246            )?;
247        Ok(Some(ts))
248    }
249
250    fn oracle_updated_after_slot(&self) -> CoreResult<Option<u64>> {
251        Ok(Some(
252            self.withdrawal
253                .load()
254                .map_err(|_| CoreError::LoadAccountError)?
255                .header()
256                .updated_at_slot,
257        ))
258    }
259}