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, DeriveInput, ItemFn, ItemImpl};
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/// It defaults to unit migration data. Use `#[migratable(data = MigrationData)]`
192/// if the migration needs a custom input type.
193/// In that case, the contract must implement the `CustomMigratableInterface` trait. The associated `Error` type
194/// must implement the `Into<ContractError>` trait. The `ContractError` type itself implements it implicitly,
195/// so that is an easy way to use it.
196///
197/// # Example
198/// ```rust,ignore
199/// # mod test {
200/// # use stellar_axelar_std::{contract, contractimpl, contracterror, Address, Env};
201/// use stellar_axelar_std_derive::{Ownable, Upgradable};
202/// # #[contracterror]
203/// # #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
204/// # #[repr(u32)]
205/// # pub enum ContractError {
206/// #     MigrationNotAllowed = 1,
207/// # }
208///
209/// #[contract]
210/// #[derive(Ownable, Upgradable)]
211/// #[migratable(data = Address)]
212/// pub struct Contract;
213///
214/// #[contractimpl]
215/// impl Contract {
216///     pub fn __constructor(env: &Env, owner: Address) {
217///         stellar_axelar_std::interfaces::set_owner(env, &owner);
218///     }
219/// }
220///
221/// impl CustomMigratableInterface for Contract {
222///     type MigrationData = Address;
223///     type Error = ContractError;
224///
225///     fn __migrate(env: &Env, new_owner: Self::MigrationData) -> Result<(), Self::Error> {
226///         Self::transfer_ownership(env, new_owner);
227///         Ok(())
228///     }
229/// }
230/// # }
231/// ```
232#[proc_macro_derive(Upgradable, attributes(migratable))]
233pub fn derive_upgradable(input: TokenStream) -> TokenStream {
234    let input = parse_macro_input!(input as DeriveInput);
235
236    upgradable::upgradable(&input)
237        .unwrap_or_else(|err| err.to_compile_error())
238        .into()
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/// Implements the Axelar Executable interface for a Soroban contract.
299///
300/// The concrete error type must be specified with `#[axelar_executable(error = ...)]`.
301/// It must match the contract's `CustomAxelarExecutable::Error` associated type and
302/// define a `NotApproved` variant used when the gateway has not approved the message.
303///
304/// # Example
305/// ```rust,ignore
306/// # mod test {
307/// # use stellar_axelar_std::{contract, contracterror, Address, Bytes, Env, String};
308/// use stellar_axelar_std_derive::AxelarExecutable;
309/// use stellar_axelar_gateway::executable::CustomAxelarExecutable;
310///
311/// #[contracterror]
312/// #[derive(Copy, Clone, Debug, Eq, PartialEq)]
313/// #[repr(u32)]
314/// pub enum ContractError {
315///     NotApproved = 1,
316/// }
317///
318/// #[contract]
319/// #[derive(AxelarExecutable)]
320/// #[axelar_executable(error = ContractError)]
321/// pub struct Contract;
322///
323/// impl CustomAxelarExecutable for Contract {
324///     type Error = ContractError;
325///
326///     fn __gateway(env: &Env) -> Address {
327///         todo!()
328///     }
329///
330///     fn __execute(
331///         env: &Env,
332///         source_chain: String,
333///         message_id: String,
334///         source_address: String,
335///         payload: Bytes,
336///     ) -> Result<(), Self::Error> {
337///         Ok(())
338///     }
339/// }
340/// # }
341/// ```
342#[proc_macro_derive(AxelarExecutable, attributes(axelar_executable))]
343pub fn derive_axelar_executable(input: TokenStream) -> TokenStream {
344    let input = parse_macro_input!(input as DeriveInput);
345
346    axelar_executable::axelar_executable(&input)
347        .unwrap_or_else(|err| err.to_compile_error())
348        .into()
349}
350
351/// Ensures that only a contract's owner can execute the attributed function.
352///
353/// The first argument to the function must be `env`
354///
355/// # Example
356/// ```rust,ignore
357/// # use stellar_axelar_std::{contract, contractimpl, Address, Env};
358/// use stellar_axelar_std::only_owner;
359///
360/// #[contract]
361/// pub struct Contract;
362///
363/// #[contractimpl]
364/// impl Contract {
365///     #[only_owner]
366///     pub fn transfer(env: &Env, to: Address, amount: String) {
367///         // ... transfer logic ...
368///     }
369/// }
370/// ```
371#[proc_macro_attribute]
372pub fn only_owner(_attr: TokenStream, item: TokenStream) -> TokenStream {
373    let input_fn = parse_macro_input!(item as ItemFn);
374
375    ownable::only_owner_impl(input_fn)
376        .unwrap_or_else(|err| err.to_compile_error())
377        .into()
378}
379
380/// Ensures that only a contract's operator can execute the attributed function.
381///
382/// The first argument to the function must be `env`
383///
384/// # Example
385/// ```rust,ignore
386/// # use stellar_axelar_std::{contract, contractimpl, Address, Env};
387/// use stellar_axelar_std::only_operator;
388///
389/// #[contract]
390/// pub struct Contract;
391///
392/// #[contractimpl]
393/// impl Contract {
394///     #[only_operator]
395///     pub fn transfer(env: &Env, to: Address, amount: String) {
396///         // ... transfer logic ...
397///     }
398/// }
399/// ```
400#[proc_macro_attribute]
401pub fn only_operator(_attr: TokenStream, item: TokenStream) -> TokenStream {
402    let input_fn = parse_macro_input!(item as ItemFn);
403
404    operatable::only_operator_impl(input_fn)
405        .unwrap_or_else(|err| err.to_compile_error())
406        .into()
407}
408
409/// Implements a storage interface for a Stellar contract storage enum.
410///
411/// The enum variants define contract data keys, with optional named fields as contract data map keys.
412/// Each variant requires a `#[value(Type)]` xor `#[status]` attribute to specify the stored value type.
413/// Storage type can be specified with `#[instance]`, `#[persistent]`, or `#[temporary]` attributes (defaults to instance).
414///
415/// Certain types have default behaviors for TTL extensions:
416/// - `#[persistent]`: This is extended by default every time a data key is accessed, for that data key.
417///   The persistent data type does not share the same TTL as the contract instance.
418/// - `#[instance]`: This is extended by default for all contract endpoints, so it does not need to be included in generated data key access functions.
419///   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.
420/// - `#[temporary]`: This is not extended by default, since this data type can be easily recreated or only valid for a certain period of time.
421///   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.
422///
423/// More on Stellar data types: <https://developers.stellar.org/docs/learn/encyclopedia/storage/state-archival#contract-data-type-descriptions>
424///
425/// # Example
426/// ```rust,ignore
427/// # mod test {
428/// use stellar_axelar_std::{contract, contractimpl, contractype, Address, Env, String};
429/// use stellar_axelar_std::contractstorage;
430///
431/// #[contractstorage]
432/// #[derive(Clone, Debug)]
433/// enum DataKey {
434///     #[instance]
435///     #[value(Address)]
436///     Owner,
437///
438///     #[persistent]
439///     #[value(String)]
440///     TokenName { token_id: u32 },
441///
442///     #[temporary]
443///     #[value(u64)]
444///     LastUpdate { account: Address },
445///
446///     #[instance]
447///     #[status]
448///     Paused,
449/// }
450///
451/// #[contract]
452/// pub struct Contract;
453///
454/// #[contractimpl]
455/// impl Contract {
456///     pub fn __constructor(
457///         env: &Env,
458///         token_id: u32,
459///         name: String,
460///     ) {
461///         storage::set_token_name(env, token_id, &name);
462///     }
463///
464///     pub fn foo(env: &Env, token_id: u32) -> Option<String> {
465///         storage::token_name(env, token_id);
466///     }
467///
468///     pub fn bar(env: &Env, token_id: u32) -> Option<String> {
469///         storage::remove_token_name(env, token_id)
470///     }
471/// }
472/// # }
473/// ```
474#[proc_macro_attribute]
475pub fn contractstorage(_attr: TokenStream, item: TokenStream) -> TokenStream {
476    let input = parse_macro_input!(item as DeriveInput);
477
478    contractstorage::contract_storage(&input).into()
479}