Paul 3 жил өмнө
parent
commit
40596824bf
82 өөрчлөгдсөн 1076 нэмэгдсэн , 613 устгасан
  1. 2 0
      .github/workflows/tests.yaml
  2. 5 0
      CHANGELOG.md
  3. 2 0
      client/src/lib.rs
  4. 8 13
      docs/src/tutorials/tutorial-4.md
  5. 1 1
      examples/tutorial/basic-0/programs/basic-0/src/lib.rs
  6. 2 2
      examples/tutorial/basic-1/programs/basic-1/src/lib.rs
  7. 2 2
      examples/tutorial/basic-2/programs/basic-2/src/lib.rs
  8. 1 1
      examples/tutorial/basic-3/programs/puppet-master/src/lib.rs
  9. 2 2
      examples/tutorial/basic-3/programs/puppet/src/lib.rs
  10. 4 4
      examples/tutorial/basic-4/programs/basic-4/src/lib.rs
  11. 12 11
      lang/attribute/account/src/lib.rs
  12. 1 1
      lang/attribute/error/Cargo.toml
  13. 74 8
      lang/attribute/error/src/lib.rs
  14. 8 8
      lang/attribute/interface/src/lib.rs
  15. 6 6
      lang/attribute/state/src/lib.rs
  16. 1 1
      lang/derive/accounts/src/lib.rs
  17. 11 13
      lang/src/accounts/account.rs
  18. 2 3
      lang/src/accounts/account_info.rs
  19. 12 16
      lang/src/accounts/account_loader.rs
  20. 4 6
      lang/src/accounts/boxed.rs
  21. 4 6
      lang/src/accounts/cpi_account.rs
  22. 4 4
      lang/src/accounts/cpi_state.rs
  23. 10 11
      lang/src/accounts/loader.rs
  24. 2 3
      lang/src/accounts/program.rs
  25. 7 12
      lang/src/accounts/program_account.rs
  26. 2 3
      lang/src/accounts/signer.rs
  27. 5 10
      lang/src/accounts/state.rs
  28. 2 3
      lang/src/accounts/system_account.rs
  29. 4 7
      lang/src/accounts/sysvar.rs
  30. 2 3
      lang/src/accounts/unchecked_account.rs
  31. 12 20
      lang/src/bpf_upgradeable_state.rs
  32. 4 7
      lang/src/common.rs
  33. 5 5
      lang/src/context.rs
  34. 189 2
      lang/src/error.rs
  35. 64 24
      lang/src/lib.rs
  36. 2 3
      lang/src/vec.rs
  37. 64 47
      lang/syn/src/codegen/accounts/constraints.rs
  38. 8 4
      lang/syn/src/codegen/accounts/exit.rs
  39. 6 4
      lang/syn/src/codegen/accounts/try_accounts.rs
  40. 42 33
      lang/syn/src/codegen/error.rs
  41. 4 4
      lang/syn/src/codegen/program/cpi.rs
  42. 1 1
      lang/syn/src/codegen/program/dispatch.rs
  43. 10 7
      lang/syn/src/codegen/program/entry.rs
  44. 15 14
      lang/syn/src/codegen/program/handlers.rs
  45. 1 1
      lang/syn/src/idl/file.rs
  46. 30 0
      lang/syn/src/parser/error.rs
  47. 23 18
      spl/src/dex.rs
  48. 33 20
      spl/src/token.rs
  49. 1 1
      tests/auction-house
  50. 5 5
      tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/src/lib.rs
  51. 4 4
      tests/cashiers-check/programs/cashiers-check/src/lib.rs
  52. 1 1
      tests/cfo/deps/stake
  53. 1 1
      tests/cfo/deps/swap
  54. 12 12
      tests/cfo/programs/cfo/src/lib.rs
  55. 1 1
      tests/chat/programs/chat/src/lib.rs
  56. 2 2
      tests/composite/programs/composite/src/lib.rs
  57. 1 2
      tests/declare-id/programs/declare-id/src/lib.rs
  58. 21 4
      tests/errors/programs/errors/src/lib.rs
  59. 124 32
      tests/errors/tests/errors.js
  60. 3 3
      tests/escrow/programs/escrow/src/lib.rs
  61. 2 2
      tests/events/programs/events/src/lib.rs
  62. 2 2
      tests/floats/programs/floats/src/lib.rs
  63. 26 26
      tests/ido-pool/programs/ido-pool/src/lib.rs
  64. 3 3
      tests/interface/programs/counter-auth/src/lib.rs
  65. 3 3
      tests/interface/programs/counter/src/lib.rs
  66. 18 17
      tests/lockup/programs/lockup/src/lib.rs
  67. 25 25
      tests/lockup/programs/registry/src/lib.rs
  68. 46 46
      tests/misc/programs/misc/src/lib.rs
  69. 3 3
      tests/misc/programs/misc2/src/lib.rs
  70. 7 7
      tests/multisig/programs/multisig/src/lib.rs
  71. 2 2
      tests/pda-derivation/programs/pda-derivation/src/lib.rs
  72. 2 2
      tests/pyth/programs/pyth/src/lib.rs
  73. 1 1
      tests/pyth/programs/pyth/src/pc.rs
  74. 4 4
      tests/spl/token-proxy/programs/token-proxy/src/lib.rs
  75. 9 8
      tests/swap/programs/swap/src/lib.rs
  76. 1 1
      tests/system-accounts/programs/system-accounts/src/lib.rs
  77. 1 1
      tests/sysvars/programs/sysvars/src/lib.rs
  78. 5 5
      tests/tictactoe/programs/tictactoe/src/lib.rs
  79. 1 1
      tests/typescript/programs/typescript/src/lib.rs
  80. 7 7
      tests/zero-copy/programs/zero-copy/src/lib.rs
  81. 1 1
      tests/zero-copy/programs/zero-cpi/src/lib.rs
  82. 16 4
      ts/src/error.ts

+ 2 - 0
.github/workflows/tests.yaml

@@ -121,6 +121,7 @@ jobs:
     name: Setup Client Example Test
     runs-on: ubuntu-18.04
     strategy:
+      fail-fast: false
       matrix:
         node:
           - path: tests/events/
@@ -232,6 +233,7 @@ jobs:
     name: Test ${{ matrix.node.path }}
     runs-on: ubuntu-18.04
     strategy:
+      fail-fast: false
       matrix:
         node:
           - cmd: cd tests/sysvars && anchor test

+ 5 - 0
CHANGELOG.md

@@ -25,6 +25,11 @@ incremented for features.
 * lang: Enforce that the payer for an init-ed account be marked `mut` ([#1271](https://github.com/project-serum/anchor/pull/1271)).
 * lang: All error-related code is now in the error module ([#1426](https://github.com/project-serum/anchor/pull/1426)).
 * lang: Require doc comments when using AccountInfo or UncheckedAccount types ([#1452](https://github.com/project-serum/anchor/pull/1452)).
+* lang: add [`error!`](https://docs.rs/anchor-lang/latest/anchor_lang/prelude/macro.error.html) and [`err!`](https://docs.rs/anchor-lang/latest/anchor_lang/prelude/macro.err.html) macro and `Result` type ([#1462](https://github.com/project-serum/anchor/pull/1462)).
+This change will break most programs. Do the following to upgrade:
+     * change all `ProgramResult`'s to `Result<()>` 
+     * change `#[error]` to `#[error_code]`
+     * change all `Err(MyError::SomeError.into())` to `Err(error!(MyError::SomeError))` and all `Err(ProgramError::SomeProgramError)` to `Err(ProgramError::SomeProgramError.into())` or `Err(Error::from(ProgramError::SomeProgramError).with_source(source!()))` to provide file and line source of the error (`with_source` is most useful with `ProgramError`s. `error!` already adds source information for custom and anchor internal errors).
 
 ## [0.21.0] - 2022-02-07
 

+ 2 - 0
client/src/lib.rs

@@ -376,6 +376,8 @@ pub enum ClientError {
     #[error("Account not found")]
     AccountNotFound,
     #[error("{0}")]
+    AnchorError(#[from] anchor_lang::error::Error),
+    #[error("{0}")]
     ProgramError(#[from] ProgramError),
     #[error("{0}")]
     SolanaClientError(#[from] SolanaClientError),

+ 8 - 13
docs/src/tutorials/tutorial-4.md

@@ -1,8 +1,8 @@
 # Errors
 
 If you've ever programmed on a blockchain, you've probably been frustrated by
-either non existant or opaque error codes. Anchor attempts to address this by
-providing the `#[error]` attribute, which can be used to create typed Errors with
+either non existent or opaque error codes. Anchor attempts to address this by
+providing the `#[error_code]` attribute, which can be used to create typed Errors with
 descriptive messages that automatically propagate to the client.
 
 ## Defining a Program
@@ -16,31 +16,26 @@ use anchor_lang::prelude::*;
 mod errors {
     use super::*;
     pub fn hello(_ctx: Context<Hello>) -> Result<()> {
-        Err(ErrorCode::Hello.into())
+        Err(error!(ErrorCode::Hello))
     }
 }
 
 #[derive(Accounts)]
 pub struct Hello {}
 
-#[error]
+#[error_code]
 pub enum ErrorCode {
     #[msg("This is an error message clients will automatically display")]
     Hello,
 }
 ```
 
-Observe the [#[error]](https://docs.rs/anchor-lang/latest/anchor_lang/attr.error.html) attribute on the `ErrorCode` enum. This macro generates two types: an `Error` and a `Result`, both of which can be used when returning from your program.
+Observe the [#[error_code]](https://docs.rs/anchor-lang/latest/anchor_lang/attr.error_code.html) attribute on the `ErrorCode` enum.
+This macro generates internal anchor code that helps anchor turn the error code into an error and display it properly.
 
-To use the `Error`, you can simply use the user defined `ErrorCode` with Rust's [From](https://doc.rust-lang.org/std/convert/trait.From.html) trait. If you're unfamiliar with `From`, no worries. Just know that you need to either call
-`.into()` when using your `ErrorCode`. Or use Rust's `?` operator, when returning an error.
-Both of these will automatically convert *into* the correct `Error`.
+To create an error, use the [`error!`](https://docs.rs/anchor-lang/latest/anchor_lang/prelude/macro.error.html) macro together with an error code. This macro creates an [`AnchorError`](https://docs.rs/anchor-lang/latest/anchor_lang/error/struct.AnchorError.html) that includes helpful information like the file and line the error was created in.
 
-::: details
-What's the deal with this From stuff? Well, because the Solana runtime expects a [ProgramError](https://docs.rs/solana-program/1.5.5/solana_program/program_error/enum.ProgramError.html) in the return value. The framework needs to wrap the user defined error code into a
-`ProgramError::Code` variant, before returning. The alternative would be to use the
-`ProgramError` directly.
-:::
+To make writing errors even easier, anchor also provides the [`err!`](https://docs.rs/anchor-lang/latest/anchor_lang/prelude/macro.err.html and the [`require!`](https://docs.rs/anchor-lang/latest/anchor_lang/prelude/macro.require.html macros.
 
 ## Using the Client
 

+ 1 - 1
examples/tutorial/basic-0/programs/basic-0/src/lib.rs

@@ -5,7 +5,7 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
 #[program]
 mod basic_0 {
     use super::*;
-    pub fn initialize(_ctx: Context<Initialize>) -> ProgramResult {
+    pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
         Ok(())
     }
 }

+ 2 - 2
examples/tutorial/basic-1/programs/basic-1/src/lib.rs

@@ -6,13 +6,13 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
 mod basic_1 {
     use super::*;
 
-    pub fn initialize(ctx: Context<Initialize>, data: u64) -> ProgramResult {
+    pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
         let my_account = &mut ctx.accounts.my_account;
         my_account.data = data;
         Ok(())
     }
 
-    pub fn update(ctx: Context<Update>, data: u64) -> ProgramResult {
+    pub fn update(ctx: Context<Update>, data: u64) -> Result<()> {
         let my_account = &mut ctx.accounts.my_account;
         my_account.data = data;
         Ok(())

+ 2 - 2
examples/tutorial/basic-2/programs/basic-2/src/lib.rs

@@ -6,14 +6,14 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
 mod basic_2 {
     use super::*;
 
-    pub fn create(ctx: Context<Create>, authority: Pubkey) -> ProgramResult {
+    pub fn create(ctx: Context<Create>, authority: Pubkey) -> Result<()> {
         let counter = &mut ctx.accounts.counter;
         counter.authority = authority;
         counter.count = 0;
         Ok(())
     }
 
-    pub fn increment(ctx: Context<Increment>) -> ProgramResult {
+    pub fn increment(ctx: Context<Increment>) -> Result<()> {
         let counter = &mut ctx.accounts.counter;
         counter.count += 1;
         Ok(())

+ 1 - 1
examples/tutorial/basic-3/programs/puppet-master/src/lib.rs

@@ -9,7 +9,7 @@ declare_id!("HmbTLCmaGvZhKnn1Zfa1JVnp7vkMV4DYVxPLWBVoN65L");
 #[program]
 mod puppet_master {
     use super::*;
-    pub fn pull_strings(ctx: Context<PullStrings>, data: u64) -> ProgramResult {
+    pub fn pull_strings(ctx: Context<PullStrings>, data: u64) -> anchor_lang::Result<()> {
         let cpi_program = ctx.accounts.puppet_program.to_account_info();
         let cpi_accounts = SetData {
             puppet: ctx.accounts.puppet.to_account_info(),

+ 2 - 2
examples/tutorial/basic-3/programs/puppet/src/lib.rs

@@ -5,11 +5,11 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
 #[program]
 pub mod puppet {
     use super::*;
-    pub fn initialize(_ctx: Context<Initialize>) -> ProgramResult {
+    pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
         Ok(())
     }
 
-    pub fn set_data(ctx: Context<SetData>, data: u64) -> ProgramResult {
+    pub fn set_data(ctx: Context<SetData>, data: u64) -> Result<()> {
         let puppet = &mut ctx.accounts.puppet;
         puppet.data = data;
         Ok(())

+ 4 - 4
examples/tutorial/basic-4/programs/basic-4/src/lib.rs

@@ -14,16 +14,16 @@ pub mod basic_4 {
     }
 
     impl Counter {
-        pub fn new(ctx: Context<Auth>) -> Result<Self> {
+        pub fn new(ctx: Context<Auth>) -> anchor_lang::Result<Self> {
             Ok(Self {
                 authority: *ctx.accounts.authority.key,
                 count: 0,
             })
         }
 
-        pub fn increment(&mut self, ctx: Context<Auth>) -> Result<()> {
+        pub fn increment(&mut self, ctx: Context<Auth>) -> anchor_lang::Result<()> {
             if &self.authority != ctx.accounts.authority.key {
-                return Err(ErrorCode::Unauthorized.into());
+                return Err(error!(ErrorCode::Unauthorized));
             }
             self.count += 1;
             Ok(())
@@ -37,7 +37,7 @@ pub struct Auth<'info> {
 }
 // #endregion code
 
-#[error]
+#[error_code]
 pub enum ErrorCode {
     #[msg("You are not authorized to perform this action.")]
     Unauthorized,

+ 12 - 11
lang/attribute/account/src/lib.rs

@@ -146,7 +146,7 @@ pub fn account(
                 // It's expected on-chain programs deserialize via zero-copy.
                 #[automatically_derived]
                 impl #impl_gen anchor_lang::AccountDeserialize for #account_name #type_gen #where_clause {
-                    fn try_deserialize(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
+                    fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
                         if buf.len() < #discriminator.len() {
                             return Err(anchor_lang::error::ErrorCode::AccountDiscriminatorNotFound.into());
                         }
@@ -157,7 +157,7 @@ pub fn account(
                         Self::try_deserialize_unchecked(buf)
                     }
 
-                    fn try_deserialize_unchecked(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
+                    fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
                         let data: &[u8] = &buf[8..];
                         // Re-interpret raw bytes into the POD data structure.
                         let account = anchor_lang::__private::bytemuck::from_bytes(data);
@@ -175,20 +175,21 @@ pub fn account(
 
                 #[automatically_derived]
                 impl #impl_gen anchor_lang::AccountSerialize for #account_name #type_gen #where_clause {
-                    fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> std::result::Result<(), ProgramError> {
-                        writer.write_all(&#discriminator).map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotSerialize)?;
-                        AnchorSerialize::serialize(
-                            self,
-                            writer
-                        )
-                            .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotSerialize)?;
+                    fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> anchor_lang::Result<()> {
+                        if writer.write_all(&#discriminator).is_err() {
+                            return Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into());
+                        }
+
+                        if AnchorSerialize::serialize(self, writer).is_err() {
+                            return Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into());
+                        }
                         Ok(())
                     }
                 }
 
                 #[automatically_derived]
                 impl #impl_gen anchor_lang::AccountDeserialize for #account_name #type_gen #where_clause {
-                    fn try_deserialize(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
+                    fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
                         if buf.len() < #discriminator.len() {
                             return Err(anchor_lang::error::ErrorCode::AccountDiscriminatorNotFound.into());
                         }
@@ -199,7 +200,7 @@ pub fn account(
                         Self::try_deserialize_unchecked(buf)
                     }
 
-                    fn try_deserialize_unchecked(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
+                    fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
                         let mut data: &[u8] = &buf[8..];
                         AnchorDeserialize::deserialize(&mut data)
                             .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into())

+ 1 - 1
lang/attribute/error/Cargo.toml

@@ -17,4 +17,4 @@ anchor-debug = ["anchor-syn/anchor-debug"]
 proc-macro2 = "1.0"
 quote = "1.0"
 syn = { version = "1.0.60", features = ["full"] }
-anchor-syn = { path = "../../syn", version = "0.21.0" }
+anchor-syn = { path = "../../syn", version = "0.21.0" }

+ 74 - 8
lang/attribute/error/src/lib.rs

@@ -1,9 +1,12 @@
 extern crate proc_macro;
 
-use anchor_syn::codegen::error as error_codegen;
-use anchor_syn::parser::error as error_parser;
+use proc_macro::TokenStream;
+use quote::quote;
+
+use anchor_syn::parser::error::{self as error_parser, ErrorWithAccountNameInput};
 use anchor_syn::ErrorArgs;
-use syn::parse_macro_input;
+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
 /// used as return types from Anchor instruction handlers. Importantly, the
@@ -21,14 +24,14 @@ use syn::parse_macro_input;
 /// mod errors {
 ///     use super::*;
 ///     pub fn hello(_ctx: Context<Hello>) -> Result<()> {
-///         Err(MyError::Hello.into())
+///         Err(error!(MyError::Hello))
 ///     }
 /// }
 ///
 /// #[derive(Accounts)]
 /// pub struct Hello {}
 ///
-/// #[error]
+/// #[error_code]
 /// pub enum MyError {
 ///     #[msg("This is an error message clients will automatically display")]
 ///     Hello,
@@ -40,14 +43,14 @@ use syn::parse_macro_input;
 /// [`ProgramError`](../solana_program/enum.ProgramError.html), which is used
 /// pervasively, throughout solana program crates. The generated `Error` type
 /// should almost never be used directly, as the user defined error is
-/// preferred. In the example above, `MyError::Hello.into()`.
+/// preferred. In the example above, `error!(MyError::Hello)`.
 ///
 /// # Msg
 ///
 /// The `#[msg(..)]` attribute is inert, and is used only as a marker so that
 /// parsers  and IDLs can map error codes to error messages.
 #[proc_macro_attribute]
-pub fn error(
+pub fn error_code(
     args: proc_macro::TokenStream,
     input: proc_macro::TokenStream,
 ) -> proc_macro::TokenStream {
@@ -56,6 +59,69 @@ pub fn error(
         false => Some(parse_macro_input!(args as ErrorArgs)),
     };
     let mut error_enum = parse_macro_input!(input as syn::ItemEnum);
-    let error = error_codegen::generate(error_parser::parse(&mut error_enum, args));
+    let error = codegen::error::generate(error_parser::parse(&mut error_enum, args));
     proc_macro::TokenStream::from(error)
 }
+
+/// Generates an [`Error::AnchorError`](../../anchor_lang/error/enum.Error.html) that includes file and line information.
+///
+/// # Example
+/// ```rust,ignore
+/// #[program]
+/// mod errors {
+///     use super::*;
+///     pub fn example(_ctx: Context<Example>) -> Result<()> {
+///         Err(error!(MyError::Hello))
+///     }
+/// }
+///
+/// #[error_code]
+/// pub enum MyError {
+///     #[msg("This is an error message clients will automatically display")]
+///     Hello,
+/// }
+/// ```
+#[proc_macro]
+pub fn error(ts: proc_macro::TokenStream) -> TokenStream {
+    let input = parse_macro_input!(ts as ErrorInput);
+    let error_code = input.error_code;
+    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 {
+                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
+            }
+        )
+    })
+}

+ 8 - 8
lang/attribute/interface/src/lib.rs

@@ -26,7 +26,7 @@ use syn::parse_macro_input;
 ///
 /// #[interface]
 /// pub trait Auth<'info, T: Accounts<'info>> {
-///     fn is_authorized(ctx: Context<T>, current: u64, new: u64) -> ProgramResult;
+///     fn is_authorized(ctx: Context<T>, current: u64, new: u64) -> anchor_lang::Result<()>;
 /// }
 ///
 /// #[program]
@@ -74,13 +74,13 @@ use syn::parse_macro_input;
 /// impl<'info> SetCount<'info> {
 ///     pub fn accounts(counter: &Counter, ctx: &Context<SetCount>) -> Result<()> {
 ///         if ctx.accounts.auth_program.key != &counter.auth_program {
-///             return Err(ErrorCode::InvalidAuthProgram.into());
+///             return Err(error!(ErrorCode::InvalidAuthProgram));
 ///         }
 ///         Ok(())
 ///     }
 /// }
 ///
-/// #[error]
+/// #[error_code]
 /// pub enum ErrorCode {
 ///     #[msg("Invalid auth program.")]
 ///     InvalidAuthProgram,
@@ -104,14 +104,14 @@ use syn::parse_macro_input;
 ///     pub struct CounterAuth;
 ///
 ///     impl<'info> Auth<'info, Empty> for CounterAuth {
-///         fn is_authorized(_ctx: Context<Empty>, current: u64, new: u64) -> ProgramResult {
+///         fn is_authorized(_ctx: Context<Empty>, current: u64, new: u64) -> Result<()> {
 ///             if current % 2 == 0 {
 ///                 if new % 2 == 0 {
-///                     return Err(ProgramError::Custom(50)); // Arbitrary error code.
+///                     return Err(ProgramError::Custom(50).into()); // Arbitrary error code.
 ///                 }
 ///             } else {
 ///                 if new % 2 == 1 {
-///                     return Err(ProgramError::Custom(60)); // Arbitrary error code.
+///                     return Err(ProgramError::Custom(60).into()); // Arbitrary error code.
 ///                 }
 ///             }
 ///             Ok(())
@@ -200,7 +200,7 @@ pub fn interface(
                 pub fn #method_name<'a,'b, 'c, 'info, T: anchor_lang::Accounts<'info> + anchor_lang::ToAccountMetas + anchor_lang::ToAccountInfos<'info>>(
                     ctx: anchor_lang::context::CpiContext<'a, 'b, 'c, 'info, T>,
                     #(#args),*
-                ) -> anchor_lang::solana_program::entrypoint::ProgramResult {
+                ) -> anchor_lang::Result<()> {
                     #args_struct
 
                     let ix = {
@@ -224,7 +224,7 @@ pub fn interface(
                         &ix,
                         &acc_infos,
                         ctx.signer_seeds,
-                    )
+                    ).map_err(Into::into)
                 }
             }
         })

+ 6 - 6
lang/attribute/state/src/lib.rs

@@ -42,18 +42,18 @@ pub fn state(
             // as the initialized value. Use the default implementation.
             quote! {
                 impl anchor_lang::__private::AccountSize for #struct_ident {
-                    fn size(&self) -> std::result::Result<u64, anchor_lang::solana_program::program_error::ProgramError> {
+                    fn size(&self) -> anchor_lang::Result<u64> {
                         Ok(8 + self
-                           .try_to_vec()
-                           .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotSerialize)?
-                           .len() as u64)
+                            .try_to_vec()
+                            .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotSerialize)?
+                            .len() as u64)
                     }
                 }
             }
         } else if is_zero_copy {
             quote! {
                 impl anchor_lang::__private::AccountSize for #struct_ident {
-                    fn size(&self) -> std::result::Result<u64, anchor_lang::solana_program::program_error::ProgramError> {
+                    fn size(&self) -> anchor_lang::Result<u64> {
                         let len = anchor_lang::__private::bytemuck::bytes_of(self).len() as u64;
                         Ok(8 + len)
                     }
@@ -64,7 +64,7 @@ pub fn state(
             // Size override given to the macro. Use it.
             quote! {
                 impl anchor_lang::__private::AccountSize for #struct_ident {
-                    fn size(&self) -> std::result::Result<u64, anchor_lang::solana_program::program_error::ProgramError> {
+                    fn size(&self) -> anchor_lang::Result<u64> {
                         Ok(#size)
                     }
                 }

+ 1 - 1
lang/derive/accounts/src/lib.rs

@@ -22,7 +22,7 @@ use syn::parse_macro_input;
 ///
 /// ```ignore
 /// ...
-/// pub fn initialize(ctx: Context<Create>, bump: u8, authority: Pubkey, data: u64) -> ProgramResult {
+/// pub fn initialize(ctx: Context<Create>, bump: u8, authority: Pubkey, data: u64) -> anchor_lang::Result<()> {
 ///     ...
 ///     Ok(())
 /// }

+ 11 - 13
lang/src/accounts/account.rs

@@ -3,9 +3,7 @@
 use crate::error::ErrorCode;
 use crate::*;
 use solana_program::account_info::AccountInfo;
-use solana_program::entrypoint::ProgramResult;
 use solana_program::instruction::AccountMeta;
-use solana_program::program_error::ProgramError;
 use solana_program::pubkey::Pubkey;
 use std::collections::BTreeMap;
 use std::fmt;
@@ -44,7 +42,7 @@ use std::ops::{Deref, DerefMut};
 /// #[program]
 /// mod hello_anchor {
 ///     use super::*;
-///     pub fn set_data(ctx: Context<SetData>, data: u64) -> ProgramResult {
+///     pub fn set_data(ctx: Context<SetData>, data: u64) -> Result<()> {
 ///         if (*ctx.accounts.auth_account).authorized {
 ///             (*ctx.accounts.my_account).data = data;
 ///         }
@@ -104,7 +102,7 @@ use std::ops::{Deref, DerefMut};
 /// // "try_deserialize_unchecked" by default which is what we want here
 /// // because non-anchor accounts don't have a discriminator to check
 /// impl anchor_lang::AccountDeserialize for Mint {
-///     fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError> {
+///     fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self> {
 ///         spl_token::state::Mint::unpack(buf).map(Mint)
 ///     }
 /// }
@@ -161,7 +159,7 @@ use std::ops::{Deref, DerefMut};
 ///     pub fn set_initial_admin(
 ///         ctx: Context<SetInitialAdmin>,
 ///         admin_key: Pubkey
-///     ) -> ProgramResult {
+///     ) -> Result<()> {
 ///         ctx.accounts.admin_settings.admin_key = admin_key;
 ///         Ok(())
 ///     }
@@ -243,7 +241,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> Account<'a, T
 
     /// Deserializes the given `info` into a `Account`.
     #[inline(never)]
-    pub fn try_from(info: &AccountInfo<'a>) -> Result<Account<'a, T>, ProgramError> {
+    pub fn try_from(info: &AccountInfo<'a>) -> Result<Account<'a, T>> {
         if info.owner == &system_program::ID && info.lamports() == 0 {
             return Err(ErrorCode::AccountNotInitialized.into());
         }
@@ -258,7 +256,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> Account<'a, T
     /// the account discriminator. Be careful when using this and avoid it if
     /// possible.
     #[inline(never)]
-    pub fn try_from_unchecked(info: &AccountInfo<'a>) -> Result<Account<'a, T>, ProgramError> {
+    pub fn try_from_unchecked(info: &AccountInfo<'a>) -> Result<Account<'a, T>> {
         if info.owner == &system_program::ID && info.lamports() == 0 {
             return Err(ErrorCode::AccountNotInitialized.into());
         }
@@ -274,7 +272,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> Account<'a, T
 
     /// Reloads the account from storage. This is useful, for example, when
     /// observing side effects after CPI.
-    pub fn reload(&mut self) -> ProgramResult {
+    pub fn reload(&mut self) -> Result<()> {
         let mut data: &[u8] = &self.info.try_borrow_data()?;
         self.account = T::try_deserialize(&mut data)?;
         Ok(())
@@ -288,7 +286,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> Account<'a, T
     ///
     /// Instead of this:
     /// ```ignore
-    /// pub fn new_user(ctx: Context<CreateUser>, new_user:User) -> ProgramResult {
+    /// pub fn new_user(ctx: Context<CreateUser>, new_user:User) -> Result<()> {
     ///     (*ctx.accounts.user_to_create).name = new_user.name;
     ///     (*ctx.accounts.user_to_create).age = new_user.age;
     ///     (*ctx.accounts.user_to_create).address = new_user.address;
@@ -296,7 +294,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> Account<'a, T
     /// ```
     /// You can do this:
     /// ```ignore
-    /// pub fn new_user(ctx: Context<CreateUser>, new_user:User) -> ProgramResult {
+    /// pub fn new_user(ctx: Context<CreateUser>, new_user:User) -> Result<()> {
     ///     ctx.accounts.user_to_create.set_inner(new_user);
     /// }
     /// ```
@@ -316,7 +314,7 @@ where
         accounts: &mut &[AccountInfo<'info>],
         _ix_data: &[u8],
         _bumps: &mut BTreeMap<String, u8>,
-    ) -> Result<Self, ProgramError> {
+    ) -> Result<Self> {
         if accounts.is_empty() {
             return Err(ErrorCode::AccountNotEnoughKeys.into());
         }
@@ -329,7 +327,7 @@ where
 impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> AccountsExit<'info>
     for Account<'info, T>
 {
-    fn exit(&self, program_id: &Pubkey) -> ProgramResult {
+    fn exit(&self, program_id: &Pubkey) -> Result<()> {
         // Only persist if the owner is the current program.
         if &T::owner() == program_id {
             let info = self.to_account_info();
@@ -345,7 +343,7 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> AccountsEx
 impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> AccountsClose<'info>
     for Account<'info, T>
 {
-    fn close(&self, sol_destination: AccountInfo<'info>) -> ProgramResult {
+    fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()> {
         crate::common::close(self.to_account_info(), sol_destination)
     }
 }

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

@@ -3,10 +3,9 @@
 //! should be used instead.
 
 use crate::error::ErrorCode;
-use crate::{Accounts, AccountsExit, ToAccountInfos, ToAccountMetas};
+use crate::{Accounts, AccountsExit, Result, ToAccountInfos, ToAccountMetas};
 use solana_program::account_info::AccountInfo;
 use solana_program::instruction::AccountMeta;
-use solana_program::program_error::ProgramError;
 use solana_program::pubkey::Pubkey;
 use std::collections::BTreeMap;
 
@@ -16,7 +15,7 @@ impl<'info> Accounts<'info> for AccountInfo<'info> {
         accounts: &mut &[AccountInfo<'info>],
         _ix_data: &[u8],
         _bumps: &mut BTreeMap<String, u8>,
-    ) -> Result<Self, ProgramError> {
+    ) -> Result<Self> {
         if accounts.is_empty() {
             return Err(ErrorCode::AccountNotEnoughKeys.into());
         }

+ 12 - 16
lang/src/accounts/account_loader.rs

@@ -2,14 +2,12 @@
 
 use crate::error::ErrorCode;
 use crate::{
-    Accounts, AccountsClose, AccountsExit, Owner, ToAccountInfo, ToAccountInfos, ToAccountMetas,
-    ZeroCopy,
+    Accounts, AccountsClose, AccountsExit, Owner, Result, ToAccountInfo, ToAccountInfos,
+    ToAccountMetas, ZeroCopy,
 };
 use arrayref::array_ref;
 use solana_program::account_info::AccountInfo;
-use solana_program::entrypoint::ProgramResult;
 use solana_program::instruction::AccountMeta;
-use solana_program::program_error::ProgramError;
 use solana_program::pubkey::Pubkey;
 use std::cell::{Ref, RefMut};
 use std::collections::BTreeMap;
@@ -52,14 +50,14 @@ use std::ops::DerefMut;
 /// pub mod bar {
 ///     use super::*;
 ///
-///     pub fn create_bar(ctx: Context<CreateBar>, data: u64) -> ProgramResult {
+///     pub fn create_bar(ctx: Context<CreateBar>, data: u64) -> Result<()> {
 ///         let bar = &mut ctx.accounts.bar.load_init()?;
 ///         bar.authority = ctx.accounts.authority.key();
 ///         bar.data = data;
 ///         Ok(())
 ///     }
 ///
-///     pub fn update_bar(ctx: Context<UpdateBar>, data: u64) -> ProgramResult {
+///     pub fn update_bar(ctx: Context<UpdateBar>, data: u64) -> Result<()> {
 ///         (*ctx.accounts.bar.load_mut()?).data = data;
 ///         Ok(())
 ///     }
@@ -119,9 +117,7 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> {
 
     /// Constructs a new `Loader` from a previously initialized account.
     #[inline(never)]
-    pub fn try_from(
-        acc_info: &AccountInfo<'info>,
-    ) -> Result<AccountLoader<'info, T>, ProgramError> {
+    pub fn try_from(acc_info: &AccountInfo<'info>) -> Result<AccountLoader<'info, T>> {
         if acc_info.owner != &T::owner() {
             return Err(ErrorCode::AccountOwnedByWrongProgram.into());
         }
@@ -140,7 +136,7 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> {
     pub fn try_from_unchecked(
         _program_id: &Pubkey,
         acc_info: &AccountInfo<'info>,
-    ) -> Result<AccountLoader<'info, T>, ProgramError> {
+    ) -> Result<AccountLoader<'info, T>> {
         if acc_info.owner != &T::owner() {
             return Err(ErrorCode::AccountOwnedByWrongProgram.into());
         }
@@ -148,7 +144,7 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> {
     }
 
     /// Returns a Ref to the account data structure for reading.
-    pub fn load(&self) -> Result<Ref<T>, ProgramError> {
+    pub fn load(&self) -> Result<Ref<T>> {
         let data = self.acc_info.try_borrow_data()?;
 
         let disc_bytes = array_ref![data, 0, 8];
@@ -162,7 +158,7 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> {
     }
 
     /// Returns a `RefMut` to the account data structure for reading or writing.
-    pub fn load_mut(&self) -> Result<RefMut<T>, ProgramError> {
+    pub fn load_mut(&self) -> Result<RefMut<T>> {
         // AccountInfo api allows you to borrow mut even if the account isn't
         // writable, so add this check for a better dev experience.
         if !self.acc_info.is_writable {
@@ -183,7 +179,7 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> {
 
     /// Returns a `RefMut` to the account data structure for reading or writing.
     /// Should only be called once, when the account is being initialized.
-    pub fn load_init(&self) -> Result<RefMut<T>, ProgramError> {
+    pub fn load_init(&self) -> Result<RefMut<T>> {
         // AccountInfo api allows you to borrow mut even if the account isn't
         // writable, so add this check for a better dev experience.
         if !self.acc_info.is_writable {
@@ -213,7 +209,7 @@ impl<'info, T: ZeroCopy + Owner> Accounts<'info> for AccountLoader<'info, T> {
         accounts: &mut &[AccountInfo<'info>],
         _ix_data: &[u8],
         _bumps: &mut BTreeMap<String, u8>,
-    ) -> Result<Self, ProgramError> {
+    ) -> Result<Self> {
         if accounts.is_empty() {
             return Err(ErrorCode::AccountNotEnoughKeys.into());
         }
@@ -226,7 +222,7 @@ impl<'info, T: ZeroCopy + Owner> Accounts<'info> for AccountLoader<'info, T> {
 
 impl<'info, T: ZeroCopy + Owner> AccountsExit<'info> for AccountLoader<'info, T> {
     // The account *cannot* be loaded when this is called.
-    fn exit(&self, _program_id: &Pubkey) -> ProgramResult {
+    fn exit(&self, _program_id: &Pubkey) -> Result<()> {
         let mut data = self.acc_info.try_borrow_mut_data()?;
         let dst: &mut [u8] = &mut data;
         let mut cursor = std::io::Cursor::new(dst);
@@ -236,7 +232,7 @@ impl<'info, T: ZeroCopy + Owner> AccountsExit<'info> for AccountLoader<'info, T>
 }
 
 impl<'info, T: ZeroCopy + Owner> AccountsClose<'info> for AccountLoader<'info, T> {
-    fn close(&self, sol_destination: AccountInfo<'info>) -> ProgramResult {
+    fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()> {
         crate::common::close(self.to_account_info(), sol_destination)
     }
 }

+ 4 - 6
lang/src/accounts/boxed.rs

@@ -13,11 +13,9 @@
 //! }
 //! ```
 
-use crate::{Accounts, AccountsClose, AccountsExit, ToAccountInfos, ToAccountMetas};
+use crate::{Accounts, AccountsClose, AccountsExit, Result, ToAccountInfos, ToAccountMetas};
 use solana_program::account_info::AccountInfo;
-use solana_program::entrypoint::ProgramResult;
 use solana_program::instruction::AccountMeta;
-use solana_program::program_error::ProgramError;
 use solana_program::pubkey::Pubkey;
 use std::collections::BTreeMap;
 use std::ops::Deref;
@@ -28,13 +26,13 @@ impl<'info, T: Accounts<'info>> Accounts<'info> for Box<T> {
         accounts: &mut &[AccountInfo<'info>],
         ix_data: &[u8],
         bumps: &mut BTreeMap<String, u8>,
-    ) -> Result<Self, ProgramError> {
+    ) -> Result<Self> {
         T::try_accounts(program_id, accounts, ix_data, bumps).map(Box::new)
     }
 }
 
 impl<'info, T: AccountsExit<'info>> AccountsExit<'info> for Box<T> {
-    fn exit(&self, program_id: &Pubkey) -> ProgramResult {
+    fn exit(&self, program_id: &Pubkey) -> Result<()> {
         T::exit(Deref::deref(self), program_id)
     }
 }
@@ -52,7 +50,7 @@ impl<T: ToAccountMetas> ToAccountMetas for Box<T> {
 }
 
 impl<'info, T: AccountsClose<'info>> AccountsClose<'info> for Box<T> {
-    fn close(&self, sol_destination: AccountInfo<'info>) -> ProgramResult {
+    fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()> {
         T::close(self, sol_destination)
     }
 }

+ 4 - 6
lang/src/accounts/cpi_account.rs

@@ -1,9 +1,7 @@
 use crate::*;
 use crate::{error::ErrorCode, prelude::Account};
 use solana_program::account_info::AccountInfo;
-use solana_program::entrypoint::ProgramResult;
 use solana_program::instruction::AccountMeta;
-use solana_program::program_error::ProgramError;
 use solana_program::pubkey::Pubkey;
 use std::collections::BTreeMap;
 use std::ops::{Deref, DerefMut};
@@ -23,7 +21,7 @@ impl<'a, T: AccountDeserialize + Clone> CpiAccount<'a, T> {
     }
 
     /// Deserializes the given `info` into a `CpiAccount`.
-    pub fn try_from(info: &AccountInfo<'a>) -> Result<CpiAccount<'a, T>, ProgramError> {
+    pub fn try_from(info: &AccountInfo<'a>) -> Result<CpiAccount<'a, T>> {
         let mut data: &[u8] = &info.try_borrow_data()?;
         Ok(CpiAccount::new(
             info.clone(),
@@ -31,13 +29,13 @@ impl<'a, T: AccountDeserialize + Clone> CpiAccount<'a, T> {
         ))
     }
 
-    pub fn try_from_unchecked(info: &AccountInfo<'a>) -> Result<CpiAccount<'a, T>, ProgramError> {
+    pub fn try_from_unchecked(info: &AccountInfo<'a>) -> Result<CpiAccount<'a, T>> {
         Self::try_from(info)
     }
 
     /// Reloads the account from storage. This is useful, for example, when
     /// observing side effects after CPI.
-    pub fn reload(&mut self) -> ProgramResult {
+    pub fn reload(&mut self) -> Result<()> {
         let mut data: &[u8] = &self.info.try_borrow_data()?;
         self.account = Box::new(T::try_deserialize(&mut data)?);
         Ok(())
@@ -55,7 +53,7 @@ where
         accounts: &mut &[AccountInfo<'info>],
         _ix_data: &[u8],
         _bumps: &mut BTreeMap<String, u8>,
-    ) -> Result<Self, ProgramError> {
+    ) -> Result<Self> {
         if accounts.is_empty() {
             return Err(ErrorCode::AccountNotEnoughKeys.into());
         }

+ 4 - 4
lang/src/accounts/cpi_state.rs

@@ -2,11 +2,11 @@ use crate::error::ErrorCode;
 #[allow(deprecated)]
 use crate::{accounts::state::ProgramState, context::CpiStateContext};
 use crate::{
-    AccountDeserialize, AccountSerialize, Accounts, AccountsExit, ToAccountInfos, ToAccountMetas,
+    AccountDeserialize, AccountSerialize, Accounts, AccountsExit, Result, ToAccountInfos,
+    ToAccountMetas,
 };
 use solana_program::account_info::AccountInfo;
 use solana_program::instruction::AccountMeta;
-use solana_program::program_error::ProgramError;
 use solana_program::pubkey::Pubkey;
 use std::collections::BTreeMap;
 use std::ops::{Deref, DerefMut};
@@ -35,7 +35,7 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Clone> CpiState<'info, T>
 
     /// Deserializes the given `info` into a `CpiState`.
     #[inline(never)]
-    pub fn try_from(info: &AccountInfo<'info>) -> Result<CpiState<'info, T>, ProgramError> {
+    pub fn try_from(info: &AccountInfo<'info>) -> Result<CpiState<'info, T>> {
         let mut data: &[u8] = &info.try_borrow_data()?;
         Ok(CpiState::new(info.clone(), T::try_deserialize(&mut data)?))
     }
@@ -72,7 +72,7 @@ where
         accounts: &mut &[AccountInfo<'info>],
         _ix_data: &[u8],
         _bumps: &mut BTreeMap<String, u8>,
-    ) -> Result<Self, ProgramError> {
+    ) -> Result<Self> {
         if accounts.is_empty() {
             return Err(ErrorCode::AccountNotEnoughKeys.into());
         }

+ 10 - 11
lang/src/accounts/loader.rs

@@ -1,12 +1,11 @@
 use crate::error::ErrorCode;
 use crate::{
-    Accounts, AccountsClose, AccountsExit, ToAccountInfo, ToAccountInfos, ToAccountMetas, ZeroCopy,
+    Accounts, AccountsClose, AccountsExit, Result, ToAccountInfo, ToAccountInfos, ToAccountMetas,
+    ZeroCopy,
 };
 use arrayref::array_ref;
 use solana_program::account_info::AccountInfo;
-use solana_program::entrypoint::ProgramResult;
 use solana_program::instruction::AccountMeta;
-use solana_program::program_error::ProgramError;
 use solana_program::pubkey::Pubkey;
 use std::cell::{Ref, RefMut};
 use std::collections::BTreeMap;
@@ -57,7 +56,7 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
     pub fn try_from(
         program_id: &Pubkey,
         acc_info: &AccountInfo<'info>,
-    ) -> Result<Loader<'info, T>, ProgramError> {
+    ) -> Result<Loader<'info, T>> {
         if acc_info.owner != program_id {
             return Err(ErrorCode::AccountOwnedByWrongProgram.into());
         }
@@ -77,7 +76,7 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
     pub fn try_from_unchecked(
         program_id: &Pubkey,
         acc_info: &AccountInfo<'info>,
-    ) -> Result<Loader<'info, T>, ProgramError> {
+    ) -> Result<Loader<'info, T>> {
         if acc_info.owner != program_id {
             return Err(ErrorCode::AccountOwnedByWrongProgram.into());
         }
@@ -86,7 +85,7 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
 
     /// Returns a Ref to the account data structure for reading.
     #[allow(deprecated)]
-    pub fn load(&self) -> Result<Ref<T>, ProgramError> {
+    pub fn load(&self) -> Result<Ref<T>> {
         let data = self.acc_info.try_borrow_data()?;
 
         let disc_bytes = array_ref![data, 0, 8];
@@ -99,7 +98,7 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
 
     /// Returns a `RefMut` to the account data structure for reading or writing.
     #[allow(deprecated)]
-    pub fn load_mut(&self) -> Result<RefMut<T>, ProgramError> {
+    pub fn load_mut(&self) -> Result<RefMut<T>> {
         // AccountInfo api allows you to borrow mut even if the account isn't
         // writable, so add this check for a better dev experience.
         if !self.acc_info.is_writable {
@@ -121,7 +120,7 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
     /// Returns a `RefMut` to the account data structure for reading or writing.
     /// Should only be called once, when the account is being initialized.
     #[allow(deprecated)]
-    pub fn load_init(&self) -> Result<RefMut<T>, ProgramError> {
+    pub fn load_init(&self) -> Result<RefMut<T>> {
         // AccountInfo api allows you to borrow mut even if the account isn't
         // writable, so add this check for a better dev experience.
         if !self.acc_info.is_writable {
@@ -152,7 +151,7 @@ impl<'info, T: ZeroCopy> Accounts<'info> for Loader<'info, T> {
         accounts: &mut &[AccountInfo<'info>],
         _ix_data: &[u8],
         _bumps: &mut BTreeMap<String, u8>,
-    ) -> Result<Self, ProgramError> {
+    ) -> Result<Self> {
         if accounts.is_empty() {
             return Err(ErrorCode::AccountNotEnoughKeys.into());
         }
@@ -166,7 +165,7 @@ impl<'info, T: ZeroCopy> Accounts<'info> for Loader<'info, T> {
 #[allow(deprecated)]
 impl<'info, T: ZeroCopy> AccountsExit<'info> for Loader<'info, T> {
     // The account *cannot* be loaded when this is called.
-    fn exit(&self, _program_id: &Pubkey) -> ProgramResult {
+    fn exit(&self, _program_id: &Pubkey) -> Result<()> {
         let mut data = self.acc_info.try_borrow_mut_data()?;
         let dst: &mut [u8] = &mut data;
         let mut cursor = std::io::Cursor::new(dst);
@@ -177,7 +176,7 @@ impl<'info, T: ZeroCopy> AccountsExit<'info> for Loader<'info, T> {
 
 #[allow(deprecated)]
 impl<'info, T: ZeroCopy> AccountsClose<'info> for Loader<'info, T> {
-    fn close(&self, sol_destination: AccountInfo<'info>) -> ProgramResult {
+    fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()> {
         crate::common::close(self.to_account_info(), sol_destination)
     }
 }

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

@@ -5,7 +5,6 @@ use crate::*;
 use solana_program::account_info::AccountInfo;
 use solana_program::bpf_loader_upgradeable::{self, UpgradeableLoaderState};
 use solana_program::instruction::AccountMeta;
-use solana_program::program_error::ProgramError;
 use solana_program::pubkey::Pubkey;
 use std::collections::BTreeMap;
 use std::fmt;
@@ -85,7 +84,7 @@ impl<'a, T: Id + Clone> Program<'a, T> {
 
     /// Deserializes the given `info` into a `Program`.
     #[inline(never)]
-    pub fn try_from(info: &AccountInfo<'a>) -> Result<Program<'a, T>, ProgramError> {
+    pub fn try_from(info: &AccountInfo<'a>) -> Result<Program<'a, T>> {
         if info.key != &T::id() {
             return Err(ErrorCode::InvalidProgramId.into());
         }
@@ -137,7 +136,7 @@ where
         accounts: &mut &[AccountInfo<'info>],
         _ix_data: &[u8],
         _bumps: &mut BTreeMap<String, u8>,
-    ) -> Result<Self, ProgramError> {
+    ) -> Result<Self> {
         if accounts.is_empty() {
             return Err(ErrorCode::AccountNotEnoughKeys.into());
         }

+ 7 - 12
lang/src/accounts/program_account.rs

@@ -2,13 +2,11 @@
 use crate::accounts::cpi_account::CpiAccount;
 use crate::error::ErrorCode;
 use crate::{
-    AccountDeserialize, AccountSerialize, Accounts, AccountsClose, AccountsExit, ToAccountInfo,
-    ToAccountInfos, ToAccountMetas,
+    AccountDeserialize, AccountSerialize, Accounts, AccountsClose, AccountsExit, Result,
+    ToAccountInfo, ToAccountInfos, ToAccountMetas,
 };
 use solana_program::account_info::AccountInfo;
-use solana_program::entrypoint::ProgramResult;
 use solana_program::instruction::AccountMeta;
-use solana_program::program_error::ProgramError;
 use solana_program::pubkey::Pubkey;
 use std::collections::BTreeMap;
 use std::ops::{Deref, DerefMut};
@@ -37,10 +35,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramAccount<'a, T>
 
     /// Deserializes the given `info` into a `ProgramAccount`.
     #[inline(never)]
-    pub fn try_from(
-        program_id: &Pubkey,
-        info: &AccountInfo<'a>,
-    ) -> Result<ProgramAccount<'a, T>, ProgramError> {
+    pub fn try_from(program_id: &Pubkey, info: &AccountInfo<'a>) -> Result<ProgramAccount<'a, T>> {
         if info.owner != program_id {
             return Err(ErrorCode::AccountOwnedByWrongProgram.into());
         }
@@ -58,7 +53,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramAccount<'a, T>
     pub fn try_from_unchecked(
         program_id: &Pubkey,
         info: &AccountInfo<'a>,
-    ) -> Result<ProgramAccount<'a, T>, ProgramError> {
+    ) -> Result<ProgramAccount<'a, T>> {
         if info.owner != program_id {
             return Err(ErrorCode::AccountOwnedByWrongProgram.into());
         }
@@ -85,7 +80,7 @@ where
         accounts: &mut &[AccountInfo<'info>],
         _ix_data: &[u8],
         _bumps: &mut BTreeMap<String, u8>,
-    ) -> Result<Self, ProgramError> {
+    ) -> Result<Self> {
         if accounts.is_empty() {
             return Err(ErrorCode::AccountNotEnoughKeys.into());
         }
@@ -99,7 +94,7 @@ where
 impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AccountsExit<'info>
     for ProgramAccount<'info, T>
 {
-    fn exit(&self, _program_id: &Pubkey) -> ProgramResult {
+    fn exit(&self, _program_id: &Pubkey) -> Result<()> {
         let info = self.to_account_info();
         let mut data = info.try_borrow_mut_data()?;
         let dst: &mut [u8] = &mut data;
@@ -113,7 +108,7 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AccountsExit<'info
 impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AccountsClose<'info>
     for ProgramAccount<'info, T>
 {
-    fn close(&self, sol_destination: AccountInfo<'info>) -> ProgramResult {
+    fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()> {
         crate::common::close(self.to_account_info(), sol_destination)
     }
 }

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

@@ -3,7 +3,6 @@ use crate::error::ErrorCode;
 use crate::*;
 use solana_program::account_info::AccountInfo;
 use solana_program::instruction::AccountMeta;
-use solana_program::program_error::ProgramError;
 use solana_program::pubkey::Pubkey;
 use std::collections::BTreeMap;
 use std::ops::Deref;
@@ -47,7 +46,7 @@ impl<'info> Signer<'info> {
 
     /// Deserializes the given `info` into a `Signer`.
     #[inline(never)]
-    pub fn try_from(info: &AccountInfo<'info>) -> Result<Signer<'info>, ProgramError> {
+    pub fn try_from(info: &AccountInfo<'info>) -> Result<Signer<'info>> {
         if !info.is_signer {
             return Err(ErrorCode::AccountNotSigner.into());
         }
@@ -62,7 +61,7 @@ impl<'info> Accounts<'info> for Signer<'info> {
         accounts: &mut &[AccountInfo<'info>],
         _ix_data: &[u8],
         _bumps: &mut BTreeMap<String, u8>,
-    ) -> Result<Self, ProgramError> {
+    ) -> Result<Self> {
         if accounts.is_empty() {
             return Err(ErrorCode::AccountNotEnoughKeys.into());
         }

+ 5 - 10
lang/src/accounts/state.rs

@@ -2,13 +2,11 @@
 use crate::accounts::cpi_account::CpiAccount;
 use crate::error::ErrorCode;
 use crate::{
-    AccountDeserialize, AccountSerialize, Accounts, AccountsExit, ToAccountInfo, ToAccountInfos,
-    ToAccountMetas,
+    AccountDeserialize, AccountSerialize, Accounts, AccountsExit, Result, ToAccountInfo,
+    ToAccountInfos, ToAccountMetas,
 };
 use solana_program::account_info::AccountInfo;
-use solana_program::entrypoint::ProgramResult;
 use solana_program::instruction::AccountMeta;
-use solana_program::program_error::ProgramError;
 use solana_program::pubkey::Pubkey;
 use std::collections::BTreeMap;
 use std::ops::{Deref, DerefMut};
@@ -39,10 +37,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramState<'a, T> {
 
     /// Deserializes the given `info` into a `ProgramState`.
     #[inline(never)]
-    pub fn try_from(
-        program_id: &Pubkey,
-        info: &AccountInfo<'a>,
-    ) -> Result<ProgramState<'a, T>, ProgramError> {
+    pub fn try_from(program_id: &Pubkey, info: &AccountInfo<'a>) -> Result<ProgramState<'a, T>> {
         if info.owner != program_id {
             return Err(ErrorCode::AccountOwnedByWrongProgram.into());
         }
@@ -77,7 +72,7 @@ where
         accounts: &mut &[AccountInfo<'info>],
         _ix_data: &[u8],
         _bumps: &mut BTreeMap<String, u8>,
-    ) -> Result<Self, ProgramError> {
+    ) -> Result<Self> {
         if accounts.is_empty() {
             return Err(ErrorCode::AccountNotEnoughKeys.into());
         }
@@ -149,7 +144,7 @@ where
 impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AccountsExit<'info>
     for ProgramState<'info, T>
 {
-    fn exit(&self, _program_id: &Pubkey) -> ProgramResult {
+    fn exit(&self, _program_id: &Pubkey) -> Result<()> {
         let info = self.to_account_info();
         let mut data = info.try_borrow_mut_data()?;
         let dst: &mut [u8] = &mut data;

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

@@ -4,7 +4,6 @@ use crate::error::ErrorCode;
 use crate::*;
 use solana_program::account_info::AccountInfo;
 use solana_program::instruction::AccountMeta;
-use solana_program::program_error::ProgramError;
 use solana_program::pubkey::Pubkey;
 use solana_program::system_program;
 use std::collections::BTreeMap;
@@ -26,7 +25,7 @@ impl<'info> SystemAccount<'info> {
     }
 
     #[inline(never)]
-    pub fn try_from(info: &AccountInfo<'info>) -> Result<SystemAccount<'info>, ProgramError> {
+    pub fn try_from(info: &AccountInfo<'info>) -> Result<SystemAccount<'info>> {
         if *info.owner != system_program::ID {
             return Err(ErrorCode::AccountNotSystemOwned.into());
         }
@@ -41,7 +40,7 @@ impl<'info> Accounts<'info> for SystemAccount<'info> {
         accounts: &mut &[AccountInfo<'info>],
         _ix_data: &[u8],
         _bumps: &mut BTreeMap<String, u8>,
-    ) -> Result<Self, ProgramError> {
+    ) -> Result<Self> {
         if accounts.is_empty() {
             return Err(ErrorCode::AccountNotEnoughKeys.into());
         }

+ 4 - 7
lang/src/accounts/sysvar.rs

@@ -1,10 +1,9 @@
 //! Type validating that the account is a sysvar and deserializing it
 
 use crate::error::ErrorCode;
-use crate::{Accounts, AccountsExit, ToAccountInfos, ToAccountMetas};
+use crate::{Accounts, AccountsExit, Result, ToAccountInfos, ToAccountMetas};
 use solana_program::account_info::AccountInfo;
 use solana_program::instruction::AccountMeta;
-use solana_program::program_error::ProgramError;
 use solana_program::pubkey::Pubkey;
 use std::collections::BTreeMap;
 use std::fmt;
@@ -27,7 +26,7 @@ use std::ops::{Deref, DerefMut};
 ///     pub clock: Sysvar<'info, Clock>
 /// }
 /// // BETTER - via syscall in the instruction function
-/// fn better(ctx: Context<Better>) -> ProgramResult {
+/// fn better(ctx: Context<Better>) -> Result<()> {
 ///     let clock = Clock::get()?;
 /// }
 /// ```
@@ -46,9 +45,7 @@ impl<'info, T: solana_program::sysvar::Sysvar + fmt::Debug> fmt::Debug for Sysva
 }
 
 impl<'info, T: solana_program::sysvar::Sysvar> Sysvar<'info, T> {
-    pub fn from_account_info(
-        acc_info: &AccountInfo<'info>,
-    ) -> Result<Sysvar<'info, T>, ProgramError> {
+    pub fn from_account_info(acc_info: &AccountInfo<'info>) -> Result<Sysvar<'info, T>> {
         Ok(Sysvar {
             info: acc_info.clone(),
             account: T::from_account_info(acc_info)?,
@@ -71,7 +68,7 @@ impl<'info, T: solana_program::sysvar::Sysvar> Accounts<'info> for Sysvar<'info,
         accounts: &mut &[AccountInfo<'info>],
         _ix_data: &[u8],
         _bumps: &mut BTreeMap<String, u8>,
-    ) -> Result<Self, ProgramError> {
+    ) -> Result<Self> {
         if accounts.is_empty() {
             return Err(ErrorCode::AccountNotEnoughKeys.into());
         }

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

@@ -2,10 +2,9 @@
 //! that no checks are performed
 
 use crate::error::ErrorCode;
-use crate::{Accounts, AccountsExit, ToAccountInfos, ToAccountMetas};
+use crate::{Accounts, AccountsExit, Result, ToAccountInfos, ToAccountMetas};
 use solana_program::account_info::AccountInfo;
 use solana_program::instruction::AccountMeta;
-use solana_program::program_error::ProgramError;
 use solana_program::pubkey::Pubkey;
 use std::collections::BTreeMap;
 use std::ops::Deref;
@@ -27,7 +26,7 @@ impl<'info> Accounts<'info> for UncheckedAccount<'info> {
         accounts: &mut &[AccountInfo<'info>],
         _ix_data: &[u8],
         _bumps: &mut BTreeMap<String, u8>,
-    ) -> Result<Self, ProgramError> {
+    ) -> Result<Self> {
         if accounts.is_empty() {
             return Err(ErrorCode::AccountNotEnoughKeys.into());
         }

+ 12 - 20
lang/src/bpf_upgradeable_state.rs

@@ -1,4 +1,5 @@
-use crate::{AccountDeserialize, AccountSerialize, Owner};
+use crate::error::ErrorCode;
+use crate::{AccountDeserialize, AccountSerialize, Owner, Result};
 use solana_program::{
     bpf_loader_upgradeable::UpgradeableLoaderState, program_error::ProgramError, pubkey::Pubkey,
 };
@@ -10,27 +11,21 @@ pub struct ProgramData {
 }
 
 impl AccountDeserialize for ProgramData {
-    fn try_deserialize(
-        buf: &mut &[u8],
-    ) -> Result<Self, solana_program::program_error::ProgramError> {
+    fn try_deserialize(buf: &mut &[u8]) -> Result<Self> {
         ProgramData::try_deserialize_unchecked(buf)
     }
 
-    fn try_deserialize_unchecked(
-        buf: &mut &[u8],
-    ) -> Result<Self, solana_program::program_error::ProgramError> {
+    fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self> {
         let program_state = AccountDeserialize::try_deserialize_unchecked(buf)?;
 
         match program_state {
-            UpgradeableLoaderState::Uninitialized => {
-                Err(anchor_lang::error::ErrorCode::AccountNotProgramData.into())
-            }
+            UpgradeableLoaderState::Uninitialized => Err(ErrorCode::AccountNotProgramData.into()),
             UpgradeableLoaderState::Buffer {
                 authority_address: _,
-            } => Err(anchor_lang::error::ErrorCode::AccountNotProgramData.into()),
+            } => Err(ErrorCode::AccountNotProgramData.into()),
             UpgradeableLoaderState::Program {
                 programdata_address: _,
-            } => Err(anchor_lang::error::ErrorCode::AccountNotProgramData.into()),
+            } => Err(ErrorCode::AccountNotProgramData.into()),
             UpgradeableLoaderState::ProgramData {
                 slot,
                 upgrade_authority_address,
@@ -43,10 +38,7 @@ impl AccountDeserialize for ProgramData {
 }
 
 impl AccountSerialize for ProgramData {
-    fn try_serialize<W: std::io::Write>(
-        &self,
-        _writer: &mut W,
-    ) -> Result<(), solana_program::program_error::ProgramError> {
+    fn try_serialize<W: std::io::Write>(&self, _writer: &mut W) -> Result<()> {
         // no-op
         Ok(())
     }
@@ -65,18 +57,18 @@ impl Owner for UpgradeableLoaderState {
 }
 
 impl AccountSerialize for UpgradeableLoaderState {
-    fn try_serialize<W: std::io::Write>(&self, _writer: &mut W) -> Result<(), ProgramError> {
+    fn try_serialize<W: std::io::Write>(&self, _writer: &mut W) -> Result<()> {
         // no-op
         Ok(())
     }
 }
 
 impl AccountDeserialize for UpgradeableLoaderState {
-    fn try_deserialize(buf: &mut &[u8]) -> Result<Self, ProgramError> {
+    fn try_deserialize(buf: &mut &[u8]) -> Result<Self> {
         UpgradeableLoaderState::try_deserialize_unchecked(buf)
     }
 
-    fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError> {
-        bincode::deserialize(buf).map_err(|_| ProgramError::InvalidAccountData)
+    fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self> {
+        bincode::deserialize(buf).map_err(|_| ProgramError::InvalidAccountData.into())
     }
 }

+ 4 - 7
lang/src/common.rs

@@ -1,12 +1,10 @@
 use crate::error::ErrorCode;
+use crate::prelude::error;
+use crate::Result;
 use solana_program::account_info::AccountInfo;
-use solana_program::entrypoint::ProgramResult;
 use std::io::Write;
 
-pub fn close<'info>(
-    info: AccountInfo<'info>,
-    sol_destination: AccountInfo<'info>,
-) -> ProgramResult {
+pub fn close<'info>(info: AccountInfo<'info>, sol_destination: AccountInfo<'info>) -> Result<()> {
     // Transfer tokens from the account to the sol_destination.
     let dest_starting_lamports = sol_destination.lamports();
     **sol_destination.lamports.borrow_mut() =
@@ -19,6 +17,5 @@ pub fn close<'info>(
     let mut cursor = std::io::Cursor::new(dst);
     cursor
         .write_all(&crate::__private::CLOSED_ACCOUNT_DISCRIMINATOR)
-        .map_err(|_| ErrorCode::AccountDidNotSerialize)?;
-    Ok(())
+        .map_err(|_| error!(ErrorCode::AccountDidNotSerialize))
 }

+ 5 - 5
lang/src/context.rs

@@ -11,7 +11,7 @@ use std::fmt;
 ///
 /// # Example
 /// ```ignore
-/// pub fn set_data(ctx: Context<SetData>, age: u64, other_data: u32) -> ProgramResult {
+/// pub fn set_data(ctx: Context<SetData>, age: u64, other_data: u32) -> Result<()> {
 ///     // Set account data like this
 ///     (*ctx.accounts.my_account).age = age;
 ///     (*ctx.accounts.my_account).other_data = other_data;
@@ -76,12 +76,12 @@ impl<'a, 'b, 'c, 'info, T: Accounts<'info>> Context<'a, 'b, 'c, 'info, T> {
 /// #[program]
 /// pub mod callee {
 ///     use super::*;
-///     pub fn init(ctx: Context<Init>) -> ProgramResult {
+///     pub fn init(ctx: Context<Init>) -> Result<()> {
 ///         (*ctx.accounts.data).authority = ctx.accounts.authority.key();
 ///         Ok(())
 ///     }
 ///
-///     pub fn set_data(ctx: Context<SetData>, data: u64) -> ProgramResult {
+///     pub fn set_data(ctx: Context<SetData>, data: u64) -> Result<()> {
 ///         (*ctx.accounts.data_acc).data = data;
 ///         Ok(())
 ///     }
@@ -120,7 +120,7 @@ impl<'a, 'b, 'c, 'info, T: Accounts<'info>> Context<'a, 'b, 'c, 'info, T> {
 /// #[program]
 /// pub mod caller {
 ///     use super::*;
-///     pub fn do_cpi(ctx: Context<DoCpi>, data: u64) -> ProgramResult {
+///     pub fn do_cpi(ctx: Context<DoCpi>, data: u64) -> Result<()> {
 ///         let callee_id = ctx.accounts.callee.to_account_info();
 ///         let callee_accounts = callee::cpi::accounts::SetData {
 ///             data_acc: ctx.accounts.data_acc.to_account_info(),
@@ -130,7 +130,7 @@ impl<'a, 'b, 'c, 'info, T: Accounts<'info>> Context<'a, 'b, 'c, 'info, T> {
 ///         callee::cpi::set_data(cpi_ctx, data)
 ///     }
 ///
-///     pub fn do_cpi_with_pda_authority(ctx: Context<DoCpiWithPDAAuthority>, bump: u8, data: u64) -> ProgramResult {
+///     pub fn do_cpi_with_pda_authority(ctx: Context<DoCpiWithPDAAuthority>, bump: u8, data: u64) -> Result<()> {
 ///         let seeds = &[&[b"example_seed", bytemuck::bytes_of(&bump)][..]];
 ///         let callee_id = ctx.accounts.callee.to_account_info();
 ///         let callee_accounts = callee::cpi::accounts::SetData {

+ 189 - 2
lang/src/error.rs

@@ -1,4 +1,7 @@
-use crate::error;
+use anchor_attribute_error::error_code;
+use borsh::maybestd::io::Error as BorshIoError;
+use solana_program::program_error::ProgramError;
+use std::fmt::{Debug, Display};
 
 /// The starting point for user defined error codes.
 pub const ERROR_CODE_OFFSET: u32 = 6000;
@@ -15,7 +18,7 @@ pub const ERROR_CODE_OFFSET: u32 = 6000;
 ///
 /// The starting point for user-defined errors is defined
 /// by the [ERROR_CODE_OFFSET](crate::error::ERROR_CODE_OFFSET).
-#[error(offset = 0)]
+#[error_code(offset = 0)]
 pub enum ErrorCode {
     // Instructions
     /// 100 - 8 byte instruction identifier not provided
@@ -165,3 +168,187 @@ pub enum ErrorCode {
     #[msg("The API being used is deprecated and should no longer be used")]
     Deprecated = 5000,
 }
+
+#[derive(Debug)]
+pub enum Error {
+    AnchorError(AnchorError),
+    ProgramError(ProgramErrorWithOrigin),
+}
+
+impl std::error::Error for Error {}
+
+impl Display for Error {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Error::AnchorError(ae) => Display::fmt(&ae, f),
+            Error::ProgramError(pe) => Display::fmt(&pe, f),
+        }
+    }
+}
+
+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)
+    }
+}
+
+impl From<ProgramError> for Error {
+    fn from(program_error: ProgramError) -> Self {
+        Self::ProgramError(program_error.into())
+    }
+}
+impl From<BorshIoError> for Error {
+    fn from(error: BorshIoError) -> Self {
+        Error::ProgramError(ProgramError::from(error).into())
+    }
+}
+
+impl From<ProgramErrorWithOrigin> for Error {
+    fn from(pe: ProgramErrorWithOrigin) -> Self {
+        Self::ProgramError(pe)
+    }
+}
+
+impl Error {
+    pub fn log(&self) {
+        match self {
+            Error::ProgramError(program_error) => program_error.log(),
+            Error::AnchorError(anchor_error) => anchor_error.log(),
+        }
+    }
+
+    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()),
+        };
+        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),
+        };
+        self
+    }
+}
+
+#[derive(Debug)]
+pub struct ProgramErrorWithOrigin {
+    pub program_error: ProgramError,
+    pub source: Option<Source>,
+    pub account_name: Option<String>,
+}
+
+impl Display for ProgramErrorWithOrigin {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        Display::fmt(&self.program_error, f)
+    }
+}
+
+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
+            ));
+        }
+    }
+}
+
+impl From<ProgramError> for ProgramErrorWithOrigin {
+    fn from(program_error: ProgramError) -> Self {
+        Self {
+            program_error,
+            source: None,
+            account_name: None,
+        }
+    }
+}
+
+#[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>,
+}
+
+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
+            ));
+        }
+    }
+}
+
+impl std::convert::From<Error> for anchor_lang::solana_program::program_error::ProgramError {
+    fn from(e: Error) -> anchor_lang::solana_program::program_error::ProgramError {
+        match e {
+            Error::AnchorError(AnchorError {
+                error_name: _,
+                error_code_number,
+                error_msg: _,
+                source: _,
+                account_name: _,
+            }) => {
+                anchor_lang::solana_program::program_error::ProgramError::Custom(error_code_number)
+            }
+            Error::ProgramError(program_error) => program_error.program_error,
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct Source {
+    pub filename: &'static str,
+    pub line: u32,
+}

+ 64 - 24
lang/src/lib.rs

@@ -25,9 +25,7 @@ extern crate self as anchor_lang;
 
 use bytemuck::{Pod, Zeroable};
 use solana_program::account_info::AccountInfo;
-use solana_program::entrypoint::ProgramResult;
 use solana_program::instruction::AccountMeta;
-use solana_program::program_error::ProgramError;
 use solana_program::pubkey::Pubkey;
 use std::collections::BTreeMap;
 use std::io::Write;
@@ -49,7 +47,7 @@ pub use crate::bpf_upgradeable_state::*;
 pub use anchor_attribute_access_control::access_control;
 pub use anchor_attribute_account::{account, declare_id, zero_copy};
 pub use anchor_attribute_constant::constant;
-pub use anchor_attribute_error::error;
+pub use anchor_attribute_error;
 pub use anchor_attribute_event::{emit, event};
 pub use anchor_attribute_interface::interface;
 pub use anchor_attribute_program::program;
@@ -59,6 +57,8 @@ pub use anchor_derive_accounts::Accounts;
 pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize};
 pub use solana_program;
 
+pub type Result<T> = std::result::Result<T, error::Error>;
+
 /// A data structure of validated accounts that can be deserialized from the
 /// input to a Solana program. Implementations of this trait should perform any
 /// and all requisite constraint checks on accounts to ensure the accounts
@@ -82,14 +82,14 @@ pub trait Accounts<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized {
         accounts: &mut &[AccountInfo<'info>],
         ix_data: &[u8],
         bumps: &mut BTreeMap<String, u8>,
-    ) -> Result<Self, ProgramError>;
+    ) -> Result<Self>;
 }
 
 /// The exit procedure for an account. Any cleanup or persistence to storage
 /// should be done here.
 pub trait AccountsExit<'info>: ToAccountMetas + ToAccountInfos<'info> {
     /// `program_id` is the currently executing program.
-    fn exit(&self, _program_id: &Pubkey) -> ProgramResult {
+    fn exit(&self, _program_id: &Pubkey) -> Result<()> {
         // no-op
         Ok(())
     }
@@ -98,7 +98,7 @@ pub trait AccountsExit<'info>: ToAccountMetas + ToAccountInfos<'info> {
 /// The close procedure to initiate garabage collection of an account, allowing
 /// one to retrieve the rent exemption.
 pub trait AccountsClose<'info>: ToAccountInfos<'info> {
-    fn close(&self, sol_destination: AccountInfo<'info>) -> ProgramResult;
+    fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()>;
 }
 
 /// Transformation to
@@ -147,7 +147,7 @@ where
 /// [`#[account]`](./attr.account.html) attribute.
 pub trait AccountSerialize {
     /// Serializes the account data into `writer`.
-    fn try_serialize<W: Write>(&self, _writer: &mut W) -> Result<(), ProgramError> {
+    fn try_serialize<W: Write>(&self, _writer: &mut W) -> Result<()> {
         Ok(())
     }
 }
@@ -164,14 +164,14 @@ pub trait AccountDeserialize: Sized {
     /// For example, if the SPL token program were to implement this trait,
     /// it should be impossible to deserialize a `Mint` account into a token
     /// `Account`.
-    fn try_deserialize(buf: &mut &[u8]) -> Result<Self, ProgramError> {
+    fn try_deserialize(buf: &mut &[u8]) -> Result<Self> {
         Self::try_deserialize_unchecked(buf)
     }
 
     /// Deserializes account data without checking the account discriminator.
     /// This should only be used on account initialization, when the bytes of
     /// the account are zeroed.
-    fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError>;
+    fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self>;
 }
 
 /// An account data structure capable of zero copy deserialization.
@@ -240,16 +240,16 @@ pub mod prelude {
         accounts::account_loader::AccountLoader, accounts::program::Program,
         accounts::signer::Signer, accounts::system_account::SystemAccount,
         accounts::sysvar::Sysvar, accounts::unchecked_account::UncheckedAccount, constant,
-        context::Context, context::CpiContext, declare_id, emit, error, event, interface, program,
-        require, solana_program::bpf_loader_upgradeable::UpgradeableLoaderState, state, zero_copy,
-        AccountDeserialize, AccountSerialize, Accounts, AccountsExit, AnchorDeserialize,
-        AnchorSerialize, Id, Key, Owner, ProgramData, System, ToAccountInfo, ToAccountInfos,
-        ToAccountMetas,
+        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,
     };
-
+    pub use anchor_attribute_error::*;
     pub use borsh;
+    pub use error::*;
     pub use solana_program::account_info::{next_account_info, AccountInfo};
-    pub use solana_program::entrypoint::ProgramResult;
     pub use solana_program::instruction::AccountMeta;
     pub use solana_program::msg;
     pub use solana_program::program_error::ProgramError;
@@ -271,6 +271,7 @@ pub mod prelude {
 /// Internal module used by macros and unstable apis.
 #[doc(hidden)]
 pub mod __private {
+    use super::Result;
     /// The discriminator anchor uses to mark an account as closed.
     pub const CLOSED_ACCOUNT_DISCRIMINATOR: [u8; 8] = [255, 255, 255, 255, 255, 255, 255, 255];
 
@@ -284,8 +285,6 @@ pub mod __private {
 
     pub use bytemuck;
 
-    use solana_program::program_error::ProgramError;
-
     use solana_program::pubkey::Pubkey;
 
     pub mod state {
@@ -296,7 +295,7 @@ pub mod __private {
     // data in it. This trait is currently only used for `#[state]` accounts.
     #[doc(hidden)]
     pub trait AccountSize {
-        fn size(&self) -> Result<u64, ProgramError>;
+        fn size(&self) -> Result<u64>;
     }
 
     // Very experimental trait.
@@ -320,20 +319,20 @@ pub mod __private {
     pub use crate::accounts::state::PROGRAM_STATE_SEED;
 }
 
-/// Ensures a condition is true, otherwise returns the given error.
+/// Ensures a condition is true, otherwise returns with the given error.
 /// Use this with a custom error type.
 ///
 /// # Example
 /// ```ignore
 /// // Instruction function
-/// pub fn set_data(ctx: Context<SetData>, data: u64) -> ProgramResult {
+/// pub fn set_data(ctx: Context<SetData>, data: u64) -> Result<()> {
 ///     require!(ctx.accounts.data.mutation_allowed, MyError::MutationForbidden);
 ///     ctx.accounts.data.data = data;
 ///     Ok(())
 /// }
 ///
 /// // An enum for custom error codes
-/// #[error]
+/// #[error_code]
 /// pub enum MyError {
 ///     MutationForbidden
 /// }
@@ -357,12 +356,53 @@ pub mod __private {
 macro_rules! require {
     ($invariant:expr, $error:tt $(,)?) => {
         if !($invariant) {
-            return Err(crate::ErrorCode::$error.into());
+            return Err(anchor_lang::anchor_attribute_error::error!(
+                crate::ErrorCode::$error
+            ));
         }
     };
     ($invariant:expr, $error:expr $(,)?) => {
         if !($invariant) {
-            return Err($error.into());
+            return Err(anchor_lang::anchor_attribute_error::error!($error));
+        }
+    };
+}
+
+/// Returns with the given error.
+/// Use this with a custom error type.
+///
+/// # Example
+/// ```ignore
+/// // Instruction function
+/// pub fn example(ctx: Context<Example>) -> Result<()> {
+///     err!(MyError::SomeError)
+/// }
+///
+/// // An enum for custom error codes
+/// #[error_code]
+/// pub enum MyError {
+///     SomeError
+/// }
+/// ```
+#[macro_export]
+macro_rules! err {
+    ($error:tt $(,)?) => {
+        Err(anchor_lang::anchor_attribute_error::error!(
+            crate::ErrorCode::$error
+        ))
+    };
+    ($error:expr $(,)?) => {
+        Err(anchor_lang::anchor_attribute_error::error!($error))
+    };
+}
+
+/// Creates a [`Source`](crate::error::Source)
+#[macro_export]
+macro_rules! source {
+    () => {
+        anchor_lang::error::Source {
+            filename: file!(),
+            line: line!(),
         }
     };
 }

+ 2 - 3
lang/src/vec.rs

@@ -1,7 +1,6 @@
-use crate::{Accounts, ToAccountInfos, ToAccountMetas};
+use crate::{Accounts, Result, ToAccountInfos, ToAccountMetas};
 use solana_program::account_info::AccountInfo;
 use solana_program::instruction::AccountMeta;
-use solana_program::program_error::ProgramError;
 use solana_program::pubkey::Pubkey;
 use std::collections::BTreeMap;
 
@@ -27,7 +26,7 @@ impl<'info, T: Accounts<'info>> Accounts<'info> for Vec<T> {
         accounts: &mut &[AccountInfo<'info>],
         ix_data: &[u8],
         bumps: &mut BTreeMap<String, u8>,
-    ) -> Result<Self, ProgramError> {
+    ) -> Result<Self> {
         let mut vec: Vec<T> = Vec::new();
         T::try_accounts(program_id, accounts, ix_data, bumps).map(|item| vec.push(item))?;
         Ok(vec)

+ 64 - 47
lang/syn/src/codegen/accounts/constraints.rs

@@ -110,8 +110,8 @@ fn generate_constraint(f: &Field, c: &Constraint) -> proc_macro2::TokenStream {
         Constraint::Mut(c) => generate_constraint_mut(f, c),
         Constraint::HasOne(c) => generate_constraint_has_one(f, c),
         Constraint::Signer(c) => generate_constraint_signer(f, c),
-        Constraint::Literal(c) => generate_constraint_literal(c),
-        Constraint::Raw(c) => generate_constraint_raw(c),
+        Constraint::Literal(c) => generate_constraint_literal(&f.ident, c),
+        Constraint::Raw(c) => generate_constraint_raw(&f.ident, c),
         Constraint::Owner(c) => generate_constraint_owner(f, c),
         Constraint::RentExempt(c) => generate_constraint_rent_exempt(f, c),
         Constraint::Seeds(c) => generate_constraint_seeds(f, c),
@@ -123,10 +123,10 @@ fn generate_constraint(f: &Field, c: &Constraint) -> proc_macro2::TokenStream {
     }
 }
 
-fn generate_constraint_composite(_f: &CompositeField, c: &Constraint) -> proc_macro2::TokenStream {
+fn generate_constraint_composite(f: &CompositeField, c: &Constraint) -> proc_macro2::TokenStream {
     match c {
-        Constraint::Raw(c) => generate_constraint_raw(c),
-        Constraint::Literal(c) => generate_constraint_literal(c),
+        Constraint::Raw(c) => generate_constraint_raw(&f.ident, c),
+        Constraint::Literal(c) => generate_constraint_literal(&f.ident, c),
         _ => panic!("Invariant violation"),
     }
 }
@@ -134,10 +134,10 @@ fn generate_constraint_composite(_f: &CompositeField, c: &Constraint) -> proc_ma
 fn generate_constraint_address(f: &Field, c: &ConstraintAddress) -> proc_macro2::TokenStream {
     let field = &f.ident;
     let addr = &c.address;
-    let error = generate_custom_error(&c.error, quote! { ConstraintAddress });
+    let error = generate_custom_error(field, &c.error, quote! { ConstraintAddress });
     quote! {
         if #field.key() != #addr {
-            return Err(#error);
+            return #error;
         }
     }
 }
@@ -148,6 +148,7 @@ pub fn generate_constraint_init(f: &Field, c: &ConstraintInitGroup) -> proc_macr
 
 pub fn generate_constraint_zeroed(f: &Field, _c: &ConstraintZeroed) -> proc_macro2::TokenStream {
     let field = &f.ident;
+    let name_str = field.to_string();
     let ty_decl = f.ty_decl();
     let from_account_info = f.from_account_info_unchecked(None);
     quote! {
@@ -157,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::error::ErrorCode::ConstraintZero.into());
+                return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintZero, #name_str));
             }
             #from_account_info
         };
@@ -166,20 +167,21 @@ pub fn generate_constraint_zeroed(f: &Field, _c: &ConstraintZeroed) -> proc_macr
 
 pub fn generate_constraint_close(f: &Field, c: &ConstraintClose) -> proc_macro2::TokenStream {
     let field = &f.ident;
+    let name_str = field.to_string();
     let target = &c.sol_dest;
     quote! {
         if #field.key() == #target.key() {
-            return Err(anchor_lang::error::ErrorCode::ConstraintClose.into());
+            return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintClose, #name_str));
         }
     }
 }
 
 pub fn generate_constraint_mut(f: &Field, c: &ConstraintMut) -> proc_macro2::TokenStream {
     let ident = &f.ident;
-    let error = generate_custom_error(&c.error, quote! { ConstraintMut });
+    let error = generate_custom_error(ident, &c.error, quote! { ConstraintMut });
     quote! {
         if !#ident.to_account_info().is_writable {
-            return Err(#error);
+            return #error;
         }
     }
 }
@@ -192,10 +194,10 @@ pub fn generate_constraint_has_one(f: &Field, c: &ConstraintHasOne) -> proc_macr
         Ty::AccountLoader(_) => quote! {#ident.load()?},
         _ => quote! {#ident},
     };
-    let error = generate_custom_error(&c.error, quote! { ConstraintHasOne });
+    let error = generate_custom_error(ident, &c.error, quote! { ConstraintHasOne });
     quote! {
         if #field.#target != #target.key() {
-            return Err(#error);
+            return #error;
         }
     }
 }
@@ -211,15 +213,19 @@ pub fn generate_constraint_signer(f: &Field, c: &ConstraintSigner) -> proc_macro
         Ty::CpiAccount(_) => quote! { #ident.to_account_info() },
         _ => panic!("Invalid syntax: signer cannot be specified."),
     };
-    let error = generate_custom_error(&c.error, quote! { ConstraintSigner });
+    let error = generate_custom_error(ident, &c.error, quote! { ConstraintSigner });
     quote! {
         if !#info.is_signer {
-            return Err(#error);
+            return #error;
         }
     }
 }
 
-pub fn generate_constraint_literal(c: &ConstraintLiteral) -> proc_macro2::TokenStream {
+pub fn generate_constraint_literal(
+    ident: &Ident,
+    c: &ConstraintLiteral,
+) -> proc_macro2::TokenStream {
+    let name_str = ident.to_string();
     let lit: proc_macro2::TokenStream = {
         let lit = &c.lit;
         let constraint = lit.value().replace('\"', "");
@@ -232,17 +238,17 @@ pub fn generate_constraint_literal(c: &ConstraintLiteral) -> proc_macro2::TokenS
     };
     quote! {
         if !(#lit) {
-            return Err(anchor_lang::error::ErrorCode::Deprecated.into());
+            return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::Deprecated, #name_str));
         }
     }
 }
 
-pub fn generate_constraint_raw(c: &ConstraintRaw) -> proc_macro2::TokenStream {
+pub fn generate_constraint_raw(ident: &Ident, c: &ConstraintRaw) -> proc_macro2::TokenStream {
     let raw = &c.raw;
-    let error = generate_custom_error(&c.error, quote! { ConstraintRaw });
+    let error = generate_custom_error(ident, &c.error, quote! { ConstraintRaw });
     quote! {
         if !(#raw) {
-            return Err(#error);
+            return #error;
         }
     }
 }
@@ -250,10 +256,10 @@ pub fn generate_constraint_raw(c: &ConstraintRaw) -> proc_macro2::TokenStream {
 pub fn generate_constraint_owner(f: &Field, c: &ConstraintOwner) -> proc_macro2::TokenStream {
     let ident = &f.ident;
     let owner_address = &c.owner_address;
-    let error = generate_custom_error(&c.error, quote! { ConstraintOwner });
+    let error = generate_custom_error(ident, &c.error, quote! { ConstraintOwner });
     quote! {
         if #ident.as_ref().owner != &#owner_address {
-            return Err(#error);
+            return #error;
         }
     }
 }
@@ -263,6 +269,7 @@ pub fn generate_constraint_rent_exempt(
     c: &ConstraintRentExempt,
 ) -> proc_macro2::TokenStream {
     let ident = &f.ident;
+    let name_str = ident.to_string();
     let info = quote! {
         #ident.to_account_info()
     };
@@ -270,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::error::ErrorCode::ConstraintRentExempt.into());
+                return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintRentExempt, #name_str));
             }
         },
     }
@@ -278,6 +285,7 @@ pub fn generate_constraint_rent_exempt(
 
 fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_macro2::TokenStream {
     let field = &f.ident;
+    let name_str = f.ident.to_string();
     let ty_decl = f.ty_decl();
     let if_needed = if c.if_needed {
         quote! {true}
@@ -301,7 +309,6 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma
     let (find_pda, seeds_with_bump) = match &c.seeds {
         None => (quote! {}, quote! {}),
         Some(c) => {
-            let name_str = f.ident.to_string();
             let seeds = &mut c.seeds.clone();
 
             // If the seeds came with a trailing comma, we need to chop it off
@@ -367,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::error::ErrorCode::ConstraintTokenMint.into());
+                            return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintTokenMint, #name_str));
                         }
                         if pa.owner != #owner.key() {
-                            return Err(anchor_lang::error::ErrorCode::ConstraintTokenOwner.into());
+                            return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintTokenOwner, #name_str));
                         }
                     }
                     pa
@@ -402,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::error::ErrorCode::ConstraintTokenMint.into());
+                            return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintTokenMint, #name_str));
                         }
                         if pa.owner != #owner.key() {
-                            return Err(anchor_lang::error::ErrorCode::ConstraintTokenOwner.into());
+                            return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintTokenOwner, #name_str));
                         }
 
                         if pa.key() != anchor_spl::associated_token::get_associated_token_address(&#owner.key(), &#mint.key()) {
-                            return Err(anchor_lang::error::ErrorCode::AccountNotAssociatedTokenAccount.into());
+                            return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::AccountNotAssociatedTokenAccount, #name_str));
                         }
                     }
                     pa
@@ -455,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::error::ErrorCode::ConstraintMintMintAuthority.into());
+                            return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintMintMintAuthority, #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::error::ErrorCode::ConstraintMintFreezeAuthority.into());
+                            return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintMintFreezeAuthority, #name_str));
                         }
                         if pa.decimals != #decimals {
-                            return Err(anchor_lang::error::ErrorCode::ConstraintMintDecimals.into());
+                            return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintMintDecimals, #name_str));
                         }
                     }
                     pa
@@ -540,17 +547,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::error::ErrorCode::ConstraintSpace.into());
+                            return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintSpace, #name_str));
                         }
 
                         if actual_owner != #owner {
-                            return Err(anchor_lang::error::ErrorCode::ConstraintOwner.into());
+                            return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintOwner, #name_str));
                         }
 
                         {
                             let required_lamports = __anchor_rent.minimum_balance(space);
                             if pa.to_account_info().lamports() < required_lamports {
-                                return Err(anchor_lang::error::ErrorCode::ConstraintRentExempt.into());
+                                return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintRentExempt, #name_str));
                             }
                         }
                     }
@@ -592,10 +599,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::error::ErrorCode::ConstraintSeeds.into());
+                return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintSeeds, #name_str));
             }
             if __bump != #b {
-                return Err(anchor_lang::error::ErrorCode::ConstraintSeeds.into());
+                return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintSeeds, #name_str));
             }
         }
     }
@@ -607,7 +614,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::error::ErrorCode::ConstraintSeeds.into());
+                return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintSeeds, #name_str));
             }
         }
     }
@@ -616,6 +623,7 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2
         let maybe_seeds_plus_comma = (!s.is_empty()).then(|| {
             quote! { #s, }
         });
+
         let define_pda = match c.bump.as_ref() {
             // Bump target not given. Find it.
             None => quote! {
@@ -630,7 +638,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::error::ErrorCode::ConstraintSeeds)?;
+                ).map_err(|_| anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintSeeds, #name_str))?;
             },
         };
         quote! {
@@ -639,7 +647,7 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2
 
             // Check it.
             if #name.key() != __pda_address {
-                return Err(anchor_lang::error::ErrorCode::ConstraintSeeds.into());
+                return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintSeeds, #name_str));
             }
         }
     }
@@ -650,15 +658,16 @@ fn generate_constraint_associated_token(
     c: &ConstraintAssociatedToken,
 ) -> proc_macro2::TokenStream {
     let name = &f.ident;
+    let name_str = name.to_string();
     let wallet_address = &c.wallet;
     let spl_token_mint_address = &c.mint;
     quote! {
         if #name.owner != #wallet_address.key() {
-            return Err(anchor_lang::error::ErrorCode::ConstraintTokenOwner.into());
+            return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintTokenOwner, #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::error::ErrorCode::ConstraintAssociated.into());
+            return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintAssociated, #name_str));
         }
     }
 }
@@ -751,9 +760,10 @@ pub fn generate_constraint_executable(
     _c: &ConstraintExecutable,
 ) -> proc_macro2::TokenStream {
     let name = &f.ident;
+    let name_str = name.to_string();
     quote! {
         if !#name.to_account_info().executable {
-            return Err(anchor_lang::error::ErrorCode::ConstraintExecutable.into());
+            return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintExecutable, #name_str));
         }
     }
 }
@@ -761,6 +771,7 @@ pub fn generate_constraint_executable(
 pub fn generate_constraint_state(f: &Field, c: &ConstraintState) -> proc_macro2::TokenStream {
     let program_target = c.program_target.clone();
     let ident = &f.ident;
+    let name_str = ident.to_string();
     let account_ty = match &f.ty {
         Ty::CpiState(ty) => &ty.account_type_path,
         _ => panic!("Invalid state constraint"),
@@ -769,20 +780,26 @@ 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::error::ErrorCode::ConstraintState.into());
+            return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintState, #name_str));
         }
         if #ident.as_ref().owner != &#program_target.key() {
-            return Err(anchor_lang::error::ErrorCode::ConstraintState.into());
+            return Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::ConstraintState, #name_str));
         }
     }
 }
 
 fn generate_custom_error(
+    account_name: &Ident,
     custom_error: &Option<Expr>,
     error: proc_macro2::TokenStream,
 ) -> proc_macro2::TokenStream {
+    let account_name = account_name.to_string();
     match custom_error {
-        Some(error) => quote! { #error.into() },
-        None => quote! { anchor_lang::error::ErrorCode::#error.into() },
+        Some(error) => {
+            quote! { Err(anchor_lang::anchor_attribute_error::error_with_account_name!(#error, #account_name)) }
+        }
+        None => {
+            quote! { Err(anchor_lang::anchor_attribute_error::error_with_account_name!(anchor_lang::error::ErrorCode::#error, #account_name)) }
+        }
     }
 }

+ 8 - 4
lang/syn/src/codegen/accounts/exit.rs

@@ -18,25 +18,29 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
         .map(|af: &AccountField| match af {
             AccountField::CompositeField(s) => {
                 let name = &s.ident;
+                let name_str = name.to_string();
                 quote! {
-                    anchor_lang::AccountsExit::exit(&self.#name, program_id)?;
+                    anchor_lang::AccountsExit::exit(&self.#name, program_id)
+                        .map_err(|e| e.with_account_name(#name_str))?;
                 }
             }
             AccountField::Field(f) => {
                 let ident = &f.ident;
+                let name_str = ident.to_string();
                 if f.constraints.is_close() {
                     let close_target = &f.constraints.close.as_ref().unwrap().sol_dest;
                     quote! {
                         anchor_lang::AccountsClose::close(
                             &self.#ident,
                             self.#close_target.to_account_info(),
-                        )?;
+                        ).map_err(|e| e.with_account_name(#name_str))?;
                     }
                 } else {
                     match f.constraints.is_mutable() {
                         false => quote! {},
                         true => quote! {
-                            anchor_lang::AccountsExit::exit(&self.#ident, program_id)?;
+                            anchor_lang::AccountsExit::exit(&self.#ident, program_id)
+                                .map_err(|e| e.with_account_name(#name_str))?;
                         },
                     }
                 }
@@ -46,7 +50,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
     quote! {
         #[automatically_derived]
         impl<#combined_generics> anchor_lang::AccountsExit<#trait_generics> for #name<#struct_generics> #where_clause{
-            fn exit(&self, program_id: &anchor_lang::solana_program::pubkey::Pubkey) -> anchor_lang::solana_program::entrypoint::ProgramResult {
+            fn exit(&self, program_id: &anchor_lang::solana_program::pubkey::Pubkey) -> anchor_lang::Result<()> {
                 #(#on_save)*
                 Ok(())
             }

+ 6 - 4
lang/syn/src/codegen/accounts/try_accounts.rs

@@ -39,11 +39,13 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
                             *accounts = &accounts[1..];
                         }
                     } else {
-                        let name = f.typed_ident();
+                        let name = f.ident.to_string();
+                        let typed_name = f.typed_ident();
                         quote! {
                             #[cfg(feature = "anchor-debug")]
-                            ::solana_program::log::sol_log(stringify!(#name));
-                            let #name = anchor_lang::Accounts::try_accounts(program_id, accounts, ix_data, __bumps)?;
+                            ::solana_program::log::sol_log(stringify!(#typed_name));
+                            let #typed_name = anchor_lang::Accounts::try_accounts(program_id, accounts, ix_data, __bumps)
+                                .map_err(|e| e.with_account_name(#name))?;
                         }
                     }
                 }
@@ -93,7 +95,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
                 accounts: &mut &[anchor_lang::solana_program::account_info::AccountInfo<'info>],
                 ix_data: &[u8],
                 __bumps: &mut std::collections::BTreeMap<String, u8>,
-            ) -> std::result::Result<Self, anchor_lang::solana_program::program_error::ProgramError> {
+            ) -> anchor_lang::Result<Self> {
                 // Deserialize instruction, if declared.
                 #ix_de
                 // Deserialize each account.

+ 42 - 33
lang/syn/src/codegen/error.rs

@@ -6,7 +6,7 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream {
     let enum_name = &error.ident;
     // Each arm of the `match` statement for implementing `std::fmt::Display`
     // on the user defined error code.
-    let variant_dispatch: Vec<proc_macro2::TokenStream> = error
+    let display_variant_dispatch: Vec<proc_macro2::TokenStream> = error
         .raw_enum
         .variants
         .iter()
@@ -14,7 +14,7 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream {
         .map(|(idx, variant)| {
             let ident = &variant.ident;
             let error_code = &error.codes[idx];
-            let msg = match &error_code.msg {
+            let display_msg = match &error_code.msg {
                 None => {
                     quote! {
                         <Self as std::fmt::Debug>::fmt(self, fmt)
@@ -27,7 +27,22 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream {
                 }
             };
             quote! {
-                #enum_name::#ident => #msg
+                #enum_name::#ident => #display_msg
+            }
+        })
+        .collect();
+
+    // Each arm of the `match` statement for implementing the `name` function
+    // on the user defined error code.
+    let name_variant_dispatch: Vec<proc_macro2::TokenStream> = error
+        .raw_enum
+        .variants
+        .iter()
+        .map(|variant| {
+            let ident = &variant.ident;
+            let ident_name = ident.to_string();
+            quote! {
+                #enum_name::#ident => #ident_name.to_string()
             }
         })
         .collect();
@@ -41,49 +56,43 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream {
     };
 
     quote! {
-        /// Anchor generated Result to be used as the return type for the
-        /// program.
-        pub type Result<T> = std::result::Result<T, Error>;
-
-        /// Anchor generated error allowing one to easily return a
-        /// `ProgramError` or a custom, user defined error code by utilizing
-        /// its `From` implementation.
-        #[doc(hidden)]
-        #[derive(thiserror::Error, Debug)]
-        pub enum Error {
-            #[error(transparent)]
-            ProgramError(#[from] anchor_lang::solana_program::program_error::ProgramError),
-            #[error(transparent)]
-            ErrorCode(#[from] #enum_name),
-        }
-
         #[derive(std::fmt::Debug, Clone, Copy)]
         #[repr(u32)]
         #error_enum
 
-        impl std::fmt::Display for #enum_name {
-            fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
+        impl #enum_name {
+            pub fn name(&self) -> String {
                 match self {
-                    #(#variant_dispatch),*
+                    #(#name_variant_dispatch),*
                 }
             }
         }
 
-        impl std::error::Error for #enum_name {}
+        impl From<#enum_name> for u32 {
+            fn from(e: #enum_name) -> u32 {
+                e as u32 + #offset
+            }
+        }
 
-        impl std::convert::From<Error> for anchor_lang::solana_program::program_error::ProgramError {
-            fn from(e: Error) -> anchor_lang::solana_program::program_error::ProgramError {
-                match e {
-                    Error::ProgramError(e) => e,
-                    Error::ErrorCode(c) => anchor_lang::solana_program::program_error::ProgramError::Custom(c as u32 + #offset),
-                }
+        impl From<#enum_name> for anchor_lang::error::Error {
+            fn from(error_code: #enum_name) -> Error {
+                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: None,
+                        account_name: None
+                    }
+                )
             }
         }
 
-        impl std::convert::From<#enum_name> for anchor_lang::solana_program::program_error::ProgramError {
-            fn from(e: #enum_name) -> anchor_lang::solana_program::program_error::ProgramError {
-                let err: Error = e.into();
-                err.into()
+        impl std::fmt::Display for #enum_name {
+            fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
+                match self {
+                    #(#display_variant_dispatch),*
+                }
             }
         }
     }

+ 4 - 4
lang/syn/src/codegen/program/cpi.rs

@@ -31,7 +31,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                                 pub fn #method_name<'a, 'b, 'c, 'info>(
                                     ctx: anchor_lang::context::CpiStateContext<'a, 'b, 'c, 'info, #accounts_ident<'info>>,
                                     #(#args),*
-                                ) -> ProgramResult {
+                                ) -> anchor_lang::Result<()> {
                                     let ix = {
                                         let ix = instruction::state::#ix_variant;
                                         let data = anchor_lang::InstructionData::data(&ix);
@@ -47,7 +47,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                                         &ix,
                                         &acc_infos,
                                         ctx.signer_seeds(),
-                                    )
+                                    ).map_err(Into::into)
                                 }
                             }
                         })
@@ -74,7 +74,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                     pub fn #method_name<'a, 'b, 'c, 'info>(
                         ctx: anchor_lang::context::CpiContext<'a, 'b, 'c, 'info, #accounts_ident<'info>>,
                         #(#args),*
-                    ) -> ProgramResult {
+                    ) -> anchor_lang::Result<()> {
                         let ix = {
                             let ix = instruction::#ix_variant;
                             let mut ix_data = AnchorSerialize::try_to_vec(&ix)
@@ -93,7 +93,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                             &ix,
                             &acc_infos,
                             ctx.signer_seeds,
-                        )
+                        ).map_err(Into::into)
                     }
                 }
             };

+ 1 - 1
lang/syn/src/codegen/program/dispatch.rs

@@ -139,7 +139,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
             program_id: &Pubkey,
             accounts: &[AccountInfo],
             data: &[u8],
-        ) -> ProgramResult {
+        ) -> anchor_lang::Result<()> {
             // Split the instruction data into the first 8 byte method
             // identifier (sighash) and the serialized instruction data.
             let mut ix_data: &[u8] = data;

+ 10 - 7
lang/syn/src/codegen/program/entry.rs

@@ -6,7 +6,7 @@ use quote::quote;
 pub fn generate(program: &Program) -> proc_macro2::TokenStream {
     let name: proc_macro2::TokenStream = program.name.to_string().to_camel_case().parse().unwrap();
     let fallback_maybe = dispatch::gen_fallback(program).unwrap_or(quote! {
-        Err(anchor_lang::error::ErrorCode::InstructionMissing.into());
+        Err(anchor_lang::error::ErrorCode::InstructionMissing.into())
     });
     quote! {
         #[cfg(not(feature = "no-entrypoint"))]
@@ -51,7 +51,14 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
         ///
         /// The `entry` function here, defines the standard entry to a Solana
         /// program, where execution begins.
-        pub fn entry(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
+        pub fn entry(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> anchor_lang::solana_program::entrypoint::ProgramResult {
+            try_entry(program_id, accounts, data).map_err(|e| {
+                e.log();
+                e.into()
+            })
+        }
+
+        fn try_entry(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> anchor_lang::Result<()> {
             #[cfg(feature = "anchor-debug")]
             {
                 msg!("anchor-debug is active");
@@ -60,14 +67,10 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                 return Err(anchor_lang::error::ErrorCode::DeclaredProgramIdMismatch.into());
             }
             if data.len() < 8 {
-                return #fallback_maybe
+                return #fallback_maybe;
             }
 
             dispatch(program_id, accounts, data)
-                .map_err(|e| {
-                    anchor_lang::solana_program::msg!(&e.to_string());
-                    e
-                })
         }
 
         pub mod program {

+ 15 - 14
lang/syn/src/codegen/program/handlers.rs

@@ -16,7 +16,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
             // on chain.
             #[inline(never)]
             #[cfg(not(feature = "no-idl"))]
-            pub fn __idl_dispatch(program_id: &Pubkey, accounts: &[AccountInfo], idl_ix_data: &[u8]) -> ProgramResult {
+            pub fn __idl_dispatch(program_id: &Pubkey, accounts: &[AccountInfo], idl_ix_data: &[u8]) -> anchor_lang::Result<()> {
                 let mut accounts = accounts;
                 let mut data: &[u8] = idl_ix_data;
 
@@ -65,7 +65,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
 
             #[inline(never)]
             #[cfg(feature = "no-idl")]
-            pub fn __idl_dispatch(program_id: &Pubkey, accounts: &[AccountInfo], idl_ix_data: &[u8]) -> ProgramResult {
+            pub fn __idl_dispatch(program_id: &Pubkey, accounts: &[AccountInfo], idl_ix_data: &[u8]) -> anchor_lang::Result<()> {
                 Err(anchor_lang::error::ErrorCode::IdlInstructionStub.into())
             }
 
@@ -76,7 +76,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                 program_id: &Pubkey,
                 accounts: &mut anchor_lang::idl::IdlCreateAccounts,
                 data_len: u64,
-            ) -> ProgramResult {
+            ) -> anchor_lang::Result<()> {
                 #[cfg(not(feature = "no-log-ix-name"))]
                 anchor_lang::prelude::msg!("Instruction: IdlCreateAccount");
 
@@ -139,7 +139,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
             pub fn __idl_create_buffer(
                 program_id: &Pubkey,
                 accounts: &mut anchor_lang::idl::IdlCreateBuffer,
-            ) -> ProgramResult {
+            ) -> anchor_lang::Result<()> {
                 #[cfg(not(feature = "no-log-ix-name"))]
                 anchor_lang::prelude::msg!("Instruction: IdlCreateBuffer");
 
@@ -153,7 +153,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                 program_id: &Pubkey,
                 accounts: &mut anchor_lang::idl::IdlAccounts,
                 idl_data: Vec<u8>,
-            ) -> ProgramResult {
+            ) -> anchor_lang::Result<()> {
                 #[cfg(not(feature = "no-log-ix-name"))]
                 anchor_lang::prelude::msg!("Instruction: IdlWrite");
 
@@ -167,7 +167,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                 program_id: &Pubkey,
                 accounts: &mut anchor_lang::idl::IdlAccounts,
                 new_authority: Pubkey,
-            ) -> ProgramResult {
+            ) -> anchor_lang::Result<()> {
                 #[cfg(not(feature = "no-log-ix-name"))]
                 anchor_lang::prelude::msg!("Instruction: IdlSetAuthority");
 
@@ -179,7 +179,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
             pub fn __idl_set_buffer(
                 program_id: &Pubkey,
                 accounts: &mut anchor_lang::idl::IdlSetBuffer,
-            ) -> ProgramResult {
+            ) -> anchor_lang::Result<()> {
                 #[cfg(not(feature = "no-log-ix-name"))]
                 anchor_lang::prelude::msg!("Instruction: IdlSetBuffer");
 
@@ -206,7 +206,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                         // One time state account initializer. Will faill on subsequent
                         // invocations.
                         #[inline(never)]
-                        pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8]) -> ProgramResult {
+                        pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8]) -> anchor_lang::Result<()> {
                             #[cfg(not(feature = "no-log-ix-name"))]
                             anchor_lang::prelude::msg!(#ix_name_log);
 
@@ -285,7 +285,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                         // One time state account initializer. Will faill on subsequent
                         // invocations.
                         #[inline(never)]
-                        pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8]) -> ProgramResult {
+                        pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8]) -> anchor_lang::Result<()> {
                             #[cfg(not(feature = "no-log-ix-name"))]
                             anchor_lang::prelude::msg!(#ix_name_log);
 
@@ -393,7 +393,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                                     program_id: &Pubkey,
                                     accounts: &[AccountInfo],
                                     ix_data: &[u8],
-                                ) -> ProgramResult {
+                                ) -> anchor_lang::Result<()> {
                                     #[cfg(not(feature = "no-log-ix-name"))]
                                     anchor_lang::prelude::msg!(#ix_name_log);
 
@@ -449,7 +449,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                                     program_id: &Pubkey,
                                     accounts: &[AccountInfo],
                                     ix_data: &[u8],
-                                ) -> ProgramResult {
+                                ) -> anchor_lang::Result<()> {
                                     #[cfg(not(feature = "no-log-ix-name"))]
                                     anchor_lang::prelude::msg!(#ix_name_log);
 
@@ -579,7 +579,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                                             program_id: &Pubkey,
                                             accounts: &[AccountInfo],
                                             ix_data: &[u8],
-                                        ) -> ProgramResult {
+                                        ) -> anchor_lang::Result<()> {
                                             #[cfg(not(feature = "no-log-ix-name"))]
                                             anchor_lang::prelude::msg!(#ix_name_log);
 
@@ -641,7 +641,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                                             program_id: &Pubkey,
                                             accounts: &[AccountInfo],
                                             ix_data: &[u8],
-                                        ) -> ProgramResult {
+                                        ) -> anchor_lang::Result<()> {
                                             #[cfg(not(feature = "no-log-ix-name"))]
                                             anchor_lang::prelude::msg!(#ix_name_log);
 
@@ -683,6 +683,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
             })
             .unwrap_or_default(),
     };
+
     let non_inlined_handlers: Vec<proc_macro2::TokenStream> = program
         .ixs
         .iter()
@@ -699,7 +700,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                     program_id: &Pubkey,
                     accounts: &[AccountInfo],
                     ix_data: &[u8],
-                ) -> ProgramResult {
+                ) -> anchor_lang::Result<()> {
                     #[cfg(not(feature = "no-log-ix-name"))]
                     anchor_lang::prelude::msg!(#ix_name_log);
 

+ 1 - 1
lang/syn/src/idl/file.rs

@@ -293,7 +293,7 @@ fn parse_error_enum(ctx: &CrateContext) -> Option<syn::ItemEnum> {
                 .iter()
                 .filter(|attr| {
                     let segment = attr.path.segments.last().unwrap();
-                    segment.ident == "error"
+                    segment.ident == "error_code"
                 })
                 .count();
             match attrs_count {

+ 30 - 0
lang/syn/src/parser/error.rs

@@ -1,4 +1,6 @@
 use crate::{Error, ErrorArgs, ErrorCode};
+use syn::parse::{Parse, Result as ParseResult};
+use syn::{Expr, Token};
 
 // Removes any internal #[msg] attributes, as they are inert.
 pub fn parse(error_enum: &mut syn::ItemEnum, args: Option<ErrorArgs>) -> Error {
@@ -75,3 +77,31 @@ fn parse_error_attribute(variant: &syn::Variant) -> Option<String> {
         }
     }
 }
+
+pub struct ErrorInput {
+    pub error_code: Expr,
+}
+
+impl Parse for ErrorInput {
+    fn parse(stream: syn::parse::ParseStream) -> ParseResult<Self> {
+        let error_code = stream.call(Expr::parse)?;
+        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,
+        })
+    }
+}

+ 23 - 18
spl/src/dex.rs

@@ -1,7 +1,7 @@
 use anchor_lang::solana_program::account_info::AccountInfo;
-use anchor_lang::solana_program::entrypoint::ProgramResult;
+use anchor_lang::solana_program::program_error::ProgramError;
 use anchor_lang::solana_program::pubkey::Pubkey;
-use anchor_lang::{context::CpiContext, Accounts, ToAccountInfos};
+use anchor_lang::{context::CpiContext, Accounts, Result, ToAccountInfos};
 use serum_dex::instruction::SelfTradeBehavior;
 use serum_dex::matching::{OrderType, Side};
 use std::num::NonZeroU64;
@@ -25,7 +25,7 @@ pub fn new_order_v3<'info>(
     order_type: OrderType,
     client_order_id: u64,
     limit: u16,
-) -> ProgramResult {
+) -> Result<()> {
     let referral = ctx.remaining_accounts.get(0);
     let ix = serum_dex::instruction::new_order(
         ctx.accounts.market.key,
@@ -50,7 +50,8 @@ pub fn new_order_v3<'info>(
         self_trade_behavior,
         limit,
         max_native_pc_qty_including_fees,
-    )?;
+    )
+    .map_err(|pe| ProgramError::from(pe))?;
     solana_program::program::invoke_signed(
         &ix,
         &ToAccountInfos::to_account_infos(&ctx),
@@ -63,7 +64,7 @@ pub fn cancel_order_v2<'info>(
     ctx: CpiContext<'_, '_, '_, 'info, CancelOrderV2<'info>>,
     side: Side,
     order_id: u128,
-) -> ProgramResult {
+) -> Result<()> {
     let ix = serum_dex::instruction::cancel_order(
         &ID,
         ctx.accounts.market.key,
@@ -74,7 +75,8 @@ pub fn cancel_order_v2<'info>(
         ctx.accounts.event_queue.key,
         side,
         order_id,
-    )?;
+    )
+    .map_err(|pe| ProgramError::from(pe))?;
     solana_program::program::invoke_signed(
         &ix,
         &ToAccountInfos::to_account_infos(&ctx),
@@ -83,9 +85,7 @@ pub fn cancel_order_v2<'info>(
     Ok(())
 }
 
-pub fn settle_funds<'info>(
-    ctx: CpiContext<'_, '_, '_, 'info, SettleFunds<'info>>,
-) -> ProgramResult {
+pub fn settle_funds<'info>(ctx: CpiContext<'_, '_, '_, 'info, SettleFunds<'info>>) -> Result<()> {
     let referral = ctx.remaining_accounts.get(0);
     let ix = serum_dex::instruction::settle_funds(
         &ID,
@@ -99,7 +99,8 @@ pub fn settle_funds<'info>(
         ctx.accounts.pc_wallet.key,
         referral.map(|r| r.key),
         ctx.accounts.vault_signer.key,
-    )?;
+    )
+    .map_err(|pe| ProgramError::from(pe))?;
     solana_program::program::invoke_signed(
         &ix,
         &ToAccountInfos::to_account_infos(&ctx),
@@ -110,14 +111,15 @@ pub fn settle_funds<'info>(
 
 pub fn init_open_orders<'info>(
     ctx: CpiContext<'_, '_, '_, 'info, InitOpenOrders<'info>>,
-) -> ProgramResult {
+) -> Result<()> {
     let ix = serum_dex::instruction::init_open_orders(
         &ID,
         ctx.accounts.open_orders.key,
         ctx.accounts.authority.key,
         ctx.accounts.market.key,
         ctx.remaining_accounts.first().map(|acc| acc.key),
-    )?;
+    )
+    .map_err(|pe| ProgramError::from(pe))?;
     solana_program::program::invoke_signed(
         &ix,
         &ToAccountInfos::to_account_infos(&ctx),
@@ -128,14 +130,15 @@ pub fn init_open_orders<'info>(
 
 pub fn close_open_orders<'info>(
     ctx: CpiContext<'_, '_, '_, 'info, CloseOpenOrders<'info>>,
-) -> ProgramResult {
+) -> Result<()> {
     let ix = serum_dex::instruction::close_open_orders(
         &ID,
         ctx.accounts.open_orders.key,
         ctx.accounts.authority.key,
         ctx.accounts.destination.key,
         ctx.accounts.market.key,
-    )?;
+    )
+    .map_err(|pe| ProgramError::from(pe))?;
     solana_program::program::invoke_signed(
         &ix,
         &ToAccountInfos::to_account_infos(&ctx),
@@ -144,7 +147,7 @@ pub fn close_open_orders<'info>(
     Ok(())
 }
 
-pub fn sweep_fees<'info>(ctx: CpiContext<'_, '_, '_, 'info, SweepFees<'info>>) -> ProgramResult {
+pub fn sweep_fees<'info>(ctx: CpiContext<'_, '_, '_, 'info, SweepFees<'info>>) -> Result<()> {
     let ix = serum_dex::instruction::sweep_fees(
         &ID,
         ctx.accounts.market.key,
@@ -153,7 +156,8 @@ pub fn sweep_fees<'info>(ctx: CpiContext<'_, '_, '_, 'info, SweepFees<'info>>) -
         ctx.accounts.sweep_receiver.key,
         ctx.accounts.vault_signer.key,
         ctx.accounts.token_program.key,
-    )?;
+    )
+    .map_err(|pe| ProgramError::from(pe))?;
     solana_program::program::invoke_signed(
         &ix,
         &ToAccountInfos::to_account_infos(&ctx),
@@ -168,7 +172,7 @@ pub fn initialize_market<'info>(
     pc_lot_size: u64,
     vault_signer_nonce: u64,
     pc_dust_threshold: u64,
-) -> ProgramResult {
+) -> Result<()> {
     let authority = ctx.remaining_accounts.get(0);
     let prune_authority = ctx.remaining_accounts.get(1);
     let ix = serum_dex::instruction::initialize_market(
@@ -188,7 +192,8 @@ pub fn initialize_market<'info>(
         pc_lot_size,
         vault_signer_nonce,
         pc_dust_threshold,
-    )?;
+    )
+    .map_err(|pe| ProgramError::from(pe))?;
     solana_program::program::invoke_signed(
         &ix,
         &ToAccountInfos::to_account_infos(&ctx),

+ 33 - 20
spl/src/token.rs

@@ -1,10 +1,9 @@
-use anchor_lang::solana_program;
 use anchor_lang::solana_program::account_info::AccountInfo;
-use anchor_lang::solana_program::entrypoint::ProgramResult;
-use anchor_lang::solana_program::program_error::ProgramError;
+
 use anchor_lang::solana_program::program_pack::Pack;
 use anchor_lang::solana_program::pubkey::Pubkey;
 use anchor_lang::{context::CpiContext, Accounts};
+use anchor_lang::{solana_program, Result};
 use std::ops::Deref;
 
 pub use spl_token::ID;
@@ -12,7 +11,7 @@ pub use spl_token::ID;
 pub fn transfer<'a, 'b, 'c, 'info>(
     ctx: CpiContext<'a, 'b, 'c, 'info, Transfer<'info>>,
     amount: u64,
-) -> ProgramResult {
+) -> Result<()> {
     let ix = spl_token::instruction::transfer(
         &spl_token::ID,
         ctx.accounts.from.key,
@@ -30,12 +29,13 @@ pub fn transfer<'a, 'b, 'c, 'info>(
         ],
         ctx.signer_seeds,
     )
+    .map_err(Into::into)
 }
 
 pub fn mint_to<'a, 'b, 'c, 'info>(
     ctx: CpiContext<'a, 'b, 'c, 'info, MintTo<'info>>,
     amount: u64,
-) -> ProgramResult {
+) -> Result<()> {
     let ix = spl_token::instruction::mint_to(
         &spl_token::ID,
         ctx.accounts.mint.key,
@@ -53,12 +53,13 @@ pub fn mint_to<'a, 'b, 'c, 'info>(
         ],
         ctx.signer_seeds,
     )
+    .map_err(Into::into)
 }
 
 pub fn burn<'a, 'b, 'c, 'info>(
     ctx: CpiContext<'a, 'b, 'c, 'info, Burn<'info>>,
     amount: u64,
-) -> ProgramResult {
+) -> Result<()> {
     let ix = spl_token::instruction::burn(
         &spl_token::ID,
         ctx.accounts.to.key,
@@ -76,12 +77,13 @@ pub fn burn<'a, 'b, 'c, 'info>(
         ],
         ctx.signer_seeds,
     )
+    .map_err(Into::into)
 }
 
 pub fn approve<'a, 'b, 'c, 'info>(
     ctx: CpiContext<'a, 'b, 'c, 'info, Approve<'info>>,
     amount: u64,
-) -> ProgramResult {
+) -> Result<()> {
     let ix = spl_token::instruction::approve(
         &spl_token::ID,
         ctx.accounts.to.key,
@@ -99,11 +101,12 @@ pub fn approve<'a, 'b, 'c, 'info>(
         ],
         ctx.signer_seeds,
     )
+    .map_err(Into::into)
 }
 
 pub fn initialize_account<'a, 'b, 'c, 'info>(
     ctx: CpiContext<'a, 'b, 'c, 'info, InitializeAccount<'info>>,
-) -> ProgramResult {
+) -> Result<()> {
     let ix = spl_token::instruction::initialize_account(
         &spl_token::ID,
         ctx.accounts.account.key,
@@ -120,11 +123,12 @@ pub fn initialize_account<'a, 'b, 'c, 'info>(
         ],
         ctx.signer_seeds,
     )
+    .map_err(Into::into)
 }
 
 pub fn close_account<'a, 'b, 'c, 'info>(
     ctx: CpiContext<'a, 'b, 'c, 'info, CloseAccount<'info>>,
-) -> ProgramResult {
+) -> Result<()> {
     let ix = spl_token::instruction::close_account(
         &spl_token::ID,
         ctx.accounts.account.key,
@@ -141,11 +145,12 @@ pub fn close_account<'a, 'b, 'c, 'info>(
         ],
         ctx.signer_seeds,
     )
+    .map_err(Into::into)
 }
 
 pub fn freeze_account<'a, 'b, 'c, 'info>(
     ctx: CpiContext<'a, 'b, 'c, 'info, FreezeAccount<'info>>,
-) -> ProgramResult {
+) -> Result<()> {
     let ix = spl_token::instruction::freeze_account(
         &spl_token::ID,
         ctx.accounts.account.key,
@@ -162,11 +167,12 @@ pub fn freeze_account<'a, 'b, 'c, 'info>(
         ],
         ctx.signer_seeds,
     )
+    .map_err(Into::into)
 }
 
 pub fn thaw_account<'a, 'b, 'c, 'info>(
     ctx: CpiContext<'a, 'b, 'c, 'info, ThawAccount<'info>>,
-) -> ProgramResult {
+) -> Result<()> {
     let ix = spl_token::instruction::thaw_account(
         &spl_token::ID,
         ctx.accounts.account.key,
@@ -183,6 +189,7 @@ pub fn thaw_account<'a, 'b, 'c, 'info>(
         ],
         ctx.signer_seeds,
     )
+    .map_err(Into::into)
 }
 
 pub fn initialize_mint<'a, 'b, 'c, 'info>(
@@ -190,7 +197,7 @@ pub fn initialize_mint<'a, 'b, 'c, 'info>(
     decimals: u8,
     authority: &Pubkey,
     freeze_authority: Option<&Pubkey>,
-) -> ProgramResult {
+) -> Result<()> {
     let ix = spl_token::instruction::initialize_mint(
         &spl_token::ID,
         ctx.accounts.mint.key,
@@ -203,13 +210,14 @@ pub fn initialize_mint<'a, 'b, 'c, 'info>(
         &[ctx.accounts.mint.clone(), ctx.accounts.rent.clone()],
         ctx.signer_seeds,
     )
+    .map_err(Into::into)
 }
 
 pub fn set_authority<'a, 'b, 'c, 'info>(
     ctx: CpiContext<'a, 'b, 'c, 'info, SetAuthority<'info>>,
     authority_type: spl_token::instruction::AuthorityType,
     new_authority: Option<Pubkey>,
-) -> ProgramResult {
+) -> Result<()> {
     let mut spl_new_authority: Option<&Pubkey> = None;
     if new_authority.is_some() {
         spl_new_authority = new_authority.as_ref()
@@ -231,6 +239,7 @@ pub fn set_authority<'a, 'b, 'c, 'info>(
         ],
         ctx.signer_seeds,
     )
+    .map_err(Into::into)
 }
 
 #[derive(Accounts)]
@@ -310,8 +319,10 @@ impl TokenAccount {
 }
 
 impl anchor_lang::AccountDeserialize for TokenAccount {
-    fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError> {
-        spl_token::state::Account::unpack(buf).map(TokenAccount)
+    fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
+        spl_token::state::Account::unpack(buf)
+            .map(TokenAccount)
+            .map_err(Into::into)
     }
 }
 
@@ -339,8 +350,10 @@ impl Mint {
 }
 
 impl anchor_lang::AccountDeserialize for Mint {
-    fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError> {
-        spl_token::state::Mint::unpack(buf).map(Mint)
+    fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
+        spl_token::state::Mint::unpack(buf)
+            .map(Mint)
+            .map_err(Into::into)
     }
 }
 
@@ -374,21 +387,21 @@ impl anchor_lang::Id for Token {
 pub mod accessor {
     use super::*;
 
-    pub fn amount(account: &AccountInfo) -> Result<u64, ProgramError> {
+    pub fn amount(account: &AccountInfo) -> Result<u64> {
         let bytes = account.try_borrow_data()?;
         let mut amount_bytes = [0u8; 8];
         amount_bytes.copy_from_slice(&bytes[64..72]);
         Ok(u64::from_le_bytes(amount_bytes))
     }
 
-    pub fn mint(account: &AccountInfo) -> Result<Pubkey, ProgramError> {
+    pub fn mint(account: &AccountInfo) -> Result<Pubkey> {
         let bytes = account.try_borrow_data()?;
         let mut mint_bytes = [0u8; 32];
         mint_bytes.copy_from_slice(&bytes[..32]);
         Ok(Pubkey::new_from_array(mint_bytes))
     }
 
-    pub fn authority(account: &AccountInfo) -> Result<Pubkey, ProgramError> {
+    pub fn authority(account: &AccountInfo) -> Result<Pubkey> {
         let bytes = account.try_borrow_data()?;
         let mut owner_bytes = [0u8; 32];
         owner_bytes.copy_from_slice(&bytes[32..64]);

+ 1 - 1
tests/auction-house

@@ -1 +1 @@
-Subproject commit 2b1b1e04986106715ab53794bcb63d3641673f64
+Subproject commit 52c43eb392580bb333940c73d18971131070098e

+ 5 - 5
tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/src/lib.rs

@@ -5,17 +5,17 @@ declare_id!("Cum9tTyj5HwcEiAmhgaS7Bbj4UczCwsucrCkxRECzM4e");
 #[program]
 pub mod bpf_upgradeable_state {
     use super::*;
-    pub fn set_admin_settings(ctx: Context<SetAdminSettings>, admin_data: u64) -> ProgramResult {
+    pub fn set_admin_settings(ctx: Context<SetAdminSettings>, admin_data: u64) -> Result<()> {
         match *ctx.accounts.program {
             UpgradeableLoaderState::Program {
                 programdata_address,
             } => {
                 if programdata_address != ctx.accounts.program_data.key() {
-                    return Err(CustomError::InvalidProgramDataAddress.into());
+                    return err!(CustomError::InvalidProgramDataAddress);
                 }
             }
             _ => {
-                return Err(CustomError::AccountNotProgram.into());
+                return err!(CustomError::AccountNotProgram);
             }
         };
         ctx.accounts.settings.admin_data = admin_data;
@@ -25,7 +25,7 @@ pub mod bpf_upgradeable_state {
     pub fn set_admin_settings_use_program_state(
         ctx: Context<SetAdminSettingsUseProgramState>,
         admin_data: u64,
-    ) -> ProgramResult {
+    ) -> Result<()> {
         ctx.accounts.settings.admin_data = admin_data;
         Ok(())
     }
@@ -37,7 +37,7 @@ pub struct Settings {
     admin_data: u64,
 }
 
-#[error]
+#[error_code]
 pub enum CustomError {
     InvalidProgramDataAddress,
     AccountNotProgram,

+ 4 - 4
tests/cashiers-check/programs/cashiers-check/src/lib.rs

@@ -109,9 +109,9 @@ impl<'info> CreateCheck<'info> {
             &[ctx.accounts.check.to_account_info().key.as_ref(), &[nonce]],
             ctx.program_id,
         )
-        .map_err(|_| ErrorCode::InvalidCheckNonce)?;
+        .map_err(|_| error!(ErrorCode::InvalidCheckNonce))?;
         if &signer != ctx.accounts.check_signer.to_account_info().key {
-            return Err(ErrorCode::InvalidCheckSigner.into());
+            return err!(ErrorCode::InvalidCheckSigner);
         }
         Ok(())
     }
@@ -162,7 +162,7 @@ pub struct Check {
     burned: bool,
 }
 
-#[error]
+#[error_code]
 pub enum ErrorCode {
     #[msg("The given nonce does not create a valid program derived address.")]
     InvalidCheckNonce,
@@ -174,7 +174,7 @@ pub enum ErrorCode {
 
 fn not_burned(check: &Check) -> Result<()> {
     if check.burned {
-        return Err(ErrorCode::AlreadyBurned.into());
+        return err!(ErrorCode::AlreadyBurned);
     }
     Ok(())
 }

+ 1 - 1
tests/cfo/deps/stake

@@ -1 +1 @@
-Subproject commit 990eaa7944c6682838fdaa6c14cc07ed680007f0
+Subproject commit 571f2c2781e2988da213187d92869613d1ee9c69

+ 1 - 1
tests/cfo/deps/swap

@@ -1 +1 @@
-Subproject commit 96e3b1e2a53a95ef56e6ec2da68348ffd6a5c091
+Subproject commit d21838f04360502272490ff2e3a387326579aa6d

+ 12 - 12
tests/cfo/programs/cfo/src/lib.rs

@@ -155,7 +155,7 @@ pub mod cfo {
             .checked_div(100)
             .unwrap()
             .try_into()
-            .map_err(|_| ErrorCode::U128CannotConvert)?;
+            .map_err(|_| error!(ErrorCode::U128CannotConvert))?;
         token::burn(ctx.accounts.into_burn().with_signer(&[&seeds]), burn_amount)?;
 
         // Stake.
@@ -165,7 +165,7 @@ pub mod cfo {
             .checked_div(100)
             .unwrap()
             .try_into()
-            .map_err(|_| ErrorCode::U128CannotConvert)?;
+            .map_err(|_| error!(ErrorCode::U128CannotConvert))?;
         token::transfer(
             ctx.accounts.into_stake_transfer().with_signer(&[&seeds]),
             stake_amount,
@@ -178,7 +178,7 @@ pub mod cfo {
             .checked_div(100)
             .unwrap()
             .try_into()
-            .map_err(|_| ErrorCode::U128CannotConvert)?;
+            .map_err(|_| error!(ErrorCode::U128CannotConvert))?;
         token::transfer(
             ctx.accounts.into_treasury_transfer().with_signer(&[&seeds]),
             treasury_amount,
@@ -236,7 +236,7 @@ pub mod cfo {
             .checked_div(total_pool_value)
             .unwrap()
             .try_into()
-            .map_err(|_| ErrorCode::U128CannotConvert)?;
+            .map_err(|_| error!(ErrorCode::U128CannotConvert))?;
 
         // Proportion of the reward going to the msrm pool.
         //
@@ -248,7 +248,7 @@ pub mod cfo {
             .checked_div(total_pool_value)
             .unwrap()
             .try_into()
-            .map_err(|_| ErrorCode::U128CannotConvert)?;
+            .map_err(|_| error!(ErrorCode::U128CannotConvert))?;
 
         // SRM drop.
         {
@@ -606,9 +606,9 @@ pub struct Distribute<'info> {
         has_one = treasury,
         has_one = stake,
     )]
-    officer: Account<'info, Officer>,
+    officer: Box<Account<'info, Officer>>,
     #[account(mut)]
-    treasury: Account<'info, TokenAccount>,
+    treasury: Box<Account<'info, TokenAccount>>,
     #[account(mut)]
     stake: Account<'info, TokenAccount>,
     #[account(mut)]
@@ -900,7 +900,7 @@ pub struct OfficerDidCreate {
 
 // Error.
 
-#[error]
+#[error_code]
 pub enum ErrorCode {
     #[msg("Distribution does not add to 100")]
     InvalidDistribution,
@@ -918,14 +918,14 @@ pub enum ErrorCode {
 
 fn is_distribution_valid(d: &Distribution) -> Result<()> {
     if d.burn + d.stake + d.treasury != 100 {
-        return Err(ErrorCode::InvalidDistribution.into());
+        return err!(ErrorCode::InvalidDistribution);
     }
     Ok(())
 }
 
 fn is_distribution_ready(accounts: &Distribute) -> Result<()> {
     if accounts.srm_vault.amount < 1_000_000 {
-        return Err(ErrorCode::InsufficientDistributionAmount.into());
+        return err!(ErrorCode::InsufficientDistributionAmount);
     }
     Ok(())
 }
@@ -934,7 +934,7 @@ fn is_distribution_ready(accounts: &Distribute) -> Result<()> {
 fn is_not_trading(ixs: &UncheckedAccount) -> Result<()> {
     let data = ixs.try_borrow_data()?;
     match tx_instructions::load_instruction_at(1, &data) {
-        Ok(_) => Err(ErrorCode::TooManyInstructions.into()),
+        Ok(_) => err!(ErrorCode::TooManyInstructions),
         Err(_) => Ok(()),
     }
 }
@@ -943,7 +943,7 @@ fn is_stake_reward_ready(accounts: &DropStakeReward) -> Result<()> {
     // Min drop is 15,0000 SRM.
     let min_reward: u64 = 15_000_000_000;
     if accounts.stake.amount < min_reward {
-        return Err(ErrorCode::InsufficientStakeReward.into());
+        return err!(ErrorCode::InsufficientStakeReward);
     }
     Ok(())
 }

+ 1 - 1
tests/chat/programs/chat/src/lib.rs

@@ -107,7 +107,7 @@ pub struct Message {
     pub data: [u8; 280],
 }
 
-#[error]
+#[error_code]
 pub enum ErrorCode {
     Unknown,
 }

+ 2 - 2
tests/composite/programs/composite/src/lib.rs

@@ -8,7 +8,7 @@ declare_id!("EHthziFziNoac9LBGxEaVN47Y3uUiRoXvqAiR6oes4iU");
 #[program]
 mod composite {
     use super::*;
-    pub fn initialize(_ctx: Context<Initialize>) -> ProgramResult {
+    pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
         Ok(())
     }
 
@@ -16,7 +16,7 @@ mod composite {
         ctx: Context<CompositeUpdate>,
         dummy_a: u64,
         dummy_b: u64,
-    ) -> ProgramResult {
+    ) -> Result<()> {
         let a = &mut ctx.accounts.foo.dummy_a;
         let b = &mut ctx.accounts.bar.dummy_b;
 

+ 1 - 2
tests/declare-id/programs/declare-id/src/lib.rs

@@ -3,12 +3,11 @@ use anchor_lang::prelude::*;
 // Intentionally different program id than the one defined in Anchor.toml.
 declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
 
-
 #[program]
 mod declare_id {
     use super::*;
 
-    pub fn initialize(_ctx: Context<Initialize>) -> ProgramResult {
+    pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
         Ok(())
     }
 }

+ 21 - 4
tests/errors/programs/errors/src/lib.rs

@@ -10,15 +10,32 @@ mod errors {
     use super::*;
 
     pub fn hello(_ctx: Context<Hello>) -> Result<()> {
-        Err(MyError::Hello.into())
+        err!(MyError::Hello)
     }
 
     pub fn hello_no_msg(_ctx: Context<Hello>) -> Result<()> {
-        Err(MyError::HelloNoMsg.into())
+        err!(MyError::HelloNoMsg)
     }
 
     pub fn hello_next(_ctx: Context<Hello>) -> Result<()> {
-        Err(MyError::HelloNext.into())
+        err!(MyError::HelloNext)
+    }
+
+    pub fn test_require(_ctx: Context<Hello>) -> Result<()> {
+        require!(false, MyError::Hello);
+        Ok(())
+    }
+
+    pub fn test_err(_ctx: Context<Hello>) -> Result<()> {
+        err!(MyError::Hello)
+    }
+
+    pub fn test_program_error(_ctx: Context<Hello>) -> Result<()> {
+        Err(ProgramError::InvalidAccountData.into())
+    }
+
+    pub fn test_program_error_with_source(_ctx: Context<Hello>) -> Result<()> {
+        Err(Error::from(ProgramError::InvalidAccountData).with_source(source!()))
     }
 
     pub fn mut_error(_ctx: Context<MutError>) -> Result<()> {
@@ -82,7 +99,7 @@ pub struct AccountNotInitializedError<'info> {
     not_initialized_account: Account<'info, AnyAccount>,
 }
 
-#[error]
+#[error_code]
 pub enum MyError {
     #[msg("This is an error message clients will automatically display")]
     Hello,

+ 124 - 32
tests/errors/tests/errors.js

@@ -2,15 +2,62 @@ const assert = require("assert");
 const anchor = require("@project-serum/anchor");
 const { Account, Transaction, TransactionInstruction } = anchor.web3;
 
+// sleep to allow logs to come in
+const sleep = (ms) =>
+  new Promise((resolve) => {
+    setTimeout(() => resolve(), ms);
+  });
+
+const withLogTest = async (callback, expectedLog) => {
+  let logTestOk = false;
+  const listener = anchor.getProvider().connection.onLogs(
+    "all",
+    (logs) => {
+      if (logs.logs.some((logLine) => logLine === expectedLog)) {
+        logTestOk = true;
+      } else {
+        console.log(logs);
+      }
+    },
+    "recent"
+  );
+  try {
+    await callback();
+  } catch (err) {
+    anchor.getProvider().connection.removeOnLogsListener(listener);
+    throw err;
+  }
+  await sleep(3000);
+  anchor.getProvider().connection.removeOnLogsListener(listener);
+  assert.ok(logTestOk);
+};
+
 describe("errors", () => {
   // Configure the client to use the local cluster.
-  anchor.setProvider(anchor.Provider.local());
+  const localProvider = anchor.Provider.local();
+  localProvider.opts.skipPreflight = true;
+  anchor.setProvider(localProvider);
 
   const program = anchor.workspace.Errors;
 
   it("Emits a Hello error", async () => {
+    await withLogTest(async () => {
+      try {
+        const tx = await program.rpc.hello();
+        assert.ok(false);
+      } catch (err) {
+        const errMsg =
+          "This is an error message clients will automatically display";
+        assert.equal(err.toString(), errMsg);
+        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.");
+  });
+
+  it("Emits a Hello error via require!", async () => {
     try {
-      const tx = await program.rpc.hello();
+      const tx = await program.rpc.testRequire();
       assert.ok(false);
     } catch (err) {
       const errMsg =
@@ -21,6 +68,41 @@ describe("errors", () => {
     }
   });
 
+  it("Emits a Hello error via err!", async () => {
+    try {
+      const tx = await program.rpc.testErr();
+      assert.ok(false);
+    } catch (err) {
+      const errMsg =
+        "This is an error message clients will automatically display";
+      assert.equal(err.toString(), errMsg);
+      assert.equal(err.msg, errMsg);
+      assert.equal(err.code, 6000);
+    }
+  });
+
+  it("Logs a ProgramError", async () => {
+    await withLogTest(async () => {
+      try {
+        const tx = await program.rpc.testProgramError();
+        assert.ok(false);
+      } 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.");
+  });
+
+  it("Logs a ProgramError with source", async () => {
+    await withLogTest(async () => {
+      try {
+        const tx = await program.rpc.testProgramErrorWithSource();
+        assert.ok(false);
+      } 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.");
+  });
+
   it("Emits a HelloNoMsg error", async () => {
     try {
       const tx = await program.rpc.helloNoMsg();
@@ -46,19 +128,21 @@ describe("errors", () => {
   });
 
   it("Emits a mut error", async () => {
-    try {
-      const tx = await program.rpc.mutError({
-        accounts: {
-          myAccount: anchor.web3.SYSVAR_RENT_PUBKEY,
-        },
-      });
-      assert.ok(false);
-    } catch (err) {
-      const errMsg = "A mut constraint was violated";
-      assert.equal(err.toString(), errMsg);
-      assert.equal(err.msg, errMsg);
-      assert.equal(err.code, 2000);
-    }
+    await withLogTest(async () => {
+      try {
+        const tx = await program.rpc.mutError({
+          accounts: {
+            myAccount: anchor.web3.SYSVAR_RENT_PUBKEY,
+          },
+        });
+        assert.ok(false);
+      } catch (err) {
+        const errMsg = "A mut constraint was violated";
+        assert.equal(err.toString(), errMsg);
+        assert.equal(err.msg, errMsg);
+        assert.equal(err.code, 2000);
+      }
+    }, "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 () => {
@@ -88,8 +172,11 @@ describe("errors", () => {
   // instance since the client won't allow one to send a transaction
   // with an invalid signer account.
   it("Emits a signer error", async () => {
+    let signature;
+    const listener = anchor
+      .getProvider()
+      .connection.onLogs("all", (logs) => (signature = logs.signature));
     try {
-      const account = new Account();
       const tx = new Transaction();
       tx.add(
         new TransactionInstruction({
@@ -107,9 +194,12 @@ describe("errors", () => {
       await program.provider.send(tx);
       assert.ok(false);
     } catch (err) {
-      const errMsg =
-        "Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0xbc2";
+      await sleep(3000);
+      anchor.getProvider().connection.removeOnLogsListener(listener);
+      const errMsg = `Error: Raw transaction ${signature} failed ({"err":{"InstructionError":[0,{"Custom":3010}]}})`;
       assert.equal(err.toString(), errMsg);
+    } finally {
+      anchor.getProvider().connection.removeOnLogsListener(listener);
     }
   });
 
@@ -130,19 +220,21 @@ describe("errors", () => {
   });
 
   it("Emits a account not initialized error", async () => {
-    try {
-      const tx = await program.rpc.accountNotInitializedError({
-        accounts: {
-          notInitializedAccount: new anchor.web3.Keypair().publicKey,
-        },
-      });
-      assert.fail(
-        "Unexpected success in creating a transaction that should have fail with `AccountNotInitialized` error"
-      );
-    } catch (err) {
-      const errMsg =
-        "The program expected this account to be already initialized";
-      assert.equal(err.toString(), errMsg);
-    }
+    await withLogTest(async () => {
+      try {
+        const tx = await program.rpc.accountNotInitializedError({
+          accounts: {
+            notInitializedAccount: new anchor.web3.Keypair().publicKey,
+          },
+        });
+        assert.fail(
+          "Unexpected success in creating a transaction that should have fail with `AccountNotInitialized` error"
+        );
+      } catch (err) {
+        const errMsg =
+          "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.");
   });
 });

+ 3 - 3
tests/escrow/programs/escrow/src/lib.rs

@@ -31,7 +31,7 @@ pub mod escrow {
         ctx: Context<InitializeEscrow>,
         initializer_amount: u64,
         taker_amount: u64,
-    ) -> ProgramResult {
+    ) -> Result<()> {
         ctx.accounts.escrow_account.initializer_key = *ctx.accounts.initializer.key;
         ctx.accounts
             .escrow_account
@@ -55,7 +55,7 @@ pub mod escrow {
         Ok(())
     }
 
-    pub fn cancel_escrow(ctx: Context<CancelEscrow>) -> ProgramResult {
+    pub fn cancel_escrow(ctx: Context<CancelEscrow>) -> Result<()> {
         let (_pda, bump_seed) = Pubkey::find_program_address(&[ESCROW_PDA_SEED], ctx.program_id);
         let seeds = &[&ESCROW_PDA_SEED[..], &[bump_seed]];
 
@@ -70,7 +70,7 @@ pub mod escrow {
         Ok(())
     }
 
-    pub fn exchange(ctx: Context<Exchange>) -> ProgramResult {
+    pub fn exchange(ctx: Context<Exchange>) -> Result<()> {
         // Transferring from initializer to taker
         let (_pda, bump_seed) = Pubkey::find_program_address(&[ESCROW_PDA_SEED], ctx.program_id);
         let seeds = &[&ESCROW_PDA_SEED[..], &[bump_seed]];

+ 2 - 2
tests/events/programs/events/src/lib.rs

@@ -8,7 +8,7 @@ declare_id!("2dhGsWUzy5YKUsjZdLHLmkNpUDAXkNa9MYWsPc4Ziqzy");
 #[program]
 pub mod events {
     use super::*;
-    pub fn initialize(_ctx: Context<Initialize>) -> ProgramResult {
+    pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
         emit!(MyEvent {
             data: 5,
             label: "hello".to_string(),
@@ -16,7 +16,7 @@ pub mod events {
         Ok(())
     }
 
-    pub fn test_event(_ctx: Context<TestEvent>) -> ProgramResult {
+    pub fn test_event(_ctx: Context<TestEvent>) -> Result<()> {
         emit!(MyOtherEvent {
             data: 6,
             label: "bye".to_string(),

+ 2 - 2
tests/floats/programs/floats/src/lib.rs

@@ -6,7 +6,7 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
 pub mod floats {
     use super::*;
 
-    pub fn create(ctx: Context<Create>, data_f32: f32, data_f64: f64) -> ProgramResult {
+    pub fn create(ctx: Context<Create>, data_f32: f32, data_f64: f64) -> Result<()> {
         let account = &mut ctx.accounts.account;
         let authority = &mut ctx.accounts.authority;
 
@@ -17,7 +17,7 @@ pub mod floats {
         Ok(())
     }
 
-    pub fn update(ctx: Context<Update>, data_f32: f32, data_f64: f64) -> ProgramResult {
+    pub fn update(ctx: Context<Update>, data_f32: f32, data_f64: f64) -> Result<()> {
         let account = &mut ctx.accounts.account;
 
         account.data_f32 = data_f32;

+ 26 - 26
tests/ido-pool/programs/ido-pool/src/lib.rs

@@ -22,7 +22,7 @@ pub mod ido_pool {
         _bumps: PoolBumps, // No longer used.
         num_ido_tokens: u64,
         ido_times: IdoTimes,
-    ) -> ProgramResult {
+    ) -> Result<()> {
         msg!("INITIALIZE POOL");
 
         let ido_account = &mut ctx.accounts.ido_account;
@@ -63,7 +63,7 @@ pub mod ido_pool {
     }
 
     #[access_control(unrestricted_phase(&ctx.accounts.ido_account))]
-    pub fn init_user_redeemable(ctx: Context<InitUserRedeemable>) -> ProgramResult {
+    pub fn init_user_redeemable(ctx: Context<InitUserRedeemable>) -> Result<()> {
         msg!("INIT USER REDEEMABLE");
         Ok(())
     }
@@ -72,11 +72,11 @@ pub mod ido_pool {
     pub fn exchange_usdc_for_redeemable(
         ctx: Context<ExchangeUsdcForRedeemable>,
         amount: u64,
-    ) -> ProgramResult {
+    ) -> Result<()> {
         msg!("EXCHANGE USDC FOR REDEEMABLE");
         // While token::transfer will check this, we prefer a verbose err msg.
         if ctx.accounts.user_usdc.amount < amount {
-            return Err(ErrorCode::LowUsdc.into());
+            return err!(ErrorCode::LowUsdc);
         }
 
         // Transfer user's USDC to pool USDC account.
@@ -109,7 +109,7 @@ pub mod ido_pool {
     }
 
     #[access_control(withdraw_phase(&ctx.accounts.ido_account))]
-    pub fn init_escrow_usdc(ctx: Context<InitEscrowUsdc>) -> ProgramResult {
+    pub fn init_escrow_usdc(ctx: Context<InitEscrowUsdc>) -> Result<()> {
         msg!("INIT ESCROW USDC");
         Ok(())
     }
@@ -118,11 +118,11 @@ pub mod ido_pool {
     pub fn exchange_redeemable_for_usdc(
         ctx: Context<ExchangeRedeemableForUsdc>,
         amount: u64,
-    ) -> ProgramResult {
+    ) -> Result<()> {
         msg!("EXCHANGE REDEEMABLE FOR USDC");
         // While token::burn will check this, we prefer a verbose err msg.
         if ctx.accounts.user_redeemable.amount < amount {
-            return Err(ErrorCode::LowRedeemable.into());
+            return err!(ErrorCode::LowRedeemable);
         }
 
         let ido_name = ctx.accounts.ido_account.ido_name.as_ref();
@@ -159,11 +159,11 @@ pub mod ido_pool {
     pub fn exchange_redeemable_for_watermelon(
         ctx: Context<ExchangeRedeemableForWatermelon>,
         amount: u64,
-    ) -> ProgramResult {
+    ) -> Result<()> {
         msg!("EXCHANGE REDEEMABLE FOR WATERMELON");
         // While token::burn will check this, we prefer a verbose err msg.
         if ctx.accounts.user_redeemable.amount < amount {
-            return Err(ErrorCode::LowRedeemable.into());
+            return err!(ErrorCode::LowRedeemable);
         }
 
         // Calculate watermelon tokens due.
@@ -217,7 +217,7 @@ pub mod ido_pool {
     }
 
     #[access_control(ido_over(&ctx.accounts.ido_account))]
-    pub fn withdraw_pool_usdc(ctx: Context<WithdrawPoolUsdc>) -> ProgramResult {
+    pub fn withdraw_pool_usdc(ctx: Context<WithdrawPoolUsdc>) -> Result<()> {
         msg!("WITHDRAW POOL USDC");
         // Transfer total USDC from pool account to ido_authority account.
         let ido_name = ctx.accounts.ido_account.ido_name.as_ref();
@@ -239,11 +239,11 @@ pub mod ido_pool {
     }
 
     #[access_control(escrow_over(&ctx.accounts.ido_account))]
-    pub fn withdraw_from_escrow(ctx: Context<WithdrawFromEscrow>, amount: u64) -> ProgramResult {
+    pub fn withdraw_from_escrow(ctx: Context<WithdrawFromEscrow>, amount: u64) -> Result<()> {
         msg!("WITHDRAW FROM ESCROW");
         // While token::transfer will check this, we prefer a verbose err msg.
         if ctx.accounts.escrow_usdc.amount < amount {
-            return Err(ErrorCode::LowUsdc.into());
+            return err!(ErrorCode::LowUsdc);
         }
 
         let ido_name = ctx.accounts.ido_account.ido_name.as_ref();
@@ -573,7 +573,7 @@ pub struct PoolBumps {
     pub pool_usdc: u8,
 }
 
-#[error]
+#[error_code]
 pub enum ErrorCode {
     #[msg("IDO must start in the future")]
     IdoFuture,
@@ -602,55 +602,55 @@ pub enum ErrorCode {
 // Access control modifiers.
 
 // Asserts the IDO starts in the future.
-fn validate_ido_times(ido_times: IdoTimes) -> ProgramResult {
+fn validate_ido_times(ido_times: IdoTimes) -> Result<()> {
     let clock = Clock::get()?;
     if ido_times.start_ido <= clock.unix_timestamp {
-        return Err(ErrorCode::IdoFuture.into());
+        return err!(ErrorCode::IdoFuture);
     }
     if !(ido_times.start_ido < ido_times.end_deposits
         && ido_times.end_deposits < ido_times.end_ido
         && ido_times.end_ido < ido_times.end_escrow)
     {
-        return Err(ErrorCode::SeqTimes.into());
+        return err!(ErrorCode::SeqTimes);
     }
     Ok(())
 }
 
 // Asserts the IDO is still accepting deposits.
-fn unrestricted_phase(ido_account: &IdoAccount) -> ProgramResult {
+fn unrestricted_phase(ido_account: &IdoAccount) -> Result<()> {
     let clock = Clock::get()?;
     if clock.unix_timestamp <= ido_account.ido_times.start_ido {
-        return Err(ErrorCode::StartIdoTime.into());
+        return err!(ErrorCode::StartIdoTime);
     } else if ido_account.ido_times.end_deposits <= clock.unix_timestamp {
-        return Err(ErrorCode::EndDepositsTime.into());
+        return err!(ErrorCode::EndDepositsTime);
     }
     Ok(())
 }
 
 // Asserts the IDO has started but not yet finished.
-fn withdraw_phase(ido_account: &IdoAccount) -> ProgramResult {
+fn withdraw_phase(ido_account: &IdoAccount) -> Result<()> {
     let clock = Clock::get()?;
     if clock.unix_timestamp <= ido_account.ido_times.start_ido {
-        return Err(ErrorCode::StartIdoTime.into());
+        return err!(ErrorCode::StartIdoTime);
     } else if ido_account.ido_times.end_ido <= clock.unix_timestamp {
-        return Err(ErrorCode::EndIdoTime.into());
+        return err!(ErrorCode::EndIdoTime);
     }
     Ok(())
 }
 
 // Asserts the IDO sale period has ended.
-fn ido_over(ido_account: &IdoAccount) -> ProgramResult {
+fn ido_over(ido_account: &IdoAccount) -> Result<()> {
     let clock = Clock::get()?;
     if clock.unix_timestamp <= ido_account.ido_times.end_ido {
-        return Err(ErrorCode::IdoNotOver.into());
+        return err!(ErrorCode::IdoNotOver);
     }
     Ok(())
 }
 
-fn escrow_over(ido_account: &IdoAccount) -> ProgramResult {
+fn escrow_over(ido_account: &IdoAccount) -> Result<()> {
     let clock = Clock::get()?;
     if clock.unix_timestamp <= ido_account.ido_times.end_escrow {
-        return Err(ErrorCode::EscrowNotOver.into());
+        return err!(ErrorCode::EscrowNotOver);
     }
     Ok(())
 }

+ 3 - 3
tests/interface/programs/counter-auth/src/lib.rs

@@ -16,14 +16,14 @@ pub mod counter_auth {
     pub struct CounterAuth;
 
     impl<'info> Auth<'info, Empty> for CounterAuth {
-        fn is_authorized(_ctx: Context<Empty>, current: u64, new: u64) -> ProgramResult {
+        fn is_authorized(_ctx: Context<Empty>, current: u64, new: u64) -> Result<()> {
             if current % 2 == 0 {
                 if new % 2 == 0 {
-                    return Err(ProgramError::Custom(15000)); // Arbitrary error code.
+                    return Err(ProgramError::Custom(15000).into()); // Arbitrary error code.
                 }
             } else {
                 if new % 2 == 1 {
-                    return Err(ProgramError::Custom(16000)); // Arbitrary error code.
+                    return Err(ProgramError::Custom(16000).into()); // Arbitrary error code.
                 }
             }
             Ok(())

+ 3 - 3
tests/interface/programs/counter/src/lib.rs

@@ -55,7 +55,7 @@ impl<'info> SetCount<'info> {
     // we separate it from the business logic of the instruction handler itself.
     pub fn accounts(counter: &Counter, ctx: &Context<SetCount>) -> Result<()> {
         if ctx.accounts.auth_program.key != &counter.auth_program {
-            return Err(ErrorCode::InvalidAuthProgram.into());
+            return err!(ErrorCode::InvalidAuthProgram);
         }
         Ok(())
     }
@@ -63,10 +63,10 @@ impl<'info> SetCount<'info> {
 
 #[interface]
 pub trait Auth<'info, T: Accounts<'info>> {
-    fn is_authorized(ctx: Context<T>, current: u64, new: u64) -> ProgramResult;
+    fn is_authorized(ctx: Context<T>, current: u64, new: u64) -> Result<()>;
 }
 
-#[error]
+#[error_code]
 pub enum ErrorCode {
     #[msg("Invalid auth program.")]
     InvalidAuthProgram,

+ 18 - 17
tests/lockup/programs/lockup/src/lib.rs

@@ -39,10 +39,10 @@ pub mod lockup {
         #[access_control(whitelist_auth(self, &ctx))]
         pub fn whitelist_add(&mut self, ctx: Context<Auth>, entry: WhitelistEntry) -> Result<()> {
             if self.whitelist.len() == Self::WHITELIST_SIZE {
-                return Err(ErrorCode::WhitelistFull.into());
+                return err!(ErrorCode::WhitelistFull);
             }
             if self.whitelist.contains(&entry) {
-                return Err(ErrorCode::WhitelistEntryAlreadyExists.into());
+                return err!(ErrorCode::WhitelistEntryAlreadyExists);
             }
             self.whitelist.push(entry);
             Ok(())
@@ -55,7 +55,7 @@ pub mod lockup {
             entry: WhitelistEntry,
         ) -> Result<()> {
             if !self.whitelist.contains(&entry) {
-                return Err(ErrorCode::WhitelistEntryNotFound.into());
+                return err!(ErrorCode::WhitelistEntryNotFound);
             }
             self.whitelist.retain(|e| e != &entry);
             Ok(())
@@ -80,10 +80,10 @@ pub mod lockup {
         realizor: Option<Realizor>,
     ) -> Result<()> {
         if deposit_amount == 0 {
-            return Err(ErrorCode::InvalidDepositAmount.into());
+            return err!(ErrorCode::InvalidDepositAmount);
         }
         if !is_valid_schedule(start_ts, end_ts, period_count) {
-            return Err(ErrorCode::InvalidSchedule.into());
+            return err!(ErrorCode::InvalidSchedule);
         }
         let vesting = &mut ctx.accounts.vesting;
         vesting.beneficiary = beneficiary;
@@ -114,7 +114,7 @@ pub mod lockup {
                 ctx.accounts.clock.unix_timestamp,
             )
         {
-            return Err(ErrorCode::InsufficientWithdrawalBalance.into());
+            return err!(ErrorCode::InsufficientWithdrawalBalance);
         }
 
         // Transfer funds out.
@@ -151,7 +151,7 @@ pub mod lockup {
         // CPI safety checks.
         let withdraw_amount = before_amount - after_amount;
         if withdraw_amount > amount {
-            return Err(ErrorCode::WhitelistWithdrawLimit)?;
+            return err!(ErrorCode::WhitelistWithdrawLimit);
         }
 
         // Bookeeping.
@@ -177,10 +177,10 @@ pub mod lockup {
         // CPI safety checks.
         let deposit_amount = after_amount - before_amount;
         if deposit_amount <= 0 {
-            return Err(ErrorCode::InsufficientWhitelistDepositAmount)?;
+            return err!(ErrorCode::InsufficientWhitelistDepositAmount);
         }
         if deposit_amount > ctx.accounts.transfer.vesting.whitelist_owned {
-            return Err(ErrorCode::WhitelistDepositOverflow)?;
+            return err!(ErrorCode::WhitelistDepositOverflow)?;
         }
 
         // Bookkeeping.
@@ -234,9 +234,9 @@ impl<'info> CreateVesting<'info> {
             ],
             ctx.program_id,
         )
-        .map_err(|_| ErrorCode::InvalidProgramAddress)?;
+        .map_err(|_| error!(ErrorCode::InvalidProgramAddress))?;
         if ctx.accounts.vault.owner != vault_authority {
-            return Err(ErrorCode::InvalidVaultOwner)?;
+            return err!(ErrorCode::InvalidVaultOwner)?;
         }
 
         Ok(())
@@ -365,7 +365,7 @@ pub struct WhitelistEntry {
     pub program_id: Pubkey,
 }
 
-#[error]
+#[error_code]
 pub enum ErrorCode {
     #[msg("Vesting end must be greater than the current unix timestamp.")]
     InvalidTimestamp,
@@ -484,14 +484,14 @@ pub fn is_whitelisted<'info>(transfer: &WhitelistTransfer<'info>) -> Result<()>
     if !transfer.lockup.whitelist.contains(&WhitelistEntry {
         program_id: *transfer.whitelisted_program.key,
     }) {
-        return Err(ErrorCode::WhitelistEntryNotFound.into());
+        return err!(ErrorCode::WhitelistEntryNotFound);
     }
     Ok(())
 }
 
 fn whitelist_auth(lockup: &Lockup, ctx: &Context<Auth>) -> Result<()> {
     if &lockup.authority != ctx.accounts.authority.key {
-        return Err(ErrorCode::Unauthorized.into());
+        return err!(ErrorCode::Unauthorized);
     }
     Ok(())
 }
@@ -517,14 +517,15 @@ fn is_realized(ctx: &Context<Withdraw>) -> Result<()> {
         let cpi_program = {
             let p = ctx.remaining_accounts[0].clone();
             if p.key != &realizor.program {
-                return Err(ErrorCode::InvalidLockRealizor.into());
+                return err!(ErrorCode::InvalidLockRealizor);
             }
             p
         };
         let cpi_accounts = ctx.remaining_accounts.to_vec()[1..].to_vec();
         let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
         let vesting = (*ctx.accounts.vesting).clone();
-        realize_lock::is_realized(cpi_ctx, vesting).map_err(|_| ErrorCode::UnrealizedVesting)?;
+        realize_lock::is_realized(cpi_ctx, vesting)
+            .map_err(|_| error!(ErrorCode::UnrealizedVesting))?;
     }
     Ok(())
 }
@@ -537,5 +538,5 @@ fn is_realized(ctx: &Context<Withdraw>) -> Result<()> {
 /// until one has completely unstaked.
 #[interface]
 pub trait RealizeLock<'info, T: Accounts<'info>> {
-    fn is_realized(ctx: Context<T>, v: Vesting) -> ProgramResult;
+    fn is_realized(ctx: Context<T>, v: Vesting) -> Result<()>;
 }

+ 25 - 25
tests/lockup/programs/registry/src/lib.rs

@@ -41,7 +41,7 @@ mod registry {
                 .parse()
                 .unwrap();
             if ctx.accounts.authority.key != &expected {
-                return Err(ErrorCode::InvalidProgramAuthority.into());
+                return err!(ErrorCode::InvalidProgramAuthority);
             }
 
             self.lockup_program = lockup_program;
@@ -51,16 +51,16 @@ mod registry {
     }
 
     impl<'info> RealizeLock<'info, IsRealized<'info>> for Registry {
-        fn is_realized(ctx: Context<IsRealized>, v: Vesting) -> ProgramResult {
+        fn is_realized(ctx: Context<IsRealized>, v: Vesting) -> Result<()> {
             if let Some(realizor) = &v.realizor {
                 if &realizor.metadata != ctx.accounts.member.to_account_info().key {
-                    return Err(ErrorCode::InvalidRealizorMetadata.into());
+                    return err!(ErrorCode::InvalidRealizorMetadata);
                 }
                 assert!(ctx.accounts.member.beneficiary == v.beneficiary);
                 let total_staked =
                     ctx.accounts.member_spt.amount + ctx.accounts.member_spt_locked.amount;
                 if total_staked != 0 {
-                    return Err(ErrorCode::UnrealizedReward.into());
+                    return err!(ErrorCode::UnrealizedReward);
                 }
             }
             Ok(())
@@ -285,7 +285,7 @@ mod registry {
 
     pub fn end_unstake(ctx: Context<EndUnstake>) -> Result<()> {
         if ctx.accounts.pending_withdrawal.end_ts > ctx.accounts.clock.unix_timestamp {
-            return Err(ErrorCode::UnstakeTimelock.into());
+            return err!(ErrorCode::UnstakeTimelock);
         }
 
         // Select which balance set this affects.
@@ -298,10 +298,10 @@ mod registry {
         };
         // Check the vaults given are corrrect.
         if &balances.vault != ctx.accounts.vault.key {
-            return Err(ErrorCode::InvalidVault.into());
+            return err!(ErrorCode::InvalidVault);
         }
         if &balances.vault_pw != ctx.accounts.vault_pw.key {
-            return Err(ErrorCode::InvalidVault.into());
+            return err!(ErrorCode::InvalidVault);
         }
 
         // Transfer tokens between vaults.
@@ -377,10 +377,10 @@ mod registry {
         nonce: u8,
     ) -> Result<()> {
         if total < ctx.accounts.pool_mint.supply {
-            return Err(ErrorCode::InsufficientReward.into());
+            return err!(ErrorCode::InsufficientReward);
         }
         if ctx.accounts.clock.unix_timestamp >= expiry_ts {
-            return Err(ErrorCode::InvalidExpiry.into());
+            return err!(ErrorCode::InvalidExpiry);
         }
         if let RewardVendorKind::Locked {
             start_ts,
@@ -389,7 +389,7 @@ mod registry {
         } = kind
         {
             if !lockup::is_valid_schedule(start_ts, end_ts, period_count) {
-                return Err(ErrorCode::InvalidVestingSchedule.into());
+                return err!(ErrorCode::InvalidVestingSchedule);
             }
         }
 
@@ -426,7 +426,7 @@ mod registry {
     #[access_control(reward_eligible(&ctx.accounts.cmn))]
     pub fn claim_reward(ctx: Context<ClaimReward>) -> Result<()> {
         if RewardVendorKind::Unlocked != ctx.accounts.cmn.vendor.kind {
-            return Err(ErrorCode::ExpectedUnlockedVendor.into());
+            return err!(ErrorCode::ExpectedUnlockedVendor);
         }
         // Reward distribution.
         let spt_total =
@@ -469,7 +469,7 @@ mod registry {
         nonce: u8,
     ) -> Result<()> {
         let (start_ts, end_ts, period_count) = match ctx.accounts.cmn.vendor.kind {
-            RewardVendorKind::Unlocked => return Err(ErrorCode::ExpectedLockedVendor.into()),
+            RewardVendorKind::Unlocked => return err!(ErrorCode::ExpectedLockedVendor),
             RewardVendorKind::Locked {
                 start_ts,
                 end_ts,
@@ -535,7 +535,7 @@ mod registry {
 
     pub fn expire_reward(ctx: Context<ExpireReward>) -> Result<()> {
         if ctx.accounts.clock.unix_timestamp < ctx.accounts.vendor.expiry_ts {
-            return Err(ErrorCode::VendorNotYetExpired.into());
+            return err!(ErrorCode::VendorNotYetExpired);
         }
 
         // Send all remaining funds to the expiry receiver's token.
@@ -583,9 +583,9 @@ impl<'info> Initialize<'info> {
             ],
             ctx.program_id,
         )
-        .map_err(|_| ErrorCode::InvalidNonce)?;
+        .map_err(|_| error!(ErrorCode::InvalidNonce))?;
         if ctx.accounts.pool_mint.mint_authority != COption::Some(registrar_signer) {
-            return Err(ErrorCode::InvalidPoolMintAuthority.into());
+            return err!(ErrorCode::InvalidPoolMintAuthority);
         }
         assert!(ctx.accounts.pool_mint.supply == 0);
         Ok(())
@@ -633,9 +633,9 @@ impl<'info> CreateMember<'info> {
             &[nonce],
         ];
         let member_signer = Pubkey::create_program_address(seeds, ctx.program_id)
-            .map_err(|_| ErrorCode::InvalidNonce)?;
+            .map_err(|_| error!(ErrorCode::InvalidNonce))?;
         if &member_signer != ctx.accounts.member_signer.to_account_info().key {
-            return Err(ErrorCode::InvalidMemberSigner.into());
+            return err!(ErrorCode::InvalidMemberSigner);
         }
 
         Ok(())
@@ -931,9 +931,9 @@ impl<'info> DropReward<'info> {
             ],
             ctx.program_id,
         )
-        .map_err(|_| ErrorCode::InvalidNonce)?;
+        .map_err(|_| error!(ErrorCode::InvalidNonce))?;
         if vendor_signer != ctx.accounts.vendor_vault.owner {
-            return Err(ErrorCode::InvalidVaultOwner.into());
+            return err!(ErrorCode::InvalidVaultOwner);
         }
 
         Ok(())
@@ -960,7 +960,7 @@ pub struct ClaimRewardLocked<'info> {
 #[derive(Accounts)]
 pub struct ClaimRewardCommon<'info> {
     // Stake instance.
-    registrar: Account<'info, Registrar>,
+    registrar: Box<Account<'info, Registrar>>,
     // Member.
     #[account(mut, has_one = registrar, has_one = beneficiary)]
     member: Account<'info, Member>,
@@ -1173,7 +1173,7 @@ pub enum RewardVendorKind {
     },
 }
 
-#[error]
+#[error_code]
 pub enum ErrorCode {
     #[msg("The given reward queue has already been initialized.")]
     RewardQAlreadyInitialized,
@@ -1284,13 +1284,13 @@ fn reward_eligible(cmn: &ClaimRewardCommon) -> Result<()> {
     let vendor = &cmn.vendor;
     let member = &cmn.member;
     if vendor.expired {
-        return Err(ErrorCode::VendorExpired.into());
+        return err!(ErrorCode::VendorExpired);
     }
     if member.rewards_cursor > vendor.reward_event_q_cursor {
-        return Err(ErrorCode::CursorAlreadyProcessed.into());
+        return err!(ErrorCode::CursorAlreadyProcessed);
     }
     if member.last_stake_ts > vendor.start_ts {
-        return Err(ErrorCode::NotStakedDuringDrop.into());
+        return err!(ErrorCode::NotStakedDuringDrop);
     }
     Ok(())
 }
@@ -1316,7 +1316,7 @@ pub fn no_available_rewards<'info>(
         let r_event = reward_q.get(cursor);
         if member.last_stake_ts < r_event.ts {
             if balances.spt.amount > 0 || balances_locked.spt.amount > 0 {
-                return Err(ErrorCode::RewardsNeedsProcessing.into());
+                return err!(ErrorCode::RewardsNeedsProcessing);
             }
         }
         cursor += 1;

+ 46 - 46
tests/misc/programs/misc/src/lib.rs

@@ -30,41 +30,41 @@ pub mod misc {
     }
 
     impl MyState {
-        pub fn new(_ctx: Context<Ctor>) -> Result<Self, ProgramError> {
+        pub fn new(_ctx: Context<Ctor>) -> Result<Self> {
             Ok(Self { v: vec![] })
         }
 
-        pub fn remaining_accounts(&mut self, ctx: Context<RemainingAccounts>) -> ProgramResult {
+        pub fn remaining_accounts(&mut self, ctx: Context<RemainingAccounts>) -> Result<()> {
             if ctx.remaining_accounts.len() != 1 {
-                return Err(ProgramError::Custom(1)); // Arbitrary error.
+                return Err(ProgramError::Custom(1).into()); // Arbitrary error.
             }
             Ok(())
         }
     }
 
-    pub fn initialize(ctx: Context<Initialize>, udata: u128, idata: i128) -> ProgramResult {
+    pub fn initialize(ctx: Context<Initialize>, udata: u128, idata: i128) -> Result<()> {
         ctx.accounts.data.udata = udata;
         ctx.accounts.data.idata = idata;
         Ok(())
     }
 
-    pub fn initialize_no_rent_exempt(ctx: Context<InitializeNoRentExempt>) -> ProgramResult {
+    pub fn initialize_no_rent_exempt(ctx: Context<InitializeNoRentExempt>) -> Result<()> {
         Ok(())
     }
 
-    pub fn initialize_skip_rent_exempt(ctx: Context<InitializeSkipRentExempt>) -> ProgramResult {
+    pub fn initialize_skip_rent_exempt(ctx: Context<InitializeSkipRentExempt>) -> Result<()> {
         Ok(())
     }
 
-    pub fn test_owner(_ctx: Context<TestOwner>) -> ProgramResult {
+    pub fn test_owner(_ctx: Context<TestOwner>) -> Result<()> {
         Ok(())
     }
 
-    pub fn test_executable(_ctx: Context<TestExecutable>) -> ProgramResult {
+    pub fn test_executable(_ctx: Context<TestExecutable>) -> Result<()> {
         Ok(())
     }
 
-    pub fn test_state_cpi(ctx: Context<TestStateCpi>, data: u64) -> ProgramResult {
+    pub fn test_state_cpi(ctx: Context<TestStateCpi>, data: u64) -> Result<()> {
         let cpi_program = ctx.accounts.misc2_program.clone();
         let cpi_accounts = Auth {
             authority: ctx.accounts.authority.clone(),
@@ -73,41 +73,41 @@ pub mod misc {
         misc2::cpi::state::set_data(ctx, data)
     }
 
-    pub fn test_u16(ctx: Context<TestU16>, data: u16) -> ProgramResult {
+    pub fn test_u16(ctx: Context<TestU16>, data: u16) -> Result<()> {
         ctx.accounts.my_account.data = data;
         Ok(())
     }
 
-    pub fn test_simulate(_ctx: Context<TestSimulate>, data: u32) -> ProgramResult {
+    pub fn test_simulate(_ctx: Context<TestSimulate>, data: u32) -> Result<()> {
         emit!(E1 { data });
         emit!(E2 { data: 1234 });
         emit!(E3 { data: 9 });
         Ok(())
     }
 
-    pub fn test_i8(ctx: Context<TestI8>, data: i8) -> ProgramResult {
+    pub fn test_i8(ctx: Context<TestI8>, data: i8) -> Result<()> {
         ctx.accounts.data.data = data;
         Ok(())
     }
 
-    pub fn test_i16(ctx: Context<TestI16>, data: i16) -> ProgramResult {
+    pub fn test_i16(ctx: Context<TestI16>, data: i16) -> Result<()> {
         ctx.accounts.data.data = data;
         Ok(())
     }
 
-    pub fn test_const_array_size(ctx: Context<TestConstArraySize>, data: u8) -> ProgramResult {
+    pub fn test_const_array_size(ctx: Context<TestConstArraySize>, data: u8) -> Result<()> {
         ctx.accounts.data.data[0] = data;
         Ok(())
     }
 
-    pub fn test_close(_ctx: Context<TestClose>) -> ProgramResult {
+    pub fn test_close(_ctx: Context<TestClose>) -> Result<()> {
         Ok(())
     }
 
     pub fn test_instruction_constraint(
         _ctx: Context<TestInstructionConstraint>,
         _nonce: u8,
-    ) -> ProgramResult {
+    ) -> Result<()> {
         Ok(())
     }
 
@@ -116,25 +116,25 @@ pub mod misc {
         _domain: String,
         _seed: Vec<u8>,
         _bump: u8,
-    ) -> ProgramResult {
+    ) -> Result<()> {
         ctx.accounts.my_pda.data = 6;
         Ok(())
     }
 
-    pub fn test_pda_init_zero_copy(ctx: Context<TestPdaInitZeroCopy>) -> ProgramResult {
+    pub fn test_pda_init_zero_copy(ctx: Context<TestPdaInitZeroCopy>) -> Result<()> {
         let mut acc = ctx.accounts.my_pda.load_init()?;
         acc.data = 9;
         acc.bump = *ctx.bumps.get("my_pda").unwrap();
         Ok(())
     }
 
-    pub fn test_pda_mut_zero_copy(ctx: Context<TestPdaMutZeroCopy>) -> ProgramResult {
+    pub fn test_pda_mut_zero_copy(ctx: Context<TestPdaMutZeroCopy>) -> Result<()> {
         let mut acc = ctx.accounts.my_pda.load_mut()?;
         acc.data = 1234;
         Ok(())
     }
 
-    pub fn test_token_seeds_init(_ctx: Context<TestTokenSeedsInit>) -> ProgramResult {
+    pub fn test_token_seeds_init(_ctx: Context<TestTokenSeedsInit>) -> Result<()> {
         Ok(())
     }
 
@@ -142,120 +142,120 @@ pub mod misc {
         _program_id: &Pubkey,
         _accounts: &[AccountInfo<'info>],
         _data: &[u8],
-    ) -> ProgramResult {
-        Err(ProgramError::Custom(1234))
+    ) -> Result<()> {
+        Err(ProgramError::Custom(1234).into())
     }
 
-    pub fn test_init(ctx: Context<TestInit>) -> ProgramResult {
+    pub fn test_init(ctx: Context<TestInit>) -> Result<()> {
         ctx.accounts.data.data = 3;
         Ok(())
     }
 
-    pub fn test_init_zero_copy(ctx: Context<TestInitZeroCopy>) -> ProgramResult {
+    pub fn test_init_zero_copy(ctx: Context<TestInitZeroCopy>) -> Result<()> {
         let mut data = ctx.accounts.data.load_init()?;
         data.data = 10;
         data.bump = 2;
         Ok(())
     }
 
-    pub fn test_init_mint(ctx: Context<TestInitMint>) -> ProgramResult {
+    pub fn test_init_mint(ctx: Context<TestInitMint>) -> Result<()> {
         assert!(ctx.accounts.mint.decimals == 6);
         Ok(())
     }
 
-    pub fn test_init_token(ctx: Context<TestInitToken>) -> ProgramResult {
+    pub fn test_init_token(ctx: Context<TestInitToken>) -> Result<()> {
         assert!(ctx.accounts.token.mint == ctx.accounts.mint.key());
         Ok(())
     }
 
-    pub fn test_composite_payer(ctx: Context<TestCompositePayer>) -> ProgramResult {
+    pub fn test_composite_payer(ctx: Context<TestCompositePayer>) -> Result<()> {
         ctx.accounts.composite.data.data = 1;
         ctx.accounts.data.udata = 2;
         ctx.accounts.data.idata = 3;
         Ok(())
     }
 
-    pub fn test_init_associated_token(ctx: Context<TestInitAssociatedToken>) -> ProgramResult {
+    pub fn test_init_associated_token(ctx: Context<TestInitAssociatedToken>) -> Result<()> {
         assert!(ctx.accounts.token.mint == ctx.accounts.mint.key());
         Ok(())
     }
 
     pub fn test_validate_associated_token(
         _ctx: Context<TestValidateAssociatedToken>,
-    ) -> ProgramResult {
+    ) -> Result<()> {
         Ok(())
     }
 
-    pub fn test_fetch_all(ctx: Context<TestFetchAll>, filterable: Pubkey) -> ProgramResult {
+    pub fn test_fetch_all(ctx: Context<TestFetchAll>, filterable: Pubkey) -> Result<()> {
         ctx.accounts.data.authority = ctx.accounts.authority.key();
         ctx.accounts.data.filterable = filterable;
         Ok(())
     }
 
-    pub fn test_init_with_empty_seeds(ctx: Context<TestInitWithEmptySeeds>) -> ProgramResult {
+    pub fn test_init_with_empty_seeds(ctx: Context<TestInitWithEmptySeeds>) -> Result<()> {
         Ok(())
     }
 
-    pub fn test_empty_seeds_constraint(ctx: Context<TestEmptySeedsConstraint>) -> ProgramResult {
+    pub fn test_empty_seeds_constraint(ctx: Context<TestEmptySeedsConstraint>) -> Result<()> {
         Ok(())
     }
 
-    pub fn test_init_if_needed(ctx: Context<TestInitIfNeeded>, data: u16) -> ProgramResult {
+    pub fn test_init_if_needed(ctx: Context<TestInitIfNeeded>, data: u16) -> Result<()> {
         ctx.accounts.data.data = data;
         Ok(())
     }
 
     pub fn test_init_if_needed_checks_owner(
         ctx: Context<TestInitIfNeededChecksOwner>,
-    ) -> ProgramResult {
+    ) -> Result<()> {
         Ok(())
     }
 
     pub fn test_init_if_needed_checks_seeds(
         ctx: Context<TestInitIfNeededChecksSeeds>,
         seed_data: String,
-    ) -> ProgramResult {
+    ) -> Result<()> {
         Ok(())
     }
 
     pub fn test_init_mint_if_needed(
         ctx: Context<TestInitMintIfNeeded>,
         decimals: u8,
-    ) -> ProgramResult {
+    ) -> Result<()> {
         Ok(())
     }
 
-    pub fn test_init_token_if_needed(ctx: Context<TestInitTokenIfNeeded>) -> ProgramResult {
+    pub fn test_init_token_if_needed(ctx: Context<TestInitTokenIfNeeded>) -> Result<()> {
         Ok(())
     }
 
     pub fn test_init_associated_token_if_needed(
         ctx: Context<TestInitAssociatedTokenIfNeeded>,
-    ) -> ProgramResult {
+    ) -> Result<()> {
         Ok(())
     }
 
-    pub fn init_with_space(ctx: Context<InitWithSpace>, data: u16) -> ProgramResult {
+    pub fn init_with_space(ctx: Context<InitWithSpace>, data: u16) -> Result<()> {
         Ok(())
     }
 
     pub fn test_multidimensional_array(
         ctx: Context<TestMultidimensionalArray>,
         data: [[u8; 10]; 10],
-    ) -> ProgramResult {
+    ) -> Result<()> {
         ctx.accounts.data.data = data;
         Ok(())
     }
 
-    pub fn test_no_rent_exempt(ctx: Context<NoRentExempt>) -> ProgramResult {
+    pub fn test_no_rent_exempt(ctx: Context<NoRentExempt>) -> Result<()> {
         Ok(())
     }
 
-    pub fn test_enforce_rent_exempt(ctx: Context<EnforceRentExempt>) -> ProgramResult {
+    pub fn test_enforce_rent_exempt(ctx: Context<EnforceRentExempt>) -> Result<()> {
         Ok(())
     }
 
-    pub fn init_decrease_lamports(ctx: Context<InitDecreaseLamports>) -> ProgramResult {
+    pub fn init_decrease_lamports(ctx: Context<InitDecreaseLamports>) -> Result<()> {
         **ctx.accounts.data.try_borrow_mut_lamports()? -= 1;
         **ctx.accounts.user.try_borrow_mut_lamports()? += 1;
         Ok(())
@@ -263,7 +263,7 @@ pub mod misc {
 
     pub fn init_if_needed_checks_rent_exemption(
         _ctx: Context<InitIfNeededChecksRentExemption>,
-    ) -> ProgramResult {
+    ) -> Result<()> {
         Ok(())
     }
 
@@ -271,13 +271,13 @@ pub mod misc {
         _ctx: Context<TestProgramIdConstraint>,
         _bump: u8,
         _second_bump: u8,
-    ) -> ProgramResult {
+    ) -> Result<()> {
         Ok(())
     }
 
     pub fn test_program_id_constraint_find_pda(
         _ctx: Context<TestProgramIdConstraintUsingFindPda>,
-    ) -> ProgramResult {
+    ) -> Result<()> {
         Ok(())
     }
 }

+ 3 - 3
tests/misc/programs/misc2/src/lib.rs

@@ -13,16 +13,16 @@ pub mod misc2 {
     }
 
     impl MyState {
-        pub fn new(ctx: Context<Auth>) -> Result<Self, ProgramError> {
+        pub fn new(ctx: Context<Auth>) -> Result<Self> {
             Ok(Self {
                 data: 0,
                 auth: *ctx.accounts.authority.key,
             })
         }
 
-        pub fn set_data(&mut self, ctx: Context<Auth>, data: u64) -> Result<(), ProgramError> {
+        pub fn set_data(&mut self, ctx: Context<Auth>, data: u64) -> Result<()> {
             if self.auth != *ctx.accounts.authority.key {
-                return Err(ProgramError::Custom(1234)); // Arbitrary error code.
+                return Err(ProgramError::Custom(1234).into()); // Arbitrary error code.
             }
             self.data = data;
             Ok(())

+ 7 - 7
tests/multisig/programs/multisig/src/lib.rs

@@ -17,8 +17,8 @@
 //! the `execute_transaction`, once enough (i.e. `threshold`) of the owners have
 //! signed.
 
-use anchor_lang::prelude::*;
 use anchor_lang::accounts::program_account::ProgramAccount;
+use anchor_lang::prelude::*;
 use anchor_lang::solana_program;
 use anchor_lang::solana_program::instruction::Instruction;
 use std::convert::Into;
@@ -57,7 +57,7 @@ pub mod multisig {
             .owners
             .iter()
             .position(|a| a == ctx.accounts.proposer.key)
-            .ok_or(ErrorCode::InvalidOwner)?;
+            .ok_or(error!(ErrorCode::InvalidOwner))?;
 
         let mut signers = Vec::new();
         signers.resize(ctx.accounts.multisig.owners.len(), false);
@@ -82,7 +82,7 @@ pub mod multisig {
             .owners
             .iter()
             .position(|a| a == ctx.accounts.owner.key)
-            .ok_or(ErrorCode::InvalidOwner)?;
+            .ok_or(error!(ErrorCode::InvalidOwner))?;
 
         ctx.accounts.transaction.signers[owner_index] = true;
 
@@ -107,7 +107,7 @@ pub mod multisig {
     // change_threshold.
     pub fn change_threshold(ctx: Context<Auth>, threshold: u64) -> Result<()> {
         if threshold > ctx.accounts.multisig.owners.len() as u64 {
-            return Err(ErrorCode::InvalidThreshold.into());
+            return err!(ErrorCode::InvalidThreshold);
         }
         let multisig = &mut ctx.accounts.multisig;
         multisig.threshold = threshold;
@@ -118,7 +118,7 @@ pub mod multisig {
     pub fn execute_transaction(ctx: Context<ExecuteTransaction>) -> Result<()> {
         // Has this been executed already?
         if ctx.accounts.transaction.did_execute {
-            return Err(ErrorCode::AlreadyExecuted.into());
+            return err!(ErrorCode::AlreadyExecuted);
         }
 
         // Do we have enough signers?
@@ -134,7 +134,7 @@ pub mod multisig {
             .collect::<Vec<_>>()
             .len() as u64;
         if sig_count < ctx.accounts.multisig.threshold {
-            return Err(ErrorCode::NotEnoughSigners.into());
+            return err!(ErrorCode::NotEnoughSigners);
         }
 
         // Execute the transaction signed by the multisig.
@@ -262,7 +262,7 @@ impl From<TransactionAccount> for AccountMeta {
     }
 }
 
-#[error]
+#[error_code]
 pub enum ErrorCode {
     #[msg("The given owner is not part of this multisig.")]
     InvalidOwner,

+ 2 - 2
tests/pda-derivation/programs/pda-derivation/src/lib.rs

@@ -15,14 +15,14 @@ pub const MY_SEED_U64: u64 = 3;
 pub mod pda_derivation {
     use super::*;
 
-    pub fn init_base(ctx: Context<InitBase>, data: u64, data_key: Pubkey) -> ProgramResult {
+    pub fn init_base(ctx: Context<InitBase>, data: u64, data_key: Pubkey) -> Result<()> {
         let base = &mut ctx.accounts.base;
         base.base_data = data;
         base.base_data_key = data_key;
         Ok(())
     }
 
-    pub fn init_my_account(ctx: Context<InitMyAccount>, seed_a: u8) -> ProgramResult {
+    pub fn init_my_account(ctx: Context<InitMyAccount>, seed_a: u8) -> Result<()> {
         Ok(())
     }
 }

+ 2 - 2
tests/pyth/programs/pyth/src/lib.rs

@@ -8,7 +8,7 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
 pub mod pyth {
     use super::*;
 
-    pub fn initialize(ctx: Context<Initialize>, price: i64, expo: i32, conf: u64) -> ProgramResult {
+    pub fn initialize(ctx: Context<Initialize>, price: i64, expo: i32, conf: u64) -> Result<()> {
         let oracle = &ctx.accounts.price;
 
         let mut price_oracle = Price::load(&oracle).unwrap();
@@ -20,7 +20,7 @@ pub mod pyth {
         Ok(())
     }
 
-    pub fn set_price(ctx: Context<SetPrice>, price: i64) -> ProgramResult {
+    pub fn set_price(ctx: Context<SetPrice>, price: i64) -> Result<()> {
         let oracle = &ctx.accounts.price;
         let mut price_oracle = Price::load(&oracle).unwrap();
         price_oracle.agg.price = price as i64;

+ 1 - 1
tests/pyth/programs/pyth/src/pc.rs

@@ -98,7 +98,7 @@ pub struct Price {
 
 impl Price {
     #[inline]
-    pub fn load<'a>(price_feed: &'a AccountInfo) -> Result<RefMut<'a, Price>, ProgramError> {
+    pub fn load<'a>(price_feed: &'a AccountInfo) -> Result<RefMut<'a, Price>> {
         let account_data: RefMut<'a, [u8]>;
         let state: RefMut<'a, Self>;
 

+ 4 - 4
tests/spl/token-proxy/programs/token-proxy/src/lib.rs

@@ -9,15 +9,15 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
 mod token_proxy {
     use super::*;
 
-    pub fn proxy_transfer(ctx: Context<ProxyTransfer>, amount: u64) -> ProgramResult {
+    pub fn proxy_transfer(ctx: Context<ProxyTransfer>, amount: u64) -> Result<()> {
         token::transfer(ctx.accounts.into(), amount)
     }
 
-    pub fn proxy_mint_to(ctx: Context<ProxyMintTo>, amount: u64) -> ProgramResult {
+    pub fn proxy_mint_to(ctx: Context<ProxyMintTo>, amount: u64) -> Result<()> {
         token::mint_to(ctx.accounts.into(), amount)
     }
 
-    pub fn proxy_burn(ctx: Context<ProxyBurn>, amount: u64) -> ProgramResult {
+    pub fn proxy_burn(ctx: Context<ProxyBurn>, amount: u64) -> Result<()> {
         token::burn(ctx.accounts.into(), amount)
     }
 
@@ -25,7 +25,7 @@ mod token_proxy {
         ctx: Context<ProxySetAuthority>,
         authority_type: AuthorityType,
         new_authority: Option<Pubkey>,
-    ) -> ProgramResult {
+    ) -> Result<()> {
         token::set_authority(ctx.accounts.into(), authority_type.into(), new_authority)
     }
 }

+ 9 - 8
tests/swap/programs/swap/src/lib.rs

@@ -179,7 +179,7 @@ pub mod swap {
 fn apply_risk_checks(event: DidSwap) -> Result<()> {
     // Reject if the resulting amount is less than the client's expectation.
     if event.to_amount < event.min_expected_swap_amount {
-        return Err(ErrorCode::SlippageExceeded.into());
+        return err!(ErrorCode::SlippageExceeded);
     }
     emit!(event);
     Ok(())
@@ -274,11 +274,12 @@ impl<'info> OrderbookClient<'info> {
     //
     // `base_amount` is the "native" amount of the base currency, i.e., token
     // amount including decimals.
-    fn sell(&self, base_amount: u64, referral: Option<AccountInfo<'info>>) -> ProgramResult {
+    fn sell(&self, base_amount: u64, referral: Option<AccountInfo<'info>>) -> Result<()> {
         let limit_price = 1;
         let max_coin_qty = {
             // The loaded market must be dropped before CPI.
-            let market = MarketState::load(&self.market.market, &dex::ID)?;
+            let market = MarketState::load(&self.market.market, &dex::ID)
+                .map_err(|de| ProgramError::from(de))?;
             coin_lots(&market, base_amount)
         };
         let max_native_pc_qty = u64::MAX;
@@ -296,7 +297,7 @@ impl<'info> OrderbookClient<'info> {
     //
     // `quote_amount` is the "native" amount of the quote currency, i.e., token
     // amount including decimals.
-    fn buy(&self, quote_amount: u64, referral: Option<AccountInfo<'info>>) -> ProgramResult {
+    fn buy(&self, quote_amount: u64, referral: Option<AccountInfo<'info>>) -> Result<()> {
         let limit_price = u64::MAX;
         let max_coin_qty = u64::MAX;
         let max_native_pc_qty = quote_amount;
@@ -324,7 +325,7 @@ impl<'info> OrderbookClient<'info> {
         max_native_pc_qty: u64,
         side: Side,
         referral: Option<AccountInfo<'info>>,
-    ) -> ProgramResult {
+    ) -> Result<()> {
         // Client order id is only used for cancels. Not used here so hardcode.
         let client_order_id = 0;
         // Limit is the dex's custom compute budge parameter, setting an upper
@@ -363,7 +364,7 @@ impl<'info> OrderbookClient<'info> {
         )
     }
 
-    fn settle(&self, referral: Option<AccountInfo<'info>>) -> ProgramResult {
+    fn settle(&self, referral: Option<AccountInfo<'info>>) -> Result<()> {
         let settle_accs = dex::SettleFunds {
             market: self.market.market.clone(),
             open_orders: self.market.open_orders.clone(),
@@ -455,7 +456,7 @@ fn _is_valid_swap<'info>(from: &AccountInfo<'info>, to: &AccountInfo<'info>) ->
     let from_token_mint = token::accessor::mint(from)?;
     let to_token_mint = token::accessor::mint(to)?;
     if from_token_mint == to_token_mint {
-        return Err(ErrorCode::SwapTokensCannotMatch.into());
+        return err!(ErrorCode::SwapTokensCannotMatch);
     }
     Ok(())
 }
@@ -486,7 +487,7 @@ pub struct DidSwap {
     pub authority: Pubkey,
 }
 
-#[error]
+#[error_code]
 pub enum ErrorCode {
     #[msg("The tokens being swapped must have different mints")]
     SwapTokensCannotMatch,

+ 1 - 1
tests/system-accounts/programs/system-accounts/src/lib.rs

@@ -6,7 +6,7 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
 mod system_accounts {
     use super::*;
 
-    pub fn initialize(_ctx: Context<Initialize>) -> ProgramResult {
+    pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
         Ok(())
     }
 }

+ 1 - 1
tests/sysvars/programs/sysvars/src/lib.rs

@@ -5,7 +5,7 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
 #[program]
 mod sysvars {
     use super::*;
-    pub fn sysvars(_ctx: Context<Sysvars>) -> ProgramResult {
+    pub fn sysvars(_ctx: Context<Sysvars>) -> Result<()> {
         Ok(())
     }
 }

+ 5 - 5
tests/tictactoe/programs/tictactoe/src/lib.rs

@@ -18,14 +18,14 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
 pub mod tictactoe {
     use super::*;
 
-    pub fn initialize_dashboard(ctx: Context<Initializedashboard>) -> ProgramResult {
+    pub fn initialize_dashboard(ctx: Context<Initializedashboard>) -> Result<()> {
         let dashboard = &mut ctx.accounts.dashboard;
         dashboard.game_count = 0;
         dashboard.address = *dashboard.to_account_info().key;
         Ok(())
     }
 
-    pub fn initialize(ctx: Context<Initialize>) -> ProgramResult {
+    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
         let dashboard = &mut ctx.accounts.dashboard;
         let game = &mut ctx.accounts.game;
         dashboard.game_count = dashboard.game_count + 1;
@@ -34,7 +34,7 @@ pub mod tictactoe {
         Ok(())
     }
 
-    pub fn player_join(ctx: Context<Playerjoin>) -> ProgramResult {
+    pub fn player_join(ctx: Context<Playerjoin>) -> Result<()> {
         let game = &mut ctx.accounts.game;
         game.player_o = *ctx.accounts.player_o.key;
         game.game_state = 1;
@@ -42,14 +42,14 @@ pub mod tictactoe {
     }
 
     #[access_control(Playermove::accounts(&ctx, x_or_o, player_move))]
-    pub fn player_move(ctx: Context<Playermove>, x_or_o: u8, player_move: u8) -> ProgramResult {
+    pub fn player_move(ctx: Context<Playermove>, x_or_o: u8, player_move: u8) -> Result<()> {
         let game = &mut ctx.accounts.game;
         game.board[player_move as usize] = x_or_o;
         game.status(x_or_o);
         Ok(())
     }
 
-    pub fn status(ctx: Context<Status>) -> ProgramResult {
+    pub fn status(ctx: Context<Status>) -> Result<()> {
         Ok(())
     }
 }

+ 1 - 1
tests/typescript/programs/typescript/src/lib.rs

@@ -8,7 +8,7 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
 #[program]
 pub mod typescript {
     use super::*;
-    pub fn initialize(ctx: Context<Initialize>) -> ProgramResult {
+    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
         Ok(())
     }
 }

+ 7 - 7
tests/zero-copy/programs/zero-copy/src/lib.rs

@@ -13,38 +13,38 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
 pub mod zero_copy {
     use super::*;
 
-    pub fn create_foo(ctx: Context<CreateFoo>) -> ProgramResult {
+    pub fn create_foo(ctx: Context<CreateFoo>) -> Result<()> {
         let foo = &mut ctx.accounts.foo.load_init()?;
         foo.authority = *ctx.accounts.authority.key;
         foo.set_second_authority(ctx.accounts.authority.key);
         Ok(())
     }
 
-    pub fn update_foo(ctx: Context<UpdateFoo>, data: u64) -> ProgramResult {
+    pub fn update_foo(ctx: Context<UpdateFoo>, data: u64) -> Result<()> {
         let mut foo = ctx.accounts.foo.load_mut()?;
         foo.data = data;
         Ok(())
     }
 
-    pub fn update_foo_second(ctx: Context<UpdateFooSecond>, second_data: u64) -> ProgramResult {
+    pub fn update_foo_second(ctx: Context<UpdateFooSecond>, second_data: u64) -> Result<()> {
         let mut foo = ctx.accounts.foo.load_mut()?;
         foo.second_data = second_data;
         Ok(())
     }
 
-    pub fn create_bar(ctx: Context<CreateBar>) -> ProgramResult {
+    pub fn create_bar(ctx: Context<CreateBar>) -> Result<()> {
         let bar = &mut ctx.accounts.bar.load_init()?;
         bar.authority = *ctx.accounts.authority.key;
         Ok(())
     }
 
-    pub fn update_bar(ctx: Context<UpdateBar>, data: u64) -> ProgramResult {
+    pub fn update_bar(ctx: Context<UpdateBar>, data: u64) -> Result<()> {
         let bar = &mut ctx.accounts.bar.load_mut()?;
         bar.data = data;
         Ok(())
     }
 
-    pub fn create_large_account(_ctx: Context<CreateLargeAccount>) -> ProgramResult {
+    pub fn create_large_account(_ctx: Context<CreateLargeAccount>) -> Result<()> {
         Ok(())
     }
 
@@ -52,7 +52,7 @@ pub mod zero_copy {
         ctx: Context<UpdateLargeAccount>,
         idx: u32,
         data: u64,
-    ) -> ProgramResult {
+    ) -> Result<()> {
         let event_q = &mut ctx.accounts.event_q.load_mut()?;
         event_q.events[idx as usize] = Event {
             data,

+ 1 - 1
tests/zero-copy/programs/zero-cpi/src/lib.rs

@@ -8,7 +8,7 @@ declare_id!("ErjUjtqKE5AGWUsjseSJCVLtddM6rhaMbDqmhzraF9h6");
 #[program]
 pub mod zero_cpi {
     use super::*;
-    pub fn check_cpi(ctx: Context<CheckCpi>, data: u64) -> ProgramResult {
+    pub fn check_cpi(ctx: Context<CheckCpi>, data: u64) -> Result<()> {
         let cpi_program = ctx.accounts.zero_copy_program.to_account_info();
         let cpi_accounts = UpdateBar {
             authority: ctx.accounts.authority.clone(),

+ 16 - 4
ts/src/error.ts

@@ -15,16 +15,28 @@ export class ProgramError extends Error {
     err: any,
     idlErrors: Map<number, string>
   ): ProgramError | null {
+    const errString: string = err.toString();
     // TODO: don't rely on the error string. web3.js should preserve the error
     //       code information instead of giving us an untyped string.
-    let components = err.toString().split("custom program error: ");
-    if (components.length !== 2) {
-      return null;
+    let unparsedErrorCode: string;
+    if (errString.includes("custom program error:")) {
+      let components = errString.split("custom program error: ");
+      if (components.length !== 2) {
+        return null;
+      } else {
+        unparsedErrorCode = components[1];
+      }
+    } else {
+      const matches = errString.match(/"Custom":([0-9]+)}/g);
+      if (!matches || matches.length > 1) {
+        return null;
+      }
+      unparsedErrorCode = matches[0].match(/([0-9]+)/g)![0];
     }
 
     let errorCode: number;
     try {
-      errorCode = parseInt(components[1]);
+      errorCode = parseInt(unparsedErrorCode);
     } catch (parseErr) {
       return null;
     }