use crate::client::{ApiError, Transport, info, info_request, user_request};
use crate::utils::{Address, Dec, Figure, Json};
use serde::Deserialize;
#[must_use]
pub fn all_reserve_states_request() -> String {
info_request("allBorrowLendReserveStates", &[])
}
#[must_use]
pub fn reserve_state_request(token: u32) -> String {
info_request("borrowLendReserveState", &[("token", Json::Number(token.to_string()))])
}
#[must_use]
pub fn user_state_request(user: Address) -> String {
user_request("borrowLendUserState", user)
}
pub async fn all_reserve_states(transport: &impl Transport) -> Result<Vec<(u32, ReserveState)>, ApiError> {
info(transport, all_reserve_states_request()).await
}
pub async fn reserve_state(transport: &impl Transport, token: u32) -> Result<ReserveState, ApiError> {
info(transport, reserve_state_request(token)).await
}
pub async fn user_state(transport: &impl Transport, user: Address) -> Result<UserState, ApiError> {
info(transport, user_state_request(user)).await
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ReserveState {
pub borrow_yearly_rate: Dec,
pub supply_yearly_rate: Dec,
pub balance: Dec,
pub utilization: Dec,
pub oracle_px: Dec,
pub ltv: Dec,
pub total_supplied: Dec,
pub total_borrowed: Dec,
}
#[must_use]
pub fn reserve(reserves: &[(u32, ReserveState)], token: u32) -> Option<&ReserveState> {
reserves.iter().find(|(index, _)| *index == token).map(|(_, reserve)| reserve)
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserState {
#[serde(default)]
pub token_to_state: Vec<(u32, Position)>,
pub health: String,
#[serde(default)]
pub health_factor: Option<Figure>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Risk {
Safe,
Near,
Liquidatable,
}
impl UserState {
#[must_use]
pub fn position(&self, token: u32) -> Option<&Position> {
self.token_to_state.iter().find(|(index, _)| *index == token).map(|(_, position)| position)
}
pub fn positions(&self) -> impl Iterator<Item = (u32, &Position)> {
self.token_to_state
.iter()
.filter(|(_, position)| !position.supply.value.is_zero() || !position.borrow.value.is_zero())
.map(|(token, position)| (*token, position))
}
#[must_use]
pub fn supplied(&self, token: u32) -> Dec {
self.position(token).map_or(Dec::ZERO, |position| position.supply.value)
}
#[must_use]
pub fn borrowed(&self, token: u32) -> Dec {
self.position(token).map_or(Dec::ZERO, |position| position.borrow.value)
}
#[must_use]
pub fn is_healthy(&self) -> bool {
self.health == "healthy"
}
#[must_use]
pub fn risk(&self) -> Risk {
match self.health.as_str() {
"healthy" => Risk::Safe,
"liquidatable" => Risk::Liquidatable,
_ => Risk::Near,
}
}
#[must_use]
pub fn factor(&self) -> Option<Dec> {
self.health_factor.as_ref().and_then(Figure::to_dec)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)]
pub struct Position {
#[serde(default)]
pub borrow: Basis,
#[serde(default)]
pub supply: Basis,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)]
pub struct Basis {
#[serde(default)]
pub basis: Dec,
pub value: Dec,
}
impl Basis {
#[must_use]
pub fn interest(&self) -> Option<Dec> {
self.value.checked_sub(self.basis)
}
}
#[cfg(test)]
#[path = "tests/lend.rs"]
mod tests;