lang: Support for AccountLoader (#792)
This commit is contained in:
parent
9d33e13465
commit
1c5c6a8aba
|
@ -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
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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"] }
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -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>,
|
||||
}
|
|
@ -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
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue