gmsol_store/ops/
deposit.rs

1use anchor_lang::prelude::*;
2use anchor_spl::token::{Mint, TokenAccount};
3use typed_builder::TypedBuilder;
4
5use crate::{
6    events::EventEmitter,
7    ops::market::RevertibleLiquidityMarketOperation,
8    states::{
9        common::{
10            action::{Action, ActionExt, ActionParams},
11            swap::SwapActionParamsExt,
12        },
13        market::revertible::Revertible,
14        Deposit, Market, NonceBytes, Oracle, Store, ValidateOracleTime,
15    },
16    CoreError, CoreResult,
17};
18
19/// Create Deposit Params.
20#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
21pub struct CreateDepositParams {
22    /// Execution fee in lamports
23    pub execution_lamports: u64,
24    /// The length of the swap path for long token.
25    pub long_token_swap_length: u8,
26    /// The length of the swap path for short token.
27    pub short_token_swap_length: u8,
28    /// Initial long token amount to deposit.
29    pub initial_long_token_amount: u64,
30    /// Initial short otken amount to deposit.
31    pub initial_short_token_amount: u64,
32    /// The minimum acceptable amount of market tokens to receive.
33    pub min_market_token_amount: u64,
34    /// Whether to unwrap native token when sending funds back.
35    pub should_unwrap_native_token: bool,
36}
37
38impl ActionParams for CreateDepositParams {
39    fn execution_lamports(&self) -> u64 {
40        self.execution_lamports
41    }
42}
43
44/// Operation for creating a deposit.
45#[derive(TypedBuilder)]
46pub(crate) struct CreateDepositOperation<'a, 'info> {
47    deposit: AccountLoader<'info, Deposit>,
48    market: AccountLoader<'info, Market>,
49    store: AccountLoader<'info, Store>,
50    owner: &'a AccountInfo<'info>,
51    receiver: &'a AccountInfo<'info>,
52    nonce: &'a NonceBytes,
53    bump: u8,
54    #[builder(default)]
55    initial_long_token: Option<&'a Account<'info, TokenAccount>>,
56    #[builder(default)]
57    initial_short_token: Option<&'a Account<'info, TokenAccount>>,
58    market_token: &'a Account<'info, TokenAccount>,
59    params: &'a CreateDepositParams,
60    swap_paths: &'info [AccountInfo<'info>],
61}
62
63impl CreateDepositOperation<'_, '_> {
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            bump,
71            deposit,
72            market,
73            store,
74            owner,
75            receiver,
76            nonce,
77            initial_long_token,
78            initial_short_token,
79            market_token,
80            params,
81            swap_paths,
82        } = self;
83
84        let id = market.load_mut()?.indexer_mut().next_deposit_id()?;
85
86        let mut deposit = deposit.load_init()?;
87
88        deposit.header.init(
89            id,
90            store.key(),
91            market.key(),
92            owner.key(),
93            receiver.key(),
94            *nonce,
95            bump,
96            params.execution_lamports,
97            params.should_unwrap_native_token,
98        )?;
99
100        let (long_token, short_token) = {
101            let market = market.load()?;
102            let meta = market.meta();
103            (meta.long_token_mint, meta.short_token_mint)
104        };
105
106        let primary_token_in = if let Some(account) = initial_long_token {
107            deposit.tokens.initial_long_token.init(account);
108            account.mint
109        } else {
110            long_token
111        };
112
113        let secondary_token_in = if let Some(account) = initial_short_token {
114            deposit.tokens.initial_short_token.init(account);
115            account.mint
116        } else {
117            short_token
118        };
119
120        deposit.tokens.market_token.init(market_token);
121
122        deposit.params.initial_long_token_amount = params.initial_long_token_amount;
123        deposit.params.initial_short_token_amount = params.initial_short_token_amount;
124        deposit.params.min_market_token_amount = params.min_market_token_amount;
125
126        deposit.swap.validate_and_init(
127            &*market.load()?,
128            params.long_token_swap_length,
129            params.short_token_swap_length,
130            swap_paths,
131            &store.key(),
132            (&primary_token_in, &secondary_token_in),
133            (&long_token, &short_token),
134        )?;
135
136        Ok(())
137    }
138
139    fn validate_params_excluding_swap(&self) -> Result<()> {
140        let params = &self.params;
141        require!(
142            params.initial_long_token_amount != 0 || params.initial_short_token_amount != 0,
143            CoreError::EmptyDeposit
144        );
145
146        if params.initial_long_token_amount != 0 {
147            let Some(account) = self.initial_long_token.as_ref() else {
148                return err!(CoreError::TokenAccountNotProvided);
149            };
150            require_gte!(
151                account.amount,
152                params.initial_long_token_amount,
153                CoreError::NotEnoughTokenAmount
154            );
155        }
156
157        if params.initial_short_token_amount != 0 {
158            let Some(account) = self.initial_short_token.as_ref() else {
159                return err!(CoreError::TokenAccountNotProvided);
160            };
161            require_gte!(
162                account.amount,
163                params.initial_short_token_amount,
164                CoreError::NotEnoughTokenAmount
165            );
166        }
167
168        // If the two token accounts are actually the same, then we should check for the sum.
169        let same_initial_token_amount = self.initial_long_token.as_ref().and_then(|long| {
170            self.initial_short_token
171                .as_ref()
172                .and_then(|short| (long.key() == short.key()).then(|| long.amount))
173        });
174        if let Some(amount) = same_initial_token_amount {
175            let total_amount = params
176                .initial_long_token_amount
177                .checked_add(params.initial_short_token_amount)
178                .ok_or_else(|| error!(CoreError::TokenAmountExceedsLimit))?;
179            require_gte!(amount, total_amount, CoreError::NotEnoughTokenAmount);
180        }
181
182        ActionExt::validate_balance(&self.deposit, self.params.execution_lamports)?;
183
184        Ok(())
185    }
186}
187
188/// Operation for executing a deposit.
189#[derive(TypedBuilder)]
190pub(crate) struct ExecuteDepositOperation<'a, 'info> {
191    store: &'a AccountLoader<'info, Store>,
192    market: &'a AccountLoader<'info, Market>,
193    market_token_mint: &'a mut Account<'info, Mint>,
194    market_token_receiver: AccountInfo<'info>,
195    deposit: &'a AccountLoader<'info, Deposit>,
196    oracle: &'a Oracle,
197    remaining_accounts: &'info [AccountInfo<'info>],
198    throw_on_execution_error: bool,
199    token_program: AccountInfo<'info>,
200    #[builder(setter(into))]
201    event_emitter: EventEmitter<'a, 'info>,
202}
203
204impl ExecuteDepositOperation<'_, '_> {
205    pub(crate) fn execute(self) -> Result<bool> {
206        let throw_on_execution_error = self.throw_on_execution_error;
207        match self.validate_oracle() {
208            Ok(()) => {}
209            Err(CoreError::OracleTimestampsAreLargerThanRequired) if !throw_on_execution_error => {
210                msg!(
211                    "Deposit expired at {}",
212                    self.oracle_updated_before()
213                        .ok()
214                        .flatten()
215                        .expect("must have an expiration time"),
216                );
217                return Ok(false);
218            }
219            Err(err) => {
220                return Err(error!(err));
221            }
222        }
223        match self.perfrom_deposit() {
224            Ok(()) => Ok(true),
225            Err(err) if !throw_on_execution_error => {
226                msg!("Execute deposit error: {}", err);
227                Ok(false)
228            }
229            Err(err) => Err(err),
230        }
231    }
232
233    fn validate_oracle(&self) -> CoreResult<()> {
234        self.oracle.validate_time(self)
235    }
236
237    fn validate_before_execution(&self) -> Result<()> {
238        let market = self.market.load()?;
239        market.validate(&self.store.key())?;
240        Ok(())
241    }
242
243    #[inline(never)]
244    fn perfrom_deposit(self) -> Result<()> {
245        self.validate_before_execution()?;
246        {
247            let deposit = self.deposit.load()?;
248            RevertibleLiquidityMarketOperation::new(
249                self.store,
250                self.oracle,
251                self.market,
252                self.market_token_mint,
253                self.token_program.clone(),
254                Some(deposit.swap()),
255                self.remaining_accounts,
256                self.event_emitter,
257            )?
258            .op()?
259            .unchecked_deposit(
260                &deposit.header().receiver(),
261                &self.market_token_receiver,
262                &deposit.params,
263                (
264                    deposit.tokens.initial_long_token.token(),
265                    deposit.tokens.initial_short_token.token(),
266                ),
267                None,
268            )?
269            .commit();
270        }
271        Ok(())
272    }
273}
274
275impl ValidateOracleTime for ExecuteDepositOperation<'_, '_> {
276    fn oracle_updated_after(&self) -> CoreResult<Option<i64>> {
277        Ok(Some(
278            self.deposit
279                .load()
280                .map_err(|_| CoreError::LoadAccountError)?
281                .header()
282                .updated_at,
283        ))
284    }
285
286    fn oracle_updated_before(&self) -> CoreResult<Option<i64>> {
287        let ts = self
288            .store
289            .load()
290            .map_err(|_| CoreError::LoadAccountError)?
291            .request_expiration_at(
292                self.deposit
293                    .load()
294                    .map_err(|_| CoreError::LoadAccountError)?
295                    .header()
296                    .updated_at,
297            )?;
298        Ok(Some(ts))
299    }
300
301    fn oracle_updated_after_slot(&self) -> CoreResult<Option<u64>> {
302        Ok(Some(
303            self.deposit
304                .load()
305                .map_err(|_| CoreError::LoadAccountError)?
306                .header()
307                .updated_at_slot,
308        ))
309    }
310}