瀏覽代碼

lang: Initialize program derived addresses with instruction data (#386)

Armani Ferrante 4 年之前
父節點
當前提交
a6ebaabac4

+ 3 - 0
CHANGELOG.md

@@ -24,6 +24,8 @@ incremented for features.
 * cli, client, lang: Update solana toolchain to v1.7.1 ([#368](https://github.com/project-serum/anchor/pull/369)).
 * ts: Instruction decoding and formatting ([#372](https://github.com/project-serum/anchor/pull/372)).
 * lang: Add `#[account(close = <destination>)]` constraint for closing accounts and sending the rent exemption lamports to a specified destination account ([#371](https://github.com/project-serum/anchor/pull/371)).
+* lang: Instruction data is now available to accounts constraints ([#386](https://github.com/project-serum/anchor/pull/386)).
+* lang: Initialize program derived addresses with accounts constraints ([#386](https://github.com/project-serum/anchor/pull/386)).
 
 ### Fixes
 
@@ -32,6 +34,7 @@ incremented for features.
 ### Breaking
 
 * lang, ts: Framework defined error codes are introduced, reserving error codes 0-300 for Anchor, and 300 and up for user defined error codes ([#354](https://github.com/project-serum/anchor/pull/354)).
+* lang: Accounts trait now accepts an additional `&[u8]` parameter ([#386](https://github.com/project-serum/anchor/pull/386)).
 
 ## [0.7.0] - 2021-05-31
 

+ 5 - 2
examples/lockup/programs/registry/src/lib.rs

@@ -499,8 +499,11 @@ mod registry {
         let signer = &[&seeds[..]];
         let mut remaining_accounts: &[AccountInfo] = ctx.remaining_accounts;
         let cpi_program = ctx.accounts.lockup_program.clone();
-        let cpi_accounts =
-            CreateVesting::try_accounts(ctx.accounts.lockup_program.key, &mut remaining_accounts)?;
+        let cpi_accounts = CreateVesting::try_accounts(
+            ctx.accounts.lockup_program.key,
+            &mut remaining_accounts,
+            &[],
+        )?;
         let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
         lockup::cpi::create_vesting(
             cpi_ctx,

+ 79 - 0
examples/misc/programs/misc/src/lib.rs

@@ -93,6 +93,76 @@ pub mod misc {
     pub fn test_close(_ctx: Context<TestClose>) -> ProgramResult {
         Ok(())
     }
+
+    pub fn test_instruction_constraint(
+        _ctx: Context<TestInstructionConstraint>,
+        _nonce: u8,
+    ) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn test_pda_init(
+        ctx: Context<TestPdaInit>,
+        _domain: String,
+        _seed: Vec<u8>,
+        _bump: u8,
+    ) -> ProgramResult {
+        ctx.accounts.my_pda.data = 6;
+        Ok(())
+    }
+
+    pub fn test_pda_init_zero_copy(ctx: Context<TestPdaInitZeroCopy>, bump: u8) -> ProgramResult {
+        let mut acc = ctx.accounts.my_pda.load_init()?;
+        acc.data = 9;
+        acc.bump = bump;
+        Ok(())
+    }
+
+    pub fn test_pda_mut_zero_copy(ctx: Context<TestPdaMutZeroCopy>) -> ProgramResult {
+        let mut acc = ctx.accounts.my_pda.load_mut()?;
+        acc.data = 1234;
+        Ok(())
+    }
+}
+
+#[derive(Accounts)]
+#[instruction(nonce: u8)]
+pub struct TestInstructionConstraint<'info> {
+    #[account(seeds = [b"my-seed", my_account.key.as_ref(), &[nonce]])]
+    pub my_pda: AccountInfo<'info>,
+    pub my_account: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+#[instruction(domain: String, seed: Vec<u8>, bump: u8)]
+pub struct TestPdaInit<'info> {
+    #[account(
+        init,
+        seeds = [b"my-seed", domain.as_bytes(), foo.key.as_ref(), &seed, &[bump]],
+        payer = my_payer,
+    )]
+    my_pda: ProgramAccount<'info, DataU16>,
+    my_payer: AccountInfo<'info>,
+    foo: AccountInfo<'info>,
+    rent: Sysvar<'info, Rent>,
+    system_program: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+#[instruction(bump: u8)]
+pub struct TestPdaInitZeroCopy<'info> {
+    #[account(init, seeds = [b"my-seed".as_ref(), &[bump]], payer = my_payer)]
+    my_pda: Loader<'info, DataZeroCopy>,
+    my_payer: AccountInfo<'info>,
+    rent: Sysvar<'info, Rent>,
+    system_program: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct TestPdaMutZeroCopy<'info> {
+    #[account(mut, seeds = [b"my-seed".as_ref(), &[my_pda.load()?.bump]])]
+    my_pda: Loader<'info, DataZeroCopy>,
+    my_payer: AccountInfo<'info>,
 }
 
 #[derive(Accounts)]
@@ -194,6 +264,7 @@ pub struct TestI8<'info> {
 }
 
 #[associated]
+#[derive(Default)]
 pub struct TestData {
     data: u64,
 }
@@ -205,6 +276,7 @@ pub struct Data {
 }
 
 #[account]
+#[derive(Default)]
 pub struct DataU16 {
     data: u16,
 }
@@ -219,6 +291,13 @@ pub struct DataI16 {
     data: i16,
 }
 
+#[account(zero_copy)]
+#[derive(Default)]
+pub struct DataZeroCopy {
+    data: u16,
+    bump: u8,
+}
+
 #[event]
 pub struct E1 {
     data: u32,

+ 81 - 0
examples/misc/tests/misc.js

@@ -1,4 +1,5 @@
 const anchor = require("@project-serum/anchor");
+const PublicKey = anchor.web3.PublicKey;
 const serumCmn = require("@project-serum/common");
 const assert = require("assert");
 
@@ -321,4 +322,84 @@ describe("misc", () => {
     );
     assert.ok(closedAccount === null);
   });
+
+  it("Can use instruction data in accounts constraints", async () => {
+    // b"my-seed"
+    const seed = Buffer.from([109, 121, 45, 115, 101, 101, 100]);
+    const [myPda, nonce] = await PublicKey.findProgramAddress(
+      [seed, anchor.web3.SYSVAR_RENT_PUBKEY.toBuffer()],
+      program.programId
+    );
+
+    await program.rpc.testInstructionConstraint(nonce, {
+      accounts: {
+        myPda,
+        myAccount: anchor.web3.SYSVAR_RENT_PUBKEY,
+      },
+    });
+  });
+
+  it("Can create a PDA account with instruction data", async () => {
+    const seed = Buffer.from([1, 2, 3, 4]);
+    const domain = "my-domain";
+    const foo = anchor.web3.SYSVAR_RENT_PUBKEY;
+    const [myPda, nonce] = await PublicKey.findProgramAddress(
+      [
+        Buffer.from(anchor.utils.bytes.utf8.encode("my-seed")),
+        Buffer.from(anchor.utils.bytes.utf8.encode(domain)),
+        foo.toBuffer(),
+        seed,
+      ],
+      program.programId
+    );
+
+    await program.rpc.testPdaInit(domain, seed, nonce, {
+      accounts: {
+        myPda,
+        myPayer: program.provider.wallet.publicKey,
+        foo,
+        rent: anchor.web3.SYSVAR_RENT_PUBKEY,
+        systemProgram: anchor.web3.SystemProgram.programId,
+      },
+    });
+
+    const myPdaAccount = await program.account.dataU16.fetch(myPda);
+    assert.ok(myPdaAccount.data === 6);
+  });
+
+  it("Can create a zero copy PDA account", async () => {
+    const [myPda, nonce] = await PublicKey.findProgramAddress(
+      [Buffer.from(anchor.utils.bytes.utf8.encode("my-seed"))],
+      program.programId
+    );
+    await program.rpc.testPdaInitZeroCopy(nonce, {
+      accounts: {
+        myPda,
+        myPayer: program.provider.wallet.publicKey,
+        rent: anchor.web3.SYSVAR_RENT_PUBKEY,
+        systemProgram: anchor.web3.SystemProgram.programId,
+      },
+    });
+
+    const myPdaAccount = await program.account.dataZeroCopy.fetch(myPda);
+    assert.ok(myPdaAccount.data === 9);
+    assert.ok((myPdaAccount.bump = nonce));
+  });
+
+  it("Can write to a zero copy PDA account", async () => {
+    const [myPda, bump] = await PublicKey.findProgramAddress(
+      [Buffer.from(anchor.utils.bytes.utf8.encode("my-seed"))],
+      program.programId
+    );
+    await program.rpc.testPdaMutZeroCopy({
+      accounts: {
+        myPda,
+        myPayer: program.provider.wallet.publicKey,
+      },
+    });
+
+    const myPdaAccount = await program.account.dataZeroCopy.fetch(myPda);
+    assert.ok(myPdaAccount.data === 1234);
+    assert.ok((myPdaAccount.bump = bump));
+  });
 });

+ 1 - 0
examples/tutorial/basic-5/programs/basic-5/src/lib.rs

@@ -49,6 +49,7 @@ pub struct Mint {
 }
 
 #[associated]
+#[derive(Default)]
 pub struct Token {
     pub amount: u32,
     pub authority: Pubkey,

+ 1 - 0
examples/zero-copy/programs/zero-copy/src/lib.rs

@@ -175,6 +175,7 @@ pub struct Foo {
 }
 
 #[associated(zero_copy)]
+#[derive(Default)]
 pub struct Bar {
     pub authority: Pubkey,
     pub data: u64,

+ 0 - 1
lang/attribute/account/src/lib.rs

@@ -238,7 +238,6 @@ pub fn associated(
     let args: proc_macro2::TokenStream = args.into();
     proc_macro::TokenStream::from(quote! {
         #[anchor_lang::account(#args)]
-        #[derive(Default)]
         #account_strct
 
         impl anchor_lang::Bump for #account_name {

+ 1 - 1
lang/derive/accounts/src/lib.rs

@@ -53,7 +53,7 @@ use syn::parse_macro_input;
 /// | `#[account(associated = <target>, with? = <target>, payer? = <target>, space? = "<literal>")]` | On `ProgramAccount` | Whe `init` is provided, creates an associated program account at a program derived address. `associated` is the SOL address to create the account for. `with` is an optional association, for example, a `Mint` account in the SPL token program. `payer` is an optional account to pay for the account creation, defaulting to the `associated` target if none is given. `space` is an optional literal specifying how large the account is, defaulting to the account's serialized `Default::default` size (+ 8 for the account discriminator) if none is given. When creating an associated account, a `rent` `Sysvar` and `system_program` `AccountInfo` must be present in the `Accounts` struct. When `init` is not provided, then ensures the given associated account has the expected address, defined by the program and the given seeds. |
 // TODO: How do we make the markdown render correctly without putting everything
 //       on absurdly long lines?
-#[proc_macro_derive(Accounts, attributes(account))]
+#[proc_macro_derive(Accounts, attributes(account, instruction))]
 pub fn derive_anchor_deserialize(item: TokenStream) -> TokenStream {
     parse_macro_input!(item as anchor_syn::AccountsStruct)
         .to_token_stream()

+ 1 - 0
lang/src/account_info.rs

@@ -10,6 +10,7 @@ impl<'info> Accounts<'info> for AccountInfo<'info> {
     fn try_accounts(
         _program_id: &Pubkey,
         accounts: &mut &[AccountInfo<'info>],
+        _ix_data: &[u8],
     ) -> Result<Self, ProgramError> {
         if accounts.is_empty() {
             return Err(ErrorCode::AccountNotEnoughKeys.into());

+ 2 - 1
lang/src/boxed.rs

@@ -10,8 +10,9 @@ impl<'info, T: Accounts<'info>> Accounts<'info> for Box<T> {
     fn try_accounts(
         program_id: &Pubkey,
         accounts: &mut &[AccountInfo<'info>],
+        ix_data: &[u8],
     ) -> Result<Self, ProgramError> {
-        T::try_accounts(program_id, accounts).map(Box::new)
+        T::try_accounts(program_id, accounts, ix_data).map(Box::new)
     }
 }
 

+ 1 - 0
lang/src/cpi_account.rs

@@ -50,6 +50,7 @@ where
     fn try_accounts(
         _program_id: &Pubkey,
         accounts: &mut &[AccountInfo<'info>],
+        _ix_data: &[u8],
     ) -> Result<Self, ProgramError> {
         if accounts.is_empty() {
             return Err(ErrorCode::AccountNotEnoughKeys.into());

+ 1 - 0
lang/src/cpi_state.rs

@@ -66,6 +66,7 @@ where
     fn try_accounts(
         _program_id: &Pubkey,
         accounts: &mut &[AccountInfo<'info>],
+        _ix_data: &[u8],
     ) -> Result<Self, ProgramError> {
         if accounts.is_empty() {
             return Err(ErrorCode::AccountNotEnoughKeys.into());

+ 1 - 0
lang/src/lib.rs

@@ -87,6 +87,7 @@ pub trait Accounts<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized {
     fn try_accounts(
         program_id: &Pubkey,
         accounts: &mut &[AccountInfo<'info>],
+        ix_data: &[u8],
     ) -> Result<Self, ProgramError>;
 }
 

+ 1 - 0
lang/src/loader.rs

@@ -132,6 +132,7 @@ impl<'info, T: ZeroCopy> Accounts<'info> for Loader<'info, T> {
     fn try_accounts(
         program_id: &Pubkey,
         accounts: &mut &[AccountInfo<'info>],
+        _ix_data: &[u8],
     ) -> Result<Self, ProgramError> {
         if accounts.is_empty() {
             return Err(ErrorCode::AccountNotEnoughKeys.into());

+ 1 - 0
lang/src/program_account.rs

@@ -71,6 +71,7 @@ where
     fn try_accounts(
         program_id: &Pubkey,
         accounts: &mut &[AccountInfo<'info>],
+        _ix_data: &[u8],
     ) -> Result<Self, ProgramError> {
         if accounts.is_empty() {
             return Err(ErrorCode::AccountNotEnoughKeys.into());

+ 1 - 0
lang/src/state.rs

@@ -58,6 +58,7 @@ where
     fn try_accounts(
         program_id: &Pubkey,
         accounts: &mut &[AccountInfo<'info>],
+        _ix_data: &[u8],
     ) -> Result<Self, ProgramError> {
         if accounts.is_empty() {
             return Err(ErrorCode::AccountNotEnoughKeys.into());

+ 1 - 0
lang/src/sysvar.rs

@@ -37,6 +37,7 @@ impl<'info, T: solana_program::sysvar::Sysvar> Accounts<'info> for Sysvar<'info,
     fn try_accounts(
         _program_id: &Pubkey,
         accounts: &mut &[AccountInfo<'info>],
+        _ix_data: &[u8],
     ) -> Result<Self, ProgramError> {
         if accounts.is_empty() {
             return Err(ErrorCode::AccountNotEnoughKeys.into());

+ 132 - 103
lang/syn/src/codegen/accounts/constraints.rs

@@ -1,10 +1,11 @@
 use crate::{
     CompositeField, Constraint, ConstraintAssociatedGroup, ConstraintBelongsTo, ConstraintClose,
     ConstraintExecutable, ConstraintGroup, ConstraintInit, ConstraintLiteral, ConstraintMut,
-    ConstraintOwner, ConstraintRaw, ConstraintRentExempt, ConstraintSeeds, ConstraintSigner,
+    ConstraintOwner, ConstraintRaw, ConstraintRentExempt, ConstraintSeedsGroup, ConstraintSigner,
     ConstraintState, Field, Ty,
 };
 use quote::quote;
+use syn::LitInt;
 
 pub fn generate(f: &Field) -> proc_macro2::TokenStream {
     let checks: Vec<proc_macro2::TokenStream> = linearize(&f.constraints)
@@ -58,6 +59,9 @@ pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
     if let Some(c) = associated {
         constraints.push(Constraint::AssociatedGroup(c));
     }
+    if let Some(c) = seeds {
+        constraints.push(Constraint::Seeds(c));
+    }
     if let Some(c) = init {
         constraints.push(Constraint::Init(c));
     }
@@ -86,9 +90,6 @@ pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
     if let Some(c) = rent_exempt {
         constraints.push(Constraint::RentExempt(c));
     }
-    if let Some(c) = seeds {
-        constraints.push(Constraint::Seeds(c));
-    }
     if let Some(c) = executable {
         constraints.push(Constraint::Executable(c));
     }
@@ -241,47 +242,45 @@ pub fn generate_constraint_rent_exempt(
     }
 }
 
-pub fn generate_constraint_seeds(f: &Field, c: &ConstraintSeeds) -> proc_macro2::TokenStream {
-    let name = &f.ident;
-    let seeds = &c.seeds;
-    quote! {
-        let program_signer = Pubkey::create_program_address(
-            &[#seeds],
-            program_id,
-        ).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?;
-        if #name.to_account_info().key != &program_signer {
-            return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
-        }
+pub fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2::TokenStream {
+    if c.is_init {
+        generate_constraint_seeds_init(f, c)
+    } else {
+        generate_constraint_seeds_address(f, c)
     }
 }
 
-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::__private::ErrorCode::ConstraintExecutable.into());
+fn generate_constraint_seeds_init(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2::TokenStream {
+    let payer = {
+        let p = &c.payer;
+        quote! {
+            let payer = #p.to_account_info();
         }
-    }
+    };
+    let seeds_with_nonce = {
+        let s = &c.seeds;
+        let seeds_constraint = generate_constraint_seeds_address(f, c);
+        quote! {
+            #seeds_constraint
+            let seeds = [#s];
+        }
+    };
+    generate_pda(f, seeds_with_nonce, payer, &c.space, false)
 }
 
-pub fn generate_constraint_state(f: &Field, c: &ConstraintState) -> proc_macro2::TokenStream {
-    let program_target = c.program_target.clone();
-    let ident = &f.ident;
-    let account_ty = match &f.ty {
-        Ty::CpiState(ty) => &ty.account_ident,
-        _ => panic!("Invalid state constraint"),
-    };
+fn generate_constraint_seeds_address(
+    f: &Field,
+    c: &ConstraintSeedsGroup,
+) -> proc_macro2::TokenStream {
+    let name = &f.ident;
+    let seeds = &c.seeds;
     quote! {
-        // Checks the given state account is the canonical state account for
-        // the target program.
-        if #ident.to_account_info().key != &anchor_lang::CpiState::<#account_ty>::address(#program_target.to_account_info().key) {
-            return Err(anchor_lang::__private::ErrorCode::ConstraintState.into());
-        }
-        if #ident.to_account_info().owner != #program_target.to_account_info().key {
-            return Err(anchor_lang::__private::ErrorCode::ConstraintState.into());
+        let __program_signer = Pubkey::create_program_address(
+            &[#seeds],
+            program_id,
+        ).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?;
+        if #name.to_account_info().key != &__program_signer {
+            return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
         }
     }
 }
@@ -302,14 +301,55 @@ pub fn generate_constraint_associated_init(
     c: &ConstraintAssociatedGroup,
 ) -> proc_macro2::TokenStream {
     let associated_target = c.associated_target.clone();
+    let payer = match &c.payer {
+        None => quote! {
+            let payer = #associated_target.to_account_info();
+        },
+        Some(p) => quote! {
+            let payer = #p.to_account_info();
+        },
+    };
+    let associated_seeds_constraint = generate_constraint_associated_seeds(f, c);
+    let seeds_with_nonce = match c.associated_seeds.len() {
+        0 => quote! {
+            #associated_seeds_constraint
+            let seeds = [
+                &b"anchor"[..],
+                #associated_target.to_account_info().key.as_ref(),
+                &[nonce],
+            ];
+        },
+        _ => {
+            let seeds = to_seeds_tts(&c.associated_seeds);
+            quote! {
+                #associated_seeds_constraint
+                let seeds = [
+                    &b"anchor"[..],
+                    #associated_target.to_account_info().key.as_ref(),
+                    #seeds
+                    &[nonce],
+                ];
+            }
+        }
+    };
+    generate_pda(f, seeds_with_nonce, payer, &c.space, true)
+}
+
+pub fn generate_pda(
+    f: &Field,
+    seeds_with_nonce: proc_macro2::TokenStream,
+    payer: proc_macro2::TokenStream,
+    space: &Option<LitInt>,
+    assign_nonce: bool,
+) -> proc_macro2::TokenStream {
     let field = &f.ident;
     let (account_ty, is_zero_copy) = match &f.ty {
         Ty::ProgramAccount(ty) => (&ty.account_ident, false),
         Ty::Loader(ty) => (&ty.account_ident, true),
-        _ => panic!("Invalid associated constraint"),
+        _ => panic!("Invalid type for initializing a program derived address"),
     };
 
-    let space = match &c.space {
+    let space = match space {
         // If no explicit space param was given, serialize the type to bytes
         // and take the length (with +8 for the discriminator.)
         None => match is_zero_copy {
@@ -330,38 +370,6 @@ pub fn generate_constraint_associated_init(
         },
     };
 
-    let payer = match &c.payer {
-        None => quote! {
-            let payer = #associated_target.to_account_info();
-        },
-        Some(p) => quote! {
-            let payer = #p.to_account_info();
-        },
-    };
-
-    let associated_pubkey_and_nonce = generate_associated_pubkey(f, c);
-
-    let seeds_with_nonce = match c.associated_seeds.len() {
-        0 => quote! {
-            [
-                &b"anchor"[..],
-                #associated_target.to_account_info().key.as_ref(),
-                &[nonce],
-            ]
-        },
-        _ => {
-            let seeds = to_seeds_tts(&c.associated_seeds);
-            quote! {
-                [
-                    &b"anchor"[..],
-                    #associated_target.to_account_info().key.as_ref(),
-                    #seeds
-                    &[nonce],
-                ]
-            }
-        }
-    };
-
     let account_wrapper_ty = match is_zero_copy {
         false => quote! {
             anchor_lang::ProgramAccount
@@ -370,11 +378,16 @@ pub fn generate_constraint_associated_init(
             anchor_lang::Loader
         },
     };
-    let nonce_assignment = match is_zero_copy {
+    let nonce_assignment = match assign_nonce {
         false => quote! {},
-        // Zero copy is not deserialized, so the data must be lazy loaded.
-        true => quote! {
-            .load_init()?
+        true => match is_zero_copy {
+            false => quote! {
+                pa.__nonce = nonce;
+            },
+            // Zero copy is not deserialized, so the data must be lazy loaded.
+            true => quote! {
+                pa.load_init()?.__nonce = nonce;
+            },
         },
     };
 
@@ -383,29 +396,24 @@ pub fn generate_constraint_associated_init(
             #space
             #payer
 
-            #associated_pubkey_and_nonce
-
-            if &__associated_field != #field.key {
-                return Err(anchor_lang::__private::ErrorCode::ConstraintAssociatedInit.into());
-            }
             let lamports = rent.minimum_balance(space);
             let ix = anchor_lang::solana_program::system_instruction::create_account(
-                payer.key,
-                #field.key,
+                payer.to_account_info().key,
+                #field.to_account_info().key,
                 lamports,
                 space as u64,
                 program_id,
             );
 
-            let seeds = #seeds_with_nonce;
+            #seeds_with_nonce
             let signer = &[&seeds[..]];
             anchor_lang::solana_program::program::invoke_signed(
                 &ix,
                 &[
 
-                    #field.clone(),
-                    payer.clone(),
-                    system_program.clone(),
+                    #field.to_account_info(),
+                    payer.to_account_info(),
+                    system_program.to_account_info(),
                 ],
                 signer,
             ).map_err(|e| {
@@ -415,9 +423,9 @@ pub fn generate_constraint_associated_init(
             // For now, we assume all accounts created with the `associated`
             // attribute have a `nonce` field in their account.
             let mut pa: #account_wrapper_ty<#account_ty> = #account_wrapper_ty::try_from_init(
-                &#field,
+                &#field.to_account_info(),
             )?;
-            pa#nonce_assignment.__nonce = nonce;
+            #nonce_assignment
             pa
         };
     }
@@ -427,20 +435,7 @@ pub fn generate_constraint_associated_seeds(
     f: &Field,
     c: &ConstraintAssociatedGroup,
 ) -> proc_macro2::TokenStream {
-    let generated_associated_pubkey_and_nonce = generate_associated_pubkey(f, c);
-    let name = &f.ident;
-    quote! {
-        #generated_associated_pubkey_and_nonce
-        if #name.to_account_info().key != &__associated_field {
-            return Err(anchor_lang::__private::ErrorCode::ConstraintAssociated.into());
-        }
-    }
-}
-
-pub fn generate_associated_pubkey(
-    _f: &Field,
-    c: &ConstraintAssociatedGroup,
-) -> proc_macro2::TokenStream {
+    let field = &f.ident;
     let associated_target = c.associated_target.clone();
     let seeds_no_nonce = match c.associated_seeds.len() {
         0 => quote! {
@@ -465,6 +460,40 @@ pub fn generate_associated_pubkey(
             &#seeds_no_nonce,
             program_id,
         );
+        if &__associated_field != #field.to_account_info().key {
+            return Err(anchor_lang::__private::ErrorCode::ConstraintAssociatedInit.into());
+        }
+    }
+}
+
+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::__private::ErrorCode::ConstraintExecutable.into());
+        }
+    }
+}
+
+pub fn generate_constraint_state(f: &Field, c: &ConstraintState) -> proc_macro2::TokenStream {
+    let program_target = c.program_target.clone();
+    let ident = &f.ident;
+    let account_ty = match &f.ty {
+        Ty::CpiState(ty) => &ty.account_ident,
+        _ => panic!("Invalid state constraint"),
+    };
+    quote! {
+        // Checks the given state account is the canonical state account for
+        // the target program.
+        if #ident.to_account_info().key != &anchor_lang::CpiState::<#account_ty>::address(#program_target.to_account_info().key) {
+            return Err(anchor_lang::__private::ErrorCode::ConstraintState.into());
+        }
+        if #ident.to_account_info().owner != #program_target.to_account_info().key {
+            return Err(anchor_lang::__private::ErrorCode::ConstraintState.into());
+        }
     }
 }
 

+ 112 - 64
lang/syn/src/codegen/accounts/try_accounts.rs

@@ -2,19 +2,13 @@ use crate::codegen::accounts::{constraints, generics};
 use crate::{AccountField, AccountsStruct, Field, SysvarTy, Ty};
 use proc_macro2::TokenStream;
 use quote::quote;
+use syn::Expr;
 
 // Generates the `Accounts` trait implementation.
 pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
     let name = &accs.ident;
     let (combined_generics, trait_generics, strct_generics) = generics(accs);
 
-    // All fields without an `#[account(associated)]` attribute.
-    let non_associated_fields: Vec<&AccountField> = accs
-        .fields
-        .iter()
-        .filter(|af| !is_associated_init(af))
-        .collect();
-
     // Deserialization for each field
     let deser_fields: Vec<proc_macro2::TokenStream> = accs
         .fields
@@ -27,14 +21,14 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
                     quote! {
                         #[cfg(feature = "anchor-debug")]
                         ::solana_program::log::sol_log(stringify!(#name));
-                        let #name: #ty = anchor_lang::Accounts::try_accounts(program_id, accounts)?;
+                        let #name: #ty = anchor_lang::Accounts::try_accounts(program_id, accounts, ix_data)?;
                     }
                 }
                 AccountField::Field(f) => {
                     // Associated fields are *first* deserialized into
                     // AccountInfos, and then later deserialized into
                     // ProgramAccounts in the "constraint check" phase.
-                    if is_associated_init(af) {
+                    if is_pda_init(af) {
                         let name = &f.ident;
                         quote!{
                             let #name = &accounts[0];
@@ -46,7 +40,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
                             false => quote! {
                                 #[cfg(feature = "anchor-debug")]
                                 ::solana_program::log::sol_log(stringify!(#name));
-                                let #name = anchor_lang::Accounts::try_accounts(program_id, accounts)?;
+                                let #name = anchor_lang::Accounts::try_accounts(program_id, accounts, ix_data)?;
                             },
                             true => quote! {
                                 #[cfg(feature = "anchor-debug")]
@@ -60,81 +54,76 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
         })
         .collect();
 
-    // Deserialization for each *associated* field. This must be after
-    // the deser_fields.
-    let deser_associated_fields: Vec<proc_macro2::TokenStream> = accs
-        .fields
-        .iter()
-        .filter_map(|af| match af {
-            AccountField::CompositeField(_s) => None,
-            AccountField::Field(f) => match is_associated_init(af) {
-                false => None,
-                true => Some(f),
-            },
-        })
-        .map(|field: &Field| constraints::generate(field))
-        .collect();
-
-    // Constraint checks for each account fields.
-    let access_checks: Vec<proc_macro2::TokenStream> = non_associated_fields
-        .iter()
-        .map(|af: &&AccountField| match af {
-            AccountField::Field(f) => constraints::generate(f),
-            AccountField::CompositeField(s) => constraints::generate_composite(s),
-        })
-        .collect();
+    let constraints = generate_constraints(accs);
+    let accounts_instance = generate_accounts_instance(accs);
 
-    // Each field in the final deserialized accounts struct.
-    let return_tys: Vec<proc_macro2::TokenStream> = accs
-        .fields
-        .iter()
-        .map(|f: &AccountField| {
-            let name = match f {
-                AccountField::CompositeField(s) => &s.ident,
-                AccountField::Field(f) => &f.ident,
-            };
+    let ix_de = match &accs.instruction_api {
+        None => quote! {},
+        Some(ix_api) => {
+            let strct_inner = &ix_api;
+            let field_names: Vec<proc_macro2::TokenStream> = ix_api
+                .iter()
+                .map(|expr: &Expr| match expr {
+                    Expr::Type(expr_type) => {
+                        let field = &expr_type.expr;
+                        quote! {
+                            #field
+                        }
+                    }
+                    _ => panic!("Invalid instruction declaration"),
+                })
+                .collect();
             quote! {
-                #name
+                let mut ix_data = ix_data;
+                #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
+                struct __Args {
+                    #strct_inner
+                }
+                let __Args {
+                    #(#field_names),*
+                } = __Args::deserialize(&mut ix_data)
+                    .map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
             }
-        })
-        .collect();
+        }
+    };
+
     quote! {
         impl#combined_generics anchor_lang::Accounts#trait_generics for #name#strct_generics {
             #[inline(never)]
             fn try_accounts(
                 program_id: &anchor_lang::solana_program::pubkey::Pubkey,
                 accounts: &mut &[anchor_lang::solana_program::account_info::AccountInfo<'info>],
+                ix_data: &[u8],
             ) -> std::result::Result<Self, anchor_lang::solana_program::program_error::ProgramError> {
+                // Deserialize instruction, if declared.
+                #ix_de
                 // Deserialize each account.
                 #(#deser_fields)*
-                // Deserialize each associated account.
-                //
-                // Associated accounts are treated specially, because the fields
-                // do deserialization + constraint checks in a single go,
-                // whereas all other fields, i.e. the `deser_fields`, first
-                // deserialize, and then do constraint checks.
-                #(#deser_associated_fields)*
-                // Perform constraint checks on each account.
-                #(#access_checks)*
+                // Execute accounts constraints.
+                #constraints
                 // Success. Return the validated accounts.
-                Ok(#name {
-                    #(#return_tys),*
-                })
+                Ok(#accounts_instance)
             }
         }
     }
 }
 
 // Returns true if the given AccountField has an associated init constraint.
-fn is_associated_init(af: &AccountField) -> bool {
+fn is_pda_init(af: &AccountField) -> bool {
     match af {
         AccountField::CompositeField(_s) => false,
-        AccountField::Field(f) => f
-            .constraints
-            .associated
-            .as_ref()
-            .map(|f| f.is_init)
-            .unwrap_or(false),
+        AccountField::Field(f) => {
+            f.constraints
+                .associated
+                .as_ref()
+                .map(|f| f.is_init)
+                .unwrap_or(false)
+                || f.constraints
+                    .seeds
+                    .as_ref()
+                    .map(|f| f.is_init)
+                    .unwrap_or(false)
+        }
     }
 }
 
@@ -196,3 +185,62 @@ fn typed_ident(field: &Field) -> TokenStream {
         #name: #ty
     }
 }
+
+pub fn generate_constraints(accs: &AccountsStruct) -> proc_macro2::TokenStream {
+    // All fields without an `#[account(associated)]` attribute.
+    let non_associated_fields: Vec<&AccountField> =
+        accs.fields.iter().filter(|af| !is_pda_init(af)).collect();
+
+    // Deserialization for each *associated* field. This must be after
+    // the inital extraction from the accounts slice and before access_checks.
+    let init_associated_fields: Vec<proc_macro2::TokenStream> = accs
+        .fields
+        .iter()
+        .filter_map(|af| match af {
+            AccountField::CompositeField(_s) => None,
+            AccountField::Field(f) => match is_pda_init(af) {
+                false => None,
+                true => Some(f),
+            },
+        })
+        .map(constraints::generate)
+        .collect();
+
+    // Constraint checks for each account fields.
+    let access_checks: Vec<proc_macro2::TokenStream> = non_associated_fields
+        .iter()
+        .map(|af: &&AccountField| match af {
+            AccountField::Field(f) => constraints::generate(f),
+            AccountField::CompositeField(s) => constraints::generate_composite(s),
+        })
+        .collect();
+
+    quote! {
+        #(#init_associated_fields)*
+        #(#access_checks)*
+    }
+}
+
+pub fn generate_accounts_instance(accs: &AccountsStruct) -> proc_macro2::TokenStream {
+    let name = &accs.ident;
+    // Each field in the final deserialized accounts struct.
+    let return_tys: Vec<proc_macro2::TokenStream> = accs
+        .fields
+        .iter()
+        .map(|f: &AccountField| {
+            let name = match f {
+                AccountField::CompositeField(s) => &s.ident,
+                AccountField::Field(f) => &f.ident,
+            };
+            quote! {
+                #name
+            }
+        })
+        .collect();
+
+    quote! {
+        #name {
+            #(#return_tys),*
+        }
+    }
+}

+ 31 - 85
lang/syn/src/codegen/program/dispatch.rs

@@ -1,6 +1,5 @@
 use crate::codegen::program::common::*;
-use crate::{Program, State};
-use heck::CamelCase;
+use crate::Program;
 use quote::quote;
 
 pub fn generate(program: &Program) -> proc_macro2::TokenStream {
@@ -10,19 +9,16 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
         Some(state) => match state.ctor_and_anchor.is_some() {
             false => quote! {},
             true => {
-                let variant_arm = generate_ctor_variant(state);
-                let ctor_args = generate_ctor_args(state);
-                let ix_name: proc_macro2::TokenStream =
-                    generate_ctor_variant_name().parse().unwrap();
                 let sighash_arr = sighash_ctor();
                 let sighash_tts: proc_macro2::TokenStream =
                     format!("{:?}", sighash_arr).parse().unwrap();
                 quote! {
                     #sighash_tts => {
-                        let ix = instruction::state::#ix_name::deserialize(&mut ix_data)
-                            .map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
-                        let instruction::state::#variant_arm = ix;
-                        __private::__state::__ctor(program_id, accounts, #(#ctor_args),*)
+                        __private::__state::__ctor(
+                            program_id,
+                            accounts,
+                            ix_data,
+                        )
                     }
                 }
             }
@@ -39,23 +35,19 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                 methods
                     .iter()
                     .map(|ix: &crate::StateIx| {
-                        let ix_arg_names: Vec<&syn::Ident> =
-                            ix.args.iter().map(|arg| &arg.name).collect();
                         let name = &ix.raw_method.sig.ident.to_string();
                         let ix_method_name: proc_macro2::TokenStream =
-                        { format!("__{}", name).parse().unwrap() };
-                        let variant_arm =
-                            generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args);
-                        let ix_name = generate_ix_variant_name(ix.raw_method.sig.ident.to_string());
+                            { format!("__{}", name).parse().unwrap() };
                         let sighash_arr = sighash(SIGHASH_STATE_NAMESPACE, &name);
                         let sighash_tts: proc_macro2::TokenStream =
                             format!("{:?}", sighash_arr).parse().unwrap();
                         quote! {
                             #sighash_tts => {
-                                let ix = instruction::state::#ix_name::deserialize(&mut ix_data)
-                                    .map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
-                                let instruction::state::#variant_arm = ix;
-                                __private::__state::#ix_method_name(program_id, accounts, #(#ix_arg_names),*)
+                                __private::__state::#ix_method_name(
+                                    program_id,
+                                    accounts,
+                                    ix_data,
+                                )
                             }
                         }
                     })
@@ -78,42 +70,19 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                             .methods
                             .iter()
                             .map(|m: &crate::StateIx| {
-                                let ix_arg_names: Vec<&syn::Ident> =
-                                    m.args.iter().map(|arg| &arg.name).collect();
-                                let name = &m.raw_method.sig.ident.to_string();
-                                let ix_name: proc_macro2::TokenStream =  format!("__{}_{}", iface.trait_name, name).parse().unwrap();
-                                let raw_args: Vec<&syn::PatType> = m
-                                    .args
-                                    .iter()
-                                    .map(|arg: &crate::IxArg| &arg.raw_arg)
-                                    .collect();
                                 let sighash_arr = sighash(&iface.trait_name, &m.ident.to_string());
                                 let sighash_tts: proc_macro2::TokenStream =
                                     format!("{:?}", sighash_arr).parse().unwrap();
-                                let args_struct = {
-                                    if m.args.is_empty() {
-                                        quote! {
-                                            #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
-                                            struct Args;
-                                        }
-                                    } else {
-                                        quote! {
-                                            #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
-                                            struct Args {
-                                                #(#raw_args),*
-                                            }
-                                        }
-                                    }
-                                };
+                                let name = &m.raw_method.sig.ident.to_string();
+                                let ix_method_name: proc_macro2::TokenStream =
+                                    format!("__{}_{}", iface.trait_name, name).parse().unwrap();
                                 quote! {
                                     #sighash_tts => {
-                                        #args_struct
-                                        let ix = Args::deserialize(&mut ix_data)
-                                            .map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
-                                        let Args {
-                                            #(#ix_arg_names),*
-                                        } = ix;
-                                        __private::__interface::#ix_name(program_id, accounts, #(#ix_arg_names),*)
+                                        __private::__interface::#ix_method_name(
+                                            program_id,
+                                            accounts,
+                                            ix_data,
+                                        )
                                     }
                                 }
                             })
@@ -121,7 +90,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                     })
                     .collect()
             })
-            .unwrap_or_default()
+            .unwrap_or_default(),
     };
 
     // Dispatch all global instructions.
@@ -129,19 +98,17 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
         .ixs
         .iter()
         .map(|ix| {
-            let ix_arg_names: Vec<&syn::Ident> = ix.args.iter().map(|arg| &arg.name).collect();
             let ix_method_name = &ix.raw_method.sig.ident;
-            let ix_name = generate_ix_variant_name(ix.raw_method.sig.ident.to_string());
             let sighash_arr = sighash(SIGHASH_GLOBAL_NAMESPACE, &ix_method_name.to_string());
             let sighash_tts: proc_macro2::TokenStream =
                 format!("{:?}", sighash_arr).parse().unwrap();
-            let variant_arm = generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args);
             quote! {
                 #sighash_tts => {
-                    let ix = instruction::#ix_name::deserialize(&mut ix_data)
-                        .map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
-                    let instruction::#variant_arm = ix;
-                    __private::__global::#ix_method_name(program_id, accounts, #(#ix_arg_names),*)
+                    __private::__global::#ix_method_name(
+                        program_id,
+                        accounts,
+                        ix_data,
+                    )
                 }
             }
         })
@@ -171,7 +138,11 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
             // instruction, injected into all Anchor programs.
             if cfg!(not(feature = "no-idl")) {
                 if sighash == anchor_lang::idl::IDL_IX_TAG.to_le_bytes() {
-                    return __private::__idl::__idl_dispatch(program_id, accounts, &ix_data);
+                    return __private::__idl::__idl_dispatch(
+                        program_id,
+                        accounts,
+                        &ix_data,
+                    );
                 }
             }
 
@@ -188,28 +159,3 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
         }
     }
 }
-
-fn generate_ctor_variant_name() -> String {
-    "New".to_string()
-}
-
-fn generate_ctor_variant(state: &State) -> proc_macro2::TokenStream {
-    let ctor_args = generate_ctor_args(state);
-    let ctor_variant_name: proc_macro2::TokenStream = generate_ctor_variant_name().parse().unwrap();
-    if ctor_args.is_empty() {
-        quote! {
-            #ctor_variant_name
-        }
-    } else {
-        quote! {
-            #ctor_variant_name {
-                #(#ctor_args),*
-            }
-        }
-    }
-}
-
-fn generate_ix_variant_name(name: String) -> proc_macro2::TokenStream {
-    let n = name.to_camel_case();
-    n.parse().unwrap()
-}

+ 164 - 55
lang/syn/src/codegen/program/handlers.rs

@@ -1,5 +1,6 @@
 use crate::codegen::program::common::*;
-use crate::Program;
+use crate::{Program, State};
+use heck::CamelCase;
 use quote::quote;
 
 // Generate non-inlined wrappers for each instruction handler, since Solana's
@@ -24,27 +25,32 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
 
                 match ix {
                     anchor_lang::idl::IdlInstruction::Create { data_len } => {
-                        let mut accounts = anchor_lang::idl::IdlCreateAccounts::try_accounts(program_id, &mut accounts)?;
+                        let mut accounts =
+                            anchor_lang::idl::IdlCreateAccounts::try_accounts(program_id, &mut accounts, &[])?;
                         __idl_create_account(program_id, &mut accounts, data_len)?;
                         accounts.exit(program_id)?;
                     },
                     anchor_lang::idl::IdlInstruction::CreateBuffer => {
-                        let mut accounts = anchor_lang::idl::IdlCreateBuffer::try_accounts(program_id, &mut accounts)?;
+                        let mut accounts =
+                            anchor_lang::idl::IdlCreateBuffer::try_accounts(program_id, &mut accounts, &[])?;
                         __idl_create_buffer(program_id, &mut accounts)?;
                         accounts.exit(program_id)?;
                     },
                     anchor_lang::idl::IdlInstruction::Write { data } => {
-                        let mut accounts = anchor_lang::idl::IdlAccounts::try_accounts(program_id, &mut accounts)?;
+                        let mut accounts =
+                            anchor_lang::idl::IdlAccounts::try_accounts(program_id, &mut accounts, &[])?;
                         __idl_write(program_id, &mut accounts, data)?;
                         accounts.exit(program_id)?;
                     },
                     anchor_lang::idl::IdlInstruction::SetAuthority { new_authority } => {
-                        let mut accounts = anchor_lang::idl::IdlAccounts::try_accounts(program_id, &mut accounts)?;
+                        let mut accounts =
+                            anchor_lang::idl::IdlAccounts::try_accounts(program_id, &mut accounts, &[])?;
                         __idl_set_authority(program_id, &mut accounts, new_authority)?;
                         accounts.exit(program_id)?;
                     },
                     anchor_lang::idl::IdlInstruction::SetBuffer => {
-                        let mut accounts = anchor_lang::idl::IdlSetBuffer::try_accounts(program_id, &mut accounts)?;
+                        let mut accounts =
+                            anchor_lang::idl::IdlSetBuffer::try_accounts(program_id, &mut accounts, &[])?;
                         __idl_set_buffer(program_id, &mut accounts)?;
                         accounts.exit(program_id)?;
                     },
@@ -167,21 +173,27 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
         Some(state) => match state.ctor_and_anchor.as_ref() {
             None => quote! {},
             Some((_ctor, anchor_ident)) => {
-                let ctor_typed_args = generate_ctor_typed_args(state);
                 let ctor_untyped_args = generate_ctor_args(state);
                 let name = &state.strct.ident;
                 let mod_name = &program.name;
+                let variant_arm = generate_ctor_variant(state);
+                let ix_name: proc_macro2::TokenStream =
+                    generate_ctor_variant_name().parse().unwrap();
                 if state.is_zero_copy {
                     quote! {
                         // One time state account initializer. Will faill on subsequent
                         // invocations.
                         #[inline(never)]
-                        pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], #(#ctor_typed_args),*) -> ProgramResult {
-                            let mut remaining_accounts: &[AccountInfo] = accounts;
+                        pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8]) -> ProgramResult {
+                            // Deserialize instruction data.
+                            let ix = instruction::state::#ix_name::deserialize(&mut &ix_data[..])
+                                .map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
+                            let instruction::state::#variant_arm = ix;
 
                             // Deserialize accounts.
-                            let ctor_accounts = anchor_lang::__private::Ctor::try_accounts(program_id, &mut remaining_accounts)?;
-                            let mut ctor_user_def_accounts = #anchor_ident::try_accounts(program_id, &mut remaining_accounts)?;
+                            let mut remaining_accounts: &[AccountInfo] = accounts;
+                            let ctor_accounts = anchor_lang::__private::Ctor::try_accounts(program_id, &mut remaining_accounts, &[])?;
+                            let mut ctor_user_def_accounts = #anchor_ident::try_accounts(program_id, &mut remaining_accounts, ix_data)?;
 
                             // Create the solana account for the ctor data.
                             let from = ctor_accounts.from.key;
@@ -242,12 +254,16 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                         // One time state account initializer. Will faill on subsequent
                         // invocations.
                         #[inline(never)]
-                        pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], #(#ctor_typed_args),*) -> ProgramResult {
-                            let mut remaining_accounts: &[AccountInfo] = accounts;
+                        pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8]) -> ProgramResult {
+                            // Deserialize instruction data.
+                            let ix = instruction::state::#ix_name::deserialize(&mut &ix_data[..])
+                                .map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
+                            let instruction::state::#variant_arm = ix;
 
                             // Deserialize accounts.
-                            let ctor_accounts = anchor_lang::__private::Ctor::try_accounts(program_id, &mut remaining_accounts)?;
-                            let mut ctor_user_def_accounts = #anchor_ident::try_accounts(program_id, &mut remaining_accounts)?;
+                            let mut remaining_accounts: &[AccountInfo] = accounts;
+                            let ctor_accounts = anchor_lang::__private::Ctor::try_accounts(program_id, &mut remaining_accounts, &[])?;
+                            let mut ctor_user_def_accounts = #anchor_ident::try_accounts(program_id, &mut remaining_accounts, ix_data)?;
 
                             // Invoke the ctor.
                             let instance = #mod_name::#name::new(
@@ -313,46 +329,56 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                 methods
                     .iter()
                     .map(|ix| {
-                        let ix_params: Vec<_> = ix.args.iter().map(|arg| &arg.raw_arg).collect();
                         let ix_arg_names: Vec<&syn::Ident> =
                             ix.args.iter().map(|arg| &arg.name).collect();
-                        let private_ix_name: proc_macro2::TokenStream = {
+                        let private_ix_method_name: proc_macro2::TokenStream = {
                             let n = format!("__{}", &ix.raw_method.sig.ident.to_string());
                             n.parse().unwrap()
                         };
-                        let ix_name = &ix.raw_method.sig.ident;
+                        let ix_method_name = &ix.raw_method.sig.ident;
                         let state_ty: proc_macro2::TokenStream = state.name.parse().unwrap();
                         let anchor_ident = &ix.anchor_ident;
                         let name = &state.strct.ident;
                         let mod_name = &program.name;
 
+                        let variant_arm =
+                            generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args);
+                        let ix_name = generate_ix_variant_name(ix.raw_method.sig.ident.to_string());
+
                         if state.is_zero_copy {
                             quote! {
                                 #[inline(never)]
-                                pub fn #private_ix_name(
+                                pub fn #private_ix_method_name(
                                     program_id: &Pubkey,
                                     accounts: &[AccountInfo],
-                                    #(#ix_params),*
+                                    ix_data: &[u8],
                                 ) -> ProgramResult {
+																		// Deserialize instruction.
+																		let ix = instruction::state::#ix_name::deserialize(&mut &ix_data[..])
+																				.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
+																		let instruction::state::#variant_arm = ix;
+
+																		// Load state.
                                     let mut remaining_accounts: &[AccountInfo] = accounts;
                                     if remaining_accounts.is_empty() {
                                         return Err(anchor_lang::__private::ErrorCode::AccountNotEnoughKeys.into());
                                     }
-
                                     let state_account = &remaining_accounts[0];
                                     let loader: anchor_lang::Loader<#mod_name::#name> = anchor_lang::Loader::try_from(&state_account)?;
                                     remaining_accounts = &remaining_accounts[1..];
 
-                                    // Deserialize the program's execution context.
+                                    // Deserialize accounts.
                                     let mut accounts = #anchor_ident::try_accounts(
                                         program_id,
                                         &mut remaining_accounts,
+                                        ix_data,
                                     )?;
                                     let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
+
                                     // Execute user defined function.
                                     {
                                         let mut state = loader.load_mut()?;
-                                        state.#ix_name(
+                                        state.#ix_method_name(
                                             ctx,
                                             #(#ix_arg_names),*
                                         )?;
@@ -367,35 +393,39 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                         } else {
                             quote! {
                                 #[inline(never)]
-                                pub fn #private_ix_name(
+                                pub fn #private_ix_method_name(
                                     program_id: &Pubkey,
                                     accounts: &[AccountInfo],
-                                    #(#ix_params),*
+                                    ix_data: &[u8],
                                 ) -> ProgramResult {
+																		// Deserialize instruction.
+																		let ix = instruction::state::#ix_name::deserialize(&mut &ix_data[..])
+																				.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
+																		let instruction::state::#variant_arm = ix;
+
+																		// Load state.
                                     let mut remaining_accounts: &[AccountInfo] = accounts;
                                     if remaining_accounts.is_empty() {
                                         return Err(anchor_lang::__private::ErrorCode::AccountNotEnoughKeys.into());
                                     }
-
-                                    // Deserialize the program state account.
                                     let state_account = &remaining_accounts[0];
                                     let mut state: #state_ty = {
                                         let data = state_account.try_borrow_data()?;
                                         let mut sliced: &[u8] = &data;
                                         anchor_lang::AccountDeserialize::try_deserialize(&mut sliced)?
                                     };
-
                                     remaining_accounts = &remaining_accounts[1..];
 
-                                    // Deserialize the program's execution context.
+                                    // Deserialize accounts.
                                     let mut accounts = #anchor_ident::try_accounts(
                                         program_id,
                                         &mut remaining_accounts,
+                                        ix_data,
                                     )?;
                                     let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
 
                                     // Execute user defined function.
-                                    state.#ix_name(
+                                    state.#ix_method_name(
                                         ctx,
                                         #(#ix_arg_names),*
                                     )?;
@@ -431,61 +461,91 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                             .methods
                             .iter()
                             .map(|ix| {
-                                let ix_params: Vec<_> = ix.args.iter().map(|arg| &arg.raw_arg).collect();
+                                if state.is_zero_copy {
+                                    // Easy to implement. Just need to write a test.
+                                    // Feel free to open a PR.
+                                    panic!("Trait implementations not yet implemented for zero copy state structs. Please file an issue.");
+                                }
+
                                 let ix_arg_names: Vec<&syn::Ident> =
                                     ix.args.iter().map(|arg| &arg.name).collect();
-                                let private_ix_name: proc_macro2::TokenStream = {
+                                let private_ix_method_name: proc_macro2::TokenStream = {
                                     let n = format!("__{}_{}", iface.trait_name, &ix.raw_method.sig.ident.to_string());
                                     n.parse().unwrap()
                                 };
-                                let ix_name = &ix.raw_method.sig.ident;
+                                let ix_method_name = &ix.raw_method.sig.ident;
                                 let state_ty: proc_macro2::TokenStream = state.name.parse().unwrap();
                                 let anchor_ident = &ix.anchor_ident;
 
-                                if state.is_zero_copy {
-                                    // Easy to implement. Just need to write a test.
-                                    // Feel free to open a PR.
-                                    panic!("Trait implementations not yet implemented for zero copy state structs. Please file an issue.");
-                                }
+                                let raw_args: Vec<&syn::PatType> = ix
+                                    .args
+                                    .iter()
+                                    .map(|arg: &crate::IxArg| &arg.raw_arg)
+                                    .collect();
+                                let args_struct = {
+                                    if ix.args.is_empty() {
+                                        quote! {
+                                            #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
+                                            struct Args;
+                                        }
+                                    } else {
+                                        quote! {
+                                            #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
+                                            struct Args {
+                                                #(#raw_args),*
+                                            }
+                                        }
+                                    }
+                                };
+
+																let deserialize_instruction = quote! {
+																		#args_struct
+																		let ix = Args::deserialize(&mut &ix_data[..])
+																				.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
+																		let Args {
+																				#(#ix_arg_names),*
+																		} = ix;
+																};
 
                                 if ix.has_receiver {
                                     quote! {
                                         #[inline(never)]
-                                        pub fn #private_ix_name(
+                                        pub fn #private_ix_method_name(
                                             program_id: &Pubkey,
                                             accounts: &[AccountInfo],
-                                            #(#ix_params),*
+                                            ix_data: &[u8],
                                         ) -> ProgramResult {
+																						// Deserialize instruction.
+																						#deserialize_instruction
 
+																						// Deserialize the program state account.
                                             let mut remaining_accounts: &[AccountInfo] = accounts;
                                             if remaining_accounts.is_empty() {
                                                 return Err(anchor_lang::__private::ErrorCode::AccountNotEnoughKeys.into());
                                             }
-
-                                            // Deserialize the program state account.
                                             let state_account = &remaining_accounts[0];
                                             let mut state: #state_ty = {
                                                 let data = state_account.try_borrow_data()?;
                                                 let mut sliced: &[u8] = &data;
                                                 anchor_lang::AccountDeserialize::try_deserialize(&mut sliced)?
                                             };
-
                                             remaining_accounts = &remaining_accounts[1..];
 
-                                            // Deserialize the program's execution context.
+                                            // Deserialize accounts.
                                             let mut accounts = #anchor_ident::try_accounts(
                                                 program_id,
                                                 &mut remaining_accounts,
+                                                ix_data,
                                             )?;
                                             let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
 
                                             // Execute user defined function.
-                                            state.#ix_name(
+                                            state.#ix_method_name(
                                                 ctx,
                                                 #(#ix_arg_names),*
                                             )?;
 
-                                            // Serialize the state and save it to storage.
+                                            // Exit procedures.
                                             accounts.exit(program_id)?;
                                             let mut data = state_account.try_borrow_mut_data()?;
                                             let dst: &mut [u8] = &mut data;
@@ -499,20 +559,29 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                                     let state_name: proc_macro2::TokenStream = state.name.parse().unwrap();
                                     quote! {
                                         #[inline(never)]
-                                        pub fn #private_ix_name(
+                                        pub fn #private_ix_method_name(
                                             program_id: &Pubkey,
                                             accounts: &[AccountInfo],
-                                            #(#ix_params),*
+                                            ix_data: &[u8],
                                         ) -> ProgramResult {
+																						// Deserialize instruction.
+																						#deserialize_instruction
+
+																						// Deserialize accounts.
                                             let mut remaining_accounts: &[AccountInfo] = accounts;
                                             let mut accounts = #anchor_ident::try_accounts(
                                                 program_id,
                                                 &mut remaining_accounts,
+                                                ix_data,
                                             )?;
-                                            #state_name::#ix_name(
+
+																						// Execute user defined function.
+                                            #state_name::#ix_method_name(
                                                 Context::new(program_id, &mut accounts, remaining_accounts),
                                                 #(#ix_arg_names),*
                                             )?;
+
+																						// Exit procedure.
                                             accounts.exit(program_id)
                                         }
                                     }
@@ -528,24 +597,39 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
         .ixs
         .iter()
         .map(|ix| {
-            let ix_params: Vec<_> = ix.args.iter().map(|arg| &arg.raw_arg).collect();
             let ix_arg_names: Vec<&syn::Ident> = ix.args.iter().map(|arg| &arg.name).collect();
-            let ix_name = &ix.raw_method.sig.ident;
+            let ix_name = generate_ix_variant_name(ix.raw_method.sig.ident.to_string());
+            let ix_method_name = &ix.raw_method.sig.ident;
             let anchor = &ix.anchor_ident;
+            let variant_arm = generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args);
 
             quote! {
                 #[inline(never)]
-                pub fn #ix_name(
+                pub fn #ix_method_name(
                     program_id: &Pubkey,
                     accounts: &[AccountInfo],
-                    #(#ix_params),*
+                    ix_data: &[u8],
                 ) -> ProgramResult {
+										// Deserialize data.
+                    let ix = instruction::#ix_name::deserialize(&mut &ix_data[..])
+                        .map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
+                    let instruction::#variant_arm = ix;
+
+										// Deserialize accounts.
                     let mut remaining_accounts: &[AccountInfo] = accounts;
-                    let mut accounts = #anchor::try_accounts(program_id, &mut remaining_accounts)?;
-                    #program_name::#ix_name(
+                    let mut accounts = #anchor::try_accounts(
+                        program_id,
+                        &mut remaining_accounts,
+                        ix_data,
+                    )?;
+
+										// Invoke user defined handler.
+                    #program_name::#ix_method_name(
                         Context::new(program_id, &mut accounts, remaining_accounts),
                         #(#ix_arg_names),*
                     )?;
+
+										// Exit routine.
                     accounts.exit(program_id)
                 }
             }
@@ -590,3 +674,28 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
         }
     }
 }
+
+fn generate_ix_variant_name(name: String) -> proc_macro2::TokenStream {
+    let n = name.to_camel_case();
+    n.parse().unwrap()
+}
+
+fn generate_ctor_variant_name() -> String {
+    "New".to_string()
+}
+
+fn generate_ctor_variant(state: &State) -> proc_macro2::TokenStream {
+    let ctor_args = generate_ctor_args(state);
+    let ctor_variant_name: proc_macro2::TokenStream = generate_ctor_variant_name().parse().unwrap();
+    if ctor_args.is_empty() {
+        quote! {
+            #ctor_variant_name
+        }
+    } else {
+        quote! {
+            #ctor_variant_name {
+                #(#ctor_args),*
+            }
+        }
+    }
+}

+ 25 - 7
lang/syn/src/lib.rs

@@ -9,6 +9,7 @@ use syn::ext::IdentExt;
 use syn::parse::{Error as ParseError, Parse, ParseStream, Result as ParseResult};
 use syn::punctuated::Punctuated;
 use syn::spanned::Spanned;
+use syn::token::Comma;
 use syn::{
     Expr, Generics, Ident, ImplItemMethod, ItemEnum, ItemFn, ItemImpl, ItemMod, ItemStruct, LitInt,
     LitStr, PatType, Token,
@@ -99,6 +100,8 @@ pub struct AccountsStruct {
     pub generics: Generics,
     // Fields on the accounts struct.
     pub fields: Vec<AccountField>,
+    // Instruction data api expression.
+    instruction_api: Option<Punctuated<Expr, Comma>>,
 }
 
 impl Parse for AccountsStruct {
@@ -121,13 +124,18 @@ impl ToTokens for AccountsStruct {
 }
 
 impl AccountsStruct {
-    pub fn new(strct: ItemStruct, fields: Vec<AccountField>) -> Self {
+    pub fn new(
+        strct: ItemStruct,
+        fields: Vec<AccountField>,
+        instruction_api: Option<Punctuated<Expr, Comma>>,
+    ) -> Self {
         let ident = strct.ident.clone();
         let generics = strct.generics;
         Self {
             ident,
             generics,
             fields,
+            instruction_api,
         }
     }
 }
@@ -142,6 +150,7 @@ pub enum AccountField {
 pub struct Field {
     pub ident: Ident,
     pub constraints: ConstraintGroup,
+    pub instruction_constraints: ConstraintGroup,
     pub ty: Ty,
 }
 
@@ -149,6 +158,7 @@ pub struct Field {
 pub struct CompositeField {
     pub ident: Ident,
     pub constraints: ConstraintGroup,
+    pub instruction_constraints: ConstraintGroup,
     pub symbol: String,
     pub raw_field: syn::Field,
 }
@@ -250,7 +260,7 @@ pub struct ConstraintGroup {
     signer: Option<ConstraintSigner>,
     owner: Option<ConstraintOwner>,
     rent_exempt: Option<ConstraintRentExempt>,
-    seeds: Option<ConstraintSeeds>,
+    seeds: Option<ConstraintSeedsGroup>,
     executable: Option<ConstraintExecutable>,
     state: Option<ConstraintState>,
     associated: Option<ConstraintAssociatedGroup>,
@@ -291,7 +301,7 @@ pub enum Constraint {
     Raw(ConstraintRaw),
     Owner(ConstraintOwner),
     RentExempt(ConstraintRentExempt),
-    Seeds(ConstraintSeeds),
+    Seeds(ConstraintSeedsGroup),
     Executable(ConstraintExecutable),
     State(ConstraintState),
     AssociatedGroup(ConstraintAssociatedGroup),
@@ -360,6 +370,14 @@ pub enum ConstraintRentExempt {
     Skip,
 }
 
+#[derive(Debug, Clone)]
+pub struct ConstraintSeedsGroup {
+    pub is_init: bool,
+    pub seeds: Punctuated<Expr, Token![,]>,
+    pub payer: Option<Ident>,
+    pub space: Option<LitInt>,
+}
+
 #[derive(Debug, Clone)]
 pub struct ConstraintSeeds {
     pub seeds: Punctuated<Expr, Token![,]>,
@@ -382,22 +400,22 @@ pub struct ConstraintAssociatedGroup {
     pub space: Option<LitInt>,
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct ConstraintAssociated {
     pub target: Ident,
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct ConstraintAssociatedPayer {
     pub target: Ident,
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct ConstraintAssociatedWith {
     pub target: Ident,
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct ConstraintAssociatedSpace {
     pub space: LitInt,
 }

+ 63 - 9
lang/syn/src/parser/accounts/constraints.rs

@@ -2,8 +2,8 @@ use crate::{
     ConstraintAssociated, ConstraintAssociatedGroup, ConstraintAssociatedPayer,
     ConstraintAssociatedSpace, ConstraintAssociatedWith, ConstraintBelongsTo, ConstraintClose,
     ConstraintExecutable, ConstraintGroup, ConstraintInit, ConstraintLiteral, ConstraintMut,
-    ConstraintOwner, ConstraintRaw, ConstraintRentExempt, ConstraintSeeds, ConstraintSigner,
-    ConstraintState, ConstraintToken, Context, Ty,
+    ConstraintOwner, ConstraintRaw, ConstraintRentExempt, ConstraintSeeds, ConstraintSeedsGroup,
+    ConstraintSigner, ConstraintState, ConstraintToken, Context, Ty,
 };
 use syn::ext::IdentExt;
 use syn::parse::{Error as ParseError, Parse, ParseStream, Result as ParseResult};
@@ -12,14 +12,34 @@ use syn::spanned::Spanned;
 use syn::token::Comma;
 use syn::{bracketed, Expr, Ident, LitStr, Token};
 
-pub fn parse(f: &syn::Field, f_ty: Option<&Ty>) -> ParseResult<ConstraintGroup> {
+pub fn parse(
+    f: &syn::Field,
+    f_ty: Option<&Ty>,
+    has_instruction_api: bool,
+) -> ParseResult<(ConstraintGroup, ConstraintGroup)> {
     let mut constraints = ConstraintGroupBuilder::new(f_ty);
     for attr in f.attrs.iter().filter(is_account) {
         for c in attr.parse_args_with(Punctuated::<ConstraintToken, Comma>::parse_terminated)? {
             constraints.add(c)?;
         }
     }
-    constraints.build()
+    let account_constraints = constraints.build()?;
+
+    let mut constraints = ConstraintGroupBuilder::new(f_ty);
+    for attr in f.attrs.iter().filter(is_instruction) {
+        if !has_instruction_api {
+            return Err(ParseError::new(
+                attr.span(),
+                "an instruction api must be declared",
+            ));
+        }
+        for c in attr.parse_args_with(Punctuated::<ConstraintToken, Comma>::parse_terminated)? {
+            constraints.add(c)?;
+        }
+    }
+    let instruction_constraints = constraints.build()?;
+
+    Ok((account_constraints, instruction_constraints))
 }
 
 pub fn is_account(attr: &&syn::Attribute) -> bool {
@@ -28,6 +48,12 @@ pub fn is_account(attr: &&syn::Attribute) -> bool {
         .map_or(false, |ident| ident == "account")
 }
 
+pub fn is_instruction(attr: &&syn::Attribute) -> bool {
+    attr.path
+        .get_ident()
+        .map_or(false, |ident| ident == "instruction")
+}
+
 // Parses a single constraint from a parse stream for `#[account(<STREAM>)]`.
 pub fn parse_token(stream: ParseStream) -> ParseResult<ConstraintToken> {
     let is_lit = stream.peek(LitStr);
@@ -198,6 +224,14 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
                     .replace(Context::new(i.span(), ConstraintRentExempt::Enforce));
             }
         }
+        if let Some(i) = &self.seeds {
+            if self.init.is_some() && self.associated_payer.is_none() {
+                return Err(ParseError::new(
+                    i.span(),
+                    "payer must be provided when creating a program derived address",
+                ));
+            }
+        }
 
         let ConstraintGroupBuilder {
             f_ty: _,
@@ -224,6 +258,9 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
             ($opt:ident) => {
                 $opt.map(|c| c.into_inner())
             };
+            ($opt:expr) => {
+                $opt.map(|c| c.into_inner())
+            };
         }
         // Converts Vec<Context<T>> - Vec<T>.
         macro_rules! into_inner_vec {
@@ -242,7 +279,12 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
             raw: into_inner_vec!(raw),
             owner: into_inner!(owner),
             rent_exempt: into_inner!(rent_exempt),
-            seeds: into_inner!(seeds),
+            seeds: seeds.map(|c| ConstraintSeedsGroup {
+                is_init,
+                seeds: c.into_inner().seeds,
+                payer: into_inner!(associated_payer.clone()).map(|a| a.target),
+                space: associated_space.clone().map(|s| s.space.clone()),
+            }),
             executable: into_inner!(executable),
             state: into_inner!(state),
             associated: associated.map(|associated| ConstraintAssociatedGroup {
@@ -370,6 +412,12 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
         if self.seeds.is_some() {
             return Err(ParseError::new(c.span(), "seeds already provided"));
         }
+        if self.associated.is_some() {
+            return Err(ParseError::new(
+                c.span(),
+                "both seeds and associated cannot be defined together",
+            ));
+        }
         self.seeds.replace(c);
         Ok(())
     }
@@ -394,15 +442,21 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
         if self.associated.is_some() {
             return Err(ParseError::new(c.span(), "associated already provided"));
         }
+        if self.seeds.is_some() {
+            return Err(ParseError::new(
+                c.span(),
+                "both seeds and associated cannot be defined together",
+            ));
+        }
         self.associated.replace(c);
         Ok(())
     }
 
     fn add_associated_payer(&mut self, c: Context<ConstraintAssociatedPayer>) -> ParseResult<()> {
-        if self.associated.is_none() {
+        if self.associated.is_none() && self.seeds.is_none() {
             return Err(ParseError::new(
                 c.span(),
-                "associated must be provided before payer",
+                "associated or seeds must be provided before payer",
             ));
         }
         if self.associated_payer.is_some() {
@@ -413,10 +467,10 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
     }
 
     fn add_associated_space(&mut self, c: Context<ConstraintAssociatedSpace>) -> ParseResult<()> {
-        if self.associated.is_none() {
+        if self.associated.is_none() && self.seeds.is_none() {
             return Err(ParseError::new(
                 c.span(),
-                "associated must be provided before space",
+                "associated or seeds must be provided before space",
             ));
         }
         if self.associated_space.is_some() {

+ 25 - 7
lang/syn/src/parser/accounts/mod.rs

@@ -3,16 +3,30 @@ use crate::{
     ProgramAccountTy, ProgramStateTy, SysvarTy, Ty,
 };
 use syn::parse::{Error as ParseError, Result as ParseResult};
+use syn::punctuated::Punctuated;
 use syn::spanned::Spanned;
+use syn::token::Comma;
+use syn::Expr;
 
 pub mod constraints;
 
 pub fn parse(strct: &syn::ItemStruct) -> ParseResult<AccountsStruct> {
+    let instruction_api: Option<Punctuated<Expr, Comma>> = strct
+        .attrs
+        .iter()
+        .filter(|a| {
+            a.path
+                .get_ident()
+                .map_or(false, |ident| ident == "instruction")
+        })
+        .next()
+        .map(|ix_attr| ix_attr.parse_args_with(Punctuated::<Expr, Comma>::parse_terminated))
+        .transpose()?;
     let fields = match &strct.fields {
         syn::Fields::Named(fields) => fields
             .named
             .iter()
-            .map(parse_account_field)
+            .map(|f| parse_account_field(f, instruction_api.is_some()))
             .collect::<ParseResult<Vec<AccountField>>>()?,
         _ => {
             return Err(ParseError::new_spanned(
@@ -21,26 +35,30 @@ pub fn parse(strct: &syn::ItemStruct) -> ParseResult<AccountsStruct> {
             ))
         }
     };
-    Ok(AccountsStruct::new(strct.clone(), fields))
+    Ok(AccountsStruct::new(strct.clone(), fields, instruction_api))
 }
 
-pub fn parse_account_field(f: &syn::Field) -> ParseResult<AccountField> {
+pub fn parse_account_field(f: &syn::Field, has_instruction_api: bool) -> ParseResult<AccountField> {
     let ident = f.ident.clone().unwrap();
     let account_field = match is_field_primitive(f)? {
         true => {
             let ty = parse_ty(f)?;
-            let constraints = constraints::parse(f, Some(&ty))?;
+            let (account_constraints, instruction_constraints) =
+                constraints::parse(f, Some(&ty), has_instruction_api)?;
             AccountField::Field(Field {
                 ident,
                 ty,
-                constraints,
+                constraints: account_constraints,
+                instruction_constraints,
             })
         }
         false => {
-            let constraints = constraints::parse(f, None)?;
+            let (account_constraints, instruction_constraints) =
+                constraints::parse(f, None, has_instruction_api)?;
             AccountField::CompositeField(CompositeField {
                 ident,
-                constraints,
+                constraints: account_constraints,
+                instruction_constraints,
                 symbol: ident_string(f)?,
                 raw_field: f.clone(),
             })

+ 31 - 30
lang/syn/src/parser/program/instructions.rs

@@ -18,36 +18,8 @@ pub fn parse(program_mod: &syn::ItemMod) -> ParseResult<Vec<Ix>> {
             _ => None,
         })
         .map(|method: &syn::ItemFn| {
-            let mut args: Vec<IxArg> = method
-                .sig
-                .inputs
-                .iter()
-                .map(|arg: &syn::FnArg| match arg {
-                    syn::FnArg::Typed(arg) => {
-                        let ident = match &*arg.pat {
-                            syn::Pat::Ident(ident) => &ident.ident,
-                            _ => {
-                                return Err(ParseError::new(
-                                    arg.pat.span(),
-                                    "expected argument name",
-                                ))
-                            }
-                        };
-                        Ok(IxArg {
-                            name: ident.clone(),
-                            raw_arg: arg.clone(),
-                        })
-                    }
-                    syn::FnArg::Receiver(_) => Err(ParseError::new(
-                        arg.span(),
-                        "expected a typed argument not self",
-                    )),
-                })
-                .collect::<ParseResult<_>>()?;
-            // Remove the Context argument
-            let anchor = args.remove(0);
-            let anchor_ident = ctx_accounts_ident(&anchor.raw_arg)?;
-
+            let (ctx, args) = parse_args(method)?;
+            let anchor_ident = ctx_accounts_ident(&ctx.raw_arg)?;
             Ok(Ix {
                 raw_method: method.clone(),
                 ident: method.sig.ident.clone(),
@@ -57,3 +29,32 @@ pub fn parse(program_mod: &syn::ItemMod) -> ParseResult<Vec<Ix>> {
         })
         .collect::<ParseResult<Vec<Ix>>>()
 }
+
+pub fn parse_args(method: &syn::ItemFn) -> ParseResult<(IxArg, Vec<IxArg>)> {
+    let mut args: Vec<IxArg> = method
+        .sig
+        .inputs
+        .iter()
+        .map(|arg: &syn::FnArg| match arg {
+            syn::FnArg::Typed(arg) => {
+                let ident = match &*arg.pat {
+                    syn::Pat::Ident(ident) => &ident.ident,
+                    _ => return Err(ParseError::new(arg.pat.span(), "expected argument name")),
+                };
+                Ok(IxArg {
+                    name: ident.clone(),
+                    raw_arg: arg.clone(),
+                })
+            }
+            syn::FnArg::Receiver(_) => Err(ParseError::new(
+                arg.span(),
+                "expected a typed argument not self",
+            )),
+        })
+        .collect::<ParseResult<_>>()?;
+
+    // Remove the Context argument
+    let ctx = args.remove(0);
+
+    Ok((ctx, args))
+}