Skip to content

Commit e99e7e2

Browse files
authored
lang: Always execute constraints for init_if_needed (otter-sec#1096)
1 parent feb3ef9 commit e99e7e2

16 files changed

Lines changed: 668 additions & 78 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,22 @@ incremented for features.
1414
### Fixes
1515

1616
* lang: Add `deprecated` attribute to `ProgramAccount` ([#1014](https://github.com/project-serum/anchor/pull/1014)).
17-
* cli: Add version number from programs `Cargo.toml` into extracted IDL
18-
* lang: Add `deprecated` attribute to `Loader`([#1078](https://github.com/project-serum/anchor/pull/1078))
17+
* cli: Add version number from programs `Cargo.toml` into extracted IDL ([#1061](https://github.com/project-serum/anchor/pull/1061)).
18+
* lang: Add `deprecated` attribute to `Loader`([#1078](https://github.com/project-serum/anchor/pull/1078)).
19+
* 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)).
1920

2021
### Features
2122

22-
* 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))
23-
* 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))
24-
* ts: Add `getAccountInfo` helper method to account namespace/client ([#1084](https://github.com/project-serum/anchor/pull/1084))
25-
* 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))
26-
* 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))
23+
* 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)).
24+
* 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)).
25+
* 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)).
26+
* 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)).
27+
* ts: Add inputs `postInstructions` and `preInstructions` as a replacement for (the now deprecated) `instructions` ([#1007](https://github.com/project-serum/anchor/pull/1007)).
28+
* ts: Add `getAccountInfo` helper method to account namespace/client ([#1084](https://github.com/project-serum/anchor/pull/1084)).
2729

30+
### Breaking
31+
32+
* 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)).
2833

2934
## [0.18.2] - 2021-11-14
3035

@@ -33,7 +38,6 @@ incremented for features.
3338
### Features
3439

3540
* 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))
36-
* ts: Add inputs `postInstructions` and `preInstructions` as a replacement for (the now deprecated) `instructions`
3741

3842
### Fixes
3943

client/example/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ events = { path = "../../tests/events/programs/events", features = ["no-entrypoi
1515
shellexpand = "2.1.0"
1616
anyhow = "1.0.32"
1717
rand = "0.7.3"
18-
clap = "3.0.0-beta.5"
18+
clap = { version = "3.0.0-rc.0", features = ["derive"] }
1919
solana-sdk = "1.7.11"

client/example/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ use events::MyEvent;
1616
use basic_4::accounts as basic_4_accounts;
1717
use basic_4::basic_4::Counter as CounterState;
1818
use basic_4::instruction as basic_4_instruction;
19-
// The `accounts` and `instructions` modules are generated by the framework.
2019
use clap::Parser;
20+
// The `accounts` and `instructions` modules are generated by the framework.
2121
use composite::accounts::{Bar, CompositeUpdate, Foo, Initialize};
2222
use composite::instruction as composite_instruction;
2323
use composite::{DummyA, DummyB};

lang/src/error.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ pub enum ErrorCode {
1515

1616
// IDL instructions.
1717
#[msg("The program was compiled without idl instructions")]
18-
IdlInstructionStub = 120,
18+
IdlInstructionStub = 1000,
1919
#[msg("Invalid program given to the IDL instruction")]
2020
IdlInstructionInvalidProgram,
2121

2222
// Constraints.
2323
#[msg("A mut constraint was violated")]
24-
ConstraintMut = 140,
24+
ConstraintMut = 2000,
2525
#[msg("A has one constraint was violated")]
2626
ConstraintHasOne,
2727
#[msg("A signer constraint as violated")]
@@ -48,10 +48,23 @@ pub enum ErrorCode {
4848
ConstraintAddress,
4949
#[msg("Expected zero account discriminant")]
5050
ConstraintZero,
51+
#[msg("A token mint constraint was violated")]
52+
ConstraintTokenMint,
53+
#[msg("A token owner constraint was violated")]
54+
ConstraintTokenOwner,
55+
// The mint mint is intentional -> a mint authority for the mint.
56+
#[msg("A mint mint authority constraint was violated")]
57+
ConstraintMintMintAuthority,
58+
#[msg("A mint freeze authority constraint was violated")]
59+
ConstraintMintFreezeAuthority,
60+
#[msg("A mint decimals constraint was violated")]
61+
ConstraintMintDecimals,
62+
#[msg("A space constraint was violated")]
63+
ConstraintSpace,
5164

5265
// Accounts.
5366
#[msg("The account discriminator was already set on this account")]
54-
AccountDiscriminatorAlreadySet = 160,
67+
AccountDiscriminatorAlreadySet = 3000,
5568
#[msg("No 8 byte discriminator was found on the account")]
5669
AccountDiscriminatorNotFound,
5770
#[msg("8 byte discriminator did not match what was expected")]
@@ -81,9 +94,9 @@ pub enum ErrorCode {
8194

8295
// State.
8396
#[msg("The given state account does not have the correct address")]
84-
StateInvalidAddress = 180,
97+
StateInvalidAddress = 4000,
8598

8699
// Used for APIs that shouldn't be used anymore.
87100
#[msg("The API being used is deprecated and should no longer be used")]
88-
Deprecated = 299,
101+
Deprecated = 5000,
89102
}

lang/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,8 @@ pub trait AccountDeserialize: Sized {
183183
/// uninitialized accounts, where the bytes are zeroed. Implementations
184184
/// should be unique to a particular account type so that one can never
185185
/// successfully deserialize the data of one account type into another.
186-
/// For example, if the SPL token program where to implement this trait,
187-
/// it should impossible to deserialize a `Mint` account into a token
186+
/// For example, if the SPL token program were to implement this trait,
187+
/// it should be impossible to deserialize a `Mint` account into a token
188188
/// `Account`.
189189
fn try_deserialize(buf: &mut &[u8]) -> Result<Self, ProgramError>;
190190

@@ -303,7 +303,7 @@ pub mod __private {
303303
}
304304

305305
// The starting point for user defined error codes.
306-
pub const ERROR_CODE_OFFSET: u32 = 300;
306+
pub const ERROR_CODE_OFFSET: u32 = 6000;
307307

308308
// Calculates the size of an account, which may be larger than the deserialized
309309
// data in it. This trait is currently only used for `#[state]` accounts.

lang/syn/src/codegen/accounts/constraints.rs

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,14 @@ pub fn generate_init(
449449
}
450450

451451
let pa: #ty_decl = #from_account_info;
452+
if !(!#if_needed || #field.to_account_info().owner == &anchor_lang::solana_program::system_program::ID) {
453+
if pa.mint != #mint.key() {
454+
return Err(anchor_lang::__private::ErrorCode::ConstraintTokenMint.into());
455+
}
456+
if pa.owner != #owner.key() {
457+
return Err(anchor_lang::__private::ErrorCode::ConstraintTokenOwner.into());
458+
}
459+
}
452460
pa
453461
};
454462
}
@@ -473,6 +481,14 @@ pub fn generate_init(
473481
anchor_spl::associated_token::create(cpi_ctx)?;
474482
}
475483
let pa: #ty_decl = #from_account_info;
484+
if !(!#if_needed || #field.to_account_info().owner == &anchor_lang::solana_program::system_program::ID) {
485+
if pa.mint != #mint.key() {
486+
return Err(anchor_lang::__private::ErrorCode::ConstraintTokenMint.into());
487+
}
488+
if pa.owner != #owner.key() {
489+
return Err(anchor_lang::__private::ErrorCode::ConstraintTokenOwner.into());
490+
}
491+
}
476492
pa
477493
};
478494
}
@@ -489,8 +505,8 @@ pub fn generate_init(
489505
seeds_with_nonce,
490506
);
491507
let freeze_authority = match freeze_authority {
492-
Some(fa) => quote! { Some(&#fa.key()) },
493-
None => quote! { None },
508+
Some(fa) => quote! { Option::<&anchor_lang::prelude::Pubkey>::Some(&#fa.key()) },
509+
None => quote! { Option::<&anchor_lang::prelude::Pubkey>::None },
494510
};
495511
quote! {
496512
let #field: #ty_decl = {
@@ -508,9 +524,23 @@ pub fn generate_init(
508524
rent: rent.to_account_info(),
509525
};
510526
let cpi_ctx = CpiContext::new(cpi_program, accounts);
511-
anchor_spl::token::initialize_mint(cpi_ctx, #decimals, &#owner.to_account_info().key, #freeze_authority)?;
527+
anchor_spl::token::initialize_mint(cpi_ctx, #decimals, &#owner.key(), #freeze_authority)?;
512528
}
513529
let pa: #ty_decl = #from_account_info;
530+
if !(!#if_needed || #field.to_account_info().owner == &anchor_lang::solana_program::system_program::ID) {
531+
if pa.mint_authority != anchor_lang::solana_program::program_option::COption::Some(#owner.key()) {
532+
return Err(anchor_lang::__private::ErrorCode::ConstraintMintMintAuthority.into());
533+
}
534+
if pa.freeze_authority
535+
.as_ref()
536+
.map(|fa| #freeze_authority.as_ref().map(|expected_fa| fa != *expected_fa).unwrap_or(true))
537+
.unwrap_or(#freeze_authority.is_some()) {
538+
return Err(anchor_lang::__private::ErrorCode::ConstraintMintFreezeAuthority.into());
539+
}
540+
if pa.decimals != #decimals {
541+
return Err(anchor_lang::__private::ErrorCode::ConstraintMintDecimals.into());
542+
}
543+
}
514544
pa
515545
};
516546
}
@@ -550,16 +580,42 @@ pub fn generate_init(
550580
&#o
551581
},
552582
};
583+
let pda_check = if !seeds_with_nonce.is_empty() {
584+
quote! {
585+
let expected_key = anchor_lang::prelude::Pubkey::create_program_address(
586+
#seeds_with_nonce,
587+
#owner
588+
).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?;
589+
if expected_key != #field.key() {
590+
return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
591+
}
592+
}
593+
} else {
594+
quote! {}
595+
};
553596
let create_account =
554-
generate_create_account(field, quote! {space}, owner, seeds_with_nonce);
597+
generate_create_account(field, quote! {space}, owner.clone(), seeds_with_nonce);
555598
quote! {
556599
let #field = {
557-
if !#if_needed || #field.to_account_info().owner == &anchor_lang::solana_program::system_program::ID {
558-
#space
600+
let actual_field = #field.to_account_info();
601+
let actual_owner = actual_field.owner;
602+
#space
603+
if !#if_needed || actual_owner == &anchor_lang::solana_program::system_program::ID {
559604
#payer
560605
#create_account
561606
}
562607
let pa: #ty_decl = #from_account_info;
608+
if !(!#if_needed || actual_owner == &anchor_lang::solana_program::system_program::ID) {
609+
if space != actual_field.data_len() {
610+
return Err(anchor_lang::__private::ErrorCode::ConstraintSpace.into());
611+
}
612+
613+
if actual_owner != #owner {
614+
return Err(anchor_lang::__private::ErrorCode::ConstraintOwner.into());
615+
}
616+
617+
#pda_check
618+
}
563619
pa
564620
};
565621
}

lang/syn/src/idl/file.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use std::path::Path;
1111

1212
const DERIVE_NAME: &str = "Accounts";
1313
// TODO: sharee this with `anchor_lang` crate.
14-
const ERROR_CODE_OFFSET: u32 = 300;
14+
const ERROR_CODE_OFFSET: u32 = 6000;
1515

1616
// Parse an entire interface file.
1717
pub fn parse(filename: impl AsRef<Path>, version: String) -> Result<Option<Idl>> {

lang/syn/src/parser/accounts/constraints.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
432432
if self.token_mint.is_none() {
433433
return Err(ParseError::new(
434434
token_authority.span(),
435-
"token authority must be provided if token mint is",
435+
"token mint must be provided if token authority is",
436436
));
437437
}
438438
}

tests/bpf-upgradeable-state/tests/bpf-upgradable-state.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ describe('bpf_upgradeable_state', () => {
5353
});
5454
assert.ok(false);
5555
} catch (err) {
56-
assert.equal(err.code, 143);
56+
assert.equal(err.code, 2003);
5757
assert.equal(err.msg, "A raw constraint was violated");
5858
}
5959
});
@@ -73,7 +73,7 @@ describe('bpf_upgradeable_state', () => {
7373
});
7474
assert.ok(false);
7575
} catch (err) {
76-
assert.equal(err.code, 173);
76+
assert.equal(err.code, 3013);
7777
assert.equal(err.msg, "The given account is not a program data account");
7878
}
7979
});
@@ -93,7 +93,7 @@ describe('bpf_upgradeable_state', () => {
9393
});
9494
assert.ok(false);
9595
} catch (err) {
96-
assert.equal(err.code, 167);
96+
assert.equal(err.code, 3007);
9797
assert.equal(err.msg, "The given account is not owned by the executing program");
9898
}
9999
});
@@ -119,7 +119,7 @@ describe('bpf_upgradeable_state', () => {
119119
});
120120
assert.ok(false);
121121
} catch (err) {
122-
assert.equal(err.code, 300);
122+
assert.equal(err.code, 6000);
123123
}
124124
});
125125
});

tests/errors/tests/errors.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ describe("errors", () => {
1717
"This is an error message clients will automatically display";
1818
assert.equal(err.toString(), errMsg);
1919
assert.equal(err.msg, errMsg);
20-
assert.equal(err.code, 300);
20+
assert.equal(err.code, 6000);
2121
}
2222
});
2323

@@ -29,7 +29,7 @@ describe("errors", () => {
2929
const errMsg = "HelloNoMsg";
3030
assert.equal(err.toString(), errMsg);
3131
assert.equal(err.msg, errMsg);
32-
assert.equal(err.code, 300 + 123);
32+
assert.equal(err.code, 6000 + 123);
3333
}
3434
});
3535

@@ -41,7 +41,7 @@ describe("errors", () => {
4141
const errMsg = "HelloNext";
4242
assert.equal(err.toString(), errMsg);
4343
assert.equal(err.msg, errMsg);
44-
assert.equal(err.code, 300 + 124);
44+
assert.equal(err.code, 6000 + 124);
4545
}
4646
});
4747

@@ -57,7 +57,7 @@ describe("errors", () => {
5757
const errMsg = "A mut constraint was violated";
5858
assert.equal(err.toString(), errMsg);
5959
assert.equal(err.msg, errMsg);
60-
assert.equal(err.code, 140);
60+
assert.equal(err.code, 2000);
6161
}
6262
});
6363

@@ -80,7 +80,7 @@ describe("errors", () => {
8080
const errMsg = "A has_one constraint was violated";
8181
assert.equal(err.toString(), errMsg);
8282
assert.equal(err.msg, errMsg);
83-
assert.equal(err.code, 141);
83+
assert.equal(err.code, 2001);
8484
}
8585
});
8686

@@ -108,7 +108,7 @@ describe("errors", () => {
108108
assert.ok(false);
109109
} catch (err) {
110110
const errMsg =
111-
"Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x8e";
111+
"Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x7d2";
112112
assert.equal(err.toString(), errMsg);
113113
}
114114
});
@@ -125,7 +125,7 @@ describe("errors", () => {
125125
const errMsg = "HelloCustom";
126126
assert.equal(err.toString(), errMsg);
127127
assert.equal(err.msg, errMsg);
128-
assert.equal(err.code, 300 + 125);
128+
assert.equal(err.code, 6000 + 125);
129129
}
130130
});
131131

0 commit comments

Comments
 (0)