@layerzerolabs/protocol-stellar-v2 0.2.64 → 0.2.66

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.
Files changed (48) hide show
  1. package/.turbo/turbo-build.log +230 -307
  2. package/.turbo/turbo-lint.log +175 -175
  3. package/.turbo/turbo-test.log +2047 -1975
  4. package/Cargo.lock +0 -16
  5. package/Cargo.toml +0 -1
  6. package/contracts/oapps/oft/integration-tests/extensions/test_oft_fee.rs +22 -0
  7. package/contracts/oapps/oft/integration-tests/extensions/test_pausable.rs +9 -2
  8. package/contracts/oapps/oft/integration-tests/extensions/test_rate_limiter.rs +27 -2
  9. package/contracts/oapps/oft/integration-tests/setup.rs +22 -18
  10. package/contracts/oapps/oft/integration-tests/utils.rs +81 -34
  11. package/contracts/oapps/oft/src/extensions/oft_fee.rs +13 -0
  12. package/contracts/oapps/oft/src/oft.rs +10 -2
  13. package/package.json +5 -5
  14. package/sdk/.turbo/turbo-test.log +284 -291
  15. package/sdk/dist/generated/oft.d.ts +3 -3
  16. package/sdk/dist/generated/oft.js +3 -3
  17. package/sdk/node_modules/.bin/vitest +2 -2
  18. package/sdk/package.json +2 -2
  19. package/contracts/oapps/console-oft/Cargo.toml +0 -30
  20. package/contracts/oapps/console-oft/integration-tests/extensions/mod.rs +0 -5
  21. package/contracts/oapps/console-oft/integration-tests/extensions/test_combined.rs +0 -90
  22. package/contracts/oapps/console-oft/integration-tests/extensions/test_oft_fee.rs +0 -186
  23. package/contracts/oapps/console-oft/integration-tests/extensions/test_ownership.rs +0 -161
  24. package/contracts/oapps/console-oft/integration-tests/extensions/test_pausable.rs +0 -154
  25. package/contracts/oapps/console-oft/integration-tests/extensions/test_rate_limiter.rs +0 -479
  26. package/contracts/oapps/console-oft/integration-tests/mod.rs +0 -3
  27. package/contracts/oapps/console-oft/integration-tests/setup.rs +0 -303
  28. package/contracts/oapps/console-oft/integration-tests/utils.rs +0 -685
  29. package/contracts/oapps/console-oft/src/errors.rs +0 -7
  30. package/contracts/oapps/console-oft/src/extensions/mod.rs +0 -3
  31. package/contracts/oapps/console-oft/src/extensions/oft_fee.rs +0 -239
  32. package/contracts/oapps/console-oft/src/extensions/pausable.rs +0 -185
  33. package/contracts/oapps/console-oft/src/extensions/rate_limiter.rs +0 -478
  34. package/contracts/oapps/console-oft/src/interfaces/mintable.rs +0 -14
  35. package/contracts/oapps/console-oft/src/interfaces/mod.rs +0 -3
  36. package/contracts/oapps/console-oft/src/lib.rs +0 -26
  37. package/contracts/oapps/console-oft/src/oft.rs +0 -208
  38. package/contracts/oapps/console-oft/src/oft_access_control.rs +0 -93
  39. package/contracts/oapps/console-oft/src/oft_types/lock_unlock.rs +0 -50
  40. package/contracts/oapps/console-oft/src/oft_types/mint_burn.rs +0 -50
  41. package/contracts/oapps/console-oft/src/oft_types/mod.rs +0 -24
  42. package/contracts/oapps/console-oft/src/tests/extensions/mod.rs +0 -3
  43. package/contracts/oapps/console-oft/src/tests/extensions/oft_fee.rs +0 -255
  44. package/contracts/oapps/console-oft/src/tests/extensions/pausable.rs +0 -212
  45. package/contracts/oapps/console-oft/src/tests/extensions/rate_limiter.rs +0 -992
  46. package/contracts/oapps/console-oft/src/tests/mod.rs +0 -2
  47. package/contracts/oapps/console-oft/src/tests/oft_types/lock_unlock.rs +0 -185
  48. package/contracts/oapps/console-oft/src/tests/oft_types/mod.rs +0 -1
@@ -1,992 +0,0 @@
1
- extern crate std;
2
-
3
- use crate as oft;
4
- use crate::extensions::rate_limiter::{
5
- RateLimitConfig, RateLimitError, RateLimitGlobalConfig, RateLimitState, RateLimiter, RateLimiterInternal,
6
- RATE_LIMITER_MANAGER_ROLE, UNLIMITED_AMOUNT,
7
- };
8
- use soroban_sdk::{
9
- contract, contractimpl,
10
- testutils::{Address as _, Ledger as _},
11
- Address, Env, Symbol,
12
- };
13
- use utils::auth::Auth;
14
- use utils::rbac::{grant_role_no_auth, RoleBasedAccessControl};
15
-
16
- // ============================================================================
17
- // Test Contract
18
- // ============================================================================
19
-
20
- #[contract]
21
- struct RateLimiterTestContract;
22
-
23
- impl Auth for RateLimiterTestContract {
24
- fn authorizer(env: &Env) -> Option<Address> {
25
- Some(env.current_contract_address())
26
- }
27
- }
28
-
29
- impl RateLimiterInternal for RateLimiterTestContract {}
30
-
31
- #[contractimpl(contracttrait)]
32
- impl RateLimiter for RateLimiterTestContract {}
33
-
34
- #[contractimpl(contracttrait)]
35
- impl RoleBasedAccessControl for RateLimiterTestContract {}
36
-
37
- #[contractimpl]
38
- impl RateLimiterTestContract {
39
- pub fn init_roles(env: Env) {
40
- let contract_id = env.current_contract_address();
41
- grant_role_no_auth(&env, &contract_id, &Symbol::new(&env, RATE_LIMITER_MANAGER_ROLE), &contract_id);
42
- }
43
-
44
- pub fn outflow(env: Env, id: u128, from: Address, amount: i128) {
45
- <Self as RateLimiterInternal>::__outflow(&env, id, &from, amount);
46
- }
47
-
48
- pub fn inflow(env: Env, id: u128, to: Address, amount: i128) {
49
- <Self as RateLimiterInternal>::__inflow(&env, id, &to, amount);
50
- }
51
-
52
- pub fn get_state_and_config(env: Env, id: u128) -> (u128, RateLimitState, RateLimitConfig) {
53
- <Self as RateLimiterInternal>::__get_rate_limit_state_and_config(&env, id)
54
- }
55
- }
56
-
57
- // ============================================================================
58
- // Test Setup
59
- // ============================================================================
60
-
61
- struct TestSetup {
62
- env: Env,
63
- client: RateLimiterTestContractClient<'static>,
64
- contract_id: Address,
65
- }
66
-
67
- fn setup() -> TestSetup {
68
- let env = Env::default();
69
- env.mock_all_auths_allowing_non_root_auth();
70
-
71
- let contract_id = env.register(RateLimiterTestContract, ());
72
- let client = RateLimiterTestContractClient::new(&env, &contract_id);
73
- client.init_roles();
74
- TestSetup { env, client, contract_id }
75
- }
76
-
77
- fn id(v: u32) -> u128 {
78
- v as u128
79
- }
80
-
81
- fn outbound_config(limit: i128, window: u64) -> RateLimitConfig {
82
- RateLimitConfig {
83
- outbound_enabled: true,
84
- inbound_enabled: false,
85
- net_accounting_enabled: false,
86
- address_exemption_enabled: false,
87
- outbound_limit: limit,
88
- inbound_limit: 0,
89
- outbound_window: window,
90
- inbound_window: 0,
91
- }
92
- }
93
-
94
- fn inbound_config(limit: i128, window: u64) -> RateLimitConfig {
95
- RateLimitConfig {
96
- outbound_enabled: false,
97
- inbound_enabled: true,
98
- net_accounting_enabled: false,
99
- address_exemption_enabled: false,
100
- outbound_limit: 0,
101
- inbound_limit: limit,
102
- outbound_window: 0,
103
- inbound_window: window,
104
- }
105
- }
106
-
107
- fn bidirectional_net_config(limit: i128, window: u64) -> RateLimitConfig {
108
- RateLimitConfig {
109
- outbound_enabled: true,
110
- inbound_enabled: true,
111
- net_accounting_enabled: true,
112
- address_exemption_enabled: false,
113
- outbound_limit: limit,
114
- inbound_limit: limit,
115
- outbound_window: window,
116
- inbound_window: window,
117
- }
118
- }
119
-
120
- fn set_config(client: &RateLimiterTestContractClient, contract_id: &Address, eid: u32, config: RateLimitConfig) {
121
- client.set_rate_limit_config(&id(eid), &Some(config), contract_id);
122
- }
123
-
124
- // ============================================================================
125
- // Default State Tests (closed by default — limits=0 blocks all transfers)
126
- // ============================================================================
127
-
128
- #[test]
129
- fn test_default_state_blocks_outflow() {
130
- let TestSetup { env, client, .. } = setup();
131
- let user = Address::generate(&env);
132
-
133
- let usages = client.get_rate_limit_usages(&id(42));
134
- assert_eq!(usages.outbound_available_amount, 0);
135
- assert_eq!(usages.inbound_available_amount, 0);
136
-
137
- let res = client.try_outflow(&id(42), &user, &1i128);
138
- assert_eq!(res.err().unwrap().ok().unwrap(), RateLimitError::RateLimitExceeded.into());
139
- }
140
-
141
- #[test]
142
- fn test_default_state_blocks_inflow() {
143
- let TestSetup { env, client, .. } = setup();
144
- let user = Address::generate(&env);
145
-
146
- let res = client.try_inflow(&id(42), &user, &1i128);
147
- assert_eq!(res.err().unwrap().ok().unwrap(), RateLimitError::RateLimitExceeded.into());
148
- }
149
-
150
- // ============================================================================
151
- // Global Config Tests
152
- // ============================================================================
153
-
154
- #[test]
155
- fn test_set_and_get_global_config() {
156
- let TestSetup { client, contract_id, .. } = setup();
157
-
158
- let gc = RateLimitGlobalConfig { use_global_state: true, is_globally_disabled: false };
159
- client.set_rate_limit_global_config(&Some(gc.clone()), &contract_id);
160
- assert_eq!(client.get_rate_limit_global_config(), gc);
161
-
162
- client.set_rate_limit_global_config(&None, &contract_id);
163
- assert_eq!(client.get_rate_limit_global_config(), RateLimitGlobalConfig::default());
164
- }
165
-
166
- #[test]
167
- fn test_globally_disabled_bypasses_rate_limit() {
168
- let TestSetup { env, client, contract_id } = setup();
169
- let user = Address::generate(&env);
170
-
171
- let res = client.try_outflow(&id(1), &user, &1i128);
172
- assert!(res.is_err());
173
-
174
- client.set_rate_limit_global_config(
175
- &Some(RateLimitGlobalConfig { use_global_state: false, is_globally_disabled: true }),
176
- &contract_id,
177
- );
178
-
179
- client.outflow(&id(1), &user, &999i128);
180
-
181
- let usages = client.get_rate_limit_usages(&id(1));
182
- assert_eq!(usages.outbound_available_amount, UNLIMITED_AMOUNT);
183
- assert_eq!(usages.inbound_available_amount, UNLIMITED_AMOUNT);
184
- }
185
-
186
- #[test]
187
- fn test_disabled_direction_returns_unlimited() {
188
- let TestSetup { client, contract_id, .. } = setup();
189
-
190
- // Outbound-only config: inbound disabled → inbound available = UNLIMITED
191
- set_config(&client, &contract_id, 1, outbound_config(100, 10));
192
- let usages = client.get_rate_limit_usages(&id(1));
193
- assert_eq!(usages.outbound_available_amount, 100);
194
- assert_eq!(usages.inbound_available_amount, UNLIMITED_AMOUNT);
195
-
196
- // Inbound-only config: outbound disabled → outbound available = UNLIMITED
197
- let inbound_only = RateLimitConfig {
198
- outbound_enabled: false,
199
- inbound_enabled: true,
200
- net_accounting_enabled: false,
201
- address_exemption_enabled: false,
202
- outbound_limit: 0,
203
- inbound_limit: 200,
204
- outbound_window: 0,
205
- inbound_window: 10,
206
- };
207
- set_config(&client, &contract_id, 2, inbound_only);
208
- let usages = client.get_rate_limit_usages(&id(2));
209
- assert_eq!(usages.outbound_available_amount, UNLIMITED_AMOUNT);
210
- assert_eq!(usages.inbound_available_amount, 200);
211
- }
212
-
213
- // ============================================================================
214
- // Set Configs Tests
215
- // ============================================================================
216
-
217
- #[test]
218
- fn test_set_configs_basic() {
219
- let TestSetup { client, contract_id, .. } = setup();
220
- let eid = 77u32;
221
-
222
- set_config(&client, &contract_id, eid, outbound_config(100, 10));
223
-
224
- let (_, config) = client.rate_limits(&id(eid));
225
- let config = config.unwrap();
226
- assert_eq!(config.outbound_limit, 100);
227
- assert_eq!(config.outbound_window, 10);
228
- assert!(config.outbound_enabled);
229
- }
230
-
231
- #[test]
232
- fn test_set_configs_rejects_negative_outbound_limit() {
233
- let TestSetup { client, contract_id, .. } = setup();
234
-
235
- let res = client.try_set_rate_limit_config(&id(1), &Some(outbound_config(-1, 10)), &contract_id);
236
- assert_eq!(res.err().unwrap().ok().unwrap(), RateLimitError::OutboundLimitNegative.into());
237
- }
238
-
239
- #[test]
240
- fn test_set_configs_rejects_negative_inbound_limit() {
241
- let TestSetup { client, contract_id, .. } = setup();
242
-
243
- let config = RateLimitConfig {
244
- outbound_enabled: false,
245
- inbound_enabled: true,
246
- net_accounting_enabled: false,
247
- address_exemption_enabled: false,
248
- outbound_limit: 0,
249
- inbound_limit: -1,
250
- outbound_window: 0,
251
- inbound_window: 10,
252
- };
253
- let res = client.try_set_rate_limit_config(&id(1), &Some(config), &contract_id);
254
- assert_eq!(res.err().unwrap().ok().unwrap(), RateLimitError::InboundLimitNegative.into());
255
- }
256
-
257
- // ============================================================================
258
- // Outflow / Inflow Tests
259
- // ============================================================================
260
-
261
- #[test]
262
- fn test_outflow_within_capacity() {
263
- let TestSetup { env, client, contract_id } = setup();
264
- let user = Address::generate(&env);
265
- let eid = 77u32;
266
-
267
- set_config(&client, &contract_id, eid, outbound_config(100, 100));
268
-
269
- client.outflow(&id(eid), &user, &60i128);
270
- let usages = client.get_rate_limit_usages(&id(eid));
271
- assert_eq!(usages.outbound_usage, 60);
272
- assert_eq!(usages.outbound_available_amount, 40);
273
- }
274
-
275
- #[test]
276
- fn test_outflow_exceeds_capacity() {
277
- let TestSetup { env, client, contract_id } = setup();
278
- let user = Address::generate(&env);
279
- let eid = 77u32;
280
-
281
- set_config(&client, &contract_id, eid, outbound_config(100, 100));
282
- client.outflow(&id(eid), &user, &60i128);
283
-
284
- let res = client.try_outflow(&id(eid), &user, &41i128);
285
- assert_eq!(res.err().unwrap().ok().unwrap(), RateLimitError::RateLimitExceeded.into());
286
- }
287
-
288
- #[test]
289
- fn test_outflow_exact_capacity() {
290
- let TestSetup { env, client, contract_id } = setup();
291
- let user = Address::generate(&env);
292
- let eid = 1u32;
293
-
294
- set_config(&client, &contract_id, eid, outbound_config(100, 100));
295
-
296
- client.outflow(&id(eid), &user, &100i128);
297
- let usages = client.get_rate_limit_usages(&id(eid));
298
- assert_eq!(usages.outbound_usage, 100);
299
- assert_eq!(usages.outbound_available_amount, 0);
300
- }
301
-
302
- #[test]
303
- fn test_outflow_zero_amount_succeeds() {
304
- let TestSetup { env, client, contract_id } = setup();
305
- let user = Address::generate(&env);
306
- let eid = 1u32;
307
-
308
- set_config(&client, &contract_id, eid, outbound_config(100, 100));
309
-
310
- client.outflow(&id(eid), &user, &0i128);
311
- let usages = client.get_rate_limit_usages(&id(eid));
312
- assert_eq!(usages.outbound_usage, 0);
313
- }
314
-
315
- #[test]
316
- fn test_different_ids_are_independent() {
317
- let TestSetup { env, client, contract_id } = setup();
318
- let user = Address::generate(&env);
319
-
320
- set_config(&client, &contract_id, 1, outbound_config(100, 10));
321
- set_config(&client, &contract_id, 2, outbound_config(200, 20));
322
-
323
- client.outflow(&id(1), &user, &50i128);
324
- let usages1 = client.get_rate_limit_usages(&id(1));
325
- let usages2 = client.get_rate_limit_usages(&id(2));
326
- assert_eq!(usages1.outbound_usage, 50);
327
- assert_eq!(usages2.outbound_usage, 0);
328
- }
329
-
330
- #[test]
331
- fn test_inflow_within_capacity() {
332
- let TestSetup { env, client, contract_id } = setup();
333
- let user = Address::generate(&env);
334
- let eid = 77u32;
335
-
336
- set_config(&client, &contract_id, eid, inbound_config(100, 100));
337
-
338
- client.inflow(&id(eid), &user, &60i128);
339
- let usages = client.get_rate_limit_usages(&id(eid));
340
- assert_eq!(usages.inbound_usage, 60);
341
- assert_eq!(usages.inbound_available_amount, 40);
342
- }
343
-
344
- #[test]
345
- fn test_inflow_exceeds_capacity() {
346
- let TestSetup { env, client, contract_id } = setup();
347
- let user = Address::generate(&env);
348
- let eid = 77u32;
349
-
350
- set_config(&client, &contract_id, eid, inbound_config(100, 100));
351
- client.inflow(&id(eid), &user, &60i128);
352
-
353
- let res = client.try_inflow(&id(eid), &user, &41i128);
354
- assert_eq!(res.err().unwrap().ok().unwrap(), RateLimitError::RateLimitExceeded.into());
355
- }
356
-
357
- #[test]
358
- fn test_negative_amount_rejected() {
359
- let TestSetup { env, client, contract_id } = setup();
360
- let user = Address::generate(&env);
361
-
362
- set_config(&client, &contract_id, 1, outbound_config(100, 100));
363
-
364
- let res = client.try_outflow(&id(1), &user, &-1i128);
365
- assert_eq!(res.err().unwrap().ok().unwrap(), RateLimitError::InvalidAmount.into());
366
-
367
- set_config(&client, &contract_id, 2, inbound_config(100, 100));
368
- let res = client.try_inflow(&id(2), &user, &-1i128);
369
- assert_eq!(res.err().unwrap().ok().unwrap(), RateLimitError::InvalidAmount.into());
370
- }
371
-
372
- #[test]
373
- fn test_limit_zero_blocks_all() {
374
- let TestSetup { env, client, contract_id } = setup();
375
- let user = Address::generate(&env);
376
-
377
- set_config(&client, &contract_id, 1, outbound_config(0, 10));
378
-
379
- let usages = client.get_rate_limit_usages(&id(1));
380
- assert_eq!(usages.outbound_available_amount, 0);
381
-
382
- let res = client.try_outflow(&id(1), &user, &1i128);
383
- assert_eq!(res.err().unwrap().ok().unwrap(), RateLimitError::RateLimitExceeded.into());
384
- }
385
-
386
- // ============================================================================
387
- // Net Accounting Tests
388
- // ============================================================================
389
-
390
- #[test]
391
- fn test_net_accounting_inflow_releases_outbound() {
392
- let TestSetup { env, client, contract_id } = setup();
393
- let user = Address::generate(&env);
394
- let eid = 77u32;
395
-
396
- set_config(&client, &contract_id, eid, bidirectional_net_config(100, 100));
397
-
398
- client.outflow(&id(eid), &user, &60i128);
399
- let usages = client.get_rate_limit_usages(&id(eid));
400
- assert_eq!(usages.outbound_usage, 60);
401
- assert_eq!(usages.outbound_available_amount, 40);
402
-
403
- client.inflow(&id(eid), &user, &30i128);
404
- let usages2 = client.get_rate_limit_usages(&id(eid));
405
- assert_eq!(usages2.outbound_usage, 30, "net accounting should release outbound");
406
- assert_eq!(usages2.outbound_available_amount, 70);
407
- assert_eq!(usages2.inbound_usage, 30);
408
- }
409
-
410
- #[test]
411
- fn test_no_net_accounting_inflow_does_not_release() {
412
- let TestSetup { env, client, contract_id } = setup();
413
- let user = Address::generate(&env);
414
- let eid = 77u32;
415
-
416
- let config = RateLimitConfig {
417
- outbound_enabled: true,
418
- inbound_enabled: true,
419
- net_accounting_enabled: false,
420
- address_exemption_enabled: false,
421
- outbound_limit: 100,
422
- inbound_limit: 100,
423
- outbound_window: 100,
424
- inbound_window: 100,
425
- };
426
- set_config(&client, &contract_id, eid, config);
427
-
428
- client.outflow(&id(eid), &user, &60i128);
429
- client.inflow(&id(eid), &user, &30i128);
430
-
431
- let usages = client.get_rate_limit_usages(&id(eid));
432
- assert_eq!(usages.outbound_usage, 60, "gross: outbound should not release");
433
- assert_eq!(usages.inbound_usage, 30);
434
- }
435
-
436
- // ============================================================================
437
- // Decay Tests
438
- // ============================================================================
439
-
440
- #[test]
441
- fn test_decay_reduces_usage_over_time() {
442
- let TestSetup { env, client, contract_id } = setup();
443
- let user = Address::generate(&env);
444
- let eid = 5u32;
445
-
446
- env.ledger().set_timestamp(1_000);
447
- set_config(&client, &contract_id, eid, outbound_config(100, 10));
448
-
449
- client.outflow(&id(eid), &user, &100i128);
450
- assert_eq!(client.get_rate_limit_usages(&id(eid)).outbound_usage, 100);
451
-
452
- env.ledger().set_timestamp(1_005);
453
- let usages = client.get_rate_limit_usages(&id(eid));
454
- assert_eq!(usages.outbound_usage, 50);
455
- assert_eq!(usages.outbound_available_amount, 50);
456
- }
457
-
458
- #[test]
459
- fn test_decay_fully_restores_capacity() {
460
- let TestSetup { env, client, contract_id } = setup();
461
- let user = Address::generate(&env);
462
- let eid = 1u32;
463
-
464
- env.ledger().set_timestamp(1_000);
465
- set_config(&client, &contract_id, eid, outbound_config(100, 10));
466
-
467
- client.outflow(&id(eid), &user, &100i128);
468
- assert_eq!(client.get_rate_limit_usages(&id(eid)).outbound_available_amount, 0);
469
-
470
- env.ledger().set_timestamp(1_010);
471
- let usages = client.get_rate_limit_usages(&id(eid));
472
- assert_eq!(usages.outbound_usage, 0);
473
- assert_eq!(usages.outbound_available_amount, 100);
474
- }
475
-
476
- #[test]
477
- fn test_decay_beyond_window_clamps_to_zero() {
478
- let TestSetup { env, client, contract_id } = setup();
479
- let user = Address::generate(&env);
480
-
481
- env.ledger().set_timestamp(1_000);
482
- set_config(&client, &contract_id, 1, outbound_config(100, 10));
483
-
484
- client.outflow(&id(1), &user, &50i128);
485
-
486
- env.ledger().set_timestamp(2_000);
487
- assert_eq!(client.get_rate_limit_usages(&id(1)).outbound_usage, 0);
488
- }
489
-
490
- #[test]
491
- fn test_timestamp_regression_errors() {
492
- let TestSetup { env, client, contract_id } = setup();
493
- let user = Address::generate(&env);
494
-
495
- env.ledger().set_timestamp(1_000);
496
- set_config(&client, &contract_id, 5, outbound_config(100, 10));
497
- client.outflow(&id(5), &user, &100i128);
498
-
499
- env.ledger().set_timestamp(999);
500
- let res = client.try_get_rate_limit_usages(&id(5));
501
- assert_eq!(res.err().unwrap().ok().unwrap(), RateLimitError::LastUpdatedInFuture.into());
502
- }
503
-
504
- // ============================================================================
505
- // Config Inheritance Tests
506
- // ============================================================================
507
-
508
- #[test]
509
- fn test_config_inheritance_from_default() {
510
- let TestSetup { env, client, contract_id } = setup();
511
- let user = Address::generate(&env);
512
-
513
- let default_config = RateLimitConfig {
514
- outbound_enabled: true,
515
- inbound_enabled: false,
516
- net_accounting_enabled: false,
517
- address_exemption_enabled: false,
518
- outbound_limit: 500,
519
- inbound_limit: 0,
520
- outbound_window: 100,
521
- inbound_window: 0,
522
- };
523
- client.set_rate_limit_config(&id(0), &Some(default_config), &contract_id);
524
-
525
- let usages = client.get_rate_limit_usages(&id(42));
526
- assert_eq!(usages.outbound_available_amount, 500);
527
-
528
- client.outflow(&id(42), &user, &100i128);
529
- let usages = client.get_rate_limit_usages(&id(42));
530
- assert_eq!(usages.outbound_usage, 100);
531
- assert_eq!(usages.outbound_available_amount, 400);
532
- }
533
-
534
- #[test]
535
- fn test_per_id_override_uses_own_config() {
536
- let TestSetup { client, contract_id, .. } = setup();
537
-
538
- let default_config = RateLimitConfig {
539
- outbound_enabled: true,
540
- inbound_enabled: false,
541
- net_accounting_enabled: false,
542
- address_exemption_enabled: false,
543
- outbound_limit: 500,
544
- inbound_limit: 0,
545
- outbound_window: 100,
546
- inbound_window: 0,
547
- };
548
- client.set_rate_limit_config(&id(0), &Some(default_config), &contract_id);
549
-
550
- set_config(&client, &contract_id, 42, outbound_config(200, 50));
551
-
552
- let usages_42 = client.get_rate_limit_usages(&id(42));
553
- assert_eq!(usages_42.outbound_available_amount, 200);
554
-
555
- let usages_99 = client.get_rate_limit_usages(&id(99));
556
- assert_eq!(usages_99.outbound_available_amount, 500);
557
- }
558
-
559
- #[test]
560
- fn test_global_state_shares_usage() {
561
- let TestSetup { env, client, contract_id } = setup();
562
- let user = Address::generate(&env);
563
-
564
- client.set_rate_limit_global_config(
565
- &Some(RateLimitGlobalConfig { use_global_state: true, is_globally_disabled: false }),
566
- &contract_id,
567
- );
568
-
569
- let default_config = RateLimitConfig {
570
- outbound_enabled: true,
571
- inbound_enabled: false,
572
- net_accounting_enabled: false,
573
- address_exemption_enabled: false,
574
- outbound_limit: 100,
575
- inbound_limit: 0,
576
- outbound_window: 100,
577
- inbound_window: 0,
578
- };
579
- client.set_rate_limit_config(&id(0), &Some(default_config), &contract_id);
580
-
581
- client.outflow(&id(1), &user, &30i128);
582
- client.outflow(&id(2), &user, &40i128);
583
-
584
- let usages = client.get_rate_limit_usages(&id(99));
585
- assert_eq!(usages.outbound_usage, 70);
586
- assert_eq!(usages.outbound_available_amount, 30);
587
- }
588
-
589
- // ============================================================================
590
- // Config Removal Tests
591
- // ============================================================================
592
-
593
- #[test]
594
- fn test_remove_config_falls_back_to_default() {
595
- let TestSetup { client, contract_id, .. } = setup();
596
-
597
- let default_config = RateLimitConfig {
598
- outbound_enabled: true,
599
- inbound_enabled: false,
600
- net_accounting_enabled: false,
601
- address_exemption_enabled: false,
602
- outbound_limit: 500,
603
- inbound_limit: 0,
604
- outbound_window: 100,
605
- inbound_window: 0,
606
- };
607
- client.set_rate_limit_config(&id(0), &Some(default_config), &contract_id);
608
-
609
- set_config(&client, &contract_id, 42, outbound_config(200, 50));
610
- assert_eq!(client.get_rate_limit_usages(&id(42)).outbound_available_amount, 200);
611
- assert!(client.rate_limits(&id(42)).1.is_some());
612
-
613
- client.set_rate_limit_config(&id(42), &None, &contract_id);
614
- assert!(client.rate_limits(&id(42)).1.is_none());
615
-
616
- let usages = client.get_rate_limit_usages(&id(42));
617
- assert_eq!(usages.outbound_available_amount, 500, "should fall back to default config");
618
- }
619
-
620
- #[test]
621
- fn test_remove_config_preserves_state() {
622
- let TestSetup { env, client, contract_id } = setup();
623
- let user = Address::generate(&env);
624
-
625
- let default_config = RateLimitConfig {
626
- outbound_enabled: true,
627
- inbound_enabled: false,
628
- net_accounting_enabled: false,
629
- address_exemption_enabled: false,
630
- outbound_limit: 500,
631
- inbound_limit: 0,
632
- outbound_window: 100,
633
- inbound_window: 0,
634
- };
635
- client.set_rate_limit_config(&id(0), &Some(default_config), &contract_id);
636
-
637
- set_config(&client, &contract_id, 42, outbound_config(200, 50));
638
- client.outflow(&id(42), &user, &100i128);
639
-
640
- let (state_before, _) = client.rate_limits(&id(42));
641
- assert_eq!(state_before.outbound_usage, 100);
642
-
643
- client.set_rate_limit_config(&id(42), &None, &contract_id);
644
-
645
- let (state_after, _) = client.rate_limits(&id(42));
646
- assert_eq!(state_after.outbound_usage, 100, "state should be preserved after config removal");
647
- }
648
-
649
- // ============================================================================
650
- // Config Update Tests
651
- // ============================================================================
652
-
653
- #[test]
654
- fn test_updating_config_preserves_usage() {
655
- let TestSetup { env, client, contract_id } = setup();
656
- let user = Address::generate(&env);
657
- let eid = 42u32;
658
-
659
- env.ledger().set_timestamp(1_000);
660
- set_config(&client, &contract_id, eid, outbound_config(100, 10));
661
- client.outflow(&id(eid), &user, &100i128);
662
-
663
- env.ledger().set_timestamp(1_005);
664
- set_config(&client, &contract_id, eid, outbound_config(200, 20));
665
-
666
- let (state, config) = client.rate_limits(&id(eid));
667
- assert_eq!(state.outbound_usage, 100, "raw usage in storage should be preserved");
668
- assert_eq!(config.unwrap().outbound_limit, 200, "config should be updated");
669
- }
670
-
671
- #[test]
672
- fn test_reducing_limit_below_in_flight_clamps_capacity() {
673
- let TestSetup { env, client, contract_id } = setup();
674
- let user = Address::generate(&env);
675
-
676
- set_config(&client, &contract_id, 1, outbound_config(100, 100));
677
- client.outflow(&id(1), &user, &90i128);
678
-
679
- set_config(&client, &contract_id, 1, outbound_config(50, 100));
680
- assert_eq!(client.get_rate_limit_usages(&id(1)).outbound_available_amount, 0);
681
- }
682
-
683
- // ============================================================================
684
- // Address Exemption Tests
685
- // ============================================================================
686
-
687
- #[test]
688
- fn test_address_exemption_bypasses_rate_limit() {
689
- let TestSetup { env, client, contract_id } = setup();
690
- let exempt_user = Address::generate(&env);
691
- let normal_user = Address::generate(&env);
692
-
693
- let config = RateLimitConfig {
694
- outbound_enabled: true,
695
- inbound_enabled: true,
696
- net_accounting_enabled: false,
697
- address_exemption_enabled: true,
698
- outbound_limit: 10,
699
- inbound_limit: 10,
700
- outbound_window: 100,
701
- inbound_window: 100,
702
- };
703
- set_config(&client, &contract_id, 1, config);
704
-
705
- client.set_rate_limit_exemption(&exempt_user, &true, &contract_id);
706
- assert!(client.is_rate_limit_exemption(&exempt_user));
707
-
708
- // Exempt user bypasses both outbound and inbound
709
- client.outflow(&id(1), &exempt_user, &999i128);
710
- client.inflow(&id(1), &exempt_user, &999i128);
711
- let usages = client.get_rate_limit_usages(&id(1));
712
- assert_eq!(usages.outbound_usage, 0, "exempt user should not affect outbound usage");
713
- assert_eq!(usages.inbound_usage, 0, "exempt user should not affect inbound usage");
714
-
715
- // Non-exempt user is still rate-limited
716
- let res = client.try_outflow(&id(1), &normal_user, &11i128);
717
- assert_eq!(res.err().unwrap().ok().unwrap(), RateLimitError::RateLimitExceeded.into());
718
-
719
- // Remove exemption → user is now rate-limited
720
- client.set_rate_limit_exemption(&exempt_user, &false, &contract_id);
721
- assert!(!client.is_rate_limit_exemption(&exempt_user));
722
- let res = client.try_outflow(&id(1), &exempt_user, &11i128);
723
- assert_eq!(res.err().unwrap().ok().unwrap(), RateLimitError::RateLimitExceeded.into());
724
- }
725
-
726
- #[test]
727
- fn test_exemption_ignored_when_config_flag_disabled() {
728
- let TestSetup { env, client, contract_id } = setup();
729
- let user = Address::generate(&env);
730
-
731
- set_config(&client, &contract_id, 1, outbound_config(10, 100));
732
- client.set_rate_limit_exemption(&user, &true, &contract_id);
733
-
734
- let res = client.try_outflow(&id(1), &user, &11i128);
735
- assert_eq!(res.err().unwrap().ok().unwrap(), RateLimitError::RateLimitExceeded.into());
736
- }
737
-
738
- #[test]
739
- fn test_exemption_state_idempotent() {
740
- let TestSetup { env, client, contract_id } = setup();
741
- let user = Address::generate(&env);
742
-
743
- // true → true
744
- client.set_rate_limit_exemption(&user, &true, &contract_id);
745
- let res = client.try_set_rate_limit_exemption(&user, &true, &contract_id);
746
- assert_eq!(res.err().unwrap().ok().unwrap(), RateLimitError::ExemptionStateIdempotent.into());
747
-
748
- // false → false
749
- client.set_rate_limit_exemption(&user, &false, &contract_id);
750
- let res = client.try_set_rate_limit_exemption(&user, &false, &contract_id);
751
- assert_eq!(res.err().unwrap().ok().unwrap(), RateLimitError::ExemptionStateIdempotent.into());
752
- }
753
-
754
- // ============================================================================
755
- // Set States Tests
756
- // ============================================================================
757
-
758
- #[test]
759
- fn test_set_states_manually() {
760
- let TestSetup { env, client, contract_id } = setup();
761
-
762
- env.ledger().set_timestamp(1_000);
763
- set_config(&client, &contract_id, 1, outbound_config(100, 100));
764
-
765
- client.set_rate_limit_state(
766
- &id(1),
767
- &RateLimitState { outbound_usage: 50, inbound_usage: 20, last_updated: 1_000 },
768
- &contract_id,
769
- );
770
-
771
- let (state, _) = client.rate_limits(&id(1));
772
- assert_eq!(state.outbound_usage, 50);
773
- assert_eq!(state.inbound_usage, 20);
774
- assert_eq!(state.last_updated, 1_000);
775
- }
776
-
777
- #[test]
778
- fn test_set_states_rejects_future_timestamp() {
779
- let TestSetup { env, client, contract_id } = setup();
780
-
781
- env.ledger().set_timestamp(1_000);
782
- let res = client.try_set_rate_limit_state(
783
- &id(1),
784
- &RateLimitState { outbound_usage: 0, inbound_usage: 0, last_updated: 2_000 },
785
- &contract_id,
786
- );
787
- assert_eq!(res.err().unwrap().ok().unwrap(), RateLimitError::LastUpdatedInFuture.into());
788
- }
789
-
790
- #[test]
791
- fn test_set_states_rejects_negative_usage() {
792
- let TestSetup { client, contract_id, .. } = setup();
793
-
794
- let res = client.try_set_rate_limit_state(
795
- &id(1),
796
- &RateLimitState { outbound_usage: -1, inbound_usage: 0, last_updated: 0 },
797
- &contract_id,
798
- );
799
- assert_eq!(res.err().unwrap().ok().unwrap(), RateLimitError::OutboundUsageNegative.into());
800
-
801
- let res2 = client.try_set_rate_limit_state(
802
- &id(1),
803
- &RateLimitState { outbound_usage: 0, inbound_usage: -1, last_updated: 0 },
804
- &contract_id,
805
- );
806
- assert_eq!(res2.err().unwrap().ok().unwrap(), RateLimitError::InboundUsageNegative.into());
807
- }
808
-
809
- // ============================================================================
810
- // Checkpoint Tests
811
- // ============================================================================
812
-
813
- #[test]
814
- fn test_checkpoint_snapshots_decayed_usage() {
815
- let TestSetup { env, client, contract_id } = setup();
816
- let user = Address::generate(&env);
817
-
818
- env.ledger().set_timestamp(1_000);
819
- set_config(&client, &contract_id, 1, bidirectional_net_config(100, 10));
820
-
821
- client.outflow(&id(1), &user, &80i128);
822
- client.inflow(&id(1), &user, &40i128);
823
-
824
- env.ledger().set_timestamp(1_001);
825
-
826
- client.checkpoint_rate_limit(&id(1), &contract_id);
827
-
828
- let (state, _) = client.rate_limits(&id(1));
829
- assert_eq!(state.outbound_usage, 30, "checkpoint should store decayed outbound usage");
830
- assert_eq!(state.inbound_usage, 30, "checkpoint should store decayed inbound usage");
831
- assert_eq!(state.last_updated, 1_001);
832
- }
833
-
834
- #[test]
835
- fn test_checkpoint_global_state_writes_to_default_id() {
836
- let TestSetup { env, client, contract_id } = setup();
837
- let user = Address::generate(&env);
838
-
839
- env.ledger().set_timestamp(1_000);
840
- client.set_rate_limit_global_config(
841
- &Some(RateLimitGlobalConfig { use_global_state: true, is_globally_disabled: false }),
842
- &contract_id,
843
- );
844
- set_config(&client, &contract_id, 0, outbound_config(100, 10));
845
-
846
- client.outflow(&id(77), &user, &100i128);
847
-
848
- env.ledger().set_timestamp(1_005);
849
-
850
- client.checkpoint_rate_limit(&id(77), &contract_id);
851
-
852
- let (default_state, _) = client.rate_limits(&id(0));
853
- assert_eq!(default_state.outbound_usage, 50, "checkpoint should write decayed usage to DEFAULT_ID");
854
- assert_eq!(default_state.last_updated, 1_005);
855
-
856
- let (per_id_state, _) = client.rate_limits(&id(77));
857
- assert_eq!(per_id_state, RateLimitState::default(), "per-ID state should remain untouched");
858
- }
859
-
860
- // ============================================================================
861
- // __get_rate_limit_state_and_config Resolution Tests
862
- // ============================================================================
863
-
864
- #[test]
865
- fn test_get_state_and_config_no_config_returns_defaults() {
866
- let TestSetup { client, .. } = setup();
867
-
868
- let (state_id, state, config) = client.get_state_and_config(&id(42));
869
- assert_eq!(state_id, id(42));
870
- assert_eq!(state, RateLimitState::default());
871
- assert_eq!(config, RateLimitConfig::default());
872
- }
873
-
874
- #[test]
875
- fn test_get_state_and_config_inheritance_and_fallback() {
876
- let TestSetup { client, contract_id, .. } = setup();
877
-
878
- let default_cfg = outbound_config(500, 100);
879
- client.set_rate_limit_config(&id(0), &Some(default_cfg.clone()), &contract_id);
880
-
881
- // No per-ID config → inherits DEFAULT
882
- let (state_id, _, config) = client.get_state_and_config(&id(42));
883
- assert_eq!(state_id, id(42));
884
- assert_eq!(config, default_cfg);
885
-
886
- // Per-ID config exists → uses per-ID config
887
- let per_id_cfg = outbound_config(999, 50);
888
- client.set_rate_limit_config(&id(42), &Some(per_id_cfg.clone()), &contract_id);
889
- let (_, _, config) = client.get_state_and_config(&id(42));
890
- assert_eq!(config, per_id_cfg);
891
-
892
- // Remove per-ID config → falls back to DEFAULT
893
- client.set_rate_limit_config(&id(42), &None, &contract_id);
894
- let (_, _, config) = client.get_state_and_config(&id(42));
895
- assert_eq!(config, default_cfg);
896
-
897
- // Remove DEFAULT config → falls back to RateLimitConfig::default()
898
- client.set_rate_limit_config(&id(0), &None, &contract_id);
899
- let (_, _, config) = client.get_state_and_config(&id(42));
900
- assert_eq!(config, RateLimitConfig::default());
901
- }
902
-
903
- #[test]
904
- fn test_get_state_and_config_override_with_state() {
905
- let TestSetup { env, client, contract_id } = setup();
906
-
907
- env.ledger().set_timestamp(1_000);
908
- let per_id_cfg = outbound_config(200, 50);
909
- client.set_rate_limit_config(&id(42), &Some(per_id_cfg.clone()), &contract_id);
910
-
911
- let seeded_state = RateLimitState { outbound_usage: 75, inbound_usage: 25, last_updated: 1_000 };
912
- client.set_rate_limit_state(&id(42), &seeded_state, &contract_id);
913
-
914
- let (state_id, state, config) = client.get_state_and_config(&id(42));
915
- assert_eq!(state_id, id(42));
916
- assert_eq!(state, seeded_state);
917
- assert_eq!(config, per_id_cfg);
918
- }
919
-
920
- #[test]
921
- fn test_get_state_and_config_global_state_ignores_per_id() {
922
- let TestSetup { env, client, contract_id } = setup();
923
-
924
- env.ledger().set_timestamp(1_000);
925
- client.set_rate_limit_global_config(
926
- &Some(RateLimitGlobalConfig { use_global_state: true, is_globally_disabled: false }),
927
- &contract_id,
928
- );
929
-
930
- let default_cfg = outbound_config(500, 100);
931
- client.set_rate_limit_config(&id(0), &Some(default_cfg.clone()), &contract_id);
932
- let default_state = RateLimitState { outbound_usage: 30, inbound_usage: 10, last_updated: 1_000 };
933
- client.set_rate_limit_state(&id(0), &default_state, &contract_id);
934
-
935
- // Per-ID config and different state — should be ignored when use_global_state is true
936
- client.set_rate_limit_config(&id(42), &Some(outbound_config(200, 50)), &contract_id);
937
- client.set_rate_limit_state(
938
- &id(42),
939
- &RateLimitState { outbound_usage: 999, inbound_usage: 888, last_updated: 1_000 },
940
- &contract_id,
941
- );
942
-
943
- let (state_id, state, config) = client.get_state_and_config(&id(42));
944
- assert_eq!(state_id, 0u128, "state_id should be DEFAULT_ID");
945
- assert_eq!(state, default_state, "state should come from DEFAULT_ID");
946
- assert_eq!(config, default_cfg, "config should resolve from DEFAULT_ID");
947
- }
948
-
949
- // ============================================================================
950
- // Edge Case Tests (window=0, huge limit)
951
- // ============================================================================
952
-
953
- #[test]
954
- fn test_window_zero_decays_instantly() {
955
- let TestSetup { env, client, contract_id } = setup();
956
- let user = Address::generate(&env);
957
-
958
- env.ledger().set_timestamp(1_000);
959
- set_config(&client, &contract_id, 1, outbound_config(100, 0));
960
-
961
- client.outflow(&id(1), &user, &100i128);
962
- let usages = client.get_rate_limit_usages(&id(1));
963
- assert_eq!(usages.outbound_usage, 100);
964
- assert_eq!(usages.outbound_available_amount, 0);
965
-
966
- // 1 second later: effective_window=1 → decay = limit * 1 / 1 = limit → full restore
967
- env.ledger().set_timestamp(1_001);
968
- let usages = client.get_rate_limit_usages(&id(1));
969
- assert_eq!(usages.outbound_usage, 0);
970
- assert_eq!(usages.outbound_available_amount, 100);
971
- }
972
-
973
- #[test]
974
- fn test_huge_limit_no_overflow() {
975
- let TestSetup { env, client, contract_id } = setup();
976
- let user = Address::generate(&env);
977
- let huge_limit = i128::MAX / 2;
978
-
979
- env.ledger().set_timestamp(1_000);
980
- set_config(&client, &contract_id, 1, outbound_config(huge_limit, 100));
981
-
982
- client.outflow(&id(1), &user, &huge_limit);
983
- let usages = client.get_rate_limit_usages(&id(1));
984
- assert_eq!(usages.outbound_usage, huge_limit);
985
- assert_eq!(usages.outbound_available_amount, 0);
986
-
987
- // After full window: saturating_mul caps decay, but capacity partially restores without panic
988
- env.ledger().set_timestamp(1_100);
989
- let usages = client.get_rate_limit_usages(&id(1));
990
- assert!(usages.outbound_usage < huge_limit, "usage should decrease");
991
- assert!(usages.outbound_available_amount > 0, "some capacity should restore");
992
- }