2022-06-22 02:21:02 -07:00
|
|
|
use super::{OracleConfig, TokenIndex, TokenPosition};
|
2022-05-13 04:55:33 -07:00
|
|
|
use crate::error::MangoError;
|
2022-04-09 07:53:30 -07:00
|
|
|
use crate::util::checked_math as cm;
|
2022-02-22 04:31:18 -08:00
|
|
|
use anchor_lang::prelude::*;
|
|
|
|
use fixed::types::I80F48;
|
2022-04-09 07:53:30 -07:00
|
|
|
use fixed_macro::types::I80F48;
|
2022-03-31 05:37:05 -07:00
|
|
|
use static_assertions::const_assert_eq;
|
2022-06-27 02:27:17 -07:00
|
|
|
|
2022-03-31 05:37:05 -07:00
|
|
|
use std::mem::size_of;
|
2022-02-22 04:31:18 -08:00
|
|
|
|
2022-05-16 06:34:56 -07:00
|
|
|
pub const DAY: I80F48 = I80F48!(86400);
|
2022-04-09 07:53:30 -07:00
|
|
|
pub const YEAR: I80F48 = I80F48!(31536000);
|
2022-02-23 01:15:33 -08:00
|
|
|
|
2022-02-22 04:31:18 -08:00
|
|
|
#[account(zero_copy)]
|
2022-03-07 07:16:34 -08:00
|
|
|
pub struct Bank {
|
2022-04-12 07:19:58 -07:00
|
|
|
pub name: [u8; 16],
|
|
|
|
|
2022-02-26 03:17:20 -08:00
|
|
|
pub group: Pubkey,
|
|
|
|
pub mint: Pubkey,
|
|
|
|
pub vault: Pubkey,
|
2022-02-28 05:44:08 -08:00
|
|
|
pub oracle: Pubkey,
|
2022-02-26 03:17:20 -08:00
|
|
|
|
2022-06-18 07:38:46 -07:00
|
|
|
pub oracle_config: OracleConfig,
|
|
|
|
|
2022-02-22 05:23:13 -08:00
|
|
|
/// the index used to scale the value of an IndexedPosition
|
2022-02-23 01:09:01 -08:00
|
|
|
/// TODO: should always be >= 0, add checks?
|
2022-02-22 05:23:13 -08:00
|
|
|
pub deposit_index: I80F48,
|
|
|
|
pub borrow_index: I80F48,
|
2022-02-23 01:09:01 -08:00
|
|
|
|
|
|
|
/// total deposits/borrows, for utilization
|
|
|
|
pub indexed_total_deposits: I80F48,
|
|
|
|
pub indexed_total_borrows: I80F48,
|
2022-04-09 07:53:30 -07:00
|
|
|
|
2022-06-27 02:27:17 -07:00
|
|
|
/// deposits/borrows for this bank
|
|
|
|
pub indexed_deposits: I80F48,
|
|
|
|
pub indexed_borrows: I80F48,
|
|
|
|
|
2022-04-09 07:53:30 -07:00
|
|
|
pub last_updated: i64,
|
|
|
|
pub util0: I80F48,
|
|
|
|
pub rate0: I80F48,
|
|
|
|
pub util1: I80F48,
|
|
|
|
pub rate1: I80F48,
|
|
|
|
pub max_rate: I80F48,
|
2022-02-26 03:04:42 -08:00
|
|
|
|
2022-05-09 02:14:50 -07:00
|
|
|
// TODO: add ix/logic to regular send this to DAO
|
|
|
|
pub collected_fees_native: I80F48,
|
|
|
|
pub loan_origination_fee_rate: I80F48,
|
|
|
|
pub loan_fee_rate: I80F48,
|
|
|
|
|
2022-02-26 03:04:42 -08:00
|
|
|
// This is a _lot_ of bytes (64) - seems unnecessary
|
|
|
|
// (could maybe store them in one byte each, as an informal U1F7?
|
|
|
|
// that could store values between 0-2 and converting to I80F48 would be a cheap expand+shift)
|
|
|
|
pub maint_asset_weight: I80F48,
|
|
|
|
pub init_asset_weight: I80F48,
|
|
|
|
pub maint_liab_weight: I80F48,
|
|
|
|
pub init_liab_weight: I80F48,
|
|
|
|
|
2022-03-27 00:34:34 -07:00
|
|
|
// a fraction of the price, like 0.05 for a 5% fee during liquidation
|
|
|
|
//
|
|
|
|
// Liquidation always involves two tokens, and the sum of the two configured fees is used.
|
2022-03-26 11:34:44 -07:00
|
|
|
pub liquidation_fee: I80F48,
|
|
|
|
|
2022-03-04 04:33:27 -08:00
|
|
|
// Collection of all fractions-of-native-tokens that got rounded away
|
|
|
|
pub dust: I80F48,
|
|
|
|
|
2022-06-24 08:51:38 -07:00
|
|
|
pub flash_loan_vault_initial: u64,
|
|
|
|
pub flash_loan_approved_amount: u64,
|
|
|
|
|
2022-02-26 03:04:42 -08:00
|
|
|
// Index into TokenInfo on the group
|
|
|
|
pub token_index: TokenIndex,
|
2022-03-31 05:37:05 -07:00
|
|
|
|
2022-05-19 04:45:46 -07:00
|
|
|
pub bump: u8,
|
|
|
|
|
2022-05-27 05:52:03 -07:00
|
|
|
pub mint_decimals: u8,
|
|
|
|
|
|
|
|
pub reserved: [u8; 4],
|
2022-06-27 02:27:17 -07:00
|
|
|
|
|
|
|
pub bank_num: u64,
|
2022-06-09 23:42:26 -07:00
|
|
|
// TODO: add space for an oracle which services interest rate for the bank's mint
|
|
|
|
// interest rate tied to oracle might help reduce spreads between deposits and borrows
|
2022-02-22 05:23:13 -08:00
|
|
|
}
|
2022-06-27 02:27:17 -07:00
|
|
|
const_assert_eq!(
|
|
|
|
size_of::<Bank>(),
|
2022-06-24 08:51:38 -07:00
|
|
|
16 + 32 * 4 + 8 + 16 * 21 + 2 * 8 + 2 + 1 + 1 + 4 + 8
|
2022-06-27 02:27:17 -07:00
|
|
|
);
|
2022-03-31 05:37:05 -07:00
|
|
|
const_assert_eq!(size_of::<Bank>() % 8, 0);
|
2022-02-22 04:31:18 -08:00
|
|
|
|
2022-05-09 02:14:50 -07:00
|
|
|
impl std::fmt::Debug for Bank {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
f.debug_struct("Bank")
|
|
|
|
.field("name", &self.name())
|
|
|
|
.field("group", &self.group)
|
|
|
|
.field("mint", &self.mint)
|
|
|
|
.field("vault", &self.vault)
|
|
|
|
.field("oracle", &self.oracle)
|
2022-06-27 02:27:17 -07:00
|
|
|
.field("oracle_config", &self.oracle_config)
|
2022-05-09 02:14:50 -07:00
|
|
|
.field("deposit_index", &self.deposit_index)
|
|
|
|
.field("borrow_index", &self.borrow_index)
|
|
|
|
.field("indexed_total_deposits", &self.indexed_total_deposits)
|
|
|
|
.field("indexed_total_borrows", &self.indexed_total_borrows)
|
2022-06-27 02:27:17 -07:00
|
|
|
.field("indexed_deposits", &self.indexed_deposits)
|
|
|
|
.field("indexed_borrows", &self.indexed_borrows)
|
2022-05-09 02:14:50 -07:00
|
|
|
.field("last_updated", &self.last_updated)
|
|
|
|
.field("util0", &self.util0)
|
|
|
|
.field("rate0", &self.rate0)
|
|
|
|
.field("util1", &self.util1)
|
|
|
|
.field("rate1", &self.rate1)
|
|
|
|
.field("max_rate", &self.max_rate)
|
|
|
|
.field("collected_fees_native", &self.collected_fees_native)
|
|
|
|
.field("loan_origination_fee_rate", &self.loan_origination_fee_rate)
|
|
|
|
.field("loan_fee_rate", &self.loan_fee_rate)
|
|
|
|
.field("maint_asset_weight", &self.maint_asset_weight)
|
|
|
|
.field("init_asset_weight", &self.init_asset_weight)
|
|
|
|
.field("maint_liab_weight", &self.maint_liab_weight)
|
|
|
|
.field("init_liab_weight", &self.init_liab_weight)
|
|
|
|
.field("liquidation_fee", &self.liquidation_fee)
|
|
|
|
.field("dust", &self.dust)
|
|
|
|
.field("token_index", &self.token_index)
|
2022-06-24 08:51:38 -07:00
|
|
|
.field(
|
|
|
|
"flash_loan_approved_amount",
|
|
|
|
&self.flash_loan_approved_amount,
|
|
|
|
)
|
|
|
|
.field("flash_loan_vault_initial", &self.flash_loan_vault_initial)
|
2022-05-09 02:14:50 -07:00
|
|
|
.field("reserved", &self.reserved)
|
|
|
|
.finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-07 07:16:34 -08:00
|
|
|
impl Bank {
|
2022-06-27 02:27:17 -07:00
|
|
|
pub fn from_existing_bank(existing_bank: &Bank, vault: Pubkey, bank_num: u64) -> Self {
|
|
|
|
Self {
|
|
|
|
name: existing_bank.name,
|
|
|
|
group: existing_bank.group,
|
|
|
|
mint: existing_bank.mint,
|
|
|
|
vault: vault,
|
|
|
|
oracle: existing_bank.oracle,
|
|
|
|
oracle_config: existing_bank.oracle_config,
|
|
|
|
deposit_index: existing_bank.deposit_index,
|
|
|
|
borrow_index: existing_bank.borrow_index,
|
|
|
|
indexed_total_deposits: existing_bank.indexed_total_deposits,
|
|
|
|
indexed_total_borrows: existing_bank.indexed_total_borrows,
|
|
|
|
indexed_deposits: I80F48::ZERO,
|
|
|
|
indexed_borrows: I80F48::ZERO,
|
|
|
|
last_updated: existing_bank.last_updated,
|
|
|
|
util0: existing_bank.util0,
|
|
|
|
rate0: existing_bank.rate0,
|
|
|
|
util1: existing_bank.util1,
|
|
|
|
rate1: existing_bank.rate1,
|
|
|
|
max_rate: existing_bank.max_rate,
|
|
|
|
collected_fees_native: existing_bank.collected_fees_native,
|
|
|
|
loan_origination_fee_rate: existing_bank.loan_origination_fee_rate,
|
|
|
|
loan_fee_rate: existing_bank.loan_fee_rate,
|
|
|
|
maint_asset_weight: existing_bank.maint_asset_weight,
|
|
|
|
init_asset_weight: existing_bank.init_asset_weight,
|
|
|
|
maint_liab_weight: existing_bank.maint_liab_weight,
|
|
|
|
init_liab_weight: existing_bank.init_liab_weight,
|
|
|
|
liquidation_fee: existing_bank.liquidation_fee,
|
|
|
|
dust: I80F48::ZERO,
|
2022-06-24 08:51:38 -07:00
|
|
|
flash_loan_approved_amount: 0,
|
|
|
|
flash_loan_vault_initial: u64::MAX,
|
2022-06-27 02:27:17 -07:00
|
|
|
token_index: existing_bank.token_index,
|
|
|
|
bump: existing_bank.bump,
|
|
|
|
mint_decimals: existing_bank.mint_decimals,
|
|
|
|
reserved: Default::default(),
|
|
|
|
bank_num,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-06 01:13:33 -07:00
|
|
|
pub fn name(&self) -> &str {
|
|
|
|
std::str::from_utf8(&self.name)
|
|
|
|
.unwrap()
|
|
|
|
.trim_matches(char::from(0))
|
|
|
|
}
|
|
|
|
|
2022-04-09 07:53:30 -07:00
|
|
|
pub fn native_total_borrows(&self) -> I80F48 {
|
|
|
|
self.borrow_index * self.indexed_total_borrows
|
|
|
|
}
|
|
|
|
|
2022-02-25 04:10:51 -08:00
|
|
|
pub fn native_total_deposits(&self) -> I80F48 {
|
|
|
|
self.deposit_index * self.indexed_total_deposits
|
|
|
|
}
|
|
|
|
|
2022-06-27 02:27:17 -07:00
|
|
|
pub fn native_borrows(&self) -> I80F48 {
|
|
|
|
self.borrow_index * self.indexed_borrows
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn native_deposits(&self) -> I80F48 {
|
|
|
|
self.deposit_index * self.indexed_deposits
|
|
|
|
}
|
|
|
|
|
2022-03-04 04:33:27 -08:00
|
|
|
/// Returns whether the position is active
|
2022-03-26 11:34:44 -07:00
|
|
|
///
|
|
|
|
/// native_amount must be >= 0
|
|
|
|
/// fractional deposits can be relevant during liquidation, for example
|
|
|
|
pub fn deposit(
|
|
|
|
&mut self,
|
2022-06-21 07:52:40 -07:00
|
|
|
position: &mut TokenPosition,
|
2022-03-26 11:34:44 -07:00
|
|
|
mut native_amount: I80F48,
|
|
|
|
) -> Result<bool> {
|
2022-05-18 03:52:46 -07:00
|
|
|
require!(native_amount >= 0, MangoError::SomeError);
|
2022-02-23 01:09:01 -08:00
|
|
|
let native_position = position.native(self);
|
|
|
|
|
|
|
|
if native_position.is_negative() {
|
2022-03-11 00:57:30 -08:00
|
|
|
let new_native_position = cm!(native_position + native_amount);
|
2022-06-22 04:11:19 -07:00
|
|
|
let indexed_change = cm!(native_amount / self.borrow_index + I80F48::DELTA);
|
|
|
|
// this is only correct if it's not positive, because it scales the whole amount by borrow_index
|
2022-06-21 07:52:40 -07:00
|
|
|
let new_indexed_value = cm!(position.indexed_position + indexed_change);
|
2022-06-22 04:11:19 -07:00
|
|
|
if new_indexed_value.is_negative() {
|
2022-03-04 04:33:27 -08:00
|
|
|
// pay back borrows only, leaving a negative position
|
2022-06-27 02:27:17 -07:00
|
|
|
let indexed_change = cm!(native_amount / self.borrow_index + I80F48::DELTA);
|
|
|
|
self.indexed_borrows = cm!(self.indexed_borrows - indexed_change);
|
|
|
|
position.indexed_position = cm!(position.indexed_position + indexed_change);
|
2022-03-11 00:57:30 -08:00
|
|
|
return Ok(true);
|
2022-03-15 06:44:47 -07:00
|
|
|
} else if new_native_position < I80F48::ONE && !position.is_in_use() {
|
2022-03-04 04:33:27 -08:00
|
|
|
// if there's less than one token deposited, zero the position
|
2022-03-11 00:57:30 -08:00
|
|
|
self.dust = cm!(self.dust + new_native_position);
|
2022-06-27 02:27:17 -07:00
|
|
|
self.indexed_borrows = cm!(self.indexed_borrows + position.indexed_position);
|
2022-06-21 07:52:40 -07:00
|
|
|
position.indexed_position = I80F48::ZERO;
|
2022-03-11 00:57:30 -08:00
|
|
|
return Ok(false);
|
2022-02-23 01:09:01 -08:00
|
|
|
}
|
|
|
|
|
2022-03-04 04:33:27 -08:00
|
|
|
// pay back all borrows
|
2022-06-27 02:27:17 -07:00
|
|
|
self.indexed_borrows = cm!(self.indexed_borrows + position.indexed_position); // position.value is negative
|
2022-06-21 07:52:40 -07:00
|
|
|
position.indexed_position = I80F48::ZERO;
|
2022-03-04 04:33:27 -08:00
|
|
|
// deposit the rest
|
2022-03-11 00:57:30 -08:00
|
|
|
native_amount = cm!(native_amount + native_position);
|
2022-02-23 01:09:01 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// add to deposits
|
2022-03-04 04:33:27 -08:00
|
|
|
// Adding DELTA to amount/index helps because (amount/index)*index <= amount, but
|
|
|
|
// we want to ensure that users can withdraw the same amount they have deposited, so
|
|
|
|
// (amount/index + delta)*index >= amount is a better guarantee.
|
2022-03-11 00:57:30 -08:00
|
|
|
let indexed_change = cm!(native_amount / self.deposit_index + I80F48::DELTA);
|
2022-06-27 02:27:17 -07:00
|
|
|
self.indexed_deposits = cm!(self.indexed_deposits + indexed_change);
|
2022-06-21 07:52:40 -07:00
|
|
|
position.indexed_position = cm!(position.indexed_position + indexed_change);
|
2022-03-04 04:33:27 -08:00
|
|
|
|
2022-03-11 00:57:30 -08:00
|
|
|
Ok(true)
|
2022-02-23 01:09:01 -08:00
|
|
|
}
|
|
|
|
|
2022-05-18 03:52:46 -07:00
|
|
|
/// Returns whether the position is active after withdrawing from a position
|
|
|
|
/// without applying the loan origination fee.
|
2022-03-26 11:34:44 -07:00
|
|
|
///
|
|
|
|
/// native_amount must be >= 0
|
|
|
|
/// fractional withdraws can be relevant during liquidation, for example
|
2022-05-18 03:52:46 -07:00
|
|
|
pub fn withdraw_without_fee(
|
|
|
|
&mut self,
|
2022-06-21 07:52:40 -07:00
|
|
|
position: &mut TokenPosition,
|
2022-05-18 03:52:46 -07:00
|
|
|
native_amount: I80F48,
|
|
|
|
) -> Result<bool> {
|
|
|
|
self.withdraw_internal(position, native_amount, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns whether the position is active after withdrawing from a position
|
|
|
|
/// while applying the loan origination fee if a borrow is created.
|
|
|
|
///
|
|
|
|
/// native_amount must be >= 0
|
|
|
|
/// fractional withdraws can be relevant during liquidation, for example
|
|
|
|
pub fn withdraw_with_fee(
|
|
|
|
&mut self,
|
2022-06-21 07:52:40 -07:00
|
|
|
position: &mut TokenPosition,
|
2022-05-18 03:52:46 -07:00
|
|
|
native_amount: I80F48,
|
|
|
|
) -> Result<bool> {
|
|
|
|
self.withdraw_internal(position, native_amount, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn withdraw_internal(
|
2022-03-26 11:34:44 -07:00
|
|
|
&mut self,
|
2022-06-21 07:52:40 -07:00
|
|
|
position: &mut TokenPosition,
|
2022-03-26 11:34:44 -07:00
|
|
|
mut native_amount: I80F48,
|
2022-05-18 03:52:46 -07:00
|
|
|
with_loan_origination_fee: bool,
|
2022-03-26 11:34:44 -07:00
|
|
|
) -> Result<bool> {
|
2022-05-18 03:52:46 -07:00
|
|
|
require!(native_amount >= 0, MangoError::SomeError);
|
2022-02-23 01:09:01 -08:00
|
|
|
let native_position = position.native(self);
|
|
|
|
|
|
|
|
if native_position.is_positive() {
|
2022-03-11 00:57:30 -08:00
|
|
|
let new_native_position = cm!(native_position - native_amount);
|
2022-03-04 04:33:27 -08:00
|
|
|
if !new_native_position.is_negative() {
|
2022-02-23 01:09:01 -08:00
|
|
|
// withdraw deposits only
|
2022-03-15 06:44:47 -07:00
|
|
|
if new_native_position < I80F48::ONE && !position.is_in_use() {
|
2022-03-04 04:33:27 -08:00
|
|
|
// zero the account collecting the leftovers in `dust`
|
2022-03-11 00:57:30 -08:00
|
|
|
self.dust = cm!(self.dust + new_native_position);
|
2022-06-27 02:27:17 -07:00
|
|
|
self.indexed_deposits = cm!(self.indexed_deposits - position.indexed_position);
|
2022-06-21 07:52:40 -07:00
|
|
|
position.indexed_position = I80F48::ZERO;
|
2022-03-11 00:57:30 -08:00
|
|
|
return Ok(false);
|
2022-03-04 04:33:27 -08:00
|
|
|
} else {
|
2022-03-15 06:44:47 -07:00
|
|
|
// withdraw some deposits leaving a positive balance
|
2022-03-11 00:57:30 -08:00
|
|
|
let indexed_change = cm!(native_amount / self.deposit_index);
|
2022-06-27 02:27:17 -07:00
|
|
|
self.indexed_deposits = cm!(self.indexed_deposits - indexed_change);
|
2022-06-21 07:52:40 -07:00
|
|
|
position.indexed_position = cm!(position.indexed_position - indexed_change);
|
2022-03-11 00:57:30 -08:00
|
|
|
return Ok(true);
|
2022-03-04 04:33:27 -08:00
|
|
|
}
|
2022-02-23 01:09:01 -08:00
|
|
|
}
|
|
|
|
|
2022-03-04 04:33:27 -08:00
|
|
|
// withdraw all deposits
|
2022-06-27 02:27:17 -07:00
|
|
|
self.indexed_deposits = cm!(self.indexed_deposits - position.indexed_position);
|
2022-06-21 07:52:40 -07:00
|
|
|
position.indexed_position = I80F48::ZERO;
|
2022-03-04 04:33:27 -08:00
|
|
|
// borrow the rest
|
|
|
|
native_amount = -new_native_position;
|
2022-02-23 01:09:01 -08:00
|
|
|
}
|
|
|
|
|
2022-05-18 03:52:46 -07:00
|
|
|
if with_loan_origination_fee {
|
2022-06-21 02:45:38 -07:00
|
|
|
self.charge_loan_origination_fee(position, native_amount)?;
|
2022-05-18 03:52:46 -07:00
|
|
|
}
|
|
|
|
|
2022-02-23 01:09:01 -08:00
|
|
|
// add to borrows
|
2022-03-11 00:57:30 -08:00
|
|
|
let indexed_change = cm!(native_amount / self.borrow_index);
|
2022-06-27 02:27:17 -07:00
|
|
|
self.indexed_borrows = cm!(self.indexed_borrows + indexed_change);
|
2022-06-21 07:52:40 -07:00
|
|
|
position.indexed_position = cm!(position.indexed_position - indexed_change);
|
2022-03-04 04:33:27 -08:00
|
|
|
|
2022-03-11 00:57:30 -08:00
|
|
|
Ok(true)
|
2022-02-22 05:23:13 -08:00
|
|
|
}
|
2022-03-15 06:44:47 -07:00
|
|
|
|
2022-06-21 02:45:38 -07:00
|
|
|
// charge only loan origination fee, assuming borrow has already happened
|
|
|
|
pub fn charge_loan_origination_fee(
|
|
|
|
&mut self,
|
2022-06-21 07:52:40 -07:00
|
|
|
position: &mut TokenPosition,
|
2022-06-21 02:45:38 -07:00
|
|
|
already_borrowed_native_amount: I80F48,
|
|
|
|
) -> Result<()> {
|
|
|
|
let loan_origination_fee =
|
|
|
|
cm!(self.loan_origination_fee_rate * already_borrowed_native_amount);
|
|
|
|
self.collected_fees_native = cm!(self.collected_fees_native + loan_origination_fee);
|
|
|
|
|
|
|
|
let indexed_change = cm!(loan_origination_fee / self.borrow_index);
|
2022-06-27 02:27:17 -07:00
|
|
|
self.indexed_borrows = cm!(self.indexed_borrows + indexed_change);
|
2022-06-21 07:52:40 -07:00
|
|
|
position.indexed_position = cm!(position.indexed_position - indexed_change);
|
2022-06-21 02:45:38 -07:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-05-18 03:52:46 -07:00
|
|
|
/// Change a position without applying the loan origination fee
|
|
|
|
pub fn change_without_fee(
|
|
|
|
&mut self,
|
2022-06-21 07:52:40 -07:00
|
|
|
position: &mut TokenPosition,
|
2022-05-18 03:52:46 -07:00
|
|
|
native_amount: I80F48,
|
|
|
|
) -> Result<bool> {
|
2022-03-15 06:44:47 -07:00
|
|
|
if native_amount >= 0 {
|
2022-03-26 11:34:44 -07:00
|
|
|
self.deposit(position, native_amount)
|
2022-03-15 06:44:47 -07:00
|
|
|
} else {
|
2022-05-18 03:52:46 -07:00
|
|
|
self.withdraw_without_fee(position, -native_amount)
|
2022-03-15 06:44:47 -07:00
|
|
|
}
|
|
|
|
}
|
2022-04-09 07:53:30 -07:00
|
|
|
|
2022-05-18 03:52:46 -07:00
|
|
|
/// Change a position, while taking the loan origination fee into account
|
|
|
|
pub fn change_with_fee(
|
2022-05-13 04:55:33 -07:00
|
|
|
&mut self,
|
2022-06-21 07:52:40 -07:00
|
|
|
position: &mut TokenPosition,
|
2022-05-18 03:52:46 -07:00
|
|
|
native_amount: I80F48,
|
|
|
|
) -> Result<bool> {
|
|
|
|
if native_amount >= 0 {
|
|
|
|
self.deposit(position, native_amount)
|
|
|
|
} else {
|
|
|
|
self.withdraw_with_fee(position, -native_amount)
|
2022-05-13 04:55:33 -07:00
|
|
|
}
|
|
|
|
}
|
2022-05-09 02:14:50 -07:00
|
|
|
|
2022-05-13 04:55:33 -07:00
|
|
|
// Borrows continously expose insurance fund to risk, collect fees from borrowers
|
|
|
|
pub fn charge_loan_fee(&mut self, diff_ts: I80F48) {
|
2022-06-27 02:27:17 -07:00
|
|
|
let native_borrows_old = self.native_borrows();
|
|
|
|
self.indexed_borrows =
|
|
|
|
cm!((self.indexed_borrows * (I80F48::ONE + self.loan_fee_rate * (diff_ts / YEAR))));
|
|
|
|
self.collected_fees_native =
|
|
|
|
cm!(self.collected_fees_native + self.native_borrows() - native_borrows_old);
|
2022-05-13 04:55:33 -07:00
|
|
|
}
|
|
|
|
|
2022-06-27 02:27:17 -07:00
|
|
|
pub fn compute_index(
|
|
|
|
&mut self,
|
|
|
|
indexed_total_deposits: I80F48,
|
|
|
|
indexed_total_borrows: I80F48,
|
|
|
|
diff_ts: I80F48,
|
|
|
|
) -> Result<(I80F48, I80F48)> {
|
|
|
|
// compute index based on utilization
|
|
|
|
let native_total_deposits = self.deposit_index * indexed_total_deposits;
|
|
|
|
let native_total_borrows = self.borrow_index * indexed_total_borrows;
|
|
|
|
|
|
|
|
let utilization = if native_total_deposits == I80F48::ZERO {
|
2022-04-09 12:18:07 -07:00
|
|
|
I80F48::ZERO
|
|
|
|
} else {
|
2022-06-27 02:27:17 -07:00
|
|
|
cm!(native_total_borrows / native_total_deposits)
|
2022-04-09 12:18:07 -07:00
|
|
|
};
|
2022-04-09 07:53:30 -07:00
|
|
|
|
|
|
|
let interest_rate = self.compute_interest_rate(utilization);
|
|
|
|
|
|
|
|
let borrow_interest: I80F48 = cm!(interest_rate * diff_ts);
|
|
|
|
let deposit_interest = cm!(borrow_interest * utilization);
|
|
|
|
|
2022-06-27 02:27:17 -07:00
|
|
|
// msg!("utilization {}", utilization);
|
|
|
|
// msg!("interest_rate {}", interest_rate);
|
|
|
|
// msg!("borrow_interest {}", borrow_interest);
|
|
|
|
// msg!("deposit_interest {}", deposit_interest);
|
|
|
|
|
2022-04-09 07:53:30 -07:00
|
|
|
if borrow_interest <= I80F48::ZERO || deposit_interest <= I80F48::ZERO {
|
2022-06-27 02:27:17 -07:00
|
|
|
return Ok((self.deposit_index, self.borrow_index));
|
2022-04-09 07:53:30 -07:00
|
|
|
}
|
|
|
|
|
2022-06-27 02:27:17 -07:00
|
|
|
let borrow_index = cm!((self.borrow_index * borrow_interest) / YEAR + self.borrow_index);
|
|
|
|
let deposit_index =
|
2022-04-09 07:53:30 -07:00
|
|
|
cm!((self.deposit_index * deposit_interest) / YEAR + self.deposit_index);
|
|
|
|
|
2022-06-27 02:27:17 -07:00
|
|
|
Ok((deposit_index, borrow_index))
|
2022-04-09 07:53:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// returns the current interest rate in APR
|
|
|
|
#[inline(always)]
|
|
|
|
pub fn compute_interest_rate(&self, utilization: I80F48) -> I80F48 {
|
|
|
|
Bank::interest_rate_curve_calculator(
|
|
|
|
utilization,
|
|
|
|
self.util0,
|
|
|
|
self.rate0,
|
|
|
|
self.util1,
|
|
|
|
self.rate1,
|
|
|
|
self.max_rate,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// calcualtor function that can be used to compute an interest
|
|
|
|
/// rate based on the given parameters
|
|
|
|
#[inline(always)]
|
|
|
|
pub fn interest_rate_curve_calculator(
|
|
|
|
utilization: I80F48,
|
|
|
|
util0: I80F48,
|
|
|
|
rate0: I80F48,
|
|
|
|
util1: I80F48,
|
|
|
|
rate1: I80F48,
|
|
|
|
max_rate: I80F48,
|
|
|
|
) -> I80F48 {
|
2022-05-14 01:49:42 -07:00
|
|
|
// TODO: daffy: use optimal interest from oracle
|
2022-04-09 07:53:30 -07:00
|
|
|
if utilization <= util0 {
|
|
|
|
let slope = cm!(rate0 / util0);
|
|
|
|
cm!(slope * utilization)
|
|
|
|
} else if utilization <= util1 {
|
|
|
|
let extra_util = cm!(utilization - util0);
|
|
|
|
let slope = cm!((rate1 - rate0) / (util1 - util0));
|
|
|
|
cm!(rate0 + slope * extra_util)
|
|
|
|
} else {
|
2022-04-12 07:53:45 -07:00
|
|
|
let extra_util = cm!(utilization - util1);
|
|
|
|
let slope = cm!((max_rate - rate1) / (I80F48::ONE - util1));
|
|
|
|
cm!(rate1 + slope * extra_util)
|
2022-04-09 07:53:30 -07:00
|
|
|
}
|
|
|
|
}
|
2022-02-22 04:31:18 -08:00
|
|
|
}
|
2022-03-20 22:58:11 -07:00
|
|
|
|
2022-05-19 04:45:46 -07:00
|
|
|
#[macro_export]
|
|
|
|
macro_rules! bank_seeds {
|
|
|
|
( $bank:expr ) => {
|
|
|
|
&[
|
|
|
|
$bank.group.as_ref(),
|
|
|
|
b"Bank".as_ref(),
|
|
|
|
$bank.token_index.to_le_bytes(),
|
2022-06-27 02:27:17 -07:00
|
|
|
&bank.bank_num.to_le_bytes(),
|
2022-05-19 04:45:46 -07:00
|
|
|
&[$bank.bump],
|
|
|
|
]
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub use bank_seeds;
|
|
|
|
|
2022-03-20 22:58:11 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use bytemuck::Zeroable;
|
2022-05-18 03:52:46 -07:00
|
|
|
use std::cmp::min;
|
2022-03-20 22:58:11 -07:00
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
pub fn change() -> Result<()> {
|
|
|
|
let epsilon = I80F48::from_bits(1);
|
|
|
|
let cases = [
|
|
|
|
(-10.1, 1),
|
|
|
|
(-10.1, 10),
|
|
|
|
(-10.1, 11),
|
|
|
|
(-10.1, 50),
|
2022-06-22 04:11:19 -07:00
|
|
|
(-10.0, 10),
|
|
|
|
(-10.0, 11),
|
2022-03-20 22:58:11 -07:00
|
|
|
(-2.0, 2),
|
|
|
|
(-2.0, 3),
|
|
|
|
(-0.1, 1),
|
|
|
|
(0.0, 1),
|
|
|
|
(0.1, 1),
|
|
|
|
(10.1, -1),
|
|
|
|
(10.1, -9),
|
|
|
|
(10.1, -10),
|
|
|
|
(10.1, -11),
|
2022-06-22 04:11:19 -07:00
|
|
|
(10.0, -10),
|
|
|
|
(10.0, -9),
|
2022-03-20 22:58:11 -07:00
|
|
|
(1.0, -1),
|
|
|
|
(0.1, -1),
|
|
|
|
(0.0, -1),
|
|
|
|
(-0.1, -1),
|
|
|
|
(-1.1, -10),
|
|
|
|
];
|
|
|
|
|
|
|
|
for is_in_use in [false, true] {
|
|
|
|
for (start, change) in cases {
|
|
|
|
println!(
|
|
|
|
"testing: in use: {}, start: {}, change: {}",
|
|
|
|
is_in_use, start, change
|
|
|
|
);
|
|
|
|
|
|
|
|
//
|
|
|
|
// SETUP
|
|
|
|
//
|
|
|
|
|
|
|
|
let mut bank = Bank::zeroed();
|
|
|
|
bank.deposit_index = I80F48::from_num(100.0);
|
|
|
|
bank.borrow_index = I80F48::from_num(10.0);
|
2022-05-18 03:52:46 -07:00
|
|
|
bank.loan_origination_fee_rate = I80F48::from_num(0.1);
|
2022-03-20 22:58:11 -07:00
|
|
|
let indexed = |v: I80F48, b: &Bank| {
|
|
|
|
if v > 0 {
|
|
|
|
v / b.deposit_index
|
|
|
|
} else {
|
|
|
|
v / b.borrow_index
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-06-21 07:52:40 -07:00
|
|
|
let mut account = TokenPosition {
|
|
|
|
indexed_position: I80F48::ZERO,
|
2022-03-20 22:58:11 -07:00
|
|
|
token_index: 0,
|
|
|
|
in_use_count: if is_in_use { 1 } else { 0 },
|
2022-03-31 05:37:05 -07:00
|
|
|
reserved: Default::default(),
|
2022-03-20 22:58:11 -07:00
|
|
|
};
|
|
|
|
|
2022-06-21 07:52:40 -07:00
|
|
|
account.indexed_position = indexed(I80F48::from_num(start), &bank);
|
2022-03-20 22:58:11 -07:00
|
|
|
if start >= 0.0 {
|
2022-06-27 02:27:17 -07:00
|
|
|
bank.indexed_deposits = account.indexed_position;
|
2022-03-20 22:58:11 -07:00
|
|
|
} else {
|
2022-06-27 02:27:17 -07:00
|
|
|
bank.indexed_borrows = -account.indexed_position;
|
2022-03-20 22:58:11 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// get the rounded start value
|
|
|
|
let start_native = account.native(&bank);
|
|
|
|
|
|
|
|
//
|
|
|
|
// TEST
|
|
|
|
//
|
|
|
|
|
2022-03-26 11:34:44 -07:00
|
|
|
let change = I80F48::from(change);
|
2022-05-18 03:52:46 -07:00
|
|
|
let is_active = bank.change_with_fee(&mut account, change)?;
|
2022-03-20 22:58:11 -07:00
|
|
|
|
2022-03-26 11:34:44 -07:00
|
|
|
let mut expected_native = start_native + change;
|
2022-03-20 22:58:11 -07:00
|
|
|
if expected_native >= 0.0 && expected_native < 1.0 && !is_in_use {
|
|
|
|
assert!(!is_active);
|
|
|
|
assert_eq!(bank.dust, expected_native);
|
|
|
|
expected_native = I80F48::ZERO;
|
|
|
|
} else {
|
|
|
|
assert!(is_active);
|
|
|
|
assert_eq!(bank.dust, I80F48::ZERO);
|
|
|
|
}
|
2022-05-18 03:52:46 -07:00
|
|
|
if change < 0 && expected_native < 0 {
|
|
|
|
let new_borrow = -(expected_native - min(start_native, I80F48::ZERO));
|
|
|
|
expected_native -= new_borrow * bank.loan_origination_fee_rate;
|
|
|
|
}
|
2022-03-20 22:58:11 -07:00
|
|
|
let expected_indexed = indexed(expected_native, &bank);
|
|
|
|
|
|
|
|
// at most one epsilon error in the resulting indexed value
|
2022-06-21 07:52:40 -07:00
|
|
|
assert!((account.indexed_position - expected_indexed).abs() <= epsilon);
|
2022-03-20 22:58:11 -07:00
|
|
|
|
2022-06-21 07:52:40 -07:00
|
|
|
if account.indexed_position.is_positive() {
|
2022-06-27 02:27:17 -07:00
|
|
|
assert_eq!(bank.indexed_deposits, account.indexed_position);
|
|
|
|
assert_eq!(bank.indexed_borrows, I80F48::ZERO);
|
2022-03-20 22:58:11 -07:00
|
|
|
} else {
|
2022-06-27 02:27:17 -07:00
|
|
|
assert_eq!(bank.indexed_deposits, I80F48::ZERO);
|
|
|
|
assert_eq!(bank.indexed_borrows, -account.indexed_position);
|
2022-03-20 22:58:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|