use anchor_lang::prelude::*;
use anchor_spl::token::Mint;
use fix::prelude::*;
use crate::error::CoreError::ExoAmountNormalization;
pub fn normalize_mint_exp(mint: &Mint, amount: u64) -> Result<UFix64<N9>> {
match mint.decimals {
2 => UFix64::<N2>::new(amount).checked_convert(),
3 => UFix64::<N3>::new(amount).checked_convert(),
4 => UFix64::<N4>::new(amount).checked_convert(),
5 => UFix64::<N5>::new(amount).checked_convert(),
6 => UFix64::<N6>::new(amount).checked_convert(),
7 => UFix64::<N7>::new(amount).checked_convert(),
8 => UFix64::<N8>::new(amount).checked_convert(),
9 => Some(UFix64::<N9>::new(amount)),
10 => UFix64::<N10>::new(amount).checked_convert(),
_ => None,
}
.ok_or(ExoAmountNormalization.into())
}
pub fn denormalize_mint_exp(mint: &Mint, amount: UFix64<N9>) -> Result<u64> {
match mint.decimals {
2 => amount.checked_convert::<N2>().map(|o| o.bits),
3 => amount.checked_convert::<N3>().map(|o| o.bits),
4 => amount.checked_convert::<N4>().map(|o| o.bits),
5 => amount.checked_convert::<N5>().map(|o| o.bits),
6 => amount.checked_convert::<N6>().map(|o| o.bits),
7 => amount.checked_convert::<N7>().map(|o| o.bits),
8 => amount.checked_convert::<N8>().map(|o| o.bits),
9 => Some(amount.bits),
10 => amount.checked_convert::<N10>().map(|o| o.bits),
_ => None,
}
.ok_or(ExoAmountNormalization.into())
}
#[macro_export]
macro_rules! eq_tolerance {
($l:expr, $r:expr, $place:ty, $tol:expr) => {{
let diff = $l.convert::<$place>().abs_diff(&$r.convert::<$place>());
diff <= $tol
}};
}
#[cfg(test)]
pub mod proptest {
use fix::prelude::*;
use fix::typenum::{N2, N6, N9};
use proptest::prelude::*;
use crate::exchange_math::collateral_ratio;
#[derive(Debug)]
pub struct ProtocolState {
pub usd_sol_price: UFix64<N9>,
pub stablecoin_amount: UFix64<N6>,
pub stablecoin_nav: UFix64<N9>,
pub levercoin_amount: UFix64<N6>,
pub levercoin_nav: UFix64<N9>,
}
impl ProtocolState {
#[must_use]
pub fn total_sol(&self) -> Option<UFix64<N9>> {
let stablecoin_cap = self
.stablecoin_amount
.mul_div_floor(self.stablecoin_nav, UFix64::one())?;
let levercoin_cap = self
.levercoin_amount
.mul_div_floor(self.levercoin_nav, UFix64::one())?;
let tvl = stablecoin_cap.checked_add(&levercoin_cap)?;
tvl
.convert()
.mul_div_floor(UFix64::one(), self.usd_sol_price)
}
#[must_use]
pub fn collateral_ratio(&self) -> Option<UFix64<N9>> {
collateral_ratio(
self.total_sol()?,
self.usd_sol_price,
self.stablecoin_amount,
)
.ok()
}
#[must_use]
pub fn next_target_collateral_ratio(&self) -> Option<UFix64<N2>> {
let current = self.collateral_ratio()?.convert::<N2>();
let one = UFix64::<N2>::new(100);
let next = current.mul_div_ceil(UFix64::new(90), one)?;
if next < one {
None
} else {
Some(next)
}
}
}
prop_compose! {
pub fn protocol_state(_: ())
(usd_sol_price in usd_sol_price(),
stablecoin_amount in token_amount(),
levercoin_amount in token_amount(),
levercoin_nav in levercoin_nav()) -> ProtocolState {
ProtocolState {
usd_sol_price,
stablecoin_amount,
stablecoin_nav: UFix64::one(),
levercoin_amount,
levercoin_nav,
}
}
}
prop_compose! {
pub fn protocol_state_depeg(_: ())
(usd_sol_price in usd_sol_price(),
stablecoin_amount in token_amount(),
stablecoin_nav in stablecoin_nav(),
levercoin_amount in token_amount(),
levercoin_nav in levercoin_nav()) -> ProtocolState {
ProtocolState {
usd_sol_price,
stablecoin_amount,
stablecoin_nav,
levercoin_amount,
levercoin_nav,
}
}
}
pub fn usd_sol_price() -> BoxedStrategy<UFix64<N9>> {
(10_000_000_000u64..2_500_000_000_000u64)
.prop_map(UFix64::new)
.boxed()
}
pub fn token_amount() -> BoxedStrategy<UFix64<N6>> {
(1_0000u64..5_000_000_000_000u64)
.prop_map(UFix64::new)
.boxed()
}
pub fn stablecoin_nav() -> BoxedStrategy<UFix64<N9>> {
(800_000_000u64..1_000_000_000u64)
.prop_map(UFix64::new)
.boxed()
}
pub fn levercoin_nav() -> BoxedStrategy<UFix64<N9>> {
(100_000u64..1_000_000_000_000u64)
.prop_map(UFix64::new)
.boxed()
}
pub fn lst_sol_price() -> BoxedStrategy<UFix64<N9>> {
(1_000_000_000u64..5_000_000_000u64)
.prop_map(UFix64::new)
.boxed()
}
pub fn lst_sol_price_extreme() -> BoxedStrategy<UFix64<N9>> {
(1u64..u64::MAX / 2).prop_map(UFix64::new).boxed()
}
pub fn lst_amount() -> BoxedStrategy<UFix64<N9>> {
(1_000u64..100_000_000_000_000u64)
.prop_map(UFix64::new)
.boxed()
}
pub fn lst_amount_extreme() -> BoxedStrategy<UFix64<N9>> {
(1u64..u64::MAX / 2).prop_map(UFix64::new).boxed()
}
}
#[cfg(test)]
mod tests {
use fix::aliases::si::{Micro, Nano};
use fix::prelude::*;
use crate::error::CoreError::SlippageExceeded;
use crate::slippage_config::SlippageConfig;
#[test]
fn one_nano() {
let one = Nano::<u64>::one();
assert_eq!("1000000000x10^-9", format!("{one:?}"));
}
#[test]
fn precision6_unit() {
let one = Micro::<u64>::one();
let unit = one * one;
assert_eq!(one, unit.convert());
}
#[test]
fn precision6_overflow_guard() {
let max = Micro::new(u128::MAX);
assert!(max.checked_mul(&max).is_none());
}
#[test]
fn neg_sub_underflows() {
let last = Nano::new(169_120_000_u64);
let now = Nano::new(151_444_800_u64);
let sub = now.checked_sub(&last);
assert!(sub.is_none());
}
#[test]
fn slippage_neg() {
let config =
SlippageConfig::new(UFix64::<N6>::new(1_201_346), UFix64::new(20));
let amount = UFix64::<N6>::new(1_198_942);
let out = config.validate_token_out(amount);
assert_eq!(out, Err(SlippageExceeded.into()));
}
#[test]
fn slippage_pos() {
let config =
SlippageConfig::new(UFix64::<N6>::new(99_411_501), UFix64::new(10));
let amount = UFix64::<N6>::new(99_312_089);
let out = config.validate_token_out(amount);
assert!(out.is_ok());
}
}