diff --git a/CHANGELOG.md b/CHANGELOG.md index 8769091f..7836e77d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ incremented for features. * lang, client, ts: Add event emission and subscriptions ([#89](https://github.com/project-serum/anchor/pull/89)). * lang/account: Allow namespacing account discriminators ([#128](https://github.com/project-serum/anchor/pull/128)). * cli: TypeScript migrations ([#132](https://github.com/project-serum/anchor/pull/132)). +* lang: Add `#[account(executable)]` attribute ([#140](https://github.com/project-serum/anchor/pull/140)). ## Breaking Changes diff --git a/examples/misc/programs/misc/src/lib.rs b/examples/misc/programs/misc/src/lib.rs index 97031ee8..93765667 100644 --- a/examples/misc/programs/misc/src/lib.rs +++ b/examples/misc/programs/misc/src/lib.rs @@ -27,6 +27,10 @@ pub mod misc { ctx.accounts.data.idata = idata; Ok(()) } + + pub fn test_executable(ctx: Context) -> ProgramResult { + Ok(()) + } } #[derive(Accounts)] @@ -39,6 +43,12 @@ pub struct Initialize<'info> { rent: Sysvar<'info, Rent>, } +#[derive(Accounts)] +pub struct TestExecutable<'info> { + #[account(executable)] + program: AccountInfo<'info>, +} + #[account] pub struct Data { udata: u128, diff --git a/examples/misc/tests/misc.js b/examples/misc/tests/misc.js index d7dcac67..8bab271f 100644 --- a/examples/misc/tests/misc.js +++ b/examples/misc/tests/misc.js @@ -42,4 +42,25 @@ describe("misc", () => { let accInfo = await anchor.getProvider().connection.getAccountInfo(pid); assert.ok(accInfo.executable); }); + + it("Can use the executable attribtue", async () => { + await program.rpc.testExecutable({ + accounts: { + program: program.programId, + }, + }); + + await assert.rejects( + async () => { + await program.rpc.testExecutable({ + accounts: { + program: program.provider.wallet.publicKey, + }, + }); + }, + (err) => { + return true; + } + ); + }); }); diff --git a/lang/syn/src/codegen/accounts.rs b/lang/syn/src/codegen/accounts.rs index c7fbebb1..5befc205 100644 --- a/lang/syn/src/codegen/accounts.rs +++ b/lang/syn/src/codegen/accounts.rs @@ -1,7 +1,7 @@ use crate::{ AccountField, AccountsStruct, CompositeField, Constraint, ConstraintBelongsTo, - ConstraintLiteral, ConstraintOwner, ConstraintRentExempt, ConstraintSeeds, ConstraintSigner, - Field, Ty, + ConstraintExecutable, ConstraintLiteral, ConstraintOwner, ConstraintRentExempt, + ConstraintSeeds, ConstraintSigner, Field, Ty, }; use heck::SnakeCase; use quote::quote; @@ -305,6 +305,7 @@ pub fn generate_field_constraint(f: &Field, c: &Constraint) -> proc_macro2::Toke Constraint::Owner(c) => generate_constraint_owner(f, c), Constraint::RentExempt(c) => generate_constraint_rent_exempt(f, c), Constraint::Seeds(c) => generate_constraint_seeds(f, c), + Constraint::Executable(c) => generate_constraint_executable(f, c), } } @@ -411,3 +412,15 @@ pub fn generate_constraint_seeds(f: &Field, c: &ConstraintSeeds) -> proc_macro2: } } } + +pub fn generate_constraint_executable( + f: &Field, + _c: &ConstraintExecutable, +) -> proc_macro2::TokenStream { + let name = &f.ident; + quote! { + if !#name.to_account_info().executable { + return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(5)) // todo + } + } +} diff --git a/lang/syn/src/lib.rs b/lang/syn/src/lib.rs index 141fc308..8e1dd550 100644 --- a/lang/syn/src/lib.rs +++ b/lang/syn/src/lib.rs @@ -271,6 +271,7 @@ pub enum Constraint { Owner(ConstraintOwner), RentExempt(ConstraintRentExempt), Seeds(ConstraintSeeds), + Executable(ConstraintExecutable), } #[derive(Debug)] @@ -303,6 +304,9 @@ pub struct ConstraintSeeds { pub seeds: proc_macro2::Group, } +#[derive(Debug)] +pub struct ConstraintExecutable {} + #[derive(Debug)] pub struct Error { pub name: String, diff --git a/lang/syn/src/parser/accounts.rs b/lang/syn/src/parser/accounts.rs index 977baa9a..96279741 100644 --- a/lang/syn/src/parser/accounts.rs +++ b/lang/syn/src/parser/accounts.rs @@ -1,7 +1,8 @@ use crate::{ AccountField, AccountsStruct, CompositeField, Constraint, ConstraintBelongsTo, - ConstraintLiteral, ConstraintOwner, ConstraintRentExempt, ConstraintSeeds, ConstraintSigner, - CpiAccountTy, Field, ProgramAccountTy, ProgramStateTy, SysvarTy, Ty, + ConstraintExecutable, ConstraintLiteral, ConstraintOwner, ConstraintRentExempt, + ConstraintSeeds, ConstraintSigner, CpiAccountTy, Field, ProgramAccountTy, ProgramStateTy, + SysvarTy, Ty, }; pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct { @@ -270,6 +271,9 @@ fn parse_constraints(anchor: &syn::Attribute) -> (Vec, bool, bool, b } }; } + "executable" => { + constraints.push(Constraint::Executable(ConstraintExecutable {})); + } _ => { panic!("invalid syntax"); }