Browse Source

Init repo

armaniferrante 4 years ago
commit
736c4912e1

+ 21 - 0
Cargo.toml

@@ -0,0 +1,21 @@
+[package]
+name = "anchor"
+version = "0.1.0"
+description = ""
+repository = "https://github.com/project-serum/serum-dex"
+edition = "2018"
+
+[features]
+
+derive = []
+default = []
+
+[dependencies]
+thiserror = "1.0.20"
+solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
+serum-common = { path = "../common" }
+anchor-derive = { path = "./derive" }
+anchor-attributes-program = { path = "./attributes/program" }
+anchor-attributes-access-control = { path = "./attributes/access-control" }
+borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
+solana-client-gen = { path = "../solana-client-gen" }

+ 122 - 0
README.md

@@ -0,0 +1,122 @@
+# Anchor ⚓
+
+Anchor is a DSL for Solana's [Sealevel](https://medium.com/solana-labs/sealevel-parallel-processing-thousands-of-smart-contracts-d814b378192) runtime.
+
+## Goal
+
+It's primary goal is to add safety to Solana programs by providing the ability to more easily reason about program inputs. Because Solana program's are stateless, a transaction must specify accounts to be executed. And because an untrusted client specifies those accounts, a program must responsibily validate all input to the program to ensure it is what it claims to be (in addition to any instruction specific access control the program needs to do). This is particularly burdensome when there are lots of dependencies between accounts, leading to repetitive [boilerplate](https://github.com/project-serum/serum-dex/blob/master/registry/src/access_control.rs) code for account validation along with the ability to easily shoot oneself in the foot by forgetting to validate any particular account.
+
+For example, one could imagine easily writing a faulty SPL token program that forgets to check the owner of a token account actually matches the owner on the account. So one must write an `if` statement to check for all such preconditions. Instead, one can write an account "anchor" to do these checks.
+
+## Example
+
+An example program looks like this.
+
+```rust
+// Program instruction handler.
+
+#[program]
+mod example {
+    pub fn create_root(accs: &mut Initialize, initial_data: u64) {
+	  accs.root.account.initialized = true;
+	  accs.root.account.data = initial_data;
+    }
+}
+
+// Accounts anchor definition.
+
+#[derive(Anchor)]
+pub struct Initialize<'info> {
+    #[anchor(mut, "!root.initialized")]
+    pub root: AnchorAccount<'info, Root>,
+}
+
+// Program owned account.
+
+#[derive(BorshSerialize, BorshDeserialize)]
+pub struct Root {
+    pub initialized: bool,
+    pub data: u64,
+}
+```
+
+The program above does the following
+
+* Transforms the accounts array into the `Initialize` struct.
+* Performs all constraint checks. Here, ensuring that the `Root` account is not initialized
+  by checking the *literal* constraint demarcated by double quotes "".
+* Saves the newly updated account state, marked as `mut`.
+
+See a full example [here](https://github.com/armaniferrante/serum-dex/blob/armani/anchor/anchor/examples/basic/src/lib.rs).
+
+### Marking a program.
+
+The `#[program]` attribute marks a program.
+
+```rust
+#[program]
+mod example {
+ ...
+}
+```
+
+Internally, this generates the usual Solana entry code, i.e.,
+
+```rust
+solana_program::entrypoint!(entry);
+fn entry(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
+  ...
+}
+```
+
+Additionally, it will generate code to both 1) deserialize the `accounts` slice into the correct accounts `Anchor`, ensuring all specified constraints are satisified, and 2) deserialize the `instruction_data` and dispatch to the correct handler (e.g., `initialize` in the example above).
+
+### Creating an instruction handler.
+
+Each method inside the program corresponds to an instruction handler.
+
+```rust
+pub fn initialize(accs: &mut Initialize, initial_data: u64) {
+  ...
+}
+```
+
+Note that the `program` handler inputs are broken up into two sections: 1) an accounts struct for the instruction, deriving the `Anchor` and a variable length set of program arguments deserialized from the instruction data.
+
+## Marking an Anchor.
+
+Account anchors are deserialized structs from the Solana `accounts` slice. To create one, mark your struct with the `#[derive(Anchor)]` macro.
+
+```rust
+#[derive(Anchor)]
+pub struct Initialize<'info> {
+    #[anchor(mut, "!root.initialized")]
+    pub root: AnchorAccount<'info, Root>,
+}
+```
+
+This anchor will perform constraint checks before your `initialize` instruction handler is called. This example, in particular, will execute the code *literal* provided `"!root.initialized"`. If any of the constraints fail to be satisfied, the instruction will exit with an error and your instruction handler will never be called. `mut` marks the account mutable and will be written to account storage on program exit.
+
+## Anchor attribute syntax.
+
+There are several inert attributes (attributes that are consumed only for the purposes of the Anchor macro) that can be specified on the struct deriving `Anchor`.
+
+| Attribute | Where Applicable | Description |
+|:--|:--|:--|
+| `#[anchor(signer)]` | On raw `AccountInfo` structs. | Checks the given account signed the transaction. |
+| `#[anchor(mut)]` | On `AnchorAccount` structs. | Marks the account as mutable and persists the state transition. |
+| `#[anchor(belongs_to = <target>)]` | On `AnchorAccount` structs | Checks the `target` field on the account matches the `target` field in the accounts array. `target` name must match. |
+| `#[anchor(owner = <program \| skip>)]` | On `AnchorAccount` and `AccountInfo` structs | Checks the owner of the account is the current program or skips the check. Defaults to `program`, if not given. |
+| `#[anchor("<code-literal>")]` | On `AnchorAccount` structs | Executes the given code literal as a constraint. The literal should evaluate to a boolean. |
+
+## Future work.
+
+* Standalone constraint expressions. Define expressions in the same way you'd define any other type and then reference them from Anchor structs. This would allow sharing constraints between Anchor structs. Also could do something similar to solidity's function annotation.
+* Constraints on containers. Accounts can be passed in as logical groups, e.g., `Vec<Root>` using the example above, or even as custom structs, e.g., `MyCustomContainer` (where each field itself is an instance of `AnchorAccount`), which might provide a more convient way to reference a group of accounts.
+* Sysvars. Sysvars should be detected and auto deserialized with owner checks.
+* SPL programs. Similarly, SPL programs should be detected and deserialized with owner checks.
+* Client generation. It's straight forward to use the parsers here to emit an IDL that can be used to generate clients.
+* Error code generation for each constraint.
+* Relay accounts for composability
+* Error code derive for boilerplate.
+* Generate error codes for each constraint.

+ 15 - 0
attributes/access-control/Cargo.toml

@@ -0,0 +1,15 @@
+[package]
+name = "anchor-attributes-access-control"
+version = "0.1.0"
+authors = ["armaniferrante <armaniferrante@gmail.com>"]
+edition = "2018"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+proc-macro2 = "1.0"
+quote = "1.0"
+syn = { version = "1.0.54", features = ["full"] }
+anyhow = "1.0.32"
+anchor-syn = { path = "../../syn" }

+ 29 - 0
attributes/access-control/src/lib.rs

@@ -0,0 +1,29 @@
+extern crate proc_macro;
+
+use quote::quote;
+use syn::parse_macro_input;
+
+#[proc_macro_attribute]
+pub fn access_control(
+    args: proc_macro::TokenStream,
+    input: proc_macro::TokenStream,
+) -> proc_macro::TokenStream {
+    let access_control: proc_macro2::TokenStream = args.to_string().parse().unwrap();
+
+    let item_fn = parse_macro_input!(input as syn::ItemFn);
+
+    let fn_vis = item_fn.vis;
+    let fn_sig = item_fn.sig;
+    let fn_block = item_fn.block;
+
+    let fn_stmts = fn_block.stmts;
+
+    proc_macro::TokenStream::from(quote! {
+        #fn_vis #fn_sig {
+
+            #access_control?;
+
+            #(#fn_stmts)*
+        }
+    })
+}

+ 15 - 0
attributes/program/Cargo.toml

@@ -0,0 +1,15 @@
+[package]
+name = "anchor-attributes-program"
+version = "0.1.0"
+authors = ["armaniferrante <armaniferrante@gmail.com>"]
+edition = "2018"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+proc-macro2 = "1.0"
+quote = "1.0"
+syn = { version = "1.0.54", features = ["full"] }
+anyhow = "1.0.32"
+anchor-syn = { path = "../../syn" }

+ 15 - 0
attributes/program/src/lib.rs

@@ -0,0 +1,15 @@
+extern crate proc_macro;
+
+use anchor_syn::codegen::program as program_codegen;
+use anchor_syn::parser::program as program_parser;
+use syn::parse_macro_input;
+
+#[proc_macro_attribute]
+pub fn program(
+    _args: proc_macro::TokenStream,
+    input: 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)
+}

+ 19 - 0
cli/Cargo.toml

@@ -0,0 +1,19 @@
+[package]
+name = "anchor-cli"
+version = "0.1.0"
+authors = ["armaniferrante <armaniferrante@gmail.com>"]
+edition = "2018"
+
+[[bin]]
+name = "anchor"
+path = "src/main.rs"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+clap = "3.0.0-beta.1"
+anyhow = "1.0.32"
+syn = { version = "1.0.54", features = ["full", "extra-traits"] }
+anchor-syn = { path = "../syn" }
+serde_json = "1.0"
+shellexpand = "2.1.0"

+ 59 - 0
cli/src/main.rs

@@ -0,0 +1,59 @@
+use anyhow::Result;
+use clap::Clap;
+use std::fs::File;
+use std::io::Read;
+
+#[derive(Debug, Clap)]
+pub struct Opts {
+    #[clap(subcommand)]
+    pub command: Command,
+}
+
+#[derive(Debug, Clap)]
+pub enum Command {
+    /// Outputs an interface definition file.
+    Idl {
+        /// Path to the program's interface definition.
+        #[clap(short, long)]
+        file: String,
+        /// Output file for the idl (stdout if not specified).
+        #[clap(short, long)]
+        out: Option<String>,
+    },
+    /// Generates a client module.
+    Gen {
+        /// Path to the program's interface definition.
+        #[clap(short, long, required_unless_present("idl"))]
+        file: Option<String>,
+        /// Output file (stdout if not specified).
+        #[clap(short, long)]
+        out: Option<String>,
+        #[clap(short, long)]
+        idl: Option<String>,
+    },
+}
+
+fn main() -> Result<()> {
+    let opts = Opts::parse();
+    match opts.command {
+        Command::Idl { file, out } => idl(file, out),
+        Command::Gen { file, out, idl } => gen(file, out, idl),
+    }
+}
+
+fn idl(file: String, out: Option<String>) -> Result<()> {
+    let file = shellexpand::tilde(&file);
+    let idl = anchor_syn::parser::file::parse(&file)?;
+    let idl_json = serde_json::to_string_pretty(&idl)?;
+    if let Some(out) = out {
+        std::fs::write(out, idl_json);
+        return Ok(());
+    }
+    println!("{}", idl_json);
+    Ok(())
+}
+
+fn gen(file: Option<String>, out: Option<String>, idl: Option<String>) -> Result<()> {
+    // TODO. Generate clients in any language.
+    Ok(())
+}

+ 15 - 0
derive/Cargo.toml

@@ -0,0 +1,15 @@
+[package]
+name = "anchor-derive"
+version = "0.1.0"
+authors = ["armaniferrante <armaniferrante@gmail.com>"]
+edition = "2018"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+proc-macro2 = "1.0"
+quote = "1.0"
+syn = { version = "1.0.54", features = ["full"] }
+anyhow = "1.0.32"
+anchor-syn = { path = "../syn" }

+ 13 - 0
derive/src/lib.rs

@@ -0,0 +1,13 @@
+extern crate proc_macro;
+
+use anchor_syn::codegen::anchor as anchor_codegen;
+use anchor_syn::parser::anchor as anchor_parser;
+use proc_macro::TokenStream;
+use syn::parse_macro_input;
+
+#[proc_macro_derive(Accounts, attributes(account))]
+pub fn derive_anchor_deserialize(item: TokenStream) -> TokenStream {
+    let strct = parse_macro_input!(item as syn::ItemStruct);
+    let tts = anchor_codegen::generate(anchor_parser::parse(&strct));
+    proc_macro::TokenStream::from(tts)
+}

+ 933 - 0
examples/basic/Cargo.lock

@@ -0,0 +1,933 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "aho-corasick"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "anchor"
+version = "0.1.0"
+dependencies = [
+ "anchor-attributes-access-control",
+ "anchor-attributes-program",
+ "anchor-derive",
+ "borsh",
+ "serum-common",
+ "solana-client-gen",
+ "solana-sdk",
+ "thiserror",
+]
+
+[[package]]
+name = "anchor-attributes-access-control"
+version = "0.1.0"
+dependencies = [
+ "anchor-syn",
+ "anyhow",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "anchor-attributes-program"
+version = "0.1.0"
+dependencies = [
+ "anchor-syn",
+ "anyhow",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "anchor-derive"
+version = "0.1.0"
+dependencies = [
+ "anchor-syn",
+ "anyhow",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "anchor-syn"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "heck",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "syn",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c0df63cb2955042487fad3aefd2c6e3ae7389ac5dc1beb28921de0b69f779d4"
+
+[[package]]
+name = "arrayref"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "basic-program"
+version = "0.1.0"
+dependencies = [
+ "anchor",
+ "borsh",
+ "solana-program",
+ "solana-sdk",
+ "spl-token",
+]
+
+[[package]]
+name = "bincode"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d"
+dependencies = [
+ "byteorder",
+ "serde",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
+dependencies = [
+ "block-padding",
+ "byte-tools",
+ "byteorder",
+ "generic-array 0.12.3",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
+dependencies = [
+ "byte-tools",
+]
+
+[[package]]
+name = "borsh"
+version = "0.7.2"
+source = "git+https://github.com/project-serum/borsh?branch=serum#337732a185f052d5ee0424127b04b89d455ffa81"
+dependencies = [
+ "borsh-derive",
+ "solana-sdk",
+]
+
+[[package]]
+name = "borsh-derive"
+version = "0.7.2"
+source = "git+https://github.com/project-serum/borsh?branch=serum#337732a185f052d5ee0424127b04b89d455ffa81"
+dependencies = [
+ "borsh-derive-internal",
+ "borsh-schema-derive-internal",
+ "syn",
+]
+
+[[package]]
+name = "borsh-derive-internal"
+version = "0.7.2"
+source = "git+https://github.com/project-serum/borsh?branch=serum#337732a185f052d5ee0424127b04b89d455ffa81"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "borsh-schema-derive-internal"
+version = "0.7.2"
+source = "git+https://github.com/project-serum/borsh?branch=serum#337732a185f052d5ee0424127b04b89d455ffa81"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "bs58"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb"
+
+[[package]]
+name = "bv"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340"
+dependencies = [
+ "feature-probe",
+ "serde",
+]
+
+[[package]]
+name = "byte-tools"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
+
+[[package]]
+name = "byteorder"
+version = "1.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "crypto-mac"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
+dependencies = [
+ "generic-array 0.12.3",
+ "subtle 1.0.0",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d85653f070353a16313d0046f173f70d1aadd5b42600a14de626f0dfb3473a5"
+dependencies = [
+ "byteorder",
+ "digest",
+ "rand_core",
+ "subtle 2.3.0",
+ "zeroize",
+]
+
+[[package]]
+name = "derivative"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb582b60359da160a9477ee80f15c8d784c477e69c217ef2cdd4169c24ea380f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "digest"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
+dependencies = [
+ "generic-array 0.12.3",
+]
+
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
+[[package]]
+name = "env_logger"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "fake-simd"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
+
+[[package]]
+name = "feature-probe"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da"
+
+[[package]]
+name = "generic-array"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
+dependencies = [
+ "serde",
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "heck"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hex"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
+
+[[package]]
+name = "hmac"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695"
+dependencies = [
+ "crypto-mac",
+ "digest",
+]
+
+[[package]]
+name = "humantime"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
+dependencies = [
+ "quick-error",
+]
+
+[[package]]
+name = "itertools"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
+
+[[package]]
+name = "log"
+version = "0.4.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memchr"
+version = "2.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
+
+[[package]]
+name = "memmap"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "num-derive"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "226b45a5c2ac4dd696ed30fa6b94b057ad909c7b7fc2e0d0808192bced894066"
+dependencies = [
+ "derivative",
+ "num_enum_derive",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c0fd9eba1d5db0994a239e09c1be402d35622277e35468ba891aa5e3188ce7e"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "opaque-debug"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
+
+[[package]]
+name = "pbkdf2"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9"
+dependencies = [
+ "byteorder",
+ "crypto-mac",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+
+[[package]]
+name = "proc-macro-crate"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
+dependencies = [
+ "toml",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quick-error"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+
+[[package]]
+name = "quote"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom",
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "regex"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+ "thread_local",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc5b3ce5d5ea144bb04ebd093a9e14e9765bcfec866aecda9b6dec43b3d1e24"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb5d2a036dc6d2d8fd16fde3498b04306e29bd193bf306a57427019b823d5acd"
+
+[[package]]
+name = "semver"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
+dependencies = [
+ "semver-parser",
+]
+
+[[package]]
+name = "semver-parser"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
+
+[[package]]
+name = "serde"
+version = "1.0.118"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_bytes"
+version = "0.11.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.118"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serum-common"
+version = "0.1.0"
+dependencies = [
+ "arrayref",
+ "borsh",
+ "serde",
+ "solana-sdk",
+ "spl-token",
+]
+
+[[package]]
+name = "sha2"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69"
+dependencies = [
+ "block-buffer",
+ "digest",
+ "fake-simd",
+ "opaque-debug",
+]
+
+[[package]]
+name = "solana-client-gen"
+version = "0.1.0"
+dependencies = [
+ "bincode",
+ "serum-common",
+ "solana-sdk",
+ "thiserror",
+]
+
+[[package]]
+name = "solana-frozen-abi"
+version = "1.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e5ab6ad3dda6a3d95d19603eeedc65689d8125eafb3e23c6a1e01ab8a6ba60c"
+dependencies = [
+ "bs58",
+ "bv",
+ "generic-array 0.14.4",
+ "log",
+ "memmap",
+ "rustc_version",
+ "serde",
+ "serde_derive",
+ "sha2",
+ "solana-frozen-abi-macro",
+ "solana-logger",
+ "thiserror",
+]
+
+[[package]]
+name = "solana-frozen-abi-macro"
+version = "1.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffaa09aa938a67501479ed8a785071c6993f72c34e43f680db3ea7a2dadad9e7"
+dependencies = [
+ "lazy_static",
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn",
+]
+
+[[package]]
+name = "solana-logger"
+version = "1.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d949157d0b23eaf5758b427d90741d2a90751c4e3dfee028f5726ab8b36e769"
+dependencies = [
+ "env_logger",
+ "lazy_static",
+ "log",
+]
+
+[[package]]
+name = "solana-program"
+version = "1.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9c6cb16e8aa986bc0d2af8ec50628f7451bef9dac89924adf48302bd4fc755"
+dependencies = [
+ "bincode",
+ "bs58",
+ "bv",
+ "curve25519-dalek",
+ "hex",
+ "itertools",
+ "lazy_static",
+ "log",
+ "num-derive",
+ "num-traits",
+ "rand",
+ "rustc_version",
+ "rustversion",
+ "serde",
+ "serde_bytes",
+ "serde_derive",
+ "sha2",
+ "solana-frozen-abi",
+ "solana-frozen-abi-macro",
+ "solana-logger",
+ "solana-sdk-macro",
+ "thiserror",
+]
+
+[[package]]
+name = "solana-sdk"
+version = "1.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85c38a02d501422070cd6a4d561b4ab1408e311c5a0b5af3a7bb01246da14f66"
+dependencies = [
+ "bincode",
+ "bs58",
+ "bv",
+ "hex",
+ "hmac",
+ "itertools",
+ "lazy_static",
+ "log",
+ "num-derive",
+ "num-traits",
+ "pbkdf2",
+ "rustc_version",
+ "rustversion",
+ "serde",
+ "serde_bytes",
+ "serde_derive",
+ "sha2",
+ "solana-frozen-abi",
+ "solana-frozen-abi-macro",
+ "solana-program",
+ "solana-sdk-macro",
+ "thiserror",
+]
+
+[[package]]
+name = "solana-sdk-macro"
+version = "1.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "475a680cd175f2e256452e007c6f8622d3a1ab97ab36d26303b35576e24f340c"
+dependencies = [
+ "bs58",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn",
+]
+
+[[package]]
+name = "spl-token"
+version = "2.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaa27ab75067c63b8804d9fff30bd2e8bfb5be448bea8067ed768381e70ca181"
+dependencies = [
+ "arrayref",
+ "num-derive",
+ "num-traits",
+ "num_enum",
+ "remove_dir_all",
+ "solana-sdk",
+ "thiserror",
+]
+
+[[package]]
+name = "subtle"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
+
+[[package]]
+name = "subtle"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "343f3f510c2915908f155e94f17220b19ccfacf2a64a2a5d8004f2c3e311e7fd"
+
+[[package]]
+name = "syn"
+version = "1.0.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e9ae34b84616eedaaf1e9dd6026dbe00dcafa92aa0c8077cb69df1fcfe5e53e"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ba20f23e85b10754cd195504aebf6a27e2e6cbe28c17778a0c930724628dd56"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "typenum"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+
+[[package]]
+name = "version_check"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "zeroize"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f33972566adbd2d3588b0491eb94b98b43695c4ef897903470ede4f3f5a28a"

+ 34 - 0
examples/basic/Cargo.toml

@@ -0,0 +1,34 @@
+[package]
+name = "basic-program"
+version = "0.1.0"
+description = "Anchor example"
+repository = "https://github.com/project-serum/serum-dex"
+edition = "2018"
+
+[workspace]
+
+[lib]
+crate-type = ["cdylib"]
+name = "basic_program"
+
+[features]
+no-entry = []
+program = []
+client = []
+default = []
+
+[dependencies]
+spl-token = { version = "2.0.6", default-features = false, features = ["program", "no-entrypoint"] }
+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 = { path = "../../", feautres = ["derive"] }
+
+[dev-dependencies]
+# solana-client-gen = { path = "../solana-client-gen", features = ["client"] }
+
+[profile.release]
+lto = true
+
+[profile.test]
+opt-level = 2

+ 5 - 0
examples/basic/Makefile

@@ -0,0 +1,5 @@
+include ../../../Makefile
+
+LIB_NAME=basic_program
+PROGRAM_DIRNAME=.
+BPF_SDK=$(shell pwd)/../../../bin/bpf-sdk

+ 2 - 0
examples/basic/Xargo.toml

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

+ 165 - 0
examples/basic/idl.json

@@ -0,0 +1,165 @@
+{
+  "version": "0.0.0",
+  "name": "example",
+  "methods": [
+    {
+      "name": "create_root",
+      "accounts": [
+        {
+          "name": "authority",
+          "is_mut": false,
+          "is_signer": true
+        },
+        {
+          "name": "root",
+          "is_mut": true,
+          "is_signer": false
+        }
+      ],
+      "args": [
+        {
+          "name": "authority",
+          "type": "publicKey"
+        },
+        {
+          "name": "data",
+          "type": "u64"
+        }
+      ]
+    },
+    {
+      "name": "update_root",
+      "accounts": [
+        {
+          "name": "authority",
+          "is_mut": false,
+          "is_signer": true
+        },
+        {
+          "name": "root",
+          "is_mut": true,
+          "is_signer": false
+        }
+      ],
+      "args": [
+        {
+          "name": "data",
+          "type": "u64"
+        }
+      ]
+    },
+    {
+      "name": "create_leaf",
+      "accounts": [
+        {
+          "name": "root",
+          "is_mut": false,
+          "is_signer": false
+        },
+        {
+          "name": "leaf",
+          "is_mut": true,
+          "is_signer": false
+        }
+      ],
+      "args": [
+        {
+          "name": "data",
+          "type": "u64"
+        },
+        {
+          "name": "custom",
+          "type": {
+            "defined": "MyCustomType"
+          }
+        }
+      ]
+    },
+    {
+      "name": "update_leaf",
+      "accounts": [
+        {
+          "name": "authority",
+          "is_mut": false,
+          "is_signer": true
+        },
+        {
+          "name": "root",
+          "is_mut": false,
+          "is_signer": false
+        },
+        {
+          "name": "leaf",
+          "is_mut": true,
+          "is_signer": false
+        }
+      ],
+      "args": [
+        {
+          "name": "data",
+          "type": "u64"
+        }
+      ]
+    }
+  ],
+  "accounts": [
+    {
+      "type": "struct",
+      "name": "Root",
+      "fields": [
+        {
+          "name": "initialized",
+          "type": "bool"
+        },
+        {
+          "name": "authority",
+          "type": "publicKey"
+        },
+        {
+          "name": "data",
+          "type": "u64"
+        }
+      ]
+    },
+    {
+      "type": "struct",
+      "name": "Leaf",
+      "fields": [
+        {
+          "name": "initialized",
+          "type": "bool"
+        },
+        {
+          "name": "root",
+          "type": "publicKey"
+        },
+        {
+          "name": "data",
+          "type": "u64"
+        },
+        {
+          "name": "custom",
+          "type": {
+            "defined": "MyCustomType"
+          }
+        }
+      ]
+    }
+  ],
+  "types": [
+    {
+      "type": "struct",
+      "name": "MyCustomType",
+      "fields": [
+        {
+          "name": "my_data",
+          "type": "u64"
+        },
+        {
+          "name": "key",
+          "type": "publicKey"
+        }
+      ]
+    }
+  ]
+}

+ 108 - 0
examples/basic/src/lib.rs

@@ -0,0 +1,108 @@
+#![cfg_attr(feature = "program", feature(proc_macro_hygiene))]
+
+use anchor::prelude::*;
+
+// Define the program's RPC handlers.
+
+#[program]
+mod example {
+    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.account.authority = authority;
+        root.account.data = data;
+        root.account.initialized = true;
+        Ok(())
+    }
+
+    pub fn update_root(ctx: Context<UpdateRoot>, data: u64) -> ProgramResult {
+        let root = &mut ctx.accounts.root;
+        root.account.data = data;
+        Ok(())
+    }
+
+    pub fn create_leaf(ctx: Context<CreateLeaf>, data: u64, custom: MyCustomType) -> ProgramResult {
+        let leaf = &mut ctx.accounts.leaf;
+        leaf.account.data = data;
+        leaf.account.custom = custom;
+        Ok(())
+    }
+
+    pub fn update_leaf(ctx: Context<UpdateLeaf>, data: u64) -> ProgramResult {
+        let leaf = &mut ctx.accounts.leaf;
+        leaf.account.data = data;
+        Ok(())
+    }
+}
+
+// Define the validated accounts for each handler.
+
+#[derive(Accounts)]
+pub struct CreateRoot<'info> {
+    #[account(signer)]
+    pub authority: AccountInfo<'info>,
+    #[account(mut, "!root.initialized")]
+    pub root: ProgramAccount<'info, Root>,
+}
+
+#[derive(Accounts)]
+pub struct UpdateRoot<'info> {
+    #[account(signer)]
+    pub authority: AccountInfo<'info>,
+    #[account(mut, "root.initialized", "&root.authority == authority.key")]
+    pub root: ProgramAccount<'info, Root>,
+}
+
+#[derive(Accounts)]
+pub struct CreateLeaf<'info> {
+    #[account("root.initialized")]
+    pub root: ProgramAccount<'info, Root>,
+    #[account(mut, "!leaf.initialized")]
+    pub leaf: ProgramAccount<'info, Leaf>,
+}
+
+#[derive(Accounts)]
+pub struct UpdateLeaf<'info> {
+    #[account(signer)]
+    pub authority: AccountInfo<'info>,
+    #[account("root.initialized", "&root.authority == authority.key")]
+    pub root: ProgramAccount<'info, Root>,
+    #[account(mut, belongs_to = root, "!leaf.initialized")]
+    pub leaf: ProgramAccount<'info, Leaf>,
+}
+
+// Define the program owned accounts.
+
+#[derive(AnchorSerialize, AnchorDeserialize)]
+pub struct Root {
+    pub initialized: bool,
+    pub authority: Pubkey,
+    pub data: u64,
+}
+
+#[derive(AnchorSerialize, AnchorDeserialize)]
+pub struct Leaf {
+    pub initialized: bool,
+    pub root: Pubkey,
+    pub data: u64,
+    pub custom: MyCustomType,
+}
+
+// Define custom types.
+
+#[derive(AnchorSerialize, AnchorDeserialize)]
+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(())
+}

+ 18 - 0
examples/basic/tests/ex.rs

@@ -0,0 +1,18 @@
+/*
+use solana_client_gen::solana_client_gen;
+
+#[solana_client_gen]
+mod instruction {
+        pub enum Example {
+                CreateRoot,
+        }
+}
+
+#[test]
+fn example() {
+    let (client, genesis) = serum_common_tests::genesis::<client::Client>();
+
+    let accs = vec![];
+    client.initialize(&accs);
+}
+*/

+ 58 - 0
src/lib.rs

@@ -0,0 +1,58 @@
+use solana_sdk::account_info::AccountInfo;
+use solana_sdk::program_error::ProgramError;
+use solana_sdk::pubkey::Pubkey;
+use std::ops::Deref;
+
+pub use anchor_attributes_access_control::access_control;
+pub use anchor_attributes_program::program;
+pub use anchor_derive::Accounts;
+pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize};
+
+pub struct ProgramAccount<'a, T: AnchorSerialize + AnchorDeserialize> {
+    pub info: AccountInfo<'a>,
+    pub account: T,
+}
+
+impl<'a, T: AnchorSerialize + AnchorDeserialize> ProgramAccount<'a, T> {
+    pub fn new(info: AccountInfo<'a>, account: T) -> ProgramAccount<'a, T> {
+        Self { info, account }
+    }
+
+    pub fn save(&self) {
+        // todo
+    }
+}
+
+impl<'a, T: AnchorSerialize + AnchorDeserialize> Deref for ProgramAccount<'a, T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.account
+    }
+}
+
+pub trait Accounts<'info>: Sized {
+    fn try_anchor(program_id: &Pubkey, from: &[AccountInfo<'info>]) -> Result<Self, ProgramError>;
+}
+
+pub trait AccountsSave: Sized {
+    fn try_save(&self) -> Result<Self, ProgramError>;
+}
+
+pub struct Context<'a, 'b, T> {
+    pub accounts: &'a mut T,
+    pub program_id: &'b Pubkey,
+}
+
+pub mod prelude {
+    pub use super::{
+        access_control, program, Accounts, AnchorDeserialize, AnchorSerialize, Context,
+        ProgramAccount,
+    };
+
+    pub use solana_sdk::account_info::next_account_info;
+    pub use solana_sdk::account_info::AccountInfo;
+    pub use solana_sdk::entrypoint::ProgramResult;
+    pub use solana_sdk::program_error::ProgramError;
+    pub use solana_sdk::pubkey::Pubkey;
+}

+ 15 - 0
syn/Cargo.toml

@@ -0,0 +1,15 @@
+[package]
+name = "anchor-syn"
+version = "0.1.0"
+authors = ["armaniferrante <armaniferrante@gmail.com>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+proc-macro2 = "1.0"
+quote = "1.0"
+syn = { version = "1.0.54", features = ["full", "extra-traits", "parsing"] }
+anyhow = "1.0.32"
+heck = "0.3.1"
+serde = { version = "1.0.118", features = ["derive"] }

+ 180 - 0
syn/src/codegen/anchor.rs

@@ -0,0 +1,180 @@
+use crate::{
+    AccountsStruct, Constraint, ConstraintBelongsTo, ConstraintLiteral, ConstraintOwner,
+    ConstraintSigner, Field, Ty,
+};
+use quote::quote;
+
+pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
+    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 (access_checks, return_tys): (
+        Vec<proc_macro2::TokenStream>,
+        Vec<proc_macro2::TokenStream>,
+    ) = accs
+        .fields
+        .iter()
+        .map(|f: &Field| {
+            let name = &f.ident;
+
+            // Account validation.
+            let access_control = generate_field(f);
+
+            // Single field in the final deserialized accounts struct.
+            let return_ty = quote! {
+                #name
+            };
+
+            (access_control, return_ty)
+        })
+        .unzip();
+
+    let on_save: Vec<proc_macro2::TokenStream> = accs
+        .fields
+        .iter()
+        .map(|f: &Field| {
+            let ident = &f.ident;
+            let info = match f.ty {
+                Ty::AccountInfo => quote! { #ident },
+                Ty::ProgramAccount(_) => quote! { #ident.info },
+            };
+            match f.is_mut {
+                false => quote! {},
+                true => quote! {
+                        let mut data = self.#info.try_borrow_mut_data()?;
+                        let dst: &mut [u8] = &mut data;
+                        let mut cursor = std::io::Cursor::new(dst);
+                        self.#ident.account.serialize(&mut cursor)
+                                .map_err(|_| ProgramError::InvalidAccountData)?;
+                },
+            }
+        })
+        .collect();
+
+    let name = &accs.ident;
+    let generics = &accs.generics;
+
+    quote! {
+        impl#generics Accounts#generics for #name#generics {
+            fn try_anchor(program_id: &Pubkey, accounts: &[AccountInfo<'info>]) -> Result<Self, ProgramError> {
+                let acc_infos = &mut accounts.iter();
+
+                #(#acc_infos)*
+
+                #(#access_checks)*
+
+                Ok(#name {
+                    #(#return_tys),*
+                })
+            }
+        }
+
+        impl#generics #name#generics {
+            pub fn exit(&self) -> ProgramResult {
+                #(#on_save)*
+                Ok(())
+            }
+        }
+    }
+}
+
+// Unpacks the field, if needed.
+pub fn generate_field(f: &Field) -> proc_macro2::TokenStream {
+    let checks: Vec<proc_macro2::TokenStream> = f
+        .constraints
+        .iter()
+        .map(|c| generate_constraint(&f, c))
+        .collect();
+    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;
+            quote! {
+                let mut data: &[u8] = &#ident.try_borrow_data()?;
+                let #ident = ProgramAccount::new(
+                    #ident.clone(),
+                    #account_struct::deserialize(&mut data)
+                    .map_err(|_| ProgramError::InvalidAccountData)?
+                );
+            }
+        }
+    };
+    quote! {
+        #assign_ty
+        #(#checks)*
+    }
+}
+
+pub fn generate_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(f, c),
+        Constraint::Owner(c) => generate_constraint_owner(f, c),
+    }
+}
+
+pub fn generate_constraint_belongs_to(
+    f: &Field,
+    c: &ConstraintBelongsTo,
+) -> proc_macro2::TokenStream {
+    // todo: assert the field type.
+
+    let target = c.join_target.clone();
+    let ident = &f.ident;
+    // todo: would be nice if target could be an account info object.
+    quote! {
+        if &#ident.#target != #target.info.key {
+            return Err(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.info },
+    };
+    quote! {
+        if !#info.is_signer {
+            return Err(ProgramError::MissingRequiredSignature);
+        }
+    }
+}
+
+pub fn generate_constraint_literal(_f: &Field, c: &ConstraintLiteral) -> proc_macro2::TokenStream {
+    let tokens = &c.tokens;
+    quote! {
+        if #tokens {
+            return Err(ProgramError::Custom(1)); // todo: error codes
+        }
+    }
+}
+
+pub fn generate_constraint_owner(f: &Field, c: &ConstraintOwner) -> proc_macro2::TokenStream {
+    let ident = &f.ident;
+    let info = match f.ty {
+        Ty::AccountInfo => quote! { #ident },
+        Ty::ProgramAccount(_) => quote! { #ident.info },
+    };
+    match c {
+        ConstraintOwner::Skip => quote! {},
+        ConstraintOwner::Program => quote! {
+            if #info.owner != program_id {
+                return Err(ProgramError::Custom(1)); // todo: error codes
+            }
+        },
+    }
+}

+ 3 - 0
syn/src/codegen/idl.rs

@@ -0,0 +1,3 @@
+pub fn generate() {
+    // todo
+}

+ 2 - 0
syn/src/codegen/mod.rs

@@ -0,0 +1,2 @@
+pub mod anchor;
+pub mod program;

+ 134 - 0
syn/src/codegen/program.rs

@@ -0,0 +1,134 @@
+use crate::Program;
+use heck::CamelCase;
+use quote::quote;
+
+pub fn generate(program: Program) -> proc_macro2::TokenStream {
+    let mod_name = &program.name;
+    let instruction_name = instruction_enum_name(&program);
+    let dispatch = generate_dispatch(&program);
+    let methods = generate_methods(&program);
+    let instruction = generate_instruction(&program);
+
+    quote! {
+        // Import everything in the mod, in case the user wants to put anchors
+        // in there.
+        use #mod_name::*;
+
+        solana_program::entrypoint!(entry);
+        fn entry(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
+            let mut data: &[u8] = instruction_data;
+            let ix = instruction::#instruction_name::deserialize(&mut data)
+                .map_err(|_| ProgramError::Custom(1))?; // todo: error code
+
+                #dispatch
+        }
+
+        #methods
+
+        #instruction
+    }
+}
+pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
+    let program_name = &program.name;
+    let enum_name = instruction_enum_name(program);
+    let dispatch_arms: Vec<proc_macro2::TokenStream> = program
+        .rpcs
+        .iter()
+        .map(|rpc| {
+            let rpc_arg_names: Vec<&syn::Ident> = rpc.args.iter().map(|arg| &arg.name).collect();
+
+            let variant_arm = {
+                let rpc_name_camel = proc_macro2::Ident::new(
+                    &rpc.raw_method.sig.ident.to_string().to_camel_case(),
+                    rpc.raw_method.sig.ident.span(),
+                );
+                // If no args, output a "unit" variant instead of a struct variant.
+                if rpc.args.len() == 0 {
+                    quote! {
+                        #enum_name::#rpc_name_camel
+                    }
+                } else {
+                    quote! {
+                        #enum_name::#rpc_name_camel {
+                            #(#rpc_arg_names),*
+                        }
+                    }
+                }
+            };
+
+            let rpc_name = &rpc.raw_method.sig.ident;
+            let anchor = &rpc.anchor_ident;
+
+            quote! {
+                instruction::#variant_arm => {
+                    let mut accounts = #anchor::try_anchor(program_id, accounts)?;
+                    #program_name::#rpc_name(
+                        Context {
+                            accounts: &mut accounts,
+                            program_id,
+                        },
+                        #(#rpc_arg_names),*
+                    )?;
+                    accounts.exit()
+                }
+            }
+        })
+        .collect();
+
+    quote! {
+        match ix {
+            #(#dispatch_arms),*
+        }
+    }
+}
+
+pub fn generate_methods(program: &Program) -> proc_macro2::TokenStream {
+    let program_mod = &program.program_mod;
+    quote! {
+        #program_mod
+    }
+}
+
+pub fn generate_instruction(program: &Program) -> proc_macro2::TokenStream {
+    let enum_name = instruction_enum_name(program);
+    let variants: Vec<proc_macro2::TokenStream> = program
+        .rpcs
+        .iter()
+        .map(|rpc| {
+            let rpc_name_camel = proc_macro2::Ident::new(
+                &rpc.raw_method.sig.ident.to_string().to_camel_case(),
+                rpc.raw_method.sig.ident.span(),
+            );
+            let raw_args: Vec<&syn::PatType> = rpc.args.iter().map(|arg| &arg.raw_arg).collect();
+            // If no args, output a "unit" variant instead of a struct variant.
+            if rpc.args.len() == 0 {
+                quote! {
+                    #rpc_name_camel
+                }
+            } else {
+                quote! {
+                    #rpc_name_camel {
+                        #(#raw_args),*
+                    }
+                }
+            }
+        })
+        .collect();
+
+    quote! {
+        pub mod instruction {
+            use super::*;
+            #[derive(AnchorSerialize, AnchorDeserialize)]
+            pub enum #enum_name {
+                #(#variants),*
+            }
+        }
+    }
+}
+
+fn instruction_enum_name(program: &Program) -> proc_macro2::Ident {
+    proc_macro2::Ident::new(
+        &program.name.to_string().to_camel_case(),
+        program.name.span(),
+    )
+}

+ 101 - 0
syn/src/idl.rs

@@ -0,0 +1,101 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct Idl {
+    pub version: String,
+    pub name: String,
+    pub methods: Vec<IdlMethod>,
+    #[serde(skip_serializing_if = "Vec::is_empty", default)]
+    pub accounts: Vec<IdlTypeDef>,
+    #[serde(skip_serializing_if = "Vec::is_empty", default)]
+    pub types: Vec<IdlTypeDef>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct IdlMethod {
+    pub name: String,
+    pub accounts: Vec<IdlAccount>,
+    pub args: Vec<IdlField>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct IdlAccount {
+    pub name: String,
+    pub is_mut: bool,
+    pub is_signer: bool,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct IdlField {
+    pub name: String,
+    #[serde(rename = "type")]
+    pub ty: IdlType,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase", tag = "type")]
+pub enum IdlTypeDef {
+    Struct {
+        name: String,
+        fields: Vec<IdlField>,
+    },
+    Enum {
+        name: String,
+        variants: Vec<EnumVariant>,
+    },
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct EnumVariant {
+    pub name: String,
+    #[serde(skip_serializing_if = "Option::is_none", default)]
+    pub fields: Option<EnumFields>,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(untagged)]
+pub enum EnumFields {
+    Named(Vec<IdlField>),
+    Tuple(Vec<IdlType>),
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub enum IdlType {
+    Bool,
+    U8,
+    I8,
+    U16,
+    I16,
+    U32,
+    I32,
+    U64,
+    I64,
+    Bytes,
+    String,
+    PublicKey,
+    Defined(String),
+}
+
+impl std::str::FromStr for IdlType {
+    type Err = anyhow::Error;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let r = match s {
+            "bool" => IdlType::Bool,
+            "u8" => IdlType::U8,
+            "i8" => IdlType::I8,
+            "u16" => IdlType::U16,
+            "i16" => IdlType::I16,
+            "u32" => IdlType::U32,
+            "I32" => IdlType::I32,
+            "u64" => IdlType::U64,
+            "i64" => IdlType::I64,
+            "Vec<u8>" => IdlType::Bytes,
+            "String" => IdlType::String,
+            "Pubkey" => IdlType::PublicKey,
+            _ => IdlType::Defined(s.to_string()),
+        };
+        Ok(r)
+    }
+}

+ 100 - 0
syn/src/lib.rs

@@ -0,0 +1,100 @@
+//! DSL syntax tokens.
+
+pub mod codegen;
+pub mod idl;
+pub mod parser;
+
+#[derive(Debug)]
+pub struct Program {
+    pub rpcs: Vec<Rpc>,
+    pub name: syn::Ident,
+    pub program_mod: syn::ItemMod,
+}
+
+#[derive(Debug)]
+pub struct Rpc {
+    pub raw_method: syn::ItemFn,
+    pub ident: syn::Ident,
+    pub args: Vec<RpcArg>,
+    pub anchor_ident: syn::Ident,
+}
+
+#[derive(Debug)]
+pub struct RpcArg {
+    pub name: proc_macro2::Ident,
+    pub raw_arg: syn::PatType,
+}
+
+pub struct AccountsStruct {
+    // Name of the accounts struct.
+    pub ident: syn::Ident,
+    // Generics + lifetimes on the accounts struct.
+    pub generics: syn::Generics,
+    // Fields on the accounts struct.
+    pub fields: Vec<Field>,
+}
+
+impl AccountsStruct {
+    pub fn new(strct: syn::ItemStruct, fields: Vec<Field>) -> Self {
+        let ident = strct.ident.clone();
+        let generics = strct.generics.clone();
+        Self {
+            ident,
+            generics,
+            fields,
+        }
+    }
+
+    pub fn account_tys(&self) -> Vec<String> {
+        self.fields
+            .iter()
+            .filter_map(|f| match &f.ty {
+                Ty::ProgramAccount(pty) => Some(pty.account_ident.to_string()),
+                _ => None,
+            })
+            .collect::<Vec<_>>()
+    }
+}
+
+// An account in the accounts struct.
+pub struct Field {
+    pub ident: syn::Ident,
+    pub ty: Ty,
+    pub constraints: Vec<Constraint>,
+    pub is_mut: bool,
+    pub is_signer: bool,
+}
+
+// A type of an account field.
+pub enum Ty {
+    AccountInfo,
+    ProgramAccount(ProgramAccountTy),
+}
+
+pub struct ProgramAccountTy {
+    // The struct type of the account.
+    pub account_ident: syn::Ident,
+}
+
+// An access control constraint for an account.
+pub enum Constraint {
+    Signer(ConstraintSigner),
+    BelongsTo(ConstraintBelongsTo),
+    Literal(ConstraintLiteral),
+    Owner(ConstraintOwner),
+}
+
+pub struct ConstraintBelongsTo {
+    pub join_target: proc_macro2::Ident,
+}
+
+pub struct ConstraintSigner {}
+
+pub struct ConstraintLiteral {
+    pub tokens: proc_macro2::TokenStream,
+}
+
+pub enum ConstraintOwner {
+    Program,
+    Skip,
+}

+ 178 - 0
syn/src/parser/anchor.rs

@@ -0,0 +1,178 @@
+use crate::{
+    AccountsStruct, Constraint, ConstraintBelongsTo, ConstraintLiteral, ConstraintOwner,
+    ConstraintSigner, Field, ProgramAccountTy, Ty,
+};
+use quote::quote;
+
+pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
+    let fields = match &strct.fields {
+        syn::Fields::Named(fields) => fields,
+        _ => panic!("invalid input"),
+    };
+
+    let fields: Vec<Field> = fields
+        .named
+        .iter()
+        .map(|f: &syn::Field| {
+            let anchor_attr = {
+                let anchor_attrs: Vec<&syn::Attribute> = f
+                    .attrs
+                    .iter()
+                    .filter_map(|attr: &syn::Attribute| {
+                        if attr.path.segments.len() != 1 {
+                            return None;
+                        }
+                        if attr.path.segments[0].ident.to_string() != "account" {
+                            return None;
+                        }
+                        Some(attr)
+                    })
+                    .collect();
+                assert!(anchor_attrs.len() == 1);
+                anchor_attrs[0]
+            };
+            parse_field(f, anchor_attr)
+        })
+        .collect();
+
+    AccountsStruct::new(strct.clone(), fields)
+}
+
+// Parses an inert #[anchor] attribute specifying the DSL.
+fn parse_field(f: &syn::Field, anchor: &syn::Attribute) -> Field {
+    let ident = f.ident.clone().unwrap();
+    let ty = parse_ty(f);
+    let (constraints, is_mut, is_signer) = parse_constraints(anchor);
+    Field {
+        ident,
+        ty,
+        constraints,
+        is_mut,
+        is_signer,
+    }
+}
+
+fn parse_ty(f: &syn::Field) -> Ty {
+    let path = match &f.ty {
+        syn::Type::Path(ty_path) => ty_path.path.clone(),
+        _ => panic!("invalid type"),
+    };
+    // TODO: allow segmented paths.
+    assert!(path.segments.len() == 1);
+    let segments = &path.segments[0];
+    match segments.ident.to_string().as_str() {
+        "ProgramAccount" => Ty::ProgramAccount(parse_program_account(&path)),
+        "AccountInfo" => Ty::AccountInfo,
+        _ => panic!("invalid type"),
+    }
+}
+
+fn parse_program_account(path: &syn::Path) -> ProgramAccountTy {
+    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(ty) => match ty {
+                    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"),
+            }
+        }
+        _ => panic!("Invalid ProgramAccount"),
+    };
+    ProgramAccountTy { account_ident }
+}
+
+fn parse_constraints(anchor: &syn::Attribute) -> (Vec<Constraint>, bool, bool) {
+    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_mut = false;
+    let mut is_signer = false;
+    let mut constraints = vec![];
+    let mut has_owner_constraint = false;
+
+    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() {
+                "mut" => {
+                    is_mut = true;
+                }
+                "signer" => {
+                    is_signer = true;
+                    constraints.push(Constraint::Signer(ConstraintSigner {}));
+                }
+                "belongs_to" => {
+                    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 = match inner_tts.next().unwrap() {
+                        proc_macro2::TokenTree::Ident(ident) => ident,
+                        _ => panic!("invalid syntax"),
+                    };
+                    let constraint = match owner.to_string().as_str() {
+                        "program" => ConstraintOwner::Program,
+                        "skip" => ConstraintOwner::Skip,
+                        _ => panic!("invalid syntax"),
+                    };
+                    constraints.push(Constraint::Owner(constraint));
+                    has_owner_constraint = true;
+                }
+                _ => {
+                    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 no owner constraint was specified, default to it being the current
+    // program.
+    if !has_owner_constraint {
+        constraints.push(Constraint::Owner(ConstraintOwner::Program));
+    }
+
+    (constraints, is_mut, is_signer)
+}

+ 184 - 0
syn/src/parser/file.rs

@@ -0,0 +1,184 @@
+use crate::idl::*;
+use crate::parser::anchor;
+use crate::parser::program;
+use crate::AccountsStruct;
+use anyhow::Result;
+use quote::ToTokens;
+use std::collections::{HashMap, HashSet};
+use std::fs::File;
+use std::io::Read;
+
+static DERIVE_NAME: &'static str = "Accounts";
+
+// Parse an entire interface file.
+pub fn parse(filename: &str) -> Result<Idl> {
+    let mut file = File::open(&filename)?;
+
+    let mut src = String::new();
+    file.read_to_string(&mut src).expect("Unable to read file");
+
+    let f = syn::parse_file(&src).expect("Unable to parse file");
+
+    let p = program::parse(parse_program_mod(&f));
+
+    let accs = parse_accounts(&f);
+
+    let acc_names = {
+        let mut acc_names = HashSet::new();
+
+        for accs_strct in accs.values() {
+            for a in accs_strct.account_tys() {
+                acc_names.insert(a);
+            }
+        }
+
+        acc_names
+    };
+
+    let methods = p
+        .rpcs
+        .iter()
+        .map(|rpc| {
+            let args = rpc
+                .args
+                .iter()
+                .map(|arg| {
+                    let mut tts = proc_macro2::TokenStream::new();
+                    arg.raw_arg.ty.to_tokens(&mut tts);
+                    let ty = tts.to_string().parse().unwrap();
+                    IdlField {
+                        name: arg.name.to_string(),
+                        ty,
+                    }
+                })
+                .collect::<Vec<_>>();
+            // todo: don't 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(),
+                    is_mut: acc.is_mut,
+                    is_signer: acc.is_signer,
+                })
+                .collect::<Vec<_>>();
+            IdlMethod {
+                name: rpc.ident.to_string(),
+                accounts,
+                args,
+            }
+        })
+        .collect::<Vec<_>>();
+
+    // All user defined types.
+    let mut accounts = vec![];
+    let mut types = vec![];
+    let ty_defs = parse_ty_defs(&f)?;
+    for ty_def in ty_defs {
+        let name = match &ty_def {
+            IdlTypeDef::Struct { name, .. } => name,
+            IdlTypeDef::Enum { name, .. } => name,
+        };
+        if acc_names.contains(name) {
+            accounts.push(ty_def);
+        } else {
+            types.push(ty_def);
+        }
+    }
+
+    Ok(Idl {
+        version: "0.0.0".to_string(),
+        name: p.name.to_string(),
+        methods,
+        types,
+        accounts,
+    })
+}
+
+// Parse the main program mod.
+fn parse_program_mod(f: &syn::File) -> syn::ItemMod {
+    let mods = f
+        .items
+        .iter()
+        .filter_map(|i| match i {
+            syn::Item::Mod(item_mod) => {
+                let mods = item_mod
+                    .attrs
+                    .iter()
+                    .filter_map(|attr| {
+                        let segment = attr.path.segments.last().unwrap();
+                        if segment.ident.to_string() == "program" {
+                            return Some(attr);
+                        }
+                        None
+                    })
+                    .collect::<Vec<_>>();
+                if mods.len() != 1 {
+                    panic!("invalid program attribute");
+                }
+                Some(item_mod)
+            }
+            _ => None,
+        })
+        .collect::<Vec<_>>();
+    assert!(mods.len() == 1);
+    mods[0].clone()
+}
+
+// Parse all structs deriving the `Accounts` macro.
+fn parse_accounts(f: &syn::File) -> HashMap<String, AccountsStruct> {
+    f.items
+        .iter()
+        .filter_map(|i| match i {
+            syn::Item::Struct(i_strct) => {
+                for attr in &i_strct.attrs {
+                    if attr.tokens.to_string().contains(DERIVE_NAME) {
+                        let strct = anchor::parse(i_strct);
+                        return Some((strct.ident.to_string(), strct));
+                    }
+                }
+                None
+            }
+            _ => None,
+        })
+        .collect()
+}
+
+// Parse all user defined types in the file.
+fn parse_ty_defs(f: &syn::File) -> Result<Vec<IdlTypeDef>> {
+    f.items
+        .iter()
+        .filter_map(|i| match i {
+            syn::Item::Struct(item_strct) => {
+                for attr in &item_strct.attrs {
+                    if attr.tokens.to_string().contains(DERIVE_NAME) {
+                        return None;
+                    }
+                }
+                if let syn::Visibility::Public(_) = &item_strct.vis {
+                    let name = item_strct.ident.to_string();
+                    let fields = match &item_strct.fields {
+                        syn::Fields::Named(fields) => fields
+                            .named
+                            .iter()
+                            .map(|f| {
+                                let mut tts = proc_macro2::TokenStream::new();
+                                f.ty.to_tokens(&mut tts);
+                                Ok(IdlField {
+                                    name: f.ident.as_ref().unwrap().to_string(),
+                                    ty: tts.to_string().parse()?,
+                                })
+                            })
+                            .collect::<Result<Vec<IdlField>>>(),
+                        _ => panic!("Only named structs are allowed."),
+                    };
+
+                    return Some(fields.map(|fields| IdlTypeDef::Struct { name, fields }));
+                }
+                None
+            }
+            _ => None,
+        })
+        .collect()
+}

+ 3 - 0
syn/src/parser/mod.rs

@@ -0,0 +1,3 @@
+pub mod anchor;
+pub mod file;
+pub mod program;

+ 78 - 0
syn/src/parser/program.rs

@@ -0,0 +1,78 @@
+use crate::{Program, Rpc, RpcArg};
+
+pub fn parse(program_mod: syn::ItemMod) -> Program {
+    let mod_ident = &program_mod.ident;
+    let methods: Vec<&syn::ItemFn> = program_mod
+        .content
+        .as_ref()
+        .unwrap()
+        .1
+        .iter()
+        .filter_map(|item| match item {
+            syn::Item::Fn(item_fn) => Some(item_fn),
+            _ => None,
+        })
+        .collect();
+
+    let rpcs: Vec<Rpc> = methods
+        .clone()
+        .into_iter()
+        .map(|method: &syn::ItemFn| {
+            let mut args: Vec<RpcArg> = 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"),
+                        };
+                        RpcArg {
+                            name: ident.clone(),
+                            raw_arg: arg.clone(),
+                        }
+                    }
+                    _ => panic!("invalid syntax"),
+                })
+                .collect();
+            // Remove the Anchor accounts argument
+            let anchor = args.remove(0);
+            let anchor_ident = extract_ident(&anchor.raw_arg).clone();
+
+            Rpc {
+                raw_method: method.clone(),
+                ident: method.sig.ident.clone(),
+                args,
+                anchor_ident,
+            }
+        })
+        .collect();
+
+    Program {
+        rpcs,
+        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 path = match &generic_args.args.first().unwrap() {
+        syn::GenericArgument::Type(ty) => match ty {
+            syn::Type::Path(ty_path) => &ty_path.path,
+            _ => panic!("invalid syntax"),
+        },
+        _ => panic!("invalid syntax"),
+    };
+
+    &path.segments[0].ident
+}