@layerzerolabs/common-utils-macros-stellar-contracts 0.2.122

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/Cargo.toml +21 -0
  2. package/LICENSE +23 -0
  3. package/clippy.toml +7 -0
  4. package/package.json +37 -0
  5. package/rust-toolchain.toml +4 -0
  6. package/rustfmt.toml +15 -0
  7. package/src/auth.rs +95 -0
  8. package/src/contract_ttl.rs +92 -0
  9. package/src/error.rs +43 -0
  10. package/src/lib.rs +585 -0
  11. package/src/lz_contract.rs +105 -0
  12. package/src/rbac.rs +90 -0
  13. package/src/storage.rs +522 -0
  14. package/src/tests/auth.rs +230 -0
  15. package/src/tests/contract_ttl.rs +695 -0
  16. package/src/tests/error.rs +156 -0
  17. package/src/tests/lz_contract.rs +87 -0
  18. package/src/tests/mod.rs +11 -0
  19. package/src/tests/rbac.rs +523 -0
  20. package/src/tests/snapshots/common_macros__tests__auth__snapshot_generated_multisig_code.snap +31 -0
  21. package/src/tests/snapshots/common_macros__tests__auth__snapshot_generated_ownable_code.snap +39 -0
  22. package/src/tests/snapshots/common_macros__tests__auth__snapshot_only_auth_preserves_function_signature.snap +19 -0
  23. package/src/tests/snapshots/common_macros__tests__contract_ttl__snapshot_generated_contractimpl_code.snap +77 -0
  24. package/src/tests/snapshots/common_macros__tests__contract_ttl__snapshot_generated_contracttrait_code.snap +46 -0
  25. package/src/tests/snapshots/common_macros__tests__error__snapshot_generated_contract_error_code.snap +20 -0
  26. package/src/tests/snapshots/common_macros__tests__lz_contract__snapshot_generated_lz_contract_code.snap +51 -0
  27. package/src/tests/snapshots/common_macros__tests__rbac__snapshot_authorizer_role.snap +21 -0
  28. package/src/tests/snapshots/common_macros__tests__rbac__snapshot_preserve_function_signature.snap +21 -0
  29. package/src/tests/snapshots/common_macros__tests__ttl_configurable__snapshot_generated_ttl_configurable_code.snap +10 -0
  30. package/src/tests/snapshots/common_macros__tests__ttl_extendable__snapshot_generated_ttl_extendable_code.snap +8 -0
  31. package/src/tests/snapshots/common_macros__tests__upgradeable__snapshot_generated_upgradeable_code.snap +28 -0
  32. package/src/tests/storage/extract_fields.rs +87 -0
  33. package/src/tests/storage/gen_accessor_methods.rs +223 -0
  34. package/src/tests/storage/gen_args.rs +65 -0
  35. package/src/tests/storage/gen_enum_variant.rs +78 -0
  36. package/src/tests/storage/gen_key.rs +108 -0
  37. package/src/tests/storage/gen_params.rs +105 -0
  38. package/src/tests/storage/generate_storage.rs +410 -0
  39. package/src/tests/storage/is_primitive_type.rs +48 -0
  40. package/src/tests/storage/mod.rs +16 -0
  41. package/src/tests/storage/parse_default.rs +164 -0
  42. package/src/tests/storage/parse_name.rs +158 -0
  43. package/src/tests/storage/parse_no_ttl_extension.rs +124 -0
  44. package/src/tests/storage/parse_storage_type.rs +174 -0
  45. package/src/tests/storage/snapshots/common_macros__tests__storage__generate_storage__snapshot_generated_storage_code.snap +412 -0
  46. package/src/tests/storage/storage_kind.rs +39 -0
  47. package/src/tests/storage/test_setup.rs +25 -0
  48. package/src/tests/storage/validate_attrs.rs +138 -0
  49. package/src/tests/storage/variant_config.rs +226 -0
  50. package/src/tests/test_helpers.rs +87 -0
  51. package/src/tests/ttl_configurable.rs +34 -0
  52. package/src/tests/ttl_extendable.rs +32 -0
  53. package/src/tests/upgradeable.rs +169 -0
  54. package/src/tests/utils.rs +267 -0
  55. package/src/ttl_configurable.rs +24 -0
  56. package/src/ttl_extendable.rs +28 -0
  57. package/src/upgradeable.rs +136 -0
  58. package/src/utils.rs +56 -0
package/src/lib.rs ADDED
@@ -0,0 +1,585 @@
1
+ //! Common procedural macros for Stellar smart contracts.
2
+ //!
3
+ //! # Quick Links
4
+ //! - [`contract_error`] - Error enum generation macro
5
+ //! - [`contract_impl`] - Contract impl with automatic instance TTL extension
6
+ //! - [`contract_trait`] - Contract trait with automatic instance TTL extension
7
+ //! - [`lz_contract`] - Wrapper macro combining common LayerZero contract attributes
8
+ //! - [`multisig`] - MultiSig trait implementation macro
9
+ //! - [`only_auth`] - Auth-based access control attribute macro
10
+ //! - [`only_role`] - RBAC role check with auth attribute macro
11
+ //! - [`has_role`] - RBAC role check attribute macro
12
+ //! - [`ownable`] - Ownable trait implementation macro
13
+ //! - [`storage`] - Storage enum to API macro
14
+ //! - [`ttl_configurable`] - TTL configuration with freeze support
15
+ //! - [`ttl_extendable`] - Manual instance TTL extension
16
+ //! - [`upgradeable`] - Upgradeable trait implementation macro
17
+ //!
18
+
19
+ use proc_macro::TokenStream;
20
+
21
+ mod auth;
22
+ mod contract_ttl;
23
+ mod error;
24
+ mod lz_contract;
25
+ mod rbac;
26
+ mod storage;
27
+ mod ttl_configurable;
28
+ mod ttl_extendable;
29
+ mod upgradeable;
30
+ mod utils;
31
+
32
+ #[cfg(test)]
33
+ mod tests;
34
+
35
+ // ============================================================================
36
+ // Storage Macro
37
+ // ============================================================================
38
+
39
+ /// Generates strongly-typed storage API from enum variants.
40
+ ///
41
+ /// Transforms a storage enum into getter/setter/remove/set_or_remove/has/extend_ttl methods.
42
+ /// TTL extension is automatic for persistent storage on get/set/has operations.
43
+ ///
44
+ /// # Example
45
+ /// ```ignore
46
+ /// #[storage]
47
+ /// pub enum DataKey {
48
+ /// #[instance(u32)]
49
+ /// Counter,
50
+ ///
51
+ /// #[persistent(Address)]
52
+ /// #[default(Address::default())]
53
+ /// Owner,
54
+ ///
55
+ /// #[persistent(u64)]
56
+ /// Nonce { user: Address },
57
+ ///
58
+ /// #[persistent(BytesN<32>)]
59
+ /// #[no_ttl_extension] // opt-out of automatic TTL extension
60
+ /// CacheData,
61
+ ///
62
+ /// #[temporary(BytesN<32>)]
63
+ /// TempData,
64
+ /// }
65
+ ///
66
+ /// // Generated API for instance storage (no extend_ttl method):
67
+ /// DataKey::counter(&env) // -> Option<u32>
68
+ /// DataKey::set_counter(&env, &value) // set value
69
+ /// DataKey::has_counter(&env) // -> bool
70
+ /// DataKey::remove_counter(&env) // remove entry
71
+ /// DataKey::set_or_remove_counter(&env, &opt) // set if Some, remove if None
72
+ ///
73
+ /// // Generated API for persistent/temporary storage (includes extend_ttl):
74
+ /// DataKey::nonce(&env, &user) // -> Option<u64>
75
+ /// DataKey::set_nonce(&env, &user, &value)
76
+ /// DataKey::has_nonce(&env, &user) // -> bool
77
+ /// DataKey::remove_nonce(&env, &user)
78
+ /// DataKey::set_or_remove_nonce(&env, &user, &opt)
79
+ /// DataKey::extend_nonce_ttl(&env, &user, threshold, extend_to) // manual TTL extension
80
+ /// ```
81
+ ///
82
+ /// # Storage Types (required, exactly one per variant)
83
+ /// - `#[instance(Type)]` - Stored with contract instance (no extend_ttl method generated)
84
+ /// - `#[persistent(Type)]` - Durable ledger entries (TTL auto-extended on get/set/has)
85
+ /// - `#[temporary(Type)]` - Short-lived entries
86
+ ///
87
+ /// # Variant Attributes (optional)
88
+ /// - `#[default(expr)]` - Default value; changes getter return from `Option<T>` to `T`
89
+ /// - `#[name("custom")]` - Override the generated function name base
90
+ /// - `#[no_ttl_extension]` - Disable automatic TTL extension for this persistent variant
91
+ #[proc_macro_attribute]
92
+ pub fn storage(_attr: TokenStream, item: TokenStream) -> TokenStream {
93
+ storage::generate_storage(item.into()).into()
94
+ }
95
+
96
+ // ============================================================================
97
+ // Error Macro
98
+ // ============================================================================
99
+
100
+ /// Generates a Soroban contract error enum with all required attributes and derives.
101
+ ///
102
+ /// This macro simplifies error enum definitions by automatically adding:
103
+ /// - `#[contracterror]` from soroban-sdk for Soroban compatibility
104
+ /// - `#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]` for standard error traits
105
+ /// - `#[repr(u32)]` for stable ABI representation
106
+ ///
107
+ /// # Discriminant Assignment
108
+ ///
109
+ /// Variants without explicit discriminants are automatically assigned sequential values
110
+ /// starting at 1. Explicit discriminants must be strictly increasing.
111
+ ///
112
+ /// # Constraints
113
+ ///
114
+ /// - All variants must be unit variants (no fields)
115
+ /// - Explicit discriminants must be valid `u32` integer literals
116
+ /// - Each discriminant must be greater than the previous one
117
+ ///
118
+ /// # Examples
119
+ ///
120
+ /// Basic usage with auto-assigned discriminants:
121
+ ///
122
+ /// ```ignore
123
+ /// #[contract_error]
124
+ /// pub enum MyError {
125
+ /// InvalidInput, // = 1
126
+ /// Unauthorized, // = 2
127
+ /// NotFound, // = 3
128
+ /// }
129
+ /// ```
130
+ ///
131
+ /// Mixed explicit and auto-assigned discriminants:
132
+ ///
133
+ /// ```ignore
134
+ /// #[contract_error]
135
+ /// pub enum MyError {
136
+ /// InvalidInput, // = 1
137
+ /// Unauthorized, // = 2
138
+ /// NotFound = 10, // = 10 (explicit)
139
+ /// Expired, // = 11
140
+ /// }
141
+ /// ```
142
+ #[proc_macro_attribute]
143
+ pub fn contract_error(_attr: TokenStream, item: TokenStream) -> TokenStream {
144
+ error::generate_error(item.into()).into()
145
+ }
146
+
147
+ // ============================================================================
148
+ // Ownable Macro
149
+ // ============================================================================
150
+
151
+ /// Generates ownable implementation with owner-based access control.
152
+ ///
153
+ /// Implements the `Ownable` trait and provides owner initialization and
154
+ /// ownership transfer functionality.
155
+ ///
156
+ /// # Example
157
+ /// ```ignore
158
+ /// #[ownable]
159
+ /// pub struct MyContract;
160
+ /// ```
161
+ ///
162
+ /// Generated code includes:
163
+ /// - `OwnableInitializer` trait impl - Use `<Self as OwnableInitializer>::init_owner(env, owner)` to initialize
164
+ /// - `Auth` trait impl - `authorizer(env)` returns the stored owner address
165
+ /// - `Ownable` trait impl
166
+ #[proc_macro_attribute]
167
+ pub fn ownable(_attr: TokenStream, item: TokenStream) -> TokenStream {
168
+ auth::generate_ownable_impl(item.into()).into()
169
+ }
170
+
171
+ // ============================================================================
172
+ // MultiSig Macro
173
+ // ============================================================================
174
+
175
+ /// Generates multisig implementation with self-owning access control.
176
+ ///
177
+ /// Implements the `MultiSig` trait and the `Auth` trait with self-owning pattern,
178
+ /// where the contract's own address is the authorizer. This allows multisig
179
+ /// quorum approval to serve as the authorizer for owner-protected operations
180
+ /// like TTL configuration and upgrades.
181
+ ///
182
+ /// # Example
183
+ /// ```ignore
184
+ /// #[multisig]
185
+ /// pub struct MyContract;
186
+ /// ```
187
+ ///
188
+ /// Generated code includes:
189
+ /// - `Auth` trait impl - `authorizer(env)` returns `env.current_contract_address()`
190
+ /// - `MultiSig` trait impl
191
+ #[proc_macro_attribute]
192
+ pub fn multisig(_attr: TokenStream, item: TokenStream) -> TokenStream {
193
+ auth::generate_multisig_impl(item.into()).into()
194
+ }
195
+
196
+ // ============================================================================
197
+ // Only Auth Macro
198
+ // ============================================================================
199
+
200
+ /// Restricts function access to the contract authorizer only.
201
+ ///
202
+ /// This attribute macro injects an auth check at the beginning of the function
203
+ /// using the `Auth` trait. The function will panic if called without authorization.
204
+ ///
205
+ /// Works with any contract that implements `Auth`, including both `Ownable` and
206
+ /// `MultiSig` contracts.
207
+ ///
208
+ /// # Requirements
209
+ /// - The function must have an `Env` parameter (by value or reference)
210
+ /// - The containing contract must implement the `Auth` trait
211
+ ///
212
+ /// # Example
213
+ /// ```ignore
214
+ /// #[ownable] // or implement `#[multisig]`
215
+ /// pub struct MyContract;
216
+ ///
217
+ /// #[soroban_sdk::contractimpl]
218
+ /// impl MyContract {
219
+ /// #[only_auth]
220
+ /// pub fn protected_action(env: Env) {
221
+ /// // Only the authorizer can execute this
222
+ /// }
223
+ /// }
224
+ /// ```
225
+ ///
226
+ /// Generated code (conceptual):
227
+ /// ```ignore
228
+ /// pub fn protected_action(env: Env) {
229
+ /// utils::auth::require_auth::<Self>(&env);
230
+ /// // Original function body
231
+ /// }
232
+ /// ```
233
+ #[proc_macro_attribute]
234
+ pub fn only_auth(_attr: TokenStream, item: TokenStream) -> TokenStream {
235
+ auth::prepend_only_auth_check(item.into()).into()
236
+ }
237
+
238
+ // ============================================================================
239
+ // RBAC Macros
240
+ // ============================================================================
241
+
242
+ /// Checks that the given account has the specified role.
243
+ ///
244
+ /// Injects a role check at the start of the function. Panics with
245
+ /// `RbacError::Unauthorized` if the account does not have the role (aligns with OpenZeppelin).
246
+ ///
247
+ /// # Security Warning
248
+ ///
249
+ /// **IMPORTANT**: This macro checks role membership but does NOT call
250
+ /// `require_auth()`. Use this macro when:
251
+ ///
252
+ /// 1. Your function already contains a `require_auth()` call for the account
253
+ /// 2. You need role-based access control without authorization enforcement
254
+ ///
255
+ /// If you need both role checking AND authorization, use `#[only_role]` instead.
256
+ ///
257
+ /// # Requirements
258
+ /// - The function must have an `Env` parameter
259
+ /// - The function must have a parameter matching the first macro arg (of type `Address` or `&Address`)
260
+ /// - The contract must implement `RoleBasedAccessControl` (which extends `Auth`)
261
+ ///
262
+ /// # Example
263
+ /// ```ignore
264
+ /// #[has_role(caller, "minter")]
265
+ /// pub fn mint(env: Env, caller: Address, amount: i128) { ... }
266
+ ///
267
+ /// // Or with a &str constant:
268
+ /// const MINTER_ROLE: &str = "minter";
269
+ /// #[has_role(caller, MINTER_ROLE)]
270
+ /// pub fn mint(env: Env, caller: Address, amount: i128) { ... }
271
+ /// ```
272
+ ///
273
+ /// # Generated code
274
+ /// ```ignore
275
+ /// pub fn mint(env: Env, caller: Address, amount: i128) {
276
+ /// utils::rbac::ensure_role::<Self>(&env, &soroban_sdk::Symbol::new(&env, "minter"), &caller);
277
+ /// // Original function body (no require_auth)
278
+ /// }
279
+ /// ```
280
+ #[proc_macro_attribute]
281
+ pub fn has_role(attr: TokenStream, item: TokenStream) -> TokenStream {
282
+ rbac::generate_role_check(attr.into(), item.into(), false).into()
283
+ }
284
+
285
+ /// Checks that the given account has the specified role and requires auth.
286
+ ///
287
+ /// Same as `#[has_role]` but also calls `account.require_auth()` to ensure
288
+ /// the caller has authorized the transaction.
289
+ ///
290
+ /// **IMPORTANT**: This macro both checks role membership AND enforces
291
+ /// authorization. In Stellar contracts, duplicate `require_auth()` calls for
292
+ /// the same account will cause panics. If your function already contains a
293
+ /// `require_auth()` call for the same account, use `#[has_role]` instead to
294
+ /// avoid duplicate authorization checks.
295
+ ///
296
+ /// # Requirements
297
+ /// Same as `#[has_role]`.
298
+ ///
299
+ /// # Example
300
+ /// ```ignore
301
+ /// #[only_role(caller, "minter")]
302
+ /// pub fn mint(env: Env, caller: Address, amount: i128) { ... }
303
+ ///
304
+ /// // Or with a &str constant: #[only_role(caller, MINTER_ROLE)]
305
+ /// ```
306
+ ///
307
+ /// # Generated code
308
+ /// ```ignore
309
+ /// pub fn mint(env: Env, caller: Address, amount: i128) {
310
+ /// utils::rbac::ensure_role::<Self>(&env, &soroban_sdk::Symbol::new(&env, "minter"), &caller);
311
+ /// caller.require_auth();
312
+ /// // Original function body
313
+ /// }
314
+ /// ```
315
+ #[proc_macro_attribute]
316
+ pub fn only_role(attr: TokenStream, item: TokenStream) -> TokenStream {
317
+ rbac::generate_role_check(attr.into(), item.into(), true).into()
318
+ }
319
+
320
+ // ============================================================================
321
+ // TTL Configuration Macro
322
+ // ============================================================================
323
+
324
+ /// Generates TtlConfigurable trait implementation.
325
+ ///
326
+ /// This macro implements the `TtlConfigurable` trait for a contract struct,
327
+ /// providing TTL configuration management with auth-based access control.
328
+ ///
329
+ /// # Requirements
330
+ /// The contract must implement the `Auth` trait (typically via `#[ownable]` or `#[multisig]`).
331
+ ///
332
+ /// # Example
333
+ /// ```ignore
334
+ /// #[ownable] // or `#[multisig]` for self-owning contracts
335
+ /// #[ttl_configurable]
336
+ /// pub struct MyContract;
337
+ /// ```
338
+ ///
339
+ /// Generated code includes:
340
+ /// - `set_ttl_configs(env, instance, persistent)` - Set TTL configs (auth required)
341
+ /// - `ttl_configs(env)` - Get current TTL configs (instance, persistent)
342
+ /// - `freeze_ttl_configs(env)` - Permanently freeze TTL configs (auth required)
343
+ /// - `is_ttl_configs_frozen(env)` - Check if TTL configs are frozen
344
+ #[proc_macro_attribute]
345
+ pub fn ttl_configurable(_attr: TokenStream, item: TokenStream) -> TokenStream {
346
+ ttl_configurable::generate_ttl_configurable_impl(item.into()).into()
347
+ }
348
+
349
+ // ============================================================================
350
+ // TTL Extendable Macro
351
+ // ============================================================================
352
+
353
+ /// Generates TtlExtendable trait implementation for manual instance TTL extension.
354
+ ///
355
+ /// This macro implements the `TtlExtendable` trait, providing a public
356
+ /// `extend_instance_ttl` function that allows external callers to extend
357
+ /// the contract's instance storage TTL.
358
+ ///
359
+ /// # Example
360
+ /// ```ignore
361
+ /// #[contract]
362
+ /// #[ttl_extendable]
363
+ /// pub struct MyContract;
364
+ /// ```
365
+ ///
366
+ /// Generated code includes:
367
+ /// - `extend_instance_ttl(env, threshold, extend_to)` - Extends instance TTL
368
+ #[proc_macro_attribute]
369
+ pub fn ttl_extendable(_attr: TokenStream, item: TokenStream) -> TokenStream {
370
+ ttl_extendable::generate_ttl_extendable_impl(item.into()).into()
371
+ }
372
+
373
+ // ============================================================================
374
+ // Contract Impl Macro
375
+ // ============================================================================
376
+
377
+ /// Wraps `#[soroban_sdk::contractimpl]` with automatic instance TTL extension.
378
+ ///
379
+ /// This macro applies `#[soroban_sdk::contractimpl]` and injects TTL extension logic
380
+ /// at the beginning of each contract entry function to keep the contract instance alive.
381
+ ///
382
+ /// # Requirements
383
+ /// - The contract struct must have `#[ttl_configurable]` applied to provide `ttl_configs()`
384
+ /// - Methods must have an `Env` parameter to receive TTL extension
385
+ ///
386
+ /// # Behavior
387
+ /// - **Inherent impls** (`impl MyContract`): Only public methods receive TTL extension
388
+ /// - **Trait impls** (`impl SomeTrait for MyContract`): All methods receive TTL extension
389
+ /// - Methods without an `Env` parameter are skipped
390
+ ///
391
+ /// # Example
392
+ /// ```ignore
393
+ /// #[contract]
394
+ /// #[ttl_configurable]
395
+ /// pub struct MyContract;
396
+ ///
397
+ /// #[contract_impl]
398
+ /// impl MyContract {
399
+ /// pub fn my_method(env: &Env) {
400
+ /// // TTL extension is automatically injected here
401
+ /// // ... your code
402
+ /// }
403
+ /// }
404
+ /// ```
405
+ ///
406
+ /// Generated code (conceptual):
407
+ /// ```ignore
408
+ /// #[soroban_sdk::contractimpl]
409
+ /// impl MyContract {
410
+ /// pub fn my_method(env: &Env) {
411
+ /// utils::ttl_configurable::extend_instance_ttl(env);
412
+ /// // ... your code
413
+ /// }
414
+ /// }
415
+ /// ```
416
+ #[proc_macro_attribute]
417
+ pub fn contract_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
418
+ contract_ttl::contractimpl_with_ttl(attr.into(), item.into()).into()
419
+ }
420
+
421
+ // ============================================================================
422
+ // Contract Trait Macro
423
+ // ============================================================================
424
+
425
+ /// Wraps `#[soroban_sdk::contracttrait]` with automatic instance TTL extension.
426
+ ///
427
+ /// This macro applies `#[soroban_sdk::contracttrait]` and injects TTL extension logic
428
+ /// at the beginning of each default trait method to keep the contract instance alive.
429
+ ///
430
+ /// # Requirements
431
+ /// - The implementing contract must have `#[ttl_configurable]` applied
432
+ /// - Methods must have an `Env` parameter to receive TTL extension
433
+ /// - Only methods with default implementations are processed
434
+ ///
435
+ /// # Behavior
436
+ /// - All default methods with an `Env` parameter receive TTL extension
437
+ /// - Methods without a body (abstract methods) are not modified
438
+ /// - Methods without an `Env` parameter are skipped
439
+ ///
440
+ /// # Example
441
+ /// ```ignore
442
+ /// #[contract_trait]
443
+ /// pub trait MyTrait {
444
+ /// /// This method will have TTL extension injected
445
+ /// fn my_method(env: &Env) {
446
+ /// // TTL extension is automatically injected here
447
+ /// // ... your code
448
+ /// }
449
+ ///
450
+ /// /// Abstract methods are not modified
451
+ /// fn abstract_method(env: &Env) -> u32;
452
+ /// }
453
+ /// ```
454
+ ///
455
+ /// Generated code (conceptual):
456
+ /// ```ignore
457
+ /// #[soroban_sdk::contracttrait]
458
+ /// pub trait MyTrait {
459
+ /// fn my_method(env: &Env) {
460
+ /// utils::ttl_configurable::extend_instance_ttl(env);
461
+ /// // ... your code
462
+ /// }
463
+ ///
464
+ /// fn abstract_method(env: &Env) -> u32;
465
+ /// }
466
+ /// ```
467
+ #[proc_macro_attribute]
468
+ pub fn contract_trait(attr: TokenStream, item: TokenStream) -> TokenStream {
469
+ contract_ttl::contracttrait_with_ttl(attr.into(), item.into()).into()
470
+ }
471
+
472
+ // ============================================================================
473
+ // Upgradeable Macro
474
+ // ============================================================================
475
+
476
+ /// Generates upgradeable implementation using `Upgradeable` or `UpgradeableRbac` traits.
477
+ ///
478
+ /// `Upgradeable` uses Auth directly; `UpgradeableRbac` layers RoleBased
479
+ /// access control on top of Auth.
480
+ ///
481
+ /// # Requirements
482
+ /// - `Upgradeable` (default): contract must implement `Auth` (via `#[ownable]` or `#[multisig]`)
483
+ /// - `UpgradeableRbac` (with `rbac`): contract must implement both `Auth` and `RoleBasedAccessControl` (e.g. from OApp)
484
+ /// - By default, requires manual `UpgradeableInternal` implementation
485
+ /// - With `no_migration` flag, auto-generates a no-op `UpgradeableInternal` impl
486
+ ///
487
+ /// # Options
488
+ /// - `#[upgradeable]` - Implements Upgradeable, requires manual `UpgradeableInternal` (safety by default)
489
+ /// - `#[upgradeable(no_migration)]` - Implements Upgradeable, auto-generates no-op `UpgradeableInternal`
490
+ /// - `#[upgradeable(rbac)]` - Implements UpgradeableRbac, requires manual `UpgradeableInternal`
491
+ /// - `#[upgradeable(rbac, no_migration)]` - Implements UpgradeableRbac, auto-generates no-op `UpgradeableInternal`
492
+ ///
493
+ /// # Example
494
+ /// ```ignore
495
+ /// // Implements Upgradeable (default)
496
+ /// #[ownable]
497
+ /// #[upgradeable]
498
+ /// pub struct MyContract;
499
+ ///
500
+ /// impl utils::upgradeable::UpgradeableInternal for MyContract {
501
+ /// type MigrationData = MyMigrationParams;
502
+ ///
503
+ /// fn __migrate(env: &Env, migration_data: &Self::MigrationData) {
504
+ /// // Custom migration logic here
505
+ /// }
506
+ /// }
507
+ ///
508
+ /// // Implements Upgradeable (no migration)
509
+ /// #[ownable]
510
+ /// #[upgradeable(no_migration)]
511
+ /// pub struct SimpleContract;
512
+ ///
513
+ /// // Implements UpgradeableRbac (layered)
514
+ /// #[ownable]
515
+ /// #[upgradeable(rbac)]
516
+ /// pub struct RbacContract;
517
+ ///
518
+ /// impl utils::upgradeable::UpgradeableInternal for RbacContract {
519
+ /// type MigrationData = MyMigrationParams;
520
+ ///
521
+ /// fn __migrate(env: &Env, migration_data: &Self::MigrationData) {
522
+ /// // Custom migration logic here
523
+ /// }
524
+ /// }
525
+ ///
526
+ /// // Implements UpgradeableRbac (no migration)
527
+ /// #[ownable]
528
+ /// #[upgradeable(rbac, no_migration)]
529
+ /// pub struct SimpleRbacContract;
530
+ /// ```
531
+ ///
532
+ /// Generated code includes:
533
+ /// - `upgrade` / `migrate` - Auth-based or Auth + RoleBased depending on options
534
+ /// - `contractmeta!` with `binver` set to the Cargo package version (if not 0.0.0)
535
+ #[proc_macro_attribute]
536
+ pub fn upgradeable(attr: TokenStream, item: TokenStream) -> TokenStream {
537
+ upgradeable::generate_upgradeable_impl(attr.into(), item.into()).into()
538
+ }
539
+
540
+ // ============================================================================
541
+ // LZ Contract Wrapper Macro
542
+ // ============================================================================
543
+
544
+ /// Wrapper macro that combines common LayerZero contract attributes.
545
+ ///
546
+ /// This macro simplifies contract declarations by combining multiple commonly
547
+ /// used macros into a single attribute.
548
+ ///
549
+ /// # Default (no options)
550
+ /// `#[lz_contract]` generates:
551
+ /// - `#[contract]` - Soroban contract
552
+ /// - `#[ttl_configurable]` - TTL configuration with auth
553
+ /// - `#[ttl_extendable]` - Manual TTL extension
554
+ /// - `#[ownable]` - Single-owner access control
555
+ ///
556
+ /// # Options
557
+ /// - `upgradeable(...)` - Adds `#[upgradeable(...)]`; content is passed verbatim to the upgradeable macro
558
+ /// - `multisig` - Uses `#[multisig]` instead of `#[ownable]`
559
+ ///
560
+ /// # Examples
561
+ /// ```ignore
562
+ /// // Basic contract with ownable auth
563
+ /// #[lz_contract]
564
+ /// pub struct EndpointV2;
565
+ ///
566
+ /// // Contract with upgrade support (requires manual UpgradeableInternal)
567
+ /// #[lz_contract(upgradeable)]
568
+ /// pub struct DVNFeeLib;
569
+ ///
570
+ /// // Contract with upgrade support and no migration (auto no-op impl)
571
+ /// #[lz_contract(upgradeable(no_migration))]
572
+ /// pub struct DVNFeeLib;
573
+ ///
574
+ /// // Contract with RBAC-based upgrade support
575
+ /// #[lz_contract(upgradeable(rbac))]
576
+ /// pub struct RbacOft;
577
+ ///
578
+ /// // Contract with multisig auth and upgrade support (no migration)
579
+ /// #[lz_contract(multisig, upgradeable(no_migration))]
580
+ /// pub struct DVN;
581
+ /// ```
582
+ #[proc_macro_attribute]
583
+ pub fn lz_contract(attr: TokenStream, item: TokenStream) -> TokenStream {
584
+ lz_contract::generate_lz_contract(attr.into(), item.into()).into()
585
+ }
@@ -0,0 +1,105 @@
1
+ //! LzContract wrapper macro for Stellar smart contracts.
2
+ //!
3
+ //! This module provides the `#[lz_contract]` macro which combines commonly used
4
+ //! LayerZero contract attributes into a single macro invocation.
5
+
6
+ use proc_macro2::TokenStream;
7
+ use quote::quote;
8
+ use syn::{
9
+ parenthesized,
10
+ parse::{Parse, ParseStream},
11
+ Error, Ident, ItemStruct, Token,
12
+ };
13
+
14
+ /// Configuration options for the `#[lz_contract]` macro.
15
+ #[derive(Debug, Default)]
16
+ pub struct LzContractConfig {
17
+ /// If true, adds `#[upgradeable]` for contract upgrade support.
18
+ pub upgradeable: bool,
19
+ /// Raw tokens inside `upgradeable(...)`, passed verbatim to the upgradeable macro.
20
+ /// Empty when `upgradeable` has no parentheses.
21
+ pub upgradeable_attr: TokenStream,
22
+ /// If true, uses `#[multisig]` instead of `#[ownable]` for auth.
23
+ pub multisig: bool,
24
+ }
25
+
26
+ impl Parse for LzContractConfig {
27
+ fn parse(input: ParseStream) -> syn::Result<Self> {
28
+ let mut config = Self::default();
29
+ if input.is_empty() {
30
+ return Ok(config);
31
+ }
32
+
33
+ // Parse comma-separated items, handling nested parentheses for upgradeable(no_migration)
34
+ while !input.is_empty() {
35
+ let ident: Ident = input.parse()?;
36
+
37
+ match ident.to_string().as_str() {
38
+ "upgradeable" => {
39
+ config.upgradeable = true;
40
+ // Pass through optional (...) content verbatim to the upgradeable macro
41
+ if input.peek(syn::token::Paren) {
42
+ let content;
43
+ parenthesized!(content in input);
44
+ config.upgradeable_attr = content.parse()?;
45
+ }
46
+ }
47
+ "multisig" => config.multisig = true,
48
+ _ => {
49
+ return Err(Error::new(ident.span(), "expected one of `upgradeable`, `multisig`"));
50
+ }
51
+ }
52
+
53
+ // Consume optional trailing comma
54
+ if input.peek(Token![,]) {
55
+ let _: Token![,] = input.parse()?;
56
+ }
57
+ }
58
+
59
+ Ok(config)
60
+ }
61
+ }
62
+
63
+ /// Generates a complete LayerZero contract with common macro attributes.
64
+ ///
65
+ /// # Default (no options)
66
+ /// Generates:
67
+ /// - `#[soroban_sdk::contract]` - Soroban contract
68
+ /// - `#[common_macros::ttl_configurable]` - TTL configuration with auth
69
+ /// - `#[common_macros::ttl_extendable]` - Manual TTL extension
70
+ /// - `#[common_macros::ownable]` - Single-owner access control
71
+ ///
72
+ /// # Options
73
+ /// - `upgradeable(...)` - Adds `#[upgradeable(...)]`; content is passed verbatim to the upgradeable macro
74
+ /// - `multisig` - Uses `#[multisig]` instead of `#[ownable]`
75
+ pub fn generate_lz_contract(attr: TokenStream, input: TokenStream) -> TokenStream {
76
+ let config: LzContractConfig =
77
+ syn::parse2(attr).unwrap_or_else(|e| panic!("failed to parse lz_contract config: {}", e));
78
+ let item: ItemStruct = syn::parse2(input).unwrap_or_else(|e| panic!("failed to parse struct: {}", e));
79
+
80
+ let auth = if config.multisig {
81
+ quote! { #[common_macros::multisig] }
82
+ } else {
83
+ quote! { #[common_macros::ownable] }
84
+ };
85
+
86
+ let upgrade = if config.upgradeable {
87
+ if config.upgradeable_attr.is_empty() {
88
+ quote! { #[common_macros::upgradeable] }
89
+ } else {
90
+ let upgradeable_attr = &config.upgradeable_attr;
91
+ quote! { #[common_macros::upgradeable(#upgradeable_attr)] }
92
+ }
93
+ } else {
94
+ quote! {}
95
+ };
96
+
97
+ quote! {
98
+ #[soroban_sdk::contract]
99
+ #[common_macros::ttl_configurable]
100
+ #[common_macros::ttl_extendable]
101
+ #auth
102
+ #upgrade
103
+ #item
104
+ }
105
+ }