@layerzerolabs/protocol-stellar-v2 0.2.9 → 0.2.11

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 (83) hide show
  1. package/.turbo/turbo-build.log +245 -199
  2. package/.turbo/turbo-lint.log +79 -107
  3. package/.turbo/turbo-test.log +1017 -841
  4. package/Cargo.lock +13 -5
  5. package/contracts/common-macros/src/contract_impl.rs +6 -3
  6. package/contracts/common-macros/src/error.rs +9 -17
  7. package/contracts/common-macros/src/event.rs +4 -4
  8. package/contracts/common-macros/src/lib.rs +2 -2
  9. package/contracts/common-macros/src/ownable.rs +9 -5
  10. package/contracts/common-macros/src/tests/contract_impl.rs +178 -86
  11. package/contracts/common-macros/src/tests/error.rs +168 -0
  12. package/contracts/common-macros/src/tests/mod.rs +2 -4
  13. package/contracts/common-macros/src/tests/ownable.rs +37 -60
  14. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__contract_impl__snapshot_generated_contract_impl_code.snap +16 -6
  15. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__error__snapshot_generated_contract_error_code.snap +20 -0
  16. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ownable__snapshot_generated_ownable_code.snap +3 -1
  17. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ownable__snapshot_only_owner_preserves_function_signature.snap +12 -2
  18. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ttl_configurable__snapshot_generated_ttl_configurable_code.snap +5 -1
  19. package/contracts/common-macros/src/tests/utils.rs +267 -0
  20. package/contracts/common-macros/src/ttl_configurable.rs +15 -12
  21. package/contracts/common-macros/src/utils.rs +35 -6
  22. package/contracts/message-libs/uln-302/src/receive_uln.rs +1 -1
  23. package/contracts/message-libs/uln-302/src/send_uln.rs +2 -2
  24. package/contracts/message-libs/uln-302/src/uln302.rs +2 -2
  25. package/contracts/oapp-macros/src/oapp_core.rs +1 -1
  26. package/contracts/oapps/oft/integration-tests/setup.rs +4 -3
  27. package/contracts/oapps/oft/src/default_oft_impl.rs +146 -0
  28. package/contracts/oapps/oft/src/extensions/mod.rs +3 -0
  29. package/contracts/oapps/oft/src/extensions/oft_fee.rs +164 -0
  30. package/contracts/oapps/oft/src/extensions/pausable.rs +50 -0
  31. package/contracts/oapps/oft/src/extensions/rate_limiter.rs +198 -0
  32. package/contracts/oapps/oft/src/lib.rs +2 -3
  33. package/contracts/oapps/oft/src/oft.rs +16 -85
  34. package/contracts/oapps/oft/src/oft_types/mint_burn.rs +1 -1
  35. package/contracts/oapps/oft/src/tests/extensions/mod.rs +11 -0
  36. package/contracts/oapps/oft/src/tests/extensions/setup.rs +888 -0
  37. package/contracts/oapps/oft/src/tests/extensions/test_oft_fee.rs +749 -0
  38. package/contracts/oapps/oft/src/tests/extensions/test_pausable.rs +432 -0
  39. package/contracts/oapps/oft/src/tests/extensions/test_rate_limiter.rs +1078 -0
  40. package/contracts/oapps/oft/src/tests/mod.rs +2 -0
  41. package/contracts/oapps/oft/src/tests/test_utils.rs +24 -6
  42. package/contracts/oapps/{oft-mint-burn → oft-std}/Cargo.toml +1 -8
  43. package/contracts/oapps/oft-std/src/lib.rs +5 -0
  44. package/contracts/oapps/oft-std/src/oft.rs +59 -0
  45. package/contracts/utils/src/ownable.rs +2 -2
  46. package/contracts/utils/src/tests/ownable.rs +0 -63
  47. package/contracts/utils/src/ttl.rs +19 -1
  48. package/contracts/workers/dvn/src/auth.rs +91 -27
  49. package/contracts/workers/dvn/src/dvn.rs +22 -20
  50. package/contracts/workers/dvn/src/interfaces/dvn.rs +48 -3
  51. package/contracts/workers/dvn/src/interfaces/multisig.rs +41 -0
  52. package/contracts/workers/dvn/src/lib.rs +6 -8
  53. package/contracts/workers/dvn/src/multisig.rs +6 -3
  54. package/contracts/workers/dvn/src/tests/auth.rs +1 -1
  55. package/contracts/workers/dvn/src/tests/dvn.rs +3 -4
  56. package/contracts/workers/dvn-fee-lib/Cargo.toml +2 -1
  57. package/contracts/workers/dvn-fee-lib/src/dvn_fee_lib.rs +4 -3
  58. package/contracts/workers/dvn-fee-lib/src/tests/dvn_fee_lib.rs +8 -6
  59. package/contracts/workers/executor/src/interfaces/executor.rs +5 -2
  60. package/contracts/workers/executor/src/lz_executor.rs +6 -6
  61. package/contracts/workers/price-feed/Cargo.toml +21 -0
  62. package/contracts/workers/price-feed/src/errors.rs +9 -0
  63. package/contracts/workers/price-feed/src/events.rs +30 -0
  64. package/contracts/workers/price-feed/src/lib.rs +11 -0
  65. package/contracts/workers/price-feed/src/price_feed.rs +265 -0
  66. package/contracts/workers/price-feed/src/storage.rs +42 -0
  67. package/contracts/workers/price-feed/src/types.rs +59 -0
  68. package/contracts/workers/worker/src/interfaces/dvn_fee_lib.rs +2 -1
  69. package/package.json +3 -3
  70. package/sdk/dist/generated/bml.js +3 -1
  71. package/sdk/dist/generated/counter.d.ts +102 -0
  72. package/sdk/dist/generated/counter.js +13 -1
  73. package/sdk/dist/generated/endpoint.js +3 -1
  74. package/sdk/dist/generated/sml.js +3 -1
  75. package/sdk/dist/generated/uln302.js +3 -1
  76. package/sdk/package.json +1 -1
  77. package/contracts/oapps/oft/src/macro_tests/mod.rs +0 -2
  78. package/contracts/oapps/oft/src/macro_tests/test_all_default.rs +0 -41
  79. package/contracts/oapps/oft/src/macro_tests/test_override.rs +0 -83
  80. package/contracts/oapps/oft-mint-burn/src/lib.rs +0 -3
  81. package/contracts/oapps/oft-mint-burn/src/oft.rs +0 -28
  82. package/contracts/oapps/oft-mint-burn/src/tests/mod.rs +0 -1
  83. package/contracts/workers/dvn/src/types.rs +0 -26
@@ -0,0 +1,749 @@
1
+ //! Tests for the OFT fee extension functionality.
2
+ //!
3
+ //! Tests verify:
4
+ //! - Fee configuration (default fee, per-destination fee)
5
+ //! - Fee calculation in quote_oft
6
+ //! - Fee deduction and transfer in send
7
+ //! - Fee deposit address configuration
8
+
9
+ use crate::extensions::oft_fee::OFTFeeError;
10
+ use crate::tests::test_utils::{create_origin, create_recipient_address, encode_oft_message};
11
+ use crate::utils::address_to_bytes32;
12
+ use endpoint_v2::MessagingFee;
13
+ use soroban_sdk::{testutils::Address as _, Address, Bytes, BytesN, Env, IntoVal};
14
+
15
+ use super::setup::ExtensiveOFTTestSetup;
16
+
17
+ // ==================== Default Fee Configuration Tests ====================
18
+
19
+ #[test]
20
+ fn test_initial_default_fee_is_zero() {
21
+ let env = Env::default();
22
+ let setup = ExtensiveOFTTestSetup::new(&env);
23
+
24
+ assert_eq!(setup.default_fee_bps(), 0);
25
+ }
26
+
27
+ #[test]
28
+ fn test_set_default_fee_bps() {
29
+ let env = Env::default();
30
+ let setup = ExtensiveOFTTestSetup::new(&env);
31
+
32
+ setup.set_default_fee_bps(100); // 1%
33
+ assert_eq!(setup.default_fee_bps(), 100);
34
+ }
35
+
36
+ #[test]
37
+ fn test_set_default_fee_bps_max() {
38
+ let env = Env::default();
39
+ let setup = ExtensiveOFTTestSetup::new(&env);
40
+
41
+ // Max fee is 10000 BPS (100%)
42
+ setup.set_default_fee_bps(10000);
43
+ assert_eq!(setup.default_fee_bps(), 10000);
44
+ }
45
+
46
+ #[test]
47
+ fn test_set_default_fee_bps_exceeds_max() {
48
+ let env = Env::default();
49
+ let setup = ExtensiveOFTTestSetup::new(&env);
50
+
51
+ // Should fail - exceeds 100%
52
+ let result = setup.try_set_default_fee_bps(10001);
53
+ assert_eq!(result.err().unwrap().ok().unwrap(), OFTFeeError::InvalidFeeBps.into());
54
+ }
55
+
56
+ #[test]
57
+ fn test_set_default_fee_bps_same_value_fails() {
58
+ let env = Env::default();
59
+ let setup = ExtensiveOFTTestSetup::new(&env);
60
+
61
+ setup.set_default_fee_bps(100);
62
+ // Setting same value should fail
63
+ let result = setup.try_set_default_fee_bps(100);
64
+ assert_eq!(result.err().unwrap().ok().unwrap(), OFTFeeError::SameValue.into());
65
+ }
66
+
67
+ // ==================== Destination Fee Configuration Tests ====================
68
+
69
+ #[test]
70
+ fn test_set_fee_bps_for_destination() {
71
+ let env = Env::default();
72
+ let setup = ExtensiveOFTTestSetup::new(&env);
73
+
74
+ let dst_eid = 100u32;
75
+ setup.set_fee_bps(dst_eid, 200); // 2%
76
+
77
+ assert!(setup.has_fee_bps(dst_eid));
78
+ assert_eq!(setup.fee_bps(dst_eid), 200);
79
+ }
80
+
81
+ #[test]
82
+ fn test_set_fee_bps_max_value() {
83
+ let env = Env::default();
84
+ let setup = ExtensiveOFTTestSetup::new(&env);
85
+
86
+ let dst_eid = 100u32;
87
+
88
+ // Set max fee (100%)
89
+ setup.set_fee_bps(dst_eid, 10000);
90
+ assert_eq!(setup.fee_bps(dst_eid), 10000);
91
+ }
92
+
93
+ #[test]
94
+ fn test_set_fee_bps_exceeds_max() {
95
+ let env = Env::default();
96
+ let setup = ExtensiveOFTTestSetup::new(&env);
97
+
98
+ let dst_eid = 100u32;
99
+
100
+ // Try to set fee > 100% (> 10000 BPS)
101
+ let result = setup.try_set_fee_bps(dst_eid, 10001);
102
+ assert_eq!(result.err().unwrap().ok().unwrap(), OFTFeeError::InvalidFeeBps.into());
103
+ }
104
+
105
+ #[test]
106
+ fn test_set_fee_bps_same_value_for_destination_fails() {
107
+ let env = Env::default();
108
+ let setup = ExtensiveOFTTestSetup::new(&env);
109
+
110
+ let dst_eid = 100u32;
111
+ let fee_bps = 200u64; // 2%
112
+
113
+ // Set fee for destination
114
+ setup.set_fee_bps(dst_eid, fee_bps);
115
+ assert_eq!(setup.fee_bps(dst_eid), fee_bps);
116
+
117
+ // Try to set same fee again - should fail with SameValue
118
+ let result = setup.try_set_fee_bps(dst_eid, fee_bps);
119
+ assert_eq!(result.err().unwrap().ok().unwrap(), OFTFeeError::SameValue.into());
120
+ }
121
+
122
+ #[test]
123
+ fn test_update_fee_bps_for_destination() {
124
+ let env = Env::default();
125
+ let setup = ExtensiveOFTTestSetup::new(&env);
126
+
127
+ let dst_eid = 100u32;
128
+
129
+ // Set initial fee
130
+ setup.set_fee_bps(dst_eid, 100);
131
+ assert_eq!(setup.fee_bps(dst_eid), 100);
132
+
133
+ // Update to different fee
134
+ setup.set_fee_bps(dst_eid, 200);
135
+ assert_eq!(setup.fee_bps(dst_eid), 200);
136
+ }
137
+
138
+ #[test]
139
+ fn test_unset_fee_bps() {
140
+ let env = Env::default();
141
+ let setup = ExtensiveOFTTestSetup::new(&env);
142
+
143
+ let dst_eid = 100u32;
144
+ setup.set_fee_bps(dst_eid, 200);
145
+ assert!(setup.has_fee_bps(dst_eid));
146
+
147
+ setup.unset_fee_bps(dst_eid);
148
+ assert!(!setup.has_fee_bps(dst_eid));
149
+ }
150
+
151
+ #[test]
152
+ fn test_unset_fee_bps_not_found() {
153
+ let env = Env::default();
154
+ let setup = ExtensiveOFTTestSetup::new(&env);
155
+
156
+ let dst_eid = 100u32;
157
+
158
+ // Try to unset fee for destination that doesn't have one - should fail with NotFound
159
+ let result = setup.try_unset_fee_bps(dst_eid);
160
+ assert_eq!(result.err().unwrap().ok().unwrap(), OFTFeeError::NotFound.into());
161
+ }
162
+
163
+ // ==================== Effective Fee Tests ====================
164
+
165
+ #[test]
166
+ fn test_has_oft_fee_returns_true_when_fee_configured() {
167
+ let env = Env::default();
168
+ let setup = ExtensiveOFTTestSetup::new(&env);
169
+
170
+ // Initially no fee is configured
171
+ let dst_eid = 100u32;
172
+ assert_eq!(setup.effective_fee_bps(dst_eid), 0);
173
+
174
+ // Set default fee
175
+ setup.set_default_fee_bps(100); // 1%
176
+
177
+ // Now has_fee_bps should return true since the default fee is non-zero
178
+ assert!(setup.has_fee_bps(dst_eid));
179
+ }
180
+
181
+ #[test]
182
+ fn test_has_oft_fee_returns_false_when_no_fee() {
183
+ let env = Env::default();
184
+ let setup = ExtensiveOFTTestSetup::new(&env);
185
+
186
+ let dst_eid = 100u32;
187
+
188
+ // No fee configured - should return false
189
+ assert!(!setup.has_fee_bps(dst_eid));
190
+ }
191
+
192
+ #[test]
193
+ fn test_has_oft_fee_uses_destination_specific_fee() {
194
+ let env = Env::default();
195
+ let setup = ExtensiveOFTTestSetup::new(&env);
196
+
197
+ let dst_eid_1 = 100u32;
198
+ let dst_eid_2 = 200u32;
199
+
200
+ // Set destination-specific fee for dst_eid_1 only
201
+ setup.set_fee_bps(dst_eid_1, 100); // 1%
202
+
203
+ // dst_eid_1 should have fee
204
+ assert!(setup.has_fee_bps(dst_eid_1));
205
+
206
+ // dst_eid_2 should not have fee (no default, no specific)
207
+ assert!(!setup.has_fee_bps(dst_eid_2));
208
+ }
209
+
210
+ #[test]
211
+ fn test_effective_fee_uses_destination_over_default() {
212
+ let env = Env::default();
213
+ let setup = ExtensiveOFTTestSetup::new(&env);
214
+
215
+ setup.set_default_fee_bps(100); // 1% default
216
+
217
+ let dst_eid = 100u32;
218
+ setup.set_fee_bps(dst_eid, 200); // 2% for specific destination
219
+
220
+ // Effective fee should use destination-specific fee
221
+ assert_eq!(setup.effective_fee_bps(dst_eid), 200);
222
+ }
223
+
224
+ #[test]
225
+ fn test_effective_fee_falls_back_to_default() {
226
+ let env = Env::default();
227
+ let setup = ExtensiveOFTTestSetup::new(&env);
228
+
229
+ setup.set_default_fee_bps(100); // 1% default
230
+
231
+ let dst_eid = 100u32;
232
+ // No destination-specific fee set
233
+
234
+ // Effective fee should use default
235
+ assert_eq!(setup.effective_fee_bps(dst_eid), 100);
236
+ }
237
+
238
+ // ==================== Fee Deposit Address Tests ====================
239
+
240
+ #[test]
241
+ fn test_set_fee_deposit_address() {
242
+ let env = Env::default();
243
+ let setup = ExtensiveOFTTestSetup::new(&env);
244
+
245
+ let fee_collector = create_recipient_address(&env);
246
+ setup.set_fee_deposit_address(&fee_collector);
247
+
248
+ assert_eq!(setup.fee_deposit_address(), fee_collector);
249
+ }
250
+
251
+ #[test]
252
+ fn test_set_fee_deposit_address_same_value_fails() {
253
+ let env = Env::default();
254
+ let setup = ExtensiveOFTTestSetup::new(&env);
255
+
256
+ let fee_collector = create_recipient_address(&env);
257
+
258
+ // Set fee deposit address
259
+ setup.set_fee_deposit_address(&fee_collector);
260
+ assert_eq!(setup.fee_deposit_address(), fee_collector);
261
+
262
+ // Try to set same address again - should fail with SameValue
263
+ let result = setup.try_set_fee_deposit_address(&fee_collector);
264
+ assert_eq!(result.err().unwrap().ok().unwrap(), OFTFeeError::SameValue.into());
265
+ }
266
+
267
+ #[test]
268
+ fn test_update_fee_deposit_address() {
269
+ let env = Env::default();
270
+ let setup = ExtensiveOFTTestSetup::new(&env);
271
+
272
+ let fee_collector_1 = create_recipient_address(&env);
273
+ let fee_collector_2 = create_recipient_address(&env);
274
+
275
+ // Set initial address
276
+ setup.set_fee_deposit_address(&fee_collector_1);
277
+ assert_eq!(setup.fee_deposit_address(), fee_collector_1);
278
+
279
+ // Update to different address
280
+ setup.set_fee_deposit_address(&fee_collector_2);
281
+ assert_eq!(setup.fee_deposit_address(), fee_collector_2);
282
+ }
283
+
284
+ // ==================== quote_oft Tests (Fee Calculation) ====================
285
+
286
+ #[test]
287
+ fn test_quote_oft_no_fee() {
288
+ let env = Env::default();
289
+ let setup = ExtensiveOFTTestSetup::new(&env);
290
+
291
+ // Set fee deposit address (required by ExtensiveOFT even with no fee)
292
+ let fee_collector = create_recipient_address(&env);
293
+ setup.set_fee_deposit_address(&fee_collector);
294
+
295
+ let dst_eid = 100u32;
296
+ let peer = BytesN::from_array(&env, &[2u8; 32]);
297
+ setup.set_peer(dst_eid, &peer);
298
+
299
+ let amount_ld = 1_000_000i128;
300
+ let send_param = setup.create_send_param(dst_eid, amount_ld, amount_ld);
301
+
302
+ let receipt = setup.quote_oft(&send_param);
303
+
304
+ // No fee configured, amounts should be equal
305
+ assert_eq!(receipt.amount_sent_ld, amount_ld);
306
+ assert_eq!(receipt.amount_received_ld, amount_ld);
307
+ }
308
+
309
+ #[test]
310
+ fn test_quote_oft_with_default_fee() {
311
+ let env = Env::default();
312
+ let setup = ExtensiveOFTTestSetup::new(&env);
313
+
314
+ // Set fee deposit address (required for fee to be applied)
315
+ let fee_collector = create_recipient_address(&env);
316
+ setup.set_fee_deposit_address(&fee_collector);
317
+
318
+ // Set 1% default fee (100 BPS)
319
+ setup.set_default_fee_bps(100);
320
+
321
+ let dst_eid = 100u32;
322
+ let peer = BytesN::from_array(&env, &[2u8; 32]);
323
+ setup.set_peer(dst_eid, &peer);
324
+
325
+ let dust = 5i128;
326
+ let amount_ld = 1_000_000i128 + dust; // dust will be charged as a fee
327
+ // min_amount_ld should account for fee
328
+ let expected_after_fee = 990_000i128; // 1% fee
329
+ let send_param = setup.create_send_param(dst_eid, amount_ld, expected_after_fee);
330
+
331
+ let receipt = setup.quote_oft(&send_param);
332
+
333
+ // Amount sent is original, amount received is after fee
334
+ assert_eq!(receipt.amount_sent_ld, amount_ld);
335
+ assert_eq!(receipt.amount_received_ld, expected_after_fee);
336
+ }
337
+
338
+ #[test]
339
+ fn test_quote_oft_with_destination_fee() {
340
+ let env = Env::default();
341
+ let setup = ExtensiveOFTTestSetup::new(&env);
342
+
343
+ // Set fee deposit address
344
+ let fee_collector = create_recipient_address(&env);
345
+ setup.set_fee_deposit_address(&fee_collector);
346
+
347
+ // Set 1% default fee and 2% for specific destination
348
+ setup.set_default_fee_bps(100);
349
+ let dst_eid = 100u32;
350
+ setup.set_fee_bps(dst_eid, 200); // 2%
351
+
352
+ let peer = BytesN::from_array(&env, &[2u8; 32]);
353
+ setup.set_peer(dst_eid, &peer);
354
+
355
+ let amount_ld = 1_000_000i128;
356
+ let expected_after_fee = 980_000i128; // 2% fee
357
+ let send_param = setup.create_send_param(dst_eid, amount_ld, expected_after_fee);
358
+
359
+ let receipt = setup.quote_oft(&send_param);
360
+
361
+ // Should use destination-specific 2% fee
362
+ assert_eq!(receipt.amount_sent_ld, amount_ld);
363
+ assert_eq!(receipt.amount_received_ld, expected_after_fee);
364
+ }
365
+
366
+ #[test]
367
+ fn test_quote_oft_fee_calculation_precision() {
368
+ let env = Env::default();
369
+ let setup = ExtensiveOFTTestSetup::new(&env);
370
+
371
+ // Set fee deposit address
372
+ let fee_collector = create_recipient_address(&env);
373
+ setup.set_fee_deposit_address(&fee_collector);
374
+
375
+ // Set 0.5% fee (50 BPS)
376
+ setup.set_default_fee_bps(50);
377
+
378
+ let dst_eid = 100u32;
379
+ let peer = BytesN::from_array(&env, &[2u8; 32]);
380
+ setup.set_peer(dst_eid, &peer);
381
+
382
+ let amount_ld = 1_000_000i128;
383
+ // 0.5% of 1_000_000 = 5_000 fee
384
+ let expected_after_fee = 995_000i128;
385
+ let send_param = setup.create_send_param(dst_eid, amount_ld, expected_after_fee);
386
+
387
+ let receipt = setup.quote_oft(&send_param);
388
+
389
+ assert_eq!(receipt.amount_sent_ld, amount_ld);
390
+ assert_eq!(receipt.amount_received_ld, expected_after_fee);
391
+ }
392
+
393
+ // ==================== quote_send Tests (Fee) ====================
394
+
395
+ #[test]
396
+ fn test_quote_send_with_fee() {
397
+ let env = Env::default();
398
+ let setup = ExtensiveOFTTestSetup::new(&env);
399
+
400
+ // Set fee deposit address and fee
401
+ let fee_collector = create_recipient_address(&env);
402
+ setup.set_fee_deposit_address(&fee_collector);
403
+ setup.set_default_fee_bps(100); // 1%
404
+
405
+ let sender = Address::generate(&env);
406
+ let dst_eid = 100u32;
407
+ let peer = BytesN::from_array(&env, &[2u8; 32]);
408
+ setup.set_peer(dst_eid, &peer);
409
+
410
+ let amount_ld = 1_000_000i128;
411
+ let expected_after_fee = 990_000i128;
412
+ let send_param = setup.create_send_param(dst_eid, amount_ld, expected_after_fee);
413
+
414
+ // quote_send should work with fees configured
415
+ let msg_fee = setup.quote_send(&sender, &send_param, false);
416
+ assert!(msg_fee.native_fee > 0);
417
+ }
418
+
419
+ // ==================== send Tests (Fee Transfer) ====================
420
+
421
+ #[test]
422
+ fn test_quote_oft_with_fee_without_deposit_address_fails() {
423
+ let env = Env::default();
424
+ let setup = ExtensiveOFTTestSetup::new(&env);
425
+
426
+ // Set fee rate but no fee deposit address
427
+ setup.set_default_fee_bps(100); // 1%
428
+
429
+ let dst_eid = 100u32;
430
+ let peer = BytesN::from_array(&env, &[2u8; 32]);
431
+ setup.set_peer(dst_eid, &peer);
432
+
433
+ let amount_ld = 1_000_000i128;
434
+ let expected_after_fee = 990_000i128;
435
+ let send_param = setup.create_send_param(dst_eid, amount_ld, expected_after_fee);
436
+
437
+ // quote_oft should fail because fee deposit address is not set
438
+ let result = setup.try_quote_oft(&send_param);
439
+ assert_eq!(result.err().unwrap().ok().unwrap(), OFTFeeError::InvalidFeeDepositAddress.into());
440
+ }
441
+
442
+ #[test]
443
+ fn test_send_with_fee_transfers_to_collector() {
444
+ let env = Env::default();
445
+ let setup = ExtensiveOFTTestSetup::new(&env);
446
+
447
+ // Set fee deposit address
448
+ let fee_collector = create_recipient_address(&env);
449
+ setup.set_fee_deposit_address(&fee_collector);
450
+
451
+ // Set 1% fee
452
+ setup.set_default_fee_bps(100);
453
+
454
+ let sender = Address::generate(&env);
455
+ let dst_eid = 100u32;
456
+ let peer = BytesN::from_array(&env, &[2u8; 32]);
457
+ setup.set_peer(dst_eid, &peer);
458
+
459
+ let amount_ld = 1_000_000i128;
460
+ let expected_fee = 10_000i128; // 1%
461
+ let expected_after_fee = 990_000i128;
462
+
463
+ setup.fund_tokens(&sender, amount_ld);
464
+ setup.fund_native_fees(&sender, setup.native_fee);
465
+
466
+ let initial_collector_balance = setup.token_client.balance(&fee_collector);
467
+
468
+ let send_param = setup.create_send_param(dst_eid, amount_ld, expected_after_fee);
469
+ let fee = MessagingFee { native_fee: setup.native_fee, zro_fee: 0 };
470
+ let oft_receipt = setup.quote_oft(&send_param);
471
+
472
+ // Use send_with_fee which includes fee transfer auth
473
+ let (_, receipt) = setup.send_with_fee(&sender, &send_param, &fee, &sender, &oft_receipt, &fee_collector);
474
+
475
+ // Verify receipt
476
+ assert_eq!(receipt.amount_sent_ld, amount_ld);
477
+ assert_eq!(receipt.amount_received_ld, expected_after_fee);
478
+
479
+ // Verify fee was transferred to collector
480
+ assert_eq!(setup.token_client.balance(&fee_collector), initial_collector_balance + expected_fee);
481
+
482
+ // Verify sender's tokens were burned (amount_sent_ld)
483
+ assert_eq!(setup.token_client.balance(&sender), 0);
484
+ }
485
+
486
+ #[test]
487
+ fn test_send_without_fee_no_transfer() {
488
+ let env = Env::default();
489
+ let setup = ExtensiveOFTTestSetup::new(&env);
490
+
491
+ // Set fee deposit address (required by ExtensiveOFT)
492
+ let fee_collector = create_recipient_address(&env);
493
+ setup.set_fee_deposit_address(&fee_collector);
494
+
495
+ // No fee rate configured (default is 0)
496
+ let sender = Address::generate(&env);
497
+ let dst_eid = 100u32;
498
+ let peer = BytesN::from_array(&env, &[2u8; 32]);
499
+ setup.set_peer(dst_eid, &peer);
500
+
501
+ let amount_ld = 1_000_000i128;
502
+ setup.fund_tokens(&sender, amount_ld);
503
+ setup.fund_native_fees(&sender, setup.native_fee);
504
+
505
+ let send_param = setup.create_send_param(dst_eid, amount_ld, amount_ld);
506
+ let fee = MessagingFee { native_fee: setup.native_fee, zro_fee: 0 };
507
+ let oft_receipt = setup.quote_oft(&send_param);
508
+
509
+ // Use regular send (no fee transfer needed since fee is 0)
510
+ let (_, receipt) = setup.send(&sender, &send_param, &fee, &sender, &oft_receipt);
511
+
512
+ // Amounts should be equal (no fee)
513
+ assert_eq!(receipt.amount_sent_ld, amount_ld);
514
+ assert_eq!(receipt.amount_received_ld, amount_ld);
515
+ }
516
+
517
+ #[test]
518
+ fn test_send_with_different_destination_fees() {
519
+ let env = Env::default();
520
+ let setup = ExtensiveOFTTestSetup::new(&env);
521
+
522
+ // Set fee deposit address
523
+ let fee_collector = create_recipient_address(&env);
524
+ setup.set_fee_deposit_address(&fee_collector);
525
+
526
+ // Set different fees for different destinations
527
+ let dst_eid_1 = 100u32;
528
+ let dst_eid_2 = 200u32;
529
+ setup.set_fee_bps(dst_eid_1, 100); // 1%
530
+ setup.set_fee_bps(dst_eid_2, 500); // 5%
531
+
532
+ let peer = BytesN::from_array(&env, &[2u8; 32]);
533
+ setup.set_peer(dst_eid_1, &peer);
534
+ setup.set_peer(dst_eid_2, &peer);
535
+
536
+ let sender = Address::generate(&env);
537
+ let amount_ld = 1_000_000i128;
538
+ let fee = MessagingFee { native_fee: setup.native_fee, zro_fee: 0 };
539
+
540
+ // First send to dst_eid_1 (1% fee)
541
+ setup.fund_tokens(&sender, amount_ld);
542
+ setup.fund_native_fees(&sender, setup.native_fee);
543
+
544
+ let expected_after_fee_1 = 990_000i128;
545
+ let send_param_1 = setup.create_send_param(dst_eid_1, amount_ld, expected_after_fee_1);
546
+ let oft_receipt_1 = setup.quote_oft(&send_param_1);
547
+
548
+ let (_, receipt_1) = setup.send_with_fee(&sender, &send_param_1, &fee, &sender, &oft_receipt_1, &fee_collector);
549
+ assert_eq!(receipt_1.amount_received_ld, expected_after_fee_1);
550
+
551
+ // Second send to dst_eid_2 (5% fee)
552
+ setup.fund_tokens(&sender, amount_ld);
553
+ setup.fund_native_fees(&sender, setup.native_fee);
554
+
555
+ let expected_after_fee_2 = 950_000i128;
556
+ let send_param_2 = setup.create_send_param(dst_eid_2, amount_ld, expected_after_fee_2);
557
+ let oft_receipt_2 = setup.quote_oft(&send_param_2);
558
+
559
+ let (_, receipt_2) = setup.send_with_fee(&sender, &send_param_2, &fee, &sender, &oft_receipt_2, &fee_collector);
560
+ assert_eq!(receipt_2.amount_received_ld, expected_after_fee_2);
561
+ }
562
+
563
+ // ==================== lz_receive Tests (No Fee on Inbound) ====================
564
+
565
+ #[test]
566
+ fn test_lz_receive_no_fee_on_inbound() {
567
+ let env = Env::default();
568
+ let setup = ExtensiveOFTTestSetup::new(&env);
569
+
570
+ // Set fee (only applies to outbound)
571
+ let fee_collector = create_recipient_address(&env);
572
+ setup.set_fee_deposit_address(&fee_collector);
573
+ setup.set_default_fee_bps(100); // 1%
574
+
575
+ let executor = Address::generate(&env);
576
+ let recipient = create_recipient_address(&env);
577
+ let src_eid = 100u32;
578
+ let peer = BytesN::from_array(&env, &[2u8; 32]);
579
+ setup.set_peer(src_eid, &peer);
580
+
581
+ let amount_sd = 1_000_000u64;
582
+ let recipient_bytes32 = address_to_bytes32(&recipient);
583
+ let message = encode_oft_message(&env, &recipient_bytes32, amount_sd);
584
+
585
+ let guid = BytesN::from_array(&env, &[1u8; 32]);
586
+ let origin = create_origin(src_eid, &peer, 1);
587
+ let extra_data = Bytes::new(&env);
588
+
589
+ let initial_balance = setup.token_client.balance(&recipient);
590
+ let initial_collector_balance = setup.token_client.balance(&fee_collector);
591
+
592
+ setup.lz_receive(&executor, &origin, &guid, &message, &extra_data, 0);
593
+
594
+ // Recipient should receive full amount (no fee on inbound)
595
+ let conversion_rate = setup.oft.decimal_conversion_rate();
596
+ let expected_amount_ld = (amount_sd as i128) * conversion_rate;
597
+ assert_eq!(setup.token_client.balance(&recipient), initial_balance + expected_amount_ld);
598
+
599
+ // Fee collector should not receive anything on inbound
600
+ assert_eq!(setup.token_client.balance(&fee_collector), initial_collector_balance);
601
+ }
602
+
603
+ // ==================== Authentication Tests ====================
604
+
605
+ #[test]
606
+ fn test_set_default_fee_bps_requires_owner_auth() {
607
+ let env = Env::default();
608
+ let setup = ExtensiveOFTTestSetup::new(&env);
609
+
610
+ // Try to set default fee without auth (no auth provided)
611
+ let result = setup.oft.try_set_default_fee_bps(&100);
612
+ assert!(result.is_err());
613
+ }
614
+
615
+ #[test]
616
+ fn test_set_default_fee_bps_wrong_auth_fails() {
617
+ let env = Env::default();
618
+ let setup = ExtensiveOFTTestSetup::new(&env);
619
+
620
+ let non_owner = Address::generate(&env);
621
+
622
+ // Try to set default fee with wrong auth (non-owner)
623
+ env.mock_auths(&[soroban_sdk::testutils::MockAuth {
624
+ address: &non_owner,
625
+ invoke: &soroban_sdk::testutils::MockAuthInvoke {
626
+ contract: &setup.oft.address,
627
+ fn_name: "set_default_fee_bps",
628
+ args: (100u64,).into_val(&env),
629
+ sub_invokes: &[],
630
+ },
631
+ }]);
632
+ let result = setup.oft.try_set_default_fee_bps(&100);
633
+ assert!(result.is_err());
634
+ }
635
+
636
+ #[test]
637
+ fn test_set_fee_bps_requires_owner_auth() {
638
+ let env = Env::default();
639
+ let setup = ExtensiveOFTTestSetup::new(&env);
640
+
641
+ let dst_eid = 100u32;
642
+
643
+ // Try to set fee bps without auth
644
+ let result = setup.oft.try_set_fee_bps(&dst_eid, &200);
645
+ assert!(result.is_err());
646
+ }
647
+
648
+ #[test]
649
+ fn test_set_fee_bps_wrong_auth_fails() {
650
+ let env = Env::default();
651
+ let setup = ExtensiveOFTTestSetup::new(&env);
652
+
653
+ let non_owner = Address::generate(&env);
654
+ let dst_eid = 100u32;
655
+
656
+ // Try to set fee bps with wrong auth (non-owner)
657
+ env.mock_auths(&[soroban_sdk::testutils::MockAuth {
658
+ address: &non_owner,
659
+ invoke: &soroban_sdk::testutils::MockAuthInvoke {
660
+ contract: &setup.oft.address,
661
+ fn_name: "set_fee_bps",
662
+ args: (dst_eid, 200u64).into_val(&env),
663
+ sub_invokes: &[],
664
+ },
665
+ }]);
666
+ let result = setup.oft.try_set_fee_bps(&dst_eid, &200);
667
+ assert!(result.is_err());
668
+ }
669
+
670
+ #[test]
671
+ fn test_unset_fee_bps_requires_owner_auth() {
672
+ let env = Env::default();
673
+ let setup = ExtensiveOFTTestSetup::new(&env);
674
+
675
+ let dst_eid = 100u32;
676
+
677
+ // First set fee bps as owner
678
+ setup.set_fee_bps(dst_eid, 200);
679
+ assert!(setup.has_fee_bps(dst_eid));
680
+
681
+ // Try to unset fee bps without auth
682
+ let result = setup.oft.try_unset_fee_bps(&dst_eid);
683
+ assert!(result.is_err());
684
+
685
+ // Fee should still be set
686
+ assert!(setup.has_fee_bps(dst_eid));
687
+ }
688
+
689
+ #[test]
690
+ fn test_unset_fee_bps_wrong_auth_fails() {
691
+ let env = Env::default();
692
+ let setup = ExtensiveOFTTestSetup::new(&env);
693
+
694
+ let non_owner = Address::generate(&env);
695
+ let dst_eid = 100u32;
696
+
697
+ // First set fee bps as owner
698
+ setup.set_fee_bps(dst_eid, 200);
699
+
700
+ // Try to unset fee bps with wrong auth (non-owner)
701
+ env.mock_auths(&[soroban_sdk::testutils::MockAuth {
702
+ address: &non_owner,
703
+ invoke: &soroban_sdk::testutils::MockAuthInvoke {
704
+ contract: &setup.oft.address,
705
+ fn_name: "unset_fee_bps",
706
+ args: (dst_eid,).into_val(&env),
707
+ sub_invokes: &[],
708
+ },
709
+ }]);
710
+ let result = setup.oft.try_unset_fee_bps(&dst_eid);
711
+ assert!(result.is_err());
712
+
713
+ // Fee should still be set
714
+ assert!(setup.has_fee_bps(dst_eid));
715
+ }
716
+
717
+ #[test]
718
+ fn test_set_fee_deposit_address_requires_owner_auth() {
719
+ let env = Env::default();
720
+ let setup = ExtensiveOFTTestSetup::new(&env);
721
+
722
+ let fee_collector = create_recipient_address(&env);
723
+
724
+ // Try to set fee deposit address without auth
725
+ let result = setup.oft.try_set_fee_deposit_address(&fee_collector);
726
+ assert!(result.is_err());
727
+ }
728
+
729
+ #[test]
730
+ fn test_set_fee_deposit_address_wrong_auth_fails() {
731
+ let env = Env::default();
732
+ let setup = ExtensiveOFTTestSetup::new(&env);
733
+
734
+ let non_owner = Address::generate(&env);
735
+ let fee_collector = create_recipient_address(&env);
736
+
737
+ // Try to set fee deposit address with wrong auth (non-owner)
738
+ env.mock_auths(&[soroban_sdk::testutils::MockAuth {
739
+ address: &non_owner,
740
+ invoke: &soroban_sdk::testutils::MockAuthInvoke {
741
+ contract: &setup.oft.address,
742
+ fn_name: "set_fee_deposit_address",
743
+ args: (&fee_collector,).into_val(&env),
744
+ sub_invokes: &[],
745
+ },
746
+ }]);
747
+ let result = setup.oft.try_set_fee_deposit_address(&fee_collector);
748
+ assert!(result.is_err());
749
+ }