cbe_program/
program_error.rs

1//! The [`ProgramError`] type and related definitions.
2
3#![allow(clippy::integer_arithmetic)]
4use {
5    crate::{decode_error::DecodeError, instruction::InstructionError, msg, pubkey::PubkeyError},
6    borsh::maybestd::io::Error as BorshIoError,
7    num_traits::{FromPrimitive, ToPrimitive},
8    std::convert::TryFrom,
9    thiserror::Error,
10};
11
12/// Reasons the program may fail
13#[derive(Clone, Debug, Deserialize, Eq, Error, PartialEq, Serialize)]
14pub enum ProgramError {
15    /// Allows on-chain programs to implement program-specific error types and see them returned
16    /// by the Cartallum CBE runtime. A program-specific error may be any type that is represented as
17    /// or serialized to a u32 integer.
18    #[error("Custom program error: {0:#x}")]
19    Custom(u32),
20    #[error("The arguments provided to a program instruction where invalid")]
21    InvalidArgument,
22    #[error("An instruction's data contents was invalid")]
23    InvalidInstructionData,
24    #[error("An account's data contents was invalid")]
25    InvalidAccountData,
26    #[error("An account's data was too small")]
27    AccountDataTooSmall,
28    #[error("An account's balance was too small to complete the instruction")]
29    InsufficientFunds,
30    #[error("The account did not have the expected program id")]
31    IncorrectProgramId,
32    #[error("A signature was required but not found")]
33    MissingRequiredSignature,
34    #[error("An initialize instruction was sent to an account that has already been initialized")]
35    AccountAlreadyInitialized,
36    #[error("An attempt to operate on an account that hasn't been initialized")]
37    UninitializedAccount,
38    #[error("The instruction expected additional account keys")]
39    NotEnoughAccountKeys,
40    #[error("Failed to borrow a reference to account data, already borrowed")]
41    AccountBorrowFailed,
42    #[error("Length of the seed is too long for address generation")]
43    MaxSeedLengthExceeded,
44    #[error("Provided seeds do not result in a valid address")]
45    InvalidSeeds,
46    #[error("IO Error: {0}")]
47    BorshIoError(String),
48    #[error("An account does not have enough scoobies to be rent-exempt")]
49    AccountNotRentExempt,
50    #[error("Unsupported sysvar")]
51    UnsupportedSysvar,
52    #[error("Provided owner is not allowed")]
53    IllegalOwner,
54    #[error("Accounts data allocations exceeded the maximum allowed per transaction")]
55    MaxAccountsDataAllocationsExceeded,
56    #[error("Account data reallocation was invalid")]
57    InvalidRealloc,
58    #[error("Instruction trace length exceeded the maximum allowed per transaction")]
59    MaxInstructionTraceLengthExceeded,
60}
61
62pub trait PrintProgramError {
63    fn print<E>(&self)
64    where
65        E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive;
66}
67
68impl PrintProgramError for ProgramError {
69    fn print<E>(&self)
70    where
71        E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
72    {
73        match self {
74            Self::Custom(error) => {
75                if let Some(custom_error) = E::decode_custom_error_to_enum(*error) {
76                    custom_error.print::<E>();
77                } else {
78                    msg!("Error: Unknown");
79                }
80            }
81            Self::InvalidArgument => msg!("Error: InvalidArgument"),
82            Self::InvalidInstructionData => msg!("Error: InvalidInstructionData"),
83            Self::InvalidAccountData => msg!("Error: InvalidAccountData"),
84            Self::AccountDataTooSmall => msg!("Error: AccountDataTooSmall"),
85            Self::InsufficientFunds => msg!("Error: InsufficientFunds"),
86            Self::IncorrectProgramId => msg!("Error: IncorrectProgramId"),
87            Self::MissingRequiredSignature => msg!("Error: MissingRequiredSignature"),
88            Self::AccountAlreadyInitialized => msg!("Error: AccountAlreadyInitialized"),
89            Self::UninitializedAccount => msg!("Error: UninitializedAccount"),
90            Self::NotEnoughAccountKeys => msg!("Error: NotEnoughAccountKeys"),
91            Self::AccountBorrowFailed => msg!("Error: AccountBorrowFailed"),
92            Self::MaxSeedLengthExceeded => msg!("Error: MaxSeedLengthExceeded"),
93            Self::InvalidSeeds => msg!("Error: InvalidSeeds"),
94            Self::BorshIoError(_) => msg!("Error: BorshIoError"),
95            Self::AccountNotRentExempt => msg!("Error: AccountNotRentExempt"),
96            Self::UnsupportedSysvar => msg!("Error: UnsupportedSysvar"),
97            Self::IllegalOwner => msg!("Error: IllegalOwner"),
98            Self::MaxAccountsDataAllocationsExceeded => {
99                msg!("Error: MaxAccountsDataAllocationsExceeded")
100            }
101            Self::InvalidRealloc => msg!("Error: InvalidRealloc"),
102            Self::MaxInstructionTraceLengthExceeded => {
103                msg!("Error: MaxInstructionTraceLengthExceeded")
104            }
105        }
106    }
107}
108
109/// Builtin return values occupy the upper 32 bits
110const BUILTIN_BIT_SHIFT: usize = 32;
111macro_rules! to_builtin {
112    ($error:expr) => {
113        ($error as u64) << BUILTIN_BIT_SHIFT
114    };
115}
116
117pub const CUSTOM_ZERO: u64 = to_builtin!(1);
118pub const INVALID_ARGUMENT: u64 = to_builtin!(2);
119pub const INVALID_INSTRUCTION_DATA: u64 = to_builtin!(3);
120pub const INVALID_ACCOUNT_DATA: u64 = to_builtin!(4);
121pub const ACCOUNT_DATA_TOO_SMALL: u64 = to_builtin!(5);
122pub const INSUFFICIENT_FUNDS: u64 = to_builtin!(6);
123pub const INCORRECT_PROGRAM_ID: u64 = to_builtin!(7);
124pub const MISSING_REQUIRED_SIGNATURES: u64 = to_builtin!(8);
125pub const ACCOUNT_ALREADY_INITIALIZED: u64 = to_builtin!(9);
126pub const UNINITIALIZED_ACCOUNT: u64 = to_builtin!(10);
127pub const NOT_ENOUGH_ACCOUNT_KEYS: u64 = to_builtin!(11);
128pub const ACCOUNT_BORROW_FAILED: u64 = to_builtin!(12);
129pub const MAX_SEED_LENGTH_EXCEEDED: u64 = to_builtin!(13);
130pub const INVALID_SEEDS: u64 = to_builtin!(14);
131pub const BORSH_IO_ERROR: u64 = to_builtin!(15);
132pub const ACCOUNT_NOT_RENT_EXEMPT: u64 = to_builtin!(16);
133pub const UNSUPPORTED_SYSVAR: u64 = to_builtin!(17);
134pub const ILLEGAL_OWNER: u64 = to_builtin!(18);
135pub const MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED: u64 = to_builtin!(19);
136pub const INVALID_ACCOUNT_DATA_REALLOC: u64 = to_builtin!(20);
137pub const MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED: u64 = to_builtin!(21);
138// Warning: Any new program errors added here must also be:
139// - Added to the below conversions
140// - Added as an equivalent to InstructionError
141// - Be featureized in the BPF loader to return `InstructionError::InvalidError`
142//   until the feature is activated
143
144impl From<ProgramError> for u64 {
145    fn from(error: ProgramError) -> Self {
146        match error {
147            ProgramError::InvalidArgument => INVALID_ARGUMENT,
148            ProgramError::InvalidInstructionData => INVALID_INSTRUCTION_DATA,
149            ProgramError::InvalidAccountData => INVALID_ACCOUNT_DATA,
150            ProgramError::AccountDataTooSmall => ACCOUNT_DATA_TOO_SMALL,
151            ProgramError::InsufficientFunds => INSUFFICIENT_FUNDS,
152            ProgramError::IncorrectProgramId => INCORRECT_PROGRAM_ID,
153            ProgramError::MissingRequiredSignature => MISSING_REQUIRED_SIGNATURES,
154            ProgramError::AccountAlreadyInitialized => ACCOUNT_ALREADY_INITIALIZED,
155            ProgramError::UninitializedAccount => UNINITIALIZED_ACCOUNT,
156            ProgramError::NotEnoughAccountKeys => NOT_ENOUGH_ACCOUNT_KEYS,
157            ProgramError::AccountBorrowFailed => ACCOUNT_BORROW_FAILED,
158            ProgramError::MaxSeedLengthExceeded => MAX_SEED_LENGTH_EXCEEDED,
159            ProgramError::InvalidSeeds => INVALID_SEEDS,
160            ProgramError::BorshIoError(_) => BORSH_IO_ERROR,
161            ProgramError::AccountNotRentExempt => ACCOUNT_NOT_RENT_EXEMPT,
162            ProgramError::UnsupportedSysvar => UNSUPPORTED_SYSVAR,
163            ProgramError::IllegalOwner => ILLEGAL_OWNER,
164            ProgramError::MaxAccountsDataAllocationsExceeded => {
165                MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED
166            }
167            ProgramError::InvalidRealloc => INVALID_ACCOUNT_DATA_REALLOC,
168            ProgramError::MaxInstructionTraceLengthExceeded => {
169                MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED
170            }
171            ProgramError::Custom(error) => {
172                if error == 0 {
173                    CUSTOM_ZERO
174                } else {
175                    error as u64
176                }
177            }
178        }
179    }
180}
181
182impl From<u64> for ProgramError {
183    fn from(error: u64) -> Self {
184        match error {
185            CUSTOM_ZERO => Self::Custom(0),
186            INVALID_ARGUMENT => Self::InvalidArgument,
187            INVALID_INSTRUCTION_DATA => Self::InvalidInstructionData,
188            INVALID_ACCOUNT_DATA => Self::InvalidAccountData,
189            ACCOUNT_DATA_TOO_SMALL => Self::AccountDataTooSmall,
190            INSUFFICIENT_FUNDS => Self::InsufficientFunds,
191            INCORRECT_PROGRAM_ID => Self::IncorrectProgramId,
192            MISSING_REQUIRED_SIGNATURES => Self::MissingRequiredSignature,
193            ACCOUNT_ALREADY_INITIALIZED => Self::AccountAlreadyInitialized,
194            UNINITIALIZED_ACCOUNT => Self::UninitializedAccount,
195            NOT_ENOUGH_ACCOUNT_KEYS => Self::NotEnoughAccountKeys,
196            ACCOUNT_BORROW_FAILED => Self::AccountBorrowFailed,
197            MAX_SEED_LENGTH_EXCEEDED => Self::MaxSeedLengthExceeded,
198            INVALID_SEEDS => Self::InvalidSeeds,
199            BORSH_IO_ERROR => Self::BorshIoError("Unknown".to_string()),
200            ACCOUNT_NOT_RENT_EXEMPT => Self::AccountNotRentExempt,
201            UNSUPPORTED_SYSVAR => Self::UnsupportedSysvar,
202            ILLEGAL_OWNER => Self::IllegalOwner,
203            MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED => Self::MaxAccountsDataAllocationsExceeded,
204            INVALID_ACCOUNT_DATA_REALLOC => Self::InvalidRealloc,
205            MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED => Self::MaxInstructionTraceLengthExceeded,
206            _ => Self::Custom(error as u32),
207        }
208    }
209}
210
211impl TryFrom<InstructionError> for ProgramError {
212    type Error = InstructionError;
213
214    fn try_from(error: InstructionError) -> Result<Self, Self::Error> {
215        match error {
216            Self::Error::Custom(err) => Ok(Self::Custom(err)),
217            Self::Error::InvalidArgument => Ok(Self::InvalidArgument),
218            Self::Error::InvalidInstructionData => Ok(Self::InvalidInstructionData),
219            Self::Error::InvalidAccountData => Ok(Self::InvalidAccountData),
220            Self::Error::AccountDataTooSmall => Ok(Self::AccountDataTooSmall),
221            Self::Error::InsufficientFunds => Ok(Self::InsufficientFunds),
222            Self::Error::IncorrectProgramId => Ok(Self::IncorrectProgramId),
223            Self::Error::MissingRequiredSignature => Ok(Self::MissingRequiredSignature),
224            Self::Error::AccountAlreadyInitialized => Ok(Self::AccountAlreadyInitialized),
225            Self::Error::UninitializedAccount => Ok(Self::UninitializedAccount),
226            Self::Error::NotEnoughAccountKeys => Ok(Self::NotEnoughAccountKeys),
227            Self::Error::AccountBorrowFailed => Ok(Self::AccountBorrowFailed),
228            Self::Error::MaxSeedLengthExceeded => Ok(Self::MaxSeedLengthExceeded),
229            Self::Error::InvalidSeeds => Ok(Self::InvalidSeeds),
230            Self::Error::BorshIoError(err) => Ok(Self::BorshIoError(err)),
231            Self::Error::AccountNotRentExempt => Ok(Self::AccountNotRentExempt),
232            Self::Error::UnsupportedSysvar => Ok(Self::UnsupportedSysvar),
233            Self::Error::IllegalOwner => Ok(Self::IllegalOwner),
234            Self::Error::MaxAccountsDataAllocationsExceeded => {
235                Ok(Self::MaxAccountsDataAllocationsExceeded)
236            }
237            Self::Error::InvalidRealloc => Ok(Self::InvalidRealloc),
238            Self::Error::MaxInstructionTraceLengthExceeded => {
239                Ok(Self::MaxInstructionTraceLengthExceeded)
240            }
241            _ => Err(error),
242        }
243    }
244}
245
246impl<T> From<T> for InstructionError
247where
248    T: ToPrimitive,
249{
250    fn from(error: T) -> Self {
251        let error = error.to_u64().unwrap_or(0xbad_c0de);
252        match error {
253            CUSTOM_ZERO => Self::Custom(0),
254            INVALID_ARGUMENT => Self::InvalidArgument,
255            INVALID_INSTRUCTION_DATA => Self::InvalidInstructionData,
256            INVALID_ACCOUNT_DATA => Self::InvalidAccountData,
257            ACCOUNT_DATA_TOO_SMALL => Self::AccountDataTooSmall,
258            INSUFFICIENT_FUNDS => Self::InsufficientFunds,
259            INCORRECT_PROGRAM_ID => Self::IncorrectProgramId,
260            MISSING_REQUIRED_SIGNATURES => Self::MissingRequiredSignature,
261            ACCOUNT_ALREADY_INITIALIZED => Self::AccountAlreadyInitialized,
262            UNINITIALIZED_ACCOUNT => Self::UninitializedAccount,
263            NOT_ENOUGH_ACCOUNT_KEYS => Self::NotEnoughAccountKeys,
264            ACCOUNT_BORROW_FAILED => Self::AccountBorrowFailed,
265            MAX_SEED_LENGTH_EXCEEDED => Self::MaxSeedLengthExceeded,
266            INVALID_SEEDS => Self::InvalidSeeds,
267            BORSH_IO_ERROR => Self::BorshIoError("Unknown".to_string()),
268            ACCOUNT_NOT_RENT_EXEMPT => Self::AccountNotRentExempt,
269            UNSUPPORTED_SYSVAR => Self::UnsupportedSysvar,
270            ILLEGAL_OWNER => Self::IllegalOwner,
271            MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED => Self::MaxAccountsDataAllocationsExceeded,
272            INVALID_ACCOUNT_DATA_REALLOC => Self::InvalidRealloc,
273            MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED => Self::MaxInstructionTraceLengthExceeded,
274            _ => {
275                // A valid custom error has no bits set in the upper 32
276                if error >> BUILTIN_BIT_SHIFT == 0 {
277                    Self::Custom(error as u32)
278                } else {
279                    Self::InvalidError
280                }
281            }
282        }
283    }
284}
285
286impl From<PubkeyError> for ProgramError {
287    fn from(error: PubkeyError) -> Self {
288        match error {
289            PubkeyError::MaxSeedLengthExceeded => Self::MaxSeedLengthExceeded,
290            PubkeyError::InvalidSeeds => Self::InvalidSeeds,
291            PubkeyError::IllegalOwner => Self::IllegalOwner,
292        }
293    }
294}
295
296impl From<BorshIoError> for ProgramError {
297    fn from(error: BorshIoError) -> Self {
298        Self::BorshIoError(format!("{error}"))
299    }
300}