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/// # Data payload encoding
247///
248/// `#[data]` publishes the data payload as a `Vec<Val>`, even when there is only one such field.
249/// `#[datum]` publishes a single field as a bare `Val` instead of a one-element `Vec`. Pick one
250/// encoding per event; the derive rejects at compile time:
251/// - a tuple struct, since unnamed fields cannot be emitted;
252/// - a field carrying more than one `#[data]`/`#[datum]` attribute;
253/// - more than one `#[datum]` field, or a `#[datum]` combined with any `#[data]` field, since only
254///   the first data field would be published.
255///
256/// # Example
257/// ```rust,ignore
258/// # mod test {
259/// use core::fmt::Debug;
260/// use stellar_axelar_std::events::Event;
261/// use stellar_axelar_std::IntoEvent;
262/// use stellar_axelar_std::{Address, contract, contractimpl, Env, String};
263///
264/// #[derive(Debug, PartialEq, IntoEvent)]
265/// #[event_name("transfer")]
266/// pub struct TransferEvent {
267///     pub from: Address,
268///     pub to: Address,
269///     #[data]
270///     pub amount: String,
271/// }
272///
273/// #[contract]
274/// pub struct Token;
275///
276/// #[contractimpl]
277/// impl Token {
278///     pub fn transfer(env: &Env, to: Address, amount: String) {
279///         // ... transfer logic ...
280///
281///         // Generates event with:
282///         // - Topics: ["transfer", contract_address, to]
283///         // - Data: [amount]
284///         TransferEvent {
285///             from: env.current_contract_address(),
286///             to,
287///             amount,
288///         }.emit(env);
289///     }
290/// }
291/// }
292/// ```
293#[proc_macro_derive(IntoEvent, attributes(event_name, datum, data))]
294pub fn derive_into_event(input: TokenStream) -> TokenStream {
295    let input = parse_macro_input!(input as DeriveInput);
296
297    into_event::into_event(&input).into()
298}
299
300#[proc_macro_derive(InterchainTokenExecutable)]
301pub fn derive_its_executable(input: TokenStream) -> TokenStream {
302    let input = parse_macro_input!(input as DeriveInput);
303    let name = &input.ident;
304
305    its_executable::its_executable(name).into()
306}
307
308/// Implements the Axelar Executable interface for a Soroban contract.
309///
310/// The concrete error type must be specified with `#[axelar_executable(error = ...)]`.
311/// It must match the contract's `CustomAxelarExecutable::Error` associated type and
312/// define a `NotApproved` variant used when the gateway has not approved the message.
313///
314/// # Example
315/// ```rust,ignore
316/// # mod test {
317/// # use stellar_axelar_std::{contract, contracterror, Address, Bytes, Env, String};
318/// use stellar_axelar_std_derive::AxelarExecutable;
319/// use stellar_axelar_gateway::executable::CustomAxelarExecutable;
320///
321/// #[contracterror]
322/// #[derive(Copy, Clone, Debug, Eq, PartialEq)]
323/// #[repr(u32)]
324/// pub enum ContractError {
325///     NotApproved = 1,
326/// }
327///
328/// #[contract]
329/// #[derive(AxelarExecutable)]
330/// #[axelar_executable(error = ContractError)]
331/// pub struct Contract;
332///
333/// impl CustomAxelarExecutable for Contract {
334///     type Error = ContractError;
335///
336///     fn __gateway(env: &Env) -> Address {
337///         todo!()
338///     }
339///
340///     fn __execute(
341///         env: &Env,
342///         source_chain: String,
343///         message_id: String,
344///         source_address: String,
345///         payload: Bytes,
346///     ) -> Result<(), Self::Error> {
347///         Ok(())
348///     }
349/// }
350/// # }
351/// ```
352#[proc_macro_derive(AxelarExecutable, attributes(axelar_executable))]
353pub fn derive_axelar_executable(input: TokenStream) -> TokenStream {
354    let input = parse_macro_input!(input as DeriveInput);
355
356    axelar_executable::axelar_executable(&input)
357        .unwrap_or_else(|err| err.to_compile_error())
358        .into()
359}
360
361/// Ensures that only a contract's owner can execute the attributed function.
362///
363/// The first argument to the function must be `env`
364///
365/// # Example
366/// ```rust,ignore
367/// # use stellar_axelar_std::{contract, contractimpl, Address, Env};
368/// use stellar_axelar_std::only_owner;
369///
370/// #[contract]
371/// pub struct Contract;
372///
373/// #[contractimpl]
374/// impl Contract {
375///     #[only_owner]
376///     pub fn transfer(env: &Env, to: Address, amount: String) {
377///         // ... transfer logic ...
378///     }
379/// }
380/// ```
381#[proc_macro_attribute]
382pub fn only_owner(_attr: TokenStream, item: TokenStream) -> TokenStream {
383    let input_fn = parse_macro_input!(item as ItemFn);
384
385    ownable::only_owner_impl(input_fn)
386        .unwrap_or_else(|err| err.to_compile_error())
387        .into()
388}
389
390/// Ensures that only a contract's operator can execute the attributed function.
391///
392/// The first argument to the function must be `env`
393///
394/// # Example
395/// ```rust,ignore
396/// # use stellar_axelar_std::{contract, contractimpl, Address, Env};
397/// use stellar_axelar_std::only_operator;
398///
399/// #[contract]
400/// pub struct Contract;
401///
402/// #[contractimpl]
403/// impl Contract {
404///     #[only_operator]
405///     pub fn transfer(env: &Env, to: Address, amount: String) {
406///         // ... transfer logic ...
407///     }
408/// }
409/// ```
410#[proc_macro_attribute]
411pub fn only_operator(_attr: TokenStream, item: TokenStream) -> TokenStream {
412    let input_fn = parse_macro_input!(item as ItemFn);
413
414    operatable::only_operator_impl(input_fn)
415        .unwrap_or_else(|err| err.to_compile_error())
416        .into()
417}
418
419/// Implements a storage interface for a Stellar contract storage enum.
420///
421/// The enum variants define contract data keys, with optional named fields as contract data map keys.
422/// Each variant requires a `#[value(Type)]` xor `#[status]` attribute to specify the stored value type.
423/// Storage type can be specified with `#[instance]`, `#[persistent]`, or `#[temporary]` attributes (defaults to instance).
424///
425/// Certain types have default behaviors for TTL extensions:
426/// - `#[persistent]`: This is extended by default every time a data key is accessed, for that data key.
427///   The persistent data type does not share the same TTL as the contract instance.
428/// - `#[instance]`: This is extended by default for all contract endpoints, so it does not need to be included in generated data key access functions.
429///   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.
430/// - `#[temporary]`: This is not extended by default, since this data type can be easily recreated or only valid for a certain period of time.
431///   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.
432///
433/// More on Stellar data types: <https://developers.stellar.org/docs/learn/encyclopedia/storage/state-archival#contract-data-type-descriptions>
434///
435/// # Example
436/// ```rust,ignore
437/// # mod test {
438/// use stellar_axelar_std::{contract, contractimpl, contractype, Address, Env, String};
439/// use stellar_axelar_std::contractstorage;
440///
441/// #[contractstorage]
442/// #[derive(Clone, Debug)]
443/// enum DataKey {
444///     #[instance]
445///     #[value(Address)]
446///     Owner,
447///
448///     #[persistent]
449///     #[value(String)]
450///     TokenName { token_id: u32 },
451///
452///     #[temporary]
453///     #[value(u64)]
454///     LastUpdate { account: Address },
455///
456///     #[instance]
457///     #[status]
458///     Paused,
459/// }
460///
461/// #[contract]
462/// pub struct Contract;
463///
464/// #[contractimpl]
465/// impl Contract {
466///     pub fn __constructor(
467///         env: &Env,
468///         token_id: u32,
469///         name: String,
470///     ) {
471///         storage::set_token_name(env, token_id, &name);
472///     }
473///
474///     pub fn foo(env: &Env, token_id: u32) -> Option<String> {
475///         storage::token_name(env, token_id);
476///     }
477///
478///     pub fn bar(env: &Env, token_id: u32) -> Option<String> {
479///         storage::remove_token_name(env, token_id)
480///     }
481/// }
482/// # }
483/// ```
484#[proc_macro_attribute]
485pub fn contractstorage(_attr: TokenStream, item: TokenStream) -> TokenStream {
486    let input = parse_macro_input!(item as DeriveInput);
487
488    contractstorage::contract_storage(&input).into()
489}