Browse Source

State struct instructions

Armani Ferrante 4 năm trước cách đây
mục cha
commit
ef23756978

+ 71 - 13
README.md

@@ -8,7 +8,7 @@
 
 Anchor is a framework for Solana's [Sealevel](https://medium.com/solana-labs/sealevel-parallel-processing-thousands-of-smart-contracts-d814b378192) runtime providing several convenient developer tools.
 
-- Rust DSL for writing Solana programs
+- Rust eDSL for writing Solana programs
 - [IDL](https://en.wikipedia.org/wiki/Interface_description_language) specification
 - TypeScript package for generating clients from IDL
 - CLI and workspace management for developing complete applications
@@ -25,7 +25,55 @@ To jump straight to examples, go [here](https://github.com/project-serum/anchor/
 * **Anchor is in active development, so all APIs are subject to change.**
 * **This code is unaudited. Use at your own risk.**
 
-## Example
+## Examples
+
+Build stateful programs on Solana by defining a state struct with associated
+methods. Here's a classic counter example, where only the designated `authority`
+can increment the count.
+
+```rust
+#[program]
+mod counter {
+
+    #[state]
+    pub struct Counter {
+      authority: Pubkey,
+      count: u64,
+    }
+
+    pub fn new(ctx: Context<Auth>) -> Result<Self> {
+        Ok(Self {
+            auth: *auth.accounts.authority.key
+        })
+    }
+
+    pub fn increment(&mut self, ctx: Context<Auth>) -> Result<()> {
+        if &self.authority != ctx.accounts.authority.key {
+            return Err(ErrorCode::Unauthorized.into());
+        }
+
+        self.count += 1;
+
+        Ok(())
+    }
+}
+
+#[derive(Accounts)]
+pub struct Auth<'info> {
+    #[account(signer)]
+    authority: AccountInfo<'info>,
+}
+
+#[error]
+pub enum ErrorCode {
+    #[msg("You are not authorized to perform this action.")]
+    Unauthorized,
+}
+```
+
+Additionally, one can utilize the full power of Solana's parallel execution model by
+keeping the program stateless and working with accounts directly. The above example
+can be rewritten as follows.
 
 ```rust
 use anchor::prelude::*;
@@ -33,18 +81,23 @@ use anchor::prelude::*;
 // Define instruction handlers.
 
 #[program]
-mod example {
+mod counter {
     use super::*;
 
     pub fn initialize(ctx: Context<Initialize>, authority: Pubkey) -> ProgramResult {
-        let my_account = &mut ctx.accounts.my_account;
-        my_account.authority = authority;
+        let counter = &mut ctx.accounts.counter;
+
+        counter.authority = authority;
+        counter.count = 0;
+
         Ok(())
     }
 
-    pub fn update(ctx: Context<Update>, data: u64) -> ProgramResult {
-        let my_account = &mut ctx.accounts.my_account;
-        my_account.data = data;
+    pub fn increment(ctx: Context<Update>) -> ProgramResult {
+        let counter = &mut ctx.accounts.counter;
+
+        counter += 1;
+
         Ok(())
     }
 }
@@ -54,14 +107,14 @@ mod example {
 #[derive(Accounts)]
 pub struct Initialize<'info> {
     #[account(init)]
-    pub my_account: ProgramAccount<'info, MyAccount>,
+    pub counter: ProgramAccount<'info, Counter>,
     pub rent: Sysvar<'info, Rent>,
 }
 
 #[derive(Accounts)]
-pub struct Update<'info> {
+pub struct Increment<'info> {
     #[account(mut, has_one = authority)]
-    pub my_account: ProgramAccount<'info, MyAccount>,
+    pub counter: ProgramAccount<'info, Counter>,
     #[account(signer)]
     pub authority: AccountInfo<'info>,
 }
@@ -69,12 +122,17 @@ pub struct Update<'info> {
 // Define program owned accounts.
 
 #[account]
-pub struct MyAccount {
+pub struct Counter {
     pub authority: Pubkey,
-    pub data: u64,
+    pub count: u64,
 }
 ```
 
+Due to the fact that account sizes on Solana are fixed, some combination of
+the above is often required. For example, one can store store global state
+associated with the entire program in the `#[state]` struct and local
+state assocated with each user in individual `#[account]` structs.
+
 ## Accounts attribute syntax.
 
 There are several inert attributes (attributes that are consumed only for the

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

@@ -25,7 +25,7 @@ pub fn account(
 
     let coder = quote! {
         impl anchor_lang::AccountSerialize for #account_name {
-            fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> Result<(), ProgramError> {
+            fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> std::result::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.
@@ -48,7 +48,7 @@ pub fn account(
 
         impl anchor_lang::AccountDeserialize for #account_name {
 
-            fn try_deserialize(buf: &mut &[u8]) -> Result<Self, ProgramError> {
+            fn try_deserialize(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
                 let mut discriminator = [0u8; 8];
                 discriminator.copy_from_slice(
                     &anchor_lang::solana_program::hash::hash(
@@ -66,7 +66,7 @@ pub fn account(
                 Self::try_deserialize_unchecked(buf)
             }
 
-            fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError> {
+            fn try_deserialize_unchecked(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
                 let mut data: &[u8] = &buf[8..];
                 AnchorDeserialize::deserialize(&mut data)
                     .map_err(|_| ProgramError::InvalidAccountData)

+ 8 - 0
attribute/state/src/lib.rs

@@ -15,3 +15,11 @@ pub fn state(
         #item_struct
     })
 }
+
+/*
+        impl<'a, 'b, 'c, 'info> anchor_lang::ProgramStateContext<'a, 'b, 'c, 'info> for #strct {
+            fn context() -> Context<'a, 'b, 'c, 'info, ProgramStateAccounts<'info>> {
+                // todo
+            }
+        }
+*/

+ 1 - 1
cli/src/template.rs

@@ -47,7 +47,7 @@ pub fn lib_rs(name: &str) -> String {
 use anchor_lang::prelude::*;
 
 #[program]
-mod {} {{
+pub mod {} {{
     use super::*;
     pub fn initialize(ctx: Context<Initialize>) -> ProgramResult {{
         Ok(())

+ 53 - 62
examples/lockup/programs/lockup/src/lib.rs

@@ -10,8 +10,10 @@ use anchor_spl::token::{self, TokenAccount, Transfer};
 
 mod calculator;
 
+type Result<T> = std::result::Result<T, Error>;
+
 #[program]
-mod lockup {
+pub mod lockup {
     use super::*;
 
     #[state]
@@ -25,20 +27,45 @@ mod lockup {
     impl Lockup {
         pub const WHITELIST_SIZE: usize = 5;
 
-        pub fn new(authority: Pubkey) -> Result<Self, Error> {
+        pub fn new(ctx: Context<Auth>) -> Result<Self> {
             let mut whitelist = vec![];
             whitelist.resize(Self::WHITELIST_SIZE, Default::default());
             Ok(Lockup {
-                authority,
+                authority: *ctx.accounts.authority.key,
                 whitelist,
             })
         }
-    }
 
-    pub fn set_authority(ctx: Context<SetAuthority>, new_authority: Pubkey) -> Result<(), Error> {
-        let lockup = &mut ctx.accounts.lockup;
-        lockup.authority = new_authority;
-        Ok(())
+        #[access_control(whitelist_auth(self, &ctx))]
+        pub fn whitelist_add(&mut self, ctx: Context<Auth>, entry: WhitelistEntry) -> Result<()> {
+            if self.whitelist.len() == Self::WHITELIST_SIZE {
+                return Err(ErrorCode::WhitelistFull.into());
+            }
+            if self.whitelist.contains(&entry) {
+                return Err(ErrorCode::WhitelistEntryAlreadyExists.into());
+            }
+            self.whitelist.push(entry);
+            Ok(())
+        }
+
+        #[access_control(whitelist_auth(self, &ctx))]
+        pub fn whitelist_delete(
+            &mut self,
+            ctx: Context<Auth>,
+            entry: WhitelistEntry,
+        ) -> Result<()> {
+            if !self.whitelist.contains(&entry) {
+                return Err(ErrorCode::WhitelistEntryNotFound.into());
+            }
+            self.whitelist.retain(|e| e != &entry);
+            Ok(())
+        }
+
+        #[access_control(whitelist_auth(self, &ctx))]
+        pub fn set_authority(&mut self, ctx: Context<Auth>, new_authority: Pubkey) -> Result<()> {
+            self.authority = new_authority;
+            Ok(())
+        }
     }
 
     #[access_control(CreateVesting::accounts(&ctx, nonce))]
@@ -49,7 +76,7 @@ mod lockup {
         period_count: u64,
         deposit_amount: u64,
         nonce: u8,
-    ) -> Result<(), Error> {
+    ) -> Result<()> {
         if end_ts <= ctx.accounts.clock.unix_timestamp {
             return Err(ErrorCode::InvalidTimestamp.into());
         }
@@ -78,7 +105,7 @@ mod lockup {
         Ok(())
     }
 
-    pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<(), Error> {
+    pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
         // Has the given amount vested?
         if amount
             > calculator::available_for_withdrawal(
@@ -105,32 +132,12 @@ mod lockup {
         Ok(())
     }
 
-    #[access_control(whitelist_has_capacity(&ctx))]
-    pub fn whitelist_add(ctx: Context<WhitelistAdd>, entry: WhitelistEntry) -> Result<(), Error> {
-        if ctx.accounts.lockup.whitelist.contains(&entry) {
-            return Err(ErrorCode::WhitelistEntryAlreadyExists.into());
-        }
-        ctx.accounts.lockup.whitelist.push(entry);
-        Ok(())
-    }
-
-    pub fn whitelist_delete(
-        ctx: Context<WhitelistAdd>,
-        entry: WhitelistEntry,
-    ) -> Result<(), Error> {
-        if !ctx.accounts.lockup.whitelist.contains(&entry) {
-            return Err(ErrorCode::WhitelistEntryNotFound.into());
-        }
-        ctx.accounts.lockup.whitelist.retain(|e| e != &entry);
-        Ok(())
-    }
-
     // Sends funds from the lockup program to a whitelisted program.
     pub fn whitelist_withdraw(
         ctx: Context<WhitelistWithdraw>,
         instruction_data: Vec<u8>,
         amount: u64,
-    ) -> Result<(), Error> {
+    ) -> Result<()> {
         let before_amount = ctx.accounts.transfer.vault.amount;
         whitelist_relay_cpi(
             &ctx.accounts.transfer,
@@ -155,7 +162,7 @@ mod lockup {
     pub fn whitelist_deposit(
         ctx: Context<WhitelistDeposit>,
         instruction_data: Vec<u8>,
-    ) -> Result<(), Error> {
+    ) -> Result<()> {
         let before_amount = ctx.accounts.transfer.vault.amount;
         whitelist_relay_cpi(
             &ctx.accounts.transfer,
@@ -180,7 +187,7 @@ mod lockup {
     }
 
     // Convenience function for UI's to calculate the withdrawalable amount.
-    pub fn available_for_withdrawal(ctx: Context<AvailableForWithdrawal>) -> Result<(), Error> {
+    pub fn available_for_withdrawal(ctx: Context<AvailableForWithdrawal>) -> Result<()> {
         let available = calculator::available_for_withdrawal(
             &ctx.accounts.vesting,
             ctx.accounts.clock.unix_timestamp,
@@ -192,9 +199,7 @@ mod lockup {
 }
 
 #[derive(Accounts)]
-pub struct SetAuthority<'info> {
-    #[account(mut, has_one = authority)]
-    lockup: ProgramState<'info, Lockup>,
+pub struct Auth<'info> {
     #[account(signer)]
     authority: AccountInfo<'info>,
 }
@@ -219,7 +224,7 @@ pub struct CreateVesting<'info> {
 }
 
 impl<'info> CreateVesting<'info> {
-    fn accounts(ctx: &Context<CreateVesting>, nonce: u8) -> Result<(), Error> {
+    fn accounts(ctx: &Context<CreateVesting>, nonce: u8) -> Result<()> {
         let vault_authority = Pubkey::create_program_address(
             &[
                 ctx.accounts.vesting.to_account_info().key.as_ref(),
@@ -256,22 +261,6 @@ pub struct Withdraw<'info> {
     clock: Sysvar<'info, Clock>,
 }
 
-#[derive(Accounts)]
-pub struct WhitelistAdd<'info> {
-    #[account(mut, has_one = authority)]
-    lockup: ProgramState<'info, Lockup>,
-    #[account(signer)]
-    authority: AccountInfo<'info>,
-}
-
-#[derive(Accounts)]
-pub struct WhitelistDelete<'info> {
-    #[account(mut, has_one = authority)]
-    lockup: ProgramState<'info, Lockup>,
-    #[account(signer)]
-    authority: AccountInfo<'info>,
-}
-
 #[derive(Accounts)]
 pub struct WhitelistWithdraw<'info> {
     transfer: WhitelistTransfer<'info>,
@@ -374,6 +363,8 @@ pub enum ErrorCode {
     WhitelistWithdrawLimit,
     #[msg("Whitelist entry not found.")]
     WhitelistEntryNotFound,
+    #[msg("You do not have sufficient permissions to perform this action.")]
+    Unauthorized,
 }
 
 impl<'a, 'b, 'c, 'info> From<&mut CreateVesting<'info>>
@@ -402,19 +393,12 @@ impl<'a, 'b, 'c, 'info> From<&Withdraw<'info>> for CpiContext<'a, 'b, 'c, 'info,
     }
 }
 
-fn whitelist_has_capacity(ctx: &Context<WhitelistAdd>) -> Result<(), Error> {
-    if ctx.accounts.lockup.whitelist.len() == lockup::Lockup::WHITELIST_SIZE {
-        return Err(ErrorCode::WhitelistFull.into());
-    }
-    Ok(())
-}
-
 #[access_control(is_whitelisted(transfer))]
 pub fn whitelist_relay_cpi<'info>(
     transfer: &WhitelistTransfer,
     remaining_accounts: &[AccountInfo<'info>],
     instruction_data: Vec<u8>,
-) -> Result<(), Error> {
+) -> Result<()> {
     let mut meta_accounts = vec![
         AccountMeta::new_readonly(*transfer.vesting.to_account_info().key, false),
         AccountMeta::new(*transfer.vault.to_account_info().key, false),
@@ -454,7 +438,7 @@ pub fn whitelist_relay_cpi<'info>(
         .map_err(Into::into)
 }
 
-pub fn is_whitelisted<'info>(transfer: &WhitelistTransfer<'info>) -> Result<(), Error> {
+pub fn is_whitelisted<'info>(transfer: &WhitelistTransfer<'info>) -> Result<()> {
     if !transfer.lockup.whitelist.contains(&WhitelistEntry {
         program_id: *transfer.whitelisted_program.key,
     }) {
@@ -462,3 +446,10 @@ pub fn is_whitelisted<'info>(transfer: &WhitelistTransfer<'info>) -> Result<(),
     }
     Ok(())
 }
+
+fn whitelist_auth(lockup: &Lockup, ctx: &Context<Auth>) -> Result<()> {
+    if &lockup.authority != ctx.accounts.authority.key {
+        return Err(ErrorCode::Unauthorized.into());
+    }
+    Ok(())
+}

+ 9 - 2
examples/lockup/programs/registry/src/lib.rs

@@ -19,8 +19,10 @@ mod registry {
     }
 
     impl Registry {
-        pub fn new<'info>(lockup_program: Pubkey) -> Result<Self, Error> {
-            Ok(Registry { lockup_program })
+        pub fn new<'info>(ctx: Context<Ctor>) -> Result<Self, Error> {
+            Ok(Registry {
+                lockup_program: *ctx.accounts.lockup_program.key,
+            })
         }
     }
 
@@ -598,6 +600,11 @@ pub struct BalanceSandboxAccounts<'info> {
     vault_pw: CpiAccount<'info, TokenAccount>,
 }
 
+#[derive(Accounts)]
+pub struct Ctor<'info> {
+    lockup_program: AccountInfo<'info>,
+}
+
 #[derive(Accounts)]
 pub struct UpdateMember<'info> {
     #[account(mut, has_one = beneficiary)]

+ 18 - 17
examples/lockup/tests/lockup.js

@@ -28,7 +28,11 @@ describe("Lockup and Registry", () => {
   });
 
   it("Is initialized!", async () => {
-    await lockup.state.rpc.new(provider.wallet.publicKey);
+    await lockup.state.rpc.new({
+      accounts: {
+        authority: provider.wallet.publicKey,
+      },
+    });
 
     lockupAddress = await lockup.state.address();
     const lockupAccount = await lockup.state();
@@ -42,30 +46,27 @@ describe("Lockup and Registry", () => {
 
   it("Deletes the default whitelisted addresses", async () => {
     const defaultEntry = { programId: new anchor.web3.PublicKey() };
-    await lockup.rpc.whitelistDelete(defaultEntry, {
+    await lockup.state.rpc.whitelistDelete(defaultEntry, {
       accounts: {
         authority: provider.wallet.publicKey,
-        lockup: lockupAddress,
       },
     });
   });
 
   it("Sets a new authority", async () => {
     const newAuthority = new anchor.web3.Account();
-    await lockup.rpc.setAuthority(newAuthority.publicKey, {
+    await lockup.state.rpc.setAuthority(newAuthority.publicKey, {
       accounts: {
         authority: provider.wallet.publicKey,
-        lockup: lockupAddress,
       },
     });
 
     let lockupAccount = await lockup.state();
     assert.ok(lockupAccount.authority.equals(newAuthority.publicKey));
 
-    await lockup.rpc.setAuthority(provider.wallet.publicKey, {
+    await lockup.state.rpc.setAuthority(provider.wallet.publicKey, {
       accounts: {
         authority: newAuthority.publicKey,
-        lockup: lockupAddress,
       },
       signers: [newAuthority],
     });
@@ -96,20 +97,19 @@ describe("Lockup and Registry", () => {
 
     const accounts = {
       authority: provider.wallet.publicKey,
-      lockup: lockupAddress,
     };
 
-    await lockup.rpc.whitelistAdd(e0, { accounts });
+    await lockup.state.rpc.whitelistAdd(e0, { accounts });
 
     let lockupAccount = await lockup.state();
 
     assert.ok(lockupAccount.whitelist.length === 1);
     assert.deepEqual(lockupAccount.whitelist, [e0]);
 
-    await lockup.rpc.whitelistAdd(e1, { accounts });
-    await lockup.rpc.whitelistAdd(e2, { accounts });
-    await lockup.rpc.whitelistAdd(e3, { accounts });
-    await lockup.rpc.whitelistAdd(e4, { accounts });
+    await lockup.state.rpc.whitelistAdd(e1, { accounts });
+    await lockup.state.rpc.whitelistAdd(e2, { accounts });
+    await lockup.state.rpc.whitelistAdd(e3, { accounts });
+    await lockup.state.rpc.whitelistAdd(e4, { accounts });
 
     lockupAccount = await lockup.state();
 
@@ -117,7 +117,7 @@ describe("Lockup and Registry", () => {
 
     await assert.rejects(
       async () => {
-        await lockup.rpc.whitelistAdd(e5, { accounts });
+        await lockup.state.rpc.whitelistAdd(e5, { accounts });
       },
       (err) => {
         assert.equal(err.code, 108);
@@ -128,10 +128,9 @@ describe("Lockup and Registry", () => {
   });
 
   it("Removes from the whitelist", async () => {
-    await lockup.rpc.whitelistDelete(e0, {
+    await lockup.state.rpc.whitelistDelete(e0, {
       accounts: {
         authority: provider.wallet.publicKey,
-        lockup: lockupAddress,
       },
     });
     let lockupAccount = await lockup.state();
@@ -284,7 +283,9 @@ describe("Lockup and Registry", () => {
   });
 
   it("Initializes registry's global state", async () => {
-    await registry.state.rpc.new(lockup.programId);
+    await registry.state.rpc.new({
+      accounts: { lockupProgram: lockup.programId },
+    });
 
     const state = await registry.state();
     assert.ok(state.lockupProgram.equals(lockup.programId));

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

@@ -4,7 +4,7 @@
 use anchor_lang::prelude::*;
 
 #[program]
-mod basic_4 {
+pub mod basic_4 {
     use super::*;
 
     #[state]
@@ -13,9 +13,12 @@ mod basic_4 {
     }
 
     impl MyProgram {
-        pub fn new(data: u64) -> Result<Self, ProgramError> {
+        pub fn new(ctx: Context<Ctor>, data: u64) -> Result<Self, ProgramError> {
             Ok(Self { data })
         }
     }
 }
+
+#[derive(Accounts)]
+pub struct Ctor {}
 // #endregion code

+ 1 - 1
src/lib.rs

@@ -41,7 +41,7 @@ pub use crate::context::{Context, CpiContext};
 pub use crate::cpi_account::CpiAccount;
 pub use crate::ctor::Ctor;
 pub use crate::program_account::ProgramAccount;
-pub use crate::state::ProgramState;
+pub use crate::state::{ProgramState, ProgramStateAccounts};
 pub use crate::sysvar::Sysvar;
 pub use anchor_attribute_access_control::access_control;
 pub use anchor_attribute_account::account;

+ 16 - 2
src/state.rs

@@ -1,6 +1,6 @@
 use crate::{
-    AccountDeserialize, AccountSerialize, Accounts, AccountsExit, CpiAccount, ToAccountInfo,
-    ToAccountInfos, ToAccountMetas,
+    self as anchor_lang, AccountDeserialize, AccountSerialize, Accounts, AccountsExit, Context,
+    CpiAccount, ToAccountInfo, ToAccountInfos, ToAccountMetas,
 };
 use solana_program::account_info::AccountInfo;
 use solana_program::entrypoint::ProgramResult;
@@ -9,6 +9,20 @@ use solana_program::program_error::ProgramError;
 use solana_program::pubkey::Pubkey;
 use std::ops::{Deref, DerefMut};
 
+/// Trait implemented by program state structs, providing the execution context
+/// for the currently executing program.
+pub trait ProgramStateContext<'a, 'b, 'c, 'info> {
+    fn context() -> Context<'a, 'b, 'c, 'info, ProgramStateAccounts<'info>>;
+}
+
+/// Accounts given to instructions defined on a state struct, excluding the
+/// state account itself.
+#[derive(Accounts)]
+pub struct ProgramStateAccounts<'info> {
+    #[account(signer)]
+    pub payer: AccountInfo<'info>,
+}
+
 /// Boxed container for the program state singleton.
 #[derive(Clone)]
 pub struct ProgramState<'info, T: AccountSerialize + AccountDeserialize + Clone> {

+ 1 - 1
src/sysvar.rs

@@ -38,7 +38,7 @@ impl<'info, T: solana_program::sysvar::Sysvar> Accounts<'info> for Sysvar<'info,
 }
 
 impl<'info, T: solana_program::sysvar::Sysvar> ToAccountMetas for Sysvar<'info, T> {
-    fn to_account_metas(&self, is_mut_signer: Option<bool>) -> Vec<AccountMeta> {
+    fn to_account_metas(&self, _is_signer: Option<bool>) -> Vec<AccountMeta> {
         vec![AccountMeta::new_readonly(*self.info.key, false)]
     }
 }

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

@@ -141,7 +141,7 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
     quote! {
         impl#combined_generics anchor_lang::Accounts#trait_generics for #name#strct_generics {
             #[inline(never)]
-            fn try_accounts(program_id: &anchor_lang::solana_program::pubkey::Pubkey, accounts: &mut &[anchor_lang::solana_program::account_info::AccountInfo<'info>]) -> Result<Self, anchor_lang::solana_program::program_error::ProgramError> {
+            fn try_accounts(program_id: &anchor_lang::solana_program::pubkey::Pubkey, accounts: &mut &[anchor_lang::solana_program::account_info::AccountInfo<'info>]) -> std::result::Result<Self, anchor_lang::solana_program::program_error::ProgramError> {
                 // Deserialize each account.
                 #(#deser_fields)*
 

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

@@ -18,7 +18,7 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream {
         #error_enum
 
         impl std::fmt::Display for #enum_name {
-            fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+            fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
                 <Self as std::fmt::Debug>::fmt(self, fmt)
             }
         }

+ 184 - 31
syn/src/codegen/program.rs

@@ -1,5 +1,5 @@
 use crate::parser;
-use crate::{Program, Rpc, State};
+use crate::{Program, RpcArg, State};
 use heck::CamelCase;
 use quote::quote;
 
@@ -42,6 +42,7 @@ pub fn generate(program: Program) -> proc_macro2::TokenStream {
         #cpi
     }
 }
+
 pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
     let ctor_state_dispatch_arm = match &program.state {
         None => quote! { /* no-op */ },
@@ -49,21 +50,33 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
             let variant_arm = generate_ctor_variant(program, state);
             let ctor_args = generate_ctor_args(state);
             quote! {
-                __private::instruction::#variant_arm => {
-                    __private::__ctor(program_id, accounts, #(#ctor_args),*)
-                }
+                __private::instruction::#variant_arm => __private::__ctor(program_id, accounts, #(#ctor_args),*),
             }
         }
     };
     let state_dispatch_arms: Vec<proc_macro2::TokenStream> = match &program.state {
-        None => vec![quote! { /* no-op */}],
+        None => vec![],
         Some(s) => s
             .methods
             .iter()
-            .map(|m| {
+            .map(|rpc: &crate::StateRpc| {
+                let rpc_arg_names: Vec<&syn::Ident> =
+                    rpc.args.iter().map(|arg| &arg.name).collect();
+                let variant_arm: proc_macro2::TokenStream = generate_ix_variant(
+                    program,
+                    rpc.raw_method.sig.ident.to_string(),
+                    &rpc.args,
+                    true,
+                );
+                let rpc_name: proc_macro2::TokenStream = {
+                    let name = &rpc.raw_method.sig.ident.to_string();
+                    format!("__{}", name).parse().unwrap()
+                };
                 quote! {
-                        // todo
-                }
+                    __private::instruction::#variant_arm => {
+												__private::#rpc_name(program_id, accounts, #(#rpc_arg_names),*)
+										}
+								}
             })
             .collect(),
     };
@@ -72,7 +85,12 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
         .iter()
         .map(|rpc| {
             let rpc_arg_names: Vec<&syn::Ident> = rpc.args.iter().map(|arg| &arg.name).collect();
-            let variant_arm = generate_ix_variant(program, rpc);
+            let variant_arm = generate_ix_variant(
+                program,
+                rpc.raw_method.sig.ident.to_string(),
+                &rpc.args,
+                false,
+            );
             let rpc_name = &rpc.raw_method.sig.ident;
             quote! {
                 __private::instruction::#variant_arm => {
@@ -85,9 +103,7 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
     quote! {
         match ix {
             #ctor_state_dispatch_arm
-
             #(#state_dispatch_arms),*
-
             #(#dispatch_arms),*
         }
     }
@@ -105,14 +121,27 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
             let ctor_untyped_args = generate_ctor_args(state);
             let name = &state.strct.ident;
             let mod_name = &program.name;
+            let anchor_ident = &state.ctor_anchor;
             quote! {
                 #[inline(never)]
                 pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], #(#ctor_typed_args),*) -> ProgramResult {
-                    let mut accounts: &[AccountInfo] = accounts;
-                    let ctor_accounts = anchor_lang::Ctor::try_accounts(program_id, &mut accounts)?;
+                    let mut remaining_accounts: &[AccountInfo] = accounts;
 
-                    let instance = #mod_name::#name::new(#(#ctor_untyped_args),*)?;
+                    // Deserialize accounts.
+                    let ctor_accounts = anchor_lang::Ctor::try_accounts(program_id, &mut remaining_accounts)?;
+                    let mut ctor_user_def_accounts = #anchor_ident::try_accounts(program_id, &mut remaining_accounts)?;
+
+                    // Invoke the ctor.
+                    let instance = #mod_name::#name::new(
+                        anchor_lang::Context::new(
+                            program_id,
+                            &mut ctor_user_def_accounts,
+                            remaining_accounts,
+                        ),
+                        #(#ctor_untyped_args),*
+                    )?;
 
+                    // Create the solana account for the ctor data.
                     let from = ctor_accounts.from.key;
                     let (base, nonce) = Pubkey::find_program_address(&[], ctor_accounts.program.key);
                     let seed = anchor_lang::ProgramState::<#name>::seed();
@@ -122,8 +151,6 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
                     let space = 8 + instance.try_to_vec().map_err(|_| ProgramError::Custom(1))?.len();
                     let lamports = ctor_accounts.rent.minimum_balance(space);
                     let seeds = &[&[nonce][..]];
-
-                    // Create the new program owned account (from within the program).
                     let ix = anchor_lang::solana_program::system_instruction::create_account_with_seed(
                         from,
                         &to,
@@ -145,6 +172,7 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
                     )?;
 
                     // Serialize the state and save it to storage.
+                    ctor_user_def_accounts.exit(program_id)?;
                     let mut data = ctor_accounts.to.try_borrow_mut_data()?;
                     let dst: &mut [u8] = &mut data;
                     let mut cursor = std::io::Cursor::new(dst);
@@ -155,6 +183,71 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
             }
         }
     };
+    let non_inlined_state_handlers: Vec<proc_macro2::TokenStream> = match &program.state {
+        None => vec![],
+        Some(state) => state
+            .methods
+            .iter()
+            .map(|rpc| {
+                let rpc_params: Vec<_> = rpc.args.iter().map(|arg| &arg.raw_arg).collect();
+                let rpc_arg_names: Vec<&syn::Ident> =
+                    rpc.args.iter().map(|arg| &arg.name).collect();
+                let private_rpc_name: proc_macro2::TokenStream = {
+                    let n = format!("__{}", &rpc.raw_method.sig.ident.to_string());
+                    n.parse().unwrap()
+                };
+                let rpc_name = &rpc.raw_method.sig.ident;
+                let state_ty: proc_macro2::TokenStream = state.name.parse().unwrap();
+                let anchor_ident = &rpc.anchor_ident;
+                quote! {
+                    #[inline(never)]
+                    pub fn #private_rpc_name(
+                        program_id: &Pubkey,
+                        accounts: &[AccountInfo],
+                        #(#rpc_params),*
+                    ) -> ProgramResult {
+
+                        let mut remaining_accounts: &[AccountInfo] = accounts;
+                        if remaining_accounts.len() == 0 {
+                            return Err(ProgramError::Custom(1)); // todo
+                        }
+
+                        // Deserialize the program state account.
+                        let state_account = &remaining_accounts[0];
+                        let mut state: #state_ty = {
+                            let data = state_account.try_borrow_data()?;
+                            let mut sliced: &[u8] = &data;
+                            anchor_lang::AccountDeserialize::try_deserialize(&mut sliced)?
+                        };
+
+                        remaining_accounts = &remaining_accounts[1..];
+
+                        // Deserialize the program's execution context.
+                        let mut accounts = #anchor_ident::try_accounts(
+                            program_id,
+                            &mut remaining_accounts,
+                        )?;
+                        let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
+
+                        // Execute user defined function.
+                        state.#rpc_name(
+                            ctx,
+                            #(#rpc_arg_names),*
+                        )?;
+
+                        // Serialize the state and save it to storage.
+                        accounts.exit(program_id)?;
+                        let mut data = state_account.try_borrow_mut_data()?;
+                        let dst: &mut [u8] = &mut data;
+                        let mut cursor = std::io::Cursor::new(dst);
+                        state.try_serialize(&mut cursor)?;
+
+                        Ok(())
+                    }
+                }
+            })
+            .collect(),
+    };
     let non_inlined_handlers: Vec<proc_macro2::TokenStream> = program
         .rpcs
         .iter()
@@ -185,7 +278,7 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
 
     quote! {
         #non_inlined_ctor
-
+        #(#non_inlined_state_handlers)*
         #(#non_inlined_handlers)*
     }
 }
@@ -213,7 +306,7 @@ pub fn generate_ctor_typed_variant_with_comma(program: &Program) -> proc_macro2:
             let ctor_args = generate_ctor_typed_args(state);
             if ctor_args.len() == 0 {
                 quote! {
-                    __Ctor
+                    __Ctor,
                 }
             } else {
                 quote! {
@@ -232,9 +325,16 @@ fn generate_ctor_typed_args(state: &State) -> Vec<syn::PatType> {
         .sig
         .inputs
         .iter()
-        .map(|arg: &syn::FnArg| match arg {
-            syn::FnArg::Typed(pat_ty) => pat_ty.clone(),
-            _ => panic!(""),
+        .filter_map(|arg: &syn::FnArg| match arg {
+            syn::FnArg::Typed(pat_ty) => {
+                let mut arg_str = parser::tts_to_string(&pat_ty.ty);
+                arg_str.retain(|c| !c.is_whitespace());
+                if arg_str.starts_with("Context<") {
+                    return None;
+                }
+                Some(pat_ty.clone())
+            }
+            _ => panic!("Invalid syntaxe,"),
         })
         .collect()
 }
@@ -245,21 +345,38 @@ fn generate_ctor_args(state: &State) -> Vec<Box<syn::Pat>> {
         .sig
         .inputs
         .iter()
-        .map(|arg: &syn::FnArg| match arg {
-            syn::FnArg::Typed(pat_ty) => pat_ty.pat.clone(),
+        .filter_map(|arg: &syn::FnArg| match arg {
+            syn::FnArg::Typed(pat_ty) => {
+                let mut arg_str = parser::tts_to_string(&pat_ty.ty);
+                arg_str.retain(|c| !c.is_whitespace());
+                if arg_str.starts_with("Context<") {
+                    return None;
+                }
+                Some(pat_ty.pat.clone())
+            }
             _ => panic!(""),
         })
         .collect()
 }
 
-pub fn generate_ix_variant(program: &Program, rpc: &Rpc) -> proc_macro2::TokenStream {
+pub fn generate_ix_variant(
+    program: &Program,
+    name: String,
+    args: &[RpcArg],
+    underscore: bool,
+) -> 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 {
+    let rpc_arg_names: Vec<&syn::Ident> = args.iter().map(|arg| &arg.name).collect();
+    let rpc_name_camel: proc_macro2::TokenStream = {
+        let n = name.to_camel_case();
+        if underscore {
+            format!("__{}", n).parse().unwrap()
+        } else {
+            n.parse().unwrap()
+        }
+    };
+
+    if args.len() == 0 {
         quote! {
             #enum_name::#rpc_name_camel
         }
@@ -282,6 +399,36 @@ pub fn generate_methods(program: &Program) -> proc_macro2::TokenStream {
 pub fn generate_instruction(program: &Program) -> proc_macro2::TokenStream {
     let enum_name = instruction_enum_name(program);
     let ctor_variant = generate_ctor_typed_variant_with_comma(program);
+    let state_method_variants: Vec<proc_macro2::TokenStream> = match &program.state {
+        None => vec![],
+        Some(state) => state
+            .methods
+            .iter()
+            .map(|method| {
+                let rpc_name_camel: proc_macro2::TokenStream = {
+                    let name = format!(
+                        "__{}",
+                        &method.raw_method.sig.ident.to_string().to_camel_case(),
+                    );
+                    name.parse().unwrap()
+                };
+                let raw_args: Vec<&syn::PatType> =
+                    method.args.iter().map(|arg| &arg.raw_arg).collect();
+                // If no args, output a "unit" variant instead of a struct variant.
+                if method.args.len() == 0 {
+                    quote! {
+                        #rpc_name_camel,
+                    }
+                } else {
+                    quote! {
+                        #rpc_name_camel {
+                            #(#raw_args),*
+                        },
+                    }
+                }
+            })
+            .collect(),
+    };
     let variants: Vec<proc_macro2::TokenStream> = program
         .rpcs
         .iter()
@@ -312,6 +459,7 @@ pub fn generate_instruction(program: &Program) -> proc_macro2::TokenStream {
             #[derive(AnchorSerialize, AnchorDeserialize)]
             pub enum #enum_name {
                 #ctor_variant
+                #(#state_method_variants)*
                 #(#variants),*
             }
         }
@@ -332,7 +480,12 @@ fn generate_cpi(program: &Program) -> proc_macro2::TokenStream {
         .map(|rpc| {
             let accounts_ident = &rpc.anchor_ident;
             let cpi_method = {
-                let ix_variant = generate_ix_variant(program, rpc);
+                let ix_variant = generate_ix_variant(
+                    program,
+                    rpc.raw_method.sig.ident.to_string(),
+                    &rpc.args,
+                    false,
+                );
                 let method_name = &rpc.ident;
                 let args: Vec<&syn::PatType> = rpc.args.iter().map(|arg| &arg.raw_arg).collect();
                 quote! {

+ 1 - 7
syn/src/idl.rs

@@ -24,13 +24,7 @@ pub struct IdlState {
     pub methods: Vec<IdlStateMethod>,
 }
 
-// IdlStateMethods are similar to instructions, except they only allow
-// for a single account, the state account.
-#[derive(Debug, Serialize, Deserialize)]
-pub struct IdlStateMethod {
-    pub name: String,
-    pub args: Vec<IdlField>,
-}
+pub type IdlStateMethod = IdlInstruction;
 
 #[derive(Debug, Serialize, Deserialize)]
 pub struct IdlInstruction {

+ 10 - 1
syn/src/lib.rs

@@ -27,8 +27,17 @@ pub struct State {
     pub name: String,
     pub strct: syn::ItemStruct,
     pub impl_block: syn::ItemImpl,
-    pub methods: Vec<Rpc>,
+    pub methods: Vec<StateRpc>,
     pub ctor: syn::ImplItemMethod,
+    pub ctor_anchor: syn::Ident, // TODO: consolidate this with ctor above.
+}
+
+#[derive(Debug)]
+pub struct StateRpc {
+    pub raw_method: syn::ImplItemMethod,
+    pub ident: syn::Ident,
+    pub args: Vec<RpcArg>,
+    pub anchor_ident: syn::Ident,
 }
 
 #[derive(Debug)]

+ 40 - 19
syn/src/parser/file.rs

@@ -1,6 +1,6 @@
 use crate::idl::*;
 use crate::parser::{self, accounts, error, program};
-use crate::{AccountsStruct, Rpc};
+use crate::{AccountsStruct, StateRpc};
 use anyhow::Result;
 use heck::MixedCase;
 use quote::ToTokens;
@@ -22,12 +22,23 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
 
     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(&accs)? {
+                acc_names.insert(a);
+            }
+        }
+        acc_names
+    };
+
     let state = p.state.map(|state| {
         let mut methods = state
             .methods
             .iter()
-            .map(|method: &Rpc| {
-                let name = method.ident.to_string();
+            .map(|method: &StateRpc| {
+                let name = method.ident.to_string().to_mixed_case();
                 let args = method
                     .args
                     .iter()
@@ -41,7 +52,13 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
                         }
                     })
                     .collect::<Vec<_>>();
-                IdlStateMethod { name, args }
+                let accounts_strct = accs.get(&method.anchor_ident.to_string()).unwrap();
+                let accounts = accounts_strct.idl_accounts(&accs);
+                IdlStateMethod {
+                    name,
+                    args,
+                    accounts,
+                }
             })
             .collect::<Vec<_>>();
         let ctor = {
@@ -51,6 +68,18 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
                 .sig
                 .inputs
                 .iter()
+                .filter_map(|arg: &syn::FnArg| match arg {
+                    syn::FnArg::Typed(pat_ty) => {
+                        // TODO: this filtering should be donein the parser.
+                        let mut arg_str = parser::tts_to_string(&pat_ty.ty);
+                        arg_str.retain(|c| !c.is_whitespace());
+                        if arg_str.starts_with("Context<") {
+                            return None;
+                        }
+                        Some(arg)
+                    }
+                    _ => None,
+                })
                 .map(|arg: &syn::FnArg| match arg {
                     syn::FnArg::Typed(arg_typed) => {
                         let mut tts = proc_macro2::TokenStream::new();
@@ -64,7 +93,13 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
                     _ => panic!("Invalid syntax"),
                 })
                 .collect();
-            IdlStateMethod { name, args }
+            let accounts_strct = accs.get(&state.ctor_anchor.to_string()).unwrap();
+            let accounts = accounts_strct.idl_accounts(&accs);
+            IdlStateMethod {
+                name,
+                args,
+                accounts,
+            }
         };
 
         methods.insert(0, ctor);
@@ -106,20 +141,6 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
             .collect::<Vec<IdlErrorCode>>()
     });
 
-    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(&accs)? {
-                acc_names.insert(a);
-            }
-        }
-
-        acc_names
-    };
-
     let instructions = p
         .rpcs
         .iter()

+ 56 - 4
syn/src/parser/program.rs

@@ -1,5 +1,5 @@
 use crate::parser;
-use crate::{Program, Rpc, RpcArg, State};
+use crate::{Program, Rpc, RpcArg, State, StateRpc};
 
 pub fn parse(program_mod: syn::ItemMod) -> Program {
     let mod_ident = &program_mod.ident;
@@ -52,13 +52,19 @@ pub fn parse(program_mod: syn::ItemMod) -> Program {
             strct.attrs = vec![];
 
             let impl_block = impl_block.expect("Must exist if struct exists").clone();
-            let ctor = impl_block
+            let (ctor, ctor_anchor) = impl_block
                 .items
                 .iter()
                 .filter_map(|item: &syn::ImplItem| match item {
                     syn::ImplItem::Method(m) => {
                         if m.sig.ident.to_string() == "new" {
-                            Some(m)
+                            let ctx_arg = m.sig.inputs.first().unwrap(); // todo: unwrap.
+                            match ctx_arg {
+                                syn::FnArg::Receiver(_) => panic!("invalid syntax"),
+                                syn::FnArg::Typed(arg) => {
+                                    Some((m.clone(), extract_ident(&arg).clone()))
+                                }
+                            }
                         } else {
                             None
                         }
@@ -68,12 +74,58 @@ pub fn parse(program_mod: syn::ItemMod) -> Program {
                 .next()
                 .expect("Must exist if struct exists")
                 .clone();
+
+            let methods: Vec<StateRpc> = impl_block
+                .items
+                .iter()
+                .filter_map(|item: &syn::ImplItem| match item {
+                    syn::ImplItem::Method(m) => match m.sig.inputs.first() {
+                        None => None,
+                        Some(arg) => match arg {
+                            syn::FnArg::Typed(_) => None,
+                            syn::FnArg::Receiver(_) => {
+                                let mut args = m
+                                    .sig
+                                    .inputs
+                                    .iter()
+                                    .filter_map(|arg| match arg {
+                                        syn::FnArg::Receiver(_) => None,
+                                        syn::FnArg::Typed(arg) => Some(arg),
+                                    })
+                                    .map(|raw_arg| {
+                                        let ident = match &*raw_arg.pat {
+                                            syn::Pat::Ident(ident) => &ident.ident,
+                                            _ => panic!("invalid syntax"),
+                                        };
+                                        RpcArg {
+                                            name: ident.clone(),
+                                            raw_arg: raw_arg.clone(),
+                                        }
+                                    })
+                                    .collect::<Vec<RpcArg>>();
+                                // Remove the Anchor accounts argument
+                                let anchor = args.remove(0);
+                                let anchor_ident = extract_ident(&anchor.raw_arg).clone();
+
+                                Some(StateRpc {
+                                    raw_method: m.clone(),
+                                    ident: m.sig.ident.clone(),
+                                    args,
+                                    anchor_ident,
+                                })
+                            }
+                        },
+                    },
+                    _ => None,
+                })
+                .collect();
             State {
                 name: strct.ident.to_string(),
                 strct: strct.clone(),
                 impl_block,
                 ctor,
-                methods: vec![], // todo
+                ctor_anchor,
+                methods,
             }
         })
     };

+ 1 - 4
ts/src/idl.ts

@@ -21,10 +21,7 @@ export type IdlState = {
   methods: IdlStateMethod[];
 };
 
-export type IdlStateMethod = {
-  name: string;
-  args: IdlField[];
-};
+export type IdlStateMethod = IdlInstruction;
 
 export type IdlAccountItem = IdlAccount | IdlAccounts;
 

+ 87 - 46
ts/src/rpc.ts

@@ -131,7 +131,7 @@ export class RpcFactory {
     const rpcs: Rpcs = {};
     const ixFns: Ixs = {};
     const txFns: Txs = {};
-    const state = RpcFactory.buildState(idl, coder, programId);
+    const state = RpcFactory.buildState(idl, coder, programId, idlErrors);
 
     idl.instructions.forEach((idlIx) => {
       // Function to create a raw `TransactionInstruction`.
@@ -157,7 +157,8 @@ export class RpcFactory {
   private static buildState(
     idl: Idl,
     coder: Coder,
-    programId: PublicKey
+    programId: PublicKey,
+    idlErrors: Map<number, string>
   ): State | undefined {
     if (idl.state === undefined) {
       return undefined;
@@ -172,52 +173,92 @@ export class RpcFactory {
 
     const rpc: Rpcs = {};
     idl.state.methods.forEach((m: IdlStateMethod) => {
-      if (m.name !== "new") {
-        throw new Error("State struct mutatation not yet implemented.");
-      }
-      // Ctor `new` method.
-      rpc[m.name] = async (...args: any[]): Promise<TransactionSignature> => {
-        const tx = new Transaction();
-        const [programSigner, _nonce] = await PublicKey.findProgramAddress(
-          [],
-          programId
-        );
-        const ix = new TransactionInstruction({
-          keys: [
-            {
-              pubkey: getProvider().wallet.publicKey,
-              isWritable: false,
-              isSigner: true,
-            },
+      if (m.name === "new") {
+        // Ctor `new` method.
+        rpc[m.name] = async (...args: any[]): Promise<TransactionSignature> => {
+          const [ixArgs, ctx] = splitArgsAndCtx(m, [...args]);
+          const tx = new Transaction();
+          const [programSigner, _nonce] = await PublicKey.findProgramAddress(
+            [],
+            programId
+          );
+          const ix = new TransactionInstruction({
+            keys: [
+              {
+                pubkey: getProvider().wallet.publicKey,
+                isWritable: false,
+                isSigner: true,
+              },
+              { pubkey: await address(), isWritable: true, isSigner: false },
+              { pubkey: programSigner, isWritable: false, isSigner: false },
+              {
+                pubkey: SystemProgram.programId,
+                isWritable: false,
+                isSigner: false,
+              },
+
+              { pubkey: programId, isWritable: false, isSigner: false },
+              {
+                pubkey: SYSVAR_RENT_PUBKEY,
+                isWritable: false,
+                isSigner: false,
+              },
+            ].concat(RpcFactory.accountsArray(ctx.accounts, m.accounts)),
+            programId,
+            data: coder.instruction.encode(toInstruction(m, ...ixArgs)),
+          });
+
+          tx.add(ix);
+
+          const provider = getProvider();
+          if (provider === null) {
+            throw new Error("Provider not found");
+          }
+          try {
+            const txSig = await provider.send(tx, ctx.signers, ctx.options);
+            return txSig;
+          } catch (err) {
+            let translatedErr = translateError(idlErrors, err);
+            if (translatedErr === null) {
+              throw err;
+            }
+            throw translatedErr;
+          }
+        };
+      } else {
+        rpc[m.name] = async (...args: any[]): Promise<TransactionSignature> => {
+          const [ixArgs, ctx] = splitArgsAndCtx(m, [...args]);
+          validateAccounts(m.accounts, ctx.accounts);
+          const tx = new Transaction();
+
+          const keys = [
             { pubkey: await address(), isWritable: true, isSigner: false },
-            { pubkey: programSigner, isWritable: false, isSigner: false },
-            {
-              pubkey: SystemProgram.programId,
-              isWritable: false,
-              isSigner: false,
-            },
-
-            { pubkey: programId, isWritable: false, isSigner: false },
-            { pubkey: SYSVAR_RENT_PUBKEY, isWritable: false, isSigner: false },
-          ],
-          programId,
-          data: coder.instruction.encode(toInstruction(m, ...args)),
-        });
+          ].concat(RpcFactory.accountsArray(ctx.accounts, m.accounts));
 
-        tx.add(ix);
-
-        const provider = getProvider();
-        if (provider === null) {
-          throw new Error("Provider not found");
-        }
-        try {
-          const txSig = await provider.send(tx);
-          return txSig;
-        } catch (err) {
-          // TODO: translate error.
-          throw err;
-        }
-      };
+          tx.add(
+            new TransactionInstruction({
+              keys,
+              programId,
+              data: coder.instruction.encode(toInstruction(m, ...ixArgs)),
+            })
+          );
+
+          const provider = getProvider();
+          if (provider === null) {
+            throw new Error("Provider not found");
+          }
+          try {
+            const txSig = await provider.send(tx, ctx.signers, ctx.options);
+            return txSig;
+          } catch (err) {
+            let translatedErr = translateError(idlErrors, err);
+            if (translatedErr === null) {
+              throw err;
+            }
+            throw translatedErr;
+          }
+        };
+      }
     });
 
     // Fetches the state object from the blockchain.