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}