lang: Framework defined error codes (#354)

This commit is contained in:
Armani Ferrante 2021-06-09 13:02:50 -07:00 committed by GitHub
parent 39d0c62a2c
commit ba99c9c920
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 510 additions and 176 deletions

View File

@ -20,6 +20,10 @@ incremented for features.
* lang: Allows one to use `remaining_accounts` with `CpiContext` by implementing the `ToAccountMetas` trait on `CpiContext` ([#351](https://github.com/project-serum/anchor/pull/351/files)).
### Breaking
* lang, ts: Framework defined error codes are introduced, reserving error codes 0-300 for Anchor, and 300 and up for user defined error codes ([#354](https://github.com/project-serum/anchor/pull/354)).
## [0.7.0] - 2021-05-31
### Features

4
Cargo.lock generated
View File

@ -4066,9 +4066,9 @@ dependencies = [
[[package]]
name = "url"
version = "2.2.1"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b"
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
dependencies = [
"form_urlencoded",
"idna",

View File

@ -89,7 +89,7 @@ describe("chat", () => {
assert.ok(msg.from.equals(user));
assert.ok(data.startsWith(messages[idx]));
} else {
assert.ok(new anchor.web3.PublicKey());
assert.ok(anchor.web3.PublicKey.default);
assert.ok(
JSON.stringify(msg.data) === JSON.stringify(new Array(280).fill(0))
);

View File

@ -6,6 +6,7 @@ use anchor_lang::prelude::*;
#[program]
mod errors {
use super::*;
pub fn hello(_ctx: Context<Hello>) -> Result<()> {
Err(MyError::Hello.into())
}
@ -17,11 +18,48 @@ mod errors {
pub fn hello_next(_ctx: Context<Hello>) -> Result<()> {
Err(MyError::HelloNext.into())
}
pub fn mut_error(_ctx: Context<MutError>) -> Result<()> {
Ok(())
}
pub fn belongs_to_error(_ctx: Context<BelongsToError>) -> Result<()> {
Ok(())
}
pub fn signer_error(_ctx: Context<SignerError>) -> Result<()> {
Ok(())
}
}
#[derive(Accounts)]
pub struct Hello {}
#[derive(Accounts)]
pub struct MutError<'info> {
#[account(mut)]
my_account: AccountInfo<'info>,
}
#[derive(Accounts)]
pub struct BelongsToError<'info> {
#[account(init, belongs_to = owner)]
my_account: ProgramAccount<'info, BelongsToAccount>,
owner: AccountInfo<'info>,
rent: Sysvar<'info, Rent>,
}
#[derive(Accounts)]
pub struct SignerError<'info> {
#[account(signer)]
my_account: AccountInfo<'info>,
}
#[account]
pub struct BelongsToAccount {
owner: Pubkey,
}
#[error]
pub enum MyError {
#[msg("This is an error message clients will automatically display")]

View File

@ -1,5 +1,6 @@
const assert = require("assert");
const anchor = require('@project-serum/anchor');
const { Account, Transaction, TransactionInstruction } = anchor.web3;
describe("errors", () => {
// Configure the client to use the local cluster.
@ -16,7 +17,7 @@ describe("errors", () => {
"This is an error message clients will automatically display";
assert.equal(err.toString(), errMsg);
assert.equal(err.msg, errMsg);
assert.equal(err.code, 100);
assert.equal(err.code, 300);
}
});
@ -28,7 +29,7 @@ describe("errors", () => {
const errMsg = "HelloNoMsg";
assert.equal(err.toString(), errMsg);
assert.equal(err.msg, errMsg);
assert.equal(err.code, 100 + 123);
assert.equal(err.code, 300 + 123);
}
});
@ -40,7 +41,75 @@ describe("errors", () => {
const errMsg = "HelloNext";
assert.equal(err.toString(), errMsg);
assert.equal(err.msg, errMsg);
assert.equal(err.code, 100 + 124);
assert.equal(err.code, 300 + 124);
}
});
it("Emits a mut error", async () => {
try {
const tx = await program.rpc.mutError({
accounts: {
myAccount: anchor.web3.SYSVAR_RENT_PUBKEY,
},
});
assert.ok(false);
} catch (err) {
const errMsg = "A mut constraint was violated";
assert.equal(err.toString(), errMsg);
assert.equal(err.msg, errMsg);
assert.equal(err.code, 140);
}
});
it("Emits a belongs to error", async () => {
try {
const account = new Account();
const tx = await program.rpc.belongsToError({
accounts: {
myAccount: account.publicKey,
owner: anchor.web3.SYSVAR_RENT_PUBKEY,
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
},
instructions: [
await program.account.belongsToAccount.createInstruction(account),
],
signers: [account],
});
assert.ok(false);
} catch (err) {
const errMsg = "A belongs_to constraint was violated";
assert.equal(err.toString(), errMsg);
assert.equal(err.msg, errMsg);
assert.equal(err.code, 141);
}
});
// This test uses a raw transaction and provider instead of a program
// instance since the client won't allow one to send a transaction
// with an invalid signer account.
it("Emits a signer error", async () => {
try {
const account = new Account();
const tx = new Transaction();
tx.add(
new TransactionInstruction({
keys: [
{
pubkey: anchor.web3.SYSVAR_RENT_PUBKEY,
isWritable: false,
isSigner: false,
},
],
programId: program.programId,
data: program.coder.instruction.encode("signer_error", {}),
})
);
await program.provider.send(tx);
assert.ok(false);
} catch (err) {
const errMsg =
"Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x8e";
assert.equal(err.toString(), errMsg);
}
});
});

View File

@ -31,7 +31,7 @@ module.exports = async function (provider) {
});
// Delete the default whitelist entries.
const defaultEntry = { programId: new anchor.web3.PublicKey() };
const defaultEntry = { programId: new anchor.web3.PublicKey.default };
await lockup.state.rpc.whitelistDelete(defaultEntry, {
accounts: {
authority: provider.wallet.publicKey,

View File

@ -42,12 +42,12 @@ describe("Lockup and Registry", () => {
assert.ok(lockupAccount.authority.equals(provider.wallet.publicKey));
assert.ok(lockupAccount.whitelist.length === WHITELIST_SIZE);
lockupAccount.whitelist.forEach((e) => {
assert.ok(e.programId.equals(new anchor.web3.PublicKey()));
assert.ok(e.programId.equals(anchor.web3.PublicKey.default));
});
});
it("Deletes the default whitelisted addresses", async () => {
const defaultEntry = { programId: new anchor.web3.PublicKey() };
const defaultEntry = { programId: anchor.web3.PublicKey.default };
await lockup.state.rpc.whitelistDelete(defaultEntry, {
accounts: {
authority: provider.wallet.publicKey,
@ -116,7 +116,7 @@ describe("Lockup and Registry", () => {
await lockup.state.rpc.whitelistAdd(e, { accounts });
},
(err) => {
assert.equal(err.code, 108);
assert.equal(err.code, 308);
assert.equal(err.msg, "Whitelist is full");
return true;
}
@ -216,7 +216,7 @@ describe("Lockup and Registry", () => {
});
},
(err) => {
assert.equal(err.code, 107);
assert.equal(err.code, 307);
assert.equal(err.msg, "Insufficient withdrawal balance.");
return true;
}
@ -389,7 +389,7 @@ describe("Lockup and Registry", () => {
assert.ok(memberAccount.registrar.equals(registrar.publicKey));
assert.ok(memberAccount.beneficiary.equals(provider.wallet.publicKey));
assert.ok(memberAccount.metadata.equals(new anchor.web3.PublicKey()));
assert.ok(memberAccount.metadata.equals(anchor.web3.PublicKey.default));
assert.equal(
JSON.stringify(memberAccount.balances),
JSON.stringify(balances)
@ -781,7 +781,7 @@ describe("Lockup and Registry", () => {
(err) => {
// Solana doesn't propagate errors across CPI. So we receive the registry's error code,
// not the lockup's.
const errorCode = "custom program error: 0x78";
const errorCode = "custom program error: 0x140";
assert.ok(err.toString().split(errorCode).length === 2);
return true;
}
@ -863,7 +863,7 @@ describe("Lockup and Registry", () => {
await tryEndUnstake();
},
(err) => {
assert.equal(err.code, 109);
assert.equal(err.code, 309);
assert.equal(err.msg, "The unstake timelock has not yet expired.");
return true;
}

View File

@ -21,14 +21,14 @@ describe("zero-copy", () => {
assert.ok(state.authority.equals(program.provider.wallet.publicKey));
assert.ok(state.events.length === 250);
state.events.forEach((event, idx) => {
assert.ok(event.from.equals(new PublicKey()));
assert.ok(event.from.equals(PublicKey.default));
assert.ok(event.data.toNumber() === 0);
});
});
it("Updates zero copy state", async () => {
let event = {
from: new PublicKey(),
from: PublicKey.default,
data: new BN(1234),
};
await program.state.rpc.setEvent(5, event, {
@ -44,7 +44,7 @@ describe("zero-copy", () => {
assert.ok(event.from.equals(event.from));
assert.ok(event.data.eq(event.data));
} else {
assert.ok(event.from.equals(new PublicKey()));
assert.ok(event.from.equals(PublicKey.default));
assert.ok(event.data.toNumber() === 0);
}
});
@ -175,7 +175,7 @@ describe("zero-copy", () => {
const account = await program.account.eventQ.fetch(eventQ.publicKey);
assert.ok(account.events.length === 25000);
account.events.forEach((event) => {
assert.ok(event.from.equals(new PublicKey()));
assert.ok(event.from.equals(PublicKey.default));
assert.ok(event.data.toNumber() === 0);
});
});
@ -196,7 +196,7 @@ describe("zero-copy", () => {
assert.ok(event.from.equals(program.provider.wallet.publicKey));
assert.ok(event.data.toNumber() === 48);
} else {
assert.ok(event.from.equals(new PublicKey()));
assert.ok(event.from.equals(PublicKey.default));
assert.ok(event.data.toNumber() === 0);
}
});
@ -219,7 +219,7 @@ describe("zero-copy", () => {
assert.ok(event.from.equals(program.provider.wallet.publicKey));
assert.ok(event.data.toNumber() === 1234);
} else {
assert.ok(event.from.equals(new PublicKey()));
assert.ok(event.from.equals(PublicKey.default));
assert.ok(event.data.toNumber() === 0);
}
});
@ -245,7 +245,7 @@ describe("zero-copy", () => {
assert.ok(event.from.equals(program.provider.wallet.publicKey));
assert.ok(event.data.toNumber() === 99);
} else {
assert.ok(event.from.equals(new PublicKey()));
assert.ok(event.from.equals(PublicKey.default));
assert.ok(event.data.toNumber() === 0);
}
});

View File

@ -119,11 +119,11 @@ pub fn account(
impl anchor_lang::AccountDeserialize for #account_name {
fn try_deserialize(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
if buf.len() < #discriminator.len() {
return Err(ProgramError::AccountDataTooSmall);
return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorNotFound.into());
}
let given_disc = &buf[..8];
if &#discriminator != given_disc {
return Err(ProgramError::InvalidInstructionData);
return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorMismatch.into());
}
Self::try_deserialize_unchecked(buf)
}
@ -144,12 +144,12 @@ pub fn account(
impl anchor_lang::AccountSerialize for #account_name {
fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> std::result::Result<(), ProgramError> {
writer.write_all(&#discriminator).map_err(|_| ProgramError::InvalidAccountData)?;
writer.write_all(&#discriminator).map_err(|_| anchor_lang::__private::ErrorCode::AccountDidNotSerialize)?;
AnchorSerialize::serialize(
self,
writer
)
.map_err(|_| ProgramError::InvalidAccountData)?;
.map_err(|_| anchor_lang::__private::ErrorCode::AccountDidNotSerialize)?;
Ok(())
}
}
@ -157,11 +157,11 @@ pub fn account(
impl anchor_lang::AccountDeserialize for #account_name {
fn try_deserialize(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
if buf.len() < #discriminator.len() {
return Err(ProgramError::AccountDataTooSmall);
return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorNotFound.into());
}
let given_disc = &buf[..8];
if &#discriminator != given_disc {
return Err(ProgramError::InvalidInstructionData);
return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorMismatch.into());
}
Self::try_deserialize_unchecked(buf)
}
@ -169,7 +169,7 @@ pub fn account(
fn try_deserialize_unchecked(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
let mut data: &[u8] = &buf[8..];
AnchorDeserialize::deserialize(&mut data)
.map_err(|_| ProgramError::InvalidAccountData)
.map_err(|_| anchor_lang::__private::ErrorCode::AccountDidNotDeserialize.into())
}
}
@ -327,8 +327,8 @@ pub fn zero_copy(
let account_strct = parse_macro_input!(item as syn::ItemStruct);
proc_macro::TokenStream::from(quote! {
#[derive(anchor_lang::__private::ZeroCopyAccessor, Copy, Clone)]
#[repr(packed)]
#account_strct
#[derive(anchor_lang::__private::ZeroCopyAccessor, Copy, Clone)]
#[repr(packed)]
#account_strct
})
}

View File

@ -2,6 +2,7 @@ extern crate proc_macro;
use anchor_syn::codegen::error as error_codegen;
use anchor_syn::parser::error as error_parser;
use anchor_syn::ErrorArgs;
use syn::parse_macro_input;
/// Generates `Error` and `type Result<T> = Result<T, Error>` types to be
@ -47,10 +48,14 @@ use syn::parse_macro_input;
/// parsers and IDLs can map error codes to error messages.
#[proc_macro_attribute]
pub fn error(
_args: proc_macro::TokenStream,
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let args = match args.is_empty() {
true => None,
false => Some(parse_macro_input!(args as ErrorArgs)),
};
let mut error_enum = parse_macro_input!(input as syn::ItemEnum);
let error = error_codegen::generate(error_parser::parse(&mut error_enum));
let error = error_codegen::generate(error_parser::parse(&mut error_enum, args));
proc_macro::TokenStream::from(error)
}

View File

@ -101,15 +101,7 @@ use syn::parse_macro_input;
/// use super::*;
///
/// #[state]
/// pub struct CounterAuth {}
///
/// // TODO: remove this impl block after addressing
/// // https://github.com/project-serum/anchor/issues/71.
/// impl CounterAuth {
/// pub fn new(_ctx: Context<Empty>) -> Result<Self, ProgramError> {
/// Ok(Self {})
/// }
/// }
/// pub struct CounterAuth;
///
/// impl<'info> Auth<'info, Empty> for CounterAuth {
/// fn is_authorized(_ctx: Context<Empty>, current: u64, new: u64) -> ProgramResult {
@ -216,7 +208,7 @@ pub fn interface(
#(#args_no_tys),*
};
let mut ix_data = anchor_lang::AnchorSerialize::try_to_vec(&ix)
.map_err(|_| anchor_lang::solana_program::program_error::ProgramError::InvalidInstructionData)?;
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotSerialize)?;
let mut data = #sighash_tts.to_vec();
data.append(&mut ix_data);
let accounts = ctx.accounts.to_account_metas(None);

View File

@ -41,7 +41,7 @@ pub fn state(
fn size(&self) -> std::result::Result<u64, anchor_lang::solana_program::program_error::ProgramError> {
Ok(8 + self
.try_to_vec()
.map_err(|_| ProgramError::Custom(1))?
.map_err(|_| anchor_lang::__private::ErrorCode::AccountDidNotSerialize)?
.len() as u64)
}
}

View File

@ -1,3 +1,4 @@
use crate::error::ErrorCode;
use crate::{Accounts, AccountsExit, AccountsInit, ToAccountInfo, ToAccountInfos, ToAccountMetas};
use solana_program::account_info::AccountInfo;
use solana_program::entrypoint::ProgramResult;
@ -11,7 +12,7 @@ impl<'info> Accounts<'info> for AccountInfo<'info> {
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ProgramError::NotEnoughAccountKeys);
return Err(ErrorCode::AccountNotEnoughKeys.into());
}
let account = &accounts[0];
*accounts = &accounts[1..];
@ -25,7 +26,7 @@ impl<'info> AccountsInit<'info> for AccountInfo<'info> {
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ProgramError::NotEnoughAccountKeys);
return Err(ErrorCode::AccountNotEnoughKeys.into());
}
let account = &accounts[0];
@ -37,7 +38,7 @@ impl<'info> AccountsInit<'info> for AccountInfo<'info> {
disc_bytes.copy_from_slice(&data[..8]);
let discriminator = u64::from_le_bytes(disc_bytes);
if discriminator != 0 {
return Err(ProgramError::InvalidAccountData);
return Err(ErrorCode::AccountDiscriminatorAlreadySet.into());
}
Ok(account.clone())

View File

@ -1,3 +1,4 @@
use crate::error::ErrorCode;
use crate::{
AccountDeserialize, Accounts, AccountsExit, ToAccountInfo, ToAccountInfos, ToAccountMetas,
};
@ -51,7 +52,7 @@ where
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ProgramError::NotEnoughAccountKeys);
return Err(ErrorCode::AccountNotEnoughKeys.into());
}
let account = &accounts[0];
*accounts = &accounts[1..];

View File

@ -1,3 +1,4 @@
use crate::error::ErrorCode;
use crate::{
AccountDeserialize, AccountSerialize, Accounts, AccountsExit, CpiStateContext, ProgramState,
ToAccountInfo, ToAccountInfos, ToAccountMetas,
@ -67,7 +68,7 @@ where
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ProgramError::NotEnoughAccountKeys);
return Err(ErrorCode::AccountNotEnoughKeys.into());
}
let account = &accounts[0];
*accounts = &accounts[1..];

View File

@ -1,34 +1,71 @@
use solana_program::program_error::ProgramError;
use crate::error;
// Error type that can be returned by internal framework code.
#[doc(hidden)]
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
ProgramError(#[from] ProgramError),
#[error("{0:?}")]
ErrorCode(#[from] ErrorCode),
}
#[derive(Debug, Clone, Copy)]
#[repr(u32)]
// Error codes that can be returned by internal framework code.
#[error(offset = 0)]
pub enum ErrorCode {
WrongSerialization = 1,
}
// Instructions.
#[msg("8 byte instruction identifier not provided")]
InstructionMissing = 100,
#[msg("Fallback functions are not supported")]
InstructionFallbackNotFound,
#[msg("The program could not deserialize the given instruction")]
InstructionDidNotDeserialize,
#[msg("The program could not serialize the given instruction")]
InstructionDidNotSerialize,
impl std::fmt::Display for ErrorCode {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
<Self as std::fmt::Debug>::fmt(self, fmt)
}
}
// IDL instructions.
#[msg("The program was compiled without idl instructions")]
IdlInstructionStub = 120,
#[msg("Invalid program given to the IDL instruction")]
IdlInstructionInvalidProgram,
impl std::error::Error for ErrorCode {}
// Constraints.
#[msg("A mut constraint was violated")]
ConstraintMut = 140,
#[msg("A belongs to constraint was violated")]
ConstraintBelongsTo,
#[msg("A signer constraint as violated")]
ConstraintSigner,
#[msg("A raw constraint was violated")]
ConstraintRaw,
#[msg("An owner constraint was violated")]
ConstraintOwner,
#[msg("A rent exemption constraint was violated")]
ConstraintRentExempt,
#[msg("A seeds constraint was violated")]
ConstraintSeeds,
#[msg("An executable constraint was violated")]
ConstraintExecutable,
#[msg("A state constraint was violated")]
ConstraintState,
#[msg("An associated constraint was violated")]
ConstraintAssociated,
#[msg("An associated init constraint was violated")]
ConstraintAssociatedInit,
impl std::convert::From<Error> for ProgramError {
fn from(e: Error) -> ProgramError {
match e {
Error::ProgramError(e) => e,
Error::ErrorCode(c) => ProgramError::Custom(c as u32),
}
}
// Accounts.
#[msg("The account discriminator was already set on this account")]
AccountDiscriminatorAlreadySet = 160,
#[msg("No 8 byte discriminator was found on the account")]
AccountDiscriminatorNotFound,
#[msg("8 byte discriminator did not match what was expected")]
AccountDiscriminatorMismatch,
#[msg("Failed to deserialize the account")]
AccountDidNotDeserialize,
#[msg("Failed to serialize the account")]
AccountDidNotSerialize,
#[msg("Not enough account keys given to the instruction")]
AccountNotEnoughKeys,
#[msg("The given account is not mutable")]
AccountNotMutable,
#[msg("The given account is not owned by the executing program")]
AccountNotProgramOwned,
// State.
#[msg("The given state account does not have the correct address")]
StateInvalidAddress = 180,
// Used for APIs that shouldn't be used anymore.
#[msg("The API being used is deprecated and should no longer be used")]
Deprecated = 299,
}

View File

@ -239,7 +239,7 @@ pub mod __private {
use solana_program::pubkey::Pubkey;
pub use crate::ctor::Ctor;
pub use crate::error::Error;
pub use crate::error::{Error, ErrorCode};
pub use anchor_attribute_account::ZeroCopyAccessor;
pub use anchor_attribute_event::EventIndex;
pub use base64;
@ -249,6 +249,9 @@ pub mod __private {
pub use crate::state::*;
}
// The starting point for user defined error codes.
pub const ERROR_CODE_OFFSET: u32 = 300;
// Calculates the size of an account, which may be larger than the deserialized
// data in it. This trait is currently only used for `#[state]` accounts.
#[doc(hidden)]

View File

@ -1,3 +1,4 @@
use crate::error::ErrorCode;
use crate::{
Accounts, AccountsExit, AccountsInit, ToAccountInfo, ToAccountInfos, ToAccountMetas, ZeroCopy,
};
@ -44,7 +45,7 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
let mut disc_bytes = [0u8; 8];
disc_bytes.copy_from_slice(&data[..8]);
if disc_bytes != T::discriminator() {
return Err(ProgramError::InvalidAccountData);
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
}
Ok(Loader::new(acc_info.clone()))
@ -60,7 +61,7 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
disc_bytes.copy_from_slice(&data[..8]);
let discriminator = u64::from_le_bytes(disc_bytes);
if discriminator != 0 {
return Err(ProgramError::InvalidAccountData);
return Err(ErrorCode::AccountDiscriminatorAlreadySet.into());
}
Ok(Loader::new(acc_info.clone()))
@ -73,7 +74,7 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
let mut disc_bytes = [0u8; 8];
disc_bytes.copy_from_slice(&data[..8]);
if disc_bytes != T::discriminator() {
return Err(ProgramError::InvalidAccountData);
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
}
Ok(Ref::map(data, |data| bytemuck::from_bytes(&data[8..])))
@ -84,7 +85,7 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
// AccountInfo api allows you to borrow mut even if the account isn't
// writable, so add this check for a better dev experience.
if !self.acc_info.is_writable {
return Err(ProgramError::Custom(87)); // todo: proper error
return Err(ErrorCode::AccountNotMutable.into());
}
let data = self.acc_info.try_borrow_mut_data()?;
@ -92,7 +93,7 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
let mut disc_bytes = [0u8; 8];
disc_bytes.copy_from_slice(&data[..8]);
if disc_bytes != T::discriminator() {
return Err(ProgramError::InvalidAccountData);
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
}
Ok(RefMut::map(data, |data| {
@ -106,7 +107,7 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
// AccountInfo api allows you to borrow mut even if the account isn't
// writable, so add this check for a better dev experience.
if !self.acc_info.is_writable {
return Err(ProgramError::Custom(87)); // todo: proper error
return Err(ErrorCode::AccountNotMutable.into());
}
let data = self.acc_info.try_borrow_mut_data()?;
@ -116,7 +117,7 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
disc_bytes.copy_from_slice(&data[..8]);
let discriminator = u64::from_le_bytes(disc_bytes);
if discriminator != 0 {
return Err(ProgramError::InvalidAccountData);
return Err(ErrorCode::AccountDiscriminatorAlreadySet.into());
}
Ok(RefMut::map(data, |data| {
@ -132,13 +133,13 @@ impl<'info, T: ZeroCopy> Accounts<'info> for Loader<'info, T> {
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ProgramError::NotEnoughAccountKeys);
return Err(ErrorCode::AccountNotEnoughKeys.into());
}
let account = &accounts[0];
*accounts = &accounts[1..];
let l = Loader::try_from(account)?;
if l.acc_info.owner != program_id {
return Err(ProgramError::Custom(1)); // todo: proper error
return Err(ErrorCode::AccountNotProgramOwned.into());
}
Ok(l)
}
@ -151,13 +152,13 @@ impl<'info, T: ZeroCopy> AccountsInit<'info> for Loader<'info, T> {
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ProgramError::NotEnoughAccountKeys);
return Err(ErrorCode::AccountNotEnoughKeys.into());
}
let account = &accounts[0];
*accounts = &accounts[1..];
let l = Loader::try_from_init(account)?;
if l.acc_info.owner != program_id {
return Err(ProgramError::Custom(1)); // todo: proper error
return Err(ErrorCode::AccountNotProgramOwned.into());
}
Ok(l)
}

View File

@ -1,3 +1,4 @@
use crate::error::ErrorCode;
use crate::{
AccountDeserialize, AccountSerialize, Accounts, AccountsExit, AccountsInit, CpiAccount,
ToAccountInfo, ToAccountInfos, ToAccountMetas,
@ -52,7 +53,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramAccount<'a, T>
disc_bytes.copy_from_slice(&data[..8]);
let discriminator = u64::from_le_bytes(disc_bytes);
if discriminator != 0 {
return Err(ProgramError::InvalidAccountData);
return Err(ErrorCode::AccountDiscriminatorAlreadySet.into());
}
Ok(ProgramAccount::new(
@ -72,13 +73,13 @@ where
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ProgramError::NotEnoughAccountKeys);
return Err(ErrorCode::AccountNotEnoughKeys.into());
}
let account = &accounts[0];
*accounts = &accounts[1..];
let pa = ProgramAccount::try_from(account)?;
if pa.inner.info.owner != program_id {
return Err(ProgramError::Custom(1)); // todo: proper error
return Err(ErrorCode::AccountNotProgramOwned.into());
}
Ok(pa)
}
@ -94,13 +95,13 @@ where
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ProgramError::NotEnoughAccountKeys);
return Err(ErrorCode::AccountNotEnoughKeys.into());
}
let account = &accounts[0];
*accounts = &accounts[1..];
let pa = ProgramAccount::try_from_init(account)?;
if pa.inner.info.owner != program_id {
return Err(ProgramError::Custom(1)); // todo: proper error
return Err(ErrorCode::AccountNotProgramOwned.into());
}
Ok(pa)
}

View File

@ -1,3 +1,4 @@
use crate::error::ErrorCode;
use crate::{
AccountDeserialize, AccountSerialize, Accounts, AccountsExit, CpiAccount, ToAccountInfo,
ToAccountInfos, ToAccountMetas,
@ -59,20 +60,20 @@ where
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ProgramError::NotEnoughAccountKeys);
return Err(ErrorCode::AccountNotEnoughKeys.into());
}
let account = &accounts[0];
*accounts = &accounts[1..];
if account.key != &Self::address(program_id) {
solana_program::msg!("Invalid state address");
return Err(ProgramError::Custom(1)); // todo: proper error.
return Err(ErrorCode::StateInvalidAddress.into());
}
let pa = ProgramState::try_from(account)?;
if pa.inner.info.owner != program_id {
solana_program::msg!("Invalid state owner");
return Err(ProgramError::Custom(1)); // todo: proper error.
return Err(ErrorCode::AccountNotProgramOwned.into());
}
Ok(pa)
}

View File

@ -1,3 +1,4 @@
use crate::error::ErrorCode;
use crate::{Accounts, AccountsExit, ToAccountInfo, ToAccountInfos, ToAccountMetas};
use solana_program::account_info::AccountInfo;
use solana_program::entrypoint::ProgramResult;
@ -38,7 +39,7 @@ impl<'info, T: solana_program::sysvar::Sysvar> Accounts<'info> for Sysvar<'info,
accounts: &mut &[AccountInfo<'info>],
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ProgramError::NotEnoughAccountKeys);
return Err(ErrorCode::AccountNotEnoughKeys.into());
}
let account = &accounts[0];
*accounts = &accounts[1..];

View File

@ -130,7 +130,7 @@ pub fn generate_constraint_mut(f: &Field, _c: &ConstraintMut) -> proc_macro2::To
let ident = &f.ident;
quote! {
if !#ident.to_account_info().is_writable {
return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(36)); // todo: error codes
return Err(anchor_lang::__private::ErrorCode::ConstraintMut.into());
}
}
}
@ -147,7 +147,7 @@ pub fn generate_constraint_belongs_to(
};
quote! {
if &#field.#target != #target.to_account_info().key {
return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo: error codes
return Err(anchor_lang::__private::ErrorCode::ConstraintBelongsTo.into());
}
}
}
@ -167,7 +167,7 @@ pub fn generate_constraint_signer(f: &Field, _c: &ConstraintSigner) -> proc_macr
// This check will be performed on the other end of the invocation.
if cfg!(not(feature = "cpi")) {
if !#info.to_account_info().is_signer {
return Err(anchor_lang::solana_program::program_error::ProgramError::MissingRequiredSignature);
return Err(anchor_lang::__private::ErrorCode::ConstraintSigner.into());
}
}
}
@ -181,7 +181,7 @@ pub fn generate_constraint_literal(c: &ConstraintLiteral) -> proc_macro2::TokenS
};
quote! {
if !(#lit) {
return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo: error codes
return Err(anchor_lang::__private::ErrorCode::Deprecated.into());
}
}
}
@ -190,7 +190,7 @@ pub fn generate_constraint_raw(c: &ConstraintRaw) -> proc_macro2::TokenStream {
let raw = &c.raw;
quote! {
if !(#raw) {
return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(14)); // todo: error codes
return Err(anchor_lang::__private::ErrorCode::ConstraintRaw.into());
}
}
}
@ -200,7 +200,7 @@ pub fn generate_constraint_owner(f: &Field, c: &ConstraintOwner) -> proc_macro2:
let owner_target = c.owner_target.clone();
quote! {
if #ident.to_account_info().owner != #owner_target.to_account_info().key {
return Err(ProgramError::Custom(76)); // todo: proper error.
return Err(anchor_lang::__private::ErrorCode::ConstraintOwner.into());
}
}
}
@ -220,7 +220,7 @@ pub fn generate_constraint_rent_exempt(
ConstraintRentExempt::Skip => quote! {},
ConstraintRentExempt::Enforce => quote! {
if !rent.is_exempt(#info.lamports(), #info.try_data_len()?) {
return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(2)); // todo: error codes
return Err(anchor_lang::__private::ErrorCode::ConstraintRentExempt.into());
}
},
}
@ -233,9 +233,9 @@ pub fn generate_constraint_seeds(f: &Field, c: &ConstraintSeeds) -> proc_macro2:
let program_signer = Pubkey::create_program_address(
&[#seeds],
program_id,
).map_err(|_| anchor_lang::solana_program::program_error::ProgramError::Custom(1))?; // todo
).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?;
if #name.to_account_info().key != &program_signer {
return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo
return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
}
}
}
@ -247,7 +247,7 @@ pub fn generate_constraint_executable(
let name = &f.ident;
quote! {
if !#name.to_account_info().executable {
return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(5)) // todo
return Err(anchor_lang::__private::ErrorCode::ConstraintExecutable.into());
}
}
}
@ -263,10 +263,10 @@ pub fn generate_constraint_state(f: &Field, c: &ConstraintState) -> proc_macro2:
// Checks the given state account is the canonical state account for
// the target program.
if #ident.to_account_info().key != &anchor_lang::CpiState::<#account_ty>::address(#program_target.to_account_info().key) {
return Err(ProgramError::Custom(1)); // todo: proper error.
return Err(anchor_lang::__private::ErrorCode::ConstraintState.into());
}
if #ident.to_account_info().owner != #program_target.to_account_info().key {
return Err(ProgramError::Custom(1)); // todo: proper error.
return Err(anchor_lang::__private::ErrorCode::ConstraintState.into());
}
}
}
@ -371,7 +371,7 @@ pub fn generate_constraint_associated_init(
#associated_pubkey_and_nonce
if &__associated_field != #field.key {
return Err(ProgramError::Custom(45)); // todo: proper error.
return Err(anchor_lang::__private::ErrorCode::ConstraintAssociatedInit.into());
}
let lamports = rent.minimum_balance(space);
let ix = anchor_lang::solana_program::system_instruction::create_account(
@ -417,8 +417,7 @@ pub fn generate_constraint_associated_seeds(
quote! {
#generated_associated_pubkey_and_nonce
if #name.to_account_info().key != &__associated_field {
// TODO: proper error.
return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(45));
return Err(anchor_lang::__private::ErrorCode::ConstraintAssociated.into());
}
}
}

View File

@ -32,6 +32,14 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream {
})
.collect();
let offset = match error.args {
None => quote! { anchor_lang::__private::ERROR_CODE_OFFSET},
Some(args) => {
let offset = &args.offset;
quote! { #offset }
}
};
quote! {
/// Anchor generated Result to be used as the return type for the
/// program.
@ -40,15 +48,16 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream {
/// Anchor generated error allowing one to easily return a
/// `ProgramError` or a custom, user defined error code by utilizing
/// its `From` implementation.
#[doc(hidden)]
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
ProgramError(#[from] ProgramError),
ProgramError(#[from] anchor_lang::solana_program::program_error::ProgramError),
#[error(transparent)]
ErrorCode(#[from] #enum_name),
}
#[derive(Debug, Clone, Copy)]
#[derive(std::fmt::Debug, Clone, Copy)]
#[repr(u32)]
#error_enum
@ -62,19 +71,17 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream {
impl std::error::Error for #enum_name {}
impl std::convert::From<Error> for ProgramError {
fn from(e: Error) -> ProgramError {
// Errors 0-100 are reserved for the framework.
let error_offset = 100u32;
impl std::convert::From<Error> for anchor_lang::solana_program::program_error::ProgramError {
fn from(e: Error) -> anchor_lang::solana_program::program_error::ProgramError {
match e {
Error::ProgramError(e) => e,
Error::ErrorCode(c) => ProgramError::Custom(c as u32 + error_offset),
Error::ErrorCode(c) => anchor_lang::solana_program::program_error::ProgramError::Custom(c as u32 + #offset),
}
}
}
impl std::convert::From<#enum_name> for ProgramError {
fn from(e: #enum_name) -> ProgramError {
impl std::convert::From<#enum_name> for anchor_lang::solana_program::program_error::ProgramError {
fn from(e: #enum_name) -> anchor_lang::solana_program::program_error::ProgramError {
let err: Error = e.into();
err.into()
}

View File

@ -77,7 +77,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
let ix = {
let ix = instruction::#ix_variant;
let mut ix_data = AnchorSerialize::try_to_vec(&ix)
.map_err(|_| ProgramError::InvalidInstructionData)?;
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotSerialize)?;
let mut data = #sighash_tts.to_vec();
data.append(&mut ix_data);
let accounts = ctx.to_account_metas(None);

View File

@ -20,7 +20,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
quote! {
#sighash_tts => {
let ix = instruction::state::#ix_name::deserialize(&mut ix_data)
.map_err(|_| ProgramError::Custom(1))?; // todo: error code
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::state::#variant_arm = ix;
__private::__state::__ctor(program_id, accounts, #(#ctor_args),*)
}
@ -53,7 +53,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
quote! {
#sighash_tts => {
let ix = instruction::state::#ix_name::deserialize(&mut ix_data)
.map_err(|_| ProgramError::Custom(1))?; // todo: error code
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::state::#variant_arm = ix;
__private::__state::#ix_method_name(program_id, accounts, #(#ix_arg_names),*)
}
@ -109,7 +109,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
#sighash_tts => {
#args_struct
let ix = Args::deserialize(&mut ix_data)
.map_err(|_| ProgramError::Custom(1))?; // todo: error code
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
let Args {
#(#ix_arg_names),*
} = ix;
@ -139,7 +139,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
quote! {
#sighash_tts => {
let ix = instruction::#ix_name::deserialize(&mut ix_data)
.map_err(|_| ProgramError::Custom(1))?; // todo: error code
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::#variant_arm = ix;
__private::__global::#ix_method_name(program_id, accounts, #(#ix_arg_names),*)
}
@ -182,7 +182,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
#(#global_dispatch_arms)*
_ => {
msg!("Fallback functions are not supported. If you have a use case, please file an issue.");
Err(ProgramError::Custom(99))
Err(anchor_lang::__private::ErrorCode::InstructionFallbackNotFound.into())
}
}
}

View File

@ -52,7 +52,7 @@ pub fn generate(_program: &Program) -> proc_macro2::TokenStream {
msg!("anchor-debug is active");
}
if ix_data.len() < 8 {
return Err(ProgramError::Custom(99));
return Err(anchor_lang::__private::ErrorCode::InstructionMissing.into());
}
// Split the instruction data into the first 8 byte method

View File

@ -20,7 +20,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
let mut data: &[u8] = idl_ix_data;
let ix = anchor_lang::idl::IdlInstruction::deserialize(&mut data)
.map_err(|_| ProgramError::Custom(2))?; // todo
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
match ix {
anchor_lang::idl::IdlInstruction::Create { data_len } => {
@ -55,7 +55,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
#[inline(never)]
#[cfg(feature = "no-idl")]
pub fn __idl_dispatch(program_id: &Pubkey, accounts: &[AccountInfo], idl_ix_data: &[u8]) -> ProgramResult {
Err(anchor_lang::solana_program::program_error::ProgramError::Custom(99))
Err(anchor_lang::__private::ErrorCode::IdlInstructionStub.into())
}
// One time IDL account initializer. Will faill on subsequent
@ -67,7 +67,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
data_len: u64,
) -> ProgramResult {
if program_id != accounts.program.key {
return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(98)); // todo proper error
return Err(anchor_lang::__private::ErrorCode::IdlInstructionInvalidProgram.into());
}
// Create the IDL's account.
let from = accounts.from.key;
@ -336,7 +336,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
) -> ProgramResult {
let mut remaining_accounts: &[AccountInfo] = accounts;
if remaining_accounts.is_empty() {
return Err(ProgramError::Custom(1)); // todo
return Err(anchor_lang::__private::ErrorCode::AccountNotEnoughKeys.into());
}
let state_account = &remaining_accounts[0];
@ -374,7 +374,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
) -> ProgramResult {
let mut remaining_accounts: &[AccountInfo] = accounts;
if remaining_accounts.is_empty() {
return Err(ProgramError::Custom(1)); // todo
return Err(anchor_lang::__private::ErrorCode::AccountNotEnoughKeys.into());
}
// Deserialize the program state account.
@ -459,7 +459,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
let mut remaining_accounts: &[AccountInfo] = accounts;
if remaining_accounts.is_empty() {
return Err(ProgramError::Custom(1)); // todo
return Err(anchor_lang::__private::ErrorCode::AccountNotEnoughKeys.into());
}
// Deserialize the program state account.

View File

@ -11,6 +11,8 @@ use std::iter::FromIterator;
use std::path::Path;
const DERIVE_NAME: &str = "Accounts";
// TODO: sharee this with `anchor_lang` crate.
const ERROR_CODE_OFFSET: u32 = 300;
// Parse an entire interface file.
pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
@ -128,12 +130,12 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
}
},
};
let error = parse_error_enum(&f).map(|mut e| error::parse(&mut e));
let error = parse_error_enum(&f).map(|mut e| error::parse(&mut e, None));
let error_codes = error.as_ref().map(|e| {
e.codes
.iter()
.map(|code| IdlErrorCode {
code: 100 + code.id,
code: ERROR_CODE_OFFSET + code.id,
name: code.ident.to_string(),
msg: code.msg.clone(),
})

View File

@ -5,7 +5,8 @@ use parser::program as program_parser;
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use std::ops::Deref;
use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::ext::IdentExt;
use syn::parse::{Error as ParseError, Parse, ParseStream, Result as ParseResult};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{
@ -212,6 +213,26 @@ pub struct Error {
pub raw_enum: ItemEnum,
pub ident: Ident,
pub codes: Vec<ErrorCode>,
pub args: Option<ErrorArgs>,
}
#[derive(Debug)]
pub struct ErrorArgs {
pub offset: LitInt,
}
impl Parse for ErrorArgs {
fn parse(stream: ParseStream) -> ParseResult<Self> {
let offset_span = stream.span();
let offset = stream.call(Ident::parse_any)?;
if offset.to_string().as_str() != "offset" {
return Err(ParseError::new(offset_span, "expected keyword offset"));
}
stream.parse::<Token![=]>()?;
Ok(ErrorArgs {
offset: stream.parse()?,
})
}
}
#[derive(Debug)]

View File

@ -1,7 +1,7 @@
use crate::{Error, ErrorCode};
use crate::{Error, ErrorArgs, ErrorCode};
// Removes any internal #[msg] attributes, as they are inert.
pub fn parse(error_enum: &mut syn::ItemEnum) -> Error {
pub fn parse(error_enum: &mut syn::ItemEnum, args: Option<ErrorArgs>) -> Error {
let ident = error_enum.ident.clone();
let mut last_discriminant = 0;
let codes: Vec<ErrorCode> = error_enum
@ -30,12 +30,12 @@ pub fn parse(error_enum: &mut syn::ItemEnum) -> Error {
ErrorCode { id, ident, msg }
})
.collect();
Error {
name: error_enum.ident.to_string(),
raw_enum: error_enum.clone(),
ident,
codes,
args,
}
}

View File

@ -22,7 +22,7 @@
},
"dependencies": {
"@project-serum/borsh": "^0.2.2",
"@solana/web3.js": "^1.11.0",
"@solana/web3.js": "^1.17.0",
"base64-js": "^1.5.1",
"bn.js": "^5.1.2",
"bs58": "^4.0.1",

View File

@ -6,7 +6,165 @@ export class ProgramError extends Error {
super(...params);
}
public static parse(
err: any,
idlErrors: Map<number, string>
): ProgramError | null {
// TODO: don't rely on the error string. web3.js should preserve the error
// code information instead of giving us an untyped string.
let components = err.toString().split("custom program error: ");
if (components.length !== 2) {
return null;
}
let errorCode: number;
try {
errorCode = parseInt(components[1]);
} catch (parseErr) {
return null;
}
// Parse user error.
let errorMsg = idlErrors.get(errorCode);
if (errorMsg !== undefined) {
return new ProgramError(errorCode, errorMsg);
}
// Parse framework internal error.
errorMsg = LangErrorMessage.get(errorCode);
if (errorMsg !== undefined) {
return new ProgramError(errorCode, errorMsg);
}
// Unable to parse the error. Just return the untranslated error.
return null;
}
public toString(): string {
return this.msg;
}
}
const LangErrorCode = {
// Instructions.
InstructionMissing: 100,
InstructionFallbackNotFound: 101,
InstructionDidNotDeserialize: 102,
InstructionDidNotSerialize: 103,
// IDL instructions.
IdlInstructionStub: 120,
IdlInstructionInvalidProgram: 121,
// Constraints.
ConstraintMut: 140,
ConstraintBelongsTo: 141,
ConstraintSigner: 142,
ConstraintRaw: 143,
ConstraintOwner: 144,
ConstraintRentExempt: 145,
ConstraintSeeds: 146,
ConstraintExecutable: 147,
ConstraintState: 148,
ConstraintAssociated: 149,
ConstraintAssociatedInit: 150,
// Accounts.
AccountDiscriminatorAlreadySet: 160,
AccountDiscriminatorNotFound: 161,
AccountDiscriminatorMismatch: 162,
AccountDidNotDeserialize: 163,
AccountDidNotSerialize: 164,
AccountNotEnoughKeys: 165,
AccountNotMutable: 166,
AccountNotProgramOwned: 167,
// State.
StateInvalidAddress: 180,
// Used for APIs that shouldn't be used anymore.
Deprecated: 299,
};
const LangErrorMessage = new Map([
// Instructions.
[
LangErrorCode.InstructionMissing,
"8 byte instruction identifier not provided",
],
[
LangErrorCode.InstructionFallbackNotFound,
"Fallback functions are not supported",
],
[
LangErrorCode.InstructionDidNotDeserialize,
"The program could not deserialize the given instruction",
],
[
LangErrorCode.InstructionDidNotSerialize,
"The program could not serialize the given instruction",
],
// Idl instructions.
[
LangErrorCode.IdlInstructionStub,
"The program was compiled without idl instructions",
],
[
LangErrorCode.IdlInstructionInvalidProgram,
"The transaction was given an invalid program for the IDL instruction",
],
// Constraints.
[LangErrorCode.ConstraintMut, "A mut constraint was violated"],
[LangErrorCode.ConstraintBelongsTo, "A belongs_to constraint was violated"],
[LangErrorCode.ConstraintSigner, "A signer constraint was violated"],
[LangErrorCode.ConstraintRaw, "A raw constraint as violated"],
[LangErrorCode.ConstraintOwner, "An owner constraint was violated"],
[LangErrorCode.ConstraintRentExempt, "A rent exempt constraint was violated"],
[LangErrorCode.ConstraintSeeds, "A seeds constraint was violated"],
[LangErrorCode.ConstraintExecutable, "An executable constraint was violated"],
[LangErrorCode.ConstraintState, "A state constraint was violated"],
[LangErrorCode.ConstraintAssociated, "An associated constraint was violated"],
[
LangErrorCode.ConstraintAssociatedInit,
"An associated init constraint was violated",
],
// Accounts.
[
LangErrorCode.AccountDiscriminatorAlreadySet,
"The account discriminator was already set on this account",
],
[
LangErrorCode.AccountDiscriminatorNotFound,
"No 8 byte discriminator was found on the account",
],
[
LangErrorCode.AccountDiscriminatorMismatch,
"8 byte discriminator did not match what was expected",
],
[LangErrorCode.AccountDidNotDeserialize, "Failed to deserialize the account"],
[LangErrorCode.AccountDidNotSerialize, "Failed to serialize the account"],
[
LangErrorCode.AccountNotEnoughKeys,
"Not enough account keys given to the instruction",
],
[LangErrorCode.AccountNotMutable, "The given account is not mutable"],
[
LangErrorCode.AccountNotProgramOwned,
"The given account is not owned by the executing program",
],
// State.
[
LangErrorCode.StateInvalidAddress,
"The given state account does not have the correct address",
],
// Misc.
[
LangErrorCode.Deprecated,
"The API being used is deprecated and should no longer be used",
],
]);

View File

@ -1,7 +1,6 @@
import EventEmitter from "eventemitter3";
import { PublicKey } from "@solana/web3.js";
import { Idl, IdlInstruction, IdlAccountItem, IdlStateMethod } from "../idl";
import { ProgramError } from "../error";
import { Accounts } from "./context";
export type Subscription = {
@ -56,29 +55,6 @@ export function validateAccounts(
});
}
export function translateError(
idlErrors: Map<number, string>,
err: any
): Error | null {
// TODO: don't rely on the error string. web3.js should preserve the error
// code information instead of giving us an untyped string.
let components = err.toString().split("custom program error: ");
if (components.length === 2) {
try {
const errorCode = parseInt(components[1]);
let errorMsg = idlErrors.get(errorCode);
if (errorMsg === undefined) {
// Unexpected error code so just throw the untranslated error.
return null;
}
return new ProgramError(errorCode, errorMsg);
} catch (parseErr) {
// Unable to parse the error. Just return the untranslated error.
return null;
}
}
}
// Translates an address to a Pubkey.
export function translateAddress(address: Address): PublicKey {
if (typeof address === "string") {

View File

@ -1,9 +1,9 @@
import { TransactionSignature } from "@solana/web3.js";
import Provider from "../../provider";
import { IdlInstruction } from "../../idl";
import { translateError } from "../common";
import { splitArgsAndCtx } from "../context";
import { TransactionFn } from "./transaction";
import { ProgramError } from "../../error";
export default class RpcFactory {
public static build(
@ -20,7 +20,7 @@ export default class RpcFactory {
return txSig;
} catch (err) {
console.log("Translating error", err);
let translatedErr = translateError(idlErrors, err);
let translatedErr = ProgramError.parse(err, idlErrors);
if (translatedErr === null) {
throw err;
}

View File

@ -1,12 +1,12 @@
import { PublicKey } from "@solana/web3.js";
import Provider from "../../provider";
import { IdlInstruction } from "../../idl";
import { translateError } from "../common";
import { splitArgsAndCtx } from "../context";
import { TransactionFn } from "./transaction";
import { EventParser } from "../event";
import Coder from "../../coder";
import { Idl } from "../../idl";
import { ProgramError } from "../../error";
export default class SimulateFactory {
public static build(
@ -26,7 +26,7 @@ export default class SimulateFactory {
resp = await provider.simulate(tx, ctx.signers, ctx.options);
} catch (err) {
console.log("Translating error", err);
let translatedErr = translateError(idlErrors, err);
let translatedErr = ProgramError.parse(err, idlErrors);
if (translatedErr === null) {
throw err;
}

View File

@ -676,13 +676,14 @@
dependencies:
"@sinonjs/commons" "^1.7.0"
"@solana/web3.js@^1.11.0":
version "1.11.0"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.11.0.tgz#1cc9a25381687c82e444ad0633f028e050a06753"
integrity sha512-kmngWxntzp0HNhWInd7/3g2uqxdOrahvaHOyjilcRe+WCiC777gERz3+eIAbxIYx2zAZPjy02MZzLgoRHccZoQ==
"@solana/web3.js@^1.17.0":
version "1.17.0"
resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.17.0.tgz#51775bd17af77132450c22ac175870d4a9721b9b"
integrity sha512-PBOHY260CudciLwBgwt1U8upwCS1Jq0BbS6EVyX0tz6Tj14Dp4i87dQNyntentNiGQQ+yWBIk4vJEm+PMCSd/A==
dependencies:
"@babel/runtime" "^7.12.5"
bn.js "^5.0.0"
borsh "^0.4.0"
bs58 "^4.0.1"
buffer "6.0.1"
buffer-layout "^1.2.0"
@ -728,7 +729,7 @@
dependencies:
"@babel/types" "^7.3.0"
"@types/bn.js@^4.11.6":
"@types/bn.js@^4.11.5", "@types/bn.js@^4.11.6":
version "4.11.6"
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c"
integrity sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==
@ -1261,6 +1262,16 @@ bn.js@^5.1.2:
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.3.tgz#beca005408f642ebebea80b042b4d18d2ac0ee6b"
integrity sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==
borsh@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/borsh/-/borsh-0.4.0.tgz#9dd6defe741627f1315eac2a73df61421f6ddb9f"
integrity sha512-aX6qtLya3K0AkT66CmYWCCDr77qsE9arV05OmdFpmat9qu8Pg9J5tBUPDztAW5fNh/d/MyVG/OYziP52Ndzx1g==
dependencies:
"@types/bn.js" "^4.11.5"
bn.js "^5.0.0"
bs58 "^4.0.0"
text-encoding-utf-8 "^1.0.2"
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@ -1309,7 +1320,7 @@ bs-logger@0.x:
dependencies:
fast-json-stable-stringify "2.x"
bs58@^4.0.1:
bs58@^4.0.0, bs58@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a"
integrity sha1-vhYedsNU9veIrkBx9j806MTwpCo=
@ -5035,6 +5046,11 @@ test-exclude@^6.0.0:
glob "^7.1.4"
minimatch "^3.0.4"
text-encoding-utf-8@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz#585b62197b0ae437e3c7b5d0af27ac1021e10d13"
integrity sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==
text-extensions@^1.0.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26"