diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index 51f2e8f3..1c50f500 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/lang/syn/src/codegen/accounts/constraints.rs @@ -134,10 +134,19 @@ fn generate_constraint_composite(f: &CompositeField, c: &Constraint) -> proc_mac fn generate_constraint_address(f: &Field, c: &ConstraintAddress) -> proc_macro2::TokenStream { let field = &f.ident; let addr = &c.address; - let error = generate_custom_error(field, &c.error, quote! { ConstraintAddress }); + let error = generate_custom_error( + field, + &c.error, + quote! { ConstraintAddress }, + &Some(&(quote! { actual }, quote! { expected })), + ); quote! { - if #field.key() != #addr { - return #error; + { + let actual = #field.key(); + let expected = #addr; + if actual != expected { + return #error; + } } } } @@ -178,7 +187,7 @@ pub fn generate_constraint_close(f: &Field, c: &ConstraintClose) -> proc_macro2: pub fn generate_constraint_mut(f: &Field, c: &ConstraintMut) -> proc_macro2::TokenStream { let ident = &f.ident; - let error = generate_custom_error(ident, &c.error, quote! { ConstraintMut }); + let error = generate_custom_error(ident, &c.error, quote! { ConstraintMut }, &None); quote! { if !#ident.to_account_info().is_writable { return #error; @@ -194,10 +203,19 @@ pub fn generate_constraint_has_one(f: &Field, c: &ConstraintHasOne) -> proc_macr Ty::AccountLoader(_) => quote! {#ident.load()?}, _ => quote! {#ident}, }; - let error = generate_custom_error(ident, &c.error, quote! { ConstraintHasOne }); + let error = generate_custom_error( + ident, + &c.error, + quote! { ConstraintHasOne }, + &Some(&(quote! { my_key }, quote! { target_key })), + ); quote! { - if #field.#target != #target.key() { - return #error; + { + let my_key = #field.#target; + let target_key = #target.key(); + if my_key != target_key { + return #error; + } } } } @@ -213,7 +231,7 @@ 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(ident, &c.error, quote! { ConstraintSigner }); + let error = generate_custom_error(ident, &c.error, quote! { ConstraintSigner }, &None); quote! { if !#info.is_signer { return #error; @@ -245,7 +263,7 @@ pub fn generate_constraint_literal( pub fn generate_constraint_raw(ident: &Ident, c: &ConstraintRaw) -> proc_macro2::TokenStream { let raw = &c.raw; - let error = generate_custom_error(ident, &c.error, quote! { ConstraintRaw }); + let error = generate_custom_error(ident, &c.error, quote! { ConstraintRaw }, &None); quote! { if !(#raw) { return #error; @@ -256,10 +274,19 @@ pub fn generate_constraint_raw(ident: &Ident, c: &ConstraintRaw) -> proc_macro2: 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(ident, &c.error, quote! { ConstraintOwner }); + let error = generate_custom_error( + ident, + &c.error, + quote! { ConstraintOwner }, + &Some(&(quote! { *my_owner }, quote! { owner_address })), + ); quote! { - if #ident.as_ref().owner != &#owner_address { - return #error; + { + let my_owner = #ident.as_ref().owner; + let owner_address = #owner_address; + if my_owner != &owner_address { + return #error; + } } } } @@ -374,10 +401,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::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenMint).with_account_name(#name_str)); + return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenMint).with_account_name(#name_str).with_pubkeys((pa.mint, #mint.key()))); } if pa.owner != #owner.key() { - return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenOwner).with_account_name(#name_str)); + return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenOwner).with_account_name(#name_str).with_pubkeys((pa.owner, #owner.key()))); } } pa @@ -409,10 +436,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::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenMint).with_account_name(#name_str)); + return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenMint).with_account_name(#name_str).with_pubkeys((pa.mint, #mint.key()))); } if pa.owner != #owner.key() { - return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenOwner).with_account_name(#name_str)); + return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenOwner).with_account_name(#name_str).with_pubkeys((pa.owner, #owner.key()))); } if pa.key() != anchor_spl::associated_token::get_associated_token_address(&#owner.key(), &#mint.key()) { @@ -471,7 +498,7 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintMintFreezeAuthority).with_account_name(#name_str)); } if pa.decimals != #decimals { - return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintMintDecimals).with_account_name(#name_str)); + return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintMintDecimals).with_account_name(#name_str).with_values((pa.decimals, #decimals))); } } pa @@ -525,11 +552,11 @@ 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::Error::from(anchor_lang::error::ErrorCode::ConstraintSpace).with_account_name(#name_str)); + return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSpace).with_account_name(#name_str).with_values((space, actual_field.data_len()))); } if actual_owner != #owner { - return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintOwner).with_account_name(#name_str)); + return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintOwner).with_account_name(#name_str).with_pubkeys((*actual_owner, *#owner))); } { @@ -577,10 +604,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::Error::from(anchor_lang::error::ErrorCode::ConstraintSeeds).with_account_name(#name_str)); + return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSeeds).with_account_name(#name_str).with_pubkeys((#name.key(), __pda_address))); } if __bump != #b { - return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSeeds).with_account_name(#name_str)); + return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSeeds).with_account_name(#name_str).with_values((__bump, #b))); } } } @@ -592,7 +619,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::Error::from(anchor_lang::error::ErrorCode::ConstraintSeeds).with_account_name(#name_str)); + return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSeeds).with_account_name(#name_str).with_pubkeys((#name.key(), __pda_address))); } } } @@ -625,7 +652,7 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2 // Check it. if #name.key() != __pda_address { - return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSeeds).with_account_name(#name_str)); + return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintSeeds).with_account_name(#name_str).with_pubkeys((#name.key(), __pda_address))); } } } @@ -640,12 +667,17 @@ fn generate_constraint_associated_token( let wallet_address = &c.wallet; let spl_token_mint_address = &c.mint; quote! { - if #name.owner != #wallet_address.key() { - return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenOwner).with_account_name(#name_str)); - } - let __associated_token_address = anchor_spl::associated_token::get_associated_token_address(&#wallet_address.key(), &#spl_token_mint_address.key()); - if #name.key() != __associated_token_address { - return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintAssociated).with_account_name(#name_str)); + { + let my_owner = #name.owner; + let wallet_address = #wallet_address.key(); + if my_owner != wallet_address { + return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenOwner).with_account_name(#name_str).with_pubkeys((my_owner, wallet_address))); + } + let __associated_token_address = anchor_spl::associated_token::get_associated_token_address(&wallet_address, &#spl_token_mint_address.key()); + let my_key = #name.key(); + if my_key != __associated_token_address { + return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintAssociated).with_account_name(#name_str).with_pubkeys((my_key, __associated_token_address))); + } } } } @@ -743,14 +775,26 @@ fn generate_custom_error( account_name: &Ident, custom_error: &Option, error: proc_macro2::TokenStream, + compared_values: &Option<&(proc_macro2::TokenStream, proc_macro2::TokenStream)>, ) -> proc_macro2::TokenStream { let account_name = account_name.to_string(); - match custom_error { + let mut error = match custom_error { Some(error) => { - quote! { Err(anchor_lang::error::Error::from(#error).with_account_name(#account_name)) } + quote! { anchor_lang::error::Error::from(#error).with_account_name(#account_name) } } None => { - quote! { Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::#error).with_account_name(#account_name)) } + quote! { anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::#error).with_account_name(#account_name) } } + }; + + let compared_values = match compared_values { + Some((left, right)) => quote! { .with_pubkeys((#left, #right)) }, + None => quote! {}, + }; + + error.extend(compared_values); + + quote! { + Err(#error) } } diff --git a/tests/errors/tests/errors.js b/tests/errors/tests/errors.js index cbb28a3a..0fab2277 100644 --- a/tests/errors/tests/errors.js +++ b/tests/errors/tests/errors.js @@ -2,6 +2,7 @@ const assert = require("assert"); const anchor = require("@project-serum/anchor"); const { Account, Transaction, TransactionInstruction } = anchor.web3; const { TOKEN_PROGRAM_ID, Token } = require("@solana/spl-token"); +const { Keypair } = require("@solana/web3.js"); // sleep to allow logs to come in const sleep = (ms) => @@ -171,26 +172,34 @@ describe("errors", () => { }); it("Emits a has one error", async () => { - try { - const account = new Account(); - const tx = await program.rpc.hasOneError({ - accounts: { - myAccount: account.publicKey, - owner: anchor.web3.SYSVAR_RENT_PUBKEY, - rent: anchor.web3.SYSVAR_RENT_PUBKEY, - }, - instructions: [ - await program.account.hasOneAccount.createInstruction(account), - ], - signers: [account], - }); - assert.ok(false); - } catch (err) { - const errMsg = "A has_one constraint was violated"; - assert.equal(err.toString(), errMsg); - assert.equal(err.msg, errMsg); - assert.equal(err.code, 2001); - } + await withLogTest(async () => { + try { + const account = new Keypair(); + const tx = await program.rpc.hasOneError({ + accounts: { + myAccount: account.publicKey, + owner: anchor.web3.SYSVAR_RENT_PUBKEY, + }, + // this initializes the account.owner variable with Pubkey::default + instructions: [ + await program.account.hasOneAccount.createInstruction(account), + ], + signers: [account], + }); + assert.ok(false); + } catch (err) { + const errMsg = "A has_one constraint was violated"; + assert.equal(err.toString(), errMsg); + assert.equal(err.msg, errMsg); + assert.equal(err.code, 2001); + } + }, [ + "Program log: AnchorError caused by account: my_account. Error Code: ConstraintHasOne. Error Number: 2001. Error Message: A has one constraint was violated.", + "Program log: Left:", + "Program log: 11111111111111111111111111111111", + "Program log: Right:", + "Program log: SysvarRent111111111111111111111111111111111", + ]); }); // This test uses a raw transaction and provider instead of a program