use crate::client::USDC_INDEX;
use crate::earn::lending::state::{ReserveState, Risk, UserState, reserve};
use crate::utils::{Dec, Round};
pub const RATE_PLACES: u32 = 10;
pub const PROTOCOL_SHARE: Dec = Dec::lit::<1>(1);
pub const USER_BORROW_CAP: Dec = Dec::from_int(50_000_000);
const BASE_RATE: Dec = Dec::lit::<2>(5);
const KINK: Dec = Dec::lit::<1>(8);
const SLOPE: Dec = Dec::lit::<2>(475);
#[must_use]
pub fn borrow_rate(utilization: Dec) -> Dec {
let past = utilization.checked_sub(KINK).unwrap_or(Dec::ZERO).max(Dec::ZERO);
past.checked_mul(SLOPE).and_then(|steep| steep.checked_add(BASE_RATE)).unwrap_or(BASE_RATE)
}
#[must_use]
pub fn supply_rate(borrow_rate: Dec, utilization: Dec) -> Dec {
let kept = Dec::ONE.checked_sub(PROTOCOL_SHARE).unwrap_or(Dec::ONE);
borrow_rate
.checked_mul(utilization)
.and_then(|earned| earned.checked_mul(kept))
.map_or(Dec::ZERO, |rate| rate.round(RATE_PLACES, Round::Nearest))
}
impl ReserveState {
#[must_use]
pub fn utilization_now(&self) -> Option<Dec> {
match self.total_supplied.is_zero() {
true => Some(Dec::ZERO),
false => ratio(self.total_borrowed, self.total_supplied),
}
}
#[must_use]
pub fn supply_rate_now(&self) -> Dec {
supply_rate(self.borrow_yearly_rate, self.utilization)
}
#[must_use]
pub const fn lendable(&self) -> Dec {
self.balance
}
#[must_use]
pub fn value_of(&self, amount: Dec) -> Option<Dec> {
amount.checked_mul(self.oracle_px)
}
#[must_use]
pub fn backing(&self, amount: Dec) -> Option<Dec> {
self.value_of(amount)?.checked_mul(self.ltv)
}
}
fn ratio(part: Dec, whole: Dec) -> Option<Dec> {
if part < Dec::ZERO || whole <= Dec::ZERO {
return None;
}
part.checked_div(whole, RATE_PLACES, Round::Nearest)
}
#[must_use]
pub fn borrowable(held: &[(u32, Dec)], reserves: &[(u32, ReserveState)], state: &UserState) -> Dec {
let Some(usdc) = reserve(reserves, USDC_INDEX) else { return Dec::ZERO };
if state.risk() == Risk::Liquidatable {
return Dec::ZERO;
}
let backed = held
.iter()
.filter(|(token, free)| *token != USDC_INDEX && free.is_positive())
.filter_map(|(token, free)| reserve(reserves, *token)?.backing(*free))
.fold(Dec::ZERO, |sum, backing| sum.checked_add(backing).unwrap_or(sum));
let owed = state
.positions()
.filter_map(|(token, position)| reserve(reserves, token)?.value_of(position.borrow.value))
.fold(Dec::ZERO, |sum, value| sum.checked_add(value).unwrap_or(sum));
backed.checked_sub(owed).unwrap_or(Dec::ZERO).min(usdc.lendable()).min(USER_BORROW_CAP).max(Dec::ZERO)
}
#[cfg(test)]
#[path = "tests/math.rs"]
mod tests;