lang: store calculated bump seeds in context (#1367)

This commit is contained in:
Armani Ferrante 2022-01-27 16:55:13 -05:00 committed by GitHub
parent 7615c7821c
commit 16a7dc5dc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 321 additions and 187 deletions

View File

@ -14,12 +14,14 @@ incremented for features.
### Features
* lang: Add `seeds::program` constraint for specifying which program_id to use when deriving PDAs.([#1197](https://github.com/project-serum/anchor/pull/1197))
* ts: Remove error logging in the event parser when log websocket encounters a program error. ([#1313](https://github.com/project-serum/anchor/pull/1313))
* lang: `Context` now has a new `bumps: BTree<String, u8>` argument, mapping account name to bump seed "found" by the accounts context. This allows one to access bump seeds without having to pass them in from the client or recalculate them in the handler ([#1367](calculated )).
* ts: Remove error logging in the event parser when log websocket encounters a program error ([#1313](https://github.com/project-serum/anchor/pull/1313)).
* ts: Add new `methods` namespace to the program client, introducing a more ergonomic builder API ([#1324](https://github.com/project-serum/anchor/pull/1324)).
### Breaking
* lang: rename `loader_account` module to `account_loader` module ([#1279](https://github.com/project-serum/anchor/pull/1279))
* lang: The `Accounts` trait's `try_accounts` method now has an additional `bumps: &mut BTreeMap<String, u8>` argument, which accumulates bump seeds ([#1367](https://github.com/project-serum/anchor/pull/1367)).
* ts: `Coder` is now an interface and the existing class has been renamed to `BorshCoder`. This change allows the generation of Anchor clients for non anchor programs ([#1259](https://github.com/project-serum/anchor/pull/1259/files)).
## [0.20.1] - 2022-01-09

View File

@ -7,6 +7,7 @@ use solana_program::entrypoint::ProgramResult;
use solana_program::instruction::AccountMeta;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::fmt;
use std::ops::{Deref, DerefMut};
@ -314,6 +315,7 @@ where
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());

View File

@ -8,12 +8,14 @@ use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
impl<'info> Accounts<'info> for AccountInfo<'info> {
fn try_accounts(
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());

View File

@ -12,6 +12,7 @@ use solana_program::instruction::AccountMeta;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use std::cell::{Ref, RefMut};
use std::collections::BTreeMap;
use std::fmt;
use std::io::Write;
use std::marker::PhantomData;
@ -211,6 +212,7 @@ impl<'info, T: ZeroCopy + Owner> Accounts<'info> for AccountLoader<'info, T> {
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());

View File

@ -19,6 +19,7 @@ use solana_program::entrypoint::ProgramResult;
use solana_program::instruction::AccountMeta;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::ops::Deref;
impl<'info, T: Accounts<'info>> Accounts<'info> for Box<T> {
@ -26,8 +27,9 @@ impl<'info, T: Accounts<'info>> Accounts<'info> for Box<T> {
program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
ix_data: &[u8],
bumps: &mut BTreeMap<String, u8>,
) -> Result<Self, ProgramError> {
T::try_accounts(program_id, accounts, ix_data).map(Box::new)
T::try_accounts(program_id, accounts, ix_data, bumps).map(Box::new)
}
}

View File

@ -5,6 +5,7 @@ use solana_program::entrypoint::ProgramResult;
use solana_program::instruction::AccountMeta;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::ops::{Deref, DerefMut};
/// Container for any account *not* owned by the current program.
@ -53,6 +54,7 @@ where
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());

View File

@ -8,6 +8,7 @@ use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::ops::{Deref, DerefMut};
/// Boxed container for the program state singleton, used when the state
@ -70,6 +71,7 @@ where
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());

View File

@ -9,6 +9,7 @@ use solana_program::instruction::AccountMeta;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use std::cell::{Ref, RefMut};
use std::collections::BTreeMap;
use std::fmt;
use std::io::Write;
use std::marker::PhantomData;
@ -150,6 +151,7 @@ impl<'info, T: ZeroCopy> Accounts<'info> for Loader<'info, T> {
program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());

View File

@ -7,6 +7,7 @@ use solana_program::bpf_loader_upgradeable::{self, UpgradeableLoaderState};
use solana_program::instruction::AccountMeta;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::fmt;
use std::marker::PhantomData;
use std::ops::Deref;
@ -135,6 +136,7 @@ where
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());

View File

@ -10,6 +10,7 @@ use solana_program::entrypoint::ProgramResult;
use solana_program::instruction::AccountMeta;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::ops::{Deref, DerefMut};
/// Boxed container for a deserialized `account`. Use this to reference any
@ -83,6 +84,7 @@ where
program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());

View File

@ -5,6 +5,7 @@ use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::ops::Deref;
/// Type validating that the account signed the transaction. No other ownership
@ -60,6 +61,7 @@ impl<'info> Accounts<'info> for Signer<'info> {
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());

View File

@ -10,6 +10,7 @@ use solana_program::entrypoint::ProgramResult;
use solana_program::instruction::AccountMeta;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::ops::{Deref, DerefMut};
pub const PROGRAM_STATE_SEED: &str = "unversioned";
@ -75,6 +76,7 @@ where
program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());

View File

@ -7,6 +7,7 @@ use solana_program::instruction::AccountMeta;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use solana_program::system_program;
use std::collections::BTreeMap;
use std::ops::Deref;
/// Type validating that the account is owned by the system program
@ -39,6 +40,7 @@ impl<'info> Accounts<'info> for SystemAccount<'info> {
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());

View File

@ -6,6 +6,7 @@ use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::fmt;
use std::ops::{Deref, DerefMut};
@ -69,6 +70,7 @@ impl<'info, T: solana_program::sysvar::Sysvar> Accounts<'info> for Sysvar<'info,
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());

View File

@ -7,6 +7,7 @@ use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::ops::Deref;
/// Explicit wrapper for AccountInfo types to emphasize
@ -25,6 +26,7 @@ impl<'info> Accounts<'info> for UncheckedAccount<'info> {
_program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
_ix_data: &[u8],
_bumps: &mut BTreeMap<String, u8>,
) -> Result<Self, ProgramError> {
if accounts.is_empty() {
return Err(ErrorCode::AccountNotEnoughKeys.into());

View File

@ -4,6 +4,7 @@ use crate::{Accounts, ToAccountInfos, ToAccountMetas};
use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::fmt;
/// Provides non-argument inputs to the program.
@ -29,6 +30,10 @@ pub struct Context<'a, 'b, 'c, 'info, T> {
/// Remaining accounts given but not deserialized or validated.
/// Be very careful when using this directly.
pub remaining_accounts: &'c [AccountInfo<'info>],
/// Bump seeds found during constraint validation. This is provided as a
/// convenience so that handlers don't have to recalculate bump seeds or
/// pass them in as arguments.
pub bumps: BTreeMap<String, u8>,
}
impl<'a, 'b, 'c, 'info, T: fmt::Debug> fmt::Debug for Context<'a, 'b, 'c, 'info, T> {
@ -37,6 +42,7 @@ impl<'a, 'b, 'c, 'info, T: fmt::Debug> fmt::Debug for Context<'a, 'b, 'c, 'info,
.field("program_id", &self.program_id)
.field("accounts", &self.accounts)
.field("remaining_accounts", &self.remaining_accounts)
.field("bumps", &self.bumps)
.finish()
}
}
@ -46,11 +52,13 @@ impl<'a, 'b, 'c, 'info, T: Accounts<'info>> Context<'a, 'b, 'c, 'info, T> {
program_id: &'a Pubkey,
accounts: &'b mut T,
remaining_accounts: &'c [AccountInfo<'info>],
bumps: BTreeMap<String, u8>,
) -> Self {
Self {
program_id,
accounts,
remaining_accounts,
bumps,
}
}
}

View File

@ -29,6 +29,7 @@ use solana_program::entrypoint::ProgramResult;
use solana_program::instruction::AccountMeta;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
use std::io::Write;
mod account_meta;
@ -80,6 +81,7 @@ pub trait Accounts<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized {
program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
ix_data: &[u8],
bumps: &mut BTreeMap<String, u8>,
) -> Result<Self, ProgramError>;
}

View File

@ -3,6 +3,7 @@ use solana_program::account_info::AccountInfo;
use solana_program::instruction::AccountMeta;
use solana_program::program_error::ProgramError;
use solana_program::pubkey::Pubkey;
use std::collections::BTreeMap;
impl<'info, T: ToAccountInfos<'info>> ToAccountInfos<'info> for Vec<T> {
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
@ -25,9 +26,10 @@ impl<'info, T: Accounts<'info>> Accounts<'info> for Vec<T> {
program_id: &Pubkey,
accounts: &mut &[AccountInfo<'info>],
ix_data: &[u8],
bumps: &mut BTreeMap<String, u8>,
) -> Result<Self, ProgramError> {
let mut vec: Vec<T> = Vec::new();
T::try_accounts(program_id, accounts, ix_data).map(|item| vec.push(item))?;
T::try_accounts(program_id, accounts, ix_data, bumps).map(|item| vec.push(item))?;
Ok(vec)
}
}
@ -76,9 +78,10 @@ mod tests {
false,
Epoch::default(),
);
let mut bumps = std::collections::BTreeMap::new();
let mut accounts = &[account1, account2][..];
let parsed_accounts = Vec::<Test>::try_accounts(&program_id, &mut accounts, &[]).unwrap();
let parsed_accounts =
Vec::<Test>::try_accounts(&program_id, &mut accounts, &[], &mut bumps).unwrap();
assert_eq!(accounts.len(), parsed_accounts.len());
}
@ -87,8 +90,8 @@ mod tests {
#[should_panic]
fn test_accounts_trait_for_vec_empty() {
let program_id = Pubkey::default();
let mut bumps = std::collections::BTreeMap::new();
let mut accounts = &[][..];
Vec::<Test>::try_accounts(&program_id, &mut accounts, &[]).unwrap();
Vec::<Test>::try_accounts(&program_id, &mut accounts, &[], &mut bumps).unwrap();
}
}

View File

@ -277,6 +277,16 @@ pub fn generate_constraint_rent_exempt(
}
fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_macro2::TokenStream {
let field = &f.ident;
let ty_decl = f.ty_decl();
let if_needed = if c.if_needed {
quote! {true}
} else {
quote! {false}
};
let space = &c.space;
// Payer for rent exemption.
let payer = {
let p = &c.payer;
quote! {
@ -284,162 +294,56 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma
}
};
let seeds_with_nonce = match &c.seeds {
None => quote! {},
// Convert from account info to account context wrapper type.
let from_account_info = f.from_account_info_unchecked(Some(&c.kind));
// PDA bump seeds.
let (find_pda, seeds_with_bump) = match &c.seeds {
None => (quote! {}, quote! {}),
Some(c) => {
let s = &mut c.seeds.clone();
let name_str = f.ident.to_string();
let seeds = &mut c.seeds.clone();
// If the seeds came with a trailing comma, we need to chop it off
// before we interpolate them below.
if let Some(pair) = s.pop() {
s.push_value(pair.into_value());
if let Some(pair) = seeds.pop() {
seeds.push_value(pair.into_value());
}
let maybe_seeds_plus_comma = (!s.is_empty()).then(|| {
quote! { #s, }
let maybe_seeds_plus_comma = (!seeds.is_empty()).then(|| {
quote! { #seeds, }
});
let inner = match c.bump.as_ref() {
// Bump target not given. Use the canonical bump.
None => {
quote! {
[
#maybe_seeds_plus_comma
&[
Pubkey::find_program_address(
&[#s],
program_id,
).1
][..]
]
}
}
// Bump target given. Use it.
Some(b) => quote! {
[#maybe_seeds_plus_comma &[#b][..]]
(
quote! {
let (__pda_address, __bump) = Pubkey::find_program_address(
&[#maybe_seeds_plus_comma],
program_id,
);
__bumps.insert(#name_str.to_string(), __bump);
},
};
quote! {
&#inner[..]
}
}
};
generate_init(f, c.if_needed, seeds_with_nonce, payer, &c.space, &c.kind)
}
fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2::TokenStream {
let name = &f.ident;
let s = &mut c.seeds.clone();
let deriving_program_id = c
.program_seed
.clone()
// If they specified a seeds::program to use when deriving the PDA, use it.
.map(|program_id| quote! { #program_id })
// Otherwise fall back to the current program's program_id.
.unwrap_or(quote! { program_id });
// If the seeds came with a trailing comma, we need to chop it off
// before we interpolate them below.
if let Some(pair) = s.pop() {
s.push_value(pair.into_value());
}
// If the bump is provided with init *and target*, then force it to be the
// canonical bump.
if c.is_init && c.bump.is_some() {
let b = c.bump.as_ref().unwrap();
quote! {
let (__program_signer, __bump) = anchor_lang::solana_program::pubkey::Pubkey::find_program_address(
&[#s],
&#deriving_program_id,
);
if #name.key() != __program_signer {
return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
}
if __bump != #b {
return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
}
}
} else {
let maybe_seeds_plus_comma = (!s.is_empty()).then(|| {
quote! { #s, }
});
let seeds = match c.bump.as_ref() {
// Bump target not given. Find it.
None => {
quote! {
[
&[
#maybe_seeds_plus_comma
&[
Pubkey::find_program_address(
&[#s],
&#deriving_program_id,
).1
][..]
]
}
}
// Bump target given. Use it.
Some(b) => {
quote! {
[#maybe_seeds_plus_comma &[#b][..]]
}
}
};
quote! {
let __program_signer = Pubkey::create_program_address(
&#seeds[..],
&#deriving_program_id,
).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?;
if #name.key() != __program_signer {
return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
}
&[__bump][..]
][..]
},
)
}
}
}
fn generate_constraint_associated_token(
f: &Field,
c: &ConstraintAssociatedToken,
) -> proc_macro2::TokenStream {
let name = &f.ident;
let wallet_address = &c.wallet;
let spl_token_mint_address = &c.mint;
quote! {
if #name.owner != #wallet_address.key() {
return Err(anchor_lang::__private::ErrorCode::ConstraintTokenOwner.into());
}
let __associated_token_address = anchor_spl::associated_token::get_associated_token_address(&#wallet_address.key(), &#spl_token_mint_address.key());
if #name.key() != __associated_token_address {
return Err(anchor_lang::__private::ErrorCode::ConstraintAssociated.into());
}
}
}
// `if_needed` is set if account allocation and initialization is optional.
pub fn generate_init(
f: &Field,
if_needed: bool,
seeds_with_nonce: proc_macro2::TokenStream,
payer: proc_macro2::TokenStream,
space: &Option<Expr>,
kind: &InitKind,
) -> proc_macro2::TokenStream {
let field = &f.ident;
let ty_decl = f.ty_decl();
let from_account_info = f.from_account_info_unchecked(Some(kind));
let if_needed = if if_needed {
quote! {true}
} else {
quote! {false}
};
match kind {
match &c.kind {
InitKind::Token { owner, mint } => {
let create_account = generate_create_account(
field,
quote! {anchor_spl::token::TokenAccount::LEN},
quote! {&token_program.key()},
seeds_with_nonce,
seeds_with_bump,
);
quote! {
// Define the bump and pda variable.
#find_pda
let #field: #ty_decl = {
if !#if_needed || #field.as_ref().owner == &anchor_lang::solana_program::system_program::ID {
// Define payer variable.
@ -475,6 +379,9 @@ pub fn generate_init(
}
InitKind::AssociatedToken { owner, mint } => {
quote! {
// Define the bump and pda variable.
#find_pda
let #field: #ty_decl = {
if !#if_needed || #field.as_ref().owner == &anchor_lang::solana_program::system_program::ID {
#payer
@ -518,13 +425,16 @@ pub fn generate_init(
field,
quote! {anchor_spl::token::Mint::LEN},
quote! {&token_program.key()},
seeds_with_nonce,
seeds_with_bump,
);
let freeze_authority = match freeze_authority {
Some(fa) => quote! { Option::<&anchor_lang::prelude::Pubkey>::Some(&#fa.key()) },
None => quote! { Option::<&anchor_lang::prelude::Pubkey>::None },
};
quote! {
// Define the bump and pda variable.
#find_pda
let #field: #ty_decl = {
if !#if_needed || #field.as_ref().owner == &anchor_lang::solana_program::system_program::ID {
// Define payer variable.
@ -562,6 +472,7 @@ pub fn generate_init(
}
}
InitKind::Program { owner } => {
// Define the space variable.
let space = match space {
// If no explicit space param was given, serialize the type to bytes
// and take the length (with +8 for the discriminator.)
@ -586,7 +497,7 @@ pub fn generate_init(
},
};
// Owner of the account being created. If not specified,
// Define the owner of the account being created. If not specified,
// default to the currently executing program.
let owner = match owner {
None => quote! {
@ -596,31 +507,37 @@ pub fn generate_init(
&#o
},
};
let pda_check = if !seeds_with_nonce.is_empty() {
quote! {
let expected_key = anchor_lang::prelude::Pubkey::create_program_address(
#seeds_with_nonce,
#owner
).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?;
if expected_key != #field.key() {
return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
}
}
} else {
quote! {}
};
// CPI to the system program to create the account.
let create_account =
generate_create_account(field, quote! {space}, owner.clone(), seeds_with_nonce);
generate_create_account(field, quote! {space}, owner.clone(), seeds_with_bump);
// Put it all together.
quote! {
// Define the bump variable.
#find_pda
let #field = {
let actual_field = #field.to_account_info();
let actual_owner = actual_field.owner;
// Define the account space variable.
#space
// Create the account. Always do this in the event
// if needed is not specified or the system program is the owner.
if !#if_needed || actual_owner == &anchor_lang::solana_program::system_program::ID {
// Define the payer variable.
#payer
// CPI to the system program to create.
#create_account
}
// Convert from account info to account context wrapper type.
let pa: #ty_decl = #from_account_info;
// Assert the account was created correctly.
if !(!#if_needed || actual_owner == &anchor_lang::solana_program::system_program::ID) {
if space != actual_field.data_len() {
return Err(anchor_lang::__private::ErrorCode::ConstraintSpace.into());
@ -636,9 +553,9 @@ pub fn generate_init(
return Err(anchor_lang::__private::ErrorCode::ConstraintRentExempt.into());
}
}
#pda_check
}
// Done.
pa
};
}
@ -646,6 +563,106 @@ pub fn generate_init(
}
}
fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2::TokenStream {
let name = &f.ident;
let name_str = name.to_string();
let s = &mut c.seeds.clone();
let deriving_program_id = c
.program_seed
.clone()
// If they specified a seeds::program to use when deriving the PDA, use it.
.map(|program_id| quote! { #program_id })
// Otherwise fall back to the current program's program_id.
.unwrap_or(quote! { program_id });
// If the seeds came with a trailing comma, we need to chop it off
// before we interpolate them below.
if let Some(pair) = s.pop() {
s.push_value(pair.into_value());
}
// If the bump is provided with init *and target*, then force it to be the
// canonical bump.
//
// Note that for `#[account(init, seeds)]`, find_program_address has already
// been run in the init constraint.
if c.is_init && c.bump.is_some() {
let b = c.bump.as_ref().unwrap();
quote! {
if #name.key() != __pda_address {
return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
}
if __bump != #b {
return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
}
}
}
// Init seeds but no bump. We already used the canonical to create bump so
// just check the address.
//
// Note that for `#[account(init, seeds)]`, find_program_address has already
// been run in the init constraint.
else if c.is_init {
quote! {
if #name.key() != __pda_address {
return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
}
}
}
// No init. So we just check the address.
else {
let maybe_seeds_plus_comma = (!s.is_empty()).then(|| {
quote! { #s, }
});
let define_pda = match c.bump.as_ref() {
// Bump target not given. Find it.
None => quote! {
let (__pda_address, __bump) = Pubkey::find_program_address(
&[#maybe_seeds_plus_comma],
&#deriving_program_id,
);
__bumps.insert(#name_str.to_string(), __bump);
},
// Bump target given. Use it.
Some(b) => quote! {
let __pda_address = Pubkey::create_program_address(
&[#maybe_seeds_plus_comma &[#b][..]],
&#deriving_program_id,
).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?;
},
};
quote! {
// Define the PDA.
#define_pda
// Check it.
if #name.key() != __pda_address {
return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into());
}
}
}
}
fn generate_constraint_associated_token(
f: &Field,
c: &ConstraintAssociatedToken,
) -> proc_macro2::TokenStream {
let name = &f.ident;
let wallet_address = &c.wallet;
let spl_token_mint_address = &c.mint;
quote! {
if #name.owner != #wallet_address.key() {
return Err(anchor_lang::__private::ErrorCode::ConstraintTokenOwner.into());
}
let __associated_token_address = anchor_spl::associated_token::get_associated_token_address(&#wallet_address.key(), &#spl_token_mint_address.key());
if #name.key() != __associated_token_address {
return Err(anchor_lang::__private::ErrorCode::ConstraintAssociated.into());
}
}
}
// Generated code to create an account with with system program with the
// given `space` amount of data, owned by `owner`.
//

View File

@ -25,7 +25,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
quote! {
#[cfg(feature = "anchor-debug")]
::solana_program::log::sol_log(stringify!(#name));
let #name: #ty = anchor_lang::Accounts::try_accounts(program_id, accounts, ix_data)?;
let #name: #ty = anchor_lang::Accounts::try_accounts(program_id, accounts, ix_data, __bumps)?;
}
}
AccountField::Field(f) => {
@ -43,7 +43,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
quote! {
#[cfg(feature = "anchor-debug")]
::solana_program::log::sol_log(stringify!(#name));
let #name = anchor_lang::Accounts::try_accounts(program_id, accounts, ix_data)?;
let #name = anchor_lang::Accounts::try_accounts(program_id, accounts, ix_data, __bumps)?;
}
}
}
@ -92,6 +92,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
program_id: &anchor_lang::solana_program::pubkey::Pubkey,
accounts: &mut &[anchor_lang::solana_program::account_info::AccountInfo<'info>],
ix_data: &[u8],
__bumps: &mut std::collections::BTreeMap<String, u8>,
) -> std::result::Result<Self, anchor_lang::solana_program::program_error::ProgramError> {
// Deserialize instruction, if declared.
#ix_de

View File

@ -25,32 +25,37 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
match ix {
anchor_lang::idl::IdlInstruction::Create { data_len } => {
let mut bumps = std::collections::BTreeMap::new();
let mut accounts =
anchor_lang::idl::IdlCreateAccounts::try_accounts(program_id, &mut accounts, &[])?;
anchor_lang::idl::IdlCreateAccounts::try_accounts(program_id, &mut accounts, &[], &mut bumps)?;
__idl_create_account(program_id, &mut accounts, data_len)?;
accounts.exit(program_id)?;
},
anchor_lang::idl::IdlInstruction::CreateBuffer => {
let mut bumps = std::collections::BTreeMap::new();
let mut accounts =
anchor_lang::idl::IdlCreateBuffer::try_accounts(program_id, &mut accounts, &[])?;
anchor_lang::idl::IdlCreateBuffer::try_accounts(program_id, &mut accounts, &[], &mut bumps)?;
__idl_create_buffer(program_id, &mut accounts)?;
accounts.exit(program_id)?;
},
anchor_lang::idl::IdlInstruction::Write { data } => {
let mut bumps = std::collections::BTreeMap::new();
let mut accounts =
anchor_lang::idl::IdlAccounts::try_accounts(program_id, &mut accounts, &[])?;
anchor_lang::idl::IdlAccounts::try_accounts(program_id, &mut accounts, &[], &mut bumps)?;
__idl_write(program_id, &mut accounts, data)?;
accounts.exit(program_id)?;
},
anchor_lang::idl::IdlInstruction::SetAuthority { new_authority } => {
let mut bumps = std::collections::BTreeMap::new();
let mut accounts =
anchor_lang::idl::IdlAccounts::try_accounts(program_id, &mut accounts, &[])?;
anchor_lang::idl::IdlAccounts::try_accounts(program_id, &mut accounts, &[], &mut bumps)?;
__idl_set_authority(program_id, &mut accounts, new_authority)?;
accounts.exit(program_id)?;
},
anchor_lang::idl::IdlInstruction::SetBuffer => {
let mut bumps = std::collections::BTreeMap::new();
let mut accounts =
anchor_lang::idl::IdlSetBuffer::try_accounts(program_id, &mut accounts, &[])?;
anchor_lang::idl::IdlSetBuffer::try_accounts(program_id, &mut accounts, &[], &mut bumps)?;
__idl_set_buffer(program_id, &mut accounts)?;
accounts.exit(program_id)?;
},
@ -210,10 +215,14 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::state::#variant_arm = ix;
let mut __bumps = std::collections::BTreeMap::new();
// Deserialize accounts.
let mut remaining_accounts: &[AccountInfo] = accounts;
let ctor_accounts = anchor_lang::__private::Ctor::try_accounts(program_id, &mut remaining_accounts, &[])?;
let mut ctor_user_def_accounts = #anchor_ident::try_accounts(program_id, &mut remaining_accounts, ix_data)?;
let ctor_accounts =
anchor_lang::__private::Ctor::try_accounts(program_id, &mut remaining_accounts, &[], &mut __bumps)?;
let mut ctor_user_def_accounts =
#anchor_ident::try_accounts(program_id, &mut remaining_accounts, ix_data, &mut __bumps)?;
// Create the solana account for the ctor data.
let from = ctor_accounts.from.key;
@ -258,6 +267,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
program_id,
&mut ctor_user_def_accounts,
remaining_accounts,
__bumps,
),
#(#ctor_untyped_args),*
)?;
@ -284,10 +294,14 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::state::#variant_arm = ix;
let mut __bumps = std::collections::BTreeMap::new();
// Deserialize accounts.
let mut remaining_accounts: &[AccountInfo] = accounts;
let ctor_accounts = anchor_lang::__private::Ctor::try_accounts(program_id, &mut remaining_accounts, &[])?;
let mut ctor_user_def_accounts = #anchor_ident::try_accounts(program_id, &mut remaining_accounts, ix_data)?;
let ctor_accounts =
anchor_lang::__private::Ctor::try_accounts(program_id, &mut remaining_accounts, &[], &mut __bumps)?;
let mut ctor_user_def_accounts =
#anchor_ident::try_accounts(program_id, &mut remaining_accounts, ix_data, &mut __bumps)?;
// Invoke the ctor.
let instance = #mod_name::#name::new(
@ -295,6 +309,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
program_id,
&mut ctor_user_def_accounts,
remaining_accounts,
__bumps,
),
#(#ctor_untyped_args),*
)?;
@ -387,20 +402,30 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::state::#variant_arm = ix;
// Bump collector.
let mut __bumps = std::collections::BTreeMap::new();
// Load state.
let mut remaining_accounts: &[AccountInfo] = accounts;
if remaining_accounts.is_empty() {
return Err(anchor_lang::__private::ErrorCode::AccountNotEnoughKeys.into());
}
let loader: anchor_lang::accounts::loader::Loader<#mod_name::#name> = anchor_lang::accounts::loader::Loader::try_accounts(program_id, &mut remaining_accounts, &[])?;
let loader: anchor_lang::accounts::loader::Loader<#mod_name::#name> = anchor_lang::accounts::loader::Loader::try_accounts(program_id, &mut remaining_accounts, &[], &mut __bumps)?;
// Deserialize accounts.
let mut accounts = #anchor_ident::try_accounts(
program_id,
&mut remaining_accounts,
ix_data,
&mut __bumps,
)?;
let ctx = anchor_lang::context::Context::new(program_id, &mut accounts, remaining_accounts);
let ctx =
anchor_lang::context::Context::new(
program_id,
&mut accounts,
remaining_accounts,
__bumps,
);
// Execute user defined function.
{
@ -433,20 +458,35 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::state::#variant_arm = ix;
// Bump collector.
let mut __bumps = std::collections::BTreeMap::new();
// Load state.
let mut remaining_accounts: &[AccountInfo] = accounts;
if remaining_accounts.is_empty() {
return Err(anchor_lang::__private::ErrorCode::AccountNotEnoughKeys.into());
}
let mut state: anchor_lang::accounts::state::ProgramState<#state_ty> = anchor_lang::accounts::state::ProgramState::try_accounts(program_id, &mut remaining_accounts, &[])?;
let mut state: anchor_lang::accounts::state::ProgramState<#state_ty> = anchor_lang::accounts::state::ProgramState::try_accounts(
program_id,
&mut remaining_accounts,
&[],
&mut __bumps,
)?;
// Deserialize accounts.
let mut accounts = #anchor_ident::try_accounts(
program_id,
&mut remaining_accounts,
ix_data,
&mut __bumps,
)?;
let ctx = anchor_lang::context::Context::new(program_id, &mut accounts, remaining_accounts);
let ctx =
anchor_lang::context::Context::new(
program_id,
&mut accounts,
remaining_accounts,
__bumps
);
// Execute user defined function.
state.#ix_method_name(
@ -488,7 +528,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
.map(|ix| {
// Easy to implement. Just need to write a test.
// Feel free to open a PR.
assert!(!state.is_zero_copy, "Trait implementations not yet implemented for zero copy state structs. Please file an issue.");
assert!(!state.is_zero_copy, "Trait implementations not yet implemented for zero copy state structs. Please file an issue.");
let ix_arg_names: Vec<&syn::Ident> =
ix.args.iter().map(|arg| &arg.name).collect();
@ -546,20 +586,35 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
// Deserialize instruction.
#deserialize_instruction
// Bump collector.
let mut __bumps = std::collections::BTreeMap::new();
// Deserialize the program state account.
let mut remaining_accounts: &[AccountInfo] = accounts;
if remaining_accounts.is_empty() {
return Err(anchor_lang::__private::ErrorCode::AccountNotEnoughKeys.into());
}
let mut state: anchor_lang::accounts::state::ProgramState<#state_ty> = anchor_lang::accounts::state::ProgramState::try_accounts(program_id, &mut remaining_accounts, &[])?;
let mut state: anchor_lang::accounts::state::ProgramState<#state_ty> = anchor_lang::accounts::state::ProgramState::try_accounts(
program_id,
&mut remaining_accounts,
&[],
&mut __bumps,
)?;
// Deserialize accounts.
let mut accounts = #anchor_ident::try_accounts(
program_id,
&mut remaining_accounts,
ix_data,
&mut __bumps,
)?;
let ctx = anchor_lang::context::Context::new(program_id, &mut accounts, remaining_accounts);
let ctx =
anchor_lang::context::Context::new(
program_id,
&mut accounts,
remaining_accounts,
__bumps,
);
// Execute user defined function.
state.#ix_method_name(
@ -593,17 +648,26 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
// Deserialize instruction.
#deserialize_instruction
// Bump collector.
let mut __bumps = std::collections::BTreeMap::new();
// Deserialize accounts.
let mut remaining_accounts: &[AccountInfo] = accounts;
let mut accounts = #anchor_ident::try_accounts(
program_id,
&mut remaining_accounts,
ix_data,
&mut __bumps,
)?;
// Execute user defined function.
#state_name::#ix_method_name(
anchor_lang::context::Context::new(program_id, &mut accounts, remaining_accounts),
anchor_lang::context::Context::new(
program_id,
&mut accounts,
remaining_accounts,
__bumps
),
#(#ix_arg_names),*
)?;
@ -644,17 +708,26 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
.map_err(|_| anchor_lang::__private::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::#variant_arm = ix;
// Bump collector.
let mut __bumps = std::collections::BTreeMap::new();
// Deserialize accounts.
let mut remaining_accounts: &[AccountInfo] = accounts;
let mut accounts = #anchor::try_accounts(
program_id,
&mut remaining_accounts,
ix_data,
&mut __bumps,
)?;
// Invoke user defined handler.
#program_name::#ix_method_name(
anchor_lang::context::Context::new(program_id, &mut accounts, remaining_accounts),
anchor_lang::context::Context::new(
program_id,
&mut accounts,
remaining_accounts,
__bumps,
),
#(#ix_arg_names),*
)?;

View File

@ -570,7 +570,7 @@ impl<'ty> ConstraintGroupBuilder<'ty> {
};
Ok(ConstraintGroup {
init: init.as_ref().map(|i| Ok(ConstraintInitGroup {
if_needed: i.if_needed,
if_needed: i.if_needed,
seeds: seeds.clone(),
payer: into_inner!(payer.clone()).map(|a| a.target),
space: space.clone().map(|s| s.space.clone()),

@ -1 +1 @@
Subproject commit fea2d89c2b17ee39fcf0ebaadb0317b9e97206f4
Subproject commit 09c911a53fd710d7d56972089c64de19d54ecf37

@ -1 +1 @@
Subproject commit bc73b510285050f5a21da13c5bc3b8b4c959948f
Subproject commit f04b2aaf8817dac4c8d89e75eaaa0c099dfbf166