stellar_macros/lib.rs
1mod access_control;
2mod helpers;
3mod pausable;
4
5use access_control::{generate_any_role_check, generate_role_check};
6use helpers::*;
7use pausable::generate_pause_check;
8use proc_macro::TokenStream;
9use quote::quote;
10use syn::{parse_macro_input, ItemFn};
11
12/* ACCESS CONTROL MACROS */
13
14/// A procedural macro that retrieves the admin from storage and requires
15/// authorization from the admin before executing the function body.
16///
17/// # Usage
18///
19/// ```rust
20/// #[only_admin]
21/// pub fn restricted_function(e: &Env, other_param: u32) {
22/// // Function body
23/// }
24/// ```
25///
26/// This will expand to:
27///
28/// ```rust
29/// pub fn restricted_function(e: &Env, other_param: u32) {
30/// stellar_access::access_control::enforce_admin_auth(e);
31/// // Function body
32/// }
33/// ```
34#[proc_macro_attribute]
35pub fn only_admin(attrs: TokenStream, input: TokenStream) -> TokenStream {
36 assert!(attrs.is_empty(), "This macro does not accept any arguments");
37
38 let input_fn = parse_macro_input!(input as ItemFn);
39
40 // Generate the function with the admin authorization check
41 let auth_check_path = quote! { stellar_access::access_control::enforce_admin_auth };
42 let expanded = generate_auth_check(&input_fn, auth_check_path);
43
44 TokenStream::from(expanded)
45}
46
47/// A procedural macro that ensures the parameter has the specified role.
48///
49/// # Security Warning
50///
51/// **IMPORTANT**: This macro checks role membership but does NOT enforce
52/// authorization. This design prevents duplicate `require_auth()` calls which
53/// would cause panics in Stellar contracts. Use this macro when:
54///
55/// 1. The function already contains a `require_auth()` call
56/// 2. Additional role-based access control is needed
57///
58/// If both role checking AND authorization are needed, use `#[only_role]`
59/// instead.
60///
61/// # Usage
62///
63/// ```rust
64/// #[has_role(account, "minter")]
65/// pub fn mint_tokens(e: &Env, amount: u32, account: Address) {
66/// // Function body
67/// }
68/// ```
69///
70/// This will expand to:
71///
72/// ```rust
73/// pub fn mint_tokens(e: &Env, amount: u32, account: Address) {
74/// stellar_access::access_control::ensure_role(
75/// e,
76/// &account,
77/// &soroban_sdk::Symbol::new(e, "minter"),
78/// );
79/// // Function body
80/// }
81/// ```
82#[proc_macro_attribute]
83pub fn has_role(args: TokenStream, input: TokenStream) -> TokenStream {
84 generate_role_check(args, input, false)
85}
86
87/// A procedural macro that ensures the parameter has the specified role and
88/// requires authorization.
89///
90/// **IMPORTANT**: This macro both checks role membership AND enforces
91/// authorization. Be aware that in Stellar contracts, duplicate
92/// `require_auth()` calls for the same account will cause panics. If the
93/// function already contains a `require_auth()` call for the same account, use
94/// `#[has_role]` instead to avoid duplicate authorization checks.
95///
96/// # Usage
97///
98/// ```rust
99/// #[only_role(account, "minter")]
100/// pub fn mint_tokens(e: &Env, amount: u32, account: Address) {
101/// // Function body
102/// }
103/// ```
104///
105/// This will expand to:
106///
107/// ```rust
108/// pub fn mint_tokens(e: &Env, amount: u32, account: Address) {
109/// stellar_access::access_control::ensure_role(
110/// e,
111/// &account,
112/// &soroban_sdk::Symbol::new(e, "minter"),
113/// );
114/// account.require_auth();
115/// // Function body
116/// }
117/// ```
118#[proc_macro_attribute]
119pub fn only_role(args: TokenStream, input: TokenStream) -> TokenStream {
120 generate_role_check(args, input, true)
121}
122
123/// A procedural macro that ensures the parameter has any of the specified
124/// roles.
125///
126/// # Security Warning
127///
128/// **IMPORTANT**: This macro checks role membership but does NOT enforce
129/// authorization. This design prevents duplicate `require_auth()` calls which
130/// would cause panics in Stellar contracts. Use this macro when:
131///
132/// 1. The function already contains a `require_auth()` call
133/// 2. Additional role-based access control is needed
134///
135/// If both role checking AND authorization are needed, use
136/// `#[only_any_role]`
137/// instead.
138///
139/// # Usage
140///
141/// ```rust
142/// #[has_any_role(account, ["minter", "admin", "operator"])]
143/// pub fn manage_tokens(e: &Env, amount: u32, account: Address) {
144/// // Function body
145/// }
146/// ```
147///
148/// This will expand to code that checks if the account has any of the specified
149/// roles.
150#[proc_macro_attribute]
151pub fn has_any_role(args: TokenStream, input: TokenStream) -> TokenStream {
152 generate_any_role_check(args, input, false)
153}
154
155/// A procedural macro that ensures the parameter has any of the specified roles
156/// and requires authorization.
157///
158/// **IMPORTANT**: This macro both checks role membership AND enforces
159/// authorization. Be aware that in Stellar contracts, duplicate
160/// `require_auth()` calls for the same account will cause panics. If the
161/// function already contains a `require_auth()` call for the same account, use
162/// `#[has_any_role]` instead to avoid duplicate authorization checks.
163///
164/// # Usage
165///
166/// ```rust
167/// #[only_any_role(account, ["minter", "admin", "operator"])]
168/// pub fn manage_tokens(e: &Env, amount: u32, account: Address) {
169/// // Function body
170/// }
171/// ```
172///
173/// This will expand to code that checks if the account has any of the specified
174/// roles and requires authorization from the account.
175#[proc_macro_attribute]
176pub fn only_any_role(args: TokenStream, input: TokenStream) -> TokenStream {
177 generate_any_role_check(args, input, true)
178}
179
180/// A procedural macro that retrieves the owner from storage and requires
181/// authorization from the owner before executing the function body.
182///
183/// # Usage
184///
185/// ```rust
186/// #[only_owner]
187/// pub fn restricted_function(e: &Env, other_param: u32) {
188/// // Function body
189/// }
190/// ```
191///
192/// This will expand to:
193///
194/// ```rust
195/// pub fn restricted_function(e: &Env, other_param: u32) {
196/// let owner: soroban_sdk::Address =
197/// e.storage().instance().get(&stellar_access::ownable::OwnableStorageKey::Owner).unwrap();
198/// owner.require_auth();
199/// // Function body
200/// }
201/// ```
202#[proc_macro_attribute]
203pub fn only_owner(attrs: TokenStream, input: TokenStream) -> TokenStream {
204 assert!(attrs.is_empty(), "This macro does not accept any arguments");
205
206 let input_fn = parse_macro_input!(input as ItemFn);
207
208 // Generate the function with the owner authorization check
209 let auth_check_path = quote! { stellar_access::ownable::enforce_owner_auth };
210 let expanded = generate_auth_check(&input_fn, auth_check_path);
211
212 TokenStream::from(expanded)
213}
214
215/// Adds a pause check at the beginning of the function that ensures the
216/// contract is not paused.
217///
218/// This macro will inject a `when_not_paused` check at the start of the
219/// function body. If the contract is paused, the function will return early
220/// with a panic.
221///
222/// # Requirement:
223///
224/// - The first argument of the decorated function must be of type `Env` or
225/// `&Env`
226///
227/// # Example:
228///
229/// ```ignore
230/// #[when_not_paused]
231/// pub fn my_function(e: &Env) {
232/// // This code will only execute if the contract is not paused
233/// }
234/// ```
235#[proc_macro_attribute]
236pub fn when_not_paused(attrs: TokenStream, item: TokenStream) -> TokenStream {
237 assert!(attrs.is_empty(), "This macro does not accept any arguments");
238
239 generate_pause_check(item, "when_not_paused")
240}
241
242/* PAUSABLE MACROS */
243
244/// Adds a pause check at the beginning of the function that ensures the
245/// contract is paused.
246///
247/// This macro will inject a `when_paused` check at the start of the function
248/// body. If the contract is not paused, the function will return early with a
249/// panic.
250///
251/// # Requirement:
252///
253/// - The first argument of the decorated function must be of type `Env` or
254/// `&Env`
255///
256/// # Example:
257///
258/// ```ignore
259/// #[when_paused]
260/// pub fn my_function(e: &Env) {
261/// // This code will only execute if the contract is paused
262/// }
263/// ```
264#[proc_macro_attribute]
265pub fn when_paused(attrs: TokenStream, item: TokenStream) -> TokenStream {
266 assert!(attrs.is_empty(), "This macro does not accept any arguments");
267
268 generate_pause_check(item, "when_paused")
269}