Add support for idiomatic error handling to BPF instruction processors (#7968)
This commit is contained in:
@ -1,9 +1,7 @@
|
||||
//! @brief Solana Rust-based BPF program entry point and its parameter types
|
||||
|
||||
#![cfg(feature = "program")]
|
||||
|
||||
extern crate alloc;
|
||||
use crate::{account_info::AccountInfo, pubkey::Pubkey};
|
||||
use crate::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey};
|
||||
use alloc::vec::Vec;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
@ -17,8 +15,11 @@ use std::{
|
||||
/// program_id: Program ID of the currently executing program
|
||||
/// accounts: Accounts passed as part of the instruction
|
||||
/// instruction_data: Instruction data
|
||||
pub type ProcessInstruction =
|
||||
fn(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> u32;
|
||||
pub type ProcessInstruction = fn(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
instruction_data: &[u8],
|
||||
) -> Result<(), ProgramError>;
|
||||
|
||||
/// Programs indicate success with a return value of 0
|
||||
pub const SUCCESS: u32 = 0;
|
||||
@ -35,10 +36,11 @@ macro_rules! entrypoint {
|
||||
/// # Safety
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u32 {
|
||||
unsafe {
|
||||
let (program_id, accounts, instruction_data) =
|
||||
$crate::entrypoint::deserialize(input);
|
||||
$process_instruction(&program_id, &accounts, &instruction_data)
|
||||
let (program_id, accounts, instruction_data) =
|
||||
unsafe { $crate::entrypoint::deserialize(input) };
|
||||
match $process_instruction(&program_id, &accounts, &instruction_data) {
|
||||
Ok(()) => $crate::entrypoint::SUCCESS,
|
||||
Err(error) => error.into(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -93,6 +93,11 @@ pub enum InstructionError {
|
||||
/// 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
|
||||
/// a builtin error. The value held by this variant is the u32 error code
|
||||
/// returned by the program but with the 30th bit cleared.
|
||||
ConflictingError(u32),
|
||||
}
|
||||
|
||||
impl InstructionError {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{account::KeyedAccount, instruction::InstructionError, pubkey::Pubkey};
|
||||
use num_traits::{FromPrimitive, ToPrimitive};
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
// Native program ENTRYPOINT prototype
|
||||
// Prototype of a native program entry point
|
||||
pub type Entrypoint = unsafe extern "C" fn(
|
||||
program_id: &Pubkey,
|
||||
keyed_accounts: &[KeyedAccount],
|
||||
@ -99,15 +99,6 @@ macro_rules! declare_program(
|
||||
)
|
||||
);
|
||||
|
||||
impl<T> From<T> for InstructionError
|
||||
where
|
||||
T: ToPrimitive,
|
||||
{
|
||||
fn from(error: T) -> Self {
|
||||
InstructionError::CustomError(error.to_u32().unwrap_or(0xbad_c0de))
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the next KeyedAccount or a NotEnoughAccountKeys instruction error
|
||||
pub fn next_keyed_account<I: Iterator>(iter: &mut I) -> Result<I::Item, InstructionError> {
|
||||
iter.next().ok_or(InstructionError::NotEnoughAccountKeys)
|
||||
|
@ -59,6 +59,7 @@ pub use solana_sdk_macro::declare_id;
|
||||
pub mod account_info;
|
||||
pub mod entrypoint;
|
||||
pub mod log;
|
||||
pub mod program_error;
|
||||
|
||||
// Modules not usable by on-chain programs
|
||||
#[cfg(not(feature = "program"))]
|
||||
|
129
sdk/src/program_error.rs
Normal file
129
sdk/src/program_error.rs
Normal file
@ -0,0 +1,129 @@
|
||||
use crate::instruction::InstructionError;
|
||||
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.
|
||||
CustomError(u32),
|
||||
/// The arguments provided to a program instruction where invalid
|
||||
InvalidArgument,
|
||||
/// An instruction's data contents was invalid
|
||||
InvalidInstructionData,
|
||||
/// An account's data contents was invalid
|
||||
InvalidAccountData,
|
||||
/// An account's data was too small
|
||||
AccountDataTooSmall,
|
||||
/// An account's balance was too small to complete the instruction
|
||||
InsufficientFunds,
|
||||
/// The account did not have the expected program id
|
||||
IncorrectProgramId,
|
||||
/// A signature was required but not found
|
||||
MissingRequiredSignature,
|
||||
/// An initialize instruction was sent to an account that has already been initialized.
|
||||
AccountAlreadyInitialized,
|
||||
/// An attempt to operate on an account that hasn't been initialized.
|
||||
UninitializedAccount,
|
||||
/// The instruction expected additional account keys
|
||||
NotEnoughAccountKeys,
|
||||
/// Failed to borrow a reference to an account, already borrowed
|
||||
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
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
||||
/// 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 {
|
||||
fn from(error: ProgramError) -> Self {
|
||||
match error {
|
||||
ProgramError::InvalidArgument => INVALID_ARGUMENT,
|
||||
ProgramError::InvalidInstructionData => INVALID_INSTRUCTION_DATA,
|
||||
ProgramError::InvalidAccountData => INVALID_ACCOUNT_DATA,
|
||||
ProgramError::AccountDataTooSmall => ACCOUNT_DATA_TOO_SMALL,
|
||||
ProgramError::InsufficientFunds => INSUFFICIENT_FUNDS,
|
||||
ProgramError::IncorrectProgramId => INCORRECT_PROGRAM_ID,
|
||||
ProgramError::MissingRequiredSignature => MISSING_REQUIRED_SIGNATURES,
|
||||
ProgramError::AccountAlreadyInitialized => ACCOUNT_ALREADY_INITIALIZED,
|
||||
ProgramError::UninitializedAccount => UNINITIALIZED_ACCOUNT,
|
||||
ProgramError::NotEnoughAccountKeys => NOT_ENOUGH_ACCOUNT_KEYS,
|
||||
ProgramError::AccountBorrowFailed => ACCOUNT_BORROW_FAILED,
|
||||
ProgramError::CustomError(error) => {
|
||||
if error == 0 || is_builtin(error) {
|
||||
mark_conflicting(error)
|
||||
} else {
|
||||
error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for InstructionError
|
||||
where
|
||||
T: ToPrimitive,
|
||||
{
|
||||
fn from(error: T) -> Self {
|
||||
let error = error.to_u32().unwrap_or(0xbad_c0de);
|
||||
match error {
|
||||
INVALID_ARGUMENT => InstructionError::InvalidArgument,
|
||||
INVALID_INSTRUCTION_DATA => InstructionError::InvalidInstructionData,
|
||||
INVALID_ACCOUNT_DATA => InstructionError::InvalidAccountData,
|
||||
ACCOUNT_DATA_TOO_SMALL => InstructionError::AccountDataTooSmall,
|
||||
INSUFFICIENT_FUNDS => InstructionError::InsufficientFunds,
|
||||
INCORRECT_PROGRAM_ID => InstructionError::IncorrectProgramId,
|
||||
MISSING_REQUIRED_SIGNATURES => InstructionError::MissingRequiredSignature,
|
||||
ACCOUNT_ALREADY_INITIALIZED => InstructionError::AccountAlreadyInitialized,
|
||||
UNINITIALIZED_ACCOUNT => InstructionError::UninitializedAccount,
|
||||
NOT_ENOUGH_ACCOUNT_KEYS => InstructionError::NotEnoughAccountKeys,
|
||||
ACCOUNT_BORROW_FAILED => InstructionError::AccountBorrowFailed,
|
||||
_ => {
|
||||
if is_marked_conflicting(error) {
|
||||
InstructionError::ConflictingError(unmark_conflicting(error))
|
||||
} else {
|
||||
InstructionError::CustomError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user