satellite_attribute_program/
lib.rs

1extern crate proc_macro;
2
3mod declare_program;
4
5use declare_program::DeclareProgram;
6use quote::{quote, ToTokens};
7use syn::{parse_macro_input, AttributeArgs, ItemMod, Meta, NestedMeta};
8
9/// The `#[program]` attribute defines the module containing all instruction
10/// handlers defining all entries into a Solana program.
11#[proc_macro_attribute]
12pub fn program(
13    args: proc_macro::TokenStream,
14    input: proc_macro::TokenStream,
15) -> proc_macro::TokenStream {
16    // Parse the attribute arguments, looking for an optional `btc_tx(...)` section.
17    let args_parsed = parse_macro_input!(args as AttributeArgs);
18
19    // Parse the incoming module so we can attach a synthetic attribute.
20    let mut program_mod = parse_macro_input!(input as ItemMod);
21
22    // Extract btc_tx nested list if present.
23    for nested in args_parsed {
24        match nested {
25            NestedMeta::Meta(Meta::List(list)) if list.path.is_ident("btc_tx") => {
26                let nested = &list.nested;
27                // `nested` contains arguments like `max_inputs_to_sign = 4, ...`
28                let cfg_attr: syn::Attribute = syn::parse_quote! {
29                    #[btc_tx_cfg( #nested )]
30                };
31                program_mod.attrs.push(cfg_attr);
32            }
33            _ => {
34                // Ignore or emit error for unsupported meta
35            }
36        }
37    }
38
39    // Convert modified module back into tokens and let satellite_syn handle the heavy lifting.
40    let program_tokens = quote! { #program_mod };
41    match syn::parse2::<satellite_syn::Program>(program_tokens) {
42        Ok(p) => p.to_token_stream().into(),
43        Err(e) => e.to_compile_error().into(),
44    }
45}
46
47/// Declare an external program based on its IDL.
48///
49/// The IDL of the program must exist in a directory named `idls`. This directory can be at any
50/// depth, e.g. both inside the program's directory (`<PROGRAM_DIR>/idls`) and inside Anchor
51/// workspace root directory (`<PROGRAM_DIR>/../../idls`) are valid.
52///
53/// # Usage
54///
55/// ```rs
56/// declare_program!(program_name);
57/// ```
58///
59/// This generates a module named `program_name` that can be used to interact with the program
60/// without having to add the program's crate as a dependency.
61///
62/// Both on-chain and off-chain usage is supported.
63///
64/// Use `cargo doc --open` to see the generated modules and their documentation.
65///
66/// # Note
67///
68/// Re-defining the same program to use the same definitions should be avoided since this results
69/// in a larger binary size.
70///
71/// A program should only be defined once. If you have multiple programs that depend on the same
72/// definition, you should consider creating a separate crate for the external program definition
73/// and reusing it in your programs.
74///
75/// # Example
76///
77/// A full on-chain CPI usage example can be found [here].
78///
79/// [here]: https://github.com/coral-xyz/anchor/tree/v0.31.1/tests/declare-program
80#[proc_macro]
81pub fn declare_program(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
82    parse_macro_input!(input as DeclareProgram)
83        .to_token_stream()
84        .into()
85}
86
87/// The `#[interface]` attribute is used to mark an instruction as belonging
88/// to an interface implementation, thus transforming its discriminator to the
89/// proper bytes for that interface instruction.
90///
91/// # Example
92///
93/// ```rust,ignore
94/// use satellite_lang::prelude::*;
95///
96/// // SPL Transfer Hook Interface: `Execute` instruction.
97/// //
98/// // This instruction is invoked by Token-2022 when a transfer occurs,
99/// // if a mint has specified this program as its transfer hook.
100/// #[interface(spl_transfer_hook_interface::execute)]
101/// pub fn execute_transfer(ctx: Context<Execute>, amount: u64) -> Result<()> {
102///     // Check that all extra accounts were provided
103///     let data = ctx.accounts.extra_metas_account.try_borrow_data()?;
104///     ExtraAccountMetaList::check_account_infos::<ExecuteInstruction>(
105///         &ctx.accounts.to_account_infos(),
106///         &TransferHookInstruction::Execute { amount }.pack(),
107///         &ctx.program_id,
108///         &data,
109///     )?;
110///
111///     // Or maybe perform some custom logic
112///     if ctx.accounts.token_metadata.mint != ctx.accounts.token_account.mint {
113///         return Err(ProgramError::IncorrectAccount);
114///     }
115///
116///     Ok(())
117/// }
118/// ```
119#[cfg(feature = "interface-instructions")]
120#[deprecated(
121    since = "0.31.1",
122    note = "Use `#[instruction(discriminator = <EXPR>)]` instead.
123    See examples in https://github.com/coral-xyz/anchor/tree/v0.31.1/tests/spl/transfer-hook"
124)]
125#[proc_macro_attribute]
126pub fn interface(
127    _args: proc_macro::TokenStream,
128    input: proc_macro::TokenStream,
129) -> proc_macro::TokenStream {
130    // This macro itself is a no-op, but must be defined as a proc-macro
131    // attribute to be used on a function as the `#[interface]` attribute.
132    //
133    // The `#[program]` macro will detect this attribute and transform the
134    // discriminator.
135    input
136}
137
138/// This attribute is used to override the Anchor defaults of program instructions.
139///
140/// # Arguments
141///
142/// - `discriminator`: Override the default 8-byte discriminator
143///
144///     **Usage:** `discriminator = <CONST_EXPR>`
145///
146///     All constant expressions are supported.
147///
148///     **Examples:**
149///
150///     - `discriminator = 1` (shortcut for `[1]`)
151///     - `discriminator = [1, 2, 3, 4]`
152///     - `discriminator = b"hi"`
153///     - `discriminator = MY_DISC`
154///     - `discriminator = get_disc(...)`
155///
156/// # Example
157///
158/// ```ignore
159/// use satellite_lang::prelude::*;
160///
161/// declare_id!("CustomDiscriminator111111111111111111111111");
162///
163/// #[program]
164/// pub mod custom_discriminator {
165///     use super::*;
166///
167///     #[instruction(discriminator = [1, 2, 3, 4])]
168///     pub fn my_ix(_ctx: Context<MyIx>) -> Result<()> {
169///         Ok(())
170///     }
171/// }
172///
173/// #[derive(Accounts)]
174/// pub struct MyIx<'info> {
175///     pub signer: Signer<'info>,
176/// }
177/// ```
178#[proc_macro_attribute]
179pub fn instruction(
180    _args: proc_macro::TokenStream,
181    input: proc_macro::TokenStream,
182) -> proc_macro::TokenStream {
183    // This macro itself is a no-op, but the `#[program]` macro will detect this attribute and use
184    // the arguments to transform the instruction.
185    input
186}