Przeglądaj źródła

Composable Accounts derivations (#21)

Armani Ferrante 4 lat temu
rodzic
commit
34a3474663

+ 1 - 0
.travis.yml

@@ -41,6 +41,7 @@ jobs:
       name: Runs the examples
       name: Runs the examples
       script:
       script:
         - pushd examples/sysvars && anchor test && popd
         - pushd examples/sysvars && anchor test && popd
+        - pushd examples/composite && anchor test && popd
         - pushd examples/tutorial/basic-0 && anchor test && popd
         - pushd examples/tutorial/basic-0 && anchor test && popd
         - pushd examples/tutorial/basic-1 && anchor test && popd
         - pushd examples/tutorial/basic-1 && anchor test && popd
         - pushd examples/tutorial/basic-2 && anchor test && popd
         - pushd examples/tutorial/basic-2 && anchor test && popd

+ 14 - 14
Cargo.lock

@@ -43,20 +43,6 @@ dependencies = [
  "memchr",
  "memchr",
 ]
 ]
 
 
-[[package]]
-name = "anchor"
-version = "0.1.0"
-dependencies = [
- "anchor-attribute-access-control",
- "anchor-attribute-account",
- "anchor-attribute-program",
- "anchor-derive-accounts",
- "borsh",
- "solana-program",
- "solana-sdk",
- "thiserror",
-]
-
 [[package]]
 [[package]]
 name = "anchor-attribute-access-control"
 name = "anchor-attribute-access-control"
 version = "0.1.0"
 version = "0.1.0"
@@ -121,6 +107,20 @@ dependencies = [
  "syn 1.0.54",
  "syn 1.0.54",
 ]
 ]
 
 
+[[package]]
+name = "anchor-lang"
+version = "0.1.0"
+dependencies = [
+ "anchor-attribute-access-control",
+ "anchor-attribute-account",
+ "anchor-attribute-program",
+ "anchor-derive-accounts",
+ "borsh",
+ "solana-program",
+ "solana-sdk",
+ "thiserror",
+]
+
 [[package]]
 [[package]]
 name = "anchor-syn"
 name = "anchor-syn"
 version = "0.1.0"
 version = "0.1.0"

+ 1 - 1
Cargo.toml

@@ -1,5 +1,5 @@
 [package]
 [package]
-name = "anchor"
+name = "anchor-lang"
 version = "0.1.0"
 version = "0.1.0"
 description = ""
 description = ""
 repository = "https://github.com/project-serum/serum-dex"
 repository = "https://github.com/project-serum/serum-dex"

+ 2 - 2
README.md

@@ -55,7 +55,7 @@ mod basic_1 {
 pub struct Initialize<'info> {
 pub struct Initialize<'info> {
     #[account(init)]
     #[account(init)]
     pub my_account: ProgramAccount<'info, MyAccount>,
     pub my_account: ProgramAccount<'info, MyAccount>,
-    pub rent: Rent,
+    pub rent: Sysvar<'info, Rent>,
 }
 }
 
 
 #[derive(Accounts)]
 #[derive(Accounts)]
@@ -95,7 +95,7 @@ purposes of the Accounts macro) that can be specified on a struct deriving `Acco
 | `#[account(mut)]` | On `ProgramAccount` structs. | Marks the account as mutable and persists the state transition. |
 | `#[account(mut)]` | On `ProgramAccount` structs. | Marks the account as mutable and persists the state transition. |
 | `#[account(init)]` | On `ProgramAccount` structs. | Marks the account as being initialized, skipping the account discriminator check. |
 | `#[account(init)]` | On `ProgramAccount` structs. | Marks the account as being initialized, skipping the account discriminator check. |
 | `#[account(belongs_to = <target>)]` | On `ProgramAccount` structs | Checks the `target` field on the account matches the `target` field in the struct deriving `Accounts`. |
 | `#[account(belongs_to = <target>)]` | On `ProgramAccount` structs | Checks the `target` field on the account matches the `target` field in the struct deriving `Accounts`. |
-| `#[account(owner = program \| skip)]` | On `ProgramAccount` and `AccountInfo` structs | Checks the owner of the account is the current program or skips the check. |
+| `#[account(owner = program \| skip)]` | On `AccountInfo` structs | Checks the owner of the account is the current program or skips the check. |
 | `#[account("<literal>")]` | On `ProgramAccount` structs | Executes the given code literal as a constraint. The literal should evaluate to a boolean. |
 | `#[account("<literal>")]` | On `ProgramAccount` structs | Executes the given code literal as a constraint. The literal should evaluate to a boolean. |
 | `#[account(rent_exempt = <skip>)]` | On `AccountInfo` or `ProgramAccount` structs | Optional attribute to skip the rent exemption check. By default, all accounts marked with `#[account(init)]` will be rent exempt. Similarly, omitting `= skip` will mark the account rent exempt. |
 | `#[account(rent_exempt = <skip>)]` | On `AccountInfo` or `ProgramAccount` structs | Optional attribute to skip the rent exemption check. By default, all accounts marked with `#[account(init)]` will be rent exempt. Similarly, omitting `= skip` will mark the account rent exempt. |
 
 

+ 2 - 2
attribute/account/src/lib.rs

@@ -17,7 +17,7 @@ pub fn account(
     let discriminator_preimage = format!("account:{}", account_name.to_string());
     let discriminator_preimage = format!("account:{}", account_name.to_string());
 
 
     let coder = quote! {
     let coder = quote! {
-        impl anchor::AccountSerialize for #account_name {
+        impl anchor_lang::AccountSerialize for #account_name {
             fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<(), ProgramError> {
             fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<(), ProgramError> {
                 // TODO: we shouldn't have to hash at runtime. However, rust
                 // TODO: we shouldn't have to hash at runtime. However, rust
                 //       is not happy when trying to include solana-sdk from
                 //       is not happy when trying to include solana-sdk from
@@ -39,7 +39,7 @@ pub fn account(
             }
             }
         }
         }
 
 
-        impl anchor::AccountDeserialize for #account_name {
+        impl anchor_lang::AccountDeserialize for #account_name {
             fn try_deserialize(buf: &mut &[u8]) -> Result<Self, ProgramError> {
             fn try_deserialize(buf: &mut &[u8]) -> Result<Self, ProgramError> {
                 let mut discriminator = [0u8; 8];
                 let mut discriminator = [0u8; 8];
                 discriminator.copy_from_slice(
                 discriminator.copy_from_slice(

+ 2 - 2
cli/src/template.rs

@@ -30,7 +30,7 @@ cpi = ["no-entrypoint"]
 borsh = {{ git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }}
 borsh = {{ git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }}
 solana-program = "1.4.3"
 solana-program = "1.4.3"
 solana-sdk = {{ version = "1.3.14", default-features = false, features = ["program"] }}
 solana-sdk = {{ version = "1.3.14", default-features = false, features = ["program"] }}
-anchor = {{ git = "https://github.com/project-serum/anchor", features = ["derive"] }}
+anchor-lang = {{ git = "https://github.com/project-serum/anchor", features = ["derive"] }}
 "#,
 "#,
         name,
         name,
         name.to_snake_case(),
         name.to_snake_case(),
@@ -47,7 +47,7 @@ pub fn lib_rs(name: &str) -> String {
     format!(
     format!(
         r#"#![feature(proc_macro_hygiene)]
         r#"#![feature(proc_macro_hygiene)]
 
 
-use anchor::prelude::*;
+use anchor_lang::prelude::*;
 
 
 #[program]
 #[program]
 mod {} {{
 mod {} {{

+ 2 - 0
examples/composite/Anchor.toml

@@ -0,0 +1,2 @@
+cluster = "localnet"
+wallet = "/home/armaniferrante/.config/solana/id.json"

+ 4 - 0
examples/composite/Cargo.toml

@@ -0,0 +1,4 @@
+[workspace]
+members = [
+    "programs/*"
+]

+ 19 - 0
examples/composite/programs/composite/Cargo.toml

@@ -0,0 +1,19 @@
+[package]
+name = "composite"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2018"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "composite"
+
+[features]
+no-entrypoint = []
+cpi = ["no-entrypoint"]
+
+[dependencies]
+borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
+solana-program = "1.4.3"
+solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
+anchor-lang = { git = "https://github.com/project-serum/anchor", features = ["derive"] }

+ 2 - 0
examples/composite/programs/composite/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 65 - 0
examples/composite/programs/composite/src/lib.rs

@@ -0,0 +1,65 @@
+//! This example demonstrates the ability to compose together multiple
+//! structs deriving `Accounts`. See `CompositeUpdate`, below.
+
+#![feature(proc_macro_hygiene)]
+
+use anchor_lang::prelude::*;
+
+#[program]
+mod composite {
+    use super::*;
+    pub fn initialize(ctx: Context<Initialize>) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn composite_update(
+        ctx: Context<CompositeUpdate>,
+        dummy_a: u64,
+        dummy_b: String,
+    ) -> ProgramResult {
+        let a = &mut ctx.accounts.foo.dummy_a;
+        let b = &mut ctx.accounts.bar.dummy_b;
+
+        a.data = dummy_a;
+        b.data = dummy_b;
+
+        Ok(())
+    }
+}
+
+#[derive(Accounts)]
+pub struct Initialize<'info> {
+    #[account(init)]
+    pub dummy_a: ProgramAccount<'info, DummyA>,
+    #[account(init)]
+    pub dummy_b: ProgramAccount<'info, DummyB>,
+    pub rent: Sysvar<'info, Rent>,
+}
+
+#[derive(Accounts)]
+pub struct CompositeUpdate<'info> {
+    foo: Foo<'info>,
+    bar: Bar<'info>,
+}
+
+#[derive(Accounts)]
+pub struct Foo<'info> {
+    #[account(mut)]
+    pub dummy_a: ProgramAccount<'info, DummyA>,
+}
+
+#[derive(Accounts)]
+pub struct Bar<'info> {
+    #[account(mut)]
+    pub dummy_b: ProgramAccount<'info, DummyB>,
+}
+
+#[account]
+pub struct DummyA {
+    pub data: u64,
+}
+
+#[account]
+pub struct DummyB {
+    pub data: String,
+}

+ 59 - 0
examples/composite/tests/composite.js

@@ -0,0 +1,59 @@
+const assert = require('assert');
+const anchor = require('@project-serum/anchor');
+
+describe('composite', () => {
+
+	const provider = anchor.Provider.local();
+
+  // Configure the client to use the local cluster.
+  anchor.setProvider(provider);
+
+  it('Is initialized!', async () => {
+		const program = anchor.workspace.Composite;
+
+		const dummyA = new anchor.web3.Account();
+		const dummyB = new anchor.web3.Account();
+
+		const tx = await program.rpc.initialize({
+      accounts: {
+				dummyA: dummyA.publicKey,
+				dummyB: dummyB.publicKey,
+				rent: anchor.web3.SYSVAR_RENT_PUBKEY,
+      },
+				signers: [dummyA, dummyB],
+      instructions: [
+        anchor.web3.SystemProgram.createAccount({
+          fromPubkey: provider.wallet.publicKey,
+          newAccountPubkey: dummyA.publicKey,
+          space: 8 + 8,
+          lamports: await provider.connection.getMinimumBalanceForRentExemption(
+            8 + 8
+          ),
+          programId: program.programId,
+        }),
+        anchor.web3.SystemProgram.createAccount({
+          fromPubkey: provider.wallet.publicKey,
+          newAccountPubkey: dummyB.publicKey,
+          space: 8 + 100,
+          lamports: await provider.connection.getMinimumBalanceForRentExemption(
+            8 + 100
+          ),
+          programId: program.programId,
+        }),
+      ],
+		});
+
+			await program.rpc.compositeUpdate(new anchor.BN(1234), 'hello', {
+					accounts: {
+							dummyA: dummyA.publicKey,
+							dummyB: dummyB.publicKey,
+					},
+			});
+
+			const dummyAAccount = await program.account.dummyA(dummyA.publicKey);
+			const dummyBAccount = await program.account.dummyB(dummyB.publicKey);
+
+			assert.ok(dummyAAccount.data.eq(new anchor.BN(1234)));
+			assert.ok(dummyBAccount.data === 'hello');
+  });
+});

+ 1 - 1
examples/sysvars/programs/sysvars/Cargo.toml

@@ -16,4 +16,4 @@ cpi = ["no-entrypoint"]
 borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
 borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
 solana-program = "1.4.3"
 solana-program = "1.4.3"
 solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
 solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
-anchor = { git = "https://github.com/project-serum/anchor", features = ["derive"] }
+anchor-lang = { git = "https://github.com/project-serum/anchor", features = ["derive"] }

+ 1 - 1
examples/tutorial/basic-0/programs/basic-0/Cargo.toml

@@ -16,4 +16,4 @@ cpi = ["no-entrypoint"]
 borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
 borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
 solana-program = "1.4.3"
 solana-program = "1.4.3"
 solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
 solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
-anchor = { git = "https://github.com/project-serum/anchor", features = ["derive"] }
+anchor-lang = { git = "https://github.com/project-serum/anchor", features = ["derive"] }

+ 1 - 1
examples/tutorial/basic-1/programs/basic-1/Cargo.toml

@@ -16,4 +16,4 @@ cpi = ["no-entrypoint"]
 borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
 borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
 solana-program = "1.4.3"
 solana-program = "1.4.3"
 solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
 solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
-anchor = { git = "https://github.com/project-serum/anchor", features = ["derive"] }
+anchor-lang = { git = "https://github.com/project-serum/anchor", features = ["derive"] }

+ 1 - 1
examples/tutorial/basic-2/programs/basic-2/Cargo.toml

@@ -16,4 +16,4 @@ cpi = ["no-entrypoint"]
 borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
 borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
 solana-program = "1.4.3"
 solana-program = "1.4.3"
 solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
 solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
-anchor = { git = "https://github.com/project-serum/anchor", features = ["derive"] }
+anchor-lang = { git = "https://github.com/project-serum/anchor", features = ["derive"] }

+ 53 - 54
examples/tutorial/basic-2/programs/basic-2/src/lib.rs

@@ -1,44 +1,49 @@
 #![feature(proc_macro_hygiene)]
 #![feature(proc_macro_hygiene)]
 
 
-use anchor::prelude::*;
+use anchor_lang::prelude::*;
 
 
-// Define the program's RPC handlers.
+// Define the program's instruction handlers.
 
 
 #[program]
 #[program]
 mod basic_2 {
 mod basic_2 {
     use super::*;
     use super::*;
 
 
-    #[access_control(not_zero(authority))]
-    pub fn create_root(ctx: Context<CreateRoot>, authority: Pubkey, data: u64) -> ProgramResult {
-        let root = &mut ctx.accounts.root;
-        root.authority = authority;
-        root.data = data;
+    pub fn create_author(
+        ctx: Context<CreateAuthor>,
+        authority: Pubkey,
+        name: String,
+    ) -> ProgramResult {
+        let author = &mut ctx.accounts.author;
+        author.authority = authority;
+        author.name = name;
         Ok(())
         Ok(())
     }
     }
 
 
-    pub fn update_root(ctx: Context<UpdateRoot>, data: u64) -> ProgramResult {
-        let root = &mut ctx.accounts.root;
-        root.data = data;
+    pub fn update_author(ctx: Context<UpdateAuthor>, name: String) -> ProgramResult {
+        let author = &mut ctx.accounts.author;
+        author.name = name;
         Ok(())
         Ok(())
     }
     }
 
 
-    pub fn create_leaf(ctx: Context<CreateLeaf>, data: u64, custom: MyCustomType) -> ProgramResult {
-        let leaf = &mut ctx.accounts.leaf;
-        leaf.root = *ctx.accounts.root.to_account_info().key;
-        leaf.data = data;
-        leaf.custom = custom;
+    pub fn create_book(ctx: Context<CreateBook>, title: String, pages: Vec<Page>) -> ProgramResult {
+        let book = &mut ctx.accounts.book;
+        book.author = *ctx.accounts.author.to_account_info().key;
+        book.title = title;
+        book.pages = pages;
         Ok(())
         Ok(())
     }
     }
 
 
-    pub fn update_leaf(
-        ctx: Context<UpdateLeaf>,
-        data: u64,
-        custom: Option<MyCustomType>,
+    pub fn update_book(
+        ctx: Context<UpdateBook>,
+        title: Option<String>,
+        pages: Option<Vec<Page>>,
     ) -> ProgramResult {
     ) -> ProgramResult {
-        let leaf = &mut ctx.accounts.leaf;
-        leaf.data = data;
-        if let Some(custom) = custom {
-            leaf.custom = custom;
+        let book = &mut ctx.accounts.book;
+        if let Some(title) = title {
+            book.title = title;
+        }
+        if let Some(pages) = pages {
+            book.pages = pages;
         }
         }
         Ok(())
         Ok(())
     }
     }
@@ -47,66 +52,60 @@ mod basic_2 {
 // Define the validated accounts for each handler.
 // Define the validated accounts for each handler.
 
 
 #[derive(Accounts)]
 #[derive(Accounts)]
-pub struct CreateRoot<'info> {
+pub struct CreateAuthor<'info> {
     #[account(init)]
     #[account(init)]
-    pub root: ProgramAccount<'info, Root>,
+    pub author: ProgramAccount<'info, Author>,
     pub rent: Sysvar<'info, Rent>,
     pub rent: Sysvar<'info, Rent>,
 }
 }
 
 
 #[derive(Accounts)]
 #[derive(Accounts)]
-pub struct UpdateRoot<'info> {
+pub struct UpdateAuthor<'info> {
     #[account(signer)]
     #[account(signer)]
     pub authority: AccountInfo<'info>,
     pub authority: AccountInfo<'info>,
-    #[account(mut, "&root.authority == authority.key")]
-    pub root: ProgramAccount<'info, Root>,
+    #[account(mut, "&author.authority == authority.key")]
+    pub author: ProgramAccount<'info, Author>,
 }
 }
 
 
 #[derive(Accounts)]
 #[derive(Accounts)]
-pub struct CreateLeaf<'info> {
-    pub root: ProgramAccount<'info, Root>,
+pub struct CreateBook<'info> {
+    #[account(signer)]
+    pub authority: AccountInfo<'info>,
+    #[account("&author.authority == authority.key")]
+    pub author: ProgramAccount<'info, Author>,
     #[account(init)]
     #[account(init)]
-    pub leaf: ProgramAccount<'info, Leaf>,
+    pub book: ProgramAccount<'info, Book>,
     pub rent: Sysvar<'info, Rent>,
     pub rent: Sysvar<'info, Rent>,
 }
 }
 
 
 #[derive(Accounts)]
 #[derive(Accounts)]
-pub struct UpdateLeaf<'info> {
+pub struct UpdateBook<'info> {
     #[account(signer)]
     #[account(signer)]
     pub authority: AccountInfo<'info>,
     pub authority: AccountInfo<'info>,
-    #[account("&root.authority == authority.key")]
-    pub root: ProgramAccount<'info, Root>,
-    #[account(mut, belongs_to = root)]
-    pub leaf: ProgramAccount<'info, Leaf>,
+    #[account("&author.authority == authority.key")]
+    pub author: ProgramAccount<'info, Author>,
+    #[account(mut, belongs_to = author)]
+    pub book: ProgramAccount<'info, Book>,
 }
 }
 
 
 // Define the program owned accounts.
 // Define the program owned accounts.
 
 
 #[account]
 #[account]
-pub struct Root {
+pub struct Author {
     pub authority: Pubkey,
     pub authority: Pubkey,
-    pub data: u64,
+    pub name: String,
 }
 }
 
 
 #[account]
 #[account]
-pub struct Leaf {
-    pub root: Pubkey,
-    pub data: u64,
-    pub custom: MyCustomType,
+pub struct Book {
+    pub author: Pubkey,
+    pub title: String,
+    pub pages: Vec<Page>,
 }
 }
 
 
 // Define custom types.
 // Define custom types.
 
 
 #[derive(AnchorSerialize, AnchorDeserialize, Clone)]
 #[derive(AnchorSerialize, AnchorDeserialize, Clone)]
-pub struct MyCustomType {
-    pub my_data: u64,
-    pub key: Pubkey,
-}
-
-// Define any auxiliary access control checks.
-
-fn not_zero(authority: Pubkey) -> ProgramResult {
-    if authority == Pubkey::new_from_array([0; 32]) {
-        return Err(ProgramError::InvalidInstructionData);
-    }
-    Ok(())
+pub struct Page {
+    pub content: String,
+    pub footnote: String,
 }
 }

+ 108 - 5
examples/tutorial/basic-2/tests/basic-2.js

@@ -1,11 +1,114 @@
-const anchor = require('@project-serum/anchor');
+const assert = require("assert");
+//const anchor = require('@project-serum/anchor');
+const anchor = require("/home/armaniferrante/Documents/code/src/github.com/project-serum/anchor/ts");
 
 
-describe('basic-2', () => {
+describe("basic-2", () => {
+  const provider = anchor.Provider.local();
 
 
   // Configure the client to use the local cluster.
   // Configure the client to use the local cluster.
-  anchor.setProvider(anchor.Provider.local());
+  anchor.setProvider(provider);
 
 
-  it('Applies constraints and access control', async () => {
-    const program = anchor.workspace.Basic2;
+  // Author for the tests.
+  const author = new anchor.web3.Account();
+
+  // Program for the tests.
+  const program = anchor.workspace.Basic2;
+
+  it("Creates an author", async () => {
+    await program.rpc.createAuthor(provider.wallet.publicKey, "Ghost", {
+      accounts: {
+        author: author.publicKey,
+        rent: anchor.web3.SYSVAR_RENT_PUBKEY,
+      },
+      signers: [author],
+      instructions: [
+        anchor.web3.SystemProgram.createAccount({
+          fromPubkey: provider.wallet.publicKey,
+          newAccountPubkey: author.publicKey,
+          space: 8 + 1000,
+          lamports: await provider.connection.getMinimumBalanceForRentExemption(
+            8 + 1000
+          ),
+          programId: program.programId,
+        }),
+      ],
+    });
+
+    let authorAccount = await program.account.author(author.publicKey);
+
+    assert.ok(authorAccount.authority.equals(provider.wallet.publicKey));
+    assert.ok(authorAccount.name === "Ghost");
+  });
+
+  it("Updates an author", async () => {
+    await program.rpc.updateAuthor("Updated author", {
+      accounts: {
+        author: author.publicKey,
+        authority: provider.wallet.publicKey,
+      },
+    });
+
+    authorAccount = await program.account.author(author.publicKey);
+
+    assert.ok(authorAccount.authority.equals(provider.wallet.publicKey));
+    assert.ok(authorAccount.name === "Updated author");
+  });
+
+  // Book params to use accross tests.
+  const book = new anchor.web3.Account();
+  const pages = [
+    {
+      content: "first page",
+      footnote: "first footnote",
+    },
+    {
+      content: "second page",
+      footnote: "second footnote",
+    },
+  ];
+
+  it("Creates a book", async () => {
+    await program.rpc.createBook("Book title", pages, {
+      accounts: {
+        authority: provider.wallet.publicKey,
+        author: author.publicKey,
+        book: book.publicKey,
+        rent: anchor.web3.SYSVAR_RENT_PUBKEY,
+      },
+      signers: [book],
+      instructions: [
+        anchor.web3.SystemProgram.createAccount({
+          fromPubkey: provider.wallet.publicKey,
+          newAccountPubkey: book.publicKey,
+          space: 8 + 1000,
+          lamports: await provider.connection.getMinimumBalanceForRentExemption(
+            8 + 1000
+          ),
+          programId: program.programId,
+        }),
+      ],
+    });
+
+    const bookAccount = await program.account.book(book.publicKey);
+
+    assert.ok(bookAccount.author.equals(author.publicKey));
+    assert.ok(bookAccount.title === "Book title");
+    assert.deepEqual(bookAccount.pages, pages);
+  });
+
+  it("Updates a book", async () => {
+    await program.rpc.updateBook("New book title", null, {
+      accounts: {
+        authority: provider.wallet.publicKey,
+        author: author.publicKey,
+        book: book.publicKey,
+      },
+    });
+
+    const bookAccount = await program.account.book(book.publicKey);
+
+    assert.ok(bookAccount.author.equals(author.publicKey));
+    assert.ok(bookAccount.title === "New book title");
+    assert.deepEqual(bookAccount.pages, pages);
   });
   });
 });
 });

+ 1 - 1
examples/tutorial/basic-3/programs/puppet-master/Cargo.toml

@@ -16,5 +16,5 @@ cpi = ["no-entrypoint"]
 borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
 borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
 solana-program = "1.4.3"
 solana-program = "1.4.3"
 solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
 solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
-anchor = { git = "https://github.com/project-serum/anchor", features = ["derive"] }
+anchor-lang = { git = "https://github.com/project-serum/anchor", features = ["derive"] }
 puppet = { path = "../puppet", features = ["cpi"] }
 puppet = { path = "../puppet", features = ["cpi"] }

+ 1 - 1
examples/tutorial/basic-3/programs/puppet/Cargo.toml

@@ -16,4 +16,4 @@ cpi = ["no-entrypoint"]
 borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
 borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
 solana-program = "1.4.3"
 solana-program = "1.4.3"
 solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
 solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
-anchor = { git = "https://github.com/project-serum/anchor", features = ["derive"] }
+anchor-lang = { git = "https://github.com/project-serum/anchor", features = ["derive"] }

+ 41 - 0
src/account_info.rs

@@ -0,0 +1,41 @@
+use crate::{Accounts, ToAccountInfo, ToAccountInfos, ToAccountMetas};
+use solana_sdk::account_info::AccountInfo;
+use solana_sdk::instruction::AccountMeta;
+use solana_sdk::program_error::ProgramError;
+use solana_sdk::pubkey::Pubkey;
+
+impl<'info> Accounts<'info> for AccountInfo<'info> {
+    fn try_accounts(
+        _program_id: &Pubkey,
+        accounts: &mut &[AccountInfo<'info>],
+    ) -> Result<Self, ProgramError> {
+        if accounts.len() == 0 {
+            return Err(ProgramError::NotEnoughAccountKeys);
+        }
+        let account = &accounts[0];
+        *accounts = &accounts[1..];
+        Ok(account.clone())
+    }
+}
+
+impl<'info> ToAccountMetas for AccountInfo<'info> {
+    fn to_account_metas(&self) -> Vec<AccountMeta> {
+        let meta = match self.is_writable {
+            false => AccountMeta::new_readonly(*self.key, self.is_signer),
+            true => AccountMeta::new(*self.key, self.is_signer),
+        };
+        vec![meta]
+    }
+}
+
+impl<'info> ToAccountInfos<'info> for AccountInfo<'info> {
+    fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
+        vec![self.clone()]
+    }
+}
+
+impl<'info> ToAccountInfo<'info> for AccountInfo<'info> {
+    fn to_account_info(&self) -> AccountInfo<'info> {
+        self.clone()
+    }
+}

+ 56 - 0
src/context.rs

@@ -0,0 +1,56 @@
+use crate::Accounts;
+use solana_sdk::account_info::AccountInfo;
+use solana_sdk::pubkey::Pubkey;
+
+/// Provides non-argument inputs to the program.
+pub struct Context<'a, 'b, 'c, 'info, T> {
+    /// Currently executing program id.
+    pub program_id: &'a Pubkey,
+    /// Deserialized accounts.
+    pub accounts: &'b mut T,
+    /// Remaining accounts given but not deserialized or validated.
+    pub remaining_accounts: &'c [AccountInfo<'info>],
+}
+
+impl<'a, 'b, 'c, 'info, T> Context<'a, 'b, 'c, 'info, T> {
+    pub fn new(
+        program_id: &'a Pubkey,
+        accounts: &'b mut T,
+        remaining_accounts: &'c [AccountInfo<'info>],
+    ) -> Self {
+        Self {
+            accounts,
+            program_id,
+            remaining_accounts,
+        }
+    }
+}
+
+/// Context speciying non-argument inputs for cross-program-invocations.
+pub struct CpiContext<'a, 'b, 'c, 'info, T: Accounts<'info>> {
+    pub accounts: T,
+    pub program: AccountInfo<'info>,
+    pub signer_seeds: &'a [&'b [&'c [u8]]],
+}
+
+impl<'a, 'b, 'c, 'info, T: Accounts<'info>> CpiContext<'a, 'b, 'c, 'info, T> {
+    pub fn new(program: AccountInfo<'info>, accounts: T) -> Self {
+        Self {
+            accounts,
+            program,
+            signer_seeds: &[],
+        }
+    }
+
+    pub fn new_with_signer(
+        accounts: T,
+        program: AccountInfo<'info>,
+        signer_seeds: &'a [&'b [&'c [u8]]],
+    ) -> Self {
+        Self {
+            accounts,
+            program,
+            signer_seeds,
+        }
+    }
+}

+ 90 - 0
src/cpi_account.rs

@@ -0,0 +1,90 @@
+use crate::{
+    AccountDeserialize, AccountSerialize, Accounts, ToAccountInfo, ToAccountInfos, ToAccountMetas,
+};
+use solana_sdk::account_info::AccountInfo;
+use solana_sdk::instruction::AccountMeta;
+use solana_sdk::program_error::ProgramError;
+use solana_sdk::pubkey::Pubkey;
+use std::ops::{Deref, DerefMut};
+
+/// Container for any account *not* owned by the current program.
+#[derive(Clone)]
+pub struct CpiAccount<'a, T: AccountSerialize + AccountDeserialize + Clone> {
+    info: AccountInfo<'a>,
+    account: T,
+}
+
+impl<'a, T: AccountSerialize + AccountDeserialize + Clone> CpiAccount<'a, T> {
+    pub fn new(info: AccountInfo<'a>, account: T) -> CpiAccount<'a, T> {
+        Self { info, account }
+    }
+
+    /// Deserializes the given `info` into a `CpiAccount`.
+    pub fn try_from(info: &AccountInfo<'a>) -> Result<CpiAccount<'a, T>, ProgramError> {
+        let mut data: &[u8] = &info.try_borrow_data()?;
+        Ok(CpiAccount::new(
+            info.clone(),
+            T::try_deserialize(&mut data)?,
+        ))
+    }
+}
+
+impl<'info, T> Accounts<'info> for CpiAccount<'info, T>
+where
+    T: AccountSerialize + AccountDeserialize + Clone,
+{
+    fn try_accounts(
+        _program_id: &Pubkey,
+        accounts: &mut &[AccountInfo<'info>],
+    ) -> Result<Self, ProgramError> {
+        if accounts.len() == 0 {
+            return Err(ProgramError::NotEnoughAccountKeys);
+        }
+        let account = &accounts[0];
+        *accounts = &accounts[1..];
+        let pa = CpiAccount::try_from(account)?;
+        Ok(pa)
+    }
+}
+
+impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountMetas
+    for CpiAccount<'info, T>
+{
+    fn to_account_metas(&self) -> Vec<AccountMeta> {
+        let meta = match self.info.is_writable {
+            false => AccountMeta::new_readonly(*self.info.key, self.info.is_signer),
+            true => AccountMeta::new(*self.info.key, self.info.is_signer),
+        };
+        vec![meta]
+    }
+}
+
+impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountInfos<'info>
+    for CpiAccount<'info, T>
+{
+    fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
+        vec![self.info.clone()]
+    }
+}
+
+impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountInfo<'info>
+    for CpiAccount<'info, T>
+{
+    fn to_account_info(&self) -> AccountInfo<'info> {
+        self.info.clone()
+    }
+}
+
+impl<'a, T: AccountSerialize + AccountDeserialize + Clone> Deref for CpiAccount<'a, T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.account
+    }
+}
+
+impl<'a, T: AccountSerialize + AccountDeserialize + Clone> DerefMut for CpiAccount<'a, T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.account
+    }
+}

+ 50 - 171
src/lib.rs

@@ -1,10 +1,42 @@
+//! Anchor ⚓ is a framework for Solana's Sealevel runtime providing several
+//! convenient developer tools.
+//!
+//! - Rust eDSL for writing safe, secure, and high level Solana programs
+//! - [IDL](https://en.wikipedia.org/wiki/Interface_description_language) specification
+//! - TypeScript package for generating clients from IDL
+//! - CLI and workspace management for developing complete applications
+//!
+//! If you're familiar with developing in Ethereum's
+//! [Solidity](https://docs.soliditylang.org/en/v0.7.4/),
+//! [Truffle](https://www.trufflesuite.com/),
+//! [web3.js](https://github.com/ethereum/web3.js) or Parity's
+//! [Ink!](https://github.com/paritytech/ink), then the experience will be
+//! familiar. Although the syntax and semantics are targeted at Solana, the high
+//! level workflow of writing RPC request handlers, emitting an IDL, and
+//! generating clients from IDL is the same.
+//!
+//! For detailed tutorials and examples on how to use Anchor, see the guided
+//! [tutorials](https://project-serum.github.io/anchor) or examples in the GitHub
+//! [repository](https://github.com/project-serum/anchor).
+//!
+//! Presented here are the Rust primitives for building on Solana.
+
 use solana_sdk::account_info::AccountInfo;
 use solana_sdk::account_info::AccountInfo;
 use solana_sdk::instruction::AccountMeta;
 use solana_sdk::instruction::AccountMeta;
 use solana_sdk::program_error::ProgramError;
 use solana_sdk::program_error::ProgramError;
 use solana_sdk::pubkey::Pubkey;
 use solana_sdk::pubkey::Pubkey;
 use std::io::Write;
 use std::io::Write;
-use std::ops::{Deref, DerefMut};
 
 
+mod account_info;
+mod context;
+mod cpi_account;
+mod program_account;
+mod sysvar;
+
+pub use crate::context::{Context, CpiContext};
+pub use crate::cpi_account::CpiAccount;
+pub use crate::program_account::ProgramAccount;
+pub use crate::sysvar::Sysvar;
 pub use anchor_attribute_access_control::access_control;
 pub use anchor_attribute_access_control::access_control;
 pub use anchor_attribute_account::account;
 pub use anchor_attribute_account::account;
 pub use anchor_attribute_program::program;
 pub use anchor_attribute_program::program;
@@ -12,7 +44,7 @@ pub use anchor_derive_accounts::Accounts;
 /// Default serialization format for anchor instructions and accounts.
 /// Default serialization format for anchor instructions and accounts.
 pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize};
 pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize};
 
 
-/// A data structure of Solana accounts that can be deserialized from the input
+/// A data structure of accounts that can be deserialized from the input
 /// of a Solana program. Due to the freewheeling nature of the accounts array,
 /// of a Solana program. Due to the freewheeling nature of the accounts array,
 /// implementations of this trait should perform any and all constraint checks
 /// implementations of this trait should perform any and all constraint checks
 /// (in addition to any done within `AccountDeserialize`) on accounts to ensure
 /// (in addition to any done within `AccountDeserialize`) on accounts to ensure
@@ -21,7 +53,18 @@ pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorS
 pub trait Accounts<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized {
 pub trait Accounts<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized {
     fn try_accounts(
     fn try_accounts(
         program_id: &Pubkey,
         program_id: &Pubkey,
-        from: &mut &[AccountInfo<'info>],
+        accounts: &mut &[AccountInfo<'info>],
+    ) -> Result<Self, ProgramError>;
+}
+
+/// A data structure of accounts providing a one time deserialization upon
+/// initialization, i.e., when the data array for a given account is zeroed.
+/// For all subsequent deserializations, it's expected that
+/// [Accounts](trait.Accounts.html) is used.
+pub trait AccountsInit<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized {
+    fn try_accounts_init(
+        program_id: &Pubkey,
+        accounts: &mut &[AccountInfo<'info>],
     ) -> Result<Self, ProgramError>;
     ) -> Result<Self, ProgramError>;
 }
 }
 
 
@@ -67,177 +110,13 @@ pub trait AccountDeserialize: Sized {
     fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError>;
     fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError>;
 }
 }
 
 
-/// Container for a serializable `account`. Use this to reference any account
-/// owned by the currently executing program.
-#[derive(Clone)]
-pub struct ProgramAccount<'a, T: AccountSerialize + AccountDeserialize + Clone> {
-    info: AccountInfo<'a>,
-    account: T,
-}
-
-impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramAccount<'a, T> {
-    pub fn new(info: AccountInfo<'a>, account: T) -> ProgramAccount<'a, T> {
-        Self { info, account }
-    }
-
-    /// Deserializes the given `info` into a `ProgramAccount`.
-    pub fn try_from(info: &AccountInfo<'a>) -> Result<ProgramAccount<'a, T>, ProgramError> {
-        let mut data: &[u8] = &info.try_borrow_data()?;
-        Ok(ProgramAccount::new(
-            info.clone(),
-            T::try_deserialize(&mut data)?,
-        ))
-    }
-
-    /// Deserializes the zero-initialized `info` into a `ProgramAccount` without
-    /// checking the account type. This should only be used upon program account
-    /// initialization (since the entire account data array is zeroed and thus
-    /// no account type is set).
-    pub fn try_from_init(info: &AccountInfo<'a>) -> Result<ProgramAccount<'a, T>, ProgramError> {
-        let mut data: &[u8] = &info.try_borrow_data()?;
-
-        // The discriminator should be zero, since we're initializing.
-        let mut disc_bytes = [0u8; 8];
-        disc_bytes.copy_from_slice(&data[..8]);
-        let discriminator = u64::from_le_bytes(disc_bytes);
-        if discriminator != 0 {
-            return Err(ProgramError::InvalidAccountData);
-        }
-
-        Ok(ProgramAccount::new(
-            info.clone(),
-            T::try_deserialize_unchecked(&mut data)?,
-        ))
-    }
-}
-
-impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountInfo<'info>
-    for ProgramAccount<'info, T>
-{
-    fn to_account_info(&self) -> AccountInfo<'info> {
-        self.info.clone()
-    }
-}
-
-impl<'a, T: AccountSerialize + AccountDeserialize + Clone> Deref for ProgramAccount<'a, T> {
-    type Target = T;
-
-    fn deref(&self) -> &Self::Target {
-        &self.account
-    }
-}
-
-impl<'a, T: AccountSerialize + AccountDeserialize + Clone> DerefMut for ProgramAccount<'a, T> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.account
-    }
-}
-
-/// Similar to `ProgramAccount`, but to reference any account *not* owned by
-/// the current program.
-pub type CpiAccount<'a, T> = ProgramAccount<'a, T>;
-
-/// Container for a Solana sysvar.
-pub struct Sysvar<'info, T: solana_sdk::sysvar::Sysvar> {
-    info: AccountInfo<'info>,
-    account: T,
-}
-
-impl<'info, T: solana_sdk::sysvar::Sysvar> Sysvar<'info, T> {
-    pub fn from_account_info(
-        acc_info: &AccountInfo<'info>,
-    ) -> Result<Sysvar<'info, T>, ProgramError> {
-        Ok(Sysvar {
-            info: acc_info.clone(),
-            account: T::from_account_info(&acc_info)?,
-        })
-    }
-}
-
-impl<'a, T: solana_sdk::sysvar::Sysvar> Deref for Sysvar<'a, T> {
-    type Target = T;
-
-    fn deref(&self) -> &Self::Target {
-        &self.account
-    }
-}
-
-impl<'a, T: solana_sdk::sysvar::Sysvar> DerefMut for Sysvar<'a, T> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.account
-    }
-}
-
-impl<'info, T: solana_sdk::sysvar::Sysvar> ToAccountInfo<'info> for Sysvar<'info, T> {
-    fn to_account_info(&self) -> AccountInfo<'info> {
-        self.info.clone()
-    }
-}
-
-impl<'info> ToAccountInfo<'info> for AccountInfo<'info> {
-    fn to_account_info(&self) -> AccountInfo<'info> {
-        self.clone()
-    }
-}
-
-/// Provides non-argument inputs to the program.
-pub struct Context<'a, 'b, 'c, 'info, T> {
-    /// Deserialized accounts.
-    pub accounts: &'a mut T,
-    /// Currently executing program id.
-    pub program_id: &'b Pubkey,
-    /// Remaining accounts given but not deserialized or validated.
-    pub remaining_accounts: &'c [AccountInfo<'info>],
-}
-
-impl<'a, 'b, 'c, 'info, T> Context<'a, 'b, 'c, 'info, T> {
-    pub fn new(
-        accounts: &'a mut T,
-        program_id: &'b Pubkey,
-        remaining_accounts: &'c [AccountInfo<'info>],
-    ) -> Self {
-        Self {
-            accounts,
-            program_id,
-            remaining_accounts,
-        }
-    }
-}
-
-/// Context speciying non-argument inputs for cross-program-invocations.
-pub struct CpiContext<'a, 'b, 'c, 'info, T: Accounts<'info>> {
-    pub accounts: T,
-    pub program: AccountInfo<'info>,
-    pub signer_seeds: &'a [&'b [&'c [u8]]],
-}
-
-impl<'a, 'b, 'c, 'info, T: Accounts<'info>> CpiContext<'a, 'b, 'c, 'info, T> {
-    pub fn new(program: AccountInfo<'info>, accounts: T) -> Self {
-        Self {
-            accounts,
-            program,
-            signer_seeds: &[],
-        }
-    }
-
-    pub fn new_with_signer(
-        accounts: T,
-        program: AccountInfo<'info>,
-        signer_seeds: &'a [&'b [&'c [u8]]],
-    ) -> Self {
-        Self {
-            accounts,
-            program,
-            signer_seeds,
-        }
-    }
-}
-
+/// The prelude contains all commonly used components of the crate.
+/// All programs should include it via `anchor_lang::prelude::*;`.
 pub mod prelude {
 pub mod prelude {
     pub use super::{
     pub use super::{
         access_control, account, program, AccountDeserialize, AccountSerialize, Accounts,
         access_control, account, program, AccountDeserialize, AccountSerialize, Accounts,
-        AnchorDeserialize, AnchorSerialize, Context, CpiAccount, CpiContext, ProgramAccount,
-        Sysvar, ToAccountInfo, ToAccountInfos, ToAccountMetas,
+        AccountsInit, AnchorDeserialize, AnchorSerialize, Context, CpiAccount, CpiContext,
+        ProgramAccount, Sysvar, ToAccountInfo, ToAccountInfos, ToAccountMetas,
     };
     };
 
 
     pub use solana_program::msg;
     pub use solana_program::msg;

+ 131 - 0
src/program_account.rs

@@ -0,0 +1,131 @@
+use crate::{
+    AccountDeserialize, AccountSerialize, Accounts, AccountsInit, ToAccountInfo, ToAccountInfos,
+    ToAccountMetas,
+};
+use solana_sdk::account_info::AccountInfo;
+use solana_sdk::instruction::AccountMeta;
+use solana_sdk::program_error::ProgramError;
+use solana_sdk::pubkey::Pubkey;
+use std::ops::{Deref, DerefMut};
+
+/// Container for a serializable `account`. Use this to reference any account
+/// owned by the currently executing program.
+#[derive(Clone)]
+pub struct ProgramAccount<'a, T: AccountSerialize + AccountDeserialize + Clone> {
+    info: AccountInfo<'a>,
+    account: T,
+}
+
+impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramAccount<'a, T> {
+    pub fn new(info: AccountInfo<'a>, account: T) -> ProgramAccount<'a, T> {
+        Self { info, account }
+    }
+
+    /// Deserializes the given `info` into a `ProgramAccount`.
+    pub fn try_from(info: &AccountInfo<'a>) -> Result<ProgramAccount<'a, T>, ProgramError> {
+        let mut data: &[u8] = &info.try_borrow_data()?;
+        Ok(ProgramAccount::new(
+            info.clone(),
+            T::try_deserialize(&mut data)?,
+        ))
+    }
+
+    /// Deserializes the zero-initialized `info` into a `ProgramAccount` without
+    /// checking the account type. This should only be used upon program account
+    /// initialization (since the entire account data array is zeroed and thus
+    /// no account type is set).
+    pub fn try_from_init(info: &AccountInfo<'a>) -> Result<ProgramAccount<'a, T>, ProgramError> {
+        let mut data: &[u8] = &info.try_borrow_data()?;
+
+        // The discriminator should be zero, since we're initializing.
+        let mut disc_bytes = [0u8; 8];
+        disc_bytes.copy_from_slice(&data[..8]);
+        let discriminator = u64::from_le_bytes(disc_bytes);
+        if discriminator != 0 {
+            return Err(ProgramError::InvalidAccountData);
+        }
+
+        Ok(ProgramAccount::new(
+            info.clone(),
+            T::try_deserialize_unchecked(&mut data)?,
+        ))
+    }
+}
+
+impl<'info, T> Accounts<'info> for ProgramAccount<'info, T>
+where
+    T: AccountSerialize + AccountDeserialize + Clone,
+{
+    fn try_accounts(
+        program_id: &Pubkey,
+        accounts: &mut &[AccountInfo<'info>],
+    ) -> Result<Self, ProgramError> {
+        if accounts.len() == 0 {
+            return Err(ProgramError::NotEnoughAccountKeys);
+        }
+        let account = &accounts[0];
+        *accounts = &accounts[1..];
+        let pa = ProgramAccount::try_from(account)?;
+        if pa.info.owner != program_id {}
+        Ok(pa)
+    }
+}
+
+impl<'info, T> AccountsInit<'info> for ProgramAccount<'info, T>
+where
+    T: AccountSerialize + AccountDeserialize + Clone,
+{
+    fn try_accounts_init(
+        program_id: &Pubkey,
+        accounts: &mut &[AccountInfo<'info>],
+    ) -> Result<Self, ProgramError> {
+        if accounts.len() == 0 {
+            return Err(ProgramError::NotEnoughAccountKeys);
+        }
+        let account = &accounts[0];
+        *accounts = &accounts[1..];
+        ProgramAccount::try_from_init(account)
+    }
+}
+
+impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountMetas
+    for ProgramAccount<'info, T>
+{
+    fn to_account_metas(&self) -> Vec<AccountMeta> {
+        let meta = match self.info.is_writable {
+            false => AccountMeta::new_readonly(*self.info.key, self.info.is_signer),
+            true => AccountMeta::new(*self.info.key, self.info.is_signer),
+        };
+        vec![meta]
+    }
+}
+
+impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountInfos<'info>
+    for ProgramAccount<'info, T>
+{
+    fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
+        vec![self.info.clone()]
+    }
+}
+
+impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountInfo<'info>
+    for ProgramAccount<'info, T>
+{
+    fn to_account_info(&self) -> AccountInfo<'info> {
+        self.info.clone()
+    }
+}
+
+impl<'a, T: AccountSerialize + AccountDeserialize + Clone> Deref for ProgramAccount<'a, T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.account
+    }
+}
+
+impl<'a, T: AccountSerialize + AccountDeserialize + Clone> DerefMut for ProgramAccount<'a, T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.account
+    }
+}

+ 69 - 0
src/sysvar.rs

@@ -0,0 +1,69 @@
+use crate::{Accounts, ToAccountInfo, ToAccountInfos, ToAccountMetas};
+use solana_sdk::account_info::AccountInfo;
+use solana_sdk::instruction::AccountMeta;
+use solana_sdk::program_error::ProgramError;
+use solana_sdk::pubkey::Pubkey;
+use std::ops::{Deref, DerefMut};
+
+/// Container for sysvars.
+pub struct Sysvar<'info, T: solana_sdk::sysvar::Sysvar> {
+    info: AccountInfo<'info>,
+    account: T,
+}
+
+impl<'info, T: solana_sdk::sysvar::Sysvar> Sysvar<'info, T> {
+    pub fn from_account_info(
+        acc_info: &AccountInfo<'info>,
+    ) -> Result<Sysvar<'info, T>, ProgramError> {
+        Ok(Sysvar {
+            info: acc_info.clone(),
+            account: T::from_account_info(&acc_info)?,
+        })
+    }
+}
+
+impl<'info, T: solana_sdk::sysvar::Sysvar> Accounts<'info> for Sysvar<'info, T> {
+    fn try_accounts(
+        _program_id: &Pubkey,
+        accounts: &mut &[AccountInfo<'info>],
+    ) -> Result<Self, ProgramError> {
+        if accounts.len() == 0 {
+            return Err(ProgramError::NotEnoughAccountKeys);
+        }
+        let account = &accounts[0];
+        *accounts = &accounts[1..];
+        Sysvar::from_account_info(account)
+    }
+}
+
+impl<'info, T: solana_sdk::sysvar::Sysvar> ToAccountMetas for Sysvar<'info, T> {
+    fn to_account_metas(&self) -> Vec<AccountMeta> {
+        vec![AccountMeta::new_readonly(*self.info.key, false)]
+    }
+}
+
+impl<'info, T: solana_sdk::sysvar::Sysvar> ToAccountInfos<'info> for Sysvar<'info, T> {
+    fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
+        vec![self.info.clone()]
+    }
+}
+
+impl<'a, T: solana_sdk::sysvar::Sysvar> Deref for Sysvar<'a, T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.account
+    }
+}
+
+impl<'a, T: solana_sdk::sysvar::Sysvar> DerefMut for Sysvar<'a, T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.account
+    }
+}
+
+impl<'info, T: solana_sdk::sysvar::Sysvar> ToAccountInfo<'info> for Sysvar<'info, T> {
+    fn to_account_info(&self) -> AccountInfo<'info> {
+        self.info.clone()
+    }
+}

+ 84 - 139
syn/src/codegen/accounts.rs

@@ -1,39 +1,44 @@
 use crate::{
 use crate::{
-    AccountsStruct, Constraint, ConstraintBelongsTo, ConstraintLiteral, ConstraintOwner,
-    ConstraintRentExempt, ConstraintSigner, Field, SysvarTy, Ty,
+    AccountField, AccountsStruct, Constraint, ConstraintBelongsTo, ConstraintLiteral,
+    ConstraintOwner, ConstraintRentExempt, ConstraintSigner, Field, Ty,
 };
 };
 use quote::quote;
 use quote::quote;
 
 
 pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
 pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
-    // Extract out each account info.
-    let acc_infos: Vec<proc_macro2::TokenStream> = accs
-        .fields
-        .iter()
-        .map(|f: &Field| {
-            let name = &f.ident;
-            quote! {
-                let #name = next_account_info(acc_infos)?;
-            }
-        })
-        .collect();
-    let acc_infos_len = {
-        let acc_infos_len = acc_infos.len();
-        quote! {
-            #acc_infos_len
-        }
-    };
-
     // Deserialization for each field.
     // Deserialization for each field.
     let deser_fields: Vec<proc_macro2::TokenStream> = accs
     let deser_fields: Vec<proc_macro2::TokenStream> = accs
         .fields
         .fields
         .iter()
         .iter()
-        .map(generate_field_deserialization)
+        .map(|af: &AccountField| match af {
+            AccountField::AccountsStruct(s) => {
+                let name = &s.ident;
+                quote! {
+                    let #name = Accounts::try_accounts(program_id, accounts)?;
+                }
+            }
+            AccountField::Field(f) => {
+                let name = f.typed_ident();
+                match f.is_init {
+                    false => quote! {
+                        let #name = Accounts::try_accounts(program_id, accounts)?;
+                    },
+                    true => quote! {
+                        let #name = AccountsInit::try_accounts_init(program_id, accounts)?;
+                    },
+                }
+            }
+        })
         .collect();
         .collect();
 
 
     // Constraint checks for each account fields.
     // Constraint checks for each account fields.
     let access_checks: Vec<proc_macro2::TokenStream> = accs
     let access_checks: Vec<proc_macro2::TokenStream> = accs
         .fields
         .fields
         .iter()
         .iter()
+        // TODO: allow constraints on composite fields.
+        .filter_map(|af: &AccountField| match af {
+            AccountField::AccountsStruct(_) => None,
+            AccountField::Field(f) => Some(f),
+        })
         .map(|f: &Field| {
         .map(|f: &Field| {
             let checks: Vec<proc_macro2::TokenStream> = f
             let checks: Vec<proc_macro2::TokenStream> = f
                 .constraints
                 .constraints
@@ -50,8 +55,11 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
     let return_tys: Vec<proc_macro2::TokenStream> = accs
     let return_tys: Vec<proc_macro2::TokenStream> = accs
         .fields
         .fields
         .iter()
         .iter()
-        .map(|f: &Field| {
-            let name = &f.ident;
+        .map(|f: &AccountField| {
+            let name = match f {
+                AccountField::AccountsStruct(s) => &s.ident,
+                AccountField::Field(f) => &f.ident,
+            };
             quote! {
             quote! {
                 #name
                 #name
             }
             }
@@ -62,26 +70,36 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
     let on_save: Vec<proc_macro2::TokenStream> = accs
     let on_save: Vec<proc_macro2::TokenStream> = accs
         .fields
         .fields
         .iter()
         .iter()
-        .map(|f: &Field| {
-            let ident = &f.ident;
-            let info = match f.ty {
-                Ty::AccountInfo => quote! { #ident },
-                Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
-                _ => return quote! {},
-            };
-            match f.is_mut {
-                false => quote! {},
-                true => quote! {
-                    // Only persist the change if the account is owned by the
-                    // current program.
-                    if program_id == self.#info.owner  {
-                        let info = self.#info;
-                        let mut data = info.try_borrow_mut_data()?;
-                        let dst: &mut [u8] = &mut data;
-                        let mut cursor = std::io::Cursor::new(dst);
-                        self.#ident.try_serialize(&mut cursor)?;
+        .map(|af: &AccountField| {
+            match af {
+                AccountField::AccountsStruct(s) => {
+                    let name = &s.ident;
+                    quote! {
+                        self.#name.exit(program_id);
+                    }
+                }
+                AccountField::Field(f) => {
+                    let ident = &f.ident;
+                    let info = match f.ty {
+                        Ty::AccountInfo => quote! { #ident },
+                        Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
+                        _ => return quote! {},
+                    };
+                    match f.is_mut {
+                        false => quote! {},
+                        true => quote! {
+                                // Only persist the change if the account is owned by the
+                                // current program.
+                                if program_id == self.#info.owner  {
+                                        let info = self.#info;
+                                        let mut data = info.try_borrow_mut_data()?;
+                                        let dst: &mut [u8] = &mut data;
+                                        let mut cursor = std::io::Cursor::new(dst);
+                                        self.#ident.try_serialize(&mut cursor)?;
+                                }
+                        },
                     }
                     }
-                },
+                }
             }
             }
         })
         })
         .collect();
         .collect();
@@ -90,10 +108,13 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
     let to_acc_infos: Vec<proc_macro2::TokenStream> = accs
     let to_acc_infos: Vec<proc_macro2::TokenStream> = accs
         .fields
         .fields
         .iter()
         .iter()
-        .map(|f: &Field| {
-            let name = &f.ident;
+        .map(|f: &AccountField| {
+            let name = match f {
+                AccountField::AccountsStruct(s) => &s.ident,
+                AccountField::Field(f) => &f.ident,
+            };
             quote! {
             quote! {
-                self.#name.to_account_info()
+                account_infos.extend(self.#name.to_account_infos());
             }
             }
         })
         })
         .collect();
         .collect();
@@ -102,19 +123,13 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
     let to_acc_metas: Vec<proc_macro2::TokenStream> = accs
     let to_acc_metas: Vec<proc_macro2::TokenStream> = accs
         .fields
         .fields
         .iter()
         .iter()
-        .map(|f: &Field| {
-            let name = &f.ident;
-            let is_signer = match f.is_signer {
-                false => quote! { false },
-                true => quote! { true },
+        .map(|f: &AccountField| {
+            let name = match f {
+                AccountField::AccountsStruct(s) => &s.ident,
+                AccountField::Field(f) => &f.ident,
             };
             };
-            match f.is_mut {
-                false => quote! {
-                    AccountMeta::new_readonly(*self.#name.to_account_info().key, #is_signer)
-                },
-                true => quote! {
-                    AccountMeta::new(*self.#name.to_account_info().key, #is_signer)
-                },
+            quote! {
+                account_metas.extend(self.#name.to_account_metas());
             }
             }
         })
         })
         .collect();
         .collect();
@@ -130,15 +145,7 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
 
 
     quote! {
     quote! {
         impl#combined_generics Accounts#trait_generics for #name#strct_generics {
         impl#combined_generics Accounts#trait_generics for #name#strct_generics {
-            fn try_accounts(program_id: &Pubkey, remaining_accounts: &mut &[AccountInfo<'info>]) -> Result<Self, ProgramError> {
-                let acc_infos = &mut remaining_accounts.iter();
-
-                // Pull out each account info from the `accounts` slice.
-                #(#acc_infos)*
-
-                // Move the remaining_accounts cursor to the iterator end.
-                *remaining_accounts = &remaining_accounts[#acc_infos_len..];
-
+            fn try_accounts(program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>]) -> Result<Self, ProgramError> {
                 // Deserialize each account.
                 // Deserialize each account.
                 #(#deser_fields)*
                 #(#deser_fields)*
 
 
@@ -154,17 +161,22 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
 
 
         impl#combined_generics ToAccountInfos#trait_generics for #name#strct_generics {
         impl#combined_generics ToAccountInfos#trait_generics for #name#strct_generics {
             fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
             fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
-                vec![
-                    #(#to_acc_infos),*
-                ]
+                let mut account_infos = vec![];
+
+                #(#to_acc_infos)*
+
+                account_infos
             }
             }
         }
         }
 
 
         impl#combined_generics ToAccountMetas for #name#strct_generics {
         impl#combined_generics ToAccountMetas for #name#strct_generics {
             fn to_account_metas(&self) -> Vec<AccountMeta> {
             fn to_account_metas(&self) -> Vec<AccountMeta> {
-                vec![
-                    #(#to_acc_metas),*
-                ]
+                let mut account_metas = vec![];
+
+                #(#to_acc_metas)*
+
+
+                account_metas
             }
             }
         }
         }
 
 
@@ -177,73 +189,6 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
     }
     }
 }
 }
 
 
-pub fn generate_field_deserialization(f: &Field) -> proc_macro2::TokenStream {
-    let ident = &f.ident;
-    let assign_ty = match &f.ty {
-        Ty::AccountInfo => quote! {
-            let #ident = #ident.clone();
-        },
-        Ty::ProgramAccount(acc) => {
-            let account_struct = &acc.account_ident;
-            match f.is_init {
-                false => quote! {
-                    let #ident: ProgramAccount<#account_struct> = ProgramAccount::try_from(#ident)?;
-                },
-                true => quote! {
-                    let #ident: ProgramAccount<#account_struct> = ProgramAccount::try_from_init(#ident)?;
-                },
-            }
-        }
-        Ty::CpiAccount(acc) => {
-            let account_struct = &acc.account_ident;
-            match f.is_init {
-                false => quote! {
-                    let #ident: CpiAccount<#account_struct> = CpiAccount::try_from(#ident)?;
-                },
-                true => quote! {
-                    let #ident: CpiAccount<#account_struct> = CpiAccount::try_from_init(#ident)?;
-                },
-            }
-        }
-        Ty::Sysvar(sysvar) => match sysvar {
-            SysvarTy::Clock => quote! {
-                let #ident: Sysvar<Clock> = Sysvar::from_account_info(#ident)?;
-            },
-            SysvarTy::Rent => quote! {
-                let #ident: Sysvar<Rent> = Sysvar::from_account_info(#ident)?;
-            },
-            SysvarTy::EpochSchedule => quote! {
-                let #ident: Sysvar<EpochSchedule> = Sysvar::from_account_info(#ident)?;
-            },
-            SysvarTy::Fees => quote! {
-                let #ident: Sysvar<Fees> = Sysvar::from_account_info(#ident)?;
-            },
-            SysvarTy::RecentBlockHashes => quote! {
-                let #ident: Sysvar<RecentBlockhashes> = Sysvar::from_account_info(#ident)?;
-            },
-            SysvarTy::SlotHashes => quote! {
-                let #ident: Sysvar<SlotHashes> = Sysvar::from_account_info(#ident)?;
-            },
-            SysvarTy::SlotHistory => quote! {
-                let #ident: Sysvar<SlotHistory> = Sysvar::from_account_info(#ident)?;
-            },
-            SysvarTy::StakeHistory => quote! {
-                let #ident: Sysvar<StakeHistory> = Sysvar::from_account_info(#ident)?;
-            },
-            SysvarTy::Instructions => quote! {
-                let #ident: Sysvar<Instructions> = Sysvar::from_account_info(#ident)?;
-            },
-            SysvarTy::Rewards => quote! {
-                let #ident: Sysvar<Rewards> = Sysvar::from_account_info(#ident)?;
-            },
-        },
-    };
-
-    quote! {
-        #assign_ty
-    }
-}
-
 pub fn generate_constraint(f: &Field, c: &Constraint) -> proc_macro2::TokenStream {
 pub fn generate_constraint(f: &Field, c: &Constraint) -> proc_macro2::TokenStream {
     match c {
     match c {
         Constraint::BelongsTo(c) => generate_constraint_belongs_to(f, c),
         Constraint::BelongsTo(c) => generate_constraint_belongs_to(f, c),

+ 1 - 1
syn/src/codegen/program.rs

@@ -49,7 +49,7 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
                     let mut remaining_accounts: &[AccountInfo] = accounts;
                     let mut remaining_accounts: &[AccountInfo] = accounts;
                     let mut accounts = #anchor::try_accounts(program_id, &mut remaining_accounts)?;
                     let mut accounts = #anchor::try_accounts(program_id, &mut remaining_accounts)?;
                     #program_name::#rpc_name(
                     #program_name::#rpc_name(
-                        Context::new(&mut accounts, program_id, remaining_accounts),
+                        Context::new(program_id, &mut accounts, remaining_accounts),
                         #(#rpc_arg_names),*
                         #(#rpc_arg_names),*
                     )?;
                     )?;
                     accounts.exit(program_id)
                     accounts.exit(program_id)

+ 12 - 1
syn/src/idl.rs

@@ -80,6 +80,7 @@ pub enum IdlType {
     PublicKey,
     PublicKey,
     Defined(String),
     Defined(String),
     Option(Box<IdlType>),
     Option(Box<IdlType>),
+    Vec(Box<IdlType>),
 }
 }
 
 
 #[derive(Debug, Serialize, Deserialize)]
 #[derive(Debug, Serialize, Deserialize)]
@@ -107,7 +108,17 @@ impl std::str::FromStr for IdlType {
             "String" => IdlType::String,
             "String" => IdlType::String,
             "Pubkey" => IdlType::PublicKey,
             "Pubkey" => IdlType::PublicKey,
             _ => match s.to_string().strip_prefix("Option<") {
             _ => match s.to_string().strip_prefix("Option<") {
-                None => IdlType::Defined(s.to_string()),
+                None => match s.to_string().strip_prefix("Vec<") {
+                    None => IdlType::Defined(s.to_string()),
+                    Some(inner) => {
+                        let inner_ty = Self::from_str(
+                            inner
+                                .strip_suffix(">")
+                                .ok_or(anyhow::anyhow!("Invalid option"))?,
+                        )?;
+                        IdlType::Vec(Box::new(inner_ty))
+                    }
+                },
                 Some(inner) => {
                 Some(inner) => {
                     let inner_ty = Self::from_str(
                     let inner_ty = Self::from_str(
                         inner
                         inner

+ 124 - 11
syn/src/lib.rs

@@ -1,5 +1,13 @@
 //! DSL syntax tokens.
 //! DSL syntax tokens.
 
 
+#[cfg(feature = "idl")]
+use crate::idl::IdlAccount;
+use anyhow::Result;
+#[cfg(feature = "idl")]
+use heck::MixedCase;
+use quote::quote;
+use std::collections::HashMap;
+
 pub mod codegen;
 pub mod codegen;
 #[cfg(feature = "idl")]
 #[cfg(feature = "idl")]
 pub mod idl;
 pub mod idl;
@@ -27,17 +35,18 @@ pub struct RpcArg {
     pub raw_arg: syn::PatType,
     pub raw_arg: syn::PatType,
 }
 }
 
 
+#[derive(Debug)]
 pub struct AccountsStruct {
 pub struct AccountsStruct {
     // Name of the accounts struct.
     // Name of the accounts struct.
     pub ident: syn::Ident,
     pub ident: syn::Ident,
     // Generics + lifetimes on the accounts struct.
     // Generics + lifetimes on the accounts struct.
     pub generics: syn::Generics,
     pub generics: syn::Generics,
     // Fields on the accounts struct.
     // Fields on the accounts struct.
-    pub fields: Vec<Field>,
+    pub fields: Vec<AccountField>,
 }
 }
 
 
 impl AccountsStruct {
 impl AccountsStruct {
-    pub fn new(strct: syn::ItemStruct, fields: Vec<Field>) -> Self {
+    pub fn new(strct: syn::ItemStruct, fields: Vec<AccountField>) -> Self {
         let ident = strct.ident.clone();
         let ident = strct.ident.clone();
         let generics = strct.generics.clone();
         let generics = strct.generics.clone();
         Self {
         Self {
@@ -48,18 +57,73 @@ impl AccountsStruct {
     }
     }
 
 
     // Returns all program owned accounts in the Accounts struct.
     // Returns all program owned accounts in the Accounts struct.
-    pub fn account_tys(&self) -> Vec<String> {
+    //
+    // `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(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<IdlAccount> {
         self.fields
         self.fields
             .iter()
             .iter()
-            .filter_map(|f| match &f.ty {
-                Ty::ProgramAccount(pty) => Some(pty.account_ident.to_string()),
-                _ => None,
+            .flat_map(|acc: &AccountField| match acc {
+                AccountField::AccountsStruct(comp_f) => {
+                    let accs_strct = global_accs
+                        .get(&comp_f.symbol)
+                        .expect("Could not reslve Accounts symbol");
+                    accs_strct.idl_accounts(global_accs)
+                }
+                AccountField::Field(acc) => vec![IdlAccount {
+                    name: acc.ident.to_string().to_mixed_case(),
+                    is_mut: acc.is_mut,
+                    is_signer: acc.is_signer,
+                }],
             })
             })
             .collect::<Vec<_>>()
             .collect::<Vec<_>>()
     }
     }
 }
 }
 
 
+#[derive(Debug)]
+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
+}
+
+#[derive(Debug)]
+pub struct CompositeField {
+    pub ident: syn::Ident,
+    pub symbol: String,
+}
+
 // An account in the accounts struct.
 // An account in the accounts struct.
+#[derive(Debug)]
 pub struct Field {
 pub struct Field {
     pub ident: syn::Ident,
     pub ident: syn::Ident,
     pub ty: Ty,
     pub ty: Ty,
@@ -69,16 +133,59 @@ pub struct Field {
     pub is_init: bool,
     pub is_init: bool,
 }
 }
 
 
+impl Field {
+    pub fn typed_ident(&self) -> proc_macro2::TokenStream {
+        let name = &self.ident;
+
+        let ty = match &self.ty {
+            Ty::AccountInfo => quote! { AccountInfo },
+            Ty::ProgramAccount(ty) => {
+                let account = &ty.account_ident;
+                quote! {
+                    ProgramAccount<#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
+        }
+    }
+}
+
 // A type of an account field.
 // A type of an account field.
-#[derive(PartialEq)]
+#[derive(Debug, PartialEq)]
 pub enum Ty {
 pub enum Ty {
     AccountInfo,
     AccountInfo,
     ProgramAccount(ProgramAccountTy),
     ProgramAccount(ProgramAccountTy),
-    Sysvar(SysvarTy),
     CpiAccount(CpiAccountTy),
     CpiAccount(CpiAccountTy),
+    Sysvar(SysvarTy),
 }
 }
 
 
-#[derive(PartialEq)]
+#[derive(Debug, PartialEq)]
 pub enum SysvarTy {
 pub enum SysvarTy {
     Clock,
     Clock,
     Rent,
     Rent,
@@ -92,19 +199,20 @@ pub enum SysvarTy {
     Rewards,
     Rewards,
 }
 }
 
 
-#[derive(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: syn::Ident,
 }
 }
 
 
-#[derive(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: syn::Ident,
 }
 }
 
 
 // An access control constraint for an account.
 // An access control constraint for an account.
+#[derive(Debug)]
 pub enum Constraint {
 pub enum Constraint {
     Signer(ConstraintSigner),
     Signer(ConstraintSigner),
     BelongsTo(ConstraintBelongsTo),
     BelongsTo(ConstraintBelongsTo),
@@ -113,21 +221,26 @@ pub enum Constraint {
     RentExempt(ConstraintRentExempt),
     RentExempt(ConstraintRentExempt),
 }
 }
 
 
+#[derive(Debug)]
 pub struct ConstraintBelongsTo {
 pub struct ConstraintBelongsTo {
     pub join_target: proc_macro2::Ident,
     pub join_target: proc_macro2::Ident,
 }
 }
 
 
+#[derive(Debug)]
 pub struct ConstraintSigner {}
 pub struct ConstraintSigner {}
 
 
+#[derive(Debug)]
 pub struct ConstraintLiteral {
 pub struct ConstraintLiteral {
     pub tokens: proc_macro2::TokenStream,
     pub tokens: proc_macro2::TokenStream,
 }
 }
 
 
+#[derive(Debug)]
 pub enum ConstraintOwner {
 pub enum ConstraintOwner {
     Program,
     Program,
     Skip,
     Skip,
 }
 }
 
 
+#[derive(Debug)]
 pub enum ConstraintRentExempt {
 pub enum ConstraintRentExempt {
     Enforce,
     Enforce,
     Skip,
     Skip,

+ 43 - 21
syn/src/parser/accounts.rs

@@ -1,6 +1,7 @@
 use crate::{
 use crate::{
-    AccountsStruct, Constraint, ConstraintBelongsTo, ConstraintLiteral, ConstraintOwner,
-    ConstraintRentExempt, ConstraintSigner, CpiAccountTy, Field, ProgramAccountTy, SysvarTy, Ty,
+    AccountField, AccountsStruct, CompositeField, Constraint, ConstraintBelongsTo,
+    ConstraintLiteral, ConstraintOwner, ConstraintRentExempt, ConstraintSigner, CpiAccountTy,
+    Field, ProgramAccountTy, SysvarTy, Ty,
 };
 };
 
 
 pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
 pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
@@ -9,7 +10,7 @@ pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
         _ => panic!("invalid input"),
         _ => panic!("invalid input"),
     };
     };
 
 
-    let fields: Vec<Field> = fields
+    let fields: Vec<AccountField> = fields
         .named
         .named
         .iter()
         .iter()
         .map(|f: &syn::Field| {
         .map(|f: &syn::Field| {
@@ -40,21 +41,34 @@ pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
     AccountsStruct::new(strct.clone(), fields)
     AccountsStruct::new(strct.clone(), fields)
 }
 }
 
 
-// Parses an inert #[anchor] attribute specifying the DSL.
-fn parse_field(f: &syn::Field, anchor: Option<&syn::Attribute>) -> Field {
+fn parse_field(f: &syn::Field, anchor: Option<&syn::Attribute>) -> AccountField {
     let ident = f.ident.clone().unwrap();
     let ident = f.ident.clone().unwrap();
-    let ty = parse_ty(f);
-    let (constraints, is_mut, is_signer, is_init) = match anchor {
-        None => (vec![], false, false, false),
-        Some(anchor) => parse_constraints(anchor, &ty),
-    };
-    Field {
-        ident,
-        ty,
-        constraints,
-        is_mut,
-        is_signer,
-        is_init,
+    match is_field_primitive(f) {
+        true => {
+            let ty = parse_ty(f);
+            let (constraints, is_mut, is_signer, is_init) = match anchor {
+                None => (vec![], false, false, false),
+                Some(anchor) => parse_constraints(anchor, &ty),
+            };
+            AccountField::Field(Field {
+                ident,
+                ty,
+                constraints,
+                is_mut,
+                is_signer,
+                is_init,
+            })
+        }
+        false => AccountField::AccountsStruct(CompositeField {
+            ident,
+            symbol: ident_string(f),
+        }),
+    }
+}
+fn is_field_primitive(f: &syn::Field) -> bool {
+    match ident_string(f).as_str() {
+        "ProgramAccount" | "CpiAccount" | "Sysvar" | "AccountInfo" => true,
+        _ => false,
     }
     }
 }
 }
 
 
@@ -63,10 +77,7 @@ fn parse_ty(f: &syn::Field) -> Ty {
         syn::Type::Path(ty_path) => ty_path.path.clone(),
         syn::Type::Path(ty_path) => ty_path.path.clone(),
         _ => panic!("invalid account syntax"),
         _ => panic!("invalid account syntax"),
     };
     };
-    // TODO: allow segmented paths.
-    assert!(path.segments.len() == 1);
-    let segments = &path.segments[0];
-    match segments.ident.to_string().as_str() {
+    match ident_string(f).as_str() {
         "ProgramAccount" => Ty::ProgramAccount(parse_program_account(&path)),
         "ProgramAccount" => Ty::ProgramAccount(parse_program_account(&path)),
         "CpiAccount" => Ty::CpiAccount(parse_cpi_account(&path)),
         "CpiAccount" => Ty::CpiAccount(parse_cpi_account(&path)),
         "Sysvar" => Ty::Sysvar(parse_sysvar(&path)),
         "Sysvar" => Ty::Sysvar(parse_sysvar(&path)),
@@ -75,6 +86,17 @@ fn parse_ty(f: &syn::Field) -> Ty {
     }
     }
 }
 }
 
 
+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_cpi_account(path: &syn::Path) -> CpiAccountTy {
 fn parse_cpi_account(path: &syn::Path) -> CpiAccountTy {
     let account_ident = parse_account(path);
     let account_ident = parse_account(path);
     CpiAccountTy { account_ident }
     CpiAccountTy { account_ident }

+ 5 - 11
syn/src/parser/file.rs

@@ -29,7 +29,7 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
         let mut acc_names = HashSet::new();
         let mut acc_names = HashSet::new();
 
 
         for accs_strct in accs.values() {
         for accs_strct in accs.values() {
-            for a in accs_strct.account_tys() {
+            for a in accs_strct.account_tys(&accs)? {
                 acc_names.insert(a);
                 acc_names.insert(a);
             }
             }
         }
         }
@@ -56,15 +56,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(&rpc.anchor_ident.to_string()).unwrap();
             let accounts_strct = accs.get(&rpc.anchor_ident.to_string()).unwrap();
-            let accounts = accounts_strct
-                .fields
-                .iter()
-                .map(|acc| IdlAccount {
-                    name: acc.ident.to_string().to_mixed_case(),
-                    is_mut: acc.is_mut,
-                    is_signer: acc.is_signer,
-                })
-                .collect::<Vec<_>>();
+            let accounts = accounts_strct.idl_accounts(&accs);
             IdlInstruction {
             IdlInstruction {
                 name: rpc.ident.to_string().to_mixed_case(),
                 name: rpc.ident.to_string().to_mixed_case(),
                 accounts,
                 accounts,
@@ -125,7 +117,7 @@ fn parse_program_mod(f: &syn::File) -> syn::ItemMod {
     mods[0].clone()
     mods[0].clone()
 }
 }
 
 
-// Parse all structs deriving the `Accounts` macro.
+// Parse all structs implementing the `Accounts` trait.
 fn parse_accounts(f: &syn::File) -> HashMap<String, AccountsStruct> {
 fn parse_accounts(f: &syn::File) -> HashMap<String, AccountsStruct> {
     f.items
     f.items
         .iter()
         .iter()
@@ -139,6 +131,8 @@ fn parse_accounts(f: &syn::File) -> HashMap<String, AccountsStruct> {
                 }
                 }
                 None
                 None
             }
             }
+            // TODO: parse manual implementations. Currently we only look
+            //       for derives.
             _ => None,
             _ => None,
         })
         })
         .collect()
         .collect()

+ 1 - 1
ts/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@project-serum/anchor",
   "name": "@project-serum/anchor",
-  "version": "0.0.0-alpha.3",
+  "version": "0.0.0-alpha.4",
   "description": "Anchor client",
   "description": "Anchor client",
   "main": "dist/cjs/index.js",
   "main": "dist/cjs/index.js",
   "module": "dist/esm/index.js",
   "module": "dist/esm/index.js",

+ 14 - 1
ts/src/coder.ts

@@ -126,7 +126,20 @@ class IdlCoder {
       // TODO: all the other types that need to be exported by the borsh package.
       // TODO: all the other types that need to be exported by the borsh package.
       default: {
       default: {
         // @ts-ignore
         // @ts-ignore
-        if (field.type.option) {
+        if (field.type.vec) {
+          return borsh.vec(
+            IdlCoder.fieldLayout(
+              {
+                name: undefined,
+                // @ts-ignore
+                type: field.type.vec,
+              },
+              types
+            ),
+            fieldName
+          );
+          // @ts-ignore
+        } else if (field.type.option) {
           return borsh.option(
           return borsh.option(
             IdlCoder.fieldLayout(
             IdlCoder.fieldLayout(
               {
               {

+ 5 - 0
ts/src/idl.ts

@@ -54,9 +54,14 @@ type IdlType =
   | "bytes"
   | "bytes"
   | "string"
   | "string"
   | "publicKey"
   | "publicKey"
+  | IdlTypeVec
   | IdlTypeOption
   | IdlTypeOption
   | IdlTypeDefined;
   | IdlTypeDefined;
 
 
+export type IdlTypeVec = {
+  vec: IdlType;
+};
+
 export type IdlTypeOption = {
 export type IdlTypeOption = {
   option: IdlType;
   option: IdlType;
 };
 };