123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 |
- use crate::codegen::program::common::{generate_ix_variant, sighash, SIGHASH_GLOBAL_NAMESPACE};
- use crate::Program;
- use heck::SnakeCase;
- use quote::{quote, ToTokens};
- pub fn generate(program: &Program) -> proc_macro2::TokenStream {
- // Generate cpi methods for global methods.
- let global_cpi_methods: Vec<proc_macro2::TokenStream> = program
- .ixs
- .iter()
- .map(|ix| {
- 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;
- let args: Vec<&syn::PatType> = ix.args.iter().map(|arg| &arg.raw_arg).collect();
- let name = &ix.raw_method.sig.ident.to_string();
- let sighash_arr = sighash(SIGHASH_GLOBAL_NAMESPACE, name);
- let sighash_tts: proc_macro2::TokenStream =
- format!("{sighash_arr:?}").parse().unwrap();
- let ret_type = &ix.returns.ty.to_token_stream();
- let (method_ret, maybe_return) = match ret_type.to_string().as_str() {
- "()" => (quote! {anchor_lang::Result<()> }, quote! { Ok(()) }),
- _ => (
- quote! { anchor_lang::Result<crate::cpi::Return::<#ret_type>> },
- quote! { Ok(crate::cpi::Return::<#ret_type> { phantom: crate::cpi::PhantomData }) }
- )
- };
- quote! {
- pub fn #method_name<'a, 'b, 'c, 'info>(
- ctx: anchor_lang::context::CpiContext<'a, 'b, 'c, 'info, #accounts_ident<'info>>,
- #(#args),*
- ) -> #method_ret {
- let ix = {
- let ix = instruction::#ix_variant;
- let mut ix_data = AnchorSerialize::try_to_vec(&ix)
- .map_err(|_| anchor_lang::error::ErrorCode::InstructionDidNotSerialize)?;
- let mut data = #sighash_tts.to_vec();
- data.append(&mut ix_data);
- let accounts = ctx.to_account_metas(None);
- anchor_lang::solana_program::instruction::Instruction {
- program_id: ctx.program.key(),
- accounts,
- data,
- }
- };
- let mut acc_infos = ctx.to_account_infos();
- anchor_lang::solana_program::program::invoke_signed(
- &ix,
- &acc_infos,
- ctx.signer_seeds,
- ).map_or_else(
- |e| Err(Into::into(e)),
- // Maybe handle Solana return data.
- |_| { #maybe_return }
- )
- }
- }
- };
- cpi_method
- })
- .collect();
- let accounts = generate_accounts(program);
- quote! {
- #[cfg(feature = "cpi")]
- pub mod cpi {
- use super::*;
- use std::marker::PhantomData;
- pub struct Return<T> {
- phantom: std::marker::PhantomData<T>
- }
- impl<T: AnchorDeserialize> Return<T> {
- pub fn get(&self) -> T {
- let (_key, data) = anchor_lang::solana_program::program::get_return_data().unwrap();
- T::try_from_slice(&data).unwrap()
- }
- }
- #(#global_cpi_methods)*
- #accounts
- }
- }
- }
- pub fn generate_accounts(program: &Program) -> proc_macro2::TokenStream {
- let mut accounts = std::collections::HashSet::new();
- // 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)*
- }
- }
- }
|