mod exo;
mod lst;
use fix::prelude::*;
pub use self::exo::ExoExchangeContext;
pub use self::lst::LstExchangeContext;
use crate::conversion::SwapConversion;
use crate::error::CoreError;
use crate::error::CoreError::{
DestinationStablecoin, LevercoinNav, MaxMintable, MaxSwappable,
RebalanceBuySideTarget, RebalanceSellSideLiquidity,
RequestedStablecoinOverMaxMintable, VirtualStablecoinOverhang,
VirtualStablecoinSurplus,
};
use crate::exchange_math::{
collateral_ratio, depeg_stablecoin_nav, levercoin_market_cap,
max_mintable_stablecoin, max_swappable_stablecoin, next_levercoin_mint_nav,
next_levercoin_redeem_nav, total_value_locked,
};
use crate::fees::controller::{FeeExtract, LevercoinFees};
use crate::pyth::{OraclePrice, PriceRange};
use crate::rebalance::math::{
max_buyable_collateral, max_sellable_collateral, midpoint,
};
use crate::rebalance::mode::RebalanceMode;
use crate::rebalance::pricing::{
BuyPriceCurve, RebalanceCurveConfig, RebalancePriceController, SellPriceCurve,
};
pub trait ExchangeContext {
fn total_collateral(&self) -> UFix64<N9>;
fn collateral_usd_price(&self) -> PriceRange<N9>;
fn collateral_oracle_price(&self) -> OraclePrice;
fn sell_curve_config(&self) -> &RebalanceCurveConfig;
fn buy_curve_config(&self) -> &RebalanceCurveConfig;
fn stablecoin_mint_threshold(&self) -> UFix64<N9>;
fn stablecoin_mint_enabled(&self) -> bool {
self.collateral_ratio() >= self.stablecoin_mint_threshold()
}
fn levercoin_mint_enabled(&self) -> bool {
self.rebalance_mode() != RebalanceMode::Depeg
}
fn rebalance_sell_curve(&self) -> Result<SellPriceCurve, CoreError> {
SellPriceCurve::new(
self.collateral_oracle_price(),
self.sell_curve_config(),
)
}
fn rebalance_buy_curve(&self) -> Result<BuyPriceCurve, CoreError> {
BuyPriceCurve::new(self.collateral_oracle_price(), self.buy_curve_config())
}
fn rebalance_sell_active(&self) -> bool {
self
.rebalance_sell_curve()
.is_ok_and(|c| c.is_active(self.collateral_ratio()))
}
fn rebalance_buy_active(&self) -> bool {
self
.rebalance_buy_curve()
.is_ok_and(|c| c.is_active(self.collateral_ratio()))
}
fn rebalance_sell_liquidity(&self) -> Result<UFix64<N9>, CoreError> {
let target_cr = RebalanceMode::Neutral.active_range().start()?;
let virtual_stablecoin = self.virtual_stablecoin_supply()?;
let collateral_usd_price = self.collateral_oracle_price().spot;
let total_collateral = self.total_collateral();
max_sellable_collateral(
target_cr,
virtual_stablecoin,
collateral_usd_price,
total_collateral,
)
.ok_or(RebalanceSellSideLiquidity)
}
fn rebalance_buy_target(&self) -> Result<UFix64<N9>, CoreError> {
let target_cr = RebalanceMode::BuyZone1.active_range().start()?;
let virtual_stablecoin = self.virtual_stablecoin_supply()?;
let collateral_usd_price = self.collateral_oracle_price().spot;
let total_collateral = self.total_collateral();
max_buyable_collateral(
target_cr,
virtual_stablecoin,
collateral_usd_price,
total_collateral,
)
.ok_or(RebalanceBuySideTarget)
}
fn rebalance_buy_target_cr(&self) -> Result<UFix64<N9>, CoreError> {
match self.rebalance_mode() {
RebalanceMode::BuyZone2 => RebalanceMode::BuyZone1.active_range().end(),
RebalanceMode::BuyZone1 => {
let spot = self.collateral_oracle_price().spot;
let cr_spot = self.rebalance_buy_curve()?.cr_at_price(spot)?;
midpoint(self.collateral_ratio(), cr_spot).ok_or(RebalanceBuySideTarget)
}
_ => Err(RebalanceBuySideTarget),
}
}
fn rebalance_buy_amount_at_premium(&self) -> Result<UFix64<N9>, CoreError> {
max_buyable_collateral(
self.rebalance_buy_target_cr()?,
self.virtual_stablecoin_supply()?,
self.collateral_oracle_price().spot,
self.total_collateral(),
)
.ok_or(RebalanceBuySideTarget)
}
fn rebalance_sell_target_cr(&self) -> Result<UFix64<N9>, CoreError> {
match self.rebalance_mode() {
RebalanceMode::SellZone2 => {
RebalanceMode::SellZone1.active_range().start()
}
RebalanceMode::SellZone1 => {
let spot = self.collateral_oracle_price().spot;
let cr_spot = self.rebalance_sell_curve()?.cr_at_price(spot)?;
midpoint(self.collateral_ratio(), cr_spot)
.ok_or(RebalanceSellSideLiquidity)
}
_ => Err(RebalanceSellSideLiquidity),
}
}
fn rebalance_sell_amount_at_discount(&self) -> Result<UFix64<N9>, CoreError> {
max_sellable_collateral(
self.rebalance_sell_target_cr()?,
self.virtual_stablecoin_supply()?,
self.collateral_oracle_price().spot,
self.total_collateral(),
)
.ok_or(RebalanceSellSideLiquidity)
}
fn virtual_stablecoin_supply(&self) -> Result<UFix64<N6>, CoreError>;
fn levercoin_supply(&self) -> Result<UFix64<N6>, CoreError>;
fn rebalance_mode(&self) -> RebalanceMode;
fn collateral_ratio(&self) -> UFix64<N9>;
fn levercoin_fees(&self) -> &LevercoinFees;
fn total_value_locked(&self) -> Result<UFix64<N9>, CoreError> {
total_value_locked(
self.total_collateral(),
self.collateral_usd_price().lower,
)
}
fn levercoin_market_cap(&self) -> Result<UFix64<N9>, CoreError> {
levercoin_market_cap(self.levercoin_supply()?, self.levercoin_mint_nav()?)
}
fn stablecoin_nav(&self) -> Result<UFix64<N9>, CoreError> {
match self.rebalance_mode() {
RebalanceMode::Depeg => depeg_stablecoin_nav(
self.total_collateral(),
self.collateral_usd_price().lower,
self.virtual_stablecoin_supply()?,
),
_ => Ok(UFix64::one()),
}
}
fn levercoin_mint_nav(&self) -> Result<UFix64<N9>, CoreError> {
next_levercoin_mint_nav(
self.total_collateral(),
self.collateral_usd_price(),
self.virtual_stablecoin_supply()?,
self.stablecoin_nav()?,
self.levercoin_supply()?,
)
.ok_or(LevercoinNav)
}
fn levercoin_redeem_nav(&self) -> Result<UFix64<N9>, CoreError> {
next_levercoin_redeem_nav(
self.total_collateral(),
self.collateral_usd_price(),
self.virtual_stablecoin_supply()?,
self.stablecoin_nav()?,
self.levercoin_supply()?,
)
.ok_or(LevercoinNav)
}
fn virtual_stablecoin_overhang(&self) -> Result<UFix64<N6>, CoreError> {
let tvl = self.total_value_locked()?;
let virtual_stablecoin = self.virtual_stablecoin_supply()?;
tvl
.checked_convert::<N6>()
.and_then(|tvl| virtual_stablecoin.checked_sub(&tvl))
.ok_or(VirtualStablecoinOverhang)
}
fn virtual_stablecoin_surplus(&self) -> Result<UFix64<N6>, CoreError> {
let tvl = self.total_value_locked()?;
let virtual_stablecoin = self.virtual_stablecoin_supply()?;
tvl
.checked_convert::<N6>()
.and_then(|tvl| tvl.checked_sub(&virtual_stablecoin))
.ok_or(VirtualStablecoinSurplus)
}
fn projected_rebalance_mode(
&self,
new_total: UFix64<N9>,
new_stablecoin: UFix64<N6>,
) -> Result<RebalanceMode, CoreError> {
let projected_cr = collateral_ratio(
new_total,
self.collateral_usd_price().lower,
new_stablecoin,
)?;
Ok(RebalanceMode::from_cr(projected_cr))
}
fn select_rebalance_mode_for_fees(
&self,
projected: RebalanceMode,
) -> RebalanceMode {
projected.min(self.rebalance_mode())
}
fn swap_conversion(&self) -> Result<SwapConversion, CoreError> {
let levercoin_nav =
PriceRange::new(self.levercoin_redeem_nav()?, self.levercoin_mint_nav()?);
Ok(SwapConversion::new(self.stablecoin_nav()?, levercoin_nav))
}
fn max_mintable_stablecoin(&self) -> Result<UFix64<N6>, CoreError> {
let target = self
.stablecoin_mint_threshold()
.checked_convert()
.ok_or(MaxMintable)?;
max_mintable_stablecoin(
target,
self.total_collateral(),
self.collateral_usd_price().upper,
self.virtual_stablecoin_supply()?,
)
}
fn max_swappable_stablecoin(&self) -> Result<UFix64<N6>, CoreError> {
let target = RebalanceMode::SellZone1
.active_range()
.end()?
.checked_convert()
.ok_or(MaxSwappable)?;
max_swappable_stablecoin(
target,
self.total_value_locked()?,
self.virtual_stablecoin_supply()?,
)
}
fn validate_stablecoin_amount(
&self,
requested: UFix64<N6>,
) -> Result<UFix64<N6>, CoreError> {
let max = self.max_mintable_stablecoin()?;
if requested <= max {
Ok(requested)
} else {
Err(RequestedStablecoinOverMaxMintable)
}
}
fn validate_stablecoin_pnl_profit(
&self,
requested: UFix64<N6>,
) -> Result<UFix64<N6>, CoreError> {
let target = RebalanceMode::SellZone2
.active_range()
.end()?
.checked_convert()
.ok_or(MaxMintable)?;
let max = max_swappable_stablecoin(
target,
self.total_value_locked()?,
self.virtual_stablecoin_supply()?,
)
.unwrap_or_default();
Ok(requested.min(max))
}
fn validate_stablecoin_swap_amount(
&self,
requested: UFix64<N6>,
) -> Result<UFix64<N6>, CoreError> {
let max = self.max_swappable_stablecoin()?;
if requested <= max {
Ok(requested)
} else {
Err(RequestedStablecoinOverMaxMintable)
}
}
fn levercoin_to_stablecoin_fee(
&self,
amount_stablecoin: UFix64<N6>,
) -> Result<FeeExtract<N6>, CoreError> {
let new_stablecoin = self
.virtual_stablecoin_supply()?
.checked_add(&amount_stablecoin)
.ok_or(DestinationStablecoin)?;
let projected =
self.projected_rebalance_mode(self.total_collateral(), new_stablecoin)?;
let mode = self.select_rebalance_mode_for_fees(projected);
let fee = self.levercoin_fees().convert_to_stablecoin_fee(mode)?;
FeeExtract::new(fee, amount_stablecoin)
}
fn stablecoin_to_levercoin_fee(
&self,
amount_stablecoin: UFix64<N6>,
) -> Result<FeeExtract<N6>, CoreError> {
let new_stablecoin = self
.virtual_stablecoin_supply()?
.checked_sub(&amount_stablecoin)
.ok_or(DestinationStablecoin)?;
let projected =
self.projected_rebalance_mode(self.total_collateral(), new_stablecoin)?;
let mode = self.select_rebalance_mode_for_fees(projected);
let fee = self.levercoin_fees().convert_from_stablecoin_fee(mode)?;
FeeExtract::new(fee, amount_stablecoin)
}
}