Bank: fix collection of loan fee

Instead of changing indexed_borrows, change borrow_index!
This commit is contained in:
Christian Kamm 2022-08-16 16:17:13 +02:00
parent 4c65204c19
commit e8bbfbef83
3 changed files with 57 additions and 32 deletions

View File

@ -91,14 +91,16 @@ pub fn token_update_index_and_rate(ctx: Context<TokenUpdateIndexAndRate>) -> Res
// compute and set latest index and average utilization on each bank // compute and set latest index and average utilization on each bank
{ {
let some_bank = ctx.remaining_accounts[0].load::<Bank>()?; let mut some_bank = ctx.remaining_accounts[0].load_mut::<Bank>()?;
let now_ts_i80f48 = I80F48::from_num(now_ts); let now_ts_i80f48 = I80F48::from_num(now_ts);
let diff_ts = I80F48::from_num(now_ts - some_bank.index_last_updated); let diff_ts = I80F48::from_num(now_ts - some_bank.index_last_updated);
let (deposit_index, borrow_index) = let (deposit_index, borrow_index, borrow_fees) =
some_bank.compute_index(indexed_total_deposits, indexed_total_borrows, diff_ts)?; some_bank.compute_index(indexed_total_deposits, indexed_total_borrows, diff_ts)?;
some_bank.collected_fees_native = cm!(some_bank.collected_fees_native + borrow_fees);
let new_avg_utilization = some_bank.compute_new_avg_utilization( let new_avg_utilization = some_bank.compute_new_avg_utilization(
indexed_total_deposits, indexed_total_deposits,
indexed_total_borrows, indexed_total_borrows,
@ -135,7 +137,6 @@ pub fn token_update_index_and_rate(ctx: Context<TokenUpdateIndexAndRate>) -> Res
bank.cached_indexed_total_borrows = indexed_total_borrows; bank.cached_indexed_total_borrows = indexed_total_borrows;
bank.index_last_updated = now_ts; bank.index_last_updated = now_ts;
bank.charge_loan_fee(diff_ts);
bank.deposit_index = deposit_index; bank.deposit_index = deposit_index;
bank.borrow_index = borrow_index; bank.borrow_index = borrow_index;

View File

@ -392,22 +392,12 @@ impl Bank {
} }
} }
// Borrows continously expose insurance fund to risk, collect fees from borrowers
pub fn charge_loan_fee(&mut self, diff_ts: I80F48) {
let native_borrows_old = self.native_borrows();
self.indexed_borrows =
cm!((self.indexed_borrows
* (I80F48::ONE + self.loan_fee_rate * (diff_ts / YEAR_I80F48))));
self.collected_fees_native =
cm!(self.collected_fees_native + self.native_borrows() - native_borrows_old);
}
pub fn compute_index( pub fn compute_index(
&self, &self,
indexed_total_deposits: I80F48, indexed_total_deposits: I80F48,
indexed_total_borrows: I80F48, indexed_total_borrows: I80F48,
diff_ts: I80F48, diff_ts: I80F48,
) -> Result<(I80F48, I80F48)> { ) -> Result<(I80F48, I80F48, I80F48)> {
// compute index based on utilization // compute index based on utilization
let native_total_deposits = cm!(self.deposit_index * indexed_total_deposits); let native_total_deposits = cm!(self.deposit_index * indexed_total_deposits);
let native_total_borrows = cm!(self.borrow_index * indexed_total_borrows); let native_total_borrows = cm!(self.borrow_index * indexed_total_borrows);
@ -419,21 +409,31 @@ impl Bank {
cm!(native_total_borrows / native_total_deposits) cm!(native_total_borrows / native_total_deposits)
}; };
let borrow_interest_rate = self.compute_interest_rate(instantaneous_utilization); let borrow_rate = self.compute_interest_rate(instantaneous_utilization);
let borrow_interest = cm!(borrow_interest_rate * diff_ts); // We want to grant depositors a rate that exactly matches the amount that is
let deposit_interest = cm!(borrow_interest * instantaneous_utilization); // taken from borrowers. That means:
// (new_deposit_index - old_deposit_index) * indexed_deposits
// = (new_borrow_index - old_borrow_index) * indexed_borrows
// with
// new_deposit_index = old_deposit_index * (1 + deposit_rate) and
// new_borrow_index = old_borrow_index * (1 * borrow_rate)
// we have
// deposit_rate = borrow_rate * (old_borrow_index * indexed_borrows) / (old_deposit_index * indexed_deposits)
// and the latter factor is exactly instantaneous_utilization.
let deposit_rate = cm!(borrow_rate * instantaneous_utilization);
if borrow_interest <= I80F48::ZERO || deposit_interest <= I80F48::ZERO { // The loan fee rate is not distributed to depositors.
return Ok((self.deposit_index, self.borrow_index)); let borrow_rate_with_fees = cm!(borrow_rate + self.loan_fee_rate);
} let borrow_fees = native_total_borrows * self.loan_fee_rate * diff_ts / YEAR_I80F48;
let borrow_index = let borrow_index = cm!(
cm!((self.borrow_index * borrow_interest) / YEAR_I80F48 + self.borrow_index); (self.borrow_index * borrow_rate_with_fees * diff_ts) / YEAR_I80F48 + self.borrow_index
);
let deposit_index = let deposit_index =
cm!((self.deposit_index * deposit_interest) / YEAR_I80F48 + self.deposit_index); cm!((self.deposit_index * deposit_rate * diff_ts) / YEAR_I80F48 + self.deposit_index);
Ok((deposit_index, borrow_index)) Ok((deposit_index, borrow_index, borrow_fees))
} }
/// returns the current interest rate in APR /// returns the current interest rate in APR

View File

@ -107,9 +107,11 @@ async fn test_token_update_index_and_rate() -> Result<(), TransportError> {
.await .await
.unwrap(); .unwrap();
let bank_before_update_index_and_rate = solana.get_account::<Bank>(tokens[0].bank).await; let bank_before = solana.get_account::<Bank>(tokens[0].bank).await;
let time_before = solana.get_clock().await.unix_timestamp;
solana.advance_clock().await; solana.advance_clock().await;
let time_after = solana.get_clock().await.unix_timestamp;
send_tx( send_tx(
solana, solana,
@ -120,16 +122,38 @@ async fn test_token_update_index_and_rate() -> Result<(), TransportError> {
.await .await
.unwrap(); .unwrap();
let bank_after_update_index_and_rate = solana.get_account::<Bank>(tokens[0].bank).await; let bank_after = solana.get_account::<Bank>(tokens[0].bank).await;
dbg!(bank_after_update_index_and_rate); dbg!(bank_after);
dbg!(bank_after_update_index_and_rate); dbg!(bank_after);
let utilization = 0.5; // 10000 deposits / 5000 borrows
let diff_ts = (time_after - time_before) as f64;
let year = 31536000.0;
let loan_fee_rate = 0.0005;
let dynamic_rate = 0.07 + 0.9 * (utilization - 0.4) / (0.8 - 0.4);
let interest_change = 5000.0 * (dynamic_rate + loan_fee_rate) * diff_ts / year;
let fee_change = 5000.0 * loan_fee_rate * diff_ts / year;
assert!( assert!(
bank_before_update_index_and_rate.deposit_index (bank_after.native_borrows().to_num::<f64>()
< bank_after_update_index_and_rate.deposit_index - bank_before.native_borrows().to_num::<f64>()
- interest_change)
.abs()
< 0.1
); );
assert!( assert!(
bank_before_update_index_and_rate.borrow_index (bank_after.native_deposits().to_num::<f64>()
< bank_after_update_index_and_rate.borrow_index - bank_before.native_deposits().to_num::<f64>()
- interest_change)
.abs()
< 0.1
);
assert!(
(bank_after.collected_fees_native.to_num::<f64>()
- bank_before.collected_fees_native.to_num::<f64>()
- fee_change)
.abs()
< 0.1
); );
Ok(()) Ok(())