Skip to main content

hopper_runtime/
error.rs

1//! Hopper-owned program error type for Solana on-chain programs.
2//!
3//! Wire-compatible with all backends. Each variant maps to a fixed u64
4//! error code returned to the Solana runtime.
5
6/// Errors that a Solana program can return.
7///
8/// This is part of the Hopper runtime type surface. Variant discriminants
9/// match the Solana runtime ABI.
10#[derive(Clone, Debug, Eq, PartialEq)]
11pub enum ProgramError {
12    /// Custom program error with a u32 code.
13    Custom(u32),
14    InvalidArgument,
15    InvalidInstructionData,
16    InvalidAccountData,
17    AccountDataTooSmall,
18    InsufficientFunds,
19    IncorrectProgramId,
20    MissingRequiredSignature,
21    AccountAlreadyInitialized,
22    UninitializedAccount,
23    NotEnoughAccountKeys,
24    AccountBorrowFailed,
25    MaxSeedLengthExceeded,
26    InvalidSeeds,
27    BorshIoError,
28    AccountNotRentExempt,
29    UnsupportedSysvar,
30    IllegalOwner,
31    MaxAccountsDataAllocationsExceeded,
32    InvalidRealloc,
33    MaxInstructionTraceLengthExceeded,
34    BuiltinProgramsMustConsumeComputeUnits,
35    InvalidAccountOwner,
36    ArithmeticOverflow,
37    Immutable,
38    IncorrectAuthority,
39}
40
41// ── u64 conversion (Solana runtime ABI) ──────────────────────────────
42
43/// Map a builtin error index to its runtime u64 code.
44const BUILTIN_BIT_SHIFT: usize = 32;
45const CUSTOM_ZERO: u64 = 1_u64 << BUILTIN_BIT_SHIFT;
46
47/// Hopper builtin errors follow Solana's ABI:
48/// - `Custom(0)` occupies `1 << 32`
49/// - builtin errors start at `2 << 32`
50#[inline(always)]
51const fn to_builtin(index: u64) -> u64 {
52    (index + 2) << BUILTIN_BIT_SHIFT
53}
54
55const BUILTIN_LOW_MASK: u64 = (1_u64 << BUILTIN_BIT_SHIFT) - 1;
56
57impl From<ProgramError> for u64 {
58    fn from(err: ProgramError) -> u64 {
59        match err {
60            ProgramError::Custom(0) => CUSTOM_ZERO,
61            ProgramError::Custom(code) => code as u64,
62            ProgramError::InvalidArgument => to_builtin(0),
63            ProgramError::InvalidInstructionData => to_builtin(1),
64            ProgramError::InvalidAccountData => to_builtin(2),
65            ProgramError::AccountDataTooSmall => to_builtin(3),
66            ProgramError::InsufficientFunds => to_builtin(4),
67            ProgramError::IncorrectProgramId => to_builtin(5),
68            ProgramError::MissingRequiredSignature => to_builtin(6),
69            ProgramError::AccountAlreadyInitialized => to_builtin(7),
70            ProgramError::UninitializedAccount => to_builtin(8),
71            ProgramError::NotEnoughAccountKeys => to_builtin(9),
72            ProgramError::AccountBorrowFailed => to_builtin(10),
73            ProgramError::MaxSeedLengthExceeded => to_builtin(11),
74            ProgramError::InvalidSeeds => to_builtin(12),
75            ProgramError::BorshIoError => to_builtin(13),
76            ProgramError::AccountNotRentExempt => to_builtin(14),
77            ProgramError::UnsupportedSysvar => to_builtin(15),
78            ProgramError::IllegalOwner => to_builtin(16),
79            ProgramError::MaxAccountsDataAllocationsExceeded => to_builtin(17),
80            ProgramError::InvalidRealloc => to_builtin(18),
81            ProgramError::MaxInstructionTraceLengthExceeded => to_builtin(19),
82            ProgramError::BuiltinProgramsMustConsumeComputeUnits => to_builtin(20),
83            ProgramError::InvalidAccountOwner => to_builtin(21),
84            ProgramError::ArithmeticOverflow => to_builtin(22),
85            ProgramError::Immutable => to_builtin(23),
86            ProgramError::IncorrectAuthority => to_builtin(24),
87        }
88    }
89}
90
91impl From<u64> for ProgramError {
92    fn from(code: u64) -> Self {
93        if code == CUSTOM_ZERO {
94            return ProgramError::Custom(0);
95        }
96        let builtin = code >> BUILTIN_BIT_SHIFT;
97        if code & BUILTIN_LOW_MASK == 0 && builtin >= 2 {
98            match builtin - 2 {
99                0 => return ProgramError::InvalidArgument,
100                1 => return ProgramError::InvalidInstructionData,
101                2 => return ProgramError::InvalidAccountData,
102                3 => return ProgramError::AccountDataTooSmall,
103                4 => return ProgramError::InsufficientFunds,
104                5 => return ProgramError::IncorrectProgramId,
105                6 => return ProgramError::MissingRequiredSignature,
106                7 => return ProgramError::AccountAlreadyInitialized,
107                8 => return ProgramError::UninitializedAccount,
108                9 => return ProgramError::NotEnoughAccountKeys,
109                10 => return ProgramError::AccountBorrowFailed,
110                11 => return ProgramError::MaxSeedLengthExceeded,
111                12 => return ProgramError::InvalidSeeds,
112                13 => return ProgramError::BorshIoError,
113                14 => return ProgramError::AccountNotRentExempt,
114                15 => return ProgramError::UnsupportedSysvar,
115                16 => return ProgramError::IllegalOwner,
116                17 => return ProgramError::MaxAccountsDataAllocationsExceeded,
117                18 => return ProgramError::InvalidRealloc,
118                19 => return ProgramError::MaxInstructionTraceLengthExceeded,
119                20 => return ProgramError::BuiltinProgramsMustConsumeComputeUnits,
120                21 => return ProgramError::InvalidAccountOwner,
121                22 => return ProgramError::ArithmeticOverflow,
122                23 => return ProgramError::Immutable,
123                24 => return ProgramError::IncorrectAuthority,
124                _ => {}
125            }
126        }
127        ProgramError::Custom(code as u32)
128    }
129}
130
131impl core::fmt::Display for ProgramError {
132    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
133        match self {
134            ProgramError::Custom(code) => write!(f, "Custom({code})"),
135            ProgramError::InvalidArgument => write!(f, "InvalidArgument"),
136            ProgramError::InvalidInstructionData => write!(f, "InvalidInstructionData"),
137            ProgramError::InvalidAccountData => write!(f, "InvalidAccountData"),
138            ProgramError::AccountDataTooSmall => write!(f, "AccountDataTooSmall"),
139            ProgramError::InsufficientFunds => write!(f, "InsufficientFunds"),
140            ProgramError::IncorrectProgramId => write!(f, "IncorrectProgramId"),
141            ProgramError::MissingRequiredSignature => write!(f, "MissingRequiredSignature"),
142            ProgramError::AccountAlreadyInitialized => write!(f, "AccountAlreadyInitialized"),
143            ProgramError::UninitializedAccount => write!(f, "UninitializedAccount"),
144            ProgramError::NotEnoughAccountKeys => write!(f, "NotEnoughAccountKeys"),
145            ProgramError::AccountBorrowFailed => write!(f, "AccountBorrowFailed"),
146            ProgramError::MaxSeedLengthExceeded => write!(f, "MaxSeedLengthExceeded"),
147            ProgramError::InvalidSeeds => write!(f, "InvalidSeeds"),
148            ProgramError::BorshIoError => write!(f, "BorshIoError"),
149            ProgramError::AccountNotRentExempt => write!(f, "AccountNotRentExempt"),
150            ProgramError::UnsupportedSysvar => write!(f, "UnsupportedSysvar"),
151            ProgramError::IllegalOwner => write!(f, "IllegalOwner"),
152            ProgramError::MaxAccountsDataAllocationsExceeded => {
153                write!(f, "MaxAccountsDataAllocationsExceeded")
154            }
155            ProgramError::InvalidRealloc => write!(f, "InvalidRealloc"),
156            ProgramError::MaxInstructionTraceLengthExceeded => {
157                write!(f, "MaxInstructionTraceLengthExceeded")
158            }
159            ProgramError::BuiltinProgramsMustConsumeComputeUnits => {
160                write!(f, "BuiltinProgramsMustConsumeComputeUnits")
161            }
162            ProgramError::InvalidAccountOwner => write!(f, "InvalidAccountOwner"),
163            ProgramError::ArithmeticOverflow => write!(f, "ArithmeticOverflow"),
164            ProgramError::Immutable => write!(f, "Immutable"),
165            ProgramError::IncorrectAuthority => write!(f, "IncorrectAuthority"),
166        }
167    }
168}
169
170// ── Backend conversions ──────────────────────────────────────────────
171
172/// Convert between Hopper and backend ProgramError via u64 error codes.
173///
174/// Both types encode errors as the same u64 values, so we round-trip
175/// through the integer representation. This is zero-cost in the happy
176/// path (errors are exceptional).
177#[cfg(feature = "hopper-native-backend")]
178impl From<hopper_native::error::ProgramError> for ProgramError {
179    #[inline]
180    fn from(e: hopper_native::error::ProgramError) -> Self {
181        match e {
182            hopper_native::error::ProgramError::Custom(code) => ProgramError::Custom(code),
183            hopper_native::error::ProgramError::InvalidArgument => ProgramError::InvalidArgument,
184            hopper_native::error::ProgramError::InvalidInstructionData => {
185                ProgramError::InvalidInstructionData
186            }
187            hopper_native::error::ProgramError::InvalidAccountData => {
188                ProgramError::InvalidAccountData
189            }
190            hopper_native::error::ProgramError::AccountDataTooSmall => {
191                ProgramError::AccountDataTooSmall
192            }
193            hopper_native::error::ProgramError::InsufficientFunds => {
194                ProgramError::InsufficientFunds
195            }
196            hopper_native::error::ProgramError::IncorrectProgramId => {
197                ProgramError::IncorrectProgramId
198            }
199            hopper_native::error::ProgramError::MissingRequiredSignature => {
200                ProgramError::MissingRequiredSignature
201            }
202            hopper_native::error::ProgramError::AccountAlreadyInitialized => {
203                ProgramError::AccountAlreadyInitialized
204            }
205            hopper_native::error::ProgramError::UninitializedAccount => {
206                ProgramError::UninitializedAccount
207            }
208            hopper_native::error::ProgramError::NotEnoughAccountKeys => {
209                ProgramError::NotEnoughAccountKeys
210            }
211            hopper_native::error::ProgramError::AccountBorrowFailed => {
212                ProgramError::AccountBorrowFailed
213            }
214            hopper_native::error::ProgramError::MaxSeedLengthExceeded => {
215                ProgramError::MaxSeedLengthExceeded
216            }
217            hopper_native::error::ProgramError::InvalidSeeds => ProgramError::InvalidSeeds,
218            hopper_native::error::ProgramError::BorshIoError => ProgramError::BorshIoError,
219            hopper_native::error::ProgramError::AccountNotRentExempt => {
220                ProgramError::AccountNotRentExempt
221            }
222            hopper_native::error::ProgramError::UnsupportedSysvar => {
223                ProgramError::UnsupportedSysvar
224            }
225            hopper_native::error::ProgramError::IllegalOwner => ProgramError::IllegalOwner,
226            hopper_native::error::ProgramError::MaxAccountsDataAllocationsExceeded => {
227                ProgramError::MaxAccountsDataAllocationsExceeded
228            }
229            hopper_native::error::ProgramError::InvalidRealloc => ProgramError::InvalidRealloc,
230            hopper_native::error::ProgramError::MaxInstructionTraceLengthExceeded => {
231                ProgramError::MaxInstructionTraceLengthExceeded
232            }
233            hopper_native::error::ProgramError::BuiltinProgramsMustConsumeComputeUnits => {
234                ProgramError::BuiltinProgramsMustConsumeComputeUnits
235            }
236            hopper_native::error::ProgramError::InvalidAccountOwner => {
237                ProgramError::InvalidAccountOwner
238            }
239            hopper_native::error::ProgramError::ArithmeticOverflow => {
240                ProgramError::ArithmeticOverflow
241            }
242            hopper_native::error::ProgramError::Immutable => ProgramError::Immutable,
243            hopper_native::error::ProgramError::IncorrectAuthority => {
244                ProgramError::IncorrectAuthority
245            }
246        }
247    }
248}
249
250#[cfg(feature = "hopper-native-backend")]
251impl From<ProgramError> for hopper_native::error::ProgramError {
252    #[inline]
253    fn from(e: ProgramError) -> Self {
254        match e {
255            ProgramError::Custom(code) => hopper_native::error::ProgramError::Custom(code),
256            ProgramError::InvalidArgument => hopper_native::error::ProgramError::InvalidArgument,
257            ProgramError::InvalidInstructionData => {
258                hopper_native::error::ProgramError::InvalidInstructionData
259            }
260            ProgramError::InvalidAccountData => {
261                hopper_native::error::ProgramError::InvalidAccountData
262            }
263            ProgramError::AccountDataTooSmall => {
264                hopper_native::error::ProgramError::AccountDataTooSmall
265            }
266            ProgramError::InsufficientFunds => {
267                hopper_native::error::ProgramError::InsufficientFunds
268            }
269            ProgramError::IncorrectProgramId => {
270                hopper_native::error::ProgramError::IncorrectProgramId
271            }
272            ProgramError::MissingRequiredSignature => {
273                hopper_native::error::ProgramError::MissingRequiredSignature
274            }
275            ProgramError::AccountAlreadyInitialized => {
276                hopper_native::error::ProgramError::AccountAlreadyInitialized
277            }
278            ProgramError::UninitializedAccount => {
279                hopper_native::error::ProgramError::UninitializedAccount
280            }
281            ProgramError::NotEnoughAccountKeys => {
282                hopper_native::error::ProgramError::NotEnoughAccountKeys
283            }
284            ProgramError::AccountBorrowFailed => {
285                hopper_native::error::ProgramError::AccountBorrowFailed
286            }
287            ProgramError::MaxSeedLengthExceeded => {
288                hopper_native::error::ProgramError::MaxSeedLengthExceeded
289            }
290            ProgramError::InvalidSeeds => hopper_native::error::ProgramError::InvalidSeeds,
291            ProgramError::BorshIoError => hopper_native::error::ProgramError::BorshIoError,
292            ProgramError::AccountNotRentExempt => {
293                hopper_native::error::ProgramError::AccountNotRentExempt
294            }
295            ProgramError::UnsupportedSysvar => {
296                hopper_native::error::ProgramError::UnsupportedSysvar
297            }
298            ProgramError::IllegalOwner => hopper_native::error::ProgramError::IllegalOwner,
299            ProgramError::MaxAccountsDataAllocationsExceeded => {
300                hopper_native::error::ProgramError::MaxAccountsDataAllocationsExceeded
301            }
302            ProgramError::InvalidRealloc => hopper_native::error::ProgramError::InvalidRealloc,
303            ProgramError::MaxInstructionTraceLengthExceeded => {
304                hopper_native::error::ProgramError::MaxInstructionTraceLengthExceeded
305            }
306            ProgramError::BuiltinProgramsMustConsumeComputeUnits => {
307                hopper_native::error::ProgramError::BuiltinProgramsMustConsumeComputeUnits
308            }
309            ProgramError::InvalidAccountOwner => {
310                hopper_native::error::ProgramError::InvalidAccountOwner
311            }
312            ProgramError::ArithmeticOverflow => {
313                hopper_native::error::ProgramError::ArithmeticOverflow
314            }
315            ProgramError::Immutable => hopper_native::error::ProgramError::Immutable,
316            ProgramError::IncorrectAuthority => {
317                hopper_native::error::ProgramError::IncorrectAuthority
318            }
319        }
320    }
321}
322
323// ══════════════════════════════════════════════════════════════════════
324//  Cold error constructors
325// ══════════════════════════════════════════════════════════════════════
326//
327// Following Pinocchio's pattern: `#[cold]` + `#[inline(never)]` on error
328// return helpers keeps the error path out of the hot-path instruction cache.
329// Call sites become a single branch + call, keeping the inlined fast path tiny.
330
331impl ProgramError {
332    #[inline(always)]
333    pub fn err_data_too_small<T>() -> Result<T, Self> {
334        Err(ProgramError::AccountDataTooSmall)
335    }
336
337    #[cold]
338    #[inline(never)]
339    pub fn err_invalid_data<T>() -> Result<T, Self> {
340        Err(ProgramError::InvalidAccountData)
341    }
342
343    #[cold]
344    #[inline(never)]
345    pub fn err_missing_signer<T>() -> Result<T, Self> {
346        Err(ProgramError::MissingRequiredSignature)
347    }
348
349    #[inline(always)]
350    pub fn err_immutable<T>() -> Result<T, Self> {
351        Err(ProgramError::Immutable)
352    }
353
354    #[cold]
355    #[inline(never)]
356    pub fn err_not_enough_keys<T>() -> Result<T, Self> {
357        Err(ProgramError::NotEnoughAccountKeys)
358    }
359
360    #[cold]
361    #[inline(never)]
362    pub fn err_borrow_failed<T>() -> Result<T, Self> {
363        Err(ProgramError::AccountBorrowFailed)
364    }
365
366    #[cold]
367    #[inline(never)]
368    pub fn err_overflow<T>() -> Result<T, Self> {
369        Err(ProgramError::ArithmeticOverflow)
370    }
371
372    #[cold]
373    #[inline(never)]
374    pub fn err_invalid_argument<T>() -> Result<T, Self> {
375        Err(ProgramError::InvalidArgument)
376    }
377
378    #[inline(always)]
379    pub fn err_incorrect_program<T>() -> Result<T, Self> {
380        Err(ProgramError::IncorrectProgramId)
381    }
382}