use crate::prelude::*;
use anyhow::{Context, Result};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::OverflowError;
#[cw_serde]
#[derive(Default)]
pub struct LiquidityStats {
pub locked: Collateral,
pub unlocked: Collateral,
pub total_lp: LpToken,
pub total_xlp: LpToken,
}
impl LiquidityStats {
pub fn total_collateral(&self) -> Result<Collateral, OverflowError> {
self.locked + self.unlocked
}
pub fn total_tokens(&self) -> Result<LpToken, OverflowError> {
self.total_lp + self.total_xlp
}
pub fn lp_to_collateral(&self, lp: LpToken) -> Result<Collateral> {
if lp.is_zero() {
return Ok(Collateral::zero());
}
let total_collateral = self.total_collateral()?;
anyhow::ensure!(
!total_collateral.approx_eq(Collateral::zero()),
"LiquidityStats::lp_to_collateral: no liquidity is in the pool"
);
let total_tokens = self.total_tokens()?;
debug_assert_ne!(total_tokens, LpToken::zero());
Ok(Collateral::from_decimal256(
total_collateral
.into_decimal256()
.checked_mul(lp.into_decimal256())?
.checked_div(total_tokens.into_decimal256())?,
))
}
pub fn lp_to_collateral_non_zero(&self, lp: NonZero<LpToken>) -> Result<NonZero<Collateral>> {
self.lp_to_collateral(lp.raw()).and_then(|c| {
NonZero::new(c)
.context("lp_to_collateral_non_zero: amount of backing collateral rounded to 0")
})
}
pub fn collateral_to_lp(&self, amount: NonZero<Collateral>) -> Result<NonZero<LpToken>> {
let total_collateral = self.total_collateral()?;
NonZero::new(LpToken::from_decimal256(if total_collateral.is_zero() {
debug_assert!(self.total_lp.is_zero());
debug_assert!(self.total_xlp.is_zero());
amount.into_decimal256()
} else {
self.total_tokens()?
.into_decimal256()
.checked_mul(amount.into_decimal256())?
.checked_div(total_collateral.into_decimal256())?
}))
.context("liquidity_deposit_inner: new shares is (impossibly) 0")
}
pub fn approx_eq(&self, other: &Self) -> bool {
self.locked.approx_eq(other.locked)
&& self.unlocked.approx_eq(other.unlocked)
&& self.total_lp.approx_eq(other.total_lp)
&& self.total_xlp.approx_eq(other.total_xlp)
}
}
pub mod events {
use super::LiquidityStats;
use crate::prelude::*;
use cosmwasm_std::Event;
pub struct WithdrawEvent {
pub burned_shares: NonZero<LpToken>,
pub withdrawn_funds: NonZero<Collateral>,
pub withdrawn_funds_usd: NonZero<Usd>,
}
impl From<WithdrawEvent> for cosmwasm_std::Event {
fn from(src: WithdrawEvent) -> Self {
cosmwasm_std::Event::new("liquidity-withdraw").add_attributes(vec![
("burned-shares", src.burned_shares.to_string()),
("withdrawn-funds", src.withdrawn_funds.to_string()),
("withdrawn-funds-usd", src.withdrawn_funds_usd.to_string()),
])
}
}
pub struct DepositEvent {
pub amount: NonZero<Collateral>,
pub amount_usd: NonZero<Usd>,
pub shares: NonZero<LpToken>,
}
impl From<DepositEvent> for cosmwasm_std::Event {
fn from(src: DepositEvent) -> Self {
cosmwasm_std::Event::new("liquidity-deposit").add_attributes(vec![
("amount", src.amount.to_string()),
("amount-usd", src.amount_usd.to_string()),
("shares", src.shares.to_string()),
])
}
}
pub struct LockEvent {
pub amount: NonZero<Collateral>,
}
impl From<LockEvent> for cosmwasm_std::Event {
fn from(src: LockEvent) -> Self {
cosmwasm_std::Event::new("liquidity-lock")
.add_attribute("amount", src.amount.to_string())
}
}
pub struct UnlockEvent {
pub amount: NonZero<Collateral>,
}
impl From<UnlockEvent> for cosmwasm_std::Event {
fn from(src: UnlockEvent) -> Self {
cosmwasm_std::Event::new("liquidity-unlock")
.add_attribute("amount", src.amount.to_string())
}
}
pub struct LockUpdateEvent {
pub amount: Signed<Collateral>,
}
impl From<LockUpdateEvent> for cosmwasm_std::Event {
fn from(src: LockUpdateEvent) -> Self {
cosmwasm_std::Event::new("liquidity-update")
.add_attributes(vec![("amount", src.amount.to_string())])
}
}
pub struct LiquidityPoolSizeEvent {
pub locked: Collateral,
pub locked_usd: Usd,
pub unlocked: Collateral,
pub unlocked_usd: Usd,
pub lp_collateral: Collateral,
pub xlp_collateral: Collateral,
pub total_lp: LpToken,
pub total_xlp: LpToken,
}
impl LiquidityPoolSizeEvent {
pub fn from_stats(stats: &LiquidityStats, price: &PricePoint) -> Result<Self> {
let total_collateral = stats.total_collateral()?;
let total_tokens = stats.total_tokens()?;
let (lp_collateral, xlp_collateral) = if total_tokens.is_zero() {
debug_assert_eq!(total_collateral, Collateral::zero());
(Collateral::zero(), Collateral::zero())
} else {
let lp_collateral = total_collateral.into_decimal256()
* stats.total_lp.into_decimal256()
/ total_tokens.into_decimal256();
let lp_collateral = Collateral::from_decimal256(lp_collateral);
let xlp_collateral = (total_collateral - lp_collateral)?;
(lp_collateral, xlp_collateral)
};
Ok(Self {
locked: stats.locked,
locked_usd: price.collateral_to_usd(stats.locked),
unlocked: stats.unlocked,
unlocked_usd: price.collateral_to_usd(stats.unlocked),
lp_collateral,
xlp_collateral,
total_lp: stats.total_lp,
total_xlp: stats.total_xlp,
})
}
}
impl From<LiquidityPoolSizeEvent> for cosmwasm_std::Event {
fn from(src: LiquidityPoolSizeEvent) -> Self {
cosmwasm_std::Event::new("liquidity-pool-size").add_attributes(vec![
("locked", src.locked.to_string()),
("locked-usd", src.locked_usd.to_string()),
("unlocked", src.unlocked.to_string()),
("unlocked-usd", src.unlocked_usd.to_string()),
("lp-collateral", src.lp_collateral.to_string()),
("xlp-collateral", src.xlp_collateral.to_string()),
("total-lp", src.total_lp.to_string()),
("total-xlp", src.total_xlp.to_string()),
])
}
}
impl TryFrom<Event> for LiquidityPoolSizeEvent {
type Error = anyhow::Error;
fn try_from(evt: Event) -> anyhow::Result<Self> {
Ok(LiquidityPoolSizeEvent {
locked: evt.decimal_attr("locked")?,
locked_usd: evt.decimal_attr("locked-usd")?,
unlocked: evt.decimal_attr("unlocked")?,
unlocked_usd: evt.decimal_attr("unlocked-usd")?,
lp_collateral: evt.decimal_attr("lp-collateral")?,
xlp_collateral: evt.decimal_attr("xlp-collateral")?,
total_lp: evt.decimal_attr("total-lp")?,
total_xlp: evt.decimal_attr("total-xlp")?,
})
}
}
#[derive(Debug)]
pub struct DeltaNeutralityRatioEvent {
pub total_liquidity: Collateral,
pub long_interest: Notional,
pub short_interest: Notional,
pub net_notional: Signed<Notional>,
pub price_notional: Price,
pub delta_neutrality_ratio: Signed<Decimal256>,
}
impl From<DeltaNeutralityRatioEvent> for cosmwasm_std::Event {
fn from(
DeltaNeutralityRatioEvent {
total_liquidity,
long_interest,
short_interest,
net_notional,
price_notional,
delta_neutrality_ratio,
}: DeltaNeutralityRatioEvent,
) -> Self {
cosmwasm_std::Event::new("delta-neutrality-ratio").add_attributes(vec![
("total-liquidity", total_liquidity.to_string()),
("long-interest", long_interest.to_string()),
("short-interest", short_interest.to_string()),
("net-notional", net_notional.to_string()),
("price-notional", price_notional.to_string()),
("delta-neutrality-ratio", delta_neutrality_ratio.to_string()),
])
}
}
impl TryFrom<Event> for DeltaNeutralityRatioEvent {
type Error = anyhow::Error;
fn try_from(evt: Event) -> anyhow::Result<Self> {
Ok(DeltaNeutralityRatioEvent {
total_liquidity: evt.decimal_attr("total-liquidity")?,
long_interest: evt.decimal_attr("long-interest")?,
short_interest: evt.decimal_attr("short-interest")?,
net_notional: evt.number_attr("net-notional")?,
price_notional: Price::try_from_number(evt.number_attr("price-notional")?)?,
delta_neutrality_ratio: evt.number_attr("delta-neutrality-ratio")?,
})
}
}
}