use crate::account::Signers;
use crate::client::Network;
use crate::earn::vaults::reads::{VaultDetails, VaultEquity};
use crate::earn::vaults::transfer::{VaultTransfer, lockup_ms};
use crate::utils::{Address, BadUnits, Dec, USDC_DECIMALS, cut_units};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Balance {
Vault(Address),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Change {
Rises(Dec),
Falls(Dec),
}
#[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,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Plan<A> {
pub network: Network,
pub action: A,
pub nonce: u64,
pub key: Signers,
pub effect: Effect,
pub free_at: Option<u64>,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum Refused {
#[error("{0} takes no deposits")]
NoDeposits(Address),
#[error("locked until {until} ms")]
Locked { until: u64 },
#[error("{amount} USDC is more than the {held} there is")]
MoreThan { amount: Dec, held: Dec },
#[error(transparent)]
Units(#[from] BadUnits),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Ctx {
pub network: Network,
pub nonce: u64,
pub now_ms: u64,
}
impl Ctx {
const fn plan(&self, action: VaultTransfer, change: Change, free_at: Option<u64>) -> Plan<VaultTransfer> {
let effect = Effect { balance: Balance::Vault(action.vault()), change };
Plan { network: self.network, action, nonce: self.nonce, key: VaultTransfer::KEY, effect, free_at }
}
pub fn deposit(&self, vault: &VaultDetails, usdc: Dec) -> Result<Plan<VaultTransfer>, Refused> {
let action = VaultTransfer::deposit(vault.vault_address, usdc)?;
if !vault.takes_deposits() {
return Err(Refused::NoDeposits(vault.vault_address));
}
let free_at = self.now_ms.saturating_add(lockup_ms(self.network, vault.vault_address));
Ok(self.plan(action, Change::Rises(usdc), Some(free_at)))
}
pub fn withdraw(&self, equity: &VaultEquity, usdc: Dec) -> Result<Plan<VaultTransfer>, Refused> {
let action = VaultTransfer::withdraw(equity.vault_address, usdc)?;
unlocked(equity, self.now_ms)?;
if usdc > equity.equity {
return Err(Refused::MoreThan { amount: usdc, held: equity.equity });
}
Ok(self.plan(action, Change::Falls(usdc), None))
}
pub fn withdraw_all(&self, equity: &VaultEquity) -> Result<Plan<VaultTransfer>, Refused> {
unlocked(equity, self.now_ms)?;
let usd = cut_units(equity.equity, USDC_DECIMALS)?;
let action = VaultTransfer::from_micros(equity.vault_address, false, usd);
let amount = equity.withdrawable(self.now_ms);
Ok(self.plan(action, Change::Falls(amount), None))
}
}
fn unlocked(equity: &VaultEquity, now_ms: u64) -> Result<(), Refused> {
match equity.is_locked(now_ms) {
true => Err(Refused::Locked { until: equity.locked_until_timestamp }),
false => Ok(()),
}
}
#[cfg(test)]
#[path = "tests/plan.rs"]
mod tests;