Clarkeni/loan fee logging (#180)

Logging for loan origination fees and token bankruptcy
This commit is contained in:
Nicholas Clarke 2022-08-19 18:50:54 -07:00 committed by GitHub
parent 6814701046
commit 704dfcaa27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 663 additions and 199 deletions

2
anchor

@ -1 +1 @@
Subproject commit b52f23614601652a99ec6c27aec77bd327363b31
Subproject commit 9e546f52d967e95fea4ae105f4bc7bf3720b9464

View File

@ -10,6 +10,11 @@ use crate::state::ScanningAccountRetriever;
use crate::state::*;
use crate::util::checked_math as cm;
use crate::logs::{
LiquidateTokenBankruptcyLog, LoanOriginationFeeInstruction, TokenBalanceLog,
WithdrawLoanOriginationFeeLog,
};
// Remaining accounts:
// - all banks for liab_mint_info (writable)
// - merged health accounts for liqor+liqee
@ -98,7 +103,8 @@ pub fn liq_token_bankruptcy(
let (liab_bank, liab_price, opt_quote_bank_and_price) =
account_retriever.banks_mut_and_oracles(liab_token_index, QUOTE_TOKEN_INDEX)?;
let liab_deposit_index = liab_bank.deposit_index;
let mut liab_deposit_index = liab_bank.deposit_index;
let liab_borrow_index = liab_bank.borrow_index;
let (liqee_liab, liqee_raw_token_index) = liqee.token_position_mut(liab_token_index)?;
let initial_liab_native = liqee_liab.native(&liab_bank);
let mut remaining_liab_loss = -initial_liab_native;
@ -151,26 +157,52 @@ pub fn liq_token_bankruptcy(
)?;
// move quote assets into liqor and withdraw liab assets
if let Some((quote_bank, _)) = opt_quote_bank_and_price {
if let Some((quote_bank, quote_price)) = opt_quote_bank_and_price {
// account constraint #2 a)
require_keys_eq!(quote_bank.vault, ctx.accounts.quote_vault.key());
require_keys_eq!(quote_bank.mint, ctx.accounts.insurance_vault.mint);
let quote_deposit_index = quote_bank.deposit_index;
let quote_borrow_index = quote_bank.borrow_index;
// credit the liqor
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;
// transfer liab from liqee to liqor
let (liqor_liab, liqor_liab_raw_token_index, _) =
liqor.ensure_token_position(liab_token_index)?;
let liqor_liab_active = liab_bank.withdraw_with_fee(liqor_liab, liab_transfer)?;
let (liqor_liab_active, loan_origination_fee) =
liab_bank.withdraw_with_fee(liqor_liab, liab_transfer)?;
// Check liqor's health
let liqor_health =
compute_health(&liqor.borrow(), HealthType::Init, &account_retriever)?;
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(),
price: quote_price.to_bits(),
});
if loan_origination_fee.is_positive() {
emit!(WithdrawLoanOriginationFeeLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqor.key(),
token_index: liab_token_index,
loan_origination_fee: loan_origination_fee.to_bits(),
instruction: LoanOriginationFeeInstruction::LiqTokenBankruptcy
});
}
if !liqor_quote_active {
liqor.deactivate_token_position(liqor_quote_raw_token_index);
}
@ -191,6 +223,7 @@ pub fn liq_token_bankruptcy(
// Socialize loss if there's more loss and noone else could use the
// insurance fund to cover it.
let mut socialized_loss = I80F48::ZERO;
if insurance_fund_exhausted && remaining_liab_loss.is_positive() {
// find the total deposits
let mut indexed_total_deposits = I80F48::ZERO;
@ -205,6 +238,8 @@ pub fn liq_token_bankruptcy(
// Probably not.
let new_deposit_index =
cm!(liab_deposit_index - remaining_liab_loss / indexed_total_deposits);
liab_deposit_index = new_deposit_index;
socialized_loss = remaining_liab_loss;
let mut amount_to_credit = remaining_liab_loss;
for bank_ai in bank_ais.iter() {
@ -228,6 +263,28 @@ pub fn liq_token_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(),
price: liab_price.to_bits(),
});
// liqee liab
emit!(TokenBalanceLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.liqee.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(),
price: liab_price.to_bits(),
});
let liab_bank = bank_ais[0].load::<Bank>()?;
let end_liab_native = liqee_liab.native(&liab_bank);
liqee_health_cache
@ -243,5 +300,17 @@ pub fn liq_token_bankruptcy(
liqee.deactivate_token_position(liqee_raw_token_index);
}
emit!(LiquidateTokenBankruptcyLog {
mango_group: ctx.accounts.group.key(),
liqee: ctx.accounts.liqee.key(),
liqor: ctx.accounts.liqor.key(),
liab_token_index: liab_token_index,
initial_liab_native: initial_liab_native.to_bits(),
liab_price: liab_price.to_bits(),
insurance_token_index: QUOTE_TOKEN_INDEX,
insurance_transfer: insurance_transfer_i80f48.to_bits(),
socialized_loss: socialized_loss.to_bits()
});
Ok(())
}

View File

@ -3,7 +3,10 @@ use fixed::types::I80F48;
use std::cmp::min;
use crate::error::*;
use crate::logs::{LiquidateTokenAndTokenLog, TokenBalanceLog};
use crate::logs::{
LiquidateTokenAndTokenLog, LoanOriginationFeeInstruction, TokenBalanceLog,
WithdrawLoanOriginationFeeLog,
};
use crate::state::ScanningAccountRetriever;
use crate::state::*;
use crate::util::checked_math as cm;
@ -77,171 +80,168 @@ pub fn liq_token_with_token(
// Transfer some liab_token from liqor to liqee and
// transfer some asset_token from liqee to liqor.
//
{
// Get the mut banks and oracle prices
//
// This must happen _after_ the health computation, since immutable borrows of
// the bank are not allowed at the same time.
let (asset_bank, asset_price, opt_liab_bank_and_price) =
account_retriever.banks_mut_and_oracles(asset_token_index, liab_token_index)?;
let (liab_bank, liab_price) = opt_liab_bank_and_price.unwrap();
// The main complication here is that we can't keep the liqee_asset_position and liqee_liab_position
// borrows alive at the same time. Possibly adding get_mut_pair() would be helpful.
let (liqee_asset_position, liqee_asset_raw_index) =
liqee.token_position_and_raw_index(asset_token_index)?;
let liqee_asset_native = liqee_asset_position.native(asset_bank);
require!(liqee_asset_native.is_positive(), MangoError::SomeError);
// Get the mut banks and oracle prices
//
// This must happen _after_ the health computation, since immutable borrows of
// the bank are not allowed at the same time.
let (asset_bank, asset_price, opt_liab_bank_and_price) =
account_retriever.banks_mut_and_oracles(asset_token_index, liab_token_index)?;
let (liab_bank, liab_price) = opt_liab_bank_and_price.unwrap();
let (liqee_liab_position, liqee_liab_raw_index) =
liqee.token_position_and_raw_index(liab_token_index)?;
let liqee_liab_native = liqee_liab_position.native(liab_bank);
require!(liqee_liab_native.is_negative(), MangoError::SomeError);
// The main complication here is that we can't keep the liqee_asset_position and liqee_liab_position
// borrows alive at the same time. Possibly adding get_mut_pair() would be helpful.
let (liqee_asset_position, liqee_asset_raw_index) =
liqee.token_position_and_raw_index(asset_token_index)?;
let liqee_asset_native = liqee_asset_position.native(asset_bank);
require!(liqee_asset_native.is_positive(), MangoError::SomeError);
// TODO why sum of both tokens liquidation fees? Add comment
let fee_factor = I80F48::ONE + asset_bank.liquidation_fee + liab_bank.liquidation_fee;
let liab_price_adjusted = liab_price * fee_factor;
let (liqee_liab_position, liqee_liab_raw_index) =
liqee.token_position_and_raw_index(liab_token_index)?;
let liqee_liab_native = liqee_liab_position.native(liab_bank);
require!(liqee_liab_native.is_negative(), MangoError::SomeError);
let init_asset_weight = asset_bank.init_asset_weight;
let init_liab_weight = liab_bank.init_liab_weight;
// TODO why sum of both tokens liquidation fees? Add comment
let fee_factor = I80F48::ONE + asset_bank.liquidation_fee + liab_bank.liquidation_fee;
let liab_price_adjusted = liab_price * fee_factor;
// How much asset would need to be exchanged to liab in order to bring health to 0?
//
// That means: what is x (unit: native liab tokens) such that
// init_health + x * ilw * lp - y * iaw * ap = 0
// where
// ilw = init_liab_weight, lp = liab_price
// iap = init_asset_weight, ap = asset_price
// ff = fee_factor, lpa = lp * ff
// and the asset cost of getting x native units of liab is:
// y = x * lp / ap * ff = x * lpa / ap (native asset tokens)
//
// Result: x = -init_health / (lp * ilw - iaw * lpa)
let liab_needed = cm!(-init_health
let init_asset_weight = asset_bank.init_asset_weight;
let init_liab_weight = liab_bank.init_liab_weight;
// How much asset would need to be exchanged to liab in order to bring health to 0?
//
// That means: what is x (unit: native liab tokens) such that
// init_health + x * ilw * lp - y * iaw * ap = 0
// where
// ilw = init_liab_weight, lp = liab_price
// iap = init_asset_weight, ap = asset_price
// ff = fee_factor, lpa = lp * ff
// and the asset cost of getting x native units of liab is:
// y = x * lp / ap * ff = x * lpa / ap (native asset tokens)
//
// Result: x = -init_health / (lp * ilw - iaw * lpa)
let liab_needed =
cm!(-init_health
/ (liab_price * init_liab_weight - init_asset_weight * liab_price_adjusted));
// How much liab can we get at most for the asset balance?
let liab_possible = cm!(liqee_asset_native * asset_price / liab_price_adjusted);
// How much liab can we get at most for the asset balance?
let liab_possible = cm!(liqee_asset_native * asset_price / liab_price_adjusted);
// The amount of liab native tokens we will transfer
let liab_transfer = min(
min(min(liab_needed, -liqee_liab_native), liab_possible),
max_liab_transfer,
);
// The amount of liab native tokens we will transfer
let liab_transfer = min(
min(min(liab_needed, -liqee_liab_native), liab_possible),
max_liab_transfer,
);
// The amount of asset native tokens we will give up for them
let asset_transfer = cm!(liab_transfer * liab_price_adjusted / asset_price);
// The amount of asset native tokens we will give up for them
let asset_transfer = cm!(liab_transfer * liab_price_adjusted / asset_price);
// During liquidation, we mustn't leave small positive balances in the liqee. Those
// could break bankruptcy-detection. Thus we dust them even if the token position
// is nominally in-use.
// During liquidation, we mustn't leave small positive balances in the liqee. Those
// could break bankruptcy-detection. Thus we dust them even if the token position
// is nominally in-use.
// 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;
// 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 (liqor_liab_position, liqor_liab_raw_index, _) =
liqor.ensure_token_position(liab_token_index)?;
let liqor_liab_active = liab_bank.withdraw_with_fee(liqor_liab_position, liab_transfer)?;
let liqor_liab_position_indexed = liqor_liab_position.indexed_position;
let liqee_liab_native_after = liqee_liab_position.native(&liab_bank);
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 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_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 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_assets_native_after = liqee_asset_position.native(&asset_bank);
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_assets_native_after = liqee_asset_position.native(&asset_bank);
// Update the health cache
liqee_health_cache.adjust_token_balance(
liab_token_index,
cm!(liqee_liab_native_after - liqee_liab_native),
)?;
liqee_health_cache.adjust_token_balance(
asset_token_index,
cm!(liqee_assets_native_after - liqee_asset_native),
)?;
// Update the health cache
liqee_health_cache.adjust_token_balance(
liab_token_index,
cm!(liqee_liab_native_after - liqee_liab_native),
)?;
liqee_health_cache.adjust_token_balance(
asset_token_index,
cm!(liqee_assets_native_after - liqee_asset_native),
)?;
msg!(
"liquidated {} liab for {} asset",
liab_transfer,
asset_transfer
);
msg!(
"liquidated {} liab for {} asset",
liab_transfer,
asset_transfer
);
emit!(LiquidateTokenAndTokenLog {
mango_group: ctx.accounts.group.key(),
liqee: ctx.accounts.liqee.key(),
liqor: ctx.accounts.liqor.key(),
asset_token_index,
liab_token_index,
asset_transfer: asset_transfer.to_bits(),
liab_transfer: liab_transfer.to_bits(),
asset_price: asset_price.to_bits(),
liab_price: liab_price.to_bits(),
// bankruptcy:
});
// liqee asset
emit!(TokenBalanceLog {
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(),
deposit_index: asset_bank.deposit_index.to_bits(),
borrow_index: asset_bank.borrow_index.to_bits(),
price: asset_price.to_bits(),
});
// liqee liab
emit!(TokenBalanceLog {
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(),
deposit_index: liab_bank.deposit_index.to_bits(),
borrow_index: liab_bank.borrow_index.to_bits(),
price: liab_price.to_bits(),
});
// liqor asset
emit!(TokenBalanceLog {
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(),
deposit_index: asset_bank.deposit_index.to_bits(),
borrow_index: asset_bank.borrow_index.to_bits(),
price: asset_price.to_bits(),
});
// 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_position_indexed.to_bits(),
deposit_index: liab_bank.deposit_index.to_bits(),
borrow_index: liab_bank.borrow_index.to_bits(),
price: liab_price.to_bits(),
});
// liqee asset
emit!(TokenBalanceLog {
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(),
deposit_index: asset_bank.deposit_index.to_bits(),
borrow_index: asset_bank.borrow_index.to_bits(),
price: asset_price.to_bits(),
});
// liqee liab
emit!(TokenBalanceLog {
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(),
deposit_index: liab_bank.deposit_index.to_bits(),
borrow_index: liab_bank.borrow_index.to_bits(),
price: liab_price.to_bits(),
});
// liqor asset
emit!(TokenBalanceLog {
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(),
deposit_index: asset_bank.deposit_index.to_bits(),
borrow_index: asset_bank.borrow_index.to_bits(),
price: asset_price.to_bits(),
});
// liqor liab
emit!(TokenBalanceLog {
if loan_origination_fee.is_positive() {
emit!(WithdrawLoanOriginationFeeLog {
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(),
deposit_index: liab_bank.deposit_index.to_bits(),
borrow_index: liab_bank.borrow_index.to_bits(),
price: liab_price.to_bits(),
loan_origination_fee: loan_origination_fee.to_bits(),
instruction: LoanOriginationFeeInstruction::LiqTokenWithToken
});
}
// 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);
}
if !liqee_liab_active {
liqee.deactivate_token_position(liqee_liab_raw_index);
}
if !liqor_asset_active {
liqor.deactivate_token_position(liqor_asset_raw_index);
}
if !liqor_liab_active {
liqor.deactivate_token_position(liqor_liab_raw_index)
}
// 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);
}
if !liqee_liab_active {
liqee.deactivate_token_position(liqee_liab_raw_index);
}
if !liqor_asset_active {
liqor.deactivate_token_position(liqor_asset_raw_index);
}
if !liqor_liab_active {
liqor.deactivate_token_position(liqor_liab_raw_index)
}
// Check liqee health again
@ -255,5 +255,18 @@ pub fn liq_token_with_token(
.context("compute liqor health")?;
require!(liqor_health >= 0, MangoError::HealthMustBePositive);
emit!(LiquidateTokenAndTokenLog {
mango_group: ctx.accounts.group.key(),
liqee: ctx.accounts.liqee.key(),
liqor: ctx.accounts.liqor.key(),
asset_token_index,
liab_token_index,
asset_transfer: asset_transfer.to_bits(),
liab_transfer: liab_transfer.to_bits(),
asset_price: asset_price.to_bits(),
liab_price: liab_price.to_bits(),
bankruptcy: !liqee_health_cache.has_liquidatable_assets() & liqee_init_health.is_negative()
});
Ok(())
}

View File

@ -5,6 +5,8 @@ use crate::error::*;
use crate::instructions::apply_vault_difference;
use crate::state::*;
use crate::logs::{LoanOriginationFeeInstruction, WithdrawLoanOriginationFeeLog};
#[derive(Accounts)]
pub struct Serum3LiqForceCancelOrders<'info> {
pub group: AccountLoader<'info, Group>,
@ -68,9 +70,9 @@ pub fn serum3_liq_force_cancel_orders(
//
// Validation
//
let serum_market = ctx.accounts.serum_market.load()?;
{
let account = ctx.accounts.account.load()?;
let serum_market = ctx.accounts.serum_market.load()?;
// Validate open_orders
require!(
@ -138,16 +140,36 @@ pub fn serum3_liq_force_cancel_orders(
let mut account = ctx.accounts.account.load_mut()?;
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
apply_vault_difference(
&mut account.borrow_mut(),
&mut base_bank,
after_base_vault,
before_base_vault,
&mut quote_bank,
after_quote_vault,
before_quote_vault,
)?
.deactivate_inactive_token_accounts(&mut account.borrow_mut());
let (vault_difference_result, base_loan_origination_fee, quote_loan_origination_fee) =
apply_vault_difference(
&mut account.borrow_mut(),
&mut base_bank,
after_base_vault,
before_base_vault,
&mut quote_bank,
after_quote_vault,
before_quote_vault,
)?;
vault_difference_result.deactivate_inactive_token_accounts(&mut account.borrow_mut());
if base_loan_origination_fee.is_positive() {
emit!(WithdrawLoanOriginationFeeLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
token_index: serum_market.base_token_index,
loan_origination_fee: base_loan_origination_fee.to_bits(),
instruction: LoanOriginationFeeInstruction::Serum3LiqForceCancelOrders
});
}
if quote_loan_origination_fee.is_positive() {
emit!(WithdrawLoanOriginationFeeLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
token_index: serum_market.quote_token_index,
loan_origination_fee: quote_loan_origination_fee.to_bits(),
instruction: LoanOriginationFeeInstruction::Serum3LiqForceCancelOrders
});
}
Ok(())
}

View File

@ -12,6 +12,8 @@ use serum_dex::instruction::NewOrderInstructionV3;
use serum_dex::matching::Side;
use serum_dex::state::OpenOrders;
use crate::logs::{LoanOriginationFeeInstruction, WithdrawLoanOriginationFeeLog};
/// For loan origination fees bookkeeping purposes
pub struct OpenOrdersSlim {
pub native_coin_free: u64,
@ -273,9 +275,10 @@ pub fn serum3_place_order(
// Charge the difference in vault balances to the user's account
let mut account = ctx.accounts.account.load_mut()?;
let vault_difference_result = {
let (vault_difference_result, base_loan_origination_fee, quote_loan_origination_fee) = {
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
apply_vault_difference(
&mut account.borrow_mut(),
&mut base_bank,
@ -298,6 +301,25 @@ pub fn serum3_place_order(
vault_difference_result.deactivate_inactive_token_accounts(&mut account.borrow_mut());
if base_loan_origination_fee.is_positive() {
emit!(WithdrawLoanOriginationFeeLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
token_index: serum_market.base_token_index,
loan_origination_fee: base_loan_origination_fee.to_bits(),
instruction: LoanOriginationFeeInstruction::Serum3PlaceOrder
});
}
if quote_loan_origination_fee.is_positive() {
emit!(WithdrawLoanOriginationFeeLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
token_index: serum_market.quote_token_index,
loan_origination_fee: quote_loan_origination_fee.to_bits(),
instruction: LoanOriginationFeeInstruction::Serum3PlaceOrder
});
}
Ok(())
}
@ -350,25 +372,31 @@ pub fn apply_vault_difference(
quote_bank: &mut Bank,
after_quote_vault: u64,
before_quote_vault: u64,
) -> Result<VaultDifferenceResult> {
) -> Result<(VaultDifferenceResult, I80F48, I80F48)> {
// TODO: Applying the loan origination fee here may be too early: it should only be
// charged if an order executes and the loan materializes? Otherwise MMs that place
// an order without having the funds will be charged for each place_order!
let (base_position, base_raw_index) = account.token_position_mut(base_bank.token_index)?;
let base_change = I80F48::from(after_base_vault) - I80F48::from(before_base_vault);
let base_active = base_bank.change_with_fee(base_position, base_change)?;
let (base_active, base_loan_origination_fee) =
base_bank.change_with_fee(base_position, base_change)?;
let (quote_position, quote_raw_index) = account.token_position_mut(quote_bank.token_index)?;
let quote_change = I80F48::from(after_quote_vault) - I80F48::from(before_quote_vault);
let quote_active = quote_bank.change_with_fee(quote_position, quote_change)?;
let (quote_active, quote_loan_origination_fee) =
quote_bank.change_with_fee(quote_position, quote_change)?;
Ok(VaultDifferenceResult {
base_raw_index,
base_active,
quote_raw_index,
quote_active,
})
Ok((
VaultDifferenceResult {
base_raw_index,
base_active,
quote_raw_index,
quote_active,
},
base_loan_origination_fee,
quote_loan_origination_fee,
))
}
fn cpi_place_order(ctx: &Serum3PlaceOrder, order: NewOrderInstructionV3) -> Result<()> {

View File

@ -10,6 +10,7 @@ use crate::serum3_cpi::load_open_orders_ref;
use crate::state::*;
use super::{apply_vault_difference, OpenOrdersReserved, OpenOrdersSlim};
use crate::logs::{LoanOriginationFeeInstruction, WithdrawLoanOriginationFeeLog};
#[derive(Accounts)]
pub struct Serum3SettleFunds<'info> {
@ -149,16 +150,36 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
let mut account = ctx.accounts.account.load_mut()?;
let mut base_bank = ctx.accounts.base_bank.load_mut()?;
let mut quote_bank = ctx.accounts.quote_bank.load_mut()?;
apply_vault_difference(
&mut account.borrow_mut(),
&mut base_bank,
after_base_vault,
before_base_vault,
&mut quote_bank,
after_quote_vault,
before_quote_vault,
)?
.deactivate_inactive_token_accounts(&mut account.borrow_mut());
let (vault_difference_result, base_loan_origination_fee, quote_loan_origination_fee) =
apply_vault_difference(
&mut account.borrow_mut(),
&mut base_bank,
after_base_vault,
before_base_vault,
&mut quote_bank,
after_quote_vault,
before_quote_vault,
)?;
vault_difference_result.deactivate_inactive_token_accounts(&mut account.borrow_mut());
if base_loan_origination_fee.is_positive() {
emit!(WithdrawLoanOriginationFeeLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
token_index: serum_market.base_token_index,
loan_origination_fee: base_loan_origination_fee.to_bits(),
instruction: LoanOriginationFeeInstruction::Serum3SettleFunds
});
}
if quote_loan_origination_fee.is_positive() {
emit!(WithdrawLoanOriginationFeeLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
token_index: serum_market.quote_token_index,
loan_origination_fee: quote_loan_origination_fee.to_bits(),
instruction: LoanOriginationFeeInstruction::Serum3SettleFunds
});
}
}
Ok(())

View File

@ -117,7 +117,9 @@ pub fn token_update_index_and_rate(ctx: Context<TokenUpdateIndexAndRate>) -> Res
deposit_index: deposit_index.to_bits(),
borrow_index: borrow_index.to_bits(),
avg_utilization: new_avg_utilization.to_bits(),
price: price.to_bits()
price: price.to_bits(),
collected_fees: some_bank.collected_fees_native.to_bits(),
loan_fee_rate: some_bank.loan_fee_rate.to_bits()
});
drop(some_bank);

View File

@ -6,7 +6,9 @@ use anchor_spl::token::Token;
use anchor_spl::token::TokenAccount;
use fixed::types::I80F48;
use crate::logs::{TokenBalanceLog, WithdrawLog};
use crate::logs::{
LoanOriginationFeeInstruction, TokenBalanceLog, WithdrawLoanOriginationFeeLog, WithdrawLog,
};
use crate::state::new_fixed_order_account_retriever;
use crate::util::checked_math as cm;
@ -62,7 +64,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
// The bank will also be passed in remainingAccounts. Use an explicit scope
// to drop the &mut before we borrow it immutably again later.
let (position_is_active, amount_i80f48) = {
let (position_is_active, amount_i80f48, loan_origination_fee) = {
let mut bank = ctx.accounts.bank.load_mut()?;
let native_position = position.native(&bank);
@ -87,7 +89,8 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
let amount_i80f48 = I80F48::from(amount);
// Update the bank and position
let position_is_active = bank.withdraw_with_fee(position, amount_i80f48)?;
let (position_is_active, loan_origination_fee) =
bank.withdraw_with_fee(position, amount_i80f48)?;
// Provide a readable error message in case the vault doesn't have enough tokens
if ctx.accounts.vault.amount < amount {
@ -106,7 +109,7 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
amount,
)?;
(position_is_active, amount_i80f48)
(position_is_active, amount_i80f48, loan_origination_fee)
};
let indexed_position = position.indexed_position;
@ -156,5 +159,15 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
price: oracle_price.to_bits(),
});
if loan_origination_fee.is_positive() {
emit!(WithdrawLoanOriginationFeeLog {
mango_group: ctx.accounts.group.key(),
mango_account: ctx.accounts.account.key(),
token_index,
loan_origination_fee: loan_origination_fee.to_bits(),
instruction: LoanOriginationFeeInstruction::TokenWithdraw,
});
}
Ok(())
}

View File

@ -137,6 +137,8 @@ pub struct UpdateIndexLog {
pub borrow_index: i128, // I80F48
pub avg_utilization: i128, // I80F48
pub price: i128, // I80F48
pub collected_fees: i128, // I80F48
pub loan_fee_rate: i128, // I80F48
}
#[event]
@ -159,7 +161,7 @@ pub struct LiquidateTokenAndTokenLog {
pub liab_transfer: i128, // I80F48
pub asset_price: i128, // I80F48
pub liab_price: i128, // I80F48
// pub bankruptcy: bool,
pub bankruptcy: bool,
}
#[event]
@ -175,3 +177,37 @@ pub struct OpenOrdersBalanceLog {
pub referrer_rebates_accrued: u64,
pub price: i128, // I80F48
}
#[derive(PartialEq, Copy, Clone, Debug, AnchorSerialize, AnchorDeserialize)]
#[repr(u8)]
pub enum LoanOriginationFeeInstruction {
Unknown,
LiqTokenBankruptcy,
LiqTokenWithToken,
Serum3LiqForceCancelOrders,
Serum3PlaceOrder,
Serum3SettleFunds,
TokenWithdraw,
}
#[event]
pub struct WithdrawLoanOriginationFeeLog {
pub mango_group: Pubkey,
pub mango_account: Pubkey,
pub token_index: u16,
pub loan_origination_fee: i128, // I80F48
pub instruction: LoanOriginationFeeInstruction,
}
#[event]
pub struct LiquidateTokenBankruptcyLog {
pub mango_group: Pubkey,
pub liqee: Pubkey,
pub liqor: Pubkey,
pub liab_token_index: u16,
pub initial_liab_native: i128,
pub liab_price: i128,
pub insurance_token_index: u16,
pub insurance_transfer: i128,
pub socialized_loss: i128,
}

View File

@ -271,7 +271,10 @@ impl Bank {
position: &mut TokenPosition,
native_amount: I80F48,
) -> Result<bool> {
self.withdraw_internal(position, native_amount, false, !position.is_in_use())
let (position_is_active, _) =
self.withdraw_internal(position, native_amount, false, !position.is_in_use())?;
return Ok(position_is_active);
}
/// Like `withdraw_without_fee()` but allows dusting of in-use token accounts.
@ -282,8 +285,9 @@ impl Bank {
position: &mut TokenPosition,
native_amount: I80F48,
) -> Result<bool> {
self.withdraw_internal(position, native_amount, false, true)
.map(|not_dusted| not_dusted || position.is_in_use())
Ok(self
.withdraw_internal(position, native_amount, false, true)
.map(|(not_dusted, _)| not_dusted || position.is_in_use())?)
}
/// Withdraws `native_amount` while applying the loan origination fee if a borrow is created.
@ -298,7 +302,7 @@ impl Bank {
&mut self,
position: &mut TokenPosition,
native_amount: I80F48,
) -> Result<bool> {
) -> Result<(bool, I80F48)> {
self.withdraw_internal(position, native_amount, true, !position.is_in_use())
}
@ -309,7 +313,7 @@ impl Bank {
mut native_amount: I80F48,
with_loan_origination_fee: bool,
allow_dusting: bool,
) -> Result<bool> {
) -> Result<(bool, I80F48)> {
require_gte!(native_amount, 0);
let native_position = position.native(self);
@ -322,13 +326,13 @@ impl Bank {
self.dust = cm!(self.dust + new_native_position);
self.indexed_deposits = cm!(self.indexed_deposits - position.indexed_position);
position.indexed_position = I80F48::ZERO;
return Ok(false);
return Ok((false, I80F48::ZERO));
} else {
// withdraw some deposits leaving a positive balance
let indexed_change = cm!(native_amount / self.deposit_index);
self.indexed_deposits = cm!(self.indexed_deposits - indexed_change);
position.indexed_position = cm!(position.indexed_position - indexed_change);
return Ok(true);
return Ok((true, I80F48::ZERO));
}
}
@ -339,8 +343,9 @@ impl Bank {
native_amount = -new_native_position;
}
let mut loan_origination_fee = I80F48::ZERO;
if with_loan_origination_fee {
let loan_origination_fee = cm!(self.loan_origination_fee_rate * native_amount);
loan_origination_fee = cm!(self.loan_origination_fee_rate * native_amount);
self.collected_fees_native = cm!(self.collected_fees_native + loan_origination_fee);
native_amount = cm!(native_amount + loan_origination_fee);
}
@ -350,7 +355,7 @@ impl Bank {
self.indexed_borrows = cm!(self.indexed_borrows + indexed_change);
position.indexed_position = cm!(position.indexed_position - indexed_change);
Ok(true)
Ok((true, loan_origination_fee))
}
// withdraw the loan origination fee for a borrow that happenend earlier
@ -358,12 +363,15 @@ impl Bank {
&mut self,
position: &mut TokenPosition,
already_borrowed_native_amount: I80F48,
) -> Result<bool> {
) -> Result<(bool, I80F48)> {
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);
self.withdraw_internal(position, loan_origination_fee, false, !position.is_in_use())
let (position_is_active, _) =
self.withdraw_internal(position, loan_origination_fee, false, !position.is_in_use())?;
Ok((position_is_active, loan_origination_fee))
}
/// Change a position without applying the loan origination fee
@ -384,9 +392,9 @@ impl Bank {
&mut self,
position: &mut TokenPosition,
native_amount: I80F48,
) -> Result<bool> {
) -> Result<(bool, I80F48)> {
if native_amount >= 0 {
self.deposit(position, native_amount)
Ok((self.deposit(position, native_amount)?, I80F48::ZERO))
} else {
self.withdraw_with_fee(position, -native_amount)
}
@ -635,7 +643,7 @@ mod tests {
//
let change = I80F48::from(change);
let is_active = bank.change_with_fee(&mut account, change)?;
let (is_active, _) = bank.change_with_fee(&mut account, change)?;
let mut expected_native = start_native + change;
if expected_native >= 0.0 && expected_native < 1.0 && !is_in_use {

View File

@ -109,7 +109,7 @@ impl TestContextBuilder {
}));
// intentionally set to as tight as possible, to catch potential problems early
test.set_compute_max_units(86000);
test.set_compute_max_units(87000);
Self {
test,

View File

@ -4322,6 +4322,35 @@ export type MangoV4 = {
]
}
},
{
"name": "LoanOriginationFeeInstruction",
"type": {
"kind": "enum",
"variants": [
{
"name": "Unknown"
},
{
"name": "LiqTokenBankruptcy"
},
{
"name": "LiqTokenWithToken"
},
{
"name": "Serum3LiqForceCancelOrders"
},
{
"name": "Serum3PlaceOrder"
},
{
"name": "Serum3SettleFunds"
},
{
"name": "TokenWithdraw"
}
]
}
},
{
"name": "HealthType",
"docs": [
@ -4848,6 +4877,16 @@ export type MangoV4 = {
"name": "price",
"type": "i128",
"index": false
},
{
"name": "collectedFees",
"type": "i128",
"index": false
},
{
"name": "loanFeeRate",
"type": "i128",
"index": false
}
]
},
@ -4928,6 +4967,11 @@ export type MangoV4 = {
"name": "liabPrice",
"type": "i128",
"index": false
},
{
"name": "bankruptcy",
"type": "bool",
"index": false
}
]
},
@ -4980,6 +5024,88 @@ export type MangoV4 = {
"index": false
}
]
},
{
"name": "WithdrawLoanOriginationFeeLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
"index": false
},
{
"name": "tokenIndex",
"type": "u16",
"index": false
},
{
"name": "loanOriginationFee",
"type": "i128",
"index": false
},
{
"name": "instruction",
"type": {
"defined": "LoanOriginationFeeInstruction"
},
"index": false
}
]
},
{
"name": "LiquidateTokenBankruptcyLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "liqee",
"type": "publicKey",
"index": false
},
{
"name": "liqor",
"type": "publicKey",
"index": false
},
{
"name": "liabTokenIndex",
"type": "u16",
"index": false
},
{
"name": "initialLiabNative",
"type": "i128",
"index": false
},
{
"name": "liabPrice",
"type": "i128",
"index": false
},
{
"name": "insuranceTokenIndex",
"type": "u16",
"index": false
},
{
"name": "insuranceTransfer",
"type": "i128",
"index": false
},
{
"name": "socializedLoss",
"type": "i128",
"index": false
}
]
}
],
"errors": [
@ -9385,6 +9511,35 @@ export const IDL: MangoV4 = {
]
}
},
{
"name": "LoanOriginationFeeInstruction",
"type": {
"kind": "enum",
"variants": [
{
"name": "Unknown"
},
{
"name": "LiqTokenBankruptcy"
},
{
"name": "LiqTokenWithToken"
},
{
"name": "Serum3LiqForceCancelOrders"
},
{
"name": "Serum3PlaceOrder"
},
{
"name": "Serum3SettleFunds"
},
{
"name": "TokenWithdraw"
}
]
}
},
{
"name": "HealthType",
"docs": [
@ -9911,6 +10066,16 @@ export const IDL: MangoV4 = {
"name": "price",
"type": "i128",
"index": false
},
{
"name": "collectedFees",
"type": "i128",
"index": false
},
{
"name": "loanFeeRate",
"type": "i128",
"index": false
}
]
},
@ -9991,6 +10156,11 @@ export const IDL: MangoV4 = {
"name": "liabPrice",
"type": "i128",
"index": false
},
{
"name": "bankruptcy",
"type": "bool",
"index": false
}
]
},
@ -10043,6 +10213,88 @@ export const IDL: MangoV4 = {
"index": false
}
]
},
{
"name": "WithdrawLoanOriginationFeeLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "mangoAccount",
"type": "publicKey",
"index": false
},
{
"name": "tokenIndex",
"type": "u16",
"index": false
},
{
"name": "loanOriginationFee",
"type": "i128",
"index": false
},
{
"name": "instruction",
"type": {
"defined": "LoanOriginationFeeInstruction"
},
"index": false
}
]
},
{
"name": "LiquidateTokenBankruptcyLog",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "liqee",
"type": "publicKey",
"index": false
},
{
"name": "liqor",
"type": "publicKey",
"index": false
},
{
"name": "liabTokenIndex",
"type": "u16",
"index": false
},
{
"name": "initialLiabNative",
"type": "i128",
"index": false
},
{
"name": "liabPrice",
"type": "i128",
"index": false
},
{
"name": "insuranceTokenIndex",
"type": "u16",
"index": false
},
{
"name": "insuranceTransfer",
"type": "i128",
"index": false
},
{
"name": "socializedLoss",
"type": "i128",
"index": false
}
]
}
],
"errors": [