levana_perpswap_cosmos/contracts/market/
liquidity.rs1use crate::prelude::*;
3use anyhow::{Context, Result};
4use cosmwasm_schema::cw_serde;
5use cosmwasm_std::OverflowError;
6
7#[cw_serde]
9#[derive(Default)]
10pub struct LiquidityStats {
11 pub locked: Collateral,
13 pub unlocked: Collateral,
15 pub total_lp: LpToken,
17 pub total_xlp: LpToken,
19}
20
21impl LiquidityStats {
22 pub fn total_collateral(&self) -> Result<Collateral, OverflowError> {
24 self.locked + self.unlocked
25 }
26
27 pub fn total_tokens(&self) -> Result<LpToken, OverflowError> {
29 self.total_lp + self.total_xlp
30 }
31
32 pub fn lp_to_collateral(&self, lp: LpToken) -> Result<Collateral> {
41 if lp.is_zero() {
42 return Ok(Collateral::zero());
43 }
44 let total_collateral = self.total_collateral()?;
45
46 anyhow::ensure!(
47 !total_collateral.approx_eq(Collateral::zero()),
48 "LiquidityStats::lp_to_collateral: no liquidity is in the pool"
49 );
50 let total_tokens = self.total_tokens()?;
51 debug_assert_ne!(total_tokens, LpToken::zero());
52
53 Ok(Collateral::from_decimal256(
54 total_collateral
55 .into_decimal256()
56 .checked_mul(lp.into_decimal256())?
57 .checked_div(total_tokens.into_decimal256())?,
58 ))
59 }
60
61 pub fn lp_to_collateral_non_zero(&self, lp: NonZero<LpToken>) -> Result<NonZero<Collateral>> {
63 self.lp_to_collateral(lp.raw()).and_then(|c| {
64 NonZero::new(c)
65 .context("lp_to_collateral_non_zero: amount of backing collateral rounded to 0")
66 })
67 }
68
69 pub fn collateral_to_lp(&self, amount: NonZero<Collateral>) -> Result<NonZero<LpToken>> {
73 let total_collateral = self.total_collateral()?;
74
75 NonZero::new(LpToken::from_decimal256(if total_collateral.is_zero() {
76 debug_assert!(self.total_lp.is_zero());
77 debug_assert!(self.total_xlp.is_zero());
78 amount.into_decimal256()
79 } else {
80 self.total_tokens()?
81 .into_decimal256()
82 .checked_mul(amount.into_decimal256())?
83 .checked_div(total_collateral.into_decimal256())?
84 }))
85 .context("liquidity_deposit_inner: new shares is (impossibly) 0")
86 }
87
88 pub fn approx_eq(&self, other: &Self) -> bool {
90 self.locked.approx_eq(other.locked)
91 && self.unlocked.approx_eq(other.unlocked)
92 && self.total_lp.approx_eq(other.total_lp)
93 && self.total_xlp.approx_eq(other.total_xlp)
94 }
95}
96
97pub mod events {
99 use super::LiquidityStats;
100 use crate::prelude::*;
101 use cosmwasm_std::Event;
102
103 pub struct WithdrawEvent {
105 pub burned_shares: NonZero<LpToken>,
107 pub withdrawn_funds: NonZero<Collateral>,
109 pub withdrawn_funds_usd: NonZero<Usd>,
111 }
112
113 impl From<WithdrawEvent> for cosmwasm_std::Event {
114 fn from(src: WithdrawEvent) -> Self {
115 cosmwasm_std::Event::new("liquidity-withdraw").add_attributes(vec![
116 ("burned-shares", src.burned_shares.to_string()),
117 ("withdrawn-funds", src.withdrawn_funds.to_string()),
118 ("withdrawn-funds-usd", src.withdrawn_funds_usd.to_string()),
119 ])
120 }
121 }
122
123 pub struct DepositEvent {
125 pub amount: NonZero<Collateral>,
127 pub amount_usd: NonZero<Usd>,
129 pub shares: NonZero<LpToken>,
131 }
132
133 impl From<DepositEvent> for cosmwasm_std::Event {
134 fn from(src: DepositEvent) -> Self {
135 cosmwasm_std::Event::new("liquidity-deposit").add_attributes(vec![
136 ("amount", src.amount.to_string()),
137 ("amount-usd", src.amount_usd.to_string()),
138 ("shares", src.shares.to_string()),
139 ])
140 }
141 }
142
143 pub struct LockEvent {
145 pub amount: NonZero<Collateral>,
147 }
148
149 impl From<LockEvent> for cosmwasm_std::Event {
150 fn from(src: LockEvent) -> Self {
151 cosmwasm_std::Event::new("liquidity-lock")
152 .add_attribute("amount", src.amount.to_string())
153 }
154 }
155
156 pub struct UnlockEvent {
158 pub amount: NonZero<Collateral>,
160 }
161
162 impl From<UnlockEvent> for cosmwasm_std::Event {
163 fn from(src: UnlockEvent) -> Self {
164 cosmwasm_std::Event::new("liquidity-unlock")
165 .add_attribute("amount", src.amount.to_string())
166 }
167 }
168
169 pub struct LockUpdateEvent {
171 pub amount: Signed<Collateral>,
173 }
174
175 impl From<LockUpdateEvent> for cosmwasm_std::Event {
176 fn from(src: LockUpdateEvent) -> Self {
177 cosmwasm_std::Event::new("liquidity-update")
178 .add_attributes(vec![("amount", src.amount.to_string())])
179 }
180 }
181
182 pub struct LiquidityPoolSizeEvent {
184 pub locked: Collateral,
186 pub locked_usd: Usd,
188 pub unlocked: Collateral,
190 pub unlocked_usd: Usd,
192 pub lp_collateral: Collateral,
194 pub xlp_collateral: Collateral,
196 pub total_lp: LpToken,
198 pub total_xlp: LpToken,
200 }
201
202 impl LiquidityPoolSizeEvent {
203 pub fn from_stats(stats: &LiquidityStats, price: &PricePoint) -> Result<Self> {
205 let total_collateral = stats.total_collateral()?;
206 let total_tokens = stats.total_tokens()?;
207 let (lp_collateral, xlp_collateral) = if total_tokens.is_zero() {
208 debug_assert_eq!(total_collateral, Collateral::zero());
209 (Collateral::zero(), Collateral::zero())
210 } else {
211 let lp_collateral = total_collateral.into_decimal256()
212 * stats.total_lp.into_decimal256()
213 / total_tokens.into_decimal256();
214 let lp_collateral = Collateral::from_decimal256(lp_collateral);
215 let xlp_collateral = (total_collateral - lp_collateral)?;
216
217 (lp_collateral, xlp_collateral)
218 };
219
220 Ok(Self {
221 locked: stats.locked,
222 locked_usd: price.collateral_to_usd(stats.locked),
223 unlocked: stats.unlocked,
224 unlocked_usd: price.collateral_to_usd(stats.unlocked),
225 lp_collateral,
226 xlp_collateral,
227 total_lp: stats.total_lp,
228 total_xlp: stats.total_xlp,
229 })
230 }
231 }
232
233 impl From<LiquidityPoolSizeEvent> for cosmwasm_std::Event {
234 fn from(src: LiquidityPoolSizeEvent) -> Self {
235 cosmwasm_std::Event::new("liquidity-pool-size").add_attributes(vec![
236 ("locked", src.locked.to_string()),
237 ("locked-usd", src.locked_usd.to_string()),
238 ("unlocked", src.unlocked.to_string()),
239 ("unlocked-usd", src.unlocked_usd.to_string()),
240 ("lp-collateral", src.lp_collateral.to_string()),
241 ("xlp-collateral", src.xlp_collateral.to_string()),
242 ("total-lp", src.total_lp.to_string()),
243 ("total-xlp", src.total_xlp.to_string()),
244 ])
245 }
246 }
247
248 impl TryFrom<Event> for LiquidityPoolSizeEvent {
249 type Error = anyhow::Error;
250
251 fn try_from(evt: Event) -> anyhow::Result<Self> {
252 Ok(LiquidityPoolSizeEvent {
253 locked: evt.decimal_attr("locked")?,
254 locked_usd: evt.decimal_attr("locked-usd")?,
255 unlocked: evt.decimal_attr("unlocked")?,
256 unlocked_usd: evt.decimal_attr("unlocked-usd")?,
257 lp_collateral: evt.decimal_attr("lp-collateral")?,
258 xlp_collateral: evt.decimal_attr("xlp-collateral")?,
259 total_lp: evt.decimal_attr("total-lp")?,
260 total_xlp: evt.decimal_attr("total-xlp")?,
261 })
262 }
263 }
264
265 #[derive(Debug)]
267 pub struct DeltaNeutralityRatioEvent {
268 pub total_liquidity: Collateral,
270 pub long_interest: Notional,
272 pub short_interest: Notional,
274 pub net_notional: Signed<Notional>,
276 pub price_notional: Price,
278 pub delta_neutrality_ratio: Signed<Decimal256>,
280 }
281
282 impl From<DeltaNeutralityRatioEvent> for cosmwasm_std::Event {
283 fn from(
284 DeltaNeutralityRatioEvent {
285 total_liquidity,
286 long_interest,
287 short_interest,
288 net_notional,
289 price_notional,
290 delta_neutrality_ratio,
291 }: DeltaNeutralityRatioEvent,
292 ) -> Self {
293 cosmwasm_std::Event::new("delta-neutrality-ratio").add_attributes(vec![
294 ("total-liquidity", total_liquidity.to_string()),
295 ("long-interest", long_interest.to_string()),
296 ("short-interest", short_interest.to_string()),
297 ("net-notional", net_notional.to_string()),
298 ("price-notional", price_notional.to_string()),
299 ("delta-neutrality-ratio", delta_neutrality_ratio.to_string()),
300 ])
301 }
302 }
303
304 impl TryFrom<Event> for DeltaNeutralityRatioEvent {
305 type Error = anyhow::Error;
306
307 fn try_from(evt: Event) -> anyhow::Result<Self> {
308 Ok(DeltaNeutralityRatioEvent {
309 total_liquidity: evt.decimal_attr("total-liquidity")?,
310 long_interest: evt.decimal_attr("long-interest")?,
311 short_interest: evt.decimal_attr("short-interest")?,
312 net_notional: evt.number_attr("net-notional")?,
313 price_notional: Price::try_from_number(evt.number_attr("price-notional")?)?,
314 delta_neutrality_ratio: evt.number_attr("delta-neutrality-ratio")?,
315 })
316 }
317 }
318}