From 0fd795a67657b57d3a51971d6afa02a5098616a2 Mon Sep 17 00:00:00 2001 From: Jack May Date: Fri, 31 Jan 2020 10:58:07 -0800 Subject: [PATCH] Remove program error footgun and cleaner developer experience (#8042) --- programs/bpf/c/src/bench_alu/bench_alu.c | 2 +- programs/bpf/c/src/bpf_to_bpf/entrypoint.c | 2 +- .../bpf/c/src/dup_accounts/dup_accounts.c | 6 +- .../bpf/c/src/error_handling/error_handling.c | 27 +++--- programs/bpf/c/src/move_funds/move_funds.c | 6 +- .../c/src/multiple_static/multiple_static.c | 2 +- programs/bpf/c/src/noop++/noop++.cc | 4 +- programs/bpf/c/src/noop/noop.c | 4 +- programs/bpf/c/src/panic/panic.c | 2 +- .../bpf/c/src/relative_call/relative_call.c | 2 +- programs/bpf/c/src/struct_pass/struct_pass.c | 2 +- programs/bpf/c/src/struct_ret/struct_ret.c | 2 +- programs/bpf/rust/128bit/src/lib.rs | 2 +- programs/bpf/rust/alloc/src/lib.rs | 2 +- programs/bpf/rust/dep_crate/src/lib.rs | 2 +- programs/bpf/rust/error_handling/src/lib.rs | 20 ++--- programs/bpf/rust/iter/src/lib.rs | 2 +- programs/bpf/rust/many_args/src/lib.rs | 2 +- programs/bpf/rust/panic/src/lib.rs | 2 +- programs/bpf/rust/param_passing/src/lib.rs | 2 +- programs/bpf/tests/programs.rs | 23 +++--- programs/bpf_loader/src/lib.rs | 5 +- sdk/bpf/c/inc/solana_sdk.h | 58 ++++++------- sdk/src/entrypoint.rs | 6 +- sdk/src/instruction.rs | 18 ++-- sdk/src/program_error.rs | 82 +++++++------------ 26 files changed, 127 insertions(+), 160 deletions(-) diff --git a/programs/bpf/c/src/bench_alu/bench_alu.c b/programs/bpf/c/src/bench_alu/bench_alu.c index 870c67f2eb..722223f7fb 100644 --- a/programs/bpf/c/src/bench_alu/bench_alu.c +++ b/programs/bpf/c/src/bench_alu/bench_alu.c @@ -6,7 +6,7 @@ #include -extern uint32_t entrypoint(const uint8_t *input) { +extern uint64_t entrypoint(const uint8_t *input) { uint64_t x = *(uint64_t *) input; uint64_t *result = (uint64_t *) input + 1; uint64_t count = 0; diff --git a/programs/bpf/c/src/bpf_to_bpf/entrypoint.c b/programs/bpf/c/src/bpf_to_bpf/entrypoint.c index 3e55cc137b..4fd6de8bf5 100644 --- a/programs/bpf/c/src/bpf_to_bpf/entrypoint.c +++ b/programs/bpf/c/src/bpf_to_bpf/entrypoint.c @@ -6,7 +6,7 @@ #include "helper.h" -extern uint32_t entrypoint(const uint8_t *input) { +extern uint64_t entrypoint(const uint8_t *input) { sol_log(__FILE__); helper_function(); sol_log(__FILE__); diff --git a/programs/bpf/c/src/dup_accounts/dup_accounts.c b/programs/bpf/c/src/dup_accounts/dup_accounts.c index 4fabcb4866..8a70e29a42 100644 --- a/programs/bpf/c/src/dup_accounts/dup_accounts.c +++ b/programs/bpf/c/src/dup_accounts/dup_accounts.c @@ -8,12 +8,12 @@ * 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]; SolParameters params = (SolParameters) { .ka = ka }; if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(ka))) { - return INVALID_ARGUMENT; + return ERROR_INVALID_ARGUMENT; } switch (params.data[0]) { @@ -48,7 +48,7 @@ extern uint32_t entrypoint(const uint8_t *input) { break; default: sol_log("Unrecognized command"); - return INVALID_INSTRUCTION_DATA; + return ERROR_INVALID_INSTRUCTION_DATA; } return SUCCESS; } diff --git a/programs/bpf/c/src/error_handling/error_handling.c b/programs/bpf/c/src/error_handling/error_handling.c index ce131eda88..cd611bed6a 100644 --- a/programs/bpf/c/src/error_handling/error_handling.c +++ b/programs/bpf/c/src/error_handling/error_handling.c @@ -8,30 +8,33 @@ * 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]; SolParameters params = (SolParameters) { .ka = ka }; if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(ka))) { - return INVALID_ARGUMENT; + return ERROR_INVALID_ARGUMENT; } switch (params.data[0]) { case(1): - sol_log("return success"); - return SUCCESS; + sol_log("return success"); + return SUCCESS; case(2): - sol_log("return a builtin"); - return INVALID_ACCOUNT_DATA; + sol_log("return a builtin"); + return ERROR_INVALID_ACCOUNT_DATA; case(3): - sol_log("return custom error"); - return 42; + sol_log("return custom error 42"); + return 42; case(4): - sol_log("return error that conflicts with success"); - return 0x40000000; + sol_log("return an invalid error"); + return ERROR_INVALID_ACCOUNT_DATA + 1; + case(5): + sol_log("return unknown builtin"); + return TO_BUILTIN(50); default: - sol_log("Unrecognized command"); - return INVALID_INSTRUCTION_DATA; + sol_log("Unrecognized command"); + return ERROR_INVALID_INSTRUCTION_DATA; } return SUCCESS; } diff --git a/programs/bpf/c/src/move_funds/move_funds.c b/programs/bpf/c/src/move_funds/move_funds.c index d38325ba03..6b5a8c1699 100644 --- a/programs/bpf/c/src/move_funds/move_funds.c +++ b/programs/bpf/c/src/move_funds/move_funds.c @@ -11,17 +11,17 @@ */ #define NUM_KA 3 -extern uint32_t entrypoint(const uint8_t *input) { +extern uint64_t entrypoint(const uint8_t *input) { SolKeyedAccount ka[NUM_KA]; SolParameters params = (SolParameters) { .ka = ka }; if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(ka))) { - return INVALID_ARGUMENT; + return ERROR_INVALID_ARGUMENT; } if (!params.ka[0].is_signer) { sol_log("Transaction not signed by key 0"); - return MISSING_REQUIRED_SIGNATURES; + return ERROR_MISSING_REQUIRED_SIGNATURES; } int64_t lamports = *(int64_t *)params.data; diff --git a/programs/bpf/c/src/multiple_static/multiple_static.c b/programs/bpf/c/src/multiple_static/multiple_static.c index f838f2b3ac..c9793ac4f8 100644 --- a/programs/bpf/c/src/multiple_static/multiple_static.c +++ b/programs/bpf/c/src/multiple_static/multiple_static.c @@ -3,7 +3,7 @@ static const char msg[] = "This is a 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*)msg2); return SUCCESS; diff --git a/programs/bpf/c/src/noop++/noop++.cc b/programs/bpf/c/src/noop++/noop++.cc index 2ad78b4e49..8003cfdea0 100644 --- a/programs/bpf/c/src/noop++/noop++.cc +++ b/programs/bpf/c/src/noop++/noop++.cc @@ -9,14 +9,14 @@ */ #define INVALID_INPUT 1 -extern uint32_t entrypoint(const uint8_t *input) { +extern uint64_t entrypoint(const uint8_t *input) { SolKeyedAccount ka[1]; SolParameters params = (SolParameters) { .ka = ka }; sol_log(__FILE__); if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(ka))) { - return INVALID_ARGUMENT; + return ERROR_INVALID_ARGUMENT; } // Log the provided input parameters. In the case of the no-op diff --git a/programs/bpf/c/src/noop/noop.c b/programs/bpf/c/src/noop/noop.c index 31985ed4bc..e2950b273b 100644 --- a/programs/bpf/c/src/noop/noop.c +++ b/programs/bpf/c/src/noop/noop.c @@ -4,14 +4,14 @@ */ #include -extern uint32_t entrypoint(const uint8_t *input) { +extern uint64_t entrypoint(const uint8_t *input) { SolKeyedAccount ka[1]; SolParameters params = (SolParameters) { .ka = ka }; sol_log(__FILE__); if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(ka))) { - return INVALID_ARGUMENT; + return ERROR_INVALID_ARGUMENT; } // Log the provided input parameters. In the case of the no-op diff --git a/programs/bpf/c/src/panic/panic.c b/programs/bpf/c/src/panic/panic.c index 28355737a4..4d486ac4fd 100644 --- a/programs/bpf/c/src/panic/panic.c +++ b/programs/bpf/c/src/panic/panic.c @@ -4,7 +4,7 @@ */ #include -extern uint32_t entrypoint(const uint8_t *input) { +extern uint64_t entrypoint(const uint8_t *input) { sol_panic(); return SUCCESS; } diff --git a/programs/bpf/c/src/relative_call/relative_call.c b/programs/bpf/c/src/relative_call/relative_call.c index 7200987b6c..251281b245 100644 --- a/programs/bpf/c/src/relative_call/relative_call.c +++ b/programs/bpf/c/src/relative_call/relative_call.c @@ -8,7 +8,7 @@ void __attribute__ ((noinline)) helper() { sol_log(__func__); } -extern uint32_t entrypoint(const uint8_t *input) { +extern uint64_t entrypoint(const uint8_t *input) { sol_log(__func__); helper(); return SUCCESS; diff --git a/programs/bpf/c/src/struct_pass/struct_pass.c b/programs/bpf/c/src/struct_pass/struct_pass.c index 165df0bbaf..32ffc5d693 100644 --- a/programs/bpf/c/src/struct_pass/struct_pass.c +++ b/programs/bpf/c/src/struct_pass/struct_pass.c @@ -3,7 +3,7 @@ struct foo {const uint8_t *input;}; 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; f.input = input; foo(input, f); diff --git a/programs/bpf/c/src/struct_ret/struct_ret.c b/programs/bpf/c/src/struct_ret/struct_ret.c index 62ff212f0e..911f348cb8 100644 --- a/programs/bpf/c/src/struct_ret/struct_ret.c +++ b/programs/bpf/c/src/struct_ret/struct_ret.c @@ -15,7 +15,7 @@ static struct test_struct __attribute__ ((noinline)) test_function(void) { return s; } -extern uint32_t entrypoint(const uint8_t* input) { +extern uint64_t entrypoint(const uint8_t* input) { struct test_struct s = test_function(); sol_log("foobar"); if (s.x + s.y + s.z == 12 ) { diff --git a/programs/bpf/rust/128bit/src/lib.rs b/programs/bpf/rust/128bit/src/lib.rs index 4f380af222..efd0991311 100644 --- a/programs/bpf/rust/128bit/src/lib.rs +++ b/programs/bpf/rust/128bit/src/lib.rs @@ -4,7 +4,7 @@ extern crate solana_sdk; use solana_sdk::entrypoint::SUCCESS; #[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 y = x.rotate_right(1); assert_eq!(y, 170_141_183_460_469_231_731_687_303_715_884_105_728); diff --git a/programs/bpf/rust/alloc/src/lib.rs b/programs/bpf/rust/alloc/src/lib.rs index 4d01f35e22..59e647c289 100644 --- a/programs/bpf/rust/alloc/src/lib.rs +++ b/programs/bpf/rust/alloc/src/lib.rs @@ -7,7 +7,7 @@ use solana_sdk::{entrypoint::SUCCESS, info}; use std::{alloc::Layout, mem}; #[no_mangle] -pub extern "C" fn entrypoint(_input: *mut u8) -> u32 { +pub extern "C" fn entrypoint(_input: *mut u8) -> u64 { unsafe { // Confirm large allocation fails diff --git a/programs/bpf/rust/dep_crate/src/lib.rs b/programs/bpf/rust/dep_crate/src/lib.rs index 101accf7da..d37e749d3d 100644 --- a/programs/bpf/rust/dep_crate/src/lib.rs +++ b/programs/bpf/rust/dep_crate/src/lib.rs @@ -5,7 +5,7 @@ use byteorder::{ByteOrder, LittleEndian}; use solana_sdk::entrypoint::SUCCESS; #[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]; LittleEndian::write_u32(&mut buf, 1_000_000); assert_eq!(1_000_000, LittleEndian::read_u32(&buf)); diff --git a/programs/bpf/rust/error_handling/src/lib.rs b/programs/bpf/rust/error_handling/src/lib.rs index 7b7cdf481a..516ecc9c71 100644 --- a/programs/bpf/rust/error_handling/src/lib.rs +++ b/programs/bpf/rust/error_handling/src/lib.rs @@ -9,15 +9,11 @@ use thiserror::Error; /// Custom program errors #[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 { + #[error("Default enum start")] + DefaultEnumStart, #[error("The Answer")] TheAnswer = 42, - #[error("Conflicting with success")] - ConflictingSuccess = 0, - #[error("Conflicting with builtin")] - ConflictingBuiltin = 0x8000_002d, } impl From for ProgramError { fn from(e: MyError) -> Self { @@ -41,16 +37,12 @@ fn process_instruction( Err(ProgramError::InvalidAccountData) } 3 => { - info!("return custom error"); - Err(MyError::TheAnswer.into()) + info!("return default enum start value"); + Err(MyError::DefaultEnumStart.into()) } 4 => { - info!("return error that conflicts with success"); - Err(MyError::ConflictingSuccess.into()) - } - 5 => { - info!("return error that conflicts with builtin"); - Err(MyError::ConflictingBuiltin.into()) + info!("return custom error"); + Err(MyError::TheAnswer.into()) } 6 => { let data = accounts[0].try_borrow_mut_data()?; diff --git a/programs/bpf/rust/iter/src/lib.rs b/programs/bpf/rust/iter/src/lib.rs index f2961a71a9..c45639a174 100644 --- a/programs/bpf/rust/iter/src/lib.rs +++ b/programs/bpf/rust/iter/src/lib.rs @@ -4,7 +4,7 @@ extern crate solana_sdk; use solana_sdk::{entrypoint::SUCCESS, info}; #[no_mangle] -pub extern "C" fn entrypoint(_input: *mut u8) -> u32 { +pub extern "C" fn entrypoint(_input: *mut u8) -> u64 { const ITERS: usize = 100; let ones = [1_u64; ITERS]; let mut sum: u64 = 0; diff --git a/programs/bpf/rust/many_args/src/lib.rs b/programs/bpf/rust/many_args/src/lib.rs index 0c797dedaa..6ea4388177 100644 --- a/programs/bpf/rust/many_args/src/lib.rs +++ b/programs/bpf/rust/many_args/src/lib.rs @@ -5,7 +5,7 @@ extern crate solana_sdk; use solana_sdk::{entrypoint::SUCCESS, info}; #[no_mangle] -pub extern "C" fn entrypoint(_input: *mut u8) -> u32 { +pub extern "C" fn entrypoint(_input: *mut u8) -> u64 { info!("Call same package"); assert_eq!(crate::helper::many_args(1, 2, 3, 4, 5, 6, 7, 8, 9), 45); diff --git a/programs/bpf/rust/panic/src/lib.rs b/programs/bpf/rust/panic/src/lib.rs index 4b5463591b..f520e48432 100644 --- a/programs/bpf/rust/panic/src/lib.rs +++ b/programs/bpf/rust/panic/src/lib.rs @@ -3,6 +3,6 @@ extern crate solana_sdk; #[no_mangle] -pub extern "C" fn entrypoint(_input: *mut u8) -> u32 { +pub extern "C" fn entrypoint(_input: *mut u8) -> u64 { panic!(); } diff --git a/programs/bpf/rust/param_passing/src/lib.rs b/programs/bpf/rust/param_passing/src/lib.rs index 7ddf93aaa3..f0f28a65d6 100644 --- a/programs/bpf/rust/param_passing/src/lib.rs +++ b/programs/bpf/rust/param_passing/src/lib.rs @@ -5,7 +5,7 @@ use solana_bpf_rust_param_passing_dep::{Data, TestDep}; use solana_sdk::{entrypoint::SUCCESS, info}; #[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 data = Data { twentyone: 21u64, diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index 3cb6623f49..3ea22119a1 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -196,7 +196,14 @@ mod bpf { let result = bank_client.send_instruction(&mint_keypair, instruction); assert_eq!( 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()); @@ -391,24 +398,14 @@ mod bpf { let result = bank_client.send_instruction(&mint_keypair, instruction); assert_eq!( 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 result = bank_client.send_instruction(&mint_keypair, instruction); assert_eq!( result.unwrap_err().unwrap(), - TransactionError::InstructionError(0, InstructionError::ConflictingError(0)) - ); - - 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) - ) + TransactionError::InstructionError(0, InstructionError::CustomError(42)) ); let instruction = Instruction::new(program_id, &6u8, account_metas.clone()); diff --git a/programs/bpf_loader/src/lib.rs b/programs/bpf_loader/src/lib.rs index 1196b08ec7..68a30df554 100644 --- a/programs/bpf_loader/src/lib.rs +++ b/programs/bpf_loader/src/lib.rs @@ -8,6 +8,7 @@ use log::*; use solana_rbpf::{memory_region::MemoryRegion, EbpfVm}; use solana_sdk::{ account::KeyedAccount, + entrypoint::SUCCESS, instruction::InstructionError, instruction_processor_utils::{is_executable, limited_deserialize, next_keyed_account}, loader_instruction::LoaderInstruction, @@ -145,9 +146,7 @@ pub fn process_instruction( info!("Call BPF program"); match vm.execute_program(parameter_bytes.as_slice(), &[], &[heap_region]) { Ok(status) => { - // ignore upper 32bits if any, programs only return lower 32bits - let status = status as u32; - if status != 0 { + if status != SUCCESS { let error: InstructionError = status.into(); warn!("BPF program failed: {:?}", error); return Err(error); diff --git a/sdk/bpf/c/inc/solana_sdk.h b/sdk/bpf/c/inc/solana_sdk.h index f4851a6d26..ceb3ca67c7 100644 --- a/sdk/bpf/c/inc/solana_sdk.h +++ b/sdk/bpf/c/inc/solana_sdk.h @@ -51,40 +51,40 @@ static_assert(sizeof(uint64_t) == 8); */ #define NULL 0 -/** - * SUCCESS return value - */ +/** Indicates the instruction was processed successfully */ #define SUCCESS 0 /** - * Builtin program error return values have the 31st bit set. Programs - * may define their own values but their 31st and 30th bit must be unset - * to avoid conflicting with the builtin errors + * Builtin program status values occupy the upper 32 bits of the program return + * value. Programs may define their own error values but they must be confined + * 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 */ -#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 @@ -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 * @return 0 if the instruction executed successfully */ -uint32_t entrypoint(const uint8_t *input); +uint64_t entrypoint(const uint8_t *input); #ifdef SOL_TEST diff --git a/sdk/src/entrypoint.rs b/sdk/src/entrypoint.rs index 4b0962cc3f..b8ca0c1eda 100644 --- a/sdk/src/entrypoint.rs +++ b/sdk/src/entrypoint.rs @@ -22,12 +22,12 @@ pub type ProcessInstruction = fn( ) -> Result<(), ProgramError>; /// 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. /// /// 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 /// their program will not be created. #[macro_export] @@ -35,7 +35,7 @@ macro_rules! entrypoint { ($process_instruction:ident) => { /// # Safety #[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) = unsafe { $crate::entrypoint::deserialize(input) }; match $process_instruction(&program_id, &accounts, &instruction_data) { diff --git a/sdk/src/instruction.rs b/sdk/src/instruction.rs index efee534cd1..cb95c1f451 100644 --- a/sdk/src/instruction.rs +++ b/sdk/src/instruction.rs @@ -86,18 +86,14 @@ pub enum InstructionError { /// the runtime cannot determine which changes to pick or how to merge them if both are modified DuplicateAccountOutOfSync, - /// CustomError allows on-chain 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 return value from the program was invalid. Valid errors are either a defined builtin + /// error value or a user-defined error in the lower 32 bits. + InvalidError, - /// 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), + /// 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), } impl InstructionError { diff --git a/sdk/src/program_error.rs b/sdk/src/program_error.rs index e9563d9d36..9891c22960 100644 --- a/sdk/src/program_error.rs +++ b/sdk/src/program_error.rs @@ -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 for u32 { +impl From for u64 { fn from(error: ProgramError) -> Self { match error { ProgramError::InvalidArgument => INVALID_ARGUMENT, @@ -89,10 +67,10 @@ impl From 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 } } }