Remove program error footgun and cleaner developer experience (#8042)

This commit is contained in:
Jack May
2020-01-31 10:58:07 -08:00
committed by GitHub
parent eff876881b
commit 0fd795a676
26 changed files with 127 additions and 160 deletions

View File

@ -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
}
}
}