df_hyperliquid_core/earn/lending/
plan.rs1use crate::account::Signers;
6use crate::client::Network;
7use crate::earn::lending::action::{BadLend, BorrowLend};
8use crate::earn::lending::state::{ReserveState, UserState, reserve};
9use crate::utils::Dec;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub enum Balance {
15 Supplied(u32),
16 Borrowed(u32),
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Change {
23 Rises(Dec),
24 Falls(Dec),
25 Empties,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct Effect {
32 pub balance: Balance,
33 pub change: Change,
34}
35
36impl Effect {
37 #[must_use]
39 pub fn landed(&self, before: Dec, after: Dec) -> bool {
40 match self.change {
41 Change::Rises(_) => after > before,
42 Change::Falls(_) => after < before,
43 Change::Empties => after.is_zero(),
44 }
45 }
46}
47
48#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct Plan<A> {
51 pub network: Network,
52 pub action: A,
53 pub nonce: u64,
54 pub key: Signers,
55 pub effect: Effect,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
60pub enum Refused {
61 #[error("token {0} has no lending reserve")]
62 NoReserve(u32),
63 #[error("{amount} is more than the {held} there is")]
64 MoreThan { amount: Dec, held: Dec },
65 #[error("nothing of token {0} is there to take back")]
66 Nothing(u32),
67 #[error(transparent)]
68 Lend(#[from] BadLend),
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub struct Ctx {
74 pub network: Network,
75 pub nonce: u64,
76}
77
78impl Ctx {
79 const fn plan(&self, action: BorrowLend, effect: Effect) -> Plan<BorrowLend> {
80 Plan { network: self.network, action, nonce: self.nonce, key: BorrowLend::KEY, effect }
81 }
82
83 pub fn supply(
85 &self,
86 token: u32,
87 amount: Dec,
88 reserves: &[(u32, ReserveState)],
89 ) -> Result<Plan<BorrowLend>, Refused> {
90 let action = BorrowLend::supply(token, amount)?;
91 reserve(reserves, token).ok_or(Refused::NoReserve(token))?;
92 Ok(self.plan(action, Effect { balance: Balance::Supplied(token), change: Change::Rises(amount) }))
93 }
94
95 pub fn withdraw(&self, token: u32, amount: Option<Dec>, state: &UserState) -> Result<Plan<BorrowLend>, Refused> {
97 let held = state.supplied(token);
98 let (action, change) = back(token, amount, held, BorrowLend::withdraw, BorrowLend::withdraw_all)?;
99 Ok(self.plan(action, Effect { balance: Balance::Supplied(token), change }))
100 }
101
102 pub fn borrow(
104 &self,
105 token: u32,
106 amount: Dec,
107 reserves: &[(u32, ReserveState)],
108 ) -> Result<Plan<BorrowLend>, Refused> {
109 let action = BorrowLend::borrow(token, amount)?;
110 let lendable = reserve(reserves, token).ok_or(Refused::NoReserve(token))?.lendable();
111 if amount > lendable {
112 return Err(Refused::MoreThan { amount, held: lendable });
113 }
114 Ok(self.plan(action, Effect { balance: Balance::Borrowed(token), change: Change::Rises(amount) }))
115 }
116
117 pub fn repay(&self, token: u32, amount: Option<Dec>, state: &UserState) -> Result<Plan<BorrowLend>, Refused> {
119 let held = state.borrowed(token);
120 let (action, change) = back(token, amount, held, BorrowLend::repay, BorrowLend::repay_all)?;
121 Ok(self.plan(action, Effect { balance: Balance::Borrowed(token), change }))
122 }
123}
124
125fn back(
127 token: u32,
128 amount: Option<Dec>,
129 held: Dec,
130 some: fn(u32, Dec) -> Result<BorrowLend, BadLend>,
131 all: fn(u32) -> BorrowLend,
132) -> Result<(BorrowLend, Change), Refused> {
133 if !held.is_positive() {
134 return Err(Refused::Nothing(token));
135 }
136 match amount {
137 None => Ok((all(token), Change::Empties)),
138 Some(amount) => {
139 let action = some(token, amount)?;
140 match amount > held {
141 true => Err(Refused::MoreThan { amount, held }),
142 false => Ok((action, Change::Falls(amount))),
143 }
144 }
145 }
146}
147
148#[cfg(test)]
149#[path = "tests/plan.rs"]
150mod tests;