solend_sdk/math/
decimal.rs

1//! Math for preserving precision of token amounts which are limited
2//! by the SPL Token program to be at most u64::MAX.
3//!
4//! Decimals are internally scaled by a WAD (10^18) to preserve
5//! precision up to 18 decimal places. Decimals are sized to support
6//! both serialization and precise math for the full range of
7//! unsigned 64-bit integers. The underlying representation is a
8//! u192 rather than u256 to reduce compute cost while losing
9//! support for arithmetic operations at the high end of u64 range.
10
11#![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
23// U192 with 192 bits consisting of 3 x 64-bit words
24construct_uint! {
25    pub struct U192(3);
26}
27
28/// Large decimal values, precise to 18 digits
29#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Eq, Ord)]
30pub struct Decimal(pub U192);
31
32impl Decimal {
33    /// One
34    pub fn one() -> Self {
35        Self(Self::wad())
36    }
37
38    /// Zero
39    pub fn zero() -> Self {
40        Self(U192::zero())
41    }
42
43    // OPTIMIZE: use const slice when fixed in BPF toolchain
44    fn wad() -> U192 {
45        U192::from(WAD)
46    }
47
48    // OPTIMIZE: use const slice when fixed in BPF toolchain
49    fn half_wad() -> U192 {
50        U192::from(HALF_WAD)
51    }
52
53    /// Create scaled decimal from percent value
54    pub fn from_percent(percent: u8) -> Self {
55        Self(U192::from(percent as u64 * PERCENT_SCALER))
56    }
57
58    /// Return raw scaled value if it fits within u128
59    #[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    /// Create decimal from scaled value
65    pub fn from_scaled_val(scaled_val: u128) -> Self {
66        Self(U192::from(scaled_val))
67    }
68
69    /// Round scaled decimal to u64
70    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    /// Ceiling scaled decimal to u64
80    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    /// Floor scaled decimal to u64
92    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        // 2^64 * 5 + 7766279631452241920 = 1e20
227        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}