Remove program error footgun and cleaner developer experience (#8042)
This commit is contained in:
@ -3,12 +3,9 @@ use num_traits::ToPrimitive;
|
||||
|
||||
/// Reasons the program may fail
|
||||
pub enum ProgramError {
|
||||
/// CustomError allows programs to implement program-specific error types and see
|
||||
/// them returned by the Solana runtime. A CustomError may be any type that is represented
|
||||
/// as or serialized to a u32 integer.
|
||||
///
|
||||
/// NOTE: u64 requires special serialization to avoid the loss of precision in JS clients and
|
||||
/// so is not used for now.
|
||||
/// Allows on-chain programs to implement program-specific error types and see them returned
|
||||
/// by the Solana runtime. A program-specific error may be any type that is represented as
|
||||
/// or serialized to a u32 integer.
|
||||
CustomError(u32),
|
||||
/// The arguments provided to a program instruction where invalid
|
||||
InvalidArgument,
|
||||
@ -34,47 +31,28 @@ pub enum ProgramError {
|
||||
AccountBorrowFailed,
|
||||
}
|
||||
|
||||
/// 32bit representations of builtin program errors returned by the entry point
|
||||
const BUILTIN_ERROR_START: u32 = 0x8000_0000; // 31st bit set
|
||||
const INVALID_ARGUMENT: u32 = BUILTIN_ERROR_START;
|
||||
const INVALID_INSTRUCTION_DATA: u32 = BUILTIN_ERROR_START + 1;
|
||||
const INVALID_ACCOUNT_DATA: u32 = BUILTIN_ERROR_START + 2;
|
||||
const ACCOUNT_DATA_TOO_SMALL: u32 = BUILTIN_ERROR_START + 3;
|
||||
const INSUFFICIENT_FUNDS: u32 = BUILTIN_ERROR_START + 4;
|
||||
const INCORRECT_PROGRAM_ID: u32 = BUILTIN_ERROR_START + 5;
|
||||
const MISSING_REQUIRED_SIGNATURES: u32 = BUILTIN_ERROR_START + 6;
|
||||
const ACCOUNT_ALREADY_INITIALIZED: u32 = BUILTIN_ERROR_START + 7;
|
||||
const UNINITIALIZED_ACCOUNT: u32 = BUILTIN_ERROR_START + 8;
|
||||
const NOT_ENOUGH_ACCOUNT_KEYS: u32 = BUILTIN_ERROR_START + 9;
|
||||
const ACCOUNT_BORROW_FAILED: u32 = BUILTIN_ERROR_START + 10;
|
||||
|
||||
/// Is this a builtin error? (is 31th bit set?)
|
||||
fn is_builtin(error: u32) -> bool {
|
||||
(error & BUILTIN_ERROR_START) != 0
|
||||
/// Builtin return values occupy the upper 32 bits
|
||||
const BUILTIN_BIT_SHIFT: usize = 32;
|
||||
macro_rules! to_builtin {
|
||||
($error:expr) => {
|
||||
($error as u64) << BUILTIN_BIT_SHIFT
|
||||
};
|
||||
}
|
||||
|
||||
/// If a program defined error conflicts with a builtin error
|
||||
/// its 30th bit is set before returning to distinguish it.
|
||||
/// The side effect is that the original error's 30th bit
|
||||
/// value is lost, be aware.
|
||||
const CONFLICTING_ERROR_MARK: u32 = 0x4000_0000; // 30st bit set
|
||||
const CUSTOM_ZERO: u64 = to_builtin!(1);
|
||||
const INVALID_ARGUMENT: u64 = to_builtin!(2);
|
||||
const INVALID_INSTRUCTION_DATA: u64 = to_builtin!(3);
|
||||
const INVALID_ACCOUNT_DATA: u64 = to_builtin!(4);
|
||||
const ACCOUNT_DATA_TOO_SMALL: u64 = to_builtin!(5);
|
||||
const INSUFFICIENT_FUNDS: u64 = to_builtin!(6);
|
||||
const INCORRECT_PROGRAM_ID: u64 = to_builtin!(7);
|
||||
const MISSING_REQUIRED_SIGNATURES: u64 = to_builtin!(8);
|
||||
const ACCOUNT_ALREADY_INITIALIZED: u64 = to_builtin!(9);
|
||||
const UNINITIALIZED_ACCOUNT: u64 = to_builtin!(10);
|
||||
const NOT_ENOUGH_ACCOUNT_KEYS: u64 = to_builtin!(11);
|
||||
const ACCOUNT_BORROW_FAILED: u64 = to_builtin!(12);
|
||||
|
||||
/// Is this error marked as conflicting? (is 30th bit set?)
|
||||
fn is_marked_conflicting(error: u32) -> bool {
|
||||
(error & CONFLICTING_ERROR_MARK) != 0
|
||||
}
|
||||
|
||||
/// Mark as a conflicting error
|
||||
fn mark_conflicting(error: u32) -> u32 {
|
||||
error | CONFLICTING_ERROR_MARK
|
||||
}
|
||||
|
||||
/// Unmark as a conflicting error
|
||||
fn unmark_conflicting(error: u32) -> u32 {
|
||||
error & !CONFLICTING_ERROR_MARK
|
||||
}
|
||||
|
||||
impl From<ProgramError> for u32 {
|
||||
impl From<ProgramError> for u64 {
|
||||
fn from(error: ProgramError) -> Self {
|
||||
match error {
|
||||
ProgramError::InvalidArgument => INVALID_ARGUMENT,
|
||||
@ -89,10 +67,10 @@ impl From<ProgramError> for u32 {
|
||||
ProgramError::NotEnoughAccountKeys => NOT_ENOUGH_ACCOUNT_KEYS,
|
||||
ProgramError::AccountBorrowFailed => ACCOUNT_BORROW_FAILED,
|
||||
ProgramError::CustomError(error) => {
|
||||
if error == 0 || is_builtin(error) {
|
||||
mark_conflicting(error)
|
||||
if error == 0 {
|
||||
CUSTOM_ZERO
|
||||
} else {
|
||||
error
|
||||
error as u64
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -104,8 +82,9 @@ where
|
||||
T: ToPrimitive,
|
||||
{
|
||||
fn from(error: T) -> Self {
|
||||
let error = error.to_u32().unwrap_or(0xbad_c0de);
|
||||
let error = error.to_u64().unwrap_or(0xbad_c0de);
|
||||
match error {
|
||||
CUSTOM_ZERO => InstructionError::CustomError(0),
|
||||
INVALID_ARGUMENT => InstructionError::InvalidArgument,
|
||||
INVALID_INSTRUCTION_DATA => InstructionError::InvalidInstructionData,
|
||||
INVALID_ACCOUNT_DATA => InstructionError::InvalidAccountData,
|
||||
@ -118,10 +97,11 @@ where
|
||||
NOT_ENOUGH_ACCOUNT_KEYS => InstructionError::NotEnoughAccountKeys,
|
||||
ACCOUNT_BORROW_FAILED => InstructionError::AccountBorrowFailed,
|
||||
_ => {
|
||||
if is_marked_conflicting(error) {
|
||||
InstructionError::ConflictingError(unmark_conflicting(error))
|
||||
// A valid custom error has no bits set in the upper 32
|
||||
if error >> BUILTIN_BIT_SHIFT == 0 {
|
||||
InstructionError::CustomError(error as u32)
|
||||
} else {
|
||||
InstructionError::CustomError(error)
|
||||
InstructionError::InvalidError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user