From bfa1a0716ac720c96c172fca53fad2b069c2cf3f Mon Sep 17 00:00:00 2001 From: Paul Schaaf Date: Fri, 3 Dec 2021 18:04:42 +0100 Subject: [PATCH 01/19] lang: init_if_needed should run checks on constraints even if no init is needed --- lang/src/error.rs | 23 ++++- lang/src/lib.rs | 6 +- lang/syn/src/codegen/accounts/constraints.rs | 98 +++++++++++++++----- lang/syn/src/idl/file.rs | 2 +- lang/syn/src/parser/accounts/constraints.rs | 2 +- 5 files changed, 96 insertions(+), 35 deletions(-) diff --git a/lang/src/error.rs b/lang/src/error.rs index 70a7ca977e..a153b0fad5 100644 --- a/lang/src/error.rs +++ b/lang/src/error.rs @@ -15,13 +15,13 @@ pub enum ErrorCode { // IDL instructions. #[msg("The program was compiled without idl instructions")] - IdlInstructionStub = 120, + IdlInstructionStub = 1000, #[msg("Invalid program given to the IDL instruction")] IdlInstructionInvalidProgram, // Constraints. #[msg("A mut constraint was violated")] - ConstraintMut = 140, + ConstraintMut = 2000, #[msg("A has one constraint was violated")] ConstraintHasOne, #[msg("A signer constraint as violated")] @@ -48,10 +48,23 @@ pub enum ErrorCode { ConstraintAddress, #[msg("Expected zero account discriminant")] ConstraintZero, + #[msg("A token mint constraint was violated")] + ConstraintTokenMint, + #[msg("A token authority constraint was violated")] + ConstraintTokenAuthority, + // the mint mint is intentional -> a mint authority for the mint + #[msg("A mint mint authority constraint was violated")] + ConstraintMintMintAuthority, + #[msg("A mint freeze authority constraint was violated")] + ConstraintMintFreezeAuthority, + #[msg("A mint decimals constraint was violated")] + ConstraintMintDecimals, + #[msg("A space constraint was violated")] + ConstraintSpace, // Accounts. #[msg("The account discriminator was already set on this account")] - AccountDiscriminatorAlreadySet = 160, + AccountDiscriminatorAlreadySet = 3000, #[msg("No 8 byte discriminator was found on the account")] AccountDiscriminatorNotFound, #[msg("8 byte discriminator did not match what was expected")] @@ -81,9 +94,9 @@ pub enum ErrorCode { // State. #[msg("The given state account does not have the correct address")] - StateInvalidAddress = 180, + StateInvalidAddress = 4000, // Used for APIs that shouldn't be used anymore. #[msg("The API being used is deprecated and should no longer be used")] - Deprecated = 299, + Deprecated = 5000, } diff --git a/lang/src/lib.rs b/lang/src/lib.rs index b570c22b70..cbfc246446 100644 --- a/lang/src/lib.rs +++ b/lang/src/lib.rs @@ -183,8 +183,8 @@ pub trait AccountDeserialize: Sized { /// uninitialized accounts, where the bytes are zeroed. Implementations /// should be unique to a particular account type so that one can never /// successfully deserialize the data of one account type into another. - /// For example, if the SPL token program where to implement this trait, - /// it should impossible to deserialize a `Mint` account into a token + /// 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; @@ -303,7 +303,7 @@ pub mod __private { } // The starting point for user defined error codes. - pub const ERROR_CODE_OFFSET: u32 = 300; + pub const ERROR_CODE_OFFSET: u32 = 6000; // Calculates the size of an account, which may be larger than the deserialized // data in it. This trait is currently only used for `#[state]` accounts. diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index a02ef0c7e4..13ba2b3d4c 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/lang/syn/src/codegen/accounts/constraints.rs @@ -285,7 +285,7 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma }; let seeds_with_nonce = match &c.seeds { - None => quote! {}, + None => (quote! {}, quote! {}), Some(c) => { let s = &mut c.seeds.clone(); // If the seeds came with a trailing comma, we need to chop it off @@ -296,32 +296,31 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma let maybe_seeds_plus_comma = (!s.is_empty()).then(|| { quote! { #s, } }); - let inner = match c.bump.as_ref() { + match c.bump.as_ref() { // Bump target not given. Use the canonical bump. - None => { - quote! { - [ - #maybe_seeds_plus_comma - &[ - Pubkey::find_program_address( - &[#s], - program_id, - ).1 - ][..] - ] - } - } + None => ( + quote! {#maybe_seeds_plus_comma}, + quote! {&[ + Pubkey::find_program_address( + &[#s], + program_id, + ).1 + ][..]}, + ), // Bump target given. Use it. - Some(b) => quote! { - [#maybe_seeds_plus_comma &[#b][..]] - }, - }; - quote! { - &#inner[..] + Some(b) => (quote! {#maybe_seeds_plus_comma}, quote! {&[#b][..]}), } } }; - generate_init(f, c.if_needed, seeds_with_nonce, payer, &c.space, &c.kind) + generate_init( + f, + c.if_needed, + seeds_with_nonce.0, + seeds_with_nonce.1, + payer, + &c.space, + &c.kind, + ) } fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2::TokenStream { @@ -406,7 +405,8 @@ fn generate_constraint_associated_token( pub fn generate_init( f: &Field, if_needed: bool, - seeds_with_nonce: proc_macro2::TokenStream, + seeds: proc_macro2::TokenStream, + nonce: proc_macro2::TokenStream, payer: proc_macro2::TokenStream, space: &Option, kind: &InitKind, @@ -414,6 +414,7 @@ pub fn generate_init( let field = &f.ident; let ty_decl = f.ty_decl(); let from_account_info = f.from_account_info_unchecked(Some(kind)); + let seeds_with_nonce = quote! { &[#seeds #nonce][..] }; let if_needed = if if_needed { quote! {true} } else { @@ -449,6 +450,14 @@ pub fn generate_init( } let pa: #ty_decl = #from_account_info; + if !(!#if_needed || #field.to_account_info().owner == &anchor_lang::solana_program::system_program::ID) { + if pa.mint != #mint.key() { + return Err(anchor_lang::__private::ErrorCode::ConstraintTokenMint.into()); + } + if pa.authority != #owner.key() { + return Err(anchor_lang::__private::ErrorCode::ConstraintTokenAuthority.into()); + } + } pa }; } @@ -473,6 +482,14 @@ pub fn generate_init( anchor_spl::associated_token::create(cpi_ctx)?; } let pa: #ty_decl = #from_account_info; + if !(!#if_needed || #field.to_account_info().owner == &anchor_lang::solana_program::system_program::ID) { + if pa.mint != #mint.key() { + return Err(anchor_lang::__private::ErrorCode::ConstraintTokenMint.into()); + } + if pa.authority != #owner.key() { + return Err(anchor_lang::__private::ErrorCode::ConstraintTokenAuthority.into()); + } + } pa }; } @@ -511,6 +528,17 @@ pub fn generate_init( anchor_spl::token::initialize_mint(cpi_ctx, #decimals, &#owner.to_account_info().key, #freeze_authority)?; } let pa: #ty_decl = #from_account_info; + if !(!#if_needed || #field.to_account_info().owner == &anchor_lang::solana_program::system_program::ID) { + if pa.mint_authority != #owner.key() { + return Err(anchor_lang::__private::ErrorCode::ConstraintMintMintAuthority.into()); + } + if pa.freeze_authority != #freeze_authority.key() { + return Err(anchor_lang::__private::ErrorCode::ConstraintMintFreezeAuthority.into()); + } + if pa.decimals != #decimals { + return Err(anchor_lang::__private::ErrorCode::ConstraintMintDecimals.into()); + } + } pa }; } @@ -550,16 +578,36 @@ pub fn generate_init( &#o }, }; + let is_pda = !seeds.is_empty(); let create_account = - generate_create_account(field, quote! {space}, owner, seeds_with_nonce); + generate_create_account(field, quote! {space}, owner.clone(), seeds_with_nonce); quote! { let #field = { - if !#if_needed || #field.to_account_info().owner == &anchor_lang::solana_program::system_program::ID { + let actual_owner = #field.to_account_info().owner; + if !#if_needed || actual_owner == &anchor_lang::solana_program::system_program::ID { #space #payer #create_account } let pa: #ty_decl = #from_account_info; + if !(!#if_needed || actual_owner == &anchor_lang::solana_program::system_program::ID) { + if #space != #field.as_ref().data_len() { + return Err(anchor_lang::__private::ErrorCode::ConstraintSpace.into()); + } + if actual_owner != #owner { + return Err(anchor_lang::__private::ErrorCode::ConstraintOwner.into()); + } + + if #is_pda { + let (expected_key, expected_nonce) = anchor_lang::solana_program::pubkey::Pubkey::create_program_address( + &#seeds[..], + #owner, + ).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?; + if expected_key != #field.key() { + return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into()); + } + } + } pa }; } diff --git a/lang/syn/src/idl/file.rs b/lang/syn/src/idl/file.rs index 689d133889..ca90525a80 100644 --- a/lang/syn/src/idl/file.rs +++ b/lang/syn/src/idl/file.rs @@ -11,7 +11,7 @@ use std::path::Path; const DERIVE_NAME: &str = "Accounts"; // TODO: sharee this with `anchor_lang` crate. -const ERROR_CODE_OFFSET: u32 = 300; +const ERROR_CODE_OFFSET: u32 = 6000; // Parse an entire interface file. pub fn parse(filename: impl AsRef, version: String) -> Result> { diff --git a/lang/syn/src/parser/accounts/constraints.rs b/lang/syn/src/parser/accounts/constraints.rs index 119814d5bf..3915f003dc 100644 --- a/lang/syn/src/parser/accounts/constraints.rs +++ b/lang/syn/src/parser/accounts/constraints.rs @@ -432,7 +432,7 @@ impl<'ty> ConstraintGroupBuilder<'ty> { if self.token_mint.is_none() { return Err(ParseError::new( token_authority.span(), - "token authority must be provided if token mint is", + "token mint must be provided if token authority is", )); } } From a6a24bf624bf75738eb32efa45489ef6dd82ddc9 Mon Sep 17 00:00:00 2001 From: Paul Schaaf Date: Fri, 3 Dec 2021 18:44:51 +0100 Subject: [PATCH 02/19] lang: fix --- lang/syn/src/codegen/accounts/constraints.rs | 61 ++++++++++++-------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index 13ba2b3d4c..1d61f8f125 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/lang/syn/src/codegen/accounts/constraints.rs @@ -285,7 +285,7 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma }; let seeds_with_nonce = match &c.seeds { - None => (quote! {}, quote! {}), + None => quote! {}, Some(c) => { let s = &mut c.seeds.clone(); // If the seeds came with a trailing comma, we need to chop it off @@ -296,27 +296,35 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma let maybe_seeds_plus_comma = (!s.is_empty()).then(|| { quote! { #s, } }); - match c.bump.as_ref() { + let inner = match c.bump.as_ref() { // Bump target not given. Use the canonical bump. - None => ( - quote! {#maybe_seeds_plus_comma}, - quote! {&[ - Pubkey::find_program_address( - &[#s], - program_id, - ).1 - ][..]}, - ), + None => { + quote! { + [ + #maybe_seeds_plus_comma + &[ + Pubkey::find_program_address( + &[#s], + program_id, + ).1 + ][..] + ] + } + } // Bump target given. Use it. - Some(b) => (quote! {#maybe_seeds_plus_comma}, quote! {&[#b][..]}), + Some(b) => quote! { + [#maybe_seeds_plus_comma &[#b][..]] + }, + }; + quote! { + &#inner[..] } } }; generate_init( f, c.if_needed, - seeds_with_nonce.0, - seeds_with_nonce.1, + seeds_with_nonce, payer, &c.space, &c.kind, @@ -405,8 +413,7 @@ fn generate_constraint_associated_token( pub fn generate_init( f: &Field, if_needed: bool, - seeds: proc_macro2::TokenStream, - nonce: proc_macro2::TokenStream, + seeds_with_nonce: proc_macro2::TokenStream, payer: proc_macro2::TokenStream, space: &Option, kind: &InitKind, @@ -414,7 +421,6 @@ pub fn generate_init( let field = &f.ident; let ty_decl = f.ty_decl(); let from_account_info = f.from_account_info_unchecked(Some(kind)); - let seeds_with_nonce = quote! { &[#seeds #nonce][..] }; let if_needed = if if_needed { quote! {true} } else { @@ -578,20 +584,25 @@ pub fn generate_init( &#o }, }; - let is_pda = !seeds.is_empty(); + let is_pda = if !seeds_with_nonce.is_empty() { + quote!{true} + } else { + quote!{false} + }; let create_account = - generate_create_account(field, quote! {space}, owner.clone(), seeds_with_nonce); + generate_create_account(field, quote! {space}, owner.clone(), seeds_with_nonce.clone()); quote! { let #field = { - let actual_owner = #field.to_account_info().owner; + let actual_field = #field.to_account_info(); + let actual_owner = actual_field.owner; + #space if !#if_needed || actual_owner == &anchor_lang::solana_program::system_program::ID { - #space #payer #create_account } let pa: #ty_decl = #from_account_info; if !(!#if_needed || actual_owner == &anchor_lang::solana_program::system_program::ID) { - if #space != #field.as_ref().data_len() { + if space != actual_field.data_len() { return Err(anchor_lang::__private::ErrorCode::ConstraintSpace.into()); } if actual_owner != #owner { @@ -599,9 +610,9 @@ pub fn generate_init( } if #is_pda { - let (expected_key, expected_nonce) = anchor_lang::solana_program::pubkey::Pubkey::create_program_address( - &#seeds[..], - #owner, + let expected_key = anchor_lang::prelude::Pubkey::create_program_address( + &[#seeds_with_nonce], + #owner ).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?; if expected_key != #field.key() { return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into()); From b0dbc32ad86a6447f691a58a460bc3ad93f566c8 Mon Sep 17 00:00:00 2001 From: Paul Schaaf Date: Fri, 3 Dec 2021 18:49:18 +0100 Subject: [PATCH 03/19] lang: fmt --- lang/syn/src/codegen/accounts/constraints.rs | 21 +++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index 1d61f8f125..a52d390e02 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/lang/syn/src/codegen/accounts/constraints.rs @@ -321,14 +321,7 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma } } }; - generate_init( - f, - c.if_needed, - seeds_with_nonce, - payer, - &c.space, - &c.kind, - ) + generate_init(f, c.if_needed, seeds_with_nonce, payer, &c.space, &c.kind) } fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2::TokenStream { @@ -585,12 +578,16 @@ pub fn generate_init( }, }; let is_pda = if !seeds_with_nonce.is_empty() { - quote!{true} + quote! {true} } else { - quote!{false} + quote! {false} }; - let create_account = - generate_create_account(field, quote! {space}, owner.clone(), seeds_with_nonce.clone()); + let create_account = generate_create_account( + field, + quote! {space}, + owner.clone(), + seeds_with_nonce.clone(), + ); quote! { let #field = { let actual_field = #field.to_account_info(); From 16fdb89a909f72de0050ffb21b78083a6a3053bc Mon Sep 17 00:00:00 2001 From: Paul Schaaf Date: Fri, 3 Dec 2021 19:29:41 +0100 Subject: [PATCH 04/19] tests: fix --- lang/src/error.rs | 4 +- lang/syn/src/codegen/accounts/constraints.rs | 51 +++++++++----------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/lang/src/error.rs b/lang/src/error.rs index a153b0fad5..bfe792d7d3 100644 --- a/lang/src/error.rs +++ b/lang/src/error.rs @@ -50,8 +50,8 @@ pub enum ErrorCode { ConstraintZero, #[msg("A token mint constraint was violated")] ConstraintTokenMint, - #[msg("A token authority constraint was violated")] - ConstraintTokenAuthority, + #[msg("A token owner constraint was violated")] + ConstraintTokenOwner, // the mint mint is intentional -> a mint authority for the mint #[msg("A mint mint authority constraint was violated")] ConstraintMintMintAuthority, diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index a52d390e02..5a48774428 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/lang/syn/src/codegen/accounts/constraints.rs @@ -453,8 +453,8 @@ pub fn generate_init( if pa.mint != #mint.key() { return Err(anchor_lang::__private::ErrorCode::ConstraintTokenMint.into()); } - if pa.authority != #owner.key() { - return Err(anchor_lang::__private::ErrorCode::ConstraintTokenAuthority.into()); + if pa.owner != #owner.key() { + return Err(anchor_lang::__private::ErrorCode::ConstraintTokenOwner.into()); } } pa @@ -485,8 +485,8 @@ pub fn generate_init( if pa.mint != #mint.key() { return Err(anchor_lang::__private::ErrorCode::ConstraintTokenMint.into()); } - if pa.authority != #owner.key() { - return Err(anchor_lang::__private::ErrorCode::ConstraintTokenAuthority.into()); + if pa.owner != #owner.key() { + return Err(anchor_lang::__private::ErrorCode::ConstraintTokenOwner.into()); } } pa @@ -505,8 +505,8 @@ pub fn generate_init( seeds_with_nonce, ); let freeze_authority = match freeze_authority { - Some(fa) => quote! { Some(&#fa.key()) }, - None => quote! { None }, + Some(fa) => quote! { Option::<&anchor_lang::prelude::Pubkey>::Some(&#fa.key()) }, + None => quote! { Option::<&anchor_lang::prelude::Pubkey>::None }, }; quote! { let #field: #ty_decl = { @@ -524,14 +524,17 @@ pub fn generate_init( rent: rent.to_account_info(), }; let cpi_ctx = CpiContext::new(cpi_program, accounts); - anchor_spl::token::initialize_mint(cpi_ctx, #decimals, &#owner.to_account_info().key, #freeze_authority)?; + anchor_spl::token::initialize_mint(cpi_ctx, #decimals, &#owner.key(), #freeze_authority)?; } let pa: #ty_decl = #from_account_info; if !(!#if_needed || #field.to_account_info().owner == &anchor_lang::solana_program::system_program::ID) { - if pa.mint_authority != #owner.key() { + if pa.mint_authority != anchor_lang::solana_program::program_option::COption::Some(#owner.key()) { return Err(anchor_lang::__private::ErrorCode::ConstraintMintMintAuthority.into()); } - if pa.freeze_authority != #freeze_authority.key() { + if pa.freeze_authority + .as_ref() + .map(|fa| #freeze_authority.as_ref().map(|expected_fa| fa == *expected_fa).unwrap_or(false)) + .unwrap_or(#freeze_authority.is_none()) { return Err(anchor_lang::__private::ErrorCode::ConstraintMintFreezeAuthority.into()); } if pa.decimals != #decimals { @@ -577,17 +580,19 @@ pub fn generate_init( &#o }, }; - let is_pda = if !seeds_with_nonce.is_empty() { - quote! {true} + let pda_check = if !seeds_with_nonce.is_empty() { + quote! {let expected_key = anchor_lang::prelude::Pubkey::create_program_address( + #seeds_with_nonce, + #owner + ).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?; + if expected_key != #field.key() { + return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into()); + }} } else { - quote! {false} + quote! {} }; - let create_account = generate_create_account( - field, - quote! {space}, - owner.clone(), - seeds_with_nonce.clone(), - ); + let create_account = + generate_create_account(field, quote! {space}, owner.clone(), seeds_with_nonce); quote! { let #field = { let actual_field = #field.to_account_info(); @@ -606,15 +611,7 @@ pub fn generate_init( return Err(anchor_lang::__private::ErrorCode::ConstraintOwner.into()); } - if #is_pda { - let expected_key = anchor_lang::prelude::Pubkey::create_program_address( - &[#seeds_with_nonce], - #owner - ).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?; - if expected_key != #field.key() { - return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into()); - } - } + #pda_check } pa }; From 9cfcee74c959769d0465d3b331d59d9d94444aae Mon Sep 17 00:00:00 2001 From: Paul Schaaf Date: Fri, 3 Dec 2021 19:49:25 +0100 Subject: [PATCH 05/19] ts: adjust errors --- tests/errors/tests/errors.js | 14 ++++---- ts/src/error.ts | 63 ++++++++++++++++++------------------ 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/tests/errors/tests/errors.js b/tests/errors/tests/errors.js index 8880578d91..c79bb1c643 100644 --- a/tests/errors/tests/errors.js +++ b/tests/errors/tests/errors.js @@ -17,7 +17,7 @@ describe("errors", () => { "This is an error message clients will automatically display"; assert.equal(err.toString(), errMsg); assert.equal(err.msg, errMsg); - assert.equal(err.code, 300); + assert.equal(err.code, 6000); } }); @@ -29,7 +29,7 @@ describe("errors", () => { const errMsg = "HelloNoMsg"; assert.equal(err.toString(), errMsg); assert.equal(err.msg, errMsg); - assert.equal(err.code, 300 + 123); + assert.equal(err.code, 6000 + 123); } }); @@ -41,7 +41,7 @@ describe("errors", () => { const errMsg = "HelloNext"; assert.equal(err.toString(), errMsg); assert.equal(err.msg, errMsg); - assert.equal(err.code, 300 + 124); + assert.equal(err.code, 6000 + 124); } }); @@ -57,7 +57,7 @@ describe("errors", () => { const errMsg = "A mut constraint was violated"; assert.equal(err.toString(), errMsg); assert.equal(err.msg, errMsg); - assert.equal(err.code, 140); + assert.equal(err.code, 2000); } }); @@ -80,7 +80,7 @@ describe("errors", () => { const errMsg = "A has_one constraint was violated"; assert.equal(err.toString(), errMsg); assert.equal(err.msg, errMsg); - assert.equal(err.code, 141); + assert.equal(err.code, 2001); } }); @@ -108,7 +108,7 @@ describe("errors", () => { assert.ok(false); } catch (err) { const errMsg = - "Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x8e"; + "Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x7d2"; assert.equal(err.toString(), errMsg); } }); @@ -125,7 +125,7 @@ describe("errors", () => { const errMsg = "HelloCustom"; assert.equal(err.toString(), errMsg); assert.equal(err.msg, errMsg); - assert.equal(err.code, 300 + 125); + assert.equal(err.code, 6000 + 125); } }); diff --git a/ts/src/error.ts b/ts/src/error.ts index d31fa5adca..d513db7e43 100644 --- a/ts/src/error.ts +++ b/ts/src/error.ts @@ -58,45 +58,44 @@ const LangErrorCode = { InstructionDidNotSerialize: 103, // IDL instructions. - IdlInstructionStub: 120, - IdlInstructionInvalidProgram: 121, + IdlInstructionStub: 1000, + IdlInstructionInvalidProgram: 1001, // Constraints. - ConstraintMut: 140, - ConstraintHasOne: 141, - ConstraintSigner: 142, - ConstraintRaw: 143, - ConstraintOwner: 144, - ConstraintRentExempt: 145, - ConstraintSeeds: 146, - ConstraintExecutable: 147, - ConstraintState: 148, - ConstraintAssociated: 149, - ConstraintAssociatedInit: 150, - ConstraintClose: 151, - ConstraintAddress: 152, + ConstraintMut: 2000, + ConstraintHasOne: 2001, + ConstraintSigner: 2002, + ConstraintRaw: 2003, + ConstraintOwner: 2004, + ConstraintRentExempt: 2005, + ConstraintSeeds: 2006, + ConstraintExecutable: 2007, + ConstraintState: 2008, + ConstraintAssociated: 2009, + ConstraintAssociatedInit: 2010, + ConstraintClose: 2011, + ConstraintAddress: 2012, // Accounts. - AccountDiscriminatorAlreadySet: 160, - AccountDiscriminatorNotFound: 161, - AccountDiscriminatorMismatch: 162, - AccountDidNotDeserialize: 163, - AccountDidNotSerialize: 164, - AccountNotEnoughKeys: 165, - AccountNotMutable: 166, - AccountNotProgramOwned: 167, - InvalidProgramId: 168, - InvalidProgramExecutable: 169, - AccountNotSigner: 170, - AccountNotSystemOwned: 171, - AccountNotInitialized: 172, - AccountNotProgramData: 173, - + AccountDiscriminatorAlreadySet: 3000, + AccountDiscriminatorNotFound: 3001, + AccountDiscriminatorMismatch: 3002, + AccountDidNotDeserialize: 3003, + AccountDidNotSerialize: 3004, + AccountNotEnoughKeys: 3005, + AccountNotMutable: 3006, + AccountNotProgramOwned: 3007, + InvalidProgramId: 3008, + InvalidProgramExecutable: 3009, + AccountNotSigner: 3010, + AccountNotSystemOwned: 3011, + AccountNotInitialized: 3012, + AccountNotProgramData: 3013, // State. - StateInvalidAddress: 180, + StateInvalidAddress: 4000, // Used for APIs that shouldn't be used anymore. - Deprecated: 299, + Deprecated: 5000, }; const LangErrorMessage = new Map([ From 23fb7bb087df209f9ea377e76fe5dda3af6bc21f Mon Sep 17 00:00:00 2001 From: Paul Schaaf Date: Fri, 3 Dec 2021 20:07:26 +0100 Subject: [PATCH 06/19] tests: fix misc --- tests/misc/tests/misc.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/misc/tests/misc.js b/tests/misc/tests/misc.js index 81509d62a8..23b95b096c 100644 --- a/tests/misc/tests/misc.js +++ b/tests/misc/tests/misc.js @@ -214,7 +214,7 @@ describe("misc", () => { const errMsg = "A close constraint was violated"; assert.equal(err.toString(), errMsg); assert.equal(err.msg, errMsg); - assert.equal(err.code, 151); + assert.equal(err.code, 2011); } }); @@ -667,7 +667,7 @@ describe("misc", () => { }); }, (err) => { - assert.equal(err.code, 149); + assert.equal(err.code, 2009); return true; } ); @@ -802,7 +802,7 @@ describe("misc", () => { }, }), (err) => { - assert.equal(err.code, 146); + assert.equal(err.code, 2006); return true; } ); From 41cea80ed0e4f35cf53826c823a0a896da9ff176 Mon Sep 17 00:00:00 2001 From: Paul Schaaf Date: Fri, 3 Dec 2021 20:25:03 +0100 Subject: [PATCH 07/19] tests: fix --- tests/lockup/tests/lockup.js | 6 +++--- tests/system-accounts/tests/system-accounts.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/lockup/tests/lockup.js b/tests/lockup/tests/lockup.js index 5415820b92..f18854218f 100644 --- a/tests/lockup/tests/lockup.js +++ b/tests/lockup/tests/lockup.js @@ -118,7 +118,7 @@ describe("Lockup and Registry", () => { await lockup.state.rpc.whitelistAdd(e, { accounts }); }, (err) => { - assert.equal(err.code, 308); + assert.equal(err.code, 6008); assert.equal(err.msg, "Whitelist is full"); return true; } @@ -218,7 +218,7 @@ describe("Lockup and Registry", () => { }); }, (err) => { - assert.equal(err.code, 307); + assert.equal(err.code, 6007); assert.equal(err.msg, "Insufficient withdrawal balance."); return true; } @@ -865,7 +865,7 @@ describe("Lockup and Registry", () => { await tryEndUnstake(); }, (err) => { - assert.equal(err.code, 309); + assert.equal(err.code, 6009); assert.equal(err.msg, "The unstake timelock has not yet expired."); return true; } diff --git a/tests/system-accounts/tests/system-accounts.js b/tests/system-accounts/tests/system-accounts.js index 97a2cfa6c9..9930ab70c2 100644 --- a/tests/system-accounts/tests/system-accounts.js +++ b/tests/system-accounts/tests/system-accounts.js @@ -54,7 +54,7 @@ describe('system_accounts', () => { const errMsg = 'The given account is not owned by the system program'; assert.equal(err.toString(), errMsg); assert.equal(err.msg, errMsg); - assert.equal(err.code, 171); + assert.equal(err.code, 3011); } }); }); From 2877532bc8536ceb499ac66d55a10ca686efe102 Mon Sep 17 00:00:00 2001 From: Paul Schaaf Date: Fri, 3 Dec 2021 20:45:48 +0100 Subject: [PATCH 08/19] tests: fix --- tests/lockup/tests/lockup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lockup/tests/lockup.js b/tests/lockup/tests/lockup.js index f18854218f..b825af3c98 100644 --- a/tests/lockup/tests/lockup.js +++ b/tests/lockup/tests/lockup.js @@ -783,7 +783,7 @@ describe("Lockup and Registry", () => { (err) => { // Solana doesn't propagate errors across CPI. So we receive the registry's error code, // not the lockup's. - const errorCode = "custom program error: 0x140"; + const errorCode = "custom program error: 0x1784"; assert.ok(err.toString().split(errorCode).length === 2); return true; } From a896ab515a1e9093278be85b818425e8f155411a Mon Sep 17 00:00:00 2001 From: Paul Schaaf Date: Fri, 3 Dec 2021 22:56:52 +0100 Subject: [PATCH 09/19] lang: fix freeze bug, tests: add init_if_needed tests, lang: only allow init on accounts or loaders --- lang/syn/src/codegen/accounts/constraints.rs | 8 +- lang/syn/src/parser/accounts/constraints.rs | 10 + tests/misc/programs/misc/src/context.rs | 55 ++- tests/misc/programs/misc/src/lib.rs | 17 + tests/misc/tests/misc.js | 338 +++++++++++++++++++ ts/src/error.ts | 16 + 6 files changed, 441 insertions(+), 3 deletions(-) diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index 5a48774428..9a0fcd0bbb 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/lang/syn/src/codegen/accounts/constraints.rs @@ -533,8 +533,8 @@ pub fn generate_init( } if pa.freeze_authority .as_ref() - .map(|fa| #freeze_authority.as_ref().map(|expected_fa| fa == *expected_fa).unwrap_or(false)) - .unwrap_or(#freeze_authority.is_none()) { + .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::__private::ErrorCode::ConstraintMintFreezeAuthority.into()); } if pa.decimals != #decimals { @@ -607,6 +607,10 @@ pub fn generate_init( if space != actual_field.data_len() { return Err(anchor_lang::__private::ErrorCode::ConstraintSpace.into()); } + + // this check is for safety only and should never + // be true as long as init is only valid on Accounts + // (because they check the owner themselves) if actual_owner != #owner { return Err(anchor_lang::__private::ErrorCode::ConstraintOwner.into()); } diff --git a/lang/syn/src/parser/accounts/constraints.rs b/lang/syn/src/parser/accounts/constraints.rs index 3915f003dc..343fa6eb0d 100644 --- a/lang/syn/src/parser/accounts/constraints.rs +++ b/lang/syn/src/parser/accounts/constraints.rs @@ -344,6 +344,16 @@ impl<'ty> ConstraintGroupBuilder<'ty> { pub fn build(mut self) -> ParseResult { // Init. if let Some(i) = &self.init { + match self.f_ty.unwrap() { + Ty::Account(_) | Ty::ProgramAccount(_) | Ty::Loader(_) | Ty::AccountLoader(_) => (), + _ => { + return Err(ParseError::new( + i.span(), + "init is only allowed on (Program)Account and (Account)Loader", + )); + } + }; + match self.mutable { Some(m) => { return Err(ParseError::new( diff --git a/tests/misc/programs/misc/src/context.rs b/tests/misc/programs/misc/src/context.rs index 86c8421bcf..17fbbc4c3f 100644 --- a/tests/misc/programs/misc/src/context.rs +++ b/tests/misc/programs/misc/src/context.rs @@ -245,14 +245,67 @@ pub struct TestEmptySeedsConstraint<'info> { pub pda: AccountInfo<'info>, } +#[derive(Accounts)] +pub struct InitWithSpace<'info> { + #[account(init, payer = payer)] + pub data: Account<'info, DataU16>, + pub payer: Signer<'info>, + pub system_program: Program<'info, System>, +} + #[derive(Accounts)] pub struct TestInitIfNeeded<'info> { - #[account(init_if_needed, payer = payer)] + #[account(init_if_needed, payer = payer, space = 500)] pub data: Account<'info, DataU16>, pub payer: Signer<'info>, pub system_program: Program<'info, System>, } +#[derive(Accounts)] +#[instruction(decimals: u8)] +pub struct TestInitMintIfNeeded<'info> { + #[account(init_if_needed, mint::decimals = decimals, mint::authority = mint_authority, mint::freeze_authority = freeze_authority, payer = payer)] + pub mint: Account<'info, Mint>, + #[account(signer)] + pub payer: AccountInfo<'info>, + pub rent: Sysvar<'info, Rent>, + pub system_program: AccountInfo<'info>, + pub token_program: AccountInfo<'info>, + pub mint_authority: AccountInfo<'info>, + pub freeze_authority: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct TestInitTokenIfNeeded<'info> { + #[account(init_if_needed, token::mint = mint, token::authority = authority, payer = payer)] + pub token: Account<'info, TokenAccount>, + pub mint: Account<'info, Mint>, + #[account(signer)] + pub payer: AccountInfo<'info>, + pub rent: Sysvar<'info, Rent>, + pub system_program: AccountInfo<'info>, + pub token_program: AccountInfo<'info>, + pub authority: AccountInfo<'info>, +} + +#[derive(Accounts)] +pub struct TestInitAssociatedTokenIfNeeded<'info> { + #[account( + init_if_needed, + payer = payer, + associated_token::mint = mint, + associated_token::authority = authority, + )] + pub token: Account<'info, TokenAccount>, + pub mint: Account<'info, Mint>, + pub payer: Signer<'info>, + pub rent: Sysvar<'info, Rent>, + pub system_program: Program<'info, System>, + pub token_program: Program<'info, Token>, + pub associated_token_program: Program<'info, AssociatedToken>, + pub authority: AccountInfo<'info> +} + #[derive(Accounts)] pub struct TestMultidimensionalArray<'info> { #[account(zero)] diff --git a/tests/misc/programs/misc/src/lib.rs b/tests/misc/programs/misc/src/lib.rs index 90d9d198dc..7ffbe34767 100644 --- a/tests/misc/programs/misc/src/lib.rs +++ b/tests/misc/programs/misc/src/lib.rs @@ -196,6 +196,23 @@ pub mod misc { Ok(()) } + pub fn test_init_mint_if_needed(ctx: Context, decimals: u8) -> ProgramResult { + Ok(()) + } + + pub fn test_init_token_if_needed(ctx: Context) -> ProgramResult { + Ok(()) + } + + pub fn test_init_associated_token_if_needed(ctx: Context) -> ProgramResult { + Ok(()) + } + + pub fn init_with_space(ctx: Context, data: u16) -> ProgramResult { + Ok(()) + } + + pub fn test_multidimensional_array( ctx: Context, data: [[u8; 10]; 10], diff --git a/tests/misc/tests/misc.js b/tests/misc/tests/misc.js index 23b95b096c..f5770b00fb 100644 --- a/tests/misc/tests/misc.js +++ b/tests/misc/tests/misc.js @@ -859,6 +859,344 @@ describe("misc", () => { ); }); + it("init_if_needed throws if account exists but is not the expected space", async () => { + const newAcc = anchor.web3.Keypair.generate(); + await program.rpc.initWithSpace(3, { + accounts: { + data: newAcc.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + payer: program.provider.wallet.publicKey, + }, + signers: [newAcc], + }); + + try { + await program.rpc.testInitIfNeeded(3, { + accounts: { + data: newAcc.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + payer: program.provider.wallet.publicKey, + }, + signers: [newAcc], + }); + assert.ok(false); + } catch (err) { + assert.equal(err.code, 2019); + } + }); + + it("init_if_needed throws if mint exists but has the wrong mint authority", async () => { + const mint = anchor.web3.Keypair.generate(); + await program.rpc.testInitMint({ + accounts: { + mint: mint.publicKey, + payer: program.provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + }, + signers: [mint], + }); + + try { + await program.rpc.testInitMintIfNeeded(6,{ + accounts: { + mint: mint.publicKey, + payer: program.provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + mintAuthority: anchor.web3.Keypair.generate().publicKey, + freezeAuthority: program.provider.wallet.publicKey + }, + signers: [mint], + }); + assert.ok(false); + } catch (err) { + assert.equal(err.code, 2016); + } + }); + + it("init_if_needed throws if mint exists but has the wrong freeze authority", async () => { + const mint = anchor.web3.Keypair.generate(); + await program.rpc.testInitMint({ + accounts: { + mint: mint.publicKey, + payer: program.provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + }, + signers: [mint], + }); + + try { + await program.rpc.testInitMintIfNeeded(6,{ + accounts: { + mint: mint.publicKey, + payer: program.provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + mintAuthority: program.provider.wallet.publicKey, + freezeAuthority: anchor.web3.Keypair.generate().publicKey + }, + signers: [mint], + }); + assert.ok(false); + } catch (err) { + assert.equal(err.code, 2017); + } + }); + + it("init_if_needed throws if mint exists but has the wrong decimals", async () => { + const mint = anchor.web3.Keypair.generate(); + await program.rpc.testInitMint({ + accounts: { + mint: mint.publicKey, + payer: program.provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + }, + signers: [mint], + }); + + try { + await program.rpc.testInitMintIfNeeded(9,{ + accounts: { + mint: mint.publicKey, + payer: program.provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + mintAuthority: program.provider.wallet.publicKey, + freezeAuthority: program.provider.wallet.publicKey + }, + signers: [mint], + }); + assert.ok(false); + } catch (err) { + assert.equal(err.code, 2018); + } + }); + + it("init_if_needed throws if token exists but has the wrong owner", async () => { + const mint = anchor.web3.Keypair.generate(); + await program.rpc.testInitMint({ + accounts: { + mint: mint.publicKey, + payer: program.provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + }, + signers: [mint], + }); + + const token = anchor.web3.Keypair.generate(); + await program.rpc.testInitToken({ + accounts: { + token: token.publicKey, + mint: mint.publicKey, + payer: program.provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + }, + signers: [token], + }); + + try { + await program.rpc.testInitTokenIfNeeded({ + accounts: { + token: token.publicKey, + mint: mint.publicKey, + payer: program.provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + authority: anchor.web3.Keypair.generate().publicKey, + }, + signers: [token], + }); + assert.ok(false); + } catch (err) { + assert.equal(err.code, 2015); + } + }); + + it("init_if_needed throws if token exists but has the wrong mint", async () => { + const mint = anchor.web3.Keypair.generate(); + await program.rpc.testInitMint({ + accounts: { + mint: mint.publicKey, + payer: program.provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + }, + signers: [mint], + }); + + const mint2 = anchor.web3.Keypair.generate(); + await program.rpc.testInitMint({ + accounts: { + mint: mint2.publicKey, + payer: program.provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + }, + signers: [mint2], + }); + + const token = anchor.web3.Keypair.generate(); + await program.rpc.testInitToken({ + accounts: { + token: token.publicKey, + mint: mint.publicKey, + payer: program.provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + }, + signers: [token], + }); + + try { + await program.rpc.testInitTokenIfNeeded({ + accounts: { + token: token.publicKey, + mint: mint2.publicKey, + payer: program.provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + authority: program.provider.wallet.publicKey, + }, + signers: [token], + }); + assert.ok(false); + } catch (err) { + assert.equal(err.code, 2014); + } + }); + + it("init_if_needed throws if associated token exists but has the wrong owner", async () => { + const mint = anchor.web3.Keypair.generate(); + await program.rpc.testInitMint({ + accounts: { + mint: mint.publicKey, + payer: program.provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + }, + signers: [mint], + }); + + const associatedToken = await Token.getAssociatedTokenAddress( + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + mint.publicKey, + program.provider.wallet.publicKey + ); + + await program.rpc.testInitAssociatedToken({ + accounts: { + token: associatedToken, + mint: mint.publicKey, + payer: program.provider.wallet.publicKey, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + }, + }); + + try { + await program.rpc.testInitAssociatedTokenIfNeeded({ + accounts: { + token: associatedToken, + mint: mint.publicKey, + payer: program.provider.wallet.publicKey, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + authority: anchor.web3.Keypair.generate().publicKey + }, + }); + assert.ok(false); + } catch (err) { + assert.equal(err.code, 2015); + } + }) + + it("init_if_needed throws if associated token exists but has the wrong mint", async () => { + const mint = anchor.web3.Keypair.generate(); + await program.rpc.testInitMint({ + accounts: { + mint: mint.publicKey, + payer: program.provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + }, + signers: [mint], + }); + + const mint2 = anchor.web3.Keypair.generate(); + await program.rpc.testInitMint({ + accounts: { + mint: mint2.publicKey, + payer: program.provider.wallet.publicKey, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + }, + signers: [mint2], + }); + + const associatedToken = await Token.getAssociatedTokenAddress( + ASSOCIATED_TOKEN_PROGRAM_ID, + TOKEN_PROGRAM_ID, + mint.publicKey, + program.provider.wallet.publicKey + ); + + await program.rpc.testInitAssociatedToken({ + accounts: { + token: associatedToken, + mint: mint.publicKey, + payer: program.provider.wallet.publicKey, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + }, + }); + + try { + await program.rpc.testInitAssociatedTokenIfNeeded({ + accounts: { + token: associatedToken, + mint: mint2.publicKey, + payer: program.provider.wallet.publicKey, + rent: anchor.web3.SYSVAR_RENT_PUBKEY, + systemProgram: anchor.web3.SystemProgram.programId, + tokenProgram: TOKEN_PROGRAM_ID, + associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, + authority: program.provider.wallet.publicKey + }, + }); + assert.ok(false); + } catch (err) { + assert.equal(err.code, 2014); + } + }) + it("Can use multidimensional array", async () => { const array2d = new Array(10).fill(new Array(10).fill(99)); const data = anchor.web3.Keypair.generate(); diff --git a/ts/src/error.ts b/ts/src/error.ts index d513db7e43..79ce9e148b 100644 --- a/ts/src/error.ts +++ b/ts/src/error.ts @@ -75,6 +75,14 @@ const LangErrorCode = { ConstraintAssociatedInit: 2010, ConstraintClose: 2011, ConstraintAddress: 2012, + ConstraintZero: 2013, + ConstraintTokenMint: 2014, + ConstraintTokenOwner: 2015, + ConstraintMintMintAuthority: 2016, + ConstraintMintFreezeAuthority: 2017, + ConstraintMintDecimals: 2018, + ConstraintSpace: 2019, + // Accounts. AccountDiscriminatorAlreadySet: 3000, @@ -144,6 +152,14 @@ const LangErrorMessage = new Map([ ], [LangErrorCode.ConstraintClose, "A close constraint was violated"], [LangErrorCode.ConstraintAddress, "An address constraint was violated"], + [LangErrorCode.ConstraintZero, "Expected zero account discriminant"], + [LangErrorCode.ConstraintTokenMint, "A token mint constraint was violated"], + [LangErrorCode.ConstraintTokenOwner, "A token owner constraint was violated"], + [LangErrorCode.ConstraintMintMintAuthority, "A mint mint authority constraint was violated"], + [LangErrorCode.ConstraintMintFreezeAuthority, "A mint freeze authority constraint was violated"], + [LangErrorCode.ConstraintMintDecimals, "A mint decimals constraint was violated"], + [LangErrorCode.ConstraintSpace, "A space constraint was violated"], + // Accounts. [ From bd95ca36920fc12955e90b186b56618c0f5bcbe9 Mon Sep 17 00:00:00 2001 From: Paul Schaaf Date: Fri, 3 Dec 2021 23:01:26 +0100 Subject: [PATCH 10/19] ts:lint --- ts/src/error.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/ts/src/error.ts b/ts/src/error.ts index 79ce9e148b..1ed4eca426 100644 --- a/ts/src/error.ts +++ b/ts/src/error.ts @@ -83,7 +83,6 @@ const LangErrorCode = { ConstraintMintDecimals: 2018, ConstraintSpace: 2019, - // Accounts. AccountDiscriminatorAlreadySet: 3000, AccountDiscriminatorNotFound: 3001, @@ -155,12 +154,20 @@ const LangErrorMessage = new Map([ [LangErrorCode.ConstraintZero, "Expected zero account discriminant"], [LangErrorCode.ConstraintTokenMint, "A token mint constraint was violated"], [LangErrorCode.ConstraintTokenOwner, "A token owner constraint was violated"], - [LangErrorCode.ConstraintMintMintAuthority, "A mint mint authority constraint was violated"], - [LangErrorCode.ConstraintMintFreezeAuthority, "A mint freeze authority constraint was violated"], - [LangErrorCode.ConstraintMintDecimals, "A mint decimals constraint was violated"], + [ + LangErrorCode.ConstraintMintMintAuthority, + "A mint mint authority constraint was violated", + ], + [ + LangErrorCode.ConstraintMintFreezeAuthority, + "A mint freeze authority constraint was violated", + ], + [ + LangErrorCode.ConstraintMintDecimals, + "A mint decimals constraint was violated", + ], [LangErrorCode.ConstraintSpace, "A space constraint was violated"], - // Accounts. [ LangErrorCode.AccountDiscriminatorAlreadySet, From 33881da810abeca20599c46470173d1613c1fc7b Mon Sep 17 00:00:00 2001 From: Paul Schaaf Date: Fri, 3 Dec 2021 23:15:55 +0100 Subject: [PATCH 11/19] lang: allow everyone to use init --- lang/syn/src/codegen/accounts/constraints.rs | 3 --- lang/syn/src/parser/accounts/constraints.rs | 10 ---------- 2 files changed, 13 deletions(-) diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index 9a0fcd0bbb..2424a5812a 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/lang/syn/src/codegen/accounts/constraints.rs @@ -608,9 +608,6 @@ pub fn generate_init( return Err(anchor_lang::__private::ErrorCode::ConstraintSpace.into()); } - // this check is for safety only and should never - // be true as long as init is only valid on Accounts - // (because they check the owner themselves) if actual_owner != #owner { return Err(anchor_lang::__private::ErrorCode::ConstraintOwner.into()); } diff --git a/lang/syn/src/parser/accounts/constraints.rs b/lang/syn/src/parser/accounts/constraints.rs index 343fa6eb0d..3915f003dc 100644 --- a/lang/syn/src/parser/accounts/constraints.rs +++ b/lang/syn/src/parser/accounts/constraints.rs @@ -344,16 +344,6 @@ impl<'ty> ConstraintGroupBuilder<'ty> { pub fn build(mut self) -> ParseResult { // Init. if let Some(i) = &self.init { - match self.f_ty.unwrap() { - Ty::Account(_) | Ty::ProgramAccount(_) | Ty::Loader(_) | Ty::AccountLoader(_) => (), - _ => { - return Err(ParseError::new( - i.span(), - "init is only allowed on (Program)Account and (Account)Loader", - )); - } - }; - match self.mutable { Some(m) => { return Err(ParseError::new( From cad2a5feecf07996aa518b2ae2bf947f2afac744 Mon Sep 17 00:00:00 2001 From: Paul Schaaf Date: Sat, 4 Dec 2021 00:29:11 +0100 Subject: [PATCH 12/19] tests: tests for owner and pda_check --- tests/misc/programs/misc/src/context.rs | 19 ++++++++ tests/misc/programs/misc/src/lib.rs | 8 ++++ tests/misc/tests/misc.js | 60 +++++++++++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/tests/misc/programs/misc/src/context.rs b/tests/misc/programs/misc/src/context.rs index 17fbbc4c3f..6caa1e9ab9 100644 --- a/tests/misc/programs/misc/src/context.rs +++ b/tests/misc/programs/misc/src/context.rs @@ -261,6 +261,25 @@ pub struct TestInitIfNeeded<'info> { pub system_program: Program<'info, System>, } +#[derive(Accounts)] +pub struct TestInitIfNeededChecksOwner<'info> { + #[account(init_if_needed, payer = payer, space = 100, owner = *owner.key, seeds = [b"hello"], bump)] + pub data: UncheckedAccount<'info>, + pub payer: Signer<'info>, + pub system_program: Program<'info, System>, + pub owner: AccountInfo<'info> +} + +#[derive(Accounts)] +#[instruction(seed_data: String)] +pub struct TestInitIfNeededChecksSeeds<'info> { + #[account(init_if_needed, payer = payer, space = 100, seeds = [seed_data.as_bytes()], bump)] + pub data: UncheckedAccount<'info>, + #[account(mut)] + pub payer: Signer<'info>, + pub system_program: Program<'info, System>, +} + #[derive(Accounts)] #[instruction(decimals: u8)] pub struct TestInitMintIfNeeded<'info> { diff --git a/tests/misc/programs/misc/src/lib.rs b/tests/misc/programs/misc/src/lib.rs index 7ffbe34767..6a49ae1f6f 100644 --- a/tests/misc/programs/misc/src/lib.rs +++ b/tests/misc/programs/misc/src/lib.rs @@ -196,6 +196,14 @@ pub mod misc { Ok(()) } + pub fn test_init_if_needed_checks_owner(ctx: Context) -> ProgramResult { + Ok(()) + } + + pub fn test_init_if_needed_checks_seeds(ctx: Context, seed_data: String) -> ProgramResult { + Ok(()) + } + pub fn test_init_mint_if_needed(ctx: Context, decimals: u8) -> ProgramResult { Ok(()) } diff --git a/tests/misc/tests/misc.js b/tests/misc/tests/misc.js index f5770b00fb..992a517cfc 100644 --- a/tests/misc/tests/misc.js +++ b/tests/misc/tests/misc.js @@ -7,6 +7,7 @@ const { Token, } = require("@solana/spl-token"); const miscIdl = require("../target/idl/misc.json"); +const utf8 = anchor.utils.bytes.utf8; describe("misc", () => { // Configure the client to use the local cluster. @@ -859,6 +860,65 @@ describe("misc", () => { ); }); + it("init_if_needed throws if account exists but is not owned by the expected program", async () => { + const newAcc = await anchor.web3.PublicKey.findProgramAddress([utf8.encode("hello")], program.programId); + await program.rpc.testInitIfNeededChecksOwner({ + accounts: { + data: newAcc[0], + systemProgram: anchor.web3.SystemProgram.programId, + payer: program.provider.wallet.publicKey, + owner: program.programId + } + }); + + try { + await program.rpc.testInitIfNeededChecksOwner({ + accounts: { + data: newAcc[0], + systemProgram: anchor.web3.SystemProgram.programId, + payer: program.provider.wallet.publicKey, + owner: anchor.web3.Keypair.generate().publicKey + }, + }); + assert.ok(false); + } catch (err) { + assert.equal(err.code, 2004); + } + }); + + it("init_if_needed throws if pda account exists but does not have the expected seeds", async () => { + const newAcc = await anchor.web3.PublicKey.findProgramAddress([utf8.encode("nothello")], program.programId); + await program.rpc.testInitIfNeededChecksSeeds("nothello", { + accounts: { + data: newAcc[0], + systemProgram: anchor.web3.SystemProgram.programId, + payer: program.provider.wallet.publicKey, + } + }); + + // this will throw if it is not a proper PDA + // we need this so we know that the following tx failed + // not because it couldn't create this pda + // but because the two pdas were different + anchor.web3.PublicKey.createProgramAddress([utf8.encode("hello")], program.programId); + + try { + await program.rpc.testInitIfNeededChecksSeeds("hello", { + accounts: { + data: newAcc[0], + systemProgram: anchor.web3.SystemProgram.programId, + payer: program.provider.wallet.publicKey, + owner: anchor.web3.Keypair.generate().publicKey + }, + }); + assert.ok(false); + } catch (err) { + assert.equal(err.code, 2006); + } + }); + + + it("init_if_needed throws if account exists but is not the expected space", async () => { const newAcc = anchor.web3.Keypair.generate(); await program.rpc.initWithSpace(3, { From ead57585b17cca3c3f6dbfd694ed3fd69a617251 Mon Sep 17 00:00:00 2001 From: Paul Schaaf Date: Sat, 4 Dec 2021 00:42:30 +0100 Subject: [PATCH 13/19] docs: changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8427a2f693..57e9dddd22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ incremented for features. * lang: Add `deprecated` attribute to `ProgramAccount` ([#1014](https://github.com/project-serum/anchor/pull/1014)). * cli: Add version number from programs `Cargo.toml` into extracted IDL * lang: Add `deprecated` attribute to `Loader`([#1078](https://github.com/project-serum/anchor/pull/1078)) +* lang: the `init_if_needed` attribute now checks that given attributes (e.g. space, owner, token::authority etc.) are validated even when init is not needed([#1096](https://github.com/project-serum/anchor/pull/1096)) ### Features From 5c814eff67950c0dfe82fa25e2382b7ef18ff397 Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Sat, 4 Dec 2021 00:14:45 +0000 Subject: [PATCH 14/19] Update lang/src/error.rs --- lang/src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/src/error.rs b/lang/src/error.rs index bfe792d7d3..3f895736fb 100644 --- a/lang/src/error.rs +++ b/lang/src/error.rs @@ -52,7 +52,7 @@ pub enum ErrorCode { ConstraintTokenMint, #[msg("A token owner constraint was violated")] ConstraintTokenOwner, - // the mint mint is intentional -> a mint authority for the mint + // The mint mint is intentional -> a mint authority for the mint. #[msg("A mint mint authority constraint was violated")] ConstraintMintMintAuthority, #[msg("A mint freeze authority constraint was violated")] From 6eb2ae2e2f5281503d1deaa0a20efcd9732f4365 Mon Sep 17 00:00:00 2001 From: Paul Schaaf Date: Mon, 6 Dec 2021 02:42:12 +0100 Subject: [PATCH 15/19] docs: changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57e9dddd22..6dd7363934 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 The minor version will be incremented upon a breaking change and the patch version will be incremented for features. +## [Unreleased Breaking] + +### Fixes + +* lang/ts: error codes have been mapped to new numbers to allow for more errors per namespace ([#1096](https://github.com/project-serum/anchor/pull/1096)) + +### Features + ## [Unreleased] ### Fixes From 14a88aeb9d0ab25df11691e39c799d07b6998290 Mon Sep 17 00:00:00 2001 From: Paul Schaaf Date: Mon, 6 Dec 2021 02:57:15 +0100 Subject: [PATCH 16/19] tests: fix ProgramData tests --- tests/bpf-upgradeable-state/tests/bpf-upgradable-state.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/bpf-upgradeable-state/tests/bpf-upgradable-state.ts b/tests/bpf-upgradeable-state/tests/bpf-upgradable-state.ts index eef41fe03f..c2a1efb483 100644 --- a/tests/bpf-upgradeable-state/tests/bpf-upgradable-state.ts +++ b/tests/bpf-upgradeable-state/tests/bpf-upgradable-state.ts @@ -53,7 +53,7 @@ describe('bpf_upgradeable_state', () => { }); assert.ok(false); } catch (err) { - assert.equal(err.code, 143); + assert.equal(err.code, 2003); assert.equal(err.msg, "A raw constraint was violated"); } }); @@ -73,7 +73,7 @@ describe('bpf_upgradeable_state', () => { }); assert.ok(false); } catch (err) { - assert.equal(err.code, 173); + assert.equal(err.code, 3013); assert.equal(err.msg, "The given account is not a program data account"); } }); @@ -93,7 +93,7 @@ describe('bpf_upgradeable_state', () => { }); assert.ok(false); } catch (err) { - assert.equal(err.code, 167); + assert.equal(err.code, 3007); assert.equal(err.msg, "The given account is not owned by the executing program"); } }); @@ -119,7 +119,7 @@ describe('bpf_upgradeable_state', () => { }); assert.ok(false); } catch (err) { - assert.equal(err.code, 300); + assert.equal(err.code, 6000); } }); }); From 39fa5f841e35c42e6a19ba2c9b75287b44994708 Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Wed, 8 Dec 2021 15:44:43 -0500 Subject: [PATCH 17/19] Fix nits --- CHANGELOG.md | 26 +++++++++----------- lang/syn/src/codegen/accounts/constraints.rs | 16 ++++++------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dd7363934..d9c1520800 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,12 +9,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 The minor version will be incremented upon a breaking change and the patch version will be incremented for features. -## [Unreleased Breaking] - -### Fixes - -* lang/ts: error codes have been mapped to new numbers to allow for more errors per namespace ([#1096](https://github.com/project-serum/anchor/pull/1096)) - ### Features ## [Unreleased] @@ -22,16 +16,21 @@ incremented for features. ### Fixes * lang: Add `deprecated` attribute to `ProgramAccount` ([#1014](https://github.com/project-serum/anchor/pull/1014)). -* cli: Add version number from programs `Cargo.toml` into extracted IDL -* lang: Add `deprecated` attribute to `Loader`([#1078](https://github.com/project-serum/anchor/pull/1078)) -* lang: the `init_if_needed` attribute now checks that given attributes (e.g. space, owner, token::authority etc.) are validated even when init is not needed([#1096](https://github.com/project-serum/anchor/pull/1096)) +* cli: Add version number from programs `Cargo.toml` into extracted IDL ([#1061](https://github.com/project-serum/anchor/pull/1061)). +* lang: Add `deprecated` attribute to `Loader`([#1078](https://github.com/project-serum/anchor/pull/1078)). +* lang: the `init_if_needed` attribute now checks that given attributes (e.g. space, owner, token::authority etc.) are validated even when init is not needed ([#1096](https://github.com/project-serum/anchor/pull/1096)). ### Features -* lang: Add `ErrorCode::AccountNotInitialized` error to separate the situation when the account has the wrong owner from when it does not exist (#[1024](https://github.com/project-serum/anchor/pull/1024)) -* lang: Called instructions now log their name by default. This can be turned off with the `no-log-ix-name` flag ([#1057](https://github.com/project-serum/anchor/pull/1057)) -* lang: `ProgramData` and `UpgradableLoaderState` can now be passed into `Account` as generics. see [UpgradeableLoaderState](https://docs.rs/solana-program/latest/solana_program/bpf_loader_upgradeable/enum.UpgradeableLoaderState.html). `UpgradableLoaderState` can also be matched on to get `ProgramData`, but when `ProgramData` is used instead, anchor does the serialization and checking that it is actually program data for you ([#1095](https://github.com/project-serum/anchor/pull/1095)) -* ts: Add better error msgs in the ts client if something wrong (i.e. not a pubkey or a string) is passed in as an account in an instruction accounts object ([#1098](https://github.com/project-serum/anchor/pull/1098)) +* lang: Add `ErrorCode::AccountNotInitialized` error to separate the situation when the account has the wrong owner from when it does not exist (#[1024](https://github.com/project-serum/anchor/pull/1024)). +* lang: Called instructions now log their name by default. This can be turned off with the `no-log-ix-name` flag ([#1057](https://github.com/project-serum/anchor/pull/1057)). +* lang: `ProgramData` and `UpgradableLoaderState` can now be passed into `Account` as generics. see [UpgradeableLoaderState](https://docs.rs/solana-program/latest/solana_program/bpf_loader_upgradeable/enum.UpgradeableLoaderState.html). `UpgradableLoaderState` can also be matched on to get `ProgramData`, but when `ProgramData` is used instead, anchor does the serialization and checking that it is actually program data for you ([#1095](https://github.com/project-serum/anchor/pull/1095)). +* ts: Add better error msgs in the ts client if something wrong (i.e. not a pubkey or a string) is passed in as an account in an instruction accounts object ([#1098](https://github.com/project-serum/anchor/pull/1098)). +* ts: Add inputs `postInstructions` and `preInstructions` as a replacement for (the now deprecated) `instructions` + +### Breaking + +* lang/ts: Error codes have been mapped to new numbers to allow for more errors per namespace ([#1096](https://github.com/project-serum/anchor/pull/1096)). ## [0.18.2] - 2021-11-14 @@ -40,7 +39,6 @@ incremented for features. ### Features * lang: Add `SystemAccount<'info>` account type for generic wallet addresses or accounts owned by the system program ([#954](https://github.com/project-serum/anchor/pull/954)) -* ts: Add inputs `postInstructions` and `preInstructions` as a replacement for (the now deprecated) `instructions` ### Fixes diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index 2424a5812a..86087851fc 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/lang/syn/src/codegen/accounts/constraints.rs @@ -581,13 +581,15 @@ pub fn generate_init( }, }; let pda_check = if !seeds_with_nonce.is_empty() { - quote! {let expected_key = anchor_lang::prelude::Pubkey::create_program_address( - #seeds_with_nonce, - #owner - ).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?; - if expected_key != #field.key() { - return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into()); - }} + quote! { + let expected_key = anchor_lang::prelude::Pubkey::create_program_address( + #seeds_with_nonce, + #owner + ).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?; + if expected_key != #field.key() { + return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into()); + } + } } else { quote! {} }; From b3ba8f59deb38267892749a49dbcd1f77ec5dde9 Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Wed, 8 Dec 2021 15:48:02 -0500 Subject: [PATCH 18/19] Update CHANGELOG.md --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1a90415f2..c424a5dafc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,6 @@ incremented for features. * lang, ts: Error codes have been mapped to new numbers to allow for more errors per namespace ([#1096](https://github.com/project-serum/anchor/pull/1096)). - ## [0.18.2] - 2021-11-14 * cli: Replace global JavaScript dependency installs with local. From 854affedf3c24c4d10177125f88c7d86f2e8a1a0 Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Wed, 8 Dec 2021 16:25:54 -0500 Subject: [PATCH 19/19] Fix clap dep --- client/example/Cargo.toml | 2 +- client/example/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/example/Cargo.toml b/client/example/Cargo.toml index 8058969bd6..d34fa48729 100644 --- a/client/example/Cargo.toml +++ b/client/example/Cargo.toml @@ -15,5 +15,5 @@ events = { path = "../../tests/events/programs/events", features = ["no-entrypoi shellexpand = "2.1.0" anyhow = "1.0.32" rand = "0.7.3" -clap = "3.0.0-beta.5" +clap = { version = "3.0.0-rc.0", features = ["derive"] } solana-sdk = "1.7.11" \ No newline at end of file diff --git a/client/example/src/main.rs b/client/example/src/main.rs index 29aa16ba5a..6ce0b536df 100644 --- a/client/example/src/main.rs +++ b/client/example/src/main.rs @@ -16,8 +16,8 @@ use events::MyEvent; use basic_4::accounts as basic_4_accounts; use basic_4::basic_4::Counter as CounterState; use basic_4::instruction as basic_4_instruction; -// The `accounts` and `instructions` modules are generated by the framework. use clap::Parser; +// The `accounts` and `instructions` modules are generated by the framework. use composite::accounts::{Bar, CompositeUpdate, Foo, Initialize}; use composite::instruction as composite_instruction; use composite::{DummyA, DummyB};