Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lang: init_if_needed should run checks on constraints even if no init is needed #1096

Merged
merged 20 commits into from
Dec 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,22 @@ 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))
* 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))
* ts: Add `getAccountInfo` helper method to account namespace/client ([#1084](https://github.com/project-serum/anchor/pull/1084))
* 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` ([#1007](https://github.com/project-serum/anchor/pull/1007)).
* ts: Add `getAccountInfo` helper method to account namespace/client ([#1084](https://github.com/project-serum/anchor/pull/1084)).

### 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)).

armaniferrante marked this conversation as resolved.
Show resolved Hide resolved
## [0.18.2] - 2021-11-14

Expand All @@ -33,7 +38,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

Expand Down
2 changes: 1 addition & 1 deletion client/example/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 1 addition & 1 deletion client/example/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
23 changes: 18 additions & 5 deletions lang/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ pub enum ErrorCode {

// IDL instructions.
#[msg("The program was compiled without idl instructions")]
IdlInstructionStub = 120,
IdlInstructionStub = 1000,
paul-schaaf marked this conversation as resolved.
Show resolved Hide resolved
#[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")]
Expand All @@ -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 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,
#[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")]
Expand Down Expand Up @@ -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,
}
6 changes: 3 additions & 3 deletions lang/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self, ProgramError>;

Expand Down Expand Up @@ -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.
Expand Down
68 changes: 62 additions & 6 deletions lang/syn/src/codegen/accounts/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,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.owner != #owner.key() {
return Err(anchor_lang::__private::ErrorCode::ConstraintTokenOwner.into());
}
}
pa
};
}
Expand All @@ -473,6 +481,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.owner != #owner.key() {
return Err(anchor_lang::__private::ErrorCode::ConstraintTokenOwner.into());
}
}
pa
};
}
Expand All @@ -489,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 = {
Expand All @@ -508,9 +524,23 @@ 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 != anchor_lang::solana_program::program_option::COption::Some(#owner.key()) {
return Err(anchor_lang::__private::ErrorCode::ConstraintMintMintAuthority.into());
}
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::__private::ErrorCode::ConstraintMintFreezeAuthority.into());
}
if pa.decimals != #decimals {
return Err(anchor_lang::__private::ErrorCode::ConstraintMintDecimals.into());
}
}
pa
};
}
Expand Down Expand Up @@ -550,16 +580,42 @@ pub fn generate_init(
&#o
},
};
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! {}
};
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 {
#space
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 {
#payer
#create_account
}
let pa: #ty_decl = #from_account_info;
if !(!#if_needed || actual_owner == &anchor_lang::solana_program::system_program::ID) {
if space != actual_field.data_len() {
return Err(anchor_lang::__private::ErrorCode::ConstraintSpace.into());
}

if actual_owner != #owner {
return Err(anchor_lang::__private::ErrorCode::ConstraintOwner.into());
}

#pda_check
}
pa
};
}
Expand Down
2 changes: 1 addition & 1 deletion lang/syn/src/idl/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Path>, version: String) -> Result<Option<Idl>> {
Expand Down
2 changes: 1 addition & 1 deletion lang/syn/src/parser/accounts/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
));
}
}
Expand Down
8 changes: 4 additions & 4 deletions tests/bpf-upgradeable-state/tests/bpf-upgradable-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
});
Expand All @@ -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");
}
});
Expand All @@ -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");
}
});
Expand All @@ -119,7 +119,7 @@ describe('bpf_upgradeable_state', () => {
});
assert.ok(false);
} catch (err) {
assert.equal(err.code, 300);
assert.equal(err.code, 6000);
}
});
});
14 changes: 7 additions & 7 deletions tests/errors/tests/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
});

Expand All @@ -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);
}
});

Expand All @@ -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);
}
});

Expand All @@ -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);
}
});

Expand All @@ -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);
}
});

Expand Down Expand Up @@ -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);
}
});
Expand All @@ -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);
}
});

Expand Down
8 changes: 4 additions & 4 deletions tests/lockup/tests/lockup.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
Loading