extern crate proc_macro; use anchor_syn::parser; use heck::SnakeCase; use quote::quote; use syn::parse_macro_input; /// The `#[interface]` attribute allows one to define an external program /// dependency, without having any knowledge about the program, other than /// the fact that it implements the given trait. /// /// Additionally, the attribute generates a client that can be used to perform /// CPI to these external dependencies. /// /// # Example /// /// In the following example, we have a counter program, where the count /// can only be set if the configured external program authorizes it. /// /// ## Defining an `#[interface]` /// /// First we define the program that depends on an external interface. /// /// ```ignore /// use anchor_lang::prelude::*; /// /// #[interface] /// pub trait Auth<'info, T: Accounts<'info>> { /// fn is_authorized(ctx: Context, current: u64, new: u64) -> ProgramResult; /// } /// /// #[program] /// pub mod counter { /// use super::*; /// /// #[state] /// pub struct Counter { /// pub count: u64, /// pub auth_program: Pubkey, /// } /// /// impl Counter { /// pub fn new(_ctx: Context, auth_program: Pubkey) -> Result { /// Ok(Self { /// count: 0, /// auth_program, /// }) /// } /// /// #[access_control(SetCount::accounts(&self, &ctx))] /// pub fn set_count(&mut self, ctx: Context, new_count: u64) -> Result<()> { /// // Ask the auth program if we should approve the transaction. /// let cpi_program = ctx.accounts.auth_program.clone(); /// let cpi_ctx = CpiContext::new(cpi_program, Empty {}); /// /// // This is the client generated by the `#[interface]` attribute. /// auth::is_authorized(cpi_ctx, self.count, new_count)?; /// /// // Approved, so update. /// self.count = new_count; /// Ok(()) /// } /// } /// } /// /// #[derive(Accounts)] /// pub struct Empty {} /// /// #[derive(Accounts)] /// pub struct SetCount<'info> { /// auth_program: AccountInfo<'info>, /// } /// /// impl<'info> SetCount<'info> { /// pub fn accounts(counter: &Counter, ctx: &Context) -> Result<()> { /// if ctx.accounts.auth_program.key != &counter.auth_program { /// return Err(ErrorCode::InvalidAuthProgram.into()); /// } /// Ok(()) /// } /// } /// /// #[error] /// pub enum ErrorCode { /// #[msg("Invalid auth program.")] /// InvalidAuthProgram, /// } ///``` /// /// ## Defining an implementation /// /// Now we define the program that implements the interface, which the above /// program will call. /// /// ```ignore /// use anchor_lang::prelude::*; /// use counter::Auth; /// /// #[program] /// pub mod counter_auth { /// use super::*; /// /// #[state] /// pub struct CounterAuth; /// /// impl<'info> Auth<'info, Empty> for CounterAuth { /// fn is_authorized(_ctx: Context, current: u64, new: u64) -> ProgramResult { /// if current % 2 == 0 { /// if new % 2 == 0 { /// return Err(ProgramError::Custom(50)); // Arbitrary error code. /// } /// } else { /// if new % 2 == 1 { /// return Err(ProgramError::Custom(60)); // Arbitrary error code. /// } /// } /// Ok(()) /// } /// } /// } /// #[derive(Accounts)] /// pub struct Empty {} /// ``` /// /// # Returning Values Across CPI /// /// The caller above uses a `Result` to act as a boolean. However, in order /// for this feature to be maximally useful, we need a way to return values from /// interfaces. For now, one can do this by writing to a shared account, e.g., /// with the SPL's [Shared Memory Program](https://github.com/solana-labs/solana-program-library/tree/master/shared-memory). /// In the future, Anchor will add the ability to return values across CPI /// without having to worry about the details of shared memory accounts. #[proc_macro_attribute] pub fn interface( _args: proc_macro::TokenStream, input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { let item_trait = parse_macro_input!(input as syn::ItemTrait); let trait_name = item_trait.ident.to_string(); let mod_name: proc_macro2::TokenStream = item_trait .ident .to_string() .to_snake_case() .parse() .unwrap(); let methods: Vec = item_trait .items .iter() .filter_map(|trait_item: &syn::TraitItem| match trait_item { syn::TraitItem::Method(m) => Some(m), _ => None, }) .map(|method: &syn::TraitItemMethod| { let method_name = &method.sig.ident; let args: Vec<&syn::PatType> = method .sig .inputs .iter() .filter_map(|arg: &syn::FnArg| match arg { syn::FnArg::Typed(pat_ty) => Some(pat_ty), // TODO: just map this to None once we allow this feature. _ => panic!("Invalid syntax. No self allowed."), }) .filter(|pat_ty| { let mut ty = parser::tts_to_string(&pat_ty.ty); ty.retain(|s| !s.is_whitespace()); !ty.starts_with("Context<") }) .collect(); let args_no_tys: Vec<&Box> = args .iter() .map(|arg| { &arg.pat }) .collect(); let args_struct = { if args.is_empty() { quote! { use anchor_lang::prelude::borsh; #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)] struct Args; } } else { quote! { use anchor_lang::prelude::borsh; #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)] struct Args { #(#args),* } } } }; let sighash_arr = anchor_syn::codegen::program::common::sighash(&trait_name, &method_name.to_string()); let sighash_tts: proc_macro2::TokenStream = format!("{:?}", sighash_arr).parse().unwrap(); quote! { pub fn #method_name<'a,'b, 'c, 'info, T: anchor_lang::Accounts<'info> + anchor_lang::ToAccountMetas + anchor_lang::ToAccountInfos<'info>>( ctx: anchor_lang::context::CpiContext<'a, 'b, 'c, 'info, T>, #(#args),* ) -> anchor_lang::solana_program::entrypoint::ProgramResult { #args_struct let ix = { let ix = Args { #(#args_no_tys),* }; let mut ix_data = anchor_lang::AnchorSerialize::try_to_vec(&ix) .map_err(|_| anchor_lang::__private::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(); acc_infos.push(ctx.program.clone()); anchor_lang::solana_program::program::invoke_signed( &ix, &acc_infos, ctx.signer_seeds, ) } } }) .collect(); proc_macro::TokenStream::from(quote! { #item_trait /// Anchor generated module for invoking programs implementing an /// `#[interface]` via CPI. mod #mod_name { use super::*; #(#methods)* } }) }