diff --git a/lang/src/accounts/account.rs b/lang/src/accounts/account.rs index 9e53452cd6..e86ba8823a 100644 --- a/lang/src/accounts/account.rs +++ b/lang/src/accounts/account.rs @@ -10,7 +10,7 @@ use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; use solana_program::pubkey::Pubkey; use solana_program::system_program; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use std::fmt; use std::ops::{Deref, DerefMut}; @@ -310,7 +310,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + crate::Owner + Clone> Accoun } } -impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> Accounts<'info> +impl<'info, B, T: AccountSerialize + AccountDeserialize + Owner + Clone> Accounts<'info, B> for Account<'info, T> where T: AccountSerialize + AccountDeserialize + Owner + Clone, @@ -320,7 +320,7 @@ where _program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], - _bumps: &mut BTreeMap, + _bumps: &mut B, _reallocs: &mut BTreeSet, ) -> Result { if accounts.is_empty() { diff --git a/lang/src/accounts/account_info.rs b/lang/src/accounts/account_info.rs index 54d6cc80b5..56922a78a5 100644 --- a/lang/src/accounts/account_info.rs +++ b/lang/src/accounts/account_info.rs @@ -7,14 +7,14 @@ use crate::{Accounts, AccountsExit, Key, Result, ToAccountInfos, ToAccountMetas} use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; use solana_program::pubkey::Pubkey; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; -impl<'info> Accounts<'info> for AccountInfo<'info> { +impl<'info, B> Accounts<'info, B> for AccountInfo<'info> { fn try_accounts( _program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], - _bumps: &mut BTreeMap, + _bumps: &mut B, _reallocs: &mut BTreeSet, ) -> Result { if accounts.is_empty() { diff --git a/lang/src/accounts/account_loader.rs b/lang/src/accounts/account_loader.rs index 73d8c22a03..dbee6cb23b 100644 --- a/lang/src/accounts/account_loader.rs +++ b/lang/src/accounts/account_loader.rs @@ -11,7 +11,7 @@ use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; use solana_program::pubkey::Pubkey; use std::cell::{Ref, RefMut}; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use std::fmt; use std::io::Write; use std::marker::PhantomData; @@ -214,13 +214,13 @@ impl<'info, T: ZeroCopy + Owner> AccountLoader<'info, T> { } } -impl<'info, T: ZeroCopy + Owner> Accounts<'info> for AccountLoader<'info, T> { +impl<'info, B, T: ZeroCopy + Owner> Accounts<'info, B> for AccountLoader<'info, T> { #[inline(never)] fn try_accounts( _program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], - _bumps: &mut BTreeMap, + _bumps: &mut B, _reallocs: &mut BTreeSet, ) -> Result { if accounts.is_empty() { diff --git a/lang/src/accounts/boxed.rs b/lang/src/accounts/boxed.rs index b143024ead..6905ab2854 100644 --- a/lang/src/accounts/boxed.rs +++ b/lang/src/accounts/boxed.rs @@ -17,15 +17,15 @@ use crate::{Accounts, AccountsClose, AccountsExit, Result, ToAccountInfos, ToAcc use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; use solana_program::pubkey::Pubkey; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use std::ops::Deref; -impl<'info, T: Accounts<'info>> Accounts<'info> for Box { +impl<'info, B, T: Accounts<'info, B>> Accounts<'info, B> for Box { fn try_accounts( program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>], ix_data: &[u8], - bumps: &mut BTreeMap, + bumps: &mut B, reallocs: &mut BTreeSet, ) -> Result { T::try_accounts(program_id, accounts, ix_data, bumps, reallocs).map(Box::new) diff --git a/lang/src/accounts/cpi_account.rs b/lang/src/accounts/cpi_account.rs index ee7c907615..79b724a0d5 100644 --- a/lang/src/accounts/cpi_account.rs +++ b/lang/src/accounts/cpi_account.rs @@ -3,7 +3,6 @@ use crate::{error::ErrorCode, prelude::Account}; use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; use solana_program::pubkey::Pubkey; -use std::collections::BTreeMap; use std::ops::{Deref, DerefMut}; /// Container for any account *not* owned by the current program. @@ -43,7 +42,7 @@ impl<'a, T: AccountDeserialize + Clone> CpiAccount<'a, T> { } #[allow(deprecated)] -impl<'info, T> Accounts<'info> for CpiAccount<'info, T> +impl<'info, B, T> Accounts<'info, B> for CpiAccount<'info, T> where T: AccountDeserialize + Clone, { @@ -52,7 +51,7 @@ where _program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], - _bumps: &mut BTreeMap, + _bumps: &mut B, _reallocs: &mut BTreeSet, ) -> Result { if accounts.is_empty() { diff --git a/lang/src/accounts/cpi_state.rs b/lang/src/accounts/cpi_state.rs index 42150d2c89..0373a5a36c 100644 --- a/lang/src/accounts/cpi_state.rs +++ b/lang/src/accounts/cpi_state.rs @@ -8,7 +8,7 @@ use crate::{ use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; use solana_program::pubkey::Pubkey; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use std::ops::{Deref, DerefMut}; /// Boxed container for the program state singleton, used when the state @@ -52,17 +52,17 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Clone> CpiState<'info, T> } /// Convenience api for creating a `CpiStateContext`. - pub fn context<'a, 'b, 'c, A: Accounts<'info>>( + pub fn context<'a, 'b, 'c, B, A: Accounts<'info, B>>( &self, program: AccountInfo<'info>, accounts: A, - ) -> CpiStateContext<'a, 'b, 'c, 'info, A> { + ) -> CpiStateContext<'a, 'b, 'c, 'info, B, A> { CpiStateContext::new(program, self.inner.info.clone(), accounts) } } #[allow(deprecated)] -impl<'info, T> Accounts<'info> for CpiState<'info, T> +impl<'info, B, T> Accounts<'info, B> for CpiState<'info, T> where T: AccountSerialize + AccountDeserialize + Clone, { @@ -71,7 +71,7 @@ where _program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], - _bumps: &mut BTreeMap, + _bumps: &mut B, _reallocs: &mut BTreeSet, ) -> Result { if accounts.is_empty() { diff --git a/lang/src/accounts/loader.rs b/lang/src/accounts/loader.rs index 1d52207d58..190994d832 100644 --- a/lang/src/accounts/loader.rs +++ b/lang/src/accounts/loader.rs @@ -9,7 +9,7 @@ use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; use solana_program::pubkey::Pubkey; use std::cell::{Ref, RefMut}; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use std::fmt; use std::io::Write; use std::marker::PhantomData; @@ -156,13 +156,13 @@ impl<'info, T: ZeroCopy> Loader<'info, T> { } #[allow(deprecated)] -impl<'info, T: ZeroCopy> Accounts<'info> for Loader<'info, T> { +impl<'info, B, T: ZeroCopy> Accounts<'info, B> for Loader<'info, T> { #[inline(never)] fn try_accounts( program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], - _bumps: &mut BTreeMap, + _bumps: &mut B, _reallocs: &mut BTreeSet, ) -> Result { if accounts.is_empty() { diff --git a/lang/src/accounts/program.rs b/lang/src/accounts/program.rs index d7e9975f89..26359d6a12 100644 --- a/lang/src/accounts/program.rs +++ b/lang/src/accounts/program.rs @@ -8,7 +8,7 @@ use solana_program::account_info::AccountInfo; use solana_program::bpf_loader_upgradeable::{self, UpgradeableLoaderState}; use solana_program::instruction::AccountMeta; use solana_program::pubkey::Pubkey; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use std::fmt; use std::marker::PhantomData; use std::ops::Deref; @@ -137,7 +137,7 @@ impl<'a, T: Id + Clone> Program<'a, T> { } } -impl<'info, T> Accounts<'info> for Program<'info, T> +impl<'info, B, T> Accounts<'info, B> for Program<'info, T> where T: Id + Clone, { @@ -146,7 +146,7 @@ where _program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], - _bumps: &mut BTreeMap, + _bumps: &mut B, _reallocs: &mut BTreeSet, ) -> Result { if accounts.is_empty() { diff --git a/lang/src/accounts/program_account.rs b/lang/src/accounts/program_account.rs index db6baffc72..489acb436c 100644 --- a/lang/src/accounts/program_account.rs +++ b/lang/src/accounts/program_account.rs @@ -9,7 +9,7 @@ use crate::{ use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; use solana_program::pubkey::Pubkey; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use std::ops::{Deref, DerefMut}; /// Boxed container for a deserialized `account`. Use this to reference any @@ -73,7 +73,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramAccount<'a, T> } #[allow(deprecated)] -impl<'info, T> Accounts<'info> for ProgramAccount<'info, T> +impl<'info, B, T> Accounts<'info, B> for ProgramAccount<'info, T> where T: AccountSerialize + AccountDeserialize + Clone, { @@ -82,7 +82,7 @@ where program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], - _bumps: &mut BTreeMap, + _bumps: &mut B, _reallocs: &mut BTreeSet, ) -> Result { if accounts.is_empty() { diff --git a/lang/src/accounts/signer.rs b/lang/src/accounts/signer.rs index 7d757024a3..6ce5c22394 100644 --- a/lang/src/accounts/signer.rs +++ b/lang/src/accounts/signer.rs @@ -4,7 +4,7 @@ use crate::{Accounts, AccountsExit, Key, Result, ToAccountInfos, ToAccountMetas} use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; use solana_program::pubkey::Pubkey; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use std::ops::Deref; /// Type validating that the account signed the transaction. No other ownership @@ -54,13 +54,13 @@ impl<'info> Signer<'info> { } } -impl<'info> Accounts<'info> for Signer<'info> { +impl<'info, B> Accounts<'info, B> for Signer<'info> { #[inline(never)] fn try_accounts( _program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], - _bumps: &mut BTreeMap, + _bumps: &mut B, _reallocs: &mut BTreeSet, ) -> Result { if accounts.is_empty() { diff --git a/lang/src/accounts/state.rs b/lang/src/accounts/state.rs index 563409fdb5..129574960b 100644 --- a/lang/src/accounts/state.rs +++ b/lang/src/accounts/state.rs @@ -9,7 +9,7 @@ use crate::{ use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; use solana_program::pubkey::Pubkey; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use std::ops::{Deref, DerefMut}; pub const PROGRAM_STATE_SEED: &str = "unversioned"; @@ -64,7 +64,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Clone> ProgramState<'a, T> { } #[allow(deprecated)] -impl<'info, T> Accounts<'info> for ProgramState<'info, T> +impl<'info, B, T> Accounts<'info, B> for ProgramState<'info, T> where T: AccountSerialize + AccountDeserialize + Clone, { @@ -73,7 +73,7 @@ where program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], - _bumps: &mut BTreeMap, + _bumps: &mut B, _reallocs: &mut BTreeSet, ) -> Result { if accounts.is_empty() { diff --git a/lang/src/accounts/system_account.rs b/lang/src/accounts/system_account.rs index 8c90fd537c..66c786e0e0 100644 --- a/lang/src/accounts/system_account.rs +++ b/lang/src/accounts/system_account.rs @@ -6,7 +6,7 @@ use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; use solana_program::pubkey::Pubkey; use solana_program::system_program; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use std::ops::Deref; /// Type validating that the account is owned by the system program @@ -33,13 +33,13 @@ impl<'info> SystemAccount<'info> { } } -impl<'info> Accounts<'info> for SystemAccount<'info> { +impl<'info, B> Accounts<'info, B> for SystemAccount<'info> { #[inline(never)] fn try_accounts( _program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], - _bumps: &mut BTreeMap, + _bumps: &mut B, _reallocs: &mut BTreeSet, ) -> Result { if accounts.is_empty() { diff --git a/lang/src/accounts/sysvar.rs b/lang/src/accounts/sysvar.rs index c955ff9c15..7edfcf6223 100644 --- a/lang/src/accounts/sysvar.rs +++ b/lang/src/accounts/sysvar.rs @@ -5,7 +5,7 @@ use crate::{Accounts, AccountsExit, Key, Result, ToAccountInfos, ToAccountMetas} use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; use solana_program::pubkey::Pubkey; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use std::fmt; use std::ops::{Deref, DerefMut}; @@ -65,12 +65,12 @@ impl<'info, T: solana_program::sysvar::Sysvar> Clone for Sysvar<'info, T> { } } -impl<'info, T: solana_program::sysvar::Sysvar> Accounts<'info> for Sysvar<'info, T> { +impl<'info, B, T: solana_program::sysvar::Sysvar> Accounts<'info, B> for Sysvar<'info, T> { fn try_accounts( _program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], - _bumps: &mut BTreeMap, + _bumps: &mut B, _reallocs: &mut BTreeSet, ) -> Result { if accounts.is_empty() { diff --git a/lang/src/accounts/unchecked_account.rs b/lang/src/accounts/unchecked_account.rs index 5a00127579..562690d919 100644 --- a/lang/src/accounts/unchecked_account.rs +++ b/lang/src/accounts/unchecked_account.rs @@ -6,7 +6,7 @@ use crate::{Accounts, AccountsExit, Key, Result, ToAccountInfos, ToAccountMetas} use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; use solana_program::pubkey::Pubkey; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use std::ops::Deref; /// Explicit wrapper for AccountInfo types to emphasize @@ -20,12 +20,12 @@ impl<'info> UncheckedAccount<'info> { } } -impl<'info> Accounts<'info> for UncheckedAccount<'info> { +impl<'info, B> Accounts<'info, B> for UncheckedAccount<'info> { fn try_accounts( _program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>], _ix_data: &[u8], - _bumps: &mut BTreeMap, + _bumps: &mut B, _reallocs: &mut BTreeSet, ) -> Result { if accounts.is_empty() { diff --git a/lang/src/context.rs b/lang/src/context.rs index f007bc5102..a332cbfcf1 100644 --- a/lang/src/context.rs +++ b/lang/src/context.rs @@ -4,8 +4,8 @@ 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; +use std::marker::PhantomData; /// Provides non-argument inputs to the program. /// @@ -22,7 +22,7 @@ use std::fmt; /// Ok(()) /// } /// ``` -pub struct Context<'a, 'b, 'c, 'info, T> { +pub struct Context<'a, 'b, 'c, 'info, B, T> { /// Currently executing program id. pub program_id: &'a Pubkey, /// Deserialized accounts. @@ -33,10 +33,13 @@ pub struct Context<'a, 'b, 'c, 'info, T> { /// 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, + /// Type is the bumps struct generated by #[derive(Accounts)] + pub bumps: B, } -impl<'a, 'b, 'c, 'info, T: fmt::Debug> fmt::Debug for Context<'a, 'b, 'c, 'info, T> { +impl<'a, 'b, 'c, 'info, B: fmt::Debug, T: fmt::Debug> fmt::Debug + for Context<'a, 'b, 'c, 'info, B, T> +{ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Context") .field("program_id", &self.program_id) @@ -47,12 +50,12 @@ impl<'a, 'b, 'c, 'info, T: fmt::Debug> fmt::Debug for Context<'a, 'b, 'c, 'info, } } -impl<'a, 'b, 'c, 'info, T: Accounts<'info>> Context<'a, 'b, 'c, 'info, T> { +impl<'a, 'b, 'c, 'info, B, T: Accounts<'info, B>> Context<'a, 'b, 'c, 'info, B, T> { pub fn new( program_id: &'a Pubkey, accounts: &'b mut T, remaining_accounts: &'c [AccountInfo<'info>], - bumps: BTreeMap, + bumps: B, ) -> Self { Self { program_id, @@ -246,13 +249,14 @@ impl<'info, T: ToAccountInfos<'info> + ToAccountMetas> ToAccountMetas /// targeted at program state instructions. #[doc(hidden)] #[deprecated] -pub struct CpiStateContext<'a, 'b, 'c, 'info, T: Accounts<'info>> { +pub struct CpiStateContext<'a, 'b, 'c, 'info, B, T: Accounts<'info, B>> { state: AccountInfo<'info>, cpi_ctx: CpiContext<'a, 'b, 'c, 'info, T>, + _bumps: PhantomData, } #[allow(deprecated)] -impl<'a, 'b, 'c, 'info, T: Accounts<'info>> CpiStateContext<'a, 'b, 'c, 'info, T> { +impl<'a, 'b, 'c, 'info, B, T: Accounts<'info, B>> CpiStateContext<'a, 'b, 'c, 'info, B, T> { pub fn new(program: AccountInfo<'info>, state: AccountInfo<'info>, accounts: T) -> Self { Self { state, @@ -262,6 +266,7 @@ impl<'a, 'b, 'c, 'info, T: Accounts<'info>> CpiStateContext<'a, 'b, 'c, 'info, T signer_seeds: &[], remaining_accounts: Vec::new(), }, + _bumps: PhantomData, } } @@ -279,6 +284,7 @@ impl<'a, 'b, 'c, 'info, T: Accounts<'info>> CpiStateContext<'a, 'b, 'c, 'info, T signer_seeds, remaining_accounts: Vec::new(), }, + _bumps: PhantomData, } } @@ -298,8 +304,8 @@ impl<'a, 'b, 'c, 'info, T: Accounts<'info>> CpiStateContext<'a, 'b, 'c, 'info, T } #[allow(deprecated)] -impl<'a, 'b, 'c, 'info, T: Accounts<'info>> ToAccountMetas - for CpiStateContext<'a, 'b, 'c, 'info, T> +impl<'a, 'b, 'c, 'info, B, T: Accounts<'info, B>> ToAccountMetas + for CpiStateContext<'a, 'b, 'c, 'info, B, T> { fn to_account_metas(&self, is_signer: Option) -> Vec { // State account is always first for state instructions. @@ -313,8 +319,8 @@ impl<'a, 'b, 'c, 'info, T: Accounts<'info>> ToAccountMetas } #[allow(deprecated)] -impl<'a, 'b, 'c, 'info, T: Accounts<'info>> ToAccountInfos<'info> - for CpiStateContext<'a, 'b, 'c, 'info, T> +impl<'a, 'b, 'c, 'info, B, T: Accounts<'info, B>> ToAccountInfos<'info> + for CpiStateContext<'a, 'b, 'c, 'info, B, T> { fn to_account_infos(&self) -> Vec> { let mut infos = self.cpi_ctx.accounts.to_account_infos(); diff --git a/lang/src/idl.rs b/lang/src/idl.rs index 8bbd89316a..74e43865f8 100644 --- a/lang/src/idl.rs +++ b/lang/src/idl.rs @@ -22,6 +22,9 @@ use crate::accounts::program_account::ProgramAccount; use crate::prelude::*; use solana_program::pubkey::Pubkey; +// re-export CtorBumps +pub use crate::ctor::CtorBumps; + // The first 8 bytes of an instruction to create or modify the IDL account. This // instruction is defined outside the main program's instruction enum, so that // the enum variant tags can align with function source order. diff --git a/lang/src/lib.rs b/lang/src/lib.rs index 65147d87de..59ba7114b7 100644 --- a/lang/src/lib.rs +++ b/lang/src/lib.rs @@ -27,7 +27,7 @@ use bytemuck::{Pod, Zeroable}; use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; use solana_program::pubkey::Pubkey; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use std::io::Write; mod account_meta; @@ -65,7 +65,30 @@ pub type Result = std::result::Result; /// maintain any invariants required for the program to run securely. In most /// cases, it's recommended to use the [`Accounts`](./derive.Accounts.html) /// derive macro to implement this trait. -pub trait Accounts<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized { +/// +/// Generics: +/// - `B`: the type of the PDA bumps cache struct generated by the `Accounts` struct. +/// For example, +/// ```rust,ignore +/// pub struct Example<'info> { +/// #[account( +/// init, +/// seeds = [...], +/// bump, +/// )] +/// pub pda_1: AccountInfo<'info>, +/// pub not_pda: AccountInfo<'info>, +/// } +/// ``` +/// +/// generates: +/// +/// ```rust,ignore +/// pub struct ExampleBumps { +/// pub pda_1: Option, +/// } +/// ``` +pub trait Accounts<'info, B>: ToAccountMetas + ToAccountInfos<'info> + Sized { /// Returns the validated accounts struct. What constitutes "valid" is /// program dependent. However, users of these types should never have to /// worry about account substitution attacks. For example, if a program @@ -81,7 +104,7 @@ pub trait Accounts<'info>: ToAccountMetas + ToAccountInfos<'info> + Sized { program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>], ix_data: &[u8], - bumps: &mut BTreeMap, + bumps: &mut B, reallocs: &mut BTreeSet, ) -> Result; } diff --git a/lang/src/vec.rs b/lang/src/vec.rs index 44c132be6b..150cd570f8 100644 --- a/lang/src/vec.rs +++ b/lang/src/vec.rs @@ -2,7 +2,7 @@ use crate::{Accounts, Result, ToAccountInfos, ToAccountMetas}; use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; use solana_program::pubkey::Pubkey; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; impl<'info, T: ToAccountInfos<'info>> ToAccountInfos<'info> for Vec { fn to_account_infos(&self) -> Vec> { @@ -20,12 +20,12 @@ impl ToAccountMetas for Vec { } } -impl<'info, T: Accounts<'info>> Accounts<'info> for Vec { +impl<'info, B, T: Accounts<'info, B>> Accounts<'info, B> for Vec { fn try_accounts( program_id: &Pubkey, accounts: &mut &[AccountInfo<'info>], ix_data: &[u8], - bumps: &mut BTreeMap, + bumps: &mut B, reallocs: &mut BTreeSet, ) -> Result { let mut vec: Vec = Vec::new(); @@ -79,7 +79,7 @@ mod tests { false, Epoch::default(), ); - let mut bumps = std::collections::BTreeMap::new(); + let mut bumps = TestBumps::default(); let mut reallocs = std::collections::BTreeSet::new(); let mut accounts = &[account1, account2][..]; let parsed_accounts = @@ -93,7 +93,7 @@ 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 bumps = TestBumps::default(); let mut reallocs = std::collections::BTreeSet::new(); let mut accounts = &[][..]; Vec::::try_accounts(&program_id, &mut accounts, &[], &mut bumps, &mut reallocs) diff --git a/lang/syn/src/codegen/accounts/bumps.rs b/lang/syn/src/codegen/accounts/bumps.rs new file mode 100644 index 0000000000..c08cd37d87 --- /dev/null +++ b/lang/syn/src/codegen/accounts/bumps.rs @@ -0,0 +1,54 @@ +use crate::*; + +use super::*; + +pub fn generate_bumps_name(anchor_ident: &Ident) -> Ident { + Ident::new(&format!("{}Bumps", anchor_ident), Span::call_site()) +} + +pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { + let bumps_name = generate_bumps_name(&accs.ident); + + let bump_fields: Vec = accs + .fields + .iter() + .filter_map(|af| { + let constraints = match af { + AccountField::Field(f) => constraints::linearize(&f.constraints), + AccountField::CompositeField(s) => constraints::linearize(&s.constraints), + }; + let ident = af.ident(); + let bump_field = quote! { + pub #ident: u8 + }; + for c in constraints.iter() { + // Verify this in super::constraints + // The bump is only cached if + // - PDA is marked as init + // - PDA is not init, but marked with bump without a target + + match c { + Constraint::Seeds(c) => { + if !c.is_init && c.bump.is_none() { + return Some(bump_field); + } + } + Constraint::Init(c) => { + if c.seeds.is_some() { + return Some(bump_field); + } + } + _ => (), + } + } + None + }) + .collect(); + + quote! { + #[derive(Default, Debug)] + pub struct #bumps_name { + #(#bump_fields),* + } + } +} diff --git a/lang/syn/src/codegen/accounts/constraints.rs b/lang/syn/src/codegen/accounts/constraints.rs index d8286eb79a..309957bbd0 100644 --- a/lang/syn/src/codegen/accounts/constraints.rs +++ b/lang/syn/src/codegen/accounts/constraints.rs @@ -423,7 +423,7 @@ fn generate_constraint_init_group(f: &Field, c: &ConstraintInitGroup) -> proc_ma &[#maybe_seeds_plus_comma], program_id, ); - __bumps.insert(#name_str.to_string(), __bump); + __bumps.#field = __bump; }, quote! { &[ @@ -708,7 +708,7 @@ fn generate_constraint_seeds(f: &Field, c: &ConstraintSeedsGroup) -> proc_macro2 &[#maybe_seeds_plus_comma], &#deriving_program_id, ); - __bumps.insert(#name_str.to_string(), __bump); + __bumps.#name = __bump; }, // Bump target given. Use it. Some(b) => quote! { diff --git a/lang/syn/src/codegen/accounts/mod.rs b/lang/syn/src/codegen/accounts/mod.rs index 3a239cbe33..5b9870fb68 100644 --- a/lang/syn/src/codegen/accounts/mod.rs +++ b/lang/syn/src/codegen/accounts/mod.rs @@ -7,6 +7,7 @@ use syn::{GenericParam, PredicateLifetime, WhereClause, WherePredicate}; mod __client_accounts; mod __cpi_client_accounts; +pub mod bumps; mod constraints; mod exit; mod to_account_infos; @@ -22,6 +23,8 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { let __client_accounts_mod = __client_accounts::generate(accs); let __cpi_client_accounts_mod = __cpi_client_accounts::generate(accs); + let bumps_struct = bumps::generate(accs); + quote! { #impl_try_accounts #impl_to_account_infos @@ -30,6 +33,8 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { #__client_accounts_mod #__cpi_client_accounts_mod + + #bumps_struct } } diff --git a/lang/syn/src/codegen/accounts/try_accounts.rs b/lang/syn/src/codegen/accounts/try_accounts.rs index 0cacf9c863..d710f782f2 100644 --- a/lang/syn/src/codegen/accounts/try_accounts.rs +++ b/lang/syn/src/codegen/accounts/try_accounts.rs @@ -1,4 +1,4 @@ -use crate::codegen::accounts::{constraints, generics, ParsedGenerics}; +use crate::codegen::accounts::{bumps, constraints, generics, ParsedGenerics}; use crate::{AccountField, AccountsStruct}; use quote::quote; use syn::Expr; @@ -89,15 +89,17 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { } }; + let bumps_stuct_name = bumps::generate_bumps_name(&accs.ident); + quote! { #[automatically_derived] - impl<#combined_generics> anchor_lang::Accounts<#trait_generics> for #name<#struct_generics> #where_clause { + impl<#combined_generics> anchor_lang::Accounts<#trait_generics, #bumps_stuct_name> for #name<#struct_generics> #where_clause { #[inline(never)] fn try_accounts( 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, + __bumps: &mut #bumps_stuct_name, __reallocs: &mut std::collections::BTreeSet, ) -> anchor_lang::Result { // Deserialize instruction, if declared. diff --git a/lang/syn/src/codegen/program/handlers.rs b/lang/syn/src/codegen/program/handlers.rs index f965bd3f62..5c811d36f2 100644 --- a/lang/syn/src/codegen/program/handlers.rs +++ b/lang/syn/src/codegen/program/handlers.rs @@ -1,4 +1,4 @@ -use crate::codegen::program::common::*; +use crate::codegen::{accounts::bumps, program::common::*}; use crate::{Program, State}; use heck::CamelCase; use quote::{quote, ToTokens}; @@ -25,7 +25,7 @@ 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 bumps = anchor_lang::idl::CtorBumps::default(); let mut reallocs = std::collections::BTreeSet::new(); let mut accounts = anchor_lang::idl::IdlCreateAccounts::try_accounts(program_id, &mut accounts, &[], &mut bumps, &mut reallocs)?; @@ -33,7 +33,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { accounts.exit(program_id)?; }, anchor_lang::idl::IdlInstruction::CreateBuffer => { - let mut bumps = std::collections::BTreeMap::new(); + let mut bumps = anchor_lang::idl::IdlCreateBufferBumps::default(); let mut reallocs = std::collections::BTreeSet::new(); let mut accounts = anchor_lang::idl::IdlCreateBuffer::try_accounts(program_id, &mut accounts, &[], &mut bumps, &mut reallocs)?; @@ -41,7 +41,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { accounts.exit(program_id)?; }, anchor_lang::idl::IdlInstruction::Write { data } => { - let mut bumps = std::collections::BTreeMap::new(); + let mut bumps = anchor_lang::idl::IdlAccountsBumps::default(); let mut reallocs = std::collections::BTreeSet::new(); let mut accounts = anchor_lang::idl::IdlAccounts::try_accounts(program_id, &mut accounts, &[], &mut bumps, &mut reallocs)?; @@ -49,7 +49,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { accounts.exit(program_id)?; }, anchor_lang::idl::IdlInstruction::SetAuthority { new_authority } => { - let mut bumps = std::collections::BTreeMap::new(); + let mut bumps = anchor_lang::idl::IdlAccountsBumps::default(); let mut reallocs = std::collections::BTreeSet::new(); let mut accounts = anchor_lang::idl::IdlAccounts::try_accounts(program_id, &mut accounts, &[], &mut bumps, &mut reallocs)?; @@ -57,7 +57,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { accounts.exit(program_id)?; }, anchor_lang::idl::IdlInstruction::SetBuffer => { - let mut bumps = std::collections::BTreeMap::new(); + let mut bumps = anchor_lang::idl::IdlSetBufferBumps::default(); let mut reallocs = std::collections::BTreeSet::new(); let mut accounts = anchor_lang::idl::IdlSetBuffer::try_accounts(program_id, &mut accounts, &[], &mut bumps, &mut reallocs)?; @@ -206,6 +206,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { let ix_name: proc_macro2::TokenStream = generate_ctor_variant_name().parse().unwrap(); let ix_name_log = format!("Instruction: {}", ix_name); + let bumps_struct = bumps::generate_bumps_name(anchor_ident); if state.is_zero_copy { quote! { // One time state account initializer. Will faill on subsequent @@ -220,7 +221,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { .map_err(|_| anchor_lang::error::ErrorCode::InstructionDidNotDeserialize)?; let instruction::state::#variant_arm = ix; - let mut __bumps = std::collections::BTreeMap::new(); + let mut __bumps = #bumps_struct::default(); let mut __reallocs = std::collections::BTreeSet::new(); // Deserialize accounts. @@ -300,7 +301,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { .map_err(|_| anchor_lang::error::ErrorCode::InstructionDidNotDeserialize)?; let instruction::state::#variant_arm = ix; - let mut __bumps = std::collections::BTreeMap::new(); + let mut __bumps = #bumps_struct::default(); let mut __reallocs = std::collections::BTreeSet::new(); // Deserialize accounts. @@ -385,6 +386,8 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { let ix_method_name = &ix.raw_method.sig.ident; let state_ty: proc_macro2::TokenStream = state.name.parse().unwrap(); let anchor_ident = &ix.anchor_ident; + let bumps_struct = bumps::generate_bumps_name(anchor_ident); + let name = &state.strct.ident; let mod_name = &program.name; @@ -410,7 +413,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { let instruction::state::#variant_arm = ix; // Bump collector. - let mut __bumps = std::collections::BTreeMap::new(); + let mut __bumps = #bumps_struct::default(); // Realloc tracker let mut __reallocs= std::collections::BTreeSet::new(); @@ -470,7 +473,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { let instruction::state::#variant_arm = ix; // Bump collector. - let mut __bumps = std::collections::BTreeMap::new(); + let mut __bumps = #bumps_struct::default(); // Realloc tracker. let mut __reallocs = std::collections::BTreeSet::new(); @@ -555,6 +558,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { let ix_method_name = &ix.raw_method.sig.ident; let state_ty: proc_macro2::TokenStream = state.name.parse().unwrap(); let anchor_ident = &ix.anchor_ident; + let bumps_struct = bumps::generate_bumps_name(anchor_ident); let ix_name = generate_ix_variant_name(ix.raw_method.sig.ident.to_string()); let ix_name_log = format!("Instruction: {}", ix_name); @@ -603,7 +607,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { #deserialize_instruction // Bump collector. - let mut __bumps = std::collections::BTreeMap::new(); + let mut __bumps = #bumps_struct::default(); // Realloc tracker. let mut __reallocs= std::collections::BTreeSet::new(); @@ -670,7 +674,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { #deserialize_instruction // Bump collector. - let mut __bumps = std::collections::BTreeMap::new(); + let mut __bumps = #bumps_struct::default(); let mut __reallocs = std::collections::BTreeSet::new(); @@ -716,6 +720,8 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { let ix_name = generate_ix_variant_name(ix.raw_method.sig.ident.to_string()); let ix_method_name = &ix.raw_method.sig.ident; let anchor = &ix.anchor_ident; + let bumps_struct = bumps::generate_bumps_name(anchor); + let variant_arm = generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args); let ix_name_log = format!("Instruction: {}", ix_name); let ret_type = &ix.returns.ty.to_token_stream(); @@ -741,7 +747,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { let instruction::#variant_arm = ix; // Bump collector. - let mut __bumps = std::collections::BTreeMap::new(); + let mut __bumps = #bumps_struct::default(); let mut __reallocs = std::collections::BTreeSet::new(); diff --git a/lang/syn/src/idl/file.rs b/lang/syn/src/idl/file.rs index 23e813aa52..46dc38c3f1 100644 --- a/lang/syn/src/idl/file.rs +++ b/lang/syn/src/idl/file.rs @@ -189,7 +189,7 @@ pub fn parse( let instructions = p .ixs .iter() - .map(|ix| { + .filter_map(|ix| { let args = ix .args .iter() @@ -206,21 +206,24 @@ pub fn parse( } }) .collect::>(); - // todo: don't unwrap - let accounts_strct = accs.get(&ix.anchor_ident.to_string()).unwrap(); + let anchor_ident_str = ix.anchor_ident.to_string(); + let accounts_strct = accs.get(&anchor_ident_str).or_else(|| { + eprintln!("failed to get account metadata for {}", anchor_ident_str); + None + })?; let accounts = idl_accounts(&ctx, accounts_strct, &accs, seeds_feature, no_docs); let ret_type_str = ix.returns.ty.to_token_stream().to_string(); let returns = match ret_type_str.as_str() { "()" => None, _ => Some(ret_type_str.parse().unwrap()), }; - IdlInstruction { + Some(IdlInstruction { name: ix.ident.to_string().to_mixed_case(), docs: ix.docs.clone(), accounts, args, returns, - } + }) }) .collect::>(); diff --git a/lang/syn/src/parser/program/mod.rs b/lang/syn/src/parser/program/mod.rs index 87c298437a..171e73af89 100644 --- a/lang/syn/src/parser/program/mod.rs +++ b/lang/syn/src/parser/program/mod.rs @@ -34,13 +34,17 @@ fn ctx_accounts_ident(path_ty: &syn::PatType) -> ParseResult syn::PathArguments::AngleBracketed(args) => args, _ => return Err(ParseError::new(path_ty.span(), "missing accounts context")), }; - let generic_ty = generic_args - .args - .iter() - .filter_map(|arg| match arg { - syn::GenericArgument::Type(ty) => Some(ty), - _ => None, - }) + + let mut generic_args_iter = generic_args.args.iter().filter_map(|arg| match arg { + syn::GenericArgument::Type(ty) => Some(ty), + _ => None, + }); + + let _generic_bumps_ty = generic_args_iter + .next() + .ok_or_else(|| ParseError::new(generic_args.span(), "expected AccountsBumps type"))?; + + let generic_ty = generic_args_iter .next() .ok_or_else(|| ParseError::new(generic_args.span(), "expected Accounts type"))?; diff --git a/lang/tests/bumps_test.rs b/lang/tests/bumps_test.rs new file mode 100644 index 0000000000..56eba2ac6d --- /dev/null +++ b/lang/tests/bumps_test.rs @@ -0,0 +1,11 @@ +use anchor_lang::prelude::*; + +#[derive(Accounts)] +pub struct BumpsTest<'info> { + #[account( + seeds = [&[1u8]], + bump, + )] + pub is_pda: SystemAccount<'info>, + pub non_pda: SystemAccount<'info>, +}