use fix::prelude::*;
use crate::error::CoreError;
use crate::error::CoreError::CollateralRatioOverflow;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum CollateralRatio {
Finite(UFix64<N9>),
Infinite,
}
pub type CR = CollateralRatio;
impl CollateralRatio {
pub fn new(
total_collateral: UFix64<N9>,
usd_collateral_price: UFix64<N9>,
amount_stablecoin: UFix64<N6>,
) -> Result<CollateralRatio, CoreError> {
if amount_stablecoin == UFix64::zero() {
Ok(CR::Infinite)
} else {
amount_stablecoin
.checked_convert::<N9>()
.and_then(|stablecoin| {
total_collateral.mul_div_floor(usd_collateral_price, stablecoin)
})
.map(CR::Finite)
.ok_or(CollateralRatioOverflow)
}
}
#[must_use]
pub fn as_finite(self) -> Option<UFix64<N9>> {
match self {
CR::Finite(cr) => Some(cr),
CR::Infinite => None,
}
}
#[must_use]
pub fn price_curve_x(self) -> IFix64<N9> {
match self {
CR::Infinite => IFix64::new(i64::MAX),
CR::Finite(cr) => cr.narrow::<i64>().unwrap_or(IFix64::new(i64::MAX)),
}
}
#[must_use]
pub fn fee_curve_x(self) -> IFix64<N5> {
match self {
CR::Infinite => IFix64::new(i64::MAX),
CR::Finite(cr) => cr
.convert::<N5>()
.narrow::<i64>()
.unwrap_or(IFix64::new(i64::MAX)),
}
}
#[must_use]
pub fn at_least(self, bound: UFix64<N9>) -> bool {
self >= CR::Finite(bound)
}
}
impl From<CollateralRatio> for UFix64<N9> {
fn from(cr: CollateralRatio) -> UFix64<N9> {
cr.as_finite().unwrap_or(UFix64::new(u64::MAX))
}
}
impl From<UFix64<N9>> for CollateralRatio {
fn from(cr: UFix64<N9>) -> CollateralRatio {
if cr == UFix64::new(u64::MAX) {
CR::Infinite
} else {
CR::Finite(cr)
}
}
}
#[cfg(test)]
mod tests {
use more_asserts::assert_lt;
use super::*;
const PRICE: UFix64<N9> = UFix64::constant(200_000_000_000);
const COLLATERAL: UFix64<N9> = UFix64::constant(10_000_000_000);
#[test]
fn new_infinite_at_zero_supply() -> Result<(), CoreError> {
let cr = CollateralRatio::new(COLLATERAL, PRICE, UFix64::zero())?;
assert_eq!(cr, CR::Infinite);
Ok(())
}
#[test]
fn new_finite_ratio() -> Result<(), CoreError> {
let supply = UFix64::<N6>::constant(1_000_000_000);
let cr = CollateralRatio::new(COLLATERAL, PRICE, supply)?;
assert_eq!(cr, CR::Finite(UFix64::constant(2_000_000_000)));
Ok(())
}
#[test]
fn new_below_par() -> Result<(), CoreError> {
let total_collateral = UFix64::<N9>::new(8_217_712_567_008);
let price = UFix64::<N9>::new(137_704_920_000);
let supply = UFix64::<N6>::new(1_150_380_112_112);
let cr = CollateralRatio::new(total_collateral, price, supply)?;
assert_eq!(cr, CR::Finite(UFix64::new(983_691_772)));
Ok(())
}
#[test]
fn new_above_par() -> Result<(), CoreError> {
let total_collateral = UFix64::<N9>::new(976_123_127_719);
let price = UFix64::<N9>::new(137_704_920_000);
let supply = UFix64::<N6>::new(97_411_342_200);
let cr = CollateralRatio::new(total_collateral, price, supply)?;
assert_eq!(cr, CR::Finite(UFix64::new(1_379_890_207)));
Ok(())
}
#[test]
fn infinite_orders_above_every_finite() {
let max = CR::Finite(UFix64::new(u64::MAX));
assert_lt!(max, CR::Infinite);
assert_lt!(CR::Finite(UFix64::zero()), max);
}
#[test]
fn infinite_saturates_curve_space() {
let infinite = CR::Infinite;
assert_eq!(infinite.price_curve_x(), IFix64::<N9>::new(i64::MAX));
assert_eq!(infinite.fee_curve_x(), IFix64::<N5>::new(i64::MAX));
}
#[test]
fn finite_beyond_signed_range_saturates() {
let cr = CR::Finite(UFix64::new(u64::MAX));
assert_eq!(cr.price_curve_x(), IFix64::new(i64::MAX));
assert_lt!(cr.fee_curve_x(), IFix64::new(i64::MAX));
}
#[test]
fn finite_in_range_converts_exactly() {
let cr = CR::Finite(UFix64::constant(1_500_000_000));
assert_eq!(cr.price_curve_x(), IFix64::constant(1_500_000_000));
assert_eq!(cr.fee_curve_x(), IFix64::constant(150_000));
}
#[test]
fn round_trip_infinite() {
let raw: UFix64<N9> = CR::Infinite.into();
assert_eq!(raw, UFix64::new(u64::MAX));
assert_eq!(CR::from(raw), CR::Infinite);
}
#[test]
fn round_trip_finite() {
let cr = CR::Finite(UFix64::constant(1_500_000_000));
let raw: UFix64<N9> = cr.into();
assert_eq!(raw, UFix64::constant(1_500_000_000));
assert_eq!(CR::from(raw), cr);
}
#[test]
fn at_least_finite_bound() {
let bound = UFix64::<N9>::constant(1_350_000_000);
assert!(CR::Infinite.at_least(bound));
assert!(CR::Finite(bound).at_least(bound));
assert!(!CR::Finite(UFix64::constant(1_200_000_000)).at_least(bound));
}
}