Browse Source

Account discriminators (#14)

Armani Ferrante 4 years ago
parent
commit
de353cb4e4

+ 1 - 1
.travis.yml

@@ -21,7 +21,7 @@ _examples: &examples
   - export PATH="/home/travis/.local/share/solana/install/active_release/bin:$PATH"
   - export PATH="/home/travis/.local/share/solana/install/active_release/bin:$PATH"
   - export NODE_PATH="/home/travis/.nvm/versions/node/v$NODE_VERSION/lib/node_modules/:$NODE_PATH"
   - export NODE_PATH="/home/travis/.nvm/versions/node/v$NODE_VERSION/lib/node_modules/:$NODE_PATH"
   - yes | solana-keygen new
   - yes | solana-keygen new
-  - cargo install --git https://github.com/project-serum/anchor anchor-cli
+  - cargo install --git https://github.com/project-serum/anchor anchor-cli --locked
 
 
 jobs:
 jobs:
   include:
   include:

+ 18 - 6
Cargo.lock

@@ -47,9 +47,10 @@ dependencies = [
 name = "anchor"
 name = "anchor"
 version = "0.1.0"
 version = "0.1.0"
 dependencies = [
 dependencies = [
- "anchor-attributes-access-control",
- "anchor-attributes-program",
- "anchor-derive",
+ "anchor-attribute-access-control",
+ "anchor-attribute-account",
+ "anchor-attribute-program",
+ "anchor-derive-accounts",
  "borsh",
  "borsh",
  "solana-program",
  "solana-program",
  "solana-sdk",
  "solana-sdk",
@@ -57,7 +58,7 @@ dependencies = [
 ]
 ]
 
 
 [[package]]
 [[package]]
-name = "anchor-attributes-access-control"
+name = "anchor-attribute-access-control"
 version = "0.1.0"
 version = "0.1.0"
 dependencies = [
 dependencies = [
  "anchor-syn",
  "anchor-syn",
@@ -68,7 +69,18 @@ dependencies = [
 ]
 ]
 
 
 [[package]]
 [[package]]
-name = "anchor-attributes-program"
+name = "anchor-attribute-account"
+version = "0.1.0"
+dependencies = [
+ "anchor-syn",
+ "anyhow",
+ "proc-macro2 1.0.24",
+ "quote 1.0.8",
+ "syn 1.0.54",
+]
+
+[[package]]
+name = "anchor-attribute-program"
 version = "0.1.0"
 version = "0.1.0"
 dependencies = [
 dependencies = [
  "anchor-syn",
  "anchor-syn",
@@ -99,7 +111,7 @@ dependencies = [
 ]
 ]
 
 
 [[package]]
 [[package]]
-name = "anchor-derive"
+name = "anchor-derive-accounts"
 version = "0.1.0"
 version = "0.1.0"
 dependencies = [
 dependencies = [
  "anchor-syn",
  "anchor-syn",

+ 6 - 5
Cargo.toml

@@ -13,15 +13,16 @@ default = []
 thiserror = "1.0.20"
 thiserror = "1.0.20"
 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-derive = { path = "./derive" }
-anchor-attributes-program = { path = "./attributes/program" }
-anchor-attributes-access-control = { path = "./attributes/access-control" }
+anchor-derive-accounts = { path = "./derive/accounts" }
+anchor-attribute-program = { path = "./attribute/program" }
+anchor-attribute-access-control = { path = "./attribute/access-control" }
+anchor-attribute-account = { path = "./attribute/account" }
 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"] }
 
 
 [workspace]
 [workspace]
 members = [
 members = [
     "cli",
     "cli",
     "syn",
     "syn",
-    "attributes/*",
-    "derive",
+    "attribute/*",
+    "derive/*",
 ]
 ]

+ 1 - 0
README.md

@@ -91,6 +91,7 @@ purposes of the Accounts macro) that can be specified on a struct deriving `Acco
 |:--|:--|:--|
 |:--|:--|:--|
 | `#[account(signer)]` | On raw `AccountInfo` structs. | Checks the given account signed the transaction. |
 | `#[account(signer)]` | On raw `AccountInfo` structs. | Checks the given account signed the transaction. |
 | `#[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(belongs_to = <target>)]` | On `ProgramAccount` structs | Checks the `target` field on the account matches the `target` field in the accounts array. |
 | `#[account(belongs_to = <target>)]` | On `ProgramAccount` structs | Checks the `target` field on the account matches the `target` field in the accounts array. |
 | `#[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 `ProgramAccount` and `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. |

+ 1 - 1
attributes/program/Cargo.toml → attribute/access-control/Cargo.toml

@@ -1,5 +1,5 @@
 [package]
 [package]
-name = "anchor-attributes-program"
+name = "anchor-attribute-access-control"
 version = "0.1.0"
 version = "0.1.0"
 authors = ["armaniferrante <armaniferrante@gmail.com>"]
 authors = ["armaniferrante <armaniferrante@gmail.com>"]
 edition = "2018"
 edition = "2018"

+ 3 - 0
attributes/access-control/src/lib.rs → attribute/access-control/src/lib.rs

@@ -3,6 +3,9 @@ extern crate proc_macro;
 use quote::quote;
 use quote::quote;
 use syn::parse_macro_input;
 use syn::parse_macro_input;
 
 
+/// Executes the given access control method before running the decorated
+/// instruction handler. Any method in scope of the attribute can be invoked
+/// with any arguments from the associated instruction handler.
 #[proc_macro_attribute]
 #[proc_macro_attribute]
 pub fn access_control(
 pub fn access_control(
     args: proc_macro::TokenStream,
     args: proc_macro::TokenStream,

+ 2 - 2
attributes/access-control/Cargo.toml → attribute/account/Cargo.toml

@@ -1,5 +1,5 @@
 [package]
 [package]
-name = "anchor-attributes-access-control"
+name = "anchor-attribute-account"
 version = "0.1.0"
 version = "0.1.0"
 authors = ["armaniferrante <armaniferrante@gmail.com>"]
 authors = ["armaniferrante <armaniferrante@gmail.com>"]
 edition = "2018"
 edition = "2018"
@@ -12,4 +12,4 @@ proc-macro2 = "1.0"
 quote = "1.0"
 quote = "1.0"
 syn = { version = "1.0.54", features = ["full"] }
 syn = { version = "1.0.54", features = ["full"] }
 anyhow = "1.0.32"
 anyhow = "1.0.32"
-anchor-syn = { path = "../../syn" }
+anchor-syn = { path = "../../syn" }

+ 75 - 0
attribute/account/src/lib.rs

@@ -0,0 +1,75 @@
+extern crate proc_macro;
+
+use quote::quote;
+use syn::parse_macro_input;
+
+/// A data structure representing a Solana account.
+#[proc_macro_attribute]
+pub fn account(
+    _args: proc_macro::TokenStream,
+    input: proc_macro::TokenStream,
+) -> proc_macro::TokenStream {
+    let account_strct = parse_macro_input!(input as syn::ItemStruct);
+    let account_name = &account_strct.ident;
+    // Namespace the discriminator to prevent future collisions, e.g.,
+    // if we (for some unforseen reason) wanted to hash other parts of the
+    // program.
+    let discriminator_preimage = format!("account:{}", account_name.to_string());
+
+    let coder = quote! {
+        impl anchor::AccountSerialize for #account_name {
+            fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<(), ProgramError> {
+                // TODO: we shouldn't have to hash at runtime. However, rust
+                //       is not happy when trying to include solana-sdk from
+                //       the proc-macro crate.
+                let mut discriminator = [0u8; 8];
+                discriminator.copy_from_slice(
+                    &solana_program::hash::hash(
+                        #discriminator_preimage.as_bytes(),
+                    ).to_bytes()[..8],
+                );
+
+                writer.write_all(&discriminator).map_err(|_| ProgramError::InvalidAccountData)?;
+                AnchorSerialize::serialize(
+                    self,
+                    writer
+                )
+                    .map_err(|_| ProgramError::InvalidAccountData)?;
+                Ok(())
+            }
+        }
+
+        impl anchor::AccountDeserialize for #account_name {
+            fn try_deserialize(buf: &mut &[u8]) -> Result<Self, ProgramError> {
+                let mut discriminator = [0u8; 8];
+                discriminator.copy_from_slice(
+                    &solana_program::hash::hash(
+                        #discriminator_preimage.as_bytes(),
+                    ).to_bytes()[..8],
+                );
+
+                if buf.len() < discriminator.len() {
+                    return Err(ProgramError::AccountDataTooSmall);
+                }
+                let given_disc = &buf[..8];
+                if &discriminator != given_disc {
+                    return Err(ProgramError::InvalidInstructionData);
+                }
+                Self::try_deserialize_unchecked(buf)
+            }
+
+            fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError> {
+                let mut data: &[u8] = &buf[8..];
+                AnchorDeserialize::deserialize(&mut data)
+                    .map_err(|_| ProgramError::InvalidAccountData)
+            }
+        }
+    };
+
+    proc_macro::TokenStream::from(quote! {
+        #[derive(AnchorSerialize, AnchorDeserialize)]
+        #account_strct
+
+        #coder
+    })
+}

+ 15 - 0
attribute/program/Cargo.toml

@@ -0,0 +1,15 @@
+[package]
+name = "anchor-attribute-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" }

+ 2 - 0
attributes/program/src/lib.rs → attribute/program/src/lib.rs

@@ -4,6 +4,8 @@ use anchor_syn::codegen::program as program_codegen;
 use anchor_syn::parser::program as program_parser;
 use anchor_syn::parser::program as program_parser;
 use syn::parse_macro_input;
 use syn::parse_macro_input;
 
 
+/// The module containing all instruction handlers defining all entries to the
+/// Solana program.
 #[proc_macro_attribute]
 #[proc_macro_attribute]
 pub fn program(
 pub fn program(
     _args: proc_macro::TokenStream,
     _args: proc_macro::TokenStream,

+ 2 - 2
derive/Cargo.toml → derive/accounts/Cargo.toml

@@ -1,5 +1,5 @@
 [package]
 [package]
-name = "anchor-derive"
+name = "anchor-derive-accounts"
 version = "0.1.0"
 version = "0.1.0"
 authors = ["armaniferrante <armaniferrante@gmail.com>"]
 authors = ["armaniferrante <armaniferrante@gmail.com>"]
 edition = "2018"
 edition = "2018"
@@ -12,4 +12,4 @@ proc-macro2 = "1.0"
 quote = "1.0"
 quote = "1.0"
 syn = { version = "1.0.54", features = ["full"] }
 syn = { version = "1.0.54", features = ["full"] }
 anyhow = "1.0.32"
 anyhow = "1.0.32"
-anchor-syn = { path = "../syn" }
+anchor-syn = { path = "../../syn" }

+ 0 - 0
derive/src/lib.rs → derive/accounts/src/lib.rs


+ 1 - 1
docs/src/getting-started/installation.md

@@ -34,7 +34,7 @@ npm install -g mocha
 For now, we can use Cargo to install the CLI.
 For now, we can use Cargo to install the CLI.
 
 
 ```bash
 ```bash
-cargo install --git https://github.com/project-serum/anchor anchor-cli
+cargo install --git https://github.com/project-serum/anchor anchor-cli --locked
 ```
 ```
 
 
 On Linux systems you may need to install additional dependencies. On Ubuntu,
 On Linux systems you may need to install additional dependencies. On Ubuntu,

+ 1 - 1
docs/src/tutorials/tutorial-0.md

@@ -100,7 +100,7 @@ Once built, we can deploy the program by running
 anchor deploy
 anchor deploy
 ```
 ```
 
 
-Take note of program's deployed address. We'll use it next.
+Take note of the program's deployed address. We'll use it next.
 
 
 ## Generating a Client
 ## Generating a Client
 
 

+ 47 - 16
docs/src/tutorials/tutorial-1.md

@@ -1,4 +1,4 @@
-# Tutorial 1: Accounts, Arguments, and Types
+# Tutorial 1: Arguments and Accounts
 
 
 This tutorial covers the basics of creating and mutating accounts using Anchor.
 This tutorial covers the basics of creating and mutating accounts using Anchor.
 It's recommended to read [Tutorial 0](./tutorial-0.md) first, as this tutorial will
 It's recommended to read [Tutorial 0](./tutorial-0.md) first, as this tutorial will
@@ -22,25 +22,56 @@ cd anchor/examples/tutorial/basic-1
 
 
 We define our program as follows
 We define our program as follows
 
 
-<<< @/../examples/tutorial/basic-1/programs/basic-1/src/lib.rs#program
+<<< @/../examples/tutorial/basic-1/programs/basic-1/src/lib.rs
 
 
 Some new syntax elements are introduced here.
 Some new syntax elements are introduced here.
 
 
-First, notice the `data` argument passed into the program. This argument and any other valid
-Rust types can be passed to the instruction to define inputs to the program. If you'd like to
-pass in your own type, then it must be defined in the same `src/lib.rs` file as the
-`#[program]` module (so that the IDL can pick it up). Additionally,
+### `initialize` instruction
+
+First, let's start with the initialize instruction. Notice the `data` argument passed into the program. This argument and any other valid
+Rust types can be passed to the instruction to define inputs to the program.
+
+::: tip
+If you'd like to pass in your own type as an input to an instruction handler, then it must be
+defined in the same `src/lib.rs` file as the `#[program]` module, so that the IDL parser can
+pick it up.
+:::
+
+Additionally,
 notice how we take a mutable reference to `my_account` and assign the `data` to it. This leads us
 notice how we take a mutable reference to `my_account` and assign the `data` to it. This leads us
-the `Initialize` struct, deriving `Accounts`.
+the `Initialize` struct, deriving `Accounts`. There are two things to notice about `Initialize`.
+
+1. The `my_account` field is of type `ProgramAccount<'info, MyAccount>`, telling the program it *must*
+be **owned** by the currently executing program, and the deserialized data structure is `MyAccount`.
+2. The `my_account` field is marked with the `#[account(init)]` attribute. This should be used
+in one situation: when a given `ProgramAccount` is newly created and is being used by the program
+for the first time (and thus it's data field is all zero). If `#[account(init)]` is not used
+when account data is zero initialized, the transaction will be rejected.
+
+::: details
+All accounts created with Anchor are laid out as follows: `8-byte-discriminator || borsh
+serialized data`. The 8-byte-discriminator is created from the first 8 bytes of the
+`Sha256` hash of the account's type--using the example above, `sha256("MyAccount")[..8]`.
+
+Importantly, this allows a program to know for certain an account is indeed of a given type.
+Without it, a program would be vulnerable to account injection attacks, where a malicious user
+specifies an account of an unexpected type, causing the program to do unexpected things.
+
+On account creation, this 8-byte discriminator doesn't exist, since the account storage is
+zeroed. The first time an Anchor program mutates an account, this discriminator is prepended
+to the account storage array and all subsequent accesses to the account (not decorated with
+`#[account(init)]`) will check for this discriminator.
+:::
+
+### `update` instruction
 
 
-There are two things to notice about `Initialize`. First, the
-`my_account` field is marked with the `#[account(mut)]` attribute. This means that any
-changes to the field will be persisted upon exiting the program. Second, the field is of
-type `ProgramAccount<'info, MyAccount>`, telling the program it *must* be **owned**
-by the currently executing program and the deserialized data structure is `MyAccount`.
+Similarly, the `Update` accounts struct is marked  with the `#[account(mut)]` attribute.
+Marking an account as `mut` persists any changes made upon exiting the program.
 
 
-In a later tutorial we'll delve more deeply into deriving `Accounts`. For now, just know
-one must mark their accounts `mut` if they want them to, well, mutate. ;)
+Here we've covered the basics of how to interact with accounts. In a later tutorial,
+we'll delve more deeply into deriving `Accounts`, but for now, just know
+one must mark their accounts `init` when using an account for the first time and `mut`
+for persisting changes.
 
 
 ## Creating and Initializing Accounts
 ## Creating and Initializing Accounts
 
 
@@ -74,8 +105,8 @@ which in this case is `initialize`. Because we are creating `myAccount`, it need
 sign the transaction, as required by the Solana runtime.
 sign the transaction, as required by the Solana runtime.
 
 
 ::: details
 ::: details
-In future work, we might want to add something like a *Builder* pattern for constructing
-common transactions like creating and then initializing an account.
+In future work, we can simplify this example further by using something like a *Builder*
+pattern for constructing common transactions like creating and then initializing an account.
 :::
 :::
 
 
 As before, we can run the example tests.
 As before, we can run the example tests.

+ 11 - 4
docs/src/tutorials/tutorial-2.md

@@ -1,11 +1,18 @@
 # Tutorial 2: Account Constraints and Access Control
 # Tutorial 2: Account Constraints and Access Control
 
 
-Building on the previous two, this tutorial covers how to specify constraints and access control
-on accounts.
+This tutorial covers how to specify constraints and access control on accounts.
 
 
-Because Solana programs 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.
+Because Solana programs 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.
 
 
-For example, one could imagine easily writing a faulty token program that forgets to check if the signer of a transaction claiming to be 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 conditions. Instead, one can use the Anchor DSL to do these checks by specifying **constraints** when deriving `Accounts`.
+For example, one could imagine easily writing a faulty token program that forgets to check if the signer of a transaction claiming to be the owner of a token account actually matches the owner on the account. A simpler question that must be asked: what happens if the program expects a `Mint` account but a `Token` account is given instead?
+
+
+Doing these checks 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.
+Instead, one can use the Anchor DSL to do these checks by specifying **constraints** when deriving
+`Accounts`. We briefly touched on the most basic (and important) type of account constraint in the
+[previous tutorial](./tutorial-1.md), the account discriminator. Here, we demonstrate others.
 
 
 ## Clone the Repo
 ## Clone the Repo
 
 

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

@@ -11,15 +11,27 @@ mod basic_1 {
         my_account.data = data;
         my_account.data = data;
         Ok(())
         Ok(())
     }
     }
+
+    pub fn update(ctx: Context<Update>, data: u64) -> ProgramResult {
+        let my_account = &mut ctx.accounts.my_account;
+        my_account.data = data;
+        Ok(())
+    }
 }
 }
 
 
 #[derive(Accounts)]
 #[derive(Accounts)]
 pub struct Initialize<'info> {
 pub struct Initialize<'info> {
+    #[account(init)]
+    pub my_account: ProgramAccount<'info, MyAccount>,
+}
+
+#[derive(Accounts)]
+pub struct Update<'info> {
     #[account(mut)]
     #[account(mut)]
     pub my_account: ProgramAccount<'info, MyAccount>,
     pub my_account: ProgramAccount<'info, MyAccount>,
 }
 }
 
 
-#[derive(AnchorSerialize, AnchorDeserialize)]
+#[account]
 pub struct MyAccount {
 pub struct MyAccount {
     pub data: u64,
     pub data: u64,
 }
 }

+ 34 - 4
examples/tutorial/basic-1/tests/basic-1.js

@@ -22,8 +22,8 @@ describe('basic-1', () => {
       anchor.web3.SystemProgram.createAccount({
       anchor.web3.SystemProgram.createAccount({
         fromPubkey: provider.wallet.publicKey,
         fromPubkey: provider.wallet.publicKey,
         newAccountPubkey: myAccount.publicKey,
         newAccountPubkey: myAccount.publicKey,
-        space: 8,
-        lamports: await provider.connection.getMinimumBalanceForRentExemption(8),
+        space: 8+8,
+        lamports: await provider.connection.getMinimumBalanceForRentExemption(8+8),
         programId: program.programId,
         programId: program.programId,
       }),
       }),
     );
     );
@@ -47,6 +47,8 @@ describe('basic-1', () => {
     assert.ok(account.data.eq(new anchor.BN(1234)));
     assert.ok(account.data.eq(new anchor.BN(1234)));
   });
   });
 
 
+  // Reference to an account to use between multiple tests.
+  let _myAccount = undefined;
 
 
   it('Creates and initializes an account in a single atomic transaction', async () => {
   it('Creates and initializes an account in a single atomic transaction', async () => {
     // The program to execute.
     // The program to execute.
@@ -66,8 +68,8 @@ describe('basic-1', () => {
         anchor.web3.SystemProgram.createAccount({
         anchor.web3.SystemProgram.createAccount({
           fromPubkey: provider.wallet.publicKey,
           fromPubkey: provider.wallet.publicKey,
           newAccountPubkey: myAccount.publicKey,
           newAccountPubkey: myAccount.publicKey,
-          space: 8,
-          lamports: await provider.connection.getMinimumBalanceForRentExemption(8),
+          space: 8+8, // Add 8 for the account discriminator.
+          lamports: await provider.connection.getMinimumBalanceForRentExemption(8+8),
           programId: program.programId,
           programId: program.programId,
         }),
         }),
       ],
       ],
@@ -79,5 +81,33 @@ describe('basic-1', () => {
     // Check it's state was initialized.
     // Check it's state was initialized.
     assert.ok(account.data.eq(new anchor.BN(1234)));
     assert.ok(account.data.eq(new anchor.BN(1234)));
     // #endregion code
     // #endregion code
+
+    // Store the account for the next test.
+    _myAccount = myAccount;
+  });
+
+  it('Updates a previously created account', async () => {
+
+    const myAccount = _myAccount;
+
+    // #region update-test
+
+    // The program to execute.
+    const program = anchor.workspace.Basic1;
+
+    // Invoke the update rpc.
+    await program.rpc.update(new anchor.BN(4321), {
+      accounts: {
+        myAccount: myAccount.publicKey,
+      },
+    });
+
+    // Fetch the newly updated account.
+    const account = await program.account.myAccount(myAccount.publicKey);
+
+    // Check it's state was mutated.
+    assert.ok(account.data.eq(new anchor.BN(4321)));
+
+    // #endregion update-test
   });
   });
 });
 });

+ 7 - 8
examples/tutorial/basic-2/programs/basic-2/src/lib.rs

@@ -50,7 +50,7 @@ mod basic_2 {
 
 
 #[derive(Accounts)]
 #[derive(Accounts)]
 pub struct CreateRoot<'info> {
 pub struct CreateRoot<'info> {
-    #[account(mut, "!root.initialized")]
+    #[account(init)]
     pub root: ProgramAccount<'info, Root>,
     pub root: ProgramAccount<'info, Root>,
 }
 }
 
 
@@ -58,15 +58,14 @@ pub struct CreateRoot<'info> {
 pub struct UpdateRoot<'info> {
 pub struct UpdateRoot<'info> {
     #[account(signer)]
     #[account(signer)]
     pub authority: AccountInfo<'info>,
     pub authority: AccountInfo<'info>,
-    #[account(mut, "root.initialized", "&root.authority == authority.key")]
+    #[account(mut, "&root.authority == authority.key")]
     pub root: ProgramAccount<'info, Root>,
     pub root: ProgramAccount<'info, Root>,
 }
 }
 
 
 #[derive(Accounts)]
 #[derive(Accounts)]
 pub struct CreateLeaf<'info> {
 pub struct CreateLeaf<'info> {
-    #[account("root.initialized")]
     pub root: ProgramAccount<'info, Root>,
     pub root: ProgramAccount<'info, Root>,
-    #[account(mut, "!leaf.initialized")]
+    #[account(init)]
     pub leaf: ProgramAccount<'info, Leaf>,
     pub leaf: ProgramAccount<'info, Leaf>,
 }
 }
 
 
@@ -74,22 +73,22 @@ pub struct CreateLeaf<'info> {
 pub struct UpdateLeaf<'info> {
 pub struct UpdateLeaf<'info> {
     #[account(signer)]
     #[account(signer)]
     pub authority: AccountInfo<'info>,
     pub authority: AccountInfo<'info>,
-    #[account("root.initialized", "&root.authority == authority.key")]
+    #[account("&root.authority == authority.key")]
     pub root: ProgramAccount<'info, Root>,
     pub root: ProgramAccount<'info, Root>,
-    #[account(mut, belongs_to = root, "leaf.initialized")]
+    #[account(mut, belongs_to = root)]
     pub leaf: ProgramAccount<'info, Leaf>,
     pub leaf: ProgramAccount<'info, Leaf>,
 }
 }
 
 
 // Define the program owned accounts.
 // Define the program owned accounts.
 
 
-#[derive(AnchorSerialize, AnchorDeserialize)]
+#[account]
 pub struct Root {
 pub struct Root {
     pub initialized: bool,
     pub initialized: bool,
     pub authority: Pubkey,
     pub authority: Pubkey,
     pub data: u64,
     pub data: u64,
 }
 }
 
 
-#[derive(AnchorSerialize, AnchorDeserialize)]
+#[account]
 pub struct Leaf {
 pub struct Leaf {
     pub initialized: bool,
     pub initialized: bool,
     pub root: Pubkey,
     pub root: Pubkey,

+ 86 - 13
src/lib.rs

@@ -1,25 +1,99 @@
 use solana_sdk::account_info::AccountInfo;
 use solana_sdk::account_info::AccountInfo;
 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::ops::{Deref, DerefMut};
 use std::ops::{Deref, DerefMut};
 
 
-pub use anchor_attributes_access_control::access_control;
-pub use anchor_attributes_program::program;
-pub use anchor_derive::Accounts;
+pub use anchor_attribute_access_control::access_control;
+pub use anchor_attribute_account::account;
+pub use anchor_attribute_program::program;
+pub use anchor_derive_accounts::Accounts;
 pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize};
 pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize};
 
 
-pub struct ProgramAccount<'a, T: AnchorSerialize + AnchorDeserialize> {
+/// A data structure of Solana accounts that can be deserialized from the input
+/// of a Solana program. Due to the freewheeling nature of the accounts array,
+/// implementations of this trait should perform any and all constraint checks
+/// (in addition to any done within `AccountDeserialize`) on accounts to ensure
+/// the accounts maintain any invariants required for the program to run
+/// securely.
+pub trait Accounts<'info>: Sized {
+    fn try_accounts(program_id: &Pubkey, from: &[AccountInfo<'info>])
+        -> Result<Self, ProgramError>;
+}
+
+/// A data structure that can be serialized and stored in an `AccountInfo` data
+/// array.
+///
+/// Implementors of this trait should ensure that any subsequent usage the
+/// `AccountDeserialize` trait succeeds if and only if the account is of the
+/// correct type. For example, the implementation provided by the `#[account]`
+/// attribute sets the first 8 bytes to be a unique account discriminator,
+/// defined as the first 8 bytes of the SHA256 of the account's Rust ident.
+/// Thus, any subsequent  calls via `AccountDeserialize`'s `try_deserialize`
+/// will check this discriminator. If it doesn't match, an invalid account
+/// was given, and the program will exit with an error.
+pub trait AccountSerialize {
+    /// Serilalizes the account data into `writer`.
+    fn try_serialize<W: Write>(&self, writer: &mut W) -> Result<(), ProgramError>;
+}
+
+/// A data structure that can be deserialized from an `AccountInfo` data array.
+pub trait AccountDeserialize: Sized {
+    /// Deserializes the account data.
+    fn try_deserialize(buf: &mut &[u8]) -> Result<Self, ProgramError>;
+
+    /// Deserializes account data without checking the account discriminator.
+    /// This should only be used on account initialization, when the
+    /// discriminator is not yet set (since the entire account data is zeroed).
+    fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError>;
+}
+
+/// A container for a deserialized `account` and raw `AccountInfo` object.
+///
+/// Using this within a data structure deriving `Accounts` will ensure the
+/// account is owned by the currently executing program.
+pub struct ProgramAccount<'a, T: AccountSerialize + AccountDeserialize> {
     pub info: AccountInfo<'a>,
     pub info: AccountInfo<'a>,
     pub account: T,
     pub account: T,
 }
 }
 
 
-impl<'a, T: AnchorSerialize + AnchorDeserialize> ProgramAccount<'a, T> {
+impl<'a, T: AccountSerialize + AccountDeserialize> ProgramAccount<'a, T> {
     pub fn new(info: AccountInfo<'a>, account: T) -> ProgramAccount<'a, T> {
     pub fn new(info: AccountInfo<'a>, account: T) -> ProgramAccount<'a, T> {
         Self { info, account }
         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<'a, T: AnchorSerialize + AnchorDeserialize> Deref for ProgramAccount<'a, T> {
+impl<'a, T: AccountSerialize + AccountDeserialize> Deref for ProgramAccount<'a, T> {
     type Target = T;
     type Target = T;
 
 
     fn deref(&self) -> &Self::Target {
     fn deref(&self) -> &Self::Target {
@@ -27,16 +101,15 @@ impl<'a, T: AnchorSerialize + AnchorDeserialize> Deref for ProgramAccount<'a, T>
     }
     }
 }
 }
 
 
-impl<'a, T: AnchorSerialize + AnchorDeserialize> DerefMut for ProgramAccount<'a, T> {
+impl<'a, T: AccountSerialize + AccountDeserialize> DerefMut for ProgramAccount<'a, T> {
     fn deref_mut(&mut self) -> &mut Self::Target {
     fn deref_mut(&mut self) -> &mut Self::Target {
         &mut self.account
         &mut self.account
     }
     }
 }
 }
 
 
-pub trait Accounts<'info>: Sized {
-    fn try_anchor(program_id: &Pubkey, from: &[AccountInfo<'info>]) -> Result<Self, ProgramError>;
-}
-
+/// A data structure providing non-argument inputs to the Solana program, namely
+/// the currently executing program's ID and the set of validated, deserialized
+/// accounts.
 pub struct Context<'a, 'b, T> {
 pub struct Context<'a, 'b, T> {
     pub accounts: &'a mut T,
     pub accounts: &'a mut T,
     pub program_id: &'b Pubkey,
     pub program_id: &'b Pubkey,
@@ -44,8 +117,8 @@ pub struct Context<'a, 'b, T> {
 
 
 pub mod prelude {
 pub mod prelude {
     pub use super::{
     pub use super::{
-        access_control, program, Accounts, AnchorDeserialize, AnchorSerialize, Context,
-        ProgramAccount,
+        access_control, account, program, AccountDeserialize, AccountSerialize, Accounts,
+        AnchorDeserialize, AnchorSerialize, Context, ProgramAccount,
     };
     };
 
 
     pub use solana_program::msg;
     pub use solana_program::msg;

+ 14 - 16
syn/src/codegen/accounts.rs

@@ -52,8 +52,7 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
                     let mut data = self.#info.try_borrow_mut_data()?;
                     let mut data = self.#info.try_borrow_mut_data()?;
                     let dst: &mut [u8] = &mut data;
                     let dst: &mut [u8] = &mut data;
                     let mut cursor = std::io::Cursor::new(dst);
                     let mut cursor = std::io::Cursor::new(dst);
-                    self.#ident.account.serialize(&mut cursor)
-                        .map_err(|_| ProgramError::InvalidAccountData)?;
+                    self.#ident.account.try_serialize(&mut cursor)?;
                 },
                 },
             }
             }
         })
         })
@@ -70,7 +69,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_anchor(program_id: &Pubkey, accounts: &[AccountInfo<'info>]) -> Result<Self, ProgramError> {
+            fn try_accounts(program_id: &Pubkey, accounts: &[AccountInfo<'info>]) -> Result<Self, ProgramError> {
                 let acc_infos = &mut accounts.iter();
                 let acc_infos = &mut accounts.iter();
 
 
                 #(#acc_infos)*
                 #(#acc_infos)*
@@ -92,13 +91,7 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
     }
     }
 }
 }
 
 
-// Unpacks the field, if needed.
 pub fn generate_field(f: &Field) -> proc_macro2::TokenStream {
 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 ident = &f.ident;
     let assign_ty = match &f.ty {
     let assign_ty = match &f.ty {
         Ty::AccountInfo => quote! {
         Ty::AccountInfo => quote! {
@@ -106,16 +99,21 @@ pub fn generate_field(f: &Field) -> proc_macro2::TokenStream {
         },
         },
         Ty::ProgramAccount(acc) => {
         Ty::ProgramAccount(acc) => {
             let account_struct = &acc.account_ident;
             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)?
-                );
+            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)?;
+                },
             }
             }
         }
         }
     };
     };
+    let checks: Vec<proc_macro2::TokenStream> = f
+        .constraints
+        .iter()
+        .map(|c| generate_constraint(&f, c))
+        .collect();
     quote! {
     quote! {
         #assign_ty
         #assign_ty
         #(#checks)*
         #(#checks)*

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

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

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

@@ -10,7 +10,7 @@ pub fn generate(program: Program) -> proc_macro2::TokenStream {
     let instruction = generate_instruction(&program);
     let instruction = generate_instruction(&program);
 
 
     quote! {
     quote! {
-        // Import everything in the mod, in case the user wants to put anchors
+        // Import everything in the mod, in case the user wants to put types
         // in there.
         // in there.
         use #mod_name::*;
         use #mod_name::*;
 
 
@@ -61,7 +61,7 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
 
 
             quote! {
             quote! {
                 instruction::#variant_arm => {
                 instruction::#variant_arm => {
-                    let mut accounts = #anchor::try_anchor(program_id, accounts)?;
+                    let mut accounts = #anchor::try_accounts(program_id, accounts)?;
                     #program_name::#rpc_name(
                     #program_name::#rpc_name(
                         Context {
                         Context {
                             accounts: &mut accounts,
                             accounts: &mut accounts,

+ 1 - 0
syn/src/lib.rs

@@ -64,6 +64,7 @@ pub struct Field {
     pub constraints: Vec<Constraint>,
     pub constraints: Vec<Constraint>,
     pub is_mut: bool,
     pub is_mut: bool,
     pub is_signer: bool,
     pub is_signer: bool,
+    pub is_init: bool,
 }
 }
 
 
 // A type of an account field.
 // A type of an account field.

+ 18 - 6
syn/src/parser/accounts.rs

@@ -27,8 +27,11 @@ pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
                         Some(attr)
                         Some(attr)
                     })
                     })
                     .collect();
                     .collect();
-                assert!(anchor_attrs.len() == 1);
-                anchor_attrs[0]
+                match anchor_attrs.len() {
+                    0 => None,
+                    1 => Some(anchor_attrs[0]),
+                    _ => panic!("invalid syntax: only one account attribute is allowed"),
+                }
             };
             };
             parse_field(f, anchor_attr)
             parse_field(f, anchor_attr)
         })
         })
@@ -38,16 +41,20 @@ pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
 }
 }
 
 
 // Parses an inert #[anchor] attribute specifying the DSL.
 // Parses an inert #[anchor] attribute specifying the DSL.
-fn parse_field(f: &syn::Field, anchor: &syn::Attribute) -> Field {
+fn parse_field(f: &syn::Field, anchor: Option<&syn::Attribute>) -> Field {
     let ident = f.ident.clone().unwrap();
     let ident = f.ident.clone().unwrap();
     let ty = parse_ty(f);
     let ty = parse_ty(f);
-    let (constraints, is_mut, is_signer) = parse_constraints(anchor, &ty);
+    let (constraints, is_mut, is_signer, is_init) = match anchor {
+        None => (vec![], false, false, false),
+        Some(anchor) => parse_constraints(anchor, &ty),
+    };
     Field {
     Field {
         ident,
         ident,
         ty,
         ty,
         constraints,
         constraints,
         is_mut,
         is_mut,
         is_signer,
         is_signer,
+        is_init,
     }
     }
 }
 }
 
 
@@ -90,13 +97,14 @@ fn parse_program_account(path: &syn::Path) -> ProgramAccountTy {
     ProgramAccountTy { account_ident }
     ProgramAccountTy { account_ident }
 }
 }
 
 
-fn parse_constraints(anchor: &syn::Attribute, ty: &Ty) -> (Vec<Constraint>, bool, bool) {
+fn parse_constraints(anchor: &syn::Attribute, ty: &Ty) -> (Vec<Constraint>, bool, bool, bool) {
     let mut tts = anchor.tokens.clone().into_iter();
     let mut tts = anchor.tokens.clone().into_iter();
     let g_stream = match tts.next().expect("Must have a token group") {
     let g_stream = match tts.next().expect("Must have a token group") {
         proc_macro2::TokenTree::Group(g) => g.stream(),
         proc_macro2::TokenTree::Group(g) => g.stream(),
         _ => panic!("Invalid syntax"),
         _ => panic!("Invalid syntax"),
     };
     };
 
 
+    let mut is_init = false;
     let mut is_mut = false;
     let mut is_mut = false;
     let mut is_signer = false;
     let mut is_signer = false;
     let mut constraints = vec![];
     let mut constraints = vec![];
@@ -106,6 +114,10 @@ fn parse_constraints(anchor: &syn::Attribute, ty: &Ty) -> (Vec<Constraint>, bool
     while let Some(token) = inner_tts.next() {
     while let Some(token) = inner_tts.next() {
         match token {
         match token {
             proc_macro2::TokenTree::Ident(ident) => match ident.to_string().as_str() {
             proc_macro2::TokenTree::Ident(ident) => match ident.to_string().as_str() {
+                "init" => {
+                    is_init = true;
+                    is_mut = true;
+                }
                 "mut" => {
                 "mut" => {
                     is_mut = true;
                     is_mut = true;
                 }
                 }
@@ -175,5 +187,5 @@ fn parse_constraints(anchor: &syn::Attribute, ty: &Ty) -> (Vec<Constraint>, bool
         }
         }
     }
     }
 
 
-    (constraints, is_mut, is_signer)
+    (constraints, is_mut, is_signer, is_init)
 }
 }

+ 2 - 1
ts/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@project-serum/anchor",
   "name": "@project-serum/anchor",
-  "version": "0.0.0-alpha.1",
+  "version": "0.0.0-alpha.2",
   "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",
@@ -30,6 +30,7 @@
     "bn.js": "^5.1.2",
     "bn.js": "^5.1.2",
     "buffer-layout": "^1.2.0",
     "buffer-layout": "^1.2.0",
     "camelcase": "^5.3.1",
     "camelcase": "^5.3.1",
+    "crypto-hash": "^1.3.0",
     "find": "^0.3.0"
     "find": "^0.3.0"
   },
   },
   "devDependencies": {
   "devDependencies": {

+ 24 - 7
ts/src/rpc.ts

@@ -7,6 +7,7 @@ import {
   TransactionSignature,
   TransactionSignature,
   TransactionInstruction,
   TransactionInstruction,
 } from "@solana/web3.js";
 } from "@solana/web3.js";
+import { sha256 } from "crypto-hash";
 import { Idl, IdlInstruction } from "./idl";
 import { Idl, IdlInstruction } from "./idl";
 import { IdlError } from "./error";
 import { IdlError } from "./error";
 import Coder from "./coder";
 import Coder from "./coder";
@@ -35,24 +36,24 @@ export interface Accounts {
 }
 }
 
 
 /**
 /**
- * RpcFn is a single rpc method.
+ * RpcFn is a single rpc method generated from an IDL.
  */
  */
-export type RpcFn = (...args: any[]) => Promise<any>;
+export type RpcFn = (...args: any[]) => Promise<TransactionSignature>;
 
 
 /**
 /**
- * Ix is a function to create a `TransactionInstruction`.
+ * Ix is a function to create a `TransactionInstruction` generated from an IDL.
  */
  */
 export type IxFn = (...args: any[]) => TransactionInstruction;
 export type IxFn = (...args: any[]) => TransactionInstruction;
 
 
 /**
 /**
  * Account is a function returning a deserialized account, given an address.
  * Account is a function returning a deserialized account, given an address.
  */
  */
-export type AccountFn = (address: PublicKey) => any;
+export type AccountFn<T = any> = (address: PublicKey) => T;
 
 
 /**
 /**
  * Options for an RPC invocation.
  * Options for an RPC invocation.
  */
  */
-type RpcOptions = ConfirmOptions;
+export type RpcOptions = ConfirmOptions;
 
 
 /**
 /**
  * RpcContext provides all arguments for an RPC/IX invocation that are not
  * RpcContext provides all arguments for an RPC/IX invocation that are not
@@ -107,7 +108,6 @@ export class RpcFactory {
 
 
     if (idl.accounts) {
     if (idl.accounts) {
       idl.accounts.forEach((idlAccount) => {
       idl.accounts.forEach((idlAccount) => {
-        // todo
         const accountFn = async (address: PublicKey): Promise<any> => {
         const accountFn = async (address: PublicKey): Promise<any> => {
           const provider = getProvider();
           const provider = getProvider();
           if (provider === null) {
           if (provider === null) {
@@ -117,7 +117,24 @@ export class RpcFactory {
           if (accountInfo === null) {
           if (accountInfo === null) {
             throw new Error(`Entity does not exist ${address}`);
             throw new Error(`Entity does not exist ${address}`);
           }
           }
-          return coder.accounts.decode(idlAccount.name, accountInfo.data);
+
+          // Assert the account discriminator is correct.
+          const expectedDiscriminator = Buffer.from(
+            (
+              await sha256(`account:${idlAccount.name}`, {
+                outputFormat: "buffer",
+              })
+            ).slice(0, 8)
+          );
+          const discriminator = accountInfo.data.slice(0, 8);
+
+          if (expectedDiscriminator.compare(discriminator)) {
+            throw new Error("Invalid account discriminator");
+          }
+
+          // Chop off the discriminator before decoding.
+          const data = accountInfo.data.slice(8);
+          return coder.accounts.decode(idlAccount.name, data);
         };
         };
         const name = camelCase(idlAccount.name);
         const name = camelCase(idlAccount.name);
         accountFns[name] = accountFn;
         accountFns[name] = accountFn;

+ 1 - 1
ts/yarn.lock

@@ -1800,7 +1800,7 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2:
     shebang-command "^2.0.0"
     shebang-command "^2.0.0"
     which "^2.0.1"
     which "^2.0.1"
 
 
-crypto-hash@^1.2.2:
+crypto-hash@^1.2.2, crypto-hash@^1.3.0:
   version "1.3.0"
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/crypto-hash/-/crypto-hash-1.3.0.tgz#b402cb08f4529e9f4f09346c3e275942f845e247"
   resolved "https://registry.yarnpkg.com/crypto-hash/-/crypto-hash-1.3.0.tgz#b402cb08f4529e9f4f09346c3e275942f845e247"
   integrity sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg==
   integrity sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg==