sol_dev_proc_macros/
lib.rs

1extern crate proc_macro;
2extern crate quote;
3extern crate syn;
4
5use proc_macro::TokenStream;
6
7use quote::quote;
8use syn::{parse_macro_input, ItemFn};
9
10/// Generates a discriminant for a given function name within a global namespace.
11///
12/// This macro is used to create a unique identifier (discriminant) for a function
13/// within the Anchor framework. It's typically used for generating unique
14/// instruction identifiers in Solana programs.
15///
16/// Anchor typically requires adds the namespace to the function name to generate
17/// a unique identifier. In this macro, we default to the global namespace if no
18/// namespace is provided.
19///
20/// # Arguments
21///
22/// * `input` - A `TokenStream` containing the function name to generate a discriminant for.
23///
24/// # Returns
25///
26/// A `TokenStream` representing an array of bytes, which is the generated discriminant.
27///
28/// # Example
29///
30/// ```rust
31/// use sol_dev_proc_macros::anchor_discriminant;
32/// const DISCRIMINANT: [u8; 8] = anchor_discriminant!(initialize);
33/// const DISCRIMINANT_WITH_NAMESPACE: [u8; 8] = anchor_discriminant!(global:initialize);
34/// assert_eq!(
35///    DISCRIMINANT,
36///    [175, 175, 109, 31, 13, 152, 155, 237]
37/// );
38/// assert_eq!(
39///     DISCRIMINANT_WITH_NAMESPACE,
40///     DISCRIMINANT
41/// );
42/// ```
43#[proc_macro]
44pub fn anchor_discriminant(input: TokenStream) -> TokenStream {
45    const NAMESPACE: &str = "global";
46    let function_name = input.to_string();
47    // If the function does not contain a namespace, we add the global namespace.
48    let full_name = if function_name.contains(':') {
49        function_name
50    } else {
51        format!("{}:{}", NAMESPACE, function_name)
52    };
53    let arr = sol_dev_utils::anchor_discriminant(&full_name);
54    let expanded = quote::quote! {
55        [#(#arr),*]
56    };
57    TokenStream::from(expanded)
58}
59
60/// Attribute macro for instrumenting functions with compute unit logging.
61///
62/// This macro wraps the decorated function with additional logging statements
63/// that print the function name and the number of compute units used before and after
64/// the function execution.
65///
66/// # Usage
67///
68/// ```rust,ignore
69/// #[compute_fn]
70/// fn my_function() {
71///     // Function body
72/// }
73/// ```
74///
75/// # Effects
76///
77/// - Adds a log message with the function name at the start of execution.
78/// - Logs the number of compute units before and after the function execution.
79/// - Adds a closing log message with the function name at the end of execution.
80///
81/// # Note on Compute Units Used by `compute_fn!`
82///
83/// ## Testing Results (as of 2024-09-01)
84///
85///  TOTAL COST OF LOGGING: 445 - 36 = 409
86///  EXTRA COST INSIDE THE FUNCTION: 101
87///
88///  EMPTY_WITH_LOG where nothing happens inside the log.
89///  TOTAL COMPUTE UNITS USED: 445
90///  INNER LOG COST: 101
91///
92///  "Program EMPTY_WITH_LOG invoke [1]",
93///  "Program log: Program log: process_instruction {{",
94///  "Program consumption: 199762 units remaining",
95///  "Program consumption: 199661 units remaining", // 199762 - 199661 = 101
96///  "Program log: Program log: }} // process_instruction",
97///  "Program EMPTY_WITH_LOG consumed 445 of 200000 compute units",
98///  "Program EMPTY_WITH_LOG success"
99///
100///  EMPTY where nothing happens at all.
101///  TOTAL COMPUTE UNITS USED: 36
102///  
103///  "Program EMPTY invoke [1]",
104///  "Program EMPTY consumed 36 of 200000 compute units",
105///  "Program EMPTY success"
106///  
107///  Total extra compute units used per `compute_fn!` call: 409 CU
108///  For more details, see:
109///  - https://github.com/anza-xyz/agave/blob/d88050cda335f87e872eddbdf8506bc063f039d3/programs/bpf_loader/src/syscalls/logging.rs#L70
110///  - https://github.com/anza-xyz/agave/blob/d88050cda335f87e872eddbdf8506bc063f039d3/program-runtime/src/compute_budget.rs#L150
111#[proc_macro_attribute]
112pub fn compute_fn(_attr: TokenStream, item: TokenStream) -> TokenStream {
113    let mut input = parse_macro_input!(item as ItemFn);
114    let fn_name = &input.sig.ident;
115    let block = &input.block;
116
117    input.block = syn::parse_quote!({
118        ::solana_program::msg!(concat!(stringify!(#fn_name), " {{"));
119        ::solana_program::log::sol_log_compute_units();
120
121        let __result = (|| #block)();
122
123        ::solana_program::log::sol_log_compute_units();
124        ::solana_program::msg!(concat!("}} // ", stringify!(#fn_name)));
125
126        __result
127    });
128
129    quote!(#input).into()
130}