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 = 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> }, 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 { phantom: std::marker::PhantomData } impl Return { 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 = 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)* } } }