@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.
- package/Cargo.toml +21 -0
- package/LICENSE +23 -0
- package/clippy.toml +7 -0
- package/package.json +37 -0
- package/rust-toolchain.toml +4 -0
- package/rustfmt.toml +15 -0
- package/src/auth.rs +95 -0
- package/src/contract_ttl.rs +92 -0
- package/src/error.rs +43 -0
- package/src/lib.rs +585 -0
- package/src/lz_contract.rs +105 -0
- package/src/rbac.rs +90 -0
- package/src/storage.rs +522 -0
- package/src/tests/auth.rs +230 -0
- package/src/tests/contract_ttl.rs +695 -0
- package/src/tests/error.rs +156 -0
- package/src/tests/lz_contract.rs +87 -0
- package/src/tests/mod.rs +11 -0
- package/src/tests/rbac.rs +523 -0
- package/src/tests/snapshots/common_macros__tests__auth__snapshot_generated_multisig_code.snap +31 -0
- package/src/tests/snapshots/common_macros__tests__auth__snapshot_generated_ownable_code.snap +39 -0
- package/src/tests/snapshots/common_macros__tests__auth__snapshot_only_auth_preserves_function_signature.snap +19 -0
- package/src/tests/snapshots/common_macros__tests__contract_ttl__snapshot_generated_contractimpl_code.snap +77 -0
- package/src/tests/snapshots/common_macros__tests__contract_ttl__snapshot_generated_contracttrait_code.snap +46 -0
- package/src/tests/snapshots/common_macros__tests__error__snapshot_generated_contract_error_code.snap +20 -0
- package/src/tests/snapshots/common_macros__tests__lz_contract__snapshot_generated_lz_contract_code.snap +51 -0
- package/src/tests/snapshots/common_macros__tests__rbac__snapshot_authorizer_role.snap +21 -0
- package/src/tests/snapshots/common_macros__tests__rbac__snapshot_preserve_function_signature.snap +21 -0
- package/src/tests/snapshots/common_macros__tests__ttl_configurable__snapshot_generated_ttl_configurable_code.snap +10 -0
- package/src/tests/snapshots/common_macros__tests__ttl_extendable__snapshot_generated_ttl_extendable_code.snap +8 -0
- package/src/tests/snapshots/common_macros__tests__upgradeable__snapshot_generated_upgradeable_code.snap +28 -0
- package/src/tests/storage/extract_fields.rs +87 -0
- package/src/tests/storage/gen_accessor_methods.rs +223 -0
- package/src/tests/storage/gen_args.rs +65 -0
- package/src/tests/storage/gen_enum_variant.rs +78 -0
- package/src/tests/storage/gen_key.rs +108 -0
- package/src/tests/storage/gen_params.rs +105 -0
- package/src/tests/storage/generate_storage.rs +410 -0
- package/src/tests/storage/is_primitive_type.rs +48 -0
- package/src/tests/storage/mod.rs +16 -0
- package/src/tests/storage/parse_default.rs +164 -0
- package/src/tests/storage/parse_name.rs +158 -0
- package/src/tests/storage/parse_no_ttl_extension.rs +124 -0
- package/src/tests/storage/parse_storage_type.rs +174 -0
- package/src/tests/storage/snapshots/common_macros__tests__storage__generate_storage__snapshot_generated_storage_code.snap +412 -0
- package/src/tests/storage/storage_kind.rs +39 -0
- package/src/tests/storage/test_setup.rs +25 -0
- package/src/tests/storage/validate_attrs.rs +138 -0
- package/src/tests/storage/variant_config.rs +226 -0
- package/src/tests/test_helpers.rs +87 -0
- package/src/tests/ttl_configurable.rs +34 -0
- package/src/tests/ttl_extendable.rs +32 -0
- package/src/tests/upgradeable.rs +169 -0
- package/src/tests/utils.rs +267 -0
- package/src/ttl_configurable.rs +24 -0
- package/src/ttl_extendable.rs +28 -0
- package/src/upgradeable.rs +136 -0
- 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
|
+
}
|