@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.
@@ -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
+ }