Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for LoaderAccount #792

Merged
merged 23 commits into from
Oct 8, 2021
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e28e41b
update pyth to v2
NorbertBodziony Jun 14, 2021
cdd94b8
Merge branch 'master' of github.com:project-serum/anchor
NorbertBodziony Jun 17, 2021
7ec77ca
Merge branch 'master' of github.com:project-serum/anchor
NorbertBodziony Jul 12, 2021
94e067b
Merge branch 'master' of github.com:project-serum/anchor
NorbertBodziony Jul 23, 2021
3978af1
Merge branch 'master' of github.com:project-serum/anchor
NorbertBodziony Aug 5, 2021
f07d472
Merge branch 'master' of github.com:project-serum/anchor
NorbertBodziony Aug 5, 2021
8bd0283
Merge branch 'master' of github.com:project-serum/anchor
NorbertBodziony Aug 23, 2021
2596155
Merge branch 'master' of github.com:project-serum/anchor
NorbertBodziony Aug 26, 2021
5c737a3
Merge branches 'master' and 'master' of github.com:project-serum/anchor
NorbertBodziony Sep 9, 2021
0f6f3fe
Merge branch 'master' of github.com:project-serum/anchor
NorbertBodziony Sep 18, 2021
83460f9
Merge branch 'master' of github.com:project-serum/anchor
NorbertBodziony Sep 24, 2021
b55e1a4
add LoaderAccount
NorbertBodziony Sep 24, 2021
16ea2cf
rename to AccountLoader
NorbertBodziony Oct 6, 2021
f3818dd
fix bug
NorbertBodziony Oct 6, 2021
198315c
Merge tag 'v0.16.2'
NorbertBodziony Oct 7, 2021
6fe022d
add clone trait
NorbertBodziony Oct 8, 2021
4ff3a1d
add zero_copy cpi to tests
NorbertBodziony Oct 8, 2021
ce8373b
remove leftover
NorbertBodziony Oct 8, 2021
896f077
more leftovers
NorbertBodziony Oct 8, 2021
9bdebb6
Merge branch 'master' of github.com:project-serum/anchor
NorbertBodziony Oct 8, 2021
0bf43f1
add changelog
NorbertBodziony Oct 8, 2021
0e98628
fix cpi
NorbertBodziony Oct 8, 2021
b214d33
rever formating
NorbertBodziony Oct 8, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions lang/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ mod error;
#[doc(hidden)]
pub mod idl;
mod loader;
mod loader_account;
mod program;
mod program_account;
mod signer;
Expand All @@ -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::LoaderAccount;
pub use crate::program::Program;
#[doc(hidden)]
#[allow(deprecated)]
Expand Down Expand Up @@ -247,9 +249,9 @@ 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,
AnchorDeserialize, AnchorSerialize, Context, CpiContext, Id, Key, Loader, LoaderAccount,
Owner, Program, ProgramAccount, Signer, System, Sysvar, ToAccountInfo, ToAccountInfos,
ToAccountMetas, UncheckedAccount,
};

#[allow(deprecated)]
Expand Down
196 changes: 196 additions & 0 deletions lang/src/loader_account.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
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 LoaderAccount 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.
pub struct LoaderAccount<'info, T: ZeroCopy + Owner> {
NorbertBodziony marked this conversation as resolved.
Show resolved Hide resolved
acc_info: AccountInfo<'info>,
phantom: PhantomData<&'info T>,
}

impl<'info, T: ZeroCopy + Owner> LoaderAccount<'info, T> {
fn new(acc_info: AccountInfo<'info>) -> LoaderAccount<'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<LoaderAccount<'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(LoaderAccount::new(acc_info.clone()))
}

/// Constructs a new `Loader` from an uninitialized account.
#[inline(never)]
pub fn try_from_unchecked(
acc_info: &AccountInfo<'info>,
) -> Result<LoaderAccount<'info, T>, ProgramError> {
if acc_info.owner != &T::owner() {
return Err(ErrorCode::AccountNotProgramOwned.into());
}
Ok(LoaderAccount::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 LoaderAccount<'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 = LoaderAccount::try_from(account)?;
Ok(l)
}
}

impl<'info, T: ZeroCopy + Owner> AccountsExit<'info> for LoaderAccount<'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 LoaderAccount<'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 LoaderAccount<'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 LoaderAccount<'info, T> {
fn as_ref(&self) -> &AccountInfo<'info> {
&self.acc_info
}
}

impl<'info, T: ZeroCopy + Owner> ToAccountInfos<'info> for LoaderAccount<'info, T> {
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
vec![self.acc_info.clone()]
}
}

impl<'info, T: ZeroCopy + Owner> ToAccountInfo<'info> for LoaderAccount<'info, T> {
fn to_account_info(&self) -> AccountInfo<'info> {
self.acc_info.clone()
}
}

impl<'info, T: ZeroCopy + Owner> Key for LoaderAccount<'info, T> {
fn key(&self) -> Pubkey {
*self.acc_info.key
}
}
4 changes: 3 additions & 1 deletion lang/syn/src/codegen/accounts/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,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::LoaderAccount(_) => quote! {#ident.load()?},
_ => quote! {#ident},
};
quote! {
Expand All @@ -198,6 +199,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::LoaderAccount(_) => quote! { #ident.to_account_info() },
Ty::CpiAccount(_) => quote! { #ident.to_account_info() },
_ => panic!("Invalid syntax: signer cannot be specified."),
};
Expand Down Expand Up @@ -467,7 +469,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::LoaderAccount(_)) {
false => {
quote! {
let space = 8 + #account_ty::default().try_to_vec().unwrap().len();
Expand Down
16 changes: 16 additions & 0 deletions lang/syn/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,9 @@ impl Field {
Ty::Account(_) => quote! {
anchor_lang::Account
},
Ty::LoaderAccount(_) => quote! {
anchor_lang::Account
},
Ty::Loader(_) => quote! {
anchor_lang::Loader
},
Expand Down Expand Up @@ -318,6 +321,12 @@ impl Field {
#ident
}
}
Ty::LoaderAccount(ty) => {
let ident = &ty.account_type_path;
quote! {
#ident
}
}
Ty::Loader(ty) => {
let ident = &ty.account_type_path;
quote! {
Expand Down Expand Up @@ -382,6 +391,7 @@ pub enum Ty {
CpiState(CpiStateTy),
ProgramAccount(ProgramAccountTy),
Loader(LoaderTy),
LoaderAccount(LoaderAccountTy),
CpiAccount(CpiAccountTy),
Sysvar(SysvarTy),
Account(AccountTy),
Expand Down Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions lang/syn/src/parser/accounts/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,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::LoaderAccount(_)))
{
return Err(ParseError::new(
c.span(),
Expand Down
8 changes: 8 additions & 0 deletions lang/syn/src/parser/accounts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ fn is_field_primitive(f: &syn::Field) -> ParseResult<bool> {
| "UncheckedAccount"
| "CpiState"
| "Loader"
| "LoaderAccount"
| "Account"
| "Program"
| "Signer"
Expand All @@ -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)?),
"LoaderAccount" => Ty::LoaderAccount(parse_program_loader_account(&path)?),
"Account" => Ty::Account(parse_account_ty(&path)?),
"Program" => Ty::Program(parse_program_ty(&path)?),
"Signer" => Ty::Signer,
Expand Down Expand Up @@ -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)?;
Expand Down
7 changes: 7 additions & 0 deletions lang/tests/generics_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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: LoaderAccount<'info, FooAccount<N>>,
pub associated: Account<'info, Associated<U>>,
}

Expand Down Expand Up @@ -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
}
}