solana_program_error/
lib.rs

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