1use crate::{
2 market::{BaseMarket, BaseMarketExt, LiquidityMarketExt, LiquidityMarketMut},
3 num::{MulDiv, Unsigned, UnsignedAbs},
4 params::Fees,
5 price::{Price, Prices},
6 utils, BalanceExt, PnlFactorKind, PoolExt,
7};
8use num_traits::{CheckedAdd, CheckedDiv, Signed, Zero};
9
10use super::MarketAction;
11
12#[must_use = "actions do nothing unless you `execute` them"]
14pub struct Withdrawal<M: BaseMarket<DECIMALS>, const DECIMALS: u8> {
15 market: M,
16 params: WithdrawParams<M::Num>,
17}
18
19#[derive(Debug, Clone, Copy)]
21#[cfg_attr(
22 feature = "anchor-lang",
23 derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
24)]
25pub struct WithdrawParams<T> {
26 market_token_amount: T,
27 prices: Prices<T>,
28}
29
30#[cfg(feature = "gmsol-utils")]
31impl<T: gmsol_utils::InitSpace> gmsol_utils::InitSpace for WithdrawParams<T> {
32 const INIT_SPACE: usize = T::INIT_SPACE + Prices::<T>::INIT_SPACE;
33}
34
35impl<T> WithdrawParams<T> {
36 pub fn market_token_amount(&self) -> &T {
38 &self.market_token_amount
39 }
40
41 pub fn long_token_price(&self) -> &Price<T> {
43 &self.prices.long_token_price
44 }
45
46 pub fn short_token_price(&self) -> &Price<T> {
48 &self.prices.short_token_price
49 }
50}
51
52#[must_use = "`long_token_output` and `short_token_output` must be used"]
54#[derive(Debug, Clone, Copy)]
55#[cfg_attr(
56 feature = "anchor-lang",
57 derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
58)]
59pub struct WithdrawReport<T> {
60 params: WithdrawParams<T>,
61 long_token_fees: Fees<T>,
62 short_token_fees: Fees<T>,
63 long_token_output: T,
64 short_token_output: T,
65}
66
67#[cfg(feature = "gmsol-utils")]
68impl<T: gmsol_utils::InitSpace> gmsol_utils::InitSpace for WithdrawReport<T> {
69 const INIT_SPACE: usize =
70 WithdrawParams::<T>::INIT_SPACE + 2 * Fees::<T>::INIT_SPACE + 2 * T::INIT_SPACE;
71}
72
73impl<T> WithdrawReport<T> {
74 pub fn params(&self) -> &WithdrawParams<T> {
76 &self.params
77 }
78
79 pub fn long_token_fees(&self) -> &Fees<T> {
81 &self.long_token_fees
82 }
83
84 pub fn short_token_fees(&self) -> &Fees<T> {
86 &self.short_token_fees
87 }
88
89 #[must_use = "the returned amount of long tokens should be transferred out from the market vault"]
91 pub fn long_token_output(&self) -> &T {
92 &self.long_token_output
93 }
94
95 #[must_use = "the returned amount of short tokens should be transferred out from the market vault"]
97 pub fn short_token_output(&self) -> &T {
98 &self.short_token_output
99 }
100}
101
102impl<const DECIMALS: u8, M: LiquidityMarketMut<DECIMALS>> Withdrawal<M, DECIMALS> {
103 pub fn try_new(
105 market: M,
106 market_token_amount: M::Num,
107 prices: Prices<M::Num>,
108 ) -> crate::Result<Self> {
109 if market_token_amount.is_zero() {
110 return Err(crate::Error::EmptyWithdrawal);
111 }
112 prices.validate()?;
113 Ok(Self {
114 market,
115 params: WithdrawParams {
116 market_token_amount,
117 prices,
118 },
119 })
120 }
121
122 fn output_amounts(&self) -> crate::Result<(M::Num, M::Num)> {
123 let pool_value = self.market.pool_value(
124 &self.params.prices,
125 PnlFactorKind::MaxAfterWithdrawal,
126 false,
127 )?;
128 if pool_value.is_negative() {
129 return Err(crate::Error::InvalidPoolValue(
130 "withdrawal: current pool value is negative",
131 ));
132 }
133 if pool_value.is_zero() {
134 return Err(crate::Error::InvalidPoolValue(
135 "withdrawal: current pool value is zero",
136 ));
137 }
138 let total_supply = self.market.total_supply();
139
140 let pool = self.market.liquidity_pool()?;
143 let long_token_value =
144 pool.long_usd_value(self.params.long_token_price().pick_price(true))?;
145 let short_token_value =
146 pool.short_usd_value(self.params.short_token_price().pick_price(true))?;
147 let total_pool_token_value =
148 long_token_value
149 .checked_add(&short_token_value)
150 .ok_or(crate::Error::Computation(
151 "calculating total liquidity pool value",
152 ))?;
153
154 let market_token_value = utils::market_token_amount_to_usd(
155 &self.params.market_token_amount,
156 &pool_value.unsigned_abs(),
157 &total_supply,
158 )
159 .ok_or(crate::Error::Computation("amount to usd"))?;
160
161 debug_assert!(!self.params.long_token_price().has_zero());
162 debug_assert!(!self.params.short_token_price().has_zero());
163 let long_token_amount = market_token_value
164 .checked_mul_div(&long_token_value, &total_pool_token_value)
165 .and_then(|a| a.checked_div(self.params.long_token_price().pick_price(true)))
166 .ok_or(crate::Error::Computation("long token amount"))?;
167 let short_token_amount = market_token_value
168 .checked_mul_div(&short_token_value, &total_pool_token_value)
169 .and_then(|a| a.checked_div(self.params.short_token_price().pick_price(true)))
170 .ok_or(crate::Error::Computation("short token amount"))?;
171 Ok((long_token_amount, short_token_amount))
172 }
173
174 fn charge_fees(&self, amount: &mut M::Num) -> crate::Result<Fees<M::Num>> {
175 let (amount_after_fees, fees) = self
176 .market
177 .swap_fee_params()?
178 .apply_fees(false, amount)
179 .ok_or(crate::Error::Computation("apply fees"))?;
180 *amount = amount_after_fees;
181 Ok(fees)
182 }
183}
184
185impl<const DECIMALS: u8, M: LiquidityMarketMut<DECIMALS>> MarketAction for Withdrawal<M, DECIMALS> {
186 type Report = WithdrawReport<M::Num>;
187
188 fn execute(mut self) -> crate::Result<Self::Report> {
189 let (mut long_token_amount, mut short_token_amount) = self.output_amounts()?;
190 let long_token_fees = self.charge_fees(&mut long_token_amount)?;
191 let short_token_fees = self.charge_fees(&mut short_token_amount)?;
192 let pool = self.market.claimable_fee_pool_mut()?;
194 pool.apply_delta_amount(
195 true,
196 &long_token_fees
197 .fee_amount_for_receiver()
198 .clone()
199 .try_into()
200 .map_err(|_| crate::Error::Convert)?,
201 )?;
202 pool.apply_delta_amount(
203 false,
204 &short_token_fees
205 .fee_amount_for_receiver()
206 .clone()
207 .try_into()
208 .map_err(|_| crate::Error::Convert)?,
209 )?;
210 let pool = self.market.liquidity_pool_mut()?;
213
214 let delta = long_token_fees
215 .fee_amount_for_receiver()
216 .checked_add(&long_token_amount)
217 .ok_or(crate::Error::Overflow)?
218 .to_opposite_signed()?;
219 pool.apply_delta_amount(true, &delta)?;
220
221 let delta = short_token_fees
222 .fee_amount_for_receiver()
223 .checked_add(&short_token_amount)
224 .ok_or(crate::Error::Overflow)?
225 .to_opposite_signed()?;
226 pool.apply_delta_amount(false, &delta)?;
227
228 self.market.validate_reserve(&self.params.prices, true)?;
229 self.market.validate_reserve(&self.params.prices, false)?;
230 self.market.validate_max_pnl(
231 &self.params.prices,
232 PnlFactorKind::MaxAfterWithdrawal,
233 PnlFactorKind::MaxAfterWithdrawal,
234 )?;
235
236 self.market.burn(&self.params.market_token_amount)?;
237
238 Ok(WithdrawReport {
239 params: self.params,
240 long_token_fees,
241 short_token_fees,
242 long_token_output: long_token_amount,
243 short_token_output: short_token_amount,
244 })
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use crate::{
251 market::LiquidityMarketMutExt, pool::Balance, price::Prices, test::TestMarket, BaseMarket,
252 LiquidityMarket, MarketAction,
253 };
254
255 #[test]
256 fn basic() -> crate::Result<()> {
257 let mut market = TestMarket::<u64, 9>::default();
258 let prices = Prices::new_for_test(120, 120, 1);
259 market.deposit(1_000_000_000, 0, prices)?.execute()?;
260 market.deposit(1_000_000_000, 0, prices)?.execute()?;
261 market.deposit(0, 1_000_000_000, prices)?.execute()?;
262 println!("{market:#?}");
263 let before_supply = market.total_supply();
264 let before_long_amount = market.liquidity_pool()?.long_amount()?;
265 let before_short_amount = market.liquidity_pool()?.short_amount()?;
266 let prices = Prices::new_for_test(120, 120, 1);
267 let report = market.withdraw(1_000_000_000, prices)?.execute()?;
268 println!("{report:#?}");
269 println!("{market:#?}");
270 assert_eq!(
271 market.total_supply() + report.params.market_token_amount,
272 before_supply
273 );
274 assert_eq!(
275 market.liquidity_pool()?.long_amount()?
276 + report.long_token_fees.fee_amount_for_receiver()
277 + report.long_token_output,
278 before_long_amount
279 );
280 assert_eq!(
281 market.liquidity_pool()?.short_amount()?
282 + report.short_token_fees.fee_amount_for_receiver()
283 + report.short_token_output,
284 before_short_amount
285 );
286 Ok(())
287 }
288
289 #[test]
291 fn zero_amount_withdrawal() -> crate::Result<()> {
292 let mut market = TestMarket::<u64, 9>::default();
293 let prices = Prices::new_for_test(120, 120, 1);
294 market.deposit(1_000_000_000, 0, prices)?.execute()?;
295 market.deposit(0, 1_000_000_000, prices)?.execute()?;
296 let result = market.withdraw(0, prices);
297 assert!(result.is_err());
298 Ok(())
299 }
300
301 #[test]
303 fn over_amount_withdrawal() -> crate::Result<()> {
304 let mut market = TestMarket::<u64, 9>::default();
305 let prices = Prices::new_for_test(120, 120, 1);
306 market.deposit(1_000_000, 0, prices)?.execute()?;
307 market.deposit(0, 1_000_000, prices)?.execute()?;
308 println!("{market:#?}");
309
310 let result = market.withdraw(1_000_000_000, prices)?.execute();
311 assert!(result.is_err());
312 println!("{market:#?}");
313 Ok(())
314 }
315
316 #[test]
318 fn small_amount_withdrawal() -> crate::Result<()> {
319 let mut market = TestMarket::<u64, 9>::default();
320 let prices = Prices::new_for_test(120, 120, 1);
321 market.deposit(1_000_000_000, 0, prices)?.execute()?;
322 market.deposit(1_000_000_000, 0, prices)?.execute()?;
323 market.deposit(0, 1_000_000_000, prices)?.execute()?;
324 println!("{market:#?}");
325 let before_supply = market.total_supply();
326 let before_long_amount = market.liquidity_pool()?.long_amount()?;
327 let before_short_amount = market.liquidity_pool()?.short_amount()?;
328 let prices = Prices::new_for_test(120, 120, 1);
329
330 let small_amount = 1;
331 let report = market.withdraw(small_amount, prices)?.execute()?;
332 println!("{report:#?}");
333 println!("{market:#?}");
334 assert_eq!(
335 market.total_supply() + report.params.market_token_amount,
336 before_supply
337 );
338 assert_eq!(
339 market.liquidity_pool()?.long_amount()?
340 + report.long_token_fees.fee_amount_for_receiver()
341 + report.long_token_output,
342 before_long_amount
343 );
344 assert_eq!(
345 market.liquidity_pool()?.short_amount()?
346 + report.short_token_fees.fee_amount_for_receiver()
347 + report.short_token_output,
348 before_short_amount
349 );
350
351 Ok(())
352 }
353}