@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,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
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//! TtlConfigurable macro for Stellar smart contracts.
|
|
2
|
+
|
|
3
|
+
use proc_macro2::TokenStream;
|
|
4
|
+
use quote::quote;
|
|
5
|
+
use syn::ItemStruct;
|
|
6
|
+
|
|
7
|
+
/// Generates the `TtlConfigurable` trait implementation for a contract.
|
|
8
|
+
///
|
|
9
|
+
/// This macro implements `TtlConfigurable` using the trait's default methods (which include auth).
|
|
10
|
+
///
|
|
11
|
+
/// The contract must also implement `Auth` (typically via `#[ownable]` or `#[multisig]`).
|
|
12
|
+
pub fn generate_ttl_configurable_impl(input: TokenStream) -> TokenStream {
|
|
13
|
+
let item_struct: ItemStruct = syn::parse2(input).unwrap_or_else(|e| panic!("failed to parse struct: {}", e));
|
|
14
|
+
let name = &item_struct.ident;
|
|
15
|
+
|
|
16
|
+
quote! {
|
|
17
|
+
#item_struct
|
|
18
|
+
|
|
19
|
+
use utils::ttl_configurable::TtlConfigurable as _;
|
|
20
|
+
|
|
21
|
+
#[common_macros::contract_impl(contracttrait)]
|
|
22
|
+
impl utils::ttl_configurable::TtlConfigurable for #name {}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//! TtlExtendable macro for Stellar smart contracts.
|
|
2
|
+
|
|
3
|
+
use proc_macro2::TokenStream;
|
|
4
|
+
use quote::quote;
|
|
5
|
+
use syn::ItemStruct;
|
|
6
|
+
|
|
7
|
+
/// Generates the TtlExtendable trait implementation from the `#[ttl_extendable]` attribute macro.
|
|
8
|
+
///
|
|
9
|
+
/// This macro implements the `TtlExtendable` trait for a contract struct,
|
|
10
|
+
/// providing a public `extend_instance_ttl` function for manual TTL extension.
|
|
11
|
+
///
|
|
12
|
+
/// Uses `soroban_sdk::contractimpl` directly instead of `common_macros::contract_impl`
|
|
13
|
+
/// because `contract_impl` automatically extends TTL on every invocation. Since this
|
|
14
|
+
/// impl block provides manual TTL extension control, auto-extension would be redundant
|
|
15
|
+
/// and could mask the intended behavior of `extend_instance_ttl`.
|
|
16
|
+
pub fn generate_ttl_extendable_impl(input: TokenStream) -> TokenStream {
|
|
17
|
+
let item_struct: ItemStruct = syn::parse2(input).unwrap_or_else(|e| panic!("failed to parse struct: {}", e));
|
|
18
|
+
let name = &item_struct.ident;
|
|
19
|
+
|
|
20
|
+
quote! {
|
|
21
|
+
#item_struct
|
|
22
|
+
|
|
23
|
+
use utils::ttl_extendable::TtlExtendable as _;
|
|
24
|
+
|
|
25
|
+
#[soroban_sdk::contractimpl(contracttrait)]
|
|
26
|
+
impl utils::ttl_extendable::TtlExtendable for #name {}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
//! Upgradeable macro for Stellar smart contracts.
|
|
2
|
+
|
|
3
|
+
use proc_macro2::TokenStream;
|
|
4
|
+
use quote::quote;
|
|
5
|
+
use syn::{
|
|
6
|
+
parse::{Parse, ParseStream},
|
|
7
|
+
Ident, ItemStruct, Token,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/// Configuration options for the `#[upgradeable]` macro.
|
|
11
|
+
#[derive(Debug, Default)]
|
|
12
|
+
pub struct UpgradeableConfig {
|
|
13
|
+
/// If true, generates a default no-op `UpgradeableInternal` implementation.
|
|
14
|
+
/// Use this for initial deployments when no migration logic is needed yet.
|
|
15
|
+
pub no_migration: bool,
|
|
16
|
+
/// If true, uses `UpgradeableRbac` (Auth + RoleBased) instead of `Upgradeable` (Auth only).
|
|
17
|
+
pub rbac: bool,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
impl Parse for UpgradeableConfig {
|
|
21
|
+
fn parse(input: ParseStream) -> syn::Result<Self> {
|
|
22
|
+
let mut config = Self::default();
|
|
23
|
+
if input.is_empty() {
|
|
24
|
+
return Ok(config);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
while !input.is_empty() {
|
|
28
|
+
let ident: Ident = input.parse()?;
|
|
29
|
+
match ident.to_string().as_str() {
|
|
30
|
+
"no_migration" => config.no_migration = true,
|
|
31
|
+
"rbac" => config.rbac = true,
|
|
32
|
+
_ => return Err(syn::Error::new(ident.span(), "expected `no_migration` or `rbac`")),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Consume optional trailing comma
|
|
36
|
+
if input.peek(Token![,]) {
|
|
37
|
+
input.parse::<Token![,]>()?;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
Ok(config)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Generates the upgradeable implementation from the `#[upgradeable]` attribute macro.
|
|
45
|
+
///
|
|
46
|
+
/// Generates an impl of `Upgradeable` or `UpgradeableRbac` for a contract type,
|
|
47
|
+
/// enabling upgrades by replacing WASM bytecode with migration support.
|
|
48
|
+
///
|
|
49
|
+
/// # Behavior
|
|
50
|
+
///
|
|
51
|
+
/// - By default implements `Upgradeable` (Auth-based, `#[only_auth]`). With `rbac`,
|
|
52
|
+
/// implements `UpgradeableRbac` (Auth + RoleBased, `UPGRADER_ROLE`).
|
|
53
|
+
/// - Sets the contract crate version as `"binver"` metadata using
|
|
54
|
+
/// `soroban_sdk::contractmeta!`. Uses `CARGO_PKG_VERSION` (from Cargo.toml
|
|
55
|
+
/// `[package]` version). Skips if missing or `"0.0.0"`.
|
|
56
|
+
/// - By default, requires the contract to implement `UpgradeableInternal`.
|
|
57
|
+
/// - With `no_migration`, generates a no-op `UpgradeableInternal` impl.
|
|
58
|
+
/// - With `rbac`, uses `UpgradeableRbac` (requires `RoleBasedAccessControl`, which
|
|
59
|
+
/// extends `Auth`) instead of `Upgradeable` (requires `Auth`).
|
|
60
|
+
///
|
|
61
|
+
/// See the `#[upgradeable]` macro documentation for full examples.
|
|
62
|
+
pub fn generate_upgradeable_impl(attr: TokenStream, input: TokenStream) -> TokenStream {
|
|
63
|
+
let config: UpgradeableConfig =
|
|
64
|
+
syn::parse2(attr).unwrap_or_else(|e| panic!("failed to parse upgradeable config: {}", e));
|
|
65
|
+
let item_struct: ItemStruct = syn::parse2(input).unwrap_or_else(|e| panic!("failed to parse struct: {}", e));
|
|
66
|
+
|
|
67
|
+
let name = &item_struct.ident;
|
|
68
|
+
let binver = set_binver_from_env();
|
|
69
|
+
|
|
70
|
+
// Generate default UpgradeableInternal impl only when no_migration is set
|
|
71
|
+
let default_internal_impl = if config.no_migration {
|
|
72
|
+
quote! {
|
|
73
|
+
impl utils::upgradeable::UpgradeableInternal for #name {
|
|
74
|
+
type MigrationData = ();
|
|
75
|
+
fn __migrate(_env: &soroban_sdk::Env, _migration_data: &Self::MigrationData) {}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
quote! {}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
let trait_path = if config.rbac {
|
|
83
|
+
quote! { utils::upgradeable::UpgradeableRbac }
|
|
84
|
+
} else {
|
|
85
|
+
quote! { utils::upgradeable::Upgradeable }
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
quote! {
|
|
89
|
+
#item_struct
|
|
90
|
+
|
|
91
|
+
use #trait_path as _;
|
|
92
|
+
|
|
93
|
+
#binver
|
|
94
|
+
|
|
95
|
+
#default_internal_impl
|
|
96
|
+
|
|
97
|
+
#[common_macros::contract_impl(contracttrait)]
|
|
98
|
+
impl #trait_path for #name {}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// Sets the value of the environment variable `CARGO_PKG_VERSION` as `binver`
|
|
103
|
+
/// in the wasm binary metadata. This env variable corresponds to the attribute
|
|
104
|
+
/// "version" in Cargo.toml. If the attribute is missing or if it is "0.0.0",
|
|
105
|
+
/// the function does nothing.
|
|
106
|
+
fn set_binver_from_env() -> TokenStream {
|
|
107
|
+
// However when "version" is missing from Cargo.toml,
|
|
108
|
+
// the following does not return error, but Ok("0.0.0")
|
|
109
|
+
let version = std::env::var("CARGO_PKG_VERSION");
|
|
110
|
+
|
|
111
|
+
match version {
|
|
112
|
+
Ok(v) if v != "0.0.0" => {
|
|
113
|
+
quote! { soroban_sdk::contractmeta!(key = "binver", val = #v); }
|
|
114
|
+
}
|
|
115
|
+
_ => quote! {},
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#[cfg(test)]
|
|
120
|
+
mod tests {
|
|
121
|
+
use std::env;
|
|
122
|
+
|
|
123
|
+
use super::*;
|
|
124
|
+
|
|
125
|
+
#[test]
|
|
126
|
+
fn test_set_binver_from_env_zero_version() {
|
|
127
|
+
// Set version to 0.0.0
|
|
128
|
+
env::set_var("CARGO_PKG_VERSION", "0.0.0");
|
|
129
|
+
|
|
130
|
+
let result = set_binver_from_env();
|
|
131
|
+
let result_str = result.to_string();
|
|
132
|
+
|
|
133
|
+
// Should return empty tokens
|
|
134
|
+
assert_eq!(result_str.trim(), "");
|
|
135
|
+
}
|
|
136
|
+
}
|
package/src/utils.rs
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
use proc_macro2::TokenStream;
|
|
2
|
+
use quote::quote;
|
|
3
|
+
use syn::{punctuated::Punctuated, token::Comma, FnArg, Ident, Pat, Type, TypePath};
|
|
4
|
+
|
|
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<'_>> {
|
|
29
|
+
args.iter().find_map(|arg| {
|
|
30
|
+
let FnArg::Typed(pat_type) = arg else { return None };
|
|
31
|
+
if !is_env_type(&pat_type.ty) {
|
|
32
|
+
return None;
|
|
33
|
+
}
|
|
34
|
+
let Pat::Ident(pat) = pat_type.pat.as_ref() else { return None };
|
|
35
|
+
Some(EnvParam { ident: &pat.ident, is_reference: is_reference_type(&pat_type.ty) })
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
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")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Checks if a type is an `Env` type.
|
|
45
|
+
pub fn is_env_type(ty: &Type) -> bool {
|
|
46
|
+
match ty {
|
|
47
|
+
Type::Path(TypePath { path, .. }) => path.segments.last().is_some_and(|seg| seg.ident == "Env"),
|
|
48
|
+
Type::Reference(r) => is_env_type(&r.elem),
|
|
49
|
+
_ => false,
|
|
50
|
+
}
|
|
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
|
+
}
|