Browse Source

lang: Support for AccountLoader (#792)

NorbertBodziony 4 years ago
parent
commit
1c5c6a8aba

+ 1 - 0
CHANGELOG.md

@@ -17,6 +17,7 @@ incremented for features.
 * cli: `target/types` directory now created on build to store a TypeScript types file for each program's IDL ([#795](https://github.com/project-serum/anchor/pull/795)).
 * ts: `Program<T>` can now be typed with an IDL type ([#795](https://github.com/project-serum/anchor/pull/795)).
 * lang: Add `mint::freeze_authority` keyword for mint initialization within `#[derive(Accounts)]` ([#835](https://github.com/project-serum/anchor/pull/835)).
+* lang: Add `AccountLoader` type for `zero_copy` accounts with support for CPI ([#792](https://github.com/project-serum/anchor/pull/792)).
 
 ### Breaking
 

+ 6 - 4
lang/src/lib.rs

@@ -44,6 +44,7 @@ mod error;
 #[doc(hidden)]
 pub mod idl;
 mod loader;
+mod loader_account;
 mod program;
 mod program_account;
 mod signer;
@@ -65,6 +66,7 @@ pub use crate::cpi_account::CpiAccount;
 #[allow(deprecated)]
 pub use crate::cpi_state::CpiState;
 pub use crate::loader::Loader;
+pub use crate::loader_account::AccountLoader;
 pub use crate::program::Program;
 #[doc(hidden)]
 #[allow(deprecated)]
@@ -246,10 +248,10 @@ impl Key for Pubkey {
 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, Id, Key, Loader, Owner, Program,
-        ProgramAccount, Signer, System, Sysvar, ToAccountInfo, ToAccountInfos, ToAccountMetas,
-        UncheckedAccount,
+        state, zero_copy, Account, AccountDeserialize, AccountLoader, AccountSerialize, Accounts,
+        AccountsExit, AnchorDeserialize, AnchorSerialize, Context, CpiContext, Id, Key, Loader,
+        Owner, Program, ProgramAccount, Signer, System, Sysvar, ToAccountInfo, ToAccountInfos,
+        ToAccountMetas, UncheckedAccount,
     };
 
     #[allow(deprecated)]

+ 198 - 0
lang/src/loader_account.rs

@@ -0,0 +1,198 @@
+use crate::error::ErrorCode;
+use crate::{
+    Accounts, AccountsClose, AccountsExit, Key, Owner, ToAccountInfo, ToAccountInfos,
+    ToAccountMetas, ZeroCopy,
+};
+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::cell::{Ref, RefMut};
+use std::io::Write;
+use std::marker::PhantomData;
+use std::ops::DerefMut;
+
+/// Account AccountLoader facilitating on demand zero copy deserialization.
+/// Note that using accounts in this way is distinctly different from using,
+/// for example, the [`ProgramAccount`](./struct.ProgramAccount.html). Namely,
+/// one must call `load`, `load_mut`, or `load_init`, before reading or writing
+/// to the account. For more details on zero-copy-deserialization, see the
+/// [`account`](./attr.account.html) attribute.
+///
+/// When using it's important to be mindful of any calls to `load` so as not to
+/// induce a `RefCell` panic, especially when sharing accounts across CPI
+/// boundaries. When in doubt, one should make sure all refs resulting from a
+/// call to `load` are dropped before CPI.
+#[derive(Clone)]
+pub struct AccountLoader<'info, T: ZeroCopy + Owner> {
+    acc_info: AccountInfo<'info>,
+    phantom: PhantomData<&'info T>,
+}
+
+impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> {
+    fn new(acc_info: AccountInfo<'info>) -> AccountLoader<'info, T> {
+        Self {
+            acc_info,
+            phantom: PhantomData,
+        }
+    }
+
+    /// Constructs a new `Loader` from a previously initialized account.
+    #[inline(never)]
+    pub fn try_from(
+        acc_info: &AccountInfo<'info>,
+    ) -> Result<AccountLoader<'info, T>, ProgramError> {
+        if acc_info.owner != &T::owner() {
+            return Err(ErrorCode::AccountNotProgramOwned.into());
+        }
+        let data: &[u8] = &acc_info.try_borrow_data()?;
+        // Discriminator must match.
+        let mut disc_bytes = [0u8; 8];
+        disc_bytes.copy_from_slice(&data[..8]);
+        if disc_bytes != T::discriminator() {
+            return Err(ErrorCode::AccountDiscriminatorMismatch.into());
+        }
+
+        Ok(AccountLoader::new(acc_info.clone()))
+    }
+
+    /// Constructs a new `Loader` from an uninitialized account.
+    #[inline(never)]
+    pub fn try_from_unchecked(
+        _program_id: &Pubkey,
+        acc_info: &AccountInfo<'info>,
+    ) -> Result<AccountLoader<'info, T>, ProgramError> {
+        if acc_info.owner != &T::owner() {
+            return Err(ErrorCode::AccountNotProgramOwned.into());
+        }
+        Ok(AccountLoader::new(acc_info.clone()))
+    }
+
+    /// Returns a Ref to the account data structure for reading.
+    pub fn load(&self) -> Result<Ref<T>, ProgramError> {
+        let data = self.acc_info.try_borrow_data()?;
+
+        let mut disc_bytes = [0u8; 8];
+        disc_bytes.copy_from_slice(&data[..8]);
+        if disc_bytes != T::discriminator() {
+            return Err(ErrorCode::AccountDiscriminatorMismatch.into());
+        }
+
+        Ok(Ref::map(data, |data| bytemuck::from_bytes(&data[8..])))
+    }
+
+    /// Returns a `RefMut` to the account data structure for reading or writing.
+    pub fn load_mut(&self) -> Result<RefMut<T>, ProgramError> {
+        // AccountInfo api allows you to borrow mut even if the account isn't
+        // writable, so add this check for a better dev experience.
+        if !self.acc_info.is_writable {
+            return Err(ErrorCode::AccountNotMutable.into());
+        }
+
+        let data = self.acc_info.try_borrow_mut_data()?;
+
+        let mut disc_bytes = [0u8; 8];
+        disc_bytes.copy_from_slice(&data[..8]);
+        if disc_bytes != T::discriminator() {
+            return Err(ErrorCode::AccountDiscriminatorMismatch.into());
+        }
+
+        Ok(RefMut::map(data, |data| {
+            bytemuck::from_bytes_mut(&mut data.deref_mut()[8..])
+        }))
+    }
+
+    /// Returns a `RefMut` to the account data structure for reading or writing.
+    /// Should only be called once, when the account is being initialized.
+    pub fn load_init(&self) -> Result<RefMut<T>, ProgramError> {
+        // AccountInfo api allows you to borrow mut even if the account isn't
+        // writable, so add this check for a better dev experience.
+        if !self.acc_info.is_writable {
+            return Err(ErrorCode::AccountNotMutable.into());
+        }
+
+        let data = self.acc_info.try_borrow_mut_data()?;
+
+        // The discriminator should be zero, since we're initializing.
+        let mut disc_bytes = [0u8; 8];
+        disc_bytes.copy_from_slice(&data[..8]);
+        let discriminator = u64::from_le_bytes(disc_bytes);
+        if discriminator != 0 {
+            return Err(ErrorCode::AccountDiscriminatorAlreadySet.into());
+        }
+
+        Ok(RefMut::map(data, |data| {
+            bytemuck::from_bytes_mut(&mut data.deref_mut()[8..])
+        }))
+    }
+}
+
+impl<'info, T: ZeroCopy + Owner> Accounts<'info> for AccountLoader<'info, T> {
+    #[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..];
+        let l = AccountLoader::try_from(account)?;
+        Ok(l)
+    }
+}
+
+impl<'info, T: ZeroCopy + Owner> AccountsExit<'info> for AccountLoader<'info, T> {
+    // The account *cannot* be loaded when this is called.
+    fn exit(&self, _program_id: &Pubkey) -> ProgramResult {
+        let mut data = self.acc_info.try_borrow_mut_data()?;
+        let dst: &mut [u8] = &mut data;
+        let mut cursor = std::io::Cursor::new(dst);
+        cursor.write_all(&T::discriminator()).unwrap();
+        Ok(())
+    }
+}
+
+impl<'info, T: ZeroCopy + Owner> AccountsClose<'info> for AccountLoader<'info, T> {
+    fn close(&self, sol_destination: AccountInfo<'info>) -> ProgramResult {
+        crate::common::close(self.to_account_info(), sol_destination)
+    }
+}
+
+impl<'info, T: ZeroCopy + Owner> ToAccountMetas for AccountLoader<'info, T> {
+    fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
+        let is_signer = is_signer.unwrap_or(self.acc_info.is_signer);
+        let meta = match self.acc_info.is_writable {
+            false => AccountMeta::new_readonly(*self.acc_info.key, is_signer),
+            true => AccountMeta::new(*self.acc_info.key, is_signer),
+        };
+        vec![meta]
+    }
+}
+
+impl<'info, T: ZeroCopy + Owner> AsRef<AccountInfo<'info>> for AccountLoader<'info, T> {
+    fn as_ref(&self) -> &AccountInfo<'info> {
+        &self.acc_info
+    }
+}
+
+impl<'info, T: ZeroCopy + Owner> ToAccountInfos<'info> for AccountLoader<'info, T> {
+    fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
+        vec![self.acc_info.clone()]
+    }
+}
+
+impl<'info, T: ZeroCopy + Owner> ToAccountInfo<'info> for AccountLoader<'info, T> {
+    fn to_account_info(&self) -> AccountInfo<'info> {
+        self.acc_info.clone()
+    }
+}
+
+impl<'info, T: ZeroCopy + Owner> Key for AccountLoader<'info, T> {
+    fn key(&self) -> Pubkey {
+        *self.acc_info.key
+    }
+}

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

@@ -187,6 +187,7 @@ pub fn generate_constraint_has_one(f: &Field, c: &ConstraintHasOne) -> proc_macr
     let ident = &f.ident;
     let field = match &f.ty {
         Ty::Loader(_) => quote! {#ident.load()?},
+        Ty::AccountLoader(_) => quote! {#ident.load()?},
         _ => quote! {#ident},
     };
     quote! {
@@ -203,6 +204,7 @@ pub fn generate_constraint_signer(f: &Field, _c: &ConstraintSigner) -> proc_macr
         Ty::ProgramAccount(_) => quote! { #ident.to_account_info() },
         Ty::Account(_) => quote! { #ident.to_account_info() },
         Ty::Loader(_) => quote! { #ident.to_account_info() },
+        Ty::AccountLoader(_) => quote! { #ident.to_account_info() },
         Ty::CpiAccount(_) => quote! { #ident.to_account_info() },
         _ => panic!("Invalid syntax: signer cannot be specified."),
     };
@@ -488,7 +490,7 @@ pub fn generate_init(
                 // and take the length (with +8 for the discriminator.)
                 None => {
                     let account_ty = f.account_ty();
-                    match matches!(f.ty, Ty::Loader(_)) {
+                    match matches!(f.ty, Ty::Loader(_) | Ty::AccountLoader(_)) {
                         false => {
                             quote! {
                                 let space = 8 + #account_ty::default().try_to_vec().unwrap().len();

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

@@ -278,6 +278,9 @@ impl Field {
             Ty::Account(_) => quote! {
                 anchor_lang::Account
             },
+            Ty::AccountLoader(_) => quote! {
+                anchor_lang::AccountLoader
+            },
             Ty::Loader(_) => quote! {
                 anchor_lang::Loader
             },
@@ -318,6 +321,12 @@ impl Field {
                     #ident
                 }
             }
+            Ty::AccountLoader(ty) => {
+                let ident = &ty.account_type_path;
+                quote! {
+                    #ident
+                }
+            }
             Ty::Loader(ty) => {
                 let ident = &ty.account_type_path;
                 quote! {
@@ -382,6 +391,7 @@ pub enum Ty {
     CpiState(CpiStateTy),
     ProgramAccount(ProgramAccountTy),
     Loader(LoaderTy),
+    AccountLoader(LoaderAccountTy),
     CpiAccount(CpiAccountTy),
     Sysvar(SysvarTy),
     Account(AccountTy),
@@ -425,6 +435,12 @@ pub struct CpiAccountTy {
     pub account_type_path: TypePath,
 }
 
+#[derive(Debug, PartialEq)]
+pub struct LoaderAccountTy {
+    // The struct type of the account.
+    pub account_type_path: TypePath,
+}
+
 #[derive(Debug, PartialEq)]
 pub struct LoaderTy {
     // The struct type of the account.

+ 1 - 0
lang/syn/src/parser/accounts/constraints.rs

@@ -625,6 +625,7 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
         if !matches!(self.f_ty, Some(Ty::ProgramAccount(_)))
             && !matches!(self.f_ty, Some(Ty::Account(_)))
             && !matches!(self.f_ty, Some(Ty::Loader(_)))
+            && !matches!(self.f_ty, Some(Ty::AccountLoader(_)))
         {
             return Err(ParseError::new(
                 c.span(),

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

@@ -74,6 +74,7 @@ fn is_field_primitive(f: &syn::Field) -> ParseResult<bool> {
             | "UncheckedAccount"
             | "CpiState"
             | "Loader"
+            | "AccountLoader"
             | "Account"
             | "Program"
             | "Signer"
@@ -95,6 +96,7 @@ fn parse_ty(f: &syn::Field) -> ParseResult<Ty> {
         "AccountInfo" => Ty::AccountInfo,
         "UncheckedAccount" => Ty::UncheckedAccount,
         "Loader" => Ty::Loader(parse_program_account_zero_copy(&path)?),
+        "AccountLoader" => Ty::AccountLoader(parse_program_loader_account(&path)?),
         "Account" => Ty::Account(parse_account_ty(&path)?),
         "Program" => Ty::Program(parse_program_ty(&path)?),
         "Signer" => Ty::Signer,
@@ -161,6 +163,12 @@ fn parse_program_account_zero_copy(path: &syn::Path) -> ParseResult<LoaderTy> {
         account_type_path: account_ident,
     })
 }
+fn parse_program_loader_account(path: &syn::Path) -> ParseResult<LoaderAccountTy> {
+    let account_ident = parse_account(path)?;
+    Ok(LoaderAccountTy {
+        account_type_path: account_ident,
+    })
+}
 
 fn parse_account_ty(path: &syn::Path) -> ParseResult<AccountTy> {
     let account_type_path = parse_account(path)?;

+ 7 - 0
lang/tests/generics_test.rs

@@ -3,6 +3,7 @@
 use anchor_lang::prelude::borsh::maybestd::io::Write;
 use anchor_lang::prelude::*;
 use borsh::{BorshDeserialize, BorshSerialize};
+use solana_program::pubkey::Pubkey;
 
 // Needed to declare accounts.
 declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
@@ -16,6 +17,7 @@ where
     pub non_generic: AccountInfo<'info>,
     pub generic: Account<'info, T>,
     pub const_generic: Loader<'info, FooAccount<N>>,
+    pub const_generic_loader: AccountLoader<'info, FooAccount<N>>,
     pub associated: Account<'info, Associated<U>>,
 }
 
@@ -45,3 +47,8 @@ impl<const N: usize> BorshDeserialize for WrappedU8Array<N> {
         todo!()
     }
 }
+impl<const N: usize> Owner for WrappedU8Array<N> {
+    fn owner() -> Pubkey {
+        crate::ID
+    }
+}

+ 5 - 1
tests/zero-copy/Anchor.toml

@@ -3,7 +3,11 @@ cluster = "localnet"
 wallet = "~/.config/solana/id.json"
 
 [workspace]
-members = ["programs/zero-copy"]
+members = ["programs/zero-copy", "programs/zero-cpi"]
 
 [scripts]
 test = "mocha -t 1000000 tests/"
+
+[programs.localnet]
+zero_cpi = "ErjUjtqKE5AGWUsjseSJCVLtddM6rhaMbDqmhzraF9h6"
+zero_copy = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"

+ 13 - 60
tests/zero-copy/programs/zero-copy/src/lib.rs

@@ -11,42 +11,9 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
 
 #[program]
 pub mod zero_copy {
-    use super::*;
-
-    #[state(zero_copy)]
-    pub struct Globals {
-        pub authority: Pubkey,
-        // The solana runtime currently restricts how much one can resize an
-        // account on CPI to ~10240 bytes. State accounts are program derived
-        // addresses, which means its max size is limited by this restriction
-        // (i.e., this is not an Anchor specific issue).
-        //
-        // As a result, we only use 250 events here.
-        //
-        // For larger zero-copy data structures, one must use non-state anchor
-        // accounts, as is demonstrated below.
-        pub events: [Event; 250],
-    }
-
-    impl Globals {
-        // Note that the `new` constructor is different from non-zero-copy
-        // state accounts. Namely, it takes in a `&mut self` parameter.
-        pub fn new(&mut self, ctx: Context<New>) -> ProgramResult {
-            self.authority = *ctx.accounts.authority.key;
-            Ok(())
-        }
+    use std::str::FromStr;
 
-        #[access_control(auth(&self, &ctx))]
-        pub fn set_event(
-            &mut self,
-            ctx: Context<SetEvent>,
-            idx: u32,
-            event: RpcEvent,
-        ) -> ProgramResult {
-            self.events[idx as usize] = event.into();
-            Ok(())
-        }
-    }
+    use super::*;
 
     pub fn create_foo(ctx: Context<CreateFoo>) -> ProgramResult {
         let foo = &mut ctx.accounts.foo.load_init()?;
@@ -97,12 +64,6 @@ pub mod zero_copy {
     }
 }
 
-#[derive(Accounts)]
-pub struct New<'info> {
-    #[account(signer)]
-    authority: AccountInfo<'info>,
-}
-
 #[derive(Accounts)]
 pub struct SetEvent<'info> {
     #[account(signer)]
@@ -112,7 +73,7 @@ pub struct SetEvent<'info> {
 #[derive(Accounts)]
 pub struct CreateFoo<'info> {
     #[account(zero)]
-    foo: Loader<'info, Foo>,
+    foo: AccountLoader<'info, Foo>,
     #[account(signer)]
     authority: AccountInfo<'info>,
 }
@@ -120,7 +81,7 @@ pub struct CreateFoo<'info> {
 #[derive(Accounts)]
 pub struct UpdateFoo<'info> {
     #[account(mut, has_one = authority)]
-    foo: Loader<'info, Foo>,
+    foo: AccountLoader<'info, Foo>,
     #[account(signer)]
     authority: AccountInfo<'info>,
 }
@@ -131,7 +92,7 @@ pub struct UpdateFooSecond<'info> {
         mut,
         constraint = &foo.load()?.get_second_authority() == second_authority.key,
     )]
-    foo: Loader<'info, Foo>,
+    foo: AccountLoader<'info, Foo>,
     #[account(signer)]
     second_authority: AccountInfo<'info>,
 }
@@ -142,15 +103,14 @@ pub struct CreateBar<'info> {
         init,
         seeds = [authority.key().as_ref(), foo.key().as_ref()],
         bump,
-        payer = authority,
+        payer = authority, owner = *program_id
     )]
-    bar: Loader<'info, Bar>,
+    bar: AccountLoader<'info, Bar>,
     #[account(signer)]
     authority: AccountInfo<'info>,
-    foo: Loader<'info, Foo>,
+    foo: AccountLoader<'info, Foo>,
     system_program: AccountInfo<'info>,
 }
-
 #[derive(Accounts)]
 pub struct UpdateBar<'info> {
     #[account(
@@ -159,22 +119,22 @@ pub struct UpdateBar<'info> {
         seeds = [authority.key().as_ref(), foo.key().as_ref()],
         bump,
     )]
-    bar: Loader<'info, Bar>,
+    pub bar: AccountLoader<'info, Bar>,
     #[account(signer)]
-    authority: AccountInfo<'info>,
-    foo: Loader<'info, Foo>,
+    pub authority: AccountInfo<'info>,
+    pub foo: AccountLoader<'info, Foo>,
 }
 
 #[derive(Accounts)]
 pub struct CreateLargeAccount<'info> {
     #[account(zero)]
-    event_q: Loader<'info, EventQ>,
+    event_q: AccountLoader<'info, EventQ>,
 }
 
 #[derive(Accounts)]
 pub struct UpdateLargeAccount<'info> {
     #[account(mut)]
-    event_q: Loader<'info, EventQ>,
+    event_q: AccountLoader<'info, EventQ>,
     #[account(signer)]
     from: AccountInfo<'info>,
 }
@@ -231,10 +191,3 @@ impl From<RpcEvent> for Event {
         }
     }
 }
-
-fn auth(globals: &Globals, ctx: &Context<SetEvent>) -> ProgramResult {
-    if &globals.authority != ctx.accounts.authority.key {
-        return Err(ProgramError::Custom(1)); // Arbitrary error.
-    }
-    Ok(())
-}

+ 19 - 0
tests/zero-copy/programs/zero-cpi/Cargo.toml

@@ -0,0 +1,19 @@
+[package]
+name = "zero-cpi"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2018"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "zero_cpi"
+
+[features]
+no-entrypoint = []
+no-idl = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = { path = "../../../../lang" }
+zero-copy = { path = "../zero-copy", features = ["cpi"] }

+ 2 - 0
tests/zero-copy/programs/zero-cpi/Xargo.toml

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

+ 35 - 0
tests/zero-copy/programs/zero-cpi/src/lib.rs

@@ -0,0 +1,35 @@
+use anchor_lang::prelude::*;
+use zero_copy::cpi::accounts::UpdateBar;
+use zero_copy::program::ZeroCopy;
+use zero_copy::{self, Bar, Foo};
+
+declare_id!("ErjUjtqKE5AGWUsjseSJCVLtddM6rhaMbDqmhzraF9h6");
+
+#[program]
+pub mod zero_cpi {
+    use super::*;
+    pub fn check_cpi(ctx: Context<CheckCpi>, data: u64) -> ProgramResult {
+        let cpi_program = ctx.accounts.zero_copy_program.to_account_info();
+        let cpi_accounts = UpdateBar {
+            authority: ctx.accounts.authority.clone(),
+            bar: ctx.accounts.bar.to_account_info(),
+            foo: ctx.accounts.foo.to_account_info(),
+        };
+        let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
+        zero_copy::cpi::update_bar(cpi_ctx, data);
+        Ok(())
+    }
+}
+
+#[derive(Accounts)]
+pub struct CheckCpi<'info> {
+    #[account(
+        mut,
+        has_one = authority,
+    )]
+    bar: AccountLoader<'info, Bar>,
+    #[account(signer)]
+    authority: AccountInfo<'info>,
+    foo: AccountLoader<'info, Foo>,
+    zero_copy_program: Program<'info, ZeroCopy>,
+}

+ 112 - 150
tests/zero-copy/tests/zero-copy.js

@@ -1,56 +1,17 @@
-const anchor = require("@project-serum/anchor");
-const PublicKey = anchor.web3.PublicKey;
-const BN = anchor.BN;
-const assert = require("assert");
+const anchor = require('@project-serum/anchor')
+const PublicKey = anchor.web3.PublicKey
+const BN = anchor.BN
+const assert = require('assert')
 
-describe("zero-copy", () => {
+describe('zero-copy', () => {
   // Configure the client to use the local cluster.
-  anchor.setProvider(anchor.Provider.env());
+  anchor.setProvider(anchor.Provider.env())
 
-  const program = anchor.workspace.ZeroCopy;
+  const program = anchor.workspace.ZeroCopy
+  const programCpi = anchor.workspace.ZeroCpi
 
-  const foo = anchor.web3.Keypair.generate();
-
-  it("Creates zero copy state", async () => {
-    await program.state.rpc.new({
-      accounts: {
-        authority: program.provider.wallet.publicKey,
-      },
-    });
-    const state = await program.state.fetch();
-    assert.ok(state.authority.equals(program.provider.wallet.publicKey));
-    assert.ok(state.events.length === 250);
-    state.events.forEach((event, idx) => {
-      assert.ok(event.from.equals(PublicKey.default));
-      assert.ok(event.data.toNumber() === 0);
-    });
-  });
-
-  it("Updates zero copy state", async () => {
-    let event = {
-      from: PublicKey.default,
-      data: new BN(1234),
-    };
-    await program.state.rpc.setEvent(5, event, {
-      accounts: {
-        authority: program.provider.wallet.publicKey,
-      },
-    });
-    const state = await program.state.fetch();
-    assert.ok(state.authority.equals(program.provider.wallet.publicKey));
-    assert.ok(state.events.length === 250);
-    state.events.forEach((event, idx) => {
-      if (idx === 5) {
-        assert.ok(event.from.equals(event.from));
-        assert.ok(event.data.eq(event.data));
-      } else {
-        assert.ok(event.from.equals(PublicKey.default));
-        assert.ok(event.data.toNumber() === 0);
-      }
-    });
-  });
-
-  it("Is creates a zero copy account", async () => {
+  const foo = anchor.web3.Keypair.generate()
+  it('Is creates a zero copy account', async () => {
     await program.rpc.createFoo({
       accounts: {
         foo: foo.publicKey,
@@ -59,73 +20,70 @@ describe("zero-copy", () => {
       },
       instructions: [await program.account.foo.createInstruction(foo)],
       signers: [foo],
-    });
-    const account = await program.account.foo.fetch(foo.publicKey);
+    })
+    const account = await program.account.foo.fetch(foo.publicKey)
     assert.ok(
       JSON.stringify(account.authority.toBuffer()) ===
         JSON.stringify(program.provider.wallet.publicKey.toBuffer())
-    );
-    assert.ok(account.data.toNumber() === 0);
-    assert.ok(account.secondData.toNumber() === 0);
+    )
+    assert.ok(account.data.toNumber() === 0)
+    assert.ok(account.secondData.toNumber() === 0)
     assert.ok(
       JSON.stringify(account.secondAuthority) ===
         JSON.stringify([...program.provider.wallet.publicKey.toBuffer()])
-    );
-  });
+    )
+  })
 
-  it("Updates a zero copy account field", async () => {
+  it('Updates a zero copy account field', async () => {
     await program.rpc.updateFoo(new BN(1234), {
       accounts: {
         foo: foo.publicKey,
         authority: program.provider.wallet.publicKey,
       },
-    });
+    })
 
-    const account = await program.account.foo.fetch(foo.publicKey);
+    const account = await program.account.foo.fetch(foo.publicKey)
 
     assert.ok(
       JSON.stringify(account.authority.toBuffer()) ===
         JSON.stringify(program.provider.wallet.publicKey.toBuffer())
-    );
-    assert.ok(account.data.toNumber() === 1234);
-    assert.ok(account.secondData.toNumber() === 0);
+    )
+    assert.ok(account.data.toNumber() === 1234)
+    assert.ok(account.secondData.toNumber() === 0)
     assert.ok(
       JSON.stringify(account.secondAuthority) ===
         JSON.stringify([...program.provider.wallet.publicKey.toBuffer()])
-    );
-  });
+    )
+  })
 
-  it("Updates a a second zero copy account field", async () => {
+  it('Updates a a second zero copy account field', async () => {
     await program.rpc.updateFooSecond(new BN(55), {
       accounts: {
         foo: foo.publicKey,
         secondAuthority: program.provider.wallet.publicKey,
       },
-    });
+    })
 
-    const account = await program.account.foo.fetch(foo.publicKey);
+    const account = await program.account.foo.fetch(foo.publicKey)
 
     assert.ok(
       JSON.stringify(account.authority.toBuffer()) ===
         JSON.stringify(program.provider.wallet.publicKey.toBuffer())
-    );
-    assert.ok(account.data.toNumber() === 1234);
-    assert.ok(account.secondData.toNumber() === 55);
+    )
+    assert.ok(account.data.toNumber() === 1234)
+    assert.ok(account.secondData.toNumber() === 55)
     assert.ok(
       JSON.stringify(account.secondAuthority) ===
         JSON.stringify([...program.provider.wallet.publicKey.toBuffer()])
-    );
-  });
+    )
+  })
 
-  it("Creates an associated zero copy account", async () => {
+  it('Creates an associated zero copy account', async () => {
     await program.rpc.createBar({
       accounts: {
         bar: (
           await PublicKey.findProgramAddress(
-            [
-              program.provider.wallet.publicKey.toBuffer(),
-              foo.publicKey.toBuffer(),
-            ],
+            [program.provider.wallet.publicKey.toBuffer(), foo.publicKey.toBuffer()],
             program.programId
           )
         )[0],
@@ -133,86 +91,90 @@ describe("zero-copy", () => {
         foo: foo.publicKey,
         systemProgram: anchor.web3.SystemProgram.programId,
       },
-    });
+    })
 
     const bar = (
       await PublicKey.findProgramAddress(
-        [
-          program.provider.wallet.publicKey.toBuffer(),
-          foo.publicKey.toBuffer(),
-        ],
+        [program.provider.wallet.publicKey.toBuffer(), foo.publicKey.toBuffer()],
         program.programId
       )
-    )[0];
-    const barAccount = await program.account.bar.fetch(bar);
-    assert.ok(barAccount.authority.equals(program.provider.wallet.publicKey));
-    assert.ok(barAccount.data.toNumber() === 0);
-  });
+    )[0]
+    const barAccount = await program.account.bar.fetch(bar)
+    assert.ok(barAccount.authority.equals(program.provider.wallet.publicKey))
+    assert.ok(barAccount.data.toNumber() === 0)
+  })
 
-  it("Updates an associated zero copy account", async () => {
+  it('Updates an associated zero copy account', async () => {
     const bar = (
       await PublicKey.findProgramAddress(
-        [
-          program.provider.wallet.publicKey.toBuffer(),
-          foo.publicKey.toBuffer(),
-        ],
+        [program.provider.wallet.publicKey.toBuffer(), foo.publicKey.toBuffer()],
         program.programId
       )
-    )[0];
+    )[0]
     await program.rpc.updateBar(new BN(99), {
       accounts: {
         bar,
         authority: program.provider.wallet.publicKey,
         foo: foo.publicKey,
       },
-    });
-    const barAccount = await program.account.bar.fetch(bar);
-    assert.ok(barAccount.authority.equals(program.provider.wallet.publicKey));
-    assert.ok(barAccount.data.toNumber() === 99);
-  });
+    })
+    const barAccount = await program.account.bar.fetch(bar)
+    assert.ok(barAccount.authority.equals(program.provider.wallet.publicKey))
+    assert.ok(barAccount.data.toNumber() === 99)
+    // Check zero_copy CPI
+    await programCpi.rpc.checkCpi(new BN(1337), {
+      accounts: {
+        bar,
+        authority: program.provider.wallet.publicKey,
+        foo: foo.publicKey,
+        zeroCopyProgram: program.programId,
+      },
+    })
+    const barAccountAfterCpi = await program.account.bar.fetch(bar)
+    assert.ok(barAccountAfterCpi.authority.equals(program.provider.wallet.publicKey))
+    assert.ok(barAccountAfterCpi.data.toNumber() === 1337)
+  })
 
-  const eventQ = anchor.web3.Keypair.generate();
-  const size = 1000000 + 8; // Account size in bytes.
+  const eventQ = anchor.web3.Keypair.generate()
+  const size = 1000000 + 8 // Account size in bytes.
 
-  it("Creates a large event queue", async () => {
+  it('Creates a large event queue', async () => {
     await program.rpc.createLargeAccount({
       accounts: {
         eventQ: eventQ.publicKey,
         rent: anchor.web3.SYSVAR_RENT_PUBKEY,
       },
-      instructions: [
-        await program.account.eventQ.createInstruction(eventQ, size),
-      ],
+      instructions: [await program.account.eventQ.createInstruction(eventQ, size)],
       signers: [eventQ],
-    });
-    const account = await program.account.eventQ.fetch(eventQ.publicKey);
-    assert.ok(account.events.length === 25000);
+    })
+    const account = await program.account.eventQ.fetch(eventQ.publicKey)
+    assert.ok(account.events.length === 25000)
     account.events.forEach((event) => {
-      assert.ok(event.from.equals(PublicKey.default));
-      assert.ok(event.data.toNumber() === 0);
-    });
-  });
+      assert.ok(event.from.equals(PublicKey.default))
+      assert.ok(event.data.toNumber() === 0)
+    })
+  })
 
-  it("Updates a large event queue", async () => {
+  it('Updates a large event queue', async () => {
     // Set index 0.
     await program.rpc.updateLargeAccount(0, new BN(48), {
       accounts: {
         eventQ: eventQ.publicKey,
         from: program.provider.wallet.publicKey,
       },
-    });
+    })
     // Verify update.
-    let account = await program.account.eventQ.fetch(eventQ.publicKey);
-    assert.ok(account.events.length === 25000);
+    let account = await program.account.eventQ.fetch(eventQ.publicKey)
+    assert.ok(account.events.length === 25000)
     account.events.forEach((event, idx) => {
       if (idx === 0) {
-        assert.ok(event.from.equals(program.provider.wallet.publicKey));
-        assert.ok(event.data.toNumber() === 48);
+        assert.ok(event.from.equals(program.provider.wallet.publicKey))
+        assert.ok(event.data.toNumber() === 48)
       } else {
-        assert.ok(event.from.equals(PublicKey.default));
-        assert.ok(event.data.toNumber() === 0);
+        assert.ok(event.from.equals(PublicKey.default))
+        assert.ok(event.data.toNumber() === 0)
       }
-    });
+    })
 
     // Set index 11111.
     await program.rpc.updateLargeAccount(11111, new BN(1234), {
@@ -220,22 +182,22 @@ describe("zero-copy", () => {
         eventQ: eventQ.publicKey,
         from: program.provider.wallet.publicKey,
       },
-    });
+    })
     // Verify update.
-    account = await program.account.eventQ.fetch(eventQ.publicKey);
-    assert.ok(account.events.length === 25000);
+    account = await program.account.eventQ.fetch(eventQ.publicKey)
+    assert.ok(account.events.length === 25000)
     account.events.forEach((event, idx) => {
       if (idx === 0) {
-        assert.ok(event.from.equals(program.provider.wallet.publicKey));
-        assert.ok(event.data.toNumber() === 48);
+        assert.ok(event.from.equals(program.provider.wallet.publicKey))
+        assert.ok(event.data.toNumber() === 48)
       } else if (idx === 11111) {
-        assert.ok(event.from.equals(program.provider.wallet.publicKey));
-        assert.ok(event.data.toNumber() === 1234);
+        assert.ok(event.from.equals(program.provider.wallet.publicKey))
+        assert.ok(event.data.toNumber() === 1234)
       } else {
-        assert.ok(event.from.equals(PublicKey.default));
-        assert.ok(event.data.toNumber() === 0);
+        assert.ok(event.from.equals(PublicKey.default))
+        assert.ok(event.data.toNumber() === 0)
       }
-    });
+    })
 
     // Set last index.
     await program.rpc.updateLargeAccount(24999, new BN(99), {
@@ -243,28 +205,28 @@ describe("zero-copy", () => {
         eventQ: eventQ.publicKey,
         from: program.provider.wallet.publicKey,
       },
-    });
+    })
     // Verify update.
-    account = await program.account.eventQ.fetch(eventQ.publicKey);
-    assert.ok(account.events.length === 25000);
+    account = await program.account.eventQ.fetch(eventQ.publicKey)
+    assert.ok(account.events.length === 25000)
     account.events.forEach((event, idx) => {
       if (idx === 0) {
-        assert.ok(event.from.equals(program.provider.wallet.publicKey));
-        assert.ok(event.data.toNumber() === 48);
+        assert.ok(event.from.equals(program.provider.wallet.publicKey))
+        assert.ok(event.data.toNumber() === 48)
       } else if (idx === 11111) {
-        assert.ok(event.from.equals(program.provider.wallet.publicKey));
-        assert.ok(event.data.toNumber() === 1234);
+        assert.ok(event.from.equals(program.provider.wallet.publicKey))
+        assert.ok(event.data.toNumber() === 1234)
       } else if (idx === 24999) {
-        assert.ok(event.from.equals(program.provider.wallet.publicKey));
-        assert.ok(event.data.toNumber() === 99);
+        assert.ok(event.from.equals(program.provider.wallet.publicKey))
+        assert.ok(event.data.toNumber() === 99)
       } else {
-        assert.ok(event.from.equals(PublicKey.default));
-        assert.ok(event.data.toNumber() === 0);
+        assert.ok(event.from.equals(PublicKey.default))
+        assert.ok(event.data.toNumber() === 0)
       }
-    });
-  });
+    })
+  })
 
-  it("Errors when setting an out of bounds index", async () => {
+  it('Errors when setting an out of bounds index', async () => {
     // Fail to set non existing index.
     await assert.rejects(
       async () => {
@@ -273,12 +235,12 @@ describe("zero-copy", () => {
             eventQ: eventQ.publicKey,
             from: program.provider.wallet.publicKey,
           },
-        });
+        })
       },
       (err) => {
-        console.log("err", err);
-        return true;
+        console.log('err', err)
+        return true
       }
-    );
-  });
-});
+    )
+  })
+})