Skip to content

Commit

Permalink
lang: Framework defined error codes
Browse files Browse the repository at this point in the history
  • Loading branch information
armaniferrante committed Jun 9, 2021
1 parent f8909ef commit 339da4f
Show file tree
Hide file tree
Showing 30 changed files with 488 additions and 158 deletions.
38 changes: 38 additions & 0 deletions examples/errors/programs/errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use anchor_lang::prelude::*;
#[program]
mod errors {
use super::*;

pub fn hello(_ctx: Context<Hello>) -> Result<()> {
Err(MyError::Hello.into())
}
Expand All @@ -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")]
Expand Down
75 changes: 72 additions & 3 deletions examples/errors/tests/errors.js
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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);
}
});

Expand All @@ -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);
}
});

Expand All @@ -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);
}
});
});
20 changes: 10 additions & 10 deletions lang/attribute/account/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -144,32 +144,32 @@ 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(())
}
}

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)
}

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())
}
}

Expand Down Expand Up @@ -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
})
}
9 changes: 7 additions & 2 deletions lang/attribute/error/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
12 changes: 2 additions & 10 deletions lang/attribute/interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion lang/attribute/state/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
7 changes: 4 additions & 3 deletions lang/src/account_info.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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..];
Expand All @@ -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];
Expand All @@ -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())
Expand Down
3 changes: 2 additions & 1 deletion lang/src/cpi_account.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::error::ErrorCode;
use crate::{
AccountDeserialize, Accounts, AccountsExit, ToAccountInfo, ToAccountInfos, ToAccountMetas,
};
Expand Down Expand Up @@ -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..];
Expand Down
3 changes: 2 additions & 1 deletion lang/src/cpi_state.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::error::ErrorCode;
use crate::{
AccountDeserialize, AccountSerialize, Accounts, AccountsExit, CpiStateContext, ProgramState,
ToAccountInfo, ToAccountInfos, ToAccountMetas,
Expand Down Expand Up @@ -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..];
Expand Down
Loading

0 comments on commit 339da4f

Please sign in to comment.