@layerzerolabs/common-utils-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 +28 -0
- package/LICENSE +23 -0
- package/clippy.toml +7 -0
- package/package.json +43 -0
- package/rust-toolchain.toml +4 -0
- package/rustfmt.toml +15 -0
- package/src/auth.rs +48 -0
- package/src/buffer_reader.rs +194 -0
- package/src/buffer_writer.rs +138 -0
- package/src/bytes_ext.rs +18 -0
- package/src/errors.rs +82 -0
- package/src/lib.rs +20 -0
- package/src/multisig.rs +247 -0
- package/src/option_ext.rs +38 -0
- package/src/ownable.rs +227 -0
- package/src/rbac.rs +438 -0
- package/src/testing_utils.rs +180 -0
- package/src/tests/auth.rs +179 -0
- package/src/tests/buffer_reader.rs +969 -0
- package/src/tests/buffer_writer.rs +689 -0
- package/src/tests/bytes_ext.rs +160 -0
- package/src/tests/mod.rs +13 -0
- package/src/tests/multisig.rs +760 -0
- package/src/tests/option_ext.rs +18 -0
- package/src/tests/ownable.rs +822 -0
- package/src/tests/rbac.rs +559 -0
- package/src/tests/test_helper.rs +67 -0
- package/src/tests/testing_utils.rs +522 -0
- package/src/tests/ttl_configurable.rs +586 -0
- package/src/tests/ttl_extendable.rs +64 -0
- package/src/tests/upgradeable.rs +529 -0
- package/src/ttl_configurable.rs +169 -0
- package/src/ttl_extendable.rs +25 -0
- package/src/upgradeable.rs +103 -0
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
extern crate std;
|
|
2
|
+
|
|
3
|
+
use crate::{
|
|
4
|
+
auth::Auth,
|
|
5
|
+
errors::{AuthError, TtlConfigurableError},
|
|
6
|
+
ownable::{Ownable, OwnableInitializer},
|
|
7
|
+
testing_utils::assert_eq_event,
|
|
8
|
+
tests::test_helper::mock_auth,
|
|
9
|
+
ttl_configurable::{
|
|
10
|
+
init_default_ttl_configs, TtlConfig, TtlConfigStorage, TtlConfigsFrozen, TtlConfigsSet, TtlConfigurable,
|
|
11
|
+
LEDGERS_PER_DAY, MAX_TTL,
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
use soroban_sdk::{contract, contractimpl, testutils::Address as _, Address, Env};
|
|
15
|
+
|
|
16
|
+
const DEFAULT_INSTANCE_TTL: TtlConfig = TtlConfig::new(5 * LEDGERS_PER_DAY, 10 * LEDGERS_PER_DAY);
|
|
17
|
+
const DEFAULT_PERSISTENT_TTL: TtlConfig = TtlConfig::new(5 * LEDGERS_PER_DAY, 10 * LEDGERS_PER_DAY);
|
|
18
|
+
|
|
19
|
+
// ============================================
|
|
20
|
+
// Test Contract for TTL functionality
|
|
21
|
+
// ============================================
|
|
22
|
+
|
|
23
|
+
#[contract]
|
|
24
|
+
pub struct TtlTestContract;
|
|
25
|
+
|
|
26
|
+
#[contractimpl]
|
|
27
|
+
impl TtlTestContract {
|
|
28
|
+
/// Test-only initializer to set the contract owner (required by `TtlConfigurable`).
|
|
29
|
+
///
|
|
30
|
+
/// NOTE: This is intentionally *not* protected by auth, since it's only used in unit tests.
|
|
31
|
+
pub fn init_owner(env: &Env, owner: Address) {
|
|
32
|
+
<Self as OwnableInitializer>::init_owner(env, &owner);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// TtlConfigStorage storage tests
|
|
36
|
+
pub fn frozen(env: &Env) -> bool {
|
|
37
|
+
TtlConfigStorage::frozen(env)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
pub fn set_frozen(env: &Env, value: bool) {
|
|
41
|
+
TtlConfigStorage::set_frozen(env, &value);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
pub fn instance_ttl(env: &Env) -> TtlConfig {
|
|
45
|
+
TtlConfigStorage::instance(env).unwrap_or(DEFAULT_INSTANCE_TTL)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
pub fn set_instance_ttl(env: &Env, config: TtlConfig) {
|
|
49
|
+
TtlConfigStorage::set_instance(env, &config);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
pub fn has_instance_ttl(env: &Env) -> bool {
|
|
53
|
+
TtlConfigStorage::has_instance(env)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
pub fn remove_instance_ttl(env: &Env) {
|
|
57
|
+
TtlConfigStorage::remove_instance(env);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
pub fn remove_persistent_ttl(env: &Env) {
|
|
61
|
+
TtlConfigStorage::remove_persistent(env);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
pub fn persistent_ttl(env: &Env) -> TtlConfig {
|
|
65
|
+
TtlConfigStorage::persistent(env).unwrap_or(DEFAULT_PERSISTENT_TTL)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
pub fn set_persistent_ttl(env: &Env, config: TtlConfig) {
|
|
69
|
+
TtlConfigStorage::set_persistent(env, &config);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
pub fn has_persistent_ttl(env: &Env) -> bool {
|
|
73
|
+
TtlConfigStorage::has_persistent(env)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// TtlConfigurable tests (via default trait implementation on this contract)
|
|
77
|
+
pub fn configurable_set_ttl_configs(env: &Env, instance: Option<TtlConfig>, persistent: Option<TtlConfig>) {
|
|
78
|
+
<Self as TtlConfigurable>::set_ttl_configs(env, &instance, &persistent);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
pub fn configurable_ttl_configs(env: &Env) -> (Option<TtlConfig>, Option<TtlConfig>) {
|
|
82
|
+
<Self as TtlConfigurable>::ttl_configs(env)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
pub fn configurable_freeze_ttl_configs(env: &Env) {
|
|
86
|
+
<Self as TtlConfigurable>::freeze_ttl_configs(env);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
pub fn is_ttl_configs_frozen(env: &Env) -> bool {
|
|
90
|
+
<Self as TtlConfigurable>::is_ttl_configs_frozen(env)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
pub fn call_init_default_ttl_configs(env: &Env) {
|
|
94
|
+
init_default_ttl_configs(env);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Enable the default `TtlConfigurable` implementation (which requires `Auth`) on the test contract.
|
|
99
|
+
//
|
|
100
|
+
// Note: use plain Rust `impl` (not `#[contractimpl(contracttrait)]`) to avoid generating
|
|
101
|
+
// duplicate Soroban contract entrypoints that would collide with this test contract's helpers.
|
|
102
|
+
|
|
103
|
+
/// Auth implementation for test contract - uses the stored owner as authorizer.
|
|
104
|
+
impl Auth for TtlTestContract {
|
|
105
|
+
fn authorizer(env: &soroban_sdk::Env) -> Option<Address> {
|
|
106
|
+
<Self as Ownable>::owner(env)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
impl Ownable for TtlTestContract {}
|
|
110
|
+
impl OwnableInitializer for TtlTestContract {}
|
|
111
|
+
impl TtlConfigurable for TtlTestContract {}
|
|
112
|
+
|
|
113
|
+
fn setup_contract() -> (Env, Address, Address, TtlTestContractClient<'static>) {
|
|
114
|
+
let env = Env::default();
|
|
115
|
+
let contract_id = env.register(TtlTestContract, ());
|
|
116
|
+
let client = TtlTestContractClient::new(&env, &contract_id);
|
|
117
|
+
let owner = Address::generate(&env);
|
|
118
|
+
client.init_owner(&owner);
|
|
119
|
+
(env, contract_id, owner, client)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fn owner_set_ttl_configs(
|
|
123
|
+
env: &Env,
|
|
124
|
+
contract_id: &Address,
|
|
125
|
+
owner: &Address,
|
|
126
|
+
client: &TtlTestContractClient,
|
|
127
|
+
instance: &Option<TtlConfig>,
|
|
128
|
+
persistent: &Option<TtlConfig>,
|
|
129
|
+
) {
|
|
130
|
+
mock_auth(env, contract_id, owner, "configurable_set_ttl_configs", (instance, persistent));
|
|
131
|
+
client.configurable_set_ttl_configs(instance, persistent);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fn owner_freeze_ttl_configs(env: &Env, contract_id: &Address, owner: &Address, client: &TtlTestContractClient) {
|
|
135
|
+
mock_auth(env, contract_id, owner, "configurable_freeze_ttl_configs", ());
|
|
136
|
+
client.configurable_freeze_ttl_configs();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ============================================
|
|
140
|
+
// TtlConfig Struct Tests
|
|
141
|
+
// ============================================
|
|
142
|
+
|
|
143
|
+
#[test]
|
|
144
|
+
fn test_ttl_config_new() {
|
|
145
|
+
let config = TtlConfig::new(100, 200);
|
|
146
|
+
assert_eq!(config.threshold, 100);
|
|
147
|
+
assert_eq!(config.extend_to, 200);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
#[test]
|
|
151
|
+
fn test_ttl_config_is_valid() {
|
|
152
|
+
// Representative valid configs
|
|
153
|
+
assert!(TtlConfig::new(100, 200).is_valid(300)); // threshold < extend_to < max
|
|
154
|
+
assert!(TtlConfig::new(100, 100).is_valid(200)); // threshold == extend_to
|
|
155
|
+
assert!(TtlConfig::new(0, 0).is_valid(0)); // zeroes allowed
|
|
156
|
+
assert!(TtlConfig::new(MAX_TTL, MAX_TTL).is_valid(MAX_TTL)); // max boundary
|
|
157
|
+
|
|
158
|
+
// Representative invalid configs (the two failure modes)
|
|
159
|
+
assert!(!TtlConfig::new(200, 100).is_valid(300)); // threshold > extend_to
|
|
160
|
+
assert!(!TtlConfig::new(100, 400).is_valid(300)); // extend_to > max
|
|
161
|
+
}
|
|
162
|
+
// ============================================
|
|
163
|
+
// TtlConfigStorage Storage Tests
|
|
164
|
+
// ============================================
|
|
165
|
+
|
|
166
|
+
#[test]
|
|
167
|
+
fn test_ttl_config_data_defaults() {
|
|
168
|
+
let (_, _, _owner, client) = setup_contract();
|
|
169
|
+
|
|
170
|
+
// Default frozen state should be false
|
|
171
|
+
assert!(!client.frozen());
|
|
172
|
+
|
|
173
|
+
// Default TTLs should be returned when not set
|
|
174
|
+
assert_eq!(client.instance_ttl(), DEFAULT_INSTANCE_TTL);
|
|
175
|
+
assert_eq!(client.persistent_ttl(), DEFAULT_PERSISTENT_TTL);
|
|
176
|
+
|
|
177
|
+
// has_* should return false initially
|
|
178
|
+
assert!(!client.has_instance_ttl());
|
|
179
|
+
assert!(!client.has_persistent_ttl());
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#[test]
|
|
183
|
+
fn test_ttl_config_data_set_get_remove() {
|
|
184
|
+
let (_, _, _owner, client) = setup_contract();
|
|
185
|
+
|
|
186
|
+
let instance_cfg = TtlConfig::new(1000, 2000);
|
|
187
|
+
let persistent_cfg = TtlConfig::new(3000, 4000);
|
|
188
|
+
|
|
189
|
+
// Set and verify instance
|
|
190
|
+
client.set_instance_ttl(&instance_cfg);
|
|
191
|
+
assert!(client.has_instance_ttl());
|
|
192
|
+
assert_eq!(client.instance_ttl(), instance_cfg);
|
|
193
|
+
|
|
194
|
+
// Set and verify persistent
|
|
195
|
+
client.set_persistent_ttl(&persistent_cfg);
|
|
196
|
+
assert!(client.has_persistent_ttl());
|
|
197
|
+
assert_eq!(client.persistent_ttl(), persistent_cfg);
|
|
198
|
+
|
|
199
|
+
// Remove and verify defaults return
|
|
200
|
+
client.remove_instance_ttl();
|
|
201
|
+
assert!(!client.has_instance_ttl());
|
|
202
|
+
assert_eq!(client.instance_ttl(), DEFAULT_INSTANCE_TTL);
|
|
203
|
+
|
|
204
|
+
// Remove persistent
|
|
205
|
+
client.remove_persistent_ttl();
|
|
206
|
+
assert!(!client.has_persistent_ttl());
|
|
207
|
+
assert_eq!(client.persistent_ttl(), DEFAULT_PERSISTENT_TTL);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
#[test]
|
|
211
|
+
fn test_ttl_config_data_frozen() {
|
|
212
|
+
let (_, _, _owner, client) = setup_contract();
|
|
213
|
+
|
|
214
|
+
assert!(!client.frozen());
|
|
215
|
+
|
|
216
|
+
client.set_frozen(&true);
|
|
217
|
+
assert!(client.frozen());
|
|
218
|
+
|
|
219
|
+
client.set_frozen(&false);
|
|
220
|
+
assert!(!client.frozen());
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
#[test]
|
|
224
|
+
fn test_ttl_config_data_independent_storage() {
|
|
225
|
+
let (_, _, _owner, client) = setup_contract();
|
|
226
|
+
|
|
227
|
+
let instance_cfg = TtlConfig::new(100, 200);
|
|
228
|
+
let persistent_cfg = TtlConfig::new(300, 400);
|
|
229
|
+
|
|
230
|
+
// Set each independently and verify others unchanged
|
|
231
|
+
client.set_instance_ttl(&instance_cfg);
|
|
232
|
+
assert_eq!(client.instance_ttl(), instance_cfg);
|
|
233
|
+
assert_eq!(client.persistent_ttl(), DEFAULT_PERSISTENT_TTL);
|
|
234
|
+
|
|
235
|
+
client.set_persistent_ttl(&persistent_cfg);
|
|
236
|
+
assert_eq!(client.instance_ttl(), instance_cfg);
|
|
237
|
+
assert_eq!(client.persistent_ttl(), persistent_cfg);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
#[test]
|
|
241
|
+
fn test_ttl_config_data_remove_when_not_set() {
|
|
242
|
+
let (_, _, _owner, client) = setup_contract();
|
|
243
|
+
|
|
244
|
+
// Removing when not set should not panic
|
|
245
|
+
client.remove_instance_ttl();
|
|
246
|
+
client.remove_persistent_ttl();
|
|
247
|
+
|
|
248
|
+
// Should still return defaults
|
|
249
|
+
assert_eq!(client.instance_ttl(), DEFAULT_INSTANCE_TTL);
|
|
250
|
+
assert_eq!(client.persistent_ttl(), DEFAULT_PERSISTENT_TTL);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
#[test]
|
|
254
|
+
fn test_init_default_ttl_configs() {
|
|
255
|
+
let (_, _, _owner, client) = setup_contract();
|
|
256
|
+
|
|
257
|
+
// Initially no configs set
|
|
258
|
+
assert!(!client.has_instance_ttl());
|
|
259
|
+
assert!(!client.has_persistent_ttl());
|
|
260
|
+
|
|
261
|
+
// Call init_default_ttl_configs
|
|
262
|
+
client.call_init_default_ttl_configs();
|
|
263
|
+
|
|
264
|
+
// Both should now be set to default values (29 days threshold, 30 days extend_to)
|
|
265
|
+
let expected_config = TtlConfig::new(29 * LEDGERS_PER_DAY, 30 * LEDGERS_PER_DAY);
|
|
266
|
+
assert!(client.has_instance_ttl());
|
|
267
|
+
assert!(client.has_persistent_ttl());
|
|
268
|
+
assert_eq!(client.instance_ttl(), expected_config);
|
|
269
|
+
assert_eq!(client.persistent_ttl(), expected_config);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ============================================
|
|
273
|
+
// DefaultTtlConfigurable Tests - Basic Operations
|
|
274
|
+
// ============================================
|
|
275
|
+
|
|
276
|
+
#[test]
|
|
277
|
+
fn test_default_ttl_configurable_initial_state() {
|
|
278
|
+
let (_, _, _owner, client) = setup_contract();
|
|
279
|
+
|
|
280
|
+
let (instance, persistent) = client.configurable_ttl_configs();
|
|
281
|
+
assert!(instance.is_none());
|
|
282
|
+
assert!(persistent.is_none());
|
|
283
|
+
|
|
284
|
+
assert!(!client.is_ttl_configs_frozen());
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
#[test]
|
|
288
|
+
fn test_default_ttl_configurable_set_all() {
|
|
289
|
+
let (env, contract_id, owner, client) = setup_contract();
|
|
290
|
+
|
|
291
|
+
let instance_cfg = TtlConfig::new(1000, 2000);
|
|
292
|
+
let persistent_cfg = TtlConfig::new(3000, 4000);
|
|
293
|
+
|
|
294
|
+
let instance = Some(instance_cfg);
|
|
295
|
+
let persistent = Some(persistent_cfg);
|
|
296
|
+
owner_set_ttl_configs(&env, &contract_id, &owner, &client, &instance, &persistent);
|
|
297
|
+
|
|
298
|
+
assert_eq_event(&env, &contract_id, TtlConfigsSet { instance, persistent });
|
|
299
|
+
|
|
300
|
+
let (instance, persistent) = client.configurable_ttl_configs();
|
|
301
|
+
assert_eq!(instance, Some(instance_cfg));
|
|
302
|
+
assert_eq!(persistent, Some(persistent_cfg));
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
#[test]
|
|
306
|
+
fn test_default_ttl_configurable_set_partial() {
|
|
307
|
+
let (env, contract_id, owner, client) = setup_contract();
|
|
308
|
+
|
|
309
|
+
let instance_cfg = TtlConfig::new(1000, 2000);
|
|
310
|
+
|
|
311
|
+
// Only set instance config
|
|
312
|
+
let instance = Some(instance_cfg);
|
|
313
|
+
let persistent = None;
|
|
314
|
+
owner_set_ttl_configs(&env, &contract_id, &owner, &client, &instance, &persistent);
|
|
315
|
+
assert_eq_event(&env, &contract_id, TtlConfigsSet { instance, persistent });
|
|
316
|
+
|
|
317
|
+
let (instance, persistent) = client.configurable_ttl_configs();
|
|
318
|
+
assert_eq!(instance, Some(instance_cfg));
|
|
319
|
+
assert!(persistent.is_none());
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
#[test]
|
|
323
|
+
fn test_default_ttl_configurable_set_and_remove() {
|
|
324
|
+
let (env, contract_id, owner, client) = setup_contract();
|
|
325
|
+
|
|
326
|
+
let instance_cfg = TtlConfig::new(1000, 2000);
|
|
327
|
+
let persistent_cfg = TtlConfig::new(3000, 4000);
|
|
328
|
+
|
|
329
|
+
// Set configs
|
|
330
|
+
let instance = Some(instance_cfg);
|
|
331
|
+
let persistent = Some(persistent_cfg);
|
|
332
|
+
owner_set_ttl_configs(&env, &contract_id, &owner, &client, &instance, &persistent);
|
|
333
|
+
assert_eq!(client.configurable_ttl_configs(), (Some(instance_cfg), Some(persistent_cfg)));
|
|
334
|
+
|
|
335
|
+
// Remove only instance by passing None, keep persistent
|
|
336
|
+
let instance = None;
|
|
337
|
+
let persistent = Some(persistent_cfg);
|
|
338
|
+
owner_set_ttl_configs(&env, &contract_id, &owner, &client, &instance, &persistent);
|
|
339
|
+
assert_eq_event(&env, &contract_id, TtlConfigsSet { instance, persistent });
|
|
340
|
+
assert_eq!(client.configurable_ttl_configs(), (None, Some(persistent_cfg)));
|
|
341
|
+
|
|
342
|
+
// Remove both by passing None
|
|
343
|
+
let instance = None;
|
|
344
|
+
let persistent = None;
|
|
345
|
+
owner_set_ttl_configs(&env, &contract_id, &owner, &client, &instance, &persistent);
|
|
346
|
+
assert_eq_event(&env, &contract_id, TtlConfigsSet { instance, persistent });
|
|
347
|
+
assert_eq!(client.configurable_ttl_configs(), (None, None));
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// ============================================
|
|
351
|
+
// DefaultTtlConfigurable Tests - Freeze Operations
|
|
352
|
+
// ============================================
|
|
353
|
+
|
|
354
|
+
#[test]
|
|
355
|
+
fn test_default_ttl_configurable_freeze() {
|
|
356
|
+
let (env, contract_id, owner, client) = setup_contract();
|
|
357
|
+
|
|
358
|
+
assert!(!client.is_ttl_configs_frozen());
|
|
359
|
+
owner_freeze_ttl_configs(&env, &contract_id, &owner, &client);
|
|
360
|
+
assert_eq_event(&env, &contract_id, TtlConfigsFrozen {});
|
|
361
|
+
assert!(client.is_ttl_configs_frozen());
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
#[test]
|
|
365
|
+
fn test_default_ttl_configurable_freeze_preserves_configs() {
|
|
366
|
+
let (env, contract_id, owner, client) = setup_contract();
|
|
367
|
+
|
|
368
|
+
let instance_cfg = TtlConfig::new(1000, 2000);
|
|
369
|
+
let persistent_cfg = TtlConfig::new(3000, 4000);
|
|
370
|
+
let instance = Some(instance_cfg);
|
|
371
|
+
let persistent = Some(persistent_cfg);
|
|
372
|
+
owner_set_ttl_configs(&env, &contract_id, &owner, &client, &instance, &persistent);
|
|
373
|
+
assert_eq!(client.configurable_ttl_configs(), (Some(instance_cfg), Some(persistent_cfg)));
|
|
374
|
+
|
|
375
|
+
owner_freeze_ttl_configs(&env, &contract_id, &owner, &client);
|
|
376
|
+
assert!(client.is_ttl_configs_frozen());
|
|
377
|
+
|
|
378
|
+
// Freezing should not alter existing config values.
|
|
379
|
+
assert_eq!(client.configurable_ttl_configs(), (Some(instance_cfg), Some(persistent_cfg)));
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
#[test]
|
|
383
|
+
fn test_default_ttl_configurable_set_when_frozen() {
|
|
384
|
+
let (env, contract_id, owner, client) = setup_contract();
|
|
385
|
+
|
|
386
|
+
owner_freeze_ttl_configs(&env, &contract_id, &owner, &client);
|
|
387
|
+
|
|
388
|
+
let instance_cfg = TtlConfig::new(1000, 2000);
|
|
389
|
+
let instance = Some(instance_cfg);
|
|
390
|
+
let persistent = None;
|
|
391
|
+
mock_auth(&env, &contract_id, &owner, "configurable_set_ttl_configs", (&instance, &persistent));
|
|
392
|
+
let res = client.try_configurable_set_ttl_configs(&instance, &persistent);
|
|
393
|
+
assert_eq!(res.err().unwrap().ok().unwrap(), TtlConfigurableError::TtlConfigFrozen.into());
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
#[test]
|
|
397
|
+
fn test_default_ttl_configurable_freeze_when_already_frozen() {
|
|
398
|
+
let (env, contract_id, owner, client) = setup_contract();
|
|
399
|
+
|
|
400
|
+
owner_freeze_ttl_configs(&env, &contract_id, &owner, &client);
|
|
401
|
+
assert_eq_event(&env, &contract_id, TtlConfigsFrozen {});
|
|
402
|
+
|
|
403
|
+
mock_auth(&env, &contract_id, &owner, "configurable_freeze_ttl_configs", ());
|
|
404
|
+
let res = client.try_configurable_freeze_ttl_configs();
|
|
405
|
+
assert_eq!(res.err().unwrap().ok().unwrap(), TtlConfigurableError::TtlConfigAlreadyFrozen.into());
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
#[test]
|
|
409
|
+
#[should_panic(expected = "Error(Auth, InvalidAction)")]
|
|
410
|
+
fn test_default_ttl_configurable_freeze_requires_auth() {
|
|
411
|
+
let (_env, _contract_id, _owner, client) = setup_contract();
|
|
412
|
+
|
|
413
|
+
// No `mock_auths` provided -> owner.require_auth() must fail.
|
|
414
|
+
client.configurable_freeze_ttl_configs();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
#[test]
|
|
418
|
+
#[should_panic(expected = "Error(Auth, InvalidAction)")]
|
|
419
|
+
fn test_default_ttl_configurable_freeze_wrong_signer_fails() {
|
|
420
|
+
let (env, contract_id, _owner, client) = setup_contract();
|
|
421
|
+
let attacker = Address::generate(&env);
|
|
422
|
+
|
|
423
|
+
// Provide auth for the wrong address -> still must fail because stored owner is different.
|
|
424
|
+
mock_auth(&env, &contract_id, &attacker, "configurable_freeze_ttl_configs", ());
|
|
425
|
+
client.configurable_freeze_ttl_configs();
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
#[test]
|
|
429
|
+
fn test_default_ttl_configurable_freeze_when_owner_not_set() {
|
|
430
|
+
let env = Env::default();
|
|
431
|
+
let contract_id = env.register(TtlTestContract, ());
|
|
432
|
+
let client = TtlTestContractClient::new(&env, &contract_id);
|
|
433
|
+
|
|
434
|
+
let owner = Address::generate(&env);
|
|
435
|
+
|
|
436
|
+
// Even with auth, `require_owner_auth` should fail because no owner is set in storage.
|
|
437
|
+
mock_auth(&env, &contract_id, &owner, "configurable_freeze_ttl_configs", ());
|
|
438
|
+
let res = client.try_configurable_freeze_ttl_configs();
|
|
439
|
+
assert_eq!(res.err().unwrap().ok().unwrap(), AuthError::AuthorizerNotFound.into());
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ============================================
|
|
443
|
+
// DefaultTtlConfigurable Tests - Validation
|
|
444
|
+
// ============================================
|
|
445
|
+
|
|
446
|
+
#[test]
|
|
447
|
+
fn test_default_ttl_configurable_invalid_instance_config() {
|
|
448
|
+
let (env, contract_id, owner, client) = setup_contract();
|
|
449
|
+
// threshold > extend_to is invalid
|
|
450
|
+
let invalid_cfg = TtlConfig::new(3000, 2000);
|
|
451
|
+
let instance = Some(invalid_cfg);
|
|
452
|
+
let persistent = None;
|
|
453
|
+
mock_auth(&env, &contract_id, &owner, "configurable_set_ttl_configs", (&instance, &persistent));
|
|
454
|
+
let res = client.try_configurable_set_ttl_configs(&instance, &persistent);
|
|
455
|
+
assert_eq!(res.err().unwrap().ok().unwrap(), TtlConfigurableError::InvalidTtlConfig.into());
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
#[test]
|
|
459
|
+
fn test_default_ttl_configurable_invalid_persistent_config() {
|
|
460
|
+
let (env, contract_id, owner, client) = setup_contract();
|
|
461
|
+
let invalid_cfg = TtlConfig::new(5000, 4000);
|
|
462
|
+
let instance = None;
|
|
463
|
+
let persistent = Some(invalid_cfg);
|
|
464
|
+
mock_auth(&env, &contract_id, &owner, "configurable_set_ttl_configs", (&instance, &persistent));
|
|
465
|
+
let res = client.try_configurable_set_ttl_configs(&instance, &persistent);
|
|
466
|
+
assert_eq!(res.err().unwrap().ok().unwrap(), TtlConfigurableError::InvalidTtlConfig.into());
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
#[test]
|
|
470
|
+
fn test_default_ttl_configurable_invalid_does_not_partially_update() {
|
|
471
|
+
let (env, contract_id, owner, client) = setup_contract();
|
|
472
|
+
|
|
473
|
+
// Seed with an initial valid state.
|
|
474
|
+
let before_instance = TtlConfig::new(1000, 2000);
|
|
475
|
+
let before_persistent = TtlConfig::new(3000, 4000);
|
|
476
|
+
let instance = Some(before_instance);
|
|
477
|
+
let persistent = Some(before_persistent);
|
|
478
|
+
owner_set_ttl_configs(&env, &contract_id, &owner, &client, &instance, &persistent);
|
|
479
|
+
assert_eq!(client.configurable_ttl_configs(), (Some(before_instance), Some(before_persistent)));
|
|
480
|
+
|
|
481
|
+
// Attempt to update with one invalid config and one different valid config.
|
|
482
|
+
// This must fail and must not partially apply any changes.
|
|
483
|
+
let invalid_instance = TtlConfig::new(3000, 2000); // threshold > extend_to
|
|
484
|
+
let next_persistent = TtlConfig::new(3100, 4100);
|
|
485
|
+
let instance = Some(invalid_instance);
|
|
486
|
+
let persistent = Some(next_persistent);
|
|
487
|
+
mock_auth(&env, &contract_id, &owner, "configurable_set_ttl_configs", (&instance, &persistent));
|
|
488
|
+
assert_eq!(
|
|
489
|
+
client.try_configurable_set_ttl_configs(&instance, &persistent).unwrap_err().unwrap(),
|
|
490
|
+
TtlConfigurableError::InvalidTtlConfig.into()
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
// Ensure nothing changed.
|
|
494
|
+
assert_eq!(client.configurable_ttl_configs(), (Some(before_instance), Some(before_persistent)));
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// ============================================================================
|
|
498
|
+
// Missing branches: owner auth & max-ttl bound behaviors
|
|
499
|
+
// ============================================================================
|
|
500
|
+
|
|
501
|
+
#[test]
|
|
502
|
+
#[should_panic(expected = "Error(Auth, InvalidAction)")]
|
|
503
|
+
fn test_default_ttl_configurable_set_requires_auth() {
|
|
504
|
+
let (_env, _contract_id, _owner, client) = setup_contract();
|
|
505
|
+
let instance = Some(TtlConfig::new(1000, 2000));
|
|
506
|
+
let persistent = None;
|
|
507
|
+
|
|
508
|
+
// No `mock_auths` provided -> owner.require_auth() must fail.
|
|
509
|
+
client.configurable_set_ttl_configs(&instance, &persistent);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
#[test]
|
|
513
|
+
#[should_panic(expected = "Error(Auth, InvalidAction)")]
|
|
514
|
+
fn test_default_ttl_configurable_set_wrong_signer_fails() {
|
|
515
|
+
let (env, contract_id, _owner, client) = setup_contract();
|
|
516
|
+
let attacker = Address::generate(&env);
|
|
517
|
+
|
|
518
|
+
let instance = Some(TtlConfig::new(1000, 2000));
|
|
519
|
+
let persistent = None;
|
|
520
|
+
|
|
521
|
+
// Provide auth for the wrong address -> still must fail because stored owner is different.
|
|
522
|
+
mock_auth(&env, &contract_id, &attacker, "configurable_set_ttl_configs", (&instance, &persistent));
|
|
523
|
+
client.configurable_set_ttl_configs(&instance, &persistent);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
#[test]
|
|
527
|
+
fn test_default_ttl_configurable_set_when_owner_not_set() {
|
|
528
|
+
let env = Env::default();
|
|
529
|
+
let contract_id = env.register(TtlTestContract, ());
|
|
530
|
+
let client = TtlTestContractClient::new(&env, &contract_id);
|
|
531
|
+
|
|
532
|
+
let owner = Address::generate(&env);
|
|
533
|
+
let instance = Some(TtlConfig::new(1000, 2000));
|
|
534
|
+
let persistent = None;
|
|
535
|
+
|
|
536
|
+
// Even with auth, `require_owner_auth` should fail because no owner is set in storage.
|
|
537
|
+
mock_auth(&env, &contract_id, &owner, "configurable_set_ttl_configs", (&instance, &persistent));
|
|
538
|
+
let res = client.try_configurable_set_ttl_configs(&instance, &persistent);
|
|
539
|
+
assert_eq!(res.err().unwrap().ok().unwrap(), AuthError::AuthorizerNotFound.into());
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
#[test]
|
|
543
|
+
fn test_default_ttl_configurable_exceeds_effective_max_ttl() {
|
|
544
|
+
let (env, contract_id, owner, client) = setup_contract();
|
|
545
|
+
|
|
546
|
+
let effective_max = u32::min(MAX_TTL, env.storage().max_ttl());
|
|
547
|
+
let invalid_extend_to = effective_max.checked_add(1).expect("effective_max at u32::MAX");
|
|
548
|
+
let instance = Some(TtlConfig::new(1, invalid_extend_to));
|
|
549
|
+
let persistent = None;
|
|
550
|
+
|
|
551
|
+
mock_auth(&env, &contract_id, &owner, "configurable_set_ttl_configs", (&instance, &persistent));
|
|
552
|
+
let res = client.try_configurable_set_ttl_configs(&instance, &persistent);
|
|
553
|
+
assert_eq!(res.err().unwrap().ok().unwrap(), TtlConfigurableError::InvalidTtlConfig.into());
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// ============================================
|
|
557
|
+
// DefaultTtlConfigurable Tests - Boundary Values
|
|
558
|
+
// ============================================
|
|
559
|
+
|
|
560
|
+
#[test]
|
|
561
|
+
fn test_default_ttl_configurable_boundary_values() {
|
|
562
|
+
let (env, contract_id, owner, client) = setup_contract();
|
|
563
|
+
|
|
564
|
+
let effective_max = u32::min(MAX_TTL, env.storage().max_ttl());
|
|
565
|
+
|
|
566
|
+
// Zero threshold and extend_to
|
|
567
|
+
let cfg = TtlConfig::new(0, 0);
|
|
568
|
+
let instance = Some(cfg);
|
|
569
|
+
let persistent = None;
|
|
570
|
+
owner_set_ttl_configs(&env, &contract_id, &owner, &client, &instance, &persistent);
|
|
571
|
+
assert_eq!(client.configurable_ttl_configs().0, Some(cfg));
|
|
572
|
+
|
|
573
|
+
// Zero threshold, max extend_to
|
|
574
|
+
let cfg = TtlConfig::new(0, effective_max);
|
|
575
|
+
let instance = Some(cfg);
|
|
576
|
+
let persistent = None;
|
|
577
|
+
owner_set_ttl_configs(&env, &contract_id, &owner, &client, &instance, &persistent);
|
|
578
|
+
assert_eq!(client.configurable_ttl_configs().0, Some(cfg));
|
|
579
|
+
|
|
580
|
+
// Equal threshold and extend_to
|
|
581
|
+
let cfg = TtlConfig::new(1000, 1000);
|
|
582
|
+
let instance = Some(cfg);
|
|
583
|
+
let persistent = None;
|
|
584
|
+
owner_set_ttl_configs(&env, &contract_id, &owner, &client, &instance, &persistent);
|
|
585
|
+
assert_eq!(client.configurable_ttl_configs().0, Some(cfg));
|
|
586
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
use crate::ttl_extendable::TtlExtendable;
|
|
2
|
+
use soroban_sdk::{contract, contractimpl, testutils::storage::Instance as _, testutils::Ledger as _, Env};
|
|
3
|
+
|
|
4
|
+
// ============================================
|
|
5
|
+
// Test Contract for TtlExtendable
|
|
6
|
+
// ============================================
|
|
7
|
+
|
|
8
|
+
#[contract]
|
|
9
|
+
pub struct TtlExtendableTestContract;
|
|
10
|
+
|
|
11
|
+
#[contractimpl(contracttrait)]
|
|
12
|
+
impl TtlExtendable for TtlExtendableTestContract {}
|
|
13
|
+
|
|
14
|
+
// ============================================
|
|
15
|
+
// TtlExtendable Tests
|
|
16
|
+
// ============================================
|
|
17
|
+
|
|
18
|
+
#[test]
|
|
19
|
+
fn test_extend_instance_ttl_updates_instance_ttl() {
|
|
20
|
+
let env = Env::default();
|
|
21
|
+
let contract_id = env.register(TtlExtendableTestContract, ());
|
|
22
|
+
let client = TtlExtendableTestContractClient::new(&env, &contract_id);
|
|
23
|
+
|
|
24
|
+
// This is a minimal, high-signal assertion:
|
|
25
|
+
// verify we actually extended *instance* TTL via the Soroban host.
|
|
26
|
+
let (ttl_before, seq_before) =
|
|
27
|
+
env.as_contract(&contract_id, || (env.storage().instance().get_ttl(), env.ledger().sequence()));
|
|
28
|
+
let live_until = seq_before + ttl_before;
|
|
29
|
+
let threshold = ttl_before.saturating_sub(1);
|
|
30
|
+
let extend_to = ttl_before + 500;
|
|
31
|
+
|
|
32
|
+
// Move ledger sequence so TTL is below threshold, ensuring extension triggers.
|
|
33
|
+
env.ledger().set_sequence_number(live_until - threshold);
|
|
34
|
+
|
|
35
|
+
client.extend_instance_ttl(&threshold, &extend_to);
|
|
36
|
+
|
|
37
|
+
let ttl_after = env.as_contract(&contract_id, || env.storage().instance().get_ttl());
|
|
38
|
+
assert_eq!(ttl_after, extend_to);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#[test]
|
|
42
|
+
fn test_extend_instance_ttl_noop_when_above_threshold() {
|
|
43
|
+
let env = Env::default();
|
|
44
|
+
let contract_id = env.register(TtlExtendableTestContract, ());
|
|
45
|
+
let client = TtlExtendableTestContractClient::new(&env, &contract_id);
|
|
46
|
+
|
|
47
|
+
// First, force an extension so we have a known positive TTL baseline.
|
|
48
|
+
let (ttl_before, seq_before) =
|
|
49
|
+
env.as_contract(&contract_id, || (env.storage().instance().get_ttl(), env.ledger().sequence()));
|
|
50
|
+
let live_until = seq_before + ttl_before;
|
|
51
|
+
let threshold = ttl_before.saturating_sub(1);
|
|
52
|
+
let extend_to = ttl_before + 500;
|
|
53
|
+
env.ledger().set_sequence_number(live_until - threshold);
|
|
54
|
+
client.extend_instance_ttl(&threshold, &extend_to);
|
|
55
|
+
let ttl_after = env.as_contract(&contract_id, || env.storage().instance().get_ttl());
|
|
56
|
+
assert_eq!(ttl_after, extend_to);
|
|
57
|
+
|
|
58
|
+
// Now call again with a threshold below the current TTL; it should NOT extend further.
|
|
59
|
+
let threshold = ttl_after.saturating_sub(1);
|
|
60
|
+
let extend_to_2 = ttl_after + 500;
|
|
61
|
+
client.extend_instance_ttl(&threshold, &extend_to_2);
|
|
62
|
+
let ttl_after_2 = env.as_contract(&contract_id, || env.storage().instance().get_ttl());
|
|
63
|
+
assert_eq!(ttl_after_2, ttl_after);
|
|
64
|
+
}
|