Skip to content

Commit

Permalink
lang: Cpi AccountInfos (#824)
Browse files Browse the repository at this point in the history
  • Loading branch information
armaniferrante authored Oct 3, 2021
1 parent 15eca29 commit db193d8
Show file tree
Hide file tree
Showing 10 changed files with 299 additions and 43 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions examples/tutorial/basic-3/programs/puppet-master/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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");

Expand All @@ -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)
Expand Down
8 changes: 6 additions & 2 deletions lang/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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(
Expand Down
163 changes: 163 additions & 0 deletions lang/syn/src/codegen/accounts/__cpi_client_accounts.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 3 additions & 0 deletions lang/syn/src/codegen/accounts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -27,6 +29,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
#impl_exit

#__client_accounts_mod
#__cpi_client_accounts_mod
}
}

Expand Down
67 changes: 66 additions & 1 deletion lang/syn/src/codegen/program/cpi.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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)*
}
}
}
2 changes: 1 addition & 1 deletion tests/cfo/deps/stake
Loading

0 comments on commit db193d8

Please sign in to comment.