Add support for idiomatic error handling to BPF instruction processors (#7968)
This commit is contained in:
22
programs/bpf/Cargo.lock
generated
22
programs/bpf/Cargo.lock
generated
@ -1198,6 +1198,16 @@ dependencies = [
|
|||||||
"version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-derive"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-derive"
|
name = "num-derive"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@ -1995,6 +2005,17 @@ dependencies = [
|
|||||||
"solana-sdk-bpf-test 0.24.0",
|
"solana-sdk-bpf-test 0.24.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "solana-bpf-rust-error-handling"
|
||||||
|
version = "0.24.0"
|
||||||
|
dependencies = [
|
||||||
|
"num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"solana-sdk 0.24.0",
|
||||||
|
"solana-sdk-bpf-test 0.24.0",
|
||||||
|
"thiserror 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "solana-bpf-rust-external-spend"
|
name = "solana-bpf-rust-external-spend"
|
||||||
version = "0.24.0"
|
version = "0.24.0"
|
||||||
@ -3156,6 +3177,7 @@ dependencies = [
|
|||||||
"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
|
"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
|
||||||
"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
|
"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
|
||||||
"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
|
"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
|
||||||
|
"checksum num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "eafd0b45c5537c3ba526f79d3e75120036502bebacbb3f3220914067ce39dbf2"
|
||||||
"checksum num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0c8b15b261814f992e33760b1fca9fe8b693d8a65299f20c9901688636cfb746"
|
"checksum num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0c8b15b261814f992e33760b1fca9fe8b693d8a65299f20c9901688636cfb746"
|
||||||
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
|
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
|
||||||
"checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
|
"checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
|
||||||
|
@ -38,6 +38,7 @@ members = [
|
|||||||
"rust/alloc",
|
"rust/alloc",
|
||||||
"rust/dep_crate",
|
"rust/dep_crate",
|
||||||
"rust/dup_accounts",
|
"rust/dup_accounts",
|
||||||
|
"rust/error_handling",
|
||||||
"rust/external_spend",
|
"rust/external_spend",
|
||||||
"rust/iter",
|
"rust/iter",
|
||||||
"rust/many_args",
|
"rust/many_args",
|
||||||
|
@ -69,9 +69,10 @@ fn main() {
|
|||||||
"alloc",
|
"alloc",
|
||||||
"dep_crate",
|
"dep_crate",
|
||||||
"dup_accounts",
|
"dup_accounts",
|
||||||
|
"error_handling",
|
||||||
|
"external_spend",
|
||||||
"iter",
|
"iter",
|
||||||
"many_args",
|
"many_args",
|
||||||
"external_spend",
|
|
||||||
"noop",
|
"noop",
|
||||||
"panic",
|
"panic",
|
||||||
"param_passing",
|
"param_passing",
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
Test(bench_alu, sanity) {
|
Test(bench_alu, sanity) {
|
||||||
uint64_t input[] = {500, 0};
|
uint64_t input[] = {500, 0};
|
||||||
|
|
||||||
cr_assert_eq(entrypoint((uint8_t *) input), 0);
|
cr_assert_eq(entrypoint((uint8_t *) input), SUCCESS);
|
||||||
|
|
||||||
cr_assert_eq(input[0], 500);
|
cr_assert_eq(input[0], 500);
|
||||||
cr_assert_eq(input[1], 5);
|
cr_assert_eq(input[1], 5);
|
||||||
|
@ -9,49 +9,46 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
extern uint32_t entrypoint(const uint8_t *input) {
|
extern uint32_t entrypoint(const uint8_t *input) {
|
||||||
#define FAILURE 1
|
|
||||||
#define INVALID_INPUT 2
|
|
||||||
|
|
||||||
SolKeyedAccount ka[4];
|
SolKeyedAccount ka[4];
|
||||||
SolParameters params = (SolParameters) { .ka = ka };
|
SolParameters params = (SolParameters) { .ka = ka };
|
||||||
|
|
||||||
if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(ka))) {
|
if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(ka))) {
|
||||||
return INVALID_INPUT;
|
return INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (params.data[0]) {
|
switch (params.data[0]) {
|
||||||
case(1):
|
case(1):
|
||||||
sol_log("modify first account userdata");
|
sol_log("modify first account userdata");
|
||||||
ka[2].userdata[0] = 1;
|
ka[2].userdata[0] = 1;
|
||||||
break;
|
break;
|
||||||
case(2):
|
case(2):
|
||||||
sol_log("modify first account userdata");
|
sol_log("modify first account userdata");
|
||||||
ka[3].userdata[0] = 2;
|
ka[3].userdata[0] = 2;
|
||||||
break;
|
break;
|
||||||
case(3):
|
case(3):
|
||||||
sol_log("modify both account userdata");
|
sol_log("modify both account userdata");
|
||||||
ka[2].userdata[0] += 1;
|
ka[2].userdata[0] += 1;
|
||||||
ka[3].userdata[0] += 2;
|
ka[3].userdata[0] += 2;
|
||||||
break;
|
break;
|
||||||
case(4):
|
case(4):
|
||||||
sol_log("modify first account lamports");
|
sol_log("modify first account lamports");
|
||||||
*ka[1].lamports -= 1;
|
*ka[1].lamports -= 1;
|
||||||
*ka[2].lamports += 1;
|
*ka[2].lamports += 1;
|
||||||
break;
|
break;
|
||||||
case(5):
|
case(5):
|
||||||
sol_log("modify first account lamports");
|
sol_log("modify first account lamports");
|
||||||
*ka[1].lamports -= 2;
|
*ka[1].lamports -= 2;
|
||||||
*ka[3].lamports += 2;
|
*ka[3].lamports += 2;
|
||||||
break;
|
break;
|
||||||
case(6):
|
case(6):
|
||||||
sol_log("modify both account lamports");
|
sol_log("modify both account lamports");
|
||||||
*ka[1].lamports -= 3;
|
*ka[1].lamports -= 3;
|
||||||
*ka[2].lamports += 1;
|
*ka[2].lamports += 1;
|
||||||
*ka[3].lamports += 2;
|
*ka[3].lamports += 2;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
sol_log("Unrecognized command");
|
sol_log("Unrecognized command");
|
||||||
return FAILURE;
|
return INVALID_INSTRUCTION_DATA;
|
||||||
}
|
}
|
||||||
return SUCCESS;
|
return SUCCESS;
|
||||||
}
|
}
|
||||||
|
37
programs/bpf/c/src/error_handling/error_handling.c
Normal file
37
programs/bpf/c/src/error_handling/error_handling.c
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* @brief Example C-based BPF program that exercises duplicate keyed ka
|
||||||
|
* passed to it
|
||||||
|
*/
|
||||||
|
#include <solana_sdk.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom error for when input serialization fails
|
||||||
|
*/
|
||||||
|
|
||||||
|
extern uint32_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (params.data[0]) {
|
||||||
|
case(1):
|
||||||
|
sol_log("return success");
|
||||||
|
return SUCCESS;
|
||||||
|
case(2):
|
||||||
|
sol_log("return a builtin");
|
||||||
|
return INVALID_ACCOUNT_DATA;
|
||||||
|
case(3):
|
||||||
|
sol_log("return custom error");
|
||||||
|
return 42;
|
||||||
|
case(4):
|
||||||
|
sol_log("return error that conflicts with success");
|
||||||
|
return 0x40000000;
|
||||||
|
default:
|
||||||
|
sol_log("Unrecognized command");
|
||||||
|
return INVALID_INSTRUCTION_DATA;
|
||||||
|
}
|
||||||
|
return SUCCESS;
|
||||||
|
}
|
@ -11,27 +11,17 @@
|
|||||||
*/
|
*/
|
||||||
#define NUM_KA 3
|
#define NUM_KA 3
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom error for when input serialization fails
|
|
||||||
*/
|
|
||||||
#define INVALID_INPUT 1
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom error for when transaction is not signed properly
|
|
||||||
*/
|
|
||||||
#define NOT_SIGNED 2
|
|
||||||
|
|
||||||
extern uint32_t entrypoint(const uint8_t *input) {
|
extern uint32_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, ¶ms, SOL_ARRAY_SIZE(ka))) {
|
if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(ka))) {
|
||||||
return INVALID_INPUT;
|
return 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 NOT_SIGNED;
|
return MISSING_REQUIRED_SIGNATURES;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t lamports = *(int64_t *)params.data;
|
int64_t lamports = *(int64_t *)params.data;
|
||||||
|
@ -16,7 +16,7 @@ extern uint32_t entrypoint(const uint8_t *input) {
|
|||||||
sol_log(__FILE__);
|
sol_log(__FILE__);
|
||||||
|
|
||||||
if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(ka))) {
|
if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(ka))) {
|
||||||
return INVALID_INPUT;
|
return 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
|
||||||
|
@ -4,11 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
#include <solana_sdk.h>
|
#include <solana_sdk.h>
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom error for when input serialization fails
|
|
||||||
*/
|
|
||||||
#define INVALID_INPUT 1
|
|
||||||
|
|
||||||
extern uint32_t entrypoint(const uint8_t *input) {
|
extern uint32_t entrypoint(const uint8_t *input) {
|
||||||
SolKeyedAccount ka[1];
|
SolKeyedAccount ka[1];
|
||||||
SolParameters params = (SolParameters) { .ka = ka };
|
SolParameters params = (SolParameters) { .ka = ka };
|
||||||
@ -16,7 +11,7 @@ extern uint32_t entrypoint(const uint8_t *input) {
|
|||||||
sol_log(__FILE__);
|
sol_log(__FILE__);
|
||||||
|
|
||||||
if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(ka))) {
|
if (!sol_deserialize(input, ¶ms, SOL_ARRAY_SIZE(ka))) {
|
||||||
return INVALID_INPUT;
|
return 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
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
extern crate solana_sdk;
|
extern crate solana_sdk;
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
account_info::AccountInfo, entrypoint, entrypoint::SUCCESS, info, pubkey::Pubkey,
|
account_info::AccountInfo, entrypoint, info, program_error::ProgramError, pubkey::Pubkey,
|
||||||
};
|
};
|
||||||
|
|
||||||
entrypoint!(process_instruction);
|
entrypoint!(process_instruction);
|
||||||
@ -10,9 +10,7 @@ fn process_instruction(
|
|||||||
_program_id: &Pubkey,
|
_program_id: &Pubkey,
|
||||||
accounts: &[AccountInfo],
|
accounts: &[AccountInfo],
|
||||||
instruction_data: &[u8],
|
instruction_data: &[u8],
|
||||||
) -> u32 {
|
) -> Result<(), ProgramError> {
|
||||||
const FAILURE: u32 = 1;
|
|
||||||
|
|
||||||
match instruction_data[0] {
|
match instruction_data[0] {
|
||||||
1 => {
|
1 => {
|
||||||
info!("modify first account data");
|
info!("modify first account data");
|
||||||
@ -45,8 +43,8 @@ fn process_instruction(
|
|||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
info!("Unrecognized command");
|
info!("Unrecognized command");
|
||||||
return FAILURE;
|
return Err(ProgramError::InvalidArgument);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SUCCESS
|
Ok(())
|
||||||
}
|
}
|
||||||
|
29
programs/bpf/rust/error_handling/Cargo.toml
Normal file
29
programs/bpf/rust/error_handling/Cargo.toml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
# Note: This crate must be built using do.sh
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "solana-bpf-rust-error-handling"
|
||||||
|
version = "0.24.0"
|
||||||
|
description = "Solana BPF test program written in Rust"
|
||||||
|
authors = ["Solana Maintainers <maintainers@solana.com>"]
|
||||||
|
repository = "https://github.com/solana-labs/solana"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
homepage = "https://solana.com/"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
num-derive = "0.2"
|
||||||
|
num-traits = "0.2"
|
||||||
|
solana-sdk = { path = "../../../../sdk/", version = "0.24.0", default-features = false }
|
||||||
|
thiserror = "1.0"
|
||||||
|
|
||||||
|
[dev_dependencies]
|
||||||
|
solana-sdk-bpf-test = { path = "../../../../sdk/bpf/rust/test", version = "0.24.0" }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
program = ["solana-sdk/program"]
|
||||||
|
default = ["program"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "solana_bpf_rust_error_handling"
|
||||||
|
crate-type = ["cdylib"]
|
2
programs/bpf/rust/error_handling/Xargo.toml
Normal file
2
programs/bpf/rust/error_handling/Xargo.toml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[target.bpfel-unknown-unknown.dependencies.std]
|
||||||
|
features = []
|
60
programs/bpf/rust/error_handling/src/lib.rs
Normal file
60
programs/bpf/rust/error_handling/src/lib.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
//! @brief Example Rust-based BPF program that exercises error handling
|
||||||
|
|
||||||
|
extern crate solana_sdk;
|
||||||
|
use num_derive::FromPrimitive;
|
||||||
|
use solana_sdk::{
|
||||||
|
account_info::AccountInfo, entrypoint, info, program_error::ProgramError, pubkey::Pubkey,
|
||||||
|
};
|
||||||
|
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("The Answer")]
|
||||||
|
TheAnswer = 42,
|
||||||
|
#[error("Conflicting with success")]
|
||||||
|
ConflictingSuccess = 0,
|
||||||
|
#[error("Conflicting with builtin")]
|
||||||
|
ConflictingBuiltin = 0x8000_002d,
|
||||||
|
}
|
||||||
|
impl From<MyError> for ProgramError {
|
||||||
|
fn from(e: MyError) -> Self {
|
||||||
|
ProgramError::CustomError(e as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entrypoint!(process_instruction);
|
||||||
|
fn process_instruction(
|
||||||
|
_program_id: &Pubkey,
|
||||||
|
_accounts: &[AccountInfo],
|
||||||
|
instruction_data: &[u8],
|
||||||
|
) -> Result<(), ProgramError> {
|
||||||
|
match instruction_data[0] {
|
||||||
|
1 => {
|
||||||
|
info!("return success");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
info!("return a builtin");
|
||||||
|
Err(ProgramError::AccountBorrowFailed)
|
||||||
|
}
|
||||||
|
3 => {
|
||||||
|
info!("return custom error");
|
||||||
|
Err(MyError::TheAnswer.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!("Unrecognized command");
|
||||||
|
Err(ProgramError::InvalidInstructionData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,20 @@
|
|||||||
//! @brief Example Rust-based BPF program that moves a lamport from one account to another
|
//! @brief Example Rust-based BPF program that moves a lamport from one account to another
|
||||||
|
|
||||||
extern crate solana_sdk;
|
extern crate solana_sdk;
|
||||||
use solana_sdk::{account_info::AccountInfo, entrypoint, entrypoint::SUCCESS, pubkey::Pubkey};
|
use solana_sdk::{
|
||||||
|
account_info::AccountInfo, entrypoint, program_error::ProgramError, pubkey::Pubkey,
|
||||||
|
};
|
||||||
|
|
||||||
entrypoint!(process_instruction);
|
entrypoint!(process_instruction);
|
||||||
fn process_instruction(
|
fn process_instruction(
|
||||||
_program_id: &Pubkey,
|
_program_id: &Pubkey,
|
||||||
accounts: &[AccountInfo],
|
accounts: &[AccountInfo],
|
||||||
_instruction_data: &[u8],
|
_instruction_data: &[u8],
|
||||||
) -> u32 {
|
) -> Result<(), ProgramError> {
|
||||||
// account 0 is the mint and not owned by this program, any debit of its lamports
|
// account 0 is the mint and not owned by this program, any debit of its lamports
|
||||||
// should result in a failed program execution. Test to ensure that this debit
|
// should result in a failed program execution. Test to ensure that this debit
|
||||||
// is seen by the runtime and fails as expected
|
// is seen by the runtime and fails as expected
|
||||||
**accounts[0].lamports.borrow_mut() -= 1;
|
**accounts[0].lamports.borrow_mut() -= 1;
|
||||||
|
|
||||||
SUCCESS
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,10 @@
|
|||||||
#![allow(unreachable_code)]
|
#![allow(unreachable_code)]
|
||||||
|
|
||||||
extern crate solana_sdk;
|
extern crate solana_sdk;
|
||||||
|
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
account_info::AccountInfo, entrypoint, entrypoint::SUCCESS, info, log::*, pubkey::Pubkey,
|
account_info::AccountInfo, entrypoint, info, log::*, program_error::ProgramError,
|
||||||
|
pubkey::Pubkey,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
@ -24,7 +26,7 @@ fn process_instruction(
|
|||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
accounts: &[AccountInfo],
|
accounts: &[AccountInfo],
|
||||||
instruction_data: &[u8],
|
instruction_data: &[u8],
|
||||||
) -> u32 {
|
) -> Result<(), ProgramError> {
|
||||||
info!("Program identifier:");
|
info!("Program identifier:");
|
||||||
program_id.log();
|
program_id.log();
|
||||||
|
|
||||||
@ -58,7 +60,7 @@ fn process_instruction(
|
|||||||
panic!();
|
panic!();
|
||||||
}
|
}
|
||||||
|
|
||||||
SUCCESS
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -4,10 +4,9 @@ extern crate solana_sdk;
|
|||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
account_info::AccountInfo,
|
account_info::AccountInfo,
|
||||||
clock::{get_segment_from_slot, DEFAULT_SLOTS_PER_EPOCH, DEFAULT_SLOTS_PER_SEGMENT},
|
clock::{get_segment_from_slot, DEFAULT_SLOTS_PER_EPOCH, DEFAULT_SLOTS_PER_SEGMENT},
|
||||||
entrypoint,
|
entrypoint, info,
|
||||||
entrypoint::SUCCESS,
|
|
||||||
info,
|
|
||||||
log::Log,
|
log::Log,
|
||||||
|
program_error::ProgramError,
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
rent,
|
rent,
|
||||||
sysvar::{
|
sysvar::{
|
||||||
@ -21,7 +20,7 @@ fn process_instruction(
|
|||||||
_program_id: &Pubkey,
|
_program_id: &Pubkey,
|
||||||
accounts: &[AccountInfo],
|
accounts: &[AccountInfo],
|
||||||
_instruction_data: &[u8],
|
_instruction_data: &[u8],
|
||||||
) -> u32 {
|
) -> Result<(), ProgramError> {
|
||||||
// Clock
|
// Clock
|
||||||
info!("Clock identifier:");
|
info!("Clock identifier:");
|
||||||
sysvar::clock::id().log();
|
sysvar::clock::id().log();
|
||||||
@ -66,5 +65,5 @@ fn process_instruction(
|
|||||||
(0, true)
|
(0, true)
|
||||||
);
|
);
|
||||||
|
|
||||||
SUCCESS
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -31,9 +31,10 @@ mod bpf {
|
|||||||
account::Account,
|
account::Account,
|
||||||
bpf_loader,
|
bpf_loader,
|
||||||
client::SyncClient,
|
client::SyncClient,
|
||||||
instruction::{AccountMeta, Instruction},
|
instruction::{AccountMeta, Instruction, InstructionError},
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
signature::KeypairUtil,
|
signature::KeypairUtil,
|
||||||
|
transaction::TransactionError,
|
||||||
};
|
};
|
||||||
use std::{io::Read, sync::Arc};
|
use std::{io::Read, sync::Arc};
|
||||||
|
|
||||||
@ -152,6 +153,59 @@ mod bpf {
|
|||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert_eq!(lamports, 13);
|
assert_eq!(lamports, 13);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_program_bpf_c_error_handling() {
|
||||||
|
solana_logger::setup();
|
||||||
|
|
||||||
|
let filename = create_bpf_path("error_handling");
|
||||||
|
let mut file = File::open(filename).unwrap();
|
||||||
|
let mut elf = Vec::new();
|
||||||
|
file.read_to_end(&mut elf).unwrap();
|
||||||
|
|
||||||
|
let GenesisConfigInfo {
|
||||||
|
genesis_config,
|
||||||
|
mint_keypair,
|
||||||
|
..
|
||||||
|
} = create_genesis_config(50);
|
||||||
|
|
||||||
|
let bank = Bank::new(&genesis_config);
|
||||||
|
let bank_client = BankClient::new(bank);
|
||||||
|
let program_id = load_program(&bank_client, &mint_keypair, &bpf_loader::id(), elf);
|
||||||
|
let account_metas = vec![AccountMeta::new(mint_keypair.pubkey(), true)];
|
||||||
|
|
||||||
|
let instruction = Instruction::new(program_id, &1u8, account_metas.clone());
|
||||||
|
let result = bank_client.send_instruction(&mint_keypair, instruction);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
let instruction = Instruction::new(program_id, &2u8, account_metas.clone());
|
||||||
|
let result = bank_client.send_instruction(&mint_keypair, instruction);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().unwrap(),
|
||||||
|
TransactionError::InstructionError(0, InstructionError::InvalidAccountData)
|
||||||
|
);
|
||||||
|
|
||||||
|
let instruction = Instruction::new(program_id, &3u8, account_metas.clone());
|
||||||
|
let result = bank_client.send_instruction(&mint_keypair, instruction);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().unwrap(),
|
||||||
|
TransactionError::InstructionError(0, InstructionError::CustomError(42))
|
||||||
|
);
|
||||||
|
|
||||||
|
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, &6u8, account_metas.clone());
|
||||||
|
let result = bank_client.send_instruction(&mint_keypair, instruction);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().unwrap(),
|
||||||
|
TransactionError::InstructionError(0, InstructionError::InvalidInstructionData)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bpf_rust")]
|
#[cfg(feature = "bpf_rust")]
|
||||||
@ -162,10 +216,11 @@ mod bpf {
|
|||||||
bpf_loader,
|
bpf_loader,
|
||||||
client::SyncClient,
|
client::SyncClient,
|
||||||
clock::DEFAULT_SLOTS_PER_EPOCH,
|
clock::DEFAULT_SLOTS_PER_EPOCH,
|
||||||
instruction::{AccountMeta, Instruction},
|
instruction::{AccountMeta, Instruction, InstructionError},
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
signature::{Keypair, KeypairUtil},
|
signature::{Keypair, KeypairUtil},
|
||||||
sysvar::{clock, fees, rent, rewards, slot_hashes, stake_history},
|
sysvar::{clock, fees, rent, rewards, slot_hashes, stake_history},
|
||||||
|
transaction::TransactionError,
|
||||||
};
|
};
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -300,5 +355,68 @@ mod bpf {
|
|||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert_eq!(lamports, 13);
|
assert_eq!(lamports, 13);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_program_bpf_rust_error_handling() {
|
||||||
|
solana_logger::setup();
|
||||||
|
|
||||||
|
let filename = create_bpf_path("solana_bpf_rust_error_handling");
|
||||||
|
let mut file = File::open(filename).unwrap();
|
||||||
|
let mut elf = Vec::new();
|
||||||
|
file.read_to_end(&mut elf).unwrap();
|
||||||
|
|
||||||
|
let GenesisConfigInfo {
|
||||||
|
genesis_config,
|
||||||
|
mint_keypair,
|
||||||
|
..
|
||||||
|
} = create_genesis_config(50);
|
||||||
|
|
||||||
|
let bank = Bank::new(&genesis_config);
|
||||||
|
let bank_client = BankClient::new(bank);
|
||||||
|
let program_id = load_program(&bank_client, &mint_keypair, &bpf_loader::id(), elf);
|
||||||
|
let account_metas = vec![AccountMeta::new(mint_keypair.pubkey(), true)];
|
||||||
|
|
||||||
|
let instruction = Instruction::new(program_id, &1u8, account_metas.clone());
|
||||||
|
let result = bank_client.send_instruction(&mint_keypair, instruction);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
let instruction = Instruction::new(program_id, &2u8, account_metas.clone());
|
||||||
|
let result = bank_client.send_instruction(&mint_keypair, instruction);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().unwrap(),
|
||||||
|
TransactionError::InstructionError(0, InstructionError::AccountBorrowFailed)
|
||||||
|
);
|
||||||
|
|
||||||
|
let instruction = Instruction::new(program_id, &3u8, account_metas.clone());
|
||||||
|
let result = bank_client.send_instruction(&mint_keypair, instruction);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().unwrap(),
|
||||||
|
TransactionError::InstructionError(0, InstructionError::CustomError(42))
|
||||||
|
);
|
||||||
|
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let instruction = Instruction::new(program_id, &6u8, account_metas.clone());
|
||||||
|
let result = bank_client.send_instruction(&mint_keypair, instruction);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().unwrap(),
|
||||||
|
TransactionError::InstructionError(0, InstructionError::InvalidInstructionData)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ use solana_sdk::{
|
|||||||
sysvar::rent,
|
sysvar::rent,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
convert::TryFrom,
|
|
||||||
io::{prelude::*, Error},
|
io::{prelude::*, Error},
|
||||||
mem,
|
mem,
|
||||||
};
|
};
|
||||||
@ -145,18 +144,15 @@ 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) => match u32::try_from(status) {
|
Ok(status) => {
|
||||||
Ok(status) => {
|
// ignore upper 32bits if any, programs only return lower 32bits
|
||||||
if status > 0 {
|
let status = status as u32;
|
||||||
warn!("BPF program failed: {}", status);
|
if status != 0 {
|
||||||
return Err(InstructionError::CustomError(status));
|
let error: InstructionError = status.into();
|
||||||
}
|
warn!("BPF program failed: {:?}", error);
|
||||||
|
return Err(error);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
}
|
||||||
warn!("BPF VM encountered invalid status: {}", e);
|
|
||||||
return Err(InstructionError::GenericError);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("BPF VM failed to run program: {}", e);
|
warn!("BPF VM failed to run program: {}", e);
|
||||||
return Err(InstructionError::GenericError);
|
return Err(InstructionError::GenericError);
|
||||||
|
Binary file not shown.
@ -56,6 +56,36 @@ static_assert(sizeof(uint64_t) == 8);
|
|||||||
*/
|
*/
|
||||||
#define SUCCESS 0
|
#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
|
||||||
|
*/
|
||||||
|
#define BUILTIN_ERROR_START 0x80000000
|
||||||
|
|
||||||
|
/** 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)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Boolean type
|
* Boolean type
|
||||||
*/
|
*/
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
//! @brief Solana Rust-based BPF program entry point and its parameter types
|
//! @brief Solana Rust-based BPF program entry point and its parameter types
|
||||||
|
|
||||||
#![cfg(feature = "program")]
|
|
||||||
|
|
||||||
extern crate alloc;
|
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 alloc::vec::Vec;
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
@ -17,8 +15,11 @@ use std::{
|
|||||||
/// program_id: Program ID of the currently executing program
|
/// program_id: Program ID of the currently executing program
|
||||||
/// accounts: Accounts passed as part of the instruction
|
/// accounts: Accounts passed as part of the instruction
|
||||||
/// instruction_data: Instruction data
|
/// instruction_data: Instruction data
|
||||||
pub type ProcessInstruction =
|
pub type ProcessInstruction = fn(
|
||||||
fn(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> u32;
|
program_id: &Pubkey,
|
||||||
|
accounts: &[AccountInfo],
|
||||||
|
instruction_data: &[u8],
|
||||||
|
) -> 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: u32 = 0;
|
||||||
@ -35,10 +36,11 @@ macro_rules! entrypoint {
|
|||||||
/// # 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) -> u32 {
|
||||||
unsafe {
|
let (program_id, accounts, instruction_data) =
|
||||||
let (program_id, accounts, instruction_data) =
|
unsafe { $crate::entrypoint::deserialize(input) };
|
||||||
$crate::entrypoint::deserialize(input);
|
match $process_instruction(&program_id, &accounts, &instruction_data) {
|
||||||
$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
|
/// NOTE: u64 requires special serialization to avoid the loss of precision in JS clients and
|
||||||
/// so is not used for now.
|
/// so is not used for now.
|
||||||
CustomError(u32),
|
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 {
|
impl InstructionError {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::{account::KeyedAccount, instruction::InstructionError, pubkey::Pubkey};
|
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(
|
pub type Entrypoint = unsafe extern "C" fn(
|
||||||
program_id: &Pubkey,
|
program_id: &Pubkey,
|
||||||
keyed_accounts: &[KeyedAccount],
|
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
|
/// Return the next KeyedAccount or a NotEnoughAccountKeys instruction error
|
||||||
pub fn next_keyed_account<I: Iterator>(iter: &mut I) -> Result<I::Item, InstructionError> {
|
pub fn next_keyed_account<I: Iterator>(iter: &mut I) -> Result<I::Item, InstructionError> {
|
||||||
iter.next().ok_or(InstructionError::NotEnoughAccountKeys)
|
iter.next().ok_or(InstructionError::NotEnoughAccountKeys)
|
||||||
|
@ -59,6 +59,7 @@ pub use solana_sdk_macro::declare_id;
|
|||||||
pub mod account_info;
|
pub mod account_info;
|
||||||
pub mod entrypoint;
|
pub mod entrypoint;
|
||||||
pub mod log;
|
pub mod log;
|
||||||
|
pub mod program_error;
|
||||||
|
|
||||||
// Modules not usable by on-chain programs
|
// Modules not usable by on-chain programs
|
||||||
#[cfg(not(feature = "program"))]
|
#[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