Change liquidation end target to a new, third health type (#447)

Due to the safety features in v4, the init health can differ from maint
health a lot more than it used to in v3. This is because of stable-price
adjusted oracle prices used in init health, and the weight scaling based
on total deposits and borrows used in init health.

The effect is that once an account becomes liquidatable, it could be
liquidated a lot until it reaches init>=0.

The original idea of liquidating until init>=0 was just to provide some
buffer, such that liquidated accounts wouldn't immediately become
liquidatable again.

This patch decouples the buffer idea explicit from init health by
creating a new LiquidationEnd health type. Liquidation proceeds until
the LiquidationEnd health becomes positive.

Co-authored-by: microwavedcola1 <89031858+microwavedcola1@users.noreply.github.com>
This commit is contained in:
Christian Kamm 2023-02-10 09:00:36 +01:00 committed by GitHub
parent b2b029e21e
commit 1f66bef88f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 330 additions and 181 deletions

View File

@ -32,34 +32,41 @@ impl Prices {
/// The liability price to use for the given health type
#[inline(always)]
pub fn liab(&self, health_type: HealthType) -> I80F48 {
if health_type == HealthType::Maint {
self.oracle
} else {
self.oracle.max(self.stable)
match health_type {
HealthType::Maint | HealthType::LiquidationEnd => self.oracle,
HealthType::Init => self.oracle.max(self.stable),
}
}
/// The asset price to use for the given health type
#[inline(always)]
pub fn asset(&self, health_type: HealthType) -> I80F48 {
if health_type == HealthType::Maint {
self.oracle
} else {
self.oracle.min(self.stable)
match health_type {
HealthType::Maint | HealthType::LiquidationEnd => self.oracle,
HealthType::Init => self.oracle.min(self.stable),
}
}
}
/// There are two types of health, initial health used for opening new positions and maintenance
/// health used for liquidations. They are both calculated as a weighted sum of the assets
/// minus the liabilities but the maint. health uses slightly larger weights for assets and
/// slightly smaller weights for the liabilities. Zero is used as the bright line for both
/// i.e. if your init health falls below zero, you cannot open new positions and if your maint. health
/// falls below zero you will be liquidated.
/// There are three types of health:
/// - initial health ("init"): users can only open new positions if it's >= 0
/// - maintenance health ("maint"): users get liquidated if it's < 0
/// - liquidation end health: once liquidation started (see being_liquidated), it
/// only stops once this is >= 0
///
/// The ordering is
/// init health <= liquidation end health <= maint health
///
/// The different health types are realized by using different weights and prices:
/// - init health: init weights with scaling, stable-price adjusted prices
/// - liq end health: init weights without scaling, oracle prices
/// - maint health: maint weights, oracle prices
///
#[derive(PartialEq, Copy, Clone, AnchorSerialize, AnchorDeserialize)]
pub enum HealthType {
Init,
Maint,
Maint, // aka LiquidationStart
LiquidationEnd,
}
/// Computes health for a mango account given a set of account infos
@ -88,8 +95,10 @@ pub struct TokenInfo {
pub token_index: TokenIndex,
pub maint_asset_weight: I80F48,
pub init_asset_weight: I80F48,
pub init_scaled_asset_weight: I80F48,
pub maint_liab_weight: I80F48,
pub init_liab_weight: I80F48,
pub init_scaled_liab_weight: I80F48,
pub prices: Prices,
pub balance_native: I80F48,
}
@ -98,7 +107,8 @@ impl TokenInfo {
#[inline(always)]
fn asset_weight(&self, health_type: HealthType) -> I80F48 {
match health_type {
HealthType::Init => self.init_asset_weight,
HealthType::Init => self.init_scaled_asset_weight,
HealthType::LiquidationEnd => self.init_asset_weight,
HealthType::Maint => self.maint_asset_weight,
}
}
@ -106,7 +116,8 @@ impl TokenInfo {
#[inline(always)]
fn liab_weight(&self, health_type: HealthType) -> I80F48 {
match health_type {
HealthType::Init => self.init_liab_weight,
HealthType::Init => self.init_scaled_liab_weight,
HealthType::LiquidationEnd => self.init_liab_weight,
HealthType::Maint => self.maint_liab_weight,
}
}
@ -284,7 +295,7 @@ impl PerpInfo {
pub fn weigh_health_contribution(&self, unweighted: I80F48, health_type: HealthType) -> I80F48 {
if unweighted > 0 {
let asset_weight = match health_type {
HealthType::Init => self.init_overall_asset_weight,
HealthType::Init | HealthType::LiquidationEnd => self.init_overall_asset_weight,
HealthType::Maint => self.maint_overall_asset_weight,
};
@ -299,20 +310,22 @@ impl PerpInfo {
let order_execution_case = |orders_base_lots: i64, order_price: I80F48| {
let net_base_native =
I80F48::from(cm!((self.base_lots + orders_base_lots) * self.base_lot_size));
let (weight, base_price) = match (health_type, net_base_native.is_negative()) {
(HealthType::Init, true) => {
(self.init_base_liab_weight, self.prices.liab(health_type))
let weight = match (health_type, net_base_native.is_negative()) {
(HealthType::Init, true) | (HealthType::LiquidationEnd, true) => {
self.init_base_liab_weight
}
(HealthType::Init, false) => {
(self.init_base_asset_weight, self.prices.asset(health_type))
}
(HealthType::Maint, true) => {
(self.maint_base_liab_weight, self.prices.liab(health_type))
}
(HealthType::Maint, false) => {
(self.maint_base_asset_weight, self.prices.asset(health_type))
(HealthType::Init, false) | (HealthType::LiquidationEnd, false) => {
self.init_base_asset_weight
}
(HealthType::Maint, true) => self.maint_base_liab_weight,
(HealthType::Maint, false) => self.maint_base_asset_weight,
};
let base_price = if net_base_native.is_negative() {
self.prices.liab(health_type)
} else {
self.prices.asset(health_type)
};
// Total value of the order-execution adjusted base position
let base_health = cm!(net_base_native * weight * base_price);
@ -407,9 +420,10 @@ impl HealthCache {
// Note: resetting the weights here assumes that the change has been applied to
// the passed in bank already
entry.init_asset_weight =
entry.init_scaled_asset_weight =
bank.scaled_init_asset_weight(entry.prices.asset(HealthType::Init));
entry.init_liab_weight = bank.scaled_init_liab_weight(entry.prices.liab(HealthType::Init));
entry.init_scaled_liab_weight =
bank.scaled_init_liab_weight(entry.prices.liab(HealthType::Init));
// Work around the fact that -((-x) * y) == x * y does not hold for I80F48:
// We need to make sure that if balance is before * price, then change = -before
@ -714,12 +728,17 @@ pub fn new_health_cache(
oracle: oracle_price,
stable: bank.stable_price(),
};
// Use the liab price for computing weight scaling, because it's pessimistic and
// causes the most unfavorable scaling.
let liab_price = prices.liab(HealthType::Init);
token_infos.push(TokenInfo {
token_index: bank.token_index,
maint_asset_weight: bank.maint_asset_weight,
init_asset_weight: bank.scaled_init_asset_weight(prices.asset(HealthType::Init)),
init_asset_weight: bank.init_asset_weight,
init_scaled_asset_weight: bank.scaled_init_asset_weight(liab_price),
maint_liab_weight: bank.maint_liab_weight,
init_liab_weight: bank.scaled_init_liab_weight(prices.liab(HealthType::Init)),
init_liab_weight: bank.init_liab_weight,
init_scaled_liab_weight: bank.scaled_init_liab_weight(liab_price),
prices,
balance_native: native,
});

View File

@ -15,7 +15,7 @@ use super::*;
impl HealthCache {
pub fn is_liquidatable(&self) -> bool {
if self.being_liquidated {
self.health(HealthType::Init).is_negative()
self.health(HealthType::LiquidationEnd).is_negative()
} else {
self.health(HealthType::Maint).is_negative()
}
@ -162,7 +162,7 @@ impl HealthCache {
// This is just the highest final slope we can get. If the health weights are
// scaled because the collateral or borrow limits are exceeded, health will decrease
// more quickly than this number.
let final_health_slope = -source.init_liab_weight * source.prices.liab(health_type)
let final_health_slope = -source.init_scaled_liab_weight * source.prices.liab(health_type)
+ target.init_asset_weight * target.prices.asset(health_type) * price;
if final_health_slope >= 0 {
// TODO: not true if weights scaled with deposits/borrows
@ -422,7 +422,7 @@ impl HealthCache {
// At most withdraw all deposits plus enough borrows to bring health to zero
// (ensure this works with zero asset weight)
let limit = token.balance_native.max(I80F48::ZERO)
+ self.health(health_type).max(I80F48::ZERO) / token.init_liab_weight;
+ self.health(health_type).max(I80F48::ZERO) / token.init_scaled_liab_weight;
if limit <= 0 {
return Ok(I80F48::ZERO);
}
@ -621,8 +621,10 @@ mod tests {
token_index: 0,
maint_asset_weight: I80F48::from_num(1.0 - x),
init_asset_weight: I80F48::from_num(1.0 - x),
init_scaled_asset_weight: I80F48::from_num(1.0 - x),
maint_liab_weight: I80F48::from_num(1.0 + x),
init_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)),
balance_native: I80F48::ZERO,
}

View File

@ -366,7 +366,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
// Check health before balance adjustments
let retriever = new_fixed_order_account_retriever(health_ais, &account.borrow())?;
let health_cache = new_health_cache(&account.borrow(), &retriever)?;
let pre_health = account.check_health_pre(&health_cache)?;
let pre_init_health = account.check_health_pre(&health_cache)?;
// Prices for logging and net borrow checks
let mut oracle_prices = vec![];
@ -464,7 +464,7 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
// Check health after account position changes
let retriever = new_fixed_order_account_retriever(health_ais, &account.borrow())?;
let health_cache = new_health_cache(&account.borrow(), &retriever)?;
account.check_health_post(&health_cache, pre_health)?;
account.check_health_post(&health_cache, pre_init_health)?;
// Deactivate inactive token accounts after health check
for raw_token_index in deactivated_token_positions {

View File

@ -94,8 +94,9 @@ pub fn health_region_begin<'key, 'accounts, 'remaining, 'info>(
// Compute pre-health and store it on the account
let health_cache = new_health_cache(&account.borrow(), &account_retriever)?;
let pre_health = account.check_health_pre(&health_cache)?;
account.fixed.health_region_begin_init_health = pre_health.ceil().checked_to_num().unwrap();
let pre_init_health = account.check_health_pre(&health_cache)?;
account.fixed.health_region_begin_init_health =
pre_init_health.ceil().checked_to_num().unwrap();
Ok(())
}
@ -115,8 +116,8 @@ pub fn health_region_end<'key, 'accounts, 'remaining, 'info>(
.context("create account retriever")?;
let health_cache = new_health_cache(&account.borrow(), &account_retriever)?;
let pre_health = I80F48::from(account.fixed.health_region_begin_init_health);
account.check_health_post(&health_cache, pre_health)?;
let pre_init_health = I80F48::from(account.fixed.health_region_begin_init_health);
account.check_health_post(&health_cache, pre_init_health)?;
account.fixed.health_region_begin_init_health = 0;
Ok(())

View File

@ -99,7 +99,7 @@ pub fn perp_liq_base_or_positive_pnl(
new_health_cache(&liqee.borrow(), &account_retriever)
.context("create liqee health cache")?
};
let liqee_init_health = liqee_health_cache.health(HealthType::Init);
let liqee_liq_end_health = liqee_health_cache.health(HealthType::LiquidationEnd);
liqee_health_cache.require_after_phase1_liquidation()?;
if !liqee.check_liquidatable(&liqee_health_cache)? {
@ -150,7 +150,7 @@ pub fn perp_liq_base_or_positive_pnl(
&mut liqor.borrow_mut(),
&mut liqee.borrow_mut(),
&mut liqee_health_cache,
liqee_init_health,
liqee_liq_end_health,
now_ts,
max_base_transfer,
max_pnl_transfer,
@ -215,15 +215,15 @@ pub fn perp_liq_base_or_positive_pnl(
}
// Check liqee health again
let liqee_init_health_after = liqee_health_cache.health(HealthType::Init);
let liqee_liq_end_health_after = liqee_health_cache.health(HealthType::LiquidationEnd);
liqee
.fixed
.maybe_recover_from_being_liquidated(liqee_init_health_after);
require_gte!(liqee_init_health_after, liqee_init_health);
.maybe_recover_from_being_liquidated(liqee_liq_end_health_after);
require_gte!(liqee_liq_end_health_after, liqee_liq_end_health);
msg!(
"liqee health: {} -> {}",
liqee_init_health,
liqee_init_health_after
"liqee liq end health: {} -> {}",
liqee_liq_end_health,
liqee_liq_end_health_after
);
drop(settle_bank);
@ -247,7 +247,7 @@ pub(crate) fn liquidation_action(
liqor: &mut MangoAccountRefMut,
liqee: &mut MangoAccountRefMut,
liqee_health_cache: &mut HealthCache,
liqee_init_health: I80F48,
liqee_liq_end_health: I80F48,
now_ts: u64,
max_base_transfer: i64,
max_pnl_transfer: u64,
@ -290,7 +290,7 @@ pub(crate) fn liquidation_action(
let quote_init_asset_weight = I80F48::ONE;
direction = -1;
fee_factor = cm!(I80F48::ONE - perp_market.base_liquidation_fee);
let asset_price = perp_info.prices.asset(HealthType::Init);
let asset_price = perp_info.prices.asset(HealthType::LiquidationEnd);
unweighted_health_per_lot = cm!(-asset_price
* base_lot_size
* perp_market.init_base_asset_weight
@ -307,7 +307,7 @@ pub(crate) fn liquidation_action(
let quote_init_liab_weight = I80F48::ONE;
direction = 1;
fee_factor = cm!(I80F48::ONE + perp_market.base_liquidation_fee);
let liab_price = perp_info.prices.liab(HealthType::Init);
let liab_price = perp_info.prices.liab(HealthType::LiquidationEnd);
unweighted_health_per_lot = cm!(liab_price
* base_lot_size
* perp_market.init_base_liab_weight
@ -340,12 +340,12 @@ pub(crate) fn liquidation_action(
//
let mut base_reduction = 0;
let mut current_unweighted_perp_health =
perp_info.unweighted_health_contribution(HealthType::Init);
let initial_weighted_perp_health =
perp_info.weigh_health_contribution(current_unweighted_perp_health, HealthType::Init);
perp_info.unweighted_health_contribution(HealthType::LiquidationEnd);
let initial_weighted_perp_health = perp_info
.weigh_health_contribution(current_unweighted_perp_health, HealthType::LiquidationEnd);
let mut current_expected_perp_health = expected_perp_health(current_unweighted_perp_health);
let mut current_expected_health =
cm!(liqee_init_health + current_expected_perp_health - initial_weighted_perp_health);
cm!(liqee_liq_end_health + current_expected_perp_health - initial_weighted_perp_health);
let mut reduce_base = |step: &str,
health_amount: I80F48,
@ -446,10 +446,10 @@ pub(crate) fn liquidation_action(
// Step 4: Let the liqor take over positive pnl until the account health is positive,
// but only while the unweighted perp health is positive (otherwise it would decrease liqee health!)
//
let final_weighted_perp_health =
perp_info.weigh_health_contribution(current_unweighted_perp_health, HealthType::Init);
let final_weighted_perp_health = perp_info
.weigh_health_contribution(current_unweighted_perp_health, HealthType::LiquidationEnd);
let current_actual_health =
cm!(liqee_init_health - initial_weighted_perp_health + final_weighted_perp_health);
cm!(liqee_liq_end_health - initial_weighted_perp_health + final_weighted_perp_health);
let pnl_transfer_possible =
current_actual_health < 0 && current_unweighted_perp_health > 0 && max_pnl_transfer > 0;
let (pnl_transfer, limit_transfer) = if pnl_transfer_possible {
@ -596,7 +596,7 @@ mod tests {
let mut setup = self.clone();
let mut liqee_health_cache = setup.liqee_health_cache();
let liqee_init_health = liqee_health_cache.health(HealthType::Init);
let liqee_liq_end_health = liqee_health_cache.health(HealthType::LiquidationEnd);
liquidation_action(
setup.perp_market.data(),
@ -604,7 +604,7 @@ mod tests {
&mut setup.liqor.borrow_mut(),
&mut setup.liqee.borrow_mut(),
&mut liqee_health_cache,
liqee_init_health,
liqee_liq_end_health,
0,
max_base,
max_pnl,
@ -848,18 +848,21 @@ mod tests {
}
}
// Checks that the stable price does _not_ affect the liquidation target amount
#[test]
fn test_liq_base_or_positive_pnl_stable_price() {
let mut setup = TestSetup::new();
{
let pm = setup.perp_market.data();
pm.stable_price_model.stable_price = 0.5;
pm.init_base_asset_weight = I80F48::from_num(0.6);
pm.maint_base_asset_weight = I80F48::from_num(0.8);
}
{
perp_p(&mut setup.liqee).record_trade(
setup.perp_market.data(),
10,
I80F48::from_num(-10),
30,
I80F48::from_num(-30),
);
let settle_bank = setup.settle_bank.data();
@ -884,19 +887,29 @@ mod tests {
let hc = setup.liqee_health_cache();
assert_eq_f!(
hc.health(HealthType::Init),
5.0 + (-10.0 + 10.0 * 0.5 * 0.8),
5.0 + (-30.0 + 30.0 * 0.5 * 0.6), // init + stable
0.1
);
assert_eq_f!(
hc.health(HealthType::LiquidationEnd),
5.0 + (-30.0 + 30.0 * 0.6), // init + oracle
0.1
);
assert_eq_f!(
hc.health(HealthType::Maint),
5.0 + (-30.0 + 30.0 * 0.8), // maint + oracle
0.1
);
let mut result = setup.run(100, 0).unwrap();
let liqee_perp = perp_p(&mut result.liqee);
assert_eq!(liqee_perp.base_position_lots(), 8);
assert_eq!(liqee_perp.base_position_lots(), 12);
let hc = result.liqee_health_cache();
assert_eq_f!(
hc.health(HealthType::Init),
5.0 + (-8.0 + 8.0 * 0.5 * 0.8),
hc.health(HealthType::LiquidationEnd),
5.0 + (-12.0 + 12.0 * 0.6),
0.1
);
}

View File

@ -84,7 +84,7 @@ pub fn perp_liq_force_cancel_orders(
//
// Health check at the end
//
let init_health = health_cache.health(HealthType::Init);
let init_health = health_cache.health(HealthType::LiquidationEnd);
account
.fixed
.maybe_recover_from_being_liquidated(init_health);

View File

@ -111,7 +111,7 @@ pub fn perp_liq_negative_pnl_or_bankruptcy(
.context("create account retriever")?;
new_health_cache(&liqee.borrow(), &retriever)?
};
let liqee_init_health = liqee_health_cache.health(HealthType::Init);
let liqee_liq_end_health = liqee_health_cache.health(HealthType::LiquidationEnd);
let liqee_settle_health = liqee_health_cache.perp_settle_health();
liqee_health_cache.require_after_phase2_liquidation()?;
@ -218,7 +218,8 @@ pub fn perp_liq_negative_pnl_or_bankruptcy(
let liqee_perp_position = liqee.perp_position_mut(perp_market_index)?;
let liqee_pnl = liqee_perp_position.unsettled_pnl(&perp_market, oracle_price)?;
let max_liab_transfer_from_liqee = (-liqee_pnl).min(-liqee_init_health).max(I80F48::ZERO);
let max_liab_transfer_from_liqee =
(-liqee_pnl).min(-liqee_liq_end_health).max(I80F48::ZERO);
let liab_transfer = max_liab_transfer_from_liqee
.min(max_liab_transfer)
.max(I80F48::ZERO);
@ -343,10 +344,10 @@ pub fn perp_liq_negative_pnl_or_bankruptcy(
// Check liqee health again: bankruptcy would improve health
liqee_health_cache.recompute_perp_info(liqee_perp_position, &perp_market)?;
let liqee_init_health = liqee_health_cache.health(HealthType::Init);
let liqee_liq_end_health = liqee_health_cache.health(HealthType::LiquidationEnd);
liqee
.fixed
.maybe_recover_from_being_liquidated(liqee_init_health);
.maybe_recover_from_being_liquidated(liqee_liq_end_health);
drop(perp_market);
drop(settle_bank);

View File

@ -108,8 +108,8 @@ pub fn perp_place_order(
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
let health_cache =
new_health_cache(&account.borrow(), &retriever).context("pre-withdraw init health")?;
let pre_health = account.check_health_pre(&health_cache)?;
Some((health_cache, pre_health))
let pre_init_health = account.check_health_pre(&health_cache)?;
Some((health_cache, pre_init_health))
} else {
None
};
@ -168,10 +168,10 @@ pub fn perp_place_order(
//
// Health check
//
if let Some((mut health_cache, pre_health)) = pre_health_opt {
if let Some((mut health_cache, pre_init_health)) = pre_health_opt {
let perp_position = account.perp_position(perp_market_index)?;
health_cache.recompute_perp_info(perp_position, &perp_market)?;
account.check_health_post(&health_cache, pre_health)?;
account.check_health_post(&health_cache, pre_init_health)?;
}
Ok(order_id_opt)

View File

@ -82,7 +82,7 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
account_b.token_position(settle_token_index)?;
}
let a_init_health;
let a_liq_end_health;
let a_maint_health;
let b_settle_health;
{
@ -91,7 +91,7 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
.context("create account retriever")?;
b_settle_health = new_health_cache(&account_b.borrow(), &retriever)?.perp_settle_health();
let a_cache = new_health_cache(&account_a.borrow(), &retriever)?;
a_init_health = a_cache.health(HealthType::Init);
a_liq_end_health = a_cache.health(HealthType::LiquidationEnd);
a_maint_health = a_cache.health(HealthType::Maint);
};
@ -181,7 +181,7 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
b_settle_health,
);
let fee = compute_settle_fee(&perp_market, a_init_health, a_maint_health, settlement)?;
let fee = compute_settle_fee(&perp_market, a_liq_end_health, a_maint_health, settlement)?;
a_perp_position.record_settle(settlement);
b_perp_position.record_settle(-settlement);
@ -285,21 +285,23 @@ pub fn perp_settle_pnl(ctx: Context<PerpSettlePnl>) -> Result<()> {
pub fn compute_settle_fee(
perp_market: &PerpMarket,
source_init_health: I80F48,
source_liq_end_health: I80F48,
source_maint_health: I80F48,
settlement: I80F48,
) -> Result<I80F48> {
assert!(source_maint_health >= source_liq_end_health);
// A percentage fee is paid to the settler when the source account's health is low.
// That's because the settlement could avoid it getting liquidated: settling will
// increase its health by actualizing positive perp pnl.
let low_health_fee = if source_init_health < 0 {
let low_health_fee = if source_liq_end_health < 0 {
let fee_fraction = I80F48::from_num(perp_market.settle_fee_fraction_low_health);
if source_maint_health < 0 {
cm!(settlement * fee_fraction)
} else {
cm!(settlement
* fee_fraction
* (-source_init_health / (source_maint_health - source_init_health)))
* (-source_liq_end_health / (source_maint_health - source_liq_end_health)))
}
} else {
I80F48::ZERO

View File

@ -241,10 +241,10 @@ pub fn serum3_liq_force_cancel_orders(
//
// Health check at the end
//
let init_health = health_cache.health(HealthType::Init);
let liq_end_health = health_cache.health(HealthType::LiquidationEnd);
account
.fixed
.maybe_recover_from_being_liquidated(init_health);
.maybe_recover_from_being_liquidated(liq_end_health);
Ok(())
}

View File

@ -260,8 +260,8 @@ pub fn serum3_place_order(
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
let health_cache =
new_health_cache(&account.borrow(), &retriever).context("pre-withdraw init health")?;
let pre_health = account.check_health_pre(&health_cache)?;
Some((health_cache, pre_health))
let pre_init_health = account.check_health_pre(&health_cache)?;
Some((health_cache, pre_init_health))
} else {
None
};
@ -383,10 +383,10 @@ pub fn serum3_place_order(
//
// Health check
//
if let Some((mut health_cache, pre_health)) = pre_health_opt {
if let Some((mut health_cache, pre_init_health)) = pre_health_opt {
vault_difference.adjust_health_cache(&mut health_cache, &payer_bank)?;
oo_difference.adjust_health_cache(&mut health_cache, &serum_market)?;
account.check_health_post(&health_cache, pre_health)?;
account.check_health_post(&health_cache, pre_init_health)?;
}
// TODO: enforce min_vault_to_deposits_ratio

View File

@ -193,7 +193,7 @@ impl<'a, 'info> DepositCommon<'a, 'info> {
// Also, TokenDeposit is one of the rare instructions that is allowed even during being_liquidated.
// Being in a health region always means being_liquidated is false, so it's safe to gate the check.
if !account.fixed.is_in_health_region() {
let health = cache.health(HealthType::Init);
let health = cache.health(HealthType::LiquidationEnd);
msg!("health: {}", health);
let was_being_liquidated = account.being_liquidated();

View File

@ -108,7 +108,7 @@ pub fn token_liq_bankruptcy(
liqee_health_cache.require_after_phase2_liquidation()?;
liqee.fixed.set_being_liquidated(true);
let (liab_bank, liab_price, opt_quote_bank_and_price) =
let (liab_bank, liab_oracle_price, opt_quote_bank_and_price) =
account_retriever.banks_mut_and_oracles(liab_token_index, QUOTE_TOKEN_INDEX)?;
let mut liab_deposit_index = liab_bank.deposit_index;
let liab_borrow_index = liab_bank.borrow_index;
@ -123,7 +123,7 @@ pub fn token_liq_bankruptcy(
} else {
cm!(I80F48::ONE + liab_bank.liquidation_fee)
};
let liab_price_adjusted = cm!(liab_price * liab_fee_factor);
let liab_price_adjusted = cm!(liab_oracle_price * liab_fee_factor);
let liab_transfer_unrounded = remaining_liab_loss.min(max_liab_transfer);
@ -193,8 +193,12 @@ pub fn token_liq_bankruptcy(
// 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, loan_origination_fee) =
liab_bank.withdraw_with_fee(liqor_liab, liab_transfer, now_ts, liab_price)?;
let (liqor_liab_active, loan_origination_fee) = liab_bank.withdraw_with_fee(
liqor_liab,
liab_transfer,
now_ts,
liab_oracle_price,
)?;
// liqor liab
emit!(TokenBalanceLog {
@ -307,10 +311,10 @@ pub fn token_liq_bankruptcy(
.adjust_token_balance(&liab_bank, cm!(end_liab_native - initial_liab_native))?;
// Check liqee health again
let liqee_init_health = liqee_health_cache.health(HealthType::Init);
let liqee_liq_end_health = liqee_health_cache.health(HealthType::LiquidationEnd);
liqee
.fixed
.maybe_recover_from_being_liquidated(liqee_init_health);
.maybe_recover_from_being_liquidated(liqee_liq_end_health);
if !liqee_liab_active {
liqee.deactivate_token_position_and_log(liqee_raw_token_index, ctx.accounts.liqee.key());
@ -322,7 +326,7 @@ pub fn token_liq_bankruptcy(
liqor: ctx.accounts.liqor.key(),
liab_token_index,
initial_liab_native: initial_liab_native.to_bits(),
liab_price: liab_price.to_bits(),
liab_price: liab_oracle_price.to_bits(),
insurance_token_index: QUOTE_TOKEN_INDEX,
insurance_transfer: insurance_transfer_i80f48.to_bits(),
socialized_loss: socialized_loss.to_bits(),

View File

@ -67,7 +67,7 @@ pub fn token_liq_with_token(
// Initial liqee health check
let mut liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever)
.context("create liqee health cache")?;
let liqee_init_health = liqee_health_cache.health(HealthType::Init);
let liqee_liq_end_health = liqee_health_cache.health(HealthType::LiquidationEnd);
liqee_health_cache.require_after_phase1_liquidation()?;
if !liqee.check_liquidatable(&liqee_health_cache)? {
@ -88,7 +88,7 @@ pub fn token_liq_with_token(
&mut liqee.borrow_mut(),
ctx.accounts.liqee.key(),
&mut liqee_health_cache,
liqee_init_health,
liqee_liq_end_health,
now_ts,
max_liab_transfer,
)?;
@ -112,7 +112,7 @@ pub(crate) fn liquidation_action(
liqee: &mut MangoAccountRefMut,
liqee_key: Pubkey,
liqee_health_cache: &mut HealthCache,
liqee_init_health: I80F48,
liqee_liq_end_health: I80F48,
now_ts: u64,
max_liab_transfer: I80F48,
) -> Result<()> {
@ -154,43 +154,43 @@ pub(crate) fn liquidation_action(
let init_asset_weight = asset_bank.init_asset_weight;
let init_liab_weight = liab_bank.init_liab_weight;
// The price the health computation uses for a liability of one native liab token
let liab_init_liab_price = liqee_health_cache
// The price the LiquidationEnd health computation uses for a liability of one native liab token
let liab_liq_end_price = liqee_health_cache
.token_info(liab_token_index)
.unwrap()
.prices
.liab(HealthType::Init);
.liab(HealthType::LiquidationEnd);
// Health price for an asset of one native asset token
let asset_init_asset_price = liqee_health_cache
let asset_liq_end_price = liqee_health_cache
.token_info(asset_token_index)
.unwrap()
.prices
.asset(HealthType::Init);
.asset(HealthType::LiquidationEnd);
// 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 * lilp health gain from reducing liabs
// - y * iaw * aiap health loss from paying asset
// + x * ilw * llep health gain from reducing liabs
// - y * iaw * alep health loss from paying asset
// = 0
// where
// ilw = init_liab_weight,
// lilp = liab_init_liab_price,
// llep = liab_liq_end_price,
// lopa = liab_oracle_price_adjusted, (see above)
// iap = init_asset_weight,
// aiap = asset_init_asset_price,
// alep = asset_liq_end_price,
// aop = asset_oracle_price
// ff = fee_factor
// and the asset cost of getting x native units of liab is:
// y = x * lopa / aop (native asset tokens, see above)
//
// Result: x = -init_health / (ilw * lilp - iaw * lopa * aiap / aop)
let liab_needed = cm!(-liqee_init_health
/ (liab_init_liab_price * init_liab_weight
// Result: x = -init_health / (ilw * llep - iaw * lopa * alep / aop)
let liab_needed = cm!(-liqee_liq_end_health
/ (liab_liq_end_price * init_liab_weight
- liab_oracle_price_adjusted
* init_asset_weight
* (asset_init_asset_price / asset_oracle_price)));
* (asset_liq_end_price / asset_oracle_price)));
// How much liab can we get at most for the asset balance?
let liab_possible = cm!(liqee_asset_native * asset_oracle_price / liab_oracle_price_adjusted);
@ -316,10 +316,10 @@ pub(crate) fn liquidation_action(
}
// Check liqee health again
let liqee_init_health = liqee_health_cache.health(HealthType::Init);
let liqee_liq_end_health = liqee_health_cache.health(HealthType::LiquidationEnd);
liqee
.fixed
.maybe_recover_from_being_liquidated(liqee_init_health);
.maybe_recover_from_being_liquidated(liqee_liq_end_health);
emit!(TokenLiqWithTokenLog {
mango_group: liqee.fixed.group,
@ -331,7 +331,8 @@ pub(crate) fn liquidation_action(
liab_transfer: liab_transfer.to_bits(),
asset_price: asset_oracle_price.to_bits(),
liab_price: liab_oracle_price.to_bits(),
bankruptcy: !liqee_health_cache.has_phase2_liquidatable() & liqee_init_health.is_negative()
bankruptcy: !liqee_health_cache.has_phase2_liquidatable()
& liqee_liq_end_health.is_negative()
});
Ok(())
@ -413,7 +414,7 @@ mod tests {
let mut liqee_health_cache =
health::new_health_cache(&setup.liqee.borrow(), &retriever).unwrap();
let liqee_init_health = liqee_health_cache.health(HealthType::Init);
let liqee_liq_end_health = liqee_health_cache.health(HealthType::LiquidationEnd);
liquidation_action(
&mut retriever,
@ -424,7 +425,7 @@ mod tests {
&mut setup.liqee.borrow_mut(),
Pubkey::new_unique(),
&mut liqee_health_cache,
liqee_init_health,
liqee_liq_end_health,
0,
max_liab_transfer,
)?;
@ -452,14 +453,19 @@ mod tests {
};
}
// Check that stable price and weight scaling does not affect liquidation targets
#[test]
fn test_liq_with_token_stable_price() {
let mut setup = TestSetup::new();
{
let ab = setup.asset_bank.data();
ab.stable_price_model.stable_price = 0.5;
ab.deposit_weight_scale_start_quote = 505.0;
let lb = setup.liab_bank.data();
lb.stable_price_model.stable_price = 1.25;
lb.borrow_weight_scale_start_quote = 3.75;
lb.init_liab_weight = I80F48::from_num(1.4);
lb.maint_liab_weight = I80F48::from_num(1.2);
}
{
let asset_bank = setup.asset_bank.data();
@ -492,7 +498,7 @@ mod tests {
liab_bank
.change_without_fee(
liab_p(&mut setup.liqee),
I80F48::from_num(-5.0),
I80F48::from_num(-9.0),
0,
I80F48::from(1),
)
@ -500,16 +506,24 @@ mod tests {
}
let hc = setup.liqee_health_cache();
assert_eq_f!(hc.health(HealthType::Init), 10.0 * 0.5 - 5.0 * 1.25, 0.1);
let asset_scale = 505.0 / 1010.0;
let liab_scale = 9.0 * 1.25 / 3.75;
assert_eq_f!(
hc.health(HealthType::Init),
10.0 * 0.5 * asset_scale - 9.0 * 1.25 * 1.4 * liab_scale,
0.1
);
assert_eq_f!(hc.health(HealthType::LiquidationEnd), 10.0 - 9.0 * 1.4, 0.1);
assert_eq_f!(hc.health(HealthType::Maint), 10.0 - 9.0 * 1.2, 0.1);
let mut result = setup.run(I80F48::from(100)).unwrap();
let liqee_asset = asset_p(&mut result.liqee);
assert_eq_f!(liqee_asset.native(&result.asset_bank.data()), 8.335, 0.01);
assert_eq_f!(liqee_asset.native(&result.asset_bank.data()), 3.5, 0.01);
let liqee_liab = liab_p(&mut result.liqee);
assert_eq_f!(liqee_liab.native(&result.liab_bank.data()), -3.335, 0.01);
assert_eq_f!(liqee_liab.native(&result.liab_bank.data()), -2.5, 0.01);
let hc = result.liqee_health_cache();
assert_eq_f!(hc.health(HealthType::Init), 0.0, 0.01);
assert_eq_f!(hc.health(HealthType::LiquidationEnd), 0.0, 0.01);
}
}

View File

@ -79,8 +79,8 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
new_fixed_order_account_retriever(ctx.remaining_accounts, &account.borrow())?;
let health_cache =
new_health_cache(&account.borrow(), &retriever).context("pre-withdraw init health")?;
let pre_health = account.check_health_pre(&health_cache)?;
Some((health_cache, pre_health))
let pre_init_health = account.check_health_pre(&health_cache)?;
Some((health_cache, pre_init_health))
} else {
None
};
@ -159,9 +159,9 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
//
// Health check
//
if let Some((mut health_cache, pre_health)) = pre_health_opt {
if let Some((mut health_cache, pre_init_health)) = pre_health_opt {
health_cache.adjust_token_balance(&bank, cm!(native_position_after - native_position))?;
account.check_health_post(&health_cache, pre_health)?;
account.check_health_post(&health_cache, pre_init_health)?;
}
//

View File

@ -247,10 +247,10 @@ impl MangoAccountFixed {
self.in_health_region = u8::from(b);
}
pub fn maybe_recover_from_being_liquidated(&mut self, init_health: I80F48) -> bool {
pub fn maybe_recover_from_being_liquidated(&mut self, liq_end_health: I80F48) -> bool {
// This is used as threshold to flip flag instead of 0 because of dust issues
let one_native_usdc = I80F48::ONE;
if self.being_liquidated() && init_health > -one_native_usdc {
if self.being_liquidated() && liq_end_health > -one_native_usdc {
self.set_being_liquidated(false);
true
} else {
@ -941,30 +941,37 @@ impl<
}
pub fn check_health_pre(&mut self, health_cache: &HealthCache) -> Result<I80F48> {
let pre_health = health_cache.health(HealthType::Init);
msg!("pre_health: {}", pre_health);
let pre_init_health = health_cache.health(HealthType::Init);
msg!("pre_init_health: {}", pre_init_health);
// We can skip computing LiquidationEnd health if Init health > 0, because
// LiquidationEnd health >= Init health.
self.fixed_mut()
.maybe_recover_from_being_liquidated(pre_health);
.maybe_recover_from_being_liquidated(pre_init_health);
if self.fixed().being_liquidated() {
let liq_end_health = health_cache.health(HealthType::LiquidationEnd);
self.fixed_mut()
.maybe_recover_from_being_liquidated(liq_end_health);
}
require!(
!self.fixed().being_liquidated(),
MangoError::BeingLiquidated
);
Ok(pre_health)
Ok(pre_init_health)
}
pub fn check_health_post(
&mut self,
health_cache: &HealthCache,
pre_health: I80F48,
pre_init_health: I80F48,
) -> Result<()> {
let post_health = health_cache.health(HealthType::Init);
msg!("post_health: {}", post_health);
let post_init_health = health_cache.health(HealthType::Init);
msg!("post_init_health: {}", post_init_health);
require!(
post_health >= 0 || post_health > pre_health,
post_init_health >= 0 || post_init_health > pre_init_health,
MangoError::HealthMustBePositiveOrIncrease
);
self.fixed_mut()
.maybe_recover_from_being_liquidated(post_health);
Ok(())
}
@ -973,10 +980,10 @@ impl<
// we want to allow liquidation to continue until init_health is positive,
// to prevent constant oscillation between the two states
if self.being_liquidated() {
let init_health = health_cache.health(HealthType::Init);
let liq_end_health = health_cache.health(HealthType::LiquidationEnd);
if self
.fixed_mut()
.maybe_recover_from_being_liquidated(init_health)
.maybe_recover_from_being_liquidated(liq_end_health)
{
msg!("Liqee init_health above zero");
return Ok(false);

View File

@ -356,7 +356,7 @@ pub async fn check_prev_instruction_post_health(solana: &SolanaCookie, account:
let logs = solana.program_log();
let post_health_str = logs
.iter()
.find_map(|line| line.strip_prefix("post_health: "))
.find_map(|line| line.strip_prefix("post_init_health: "))
.unwrap();
let post_health = post_health_str.parse::<f64>().unwrap();

View File

@ -39,8 +39,8 @@ export interface BankForHealth {
price: I80F48;
stablePriceModel: StablePriceModel;
scaledInitAssetWeight(): I80F48;
scaledInitLiabWeight(): I80F48;
scaledInitAssetWeight(price: I80F48): I80F48;
scaledInitLiabWeight(price: I80F48): I80F48;
}
export class Bank implements BankForHealth {
@ -309,8 +309,8 @@ export class Bank implements BankForHealth {
);
}
scaledInitAssetWeight(): I80F48 {
const depositsQuote = this.nativeDeposits().mul(this.price);
scaledInitAssetWeight(price: I80F48): I80F48 {
const depositsQuote = this.nativeDeposits().mul(price);
if (
this.depositWeightScaleStartQuote >= Number.MAX_SAFE_INTEGER ||
depositsQuote.lte(I80F48.fromNumber(this.depositWeightScaleStartQuote))
@ -322,8 +322,8 @@ export class Bank implements BankForHealth {
);
}
scaledInitLiabWeight(): I80F48 {
const borrowsQuote = this.nativeBorrows().mul(this.price);
scaledInitLiabWeight(price: I80F48): I80F48 {
const borrowsQuote = this.nativeBorrows().mul(price);
if (
this.borrowWeightScaleStartQuote >= Number.MAX_SAFE_INTEGER ||
borrowsQuote.lte(I80F48.fromNumber(this.borrowWeightScaleStartQuote))

View File

@ -1165,14 +1165,22 @@ export class Prices {
constructor(public oracle: I80F48, public stable: I80F48) {}
public liab(healthType: HealthType | undefined): I80F48 {
if (healthType === HealthType.maint || healthType === undefined) {
if (
healthType === HealthType.maint ||
healthType === HealthType.liquidationEnd ||
healthType === undefined
) {
return this.oracle;
}
return this.oracle.max(this.stable);
}
public asset(healthType: HealthType | undefined): I80F48 {
if (healthType === HealthType.maint || healthType === undefined) {
if (
healthType === HealthType.maint ||
healthType === HealthType.liquidationEnd ||
healthType === undefined
) {
return this.oracle;
}
return this.oracle.min(this.stable);
@ -1184,8 +1192,10 @@ export class TokenInfo {
public tokenIndex: TokenIndex,
public maintAssetWeight: I80F48,
public initAssetWeight: I80F48,
public initScaledAssetWeight: I80F48,
public maintLiabWeight: I80F48,
public initLiabWeight: I80F48,
public initScaledLiabWeight: I80F48,
public prices: Prices,
public balanceNative: I80F48,
) {}
@ -1195,8 +1205,10 @@ export class TokenInfo {
dto.tokenIndex as TokenIndex,
I80F48.from(dto.maintAssetWeight),
I80F48.from(dto.initAssetWeight),
I80F48.from(dto.initScaledAssetWeight),
I80F48.from(dto.maintLiabWeight),
I80F48.from(dto.initLiabWeight),
I80F48.from(dto.initScaledLiabWeight),
new Prices(
I80F48.from(dto.prices.oracle),
I80F48.from(dto.prices.stable),
@ -1206,30 +1218,44 @@ export class TokenInfo {
}
static fromBank(bank: BankForHealth, nativeBalance?: I80F48): TokenInfo {
const p = new Prices(
bank.price,
I80F48.fromNumber(bank.stablePriceModel.stablePrice),
);
// Use the liab price for computing weight scaling, because it's pessimistic and
// causes the most unfavorable scaling.
const liabPrice = p.liab(HealthType.init);
return new TokenInfo(
bank.tokenIndex,
bank.maintAssetWeight,
bank.scaledInitAssetWeight(),
bank.initAssetWeight,
bank.scaledInitAssetWeight(liabPrice),
bank.maintLiabWeight,
bank.scaledInitLiabWeight(),
new Prices(
bank.price,
I80F48.fromNumber(bank.stablePriceModel.stablePrice),
),
bank.initLiabWeight,
bank.scaledInitLiabWeight(liabPrice),
p,
nativeBalance ? nativeBalance : ZERO_I80F48(),
);
}
assetWeight(healthType: HealthType): I80F48 {
return healthType == HealthType.init
? this.initAssetWeight
: this.maintAssetWeight;
if (healthType == HealthType.init) {
return this.initScaledAssetWeight;
} else if (healthType == HealthType.liquidationEnd) {
return this.initAssetWeight;
}
// healthType == HealthType.maint
return this.maintAssetWeight;
}
liabWeight(healthType: HealthType): I80F48 {
return healthType == HealthType.init
? this.initLiabWeight
: this.maintLiabWeight;
if (healthType == HealthType.init) {
return this.initScaledLiabWeight;
} else if (healthType == HealthType.liquidationEnd) {
return this.initLiabWeight;
}
// healthType == HealthType.maint
return this.maintLiabWeight;
}
healthContribution(healthType?: HealthType): I80F48 {
@ -1508,7 +1534,7 @@ export class PerpInfo {
const contrib = this.unweightedHealthContribution(healthType);
if (contrib.gt(ZERO_I80F48())) {
const assetWeight =
healthType == HealthType.init
healthType == HealthType.init || healthType == HealthType.liquidationEnd
? this.initOverallAssetWeight
: this.maintOverallAssetWeight;
return assetWeight.mul(contrib);
@ -1527,24 +1553,31 @@ export class PerpInfo {
);
let weight, basePrice;
if (healthType == HealthType.init) {
if (
healthType == HealthType.init ||
healthType == HealthType.liquidationEnd
) {
if (netBaseNative.isNeg()) {
weight = pi.initBaseLiabWeight;
basePrice = pi.prices.liab(healthType);
} else {
weight = pi.initBaseAssetWeight;
basePrice = pi.prices.asset(healthType);
}
} else {
}
// healthType == HealthType.maint
else {
if (netBaseNative.isNeg()) {
weight = pi.maintBaseLiabWeight;
basePrice = pi.prices.liab(healthType);
} else {
weight = pi.maintBaseAssetWeight;
basePrice = pi.prices.asset(healthType);
}
}
if (netBaseNative.isNeg()) {
basePrice = pi.prices.liab(healthType);
} else {
basePrice = pi.prices.asset(healthType);
}
// Total value of the order-execution adjusted base position
const baseHealth = netBaseNative.mul(weight).mul(basePrice);
@ -1615,8 +1648,10 @@ export class TokenInfoDto {
tokenIndex: number;
maintAssetWeight: I80F48Dto;
initAssetWeight: I80F48Dto;
initScaledAssetWeight: I80F48Dto;
maintLiabWeight: I80F48Dto;
initLiabWeight: I80F48Dto;
initScaledLiabWeight: I80F48Dto;
prices: { oracle: I80F48Dto; stable: I80F48Dto };
balanceNative: I80F48Dto;
@ -1624,16 +1659,20 @@ export class TokenInfoDto {
tokenIndex: number,
maintAssetWeight: I80F48Dto,
initAssetWeight: I80F48Dto,
initScaledAssetWeight: I80F48Dto,
maintLiabWeight: I80F48Dto,
initLiabWeight: I80F48Dto,
initScaledLiabWeight: I80F48Dto,
prices: { oracle: I80F48Dto; stable: I80F48Dto },
balanceNative: I80F48Dto,
) {
this.tokenIndex = tokenIndex;
this.maintAssetWeight = maintAssetWeight;
this.initAssetWeight = initAssetWeight;
this.initScaledAssetWeight = initScaledAssetWeight;
this.maintLiabWeight = maintLiabWeight;
this.initLiabWeight = initLiabWeight;
this.initScaledLiabWeight = initScaledLiabWeight;
this.prices = prices;
this.balanceNative = balanceNative;
}

View File

@ -1580,4 +1580,5 @@ export class PerpOoDto {
export class HealthType {
static maint = { maint: {} };
static init = { init: {} };
static liquidationEnd = { liquidationEnd: {} };
}

View File

@ -4974,6 +4974,12 @@ export type MangoV4 = {
"defined": "I80F48"
}
},
{
"name": "initScaledAssetWeight",
"type": {
"defined": "I80F48"
}
},
{
"name": "maintLiabWeight",
"type": {
@ -4986,6 +4992,12 @@ export type MangoV4 = {
"defined": "I80F48"
}
},
{
"name": "initScaledLiabWeight",
"type": {
"defined": "I80F48"
}
},
{
"name": "prices",
"type": {
@ -6383,12 +6395,20 @@ export type MangoV4 = {
{
"name": "HealthType",
"docs": [
"There are two types of health, initial health used for opening new positions and maintenance",
"health used for liquidations. They are both calculated as a weighted sum of the assets",
"minus the liabilities but the maint. health uses slightly larger weights for assets and",
"slightly smaller weights for the liabilities. Zero is used as the bright line for both",
"i.e. if your init health falls below zero, you cannot open new positions and if your maint. health",
"falls below zero you will be liquidated."
"There are three types of health:",
"- initial health (\"init\"): users can only open new positions if it's >= 0",
"- maintenance health (\"maint\"): users get liquidated if it's < 0",
"- liquidation end health: once liquidation started (see being_liquidated), it",
"only stops once this is >= 0",
"",
"The ordering is",
"init health <= liquidation end health <= maint health",
"",
"The different health types are realized by using different weights and prices:",
"- init health: init weights with scaling, stable-price adjusted prices",
"- liq end health: init weights without scaling, oracle prices",
"- maint health: maint weights, oracle prices",
""
],
"type": {
"kind": "enum",
@ -6398,6 +6418,9 @@ export type MangoV4 = {
},
{
"name": "Maint"
},
{
"name": "LiquidationEnd"
}
]
}
@ -13292,6 +13315,12 @@ export const IDL: MangoV4 = {
"defined": "I80F48"
}
},
{
"name": "initScaledAssetWeight",
"type": {
"defined": "I80F48"
}
},
{
"name": "maintLiabWeight",
"type": {
@ -13304,6 +13333,12 @@ export const IDL: MangoV4 = {
"defined": "I80F48"
}
},
{
"name": "initScaledLiabWeight",
"type": {
"defined": "I80F48"
}
},
{
"name": "prices",
"type": {
@ -14701,12 +14736,20 @@ export const IDL: MangoV4 = {
{
"name": "HealthType",
"docs": [
"There are two types of health, initial health used for opening new positions and maintenance",
"health used for liquidations. They are both calculated as a weighted sum of the assets",
"minus the liabilities but the maint. health uses slightly larger weights for assets and",
"slightly smaller weights for the liabilities. Zero is used as the bright line for both",
"i.e. if your init health falls below zero, you cannot open new positions and if your maint. health",
"falls below zero you will be liquidated."
"There are three types of health:",
"- initial health (\"init\"): users can only open new positions if it's >= 0",
"- maintenance health (\"maint\"): users get liquidated if it's < 0",
"- liquidation end health: once liquidation started (see being_liquidated), it",
"only stops once this is >= 0",
"",
"The ordering is",
"init health <= liquidation end health <= maint health",
"",
"The different health types are realized by using different weights and prices:",
"- init health: init weights with scaling, stable-price adjusted prices",
"- liq end health: init weights without scaling, oracle prices",
"- maint health: maint weights, oracle prices",
""
],
"type": {
"kind": "enum",
@ -14716,6 +14759,9 @@ export const IDL: MangoV4 = {
},
{
"name": "Maint"
},
{
"name": "LiquidationEnd"
}
]
}