defituna_client/implementation/
vault.rs1use crate::accounts::Vault;
2use crate::TunaError as ErrorCode;
3use defituna_core::borrow_curve::sample;
4use defituna_core::fixed::{mul_div_64, Rounding};
5use defituna_core::Fixed128;
6use std::fmt;
7
8pub const INTEREST_ACCRUE_MIN_INTERVAL: u64 = 60;
9
10impl Vault {
11 pub fn get_utilization(&self) -> f64 {
12 if self.deposited_funds > 0 {
13 self.borrowed_funds as f64 / self.deposited_funds as f64
14 } else {
15 1.0
16 }
17 }
18
19 pub fn compounded_interest_rate(r: f64) -> f64 {
22 let t1 = r;
23 let t2 = r * r / 2.0;
24 let t3 = t2 * r / 3.0;
25 t1 + t2 + t3
26 }
27
28 pub fn accrue_interest(&mut self, timestamp: u64) -> Result<(), ErrorCode> {
29 let elapsed_time_seconds = timestamp.checked_sub(self.last_update_timestamp).ok_or(ErrorCode::MathUnderflow)?;
30
31 if self.borrowed_funds == 0 {
33 self.last_update_timestamp = timestamp;
34 return Ok(());
35 }
36
37 if elapsed_time_seconds < INTEREST_ACCRUE_MIN_INTERVAL {
39 return Ok(());
40 }
41 let utilization = self.get_utilization();
43 let interest_rate_multiplier = sample(utilization);
44 let interest_rate = Fixed128::from_bits(self.interest_rate as u128).to_num::<f64>() * interest_rate_multiplier;
45
46 let interest = Self::compounded_interest_rate(interest_rate * elapsed_time_seconds as f64);
47 let interest_amount = (interest * self.borrowed_funds as f64) as u64;
48
49 self.borrowed_funds = self.borrowed_funds.checked_add(interest_amount).ok_or(ErrorCode::MathOverflow)?;
50 self.deposited_funds = self.deposited_funds.checked_add(interest_amount).ok_or(ErrorCode::MathOverflow)?;
51
52 self.last_update_timestamp = timestamp;
53
54 Ok(())
55 }
56
57 pub fn funds_to_shares(funds: u64, total_funds: u64, total_shares: u64, rounding: Rounding) -> Result<u64, ErrorCode> {
58 if total_funds > 0 {
59 Ok(mul_div_64(funds, total_shares, total_funds, rounding).map_err(|_| ErrorCode::MathOverflow)?)
60 } else {
61 Ok(funds)
62 }
63 }
64
65 pub fn shares_to_funds(shares: u64, total_funds: u64, total_shares: u64, rounding: Rounding) -> Result<u64, ErrorCode> {
66 if total_shares > 0 {
67 Ok(mul_div_64(shares, total_funds, total_shares, rounding).map_err(|_| ErrorCode::MathOverflow)?)
68 } else {
69 Ok(shares)
70 }
71 }
72
73 pub fn calculate_deposited_shares(&self, funds: u64, rounding: Rounding) -> Result<u64, ErrorCode> {
74 Self::funds_to_shares(funds, self.deposited_funds, self.deposited_shares, rounding)
75 }
76
77 pub fn calculate_deposited_funds(&self, shares: u64, rounding: Rounding) -> Result<u64, ErrorCode> {
78 Self::shares_to_funds(shares, self.deposited_funds, self.deposited_shares, rounding)
79 }
80
81 pub fn calculate_borrowed_shares(&self, funds: u64, rounding: Rounding) -> Result<u64, ErrorCode> {
82 Self::funds_to_shares(funds, self.borrowed_funds, self.borrowed_shares, rounding)
83 }
84
85 pub fn calculate_borrowed_funds(&self, shares: u64, rounding: Rounding) -> Result<u64, ErrorCode> {
86 Self::shares_to_funds(shares, self.borrowed_funds, self.borrowed_shares, rounding)
87 }
88}
89
90impl fmt::Display for Vault {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 write!(
93 f,
94 "mint={}; borrowed/deposited funds = {} / {}; bad_debt = {}",
95 self.mint.to_string(),
96 self.borrowed_funds,
97 self.deposited_funds,
98 self.unpaid_debt_shares
99 )
100 }
101}