From a94e24aea4880f6259cabc67c90a0d223fefced2 Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Mon, 12 Apr 2021 12:54:35 +0800 Subject: [PATCH] lang: Add owner constraint (#178) --- CHANGELOG.md | 1 + examples/misc/programs/misc/src/lib.rs | 11 +++++++++++ examples/misc/tests/misc.js | 26 +++++++++++++++++++++++++- lang/derive/accounts/src/lib.rs | 1 + lang/syn/src/codegen/accounts.rs | 20 ++++++++------------ lang/syn/src/lib.rs | 5 ++--- lang/syn/src/parser/accounts.rs | 9 ++------- 7 files changed, 50 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a52a95505..947cd0033 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ incremented for features. ## Features * lang: CPI clients for program state instructions ([#43](https://github.com/project-serum/anchor/pull/43)). +* lang: Add `#[account(owner = )]` constraint ([#178](https://github.com/project-serum/anchor/pull/178)). ## Fixes diff --git a/examples/misc/programs/misc/src/lib.rs b/examples/misc/programs/misc/src/lib.rs index 4b8e7fcef..012d6c7b1 100644 --- a/examples/misc/programs/misc/src/lib.rs +++ b/examples/misc/programs/misc/src/lib.rs @@ -28,6 +28,10 @@ pub mod misc { Ok(()) } + pub fn test_owner(_ctx: Context) -> ProgramResult { + Ok(()) + } + pub fn test_executable(_ctx: Context) -> ProgramResult { Ok(()) } @@ -52,6 +56,13 @@ pub struct Initialize<'info> { rent: Sysvar<'info, Rent>, } +#[derive(Accounts)] +pub struct TestOwner<'info> { + #[account(owner = misc)] + data: AccountInfo<'info>, + misc: AccountInfo<'info>, +} + #[derive(Accounts)] pub struct TestExecutable<'info> { #[account(executable)] diff --git a/examples/misc/tests/misc.js b/examples/misc/tests/misc.js index b573b94c7..f37f0c3b6 100644 --- a/examples/misc/tests/misc.js +++ b/examples/misc/tests/misc.js @@ -17,8 +17,9 @@ describe("misc", () => { assert.ok(accountInfo.data.length === 99); }); + const data = new anchor.web3.Account(); + it("Can use u128 and i128", async () => { - const data = new anchor.web3.Account(); const tx = await program.rpc.initialize( new anchor.BN(1234), new anchor.BN(22), @@ -44,6 +45,29 @@ describe("misc", () => { assert.ok(accInfo.executable); }); + it("Can use the owner constraint", async () => { + await program.rpc.testOwner({ + accounts: { + data: data.publicKey, + misc: program.programId, + }, + }); + + await assert.rejects( + async () => { + await program.rpc.testOwner({ + accounts: { + data: program.provider.wallet.publicKey, + misc: program.programId, + }, + }); + }, + (err) => { + return true; + } + ); + }); + it("Can use the executable attribtue", async () => { await program.rpc.testExecutable({ accounts: { diff --git a/lang/derive/accounts/src/lib.rs b/lang/derive/accounts/src/lib.rs index b5c15985c..f37f3ddc0 100644 --- a/lang/derive/accounts/src/lib.rs +++ b/lang/derive/accounts/src/lib.rs @@ -48,6 +48,7 @@ use syn::parse_macro_input; /// | `#[account(rent_exempt = )]` | On `AccountInfo` or `ProgramAccount` structs | Optional attribute to skip the rent exemption check. By default, all accounts marked with `#[account(init)]` will be rent exempt, and so this should rarely (if ever) be used. Similarly, omitting `= skip` will mark the account rent exempt. | /// | `#[account(executable)]` | On `AccountInfo` structs | Checks the given account is an executable program. | /// | `#[account(state = )]` | On `CpiState` structs | Checks the given state is the canonical state account for the target program. | +/// | `#[account(owner = )]` | On `CpiState`, `CpiAccount`, and `AccountInfo` | Checks the account owner matches the target. | #[proc_macro_derive(Accounts, attributes(account))] pub fn derive_anchor_deserialize(item: TokenStream) -> TokenStream { let strct = parse_macro_input!(item as syn::ItemStruct); diff --git a/lang/syn/src/codegen/accounts.rs b/lang/syn/src/codegen/accounts.rs index 07300dfd5..488a0fdfa 100644 --- a/lang/syn/src/codegen/accounts.rs +++ b/lang/syn/src/codegen/accounts.rs @@ -374,18 +374,11 @@ pub fn generate_constraint_literal(c: &ConstraintLiteral) -> proc_macro2::TokenS pub fn generate_constraint_owner(f: &Field, c: &ConstraintOwner) -> proc_macro2::TokenStream { let ident = &f.ident; - let info = match f.ty { - Ty::AccountInfo => quote! { #ident }, - Ty::ProgramAccount(_) => quote! { #ident.to_account_info() }, - _ => panic!("Invalid syntax: owner cannot be specified."), - }; - match c { - ConstraintOwner::Skip => quote! {}, - ConstraintOwner::Program => quote! { - if #info.owner != program_id { - return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo: error codes - } - }, + let owner_target = c.owner_target.clone(); + quote! { + if #ident.to_account_info().owner != #owner_target.to_account_info().key { + return Err(ProgramError::Custom(76)); // todo: proper error. + } } } @@ -448,5 +441,8 @@ pub fn generate_constraint_state(f: &Field, c: &ConstraintState) -> proc_macro2: if #ident.to_account_info().key != &anchor_lang::CpiState::<#account_ty>::address(#program_target.to_account_info().key) { return Err(ProgramError::Custom(1)); // todo: proper error. } + if #ident.to_account_info().owner != #program_target.to_account_info().key { + return Err(ProgramError::Custom(1)); // todo: proper error. + } } } diff --git a/lang/syn/src/lib.rs b/lang/syn/src/lib.rs index 096e13141..b806ff36e 100644 --- a/lang/syn/src/lib.rs +++ b/lang/syn/src/lib.rs @@ -301,9 +301,8 @@ pub struct ConstraintLiteral { } #[derive(Debug)] -pub enum ConstraintOwner { - Program, - Skip, +pub struct ConstraintOwner { + pub owner_target: proc_macro2::Ident, } #[derive(Debug)] diff --git a/lang/syn/src/parser/accounts.rs b/lang/syn/src/parser/accounts.rs index d9408bb57..683bbdc36 100644 --- a/lang/syn/src/parser/accounts.rs +++ b/lang/syn/src/parser/accounts.rs @@ -243,16 +243,11 @@ fn parse_constraints(anchor: &syn::Attribute) -> (Vec, bool, bool, b } _ => panic!("invalid syntax"), }; - let owner = match inner_tts.next().unwrap() { + let owner_target = match inner_tts.next().unwrap() { proc_macro2::TokenTree::Ident(ident) => ident, _ => panic!("invalid syntax"), }; - let constraint = match owner.to_string().as_str() { - "program" => ConstraintOwner::Program, - "skip" => ConstraintOwner::Skip, - _ => panic!("invalid syntax"), - }; - constraints.push(Constraint::Owner(constraint)); + constraints.push(Constraint::Owner(ConstraintOwner { owner_target })); } "rent_exempt" => { match inner_tts.next() {