Skip to main content

rialo_spl_token_2022_interface/
error.rs

1// Copyright (c) Subzero Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3//! Error types
4
5use num_derive::FromPrimitive;
6use num_traits::FromPrimitive;
7use rialo_s_decode_error::DecodeError;
8use rialo_s_msg::msg;
9use rialo_s_program_error::{PrintProgramError, ProgramError, ToStr};
10use rialo_s_spl_token_confidential_transfer_proof_extraction::errors::TokenProofExtractionError;
11#[cfg(not(target_os = "solana"))]
12use rialo_s_spl_token_confidential_transfer_proof_generation::errors::TokenProofGenerationError;
13use thiserror::Error;
14
15/// Errors that may be returned by the Token program.
16#[repr(u32)]
17#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
18#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
19pub enum TokenError {
20    // 0
21    /// Kelvin balance below rent-exempt threshold.
22    #[error("Kelvin balance below rent-exempt threshold")]
23    NotRentExempt,
24    /// Insufficient funds for the operation requested.
25    #[error("Insufficient funds")]
26    InsufficientFunds,
27    /// Invalid Mint.
28    #[error("Invalid Mint")]
29    InvalidMint,
30    /// Account not associated with this Mint.
31    #[error("Account not associated with this Mint")]
32    MintMismatch,
33    /// Owner does not match.
34    #[error("Owner does not match")]
35    OwnerMismatch,
36
37    // 5
38    /// This token's supply is fixed and new tokens cannot be minted.
39    #[error("Fixed supply")]
40    FixedSupply,
41    /// The account cannot be initialized because it is already being used.
42    #[error("Already in use")]
43    AlreadyInUse,
44    /// Invalid number of provided signers.
45    #[error("Invalid number of provided signers")]
46    InvalidNumberOfProvidedSigners,
47    /// Invalid number of required signers.
48    #[error("Invalid number of required signers")]
49    InvalidNumberOfRequiredSigners,
50    /// State is uninitialized.
51    #[error("State is uninitialized")]
52    UninitializedState,
53
54    // 10
55    /// Instruction does not support native tokens
56    #[error("Instruction does not support native tokens")]
57    NativeNotSupported,
58    /// Non-native account can only be closed if its balance is zero
59    #[error("Non-native account can only be closed if its balance is zero")]
60    NonNativeHasBalance,
61    /// Invalid instruction
62    #[error("Invalid instruction")]
63    InvalidInstruction,
64    /// State is invalid for requested operation.
65    #[error("State is invalid for requested operation")]
66    InvalidState,
67    /// Operation overflowed
68    #[error("Operation overflowed")]
69    Overflow,
70
71    // 15
72    /// Account does not support specified authority type.
73    #[error("Account does not support specified authority type")]
74    AuthorityTypeNotSupported,
75    /// This token mint cannot freeze accounts.
76    #[error("This token mint cannot freeze accounts")]
77    MintCannotFreeze,
78    /// Account is frozen; all account operations will fail
79    #[error("Account is frozen")]
80    AccountFrozen,
81    /// Mint decimals mismatch between the client and mint
82    #[error("The provided decimals value different from the Mint decimals")]
83    MintDecimalsMismatch,
84    /// Instruction does not support non-native tokens
85    #[error("Instruction does not support non-native tokens")]
86    NonNativeNotSupported,
87
88    // 20
89    /// Extension type does not match already existing extensions
90    #[error("Extension type does not match already existing extensions")]
91    ExtensionTypeMismatch,
92    /// Extension does not match the base type provided
93    #[error("Extension does not match the base type provided")]
94    ExtensionBaseMismatch,
95    /// Extension already initialized on this account
96    #[error("Extension already initialized on this account")]
97    ExtensionAlreadyInitialized,
98    /// An account can only be closed if its confidential balance is zero
99    #[error("An account can only be closed if its confidential balance is zero")]
100    ConfidentialTransferAccountHasBalance,
101    /// Account not approved for confidential transfers
102    #[error("Account not approved for confidential transfers")]
103    ConfidentialTransferAccountNotApproved,
104
105    // 25
106    /// Account not accepting deposits or transfers
107    #[error("Account not accepting deposits or transfers")]
108    ConfidentialTransferDepositsAndTransfersDisabled,
109    /// ElGamal public key mismatch
110    #[error("ElGamal public key mismatch")]
111    ConfidentialTransferElGamalPubkeyMismatch,
112    /// Balance mismatch
113    #[error("Balance mismatch")]
114    ConfidentialTransferBalanceMismatch,
115    /// Mint has non-zero supply. Burn all tokens before closing the mint.
116    #[error("Mint has non-zero supply. Burn all tokens before closing the mint")]
117    MintHasSupply,
118    /// No authority exists to perform the desired operation
119    #[error("No authority exists to perform the desired operation")]
120    NoAuthorityExists,
121
122    // 30
123    /// Transfer fee exceeds maximum of 10,000 basis points
124    #[error("Transfer fee exceeds maximum of 10,000 basis points")]
125    TransferFeeExceedsMaximum,
126    /// Mint required for this account to transfer tokens, use
127    /// `transfer_checked` or `transfer_checked_with_fee`
128    #[error("Mint required for this account to transfer tokens, use `transfer_checked` or `transfer_checked_with_fee`")]
129    MintRequiredForTransfer,
130    /// Calculated fee does not match expected fee
131    #[error("Calculated fee does not match expected fee")]
132    FeeMismatch,
133    /// Fee parameters associated with confidential transfer zero-knowledge
134    /// proofs do not match fee parameters in mint
135    #[error(
136        "Fee parameters associated with zero-knowledge proofs do not match fee parameters in mint"
137    )]
138    FeeParametersMismatch,
139    /// The owner authority cannot be changed
140    #[error("The owner authority cannot be changed")]
141    ImmutableOwner,
142
143    // 35
144    /// An account can only be closed if its withheld fee balance is zero,
145    /// harvest fees to the mint and try again
146    #[error("An account can only be closed if its withheld fee balance is zero, harvest fees to the mint and try again")]
147    AccountHasWithheldTransferFees,
148    /// No memo in previous instruction; required for recipient to receive a
149    /// transfer
150    #[error("No memo in previous instruction; required for recipient to receive a transfer")]
151    NoMemo,
152    /// Transfer is disabled for this mint
153    #[error("Transfer is disabled for this mint")]
154    NonTransferable,
155    /// Non-transferable tokens can't be minted to an account without immutable
156    /// ownership
157    #[error("Non-transferable tokens can't be minted to an account without immutable ownership")]
158    NonTransferableNeedsImmutableOwnership,
159    /// The total number of `Deposit` and `Transfer` instructions to an account
160    /// cannot exceed the associated
161    /// `maximum_pending_balance_credit_counter`
162    #[error(
163        "The total number of `Deposit` and `Transfer` instructions to an account cannot exceed
164            the associated `maximum_pending_balance_credit_counter`"
165    )]
166    MaximumPendingBalanceCreditCounterExceeded,
167
168    // 40
169    /// The deposit amount for the confidential extension exceeds the maximum
170    /// limit
171    #[error("Deposit amount exceeds maximum limit")]
172    MaximumDepositAmountExceeded,
173    /// CPI Guard cannot be enabled or disabled in CPI
174    #[error("CPI Guard cannot be enabled or disabled in CPI")]
175    CpiGuardSettingsLocked,
176    /// CPI Guard is enabled, and a program attempted to transfer user funds
177    /// without using a delegate
178    #[error("CPI Guard is enabled, and a program attempted to transfer user funds via CPI without using a delegate")]
179    CpiGuardTransferBlocked,
180    /// CPI Guard is enabled, and a program attempted to burn user funds without
181    /// using a delegate
182    #[error(
183        "CPI Guard is enabled, and a program attempted to burn user funds via CPI without using a delegate"
184    )]
185    CpiGuardBurnBlocked,
186    /// CPI Guard is enabled, and a program attempted to close an account
187    /// without returning kelvins to owner
188    #[error("CPI Guard is enabled, and a program attempted to close an account via CPI without returning kelvins to owner")]
189    CpiGuardCloseAccountBlocked,
190
191    // 45
192    /// CPI Guard is enabled, and a program attempted to approve a delegate
193    #[error("CPI Guard is enabled, and a program attempted to approve a delegate via CPI")]
194    CpiGuardApproveBlocked,
195    /// CPI Guard is enabled, and a program attempted to add or replace an
196    /// authority
197    #[error(
198        "CPI Guard is enabled, and a program attempted to add or replace an authority via CPI"
199    )]
200    CpiGuardSetAuthorityBlocked,
201    /// Account ownership cannot be changed while CPI Guard is enabled
202    #[error("Account ownership cannot be changed while CPI Guard is enabled")]
203    CpiGuardOwnerChangeBlocked,
204    /// Extension not found in account data
205    #[error("Extension not found in account data")]
206    ExtensionNotFound,
207    /// Account does not accept non-confidential transfers
208    #[error("Non-confidential transfers disabled")]
209    NonConfidentialTransfersDisabled,
210
211    // 50
212    /// An account can only be closed if the confidential withheld fee is zero
213    #[error("An account can only be closed if the confidential withheld fee is zero")]
214    ConfidentialTransferFeeAccountHasWithheldFee,
215    /// A mint or an account is initialized to an invalid combination of
216    /// extensions
217    #[error("A mint or an account is initialized to an invalid combination of extensions")]
218    InvalidExtensionCombination,
219    /// Extension allocation with overwrite must use the same length
220    #[error("Extension allocation with overwrite must use the same length")]
221    InvalidLengthForAlloc,
222    /// Failed to decrypt a confidential transfer account
223    #[error("Failed to decrypt a confidential transfer account")]
224    AccountDecryption,
225    /// Failed to generate a zero-knowledge proof needed for a token instruction
226    #[error("Failed to generate proof")]
227    ProofGeneration,
228
229    // 55
230    /// An invalid proof instruction offset was provided
231    #[error("An invalid proof instruction offset was provided")]
232    InvalidProofInstructionOffset,
233    /// Harvest of withheld tokens to mint is disabled
234    #[error("Harvest of withheld tokens to mint is disabled")]
235    HarvestToMintDisabled,
236    /// Split proof context state accounts not supported for instruction
237    #[error("Split proof context state accounts not supported for instruction")]
238    SplitProofContextStateAccountsNotSupported,
239    /// Not enough proof context state accounts provided
240    #[error("Not enough proof context state accounts provided")]
241    NotEnoughProofContextStateAccounts,
242    /// Ciphertext is malformed
243    #[error("Ciphertext is malformed")]
244    MalformedCiphertext,
245
246    // 60
247    /// Ciphertext arithmetic failed
248    #[error("Ciphertext arithmetic failed")]
249    CiphertextArithmeticFailed,
250    /// Pedersen commitments did not match
251    #[error("Pedersen commitment mismatch")]
252    PedersenCommitmentMismatch,
253    /// Range proof length did not match
254    #[error("Range proof length mismatch")]
255    RangeProofLengthMismatch,
256    /// Illegal transfer amount bit length
257    #[error("Illegal transfer amount bit length")]
258    IllegalBitLength,
259    /// Fee calculation failed
260    #[error("Fee calculation failed")]
261    FeeCalculation,
262
263    //65
264    /// Withdraw / Deposit not allowed for confidential-mint-burn
265    #[error("Withdraw / Deposit not allowed for confidential-mint-burn")]
266    IllegalMintBurnConversion,
267    /// Invalid scale for scaled ui amount
268    #[error("Invalid scale for scaled ui amount")]
269    InvalidScale,
270    /// Transferring, minting, and burning is paused on this mint
271    #[error("Transferring, minting, and burning is paused on this mint")]
272    MintPaused,
273    /// Pending supply is not zero
274    #[error("Key rotation attempted while pending balance is not zero")]
275    PendingBalanceNonZero,
276    /// Mint address is tombstoned
277    #[error("Mint address has been retired (tombstoned)")]
278    MintRetired,
279    /// Tombstone already exists
280    #[error("Tombstone already exists")]
281    TombstoneAlreadyExists,
282    /// Tombstone PDA address mismatch
283    #[error("Expected tombstone PDA address mismatch")]
284    TombstoneAddressMismatch,
285}
286impl From<TokenError> for ProgramError {
287    fn from(e: TokenError) -> Self {
288        ProgramError::Custom(e as u32)
289    }
290}
291impl TryFrom<u32> for TokenError {
292    type Error = ProgramError;
293    fn try_from(code: u32) -> Result<Self, Self::Error> {
294        num_traits::FromPrimitive::from_u32(code).ok_or(ProgramError::InvalidArgument)
295    }
296}
297
298impl ToStr for TokenError {
299    fn to_str(&self) -> &'static str {
300        match self {
301            TokenError::NotRentExempt => "Error: Kelvin balance below rent-exempt threshold",
302            TokenError::InsufficientFunds => "Error: insufficient funds",
303            TokenError::InvalidMint => "Error: Invalid Mint",
304            TokenError::MintMismatch => "Error: Account not associated with this Mint",
305            TokenError::OwnerMismatch => "Error: owner does not match",
306            TokenError::FixedSupply => "Error: the total supply of this token is fixed",
307            TokenError::AlreadyInUse => "Error: account or token already in use",
308            TokenError::InvalidNumberOfProvidedSigners => {
309                "Error: Invalid number of provided signers"
310            }
311            TokenError::InvalidNumberOfRequiredSigners => {
312                "Error: Invalid number of required signers"
313            }
314            TokenError::UninitializedState => "Error: State is uninitialized",
315            TokenError::NativeNotSupported => {
316                "Error: Instruction does not support native tokens"
317            }
318            TokenError::NonNativeHasBalance => {
319                "Error: Non-native account can only be closed if its balance is zero"
320            }
321            TokenError::InvalidInstruction => "Error: Invalid instruction",
322            TokenError::InvalidState => "Error: Invalid account state for operation",
323            TokenError::Overflow => "Error: Operation overflowed",
324            TokenError::AuthorityTypeNotSupported => {
325                "Error: Account does not support specified authority type"
326            }
327            TokenError::MintCannotFreeze => "Error: This token mint cannot freeze accounts",
328            TokenError::AccountFrozen => "Error: Account is frozen",
329            TokenError::MintDecimalsMismatch => {
330                "Error: decimals different from the Mint decimals"
331            }
332            TokenError::NonNativeNotSupported => {
333                "Error: Instruction does not support non-native tokens"
334            }
335            TokenError::ExtensionTypeMismatch => {
336                "Error: New extension type does not match already existing extensions"
337            }
338            TokenError::ExtensionBaseMismatch => {
339                "Error: Extension does not match the base type provided"
340            }
341            TokenError::ExtensionAlreadyInitialized => {
342                "Error: Extension already initialized on this account"
343            }
344            TokenError::ConfidentialTransferAccountHasBalance => {
345                "Error: An account can only be closed if its confidential balance is zero"
346            }
347            TokenError::ConfidentialTransferAccountNotApproved => {
348                "Error: Account not approved for confidential transfers"
349            }
350            TokenError::ConfidentialTransferDepositsAndTransfersDisabled => {
351                "Error: Account not accepting deposits or transfers"
352            }
353            TokenError::ConfidentialTransferElGamalPubkeyMismatch => {
354                "Error: ElGamal public key mismatch"
355            }
356            TokenError::ConfidentialTransferBalanceMismatch => {
357                "Error: Balance mismatch"
358            }
359            TokenError::MintHasSupply => {
360                "Error: Mint has non-zero supply. Burn all tokens before closing the mint"
361            }
362            TokenError::NoAuthorityExists => {
363                "Error: No authority exists to perform the desired operation"
364            }
365            TokenError::TransferFeeExceedsMaximum => {
366                "Error: Transfer fee exceeds maximum of 10,000 basis points"
367            }
368            TokenError::MintRequiredForTransfer => {
369                "Mint required for this account to transfer tokens, use `transfer_checked` or `transfer_checked_with_fee`"
370            }
371            TokenError::FeeMismatch => {
372                "Calculated fee does not match expected fee"
373            }
374            TokenError::FeeParametersMismatch => {
375                "Fee parameters associated with zero-knowledge proofs do not match fee parameters in mint"
376            }
377            TokenError::ImmutableOwner => {
378                "The owner authority cannot be changed"
379            }
380            TokenError::AccountHasWithheldTransferFees => {
381                "Error: An account can only be closed if its withheld fee balance is zero, harvest fees to the mint and try again"
382            }
383            TokenError::NoMemo => {
384                "Error: No memo in previous instruction required for recipient to receive a transfer"
385            }
386            TokenError::NonTransferable => {
387                "Transfer is disabled for this mint"
388            }
389            TokenError::NonTransferableNeedsImmutableOwnership => {
390                "Non-transferable tokens can't be minted to an account without immutable ownership"
391            }
392            TokenError::MaximumPendingBalanceCreditCounterExceeded => {
393                "The total number of `Deposit` and `Transfer` instructions to an account cannot exceed the associated `maximum_pending_balance_credit_counter`"
394            }
395            TokenError::MaximumDepositAmountExceeded => {
396                "Deposit amount exceeds maximum limit"
397            }
398            TokenError::CpiGuardSettingsLocked => {
399                "CPI Guard status cannot be changed in CPI"
400            }
401            TokenError::CpiGuardTransferBlocked => {
402                "CPI Guard is enabled, and a program attempted to transfer user funds without using a delegate"
403            }
404            TokenError::CpiGuardBurnBlocked => {
405                "CPI Guard is enabled, and a program attempted to burn user funds without using a delegate"
406            }
407            TokenError::CpiGuardCloseAccountBlocked => {
408                "CPI Guard is enabled, and a program attempted to close an account without returning kelvins to owner"
409            }
410            TokenError::CpiGuardApproveBlocked => {
411                "CPI Guard is enabled, and a program attempted to approve a delegate"
412            }
413            TokenError::CpiGuardSetAuthorityBlocked => {
414                "CPI Guard is enabled, and a program attempted to add or change an authority"
415            }
416            TokenError::CpiGuardOwnerChangeBlocked => {
417                "Account ownership cannot be changed while CPI Guard is enabled"
418            }
419            TokenError::ExtensionNotFound => {
420                "Extension not found in account data"
421            }
422            TokenError::NonConfidentialTransfersDisabled => {
423                "Non-confidential transfers disabled"
424            }
425            TokenError::ConfidentialTransferFeeAccountHasWithheldFee => {
426                "Account has non-zero confidential withheld fee"
427            }
428            TokenError::InvalidExtensionCombination => {
429                "Mint or account is initialized to an invalid combination of extensions"
430            }
431            TokenError::InvalidLengthForAlloc => {
432                "Extension allocation with overwrite must use the same length"
433            }
434            TokenError::AccountDecryption => {
435                "Failed to decrypt a confidential transfer account"
436            }
437            TokenError::ProofGeneration => {
438                "Failed to generate proof"
439            }
440            TokenError::InvalidProofInstructionOffset => {
441                "An invalid proof instruction offset was provided"
442            }
443            TokenError::HarvestToMintDisabled => {
444                "Harvest of withheld tokens to mint is disabled"
445            }
446            TokenError::SplitProofContextStateAccountsNotSupported => {
447                "Split proof context state accounts not supported for instruction"
448            }
449            TokenError::NotEnoughProofContextStateAccounts => {
450                "Not enough proof context state accounts provided"
451            }
452            TokenError::MalformedCiphertext => {
453                "Ciphertext is malformed"
454            }
455            TokenError::CiphertextArithmeticFailed => {
456                "Ciphertext arithmetic failed"
457            }
458            TokenError::PedersenCommitmentMismatch => {
459                "Pedersen commitments did not match"
460            }
461            TokenError::RangeProofLengthMismatch => {
462                "Range proof lengths did not match"
463            }
464            TokenError::IllegalBitLength => {
465                "Illegal transfer amount bit length"
466            }
467            TokenError::FeeCalculation => {
468                "Transfer fee calculation failed"
469            }
470            TokenError::IllegalMintBurnConversion => {
471                "Conversions from normal to confidential token balance and vice versa are illegal if the confidential-mint-burn extension is enabled"
472            }
473            TokenError::InvalidScale => {
474                "Invalid scale for scaled ui amount"
475            }
476            TokenError::MintPaused => {
477                "Transferring, minting, and burning is paused on this mint"
478            }
479            TokenError::PendingBalanceNonZero => {
480                "Key rotation attempted while pending balance is not zero"
481            }
482            TokenError::MintRetired => "Mint address has been retired (tombstoned)",
483            TokenError::TombstoneAlreadyExists => "Tombstone already exists",
484            TokenError::TombstoneAddressMismatch => "Expected tombstone PDA address mismatch",
485        }
486    }
487}
488
489#[cfg(not(target_os = "solana"))]
490impl From<TokenProofGenerationError> for TokenError {
491    fn from(e: TokenProofGenerationError) -> Self {
492        match e {
493            TokenProofGenerationError::ProofGeneration(_) => TokenError::ProofGeneration,
494            TokenProofGenerationError::NotEnoughFunds => TokenError::InsufficientFunds,
495            TokenProofGenerationError::IllegalAmountBitLength => TokenError::IllegalBitLength,
496            TokenProofGenerationError::FeeCalculation => TokenError::FeeCalculation,
497            TokenProofGenerationError::CiphertextExtraction => TokenError::MalformedCiphertext,
498        }
499    }
500}
501
502impl From<TokenProofExtractionError> for TokenError {
503    fn from(e: TokenProofExtractionError) -> Self {
504        match e {
505            TokenProofExtractionError::ElGamalPubkeyMismatch => {
506                TokenError::ConfidentialTransferElGamalPubkeyMismatch
507            }
508            TokenProofExtractionError::PedersenCommitmentMismatch => {
509                TokenError::PedersenCommitmentMismatch
510            }
511            TokenProofExtractionError::RangeProofLengthMismatch => {
512                TokenError::RangeProofLengthMismatch
513            }
514            TokenProofExtractionError::FeeParametersMismatch => TokenError::FeeParametersMismatch,
515            TokenProofExtractionError::CurveArithmetic => TokenError::CiphertextArithmeticFailed,
516            TokenProofExtractionError::CiphertextExtraction => TokenError::MalformedCiphertext,
517        }
518    }
519}
520
521impl DecodeError<TokenError> for TokenError {
522    fn type_of() -> &'static str {
523        "TokenError"
524    }
525    fn decode_custom_error_to_enum(e: u32) -> Option<TokenError> {
526        TokenError::from_u32(e)
527    }
528}
529
530impl PrintProgramError for TokenError {
531    fn print<E>(&self)
532    where
533        E: 'static
534            + std::error::Error
535            + rialo_s_decode_error::DecodeError<E>
536            + PrintProgramError
537            + num_traits::FromPrimitive,
538    {
539        msg!("{}", self.to_str());
540    }
541}
542
543#[cfg(test)]
544mod test {
545    use strum::IntoEnumIterator;
546
547    use super::*;
548    #[test]
549    fn test_parse_error_from_primitive_exhaustive() {
550        for variant in TokenError::iter() {
551            let variant_u32 = variant as u32;
552            assert_eq!(
553                TokenError::from_repr(variant_u32).unwrap(),
554                TokenError::try_from(variant_u32).unwrap()
555            );
556        }
557    }
558}