Force close for serum3 market (#551)
* force close for serum3 market Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * always require admin Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * format Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * update client Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> --------- Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
parent
6fba1692e9
commit
497012042c
|
@ -11,7 +11,7 @@ pub struct PerpForceClosePosition<'info> {
|
|||
|
||||
#[account(
|
||||
mut,
|
||||
has_one = group,
|
||||
has_one = group,
|
||||
has_one = oracle,
|
||||
constraint = perp_market.load()?.is_force_close()
|
||||
)]
|
||||
|
|
|
@ -58,7 +58,7 @@ pub fn perp_liq_base_or_positive_pnl(
|
|||
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)? {
|
||||
if liqee.check_liquidatable(&liqee_health_cache)? != CheckLiquidatable::Liquidatable {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
|
|
@ -10,9 +10,10 @@ pub fn perp_liq_force_cancel_orders(
|
|||
limit: u8,
|
||||
) -> Result<()> {
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||
|
||||
//
|
||||
// Check liqee health if liquidation is allowed
|
||||
// Early return if if liquidation is not allowed or if market is not in force close
|
||||
//
|
||||
let mut health_cache = {
|
||||
let retriever =
|
||||
|
@ -21,17 +22,12 @@ pub fn perp_liq_force_cancel_orders(
|
|||
new_health_cache(&account.borrow(), &retriever).context("create health cache")?;
|
||||
|
||||
{
|
||||
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 !result.is_anchor_error_with_code(MangoError::HealthMustBeNegative.into()) {
|
||||
// Propagate unexpected errors
|
||||
result?;
|
||||
}
|
||||
let liquidatable = account.check_liquidatable(&health_cache)?;
|
||||
if account.fixed.is_operational()
|
||||
&& liquidatable != CheckLiquidatable::Liquidatable
|
||||
&& !perp_market.is_force_close()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,7 +38,6 @@ pub fn perp_liq_force_cancel_orders(
|
|||
// Cancel orders
|
||||
//
|
||||
{
|
||||
let mut perp_market = ctx.accounts.perp_market.load_mut()?;
|
||||
let mut book = Orderbook {
|
||||
bids: ctx.accounts.bids.load_mut()?,
|
||||
asks: ctx.accounts.asks.load_mut()?,
|
||||
|
|
|
@ -50,7 +50,7 @@ pub fn perp_liq_negative_pnl_or_bankruptcy(
|
|||
let liqee_settle_health = liqee_health_cache.perp_settle_health();
|
||||
liqee_health_cache.require_after_phase2_liquidation()?;
|
||||
|
||||
if !liqee.check_liquidatable(&liqee_health_cache)? {
|
||||
if liqee.check_liquidatable(&liqee_health_cache)? != CheckLiquidatable::Liquidatable {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ use anchor_lang::prelude::*;
|
|||
pub fn serum3_edit_market(
|
||||
ctx: Context<Serum3EditMarket>,
|
||||
reduce_only_opt: Option<bool>,
|
||||
force_close_opt: Option<bool>,
|
||||
) -> Result<()> {
|
||||
let mut serum3_market = ctx.accounts.market.load_mut()?;
|
||||
|
||||
|
@ -24,6 +25,16 @@ pub fn serum3_edit_market(
|
|||
}
|
||||
};
|
||||
|
||||
if let Some(force_close) = force_close_opt {
|
||||
msg!(
|
||||
"Force close: old - {:?}, new - {:?}",
|
||||
serum3_market.force_close,
|
||||
u8::from(force_close)
|
||||
);
|
||||
serum3_market.force_close = u8::from(force_close);
|
||||
require_group_admin = true;
|
||||
};
|
||||
|
||||
if require_group_admin {
|
||||
require!(
|
||||
group.admin == ctx.accounts.admin.key(),
|
||||
|
|
|
@ -51,7 +51,7 @@ pub fn serum3_liq_force_cancel_orders(
|
|||
}
|
||||
|
||||
//
|
||||
// Check liqee health if liquidation is allowed
|
||||
// Early return if if liquidation is not allowed or if market is not in force close
|
||||
//
|
||||
let mut health_cache = {
|
||||
let mut account = ctx.accounts.account.load_full_mut()?;
|
||||
|
@ -61,17 +61,12 @@ pub fn serum3_liq_force_cancel_orders(
|
|||
new_health_cache(&account.borrow(), &retriever).context("create health cache")?;
|
||||
|
||||
{
|
||||
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 !result.is_anchor_error_with_code(MangoError::HealthMustBeNegative.into()) {
|
||||
// Propagate unexpected errors
|
||||
result?;
|
||||
}
|
||||
let liquidatable = account.check_liquidatable(&health_cache)?;
|
||||
if account.fixed.is_operational()
|
||||
&& liquidatable != CheckLiquidatable::Liquidatable
|
||||
&& !serum_market.is_force_close()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ pub fn serum3_register_market(
|
|||
base_token_index: base_bank.token_index,
|
||||
quote_token_index: quote_bank.token_index,
|
||||
reduce_only: 0,
|
||||
force_close: 0,
|
||||
padding1: Default::default(),
|
||||
name: fill_from_str(&name)?,
|
||||
serum_program: ctx.accounts.serum_program.key(),
|
||||
|
|
|
@ -46,7 +46,7 @@ pub fn token_liq_with_token(
|
|||
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)? {
|
||||
if liqee.check_liquidatable(&liqee_health_cache)? != CheckLiquidatable::Liquidatable {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
|
|
@ -403,9 +403,10 @@ pub mod mango_v4 {
|
|||
pub fn serum3_edit_market(
|
||||
ctx: Context<Serum3EditMarket>,
|
||||
reduce_only_opt: Option<bool>,
|
||||
force_close_opt: Option<bool>,
|
||||
) -> Result<()> {
|
||||
#[cfg(feature = "enable-gpl")]
|
||||
instructions::serum3_edit_market(ctx, reduce_only_opt)?;
|
||||
instructions::serum3_edit_market(ctx, reduce_only_opt, force_close_opt)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,15 @@ const BORSH_VEC_PADDING_BYTES: usize = 4;
|
|||
const BORSH_VEC_SIZE_BYTES: usize = 4;
|
||||
const DEFAULT_MANGO_ACCOUNT_VERSION: u8 = 1;
|
||||
|
||||
// Return variants for check_liquidatable method, should be wrapped in a Result
|
||||
// for a future possiblity of returning any error
|
||||
#[derive(PartialEq)]
|
||||
pub enum CheckLiquidatable {
|
||||
NotLiquidatable,
|
||||
Liquidatable,
|
||||
BecameNotLiquidatable,
|
||||
}
|
||||
|
||||
// Mango Account
|
||||
// This struct definition is only for clients e.g. typescript, so that they can easily use out of the box
|
||||
// deserialization and not have to do custom deserialization
|
||||
|
@ -1028,7 +1037,7 @@ impl<
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_liquidatable(&mut self, health_cache: &HealthCache) -> Result<bool> {
|
||||
pub fn check_liquidatable(&mut self, health_cache: &HealthCache) -> Result<CheckLiquidatable> {
|
||||
// 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
|
||||
|
@ -1039,17 +1048,17 @@ impl<
|
|||
.maybe_recover_from_being_liquidated(liq_end_health)
|
||||
{
|
||||
msg!("Liqee init_health above zero");
|
||||
return Ok(false);
|
||||
return Ok(CheckLiquidatable::BecameNotLiquidatable);
|
||||
}
|
||||
} else {
|
||||
let maint_health = health_cache.health(HealthType::Maint);
|
||||
require!(
|
||||
maint_health < I80F48::ZERO,
|
||||
MangoError::HealthMustBeNegative
|
||||
);
|
||||
if maint_health >= I80F48::ZERO {
|
||||
msg!("Liqee is not liquidatable");
|
||||
return Ok(CheckLiquidatable::NotLiquidatable);
|
||||
}
|
||||
self.fixed_mut().set_being_liquidated(true);
|
||||
}
|
||||
Ok(true)
|
||||
return Ok(CheckLiquidatable::Liquidatable);
|
||||
}
|
||||
|
||||
// writes length of tokens vec at appropriate offset so that borsh can infer the vector length
|
||||
|
|
|
@ -16,7 +16,8 @@ pub struct Serum3Market {
|
|||
// ABI: Clients rely on this being at offset 42
|
||||
pub quote_token_index: TokenIndex,
|
||||
pub reduce_only: u8,
|
||||
pub padding1: [u8; 3],
|
||||
pub force_close: u8,
|
||||
pub padding1: [u8; 2],
|
||||
pub name: [u8; 16],
|
||||
pub serum_program: Pubkey,
|
||||
pub serum_market_external: Pubkey,
|
||||
|
@ -48,6 +49,10 @@ impl Serum3Market {
|
|||
pub fn is_reduce_only(&self) -> bool {
|
||||
self.reduce_only == 1
|
||||
}
|
||||
|
||||
pub fn is_force_close(&self) -> bool {
|
||||
self.force_close == 1
|
||||
}
|
||||
}
|
||||
|
||||
#[account(zero_copy)]
|
||||
|
|
|
@ -25,6 +25,7 @@ export class Serum3Market {
|
|||
marketIndex: number;
|
||||
registrationTime: BN;
|
||||
reduceOnly: number;
|
||||
forceClose: number;
|
||||
},
|
||||
): Serum3Market {
|
||||
return new Serum3Market(
|
||||
|
@ -38,6 +39,7 @@ export class Serum3Market {
|
|||
obj.marketIndex as MarketIndex,
|
||||
obj.registrationTime,
|
||||
obj.reduceOnly == 1,
|
||||
obj.forceClose == 1,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -52,6 +54,7 @@ export class Serum3Market {
|
|||
public marketIndex: MarketIndex,
|
||||
public registrationTime: BN,
|
||||
public reduceOnly: boolean,
|
||||
public forceClose: boolean,
|
||||
) {
|
||||
this.name = utf8.decode(new Uint8Array(name)).split('\x00')[0];
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue