use anchor_lang::prelude::*;
use fix::prelude::*;
use crate::error::CoreError::{
LeverToStable, LstToToken, StableToLever, TokenToLst,
};
use crate::pyth::PriceRange;
pub struct Conversion {
pub usd_sol_price: PriceRange<N8>,
pub lst_sol_price: UFix64<N9>,
}
impl Conversion {
#[must_use]
pub fn new(usd_sol_price: PriceRange<N8>, lst_sol_price: UFix64<N9>) -> Self {
Conversion {
usd_sol_price,
lst_sol_price,
}
}
pub fn lst_to_token(
&self,
amount_lst: UFix64<N9>,
token_nav: UFix64<N9>,
) -> Result<UFix64<N6>> {
amount_lst
.mul_div_floor(self.lst_sol_price, UFix64::one())
.and_then(|sol| {
sol.mul_div_floor(self.usd_sol_price.lower.convert(), token_nav)
})
.map(UFix64::convert)
.ok_or(LstToToken.into())
}
pub fn token_to_lst(
&self,
amount_token: UFix64<N6>,
token_nav: UFix64<N9>,
) -> Result<UFix64<N9>> {
amount_token
.convert::<N9>()
.mul_div_floor(token_nav, self.usd_sol_price.upper.convert())
.and_then(|sol| sol.mul_div_floor(UFix64::one(), self.lst_sol_price))
.ok_or(TokenToLst.into())
}
}
pub struct SwapConversion {
pub stablecoin_nav: UFix64<N9>,
pub levercoin_nav: PriceRange<N9>,
}
impl SwapConversion {
#[must_use]
pub fn new(
stablecoin_nav: UFix64<N9>,
levercoin_nav: PriceRange<N9>,
) -> Self {
SwapConversion {
stablecoin_nav,
levercoin_nav,
}
}
pub fn stable_to_lever(
&self,
amount_stable: UFix64<N6>,
) -> Result<UFix64<N6>> {
amount_stable
.mul_div_floor(self.stablecoin_nav, UFix64::one())
.and_then(|usd| {
usd.mul_div_floor(UFix64::one(), self.levercoin_nav.upper)
})
.ok_or(StableToLever.into())
}
pub fn lever_to_stable(
&self,
amount_lever: UFix64<N6>,
) -> Result<UFix64<N6>> {
amount_lever
.mul_div_floor(self.levercoin_nav.lower, UFix64::one())
.and_then(|usd| usd.mul_div_floor(UFix64::one(), self.stablecoin_nav))
.ok_or(LeverToStable.into())
}
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::*;
use crate::eq_tolerance;
use crate::util::proptest::*;
proptest! {
#[test]
fn lst_to_stablecoin_roundtrip(
state in protocol_state(()),
lst_sol_price in lst_sol_price(),
lst_amount in lst_amount(),
) {
let usd_sol_price = PriceRange::one(state.usd_sol_price);
let conversion = Conversion::new(usd_sol_price, lst_sol_price);
let amount_token = conversion.lst_to_token(lst_amount, state.stablecoin_nav)?;
let back_amount_lst = conversion.token_to_lst(amount_token, state.stablecoin_nav)?;
prop_assert!(
eq_tolerance!(lst_amount, back_amount_lst, N9, UFix64::new(1000))
);
}
#[test]
fn lst_to_levercoin_roundtrip(
state in protocol_state(()),
lst_sol_price in lst_sol_price(),
lst_amount in lst_amount(),
) {
let usd_sol_price = PriceRange::one(state.usd_sol_price);
let conversion = Conversion::new(usd_sol_price, lst_sol_price);
let amount_token = conversion.lst_to_token(lst_amount, state.levercoin_nav)?;
let back_amount_lst = conversion.token_to_lst(amount_token, state.levercoin_nav)?;
prop_assert!(
eq_tolerance!(lst_amount, back_amount_lst, N9, UFix64::new(100_000))
);
}
#[test]
fn stablecoin_to_lst_roundtrip(
state in protocol_state(()),
lst_sol_price in lst_sol_price(),
) {
let usd_sol_price = PriceRange::one(state.usd_sol_price);
let conversion = Conversion::new(usd_sol_price, lst_sol_price);
let amount_lst = conversion.token_to_lst(state.stablecoin_amount, state.stablecoin_nav)?;
let back_amount_token = conversion.lst_to_token(amount_lst, state.stablecoin_nav)?;
prop_assert!(
eq_tolerance!(state.stablecoin_amount, back_amount_token, N6, UFix64::new(1000))
);
}
#[test]
fn levercoin_to_lst_roundtrip(
state in protocol_state(()),
lst_sol_price in lst_sol_price(),
) {
let usd_sol_price = PriceRange::one(state.usd_sol_price);
let conversion = Conversion::new(usd_sol_price, lst_sol_price);
let amount_lst = conversion.token_to_lst(state.levercoin_amount, state.levercoin_nav)?;
let back_amount_levercoin = conversion.lst_to_token(amount_lst, state.levercoin_nav)?;
prop_assert!(
eq_tolerance!(state.levercoin_amount, back_amount_levercoin, N6, UFix64::new(1000))
);
}
}
#[test]
fn amount_to_mint_lever() -> Result<()> {
let usd_sol_price = PriceRange::one(UFix64::<N8>::new(17_103_000_000));
let lst_sol = UFix64::<N9>::new(1_736_835_834);
let conversion = Conversion::new(usd_sol_price, lst_sol);
let amount_in = UFix64::<N9>::new(50_123_303_006);
let nav = UFix64::<N9>::new(100_232_580_000);
let out = conversion.lst_to_token(amount_in, nav)?;
assert_eq!(UFix64::new(148_546_300), out);
Ok(())
}
#[test]
fn amount_to_mint_stable() -> Result<()> {
let usd_sol_price = PriceRange::one(UFix64::<N8>::new(17_103_000_000));
let lst_sol = UFix64::<N9>::new(1_736_835_834);
let conversion = Conversion::new(usd_sol_price, lst_sol);
let amount_in = UFix64::<N9>::new(568);
let out = conversion.lst_to_token(amount_in, UFix64::one())?;
assert_eq!(UFix64::new(168), out);
Ok(())
}
#[test]
fn amount_to_redeem_stable() -> Result<()> {
let usd_sol_price = PriceRange::one(UFix64::<N8>::new(17_103_000_000));
let lst_sol = UFix64::<N9>::new(1_110_462_847);
let conversion = Conversion::new(usd_sol_price, lst_sol);
let amount = UFix64::<N6>::new(9_937_412_179);
let lst_out: UFix64<N9> = conversion.token_to_lst(amount, UFix64::one())?;
assert_eq!(UFix64::new(52_323_522_668), lst_out);
Ok(())
}
#[test]
fn amount_to_redeem_lever() -> Result<()> {
let usd_sol_price = PriceRange::one(UFix64::<N8>::new(17_103_000_000));
let lst_sol = UFix64::<N9>::new(1_110_462_847);
let conversion = Conversion::new(usd_sol_price, lst_sol);
let nav = UFix64::<N9>::new(137_992_981_000);
let amount = UFix64::<N6>::new(543_150_099);
let lst_out: UFix64<N9> = conversion.token_to_lst(amount, nav)?;
assert_eq!(UFix64::new(394_639_480_798), lst_out);
Ok(())
}
proptest! {
#[test]
fn stable_lever_roundtrip(
stablecoin_nav in stablecoin_nav(),
levercoin_nav in levercoin_nav(),
amount_stable in token_amount(),
) {
let conversion = SwapConversion::new(stablecoin_nav, PriceRange::one(levercoin_nav));
let amount_lever = conversion.stable_to_lever(amount_stable)?;
let amount_stable_out = conversion.lever_to_stable(amount_lever)?;
prop_assert!(
eq_tolerance!(amount_stable, amount_stable_out, N6, UFix64::new(10000))
);
}
#[test]
fn lever_stable_roundtrip(
stablecoin_nav in stablecoin_nav(),
levercoin_nav in levercoin_nav(),
amount_lever in token_amount(),
) {
let conversion = SwapConversion::new(stablecoin_nav, PriceRange::one(levercoin_nav));
let amount_stable = conversion.lever_to_stable(amount_lever)?;
let amount_lever_out = conversion.stable_to_lever(amount_stable)?;
prop_assert!(
eq_tolerance!(amount_lever, amount_lever_out, N6, UFix64::new(10000))
);
}
}
}