mango-v4/programs/mango-v4/src/error.rs

287 lines
9.5 KiB
Rust

use anchor_lang::prelude::*;
use core::fmt::Display;
// todo: group error blocks by kind
// todo: add comments which indicate decimal code for an error
#[error_code]
pub enum MangoError {
#[msg("")]
SomeError,
#[msg("")]
NotImplementedError,
#[msg("checked math error")]
MathError,
#[msg("")]
UnexpectedOracle,
#[msg("oracle type cannot be determined")]
UnknownOracleType,
#[msg("")]
InvalidFlashLoanTargetCpiProgram,
#[msg("health must be positive")]
HealthMustBePositive,
#[msg("health must be positive or not decrease")]
HealthMustBePositiveOrIncrease, // outdated name is kept for backwards compatibility
#[msg("health must be negative")]
HealthMustBeNegative,
#[msg("the account is bankrupt")]
IsBankrupt,
#[msg("the account is not bankrupt")]
IsNotBankrupt,
#[msg("no free token position index")]
NoFreeTokenPositionIndex,
#[msg("no free serum3 open orders index")]
NoFreeSerum3OpenOrdersIndex,
#[msg("no free perp position index")]
NoFreePerpPositionIndex,
#[msg("serum3 open orders exist already")]
Serum3OpenOrdersExistAlready,
#[msg("bank vault has insufficent funds")]
InsufficentBankVaultFunds,
#[msg("account is currently being liquidated")]
BeingLiquidated,
#[msg("invalid bank")]
InvalidBank,
#[msg("account profitability is mismatched")]
ProfitabilityMismatch,
#[msg("cannot settle with self")]
CannotSettleWithSelf,
#[msg("perp position does not exist")]
PerpPositionDoesNotExist,
#[msg("max settle amount must be greater than zero")]
MaxSettleAmountMustBeGreaterThanZero,
#[msg("the perp position has open orders or unprocessed fill events")]
HasOpenPerpOrders,
#[msg("an oracle does not reach the confidence threshold")]
OracleConfidence,
#[msg("an oracle is stale")]
OracleStale,
#[msg("settlement amount must always be positive")]
SettlementAmountMustBePositive,
#[msg("bank utilization has reached limit")]
BankBorrowLimitReached,
#[msg("bank net borrows has reached limit - this is an intermittent error - the limit will reset regularly")]
BankNetBorrowsLimitReached,
#[msg("token position does not exist")]
TokenPositionDoesNotExist,
#[msg("token deposits into accounts that are being liquidated must bring their health above the init threshold")]
DepositsIntoLiquidatingMustRecover,
#[msg("token is in reduce only mode")]
TokenInReduceOnlyMode,
#[msg("market is in reduce only mode")]
MarketInReduceOnlyMode,
#[msg("group is halted")]
GroupIsHalted,
#[msg("the perp position has non-zero base lots")]
PerpHasBaseLots,
#[msg("there are open or unsettled serum3 orders")]
HasOpenOrUnsettledSerum3Orders,
#[msg("has liquidatable token position")]
HasLiquidatableTokenPosition,
#[msg("has liquidatable perp base position")]
HasLiquidatablePerpBasePosition,
#[msg("has liquidatable positive perp pnl")]
HasLiquidatablePositivePerpPnl,
#[msg("account is frozen")]
AccountIsFrozen,
#[msg("Init Asset Weight can't be negative")]
InitAssetWeightCantBeNegative,
#[msg("has open perp taker fills")]
HasOpenPerpTakerFills,
#[msg("deposit crosses the current group deposit limit")]
DepositLimit,
#[msg("instruction is disabled")]
IxIsDisabled,
#[msg("no liquidatable perp base position")]
NoLiquidatablePerpBasePosition,
#[msg("perp order id not found on the orderbook")]
PerpOrderIdNotFound,
#[msg("HealthRegions allow only specific instructions between Begin and End")]
HealthRegionBadInnerInstruction,
#[msg("token is in force close")]
TokenInForceClose,
#[msg("incorrect number of health accounts")]
InvalidHealthAccountCount,
#[msg("would self trade")]
WouldSelfTrade,
#[msg("token conditional swap oracle price is not in execution range")]
TokenConditionalSwapPriceNotInRange,
#[msg("token conditional swap is expired")]
TokenConditionalSwapExpired,
#[msg("token conditional swap is not available yet")]
TokenConditionalSwapNotStarted,
#[msg("token conditional swap was already started")]
TokenConditionalSwapAlreadyStarted,
#[msg("token conditional swap it not set")]
TokenConditionalSwapNotSet,
#[msg("token conditional swap trigger did not reach min_buy_token")]
TokenConditionalSwapMinBuyTokenNotReached,
#[msg("token conditional swap cannot pay incentive")]
TokenConditionalSwapCantPayIncentive,
#[msg("token conditional swap taker price is too low")]
TokenConditionalSwapTakerPriceTooLow,
#[msg("token conditional swap index and id don't match")]
TokenConditionalSwapIndexIdMismatch,
#[msg("token conditional swap volume is too small compared to the cost of starting it")]
TokenConditionalSwapTooSmallForStartIncentive,
#[msg("token conditional swap type cannot be started")]
TokenConditionalSwapTypeNotStartable,
#[msg("a bank in the health account list should be writable but is not")]
HealthAccountBankNotWritable,
#[msg("the market does not allow limit orders too far from the current oracle value")]
Serum3PriceBandExceeded,
#[msg("deposit crosses the token's deposit limit")]
BankDepositLimit,
#[msg("delegates can only withdraw to the owner's associated token account")]
DelegateWithdrawOnlyToOwnerAta,
#[msg("delegates can only withdraw if they close the token position")]
DelegateWithdrawMustClosePosition,
#[msg("delegates can only withdraw small amounts")]
DelegateWithdrawSmall,
#[msg("The provided CLMM oracle is not valid")]
InvalidCLMMOracle,
#[msg("invalid usdc/usd feed provided for the CLMM oracle")]
InvalidFeedForCLMMOracle,
#[msg("Pyth USDC/USD or SOL/USD feed not found (required by CLMM oracle)")]
MissingFeedForCLMMOracle,
#[msg("the asset does not allow liquidation")]
TokenAssetLiquidationDisabled,
}
impl MangoError {
pub fn error_code(&self) -> u32 {
(*self).into()
}
}
pub trait IsAnchorErrorWithCode {
fn is_anchor_error_with_code(&self, code: u32) -> bool;
fn is_oracle_error(&self) -> 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,
}
}
fn is_oracle_error(&self) -> bool {
match self {
Err(Error::AnchorError(e)) => {
e.error_code_number == MangoError::OracleConfidence.error_code()
|| e.error_code_number == MangoError::OracleStale.error_code()
}
_ => false,
}
}
}
pub trait Contextable {
/// Add a context string `c` to a Result or Error
///
/// Example: foo().context("calling foo")?;
fn context(self, c: impl Display) -> Self;
/// Like `context()`, but evaluate the context string lazily
///
/// Use this if it's expensive to generate, like a format!() call.
fn with_context<C, F>(self, c: F) -> Self
where
C: Display,
F: FnOnce() -> C;
}
impl Contextable for Error {
fn context(self, c: impl Display) -> Self {
match self {
Error::AnchorError(err) => Error::AnchorError(AnchorError {
error_msg: if err.error_msg.is_empty() {
format!("{}", c)
} else {
format!("{}; {}", err.error_msg, c)
},
..err
}),
// Maybe wrap somehow?
Error::ProgramError(err) => Error::ProgramError(err),
}
}
fn with_context<C, F>(self, c: F) -> Self
where
C: Display,
F: FnOnce() -> C,
{
self.context(c())
}
}
impl<T> Contextable for Result<T> {
fn context(self, c: impl Display) -> Self {
if let Err(err) = self {
Err(err.context(c))
} else {
self
}
}
fn with_context<C, F>(self, c: F) -> Self
where
C: Display,
F: FnOnce() -> C,
{
if let Err(err) = self {
Err(err.context(c()))
} else {
self
}
}
}
/// Creates an Error with a particular message, using format!() style arguments
///
/// Example: error_msg!("index {} not found", index)
#[macro_export]
macro_rules! error_msg {
($($arg:tt)*) => {
error!(MangoError::SomeError).context(format!($($arg)*))
};
}
/// Creates an Error with a particular message, using format!() style arguments
///
/// Example: error_msg_typed!(TokenPositionMissing, "index {} not found", index)
#[macro_export]
macro_rules! error_msg_typed {
($code:expr, $($arg:tt)*) => {
error!($code).context(format!($($arg)*))
};
}
/// Like anchor's require!(), but with a customizable message
///
/// Example: require_msg!(condition, "the condition on account {} was violated", account_key);
#[macro_export]
macro_rules! require_msg {
($invariant:expr, $($arg:tt)*) => {
if !($invariant) {
return Err(error_msg!($($arg)*));
}
};
}
/// Like anchor's require!(), but with a customizable message and type
///
/// Example: require_msg_typed!(condition, "the condition on account {} was violated", account_key);
#[macro_export]
macro_rules! require_msg_typed {
($invariant:expr, $code:expr, $($arg:tt)*) => {
if !($invariant) {
return Err(error_msg_typed!($code, $($arg)*));
}
};
}
pub use error_msg;
pub use error_msg_typed;
pub use require_msg;
pub use require_msg_typed;