|
@@ -5,6 +5,143 @@ 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
|
|
|
+/// #![feature(proc_macro_hygiene)]
|
|
|
+///
|
|
|
+/// 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
|
|
|
+/// #![feature(proc_macro_hygiene)]
|
|
|
+///
|
|
|
+/// use anchor_lang::prelude::*;
|
|
|
+/// use counter::Auth;
|
|
|
+///
|
|
|
+/// #[program]
|
|
|
+/// pub mod counter_auth {
|
|
|
+/// use super::*;
|
|
|
+///
|
|
|
+/// #[state]
|
|
|
+/// pub struct CounterAuth {}
|
|
|
+///
|
|
|
+/// // TODO: remove this impl block after addressing
|
|
|
+/// // https://github.com/project-serum/anchor/issues/71.
|
|
|
+/// impl CounterAuth {
|
|
|
+/// pub fn new(_ctx: Context<Empty>) -> Result<Self, ProgramError> {
|
|
|
+/// Ok(Self {})
|
|
|
+/// }
|
|
|
+/// }
|
|
|
+///
|
|
|
+/// 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,
|