Browse Source

lang: add support for logging expected and actual values and pubkeys (#1572)

Paul 3 years ago
parent
commit
721fe6693c

+ 2 - 1
CHANGELOG.md

@@ -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: 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])
 * 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
 ## [0.22.1] - 2022-02-28
 
 

+ 14 - 25
lang/attribute/error/src/lib.rs

@@ -3,9 +3,9 @@ extern crate proc_macro;
 use proc_macro::TokenStream;
 use proc_macro::TokenStream;
 use quote::quote;
 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::ErrorArgs;
-use anchor_syn::{codegen, parser::error::ErrorInput};
 use syn::{parse_macro_input, Expr};
 use syn::{parse_macro_input, Expr};
 
 
 /// Generates `Error` and `type Result<T> = Result<T, Error>` types to be
 /// 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)
     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 {
 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!(),
                 filename: file!(),
                 line: line!()
                 line: line!()
-            })
-        }
-    } else {
-        quote! {
-            None
-        }
-    };
-    let account_name = match account_name {
-        Some(_) => quote! { Some(#account_name.to_string()) },
-        None => quote! { None },
+            }))
+        },
     };
     };
+
     TokenStream::from(quote! {
     TokenStream::from(quote! {
         anchor_lang::error::Error::from(
         anchor_lang::error::Error::from(
             anchor_lang::error::AnchorError {
             anchor_lang::error::AnchorError {
                 error_name: #error_code.name(),
                 error_name: #error_code.name(),
                 error_code_number: #error_code.into(),
                 error_code_number: #error_code.into(),
                 error_msg: #error_code.to_string(),
                 error_msg: #error_code.to_string(),
-                source: #source,
-                account_name: #account_name
+                error_origin: #error_origin,
+                compared_values: None
             }
             }
         )
         )
     })
     })

+ 5 - 3
lang/src/accounts/account.rs

@@ -1,7 +1,7 @@
 //! Account container that checks ownership on deserialization.
 //! Account container that checks ownership on deserialization.
 
 
 use crate::bpf_writer::BpfWriter;
 use crate::bpf_writer::BpfWriter;
-use crate::error::ErrorCode;
+use crate::error::{Error, ErrorCode};
 use crate::{
 use crate::{
     AccountDeserialize, AccountSerialize, Accounts, AccountsClose, AccountsExit, Key, Owner,
     AccountDeserialize, AccountSerialize, Accounts, AccountsClose, AccountsExit, Key, Owner,
     Result, ToAccountInfo, ToAccountInfos, ToAccountMetas,
     Result, ToAccountInfo, ToAccountInfos, ToAccountMetas,
@@ -251,7 +251,8 @@ impl<'a, T: AccountSerialize + AccountDeserialize + crate::Owner + Clone> Accoun
             return Err(ErrorCode::AccountNotInitialized.into());
             return Err(ErrorCode::AccountNotInitialized.into());
         }
         }
         if info.owner != &T::owner() {
         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()?;
         let mut data: &[u8] = &info.try_borrow_data()?;
         Ok(Account::new(info.clone(), T::try_deserialize(&mut 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());
             return Err(ErrorCode::AccountNotInitialized.into());
         }
         }
         if info.owner != &T::owner() {
         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()?;
         let mut data: &[u8] = &info.try_borrow_data()?;
         Ok(Account::new(
         Ok(Account::new(

+ 5 - 3
lang/src/accounts/account_loader.rs

@@ -1,7 +1,7 @@
 //! Type facilitating on demand zero copy deserialization.
 //! Type facilitating on demand zero copy deserialization.
 
 
 use crate::bpf_writer::BpfWriter;
 use crate::bpf_writer::BpfWriter;
-use crate::error::ErrorCode;
+use crate::error::{Error, ErrorCode};
 use crate::{
 use crate::{
     Accounts, AccountsClose, AccountsExit, Key, Owner, Result, ToAccountInfo, ToAccountInfos,
     Accounts, AccountsClose, AccountsExit, Key, Owner, Result, ToAccountInfo, ToAccountInfos,
     ToAccountMetas, ZeroCopy,
     ToAccountMetas, ZeroCopy,
@@ -120,7 +120,8 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> {
     #[inline(never)]
     #[inline(never)]
     pub fn try_from(acc_info: &AccountInfo<'info>) -> Result<AccountLoader<'info, T>> {
     pub fn try_from(acc_info: &AccountInfo<'info>) -> Result<AccountLoader<'info, T>> {
         if acc_info.owner != &T::owner() {
         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()?;
         let data: &[u8] = &acc_info.try_borrow_data()?;
         // Discriminator must match.
         // Discriminator must match.
@@ -139,7 +140,8 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> {
         acc_info: &AccountInfo<'info>,
         acc_info: &AccountInfo<'info>,
     ) -> Result<AccountLoader<'info, T>> {
     ) -> Result<AccountLoader<'info, T>> {
         if acc_info.owner != &T::owner() {
         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()))
         Ok(AccountLoader::new(acc_info.clone()))
     }
     }

+ 5 - 3
lang/src/accounts/loader.rs

@@ -1,5 +1,5 @@
 use crate::bpf_writer::BpfWriter;
 use crate::bpf_writer::BpfWriter;
-use crate::error::ErrorCode;
+use crate::error::{Error, ErrorCode};
 use crate::{
 use crate::{
     Accounts, AccountsClose, AccountsExit, Key, Result, ToAccountInfo, ToAccountInfos,
     Accounts, AccountsClose, AccountsExit, Key, Result, ToAccountInfo, ToAccountInfos,
     ToAccountMetas, ZeroCopy,
     ToAccountMetas, ZeroCopy,
@@ -59,7 +59,8 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
         acc_info: &AccountInfo<'info>,
         acc_info: &AccountInfo<'info>,
     ) -> Result<Loader<'info, T>> {
     ) -> Result<Loader<'info, T>> {
         if acc_info.owner != program_id {
         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()?;
         let data: &[u8] = &acc_info.try_borrow_data()?;
         // Discriminator must match.
         // Discriminator must match.
@@ -79,7 +80,8 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
         acc_info: &AccountInfo<'info>,
         acc_info: &AccountInfo<'info>,
     ) -> Result<Loader<'info, T>> {
     ) -> Result<Loader<'info, T>> {
         if acc_info.owner != program_id {
         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()))
         Ok(Loader::new(acc_info.clone()))
     }
     }

+ 3 - 2
lang/src/accounts/program.rs

@@ -1,5 +1,6 @@
 //! Type validating that the account is the given Program
 //! Type validating that the account is the given Program
-use crate::error::ErrorCode;
+
+use crate::error::{Error, ErrorCode};
 use crate::{
 use crate::{
     AccountDeserialize, Accounts, AccountsExit, Id, Key, Result, ToAccountInfos, ToAccountMetas,
     AccountDeserialize, Accounts, AccountsExit, Id, Key, Result, ToAccountInfos, ToAccountMetas,
 };
 };
@@ -102,7 +103,7 @@ impl<'a, T: Id + Clone> Program<'a, T> {
     #[inline(never)]
     #[inline(never)]
     pub fn try_from(info: &AccountInfo<'a>) -> Result<Program<'a, T>> {
     pub fn try_from(info: &AccountInfo<'a>) -> Result<Program<'a, T>> {
         if info.key != &T::id() {
         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 {
         if !info.executable {
             return Err(ErrorCode::InvalidProgramExecutable.into());
             return Err(ErrorCode::InvalidProgramExecutable.into());

+ 5 - 3
lang/src/accounts/program_account.rs

@@ -1,7 +1,7 @@
 #[allow(deprecated)]
 #[allow(deprecated)]
 use crate::accounts::cpi_account::CpiAccount;
 use crate::accounts::cpi_account::CpiAccount;
 use crate::bpf_writer::BpfWriter;
 use crate::bpf_writer::BpfWriter;
-use crate::error::ErrorCode;
+use crate::error::{Error, ErrorCode};
 use crate::{
 use crate::{
     AccountDeserialize, AccountSerialize, Accounts, AccountsClose, AccountsExit, Key, Result,
     AccountDeserialize, AccountSerialize, Accounts, AccountsClose, AccountsExit, Key, Result,
     ToAccountInfo, ToAccountInfos, ToAccountMetas,
     ToAccountInfo, ToAccountInfos, ToAccountMetas,
@@ -38,7 +38,8 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramAccount<'a, T>
     #[inline(never)]
     #[inline(never)]
     pub fn try_from(program_id: &Pubkey, info: &AccountInfo<'a>) -> Result<ProgramAccount<'a, T>> {
     pub fn try_from(program_id: &Pubkey, info: &AccountInfo<'a>) -> Result<ProgramAccount<'a, T>> {
         if info.owner != program_id {
         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()?;
         let mut data: &[u8] = &info.try_borrow_data()?;
         Ok(ProgramAccount::new(
         Ok(ProgramAccount::new(
@@ -56,7 +57,8 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramAccount<'a, T>
         info: &AccountInfo<'a>,
         info: &AccountInfo<'a>,
     ) -> Result<ProgramAccount<'a, T>> {
     ) -> Result<ProgramAccount<'a, T>> {
         if info.owner != program_id {
         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()?;
         let mut data: &[u8] = &info.try_borrow_data()?;
         Ok(ProgramAccount::new(
         Ok(ProgramAccount::new(

+ 3 - 2
lang/src/accounts/state.rs

@@ -1,7 +1,7 @@
 #[allow(deprecated)]
 #[allow(deprecated)]
 use crate::accounts::cpi_account::CpiAccount;
 use crate::accounts::cpi_account::CpiAccount;
 use crate::bpf_writer::BpfWriter;
 use crate::bpf_writer::BpfWriter;
-use crate::error::ErrorCode;
+use crate::error::{Error, ErrorCode};
 use crate::{
 use crate::{
     AccountDeserialize, AccountSerialize, Accounts, AccountsExit, Key, Result, ToAccountInfo,
     AccountDeserialize, AccountSerialize, Accounts, AccountsExit, Key, Result, ToAccountInfo,
     ToAccountInfos, ToAccountMetas,
     ToAccountInfos, ToAccountMetas,
@@ -40,7 +40,8 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramState<'a, T> {
     #[inline(never)]
     #[inline(never)]
     pub fn try_from(program_id: &Pubkey, info: &AccountInfo<'a>) -> Result<ProgramState<'a, T>> {
     pub fn try_from(program_id: &Pubkey, info: &AccountInfo<'a>) -> Result<ProgramState<'a, T>> {
         if info.owner != program_id {
         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) {
         if info.key != &Self::address(program_id) {
             solana_program::msg!("Invalid state address");
             solana_program::msg!("Invalid state address");

+ 177 - 68
lang/src/error.rs

@@ -1,6 +1,6 @@
 use anchor_attribute_error::error_code;
 use anchor_attribute_error::error_code;
 use borsh::maybestd::io::Error as BorshIoError;
 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};
 use std::fmt::{Debug, Display};
 
 
 /// The starting point for user defined error codes.
 /// The starting point for user defined error codes.
@@ -106,6 +106,17 @@ pub enum ErrorCode {
     #[msg("A space constraint was violated")]
     #[msg("A space constraint was violated")]
     ConstraintSpace,
     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.
     // Accounts.
     /// 3000 - The account discriminator was already set on this account
     /// 3000 - The account discriminator was already set on this account
     #[msg("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 {
 impl From<AnchorError> for Error {
     fn from(ae: AnchorError) -> Self {
     fn from(ae: AnchorError) -> Self {
         Self::AnchorError(ae)
         Self::AnchorError(ae)
@@ -228,16 +233,51 @@ impl Error {
 
 
     pub fn with_account_name(mut self, account_name: impl ToString) -> Self {
     pub fn with_account_name(mut self, account_name: impl ToString) -> Self {
         match &mut 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
         self
     }
     }
 
 
     pub fn with_source(mut self, source: Source) -> Self {
     pub fn with_source(mut self, source: Source) -> Self {
         match &mut 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
         self
     }
     }
@@ -246,8 +286,8 @@ impl Error {
 #[derive(Debug)]
 #[derive(Debug)]
 pub struct ProgramErrorWithOrigin {
 pub struct ProgramErrorWithOrigin {
     pub program_error: ProgramError,
     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 {
 impl Display for ProgramErrorWithOrigin {
@@ -258,78 +298,151 @@ impl Display for ProgramErrorWithOrigin {
 
 
 impl ProgramErrorWithOrigin {
 impl ProgramErrorWithOrigin {
     pub fn log(&self) {
     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
+    }
 }
 }
 
 
 impl From<ProgramError> for ProgramErrorWithOrigin {
 impl From<ProgramError> for ProgramErrorWithOrigin {
     fn from(program_error: ProgramError) -> Self {
     fn from(program_error: ProgramError) -> Self {
         Self {
         Self {
             program_error,
             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)]
 #[derive(Debug)]
 pub struct AnchorError {
 pub struct AnchorError {
     pub error_name: String,
     pub error_name: String,
     pub error_code_number: u32,
     pub error_code_number: u32,
     pub error_msg: String,
     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 {
 impl AnchorError {
     pub fn log(&self) {
     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 {
     fn from(e: Error) -> anchor_lang::solana_program::program_error::ProgramError {
         match e {
         match e {
             Error::AnchorError(AnchorError {
             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)
                 anchor_lang::solana_program::program_error::ProgramError::Custom(error_code_number)
             }
             }

+ 66 - 5
lang/src/lib.rs

@@ -239,10 +239,11 @@ pub mod prelude {
         accounts::signer::Signer, accounts::system_account::SystemAccount,
         accounts::signer::Signer, accounts::system_account::SystemAccount,
         accounts::sysvar::Sysvar, accounts::unchecked_account::UncheckedAccount, constant,
         accounts::sysvar::Sysvar, accounts::unchecked_account::UncheckedAccount, constant,
         context::Context, context::CpiContext, declare_id, emit, err, error, event, interface,
         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 anchor_attribute_error::*;
     pub use borsh;
     pub use borsh;
@@ -318,7 +319,7 @@ pub mod __private {
 }
 }
 
 
 /// Ensures a condition is true, otherwise returns with the given error.
 /// 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
 /// # Example
 /// ```ignore
 /// ```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.
 /// Returns with the given error.
 /// Use this with a custom error type.
 /// Use this with a custom error type.
 ///
 ///

+ 27 - 27
lang/syn/src/codegen/accounts/constraints.rs

@@ -158,7 +158,7 @@ pub fn generate_constraint_zeroed(f: &Field, _c: &ConstraintZeroed) -> proc_macr
             __disc_bytes.copy_from_slice(&__data[..8]);
             __disc_bytes.copy_from_slice(&__data[..8]);
             let __discriminator = u64::from_le_bytes(__disc_bytes);
             let __discriminator = u64::from_le_bytes(__disc_bytes);
             if __discriminator != 0 {
             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
             #from_account_info
         };
         };
@@ -171,7 +171,7 @@ pub fn generate_constraint_close(f: &Field, c: &ConstraintClose) -> proc_macro2:
     let target = &c.sol_dest;
     let target = &c.sol_dest;
     quote! {
     quote! {
         if #field.key() == #target.key() {
         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! {
     quote! {
         if !(#lit) {
         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::Skip => quote! {},
         ConstraintRentExempt::Enforce => quote! {
         ConstraintRentExempt::Enforce => quote! {
             if !__anchor_rent.is_exempt(#info.lamports(), #info.try_data_len()?) {
             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;
                     let pa: #ty_decl = #from_account_info;
                     if #if_needed {
                     if #if_needed {
                         if pa.mint != #mint.key() {
                         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() {
                         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
                     pa
@@ -409,14 +409,14 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma
                     let pa: #ty_decl = #from_account_info;
                     let pa: #ty_decl = #from_account_info;
                     if #if_needed {
                     if #if_needed {
                         if pa.mint != #mint.key() {
                         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() {
                         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()) {
                         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
                     pa
@@ -462,16 +462,16 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma
                     let pa: #ty_decl = #from_account_info;
                     let pa: #ty_decl = #from_account_info;
                     if #if_needed {
                     if #if_needed {
                         if pa.mint_authority != anchor_lang::solana_program::program_option::COption::Some(#owner.key()) {
                         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
                         if pa.freeze_authority
                             .as_ref()
                             .as_ref()
                             .map(|fa| #freeze_authority.as_ref().map(|expected_fa| fa != *expected_fa).unwrap_or(true))
                             .map(|fa| #freeze_authority.as_ref().map(|expected_fa| fa != *expected_fa).unwrap_or(true))
                             .unwrap_or(#freeze_authority.is_some()) {
                             .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 {
                         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
                     pa
@@ -525,17 +525,17 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma
                     // Assert the account was created correctly.
                     // Assert the account was created correctly.
                     if #if_needed {
                     if #if_needed {
                         if space != actual_field.data_len() {
                         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 {
                         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);
                             let required_lamports = __anchor_rent.minimum_balance(space);
                             if pa.to_account_info().lamports() < required_lamports {
                             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();
         let b = c.bump.as_ref().unwrap();
         quote! {
         quote! {
             if #name.key() != __pda_address {
             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 {
             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 {
     else if c.is_init {
         quote! {
         quote! {
             if #name.key() != __pda_address {
             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(
                 let __pda_address = Pubkey::create_program_address(
                     &[#maybe_seeds_plus_comma &[#b][..]],
                     &[#maybe_seeds_plus_comma &[#b][..]],
                     &#deriving_program_id,
                     &#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! {
         quote! {
@@ -625,7 +625,7 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2
 
 
             // Check it.
             // Check it.
             if #name.key() != __pda_address {
             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;
     let spl_token_mint_address = &c.mint;
     quote! {
     quote! {
         if #name.owner != #wallet_address.key() {
         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());
         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 {
         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();
     let name_str = name.to_string();
     quote! {
     quote! {
         if !#name.to_account_info().executable {
         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
         // Checks the given state account is the canonical state account for
         // the target program.
         // the target program.
         if #ident.key() != anchor_lang::accounts::cpi_state::CpiState::<#account_ty>::address(&#program_target.key()) {
         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() {
         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();
     let account_name = account_name.to_string();
     match custom_error {
     match custom_error {
         Some(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 => {
         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)) }
         }
         }
     }
     }
 }
 }

+ 2 - 2
lang/syn/src/codegen/error.rs

@@ -82,8 +82,8 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream {
                         error_name: error_code.name(),
                         error_name: error_code.name(),
                         error_code_number: error_code.into(),
                         error_code_number: error_code.into(),
                         error_msg: error_code.to_string(),
                         error_msg: error_code.to_string(),
-                        source: None,
-                        account_name: None
+                        error_origin: None,
+                        compared_values: None
                     }
                     }
                 )
                 )
             }
             }

+ 1 - 18
lang/syn/src/parser/error.rs

@@ -1,6 +1,6 @@
 use crate::{Error, ErrorArgs, ErrorCode};
 use crate::{Error, ErrorArgs, ErrorCode};
 use syn::parse::{Parse, Result as ParseResult};
 use syn::parse::{Parse, Result as ParseResult};
-use syn::{Expr, Token};
+use syn::Expr;
 
 
 // Removes any internal #[msg] attributes, as they are inert.
 // Removes any internal #[msg] attributes, as they are inert.
 pub fn parse(error_enum: &mut syn::ItemEnum, args: Option<ErrorArgs>) -> Error {
 pub fn parse(error_enum: &mut syn::ItemEnum, args: Option<ErrorArgs>) -> Error {
@@ -88,20 +88,3 @@ impl Parse for ErrorInput {
         Ok(Self { error_code })
         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,
-        })
-    }
-}

+ 40 - 0
tests/errors/programs/errors/src/lib.rs

@@ -57,6 +57,32 @@ mod errors {
     pub fn account_not_initialized_error(_ctx: Context<AccountNotInitializedError>) -> Result<()> {
     pub fn account_not_initialized_error(_ctx: Context<AccountNotInitializedError>) -> Result<()> {
         Ok(())
         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)]
 #[derive(Accounts)]
@@ -99,6 +125,19 @@ pub struct AccountNotInitializedError<'info> {
     not_initialized_account: Account<'info, AnyAccount>,
     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]
 #[error_code]
 pub enum MyError {
 pub enum MyError {
     #[msg("This is an error message clients will automatically display")]
     #[msg("This is an error message clients will automatically display")]
@@ -106,4 +145,5 @@ pub enum MyError {
     HelloNoMsg = 123,
     HelloNoMsg = 123,
     HelloNext,
     HelloNext,
     HelloCustom,
     HelloCustom,
+    ValueMismatch,
 }
 }

+ 152 - 9
tests/errors/tests/errors.js

@@ -1,6 +1,7 @@
 const assert = require("assert");
 const assert = require("assert");
 const anchor = require("@project-serum/anchor");
 const anchor = require("@project-serum/anchor");
 const { Account, Transaction, TransactionInstruction } = anchor.web3;
 const { Account, Transaction, TransactionInstruction } = anchor.web3;
+const { TOKEN_PROGRAM_ID, Token } = require("@solana/spl-token");
 
 
 // sleep to allow logs to come in
 // sleep to allow logs to come in
 const sleep = (ms) =>
 const sleep = (ms) =>
@@ -8,15 +9,31 @@ const sleep = (ms) =>
     setTimeout(() => resolve(), ms);
     setTimeout(() => resolve(), ms);
   });
   });
 
 
-const withLogTest = async (callback, expectedLog) => {
+const withLogTest = async (callback, expectedLogs) => {
   let logTestOk = false;
   let logTestOk = false;
   const listener = anchor.getProvider().connection.onLogs(
   const listener = anchor.getProvider().connection.onLogs(
     "all",
     "all",
     (logs) => {
     (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);
         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"
     "recent"
@@ -52,7 +69,9 @@ describe("errors", () => {
         assert.equal(err.msg, errMsg);
         assert.equal(err.msg, errMsg);
         assert.equal(err.code, 6000);
         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 () => {
   it("Emits a Hello error via require!", async () => {
@@ -89,7 +108,9 @@ describe("errors", () => {
       } catch (err) {
       } catch (err) {
         // No-op (withLogTest expects the callback to catch the initial tx error)
         // 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 () => {
   it("Logs a ProgramError with source", async () => {
@@ -100,7 +121,9 @@ describe("errors", () => {
       } catch (err) {
       } catch (err) {
         // No-op (withLogTest expects the callback to catch the initial tx error)
         // 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 () => {
   it("Emits a HelloNoMsg error", async () => {
@@ -142,7 +165,9 @@ describe("errors", () => {
         assert.equal(err.msg, errMsg);
         assert.equal(err.msg, errMsg);
         assert.equal(err.code, 2000);
         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 () => {
   it("Emits a has one error", async () => {
@@ -235,6 +260,124 @@ describe("errors", () => {
           "The program expected this account to be already initialized";
           "The program expected this account to be already initialized";
         assert.equal(err.toString(), errMsg);
         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}`,
+    ]);
   });
   });
 });
 });

+ 13 - 0
ts/src/error.ts

@@ -95,6 +95,11 @@ const LangErrorCode = {
   ConstraintMintDecimals: 2018,
   ConstraintMintDecimals: 2018,
   ConstraintSpace: 2019,
   ConstraintSpace: 2019,
 
 
+  // Require.
+  RequireViolated: 2500,
+  RequireEqViolated: 2501,
+  RequireKeysEqViolated: 2502,
+
   // Accounts.
   // Accounts.
   AccountDiscriminatorAlreadySet: 3000,
   AccountDiscriminatorAlreadySet: 3000,
   AccountDiscriminatorNotFound: 3001,
   AccountDiscriminatorNotFound: 3001,
@@ -185,6 +190,14 @@ const LangErrorMessage = new Map([
   ],
   ],
   [LangErrorCode.ConstraintSpace, "A space constraint was violated"],
   [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.
   // Accounts.
   [
   [
     LangErrorCode.AccountDiscriminatorAlreadySet,
     LangErrorCode.AccountDiscriminatorAlreadySet,