lang, ts: account versioning
This commit is contained in:
parent
1164ebdcfc
commit
9f3ea48b7b
|
@ -11,6 +11,11 @@ incremented for features.
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Breaking
|
||||||
|
|
||||||
|
* ts: `BorshAccountsCoder.accountDiscriminator` method has been replaced with `BorshAccountHeader.discriminator` ([#]()).
|
||||||
|
* lang, ts: 8 byte account discriminator has been replaced with a versioned account header ([#]()).
|
||||||
|
|
||||||
## [0.21.0] - 2022-02-07
|
## [0.21.0] - 2022-02-07
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
|
@ -303,7 +303,7 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "avm"
|
name = "avm"
|
||||||
version = "0.20.1"
|
version = "0.21.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
|
|
|
@ -291,12 +291,22 @@ fn handle_program_log<T: anchor_lang::Event + anchor_lang::AnchorDeserialize>(
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut slice: &[u8] = &borsh_bytes[..];
|
let mut slice: &[u8] = &borsh_bytes[..];
|
||||||
|
|
||||||
|
#[cfg(feature = "deprecated-layout")]
|
||||||
let disc: [u8; 8] = {
|
let disc: [u8; 8] = {
|
||||||
let mut disc = [0; 8];
|
let mut disc = [0; 8];
|
||||||
disc.copy_from_slice(&borsh_bytes[..8]);
|
disc.copy_from_slice(&borsh_bytes[..8]);
|
||||||
slice = &slice[8..];
|
slice = &slice[8..];
|
||||||
disc
|
disc
|
||||||
};
|
};
|
||||||
|
#[cfg(not(feature = "deprecated-layout"))]
|
||||||
|
let disc: [u8; 4] = {
|
||||||
|
let mut disc = [0; 4];
|
||||||
|
disc.copy_from_slice(&borsh_bytes[2..6]);
|
||||||
|
slice = &slice[8..];
|
||||||
|
disc
|
||||||
|
};
|
||||||
|
|
||||||
let mut event = None;
|
let mut event = None;
|
||||||
if disc == T::discriminator() {
|
if disc == T::discriminator() {
|
||||||
let e: T = anchor_lang::AnchorDeserialize::deserialize(&mut slice)
|
let e: T = anchor_lang::AnchorDeserialize::deserialize(&mut slice)
|
||||||
|
|
|
@ -88,24 +88,6 @@ pub fn account(
|
||||||
let account_name = &account_strct.ident;
|
let account_name = &account_strct.ident;
|
||||||
let (impl_gen, type_gen, where_clause) = account_strct.generics.split_for_impl();
|
let (impl_gen, type_gen, where_clause) = account_strct.generics.split_for_impl();
|
||||||
|
|
||||||
let discriminator: proc_macro2::TokenStream = {
|
|
||||||
// Namespace the discriminator to prevent collisions.
|
|
||||||
let discriminator_preimage = {
|
|
||||||
// For now, zero copy accounts can't be namespaced.
|
|
||||||
if namespace.is_empty() {
|
|
||||||
format!("account:{}", account_name)
|
|
||||||
} else {
|
|
||||||
format!("{}:{}", namespace, account_name)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut discriminator = [0u8; 8];
|
|
||||||
discriminator.copy_from_slice(
|
|
||||||
&anchor_syn::hash::hash(discriminator_preimage.as_bytes()).to_bytes()[..8],
|
|
||||||
);
|
|
||||||
format!("{:?}", discriminator).parse().unwrap()
|
|
||||||
};
|
|
||||||
|
|
||||||
let owner_impl = {
|
let owner_impl = {
|
||||||
if namespace.is_empty() {
|
if namespace.is_empty() {
|
||||||
quote! {
|
quote! {
|
||||||
|
@ -121,6 +103,60 @@ pub fn account(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let discriminator: proc_macro2::TokenStream = {
|
||||||
|
// Namespace the discriminator to prevent collisions.
|
||||||
|
let discriminator_preimage = {
|
||||||
|
// For now, zero copy accounts can't be namespaced.
|
||||||
|
if namespace.is_empty() {
|
||||||
|
format!("account:{}", account_name)
|
||||||
|
} else {
|
||||||
|
format!("{}:{}", namespace, account_name)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if cfg!(feature = "deprecated-layout") {
|
||||||
|
let mut discriminator = [0u8; 8];
|
||||||
|
discriminator.copy_from_slice(
|
||||||
|
&anchor_syn::hash::hash(discriminator_preimage.as_bytes()).to_bytes()[..8],
|
||||||
|
);
|
||||||
|
format!("{:?}", discriminator).parse().unwrap()
|
||||||
|
} else {
|
||||||
|
let mut discriminator = [0u8; 4];
|
||||||
|
discriminator.copy_from_slice(
|
||||||
|
&anchor_syn::hash::hash(discriminator_preimage.as_bytes()).to_bytes()[..4],
|
||||||
|
);
|
||||||
|
format!("{:?}", discriminator).parse().unwrap()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let disc_bytes = {
|
||||||
|
if cfg!(feature = "deprecated-layout") {
|
||||||
|
quote! {
|
||||||
|
let given_disc = &buf[..8];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
let given_disc = &buf[2..6];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let disc_fn = {
|
||||||
|
if cfg!(feature = "deprecated-layout") {
|
||||||
|
quote! {
|
||||||
|
fn discriminator() -> [u8; 8] {
|
||||||
|
#discriminator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
fn discriminator() -> [u8; 4] {
|
||||||
|
#discriminator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
proc_macro::TokenStream::from({
|
proc_macro::TokenStream::from({
|
||||||
if is_zero_copy {
|
if is_zero_copy {
|
||||||
quote! {
|
quote! {
|
||||||
|
@ -137,9 +173,7 @@ pub fn account(
|
||||||
|
|
||||||
#[automatically_derived]
|
#[automatically_derived]
|
||||||
impl #impl_gen anchor_lang::Discriminator for #account_name #type_gen #where_clause {
|
impl #impl_gen anchor_lang::Discriminator for #account_name #type_gen #where_clause {
|
||||||
fn discriminator() -> [u8; 8] {
|
#disc_fn
|
||||||
#discriminator
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This trait is useful for clients deserializing accounts.
|
// This trait is useful for clients deserializing accounts.
|
||||||
|
@ -147,10 +181,11 @@ pub fn account(
|
||||||
#[automatically_derived]
|
#[automatically_derived]
|
||||||
impl #impl_gen anchor_lang::AccountDeserialize for #account_name #type_gen #where_clause {
|
impl #impl_gen anchor_lang::AccountDeserialize for #account_name #type_gen #where_clause {
|
||||||
fn try_deserialize(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
|
fn try_deserialize(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
|
||||||
if buf.len() < #discriminator.len() {
|
// Header is always 8 bytes.
|
||||||
|
if buf.len() < 8 {
|
||||||
return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorNotFound.into());
|
return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorNotFound.into());
|
||||||
}
|
}
|
||||||
let given_disc = &buf[..8];
|
#disc_bytes
|
||||||
if &#discriminator != given_disc {
|
if &#discriminator != given_disc {
|
||||||
return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorMismatch.into());
|
return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorMismatch.into());
|
||||||
}
|
}
|
||||||
|
@ -176,7 +211,6 @@ pub fn account(
|
||||||
#[automatically_derived]
|
#[automatically_derived]
|
||||||
impl #impl_gen anchor_lang::AccountSerialize for #account_name #type_gen #where_clause {
|
impl #impl_gen anchor_lang::AccountSerialize for #account_name #type_gen #where_clause {
|
||||||
fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> std::result::Result<(), ProgramError> {
|
fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> std::result::Result<(), ProgramError> {
|
||||||
writer.write_all(&#discriminator).map_err(|_| anchor_lang::__private::ErrorCode::AccountDidNotSerialize)?;
|
|
||||||
AnchorSerialize::serialize(
|
AnchorSerialize::serialize(
|
||||||
self,
|
self,
|
||||||
writer
|
writer
|
||||||
|
@ -192,7 +226,7 @@ pub fn account(
|
||||||
if buf.len() < #discriminator.len() {
|
if buf.len() < #discriminator.len() {
|
||||||
return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorNotFound.into());
|
return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorNotFound.into());
|
||||||
}
|
}
|
||||||
let given_disc = &buf[..8];
|
#disc_bytes
|
||||||
if &#discriminator != given_disc {
|
if &#discriminator != given_disc {
|
||||||
return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorMismatch.into());
|
return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorMismatch.into());
|
||||||
}
|
}
|
||||||
|
@ -208,9 +242,7 @@ pub fn account(
|
||||||
|
|
||||||
#[automatically_derived]
|
#[automatically_derived]
|
||||||
impl #impl_gen anchor_lang::Discriminator for #account_name #type_gen #where_clause {
|
impl #impl_gen anchor_lang::Discriminator for #account_name #type_gen #where_clause {
|
||||||
fn discriminator() -> [u8; 8] {
|
#disc_fn
|
||||||
#discriminator
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#owner_impl
|
#owner_impl
|
||||||
|
|
|
@ -18,13 +18,42 @@ pub fn event(
|
||||||
|
|
||||||
let discriminator: proc_macro2::TokenStream = {
|
let discriminator: proc_macro2::TokenStream = {
|
||||||
let discriminator_preimage = format!("event:{}", event_name);
|
let discriminator_preimage = format!("event:{}", event_name);
|
||||||
|
|
||||||
|
#[cfg(feature = "deprecated-layout")]
|
||||||
|
let discriminator = {
|
||||||
let mut discriminator = [0u8; 8];
|
let mut discriminator = [0u8; 8];
|
||||||
discriminator.copy_from_slice(
|
discriminator.copy_from_slice(
|
||||||
&anchor_syn::hash::hash(discriminator_preimage.as_bytes()).to_bytes()[..8],
|
&anchor_syn::hash::hash(discriminator_preimage.as_bytes()).to_bytes()[..8],
|
||||||
);
|
);
|
||||||
|
discriminator
|
||||||
|
};
|
||||||
|
#[cfg(not(feature = "deprecated-layout"))]
|
||||||
|
let discriminator = {
|
||||||
|
let mut discriminator = [0u8; 4];
|
||||||
|
discriminator.copy_from_slice(
|
||||||
|
&anchor_syn::hash::hash(discriminator_preimage.as_bytes()).to_bytes()[..4],
|
||||||
|
);
|
||||||
|
discriminator
|
||||||
|
};
|
||||||
format!("{:?}", discriminator).parse().unwrap()
|
format!("{:?}", discriminator).parse().unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let discriminator_trait_impl = {
|
||||||
|
if cfg!(feature = "deprecated_layout") {
|
||||||
|
quote! {
|
||||||
|
fn discriminator() -> [u8; 8] {
|
||||||
|
#discriminator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
fn discriminator() -> [u8; 4] {
|
||||||
|
#discriminator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
proc_macro::TokenStream::from(quote! {
|
proc_macro::TokenStream::from(quote! {
|
||||||
#[derive(anchor_lang::__private::EventIndex, AnchorSerialize, AnchorDeserialize)]
|
#[derive(anchor_lang::__private::EventIndex, AnchorSerialize, AnchorDeserialize)]
|
||||||
#event_strct
|
#event_strct
|
||||||
|
@ -38,9 +67,7 @@ pub fn event(
|
||||||
}
|
}
|
||||||
|
|
||||||
impl anchor_lang::Discriminator for #event_name {
|
impl anchor_lang::Discriminator for #event_name {
|
||||||
fn discriminator() -> [u8; 8] {
|
#discriminator_trait_impl
|
||||||
#discriminator
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -334,7 +334,10 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> AccountsEx
|
||||||
if &T::owner() == program_id {
|
if &T::owner() == program_id {
|
||||||
let info = self.to_account_info();
|
let info = self.to_account_info();
|
||||||
let mut data = info.try_borrow_mut_data()?;
|
let mut data = info.try_borrow_mut_data()?;
|
||||||
let dst: &mut [u8] = &mut data;
|
|
||||||
|
// Chop off the header.
|
||||||
|
let dst: &mut [u8] = &mut data[8..];
|
||||||
|
|
||||||
let mut cursor = std::io::Cursor::new(dst);
|
let mut cursor = std::io::Cursor::new(dst);
|
||||||
self.account.try_serialize(&mut cursor)?;
|
self.account.try_serialize(&mut cursor)?;
|
||||||
}
|
}
|
||||||
|
@ -405,3 +408,10 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> DerefMut for
|
||||||
&mut self.account
|
&mut self.account
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "deprecated-layout"))]
|
||||||
|
impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> Bump for Account<'a, T> {
|
||||||
|
fn seed(&self) -> u8 {
|
||||||
|
self.info.data.borrow()[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
//! Type facilitating on demand zero copy deserialization.
|
//! Type facilitating on demand zero copy deserialization.
|
||||||
|
|
||||||
use crate::error::ErrorCode;
|
use crate::error::ErrorCode;
|
||||||
use crate::{
|
use crate::*;
|
||||||
Accounts, AccountsClose, AccountsExit, Owner, ToAccountInfo, ToAccountInfos, ToAccountMetas,
|
|
||||||
ZeroCopy,
|
|
||||||
};
|
|
||||||
use arrayref::array_ref;
|
use arrayref::array_ref;
|
||||||
use solana_program::account_info::AccountInfo;
|
use solana_program::account_info::AccountInfo;
|
||||||
use solana_program::entrypoint::ProgramResult;
|
use solana_program::entrypoint::ProgramResult;
|
||||||
|
@ -14,7 +11,6 @@ use solana_program::pubkey::Pubkey;
|
||||||
use std::cell::{Ref, RefMut};
|
use std::cell::{Ref, RefMut};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::Write;
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
|
@ -24,8 +20,6 @@ use std::ops::DerefMut;
|
||||||
/// Note that using accounts in this way is distinctly different from using,
|
/// Note that using accounts in this way is distinctly different from using,
|
||||||
/// for example, the [`Account`](./struct.Account.html). Namely,
|
/// for example, the [`Account`](./struct.Account.html). Namely,
|
||||||
/// one must call
|
/// one must call
|
||||||
/// - `load_init` after initializing an account (this will ignore the missing
|
|
||||||
/// account discriminator that gets added only after the user's instruction code)
|
|
||||||
/// - `load` when the account is not mutable
|
/// - `load` when the account is not mutable
|
||||||
/// - `load_mut` when the account is mutable
|
/// - `load_mut` when the account is mutable
|
||||||
///
|
///
|
||||||
|
@ -117,7 +111,7 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a new `Loader` from a previously initialized account.
|
/// Constructs a new `AccountLoader` from a previously initialized account.
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
pub fn try_from(
|
pub fn try_from(
|
||||||
acc_info: &AccountInfo<'info>,
|
acc_info: &AccountInfo<'info>,
|
||||||
|
@ -127,7 +121,11 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> {
|
||||||
}
|
}
|
||||||
let data: &[u8] = &acc_info.try_borrow_data()?;
|
let data: &[u8] = &acc_info.try_borrow_data()?;
|
||||||
// Discriminator must match.
|
// Discriminator must match.
|
||||||
|
#[cfg(feature = "deprecated-layout")]
|
||||||
let disc_bytes = array_ref![data, 0, 8];
|
let disc_bytes = array_ref![data, 0, 8];
|
||||||
|
#[cfg(not(feature = "deprecated-layout"))]
|
||||||
|
let disc_bytes = array_ref![data, 2, 4];
|
||||||
|
|
||||||
if disc_bytes != &T::discriminator() {
|
if disc_bytes != &T::discriminator() {
|
||||||
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
|
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
|
||||||
}
|
}
|
||||||
|
@ -135,7 +133,7 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> {
|
||||||
Ok(AccountLoader::new(acc_info.clone()))
|
Ok(AccountLoader::new(acc_info.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a new `Loader` from an uninitialized account.
|
/// Constructs a new `AccountLoader` from an uninitialized account.
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
pub fn try_from_unchecked(
|
pub fn try_from_unchecked(
|
||||||
_program_id: &Pubkey,
|
_program_id: &Pubkey,
|
||||||
|
@ -146,16 +144,18 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> {
|
||||||
}
|
}
|
||||||
Ok(AccountLoader::new(acc_info.clone()))
|
Ok(AccountLoader::new(acc_info.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a Ref to the account data structure for reading.
|
/// Returns a Ref to the account data structure for reading.
|
||||||
pub fn load(&self) -> Result<Ref<T>, ProgramError> {
|
pub fn load(&self) -> Result<Ref<T>, ProgramError> {
|
||||||
let data = self.acc_info.try_borrow_data()?;
|
let data = self.acc_info.try_borrow_data()?;
|
||||||
|
|
||||||
|
#[cfg(feature = "deprecated-layout")]
|
||||||
let disc_bytes = array_ref![data, 0, 8];
|
let disc_bytes = array_ref![data, 0, 8];
|
||||||
|
#[cfg(not(feature = "deprecated-layout"))]
|
||||||
|
let disc_bytes = array_ref![data, 2, 4];
|
||||||
|
|
||||||
if disc_bytes != &T::discriminator() {
|
if disc_bytes != &T::discriminator() {
|
||||||
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
|
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Ref::map(data, |data| {
|
Ok(Ref::map(data, |data| {
|
||||||
bytemuck::from_bytes(&data[8..mem::size_of::<T>() + 8])
|
bytemuck::from_bytes(&data[8..mem::size_of::<T>() + 8])
|
||||||
}))
|
}))
|
||||||
|
@ -171,7 +171,11 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> {
|
||||||
|
|
||||||
let data = self.acc_info.try_borrow_mut_data()?;
|
let data = self.acc_info.try_borrow_mut_data()?;
|
||||||
|
|
||||||
|
#[cfg(feature = "deprecated-layout")]
|
||||||
let disc_bytes = array_ref![data, 0, 8];
|
let disc_bytes = array_ref![data, 0, 8];
|
||||||
|
#[cfg(not(feature = "deprecated-layout"))]
|
||||||
|
let disc_bytes = array_ref![data, 2, 4];
|
||||||
|
|
||||||
if disc_bytes != &T::discriminator() {
|
if disc_bytes != &T::discriminator() {
|
||||||
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
|
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
|
||||||
}
|
}
|
||||||
|
@ -180,30 +184,6 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> {
|
||||||
bytemuck::from_bytes_mut(&mut data.deref_mut()[8..mem::size_of::<T>() + 8])
|
bytemuck::from_bytes_mut(&mut data.deref_mut()[8..mem::size_of::<T>() + 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..mem::size_of::<T>() + 8])
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'info, T: ZeroCopy + Owner> Accounts<'info> for AccountLoader<'info, T> {
|
impl<'info, T: ZeroCopy + Owner> Accounts<'info> for AccountLoader<'info, T> {
|
||||||
|
@ -227,10 +207,7 @@ impl<'info, T: ZeroCopy + Owner> Accounts<'info> for AccountLoader<'info, T> {
|
||||||
impl<'info, T: ZeroCopy + Owner> AccountsExit<'info> for AccountLoader<'info, T> {
|
impl<'info, T: ZeroCopy + Owner> AccountsExit<'info> for AccountLoader<'info, T> {
|
||||||
// The account *cannot* be loaded when this is called.
|
// The account *cannot* be loaded when this is called.
|
||||||
fn exit(&self, _program_id: &Pubkey) -> ProgramResult {
|
fn exit(&self, _program_id: &Pubkey) -> ProgramResult {
|
||||||
let mut data = self.acc_info.try_borrow_mut_data()?;
|
// No-op.
|
||||||
let dst: &mut [u8] = &mut data;
|
|
||||||
let mut cursor = std::io::Cursor::new(dst);
|
|
||||||
cursor.write_all(&T::discriminator()).unwrap();
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -263,3 +240,10 @@ impl<'info, T: ZeroCopy + Owner> ToAccountInfos<'info> for AccountLoader<'info,
|
||||||
vec![self.acc_info.clone()]
|
vec![self.acc_info.clone()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "deprecated-layout"))]
|
||||||
|
impl<'a, T: ZeroCopy + Owner> Bump for AccountLoader<'a, T> {
|
||||||
|
fn seed(&self) -> u8 {
|
||||||
|
self.acc_info.data.borrow()[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
use crate::error::ErrorCode;
|
use crate::error::ErrorCode;
|
||||||
use crate::{
|
use crate::*;
|
||||||
Accounts, AccountsClose, AccountsExit, ToAccountInfo, ToAccountInfos, ToAccountMetas, ZeroCopy,
|
|
||||||
};
|
|
||||||
use arrayref::array_ref;
|
use arrayref::array_ref;
|
||||||
use solana_program::account_info::AccountInfo;
|
use solana_program::account_info::AccountInfo;
|
||||||
use solana_program::entrypoint::ProgramResult;
|
use solana_program::entrypoint::ProgramResult;
|
||||||
|
@ -11,7 +9,6 @@ use solana_program::pubkey::Pubkey;
|
||||||
use std::cell::{Ref, RefMut};
|
use std::cell::{Ref, RefMut};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::Write;
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
|
|
||||||
|
@ -62,8 +59,13 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
|
||||||
return Err(ErrorCode::AccountOwnedByWrongProgram.into());
|
return Err(ErrorCode::AccountOwnedByWrongProgram.into());
|
||||||
}
|
}
|
||||||
let data: &[u8] = &acc_info.try_borrow_data()?;
|
let data: &[u8] = &acc_info.try_borrow_data()?;
|
||||||
|
|
||||||
// Discriminator must match.
|
// Discriminator must match.
|
||||||
|
#[cfg(feature = "deprecated-layout")]
|
||||||
let disc_bytes = array_ref![data, 0, 8];
|
let disc_bytes = array_ref![data, 0, 8];
|
||||||
|
#[cfg(not(feature = "deprecated-layout"))]
|
||||||
|
let disc_bytes = array_ref![data, 2, 4];
|
||||||
|
|
||||||
if disc_bytes != &T::discriminator() {
|
if disc_bytes != &T::discriminator() {
|
||||||
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
|
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
|
||||||
}
|
}
|
||||||
|
@ -89,7 +91,11 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
|
||||||
pub fn load(&self) -> Result<Ref<T>, ProgramError> {
|
pub fn load(&self) -> Result<Ref<T>, ProgramError> {
|
||||||
let data = self.acc_info.try_borrow_data()?;
|
let data = self.acc_info.try_borrow_data()?;
|
||||||
|
|
||||||
|
#[cfg(feature = "deprecated-layout")]
|
||||||
let disc_bytes = array_ref![data, 0, 8];
|
let disc_bytes = array_ref![data, 0, 8];
|
||||||
|
#[cfg(not(feature = "deprecated-layout"))]
|
||||||
|
let disc_bytes = array_ref![data, 2, 4];
|
||||||
|
|
||||||
if disc_bytes != &T::discriminator() {
|
if disc_bytes != &T::discriminator() {
|
||||||
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
|
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
|
||||||
}
|
}
|
||||||
|
@ -108,7 +114,11 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
|
||||||
|
|
||||||
let data = self.acc_info.try_borrow_mut_data()?;
|
let data = self.acc_info.try_borrow_mut_data()?;
|
||||||
|
|
||||||
|
#[cfg(feature = "deprecated-layout")]
|
||||||
let disc_bytes = array_ref![data, 0, 8];
|
let disc_bytes = array_ref![data, 0, 8];
|
||||||
|
#[cfg(not(feature = "deprecated-layout"))]
|
||||||
|
let disc_bytes = array_ref![data, 2, 4];
|
||||||
|
|
||||||
if disc_bytes != &T::discriminator() {
|
if disc_bytes != &T::discriminator() {
|
||||||
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
|
return Err(ErrorCode::AccountDiscriminatorMismatch.into());
|
||||||
}
|
}
|
||||||
|
@ -117,31 +127,6 @@ impl<'info, T: ZeroCopy> Loader<'info, T> {
|
||||||
bytemuck::from_bytes_mut(&mut data.deref_mut()[8..])
|
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.
|
|
||||||
#[allow(deprecated)]
|
|
||||||
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..])
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
|
@ -167,10 +152,7 @@ impl<'info, T: ZeroCopy> Accounts<'info> for Loader<'info, T> {
|
||||||
impl<'info, T: ZeroCopy> AccountsExit<'info> for Loader<'info, T> {
|
impl<'info, T: ZeroCopy> AccountsExit<'info> for Loader<'info, T> {
|
||||||
// The account *cannot* be loaded when this is called.
|
// The account *cannot* be loaded when this is called.
|
||||||
fn exit(&self, _program_id: &Pubkey) -> ProgramResult {
|
fn exit(&self, _program_id: &Pubkey) -> ProgramResult {
|
||||||
let mut data = self.acc_info.try_borrow_mut_data()?;
|
// No-op.
|
||||||
let dst: &mut [u8] = &mut data;
|
|
||||||
let mut cursor = std::io::Cursor::new(dst);
|
|
||||||
cursor.write_all(&T::discriminator()).unwrap();
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,3 +189,10 @@ impl<'info, T: ZeroCopy> ToAccountInfos<'info> for Loader<'info, T> {
|
||||||
vec![self.acc_info.clone()]
|
vec![self.acc_info.clone()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "deprecated-layout"))]
|
||||||
|
impl<'a, T: ZeroCopy> Bump for Loader<'a, T> {
|
||||||
|
fn seed(&self) -> u8 {
|
||||||
|
self.acc_info.data.borrow()[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
use crate::accounts::cpi_account::CpiAccount;
|
use crate::accounts::cpi_account::CpiAccount;
|
||||||
use crate::error::ErrorCode;
|
use crate::error::ErrorCode;
|
||||||
use crate::{
|
use crate::*;
|
||||||
AccountDeserialize, AccountSerialize, Accounts, AccountsClose, AccountsExit, ToAccountInfo,
|
|
||||||
ToAccountInfos, ToAccountMetas,
|
|
||||||
};
|
|
||||||
use solana_program::account_info::AccountInfo;
|
use solana_program::account_info::AccountInfo;
|
||||||
use solana_program::entrypoint::ProgramResult;
|
use solana_program::entrypoint::ProgramResult;
|
||||||
use solana_program::instruction::AccountMeta;
|
use solana_program::instruction::AccountMeta;
|
||||||
|
@ -102,7 +99,10 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AccountsExit<'info
|
||||||
fn exit(&self, _program_id: &Pubkey) -> ProgramResult {
|
fn exit(&self, _program_id: &Pubkey) -> ProgramResult {
|
||||||
let info = self.to_account_info();
|
let info = self.to_account_info();
|
||||||
let mut data = info.try_borrow_mut_data()?;
|
let mut data = info.try_borrow_mut_data()?;
|
||||||
let dst: &mut [u8] = &mut data;
|
|
||||||
|
// Chop off the header.
|
||||||
|
let dst: &mut [u8] = &mut data[8..];
|
||||||
|
|
||||||
let mut cursor = std::io::Cursor::new(dst);
|
let mut cursor = std::io::Cursor::new(dst);
|
||||||
self.inner.account.try_serialize(&mut cursor)?;
|
self.inner.account.try_serialize(&mut cursor)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -181,3 +181,10 @@ where
|
||||||
Self::new(a.to_account_info(), Deref::deref(&a).clone())
|
Self::new(a.to_account_info(), Deref::deref(&a).clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "deprecated-layout"))]
|
||||||
|
impl<'a, T: AccountSerialize + AccountDeserialize + Clone> Bump for ProgramAccount<'a, T> {
|
||||||
|
fn seed(&self) -> u8 {
|
||||||
|
self.inner.info.data.borrow()[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -200,7 +200,10 @@ pub trait EventData: AnchorSerialize + Discriminator {
|
||||||
|
|
||||||
/// 8 byte unique identifier for a type.
|
/// 8 byte unique identifier for a type.
|
||||||
pub trait Discriminator {
|
pub trait Discriminator {
|
||||||
|
#[cfg(feature = "deprecated-layout")]
|
||||||
fn discriminator() -> [u8; 8];
|
fn discriminator() -> [u8; 8];
|
||||||
|
#[cfg(not(feature = "deprecated-layout"))]
|
||||||
|
fn discriminator() -> [u8; 4];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bump seed for program derived addresses.
|
/// Bump seed for program derived addresses.
|
||||||
|
|
|
@ -142,23 +142,44 @@ fn generate_constraint_address(f: &Field, c: &ConstraintAddress) -> proc_macro2:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_constraint_init(f: &Field, c: &ConstraintInitGroup) -> proc_macro2::TokenStream {
|
|
||||||
generate_constraint_init_group(f, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate_constraint_zeroed(f: &Field, _c: &ConstraintZeroed) -> proc_macro2::TokenStream {
|
pub fn generate_constraint_zeroed(f: &Field, _c: &ConstraintZeroed) -> proc_macro2::TokenStream {
|
||||||
let field = &f.ident;
|
let field = &f.ident;
|
||||||
let ty_decl = f.ty_decl();
|
let ty_decl = f.ty_decl();
|
||||||
let from_account_info = f.from_account_info_unchecked(None);
|
let account_ty = f.account_ty();
|
||||||
|
let from_account_info = f.from_account_info(None);
|
||||||
|
let header_write = {
|
||||||
|
if cfg!(feature = "deprecated-layout") {
|
||||||
|
quote! {
|
||||||
|
use std::io::{Write, Cursor};
|
||||||
|
use anchor_lang::Discriminator;
|
||||||
|
let __dst: &mut [u8] = &mut __data;
|
||||||
|
let mut __cursor = Cursor::new(__dst);
|
||||||
|
Write::write_all(&mut __cursor, &#account_ty::discriminator()).unwrap();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
use std::io::{Write, Cursor};
|
||||||
|
use anchor_lang::Discriminator;
|
||||||
|
let __dst: &mut [u8] = &mut __data[2..];
|
||||||
|
let mut __cursor = Cursor::new(__dst);
|
||||||
|
Write::write_all(&mut __cursor, &#account_ty::discriminator()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Check the *entire* account header is zero.
|
||||||
quote! {
|
quote! {
|
||||||
let #field: #ty_decl = {
|
let #field: #ty_decl = {
|
||||||
let mut __data: &[u8] = &#field.try_borrow_data()?;
|
{
|
||||||
let mut __disc_bytes = [0u8; 8];
|
let mut __data: &mut [u8] = &mut #field.try_borrow_mut_data()?;
|
||||||
__disc_bytes.copy_from_slice(&__data[..8]);
|
let mut __header_bytes = [0u8; 8];
|
||||||
let __discriminator = u64::from_le_bytes(__disc_bytes);
|
__header_bytes.copy_from_slice(&__data[..8]);
|
||||||
if __discriminator != 0 {
|
let __header = u64::from_le_bytes(__header_bytes);
|
||||||
|
if __header != 0 {
|
||||||
return Err(anchor_lang::__private::ErrorCode::ConstraintZero.into());
|
return Err(anchor_lang::__private::ErrorCode::ConstraintZero.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#header_write
|
||||||
|
}
|
||||||
#from_account_info
|
#from_account_info
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -276,7 +297,7 @@ pub fn generate_constraint_rent_exempt(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_macro2::TokenStream {
|
fn generate_constraint_init(f: &Field, c: &ConstraintInitGroup) -> proc_macro2::TokenStream {
|
||||||
let field = &f.ident;
|
let field = &f.ident;
|
||||||
let ty_decl = f.ty_decl();
|
let ty_decl = f.ty_decl();
|
||||||
let if_needed = if c.if_needed {
|
let if_needed = if c.if_needed {
|
||||||
|
@ -295,7 +316,7 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma
|
||||||
};
|
};
|
||||||
|
|
||||||
// Convert from account info to account context wrapper type.
|
// Convert from account info to account context wrapper type.
|
||||||
let from_account_info = f.from_account_info_unchecked(Some(&c.kind));
|
let from_account_info = f.from_account_info(Some(&c.kind));
|
||||||
|
|
||||||
// PDA bump seeds.
|
// PDA bump seeds.
|
||||||
let (find_pda, seeds_with_bump) = match &c.seeds {
|
let (find_pda, seeds_with_bump) = match &c.seeds {
|
||||||
|
@ -512,6 +533,45 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma
|
||||||
let create_account =
|
let create_account =
|
||||||
generate_create_account(field, quote! {space}, owner.clone(), seeds_with_bump);
|
generate_create_account(field, quote! {space}, owner.clone(), seeds_with_bump);
|
||||||
|
|
||||||
|
// Write the 8 byte header.
|
||||||
|
let header_write = {
|
||||||
|
match &f.ty {
|
||||||
|
Ty::Account(_)
|
||||||
|
| Ty::ProgramAccount(_)
|
||||||
|
| Ty::Loader(_)
|
||||||
|
| Ty::AccountLoader(_) => {
|
||||||
|
let account_ty = f.account_ty();
|
||||||
|
if cfg!(feature = "deprecated-layout") {
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
use std::io::{Write, Cursor};
|
||||||
|
use anchor_lang::Discriminator;
|
||||||
|
|
||||||
|
let mut __data = actual_field.try_borrow_mut_data()?;
|
||||||
|
let __dst: &mut [u8] = &mut __data;
|
||||||
|
let mut __cursor = Cursor::new(__dst);
|
||||||
|
Write::write_all(&mut __cursor, &#account_ty::discriminator()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
use std::io::{Write, Seek, SeekFrom, Cursor};
|
||||||
|
use anchor_lang::Discriminator;
|
||||||
|
|
||||||
|
let mut __data = actual_field.try_borrow_mut_data()?;
|
||||||
|
let __dst: &mut [u8] = &mut __data;
|
||||||
|
let mut __cursor = Cursor::new(__dst);
|
||||||
|
Seek::seek(&mut __cursor, SeekFrom::Start(2)).unwrap();
|
||||||
|
Write::write_all(&mut __cursor, &#account_ty::discriminator()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => quote! {},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Put it all together.
|
// Put it all together.
|
||||||
quote! {
|
quote! {
|
||||||
// Define the bump variable.
|
// Define the bump variable.
|
||||||
|
@ -534,6 +594,10 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma
|
||||||
#create_account
|
#create_account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write the account header into the account data before
|
||||||
|
// deserializing.
|
||||||
|
#header_write
|
||||||
|
|
||||||
// Convert from account info to account context wrapper type.
|
// Convert from account info to account context wrapper type.
|
||||||
let pa: #ty_decl = #from_account_info;
|
let pa: #ty_decl = #from_account_info;
|
||||||
|
|
||||||
|
|
|
@ -89,7 +89,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
|
||||||
let seed = anchor_lang::idl::IdlAccount::seed();
|
let seed = anchor_lang::idl::IdlAccount::seed();
|
||||||
let owner = accounts.program.key;
|
let owner = accounts.program.key;
|
||||||
let to = Pubkey::create_with_seed(&base, seed, owner).unwrap();
|
let to = Pubkey::create_with_seed(&base, seed, owner).unwrap();
|
||||||
// Space: account discriminator || authority pubkey || vec len || vec data
|
// Space: account header || authority pubkey || vec len || vec data
|
||||||
let space = 8 + 32 + 4 + data_len as usize;
|
let space = 8 + 32 + 4 + data_len as usize;
|
||||||
let rent = Rent::get()?;
|
let rent = Rent::get()?;
|
||||||
let lamports = rent.minimum_balance(space);
|
let lamports = rent.minimum_balance(space);
|
||||||
|
@ -130,6 +130,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
|
||||||
let mut data = accounts.to.try_borrow_mut_data()?;
|
let mut data = accounts.to.try_borrow_mut_data()?;
|
||||||
let dst: &mut [u8] = &mut data;
|
let dst: &mut [u8] = &mut data;
|
||||||
let mut cursor = std::io::Cursor::new(dst);
|
let mut cursor = std::io::Cursor::new(dst);
|
||||||
|
std::io::Seek::seek(&mut cursor, std::io::SeekFrom::Start(8)).unwrap();
|
||||||
idl_account.try_serialize(&mut cursor)?;
|
idl_account.try_serialize(&mut cursor)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -201,6 +202,34 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
|
||||||
let ix_name: proc_macro2::TokenStream =
|
let ix_name: proc_macro2::TokenStream =
|
||||||
generate_ctor_variant_name().parse().unwrap();
|
generate_ctor_variant_name().parse().unwrap();
|
||||||
let ix_name_log = format!("Instruction: {}", ix_name);
|
let ix_name_log = format!("Instruction: {}", ix_name);
|
||||||
|
let header_write = {
|
||||||
|
if cfg!(feature = "deprecated-layout") {
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
use std::io::{Write, Cursor};
|
||||||
|
use anchor_lang::Discriminator;
|
||||||
|
|
||||||
|
let mut __data = ctor_accounts.to.try_borrow_mut_data()?;
|
||||||
|
let __dst: &mut [u8] = &mut __data;
|
||||||
|
let mut __cursor = Cursor::new(__dst);
|
||||||
|
Write::write_all(&mut __cursor, &#name::discriminator()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
use std::io::{Write, Cursor, SeekFrom, Seek};
|
||||||
|
use anchor_lang::Discriminator;
|
||||||
|
|
||||||
|
let mut __data = ctor_accounts.to.try_borrow_mut_data()?;
|
||||||
|
let __dst: &mut [u8] = &mut __data;
|
||||||
|
let mut __cursor = Cursor::new(__dst);
|
||||||
|
Seek::seek(&mut __cursor, SeekFrom::Start(2)).unwrap();
|
||||||
|
Write::write_all(&mut __cursor, &#name::discriminator()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
if state.is_zero_copy {
|
if state.is_zero_copy {
|
||||||
quote! {
|
quote! {
|
||||||
// One time state account initializer. Will faill on subsequent
|
// One time state account initializer. Will faill on subsequent
|
||||||
|
@ -254,14 +283,17 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
|
||||||
&[seeds],
|
&[seeds],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Initialize the header.
|
||||||
|
#header_write
|
||||||
|
|
||||||
// Zero copy deserialize.
|
// Zero copy deserialize.
|
||||||
let loader: anchor_lang::accounts::loader::Loader<#mod_name::#name> = anchor_lang::accounts::loader::Loader::try_from_unchecked(program_id, &ctor_accounts.to)?;
|
let loader: anchor_lang::accounts::loader::Loader<#mod_name::#name> = anchor_lang::accounts::loader::Loader::try_from(program_id, &ctor_accounts.to)?;
|
||||||
|
|
||||||
// Invoke the ctor in a new lexical scope so that
|
// Invoke the ctor in a new lexical scope so that
|
||||||
// the zero-copy RefMut gets dropped. Required
|
// the zero-copy RefMut gets dropped. Required
|
||||||
// so that we can subsequently run the exit routine.
|
// so that we can subsequently run the exit routine.
|
||||||
{
|
{
|
||||||
let mut instance = loader.load_init()?;
|
let mut instance = loader.load_mut()?;
|
||||||
instance.new(
|
instance.new(
|
||||||
anchor_lang::context::Context::new(
|
anchor_lang::context::Context::new(
|
||||||
program_id,
|
program_id,
|
||||||
|
@ -344,11 +376,15 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
|
||||||
&[seeds],
|
&[seeds],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Initialize the account header.
|
||||||
|
#header_write
|
||||||
|
|
||||||
// Serialize the state and save it to storage.
|
// Serialize the state and save it to storage.
|
||||||
ctor_user_def_accounts.exit(program_id)?;
|
ctor_user_def_accounts.exit(program_id)?;
|
||||||
let mut data = ctor_accounts.to.try_borrow_mut_data()?;
|
let mut data = ctor_accounts.to.try_borrow_mut_data()?;
|
||||||
let dst: &mut [u8] = &mut data;
|
let dst: &mut [u8] = &mut data;
|
||||||
let mut cursor = std::io::Cursor::new(dst);
|
let mut cursor = std::io::Cursor::new(dst);
|
||||||
|
std::io::Seek::seek(&mut cursor, std::io::SeekFrom::Start(8)).unwrap();
|
||||||
instance.try_serialize(&mut cursor)?;
|
instance.try_serialize(&mut cursor)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -500,6 +536,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
|
||||||
let mut data = acc_info.try_borrow_mut_data()?;
|
let mut data = acc_info.try_borrow_mut_data()?;
|
||||||
let dst: &mut [u8] = &mut data;
|
let dst: &mut [u8] = &mut data;
|
||||||
let mut cursor = std::io::Cursor::new(dst);
|
let mut cursor = std::io::Cursor::new(dst);
|
||||||
|
std::io::Seek::seek(&mut cursor, std::io::SeekFrom::Start(8)).unwrap();
|
||||||
state.try_serialize(&mut cursor)?;
|
state.try_serialize(&mut cursor)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -628,6 +665,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
|
||||||
let mut data = acc_info.try_borrow_mut_data()?;
|
let mut data = acc_info.try_borrow_mut_data()?;
|
||||||
let dst: &mut [u8] = &mut data;
|
let dst: &mut [u8] = &mut data;
|
||||||
let mut cursor = std::io::Cursor::new(dst);
|
let mut cursor = std::io::Cursor::new(dst);
|
||||||
|
std::io::Seek::seek(&mut cursor, std::io::SeekFrom::Start(8)).unwrap();
|
||||||
state.try_serialize(&mut cursor)?;
|
state.try_serialize(&mut cursor)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -238,6 +238,7 @@ pub fn parse(
|
||||||
.collect::<Vec<IdlConst>>();
|
.collect::<Vec<IdlConst>>();
|
||||||
|
|
||||||
Ok(Some(Idl {
|
Ok(Some(Idl {
|
||||||
|
layout_version: "0.1.0".to_string(),
|
||||||
version,
|
version,
|
||||||
name: p.name.to_string(),
|
name: p.name.to_string(),
|
||||||
state,
|
state,
|
||||||
|
|
|
@ -5,7 +5,11 @@ pub mod file;
|
||||||
pub mod pda;
|
pub mod pda;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Idl {
|
pub struct Idl {
|
||||||
|
// Version of the idl protocol.
|
||||||
|
pub layout_version: String,
|
||||||
|
// Version of the program.
|
||||||
pub version: String,
|
pub version: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||||
|
|
|
@ -26,6 +26,13 @@ pub(crate) mod hash;
|
||||||
pub mod idl;
|
pub mod idl;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
|
|
||||||
|
// Layout indices.
|
||||||
|
pub const LAYOUT_VERSION: u8 = 0;
|
||||||
|
pub const LAYOUT_VERSION_INDEX: u8 = 0;
|
||||||
|
pub const LAYOUT_BUMP_INDEX: u8 = 1;
|
||||||
|
pub const LAYOUT_DISCRIMINATOR_INDEX: u8 = 2;
|
||||||
|
pub const LAYOUT_UNUSED_INDEX: u8 = 6;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Program {
|
pub struct Program {
|
||||||
pub state: Option<State>,
|
pub state: Option<State>,
|
||||||
|
@ -273,7 +280,7 @@ impl Field {
|
||||||
|
|
||||||
// TODO: remove the option once `CpiAccount` is completely removed (not
|
// TODO: remove the option once `CpiAccount` is completely removed (not
|
||||||
// just deprecated).
|
// just deprecated).
|
||||||
pub fn from_account_info_unchecked(&self, kind: Option<&InitKind>) -> proc_macro2::TokenStream {
|
pub fn from_account_info(&self, kind: Option<&InitKind>) -> proc_macro2::TokenStream {
|
||||||
let field = &self.ident;
|
let field = &self.ident;
|
||||||
let container_ty = self.container_ty();
|
let container_ty = self.container_ty();
|
||||||
match &self.ty {
|
match &self.ty {
|
||||||
|
@ -284,13 +291,13 @@ impl Field {
|
||||||
Ty::Account(AccountTy { boxed, .. }) => {
|
Ty::Account(AccountTy { boxed, .. }) => {
|
||||||
if *boxed {
|
if *boxed {
|
||||||
quote! {
|
quote! {
|
||||||
Box::new(#container_ty::try_from_unchecked(
|
Box::new(#container_ty::try_from(
|
||||||
&#field,
|
&#field,
|
||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
#container_ty::try_from_unchecked(
|
#container_ty::try_from(
|
||||||
&#field,
|
&#field,
|
||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
|
@ -298,7 +305,14 @@ impl Field {
|
||||||
}
|
}
|
||||||
Ty::CpiAccount(_) => {
|
Ty::CpiAccount(_) => {
|
||||||
quote! {
|
quote! {
|
||||||
#container_ty::try_from_unchecked(
|
#container_ty::try_from(
|
||||||
|
&#field,
|
||||||
|
)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ty::AccountLoader(_) => {
|
||||||
|
quote! {
|
||||||
|
#container_ty::try_from(
|
||||||
&#field,
|
&#field,
|
||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
|
@ -314,7 +328,7 @@ impl Field {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
quote! {
|
quote! {
|
||||||
#container_ty::try_from_unchecked(
|
#container_ty::try_from(
|
||||||
#owner_addr,
|
#owner_addr,
|
||||||
&#field,
|
&#field,
|
||||||
)?
|
)?
|
||||||
|
|
|
@ -19,7 +19,7 @@ pub mod chat {
|
||||||
let given_name = name.as_bytes();
|
let given_name = name.as_bytes();
|
||||||
let mut name = [0u8; 280];
|
let mut name = [0u8; 280];
|
||||||
name[..given_name.len()].copy_from_slice(given_name);
|
name[..given_name.len()].copy_from_slice(given_name);
|
||||||
let mut chat = ctx.accounts.chat_room.load_init()?;
|
let mut chat = ctx.accounts.chat_room.load_mut()?;
|
||||||
chat.name = name;
|
chat.name = name;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,7 +122,7 @@ pub mod misc {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test_pda_init_zero_copy(ctx: Context<TestPdaInitZeroCopy>) -> ProgramResult {
|
pub fn test_pda_init_zero_copy(ctx: Context<TestPdaInitZeroCopy>) -> ProgramResult {
|
||||||
let mut acc = ctx.accounts.my_pda.load_init()?;
|
let mut acc = ctx.accounts.my_pda.load_mut()?;
|
||||||
acc.data = 9;
|
acc.data = 9;
|
||||||
acc.bump = *ctx.bumps.get("my_pda").unwrap();
|
acc.bump = *ctx.bumps.get("my_pda").unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -152,7 +152,7 @@ pub mod misc {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn test_init_zero_copy(ctx: Context<TestInitZeroCopy>) -> ProgramResult {
|
pub fn test_init_zero_copy(ctx: Context<TestInitZeroCopy>) -> ProgramResult {
|
||||||
let mut data = ctx.accounts.data.load_init()?;
|
let mut data = ctx.accounts.data.load_mut()?;
|
||||||
data.data = 10;
|
data.data = 10;
|
||||||
data.bump = 2;
|
data.bump = 2;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -14,7 +14,7 @@ pub mod zero_copy {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub fn create_foo(ctx: Context<CreateFoo>) -> ProgramResult {
|
pub fn create_foo(ctx: Context<CreateFoo>) -> ProgramResult {
|
||||||
let foo = &mut ctx.accounts.foo.load_init()?;
|
let foo = &mut ctx.accounts.foo.load_mut()?;
|
||||||
foo.authority = *ctx.accounts.authority.key;
|
foo.authority = *ctx.accounts.authority.key;
|
||||||
foo.set_second_authority(ctx.accounts.authority.key);
|
foo.set_second_authority(ctx.accounts.authority.key);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -33,7 +33,7 @@ pub mod zero_copy {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_bar(ctx: Context<CreateBar>) -> ProgramResult {
|
pub fn create_bar(ctx: Context<CreateBar>) -> ProgramResult {
|
||||||
let bar = &mut ctx.accounts.bar.load_init()?;
|
let bar = &mut ctx.accounts.bar.load_mut()?;
|
||||||
bar.authority = *ctx.accounts.authority.key;
|
bar.authority = *ctx.accounts.authority.key;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,11 @@ async fn update_foo() {
|
||||||
let authority = Keypair::new();
|
let authority = Keypair::new();
|
||||||
let foo_pubkey = Pubkey::new_unique();
|
let foo_pubkey = Pubkey::new_unique();
|
||||||
let foo_account = {
|
let foo_account = {
|
||||||
let mut foo_data = Vec::new();
|
// Write header.
|
||||||
|
let mut foo_data = vec![0, 0];
|
||||||
foo_data.extend_from_slice(&zero_copy::Foo::discriminator());
|
foo_data.extend_from_slice(&zero_copy::Foo::discriminator());
|
||||||
|
foo_data.extend_from_slice(&[0, 0]);
|
||||||
|
// Write data.
|
||||||
foo_data.extend_from_slice(bytemuck::bytes_of(&zero_copy::Foo {
|
foo_data.extend_from_slice(bytemuck::bytes_of(&zero_copy::Foo {
|
||||||
authority: authority.pubkey(),
|
authority: authority.pubkey(),
|
||||||
..zero_copy::Foo::default()
|
..zero_copy::Foo::default()
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { GetProgramAccountsFilter } from "@solana/web3.js";
|
||||||
import bs58 from "bs58";
|
import bs58 from "bs58";
|
||||||
import { Buffer } from "buffer";
|
import { Buffer } from "buffer";
|
||||||
import { Layout } from "buffer-layout";
|
import { Layout } from "buffer-layout";
|
||||||
|
@ -7,11 +8,18 @@ import { Idl, IdlTypeDef } from "../../idl.js";
|
||||||
import { IdlCoder } from "./idl.js";
|
import { IdlCoder } from "./idl.js";
|
||||||
import { AccountsCoder } from "../index.js";
|
import { AccountsCoder } from "../index.js";
|
||||||
import { accountSize } from "../common.js";
|
import { accountSize } from "../common.js";
|
||||||
|
import * as features from "../../utils/features";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of bytes of the account header.
|
||||||
|
*/
|
||||||
|
const ACCOUNT_HEADER_SIZE = 8;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of bytes of the account discriminator.
|
* Number of bytes of the account discriminator.
|
||||||
*/
|
*/
|
||||||
export const ACCOUNT_DISCRIMINATOR_SIZE = 8;
|
const ACCOUNT_DISCRIMINATOR_SIZE = 4;
|
||||||
|
const DEPRECATED_ACCOUNT_DISCRIMINATOR_SIZE = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes and decodes account objects.
|
* Encodes and decodes account objects.
|
||||||
|
@ -49,22 +57,21 @@ export class BorshAccountsCoder<A extends string = string>
|
||||||
}
|
}
|
||||||
const len = layout.encode(account, buffer);
|
const len = layout.encode(account, buffer);
|
||||||
let accountData = buffer.slice(0, len);
|
let accountData = buffer.slice(0, len);
|
||||||
let discriminator = BorshAccountsCoder.accountDiscriminator(accountName);
|
let header = BorshAccountHeader.encode(accountName);
|
||||||
return Buffer.concat([discriminator, accountData]);
|
return Buffer.concat([header, accountData]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public decode<T = any>(accountName: A, data: Buffer): T {
|
public decode<T = any>(accountName: A, data: Buffer): T {
|
||||||
// Assert the account discriminator is correct.
|
const expectedDiscriminator = BorshAccountHeader.discriminator(accountName);
|
||||||
const discriminator = BorshAccountsCoder.accountDiscriminator(accountName);
|
const givenDisc = BorshAccountHeader.parseDiscriminator(data);
|
||||||
if (discriminator.compare(data.slice(0, 8))) {
|
if (expectedDiscriminator.compare(givenDisc)) {
|
||||||
throw new Error("Invalid account discriminator");
|
throw new Error("Invalid account discriminator");
|
||||||
}
|
}
|
||||||
return this.decodeUnchecked(accountName, data);
|
return this.decodeUnchecked(accountName, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public decodeUnchecked<T = any>(accountName: A, ix: Buffer): T {
|
public decodeUnchecked<T = any>(accountName: A, ix: Buffer): T {
|
||||||
// Chop off the discriminator before decoding.
|
const data = ix.slice(BorshAccountHeader.size()); // Chop off the header.
|
||||||
const data = ix.slice(ACCOUNT_DISCRIMINATOR_SIZE);
|
|
||||||
const layout = this.accountLayouts.get(accountName);
|
const layout = this.accountLayouts.get(accountName);
|
||||||
if (!layout) {
|
if (!layout) {
|
||||||
throw new Error(`Unknown account: ${accountName}`);
|
throw new Error(`Unknown account: ${accountName}`);
|
||||||
|
@ -72,20 +79,40 @@ export class BorshAccountsCoder<A extends string = string>
|
||||||
return layout.decode(data);
|
return layout.decode(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public memcmp(accountName: A, appendData?: Buffer): any {
|
public memcmp(accountName: A): GetProgramAccountsFilter {
|
||||||
const discriminator = BorshAccountsCoder.accountDiscriminator(accountName);
|
const discriminator = BorshAccountHeader.discriminator(accountName);
|
||||||
return {
|
return {
|
||||||
offset: 0,
|
memcmp: {
|
||||||
bytes: bs58.encode(
|
offset: BorshAccountHeader.discriminatorOffset(),
|
||||||
appendData ? Buffer.concat([discriminator, appendData]) : discriminator
|
bytes: bs58.encode(discriminator),
|
||||||
),
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public memcmpDataOffset(): number {
|
||||||
|
return BorshAccountHeader.size();
|
||||||
|
}
|
||||||
|
|
||||||
public size(idlAccount: IdlTypeDef): number {
|
public size(idlAccount: IdlTypeDef): number {
|
||||||
return (
|
return BorshAccountHeader.size() + (accountSize(this.idl, idlAccount) ?? 0);
|
||||||
ACCOUNT_DISCRIMINATOR_SIZE + (accountSize(this.idl, idlAccount) ?? 0)
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
|
export class BorshAccountHeader {
|
||||||
|
/**
|
||||||
|
* Returns the default account header for an account with the given name.
|
||||||
|
*/
|
||||||
|
public static encode(accountName: string, nameSpace?: string): Buffer {
|
||||||
|
if (features.isSet("deprecated-layout")) {
|
||||||
|
return BorshAccountHeader.discriminator(accountName, nameSpace);
|
||||||
|
} else {
|
||||||
|
return Buffer.concat([
|
||||||
|
Buffer.from([0]), // Version.
|
||||||
|
Buffer.from([0]), // Bump.
|
||||||
|
BorshAccountHeader.discriminator(accountName), // Disc.
|
||||||
|
Buffer.from([0, 0]), // Unused.
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -93,9 +120,46 @@ export class BorshAccountsCoder<A extends string = string>
|
||||||
*
|
*
|
||||||
* @param name The name of the account to calculate the discriminator.
|
* @param name The name of the account to calculate the discriminator.
|
||||||
*/
|
*/
|
||||||
public static accountDiscriminator(name: string): Buffer {
|
public static discriminator(name: string, nameSpace?: string): Buffer {
|
||||||
return Buffer.from(
|
return Buffer.from(
|
||||||
sha256.digest(`account:${camelcase(name, { pascalCase: true })}`)
|
sha256.digest(
|
||||||
).slice(0, ACCOUNT_DISCRIMINATOR_SIZE);
|
`${nameSpace ?? "account"}:${camelcase(name, { pascalCase: true })}`
|
||||||
|
)
|
||||||
|
).slice(0, BorshAccountHeader.discriminatorSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static discriminatorSize(): number {
|
||||||
|
return features.isSet("deprecated-layout")
|
||||||
|
? DEPRECATED_ACCOUNT_DISCRIMINATOR_SIZE
|
||||||
|
: ACCOUNT_DISCRIMINATOR_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the account data index at which the discriminator starts.
|
||||||
|
*/
|
||||||
|
public static discriminatorOffset(): number {
|
||||||
|
if (features.isSet("deprecated-layout")) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the byte size of the account header.
|
||||||
|
*/
|
||||||
|
public static size(): number {
|
||||||
|
return ACCOUNT_HEADER_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the discriminator from the given account data.
|
||||||
|
*/
|
||||||
|
public static parseDiscriminator(data: Buffer): Buffer {
|
||||||
|
if (features.isSet("deprecated-layout")) {
|
||||||
|
return data.slice(0, 8);
|
||||||
|
} else {
|
||||||
|
return data.slice(2, 6);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { Idl, IdlEvent, IdlTypeDef } from "../../idl.js";
|
||||||
import { Event, EventData } from "../../program/event.js";
|
import { Event, EventData } from "../../program/event.js";
|
||||||
import { IdlCoder } from "./idl.js";
|
import { IdlCoder } from "./idl.js";
|
||||||
import { EventCoder } from "../index.js";
|
import { EventCoder } from "../index.js";
|
||||||
|
import * as features from "../../utils/features";
|
||||||
|
|
||||||
export class BorshEventCoder implements EventCoder {
|
export class BorshEventCoder implements EventCoder {
|
||||||
/**
|
/**
|
||||||
|
@ -41,7 +42,7 @@ export class BorshEventCoder implements EventCoder {
|
||||||
idl.events === undefined
|
idl.events === undefined
|
||||||
? []
|
? []
|
||||||
: idl.events.map((e) => [
|
: idl.events.map((e) => [
|
||||||
base64.fromByteArray(eventDiscriminator(e.name)),
|
base64.fromByteArray(EventHeader.discriminator(e.name)),
|
||||||
e.name,
|
e.name,
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
@ -57,7 +58,7 @@ export class BorshEventCoder implements EventCoder {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const disc = base64.fromByteArray(logArr.slice(0, 8));
|
const disc = base64.fromByteArray(EventHeader.parseDiscriminator(logArr));
|
||||||
|
|
||||||
// Only deserialize if the discriminator implies a proper event.
|
// Only deserialize if the discriminator implies a proper event.
|
||||||
const eventName = this.discriminators.get(disc);
|
const eventName = this.discriminators.get(disc);
|
||||||
|
@ -69,7 +70,7 @@ export class BorshEventCoder implements EventCoder {
|
||||||
if (!layout) {
|
if (!layout) {
|
||||||
throw new Error(`Unknown event: ${eventName}`);
|
throw new Error(`Unknown event: ${eventName}`);
|
||||||
}
|
}
|
||||||
const data = layout.decode(logArr.slice(8)) as EventData<
|
const data = layout.decode(logArr.slice(EventHeader.size())) as EventData<
|
||||||
E["fields"][number],
|
E["fields"][number],
|
||||||
T
|
T
|
||||||
>;
|
>;
|
||||||
|
@ -78,5 +79,31 @@ export class BorshEventCoder implements EventCoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function eventDiscriminator(name: string): Buffer {
|
export function eventDiscriminator(name: string): Buffer {
|
||||||
return Buffer.from(sha256.digest(`event:${name}`)).slice(0, 8);
|
return EventHeader.discriminator(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
class EventHeader {
|
||||||
|
public static parseDiscriminator(data: Buffer): Buffer {
|
||||||
|
if (features.isSet("deprecated-layout")) {
|
||||||
|
return data.slice(0, 8);
|
||||||
|
} else {
|
||||||
|
return data.slice(0, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static size(): number {
|
||||||
|
if (features.isSet("deprecated-layout")) {
|
||||||
|
return 8;
|
||||||
|
} else {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static discriminator(name: string): Buffer {
|
||||||
|
if (features.isSet("deprecated-layout")) {
|
||||||
|
return Buffer.from(sha256.digest(`event:${name}`)).slice(0, 8);
|
||||||
|
} else {
|
||||||
|
return Buffer.from(sha256.digest(`event:${name}`)).slice(0, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { BorshStateCoder } from "./state.js";
|
||||||
import { Coder } from "../index.js";
|
import { Coder } from "../index.js";
|
||||||
|
|
||||||
export { BorshInstructionCoder } from "./instruction.js";
|
export { BorshInstructionCoder } from "./instruction.js";
|
||||||
export { BorshAccountsCoder, ACCOUNT_DISCRIMINATOR_SIZE } from "./accounts.js";
|
export { BorshAccountsCoder, BorshAccountHeader } from "./accounts.js";
|
||||||
export { BorshEventCoder, eventDiscriminator } from "./event.js";
|
export { BorshEventCoder, eventDiscriminator } from "./event.js";
|
||||||
export { BorshStateCoder, stateDiscriminator } from "./state.js";
|
export { BorshStateCoder, stateDiscriminator } from "./state.js";
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { sha256 } from "js-sha256";
|
||||||
import { Idl } from "../../idl.js";
|
import { Idl } from "../../idl.js";
|
||||||
import { IdlCoder } from "./idl.js";
|
import { IdlCoder } from "./idl.js";
|
||||||
import * as features from "../../utils/features.js";
|
import * as features from "../../utils/features.js";
|
||||||
|
import { BorshAccountHeader } from "./accounts";
|
||||||
|
|
||||||
export class BorshStateCoder {
|
export class BorshStateCoder {
|
||||||
private layout: Layout;
|
private layout: Layout;
|
||||||
|
@ -19,15 +20,16 @@ export class BorshStateCoder {
|
||||||
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
||||||
const len = this.layout.encode(account, buffer);
|
const len = this.layout.encode(account, buffer);
|
||||||
|
|
||||||
const disc = await stateDiscriminator(name);
|
let ns = features.isSet("anchor-deprecated-state") ? "account" : "state";
|
||||||
|
const header = BorshAccountHeader.encode(name, ns);
|
||||||
const accData = buffer.slice(0, len);
|
const accData = buffer.slice(0, len);
|
||||||
|
|
||||||
return Buffer.concat([disc, accData]);
|
return Buffer.concat([header, accData]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public decode<T = any>(ix: Buffer): T {
|
public decode<T = any>(data: Buffer): T {
|
||||||
// Chop off discriminator.
|
// Chop off header.
|
||||||
const data = ix.slice(8);
|
data = data.slice(BorshAccountHeader.size());
|
||||||
return this.layout.decode(data);
|
return this.layout.decode(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,5 +37,8 @@ export class BorshStateCoder {
|
||||||
// Calculates unique 8 byte discriminator prepended to all anchor state accounts.
|
// Calculates unique 8 byte discriminator prepended to all anchor state accounts.
|
||||||
export async function stateDiscriminator(name: string): Promise<Buffer> {
|
export async function stateDiscriminator(name: string): Promise<Buffer> {
|
||||||
let ns = features.isSet("anchor-deprecated-state") ? "account" : "state";
|
let ns = features.isSet("anchor-deprecated-state") ? "account" : "state";
|
||||||
return Buffer.from(sha256.digest(`${ns}:${name}`)).slice(0, 8);
|
return Buffer.from(sha256.digest(`${ns}:${name}`)).slice(
|
||||||
|
0,
|
||||||
|
BorshAccountHeader.discriminatorSize()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { GetProgramAccountsFilter } from "@solana/web3.js";
|
||||||
import { IdlEvent, IdlTypeDef } from "../idl.js";
|
import { IdlEvent, IdlTypeDef } from "../idl.js";
|
||||||
import { Event } from "../program/event.js";
|
import { Event } from "../program/event.js";
|
||||||
|
|
||||||
|
@ -38,7 +39,8 @@ export interface AccountsCoder<A extends string = string> {
|
||||||
encode<T = any>(accountName: A, account: T): Promise<Buffer>;
|
encode<T = any>(accountName: A, account: T): Promise<Buffer>;
|
||||||
decode<T = any>(accountName: A, ix: Buffer): T;
|
decode<T = any>(accountName: A, ix: Buffer): T;
|
||||||
decodeUnchecked<T = any>(accountName: A, ix: Buffer): T;
|
decodeUnchecked<T = any>(accountName: A, ix: Buffer): T;
|
||||||
memcmp(accountName: A, appendData?: Buffer): any;
|
memcmp(accountName: A): GetProgramAccountsFilter;
|
||||||
|
memcmpDataOffset(): number;
|
||||||
size(idlAccount: IdlTypeDef): number;
|
size(idlAccount: IdlTypeDef): number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { GetProgramAccountsFilter } from "@solana/web3.js";
|
||||||
import * as BufferLayout from "buffer-layout";
|
import * as BufferLayout from "buffer-layout";
|
||||||
import { publicKey, uint64, coption, bool } from "./buffer-layout.js";
|
import { publicKey, uint64, coption, bool } from "./buffer-layout.js";
|
||||||
import { AccountsCoder } from "../index.js";
|
import { AccountsCoder } from "../index.js";
|
||||||
|
@ -44,8 +45,7 @@ export class SplTokenAccountsCoder<A extends string = string>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this won't use the appendData.
|
public memcmp(accountName: A): GetProgramAccountsFilter {
|
||||||
public memcmp(accountName: A, _appendData?: Buffer): any {
|
|
||||||
switch (accountName) {
|
switch (accountName) {
|
||||||
case "Token": {
|
case "Token": {
|
||||||
return {
|
return {
|
||||||
|
@ -63,6 +63,10 @@ export class SplTokenAccountsCoder<A extends string = string>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public memcmpDataOffset(): number {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
public size(idlAccount: IdlTypeDef): number {
|
public size(idlAccount: IdlTypeDef): number {
|
||||||
return accountSize(this.idl, idlAccount) ?? 0;
|
return accountSize(this.idl, idlAccount) ?? 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { PublicKey } from "@solana/web3.js";
|
||||||
import * as borsh from "@project-serum/borsh";
|
import * as borsh from "@project-serum/borsh";
|
||||||
|
|
||||||
export type Idl = {
|
export type Idl = {
|
||||||
|
layoutVersion: string;
|
||||||
version: string;
|
version: string;
|
||||||
name: string;
|
name: string;
|
||||||
instructions: IdlInstruction[];
|
instructions: IdlInstruction[];
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import * as bs58 from 'bs58';
|
||||||
import camelCase from "camelcase";
|
import camelCase from "camelcase";
|
||||||
import EventEmitter from "eventemitter3";
|
import EventEmitter from "eventemitter3";
|
||||||
import {
|
import {
|
||||||
|
@ -201,19 +202,25 @@ export class AccountClient<
|
||||||
async all(
|
async all(
|
||||||
filters?: Buffer | GetProgramAccountsFilter[]
|
filters?: Buffer | GetProgramAccountsFilter[]
|
||||||
): Promise<ProgramAccount<T>[]> {
|
): Promise<ProgramAccount<T>[]> {
|
||||||
|
const typeFilter = [this.coder.accounts.memcmp(this._idlAccount.name)];
|
||||||
|
const dataFilter =
|
||||||
|
filters instanceof Buffer
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
memcmp: {
|
||||||
|
offset: this.coder.accounts.memcmpDataOffset(),
|
||||||
|
bytes: bs58.encode(filters),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
const miscFilters = Array.isArray(filters) ? filters : [];
|
||||||
|
|
||||||
let resp = await this._provider.connection.getProgramAccounts(
|
let resp = await this._provider.connection.getProgramAccounts(
|
||||||
this._programId,
|
this._programId,
|
||||||
{
|
{
|
||||||
commitment: this._provider.connection.commitment,
|
commitment: this._provider.connection.commitment,
|
||||||
filters: [
|
filters: typeFilter.concat(dataFilter).concat(miscFilters),
|
||||||
{
|
|
||||||
memcmp: this.coder.accounts.memcmp(
|
|
||||||
this._idlAccount.name,
|
|
||||||
filters instanceof Buffer ? filters : undefined
|
|
||||||
),
|
|
||||||
},
|
|
||||||
...(Array.isArray(filters) ? filters : []),
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return resp.map(({ pubkey, account }) => {
|
return resp.map(({ pubkey, account }) => {
|
||||||
|
|
|
@ -24,6 +24,7 @@ import InstructionNamespaceFactory from "./instruction.js";
|
||||||
import RpcNamespaceFactory from "./rpc.js";
|
import RpcNamespaceFactory from "./rpc.js";
|
||||||
import TransactionNamespaceFactory from "./transaction.js";
|
import TransactionNamespaceFactory from "./transaction.js";
|
||||||
import { IdlTypes, TypeDef } from "./types.js";
|
import { IdlTypes, TypeDef } from "./types.js";
|
||||||
|
import * as features from "../../utils/features.js";
|
||||||
|
|
||||||
export default class StateFactory {
|
export default class StateFactory {
|
||||||
public static build<IDL extends Idl>(
|
public static build<IDL extends Idl>(
|
||||||
|
@ -172,10 +173,19 @@ export class StateClient<IDL extends Idl> {
|
||||||
if (!state) {
|
if (!state) {
|
||||||
throw new Error("State is not specified in IDL.");
|
throw new Error("State is not specified in IDL.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const expectedDiscriminator = await stateDiscriminator(state.struct.name);
|
const expectedDiscriminator = await stateDiscriminator(state.struct.name);
|
||||||
|
|
||||||
|
if (features.isSet("deprecated-layout")) {
|
||||||
if (expectedDiscriminator.compare(accountInfo.data.slice(0, 8))) {
|
if (expectedDiscriminator.compare(accountInfo.data.slice(0, 8))) {
|
||||||
throw new Error("Invalid account discriminator");
|
throw new Error("Invalid state discriminator");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (expectedDiscriminator.compare(accountInfo.data.slice(2, 6))) {
|
||||||
|
throw new Error("Invalid state discriminator");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return this.coder.state.decode(accountInfo.data);
|
return this.coder.state.decode(accountInfo.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ export function coder(): SplTokenCoder {
|
||||||
* SplToken IDL.
|
* SplToken IDL.
|
||||||
*/
|
*/
|
||||||
export type SplToken = {
|
export type SplToken = {
|
||||||
|
layoutVersion: "custom";
|
||||||
version: "0.1.0";
|
version: "0.1.0";
|
||||||
name: "spl_token";
|
name: "spl_token";
|
||||||
instructions: [
|
instructions: [
|
||||||
|
@ -624,6 +625,7 @@ export type SplToken = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IDL: SplToken = {
|
export const IDL: SplToken = {
|
||||||
|
layoutVersion: "custom",
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
name: "spl_token",
|
name: "spl_token",
|
||||||
instructions: [
|
instructions: [
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
const _AVAILABLE_FEATURES = new Set(["anchor-deprecated-state", "debug-logs"]);
|
const _AVAILABLE_FEATURES = new Set([
|
||||||
|
"anchor-deprecated-state",
|
||||||
|
"debug-logs",
|
||||||
|
"deprecated-layout",
|
||||||
|
]);
|
||||||
|
|
||||||
const _FEATURES = new Map();
|
const _FEATURES = new Map();
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ describe("Events", () => {
|
||||||
"Program J2XMGdW2qQLx7rAdwWtSZpTXDgAQ988BLP9QTgUZvm54 success",
|
"Program J2XMGdW2qQLx7rAdwWtSZpTXDgAQ988BLP9QTgUZvm54 success",
|
||||||
];
|
];
|
||||||
const idl = {
|
const idl = {
|
||||||
|
layoutVersion: "0.1.0",
|
||||||
version: "0.0.0",
|
version: "0.0.0",
|
||||||
name: "basic_0",
|
name: "basic_0",
|
||||||
instructions: [
|
instructions: [
|
||||||
|
|
|
@ -15,6 +15,7 @@ describe("Transaction", () => {
|
||||||
data: Buffer.from("post"),
|
data: Buffer.from("post"),
|
||||||
});
|
});
|
||||||
const idl = {
|
const idl = {
|
||||||
|
layoutVersion: "0.1.0",
|
||||||
version: "0.0.0",
|
version: "0.0.0",
|
||||||
name: "basic_0",
|
name: "basic_0",
|
||||||
instructions: [
|
instructions: [
|
||||||
|
|
Loading…
Reference in New Issue