solend_sdk/math/
decimal.rs1#![allow(clippy::assign_op_pattern)]
12#![allow(clippy::ptr_offset_with_cast)]
13#![allow(clippy::manual_range_contains)]
14
15use crate::{
16 error::LendingError,
17 math::{common::*, Rate},
18};
19use solana_program::program_error::ProgramError;
20use std::{convert::TryFrom, fmt};
21use uint::construct_uint;
22
23construct_uint! {
25 pub struct U192(3);
26}
27
28#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Eq, Ord)]
30pub struct Decimal(pub U192);
31
32impl Decimal {
33 pub fn one() -> Self {
35 Self(Self::wad())
36 }
37
38 pub fn zero() -> Self {
40 Self(U192::zero())
41 }
42
43 fn wad() -> U192 {
45 U192::from(WAD)
46 }
47
48 fn half_wad() -> U192 {
50 U192::from(HALF_WAD)
51 }
52
53 pub fn from_percent(percent: u8) -> Self {
55 Self(U192::from(percent as u64 * PERCENT_SCALER))
56 }
57
58 #[allow(clippy::wrong_self_convention)]
60 pub fn to_scaled_val(&self) -> Result<u128, ProgramError> {
61 Ok(u128::try_from(self.0).map_err(|_| LendingError::MathOverflow)?)
62 }
63
64 pub fn from_scaled_val(scaled_val: u128) -> Self {
66 Self(U192::from(scaled_val))
67 }
68
69 pub fn try_round_u64(&self) -> Result<u64, ProgramError> {
71 let rounded_val = Self::half_wad()
72 .checked_add(self.0)
73 .ok_or(LendingError::MathOverflow)?
74 .checked_div(Self::wad())
75 .ok_or(LendingError::MathOverflow)?;
76 Ok(u64::try_from(rounded_val).map_err(|_| LendingError::MathOverflow)?)
77 }
78
79 pub fn try_ceil_u64(&self) -> Result<u64, ProgramError> {
81 let ceil_val = Self::wad()
82 .checked_sub(U192::from(1u64))
83 .ok_or(LendingError::MathOverflow)?
84 .checked_add(self.0)
85 .ok_or(LendingError::MathOverflow)?
86 .checked_div(Self::wad())
87 .ok_or(LendingError::MathOverflow)?;
88 Ok(u64::try_from(ceil_val).map_err(|_| LendingError::MathOverflow)?)
89 }
90
91 pub fn try_floor_u64(&self) -> Result<u64, ProgramError> {
93 let ceil_val = self
94 .0
95 .checked_div(Self::wad())
96 .ok_or(LendingError::MathOverflow)?;
97 Ok(u64::try_from(ceil_val).map_err(|_| LendingError::MathOverflow)?)
98 }
99}
100
101impl fmt::Display for Decimal {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 let mut scaled_val = self.0.to_string();
104 if scaled_val.len() <= SCALE {
105 scaled_val.insert_str(0, &vec!["0"; SCALE - scaled_val.len()].join(""));
106 scaled_val.insert_str(0, "0.");
107 } else {
108 scaled_val.insert(scaled_val.len() - SCALE, '.');
109 }
110 f.write_str(&scaled_val)
111 }
112}
113
114impl From<u64> for Decimal {
115 fn from(val: u64) -> Self {
116 Self(Self::wad() * U192::from(val))
117 }
118}
119
120impl From<u128> for Decimal {
121 fn from(val: u128) -> Self {
122 Self(Self::wad() * U192::from(val))
123 }
124}
125
126impl From<Rate> for Decimal {
127 fn from(val: Rate) -> Self {
128 Self(U192::from(val.to_scaled_val()))
129 }
130}
131
132impl TryAdd for Decimal {
133 fn try_add(self, rhs: Self) -> Result<Self, ProgramError> {
134 Ok(Self(
135 self.0
136 .checked_add(rhs.0)
137 .ok_or(LendingError::MathOverflow)?,
138 ))
139 }
140}
141
142impl TrySub for Decimal {
143 fn try_sub(self, rhs: Self) -> Result<Self, ProgramError> {
144 Ok(Self(
145 self.0
146 .checked_sub(rhs.0)
147 .ok_or(LendingError::MathOverflow)?,
148 ))
149 }
150}
151
152impl TryDiv<u64> for Decimal {
153 fn try_div(self, rhs: u64) -> Result<Self, ProgramError> {
154 Ok(Self(
155 self.0
156 .checked_div(U192::from(rhs))
157 .ok_or(LendingError::MathOverflow)?,
158 ))
159 }
160}
161
162impl TryDiv<Rate> for Decimal {
163 fn try_div(self, rhs: Rate) -> Result<Self, ProgramError> {
164 self.try_div(Self::from(rhs))
165 }
166}
167
168impl TryDiv<Decimal> for Decimal {
169 fn try_div(self, rhs: Self) -> Result<Self, ProgramError> {
170 Ok(Self(
171 self.0
172 .checked_mul(Self::wad())
173 .ok_or(LendingError::MathOverflow)?
174 .checked_div(rhs.0)
175 .ok_or(LendingError::MathOverflow)?,
176 ))
177 }
178}
179
180impl TryMul<u64> for Decimal {
181 fn try_mul(self, rhs: u64) -> Result<Self, ProgramError> {
182 Ok(Self(
183 self.0
184 .checked_mul(U192::from(rhs))
185 .ok_or(LendingError::MathOverflow)?,
186 ))
187 }
188}
189
190impl TryMul<Rate> for Decimal {
191 fn try_mul(self, rhs: Rate) -> Result<Self, ProgramError> {
192 self.try_mul(Self::from(rhs))
193 }
194}
195
196impl TryMul<Decimal> for Decimal {
197 fn try_mul(self, rhs: Self) -> Result<Self, ProgramError> {
198 Ok(Self(
199 self.0
200 .checked_mul(rhs.0)
201 .ok_or(LendingError::MathOverflow)?
202 .checked_div(Self::wad())
203 .ok_or(LendingError::MathOverflow)?,
204 ))
205 }
206}
207
208#[cfg(test)]
209mod test {
210 use super::*;
211
212 #[test]
213 fn test_scaler() {
214 assert_eq!(U192::exp10(SCALE), Decimal::wad());
215 }
216
217 #[test]
218 fn test_u192() {
219 let one = U192::from(1);
220 assert_eq!(one.0, [1u64, 0, 0]);
221
222 let wad = Decimal::wad();
223 assert_eq!(wad.0, [WAD, 0, 0]);
224
225 let hundred = Decimal::from(100u64);
226 assert_eq!(hundred.0 .0, [7766279631452241920, 5, 0]);
228 }
229
230 #[test]
231 fn test_from_percent() {
232 let left = Decimal::from_percent(20);
233 let right = Decimal::from(20u64).try_div(Decimal::from(100u64)).unwrap();
234
235 assert_eq!(left, right);
236 }
237
238 #[test]
239 fn test_to_scaled_val() {
240 assert_eq!(
241 Decimal(U192::from(u128::MAX)).to_scaled_val().unwrap(),
242 u128::MAX
243 );
244
245 assert_eq!(
246 Decimal(U192::from(u128::MAX))
247 .try_add(Decimal(U192::from(1)))
248 .unwrap()
249 .to_scaled_val(),
250 Err(ProgramError::from(LendingError::MathOverflow))
251 );
252 }
253
254 #[test]
255 fn test_round_floor_ceil_u64() {
256 let mut val = Decimal::one();
257 assert_eq!(val.try_round_u64().unwrap(), 1);
258 assert_eq!(val.try_floor_u64().unwrap(), 1);
259 assert_eq!(val.try_ceil_u64().unwrap(), 1);
260
261 val = val
262 .try_add(Decimal::from_scaled_val(HALF_WAD as u128 - 1))
263 .unwrap();
264 assert_eq!(val.try_round_u64().unwrap(), 1);
265 assert_eq!(val.try_floor_u64().unwrap(), 1);
266 assert_eq!(val.try_ceil_u64().unwrap(), 2);
267
268 val = val.try_add(Decimal::from_scaled_val(1)).unwrap();
269 assert_eq!(val.try_round_u64().unwrap(), 2);
270 assert_eq!(val.try_floor_u64().unwrap(), 1);
271 assert_eq!(val.try_ceil_u64().unwrap(), 2);
272 }
273
274 #[test]
275 fn test_display() {
276 assert_eq!(Decimal::from(1u64).to_string(), "1.000000000000000000");
277 assert_eq!(
278 Decimal::from_scaled_val(1u128).to_string(),
279 "0.000000000000000001"
280 );
281 }
282}