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}