Skip to main content

kvault_interface/
errors.rs

1//! Kvault program error codes.
2//!
3//! Anchor custom errors start at offset 6000. Kvault errors use offsets starting at 1000,
4//! so error codes start at 7000. Each variant's error code is `6000 + offset`.
5//! Use [`KvaultError::from_error_code`] to convert an on-chain error code back to a variant,
6//! or `TryFrom<u32>` for the same purpose.
7//!
8//! # Example
9//!
10//! ```rust
11//! use kvault_interface::KvaultError;
12//!
13//! let error = KvaultError::from_error_code(7002);
14//! assert_eq!(error, Some(KvaultError::MathOverflow));
15//! assert_eq!(error.unwrap().error_code(), 7002);
16//! assert_eq!(
17//!     error.unwrap().to_string(),
18//!     "Math operation overflowed"
19//! );
20//! ```
21
22/// Anchor error-code base for custom program errors.
23const ANCHOR_ERROR_BASE: u32 = 6000;
24
25macro_rules! define_kvault_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 KvaultError {
36            $(
37                $(#[doc = $doc])*
38                $variant = ANCHOR_ERROR_BASE + $offset,
39            )*
40        }
41
42        impl KvaultError {
43            /// Returns the Anchor error code for this variant (`6000 + offset`).
44            pub const fn error_code(self) -> u32 {
45                self as u32
46            }
47
48            /// Returns the human-readable error message.
49            pub const fn message(self) -> &'static str {
50                match self {
51                    $(Self::$variant => $msg,)*
52                }
53            }
54
55            /// Converts an Anchor error code to a `KvaultError`, if it matches a known variant.
56            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 KvaultError {
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 KvaultError {
71            type Error = u32;
72
73            /// Converts an error code to a `KvaultError`.
74            /// Returns `Err(code)` if the code doesn't match any known variant.
75            fn try_from(code: u32) -> Result<Self, Self::Error> {
76                Self::from_error_code(code).ok_or(code)
77            }
78        }
79    };
80}
81
82define_kvault_errors! {
83    /// Deposit amounts are zero.
84    DepositAmountsZero = 1000 => "Cannot deposit zero tokens",
85    /// Shares issued amount does not match expected.
86    SharesIssuedAmountDoesNotMatch = 1001 => "Post check failed on share issued",
87    /// Math operation overflowed.
88    MathOverflow = 1002 => "Math operation overflowed",
89    /// Integer conversion overflowed.
90    IntegerOverflow = 1003 => "Integer conversion overflowed",
91    /// Withdraw amount is below the minimum.
92    WithdrawAmountBelowMinimum = 1004 => "Withdrawn amount is below minimum",
93    /// Too much liquidity requested for withdrawal.
94    TooMuchLiquidityToWithdraw = 1005 => "TooMuchLiquidityToWithdraw",
95    /// Reserve already exists in the vault.
96    ReserveAlreadyExists = 1006 => "ReserveAlreadyExists",
97    /// Reserve is not part of vault allocations.
98    ReserveNotPartOfAllocations = 1007 => "ReserveNotPartOfAllocations",
99    /// Could not deserialize account as a reserve.
100    CouldNotDeserializeAccountAsReserve = 1008 => "CouldNotDeserializeAccountAsReserve",
101    /// Reserve not provided in the accounts.
102    ReserveNotProvidedInTheAccounts = 1009 => "ReserveNotProvidedInTheAccounts",
103    /// Reserve account and key mismatch.
104    ReserveAccountAndKeyMismatch = 1010 => "ReserveAccountAndKeyMismatch",
105    /// Reserve index is out of range.
106    OutOfRangeOfReserveIndex = 1011 => "OutOfRangeOfReserveIndex",
107    /// Cannot find reserve in allocations.
108    CannotFindReserveInAllocations = 1012 => "OutOfRangeOfReserveIndex",
109    /// Invest amount is below the minimum.
110    InvestAmountBelowMinimum = 1013 => "Invested amount is below minimum",
111    /// Admin authority is incorrect.
112    AdminAuthorityIncorrect = 1014 => "AdminAuthorityIncorrect",
113    /// Base vault authority is incorrect.
114    BaseVaultAuthorityIncorrect = 1015 => "BaseVaultAuthorityIncorrect",
115    /// Base vault authority bump is incorrect.
116    BaseVaultAuthorityBumpIncorrect = 1016 => "BaseVaultAuthorityBumpIncorrect",
117    /// Token mint is incorrect.
118    TokenMintIncorrect = 1017 => "TokenMintIncorrect",
119    /// Token mint decimals are incorrect.
120    TokenMintDecimalsIncorrect = 1018 => "TokenMintDecimalsIncorrect",
121    /// Token vault is incorrect.
122    TokenVaultIncorrect = 1019 => "TokenVaultIncorrect",
123    /// Shares mint decimals are incorrect.
124    SharesMintDecimalsIncorrect = 1020 => "SharesMintDecimalsIncorrect",
125    /// Shares mint is incorrect.
126    SharesMintIncorrect = 1021 => "SharesMintIncorrect",
127    /// Initial accounting is incorrect.
128    InitialAccountingIncorrect = 1022 => "InitialAccountingIncorrect",
129    /// Reserve state needs to be refreshed.
130    ReserveIsStale = 1023 => "Reserve is stale and must be refreshed before any operation",
131    /// Not enough liquidity disinvested to send to user.
132    NotEnoughLiquidityDisinvestedToSendToUser = 1024 => "Not enough liquidity disinvested to send to user",
133    /// Basis points value is too large.
134    BPSValueTooBig = 1025 => "BPS value is greater than 10000",
135    /// Deposit amount is below the minimum.
136    DepositAmountBelowMinimum = 1026 => "Deposited amount is below minimum",
137    /// No more reserve slots available in the vault.
138    ReserveSpaceExhausted = 1027 => "Vault have no space for new reserves",
139    /// Cannot withdraw from an empty vault.
140    CannotWithdrawFromEmptyVault = 1028 => "Cannot withdraw from empty vault",
141    /// Tokens deposited amount does not match expected.
142    TokensDepositedAmountDoesNotMatch = 1029 => "TokensDepositedAmountDoesNotMatch",
143    /// Amount to withdraw does not match expected.
144    AmountToWithdrawDoesNotMatch = 1030 => "Amount to withdraw does not match",
145    /// Liquidity to withdraw does not match expected.
146    LiquidityToWithdrawDoesNotMatch = 1031 => "Liquidity to withdraw does not match",
147    /// User received amount does not match expected.
148    UserReceivedAmountDoesNotMatch = 1032 => "User received amount does not match",
149    /// Shares burned amount does not match expected.
150    SharesBurnedAmountDoesNotMatch = 1033 => "Shares burned amount does not match",
151    /// Disinvested liquidity amount does not match expected.
152    DisinvestedLiquidityAmountDoesNotMatch = 1034 => "Disinvested liquidity amount does not match",
153    /// Shares minted amount does not match expected.
154    SharesMintedAmountDoesNotMatch = 1035 => "SharesMintedAmountDoesNotMatch",
155    /// Assets under management decreased after invest.
156    AUMDecreasedAfterInvest = 1036 => "AUM decreased after invest",
157    /// Assets under management is below pending fees.
158    AUMBelowPendingFees = 1037 => "AUM is below pending fees",
159    /// Deposit would result in zero shares.
160    DepositAmountsZeroShares = 1038 => "Deposit amount results in 0 shares",
161    /// Withdraw results in zero shares.
162    WithdrawResultsInZeroShares = 1039 => "Withdraw amount results in 0 shares",
163    /// Cannot withdraw zero shares.
164    CannotWithdrawZeroShares = 1040 => "Cannot withdraw zero shares",
165    /// Management fee exceeds the maximum allowed.
166    ManagementFeeGreaterThanMaxAllowed = 1041 => "Management fee is greater than maximum allowed",
167    /// Vault assets under management is zero.
168    VaultAUMZero = 1042 => "Vault assets under management are empty",
169    /// Missing reserve account for batch refresh.
170    MissingReserveForBatchRefresh = 1043 => "Missing reserve for batch refresh",
171    /// Minimum withdraw amount is too large.
172    MinWithdrawAmountTooBig = 1044 => "Min withdraw amount is too big",
173    /// Invest called too soon after the previous invest.
174    InvestTooSoon = 1045 => "Invest is called too soon after last invest",
175    /// Signer is neither the admin nor the allocation admin.
176    WrongAdminOrAllocationAdmin = 1046 => "Wrong admin or allocation admin",
177    /// Reserve has non-zero allocation or cToken balance.
178    ReserveHasNonZeroAllocationOrCTokens = 1047 => "Reserve has non-zero allocation or ctokens so cannot be removed",
179    /// Deposit amount exceeds the requested amount.
180    DepositAmountGreaterThanRequestedAmount = 1048 => "Deposit amount is greater than requested amount",
181    /// Withdraw amount is less than the withdrawal penalty.
182    WithdrawAmountLessThanWithdrawalPenalty = 1049 => "Withdraw amount is less than withdrawal penalty",
183    /// Cannot withdraw zero lamports.
184    CannotWithdrawZeroLamports = 1050 => "Cannot withdraw 0 lamports",
185    /// Program has no upgrade authority.
186    NoUpgradeAuthority = 1051 => "Cannot initialize global config because there is no upgrade authority to the program",
187    /// Withdrawal fee BPS exceeds the maximum allowed.
188    WithdrawalFeeBPSGreaterThanMaxAllowed = 1052 => "Withdrawal fee BPS is greater than maximum allowed",
189    /// Withdrawal fee lamports exceeds the maximum allowed.
190    WithdrawalFeeLamportsGreaterThanMaxAllowed = 1053 => "Withdrawal fee lamports is greater than maximum allowed",
191    /// Reserve is not whitelisted.
192    ReserveNotWhitelisted = 1054 => "Reserve is not whitelisted",
193    /// Invalid boolean-like value.
194    InvalidBoolLikeValue = 1055 => "Invalid bool-like value passed in (should be 0 or 1)",
195    /// Assets under management decreased more than expected.
196    AUMDecreasedMoreThanExpected = 1056 => "AUM decreased more than expected during redeem in kind",
197    RewardTopupAmountZero = 1057 => "Reward topup amount is zero",
198    RewardTopupAmountNotExpected = 1058 => "Reward topup is not as expected after transfer",
199    RewardWithdrawAmountZero = 1059 => "Reward withdraw amount is zero",
200    RewardWithdrawAmountNotExpected = 1060 => "Reward withdraw is not as expected after transfer",
201    RewardsStaleForFeeUpdate = 1061 => "Rewards are stale - must be refreshed before updating fees",
202    /// Vault deposit cap reached.
203    VaultDepositCapReached = 1062 => "Vault deposit cap reached",
204    /// Max invest amount must be greater than zero.
205    MaxInvestAmountMustBeGreaterThanZero = 1063 => "max_amount must be greater than 0",
206}
207
208impl std::error::Error for KvaultError {}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn verify_error_codes() {
216        assert_eq!(KvaultError::DepositAmountsZero.error_code(), 7000);
217        assert_eq!(KvaultError::MathOverflow.error_code(), 7002);
218        assert_eq!(KvaultError::AUMDecreasedMoreThanExpected.error_code(), 7056);
219        assert_eq!(KvaultError::RewardsStaleForFeeUpdate.error_code(), 7061);
220        assert_eq!(KvaultError::VaultDepositCapReached.error_code(), 7062);
221        assert_eq!(
222            KvaultError::MaxInvestAmountMustBeGreaterThanZero.error_code(),
223            7063
224        );
225
226        assert_eq!(
227            KvaultError::from_error_code(7000),
228            Some(KvaultError::DepositAmountsZero)
229        );
230        assert_eq!(
231            KvaultError::from_error_code(7056),
232            Some(KvaultError::AUMDecreasedMoreThanExpected)
233        );
234        assert_eq!(KvaultError::from_error_code(5000), None);
235        assert_eq!(KvaultError::from_error_code(9999), None);
236
237        // Display shows human-readable message
238        assert_eq!(
239            KvaultError::MathOverflow.to_string(),
240            "Math operation overflowed"
241        );
242
243        // TryFrom works the same as from_error_code
244        assert_eq!(KvaultError::try_from(7002), Ok(KvaultError::MathOverflow));
245        assert_eq!(KvaultError::try_from(9999), Err(9999));
246    }
247}