Skip to main content

hopper_native/
error.rs

1//! Program error type for Solana on-chain programs.
2//!
3//! Wire-compatible with pinocchio/solana-program ProgramError.
4//! Each variant maps to a fixed u64 error code returned to the runtime.
5
6/// Errors that a Solana program can return.
7#[derive(Clone, Debug, Eq, PartialEq)]
8pub enum ProgramError {
9    /// Custom program error with a u32 code.
10    Custom(u32),
11    InvalidArgument,
12    InvalidInstructionData,
13    InvalidAccountData,
14    AccountDataTooSmall,
15    InsufficientFunds,
16    IncorrectProgramId,
17    MissingRequiredSignature,
18    AccountAlreadyInitialized,
19    UninitializedAccount,
20    NotEnoughAccountKeys,
21    AccountBorrowFailed,
22    MaxSeedLengthExceeded,
23    InvalidSeeds,
24    BorshIoError,
25    AccountNotRentExempt,
26    UnsupportedSysvar,
27    IllegalOwner,
28    MaxAccountsDataAllocationsExceeded,
29    InvalidRealloc,
30    MaxInstructionTraceLengthExceeded,
31    BuiltinProgramsMustConsumeComputeUnits,
32    InvalidAccountOwner,
33    ArithmeticOverflow,
34    Immutable,
35    IncorrectAuthority,
36}
37
38// ── u64 conversion (Solana runtime ABI) ──────────────────────────────
39
40impl From<ProgramError> for u64 {
41    fn from(err: ProgramError) -> u64 {
42        match err {
43            ProgramError::Custom(0) => CUSTOM_ZERO,
44            ProgramError::Custom(code) => code as u64,
45            ProgramError::InvalidArgument => to_builtin(0),
46            ProgramError::InvalidInstructionData => to_builtin(1),
47            ProgramError::InvalidAccountData => to_builtin(2),
48            ProgramError::AccountDataTooSmall => to_builtin(3),
49            ProgramError::InsufficientFunds => to_builtin(4),
50            ProgramError::IncorrectProgramId => to_builtin(5),
51            ProgramError::MissingRequiredSignature => to_builtin(6),
52            ProgramError::AccountAlreadyInitialized => to_builtin(7),
53            ProgramError::UninitializedAccount => to_builtin(8),
54            ProgramError::NotEnoughAccountKeys => to_builtin(9),
55            ProgramError::AccountBorrowFailed => to_builtin(10),
56            ProgramError::MaxSeedLengthExceeded => to_builtin(11),
57            ProgramError::InvalidSeeds => to_builtin(12),
58            ProgramError::BorshIoError => to_builtin(13),
59            ProgramError::AccountNotRentExempt => to_builtin(14),
60            ProgramError::UnsupportedSysvar => to_builtin(15),
61            ProgramError::IllegalOwner => to_builtin(16),
62            ProgramError::MaxAccountsDataAllocationsExceeded => to_builtin(17),
63            ProgramError::InvalidRealloc => to_builtin(18),
64            ProgramError::MaxInstructionTraceLengthExceeded => to_builtin(19),
65            ProgramError::BuiltinProgramsMustConsumeComputeUnits => to_builtin(20),
66            ProgramError::InvalidAccountOwner => to_builtin(21),
67            ProgramError::ArithmeticOverflow => to_builtin(22),
68            ProgramError::Immutable => to_builtin(23),
69            ProgramError::IncorrectAuthority => to_builtin(24),
70        }
71    }
72}
73
74impl From<u64> for ProgramError {
75    fn from(code: u64) -> Self {
76        if code == CUSTOM_ZERO {
77            return ProgramError::Custom(0);
78        }
79        let builtin = code >> BUILTIN_BIT_SHIFT;
80        if code & BUILTIN_LOW_MASK == 0 && builtin >= 2 {
81            match builtin - 2 {
82                0 => return ProgramError::InvalidArgument,
83                1 => return ProgramError::InvalidInstructionData,
84                2 => return ProgramError::InvalidAccountData,
85                3 => return ProgramError::AccountDataTooSmall,
86                4 => return ProgramError::InsufficientFunds,
87                5 => return ProgramError::IncorrectProgramId,
88                6 => return ProgramError::MissingRequiredSignature,
89                7 => return ProgramError::AccountAlreadyInitialized,
90                8 => return ProgramError::UninitializedAccount,
91                9 => return ProgramError::NotEnoughAccountKeys,
92                10 => return ProgramError::AccountBorrowFailed,
93                11 => return ProgramError::MaxSeedLengthExceeded,
94                12 => return ProgramError::InvalidSeeds,
95                13 => return ProgramError::BorshIoError,
96                14 => return ProgramError::AccountNotRentExempt,
97                15 => return ProgramError::UnsupportedSysvar,
98                16 => return ProgramError::IllegalOwner,
99                17 => return ProgramError::MaxAccountsDataAllocationsExceeded,
100                18 => return ProgramError::InvalidRealloc,
101                19 => return ProgramError::MaxInstructionTraceLengthExceeded,
102                20 => return ProgramError::BuiltinProgramsMustConsumeComputeUnits,
103                21 => return ProgramError::InvalidAccountOwner,
104                22 => return ProgramError::ArithmeticOverflow,
105                23 => return ProgramError::Immutable,
106                24 => return ProgramError::IncorrectAuthority,
107                _ => {}
108            }
109        }
110        ProgramError::Custom(code as u32)
111    }
112}
113
114/// Map a builtin error index to its runtime u64 code.
115///
116/// The Solana runtime uses a specific encoding for builtin errors:
117/// - `Custom(0)` occupies `1 << 32`
118/// - builtin errors start at `2 << 32`
119const BUILTIN_BIT_SHIFT: usize = 32;
120const CUSTOM_ZERO: u64 = 1_u64 << BUILTIN_BIT_SHIFT;
121const BUILTIN_LOW_MASK: u64 = (1_u64 << BUILTIN_BIT_SHIFT) - 1;
122
123#[inline(always)]
124const fn to_builtin(index: u64) -> u64 {
125    (index + 2) << BUILTIN_BIT_SHIFT
126}
127
128impl core::fmt::Display for ProgramError {
129    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
130        match self {
131            ProgramError::Custom(code) => write!(f, "Custom({code})"),
132            ProgramError::InvalidArgument => write!(f, "InvalidArgument"),
133            ProgramError::InvalidInstructionData => write!(f, "InvalidInstructionData"),
134            ProgramError::InvalidAccountData => write!(f, "InvalidAccountData"),
135            ProgramError::AccountDataTooSmall => write!(f, "AccountDataTooSmall"),
136            ProgramError::InsufficientFunds => write!(f, "InsufficientFunds"),
137            ProgramError::IncorrectProgramId => write!(f, "IncorrectProgramId"),
138            ProgramError::MissingRequiredSignature => write!(f, "MissingRequiredSignature"),
139            ProgramError::AccountAlreadyInitialized => write!(f, "AccountAlreadyInitialized"),
140            ProgramError::UninitializedAccount => write!(f, "UninitializedAccount"),
141            ProgramError::NotEnoughAccountKeys => write!(f, "NotEnoughAccountKeys"),
142            ProgramError::AccountBorrowFailed => write!(f, "AccountBorrowFailed"),
143            ProgramError::MaxSeedLengthExceeded => write!(f, "MaxSeedLengthExceeded"),
144            ProgramError::InvalidSeeds => write!(f, "InvalidSeeds"),
145            ProgramError::BorshIoError => write!(f, "BorshIoError"),
146            ProgramError::AccountNotRentExempt => write!(f, "AccountNotRentExempt"),
147            ProgramError::UnsupportedSysvar => write!(f, "UnsupportedSysvar"),
148            ProgramError::IllegalOwner => write!(f, "IllegalOwner"),
149            ProgramError::MaxAccountsDataAllocationsExceeded => {
150                write!(f, "MaxAccountsDataAllocationsExceeded")
151            }
152            ProgramError::InvalidRealloc => write!(f, "InvalidRealloc"),
153            ProgramError::MaxInstructionTraceLengthExceeded => {
154                write!(f, "MaxInstructionTraceLengthExceeded")
155            }
156            ProgramError::BuiltinProgramsMustConsumeComputeUnits => {
157                write!(f, "BuiltinProgramsMustConsumeComputeUnits")
158            }
159            ProgramError::InvalidAccountOwner => write!(f, "InvalidAccountOwner"),
160            ProgramError::ArithmeticOverflow => write!(f, "ArithmeticOverflow"),
161            ProgramError::Immutable => write!(f, "Immutable"),
162            ProgramError::IncorrectAuthority => write!(f, "IncorrectAuthority"),
163        }
164    }
165}