rialo_spl_token_2022_interface/
error.rs1use 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#[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 #[error("Kelvin balance below rent-exempt threshold")]
23 NotRentExempt,
24 #[error("Insufficient funds")]
26 InsufficientFunds,
27 #[error("Invalid Mint")]
29 InvalidMint,
30 #[error("Account not associated with this Mint")]
32 MintMismatch,
33 #[error("Owner does not match")]
35 OwnerMismatch,
36
37 #[error("Fixed supply")]
40 FixedSupply,
41 #[error("Already in use")]
43 AlreadyInUse,
44 #[error("Invalid number of provided signers")]
46 InvalidNumberOfProvidedSigners,
47 #[error("Invalid number of required signers")]
49 InvalidNumberOfRequiredSigners,
50 #[error("State is uninitialized")]
52 UninitializedState,
53
54 #[error("Instruction does not support native tokens")]
57 NativeNotSupported,
58 #[error("Non-native account can only be closed if its balance is zero")]
60 NonNativeHasBalance,
61 #[error("Invalid instruction")]
63 InvalidInstruction,
64 #[error("State is invalid for requested operation")]
66 InvalidState,
67 #[error("Operation overflowed")]
69 Overflow,
70
71 #[error("Account does not support specified authority type")]
74 AuthorityTypeNotSupported,
75 #[error("This token mint cannot freeze accounts")]
77 MintCannotFreeze,
78 #[error("Account is frozen")]
80 AccountFrozen,
81 #[error("The provided decimals value different from the Mint decimals")]
83 MintDecimalsMismatch,
84 #[error("Instruction does not support non-native tokens")]
86 NonNativeNotSupported,
87
88 #[error("Extension type does not match already existing extensions")]
91 ExtensionTypeMismatch,
92 #[error("Extension does not match the base type provided")]
94 ExtensionBaseMismatch,
95 #[error("Extension already initialized on this account")]
97 ExtensionAlreadyInitialized,
98 #[error("An account can only be closed if its confidential balance is zero")]
100 ConfidentialTransferAccountHasBalance,
101 #[error("Account not approved for confidential transfers")]
103 ConfidentialTransferAccountNotApproved,
104
105 #[error("Account not accepting deposits or transfers")]
108 ConfidentialTransferDepositsAndTransfersDisabled,
109 #[error("ElGamal public key mismatch")]
111 ConfidentialTransferElGamalPubkeyMismatch,
112 #[error("Balance mismatch")]
114 ConfidentialTransferBalanceMismatch,
115 #[error("Mint has non-zero supply. Burn all tokens before closing the mint")]
117 MintHasSupply,
118 #[error("No authority exists to perform the desired operation")]
120 NoAuthorityExists,
121
122 #[error("Transfer fee exceeds maximum of 10,000 basis points")]
125 TransferFeeExceedsMaximum,
126 #[error("Mint required for this account to transfer tokens, use `transfer_checked` or `transfer_checked_with_fee`")]
129 MintRequiredForTransfer,
130 #[error("Calculated fee does not match expected fee")]
132 FeeMismatch,
133 #[error(
136 "Fee parameters associated with zero-knowledge proofs do not match fee parameters in mint"
137 )]
138 FeeParametersMismatch,
139 #[error("The owner authority cannot be changed")]
141 ImmutableOwner,
142
143 #[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 #[error("No memo in previous instruction; required for recipient to receive a transfer")]
151 NoMemo,
152 #[error("Transfer is disabled for this mint")]
154 NonTransferable,
155 #[error("Non-transferable tokens can't be minted to an account without immutable ownership")]
158 NonTransferableNeedsImmutableOwnership,
159 #[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 #[error("Deposit amount exceeds maximum limit")]
172 MaximumDepositAmountExceeded,
173 #[error("CPI Guard cannot be enabled or disabled in CPI")]
175 CpiGuardSettingsLocked,
176 #[error("CPI Guard is enabled, and a program attempted to transfer user funds via CPI without using a delegate")]
179 CpiGuardTransferBlocked,
180 #[error(
183 "CPI Guard is enabled, and a program attempted to burn user funds via CPI without using a delegate"
184 )]
185 CpiGuardBurnBlocked,
186 #[error("CPI Guard is enabled, and a program attempted to close an account via CPI without returning kelvins to owner")]
189 CpiGuardCloseAccountBlocked,
190
191 #[error("CPI Guard is enabled, and a program attempted to approve a delegate via CPI")]
194 CpiGuardApproveBlocked,
195 #[error(
198 "CPI Guard is enabled, and a program attempted to add or replace an authority via CPI"
199 )]
200 CpiGuardSetAuthorityBlocked,
201 #[error("Account ownership cannot be changed while CPI Guard is enabled")]
203 CpiGuardOwnerChangeBlocked,
204 #[error("Extension not found in account data")]
206 ExtensionNotFound,
207 #[error("Non-confidential transfers disabled")]
209 NonConfidentialTransfersDisabled,
210
211 #[error("An account can only be closed if the confidential withheld fee is zero")]
214 ConfidentialTransferFeeAccountHasWithheldFee,
215 #[error("A mint or an account is initialized to an invalid combination of extensions")]
218 InvalidExtensionCombination,
219 #[error("Extension allocation with overwrite must use the same length")]
221 InvalidLengthForAlloc,
222 #[error("Failed to decrypt a confidential transfer account")]
224 AccountDecryption,
225 #[error("Failed to generate proof")]
227 ProofGeneration,
228
229 #[error("An invalid proof instruction offset was provided")]
232 InvalidProofInstructionOffset,
233 #[error("Harvest of withheld tokens to mint is disabled")]
235 HarvestToMintDisabled,
236 #[error("Split proof context state accounts not supported for instruction")]
238 SplitProofContextStateAccountsNotSupported,
239 #[error("Not enough proof context state accounts provided")]
241 NotEnoughProofContextStateAccounts,
242 #[error("Ciphertext is malformed")]
244 MalformedCiphertext,
245
246 #[error("Ciphertext arithmetic failed")]
249 CiphertextArithmeticFailed,
250 #[error("Pedersen commitment mismatch")]
252 PedersenCommitmentMismatch,
253 #[error("Range proof length mismatch")]
255 RangeProofLengthMismatch,
256 #[error("Illegal transfer amount bit length")]
258 IllegalBitLength,
259 #[error("Fee calculation failed")]
261 FeeCalculation,
262
263 #[error("Withdraw / Deposit not allowed for confidential-mint-burn")]
266 IllegalMintBurnConversion,
267 #[error("Invalid scale for scaled ui amount")]
269 InvalidScale,
270 #[error("Transferring, minting, and burning is paused on this mint")]
272 MintPaused,
273 #[error("Key rotation attempted while pending balance is not zero")]
275 PendingBalanceNonZero,
276 #[error("Mint address has been retired (tombstoned)")]
278 MintRetired,
279 #[error("Tombstone already exists")]
281 TombstoneAlreadyExists,
282 #[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}