1use crate::{
2 market::{BaseMarket, BaseMarketExt, BaseMarketMutExt, LiquidityMarketExt, LiquidityMarketMut},
3 num::{MulDiv, Unsigned, UnsignedAbs},
4 params::Fees,
5 pool::delta::BalanceChange,
6 price::{Price, Prices},
7 utils, BalanceExt, PnlFactorKind, PoolExt,
8};
9use num_traits::{CheckedAdd, CheckedDiv, Signed, Zero};
10
11use super::MarketAction;
12
13#[must_use = "actions do nothing unless you `execute` them"]
15pub struct Withdrawal<M: BaseMarket<DECIMALS>, const DECIMALS: u8> {
16 market: M,
17 params: WithdrawParams<M::Num>,
18}
19
20#[derive(Debug, Clone, Copy)]
22#[cfg_attr(
23 feature = "anchor-lang",
24 derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
25)]
26pub struct WithdrawParams<T> {
27 market_token_amount: T,
28 prices: Prices<T>,
29}
30
31#[cfg(feature = "gmsol-utils")]
32impl<T: gmsol_utils::InitSpace> gmsol_utils::InitSpace for WithdrawParams<T> {
33 const INIT_SPACE: usize = T::INIT_SPACE + Prices::<T>::INIT_SPACE;
34}
35
36impl<T> WithdrawParams<T> {
37 pub fn market_token_amount(&self) -> &T {
39 &self.market_token_amount
40 }
41
42 pub fn long_token_price(&self) -> &Price<T> {
44 &self.prices.long_token_price
45 }
46
47 pub fn short_token_price(&self) -> &Price<T> {
49 &self.prices.short_token_price
50 }
51}
52
53#[must_use = "`long_token_output` and `short_token_output` must be used"]
55#[derive(Debug, Clone, Copy)]
56#[cfg_attr(
57 feature = "anchor-lang",
58 derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)
59)]
60pub struct WithdrawReport<T> {
61 params: WithdrawParams<T>,
62 long_token_fees: Fees<T>,
63 short_token_fees: Fees<T>,
64 long_token_output: T,
65 short_token_output: T,
66}
67
68#[cfg(feature = "gmsol-utils")]
69impl<T: gmsol_utils::InitSpace> gmsol_utils::InitSpace for WithdrawReport<T> {
70 const INIT_SPACE: usize =
71 WithdrawParams::<T>::INIT_SPACE + 2 * Fees::<T>::INIT_SPACE + 2 * T::INIT_SPACE;
72}
73
74impl<T> WithdrawReport<T> {
75 pub fn params(&self) -> &WithdrawParams<T> {
77 &self.params
78 }
79
80 pub fn long_token_fees(&self) -> &Fees<T> {
82 &self.long_token_fees
83 }
84
85 pub fn short_token_fees(&self) -> &Fees<T> {
87 &self.short_token_fees
88 }
89
90 #[must_use = "the returned amount of long tokens should be transferred out from the market vault"]
92 pub fn long_token_output(&self) -> &T {
93 &self.long_token_output
94 }
95
96 #[must_use = "the returned amount of short tokens should be transferred out from the market vault"]
98 pub fn short_token_output(&self) -> &T {
99 &self.short_token_output
100 }
101}
102
103impl<const DECIMALS: u8, M: LiquidityMarketMut<DECIMALS>> Withdrawal<M, DECIMALS> {
104 pub fn try_new(
106 market: M,
107 market_token_amount: M::Num,
108 prices: Prices<M::Num>,
109 ) -> crate::Result<Self> {
110 if market_token_amount.is_zero() {
111 return Err(crate::Error::EmptyWithdrawal);
112 }
113 prices.validate()?;
114 Ok(Self {
115 market,
116 params: WithdrawParams {
117 market_token_amount,
118 prices,
119 },
120 })
121 }
122
123 fn output_amounts(&self) -> crate::Result<(M::Num, M::Num)> {
124 let pool_value = self.market.pool_value(
125 &self.params.prices,
126 PnlFactorKind::MaxAfterWithdrawal,
127 false,
128 )?;
129 if pool_value.is_negative() {
130 return Err(crate::Error::InvalidPoolValue(
131 "withdrawal: current pool value is negative",
132 ));
133 }
134 if pool_value.is_zero() {
135 return Err(crate::Error::InvalidPoolValue(
136 "withdrawal: current pool value is zero",
137 ));
138 }
139 let total_supply = self.market.total_supply();
140
141 let pool = self.market.liquidity_pool()?;
144 let long_token_value =
145 pool.long_usd_value(self.params.long_token_price().pick_price(true))?;
146 let short_token_value =
147 pool.short_usd_value(self.params.short_token_price().pick_price(true))?;
148 let total_pool_token_value =
149 long_token_value
150 .checked_add(&short_token_value)
151 .ok_or(crate::Error::Computation(
152 "calculating total liquidity pool value",
153 ))?;
154
155 let market_token_value = utils::market_token_amount_to_usd(
156 &self.params.market_token_amount,
157 &pool_value.unsigned_abs(),
158 &total_supply,
159 )
160 .ok_or(crate::Error::Computation("amount to usd"))?;
161
162 debug_assert!(!self.params.long_token_price().has_zero());
163 debug_assert!(!self.params.short_token_price().has_zero());
164 let long_token_amount = market_token_value
165 .checked_mul_div(&long_token_value, &total_pool_token_value)
166 .and_then(|a| a.checked_div(self.params.long_token_price().pick_price(true)))
167 .ok_or(crate::Error::Computation("long token amount"))?;
168 let short_token_amount = market_token_value
169 .checked_mul_div(&short_token_value, &total_pool_token_value)
170 .and_then(|a| a.checked_div(self.params.short_token_price().pick_price(true)))
171 .ok_or(crate::Error::Computation("short token amount"))?;
172 Ok((long_token_amount, short_token_amount))
173 }
174
175 fn charge_fees(&self, amount: &mut M::Num) -> crate::Result<Fees<M::Num>> {
176 let (amount_after_fees, fees) = self
177 .market
178 .swap_fee_params()?
179 .apply_fees(BalanceChange::Worsened, amount)
180 .ok_or(crate::Error::Computation("apply fees"))?;
181 *amount = amount_after_fees;
182 Ok(fees)
183 }
184}
185
186impl<const DECIMALS: u8, M: LiquidityMarketMut<DECIMALS>> MarketAction for Withdrawal<M, DECIMALS> {
187 type Report = WithdrawReport<M::Num>;
188
189 fn execute(mut self) -> crate::Result<Self::Report> {
190 let (mut long_token_amount, mut short_token_amount) = self.output_amounts()?;
191 let long_token_fees = self.charge_fees(&mut long_token_amount)?;
192 let short_token_fees = self.charge_fees(&mut short_token_amount)?;
193 let pool = self.market.claimable_fee_pool_mut()?;
195 pool.apply_delta_amount(
196 true,
197 &long_token_fees
198 .fee_amount_for_receiver()
199 .clone()
200 .try_into()
201 .map_err(|_| crate::Error::Convert)?,
202 )?;
203 pool.apply_delta_amount(
204 false,
205 &short_token_fees
206 .fee_amount_for_receiver()
207 .clone()
208 .try_into()
209 .map_err(|_| crate::Error::Convert)?,
210 )?;
211 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 self.market.apply_delta(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 self.market.apply_delta(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}