Perp cancel all: Don't error when orders are filled/expired (#453)

This commit is contained in:
Christian Kamm 2023-02-14 10:44:51 +01:00 committed by GitHub
parent 02d980f4e4
commit f6abd9579d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 66 additions and 34 deletions

View File

@ -93,6 +93,8 @@ pub enum MangoError {
IxIsDisabled,
#[msg("no liquidatable perp base position")]
NoLiquidatablePerpBasePosition,
#[msg("perp order id not found on the orderbook")]
PerpOrderIdNotFound,
}
impl MangoError {
@ -101,6 +103,19 @@ impl MangoError {
}
}
pub trait IsAnchorErrorWithCode {
fn is_anchor_error_with_code(&self, code: u32) -> bool;
}
impl<T> IsAnchorErrorWithCode for anchor_lang::Result<T> {
fn is_anchor_error_with_code(&self, code: u32) -> bool {
match self {
Err(Error::AnchorError(error)) => error.error_code_number == code,
_ => false,
}
}
}
pub trait Contextable {
/// Add a context string `c` to a Result or Error
///

View File

@ -215,16 +215,12 @@ fn can_load_as<'a, T: ZeroCopy + Owner>(
(i, ai): (usize, &'a AccountInfo),
) -> Option<(usize, Result<Ref<'a, T>>)> {
let load_result = ai.load::<T>();
match load_result {
Err(Error::AnchorError(error))
if error.error_code_number == ErrorCode::AccountDiscriminatorMismatch as u32
|| error.error_code_number == ErrorCode::AccountDiscriminatorNotFound as u32
|| error.error_code_number == ErrorCode::AccountOwnedByWrongProgram as u32 =>
{
return None;
}
_ => {}
};
if load_result.is_anchor_error_with_code(ErrorCode::AccountDiscriminatorMismatch.into())
|| load_result.is_anchor_error_with_code(ErrorCode::AccountDiscriminatorNotFound.into())
|| load_result.is_anchor_error_with_code(ErrorCode::AccountOwnedByWrongProgram.into())
{
return None;
}
Some((i, load_result))
}

View File

@ -579,17 +579,12 @@ fn find_maximum(
}
fn ignore_net_borrow_limit_errors(maybe_cache: Result<HealthCache>) -> Result<Option<HealthCache>> {
match maybe_cache {
Ok(cache) => Ok(Some(cache)),
// Special case net borrow errors: We want to be able to find a good
// swap amount even if the max swap is limited by the net borrow limit.
Err(Error::AnchorError(err))
if err.error_code_number == MangoError::BankNetBorrowsLimitReached.error_code() =>
{
Ok(None)
}
Err(err) => Err(err),
// Special case net borrow errors: We want to be able to find a good
// swap amount even if the max swap is limited by the net borrow limit.
if maybe_cache.is_anchor_error_with_code(MangoError::BankNetBorrowsLimitReached.error_code()) {
return Ok(None);
}
maybe_cache.map(|c| Some(c))
}
fn system_epoch_secs() -> u64 {

View File

@ -51,7 +51,7 @@ pub fn perp_cancel_order(ctx: Context<PerpCancelOrder>, order_id: u128) -> Resul
let oo = account
.perp_find_order_with_order_id(perp_market.perp_market_index, order_id)
.ok_or_else(|| {
error_msg!("could not find perp order with id {order_id} in perp market orderbook")
error_msg!("could not find perp order with id {order_id} in user account")
})?;
let order_id = oo.id;
let order_side_and_tree = oo.side_and_tree();

View File

@ -52,7 +52,11 @@ pub fn perp_cancel_order_by_client_order_id(
let oo = account
.perp_find_order_with_client_order_id(perp_market.perp_market_index, client_order_id)
.ok_or_else(|| error_msg!("could not find perp order with client order id {client_order_id} in perp order books"))?;
.ok_or_else(|| {
error_msg!(
"could not find perp order with client order id {client_order_id} in user account"
)
})?;
let order_id = oo.id;
let order_side_and_tree = oo.side_and_tree();

View File

@ -54,10 +54,9 @@ pub fn perp_liq_force_cancel_orders(
}
} 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?;
}
if !result.is_anchor_error_with_code(MangoError::HealthMustBeNegative.into()) {
// Propagate unexpected errors
result?;
}
}
}

View File

@ -133,11 +133,9 @@ pub fn serum3_liq_force_cancel_orders(
}
} 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?;
}
if !result.is_anchor_error_with_code(MangoError::HealthMustBeNegative.into()) {
// Propagate unexpected errors
result?;
}
}
}

View File

@ -301,7 +301,19 @@ impl<'a> Orderbook<'a> {
let order_id = oo.id;
self.cancel_order(mango_account, order_id, order_side_and_tree, None)?;
let cancel_result =
self.cancel_order(mango_account, order_id, order_side_and_tree, None);
if cancel_result.is_anchor_error_with_code(MangoError::PerpOrderIdNotFound.into()) {
// It's possible for the order to be filled or expired already.
// There will be an event on the queue, the perp order slot is freed once
// it is processed.
msg!(
"order {} was not found on orderbook, expired or filled already",
order_id
);
} else {
cancel_result?;
}
limit -= 1;
if limit == 0 {
@ -324,8 +336,9 @@ impl<'a> Orderbook<'a> {
let book_component = side_and_tree.order_tree();
let leaf_node = self.bookside_mut(side).
remove_by_key(book_component, order_id).ok_or_else(|| {
error_msg!("invalid perp order id {order_id} for side {side:?} and component {book_component:?}")
})?;
// possibly already filled or expired?
error_msg_typed!(MangoError::PerpOrderIdNotFound, "no perp order with id {order_id}, side {side:?}, component {book_component:?} found on the orderbook")
})?;
if let Some(owner) = expected_owner {
require_keys_eq!(leaf_node.owner, owner);
}

View File

@ -268,6 +268,18 @@ async fn test_perp_fixed() -> Result<(), TransportError> {
.unwrap();
check_prev_instruction_post_health(&solana, account_1).await;
// Trying to cancel-all after the order was already taken: has no effect but succeeds
send_tx(
solana,
PerpCancelAllOrdersInstruction {
account: account_0,
perp_market,
owner,
},
)
.await
.unwrap();
send_tx(
solana,
PerpConsumeEventsInstruction {