anchor_attribute_interface/
lib.rs

1extern crate proc_macro;
2
3use anchor_syn::parser;
4use heck::SnakeCase;
5use quote::quote;
6use syn::parse_macro_input;
7
8/// The `#[interface]` attribute allows one to define an external program
9/// dependency, without having any knowledge about the program, other than
10/// the fact that it implements the given trait.
11///
12/// Additionally, the attribute generates a client that can be used to perform
13/// CPI to these external dependencies.
14///
15/// # Example
16///
17/// In the following example, we have a counter program, where the count
18/// can only be set if the configured external program authorizes it.
19///
20/// ## Defining an `#[interface]`
21///
22/// First we define the program that depends on an external interface.
23///
24/// ```ignore
25/// use anchor_lang::prelude::*;
26///
27/// #[interface]
28/// pub trait Auth<'info, T: Accounts<'info>> {
29///     fn is_authorized(ctx: Context<T>, current: u64, new: u64) -> anchor_lang::Result<()>;
30/// }
31///
32/// #[program]
33/// pub mod counter {
34///     use super::*;
35///
36///     #[state]
37///     pub struct Counter {
38///         pub count: u64,
39///         pub auth_program: Pubkey,
40///     }
41///
42///     impl Counter {
43///         pub fn new(_ctx: Context<Empty>, auth_program: Pubkey) -> Result<Self> {
44///             Ok(Self {
45///                 count: 0,
46///                 auth_program,
47///             })
48///         }
49///
50///         #[access_control(SetCount::accounts(&self, &ctx))]
51///         pub fn set_count(&mut self, ctx: Context<SetCount>, new_count: u64) -> Result<()> {
52///             // Ask the auth program if we should approve the transaction.
53///             let cpi_program = ctx.accounts.auth_program.clone();
54///             let cpi_ctx = CpiContext::new(cpi_program, Empty {});
55///
56///             // This is the client generated by the `#[interface]` attribute.
57///             auth::is_authorized(cpi_ctx, self.count, new_count)?;
58///
59///             // Approved, so update.
60///             self.count = new_count;
61///             Ok(())
62///         }
63///     }
64/// }
65///
66/// #[derive(Accounts)]
67/// pub struct Empty {}
68///
69/// #[derive(Accounts)]
70/// pub struct SetCount<'info> {
71///     auth_program: AccountInfo<'info>,
72/// }
73///
74/// impl<'info> SetCount<'info> {
75///     pub fn accounts(counter: &Counter, ctx: &Context<SetCount>) -> Result<()> {
76///         if ctx.accounts.auth_program.key != &counter.auth_program {
77///             return Err(error!(ErrorCode::InvalidAuthProgram));
78///         }
79///         Ok(())
80///     }
81/// }
82///
83/// #[error_code]
84/// pub enum ErrorCode {
85///     #[msg("Invalid auth program.")]
86///     InvalidAuthProgram,
87/// }
88///```
89///
90/// ## Defining an implementation
91///
92/// Now we define the program that implements the interface, which the above
93/// program will call.
94///
95/// ```ignore
96/// use anchor_lang::prelude::*;
97/// use counter::Auth;
98///
99/// #[program]
100/// pub mod counter_auth {
101///     use super::*;
102///
103///     #[state]
104///     pub struct CounterAuth;
105///
106///     impl<'info> Auth<'info, Empty> for CounterAuth {
107///         fn is_authorized(_ctx: Context<Empty>, current: u64, new: u64) -> Result<()> {
108///             if current % 2 == 0 {
109///                 if new % 2 == 0 {
110///                     return Err(ProgramError::Custom(50).into()); // Arbitrary error code.
111///                 }
112///             } else {
113///                 if new % 2 == 1 {
114///                     return Err(ProgramError::Custom(60).into()); // Arbitrary error code.
115///                 }
116///             }
117///             Ok(())
118///         }
119///     }
120/// }
121/// #[derive(Accounts)]
122/// pub struct Empty {}
123/// ```
124///
125/// # Returning Values Across CPI
126///
127/// The caller above uses a `Result` to act as a boolean. However, in order
128/// for this feature to be maximally useful, we need a way to return values from
129/// interfaces. For now, one can do this by writing to a shared account, e.g.,
130/// with the SPL's [Shared Memory Program](https://github.com/solana-labs/solana-program-library/tree/master/shared-memory).
131/// In the future, Anchor will add the ability to return values across CPI
132/// without having to worry about the details of shared memory accounts.
133#[proc_macro_attribute]
134pub fn interface(
135    _args: proc_macro::TokenStream,
136    input: proc_macro::TokenStream,
137) -> proc_macro::TokenStream {
138    let item_trait = parse_macro_input!(input as syn::ItemTrait);
139
140    let trait_name = item_trait.ident.to_string();
141    let mod_name: proc_macro2::TokenStream = item_trait
142        .ident
143        .to_string()
144        .to_snake_case()
145        .parse()
146        .unwrap();
147
148    let methods: Vec<proc_macro2::TokenStream> = item_trait
149        .items
150        .iter()
151        .filter_map(|trait_item: &syn::TraitItem| match trait_item {
152            syn::TraitItem::Method(m) => Some(m),
153            _ => None,
154        })
155        .map(|method: &syn::TraitItemMethod| {
156            let method_name = &method.sig.ident;
157            let args: Vec<&syn::PatType> = method
158                .sig
159                .inputs
160                .iter()
161                .filter_map(|arg: &syn::FnArg| match arg {
162                    syn::FnArg::Typed(pat_ty) => Some(pat_ty),
163                    // TODO: just map this to None once we allow this feature.
164                    _ => panic!("Invalid syntax. No self allowed."),
165                })
166                .filter(|pat_ty| {
167                    let mut ty = parser::tts_to_string(&pat_ty.ty);
168                    ty.retain(|s| !s.is_whitespace());
169                    !ty.starts_with("Context<")
170                })
171                .collect();
172            let args_no_tys: Vec<&Box<syn::Pat>> = args
173                .iter()
174                .map(|arg| {
175                    &arg.pat
176                })
177                .collect();
178            let args_struct = {
179                if args.is_empty() {
180                    quote! {
181                        use anchor_lang::prelude::borsh;
182                        #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
183                        struct Args;
184                    }
185                } else {
186                    quote! {
187                        use anchor_lang::prelude::borsh;
188                        #[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
189                        struct Args {
190                            #(#args),*
191                        }
192                    }
193                }
194            };
195
196            let sighash_arr = anchor_syn::codegen::program::common::sighash(&trait_name, &method_name.to_string());
197            let sighash_tts: proc_macro2::TokenStream =
198                format!("{:?}", sighash_arr).parse().unwrap();
199            quote! {
200                pub fn #method_name<'a,'b, 'c, 'info, T: anchor_lang::Accounts<'info> + anchor_lang::ToAccountMetas + anchor_lang::ToAccountInfos<'info>>(
201                    ctx: anchor_lang::context::CpiContext<'a, 'b, 'c, 'info, T>,
202                    #(#args),*
203                ) -> anchor_lang::Result<()> {
204                    #args_struct
205
206                    let ix = {
207                        let ix = Args {
208                            #(#args_no_tys),*
209                        };
210                        let mut ix_data = anchor_lang::AnchorSerialize::try_to_vec(&ix)
211                            .map_err(|_| anchor_lang::error::ErrorCode::InstructionDidNotSerialize)?;
212                        let mut data = #sighash_tts.to_vec();
213                        data.append(&mut ix_data);
214                        let accounts = ctx.to_account_metas(None);
215                        anchor_lang::solana_program::instruction::Instruction {
216                            program_id: *ctx.program.key,
217                            accounts,
218                            data,
219                        }
220                    };
221                    let mut acc_infos = ctx.to_account_infos();
222                    acc_infos.push(ctx.program.clone());
223                    anchor_lang::solana_program::program::invoke_signed(
224                        &ix,
225                        &acc_infos,
226                        ctx.signer_seeds,
227                    ).map_err(Into::into)
228                }
229            }
230        })
231        .collect();
232
233    proc_macro::TokenStream::from(quote! {
234        #item_trait
235
236        /// Anchor generated module for invoking programs implementing an
237        /// `#[interface]` via CPI.
238        mod #mod_name {
239            use super::*;
240            #(#methods)*
241        }
242    })
243}