Skip to main content

port_variable_rate_lending/math/
rate.rs

1//! Math for preserving precision of ratios and percentages.
2//!
3//! Usages and their ranges include:
4//!   - Collateral exchange ratio <= 5.0
5//!   - Loan to value ratio <= 0.9
6//!   - Max borrow rate <= 2.56
7//!   - Percentages <= 1.0
8//!
9//! Rates are internally scaled by a WAD (10^18) to preserve
10//! precision up to 18 decimal places. Rates are sized to support
11//! both serialization and precise math for the full range of
12//! unsigned 8-bit integers. The underlying representation is a
13//! u128 rather than u192 to reduce compute cost while losing
14//! support for arithmetic operations at the high end of u8 range.
15
16#![allow(clippy::assign_op_pattern)]
17#![allow(clippy::ptr_offset_with_cast)]
18#![allow(clippy::reversed_empty_ranges)]
19#![allow(clippy::manual_range_contains)]
20
21use crate::{
22    error::LendingError,
23    math::{common::*, decimal::Decimal},
24};
25use solana_program::program_error::ProgramError;
26use std::{convert::TryFrom, fmt};
27use uint::construct_uint;
28
29// U128 with 128 bits consisting of 2 x 64-bit words
30construct_uint! {
31    pub struct U128(2);
32}
33
34/// Small decimal values, precise to 18 digits
35#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Eq, Ord)]
36pub struct Rate(pub U128);
37
38impl Rate {
39    /// One
40    pub fn one() -> Self {
41        Self(Self::wad())
42    }
43
44    /// Zero
45    pub fn zero() -> Self {
46        Self(U128::from(0))
47    }
48
49    // OPTIMIZE: use const slice when fixed in BPF toolchain
50    fn wad() -> U128 {
51        U128::from(WAD)
52    }
53
54    // OPTIMIZE: use const slice when fixed in BPF toolchain
55    fn half_wad() -> U128 {
56        U128::from(HALF_WAD)
57    }
58
59    /// Create scaled decimal from percent value
60    pub fn from_percent(percent: u8) -> Self {
61        Self(U128::from(percent as u64 * PERCENT_SCALER))
62    }
63
64    /// Return raw scaled value
65    #[allow(clippy::wrong_self_convention)]
66    pub fn to_scaled_val(&self) -> u128 {
67        self.0.as_u128()
68    }
69
70    /// Create decimal from scaled value
71    pub fn from_scaled_val(scaled_val: u64) -> Self {
72        Self(U128::from(scaled_val))
73    }
74
75    /// Round scaled decimal to u64
76    pub fn try_round_u64(&self) -> Result<u64, ProgramError> {
77        let rounded_val = Self::half_wad()
78            .checked_add(self.0)
79            .ok_or(LendingError::MathOverflow)?
80            .checked_div(Self::wad())
81            .ok_or(LendingError::MathOverflow)?;
82        Ok(u64::try_from(rounded_val).map_err(|_| LendingError::MathOverflow)?)
83    }
84
85    /// Calculates base^exp
86    pub fn try_pow(&self, mut exp: u64) -> Result<Rate, ProgramError> {
87        let mut base = *self;
88        let mut ret = if exp % 2 != 0 {
89            base
90        } else {
91            Rate(Self::wad())
92        };
93
94        while exp > 0 {
95            exp /= 2;
96            base = base.try_mul(base)?;
97
98            if exp % 2 != 0 {
99                ret = ret.try_mul(base)?;
100            }
101        }
102
103        Ok(ret)
104    }
105}
106
107impl fmt::Display for Rate {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        let mut scaled_val = self.0.to_string();
110        if scaled_val.len() <= SCALE {
111            scaled_val.insert_str(0, &vec!["0"; SCALE - scaled_val.len()].join(""));
112            scaled_val.insert_str(0, "0.");
113        } else {
114            scaled_val.insert(scaled_val.len() - SCALE, '.');
115        }
116        f.write_str(&scaled_val)
117    }
118}
119
120impl TryFrom<Decimal> for Rate {
121    type Error = ProgramError;
122    fn try_from(decimal: Decimal) -> Result<Self, Self::Error> {
123        Ok(Self(U128::from(decimal.to_scaled_val()?)))
124    }
125}
126
127impl TryAdd for Rate {
128    fn try_add(self, rhs: Self) -> Result<Self, ProgramError> {
129        Ok(Self(
130            self.0
131                .checked_add(rhs.0)
132                .ok_or(LendingError::MathOverflow)?,
133        ))
134    }
135}
136
137impl TrySub for Rate {
138    fn try_sub(self, rhs: Self) -> Result<Self, ProgramError> {
139        Ok(Self(
140            self.0
141                .checked_sub(rhs.0)
142                .ok_or(LendingError::MathOverflow)?,
143        ))
144    }
145}
146
147impl TryDiv<u64> for Rate {
148    fn try_div(self, rhs: u64) -> Result<Self, ProgramError> {
149        Ok(Self(
150            self.0
151                .checked_div(U128::from(rhs))
152                .ok_or(LendingError::MathOverflow)?,
153        ))
154    }
155}
156
157impl TryDiv<Rate> for Rate {
158    fn try_div(self, rhs: Self) -> Result<Self, ProgramError> {
159        Ok(Self(
160            self.0
161                .checked_mul(Self::wad())
162                .ok_or(LendingError::MathOverflow)?
163                .checked_div(rhs.0)
164                .ok_or(LendingError::MathOverflow)?,
165        ))
166    }
167}
168
169impl TryMul<u64> for Rate {
170    fn try_mul(self, rhs: u64) -> Result<Self, ProgramError> {
171        Ok(Self(
172            self.0
173                .checked_mul(U128::from(rhs))
174                .ok_or(LendingError::MathOverflow)?,
175        ))
176    }
177}
178
179impl TryMul<Rate> for Rate {
180    fn try_mul(self, rhs: Self) -> Result<Self, ProgramError> {
181        Ok(Self(
182            self.0
183                .checked_mul(rhs.0)
184                .ok_or(LendingError::MathOverflow)?
185                .checked_div(Self::wad())
186                .ok_or(LendingError::MathOverflow)?,
187        ))
188    }
189}
190
191#[cfg(test)]
192mod test {
193    use super::*;
194
195    #[test]
196    fn checked_pow() {
197        assert_eq!(Rate::one(), Rate::one().try_pow(u64::MAX).unwrap());
198    }
199}