@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
@@ -19,15 +19,33 @@ fn snapshot_generated_ownable_code() {
19
19
 
20
20
  #[test]
21
21
  fn snapshot_only_owner_preserves_function_signature() {
22
- let input = quote! {
22
+ // Test with borrowed Env (&Env) - should use env directly
23
+ let input_borrowed = quote! {
23
24
  pub(crate) async fn admin_action<T: Clone>(env: &Env, value: T) -> Result<T, Error> {
24
25
  Ok(value.clone())
25
26
  }
26
27
  };
27
- let result = crate::ownable::prepend_only_owner_check(input);
28
- let formatted = prettyplease::unparse(&syn::parse2::<syn::File>(result).expect("failed to parse generated code"));
28
+ let result_borrowed = crate::ownable::prepend_only_owner_check(input_borrowed);
29
+ let formatted_borrowed =
30
+ prettyplease::unparse(&syn::parse2::<syn::File>(result_borrowed).expect("failed to parse generated code"));
29
31
 
30
- insta::assert_snapshot!(formatted);
32
+ // Test with owned Env - should produce &env reference in the generated code
33
+ let input_owned = quote! {
34
+ pub(crate) async fn admin_action<T: Clone>(env: Env, value: T) -> Result<T, Error> {
35
+ Ok(value.clone())
36
+ }
37
+ };
38
+ let result_owned = crate::ownable::prepend_only_owner_check(input_owned);
39
+ let formatted_owned =
40
+ prettyplease::unparse(&syn::parse2::<syn::File>(result_owned).expect("failed to parse generated code"));
41
+
42
+ // Combine all for single snapshot
43
+ let combined = format!(
44
+ "// === Borrowed Env (&Env) ===\n\n{}\n\n// === Owned Env (Env) ===\n\n{}",
45
+ formatted_borrowed, formatted_owned
46
+ );
47
+
48
+ insta::assert_snapshot!(combined);
31
49
  }
32
50
 
33
51
  // ============================================
@@ -35,7 +53,7 @@ fn snapshot_only_owner_preserves_function_signature() {
35
53
  // ============================================
36
54
 
37
55
  /// Helper function to verify that the owner check is correctly inserted at the beginning of a function.
38
- fn assert_owner_check_inserted(input: TokenStream, test_name: &str) {
56
+ fn assert_owner_check_inserted(input: TokenStream, expected_env_ref: &str, test_name: &str) {
39
57
  let result_tokens = crate::ownable::prepend_only_owner_check(input);
40
58
  let output_fn: syn::ItemFn =
41
59
  syn::parse2(result_tokens).unwrap_or_else(|e| panic!("{}: failed to parse output function: {}", test_name, e));
@@ -43,7 +61,7 @@ fn assert_owner_check_inserted(input: TokenStream, test_name: &str) {
43
61
  assert!(!output_fn.block.stmts.is_empty(), "{}: function body should contain at least one statement", test_name);
44
62
 
45
63
  let first_stmt = &output_fn.block.stmts[0];
46
- let expected_stmt = "utils::ownable::require_owner_auth::<Self>(my_custom_env);";
64
+ let expected_stmt = format!("utils::ownable::require_owner_auth::<Self>({});", expected_env_ref);
47
65
  let actual_stmt = quote::quote!(#first_stmt).to_string().replace(" ", "");
48
66
 
49
67
  assert_eq!(
@@ -55,15 +73,22 @@ fn assert_owner_check_inserted(input: TokenStream, test_name: &str) {
55
73
 
56
74
  #[test]
57
75
  fn test_only_owner_inserts_correct_code_at_the_beginning() {
76
+ // (description, input, expected_env_ref)
77
+ // Owned Env should produce &env, reference Env should produce env directly
58
78
  let test_cases = vec![
59
- ("Env by value", quote! { pub fn f(my_custom_env: Env) {} }),
60
- ("Env not first param", quote! { pub fn f(x: u32, my_custom_env: &Env) {} }),
61
- ("Qualified path (soroban_sdk::Env)", quote! { pub fn f(my_custom_env: soroban_sdk::Env) {} }),
62
- ("Nested reference (&&Env)", quote! { pub fn f(my_custom_env: &&Env) {} }),
79
+ ("Env by value", quote! { pub fn f(my_custom_env: Env) {} }, "&my_custom_env"),
80
+ ("Env not first param (ref)", quote! { pub fn f(x: u32, my_custom_env: &Env) {} }, "my_custom_env"),
81
+ (
82
+ "Qualified path (soroban_sdk::Env)",
83
+ quote! { pub fn f(my_custom_env: soroban_sdk::Env) {} },
84
+ "&my_custom_env",
85
+ ),
86
+ ("Reference to qualified path", quote! { pub fn f(my_custom_env: &soroban_sdk::Env) {} }, "my_custom_env"),
87
+ ("Nested reference (&&Env)", quote! { pub fn f(my_custom_env: &&Env) {} }, "my_custom_env"),
63
88
  ];
64
89
 
65
- for (description, input) in test_cases {
66
- assert_owner_check_inserted(input, description);
90
+ for (description, input, expected_env_ref) in test_cases {
91
+ assert_owner_check_inserted(input, expected_env_ref, description);
67
92
  }
68
93
  }
69
94
 
@@ -101,51 +126,3 @@ fn test_ownable_rejects_non_struct_inputs() {
101
126
  });
102
127
  }
103
128
  }
104
-
105
- // ============================================
106
- // is_env_type Unit Tests
107
- // ============================================
108
-
109
- #[test]
110
- fn test_is_env_type_recognizes_env_types() {
111
- let env_types = [
112
- "Env",
113
- "&Env",
114
- "&&Env",
115
- "&mut Env",
116
- "soroban_sdk::Env",
117
- "&soroban_sdk::Env",
118
- "some::deeply::nested::module::Env",
119
- ];
120
-
121
- for ty_str in env_types {
122
- let ty = syn::parse_str::<syn::Type>(ty_str).expect("failed to parse type");
123
- assert!(crate::utils::is_env_type(&ty), "{ty_str} should be recognized as an Env type");
124
- }
125
- }
126
-
127
- #[test]
128
- fn test_is_env_type_rejects_non_env_types() {
129
- let non_env_types = ["u32", "bool", "String", "Address", "&u32", "soroban_sdk::Address"];
130
-
131
- for ty_str in non_env_types {
132
- let ty = syn::parse_str::<syn::Type>(ty_str).expect("failed to parse type");
133
- assert!(!crate::utils::is_env_type(&ty), "{ty_str} should NOT be recognized as an Env type");
134
- }
135
- }
136
-
137
- #[test]
138
- fn test_is_env_type_rejects_other_type_variants() {
139
- let other_variants = [
140
- ("(Env, u32)", "tuple"),
141
- ("[Env; 1]", "array"),
142
- ("[Env]", "slice"),
143
- ("Option<Env>", "generic"),
144
- ("!", "never"),
145
- ];
146
-
147
- for (ty_str, label) in other_variants {
148
- let ty = syn::parse_str::<syn::Type>(ty_str).expect("failed to parse type");
149
- assert!(!crate::utils::is_env_type(&ty), "{label} type {ty_str} should NOT be recognized as an Env type");
150
- }
151
- }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  source: contracts/common-macros/src/tests/contract_impl.rs
3
- assertion_line: 98
3
+ assertion_line: 104
4
4
  expression: combined
5
5
  ---
6
6
  // === Inherent Impl (no attr) ===
@@ -9,7 +9,7 @@ expression: combined
9
9
  impl MyContract {
10
10
  /// Public method with Env - should have TTL extension
11
11
  pub fn public_with_env(env: Env, value: u32) -> u32 {
12
- if let Some(instance_ttl) = utils::ttl::TtlConfigStorage::instance(env) {
12
+ if let Some(instance_ttl) = utils::ttl::TtlConfigStorage::instance(&env) {
13
13
  env.storage()
14
14
  .instance()
15
15
  .extend_ttl(instance_ttl.threshold, instance_ttl.extend_to);
@@ -18,7 +18,7 @@ impl MyContract {
18
18
  }
19
19
  /// Public method with qualified Env path - should have TTL extension
20
20
  pub fn with_qualified_env(env: soroban_sdk::Env) -> u32 {
21
- if let Some(instance_ttl) = utils::ttl::TtlConfigStorage::instance(env) {
21
+ if let Some(instance_ttl) = utils::ttl::TtlConfigStorage::instance(&env) {
22
22
  env.storage()
23
23
  .instance()
24
24
  .extend_ttl(instance_ttl.threshold, instance_ttl.extend_to);
@@ -26,7 +26,7 @@ impl MyContract {
26
26
  42
27
27
  }
28
28
  /// Public method with Env not as first parameter - should have TTL extension
29
- pub fn env_second(value: u32, env: Env) -> u32 {
29
+ pub fn env_second(value: u32, env: &Env) -> u32 {
30
30
  if let Some(instance_ttl) = utils::ttl::TtlConfigStorage::instance(env) {
31
31
  env.storage()
32
32
  .instance()
@@ -55,7 +55,7 @@ impl MyContract {
55
55
  impl SomeTrait for MyContract {
56
56
  /// Trait method with Env - should have TTL extension
57
57
  fn trait_method_with_env(env: Env, value: u32) -> u32 {
58
- if let Some(instance_ttl) = utils::ttl::TtlConfigStorage::instance(env) {
58
+ if let Some(instance_ttl) = utils::ttl::TtlConfigStorage::instance(&env) {
59
59
  env.storage()
60
60
  .instance()
61
61
  .extend_ttl(instance_ttl.threshold, instance_ttl.extend_to);
@@ -66,6 +66,16 @@ impl SomeTrait for MyContract {
66
66
  fn trait_method_without_env(value: u32) -> u32 {
67
67
  value * 4
68
68
  }
69
+ /// Trait method with macro attribute - should have TTL extension
70
+ #[common_macros::only_owner]
71
+ fn trait_method_with_only_owner_attribute(env: Env, value: u32) -> u32 {
72
+ if let Some(instance_ttl) = utils::ttl::TtlConfigStorage::instance(&env) {
73
+ env.storage()
74
+ .instance()
75
+ .extend_ttl(instance_ttl.threshold, instance_ttl.extend_to);
76
+ }
77
+ value * 5
78
+ }
69
79
  }
70
80
 
71
81
 
@@ -74,7 +84,7 @@ impl SomeTrait for MyContract {
74
84
  #[soroban_sdk::contractimpl(contracttrait)]
75
85
  impl AnotherTrait for MyContract {
76
86
  /// Trait method with contracttrait attr - should have TTL extension
77
- fn contracttrait_method(env: Env, value: u32) -> u32 {
87
+ fn contracttrait_method(env: &Env, value: u32) -> u32 {
78
88
  if let Some(instance_ttl) = utils::ttl::TtlConfigStorage::instance(env) {
79
89
  env.storage()
80
90
  .instance()
@@ -0,0 +1,20 @@
1
+ ---
2
+ source: contracts/common-macros/src/tests/error.rs
3
+ expression: formatted
4
+ ---
5
+ #[soroban_sdk::contracterror]
6
+ #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
7
+ #[repr(u32)]
8
+ /// Example error enum
9
+ pub enum MyError {
10
+ /// Implicit (should start at 1)
11
+ A = 1u32,
12
+ /// Implicit (should be 2)
13
+ B = 2u32,
14
+ /// Explicit (must be >= previous + 1)
15
+ C = 10,
16
+ /// Implicit (should be 11)
17
+ D = 11u32,
18
+ /// Explicit max boundary (u32::MAX)
19
+ E = 4294967295,
20
+ }
@@ -16,14 +16,16 @@ impl utils::ownable::OwnableInitializer for MyContract {
16
16
  }
17
17
  }
18
18
  /// Implement the Ownable trait for the contract.
19
- #[soroban_sdk::contractimpl]
19
+ #[common_macros::contract_impl]
20
20
  impl utils::ownable::Ownable for MyContract {
21
21
  fn owner(env: &soroban_sdk::Env) -> Option<soroban_sdk::Address> {
22
22
  utils::ownable::DefaultOwnable::owner(env)
23
23
  }
24
+ #[common_macros::only_owner]
24
25
  fn transfer_ownership(env: &soroban_sdk::Env, new_owner: &soroban_sdk::Address) {
25
26
  utils::ownable::DefaultOwnable::transfer_ownership(env, new_owner)
26
27
  }
28
+ #[common_macros::only_owner]
27
29
  fn renounce_ownership(env: &soroban_sdk::Env) {
28
30
  utils::ownable::DefaultOwnable::renounce_ownership(env)
29
31
  }
@@ -1,9 +1,19 @@
1
1
  ---
2
2
  source: contracts/common-macros/src/tests/ownable.rs
3
- assertion_line: 28
4
- expression: formatted
3
+ assertion_line: 48
4
+ expression: combined
5
5
  ---
6
+ // === Borrowed Env (&Env) ===
7
+
6
8
  pub(crate) async fn admin_action<T: Clone>(env: &Env, value: T) -> Result<T, Error> {
7
9
  utils::ownable::require_owner_auth::<Self>(env);
8
10
  Ok(value.clone())
9
11
  }
12
+
13
+
14
+ // === Owned Env (Env) ===
15
+
16
+ pub(crate) async fn admin_action<T: Clone>(env: Env, value: T) -> Result<T, Error> {
17
+ utils::ownable::require_owner_auth::<Self>(&env);
18
+ Ok(value.clone())
19
+ }
@@ -8,7 +8,7 @@ pub struct MyContract {
8
8
  some_field: u32,
9
9
  }
10
10
  use utils::ttl::TtlConfigurable as _;
11
- #[soroban_sdk::contractimpl]
11
+ #[common_macros::contract_impl]
12
12
  impl utils::ttl::TtlConfigurable for MyContract {
13
13
  #[common_macros::only_owner]
14
14
  fn set_ttl_configs(
@@ -31,6 +31,10 @@ impl utils::ttl::TtlConfigurable for MyContract {
31
31
  utils::ttl::DefaultTtlConfigurable::is_ttl_configs_frozen(env)
32
32
  }
33
33
  }
34
+ /// Uses `soroban_sdk::contractimpl` directly instead of `common_macros::contract_impl`
35
+ /// because `contract_impl` automatically extends TTL on every invocation. Since this
36
+ /// impl block provides manual TTL extension control, auto-extension would be redundant
37
+ /// and could mask the intended behavior of `extend_instance_ttl`.
34
38
  #[soroban_sdk::contractimpl]
35
39
  impl MyContract {
36
40
  /// Extends the instance TTL.
@@ -0,0 +1,267 @@
1
+ use quote::quote;
2
+ use syn::{parse_quote, punctuated::Punctuated, token::Comma, FnArg};
3
+
4
+ use crate::tests::test_helpers::assert_panics_contains;
5
+
6
+ // ============================================
7
+ // find_env_param Tests
8
+ // ============================================
9
+
10
+ fn parse_fn_args(input: proc_macro2::TokenStream) -> Punctuated<FnArg, Comma> {
11
+ let item_fn: syn::ItemFn = syn::parse2(input).expect("failed to parse function");
12
+ item_fn.sig.inputs
13
+ }
14
+
15
+ #[test]
16
+ fn test_find_env_param_owned_env_is_not_reference() {
17
+ let args = parse_fn_args(quote! { fn f(env: Env) {} });
18
+ let param = crate::utils::find_env_param(&args);
19
+ assert!(param.is_some());
20
+ let param = param.unwrap();
21
+ assert_eq!(param.ident.to_string(), "env");
22
+ assert!(!param.is_reference, "Env should not be marked as reference");
23
+ }
24
+
25
+ #[test]
26
+ fn test_find_env_param_ref_env_is_reference() {
27
+ let args = parse_fn_args(quote! { fn f(env: &Env) {} });
28
+ let param = crate::utils::find_env_param(&args);
29
+ assert!(param.is_some());
30
+ let param = param.unwrap();
31
+ assert_eq!(param.ident.to_string(), "env");
32
+ assert!(param.is_reference, "&Env should be marked as reference");
33
+ }
34
+
35
+ #[test]
36
+ fn test_find_env_param_mut_ref_env_is_reference() {
37
+ let args = parse_fn_args(quote! { fn f(env: &mut Env) {} });
38
+ let param = crate::utils::find_env_param(&args);
39
+ assert!(param.is_some());
40
+ let param = param.unwrap();
41
+ assert_eq!(param.ident.to_string(), "env");
42
+ assert!(param.is_reference, "&mut Env should be marked as reference");
43
+ }
44
+
45
+ #[test]
46
+ fn test_find_env_param_qualified_owned_env() {
47
+ let args = parse_fn_args(quote! { fn f(my_env: soroban_sdk::Env) {} });
48
+ let param = crate::utils::find_env_param(&args);
49
+ assert!(param.is_some());
50
+ let param = param.unwrap();
51
+ assert_eq!(param.ident.to_string(), "my_env");
52
+ assert!(!param.is_reference, "soroban_sdk::Env should not be marked as reference");
53
+ }
54
+
55
+ #[test]
56
+ fn test_find_env_param_qualified_ref_env() {
57
+ let args = parse_fn_args(quote! { fn f(my_env: &soroban_sdk::Env) {} });
58
+ let param = crate::utils::find_env_param(&args);
59
+ assert!(param.is_some());
60
+ let param = param.unwrap();
61
+ assert_eq!(param.ident.to_string(), "my_env");
62
+ assert!(param.is_reference, "&soroban_sdk::Env should be marked as reference");
63
+ }
64
+
65
+ #[test]
66
+ fn test_find_env_param_returns_none_for_no_env() {
67
+ let args = parse_fn_args(quote! { fn f(x: u32) {} });
68
+ let param = crate::utils::find_env_param(&args);
69
+ assert!(param.is_none());
70
+ }
71
+
72
+ #[test]
73
+ fn test_find_env_param_finds_deeply_nested_env() {
74
+ let args = parse_fn_args(quote! { fn f(e: some::deep::module::Env) {} });
75
+ let param = crate::utils::find_env_param(&args);
76
+ assert!(param.is_some());
77
+ let param = param.unwrap();
78
+ assert_eq!(param.ident.to_string(), "e");
79
+ assert!(!param.is_reference, "some::deep::module::Env should not be marked as reference");
80
+ }
81
+
82
+ #[test]
83
+ fn test_find_env_param_finds_env_not_first_param() {
84
+ let args = parse_fn_args(quote! { fn f(x: u32, y: String, env: &Env) {} });
85
+ let param = crate::utils::find_env_param(&args);
86
+ assert!(param.is_some());
87
+ let param = param.unwrap();
88
+ assert_eq!(param.ident.to_string(), "env");
89
+ assert!(param.is_reference, "&Env should be marked as reference");
90
+ }
91
+
92
+ #[test]
93
+ fn test_find_env_param_returns_first_env_when_multiple() {
94
+ let args = parse_fn_args(quote! { fn f(first_env: Env, second_env: Env) {} });
95
+ let param = crate::utils::find_env_param(&args);
96
+ assert!(param.is_some());
97
+ let param = param.unwrap();
98
+ assert_eq!(param.ident.to_string(), "first_env");
99
+ assert!(!param.is_reference, "first_env should not be marked as reference");
100
+ }
101
+
102
+ #[test]
103
+ fn test_find_env_param_returns_none_for_empty_args() {
104
+ let args = parse_fn_args(quote! { fn f() {} });
105
+ let param = crate::utils::find_env_param(&args);
106
+ assert!(param.is_none());
107
+ }
108
+
109
+ #[test]
110
+ fn test_find_env_param_returns_none_for_wildcard_pattern() {
111
+ // Wildcard pattern _ is not a valid identifier pattern
112
+ let args = parse_fn_args(quote! { fn f(_: Env) {} });
113
+ let param = crate::utils::find_env_param(&args);
114
+ assert!(param.is_none());
115
+ }
116
+
117
+ #[test]
118
+ fn test_find_env_param_returns_none_for_tuple_pattern() {
119
+ // Tuple destructuring is not a simple identifier pattern
120
+ let args = parse_fn_args(quote! { fn f((env, _): (Env, u32)) {} });
121
+ let param = crate::utils::find_env_param(&args);
122
+ assert!(param.is_none());
123
+ }
124
+
125
+ #[test]
126
+ fn test_find_env_param_ignores_self_receiver() {
127
+ // Method with self receiver - find_env_param should skip receiver and find env
128
+ let args: Punctuated<FnArg, Comma> = parse_quote!(&self, env: &Env);
129
+ let param = crate::utils::find_env_param(&args);
130
+ assert!(param.is_some());
131
+ let param = param.unwrap();
132
+ assert_eq!(param.ident.to_string(), "env");
133
+ assert!(param.is_reference, "&Env should be marked as reference");
134
+ }
135
+
136
+ #[test]
137
+ fn test_find_env_param_returns_none_for_self_only() {
138
+ // Method with only self receiver
139
+ let args: Punctuated<FnArg, Comma> = parse_quote!(&self);
140
+ let param = crate::utils::find_env_param(&args);
141
+ assert!(param.is_none());
142
+ }
143
+
144
+ #[test]
145
+ fn test_find_env_param_with_double_reference() {
146
+ let args = parse_fn_args(quote! { fn f(env: &&Env) {} });
147
+ let param = crate::utils::find_env_param(&args);
148
+ assert!(param.is_some());
149
+ let param = param.unwrap();
150
+ assert_eq!(param.ident.to_string(), "env");
151
+ assert!(param.is_reference, "&&Env should be marked as reference");
152
+ }
153
+
154
+ // ============================================
155
+ // expect_env_param Tests
156
+ // ============================================
157
+
158
+ #[test]
159
+ fn test_expect_env_param_returns_param_for_owned_env() {
160
+ let args = parse_fn_args(quote! { fn f(env: Env) {} });
161
+ let param = crate::utils::expect_env_param(&args);
162
+ assert_eq!(param.ident.to_string(), "env");
163
+ assert!(!param.is_reference);
164
+ }
165
+
166
+ #[test]
167
+ fn test_expect_env_param_returns_param_for_ref_env() {
168
+ let args = parse_fn_args(quote! { fn f(env: &Env) {} });
169
+ let param = crate::utils::expect_env_param(&args);
170
+ assert_eq!(param.ident.to_string(), "env");
171
+ assert!(param.is_reference);
172
+ }
173
+
174
+ #[test]
175
+ fn test_expect_env_param_panics_when_no_env() {
176
+ assert_panics_contains("no Env param", "function must have an Env argument", || {
177
+ let args = parse_fn_args(quote! { fn f(x: u32) {} });
178
+ crate::utils::expect_env_param(&args);
179
+ });
180
+ }
181
+
182
+ // ============================================
183
+ // EnvParam::as_ref_tokens Tests
184
+ // ============================================
185
+
186
+ #[test]
187
+ fn test_as_ref_tokens_for_owned_env_adds_ampersand() {
188
+ let args = parse_fn_args(quote! { fn f(env: Env) {} });
189
+ let param = crate::utils::find_env_param(&args).unwrap();
190
+ let tokens = param.as_ref_tokens();
191
+ // For owned Env, as_ref_tokens should produce `&env`
192
+ assert_eq!(tokens.to_string(), "& env");
193
+ }
194
+
195
+ #[test]
196
+ fn test_as_ref_tokens_for_ref_env_no_ampersand() {
197
+ let args = parse_fn_args(quote! { fn f(env: &Env) {} });
198
+ let param = crate::utils::find_env_param(&args).unwrap();
199
+ let tokens = param.as_ref_tokens();
200
+ // For reference Env, as_ref_tokens should produce `env` (no extra &)
201
+ assert_eq!(tokens.to_string(), "env");
202
+ }
203
+
204
+ #[test]
205
+ fn test_as_ref_tokens_for_custom_named_owned_env() {
206
+ let args = parse_fn_args(quote! { fn f(my_environment: Env) {} });
207
+ let param = crate::utils::find_env_param(&args).unwrap();
208
+ let tokens = param.as_ref_tokens();
209
+ assert_eq!(tokens.to_string(), "& my_environment");
210
+ }
211
+
212
+ #[test]
213
+ fn test_as_ref_tokens_for_custom_named_ref_env() {
214
+ let args = parse_fn_args(quote! { fn f(my_environment: &Env) {} });
215
+ let param = crate::utils::find_env_param(&args).unwrap();
216
+ let tokens = param.as_ref_tokens();
217
+ assert_eq!(tokens.to_string(), "my_environment");
218
+ }
219
+
220
+ // ============================================
221
+ // is_env_type Tests (comprehensive coverage)
222
+ // ============================================
223
+
224
+ #[test]
225
+ fn test_is_env_type_recognizes_env_types() {
226
+ let env_types = [
227
+ "Env",
228
+ "&Env",
229
+ "&&Env",
230
+ "&mut Env",
231
+ "soroban_sdk::Env",
232
+ "&soroban_sdk::Env",
233
+ "some::deeply::nested::module::Env",
234
+ ];
235
+
236
+ for ty_str in env_types {
237
+ let ty = syn::parse_str::<syn::Type>(ty_str).expect("failed to parse type");
238
+ assert!(crate::utils::is_env_type(&ty), "{ty_str} should be recognized as an Env type");
239
+ }
240
+ }
241
+
242
+ #[test]
243
+ fn test_is_env_type_rejects_non_env_types() {
244
+ let non_env_types = ["u32", "bool", "String", "Address", "&u32", "soroban_sdk::Address", "Environment"];
245
+
246
+ for ty_str in non_env_types {
247
+ let ty = syn::parse_str::<syn::Type>(ty_str).expect("failed to parse type");
248
+ assert!(!crate::utils::is_env_type(&ty), "{ty_str} should NOT be recognized as an Env type");
249
+ }
250
+ }
251
+
252
+ #[test]
253
+ fn test_is_env_type_rejects_other_type_variants() {
254
+ let other_variants = [
255
+ ("(Env, u32)", "tuple"),
256
+ ("[Env; 1]", "array"),
257
+ ("[Env]", "slice"),
258
+ ("Option<Env>", "generic"),
259
+ ("!", "never"),
260
+ ("fn() -> Env", "fn pointer"),
261
+ ];
262
+
263
+ for (ty_str, label) in other_variants {
264
+ let ty = syn::parse_str::<syn::Type>(ty_str).expect("failed to parse type");
265
+ assert!(!crate::utils::is_env_type(&ty), "{label} type {ty_str} should NOT be recognized as an Env type");
266
+ }
267
+ }
@@ -19,8 +19,7 @@ pub fn generate_ttl_configurable_impl(input: TokenStream) -> TokenStream {
19
19
  let expanded = quote! {
20
20
  use utils::ttl::TtlConfigurable as _;
21
21
 
22
- // TODO: should use custom contract_impl macro instead of soroban_sdk::contractimpl
23
- #[soroban_sdk::contractimpl]
22
+ #[common_macros::contract_impl]
24
23
  impl utils::ttl::TtlConfigurable for #name {
25
24
 
26
25
  #[common_macros::only_owner]
@@ -53,17 +52,21 @@ pub fn generate_ttl_configurable_impl(input: TokenStream) -> TokenStream {
53
52
  };
54
53
 
55
54
  let inherent_impl = quote! {
55
+ /// Uses `soroban_sdk::contractimpl` directly instead of `common_macros::contract_impl`
56
+ /// because `contract_impl` automatically extends TTL on every invocation. Since this
57
+ /// impl block provides manual TTL extension control, auto-extension would be redundant
58
+ /// and could mask the intended behavior of `extend_instance_ttl`.
56
59
  #[soroban_sdk::contractimpl]
57
- impl #name {
58
- /// Extends the instance TTL.
59
- ///
60
- /// # Arguments
61
- ///
62
- /// * `threshold` - The threshold to extend the TTL.
63
- /// * `extend_to` - The TTL to extend to.
64
- pub fn extend_instance_ttl(env: &soroban_sdk::Env, threshold: u32, extend_to: u32) {
65
- env.storage().instance().extend_ttl(threshold, extend_to);
66
- }
60
+ impl #name {
61
+ /// Extends the instance TTL.
62
+ ///
63
+ /// # Arguments
64
+ ///
65
+ /// * `threshold` - The threshold to extend the TTL.
66
+ /// * `extend_to` - The TTL to extend to.
67
+ pub fn extend_instance_ttl(env: &soroban_sdk::Env, threshold: u32, extend_to: u32) {
68
+ env.storage().instance().extend_ttl(threshold, extend_to);
69
+ }
67
70
  }
68
71
  };
69
72
 
@@ -1,20 +1,44 @@
1
+ use proc_macro2::TokenStream;
2
+ use quote::quote;
1
3
  use syn::{punctuated::Punctuated, token::Comma, FnArg, Ident, Pat, Type, TypePath};
2
4
 
3
- /// Finds the `Env` argument identifier in a function signature.
4
- pub fn find_env_ident(args: &Punctuated<FnArg, Comma>) -> Option<&Ident> {
5
+ /// Information about an `Env` parameter in a function signature.
6
+ pub struct EnvParam<'a> {
7
+ /// The identifier of the Env parameter
8
+ pub ident: &'a Ident,
9
+ /// Whether the parameter is a reference type (`&Env` or `&mut Env`)
10
+ pub is_reference: bool,
11
+ }
12
+
13
+ impl EnvParam<'_> {
14
+ /// Returns a token stream that produces a `&Env` reference.
15
+ /// - If the parameter is already a reference (`&Env`), returns the ident as-is
16
+ /// - If the parameter is owned (`Env`), returns `&ident`
17
+ pub fn as_ref_tokens(&self) -> TokenStream {
18
+ let ident = self.ident;
19
+ if self.is_reference {
20
+ quote!(#ident)
21
+ } else {
22
+ quote!(&#ident)
23
+ }
24
+ }
25
+ }
26
+
27
+ /// Finds the `Env` argument in a function signature and returns its info.
28
+ pub fn find_env_param(args: &Punctuated<FnArg, Comma>) -> Option<EnvParam<'_>> {
5
29
  args.iter().find_map(|arg| {
6
30
  let FnArg::Typed(pat_type) = arg else { return None };
7
31
  if !is_env_type(&pat_type.ty) {
8
32
  return None;
9
33
  }
10
34
  let Pat::Ident(pat) = pat_type.pat.as_ref() else { return None };
11
- Some(&pat.ident)
35
+ Some(EnvParam { ident: &pat.ident, is_reference: is_reference_type(&pat_type.ty) })
12
36
  })
13
37
  }
14
38
 
15
- /// Expects the `Env` argument identifier in a function signature.
16
- pub fn expect_env_ident(args: &Punctuated<FnArg, Comma>) -> &Ident {
17
- find_env_ident(args).expect("function must have an Env argument")
39
+ /// Expects the `Env` argument in a function signature and returns its info.
40
+ pub fn expect_env_param(args: &Punctuated<FnArg, Comma>) -> EnvParam<'_> {
41
+ find_env_param(args).expect("function must have an Env argument")
18
42
  }
19
43
 
20
44
  /// Checks if a type is an `Env` type.
@@ -25,3 +49,8 @@ pub fn is_env_type(ty: &Type) -> bool {
25
49
  _ => false,
26
50
  }
27
51
  }
52
+
53
+ /// Checks if a type is a reference type.
54
+ fn is_reference_type(ty: &Type) -> bool {
55
+ matches!(ty, Type::Reference(_))
56
+ }
@@ -4,7 +4,7 @@ use super::*;
4
4
  // IReceiveUln302 Contract Implementation
5
5
  // ============================================================================================
6
6
 
7
- #[contractimpl(contracttrait)]
7
+ #[contract_impl(contracttrait)]
8
8
  impl IReceiveUln302 for Uln302 {
9
9
  /// Called by a DVN to verify a message with a specific number of block confirmations.
10
10
  ///