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
55impl From<ProgramError> for u64 {
56    fn from(err: ProgramError) -> u64 {
57        match err {
58            ProgramError::Custom(0) => CUSTOM_ZERO,
59            ProgramError::Custom(code) => code as u64,
60            ProgramError::InvalidArgument => to_builtin(0),
61            ProgramError::InvalidInstructionData => to_builtin(1),
62            ProgramError::InvalidAccountData => to_builtin(2),
63            ProgramError::AccountDataTooSmall => to_builtin(3),
64            ProgramError::InsufficientFunds => to_builtin(4),
65            ProgramError::IncorrectProgramId => to_builtin(5),
66            ProgramError::MissingRequiredSignature => to_builtin(6),
67            ProgramError::AccountAlreadyInitialized => to_builtin(7),
68            ProgramError::UninitializedAccount => to_builtin(8),
69            ProgramError::NotEnoughAccountKeys => to_builtin(9),
70            ProgramError::AccountBorrowFailed => to_builtin(10),
71            ProgramError::MaxSeedLengthExceeded => to_builtin(11),
72            ProgramError::InvalidSeeds => to_builtin(12),
73            ProgramError::BorshIoError => to_builtin(13),
74            ProgramError::AccountNotRentExempt => to_builtin(14),
75            ProgramError::UnsupportedSysvar => to_builtin(15),
76            ProgramError::IllegalOwner => to_builtin(16),
77            ProgramError::MaxAccountsDataAllocationsExceeded => to_builtin(17),
78            ProgramError::InvalidRealloc => to_builtin(18),
79            ProgramError::MaxInstructionTraceLengthExceeded => to_builtin(19),
80            ProgramError::BuiltinProgramsMustConsumeComputeUnits => to_builtin(20),
81            ProgramError::InvalidAccountOwner => to_builtin(21),
82            ProgramError::ArithmeticOverflow => to_builtin(22),
83            ProgramError::Immutable => to_builtin(23),
84            ProgramError::IncorrectAuthority => to_builtin(24),
85        }
86    }
87}
88
89impl From<u64> for ProgramError {
90    fn from(code: u64) -> Self {
91        match code {
92            CUSTOM_ZERO => ProgramError::Custom(0),
93            c if c == to_builtin(0) => ProgramError::InvalidArgument,
94            c if c == to_builtin(1) => ProgramError::InvalidInstructionData,
95            c if c == to_builtin(2) => ProgramError::InvalidAccountData,
96            c if c == to_builtin(3) => ProgramError::AccountDataTooSmall,
97            c if c == to_builtin(4) => ProgramError::InsufficientFunds,
98            c if c == to_builtin(5) => ProgramError::IncorrectProgramId,
99            c if c == to_builtin(6) => ProgramError::MissingRequiredSignature,
100            c if c == to_builtin(7) => ProgramError::AccountAlreadyInitialized,
101            c if c == to_builtin(8) => ProgramError::UninitializedAccount,
102            c if c == to_builtin(9) => ProgramError::NotEnoughAccountKeys,
103            c if c == to_builtin(10) => ProgramError::AccountBorrowFailed,
104            c if c == to_builtin(11) => ProgramError::MaxSeedLengthExceeded,
105            c if c == to_builtin(12) => ProgramError::InvalidSeeds,
106            c if c == to_builtin(13) => ProgramError::BorshIoError,
107            c if c == to_builtin(14) => ProgramError::AccountNotRentExempt,
108            c if c == to_builtin(15) => ProgramError::UnsupportedSysvar,
109            c if c == to_builtin(16) => ProgramError::IllegalOwner,
110            c if c == to_builtin(17) => ProgramError::MaxAccountsDataAllocationsExceeded,
111            c if c == to_builtin(18) => ProgramError::InvalidRealloc,
112            c if c == to_builtin(19) => ProgramError::MaxInstructionTraceLengthExceeded,
113            c if c == to_builtin(20) => ProgramError::BuiltinProgramsMustConsumeComputeUnits,
114            c if c == to_builtin(21) => ProgramError::InvalidAccountOwner,
115            c if c == to_builtin(22) => ProgramError::ArithmeticOverflow,
116            c if c == to_builtin(23) => ProgramError::Immutable,
117            c if c == to_builtin(24) => ProgramError::IncorrectAuthority,
118            other => ProgramError::Custom(other as u32),
119        }
120    }
121}
122
123impl core::fmt::Display for ProgramError {
124    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
125        match self {
126            ProgramError::Custom(code) => write!(f, "Custom({code})"),
127            ProgramError::InvalidArgument => write!(f, "InvalidArgument"),
128            ProgramError::InvalidInstructionData => write!(f, "InvalidInstructionData"),
129            ProgramError::InvalidAccountData => write!(f, "InvalidAccountData"),
130            ProgramError::AccountDataTooSmall => write!(f, "AccountDataTooSmall"),
131            ProgramError::InsufficientFunds => write!(f, "InsufficientFunds"),
132            ProgramError::IncorrectProgramId => write!(f, "IncorrectProgramId"),
133            ProgramError::MissingRequiredSignature => write!(f, "MissingRequiredSignature"),
134            ProgramError::AccountAlreadyInitialized => write!(f, "AccountAlreadyInitialized"),
135            ProgramError::UninitializedAccount => write!(f, "UninitializedAccount"),
136            ProgramError::NotEnoughAccountKeys => write!(f, "NotEnoughAccountKeys"),
137            ProgramError::AccountBorrowFailed => write!(f, "AccountBorrowFailed"),
138            ProgramError::MaxSeedLengthExceeded => write!(f, "MaxSeedLengthExceeded"),
139            ProgramError::InvalidSeeds => write!(f, "InvalidSeeds"),
140            ProgramError::BorshIoError => write!(f, "BorshIoError"),
141            ProgramError::AccountNotRentExempt => write!(f, "AccountNotRentExempt"),
142            ProgramError::UnsupportedSysvar => write!(f, "UnsupportedSysvar"),
143            ProgramError::IllegalOwner => write!(f, "IllegalOwner"),
144            ProgramError::MaxAccountsDataAllocationsExceeded => write!(f, "MaxAccountsDataAllocationsExceeded"),
145            ProgramError::InvalidRealloc => write!(f, "InvalidRealloc"),
146            ProgramError::MaxInstructionTraceLengthExceeded => write!(f, "MaxInstructionTraceLengthExceeded"),
147            ProgramError::BuiltinProgramsMustConsumeComputeUnits => write!(f, "BuiltinProgramsMustConsumeComputeUnits"),
148            ProgramError::InvalidAccountOwner => write!(f, "InvalidAccountOwner"),
149            ProgramError::ArithmeticOverflow => write!(f, "ArithmeticOverflow"),
150            ProgramError::Immutable => write!(f, "Immutable"),
151            ProgramError::IncorrectAuthority => write!(f, "IncorrectAuthority"),
152        }
153    }
154}
155
156// ── Backend conversions ──────────────────────────────────────────────
157
158/// Convert between Hopper and backend ProgramError via u64 error codes.
159///
160/// Both types encode errors as the same u64 values, so we round-trip
161/// through the integer representation. This is zero-cost in the happy
162/// path (errors are exceptional).
163#[cfg(feature = "hopper-native-backend")]
164impl From<hopper_native::error::ProgramError> for ProgramError {
165    #[inline]
166    fn from(e: hopper_native::error::ProgramError) -> Self {
167        ProgramError::from(u64::from(e))
168    }
169}
170
171#[cfg(feature = "hopper-native-backend")]
172impl From<ProgramError> for hopper_native::error::ProgramError {
173    #[inline]
174    fn from(e: ProgramError) -> Self {
175        hopper_native::error::ProgramError::from(u64::from(e))
176    }
177}
178
179// ══════════════════════════════════════════════════════════════════════
180//  Cold error constructors
181// ══════════════════════════════════════════════════════════════════════
182//
183// Following Pinocchio's pattern: `#[cold]` + `#[inline(never)]` on error
184// return helpers keeps the error path out of the hot-path instruction cache.
185// Call sites become a single branch + call, keeping the inlined fast path tiny.
186
187impl ProgramError {
188    #[cold]
189    #[inline(never)]
190    pub fn err_data_too_small<T>() -> Result<T, Self> {
191        Err(ProgramError::AccountDataTooSmall)
192    }
193
194    #[cold]
195    #[inline(never)]
196    pub fn err_invalid_data<T>() -> Result<T, Self> {
197        Err(ProgramError::InvalidAccountData)
198    }
199
200    #[cold]
201    #[inline(never)]
202    pub fn err_missing_signer<T>() -> Result<T, Self> {
203        Err(ProgramError::MissingRequiredSignature)
204    }
205
206    #[cold]
207    #[inline(never)]
208    pub fn err_immutable<T>() -> Result<T, Self> {
209        Err(ProgramError::Immutable)
210    }
211
212    #[cold]
213    #[inline(never)]
214    pub fn err_not_enough_keys<T>() -> Result<T, Self> {
215        Err(ProgramError::NotEnoughAccountKeys)
216    }
217
218    #[cold]
219    #[inline(never)]
220    pub fn err_borrow_failed<T>() -> Result<T, Self> {
221        Err(ProgramError::AccountBorrowFailed)
222    }
223
224    #[cold]
225    #[inline(never)]
226    pub fn err_overflow<T>() -> Result<T, Self> {
227        Err(ProgramError::ArithmeticOverflow)
228    }
229
230    #[cold]
231    #[inline(never)]
232    pub fn err_invalid_argument<T>() -> Result<T, Self> {
233        Err(ProgramError::InvalidArgument)
234    }
235
236    #[cold]
237    #[inline(never)]
238    pub fn err_incorrect_program<T>() -> Result<T, Self> {
239        Err(ProgramError::IncorrectProgramId)
240    }
241}
242