Clarkeni/onchain interest (#244)

* Onchain interest calculation
* Fix to TokenBalanceLog for token_liq_bankruptcy (was previously using liqee liab position for liqor liab position).
* Log cumulative interest when token position is deactivated.
This commit is contained in:
Nicholas Clarke 2022-09-28 23:04:33 -07:00 committed by GitHub
parent 984695e8d0
commit 01a958cd22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 221 additions and 46 deletions

View File

@ -394,7 +394,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
// Deactivate inactive token accounts after health check
for raw_token_index in deactivated_token_positions {
account.deactivate_token_position(raw_token_index);
account.deactivate_token_position_and_log(raw_token_index, ctx.accounts.account.key());
}
Ok(())

View File

@ -112,7 +112,7 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
// Deposits can deactivate a position if they cancel out a previous borrow.
//
if !position_is_active {
account.deactivate_token_position(raw_token_index);
account.deactivate_token_position_and_log(raw_token_index, ctx.accounts.account.key());
}
emit!(DepositLog {

View File

@ -170,7 +170,16 @@ pub fn token_liq_bankruptcy(
let (liqor_quote, liqor_quote_raw_token_index, _) =
liqor.ensure_token_position(QUOTE_TOKEN_INDEX)?;
let liqor_quote_active = quote_bank.deposit(liqor_quote, insurance_transfer_i80f48)?;
let liqor_quote_indexed_position = liqor_quote.indexed_position;
// liqor quote
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqor.key(),
token_index: QUOTE_TOKEN_INDEX,
indexed_position: liqor_quote.indexed_position.to_bits(),
deposit_index: quote_deposit_index.to_bits(),
borrow_index: quote_borrow_index.to_bits(),
});
// transfer liab from liqee to liqor
let (liqor_liab, liqor_liab_raw_token_index, _) =
@ -178,6 +187,16 @@ pub fn token_liq_bankruptcy(
let (liqor_liab_active, loan_origination_fee) =
liab_bank.withdraw_with_fee(liqor_liab, liab_transfer)?;
// liqor liab
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqor.key(),
token_index: liab_token_index,
indexed_position: liqor_liab.indexed_position.to_bits(),
deposit_index: liab_deposit_index.to_bits(),
borrow_index: liab_borrow_index.to_bits(),
});
// Check liqor's health
if !liqor.fixed.is_in_health_region() {
let liqor_health =
@ -185,16 +204,6 @@ pub fn token_liq_bankruptcy(
require!(liqor_health >= 0, MangoError::HealthMustBePositive);
}
// liqor quote
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqor.key(),
token_index: QUOTE_TOKEN_INDEX,
indexed_position: liqor_quote_indexed_position.to_bits(),
deposit_index: quote_deposit_index.to_bits(),
borrow_index: quote_borrow_index.to_bits(),
});
if loan_origination_fee.is_positive() {
emit!(WithdrawLoanOriginationFeeLog {
mango_group: ctx.accounts.group.key(),
@ -206,10 +215,16 @@ pub fn token_liq_bankruptcy(
}
if !liqor_quote_active {
liqor.deactivate_token_position(liqor_quote_raw_token_index);
liqor.deactivate_token_position_and_log(
liqor_quote_raw_token_index,
ctx.accounts.liqor.key(),
);
}
if !liqor_liab_active {
liqor.deactivate_token_position(liqor_liab_raw_token_index);
liqor.deactivate_token_position_and_log(
liqor_liab_raw_token_index,
ctx.accounts.liqor.key(),
);
}
} else {
// For liab_token_index == QUOTE_TOKEN_INDEX: the insurance fund deposits directly into liqee,
@ -265,16 +280,6 @@ pub fn token_liq_bankruptcy(
require_eq!(liqee_liab.indexed_position, I80F48::ZERO);
}
// liqor liab
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqor.key(),
token_index: liab_token_index,
indexed_position: liqee_liab.indexed_position.to_bits(),
deposit_index: liab_deposit_index.to_bits(),
borrow_index: liab_borrow_index.to_bits(),
});
// liqee liab
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
@ -297,7 +302,7 @@ pub fn token_liq_bankruptcy(
.maybe_recover_from_being_liquidated(liqee_init_health);
if !liqee_liab_active {
liqee.deactivate_token_position(liqee_raw_token_index);
liqee.deactivate_token_position_and_log(liqee_raw_token_index, ctx.accounts.liqee.key());
}
emit!(LiquidateTokenBankruptcyLog {

View File

@ -143,24 +143,24 @@ pub fn token_liq_with_token(
// Apply the balance changes to the liqor and liqee accounts
let liqee_liab_position = liqee.token_position_mut_by_raw_index(liqee_liab_raw_index);
let liqee_liab_active = liab_bank.deposit_with_dusting(liqee_liab_position, liab_transfer)?;
let liqee_liab_position_indexed = liqee_liab_position.indexed_position;
let liqee_liab_indexed_position = liqee_liab_position.indexed_position;
let (liqor_liab_position, liqor_liab_raw_index, _) =
liqor.ensure_token_position(liab_token_index)?;
let (liqor_liab_active, loan_origination_fee) =
liab_bank.withdraw_with_fee(liqor_liab_position, liab_transfer)?;
let liqor_liab_position_indexed = liqor_liab_position.indexed_position;
let liqor_liab_indexed_position = liqor_liab_position.indexed_position;
let liqee_liab_native_after = liqee_liab_position.native(liab_bank);
let (liqor_asset_position, liqor_asset_raw_index, _) =
liqor.ensure_token_position(asset_token_index)?;
let liqor_asset_active = asset_bank.deposit(liqor_asset_position, asset_transfer)?;
let liqor_asset_position_indexed = liqor_asset_position.indexed_position;
let liqor_asset_indexed_position = liqor_asset_position.indexed_position;
let liqee_asset_position = liqee.token_position_mut_by_raw_index(liqee_asset_raw_index);
let liqee_asset_active =
asset_bank.withdraw_without_fee_with_dusting(liqee_asset_position, asset_transfer)?;
let liqee_asset_position_indexed = liqee_asset_position.indexed_position;
let liqee_asset_indexed_position = liqee_asset_position.indexed_position;
let liqee_assets_native_after = liqee_asset_position.native(asset_bank);
// Update the health cache
@ -184,7 +184,7 @@ pub fn token_liq_with_token(
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqee.key(),
token_index: asset_token_index,
indexed_position: liqee_asset_position_indexed.to_bits(),
indexed_position: liqee_asset_indexed_position.to_bits(),
deposit_index: asset_bank.deposit_index.to_bits(),
borrow_index: asset_bank.borrow_index.to_bits(),
});
@ -193,7 +193,7 @@ pub fn token_liq_with_token(
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqee.key(),
token_index: liab_token_index,
indexed_position: liqee_liab_position_indexed.to_bits(),
indexed_position: liqee_liab_indexed_position.to_bits(),
deposit_index: liab_bank.deposit_index.to_bits(),
borrow_index: liab_bank.borrow_index.to_bits(),
});
@ -202,7 +202,7 @@ pub fn token_liq_with_token(
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqor.key(),
token_index: asset_token_index,
indexed_position: liqor_asset_position_indexed.to_bits(),
indexed_position: liqor_asset_indexed_position.to_bits(),
deposit_index: asset_bank.deposit_index.to_bits(),
borrow_index: asset_bank.borrow_index.to_bits(),
});
@ -211,7 +211,7 @@ pub fn token_liq_with_token(
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqor.key(),
token_index: liab_token_index,
indexed_position: liqor_liab_position_indexed.to_bits(),
indexed_position: liqor_liab_indexed_position.to_bits(),
deposit_index: liab_bank.deposit_index.to_bits(),
borrow_index: liab_bank.borrow_index.to_bits(),
});
@ -228,16 +228,16 @@ pub fn token_liq_with_token(
// Since we use a scanning account retriever, it's safe to deactivate inactive token positions
if !liqee_asset_active {
liqee.deactivate_token_position(liqee_asset_raw_index);
liqee.deactivate_token_position_and_log(liqee_asset_raw_index, ctx.accounts.liqee.key());
}
if !liqee_liab_active {
liqee.deactivate_token_position(liqee_liab_raw_index);
liqee.deactivate_token_position_and_log(liqee_liab_raw_index, ctx.accounts.liqee.key());
}
if !liqor_asset_active {
liqor.deactivate_token_position(liqor_asset_raw_index);
liqor.deactivate_token_position_and_log(liqor_asset_raw_index, ctx.accounts.liqor.key());
}
if !liqor_liab_active {
liqor.deactivate_token_position(liqor_liab_raw_index)
liqor.deactivate_token_position_and_log(liqor_liab_raw_index, ctx.accounts.liqor.key())
}
// Check liqee health again

View File

@ -152,7 +152,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
// deactivated.
//
if !position_is_active {
account.deactivate_token_position(raw_token_index);
account.deactivate_token_position_and_log(raw_token_index, ctx.accounts.account.key());
}
emit!(WithdrawLog {

View File

@ -207,3 +207,12 @@ pub struct LiquidateTokenBankruptcyLog {
pub insurance_transfer: i128,
pub socialized_loss: i128,
}
#[event]
pub struct DeactivateTokenPositionLog {
pub mango_group: Pubkey,
pub mango_account: Pubkey,
pub token_index: u16,
pub cumulative_deposit_interest: f32,
pub cumulative_borrow_interest: f32,
}

View File

@ -210,6 +210,7 @@ impl Bank {
) -> Result<bool> {
require_gte!(native_amount, 0);
let native_position = position.native(self);
let opening_indexed_position = position.indexed_position;
// 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
@ -235,12 +236,14 @@ impl Bank {
// pay back borrows only, leaving a negative position
cm!(self.indexed_borrows -= indexed_change);
position.indexed_position = new_indexed_value;
self.update_cumulative_interest(position, opening_indexed_position);
return Ok(true);
} else if new_native_position < I80F48::ONE && allow_dusting {
// if there's less than one token deposited, zero the position
cm!(self.dust += new_native_position);
cm!(self.indexed_borrows += position.indexed_position);
position.indexed_position = I80F48::ZERO;
self.update_cumulative_interest(position, opening_indexed_position);
return Ok(false);
}
@ -256,6 +259,7 @@ impl Bank {
let indexed_change = div_rounding_up(native_amount, self.deposit_index);
cm!(self.indexed_deposits += indexed_change);
cm!(position.indexed_position += indexed_change);
self.update_cumulative_interest(position, opening_indexed_position);
Ok(true)
}
@ -317,6 +321,7 @@ impl Bank {
) -> Result<(bool, I80F48)> {
require_gte!(native_amount, 0);
let native_position = position.native(self);
let opening_indexed_position = position.indexed_position;
if native_position.is_positive() {
let new_native_position = cm!(native_position - native_amount);
@ -327,12 +332,14 @@ impl Bank {
cm!(self.dust += new_native_position);
cm!(self.indexed_deposits -= position.indexed_position);
position.indexed_position = I80F48::ZERO;
self.update_cumulative_interest(position, opening_indexed_position);
return Ok((false, I80F48::ZERO));
} else {
// withdraw some deposits leaving a positive balance
let indexed_change = cm!(native_amount / self.deposit_index);
cm!(self.indexed_deposits -= indexed_change);
cm!(position.indexed_position -= indexed_change);
self.update_cumulative_interest(position, opening_indexed_position);
return Ok((true, I80F48::ZERO));
}
}
@ -355,6 +362,7 @@ impl Bank {
let indexed_change = cm!(native_amount / self.borrow_index);
cm!(self.indexed_borrows += indexed_change);
cm!(position.indexed_position -= indexed_change);
self.update_cumulative_interest(position, opening_indexed_position);
Ok((true, loan_origination_fee))
}
@ -401,6 +409,30 @@ impl Bank {
}
}
pub fn update_cumulative_interest(
&self,
position: &mut TokenPosition,
opening_indexed_position: I80F48,
) {
if opening_indexed_position.is_positive() {
let interest =
cm!((self.deposit_index - position.previous_index) * opening_indexed_position)
.to_num::<f32>();
position.cumulative_deposit_interest += interest;
} else {
let interest =
cm!((self.borrow_index - position.previous_index) * opening_indexed_position)
.to_num::<f32>();
position.cumulative_borrow_interest += interest;
}
if position.indexed_position.is_positive() {
position.previous_index = self.deposit_index
} else {
position.previous_index = self.borrow_index
}
}
pub fn compute_index(
&self,
indexed_total_deposits: I80F48,
@ -634,8 +666,11 @@ mod tests {
indexed_position: I80F48::ZERO,
token_index: 0,
in_use_count: if is_in_use { 1 } else { 0 },
cumulative_deposit_interest: 0.0,
cumulative_borrow_interest: 0.0,
previous_index: I80F48::ZERO,
padding: Default::default(),
reserved: [0; 40],
reserved: [0; 16],
};
account.indexed_position = indexed(I80F48::from_num(start), &bank);

View File

@ -24,6 +24,7 @@ use super::TokenIndex;
use super::FREE_ORDER_SLOT;
use super::{HealthCache, HealthType};
use super::{PerpPosition, Serum3Orders, TokenPosition};
use crate::logs::DeactivateTokenPositionLog;
use checked_math as cm;
type BorshVecLength = u32;
@ -72,10 +73,12 @@ pub struct MangoAccount {
pub padding: [u8; 1],
// (Display only)
// Cumulative (deposits - withdraws)
// using USD prices at the time of the deposit/withdraw
// in USD units with 6 decimals
pub net_deposits: i64,
// (Display only)
// Cumulative settles on perp positions
// TODO: unimplemented
pub net_settled: i64,
@ -616,8 +619,11 @@ impl<
indexed_position: I80F48::ZERO,
token_index,
in_use_count: 0,
cumulative_deposit_interest: 0.0,
cumulative_borrow_interest: 0.0,
previous_index: I80F48::ZERO,
padding: Default::default(),
reserved: [0; 40],
reserved: [0; 16],
};
}
Ok((v, raw_index, bank_index))
@ -632,6 +638,24 @@ impl<
self.token_position_mut_by_raw_index(raw_index).token_index = TokenIndex::MAX;
}
pub fn deactivate_token_position_and_log(
&mut self,
raw_index: usize,
mango_account_pubkey: Pubkey,
) {
let mango_group = self.fixed.deref_or_borrow().group;
let token_position = self.token_position_mut_by_raw_index(raw_index);
assert!(token_position.in_use_count == 0);
emit!(DeactivateTokenPositionLog {
mango_group: mango_group,
mango_account: mango_account_pubkey,
token_index: token_position.token_index,
cumulative_deposit_interest: token_position.cumulative_deposit_interest,
cumulative_borrow_interest: token_position.cumulative_borrow_interest,
});
self.token_position_mut_by_raw_index(raw_index).token_index = TokenIndex::MAX;
}
// get mut Serum3Orders at raw_index
pub fn serum3_orders_mut_by_raw_index(&mut self, raw_index: usize) -> &mut Serum3Orders {
let offset = self.header().serum3_offset(raw_index);

View File

@ -32,13 +32,24 @@ pub struct TokenPosition {
pub padding: [u8; 5],
#[derivative(Debug = "ignore")]
pub reserved: [u8; 40],
pub reserved: [u8; 16],
// bookkeeping variable for onchain interest calculation
// either deposit_index or borrow_index at last indexed_position change
pub previous_index: I80F48,
// (Display only)
// Cumulative deposit interest in token native units
pub cumulative_deposit_interest: f32,
// (Display only)
// Cumulative borrow interest in token native units
pub cumulative_borrow_interest: f32,
}
unsafe impl bytemuck::Pod for TokenPosition {}
unsafe impl bytemuck::Zeroable for TokenPosition {}
const_assert_eq!(size_of::<TokenPosition>(), 24 + 40);
const_assert_eq!(size_of::<TokenPosition>(), 64);
const_assert_eq!(size_of::<TokenPosition>() % 8, 0);
impl Default for TokenPosition {
@ -47,8 +58,11 @@ impl Default for TokenPosition {
indexed_position: I80F48::ZERO,
token_index: TokenIndex::MAX,
in_use_count: 0,
cumulative_deposit_interest: 0.0,
cumulative_borrow_interest: 0.0,
previous_index: I80F48::ZERO,
padding: Default::default(),
reserved: [0; 40],
reserved: [0; 16],
}
}
}

View File

@ -4487,9 +4487,23 @@ export type MangoV4 = {
"type": {
"array": [
"u8",
40
8
]
}
},
{
"name": "previousIndex",
"type": {
"defined": "I80F48"
}
},
{
"name": "cumulativeDepositInterest",
"type": "i64"
},
{
"name": "cumulativeBorrowInterest",
"type": "i64"
}
]
}
@ -5978,6 +5992,36 @@ export type MangoV4 = {
"index": false
}
]
},
{
"name": "DeactivateTokenPositionLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
"index": false
},
{
"name": "tokenIndex",
"type": "u16",
"index": false
},
{
"name": "cumulativeDepositInterest",
"type": "i64",
"index": false
},
{
"name": "cumulativeBorrowInterest",
"type": "i64",
"index": false
}
]
}
],
"errors": [
@ -10588,9 +10632,23 @@ export const IDL: MangoV4 = {
"type": {
"array": [
"u8",
40
8
]
}
},
{
"name": "previousIndex",
"type": {
"defined": "I80F48"
}
},
{
"name": "cumulativeDepositInterest",
"type": "i64"
},
{
"name": "cumulativeBorrowInterest",
"type": "i64"
}
]
}
@ -12079,6 +12137,36 @@ export const IDL: MangoV4 = {
"index": false
}
]
},
{
"name": "DeactivateTokenPositionLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
"index": false
},
{
"name": "tokenIndex",
"type": "u16",
"index": false
},
{
"name": "cumulativeDepositInterest",
"type": "i64",
"index": false
},
{
"name": "cumulativeBorrowInterest",
"type": "i64",
"index": false
}
]
}
],
"errors": [