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#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
21pub struct CreateDepositParams {
22 pub execution_lamports: u64,
24 pub long_token_swap_length: u8,
26 pub short_token_swap_length: u8,
28 pub initial_long_token_amount: u64,
30 pub initial_short_token_amount: u64,
32 pub min_market_token_amount: u64,
34 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#[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 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 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#[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}