@layerzerolabs/protocol-stellar-v2 0.2.9 → 0.2.11

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 (83) hide show
  1. package/.turbo/turbo-build.log +245 -199
  2. package/.turbo/turbo-lint.log +79 -107
  3. package/.turbo/turbo-test.log +1017 -841
  4. package/Cargo.lock +13 -5
  5. package/contracts/common-macros/src/contract_impl.rs +6 -3
  6. package/contracts/common-macros/src/error.rs +9 -17
  7. package/contracts/common-macros/src/event.rs +4 -4
  8. package/contracts/common-macros/src/lib.rs +2 -2
  9. package/contracts/common-macros/src/ownable.rs +9 -5
  10. package/contracts/common-macros/src/tests/contract_impl.rs +178 -86
  11. package/contracts/common-macros/src/tests/error.rs +168 -0
  12. package/contracts/common-macros/src/tests/mod.rs +2 -4
  13. package/contracts/common-macros/src/tests/ownable.rs +37 -60
  14. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__contract_impl__snapshot_generated_contract_impl_code.snap +16 -6
  15. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__error__snapshot_generated_contract_error_code.snap +20 -0
  16. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ownable__snapshot_generated_ownable_code.snap +3 -1
  17. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ownable__snapshot_only_owner_preserves_function_signature.snap +12 -2
  18. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ttl_configurable__snapshot_generated_ttl_configurable_code.snap +5 -1
  19. package/contracts/common-macros/src/tests/utils.rs +267 -0
  20. package/contracts/common-macros/src/ttl_configurable.rs +15 -12
  21. package/contracts/common-macros/src/utils.rs +35 -6
  22. package/contracts/message-libs/uln-302/src/receive_uln.rs +1 -1
  23. package/contracts/message-libs/uln-302/src/send_uln.rs +2 -2
  24. package/contracts/message-libs/uln-302/src/uln302.rs +2 -2
  25. package/contracts/oapp-macros/src/oapp_core.rs +1 -1
  26. package/contracts/oapps/oft/integration-tests/setup.rs +4 -3
  27. package/contracts/oapps/oft/src/default_oft_impl.rs +146 -0
  28. package/contracts/oapps/oft/src/extensions/mod.rs +3 -0
  29. package/contracts/oapps/oft/src/extensions/oft_fee.rs +164 -0
  30. package/contracts/oapps/oft/src/extensions/pausable.rs +50 -0
  31. package/contracts/oapps/oft/src/extensions/rate_limiter.rs +198 -0
  32. package/contracts/oapps/oft/src/lib.rs +2 -3
  33. package/contracts/oapps/oft/src/oft.rs +16 -85
  34. package/contracts/oapps/oft/src/oft_types/mint_burn.rs +1 -1
  35. package/contracts/oapps/oft/src/tests/extensions/mod.rs +11 -0
  36. package/contracts/oapps/oft/src/tests/extensions/setup.rs +888 -0
  37. package/contracts/oapps/oft/src/tests/extensions/test_oft_fee.rs +749 -0
  38. package/contracts/oapps/oft/src/tests/extensions/test_pausable.rs +432 -0
  39. package/contracts/oapps/oft/src/tests/extensions/test_rate_limiter.rs +1078 -0
  40. package/contracts/oapps/oft/src/tests/mod.rs +2 -0
  41. package/contracts/oapps/oft/src/tests/test_utils.rs +24 -6
  42. package/contracts/oapps/{oft-mint-burn → oft-std}/Cargo.toml +1 -8
  43. package/contracts/oapps/oft-std/src/lib.rs +5 -0
  44. package/contracts/oapps/oft-std/src/oft.rs +59 -0
  45. package/contracts/utils/src/ownable.rs +2 -2
  46. package/contracts/utils/src/tests/ownable.rs +0 -63
  47. package/contracts/utils/src/ttl.rs +19 -1
  48. package/contracts/workers/dvn/src/auth.rs +91 -27
  49. package/contracts/workers/dvn/src/dvn.rs +22 -20
  50. package/contracts/workers/dvn/src/interfaces/dvn.rs +48 -3
  51. package/contracts/workers/dvn/src/interfaces/multisig.rs +41 -0
  52. package/contracts/workers/dvn/src/lib.rs +6 -8
  53. package/contracts/workers/dvn/src/multisig.rs +6 -3
  54. package/contracts/workers/dvn/src/tests/auth.rs +1 -1
  55. package/contracts/workers/dvn/src/tests/dvn.rs +3 -4
  56. package/contracts/workers/dvn-fee-lib/Cargo.toml +2 -1
  57. package/contracts/workers/dvn-fee-lib/src/dvn_fee_lib.rs +4 -3
  58. package/contracts/workers/dvn-fee-lib/src/tests/dvn_fee_lib.rs +8 -6
  59. package/contracts/workers/executor/src/interfaces/executor.rs +5 -2
  60. package/contracts/workers/executor/src/lz_executor.rs +6 -6
  61. package/contracts/workers/price-feed/Cargo.toml +21 -0
  62. package/contracts/workers/price-feed/src/errors.rs +9 -0
  63. package/contracts/workers/price-feed/src/events.rs +30 -0
  64. package/contracts/workers/price-feed/src/lib.rs +11 -0
  65. package/contracts/workers/price-feed/src/price_feed.rs +265 -0
  66. package/contracts/workers/price-feed/src/storage.rs +42 -0
  67. package/contracts/workers/price-feed/src/types.rs +59 -0
  68. package/contracts/workers/worker/src/interfaces/dvn_fee_lib.rs +2 -1
  69. package/package.json +3 -3
  70. package/sdk/dist/generated/bml.js +3 -1
  71. package/sdk/dist/generated/counter.d.ts +102 -0
  72. package/sdk/dist/generated/counter.js +13 -1
  73. package/sdk/dist/generated/endpoint.js +3 -1
  74. package/sdk/dist/generated/sml.js +3 -1
  75. package/sdk/dist/generated/uln302.js +3 -1
  76. package/sdk/package.json +1 -1
  77. package/contracts/oapps/oft/src/macro_tests/mod.rs +0 -2
  78. package/contracts/oapps/oft/src/macro_tests/test_all_default.rs +0 -41
  79. package/contracts/oapps/oft/src/macro_tests/test_override.rs +0 -83
  80. package/contracts/oapps/oft-mint-burn/src/lib.rs +0 -3
  81. package/contracts/oapps/oft-mint-burn/src/oft.rs +0 -28
  82. package/contracts/oapps/oft-mint-burn/src/tests/mod.rs +0 -1
  83. package/contracts/workers/dvn/src/types.rs +0 -26
package/Cargo.lock CHANGED
@@ -613,6 +613,7 @@ dependencies = [
613
613
  "common-macros",
614
614
  "message-lib-common",
615
615
  "soroban-sdk",
616
+ "utils",
616
617
  "worker",
617
618
  ]
618
619
 
@@ -1176,18 +1177,14 @@ dependencies = [
1176
1177
  ]
1177
1178
 
1178
1179
  [[package]]
1179
- name = "oft-mint-burn"
1180
+ name = "oft-std"
1180
1181
  version = "0.0.1"
1181
1182
  dependencies = [
1182
- "assert_unordered",
1183
1183
  "common-macros",
1184
1184
  "endpoint-v2",
1185
- "executor",
1186
- "message-lib-common",
1187
1185
  "oapp",
1188
1186
  "oapp-macros",
1189
1187
  "oft",
1190
- "simple-message-lib",
1191
1188
  "soroban-sdk",
1192
1189
  "utils",
1193
1190
  ]
@@ -1257,6 +1254,17 @@ dependencies = [
1257
1254
  "syn 2.0.108",
1258
1255
  ]
1259
1256
 
1257
+ [[package]]
1258
+ name = "price-feed"
1259
+ version = "0.0.1"
1260
+ dependencies = [
1261
+ "common-macros",
1262
+ "endpoint-v2",
1263
+ "soroban-sdk",
1264
+ "utils",
1265
+ "worker",
1266
+ ]
1267
+
1260
1268
  [[package]]
1261
1269
  name = "primeorder"
1262
1270
  version = "0.13.6"
@@ -18,12 +18,15 @@ pub fn contract_impl(attr: TokenStream, input: TokenStream) -> TokenStream {
18
18
  }
19
19
 
20
20
  // Skip methods without Env parameter
21
- let Some(env_ident) = utils::find_env_ident(&method.sig.inputs) else { continue };
21
+ let Some(env_param) = utils::find_env_param(&method.sig.inputs) else { continue };
22
+
23
+ // Get a reference to env (handles both `Env` and `&Env` parameter types)
24
+ let env_ref = env_param.as_ref_tokens();
25
+ let env_ident = env_param.ident;
22
26
 
23
- // TODO: should support Env instead of &Env?
24
27
  // Use fully qualified syntax to call ttl_configs from TtlConfigurable trait
25
28
  let extend_ttl_stmt = parse_quote! {
26
- if let Some(instance_ttl) = utils::ttl::TtlConfigStorage::instance(#env_ident) {
29
+ if let Some(instance_ttl) = utils::ttl::TtlConfigStorage::instance(#env_ref) {
27
30
  #env_ident.storage().instance().extend_ttl(instance_ttl.threshold, instance_ttl.extend_to);
28
31
  }
29
32
  };
@@ -1,17 +1,15 @@
1
- use proc_macro::TokenStream;
1
+ use proc_macro2::TokenStream;
2
2
  use quote::quote;
3
- use syn::{parse_macro_input, parse_quote, spanned::Spanned, ExprLit, Fields, ItemEnum, Lit, Token};
4
-
5
- /// The default starting value for error enum discriminants when no explicit value is provided.
6
- /// This ensures error codes start from 1 instead of 0.
7
- const DEFAULT_OFFSET: u32 = 1;
3
+ use syn::{parse_quote, spanned::Spanned, ExprLit, Fields, ItemEnum, Lit, Token};
8
4
 
9
5
  pub fn generate_error(attr: TokenStream, item: TokenStream) -> TokenStream {
10
6
  assert!(attr.is_empty(), "contract_error attribute is not supported");
11
7
 
12
- let mut data_enum = parse_macro_input!(item as ItemEnum);
8
+ let mut data_enum: ItemEnum = syn::parse2(item).unwrap_or_else(|e| panic!("failed to parse enum: {}", e));
13
9
 
14
- let mut current_value = DEFAULT_OFFSET;
10
+ // For variants without an explicit discriminant, assign sequential error codes starting at 1
11
+ // (by initializing the counter to 0 and incrementing before assignment).
12
+ let mut current_value = 0;
15
13
  for variant in &mut data_enum.variants {
16
14
  assert!(matches!(variant.fields, Fields::Unit), "Error enum variants must be unit variants");
17
15
 
@@ -19,15 +17,12 @@ pub fn generate_error(attr: TokenStream, item: TokenStream) -> TokenStream {
19
17
  if let Some((_, disc)) = &variant.discriminant {
20
18
  // Explicit discriminant - validate it's greater than previous and update counter
21
19
  let val = parse_discriminant_value(disc);
22
- assert!(
23
- val >= current_value,
24
- "Error enum discriminant must be greater than or equal to the previous discriminant"
25
- );
26
- current_value = val + 1;
20
+ assert!(val > current_value, "Error enum discriminant must be greater than the previous discriminant");
21
+ current_value = val;
27
22
  } else {
28
23
  // No discriminant - assign the next sequential value
29
- variant.discriminant = Some((Token![=](variant.span()), parse_quote!(#current_value)));
30
24
  current_value += 1;
25
+ variant.discriminant = Some((Token![=](variant.span()), parse_quote!(#current_value)));
31
26
  }
32
27
  }
33
28
 
@@ -37,7 +32,6 @@ pub fn generate_error(attr: TokenStream, item: TokenStream) -> TokenStream {
37
32
  #[repr(u32)]
38
33
  #data_enum
39
34
  }
40
- .into()
41
35
  }
42
36
 
43
37
  /// Parses a discriminant value from a variant, returning the u32 value if valid.
@@ -49,5 +43,3 @@ fn parse_discriminant_value(disc: &syn::Expr) -> u32 {
49
43
  panic!("Error enum discriminant must be an integer literal")
50
44
  }
51
45
  }
52
-
53
- // TODO: add test for this
@@ -1,9 +1,9 @@
1
- use proc_macro::TokenStream;
1
+ use proc_macro2::TokenStream;
2
2
  use quote::quote;
3
- use syn::{parse_macro_input, DeriveInput};
3
+ use syn::DeriveInput;
4
4
 
5
5
  pub fn generate_event(_attr: TokenStream, item: TokenStream) -> TokenStream {
6
- let input = parse_macro_input!(item as DeriveInput);
6
+ let input: DeriveInput = syn::parse2(item).unwrap_or_else(|e| panic!("failed to parse input: {}", e));
7
7
  let struct_name = input.ident.to_string();
8
8
 
9
9
  let expanded = quote! {
@@ -12,5 +12,5 @@ pub fn generate_event(_attr: TokenStream, item: TokenStream) -> TokenStream {
12
12
  #input
13
13
  };
14
14
 
15
- TokenStream::from(expanded)
15
+ expanded
16
16
  }
@@ -115,7 +115,7 @@ pub fn storage(attr: TokenStream, item: TokenStream) -> TokenStream {
115
115
  /// ```
116
116
  #[proc_macro_attribute]
117
117
  pub fn event(attr: TokenStream, item: TokenStream) -> TokenStream {
118
- event::generate_event(attr, item)
118
+ event::generate_event(attr.into(), item.into()).into()
119
119
  }
120
120
 
121
121
  // ============================================================================
@@ -149,7 +149,7 @@ pub fn event(attr: TokenStream, item: TokenStream) -> TokenStream {
149
149
  /// ```
150
150
  #[proc_macro_attribute]
151
151
  pub fn contract_error(attr: TokenStream, item: TokenStream) -> TokenStream {
152
- error::generate_error(attr, item)
152
+ error::generate_error(attr.into(), item.into()).into()
153
153
  }
154
154
 
155
155
  // ============================================================================
@@ -1,4 +1,4 @@
1
- use crate::utils::expect_env_ident;
1
+ use crate::utils;
2
2
  use proc_macro2::TokenStream;
3
3
  use quote::{quote, ToTokens};
4
4
  use syn::{parse_quote, ItemFn, ItemStruct};
@@ -20,16 +20,18 @@ pub fn generate_ownable_impl(input: TokenStream) -> TokenStream {
20
20
  }
21
21
 
22
22
  /// Implement the Ownable trait for the contract.
23
- #[soroban_sdk::contractimpl]
23
+ #[common_macros::contract_impl]
24
24
  impl utils::ownable::Ownable for #name {
25
25
  fn owner(env: &soroban_sdk::Env) -> Option<soroban_sdk::Address> {
26
26
  utils::ownable::DefaultOwnable::owner(env)
27
27
  }
28
28
 
29
+ #[common_macros::only_owner]
29
30
  fn transfer_ownership(env: &soroban_sdk::Env, new_owner: &soroban_sdk::Address) {
30
31
  utils::ownable::DefaultOwnable::transfer_ownership(env, new_owner)
31
32
  }
32
33
 
34
+ #[common_macros::only_owner]
33
35
  fn renounce_ownership(env: &soroban_sdk::Env) {
34
36
  utils::ownable::DefaultOwnable::renounce_ownership(env)
35
37
  }
@@ -46,10 +48,12 @@ pub fn generate_ownable_impl(input: TokenStream) -> TokenStream {
46
48
  pub fn prepend_only_owner_check(input: TokenStream) -> TokenStream {
47
49
  let mut input_fn: ItemFn = syn::parse2(input).unwrap_or_else(|e| panic!("failed to parse function: {}", e));
48
50
 
49
- let env_ident = expect_env_ident(&input_fn.sig.inputs);
51
+ let env_param = utils::expect_env_param(&input_fn.sig.inputs);
52
+
53
+ // Get a reference to env (handles both `Env` and `&Env` parameter types)
54
+ let env_ref = env_param.as_ref_tokens();
50
55
 
51
- // TODO: should support Env instead of &Env?
52
56
  // Insert the owner authentication check at the beginning of the function body
53
- input_fn.block.stmts.insert(0, parse_quote!(utils::ownable::require_owner_auth::<Self>(#env_ident);));
57
+ input_fn.block.stmts.insert(0, parse_quote!(utils::ownable::require_owner_auth::<Self>(#env_ref);));
54
58
  input_fn.into_token_stream()
55
59
  }
@@ -30,7 +30,7 @@ fn snapshot_generated_contract_impl_code() {
30
30
  }
31
31
 
32
32
  /// Public method with Env not as first parameter - should have TTL extension
33
- pub fn env_second(value: u32, env: Env) -> u32 {
33
+ pub fn env_second(value: u32, env: &Env) -> u32 {
34
34
  value * 2
35
35
  }
36
36
 
@@ -67,6 +67,12 @@ fn snapshot_generated_contract_impl_code() {
67
67
  fn trait_method_without_env(value: u32) -> u32 {
68
68
  value * 4
69
69
  }
70
+
71
+ /// Trait method with macro attribute - should have TTL extension
72
+ #[common_macros::only_owner]
73
+ fn trait_method_with_only_owner_attribute(env: Env, value: u32) -> u32 {
74
+ value * 5
75
+ }
70
76
  }
71
77
  };
72
78
 
@@ -79,7 +85,7 @@ fn snapshot_generated_contract_impl_code() {
79
85
  let contracttrait_input = quote! {
80
86
  impl AnotherTrait for MyContract {
81
87
  /// Trait method with contracttrait attr - should have TTL extension
82
- fn contracttrait_method(env: Env, value: u32) -> u32 {
88
+ fn contracttrait_method(env: &Env, value: u32) -> u32 {
83
89
  value * 3
84
90
  }
85
91
  }
@@ -193,97 +199,183 @@ fn test_adds_soroban_contractimpl_attribute_with_attr() {
193
199
  );
194
200
  }
195
201
 
196
- /// Helper to assert TTL extension behavior in generated code
197
- fn assert_ttl_extension(input: TokenStream, expected: bool, env_ident: Option<&str>, case: &str) {
198
- let result = crate::contract_impl::contract_impl(TokenStream::new(), input);
199
- let result_str = result.to_string();
202
+ /// Expected TTL extension behavior for a test case
203
+ #[derive(Clone)]
204
+ enum TtlExpectation {
205
+ /// TTL extension should NOT be inserted
206
+ None,
207
+ /// TTL extension should be inserted with the given env patterns
208
+ /// - `instance_arg`: pattern in `TtlConfigStorage::instance(...)` (e.g., "& env" for owned, "env" for ref)
209
+ /// - `storage_ident`: identifier for `.storage()` call
210
+ Present { instance_arg: &'static str, storage_ident: &'static str },
211
+ }
200
212
 
201
- let has_ttl = result_str.contains("TtlConfigStorage :: instance");
213
+ struct TtlTestCase {
214
+ name: &'static str,
215
+ input: TokenStream,
216
+ expectation: TtlExpectation,
217
+ }
202
218
 
203
- assert_eq!(
204
- has_ttl,
205
- expected,
206
- "{}: TTL extension {} but {}",
207
- case,
208
- if expected { "expected" } else { "not expected" },
209
- if has_ttl { "was found" } else { "was not found" }
210
- );
219
+ impl TtlTestCase {
220
+ fn expect_ttl(
221
+ name: &'static str,
222
+ input: TokenStream,
223
+ instance_arg: &'static str,
224
+ storage_ident: &'static str,
225
+ ) -> Self {
226
+ Self { name, input, expectation: TtlExpectation::Present { instance_arg, storage_ident } }
227
+ }
211
228
 
212
- if expected {
213
- assert!(result_str.contains("extend_ttl"), "{}: should insert extend_ttl call", case);
214
-
215
- // Verify correct env identifier is used in TTL extension code
216
- if let Some(env) = env_ident {
217
- let instance_pattern = format!("TtlConfigStorage :: instance ({})", env);
218
- let storage_pattern = format!("{} . storage ()", env);
219
- assert!(
220
- result_str.contains(&instance_pattern),
221
- "{}: should use '{}' in TtlConfigStorage::instance call",
222
- case,
223
- env
224
- );
225
- assert!(result_str.contains(&storage_pattern), "{}: should use '{}' for storage access", case, env);
229
+ fn expect_no_ttl(name: &'static str, input: TokenStream) -> Self {
230
+ Self { name, input, expectation: TtlExpectation::None }
231
+ }
232
+
233
+ fn run(&self) {
234
+ let result = crate::contract_impl::contract_impl(TokenStream::new(), self.input.clone());
235
+ let result_str = result.to_string();
236
+ let has_ttl = result_str.contains("TtlConfigStorage :: instance");
237
+
238
+ match &self.expectation {
239
+ TtlExpectation::None => {
240
+ assert!(!has_ttl, "{}: TTL extension should NOT be present, but was found", self.name);
241
+ }
242
+ TtlExpectation::Present { instance_arg, storage_ident } => {
243
+ assert!(has_ttl, "{}: TTL extension should be present, but was not found", self.name);
244
+ assert!(result_str.contains("extend_ttl"), "{}: should insert extend_ttl call", self.name);
245
+
246
+ let instance_pattern = format!("TtlConfigStorage :: instance ({})", instance_arg);
247
+ assert!(
248
+ result_str.contains(&instance_pattern),
249
+ "{}: expected '{}' in TtlConfigStorage::instance call. Got: {}",
250
+ self.name,
251
+ instance_arg,
252
+ result_str
253
+ );
254
+
255
+ let storage_pattern = format!("{} . storage ()", storage_ident);
256
+ assert!(
257
+ result_str.contains(&storage_pattern),
258
+ "{}: expected '{}.storage()' call",
259
+ self.name,
260
+ storage_ident
261
+ );
262
+ }
226
263
  }
227
264
  }
228
265
  }
229
266
 
230
267
  #[test]
231
- fn test_ttl_extension_insertion() {
232
- let cases: Vec<(&str, TokenStream, bool, Option<&str>)> = vec![
233
- // (case name, input, should have TTL extension, optional env ident to verify)
234
- (
235
- "public method with Env",
236
- quote! {
237
- impl MyContract {
238
- pub fn my_method(env: Env) { let x = 1; }
239
- }
240
- },
241
- true,
242
- Some("env"),
243
- ),
244
- (
245
- "private method with Env",
246
- quote! {
247
- impl MyContract {
248
- fn private_method(env: Env) { let x = 1; }
249
- }
250
- },
251
- false,
252
- None,
253
- ),
254
- (
255
- "public method without Env",
256
- quote! {
257
- impl MyContract {
258
- pub fn no_env_method(value: u32) -> u32 { value }
259
- }
260
- },
261
- false,
262
- None,
263
- ),
264
- (
265
- "custom Env identifier",
266
- quote! {
267
- impl MyContract {
268
- pub fn my_method(my_custom_env: Env) { let x = 1; }
269
- }
270
- },
271
- true,
272
- Some("my_custom_env"),
273
- ),
274
- (
275
- "trait impl method with Env",
276
- quote! {
277
- impl SomeTrait for MyContract {
278
- fn trait_method(env: Env) { let x = 1; }
279
- }
280
- },
281
- true,
282
- Some("env"),
283
- ),
284
- ];
268
+ fn test_ttl_extension_with_owned_env() {
269
+ TtlTestCase::expect_ttl(
270
+ "public method with owned Env",
271
+ quote! {
272
+ impl MyContract {
273
+ pub fn my_method(env: Env) { let x = 1; }
274
+ }
275
+ },
276
+ "& env", // instance takes reference
277
+ "env", // storage called on ident
278
+ )
279
+ .run();
280
+ }
285
281
 
286
- for (case, input, expected, env_ident) in cases {
287
- assert_ttl_extension(input, expected, env_ident, case);
288
- }
282
+ #[test]
283
+ fn test_ttl_extension_with_ref_env() {
284
+ TtlTestCase::expect_ttl(
285
+ "public method with ref Env",
286
+ quote! {
287
+ impl MyContract {
288
+ pub fn my_method(env: &Env) { let x = 1; }
289
+ }
290
+ },
291
+ "env", // instance takes ident directly (already a ref)
292
+ "env",
293
+ )
294
+ .run();
295
+ }
296
+
297
+ #[test]
298
+ fn test_ttl_extension_with_custom_env_ident_owned() {
299
+ TtlTestCase::expect_ttl(
300
+ "custom Env identifier (owned)",
301
+ quote! {
302
+ impl MyContract {
303
+ pub fn my_method(my_custom_env: Env) { let x = 1; }
304
+ }
305
+ },
306
+ "& my_custom_env",
307
+ "my_custom_env",
308
+ )
309
+ .run();
310
+ }
311
+
312
+ #[test]
313
+ fn test_ttl_extension_with_custom_env_ident_ref() {
314
+ TtlTestCase::expect_ttl(
315
+ "custom Env identifier (ref)",
316
+ quote! {
317
+ impl MyContract {
318
+ pub fn my_method(my_custom_env: &Env) { let x = 1; }
319
+ }
320
+ },
321
+ "my_custom_env",
322
+ "my_custom_env",
323
+ )
324
+ .run();
325
+ }
326
+
327
+ #[test]
328
+ fn test_ttl_extension_for_trait_impl_owned_env() {
329
+ TtlTestCase::expect_ttl(
330
+ "trait impl method with owned Env",
331
+ quote! {
332
+ impl SomeTrait for MyContract {
333
+ fn trait_method(env: Env) { let x = 1; }
334
+ }
335
+ },
336
+ "& env",
337
+ "env",
338
+ )
339
+ .run();
340
+ }
341
+
342
+ #[test]
343
+ fn test_ttl_extension_for_trait_impl_ref_env() {
344
+ TtlTestCase::expect_ttl(
345
+ "trait impl method with ref Env",
346
+ quote! {
347
+ impl SomeTrait for MyContract {
348
+ fn trait_method(env: &Env) { let x = 1; }
349
+ }
350
+ },
351
+ "env",
352
+ "env",
353
+ )
354
+ .run();
355
+ }
356
+
357
+ #[test]
358
+ fn test_no_ttl_extension_for_private_method() {
359
+ TtlTestCase::expect_no_ttl(
360
+ "private method with Env",
361
+ quote! {
362
+ impl MyContract {
363
+ fn private_method(env: Env) { let x = 1; }
364
+ }
365
+ },
366
+ )
367
+ .run();
368
+ }
369
+
370
+ #[test]
371
+ fn test_no_ttl_extension_without_env_param() {
372
+ TtlTestCase::expect_no_ttl(
373
+ "public method without Env",
374
+ quote! {
375
+ impl MyContract {
376
+ pub fn no_env_method(value: u32) -> u32 { value }
377
+ }
378
+ },
379
+ )
380
+ .run();
289
381
  }
@@ -0,0 +1,168 @@
1
+ use quote::quote;
2
+
3
+ use crate::tests::test_helpers::{assert_panics_contains, non_enum_item_inputs};
4
+
5
+ // ============================================
6
+ // Error Cases: Invalid Inputs
7
+ // ============================================
8
+
9
+ #[test]
10
+ fn test_contract_error_rejects_non_enum_inputs() {
11
+ for (case, input) in non_enum_item_inputs() {
12
+ assert_panics_contains(case, "failed to parse enum", || {
13
+ crate::error::generate_error(quote! {}, input.clone());
14
+ });
15
+ }
16
+ }
17
+
18
+ #[test]
19
+ fn test_contract_error_rejects_attr_arguments() {
20
+ let input = quote! {
21
+ pub enum MyError {
22
+ A,
23
+ }
24
+ };
25
+ assert_panics_contains("attr not empty", "contract_error attribute is not supported", || {
26
+ crate::error::generate_error(quote! { some_attr }, input);
27
+ });
28
+ }
29
+
30
+ #[test]
31
+ fn test_contract_error_requires_unit_variants() {
32
+ let cases = vec![("tuple variant", quote! { A(u32) }), ("struct variant", quote! { A { x: u32 } })];
33
+
34
+ for (case, variant) in cases {
35
+ let input = quote! {
36
+ pub enum MyError {
37
+ #variant,
38
+ }
39
+ };
40
+ assert_panics_contains(case, "Error enum variants must be unit variants", || {
41
+ crate::error::generate_error(quote! {}, input.clone());
42
+ });
43
+ }
44
+ }
45
+
46
+ #[test]
47
+ fn test_contract_error_discriminant_must_be_integer_literal() {
48
+ let cases = vec![
49
+ ("binary expr", quote! { 1 + 1 }),
50
+ ("path expr", quote! { SOME_CONST }),
51
+ ("negative integer (unary expr)", quote! { -1 }),
52
+ ("paren expr", quote! { (1) }),
53
+ ("bool literal", quote! { true }),
54
+ ("string literal", quote! { "1" }),
55
+ ("float literal", quote! { 1.0 }),
56
+ ("char literal", quote! { 'a' }),
57
+ ];
58
+
59
+ for (case, expr) in cases {
60
+ let input = quote! {
61
+ pub enum MyError {
62
+ A = #expr,
63
+ }
64
+ };
65
+ assert_panics_contains(case, "Error enum discriminant must be an integer literal", || {
66
+ crate::error::generate_error(quote! {}, input.clone());
67
+ });
68
+ }
69
+ }
70
+
71
+ #[test]
72
+ fn test_contract_error_discriminant_must_fit_u32() {
73
+ let input = quote! {
74
+ pub enum MyError {
75
+ A = 4294967296,
76
+ }
77
+ };
78
+
79
+ assert_panics_contains("u32::MAX + 1", "Error enum discriminant must be a valid u32 integer", || {
80
+ crate::error::generate_error(quote! {}, input);
81
+ });
82
+ }
83
+
84
+ #[test]
85
+ fn test_contract_error_discriminants_ordering_rejections_table_driven() {
86
+ let cases = vec![
87
+ (
88
+ "explicit value is less than previous",
89
+ quote! {
90
+ // A is assigned 1 by default; B cannot go backwards.
91
+ A,
92
+ B = 1,
93
+ },
94
+ ),
95
+ (
96
+ "explicit zero",
97
+ quote! {
98
+ A = 0,
99
+ },
100
+ ),
101
+ (
102
+ "explicit equal to previous explicit",
103
+ quote! {
104
+ A = 1,
105
+ B = 1,
106
+ },
107
+ ),
108
+ (
109
+ "explicit decreases after explicit",
110
+ quote! {
111
+ A = 5,
112
+ B = 4,
113
+ },
114
+ ),
115
+ ];
116
+
117
+ for (case, variants) in cases {
118
+ let input = quote! {
119
+ pub enum MyError {
120
+ #variants
121
+ }
122
+ };
123
+ assert_panics_contains(case, "Error enum discriminant must be greater than the previous discriminant", || {
124
+ crate::error::generate_error(quote! {}, input.clone());
125
+ });
126
+ }
127
+ }
128
+
129
+ #[test]
130
+ fn test_contract_error_max_not_last_panics_overflow() {
131
+ let input = quote! {
132
+ pub enum MyError {
133
+ A = 4294967295,
134
+ B,
135
+ }
136
+ };
137
+ assert_panics_contains("max not last overflows", "attempt to add with overflow", || {
138
+ crate::error::generate_error(quote! {}, input);
139
+ });
140
+ }
141
+
142
+ // ============================================
143
+ // Valid Cases: Snapshot Tests for Generated Code
144
+ // ============================================
145
+
146
+ #[test]
147
+ fn snapshot_generated_contract_error_code() {
148
+ let input = quote! {
149
+ /// Example error enum
150
+ pub enum MyError {
151
+ /// Implicit (should start at 1)
152
+ A,
153
+ /// Implicit (should be 2)
154
+ B,
155
+ /// Explicit (must be >= previous + 1)
156
+ C = 10,
157
+ /// Implicit (should be 11)
158
+ D,
159
+ /// Explicit max boundary (u32::MAX)
160
+ E = 4294967295,
161
+ }
162
+ };
163
+
164
+ let result = crate::error::generate_error(quote! {}, input);
165
+ let formatted = prettyplease::unparse(&syn::parse2::<syn::File>(result).expect("failed to parse generated code"));
166
+
167
+ insta::assert_snapshot!(formatted);
168
+ }
@@ -1,9 +1,7 @@
1
1
  mod contract_impl;
2
+ mod error;
2
3
  mod ownable;
3
4
  mod storage;
4
5
  mod test_helpers;
5
6
  mod ttl_configurable;
6
-
7
- // Note: error.rs and event.rs use proc_macro::TokenStream which cannot be tested
8
- // outside of a procedural macro context. These macros are tested via integration
9
- // tests in the contracts that use them (e.g., utils crate).
7
+ mod utils;