@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
@@ -0,0 +1,695 @@
1
+ use proc_macro2::TokenStream;
2
+ use quote::quote;
3
+
4
+ use crate::tests::test_helpers::{assert_panics_contains, filter_item_inputs_excluding_labels};
5
+
6
+ // ============================================================================
7
+ // Snapshot Tests
8
+ // ============================================================================
9
+
10
+ /// Comprehensive snapshot test for contractimpl_with_ttl macro:
11
+ /// - Inherent impl: public methods with Env (adds TTL), private/pub(crate) methods (skipped)
12
+ /// - Trait impl: all methods with Env get TTL extension
13
+ /// - Qualified Env types (soroban_sdk::Env)
14
+ /// - Env parameter not in first position
15
+ /// - Methods without Env (skipped)
16
+ #[test]
17
+ fn snapshot_generated_contractimpl_code() {
18
+ // Test inherent impl with empty attr
19
+ let empty_attr = TokenStream::new();
20
+ let inherent_input = quote! {
21
+ impl MyContract {
22
+ // Non-fn items should be preserved by the macro
23
+ const A_CONST: u32 = 1;
24
+ type Alias = u32;
25
+
26
+ /// Public method with Env - should have TTL extension
27
+ pub fn public_with_env(env: Env, value: u32) -> u32 {
28
+ value * 2
29
+ }
30
+
31
+ /// Public method with qualified Env path - should have TTL extension
32
+ pub fn with_qualified_env(env: soroban_sdk::Env) -> u32 {
33
+ 42
34
+ }
35
+
36
+ /// Public method with Env not as first parameter - should have TTL extension
37
+ pub fn env_second(value: u32, env: &Env) -> u32 {
38
+ value * 2
39
+ }
40
+
41
+ /// Public method without Env - should NOT have TTL extension
42
+ pub fn public_without_env(value: u32) -> u32 {
43
+ value * 4
44
+ }
45
+
46
+ /// Private method with Env - should NOT have TTL extension (not public)
47
+ fn private_with_env(env: Env, value: u32) -> u32 {
48
+ value * 5
49
+ }
50
+
51
+ /// Pub(crate) method with Env - should NOT have TTL extension (not fully public)
52
+ pub(crate) fn pub_crate_with_env(env: Env, value: u32) -> u32 {
53
+ value * 7
54
+ }
55
+
56
+ /// Constructor method - should have init_default_ttl_configs
57
+ pub fn __constructor(env: &Env, value: u32) {
58
+ let _ = value * 2;
59
+ }
60
+ }
61
+ };
62
+
63
+ let inherent_result = crate::contract_ttl::contractimpl_with_ttl(empty_attr.clone(), inherent_input);
64
+ let inherent_formatted =
65
+ prettyplease::unparse(&syn::parse2::<syn::File>(inherent_result).expect("failed to parse generated code"));
66
+
67
+ // Test trait impl with empty attr
68
+ let trait_input = quote! {
69
+ impl SomeTrait for MyContract {
70
+ /// Trait method with Env - should have TTL extension
71
+ fn trait_method_with_env(env: Env, value: u32) -> u32 {
72
+ value * 2
73
+ }
74
+
75
+ /// Trait method without Env - should NOT have TTL extension
76
+ fn trait_method_without_env(value: u32) -> u32 {
77
+ value * 4
78
+ }
79
+
80
+ /// Trait method with macro attribute - should have TTL extension
81
+ #[common_macros::only_auth]
82
+ fn trait_method_with_only_auth_attribute(env: Env, value: u32) -> u32 {
83
+ value * 5
84
+ }
85
+ }
86
+ };
87
+
88
+ let trait_result = crate::contract_ttl::contractimpl_with_ttl(empty_attr, trait_input);
89
+ let trait_formatted =
90
+ prettyplease::unparse(&syn::parse2::<syn::File>(trait_result).expect("failed to parse generated code"));
91
+
92
+ // Test trait impl with contracttrait attr
93
+ let contracttrait_attr = quote! { contracttrait };
94
+ let contracttrait_input = quote! {
95
+ impl AnotherTrait for MyContract {
96
+ /// Trait method with contracttrait attr - should have TTL extension
97
+ fn contracttrait_method(env: &Env, value: u32) -> u32 {
98
+ value * 3
99
+ }
100
+ }
101
+ };
102
+
103
+ let contracttrait_result = crate::contract_ttl::contractimpl_with_ttl(contracttrait_attr, contracttrait_input);
104
+ let contracttrait_formatted =
105
+ prettyplease::unparse(&syn::parse2::<syn::File>(contracttrait_result).expect("failed to parse generated code"));
106
+
107
+ // Combine all for single snapshot
108
+ let combined = format!(
109
+ "// === Inherent Impl (no attr) ===\n\n{}\n\n// === Trait Impl (no attr) ===\n\n{}\n\n// === Trait Impl (contracttrait attr) ===\n\n{}",
110
+ inherent_formatted, trait_formatted, contracttrait_formatted
111
+ );
112
+
113
+ insta::assert_snapshot!(combined);
114
+ }
115
+
116
+ /// Comprehensive snapshot test for contracttrait_with_ttl macro:
117
+ /// - Default methods with Env (adds TTL)
118
+ /// - Abstract methods without body (skipped)
119
+ /// - Methods without Env (skipped)
120
+ /// - Qualified Env types (soroban_sdk::Env)
121
+ /// - Env parameter not in first position
122
+ #[test]
123
+ fn snapshot_generated_contracttrait_code() {
124
+ // Test trait with empty attr
125
+ let empty_attr = TokenStream::new();
126
+ let trait_input = quote! {
127
+ pub trait MyTrait: Sized {
128
+ // Non-fn items should be preserved by the macro
129
+ const A_CONST: u32;
130
+ type Alias;
131
+
132
+ /// Default method with Env - should have TTL extension
133
+ fn default_with_env(env: Env, value: u32) -> u32 {
134
+ value * 2
135
+ }
136
+
137
+ /// Default method with qualified Env path - should have TTL extension
138
+ fn with_qualified_env(env: soroban_sdk::Env) -> u32 {
139
+ 42
140
+ }
141
+
142
+ /// Default method with Env not as first parameter - should have TTL extension
143
+ fn env_second(value: u32, env: &Env) -> u32 {
144
+ value * 2
145
+ }
146
+
147
+ /// Default method without Env - should NOT have TTL extension
148
+ fn default_without_env(value: u32) -> u32 {
149
+ value * 4
150
+ }
151
+
152
+ /// Abstract method with Env - should NOT have TTL extension (no body)
153
+ fn abstract_with_env(env: Env, value: u32) -> u32;
154
+
155
+ /// Abstract method without Env - should NOT have TTL extension (no body)
156
+ fn abstract_without_env(value: u32) -> u32;
157
+ }
158
+ };
159
+
160
+ let trait_result = crate::contract_ttl::contracttrait_with_ttl(empty_attr.clone(), trait_input);
161
+ let trait_formatted =
162
+ prettyplease::unparse(&syn::parse2::<syn::File>(trait_result).expect("failed to parse generated code"));
163
+
164
+ // Test trait with crate attr
165
+ let crate_attr = quote! { crate = "other_sdk" };
166
+ let trait_with_attr_input = quote! {
167
+ pub trait AnotherTrait {
168
+ /// Default method with Env - should have TTL extension
169
+ fn method_with_env(env: &Env, value: u32) -> u32 {
170
+ value * 3
171
+ }
172
+ }
173
+ };
174
+
175
+ let trait_with_attr_result = crate::contract_ttl::contracttrait_with_ttl(crate_attr, trait_with_attr_input);
176
+ let trait_with_attr_formatted = prettyplease::unparse(
177
+ &syn::parse2::<syn::File>(trait_with_attr_result).expect("failed to parse generated code"),
178
+ );
179
+
180
+ // Combine all for single snapshot
181
+ let combined = format!(
182
+ "// === Trait (no attr) ===\n\n{}\n\n// === Trait (crate attr) ===\n\n{}",
183
+ trait_formatted, trait_with_attr_formatted
184
+ );
185
+
186
+ insta::assert_snapshot!(combined);
187
+ }
188
+
189
+ // ============================================================================
190
+ // Error Cases: Invalid Input
191
+ // ============================================================================
192
+
193
+ #[test]
194
+ fn test_contractimpl_with_ttl_rejects_non_impl_block_input() {
195
+ for (case, input) in filter_item_inputs_excluding_labels(&["impl block"]) {
196
+ assert_panics_contains(case, "failed to parse impl block", || {
197
+ crate::contract_ttl::contractimpl_with_ttl(TokenStream::new(), input.clone());
198
+ });
199
+ }
200
+ }
201
+
202
+ #[test]
203
+ fn test_contracttrait_with_ttl_rejects_non_trait_input() {
204
+ for (case, input) in filter_item_inputs_excluding_labels(&["trait"]) {
205
+ assert_panics_contains(case, "failed to parse trait definition", || {
206
+ crate::contract_ttl::contracttrait_with_ttl(TokenStream::new(), input.clone());
207
+ });
208
+ }
209
+ }
210
+
211
+ // ============================================================================
212
+ // Unit Tests: Attribute Handling
213
+ // ============================================================================
214
+
215
+ #[test]
216
+ fn test_contractimpl_with_ttl_adds_contractimpl_attribute_table_driven() {
217
+ // (name, attr, input, expected_substrings, forbidden_substrings)
218
+ let cases: Vec<(&str, TokenStream, TokenStream, Vec<&str>, Vec<&str>)> = vec![
219
+ (
220
+ "empty attr",
221
+ TokenStream::new(),
222
+ quote! {
223
+ impl MyContract {
224
+ pub fn my_method(env: Env) {}
225
+ }
226
+ },
227
+ vec!["# [soroban_sdk :: contractimpl]"],
228
+ // ensure we don't emit `#[...::contractimpl(...)]` when attr is empty
229
+ vec!["contractimpl(", "contractimpl ("],
230
+ ),
231
+ (
232
+ "with attr",
233
+ quote! { contracttrait },
234
+ quote! {
235
+ impl SomeTrait for MyContract {
236
+ fn my_method(env: Env) {}
237
+ }
238
+ },
239
+ vec!["contractimpl (contracttrait)"],
240
+ vec![],
241
+ ),
242
+ ];
243
+
244
+ for (name, attr, input, expected, forbidden) in cases {
245
+ let result = crate::contract_ttl::contractimpl_with_ttl(attr, input);
246
+ let result_str = result.to_string();
247
+ for needle in expected {
248
+ assert!(result_str.contains(needle), "{name}: expected '{needle}'. Got: {result_str}");
249
+ }
250
+ for needle in forbidden {
251
+ assert!(!result_str.contains(needle), "{name}: should NOT contain '{needle}'. Got: {result_str}");
252
+ }
253
+ }
254
+ }
255
+
256
+ #[test]
257
+ fn test_contracttrait_with_ttl_adds_contracttrait_attribute_table_driven() {
258
+ // (name, attr, input, expected_substrings, forbidden_substrings)
259
+ let cases: Vec<(&str, TokenStream, TokenStream, Vec<&str>, Vec<&str>)> = vec![
260
+ (
261
+ "empty attr",
262
+ TokenStream::new(),
263
+ quote! {
264
+ pub trait MyTrait {
265
+ fn my_method(env: Env) {}
266
+ }
267
+ },
268
+ vec!["# [soroban_sdk :: contracttrait]"],
269
+ // ensure we don't emit `#[...::contracttrait(...)]` when attr is empty
270
+ vec!["contracttrait(", "contracttrait ("],
271
+ ),
272
+ (
273
+ "with attr",
274
+ quote! { crate = "my_crate" },
275
+ quote! {
276
+ pub trait MyTrait {
277
+ fn my_method(env: Env) {}
278
+ }
279
+ },
280
+ vec!["contracttrait (crate = \"my_crate\")"],
281
+ vec![],
282
+ ),
283
+ ];
284
+
285
+ for (name, attr, input, expected, forbidden) in cases {
286
+ let result = crate::contract_ttl::contracttrait_with_ttl(attr, input);
287
+ let result_str = result.to_string();
288
+ for needle in expected {
289
+ assert!(result_str.contains(needle), "{name}: expected '{needle}'. Got: {result_str}");
290
+ }
291
+ for needle in forbidden {
292
+ assert!(!result_str.contains(needle), "{name}: should NOT contain '{needle}'. Got: {result_str}");
293
+ }
294
+ }
295
+ }
296
+
297
+ // ============================================================================
298
+ // TTL Extension Test Infrastructure
299
+ // ============================================================================
300
+
301
+ /// Expected TTL extension behavior for a test case
302
+ #[derive(Clone)]
303
+ enum TtlExpectation {
304
+ /// TTL extension should NOT be inserted
305
+ None,
306
+ /// TTL extension should be inserted with the given env argument
307
+ /// - `env_arg`: pattern in `extend_instance_ttl(...)` (e.g., "& env" for owned, "env" for ref)
308
+ Present { env_arg: &'static str },
309
+ }
310
+
311
+ /// Test runner for contractimpl_with_ttl TTL behavior
312
+ struct ImplTtlTestCase {
313
+ name: &'static str,
314
+ input: TokenStream,
315
+ expectation: TtlExpectation,
316
+ }
317
+
318
+ impl ImplTtlTestCase {
319
+ fn expect_ttl(name: &'static str, input: TokenStream, env_arg: &'static str) -> Self {
320
+ Self { name, input, expectation: TtlExpectation::Present { env_arg } }
321
+ }
322
+
323
+ fn expect_no_ttl(name: &'static str, input: TokenStream) -> Self {
324
+ Self { name, input, expectation: TtlExpectation::None }
325
+ }
326
+
327
+ fn run(&self) {
328
+ let result = crate::contract_ttl::contractimpl_with_ttl(TokenStream::new(), self.input.clone());
329
+ let result_str = result.to_string();
330
+ assert_ttl_expectation(&self.expectation, &result_str, self.name);
331
+ }
332
+ }
333
+
334
+ /// Test runner for contracttrait_with_ttl TTL behavior
335
+ struct TraitTtlTestCase {
336
+ name: &'static str,
337
+ input: TokenStream,
338
+ expectation: TtlExpectation,
339
+ }
340
+
341
+ impl TraitTtlTestCase {
342
+ fn expect_ttl(name: &'static str, input: TokenStream, env_arg: &'static str) -> Self {
343
+ Self { name, input, expectation: TtlExpectation::Present { env_arg } }
344
+ }
345
+
346
+ fn expect_no_ttl(name: &'static str, input: TokenStream) -> Self {
347
+ Self { name, input, expectation: TtlExpectation::None }
348
+ }
349
+
350
+ fn run(&self) {
351
+ let result = crate::contract_ttl::contracttrait_with_ttl(TokenStream::new(), self.input.clone());
352
+ let result_str = result.to_string();
353
+ assert_ttl_expectation(&self.expectation, &result_str, self.name);
354
+ }
355
+ }
356
+
357
+ fn assert_ttl_expectation(expectation: &TtlExpectation, result_str: &str, name: &str) {
358
+ let has_ttl = result_str.contains("extend_instance_ttl");
359
+
360
+ match expectation {
361
+ TtlExpectation::None => {
362
+ assert!(!has_ttl, "{}: TTL extension should NOT be present, but was found", name);
363
+ }
364
+ TtlExpectation::Present { env_arg } => {
365
+ assert!(has_ttl, "{}: TTL extension should be present, but was not found", name);
366
+
367
+ let extend_pattern = format!("extend_instance_ttl ({})", env_arg);
368
+ assert!(
369
+ result_str.contains(&extend_pattern),
370
+ "{}: expected '{}' in extend_instance_ttl call. Got: {}",
371
+ name,
372
+ env_arg,
373
+ result_str
374
+ );
375
+ }
376
+ }
377
+ }
378
+
379
+ // ============================================================================
380
+ // TTL Extension Tests: contractimpl_with_ttl
381
+ // ============================================================================
382
+
383
+ #[test]
384
+ fn test_contractimpl_with_ttl_inserts_ttl_for_methods_with_env_table_driven() {
385
+ // contractimpl_with_ttl: TTL should be inserted for methods with Env
386
+ // - inherent impl: only `pub` methods are eligible
387
+ // - trait impl: all methods are eligible
388
+ //
389
+ // (name, input, env_arg)
390
+ let test_data = vec![
391
+ (
392
+ "public method with owned Env",
393
+ quote! {
394
+ impl MyContract {
395
+ pub fn my_method(env: Env) { let x = 1; }
396
+ }
397
+ },
398
+ "& env",
399
+ ),
400
+ (
401
+ "public method with ref Env",
402
+ quote! {
403
+ impl MyContract {
404
+ pub fn my_method(env: &Env) { let x = 1; }
405
+ }
406
+ },
407
+ "env",
408
+ ),
409
+ (
410
+ "public method with mut ref Env",
411
+ quote! {
412
+ impl MyContract {
413
+ pub fn my_method(env: &mut Env) { let x = 1; }
414
+ }
415
+ },
416
+ "env",
417
+ ),
418
+ (
419
+ "custom Env identifier (owned)",
420
+ quote! {
421
+ impl MyContract {
422
+ pub fn my_method(my_custom_env: Env) { let x = 1; }
423
+ }
424
+ },
425
+ "& my_custom_env",
426
+ ),
427
+ (
428
+ "custom Env identifier (ref)",
429
+ quote! {
430
+ impl MyContract {
431
+ pub fn my_method(my_custom_env: &Env) { let x = 1; }
432
+ }
433
+ },
434
+ "my_custom_env",
435
+ ),
436
+ (
437
+ "public method with receiver and Env",
438
+ quote! {
439
+ impl MyContract {
440
+ pub fn my_method(&self, env: &Env) { let x = 1; }
441
+ }
442
+ },
443
+ "env",
444
+ ),
445
+ (
446
+ "public method with mut Env binding (owned)",
447
+ quote! {
448
+ impl MyContract {
449
+ pub fn my_method(mut env: Env) { let x = 1; }
450
+ }
451
+ },
452
+ "& env",
453
+ ),
454
+ (
455
+ "public method with ::soroban_sdk::Env (owned)",
456
+ quote! {
457
+ impl MyContract {
458
+ pub fn my_method(env: ::soroban_sdk::Env) { let x = 1; }
459
+ }
460
+ },
461
+ "& env",
462
+ ),
463
+ (
464
+ "public method with &::soroban_sdk::Env (ref)",
465
+ quote! {
466
+ impl MyContract {
467
+ pub fn my_method(env: &::soroban_sdk::Env) { let x = 1; }
468
+ }
469
+ },
470
+ "env",
471
+ ),
472
+ (
473
+ "trait impl method with owned Env",
474
+ quote! {
475
+ impl SomeTrait for MyContract {
476
+ fn trait_method(env: Env) { let x = 1; }
477
+ }
478
+ },
479
+ "& env",
480
+ ),
481
+ (
482
+ "trait impl method with ref Env",
483
+ quote! {
484
+ impl SomeTrait for MyContract {
485
+ fn trait_method(env: &Env) { let x = 1; }
486
+ }
487
+ },
488
+ "env",
489
+ ),
490
+ ];
491
+
492
+ for (name, input, env_arg) in test_data {
493
+ ImplTtlTestCase::expect_ttl(name, input, env_arg).run();
494
+ }
495
+ }
496
+
497
+ #[test]
498
+ fn test_contractimpl_with_ttl_skips_ttl_insertion_table_driven() {
499
+ // (name, input)
500
+ let cases = vec![
501
+ (
502
+ "private method with Env",
503
+ quote! {
504
+ impl MyContract {
505
+ fn private_method(env: Env) { let x = 1; }
506
+ }
507
+ },
508
+ ),
509
+ (
510
+ "public method without Env",
511
+ quote! {
512
+ impl MyContract {
513
+ pub fn no_env_method(value: u32) -> u32 { value }
514
+ }
515
+ },
516
+ ),
517
+ (
518
+ "public method with wildcard Env binding (unsupported pattern)",
519
+ quote! {
520
+ impl MyContract {
521
+ pub fn my_method(_: Env) { let x = 1; }
522
+ }
523
+ },
524
+ ),
525
+ (
526
+ "pub(super) method with Env (not fully public)",
527
+ quote! {
528
+ impl MyContract {
529
+ pub(super) fn my_method(env: Env) { let x = 1; }
530
+ }
531
+ },
532
+ ),
533
+ (
534
+ "pub(in ...) method with Env (not fully public)",
535
+ quote! {
536
+ impl MyContract {
537
+ pub(in crate::some_module) fn my_method(env: Env) { let x = 1; }
538
+ }
539
+ },
540
+ ),
541
+ ];
542
+
543
+ for (name, input) in cases {
544
+ ImplTtlTestCase::expect_no_ttl(name, input).run();
545
+ }
546
+ }
547
+
548
+ // ============================================================================
549
+ // TTL Extension Tests: contracttrait_with_ttl
550
+ // ============================================================================
551
+
552
+ #[test]
553
+ fn test_contracttrait_with_ttl_inserts_ttl_for_default_methods_with_env_table_driven() {
554
+ // (name, input, env_arg)
555
+ let test_data = vec![
556
+ (
557
+ "default method with owned Env",
558
+ quote! {
559
+ trait MyTrait {
560
+ fn my_method(env: Env) { let x = 1; }
561
+ }
562
+ },
563
+ "& env",
564
+ ),
565
+ (
566
+ "default method with ref Env",
567
+ quote! {
568
+ trait MyTrait {
569
+ fn my_method(env: &Env) { let x = 1; }
570
+ }
571
+ },
572
+ "env",
573
+ ),
574
+ (
575
+ "default method with mut ref Env",
576
+ quote! {
577
+ trait MyTrait {
578
+ fn my_method(env: &mut Env) { let x = 1; }
579
+ }
580
+ },
581
+ "env",
582
+ ),
583
+ (
584
+ "custom Env identifier (owned)",
585
+ quote! {
586
+ trait MyTrait {
587
+ fn my_method(my_custom_env: Env) { let x = 1; }
588
+ }
589
+ },
590
+ "& my_custom_env",
591
+ ),
592
+ (
593
+ "custom Env identifier (ref)",
594
+ quote! {
595
+ trait MyTrait {
596
+ fn my_method(my_custom_env: &Env) { let x = 1; }
597
+ }
598
+ },
599
+ "my_custom_env",
600
+ ),
601
+ (
602
+ "default method with receiver and Env",
603
+ quote! {
604
+ trait MyTrait {
605
+ fn my_method(&self, env: &Env) { let x = 1; }
606
+ }
607
+ },
608
+ "env",
609
+ ),
610
+ ];
611
+
612
+ for (name, input, env_arg) in test_data {
613
+ TraitTtlTestCase::expect_ttl(name, input, env_arg).run();
614
+ }
615
+ }
616
+
617
+ // ============================================================================
618
+ // Constructor behavior: contractimpl_with_ttl
619
+ // ============================================================================
620
+
621
+ #[test]
622
+ fn test_contractimpl_with_ttl_injects_init_default_ttl_configs_only_in_constructor() {
623
+ // `__constructor` should get default TTL config init, and should NOT get TTL extension.
624
+ let cases: Vec<(&str, TokenStream, Vec<&str>, Vec<&str>)> = vec![
625
+ (
626
+ "constructor with owned Env",
627
+ quote! {
628
+ impl MyContract {
629
+ pub fn __constructor(env: Env) { let x = 1; }
630
+ }
631
+ },
632
+ // expects init call and correct arg form (`&env` because Env is owned)
633
+ vec!["ttl_configurable :: init_default_ttl_configs (& env)"],
634
+ // should not insert TTL extension
635
+ vec!["TtlConfigStorage :: instance", "extend_ttl"],
636
+ ),
637
+ (
638
+ "constructor with ref Env",
639
+ quote! {
640
+ impl MyContract {
641
+ pub fn __constructor(env: &Env) { let x = 1; }
642
+ }
643
+ },
644
+ // expects init call and correct arg form (`env` because Env is already a ref)
645
+ vec!["ttl_configurable :: init_default_ttl_configs (env)"],
646
+ vec!["TtlConfigStorage :: instance", "extend_ttl"],
647
+ ),
648
+ ];
649
+
650
+ for (name, input, expected, forbidden) in cases {
651
+ let result = crate::contract_ttl::contractimpl_with_ttl(TokenStream::new(), input);
652
+ let result_str = result.to_string();
653
+ for needle in expected {
654
+ assert!(result_str.contains(needle), "{name}: expected '{needle}'. Got: {result_str}");
655
+ }
656
+ for needle in forbidden {
657
+ assert!(!result_str.contains(needle), "{name}: should NOT contain '{needle}'. Got: {result_str}");
658
+ }
659
+ }
660
+ }
661
+
662
+ #[test]
663
+ fn test_contracttrait_with_ttl_skips_ttl_insertion_table_driven() {
664
+ // (name, input)
665
+ let cases = vec![
666
+ (
667
+ "abstract method with Env (no body)",
668
+ quote! {
669
+ trait MyTrait {
670
+ fn abstract_method(env: Env) -> u32;
671
+ }
672
+ },
673
+ ),
674
+ (
675
+ "default method without Env",
676
+ quote! {
677
+ trait MyTrait {
678
+ fn no_env_method(value: u32) -> u32 { value }
679
+ }
680
+ },
681
+ ),
682
+ (
683
+ "default method with wildcard Env binding (unsupported pattern)",
684
+ quote! {
685
+ trait MyTrait {
686
+ fn my_method(_: Env) { let x = 1; }
687
+ }
688
+ },
689
+ ),
690
+ ];
691
+
692
+ for (name, input) in cases {
693
+ TraitTtlTestCase::expect_no_ttl(name, input).run();
694
+ }
695
+ }