1use num_traits::{CheckedAdd, CheckedMul, CheckedSub, Signed, Zero};
2
3use crate::{
4 market::{
5 BaseMarket, BaseMarketExt, BaseMarketMutExt, LiquidityMarketExt, LiquidityMarketMut,
6 SwapMarketMutExt,
7 },
8 num::{MulDiv, Unsigned, UnsignedAbs},
9 params::Fees,
10 price::{Price, Prices},
11 utils, BalanceExt, PnlFactorKind, PoolExt,
12};
13
14use super::MarketAction;
15
16#[must_use = "actions do nothing unless you `execute` them"]
18pub struct Deposit<M: BaseMarket<DECIMALS>, const DECIMALS: u8> {
19 market: M,
20 params: DepositParams<M::Num>,
21}
22
23#[derive(Debug, Clone, Copy)]
25#[cfg_attr(
26 feature = "anchor-lang",
27 derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
28)]
29pub struct DepositParams<T> {
30 long_token_amount: T,
31 short_token_amount: T,
32 prices: Prices<T>,
33}
34
35#[cfg(feature = "gmsol-utils")]
36impl<T: gmsol_utils::InitSpace> gmsol_utils::InitSpace for DepositParams<T> {
37 const INIT_SPACE: usize = 2 * T::INIT_SPACE + Prices::<T>::INIT_SPACE;
38}
39
40impl<T> DepositParams<T> {
41 pub fn long_token_amount(&self) -> &T {
43 &self.long_token_amount
44 }
45
46 pub fn short_token_amount(&self) -> &T {
48 &self.short_token_amount
49 }
50
51 pub fn long_token_price(&self) -> &Price<T> {
53 &self.prices.long_token_price
54 }
55
56 pub fn short_token_price(&self) -> &Price<T> {
58 &self.prices.short_token_price
59 }
60
61 fn reassign_values(&self, is_long_token: bool) -> ReassignedValues<T>
62 where
63 T: Clone,
64 {
65 if is_long_token {
66 ReassignedValues {
67 amount: self.long_token_amount.clone(),
68 price: self.long_token_price(),
69 opposite_price: self.short_token_price(),
70 }
71 } else {
72 ReassignedValues {
73 amount: self.short_token_amount.clone(),
74 price: self.short_token_price(),
75 opposite_price: self.long_token_price(),
76 }
77 }
78 }
79}
80
81#[derive(Debug, Clone, Copy)]
83#[cfg_attr(
84 feature = "anchor-lang",
85 derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
86)]
87pub struct DepositReport<Unsigned, Signed> {
88 params: DepositParams<Unsigned>,
89 minted: Unsigned,
90 price_impact: Signed,
91 fees: [Fees<Unsigned>; 2],
92}
93
94#[cfg(feature = "gmsol-utils")]
95impl<Unsigned, Signed> gmsol_utils::InitSpace for DepositReport<Unsigned, Signed>
96where
97 Unsigned: gmsol_utils::InitSpace,
98 Signed: gmsol_utils::InitSpace,
99{
100 const INIT_SPACE: usize = DepositParams::<Unsigned>::INIT_SPACE
101 + Unsigned::INIT_SPACE
102 + Signed::INIT_SPACE
103 + 2 * Fees::<Unsigned>::INIT_SPACE;
104}
105
106impl<T> DepositReport<T, T::Signed>
107where
108 T: Unsigned,
109{
110 fn new(
111 params: DepositParams<T>,
112 price_impact: T::Signed,
113 minted: T,
114 fees: [Fees<T>; 2],
115 ) -> Self {
116 Self {
117 params,
118 minted,
119 price_impact,
120 fees,
121 }
122 }
123
124 pub fn minted(&self) -> &T {
126 &self.minted
127 }
128
129 pub fn price_impact(&self) -> &T::Signed {
131 &self.price_impact
132 }
133
134 pub fn params(&self) -> &DepositParams<T> {
136 &self.params
137 }
138
139 pub fn long_token_fees(&self) -> &Fees<T> {
141 &self.fees[0]
142 }
143
144 pub fn short_token_fees(&self) -> &Fees<T> {
146 &self.fees[1]
147 }
148}
149
150impl<const DECIMALS: u8, M: LiquidityMarketMut<DECIMALS>> Deposit<M, DECIMALS> {
151 pub fn try_new(
153 market: M,
154 long_token_amount: M::Num,
155 short_token_amount: M::Num,
156 prices: Prices<M::Num>,
157 ) -> Result<Self, crate::Error> {
158 if long_token_amount.is_zero() && short_token_amount.is_zero() {
159 return Err(crate::Error::EmptyDeposit);
160 }
161 Ok(Self {
162 market,
163 params: DepositParams {
164 long_token_amount,
165 short_token_amount,
166 prices,
167 },
168 })
169 }
170
171 fn price_impact(&self) -> crate::Result<(M::Signed, M::Num, M::Num)> {
173 let delta = self.market.liquidity_pool()?.pool_delta_with_amounts(
174 &self
175 .params
176 .long_token_amount
177 .clone()
178 .try_into()
179 .map_err(|_| crate::Error::Convert)?,
180 &self
181 .params
182 .short_token_amount
183 .clone()
184 .try_into()
185 .map_err(|_| crate::Error::Convert)?,
186 &self.params.long_token_price().mid(),
187 &self.params.short_token_price().mid(),
188 )?;
189 let price_impact = delta.price_impact(&self.market.swap_impact_params()?)?;
190 let delta = delta.delta();
191 debug_assert!(!delta.long_value().is_negative(), "must be non-negative");
192 debug_assert!(!delta.short_value().is_negative(), "must be non-negative");
193 Ok((
194 price_impact,
195 delta.long_value().unsigned_abs(),
196 delta.short_value().unsigned_abs(),
197 ))
198 }
199
200 fn charge_fees(
204 &self,
205 is_positive_impact: bool,
206 amount: &mut M::Num,
207 ) -> crate::Result<Fees<M::Num>> {
208 let (amount_after_fees, fees) = self
209 .market
210 .swap_fee_params()?
211 .apply_fees(is_positive_impact, amount)
212 .ok_or(crate::Error::Computation("apply fees"))?;
213 *amount = amount_after_fees;
214 Ok(fees)
215 }
216
217 fn execute_deposit(
218 &mut self,
219 is_long_token: bool,
220 pool_value: M::Num,
221 mut price_impact: M::Signed,
222 ) -> Result<(M::Num, Fees<M::Num>), crate::Error> {
223 let mut mint_amount: M::Num = Zero::zero();
224 let supply = self.market.total_supply();
225
226 if pool_value.is_zero() && !supply.is_zero() {
227 return Err(crate::Error::InvalidPoolValue("deposit"));
228 }
229
230 let ReassignedValues {
231 mut amount,
232 price,
233 opposite_price,
234 } = self.params.reassign_values(is_long_token);
235
236 let fees = self.charge_fees(price_impact.is_positive(), &mut amount)?;
237 self.market.claimable_fee_pool_mut()?.apply_delta_amount(
238 is_long_token,
239 &fees
240 .fee_amount_for_receiver()
241 .clone()
242 .try_into()
243 .map_err(|_| crate::Error::Convert)?,
244 )?;
245
246 if price_impact.is_positive() && supply.is_zero() {
247 price_impact = Zero::zero();
248 }
249 if price_impact.is_positive() {
250 let positive_impact_amount = self.market.apply_swap_impact_value_with_cap(
251 !is_long_token,
252 opposite_price,
253 &price_impact,
254 )?;
255 mint_amount = mint_amount
256 .checked_add(
257 &utils::usd_to_market_token_amount(
258 positive_impact_amount
259 .checked_mul(opposite_price.pick_price(true))
260 .ok_or(crate::Error::Overflow)?,
261 pool_value.clone(),
262 supply.clone(),
263 self.market.usd_to_amount_divisor(),
264 )
265 .ok_or(crate::Error::Computation("convert positive usd to amount"))?,
266 )
267 .ok_or(crate::Error::Overflow)?;
268 self.market.apply_delta(
269 !is_long_token,
270 &positive_impact_amount
271 .try_into()
272 .map_err(|_| crate::Error::Convert)?,
273 )?;
274 self.market.validate_pool_amount(!is_long_token)?;
275 } else if price_impact.is_negative() {
276 let negative_impact_amount = self.market.apply_swap_impact_value_with_cap(
277 is_long_token,
278 price,
279 &price_impact,
280 )?;
281 amount =
282 amount
283 .checked_sub(&negative_impact_amount)
284 .ok_or(crate::Error::Computation(
285 "deposit: not enough fund to pay negative impact amount",
286 ))?;
287 }
288 mint_amount = mint_amount
289 .checked_add(
290 &utils::usd_to_market_token_amount(
291 amount
292 .checked_mul(price.pick_price(false))
293 .ok_or(crate::Error::Overflow)?,
294 pool_value,
295 supply.clone(),
296 self.market.usd_to_amount_divisor(),
297 )
298 .ok_or(crate::Error::Computation("convert negative usd to amount"))?,
299 )
300 .ok_or(crate::Error::Overflow)?;
301 self.market.apply_delta(
302 is_long_token,
303 &(amount
304 .checked_add(fees.fee_amount_for_pool())
305 .ok_or(crate::Error::Overflow)?)
306 .clone()
307 .try_into()
308 .map_err(|_| crate::Error::Convert)?,
309 )?;
310 self.market.validate_pool_amount(is_long_token)?;
311 self.market
312 .validate_pool_value_for_deposit(&self.params.prices, is_long_token)?;
313 Ok((mint_amount, fees))
314 }
315}
316
317impl<const DECIMALS: u8, M> MarketAction for Deposit<M, DECIMALS>
318where
319 M: LiquidityMarketMut<DECIMALS>,
320{
321 type Report = DepositReport<M::Num, <M::Num as Unsigned>::Signed>;
322
323 fn execute(mut self) -> crate::Result<Self::Report> {
324 debug_assert!(
325 !self.params.long_token_amount.is_zero() || !self.params.short_token_amount.is_zero(),
326 "shouldn't be empty deposit"
327 );
328
329 self.market.validate_max_pnl(
337 &self.params.prices,
338 PnlFactorKind::MaxAfterDeposit,
339 PnlFactorKind::MaxAfterDeposit,
340 )?;
341
342 let report = {
343 let (price_impact, long_token_usd_value, short_token_usd_value) =
344 self.price_impact()?;
345 let mut market_token_to_mint: M::Num = Zero::zero();
346 let pool_value = self.market.pool_value(
347 &self.params.prices,
348 PnlFactorKind::MaxAfterDeposit,
349 true,
350 )?;
351 if pool_value.is_negative() {
352 return Err(crate::Error::InvalidPoolValue(
353 "deposit: current pool value is negative",
354 ));
355 }
356 let mut all_fees = [Default::default(), Default::default()];
357 if !self.params.long_token_amount.is_zero() {
358 let price_impact = long_token_usd_value
359 .clone()
360 .checked_mul_div_with_signed_numerator(
361 &price_impact,
362 &long_token_usd_value
363 .checked_add(&short_token_usd_value)
364 .ok_or(crate::Error::Overflow)?,
365 )
366 .ok_or(crate::Error::Computation("price impact for long"))?;
367 let (mint_amount, fees) =
368 self.execute_deposit(true, pool_value.unsigned_abs(), price_impact)?;
369 market_token_to_mint = market_token_to_mint
370 .checked_add(&mint_amount)
371 .ok_or(crate::Error::Overflow)?;
372 all_fees[0] = fees;
373 }
374 if !self.params.short_token_amount.is_zero() {
375 let price_impact = short_token_usd_value
376 .clone()
377 .checked_mul_div_with_signed_numerator(
378 &price_impact,
379 &long_token_usd_value
380 .checked_add(&short_token_usd_value)
381 .ok_or(crate::Error::Overflow)?,
382 )
383 .ok_or(crate::Error::Computation("price impact for short"))?;
384 let (mint_amount, fees) =
385 self.execute_deposit(false, pool_value.unsigned_abs(), price_impact)?;
386 market_token_to_mint = market_token_to_mint
387 .checked_add(&mint_amount)
388 .ok_or(crate::Error::Overflow)?;
389 all_fees[1] = fees;
390 }
391 DepositReport::new(self.params, price_impact, market_token_to_mint, all_fees)
392 };
393 self.market.mint(&report.minted)?;
394 Ok(report)
395 }
396}
397
398struct ReassignedValues<'a, T> {
399 amount: T,
400 price: &'a Price<T>,
401 opposite_price: &'a Price<T>,
402}
403
404#[cfg(test)]
405mod tests {
406 use crate::{
407 market::LiquidityMarketMutExt,
408 price::Prices,
409 test::{TestMarket, TestMarketConfig},
410 MarketAction,
411 };
412
413 #[test]
414 fn basic() -> Result<(), crate::Error> {
415 let mut market = TestMarket::<u64, 9>::with_config(TestMarketConfig {
416 reserve_factor: 1_050_000_000,
417 ..Default::default()
418 });
419 let prices = Prices::new_for_test(120, 120, 1);
420 println!(
421 "{:#?}",
422 market.deposit(1_000_000_000, 0, prices)?.execute()?
423 );
424 println!("{market:#?}");
425 println!(
426 "{:#?}",
427 market.deposit(1_000_000_000, 0, prices)?.execute()?
428 );
429 println!("{market:#?}");
430 println!(
431 "{:#?}",
432 market.deposit(0, 1_000_000_000, prices)?.execute()?
433 );
434 println!("{market:#?}");
435 Ok(())
436 }
437
438 #[test]
439 fn sequence() -> crate::Result<()> {
440 let mut market_1 = TestMarket::<u64, 9>::default();
441 let prices = Prices::new_for_test(120, 120, 1);
442 println!(
443 "{:#?}",
444 market_1.deposit(1_000_000_000, 0, prices)?.execute()?
445 );
446 println!(
447 "{:#?}",
448 market_1.deposit(1_000_000_000, 0, prices)?.execute()?
449 );
450 println!("{market_1:#?}");
451 let mut market_2 = TestMarket::<u64, 9>::default();
452 println!(
453 "{:#?}",
454 market_2.deposit(2_000_000_000, 0, prices)?.execute()?
455 );
456 println!("{market_1:#?}");
457 println!("{market_2:#?}");
458 Ok(())
459 }
460
461 #[cfg(feature = "u128")]
462 #[test]
463 fn basic_u128() -> Result<(), crate::Error> {
464 let mut market = TestMarket::<u128, 20>::default();
465 let prices = Prices::new_for_test(12_000_000_000_000, 12_000_000_000_000, 100_000_000_000);
466 println!(
467 "{:#?}",
468 market.deposit(1_000_000_000, 0, prices)?.execute()?
469 );
470 println!(
471 "{:#?}",
472 market.deposit(1_000_000_000, 0, prices)?.execute()?
473 );
474 println!(
475 "{:#?}",
476 market.deposit(0, 1_000_000_000, prices)?.execute()?
477 );
478 println!("{market:#?}");
479 Ok(())
480 }
481
482 #[test]
484 fn zero_amount_deposit() -> Result<(), crate::Error> {
485 let mut market = TestMarket::<u64, 9>::default();
486 let prices = Prices::new_for_test(120, 120, 1);
487 let result = market.deposit(0, 0, prices);
488 assert!(result.is_err());
489
490 Ok(())
491 }
492
493 #[test]
495 fn extreme_amount_deposit() -> Result<(), crate::Error> {
496 let mut market = TestMarket::<u64, 9>::default();
497 let prices = Prices::new_for_test(120, 120, 1);
498 let small_amount = 1;
499 let large_amount = u64::MAX;
500 let max_pool_amount = 1000000000000000000;
501 println!("{:#?}", market.deposit(small_amount, 0, prices)?.execute()?);
502 println!("{market:#?}");
503
504 let result = market.deposit(large_amount, 0, prices)?.execute();
505 assert!(result.is_err());
506 println!("{market:#?}");
507
508 let result = market.deposit(max_pool_amount, 0, prices)?.execute();
509 assert!(result.is_err());
510 println!("{market:#?}");
511
512 Ok(())
513 }
514
515 #[test]
517 fn round_attack_deposit() -> Result<(), crate::Error> {
518 let mut market = TestMarket::<u64, 9>::default();
519 let prices = Prices::new_for_test(120, 120, 1);
520 let mut i = 1;
521 while i < 10000000 {
522 i += 1;
523 market.deposit(1, 0, prices)?.execute()?;
524 }
525 println!("{market:#?}");
526
527 let mut market_compare = TestMarket::<u64, 9>::default();
528 market_compare.deposit(10000000 - 1, 0, prices)?.execute()?;
529 println!("{market_compare:#?}");
530 Ok(())
531 }
532
533 #[test]
534 fn concurrent_deposits() -> Result<(), crate::Error> {
535 use std::sync::{Arc, Mutex};
536 use std::thread;
537
538 let market = Arc::new(Mutex::new(TestMarket::<u64, 9>::default()));
539 let prices = Prices::new_for_test(120, 120, 1);
540
541 let handles: Vec<_> = (0..10)
542 .map(|_| {
543 let market = Arc::clone(&market);
544 thread::spawn(move || {
545 let mut market = market.lock().unwrap();
546 market
547 .deposit(1_000_000_000, 0, prices)
548 .unwrap()
549 .execute()
550 .unwrap();
551 })
552 })
553 .collect();
554
555 for handle in handles {
556 handle.join().unwrap();
557 }
558
559 let market = market.lock().unwrap();
560 println!("{:#?}", *market);
561 Ok(())
562 }
563
564 #[test]
565 fn deposit_with_price_fluctuations() -> Result<(), crate::Error> {
566 let mut market = TestMarket::<u64, 9>::default();
567 let initial_prices = Prices::new_for_test(120, 120, 1);
568 let fluctuated_prices = Prices::new_for_test(240, 240, 1);
569 println!(
570 "{:#?}",
571 market
572 .deposit(1_000_000_000, 0, initial_prices)?
573 .execute()?
574 );
575 println!("{market:#?}");
576
577 println!(
578 "{:#?}",
579 market
580 .deposit(1_000_000_000, 0, fluctuated_prices)?
581 .execute()?
582 );
583 println!("{market:#?}");
584 Ok(())
585 }
586}