From 5227daa0b88c5d43dcf7b247e11b2545f2a6132e Mon Sep 17 00:00:00 2001 From: Christian Kamm Date: Fri, 13 Jan 2023 08:13:15 +0100 Subject: [PATCH] Allow serum/perp order force cancelling on frozen accounts (#374) --- .../instructions/perp_liq_base_position.rs | 20 ++----------- .../perp_liq_force_cancel_orders.rs | 29 ++++++++---------- .../perp_liq_quote_and_bankruptcy.rs | 20 ++----------- .../serum3_liq_force_cancel_orders.rs | 30 +++++++++---------- .../src/instructions/token_liq_with_token.rs | 17 ++--------- programs/mango-v4/src/state/mango_account.rs | 24 +++++++++++++++ 6 files changed, 57 insertions(+), 83 deletions(-) diff --git a/programs/mango-v4/src/instructions/perp_liq_base_position.rs b/programs/mango-v4/src/instructions/perp_liq_base_position.rs index ccbba07db..7ccdc3f44 100644 --- a/programs/mango-v4/src/instructions/perp_liq_base_position.rs +++ b/programs/mango-v4/src/instructions/perp_liq_base_position.rs @@ -71,24 +71,8 @@ pub fn perp_liq_base_position( let liqee_init_health = liqee_health_cache.health(HealthType::Init); liqee_health_cache.require_after_phase1_liquidation()?; - // Once maint_health falls below 0, we want to start liquidating, - // we want to allow liquidation to continue until init_health is positive, - // to prevent constant oscillation between the two states - if liqee.being_liquidated() { - if liqee - .fixed - .maybe_recover_from_being_liquidated(liqee_init_health) - { - msg!("Liqee init_health above zero"); - return Ok(()); - } - } else { - let maint_health = liqee_health_cache.health(HealthType::Maint); - require!( - maint_health < I80F48::ZERO, - MangoError::HealthMustBeNegative - ); - liqee.fixed.set_being_liquidated(true); + if !liqee.check_liquidatable(&liqee_health_cache)? { + return Ok(()); } let mut perp_market = ctx.accounts.perp_market.load_mut()?; diff --git a/programs/mango-v4/src/instructions/perp_liq_force_cancel_orders.rs b/programs/mango-v4/src/instructions/perp_liq_force_cancel_orders.rs index 6d9785304..a4611f291 100644 --- a/programs/mango-v4/src/instructions/perp_liq_force_cancel_orders.rs +++ b/programs/mango-v4/src/instructions/perp_liq_force_cancel_orders.rs @@ -1,5 +1,4 @@ use anchor_lang::prelude::*; -use fixed::types::I80F48; use crate::error::*; use crate::health::*; @@ -47,22 +46,20 @@ pub fn perp_liq_force_cancel_orders( let health_cache = new_health_cache(&account.borrow(), &retriever).context("create health cache")?; - if account.being_liquidated() { - let init_health = health_cache.health(HealthType::Init); - if account - .fixed - .maybe_recover_from_being_liquidated(init_health) - { - msg!("Liqee init_health above zero"); - return Ok(()); + { + let result = account.check_liquidatable(&health_cache); + if account.fixed.is_operational() { + if !result? { + return Ok(()); + } + } else { + // Frozen accounts can always have their orders cancelled + if let Err(Error::AnchorError(ref inner)) = result { + if inner.error_code_number != MangoError::HealthMustBeNegative as u32 { + result?; + } + } } - } else { - let maint_health = health_cache.health(HealthType::Maint); - require!( - maint_health < I80F48::ZERO, - MangoError::HealthMustBeNegative - ); - account.fixed.set_being_liquidated(true); } health_cache diff --git a/programs/mango-v4/src/instructions/perp_liq_quote_and_bankruptcy.rs b/programs/mango-v4/src/instructions/perp_liq_quote_and_bankruptcy.rs index 0acbca735..e2c273677 100644 --- a/programs/mango-v4/src/instructions/perp_liq_quote_and_bankruptcy.rs +++ b/programs/mango-v4/src/instructions/perp_liq_quote_and_bankruptcy.rs @@ -118,24 +118,8 @@ pub fn perp_liq_quote_and_bankruptcy( let liqee_settle_health = liqee_health_cache.perp_settle_health(); liqee_health_cache.require_after_phase2_liquidation()?; - // Once maint_health falls below 0, we want to start liquidating, - // we want to allow liquidation to continue until init_health is positive, - // to prevent constant oscillation between the two states - if liqee.being_liquidated() { - if liqee - .fixed - .maybe_recover_from_being_liquidated(liqee_init_health) - { - msg!("Liqee init_health above zero"); - return Ok(()); - } - } else { - let maint_health = liqee_health_cache.health(HealthType::Maint); - require!( - maint_health < I80F48::ZERO, - MangoError::HealthMustBeNegative - ); - liqee.fixed.set_being_liquidated(true); + if !liqee.check_liquidatable(&liqee_health_cache)? { + return Ok(()); } // check positions exist/create them, done early for nicer error messages diff --git a/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs b/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs index 744ad64c6..6744b74d2 100644 --- a/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs +++ b/programs/mango-v4/src/instructions/serum3_liq_force_cancel_orders.rs @@ -1,6 +1,5 @@ use anchor_lang::prelude::*; use anchor_spl::token::{Token, TokenAccount}; -use fixed::types::I80F48; use crate::error::*; use crate::health::*; @@ -126,22 +125,21 @@ pub fn serum3_liq_force_cancel_orders( let health_cache = new_health_cache(&account.borrow(), &retriever).context("create health cache")?; - if account.being_liquidated() { - let init_health = health_cache.health(HealthType::Init); - if account - .fixed - .maybe_recover_from_being_liquidated(init_health) - { - msg!("Liqee init_health above zero"); - return Ok(()); + { + let result = account.check_liquidatable(&health_cache); + if account.fixed.is_operational() { + if !result? { + return Ok(()); + } + } else { + // Frozen accounts can always have their orders cancelled + if let Err(Error::AnchorError(ref inner)) = result { + if inner.error_code_number != MangoError::HealthMustBeNegative as u32 { + // propagate all unexpected errors + result?; + } + } } - } else { - let maint_health = health_cache.health(HealthType::Maint); - require!( - maint_health < I80F48::ZERO, - MangoError::HealthMustBeNegative - ); - account.fixed.set_being_liquidated(true); } health_cache diff --git a/programs/mango-v4/src/instructions/token_liq_with_token.rs b/programs/mango-v4/src/instructions/token_liq_with_token.rs index b63278f1e..fc8713bbf 100644 --- a/programs/mango-v4/src/instructions/token_liq_with_token.rs +++ b/programs/mango-v4/src/instructions/token_liq_with_token.rs @@ -69,21 +69,8 @@ pub fn token_liq_with_token( let init_health = liqee_health_cache.health(HealthType::Init); liqee_health_cache.require_after_phase1_liquidation()?; - // Once maint_health falls below 0, we want to start liquidating, - // we want to allow liquidation to continue until init_health is positive, - // to prevent constant oscillation between the two states - if liqee.being_liquidated() { - if liqee.fixed.maybe_recover_from_being_liquidated(init_health) { - msg!("Liqee init_health above zero"); - return Ok(()); - } - } else { - let maint_health = liqee_health_cache.health(HealthType::Maint); - require!( - maint_health < I80F48::ZERO, - MangoError::HealthMustBeNegative - ); - liqee.fixed.set_being_liquidated(true); + if !liqee.check_liquidatable(&liqee_health_cache)? { + return Ok(()); } // diff --git a/programs/mango-v4/src/state/mango_account.rs b/programs/mango-v4/src/state/mango_account.rs index 343f08a07..be8cfbd8f 100644 --- a/programs/mango-v4/src/state/mango_account.rs +++ b/programs/mango-v4/src/state/mango_account.rs @@ -968,6 +968,30 @@ impl< Ok(()) } + pub fn check_liquidatable(&mut self, health_cache: &HealthCache) -> Result { + // Once maint_health falls below 0, we want to start liquidating, + // 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); + if self + .fixed_mut() + .maybe_recover_from_being_liquidated(init_health) + { + msg!("Liqee init_health above zero"); + return Ok(false); + } + } else { + let maint_health = health_cache.health(HealthType::Maint); + require!( + maint_health < I80F48::ZERO, + MangoError::HealthMustBeNegative + ); + self.fixed_mut().set_being_liquidated(true); + } + Ok(true) + } + // writes length of tokens vec at appropriate offset so that borsh can infer the vector length // length used is that present in the header fn write_token_length(&mut self) {