defituna_client/implementation/
vault.rs

1use crate::accounts::Vault;
2use crate::math::borrow_curve::sample;
3use crate::math::fixed::{mul_div_64, Rounding};
4use crate::math::Fixed128;
5use crate::TunaError as ErrorCode;
6use std::fmt;
7
8pub const INTEREST_ACCRUE_MIN_INTERVAL: u64 = 60;
9
10impl Vault {
11    pub fn get_utilization(&self) -> Fixed128 {
12        if self.deposited_funds > 0 {
13            Fixed128::from_bits(self.borrowed_funds as u128) / Fixed128::from_bits(self.deposited_funds as u128)
14        } else {
15            Fixed128::ONE
16        }
17    }
18
19    /// Returns the sum of the first three terms of a Taylor expansion of e^r - 1, to approximate a
20    /// continuous compound interest rate.
21    pub fn compounded_interest_rate(r: Fixed128) -> Result<Fixed128, ErrorCode> {
22        let t1 = r;
23        let t2 = r.checked_mul(r).ok_or(ErrorCode::MathOverflow)? / Fixed128::from_num(2);
24        let t3 = t2.checked_mul(r).ok_or(ErrorCode::MathOverflow)? / Fixed128::from_num(3);
25        let rate = t1
26            .checked_add(t2)
27            .ok_or(ErrorCode::MathOverflow)?
28            .checked_add(t3)
29            .ok_or(ErrorCode::MathOverflow)?;
30        Ok(rate)
31    }
32
33    pub fn accrue_interest(&mut self, timestamp: u64) -> Result<(), ErrorCode> {
34        let elapsed_time_seconds = timestamp.checked_sub(self.last_update_timestamp).ok_or(ErrorCode::MathUnderflow)?;
35
36        // Nothing to accrue.
37        if self.borrowed_funds == 0 {
38            self.last_update_timestamp = timestamp;
39            return Ok(());
40        }
41
42        // Don't accrue interest too often to avoid losing precision.
43        if elapsed_time_seconds < INTEREST_ACCRUE_MIN_INTERVAL {
44            return Ok(());
45        }
46        // Compute interest based on utilization.
47        let utilization = self.get_utilization();
48        let interest_rate_multiplier = sample(utilization);
49        // Can't overflow because both values are limited.
50        let interest_rate = Fixed128::from_bits(self.interest_rate as u128) * interest_rate_multiplier;
51
52        let interest = Self::compounded_interest_rate(
53            interest_rate
54                .checked_mul(Fixed128::from(elapsed_time_seconds))
55                .ok_or(ErrorCode::MathOverflow)?,
56        )?;
57        let interest_amount: u64 = interest
58            .checked_mul(Fixed128::from(self.borrowed_funds))
59            .ok_or(ErrorCode::MathOverflow)?
60            .to_num::<u128>()
61            .try_into()
62            .map_err(|_| ErrorCode::MathOverflow)?;
63
64        self.borrowed_funds = self.borrowed_funds.checked_add(interest_amount).ok_or(ErrorCode::MathOverflow)?;
65        self.deposited_funds = self.deposited_funds.checked_add(interest_amount).ok_or(ErrorCode::MathOverflow)?;
66
67        self.last_update_timestamp = timestamp;
68
69        Ok(())
70    }
71
72    pub fn funds_to_shares(funds: u64, total_funds: u64, total_shares: u64, rounding: Rounding) -> Result<u64, ErrorCode> {
73        if total_funds > 0 {
74            Ok(mul_div_64(funds, total_shares, total_funds, rounding)?)
75        } else {
76            Ok(funds)
77        }
78    }
79
80    pub fn shares_to_funds(shares: u64, total_funds: u64, total_shares: u64, rounding: Rounding) -> Result<u64, ErrorCode> {
81        if total_shares > 0 {
82            Ok(mul_div_64(shares, total_funds, total_shares, rounding)?)
83        } else {
84            Ok(shares)
85        }
86    }
87
88    pub fn calculate_deposited_shares(&self, funds: u64, rounding: Rounding) -> Result<u64, ErrorCode> {
89        Self::funds_to_shares(funds, self.deposited_funds, self.deposited_shares, rounding)
90    }
91
92    pub fn calculate_deposited_funds(&self, shares: u64, rounding: Rounding) -> Result<u64, ErrorCode> {
93        Self::shares_to_funds(shares, self.deposited_funds, self.deposited_shares, rounding)
94    }
95
96    pub fn calculate_borrowed_shares(&self, funds: u64, rounding: Rounding) -> Result<u64, ErrorCode> {
97        Self::funds_to_shares(funds, self.borrowed_funds, self.borrowed_shares, rounding)
98    }
99
100    pub fn calculate_borrowed_funds(&self, shares: u64, rounding: Rounding) -> Result<u64, ErrorCode> {
101        Self::shares_to_funds(shares, self.borrowed_funds, self.borrowed_shares, rounding)
102    }
103}
104
105impl fmt::Display for Vault {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        write!(
108            f,
109            "mint={}; borrowed/deposited funds = {} / {}; bad_debt = {}",
110            self.mint.to_string(),
111            self.borrowed_funds,
112            self.deposited_funds,
113            self.unpaid_debt_shares
114        )
115    }
116}