lib.rs 8.3 KB

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