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)| { .filter_map(|(ti, effective)| {
// check constraints for liquidatable assets, see also has_possible_spot_liquidations() // check constraints for liquidatable assets, see also has_possible_spot_liquidations()
let tokens = ti.balance_spot.min(effective.spot_and_perp); 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; let quote_value = tokens * ti.prices.oracle;
// prefer to liquidate tokens with asset weight that have >$1 liquidatable // prefer to liquidate tokens with asset weight that have >$1 liquidatable
let is_preferred = 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)) is_valid_asset.then_some((ti.token_index, is_preferred, quote_value))
}) })
.collect_vec(); .collect_vec();

View File

@ -631,6 +631,10 @@
{ {
"name": "platformLiquidationFee", "name": "platformLiquidationFee",
"type": "f32" "type": "f32"
},
{
"name": "disableAssetLiquidation",
"type": "bool"
} }
] ]
}, },
@ -1041,6 +1045,12 @@
"type": { "type": {
"option": "f32" "option": "f32"
} }
},
{
"name": "disableAssetLiquidationOpt",
"type": {
"option": "bool"
}
} }
] ]
}, },
@ -7373,12 +7383,20 @@
"name": "forceClose", "name": "forceClose",
"type": "u8" "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", "name": "padding",
"type": { "type": {
"array": [ "array": [
"u8", "u8",
6 5
] ]
} }
}, },
@ -14028,6 +14046,11 @@
"code": 6068, "code": 6068,
"name": "MissingFeedForCLMMOracle", "name": "MissingFeedForCLMMOracle",
"msg": "Pyth USDC/USD or SOL/USD feed not found (required by CLMM oracle)" "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, InvalidFeedForCLMMOracle,
#[msg("Pyth USDC/USD or SOL/USD feed not found (required by CLMM oracle)")] #[msg("Pyth USDC/USD or SOL/USD feed not found (required by CLMM oracle)")]
MissingFeedForCLMMOracle, MissingFeedForCLMMOracle,
#[msg("the asset does not allow liquidation")]
TokenAssetLiquidationDisabled,
} }
impl MangoError { impl MangoError {

View File

@ -175,6 +175,8 @@ pub struct TokenInfo {
/// Includes TokenPosition and free Serum3OpenOrders balances. /// Includes TokenPosition and free Serum3OpenOrders balances.
/// Does not include perp upnl or Serum3 reserved amounts. /// Does not include perp upnl or Serum3 reserved amounts.
pub balance_spot: I80F48, pub balance_spot: I80F48,
pub allow_asset_liquidation: bool,
} }
/// Temporary value used during health computations /// 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 /// 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 { pub fn has_liq_spot_assets(&self) -> bool {
let health_token_balances = self.effective_token_balances(HealthType::LiquidationEnd); let health_token_balances = self.effective_token_balances(HealthType::LiquidationEnd);
self.token_infos self.token_infos
@ -914,11 +917,11 @@ impl HealthCache {
.zip(health_token_balances.iter()) .zip(health_token_balances.iter())
.any(|(ti, b)| { .any(|(ti, b)| {
// need 1 native token to use token_liq_with_token // 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 { pub fn has_liq_spot_borrows(&self) -> bool {
let health_token_balances = self.effective_token_balances(HealthType::LiquidationEnd); let health_token_balances = self.effective_token_balances(HealthType::LiquidationEnd);
self.token_infos self.token_infos
@ -932,7 +935,9 @@ impl HealthCache {
let health_token_balances = self.effective_token_balances(HealthType::LiquidationEnd); let health_token_balances = self.effective_token_balances(HealthType::LiquidationEnd);
let all_iter = || self.token_infos.iter().zip(health_token_balances.iter()); 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 < 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 { 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), init_scaled_liab_weight: bank.scaled_init_liab_weight(liab_price),
prices, prices,
balance_spot: native, 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), init_scaled_liab_weight: I80F48::from_num(1.0 + x),
prices: Prices::new_single_price(I80F48::from_num(price)), prices: Prices::new_single_price(I80F48::from_num(price)),
balance_spot: I80F48::ZERO, balance_spot: I80F48::ZERO,
allow_asset_liquidation: true,
} }
} }

View File

@ -53,6 +53,7 @@ pub fn token_edit(
deposit_limit_opt: Option<u64>, deposit_limit_opt: Option<u64>,
zero_util_rate: Option<f32>, zero_util_rate: Option<f32>,
platform_liquidation_fee: Option<f32>, platform_liquidation_fee: Option<f32>,
disable_asset_liquidation_opt: Option<bool>,
) -> Result<()> { ) -> Result<()> {
let group = ctx.accounts.group.load()?; let group = ctx.accounts.group.load()?;
@ -484,6 +485,16 @@ pub fn token_edit(
bank.platform_liquidation_fee = I80F48::from_num(platform_liquidation_fee); bank.platform_liquidation_fee = I80F48::from_num(platform_liquidation_fee);
require_group_admin = true; 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 // account constraint #1

View File

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

View File

@ -44,6 +44,7 @@ pub fn token_register(
deposit_limit: u64, deposit_limit: u64,
zero_util_rate: f32, zero_util_rate: f32,
platform_liquidation_fee: f32, platform_liquidation_fee: f32,
disable_asset_liquidation: bool,
) -> Result<()> { ) -> Result<()> {
// Require token 0 to be in the insurance token // Require token 0 to be in the insurance token
if token_index == INSURANCE_TOKEN_INDEX { if token_index == INSURANCE_TOKEN_INDEX {
@ -109,6 +110,7 @@ pub fn token_register(
deposit_weight_scale_start_quote, deposit_weight_scale_start_quote,
reduce_only, reduce_only,
force_close: 0, force_close: 0,
disable_asset_liquidation: u8::from(disable_asset_liquidation),
padding: Default::default(), padding: Default::default(),
fees_withdrawn: 0, fees_withdrawn: 0,
token_conditional_swap_taker_fee_rate, 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 deposit_weight_scale_start_quote: 5_000_000_000.0, // $5k
reduce_only: 2, // deposit-only reduce_only: 2, // deposit-only
force_close: 0, force_close: 0,
disable_asset_liquidation: 1,
padding: Default::default(), padding: Default::default(),
fees_withdrawn: 0, fees_withdrawn: 0,
token_conditional_swap_taker_fee_rate: 0.0, token_conditional_swap_taker_fee_rate: 0.0,

View File

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

View File

@ -158,8 +158,12 @@ pub struct Bank {
pub reduce_only: u8, pub reduce_only: u8,
pub force_close: 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")] #[derivative(Debug = "ignore")]
pub padding: [u8; 6], pub padding: [u8; 5],
// Do separate bookkeping for how many tokens were withdrawn // Do separate bookkeping for how many tokens were withdrawn
// This ensures that collected_fees_native is strictly increasing for stats gathering purposes // 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, deposit_weight_scale_start_quote: existing_bank.deposit_weight_scale_start_quote,
reduce_only: existing_bank.reduce_only, reduce_only: existing_bank.reduce_only,
force_close: existing_bank.force_close, 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: existing_bank
.token_conditional_swap_taker_fee_rate, .token_conditional_swap_taker_fee_rate,
token_conditional_swap_maker_fee_rate: existing_bank 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.maint_weight_shift_liab_target, 0.0);
require_gte!(self.zero_util_rate, I80F48::ZERO); require_gte!(self.zero_util_rate, I80F48::ZERO);
require_gte!(self.platform_liquidation_fee, 0.0); 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(()) Ok(())
} }
@ -417,6 +426,10 @@ impl Bank {
self.force_close == 1 self.force_close == 1
} }
pub fn allows_asset_liquidation(&self) -> bool {
self.disable_asset_liquidation == 0
}
#[inline(always)] #[inline(always)]
pub fn native_borrows(&self) -> I80F48 { pub fn native_borrows(&self) -> I80F48 {
self.borrow_index * self.indexed_borrows 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; 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 // TEST: liquidate borrow2 against too little collateral2
// //

View File

@ -1077,6 +1077,7 @@ impl ClientInstruction for TokenRegisterInstruction {
deposit_limit: 0, deposit_limit: 0,
zero_util_rate: 0.0, zero_util_rate: 0.0,
platform_liquidation_fee: self.platform_liquidation_fee, platform_liquidation_fee: self.platform_liquidation_fee,
disable_asset_liquidation: false,
}; };
let bank = Pubkey::find_program_address( let bank = Pubkey::find_program_address(
@ -1324,6 +1325,7 @@ pub fn token_edit_instruction_default() -> mango_v4::instruction::TokenEdit {
deposit_limit_opt: None, deposit_limit_opt: None,
zero_util_rate_opt: None, zero_util_rate_opt: None,
platform_liquidation_fee_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; depositWeightScaleStartQuote: number;
reduceOnly: number; reduceOnly: number;
forceClose: number; forceClose: number;
disableAssetLiquidation: number;
feesWithdrawn: BN; feesWithdrawn: BN;
tokenConditionalSwapTakerFeeRate: number; tokenConditionalSwapTakerFeeRate: number;
tokenConditionalSwapMakerFeeRate: number; tokenConditionalSwapMakerFeeRate: number;
@ -211,6 +212,7 @@ export class Bank implements BankForHealth {
obj.zeroUtilRate, obj.zeroUtilRate,
obj.platformLiquidationFee, obj.platformLiquidationFee,
obj.collectedLiquidationFees, obj.collectedLiquidationFees,
obj.disableAssetLiquidation == 0,
); );
} }
@ -276,6 +278,7 @@ export class Bank implements BankForHealth {
zeroUtilRate: I80F48Dto, zeroUtilRate: I80F48Dto,
platformLiquidationFee: I80F48Dto, platformLiquidationFee: I80F48Dto,
collectedLiquidationFees: I80F48Dto, collectedLiquidationFees: I80F48Dto,
public allowAssetLiquidation: boolean,
) { ) {
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0]; this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
this.oracleConfig = { this.oracleConfig = {

View File

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

View File

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

View File

@ -631,6 +631,10 @@ export type MangoV4 = {
{ {
"name": "platformLiquidationFee", "name": "platformLiquidationFee",
"type": "f32" "type": "f32"
},
{
"name": "disableAssetLiquidation",
"type": "bool"
} }
] ]
}, },
@ -1041,6 +1045,12 @@ export type MangoV4 = {
"type": { "type": {
"option": "f32" "option": "f32"
} }
},
{
"name": "disableAssetLiquidationOpt",
"type": {
"option": "bool"
}
} }
] ]
}, },
@ -7373,12 +7383,20 @@ export type MangoV4 = {
"name": "forceClose", "name": "forceClose",
"type": "u8" "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", "name": "padding",
"type": { "type": {
"array": [ "array": [
"u8", "u8",
6 5
] ]
} }
}, },
@ -14028,6 +14046,11 @@ export type MangoV4 = {
"code": 6068, "code": 6068,
"name": "MissingFeedForCLMMOracle", "name": "MissingFeedForCLMMOracle",
"msg": "Pyth USDC/USD or SOL/USD feed not found (required by CLMM oracle)" "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", "name": "platformLiquidationFee",
"type": "f32" "type": "f32"
},
{
"name": "disableAssetLiquidation",
"type": "bool"
} }
] ]
}, },
@ -15075,6 +15102,12 @@ export const IDL: MangoV4 = {
"type": { "type": {
"option": "f32" "option": "f32"
} }
},
{
"name": "disableAssetLiquidationOpt",
"type": {
"option": "bool"
}
} }
] ]
}, },
@ -21407,12 +21440,20 @@ export const IDL: MangoV4 = {
"name": "forceClose", "name": "forceClose",
"type": "u8" "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", "name": "padding",
"type": { "type": {
"array": [ "array": [
"u8", "u8",
6 5
] ]
} }
}, },
@ -28062,6 +28103,11 @@ export const IDL: MangoV4 = {
"code": 6068, "code": 6068,
"name": "MissingFeedForCLMMOracle", "name": "MissingFeedForCLMMOracle",
"msg": "Pyth USDC/USD or SOL/USD feed not found (required by CLMM oracle)" "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"
} }
] ]
}; };