lang: add support for logging expected and actual values and pubkeys (#1572)
This commit is contained in:
parent
905968d5ab
commit
721fe6693c
|
@ -33,7 +33,8 @@ incremented for features.
|
|||
|
||||
* ts: Mark `transaction`, `instruction`, `simulate` and `rpc` program namespaces as deprecated in favor of `methods` ([#1539](https://github.com/project-serum/anchor/pull/1539)).
|
||||
* ts: No longer allow manual setting of globally resolvable program public keys in `methods#accounts()`. ([#1548][https://github.com/project-serum/anchor/pull/1548])
|
||||
* lang: Remove space calculation using [`#[derive(Default)]`] (https://github.com/project-serum/anchor/pull/1519).
|
||||
* lang: Remove space calculation using `#[derive(Default)]` ([#1519](https://github.com/project-serum/anchor/pull/1519)).
|
||||
* lang: Add support for logging expected and actual values and pubkeys. Add `require_eq` and `require_keys_eq` macros. Add default error code to `require` macro ([#1572](https://github.com/project-serum/anchor/pull/1572)).
|
||||
|
||||
## [0.22.1] - 2022-02-28
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@ extern crate proc_macro;
|
|||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
use anchor_syn::parser::error::{self as error_parser, ErrorWithAccountNameInput};
|
||||
use anchor_syn::codegen;
|
||||
use anchor_syn::parser::error::{self as error_parser, ErrorInput};
|
||||
use anchor_syn::ErrorArgs;
|
||||
use anchor_syn::{codegen, parser::error::ErrorInput};
|
||||
use syn::{parse_macro_input, Expr};
|
||||
|
||||
/// Generates `Error` and `type Result<T> = Result<T, Error>` types to be
|
||||
|
@ -88,39 +88,28 @@ pub fn error(ts: proc_macro::TokenStream) -> TokenStream {
|
|||
create_error(error_code, true, None)
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn error_with_account_name(ts: proc_macro::TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(ts as ErrorWithAccountNameInput);
|
||||
let error_code = input.error_code;
|
||||
let account_name = input.account_name;
|
||||
create_error(error_code, false, Some(account_name))
|
||||
}
|
||||
|
||||
fn create_error(error_code: Expr, source: bool, account_name: Option<Expr>) -> TokenStream {
|
||||
let source = if source {
|
||||
quote! {
|
||||
Some(anchor_lang::error::Source {
|
||||
let error_origin = match (source, account_name) {
|
||||
(false, None) => quote! { None },
|
||||
(false, Some(account_name)) => quote! {
|
||||
Some(anchor_lang::error::ErrorOrigin::AccountName(#account_name.to_string()))
|
||||
},
|
||||
(true, _) => quote! {
|
||||
Some(anchor_lang::error::ErrorOrigin::Source(anchor_lang::error::Source {
|
||||
filename: file!(),
|
||||
line: line!()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
None
|
||||
}
|
||||
};
|
||||
let account_name = match account_name {
|
||||
Some(_) => quote! { Some(#account_name.to_string()) },
|
||||
None => quote! { None },
|
||||
}))
|
||||
},
|
||||
};
|
||||
|
||||
TokenStream::from(quote! {
|
||||
anchor_lang::error::Error::from(
|
||||
anchor_lang::error::AnchorError {
|
||||
error_name: #error_code.name(),
|
||||
error_code_number: #error_code.into(),
|
||||
error_msg: #error_code.to_string(),
|
||||
source: #source,
|
||||
account_name: #account_name
|
||||
error_origin: #error_origin,
|
||||
compared_values: None
|
||||
}
|
||||
)
|
||||
})
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Account container that checks ownership on deserialization.
|
||||
|
||||
use crate::bpf_writer::BpfWriter;
|
||||
use crate::error::ErrorCode;
|
||||
use crate::error::{Error, ErrorCode};
|
||||
use crate::{
|
||||
AccountDeserialize, AccountSerialize, Accounts, AccountsClose, AccountsExit, Key, Owner,
|
||||
Result, ToAccountInfo, ToAccountInfos, ToAccountMetas,
|
||||
|
@ -251,7 +251,8 @@ impl<'a, T: AccountSerialize + AccountDeserialize + crate::Owner + Clone> Accoun
|
|||
return Err(ErrorCode::AccountNotInitialized.into());
|
||||
}
|
||||
if info.owner != &T::owner() {
|
||||
return Err(ErrorCode::AccountOwnedByWrongProgram.into());
|
||||
return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
|
||||
.with_pubkeys((*info.owner, T::owner())));
|
||||
}
|
||||
let mut data: &[u8] = &info.try_borrow_data()?;
|
||||
Ok(Account::new(info.clone(), T::try_deserialize(&mut data)?))
|
||||
|
@ -266,7 +267,8 @@ impl<'a, T: AccountSerialize + AccountDeserialize + crate::Owner + Clone> Accoun
|
|||
return Err(ErrorCode::AccountNotInitialized.into());
|
||||
}
|
||||
if info.owner != &T::owner() {
|
||||
return Err(ErrorCode::AccountOwnedByWrongProgram.into());
|
||||
return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
|
||||
.with_pubkeys((*info.owner, T::owner())));
|
||||
}
|
||||
let mut data: &[u8] = &info.try_borrow_data()?;
|
||||
Ok(Account::new(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Type facilitating on demand zero copy deserialization.
|
||||
|
||||
use crate::bpf_writer::BpfWriter;
|
||||
use crate::error::ErrorCode;
|
||||
use crate::error::{Error, ErrorCode};
|
||||
use crate::{
|
||||
Accounts, AccountsClose, AccountsExit, Key, Owner, Result, ToAccountInfo, ToAccountInfos,
|
||||
ToAccountMetas, ZeroCopy,
|
||||
|
@ -120,7 +120,8 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> {
|
|||
#[inline(never)]
|
||||
pub fn try_from(acc_info: &AccountInfo<'info>) -> Result<AccountLoader<'info, T>> {
|
||||
if acc_info.owner != &T::owner() {
|
||||
return Err(ErrorCode::AccountOwnedByWrongProgram.into());
|
||||
return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
|
||||
.with_pubkeys((*acc_info.owner, T::owner())));
|
||||
}
|
||||
let data: &[u8] = &acc_info.try_borrow_data()?;
|
||||
// Discriminator must match.
|
||||
|
@ -139,7 +140,8 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> {
|
|||
acc_info: &AccountInfo<'info>,
|
||||
) -> Result<AccountLoader<'info, T>> {
|
||||
if acc_info.owner != &T::owner() {
|
||||
return Err(ErrorCode::AccountOwnedByWrongProgram.into());
|
||||
return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
|
||||
.with_pubkeys((*acc_info.owner, T::owner())));
|
||||
}
|
||||
Ok(AccountLoader::new(acc_info.clone()))
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::bpf_writer::BpfWriter;
|
||||
use crate::error::ErrorCode;
|
||||
use crate::error::{Error, ErrorCode};
|
||||
use crate::{
|
||||
Accounts, AccountsClose, AccountsExit, Key, Result, ToAccountInfo, ToAccountInfos,
|
||||
ToAccountMetas, ZeroCopy,
|
||||
|
@ -59,7 +59,8 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
|
|||
acc_info: &AccountInfo<'info>,
|
||||
) -> Result<Loader<'info, T>> {
|
||||
if acc_info.owner != program_id {
|
||||
return Err(ErrorCode::AccountOwnedByWrongProgram.into());
|
||||
return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
|
||||
.with_pubkeys((*acc_info.owner, *program_id)));
|
||||
}
|
||||
let data: &[u8] = &acc_info.try_borrow_data()?;
|
||||
// Discriminator must match.
|
||||
|
@ -79,7 +80,8 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
|
|||
acc_info: &AccountInfo<'info>,
|
||||
) -> Result<Loader<'info, T>> {
|
||||
if acc_info.owner != program_id {
|
||||
return Err(ErrorCode::AccountOwnedByWrongProgram.into());
|
||||
return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
|
||||
.with_pubkeys((*acc_info.owner, *program_id)));
|
||||
}
|
||||
Ok(Loader::new(acc_info.clone()))
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
//! Type validating that the account is the given Program
|
||||
use crate::error::ErrorCode;
|
||||
|
||||
use crate::error::{Error, ErrorCode};
|
||||
use crate::{
|
||||
AccountDeserialize, Accounts, AccountsExit, Id, Key, Result, ToAccountInfos, ToAccountMetas,
|
||||
};
|
||||
|
@ -102,7 +103,7 @@ impl<'a, T: Id + Clone> Program<'a, T> {
|
|||
#[inline(never)]
|
||||
pub fn try_from(info: &AccountInfo<'a>) -> Result<Program<'a, T>> {
|
||||
if info.key != &T::id() {
|
||||
return Err(ErrorCode::InvalidProgramId.into());
|
||||
return Err(Error::from(ErrorCode::InvalidProgramId).with_pubkeys((*info.key, T::id())));
|
||||
}
|
||||
if !info.executable {
|
||||
return Err(ErrorCode::InvalidProgramExecutable.into());
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#[allow(deprecated)]
|
||||
use crate::accounts::cpi_account::CpiAccount;
|
||||
use crate::bpf_writer::BpfWriter;
|
||||
use crate::error::ErrorCode;
|
||||
use crate::error::{Error, ErrorCode};
|
||||
use crate::{
|
||||
AccountDeserialize, AccountSerialize, Accounts, AccountsClose, AccountsExit, Key, Result,
|
||||
ToAccountInfo, ToAccountInfos, ToAccountMetas,
|
||||
|
@ -38,7 +38,8 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramAccount<'a, T>
|
|||
#[inline(never)]
|
||||
pub fn try_from(program_id: &Pubkey, info: &AccountInfo<'a>) -> Result<ProgramAccount<'a, T>> {
|
||||
if info.owner != program_id {
|
||||
return Err(ErrorCode::AccountOwnedByWrongProgram.into());
|
||||
return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
|
||||
.with_pubkeys((*info.owner, *program_id)));
|
||||
}
|
||||
let mut data: &[u8] = &info.try_borrow_data()?;
|
||||
Ok(ProgramAccount::new(
|
||||
|
@ -56,7 +57,8 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramAccount<'a, T>
|
|||
info: &AccountInfo<'a>,
|
||||
) -> Result<ProgramAccount<'a, T>> {
|
||||
if info.owner != program_id {
|
||||
return Err(ErrorCode::AccountOwnedByWrongProgram.into());
|
||||
return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
|
||||
.with_pubkeys((*info.owner, *program_id)));
|
||||
}
|
||||
let mut data: &[u8] = &info.try_borrow_data()?;
|
||||
Ok(ProgramAccount::new(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#[allow(deprecated)]
|
||||
use crate::accounts::cpi_account::CpiAccount;
|
||||
use crate::bpf_writer::BpfWriter;
|
||||
use crate::error::ErrorCode;
|
||||
use crate::error::{Error, ErrorCode};
|
||||
use crate::{
|
||||
AccountDeserialize, AccountSerialize, Accounts, AccountsExit, Key, Result, ToAccountInfo,
|
||||
ToAccountInfos, ToAccountMetas,
|
||||
|
@ -40,7 +40,8 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramState<'a, T> {
|
|||
#[inline(never)]
|
||||
pub fn try_from(program_id: &Pubkey, info: &AccountInfo<'a>) -> Result<ProgramState<'a, T>> {
|
||||
if info.owner != program_id {
|
||||
return Err(ErrorCode::AccountOwnedByWrongProgram.into());
|
||||
return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
|
||||
.with_pubkeys((*info.owner, *program_id)));
|
||||
}
|
||||
if info.key != &Self::address(program_id) {
|
||||
solana_program::msg!("Invalid state address");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use anchor_attribute_error::error_code;
|
||||
use borsh::maybestd::io::Error as BorshIoError;
|
||||
use solana_program::program_error::ProgramError;
|
||||
use solana_program::{program_error::ProgramError, pubkey::Pubkey};
|
||||
use std::fmt::{Debug, Display};
|
||||
|
||||
/// The starting point for user defined error codes.
|
||||
|
@ -106,6 +106,17 @@ pub enum ErrorCode {
|
|||
#[msg("A space constraint was violated")]
|
||||
ConstraintSpace,
|
||||
|
||||
// Require
|
||||
/// 2500 - A require expression was violated
|
||||
#[msg("A require expression was violated")]
|
||||
RequireViolated = 2500,
|
||||
/// 2501 - A require_eq expression was violated
|
||||
#[msg("A require_eq expression was violated")]
|
||||
RequireEqViolated,
|
||||
/// 2502 - A require_keys_eq expression was violated
|
||||
#[msg("A require_keys_eq expression was violated")]
|
||||
RequireKeysEqViolated,
|
||||
|
||||
// Accounts.
|
||||
/// 3000 - The account discriminator was already set on this account
|
||||
#[msg("The account discriminator was already set on this account")]
|
||||
|
@ -189,12 +200,6 @@ impl Display for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for AnchorError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Debug::fmt(&self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AnchorError> for Error {
|
||||
fn from(ae: AnchorError) -> Self {
|
||||
Self::AnchorError(ae)
|
||||
|
@ -228,16 +233,51 @@ impl Error {
|
|||
|
||||
pub fn with_account_name(mut self, account_name: impl ToString) -> Self {
|
||||
match &mut self {
|
||||
Error::AnchorError(ae) => ae.account_name = Some(account_name.to_string()),
|
||||
Error::ProgramError(pe) => pe.account_name = Some(account_name.to_string()),
|
||||
Error::AnchorError(ae) => {
|
||||
ae.error_origin = Some(ErrorOrigin::AccountName(account_name.to_string()));
|
||||
}
|
||||
Error::ProgramError(pe) => {
|
||||
pe.error_origin = Some(ErrorOrigin::AccountName(account_name.to_string()));
|
||||
}
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_source(mut self, source: Source) -> Self {
|
||||
match &mut self {
|
||||
Error::AnchorError(ae) => ae.source = Some(source),
|
||||
Error::ProgramError(pe) => pe.source = Some(source),
|
||||
Error::AnchorError(ae) => {
|
||||
ae.error_origin = Some(ErrorOrigin::Source(source));
|
||||
}
|
||||
Error::ProgramError(pe) => {
|
||||
pe.error_origin = Some(ErrorOrigin::Source(source));
|
||||
}
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_pubkeys(mut self, pubkeys: (Pubkey, Pubkey)) -> Self {
|
||||
let pubkeys = Some(ComparedValues::Pubkeys((pubkeys.0, pubkeys.1)));
|
||||
match &mut self {
|
||||
Error::AnchorError(ae) => ae.compared_values = pubkeys,
|
||||
Error::ProgramError(pe) => pe.compared_values = pubkeys,
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_values(mut self, values: (impl ToString, impl ToString)) -> Self {
|
||||
match &mut self {
|
||||
Error::AnchorError(ae) => {
|
||||
ae.compared_values = Some(ComparedValues::Values((
|
||||
values.0.to_string(),
|
||||
values.1.to_string(),
|
||||
)))
|
||||
}
|
||||
Error::ProgramError(pe) => {
|
||||
pe.compared_values = Some(ComparedValues::Values((
|
||||
values.0.to_string(),
|
||||
values.1.to_string(),
|
||||
)))
|
||||
}
|
||||
};
|
||||
self
|
||||
}
|
||||
|
@ -246,8 +286,8 @@ impl Error {
|
|||
#[derive(Debug)]
|
||||
pub struct ProgramErrorWithOrigin {
|
||||
pub program_error: ProgramError,
|
||||
pub source: Option<Source>,
|
||||
pub account_name: Option<String>,
|
||||
pub error_origin: Option<ErrorOrigin>,
|
||||
pub compared_values: Option<ComparedValues>,
|
||||
}
|
||||
|
||||
impl Display for ProgramErrorWithOrigin {
|
||||
|
@ -258,31 +298,59 @@ impl Display for ProgramErrorWithOrigin {
|
|||
|
||||
impl ProgramErrorWithOrigin {
|
||||
pub fn log(&self) {
|
||||
if let Some(source) = &self.source {
|
||||
anchor_lang::solana_program::msg!(
|
||||
"ProgramError thrown in {}:{}. Error Code: {:?}. Error Number: {}. Error Message: {}.",
|
||||
source.filename,
|
||||
source.line,
|
||||
self.program_error,
|
||||
u64::from(self.program_error.clone()),
|
||||
self.program_error
|
||||
);
|
||||
} else if let Some(account_name) = &self.account_name {
|
||||
anchor_lang::solana_program::log::sol_log(&format!(
|
||||
"ProgramError caused by account: {}. Error Code: {:?}. Error Number: {}. Error Message: {}.",
|
||||
account_name,
|
||||
self.program_error,
|
||||
u64::from(self.program_error.clone()),
|
||||
self.program_error
|
||||
));
|
||||
} else {
|
||||
anchor_lang::solana_program::log::sol_log(&format!(
|
||||
"ProgramError occurred. Error Code: {:?}. Error Number: {}. Error Message: {}.",
|
||||
self.program_error,
|
||||
u64::from(self.program_error.clone()),
|
||||
self.program_error
|
||||
));
|
||||
match &self.error_origin {
|
||||
None => {
|
||||
anchor_lang::solana_program::msg!(
|
||||
"ProgramError occurred. Error Code: {:?}. Error Number: {}. Error Message: {}.",
|
||||
self.program_error,
|
||||
u64::from(self.program_error.clone()),
|
||||
self.program_error
|
||||
);
|
||||
}
|
||||
Some(ErrorOrigin::Source(source)) => {
|
||||
anchor_lang::solana_program::msg!(
|
||||
"ProgramError thrown in {}:{}. Error Code: {:?}. Error Number: {}. Error Message: {}.",
|
||||
source.filename,
|
||||
source.line,
|
||||
self.program_error,
|
||||
u64::from(self.program_error.clone()),
|
||||
self.program_error
|
||||
);
|
||||
}
|
||||
Some(ErrorOrigin::AccountName(account_name)) => {
|
||||
// using sol_log because msg! wrongly interprets 5 inputs as u64
|
||||
anchor_lang::solana_program::log::sol_log(&format!(
|
||||
"ProgramError caused by account: {}. Error Code: {:?}. Error Number: {}. Error Message: {}.",
|
||||
account_name,
|
||||
self.program_error,
|
||||
u64::from(self.program_error.clone()),
|
||||
self.program_error
|
||||
));
|
||||
}
|
||||
}
|
||||
match &self.compared_values {
|
||||
Some(ComparedValues::Pubkeys((left, right))) => {
|
||||
anchor_lang::solana_program::msg!("Left:");
|
||||
left.log();
|
||||
anchor_lang::solana_program::msg!("Right:");
|
||||
right.log();
|
||||
}
|
||||
Some(ComparedValues::Values((left, right))) => {
|
||||
anchor_lang::solana_program::msg!("Left: {}", left);
|
||||
anchor_lang::solana_program::msg!("Right: {}", right);
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_source(mut self, source: Source) -> Self {
|
||||
self.error_origin = Some(ErrorOrigin::Source(source));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_account_name(mut self, account_name: impl ToString) -> Self {
|
||||
self.error_origin = Some(ErrorOrigin::AccountName(account_name.to_string()));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -290,46 +358,91 @@ impl From<ProgramError> for ProgramErrorWithOrigin {
|
|||
fn from(program_error: ProgramError) -> Self {
|
||||
Self {
|
||||
program_error,
|
||||
source: None,
|
||||
account_name: None,
|
||||
error_origin: None,
|
||||
compared_values: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ComparedValues {
|
||||
Values((String, String)),
|
||||
Pubkeys((Pubkey, Pubkey)),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ErrorOrigin {
|
||||
Source(Source),
|
||||
AccountName(String),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AnchorError {
|
||||
pub error_name: String,
|
||||
pub error_code_number: u32,
|
||||
pub error_msg: String,
|
||||
pub source: Option<Source>,
|
||||
pub account_name: Option<String>,
|
||||
pub error_origin: Option<ErrorOrigin>,
|
||||
pub compared_values: Option<ComparedValues>,
|
||||
}
|
||||
|
||||
impl AnchorError {
|
||||
pub fn log(&self) {
|
||||
if let Some(source) = &self.source {
|
||||
anchor_lang::solana_program::msg!(
|
||||
"AnchorError thrown in {}:{}. Error Code: {}. Error Number: {}. Error Message: {}.",
|
||||
source.filename,
|
||||
source.line,
|
||||
self.error_name,
|
||||
self.error_code_number,
|
||||
self.error_msg
|
||||
);
|
||||
} else if let Some(account_name) = &self.account_name {
|
||||
anchor_lang::solana_program::log::sol_log(&format!(
|
||||
"AnchorError caused by account: {}. Error Code: {}. Error Number: {}. Error Message: {}.",
|
||||
account_name,
|
||||
self.error_name,
|
||||
self.error_code_number,
|
||||
self.error_msg
|
||||
));
|
||||
} else {
|
||||
anchor_lang::solana_program::log::sol_log(&format!(
|
||||
"AnchorError occurred. Error Code: {}. Error Number: {}. Error Message: {}.",
|
||||
self.error_name, self.error_code_number, self.error_msg
|
||||
));
|
||||
match &self.error_origin {
|
||||
None => {
|
||||
anchor_lang::solana_program::log::sol_log(&format!(
|
||||
"AnchorError occurred. Error Code: {}. Error Number: {}. Error Message: {}.",
|
||||
self.error_name, self.error_code_number, self.error_msg
|
||||
));
|
||||
}
|
||||
Some(ErrorOrigin::Source(source)) => {
|
||||
anchor_lang::solana_program::msg!(
|
||||
"AnchorError thrown in {}:{}. Error Code: {}. Error Number: {}. Error Message: {}.",
|
||||
source.filename,
|
||||
source.line,
|
||||
self.error_name,
|
||||
self.error_code_number,
|
||||
self.error_msg
|
||||
);
|
||||
}
|
||||
Some(ErrorOrigin::AccountName(account_name)) => {
|
||||
anchor_lang::solana_program::log::sol_log(&format!(
|
||||
"AnchorError caused by account: {}. Error Code: {}. Error Number: {}. Error Message: {}.",
|
||||
account_name,
|
||||
self.error_name,
|
||||
self.error_code_number,
|
||||
self.error_msg
|
||||
));
|
||||
}
|
||||
}
|
||||
match &self.compared_values {
|
||||
Some(ComparedValues::Pubkeys((left, right))) => {
|
||||
anchor_lang::solana_program::msg!("Left:");
|
||||
left.log();
|
||||
anchor_lang::solana_program::msg!("Right:");
|
||||
right.log();
|
||||
}
|
||||
Some(ComparedValues::Values((left, right))) => {
|
||||
anchor_lang::solana_program::msg!("Left: {}", left);
|
||||
anchor_lang::solana_program::msg!("Right: {}", right);
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_source(mut self, source: Source) -> Self {
|
||||
self.error_origin = Some(ErrorOrigin::Source(source));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_account_name(mut self, account_name: impl ToString) -> Self {
|
||||
self.error_origin = Some(ErrorOrigin::AccountName(account_name.to_string()));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AnchorError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Debug::fmt(&self, f)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -337,11 +450,7 @@ impl std::convert::From<Error> for anchor_lang::solana_program::program_error::P
|
|||
fn from(e: Error) -> anchor_lang::solana_program::program_error::ProgramError {
|
||||
match e {
|
||||
Error::AnchorError(AnchorError {
|
||||
error_name: _,
|
||||
error_code_number,
|
||||
error_msg: _,
|
||||
source: _,
|
||||
account_name: _,
|
||||
error_code_number, ..
|
||||
}) => {
|
||||
anchor_lang::solana_program::program_error::ProgramError::Custom(error_code_number)
|
||||
}
|
||||
|
|
|
@ -239,10 +239,11 @@ pub mod prelude {
|
|||
accounts::signer::Signer, accounts::system_account::SystemAccount,
|
||||
accounts::sysvar::Sysvar, accounts::unchecked_account::UncheckedAccount, constant,
|
||||
context::Context, context::CpiContext, declare_id, emit, err, error, event, interface,
|
||||
program, require, solana_program::bpf_loader_upgradeable::UpgradeableLoaderState, source,
|
||||
state, zero_copy, AccountDeserialize, AccountSerialize, Accounts, AccountsExit,
|
||||
AnchorDeserialize, AnchorSerialize, Id, Key, Owner, ProgramData, Result, System,
|
||||
ToAccountInfo, ToAccountInfos, ToAccountMetas,
|
||||
program, require, require_eq, require_keys_eq,
|
||||
solana_program::bpf_loader_upgradeable::UpgradeableLoaderState, source, state, zero_copy,
|
||||
AccountDeserialize, AccountSerialize, Accounts, AccountsExit, AnchorDeserialize,
|
||||
AnchorSerialize, Id, Key, Owner, ProgramData, Result, System, ToAccountInfo,
|
||||
ToAccountInfos, ToAccountMetas,
|
||||
};
|
||||
pub use anchor_attribute_error::*;
|
||||
pub use borsh;
|
||||
|
@ -318,7 +319,7 @@ pub mod __private {
|
|||
}
|
||||
|
||||
/// Ensures a condition is true, otherwise returns with the given error.
|
||||
/// Use this with a custom error type.
|
||||
/// Use this with or without a custom error type.
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
|
@ -366,6 +367,66 @@ macro_rules! require {
|
|||
};
|
||||
}
|
||||
|
||||
/// Ensures two NON-PUBKEY values are equal.
|
||||
///
|
||||
/// Use [require_keys_eq](crate::prelude::require_keys_eq)
|
||||
/// to compare two pubkeys.
|
||||
///
|
||||
/// Can be used with or without a custom error code.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,ignore
|
||||
/// pub fn set_data(ctx: Context<SetData>, data: u64) -> Result<()> {
|
||||
/// require_eq!(ctx.accounts.data.data, 0);
|
||||
/// ctx.accounts.data.data = data;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! require_eq {
|
||||
($value1: expr, $value2: expr, $error_code:expr $(,)?) => {
|
||||
if $value1 != $value2 {
|
||||
return Err(error!($error_code).with_values(($value1, $value2)));
|
||||
}
|
||||
};
|
||||
($value1: expr, $value2: expr $(,)?) => {
|
||||
if $value1 != $value2 {
|
||||
return Err(error!(anchor_lang::error::ErrorCode::RequireEqViolated)
|
||||
.with_values(($value1, $value2)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Ensures two pubkeys values are equal.
|
||||
///
|
||||
/// Use [require_eq](crate::prelude::require_eq)
|
||||
/// to compare two non-pubkey values.
|
||||
///
|
||||
/// Can be used with or without a custom error code.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,ignore
|
||||
/// pub fn set_data(ctx: Context<SetData>, data: u64) -> Result<()> {
|
||||
/// require_keys_eq!(ctx.accounts.data.authority.key(), ctx.accounts.authority.key());
|
||||
/// ctx.accounts.data.data = data;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! require_keys_eq {
|
||||
($value1: expr, $value2: expr, $error_code:expr $(,)?) => {
|
||||
if $value1 != $value2 {
|
||||
return Err(error!($error_code).with_pubkeys(($value1, $value2)));
|
||||
}
|
||||
};
|
||||
($value1: expr, $value2: expr $(,)?) => {
|
||||
if $value1 != $value2 {
|
||||
return Err(error!(anchor_lang::error::ErrorCode::RequireKeysEqViolated)
|
||||
.with_pubkeys(($value1, $value2)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns with the given error.
|
||||
/// Use this with a custom error type.
|
||||
///
|
||||
|
|
|
@ -158,7 +158,7 @@ pub fn generate_constraint_zeroed(f: &Field, _c: &ConstraintZeroed) -> proc_macr
|
|||
__disc_bytes.copy_from_slice(&__data[..8]);
|
||||
let __discriminator = u64::from_le_bytes(__disc_bytes);
|
||||
if __discriminator != 0 {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintZero, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintZero).with_account_name(#name_str));
|
||||
}
|
||||
#from_account_info
|
||||
};
|
||||
|
@ -171,7 +171,7 @@ pub fn generate_constraint_close(f: &Field, c: &ConstraintClose) -> proc_macro2:
|
|||
let target = &c.sol_dest;
|
||||
quote! {
|
||||
if #field.key() == #target.key() {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintClose, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintClose).with_account_name(#name_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -238,7 +238,7 @@ pub fn generate_constraint_literal(
|
|||
};
|
||||
quote! {
|
||||
if !(#lit) {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::Deprecated, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::Deprecated).with_account_name(#name_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -277,7 +277,7 @@ pub fn generate_constraint_rent_exempt(
|
|||
ConstraintRentExempt::Skip => quote! {},
|
||||
ConstraintRentExempt::Enforce => quote! {
|
||||
if !__anchor_rent.is_exempt(#info.lamports(), #info.try_data_len()?) {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintRentExempt, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintRentExempt).with_account_name(#name_str));
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -374,10 +374,10 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma
|
|||
let pa: #ty_decl = #from_account_info;
|
||||
if #if_needed {
|
||||
if pa.mint != #mint.key() {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintTokenMint, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenMint).with_account_name(#name_str));
|
||||
}
|
||||
if pa.owner != #owner.key() {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintTokenOwner, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenOwner).with_account_name(#name_str));
|
||||
}
|
||||
}
|
||||
pa
|
||||
|
@ -409,14 +409,14 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma
|
|||
let pa: #ty_decl = #from_account_info;
|
||||
if #if_needed {
|
||||
if pa.mint != #mint.key() {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintTokenMint, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenMint).with_account_name(#name_str));
|
||||
}
|
||||
if pa.owner != #owner.key() {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintTokenOwner, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenOwner).with_account_name(#name_str));
|
||||
}
|
||||
|
||||
if pa.key() != anchor_spl::associated_token::get_associated_token_address(&#owner.key(), &#mint.key()) {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::AccountNotAssociatedTokenAccount, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::AccountNotAssociatedTokenAccount).with_account_name(#name_str));
|
||||
}
|
||||
}
|
||||
pa
|
||||
|
@ -462,16 +462,16 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma
|
|||
let pa: #ty_decl = #from_account_info;
|
||||
if #if_needed {
|
||||
if pa.mint_authority != anchor_lang::solana_program::program_option::COption::Some(#owner.key()) {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintMintMintAuthority, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintMintMintAuthority).with_account_name(#name_str));
|
||||
}
|
||||
if pa.freeze_authority
|
||||
.as_ref()
|
||||
.map(|fa| #freeze_authority.as_ref().map(|expected_fa| fa != *expected_fa).unwrap_or(true))
|
||||
.unwrap_or(#freeze_authority.is_some()) {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintMintFreezeAuthority, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintMintFreezeAuthority).with_account_name(#name_str));
|
||||
}
|
||||
if pa.decimals != #decimals {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintMintDecimals, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintMintDecimals).with_account_name(#name_str));
|
||||
}
|
||||
}
|
||||
pa
|
||||
|
@ -525,17 +525,17 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma
|
|||
// Assert the account was created correctly.
|
||||
if #if_needed {
|
||||
if space != actual_field.data_len() {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintSpace, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSpace).with_account_name(#name_str));
|
||||
}
|
||||
|
||||
if actual_owner != #owner {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintOwner, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintOwner).with_account_name(#name_str));
|
||||
}
|
||||
|
||||
{
|
||||
let required_lamports = __anchor_rent.minimum_balance(space);
|
||||
if pa.to_account_info().lamports() < required_lamports {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintRentExempt, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintRentExempt).with_account_name(#name_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -577,10 +577,10 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2
|
|||
let b = c.bump.as_ref().unwrap();
|
||||
quote! {
|
||||
if #name.key() != __pda_address {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintSeeds, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSeeds).with_account_name(#name_str));
|
||||
}
|
||||
if __bump != #b {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintSeeds, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSeeds).with_account_name(#name_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -592,7 +592,7 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2
|
|||
else if c.is_init {
|
||||
quote! {
|
||||
if #name.key() != __pda_address {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintSeeds, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSeeds).with_account_name(#name_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -616,7 +616,7 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2
|
|||
let __pda_address = Pubkey::create_program_address(
|
||||
&[#maybe_seeds_plus_comma &[#b][..]],
|
||||
&#deriving_program_id,
|
||||
).map_err(|_| anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintSeeds, #name_str))?;
|
||||
).map_err(|_| anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSeeds).with_account_name(#name_str))?;
|
||||
},
|
||||
};
|
||||
quote! {
|
||||
|
@ -625,7 +625,7 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2
|
|||
|
||||
// Check it.
|
||||
if #name.key() != __pda_address {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintSeeds, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSeeds).with_account_name(#name_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -641,11 +641,11 @@ fn generate_constraint_associated_token(
|
|||
let spl_token_mint_address = &c.mint;
|
||||
quote! {
|
||||
if #name.owner != #wallet_address.key() {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintTokenOwner, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenOwner).with_account_name(#name_str));
|
||||
}
|
||||
let __associated_token_address = anchor_spl::associated_token::get_associated_token_address(&#wallet_address.key(), &#spl_token_mint_address.key());
|
||||
if #name.key() != __associated_token_address {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintAssociated, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintAssociated).with_account_name(#name_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -741,7 +741,7 @@ pub fn generate_constraint_executable(
|
|||
let name_str = name.to_string();
|
||||
quote! {
|
||||
if !#name.to_account_info().executable {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintExecutable, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintExecutable).with_account_name(#name_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -758,10 +758,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.key() != anchor_lang::accounts::cpi_state::CpiState::<#account_ty>::address(&#program_target.key()) {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintState, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintState).with_account_name(#name_str));
|
||||
}
|
||||
if #ident.as_ref().owner != &#program_target.key() {
|
||||
return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintState, #name_str));
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintState).with_account_name(#name_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -774,10 +774,10 @@ fn generate_custom_error(
|
|||
let account_name = account_name.to_string();
|
||||
match custom_error {
|
||||
Some(error) => {
|
||||
quote! { Err(anchor_lang::anchor_attribute_error::error_with_account_name!(#error, #account_name)) }
|
||||
quote! { Err(anchor_lang::error::Error::from(#error).with_account_name(#account_name)) }
|
||||
}
|
||||
None => {
|
||||
quote! { Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::#error, #account_name)) }
|
||||
quote! { Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::#error).with_account_name(#account_name)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,8 +82,8 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream {
|
|||
error_name: error_code.name(),
|
||||
error_code_number: error_code.into(),
|
||||
error_msg: error_code.to_string(),
|
||||
source: None,
|
||||
account_name: None
|
||||
error_origin: None,
|
||||
compared_values: None
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{Error, ErrorArgs, ErrorCode};
|
||||
use syn::parse::{Parse, Result as ParseResult};
|
||||
use syn::{Expr, Token};
|
||||
use syn::Expr;
|
||||
|
||||
// Removes any internal #[msg] attributes, as they are inert.
|
||||
pub fn parse(error_enum: &mut syn::ItemEnum, args: Option<ErrorArgs>) -> Error {
|
||||
|
@ -88,20 +88,3 @@ impl Parse for ErrorInput {
|
|||
Ok(Self { error_code })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ErrorWithAccountNameInput {
|
||||
pub error_code: Expr,
|
||||
pub account_name: Expr,
|
||||
}
|
||||
|
||||
impl Parse for ErrorWithAccountNameInput {
|
||||
fn parse(stream: syn::parse::ParseStream) -> ParseResult<Self> {
|
||||
let error_code = stream.call(Expr::parse)?;
|
||||
let _ = stream.parse::<Token!(,)>();
|
||||
let account_name = stream.call(Expr::parse)?;
|
||||
Ok(Self {
|
||||
error_code,
|
||||
account_name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,32 @@ mod errors {
|
|||
pub fn account_not_initialized_error(_ctx: Context<AccountNotInitializedError>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn account_owned_by_wrong_program_error(
|
||||
_ctx: Context<AccountOwnedByWrongProgramError>,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn require_eq(_ctx: Context<RequireEq>) -> Result<()> {
|
||||
require_eq!(5241, 124124124, MyError::ValueMismatch);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn require_eq_default_error(_ctx: Context<RequireEq>) -> Result<()> {
|
||||
require_eq!(5241, 124124124);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn require_keys_eq(ctx: Context<RequireKeysEq>) -> Result<()> {
|
||||
require_keys_eq!(ctx.accounts.some_account.key(), *ctx.program_id, MyError::ValueMismatch);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn require_keys_eq_default_error(ctx: Context<RequireKeysEq>) -> Result<()> {
|
||||
require_keys_eq!(ctx.accounts.some_account.key(), *ctx.program_id);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
|
@ -99,6 +125,19 @@ pub struct AccountNotInitializedError<'info> {
|
|||
not_initialized_account: Account<'info, AnyAccount>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct AccountOwnedByWrongProgramError<'info> {
|
||||
pub wrong_account: Account<'info, AnyAccount>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct RequireEq {}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct RequireKeysEq<'info> {
|
||||
pub some_account: UncheckedAccount<'info>
|
||||
}
|
||||
|
||||
#[error_code]
|
||||
pub enum MyError {
|
||||
#[msg("This is an error message clients will automatically display")]
|
||||
|
@ -106,4 +145,5 @@ pub enum MyError {
|
|||
HelloNoMsg = 123,
|
||||
HelloNext,
|
||||
HelloCustom,
|
||||
ValueMismatch,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const assert = require("assert");
|
||||
const anchor = require("@project-serum/anchor");
|
||||
const { Account, Transaction, TransactionInstruction } = anchor.web3;
|
||||
const { TOKEN_PROGRAM_ID, Token } = require("@solana/spl-token");
|
||||
|
||||
// sleep to allow logs to come in
|
||||
const sleep = (ms) =>
|
||||
|
@ -8,15 +9,31 @@ const sleep = (ms) =>
|
|||
setTimeout(() => resolve(), ms);
|
||||
});
|
||||
|
||||
const withLogTest = async (callback, expectedLog) => {
|
||||
const withLogTest = async (callback, expectedLogs) => {
|
||||
let logTestOk = false;
|
||||
const listener = anchor.getProvider().connection.onLogs(
|
||||
"all",
|
||||
(logs) => {
|
||||
if (logs.logs.some((logLine) => logLine === expectedLog)) {
|
||||
logTestOk = true;
|
||||
} else {
|
||||
const index = logs.logs.findIndex(
|
||||
(logLine) => logLine === expectedLogs[0]
|
||||
);
|
||||
if (index === -1) {
|
||||
console.log("Expected: ");
|
||||
console.log(expectedLogs);
|
||||
console.log("Actual: ");
|
||||
console.log(logs);
|
||||
} else {
|
||||
const actualLogs = logs.logs.slice(index, index + expectedLogs.length);
|
||||
for (let i = 0; i < expectedLogs.length; i++) {
|
||||
if (actualLogs[i] !== expectedLogs[i]) {
|
||||
console.log("Expected: ");
|
||||
console.log(expectedLogs);
|
||||
console.log("Actual: ");
|
||||
console.log(logs);
|
||||
return;
|
||||
}
|
||||
}
|
||||
logTestOk = true;
|
||||
}
|
||||
},
|
||||
"recent"
|
||||
|
@ -52,7 +69,9 @@ describe("errors", () => {
|
|||
assert.equal(err.msg, errMsg);
|
||||
assert.equal(err.code, 6000);
|
||||
}
|
||||
}, "Program log: AnchorError thrown in programs/errors/src/lib.rs:13. Error Code: Hello. Error Number: 6000. Error Message: This is an error message clients will automatically display.");
|
||||
}, [
|
||||
"Program log: AnchorError thrown in programs/errors/src/lib.rs:13. Error Code: Hello. Error Number: 6000. Error Message: This is an error message clients will automatically display.",
|
||||
]);
|
||||
});
|
||||
|
||||
it("Emits a Hello error via require!", async () => {
|
||||
|
@ -89,7 +108,9 @@ describe("errors", () => {
|
|||
} catch (err) {
|
||||
// No-op (withLogTest expects the callback to catch the initial tx error)
|
||||
}
|
||||
}, "Program log: ProgramError occurred. Error Code: InvalidAccountData. Error Number: 17179869184. Error Message: An account's data contents was invalid.");
|
||||
}, [
|
||||
"Program log: ProgramError occurred. Error Code: InvalidAccountData. Error Number: 17179869184. Error Message: An account's data contents was invalid.",
|
||||
]);
|
||||
});
|
||||
|
||||
it("Logs a ProgramError with source", async () => {
|
||||
|
@ -100,7 +121,9 @@ describe("errors", () => {
|
|||
} catch (err) {
|
||||
// No-op (withLogTest expects the callback to catch the initial tx error)
|
||||
}
|
||||
}, "Program log: ProgramError thrown in programs/errors/src/lib.rs:38. Error Code: InvalidAccountData. Error Number: 17179869184. Error Message: An account's data contents was invalid.");
|
||||
}, [
|
||||
"Program log: ProgramError thrown in programs/errors/src/lib.rs:38. Error Code: InvalidAccountData. Error Number: 17179869184. Error Message: An account's data contents was invalid.",
|
||||
]);
|
||||
});
|
||||
|
||||
it("Emits a HelloNoMsg error", async () => {
|
||||
|
@ -142,7 +165,9 @@ describe("errors", () => {
|
|||
assert.equal(err.msg, errMsg);
|
||||
assert.equal(err.code, 2000);
|
||||
}
|
||||
}, "Program log: AnchorError caused by account: my_account. Error Code: ConstraintMut. Error Number: 2000. Error Message: A mut constraint was violated.");
|
||||
}, [
|
||||
"Program log: AnchorError caused by account: my_account. Error Code: ConstraintMut. Error Number: 2000. Error Message: A mut constraint was violated.",
|
||||
]);
|
||||
});
|
||||
|
||||
it("Emits a has one error", async () => {
|
||||
|
@ -235,6 +260,124 @@ describe("errors", () => {
|
|||
"The program expected this account to be already initialized";
|
||||
assert.equal(err.toString(), errMsg);
|
||||
}
|
||||
}, "Program log: AnchorError caused by account: not_initialized_account. Error Code: AccountNotInitialized. Error Number: 3012. Error Message: The program expected this account to be already initialized.");
|
||||
}, [
|
||||
"Program log: AnchorError caused by account: not_initialized_account. Error Code: AccountNotInitialized. Error Number: 3012. Error Message: The program expected this account to be already initialized.",
|
||||
]);
|
||||
});
|
||||
|
||||
it("Emits an AccountOwnedByWrongProgram error", async () => {
|
||||
let client = await Token.createMint(
|
||||
program.provider.connection,
|
||||
program.provider.wallet.payer,
|
||||
program.provider.wallet.publicKey,
|
||||
program.provider.wallet.publicKey,
|
||||
9,
|
||||
TOKEN_PROGRAM_ID
|
||||
);
|
||||
|
||||
await withLogTest(async () => {
|
||||
try {
|
||||
const tx = await program.rpc.accountOwnedByWrongProgramError({
|
||||
accounts: {
|
||||
wrongAccount: client.publicKey,
|
||||
},
|
||||
});
|
||||
assert.fail(
|
||||
"Unexpected success in creating a transaction that should have failed with `AccountOwnedByWrongProgram` error"
|
||||
);
|
||||
} catch (err) {
|
||||
const errMsg =
|
||||
"The given account is owned by a different program than expected";
|
||||
assert.equal(err.toString(), errMsg);
|
||||
}
|
||||
}, [
|
||||
"Program log: AnchorError caused by account: wrong_account. Error Code: AccountOwnedByWrongProgram. Error Number: 3007. Error Message: The given account is owned by a different program than expected.",
|
||||
"Program log: Left:",
|
||||
"Program log: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
|
||||
"Program log: Right:",
|
||||
"Program log: Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS",
|
||||
]);
|
||||
});
|
||||
|
||||
it("Emits a ValueMismatch error via require_eq", async () => {
|
||||
await withLogTest(async () => {
|
||||
try {
|
||||
const tx = await program.rpc.requireEq();
|
||||
assert.fail(
|
||||
"Unexpected success in creating a transaction that should have failed with `ValueMismatch` error"
|
||||
);
|
||||
} catch (err) {
|
||||
assert.equal(err.code, 6126);
|
||||
}
|
||||
}, [
|
||||
"Program log: AnchorError thrown in programs/errors/src/lib.rs:68. Error Code: ValueMismatch. Error Number: 6126. Error Message: ValueMismatch.",
|
||||
"Program log: Left: 5241",
|
||||
"Program log: Right: 124124124",
|
||||
]);
|
||||
});
|
||||
|
||||
it("Emits a RequireEqViolated error via require_eq", async () => {
|
||||
await withLogTest(async () => {
|
||||
try {
|
||||
const tx = await program.rpc.requireEqDefaultError();
|
||||
assert.fail(
|
||||
"Unexpected success in creating a transaction that should have failed with `ValueMismatch` error"
|
||||
);
|
||||
} catch (err) {
|
||||
assert.equal(err.code, 2501);
|
||||
}
|
||||
}, [
|
||||
"Program log: AnchorError thrown in programs/errors/src/lib.rs:73. Error Code: RequireEqViolated. Error Number: 2501. Error Message: A require_eq expression was violated.",
|
||||
"Program log: Left: 5241",
|
||||
"Program log: Right: 124124124",
|
||||
]);
|
||||
});
|
||||
|
||||
it("Emits a ValueMismatch error via require_keys_eq", async () => {
|
||||
const someAccount = anchor.web3.Keypair.generate().publicKey;
|
||||
await withLogTest(async () => {
|
||||
try {
|
||||
const tx = await program.rpc.requireKeysEq({
|
||||
accounts: {
|
||||
someAccount,
|
||||
},
|
||||
});
|
||||
assert.fail(
|
||||
"Unexpected success in creating a transaction that should have failed with `ValueMismatch` error"
|
||||
);
|
||||
} catch (err) {
|
||||
assert.equal(err.code, 6126);
|
||||
}
|
||||
}, [
|
||||
"Program log: AnchorError thrown in programs/errors/src/lib.rs:78. Error Code: ValueMismatch. Error Number: 6126. Error Message: ValueMismatch.",
|
||||
"Program log: Left:",
|
||||
`Program log: ${someAccount}`,
|
||||
"Program log: Right:",
|
||||
`Program log: ${program.programId}`,
|
||||
]);
|
||||
});
|
||||
|
||||
it("Emits a RequireKeysEqViolated error via require_keys_eq", async () => {
|
||||
const someAccount = anchor.web3.Keypair.generate().publicKey;
|
||||
await withLogTest(async () => {
|
||||
try {
|
||||
const tx = await program.rpc.requireKeysEqDefaultError({
|
||||
accounts: {
|
||||
someAccount,
|
||||
},
|
||||
});
|
||||
assert.fail(
|
||||
"Unexpected success in creating a transaction that should have failed with `ValueMismatch` error"
|
||||
);
|
||||
} catch (err) {
|
||||
assert.equal(err.code, 2502);
|
||||
}
|
||||
}, [
|
||||
"Program log: AnchorError thrown in programs/errors/src/lib.rs:83. Error Code: RequireKeysEqViolated. Error Number: 2502. Error Message: A require_keys_eq expression was violated.",
|
||||
"Program log: Left:",
|
||||
`Program log: ${someAccount}`,
|
||||
"Program log: Right:",
|
||||
`Program log: ${program.programId}`,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -95,6 +95,11 @@ const LangErrorCode = {
|
|||
ConstraintMintDecimals: 2018,
|
||||
ConstraintSpace: 2019,
|
||||
|
||||
// Require.
|
||||
RequireViolated: 2500,
|
||||
RequireEqViolated: 2501,
|
||||
RequireKeysEqViolated: 2502,
|
||||
|
||||
// Accounts.
|
||||
AccountDiscriminatorAlreadySet: 3000,
|
||||
AccountDiscriminatorNotFound: 3001,
|
||||
|
@ -185,6 +190,14 @@ const LangErrorMessage = new Map([
|
|||
],
|
||||
[LangErrorCode.ConstraintSpace, "A space constraint was violated"],
|
||||
|
||||
// Require.
|
||||
[LangErrorCode.RequireViolated, "A require expression was violated"],
|
||||
[LangErrorCode.RequireEqViolated, "A require_eq expression was violated"],
|
||||
[
|
||||
LangErrorCode.RequireKeysEqViolated,
|
||||
"A require_keys_eq expression was violated",
|
||||
],
|
||||
|
||||
// Accounts.
|
||||
[
|
||||
LangErrorCode.AccountDiscriminatorAlreadySet,
|
||||
|
|
Loading…
Reference in New Issue