123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- 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<T>, 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<Empty>, auth_program: Pubkey) -> Result<Self> {
- /// Ok(Self {
- /// count: 0,
- /// auth_program,
- /// })
- /// }
- ///
- /// #[access_control(SetCount::accounts(&self, &ctx))]
- /// pub fn set_count(&mut self, ctx: Context<SetCount>, 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<SetCount>) -> 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<Empty>, 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<proc_macro2::TokenStream> = 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<syn::Pat>> = 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)*
- }
- })
- }
|