defituna_client/implementation/
vault.rs1use 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 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 if self.borrowed_funds == 0 {
38 self.last_update_timestamp = timestamp;
39 return Ok(());
40 }
41
42 if elapsed_time_seconds < INTEREST_ACCRUE_MIN_INTERVAL {
44 return Ok(());
45 }
46 let utilization = self.get_utilization();
48 let interest_rate_multiplier = sample(utilization);
49 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}