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

@ -6,7 +6,7 @@
#include <solana_sdk.h> #include <solana_sdk.h>
extern uint32_t entrypoint(const uint8_t *input) { extern uint64_t entrypoint(const uint8_t *input) {
uint64_t x = *(uint64_t *) input; uint64_t x = *(uint64_t *) input;
uint64_t *result = (uint64_t *) input + 1; uint64_t *result = (uint64_t *) input + 1;
uint64_t count = 0; uint64_t count = 0;

View File

@ -6,7 +6,7 @@
#include "helper.h" #include "helper.h"
extern uint32_t entrypoint(const uint8_t *input) { extern uint64_t entrypoint(const uint8_t *input) {
sol_log(__FILE__); sol_log(__FILE__);
helper_function(); helper_function();
sol_log(__FILE__); sol_log(__FILE__);

View File

@ -8,12 +8,12 @@
* Custom error for when input serialization fails * Custom error for when input serialization fails
*/ */
extern uint32_t entrypoint(const uint8_t *input) { extern uint64_t entrypoint(const uint8_t *input) {
SolKeyedAccount ka[4]; SolKeyedAccount ka[4];
SolParameters params = (SolParameters) { .ka = ka }; SolParameters params = (SolParameters) { .ka = ka };
if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(ka))) { if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(ka))) {
return INVALID_ARGUMENT; return ERROR_INVALID_ARGUMENT;
} }
switch (params.data[0]) { switch (params.data[0]) {
@ -48,7 +48,7 @@ extern uint32_t entrypoint(const uint8_t *input) {
break; break;
default: default:
sol_log("Unrecognized command"); sol_log("Unrecognized command");
return INVALID_INSTRUCTION_DATA; return ERROR_INVALID_INSTRUCTION_DATA;
} }
return SUCCESS; return SUCCESS;
} }

View File

@ -8,12 +8,12 @@
* Custom error for when input serialization fails * Custom error for when input serialization fails
*/ */
extern uint32_t entrypoint(const uint8_t *input) { extern uint64_t entrypoint(const uint8_t *input) {
SolKeyedAccount ka[4]; SolKeyedAccount ka[4];
SolParameters params = (SolParameters) { .ka = ka }; SolParameters params = (SolParameters) { .ka = ka };
if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(ka))) { if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(ka))) {
return INVALID_ARGUMENT; return ERROR_INVALID_ARGUMENT;
} }
switch (params.data[0]) { switch (params.data[0]) {
@ -22,16 +22,19 @@ extern uint32_t entrypoint(const uint8_t *input) {
return SUCCESS; return SUCCESS;
case(2): case(2):
sol_log("return a builtin"); sol_log("return a builtin");
return INVALID_ACCOUNT_DATA; return ERROR_INVALID_ACCOUNT_DATA;
case(3): case(3):
sol_log("return custom error"); sol_log("return custom error 42");
return 42; return 42;
case(4): case(4):
sol_log("return error that conflicts with success"); sol_log("return an invalid error");
return 0x40000000; return ERROR_INVALID_ACCOUNT_DATA + 1;
case(5):
sol_log("return unknown builtin");
return TO_BUILTIN(50);
default: default:
sol_log("Unrecognized command"); sol_log("Unrecognized command");
return INVALID_INSTRUCTION_DATA; return ERROR_INVALID_INSTRUCTION_DATA;
} }
return SUCCESS; return SUCCESS;
} }

View File

@ -11,17 +11,17 @@
*/ */
#define NUM_KA 3 #define NUM_KA 3
extern uint32_t entrypoint(const uint8_t *input) { extern uint64_t entrypoint(const uint8_t *input) {
SolKeyedAccount ka[NUM_KA]; SolKeyedAccount ka[NUM_KA];
SolParameters params = (SolParameters) { .ka = ka }; SolParameters params = (SolParameters) { .ka = ka };
if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(ka))) { if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(ka))) {
return INVALID_ARGUMENT; return ERROR_INVALID_ARGUMENT;
} }
if (!params.ka[0].is_signer) { if (!params.ka[0].is_signer) {
sol_log("Transaction not signed by key 0"); sol_log("Transaction not signed by key 0");
return MISSING_REQUIRED_SIGNATURES; return ERROR_MISSING_REQUIRED_SIGNATURES;
} }
int64_t lamports = *(int64_t *)params.data; int64_t lamports = *(int64_t *)params.data;

View File

@ -3,7 +3,7 @@
static const char msg[] = "This is a message"; static const char msg[] = "This is a message";
static const char msg2[] = "This is a different message"; static const char msg2[] = "This is a different message";
extern uint32_t entrypoint(const uint8_t *input) { extern uint64_t entrypoint(const uint8_t *input) {
sol_log((char*)msg); sol_log((char*)msg);
sol_log((char*)msg2); sol_log((char*)msg2);
return SUCCESS; return SUCCESS;

View File

@ -9,14 +9,14 @@
*/ */
#define INVALID_INPUT 1 #define INVALID_INPUT 1
extern uint32_t entrypoint(const uint8_t *input) { extern uint64_t entrypoint(const uint8_t *input) {
SolKeyedAccount ka[1]; SolKeyedAccount ka[1];
SolParameters params = (SolParameters) { .ka = ka }; SolParameters params = (SolParameters) { .ka = ka };
sol_log(__FILE__); sol_log(__FILE__);
if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(ka))) { if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(ka))) {
return INVALID_ARGUMENT; return ERROR_INVALID_ARGUMENT;
} }
// Log the provided input parameters. In the case of the no-op // Log the provided input parameters. In the case of the no-op

View File

@ -4,14 +4,14 @@
*/ */
#include <solana_sdk.h> #include <solana_sdk.h>
extern uint32_t entrypoint(const uint8_t *input) { extern uint64_t entrypoint(const uint8_t *input) {
SolKeyedAccount ka[1]; SolKeyedAccount ka[1];
SolParameters params = (SolParameters) { .ka = ka }; SolParameters params = (SolParameters) { .ka = ka };
sol_log(__FILE__); sol_log(__FILE__);
if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(ka))) { if (!sol_deserialize(input, &params, SOL_ARRAY_SIZE(ka))) {
return INVALID_ARGUMENT; return ERROR_INVALID_ARGUMENT;
} }
// Log the provided input parameters. In the case of the no-op // Log the provided input parameters. In the case of the no-op

View File

@ -4,7 +4,7 @@
*/ */
#include <solana_sdk.h> #include <solana_sdk.h>
extern uint32_t entrypoint(const uint8_t *input) { extern uint64_t entrypoint(const uint8_t *input) {
sol_panic(); sol_panic();
return SUCCESS; return SUCCESS;
} }

View File

@ -8,7 +8,7 @@ void __attribute__ ((noinline)) helper() {
sol_log(__func__); sol_log(__func__);
} }
extern uint32_t entrypoint(const uint8_t *input) { extern uint64_t entrypoint(const uint8_t *input) {
sol_log(__func__); sol_log(__func__);
helper(); helper();
return SUCCESS; return SUCCESS;

View File

@ -3,7 +3,7 @@
struct foo {const uint8_t *input;}; struct foo {const uint8_t *input;};
void foo(const uint8_t *input, struct foo foo) ; void foo(const uint8_t *input, struct foo foo) ;
extern uint32_t entrypoint(const uint8_t *input) { extern uint64_t entrypoint(const uint8_t *input) {
struct foo f; struct foo f;
f.input = input; f.input = input;
foo(input, f); foo(input, f);

View File

@ -15,7 +15,7 @@ static struct test_struct __attribute__ ((noinline)) test_function(void) {
return s; return s;
} }
extern uint32_t entrypoint(const uint8_t* input) { extern uint64_t entrypoint(const uint8_t* input) {
struct test_struct s = test_function(); struct test_struct s = test_function();
sol_log("foobar"); sol_log("foobar");
if (s.x + s.y + s.z == 12 ) { if (s.x + s.y + s.z == 12 ) {

View File

@ -4,7 +4,7 @@ extern crate solana_sdk;
use solana_sdk::entrypoint::SUCCESS; use solana_sdk::entrypoint::SUCCESS;
#[no_mangle] #[no_mangle]
pub extern "C" fn entrypoint(_input: *mut u8) -> u32 { pub extern "C" fn entrypoint(_input: *mut u8) -> u64 {
let x: u128 = 1; let x: u128 = 1;
let y = x.rotate_right(1); let y = x.rotate_right(1);
assert_eq!(y, 170_141_183_460_469_231_731_687_303_715_884_105_728); assert_eq!(y, 170_141_183_460_469_231_731_687_303_715_884_105_728);

View File

@ -7,7 +7,7 @@ use solana_sdk::{entrypoint::SUCCESS, info};
use std::{alloc::Layout, mem}; use std::{alloc::Layout, mem};
#[no_mangle] #[no_mangle]
pub extern "C" fn entrypoint(_input: *mut u8) -> u32 { pub extern "C" fn entrypoint(_input: *mut u8) -> u64 {
unsafe { unsafe {
// Confirm large allocation fails // Confirm large allocation fails

View File

@ -5,7 +5,7 @@ use byteorder::{ByteOrder, LittleEndian};
use solana_sdk::entrypoint::SUCCESS; use solana_sdk::entrypoint::SUCCESS;
#[no_mangle] #[no_mangle]
pub extern "C" fn entrypoint(_input: *mut u8) -> u32 { pub extern "C" fn entrypoint(_input: *mut u8) -> u64 {
let mut buf = [0; 4]; let mut buf = [0; 4];
LittleEndian::write_u32(&mut buf, 1_000_000); LittleEndian::write_u32(&mut buf, 1_000_000);
assert_eq!(1_000_000, LittleEndian::read_u32(&buf)); assert_eq!(1_000_000, LittleEndian::read_u32(&buf));

View File

@ -9,15 +9,11 @@ use thiserror::Error;
/// Custom program errors /// Custom program errors
#[derive(Error, Debug, Clone, PartialEq, FromPrimitive)] #[derive(Error, Debug, Clone, PartialEq, FromPrimitive)]
// Clippy compains about 0x8000_002d, but we don't care about C compatibility here
#[allow(clippy::enum_clike_unportable_variant)]
pub enum MyError { pub enum MyError {
#[error("Default enum start")]
DefaultEnumStart,
#[error("The Answer")] #[error("The Answer")]
TheAnswer = 42, TheAnswer = 42,
#[error("Conflicting with success")]
ConflictingSuccess = 0,
#[error("Conflicting with builtin")]
ConflictingBuiltin = 0x8000_002d,
} }
impl From<MyError> for ProgramError { impl From<MyError> for ProgramError {
fn from(e: MyError) -> Self { fn from(e: MyError) -> Self {
@ -41,16 +37,12 @@ fn process_instruction(
Err(ProgramError::InvalidAccountData) Err(ProgramError::InvalidAccountData)
} }
3 => { 3 => {
info!("return custom error"); info!("return default enum start value");
Err(MyError::TheAnswer.into()) Err(MyError::DefaultEnumStart.into())
} }
4 => { 4 => {
info!("return error that conflicts with success"); info!("return custom error");
Err(MyError::ConflictingSuccess.into()) Err(MyError::TheAnswer.into())
}
5 => {
info!("return error that conflicts with builtin");
Err(MyError::ConflictingBuiltin.into())
} }
6 => { 6 => {
let data = accounts[0].try_borrow_mut_data()?; let data = accounts[0].try_borrow_mut_data()?;

View File

@ -4,7 +4,7 @@ extern crate solana_sdk;
use solana_sdk::{entrypoint::SUCCESS, info}; use solana_sdk::{entrypoint::SUCCESS, info};
#[no_mangle] #[no_mangle]
pub extern "C" fn entrypoint(_input: *mut u8) -> u32 { pub extern "C" fn entrypoint(_input: *mut u8) -> u64 {
const ITERS: usize = 100; const ITERS: usize = 100;
let ones = [1_u64; ITERS]; let ones = [1_u64; ITERS];
let mut sum: u64 = 0; let mut sum: u64 = 0;

View File

@ -5,7 +5,7 @@ extern crate solana_sdk;
use solana_sdk::{entrypoint::SUCCESS, info}; use solana_sdk::{entrypoint::SUCCESS, info};
#[no_mangle] #[no_mangle]
pub extern "C" fn entrypoint(_input: *mut u8) -> u32 { pub extern "C" fn entrypoint(_input: *mut u8) -> u64 {
info!("Call same package"); info!("Call same package");
assert_eq!(crate::helper::many_args(1, 2, 3, 4, 5, 6, 7, 8, 9), 45); assert_eq!(crate::helper::many_args(1, 2, 3, 4, 5, 6, 7, 8, 9), 45);

View File

@ -3,6 +3,6 @@
extern crate solana_sdk; extern crate solana_sdk;
#[no_mangle] #[no_mangle]
pub extern "C" fn entrypoint(_input: *mut u8) -> u32 { pub extern "C" fn entrypoint(_input: *mut u8) -> u64 {
panic!(); panic!();
} }

View File

@ -5,7 +5,7 @@ use solana_bpf_rust_param_passing_dep::{Data, TestDep};
use solana_sdk::{entrypoint::SUCCESS, info}; use solana_sdk::{entrypoint::SUCCESS, info};
#[no_mangle] #[no_mangle]
pub extern "C" fn entrypoint(_input: *mut u8) -> u32 { pub extern "C" fn entrypoint(_input: *mut u8) -> u64 {
let array = [0xA, 0xB, 0xC, 0xD, 0xE, 0xF]; let array = [0xA, 0xB, 0xC, 0xD, 0xE, 0xF];
let data = Data { let data = Data {
twentyone: 21u64, twentyone: 21u64,

View File

@ -196,7 +196,14 @@ mod bpf {
let result = bank_client.send_instruction(&mint_keypair, instruction); let result = bank_client.send_instruction(&mint_keypair, instruction);
assert_eq!( assert_eq!(
result.unwrap_err().unwrap(), result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::ConflictingError(0)) TransactionError::InstructionError(0, InstructionError::InvalidError)
);
let instruction = Instruction::new(program_id, &5u8, account_metas.clone());
let result = bank_client.send_instruction(&mint_keypair, instruction);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::InvalidError)
); );
let instruction = Instruction::new(program_id, &6u8, account_metas.clone()); let instruction = Instruction::new(program_id, &6u8, account_metas.clone());
@ -391,24 +398,14 @@ mod bpf {
let result = bank_client.send_instruction(&mint_keypair, instruction); let result = bank_client.send_instruction(&mint_keypair, instruction);
assert_eq!( assert_eq!(
result.unwrap_err().unwrap(), result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::CustomError(42)) TransactionError::InstructionError(0, InstructionError::CustomError(0))
); );
let instruction = Instruction::new(program_id, &4u8, account_metas.clone()); let instruction = Instruction::new(program_id, &4u8, account_metas.clone());
let result = bank_client.send_instruction(&mint_keypair, instruction); let result = bank_client.send_instruction(&mint_keypair, instruction);
assert_eq!( assert_eq!(
result.unwrap_err().unwrap(), result.unwrap_err().unwrap(),
TransactionError::InstructionError(0, InstructionError::ConflictingError(0)) TransactionError::InstructionError(0, InstructionError::CustomError(42))
);
let instruction = Instruction::new(program_id, &5u8, account_metas.clone());
let result = bank_client.send_instruction(&mint_keypair, instruction);
assert_eq!(
result.unwrap_err().unwrap(),
TransactionError::InstructionError(
0,
InstructionError::ConflictingError(0x8000_002d)
)
); );
let instruction = Instruction::new(program_id, &6u8, account_metas.clone()); let instruction = Instruction::new(program_id, &6u8, account_metas.clone());

View File

@ -8,6 +8,7 @@ use log::*;
use solana_rbpf::{memory_region::MemoryRegion, EbpfVm}; use solana_rbpf::{memory_region::MemoryRegion, EbpfVm};
use solana_sdk::{ use solana_sdk::{
account::KeyedAccount, account::KeyedAccount,
entrypoint::SUCCESS,
instruction::InstructionError, instruction::InstructionError,
instruction_processor_utils::{is_executable, limited_deserialize, next_keyed_account}, instruction_processor_utils::{is_executable, limited_deserialize, next_keyed_account},
loader_instruction::LoaderInstruction, loader_instruction::LoaderInstruction,
@ -145,9 +146,7 @@ pub fn process_instruction(
info!("Call BPF program"); info!("Call BPF program");
match vm.execute_program(parameter_bytes.as_slice(), &[], &[heap_region]) { match vm.execute_program(parameter_bytes.as_slice(), &[], &[heap_region]) {
Ok(status) => { Ok(status) => {
// ignore upper 32bits if any, programs only return lower 32bits if status != SUCCESS {
let status = status as u32;
if status != 0 {
let error: InstructionError = status.into(); let error: InstructionError = status.into();
warn!("BPF program failed: {:?}", error); warn!("BPF program failed: {:?}", error);
return Err(error); return Err(error);

View File

@ -51,40 +51,40 @@ static_assert(sizeof(uint64_t) == 8);
*/ */
#define NULL 0 #define NULL 0
/** /** Indicates the instruction was processed successfully */
* SUCCESS return value
*/
#define SUCCESS 0 #define SUCCESS 0
/** /**
* Builtin program error return values have the 31st bit set. Programs * Builtin program status values occupy the upper 32 bits of the program return
* may define their own values but their 31st and 30th bit must be unset * value. Programs may define their own error values but they must be confined
* to avoid conflicting with the builtin errors * to the lower 32 bits.
*/ */
#define BUILTIN_ERROR_START 0x80000000 #define TO_BUILTIN(error) ((uint64_t)(error) << 32)
/** The arguments provided to a program instruction where invalid */
#define INVALID_ARGUMENT (BUILTIN_ERROR_START + 0)
/** An instruction's data contents was invalid */
#define INVALID_INSTRUCTION_DATA (BUILTIN_ERROR_START + 1)
/** An account's data contents was invalid */
#define INVALID_ACCOUNT_DATA (BUILTIN_ERROR_START + 2)
/** An account's data was too small */
#define ACCOUNT_DATA_TOO_SMALL (BUILTIN_ERROR_START + 3)
/** An account's balance was too small to complete the instruction */
#define INSUFFICIENT_FUNDS (BUILTIN_ERROR_START + 4)
/** The account did not have the expected program id */
#define INCORRECT_PROGRAM_ID (BUILTIN_ERROR_START + 5)
/** A signature was required but not found */
#define MISSING_REQUIRED_SIGNATURES (BUILTIN_ERROR_START + 6)
/** An initialize instruction was sent to an account that has already been initialized */
#define ACCOUNT_ALREADY_INITIALIZED (BUILTIN_ERROR_START + 7)
/** An attempt to operate on an account that hasn't been initialized */
#define UNINITIALIZED_ACCOUNT (BUILTIN_ERROR_START + 8)
/** The instruction expected additional account keys */
#define NOT_ENOUGH_ACCOUNT_KEYS (BUILTIN_ERROR_START + 9)
/** Note: Not applicable to program written in C */ /** Note: Not applicable to program written in C */
#define ACCOUNT_BORROW_FAILED (BUILTIN_ERROR_START + 10) #define ERROR_CUSTOM_ZERO TO_BUILTIN(1)
/** The arguments provided to a program instruction where invalid */
#define ERROR_INVALID_ARGUMENT TO_BUILTIN(2)
/** An instruction's data contents was invalid */
#define ERROR_INVALID_INSTRUCTION_DATA TO_BUILTIN(3)
/** An account's data contents was invalid */
#define ERROR_INVALID_ACCOUNT_DATA TO_BUILTIN(4)
/** An account's data was too small */
#define ERROR_ACCOUNT_DATA_TOO_SMALL TO_BUILTIN(5)
/** An account's balance was too small to complete the instruction */
#define ERROR_INSUFFICIENT_FUNDS TO_BUILTIN(6)
/** The account did not have the expected program id */
#define ERROR_INCORRECT_PROGRAM_ID TO_BUILTIN(7)
/** A signature was required but not found */
#define ERROR_MISSING_REQUIRED_SIGNATURES TO_BUILTIN(8)
/** An initialize instruction was sent to an account that has already been initialized */
#define ERROR_ACCOUNT_ALREADY_INITIALIZED TO_BUILTIN(9)
/** An attempt to operate on an account that hasn't been initialized */
#define ERROR_UNINITIALIZED_ACCOUNT TO_BUILTIN(10)
/** The instruction expected additional account keys */
#define ERROR_NOT_ENOUGH_ACCOUNT_KEYS TO_BUILTIN(11)
/** Note: Not applicable to program written in C */
#define ERROR_ACCOUNT_BORROW_FAILED TO_BUILTIN(12)
/** /**
* Boolean type * Boolean type
@ -367,7 +367,7 @@ SOL_FN_PREFIX void sol_log_params(const SolParameters *params) {
* @param input Buffer of serialized input parameters. Use sol_deserialize() to decode * @param input Buffer of serialized input parameters. Use sol_deserialize() to decode
* @return 0 if the instruction executed successfully * @return 0 if the instruction executed successfully
*/ */
uint32_t entrypoint(const uint8_t *input); uint64_t entrypoint(const uint8_t *input);
#ifdef SOL_TEST #ifdef SOL_TEST

View File

@ -22,12 +22,12 @@ pub type ProcessInstruction = fn(
) -> Result<(), ProgramError>; ) -> Result<(), ProgramError>;
/// Programs indicate success with a return value of 0 /// Programs indicate success with a return value of 0
pub const SUCCESS: u32 = 0; pub const SUCCESS: u64 = 0;
/// Declare the entry point of the program. /// Declare the entry point of the program.
/// ///
/// Deserialize the program input arguments and call /// Deserialize the program input arguments and call
/// the user defined `ProcessInstruction` function. /// the user defined `process_instruction` function.
/// Users must call this macro otherwise an entry point for /// Users must call this macro otherwise an entry point for
/// their program will not be created. /// their program will not be created.
#[macro_export] #[macro_export]
@ -35,7 +35,7 @@ macro_rules! entrypoint {
($process_instruction:ident) => { ($process_instruction:ident) => {
/// # Safety /// # Safety
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u32 { pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
let (program_id, accounts, instruction_data) = let (program_id, accounts, instruction_data) =
unsafe { $crate::entrypoint::deserialize(input) }; unsafe { $crate::entrypoint::deserialize(input) };
match $process_instruction(&program_id, &accounts, &instruction_data) { match $process_instruction(&program_id, &accounts, &instruction_data) {

View File

@ -86,18 +86,14 @@ pub enum InstructionError {
/// the runtime cannot determine which changes to pick or how to merge them if both are modified /// the runtime cannot determine which changes to pick or how to merge them if both are modified
DuplicateAccountOutOfSync, DuplicateAccountOutOfSync,
/// CustomError allows on-chain programs to implement program-specific error types and see /// The return value from the program was invalid. Valid errors are either a defined builtin
/// them returned by the Solana runtime. A CustomError may be any type that is represented /// error value or a user-defined error in the lower 32 bits.
/// as or serialized to a u32 integer. InvalidError,
///
/// NOTE: u64 requires special serialization to avoid the loss of precision in JS clients and
/// so is not used for now.
CustomError(u32),
/// Like CustomError but the return value from the program conflicted with /// Allows on-chain programs to implement program-specific error types and see them returned
/// a builtin error. The value held by this variant is the u32 error code /// by the Solana runtime. A program-specific error may be any type that is represented as
/// returned by the program but with the 30th bit cleared. /// or serialized to a u32 integer.
ConflictingError(u32), CustomError(u32),
} }
impl InstructionError { impl InstructionError {

View File

@ -3,12 +3,9 @@ use num_traits::ToPrimitive;
/// Reasons the program may fail /// Reasons the program may fail
pub enum ProgramError { pub enum ProgramError {
/// CustomError allows programs to implement program-specific error types and see /// Allows on-chain programs to implement program-specific error types and see them returned
/// them returned by the Solana runtime. A CustomError may be any type that is represented /// by the Solana runtime. A program-specific error may be any type that is represented as
/// as or serialized to a u32 integer. /// 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.
CustomError(u32), CustomError(u32),
/// The arguments provided to a program instruction where invalid /// The arguments provided to a program instruction where invalid
InvalidArgument, InvalidArgument,
@ -34,47 +31,28 @@ pub enum ProgramError {
AccountBorrowFailed, AccountBorrowFailed,
} }
/// 32bit representations of builtin program errors returned by the entry point /// Builtin return values occupy the upper 32 bits
const BUILTIN_ERROR_START: u32 = 0x8000_0000; // 31st bit set const BUILTIN_BIT_SHIFT: usize = 32;
const INVALID_ARGUMENT: u32 = BUILTIN_ERROR_START; macro_rules! to_builtin {
const INVALID_INSTRUCTION_DATA: u32 = BUILTIN_ERROR_START + 1; ($error:expr) => {
const INVALID_ACCOUNT_DATA: u32 = BUILTIN_ERROR_START + 2; ($error as u64) << BUILTIN_BIT_SHIFT
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
} }
/// If a program defined error conflicts with a builtin error const CUSTOM_ZERO: u64 = to_builtin!(1);
/// its 30th bit is set before returning to distinguish it. const INVALID_ARGUMENT: u64 = to_builtin!(2);
/// The side effect is that the original error's 30th bit const INVALID_INSTRUCTION_DATA: u64 = to_builtin!(3);
/// value is lost, be aware. const INVALID_ACCOUNT_DATA: u64 = to_builtin!(4);
const CONFLICTING_ERROR_MARK: u32 = 0x4000_0000; // 30st bit set 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?) impl From<ProgramError> for u64 {
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 {
fn from(error: ProgramError) -> Self { fn from(error: ProgramError) -> Self {
match error { match error {
ProgramError::InvalidArgument => INVALID_ARGUMENT, ProgramError::InvalidArgument => INVALID_ARGUMENT,
@ -89,10 +67,10 @@ impl From<ProgramError> for u32 {
ProgramError::NotEnoughAccountKeys => NOT_ENOUGH_ACCOUNT_KEYS, ProgramError::NotEnoughAccountKeys => NOT_ENOUGH_ACCOUNT_KEYS,
ProgramError::AccountBorrowFailed => ACCOUNT_BORROW_FAILED, ProgramError::AccountBorrowFailed => ACCOUNT_BORROW_FAILED,
ProgramError::CustomError(error) => { ProgramError::CustomError(error) => {
if error == 0 || is_builtin(error) { if error == 0 {
mark_conflicting(error) CUSTOM_ZERO
} else { } else {
error error as u64
} }
} }
} }
@ -104,8 +82,9 @@ where
T: ToPrimitive, T: ToPrimitive,
{ {
fn from(error: T) -> Self { 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 { match error {
CUSTOM_ZERO => InstructionError::CustomError(0),
INVALID_ARGUMENT => InstructionError::InvalidArgument, INVALID_ARGUMENT => InstructionError::InvalidArgument,
INVALID_INSTRUCTION_DATA => InstructionError::InvalidInstructionData, INVALID_INSTRUCTION_DATA => InstructionError::InvalidInstructionData,
INVALID_ACCOUNT_DATA => InstructionError::InvalidAccountData, INVALID_ACCOUNT_DATA => InstructionError::InvalidAccountData,
@ -118,10 +97,11 @@ where
NOT_ENOUGH_ACCOUNT_KEYS => InstructionError::NotEnoughAccountKeys, NOT_ENOUGH_ACCOUNT_KEYS => InstructionError::NotEnoughAccountKeys,
ACCOUNT_BORROW_FAILED => InstructionError::AccountBorrowFailed, ACCOUNT_BORROW_FAILED => InstructionError::AccountBorrowFailed,
_ => { _ => {
if is_marked_conflicting(error) { // A valid custom error has no bits set in the upper 32
InstructionError::ConflictingError(unmark_conflicting(error)) if error >> BUILTIN_BIT_SHIFT == 0 {
InstructionError::CustomError(error as u32)
} else { } else {
InstructionError::CustomError(error) InstructionError::InvalidError
} }
} }
} }