@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,523 @@
|
|
|
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 Test: has_role and only_role
|
|
8
|
+
// ============================================
|
|
9
|
+
|
|
10
|
+
#[test]
|
|
11
|
+
fn snapshot_preserve_function_signature() {
|
|
12
|
+
let args = quote! { caller, "minter" };
|
|
13
|
+
let input = quote! {
|
|
14
|
+
pub fn mint(env: Env, caller: Address, amount: i128) {
|
|
15
|
+
// mint logic
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
let has_role_result = crate::rbac::generate_role_check(args.clone(), input.clone(), false);
|
|
20
|
+
let has_role_formatted =
|
|
21
|
+
prettyplease::unparse(&syn::parse2::<syn::File>(has_role_result).expect("failed to parse generated code"));
|
|
22
|
+
|
|
23
|
+
let only_role_result = crate::rbac::generate_role_check(args, input, true);
|
|
24
|
+
let only_role_formatted =
|
|
25
|
+
prettyplease::unparse(&syn::parse2::<syn::File>(only_role_result).expect("failed to parse generated code"));
|
|
26
|
+
|
|
27
|
+
let combined =
|
|
28
|
+
format!("// === has_role ===\n\n{}\n\n// === only_role ===\n\n{}", has_role_formatted, only_role_formatted);
|
|
29
|
+
|
|
30
|
+
insta::assert_snapshot!(combined);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ============================================
|
|
34
|
+
// generate_role_check assertion tests
|
|
35
|
+
// ============================================
|
|
36
|
+
|
|
37
|
+
fn assert_stmt_eq(stmt: &syn::Stmt, expected: &str, test_name: &str) {
|
|
38
|
+
let actual = quote::quote!(#stmt).to_string().replace(" ", "");
|
|
39
|
+
assert_eq!(actual, expected, "{test_name}: expected '{expected}', got '{actual}'");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fn assert_role_check_exact_stmts(
|
|
43
|
+
args: TokenStream,
|
|
44
|
+
input: TokenStream,
|
|
45
|
+
require_auth: bool,
|
|
46
|
+
expected_ensure_stmt_no_spaces: &str,
|
|
47
|
+
expected_auth_stmt_no_spaces: Option<&str>,
|
|
48
|
+
expected_stmt_count: Option<usize>,
|
|
49
|
+
test_name: &str,
|
|
50
|
+
) -> syn::ItemFn {
|
|
51
|
+
let result_tokens = crate::rbac::generate_role_check(args, input, require_auth);
|
|
52
|
+
let output_fn: syn::ItemFn =
|
|
53
|
+
syn::parse2(result_tokens).unwrap_or_else(|e| panic!("{test_name}: failed to parse output function: {e}"));
|
|
54
|
+
|
|
55
|
+
assert!(!output_fn.block.stmts.is_empty(), "{test_name}: function body should contain at least one statement");
|
|
56
|
+
|
|
57
|
+
assert_stmt_eq(&output_fn.block.stmts[0], expected_ensure_stmt_no_spaces, test_name);
|
|
58
|
+
|
|
59
|
+
if let Some(expected_auth) = expected_auth_stmt_no_spaces {
|
|
60
|
+
assert!(output_fn.block.stmts.len() >= 2, "{test_name}: expected at least two statements");
|
|
61
|
+
assert_stmt_eq(&output_fn.block.stmts[1], expected_auth, test_name);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if let Some(expected_count) = expected_stmt_count {
|
|
65
|
+
assert_eq!(
|
|
66
|
+
output_fn.block.stmts.len(),
|
|
67
|
+
expected_count,
|
|
68
|
+
"{test_name}: expected {expected_count} statements, got {}",
|
|
69
|
+
output_fn.block.stmts.len()
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
output_fn
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#[test]
|
|
77
|
+
fn test_role_check_inserts_expected_statements_table_driven() {
|
|
78
|
+
struct Case {
|
|
79
|
+
name: &'static str,
|
|
80
|
+
args: TokenStream,
|
|
81
|
+
input: TokenStream,
|
|
82
|
+
require_auth: bool,
|
|
83
|
+
expected_ensure_stmt: &'static str,
|
|
84
|
+
expected_auth_stmt: Option<&'static str>,
|
|
85
|
+
expected_stmt_count: usize,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let cases = vec![
|
|
89
|
+
Case {
|
|
90
|
+
name: "has_role: Env ref + Address value",
|
|
91
|
+
args: quote! { caller, "minter" },
|
|
92
|
+
input: quote! { pub fn mint(env: &Env, caller: Address, amount: i128) {} },
|
|
93
|
+
require_auth: false,
|
|
94
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(env,&soroban_sdk::Symbol::new(env,\"minter\"),&caller);",
|
|
95
|
+
expected_auth_stmt: None,
|
|
96
|
+
expected_stmt_count: 1,
|
|
97
|
+
},
|
|
98
|
+
Case {
|
|
99
|
+
name: "only_role: Env owned + Address value",
|
|
100
|
+
args: quote! { caller, "minter" },
|
|
101
|
+
input: quote! { pub fn mint(env: Env, caller: Address, amount: i128) {} },
|
|
102
|
+
require_auth: true,
|
|
103
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(&env,&soroban_sdk::Symbol::new(&env,\"minter\"),&caller);",
|
|
104
|
+
expected_auth_stmt: Some("caller.require_auth();"),
|
|
105
|
+
expected_stmt_count: 2,
|
|
106
|
+
},
|
|
107
|
+
Case {
|
|
108
|
+
name: "only_role: Env ref + Address value",
|
|
109
|
+
args: quote! { caller, "minter" },
|
|
110
|
+
input: quote! { pub fn mint(env: &Env, caller: Address, amount: i128) {} },
|
|
111
|
+
require_auth: true,
|
|
112
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(env,&soroban_sdk::Symbol::new(env,\"minter\"),&caller);",
|
|
113
|
+
expected_auth_stmt: Some("caller.require_auth();"),
|
|
114
|
+
expected_stmt_count: 2,
|
|
115
|
+
},
|
|
116
|
+
Case {
|
|
117
|
+
name: "has_role: &Address param uses account directly",
|
|
118
|
+
args: quote! { account, "admin" },
|
|
119
|
+
input: quote! { pub fn admin_action(env: Env, account: &Address) {} },
|
|
120
|
+
require_auth: false,
|
|
121
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(&env,&soroban_sdk::Symbol::new(&env,\"admin\"),account);",
|
|
122
|
+
expected_auth_stmt: None,
|
|
123
|
+
expected_stmt_count: 1,
|
|
124
|
+
},
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
for c in cases {
|
|
128
|
+
assert_role_check_exact_stmts(
|
|
129
|
+
c.args,
|
|
130
|
+
c.input,
|
|
131
|
+
c.require_auth,
|
|
132
|
+
c.expected_ensure_stmt,
|
|
133
|
+
c.expected_auth_stmt,
|
|
134
|
+
Some(c.expected_stmt_count),
|
|
135
|
+
c.name,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#[test]
|
|
141
|
+
fn test_has_role_role_arg_variants_generate_expected_symbol_new_table_driven() {
|
|
142
|
+
struct Case {
|
|
143
|
+
name: &'static str,
|
|
144
|
+
args: TokenStream,
|
|
145
|
+
input: TokenStream,
|
|
146
|
+
expected_ensure_stmt: &'static str,
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let cases = vec![
|
|
150
|
+
Case {
|
|
151
|
+
name: "role string literal passed to Symbol::new (Env ref)",
|
|
152
|
+
args: quote! { caller, "minter" },
|
|
153
|
+
input: quote! { pub fn mint(env: &Env, caller: Address) {} },
|
|
154
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(env,&soroban_sdk::Symbol::new(env,\"minter\"),&caller);",
|
|
155
|
+
},
|
|
156
|
+
Case {
|
|
157
|
+
name: "role const expr passed to Symbol::new (Env ref)",
|
|
158
|
+
args: quote! { caller, MINTER_ROLE },
|
|
159
|
+
input: quote! { pub fn mint(env: &Env, caller: Address) {} },
|
|
160
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(env,&soroban_sdk::Symbol::new(env,MINTER_ROLE),&caller);",
|
|
161
|
+
},
|
|
162
|
+
Case {
|
|
163
|
+
name: "role const expr passed to Symbol::new (Env owned)",
|
|
164
|
+
args: quote! { caller, MINTER_ROLE },
|
|
165
|
+
input: quote! { pub fn mint(env: Env, caller: Address) {} },
|
|
166
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(&env,&soroban_sdk::Symbol::new(&env,MINTER_ROLE),&caller);",
|
|
167
|
+
},
|
|
168
|
+
Case {
|
|
169
|
+
name: "role path expr passed to Symbol::new",
|
|
170
|
+
args: quote! { caller, roles::MINTER_ROLE },
|
|
171
|
+
input: quote! { pub fn mint(env: &Env, caller: Address) {} },
|
|
172
|
+
expected_ensure_stmt:
|
|
173
|
+
"utils::rbac::ensure_role::<Self>(env,&soroban_sdk::Symbol::new(env,roles::MINTER_ROLE),&caller);",
|
|
174
|
+
},
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
for c in cases {
|
|
178
|
+
assert_role_check_exact_stmts(c.args, c.input, false, c.expected_ensure_stmt, None, Some(1), c.name);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ============================================
|
|
183
|
+
// Error cases: invalid args (table-driven)
|
|
184
|
+
// ============================================
|
|
185
|
+
|
|
186
|
+
#[test]
|
|
187
|
+
fn test_has_role_rejects_invalid_args_table_driven() {
|
|
188
|
+
struct Case {
|
|
189
|
+
name: &'static str,
|
|
190
|
+
args: TokenStream,
|
|
191
|
+
expected_substring: &'static str,
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let input = quote! { pub fn mint(env: Env, caller: Address) {} };
|
|
195
|
+
let cases = vec![
|
|
196
|
+
Case {
|
|
197
|
+
name: "missing comma in args",
|
|
198
|
+
args: quote! { caller "minter" },
|
|
199
|
+
expected_substring: "failed to parse has_role/only_role args",
|
|
200
|
+
},
|
|
201
|
+
Case {
|
|
202
|
+
name: "missing role",
|
|
203
|
+
args: quote! { caller, },
|
|
204
|
+
expected_substring: "failed to parse has_role/only_role args",
|
|
205
|
+
},
|
|
206
|
+
Case {
|
|
207
|
+
name: "extra tokens in args",
|
|
208
|
+
args: quote! { caller, "minter", extra },
|
|
209
|
+
expected_substring: "failed to parse has_role/only_role args",
|
|
210
|
+
},
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
for c in cases {
|
|
214
|
+
assert_panics_contains(c.name, c.expected_substring, || {
|
|
215
|
+
crate::rbac::generate_role_check(c.args.clone(), input.clone(), false);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ============================================
|
|
221
|
+
// Error cases: invalid function signature inputs (table-driven)
|
|
222
|
+
// ============================================
|
|
223
|
+
|
|
224
|
+
#[test]
|
|
225
|
+
fn test_has_role_rejects_invalid_function_signature_table_driven() {
|
|
226
|
+
struct Case {
|
|
227
|
+
name: &'static str,
|
|
228
|
+
args: TokenStream,
|
|
229
|
+
input: TokenStream,
|
|
230
|
+
expected_substring: &'static str,
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
let args = quote! { caller, "minter" };
|
|
234
|
+
let cases = vec![
|
|
235
|
+
Case {
|
|
236
|
+
name: "no Env param",
|
|
237
|
+
args: args.clone(),
|
|
238
|
+
input: quote! { pub fn mint(caller: Address, amount: i128) {} },
|
|
239
|
+
expected_substring: "function must have an Env argument",
|
|
240
|
+
},
|
|
241
|
+
Case {
|
|
242
|
+
name: "param not in signature",
|
|
243
|
+
args: args.clone(),
|
|
244
|
+
input: quote! { pub fn mint(env: Env, account: Address, amount: i128) {} },
|
|
245
|
+
expected_substring: "not found in function signature",
|
|
246
|
+
},
|
|
247
|
+
Case {
|
|
248
|
+
name: "param not Address",
|
|
249
|
+
args: args.clone(),
|
|
250
|
+
input: quote! { pub fn mint(env: Env, caller: u32, amount: i128) {} },
|
|
251
|
+
expected_substring: "must be of type `Address` or `&Address`",
|
|
252
|
+
},
|
|
253
|
+
Case {
|
|
254
|
+
name: "wildcard Env pattern",
|
|
255
|
+
args: args.clone(),
|
|
256
|
+
input: quote! { pub fn mint(_: Env, caller: Address) {} },
|
|
257
|
+
expected_substring: "function must have an Env argument",
|
|
258
|
+
},
|
|
259
|
+
Case {
|
|
260
|
+
name: "tuple Env pattern",
|
|
261
|
+
args: args.clone(),
|
|
262
|
+
input: quote! { pub fn mint((env, _): (&Env, u32), caller: Address) { let _ = env; } },
|
|
263
|
+
expected_substring: "function must have an Env argument",
|
|
264
|
+
},
|
|
265
|
+
Case {
|
|
266
|
+
name: "struct Env pattern",
|
|
267
|
+
args: args.clone(),
|
|
268
|
+
input: quote! { pub fn mint(Env { .. }: Env, caller: Address) {} },
|
|
269
|
+
expected_substring: "function must have an Env argument",
|
|
270
|
+
},
|
|
271
|
+
Case {
|
|
272
|
+
name: "&&Address is invalid",
|
|
273
|
+
args: args.clone(),
|
|
274
|
+
input: quote! { pub fn mint(env: &Env, caller: &&Address) {} },
|
|
275
|
+
expected_substring: "must be of type `Address` or `&Address`",
|
|
276
|
+
},
|
|
277
|
+
];
|
|
278
|
+
|
|
279
|
+
for c in cases {
|
|
280
|
+
assert_panics_contains(c.name, c.expected_substring, || {
|
|
281
|
+
crate::rbac::generate_role_check(c.args.clone(), c.input.clone(), false);
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ============================================
|
|
287
|
+
// Error cases: non-function input
|
|
288
|
+
// ============================================
|
|
289
|
+
|
|
290
|
+
#[test]
|
|
291
|
+
fn test_has_role_rejects_non_function_inputs() {
|
|
292
|
+
let args = quote! { caller, "minter" };
|
|
293
|
+
for (case, input) in filter_item_inputs_excluding_labels(&["function"]) {
|
|
294
|
+
assert_panics_contains(case, "failed to parse function", || {
|
|
295
|
+
crate::rbac::generate_role_check(args.clone(), input.clone(), false);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ============================================
|
|
301
|
+
// High-value coverage: Env + Address + role variants (table-driven)
|
|
302
|
+
// ============================================
|
|
303
|
+
|
|
304
|
+
#[test]
|
|
305
|
+
fn test_has_role_env_variants_generate_correct_env_ref_in_ensure_role() {
|
|
306
|
+
struct Case {
|
|
307
|
+
name: &'static str,
|
|
308
|
+
input: TokenStream,
|
|
309
|
+
expected_ensure_stmt: &'static str,
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Role is a literal so we also validate Symbol::new uses the same env_ref form.
|
|
313
|
+
let args = quote! { caller, "minter" };
|
|
314
|
+
|
|
315
|
+
let cases = vec![
|
|
316
|
+
Case {
|
|
317
|
+
name: "owned Env",
|
|
318
|
+
input: quote! { pub fn mint(env: Env, caller: Address) {} },
|
|
319
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(&env,&soroban_sdk::Symbol::new(&env,\"minter\"),&caller);",
|
|
320
|
+
},
|
|
321
|
+
Case {
|
|
322
|
+
name: "ref Env",
|
|
323
|
+
input: quote! { pub fn mint(env: &Env, caller: Address) {} },
|
|
324
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(env,&soroban_sdk::Symbol::new(env,\"minter\"),&caller);",
|
|
325
|
+
},
|
|
326
|
+
Case {
|
|
327
|
+
name: "mut ref Env",
|
|
328
|
+
input: quote! { pub fn mint(env: &mut Env, caller: Address) {} },
|
|
329
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(env,&soroban_sdk::Symbol::new(env,\"minter\"),&caller);",
|
|
330
|
+
},
|
|
331
|
+
Case {
|
|
332
|
+
name: "qualified owned Env",
|
|
333
|
+
input: quote! { pub fn mint(env: soroban_sdk::Env, caller: Address) {} },
|
|
334
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(&env,&soroban_sdk::Symbol::new(&env,\"minter\"),&caller);",
|
|
335
|
+
},
|
|
336
|
+
Case {
|
|
337
|
+
name: "qualified ref Env",
|
|
338
|
+
input: quote! { pub fn mint(env: &soroban_sdk::Env, caller: Address) {} },
|
|
339
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(env,&soroban_sdk::Symbol::new(env,\"minter\"),&caller);",
|
|
340
|
+
},
|
|
341
|
+
Case {
|
|
342
|
+
name: "leading :: qualified owned Env",
|
|
343
|
+
input: quote! { pub fn mint(env: ::soroban_sdk::Env, caller: Address) {} },
|
|
344
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(&env,&soroban_sdk::Symbol::new(&env,\"minter\"),&caller);",
|
|
345
|
+
},
|
|
346
|
+
];
|
|
347
|
+
|
|
348
|
+
for c in cases {
|
|
349
|
+
assert_role_check_exact_stmts(args.clone(), c.input, false, c.expected_ensure_stmt, None, Some(1), c.name);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
#[test]
|
|
354
|
+
fn test_has_role_address_variants_generate_correct_param_reference() {
|
|
355
|
+
struct Case {
|
|
356
|
+
name: &'static str,
|
|
357
|
+
input: TokenStream,
|
|
358
|
+
expected_ensure_stmt: &'static str,
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
let args = quote! { caller, "minter" };
|
|
362
|
+
|
|
363
|
+
let cases = vec![
|
|
364
|
+
Case {
|
|
365
|
+
name: "Address by value -> &caller",
|
|
366
|
+
input: quote! { pub fn mint(env: &Env, caller: Address) {} },
|
|
367
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(env,&soroban_sdk::Symbol::new(env,\"minter\"),&caller);",
|
|
368
|
+
},
|
|
369
|
+
Case {
|
|
370
|
+
name: "&Address -> caller",
|
|
371
|
+
input: quote! { pub fn mint(env: &Env, caller: &Address) {} },
|
|
372
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(env,&soroban_sdk::Symbol::new(env,\"minter\"),caller);",
|
|
373
|
+
},
|
|
374
|
+
Case {
|
|
375
|
+
name: "&mut Address -> caller",
|
|
376
|
+
input: quote! { pub fn mint(env: &Env, caller: &mut Address) {} },
|
|
377
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(env,&soroban_sdk::Symbol::new(env,\"minter\"),caller);",
|
|
378
|
+
},
|
|
379
|
+
Case {
|
|
380
|
+
name: "qualified Address by value",
|
|
381
|
+
input: quote! { pub fn mint(env: &Env, caller: soroban_sdk::Address) {} },
|
|
382
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(env,&soroban_sdk::Symbol::new(env,\"minter\"),&caller);",
|
|
383
|
+
},
|
|
384
|
+
Case {
|
|
385
|
+
name: "qualified &Address",
|
|
386
|
+
input: quote! { pub fn mint(env: &Env, caller: &soroban_sdk::Address) {} },
|
|
387
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(env,&soroban_sdk::Symbol::new(env,\"minter\"),caller);",
|
|
388
|
+
},
|
|
389
|
+
];
|
|
390
|
+
|
|
391
|
+
for c in cases {
|
|
392
|
+
assert_role_check_exact_stmts(args.clone(), c.input, false, c.expected_ensure_stmt, None, Some(1), c.name);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
#[test]
|
|
397
|
+
fn test_only_role_inserts_expected_statements_and_preserves_original_body() {
|
|
398
|
+
let args = quote! { caller, "minter" };
|
|
399
|
+
let input = quote! {
|
|
400
|
+
pub fn mint(env: Env, caller: Address) {
|
|
401
|
+
let x = 1u32;
|
|
402
|
+
let _ = x + 1;
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
let output_fn = assert_role_check_exact_stmts(
|
|
407
|
+
args,
|
|
408
|
+
input,
|
|
409
|
+
true,
|
|
410
|
+
"utils::rbac::ensure_role::<Self>(&env,&soroban_sdk::Symbol::new(&env,\"minter\"),&caller);",
|
|
411
|
+
Some("caller.require_auth();"),
|
|
412
|
+
Some(4),
|
|
413
|
+
"only_role inserts ensure_role + require_auth",
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
// Ensure original statements remain after the inserted checks.
|
|
417
|
+
let third_stmt = &output_fn.block.stmts[2];
|
|
418
|
+
let third_stmt_str = quote::quote!(#third_stmt).to_string().replace(" ", "");
|
|
419
|
+
assert!(third_stmt_str.starts_with("letx=1u32;"), "expected original 'let x = 1u32;' to be preserved");
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// ============================================
|
|
423
|
+
// AUTHORIZER: snapshot test
|
|
424
|
+
// ============================================
|
|
425
|
+
|
|
426
|
+
#[test]
|
|
427
|
+
fn snapshot_authorizer_role() {
|
|
428
|
+
let args = quote! { operator, AUTHORIZER };
|
|
429
|
+
let input = quote! {
|
|
430
|
+
pub fn admin_action(env: Env, operator: Address) {
|
|
431
|
+
// admin logic
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
let has_role_result = crate::rbac::generate_role_check(args.clone(), input.clone(), false);
|
|
436
|
+
let has_role_formatted =
|
|
437
|
+
prettyplease::unparse(&syn::parse2::<syn::File>(has_role_result).expect("failed to parse generated code"));
|
|
438
|
+
|
|
439
|
+
let only_role_result = crate::rbac::generate_role_check(args, input, true);
|
|
440
|
+
let only_role_formatted =
|
|
441
|
+
prettyplease::unparse(&syn::parse2::<syn::File>(only_role_result).expect("failed to parse generated code"));
|
|
442
|
+
|
|
443
|
+
let combined = format!(
|
|
444
|
+
"// === has_role(operator, AUTHORIZER) ===\n\n{}\n\n// === only_role(operator, AUTHORIZER) ===\n\n{}",
|
|
445
|
+
has_role_formatted, only_role_formatted
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
insta::assert_snapshot!(combined);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// ============================================
|
|
452
|
+
// AUTHORIZER: table-driven assertion tests
|
|
453
|
+
// ============================================
|
|
454
|
+
|
|
455
|
+
#[test]
|
|
456
|
+
fn test_authorizer_role_generates_auth_check_instead_of_ensure_role() {
|
|
457
|
+
struct Case {
|
|
458
|
+
name: &'static str,
|
|
459
|
+
args: TokenStream,
|
|
460
|
+
input: TokenStream,
|
|
461
|
+
require_auth: bool,
|
|
462
|
+
expected_ensure_stmt: &'static str,
|
|
463
|
+
expected_auth_stmt: Option<&'static str>,
|
|
464
|
+
expected_stmt_count: usize,
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
let cases = vec![
|
|
468
|
+
Case {
|
|
469
|
+
name: "has_role with AUTHORIZER: Env ref + Address value",
|
|
470
|
+
args: quote! { operator, AUTHORIZER },
|
|
471
|
+
input: quote! { pub fn admin_action(env: &Env, operator: Address) {} },
|
|
472
|
+
require_auth: false,
|
|
473
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(env,&soroban_sdk::Symbol::new(env,AUTHORIZER),&operator);",
|
|
474
|
+
expected_auth_stmt: None,
|
|
475
|
+
expected_stmt_count: 1,
|
|
476
|
+
},
|
|
477
|
+
Case {
|
|
478
|
+
name: "only_role with AUTHORIZER: Env owned + Address value",
|
|
479
|
+
args: quote! { operator, AUTHORIZER },
|
|
480
|
+
input: quote! { pub fn admin_action(env: Env, operator: Address) {} },
|
|
481
|
+
require_auth: true,
|
|
482
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(&env,&soroban_sdk::Symbol::new(&env,AUTHORIZER),&operator);",
|
|
483
|
+
expected_auth_stmt: Some("operator.require_auth();"),
|
|
484
|
+
expected_stmt_count: 2,
|
|
485
|
+
},
|
|
486
|
+
Case {
|
|
487
|
+
name: "only_role with AUTHORIZER: Env ref + &Address",
|
|
488
|
+
args: quote! { operator, AUTHORIZER },
|
|
489
|
+
input: quote! { pub fn admin_action(env: &Env, operator: &Address) {} },
|
|
490
|
+
require_auth: true,
|
|
491
|
+
expected_ensure_stmt: "utils::rbac::ensure_role::<Self>(env,&soroban_sdk::Symbol::new(env,AUTHORIZER),operator);",
|
|
492
|
+
expected_auth_stmt: Some("operator.require_auth();"),
|
|
493
|
+
expected_stmt_count: 2,
|
|
494
|
+
},
|
|
495
|
+
];
|
|
496
|
+
|
|
497
|
+
for c in cases {
|
|
498
|
+
assert_role_check_exact_stmts(
|
|
499
|
+
c.args,
|
|
500
|
+
c.input,
|
|
501
|
+
c.require_auth,
|
|
502
|
+
c.expected_ensure_stmt,
|
|
503
|
+
c.expected_auth_stmt,
|
|
504
|
+
Some(c.expected_stmt_count),
|
|
505
|
+
c.name,
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
#[test]
|
|
511
|
+
fn test_non_authorizer_role_still_uses_ensure_role() {
|
|
512
|
+
let args = quote! { caller, MINTER_ROLE };
|
|
513
|
+
let input = quote! { pub fn mint(env: &Env, caller: Address) {} };
|
|
514
|
+
assert_role_check_exact_stmts(
|
|
515
|
+
args,
|
|
516
|
+
input,
|
|
517
|
+
false,
|
|
518
|
+
"utils::rbac::ensure_role::<Self>(env,&soroban_sdk::Symbol::new(env,MINTER_ROLE),&caller);",
|
|
519
|
+
None,
|
|
520
|
+
Some(1),
|
|
521
|
+
"non-AUTHORIZER const still goes through ensure_role",
|
|
522
|
+
);
|
|
523
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
source: contracts/common-macros/src/tests/auth.rs
|
|
3
|
+
expression: combined
|
|
4
|
+
---
|
|
5
|
+
// === Unit struct ===
|
|
6
|
+
|
|
7
|
+
pub struct MyContract;
|
|
8
|
+
use utils::{auth::Auth as _, multisig::MultiSig as _};
|
|
9
|
+
#[common_macros::contract_impl]
|
|
10
|
+
impl utils::auth::Auth for MyContract {
|
|
11
|
+
fn authorizer(env: &soroban_sdk::Env) -> Option<soroban_sdk::Address> {
|
|
12
|
+
Some(env.current_contract_address())
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
#[common_macros::contract_impl(contracttrait)]
|
|
16
|
+
impl utils::multisig::MultiSig for MyContract {}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
// === Tuple struct ===
|
|
20
|
+
|
|
21
|
+
#[derive(Copy, Clone)]
|
|
22
|
+
pub struct MyContract(pub u32);
|
|
23
|
+
use utils::{auth::Auth as _, multisig::MultiSig as _};
|
|
24
|
+
#[common_macros::contract_impl]
|
|
25
|
+
impl utils::auth::Auth for MyContract {
|
|
26
|
+
fn authorizer(env: &soroban_sdk::Env) -> Option<soroban_sdk::Address> {
|
|
27
|
+
Some(env.current_contract_address())
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
#[common_macros::contract_impl(contracttrait)]
|
|
31
|
+
impl utils::multisig::MultiSig for MyContract {}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
source: contracts/common-macros/src/tests/auth.rs
|
|
3
|
+
assertion_line: 36
|
|
4
|
+
expression: combined
|
|
5
|
+
---
|
|
6
|
+
// === Unit struct ===
|
|
7
|
+
|
|
8
|
+
pub struct MyContract;
|
|
9
|
+
use utils::{auth::Auth as _, ownable::{Ownable as _, OwnableInitializer as _}};
|
|
10
|
+
impl utils::ownable::OwnableInitializer for MyContract {}
|
|
11
|
+
#[common_macros::contract_impl]
|
|
12
|
+
impl utils::auth::Auth for MyContract {
|
|
13
|
+
fn authorizer(env: &soroban_sdk::Env) -> Option<soroban_sdk::Address> {
|
|
14
|
+
<Self as utils::ownable::Ownable>::owner(env)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
#[common_macros::contract_impl(contracttrait)]
|
|
18
|
+
impl utils::ownable::Ownable for MyContract {}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
// === Generics + fields ===
|
|
22
|
+
|
|
23
|
+
#[derive(Clone, Debug)]
|
|
24
|
+
pub struct MyContract<T>
|
|
25
|
+
where
|
|
26
|
+
T: Clone,
|
|
27
|
+
{
|
|
28
|
+
pub value: T,
|
|
29
|
+
}
|
|
30
|
+
use utils::{auth::Auth as _, ownable::{Ownable as _, OwnableInitializer as _}};
|
|
31
|
+
impl utils::ownable::OwnableInitializer for MyContract {}
|
|
32
|
+
#[common_macros::contract_impl]
|
|
33
|
+
impl utils::auth::Auth for MyContract {
|
|
34
|
+
fn authorizer(env: &soroban_sdk::Env) -> Option<soroban_sdk::Address> {
|
|
35
|
+
<Self as utils::ownable::Ownable>::owner(env)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
#[common_macros::contract_impl(contracttrait)]
|
|
39
|
+
impl utils::ownable::Ownable for MyContract {}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
source: contracts/common-macros/src/tests/auth.rs
|
|
3
|
+
assertion_line: 67
|
|
4
|
+
expression: combined
|
|
5
|
+
---
|
|
6
|
+
// === Borrowed Env (&Env) ===
|
|
7
|
+
|
|
8
|
+
pub(crate) async fn admin_action<T: Clone>(env: &Env, value: T) -> Result<T, Error> {
|
|
9
|
+
utils::auth::require_auth::<Self>(env);
|
|
10
|
+
Ok(value.clone())
|
|
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::auth::require_auth::<Self>(&env);
|
|
18
|
+
Ok(value.clone())
|
|
19
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
source: contracts/common-macros/src/tests/contract_ttl.rs
|
|
3
|
+
expression: combined
|
|
4
|
+
---
|
|
5
|
+
// === Inherent Impl (no attr) ===
|
|
6
|
+
|
|
7
|
+
#[soroban_sdk::contractimpl]
|
|
8
|
+
impl MyContract {
|
|
9
|
+
const A_CONST: u32 = 1;
|
|
10
|
+
type Alias = u32;
|
|
11
|
+
/// Public method with Env - should have TTL extension
|
|
12
|
+
pub fn public_with_env(env: Env, value: u32) -> u32 {
|
|
13
|
+
utils::ttl_configurable::extend_instance_ttl(&env);
|
|
14
|
+
value * 2
|
|
15
|
+
}
|
|
16
|
+
/// Public method with qualified Env path - should have TTL extension
|
|
17
|
+
pub fn with_qualified_env(env: soroban_sdk::Env) -> u32 {
|
|
18
|
+
utils::ttl_configurable::extend_instance_ttl(&env);
|
|
19
|
+
42
|
|
20
|
+
}
|
|
21
|
+
/// Public method with Env not as first parameter - should have TTL extension
|
|
22
|
+
pub fn env_second(value: u32, env: &Env) -> u32 {
|
|
23
|
+
utils::ttl_configurable::extend_instance_ttl(env);
|
|
24
|
+
value * 2
|
|
25
|
+
}
|
|
26
|
+
/// Public method without Env - should NOT have TTL extension
|
|
27
|
+
pub fn public_without_env(value: u32) -> u32 {
|
|
28
|
+
value * 4
|
|
29
|
+
}
|
|
30
|
+
/// Private method with Env - should NOT have TTL extension (not public)
|
|
31
|
+
fn private_with_env(env: Env, value: u32) -> u32 {
|
|
32
|
+
value * 5
|
|
33
|
+
}
|
|
34
|
+
/// Pub(crate) method with Env - should NOT have TTL extension (not fully public)
|
|
35
|
+
pub(crate) fn pub_crate_with_env(env: Env, value: u32) -> u32 {
|
|
36
|
+
value * 7
|
|
37
|
+
}
|
|
38
|
+
/// Constructor method - should have init_default_ttl_configs
|
|
39
|
+
pub fn __constructor(env: &Env, value: u32) {
|
|
40
|
+
utils::ttl_configurable::init_default_ttl_configs(env);
|
|
41
|
+
let _ = value * 2;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
// === Trait Impl (no attr) ===
|
|
47
|
+
|
|
48
|
+
#[soroban_sdk::contractimpl]
|
|
49
|
+
impl SomeTrait for MyContract {
|
|
50
|
+
/// Trait method with Env - should have TTL extension
|
|
51
|
+
fn trait_method_with_env(env: Env, value: u32) -> u32 {
|
|
52
|
+
utils::ttl_configurable::extend_instance_ttl(&env);
|
|
53
|
+
value * 2
|
|
54
|
+
}
|
|
55
|
+
/// Trait method without Env - should NOT have TTL extension
|
|
56
|
+
fn trait_method_without_env(value: u32) -> u32 {
|
|
57
|
+
value * 4
|
|
58
|
+
}
|
|
59
|
+
/// Trait method with macro attribute - should have TTL extension
|
|
60
|
+
#[common_macros::only_auth]
|
|
61
|
+
fn trait_method_with_only_auth_attribute(env: Env, value: u32) -> u32 {
|
|
62
|
+
utils::ttl_configurable::extend_instance_ttl(&env);
|
|
63
|
+
value * 5
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
// === Trait Impl (contracttrait attr) ===
|
|
69
|
+
|
|
70
|
+
#[soroban_sdk::contractimpl(contracttrait)]
|
|
71
|
+
impl AnotherTrait for MyContract {
|
|
72
|
+
/// Trait method with contracttrait attr - should have TTL extension
|
|
73
|
+
fn contracttrait_method(env: &Env, value: u32) -> u32 {
|
|
74
|
+
utils::ttl_configurable::extend_instance_ttl(env);
|
|
75
|
+
value * 3
|
|
76
|
+
}
|
|
77
|
+
}
|