use crate::account::Signers;
use crate::client::Network;
use crate::earn::lending::action::{BadLend, BorrowLend};
use crate::earn::lending::state::{ReserveState, UserState, reserve};
use crate::utils::Dec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Balance {
Supplied(u32),
Borrowed(u32),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Change {
Rises(Dec),
Falls(Dec),
Empties,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Effect {
pub balance: Balance,
pub change: Change,
}
impl Effect {
#[must_use]
pub fn landed(&self, before: Dec, after: Dec) -> bool {
match self.change {
Change::Rises(_) => after > before,
Change::Falls(_) => after < before,
Change::Empties => after.is_zero(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Plan<A> {
pub network: Network,
pub action: A,
pub nonce: u64,
pub key: Signers,
pub effect: Effect,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum Refused {
#[error("token {0} has no lending reserve")]
NoReserve(u32),
#[error("{amount} is more than the {held} there is")]
MoreThan { amount: Dec, held: Dec },
#[error("nothing of token {0} is there to take back")]
Nothing(u32),
#[error(transparent)]
Lend(#[from] BadLend),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Ctx {
pub network: Network,
pub nonce: u64,
}
impl Ctx {
const fn plan(&self, action: BorrowLend, effect: Effect) -> Plan<BorrowLend> {
Plan { network: self.network, action, nonce: self.nonce, key: BorrowLend::KEY, effect }
}
pub fn supply(
&self,
token: u32,
amount: Dec,
reserves: &[(u32, ReserveState)],
) -> Result<Plan<BorrowLend>, Refused> {
let action = BorrowLend::supply(token, amount)?;
reserve(reserves, token).ok_or(Refused::NoReserve(token))?;
Ok(self.plan(action, Effect { balance: Balance::Supplied(token), change: Change::Rises(amount) }))
}
pub fn withdraw(&self, token: u32, amount: Option<Dec>, state: &UserState) -> Result<Plan<BorrowLend>, Refused> {
let held = state.supplied(token);
let (action, change) = back(token, amount, held, BorrowLend::withdraw, BorrowLend::withdraw_all)?;
Ok(self.plan(action, Effect { balance: Balance::Supplied(token), change }))
}
pub fn borrow(
&self,
token: u32,
amount: Dec,
reserves: &[(u32, ReserveState)],
) -> Result<Plan<BorrowLend>, Refused> {
let action = BorrowLend::borrow(token, amount)?;
let lendable = reserve(reserves, token).ok_or(Refused::NoReserve(token))?.lendable();
if amount > lendable {
return Err(Refused::MoreThan { amount, held: lendable });
}
Ok(self.plan(action, Effect { balance: Balance::Borrowed(token), change: Change::Rises(amount) }))
}
pub fn repay(&self, token: u32, amount: Option<Dec>, state: &UserState) -> Result<Plan<BorrowLend>, Refused> {
let held = state.borrowed(token);
let (action, change) = back(token, amount, held, BorrowLend::repay, BorrowLend::repay_all)?;
Ok(self.plan(action, Effect { balance: Balance::Borrowed(token), change }))
}
}
fn back(
token: u32,
amount: Option<Dec>,
held: Dec,
some: fn(u32, Dec) -> Result<BorrowLend, BadLend>,
all: fn(u32) -> BorrowLend,
) -> Result<(BorrowLend, Change), Refused> {
if !held.is_positive() {
return Err(Refused::Nothing(token));
}
match amount {
None => Ok((all(token), Change::Empties)),
Some(amount) => {
let action = some(token, amount)?;
match amount > held {
true => Err(Refused::MoreThan { amount, held }),
false => Ok((action, Change::Falls(amount))),
}
}
}
}
#[cfg(test)]
#[path = "tests/plan.rs"]
mod tests;