solana_instruction/
error.rs

1use core::fmt;
2#[cfg(feature = "frozen-abi")]
3use solana_frozen_abi_macro::{AbiEnumVisitor, AbiExample};
4#[cfg(feature = "std")]
5use {
6    num_traits::ToPrimitive,
7    std::string::{String, ToString},
8};
9
10/// Builtin return values occupy the upper 32 bits
11const BUILTIN_BIT_SHIFT: usize = 32;
12macro_rules! to_builtin {
13    ($error:expr) => {
14        ($error as u64) << BUILTIN_BIT_SHIFT
15    };
16}
17
18pub const CUSTOM_ZERO: u64 = to_builtin!(1);
19pub const INVALID_ARGUMENT: u64 = to_builtin!(2);
20pub const INVALID_INSTRUCTION_DATA: u64 = to_builtin!(3);
21pub const INVALID_ACCOUNT_DATA: u64 = to_builtin!(4);
22pub const ACCOUNT_DATA_TOO_SMALL: u64 = to_builtin!(5);
23pub const INSUFFICIENT_FUNDS: u64 = to_builtin!(6);
24pub const INCORRECT_PROGRAM_ID: u64 = to_builtin!(7);
25pub const MISSING_REQUIRED_SIGNATURES: u64 = to_builtin!(8);
26pub const ACCOUNT_ALREADY_INITIALIZED: u64 = to_builtin!(9);
27pub const UNINITIALIZED_ACCOUNT: u64 = to_builtin!(10);
28pub const NOT_ENOUGH_ACCOUNT_KEYS: u64 = to_builtin!(11);
29pub const ACCOUNT_BORROW_FAILED: u64 = to_builtin!(12);
30pub const MAX_SEED_LENGTH_EXCEEDED: u64 = to_builtin!(13);
31pub const INVALID_SEEDS: u64 = to_builtin!(14);
32pub const BORSH_IO_ERROR: u64 = to_builtin!(15);
33pub const ACCOUNT_NOT_RENT_EXEMPT: u64 = to_builtin!(16);
34pub const UNSUPPORTED_SYSVAR: u64 = to_builtin!(17);
35pub const ILLEGAL_OWNER: u64 = to_builtin!(18);
36pub const MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED: u64 = to_builtin!(19);
37pub const INVALID_ACCOUNT_DATA_REALLOC: u64 = to_builtin!(20);
38pub const MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED: u64 = to_builtin!(21);
39pub const BUILTIN_PROGRAMS_MUST_CONSUME_COMPUTE_UNITS: u64 = to_builtin!(22);
40pub const INVALID_ACCOUNT_OWNER: u64 = to_builtin!(23);
41pub const ARITHMETIC_OVERFLOW: u64 = to_builtin!(24);
42pub const IMMUTABLE: u64 = to_builtin!(25);
43pub const INCORRECT_AUTHORITY: u64 = to_builtin!(26);
44// Warning: Any new error codes added here must also be:
45// - Added to the below conversions
46// - Added as an equivalent to ProgramError and InstructionError
47// - Be featurized in the BPF loader to return `InstructionError::InvalidError`
48//   until the feature is activated
49
50/// Reasons the runtime might have rejected an instruction.
51///
52/// Members of this enum must not be removed, but new ones can be added.
53/// Also, it is crucial that meta-information if any that comes along with
54/// an error be consistent across software versions.  For example, it is
55/// dangerous to include error strings from 3rd party crates because they could
56/// change at any time and changes to them are difficult to detect.
57#[cfg(feature = "std")]
58#[cfg_attr(feature = "frozen-abi", derive(AbiExample, AbiEnumVisitor))]
59#[cfg_attr(
60    feature = "serde",
61    derive(serde_derive::Serialize, serde_derive::Deserialize)
62)]
63#[derive(Debug, PartialEq, Eq, Clone)]
64pub enum InstructionError {
65    /// Deprecated! Use CustomError instead!
66    /// The program instruction returned an error
67    GenericError,
68
69    /// The arguments provided to a program were invalid
70    InvalidArgument,
71
72    /// An instruction's data contents were invalid
73    InvalidInstructionData,
74
75    /// An account's data contents was invalid
76    InvalidAccountData,
77
78    /// An account's data was too small
79    AccountDataTooSmall,
80
81    /// An account's balance was too small to complete the instruction
82    InsufficientFunds,
83
84    /// The account did not have the expected program id
85    IncorrectProgramId,
86
87    /// A signature was required but not found
88    MissingRequiredSignature,
89
90    /// An initialize instruction was sent to an account that has already been initialized.
91    AccountAlreadyInitialized,
92
93    /// An attempt to operate on an account that hasn't been initialized.
94    UninitializedAccount,
95
96    /// Program's instruction lamport balance does not equal the balance after the instruction
97    UnbalancedInstruction,
98
99    /// Program illegally modified an account's program id
100    ModifiedProgramId,
101
102    /// Program spent the lamports of an account that doesn't belong to it
103    ExternalAccountLamportSpend,
104
105    /// Program modified the data of an account that doesn't belong to it
106    ExternalAccountDataModified,
107
108    /// Read-only account's lamports modified
109    ReadonlyLamportChange,
110
111    /// Read-only account's data was modified
112    ReadonlyDataModified,
113
114    /// An account was referenced more than once in a single instruction
115    // Deprecated, instructions can now contain duplicate accounts
116    DuplicateAccountIndex,
117
118    /// Executable bit on account changed, but shouldn't have
119    ExecutableModified,
120
121    /// Rent_epoch account changed, but shouldn't have
122    RentEpochModified,
123
124    /// The instruction expected additional account keys
125    NotEnoughAccountKeys,
126
127    /// Program other than the account's owner changed the size of the account data
128    AccountDataSizeChanged,
129
130    /// The instruction expected an executable account
131    AccountNotExecutable,
132
133    /// Failed to borrow a reference to account data, already borrowed
134    AccountBorrowFailed,
135
136    /// Account data has an outstanding reference after a program's execution
137    AccountBorrowOutstanding,
138
139    /// The same account was multiply passed to an on-chain program's entrypoint, but the program
140    /// modified them differently.  A program can only modify one instance of the account because
141    /// the runtime cannot determine which changes to pick or how to merge them if both are modified
142    DuplicateAccountOutOfSync,
143
144    /// Allows on-chain programs to implement program-specific error types and see them returned
145    /// by the Solana runtime. A program-specific error may be any type that is represented as
146    /// or serialized to a u32 integer.
147    Custom(u32),
148
149    /// The return value from the program was invalid.  Valid errors are either a defined builtin
150    /// error value or a user-defined error in the lower 32 bits.
151    InvalidError,
152
153    /// Executable account's data was modified
154    ExecutableDataModified,
155
156    /// Executable account's lamports modified
157    ExecutableLamportChange,
158
159    /// Executable accounts must be rent exempt
160    ExecutableAccountNotRentExempt,
161
162    /// Unsupported program id
163    UnsupportedProgramId,
164
165    /// Cross-program invocation call depth too deep
166    CallDepth,
167
168    /// An account required by the instruction is missing
169    MissingAccount,
170
171    /// Cross-program invocation reentrancy not allowed for this instruction
172    ReentrancyNotAllowed,
173
174    /// Length of the seed is too long for address generation
175    MaxSeedLengthExceeded,
176
177    /// Provided seeds do not result in a valid address
178    InvalidSeeds,
179
180    /// Failed to reallocate account data of this length
181    InvalidRealloc,
182
183    /// Computational budget exceeded
184    ComputationalBudgetExceeded,
185
186    /// Cross-program invocation with unauthorized signer or writable account
187    PrivilegeEscalation,
188
189    /// Failed to create program execution environment
190    ProgramEnvironmentSetupFailure,
191
192    /// Program failed to complete
193    ProgramFailedToComplete,
194
195    /// Program failed to compile
196    ProgramFailedToCompile,
197
198    /// Account is immutable
199    Immutable,
200
201    /// Incorrect authority provided
202    IncorrectAuthority,
203
204    /// Failed to serialize or deserialize account data
205    ///
206    /// Warning: This error should never be emitted by the runtime.
207    ///
208    /// This error includes strings from the underlying 3rd party Borsh crate
209    /// which can be dangerous because the error strings could change across
210    /// Borsh versions. Only programs can use this error because they are
211    /// consistent across Solana software versions.
212    ///
213    BorshIoError(String),
214
215    /// An account does not have enough lamports to be rent-exempt
216    AccountNotRentExempt,
217
218    /// Invalid account owner
219    InvalidAccountOwner,
220
221    /// Program arithmetic overflowed
222    ArithmeticOverflow,
223
224    /// Unsupported sysvar
225    UnsupportedSysvar,
226
227    /// Illegal account owner
228    IllegalOwner,
229
230    /// Accounts data allocations exceeded the maximum allowed per transaction
231    MaxAccountsDataAllocationsExceeded,
232
233    /// Max accounts exceeded
234    MaxAccountsExceeded,
235
236    /// Max instruction trace length exceeded
237    MaxInstructionTraceLengthExceeded,
238
239    /// Builtin programs must consume compute units
240    BuiltinProgramsMustConsumeComputeUnits,
241    // Note: For any new error added here an equivalent ProgramError and its
242    // conversions must also be added
243}
244
245#[cfg(feature = "std")]
246impl std::error::Error for InstructionError {}
247
248#[cfg(feature = "std")]
249impl fmt::Display for InstructionError {
250    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
251        match self {
252            InstructionError::GenericError => f.write_str("generic instruction error"),
253            InstructionError::InvalidArgument => f.write_str("invalid program argument"),
254            InstructionError::InvalidInstructionData => f.write_str("invalid instruction data"),
255            InstructionError::InvalidAccountData => {
256                f.write_str("invalid account data for instruction")
257            }
258            InstructionError::AccountDataTooSmall => {
259                f.write_str("account data too small for instruction")
260            }
261            InstructionError::InsufficientFunds => {
262                f.write_str("insufficient funds for instruction")
263            }
264            InstructionError::IncorrectProgramId => {
265                f.write_str("incorrect program id for instruction")
266            }
267            InstructionError::MissingRequiredSignature => {
268                f.write_str("missing required signature for instruction")
269            }
270            InstructionError::AccountAlreadyInitialized => {
271                f.write_str("instruction requires an uninitialized account")
272            }
273            InstructionError::UninitializedAccount => {
274                f.write_str("instruction requires an initialized account")
275            }
276            InstructionError::UnbalancedInstruction => {
277                f.write_str("sum of account balances before and after instruction do not match")
278            }
279            InstructionError::ModifiedProgramId => {
280                f.write_str("instruction illegally modified the program id of an account")
281            }
282            InstructionError::ExternalAccountLamportSpend => {
283                f.write_str("instruction spent from the balance of an account it does not own")
284            }
285            InstructionError::ExternalAccountDataModified => {
286                f.write_str("instruction modified data of an account it does not own")
287            }
288            InstructionError::ReadonlyLamportChange => {
289                f.write_str("instruction changed the balance of a read-only account")
290            }
291            InstructionError::ReadonlyDataModified => {
292                f.write_str("instruction modified data of a read-only account")
293            }
294            InstructionError::DuplicateAccountIndex => {
295                f.write_str("instruction contains duplicate accounts")
296            }
297            InstructionError::ExecutableModified => {
298                f.write_str("instruction changed executable bit of an account")
299            }
300            InstructionError::RentEpochModified => {
301                f.write_str("instruction modified rent epoch of an account")
302            }
303            InstructionError::NotEnoughAccountKeys => {
304                f.write_str("insufficient account keys for instruction")
305            }
306            InstructionError::AccountDataSizeChanged => f.write_str(
307                "program other than the account's owner changed the size of the account data",
308            ),
309            InstructionError::AccountNotExecutable => {
310                f.write_str("instruction expected an executable account")
311            }
312            InstructionError::AccountBorrowFailed => f.write_str(
313                "instruction tries to borrow reference for an account which is already borrowed",
314            ),
315            InstructionError::AccountBorrowOutstanding => {
316                f.write_str("instruction left account with an outstanding borrowed reference")
317            }
318            InstructionError::DuplicateAccountOutOfSync => {
319                f.write_str("instruction modifications of multiply-passed account differ")
320            }
321            InstructionError::Custom(num) => {
322                write!(f, "custom program error: {num:#x}")
323            }
324            InstructionError::InvalidError => f.write_str("program returned invalid error code"),
325            InstructionError::ExecutableDataModified => {
326                f.write_str("instruction changed executable accounts data")
327            }
328            InstructionError::ExecutableLamportChange => {
329                f.write_str("instruction changed the balance of an executable account")
330            }
331            InstructionError::ExecutableAccountNotRentExempt => {
332                f.write_str("executable accounts must be rent exempt")
333            }
334            InstructionError::UnsupportedProgramId => f.write_str("Unsupported program id"),
335            InstructionError::CallDepth => {
336                f.write_str("Cross-program invocation call depth too deep")
337            }
338            InstructionError::MissingAccount => {
339                f.write_str("An account required by the instruction is missing")
340            }
341            InstructionError::ReentrancyNotAllowed => {
342                f.write_str("Cross-program invocation reentrancy not allowed for this instruction")
343            }
344            InstructionError::MaxSeedLengthExceeded => {
345                f.write_str("Length of the seed is too long for address generation")
346            }
347            InstructionError::InvalidSeeds => {
348                f.write_str("Provided seeds do not result in a valid address")
349            }
350            InstructionError::InvalidRealloc => f.write_str("Failed to reallocate account data"),
351            InstructionError::ComputationalBudgetExceeded => {
352                f.write_str("Computational budget exceeded")
353            }
354            InstructionError::PrivilegeEscalation => {
355                f.write_str("Cross-program invocation with unauthorized signer or writable account")
356            }
357            InstructionError::ProgramEnvironmentSetupFailure => {
358                f.write_str("Failed to create program execution environment")
359            }
360            InstructionError::ProgramFailedToComplete => f.write_str("Program failed to complete"),
361            InstructionError::ProgramFailedToCompile => f.write_str("Program failed to compile"),
362            InstructionError::Immutable => f.write_str("Account is immutable"),
363            InstructionError::IncorrectAuthority => f.write_str("Incorrect authority provided"),
364            InstructionError::BorshIoError(s) => {
365                write!(f, "Failed to serialize or deserialize account data: {s}",)
366            }
367            InstructionError::AccountNotRentExempt => {
368                f.write_str("An account does not have enough lamports to be rent-exempt")
369            }
370            InstructionError::InvalidAccountOwner => f.write_str("Invalid account owner"),
371            InstructionError::ArithmeticOverflow => f.write_str("Program arithmetic overflowed"),
372            InstructionError::UnsupportedSysvar => f.write_str("Unsupported sysvar"),
373            InstructionError::IllegalOwner => f.write_str("Provided owner is not allowed"),
374            InstructionError::MaxAccountsDataAllocationsExceeded => f.write_str(
375                "Accounts data allocations exceeded the maximum allowed per transaction",
376            ),
377            InstructionError::MaxAccountsExceeded => f.write_str("Max accounts exceeded"),
378            InstructionError::MaxInstructionTraceLengthExceeded => {
379                f.write_str("Max instruction trace length exceeded")
380            }
381            InstructionError::BuiltinProgramsMustConsumeComputeUnits => {
382                f.write_str("Builtin programs must consume compute units")
383            }
384        }
385    }
386}
387
388#[cfg(feature = "std")]
389impl<T> From<T> for InstructionError
390where
391    T: ToPrimitive,
392{
393    fn from(error: T) -> Self {
394        let error = error.to_u64().unwrap_or(0xbad_c0de);
395        match error {
396            CUSTOM_ZERO => Self::Custom(0),
397            INVALID_ARGUMENT => Self::InvalidArgument,
398            INVALID_INSTRUCTION_DATA => Self::InvalidInstructionData,
399            INVALID_ACCOUNT_DATA => Self::InvalidAccountData,
400            ACCOUNT_DATA_TOO_SMALL => Self::AccountDataTooSmall,
401            INSUFFICIENT_FUNDS => Self::InsufficientFunds,
402            INCORRECT_PROGRAM_ID => Self::IncorrectProgramId,
403            MISSING_REQUIRED_SIGNATURES => Self::MissingRequiredSignature,
404            ACCOUNT_ALREADY_INITIALIZED => Self::AccountAlreadyInitialized,
405            UNINITIALIZED_ACCOUNT => Self::UninitializedAccount,
406            NOT_ENOUGH_ACCOUNT_KEYS => Self::NotEnoughAccountKeys,
407            ACCOUNT_BORROW_FAILED => Self::AccountBorrowFailed,
408            MAX_SEED_LENGTH_EXCEEDED => Self::MaxSeedLengthExceeded,
409            INVALID_SEEDS => Self::InvalidSeeds,
410            BORSH_IO_ERROR => Self::BorshIoError("Unknown".to_string()),
411            ACCOUNT_NOT_RENT_EXEMPT => Self::AccountNotRentExempt,
412            UNSUPPORTED_SYSVAR => Self::UnsupportedSysvar,
413            ILLEGAL_OWNER => Self::IllegalOwner,
414            MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED => Self::MaxAccountsDataAllocationsExceeded,
415            INVALID_ACCOUNT_DATA_REALLOC => Self::InvalidRealloc,
416            MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED => Self::MaxInstructionTraceLengthExceeded,
417            BUILTIN_PROGRAMS_MUST_CONSUME_COMPUTE_UNITS => {
418                Self::BuiltinProgramsMustConsumeComputeUnits
419            }
420            INVALID_ACCOUNT_OWNER => Self::InvalidAccountOwner,
421            ARITHMETIC_OVERFLOW => Self::ArithmeticOverflow,
422            IMMUTABLE => Self::Immutable,
423            INCORRECT_AUTHORITY => Self::IncorrectAuthority,
424            _ => {
425                // A valid custom error has no bits set in the upper 32
426                if error >> BUILTIN_BIT_SHIFT == 0 {
427                    Self::Custom(error as u32)
428                } else {
429                    Self::InvalidError
430                }
431            }
432        }
433    }
434}
435
436#[derive(Debug)]
437pub enum LamportsError {
438    /// arithmetic underflowed
439    ArithmeticUnderflow,
440    /// arithmetic overflowed
441    ArithmeticOverflow,
442}
443
444#[cfg(feature = "std")]
445impl std::error::Error for LamportsError {}
446
447impl fmt::Display for LamportsError {
448    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
449        match self {
450            Self::ArithmeticUnderflow => f.write_str("Arithmetic underflowed"),
451            Self::ArithmeticOverflow => f.write_str("Arithmetic overflowed"),
452        }
453    }
454}
455
456#[cfg(feature = "std")]
457impl From<LamportsError> for InstructionError {
458    fn from(error: LamportsError) -> Self {
459        match error {
460            LamportsError::ArithmeticOverflow => InstructionError::ArithmeticOverflow,
461            LamportsError::ArithmeticUnderflow => InstructionError::ArithmeticOverflow,
462        }
463    }
464}