Forráskód Böngészése

lang, spl, cli: Refactor to preserve span information (#341)

Armani Ferrante 4 éve
szülő
commit
6b5ed789fc
39 módosított fájl, 3680 hozzáadás és 3078 törlés
  1. 22 16
      CHANGELOG.md
  2. 1 1
      cli/src/config.rs
  3. 1 1
      cli/src/main.rs
  4. 1 1
      examples/chat/programs/chat/src/lib.rs
  5. 1 1
      examples/misc/programs/misc/src/lib.rs
  6. 1 1
      lang/attribute/interface/src/lib.rs
  7. 4 5
      lang/attribute/program/src/lib.rs
  8. 4 5
      lang/derive/accounts/src/lib.rs
  9. 1 1
      lang/src/ctor.rs
  10. 4 4
      lang/src/idl.rs
  11. 0 720
      lang/syn/src/codegen/accounts.rs
  12. 129 0
      lang/syn/src/codegen/accounts/__client_accounts.rs
  13. 471 0
      lang/syn/src/codegen/accounts/constraints.rs
  14. 39 0
      lang/syn/src/codegen/accounts/exit.rs
  15. 43 0
      lang/syn/src/codegen/accounts/mod.rs
  16. 34 0
      lang/syn/src/codegen/accounts/to_account_infos.rs
  17. 40 0
      lang/syn/src/codegen/accounts/to_account_metas.rs
  18. 198 0
      lang/syn/src/codegen/accounts/try_accounts.rs
  19. 0 1379
      lang/syn/src/codegen/program.rs
  20. 65 0
      lang/syn/src/codegen/program/accounts.rs
  21. 83 0
      lang/syn/src/codegen/program/common.rs
  22. 118 0
      lang/syn/src/codegen/program/cpi.rs
  23. 215 0
      lang/syn/src/codegen/program/dispatch.rs
  24. 75 0
      lang/syn/src/codegen/program/entry.rs
  25. 592 0
      lang/syn/src/codegen/program/handlers.rs
  26. 194 0
      lang/syn/src/codegen/program/instruction.rs
  27. 35 0
      lang/syn/src/codegen/program/mod.rs
  28. 33 6
      lang/syn/src/idl/file.rs
  29. 2 0
      lang/syn/src/idl/mod.rs
  30. 238 200
      lang/syn/src/lib.rs
  31. 0 429
      lang/syn/src/parser/accounts.rs
  32. 385 0
      lang/syn/src/parser/accounts/constraints.rs
  33. 218 0
      lang/syn/src/parser/accounts/mod.rs
  34. 0 2
      lang/syn/src/parser/mod.rs
  35. 0 304
      lang/syn/src/parser/program.rs
  36. 59 0
      lang/syn/src/parser/program/instructions.rs
  37. 59 0
      lang/syn/src/parser/program/mod.rs
  38. 312 0
      lang/syn/src/parser/program/state.rs
  39. 3 2
      spl/src/shmem.rs

+ 22 - 16
CHANGELOG.md

@@ -11,22 +11,28 @@ incremented for features.
 
 
 ## [Unreleased]
 ## [Unreleased]
 
 
-## Features
+### Features
 
 
-* ts: Address metadata is now optional for `anchor.workspace` clients ([#310](https://github.com/project-serum/anchor/pull/310)).
 * cli: Add global options for override Anchor.toml values ([#313](https://github.com/project-serum/anchor/pull/313)).
 * cli: Add global options for override Anchor.toml values ([#313](https://github.com/project-serum/anchor/pull/313)).
 * spl: Add `SetAuthority` instruction ([#307](https://github.com/project-serum/anchor/pull/307/files)).
 * spl: Add `SetAuthority` instruction ([#307](https://github.com/project-serum/anchor/pull/307/files)).
+* lang: `constraint = <expression>` added as a replacement for (the now deprecated) string literal constraints ([#341](https://github.com/project-serum/anchor/pull/341)).
+* lang: Span information is now preserved, providing informative compiler error messages ([#341](https://github.com/project-serum/anchor/pull/341)).
+* ts: Address metadata is now optional for `anchor.workspace` clients ([#310](https://github.com/project-serum/anchor/pull/310)).
 
 
-## Breaking Changes
+### Breaking Changes
 
 
 * ts: Retrieving deserialized accounts from the `<program>.account.<my-account>` and `<program>.state` namespaces now require explicitly invoking the `fetch` API. For example, `program.account.myAccount(<adddress>)` and `program.state()` is now `program.account.myAccount.fetch(<address>)` and `program.state.fetch()` ([#322](https://github.com/project-serum/anchor/pull/322)).
 * ts: Retrieving deserialized accounts from the `<program>.account.<my-account>` and `<program>.state` namespaces now require explicitly invoking the `fetch` API. For example, `program.account.myAccount(<adddress>)` and `program.state()` is now `program.account.myAccount.fetch(<address>)` and `program.state.fetch()` ([#322](https://github.com/project-serum/anchor/pull/322)).
-* lang: `#[account(associated)]` now requires `init` to be provided to create an associated account. If not provided, then the address will be assumed to exist, and a constraint will be added to ensure its correctness ([#318](https://github.com/project-serum/anchor/pull/318)).
+* lang: `#[account(associated)]` now requires `init` to be provided to create an associated account. If not provided, then the address will be assumed to exist, and a constraint will be added to ensure the correctness of the address ([#318](https://github.com/project-serum/anchor/pull/318)).
 * lang, ts: Change account discriminator pre-image of the `#[state]` account discriminator to be namespaced by "state:" ([#320](https://github.com/project-serum/anchor/pull/320)).
 * lang, ts: Change account discriminator pre-image of the `#[state]` account discriminator to be namespaced by "state:" ([#320](https://github.com/project-serum/anchor/pull/320)).
 * lang, ts: Change domain delimiters for the pre-image of the instruciton sighash to be a single colon `:` to be consistent with accounts ([#321](https://github.com/project-serum/anchor/pull/321)).
 * lang, ts: Change domain delimiters for the pre-image of the instruciton sighash to be a single colon `:` to be consistent with accounts ([#321](https://github.com/project-serum/anchor/pull/321)).
+* lang: Associated constraints no longer automatically implement `mut` ([#341](https://github.com/project-serum/anchor/pull/341)).
+* lang: Associated `space` constraints must now be literal integers instead of literal strings ([#341](https://github.com/project-serum/anchor/pull/341)).
+
+### Fixes
 
 
 ## [0.6.0] - 2021-05-23
 ## [0.6.0] - 2021-05-23
 
 
-## Features
+### Features
 
 
 * ts: Add `program.simulate` namespace ([#266](https://github.com/project-serum/anchor/pull/266)).
 * ts: Add `program.simulate` namespace ([#266](https://github.com/project-serum/anchor/pull/266)).
 * ts: Introduce `Address` type, allowing one to use Base 58 encoded strings in public APIs ([#304](https://github.com/project-serum/anchor/pull/304)).
 * ts: Introduce `Address` type, allowing one to use Base 58 encoded strings in public APIs ([#304](https://github.com/project-serum/anchor/pull/304)).
@@ -36,7 +42,7 @@ incremented for features.
 * cli: Add `--skip-build` flag to test command ([301](https://github.com/project-serum/anchor/pull/301)).
 * cli: Add `--skip-build` flag to test command ([301](https://github.com/project-serum/anchor/pull/301)).
 * cli: Add `anchor shell` command to spawn a node shell populated with an Anchor.toml based environment ([#303](https://github.com/project-serum/anchor/pull/303)).
 * cli: Add `anchor shell` command to spawn a node shell populated with an Anchor.toml based environment ([#303](https://github.com/project-serum/anchor/pull/303)).
 
 
-## Breaking Changes
+### Breaking Changes
 
 
 * cli: The Anchor.toml's `wallet` and `cluster` settings must now be under the `[provider]` table ([#305](https://github.com/project-serum/anchor/pull/305)).
 * cli: The Anchor.toml's `wallet` and `cluster` settings must now be under the `[provider]` table ([#305](https://github.com/project-serum/anchor/pull/305)).
 * ts: Event coder `decode` API changed to decode strings directly instead of buffers ([#292](https://github.com/project-serum/anchor/pull/292)).
 * ts: Event coder `decode` API changed to decode strings directly instead of buffers ([#292](https://github.com/project-serum/anchor/pull/292)).
@@ -44,13 +50,13 @@ incremented for features.
 
 
 ## [0.5.0] - 2021-05-07
 ## [0.5.0] - 2021-05-07
 
 
-## Features
+### Features
 
 
 * client: Adds support for state instructions ([#248](https://github.com/project-serum/anchor/pull/248)).
 * client: Adds support for state instructions ([#248](https://github.com/project-serum/anchor/pull/248)).
 * lang: Add `anchor-debug` feature flag for logging ([#253](https://github.com/project-serum/anchor/pull/253)).
 * lang: Add `anchor-debug` feature flag for logging ([#253](https://github.com/project-serum/anchor/pull/253)).
 * ts: Add support for u16 ([#255](https://github.com/project-serum/anchor/pull/255)).
 * ts: Add support for u16 ([#255](https://github.com/project-serum/anchor/pull/255)).
 
 
-## Breaking
+### Breaking Changes
 
 
 * client: Renames `RequestBuilder::new` to `RequestBuilder::from` ([#248](https://github.com/project-serum/anchor/pull/248)).
 * client: Renames `RequestBuilder::new` to `RequestBuilder::from` ([#248](https://github.com/project-serum/anchor/pull/248)).
 * lang: Renames the generated `instruction::state::Ctor` struct to `instruction::state::New` ([#248](https://github.com/project-serum/anchor/pull/248)).
 * lang: Renames the generated `instruction::state::Ctor` struct to `instruction::state::New` ([#248](https://github.com/project-serum/anchor/pull/248)).
@@ -61,7 +67,7 @@ incremented for features.
 
 
 ## [0.4.4] - 2021-04-18
 ## [0.4.4] - 2021-04-18
 
 
-## Features
+### Features
 
 
 * lang: Allows one to specify multiple `with` targets when creating associated acconts ([#197](https://github.com/project-serum/anchor/pull/197)).
 * lang: Allows one to specify multiple `with` targets when creating associated acconts ([#197](https://github.com/project-serum/anchor/pull/197)).
 * lang, ts: Add array support ([#202](https://github.com/project-serum/anchor/pull/202)).
 * lang, ts: Add array support ([#202](https://github.com/project-serum/anchor/pull/202)).
@@ -70,19 +76,19 @@ incremented for features.
 
 
 ## [0.4.3] - 2021-04-13
 ## [0.4.3] - 2021-04-13
 
 
-## Features
+### Features
 
 
 * lang: CPI clients for program state instructions ([#43](https://github.com/project-serum/anchor/pull/43)).
 * lang: CPI clients for program state instructions ([#43](https://github.com/project-serum/anchor/pull/43)).
 * lang: Add `#[account(owner = <program>)]` constraint ([#178](https://github.com/project-serum/anchor/pull/178)).
 * lang: Add `#[account(owner = <program>)]` constraint ([#178](https://github.com/project-serum/anchor/pull/178)).
 * lang, cli, ts: Add `#[account(associated = <target>)]` and `#[associated]` attributes for creating associated program accounts within programs. The TypeScript package can fetch these accounts with a new `<program>.account.<account-name>.associated` (and `associatedAddress`) method ([#186](https://github.com/project-serum/anchor/pull/186)).
 * lang, cli, ts: Add `#[account(associated = <target>)]` and `#[associated]` attributes for creating associated program accounts within programs. The TypeScript package can fetch these accounts with a new `<program>.account.<account-name>.associated` (and `associatedAddress`) method ([#186](https://github.com/project-serum/anchor/pull/186)).
 
 
-## Fixes
+### Fixes
 
 
 * lang: Unused `#[account]`s are now parsed into the IDL correctly ([#177](https://github.com/project-serum/anchor/pull/177)).
 * lang: Unused `#[account]`s are now parsed into the IDL correctly ([#177](https://github.com/project-serum/anchor/pull/177)).
 
 
 ## [0.4.2] - 2021-04-10
 ## [0.4.2] - 2021-04-10
 
 
-## Features
+### Features
 
 
 * cli: Fund Anchor.toml configured wallet when testing ([#164](https://github.com/project-serum/anchor/pull/164)).
 * cli: Fund Anchor.toml configured wallet when testing ([#164](https://github.com/project-serum/anchor/pull/164)).
 * spl: Add initialize_account instruction for spl tokens ([#166](https://github.com/project-serum/anchor/pull/166)).
 * spl: Add initialize_account instruction for spl tokens ([#166](https://github.com/project-serum/anchor/pull/166)).
@@ -93,7 +99,7 @@ incremented for features.
 
 
 ## [0.4.0] - 2021-04-04
 ## [0.4.0] - 2021-04-04
 
 
-## Features
+### Features
 
 
 * cli: Specify test files to run ([#118](https://github.com/project-serum/anchor/pull/118)).
 * cli: Specify test files to run ([#118](https://github.com/project-serum/anchor/pull/118)).
 * lang: Allow overriding the `#[state]` account's size ([#121](https://github.com/project-serum/anchor/pull/121)).
 * lang: Allow overriding the `#[state]` account's size ([#121](https://github.com/project-serum/anchor/pull/121)).
@@ -102,7 +108,7 @@ incremented for features.
 * cli: TypeScript migrations ([#132](https://github.com/project-serum/anchor/pull/132)).
 * 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)).
 * lang: Add `#[account(executable)]` attribute ([#140](https://github.com/project-serum/anchor/pull/140)).
 
 
-## Breaking Changes
+### Breaking Changes
 
 
 * client: Replace url str with `Cluster` struct when constructing clients ([#89](https://github.com/project-serum/anchor/pull/89)).
 * client: Replace url str with `Cluster` struct when constructing clients ([#89](https://github.com/project-serum/anchor/pull/89)).
 * lang: Changes the account discriminator of `IdlAccount` to be namespaced by `"internal"` ([#128](https://github.com/project-serum/anchor/pull/128)).
 * lang: Changes the account discriminator of `IdlAccount` to be namespaced by `"internal"` ([#128](https://github.com/project-serum/anchor/pull/128)).
@@ -110,7 +116,7 @@ incremented for features.
 
 
 ## [0.3.0] - 2021-03-12
 ## [0.3.0] - 2021-03-12
 
 
-## Features
+### Features
 
 
 * ts: Allow preloading instructions for state rpc transactions ([cf9c84](https://github.com/project-serum/anchor/commit/cf9c847e4144989b5bc1936149d171e90204777b)).
 * ts: Allow preloading instructions for state rpc transactions ([cf9c84](https://github.com/project-serum/anchor/commit/cf9c847e4144989b5bc1936149d171e90204777b)).
 * ts: Export sighash coder function ([734c75](https://github.com/project-serum/anchor/commit/734c751882f43beec7ea3f0f4d988b502e3f24e4)).
 * ts: Export sighash coder function ([734c75](https://github.com/project-serum/anchor/commit/734c751882f43beec7ea3f0f4d988b502e3f24e4)).
@@ -124,7 +130,7 @@ incremented for features.
 
 
 * lang: Removes `IdlInstruction::Clear` ([#107](https://github.com/project-serum/anchor/pull/107)).
 * lang: Removes `IdlInstruction::Clear` ([#107](https://github.com/project-serum/anchor/pull/107)).
 
 
-## Fixes
+### Fixes
 
 
 * cli: Propagates mocha test exit status on error ([79b791](https://github.com/project-serum/anchor/commit/79b791ffa85ffae5b6163fa853562aa568650f21)).
 * cli: Propagates mocha test exit status on error ([79b791](https://github.com/project-serum/anchor/commit/79b791ffa85ffae5b6163fa853562aa568650f21)).
 
 

+ 1 - 1
cli/src/config.rs

@@ -210,7 +210,7 @@ pub fn read_all_programs() -> Result<Vec<Program>> {
     let mut r = vec![];
     let mut r = vec![];
     for f in files {
     for f in files {
         let path = f?.path();
         let path = f?.path();
-        let idl = anchor_syn::parser::file::parse(path.join("src/lib.rs"))?;
+        let idl = anchor_syn::idl::file::parse(path.join("src/lib.rs"))?;
         let lib_name = extract_lib_name(&path.join("Cargo.toml"))?;
         let lib_name = extract_lib_name(&path.join("Cargo.toml"))?;
         r.push(Program {
         r.push(Program {
             lib_name,
             lib_name,

+ 1 - 1
cli/src/main.rs

@@ -633,7 +633,7 @@ fn fetch_idl(cfg_override: &ConfigOverride, idl_addr: Pubkey) -> Result<Idl> {
 
 
 fn extract_idl(file: &str) -> Result<Idl> {
 fn extract_idl(file: &str) -> Result<Idl> {
     let file = shellexpand::tilde(file);
     let file = shellexpand::tilde(file);
-    anchor_syn::parser::file::parse(&*file)
+    anchor_syn::idl::file::parse(&*file)
 }
 }
 
 
 fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {
 fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {

+ 1 - 1
examples/chat/programs/chat/src/lib.rs

@@ -36,7 +36,7 @@ pub mod chat {
 
 
 #[derive(Accounts)]
 #[derive(Accounts)]
 pub struct CreateUser<'info> {
 pub struct CreateUser<'info> {
-    #[account(init, associated = authority, space = "312")]
+    #[account(init, associated = authority, space = 312)]
     user: ProgramAccount<'info, User>,
     user: ProgramAccount<'info, User>,
     #[account(signer)]
     #[account(signer)]
     authority: AccountInfo<'info>,
     authority: AccountInfo<'info>,

+ 1 - 1
examples/misc/programs/misc/src/lib.rs

@@ -140,7 +140,7 @@ pub struct TestInitAssociatedAccount<'info> {
 
 
 #[derive(Accounts)]
 #[derive(Accounts)]
 pub struct TestAssociatedAccount<'info> {
 pub struct TestAssociatedAccount<'info> {
-    #[account(associated = authority, with = state, with = data)]
+    #[account(mut, associated = authority, with = state, with = data)]
     my_account: ProgramAccount<'info, TestData>,
     my_account: ProgramAccount<'info, TestData>,
     #[account(mut, signer)]
     #[account(mut, signer)]
     authority: AccountInfo<'info>,
     authority: AccountInfo<'info>,

+ 1 - 1
lang/attribute/interface/src/lib.rs

@@ -201,7 +201,7 @@ pub fn interface(
                 }
                 }
             };
             };
 
 
-            let sighash_arr = anchor_syn::codegen::program::sighash(&trait_name, &method_name.to_string());
+            let sighash_arr = anchor_syn::codegen::program::common::sighash(&trait_name, &method_name.to_string());
             let sighash_tts: proc_macro2::TokenStream =
             let sighash_tts: proc_macro2::TokenStream =
                 format!("{:?}", sighash_arr).parse().unwrap();
                 format!("{:?}", sighash_arr).parse().unwrap();
             quote! {
             quote! {

+ 4 - 5
lang/attribute/program/src/lib.rs

@@ -1,7 +1,6 @@
 extern crate proc_macro;
 extern crate proc_macro;
 
 
-use anchor_syn::codegen::program as program_codegen;
-use anchor_syn::parser::program as program_parser;
+use quote::ToTokens;
 use syn::parse_macro_input;
 use syn::parse_macro_input;
 
 
 /// The `#[program]` attribute defines the module containing all instruction
 /// The `#[program]` attribute defines the module containing all instruction
@@ -11,7 +10,7 @@ pub fn program(
     _args: proc_macro::TokenStream,
     _args: proc_macro::TokenStream,
     input: proc_macro::TokenStream,
     input: proc_macro::TokenStream,
 ) -> proc_macro::TokenStream {
 ) -> proc_macro::TokenStream {
-    let program_mod = parse_macro_input!(input as syn::ItemMod);
-    let code = program_codegen::generate(program_parser::parse(program_mod));
-    proc_macro::TokenStream::from(code)
+    parse_macro_input!(input as anchor_syn::Program)
+        .to_token_stream()
+        .into()
 }
 }

+ 4 - 5
lang/derive/accounts/src/lib.rs

@@ -1,8 +1,7 @@
 extern crate proc_macro;
 extern crate proc_macro;
 
 
-use anchor_syn::codegen::accounts as accounts_codegen;
-use anchor_syn::parser::accounts as accounts_parser;
 use proc_macro::TokenStream;
 use proc_macro::TokenStream;
+use quote::ToTokens;
 use syn::parse_macro_input;
 use syn::parse_macro_input;
 
 
 /// Implements an [`Accounts`](./trait.Accounts.html) deserializer on the given
 /// Implements an [`Accounts`](./trait.Accounts.html) deserializer on the given
@@ -54,7 +53,7 @@ use syn::parse_macro_input;
 //       on absurdly long lines?
 //       on absurdly long lines?
 #[proc_macro_derive(Accounts, attributes(account))]
 #[proc_macro_derive(Accounts, attributes(account))]
 pub fn derive_anchor_deserialize(item: TokenStream) -> TokenStream {
 pub fn derive_anchor_deserialize(item: TokenStream) -> TokenStream {
-    let strct = parse_macro_input!(item as syn::ItemStruct);
-    let tts = accounts_codegen::generate(accounts_parser::parse(&strct));
-    proc_macro::TokenStream::from(tts)
+    parse_macro_input!(item as anchor_syn::AccountsStruct)
+        .to_token_stream()
+        .into()
 }
 }

+ 1 - 1
lang/src/ctor.rs

@@ -1,4 +1,4 @@
-use crate::{Accounts, Sysvar};
+use crate::{Accounts, Sysvar, ToAccountInfo};
 use solana_program::account_info::AccountInfo;
 use solana_program::account_info::AccountInfo;
 use solana_program::sysvar::rent::Rent;
 use solana_program::sysvar::rent::Rent;
 
 

+ 4 - 4
lang/src/idl.rs

@@ -49,7 +49,7 @@ pub type IdlCreateAccounts<'info> = crate::ctor::Ctor<'info>;
 pub struct IdlAccounts<'info> {
 pub struct IdlAccounts<'info> {
     #[account(mut, has_one = authority)]
     #[account(mut, has_one = authority)]
     pub idl: ProgramAccount<'info, IdlAccount>,
     pub idl: ProgramAccount<'info, IdlAccount>,
-    #[account(signer, "authority.key != &Pubkey::new_from_array([0u8; 32])")]
+    #[account(signer, constraint = authority.key != &Pubkey::new_from_array([0u8; 32]))]
     pub authority: AccountInfo<'info>,
     pub authority: AccountInfo<'info>,
 }
 }
 
 
@@ -58,7 +58,7 @@ pub struct IdlAccounts<'info> {
 pub struct IdlCreateBuffer<'info> {
 pub struct IdlCreateBuffer<'info> {
     #[account(init)]
     #[account(init)]
     pub buffer: ProgramAccount<'info, IdlAccount>,
     pub buffer: ProgramAccount<'info, IdlAccount>,
-    #[account(signer, "authority.key != &Pubkey::new_from_array([0u8; 32])")]
+    #[account(signer, constraint = authority.key != &Pubkey::new_from_array([0u8; 32]))]
     pub authority: AccountInfo<'info>,
     pub authority: AccountInfo<'info>,
     pub rent: Sysvar<'info, Rent>,
     pub rent: Sysvar<'info, Rent>,
 }
 }
@@ -67,12 +67,12 @@ pub struct IdlCreateBuffer<'info> {
 #[derive(Accounts)]
 #[derive(Accounts)]
 pub struct IdlSetBuffer<'info> {
 pub struct IdlSetBuffer<'info> {
     // The buffer with the new idl data.
     // The buffer with the new idl data.
-    #[account(mut, "buffer.authority == idl.authority")]
+    #[account(mut, constraint = buffer.authority == idl.authority)]
     pub buffer: ProgramAccount<'info, IdlAccount>,
     pub buffer: ProgramAccount<'info, IdlAccount>,
     // The idl account to be updated with the buffer's data.
     // The idl account to be updated with the buffer's data.
     #[account(mut, has_one = authority)]
     #[account(mut, has_one = authority)]
     pub idl: ProgramAccount<'info, IdlAccount>,
     pub idl: ProgramAccount<'info, IdlAccount>,
-    #[account(signer, "authority.key != &Pubkey::new_from_array([0u8; 32])")]
+    #[account(signer, constraint = authority.key != &Pubkey::new_from_array([0u8; 32]))]
     pub authority: AccountInfo<'info>,
     pub authority: AccountInfo<'info>,
 }
 }
 
 

+ 0 - 720
lang/syn/src/codegen/accounts.rs

@@ -1,720 +0,0 @@
-use crate::{
-    AccountField, AccountsStruct, CompositeField, Constraint, ConstraintAssociated,
-    ConstraintBelongsTo, ConstraintExecutable, ConstraintLiteral, ConstraintOwner,
-    ConstraintRentExempt, ConstraintSeeds, ConstraintSigner, ConstraintState, Field, Ty,
-};
-use heck::SnakeCase;
-use quote::quote;
-
-pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
-    // 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
-        .iter()
-        .map(|af: &AccountField| {
-            match af {
-                AccountField::AccountsStruct(s) => {
-                    let name = &s.ident;
-                    let ty = &s.raw_field.ty;
-                    quote! {
-                        #[cfg(feature = "anchor-debug")]
-                        ::solana_program::log::sol_log(stringify!(#name));
-                        let #name: #ty = anchor_lang::Accounts::try_accounts(program_id, accounts)?;
-                    }
-                }
-                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) {
-                        let name = &f.ident;
-                        quote!{
-                            let #name = &accounts[0];
-                            *accounts = &accounts[1..];
-                        }
-                    } else {
-                        let name = &f.typed_ident();
-                        match f.is_init {
-                            false => quote! {
-                                #[cfg(feature = "anchor-debug")]
-                                ::solana_program::log::sol_log(stringify!(#name));
-                                let #name = anchor_lang::Accounts::try_accounts(program_id, accounts)?;
-                            },
-                            true => quote! {
-                                #[cfg(feature = "anchor-debug")]
-                                ::solana_program::log::sol_log(stringify!(#name));
-                                let #name = anchor_lang::AccountsInit::try_accounts_init(program_id, accounts)?;
-                            },
-                        }
-                    }
-                }
-            }
-        })
-        .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::AccountsStruct(_s) => None,
-            AccountField::Field(f) => match is_associated_init(af) {
-                false => None,
-                true => Some(f),
-            },
-        })
-        .map(|field: &Field| {
-            // TODO: the constraints should be sorted so that the associated
-            //       constraint comes first.
-            let checks = field
-                .constraints
-                .iter()
-                .map(|c| generate_field_constraint(&field, c))
-                .collect::<Vec<proc_macro2::TokenStream>>();
-            quote! {
-                #(#checks)*
-            }
-        })
-        .collect();
-
-    // Constraint checks for each account fields.
-    let access_checks: Vec<proc_macro2::TokenStream> = non_associated_fields
-        .iter()
-        .map(|af: &&AccountField| {
-            let checks: Vec<proc_macro2::TokenStream> = match af {
-                AccountField::Field(f) => f
-                    .constraints
-                    .iter()
-                    .map(|c| generate_field_constraint(&f, c))
-                    .collect(),
-                AccountField::AccountsStruct(s) => s
-                    .constraints
-                    .iter()
-                    .map(|c| generate_composite_constraint(&s, c))
-                    .collect(),
-            };
-            quote! {
-                #(#checks)*
-            }
-        })
-        .collect();
-
-    // 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::AccountsStruct(s) => &s.ident,
-                AccountField::Field(f) => &f.ident,
-            };
-            quote! {
-                #name
-            }
-        })
-        .collect();
-
-    // Exit program code-blocks for each account.
-    let on_save: Vec<proc_macro2::TokenStream> = accs
-        .fields
-        .iter()
-        .map(|af: &AccountField| match af {
-            AccountField::AccountsStruct(s) => {
-                let name = &s.ident;
-                quote! {
-                    anchor_lang::AccountsExit::exit(&self.#name, program_id)?;
-                }
-            }
-            AccountField::Field(f) => {
-                let ident = &f.ident;
-                match f.is_mut {
-                    false => quote! {},
-                    true => quote! {
-                        anchor_lang::AccountsExit::exit(&self.#ident, program_id)?;
-                    },
-                }
-            }
-        })
-        .collect();
-
-    // Implementation for `ToAccountInfos` trait.
-    let to_acc_infos: Vec<proc_macro2::TokenStream> = accs
-        .fields
-        .iter()
-        .map(|f: &AccountField| {
-            let name = match f {
-                AccountField::AccountsStruct(s) => &s.ident,
-                AccountField::Field(f) => &f.ident,
-            };
-            quote! {
-                account_infos.extend(self.#name.to_account_infos());
-            }
-        })
-        .collect();
-
-    // Implementation for `ToAccountMetas` trait.
-    let to_acc_metas: Vec<proc_macro2::TokenStream> = accs
-        .fields
-        .iter()
-        .map(|f: &AccountField| {
-            let (name, is_signer) = match f {
-                AccountField::AccountsStruct(s) => (&s.ident, quote! {None}),
-                AccountField::Field(f) => {
-                    let is_signer = match f.is_signer {
-                        false => quote! {None},
-                        true => quote! {Some(true)},
-                    };
-                    (&f.ident, is_signer)
-                }
-            };
-            quote! {
-                account_metas.extend(self.#name.to_account_metas(#is_signer));
-            }
-        })
-        .collect();
-
-    let name = &accs.ident;
-    let (combined_generics, trait_generics, strct_generics) = match accs.generics.lt_token {
-        None => (quote! {<'info>}, quote! {<'info>}, quote! {}),
-        Some(_) => {
-            let g = &accs.generics;
-            (quote! {#g}, quote! {#g}, quote! {#g})
-        }
-    };
-
-    let account_mod_name: proc_macro2::TokenStream = format!(
-        "__client_accounts_{}",
-        accs.ident.to_string().to_snake_case()
-    )
-    .parse()
-    .unwrap();
-
-    let account_struct_fields: Vec<proc_macro2::TokenStream> = accs
-        .fields
-        .iter()
-        .map(|f: &AccountField| match f {
-            AccountField::AccountsStruct(s) => {
-                let name = &s.ident;
-                let symbol: proc_macro2::TokenStream = format!(
-                    "__client_accounts_{0}::{1}",
-                    s.symbol.to_snake_case(),
-                    s.symbol,
-                )
-                .parse()
-                .unwrap();
-                quote! {
-                    pub #name: #symbol
-                }
-            }
-            AccountField::Field(f) => {
-                let name = &f.ident;
-                quote! {
-                    pub #name: anchor_lang::solana_program::pubkey::Pubkey
-                }
-            }
-        })
-        .collect();
-
-    let account_struct_metas: Vec<proc_macro2::TokenStream> = accs
-        .fields
-        .iter()
-        .map(|f: &AccountField| match f {
-            AccountField::AccountsStruct(s) => {
-                let name = &s.ident;
-                quote! {
-                    account_metas.extend(self.#name.to_account_metas(None));
-                }
-            }
-            AccountField::Field(f) => {
-                let is_signer = match f.is_signer {
-                    false => quote! {false},
-                    true => quote! {true},
-                };
-                let meta = match f.is_mut {
-                    false => quote! { anchor_lang::solana_program::instruction::AccountMeta::new_readonly },
-                    true => quote! { anchor_lang::solana_program::instruction::AccountMeta::new },
-                };
-                let name = &f.ident;
-                quote! {
-                    account_metas.push(#meta(self.#name, #is_signer));
-                }
-            }
-        })
-        .collect();
-
-    // Re-export all composite account structs (i.e. other structs deriving
-    // accounts embedded into this struct. Required because, these embedded
-    // structs are *not* visible from the #[program] macro, which is responsible
-    // for generating the `accounts` mod, which aggregates all the the generated
-    // accounts used for structs.
-    let re_exports: Vec<proc_macro2::TokenStream> = {
-        // First, dedup the exports.
-        let mut re_exports = std::collections::HashSet::new();
-        for f in accs.fields.iter().filter_map(|f: &AccountField| match f {
-            AccountField::AccountsStruct(s) => Some(s),
-            AccountField::Field(_) => None,
-        }) {
-            re_exports.insert(format!(
-                "__client_accounts_{0}::{1}",
-                f.symbol.to_snake_case(),
-                f.symbol,
-            ));
-        }
-
-        re_exports
-            .iter()
-            .map(|symbol: &String| {
-                let symbol: proc_macro2::TokenStream = symbol.parse().unwrap();
-                quote! {
-                    pub use #symbol;
-                }
-            })
-            .collect()
-    };
-
-    quote! {
-        /// An internal, Anchor generated module. This is used (as an
-        /// implementation detail), to generate a struct for a given
-        /// `#[derive(Accounts)]` implementation, where each field is a Pubkey,
-        /// instead of an `AccountInfo`. This is useful for clients that want
-        /// to generate a list of accounts, without explicitly knowing the
-        /// order all the fields should be in.
-        ///
-        /// To access the struct in this module, one should use the sibling
-        /// `accounts` module (also generated), which re-exports this.
-        mod #account_mod_name {
-            use super::*;
-            use anchor_lang::prelude::borsh;
-            #(#re_exports)*
-
-            #[derive(anchor_lang::AnchorSerialize)]
-            pub struct #name {
-                #(#account_struct_fields),*
-            }
-
-            impl anchor_lang::ToAccountMetas for #name {
-                fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<anchor_lang::solana_program::instruction::AccountMeta> {
-                    let mut account_metas = vec![];
-
-                    #(#account_struct_metas)*
-
-                    account_metas
-                }
-            }
-        }
-
-        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>]) -> std::result::Result<Self, anchor_lang::solana_program::program_error::ProgramError> {
-                // 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)*
-                // Success. Return the validated accounts.
-                Ok(#name {
-                    #(#return_tys),*
-                })
-            }
-        }
-
-        impl#combined_generics anchor_lang::ToAccountInfos#trait_generics for #name#strct_generics {
-            fn to_account_infos(&self) -> Vec<anchor_lang::solana_program::account_info::AccountInfo<'info>> {
-                let mut account_infos = vec![];
-
-                #(#to_acc_infos)*
-
-                account_infos
-            }
-        }
-
-        impl#combined_generics anchor_lang::ToAccountMetas for #name#strct_generics {
-            fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<anchor_lang::solana_program::instruction::AccountMeta> {
-                let mut account_metas = vec![];
-
-                #(#to_acc_metas)*
-
-
-                account_metas
-            }
-        }
-
-        impl#combined_generics anchor_lang::AccountsExit#trait_generics for #name#strct_generics {
-            fn exit(&self, program_id: &anchor_lang::solana_program::pubkey::Pubkey) -> anchor_lang::solana_program::entrypoint::ProgramResult {
-                #(#on_save)*
-                Ok(())
-            }
-        }
-    }
-}
-
-// Returns true if the given AccountField has an associated init constraint.
-fn is_associated_init(af: &AccountField) -> bool {
-    match af {
-        AccountField::AccountsStruct(_s) => false,
-        AccountField::Field(f) => f
-            .constraints
-            .iter()
-            .filter(|c| match c {
-                Constraint::Associated(c) => c.is_init,
-                _ => false,
-            })
-            .next()
-            .is_some(),
-    }
-}
-
-pub fn generate_field_constraint(f: &Field, c: &Constraint) -> proc_macro2::TokenStream {
-    match c {
-        Constraint::BelongsTo(c) => generate_constraint_belongs_to(f, c),
-        Constraint::Signer(c) => generate_constraint_signer(f, c),
-        Constraint::Literal(c) => generate_constraint_literal(c),
-        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),
-        Constraint::State(c) => generate_constraint_state(f, c),
-        Constraint::Associated(c) => generate_constraint_associated(f, c),
-    }
-}
-
-pub fn generate_composite_constraint(
-    _f: &CompositeField,
-    c: &Constraint,
-) -> proc_macro2::TokenStream {
-    match c {
-        Constraint::Literal(c) => generate_constraint_literal(c),
-        _ => panic!("Composite fields can only use literal constraints"),
-    }
-}
-
-pub fn generate_constraint_belongs_to(
-    f: &Field,
-    c: &ConstraintBelongsTo,
-) -> proc_macro2::TokenStream {
-    let target = c.join_target.clone();
-    let ident = &f.ident;
-    let field = match &f.ty {
-        Ty::Loader(_) => quote! {#ident.load()?},
-        _ => quote! {#ident},
-    };
-    quote! {
-        if &#field.#target != #target.to_account_info().key {
-            return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo: error codes
-        }
-    }
-}
-
-pub fn generate_constraint_signer(f: &Field, _c: &ConstraintSigner) -> 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: signer cannot be specified."),
-    };
-    quote! {
-        // Don't enforce on CPI, since usually a program is signing and so
-        // the `try_accounts` deserializatoin will fail *if* the one
-        // tries to manually invoke it.
-        //
-        // This check will be performed on the other end of the invocation.
-        if cfg!(not(feature = "cpi")) {
-            if !#info.is_signer {
-                return Err(anchor_lang::solana_program::program_error::ProgramError::MissingRequiredSignature);
-            }
-        }
-    }
-}
-
-pub fn generate_constraint_literal(c: &ConstraintLiteral) -> proc_macro2::TokenStream {
-    let tokens = &c.tokens;
-    quote! {
-        if !(#tokens) {
-            return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo: error codes
-        }
-    }
-}
-
-pub fn generate_constraint_owner(f: &Field, c: &ConstraintOwner) -> proc_macro2::TokenStream {
-    let ident = &f.ident;
-    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.
-        }
-    }
-}
-
-pub fn generate_constraint_rent_exempt(
-    f: &Field,
-    c: &ConstraintRentExempt,
-) -> proc_macro2::TokenStream {
-    let ident = &f.ident;
-    let info = match f.ty {
-        Ty::AccountInfo => quote! { #ident },
-        Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
-        Ty::Loader(_) => quote! { #ident.to_account_info() },
-        _ => panic!("Invalid syntax: rent exemption cannot be specified."),
-    };
-    match c {
-        ConstraintRentExempt::Skip => quote! {},
-        ConstraintRentExempt::Enforce => quote! {
-            if !rent.is_exempt(#info.lamports(), #info.try_data_len()?) {
-                return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(2)); // todo: error codes
-            }
-        },
-    }
-}
-
-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::solana_program::program_error::ProgramError::Custom(1))?; // todo
-        if #name.to_account_info().key != &program_signer {
-            return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo
-        }
-    }
-}
-
-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
-        }
-    }
-}
-
-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(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.
-        }
-    }
-}
-
-pub fn generate_constraint_associated(
-    f: &Field,
-    c: &ConstraintAssociated,
-) -> proc_macro2::TokenStream {
-    if c.is_init {
-        generate_constraint_associated_init(f, c)
-    } else {
-        generate_constraint_associated_seeds(f, c)
-    }
-}
-pub fn generate_constraint_associated_init(
-    f: &Field,
-    c: &ConstraintAssociated,
-) -> proc_macro2::TokenStream {
-    let associated_target = c.associated_target.clone();
-    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"),
-    };
-
-    let space = match &f.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 {
-            false => {
-                quote! {
-                    let space = 8 + #account_ty::default().try_to_vec().unwrap().len();
-                }
-            }
-            true => {
-                quote! {
-                    let space = 8 + anchor_lang::__private::bytemuck::bytes_of(&#account_ty::default()).len();
-                }
-            }
-        },
-        // Explicit account size given. Use it.
-        Some(s) => quote! {
-            let space = #s;
-        },
-    };
-
-    let payer = match &f.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 f.associated_seeds.len() {
-        0 => quote! {
-            [
-                &b"anchor"[..],
-                #associated_target.to_account_info().key.as_ref(),
-                &[nonce],
-            ]
-        },
-        _ => {
-            let seeds = to_seeds_tts(&f.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
-        },
-        true => quote! {
-            anchor_lang::Loader
-        },
-    };
-    let nonce_assignment = match is_zero_copy {
-        false => quote! {},
-        // Zero copy is not deserialized, so the data must be lazy loaded.
-        true => quote! {
-            .load_init()?
-        },
-    };
-
-    quote! {
-        let #field: #account_wrapper_ty<#account_ty> = {
-            #space
-            #payer
-
-            #associated_pubkey_and_nonce
-
-            if &__associated_field != #field.key {
-                return Err(ProgramError::Custom(45)); // todo: proper error.
-            }
-            let lamports = rent.minimum_balance(space);
-            let ix = anchor_lang::solana_program::system_instruction::create_account(
-                payer.key,
-                #field.key,
-                lamports,
-                space as u64,
-                program_id,
-            );
-
-            let seeds = #seeds_with_nonce;
-            let signer = &[&seeds[..]];
-            anchor_lang::solana_program::program::invoke_signed(
-                &ix,
-                &[
-
-                    #field.clone(),
-                    payer.clone(),
-                    system_program.clone(),
-                ],
-                signer,
-            ).map_err(|e| {
-                anchor_lang::solana_program::msg!("Unable to create associated account");
-                e
-            })?;
-            // 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,
-            )?;
-            pa#nonce_assignment.__nonce = nonce;
-            pa
-        };
-    }
-}
-
-pub fn generate_constraint_associated_seeds(
-    f: &Field,
-    c: &ConstraintAssociated,
-) -> 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 {
-            // TODO: proper error.
-            return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(45));
-        }
-    }
-}
-pub fn generate_associated_pubkey(f: &Field, c: &ConstraintAssociated) -> proc_macro2::TokenStream {
-    let associated_target = c.associated_target.clone();
-    let seeds_no_nonce = match f.associated_seeds.len() {
-        0 => quote! {
-            [
-                &b"anchor"[..],
-                #associated_target.to_account_info().key.as_ref(),
-            ]
-        },
-        _ => {
-            let seeds = to_seeds_tts(&f.associated_seeds);
-            quote! {
-                [
-                    &b"anchor"[..],
-                    #associated_target.to_account_info().key.as_ref(),
-                    #seeds
-                ]
-            }
-        }
-    };
-    quote! {
-        let (__associated_field, nonce) = Pubkey::find_program_address(
-            &#seeds_no_nonce,
-            program_id,
-        );
-    }
-}
-
-// Returns the inner part of the seeds slice as a token stream.
-fn to_seeds_tts(seeds: &[syn::Ident]) -> proc_macro2::TokenStream {
-    assert!(seeds.len() > 0);
-    let seed_0 = &seeds[0];
-    let mut tts = quote! {
-        #seed_0.to_account_info().key.as_ref(),
-    };
-    for seed in &seeds[1..] {
-        tts = quote! {
-            #tts
-            #seed.to_account_info().key.as_ref(),
-        };
-    }
-    tts
-}

+ 129 - 0
lang/syn/src/codegen/accounts/__client_accounts.rs

@@ -0,0 +1,129 @@
+use crate::{AccountField, AccountsStruct};
+use heck::SnakeCase;
+use quote::quote;
+
+// Generates the private `__client_accounts` mod implementation, containing
+// a generated struct mapping 1-1 to the `Accounts` struct, except with
+// `Pubkey`s as the types. This is generated for Rust *clients*.
+pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
+    let name = &accs.ident;
+    let account_mod_name: proc_macro2::TokenStream = format!(
+        "__client_accounts_{}",
+        accs.ident.to_string().to_snake_case()
+    )
+    .parse()
+    .unwrap();
+
+    let account_struct_fields: Vec<proc_macro2::TokenStream> = accs
+        .fields
+        .iter()
+        .map(|f: &AccountField| match f {
+            AccountField::CompositeField(s) => {
+                let name = &s.ident;
+                let symbol: proc_macro2::TokenStream = format!(
+                    "__client_accounts_{0}::{1}",
+                    s.symbol.to_snake_case(),
+                    s.symbol,
+                )
+                .parse()
+                .unwrap();
+                quote! {
+                    pub #name: #symbol
+                }
+            }
+            AccountField::Field(f) => {
+                let name = &f.ident;
+                quote! {
+                    pub #name: anchor_lang::solana_program::pubkey::Pubkey
+                }
+            }
+        })
+        .collect();
+
+    let account_struct_metas: Vec<proc_macro2::TokenStream> = accs
+        .fields
+        .iter()
+        .map(|f: &AccountField| match f {
+            AccountField::CompositeField(s) => {
+                let name = &s.ident;
+                quote! {
+                    account_metas.extend(self.#name.to_account_metas(None));
+                }
+            }
+            AccountField::Field(f) => {
+                let is_signer = match f.constraints.is_signer() {
+                    false => quote! {false},
+                    true => quote! {true},
+                };
+                let meta = match f.constraints.is_mutable() {
+                    false => quote! { anchor_lang::solana_program::instruction::AccountMeta::new_readonly },
+                    true => quote! { anchor_lang::solana_program::instruction::AccountMeta::new },
+                };
+                let name = &f.ident;
+                quote! {
+                    account_metas.push(#meta(self.#name, #is_signer));
+                }
+            }
+        })
+        .collect();
+    // Re-export all composite account structs (i.e. other structs deriving
+    // accounts embedded into this struct. Required because, these embedded
+    // structs are *not* visible from the #[program] macro, which is responsible
+    // for generating the `accounts` mod, which aggregates all the the generated
+    // accounts used for structs.
+    let re_exports: Vec<proc_macro2::TokenStream> = {
+        // First, dedup the exports.
+        let mut re_exports = std::collections::HashSet::new();
+        for f in accs.fields.iter().filter_map(|f: &AccountField| match f {
+            AccountField::CompositeField(s) => Some(s),
+            AccountField::Field(_) => None,
+        }) {
+            re_exports.insert(format!(
+                "__client_accounts_{0}::{1}",
+                f.symbol.to_snake_case(),
+                f.symbol,
+            ));
+        }
+
+        re_exports
+            .iter()
+            .map(|symbol: &String| {
+                let symbol: proc_macro2::TokenStream = symbol.parse().unwrap();
+                quote! {
+                    pub use #symbol;
+                }
+            })
+            .collect()
+    };
+    quote! {
+        /// An internal, Anchor generated module. This is used (as an
+        /// implementation detail), to generate a struct for a given
+        /// `#[derive(Accounts)]` implementation, where each field is a Pubkey,
+        /// instead of an `AccountInfo`. This is useful for clients that want
+        /// to generate a list of accounts, without explicitly knowing the
+        /// order all the fields should be in.
+        ///
+        /// To access the struct in this module, one should use the sibling
+        /// `accounts` module (also generated), which re-exports this.
+        mod #account_mod_name {
+            use super::*;
+            use anchor_lang::prelude::borsh;
+            #(#re_exports)*
+
+            #[derive(anchor_lang::AnchorSerialize)]
+            pub struct #name {
+                #(#account_struct_fields),*
+            }
+
+            impl anchor_lang::ToAccountMetas for #name {
+                fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<anchor_lang::solana_program::instruction::AccountMeta> {
+                    let mut account_metas = vec![];
+
+                    #(#account_struct_metas)*
+
+                    account_metas
+                }
+            }
+        }
+    }
+}

+ 471 - 0
lang/syn/src/codegen/accounts/constraints.rs

@@ -0,0 +1,471 @@
+use crate::{
+    CompositeField, Constraint, ConstraintAssociatedGroup, ConstraintBelongsTo,
+    ConstraintExecutable, ConstraintGroup, ConstraintInit, ConstraintLiteral, ConstraintMut,
+    ConstraintOwner, ConstraintRaw, ConstraintRentExempt, ConstraintSeeds, ConstraintSigner,
+    ConstraintState, Field, Ty,
+};
+use quote::quote;
+
+pub fn generate(f: &Field) -> proc_macro2::TokenStream {
+    let checks: Vec<proc_macro2::TokenStream> = linearize(&f.constraints)
+        .iter()
+        .map(|c| generate_constraint(f, c))
+        .collect();
+    quote! {
+        #(#checks)*
+    }
+}
+
+pub fn generate_composite(f: &CompositeField) -> proc_macro2::TokenStream {
+    let checks: Vec<proc_macro2::TokenStream> = linearize(&f.constraints)
+        .iter()
+        .filter_map(|c| match c {
+            Constraint::Raw(_) => Some(c),
+            Constraint::Literal(_) => Some(c),
+            _ => panic!("Invariant violation: composite constraints can only be raw or literals"),
+        })
+        .map(|c| generate_constraint_composite(f, c))
+        .collect();
+    quote! {
+        #(#checks)*
+    }
+}
+
+// Linearizes the constraint group so that constraints with dependencies
+// run after those without.
+//
+// The associated cosntraint should always be first since it may also create
+// an account with a PDA.
+pub fn linearize(c_group: &ConstraintGroup) -> Vec<Constraint> {
+    let ConstraintGroup {
+        init,
+        mutable,
+        signer,
+        belongs_to,
+        literal,
+        raw,
+        owner,
+        rent_exempt,
+        seeds,
+        executable,
+        state,
+        associated,
+    } = c_group.clone();
+
+    let mut constraints = Vec::new();
+
+    if let Some(c) = associated {
+        constraints.push(Constraint::AssociatedGroup(c));
+    }
+    if let Some(c) = init {
+        constraints.push(Constraint::Init(c));
+    }
+    if let Some(c) = mutable {
+        constraints.push(Constraint::Mut(c));
+    }
+    if let Some(c) = signer {
+        constraints.push(Constraint::Signer(c));
+    }
+    constraints.append(
+        &mut belongs_to
+            .into_iter()
+            .map(|c| Constraint::BelongsTo(c))
+            .collect(),
+    );
+    constraints.append(
+        &mut literal
+            .into_iter()
+            .map(|c| Constraint::Literal(c))
+            .collect(),
+    );
+    constraints.append(&mut raw.into_iter().map(|c| Constraint::Raw(c)).collect());
+    if let Some(c) = owner {
+        constraints.push(Constraint::Owner(c));
+    }
+    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));
+    }
+    if let Some(c) = state {
+        constraints.push(Constraint::State(c));
+    }
+    constraints
+}
+
+fn generate_constraint(f: &Field, c: &Constraint) -> proc_macro2::TokenStream {
+    match c {
+        Constraint::Init(c) => generate_constraint_init(f, c),
+        Constraint::Mut(c) => generate_constraint_mut(f, c),
+        Constraint::BelongsTo(c) => generate_constraint_belongs_to(f, c),
+        Constraint::Signer(c) => generate_constraint_signer(f, c),
+        Constraint::Literal(c) => generate_constraint_literal(c),
+        Constraint::Raw(c) => generate_constraint_raw(c),
+        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),
+        Constraint::State(c) => generate_constraint_state(f, c),
+        Constraint::AssociatedGroup(c) => generate_constraint_associated(f, c),
+    }
+}
+
+fn generate_constraint_composite(_f: &CompositeField, c: &Constraint) -> proc_macro2::TokenStream {
+    match c {
+        Constraint::Raw(c) => generate_constraint_raw(c),
+        Constraint::Literal(c) => generate_constraint_literal(c),
+        _ => panic!("Invariant violation"),
+    }
+}
+
+pub fn generate_constraint_init(_f: &Field, _c: &ConstraintInit) -> proc_macro2::TokenStream {
+    quote! {}
+}
+
+pub fn generate_constraint_mut(f: &Field, _c: &ConstraintMut) -> proc_macro2::TokenStream {
+    let ident = &f.ident;
+    quote! {
+        if !#ident.to_account_info().is_writable {
+            return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(36)); // todo: error codes
+        }
+    }
+}
+
+pub fn generate_constraint_belongs_to(
+    f: &Field,
+    c: &ConstraintBelongsTo,
+) -> proc_macro2::TokenStream {
+    let target = c.join_target.clone();
+    let ident = &f.ident;
+    let field = match &f.ty {
+        Ty::Loader(_) => quote! {#ident.load()?},
+        _ => quote! {#ident},
+    };
+    quote! {
+        if &#field.#target != #target.to_account_info().key {
+            return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo: error codes
+        }
+    }
+}
+
+pub fn generate_constraint_signer(f: &Field, _c: &ConstraintSigner) -> 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: signer cannot be specified."),
+    };
+    quote! {
+        // Don't enforce on CPI, since usually a program is signing and so
+        // the `try_accounts` deserializatoin will fail *if* the one
+        // tries to manually invoke it.
+        //
+        // This check will be performed on the other end of the invocation.
+        if cfg!(not(feature = "cpi")) {
+            if !#info.to_account_info().is_signer {
+                return Err(anchor_lang::solana_program::program_error::ProgramError::MissingRequiredSignature);
+            }
+        }
+    }
+}
+
+pub fn generate_constraint_literal(c: &ConstraintLiteral) -> proc_macro2::TokenStream {
+    let lit: proc_macro2::TokenStream = {
+        let lit = &c.lit;
+        let lit_ts: proc_macro2::TokenStream = quote! {#lit};
+        lit_ts.to_string().replace("\"", "").parse().unwrap()
+    };
+    quote! {
+        if !(#lit) {
+            return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo: error codes
+        }
+    }
+}
+
+pub fn generate_constraint_raw(c: &ConstraintRaw) -> proc_macro2::TokenStream {
+    let raw = &c.raw;
+    quote! {
+        if !(#raw) {
+            return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(14)); // todo: error codes
+        }
+    }
+}
+
+pub fn generate_constraint_owner(f: &Field, c: &ConstraintOwner) -> proc_macro2::TokenStream {
+    let ident = &f.ident;
+    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.
+        }
+    }
+}
+
+pub fn generate_constraint_rent_exempt(
+    f: &Field,
+    c: &ConstraintRentExempt,
+) -> proc_macro2::TokenStream {
+    let ident = &f.ident;
+    let info = match f.ty {
+        Ty::AccountInfo => quote! { #ident },
+        Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
+        Ty::Loader(_) => quote! { #ident.to_account_info() },
+        _ => panic!("Invalid syntax: rent exemption cannot be specified."),
+    };
+    match c {
+        ConstraintRentExempt::Skip => quote! {},
+        ConstraintRentExempt::Enforce => quote! {
+            if !rent.is_exempt(#info.lamports(), #info.try_data_len()?) {
+                return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(2)); // todo: error codes
+            }
+        },
+    }
+}
+
+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::solana_program::program_error::ProgramError::Custom(1))?; // todo
+        if #name.to_account_info().key != &program_signer {
+            return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo
+        }
+    }
+}
+
+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
+        }
+    }
+}
+
+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(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.
+        }
+    }
+}
+
+pub fn generate_constraint_associated(
+    f: &Field,
+    c: &ConstraintAssociatedGroup,
+) -> proc_macro2::TokenStream {
+    if c.is_init {
+        generate_constraint_associated_init(f, c)
+    } else {
+        generate_constraint_associated_seeds(f, c)
+    }
+}
+
+pub fn generate_constraint_associated_init(
+    f: &Field,
+    c: &ConstraintAssociatedGroup,
+) -> proc_macro2::TokenStream {
+    let associated_target = c.associated_target.clone();
+    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"),
+    };
+
+    let space = match &c.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 {
+            false => {
+                quote! {
+                    let space = 8 + #account_ty::default().try_to_vec().unwrap().len();
+                }
+            }
+            true => {
+                quote! {
+                    let space = 8 + anchor_lang::__private::bytemuck::bytes_of(&#account_ty::default()).len();
+                }
+            }
+        },
+        // Explicit account size given. Use it.
+        Some(s) => quote! {
+            let space = #s;
+        },
+    };
+
+    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
+        },
+        true => quote! {
+            anchor_lang::Loader
+        },
+    };
+    let nonce_assignment = match is_zero_copy {
+        false => quote! {},
+        // Zero copy is not deserialized, so the data must be lazy loaded.
+        true => quote! {
+            .load_init()?
+        },
+    };
+
+    quote! {
+        let #field: #account_wrapper_ty<#account_ty> = {
+            #space
+            #payer
+
+            #associated_pubkey_and_nonce
+
+            if &__associated_field != #field.key {
+                return Err(ProgramError::Custom(45)); // todo: proper error.
+            }
+            let lamports = rent.minimum_balance(space);
+            let ix = anchor_lang::solana_program::system_instruction::create_account(
+                payer.key,
+                #field.key,
+                lamports,
+                space as u64,
+                program_id,
+            );
+
+            let seeds = #seeds_with_nonce;
+            let signer = &[&seeds[..]];
+            anchor_lang::solana_program::program::invoke_signed(
+                &ix,
+                &[
+
+                    #field.clone(),
+                    payer.clone(),
+                    system_program.clone(),
+                ],
+                signer,
+            ).map_err(|e| {
+                anchor_lang::solana_program::msg!("Unable to create associated account");
+                e
+            })?;
+            // 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,
+            )?;
+            pa#nonce_assignment.__nonce = nonce;
+            pa
+        };
+    }
+}
+
+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 {
+            // TODO: proper error.
+            return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(45));
+        }
+    }
+}
+
+pub fn generate_associated_pubkey(
+    _f: &Field,
+    c: &ConstraintAssociatedGroup,
+) -> proc_macro2::TokenStream {
+    let associated_target = c.associated_target.clone();
+    let seeds_no_nonce = match c.associated_seeds.len() {
+        0 => quote! {
+            [
+                &b"anchor"[..],
+                #associated_target.to_account_info().key.as_ref(),
+            ]
+        },
+        _ => {
+            let seeds = to_seeds_tts(&c.associated_seeds);
+            quote! {
+                [
+                    &b"anchor"[..],
+                    #associated_target.to_account_info().key.as_ref(),
+                    #seeds
+                ]
+            }
+        }
+    };
+    quote! {
+        let (__associated_field, nonce) = Pubkey::find_program_address(
+            &#seeds_no_nonce,
+            program_id,
+        );
+    }
+}
+
+// Returns the inner part of the seeds slice as a token stream.
+fn to_seeds_tts(seeds: &[syn::Ident]) -> proc_macro2::TokenStream {
+    assert!(seeds.len() > 0);
+    let seed_0 = &seeds[0];
+    let mut tts = quote! {
+        #seed_0.to_account_info().key.as_ref(),
+    };
+    for seed in &seeds[1..] {
+        tts = quote! {
+            #tts
+            #seed.to_account_info().key.as_ref(),
+        };
+    }
+    tts
+}

+ 39 - 0
lang/syn/src/codegen/accounts/exit.rs

@@ -0,0 +1,39 @@
+use crate::codegen::accounts::generics;
+use crate::{AccountField, AccountsStruct};
+use quote::quote;
+
+// Generates the `Exit` trait implementation.
+pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
+    let name = &accs.ident;
+    let (combined_generics, trait_generics, strct_generics) = generics(accs);
+
+    let on_save: Vec<proc_macro2::TokenStream> = accs
+        .fields
+        .iter()
+        .map(|af: &AccountField| match af {
+            AccountField::CompositeField(s) => {
+                let name = &s.ident;
+                quote! {
+                    anchor_lang::AccountsExit::exit(&self.#name, program_id)?;
+                }
+            }
+            AccountField::Field(f) => {
+                let ident = &f.ident;
+                match f.constraints.is_mutable() {
+                    false => quote! {},
+                    true => quote! {
+                        anchor_lang::AccountsExit::exit(&self.#ident, program_id)?;
+                    },
+                }
+            }
+        })
+        .collect();
+    quote! {
+        impl#combined_generics anchor_lang::AccountsExit#trait_generics for #name#strct_generics {
+            fn exit(&self, program_id: &anchor_lang::solana_program::pubkey::Pubkey) -> anchor_lang::solana_program::entrypoint::ProgramResult {
+                #(#on_save)*
+                Ok(())
+            }
+        }
+    }
+}

+ 43 - 0
lang/syn/src/codegen/accounts/mod.rs

@@ -0,0 +1,43 @@
+use crate::AccountsStruct;
+use quote::quote;
+
+mod __client_accounts;
+mod constraints;
+mod exit;
+mod to_account_infos;
+mod to_account_metas;
+mod try_accounts;
+
+pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
+    let impl_try_accounts = try_accounts::generate(accs);
+    let impl_to_account_infos = to_account_infos::generate(accs);
+    let impl_to_account_metas = to_account_metas::generate(accs);
+    let impl_exit = exit::generate(accs);
+
+    let __client_accounts_mod = __client_accounts::generate(accs);
+
+    quote! {
+        #impl_try_accounts
+        #impl_to_account_infos
+        #impl_to_account_metas
+        #impl_exit
+
+        #__client_accounts_mod
+    }
+}
+
+fn generics(
+    accs: &AccountsStruct,
+) -> (
+    proc_macro2::TokenStream,
+    proc_macro2::TokenStream,
+    proc_macro2::TokenStream,
+) {
+    match accs.generics.lt_token {
+        None => (quote! {<'info>}, quote! {<'info>}, quote! {}),
+        Some(_) => {
+            let g = &accs.generics;
+            (quote! {#g}, quote! {#g}, quote! {#g})
+        }
+    }
+}

+ 34 - 0
lang/syn/src/codegen/accounts/to_account_infos.rs

@@ -0,0 +1,34 @@
+use crate::codegen::accounts::generics;
+use crate::{AccountField, AccountsStruct};
+use quote::quote;
+
+// Generates the `ToAccountInfos` trait implementation.
+pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
+    let name = &accs.ident;
+    let (combined_generics, trait_generics, strct_generics) = generics(accs);
+
+    let to_acc_infos: 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! {
+                account_infos.extend(self.#name.to_account_infos());
+            }
+        })
+        .collect();
+    quote! {
+        impl#combined_generics anchor_lang::ToAccountInfos#trait_generics for #name#strct_generics {
+            fn to_account_infos(&self) -> Vec<anchor_lang::solana_program::account_info::AccountInfo<'info>> {
+                let mut account_infos = vec![];
+
+                #(#to_acc_infos)*
+
+                account_infos
+            }
+        }
+    }
+}

+ 40 - 0
lang/syn/src/codegen/accounts/to_account_metas.rs

@@ -0,0 +1,40 @@
+use crate::codegen::accounts::generics;
+use crate::{AccountField, AccountsStruct};
+use quote::quote;
+
+// Generates the `ToAccountMetas` trait implementation.
+pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
+    let name = &accs.ident;
+    let (combined_generics, _trait_generics, strct_generics) = generics(accs);
+
+    let to_acc_metas: Vec<proc_macro2::TokenStream> = accs
+        .fields
+        .iter()
+        .map(|f: &AccountField| {
+            let (name, is_signer) = match f {
+                AccountField::CompositeField(s) => (&s.ident, quote! {None}),
+                AccountField::Field(f) => {
+                    let is_signer = match f.constraints.is_signer() {
+                        false => quote! {None},
+                        true => quote! {Some(true)},
+                    };
+                    (&f.ident, is_signer)
+                }
+            };
+            quote! {
+                account_metas.extend(self.#name.to_account_metas(#is_signer));
+            }
+        })
+        .collect();
+    quote! {
+        impl#combined_generics anchor_lang::ToAccountMetas for #name#strct_generics {
+            fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<anchor_lang::solana_program::instruction::AccountMeta> {
+                let mut account_metas = vec![];
+
+                #(#to_acc_metas)*
+
+                account_metas
+            }
+        }
+    }
+}

+ 198 - 0
lang/syn/src/codegen/accounts/try_accounts.rs

@@ -0,0 +1,198 @@
+use crate::codegen::accounts::{constraints, generics};
+use crate::{AccountField, AccountsStruct, Field, SysvarTy, Ty};
+use proc_macro2::TokenStream;
+use quote::quote;
+
+// 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
+        .iter()
+        .map(|af: &AccountField| {
+            match af {
+                AccountField::CompositeField(s) => {
+                    let name = &s.ident;
+                    let ty = &s.raw_field.ty;
+                    quote! {
+                        #[cfg(feature = "anchor-debug")]
+                        ::solana_program::log::sol_log(stringify!(#name));
+                        let #name: #ty = anchor_lang::Accounts::try_accounts(program_id, accounts)?;
+                    }
+                }
+                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) {
+                        let name = &f.ident;
+                        quote!{
+                            let #name = &accounts[0];
+                            *accounts = &accounts[1..];
+                        }
+                    } else {
+                        let name = typed_ident(&f);
+                        match f.constraints.is_init() {
+                            false => quote! {
+                                #[cfg(feature = "anchor-debug")]
+                                ::solana_program::log::sol_log(stringify!(#name));
+                                let #name = anchor_lang::Accounts::try_accounts(program_id, accounts)?;
+                            },
+                            true => quote! {
+                                #[cfg(feature = "anchor-debug")]
+                                ::solana_program::log::sol_log(stringify!(#name));
+                                let #name = anchor_lang::AccountsInit::try_accounts_init(program_id, accounts)?;
+                            },
+                        }
+                    }
+                }
+            }
+        })
+        .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();
+
+    // 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! {
+        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>],
+            ) -> std::result::Result<Self, anchor_lang::solana_program::program_error::ProgramError> {
+                // 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)*
+                // Success. Return the validated accounts.
+                Ok(#name {
+                    #(#return_tys),*
+                })
+            }
+        }
+    }
+}
+
+// Returns true if the given AccountField has an associated init constraint.
+fn is_associated_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),
+    }
+}
+
+fn typed_ident(field: &Field) -> TokenStream {
+    let name = &field.ident;
+
+    let ty = match &field.ty {
+        Ty::AccountInfo => quote! { AccountInfo },
+        Ty::ProgramState(ty) => {
+            let account = &ty.account_ident;
+            quote! {
+                ProgramState<#account>
+            }
+        }
+        Ty::CpiState(ty) => {
+            let account = &ty.account_ident;
+            quote! {
+                CpiState<#account>
+            }
+        }
+        Ty::ProgramAccount(ty) => {
+            let account = &ty.account_ident;
+            quote! {
+                ProgramAccount<#account>
+            }
+        }
+        Ty::Loader(ty) => {
+            let account = &ty.account_ident;
+            quote! {
+                Loader<#account>
+            }
+        }
+        Ty::CpiAccount(ty) => {
+            let account = &ty.account_ident;
+            quote! {
+                CpiAccount<#account>
+            }
+        }
+        Ty::Sysvar(ty) => {
+            let account = match ty {
+                SysvarTy::Clock => quote! {Clock},
+                SysvarTy::Rent => quote! {Rent},
+                SysvarTy::EpochSchedule => quote! {EpochSchedule},
+                SysvarTy::Fees => quote! {Fees},
+                SysvarTy::RecentBlockhashes => quote! {RecentBlockhashes},
+                SysvarTy::SlotHashes => quote! {SlotHashes},
+                SysvarTy::SlotHistory => quote! {SlotHistory},
+                SysvarTy::StakeHistory => quote! {StakeHistory},
+                SysvarTy::Instructions => quote! {Instructions},
+                SysvarTy::Rewards => quote! {Rewards},
+            };
+            quote! {
+                Sysvar<#account>
+            }
+        }
+    };
+
+    quote! {
+        #name: #ty
+    }
+}

+ 0 - 1379
lang/syn/src/codegen/program.rs

@@ -1,1379 +0,0 @@
-use crate::parser;
-use crate::{IxArg, Program, State, StateIx};
-use heck::{CamelCase, SnakeCase};
-use quote::quote;
-
-// Namespace for calculating state instruction sighash signatures.
-const SIGHASH_STATE_NAMESPACE: &str = "state";
-
-// Namespace for calculating instruction sighash signatures for any instruction
-// not affecting program state.
-const SIGHASH_GLOBAL_NAMESPACE: &str = "global";
-
-pub fn generate(program: Program) -> proc_macro2::TokenStream {
-    let mod_name = &program.name;
-    let dispatch = generate_dispatch(&program);
-    let handlers_non_inlined = generate_non_inlined_handlers(&program);
-    let methods = generate_methods(&program);
-    let ixs = generate_ixs(&program);
-    let cpi = generate_cpi(&program);
-    let accounts = generate_accounts(&program);
-
-    quote! {
-        // TODO: remove once we allow segmented paths in `Accounts` structs.
-        use #mod_name::*;
-
-        #[cfg(not(feature = "no-entrypoint"))]
-        anchor_lang::solana_program::entrypoint!(entry);
-        /// The Anchor codegen exposes a programming model where a user defines
-        /// a set of methods inside of a `#[program]` module in a way similar
-        /// to writing RPC request handlers. The macro then generates a bunch of
-        /// code wrapping these user defined methods into something that can be
-        /// executed on Solana.
-        ///
-        /// These methods fall into one of three categories, each of which
-        /// can be considered a different "namespace" of the program.
-        ///
-        /// 1) Global methods - regular methods inside of the `#[program]`.
-        /// 2) State methods - associated methods inside a `#[state]` struct.
-        /// 3) Interface methods - methods inside a strait struct's
-        ///    implementation of an `#[interface]` trait.
-        ///
-        /// Care must be taken by the codegen to prevent collisions between
-        /// methods in these different namespaces. For this reason, Anchor uses
-        /// a variant of sighash to perform method dispatch, rather than
-        /// something like a simple enum variant discriminator.
-        ///
-        /// The execution flow of the generated code can be roughly outlined:
-        ///
-        /// * Start program via the entrypoint.
-        /// * Strip method identifier off the first 8 bytes of the instruction
-        ///   data and invoke the identified method. The method identifier
-        ///   is a variant of sighash. See docs.rs for `anchor_lang` for details.
-        /// * If the method identifier is an IDL identifier, execute the IDL
-        ///   instructions, which are a special set of hardcoded instructions
-        ///   baked into every Anchor program. Then exit.
-        /// * Otherwise, the method identifier is for a user defined
-        ///   instruction, i.e., one of the methods in the user defined
-        ///   `#[program]` module. Perform method dispatch, i.e., execute the
-        ///   big match statement mapping method identifier to method handler
-        ///   wrapper.
-        /// * Run the method handler wrapper. This wraps the code the user
-        ///   actually wrote, deserializing the accounts, constructing the
-        ///   context, invoking the user's code, and finally running the exit
-        ///   routine, which typically persists account changes.
-        ///
-        /// The `entry` function here, defines the standard entry to a Solana
-        /// program, where execution begins.
-        #[cfg(not(feature = "no-entrypoint"))]
-        fn entry(program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8]) -> ProgramResult {
-            #[cfg(feature = "anchor-debug")]
-            {
-                msg!("anchor-debug is active");
-            }
-            if ix_data.len() < 8 {
-                return Err(ProgramError::Custom(99));
-            }
-
-            // Split the instruction data into the first 8 byte method
-            // identifier (sighash) and the serialized instruction data.
-            let mut ix_data: &[u8] = ix_data;
-            let sighash: [u8; 8] = {
-                let mut sighash: [u8; 8] = [0; 8];
-                sighash.copy_from_slice(&ix_data[..8]);
-                ix_data = &ix_data[8..];
-                sighash
-            };
-
-            dispatch(program_id, accounts, sighash, ix_data)
-                .map_err(|e| {
-                    anchor_lang::solana_program::msg!(&e.to_string());
-                    e
-                })
-        }
-
-        #dispatch
-
-        /// Create a private module to not clutter the program's namespace.
-        /// Defines an entrypoint for each individual instruction handler
-        /// wrapper.
-        mod __private {
-            use super::*;
-
-            #handlers_non_inlined
-        }
-
-        #accounts
-
-        #ixs
-
-        #methods
-
-        #cpi
-    }
-}
-
-pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
-    // Dispatch the state constructor.
-    let ctor_state_dispatch_arm = match &program.state {
-        None => quote! { /* no-op */ },
-        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(|_| ProgramError::Custom(1))?; // todo: error code
-                        let instruction::state::#variant_arm = ix;
-                        __private::__state::__ctor(program_id, accounts, #(#ctor_args),*)
-                    }
-                }
-            }
-        },
-    };
-
-    // Dispatch the state impl instructions.
-    let state_dispatch_arms: Vec<proc_macro2::TokenStream> = match &program.state {
-        None => vec![],
-        Some(s) => s
-            .impl_block_and_methods
-            .as_ref()
-            .map(|(_impl_block, methods)| {
-                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());
-                        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(|_| ProgramError::Custom(1))?; // todo: error code
-                                let instruction::state::#variant_arm = ix;
-                                __private::__state::#ix_method_name(program_id, accounts, #(#ix_arg_names),*)
-                            }
-                        }
-                    })
-                    .collect()
-            })
-            .unwrap_or_default(),
-    };
-
-    // Dispatch all trait interface implementations.
-    let trait_dispatch_arms: Vec<proc_macro2::TokenStream> = match &program.state {
-        None => vec![],
-        Some(s) => s
-            .interfaces
-            .as_ref()
-            .map(|interfaces| {
-                interfaces
-                    .iter()
-                    .flat_map(|iface: &crate::StateInterface| {
-                        iface
-                            .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),*
-                                            }
-                                        }
-                                    }
-                                };
-                                quote! {
-                                    #sighash_tts => {
-                                        #args_struct
-                                        let ix = Args::deserialize(&mut ix_data)
-                                            .map_err(|_| ProgramError::Custom(1))?; // todo: error code
-                                        let Args {
-                                            #(#ix_arg_names),*
-                                        } = ix;
-                                        __private::__interface::#ix_name(program_id, accounts, #(#ix_arg_names),*)
-                                    }
-                                }
-                            })
-                            .collect::<Vec<proc_macro2::TokenStream>>()
-                    })
-                    .collect()
-            })
-            .unwrap_or_default()
-    };
-
-    // Dispatch all global instructions.
-    let global_dispatch_arms: Vec<proc_macro2::TokenStream> = program
-        .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(|_| ProgramError::Custom(1))?; // todo: error code
-                    let instruction::#variant_arm = ix;
-                    __private::__global::#ix_method_name(program_id, accounts, #(#ix_arg_names),*)
-                }
-            }
-        })
-        .collect();
-
-    quote! {
-        /// Performs method dispatch.
-        ///
-        /// Each method in an anchor program is uniquely defined by a namespace
-        /// and a rust identifier (i.e., the name given to the method). These
-        /// two pieces can be combined to creater a method identifier,
-        /// specifically, Anchor uses
-        ///
-        /// Sha256("<namespace>::<rust-identifier>")[..8],
-        ///
-        /// where the namespace can be one of three types. 1) "global" for a
-        /// regular instruction, 2) "state" for a state struct instruction
-        /// handler and 3) a trait namespace (used in combination with the
-        /// `#[interface]` attribute), which is defined by the trait name, e..
-        /// `MyTrait`.
-        ///
-        /// With this 8 byte identifier, Anchor performs method dispatch,
-        /// matching the given 8 byte identifier to the associated method
-        /// handler, which leads to user defined code being eventually invoked.
-        fn dispatch(program_id: &Pubkey, accounts: &[AccountInfo], sighash: [u8; 8], mut ix_data: &[u8]) -> ProgramResult {
-            // If the method identifier is the IDL tag, then execute an IDL
-            // 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);
-                }
-            }
-
-            match sighash {
-                #ctor_state_dispatch_arm
-                #(#state_dispatch_arms)*
-                #(#trait_dispatch_arms)*
-                #(#global_dispatch_arms)*
-                _ => {
-                    msg!("Fallback functions are not supported. If you have a use case, please file an issue.");
-                    Err(ProgramError::Custom(99))
-                }
-            }
-        }
-    }
-}
-
-// Generate non-inlined wrappers for each instruction handler, since Solana's
-// BPF max stack size can't handle reasonable sized dispatch trees without doing
-// so.
-pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStream {
-    let program_name = &program.name;
-    let non_inlined_idl: proc_macro2::TokenStream = {
-        quote! {
-            // Entry for all IDL related instructions. Use the "no-idl" feature
-            // to eliminate this code, for example, if one wants to make the
-            // IDL no longer mutable or if one doesn't want to store the IDL
-            // on chain.
-            #[inline(never)]
-            #[cfg(not(feature = "no-idl"))]
-            pub fn __idl_dispatch(program_id: &Pubkey, accounts: &[AccountInfo], idl_ix_data: &[u8]) -> ProgramResult {
-                let mut accounts = accounts;
-                let mut data: &[u8] = idl_ix_data;
-
-                let ix = anchor_lang::idl::IdlInstruction::deserialize(&mut data)
-                    .map_err(|_| ProgramError::Custom(2))?; // todo
-
-                match ix {
-                    anchor_lang::idl::IdlInstruction::Create { data_len } => {
-                        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)?;
-                        __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)?;
-                        __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)?;
-                        __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)?;
-                        __idl_set_buffer(program_id, &mut accounts)?;
-                        accounts.exit(program_id)?;
-                    },
-                }
-                Ok(())
-            }
-
-            #[inline(never)]
-            #[cfg(feature = "no-idl")]
-            pub fn __idl_dispatch(program_id: &Pubkey, accounts: &[AccountInfo], idl_ix_data: &[u8]) -> ProgramResult {
-                Err(anchor_lang::solana_program::program_error::ProgramError::Custom(99))
-            }
-
-            // One time IDL account initializer. Will faill on subsequent
-            // invocations.
-            #[inline(never)]
-            pub fn __idl_create_account(
-                program_id: &Pubkey,
-                accounts: &mut anchor_lang::idl::IdlCreateAccounts,
-                data_len: u64,
-            ) -> ProgramResult {
-                if program_id != accounts.program.key {
-                    return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(98)); // todo proper error
-                }
-                // Create the IDL's account.
-                let from = accounts.from.key;
-                let (base, nonce) = Pubkey::find_program_address(&[], program_id);
-                let seed = anchor_lang::idl::IdlAccount::seed();
-                let owner = accounts.program.key;
-                let to = Pubkey::create_with_seed(&base, seed, owner).unwrap();
-                // Space: account discriminator || authority pubkey || vec len || vec data
-                let space = 8 + 32 + 4 + data_len as usize;
-                let lamports = accounts.rent.minimum_balance(space);
-                let seeds = &[&[nonce][..]];
-                let ix = anchor_lang::solana_program::system_instruction::create_account_with_seed(
-                    from,
-                    &to,
-                    &base,
-                    seed,
-                    lamports,
-                    space as u64,
-                    owner,
-                );
-                anchor_lang::solana_program::program::invoke_signed(
-                    &ix,
-                    &[
-                        accounts.from.clone(),
-                        accounts.to.clone(),
-                        accounts.base.clone(),
-                        accounts.system_program.clone(),
-                    ],
-                    &[seeds],
-                )?;
-
-                // Deserialize the newly created account.
-                let mut idl_account = {
-                    let mut account_data =  accounts.to.try_borrow_data()?;
-                    let mut account_data_slice: &[u8] = &account_data;
-                    anchor_lang::idl::IdlAccount::try_deserialize_unchecked(
-                        &mut account_data_slice,
-                    )?
-                };
-
-                // Set the authority.
-                idl_account.authority = *accounts.from.key;
-
-                // Store the new account data.
-                let mut data = accounts.to.try_borrow_mut_data()?;
-                let dst: &mut [u8] = &mut data;
-                let mut cursor = std::io::Cursor::new(dst);
-                idl_account.try_serialize(&mut cursor)?;
-
-                Ok(())
-            }
-
-            #[inline(never)]
-            pub fn __idl_create_buffer(
-                program_id: &Pubkey,
-                accounts: &mut anchor_lang::idl::IdlCreateBuffer,
-            ) -> ProgramResult {
-                let mut buffer = &mut accounts.buffer;
-                buffer.authority = *accounts.authority.key;
-                Ok(())
-            }
-
-            #[inline(never)]
-            pub fn __idl_write(
-                program_id: &Pubkey,
-                accounts: &mut anchor_lang::idl::IdlAccounts,
-                idl_data: Vec<u8>,
-            ) -> ProgramResult {
-                let mut idl = &mut accounts.idl;
-                idl.data.extend(idl_data);
-                Ok(())
-            }
-
-            #[inline(never)]
-            pub fn __idl_set_authority(
-                program_id: &Pubkey,
-                accounts: &mut anchor_lang::idl::IdlAccounts,
-                new_authority: Pubkey,
-            ) -> ProgramResult {
-                accounts.idl.authority = new_authority;
-                Ok(())
-            }
-
-            #[inline(never)]
-            pub fn __idl_set_buffer(
-                program_id: &Pubkey,
-                accounts: &mut anchor_lang::idl::IdlSetBuffer,
-            ) -> ProgramResult {
-                accounts.idl.data = accounts.buffer.data.clone();
-                Ok(())
-            }
-        }
-    };
-    // Constructor handler.
-    let non_inlined_ctor: proc_macro2::TokenStream = match &program.state {
-        None => quote! {},
-        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;
-                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;
-
-                            // 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)?;
-
-                            // Create the solana account for the ctor data.
-                            let from = ctor_accounts.from.key;
-                            let (base, nonce) = Pubkey::find_program_address(&[], ctor_accounts.program.key);
-                            let seed = anchor_lang::__private::PROGRAM_STATE_SEED;
-                            let owner = ctor_accounts.program.key;
-                            let to = Pubkey::create_with_seed(&base, seed, owner).unwrap();
-                            let space = 8 + std::mem::size_of::<#name>();
-                            let lamports = ctor_accounts.rent.minimum_balance(std::convert::TryInto::try_into(space).unwrap());
-                            let seeds = &[&[nonce][..]];
-                            let ix = anchor_lang::solana_program::system_instruction::create_account_with_seed(
-                                from,
-                                &to,
-                                &base,
-                                seed,
-                                lamports,
-                                space as u64,
-                                owner,
-                            );
-                            anchor_lang::solana_program::program::invoke_signed(
-                                &ix,
-                                &[
-                                    ctor_accounts.from.clone(),
-                                    ctor_accounts.to.clone(),
-                                    ctor_accounts.base.clone(),
-                                    ctor_accounts.system_program.clone(),
-                                ],
-                                &[seeds],
-                            )?;
-
-                            // Zero copy deserialize.
-                            let loader: anchor_lang::Loader<#mod_name::#name> = anchor_lang::Loader::try_from_init(&ctor_accounts.to)?;
-
-                            // Invoke the ctor in a new lexical scope so that
-                            // the zero-copy RefMut gets dropped. Required
-                            // so that we can subsequently run the exit routine.
-                            {
-                                let mut instance = loader.load_init()?;
-                                instance.new(
-                                    anchor_lang::Context::new(
-                                        program_id,
-                                        &mut ctor_user_def_accounts,
-                                        remaining_accounts,
-                                    ),
-                                    #(#ctor_untyped_args),*
-                                )?;
-                            }
-
-                            // Exit routines.
-                            ctor_user_def_accounts.exit(program_id)?;
-                            loader.exit(program_id)?;
-
-                            Ok(())
-                        }
-                    }
-                } else {
-                    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;
-
-                            // 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)?;
-
-                            // Invoke the ctor.
-                            let instance = #mod_name::#name::new(
-                                anchor_lang::Context::new(
-                                    program_id,
-                                    &mut ctor_user_def_accounts,
-                                    remaining_accounts,
-                                ),
-                                #(#ctor_untyped_args),*
-                            )?;
-
-                            // Create the solana account for the ctor data.
-                            let from = ctor_accounts.from.key;
-                            let (base, nonce) = Pubkey::find_program_address(&[], ctor_accounts.program.key);
-                            let seed = anchor_lang::ProgramState::<#name>::seed();
-                            let owner = ctor_accounts.program.key;
-                            let to = Pubkey::create_with_seed(&base, seed, owner).unwrap();
-                            let space = anchor_lang::__private::AccountSize::size(&instance)?;
-                            let lamports = ctor_accounts.rent.minimum_balance(std::convert::TryInto::try_into(space).unwrap());
-                            let seeds = &[&[nonce][..]];
-                            let ix = anchor_lang::solana_program::system_instruction::create_account_with_seed(
-                                from,
-                                &to,
-                                &base,
-                                seed,
-                                lamports,
-                                space,
-                                owner,
-                            );
-                            anchor_lang::solana_program::program::invoke_signed(
-                                &ix,
-                                &[
-                                    ctor_accounts.from.clone(),
-                                    ctor_accounts.to.clone(),
-                                    ctor_accounts.base.clone(),
-                                    ctor_accounts.system_program.clone(),
-                                ],
-                                &[seeds],
-                            )?;
-
-                            // Serialize the state and save it to storage.
-                            ctor_user_def_accounts.exit(program_id)?;
-                            let mut data = ctor_accounts.to.try_borrow_mut_data()?;
-                            let dst: &mut [u8] = &mut data;
-                            let mut cursor = std::io::Cursor::new(dst);
-                            instance.try_serialize(&mut cursor)?;
-
-                            Ok(())
-                        }
-                    }
-                }
-            }
-        },
-    };
-
-    // State method handlers.
-    let non_inlined_state_handlers: Vec<proc_macro2::TokenStream> = match &program.state {
-        None => vec![],
-        Some(state) => state
-            .impl_block_and_methods
-            .as_ref()
-            .map(|(_impl_block, methods)| {
-                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 n = format!("__{}", &ix.raw_method.sig.ident.to_string());
-                            n.parse().unwrap()
-                        };
-                        let ix_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;
-
-                        if state.is_zero_copy {
-                            quote! {
-                                #[inline(never)]
-                                pub fn #private_ix_name(
-                                    program_id: &Pubkey,
-                                    accounts: &[AccountInfo],
-                                    #(#ix_params),*
-                                ) -> ProgramResult {
-                                    let mut remaining_accounts: &[AccountInfo] = accounts;
-                                    if remaining_accounts.is_empty() {
-                                        return Err(ProgramError::Custom(1)); // todo
-                                    }
-
-                                    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.
-                                    let mut accounts = #anchor_ident::try_accounts(
-                                        program_id,
-                                        &mut remaining_accounts,
-                                    )?;
-                                    let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
-                                    // Execute user defined function.
-                                    {
-                                        let mut state = loader.load_mut()?;
-                                        state.#ix_name(
-                                            ctx,
-                                            #(#ix_arg_names),*
-                                        )?;
-                                    }
-                                    // Serialize the state and save it to storage.
-                                    accounts.exit(program_id)?;
-                                    loader.exit(program_id)?;
-
-                                    Ok(())
-                                }
-                            }
-                        } else {
-                            quote! {
-                                #[inline(never)]
-                                pub fn #private_ix_name(
-                                    program_id: &Pubkey,
-                                    accounts: &[AccountInfo],
-                                    #(#ix_params),*
-                                ) -> ProgramResult {
-                                    let mut remaining_accounts: &[AccountInfo] = accounts;
-                                    if remaining_accounts.is_empty() {
-                                        return Err(ProgramError::Custom(1)); // todo
-                                    }
-
-                                    // 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.
-                                    let mut accounts = #anchor_ident::try_accounts(
-                                        program_id,
-                                        &mut remaining_accounts,
-                                    )?;
-                                    let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
-
-                                    // Execute user defined function.
-                                    state.#ix_name(
-                                        ctx,
-                                        #(#ix_arg_names),*
-                                    )?;
-
-                                    // Serialize the state and save it to storage.
-                                    accounts.exit(program_id)?;
-                                    let mut data = state_account.try_borrow_mut_data()?;
-                                    let dst: &mut [u8] = &mut data;
-                                    let mut cursor = std::io::Cursor::new(dst);
-                                    state.try_serialize(&mut cursor)?;
-
-                                    Ok(())
-                                }
-                            }
-                        }
-                    })
-                    .collect()
-            })
-            .unwrap_or_default(),
-    };
-
-    // State trait handlers.
-    let non_inlined_state_trait_handlers: Vec<proc_macro2::TokenStream> = match &program.state {
-        None => Vec::new(),
-        Some(state) => state
-            .interfaces
-            .as_ref()
-            .map(|interfaces| {
-                interfaces
-                    .iter()
-                    .flat_map(|iface: &crate::StateInterface| {
-                        iface
-                            .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 n = format!("__{}_{}", iface.trait_name, &ix.raw_method.sig.ident.to_string());
-                                    n.parse().unwrap()
-                                };
-                                let ix_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.");
-                                }
-
-                                if ix.has_receiver {
-                                    quote! {
-                                        #[inline(never)]
-                                        pub fn #private_ix_name(
-                                            program_id: &Pubkey,
-                                            accounts: &[AccountInfo],
-                                            #(#ix_params),*
-                                        ) -> ProgramResult {
-
-                                            let mut remaining_accounts: &[AccountInfo] = accounts;
-                                            if remaining_accounts.is_empty() {
-                                                return Err(ProgramError::Custom(1)); // todo
-                                            }
-
-                                            // 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.
-                                            let mut accounts = #anchor_ident::try_accounts(
-                                                program_id,
-                                                &mut remaining_accounts,
-                                            )?;
-                                            let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
-
-                                            // Execute user defined function.
-                                            state.#ix_name(
-                                                ctx,
-                                                #(#ix_arg_names),*
-                                            )?;
-
-                                            // Serialize the state and save it to storage.
-                                            accounts.exit(program_id)?;
-                                            let mut data = state_account.try_borrow_mut_data()?;
-                                            let dst: &mut [u8] = &mut data;
-                                            let mut cursor = std::io::Cursor::new(dst);
-                                            state.try_serialize(&mut cursor)?;
-
-                                            Ok(())
-                                        }
-                                    }
-                                } else {
-                                    let state_name: proc_macro2::TokenStream = state.name.parse().unwrap();
-                                    quote! {
-                                        #[inline(never)]
-                                        pub fn #private_ix_name(
-                                            program_id: &Pubkey,
-                                            accounts: &[AccountInfo],
-                                            #(#ix_params),*
-                                        ) -> ProgramResult {
-                                            let mut remaining_accounts: &[AccountInfo] = accounts;
-                                            let mut accounts = #anchor_ident::try_accounts(
-                                                program_id,
-                                                &mut remaining_accounts,
-                                            )?;
-                                            #state_name::#ix_name(
-                                                Context::new(program_id, &mut accounts, remaining_accounts),
-                                                #(#ix_arg_names),*
-                                            )?;
-                                            accounts.exit(program_id)
-                                        }
-                                    }
-                                }
-                            })
-                            .collect::<Vec<proc_macro2::TokenStream>>()
-                    })
-                    .collect()
-            })
-            .unwrap_or_default(),
-    };
-    let non_inlined_handlers: Vec<proc_macro2::TokenStream> = program
-        .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 anchor = &ix.anchor_ident;
-
-            quote! {
-                #[inline(never)]
-                pub fn #ix_name(
-                    program_id: &Pubkey,
-                    accounts: &[AccountInfo],
-                    #(#ix_params),*
-                ) -> ProgramResult {
-                    let mut remaining_accounts: &[AccountInfo] = accounts;
-                    let mut accounts = #anchor::try_accounts(program_id, &mut remaining_accounts)?;
-                    #program_name::#ix_name(
-                        Context::new(program_id, &mut accounts, remaining_accounts),
-                        #(#ix_arg_names),*
-                    )?;
-                    accounts.exit(program_id)
-                }
-            }
-        })
-        .collect();
-
-    quote! {
-        /// __idl mod defines handlers for injected Anchor IDL instructions.
-        pub mod __idl {
-            use super::*;
-
-            #non_inlined_idl
-        }
-
-        /// __state mod defines wrapped handlers for state instructions.
-        pub mod __state {
-            use super::*;
-
-            #non_inlined_ctor
-            #(#non_inlined_state_handlers)*
-        }
-
-        /// __interface mod defines wrapped handlers for `#[interface]` trait
-        /// implementations.
-        pub mod __interface {
-            use super::*;
-
-            #(#non_inlined_state_trait_handlers)*
-        }
-
-        /// __global mod defines wrapped handlers for global instructions.
-        pub mod __global {
-            use super::*;
-
-            #(#non_inlined_handlers)*
-        }
-    }
-}
-
-pub 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),*
-            }
-        }
-    }
-}
-
-pub fn generate_ctor_variant_name() -> String {
-    "New".to_string()
-}
-
-fn generate_ctor_typed_args(state: &State) -> Vec<syn::PatType> {
-    state
-        .ctor_and_anchor
-        .as_ref()
-        .map(|(ctor, _anchor_ident)| {
-            ctor.sig
-                .inputs
-                .iter()
-                .filter_map(|arg: &syn::FnArg| match arg {
-                    syn::FnArg::Typed(pat_ty) => {
-                        let mut arg_str = parser::tts_to_string(&pat_ty.ty);
-                        arg_str.retain(|c| !c.is_whitespace());
-                        if arg_str.starts_with("Context<") {
-                            return None;
-                        }
-                        Some(pat_ty.clone())
-                    }
-                    _ => {
-                        if !state.is_zero_copy {
-                            panic!("Cannot pass self as parameter")
-                        }
-                        None
-                    }
-                })
-                .collect()
-        })
-        .unwrap_or_default()
-}
-
-fn generate_ctor_args(state: &State) -> Vec<syn::Pat> {
-    state
-        .ctor_and_anchor
-        .as_ref()
-        .map(|(ctor, _anchor_ident)| {
-            ctor.sig
-                .inputs
-                .iter()
-                .filter_map(|arg: &syn::FnArg| match arg {
-                    syn::FnArg::Typed(pat_ty) => {
-                        let mut arg_str = parser::tts_to_string(&pat_ty.ty);
-                        arg_str.retain(|c| !c.is_whitespace());
-                        if arg_str.starts_with("Context<") {
-                            return None;
-                        }
-                        Some(*pat_ty.pat.clone())
-                    }
-                    _ => {
-                        if !state.is_zero_copy {
-                            panic!("Cannot pass self as parameter");
-                        }
-                        None
-                    }
-                })
-                .collect()
-        })
-        .unwrap_or_default()
-}
-
-pub fn generate_ix_variant(name: String, args: &[IxArg]) -> proc_macro2::TokenStream {
-    let ix_arg_names: Vec<&syn::Ident> = args.iter().map(|arg| &arg.name).collect();
-    let ix_name_camel: proc_macro2::TokenStream = {
-        let n = name.to_camel_case();
-        n.parse().unwrap()
-    };
-
-    if args.is_empty() {
-        quote! {
-            #ix_name_camel
-        }
-    } else {
-        quote! {
-            #ix_name_camel {
-                #(#ix_arg_names),*
-            }
-        }
-    }
-}
-
-pub fn generate_ix_variant_name(name: String) -> proc_macro2::TokenStream {
-    let n = name.to_camel_case();
-    n.parse().unwrap()
-}
-
-pub fn generate_methods(program: &Program) -> proc_macro2::TokenStream {
-    let program_mod = &program.program_mod;
-    quote! {
-        #program_mod
-    }
-}
-
-pub fn generate_ixs(program: &Program) -> proc_macro2::TokenStream {
-    let ctor_variant = match &program.state {
-        None => quote! {},
-        Some(state) => {
-            let ctor_args: Vec<proc_macro2::TokenStream> = generate_ctor_typed_args(state)
-                .iter()
-                .map(|arg| {
-                    format!("pub {}", parser::tts_to_string(&arg))
-                        .parse()
-                        .unwrap()
-                })
-                .collect();
-            let strct = {
-                if ctor_args.is_empty() {
-                    quote! {
-                        #[derive(AnchorSerialize, AnchorDeserialize)]
-                        pub struct New;
-                    }
-                } else {
-                    quote! {
-                        #[derive(AnchorSerialize, AnchorDeserialize)]
-                        pub struct New {
-                            #(#ctor_args),*
-                        }
-                    }
-                }
-            };
-            let sighash_arr = sighash_ctor();
-            let sighash_tts: proc_macro2::TokenStream =
-                format!("{:?}", sighash_arr).parse().unwrap();
-            quote! {
-                /// Instruction arguments to the `#[state]`'s `new`
-                /// constructor.
-                #strct
-
-                impl anchor_lang::InstructionData for New {
-                    fn data(&self) -> Vec<u8> {
-                        let mut d = #sighash_tts.to_vec();
-                        d.append(&mut self.try_to_vec().expect("Should always serialize"));
-                        d
-                    }
-                }
-            }
-        }
-    };
-    let state_method_variants: Vec<proc_macro2::TokenStream> = match &program.state {
-        None => vec![],
-        Some(state) => state
-            .impl_block_and_methods
-            .as_ref()
-            .map(|(_impl_block, methods)| {
-                methods
-                    .iter()
-                    .map(|method| {
-                        let ix_name_camel: proc_macro2::TokenStream = method
-                            .raw_method
-                            .sig
-                            .ident
-                            .to_string()
-                            .to_camel_case()
-                            .parse()
-                            .unwrap();
-                        let raw_args: Vec<proc_macro2::TokenStream> = method
-                            .args
-                            .iter()
-                            .map(|arg| {
-                                format!("pub {}", parser::tts_to_string(&arg.raw_arg))
-                                    .parse()
-                                    .unwrap()
-                            })
-                            .collect();
-
-                        let ix_data_trait = {
-                            let name = method.raw_method.sig.ident.to_string();
-                            let sighash_arr = sighash(SIGHASH_STATE_NAMESPACE, &name);
-                            let sighash_tts: proc_macro2::TokenStream =
-                                format!("{:?}", sighash_arr).parse().unwrap();
-                            quote! {
-                                impl anchor_lang::InstructionData for #ix_name_camel {
-                                    fn data(&self) -> Vec<u8> {
-                                        let mut d = #sighash_tts.to_vec();
-                                        d.append(&mut self.try_to_vec().expect("Should always serialize"));
-                                        d
-                                    }
-                                }
-                            }
-                        };
-
-                        // If no args, output a "unit" variant instead of a struct variant.
-                        if method.args.is_empty() {
-                            quote! {
-                                /// Anchor generated instruction.
-                                #[derive(AnchorSerialize, AnchorDeserialize)]
-                                pub struct #ix_name_camel;
-
-                                #ix_data_trait
-                            }
-                        } else {
-                            quote! {
-                                /// Anchor generated instruction.
-                                #[derive(AnchorSerialize, AnchorDeserialize)]
-                                pub struct #ix_name_camel {
-                                    #(#raw_args),*
-                                }
-
-                                #ix_data_trait
-                            }
-                        }
-                    })
-                    .collect()
-            })
-            .unwrap_or_default(),
-    };
-    let variants: Vec<proc_macro2::TokenStream> = program
-        .ixs
-        .iter()
-        .map(|ix| {
-            let name = &ix.raw_method.sig.ident.to_string();
-            let ix_name_camel =
-                proc_macro2::Ident::new(&name.to_camel_case(), ix.raw_method.sig.ident.span());
-            let raw_args: Vec<proc_macro2::TokenStream> = ix
-                .args
-                .iter()
-                .map(|arg| {
-                    format!("pub {}", parser::tts_to_string(&arg.raw_arg))
-                        .parse()
-                        .unwrap()
-                })
-                .collect();
-            let ix_data_trait = {
-                let sighash_arr = sighash(SIGHASH_GLOBAL_NAMESPACE, &name);
-                let sighash_tts: proc_macro2::TokenStream =
-                    format!("{:?}", sighash_arr).parse().unwrap();
-                quote! {
-                    impl anchor_lang::InstructionData for #ix_name_camel {
-                        fn data(&self) -> Vec<u8> {
-                            let mut d = #sighash_tts.to_vec();
-                            d.append(&mut self.try_to_vec().expect("Should always serialize"));
-                            d
-                        }
-                    }
-                }
-            };
-            // If no args, output a "unit" variant instead of a struct variant.
-            if ix.args.is_empty() {
-                quote! {
-                    /// Instruction.
-                    #[derive(AnchorSerialize, AnchorDeserialize)]
-                    pub struct #ix_name_camel;
-
-                    #ix_data_trait
-                }
-            } else {
-                quote! {
-                    /// Instruction.
-                    #[derive(AnchorSerialize, AnchorDeserialize)]
-                    pub struct #ix_name_camel {
-                        #(#raw_args),*
-                    }
-
-                    #ix_data_trait
-                }
-            }
-        })
-        .collect();
-
-    quote! {
-        /// An Anchor generated module containing the program's set of
-        /// instructions, where each method handler in the `#[program]` mod is
-        /// associated with a struct defining the input arguments to the
-        /// method. These should be used directly, when one wants to serialize
-        /// Anchor instruction data, for example, when speciying
-        /// instructions on a client.
-        pub mod instruction {
-            use super::*;
-
-            /// Instruction struct definitions for `#[state]` methods.
-            pub mod state {
-                use super::*;
-
-                #ctor_variant
-                #(#state_method_variants)*
-            }
-
-            #(#variants)*
-        }
-    }
-}
-
-fn generate_accounts(program: &Program) -> proc_macro2::TokenStream {
-    let mut accounts = std::collections::HashSet::new();
-
-    // Go through state accounts.
-    if let Some(state) = &program.state {
-        // Ctor.
-        if let Some((_ctor, ctor_accounts)) = &state.ctor_and_anchor {
-            let macro_name = format!(
-                "__client_accounts_{}",
-                ctor_accounts.to_string().to_snake_case()
-            );
-            accounts.insert(macro_name);
-        }
-        // Methods.
-        if let Some((_impl_block, methods)) = &state.impl_block_and_methods {
-            for ix in methods {
-                let anchor_ident = &ix.anchor_ident;
-                // TODO: move to fn and share with accounts.rs.
-                let macro_name = format!(
-                    "__client_accounts_{}",
-                    anchor_ident.to_string().to_snake_case()
-                );
-                accounts.insert(macro_name);
-            }
-        }
-    }
-
-    // Go through instruction accounts.
-    for ix in &program.ixs {
-        let anchor_ident = &ix.anchor_ident;
-        // TODO: move to fn and share with accounts.rs.
-        let macro_name = format!(
-            "__client_accounts_{}",
-            anchor_ident.to_string().to_snake_case()
-        );
-        accounts.insert(macro_name);
-    }
-
-    // Build the tokens from all accounts
-    let account_structs: Vec<proc_macro2::TokenStream> = accounts
-        .iter()
-        .map(|macro_name: &String| {
-            let macro_name: proc_macro2::TokenStream = macro_name.parse().unwrap();
-            quote! {
-                pub use crate::#macro_name::*;
-            }
-        })
-        .collect();
-
-    // TODO: calculate the account size and add it as a constant field to
-    //       each struct here. This is convenient for Rust clients.
-
-    quote! {
-        /// An Anchor generated module, providing a set of structs
-        /// mirroring the structs deriving `Accounts`, where each field is
-        /// a `Pubkey`. This is useful for specifying accounts for a client.
-        pub mod accounts {
-            #(#account_structs)*
-        }
-    }
-}
-
-fn generate_cpi(program: &Program) -> proc_macro2::TokenStream {
-    // Generate cpi methods for the state struct.
-    // The Ctor is not exposed via CPI, since it is a one time use function.
-    let state_cpi_methods: Vec<proc_macro2::TokenStream> = program
-        .state
-        .as_ref()
-        .map(|state| {
-            state
-                .impl_block_and_methods
-                .as_ref()
-                .map(|(_, methods)| {
-                    methods
-                        .iter()
-                        .map(|method: &StateIx| {
-                            let accounts_ident = &method.anchor_ident;
-                            let ix_variant = generate_ix_variant(
-                                method.raw_method.sig.ident.to_string(),
-                                &method.args,
-                            );
-                            let method_name = &method.ident;
-                            let args: Vec<&syn::PatType> =
-                                method.args.iter().map(|arg| &arg.raw_arg).collect();
-
-                            quote! {
-                                pub fn #method_name<'a, 'b, 'c, 'info>(
-                                    ctx: CpiStateContext<'a, 'b, 'c, 'info, #accounts_ident<'info>>,
-                                    #(#args),*
-                                ) -> ProgramResult {
-                                    let ix = {
-                                        let ix = instruction::state::#ix_variant;
-                                        let data = anchor_lang::InstructionData::data(&ix);
-                                        let accounts = ctx.to_account_metas(None);
-                                        anchor_lang::solana_program::instruction::Instruction {
-                                            program_id: *ctx.program().key,
-                                            accounts,
-                                            data,
-                                        }
-                                    };
-                                    let mut acc_infos = ctx.to_account_infos();
-                                    anchor_lang::solana_program::program::invoke_signed(
-                                        &ix,
-                                        &acc_infos,
-                                        ctx.signer_seeds(),
-                                    )
-                                }
-                            }
-                        })
-                        .collect()
-                })
-                .unwrap_or(vec![])
-        })
-        .unwrap_or(vec![]);
-    // Generate cpi methods for global methods.
-    let global_cpi_methods: Vec<proc_macro2::TokenStream> = program
-        .ixs
-        .iter()
-        .map(|ix| {
-            let accounts_ident = &ix.anchor_ident;
-            let cpi_method = {
-                let ix_variant = generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args);
-                let method_name = &ix.ident;
-                let args: Vec<&syn::PatType> = ix.args.iter().map(|arg| &arg.raw_arg).collect();
-                let name = &ix.raw_method.sig.ident.to_string();
-                let sighash_arr = sighash(SIGHASH_GLOBAL_NAMESPACE, &name);
-                let sighash_tts: proc_macro2::TokenStream =
-                    format!("{:?}", sighash_arr).parse().unwrap();
-                quote! {
-                    pub fn #method_name<'a, 'b, 'c, 'info>(
-                        ctx: CpiContext<'a, 'b, 'c, 'info, #accounts_ident<'info>>,
-                        #(#args),*
-                    ) -> ProgramResult {
-                        let ix = {
-                            let ix = instruction::#ix_variant;
-                            let mut ix_data = AnchorSerialize::try_to_vec(&ix)
-                                .map_err(|_| ProgramError::InvalidInstructionData)?;
-                            let mut data = #sighash_tts.to_vec();
-                            data.append(&mut ix_data);
-                            let accounts = ctx.accounts.to_account_metas(None);
-                            anchor_lang::solana_program::instruction::Instruction {
-                                program_id: *ctx.program.key,
-                                accounts,
-                                data,
-                            }
-                        };
-                        let mut acc_infos = ctx.accounts.to_account_infos();
-                        acc_infos.push(ctx.program.clone());
-                        anchor_lang::solana_program::program::invoke_signed(
-                            &ix,
-                            &acc_infos,
-                            ctx.signer_seeds,
-                        )
-                    }
-                }
-            };
-
-            cpi_method
-        })
-        .collect();
-    quote! {
-        #[cfg(feature = "cpi")]
-        pub mod cpi {
-            use super::*;
-
-            pub mod state {
-                use super::*;
-
-                #(#state_cpi_methods)*
-            }
-
-            #(#global_cpi_methods)*
-        }
-    }
-}
-
-// We don't technically use sighash, because the input arguments aren't given.
-// Rust doesn't have method overloading so no need to use the arguments.
-// However, we do namespace methods in the preeimage so that we can use
-// different traits with the same method name.
-pub fn sighash(namespace: &str, name: &str) -> [u8; 8] {
-    let preimage = format!("{}:{}", namespace, name);
-
-    let mut sighash = [0u8; 8];
-    sighash.copy_from_slice(&crate::hash::hash(preimage.as_bytes()).to_bytes()[..8]);
-    sighash
-}
-
-fn sighash_ctor() -> [u8; 8] {
-    let namespace = SIGHASH_STATE_NAMESPACE;
-    let preimage = format!("{}:new", namespace);
-
-    let mut sighash = [0u8; 8];
-    sighash.copy_from_slice(&crate::hash::hash(preimage.as_bytes()).to_bytes()[..8]);
-    sighash
-}

+ 65 - 0
lang/syn/src/codegen/program/accounts.rs

@@ -0,0 +1,65 @@
+use crate::Program;
+use heck::SnakeCase;
+use quote::quote;
+
+pub fn generate(program: &Program) -> proc_macro2::TokenStream {
+    let mut accounts = std::collections::HashSet::new();
+
+    // Go through state accounts.
+    if let Some(state) = &program.state {
+        // Ctor.
+        if let Some((_ctor, ctor_accounts)) = &state.ctor_and_anchor {
+            let macro_name = format!(
+                "__client_accounts_{}",
+                ctor_accounts.to_string().to_snake_case()
+            );
+            accounts.insert(macro_name);
+        }
+        // Methods.
+        if let Some((_impl_block, methods)) = &state.impl_block_and_methods {
+            for ix in methods {
+                let anchor_ident = &ix.anchor_ident;
+                // TODO: move to fn and share with accounts.rs.
+                let macro_name = format!(
+                    "__client_accounts_{}",
+                    anchor_ident.to_string().to_snake_case()
+                );
+                accounts.insert(macro_name);
+            }
+        }
+    }
+
+    // Go through instruction accounts.
+    for ix in &program.ixs {
+        let anchor_ident = &ix.anchor_ident;
+        // TODO: move to fn and share with accounts.rs.
+        let macro_name = format!(
+            "__client_accounts_{}",
+            anchor_ident.to_string().to_snake_case()
+        );
+        accounts.insert(macro_name);
+    }
+
+    // Build the tokens from all accounts
+    let account_structs: Vec<proc_macro2::TokenStream> = accounts
+        .iter()
+        .map(|macro_name: &String| {
+            let macro_name: proc_macro2::TokenStream = macro_name.parse().unwrap();
+            quote! {
+                pub use crate::#macro_name::*;
+            }
+        })
+        .collect();
+
+    // TODO: calculate the account size and add it as a constant field to
+    //       each struct here. This is convenient for Rust clients.
+
+    quote! {
+        /// An Anchor generated module, providing a set of structs
+        /// mirroring the structs deriving `Accounts`, where each field is
+        /// a `Pubkey`. This is useful for specifying accounts for a client.
+        pub mod accounts {
+            #(#account_structs)*
+        }
+    }
+}

+ 83 - 0
lang/syn/src/codegen/program/common.rs

@@ -0,0 +1,83 @@
+use crate::parser;
+use crate::{IxArg, State};
+use heck::CamelCase;
+use quote::quote;
+
+// Namespace for calculating state instruction sighash signatures.
+pub const SIGHASH_STATE_NAMESPACE: &str = "state";
+
+// Namespace for calculating instruction sighash signatures for any instruction
+// not affecting program state.
+pub const SIGHASH_GLOBAL_NAMESPACE: &str = "global";
+
+// We don't technically use sighash, because the input arguments aren't given.
+// Rust doesn't have method overloading so no need to use the arguments.
+// However, we do namespace methods in the preeimage so that we can use
+// different traits with the same method name.
+pub fn sighash(namespace: &str, name: &str) -> [u8; 8] {
+    let preimage = format!("{}:{}", namespace, name);
+
+    let mut sighash = [0u8; 8];
+    sighash.copy_from_slice(&crate::hash::hash(preimage.as_bytes()).to_bytes()[..8]);
+    sighash
+}
+
+pub fn sighash_ctor() -> [u8; 8] {
+    sighash(SIGHASH_STATE_NAMESPACE, "new")
+}
+
+pub fn generate_ix_variant(name: String, args: &[IxArg]) -> proc_macro2::TokenStream {
+    let ix_arg_names: Vec<&syn::Ident> = args.iter().map(|arg| &arg.name).collect();
+    let ix_name_camel: proc_macro2::TokenStream = {
+        let n = name.to_camel_case();
+        n.parse().unwrap()
+    };
+
+    if args.is_empty() {
+        quote! {
+            #ix_name_camel
+        }
+    } else {
+        quote! {
+            #ix_name_camel {
+                #(#ix_arg_names),*
+            }
+        }
+    }
+}
+
+pub fn generate_ctor_args(state: &State) -> Vec<syn::Pat> {
+    generate_ctor_typed_args(state)
+        .iter()
+        .map(|pat_ty| *pat_ty.pat.clone())
+        .collect()
+}
+
+pub fn generate_ctor_typed_args(state: &State) -> Vec<syn::PatType> {
+    state
+        .ctor_and_anchor
+        .as_ref()
+        .map(|(ctor, _anchor_ident)| {
+            ctor.sig
+                .inputs
+                .iter()
+                .filter_map(|arg: &syn::FnArg| match arg {
+                    syn::FnArg::Typed(pat_ty) => {
+                        let mut arg_str = parser::tts_to_string(&pat_ty.ty);
+                        arg_str.retain(|c| !c.is_whitespace());
+                        if arg_str.starts_with("Context<") {
+                            return None;
+                        }
+                        Some(pat_ty.clone())
+                    }
+                    _ => {
+                        if !state.is_zero_copy {
+                            panic!("Cannot pass self as parameter")
+                        }
+                        None
+                    }
+                })
+                .collect()
+        })
+        .unwrap_or_default()
+}

+ 118 - 0
lang/syn/src/codegen/program/cpi.rs

@@ -0,0 +1,118 @@
+use crate::codegen::program::common::{generate_ix_variant, sighash, SIGHASH_GLOBAL_NAMESPACE};
+use crate::Program;
+use crate::StateIx;
+use quote::quote;
+
+pub fn generate(program: &Program) -> proc_macro2::TokenStream {
+    // Generate cpi methods for the state struct.
+    // The Ctor is not exposed via CPI, since it is a one time use function.
+    let state_cpi_methods: Vec<proc_macro2::TokenStream> = program
+        .state
+        .as_ref()
+        .map(|state| {
+            state
+                .impl_block_and_methods
+                .as_ref()
+                .map(|(_, methods)| {
+                    methods
+                        .iter()
+                        .map(|method: &StateIx| {
+                            let accounts_ident = &method.anchor_ident;
+                            let ix_variant = generate_ix_variant(
+                                method.raw_method.sig.ident.to_string(),
+                                &method.args,
+                            );
+                            let method_name = &method.ident;
+                            let args: Vec<&syn::PatType> =
+                                method.args.iter().map(|arg| &arg.raw_arg).collect();
+
+                            quote! {
+                                pub fn #method_name<'a, 'b, 'c, 'info>(
+                                    ctx: CpiStateContext<'a, 'b, 'c, 'info, #accounts_ident<'info>>,
+                                    #(#args),*
+                                ) -> ProgramResult {
+                                    let ix = {
+                                        let ix = instruction::state::#ix_variant;
+                                        let data = anchor_lang::InstructionData::data(&ix);
+                                        let accounts = ctx.to_account_metas(None);
+                                        anchor_lang::solana_program::instruction::Instruction {
+                                            program_id: *ctx.program().key,
+                                            accounts,
+                                            data,
+                                        }
+                                    };
+                                    let mut acc_infos = ctx.to_account_infos();
+                                    anchor_lang::solana_program::program::invoke_signed(
+                                        &ix,
+                                        &acc_infos,
+                                        ctx.signer_seeds(),
+                                    )
+                                }
+                            }
+                        })
+                        .collect()
+                })
+                .unwrap_or(vec![])
+        })
+        .unwrap_or(vec![]);
+    // Generate cpi methods for global methods.
+    let global_cpi_methods: Vec<proc_macro2::TokenStream> = program
+        .ixs
+        .iter()
+        .map(|ix| {
+            let accounts_ident = &ix.anchor_ident;
+            let cpi_method = {
+                let ix_variant = generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args);
+                let method_name = &ix.ident;
+                let args: Vec<&syn::PatType> = ix.args.iter().map(|arg| &arg.raw_arg).collect();
+                let name = &ix.raw_method.sig.ident.to_string();
+                let sighash_arr = sighash(SIGHASH_GLOBAL_NAMESPACE, &name);
+                let sighash_tts: proc_macro2::TokenStream =
+                    format!("{:?}", sighash_arr).parse().unwrap();
+                quote! {
+                    pub fn #method_name<'a, 'b, 'c, 'info>(
+                        ctx: CpiContext<'a, 'b, 'c, 'info, #accounts_ident<'info>>,
+                        #(#args),*
+                    ) -> ProgramResult {
+                        let ix = {
+                            let ix = instruction::#ix_variant;
+                            let mut ix_data = AnchorSerialize::try_to_vec(&ix)
+                                .map_err(|_| ProgramError::InvalidInstructionData)?;
+                            let mut data = #sighash_tts.to_vec();
+                            data.append(&mut ix_data);
+                            let accounts = ctx.accounts.to_account_metas(None);
+                            anchor_lang::solana_program::instruction::Instruction {
+                                program_id: *ctx.program.key,
+                                accounts,
+                                data,
+                            }
+                        };
+                        let mut acc_infos = ctx.accounts.to_account_infos();
+                        acc_infos.push(ctx.program.clone());
+                        anchor_lang::solana_program::program::invoke_signed(
+                            &ix,
+                            &acc_infos,
+                            ctx.signer_seeds,
+                        )
+                    }
+                }
+            };
+
+            cpi_method
+        })
+        .collect();
+    quote! {
+        #[cfg(feature = "cpi")]
+        pub mod cpi {
+            use super::*;
+
+            pub mod state {
+                use super::*;
+
+                #(#state_cpi_methods)*
+            }
+
+            #(#global_cpi_methods)*
+        }
+    }
+}

+ 215 - 0
lang/syn/src/codegen/program/dispatch.rs

@@ -0,0 +1,215 @@
+use crate::codegen::program::common::*;
+use crate::{Program, State};
+use heck::CamelCase;
+use quote::quote;
+
+pub fn generate(program: &Program) -> proc_macro2::TokenStream {
+    // Dispatch the state constructor.
+    let ctor_state_dispatch_arm = match &program.state {
+        None => quote! { /* no-op */ },
+        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(|_| ProgramError::Custom(1))?; // todo: error code
+                        let instruction::state::#variant_arm = ix;
+                        __private::__state::__ctor(program_id, accounts, #(#ctor_args),*)
+                    }
+                }
+            }
+        },
+    };
+
+    // Dispatch the state impl instructions.
+    let state_dispatch_arms: Vec<proc_macro2::TokenStream> = match &program.state {
+        None => vec![],
+        Some(s) => s
+            .impl_block_and_methods
+            .as_ref()
+            .map(|(_impl_block, methods)| {
+                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());
+                        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(|_| ProgramError::Custom(1))?; // todo: error code
+                                let instruction::state::#variant_arm = ix;
+                                __private::__state::#ix_method_name(program_id, accounts, #(#ix_arg_names),*)
+                            }
+                        }
+                    })
+                    .collect()
+            })
+            .unwrap_or_default(),
+    };
+
+    // Dispatch all trait interface implementations.
+    let trait_dispatch_arms: Vec<proc_macro2::TokenStream> = match &program.state {
+        None => vec![],
+        Some(s) => s
+            .interfaces
+            .as_ref()
+            .map(|interfaces| {
+                interfaces
+                    .iter()
+                    .flat_map(|iface: &crate::StateInterface| {
+                        iface
+                            .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),*
+                                            }
+                                        }
+                                    }
+                                };
+                                quote! {
+                                    #sighash_tts => {
+                                        #args_struct
+                                        let ix = Args::deserialize(&mut ix_data)
+                                            .map_err(|_| ProgramError::Custom(1))?; // todo: error code
+                                        let Args {
+                                            #(#ix_arg_names),*
+                                        } = ix;
+                                        __private::__interface::#ix_name(program_id, accounts, #(#ix_arg_names),*)
+                                    }
+                                }
+                            })
+                            .collect::<Vec<proc_macro2::TokenStream>>()
+                    })
+                    .collect()
+            })
+            .unwrap_or_default()
+    };
+
+    // Dispatch all global instructions.
+    let global_dispatch_arms: Vec<proc_macro2::TokenStream> = program
+        .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(|_| ProgramError::Custom(1))?; // todo: error code
+                    let instruction::#variant_arm = ix;
+                    __private::__global::#ix_method_name(program_id, accounts, #(#ix_arg_names),*)
+                }
+            }
+        })
+        .collect();
+
+    quote! {
+        /// Performs method dispatch.
+        ///
+        /// Each method in an anchor program is uniquely defined by a namespace
+        /// and a rust identifier (i.e., the name given to the method). These
+        /// two pieces can be combined to creater a method identifier,
+        /// specifically, Anchor uses
+        ///
+        /// Sha256("<namespace>::<rust-identifier>")[..8],
+        ///
+        /// where the namespace can be one of three types. 1) "global" for a
+        /// regular instruction, 2) "state" for a state struct instruction
+        /// handler and 3) a trait namespace (used in combination with the
+        /// `#[interface]` attribute), which is defined by the trait name, e..
+        /// `MyTrait`.
+        ///
+        /// With this 8 byte identifier, Anchor performs method dispatch,
+        /// matching the given 8 byte identifier to the associated method
+        /// handler, which leads to user defined code being eventually invoked.
+        fn dispatch(program_id: &Pubkey, accounts: &[AccountInfo], sighash: [u8; 8], mut ix_data: &[u8]) -> ProgramResult {
+            // If the method identifier is the IDL tag, then execute an IDL
+            // 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);
+                }
+            }
+
+            match sighash {
+                #ctor_state_dispatch_arm
+                #(#state_dispatch_arms)*
+                #(#trait_dispatch_arms)*
+                #(#global_dispatch_arms)*
+                _ => {
+                    msg!("Fallback functions are not supported. If you have a use case, please file an issue.");
+                    Err(ProgramError::Custom(99))
+                }
+            }
+        }
+    }
+}
+
+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()
+}

+ 75 - 0
lang/syn/src/codegen/program/entry.rs

@@ -0,0 +1,75 @@
+use crate::Program;
+use quote::quote;
+
+pub fn generate(_program: &Program) -> proc_macro2::TokenStream {
+    quote! {
+        #[cfg(not(feature = "no-entrypoint"))]
+        anchor_lang::solana_program::entrypoint!(entry);
+        /// The Anchor codegen exposes a programming model where a user defines
+        /// a set of methods inside of a `#[program]` module in a way similar
+        /// to writing RPC request handlers. The macro then generates a bunch of
+        /// code wrapping these user defined methods into something that can be
+        /// executed on Solana.
+        ///
+        /// These methods fall into one of three categories, each of which
+        /// can be considered a different "namespace" of the program.
+        ///
+        /// 1) Global methods - regular methods inside of the `#[program]`.
+        /// 2) State methods - associated methods inside a `#[state]` struct.
+        /// 3) Interface methods - methods inside a strait struct's
+        ///    implementation of an `#[interface]` trait.
+        ///
+        /// Care must be taken by the codegen to prevent collisions between
+        /// methods in these different namespaces. For this reason, Anchor uses
+        /// a variant of sighash to perform method dispatch, rather than
+        /// something like a simple enum variant discriminator.
+        ///
+        /// The execution flow of the generated code can be roughly outlined:
+        ///
+        /// * Start program via the entrypoint.
+        /// * Strip method identifier off the first 8 bytes of the instruction
+        ///   data and invoke the identified method. The method identifier
+        ///   is a variant of sighash. See docs.rs for `anchor_lang` for details.
+        /// * If the method identifier is an IDL identifier, execute the IDL
+        ///   instructions, which are a special set of hardcoded instructions
+        ///   baked into every Anchor program. Then exit.
+        /// * Otherwise, the method identifier is for a user defined
+        ///   instruction, i.e., one of the methods in the user defined
+        ///   `#[program]` module. Perform method dispatch, i.e., execute the
+        ///   big match statement mapping method identifier to method handler
+        ///   wrapper.
+        /// * Run the method handler wrapper. This wraps the code the user
+        ///   actually wrote, deserializing the accounts, constructing the
+        ///   context, invoking the user's code, and finally running the exit
+        ///   routine, which typically persists account changes.
+        ///
+        /// The `entry` function here, defines the standard entry to a Solana
+        /// program, where execution begins.
+        #[cfg(not(feature = "no-entrypoint"))]
+        fn entry(program_id: &Pubkey, accounts: &[AccountInfo], ix_data: &[u8]) -> ProgramResult {
+            #[cfg(feature = "anchor-debug")]
+            {
+                msg!("anchor-debug is active");
+            }
+            if ix_data.len() < 8 {
+                return Err(ProgramError::Custom(99));
+            }
+
+            // Split the instruction data into the first 8 byte method
+            // identifier (sighash) and the serialized instruction data.
+            let mut ix_data: &[u8] = ix_data;
+            let sighash: [u8; 8] = {
+                let mut sighash: [u8; 8] = [0; 8];
+                sighash.copy_from_slice(&ix_data[..8]);
+                ix_data = &ix_data[8..];
+                sighash
+            };
+
+            dispatch(program_id, accounts, sighash, ix_data)
+                .map_err(|e| {
+                    anchor_lang::solana_program::msg!(&e.to_string());
+                    e
+                })
+        }
+    }
+}

+ 592 - 0
lang/syn/src/codegen/program/handlers.rs

@@ -0,0 +1,592 @@
+use crate::codegen::program::common::*;
+use crate::Program;
+use quote::quote;
+
+// Generate non-inlined wrappers for each instruction handler, since Solana's
+// BPF max stack size can't handle reasonable sized dispatch trees without doing
+// so.
+pub fn generate(program: &Program) -> proc_macro2::TokenStream {
+    let program_name = &program.name;
+    let non_inlined_idl: proc_macro2::TokenStream = {
+        quote! {
+            // Entry for all IDL related instructions. Use the "no-idl" feature
+            // to eliminate this code, for example, if one wants to make the
+            // IDL no longer mutable or if one doesn't want to store the IDL
+            // on chain.
+            #[inline(never)]
+            #[cfg(not(feature = "no-idl"))]
+            pub fn __idl_dispatch(program_id: &Pubkey, accounts: &[AccountInfo], idl_ix_data: &[u8]) -> ProgramResult {
+                let mut accounts = accounts;
+                let mut data: &[u8] = idl_ix_data;
+
+                let ix = anchor_lang::idl::IdlInstruction::deserialize(&mut data)
+                    .map_err(|_| ProgramError::Custom(2))?; // todo
+
+                match ix {
+                    anchor_lang::idl::IdlInstruction::Create { data_len } => {
+                        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)?;
+                        __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)?;
+                        __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)?;
+                        __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)?;
+                        __idl_set_buffer(program_id, &mut accounts)?;
+                        accounts.exit(program_id)?;
+                    },
+                }
+                Ok(())
+            }
+
+            #[inline(never)]
+            #[cfg(feature = "no-idl")]
+            pub fn __idl_dispatch(program_id: &Pubkey, accounts: &[AccountInfo], idl_ix_data: &[u8]) -> ProgramResult {
+                Err(anchor_lang::solana_program::program_error::ProgramError::Custom(99))
+            }
+
+            // One time IDL account initializer. Will faill on subsequent
+            // invocations.
+            #[inline(never)]
+            pub fn __idl_create_account(
+                program_id: &Pubkey,
+                accounts: &mut anchor_lang::idl::IdlCreateAccounts,
+                data_len: u64,
+            ) -> ProgramResult {
+                if program_id != accounts.program.key {
+                    return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(98)); // todo proper error
+                }
+                // Create the IDL's account.
+                let from = accounts.from.key;
+                let (base, nonce) = Pubkey::find_program_address(&[], program_id);
+                let seed = anchor_lang::idl::IdlAccount::seed();
+                let owner = accounts.program.key;
+                let to = Pubkey::create_with_seed(&base, seed, owner).unwrap();
+                // Space: account discriminator || authority pubkey || vec len || vec data
+                let space = 8 + 32 + 4 + data_len as usize;
+                let lamports = accounts.rent.minimum_balance(space);
+                let seeds = &[&[nonce][..]];
+                let ix = anchor_lang::solana_program::system_instruction::create_account_with_seed(
+                    from,
+                    &to,
+                    &base,
+                    seed,
+                    lamports,
+                    space as u64,
+                    owner,
+                );
+                anchor_lang::solana_program::program::invoke_signed(
+                    &ix,
+                    &[
+                        accounts.from.clone(),
+                        accounts.to.clone(),
+                        accounts.base.clone(),
+                        accounts.system_program.clone(),
+                    ],
+                    &[seeds],
+                )?;
+
+                // Deserialize the newly created account.
+                let mut idl_account = {
+                    let mut account_data =  accounts.to.try_borrow_data()?;
+                    let mut account_data_slice: &[u8] = &account_data;
+                    anchor_lang::idl::IdlAccount::try_deserialize_unchecked(
+                        &mut account_data_slice,
+                    )?
+                };
+
+                // Set the authority.
+                idl_account.authority = *accounts.from.key;
+
+                // Store the new account data.
+                let mut data = accounts.to.try_borrow_mut_data()?;
+                let dst: &mut [u8] = &mut data;
+                let mut cursor = std::io::Cursor::new(dst);
+                idl_account.try_serialize(&mut cursor)?;
+
+                Ok(())
+            }
+
+            #[inline(never)]
+            pub fn __idl_create_buffer(
+                program_id: &Pubkey,
+                accounts: &mut anchor_lang::idl::IdlCreateBuffer,
+            ) -> ProgramResult {
+                let mut buffer = &mut accounts.buffer;
+                buffer.authority = *accounts.authority.key;
+                Ok(())
+            }
+
+            #[inline(never)]
+            pub fn __idl_write(
+                program_id: &Pubkey,
+                accounts: &mut anchor_lang::idl::IdlAccounts,
+                idl_data: Vec<u8>,
+            ) -> ProgramResult {
+                let mut idl = &mut accounts.idl;
+                idl.data.extend(idl_data);
+                Ok(())
+            }
+
+            #[inline(never)]
+            pub fn __idl_set_authority(
+                program_id: &Pubkey,
+                accounts: &mut anchor_lang::idl::IdlAccounts,
+                new_authority: Pubkey,
+            ) -> ProgramResult {
+                accounts.idl.authority = new_authority;
+                Ok(())
+            }
+
+            #[inline(never)]
+            pub fn __idl_set_buffer(
+                program_id: &Pubkey,
+                accounts: &mut anchor_lang::idl::IdlSetBuffer,
+            ) -> ProgramResult {
+                accounts.idl.data = accounts.buffer.data.clone();
+                Ok(())
+            }
+        }
+    };
+    // Constructor handler.
+    let non_inlined_ctor: proc_macro2::TokenStream = match &program.state {
+        None => quote! {},
+        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;
+                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;
+
+                            // 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)?;
+
+                            // Create the solana account for the ctor data.
+                            let from = ctor_accounts.from.key;
+                            let (base, nonce) = Pubkey::find_program_address(&[], ctor_accounts.program.key);
+                            let seed = anchor_lang::__private::PROGRAM_STATE_SEED;
+                            let owner = ctor_accounts.program.key;
+                            let to = Pubkey::create_with_seed(&base, seed, owner).unwrap();
+                            let space = 8 + std::mem::size_of::<#name>();
+                            let lamports = ctor_accounts.rent.minimum_balance(std::convert::TryInto::try_into(space).unwrap());
+                            let seeds = &[&[nonce][..]];
+                            let ix = anchor_lang::solana_program::system_instruction::create_account_with_seed(
+                                from,
+                                &to,
+                                &base,
+                                seed,
+                                lamports,
+                                space as u64,
+                                owner,
+                            );
+                            anchor_lang::solana_program::program::invoke_signed(
+                                &ix,
+                                &[
+                                    ctor_accounts.from.clone(),
+                                    ctor_accounts.to.clone(),
+                                    ctor_accounts.base.clone(),
+                                    ctor_accounts.system_program.clone(),
+                                ],
+                                &[seeds],
+                            )?;
+
+                            // Zero copy deserialize.
+                            let loader: anchor_lang::Loader<#mod_name::#name> = anchor_lang::Loader::try_from_init(&ctor_accounts.to)?;
+
+                            // Invoke the ctor in a new lexical scope so that
+                            // the zero-copy RefMut gets dropped. Required
+                            // so that we can subsequently run the exit routine.
+                            {
+                                let mut instance = loader.load_init()?;
+                                instance.new(
+                                    anchor_lang::Context::new(
+                                        program_id,
+                                        &mut ctor_user_def_accounts,
+                                        remaining_accounts,
+                                    ),
+                                    #(#ctor_untyped_args),*
+                                )?;
+                            }
+
+                            // Exit routines.
+                            ctor_user_def_accounts.exit(program_id)?;
+                            loader.exit(program_id)?;
+
+                            Ok(())
+                        }
+                    }
+                } else {
+                    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;
+
+                            // 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)?;
+
+                            // Invoke the ctor.
+                            let instance = #mod_name::#name::new(
+                                anchor_lang::Context::new(
+                                    program_id,
+                                    &mut ctor_user_def_accounts,
+                                    remaining_accounts,
+                                ),
+                                #(#ctor_untyped_args),*
+                            )?;
+
+                            // Create the solana account for the ctor data.
+                            let from = ctor_accounts.from.key;
+                            let (base, nonce) = Pubkey::find_program_address(&[], ctor_accounts.program.key);
+                            let seed = anchor_lang::ProgramState::<#name>::seed();
+                            let owner = ctor_accounts.program.key;
+                            let to = Pubkey::create_with_seed(&base, seed, owner).unwrap();
+                            let space = anchor_lang::__private::AccountSize::size(&instance)?;
+                            let lamports = ctor_accounts.rent.minimum_balance(std::convert::TryInto::try_into(space).unwrap());
+                            let seeds = &[&[nonce][..]];
+                            let ix = anchor_lang::solana_program::system_instruction::create_account_with_seed(
+                                from,
+                                &to,
+                                &base,
+                                seed,
+                                lamports,
+                                space,
+                                owner,
+                            );
+                            anchor_lang::solana_program::program::invoke_signed(
+                                &ix,
+                                &[
+                                    ctor_accounts.from.clone(),
+                                    ctor_accounts.to.clone(),
+                                    ctor_accounts.base.clone(),
+                                    ctor_accounts.system_program.clone(),
+                                ],
+                                &[seeds],
+                            )?;
+
+                            // Serialize the state and save it to storage.
+                            ctor_user_def_accounts.exit(program_id)?;
+                            let mut data = ctor_accounts.to.try_borrow_mut_data()?;
+                            let dst: &mut [u8] = &mut data;
+                            let mut cursor = std::io::Cursor::new(dst);
+                            instance.try_serialize(&mut cursor)?;
+
+                            Ok(())
+                        }
+                    }
+                }
+            }
+        },
+    };
+
+    // State method handlers.
+    let non_inlined_state_handlers: Vec<proc_macro2::TokenStream> = match &program.state {
+        None => vec![],
+        Some(state) => state
+            .impl_block_and_methods
+            .as_ref()
+            .map(|(_impl_block, methods)| {
+                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 n = format!("__{}", &ix.raw_method.sig.ident.to_string());
+                            n.parse().unwrap()
+                        };
+                        let ix_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;
+
+                        if state.is_zero_copy {
+                            quote! {
+                                #[inline(never)]
+                                pub fn #private_ix_name(
+                                    program_id: &Pubkey,
+                                    accounts: &[AccountInfo],
+                                    #(#ix_params),*
+                                ) -> ProgramResult {
+                                    let mut remaining_accounts: &[AccountInfo] = accounts;
+                                    if remaining_accounts.is_empty() {
+                                        return Err(ProgramError::Custom(1)); // todo
+                                    }
+
+                                    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.
+                                    let mut accounts = #anchor_ident::try_accounts(
+                                        program_id,
+                                        &mut remaining_accounts,
+                                    )?;
+                                    let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
+                                    // Execute user defined function.
+                                    {
+                                        let mut state = loader.load_mut()?;
+                                        state.#ix_name(
+                                            ctx,
+                                            #(#ix_arg_names),*
+                                        )?;
+                                    }
+                                    // Serialize the state and save it to storage.
+                                    accounts.exit(program_id)?;
+                                    loader.exit(program_id)?;
+
+                                    Ok(())
+                                }
+                            }
+                        } else {
+                            quote! {
+                                #[inline(never)]
+                                pub fn #private_ix_name(
+                                    program_id: &Pubkey,
+                                    accounts: &[AccountInfo],
+                                    #(#ix_params),*
+                                ) -> ProgramResult {
+                                    let mut remaining_accounts: &[AccountInfo] = accounts;
+                                    if remaining_accounts.is_empty() {
+                                        return Err(ProgramError::Custom(1)); // todo
+                                    }
+
+                                    // 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.
+                                    let mut accounts = #anchor_ident::try_accounts(
+                                        program_id,
+                                        &mut remaining_accounts,
+                                    )?;
+                                    let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
+
+                                    // Execute user defined function.
+                                    state.#ix_name(
+                                        ctx,
+                                        #(#ix_arg_names),*
+                                    )?;
+
+                                    // Serialize the state and save it to storage.
+                                    accounts.exit(program_id)?;
+                                    let mut data = state_account.try_borrow_mut_data()?;
+                                    let dst: &mut [u8] = &mut data;
+                                    let mut cursor = std::io::Cursor::new(dst);
+                                    state.try_serialize(&mut cursor)?;
+
+                                    Ok(())
+                                }
+                            }
+                        }
+                    })
+                    .collect()
+            })
+            .unwrap_or_default(),
+    };
+
+    // State trait handlers.
+    let non_inlined_state_trait_handlers: Vec<proc_macro2::TokenStream> = match &program.state {
+        None => Vec::new(),
+        Some(state) => state
+            .interfaces
+            .as_ref()
+            .map(|interfaces| {
+                interfaces
+                    .iter()
+                    .flat_map(|iface: &crate::StateInterface| {
+                        iface
+                            .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 n = format!("__{}_{}", iface.trait_name, &ix.raw_method.sig.ident.to_string());
+                                    n.parse().unwrap()
+                                };
+                                let ix_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.");
+                                }
+
+                                if ix.has_receiver {
+                                    quote! {
+                                        #[inline(never)]
+                                        pub fn #private_ix_name(
+                                            program_id: &Pubkey,
+                                            accounts: &[AccountInfo],
+                                            #(#ix_params),*
+                                        ) -> ProgramResult {
+
+                                            let mut remaining_accounts: &[AccountInfo] = accounts;
+                                            if remaining_accounts.is_empty() {
+                                                return Err(ProgramError::Custom(1)); // todo
+                                            }
+
+                                            // 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.
+                                            let mut accounts = #anchor_ident::try_accounts(
+                                                program_id,
+                                                &mut remaining_accounts,
+                                            )?;
+                                            let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
+
+                                            // Execute user defined function.
+                                            state.#ix_name(
+                                                ctx,
+                                                #(#ix_arg_names),*
+                                            )?;
+
+                                            // Serialize the state and save it to storage.
+                                            accounts.exit(program_id)?;
+                                            let mut data = state_account.try_borrow_mut_data()?;
+                                            let dst: &mut [u8] = &mut data;
+                                            let mut cursor = std::io::Cursor::new(dst);
+                                            state.try_serialize(&mut cursor)?;
+
+                                            Ok(())
+                                        }
+                                    }
+                                } else {
+                                    let state_name: proc_macro2::TokenStream = state.name.parse().unwrap();
+                                    quote! {
+                                        #[inline(never)]
+                                        pub fn #private_ix_name(
+                                            program_id: &Pubkey,
+                                            accounts: &[AccountInfo],
+                                            #(#ix_params),*
+                                        ) -> ProgramResult {
+                                            let mut remaining_accounts: &[AccountInfo] = accounts;
+                                            let mut accounts = #anchor_ident::try_accounts(
+                                                program_id,
+                                                &mut remaining_accounts,
+                                            )?;
+                                            #state_name::#ix_name(
+                                                Context::new(program_id, &mut accounts, remaining_accounts),
+                                                #(#ix_arg_names),*
+                                            )?;
+                                            accounts.exit(program_id)
+                                        }
+                                    }
+                                }
+                            })
+                            .collect::<Vec<proc_macro2::TokenStream>>()
+                    })
+                    .collect()
+            })
+            .unwrap_or_default(),
+    };
+    let non_inlined_handlers: Vec<proc_macro2::TokenStream> = program
+        .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 anchor = &ix.anchor_ident;
+
+            quote! {
+                #[inline(never)]
+                pub fn #ix_name(
+                    program_id: &Pubkey,
+                    accounts: &[AccountInfo],
+                    #(#ix_params),*
+                ) -> ProgramResult {
+                    let mut remaining_accounts: &[AccountInfo] = accounts;
+                    let mut accounts = #anchor::try_accounts(program_id, &mut remaining_accounts)?;
+                    #program_name::#ix_name(
+                        Context::new(program_id, &mut accounts, remaining_accounts),
+                        #(#ix_arg_names),*
+                    )?;
+                    accounts.exit(program_id)
+                }
+            }
+        })
+        .collect();
+
+    quote! {
+        /// Create a private module to not clutter the program's namespace.
+        /// Defines an entrypoint for each individual instruction handler
+        /// wrapper.
+        mod __private {
+            use super::*;
+            /// __idl mod defines handlers for injected Anchor IDL instructions.
+            pub mod __idl {
+                use super::*;
+
+                #non_inlined_idl
+            }
+
+            /// __state mod defines wrapped handlers for state instructions.
+            pub mod __state {
+                use super::*;
+
+                #non_inlined_ctor
+                #(#non_inlined_state_handlers)*
+            }
+
+            /// __interface mod defines wrapped handlers for `#[interface]` trait
+            /// implementations.
+            pub mod __interface {
+                use super::*;
+
+                #(#non_inlined_state_trait_handlers)*
+            }
+
+            /// __global mod defines wrapped handlers for global instructions.
+            pub mod __global {
+                use super::*;
+
+                #(#non_inlined_handlers)*
+            }
+        }
+    }
+}

+ 194 - 0
lang/syn/src/codegen/program/instruction.rs

@@ -0,0 +1,194 @@
+use crate::codegen::program::common::*;
+use crate::parser;
+use crate::Program;
+use heck::CamelCase;
+use quote::quote;
+
+pub fn generate(program: &Program) -> proc_macro2::TokenStream {
+    let ctor_variant = match &program.state {
+        None => quote! {},
+        Some(state) => {
+            let ctor_args: Vec<proc_macro2::TokenStream> = generate_ctor_typed_args(state)
+                .iter()
+                .map(|arg| {
+                    format!("pub {}", parser::tts_to_string(&arg))
+                        .parse()
+                        .unwrap()
+                })
+                .collect();
+            let strct = {
+                if ctor_args.is_empty() {
+                    quote! {
+                        #[derive(AnchorSerialize, AnchorDeserialize)]
+                        pub struct New;
+                    }
+                } else {
+                    quote! {
+                        #[derive(AnchorSerialize, AnchorDeserialize)]
+                        pub struct New {
+                            #(#ctor_args),*
+                        }
+                    }
+                }
+            };
+            let sighash_arr = sighash_ctor();
+            let sighash_tts: proc_macro2::TokenStream =
+                format!("{:?}", sighash_arr).parse().unwrap();
+            quote! {
+                /// Instruction arguments to the `#[state]`'s `new`
+                /// constructor.
+                #strct
+
+                impl anchor_lang::InstructionData for New {
+                    fn data(&self) -> Vec<u8> {
+                        let mut d = #sighash_tts.to_vec();
+                        d.append(&mut self.try_to_vec().expect("Should always serialize"));
+                        d
+                    }
+                }
+            }
+        }
+    };
+    let state_method_variants: Vec<proc_macro2::TokenStream> = match &program.state {
+        None => vec![],
+        Some(state) => state
+            .impl_block_and_methods
+            .as_ref()
+            .map(|(_impl_block, methods)| {
+                methods
+                    .iter()
+                    .map(|method| {
+                        let ix_name_camel: proc_macro2::TokenStream = method
+                            .raw_method
+                            .sig
+                            .ident
+                            .to_string()
+                            .to_camel_case()
+                            .parse()
+                            .unwrap();
+                        let raw_args: Vec<proc_macro2::TokenStream> = method
+                            .args
+                            .iter()
+                            .map(|arg| {
+                                format!("pub {}", parser::tts_to_string(&arg.raw_arg))
+                                    .parse()
+                                    .unwrap()
+                            })
+                            .collect();
+
+                        let ix_data_trait = {
+                            let name = method.raw_method.sig.ident.to_string();
+                            let sighash_arr = sighash(SIGHASH_STATE_NAMESPACE, &name);
+                            let sighash_tts: proc_macro2::TokenStream =
+                                format!("{:?}", sighash_arr).parse().unwrap();
+                            quote! {
+                                impl anchor_lang::InstructionData for #ix_name_camel {
+                                    fn data(&self) -> Vec<u8> {
+                                        let mut d = #sighash_tts.to_vec();
+                                        d.append(&mut self.try_to_vec().expect("Should always serialize"));
+                                        d
+                                    }
+                                }
+                            }
+                        };
+
+                        // If no args, output a "unit" variant instead of a struct variant.
+                        if method.args.is_empty() {
+                            quote! {
+                                /// Anchor generated instruction.
+                                #[derive(AnchorSerialize, AnchorDeserialize)]
+                                pub struct #ix_name_camel;
+
+                                #ix_data_trait
+                            }
+                        } else {
+                            quote! {
+                                /// Anchor generated instruction.
+                                #[derive(AnchorSerialize, AnchorDeserialize)]
+                                pub struct #ix_name_camel {
+                                    #(#raw_args),*
+                                }
+
+                                #ix_data_trait
+                            }
+                        }
+                    })
+                    .collect()
+            })
+            .unwrap_or_default(),
+    };
+    let variants: Vec<proc_macro2::TokenStream> = program
+        .ixs
+        .iter()
+        .map(|ix| {
+            let name = &ix.raw_method.sig.ident.to_string();
+            let ix_name_camel =
+                proc_macro2::Ident::new(&name.to_camel_case(), ix.raw_method.sig.ident.span());
+            let raw_args: Vec<proc_macro2::TokenStream> = ix
+                .args
+                .iter()
+                .map(|arg| {
+                    format!("pub {}", parser::tts_to_string(&arg.raw_arg))
+                        .parse()
+                        .unwrap()
+                })
+                .collect();
+            let ix_data_trait = {
+                let sighash_arr = sighash(SIGHASH_GLOBAL_NAMESPACE, &name);
+                let sighash_tts: proc_macro2::TokenStream =
+                    format!("{:?}", sighash_arr).parse().unwrap();
+                quote! {
+                    impl anchor_lang::InstructionData for #ix_name_camel {
+                        fn data(&self) -> Vec<u8> {
+                            let mut d = #sighash_tts.to_vec();
+                            d.append(&mut self.try_to_vec().expect("Should always serialize"));
+                            d
+                        }
+                    }
+                }
+            };
+            // If no args, output a "unit" variant instead of a struct variant.
+            if ix.args.is_empty() {
+                quote! {
+                    /// Instruction.
+                    #[derive(AnchorSerialize, AnchorDeserialize)]
+                    pub struct #ix_name_camel;
+
+                    #ix_data_trait
+                }
+            } else {
+                quote! {
+                    /// Instruction.
+                    #[derive(AnchorSerialize, AnchorDeserialize)]
+                    pub struct #ix_name_camel {
+                        #(#raw_args),*
+                    }
+
+                    #ix_data_trait
+                }
+            }
+        })
+        .collect();
+
+    quote! {
+        /// An Anchor generated module containing the program's set of
+        /// instructions, where each method handler in the `#[program]` mod is
+        /// associated with a struct defining the input arguments to the
+        /// method. These should be used directly, when one wants to serialize
+        /// Anchor instruction data, for example, when speciying
+        /// instructions on a client.
+        pub mod instruction {
+            use super::*;
+
+            /// Instruction struct definitions for `#[state]` methods.
+            pub mod state {
+                use super::*;
+
+                #ctor_variant
+                #(#state_method_variants)*
+            }
+
+            #(#variants)*
+        }
+    }
+}

+ 35 - 0
lang/syn/src/codegen/program/mod.rs

@@ -0,0 +1,35 @@
+use crate::Program;
+use quote::quote;
+
+mod accounts;
+pub mod common;
+mod cpi;
+mod dispatch;
+mod entry;
+mod handlers;
+mod instruction;
+
+pub fn generate(program: &Program) -> proc_macro2::TokenStream {
+    let mod_name = &program.name;
+
+    let entry = entry::generate(program);
+    let dispatch = dispatch::generate(program);
+    let handlers = handlers::generate(program);
+    let user_defined_program = &program.program_mod;
+    let instruction = instruction::generate(program);
+    let cpi = cpi::generate(program);
+    let accounts = accounts::generate(program);
+
+    quote! {
+        // TODO: remove once we allow segmented paths in `Accounts` structs.
+        use #mod_name::*;
+
+        #entry
+        #dispatch
+        #handlers
+        #user_defined_program
+        #instruction
+        #cpi
+        #accounts
+    }
+}

+ 33 - 6
lang/syn/src/parser/file.rs → lang/syn/src/idl/file.rs

@@ -1,6 +1,6 @@
 use crate::idl::*;
 use crate::idl::*;
 use crate::parser::{self, accounts, error, program};
 use crate::parser::{self, accounts, error, program};
-use crate::{AccountsStruct, StateIx};
+use crate::{AccountField, AccountsStruct, StateIx};
 use anyhow::Result;
 use anyhow::Result;
 use heck::MixedCase;
 use heck::MixedCase;
 use quote::ToTokens;
 use quote::ToTokens;
@@ -21,7 +21,7 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
 
 
     let f = syn::parse_file(&src).expect("Unable to parse file");
     let f = syn::parse_file(&src).expect("Unable to parse file");
 
 
-    let p = program::parse(parse_program_mod(&f));
+    let p = program::parse(parse_program_mod(&f))?;
 
 
     let accs = parse_account_derives(&f);
     let accs = parse_account_derives(&f);
 
 
@@ -52,7 +52,7 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
                                     .collect::<Vec<_>>();
                                     .collect::<Vec<_>>();
                                 let accounts_strct =
                                 let accounts_strct =
                                     accs.get(&method.anchor_ident.to_string()).unwrap();
                                     accs.get(&method.anchor_ident.to_string()).unwrap();
-                                let accounts = accounts_strct.idl_accounts(&accs);
+                                let accounts = idl_accounts(accounts_strct, &accs);
                                 IdlStateMethod {
                                 IdlStateMethod {
                                     name,
                                     name,
                                     args,
                                     args,
@@ -91,7 +91,7 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
                         })
                         })
                         .collect();
                         .collect();
                     let accounts_strct = accs.get(&anchor_ident.to_string()).unwrap();
                     let accounts_strct = accs.get(&anchor_ident.to_string()).unwrap();
-                    let accounts = accounts_strct.idl_accounts(&accs);
+                    let accounts = idl_accounts(&accounts_strct, &accs);
                     IdlStateMethod {
                     IdlStateMethod {
                         name,
                         name,
                         args,
                         args,
@@ -159,7 +159,7 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
                 .collect::<Vec<_>>();
                 .collect::<Vec<_>>();
             // todo: don't unwrap
             // todo: don't unwrap
             let accounts_strct = accs.get(&ix.anchor_ident.to_string()).unwrap();
             let accounts_strct = accs.get(&ix.anchor_ident.to_string()).unwrap();
-            let accounts = accounts_strct.idl_accounts(&accs);
+            let accounts = idl_accounts(accounts_strct, &accs);
             IdlIx {
             IdlIx {
                 name: ix.ident.to_string().to_mixed_case(),
                 name: ix.ident.to_string().to_mixed_case(),
                 accounts,
                 accounts,
@@ -345,7 +345,7 @@ fn parse_account_derives(f: &syn::File) -> HashMap<String, AccountsStruct> {
             syn::Item::Struct(i_strct) => {
             syn::Item::Struct(i_strct) => {
                 for attr in &i_strct.attrs {
                 for attr in &i_strct.attrs {
                     if attr.tokens.to_string().contains(DERIVE_NAME) {
                     if attr.tokens.to_string().contains(DERIVE_NAME) {
-                        let strct = accounts::parse(i_strct);
+                        let strct = accounts::parse(i_strct).expect("Code not parseable");
                         return Some((strct.ident.to_string(), strct));
                         return Some((strct.ident.to_string(), strct));
                     }
                     }
                 }
                 }
@@ -439,3 +439,30 @@ fn to_idl_type(f: &syn::Field) -> IdlType {
     f.ty.to_tokens(&mut tts);
     f.ty.to_tokens(&mut tts);
     tts.to_string().parse().unwrap()
     tts.to_string().parse().unwrap()
 }
 }
+
+fn idl_accounts(
+    accounts: &AccountsStruct,
+    global_accs: &HashMap<String, AccountsStruct>,
+) -> Vec<IdlAccountItem> {
+    accounts
+        .fields
+        .iter()
+        .map(|acc: &AccountField| match acc {
+            AccountField::CompositeField(comp_f) => {
+                let accs_strct = global_accs
+                    .get(&comp_f.symbol)
+                    .expect("Could not resolve Accounts symbol");
+                let accounts = idl_accounts(accs_strct, global_accs);
+                IdlAccountItem::IdlAccounts(IdlAccounts {
+                    name: comp_f.ident.to_string().to_mixed_case(),
+                    accounts,
+                })
+            }
+            AccountField::Field(acc) => IdlAccountItem::IdlAccount(IdlAccount {
+                name: acc.ident.to_string().to_mixed_case(),
+                is_mut: acc.constraints.is_mutable(),
+                is_signer: acc.constraints.is_signer(),
+            }),
+        })
+        .collect::<Vec<_>>()
+}

+ 2 - 0
lang/syn/src/idl.rs → lang/syn/src/idl/mod.rs

@@ -1,5 +1,7 @@
 use serde::{Deserialize, Serialize};
 use serde::{Deserialize, Serialize};
 
 
+pub mod file;
+
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub struct Idl {
 pub struct Idl {
     pub version: String,
     pub version: String,

+ 238 - 200
lang/syn/src/lib.rs

@@ -1,12 +1,17 @@
-//! DSL syntax tokens.
-
-#[cfg(feature = "idl")]
-use crate::idl::{IdlAccount, IdlAccountItem, IdlAccounts};
-use anyhow::Result;
-#[cfg(feature = "idl")]
-use heck::MixedCase;
-use quote::quote;
-use std::collections::HashMap;
+use codegen::accounts as accounts_codegen;
+use codegen::program as program_codegen;
+use parser::accounts as accounts_parser;
+use parser::program as program_parser;
+use proc_macro2::{Span, TokenStream};
+use quote::ToTokens;
+use std::ops::Deref;
+use syn::parse::{Parse, ParseStream, Result as ParseResult};
+use syn::punctuated::Punctuated;
+use syn::spanned::Spanned;
+use syn::{
+    Expr, Generics, Ident, ImplItemMethod, ItemEnum, ItemFn, ItemImpl, ItemMod, ItemStruct, LitInt,
+    LitStr, PatType, Token,
+};
 
 
 pub mod codegen;
 pub mod codegen;
 #[cfg(feature = "hash")]
 #[cfg(feature = "hash")]
@@ -21,27 +26,45 @@ pub mod parser;
 pub struct Program {
 pub struct Program {
     pub state: Option<State>,
     pub state: Option<State>,
     pub ixs: Vec<Ix>,
     pub ixs: Vec<Ix>,
-    pub name: syn::Ident,
-    pub program_mod: syn::ItemMod,
+    pub name: Ident,
+    pub program_mod: ItemMod,
+}
+
+impl Parse for Program {
+    fn parse(input: ParseStream) -> ParseResult<Self> {
+        let program_mod = <ItemMod as Parse>::parse(input)?;
+        program_parser::parse(program_mod)
+    }
+}
+
+impl From<&Program> for TokenStream {
+    fn from(program: &Program) -> Self {
+        program_codegen::generate(program)
+    }
+}
+
+impl ToTokens for Program {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        tokens.extend::<TokenStream>(self.into());
+    }
 }
 }
 
 
-// State struct singleton.
 #[derive(Debug)]
 #[derive(Debug)]
 pub struct State {
 pub struct State {
     pub name: String,
     pub name: String,
-    pub strct: syn::ItemStruct,
-    pub ctor_and_anchor: Option<(syn::ImplItemMethod, syn::Ident)>,
-    pub impl_block_and_methods: Option<(syn::ItemImpl, Vec<StateIx>)>,
+    pub strct: ItemStruct,
+    pub ctor_and_anchor: Option<(ImplItemMethod, Ident)>,
+    pub impl_block_and_methods: Option<(ItemImpl, Vec<StateIx>)>,
     pub interfaces: Option<Vec<StateInterface>>,
     pub interfaces: Option<Vec<StateInterface>>,
     pub is_zero_copy: bool,
     pub is_zero_copy: bool,
 }
 }
 
 
 #[derive(Debug)]
 #[derive(Debug)]
 pub struct StateIx {
 pub struct StateIx {
-    pub raw_method: syn::ImplItemMethod,
-    pub ident: syn::Ident,
+    pub raw_method: ImplItemMethod,
+    pub ident: Ident,
     pub args: Vec<IxArg>,
     pub args: Vec<IxArg>,
-    pub anchor_ident: syn::Ident,
+    pub anchor_ident: Ident,
     // True if there exists a &self on the method.
     // True if there exists a &self on the method.
     pub has_receiver: bool,
     pub has_receiver: bool,
 }
 }
@@ -54,31 +77,50 @@ pub struct StateInterface {
 
 
 #[derive(Debug)]
 #[derive(Debug)]
 pub struct Ix {
 pub struct Ix {
-    pub raw_method: syn::ItemFn,
-    pub ident: syn::Ident,
+    pub raw_method: ItemFn,
+    pub ident: Ident,
     pub args: Vec<IxArg>,
     pub args: Vec<IxArg>,
     // The ident for the struct deriving Accounts.
     // The ident for the struct deriving Accounts.
-    pub anchor_ident: syn::Ident,
+    pub anchor_ident: Ident,
 }
 }
 
 
 #[derive(Debug)]
 #[derive(Debug)]
 pub struct IxArg {
 pub struct IxArg {
-    pub name: proc_macro2::Ident,
-    pub raw_arg: syn::PatType,
+    pub name: Ident,
+    pub raw_arg: PatType,
 }
 }
 
 
 #[derive(Debug)]
 #[derive(Debug)]
 pub struct AccountsStruct {
 pub struct AccountsStruct {
     // Name of the accounts struct.
     // Name of the accounts struct.
-    pub ident: syn::Ident,
+    pub ident: Ident,
     // Generics + lifetimes on the accounts struct.
     // Generics + lifetimes on the accounts struct.
-    pub generics: syn::Generics,
+    pub generics: Generics,
     // Fields on the accounts struct.
     // Fields on the accounts struct.
     pub fields: Vec<AccountField>,
     pub fields: Vec<AccountField>,
 }
 }
 
 
+impl Parse for AccountsStruct {
+    fn parse(input: ParseStream) -> ParseResult<Self> {
+        let strct = <ItemStruct as Parse>::parse(input)?;
+        accounts_parser::parse(&strct)
+    }
+}
+
+impl From<&AccountsStruct> for TokenStream {
+    fn from(accounts: &AccountsStruct) -> Self {
+        accounts_codegen::generate(accounts)
+    }
+}
+
+impl ToTokens for AccountsStruct {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        tokens.extend::<TokenStream>(self.into());
+    }
+}
+
 impl AccountsStruct {
 impl AccountsStruct {
-    pub fn new(strct: syn::ItemStruct, fields: Vec<AccountField>) -> Self {
+    pub fn new(strct: ItemStruct, fields: Vec<AccountField>) -> Self {
         let ident = strct.ident.clone();
         let ident = strct.ident.clone();
         let generics = strct.generics;
         let generics = strct.generics;
         Self {
         Self {
@@ -87,159 +129,27 @@ impl AccountsStruct {
             fields,
             fields,
         }
         }
     }
     }
-
-    // Returns all program owned accounts in the Accounts struct.
-    //
-    // `global_accs` is given to "link" account types that are embedded
-    // in each other.
-    pub fn account_tys(
-        &self,
-        global_accs: &HashMap<String, AccountsStruct>,
-    ) -> Result<Vec<String>> {
-        let mut tys = vec![];
-        for f in &self.fields {
-            match f {
-                AccountField::Field(f) => {
-                    if let Ty::ProgramAccount(pty) = &f.ty {
-                        tys.push(pty.account_ident.to_string());
-                    }
-                }
-                AccountField::AccountsStruct(comp_f) => {
-                    let accs = global_accs.get(&comp_f.symbol).ok_or_else(|| {
-                        anyhow::format_err!("Invalid account type: {}", comp_f.symbol)
-                    })?;
-                    tys.extend(accs.account_tys(global_accs)?);
-                }
-            }
-        }
-        Ok(tys)
-    }
-
-    #[cfg(feature = "idl")]
-    pub fn idl_accounts(
-        &self,
-        global_accs: &HashMap<String, AccountsStruct>,
-    ) -> Vec<IdlAccountItem> {
-        self.fields
-            .iter()
-            .map(|acc: &AccountField| match acc {
-                AccountField::AccountsStruct(comp_f) => {
-                    let accs_strct = global_accs
-                        .get(&comp_f.symbol)
-                        .expect("Could not reslve Accounts symbol");
-                    let accounts = accs_strct.idl_accounts(global_accs);
-                    IdlAccountItem::IdlAccounts(IdlAccounts {
-                        name: comp_f.ident.to_string().to_mixed_case(),
-                        accounts,
-                    })
-                }
-                AccountField::Field(acc) => IdlAccountItem::IdlAccount(IdlAccount {
-                    name: acc.ident.to_string().to_mixed_case(),
-                    is_mut: acc.is_mut,
-                    is_signer: acc.is_signer,
-                }),
-            })
-            .collect::<Vec<_>>()
-    }
 }
 }
 
 
 #[derive(Debug)]
 #[derive(Debug)]
 pub enum AccountField {
 pub enum AccountField {
-    // Use a `String` instead of the `AccountsStruct` because all
-    // accounts structs aren't visible to a single derive macro.
-    //
-    // When we need the global context, we fill in the String with the
-    // appropriate values. See, `account_tys` as an example.
-    AccountsStruct(CompositeField), // Composite
-    Field(Field),                   // Primitive
+    Field(Field),
+    CompositeField(CompositeField),
 }
 }
 
 
 #[derive(Debug)]
 #[derive(Debug)]
-pub struct CompositeField {
-    pub ident: syn::Ident,
-    pub symbol: String,
-    pub constraints: Vec<Constraint>,
-    pub raw_field: syn::Field,
+pub struct Field {
+    pub ident: Ident,
+    pub constraints: ConstraintGroup,
+    pub ty: Ty,
 }
 }
 
 
-// An account in the accounts struct.
 #[derive(Debug)]
 #[derive(Debug)]
-pub struct Field {
-    pub ident: syn::Ident,
-    pub ty: Ty,
-    pub constraints: Vec<Constraint>,
-    pub is_mut: bool,
-    pub is_signer: bool,
-    pub is_init: bool,
-    // TODO: move associated out of the constraints and put into tis own
-    //       field + struct.
-    // Used by the associated attribute only.
-    pub payer: Option<syn::Ident>,
-    // Used by the associated attribute only.
-    pub space: Option<proc_macro2::TokenStream>,
-    // Used by the associated attribute only.
-    pub associated_seeds: Vec<syn::Ident>,
-}
-
-impl Field {
-    pub fn typed_ident(&self) -> proc_macro2::TokenStream {
-        let name = &self.ident;
-
-        let ty = match &self.ty {
-            Ty::AccountInfo => quote! { AccountInfo },
-            Ty::ProgramState(ty) => {
-                let account = &ty.account_ident;
-                quote! {
-                    ProgramState<#account>
-                }
-            }
-            Ty::CpiState(ty) => {
-                let account = &ty.account_ident;
-                quote! {
-                    CpiState<#account>
-                }
-            }
-            Ty::ProgramAccount(ty) => {
-                let account = &ty.account_ident;
-                quote! {
-                    ProgramAccount<#account>
-                }
-            }
-            Ty::Loader(ty) => {
-                let account = &ty.account_ident;
-                quote! {
-                    Loader<#account>
-                }
-            }
-            Ty::CpiAccount(ty) => {
-                let account = &ty.account_ident;
-                quote! {
-                    CpiAccount<#account>
-                }
-            }
-            Ty::Sysvar(ty) => {
-                let account = match ty {
-                    SysvarTy::Clock => quote! {Clock},
-                    SysvarTy::Rent => quote! {Rent},
-                    SysvarTy::EpochSchedule => quote! {EpochSchedule},
-                    SysvarTy::Fees => quote! {Fees},
-                    SysvarTy::RecentBlockhashes => quote! {RecentBlockhashes},
-                    SysvarTy::SlotHashes => quote! {SlotHashes},
-                    SysvarTy::SlotHistory => quote! {SlotHistory},
-                    SysvarTy::StakeHistory => quote! {StakeHistory},
-                    SysvarTy::Instructions => quote! {Instructions},
-                    SysvarTy::Rewards => quote! {Rewards},
-                };
-                quote! {
-                    Sysvar<#account>
-                }
-            }
-        };
-
-        quote! {
-            #name: #ty
-        }
-    }
+pub struct CompositeField {
+    pub ident: Ident,
+    pub constraints: ConstraintGroup,
+    pub symbol: String,
+    pub raw_field: syn::Field,
 }
 }
 
 
 // A type of an account field.
 // A type of an account field.
@@ -270,100 +180,228 @@ pub enum SysvarTy {
 
 
 #[derive(Debug, PartialEq)]
 #[derive(Debug, PartialEq)]
 pub struct ProgramStateTy {
 pub struct ProgramStateTy {
-    pub account_ident: syn::Ident,
+    pub account_ident: Ident,
 }
 }
 
 
 #[derive(Debug, PartialEq)]
 #[derive(Debug, PartialEq)]
 pub struct CpiStateTy {
 pub struct CpiStateTy {
-    pub account_ident: syn::Ident,
+    pub account_ident: Ident,
 }
 }
 
 
 #[derive(Debug, PartialEq)]
 #[derive(Debug, PartialEq)]
 pub struct ProgramAccountTy {
 pub struct ProgramAccountTy {
     // The struct type of the account.
     // The struct type of the account.
-    pub account_ident: syn::Ident,
+    pub account_ident: Ident,
 }
 }
 
 
 #[derive(Debug, PartialEq)]
 #[derive(Debug, PartialEq)]
 pub struct CpiAccountTy {
 pub struct CpiAccountTy {
     // The struct type of the account.
     // The struct type of the account.
-    pub account_ident: syn::Ident,
+    pub account_ident: Ident,
 }
 }
 
 
 #[derive(Debug, PartialEq)]
 #[derive(Debug, PartialEq)]
 pub struct LoaderTy {
 pub struct LoaderTy {
     // The struct type of the account.
     // The struct type of the account.
-    pub account_ident: syn::Ident,
+    pub account_ident: Ident,
+}
+
+#[derive(Debug)]
+pub struct Error {
+    pub name: String,
+    pub raw_enum: ItemEnum,
+    pub ident: Ident,
+    pub codes: Vec<ErrorCode>,
 }
 }
 
 
-// An access control constraint for an account.
+#[derive(Debug)]
+pub struct ErrorCode {
+    pub id: u32,
+    pub ident: Ident,
+    pub msg: Option<String>,
+}
+
+// All well formed constraints on a single `Accounts` field.
+#[derive(Debug, Default, Clone)]
+pub struct ConstraintGroup {
+    init: Option<ConstraintInit>,
+    mutable: Option<ConstraintMut>,
+    signer: Option<ConstraintSigner>,
+    owner: Option<ConstraintOwner>,
+    rent_exempt: Option<ConstraintRentExempt>,
+    seeds: Option<ConstraintSeeds>,
+    executable: Option<ConstraintExecutable>,
+    state: Option<ConstraintState>,
+    associated: Option<ConstraintAssociatedGroup>,
+    belongs_to: Vec<ConstraintBelongsTo>,
+    literal: Vec<ConstraintLiteral>,
+    raw: Vec<ConstraintRaw>,
+}
+
+impl ConstraintGroup {
+    pub fn is_init(&self) -> bool {
+        self.init.is_some()
+    }
+
+    pub fn is_mutable(&self) -> bool {
+        self.mutable.is_some()
+    }
+
+    pub fn is_signer(&self) -> bool {
+        self.signer.is_some()
+    }
+}
+
+// A single account constraint *after* merging all tokens into a well formed
+// constraint. Some constraints like "associated" are defined by multiple
+// tokens, so a merging phase is required.
 #[derive(Debug)]
 #[derive(Debug)]
 pub enum Constraint {
 pub enum Constraint {
+    Init(ConstraintInit),
+    Mut(ConstraintMut),
     Signer(ConstraintSigner),
     Signer(ConstraintSigner),
     BelongsTo(ConstraintBelongsTo),
     BelongsTo(ConstraintBelongsTo),
     Literal(ConstraintLiteral),
     Literal(ConstraintLiteral),
+    Raw(ConstraintRaw),
     Owner(ConstraintOwner),
     Owner(ConstraintOwner),
     RentExempt(ConstraintRentExempt),
     RentExempt(ConstraintRentExempt),
     Seeds(ConstraintSeeds),
     Seeds(ConstraintSeeds),
     Executable(ConstraintExecutable),
     Executable(ConstraintExecutable),
     State(ConstraintState),
     State(ConstraintState),
-    Associated(ConstraintAssociated),
+    AssociatedGroup(ConstraintAssociatedGroup),
 }
 }
 
 
+// Constraint token is a single keyword in a `#[account(<TOKEN>)]` attribute.
 #[derive(Debug)]
 #[derive(Debug)]
-pub struct ConstraintBelongsTo {
-    pub join_target: proc_macro2::Ident,
+pub enum ConstraintToken {
+    Init(Context<ConstraintInit>),
+    Mut(Context<ConstraintMut>),
+    Signer(Context<ConstraintSigner>),
+    BelongsTo(Context<ConstraintBelongsTo>),
+    Literal(Context<ConstraintLiteral>),
+    Raw(Context<ConstraintRaw>),
+    Owner(Context<ConstraintOwner>),
+    RentExempt(Context<ConstraintRentExempt>),
+    Seeds(Context<ConstraintSeeds>),
+    Executable(Context<ConstraintExecutable>),
+    State(Context<ConstraintState>),
+    AssociatedGroup(ConstraintAssociatedGroup),
+    Associated(Context<ConstraintAssociated>),
+    AssociatedPayer(Context<ConstraintAssociatedPayer>),
+    AssociatedSpace(Context<ConstraintAssociatedSpace>),
+    AssociatedWith(Context<ConstraintAssociatedWith>),
 }
 }
 
 
-#[derive(Debug)]
+impl Parse for ConstraintToken {
+    fn parse(stream: ParseStream) -> ParseResult<Self> {
+        accounts_parser::constraints::parse_token(stream)
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct ConstraintInit {}
+
+#[derive(Debug, Clone)]
+pub struct ConstraintMut {}
+
+#[derive(Debug, Clone)]
 pub struct ConstraintSigner {}
 pub struct ConstraintSigner {}
 
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
+pub struct ConstraintBelongsTo {
+    pub join_target: Ident,
+}
+
+#[derive(Debug, Clone)]
 pub struct ConstraintLiteral {
 pub struct ConstraintLiteral {
-    pub tokens: proc_macro2::TokenStream,
+    pub lit: LitStr,
 }
 }
 
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
+pub struct ConstraintRaw {
+    pub raw: Expr,
+}
+
+#[derive(Debug, Clone)]
 pub struct ConstraintOwner {
 pub struct ConstraintOwner {
-    pub owner_target: proc_macro2::Ident,
+    pub owner_target: Ident,
 }
 }
 
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub enum ConstraintRentExempt {
 pub enum ConstraintRentExempt {
     Enforce,
     Enforce,
     Skip,
     Skip,
 }
 }
 
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct ConstraintSeeds {
 pub struct ConstraintSeeds {
-    pub seeds: proc_macro2::Group,
+    pub seeds: Punctuated<Expr, Token![,]>,
 }
 }
 
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct ConstraintExecutable {}
 pub struct ConstraintExecutable {}
 
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct ConstraintState {
 pub struct ConstraintState {
-    pub program_target: proc_macro2::Ident,
+    pub program_target: Ident,
+}
+
+#[derive(Debug, Clone)]
+pub struct ConstraintAssociatedGroup {
+    pub is_init: bool,
+    pub associated_target: Ident,
+    pub associated_seeds: Vec<Ident>,
+    pub payer: Option<Ident>,
+    pub space: Option<LitInt>,
 }
 }
 
 
 #[derive(Debug)]
 #[derive(Debug)]
 pub struct ConstraintAssociated {
 pub struct ConstraintAssociated {
-    pub associated_target: proc_macro2::Ident,
-    pub is_init: bool,
+    pub target: Ident,
 }
 }
 
 
 #[derive(Debug)]
 #[derive(Debug)]
-pub struct Error {
-    pub name: String,
-    pub raw_enum: syn::ItemEnum,
-    pub ident: syn::Ident,
-    pub codes: Vec<ErrorCode>,
+pub struct ConstraintAssociatedPayer {
+    pub target: Ident,
 }
 }
 
 
 #[derive(Debug)]
 #[derive(Debug)]
-pub struct ErrorCode {
-    pub id: u32,
-    pub ident: syn::Ident,
-    pub msg: Option<String>,
+pub struct ConstraintAssociatedWith {
+    pub target: Ident,
+}
+
+#[derive(Debug)]
+pub struct ConstraintAssociatedSpace {
+    pub space: LitInt,
+}
+
+// Syntaxt context object for preserving metadata about the inner item.
+#[derive(Debug, Clone)]
+pub struct Context<T> {
+    span: Span,
+    inner: T,
+}
+
+impl<T> Context<T> {
+    pub fn new(span: Span, inner: T) -> Self {
+        Self { span, inner }
+    }
+
+    pub fn into_inner(self) -> T {
+        self.inner
+    }
+}
+
+impl<T> Deref for Context<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl<T> Spanned for Context<T> {
+    fn span(&self) -> Span {
+        self.span
+    }
 }
 }

+ 0 - 429
lang/syn/src/parser/accounts.rs

@@ -1,429 +0,0 @@
-use crate::{
-    AccountField, AccountsStruct, CompositeField, Constraint, ConstraintAssociated,
-    ConstraintBelongsTo, ConstraintExecutable, ConstraintLiteral, ConstraintOwner,
-    ConstraintRentExempt, ConstraintSeeds, ConstraintSigner, ConstraintState, CpiAccountTy,
-    CpiStateTy, Field, LoaderTy, ProgramAccountTy, ProgramStateTy, SysvarTy, Ty,
-};
-
-pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
-    let fields = match &strct.fields {
-        syn::Fields::Named(fields) => fields.named.iter().map(parse_account_field).collect(),
-        _ => panic!("invalid input"),
-    };
-    AccountsStruct::new(strct.clone(), fields)
-}
-
-fn parse_account_field(f: &syn::Field) -> AccountField {
-    let anchor_attr = parse_account_attr(f);
-    parse_field(f, anchor_attr)
-}
-
-fn parse_account_attr(f: &syn::Field) -> Option<&syn::Attribute> {
-    let anchor_attrs: Vec<&syn::Attribute> = f
-        .attrs
-        .iter()
-        .filter(|attr| {
-            if attr.path.segments.len() != 1 {
-                return false;
-            }
-            if attr.path.segments[0].ident != "account" {
-                return false;
-            }
-            true
-        })
-        .collect();
-    match anchor_attrs.len() {
-        0 => None,
-        1 => Some(anchor_attrs[0]),
-        _ => panic!("Invalid syntax: please specify one account attribute."),
-    }
-}
-
-fn parse_field(f: &syn::Field, anchor: Option<&syn::Attribute>) -> AccountField {
-    let ident = f.ident.clone().unwrap();
-    let (constraints, is_mut, is_signer, is_init, payer, space, associated_seeds) = match anchor {
-        None => (vec![], false, false, false, None, None, Vec::new()),
-        Some(anchor) => parse_constraints(anchor),
-    };
-    match is_field_primitive(f) {
-        true => {
-            let ty = parse_ty(f);
-            AccountField::Field(Field {
-                ident,
-                ty,
-                constraints,
-                is_mut,
-                is_signer,
-                is_init,
-                payer,
-                space,
-                associated_seeds,
-            })
-        }
-        false => AccountField::AccountsStruct(CompositeField {
-            ident,
-            symbol: ident_string(f),
-            constraints,
-            raw_field: f.clone(),
-        }),
-    }
-}
-
-fn is_field_primitive(f: &syn::Field) -> bool {
-    match ident_string(f).as_str() {
-        "ProgramState" | "ProgramAccount" | "CpiAccount" | "Sysvar" | "AccountInfo"
-        | "CpiState" | "Loader" => true,
-        _ => false,
-    }
-}
-
-fn parse_ty(f: &syn::Field) -> Ty {
-    let path = match &f.ty {
-        syn::Type::Path(ty_path) => ty_path.path.clone(),
-        _ => panic!("invalid account syntax"),
-    };
-    match ident_string(f).as_str() {
-        "ProgramState" => Ty::ProgramState(parse_program_state(&path)),
-        "CpiState" => Ty::CpiState(parse_cpi_state(&path)),
-        "ProgramAccount" => Ty::ProgramAccount(parse_program_account(&path)),
-        "CpiAccount" => Ty::CpiAccount(parse_cpi_account(&path)),
-        "Sysvar" => Ty::Sysvar(parse_sysvar(&path)),
-        "AccountInfo" => Ty::AccountInfo,
-        "Loader" => Ty::Loader(parse_program_account_zero_copy(&path)),
-        _ => panic!("invalid account type"),
-    }
-}
-
-fn ident_string(f: &syn::Field) -> String {
-    let path = match &f.ty {
-        syn::Type::Path(ty_path) => ty_path.path.clone(),
-        _ => panic!("invalid account syntax"),
-    };
-    // TODO: allow segmented paths.
-    assert!(path.segments.len() == 1);
-    let segments = &path.segments[0];
-    segments.ident.to_string()
-}
-
-fn parse_program_state(path: &syn::Path) -> ProgramStateTy {
-    let account_ident = parse_account(&path);
-    ProgramStateTy { account_ident }
-}
-
-fn parse_cpi_state(path: &syn::Path) -> CpiStateTy {
-    let account_ident = parse_account(&path);
-    CpiStateTy { account_ident }
-}
-
-fn parse_cpi_account(path: &syn::Path) -> CpiAccountTy {
-    let account_ident = parse_account(path);
-    CpiAccountTy { account_ident }
-}
-
-fn parse_program_account(path: &syn::Path) -> ProgramAccountTy {
-    let account_ident = parse_account(path);
-    ProgramAccountTy { account_ident }
-}
-
-fn parse_program_account_zero_copy(path: &syn::Path) -> LoaderTy {
-    let account_ident = parse_account(path);
-    LoaderTy { account_ident }
-}
-
-fn parse_account(path: &syn::Path) -> syn::Ident {
-    let segments = &path.segments[0];
-    match &segments.arguments {
-        syn::PathArguments::AngleBracketed(args) => {
-            // Expected: <'info, MyType>.
-            assert!(args.args.len() == 2);
-            match &args.args[1] {
-                syn::GenericArgument::Type(syn::Type::Path(ty_path)) => {
-                    // TODO: allow segmented paths.
-                    assert!(ty_path.path.segments.len() == 1);
-                    let path_segment = &ty_path.path.segments[0];
-                    path_segment.ident.clone()
-                }
-                _ => panic!("Invalid ProgramAccount"),
-            }
-        }
-        _ => panic!("Invalid ProgramAccount"),
-    }
-}
-
-fn parse_sysvar(path: &syn::Path) -> SysvarTy {
-    let segments = &path.segments[0];
-    let account_ident = match &segments.arguments {
-        syn::PathArguments::AngleBracketed(args) => {
-            // Expected: <'info, MyType>.
-            assert!(args.args.len() == 2);
-            match &args.args[1] {
-                syn::GenericArgument::Type(syn::Type::Path(ty_path)) => {
-                    // TODO: allow segmented paths.
-                    assert!(ty_path.path.segments.len() == 1);
-                    let path_segment = &ty_path.path.segments[0];
-                    path_segment.ident.clone()
-                }
-                _ => panic!("Invalid Sysvar"),
-            }
-        }
-        _ => panic!("Invalid Sysvar"),
-    };
-    match account_ident.to_string().as_str() {
-        "Clock" => SysvarTy::Clock,
-        "Rent" => SysvarTy::Rent,
-        "EpochSchedule" => SysvarTy::EpochSchedule,
-        "Fees" => SysvarTy::Fees,
-        "RecentBlockhashes" => SysvarTy::RecentBlockhashes,
-        "SlotHashes" => SysvarTy::SlotHashes,
-        "SlotHistory" => SysvarTy::SlotHistory,
-        "StakeHistory" => SysvarTy::StakeHistory,
-        "Instructions" => SysvarTy::Instructions,
-        "Rewards" => SysvarTy::Rewards,
-        _ => panic!("Invalid Sysvar"),
-    }
-}
-
-fn parse_constraints(
-    anchor: &syn::Attribute,
-) -> (
-    Vec<Constraint>,
-    bool,
-    bool,
-    bool,
-    Option<syn::Ident>,
-    Option<proc_macro2::TokenStream>,
-    Vec<syn::Ident>,
-) {
-    let mut tts = anchor.tokens.clone().into_iter();
-    let g_stream = match tts.next().expect("Must have a token group") {
-        proc_macro2::TokenTree::Group(g) => g.stream(),
-        _ => panic!("Invalid syntax"),
-    };
-
-    let mut is_init = false;
-    let mut is_mut = false;
-    let mut is_signer = false;
-    let mut constraints = vec![];
-    let mut is_rent_exempt = None;
-    let mut payer = None;
-    let mut space = None;
-    let mut is_associated = false;
-    let mut associated_seeds = Vec::new();
-
-    let mut inner_tts = g_stream.into_iter();
-    while let Some(token) = inner_tts.next() {
-        match token {
-            proc_macro2::TokenTree::Ident(ident) => match ident.to_string().as_str() {
-                "init" => {
-                    is_init = true;
-                    is_mut = true;
-                    // If it's not specified, all program owned accounts default
-                    // to being rent exempt.
-                    if is_rent_exempt.is_none() {
-                        is_rent_exempt = Some(true);
-                    }
-                }
-                "mut" => {
-                    is_mut = true;
-                }
-                "signer" => {
-                    is_signer = true;
-                    constraints.push(Constraint::Signer(ConstraintSigner {}));
-                }
-                "seeds" => {
-                    match inner_tts.next().unwrap() {
-                        proc_macro2::TokenTree::Punct(punct) => {
-                            assert!(punct.as_char() == '=');
-                            punct
-                        }
-                        _ => panic!("invalid syntax"),
-                    };
-                    let seeds = match inner_tts.next().unwrap() {
-                        proc_macro2::TokenTree::Group(g) => g,
-                        _ => panic!("invalid syntax"),
-                    };
-                    constraints.push(Constraint::Seeds(ConstraintSeeds { seeds }))
-                }
-                "belongs_to" | "has_one" => {
-                    match inner_tts.next().unwrap() {
-                        proc_macro2::TokenTree::Punct(punct) => {
-                            assert!(punct.as_char() == '=');
-                            punct
-                        }
-                        _ => panic!("invalid syntax"),
-                    };
-                    let join_target = match inner_tts.next().unwrap() {
-                        proc_macro2::TokenTree::Ident(ident) => ident,
-                        _ => panic!("invalid syntax"),
-                    };
-                    constraints.push(Constraint::BelongsTo(ConstraintBelongsTo { join_target }))
-                }
-                "owner" => {
-                    match inner_tts.next().unwrap() {
-                        proc_macro2::TokenTree::Punct(punct) => {
-                            assert!(punct.as_char() == '=');
-                            punct
-                        }
-                        _ => panic!("invalid syntax"),
-                    };
-                    let owner_target = match inner_tts.next().unwrap() {
-                        proc_macro2::TokenTree::Ident(ident) => ident,
-                        _ => panic!("invalid syntax"),
-                    };
-                    constraints.push(Constraint::Owner(ConstraintOwner { owner_target }));
-                }
-                "rent_exempt" => {
-                    match inner_tts.next() {
-                        None => is_rent_exempt = Some(true),
-                        Some(tkn) => {
-                            match tkn {
-                                proc_macro2::TokenTree::Punct(punct) => {
-                                    assert!(punct.as_char() == '=');
-                                    punct
-                                }
-                                _ => panic!("invalid syntax"),
-                            };
-                            let should_skip = match inner_tts.next().unwrap() {
-                                proc_macro2::TokenTree::Ident(ident) => ident,
-                                _ => panic!("invalid syntax"),
-                            };
-                            match should_skip.to_string().as_str() {
-                                "skip" => {
-                                    is_rent_exempt = Some(false);
-                                },
-                                _ => panic!("invalid syntax: omit the rent_exempt attribute to enforce rent exemption"),
-                            };
-                        }
-                    };
-                }
-                "executable" => {
-                    constraints.push(Constraint::Executable(ConstraintExecutable {}));
-                }
-                "state" => {
-                    match inner_tts.next().unwrap() {
-                        proc_macro2::TokenTree::Punct(punct) => {
-                            assert!(punct.as_char() == '=');
-                            punct
-                        }
-                        _ => panic!("invalid syntax"),
-                    };
-                    let program_target = match inner_tts.next().unwrap() {
-                        proc_macro2::TokenTree::Ident(ident) => ident,
-                        _ => panic!("invalid syntax"),
-                    };
-                    constraints.push(Constraint::State(ConstraintState { program_target }));
-                }
-                "associated" => {
-                    is_associated = true;
-                    is_mut = true;
-                    match inner_tts.next().unwrap() {
-                        proc_macro2::TokenTree::Punct(punct) => {
-                            assert!(punct.as_char() == '=');
-                            punct
-                        }
-                        _ => panic!("invalid syntax"),
-                    };
-                    let associated_target = match inner_tts.next().unwrap() {
-                        proc_macro2::TokenTree::Ident(ident) => ident,
-                        _ => panic!("invalid syntax"),
-                    };
-                    constraints.push(Constraint::Associated(ConstraintAssociated {
-                        associated_target,
-                        is_init,
-                    }));
-                }
-                "with" => {
-                    match inner_tts.next().unwrap() {
-                        proc_macro2::TokenTree::Punct(punct) => {
-                            assert!(punct.as_char() == '=');
-                            punct
-                        }
-                        _ => panic!("invalid syntax"),
-                    };
-                    associated_seeds.push(match inner_tts.next().unwrap() {
-                        proc_macro2::TokenTree::Ident(ident) => ident,
-                        _ => panic!("invalid syntax"),
-                    });
-                }
-                "payer" => {
-                    match inner_tts.next().unwrap() {
-                        proc_macro2::TokenTree::Punct(punct) => {
-                            assert!(punct.as_char() == '=');
-                            punct
-                        }
-                        _ => panic!("invalid syntax"),
-                    };
-                    let _payer = match inner_tts.next().unwrap() {
-                        proc_macro2::TokenTree::Ident(ident) => ident,
-                        _ => panic!("invalid syntax"),
-                    };
-                    payer = Some(_payer);
-                }
-                "space" => {
-                    match inner_tts.next().unwrap() {
-                        proc_macro2::TokenTree::Punct(punct) => {
-                            assert!(punct.as_char() == '=');
-                            punct
-                        }
-                        _ => panic!("invalid syntax"),
-                    };
-                    match inner_tts.next().unwrap() {
-                        proc_macro2::TokenTree::Literal(literal) => {
-                            let tokens: proc_macro2::TokenStream =
-                                literal.to_string().replace("\"", "").parse().unwrap();
-                            space = Some(tokens);
-                        }
-                        _ => panic!("invalid space"),
-                    }
-                }
-                _ => {
-                    panic!("invalid syntax");
-                }
-            },
-            proc_macro2::TokenTree::Punct(punct) => {
-                if punct.as_char() != ',' {
-                    panic!("invalid syntax");
-                }
-            }
-            proc_macro2::TokenTree::Literal(literal) => {
-                let tokens: proc_macro2::TokenStream =
-                    literal.to_string().replace("\"", "").parse().unwrap();
-                constraints.push(Constraint::Literal(ConstraintLiteral { tokens }));
-            }
-            _ => {
-                panic!("invalid syntax");
-            }
-        }
-    }
-
-    // If init, then tag the associated constraint as being part of init.
-    if is_init {
-        for c in &mut constraints {
-            if let Constraint::Associated(ConstraintAssociated { is_init, .. }) = c {
-                *is_init = true;
-            }
-        }
-    }
-
-    // If `associated` is given, remove `init` since it's redundant.
-    if is_associated {
-        is_init = false;
-    }
-
-    if let Some(is_re) = is_rent_exempt {
-        match is_re {
-            false => constraints.push(Constraint::RentExempt(ConstraintRentExempt::Skip)),
-            true => constraints.push(Constraint::RentExempt(ConstraintRentExempt::Enforce)),
-        }
-    }
-
-    (
-        constraints,
-        is_mut,
-        is_signer,
-        is_init,
-        payer,
-        space,
-        associated_seeds,
-    )
-}

+ 385 - 0
lang/syn/src/parser/accounts/constraints.rs

@@ -0,0 +1,385 @@
+use crate::{
+    ConstraintAssociated, ConstraintAssociatedGroup, ConstraintAssociatedPayer,
+    ConstraintAssociatedSpace, ConstraintAssociatedWith, ConstraintBelongsTo, ConstraintExecutable,
+    ConstraintGroup, ConstraintInit, ConstraintLiteral, ConstraintMut, ConstraintOwner,
+    ConstraintRaw, ConstraintRentExempt, ConstraintSeeds, ConstraintSigner, ConstraintState,
+    ConstraintToken, Context,
+};
+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::{bracketed, Expr, Ident, LitStr, Token};
+
+pub fn parse(f: &syn::Field) -> ParseResult<ConstraintGroup> {
+    let mut constraints = ConstraintGroupBuilder::default();
+    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()
+}
+
+pub fn is_account(attr: &&syn::Attribute) -> bool {
+    attr.path
+        .get_ident()
+        .map_or(false, |ident| ident == "account")
+}
+
+// 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);
+    if is_lit {
+        let lit: LitStr = stream.parse()?;
+        let c = ConstraintToken::Literal(Context::new(lit.span(), ConstraintLiteral { lit }));
+        return Ok(c);
+    }
+
+    let ident = stream.call(Ident::parse_any)?;
+    let kw = ident.to_string();
+
+    let c = match kw.as_str() {
+        "init" => ConstraintToken::Init(Context::new(ident.span(), ConstraintInit {})),
+        "mut" => ConstraintToken::Mut(Context::new(ident.span(), ConstraintMut {})),
+        "signer" => ConstraintToken::Signer(Context::new(ident.span(), ConstraintSigner {})),
+        "executable" => {
+            ConstraintToken::Executable(Context::new(ident.span(), ConstraintExecutable {}))
+        }
+        _ => {
+            stream.parse::<Token![=]>()?;
+            let span = ident.span().join(stream.span()).unwrap_or(ident.span());
+            match kw.as_str() {
+                "belongs_to" | "has_one" => ConstraintToken::BelongsTo(Context::new(
+                    span,
+                    ConstraintBelongsTo {
+                        join_target: stream.parse()?,
+                    },
+                )),
+                "owner" => ConstraintToken::Owner(Context::new(
+                    span,
+                    ConstraintOwner {
+                        owner_target: stream.parse()?,
+                    },
+                )),
+                "rent_exempt" => ConstraintToken::RentExempt(Context::new(
+                    span,
+                    match stream.parse::<Ident>()?.to_string().as_str() {
+                        "skip" => ConstraintRentExempt::Skip,
+                        "enforce" => ConstraintRentExempt::Enforce,
+                        _ => {
+                            return Err(ParseError::new(
+                                span,
+                                "rent_exempt must be either skip or enforce",
+                            ))
+                        }
+                    },
+                )),
+                "state" => ConstraintToken::State(Context::new(
+                    span,
+                    ConstraintState {
+                        program_target: stream.parse()?,
+                    },
+                )),
+                "associated" => ConstraintToken::Associated(Context::new(
+                    span,
+                    ConstraintAssociated {
+                        target: stream.parse()?,
+                    },
+                )),
+                "payer" => ConstraintToken::AssociatedPayer(Context::new(
+                    span,
+                    ConstraintAssociatedPayer {
+                        target: stream.parse()?,
+                    },
+                )),
+                "with" => ConstraintToken::AssociatedWith(Context::new(
+                    span,
+                    ConstraintAssociatedWith {
+                        target: stream.parse()?,
+                    },
+                )),
+                "space" => ConstraintToken::AssociatedSpace(Context::new(
+                    span,
+                    ConstraintAssociatedSpace {
+                        space: stream.parse()?,
+                    },
+                )),
+                "seeds" => {
+                    let seeds;
+                    let bracket = bracketed!(seeds in stream);
+                    ConstraintToken::Seeds(Context::new(
+                        span.join(bracket.span).unwrap_or(span),
+                        ConstraintSeeds {
+                            seeds: seeds.parse_terminated(Expr::parse)?,
+                        },
+                    ))
+                }
+                "constraint" => ConstraintToken::Raw(Context::new(
+                    span,
+                    ConstraintRaw {
+                        raw: stream.parse()?,
+                    },
+                )),
+                _ => Err(ParseError::new(ident.span(), "Invalid attribute"))?,
+            }
+        }
+    };
+
+    Ok(c)
+}
+
+#[derive(Default)]
+pub struct ConstraintGroupBuilder {
+    pub init: Option<Context<ConstraintInit>>,
+    pub mutable: Option<Context<ConstraintMut>>,
+    pub signer: Option<Context<ConstraintSigner>>,
+    pub belongs_to: Vec<Context<ConstraintBelongsTo>>,
+    pub literal: Vec<Context<ConstraintLiteral>>,
+    pub raw: Vec<Context<ConstraintRaw>>,
+    pub owner: Option<Context<ConstraintOwner>>,
+    pub rent_exempt: Option<Context<ConstraintRentExempt>>,
+    pub seeds: Option<Context<ConstraintSeeds>>,
+    pub executable: Option<Context<ConstraintExecutable>>,
+    pub state: Option<Context<ConstraintState>>,
+    pub associated: Option<Context<ConstraintAssociated>>,
+    pub associated_payer: Option<Context<ConstraintAssociatedPayer>>,
+    pub associated_space: Option<Context<ConstraintAssociatedSpace>>,
+    pub associated_with: Vec<Context<ConstraintAssociatedWith>>,
+}
+
+impl ConstraintGroupBuilder {
+    pub fn build(mut self) -> ParseResult<ConstraintGroup> {
+        // Init implies mutable and rent exempt.
+        if let Some(i) = &self.init {
+            match self.mutable {
+                Some(m) => {
+                    return Err(ParseError::new(
+                        m.span(),
+                        "mut cannot be provided with init",
+                    ))
+                }
+                None => self
+                    .mutable
+                    .replace(Context::new(i.span(), ConstraintMut {})),
+            };
+            if self.rent_exempt.is_none() {
+                self.rent_exempt
+                    .replace(Context::new(i.span(), ConstraintRentExempt::Enforce));
+            }
+        }
+
+        let ConstraintGroupBuilder {
+            init,
+            mutable,
+            signer,
+            belongs_to,
+            literal,
+            raw,
+            owner,
+            rent_exempt,
+            seeds,
+            executable,
+            state,
+            associated,
+            associated_payer,
+            associated_space,
+            associated_with,
+        } = self;
+
+        // Converts Option<Context<T>> -> Option<T>.
+        macro_rules! into_inner {
+            ($opt:ident) => {
+                $opt.map(|c| c.into_inner())
+            };
+        }
+        // Converts Vec<Context<T>> - Vec<T>.
+        macro_rules! into_inner_vec {
+            ($opt:ident) => {
+                $opt.into_iter().map(|c| c.into_inner()).collect()
+            };
+        }
+
+        let is_init = init.is_some();
+        Ok(ConstraintGroup {
+            init: into_inner!(init),
+            mutable: into_inner!(mutable),
+            signer: into_inner!(signer),
+            belongs_to: into_inner_vec!(belongs_to),
+            literal: into_inner_vec!(literal),
+            raw: into_inner_vec!(raw),
+            owner: into_inner!(owner),
+            rent_exempt: into_inner!(rent_exempt),
+            seeds: into_inner!(seeds),
+            executable: into_inner!(executable),
+            state: into_inner!(state),
+            associated: associated.map(|associated| ConstraintAssociatedGroup {
+                is_init,
+                associated_target: associated.target.clone(),
+                associated_seeds: associated_with.iter().map(|s| s.target.clone()).collect(),
+                payer: associated_payer.map(|p| p.target.clone()),
+                space: associated_space.map(|s| s.space.clone()),
+            }),
+        })
+    }
+
+    pub fn add(&mut self, c: ConstraintToken) -> ParseResult<()> {
+        match c {
+            ConstraintToken::Init(c) => self.add_init(c),
+            ConstraintToken::Mut(c) => self.add_mut(c),
+            ConstraintToken::Signer(c) => self.add_signer(c),
+            ConstraintToken::BelongsTo(c) => self.add_belongs_to(c),
+            ConstraintToken::Literal(c) => self.add_literal(c),
+            ConstraintToken::Raw(c) => self.add_raw(c),
+            ConstraintToken::Owner(c) => self.add_owner(c),
+            ConstraintToken::RentExempt(c) => self.add_rent_exempt(c),
+            ConstraintToken::Seeds(c) => self.add_seeds(c),
+            ConstraintToken::Executable(c) => self.add_executable(c),
+            ConstraintToken::State(c) => self.add_state(c),
+            ConstraintToken::Associated(c) => self.add_associated(c),
+            ConstraintToken::AssociatedPayer(c) => self.add_associated_payer(c),
+            ConstraintToken::AssociatedSpace(c) => self.add_associated_space(c),
+            ConstraintToken::AssociatedWith(c) => self.add_associated_with(c),
+            ConstraintToken::AssociatedGroup(_) => panic!("Invariant violation"),
+        }
+    }
+
+    fn add_init(&mut self, c: Context<ConstraintInit>) -> ParseResult<()> {
+        if self.init.is_some() {
+            return Err(ParseError::new(c.span(), "init already provided"));
+        }
+        self.init.replace(c);
+        Ok(())
+    }
+
+    fn add_mut(&mut self, c: Context<ConstraintMut>) -> ParseResult<()> {
+        if self.mutable.is_some() {
+            return Err(ParseError::new(c.span(), "mut already provided"));
+        }
+        self.mutable.replace(c);
+        Ok(())
+    }
+
+    fn add_signer(&mut self, c: Context<ConstraintSigner>) -> ParseResult<()> {
+        if self.signer.is_some() {
+            return Err(ParseError::new(c.span(), "signer already provided"));
+        }
+        self.signer.replace(c);
+        Ok(())
+    }
+
+    fn add_belongs_to(&mut self, c: Context<ConstraintBelongsTo>) -> ParseResult<()> {
+        if self
+            .belongs_to
+            .iter()
+            .filter(|item| item.join_target == c.join_target)
+            .count()
+            > 0
+        {
+            return Err(ParseError::new(
+                c.span(),
+                "belongs_to target already provided",
+            ));
+        }
+        self.belongs_to.push(c);
+        Ok(())
+    }
+
+    fn add_literal(&mut self, c: Context<ConstraintLiteral>) -> ParseResult<()> {
+        self.literal.push(c);
+        Ok(())
+    }
+
+    fn add_raw(&mut self, c: Context<ConstraintRaw>) -> ParseResult<()> {
+        self.raw.push(c);
+        Ok(())
+    }
+
+    fn add_owner(&mut self, c: Context<ConstraintOwner>) -> ParseResult<()> {
+        if self.owner.is_some() {
+            return Err(ParseError::new(c.span(), "owner already provided"));
+        }
+        self.owner.replace(c);
+        Ok(())
+    }
+
+    fn add_rent_exempt(&mut self, c: Context<ConstraintRentExempt>) -> ParseResult<()> {
+        if self.rent_exempt.is_some() {
+            return Err(ParseError::new(c.span(), "rent already provided"));
+        }
+        self.rent_exempt.replace(c);
+        Ok(())
+    }
+
+    fn add_seeds(&mut self, c: Context<ConstraintSeeds>) -> ParseResult<()> {
+        if self.seeds.is_some() {
+            return Err(ParseError::new(c.span(), "seeds already provided"));
+        }
+        self.seeds.replace(c);
+        Ok(())
+    }
+
+    fn add_executable(&mut self, c: Context<ConstraintExecutable>) -> ParseResult<()> {
+        if self.executable.is_some() {
+            return Err(ParseError::new(c.span(), "executable already provided"));
+        }
+        self.executable.replace(c);
+        Ok(())
+    }
+
+    fn add_state(&mut self, c: Context<ConstraintState>) -> ParseResult<()> {
+        if self.state.is_some() {
+            return Err(ParseError::new(c.span(), "state already provided"));
+        }
+        self.state.replace(c);
+        Ok(())
+    }
+
+    fn add_associated(&mut self, c: Context<ConstraintAssociated>) -> ParseResult<()> {
+        if self.associated.is_some() {
+            return Err(ParseError::new(c.span(), "associated already provided"));
+        }
+        self.associated.replace(c);
+        Ok(())
+    }
+
+    fn add_associated_payer(&mut self, c: Context<ConstraintAssociatedPayer>) -> ParseResult<()> {
+        if self.associated.is_none() {
+            return Err(ParseError::new(
+                c.span(),
+                "associated must be provided before payer",
+            ));
+        }
+        if self.associated_payer.is_some() {
+            return Err(ParseError::new(c.span(), "payer already provided"));
+        }
+        self.associated_payer.replace(c);
+        Ok(())
+    }
+
+    fn add_associated_space(&mut self, c: Context<ConstraintAssociatedSpace>) -> ParseResult<()> {
+        if self.associated.is_none() {
+            return Err(ParseError::new(
+                c.span(),
+                "associated must be provided before space",
+            ));
+        }
+        if self.associated_space.is_some() {
+            return Err(ParseError::new(c.span(), "space already provided"));
+        }
+        self.associated_space.replace(c);
+        Ok(())
+    }
+
+    fn add_associated_with(&mut self, c: Context<ConstraintAssociatedWith>) -> ParseResult<()> {
+        if self.associated.is_none() {
+            return Err(ParseError::new(
+                c.span(),
+                "associated must be provided before with",
+            ));
+        }
+        self.associated_with.push(c);
+        Ok(())
+    }
+}

+ 218 - 0
lang/syn/src/parser/accounts/mod.rs

@@ -0,0 +1,218 @@
+use crate::{
+    AccountField, AccountsStruct, CompositeField, CpiAccountTy, CpiStateTy, Field, LoaderTy,
+    ProgramAccountTy, ProgramStateTy, SysvarTy, Ty,
+};
+use syn::parse::{Error as ParseError, Result as ParseResult};
+use syn::spanned::Spanned;
+
+pub mod constraints;
+
+pub fn parse(strct: &syn::ItemStruct) -> ParseResult<AccountsStruct> {
+    let fields = match &strct.fields {
+        syn::Fields::Named(fields) => fields
+            .named
+            .iter()
+            .map(parse_account_field)
+            .collect::<ParseResult<Vec<AccountField>>>()?,
+        _ => {
+            return Err(ParseError::new_spanned(
+                &strct.fields,
+                "fields must be named",
+            ))
+        }
+    };
+    Ok(AccountsStruct::new(strct.clone(), fields))
+}
+
+pub fn parse_account_field(f: &syn::Field) -> ParseResult<AccountField> {
+    let constraints = constraints::parse(f)?;
+
+    let ident = f.ident.clone().unwrap();
+    let account_field = match is_field_primitive(f)? {
+        true => {
+            let ty = parse_ty(f)?;
+            AccountField::Field(Field {
+                ident,
+                ty,
+                constraints,
+            })
+        }
+        false => AccountField::CompositeField(CompositeField {
+            ident,
+            constraints,
+            symbol: ident_string(f)?,
+            raw_field: f.clone(),
+        }),
+    };
+    Ok(account_field)
+}
+
+fn is_field_primitive(f: &syn::Field) -> ParseResult<bool> {
+    let r = match ident_string(f)?.as_str() {
+        "ProgramState" | "ProgramAccount" | "CpiAccount" | "Sysvar" | "AccountInfo"
+        | "CpiState" | "Loader" => true,
+        _ => false,
+    };
+    Ok(r)
+}
+
+fn parse_ty(f: &syn::Field) -> ParseResult<Ty> {
+    let path = match &f.ty {
+        syn::Type::Path(ty_path) => ty_path.path.clone(),
+        _ => return Err(ParseError::new(f.ty.span(), "invalid account type given")),
+    };
+    let ty = match ident_string(f)?.as_str() {
+        "ProgramState" => Ty::ProgramState(parse_program_state(&path)?),
+        "CpiState" => Ty::CpiState(parse_cpi_state(&path)?),
+        "ProgramAccount" => Ty::ProgramAccount(parse_program_account(&path)?),
+        "CpiAccount" => Ty::CpiAccount(parse_cpi_account(&path)?),
+        "Sysvar" => Ty::Sysvar(parse_sysvar(&path)?),
+        "AccountInfo" => Ty::AccountInfo,
+        "Loader" => Ty::Loader(parse_program_account_zero_copy(&path)?),
+        _ => return Err(ParseError::new(f.ty.span(), "invalid account type given")),
+    };
+
+    Ok(ty)
+}
+
+fn ident_string(f: &syn::Field) -> ParseResult<String> {
+    let path = match &f.ty {
+        syn::Type::Path(ty_path) => ty_path.path.clone(),
+        _ => return Err(ParseError::new(f.ty.span(), "invalid type")),
+    };
+    // TODO: allow segmented paths.
+    if path.segments.len() != 1 {
+        return Err(ParseError::new(
+            f.ty.span(),
+            "segmented paths are not currently allowed",
+        ));
+    }
+
+    let segments = &path.segments[0];
+    Ok(segments.ident.to_string())
+}
+
+fn parse_program_state(path: &syn::Path) -> ParseResult<ProgramStateTy> {
+    let account_ident = parse_account(&path)?;
+    Ok(ProgramStateTy { account_ident })
+}
+
+fn parse_cpi_state(path: &syn::Path) -> ParseResult<CpiStateTy> {
+    let account_ident = parse_account(&path)?;
+    Ok(CpiStateTy { account_ident })
+}
+
+fn parse_cpi_account(path: &syn::Path) -> ParseResult<CpiAccountTy> {
+    let account_ident = parse_account(path)?;
+    Ok(CpiAccountTy { account_ident })
+}
+
+fn parse_program_account(path: &syn::Path) -> ParseResult<ProgramAccountTy> {
+    let account_ident = parse_account(path)?;
+    Ok(ProgramAccountTy { account_ident })
+}
+
+fn parse_program_account_zero_copy(path: &syn::Path) -> ParseResult<LoaderTy> {
+    let account_ident = parse_account(path)?;
+    Ok(LoaderTy { account_ident })
+}
+
+fn parse_account(path: &syn::Path) -> ParseResult<syn::Ident> {
+    let segments = &path.segments[0];
+    match &segments.arguments {
+        syn::PathArguments::AngleBracketed(args) => {
+            // Expected: <'info, MyType>.
+            if args.args.len() != 2 {
+                return Err(ParseError::new(
+                    args.args.span(),
+                    "bracket arguments must be the lifetime and type",
+                ));
+            }
+            match &args.args[1] {
+                syn::GenericArgument::Type(syn::Type::Path(ty_path)) => {
+                    // TODO: allow segmented paths.
+                    if ty_path.path.segments.len() != 1 {
+                        return Err(ParseError::new(
+                            ty_path.path.span(),
+                            "segmented paths are not currently allowed",
+                        ));
+                    }
+
+                    let path_segment = &ty_path.path.segments[0];
+                    Ok(path_segment.ident.clone())
+                }
+                _ => {
+                    return Err(ParseError::new(
+                        args.args[1].span(),
+                        "first bracket argument must be a lifetime",
+                    ))
+                }
+            }
+        }
+        _ => {
+            return Err(ParseError::new(
+                segments.arguments.span(),
+                "expected angle brackets with a lifetime and type",
+            ))
+        }
+    }
+}
+
+fn parse_sysvar(path: &syn::Path) -> ParseResult<SysvarTy> {
+    let segments = &path.segments[0];
+    let account_ident = match &segments.arguments {
+        syn::PathArguments::AngleBracketed(args) => {
+            // Expected: <'info, MyType>.
+            if args.args.len() != 2 {
+                return Err(ParseError::new(
+                    args.args.span(),
+                    "bracket arguments must be the lifetime and type",
+                ));
+            }
+            match &args.args[1] {
+                syn::GenericArgument::Type(syn::Type::Path(ty_path)) => {
+                    // TODO: allow segmented paths.
+                    if ty_path.path.segments.len() != 1 {
+                        return Err(ParseError::new(
+                            ty_path.path.span(),
+                            "segmented paths are not currently allowed",
+                        ));
+                    }
+                    let path_segment = &ty_path.path.segments[0];
+                    path_segment.ident.clone()
+                }
+                _ => {
+                    return Err(ParseError::new(
+                        args.args[1].span(),
+                        "first bracket argument must be a lifetime",
+                    ))
+                }
+            }
+        }
+        _ => {
+            return Err(ParseError::new(
+                segments.arguments.span(),
+                "expected angle brackets with a lifetime and type",
+            ))
+        }
+    };
+    let ty = match account_ident.to_string().as_str() {
+        "Clock" => SysvarTy::Clock,
+        "Rent" => SysvarTy::Rent,
+        "EpochSchedule" => SysvarTy::EpochSchedule,
+        "Fees" => SysvarTy::Fees,
+        "RecentBlockhashes" => SysvarTy::RecentBlockhashes,
+        "SlotHashes" => SysvarTy::SlotHashes,
+        "SlotHistory" => SysvarTy::SlotHistory,
+        "StakeHistory" => SysvarTy::StakeHistory,
+        "Instructions" => SysvarTy::Instructions,
+        "Rewards" => SysvarTy::Rewards,
+        _ => {
+            return Err(ParseError::new(
+                account_ident.span(),
+                "invalid sysvar provided",
+            ))
+        }
+    };
+    Ok(ty)
+}

+ 0 - 2
lang/syn/src/parser/mod.rs

@@ -1,7 +1,5 @@
 pub mod accounts;
 pub mod accounts;
 pub mod error;
 pub mod error;
-#[cfg(feature = "idl")]
-pub mod file;
 pub mod program;
 pub mod program;
 
 
 pub fn tts_to_string<T: quote::ToTokens>(item: T) -> String {
 pub fn tts_to_string<T: quote::ToTokens>(item: T) -> String {

+ 0 - 304
lang/syn/src/parser/program.rs

@@ -1,304 +0,0 @@
-use crate::parser;
-use crate::{Ix, IxArg, Program, State, StateInterface, StateIx};
-
-// Name of the attribute denoting a state struct.
-const STATE_STRUCT_ATTRIBUTE: &str = "state";
-
-// Reserved keyword for the constructor method.
-const CTOR_METHOD_NAME: &str = "new";
-
-pub fn parse(program_mod: syn::ItemMod) -> Program {
-    let mod_ident = &program_mod.ident;
-    let mod_content = &program_mod.content.as_ref().unwrap().1;
-
-    // Parse program state.
-    let state: Option<State> = {
-        // Parse `struct` marked with the `#[state]` attribute.
-        let strct: Option<(&syn::ItemStruct, bool)> = mod_content
-            .iter()
-            .filter_map(|item| match item {
-                syn::Item::Struct(item_strct) => {
-                    let attrs = &item_strct.attrs;
-                    if attrs.is_empty() {
-                        return None;
-                    }
-                    let attr_label = attrs[0].path.get_ident().map(|i| i.to_string());
-                    if attr_label != Some(STATE_STRUCT_ATTRIBUTE.to_string()) {
-                        return None;
-                    }
-                    let is_zero_copy = parser::tts_to_string(&attrs[0].tokens) == "(zero_copy)";
-                    Some((item_strct, is_zero_copy))
-                }
-                _ => None,
-            })
-            .next();
-        // Parse `impl` block for the state struct.
-        let impl_block: Option<syn::ItemImpl> = match strct {
-            None => None,
-            Some((strct, _)) => mod_content
-                .iter()
-                .filter_map(|item| match item {
-                    syn::Item::Impl(item_impl) => {
-                        let impl_ty_str = parser::tts_to_string(&item_impl.self_ty);
-                        let strct_name = strct.ident.to_string();
-                        if item_impl.trait_.is_some() {
-                            return None;
-                        }
-                        if strct_name != impl_ty_str {
-                            return None;
-                        }
-                        Some(item_impl.clone())
-                    }
-                    _ => None,
-                })
-                .next(),
-        };
-        // Parse ctor and the generic type in `Context<MY-TYPE>`.
-        let ctor_and_anchor = match &impl_block {
-            None => None,
-            Some(impl_block) => {
-                impl_block
-                    .items
-                    .iter()
-                    .filter_map(|item: &syn::ImplItem| match item {
-                        syn::ImplItem::Method(m) => {
-                            if m.sig.ident == CTOR_METHOD_NAME {
-                                let (_, is_zero_copy) = strct.as_ref().unwrap();
-                                let ctx_arg = {
-                                    if *is_zero_copy {
-                                        // Second param is context.
-                                        let mut iter = m.sig.inputs.iter();
-                                        iter.next().expect("First param must be receiver");
-                                        iter.next().expect("Second param must be Context")
-                                    } else {
-                                        // First param is ctx.
-                                        m.sig.inputs.first().unwrap()
-                                    }
-                                };
-                                match ctx_arg {
-                                    syn::FnArg::Receiver(_) => panic!("invalid ctor syntax"),
-                                    syn::FnArg::Typed(arg) => {
-                                        Some((m.clone(), extract_ident(&arg).clone()))
-                                    }
-                                }
-                            } else {
-                                None
-                            }
-                        }
-                        _ => None,
-                    })
-                    .next()
-            }
-        };
-        // Parse all methods in the above `impl` block.
-        let methods: Option<Vec<StateIx>> = impl_block.as_ref().map(|impl_block| {
-            impl_block
-                .items
-                .iter()
-                .filter(|item| match item {
-                    syn::ImplItem::Method(m) => m.sig.ident.to_string() != CTOR_METHOD_NAME,
-                    _ => false,
-                })
-                .filter_map(|item: &syn::ImplItem| match item {
-                    syn::ImplItem::Method(m) => match m.sig.inputs.first() {
-                        None => None,
-                        Some(arg) => match arg {
-                            syn::FnArg::Typed(_) => None,
-                            syn::FnArg::Receiver(_) => {
-                                let mut args = m
-                                    .sig
-                                    .inputs
-                                    .iter()
-                                    .filter_map(|arg| match arg {
-                                        syn::FnArg::Receiver(_) => None,
-                                        syn::FnArg::Typed(arg) => Some(arg),
-                                    })
-                                    .map(|raw_arg| {
-                                        let ident = match &*raw_arg.pat {
-                                            syn::Pat::Ident(ident) => &ident.ident,
-                                            _ => panic!("invalid syntax"),
-                                        };
-                                        IxArg {
-                                            name: ident.clone(),
-                                            raw_arg: raw_arg.clone(),
-                                        }
-                                    })
-                                    .collect::<Vec<IxArg>>();
-                                // Remove the Anchor accounts argument
-                                let anchor = args.remove(0);
-                                let anchor_ident = extract_ident(&anchor.raw_arg).clone();
-
-                                Some(StateIx {
-                                    raw_method: m.clone(),
-                                    ident: m.sig.ident.clone(),
-                                    args,
-                                    anchor_ident,
-                                    has_receiver: true,
-                                })
-                            }
-                        },
-                    },
-                    _ => None,
-                })
-                .collect()
-        });
-        // Parse all trait implementations for the above `#[state]` struct.
-        let trait_impls: Option<Vec<StateInterface>> = strct.map(|_strct| {
-            mod_content
-                .iter()
-                .filter_map(|item| match item {
-                    syn::Item::Impl(item_impl) => {
-                        let trait_name = match &item_impl.trait_ {
-                            None => return None,
-                            Some((_, path, _)) => path
-                                .segments
-                                .iter()
-                                .next()
-                                .expect("Must have one segment in a path")
-                                .ident
-                                .clone()
-                                .to_string(),
-                        };
-                        let methods = item_impl
-                            .items
-                            .iter()
-                            .filter_map(|item: &syn::ImplItem| match item {
-                                syn::ImplItem::Method(m) => match m.sig.inputs.first() {
-                                    None => None,
-                                    Some(_arg) => {
-                                        let mut has_receiver = false;
-                                        let mut args = m
-                                            .sig
-                                            .inputs
-                                            .iter()
-                                            .filter_map(|arg| match arg {
-                                                syn::FnArg::Receiver(_) => {
-                                                    has_receiver = true;
-                                                    None
-                                                }
-                                                syn::FnArg::Typed(arg) => Some(arg),
-                                            })
-                                            .map(|raw_arg| {
-                                                let ident = match &*raw_arg.pat {
-                                                    syn::Pat::Ident(ident) => &ident.ident,
-                                                    _ => panic!("invalid syntax"),
-                                                };
-                                                IxArg {
-                                                    name: ident.clone(),
-                                                    raw_arg: raw_arg.clone(),
-                                                }
-                                            })
-                                            .collect::<Vec<IxArg>>();
-                                        // Remove the Anchor accounts argument
-                                        let anchor = args.remove(0);
-                                        let anchor_ident = extract_ident(&anchor.raw_arg).clone();
-
-                                        Some(StateIx {
-                                            raw_method: m.clone(),
-                                            ident: m.sig.ident.clone(),
-                                            args,
-                                            anchor_ident,
-                                            has_receiver,
-                                        })
-                                    }
-                                },
-                                _ => None,
-                            })
-                            .collect();
-                        Some(StateInterface {
-                            trait_name,
-                            methods,
-                        })
-                    }
-                    _ => None,
-                })
-                .collect::<Vec<StateInterface>>()
-        });
-        // Put it all together.
-        strct.map(|(strct, is_zero_copy)| {
-            // Chop off the `#[state]` attribute. It's just a marker.
-            let mut strct = strct.clone();
-            strct.attrs = vec![];
-
-            State {
-                name: strct.ident.to_string(),
-                strct,
-                interfaces: trait_impls,
-                impl_block_and_methods: impl_block.map(|impl_block| (impl_block, methods.unwrap())),
-                ctor_and_anchor,
-                is_zero_copy,
-            }
-        })
-    };
-    // Parse all non-state ix handlers.
-    let ixs: Vec<Ix> = mod_content
-        .iter()
-        .filter_map(|item| match item {
-            syn::Item::Fn(item_fn) => Some(item_fn),
-            _ => 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,
-                            _ => panic!("invalid syntax"),
-                        };
-                        IxArg {
-                            name: ident.clone(),
-                            raw_arg: arg.clone(),
-                        }
-                    }
-                    _ => panic!("invalid syntax"),
-                })
-                .collect();
-            // Remove the Context argument
-            let anchor = args.remove(0);
-            let anchor_ident = extract_ident(&anchor.raw_arg).clone();
-
-            Ix {
-                raw_method: method.clone(),
-                ident: method.sig.ident.clone(),
-                args,
-                anchor_ident,
-            }
-        })
-        .collect();
-
-    Program {
-        state,
-        ixs,
-        name: mod_ident.clone(),
-        program_mod,
-    }
-}
-
-fn extract_ident(path_ty: &syn::PatType) -> &proc_macro2::Ident {
-    let p = match &*path_ty.ty {
-        syn::Type::Path(p) => &p.path,
-        _ => panic!("invalid syntax"),
-    };
-    let segment = p.segments.first().unwrap();
-    let generic_args = match &segment.arguments {
-        syn::PathArguments::AngleBracketed(args) => args,
-        _ => panic!("invalid syntax"),
-    };
-    let generic_ty = generic_args
-        .args
-        .iter()
-        .filter_map(|arg| match arg {
-            syn::GenericArgument::Type(ty) => Some(ty),
-            _ => None,
-        })
-        .next()
-        .unwrap();
-    let path = match generic_ty {
-        syn::Type::Path(ty_path) => &ty_path.path,
-        _ => panic!("invalid syntax"),
-    };
-    &path.segments[0].ident
-}

+ 59 - 0
lang/syn/src/parser/program/instructions.rs

@@ -0,0 +1,59 @@
+use crate::parser::program::ctx_accounts_ident;
+use crate::{Ix, IxArg};
+use syn::parse::{Error as ParseError, Result as ParseResult};
+use syn::spanned::Spanned;
+
+// Parse all non-state ix handlers from the program mod definition.
+pub fn parse(program_mod: &syn::ItemMod) -> ParseResult<Vec<Ix>> {
+    let mod_content = &program_mod
+        .content
+        .as_ref()
+        .ok_or_else(|| ParseError::new(program_mod.span(), "program content not provided"))?
+        .1;
+
+    mod_content
+        .iter()
+        .filter_map(|item| match item {
+            syn::Item::Fn(item_fn) => Some(item_fn),
+            _ => 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)?;
+
+            Ok(Ix {
+                raw_method: method.clone(),
+                ident: method.sig.ident.clone(),
+                args,
+                anchor_ident,
+            })
+        })
+        .collect::<ParseResult<Vec<Ix>>>()
+}

+ 59 - 0
lang/syn/src/parser/program/mod.rs

@@ -0,0 +1,59 @@
+use crate::Program;
+use syn::parse::{Error as ParseError, Result as ParseResult};
+use syn::spanned::Spanned;
+
+mod instructions;
+mod state;
+
+pub fn parse(program_mod: syn::ItemMod) -> ParseResult<Program> {
+    let state = state::parse(&program_mod)?;
+    let ixs = instructions::parse(&program_mod)?;
+
+    Ok(Program {
+        state,
+        ixs,
+        name: program_mod.ident.clone(),
+        program_mod,
+    })
+}
+
+fn ctx_accounts_ident(path_ty: &syn::PatType) -> ParseResult<proc_macro2::Ident> {
+    let p = match &*path_ty.ty {
+        syn::Type::Path(p) => &p.path,
+        _ => return Err(ParseError::new(path_ty.ty.span(), "invalid type")),
+    };
+    let segment = p
+        .segments
+        .first()
+        .ok_or_else(|| ParseError::new(p.segments.span(), "expected generic arguments here"))?;
+
+    let generic_args = match &segment.arguments {
+        syn::PathArguments::AngleBracketed(args) => args,
+        _ => {
+            return Err(ParseError::new(
+                segment.arguments.span(),
+                "expected generic arguments here",
+            ))
+        }
+    };
+    let generic_ty = generic_args
+        .args
+        .iter()
+        .filter_map(|arg| match arg {
+            syn::GenericArgument::Type(ty) => Some(ty),
+            _ => None,
+        })
+        .next()
+        .ok_or_else(|| ParseError::new(generic_args.span(), "expected Accounts type"))?;
+
+    let path = match generic_ty {
+        syn::Type::Path(ty_path) => &ty_path.path,
+        _ => {
+            return Err(ParseError::new(
+                generic_ty.span(),
+                "expected Accounts struct type",
+            ))
+        }
+    };
+    Ok(path.segments[0].ident.clone())
+}

+ 312 - 0
lang/syn/src/parser/program/state.rs

@@ -0,0 +1,312 @@
+use crate::parser;
+use crate::parser::program::ctx_accounts_ident;
+use crate::{IxArg, State, StateInterface, StateIx};
+use syn::parse::{Error as ParseError, Result as ParseResult};
+use syn::spanned::Spanned;
+
+// Name of the attribute denoting a state struct.
+const STATE_STRUCT_ATTRIBUTE: &str = "state";
+
+// Reserved keyword for the constructor method.
+const CTOR_METHOD_NAME: &str = "new";
+
+// Parse the state from the program mod definition.
+pub fn parse(program_mod: &syn::ItemMod) -> ParseResult<Option<State>> {
+    let mod_content = &program_mod
+        .content
+        .as_ref()
+        .ok_or_else(|| ParseError::new(program_mod.span(), "program content not provided"))?
+        .1;
+
+    // Parse `struct` marked with the `#[state]` attribute.
+    let strct: Option<(&syn::ItemStruct, bool)> = mod_content
+        .iter()
+        .filter_map(|item| match item {
+            syn::Item::Struct(item_strct) => {
+                let attrs = &item_strct.attrs;
+                if attrs.is_empty() {
+                    return None;
+                }
+                let attr_label = attrs[0].path.get_ident().map(|i| i.to_string());
+                if attr_label != Some(STATE_STRUCT_ATTRIBUTE.to_string()) {
+                    return None;
+                }
+                let is_zero_copy = parser::tts_to_string(&attrs[0].tokens) == "(zero_copy)";
+                Some((item_strct, is_zero_copy))
+            }
+            _ => None,
+        })
+        .next();
+
+    // Parse `impl` block for the state struct.
+    let impl_block: Option<syn::ItemImpl> = match strct {
+        None => None,
+        Some((strct, _)) => mod_content
+            .iter()
+            .filter_map(|item| match item {
+                syn::Item::Impl(item_impl) => {
+                    let impl_ty_str = parser::tts_to_string(&item_impl.self_ty);
+                    let strct_name = strct.ident.to_string();
+                    if item_impl.trait_.is_some() {
+                        return None;
+                    }
+                    if strct_name != impl_ty_str {
+                        return None;
+                    }
+                    Some(item_impl.clone())
+                }
+                _ => None,
+            })
+            .next(),
+    };
+
+    // Parse ctor and the generic type in `Context<MY-TYPE>`.
+    let ctor_and_anchor: Option<(syn::ImplItemMethod, syn::Ident)> = impl_block
+        .as_ref()
+        .map(|impl_block| {
+            let r: Option<ParseResult<_>> = impl_block
+                .items
+                .iter()
+                .filter_map(|item: &syn::ImplItem| match item {
+                    syn::ImplItem::Method(m) => match m.sig.ident.to_string() == CTOR_METHOD_NAME {
+                        false => None,
+                        true => Some(m),
+                    },
+                    _ => None,
+                })
+                .map(|m: &syn::ImplItemMethod| {
+                    let (_, is_zero_copy) = strct
+                        .as_ref()
+                        .expect("impl_block exists therefore the struct exists");
+                    let ctx_arg = {
+                        if *is_zero_copy {
+                            // Second param is context.
+                            let mut iter = m.sig.inputs.iter();
+                            match iter.next() {
+                                None => {
+                                    return Err(ParseError::new(
+                                        m.sig.span(),
+                                        "first parameter must be &mut self",
+                                    ))
+                                }
+                                Some(arg) => match arg {
+                                    syn::FnArg::Receiver(r) => {
+                                        if r.mutability.is_none() {
+                                            return Err(ParseError::new(
+                                                m.sig.span(),
+                                                "first parameter must be &mut self",
+                                            ));
+                                        }
+                                    }
+                                    syn::FnArg::Typed(_) => {
+                                        return Err(ParseError::new(
+                                            m.sig.span(),
+                                            "first parameter must be &mut self",
+                                        ))
+                                    }
+                                },
+                            };
+                            match iter.next() {
+                                None => {
+                                    return Err(ParseError::new(
+                                        m.sig.span(),
+                                        "second parameter must be the Context",
+                                    ))
+                                }
+                                Some(ctx_arg) => match ctx_arg {
+                                    syn::FnArg::Receiver(_) => {
+                                        return Err(ParseError::new(
+                                            ctx_arg.span(),
+                                            "second parameter must be the Context",
+                                        ))
+                                    }
+                                    syn::FnArg::Typed(arg) => arg,
+                                },
+                            }
+                        } else {
+                            match m.sig.inputs.first() {
+                                None => {
+                                    return Err(ParseError::new(
+                                        m.sig.span(),
+                                        "first parameter must be the Context",
+                                    ))
+                                }
+                                Some(ctx_arg) => match ctx_arg {
+                                    syn::FnArg::Receiver(_) => {
+                                        return Err(ParseError::new(
+                                            ctx_arg.span(),
+                                            "second parameter must be the Context",
+                                        ))
+                                    }
+                                    syn::FnArg::Typed(arg) => arg,
+                                },
+                            }
+                        }
+                    };
+                    Ok((m.clone(), ctx_accounts_ident(&ctx_arg)?))
+                })
+                .next();
+            r.transpose()
+        })
+        .transpose()?
+        .unwrap_or(None);
+
+    // Parse all methods in the above `impl` block.
+    let methods: Option<Vec<StateIx>> = impl_block
+        .as_ref()
+        .map(|impl_block| {
+            impl_block
+                .items
+                .iter()
+                .filter_map(|item| match item {
+                    syn::ImplItem::Method(m) => match m.sig.ident.to_string() != CTOR_METHOD_NAME {
+                        false => None,
+                        true => Some(m),
+                    },
+                    _ => None,
+                })
+                .map(|m: &syn::ImplItemMethod| {
+                    let mut args = m
+                        .sig
+                        .inputs
+                        .iter()
+                        .filter_map(|arg| match arg {
+                            syn::FnArg::Receiver(_) => None,
+                            syn::FnArg::Typed(arg) => Some(arg),
+                        })
+                        .map(|raw_arg| {
+                            let ident = match &*raw_arg.pat {
+                                syn::Pat::Ident(ident) => &ident.ident,
+                                _ => {
+                                    return Err(ParseError::new(
+                                        raw_arg.pat.span(),
+                                        "unexpected type argument",
+                                    ))
+                                }
+                            };
+                            Ok(IxArg {
+                                name: ident.clone(),
+                                raw_arg: raw_arg.clone(),
+                            })
+                        })
+                        .collect::<ParseResult<Vec<IxArg>>>()?;
+                    // Remove the Anchor accounts argument
+                    let anchor = args.remove(0);
+                    let anchor_ident = ctx_accounts_ident(&anchor.raw_arg)?;
+
+                    Ok(StateIx {
+                        raw_method: m.clone(),
+                        ident: m.sig.ident.clone(),
+                        args,
+                        anchor_ident,
+                        has_receiver: true,
+                    })
+                })
+                .collect::<ParseResult<Vec<_>>>()
+        })
+        .transpose()?;
+
+    // Parse all trait implementations for the above `#[state]` struct.
+    let trait_impls: Option<Vec<StateInterface>> = strct
+        .map(|_strct| {
+            mod_content
+                .iter()
+                .filter_map(|item| match item {
+                    syn::Item::Impl(item_impl) => match &item_impl.trait_ {
+                        None => None,
+                        Some((_, path, _)) => {
+                            let trait_name = path
+                                .segments
+                                .iter()
+                                .next()
+                                .expect("Must have one segment in a path")
+                                .ident
+                                .clone()
+                                .to_string();
+                            Some((item_impl, trait_name))
+                        }
+                    },
+                    _ => None,
+                })
+                .map(|(item_impl, trait_name)| {
+                    let methods = item_impl
+                        .items
+                        .iter()
+                        .filter_map(|item: &syn::ImplItem| match item {
+                            syn::ImplItem::Method(m) => Some(m),
+                            _ => None,
+                        })
+                        .map(|m: &syn::ImplItemMethod| {
+                            match m.sig.inputs.first() {
+                                None => {
+                                    return Err(ParseError::new(
+                                        m.sig.inputs.span(),
+                                        "state methods must have a self argument",
+                                    ))
+                                }
+                                Some(_arg) => {
+                                    let mut has_receiver = false;
+                                    let mut args = m
+                                        .sig
+                                        .inputs
+                                        .iter()
+                                        .filter_map(|arg| match arg {
+                                            syn::FnArg::Receiver(_) => {
+                                                has_receiver = true;
+                                                None
+                                            }
+                                            syn::FnArg::Typed(arg) => Some(arg),
+                                        })
+                                        .map(|raw_arg| {
+                                            let ident = match &*raw_arg.pat {
+                                                syn::Pat::Ident(ident) => &ident.ident,
+                                                _ => panic!("invalid syntax"),
+                                            };
+                                            IxArg {
+                                                name: ident.clone(),
+                                                raw_arg: raw_arg.clone(),
+                                            }
+                                        })
+                                        .collect::<Vec<IxArg>>();
+                                    // Remove the Anchor accounts argument
+                                    let anchor = args.remove(0);
+                                    let anchor_ident = ctx_accounts_ident(&anchor.raw_arg)?;
+
+                                    Ok(StateIx {
+                                        raw_method: m.clone(),
+                                        ident: m.sig.ident.clone(),
+                                        args,
+                                        anchor_ident,
+                                        has_receiver,
+                                    })
+                                }
+                            }
+                        })
+                        .collect::<ParseResult<Vec<StateIx>>>()?;
+                    Ok(StateInterface {
+                        trait_name,
+                        methods,
+                    })
+                })
+                .collect::<ParseResult<Vec<StateInterface>>>()
+        })
+        .transpose()?;
+
+    Ok(strct.map(|(strct, is_zero_copy)| {
+        // Chop off the `#[state]` attribute. It's just a marker.
+        //
+        // TODO: instead of mutating the syntax, we should just implement
+        //       a macro that does nothing.
+        let mut strct = strct.clone();
+        strct.attrs = vec![];
+
+        State {
+            name: strct.ident.to_string(),
+            strct,
+            interfaces: trait_impls,
+            impl_block_and_methods: impl_block.map(|impl_block| (impl_block, methods.unwrap())),
+            ctor_and_anchor,
+            is_zero_copy,
+        }
+    }))
+}

+ 3 - 2
spl/src/shmem.rs

@@ -1,6 +1,7 @@
 //! CPI API for interacting with the SPL shared memory
 //! CPI API for interacting with the SPL shared memory
 //! [program](https://github.com/solana-labs/solana-program-library/tree/master/shared-memory).
 //! [program](https://github.com/solana-labs/solana-program-library/tree/master/shared-memory).
 
 
+use anchor_lang::ToAccountInfo;
 use anchor_lang::{Accounts, CpiContext};
 use anchor_lang::{Accounts, CpiContext};
 use solana_program::account_info::AccountInfo;
 use solana_program::account_info::AccountInfo;
 use solana_program::declare_id;
 use solana_program::declare_id;
@@ -42,8 +43,8 @@ pub struct Ret<'info> {
 #[derive(Accounts)]
 #[derive(Accounts)]
 pub struct Shmem<'info> {
 pub struct Shmem<'info> {
     // Shared memory account to write the return value into.
     // Shared memory account to write the return value into.
-    #[account(mut, "shmem.owner == shmem_program.key")]
+    #[account(mut, constraint = shmem.owner == shmem_program.key)]
     pub shmem: AccountInfo<'info>,
     pub shmem: AccountInfo<'info>,
-    #[account("shmem_program.key == &ID")]
+    #[account(constraint = shmem_program.key == &ID)]
     pub shmem_program: AccountInfo<'info>,
     pub shmem_program: AccountInfo<'info>,
 }
 }