Add a token-token platform liquidation fee (#849)

The liqor liquidation fee and platform liquidation fee for the asset and
liab token are both payed by the liqee.

The platform liquidation fee is added to the Bank's
collected_fees_native and tracked in collected_liquidation_fees.
This commit is contained in:
Christian Kamm 2024-01-19 16:34:55 +01:00 committed by GitHub
parent 511814ca97
commit 8383109f0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 735 additions and 43 deletions

View File

@ -627,6 +627,10 @@
{
"name": "zeroUtilRate",
"type": "f32"
},
{
"name": "platformLiquidationFee",
"type": "f32"
}
]
},
@ -1031,6 +1035,12 @@
"type": {
"option": "f32"
}
},
{
"name": "platformLiquidationFeeOpt",
"type": {
"option": "f32"
}
}
]
},
@ -7193,6 +7203,12 @@
},
{
"name": "collectedFeesNative",
"docs": [
"Fees collected over the lifetime of the bank",
"",
"See fees_withdrawn for how much of the fees was withdrawn.",
"See collected_liquidation_fees for the (included) subtotal for liquidation related fees."
],
"type": {
"defined": "I80F48"
}
@ -7235,6 +7251,15 @@
},
{
"name": "liquidationFee",
"docs": [
"Liquidation fee that goes to the liqor.",
"",
"Liquidation always involves two tokens, and the sum of the two configured fees is used.",
"",
"A fraction of the price, like 0.05 for a 5% fee during liquidation.",
"",
"See also platform_liquidation_fee."
],
"type": {
"defined": "I80F48"
}
@ -7458,12 +7483,32 @@
"defined": "I80F48"
}
},
{
"name": "platformLiquidationFee",
"docs": [
"Additional to liquidation_fee, but goes to the group owner instead of the liqor"
],
"type": {
"defined": "I80F48"
}
},
{
"name": "collectedLiquidationFees",
"docs": [
"Fees that were collected during liquidation (in native tokens)",
"",
"See also collected_fees_native and fees_withdrawn."
],
"type": {
"defined": "I80F48"
}
},
{
"name": "reserved",
"type": {
"array": [
"u8",
1952
1920
]
}
}
@ -11962,6 +12007,71 @@
}
]
},
{
"name": "TokenLiqWithTokenLogV2",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "liqee",
"type": "publicKey",
"index": false
},
{
"name": "liqor",
"type": "publicKey",
"index": false
},
{
"name": "assetTokenIndex",
"type": "u16",
"index": false
},
{
"name": "liabTokenIndex",
"type": "u16",
"index": false
},
{
"name": "assetTransferFromLiqee",
"type": "i128",
"index": false
},
{
"name": "assetTransferToLiqor",
"type": "i128",
"index": false
},
{
"name": "assetLiquidationFee",
"type": "i128",
"index": false
},
{
"name": "liabTransfer",
"type": "i128",
"index": false
},
{
"name": "assetPrice",
"type": "i128",
"index": false
},
{
"name": "liabPrice",
"type": "i128",
"index": false
},
{
"name": "bankruptcy",
"type": "bool",
"index": false
}
]
},
{
"name": "Serum3OpenOrdersBalanceLog",
"fields": [
@ -12838,6 +12948,71 @@
}
]
},
{
"name": "TokenForceCloseBorrowsWithTokenLogV2",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "liqor",
"type": "publicKey",
"index": false
},
{
"name": "liqee",
"type": "publicKey",
"index": false
},
{
"name": "assetTokenIndex",
"type": "u16",
"index": false
},
{
"name": "liabTokenIndex",
"type": "u16",
"index": false
},
{
"name": "assetTransferFromLiqee",
"type": "i128",
"index": false
},
{
"name": "assetTransferToLiqor",
"type": "i128",
"index": false
},
{
"name": "assetLiquidationFee",
"type": "i128",
"index": false
},
{
"name": "liabTransfer",
"type": "i128",
"index": false
},
{
"name": "assetPrice",
"type": "i128",
"index": false
},
{
"name": "liabPrice",
"type": "i128",
"index": false
},
{
"name": "feeFactor",
"type": "i128",
"index": false
}
]
},
{
"name": "TokenConditionalSwapCreateLog",
"fields": [

View File

@ -52,6 +52,7 @@ pub fn token_edit(
set_fallback_oracle: bool,
deposit_limit_opt: Option<u64>,
zero_util_rate: Option<f32>,
platform_liquidation_fee: Option<f32>,
) -> Result<()> {
let group = ctx.accounts.group.load()?;
@ -473,6 +474,16 @@ pub fn token_edit(
bank.zero_util_rate = I80F48::from_num(zero_util_rate);
require_group_admin = true;
}
if let Some(platform_liquidation_fee) = platform_liquidation_fee {
msg!(
"Platform liquidation fee old {:?}, new {:?}",
bank.platform_liquidation_fee,
platform_liquidation_fee
);
bank.platform_liquidation_fee = I80F48::from_num(platform_liquidation_fee);
require_group_admin = true;
}
}
// account constraint #1

View File

@ -1,7 +1,7 @@
use crate::accounts_ix::*;
use crate::error::*;
use crate::health::*;
use crate::logs::{emit_stack, TokenBalanceLog, TokenForceCloseBorrowsWithTokenLog};
use crate::logs::{emit_stack, TokenBalanceLog, TokenForceCloseBorrowsWithTokenLogV2};
use crate::state::*;
use anchor_lang::prelude::*;
use fixed::types::I80F48;
@ -62,12 +62,18 @@ pub fn token_force_close_borrows_with_token(
MangoError::TokenInReduceOnlyMode
);
let fee_factor_liqor =
(I80F48::ONE + liab_bank.liquidation_fee) * (I80F48::ONE + asset_bank.liquidation_fee);
let fee_factor_total =
(I80F48::ONE + liab_bank.liquidation_fee + liab_bank.platform_liquidation_fee)
* (I80F48::ONE + asset_bank.liquidation_fee + asset_bank.platform_liquidation_fee);
// account constraint #3
// only allow combination of asset and liab token,
// where liqee's health would be guaranteed to not decrease
require_gte!(
liab_bank.init_liab_weight,
asset_bank.init_liab_weight * (I80F48::ONE + liab_bank.liquidation_fee),
asset_bank.init_liab_weight * fee_factor_total,
MangoError::SomeError
);
@ -95,10 +101,13 @@ pub fn token_force_close_borrows_with_token(
.max(I80F48::ZERO);
// The amount of asset native tokens we will give up for them
let fee_factor =
(I80F48::ONE + liab_bank.liquidation_fee) * (I80F48::ONE + asset_bank.liquidation_fee);
let liab_oracle_price_adjusted = liab_oracle_price * fee_factor;
let asset_transfer = liab_transfer * liab_oracle_price_adjusted / asset_oracle_price;
let asset_transfer_base = liab_transfer * liab_oracle_price / asset_oracle_price;
let asset_transfer_to_liqor = asset_transfer_base * fee_factor_liqor;
let asset_transfer_from_liqee = asset_transfer_base * fee_factor_total;
let asset_liquidation_fee = asset_transfer_from_liqee - asset_transfer_to_liqor;
asset_bank.collected_fees_native += asset_liquidation_fee;
asset_bank.collected_liquidation_fees += asset_liquidation_fee;
// Apply the balance changes to the liqor and liqee accounts
let liqee_liab_active =
@ -113,13 +122,13 @@ pub fn token_force_close_borrows_with_token(
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, now_ts)?;
asset_bank.deposit(liqor_asset_position, asset_transfer_to_liqor, now_ts)?;
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,
asset_transfer_from_liqee,
now_ts,
)?;
let liqee_asset_indexed_position = liqee_asset_position.indexed_position;
@ -128,7 +137,7 @@ pub fn token_force_close_borrows_with_token(
msg!(
"Force closed {} liab for {} asset",
liab_transfer,
asset_transfer
asset_transfer_from_liqee,
);
// liqee asset
@ -168,17 +177,19 @@ pub fn token_force_close_borrows_with_token(
borrow_index: liab_bank.borrow_index.to_bits(),
});
emit_stack(TokenForceCloseBorrowsWithTokenLog {
emit_stack(TokenForceCloseBorrowsWithTokenLogV2 {
mango_group: liqee.fixed.group,
liqee: liqee_key,
liqor: liqor_key,
asset_token_index: asset_token_index,
liab_token_index: liab_token_index,
asset_transfer: asset_transfer.to_bits(),
asset_transfer_from_liqee: asset_transfer_from_liqee.to_bits(),
asset_transfer_to_liqor: asset_transfer_to_liqor.to_bits(),
asset_liquidation_fee: asset_liquidation_fee.to_bits(),
liab_transfer: liab_transfer.to_bits(),
asset_price: asset_oracle_price.to_bits(),
liab_price: liab_oracle_price.to_bits(),
fee_factor: fee_factor.to_bits(),
fee_factor: fee_factor_total.to_bits(),
});
// liqor should never have a borrow

View File

@ -5,7 +5,7 @@ use crate::accounts_ix::*;
use crate::error::*;
use crate::health::*;
use crate::logs::{
emit_stack, LoanOriginationFeeInstruction, TokenBalanceLog, TokenLiqWithTokenLog,
emit_stack, LoanOriginationFeeInstruction, TokenBalanceLog, TokenLiqWithTokenLogV2,
WithdrawLoanLog,
};
use crate::state::*;
@ -132,9 +132,12 @@ pub(crate) fn liquidation_action(
// assets = liabs * liab_oracle_price / asset_oracle_price * fee_factor
// assets = liabs * liab_oracle_price_adjusted / asset_oracle_price
// = liabs * lopa / aop
let fee_factor =
let fee_factor_liqor =
(I80F48::ONE + liab_bank.liquidation_fee) * (I80F48::ONE + asset_bank.liquidation_fee);
let liab_oracle_price_adjusted = liab_oracle_price * fee_factor;
let fee_factor_total =
(I80F48::ONE + liab_bank.liquidation_fee + liab_bank.platform_liquidation_fee)
* (I80F48::ONE + asset_bank.liquidation_fee + asset_bank.platform_liquidation_fee);
let liab_oracle_price_adjusted = liab_oracle_price * fee_factor_total;
let init_asset_weight = asset_bank.init_asset_weight;
let init_liab_weight = liab_bank.init_liab_weight;
@ -218,7 +221,13 @@ pub(crate) fn liquidation_action(
.max(I80F48::ZERO);
// The amount of asset native tokens we will give up for them
let asset_transfer = liab_transfer * liab_oracle_price_adjusted / asset_oracle_price;
let asset_transfer_base = liab_transfer * liab_oracle_price / asset_oracle_price;
let asset_transfer_to_liqor = asset_transfer_base * fee_factor_liqor;
let asset_transfer_from_liqee = asset_transfer_base * fee_factor_total;
let asset_liquidation_fee = asset_transfer_from_liqee - asset_transfer_to_liqor;
asset_bank.collected_fees_native += asset_liquidation_fee;
asset_bank.collected_liquidation_fees += asset_liquidation_fee;
// 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
@ -239,13 +248,14 @@ pub(crate) fn liquidation_action(
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, now_ts)?;
let liqor_asset_active =
asset_bank.deposit(liqor_asset_position, asset_transfer_to_liqor, now_ts)?;
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,
asset_transfer_from_liqee,
now_ts,
)?;
let liqee_asset_indexed_position = liqee_asset_position.indexed_position;
@ -260,7 +270,7 @@ pub(crate) fn liquidation_action(
msg!(
"liquidated {} liab for {} asset",
liab_transfer,
asset_transfer
asset_transfer_from_liqee,
);
// liqee asset
@ -335,13 +345,15 @@ pub(crate) fn liquidation_action(
.fixed
.maybe_recover_from_being_liquidated(liqee_liq_end_health);
emit_stack(TokenLiqWithTokenLog {
emit_stack(TokenLiqWithTokenLogV2 {
mango_group: liqee.fixed.group,
liqee: liqee_key,
liqor: liqor_key,
asset_token_index,
liab_token_index,
asset_transfer: asset_transfer.to_bits(),
asset_transfer_from_liqee: asset_transfer_from_liqee.to_bits(),
asset_transfer_to_liqor: asset_transfer_to_liqor.to_bits(),
asset_liquidation_fee: asset_liquidation_fee.to_bits(),
liab_transfer: liab_transfer.to_bits(),
asset_price: asset_oracle_price.to_bits(),
liab_price: liab_oracle_price.to_bits(),

View File

@ -43,6 +43,7 @@ pub fn token_register(
group_insurance_fund: bool,
deposit_limit: u64,
zero_util_rate: f32,
platform_liquidation_fee: f32,
) -> Result<()> {
// Require token 0 to be in the insurance token
if token_index == INSURANCE_TOKEN_INDEX {
@ -124,7 +125,9 @@ pub fn token_register(
fallback_oracle: ctx.accounts.fallback_oracle.key(),
deposit_limit,
zero_util_rate: I80F48::from_num(zero_util_rate),
reserved: [0; 1952],
platform_liquidation_fee: I80F48::from_num(platform_liquidation_fee),
collected_liquidation_fees: I80F48::ZERO,
reserved: [0; 1920],
};
let oracle_ref = &AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?;

View File

@ -71,7 +71,8 @@ pub fn token_register_trustless(
init_asset_weight: I80F48::from_num(0),
maint_liab_weight: I80F48::from_num(1.4), // 2.5x
init_liab_weight: I80F48::from_num(1.8), // 1.25x
liquidation_fee: I80F48::from_num(0.2),
liquidation_fee: I80F48::from_num(0.05),
platform_liquidation_fee: I80F48::from_num(0.05),
dust: I80F48::ZERO,
flash_loan_token_account_initial: u64::MAX,
flash_loan_approved_amount: 0,
@ -105,7 +106,8 @@ pub fn token_register_trustless(
fallback_oracle: ctx.accounts.fallback_oracle.key(),
deposit_limit: 0,
zero_util_rate: I80F48::ZERO,
reserved: [0; 1952],
collected_liquidation_fees: I80F48::ZERO,
reserved: [0; 1920],
};
let oracle_ref = &AccountInfoRef::borrow(ctx.accounts.oracle.as_ref())?;
if let Ok(oracle_price) = bank.oracle_price(&OracleAccountInfos::from_reader(oracle_ref), None)

View File

@ -156,6 +156,7 @@ pub mod mango_v4 {
group_insurance_fund: bool,
deposit_limit: u64,
zero_util_rate: f32,
platform_liquidation_fee: f32,
) -> Result<()> {
#[cfg(feature = "enable-gpl")]
instructions::token_register(
@ -188,6 +189,7 @@ pub mod mango_v4 {
group_insurance_fund,
deposit_limit,
zero_util_rate,
platform_liquidation_fee,
)?;
Ok(())
}
@ -242,6 +244,7 @@ pub mod mango_v4 {
set_fallback_oracle: bool,
deposit_limit_opt: Option<u64>,
zero_util_rate_opt: Option<f32>,
platform_liquidation_fee_opt: Option<f32>,
) -> Result<()> {
#[cfg(feature = "enable-gpl")]
instructions::token_edit(
@ -283,6 +286,7 @@ pub mod mango_v4 {
set_fallback_oracle,
deposit_limit_opt,
zero_util_rate_opt,
platform_liquidation_fee_opt,
)?;
Ok(())
}

View File

@ -344,6 +344,22 @@ pub struct TokenLiqWithTokenLog {
pub bankruptcy: bool,
}
#[event]
pub struct TokenLiqWithTokenLogV2 {
pub mango_group: Pubkey,
pub liqee: Pubkey,
pub liqor: Pubkey,
pub asset_token_index: u16,
pub liab_token_index: u16,
pub asset_transfer_from_liqee: i128, // I80F48
pub asset_transfer_to_liqor: i128, // I80F48
pub asset_liquidation_fee: i128, // I80F48
pub liab_transfer: i128, // I80F48
pub asset_price: i128, // I80F48
pub liab_price: i128, // I80F48
pub bankruptcy: bool,
}
#[event]
pub struct Serum3OpenOrdersBalanceLog {
pub mango_group: Pubkey,
@ -594,6 +610,23 @@ pub struct TokenForceCloseBorrowsWithTokenLog {
pub fee_factor: i128,
}
#[event]
pub struct TokenForceCloseBorrowsWithTokenLogV2 {
pub mango_group: Pubkey,
pub liqor: Pubkey,
pub liqee: Pubkey,
pub asset_token_index: u16,
pub liab_token_index: u16,
pub asset_transfer_from_liqee: i128, // I80F48
pub asset_transfer_to_liqor: i128, // I80F48
pub asset_liquidation_fee: i128, // I80F48
pub liab_transfer: i128, // I80F48
pub asset_price: i128, // I80F48
pub liab_price: i128, // I80F48
/// including liqor and platform liquidation fees
pub fee_factor: i128, // I80F48
}
#[event]
pub struct TokenConditionalSwapCreateLog {
pub mango_group: Pubkey,

View File

@ -79,8 +79,12 @@ pub struct Bank {
/// which is >=1.
pub max_rate: I80F48,
// TODO: add ix/logic to regular send this to DAO
/// Fees collected over the lifetime of the bank
///
/// See fees_withdrawn for how much of the fees was withdrawn.
/// See collected_liquidation_fees for the (included) subtotal for liquidation related fees.
pub collected_fees_native: I80F48,
pub loan_origination_fee_rate: I80F48,
pub loan_fee_rate: I80F48,
@ -92,9 +96,13 @@ pub struct Bank {
pub maint_liab_weight: I80F48,
pub init_liab_weight: I80F48,
// 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.
/// Liquidation fee that goes to the liqor.
///
/// Liquidation always involves two tokens, and the sum of the two configured fees is used.
///
/// A fraction of the price, like 0.05 for a 5% fee during liquidation.
///
/// See also platform_liquidation_fee.
pub liquidation_fee: I80F48,
// Collection of all fractions-of-native-tokens that got rounded away
@ -201,8 +209,16 @@ pub struct Bank {
/// See util0, rate0, util1, rate1, max_rate
pub zero_util_rate: I80F48,
/// Additional to liquidation_fee, but goes to the group owner instead of the liqor
pub platform_liquidation_fee: I80F48,
/// Fees that were collected during liquidation (in native tokens)
///
/// See also collected_fees_native and fees_withdrawn.
pub collected_liquidation_fees: I80F48,
#[derivative(Debug = "ignore")]
pub reserved: [u8; 1952],
pub reserved: [u8; 1920],
}
const_assert_eq!(
size_of::<Bank>(),
@ -239,8 +255,8 @@ const_assert_eq!(
+ 16 * 3
+ 32
+ 8
+ 16
+ 1952
+ 16 * 3
+ 1920
);
const_assert_eq!(size_of::<Bank>(), 3064);
const_assert_eq!(size_of::<Bank>() % 8, 0);
@ -283,6 +299,7 @@ impl Bank {
indexed_deposits: I80F48::ZERO,
indexed_borrows: I80F48::ZERO,
collected_fees_native: I80F48::ZERO,
collected_liquidation_fees: I80F48::ZERO,
fees_withdrawn: 0,
dust: I80F48::ZERO,
flash_loan_approved_amount: 0,
@ -345,7 +362,8 @@ impl Bank {
fallback_oracle: existing_bank.oracle,
deposit_limit: existing_bank.deposit_limit,
zero_util_rate: existing_bank.zero_util_rate,
reserved: [0; 1952],
platform_liquidation_fee: existing_bank.platform_liquidation_fee,
reserved: [0; 1920],
}
}
@ -377,6 +395,7 @@ impl Bank {
require_gte!(self.maint_weight_shift_asset_target, 0.0);
require_gte!(self.maint_weight_shift_liab_target, 0.0);
require_gte!(self.zero_util_rate, I80F48::ZERO);
require_gte!(self.platform_liquidation_fee, 0.0);
Ok(())
}

View File

@ -192,6 +192,25 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
let collateral_token1 = &tokens[2];
let collateral_token2 = &tokens[3];
for token in &tokens[0..4] {
send_tx(
solana,
TokenEdit {
group,
admin,
mint: token.mint.pubkey,
fallback_oracle: Pubkey::default(),
options: mango_v4::instruction::TokenEdit {
liquidation_fee_opt: Some(0.01),
platform_liquidation_fee_opt: Some(0.01),
..token_edit_instruction_default()
},
},
)
.await
.unwrap();
}
// deposit some funds, to the vaults aren't empty
let vault_account = send_tx(
solana,
@ -325,13 +344,42 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
.await
.unwrap();
// the we only have 20 collateral2, and can trade them for 20 / (1.02 * 1.02) = 19.22 borrow2
// (liq fee is 2% for both sides)
// the liqee's 20 collateral2 are traded for 20 / (1.02 * 1.02) = 19.22 borrow2
// (liq fee is 1% liqor + 1% platform for both sides)
assert_eq!(
account_position(solana, account, borrow_token2.bank).await,
-50 + 19
);
assert_eq!(
account_position(solana, vault_account, borrow_token2.bank).await,
100000 - 19
);
// All liqee collateral2 is gone
assert!(account_position_closed(solana, account, collateral_token2.bank).await,);
// The liqee pays for the 20 collateral at a price of 1.02*1.02. The liqor gets 1.01*1.01,
// so the platform fee is
let platform_fee = 20.0 * (1.0 - 1.01 * 1.01 / (1.02 * 1.02));
assert!(assert_equal_f64_f64(
account_position_f64(solana, vault_account, collateral_token2.bank).await,
100000.0 + 20.0 - platform_fee,
0.001,
));
// Verify platform liq fee tracking
let colbank = solana.get_account::<Bank>(collateral_token2.bank).await;
assert!(assert_equal_fixed_f64(
colbank.collected_fees_native,
platform_fee,
0.001
));
assert!(assert_equal_fixed_f64(
colbank.collected_liquidation_fees,
platform_fee,
0.001
));
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());

View File

@ -1012,6 +1012,7 @@ pub struct TokenRegisterInstruction {
pub maint_liab_weight: f32,
pub init_liab_weight: f32,
pub liquidation_fee: f32,
pub platform_liquidation_fee: f32,
pub min_vault_to_deposits_ratio: f64,
pub net_borrow_limit_per_window_quote: i64,
@ -1075,6 +1076,7 @@ impl ClientInstruction for TokenRegisterInstruction {
group_insurance_fund: true,
deposit_limit: 0,
zero_util_rate: 0.0,
platform_liquidation_fee: self.platform_liquidation_fee,
};
let bank = Pubkey::find_program_address(
@ -1321,6 +1323,7 @@ pub fn token_edit_instruction_default() -> mango_v4::instruction::TokenEdit {
set_fallback_oracle: false,
deposit_limit_opt: None,
zero_util_rate_opt: None,
platform_liquidation_fee_opt: None,
}
}

View File

@ -112,6 +112,7 @@ impl<'a> GroupWithTokensConfig {
min_vault_to_deposits_ratio: 0.2,
net_borrow_limit_per_window_quote: 1_000_000_000_000,
net_borrow_limit_window_size_ts: 24 * 60 * 60,
platform_liquidation_fee: 0.0,
},
)
.await

View File

@ -81,6 +81,8 @@ export class Bank implements BankForHealth {
public maintWeightShiftAssetTarget: I80F48;
public maintWeightShiftLiabTarget: I80F48;
public zeroUtilRate: I80F48;
public platformLiquidationFee: I80F48;
public collectedLiquidationFees: I80F48;
static from(
publicKey: PublicKey,
@ -142,6 +144,8 @@ export class Bank implements BankForHealth {
maintWeightShiftLiabTarget: I80F48Dto;
depositLimit: BN;
zeroUtilRate: I80F48Dto;
platformLiquidationFee: I80F48Dto;
collectedLiquidationFees: I80F48Dto;
},
): Bank {
return new Bank(
@ -203,6 +207,8 @@ export class Bank implements BankForHealth {
obj.maintWeightShiftLiabTarget,
obj.depositLimit,
obj.zeroUtilRate,
obj.platformLiquidationFee,
obj.collectedLiquidationFees,
);
}
@ -265,6 +271,8 @@ export class Bank implements BankForHealth {
maintWeightShiftLiabTarget: I80F48Dto,
public depositLimit: BN,
zeroUtilRate: I80F48Dto,
platformLiquidationFee: I80F48Dto,
collectedLiquidationFees: I80F48Dto,
) {
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
this.oracleConfig = {
@ -295,6 +303,8 @@ export class Bank implements BankForHealth {
this.maintWeightShiftAssetTarget = I80F48.from(maintWeightShiftAssetTarget);
this.maintWeightShiftLiabTarget = I80F48.from(maintWeightShiftLiabTarget);
this.zeroUtilRate = I80F48.from(zeroUtilRate);
this.platformLiquidationFee = I80F48.from(platformLiquidationFee);
this.collectedLiquidationFees = I80F48.from(collectedLiquidationFees);
this._price = undefined;
this._uiPrice = undefined;
this._oracleLastUpdatedSlot = undefined;

View File

@ -459,6 +459,7 @@ export class MangoClient {
params.groupInsuranceFund,
params.depositLimit,
params.zeroUtilRate,
params.platformLiquidationFee,
)
.accounts({
group: group.publicKey,
@ -544,6 +545,7 @@ export class MangoClient {
params.setFallbackOracle ?? false,
params.depositLimit,
params.zeroUtilRate,
params.platformLiquidationFee,
)
.accounts({
group: group.publicKey,

View File

@ -29,6 +29,7 @@ export interface TokenRegisterParams {
interestTargetUtilization: number;
depositLimit: BN;
zeroUtilRate: number;
platformLiquidationFee: number;
}
export const DefaultTokenRegisterParams: TokenRegisterParams = {
@ -68,6 +69,7 @@ export const DefaultTokenRegisterParams: TokenRegisterParams = {
interestTargetUtilization: 0.5,
depositLimit: new BN(0),
zeroUtilRate: 0.0,
platformLiquidationFee: 0.0,
};
export interface TokenEditParams {
@ -108,6 +110,7 @@ export interface TokenEditParams {
setFallbackOracle: boolean | null;
depositLimit: BN | null;
zeroUtilRate: number | null;
platformLiquidationFee: number | null;
}
export const NullTokenEditParams: TokenEditParams = {
@ -148,6 +151,7 @@ export const NullTokenEditParams: TokenEditParams = {
setFallbackOracle: null,
depositLimit: null,
zeroUtilRate: null,
platformLiquidationFee: null,
};
export interface PerpEditParams {

View File

@ -627,6 +627,10 @@ export type MangoV4 = {
{
"name": "zeroUtilRate",
"type": "f32"
},
{
"name": "platformLiquidationFee",
"type": "f32"
}
]
},
@ -1031,6 +1035,12 @@ export type MangoV4 = {
"type": {
"option": "f32"
}
},
{
"name": "platformLiquidationFeeOpt",
"type": {
"option": "f32"
}
}
]
},
@ -7193,6 +7203,12 @@ export type MangoV4 = {
},
{
"name": "collectedFeesNative",
"docs": [
"Fees collected over the lifetime of the bank",
"",
"See fees_withdrawn for how much of the fees was withdrawn.",
"See collected_liquidation_fees for the (included) subtotal for liquidation related fees."
],
"type": {
"defined": "I80F48"
}
@ -7235,6 +7251,15 @@ export type MangoV4 = {
},
{
"name": "liquidationFee",
"docs": [
"Liquidation fee that goes to the liqor.",
"",
"Liquidation always involves two tokens, and the sum of the two configured fees is used.",
"",
"A fraction of the price, like 0.05 for a 5% fee during liquidation.",
"",
"See also platform_liquidation_fee."
],
"type": {
"defined": "I80F48"
}
@ -7458,12 +7483,32 @@ export type MangoV4 = {
"defined": "I80F48"
}
},
{
"name": "platformLiquidationFee",
"docs": [
"Additional to liquidation_fee, but goes to the group owner instead of the liqor"
],
"type": {
"defined": "I80F48"
}
},
{
"name": "collectedLiquidationFees",
"docs": [
"Fees that were collected during liquidation (in native tokens)",
"",
"See also collected_fees_native and fees_withdrawn."
],
"type": {
"defined": "I80F48"
}
},
{
"name": "reserved",
"type": {
"array": [
"u8",
1952
1920
]
}
}
@ -11962,6 +12007,71 @@ export type MangoV4 = {
}
]
},
{
"name": "TokenLiqWithTokenLogV2",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "liqee",
"type": "publicKey",
"index": false
},
{
"name": "liqor",
"type": "publicKey",
"index": false
},
{
"name": "assetTokenIndex",
"type": "u16",
"index": false
},
{
"name": "liabTokenIndex",
"type": "u16",
"index": false
},
{
"name": "assetTransferFromLiqee",
"type": "i128",
"index": false
},
{
"name": "assetTransferToLiqor",
"type": "i128",
"index": false
},
{
"name": "assetLiquidationFee",
"type": "i128",
"index": false
},
{
"name": "liabTransfer",
"type": "i128",
"index": false
},
{
"name": "assetPrice",
"type": "i128",
"index": false
},
{
"name": "liabPrice",
"type": "i128",
"index": false
},
{
"name": "bankruptcy",
"type": "bool",
"index": false
}
]
},
{
"name": "Serum3OpenOrdersBalanceLog",
"fields": [
@ -12838,6 +12948,71 @@ export type MangoV4 = {
}
]
},
{
"name": "TokenForceCloseBorrowsWithTokenLogV2",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "liqor",
"type": "publicKey",
"index": false
},
{
"name": "liqee",
"type": "publicKey",
"index": false
},
{
"name": "assetTokenIndex",
"type": "u16",
"index": false
},
{
"name": "liabTokenIndex",
"type": "u16",
"index": false
},
{
"name": "assetTransferFromLiqee",
"type": "i128",
"index": false
},
{
"name": "assetTransferToLiqor",
"type": "i128",
"index": false
},
{
"name": "assetLiquidationFee",
"type": "i128",
"index": false
},
{
"name": "liabTransfer",
"type": "i128",
"index": false
},
{
"name": "assetPrice",
"type": "i128",
"index": false
},
{
"name": "liabPrice",
"type": "i128",
"index": false
},
{
"name": "feeFactor",
"type": "i128",
"index": false
}
]
},
{
"name": "TokenConditionalSwapCreateLog",
"fields": [
@ -14387,6 +14562,10 @@ export const IDL: MangoV4 = {
{
"name": "zeroUtilRate",
"type": "f32"
},
{
"name": "platformLiquidationFee",
"type": "f32"
}
]
},
@ -14791,6 +14970,12 @@ export const IDL: MangoV4 = {
"type": {
"option": "f32"
}
},
{
"name": "platformLiquidationFeeOpt",
"type": {
"option": "f32"
}
}
]
},
@ -20953,6 +21138,12 @@ export const IDL: MangoV4 = {
},
{
"name": "collectedFeesNative",
"docs": [
"Fees collected over the lifetime of the bank",
"",
"See fees_withdrawn for how much of the fees was withdrawn.",
"See collected_liquidation_fees for the (included) subtotal for liquidation related fees."
],
"type": {
"defined": "I80F48"
}
@ -20995,6 +21186,15 @@ export const IDL: MangoV4 = {
},
{
"name": "liquidationFee",
"docs": [
"Liquidation fee that goes to the liqor.",
"",
"Liquidation always involves two tokens, and the sum of the two configured fees is used.",
"",
"A fraction of the price, like 0.05 for a 5% fee during liquidation.",
"",
"See also platform_liquidation_fee."
],
"type": {
"defined": "I80F48"
}
@ -21218,12 +21418,32 @@ export const IDL: MangoV4 = {
"defined": "I80F48"
}
},
{
"name": "platformLiquidationFee",
"docs": [
"Additional to liquidation_fee, but goes to the group owner instead of the liqor"
],
"type": {
"defined": "I80F48"
}
},
{
"name": "collectedLiquidationFees",
"docs": [
"Fees that were collected during liquidation (in native tokens)",
"",
"See also collected_fees_native and fees_withdrawn."
],
"type": {
"defined": "I80F48"
}
},
{
"name": "reserved",
"type": {
"array": [
"u8",
1952
1920
]
}
}
@ -25722,6 +25942,71 @@ export const IDL: MangoV4 = {
}
]
},
{
"name": "TokenLiqWithTokenLogV2",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "liqee",
"type": "publicKey",
"index": false
},
{
"name": "liqor",
"type": "publicKey",
"index": false
},
{
"name": "assetTokenIndex",
"type": "u16",
"index": false
},
{
"name": "liabTokenIndex",
"type": "u16",
"index": false
},
{
"name": "assetTransferFromLiqee",
"type": "i128",
"index": false
},
{
"name": "assetTransferToLiqor",
"type": "i128",
"index": false
},
{
"name": "assetLiquidationFee",
"type": "i128",
"index": false
},
{
"name": "liabTransfer",
"type": "i128",
"index": false
},
{
"name": "assetPrice",
"type": "i128",
"index": false
},
{
"name": "liabPrice",
"type": "i128",
"index": false
},
{
"name": "bankruptcy",
"type": "bool",
"index": false
}
]
},
{
"name": "Serum3OpenOrdersBalanceLog",
"fields": [
@ -26598,6 +26883,71 @@ export const IDL: MangoV4 = {
}
]
},
{
"name": "TokenForceCloseBorrowsWithTokenLogV2",
"fields": [
{
"name": "mangoGroup",
"type": "publicKey",
"index": false
},
{
"name": "liqor",
"type": "publicKey",
"index": false
},
{
"name": "liqee",
"type": "publicKey",
"index": false
},
{
"name": "assetTokenIndex",
"type": "u16",
"index": false
},
{
"name": "liabTokenIndex",
"type": "u16",
"index": false
},
{
"name": "assetTransferFromLiqee",
"type": "i128",
"index": false
},
{
"name": "assetTransferToLiqor",
"type": "i128",
"index": false
},
{
"name": "assetLiquidationFee",
"type": "i128",
"index": false
},
{
"name": "liabTransfer",
"type": "i128",
"index": false
},
{
"name": "assetPrice",
"type": "i128",
"index": false
},
{
"name": "liabPrice",
"type": "i128",
"index": false
},
{
"name": "feeFactor",
"type": "i128",
"index": false
}
]
},
{
"name": "TokenConditionalSwapCreateLog",
"fields": [

View File

@ -134,7 +134,7 @@ export async function getPriceImpactForLiqor(
// Max liab of a particular token that would be liquidated to bring health above 0
mangoAccountsWithHealth.reduce((sum, a) => {
// How much would health increase for every unit liab moved to liqor
// liabprice * (liabweight - (1+fee)*assetweight)
// liabprice * (liabweight - (1+liabfees)*(1+assetfees)*assetweight)
// Choose the most valuable asset the user has
const assetBank = Array.from(group.banksMapByTokenIndex.values())
.flat()
@ -152,12 +152,16 @@ export async function getPriceImpactForLiqor(
? prev
: curr,
);
const tokenLiabHealthContrib = bank.price.mul(
bank.initLiabWeight.sub(
const feeFactor = ONE_I80F48()
.add(bank.liquidationFee)
.add(bank.platformLiquidationFee)
.mul(
ONE_I80F48()
.add(bank.liquidationFee)
.mul(assetBank.initAssetWeight),
),
.add(assetBank.liquidationFee)
.add(assetBank.platformLiquidationFee),
);
const tokenLiabHealthContrib = bank.price.mul(
bank.initLiabWeight.sub(feeFactor.mul(assetBank.initAssetWeight)),
);
// Abs liab/borrow
const maxTokenLiab = a.account