1#[derive(Clone, Debug, Eq, PartialEq)]
22#[repr(u64)]
23pub enum ProgramError {
24 Custom(u32) = 0,
26 InvalidArgument = 1,
27 InvalidInstructionData = 2,
28 InvalidAccountData = 3,
29 AccountDataTooSmall = 4,
30 InsufficientFunds = 5,
31 IncorrectProgramId = 6,
32 MissingRequiredSignature = 7,
33 AccountAlreadyInitialized = 8,
34 UninitializedAccount = 9,
35 NotEnoughAccountKeys = 10,
36 AccountBorrowFailed = 11,
37 MaxSeedLengthExceeded = 12,
38 InvalidSeeds = 13,
39 BorshIoError = 14,
40 AccountNotRentExempt = 15,
41 UnsupportedSysvar = 16,
42 IllegalOwner = 17,
43 MaxAccountsDataAllocationsExceeded = 18,
44 InvalidRealloc = 19,
45 MaxInstructionTraceLengthExceeded = 20,
46 BuiltinProgramsMustConsumeComputeUnits = 21,
47 InvalidAccountOwner = 22,
48 ArithmeticOverflow = 23,
49 Immutable = 24,
50 IncorrectAuthority = 25,
51}
52
53const BUILTIN_BIT_SHIFT: usize = 32;
57const CUSTOM_ZERO: u64 = 1_u64 << BUILTIN_BIT_SHIFT;
58
59const BUILTIN_LOW_MASK: u64 = (1_u64 << BUILTIN_BIT_SHIFT) - 1;
60
61#[cfg(test)]
66#[inline(always)]
67const fn to_builtin(index: u64) -> u64 {
68 (index + 2) << BUILTIN_BIT_SHIFT
69}
70
71impl From<ProgramError> for u64 {
72 #[inline]
73 fn from(err: ProgramError) -> u64 {
74 match err {
75 ProgramError::Custom(0) => CUSTOM_ZERO,
76 ProgramError::Custom(code) => code as u64,
77 builtin => {
78 let tag = unsafe { *(&builtin as *const ProgramError as *const u64) };
85 (tag + 1) << BUILTIN_BIT_SHIFT
88 }
89 }
90 }
91}
92
93impl From<u64> for ProgramError {
94 fn from(code: u64) -> Self {
95 if code == CUSTOM_ZERO {
96 return ProgramError::Custom(0);
97 }
98 let builtin = code >> BUILTIN_BIT_SHIFT;
99 if code & BUILTIN_LOW_MASK == 0 && builtin >= 2 {
100 match builtin - 2 {
101 0 => return ProgramError::InvalidArgument,
102 1 => return ProgramError::InvalidInstructionData,
103 2 => return ProgramError::InvalidAccountData,
104 3 => return ProgramError::AccountDataTooSmall,
105 4 => return ProgramError::InsufficientFunds,
106 5 => return ProgramError::IncorrectProgramId,
107 6 => return ProgramError::MissingRequiredSignature,
108 7 => return ProgramError::AccountAlreadyInitialized,
109 8 => return ProgramError::UninitializedAccount,
110 9 => return ProgramError::NotEnoughAccountKeys,
111 10 => return ProgramError::AccountBorrowFailed,
112 11 => return ProgramError::MaxSeedLengthExceeded,
113 12 => return ProgramError::InvalidSeeds,
114 13 => return ProgramError::BorshIoError,
115 14 => return ProgramError::AccountNotRentExempt,
116 15 => return ProgramError::UnsupportedSysvar,
117 16 => return ProgramError::IllegalOwner,
118 17 => return ProgramError::MaxAccountsDataAllocationsExceeded,
119 18 => return ProgramError::InvalidRealloc,
120 19 => return ProgramError::MaxInstructionTraceLengthExceeded,
121 20 => return ProgramError::BuiltinProgramsMustConsumeComputeUnits,
122 21 => return ProgramError::InvalidAccountOwner,
123 22 => return ProgramError::ArithmeticOverflow,
124 23 => return ProgramError::Immutable,
125 24 => return ProgramError::IncorrectAuthority,
126 _ => {}
127 }
128 }
129 ProgramError::Custom(code as u32)
130 }
131}
132
133impl core::fmt::Display for ProgramError {
134 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
135 match self {
136 ProgramError::Custom(code) => write!(f, "Custom({code})"),
137 ProgramError::InvalidArgument => write!(f, "InvalidArgument"),
138 ProgramError::InvalidInstructionData => write!(f, "InvalidInstructionData"),
139 ProgramError::InvalidAccountData => write!(f, "InvalidAccountData"),
140 ProgramError::AccountDataTooSmall => write!(f, "AccountDataTooSmall"),
141 ProgramError::InsufficientFunds => write!(f, "InsufficientFunds"),
142 ProgramError::IncorrectProgramId => write!(f, "IncorrectProgramId"),
143 ProgramError::MissingRequiredSignature => write!(f, "MissingRequiredSignature"),
144 ProgramError::AccountAlreadyInitialized => write!(f, "AccountAlreadyInitialized"),
145 ProgramError::UninitializedAccount => write!(f, "UninitializedAccount"),
146 ProgramError::NotEnoughAccountKeys => write!(f, "NotEnoughAccountKeys"),
147 ProgramError::AccountBorrowFailed => write!(f, "AccountBorrowFailed"),
148 ProgramError::MaxSeedLengthExceeded => write!(f, "MaxSeedLengthExceeded"),
149 ProgramError::InvalidSeeds => write!(f, "InvalidSeeds"),
150 ProgramError::BorshIoError => write!(f, "BorshIoError"),
151 ProgramError::AccountNotRentExempt => write!(f, "AccountNotRentExempt"),
152 ProgramError::UnsupportedSysvar => write!(f, "UnsupportedSysvar"),
153 ProgramError::IllegalOwner => write!(f, "IllegalOwner"),
154 ProgramError::MaxAccountsDataAllocationsExceeded => {
155 write!(f, "MaxAccountsDataAllocationsExceeded")
156 }
157 ProgramError::InvalidRealloc => write!(f, "InvalidRealloc"),
158 ProgramError::MaxInstructionTraceLengthExceeded => {
159 write!(f, "MaxInstructionTraceLengthExceeded")
160 }
161 ProgramError::BuiltinProgramsMustConsumeComputeUnits => {
162 write!(f, "BuiltinProgramsMustConsumeComputeUnits")
163 }
164 ProgramError::InvalidAccountOwner => write!(f, "InvalidAccountOwner"),
165 ProgramError::ArithmeticOverflow => write!(f, "ArithmeticOverflow"),
166 ProgramError::Immutable => write!(f, "Immutable"),
167 ProgramError::IncorrectAuthority => write!(f, "IncorrectAuthority"),
168 }
169 }
170}
171
172const _: () = assert!(
184 core::mem::size_of::<ProgramError>()
185 == core::mem::size_of::<hopper_native::error::ProgramError>()
186);
187const _: () = assert!(
188 core::mem::align_of::<ProgramError>()
189 == core::mem::align_of::<hopper_native::error::ProgramError>()
190);
191
192impl From<hopper_native::error::ProgramError> for ProgramError {
193 #[inline(always)]
194 fn from(e: hopper_native::error::ProgramError) -> Self {
195 unsafe { core::mem::transmute::<hopper_native::error::ProgramError, ProgramError>(e) }
202 }
203}
204
205impl From<ProgramError> for hopper_native::error::ProgramError {
206 #[inline(always)]
207 fn from(e: ProgramError) -> Self {
208 unsafe { core::mem::transmute::<ProgramError, hopper_native::error::ProgramError>(e) }
210 }
211}
212
213impl ProgramError {
222 #[inline(always)]
223 pub fn err_data_too_small<T>() -> Result<T, Self> {
224 Err(ProgramError::AccountDataTooSmall)
225 }
226
227 #[cold]
228 #[inline(never)]
229 pub fn err_invalid_data<T>() -> Result<T, Self> {
230 Err(ProgramError::InvalidAccountData)
231 }
232
233 #[cold]
234 #[inline(never)]
235 pub fn err_missing_signer<T>() -> Result<T, Self> {
236 Err(ProgramError::MissingRequiredSignature)
237 }
238
239 #[inline(always)]
240 pub fn err_immutable<T>() -> Result<T, Self> {
241 Err(ProgramError::Immutable)
242 }
243
244 #[cold]
245 #[inline(never)]
246 pub fn err_not_enough_keys<T>() -> Result<T, Self> {
247 Err(ProgramError::NotEnoughAccountKeys)
248 }
249
250 #[cold]
251 #[inline(never)]
252 pub fn err_borrow_failed<T>() -> Result<T, Self> {
253 Err(ProgramError::AccountBorrowFailed)
254 }
255
256 #[cold]
257 #[inline(never)]
258 pub fn err_overflow<T>() -> Result<T, Self> {
259 Err(ProgramError::ArithmeticOverflow)
260 }
261
262 #[cold]
263 #[inline(never)]
264 pub fn err_invalid_argument<T>() -> Result<T, Self> {
265 Err(ProgramError::InvalidArgument)
266 }
267
268 #[inline(always)]
269 pub fn err_incorrect_program<T>() -> Result<T, Self> {
270 Err(ProgramError::IncorrectProgramId)
271 }
272}
273
274#[cfg(test)]
275mod tag_encoding_tests {
276 use super::*;
277
278 fn all_variants() -> [ProgramError; 29] {
279 [
280 ProgramError::Custom(0),
281 ProgramError::Custom(1),
282 ProgramError::Custom(0xD003),
283 ProgramError::Custom(u32::MAX),
284 ProgramError::InvalidArgument,
285 ProgramError::InvalidInstructionData,
286 ProgramError::InvalidAccountData,
287 ProgramError::AccountDataTooSmall,
288 ProgramError::InsufficientFunds,
289 ProgramError::IncorrectProgramId,
290 ProgramError::MissingRequiredSignature,
291 ProgramError::AccountAlreadyInitialized,
292 ProgramError::UninitializedAccount,
293 ProgramError::NotEnoughAccountKeys,
294 ProgramError::AccountBorrowFailed,
295 ProgramError::MaxSeedLengthExceeded,
296 ProgramError::InvalidSeeds,
297 ProgramError::BorshIoError,
298 ProgramError::AccountNotRentExempt,
299 ProgramError::UnsupportedSysvar,
300 ProgramError::IllegalOwner,
301 ProgramError::MaxAccountsDataAllocationsExceeded,
302 ProgramError::InvalidRealloc,
303 ProgramError::MaxInstructionTraceLengthExceeded,
304 ProgramError::BuiltinProgramsMustConsumeComputeUnits,
305 ProgramError::InvalidAccountOwner,
306 ProgramError::ArithmeticOverflow,
307 ProgramError::Immutable,
308 ProgramError::IncorrectAuthority,
309 ]
310 }
311
312 #[test]
318 fn u64_conversion_matches_reference_for_every_variant() {
319 fn reference(err: &ProgramError) -> u64 {
320 match err {
321 ProgramError::Custom(0) => CUSTOM_ZERO,
322 ProgramError::Custom(code) => *code as u64,
323 ProgramError::InvalidArgument => to_builtin(0),
324 ProgramError::InvalidInstructionData => to_builtin(1),
325 ProgramError::InvalidAccountData => to_builtin(2),
326 ProgramError::AccountDataTooSmall => to_builtin(3),
327 ProgramError::InsufficientFunds => to_builtin(4),
328 ProgramError::IncorrectProgramId => to_builtin(5),
329 ProgramError::MissingRequiredSignature => to_builtin(6),
330 ProgramError::AccountAlreadyInitialized => to_builtin(7),
331 ProgramError::UninitializedAccount => to_builtin(8),
332 ProgramError::NotEnoughAccountKeys => to_builtin(9),
333 ProgramError::AccountBorrowFailed => to_builtin(10),
334 ProgramError::MaxSeedLengthExceeded => to_builtin(11),
335 ProgramError::InvalidSeeds => to_builtin(12),
336 ProgramError::BorshIoError => to_builtin(13),
337 ProgramError::AccountNotRentExempt => to_builtin(14),
338 ProgramError::UnsupportedSysvar => to_builtin(15),
339 ProgramError::IllegalOwner => to_builtin(16),
340 ProgramError::MaxAccountsDataAllocationsExceeded => to_builtin(17),
341 ProgramError::InvalidRealloc => to_builtin(18),
342 ProgramError::MaxInstructionTraceLengthExceeded => to_builtin(19),
343 ProgramError::BuiltinProgramsMustConsumeComputeUnits => to_builtin(20),
344 ProgramError::InvalidAccountOwner => to_builtin(21),
345 ProgramError::ArithmeticOverflow => to_builtin(22),
346 ProgramError::Immutable => to_builtin(23),
347 ProgramError::IncorrectAuthority => to_builtin(24),
348 }
349 }
350 for err in all_variants() {
351 let want = reference(&err);
352 let got: u64 = err.clone().into();
353 assert_eq!(got, want, "u64 code diverged for {err:?}");
354 assert_eq!(ProgramError::from(want), err, "roundtrip for {err:?}");
355 }
356 }
357
358 #[test]
361 fn glue_roundtrips_identity_for_every_variant() {
362 for err in all_variants() {
363 let native: hopper_native::error::ProgramError = err.clone().into();
364 assert_eq!(
365 u64::from(native.clone()),
366 u64::from(err.clone()),
367 "wire code diverged crossing into native for {err:?}"
368 );
369 let back: ProgramError = native.into();
370 assert_eq!(back, err, "native->runtime glue not identity for {err:?}");
371 }
372 }
373}