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 => {
145                write!(f, "MaxAccountsDataAllocationsExceeded")
146            }
147            ProgramError::InvalidRealloc => write!(f, "InvalidRealloc"),
148            ProgramError::MaxInstructionTraceLengthExceeded => {
149                write!(f, "MaxInstructionTraceLengthExceeded")
150            }
151            ProgramError::BuiltinProgramsMustConsumeComputeUnits => {
152                write!(f, "BuiltinProgramsMustConsumeComputeUnits")
153            }
154            ProgramError::InvalidAccountOwner => write!(f, "InvalidAccountOwner"),
155            ProgramError::ArithmeticOverflow => write!(f, "ArithmeticOverflow"),
156            ProgramError::Immutable => write!(f, "Immutable"),
157            ProgramError::IncorrectAuthority => write!(f, "IncorrectAuthority"),
158        }
159    }
160}
161
162// ── Backend conversions ──────────────────────────────────────────────
163
164/// Convert between Hopper and backend ProgramError via u64 error codes.
165///
166/// Both types encode errors as the same u64 values, so we round-trip
167/// through the integer representation. This is zero-cost in the happy
168/// path (errors are exceptional).
169#[cfg(feature = "hopper-native-backend")]
170impl From<hopper_native::error::ProgramError> for ProgramError {
171    #[inline]
172    fn from(e: hopper_native::error::ProgramError) -> Self {
173        ProgramError::from(u64::from(e))
174    }
175}
176
177#[cfg(feature = "hopper-native-backend")]
178impl From<ProgramError> for hopper_native::error::ProgramError {
179    #[inline]
180    fn from(e: ProgramError) -> Self {
181        hopper_native::error::ProgramError::from(u64::from(e))
182    }
183}
184
185// ══════════════════════════════════════════════════════════════════════
186//  Cold error constructors
187// ══════════════════════════════════════════════════════════════════════
188//
189// Following Pinocchio's pattern: `#[cold]` + `#[inline(never)]` on error
190// return helpers keeps the error path out of the hot-path instruction cache.
191// Call sites become a single branch + call, keeping the inlined fast path tiny.
192
193impl ProgramError {
194    #[cold]
195    #[inline(never)]
196    pub fn err_data_too_small<T>() -> Result<T, Self> {
197        Err(ProgramError::AccountDataTooSmall)
198    }
199
200    #[cold]
201    #[inline(never)]
202    pub fn err_invalid_data<T>() -> Result<T, Self> {
203        Err(ProgramError::InvalidAccountData)
204    }
205
206    #[cold]
207    #[inline(never)]
208    pub fn err_missing_signer<T>() -> Result<T, Self> {
209        Err(ProgramError::MissingRequiredSignature)
210    }
211
212    #[cold]
213    #[inline(never)]
214    pub fn err_immutable<T>() -> Result<T, Self> {
215        Err(ProgramError::Immutable)
216    }
217
218    #[cold]
219    #[inline(never)]
220    pub fn err_not_enough_keys<T>() -> Result<T, Self> {
221        Err(ProgramError::NotEnoughAccountKeys)
222    }
223
224    #[cold]
225    #[inline(never)]
226    pub fn err_borrow_failed<T>() -> Result<T, Self> {
227        Err(ProgramError::AccountBorrowFailed)
228    }
229
230    #[cold]
231    #[inline(never)]
232    pub fn err_overflow<T>() -> Result<T, Self> {
233        Err(ProgramError::ArithmeticOverflow)
234    }
235
236    #[cold]
237    #[inline(never)]
238    pub fn err_invalid_argument<T>() -> Result<T, Self> {
239        Err(ProgramError::InvalidArgument)
240    }
241
242    #[cold]
243    #[inline(never)]
244    pub fn err_incorrect_program<T>() -> Result<T, Self> {
245        Err(ProgramError::IncorrectProgramId)
246    }
247}