From 5c6e5d7edf11304db79eca1ba09a05fe00128836 Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Wed, 26 Jan 2022 17:38:26 -0500 Subject: [PATCH 01/16] update --- lang/src/accounts/account.rs | 2 + lang/src/accounts/account_info.rs | 2 + lang/src/accounts/account_loader.rs | 2 + lang/src/accounts/boxed.rs | 4 +- lang/src/accounts/cpi_account.rs | 2 + lang/src/accounts/cpi_state.rs | 2 + lang/src/accounts/loader.rs | 2 + lang/src/accounts/program.rs | 2 + lang/src/accounts/program_account.rs | 2 + lang/src/accounts/signer.rs | 2 + lang/src/accounts/state.rs | 2 + lang/src/accounts/system_account.rs | 2 + lang/src/accounts/sysvar.rs | 2 + lang/src/accounts/unchecked_account.rs | 2 + lang/src/context.rs | 8 ++ lang/src/lib.rs | 2 + lang/src/vec.rs | 4 +- lang/syn/src/codegen/accounts/constraints.rs | 89 +++++++++----- lang/syn/src/codegen/accounts/try_accounts.rs | 5 +- lang/syn/src/codegen/program/handlers.rs | 109 +++++++++++++++--- 20 files changed, 197 insertions(+), 50 deletions(-) diff --git a/lang/src/accounts/account.rs b/lang/src/accounts/account.rs index ab357410fe..cb2e3703aa 100644 --- a/lang/src/accounts/account.rs +++ b/lang/src/accounts/account.rs @@ -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, ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); diff --git a/lang/src/accounts/account_info.rs b/lang/src/accounts/account_info.rs index 4de5be1ee1..186c4848a1 100644 --- a/lang/src/accounts/account_info.rs +++ b/lang/src/accounts/account_info.rs @@ -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, ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); diff --git a/lang/src/accounts/account_loader.rs b/lang/src/accounts/account_loader.rs index 4e4a8a7f1f..4e2e81c44e 100644 --- a/lang/src/accounts/account_loader.rs +++ b/lang/src/accounts/account_loader.rs @@ -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, ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); diff --git a/lang/src/accounts/boxed.rs b/lang/src/accounts/boxed.rs index 7bfb5738be..b89453d16b 100644 --- a/lang/src/accounts/boxed.rs +++ b/lang/src/accounts/boxed.rs @@ -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 { @@ -26,8 +27,9 @@ impl<'info, T: Accounts<'info>> Accounts<'info> for Box { program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>], ix_data: &[u8], + _bumps: &mut BTreeMap, ) -> Result { - T::try_accounts(program_id, accounts, ix_data).map(Box::new) + T::try_accounts(program_id, accounts, ix_data, _bumps).map(Box::new) } } diff --git a/lang/src/accounts/cpi_account.rs b/lang/src/accounts/cpi_account.rs index 5efae03e61..eeff02a919 100644 --- a/lang/src/accounts/cpi_account.rs +++ b/lang/src/accounts/cpi_account.rs @@ -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, ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); diff --git a/lang/src/accounts/cpi_state.rs b/lang/src/accounts/cpi_state.rs index 38261a53b2..3fba2284e1 100644 --- a/lang/src/accounts/cpi_state.rs +++ b/lang/src/accounts/cpi_state.rs @@ -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, ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); diff --git a/lang/src/accounts/loader.rs b/lang/src/accounts/loader.rs index d436d5298d..f60bbd71c4 100644 --- a/lang/src/accounts/loader.rs +++ b/lang/src/accounts/loader.rs @@ -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, ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); diff --git a/lang/src/accounts/program.rs b/lang/src/accounts/program.rs index 31b9663557..f31b11a061 100644 --- a/lang/src/accounts/program.rs +++ b/lang/src/accounts/program.rs @@ -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, ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); diff --git a/lang/src/accounts/program_account.rs b/lang/src/accounts/program_account.rs index 67504f5488..7a76770f7f 100644 --- a/lang/src/accounts/program_account.rs +++ b/lang/src/accounts/program_account.rs @@ -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, ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); diff --git a/lang/src/accounts/signer.rs b/lang/src/accounts/signer.rs index f05b747990..fd05e70639 100644 --- a/lang/src/accounts/signer.rs +++ b/lang/src/accounts/signer.rs @@ -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, ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); diff --git a/lang/src/accounts/state.rs b/lang/src/accounts/state.rs index 010c157312..20dd35a190 100644 --- a/lang/src/accounts/state.rs +++ b/lang/src/accounts/state.rs @@ -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, ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); diff --git a/lang/src/accounts/system_account.rs b/lang/src/accounts/system_account.rs index 4e2d9415ee..8495834bc1 100644 --- a/lang/src/accounts/system_account.rs +++ b/lang/src/accounts/system_account.rs @@ -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, ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); diff --git a/lang/src/accounts/sysvar.rs b/lang/src/accounts/sysvar.rs index 2cb730d182..024264a1ed 100644 --- a/lang/src/accounts/sysvar.rs +++ b/lang/src/accounts/sysvar.rs @@ -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, ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); diff --git a/lang/src/accounts/unchecked_account.rs b/lang/src/accounts/unchecked_account.rs index 1df81aeb8d..7da7ea1581 100644 --- a/lang/src/accounts/unchecked_account.rs +++ b/lang/src/accounts/unchecked_account.rs @@ -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, ) -> Result { if accounts.is_empty() { return Err(ErrorCode::AccountNotEnoughKeys.into()); diff --git a/lang/src/context.rs b/lang/src/context.rs index ccf24e4674..736f248c96 100644 --- a/lang/src/context.rs +++ b/lang/src/context.rs @@ -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, } 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, ) -> Self { Self { program_id, accounts, remaining_accounts, + bumps, } } } diff --git a/lang/src/lib.rs b/lang/src/lib.rs index ea79ad8dc1..20e5497ae9 100644 --- a/lang/src/lib.rs +++ b/lang/src/lib.rs @@ -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, ) -> Result; } diff --git a/lang/src/vec.rs b/lang/src/vec.rs index 820c0c97a2..c158c780d0 100644 --- a/lang/src/vec.rs +++ b/lang/src/vec.rs @@ -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 { fn to_account_infos(&self) -> Vec> { @@ -25,9 +26,10 @@ impl<'info, T: Accounts<'info>> Accounts<'info> for Vec { program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>], ix_data: &[u8], + bumps: &mut BTreeMap, ) -> Result { let mut vec: Vec = 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) } } diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index 473c312194..94ceffd53f 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/lang/syn/src/codegen/accounts/constraints.rs @@ -283,9 +283,10 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma let payer = #p.to_account_info(); } }; + let name_str = f.ident.to_string(); - let seeds_with_nonce = match &c.seeds { - None => quote! {}, + let (seeds_with_nonce, bump) = match &c.seeds { + None => (quote! {}, quote! {}), Some(c) => { let s = &mut c.seeds.clone(); // If the seeds came with a trailing comma, we need to chop it off @@ -302,12 +303,7 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma quote! { [ #maybe_seeds_plus_comma - &[ - Pubkey::find_program_address( - &[#s], - program_id, - ).1 - ][..] + &[__bump][..] ] } } @@ -316,16 +312,35 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma [#maybe_seeds_plus_comma &[#b][..]] }, }; - quote! { - &#inner[..] - } + ( + quote! { + &#inner[..] + }, + quote! { + let __bump = Pubkey::find_program_address( + &[#s], + program_id, + ).1; + __bumps.insert(#name_str.to_string(), __bump); + }, + ) } }; - generate_init(f, c.if_needed, seeds_with_nonce, payer, &c.space, &c.kind) + generate_init( + f, + c.if_needed, + bump, + seeds_with_nonce, + payer, + &c.space, + &c.kind, + ) } 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 @@ -347,17 +362,21 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2 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()); - } + let (__program_signer, __bump) = anchor_lang::solana_program::pubkey::Pubkey::find_program_address( + &[#s], + &#deriving_program_id, + ); + + // Save the bump for access by the handler. + __bumps.insert(#name_str.to_string(), __bump); + + 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, } @@ -368,12 +387,7 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2 quote! { [ #maybe_seeds_plus_comma - &[ - Pubkey::find_program_address( - &[#s], - &#deriving_program_id, - ).1 - ][..] + &[__bump][..] ] } } @@ -385,6 +399,14 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2 } }; quote! { + let __bump = Pubkey::find_program_address( + &[#s], + &#deriving_program_id, + ).1; + + // Save the bump for access by the handler. + __bumps.insert(#name_str.to_string(), __bump); + let __program_signer = Pubkey::create_program_address( &#seeds[..], &#deriving_program_id, @@ -418,6 +440,7 @@ fn generate_constraint_associated_token( pub fn generate_init( f: &Field, if_needed: bool, + bump: proc_macro2::TokenStream, seeds_with_nonce: proc_macro2::TokenStream, payer: proc_macro2::TokenStream, space: &Option, @@ -445,6 +468,9 @@ pub fn generate_init( // Define payer variable. #payer + // Define the bump variable. + #bump + // Create the account with the system program. #create_account @@ -530,6 +556,9 @@ pub fn generate_init( // Define payer variable. #payer + // Define the bump variable. + #bump + // Create the account with the system program. #create_account @@ -616,6 +645,10 @@ pub fn generate_init( let actual_field = #field.to_account_info(); let actual_owner = actual_field.owner; #space + + // Define the bump variable. + #bump + if !#if_needed || actual_owner == &anchor_lang::solana_program::system_program::ID { #payer #create_account diff --git a/lang/syn/src/codegen/accounts/try_accounts.rs b/lang/syn/src/codegen/accounts/try_accounts.rs index b9c4e23e91..47d582e4be 100644 --- a/lang/syn/src/codegen/accounts/try_accounts.rs +++ b/lang/syn/src/codegen/accounts/try_accounts.rs @@ -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, ) -> std::result::Result { // Deserialize instruction, if declared. #ix_de diff --git a/lang/syn/src/codegen/program/handlers.rs b/lang/syn/src/codegen/program/handlers.rs index f622b6e13b..58873a4b32 100644 --- a/lang/syn/src/codegen/program/handlers.rs +++ b/lang/syn/src/codegen/program/handlers.rs @@ -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),* )?; From 739b05bdde565953389e6d81af2f88ee34b63e37 Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Thu, 27 Jan 2022 12:36:43 -0500 Subject: [PATCH 02/16] update --- tests/auction-house | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/auction-house b/tests/auction-house index fea2d89c2b..53cdd078d5 160000 --- a/tests/auction-house +++ b/tests/auction-house @@ -1 +1 @@ -Subproject commit fea2d89c2b17ee39fcf0ebaadb0317b9e97206f4 +Subproject commit 53cdd078d501f5f3bb84d8aaf7ea31aa0aa8b3af From 661d69c03bd866cdcd2725f33b86800c3004fa6f Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Thu, 27 Jan 2022 12:48:52 -0500 Subject: [PATCH 03/16] update --- lang/syn/src/codegen/program/handlers.rs | 106 +++++++++++------------ 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/lang/syn/src/codegen/program/handlers.rs b/lang/syn/src/codegen/program/handlers.rs index 58873a4b32..7a23fcf8be 100644 --- a/lang/syn/src/codegen/program/handlers.rs +++ b/lang/syn/src/codegen/program/handlers.rs @@ -402,8 +402,8 @@ 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(); + // Bump collector. + let mut __bumps = std::collections::BTreeMap::new(); // Load state. let mut remaining_accounts: &[AccountInfo] = accounts; @@ -417,15 +417,15 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { program_id, &mut remaining_accounts, ix_data, - &mut __bumps, + &mut __bumps, )?; let ctx = - anchor_lang::context::Context::new( - program_id, - &mut accounts, - remaining_accounts, - __bumps, - ); + anchor_lang::context::Context::new( + program_id, + &mut accounts, + remaining_accounts, + __bumps, + ); // Execute user defined function. { @@ -458,8 +458,8 @@ 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(); + // Bump collector. + let mut __bumps = std::collections::BTreeMap::new(); // Load state. let mut remaining_accounts: &[AccountInfo] = accounts; @@ -467,26 +467,26 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { 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, - &[], - &mut __bumps, - )?; + 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, + &mut __bumps, )?; let ctx = - anchor_lang::context::Context::new( - program_id, - &mut accounts, - remaining_accounts, - __bumps - ); + anchor_lang::context::Context::new( + program_id, + &mut accounts, + remaining_accounts, + __bumps + ); // Execute user defined function. state.#ix_method_name( @@ -586,8 +586,8 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { // Deserialize instruction. #deserialize_instruction - // Bump collector. - let mut __bumps = std::collections::BTreeMap::new(); + // Bump collector. + let mut __bumps = std::collections::BTreeMap::new(); // Deserialize the program state account. let mut remaining_accounts: &[AccountInfo] = accounts; @@ -595,26 +595,26 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { 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, - &[], - &mut __bumps, - )?; + 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, + &mut __bumps, )?; let ctx = - anchor_lang::context::Context::new( - program_id, - &mut accounts, - remaining_accounts, - __bumps, - ); + anchor_lang::context::Context::new( + program_id, + &mut accounts, + remaining_accounts, + __bumps, + ); // Execute user defined function. state.#ix_method_name( @@ -648,8 +648,8 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { // Deserialize instruction. #deserialize_instruction - // Bump collector. - let mut __bumps = std::collections::BTreeMap::new(); + // Bump collector. + let mut __bumps = std::collections::BTreeMap::new(); // Deserialize accounts. let mut remaining_accounts: &[AccountInfo] = accounts; @@ -657,17 +657,17 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { program_id, &mut remaining_accounts, ix_data, - &mut __bumps, + &mut __bumps, )?; // Execute user defined function. #state_name::#ix_method_name( anchor_lang::context::Context::new( - program_id, - &mut accounts, - remaining_accounts, - __bumps - ), + program_id, + &mut accounts, + remaining_accounts, + __bumps + ), #(#ix_arg_names),* )?; @@ -708,8 +708,8 @@ 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(); + // Bump collector. + let mut __bumps = std::collections::BTreeMap::new(); // Deserialize accounts. let mut remaining_accounts: &[AccountInfo] = accounts; @@ -717,17 +717,17 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { program_id, &mut remaining_accounts, ix_data, - &mut __bumps, + &mut __bumps, )?; // Invoke user defined handler. #program_name::#ix_method_name( anchor_lang::context::Context::new( - program_id, - &mut accounts, - remaining_accounts, - __bumps, - ), + program_id, + &mut accounts, + remaining_accounts, + __bumps, + ), #(#ix_arg_names),* )?; From d1f64d0013d4733d7e54297ad26f63733981ba1c Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Thu, 27 Jan 2022 12:52:40 -0500 Subject: [PATCH 04/16] update --- lang/src/vec.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lang/src/vec.rs b/lang/src/vec.rs index c158c780d0..c66f8f60ff 100644 --- a/lang/src/vec.rs +++ b/lang/src/vec.rs @@ -78,9 +78,10 @@ mod tests { false, Epoch::default(), ); - + let mut bumps = std::collections::BTreeMap::new(); let mut accounts = &[account1, account2][..]; - let parsed_accounts = Vec::::try_accounts(&program_id, &mut accounts, &[]).unwrap(); + let parsed_accounts = + Vec::::try_accounts(&program_id, &mut accounts, &[], &mut bumps).unwrap(); assert_eq!(accounts.len(), parsed_accounts.len()); } @@ -89,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::::try_accounts(&program_id, &mut accounts, &[]).unwrap(); + Vec::::try_accounts(&program_id, &mut accounts, &[], &mut bumps).unwrap(); } } From 3c2426b86ec669fbec394ec27583e57c8e24be9f Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Thu, 27 Jan 2022 13:07:33 -0500 Subject: [PATCH 05/16] update --- tests/auction-house | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/auction-house b/tests/auction-house index 53cdd078d5..09c911a53f 160000 --- a/tests/auction-house +++ b/tests/auction-house @@ -1 +1 @@ -Subproject commit 53cdd078d501f5f3bb84d8aaf7ea31aa0aa8b3af +Subproject commit 09c911a53fd710d7d56972089c64de19d54ecf37 From e5a344c095a29d0dc2a8fb5f458ff87afc3d69e6 Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Thu, 27 Jan 2022 13:08:32 -0500 Subject: [PATCH 06/16] update --- lang/src/accounts/boxed.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/src/accounts/boxed.rs b/lang/src/accounts/boxed.rs index b89453d16b..5282fb19fd 100644 --- a/lang/src/accounts/boxed.rs +++ b/lang/src/accounts/boxed.rs @@ -27,9 +27,9 @@ impl<'info, T: Accounts<'info>> Accounts<'info> for Box { program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>], ix_data: &[u8], - _bumps: &mut BTreeMap, + bumps: &mut BTreeMap, ) -> Result { - T::try_accounts(program_id, accounts, ix_data, _bumps).map(Box::new) + T::try_accounts(program_id, accounts, ix_data, bumps).map(Box::new) } } From e1613d311b967db02e68c71ade0308d8cbb1ac00 Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Thu, 27 Jan 2022 13:27:21 -0500 Subject: [PATCH 07/16] update --- tests/cfo/deps/stake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cfo/deps/stake b/tests/cfo/deps/stake index bc73b51028..506d2395d7 160000 --- a/tests/cfo/deps/stake +++ b/tests/cfo/deps/stake @@ -1 +1 @@ -Subproject commit bc73b510285050f5a21da13c5bc3b8b4c959948f +Subproject commit 506d2395d7843ac0c1f47e67aba1c19a357a693b From 52ccbbe9a7a2e8a1919ee1dfa6e4b6b6aa931771 Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Thu, 27 Jan 2022 13:30:32 -0500 Subject: [PATCH 08/16] update dep --- tests/cfo/deps/stake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cfo/deps/stake b/tests/cfo/deps/stake index 506d2395d7..f04b2aaf88 160000 --- a/tests/cfo/deps/stake +++ b/tests/cfo/deps/stake @@ -1 +1 @@ -Subproject commit 506d2395d7843ac0c1f47e67aba1c19a357a693b +Subproject commit f04b2aaf8817dac4c8d89e75eaaa0c099dfbf166 From b85feabb0fb12e6a08c02c50119ad5f47746f9c9 Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Thu, 27 Jan 2022 15:11:40 -0500 Subject: [PATCH 09/16] update --- lang/syn/src/codegen/accounts/constraints.rs | 377 ++++++++++--------- 1 file changed, 197 insertions(+), 180 deletions(-) diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index 94ceffd53f..72feaa0620 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/lang/syn/src/codegen/accounts/constraints.rs @@ -277,200 +277,88 @@ 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! { let payer = #p.to_account_info(); } }; - let name_str = f.ident.to_string(); - let (seeds_with_nonce, bump) = match &c.seeds { + // 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() { + + match c.bump.as_ref() { // Bump target not given. Use the canonical bump. - None => { + None => ( quote! { - [ + let (__pda_address, __bump) = Pubkey::find_program_address( + &[#seeds], + program_id, + ); + __bumps.insert(#name_str.to_string(), __bump); + }, + quote! { + &[ #maybe_seeds_plus_comma &[__bump][..] - ] - } - } + ][..] + }, + ), // Bump target given. Use it. - Some(b) => quote! { - [#maybe_seeds_plus_comma &[#b][..]] - }, - }; - ( - quote! { - &#inner[..] - }, - quote! { - let __bump = Pubkey::find_program_address( - &[#s], - program_id, - ).1; - __bumps.insert(#name_str.to_string(), __bump); - }, - ) - } - }; - generate_init( - f, - c.if_needed, - bump, - seeds_with_nonce, - payer, - &c.space, - &c.kind, - ) -} - -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. - 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, - ); - - // Save the bump for access by the handler. - __bumps.insert(#name_str.to_string(), __bump); - - 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 - &[__bump][..] - ] - } - } - // Bump target given. Use it. - Some(b) => { - quote! { - [#maybe_seeds_plus_comma &[#b][..]] - } - } - }; - quote! { - let __bump = Pubkey::find_program_address( - &[#s], - &#deriving_program_id, - ).1; - - // Save the bump for access by the handler. - __bumps.insert(#name_str.to_string(), __bump); - - 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()); + Some(bump) => ( + quote! {}, + quote! { + &[#maybe_seeds_plus_comma &[#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, - bump: proc_macro2::TokenStream, - seeds_with_nonce: proc_macro2::TokenStream, - payer: proc_macro2::TokenStream, - space: &Option, - 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. #payer - // Define the bump variable. - #bump - // Create the account with the system program. #create_account @@ -501,6 +389,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 @@ -544,21 +435,21 @@ 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. #payer - // Define the bump variable. - #bump - // Create the account with the system program. #create_account @@ -591,6 +482,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.) @@ -615,7 +507,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! { @@ -625,35 +517,54 @@ 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()); + + // Check the given account matches the seeds given. + let pda_check = { + if !seeds_with_bump.is_empty() { + quote! { + let expected_key = anchor_lang::prelude::Pubkey::create_program_address( + #seeds_with_bump, + #owner + ).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?; + if expected_key != #field.key() { + return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into()); + } } + } else { + quote! {} } - } 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; - #space - // Define the bump variable. - #bump + // 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()); @@ -672,6 +583,8 @@ pub fn generate_init( #pda_check } + + // Done. pa }; } @@ -679,6 +592,110 @@ 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. + 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. + 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 (bump, seeds) = match c.bump.as_ref() { + // Bump target not given. Find it. + None => ( + quote! { + let __bump = Pubkey::find_program_address( + &[#s], + &#deriving_program_id, + ).1; + __bumps.insert(#name_str.to_string(), __bump); + }, + quote! { + [ + #maybe_seeds_plus_comma + &[__bump][..] + ] + }, + ), + // Bump target given. Use it. + Some(b) => ( + quote! {}, + quote! { + [#maybe_seeds_plus_comma &[#b][..]] + }, + ), + }; + quote! { + // Define the bump. + #bump + 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()); + } + } + } +} + +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`. // From 9da6f58607ce18b32081d294a5db68cf2266f6f5 Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Thu, 27 Jan 2022 15:31:42 -0500 Subject: [PATCH 10/16] update --- lang/syn/src/codegen/accounts/constraints.rs | 19 ------------------- lang/syn/src/parser/accounts/constraints.rs | 2 +- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index 72feaa0620..26b0940f46 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/lang/syn/src/codegen/accounts/constraints.rs @@ -518,23 +518,6 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma }, }; - // Check the given account matches the seeds given. - let pda_check = { - if !seeds_with_bump.is_empty() { - quote! { - let expected_key = anchor_lang::prelude::Pubkey::create_program_address( - #seeds_with_bump, - #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_bump); @@ -580,8 +563,6 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma return Err(anchor_lang::__private::ErrorCode::ConstraintRentExempt.into()); } } - - #pda_check } // Done. diff --git a/lang/syn/src/parser/accounts/constraints.rs b/lang/syn/src/parser/accounts/constraints.rs index 90fc98d8d5..fab8087516 100644 --- a/lang/syn/src/parser/accounts/constraints.rs +++ b/lang/syn/src/parser/accounts/constraints.rs @@ -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()), From 8280b93de8b57007a7da340b2cb119fc7f7587f1 Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Thu, 27 Jan 2022 15:38:29 -0500 Subject: [PATCH 11/16] update --- lang/syn/src/codegen/accounts/constraints.rs | 40 ++++++++------------ 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index 26b0940f46..c05f5f875e 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/lang/syn/src/codegen/accounts/constraints.rs @@ -314,31 +314,21 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma quote! { #seeds, } }); - match c.bump.as_ref() { - // Bump target not given. Use the canonical bump. - None => ( - quote! { - let (__pda_address, __bump) = Pubkey::find_program_address( - &[#seeds], - program_id, - ); - __bumps.insert(#name_str.to_string(), __bump); - }, - quote! { - &[ - #maybe_seeds_plus_comma - &[__bump][..] - ][..] - }, - ), - // Bump target given. Use it. - Some(bump) => ( - quote! {}, - quote! { - &[#maybe_seeds_plus_comma &[#bump][..]][..] - }, - ), - } + ( + quote! { + let (__pda_address, __bump) = Pubkey::find_program_address( + &[#seeds], + program_id, + ); + __bumps.insert(#name_str.to_string(), __bump); + }, + quote! { + &[ + #maybe_seeds_plus_comma + &[__bump][..] + ][..] + }, + ) } }; From 1c49c31b0b173cf8b27a09d14c45b2199ebd2fa7 Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Thu, 27 Jan 2022 15:47:37 -0500 Subject: [PATCH 12/16] update --- lang/syn/src/codegen/accounts/constraints.rs | 57 +++++++++----------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index c05f5f875e..41bafc68ef 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/lang/syn/src/codegen/accounts/constraints.rs @@ -585,6 +585,9 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2 // 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! { @@ -598,6 +601,9 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2 } // 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 { @@ -607,42 +613,29 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2 } // No init. So we just check the address. else { - let maybe_seeds_plus_comma = (!s.is_empty()).then(|| { - quote! { #s, } - }); - let (bump, seeds) = match c.bump.as_ref() { + let define_pda = match c.bump.as_ref() { // Bump target not given. Find it. - None => ( - quote! { - let __bump = Pubkey::find_program_address( - &[#s], - &#deriving_program_id, - ).1; - __bumps.insert(#name_str.to_string(), __bump); - }, - quote! { - [ - #maybe_seeds_plus_comma - &[__bump][..] - ] - }, - ), + None => quote! { + let (__pda_address, __bump) = Pubkey::find_program_address( + &[#s], + &#deriving_program_id, + ); + __bumps.insert(#name_str.to_string(), __bump); + }, // Bump target given. Use it. - Some(b) => ( - quote! {}, - quote! { - [#maybe_seeds_plus_comma &[#b][..]] - }, - ), + Some(b) => quote! { + let __pda_address = Pubkey::create_program_address( + &[#s, &[#b][..]], + &#deriving_program_id, + ).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?; + }, }; quote! { - // Define the bump. - #bump - let __program_signer = Pubkey::create_program_address( - &#seeds[..], - &#deriving_program_id, - ).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?; - if #name.key() != __program_signer { + // Define the PDA. + #define_pda + + // Check it. + if #name.key() != __pda_address { return Err(anchor_lang::__private::ErrorCode::ConstraintSeeds.into()); } } From 82845c1252a856eb1c81f12e751cfb8fa6724e74 Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Thu, 27 Jan 2022 15:50:03 -0500 Subject: [PATCH 13/16] update --- lang/syn/src/codegen/accounts/constraints.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index 41bafc68ef..7ecc6c4b93 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/lang/syn/src/codegen/accounts/constraints.rs @@ -310,10 +310,6 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma seeds.push_value(pair.into_value()); } - let maybe_seeds_plus_comma = (!seeds.is_empty()).then(|| { - quote! { #seeds, } - }); - ( quote! { let (__pda_address, __bump) = Pubkey::find_program_address( @@ -324,7 +320,7 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma }, quote! { &[ - #maybe_seeds_plus_comma + #seeds, &[__bump][..] ][..] }, From d6b09c0ad35bcde43bd6bc3b235a6ef68d530498 Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Thu, 27 Jan 2022 16:01:05 -0500 Subject: [PATCH 14/16] untabify --- lang/syn/src/parser/accounts/constraints.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/syn/src/parser/accounts/constraints.rs b/lang/syn/src/parser/accounts/constraints.rs index fab8087516..18d9b4f451 100644 --- a/lang/syn/src/parser/accounts/constraints.rs +++ b/lang/syn/src/parser/accounts/constraints.rs @@ -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()), From aeaf7f768e6019f4fe7d6de35663ce1697785067 Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Thu, 27 Jan 2022 16:14:07 -0500 Subject: [PATCH 15/16] update --- lang/syn/src/codegen/accounts/constraints.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index 7ecc6c4b93..b8c1b65580 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/lang/syn/src/codegen/accounts/constraints.rs @@ -310,17 +310,21 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma seeds.push_value(pair.into_value()); } + let maybe_seeds_plus_comma = (!seeds.is_empty()).then(|| { + quote! { #seeds, } + }); + ( quote! { let (__pda_address, __bump) = Pubkey::find_program_address( - &[#seeds], + &[#maybe_seeds_plus_comma], program_id, ); __bumps.insert(#name_str.to_string(), __bump); }, quote! { &[ - #seeds, + #maybe_seeds_plus_comma &[__bump][..] ][..] }, @@ -609,11 +613,14 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2 } // 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( - &[#s], + &[#maybe_seeds_plus_comma], &#deriving_program_id, ); __bumps.insert(#name_str.to_string(), __bump); @@ -621,7 +628,7 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2 // Bump target given. Use it. Some(b) => quote! { let __pda_address = Pubkey::create_program_address( - &[#s, &[#b][..]], + &[#maybe_seeds_plus_comma &[#b][..]], &#deriving_program_id, ).map_err(|_| anchor_lang::__private::ErrorCode::ConstraintSeeds)?; }, From da3f711fa616adb967f365ef148dd06a4f44f08b Mon Sep 17 00:00:00 2001 From: Armani Ferrante Date: Thu, 27 Jan 2022 16:52:47 -0500 Subject: [PATCH 16/16] Changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 070c67392b..56ad1f91ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` 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` 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