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}