Skip to main content

solana_instruction_error/
lib.rs

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