1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
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 spot orders")]
    HasOpenOrUnsettledSpotOrders,
    #[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")]
    SpotPriceBandExceeded,
    #[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,
    #[msg("for borrows the bank must be in the health account list")]
    BorrowsRequireHealthAccountBank,
    #[msg("invalid sequence number")]
    InvalidSequenceNumber,
    #[msg("invalid health")]
    InvalidHealth,
    #[msg("no free openbook v2 open orders index")]
    NoFreeOpenbookV2OpenOrdersIndex,
    #[msg("openbook v2 open orders exist already")]
    OpenbookV2OpenOrdersExistAlready,
}

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;