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}