Browse Source

CPI client generation (#19)

Armani Ferrante 4 years ago
parent
commit
92b5f74eea

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

@@ -67,7 +67,7 @@ pub fn account(
     };
 
     proc_macro::TokenStream::from(quote! {
-        #[derive(AnchorSerialize, AnchorDeserialize)]
+        #[derive(AnchorSerialize, AnchorDeserialize, Clone)]
         #account_strct
 
         #coder

+ 4 - 1
cli/src/template.rs

@@ -19,9 +19,12 @@ description = "Created with Anchor"
 edition = "2018"
 
 [lib]
-crate-type = ["cdylib"]
+crate-type = ["cdylib", "lib"]
 name = "{1}"
 
+[features]
+no-entrypoint = []
+
 [dependencies]
 borsh = {{ git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }}
 solana-program = "1.4.3"

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

@@ -5,6 +5,8 @@ use anchor_syn::parser::accounts as accounts_parser;
 use proc_macro::TokenStream;
 use syn::parse_macro_input;
 
+/// Implements an `Accounts` deserializer on the given struct, applying any
+/// constraints specified via `#[account]` attributes.
 #[proc_macro_derive(Accounts, attributes(account))]
 pub fn derive_anchor_deserialize(item: TokenStream) -> TokenStream {
     let strct = parse_macro_input!(item as syn::ItemStruct);

+ 5 - 4
docs/src/.vuepress/config.js

@@ -50,10 +50,11 @@ module.exports = {
         collapsable: false,
         title: "Tutorials",
 				children: [
-					"/tutorials/tutorial-0",
-					"/tutorials/tutorial-1",
-					"/tutorials/tutorial-2",
-				],
+          "/tutorials/tutorial-0",
+          "/tutorials/tutorial-1",
+          "/tutorials/tutorial-2",
+          "/tutorials/tutorial-3",
+        ],
       },
     ],
 

+ 6 - 0
docs/src/tutorials/tutorial-2.md

@@ -33,3 +33,9 @@ cd anchor/examples/tutorial/basic-2
 For now see the [source](https://github.com/project-serum/anchor/tree/master/examples/basic-2).
 
 TODO
+
+## Next Steps
+
+We've covered the basics for writing a single program using Anchor on Solana. But the power of
+blockchains come not from a single program, but from combining multiple *composable* programs
+(buzzword alert!). Next, we'll see how to call one program from another.

+ 74 - 0
docs/src/tutorials/tutorial-3.md

@@ -0,0 +1,74 @@
+# Tutorial 3: Cross Program Invocations (CPI)
+
+This tutorial covers how to call one program from another, a process known as
+*cross-program-invocation* (CPI).
+
+## Clone the Repo
+
+To get started, clone the repo.
+
+```bash
+git clone https://github.com/project-serum/anchor
+```
+
+And change directories to the [example](https://github.com/project-serum/anchor/tree/master/examples/tutorial/basic-2).
+
+```bash
+cd anchor/examples/tutorial/basic-3
+```
+
+## Defining a Puppet Program
+
+We start with the program that will be called by another program, the puppet.
+
+<<< @/../examples/tutorial/basic-3/programs/puppet/src/lib.rs
+
+If you've followed along the other tutorials, this should be straight forward. We have
+a program with two instructions, `initialize`, which does nothing other than the
+initialization of the account (remember, the program *transparently* prepends a unique 8
+byte discriminator the first time an account is used). and `set_data`, which takes a previously
+initialized account, and sets its data field.
+
+Now, suppose we wanted to call `set_data` from another program.
+
+## Defining a Puppet Master Program
+
+We define a new `puppet-master` crate, which successfully executes the Puppet program's `set_data`
+instruction via CPI.
+
+<<< @/../examples/tutorial/basic-3/programs/puppet-master/src/lib.rs#core
+
+Things to notice
+
+* We create a `CpiContext` object with the target instruction's accounts and program,
+  here `SetData` and `puppet_program`.
+* To invoke an instruction on another program, just use the `cpi` module on the crate, here, `puppet::cpi::set_data`.
+* Our `Accounts` struct has a new type, `CpiAccount`, containing the target program's `Puppet`
+  account. Think of `CpiAccount` exactly like `ProgramAccount`, except used for accounts *not*
+  owned by the current program.
+
+::: details
+When adding another Anchor program to your crate's `Cargo.toml`, make sure to specify the `no-entrypoint`
+feature. If you look at the `Cargo.toml` for this example, you'll see
+`puppet = { path = "../puppet", features = ["no-entrypoint"] }`.
+:::
+
+## Signer Seeds
+
+Often it's useful for a program to sign instructions. For example, if a program controls a token
+account and wants to send tokens to another account, it must sign. In Solana, this is done by specifying
+"signer seeds" on CPI (TODO: add link to docs). To do this using the example above, simply change
+`CpiContext::new(cpi_accounts, cpi_program)` to
+`CpiContext::new_with_signer(cpi_accounts, cpi_program, signer_seeds)`.
+
+## Return values
+
+Solana currently has no way to return values from CPI, alas. However, one can approximate this
+by having the callee write return values to an account and the caller read that account to
+retrieve the return value. In future work, Anchor should do this transparently.
+
+## Conclusion
+
+That's it for the introductory tutorials for Anchor. For more, see the `examples/` directory in the
+[repository](https://github.com/project-serum/anchor). For feature requests or bugs, GitHub issues
+are appreciated. For direct questions or support, join the [Serum Discord](https://discord.com/channels/739225212658122886/752530209848295555).

+ 4 - 4
examples/sysvars/programs/sysvars/src/lib.rs

@@ -11,8 +11,8 @@ mod sysvars {
 }
 
 #[derive(Accounts)]
-pub struct Sysvars {
-    pub clock: Clock,
-    pub rent: Rent,
-    pub stake_history: StakeHistory,
+pub struct Sysvars<'info> {
+    pub clock: Sysvar<'info, Clock>,
+    pub rent: Sysvar<'info, Rent>,
+    pub stake_history: Sysvar<'info, StakeHistory>,
 }

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

@@ -23,7 +23,7 @@ mod basic_1 {
 pub struct Initialize<'info> {
     #[account(init)]
     pub my_account: ProgramAccount<'info, MyAccount>,
-    pub rent: Rent,
+    pub rent: Sysvar<'info, Rent>,
 }
 
 #[derive(Accounts)]

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

@@ -13,7 +13,6 @@ mod basic_2 {
         let root = &mut ctx.accounts.root;
         root.authority = authority;
         root.data = data;
-        root.initialized = true;
         Ok(())
     }
 
@@ -25,8 +24,7 @@ mod basic_2 {
 
     pub fn create_leaf(ctx: Context<CreateLeaf>, data: u64, custom: MyCustomType) -> ProgramResult {
         let leaf = &mut ctx.accounts.leaf;
-        leaf.initialized = true;
-        leaf.root = *ctx.accounts.root.info.key;
+        leaf.root = *ctx.accounts.root.to_account_info().key;
         leaf.data = data;
         leaf.custom = custom;
         Ok(())
@@ -52,7 +50,7 @@ mod basic_2 {
 pub struct CreateRoot<'info> {
     #[account(init)]
     pub root: ProgramAccount<'info, Root>,
-    pub rent: Rent,
+    pub rent: Sysvar<'info, Rent>,
 }
 
 #[derive(Accounts)]
@@ -68,7 +66,7 @@ pub struct CreateLeaf<'info> {
     pub root: ProgramAccount<'info, Root>,
     #[account(init)]
     pub leaf: ProgramAccount<'info, Leaf>,
-    pub rent: Rent,
+    pub rent: Sysvar<'info, Rent>,
 }
 
 #[derive(Accounts)]
@@ -85,14 +83,12 @@ pub struct UpdateLeaf<'info> {
 
 #[account]
 pub struct Root {
-    pub initialized: bool,
     pub authority: Pubkey,
     pub data: u64,
 }
 
 #[account]
 pub struct Leaf {
-    pub initialized: bool,
     pub root: Pubkey,
     pub data: u64,
     pub custom: MyCustomType,
@@ -100,7 +96,7 @@ pub struct Leaf {
 
 // Define custom types.
 
-#[derive(AnchorSerialize, AnchorDeserialize)]
+#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
 pub struct MyCustomType {
     pub my_data: u64,
     pub key: Pubkey,

+ 2 - 0
examples/tutorial/basic-3/Anchor.toml

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

+ 4 - 0
examples/tutorial/basic-3/Cargo.toml

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

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

@@ -0,0 +1,20 @@
+[package]
+name = "puppet-master"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2018"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "puppet_master"
+
+[features]
+no-entrypoint = []
+
+[dependencies]
+borsh = { git = "https://github.com/project-serum/borsh", branch = "serum", features = ["serum-program"] }
+solana-program = "1.4.3"
+solana-sdk = { version = "1.3.14", default-features = false, features = ["program"] }
+# anchor = { git = "https://github.com/project-serum/anchor", features = ["derive"] }
+anchor = { path = "/home/armaniferrante/Documents/code/src/github.com/project-serum/anchor", features = ["derive"] }
+puppet = { path = "../puppet", features = ["no-entrypoint"] }

+ 2 - 0
examples/tutorial/basic-3/programs/puppet-master/Xargo.toml

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

+ 26 - 0
examples/tutorial/basic-3/programs/puppet-master/src/lib.rs

@@ -0,0 +1,26 @@
+#![feature(proc_macro_hygiene)]
+
+// #region core
+use anchor::prelude::*;
+use puppet::{Puppet, SetData};
+
+#[program]
+mod puppet_master {
+    use super::*;
+    pub fn pull_strings(ctx: Context<PullStrings>, data: u64) -> ProgramResult {
+        let cpi_program = ctx.accounts.puppet_program.clone();
+        let cpi_accounts = SetData {
+            puppet: ctx.accounts.puppet.clone(),
+        };
+        let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
+        puppet::cpi::set_data(cpi_ctx, data)
+    }
+}
+
+#[derive(Accounts)]
+pub struct PullStrings<'info> {
+    #[account(mut)]
+    pub puppet: CpiAccount<'info, Puppet>,
+    pub puppet_program: AccountInfo<'info>,
+}
+// #endregion core

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

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

+ 2 - 0
examples/tutorial/basic-3/programs/puppet/Xargo.toml

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

+ 35 - 0
examples/tutorial/basic-3/programs/puppet/src/lib.rs

@@ -0,0 +1,35 @@
+#![feature(proc_macro_hygiene)]
+
+use anchor::prelude::*;
+
+#[program]
+mod puppet {
+    use super::*;
+    pub fn initialize(ctx: Context<Initialize>) -> ProgramResult {
+        Ok(())
+    }
+
+    pub fn set_data(ctx: Context<SetData>, data: u64) -> ProgramResult {
+        let puppet = &mut ctx.accounts.puppet;
+        puppet.data = data;
+        Ok(())
+    }
+}
+
+#[derive(Accounts)]
+pub struct Initialize<'info> {
+    #[account(init)]
+    pub puppet: ProgramAccount<'info, Puppet>,
+    pub rent: Sysvar<'info, Rent>,
+}
+
+#[derive(Accounts)]
+pub struct SetData<'info> {
+    #[account(mut)]
+    pub puppet: ProgramAccount<'info, Puppet>,
+}
+
+#[account]
+pub struct Puppet {
+    pub data: u64,
+}

+ 47 - 0
examples/tutorial/basic-3/tests/basic-3.js

@@ -0,0 +1,47 @@
+const assert = require("assert");
+const anchor = require("@project-serum/anchor");
+
+describe("basic-3", () => {
+  const provider = anchor.Provider.local();
+
+  // Configure the client to use the local cluster.
+  anchor.setProvider(provider);
+
+  it("Performs CPI from puppet master to puppet", async () => {
+    const puppetMaster = anchor.workspace.PuppetMaster;
+    const puppet = anchor.workspace.Puppet;
+
+    // Initialize a new puppet account.
+    const newPuppetAccount = new anchor.web3.Account();
+    const tx = await puppet.rpc.initialize({
+      accounts: {
+        puppet: newPuppetAccount.publicKey,
+        rent: anchor.web3.SYSVAR_RENT_PUBKEY,
+      },
+      signers: [newPuppetAccount],
+      instructions: [
+        anchor.web3.SystemProgram.createAccount({
+          fromPubkey: provider.wallet.publicKey,
+          newAccountPubkey: newPuppetAccount.publicKey,
+          space: 8 + 8, // Add 8 for the account discriminator.
+          lamports: await provider.connection.getMinimumBalanceForRentExemption(
+            8 + 8
+          ),
+          programId: puppet.programId,
+        }),
+      ],
+    });
+
+    // Invoke the puppet master to perform a CPI to the puppet.
+    await puppetMaster.rpc.pullStrings(new anchor.BN(111), {
+        accounts: {
+            puppet: newPuppetAccount.publicKey,
+            puppetProgram: puppet.programId,
+        },
+    });
+
+    // Check the state updated.
+    puppetAccount = await puppet.account.puppet(newPuppetAccount.publicKey);
+    assert.ok(puppetAccount.data.eq(new anchor.BN(111)));
+  });
+});

+ 140 - 21
src/lib.rs

@@ -1,4 +1,5 @@
 use solana_sdk::account_info::AccountInfo;
+use solana_sdk::instruction::AccountMeta;
 use solana_sdk::program_error::ProgramError;
 use solana_sdk::pubkey::Pubkey;
 use std::io::Write;
@@ -8,6 +9,7 @@ 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;
+/// Default serialization format for anchor instructions and accounts.
 pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize};
 
 /// A data structure of Solana accounts that can be deserialized from the input
@@ -16,9 +18,26 @@ pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorS
 /// (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>;
+pub trait Accounts<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized {
+    fn try_accounts(
+        program_id: &Pubkey,
+        from: &mut &[AccountInfo<'info>],
+    ) -> Result<Self, ProgramError>;
+}
+
+/// Transformation to `AccountMeta` structs.
+pub trait ToAccountMetas {
+    fn to_account_metas(&self) -> Vec<AccountMeta>;
+}
+
+/// Transformation to `AccountInfo` structs.
+pub trait ToAccountInfos<'info> {
+    fn to_account_infos(&self) -> Vec<AccountInfo<'info>>;
+}
+
+/// Transformation to an `AccountInfo` struct.
+pub trait ToAccountInfo<'info> {
+    fn to_account_info(&self) -> AccountInfo<'info>;
 }
 
 /// A data structure that can be serialized and stored in an `AccountInfo` data
@@ -48,16 +67,15 @@ pub trait AccountDeserialize: Sized {
     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 account: T,
+/// Container for a serializable `account`. Use this to reference any account
+/// owned by the currently executing program.
+#[derive(Clone)]
+pub struct ProgramAccount<'a, T: AccountSerialize + AccountDeserialize + Clone> {
+    info: AccountInfo<'a>,
+    account: T,
 }
 
-impl<'a, T: AccountSerialize + AccountDeserialize> ProgramAccount<'a, T> {
+impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramAccount<'a, T> {
     pub fn new(info: AccountInfo<'a>, account: T) -> ProgramAccount<'a, T> {
         Self { info, account }
     }
@@ -93,7 +111,15 @@ impl<'a, T: AccountSerialize + AccountDeserialize> ProgramAccount<'a, T> {
     }
 }
 
-impl<'a, T: AccountSerialize + AccountDeserialize> Deref for ProgramAccount<'a, T> {
+impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountInfo<'info>
+    for ProgramAccount<'info, T>
+{
+    fn to_account_info(&self) -> AccountInfo<'info> {
+        self.info.clone()
+    }
+}
+
+impl<'a, T: AccountSerialize + AccountDeserialize + Clone> Deref for ProgramAccount<'a, T> {
     type Target = T;
 
     fn deref(&self) -> &Self::Target {
@@ -101,30 +127,123 @@ impl<'a, T: AccountSerialize + AccountDeserialize> Deref for ProgramAccount<'a,
     }
 }
 
-impl<'a, T: AccountSerialize + AccountDeserialize> DerefMut for ProgramAccount<'a, T> {
+impl<'a, T: AccountSerialize + AccountDeserialize + Clone> DerefMut for ProgramAccount<'a, T> {
     fn deref_mut(&mut self) -> &mut Self::Target {
         &mut self.account
     }
 }
 
-/// 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> {
+/// Similar to `ProgramAccount`, but to reference any account *not* owned by
+/// the current program.
+pub type CpiAccount<'a, T> = ProgramAccount<'a, T>;
+
+/// Container for a Solana sysvar.
+pub struct Sysvar<'info, T: solana_sdk::sysvar::Sysvar> {
+    info: AccountInfo<'info>,
+    account: T,
+}
+
+impl<'info, T: solana_sdk::sysvar::Sysvar> Sysvar<'info, T> {
+    pub fn from_account_info(
+        acc_info: &AccountInfo<'info>,
+    ) -> Result<Sysvar<'info, T>, ProgramError> {
+        Ok(Sysvar {
+            info: acc_info.clone(),
+            account: T::from_account_info(&acc_info)?,
+        })
+    }
+}
+
+impl<'a, T: solana_sdk::sysvar::Sysvar> Deref for Sysvar<'a, T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.account
+    }
+}
+
+impl<'a, T: solana_sdk::sysvar::Sysvar> DerefMut for Sysvar<'a, T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.account
+    }
+}
+
+impl<'info, T: solana_sdk::sysvar::Sysvar> ToAccountInfo<'info> for Sysvar<'info, T> {
+    fn to_account_info(&self) -> AccountInfo<'info> {
+        self.info.clone()
+    }
+}
+
+impl<'info> ToAccountInfo<'info> for AccountInfo<'info> {
+    fn to_account_info(&self) -> AccountInfo<'info> {
+        self.clone()
+    }
+}
+
+/// Provides non-argument inputs to the program.
+pub struct Context<'a, 'b, 'c, 'info, T> {
+    /// Deserialized accounts.
     pub accounts: &'a mut T,
+    /// Currently executing program id.
     pub program_id: &'b Pubkey,
+    /// Remaining accounts given but not deserialized or validated.
+    pub remaining_accounts: &'c [AccountInfo<'info>],
+}
+
+impl<'a, 'b, 'c, 'info, T> Context<'a, 'b, 'c, 'info, T> {
+    pub fn new(
+        accounts: &'a mut T,
+        program_id: &'b Pubkey,
+        remaining_accounts: &'c [AccountInfo<'info>],
+    ) -> Self {
+        Self {
+            accounts,
+            program_id,
+            remaining_accounts,
+        }
+    }
+}
+
+/// Context speciying non-argument inputs for cross-program-invocations.
+pub struct CpiContext<'a, 'b, 'c, 'info, T: Accounts<'info>> {
+    pub accounts: T,
+    pub program: AccountInfo<'info>,
+    pub signer_seeds: &'a [&'b [&'c [u8]]],
+}
+
+impl<'a, 'b, 'c, 'info, T: Accounts<'info>> CpiContext<'a, 'b, 'c, 'info, T> {
+    pub fn new(program: AccountInfo<'info>, accounts: T) -> Self {
+        Self {
+            accounts,
+            program,
+            signer_seeds: &[],
+        }
+    }
+
+    pub fn new_with_signer(
+        accounts: T,
+        program: AccountInfo<'info>,
+        signer_seeds: &'a [&'b [&'c [u8]]],
+    ) -> Self {
+        Self {
+            accounts,
+            program,
+            signer_seeds,
+        }
+    }
 }
 
 pub mod prelude {
     pub use super::{
         access_control, account, program, AccountDeserialize, AccountSerialize, Accounts,
-        AnchorDeserialize, AnchorSerialize, Context, ProgramAccount,
+        AnchorDeserialize, AnchorSerialize, Context, CpiAccount, CpiContext, ProgramAccount,
+        Sysvar, ToAccountInfo, ToAccountInfos, ToAccountMetas,
     };
 
     pub use solana_program::msg;
-    pub use solana_sdk::account_info::next_account_info;
-    pub use solana_sdk::account_info::AccountInfo;
+    pub use solana_sdk::account_info::{next_account_info, AccountInfo};
     pub use solana_sdk::entrypoint::ProgramResult;
+    pub use solana_sdk::instruction::AccountMeta;
     pub use solana_sdk::program_error::ProgramError;
     pub use solana_sdk::pubkey::Pubkey;
     pub use solana_sdk::sysvar::clock::Clock;
@@ -137,5 +256,5 @@ pub mod prelude {
     pub use solana_sdk::sysvar::slot_hashes::SlotHashes;
     pub use solana_sdk::sysvar::slot_history::SlotHistory;
     pub use solana_sdk::sysvar::stake_history::StakeHistory;
-    pub use solana_sdk::sysvar::Sysvar;
+    pub use solana_sdk::sysvar::Sysvar as SolanaSysvar;
 }

+ 131 - 57
syn/src/codegen/accounts.rs

@@ -5,6 +5,7 @@ use crate::{
 use quote::quote;
 
 pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
+    // Extract out each account info.
     let acc_infos: Vec<proc_macro2::TokenStream> = accs
         .fields
         .iter()
@@ -15,44 +16,49 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
             }
         })
         .collect();
+    let acc_infos_len = {
+        let acc_infos_len = acc_infos.len();
+        quote! {
+            #acc_infos_len
+        }
+    };
 
-    let (deser_fields, access_checks, return_tys) = {
-        // Deserialization for each field.
-        let deser_fields: Vec<proc_macro2::TokenStream> = accs
-            .fields
-            .iter()
-            .map(generate_field_deserialization)
-            .collect();
-        // Constraint checks for each account fields.
-        let access_checks: Vec<proc_macro2::TokenStream> = accs
-            .fields
-            .iter()
-            .map(|f: &Field| {
-                let checks: Vec<proc_macro2::TokenStream> = f
-                    .constraints
-                    .iter()
-                    .map(|c| generate_constraint(&f, c))
-                    .collect();
-                quote! {
-                    #(#checks)*
-                }
-            })
-            .collect();
-        // Each field in the final deserialized accounts struct.
-        let return_tys: Vec<proc_macro2::TokenStream> = accs
-            .fields
-            .iter()
-            .map(|f: &Field| {
-                let name = &f.ident;
-                quote! {
-                    #name
-                }
-            })
-            .collect();
+    // Deserialization for each field.
+    let deser_fields: Vec<proc_macro2::TokenStream> = accs
+        .fields
+        .iter()
+        .map(generate_field_deserialization)
+        .collect();
 
-        (deser_fields, access_checks, return_tys)
-    };
+    // Constraint checks for each account fields.
+    let access_checks: Vec<proc_macro2::TokenStream> = accs
+        .fields
+        .iter()
+        .map(|f: &Field| {
+            let checks: Vec<proc_macro2::TokenStream> = f
+                .constraints
+                .iter()
+                .map(|c| generate_constraint(&f, c))
+                .collect();
+            quote! {
+                #(#checks)*
+            }
+        })
+        .collect();
+
+    // Each field in the final deserialized accounts struct.
+    let return_tys: Vec<proc_macro2::TokenStream> = accs
+        .fields
+        .iter()
+        .map(|f: &Field| {
+            let name = &f.ident;
+            quote! {
+                #name
+            }
+        })
+        .collect();
 
+    // Exit program code-blocks for each account.
     let on_save: Vec<proc_macro2::TokenStream> = accs
         .fields
         .iter()
@@ -60,16 +66,54 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
             let ident = &f.ident;
             let info = match f.ty {
                 Ty::AccountInfo => quote! { #ident },
-                Ty::ProgramAccount(_) => quote! { #ident.info },
+                Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
                 _ => return quote! {},
             };
             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.try_serialize(&mut cursor)?;
+                    // Only persist the change if the account is owned by the
+                    // current program.
+                    if program_id == self.#info.owner  {
+                        let info = self.#info;
+                        let mut data = info.try_borrow_mut_data()?;
+                        let dst: &mut [u8] = &mut data;
+                        let mut cursor = std::io::Cursor::new(dst);
+                        self.#ident.try_serialize(&mut cursor)?;
+                    }
+                },
+            }
+        })
+        .collect();
+
+    // Implementation for `ToAccountInfos` trait.
+    let to_acc_infos: Vec<proc_macro2::TokenStream> = accs
+        .fields
+        .iter()
+        .map(|f: &Field| {
+            let name = &f.ident;
+            quote! {
+                self.#name.to_account_info()
+            }
+        })
+        .collect();
+
+    // Implementation for `ToAccountMetas` trait.
+    let to_acc_metas: Vec<proc_macro2::TokenStream> = accs
+        .fields
+        .iter()
+        .map(|f: &Field| {
+            let name = &f.ident;
+            let is_signer = match f.is_signer {
+                false => quote! { false },
+                true => quote! { true },
+            };
+            match f.is_mut {
+                false => quote! {
+                    AccountMeta::new_readonly(*self.#name.to_account_info().key, #is_signer)
+                },
+                true => quote! {
+                    AccountMeta::new(*self.#name.to_account_info().key, #is_signer)
                 },
             }
         })
@@ -86,12 +130,15 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
 
     quote! {
         impl#combined_generics Accounts#trait_generics for #name#strct_generics {
-            fn try_accounts(program_id: &Pubkey, accounts: &[AccountInfo<'info>]) -> Result<Self, ProgramError> {
-                let acc_infos = &mut accounts.iter();
+            fn try_accounts(program_id: &Pubkey, remaining_accounts: &mut &[AccountInfo<'info>]) -> Result<Self, ProgramError> {
+                let acc_infos = &mut remaining_accounts.iter();
 
                 // Pull out each account info from the `accounts` slice.
                 #(#acc_infos)*
 
+                // Move the remaining_accounts cursor to the iterator end.
+                *remaining_accounts = &remaining_accounts[#acc_infos_len..];
+
                 // Deserialize each account.
                 #(#deser_fields)*
 
@@ -105,8 +152,24 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
             }
         }
 
+        impl#combined_generics ToAccountInfos#trait_generics for #name#strct_generics {
+            fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
+                vec![
+                    #(#to_acc_infos),*
+                ]
+            }
+        }
+
+        impl#combined_generics ToAccountMetas for #name#strct_generics {
+            fn to_account_metas(&self) -> Vec<AccountMeta> {
+                vec![
+                    #(#to_acc_metas),*
+                ]
+            }
+        }
+
         impl#strct_generics #name#strct_generics {
-            pub fn exit(&self) -> ProgramResult {
+            pub fn exit(&self, program_id: &Pubkey) -> ProgramResult {
                 #(#on_save)*
                 Ok(())
             }
@@ -131,36 +194,47 @@ pub fn generate_field_deserialization(f: &Field) -> proc_macro2::TokenStream {
                 },
             }
         }
+        Ty::CpiAccount(acc) => {
+            let account_struct = &acc.account_ident;
+            match f.is_init {
+                false => quote! {
+                    let #ident: CpiAccount<#account_struct> = CpiAccount::try_from(#ident)?;
+                },
+                true => quote! {
+                    let #ident: CpiAccount<#account_struct> = CpiAccount::try_from_init(#ident)?;
+                },
+            }
+        }
         Ty::Sysvar(sysvar) => match sysvar {
             SysvarTy::Clock => quote! {
-                let #ident = Clock::from_account_info(#ident)?;
+                let #ident: Sysvar<Clock> = Sysvar::from_account_info(#ident)?;
             },
             SysvarTy::Rent => quote! {
-                let #ident = Rent::from_account_info(#ident)?;
+                let #ident: Sysvar<Rent> = Sysvar::from_account_info(#ident)?;
             },
             SysvarTy::EpochSchedule => quote! {
-                let #ident = EpochSchedule::from_account_info(#ident)?;
+                let #ident: Sysvar<EpochSchedule> = Sysvar::from_account_info(#ident)?;
             },
             SysvarTy::Fees => quote! {
-                let #ident = Fees::from_account_info(#ident)?;
+                let #ident: Sysvar<Fees> = Sysvar::from_account_info(#ident)?;
             },
             SysvarTy::RecentBlockHashes => quote! {
-                let #ident = RecentBlockhashes::from_account_info(#ident)?;
+                let #ident: Sysvar<RecentBlockhashes> = Sysvar::from_account_info(#ident)?;
             },
             SysvarTy::SlotHashes => quote! {
-                let #ident = SlotHashes::from_account_info(#ident)?;
+                let #ident: Sysvar<SlotHashes> = Sysvar::from_account_info(#ident)?;
             },
             SysvarTy::SlotHistory => quote! {
-                let #ident = SlotHistory::from_account_info(#ident)?;
+                let #ident: Sysvar<SlotHistory> = Sysvar::from_account_info(#ident)?;
             },
             SysvarTy::StakeHistory => quote! {
-                let #ident = StakeHistory::from_account_info(#ident)?;
+                let #ident: Sysvar<StakeHistory> = Sysvar::from_account_info(#ident)?;
             },
             SysvarTy::Instructions => quote! {
-                let #ident = Instructions::from_account_info(#ident)?;
+                let #ident: Sysvar<Instructions> = Sysvar::from_account_info(#ident)?;
             },
             SysvarTy::Rewards => quote! {
-                let #ident = Rewards::from_account_info(#ident)?;
+                let #ident: Sysvar<Rewards> = Sysvar::from_account_info(#ident)?;
             },
         },
     };
@@ -190,7 +264,7 @@ pub fn generate_constraint_belongs_to(
     let ident = &f.ident;
     // todo: would be nice if target could be an account info object.
     quote! {
-        if &#ident.#target != #target.info.key {
+        if &#ident.#target != #target.to_account_info().key {
             return Err(ProgramError::Custom(1)); // todo: error codes
         }
     }
@@ -200,7 +274,7 @@ pub fn generate_constraint_signer(f: &Field, _c: &ConstraintSigner) -> proc_macr
     let ident = &f.ident;
     let info = match f.ty {
         Ty::AccountInfo => quote! { #ident },
-        Ty::ProgramAccount(_) => quote! { #ident.info },
+        Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
         _ => panic!("Invalid syntax: signer cannot be specified."),
     };
     quote! {
@@ -223,7 +297,7 @@ pub fn generate_constraint_owner(f: &Field, c: &ConstraintOwner) -> proc_macro2:
     let ident = &f.ident;
     let info = match f.ty {
         Ty::AccountInfo => quote! { #ident },
-        Ty::ProgramAccount(_) => quote! { #ident.info },
+        Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
         _ => panic!("Invalid syntax: owner cannot be specified."),
     };
     match c {
@@ -243,7 +317,7 @@ pub fn generate_constraint_rent_exempt(
     let ident = &f.ident;
     let info = match f.ty {
         Ty::AccountInfo => quote! { #ident },
-        Ty::ProgramAccount(_) => quote! { #ident.info },
+        Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
         _ => panic!("Invalid syntax: rent exemption cannot be specified."),
     };
     match c {

+ 81 - 29
syn/src/codegen/program.rs

@@ -1,4 +1,4 @@
-use crate::Program;
+use crate::{Program, Rpc};
 use heck::CamelCase;
 use quote::quote;
 
@@ -8,13 +8,16 @@ pub fn generate(program: Program) -> proc_macro2::TokenStream {
     let dispatch = generate_dispatch(&program);
     let methods = generate_methods(&program);
     let instruction = generate_instruction(&program);
+    let cpi = generate_cpi(&program);
 
     quote! {
         // Import everything in the mod, in case the user wants to put types
         // in there.
         use #mod_name::*;
 
+        #[cfg(not(feature = "no-entrypoint"))]
         solana_program::entrypoint!(entry);
+        #[cfg(not(feature = "no-entrypoint"))]
         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)
@@ -26,50 +29,30 @@ pub fn generate(program: Program) -> proc_macro2::TokenStream {
         #methods
 
         #instruction
+
+        #cpi
     }
 }
 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 variant_arm = generate_ix_variant(program, rpc);
             let rpc_name = &rpc.raw_method.sig.ident;
             let anchor = &rpc.anchor_ident;
 
             quote! {
                 instruction::#variant_arm => {
-                    let mut accounts = #anchor::try_accounts(program_id, accounts)?;
+                    let mut remaining_accounts: &[AccountInfo] = accounts;
+                    let mut accounts = #anchor::try_accounts(program_id, &mut remaining_accounts)?;
                     #program_name::#rpc_name(
-                        Context {
-                            accounts: &mut accounts,
-                            program_id,
-                        },
+                        Context::new(&mut accounts, program_id, remaining_accounts),
                         #(#rpc_arg_names),*
                     )?;
-                    accounts.exit()
+                    accounts.exit(program_id)
                 }
             }
         })
@@ -82,6 +65,26 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
     }
 }
 
+pub fn generate_ix_variant(program: &Program, rpc: &Rpc) -> proc_macro2::TokenStream {
+    let enum_name = instruction_enum_name(program);
+    let rpc_arg_names: Vec<&syn::Ident> = rpc.args.iter().map(|arg| &arg.name).collect();
+    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 rpc.args.len() == 0 {
+        quote! {
+            #enum_name::#rpc_name_camel
+        }
+    } else {
+        quote! {
+            #enum_name::#rpc_name_camel {
+                #(#rpc_arg_names),*
+            }
+        }
+    }
+}
+
 pub fn generate_methods(program: &Program) -> proc_macro2::TokenStream {
     let program_mod = &program.program_mod;
     quote! {
@@ -128,7 +131,56 @@ pub fn generate_instruction(program: &Program) -> proc_macro2::TokenStream {
 
 fn instruction_enum_name(program: &Program) -> proc_macro2::Ident {
     proc_macro2::Ident::new(
-        &program.name.to_string().to_camel_case(),
+        &format!("_{}Instruction", program.name.to_string().to_camel_case()),
         program.name.span(),
     )
 }
+
+fn generate_cpi(program: &Program) -> proc_macro2::TokenStream {
+    let cpi_methods: Vec<proc_macro2::TokenStream> = program
+        .rpcs
+        .iter()
+        .map(|rpc| {
+            let accounts_ident = &rpc.anchor_ident;
+            let cpi_method = {
+                let ix_variant = generate_ix_variant(program, rpc);
+                let method_name = &rpc.ident;
+                let args: Vec<&syn::PatType> = rpc.args.iter().map(|arg| &arg.raw_arg).collect();
+                quote! {
+                    pub fn #method_name<'a, 'b, 'c, 'info>(
+                        ctx: CpiContext<'a, 'b, 'c, 'info, #accounts_ident<'info>>,
+                        #(#args),*
+                    ) -> ProgramResult {
+                        let ix = {
+                            let ix = instruction::#ix_variant;
+                            let data = AnchorSerialize::try_to_vec(&ix)
+                                .map_err(|_| ProgramError::InvalidInstructionData)?;
+                            let accounts = ctx.accounts.to_account_metas();
+                            solana_program::instruction::Instruction {
+                                program_id: *ctx.program.key,
+                                accounts,
+                                data,
+                            }
+                        };
+                        let mut acc_infos = ctx.accounts.to_account_infos();
+                        acc_infos.push(ctx.program.clone());
+                        solana_sdk::program::invoke_signed(
+                            &ix,
+                            &acc_infos,
+                            ctx.signer_seeds,
+                        )
+                    }
+                }
+            };
+
+            cpi_method
+        })
+        .collect();
+    quote! {
+        pub mod cpi {
+            use super::*;
+
+            #(#cpi_methods)*
+        }
+    }
+}

+ 9 - 0
syn/src/lib.rs

@@ -17,6 +17,7 @@ pub struct Rpc {
     pub raw_method: syn::ItemFn,
     pub ident: syn::Ident,
     pub args: Vec<RpcArg>,
+    // The ident for the struct deriving Accounts.
     pub anchor_ident: syn::Ident,
 }
 
@@ -46,6 +47,7 @@ impl AccountsStruct {
         }
     }
 
+    // Returns all program owned accounts in the Accounts struct.
     pub fn account_tys(&self) -> Vec<String> {
         self.fields
             .iter()
@@ -73,6 +75,7 @@ pub enum Ty {
     AccountInfo,
     ProgramAccount(ProgramAccountTy),
     Sysvar(SysvarTy),
+    CpiAccount(CpiAccountTy),
 }
 
 #[derive(PartialEq)]
@@ -95,6 +98,12 @@ pub struct ProgramAccountTy {
     pub account_ident: syn::Ident,
 }
 
+#[derive(PartialEq)]
+pub struct CpiAccountTy {
+    // The struct type of the account.
+    pub account_ident: syn::Ident,
+}
+
 // An access control constraint for an account.
 pub enum Constraint {
     Signer(ConstraintSigner),

+ 54 - 19
syn/src/parser/accounts.rs

@@ -1,6 +1,6 @@
 use crate::{
     AccountsStruct, Constraint, ConstraintBelongsTo, ConstraintLiteral, ConstraintOwner,
-    ConstraintRentExempt, ConstraintSigner, Field, ProgramAccountTy, SysvarTy, Ty,
+    ConstraintRentExempt, ConstraintSigner, CpiAccountTy, Field, ProgramAccountTy, SysvarTy, Ty,
 };
 
 pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
@@ -68,22 +68,24 @@ fn parse_ty(f: &syn::Field) -> Ty {
     let segments = &path.segments[0];
     match segments.ident.to_string().as_str() {
         "ProgramAccount" => Ty::ProgramAccount(parse_program_account(&path)),
+        "CpiAccount" => Ty::CpiAccount(parse_cpi_account(&path)),
+        "Sysvar" => Ty::Sysvar(parse_sysvar(&path)),
         "AccountInfo" => Ty::AccountInfo,
-        "Clock" => Ty::Sysvar(SysvarTy::Clock),
-        "Rent" => Ty::Sysvar(SysvarTy::Rent),
-        "EpochSchedule" => Ty::Sysvar(SysvarTy::EpochSchedule),
-        "Fees" => Ty::Sysvar(SysvarTy::Fees),
-        "RecentBlockhashes" => Ty::Sysvar(SysvarTy::RecentBlockHashes),
-        "SlotHashes" => Ty::Sysvar(SysvarTy::SlotHashes),
-        "SlotHistory" => Ty::Sysvar(SysvarTy::SlotHistory),
-        "StakeHistory" => Ty::Sysvar(SysvarTy::StakeHistory),
-        "Instructions" => Ty::Sysvar(SysvarTy::Instructions),
-        "Rewards" => Ty::Sysvar(SysvarTy::Rewards),
         _ => panic!("invalid account type"),
     }
 }
 
+fn parse_cpi_account(path: &syn::Path) -> CpiAccountTy {
+    let account_ident = parse_account(path);
+    CpiAccountTy { account_ident }
+}
+
 fn parse_program_account(path: &syn::Path) -> ProgramAccountTy {
+    let account_ident = parse_account(path);
+    ProgramAccountTy { account_ident }
+}
+
+fn parse_account(path: &syn::Path) -> syn::Ident {
     let segments = &path.segments[0];
     let account_ident = match &segments.arguments {
         syn::PathArguments::AngleBracketed(args) => {
@@ -104,7 +106,43 @@ fn parse_program_account(path: &syn::Path) -> ProgramAccountTy {
         }
         _ => panic!("Invalid ProgramAccount"),
     };
-    ProgramAccountTy { account_ident }
+    account_ident
+}
+
+fn parse_sysvar(path: &syn::Path) -> SysvarTy {
+    let segments = &path.segments[0];
+    let account_ident = match &segments.arguments {
+        syn::PathArguments::AngleBracketed(args) => {
+            // Expected: <'info, MyType>.
+            assert!(args.args.len() == 2);
+            match &args.args[1] {
+                syn::GenericArgument::Type(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 Sysvar"),
+                },
+                _ => panic!("Invalid Sysvar"),
+            }
+        }
+        _ => panic!("Invalid Sysvar"),
+    };
+    match account_ident.to_string().as_str() {
+        "Clock" => SysvarTy::Clock,
+        "Rent" => SysvarTy::Rent,
+        "EpochSchedule" => SysvarTy::EpochSchedule,
+        "Fees" => SysvarTy::Fees,
+        "RecentBlockhashes" => SysvarTy::RecentBlockHashes,
+        "SlotHashes" => SysvarTy::SlotHashes,
+        "SlotHistory" => SysvarTy::SlotHistory,
+        "StakeHistory" => SysvarTy::StakeHistory,
+        "Instructions" => SysvarTy::Instructions,
+        "Rewards" => SysvarTy::Rewards,
+        _ => panic!("Invalid Sysvar"),
+    }
 }
 
 fn parse_constraints(anchor: &syn::Attribute, ty: &Ty) -> (Vec<Constraint>, bool, bool, bool) {
@@ -220,19 +258,16 @@ fn parse_constraints(anchor: &syn::Attribute, ty: &Ty) -> (Vec<Constraint>, bool
     }
 
     if !has_owner_constraint {
-        if ty == &Ty::AccountInfo {
-            constraints.push(Constraint::Owner(ConstraintOwner::Skip));
-        } else {
+        if let Ty::ProgramAccount(_) = ty {
             constraints.push(Constraint::Owner(ConstraintOwner::Program));
         }
     }
 
-    match is_rent_exempt {
-        None => constraints.push(Constraint::RentExempt(ConstraintRentExempt::Skip)),
-        Some(is_re) => match is_re {
+    if let Some(is_re) = is_rent_exempt {
+        match is_re {
             false => constraints.push(Constraint::RentExempt(ConstraintRentExempt::Skip)),
             true => constraints.push(Constraint::RentExempt(ConstraintRentExempt::Enforce)),
-        },
+        }
     }
 
     (constraints, is_mut, is_signer, is_init)

+ 1 - 1
ts/package.json

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