stellar_axelar_std_derive/lib.rs
1//! Note: The tests are located in the `stellar-axelar-std` package instead of `stellar-axelar-std-derive`
2//!
3//! This ensures compatibility and prevents cyclic dependency issues during testing and release.
4
5mod axelar_executable;
6mod contractimpl;
7mod contractstorage;
8mod into_event;
9mod its_executable;
10mod operatable;
11mod ownable;
12mod pausable;
13mod upgradable;
14mod utils;
15
16use proc_macro::TokenStream;
17use syn::{parse_macro_input, Attribute, DeriveInput, ItemFn, ItemImpl, Path};
18
19/// Designates functions in an `impl` block as contract entrypoints.
20///
21/// This is a wrapper around the soroban-sdk's `#[contractimpl]` attribute.
22/// It adds additional checks to ensure entrypoints don't get accidentally, or maliciously, called
23/// after a contract upgrade, but before the data migration is complete.
24///
25/// # Example
26/// ```rust, ignore
27/// # mod test {
28/// # use stellar_axelar_std::{contract, contracterror};
29/// use stellar_axelar_std_derive::{contractimpl, Upgradable};
30///
31/// #[contract]
32/// #[derive(Upgradable)]
33/// pub struct Contract;
34///
35/// // any function in this impl block will panic if called during migration
36/// #[contractimpl]
37/// impl Contract {
38/// pub fn __constructor(env: &Env) {
39/// // constructor code
40/// }
41///
42/// pub fn do_something(env: &Env, arg: String) {
43/// // entrypoint code
44/// }
45/// }
46///
47/// #[contracterror]
48/// #[derive(Copy, Clone, Debug, Eq, PartialEq)]
49/// #[repr(u32)]
50/// pub enum ContractError {
51/// MigrationInProgress = 1,
52/// }
53///
54/// // if an entrypoint is able to return a Result<_, ContractError>,
55/// // it will return ContractError::MigrationInProgress instead of panicking when called during migration
56/// #[contractimpl]
57/// impl Contract {
58/// pub fn return_result(env: &Env, arg: String) -> Result<u32, ContractError> {
59/// // entrypoint code
60/// }
61/// }
62/// # }
63/// ```
64#[proc_macro_attribute]
65pub fn contractimpl(_attr: TokenStream, item: TokenStream) -> TokenStream {
66 let mut input = parse_macro_input!(item as ItemImpl);
67
68 contractimpl::contractimpl(&mut input)
69 .unwrap_or_else(|err| err.to_compile_error())
70 .into()
71}
72
73/// Implements the Operatable interface for a Soroban contract.
74///
75/// # Example
76/// ```rust,ignore
77/// # mod test {
78/// # use stellar_axelar_std::{contract, contractimpl, Address, Env};
79/// use stellar_axelar_std_derive::Operatable;
80///
81/// #[contract]
82/// #[derive(Operatable)]
83/// pub struct Contract;
84///
85/// #[contractimpl]
86/// impl Contract {
87/// pub fn __constructor(env: &Env, owner: Address) {
88/// stellar_axelar_std::interfaces::set_operator(env, &owner);
89/// }
90/// }
91/// # }
92/// ```
93#[proc_macro_derive(Operatable)]
94pub fn derive_operatable(input: TokenStream) -> TokenStream {
95 let input = parse_macro_input!(input as DeriveInput);
96 let name = &input.ident;
97
98 operatable::operatable(name).into()
99}
100
101/// Implements the Ownable interface for a Soroban contract.
102///
103/// # Example
104/// ```rust,ignore
105/// # mod test {
106/// # use stellar_axelar_std::{contract, contractimpl, Address, Env};
107/// use stellar_axelar_std_derive::Ownable;
108///
109/// #[contract]
110/// #[derive(Ownable)]
111/// pub struct Contract;
112///
113/// #[contractimpl]
114/// impl Contract {
115/// pub fn __constructor(env: &Env, owner: Address) {
116/// stellar_axelar_std::interfaces::set_owner(env, &owner);
117/// }
118/// }
119/// # }
120/// ```
121#[proc_macro_derive(Ownable)]
122pub fn derive_ownable(input: TokenStream) -> TokenStream {
123 let input = parse_macro_input!(input as DeriveInput);
124 let name = &input.ident;
125
126 ownable::ownable(name).into()
127}
128
129/// Implements the Pausable interface for a Soroban contract.
130///
131/// # Example
132/// ```rust,ignore
133/// # mod test {
134/// # use stellar_axelar_std::{contract, contractimpl, Address, Env};
135/// use stellar_axelar_std_derive::Pausable;
136///
137/// #[contract]
138/// #[derive(Pausable)]
139/// pub struct Contract;
140/// # }
141/// ```
142#[proc_macro_derive(Pausable)]
143pub fn derive_pausable(input: TokenStream) -> TokenStream {
144 let input = parse_macro_input!(input as DeriveInput);
145 let name = &input.ident;
146
147 pausable::pausable(name).into()
148}
149
150/// Ensure that the Stellar contract is not paused before executing the function.
151///
152/// The first argument to the function must be `env`, and a `ContractError` error type must be defined in scope,
153/// with a `ContractPaused` variant.
154///
155/// # Example
156/// ```rust,ignore
157/// # use stellar_axelar_std::{contract, contractimpl, contracttype, Address, Env};
158/// use stellar_axelar_std::{Pausable, when_not_paused};
159///
160/// #[contracttype]
161/// pub enum ContractError {
162/// ContractPaused = 1,
163/// }
164///
165/// #[contract]
166/// #[derive(Pausable)]
167/// pub struct Contract;
168///
169/// #[contractimpl]
170/// impl Contract {
171/// #[when_not_paused]
172/// pub fn transfer(env: &Env, to: Address, amount: String) {
173/// // ... transfer logic ...
174/// }
175/// }
176/// ```
177#[proc_macro_attribute]
178pub fn when_not_paused(_attr: TokenStream, item: TokenStream) -> TokenStream {
179 let input_fn = parse_macro_input!(item as ItemFn);
180
181 pausable::when_not_paused_impl(input_fn)
182 .unwrap_or_else(|err| err.to_compile_error())
183 .into()
184}
185
186/// Implements the Upgradable and Migratable interfaces for a Soroban contract.
187///
188/// A `ContractError` error type must be defined in scope, and have a `MigrationNotAllowed` variant.
189/// A default migration implementation is automatically provided. If custom migration code is required,
190/// the `#[migratable]` attribute can be applied to the contract struct.
191/// In that case, the contract must implement the `CustomMigratableInterface` trait. The associated `Error` type
192/// must implement the `Into<ContractError>` trait. The `ContractError` type itself implements it implicitly,
193/// so that is an easy way to use it.
194///
195/// # Example
196/// ```rust,ignore
197/// # mod test {
198/// # use stellar_axelar_std::{contract, contractimpl, contracterror, Address, Env};
199/// use stellar_axelar_std_derive::{Ownable, Upgradable};
200/// # #[contracterror]
201/// # #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
202/// # #[repr(u32)]
203/// # pub enum ContractError {
204/// # MigrationNotAllowed = 1,
205/// # }
206///
207/// #[contract]
208/// #[derive(Ownable, Upgradable)]
209/// #[migratable]
210/// pub struct Contract;
211///
212/// #[contractimpl]
213/// impl Contract {
214/// pub fn __constructor(env: &Env, owner: Address) {
215/// stellar_axelar_std::interfaces::set_owner(env, &owner);
216/// }
217/// }
218///
219/// impl CustomMigratableInterface for Contract {
220/// type MigrationData = Address;
221/// type Error = ContractError;
222///
223/// fn __migrate(env: &Env, new_owner: Self::MigrationData) -> Result<(), Self::Error> {
224/// Self::transfer_ownership(env, new_owner);
225/// Ok(())
226/// }
227/// }
228/// # }
229/// ```
230#[proc_macro_derive(Upgradable, attributes(migratable))]
231pub fn derive_upgradable(input: TokenStream) -> TokenStream {
232 let input = parse_macro_input!(input as DeriveInput);
233
234 upgradable::upgradable(&input).into()
235}
236
237fn ensure_no_args(attr: &Attribute) -> syn::Result<&Path> {
238 attr.meta.require_path_only()
239}
240
241/// Implements the Event trait for a Stellar contract event.
242///
243/// Fields without a `#[data]` attribute are used as topics, while fields with `#[data]` are used as event data.
244/// The event name can be specified with `#[event_name(...)]` or will default to the struct name in snake_case (minus "Event" suffix).
245///
246/// # Example
247/// ```rust,ignore
248/// # mod test {
249/// use core::fmt::Debug;
250/// use stellar_axelar_std::events::Event;
251/// use stellar_axelar_std::IntoEvent;
252/// use stellar_axelar_std::{Address, contract, contractimpl, Env, String};
253///
254/// #[derive(Debug, PartialEq, IntoEvent)]
255/// #[event_name("transfer")]
256/// pub struct TransferEvent {
257/// pub from: Address,
258/// pub to: Address,
259/// #[data]
260/// pub amount: String,
261/// }
262///
263/// #[contract]
264/// pub struct Token;
265///
266/// #[contractimpl]
267/// impl Token {
268/// pub fn transfer(env: &Env, to: Address, amount: String) {
269/// // ... transfer logic ...
270///
271/// // Generates event with:
272/// // - Topics: ["transfer", contract_address, to]
273/// // - Data: [amount]
274/// TransferEvent {
275/// from: env.current_contract_address(),
276/// to,
277/// amount,
278/// }.emit(env);
279/// }
280/// }
281/// }
282/// ```
283#[proc_macro_derive(IntoEvent, attributes(event_name, datum, data))]
284pub fn derive_into_event(input: TokenStream) -> TokenStream {
285 let input = parse_macro_input!(input as DeriveInput);
286
287 into_event::into_event(&input).into()
288}
289
290#[proc_macro_derive(InterchainTokenExecutable)]
291pub fn derive_its_executable(input: TokenStream) -> TokenStream {
292 let input = parse_macro_input!(input as DeriveInput);
293 let name = &input.ident;
294
295 its_executable::its_executable(name).into()
296}
297
298#[proc_macro_derive(AxelarExecutable)]
299pub fn derive_axelar_executable(input: TokenStream) -> TokenStream {
300 let input = parse_macro_input!(input as DeriveInput);
301 let name = &input.ident;
302
303 axelar_executable::axelar_executable(name).into()
304}
305
306/// Ensures that only a contract's owner can execute the attributed function.
307///
308/// The first argument to the function must be `env`
309///
310/// # Example
311/// ```rust,ignore
312/// # use stellar_axelar_std::{contract, contractimpl, Address, Env};
313/// use stellar_axelar_std::only_owner;
314///
315/// #[contract]
316/// pub struct Contract;
317///
318/// #[contractimpl]
319/// impl Contract {
320/// #[only_owner]
321/// pub fn transfer(env: &Env, to: Address, amount: String) {
322/// // ... transfer logic ...
323/// }
324/// }
325/// ```
326#[proc_macro_attribute]
327pub fn only_owner(_attr: TokenStream, item: TokenStream) -> TokenStream {
328 let input_fn = parse_macro_input!(item as ItemFn);
329
330 ownable::only_owner_impl(input_fn)
331 .unwrap_or_else(|err| err.to_compile_error())
332 .into()
333}
334
335/// Ensures that only a contract's operator can execute the attributed function.
336///
337/// The first argument to the function must be `env`
338///
339/// # Example
340/// ```rust,ignore
341/// # use stellar_axelar_std::{contract, contractimpl, Address, Env};
342/// use stellar_axelar_std::only_operator;
343///
344/// #[contract]
345/// pub struct Contract;
346///
347/// #[contractimpl]
348/// impl Contract {
349/// #[only_operator]
350/// pub fn transfer(env: &Env, to: Address, amount: String) {
351/// // ... transfer logic ...
352/// }
353/// }
354/// ```
355#[proc_macro_attribute]
356pub fn only_operator(_attr: TokenStream, item: TokenStream) -> TokenStream {
357 let input_fn = parse_macro_input!(item as ItemFn);
358
359 operatable::only_operator_impl(input_fn)
360 .unwrap_or_else(|err| err.to_compile_error())
361 .into()
362}
363
364/// Implements a storage interface for a Stellar contract storage enum.
365///
366/// The enum variants define contract data keys, with optional named fields as contract data map keys.
367/// Each variant requires a `#[value(Type)]` xor `#[status]` attribute to specify the stored value type.
368/// Storage type can be specified with `#[instance]`, `#[persistent]`, or `#[temporary]` attributes (defaults to instance).
369///
370/// Certain types have default behaviors for TTL extensions:
371/// - `#[persistent]`: This is extended by default every time a data key is accessed, for that data key.
372/// The persistent data type does not share the same TTL as the contract instance.
373/// - `#[instance]`: This is extended by default for all contract endpoints, so it does not need to be included in generated data key access functions.
374/// This also serves to extend the lifetime of the contract's bytecode, since the instance data type does share the same TTL as the contract instance.
375/// - `#[temporary]`: This is not extended by default, since this data type can be easily recreated or only valid for a certain period of time.
376/// In the special case that temporary data needs to be extended, a user may call the generated #ttl_extender function for that temporary data key.
377///
378/// More on Stellar data types: <https://developers.stellar.org/docs/learn/encyclopedia/storage/state-archival#contract-data-type-descriptions>
379///
380/// # Example
381/// ```rust,ignore
382/// # mod test {
383/// use stellar_axelar_std::{contract, contractimpl, contractype, Address, Env, String};
384/// use stellar_axelar_std::contractstorage;
385///
386/// #[contractstorage]
387/// #[derive(Clone, Debug)]
388/// enum DataKey {
389/// #[instance]
390/// #[value(Address)]
391/// Owner,
392///
393/// #[persistent]
394/// #[value(String)]
395/// TokenName { token_id: u32 },
396///
397/// #[temporary]
398/// #[value(u64)]
399/// LastUpdate { account: Address },
400///
401/// #[instance]
402/// #[status]
403/// Paused,
404/// }
405///
406/// #[contract]
407/// pub struct Contract;
408///
409/// #[contractimpl]
410/// impl Contract {
411/// pub fn __constructor(
412/// env: &Env,
413/// token_id: u32,
414/// name: String,
415/// ) {
416/// storage::set_token_name(env, token_id, &name);
417/// }
418///
419/// pub fn foo(env: &Env, token_id: u32) -> Option<String> {
420/// storage::token_name(env, token_id);
421/// }
422///
423/// pub fn bar(env: &Env, token_id: u32) -> Option<String> {
424/// storage::remove_token_name(env, token_id)
425/// }
426/// }
427/// # }
428/// ```
429#[proc_macro_attribute]
430pub fn contractstorage(_attr: TokenStream, item: TokenStream) -> TokenStream {
431 let input = parse_macro_input!(item as DeriveInput);
432
433 contractstorage::contract_storage(&input).into()
434}
435
436trait MapTranspose<T> {
437 fn map_transpose<U, E, F: FnOnce(T) -> Result<U, E>>(self, f: F) -> Result<Option<U>, E>;
438}
439
440impl<T> MapTranspose<T> for Option<T> {
441 fn map_transpose<U, E, F: FnOnce(T) -> Result<U, E>>(self, f: F) -> Result<Option<U>, E> {
442 self.map(f).transpose()
443 }
444}