1const ANCHOR_ERROR_BASE: u32 = 6000;
24
25macro_rules! define_lending_errors {
26 (
27 $(
28 $(#[doc = $doc:expr])*
29 $variant:ident = $offset:literal => $msg:expr
30 ),*
31 $(,)?
32 ) => {
33 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34 #[repr(u32)]
35 pub enum LendingError {
36 $(
37 $(#[doc = $doc])*
38 $variant = ANCHOR_ERROR_BASE + $offset,
39 )*
40 }
41
42 impl LendingError {
43 pub const fn error_code(self) -> u32 {
45 self as u32
46 }
47
48 pub const fn message(self) -> &'static str {
50 match self {
51 $(Self::$variant => $msg,)*
52 }
53 }
54
55 pub const fn from_error_code(code: u32) -> Option<Self> {
57 match code {
58 $(x if x == ANCHOR_ERROR_BASE + $offset => Some(Self::$variant),)*
59 _ => None,
60 }
61 }
62 }
63
64 impl core::fmt::Display for LendingError {
65 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
66 write!(f, "{}", self.message())
67 }
68 }
69
70 impl TryFrom<u32> for LendingError {
71 type Error = u32;
72
73 fn try_from(code: u32) -> Result<Self, Self::Error> {
76 Self::from_error_code(code).ok_or(code)
77 }
78 }
79 };
80}
81
82define_lending_errors! {
83 InvalidMarketAuthority = 0 => "Market authority is invalid",
84 InvalidMarketOwner = 1 => "Market owner is invalid",
85 InvalidAccountOwner = 2 => "Input account owner is not the program address",
86 InvalidAmount = 3 => "Input amount is invalid",
87 InvalidConfig = 4 => "Input config value is invalid",
88 InvalidSigner = 5 => "Signer is not allowed to perform this action",
89 InvalidAccountInput = 6 => "Invalid account input",
90 MathOverflow = 7 => "Math operation overflow",
91 InsufficientLiquidity = 8 => "Insufficient liquidity available",
92 ReserveStale = 9 => "Reserve state needs to be refreshed",
93 WithdrawTooSmall = 10 => "Withdraw amount too small",
94 WithdrawTooLarge = 11 => "Withdraw amount too large",
95 BorrowTooSmall = 12 => "Borrow amount too small to receive liquidity after fees",
96 BorrowTooLarge = 13 => "Borrow amount too large for deposited collateral",
97 RepayTooSmall = 14 => "Repay amount too small to transfer liquidity",
98 LiquidationTooSmall = 15 => "Liquidation amount too small to receive collateral",
99 ObligationHealthy = 16 => "Cannot liquidate healthy obligations",
100 ObligationStale = 17 => "Obligation state needs to be refreshed",
101 ObligationReserveLimit = 18 => "Obligation reserve limit exceeded",
102 InvalidObligationOwner = 19 => "Obligation owner is invalid",
103 ObligationDepositsEmpty = 20 => "Obligation deposits are empty",
104 ObligationBorrowsEmpty = 21 => "Obligation borrows are empty",
105 ObligationDepositsZero = 22 => "Obligation deposits have zero value",
106 ObligationBorrowsZero = 23 => "Obligation borrows have zero value",
107 InvalidObligationCollateral = 24 => "Invalid obligation collateral",
108 InvalidObligationLiquidity = 25 => "Invalid obligation liquidity",
109 ObligationCollateralEmpty = 26 => "Obligation collateral is empty",
110 ObligationLiquidityEmpty = 27 => "Obligation liquidity is empty",
111 NegativeInterestRate = 28 => "Interest rate is negative",
112 InvalidOracleConfig = 29 => "Input oracle config is invalid",
113 InsufficientProtocolFeesToRedeem = 30 => "Insufficient protocol fees to claim or no liquidity available",
114 FlashBorrowCpi = 31 => "No cpi flash borrows allowed",
115 NoFlashRepayFound = 32 => "No corresponding repay found for flash borrow",
116 InvalidFlashRepay = 33 => "Invalid repay found",
117 FlashRepayCpi = 34 => "No cpi flash repays allowed",
118 MultipleFlashBorrows = 35 => "Multiple flash borrows not allowed in the same transaction",
119 FlashLoansDisabled = 36 => "Flash loans are disabled for this reserve",
120 SwitchboardV2Error = 37 => "Switchboard error",
121 CouldNotDeserializeScope = 38 => "Cannot deserialize the scope price account",
122 PriceTooOld = 39 => "Price too old",
123 PriceTooDivergentFromTwap = 40 => "Price too divergent from twap",
124 InvalidTwapPrice = 41 => "Invalid twap price",
125 GlobalEmergencyMode = 42 => "Emergency mode is enabled",
126 InvalidFlag = 43 => "Invalid lending market config",
127 PriceNotValid = 44 => "Price is not valid",
128 PriceIsBiggerThanHeuristic = 45 => "Price is bigger than allowed by heuristic",
129 PriceIsLowerThanHeuristic = 46 => "Price lower than allowed by heuristic",
130 PriceIsZero = 47 => "Price is zero",
131 PriceConfidenceTooWide = 48 => "Price confidence too wide",
132 IntegerOverflow = 49 => "Conversion between integers failed",
133 NoFarmForReserve = 50 => "This reserve does not have a farm",
134 IncorrectInstructionInPosition = 51 => "Wrong instruction at expected position",
135 NoPriceFound = 52 => "No price found",
136 InvalidTwapConfig = 53 => "Invalid Twap configuration: Twap is enabled but one of the enabled price doesn't have a twap",
137 InvalidPythPriceAccount = 54 => "Pyth price account does not match configuration",
138 InvalidSwitchboardAccount = 55 => "Switchboard account(s) do not match configuration",
139 InvalidScopePriceAccount = 56 => "Scope price account does not match configuration",
140 ObligationCollateralLtvZero = 57 => "The obligation has one collateral with an LTV set to 0. Withdraw it before withdrawing other collaterals",
141 InvalidObligationSeedsValue = 58 => "Seeds must be default pubkeys for tag 0, and mint addresses for tag 1 or 2",
142 DeprecatedInvalidObligationId = 59 => "[DEPRECATED] Obligation id must be 0",
143 InvalidBorrowRateCurvePoint = 60 => "Invalid borrow rate curve point",
144 InvalidUtilizationRate = 61 => "Invalid utilization rate",
145 CannotSocializeObligationWithCollateral = 62 => "Obligation hasn't been fully liquidated and debt cannot be socialized.",
146 ObligationEmpty = 63 => "Obligation has no borrows or deposits.",
147 WithdrawalCapReached = 64 => "Withdrawal cap is reached",
148 LastTimestampGreaterThanCurrent = 65 => "The last interval start timestamp is greater than the current timestamp",
149 LiquidationRewardTooSmall = 66 => "The reward amount is less than the minimum acceptable received liquidity",
150 IsolatedAssetTierViolation = 67 => "Isolated Asset Tier Violation",
151 InconsistentElevationGroup = 68 => "The obligation's elevation group and the reserve's are not the same",
152 InvalidElevationGroup = 69 => "The elevation group chosen for the reserve does not exist in the lending market",
153 InvalidElevationGroupConfig = 70 => "The elevation group updated has wrong parameters set",
154 UnhealthyElevationGroupLtv = 71 => "The current obligation must have most or all its debt repaid before changing the elevation group",
155 ElevationGroupNewLoansDisabled = 72 => "Elevation group does not accept any new loans or any new borrows/withdrawals",
156 ReserveDeprecated = 73 => "Reserve was deprecated, no longer usable",
157 ReferrerAccountNotInitialized = 74 => "Referrer account not initialized",
158 ReferrerAccountMintMissmatch = 75 => "Referrer account mint does not match the operation reserve mint",
159 ReferrerAccountWrongAddress = 76 => "Referrer account address is not a valid program address",
160 ReferrerAccountReferrerMissmatch = 77 => "Referrer account referrer does not match the owner referrer",
161 ReferrerAccountMissing = 78 => "Referrer account missing for obligation with referrer",
162 InsufficientReferralFeesToRedeem = 79 => "Insufficient referral fees to claim or no liquidity available",
163 CpiDisabled = 80 => "CPI disabled for this instruction",
164 ShortUrlNotAsciiAlphanumeric = 81 => "Referrer short_url is not ascii alphanumeric",
165 ReserveObsolete = 82 => "Reserve is marked as obsolete",
166 ElevationGroupAlreadyActivated = 83 => "Obligation already part of the same elevation group",
167 ObligationInObsoleteReserve = 84 => "Obligation has a deposit or borrow in an obsolete reserve",
168 ReferrerStateOwnerMismatch = 85 => "Referrer state owner does not match the given signer",
169 UserMetadataOwnerAlreadySet = 86 => "User metadata owner is already set",
170 CollateralNonLiquidatable = 87 => "This collateral cannot be liquidated (LTV set to 0)",
171 BorrowingDisabled = 88 => "Borrowing is disabled",
172 BorrowLimitExceeded = 89 => "Cannot borrow above borrow limit",
173 DepositLimitExceeded = 90 => "Cannot deposit above deposit limit",
174 BorrowingDisabledOutsideElevationGroup = 91 => "Reserve does not accept any new borrows outside elevation group",
175 NetValueRemainingTooSmall = 92 => "Net value remaining too small",
176 WorseLtvBlocked = 93 => "Cannot get the obligation in a worse position",
177 LiabilitiesBiggerThanAssets = 94 => "Cannot have more liabilities than assets in a position",
178 ReserveTokenBalanceMismatch = 95 => "Reserve state and token account cannot drift",
179 ReserveVaultBalanceMismatch = 96 => "Reserve token account has been unexpectedly modified",
180 ReserveAccountingMismatch = 97 => "Reserve internal state accounting has been unexpectedly modified",
181 BorrowingAboveUtilizationRateDisabled = 98 => "Borrowing above set utilization rate is disabled",
182 LiquidationBorrowFactorPriority = 99 => "Liquidation must prioritize the debt with the highest borrow factor",
183 LiquidationLowestLiquidationLtvPriority = 100 => "Liquidation must prioritize the collateral with the lowest liquidation LTV",
184 ElevationGroupBorrowLimitExceeded = 101 => "Elevation group borrow limit exceeded",
185 ElevationGroupWithoutDebtReserve = 102 => "The elevation group does not have a debt reserve defined",
186 ElevationGroupMaxCollateralReserveZero = 103 => "The elevation group does not allow any collateral reserves",
187 ElevationGroupHasAnotherDebtReserve = 104 => "In elevation group attempt to borrow from a reserve that is not the debt reserve",
188 ElevationGroupDebtReserveAsCollateral = 105 => "The elevation group's debt reserve cannot be used as a collateral reserve",
189 ObligationCollateralExceedsElevationGroupLimit = 106 => "Obligation have more collateral than the maximum allowed by the elevation group",
190 ObligationElevationGroupMultipleDebtReserve = 107 => "Obligation is an elevation group but have more than one debt reserve",
191 UnsupportedTokenExtension = 108 => "Mint has a token (2022) extension that is not supported",
192 InvalidTokenAccount = 109 => "Can't have an spl token mint with a t22 account",
193 DepositDisabledOutsideElevationGroup = 110 => "Can't deposit into this reserve outside elevation group",
194 CannotCalculateReferralAmountDueToSlotsMismatch = 111 => "Cannot calculate referral amount due to slots mismatch",
195 ObligationOwnersMustMatch = 112 => "Obligation owners must match",
196 ObligationsMustMatch = 113 => "Obligations must match",
197 LendingMarketsMustMatch = 114 => "Lending markets must match",
198 ObligationCurrentlyMarkedForDeleveraging = 115 => "Obligation is already marked for deleveraging",
199 MaximumWithdrawValueZero = 116 => "Maximum withdrawable value of this collateral is zero, LTV needs improved",
200 ZeroMaxLtvAssetsInDeposits = 117 => "No max LTV 0 assets allowed in deposits for repay and withdraw",
201 LowestLtvAssetsPriority = 118 => "Withdrawing must prioritize the collateral with the lowest reserve max-LTV",
202 WorseLtvThanUnhealthyLtv = 119 => "Cannot get the obligation liquidatable",
203 FarmAccountsMissing = 120 => "Farm accounts to refresh are missing",
204 RepayTooSmallForFullLiquidation = 121 => "Repay amount is too small to satisfy the mandatory full liquidation",
205 InsufficientRepayAmount = 122 => "Liquidator provided repay amount lower than required by liquidation rules",
206 OrderIndexOutOfBounds = 123 => "Obligation order of the given index cannot exist",
207 InvalidOrderConfiguration = 124 => "Given order configuration has wrong parameters",
208 OrderConfigurationNotSupportedByObligation = 125 => "Given order configuration cannot be used with the current state of the obligation",
209 OperationNotPermittedWithCurrentObligationOrders = 126 => "Single debt, single collateral obligation orders have to be cancelled before changing the deposit/borrow count",
210 OperationNotPermittedMarketImmutable = 127 => "Cannot update lending market because it is set as immutable",
211 OrderCreationDisabled = 128 => "Creation of new orders is disabled",
212 NoUpgradeAuthority = 129 => "Cannot initialize global config because there is no upgrade authority to the program",
213 InitialAdminDepositExecuted = 130 => "Initial admin deposit in reserve already executed",
214 ReserveHasNotReceivedInitialDeposit = 131 => "Reserve has not received the initial deposit, cannot update config",
215 CTokenUsageBlocked = 132 => "CToken minting/redeeming is blocked for this reserve",
216 CannotUseSameReserve = 133 => "Cannot call ix with same reserve",
217 TransactionIncludesRestrictedPrograms = 134 => "Transaction includes restricted programs",
218 BorrowOrderDebtLiquidityMintMismatch = 135 => "There is no borrow order requesting debt in the given asset",
219 BorrowOrderMaxBorrowRateExceeded = 136 => "Reserve used for fill exceeds the maximum borrow rate specified by the order",
220 BorrowOrderMinDebtTermInsufficient = 137 => "Reserve used for fill defines a debt term shorter than specified by the order",
221 BorrowOrderFillTimeLimitExceeded = 138 => "Borrow order can no longer be filled",
222 ReserveDebtMaturityReached = 139 => "Cannot borrow from a reserve that reached its debt maturity timestamp",
223 NonUpdatableOrderConfiguration = 140 => "Some piece of the order's configuration cannot be updated (the order should be cancelled and placed again)",
224 BorrowOrderExecutionDisabled = 141 => "Execution of borrow orders is disabled",
225 DebtReachedReserveDebtTerm = 142 => "Cannot increase the debt that has reached its end of term configured by the reserve",
226 ExpectationNotMet = 143 => "The on-chain state does not meet expectation specified by the caller, so the operation must be aborted (to avoid race conditions)",
227 BorrowOrderFillValueTooSmall = 144 => "Available liquidity could not satisfy the minimum required borrow order fill value",
228 WithdrawTicketIssuanceDisabled = 145 => "Issuing new withdraw tickets is disabled by the market",
229 WithdrawTicketRedemptionDisabled = 146 => "Redeeming withdraw tickets is disabled by the market",
230 WithdrawTicketStillValid = 147 => "Recovering collateral is only available after the withdraw ticket has been marked invalid",
231 WithdrawTicketRequiresFullRedemption = 148 => "The withdraw ticket's current state requires that it is fully redeemed (e.g. due to owner ATA creation), but there is not enough liquidity",
232 UserTokenBalanceMismatch = 149 => "The user's token account has changed its balance in an unexpected way",
233 WithdrawQueuedLiquidityValueTooSmall = 150 => "Available liquidity could not satisfy the minimum required ticketed withdrawal value",
234 InvalidTokenAccountState = 151 => "Token account is in a state preventing the handler's operation (e.g. frozen or delegate)",
235 WithdrawTicketInvalid = 152 => "Cannot use ticket that was already marked invalid",
236 BorrowOrderValueTooSmall = 153 => "Borrow order's value would be below the market-configured minimum",
237 WithdrawTicketValueTooSmall = 154 => "Withdraw ticket's value would be below the market-configured minimum",
238 InvalidWithdrawTicketProgressCallbackConfig = 155 => "Invalid configuration or required custom accounts for the requested withdraw ticket callback type",
239 WithdrawTicketProgressCallbackAccountsMissing = 156 => "One or more accounts required by the ticket's configured progress callback are missing",
240 BorrowRolloverConfigurationDisabled = 157 => "Configuring auto-rollover on loans is disabled by market owner",
241 InvalidObligationConfigUpdateSubject = 158 => "Invalid specification of the Obligation's part to be configured",
242 BorrowRolloverLiquidityMintMismatch = 159 => "Auto-rollover must use a target reserve of the same token",
243 ObligationBorrowRolloverNotApplicable = 160 => "The given borrow is not fixed-term and does not require rolling over",
244 ObligationBorrowOutsideRolloverWindow = 161 => "The given borrow is outside the corresponding market-configured rollover window",
245 ObligationBorrowRolloverNotEnabledByOwner = 162 => "Obligation's owner did not opt-in for auto-rollover of the given borrow",
246 ObligationBorrowRolloverTargetReserveMismatch = 163 => "Obligation's owner did not allow to roll over into terms offered by the given reserve",
247 BorrowRolloverExecutionDisabled = 164 => "Executing auto-rollover is disabled by market owner",
248 ObligationAccountingMismatch = 165 => "Obligation internal state accounting has been unexpectedly modified",
249 PartialRolloverValueTooSmall = 166 => "Partial rollover amount is below the market-configured minimum value",
250 ObligationBorrowRolloverConfigMismatch = 167 => "Pre-existing rollover configuration of the loan cannot be overwritten by the operation",
251 ObligationBorrowRolloverMustProlongDebtTerm = 168 => "Rollover into existing borrow must prolong the remaining debt term",
252 RolloverNotSupportedInElevationGroup = 169 => "Rollover is not supported for obligations in an elevation group",
253 WithdrawTicketCancellationDisabled = 170 => "Cancelling withdraw tickets is disabled by the market",
254 WithdrawTicketFullyCancelled = 171 => "Cannot use ticket that was already fully-cancelled",
255 CloneSourceReserveDisabled = 172 => "Cannot clone config from a reserve that is disabled",
256 CloneTargetReserveAlreadyInUse = 173 => "Cannot clone config into a reserve that has been in use",
257 ClonedReserveLiquidityMintMismatch = 174 => "Cannot clone config between reserves of different mints",
258 ReserveEmergencyMode = 175 => "Reserve emergency mode is enabled",
259 ObligationOwnershipTransferInProgress = 176 => "Obligation ownership transfer is in progress",
260 ObligationOwnershipTransferNotInInitiatedState = 177 => "Obligation ownership transfer is not in initiated state",
261 ObligationPendingOwnerNotSet = 178 => "Obligation pending owner not set",
262 ObligationInvalidPendingOwner = 179 => "Invalid pending owner address",
263 ObligationOwnershipTransferNotApproved = 180 => "Obligation ownership transfer not approved by admin",
264 ObligationHasActiveBorrowOrders = 181 => "Obligation has active borrow orders",
265 OnlyComputeBudgetCompanionIxsAllowed = 182 => "Only ComputeBudget instructions may accompany this instruction",
266 MissingPermissioner = 183 => "Required permissioning account is missing",
267 ReserveRewardsDisabled = 184 => "Reserve rewards are disabled on this market (reserve_rewards_max_apr_bps is 0)",
268}
269
270impl std::error::Error for LendingError {}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275
276 #[test]
277 fn test_error_codes() {
278 assert_eq!(LendingError::InvalidMarketAuthority.error_code(), 6000);
279 assert_eq!(LendingError::InsufficientLiquidity.error_code(), 6008);
280 assert_eq!(
281 LendingError::ClonedReserveLiquidityMintMismatch.error_code(),
282 6174
283 );
284 assert_eq!(LendingError::MissingPermissioner.error_code(), 6183);
285 assert_eq!(
286 LendingError::OnlyComputeBudgetCompanionIxsAllowed.error_code(),
287 6182
288 );
289 }
290
291 #[test]
292 fn test_from_error_code() {
293 assert_eq!(
294 LendingError::from_error_code(6000),
295 Some(LendingError::InvalidMarketAuthority)
296 );
297 assert_eq!(
298 LendingError::from_error_code(6008),
299 Some(LendingError::InsufficientLiquidity)
300 );
301 assert_eq!(LendingError::from_error_code(5999), None);
302 assert_eq!(
303 LendingError::from_error_code(6175),
304 Some(LendingError::ReserveEmergencyMode)
305 );
306 assert_eq!(
307 LendingError::from_error_code(6182),
308 Some(LendingError::OnlyComputeBudgetCompanionIxsAllowed)
309 );
310 assert_eq!(
311 LendingError::from_error_code(6183),
312 Some(LendingError::MissingPermissioner)
313 );
314 assert_eq!(
315 LendingError::from_error_code(6184),
316 Some(LendingError::ReserveRewardsDisabled)
317 );
318 assert_eq!(LendingError::from_error_code(6185), None);
319 }
320
321 #[test]
322 fn test_try_from() {
323 assert_eq!(
324 LendingError::try_from(6042),
325 Ok(LendingError::GlobalEmergencyMode)
326 );
327 assert_eq!(LendingError::try_from(9999), Err(9999));
328 }
329
330 #[test]
331 fn test_display() {
332 assert_eq!(
333 LendingError::MathOverflow.to_string(),
334 "Math operation overflow"
335 );
336 }
337
338 #[test]
340 fn test_all_codes_roundtrip() {
341 let mut count = 0u32;
342 for code in 6000..=6183 {
343 let err = LendingError::from_error_code(code)
344 .unwrap_or_else(|| panic!("Missing variant for code {code}"));
345 assert_eq!(err.error_code(), code);
346 count += 1;
347 }
348 assert_eq!(count, 184);
349 }
350}