liquidation: Add flag to disable asset liquidation (#867)

This can be used to remove the oracle dependency for tokens with zero maint asset
weight that are not borrowable.
This commit is contained in:
Christian Kamm 2024-02-07 12:52:01 +01:00 committed by GitHub
parent 712a2e3bd6
commit d9a9c7d664
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 194 additions and 10 deletions

View File

@ -370,11 +370,11 @@ impl<'a> LiquidateHelper<'a> {
.filter_map(|(ti, effective)| {
// check constraints for liquidatable assets, see also has_possible_spot_liquidations()
let tokens = ti.balance_spot.min(effective.spot_and_perp);
let is_valid_asset = tokens >= 1;
let is_valid_asset = tokens >= 1 && ti.allow_asset_liquidation;
let quote_value = tokens * ti.prices.oracle;
// prefer to liquidate tokens with asset weight that have >$1 liquidatable
let is_preferred =
ti.init_asset_weight > 0 && quote_value > I80F48::from(1_000_000);
ti.maint_asset_weight > 0 && quote_value > I80F48::from(1_000_000);
is_valid_asset.then_some((ti.token_index, is_preferred, quote_value))
})
.collect_vec();

View File

@ -631,6 +631,10 @@
{
"name": "platformLiquidationFee",
"type": "f32"
},
{
"name": "disableAssetLiquidation",
"type": "bool"
}
]
},
@ -1041,6 +1045,12 @@
"type": {
"option": "f32"
}
},
{
"name": "disableAssetLiquidationOpt",
"type": {
"option": "bool"
}
}
]
},
@ -7373,12 +7383,20 @@
"name": "forceClose",
"type": "u8"
},
{
"name": "disableAssetLiquidation",
"docs": [
"If set to 1, deposits cannot be liquidated when an account is liquidatable.",
"That means bankrupt accounts may still have assets of this type deposited."
],
"type": "u8"
},
{
"name": "padding",
"type": {
"array": [
"u8",
6
5
]
}
},
@ -14028,6 +14046,11 @@
"code": 6068,
"name": "MissingFeedForCLMMOracle",
"msg": "Pyth USDC/USD or SOL/USD feed not found (required by CLMM oracle)"
},
{
"code": 6069,
"name": "TokenAssetLiquidationDisabled",
"msg": "the asset does not allow liquidation"
}
]
}

View File

@ -143,6 +143,8 @@ pub enum MangoError {
InvalidFeedForCLMMOracle,
#[msg("Pyth USDC/USD or SOL/USD feed not found (required by CLMM oracle)")]
MissingFeedForCLMMOracle,
#[msg("the asset does not allow liquidation")]
TokenAssetLiquidationDisabled,
}
impl MangoError {

View File

@ -175,6 +175,8 @@ pub struct TokenInfo {
/// Includes TokenPosition and free Serum3OpenOrders balances.
/// Does not include perp upnl or Serum3 reserved amounts.
pub balance_spot: I80F48,
pub allow_asset_liquidation: bool,
}
/// Temporary value used during health computations
@ -907,6 +909,7 @@ impl HealthCache {
}
/// Liquidatable spot assets mean: actual token deposits and also a positive effective token balance
/// and is available for asset liquidation
pub fn has_liq_spot_assets(&self) -> bool {
let health_token_balances = self.effective_token_balances(HealthType::LiquidationEnd);
self.token_infos
@ -914,11 +917,11 @@ impl HealthCache {
.zip(health_token_balances.iter())
.any(|(ti, b)| {
// need 1 native token to use token_liq_with_token
ti.balance_spot >= 1 && b.spot_and_perp >= 1
ti.balance_spot >= 1 && b.spot_and_perp >= 1 && ti.allow_asset_liquidation
})
}
/// Liquidatable spot borrows mean: actual toen borrows plus a negative effective token balance
/// Liquidatable spot borrows mean: actual token borrows plus a negative effective token balance
pub fn has_liq_spot_borrows(&self) -> bool {
let health_token_balances = self.effective_token_balances(HealthType::LiquidationEnd);
self.token_infos
@ -932,7 +935,9 @@ impl HealthCache {
let health_token_balances = self.effective_token_balances(HealthType::LiquidationEnd);
let all_iter = || self.token_infos.iter().zip(health_token_balances.iter());
all_iter().any(|(ti, b)| ti.balance_spot < 0 && b.spot_and_perp < 0)
&& all_iter().any(|(ti, b)| ti.balance_spot >= 1 && b.spot_and_perp >= 1)
&& all_iter().any(|(ti, b)| {
ti.balance_spot >= 1 && b.spot_and_perp >= 1 && ti.allow_asset_liquidation
})
}
pub fn has_serum3_open_orders_funds(&self) -> bool {
@ -1286,6 +1291,7 @@ fn new_health_cache_impl(
init_scaled_liab_weight: bank.scaled_init_liab_weight(liab_price),
prices,
balance_spot: native,
allow_asset_liquidation: bank.allows_asset_liquidation(),
});
}

View File

@ -682,6 +682,7 @@ mod tests {
init_scaled_liab_weight: I80F48::from_num(1.0 + x),
prices: Prices::new_single_price(I80F48::from_num(price)),
balance_spot: I80F48::ZERO,
allow_asset_liquidation: true,
}
}

View File

@ -53,6 +53,7 @@ pub fn token_edit(
deposit_limit_opt: Option<u64>,
zero_util_rate: Option<f32>,
platform_liquidation_fee: Option<f32>,
disable_asset_liquidation_opt: Option<bool>,
) -> Result<()> {
let group = ctx.accounts.group.load()?;
@ -484,6 +485,16 @@ pub fn token_edit(
bank.platform_liquidation_fee = I80F48::from_num(platform_liquidation_fee);
require_group_admin = true;
}
if let Some(disable_asset_liquidation) = disable_asset_liquidation_opt {
msg!(
"Asset liquidation disabled old {:?}, new {:?}",
bank.disable_asset_liquidation,
disable_asset_liquidation
);
bank.disable_asset_liquidation = u8::from(disable_asset_liquidation);
require_group_admin = true;
}
}
// account constraint #1

View File

@ -112,6 +112,10 @@ pub(crate) fn liquidation_action(
liqee.token_position_and_raw_index(asset_token_index)?;
let liqee_asset_native = liqee_asset_position.native(asset_bank);
require_gt!(liqee_asset_native, 0);
require!(
asset_bank.allows_asset_liquidation(),
MangoError::TokenAssetLiquidationDisabled
);
let (liqee_liab_position, liqee_liab_raw_index) =
liqee.token_position_and_raw_index(liab_token_index)?;

View File

@ -44,6 +44,7 @@ pub fn token_register(
deposit_limit: u64,
zero_util_rate: f32,
platform_liquidation_fee: f32,
disable_asset_liquidation: bool,
) -> Result<()> {
// Require token 0 to be in the insurance token
if token_index == INSURANCE_TOKEN_INDEX {
@ -109,6 +110,7 @@ pub fn token_register(
deposit_weight_scale_start_quote,
reduce_only,
force_close: 0,
disable_asset_liquidation: u8::from(disable_asset_liquidation),
padding: Default::default(),
fees_withdrawn: 0,
token_conditional_swap_taker_fee_rate,

View File

@ -90,6 +90,7 @@ pub fn token_register_trustless(
deposit_weight_scale_start_quote: 5_000_000_000.0, // $5k
reduce_only: 2, // deposit-only
force_close: 0,
disable_asset_liquidation: 1,
padding: Default::default(),
fees_withdrawn: 0,
token_conditional_swap_taker_fee_rate: 0.0,

View File

@ -157,6 +157,7 @@ pub mod mango_v4 {
deposit_limit: u64,
zero_util_rate: f32,
platform_liquidation_fee: f32,
disable_asset_liquidation: bool,
) -> Result<()> {
#[cfg(feature = "enable-gpl")]
instructions::token_register(
@ -190,6 +191,7 @@ pub mod mango_v4 {
deposit_limit,
zero_util_rate,
platform_liquidation_fee,
disable_asset_liquidation,
)?;
Ok(())
}
@ -245,6 +247,7 @@ pub mod mango_v4 {
deposit_limit_opt: Option<u64>,
zero_util_rate_opt: Option<f32>,
platform_liquidation_fee_opt: Option<f32>,
disable_asset_liquidation_opt: Option<bool>,
) -> Result<()> {
#[cfg(feature = "enable-gpl")]
instructions::token_edit(
@ -287,6 +290,7 @@ pub mod mango_v4 {
deposit_limit_opt,
zero_util_rate_opt,
platform_liquidation_fee_opt,
disable_asset_liquidation_opt,
)?;
Ok(())
}

View File

@ -158,8 +158,12 @@ pub struct Bank {
pub reduce_only: u8,
pub force_close: u8,
/// If set to 1, deposits cannot be liquidated when an account is liquidatable.
/// That means bankrupt accounts may still have assets of this type deposited.
pub disable_asset_liquidation: u8,
#[derivative(Debug = "ignore")]
pub padding: [u8; 6],
pub padding: [u8; 5],
// Do separate bookkeping for how many tokens were withdrawn
// This ensures that collected_fees_native is strictly increasing for stats gathering purposes
@ -346,7 +350,8 @@ impl Bank {
deposit_weight_scale_start_quote: existing_bank.deposit_weight_scale_start_quote,
reduce_only: existing_bank.reduce_only,
force_close: existing_bank.force_close,
padding: [0; 6],
disable_asset_liquidation: existing_bank.disable_asset_liquidation,
padding: [0; 5],
token_conditional_swap_taker_fee_rate: existing_bank
.token_conditional_swap_taker_fee_rate,
token_conditional_swap_maker_fee_rate: existing_bank
@ -396,6 +401,10 @@ impl Bank {
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);
if !self.allows_asset_liquidation() {
require!(self.are_borrows_reduce_only(), MangoError::SomeError);
require_eq!(self.maint_asset_weight, I80F48::ZERO);
}
Ok(())
}
@ -417,6 +426,10 @@ impl Bank {
self.force_close == 1
}
pub fn allows_asset_liquidation(&self) -> bool {
self.disable_asset_liquidation == 0
}
#[inline(always)]
pub fn native_borrows(&self) -> I80F48 {
self.borrow_index * self.indexed_borrows

View File

@ -324,6 +324,66 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
//
set_bank_stub_oracle_price(solana, group, borrow_token1, admin, 2.0).await;
//
// TEST: can't liquidate if token has no asset weight
//
send_tx(
solana,
TokenEdit {
group,
admin,
mint: collateral_token2.mint.pubkey,
fallback_oracle: Pubkey::default(),
options: mango_v4::instruction::TokenEdit {
maint_asset_weight_opt: Some(0.0),
init_asset_weight_opt: Some(0.0),
disable_asset_liquidation_opt: Some(true),
reduce_only_opt: Some(1),
..token_edit_instruction_default()
},
},
)
.await
.unwrap();
let res = send_tx(
solana,
TokenLiqWithTokenInstruction {
liqee: account,
liqor: vault_account,
liqor_owner: owner,
asset_token_index: collateral_token2.index,
liab_token_index: borrow_token2.index,
asset_bank_index: 0,
liab_bank_index: 0,
max_liab_transfer: I80F48::from_num(10000.0),
},
)
.await;
assert_mango_error(
&res,
MangoError::TokenAssetLiquidationDisabled.into(),
"liquidation disabled".to_string(),
);
send_tx(
solana,
TokenEdit {
group,
admin,
mint: collateral_token2.mint.pubkey,
fallback_oracle: Pubkey::default(),
options: mango_v4::instruction::TokenEdit {
maint_asset_weight_opt: Some(0.8),
init_asset_weight_opt: Some(0.6),
disable_asset_liquidation_opt: Some(false),
reduce_only_opt: Some(0),
..token_edit_instruction_default()
},
},
)
.await
.unwrap();
//
// TEST: liquidate borrow2 against too little collateral2
//

View File

@ -1077,6 +1077,7 @@ impl ClientInstruction for TokenRegisterInstruction {
deposit_limit: 0,
zero_util_rate: 0.0,
platform_liquidation_fee: self.platform_liquidation_fee,
disable_asset_liquidation: false,
};
let bank = Pubkey::find_program_address(
@ -1324,6 +1325,7 @@ pub fn token_edit_instruction_default() -> mango_v4::instruction::TokenEdit {
deposit_limit_opt: None,
zero_util_rate_opt: None,
platform_liquidation_fee_opt: None,
disable_asset_liquidation_opt: None,
}
}

View File

@ -130,6 +130,7 @@ export class Bank implements BankForHealth {
depositWeightScaleStartQuote: number;
reduceOnly: number;
forceClose: number;
disableAssetLiquidation: number;
feesWithdrawn: BN;
tokenConditionalSwapTakerFeeRate: number;
tokenConditionalSwapMakerFeeRate: number;
@ -211,6 +212,7 @@ export class Bank implements BankForHealth {
obj.zeroUtilRate,
obj.platformLiquidationFee,
obj.collectedLiquidationFees,
obj.disableAssetLiquidation == 0,
);
}
@ -276,6 +278,7 @@ export class Bank implements BankForHealth {
zeroUtilRate: I80F48Dto,
platformLiquidationFee: I80F48Dto,
collectedLiquidationFees: I80F48Dto,
public allowAssetLiquidation: boolean,
) {
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
this.oracleConfig = {

View File

@ -461,6 +461,7 @@ export class MangoClient {
params.depositLimit,
params.zeroUtilRate,
params.platformLiquidationFee,
params.disableAssetLiquidation,
)
.accounts({
group: group.publicKey,
@ -548,6 +549,7 @@ export class MangoClient {
params.depositLimit,
params.zeroUtilRate,
params.platformLiquidationFee,
params.disableAssetLiquidation,
)
.accounts({
group: group.publicKey,

View File

@ -30,6 +30,7 @@ export interface TokenRegisterParams {
depositLimit: BN;
zeroUtilRate: number;
platformLiquidationFee: number;
disableAssetLiquidation: boolean;
}
export const DefaultTokenRegisterParams: TokenRegisterParams = {
@ -70,6 +71,7 @@ export const DefaultTokenRegisterParams: TokenRegisterParams = {
depositLimit: new BN(0),
zeroUtilRate: 0.0,
platformLiquidationFee: 0.0,
disableAssetLiquidation: false,
};
export interface TokenEditParams {
@ -111,6 +113,7 @@ export interface TokenEditParams {
depositLimit: BN | null;
zeroUtilRate: number | null;
platformLiquidationFee: number | null;
disableAssetLiquidation: boolean | null;
}
export const NullTokenEditParams: TokenEditParams = {
@ -152,6 +155,7 @@ export const NullTokenEditParams: TokenEditParams = {
depositLimit: null,
zeroUtilRate: null,
platformLiquidationFee: null,
disableAssetLiquidation: null,
};
export interface PerpEditParams {

View File

@ -631,6 +631,10 @@ export type MangoV4 = {
{
"name": "platformLiquidationFee",
"type": "f32"
},
{
"name": "disableAssetLiquidation",
"type": "bool"
}
]
},
@ -1041,6 +1045,12 @@ export type MangoV4 = {
"type": {
"option": "f32"
}
},
{
"name": "disableAssetLiquidationOpt",
"type": {
"option": "bool"
}
}
]
},
@ -7373,12 +7383,20 @@ export type MangoV4 = {
"name": "forceClose",
"type": "u8"
},
{
"name": "disableAssetLiquidation",
"docs": [
"If set to 1, deposits cannot be liquidated when an account is liquidatable.",
"That means bankrupt accounts may still have assets of this type deposited."
],
"type": "u8"
},
{
"name": "padding",
"type": {
"array": [
"u8",
6
5
]
}
},
@ -14028,6 +14046,11 @@ export type MangoV4 = {
"code": 6068,
"name": "MissingFeedForCLMMOracle",
"msg": "Pyth USDC/USD or SOL/USD feed not found (required by CLMM oracle)"
},
{
"code": 6069,
"name": "TokenAssetLiquidationDisabled",
"msg": "the asset does not allow liquidation"
}
]
};
@ -14665,6 +14688,10 @@ export const IDL: MangoV4 = {
{
"name": "platformLiquidationFee",
"type": "f32"
},
{
"name": "disableAssetLiquidation",
"type": "bool"
}
]
},
@ -15075,6 +15102,12 @@ export const IDL: MangoV4 = {
"type": {
"option": "f32"
}
},
{
"name": "disableAssetLiquidationOpt",
"type": {
"option": "bool"
}
}
]
},
@ -21407,12 +21440,20 @@ export const IDL: MangoV4 = {
"name": "forceClose",
"type": "u8"
},
{
"name": "disableAssetLiquidation",
"docs": [
"If set to 1, deposits cannot be liquidated when an account is liquidatable.",
"That means bankrupt accounts may still have assets of this type deposited."
],
"type": "u8"
},
{
"name": "padding",
"type": {
"array": [
"u8",
6
5
]
}
},
@ -28062,6 +28103,11 @@ export const IDL: MangoV4 = {
"code": 6068,
"name": "MissingFeedForCLMMOracle",
"msg": "Pyth USDC/USD or SOL/USD feed not found (required by CLMM oracle)"
},
{
"code": 6069,
"name": "TokenAssetLiquidationDisabled",
"msg": "the asset does not allow liquidation"
}
]
};