@layerzerolabs/protocol-stellar-v2 0.2.20 → 0.2.22

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 (198) hide show
  1. package/.turbo/turbo-build.log +783 -802
  2. package/.turbo/turbo-lint.log +320 -157
  3. package/.turbo/turbo-test.log +1414 -1457
  4. package/Cargo.lock +109 -108
  5. package/Cargo.toml +32 -18
  6. package/contracts/common-macros/Cargo.toml +7 -7
  7. package/contracts/common-macros/src/auth.rs +18 -37
  8. package/contracts/common-macros/src/contract_ttl.rs +2 -2
  9. package/contracts/common-macros/src/lib.rs +27 -10
  10. package/contracts/common-macros/src/lz_contract.rs +38 -7
  11. package/contracts/common-macros/src/storage.rs +251 -292
  12. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__auth__snapshot_generated_multisig_code.snap +6 -12
  13. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__auth__snapshot_generated_ownable_code.snap +12 -17
  14. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ttl_configurable__snapshot_generated_ttl_configurable_code.snap +2 -7
  15. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__upgradeable__snapshot_generated_upgradeable_code.snap +20 -9
  16. package/contracts/common-macros/src/tests/upgradeable.rs +26 -4
  17. package/contracts/common-macros/src/ttl_configurable.rs +2 -10
  18. package/contracts/common-macros/src/ttl_extendable.rs +2 -10
  19. package/contracts/common-macros/src/upgradeable.rs +56 -15
  20. package/contracts/common-macros/src/utils.rs +0 -9
  21. package/contracts/endpoint-v2/src/lib.rs +3 -2
  22. package/contracts/endpoint-v2/src/tests/endpoint_v2/clear.rs +2 -2
  23. package/contracts/endpoint-v2/src/tests/endpoint_v2/lz_receive_alert.rs +3 -3
  24. package/contracts/endpoint-v2/src/tests/endpoint_v2/send.rs +4 -4
  25. package/contracts/endpoint-v2/src/tests/endpoint_v2/set_delegate.rs +17 -5
  26. package/contracts/endpoint-v2/src/tests/endpoint_v2/set_zro.rs +4 -4
  27. package/contracts/endpoint-v2/src/tests/endpoint_v2/verify.rs +2 -2
  28. package/contracts/endpoint-v2/src/tests/message_lib_manager/register_library.rs +2 -2
  29. package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_receive_lib_timeout.rs +6 -6
  30. package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_receive_library.rs +67 -37
  31. package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_send_library.rs +5 -5
  32. package/contracts/endpoint-v2/src/tests/message_lib_manager/set_receive_library.rs +44 -54
  33. package/contracts/endpoint-v2/src/tests/message_lib_manager/set_receive_library_timeout.rs +7 -7
  34. package/contracts/endpoint-v2/src/tests/message_lib_manager/set_send_library.rs +8 -8
  35. package/contracts/endpoint-v2/src/tests/messaging_channel/burn.rs +3 -3
  36. package/contracts/endpoint-v2/src/tests/messaging_channel/nilify.rs +4 -4
  37. package/contracts/endpoint-v2/src/tests/messaging_channel/skip.rs +3 -3
  38. package/contracts/endpoint-v2/src/tests/messaging_composer/clear_compose.rs +2 -2
  39. package/contracts/endpoint-v2/src/tests/messaging_composer/lz_compose_alert.rs +3 -3
  40. package/contracts/endpoint-v2/src/tests/messaging_composer/send_compose.rs +2 -2
  41. package/contracts/layerzero-views/Cargo.toml +0 -1
  42. package/contracts/layerzero-views/src/layerzero_view.rs +1 -13
  43. package/contracts/macro-integration-tests/Cargo.toml +5 -15
  44. package/contracts/macro-integration-tests/tests/runtime/oapp/mod.rs +48 -0
  45. package/contracts/macro-integration-tests/tests/runtime/oapp/oapp_core.rs +170 -0
  46. package/contracts/macro-integration-tests/tests/runtime/oapp/options_type3.rs +154 -0
  47. package/contracts/macro-integration-tests/tests/runtime/oapp/receiver.rs +338 -0
  48. package/contracts/macro-integration-tests/tests/runtime/oapp/sender.rs +435 -0
  49. package/contracts/macro-integration-tests/tests/runtime.rs +1 -0
  50. package/contracts/macro-integration-tests/tests/ui/oapp/fail/custom_wrong_value.rs +8 -0
  51. package/contracts/macro-integration-tests/tests/ui/oapp/fail/custom_wrong_value.stderr +5 -0
  52. package/contracts/macro-integration-tests/tests/ui/oapp/fail/missing_lz_receive_internal.rs +8 -0
  53. package/contracts/macro-integration-tests/tests/ui/oapp/fail/missing_lz_receive_internal.stderr +71 -0
  54. package/contracts/macro-integration-tests/tests/ui/oapp/fail/non_struct_input.rs +10 -0
  55. package/contracts/macro-integration-tests/tests/ui/oapp/fail/non_struct_input.stderr +5 -0
  56. package/contracts/macro-integration-tests/tests/ui/oapp/fail/unknown_custom_option.rs +8 -0
  57. package/contracts/macro-integration-tests/tests/ui/oapp/fail/unknown_custom_option.stderr +5 -0
  58. package/contracts/macro-integration-tests/tests/ui/oapp/fail/wrong_key.rs +8 -0
  59. package/contracts/macro-integration-tests/tests/ui/oapp/fail/wrong_key.stderr +5 -0
  60. package/contracts/macro-integration-tests/tests/ui/oapp/pass/custom_all.rs +38 -0
  61. package/contracts/macro-integration-tests/tests/ui/oapp/pass/custom_single_trait.rs +96 -0
  62. package/contracts/macro-integration-tests/tests/ui/oapp/pass/minimal_contract.rs +64 -0
  63. package/contracts/macro-integration-tests/tests/ui/oapp/pass/struct_with_fields.rs +46 -0
  64. package/contracts/macro-integration-tests/tests/ui/ownable/fail/only_auth_missing_env.stderr +8 -0
  65. package/contracts/macro-integration-tests/tests/ui/ownable/pass/namespacing_and_imports.rs +1 -1
  66. package/contracts/macro-integration-tests/tests/ui/ownable/pass/only_auth_env_param_variants.rs +1 -1
  67. package/contracts/macro-integration-tests/tests/ui_oapp.rs +11 -0
  68. package/contracts/message-libs/message-lib-common/Cargo.toml +0 -1
  69. package/contracts/message-libs/message-lib-common/src/errors.rs +1 -1
  70. package/contracts/message-libs/treasury/Cargo.toml +0 -2
  71. package/contracts/message-libs/treasury/src/tests/treasury_tests.rs +2 -2
  72. package/contracts/message-libs/uln-302/src/tests/receive_uln302/effective_receive_uln_config.rs +2 -2
  73. package/contracts/message-libs/uln-302/src/tests/receive_uln302/set_default_receive_uln_configs.rs +2 -2
  74. package/contracts/message-libs/uln-302/src/tests/receive_uln302/verify.rs +2 -2
  75. package/contracts/message-libs/uln-302/src/tests/send_uln302/effective_executor_config.rs +2 -2
  76. package/contracts/message-libs/uln-302/src/tests/send_uln302/effective_send_uln_config.rs +2 -2
  77. package/contracts/message-libs/uln-302/src/tests/send_uln302/send.rs +7 -27
  78. package/contracts/message-libs/uln-302/src/tests/send_uln302/set_default_executor_configs.rs +2 -2
  79. package/contracts/message-libs/uln-302/src/tests/send_uln302/set_default_send_uln_configs.rs +2 -2
  80. package/contracts/oapps/counter/Cargo.toml +4 -6
  81. package/contracts/oapps/counter/integration_tests/utils.rs +19 -12
  82. package/contracts/oapps/oapp/src/errors.rs +1 -1
  83. package/contracts/oapps/oapp/src/interfaces/mod.rs +3 -0
  84. package/contracts/oapps/oapp/src/interfaces/oapp_msg_inspector.rs +47 -0
  85. package/contracts/oapps/oapp/src/lib.rs +1 -0
  86. package/contracts/oapps/oapp/src/macro_tests/test_macros.rs +4 -4
  87. package/contracts/oapps/oapp/src/oapp_core.rs +5 -5
  88. package/contracts/oapps/oapp/src/oapp_options_type3.rs +12 -4
  89. package/contracts/oapps/oapp/src/oapp_receiver.rs +14 -9
  90. package/contracts/oapps/oapp/src/tests/mod.rs +4 -4
  91. package/contracts/oapps/oapp/src/tests/{test_oapp_core.rs → oapp_core.rs} +4 -4
  92. package/contracts/oapps/oapp/src/tests/{test_oapp_options_type3.rs → oapp_options_type3.rs} +3 -4
  93. package/contracts/oapps/oapp-macros/Cargo.toml +8 -4
  94. package/contracts/oapps/oapp-macros/src/generators.rs +9 -34
  95. package/contracts/oapps/oapp-macros/src/lib.rs +3 -0
  96. package/contracts/oapps/oapp-macros/src/tests/mod.rs +2 -0
  97. package/contracts/oapps/oapp-macros/src/tests/oapp.rs +88 -0
  98. package/contracts/oapps/oapp-macros/src/tests/parse_custom_impls.rs +86 -0
  99. package/contracts/oapps/oapp-macros/src/tests/snapshots/oapp_macros__tests__oapp__snapshot_generate_oapp.snap +103 -0
  100. package/contracts/oapps/oft/integration-tests/utils.rs +28 -8
  101. package/contracts/oapps/oft/src/extensions/oft_fee.rs +136 -74
  102. package/contracts/oapps/oft/src/extensions/pausable.rs +44 -10
  103. package/contracts/oapps/oft/src/extensions/rate_limiter.rs +170 -130
  104. package/contracts/oapps/oft/src/oft.rs +19 -12
  105. package/contracts/oapps/oft/src/oft_types/lock_unlock.rs +1 -1
  106. package/contracts/oapps/oft/src/oft_types/mint_burn.rs +1 -1
  107. package/contracts/oapps/oft-core/Cargo.toml +1 -4
  108. package/contracts/oapps/oft-core/integration-tests/setup.rs +2 -2
  109. package/contracts/oapps/oft-core/integration-tests/utils.rs +21 -3
  110. package/contracts/oapps/oft-core/src/errors.rs +3 -2
  111. package/contracts/oapps/oft-core/src/events.rs +6 -0
  112. package/contracts/oapps/oft-core/src/lib.rs +1 -1
  113. package/contracts/oapps/oft-core/src/oft_core.rs +115 -60
  114. package/contracts/oapps/oft-core/src/storage.rs +7 -3
  115. package/contracts/oapps/oft-core/src/tests/mod.rs +1 -0
  116. package/contracts/oapps/oft-core/src/tests/test_decimals.rs +37 -2
  117. package/contracts/oapps/oft-core/src/tests/test_lz_receive.rs +2 -2
  118. package/contracts/oapps/oft-core/src/tests/test_msg_inspector.rs +323 -0
  119. package/contracts/oapps/oft-core/src/tests/test_send.rs +2 -2
  120. package/contracts/oapps/oft-core/src/tests/test_utils.rs +59 -14
  121. package/contracts/utils/Cargo.toml +0 -1
  122. package/contracts/utils/src/errors.rs +1 -1
  123. package/contracts/utils/src/multisig.rs +17 -8
  124. package/contracts/utils/src/ownable.rs +6 -6
  125. package/contracts/utils/src/testing_utils.rs +124 -54
  126. package/contracts/utils/src/tests/multisig.rs +12 -12
  127. package/contracts/utils/src/tests/ownable.rs +6 -6
  128. package/contracts/utils/src/tests/testing_utils.rs +50 -167
  129. package/contracts/utils/src/tests/ttl_configurable.rs +5 -5
  130. package/contracts/utils/src/tests/upgradeable.rs +1 -1
  131. package/contracts/utils/src/ttl_configurable.rs +10 -4
  132. package/contracts/utils/src/upgradeable.rs +5 -5
  133. package/contracts/workers/dvn/Cargo.toml +5 -6
  134. package/contracts/workers/dvn/src/dvn.rs +2 -12
  135. package/contracts/workers/dvn-fee-lib/Cargo.toml +1 -1
  136. package/contracts/workers/dvn-fee-lib/src/dvn_fee_lib.rs +37 -19
  137. package/contracts/workers/dvn-fee-lib/src/lib.rs +12 -2
  138. package/contracts/workers/dvn-fee-lib/src/tests/dvn_fee_lib.rs +15 -13
  139. package/contracts/workers/executor/Cargo.toml +3 -0
  140. package/contracts/workers/executor/src/executor.rs +2 -12
  141. package/contracts/workers/executor/src/lib.rs +2 -2
  142. package/contracts/workers/executor/src/tests/auth.rs +394 -0
  143. package/contracts/workers/executor/src/tests/executor.rs +410 -0
  144. package/contracts/workers/executor/src/tests/mod.rs +3 -0
  145. package/contracts/workers/executor/src/tests/setup.rs +250 -0
  146. package/contracts/workers/executor-fee-lib/Cargo.toml +5 -0
  147. package/contracts/workers/executor-fee-lib/src/executor_fee_lib.rs +1 -12
  148. package/contracts/workers/executor-fee-lib/src/lib.rs +8 -2
  149. package/contracts/workers/executor-helper/Cargo.toml +0 -1
  150. package/contracts/workers/price-feed/Cargo.toml +5 -0
  151. package/contracts/workers/price-feed/src/lib.rs +9 -4
  152. package/contracts/workers/price-feed/src/price_feed.rs +1 -11
  153. package/contracts/workers/worker/src/errors.rs +1 -1
  154. package/contracts/workers/worker/src/tests/setup.rs +1 -1
  155. package/contracts/workers/worker/src/tests/worker.rs +55 -41
  156. package/contracts/workers/worker/src/worker.rs +34 -25
  157. package/docs/error-spec.md +55 -0
  158. package/docs/layerzero-v2-on-stellar.md +447 -0
  159. package/docs/oapp-guide.md +212 -0
  160. package/docs/oft-guide.md +314 -0
  161. package/package.json +3 -3
  162. package/sdk/.turbo/turbo-test.log +260 -257
  163. package/sdk/dist/generated/bml.d.ts +3 -3
  164. package/sdk/dist/generated/bml.js +4 -4
  165. package/sdk/dist/generated/counter.d.ts +295 -295
  166. package/sdk/dist/generated/counter.js +43 -43
  167. package/sdk/dist/generated/dvn.d.ts +91 -91
  168. package/sdk/dist/generated/dvn.js +24 -24
  169. package/sdk/dist/generated/dvn_fee_lib.d.ts +92 -92
  170. package/sdk/dist/generated/dvn_fee_lib.js +25 -25
  171. package/sdk/dist/generated/endpoint.d.ts +99 -99
  172. package/sdk/dist/generated/endpoint.js +16 -16
  173. package/sdk/dist/generated/executor.d.ts +91 -91
  174. package/sdk/dist/generated/executor.js +24 -24
  175. package/sdk/dist/generated/executor_fee_lib.d.ts +92 -92
  176. package/sdk/dist/generated/executor_fee_lib.js +25 -25
  177. package/sdk/dist/generated/executor_helper.d.ts +3 -3
  178. package/sdk/dist/generated/executor_helper.js +4 -4
  179. package/sdk/dist/generated/layerzero_view.d.ts +186 -186
  180. package/sdk/dist/generated/layerzero_view.js +35 -35
  181. package/sdk/dist/generated/oft.d.ts +366 -352
  182. package/sdk/dist/generated/oft.js +74 -79
  183. package/sdk/dist/generated/price_feed.d.ts +198 -198
  184. package/sdk/dist/generated/price_feed.js +39 -39
  185. package/sdk/dist/generated/sml.d.ts +99 -99
  186. package/sdk/dist/generated/sml.js +16 -16
  187. package/sdk/dist/generated/treasury.d.ts +99 -99
  188. package/sdk/dist/generated/treasury.js +16 -16
  189. package/sdk/dist/generated/uln302.d.ts +99 -99
  190. package/sdk/dist/generated/uln302.js +16 -16
  191. package/sdk/dist/generated/upgrader.d.ts +3 -3
  192. package/sdk/dist/generated/upgrader.js +3 -3
  193. package/sdk/package.json +1 -1
  194. package/sdk/test/suites/localnet.ts +84 -20
  195. package/contracts/ERROR_SPEC.md +0 -51
  196. package/contracts/endpoint-v2/ARCHITECTURE.md +0 -233
  197. /package/contracts/oapps/oapp/src/tests/{test_oapp_receiver.rs → oapp_receiver.rs} +0 -0
  198. /package/contracts/oapps/oapp/src/tests/{test_oapp_sender.rs → oapp_sender.rs} +0 -0
@@ -6,198 +6,125 @@ use heck::ToSnakeCase;
6
6
  use itertools::Itertools;
7
7
  use proc_macro2::{Ident, TokenStream};
8
8
  use quote::{format_ident, quote};
9
- use syn::{Expr, Fields, FieldsNamed, Type, Variant};
9
+ use syn::{Attribute, Expr, Fields, FieldsNamed, Type, Variant};
10
10
 
11
11
  // ============================================================================
12
- // Types & Parsing
12
+ // Public API
13
13
  // ============================================================================
14
14
 
15
- /// Storage type parsed from `#[instance(Type)]`, `#[persistent(Type)]`, or `#[temporary(Type)]`.
16
- #[derive(Debug, Clone)]
17
- enum StorageType {
18
- Instance(Type),
19
- Persistent(Type),
20
- Temporary(Type),
21
- }
22
-
23
- impl StorageType {
24
- fn value_type(&self) -> &Type {
25
- match self {
26
- Self::Instance(t) | Self::Persistent(t) | Self::Temporary(t) => t,
27
- }
15
+ /// Generates the storage API from the `#[storage]` attribute macro.
16
+ pub fn generate_storage(attr: TokenStream, input: TokenStream) -> TokenStream {
17
+ if !attr.is_empty() {
18
+ panic!("the #[storage] attribute does not accept arguments");
28
19
  }
29
20
 
30
- fn is_instance(&self) -> bool {
31
- matches!(self, Self::Instance(_))
32
- }
21
+ let item_enum: syn::ItemEnum = syn::parse2(input).unwrap_or_else(|e| panic!("failed to parse enum: {}", e));
22
+ let enum_name = &item_enum.ident;
23
+ let vis = &item_enum.vis;
24
+
25
+ let variants: Vec<_> = item_enum.variants.iter().map(gen_enum_variant).collect();
26
+ let methods: Vec<_> = item_enum.variants.iter().map(|v| gen_accessor_methods(enum_name, v)).collect();
27
+
28
+ quote! {
29
+ #[soroban_sdk::contracttype]
30
+ #vis enum #enum_name { #(#variants,)* }
33
31
 
34
- fn is_persistent(&self) -> bool {
35
- matches!(self, Self::Persistent(_))
32
+ impl #enum_name { #(#methods)* }
36
33
  }
34
+ }
37
35
 
38
- /// Returns the storage accessor method name.
39
- fn accessor_name(&self) -> &'static str {
36
+ /// Checks if a type is a primitive (pass-by-value) type.
37
+ pub(crate) fn is_primitive_type(ty: &Type) -> bool {
38
+ const PRIMITIVES: &[&str] = &["u32", "i32", "u64", "i64", "u128", "i128", "bool"];
39
+ matches!(ty, Type::Path(p) if p.path.segments.len() == 1
40
+ && PRIMITIVES.contains(&p.path.segments[0].ident.to_string().as_str()))
41
+ }
42
+
43
+ // ============================================================================
44
+ // Types
45
+ // ============================================================================
46
+
47
+ /// Storage kind: instance, persistent, or temporary.
48
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
49
+ enum StorageKind {
50
+ Instance,
51
+ Persistent,
52
+ Temporary,
53
+ }
54
+
55
+ impl StorageKind {
56
+ fn name(self) -> &'static str {
40
57
  match self {
41
- Self::Instance(_) => "instance",
42
- Self::Persistent(_) => "persistent",
43
- Self::Temporary(_) => "temporary",
58
+ Self::Instance => "instance",
59
+ Self::Persistent => "persistent",
60
+ Self::Temporary => "temporary",
44
61
  }
45
62
  }
46
- }
47
63
 
48
- impl TryFrom<&syn::Attribute> for StorageType {
49
- type Error = String;
50
-
51
- fn try_from(attr: &syn::Attribute) -> std::result::Result<Self, Self::Error> {
52
- let ident = attr.path().get_ident().ok_or("identifier expected to be instance, persistent, or temporary")?;
53
- let value_type = attr.parse_args::<Type>().map_err(|e| format!("failed to parse value type: {}", e))?;
64
+ /// Generates `env.storage().{kind}()`.
65
+ fn accessor(self) -> TokenStream {
66
+ let method = format_ident!("{}", self.name());
67
+ quote! { env.storage().#method() }
68
+ }
54
69
 
55
- match ident.to_string().as_str() {
56
- "instance" => Ok(Self::Instance(value_type)),
57
- "persistent" => Ok(Self::Persistent(value_type)),
58
- "temporary" => Ok(Self::Temporary(value_type)),
59
- other => Err(format!("unknown storage type: {}", other)),
70
+ /// Generates TTL extension code.
71
+ ///
72
+ /// - `use_stored_config=true`: reads TTL config from storage (for auto-extension)
73
+ /// - `use_stored_config=false`: uses local `threshold`/`extend_to` variables (for manual)
74
+ fn gen_ttl_extension(self, use_stored_config: bool) -> TokenStream {
75
+ let accessor = self.accessor();
76
+ let extend_call = if self == Self::Instance {
77
+ quote! { #accessor.extend_ttl(threshold, extend_to); }
78
+ } else {
79
+ quote! { #accessor.extend_ttl(&key, threshold, extend_to); }
80
+ };
81
+
82
+ if use_stored_config {
83
+ let method = format_ident!("{}", self.name());
84
+ quote! {
85
+ if let Some(utils::ttl_configurable::TtlConfig { threshold, extend_to }) =
86
+ utils::ttl_configurable::TtlConfigStorage::#method(env)
87
+ {
88
+ #extend_call
89
+ }
90
+ }
91
+ } else {
92
+ extend_call
60
93
  }
61
94
  }
62
95
  }
63
96
 
64
- /// Parsed configuration for a single storage enum variant.
97
+ /// Parsed configuration for a storage enum variant.
65
98
  #[derive(Debug, Clone)]
66
- pub(crate) struct StorageVariantConfig {
99
+ struct VariantConfig {
67
100
  name: String,
68
- storage_type: StorageType,
101
+ kind: StorageKind,
102
+ value_type: Type,
69
103
  default_value: Option<Expr>,
70
- no_ttl_extension: bool,
104
+ auto_ttl: bool,
71
105
  }
72
106
 
73
- impl StorageVariantConfig {
74
- /// Returns generated function names for this storage variant.
75
- fn function_names(&self) -> FunctionNames {
107
+ impl VariantConfig {
108
+ /// Returns (getter, setter, remover, set_or_remove, has, ttl_extender) function names.
109
+ fn method_names(&self) -> (Ident, Ident, Ident, Ident, Ident, Ident) {
76
110
  let base = &self.name;
77
- FunctionNames {
78
- getter: format_ident!("{}", base),
79
- setter: format_ident!("set_{}", base),
80
- remover: format_ident!("remove_{}", base),
81
- set_or_remove: format_ident!("set_or_remove_{}", base),
82
- ttl_extender: format_ident!("extend_{}_ttl", base),
83
- has: format_ident!("has_{}", base),
84
- }
111
+ (
112
+ format_ident!("{}", base),
113
+ format_ident!("set_{}", base),
114
+ format_ident!("remove_{}", base),
115
+ format_ident!("set_or_remove_{}", base),
116
+ format_ident!("has_{}", base),
117
+ format_ident!("extend_{}_ttl", base),
118
+ )
85
119
  }
86
120
  }
87
121
 
88
- impl TryFrom<&Variant> for StorageVariantConfig {
89
- type Error = String;
90
-
91
- fn try_from(variant: &Variant) -> std::result::Result<Self, Self::Error> {
92
- let attrs = &variant.attrs;
93
-
94
- // Known attribute identifiers for this macro ("doc" allows /// comments on variants)
95
- const KNOWN_ATTRS: &[&str] =
96
- &["doc", "instance", "persistent", "temporary", "default", "name", "no_ttl_extension"];
97
-
98
- // Check for unsupported attributes
99
- if let Some(unknown) = attrs
100
- .iter()
101
- .filter_map(|attr| attr.path().get_ident())
102
- .find(|ident| !KNOWN_ATTRS.contains(&ident.to_string().as_str()))
103
- {
104
- return Err(format!(
105
- "unknown attribute '{}' on variant '{}'. Supported attributes are: {}",
106
- unknown,
107
- variant.ident,
108
- KNOWN_ATTRS.join(", ")
109
- ));
110
- }
111
-
112
- // Parse storage type (exactly one required)
113
- let storage_type =
114
- attrs.iter().filter_map(|attr| StorageType::try_from(attr).ok()).exactly_one().map_err(|e| {
115
- format!(
116
- "storage type must be specified exactly once as \
117
- '#[instance(Type)]', '#[persistent(Type)]', or '#[temporary(Type)]': {}",
118
- e
119
- )
120
- })?;
121
-
122
- // Parse optional default value
123
- let default_value = attrs
124
- .iter()
125
- .filter(|attr| attr.path().is_ident("default"))
126
- .at_most_one()
127
- .map_err(|_| "multiple default values specified")?
128
- .map(|attr| attr.parse_args::<Expr>())
129
- .transpose()
130
- .map_err(|e| format!("failed to parse default value: {}", e))?;
131
-
132
- // Parse optional name override for storage key generation
133
- // If no #[name("custom_name")] attribute is provided, defaults to snake_case of variant name
134
- let name = attrs
135
- .iter()
136
- .filter(|attr| attr.path().is_ident("name"))
137
- .at_most_one()
138
- .map_err(|_| "multiple name attributes specified")?
139
- .map(|attr| attr.parse_args::<syn::LitStr>().map(|lit| lit.value()))
140
- .transpose()
141
- .map_err(|e| format!("failed to parse name attribute: {}", e))?
142
- .unwrap_or_else(|| variant.ident.to_string().to_snake_case());
143
-
144
- // Parse optional #[no_ttl_extension] to skip automatic TTL extension (persistent only)
145
- let no_ttl_extension = attrs
146
- .iter()
147
- .filter(|attr| attr.path().is_ident("no_ttl_extension"))
148
- .at_most_one()
149
- .map_err(|_| "multiple #[no_ttl_extension] attributes specified")?
150
- .is_some();
151
- if no_ttl_extension && !storage_type.is_persistent() {
152
- return Err("#[no_ttl_extension] can only be used with #[persistent(...)] storage".to_string());
153
- }
154
-
155
- Ok(Self { storage_type, default_value, name, no_ttl_extension })
156
- }
157
- }
158
-
159
- /// Generated function names for a storage variant.
160
- struct FunctionNames {
161
- getter: Ident,
162
- setter: Ident,
163
- remover: Ident,
164
- set_or_remove: Ident,
165
- ttl_extender: Ident,
166
- has: Ident,
167
- }
168
-
169
122
  // ============================================================================
170
- // Code Generation
123
+ // Enum Variant Code Generation
171
124
  // ============================================================================
172
125
 
173
- /// Generates the storage API from the `#[storage]` attribute macro.
174
- pub(crate) fn generate_storage(attr: TokenStream, input: TokenStream) -> TokenStream {
175
- let item_enum: syn::ItemEnum = syn::parse2(input).unwrap_or_else(|e| panic!("failed to parse enum: {}", e));
176
-
177
- // Ensure no arguments are passed to #[storage]
178
- if !attr.is_empty() {
179
- panic!("the #[storage] attribute does not accept arguments");
180
- }
181
-
182
- let enum_name = &item_enum.ident;
183
- let contracttype_variants: Vec<_> = item_enum.variants.iter().map(gen_contracttype_variant).collect();
184
- let inherent_impls: Vec<_> = item_enum.variants.iter().map(|v| gen_storage_functions(enum_name, v)).collect();
185
-
186
- let visibility = &item_enum.vis;
187
- quote! {
188
- #[soroban_sdk::contracttype]
189
- #visibility enum #enum_name {
190
- #(#contracttype_variants,)*
191
- }
192
-
193
- impl #enum_name {
194
- #(#inherent_impls)*
195
- }
196
- }
197
- }
198
-
199
126
  /// Generates contracttype enum variant: `Variant` or `Variant(Type1, Type2, ...)`.
200
- fn gen_contracttype_variant(variant: &Variant) -> TokenStream {
127
+ fn gen_enum_variant(variant: &Variant) -> TokenStream {
201
128
  let name = &variant.ident;
202
129
  match &variant.fields {
203
130
  Fields::Unit => quote! { #name },
@@ -209,125 +136,94 @@ fn gen_contracttype_variant(variant: &Variant) -> TokenStream {
209
136
  }
210
137
  }
211
138
 
212
- /// Generates all storage accessor functions for a variant.
213
- fn gen_storage_functions(enum_name: &Ident, variant: &Variant) -> TokenStream {
214
- let config = StorageVariantConfig::try_from(variant)
139
+ /// Generates all storage accessor methods for a variant.
140
+ fn gen_accessor_methods(enum_name: &Ident, variant: &Variant) -> TokenStream {
141
+ let config = VariantConfig::try_from(variant)
215
142
  .unwrap_or_else(|e| panic!("failed to parse storage variant for {}: {}", variant.ident, e));
216
143
 
217
- let FunctionNames { getter, setter, remover, set_or_remove, has, ttl_extender } = config.function_names();
144
+ let (getter, setter, remover, set_or_remove, has, ttl_extender) = config.method_names();
145
+ let params = gen_params(variant);
146
+ let args = gen_args(variant);
147
+ let key = gen_key(enum_name, variant);
148
+ let accessor = config.kind.accessor();
149
+ let value_type = &config.value_type;
218
150
 
219
- let fn_params = gen_fn_params(variant);
220
- let fn_args = gen_fn_args(variant);
221
- let storage_key = gen_storage_key(variant, enum_name);
222
- let storage_acc = gen_storage_accessor(&config.storage_type);
223
- let value_type = config.storage_type.value_type();
151
+ // TTL extension tokens
152
+ let auto_ttl = if config.auto_ttl { config.kind.gen_ttl_extension(true) } else { TokenStream::new() };
153
+ let manual_ttl = config.kind.gen_ttl_extension(false);
224
154
 
225
- // Automatic TTL extension for persistent storage (unless #[no_ttl_extension] is specified)
226
- let auto_ttl_extend = if config.storage_type.is_persistent() && !config.no_ttl_extension {
227
- gen_ttl_extension(&config.storage_type, true)
155
+ // Getter return type and expression
156
+ let (ret_type, ret_expr) = match &config.default_value {
157
+ Some(default) => (quote! { #value_type }, quote! { value.unwrap_or(#default) }),
158
+ None => (quote! { Option<#value_type> }, quote! { value }),
159
+ };
160
+ let ttl_on_get = if config.auto_ttl {
161
+ quote! { if value.is_some() { #auto_ttl } }
228
162
  } else {
229
163
  TokenStream::new()
230
164
  };
231
165
 
232
- let getter_impl = gen_getter_fn(
233
- &getter,
234
- &fn_params,
235
- &storage_key,
236
- &storage_acc,
237
- value_type,
238
- &config.default_value,
239
- &auto_ttl_extend,
240
- );
241
-
242
- // Manual TTL extension (user provides threshold/extend_to)
243
- let manual_ttl_extend = gen_ttl_extension(&config.storage_type, false);
244
-
245
- // Has implementation (conditionally includes TTL extension)
246
- let has_impl = if auto_ttl_extend.is_empty() {
247
- quote! { #storage_acc.has(&key) }
166
+ // Has check body
167
+ let has_body = if config.auto_ttl {
168
+ quote! { let exists = #accessor.has(&key); if exists { #auto_ttl } exists }
248
169
  } else {
249
- quote! {
250
- let exists = #storage_acc.has(&key);
251
- if exists { #auto_ttl_extend }
252
- exists
253
- }
170
+ quote! { #accessor.has(&key) }
254
171
  };
255
172
 
256
173
  quote! {
257
- #getter_impl
174
+ pub fn #getter(#params) -> #ret_type {
175
+ let key = #key;
176
+ let value = #accessor.get::<_, #value_type>(&key);
177
+ #ttl_on_get
178
+ #ret_expr
179
+ }
258
180
 
259
- pub fn #setter(#fn_params, value: &#value_type) {
260
- let key = #storage_key;
261
- #storage_acc.set(&key, value);
262
- #auto_ttl_extend
181
+ pub fn #setter(#params, value: &#value_type) {
182
+ let key = #key;
183
+ #accessor.set(&key, value);
184
+ #auto_ttl
263
185
  }
264
186
 
265
- pub fn #remover(#fn_params) {
266
- let key = #storage_key;
267
- #storage_acc.remove(&key);
187
+ pub fn #remover(#params) {
188
+ let key = #key;
189
+ #accessor.remove(&key);
268
190
  }
269
191
 
270
- pub fn #set_or_remove(#fn_params, value: &Option<#value_type>) {
192
+ pub fn #set_or_remove(#params, value: &Option<#value_type>) {
271
193
  match value.as_ref() {
272
- Some(v) => Self::#setter(#fn_args, v),
273
- None => Self::#remover(#fn_args),
194
+ Some(v) => Self::#setter(#args, v),
195
+ None => Self::#remover(#args),
274
196
  }
275
197
  }
276
198
 
277
- pub fn #has(#fn_params) -> bool {
278
- let key = #storage_key;
279
- #has_impl
199
+ pub fn #has(#params) -> bool {
200
+ let key = #key;
201
+ #has_body
280
202
  }
281
203
 
282
- pub fn #ttl_extender(#fn_params, threshold: u32, extend_to: u32) {
283
- let key = #storage_key;
284
- #manual_ttl_extend
204
+ pub fn #ttl_extender(#params, threshold: u32, extend_to: u32) {
205
+ let key = #key;
206
+ #manual_ttl
285
207
  }
286
208
  }
287
209
  }
288
210
 
289
- /// Generates the storage accessor: `env.storage().instance()` etc.
290
- fn gen_storage_accessor(storage_type: &StorageType) -> TokenStream {
291
- let method = format_ident!("{}", storage_type.accessor_name());
292
- quote! { env.storage().#method() }
293
- }
294
-
295
- /// Generates TTL extension code.
296
- /// - use_stored_config=true: fetches TTL config from TtlConfigStorage, only extends if config is set
297
- /// - use_stored_config=false: uses local variables `threshold` and `extend_to` (for manual extension)
298
- fn gen_ttl_extension(storage_type: &StorageType, use_stored_config: bool) -> TokenStream {
299
- let accessor = gen_storage_accessor(storage_type);
300
- let extend_call = if storage_type.is_instance() {
301
- quote! { #accessor.extend_ttl(threshold, extend_to); }
302
- } else {
303
- quote! { #accessor.extend_ttl(&key, threshold, extend_to); }
304
- };
211
+ // ============================================================================
212
+ // Parameter & Key Generation Helpers
213
+ // ============================================================================
305
214
 
306
- if use_stored_config {
307
- let method = format_ident!("{}", storage_type.accessor_name());
308
- quote! {
309
- if let Some(utils::ttl_configurable::TtlConfig { threshold, extend_to }) = utils::ttl_configurable::TtlConfigStorage::#method(env) {
310
- #extend_call
311
- }
312
- }
313
- } else {
314
- extend_call
215
+ /// Extracts (name, type) pairs from variant fields.
216
+ fn extract_fields(variant: &Variant) -> Vec<(&Ident, &Type)> {
217
+ match &variant.fields {
218
+ Fields::Unit => vec![],
219
+ Fields::Named(named) => named.named.iter().map(|f| (f.ident.as_ref().unwrap(), &f.ty)).collect(),
220
+ _ => panic!("only unit variants or named fields are supported in storage enums"),
315
221
  }
316
222
  }
317
223
 
318
- /// Checks if a type is a primitive type
319
- pub(crate) fn is_primitive_type(ty: &Type) -> bool {
320
- let Type::Path(type_path) = ty else { return false };
321
- let Some(segment) = type_path.path.segments.first() else { return false };
322
- type_path.path.segments.len() == 1
323
- && matches!(segment.ident.to_string().as_str(), "u32" | "i32" | "u64" | "i64" | "u128" | "i128" | "bool")
324
- }
325
-
326
224
  /// Generates function parameters: `env: &Env` or `env: &Env, field1: Type1, ...`.
327
- /// For primitive types (u32, i32, u64, i64, u128, i128, bool), uses pass-by-value.
328
- /// For other types, uses pass-by-reference.
329
- fn gen_fn_params(variant: &Variant) -> TokenStream {
330
- let fields = extract_named_fields(&variant.fields);
225
+ fn gen_params(variant: &Variant) -> TokenStream {
226
+ let fields = extract_fields(variant);
331
227
  if fields.is_empty() {
332
228
  quote! { env: &soroban_sdk::Env }
333
229
  } else {
@@ -342,73 +238,136 @@ fn gen_fn_params(variant: &Variant) -> TokenStream {
342
238
  }
343
239
  }
344
240
 
345
- /// Generates function argument list for calls: `env, field1, field2, ...`.
346
- fn gen_fn_args(variant: &Variant) -> TokenStream {
347
- let fields = extract_named_fields(&variant.fields);
241
+ /// Generates function arguments: `env` or `env, field1, field2, ...`.
242
+ fn gen_args(variant: &Variant) -> TokenStream {
243
+ let fields = extract_fields(variant);
348
244
  if fields.is_empty() {
349
245
  quote! { env }
350
246
  } else {
351
- let field_names = fields.iter().map(|(name, _)| name);
352
- quote! { env, #(#field_names),* }
247
+ let names = fields.iter().map(|(name, _)| *name);
248
+ quote! { env, #(#names),* }
353
249
  }
354
250
  }
355
251
 
356
- /// Generates key constructor: `Enum::Variant` or `Enum::Variant(field1, field2)`.
357
- /// For non-primitive types that are passed by reference, clones them.
358
- fn gen_storage_key(variant: &Variant, enum_name: &Ident) -> TokenStream {
359
- let fields = extract_named_fields(&variant.fields);
252
+ /// Generates storage key: `Enum::Variant` or `Enum::Variant(field1.clone(), ...)`.
253
+ fn gen_key(enum_name: &Ident, variant: &Variant) -> TokenStream {
360
254
  let variant_ident = &variant.ident;
255
+ let fields = extract_fields(variant);
256
+
361
257
  if fields.is_empty() {
362
258
  quote! { #enum_name::#variant_ident }
363
259
  } else {
364
- let field_names = fields.iter().map(|(name, ty)| {
260
+ let args = fields.iter().map(|(name, ty)| {
365
261
  if is_primitive_type(ty) {
366
262
  quote! { #name }
367
263
  } else {
368
264
  quote! { #name.clone() }
369
265
  }
370
266
  });
371
- quote! { #enum_name::#variant_ident(#(#field_names),*) }
267
+ quote! { #enum_name::#variant_ident(#(#args),*) }
372
268
  }
373
269
  }
374
270
 
375
- /// Extracts (name, type) pairs from named fields.
376
- fn extract_named_fields(fields: &Fields) -> Vec<(&Ident, &Type)> {
377
- match fields {
378
- Fields::Unit => vec![],
379
- Fields::Named(named) => named.named.iter().map(|f| (f.ident.as_ref().unwrap(), &f.ty)).collect(),
380
- _ => panic!("only unit variants or named fields are supported in storage enums"),
271
+ // ============================================================================
272
+ // Attribute Parsing
273
+ // ============================================================================
274
+
275
+ /// Known attributes for storage variants ("doc" allows /// comments).
276
+ const KNOWN_ATTRS: &[&str] = &["doc", "instance", "persistent", "temporary", "default", "name", "no_ttl_extension"];
277
+
278
+ impl TryFrom<&Variant> for VariantConfig {
279
+ type Error = String;
280
+
281
+ fn try_from(variant: &Variant) -> Result<Self, Self::Error> {
282
+ let attrs = &variant.attrs;
283
+ validate_attrs(attrs, &variant.ident)?;
284
+
285
+ let (kind, value_type) = parse_storage_type(attrs)?;
286
+ let default_value = parse_default(attrs)?;
287
+ let name = parse_name(attrs)?.unwrap_or_else(|| variant.ident.to_string().to_snake_case());
288
+ let no_ttl_extension = parse_no_ttl_extension(attrs)?;
289
+
290
+ if no_ttl_extension && kind != StorageKind::Persistent {
291
+ return Err("#[no_ttl_extension] can only be used with #[persistent(...)] storage".to_string());
292
+ }
293
+
294
+ Ok(Self {
295
+ name,
296
+ kind,
297
+ value_type,
298
+ default_value,
299
+ auto_ttl: kind == StorageKind::Persistent && !no_ttl_extension,
300
+ })
381
301
  }
382
302
  }
383
303
 
384
- /// Generates the getter function with appropriate return type and TTL handling.
385
- fn gen_getter_fn(
386
- name: &Ident,
387
- params: &TokenStream,
388
- key: &TokenStream,
389
- accessor: &TokenStream,
390
- value_type: &Type,
391
- default_value: &Option<Expr>,
392
- auto_ttl_extend: &TokenStream,
393
- ) -> TokenStream {
394
- let (return_type, return_expr) = match default_value {
395
- Some(expr) => (quote! { #value_type }, quote! { value.unwrap_or(#expr) }),
396
- None => (quote! { Option<#value_type> }, quote! { value }),
397
- };
398
-
399
- // If auto TTL extension is enabled, check if the value is not None and extend the TTL
400
- let ttl_check = if auto_ttl_extend.is_empty() {
401
- TokenStream::new()
402
- } else {
403
- quote! { if value.is_some() { #auto_ttl_extend } }
404
- };
304
+ fn validate_attrs(attrs: &[Attribute], variant_ident: &Ident) -> Result<(), String> {
305
+ for attr in attrs {
306
+ let path = attr.path();
307
+ let name = path.get_ident().map(|i| i.to_string()).unwrap_or_else(|| quote!(#path).to_string());
405
308
 
406
- quote! {
407
- pub fn #name(#params) -> #return_type {
408
- let key = #key;
409
- let value = #accessor.get::<_, #value_type>(&key);
410
- #ttl_check
411
- #return_expr
309
+ if !KNOWN_ATTRS.contains(&name.as_str()) {
310
+ return Err(format!(
311
+ "unknown attribute '{}' on variant '{}'. Supported attributes are: {}",
312
+ name,
313
+ variant_ident,
314
+ KNOWN_ATTRS.join(", ")
315
+ ));
412
316
  }
413
317
  }
318
+ Ok(())
319
+ }
320
+
321
+ fn parse_storage_type(attrs: &[Attribute]) -> Result<(StorageKind, Type), String> {
322
+ attrs
323
+ .iter()
324
+ .filter_map(|attr| {
325
+ let ident = attr.path().get_ident()?;
326
+ let kind = match ident.to_string().as_str() {
327
+ "instance" => StorageKind::Instance,
328
+ "persistent" => StorageKind::Persistent,
329
+ "temporary" => StorageKind::Temporary,
330
+ _ => return None,
331
+ };
332
+ Some((kind, attr.parse_args::<Type>().ok()?))
333
+ })
334
+ .exactly_one()
335
+ .map_err(|e| {
336
+ format!(
337
+ "storage type must be specified exactly once as \
338
+ '#[instance(Type)]', '#[persistent(Type)]', or '#[temporary(Type)]': {}",
339
+ e
340
+ )
341
+ })
342
+ }
343
+
344
+ fn parse_default(attrs: &[Attribute]) -> Result<Option<Expr>, String> {
345
+ attrs
346
+ .iter()
347
+ .filter(|attr| attr.path().is_ident("default"))
348
+ .at_most_one()
349
+ .map_err(|_| "multiple default values specified")?
350
+ .map(|attr| attr.parse_args::<Expr>())
351
+ .transpose()
352
+ .map_err(|e| format!("failed to parse default value: {}", e))
353
+ }
354
+
355
+ fn parse_name(attrs: &[Attribute]) -> Result<Option<String>, String> {
356
+ attrs
357
+ .iter()
358
+ .filter(|attr| attr.path().is_ident("name"))
359
+ .at_most_one()
360
+ .map_err(|_| "multiple name attributes specified")?
361
+ .map(|attr| attr.parse_args::<syn::LitStr>().map(|lit| lit.value()))
362
+ .transpose()
363
+ .map_err(|e| format!("failed to parse name attribute: {}", e))
364
+ }
365
+
366
+ fn parse_no_ttl_extension(attrs: &[Attribute]) -> Result<bool, String> {
367
+ Ok(attrs
368
+ .iter()
369
+ .filter(|attr| attr.path().is_ident("no_ttl_extension"))
370
+ .at_most_one()
371
+ .map_err(|_| "multiple #[no_ttl_extension] attributes specified")?
372
+ .is_some())
414
373
  }