Browse Source

lang: Cpi AccountInfos (#824)

Armani Ferrante 4 năm trước cách đây
mục cha
commit
db193d8248

+ 4 - 0
CHANGELOG.md

@@ -11,6 +11,10 @@ incremented for features.
 
 ## [Unreleased]
 
+### Breaking
+
+* `CpiContext` accounts must now be used with the accounts struct generated in the `crate::cpi::accounts::*` module. These structs correspond to the accounts context for each instruction, except that each field is of type `AccountInfo` ([#824](https://github.com/project-serum/anchor/pull/824)).
+
 ## [0.16.2] - 2021-09-27
 
 ### Features

+ 3 - 2
examples/tutorial/basic-3/programs/puppet-master/src/lib.rs

@@ -1,7 +1,8 @@
 // #region core
 use anchor_lang::prelude::*;
+use puppet::cpi::accounts::SetData;
 use puppet::program::Puppet;
-use puppet::{self, Data, SetData};
+use puppet::{self, Data};
 
 declare_id!("HmbTLCmaGvZhKnn1Zfa1JVnp7vkMV4DYVxPLWBVoN65L");
 
@@ -11,7 +12,7 @@ mod puppet_master {
     pub fn pull_strings(ctx: Context<PullStrings>, data: u64) -> ProgramResult {
         let cpi_program = ctx.accounts.puppet_program.to_account_info();
         let cpi_accounts = SetData {
-            puppet: ctx.accounts.puppet.clone(),
+            puppet: ctx.accounts.puppet.to_account_info(),
         };
         let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
         puppet::cpi::set_data(cpi_ctx, data)

+ 6 - 2
lang/src/context.rs

@@ -76,7 +76,9 @@ where
     }
 }
 
-impl<'info, T: Accounts<'info>> ToAccountInfos<'info> for CpiContext<'_, '_, '_, 'info, T> {
+impl<'info, T: ToAccountInfos<'info> + ToAccountMetas> ToAccountInfos<'info>
+    for CpiContext<'_, '_, '_, 'info, T>
+{
     fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
         let mut infos = self.accounts.to_account_infos();
         infos.extend_from_slice(&self.remaining_accounts);
@@ -85,7 +87,9 @@ impl<'info, T: Accounts<'info>> ToAccountInfos<'info> for CpiContext<'_, '_, '_,
     }
 }
 
-impl<'info, T: Accounts<'info>> ToAccountMetas for CpiContext<'_, '_, '_, 'info, T> {
+impl<'info, T: ToAccountInfos<'info> + ToAccountMetas> ToAccountMetas
+    for CpiContext<'_, '_, '_, 'info, T>
+{
     fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
         let mut metas = self.accounts.to_account_metas(is_signer);
         metas.append(

+ 163 - 0
lang/syn/src/codegen/accounts/__cpi_client_accounts.rs

@@ -0,0 +1,163 @@
+use crate::{AccountField, AccountsStruct, Ty};
+use heck::SnakeCase;
+use quote::quote;
+
+// Generates the private `__cpi_client_accounts` mod implementation, containing
+// a generated struct mapping 1-1 to the `Accounts` struct, except with
+// `AccountInfo`s as the types. This is generated for CPI clients.
+pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
+    let name = &accs.ident;
+    let account_mod_name: proc_macro2::TokenStream = format!(
+        "__cpi_client_accounts_{}",
+        accs.ident.to_string().to_snake_case()
+    )
+    .parse()
+    .unwrap();
+
+    let account_struct_fields: Vec<proc_macro2::TokenStream> = accs
+        .fields
+        .iter()
+        .map(|f: &AccountField| match f {
+            AccountField::CompositeField(s) => {
+                let name = &s.ident;
+                let symbol: proc_macro2::TokenStream = format!(
+                    "__cpi_client_accounts_{0}::{1}",
+                    s.symbol.to_snake_case(),
+                    s.symbol,
+                )
+                .parse()
+                .unwrap();
+                quote! {
+                    pub #name: #symbol<'info>
+                }
+            }
+            AccountField::Field(f) => {
+                let name = &f.ident;
+                quote! {
+                    pub #name: anchor_lang::solana_program::account_info::AccountInfo<'info>
+                }
+            }
+        })
+        .collect();
+
+    let account_struct_metas: Vec<proc_macro2::TokenStream> = accs
+        .fields
+        .iter()
+        .map(|f: &AccountField| match f {
+            AccountField::CompositeField(s) => {
+                let name = &s.ident;
+                quote! {
+                    account_metas.extend(self.#name.to_account_metas(None));
+                }
+            }
+            AccountField::Field(f) => {
+                let is_signer = match f.ty {
+                    Ty::Signer => true,
+                    _ => f.constraints.is_signer(),
+                };
+                let is_signer = match is_signer {
+                    false => quote! {false},
+                    true => quote! {true},
+                };
+                let meta = match f.constraints.is_mutable() {
+                    false => quote! { anchor_lang::solana_program::instruction::AccountMeta::new_readonly },
+                    true => quote! { anchor_lang::solana_program::instruction::AccountMeta::new },
+                };
+                let name = &f.ident;
+                quote! {
+                    account_metas.push(#meta(anchor_lang::Key::key(&self.#name), #is_signer));
+                }
+            }
+        })
+        .collect();
+
+    let account_struct_infos: Vec<proc_macro2::TokenStream> = accs
+        .fields
+        .iter()
+        .map(|f: &AccountField| match f {
+            AccountField::CompositeField(s) => {
+                let name = &s.ident;
+                quote! {
+                    account_infos.extend(anchor_lang::ToAccountInfos::to_account_infos(&self.#name));
+                }
+            }
+            AccountField::Field(f) => {
+                let name = &f.ident;
+                quote! {
+                    account_infos.push(anchor_lang::ToAccountInfo::to_account_info(&self.#name));
+                }
+            }
+        })
+        .collect();
+
+    // Re-export all composite account structs (i.e. other structs deriving
+    // accounts embedded into this struct. Required because, these embedded
+    // structs are *not* visible from the #[program] macro, which is responsible
+    // for generating the `accounts` mod, which aggregates all the the generated
+    // accounts used for structs.
+    let re_exports: Vec<proc_macro2::TokenStream> = {
+        // First, dedup the exports.
+        let mut re_exports = std::collections::HashSet::new();
+        for f in accs.fields.iter().filter_map(|f: &AccountField| match f {
+            AccountField::CompositeField(s) => Some(s),
+            AccountField::Field(_) => None,
+        }) {
+            re_exports.insert(format!(
+                "__cpi_client_accounts_{0}::{1}",
+                f.symbol.to_snake_case(),
+                f.symbol,
+            ));
+        }
+
+        re_exports
+            .iter()
+            .map(|symbol: &String| {
+                let symbol: proc_macro2::TokenStream = symbol.parse().unwrap();
+                quote! {
+                    pub use #symbol;
+                }
+            })
+            .collect()
+    };
+    let generics = if account_struct_fields.is_empty() {
+        quote! {}
+    } else {
+        quote! {<'info>}
+    };
+    quote! {
+        /// An internal, Anchor generated module. This is used (as an
+        /// implementation detail), to generate a CPI struct for a given
+        /// `#[derive(Accounts)]` implementation, where each field is an
+        /// AccountInfo.
+        ///
+        /// To access the struct in this module, one should use the sibling
+        /// `cpi::accounts` module (also generated), which re-exports this.
+        pub(crate) mod #account_mod_name {
+            use super::*;
+
+            #(#re_exports)*
+
+            pub struct #name#generics {
+                #(#account_struct_fields),*
+            }
+
+            #[automatically_derived]
+            impl#generics anchor_lang::ToAccountMetas for #name#generics {
+                fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<anchor_lang::solana_program::instruction::AccountMeta> {
+                    let mut account_metas = vec![];
+                    #(#account_struct_metas)*
+                    account_metas
+                }
+            }
+
+            #[automatically_derived]
+            impl<'info> anchor_lang::ToAccountInfos<'info> for #name#generics {
+                fn to_account_infos(&self) -> Vec<anchor_lang::solana_program::account_info::AccountInfo<'info>> {
+                    let mut account_infos = vec![];
+                    #(#account_struct_infos)*
+                    account_infos
+                }
+            }
+        }
+    }
+}

+ 3 - 0
lang/syn/src/codegen/accounts/mod.rs

@@ -6,6 +6,7 @@ use syn::{ConstParam, LifetimeDef, Token, TypeParam};
 use syn::{GenericParam, PredicateLifetime, WhereClause, WherePredicate};
 
 mod __client_accounts;
+mod __cpi_client_accounts;
 mod constraints;
 mod exit;
 mod to_account_infos;
@@ -19,6 +20,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
     let impl_exit = exit::generate(accs);
 
     let __client_accounts_mod = __client_accounts::generate(accs);
+    let __cpi_client_accounts_mod = __cpi_client_accounts::generate(accs);
 
     quote! {
         #impl_try_accounts
@@ -27,6 +29,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
         #impl_exit
 
         #__client_accounts_mod
+        #__cpi_client_accounts_mod
     }
 }
 

+ 66 - 1
lang/syn/src/codegen/program/cpi.rs

@@ -1,6 +1,7 @@
 use crate::codegen::program::common::{generate_ix_variant, sighash, SIGHASH_GLOBAL_NAMESPACE};
 use crate::Program;
 use crate::StateIx;
+use heck::SnakeCase;
 use quote::quote;
 
 pub fn generate(program: &Program) -> proc_macro2::TokenStream {
@@ -60,7 +61,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
         .ixs
         .iter()
         .map(|ix| {
-            let accounts_ident = &ix.anchor_ident;
+            let accounts_ident: proc_macro2::TokenStream = format!("crate::cpi::accounts::{}", &ix.anchor_ident.to_string()).parse().unwrap();
             let cpi_method = {
                 let ix_variant = generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args);
                 let method_name = &ix.ident;
@@ -100,6 +101,9 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
             cpi_method
         })
         .collect();
+
+    let accounts = generate_accounts(program);
+
     quote! {
         #[cfg(feature = "cpi")]
         pub mod cpi {
@@ -112,6 +116,67 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
             }
 
             #(#global_cpi_methods)*
+
+            #accounts
+        }
+    }
+}
+
+pub fn generate_accounts(program: &Program) -> proc_macro2::TokenStream {
+    let mut accounts = std::collections::HashSet::new();
+
+    // Go through state accounts.
+    if let Some(state) = &program.state {
+        // Ctor.
+        if let Some((_ctor, ctor_accounts)) = &state.ctor_and_anchor {
+            let macro_name = format!(
+                "__cpi_client_accounts_{}",
+                ctor_accounts.to_string().to_snake_case()
+            );
+            accounts.insert(macro_name);
+        }
+        // Methods.
+        if let Some((_impl_block, methods)) = &state.impl_block_and_methods {
+            for ix in methods {
+                let anchor_ident = &ix.anchor_ident;
+                // TODO: move to fn and share with accounts.rs.
+                let macro_name = format!(
+                    "__cpi_client_accounts_{}",
+                    anchor_ident.to_string().to_snake_case()
+                );
+                accounts.insert(macro_name);
+            }
+        }
+    }
+
+    // Go through instruction accounts.
+    for ix in &program.ixs {
+        let anchor_ident = &ix.anchor_ident;
+        // TODO: move to fn and share with accounts.rs.
+        let macro_name = format!(
+            "__cpi_client_accounts_{}",
+            anchor_ident.to_string().to_snake_case()
+        );
+        accounts.insert(macro_name);
+    }
+
+    // Build the tokens from all accounts
+    let account_structs: Vec<proc_macro2::TokenStream> = accounts
+        .iter()
+        .map(|macro_name: &String| {
+            let macro_name: proc_macro2::TokenStream = macro_name.parse().unwrap();
+            quote! {
+                pub use crate::#macro_name::*;
+            }
+        })
+        .collect();
+
+    quote! {
+        /// An Anchor generated module, providing a set of structs
+        /// mirroring the structs deriving `Accounts`, where each field is
+        /// an `AccountInfo`. This is useful for CPI.
+        pub mod accounts {
+            #(#account_structs)*
         }
     }
 }

+ 1 - 1
tests/cfo/deps/stake

@@ -1 +1 @@
-Subproject commit 28a7f1e0c134f16f99e201069970df78ec5d7e78
+Subproject commit 2f3e7e7975e8072ebf588cfddf9f8d25c1295cd5

+ 32 - 26
tests/cfo/programs/cfo/src/lib.rs

@@ -754,11 +754,13 @@ impl<'info> From<&SweepFees<'info>> for CpiContext<'_, '_, '_, 'info, dex::Sweep
     }
 }
 
-impl<'info> From<&SwapToSrm<'info>> for CpiContext<'_, '_, '_, 'info, swap::Swap<'info>> {
+impl<'info> From<&SwapToSrm<'info>>
+    for CpiContext<'_, '_, '_, 'info, swap::cpi::accounts::Swap<'info>>
+{
     fn from(accs: &SwapToSrm<'info>) -> Self {
         let program = accs.swap_program.to_account_info();
-        let accounts = swap::Swap {
-            market: swap::MarketAccounts {
+        let accounts = swap::cpi::accounts::Swap {
+            market: swap::cpi::accounts::MarketAccounts {
                 market: accs.market.market.to_account_info(),
                 open_orders: accs.market.open_orders.to_account_info(),
                 request_queue: accs.market.request_queue.to_account_info(),
@@ -781,11 +783,13 @@ impl<'info> From<&SwapToSrm<'info>> for CpiContext<'_, '_, '_, 'info, swap::Swap
     }
 }
 
-impl<'info> From<&SwapToUsdc<'info>> for CpiContext<'_, '_, '_, 'info, swap::Swap<'info>> {
+impl<'info> From<&SwapToUsdc<'info>>
+    for CpiContext<'_, '_, '_, 'info, swap::cpi::accounts::Swap<'info>>
+{
     fn from(accs: &SwapToUsdc<'info>) -> Self {
         let program = accs.swap_program.to_account_info();
-        let accounts = swap::Swap {
-            market: swap::MarketAccounts {
+        let accounts = swap::cpi::accounts::Swap {
+            market: swap::cpi::accounts::MarketAccounts {
                 market: accs.market.market.to_account_info(),
                 open_orders: accs.market.open_orders.to_account_info(),
                 request_queue: accs.market.request_queue.to_account_info(),
@@ -841,38 +845,40 @@ impl<'info> Distribute<'info> {
 }
 
 impl<'info> DropStakeReward<'info> {
-    fn into_srm_reward(&self) -> CpiContext<'_, '_, '_, 'info, registry::DropReward<'info>> {
+    fn into_srm_reward(
+        &self,
+    ) -> CpiContext<'_, '_, '_, 'info, registry::cpi::accounts::DropReward<'info>> {
         let program = self.registry_program.clone();
-        let accounts = registry::DropReward {
-            registrar: ProgramAccount::try_from(program.key, &self.srm.registrar).unwrap(),
-            reward_event_q: ProgramAccount::try_from(program.key, &self.srm.reward_event_q)
-                .unwrap(),
-            pool_mint: self.srm.pool_mint.clone().into(),
-            vendor: ProgramAccount::try_from(program.key, &self.srm.vendor).unwrap(),
-            vendor_vault: CpiAccount::try_from(&self.srm.vendor_vault).unwrap(),
+        let accounts = registry::cpi::accounts::DropReward {
+            registrar: self.srm.registrar.to_account_info(),
+            reward_event_q: self.srm.reward_event_q.to_account_info(),
+            pool_mint: self.srm.pool_mint.to_account_info(),
+            vendor: self.srm.vendor.to_account_info(),
+            vendor_vault: self.srm.vendor_vault.to_account_info(),
             depositor: self.stake.to_account_info(),
             depositor_authority: self.officer.to_account_info(),
             token_program: self.token_program.to_account_info(),
-            clock: self.clock.clone(),
-            rent: self.rent.clone(),
+            clock: self.clock.to_account_info(),
+            rent: self.rent.to_account_info(),
         };
         CpiContext::new(program.to_account_info(), accounts)
     }
 
-    fn into_msrm_reward(&self) -> CpiContext<'_, '_, '_, 'info, registry::DropReward<'info>> {
+    fn into_msrm_reward(
+        &self,
+    ) -> CpiContext<'_, '_, '_, 'info, registry::cpi::accounts::DropReward<'info>> {
         let program = self.registry_program.clone();
-        let accounts = registry::DropReward {
-            registrar: ProgramAccount::try_from(program.key, &self.msrm.registrar).unwrap(),
-            reward_event_q: ProgramAccount::try_from(program.key, &self.msrm.reward_event_q)
-                .unwrap(),
-            pool_mint: self.msrm.pool_mint.clone().into(),
-            vendor: ProgramAccount::try_from(program.key, &self.msrm.vendor).unwrap(),
-            vendor_vault: CpiAccount::try_from(&self.msrm.vendor_vault).unwrap(),
+        let accounts = registry::cpi::accounts::DropReward {
+            registrar: self.msrm.registrar.to_account_info(),
+            reward_event_q: self.msrm.reward_event_q.to_account_info(),
+            pool_mint: self.msrm.pool_mint.to_account_info(),
+            vendor: self.msrm.vendor.to_account_info(),
+            vendor_vault: self.msrm.vendor_vault.to_account_info(),
             depositor: self.stake.to_account_info(),
             depositor_authority: self.officer.to_account_info(),
             token_program: self.token_program.to_account_info(),
-            clock: self.clock.clone(),
-            rent: self.rent.clone(),
+            clock: self.clock.to_account_info(),
+            rent: self.rent.to_account_info(),
         };
         CpiContext::new(program.to_account_info(), accounts)
     }

+ 6 - 6
tests/lockup/programs/lockup/src/lib.rs

@@ -210,18 +210,18 @@ pub struct Auth<'info> {
 pub struct CreateVesting<'info> {
     // Vesting.
     #[account(zero)]
-    vesting: Account<'info, Vesting>,
+    pub vesting: Account<'info, Vesting>,
     #[account(mut)]
-    vault: Account<'info, TokenAccount>,
+    pub vault: Account<'info, TokenAccount>,
     // Depositor.
     #[account(mut)]
-    depositor: AccountInfo<'info>,
+    pub depositor: AccountInfo<'info>,
     #[account(signer)]
-    depositor_authority: AccountInfo<'info>,
+    pub depositor_authority: AccountInfo<'info>,
     // Misc.
     #[account(constraint = token_program.key == &token::ID)]
-    token_program: AccountInfo<'info>,
-    clock: Sysvar<'info, Clock>,
+    pub token_program: AccountInfo<'info>,
+    pub clock: Sysvar<'info, Clock>,
 }
 
 impl<'info> CreateVesting<'info> {

+ 15 - 5
tests/lockup/programs/registry/src/lib.rs

@@ -501,11 +501,21 @@ mod registry {
         let signer = &[&seeds[..]];
         let mut remaining_accounts: &[AccountInfo] = ctx.remaining_accounts;
         let cpi_program = ctx.accounts.lockup_program.clone();
-        let cpi_accounts = CreateVesting::try_accounts(
-            ctx.accounts.lockup_program.key,
-            &mut remaining_accounts,
-            &[],
-        )?;
+        let cpi_accounts = {
+            let accs = CreateVesting::try_accounts(
+                ctx.accounts.lockup_program.key,
+                &mut remaining_accounts,
+                &[],
+            )?;
+            lockup::cpi::accounts::CreateVesting {
+                vesting: accs.vesting.to_account_info(),
+                vault: accs.vault.to_account_info(),
+                depositor: accs.depositor.to_account_info(),
+                depositor_authority: accs.depositor_authority.to_account_info(),
+                token_program: accs.token_program.to_account_info(),
+                clock: accs.clock.to_account_info(),
+            }
+        };
         let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer);
         lockup::cpi::create_vesting(
             cpi_ctx,