Browse Source

lang, spl: Program and Signer types (#705)

Armani Ferrante 4 years ago
parent
commit
b1ef7431ec
37 changed files with 472 additions and 106 deletions
  1. 5 0
      CHANGELOG.md
  2. 3 0
      examples/tutorial/basic-0/Anchor.toml
  3. 2 0
      examples/tutorial/basic-0/programs/basic-0/src/lib.rs
  4. 3 2
      examples/tutorial/basic-1/programs/basic-1/src/lib.rs
  5. 4 7
      examples/tutorial/basic-2/programs/basic-2/src/lib.rs
  6. 5 5
      examples/tutorial/basic-3/programs/puppet-master/src/lib.rs
  7. 7 9
      examples/tutorial/basic-3/programs/puppet/src/lib.rs
  8. 1 1
      examples/tutorial/basic-3/tests/basic-3.js
  9. 1 2
      examples/tutorial/basic-4/programs/basic-4/src/lib.rs
  10. 6 0
      lang/src/error.rs
  11. 13 2
      lang/src/lib.rs
  12. 97 0
      lang/src/program.rs
  13. 97 0
      lang/src/signer.rs
  14. 24 0
      lang/src/system_program.rs
  15. 3 3
      lang/syn/src/codegen/accounts/constraints.rs
  16. 26 0
      lang/syn/src/codegen/program/entry.rs
  17. 22 0
      lang/syn/src/lib.rs
  18. 9 0
      lang/syn/src/parser/accounts/mod.rs
  19. 29 0
      spl/src/dex/cpi.rs
  20. 19 0
      spl/src/token.rs
  21. 1 1
      tests/cfo/deps/stake
  22. 49 68
      tests/cfo/programs/cfo/src/lib.rs
  23. 3 2
      tests/lockup/programs/lockup/src/lib.rs
  24. 4 0
      tests/permissioned-markets/Anchor.toml
  25. 2 0
      tests/permissioned-markets/programs/permissioned-markets-middleware/src/lib.rs
  26. 5 4
      tests/permissioned-markets/programs/permissioned-markets/src/lib.rs
  27. 3 0
      tests/pyth/Anchor.toml
  28. 2 0
      tests/pyth/programs/pyth/src/lib.rs
  29. 3 0
      tests/spl/token-proxy/Anchor.toml
  30. 2 0
      tests/spl/token-proxy/programs/token-proxy/src/lib.rs
  31. 3 0
      tests/swap/Anchor.toml
  32. 2 0
      tests/swap/programs/swap/src/lib.rs
  33. 3 0
      tests/sysvars/Anchor.toml
  34. 2 0
      tests/sysvars/programs/sysvars/src/lib.rs
  35. 3 0
      tests/typescript/Anchor.toml
  36. 2 0
      tests/typescript/programs/typescript/src/lib.rs
  37. 7 0
      ts/src/error.ts

+ 5 - 0
CHANGELOG.md

@@ -11,6 +11,11 @@ incremented for features.
 
 ## [Unreleased]
 
+### Features
+
+* lang: `Program` type introduced for executable accounts ([#705](https://github.com/project-serum/anchor/pull/705)).
+* lang: `Signer` type introduced for signing accounts where data is not used ([#705](https://github.com/project-serum/anchor/pull/705)).
+
 ### Breaking Changes
 
 * lang: `#[account(owner = <pubkey>)]` now requires a `Pubkey` instead of an account ([#691](https://github.com/project-serum/anchor/pull/691)).

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

@@ -2,5 +2,8 @@
 cluster = "localnet"
 wallet = "~/.config/solana/id.json"
 
+[programs.localnet]
+basic_0 = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
+
 [scripts]
 test = "mocha -t 1000000 tests/"

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

@@ -1,5 +1,7 @@
 use anchor_lang::prelude::*;
 
+declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
+
 #[program]
 mod basic_0 {
     use super::*;

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

@@ -23,8 +23,9 @@ mod basic_1 {
 pub struct Initialize<'info> {
     #[account(init, payer = user, space = 8 + 8)]
     pub my_account: Account<'info, MyAccount>,
-    pub user: AccountInfo<'info>,
-    pub system_program: AccountInfo<'info>,
+    #[account(mut)]
+    pub user: Signer<'info>,
+    pub system_program: Program<'info, System>,
 }
 
 #[derive(Accounts)]

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

@@ -1,5 +1,4 @@
 use anchor_lang::prelude::*;
-use anchor_lang::solana_program::system_program;
 
 declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
 
@@ -25,18 +24,16 @@ mod basic_2 {
 pub struct Create<'info> {
     #[account(init, payer = user, space = 8 + 40)]
     pub counter: Account<'info, Counter>,
-    #[account(signer)]
-    pub user: AccountInfo<'info>,
-    #[account(address = system_program::ID)]
-    pub system_program: AccountInfo<'info>,
+    #[account(mut)]
+    pub user: Signer<'info>,
+    pub system_program: Program<'info, System>,
 }
 
 #[derive(Accounts)]
 pub struct Increment<'info> {
     #[account(mut, has_one = authority)]
     pub counter: Account<'info, Counter>,
-    #[account(signer)]
-    pub authority: AccountInfo<'info>,
+    pub authority: Signer<'info>,
 }
 
 #[account]

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

@@ -1,6 +1,7 @@
 // #region core
 use anchor_lang::prelude::*;
-use puppet::{self, Puppet, SetData};
+use puppet::program::Puppet;
+use puppet::{self, Data, SetData};
 
 declare_id!("HmbTLCmaGvZhKnn1Zfa1JVnp7vkMV4DYVxPLWBVoN65L");
 
@@ -8,7 +9,7 @@ declare_id!("HmbTLCmaGvZhKnn1Zfa1JVnp7vkMV4DYVxPLWBVoN65L");
 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_program = ctx.accounts.puppet_program.to_account_info();
         let cpi_accounts = SetData {
             puppet: ctx.accounts.puppet.clone(),
         };
@@ -20,8 +21,7 @@ mod puppet_master {
 #[derive(Accounts)]
 pub struct PullStrings<'info> {
     #[account(mut)]
-    pub puppet: Account<'info, Puppet>,
-    #[account(address = puppet::ID)]
-    pub puppet_program: AccountInfo<'info>,
+    pub puppet: Account<'info, Data>,
+    pub puppet_program: Program<'info, Puppet>,
 }
 // #endregion core

+ 7 - 9
examples/tutorial/basic-3/programs/puppet/src/lib.rs

@@ -1,12 +1,11 @@
 use anchor_lang::prelude::*;
-use anchor_lang::solana_program::system_program;
 
 declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
 
 #[program]
 pub mod puppet {
     use super::*;
-    pub fn initialize(ctx: Context<Initialize>) -> ProgramResult {
+    pub fn initialize(_ctx: Context<Initialize>) -> ProgramResult {
         Ok(())
     }
 
@@ -20,20 +19,19 @@ pub mod puppet {
 #[derive(Accounts)]
 pub struct Initialize<'info> {
     #[account(init, payer = user, space = 8 + 8)]
-    pub puppet: Account<'info, Puppet>,
-    #[account(signer)]
-    pub user: AccountInfo<'info>,
-    #[account(address = system_program::ID)]
-    pub system_program: AccountInfo<'info>,
+    pub puppet: Account<'info, Data>,
+    #[account(mut)]
+    pub user: Signer<'info>,
+    pub system_program: Program<'info, System>,
 }
 
 #[derive(Accounts)]
 pub struct SetData<'info> {
     #[account(mut)]
-    pub puppet: Account<'info, Puppet>,
+    pub puppet: Account<'info, Data>,
 }
 
 #[account]
-pub struct Puppet {
+pub struct Data {
     pub data: u64,
 }

+ 1 - 1
examples/tutorial/basic-3/tests/basic-3.js

@@ -32,7 +32,7 @@ describe("basic-3", () => {
     });
 
     // Check the state updated.
-    puppetAccount = await puppet.account.puppet.fetch(newPuppetAccount.publicKey);
+    puppetAccount = await puppet.account.data.fetch(newPuppetAccount.publicKey);
     assert.ok(puppetAccount.data.eq(new anchor.BN(111)));
   });
 });

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

@@ -33,8 +33,7 @@ pub mod basic_4 {
 
 #[derive(Accounts)]
 pub struct Auth<'info> {
-    #[account(signer)]
-    authority: AccountInfo<'info>,
+    authority: Signer<'info>,
 }
 // #endregion code
 

+ 6 - 0
lang/src/error.rs

@@ -66,6 +66,12 @@ pub enum ErrorCode {
     AccountNotMutable,
     #[msg("The given account is not owned by the executing program")]
     AccountNotProgramOwned,
+    #[msg("Program ID was not as expected")]
+    InvalidProgramId,
+    #[msg("Program account is not executable")]
+    InvalidProgramExecutable,
+    #[msg("The given account did not sign")]
+    AccountNotSigner,
 
     // State.
     #[msg("The given state account does not have the correct address")]

+ 13 - 2
lang/src/lib.rs

@@ -44,8 +44,11 @@ mod error;
 #[doc(hidden)]
 pub mod idl;
 mod loader;
+mod program;
 mod program_account;
+mod signer;
 pub mod state;
+mod system_program;
 mod sysvar;
 mod vec;
 
@@ -61,12 +64,15 @@ pub use crate::cpi_account::CpiAccount;
 #[allow(deprecated)]
 pub use crate::cpi_state::CpiState;
 pub use crate::loader::Loader;
+pub use crate::program::Program;
 #[doc(hidden)]
 #[allow(deprecated)]
 pub use crate::program_account::ProgramAccount;
+pub use crate::signer::Signer;
 #[doc(hidden)]
 #[allow(deprecated)]
 pub use crate::state::ProgramState;
+pub use crate::system_program::System;
 pub use crate::sysvar::Sysvar;
 pub use anchor_attribute_access_control::access_control;
 pub use anchor_attribute_account::{account, declare_id, zero_copy};
@@ -217,6 +223,11 @@ pub trait Owner {
     fn owner() -> Pubkey;
 }
 
+/// Defines the id of a program.
+pub trait Id {
+    fn id() -> Pubkey;
+}
+
 /// Defines the Pubkey of an account.
 pub trait Key {
     fn key(&self) -> Pubkey;
@@ -234,8 +245,8 @@ pub mod prelude {
     pub use super::{
         access_control, account, declare_id, emit, error, event, interface, program, require,
         state, zero_copy, Account, AccountDeserialize, AccountSerialize, Accounts, AccountsExit,
-        AnchorDeserialize, AnchorSerialize, Context, CpiContext, Key, Loader, Owner,
-        ProgramAccount, Sysvar, ToAccountInfo, ToAccountInfos, ToAccountMetas,
+        AnchorDeserialize, AnchorSerialize, Context, CpiContext, Key, Loader, Owner, Program,
+        ProgramAccount, Signer, System, Sysvar, ToAccountInfo, ToAccountInfos, ToAccountMetas,
     };
 
     #[allow(deprecated)]

+ 97 - 0
lang/src/program.rs

@@ -0,0 +1,97 @@
+use crate::error::ErrorCode;
+use crate::*;
+use solana_program::account_info::AccountInfo;
+use solana_program::instruction::AccountMeta;
+use solana_program::program_error::ProgramError;
+use solana_program::pubkey::Pubkey;
+use std::ops::Deref;
+
+/// Account container that checks ownership on deserialization.
+#[derive(Clone)]
+pub struct Program<'info, T: Id + AccountDeserialize + Clone> {
+    account: T,
+    info: AccountInfo<'info>,
+}
+
+impl<'a, T: Id + AccountDeserialize + Clone> Program<'a, T> {
+    fn new(info: AccountInfo<'a>, account: T) -> Program<'a, T> {
+        Self { info, account }
+    }
+
+    /// Deserializes the given `info` into a `Program`.
+    #[inline(never)]
+    pub fn try_from(info: &AccountInfo<'a>) -> Result<Program<'a, T>, ProgramError> {
+        if info.key != &T::id() {
+            return Err(ErrorCode::InvalidProgramId.into());
+        }
+        if !info.executable {
+            return Err(ErrorCode::InvalidProgramExecutable.into());
+        }
+        // Programs have no data so use an empty slice.
+        let mut empty = &[][..];
+        Ok(Program::new(info.clone(), T::try_deserialize(&mut empty)?))
+    }
+}
+
+impl<'info, T: Id + Clone> Accounts<'info> for Program<'info, T>
+where
+    T: Id + AccountDeserialize,
+{
+    #[inline(never)]
+    fn try_accounts(
+        _program_id: &Pubkey,
+        accounts: &mut &[AccountInfo<'info>],
+        _ix_data: &[u8],
+    ) -> Result<Self, ProgramError> {
+        if accounts.is_empty() {
+            return Err(ErrorCode::AccountNotEnoughKeys.into());
+        }
+        let account = &accounts[0];
+        *accounts = &accounts[1..];
+        Program::try_from(account)
+    }
+}
+
+impl<'info, T: Id + AccountDeserialize + Clone> ToAccountMetas for Program<'info, T> {
+    fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
+        let is_signer = is_signer.unwrap_or(self.info.is_signer);
+        let meta = match self.info.is_writable {
+            false => AccountMeta::new_readonly(*self.info.key, is_signer),
+            true => AccountMeta::new(*self.info.key, is_signer),
+        };
+        vec![meta]
+    }
+}
+
+impl<'info, T: Id + AccountDeserialize + Clone> ToAccountInfos<'info> for Program<'info, T> {
+    fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
+        vec![self.info.clone()]
+    }
+}
+
+impl<'info, T: Id + AccountDeserialize + Clone> ToAccountInfo<'info> for Program<'info, T> {
+    fn to_account_info(&self) -> AccountInfo<'info> {
+        self.info.clone()
+    }
+}
+
+impl<'info, T: Id + AccountDeserialize + Clone> AsRef<AccountInfo<'info>> for Program<'info, T> {
+    fn as_ref(&self) -> &AccountInfo<'info> {
+        &self.info
+    }
+}
+
+impl<'info, T: Id + AccountDeserialize + Clone> Deref for Program<'info, T> {
+    type Target = AccountInfo<'info>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.info
+    }
+}
+
+impl<'info, T: AccountDeserialize + Id + Clone> AccountsExit<'info> for Program<'info, T> {
+    fn exit(&self, _program_id: &Pubkey) -> ProgramResult {
+        // No-op.
+        Ok(())
+    }
+}

+ 97 - 0
lang/src/signer.rs

@@ -0,0 +1,97 @@
+use crate::error::ErrorCode;
+use crate::*;
+use solana_program::account_info::AccountInfo;
+use solana_program::entrypoint::ProgramResult;
+use solana_program::instruction::AccountMeta;
+use solana_program::program_error::ProgramError;
+use solana_program::pubkey::Pubkey;
+use std::ops::Deref;
+
+/// Type validating that the account signed the transaction. No other ownership
+/// or type checks are done. If this is used, one should not try to access the
+/// underlying account data.
+#[derive(Clone)]
+pub struct Signer<'info> {
+    info: AccountInfo<'info>,
+}
+
+impl<'info> Signer<'info> {
+    fn new(info: AccountInfo<'info>) -> Signer<'info> {
+        Self { info }
+    }
+
+    /// Deserializes the given `info` into a `Signer`.
+    #[inline(never)]
+    pub fn try_from(info: &AccountInfo<'info>) -> Result<Signer<'info>, ProgramError> {
+        if !info.is_signer {
+            return Err(ErrorCode::AccountNotSigner.into());
+        }
+        Ok(Signer::new(info.clone()))
+    }
+}
+
+impl<'info> Accounts<'info> for Signer<'info> {
+    #[inline(never)]
+    fn try_accounts(
+        _program_id: &Pubkey,
+        accounts: &mut &[AccountInfo<'info>],
+        _ix_data: &[u8],
+    ) -> Result<Self, ProgramError> {
+        if accounts.is_empty() {
+            return Err(ErrorCode::AccountNotEnoughKeys.into());
+        }
+        let account = &accounts[0];
+        *accounts = &accounts[1..];
+        Signer::try_from(account)
+    }
+}
+
+impl<'info> AccountsExit<'info> for Signer<'info> {
+    fn exit(&self, _program_id: &Pubkey) -> ProgramResult {
+        // No-op.
+        Ok(())
+    }
+}
+
+impl<'info> ToAccountMetas for Signer<'info> {
+    fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
+        let is_signer = is_signer.unwrap_or(self.info.is_signer);
+        let meta = match self.info.is_writable {
+            false => AccountMeta::new_readonly(*self.info.key, is_signer),
+            true => AccountMeta::new(*self.info.key, is_signer),
+        };
+        vec![meta]
+    }
+}
+
+impl<'info> ToAccountInfos<'info> for Signer<'info> {
+    fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
+        vec![self.info.clone()]
+    }
+}
+
+impl<'info> ToAccountInfo<'info> for Signer<'info> {
+    fn to_account_info(&self) -> AccountInfo<'info> {
+        self.info.clone()
+    }
+}
+
+impl<'info> AsRef<AccountInfo<'info>> for Signer<'info> {
+    fn as_ref(&self) -> &AccountInfo<'info> {
+        &self.info
+    }
+}
+
+impl<'info> Deref for Signer<'info> {
+    type Target = AccountInfo<'info>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.info
+    }
+}
+
+impl<'info> Key for Signer<'info> {
+    fn key(&self) -> Pubkey {
+        *self.info.key
+    }
+}

+ 24 - 0
lang/src/system_program.rs

@@ -0,0 +1,24 @@
+use crate::*;
+use solana_program::program_error::ProgramError;
+use solana_program::pubkey::Pubkey;
+
+pub use solana_program::system_program::ID;
+
+#[derive(Clone)]
+pub struct System;
+
+impl anchor_lang::AccountDeserialize for System {
+    fn try_deserialize(buf: &mut &[u8]) -> Result<Self, ProgramError> {
+        System::try_deserialize_unchecked(buf)
+    }
+
+    fn try_deserialize_unchecked(_buf: &mut &[u8]) -> Result<Self, ProgramError> {
+        Ok(System)
+    }
+}
+
+impl anchor_lang::Id for System {
+    fn id() -> Pubkey {
+        ID
+    }
+}

+ 3 - 3
lang/syn/src/codegen/accounts/constraints.rs

@@ -515,7 +515,7 @@ pub fn generate_create_account(
                 &[
                     payer.to_account_info(),
                     #field.to_account_info(),
-                    system_program.to_account_info().clone(),
+                    system_program.to_account_info(),
                 ],
                 &[#seeds_with_nonce],
             )?;
@@ -535,7 +535,7 @@ pub fn generate_create_account(
                     &[
                         payer.to_account_info(),
                         #field.to_account_info(),
-                        system_program.to_account_info().clone(),
+                        system_program.to_account_info(),
                     ],
                 )?;
             }
@@ -547,7 +547,7 @@ pub fn generate_create_account(
                 ),
                 &[
                     #field.to_account_info(),
-                    system_program.clone(),
+                    system_program.to_account_info(),
                 ],
                 &[#seeds_with_nonce],
             )?;

+ 26 - 0
lang/syn/src/codegen/program/entry.rs

@@ -1,8 +1,10 @@
 use crate::program_codegen::dispatch;
 use crate::Program;
+use heck::CamelCase;
 use quote::quote;
 
 pub fn generate(program: &Program) -> proc_macro2::TokenStream {
+    let name: proc_macro2::TokenStream = program.name.to_string().to_camel_case().parse().unwrap();
     let fallback_maybe = dispatch::gen_fallback(program).unwrap_or(quote! {
         Err(anchor_lang::__private::ErrorCode::InstructionMissing.into());
     });
@@ -65,5 +67,29 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
                     e
                 })
         }
+
+        pub mod program {
+            use super::*;
+
+            /// Type representing the program.
+            #[derive(Clone)]
+            pub struct #name;
+
+            impl anchor_lang::AccountDeserialize for #name {
+                fn try_deserialize(buf: &mut &[u8]) -> std::result::Result<Self, anchor_lang::solana_program::program_error::ProgramError> {
+                    #name::try_deserialize_unchecked(buf)
+                }
+
+                fn try_deserialize_unchecked(_buf: &mut &[u8]) -> std::result::Result<Self, anchor_lang::solana_program::program_error::ProgramError> {
+                    Ok(#name)
+                }
+            }
+
+            impl anchor_lang::Id for #name {
+                fn id() -> Pubkey {
+                    ID
+                }
+            }
+        }
     }
 }

+ 22 - 0
lang/syn/src/lib.rs

@@ -178,6 +178,9 @@ impl Field {
             Ty::AccountInfo => quote! {
                 AccountInfo
             },
+            Ty::Signer => quote! {
+                Signer
+            },
             Ty::Account(AccountTy { boxed, .. }) => {
                 if *boxed {
                     quote! {
@@ -271,7 +274,9 @@ impl Field {
             Ty::Sysvar(_) => quote! { anchor_lang::Sysvar },
             Ty::CpiState(_) => quote! { anchor_lang::CpiState },
             Ty::ProgramState(_) => quote! { anchor_lang::ProgramState },
+            Ty::Program(_) => quote! { anchor_lang::Program },
             Ty::AccountInfo => quote! {},
+            Ty::Signer => quote! {},
         }
     }
 
@@ -281,6 +286,9 @@ impl Field {
             Ty::AccountInfo => quote! {
                 AccountInfo
             },
+            Ty::Signer => quote! {
+                Signer
+            },
             Ty::ProgramAccount(ty) => {
                 let ident = &ty.account_type_path;
                 quote! {
@@ -329,6 +337,12 @@ impl Field {
                 SysvarTy::Instructions => quote! {Instructions},
                 SysvarTy::Rewards => quote! {Rewards},
             },
+            Ty::Program(ty) => {
+                let program = &ty.account_type_path;
+                quote! {
+                    #program
+                }
+            }
         }
     }
 }
@@ -353,6 +367,8 @@ pub enum Ty {
     CpiAccount(CpiAccountTy),
     Sysvar(SysvarTy),
     Account(AccountTy),
+    Program(ProgramTy),
+    Signer,
 }
 
 #[derive(Debug, PartialEq)]
@@ -405,6 +421,12 @@ pub struct AccountTy {
     pub boxed: bool,
 }
 
+#[derive(Debug, PartialEq)]
+pub struct ProgramTy {
+    // The struct type of the account.
+    pub account_type_path: TypePath,
+}
+
 #[derive(Debug)]
 pub struct Error {
     pub name: String,

+ 9 - 0
lang/syn/src/parser/accounts/mod.rs

@@ -74,6 +74,8 @@ fn is_field_primitive(f: &syn::Field) -> ParseResult<bool> {
             | "CpiState"
             | "Loader"
             | "Account"
+            | "Program"
+            | "Signer"
     );
     Ok(r)
 }
@@ -92,6 +94,8 @@ fn parse_ty(f: &syn::Field) -> ParseResult<Ty> {
         "AccountInfo" => Ty::AccountInfo,
         "Loader" => Ty::Loader(parse_program_account_zero_copy(&path)?),
         "Account" => Ty::Account(parse_account_ty(&path)?),
+        "Program" => Ty::Program(parse_program_ty(&path)?),
+        "Signer" => Ty::Signer,
         _ => return Err(ParseError::new(f.ty.span(), "invalid account type given")),
     };
 
@@ -167,6 +171,11 @@ fn parse_account_ty(path: &syn::Path) -> ParseResult<AccountTy> {
     })
 }
 
+fn parse_program_ty(path: &syn::Path) -> ParseResult<ProgramTy> {
+    let account_type_path = parse_account(path)?;
+    Ok(ProgramTy { account_type_path })
+}
+
 // TODO: this whole method is a hack. Do something more idiomatic.
 fn parse_account(mut path: &syn::Path) -> ParseResult<syn::TypePath> {
     if parser::tts_to_string(path)

+ 29 - 0
spl/src/dex/cpi.rs

@@ -1,8 +1,11 @@
 use anchor_lang::solana_program::account_info::AccountInfo;
 use anchor_lang::solana_program::entrypoint::ProgramResult;
+use anchor_lang::solana_program::program_error::ProgramError;
+use anchor_lang::solana_program::pubkey::Pubkey;
 use anchor_lang::{Accounts, CpiContext, ToAccountInfos};
 use serum_dex::instruction::SelfTradeBehavior;
 use serum_dex::matching::{OrderType, Side};
+use std::io::Write;
 use std::num::NonZeroU64;
 
 #[cfg(not(feature = "devnet"))]
@@ -280,3 +283,29 @@ pub struct InitializeMarket<'info> {
     pub event_q: AccountInfo<'info>,
     pub rent: AccountInfo<'info>,
 }
+
+#[derive(Clone)]
+pub struct Dex;
+
+impl anchor_lang::AccountDeserialize for Dex {
+    fn try_deserialize(buf: &mut &[u8]) -> Result<Self, ProgramError> {
+        Dex::try_deserialize_unchecked(buf)
+    }
+
+    fn try_deserialize_unchecked(_buf: &mut &[u8]) -> Result<Self, ProgramError> {
+        Ok(Dex)
+    }
+}
+
+impl anchor_lang::AccountSerialize for Dex {
+    fn try_serialize<W: Write>(&self, _writer: &mut W) -> Result<(), ProgramError> {
+        // no-op
+        Ok(())
+    }
+}
+
+impl anchor_lang::Id for Dex {
+    fn id() -> Pubkey {
+        ID
+    }
+}

+ 19 - 0
spl/src/token.rs

@@ -305,6 +305,25 @@ impl Deref for Mint {
     }
 }
 
+#[derive(Clone)]
+pub struct Token;
+
+impl anchor_lang::AccountDeserialize for Token {
+    fn try_deserialize(buf: &mut &[u8]) -> Result<Self, ProgramError> {
+        Token::try_deserialize_unchecked(buf)
+    }
+
+    fn try_deserialize_unchecked(_buf: &mut &[u8]) -> Result<Self, ProgramError> {
+        Ok(Token)
+    }
+}
+
+impl anchor_lang::Id for Token {
+    fn id() -> Pubkey {
+        ID
+    }
+}
+
 // Field parsers to save compute. All account validation is assumed to be done
 // outside of these methods.
 pub mod accessor {

+ 1 - 1
tests/cfo/deps/stake

@@ -1 +1 @@
-Subproject commit 9a257678dfd0bda0c222e516e8c1a778b401d71e
+Subproject commit 28a7f1e0c134f16f99e201069970df78ec5d7e78

+ 49 - 68
tests/cfo/programs/cfo/src/lib.rs

@@ -1,12 +1,15 @@
 // WIP. This program has been checkpointed and is not production ready.
 
 use anchor_lang::prelude::*;
-use anchor_lang::solana_program::system_program;
 use anchor_lang::solana_program::sysvar::instructions as tx_instructions;
-use anchor_spl::token::{self, Mint, TokenAccount};
-use anchor_spl::{dex, mint};
+use anchor_spl::dex::{self, Dex};
+use anchor_spl::mint;
+use anchor_spl::token::{self, Mint, Token, TokenAccount};
+use lockup::program::Lockup;
+use registry::program::Registry;
 use registry::{Registrar, RewardVendorKind};
 use std::convert::TryInto;
+use swap::program::Swap;
 
 declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
 
@@ -29,7 +32,7 @@ pub mod cfo {
         let officer = &mut ctx.accounts.officer;
         officer.authority = *ctx.accounts.authority.key;
         officer.swap_program = *ctx.accounts.swap_program.key;
-        officer.dex_program = *ctx.accounts.dex_program.key;
+        officer.dex_program = ctx.accounts.dex_program.key();
         officer.distribution = d;
         officer.registrar = registrar;
         officer.msrm_registrar = msrm_registrar;
@@ -333,21 +336,16 @@ pub struct CreateOfficer<'info> {
         token::authority = officer,
     )]
     treasury: Box<Account<'info, TokenAccount>>,
-    #[account(signer)]
     authority: AccountInfo<'info>,
     #[cfg_attr(
         not(feature = "test"),
         account(address = mint::SRM),
     )]
-    mint: AccountInfo<'info>,
-    #[account(executable)]
-    dex_program: AccountInfo<'info>,
-    #[account(executable)]
-    swap_program: AccountInfo<'info>,
-    #[account(address = system_program::ID)]
-    system_program: AccountInfo<'info>,
-    #[account(address = spl_token::ID)]
-    token_program: AccountInfo<'info>,
+    mint: Box<Account<'info, Mint>>,
+    dex_program: Program<'info, Dex>,
+    swap_program: Program<'info, Swap>,
+    system_program: Program<'info, System>,
+    token_program: Program<'info, Token>,
     rent: Sysvar<'info, Rent>,
 }
 
@@ -366,12 +364,10 @@ pub struct CreateOfficerToken<'info> {
     token: Account<'info, TokenAccount>,
     #[account(owner = spl_token::ID)]
     mint: AccountInfo<'info>,
-    #[account(mut, signer)]
-    payer: AccountInfo<'info>,
-    #[account(address = system_program::ID)]
-    system_program: AccountInfo<'info>,
-    #[account(address = spl_token::ID)]
-    token_program: AccountInfo<'info>,
+    #[account(mut)]
+    payer: Signer<'info>,
+    system_program: Program<'info, System>,
+    token_program: Program<'info, Token>,
     rent: Sysvar<'info, Rent>,
 }
 
@@ -379,8 +375,7 @@ pub struct CreateOfficerToken<'info> {
 pub struct SetDistribution<'info> {
     #[account(has_one = authority)]
     officer: Account<'info, Officer>,
-    #[account(signer)]
-    authority: AccountInfo<'info>,
+    authority: Signer<'info>,
 }
 
 #[derive(Accounts)]
@@ -398,26 +393,25 @@ pub struct SweepFees<'info> {
     )]
     sweep_vault: Account<'info, TokenAccount>,
     mint: AccountInfo<'info>,
-    dex: Dex<'info>,
+    dex: DexAccounts<'info>,
 }
 
 #[derive(Accounts)]
-pub struct Dex<'info> {
+pub struct DexAccounts<'info> {
     #[account(mut)]
     market: AccountInfo<'info>,
     #[account(mut)]
     pc_vault: AccountInfo<'info>,
     sweep_authority: AccountInfo<'info>,
     vault_signer: AccountInfo<'info>,
-    dex_program: AccountInfo<'info>,
-    #[account(address = spl_token::ID)]
-    token_program: AccountInfo<'info>,
+    dex_program: Program<'info, Dex>,
+    token_program: Program<'info, Token>,
 }
 
 #[derive(Accounts)]
 pub struct SwapToUsdc<'info> {
     #[account(
-        seeds = [dex_program.key().as_ref()],
+        seeds = [dex_program.key.as_ref()],
         bump = officer.bumps.bump,
     )]
     officer: Account<'info, Officer>,
@@ -432,12 +426,9 @@ pub struct SwapToUsdc<'info> {
     quote_vault: AccountInfo<'info>,
     #[account(seeds = [officer.key().as_ref(), mint::USDC.as_ref()], bump)]
     usdc_vault: AccountInfo<'info>,
-    #[account(address = swap::ID)]
-    swap_program: AccountInfo<'info>,
-    #[account(address = dex::ID)]
-    dex_program: AccountInfo<'info>,
-    #[account(address = token::ID)]
-    token_program: AccountInfo<'info>,
+    swap_program: Program<'info, Swap>,
+    dex_program: Program<'info, Dex>,
+    token_program: Program<'info, Token>,
     #[account(address = tx_instructions::ID)]
     instructions: AccountInfo<'info>,
     rent: Sysvar<'info, Rent>,
@@ -446,7 +437,7 @@ pub struct SwapToUsdc<'info> {
 #[derive(Accounts)]
 pub struct SwapToSrm<'info> {
     #[account(
-        seeds = [dex_program.key().as_ref()],
+        seeds = [dex_program.key.as_ref()],
         bump = officer.bumps.bump,
     )]
     officer: Account<'info, Officer>,
@@ -466,12 +457,9 @@ pub struct SwapToSrm<'info> {
         constraint = &officer.stake != from_vault.key,
     )]
     srm_vault: AccountInfo<'info>,
-    #[account(address = swap::ID)]
-    swap_program: AccountInfo<'info>,
-    #[account(address = dex::ID)]
-    dex_program: AccountInfo<'info>,
-    #[account(address = token::ID)]
-    token_program: AccountInfo<'info>,
+    swap_program: Program<'info, Swap>,
+    dex_program: Program<'info, Dex>,
+    token_program: Program<'info, Token>,
     #[account(address = tx_instructions::ID)]
     instructions: AccountInfo<'info>,
     rent: Sysvar<'info, Rent>,
@@ -522,10 +510,8 @@ pub struct Distribute<'info> {
     srm_vault: Account<'info, TokenAccount>,
     #[account(address = mint::SRM)]
     mint: AccountInfo<'info>,
-    #[account(address = spl_token::ID)]
-    token_program: AccountInfo<'info>,
-    #[account(address = dex::ID)]
-    dex_program: AccountInfo<'info>,
+    token_program: Program<'info, Token>,
+    dex_program: Program<'info, Dex>,
 }
 
 #[derive(Accounts)]
@@ -548,16 +534,11 @@ pub struct DropStakeReward<'info> {
     mint: AccountInfo<'info>,
     srm: DropStakeRewardPool<'info>,
     msrm: DropStakeRewardPool<'info>,
-    #[account(owner = *registry_program.key)]
     msrm_registrar: Box<Account<'info, Registrar>>,
-    #[account(address = token::ID)]
-    token_program: AccountInfo<'info>,
-    #[account(address = registry::ID)]
-    registry_program: AccountInfo<'info>,
-    #[account(address = lockup::ID)]
-    lockup_program: AccountInfo<'info>,
-    #[account(address = dex::ID)]
-    dex_program: AccountInfo<'info>,
+    token_program: Program<'info, Token>,
+    registry_program: Program<'info, Registry>,
+    lockup_program: Program<'info, Lockup>,
+    dex_program: Program<'info, Dex>,
     clock: Sysvar<'info, Clock>,
     rent: Sysvar<'info, Rent>,
 }
@@ -628,7 +609,7 @@ impl<'info> From<&SweepFees<'info>> for CpiContext<'_, '_, '_, 'info, dex::Sweep
             vault_signer: sweep.dex.vault_signer.to_account_info(),
             token_program: sweep.dex.token_program.to_account_info(),
         };
-        CpiContext::new(program, accounts)
+        CpiContext::new(program.to_account_info(), accounts)
     }
 }
 
@@ -655,7 +636,7 @@ impl<'info> From<&SwapToSrm<'info>> for CpiContext<'_, '_, '_, 'info, swap::Swap
             token_program: accs.token_program.to_account_info(),
             rent: accs.rent.to_account_info(),
         };
-        CpiContext::new(program, accounts)
+        CpiContext::new(program.to_account_info(), accounts)
     }
 }
 
@@ -682,7 +663,7 @@ impl<'info> From<&SwapToUsdc<'info>> for CpiContext<'_, '_, '_, 'info, swap::Swa
             token_program: accs.token_program.to_account_info(),
             rent: accs.rent.to_account_info(),
         };
-        CpiContext::new(program, accounts)
+        CpiContext::new(program.to_account_info(), accounts)
     }
 }
 
@@ -694,7 +675,7 @@ impl<'info> From<&Distribute<'info>> for CpiContext<'_, '_, '_, 'info, token::Bu
             to: accs.srm_vault.to_account_info(),
             authority: accs.officer.to_account_info(),
         };
-        CpiContext::new(program, accounts)
+        CpiContext::new(program.to_account_info(), accounts)
     }
 }
 
@@ -710,11 +691,11 @@ impl<'info> DropStakeReward<'info> {
             vendor_vault: CpiAccount::try_from(&self.srm.vendor_vault).unwrap(),
             depositor: self.stake.to_account_info(),
             depositor_authority: self.officer.to_account_info(),
-            token_program: self.token_program.clone(),
+            token_program: self.token_program.to_account_info(),
             clock: self.clock.clone(),
             rent: self.rent.clone(),
         };
-        CpiContext::new(program, accounts)
+        CpiContext::new(program.to_account_info(), accounts)
     }
 
     fn into_msrm_reward(&self) -> CpiContext<'_, '_, '_, 'info, registry::DropReward<'info>> {
@@ -728,43 +709,43 @@ impl<'info> DropStakeReward<'info> {
             vendor_vault: CpiAccount::try_from(&self.msrm.vendor_vault).unwrap(),
             depositor: self.stake.to_account_info(),
             depositor_authority: self.officer.to_account_info(),
-            token_program: self.token_program.clone(),
+            token_program: self.token_program.to_account_info(),
             clock: self.clock.clone(),
             rent: self.rent.clone(),
         };
-        CpiContext::new(program, accounts)
+        CpiContext::new(program.to_account_info(), accounts)
     }
 }
 
 impl<'info> Distribute<'info> {
     fn into_burn(&self) -> CpiContext<'_, '_, '_, 'info, token::Burn<'info>> {
-        let program = self.token_program.clone();
+        let program = self.token_program.to_account_info();
         let accounts = token::Burn {
             mint: self.mint.clone(),
             to: self.srm_vault.to_account_info(),
             authority: self.officer.to_account_info(),
         };
-        CpiContext::new(program, accounts)
+        CpiContext::new(program.to_account_info(), accounts)
     }
 
     fn into_stake_transfer(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> {
-        let program = self.token_program.clone();
+        let program = self.token_program.to_account_info();
         let accounts = token::Transfer {
             from: self.srm_vault.to_account_info(),
             to: self.stake.to_account_info(),
             authority: self.officer.to_account_info(),
         };
-        CpiContext::new(program, accounts)
+        CpiContext::new(program.to_account_info(), accounts)
     }
 
     fn into_treasury_transfer(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> {
-        let program = self.token_program.clone();
+        let program = self.token_program.to_account_info();
         let accounts = token::Transfer {
             from: self.srm_vault.to_account_info(),
             to: self.treasury.to_account_info(),
             authority: self.officer.to_account_info(),
         };
-        CpiContext::new(program, accounts)
+        CpiContext::new(program.to_account_info(), accounts)
     }
 }
 

+ 3 - 2
tests/lockup/programs/lockup/src/lib.rs

@@ -2,8 +2,8 @@
 //! it's suggested to start with the other examples.
 
 use anchor_lang::prelude::*;
+use anchor_lang::solana_program;
 use anchor_lang::solana_program::instruction::Instruction;
-use anchor_lang::solana_program::program;
 use anchor_spl::token::{self, TokenAccount, Transfer};
 
 mod calculator;
@@ -477,7 +477,8 @@ pub fn whitelist_relay_cpi<'info>(
     let signer = &[&seeds[..]];
     let mut accounts = transfer.to_account_infos();
     accounts.extend_from_slice(&remaining_accounts);
-    program::invoke_signed(&relay_instruction, &accounts, signer).map_err(Into::into)
+    solana_program::program::invoke_signed(&relay_instruction, &accounts, signer)
+        .map_err(Into::into)
 }
 
 pub fn is_whitelisted<'info>(transfer: &WhitelistTransfer<'info>) -> Result<()> {

+ 4 - 0
tests/permissioned-markets/Anchor.toml

@@ -2,6 +2,10 @@
 cluster = "localnet"
 wallet = "~/.config/solana/id.json"
 
+[programs.localnet]
+permissioned_markets = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
+permissioned_markets_middleware = "HmbTLCmaGvZhKnn1Zfa1JVnp7vkMV4DYVxPLWBVoN65L"
+
 [[test.genesis]]
 address = "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"
 program = "./deps/serum-dex/dex/target/deploy/serum_dex.so"

+ 2 - 0
tests/permissioned-markets/programs/permissioned-markets-middleware/src/lib.rs

@@ -10,6 +10,8 @@ use solana_program::entrypoint::ProgramResult;
 use solana_program::pubkey::Pubkey;
 use solana_program::sysvar::rent;
 
+declare_id!("HmbTLCmaGvZhKnn1Zfa1JVnp7vkMV4DYVxPLWBVoN65L");
+
 /// # Permissioned Markets
 ///
 /// This demonstrates how to create "permissioned markets" on Serum via a proxy.

+ 5 - 4
tests/permissioned-markets/programs/permissioned-markets/src/lib.rs

@@ -6,11 +6,12 @@ use serum_dex::instruction::MarketInstruction;
 use serum_dex::matching::Side;
 use serum_dex::state::OpenOrders;
 use solana_program::instruction::Instruction;
-use solana_program::program;
 use solana_program::system_program;
 use solana_program::sysvar::rent;
 use std::mem::size_of;
 
+declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
+
 /// A low level example of permissioned markets.
 ///
 /// It's recommended to instead study `programs/permissioned-markets-middleware`
@@ -262,7 +263,7 @@ pub mod permissioned_markets {
                 })
                 .collect();
             let signers: Vec<&[&[u8]]> = tmp_signers.iter().map(|seeds| &seeds[..]).collect();
-            program::invoke_signed(&ix, &accounts, &signers)?;
+            solana_program::program::invoke_signed(&ix, &accounts, &signers)?;
         }
 
         // CPI to the dex.
@@ -290,7 +291,7 @@ pub mod permissioned_markets {
             dex_program = dex.key,
             market = market
         };
-        program::invoke_signed(&ix, &acc_infos, &[seeds, seeds_init])?;
+        solana_program::program::invoke_signed(&ix, &acc_infos, &[seeds, seeds_init])?;
 
         // Execute post instruction.
         if let Some((ix, accounts, seeds)) = post_instruction {
@@ -302,7 +303,7 @@ pub mod permissioned_markets {
                 })
                 .collect();
             let signers: Vec<&[&[u8]]> = tmp_signers.iter().map(|seeds| &seeds[..]).collect();
-            program::invoke_signed(&ix, &accounts, &signers)?;
+            solana_program::program::invoke_signed(&ix, &accounts, &signers)?;
         }
 
         Ok(())

+ 3 - 0
tests/pyth/Anchor.toml

@@ -2,5 +2,8 @@
 cluster = "localnet"
 wallet = "~/.config/solana/id.json"
 
+[programs.localnet]
+pyth = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
+
 [scripts]
 test = "ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

+ 2 - 0
tests/pyth/programs/pyth/src/lib.rs

@@ -2,6 +2,8 @@ use anchor_lang::prelude::*;
 mod pc;
 use pc::Price;
 
+declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
+
 #[program]
 pub mod pyth {
     use super::*;

+ 3 - 0
tests/spl/token-proxy/Anchor.toml

@@ -2,5 +2,8 @@
 cluster = "localnet"
 wallet = "~/.config/solana/id.json"
 
+[programs.localnet]
+token_proxy = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
+
 [scripts]
 test = "mocha -t 1000000 tests/"

+ 2 - 0
tests/spl/token-proxy/programs/token-proxy/src/lib.rs

@@ -3,6 +3,8 @@
 use anchor_lang::prelude::*;
 use anchor_spl::token::{self, Burn, MintTo, SetAuthority, Transfer};
 
+declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
+
 #[program]
 mod token_proxy {
     use super::*;

+ 3 - 0
tests/swap/Anchor.toml

@@ -2,6 +2,9 @@
 cluster = "localnet"
 wallet = "~/.config/solana/id.json"
 
+[programs.localnet]
+swap = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
+
 [[test.genesis]]
 address = "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"
 program = "./deps/serum-dex/dex/target/deploy/serum_dex.so"

+ 2 - 0
tests/swap/programs/swap/src/lib.rs

@@ -14,6 +14,8 @@ use anchor_spl::dex::serum_dex::state::MarketState;
 use anchor_spl::token;
 use std::num::NonZeroU64;
 
+declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
+
 #[program]
 pub mod swap {
     use super::*;

+ 3 - 0
tests/sysvars/Anchor.toml

@@ -2,5 +2,8 @@
 cluster = "localnet"
 wallet = "~/.config/solana/id.json"
 
+[programs.localnet]
+sysvars = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
+
 [scripts]
 test = "mocha -t 1000000 tests/"

+ 2 - 0
tests/sysvars/programs/sysvars/src/lib.rs

@@ -1,5 +1,7 @@
 use anchor_lang::prelude::*;
 
+declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
+
 #[program]
 mod sysvars {
     use super::*;

+ 3 - 0
tests/typescript/Anchor.toml

@@ -2,6 +2,9 @@
 cluster = "localnet"
 wallet = "~/.config/solana/id.json"
 
+[programs.localnet]
+typescript = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
+
 [workspace]
 members = ["programs/typescript"]
 

+ 2 - 0
tests/typescript/programs/typescript/src/lib.rs

@@ -3,6 +3,8 @@
 
 use anchor_lang::prelude::*;
 
+declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
+
 #[program]
 pub mod typescript {
     use super::*;

+ 7 - 0
ts/src/error.ts

@@ -80,6 +80,8 @@ const LangErrorCode = {
   AccountNotEnoughKeys: 165,
   AccountNotMutable: 166,
   AccountNotProgramOwned: 167,
+  InvalidProgramId: 168,
+  InvalidProgramIdExecutable: 169,
 
   // State.
   StateInvalidAddress: 180,
@@ -159,6 +161,11 @@ const LangErrorMessage = new Map([
     LangErrorCode.AccountNotProgramOwned,
     "The given account is not owned by the executing program",
   ],
+  [LangErrorCode.InvalidProgramId, "Program ID was not as expected"],
+  [
+    LangErrorCode.InvalidProgramIdExecutable,
+    "Program account is not executable",
+  ],
 
   // State.
   [