use {
num_derive::FromPrimitive,
solana_program_error::{ProgramError, ToStr},
spl_token_confidential_transfer_proof_extraction::errors::TokenProofExtractionError,
thiserror::Error,
};
#[repr(u32)]
#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
pub enum TokenError {
#[error("Lamport balance below rent-exempt threshold")]
NotRentExempt,
#[error("Insufficient funds")]
InsufficientFunds,
#[error("Invalid Mint")]
InvalidMint,
#[error("Account not associated with this Mint")]
MintMismatch,
#[error("Owner does not match")]
OwnerMismatch,
#[error("Fixed supply")]
FixedSupply,
#[error("Already in use")]
AlreadyInUse,
#[error("Invalid number of provided signers")]
InvalidNumberOfProvidedSigners,
#[error("Invalid number of required signers")]
InvalidNumberOfRequiredSigners,
#[error("State is uninitialized")]
UninitializedState,
#[error("Instruction does not support native tokens")]
NativeNotSupported,
#[error("Non-native account can only be closed if its balance is zero")]
NonNativeHasBalance,
#[error("Invalid instruction")]
InvalidInstruction,
#[error("State is invalid for requested operation")]
InvalidState,
#[error("Operation overflowed")]
Overflow,
#[error("Account does not support specified authority type")]
AuthorityTypeNotSupported,
#[error("This token mint cannot freeze accounts")]
MintCannotFreeze,
#[error("Account is frozen")]
AccountFrozen,
#[error("The provided decimals value different from the Mint decimals")]
MintDecimalsMismatch,
#[error("Instruction does not support non-native tokens")]
NonNativeNotSupported,
#[error("Extension type does not match already existing extensions")]
ExtensionTypeMismatch,
#[error("Extension does not match the base type provided")]
ExtensionBaseMismatch,
#[error("Extension already initialized on this account")]
ExtensionAlreadyInitialized,
#[error("An account can only be closed if its confidential balance is zero")]
ConfidentialTransferAccountHasBalance,
#[error("Account not approved for confidential transfers")]
ConfidentialTransferAccountNotApproved,
#[error("Account not accepting deposits or transfers")]
ConfidentialTransferDepositsAndTransfersDisabled,
#[error("ElGamal public key mismatch")]
ConfidentialTransferElGamalPubkeyMismatch,
#[error("Balance mismatch")]
ConfidentialTransferBalanceMismatch,
#[error("Mint has non-zero supply. Burn all tokens before closing the mint")]
MintHasSupply,
#[error("No authority exists to perform the desired operation")]
NoAuthorityExists,
#[error("Transfer fee exceeds maximum of 10,000 basis points")]
TransferFeeExceedsMaximum,
#[error("Mint required for this account to transfer tokens, use `transfer_checked` or `transfer_checked_with_fee`")]
MintRequiredForTransfer,
#[error("Calculated fee does not match expected fee")]
FeeMismatch,
#[error(
"Fee parameters associated with zero-knowledge proofs do not match fee parameters in mint"
)]
FeeParametersMismatch,
#[error("The owner authority cannot be changed")]
ImmutableOwner,
#[error("An account can only be closed if its withheld fee balance is zero, harvest fees to the mint and try again")]
AccountHasWithheldTransferFees,
#[error("No memo in previous instruction; required for recipient to receive a transfer")]
NoMemo,
#[error("Transfer is disabled for this mint")]
NonTransferable,
#[error("Non-transferable tokens can't be minted to an account without immutable ownership")]
NonTransferableNeedsImmutableOwnership,
#[error(
"The total number of `Deposit` and `Transfer` instructions to an account cannot exceed
the associated `maximum_pending_balance_credit_counter`"
)]
MaximumPendingBalanceCreditCounterExceeded,
#[error("Deposit amount exceeds maximum limit")]
MaximumDepositAmountExceeded,
#[error("CPI Guard cannot be enabled or disabled in CPI")]
CpiGuardSettingsLocked,
#[error("CPI Guard is enabled, and a program attempted to transfer user funds via CPI without using a delegate")]
CpiGuardTransferBlocked,
#[error(
"CPI Guard is enabled, and a program attempted to burn user funds via CPI without using a delegate"
)]
CpiGuardBurnBlocked,
#[error("CPI Guard is enabled, and a program attempted to close an account via CPI without returning lamports to owner")]
CpiGuardCloseAccountBlocked,
#[error("CPI Guard is enabled, and a program attempted to approve a delegate via CPI")]
CpiGuardApproveBlocked,
#[error(
"CPI Guard is enabled, and a program attempted to add or replace an authority via CPI"
)]
CpiGuardSetAuthorityBlocked,
#[error("Account ownership cannot be changed while CPI Guard is enabled")]
CpiGuardOwnerChangeBlocked,
#[error("Extension not found in account data")]
ExtensionNotFound,
#[error("Non-confidential transfers disabled")]
NonConfidentialTransfersDisabled,
#[error("An account can only be closed if the confidential withheld fee is zero")]
ConfidentialTransferFeeAccountHasWithheldFee,
#[error("A mint or an account is initialized to an invalid combination of extensions")]
InvalidExtensionCombination,
#[error("Extension allocation with overwrite must use the same length")]
InvalidLengthForAlloc,
#[error("Failed to decrypt a confidential transfer account")]
AccountDecryption,
#[error("Failed to generate proof")]
ProofGeneration,
#[error("An invalid proof instruction offset was provided")]
InvalidProofInstructionOffset,
#[error("Harvest of withheld tokens to mint is disabled")]
HarvestToMintDisabled,
#[error("Split proof context state accounts not supported for instruction")]
SplitProofContextStateAccountsNotSupported,
#[error("Not enough proof context state accounts provided")]
NotEnoughProofContextStateAccounts,
#[error("Ciphertext is malformed")]
MalformedCiphertext,
#[error("Ciphertext arithmetic failed")]
CiphertextArithmeticFailed,
#[error("Pedersen commitment mismatch")]
PedersenCommitmentMismatch,
#[error("Range proof length mismatch")]
RangeProofLengthMismatch,
#[error("Illegal transfer amount bit length")]
IllegalBitLength,
#[error("Fee calculation failed")]
FeeCalculation,
#[error("Withdraw / Deposit not allowed for confidential-mint-burn")]
IllegalMintBurnConversion,
#[error("Invalid scale for scaled ui amount")]
InvalidScale,
#[error("Transferring, minting, and burning is paused on this mint")]
MintPaused,
#[error("Key rotation attempted while pending balance is not zero")]
PendingBalanceNonZero,
}
impl From<TokenError> for ProgramError {
fn from(e: TokenError) -> Self {
ProgramError::Custom(e as u32)
}
}
impl TryFrom<u32> for TokenError {
type Error = ProgramError;
fn try_from(code: u32) -> Result<Self, Self::Error> {
num_traits::FromPrimitive::from_u32(code).ok_or(ProgramError::InvalidArgument)
}
}
impl ToStr for TokenError {
fn to_str(&self) -> &'static str {
match self {
TokenError::NotRentExempt => "Error: Lamport balance below rent-exempt threshold",
TokenError::InsufficientFunds => "Error: insufficient funds",
TokenError::InvalidMint => "Error: Invalid Mint",
TokenError::MintMismatch => "Error: Account not associated with this Mint",
TokenError::OwnerMismatch => "Error: owner does not match",
TokenError::FixedSupply => "Error: the total supply of this token is fixed",
TokenError::AlreadyInUse => "Error: account or token already in use",
TokenError::InvalidNumberOfProvidedSigners => {
"Error: Invalid number of provided signers"
}
TokenError::InvalidNumberOfRequiredSigners => {
"Error: Invalid number of required signers"
}
TokenError::UninitializedState => "Error: State is uninitialized",
TokenError::NativeNotSupported => {
"Error: Instruction does not support native tokens"
}
TokenError::NonNativeHasBalance => {
"Error: Non-native account can only be closed if its balance is zero"
}
TokenError::InvalidInstruction => "Error: Invalid instruction",
TokenError::InvalidState => "Error: Invalid account state for operation",
TokenError::Overflow => "Error: Operation overflowed",
TokenError::AuthorityTypeNotSupported => {
"Error: Account does not support specified authority type"
}
TokenError::MintCannotFreeze => "Error: This token mint cannot freeze accounts",
TokenError::AccountFrozen => "Error: Account is frozen",
TokenError::MintDecimalsMismatch => {
"Error: decimals different from the Mint decimals"
}
TokenError::NonNativeNotSupported => {
"Error: Instruction does not support non-native tokens"
}
TokenError::ExtensionTypeMismatch => {
"Error: New extension type does not match already existing extensions"
}
TokenError::ExtensionBaseMismatch => {
"Error: Extension does not match the base type provided"
}
TokenError::ExtensionAlreadyInitialized => {
"Error: Extension already initialized on this account"
}
TokenError::ConfidentialTransferAccountHasBalance => {
"Error: An account can only be closed if its confidential balance is zero"
}
TokenError::ConfidentialTransferAccountNotApproved => {
"Error: Account not approved for confidential transfers"
}
TokenError::ConfidentialTransferDepositsAndTransfersDisabled => {
"Error: Account not accepting deposits or transfers"
}
TokenError::ConfidentialTransferElGamalPubkeyMismatch => {
"Error: ElGamal public key mismatch"
}
TokenError::ConfidentialTransferBalanceMismatch => {
"Error: Balance mismatch"
}
TokenError::MintHasSupply => {
"Error: Mint has non-zero supply. Burn all tokens before closing the mint"
}
TokenError::NoAuthorityExists => {
"Error: No authority exists to perform the desired operation"
}
TokenError::TransferFeeExceedsMaximum => {
"Error: Transfer fee exceeds maximum of 10,000 basis points"
}
TokenError::MintRequiredForTransfer => {
"Mint required for this account to transfer tokens, use `transfer_checked` or `transfer_checked_with_fee`"
}
TokenError::FeeMismatch => {
"Calculated fee does not match expected fee"
}
TokenError::FeeParametersMismatch => {
"Fee parameters associated with zero-knowledge proofs do not match fee parameters in mint"
}
TokenError::ImmutableOwner => {
"The owner authority cannot be changed"
}
TokenError::AccountHasWithheldTransferFees => {
"Error: An account can only be closed if its withheld fee balance is zero, harvest fees to the mint and try again"
}
TokenError::NoMemo => {
"Error: No memo in previous instruction required for recipient to receive a transfer"
}
TokenError::NonTransferable => {
"Transfer is disabled for this mint"
}
TokenError::NonTransferableNeedsImmutableOwnership => {
"Non-transferable tokens can't be minted to an account without immutable ownership"
}
TokenError::MaximumPendingBalanceCreditCounterExceeded => {
"The total number of `Deposit` and `Transfer` instructions to an account cannot exceed the associated `maximum_pending_balance_credit_counter`"
}
TokenError::MaximumDepositAmountExceeded => {
"Deposit amount exceeds maximum limit"
}
TokenError::CpiGuardSettingsLocked => {
"CPI Guard status cannot be changed in CPI"
}
TokenError::CpiGuardTransferBlocked => {
"CPI Guard is enabled, and a program attempted to transfer user funds without using a delegate"
}
TokenError::CpiGuardBurnBlocked => {
"CPI Guard is enabled, and a program attempted to burn user funds without using a delegate"
}
TokenError::CpiGuardCloseAccountBlocked => {
"CPI Guard is enabled, and a program attempted to close an account without returning lamports to owner"
}
TokenError::CpiGuardApproveBlocked => {
"CPI Guard is enabled, and a program attempted to approve a delegate"
}
TokenError::CpiGuardSetAuthorityBlocked => {
"CPI Guard is enabled, and a program attempted to add or change an authority"
}
TokenError::CpiGuardOwnerChangeBlocked => {
"Account ownership cannot be changed while CPI Guard is enabled"
}
TokenError::ExtensionNotFound => {
"Extension not found in account data"
}
TokenError::NonConfidentialTransfersDisabled => {
"Non-confidential transfers disabled"
}
TokenError::ConfidentialTransferFeeAccountHasWithheldFee => {
"Account has non-zero confidential withheld fee"
}
TokenError::InvalidExtensionCombination => {
"Mint or account is initialized to an invalid combination of extensions"
}
TokenError::InvalidLengthForAlloc => {
"Extension allocation with overwrite must use the same length"
}
TokenError::AccountDecryption => {
"Failed to decrypt a confidential transfer account"
}
TokenError::ProofGeneration => {
"Failed to generate proof"
}
TokenError::InvalidProofInstructionOffset => {
"An invalid proof instruction offset was provided"
}
TokenError::HarvestToMintDisabled => {
"Harvest of withheld tokens to mint is disabled"
}
TokenError::SplitProofContextStateAccountsNotSupported => {
"Split proof context state accounts not supported for instruction"
}
TokenError::NotEnoughProofContextStateAccounts => {
"Not enough proof context state accounts provided"
}
TokenError::MalformedCiphertext => {
"Ciphertext is malformed"
}
TokenError::CiphertextArithmeticFailed => {
"Ciphertext arithmetic failed"
}
TokenError::PedersenCommitmentMismatch => {
"Pedersen commitments did not match"
}
TokenError::RangeProofLengthMismatch => {
"Range proof lengths did not match"
}
TokenError::IllegalBitLength => {
"Illegal transfer amount bit length"
}
TokenError::FeeCalculation => {
"Transfer fee calculation failed"
}
TokenError::IllegalMintBurnConversion => {
"Conversions from normal to confidential token balance and vice versa are illegal if the confidential-mint-burn extension is enabled"
}
TokenError::InvalidScale => {
"Invalid scale for scaled ui amount"
}
TokenError::MintPaused => {
"Transferring, minting, and burning is paused on this mint"
}
TokenError::PendingBalanceNonZero => {
"Key rotation attempted while pending balance is not zero"
}
}
}
}
impl From<TokenProofExtractionError> for TokenError {
fn from(e: TokenProofExtractionError) -> Self {
match e {
TokenProofExtractionError::ElGamalPubkeyMismatch => {
TokenError::ConfidentialTransferElGamalPubkeyMismatch
}
TokenProofExtractionError::PedersenCommitmentMismatch => {
TokenError::PedersenCommitmentMismatch
}
TokenProofExtractionError::RangeProofLengthMismatch => {
TokenError::RangeProofLengthMismatch
}
TokenProofExtractionError::FeeParametersMismatch => TokenError::FeeParametersMismatch,
TokenProofExtractionError::CurveArithmetic => TokenError::CiphertextArithmeticFailed,
TokenProofExtractionError::CiphertextExtraction => TokenError::MalformedCiphertext,
}
}
}
#[cfg(test)]
mod test {
use {super::*, strum::IntoEnumIterator};
#[test]
fn test_parse_error_from_primitive_exhaustive() {
for variant in TokenError::iter() {
let variant_u32 = variant as u32;
assert_eq!(
TokenError::from_repr(variant_u32).unwrap(),
TokenError::try_from(variant_u32).unwrap()
);
}
}
}