Merge branch 'dev'

This commit is contained in:
microwavedcola1 2022-08-12 08:31:20 +02:00
commit 00c533d9d7
29 changed files with 143 additions and 212 deletions

View File

@ -108,11 +108,12 @@ pub fn maybe_liquidate_account(
let quote_token_index = 0;
let account = account_fetcher.fetch_mango_account(pubkey)?;
let maint_health = new_health_cache_(&mango_client.context, account_fetcher, &account)
.expect("always ok")
.health(HealthType::Maint);
let health_cache =
new_health_cache_(&mango_client.context, account_fetcher, &account).expect("always ok");
let maint_health = health_cache.health(HealthType::Maint);
let is_bankrupt = !health_cache.has_liquidatable_assets();
if maint_health >= 0 && !account.is_bankrupt() {
if maint_health >= 0 && !is_bankrupt {
return Ok(false);
}
@ -121,16 +122,17 @@ pub fn maybe_liquidate_account(
pubkey,
account.fixed.owner,
maint_health,
account.is_bankrupt(),
is_bankrupt,
);
// Fetch a fresh account and re-compute
// This is -- unfortunately -- needed because the websocket streams seem to not
// be great at providing timely updates to the account data.
let account = account_fetcher.fetch_fresh_mango_account(pubkey)?;
let maint_health = new_health_cache_(&mango_client.context, account_fetcher, &account)
.expect("always ok")
.health(HealthType::Maint);
let health_cache =
new_health_cache_(&mango_client.context, account_fetcher, &account).expect("always ok");
let maint_health = health_cache.health(HealthType::Maint);
let is_bankrupt = !health_cache.has_liquidatable_assets();
// find asset and liab tokens
let mut tokens = account
@ -172,7 +174,7 @@ pub fn maybe_liquidate_account(
};
// try liquidating
let txsig = if account.is_bankrupt() {
let txsig = if is_bankrupt {
if tokens.is_empty() {
anyhow::bail!("mango account {}, is bankrupt has no active tokens", pubkey);
}

View File

@ -33,7 +33,6 @@ pub fn account_close(ctx: Context<AccountClose>) -> Result<()> {
// don't perform checks if group is just testing
if group.testing == 0 {
require!(!account.fixed.being_liquidated(), MangoError::SomeError);
require!(!account.fixed.is_bankrupt(), MangoError::SomeError);
require_eq!(account.fixed.delegate, Pubkey::default());
for ele in account.token_iter() {
require_eq!(ele.is_active(), false);

View File

@ -48,7 +48,6 @@ pub fn account_create(
account.fixed.bump = *ctx.bumps.get("account").ok_or(MangoError::SomeError)?;
account.fixed.delegate = Pubkey::default();
account.fixed.set_being_liquidated(false);
account.fixed.set_bankrupt(false);
account.expand_dynamic_content(token_count, serum3_count, perp_count, perp_oo_count)?;

View File

@ -184,8 +184,6 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>(
let mut account = ctx.accounts.account.load_mut()?;
let group = account.fixed.group;
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
// Find index at which vaults start
let vaults_index = ctx
.remaining_accounts

View File

@ -90,23 +90,27 @@ pub fn liq_token_bankruptcy(
.is_owner_or_delegate(ctx.accounts.liqor_owner.key()),
MangoError::SomeError
);
require!(!liqor.fixed.is_bankrupt(), MangoError::IsBankrupt);
let mut liqee = ctx.accounts.liqee.load_mut()?;
require!(liqee.fixed.is_bankrupt(), MangoError::IsBankrupt);
let liab_bank = bank_ais[0].load::<Bank>()?;
let liab_deposit_index = liab_bank.deposit_index;
let (liqee_liab, liqee_raw_token_index) = liqee.token_get_mut(liab_token_index)?;
let mut remaining_liab_loss = -liqee_liab.native(&liab_bank);
require_gt!(remaining_liab_loss, I80F48::ZERO);
drop(liab_bank);
let mut account_retriever = ScanningAccountRetriever::new(health_ais, group_pk)?;
// find insurance transfer amount
let mut liqee = ctx.accounts.liqee.load_mut()?;
let mut liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever)
.context("create liqee health cache")?;
require!(
!liqee_health_cache.has_liquidatable_assets(),
MangoError::IsNotBankrupt
);
liqee.fixed.set_being_liquidated(true);
let (liab_bank, liab_price, opt_quote_bank_and_price) =
account_retriever.banks_mut_and_oracles(liab_token_index, QUOTE_TOKEN_INDEX)?;
let liab_deposit_index = liab_bank.deposit_index;
let (liqee_liab, liqee_raw_token_index) = liqee.token_get_mut(liab_token_index)?;
let initial_liab_native = liqee_liab.native(&liab_bank);
let mut remaining_liab_loss = -initial_liab_native;
require_gt!(remaining_liab_loss, I80F48::ZERO);
// find insurance transfer amount
let liab_fee_factor = if liab_token_index == QUOTE_TOKEN_INDEX {
I80F48::ONE
} else {
@ -226,14 +230,16 @@ pub fn liq_token_bankruptcy(
liqee_liab_active = false;
}
// If the account has no more borrows then it's no longer bankrupt
// and should (always?) no longer be liquidated.
let account_retriever = ScanningAccountRetriever::new(health_ais, group_pk)?;
let liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever)?;
liqee.fixed.set_bankrupt(liqee_health_cache.has_borrows());
if !liqee.is_bankrupt() && liqee_health_cache.health(HealthType::Init) >= 0 {
liqee.fixed.set_being_liquidated(false);
}
let liab_bank = bank_ais[0].load::<Bank>()?;
let end_liab_native = liqee_liab.native(&liab_bank);
liqee_health_cache
.adjust_token_balance(liab_token_index, cm!(end_liab_native - initial_liab_native))?;
// Check liqee health again
let liqee_init_health = liqee_health_cache.health(HealthType::Init);
liqee
.fixed
.maybe_recover_from_being_liquidated(liqee_init_health);
if !liqee_liab_active {
liqee.token_deactivate(liqee_raw_token_index);

View File

@ -47,10 +47,8 @@ pub fn liq_token_with_token(
.is_owner_or_delegate(ctx.accounts.liqor_owner.key()),
MangoError::SomeError
);
require!(!liqor.fixed.is_bankrupt(), MangoError::IsBankrupt);
let mut liqee = ctx.accounts.liqee.load_mut()?;
require!(!liqee.fixed.is_bankrupt(), MangoError::IsBankrupt);
// Initial liqee health check
let mut liqee_health_cache = new_health_cache(&liqee.borrow(), &account_retriever)
@ -236,18 +234,10 @@ pub fn liq_token_with_token(
}
// Check liqee health again
let maint_health = liqee_health_cache.health(HealthType::Maint);
if maint_health < I80F48::ZERO {
liqee
.fixed
.set_bankrupt(!liqee_health_cache.has_liquidatable_assets());
} else {
let init_health = liqee_health_cache.health(HealthType::Init);
// this is equivalent to one native USDC or 1e-6 USDC
// This is used as threshold to flip flag instead of 0 because of dust issues
liqee.fixed.set_being_liquidated(init_health < -I80F48::ONE);
}
let liqee_init_health = liqee_health_cache.health(HealthType::Init);
liqee
.fixed
.maybe_recover_from_being_liquidated(liqee_init_health);
// Check liqor's health
let liqor_health = compute_health(&liqor.borrow(), HealthType::Init, &account_retriever)

View File

@ -31,8 +31,6 @@ pub fn perp_cancel_all_orders(ctx: Context<PerpCancelAllOrders>, limit: u8) -> R
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
let bids = ctx.accounts.bids.load_mut()?;
let asks = ctx.accounts.asks.load_mut()?;

View File

@ -36,8 +36,6 @@ pub fn perp_cancel_all_orders_by_side(
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
let bids = ctx.accounts.bids.load_mut()?;
let asks = ctx.accounts.asks.load_mut()?;

View File

@ -31,8 +31,6 @@ pub fn perp_cancel_order(ctx: Context<PerpCancelOrder>, order_id: i128) -> Resul
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let perp_market = ctx.accounts.perp_market.load_mut()?;
let bids = ctx.accounts.bids.load_mut()?;
let asks = ctx.accounts.asks.load_mut()?;

View File

@ -34,8 +34,6 @@ pub fn perp_cancel_order_by_client_order_id(
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let perp_market = ctx.accounts.perp_market.load_mut()?;
let bids = ctx.accounts.bids.load_mut()?;
let asks = ctx.accounts.asks.load_mut()?;

View File

@ -81,7 +81,6 @@ pub fn perp_place_order(
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let account_pk = ctx.accounts.account.key();
{

View File

@ -51,8 +51,6 @@ pub fn serum3_cancel_all_orders(ctx: Context<Serum3CancelAllOrders>, limit: u8)
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let serum_market = ctx.accounts.serum_market.load()?;
// Validate open_orders

View File

@ -64,8 +64,6 @@ pub fn serum3_cancel_order(
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
// Validate open_orders
require!(
account

View File

@ -41,8 +41,6 @@ pub fn serum3_close_open_orders(ctx: Context<Serum3CloseOpenOrders>) -> Result<(
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let serum_market = ctx.accounts.serum_market.load()?;
// Validate open_orders

View File

@ -53,8 +53,6 @@ pub fn serum3_create_open_orders(ctx: Context<Serum3CreateOpenOrders>) -> Result
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let serum_account = account.serum3_create(serum_market.market_index)?;
serum_account.open_orders = ctx.accounts.open_orders.key();
serum_account.base_token_index = serum_market.base_token_index;

View File

@ -70,7 +70,6 @@ pub fn serum3_liq_force_cancel_orders(
//
{
let account = ctx.accounts.account.load()?;
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let serum_market = ctx.accounts.serum_market.load()?;
// Validate open_orders

View File

@ -168,7 +168,6 @@ pub fn serum3_place_order(
account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()),
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
// Validate open_orders
require!(

View File

@ -78,8 +78,6 @@ pub fn serum3_settle_funds(ctx: Context<Serum3SettleFunds>) -> Result<()> {
MangoError::SomeError
);
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
// Validate open_orders
require!(
account

View File

@ -58,7 +58,6 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
// Get the account's position for that token index
let mut account = ctx.accounts.account.load_mut()?;
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let (position, raw_token_index, active_token_index) =
account.token_get_mut_or_create(token_index)?;
@ -94,12 +93,11 @@ pub fn token_deposit(ctx: Context<TokenDeposit>, amount: u64) -> Result<()> {
//
// Health computation
// TODO: This will be used to disable is_bankrupt or being_liquidated
// when health recovers sufficiently
//
let health = compute_health(&account.borrow(), HealthType::Init, &retriever)
.context("post-deposit init health")?;
msg!("health: {}", health);
account.fixed.maybe_recover_from_being_liquidated(health);
//
// Deactivate the position only after the health check because the user passed in

View File

@ -61,7 +61,6 @@ pub fn token_withdraw(ctx: Context<TokenWithdraw>, amount: u64, allow_borrow: bo
// Get the account's position for that token index
let mut account = ctx.accounts.account.load_mut()?;
require!(!account.fixed.is_bankrupt(), MangoError::IsBankrupt);
let (position, raw_token_index, active_token_index) =
account.token_get_mut_or_create(token_index)?;

View File

@ -16,7 +16,7 @@ use crate::util::checked_math as cm;
use super::MangoAccountRef;
const BANKRUPTCY_DUST_THRESHOLD: I80F48 = I80F48!(0.000001);
const ONE_NATIVE_USDC_IN_USD: I80F48 = I80F48!(0.000001);
/// This trait abstracts how to find accounts needed for the health computation.
///
@ -565,13 +565,14 @@ impl HealthCache {
}
pub fn has_liquidatable_assets(&self) -> bool {
let spot_liquidatable = self.token_infos.iter().any(|ti| {
ti.balance > BANKRUPTCY_DUST_THRESHOLD || ti.serum3_max_reserved.is_positive()
});
let spot_liquidatable = self
.token_infos
.iter()
.any(|ti| ti.balance.is_positive() || ti.serum3_max_reserved.is_positive());
let perp_liquidatable = self
.perp_infos
.iter()
.any(|p| p.base != 0 || p.quote > BANKRUPTCY_DUST_THRESHOLD);
.any(|p| p.base != 0 || p.quote > ONE_NATIVE_USDC_IN_USD);
spot_liquidatable || perp_liquidatable
}

View File

@ -54,8 +54,7 @@ pub struct MangoAccount {
/// This account cannot open new positions or borrow until `init_health >= 0`
being_liquidated: u8,
/// This account cannot do anything except go through `resolve_bankruptcy`
is_bankrupt: u8,
padding5: u8,
pub bump: u8,
@ -97,7 +96,7 @@ impl Default for MangoAccount {
owner: Pubkey::default(),
delegate: Pubkey::default(),
being_liquidated: 0,
is_bankrupt: 0,
padding5: 0,
account_num: 0,
bump: 0,
padding: Default::default(),
@ -193,7 +192,7 @@ pub struct MangoAccountFixed {
pub delegate: Pubkey,
pub account_num: u32,
being_liquidated: u8,
is_bankrupt: u8,
padding2: u8,
pub bump: u8,
pub padding: [u8; 1],
pub net_deposits: f32,
@ -214,14 +213,6 @@ impl MangoAccountFixed {
self.owner == ix_signer || self.delegate == ix_signer
}
pub fn is_bankrupt(&self) -> bool {
self.is_bankrupt != 0
}
pub fn set_bankrupt(&mut self, b: bool) {
self.is_bankrupt = if b { 1 } else { 0 };
}
pub fn being_liquidated(&self) -> bool {
self.being_liquidated != 0
}
@ -229,6 +220,14 @@ impl MangoAccountFixed {
pub fn set_being_liquidated(&mut self, b: bool) {
self.being_liquidated = if b { 1 } else { 0 };
}
pub fn maybe_recover_from_being_liquidated(&mut self, init_health: I80F48) {
// 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 {
self.set_being_liquidated(false);
}
}
}
impl DynamicAccountType for MangoAccount {
@ -522,10 +521,6 @@ impl<
self.fixed().being_liquidated()
}
pub fn is_bankrupt(&self) -> bool {
self.fixed().is_bankrupt()
}
pub fn borrow(&self) -> DynamicAccountRef<MangoAccount> {
DynamicAccount {
header: self.header(),

View File

@ -148,10 +148,9 @@ pub struct PerpPositions {
/// measured in native quote
pub quote_position_native: I80F48,
/// Tracks what the position is to calculate average entry & break even price
pub base_entry_lots: i64,
/// Tracks what the position is to calculate average entry & break even price
pub quote_entry_native: i64,
pub quote_exit_native: i64,
pub quote_running_native: i64,
/// Already settled funding
pub long_settled_funding: I80F48,
@ -185,7 +184,7 @@ impl std::fmt::Debug for PerpPositions {
.finish()
}
}
const_assert_eq!(size_of::<PerpPositions>(), 8 + 8 * 8 + 3 * 16 + 64);
const_assert_eq!(size_of::<PerpPositions>(), 8 + 7 * 8 + 3 * 16 + 64);
const_assert_eq!(size_of::<PerpPositions>() % 8, 0);
unsafe impl bytemuck::Pod for PerpPositions {}
@ -197,9 +196,8 @@ impl Default for PerpPositions {
market_index: PerpMarketIndex::MAX,
base_position_lots: 0,
quote_position_native: I80F48::ZERO,
base_entry_lots: 0,
quote_entry_native: 0,
quote_exit_native: 0,
quote_running_native: 0,
bids_base_lots: 0,
asks_base_lots: 0,
taker_base_lots: 0,
@ -273,26 +271,24 @@ impl PerpPositions {
}
let old_position = self.base_position_lots;
let is_increasing = old_position == 0 || old_position.signum() == base_change.signum();
self.quote_running_native = cm!(self.quote_running_native + quote_change);
match is_increasing {
true => {
self.quote_entry_native = cm!(self.quote_entry_native + quote_change);
self.base_entry_lots = cm!(self.base_entry_lots + base_change);
}
false => {
let new_position = cm!(old_position + base_change);
self.quote_exit_native = cm!(self.quote_exit_native + quote_change);
let is_overflow = old_position.signum() == -new_position.signum();
if new_position == 0 {
self.quote_entry_native = 0;
self.quote_exit_native = 0;
self.base_entry_lots = 0;
}
if is_overflow {
self.quote_entry_native = cm!(((new_position as f64) * (quote_change as f64)
/ (base_change as f64))
.round()) as i64;
self.quote_exit_native = 0;
self.base_entry_lots = new_position;
let changes_side = old_position.signum() == -new_position.signum();
self.quote_entry_native = if changes_side {
cm!(
((new_position as f64) * (quote_change as f64) / (base_change as f64))
.round()
) as i64
} else {
let remaining_frac =
(1f64 - (base_change.abs() as f64) / (old_position.abs() as f64)).max(0f64);
let initial_entry = self.quote_entry_native as f64;
(initial_entry * remaining_frac).round() as i64
}
}
}
@ -311,10 +307,10 @@ impl PerpPositions {
/// Calculate the average entry price of the position
pub fn get_avg_entry_price(&self) -> I80F48 {
if self.base_entry_lots == 0 {
if self.base_position_lots == 0 {
return I80F48::ZERO; // TODO: What should this actually return? Error? NaN?
}
(I80F48::from(self.quote_entry_native) / I80F48::from(self.base_entry_lots)).abs()
(I80F48::from(self.quote_entry_native) / I80F48::from(self.base_position_lots)).abs()
}
/// Calculate the break even price of the position
@ -322,9 +318,7 @@ impl PerpPositions {
if self.base_position_lots == 0 {
return I80F48::ZERO; // TODO: What should this actually return? Error? NaN?
}
(I80F48::from(self.quote_entry_native + self.quote_exit_native)
/ I80F48::from(self.base_position_lots))
.abs()
(I80F48::from(self.quote_running_native) / I80F48::from(self.base_position_lots)).abs()
}
}
@ -389,8 +383,7 @@ mod tests {
pos.base_position_lots = base_pos;
pos.quote_position_native = I80F48::from(quote_pos);
pos.quote_entry_native = entry_pos;
pos.quote_exit_native = 0;
pos.base_entry_lots = base_pos;
pos.quote_running_native = quote_pos;
pos
}
@ -441,6 +434,7 @@ mod tests {
// Go long 10 @ 10
pos.change_base_and_entry_positions(&mut market, 10, -100);
assert_eq!(pos.quote_entry_native, -100);
assert_eq!(pos.quote_running_native, -100);
assert_eq!(pos.get_avg_entry_price(), I80F48::from(10));
}
@ -451,6 +445,7 @@ mod tests {
// Go short 10 @ 10
pos.change_base_and_entry_positions(&mut market, -10, 100);
assert_eq!(pos.quote_entry_native, 100);
assert_eq!(pos.quote_running_native, 100);
assert_eq!(pos.get_avg_entry_price(), I80F48::from(10));
}
@ -461,6 +456,7 @@ mod tests {
// Go long 10 @ 30
pos.change_base_and_entry_positions(&mut market, 10, -300);
assert_eq!(pos.quote_entry_native, -400);
assert_eq!(pos.quote_running_native, -400);
assert_eq!(pos.get_avg_entry_price(), I80F48::from(20));
}
@ -471,6 +467,7 @@ mod tests {
// Go short 10 @ 10
pos.change_base_and_entry_positions(&mut market, -10, 300);
assert_eq!(pos.quote_entry_native, 400);
assert_eq!(pos.quote_running_native, 400);
assert_eq!(pos.get_avg_entry_price(), I80F48::from(20));
}
@ -479,10 +476,9 @@ mod tests {
let mut market = create_perp_market();
let mut pos = create_perp_position(-10, 100, 100);
// Go long 5 @ 50
pos.change_base_and_entry_positions(&mut market, 5, 250);
assert_eq!(pos.quote_entry_native, 100);
assert_eq!(pos.base_entry_lots, -10);
assert_eq!(pos.quote_exit_native, 250);
pos.change_base_and_entry_positions(&mut market, 5, -250);
assert_eq!(pos.quote_entry_native, 50);
assert_eq!(pos.quote_running_native, -150);
assert_eq!(pos.get_avg_entry_price(), I80F48::from(10)); // Entry price remains the same when decreasing
}
@ -491,10 +487,9 @@ mod tests {
let mut market = create_perp_market();
let mut pos = create_perp_position(10, -100, -100);
// Go short 5 @ 50
pos.change_base_and_entry_positions(&mut market, -5, -250);
assert_eq!(pos.quote_entry_native, -100);
assert_eq!(pos.base_entry_lots, 10);
assert_eq!(pos.quote_exit_native, -250);
pos.change_base_and_entry_positions(&mut market, -5, 250);
assert_eq!(pos.quote_entry_native, -50);
assert_eq!(pos.quote_running_native, 150);
assert_eq!(pos.get_avg_entry_price(), I80F48::from(10)); // Entry price remains the same when decreasing
}
@ -505,8 +500,7 @@ mod tests {
// Go short 10 @ 50
pos.change_base_and_entry_positions(&mut market, -10, 250);
assert_eq!(pos.quote_entry_native, 0);
assert_eq!(pos.quote_exit_native, 0);
assert_eq!(pos.base_entry_lots, 0);
assert_eq!(pos.quote_running_native, 150);
assert_eq!(pos.get_avg_entry_price(), I80F48::from(0)); // Entry price zero when no position
}
@ -517,8 +511,7 @@ mod tests {
// Go long 10 @ 50
pos.change_base_and_entry_positions(&mut market, 10, -250);
assert_eq!(pos.quote_entry_native, 0);
assert_eq!(pos.quote_exit_native, 0);
assert_eq!(pos.base_entry_lots, 0);
assert_eq!(pos.quote_running_native, -150);
assert_eq!(pos.get_avg_entry_price(), I80F48::from(0)); // Entry price zero when no position
}
@ -529,8 +522,7 @@ mod tests {
// Go short 15 @ 20
pos.change_base_and_entry_positions(&mut market, -15, 300);
assert_eq!(pos.quote_entry_native, 100);
assert_eq!(pos.quote_exit_native, 0);
assert_eq!(pos.base_entry_lots, -5);
assert_eq!(pos.quote_running_native, 200);
assert_eq!(pos.get_avg_entry_price(), I80F48::from(20)); // Entry price zero when no position
}
@ -541,8 +533,7 @@ mod tests {
// Go short 15 @ 20
pos.change_base_and_entry_positions(&mut market, 15, -300);
assert_eq!(pos.quote_entry_native, -100);
assert_eq!(pos.quote_exit_native, 0);
assert_eq!(pos.base_entry_lots, 5);
assert_eq!(pos.quote_running_native, -200);
assert_eq!(pos.get_avg_entry_price(), I80F48::from(20)); // Entry price zero when no position
}
@ -554,9 +545,8 @@ mod tests {
pos.change_base_and_entry_positions(&mut market, 11, -11 * 10_000);
// Sell 1 @ 12,000
pos.change_base_and_entry_positions(&mut market, -1, 12_000);
assert_eq!(pos.quote_entry_native, -11 * 10_000);
assert_eq!(pos.quote_exit_native, 12_000);
assert_eq!(pos.base_entry_lots, 11);
assert_eq!(pos.quote_entry_native, -10 * 10_000);
assert_eq!(pos.quote_running_native, -98_000);
assert_eq!(pos.base_position_lots, 10);
assert_eq!(pos.get_break_even_price(), I80F48::from(9_800)); // We made 2k on the trade, so we can sell our contract up to a loss of 200 each
}
@ -592,9 +582,7 @@ mod tests {
assert_eq!(pos.base_position_lots, 0);
// quote entry position should be 0
assert_eq!(pos.quote_entry_native, 0);
// quote exit should be 0
assert_eq!(pos.quote_exit_native, 0);
// base entry lots should be 0
assert_eq!(pos.base_entry_lots, 0);
// running quote should be 0
assert_eq!(pos.quote_running_native, 0);
}
}

View File

@ -220,7 +220,6 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
);
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
// eat collateral2, leaving the account bankrupt
send_tx(
@ -246,7 +245,6 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
);
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(liqee.is_bankrupt());
//
// TEST: socialize loss on borrow1 and 2
@ -272,7 +270,6 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
);
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(liqee.is_bankrupt());
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
// both bank's borrows were completely wiped: no one else borrowed
let borrow1_bank0: Bank = solana.get_account(borrow_token1.bank).await;
@ -299,7 +296,6 @@ async fn test_bankrupt_tokens_socialize_loss() -> Result<(), TransportError> {
);
let liqee = get_mango_account(solana, account).await;
assert!(!liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
assert!(account_position_closed(solana, account, borrow_token2.bank).await);
Ok(())
@ -531,7 +527,6 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
assert!(account_position_closed(solana, account, collateral_token1.bank).await);
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
// eat collateral2, leaving the account bankrupt
send_tx(
@ -552,7 +547,6 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
assert!(account_position_closed(solana, account, collateral_token2.bank).await,);
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(liqee.is_bankrupt());
//
// TEST: use the insurance fund to liquidate borrow1 and borrow2
@ -577,7 +571,6 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
.unwrap();
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(liqee.is_bankrupt());
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
assert_eq!(
solana.token_account_balance(insurance_vault).await,
@ -610,7 +603,6 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
.unwrap();
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(liqee.is_bankrupt());
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
assert_eq!(
account_position(solana, account, borrow_token2.bank).await,
@ -645,7 +637,6 @@ async fn test_bankrupt_tokens_insurance_fund() -> Result<(), TransportError> {
.unwrap();
let liqee = get_mango_account(solana, account).await;
assert!(!liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
assert!(account_position_closed(solana, account, borrow_token1.bank).await);
assert!(account_position_closed(solana, account, borrow_token2.bank).await);
assert_eq!(solana.token_account_balance(insurance_vault).await, 0);

View File

@ -406,7 +406,6 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
assert!(account_position_closed(solana, account, collateral_token2.bank).await,);
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
//
// TEST: liquidate the remaining borrow2 against collateral1,
@ -436,7 +435,6 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
);
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
//
// TEST: liquidate borrow1 with collateral1, but place a limit
@ -468,7 +466,6 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
);
let liqee = get_mango_account(solana, account).await;
assert!(liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
//
// TEST: liquidate borrow1 with collateral1, making the account healthy again
@ -503,7 +500,6 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
);
let liqee = get_mango_account(solana, account).await;
assert!(!liqee.being_liquidated());
assert!(!liqee.is_bankrupt());
//
// TEST: bankruptcy when collateral is dusted
@ -567,11 +563,12 @@ async fn test_liq_tokens_with_token() -> Result<(), TransportError> {
.await
.unwrap();
// Liqee's remaining collateral got dusted, only borrows remain: bankrupt
// Liqee's remaining collateral got dusted, only borrows remain
// but the borrow amount is so tiny, that being_liquidated is already switched off
let liqee = get_mango_account(solana, account).await;
assert_eq!(liqee.token_iter_active().count(), 1);
assert!(liqee.is_bankrupt());
assert!(liqee.being_liquidated());
assert!(account_position_f64(solana, account, borrow_token1.bank).await > -1.0);
assert!(!liqee.being_liquidated());
Ok(())
}

View File

@ -162,6 +162,22 @@ export class HealthCache {
);
}
simHealthRatioWithTokenPositionChanges(
group: Group,
tokenChanges: { tokenName: string; tokenAmount: number }[],
healthType: HealthType = HealthType.init,
): I80F48 {
const adjustedCache: HealthCache = _.cloneDeep(this);
for (const change of tokenChanges) {
const bank = group.banksMap.get(change.tokenName);
adjustedCache.tokenInfos[bank.tokenIndex].balance =
adjustedCache.tokenInfos[bank.tokenIndex].balance.add(
I80F48.fromNumber(change.tokenAmount).mul(bank.price),
);
}
return adjustedCache.healthRatio(healthType);
}
getMaxSourceForTokenSwap(
group: Group,
sourceTokenName: string,

View File

@ -27,7 +27,6 @@ export class MangoAccount {
name: number[];
delegate: PublicKey;
beingLiquidated: number;
isBankrupt: number;
accountNum: number;
bump: number;
netDeposits: number;
@ -46,7 +45,6 @@ export class MangoAccount {
obj.name,
obj.delegate,
obj.beingLiquidated,
obj.isBankrupt,
obj.accountNum,
obj.bump,
obj.netDeposits,
@ -67,7 +65,6 @@ export class MangoAccount {
name: number[],
public delegate: PublicKey,
beingLiquidated: number,
isBankrupt: number,
public accountNum: number,
bump: number,
netDeposits: number,
@ -248,34 +245,19 @@ export class MangoAccount {
}
/**
* Simulates new health after applying tokenChanges to the token positions. Useful to simulate health after a potential swap.
* Simulates new health ratio after applying tokenChanges to the token positions.
* e.g. useful to simulate health after a potential swap.
*/
simHealthWithTokenPositionChanges(
simHealthRatioWithTokenPositionChanges(
group: Group,
tokenChanges: { tokenName: string; tokenAmount: number }[],
healthType: HealthType = HealthType.init,
): I80F48 {
// This is a approximation of the easy case, where
// mango account has no token positions for tokens in changes list, or
// the change is in direction e.g. deposits for deposits, borrows for borrows, of existing token position.
// TODO: recompute entire health using components.
let initHealth = (this.accountData as MangoAccountData).initHealth;
for (const change of tokenChanges) {
const bank = group.banksMap.get(change.tokenName);
if (change.tokenAmount >= 0) {
initHealth = initHealth.add(
bank.initAssetWeight
.mul(I80F48.fromNumber(change.tokenAmount))
.mul(bank.price),
);
} else {
initHealth = initHealth.sub(
bank.initLiabWeight
.mul(I80F48.fromNumber(change.tokenAmount))
.mul(bank.price),
);
}
}
return initHealth;
return this.accountData.healthCache.simHealthRatioWithTokenPositionChanges(
group,
tokenChanges,
healthType,
);
}
/**

View File

@ -60,24 +60,21 @@ async function debugUser(client, group, mangoAccount) {
);
console.log(
'mangoAccount.simHealthWithTokenPositionChanges ' +
toUiDecimalsForQuote(
(
await mangoAccount.simHealthWithTokenPositionChanges(group, [
{
tokenName: 'USDC',
tokenAmount:
-20_000 *
Math.pow(10, group.banksMap.get('BTC')!.mintDecimals!),
},
{
tokenName: 'BTC',
tokenAmount:
1 * Math.pow(10, group.banksMap.get('BTC')!.mintDecimals!),
},
])
).toNumber(),
),
'mangoAccount.simHealthRatioWithTokenPositionChanges ' +
(
await mangoAccount.simHealthRatioWithTokenPositionChanges(group, [
{
tokenName: 'USDC',
tokenAmount:
-95_000 * Math.pow(10, group.banksMap.get('USDC')!.mintDecimals!),
},
{
tokenName: 'BTC',
tokenAmount:
4 * Math.pow(10, group.banksMap.get('BTC')!.mintDecimals!),
},
])
).toNumber(),
);
function getMaxSourceForTokenSwapWrapper(src, tgt) {

View File

@ -3007,10 +3007,7 @@ export type MangoV4 = {
"type": "u8"
},
{
"name": "isBankrupt",
"docs": [
"This account cannot do anything except go through `resolve_bankruptcy`"
],
"name": "padding5",
"type": "u8"
},
{
@ -8054,10 +8051,7 @@ export const IDL: MangoV4 = {
"type": "u8"
},
{
"name": "isBankrupt",
"docs": [
"This account cannot do anything except go through `resolve_bankruptcy`"
],
"name": "padding5",
"type": "u8"
},
{