lang: Support for AccountLoader (#792)

This commit is contained in:
NorbertBodziony 2021-10-08 18:24:25 +02:00 committed by GitHub
parent 9d33e13465
commit 1c5c6a8aba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 426 additions and 216 deletions

View File

@ -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

View File

@ -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
lang/src/loader_account.rs Normal file
View File

@ -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
}
}

View File

@ -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();

View File

@ -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.

View File

@ -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(),

View File

@ -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)?;

View File

@ -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
}
}

View File

@ -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"

View File

@ -11,43 +11,10 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod zero_copy {
use std::str::FromStr;
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(())
}
#[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(())
}
}
pub fn create_foo(ctx: Context<CreateFoo>) -> ProgramResult {
let foo = &mut ctx.accounts.foo.load_init()?;
foo.authority = *ctx.accounts.authority.key;
@ -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(())
}

View File

@ -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"] }

View File

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

View File

@ -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>,
}

View File

@ -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
}
);
});
});
)
})
})