@layerzerolabs/protocol-stellar-v2 0.2.19 → 0.2.20

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 (125) hide show
  1. package/.turbo/turbo-build.log +245 -222
  2. package/.turbo/turbo-lint.log +77 -70
  3. package/.turbo/turbo-test.log +1385 -1221
  4. package/Cargo.lock +13 -3
  5. package/Cargo.toml +2 -0
  6. package/contracts/ERROR_SPEC.md +8 -1
  7. package/contracts/common-macros/src/contract_ttl.rs +18 -7
  8. package/contracts/common-macros/src/lib.rs +4 -4
  9. package/contracts/common-macros/src/tests/contract_ttl.rs +1 -1
  10. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__contract_ttl__snapshot_generated_contractimpl_code.snap +2 -1
  11. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__upgradeable__snapshot_generated_upgradeable_code.snap +7 -12
  12. package/contracts/common-macros/src/upgradeable.rs +15 -21
  13. package/contracts/message-libs/uln-302/src/events.rs +4 -0
  14. package/contracts/message-libs/uln-302/src/send_uln.rs +22 -6
  15. package/contracts/message-libs/uln-302/src/tests/send_uln302/send.rs +38 -64
  16. package/contracts/oapps/counter/Cargo.toml +1 -0
  17. package/contracts/oapps/counter/integration_tests/setup_uln.rs +1 -1
  18. package/contracts/oapps/oapp/src/tests/test_oapp_core.rs +113 -65
  19. package/contracts/oapps/oapp/src/tests/test_oapp_options_type3.rs +111 -82
  20. package/contracts/oapps/oapp/src/tests/test_oapp_receiver.rs +293 -65
  21. package/contracts/oapps/oapp/src/tests/test_oapp_sender.rs +331 -56
  22. package/contracts/oapps/oft/src/extensions/oft_fee.rs +18 -2
  23. package/contracts/oapps/oft/src/extensions/pausable.rs +19 -4
  24. package/contracts/oapps/oft/src/extensions/rate_limiter.rs +52 -28
  25. package/contracts/oapps/oft/src/oft.rs +29 -41
  26. package/contracts/oapps/oft/src/oft_types/mint_burn.rs +3 -25
  27. package/contracts/oapps/oft-core/integration-tests/setup.rs +2 -2
  28. package/contracts/oapps/oft-core/src/oft_core.rs +247 -207
  29. package/contracts/oapps/oft-core/src/tests/test_utils.rs +4 -4
  30. package/contracts/upgrader/src/lib.rs +30 -57
  31. package/contracts/upgrader/src/tests/test_data/test_upgradeable_contract1.wasm +0 -0
  32. package/contracts/upgrader/src/tests/test_data/test_upgradeable_contract2.wasm +0 -0
  33. package/contracts/upgrader/src/tests/test_upgrader.rs +44 -35
  34. package/contracts/utils/src/buffer_reader.rs +1 -0
  35. package/contracts/utils/src/errors.rs +3 -1
  36. package/contracts/utils/src/tests/upgradeable.rs +372 -175
  37. package/contracts/utils/src/ttl_configurable.rs +3 -3
  38. package/contracts/utils/src/upgradeable.rs +48 -23
  39. package/contracts/workers/dvn/Cargo.toml +1 -0
  40. package/contracts/workers/dvn/src/auth.rs +12 -42
  41. package/contracts/workers/dvn/src/dvn.rs +16 -31
  42. package/contracts/workers/dvn/src/errors.rs +0 -1
  43. package/contracts/workers/dvn/src/interfaces/dvn.rs +35 -0
  44. package/contracts/workers/dvn/src/lib.rs +4 -3
  45. package/contracts/workers/dvn/src/tests/auth.rs +1 -1
  46. package/contracts/workers/dvn/src/tests/dvn.rs +19 -15
  47. package/contracts/workers/dvn/src/tests/multisig/set_threshold.rs +2 -4
  48. package/contracts/workers/dvn/src/tests/multisig/verify_signatures.rs +1 -3
  49. package/contracts/workers/dvn/src/tests/setup.rs +5 -9
  50. package/contracts/workers/dvn-fee-lib/Cargo.toml +1 -1
  51. package/contracts/workers/dvn-fee-lib/src/dvn_fee_lib.rs +3 -5
  52. package/contracts/workers/dvn-fee-lib/src/tests/dvn_fee_lib.rs +2 -3
  53. package/contracts/workers/executor/Cargo.toml +1 -0
  54. package/contracts/workers/executor/src/executor.rs +15 -26
  55. package/contracts/workers/executor-fee-lib/Cargo.toml +2 -1
  56. package/contracts/workers/executor-fee-lib/src/executor_fee_lib.rs +63 -5
  57. package/contracts/workers/executor-fee-lib/src/executor_option.rs +28 -1
  58. package/contracts/workers/executor-fee-lib/src/lib.rs +3 -0
  59. package/contracts/workers/executor-fee-lib/src/tests/executor_fee_lib.rs +701 -0
  60. package/contracts/workers/executor-fee-lib/src/tests/executor_option.rs +370 -0
  61. package/contracts/workers/executor-fee-lib/src/tests/mod.rs +4 -0
  62. package/contracts/workers/executor-fee-lib/src/tests/setup.rs +60 -0
  63. package/contracts/workers/executor-helper/src/lib.rs +3 -0
  64. package/contracts/workers/executor-helper/src/tests/executor_helper.rs +184 -0
  65. package/contracts/workers/executor-helper/src/tests/mod.rs +2 -0
  66. package/contracts/workers/executor-helper/src/tests/setup.rs +366 -0
  67. package/contracts/workers/fee-lib-interfaces/Cargo.toml +14 -0
  68. package/contracts/workers/{worker/src/interfaces/mod.rs → fee-lib-interfaces/src/lib.rs} +4 -3
  69. package/contracts/workers/price-feed/Cargo.toml +2 -1
  70. package/contracts/workers/price-feed/src/events.rs +1 -1
  71. package/contracts/workers/price-feed/src/lib.rs +3 -0
  72. package/contracts/workers/price-feed/src/price_feed.rs +6 -12
  73. package/contracts/workers/price-feed/src/storage.rs +1 -1
  74. package/contracts/workers/price-feed/src/tests/mod.rs +2 -0
  75. package/contracts/workers/price-feed/src/tests/price_feed.rs +869 -0
  76. package/contracts/workers/price-feed/src/tests/setup.rs +70 -0
  77. package/contracts/workers/price-feed/src/types.rs +1 -1
  78. package/contracts/workers/worker/src/errors.rs +0 -3
  79. package/contracts/workers/worker/src/lib.rs +0 -2
  80. package/contracts/workers/worker/src/storage.rs +32 -29
  81. package/contracts/workers/worker/src/tests/setup.rs +1 -7
  82. package/contracts/workers/worker/src/tests/worker.rs +50 -42
  83. package/contracts/workers/worker/src/worker.rs +49 -58
  84. package/package.json +3 -3
  85. package/sdk/.turbo/turbo-test.log +220 -218
  86. package/sdk/dist/generated/bml.d.ts +12 -4
  87. package/sdk/dist/generated/bml.js +8 -6
  88. package/sdk/dist/generated/counter.d.ts +12 -4
  89. package/sdk/dist/generated/counter.js +8 -6
  90. package/sdk/dist/generated/dvn.d.ts +404 -365
  91. package/sdk/dist/generated/dvn.js +55 -53
  92. package/sdk/dist/generated/dvn_fee_lib.d.ts +224 -268
  93. package/sdk/dist/generated/dvn_fee_lib.js +22 -53
  94. package/sdk/dist/generated/endpoint.d.ts +12 -4
  95. package/sdk/dist/generated/endpoint.js +8 -6
  96. package/sdk/dist/generated/executor.d.ts +370 -326
  97. package/sdk/dist/generated/executor.js +47 -44
  98. package/sdk/dist/generated/executor_fee_lib.d.ts +258 -302
  99. package/sdk/dist/generated/executor_fee_lib.js +21 -52
  100. package/sdk/dist/generated/executor_helper.d.ts +26 -190
  101. package/sdk/dist/generated/executor_helper.js +22 -27
  102. package/sdk/dist/generated/layerzero_view.d.ts +1271 -0
  103. package/sdk/dist/generated/layerzero_view.js +294 -0
  104. package/sdk/dist/generated/oft.d.ts +49 -40
  105. package/sdk/dist/generated/oft.js +25 -23
  106. package/sdk/dist/generated/price_feed.d.ts +225 -269
  107. package/sdk/dist/generated/price_feed.js +22 -53
  108. package/sdk/dist/generated/sml.d.ts +12 -4
  109. package/sdk/dist/generated/sml.js +8 -6
  110. package/sdk/dist/generated/treasury.d.ts +12 -4
  111. package/sdk/dist/generated/treasury.js +8 -6
  112. package/sdk/dist/generated/uln302.d.ts +12 -4
  113. package/sdk/dist/generated/uln302.js +10 -8
  114. package/sdk/dist/generated/upgrader.d.ts +189 -18
  115. package/sdk/dist/generated/upgrader.js +84 -4
  116. package/sdk/dist/index.d.ts +1 -0
  117. package/sdk/dist/index.js +2 -0
  118. package/sdk/package.json +1 -1
  119. package/sdk/src/index.ts +3 -0
  120. package/sdk/test/oft-sml.test.ts +4 -4
  121. package/sdk/test/upgrader.test.ts +2 -3
  122. package/tools/ts-bindings-gen/src/main.rs +2 -1
  123. /package/contracts/workers/{worker/src/interfaces → fee-lib-interfaces/src}/dvn_fee_lib.rs +0 -0
  124. /package/contracts/workers/{worker/src/interfaces → fee-lib-interfaces/src}/executor_fee_lib.rs +0 -0
  125. /package/contracts/workers/{worker/src/interfaces → fee-lib-interfaces/src}/price_feed.rs +0 -0
@@ -0,0 +1,701 @@
1
+ use fee_lib_interfaces::{FeeEstimate, FeeParams};
2
+ use soroban_sdk::{contract, contractimpl, testutils::Address as _, Address, Bytes, Env};
3
+
4
+ use crate::errors::ExecutorFeeLibError;
5
+ use crate::ExecutorFeeLib;
6
+
7
+ use super::setup::{
8
+ bytes32, option_lz_compose, option_lz_receive, option_native_drop, option_ordered_execution, TestSetup,
9
+ };
10
+
11
+ // Mock contracts
12
+
13
+ #[contract]
14
+ struct MockPriceFeedEchoGas;
15
+
16
+ #[contractimpl]
17
+ impl MockPriceFeedEchoGas {
18
+ // Returns total_gas_fee = gas, ratio=1, denom=1, native_price_usd=0
19
+ pub fn estimate_fee_by_eid(
20
+ _env: &Env,
21
+ _fee_lib: &Address,
22
+ _dst_eid: u32,
23
+ _calldata_size: u32,
24
+ gas: u128,
25
+ ) -> FeeEstimate {
26
+ FeeEstimate { total_gas_fee: gas as i128, price_ratio: 1, price_ratio_denominator: 1, native_price_usd: 0 }
27
+ }
28
+ }
29
+
30
+ #[contract]
31
+ struct MockPriceFeedNegativeFee;
32
+
33
+ #[contractimpl]
34
+ impl MockPriceFeedNegativeFee {
35
+ // Returns negative total_gas_fee to trigger InvalidFee error
36
+ pub fn estimate_fee_by_eid(
37
+ _env: &Env,
38
+ _fee_lib: &Address,
39
+ _dst_eid: u32,
40
+ _calldata_size: u32,
41
+ _gas: u128,
42
+ ) -> FeeEstimate {
43
+ FeeEstimate { total_gas_fee: -1, price_ratio: 1, price_ratio_denominator: 1, native_price_usd: 0 }
44
+ }
45
+ }
46
+
47
+ #[contract]
48
+ struct MockPriceFeedWithRatio;
49
+
50
+ #[contractimpl]
51
+ impl MockPriceFeedWithRatio {
52
+ // Returns configurable values for testing value conversion and margin
53
+ pub fn estimate_fee_by_eid(
54
+ _env: &Env,
55
+ _fee_lib: &Address,
56
+ _dst_eid: u32,
57
+ _calldata_size: u32,
58
+ gas: u128,
59
+ ) -> FeeEstimate {
60
+ FeeEstimate {
61
+ total_gas_fee: gas as i128,
62
+ price_ratio: 20_000_000_000, // 2e10 (2:1 ratio)
63
+ price_ratio_denominator: 10_000_000_000, // 1e10
64
+ native_price_usd: 20_000_000_000_000, // 2000e10
65
+ }
66
+ }
67
+ }
68
+
69
+ // version
70
+
71
+ #[test]
72
+ fn test_version() {
73
+ let setup = TestSetup::new();
74
+ assert_eq!(setup.client.version(), (1, 1));
75
+ }
76
+
77
+ // get_fee
78
+
79
+ #[test]
80
+ fn test_get_fee_basic_lz_receive_only() {
81
+ let setup = TestSetup::new();
82
+ let price_feed = setup.env.register(MockPriceFeedEchoGas, ());
83
+
84
+ // Simple case: only lzReceive gas
85
+ let mut options = Bytes::new(&setup.env);
86
+ options.append(&option_lz_receive(&setup.env, 200_000, None));
87
+
88
+ let params = FeeParams {
89
+ sender: Address::generate(&setup.env),
90
+ dst_eid: 30_000,
91
+ calldata_size: 0,
92
+ options,
93
+ price_feed,
94
+ default_multiplier_bps: 12_000,
95
+ lz_receive_base_gas: 1,
96
+ lz_compose_base_gas: 0,
97
+ floor_margin_usd: 0,
98
+ native_cap: 222_000,
99
+ multiplier_bps: 10_000,
100
+ };
101
+
102
+ // total_gas = base(1) + lzReceive(200,000) = 200,001
103
+ // no value, multiplier=1.0x => fee = 200,001
104
+ let executor = Address::generate(&setup.env);
105
+ let fee = setup.client.get_fee(&executor, &params);
106
+ assert_eq!(fee, 200_001);
107
+ }
108
+
109
+ #[test]
110
+ fn test_get_fee_lz_receive_with_value_on_v2() {
111
+ let setup = TestSetup::new();
112
+ let price_feed = setup.env.register(MockPriceFeedEchoGas, ());
113
+
114
+ // lzReceive with value (32 bytes format) - allowed on V2
115
+ let mut options = Bytes::new(&setup.env);
116
+ options.append(&option_lz_receive(&setup.env, 200_000, Some(5)));
117
+
118
+ let params = FeeParams {
119
+ sender: Address::generate(&setup.env),
120
+ dst_eid: 30_000, // V2
121
+ calldata_size: 0,
122
+ options,
123
+ price_feed,
124
+ default_multiplier_bps: 12_000,
125
+ lz_receive_base_gas: 1,
126
+ lz_compose_base_gas: 0,
127
+ floor_margin_usd: 0,
128
+ native_cap: 222_000,
129
+ multiplier_bps: 10_000,
130
+ };
131
+
132
+ // total_gas = 1 + 200,000 = 200,001
133
+ // total_value = 5, ratio=1, denom=1 => value_fee = 5
134
+ // total = 200,006
135
+ let executor = Address::generate(&setup.env);
136
+ let fee = setup.client.get_fee(&executor, &params);
137
+ assert_eq!(fee, 200_006);
138
+ }
139
+
140
+ #[test]
141
+ fn test_get_fee_multiple_lz_receive_accumulates_gas() {
142
+ let setup = TestSetup::new();
143
+ let price_feed = setup.env.register(MockPriceFeedEchoGas, ());
144
+
145
+ // Multiple lzReceive options should accumulate gas
146
+ let mut options = Bytes::new(&setup.env);
147
+ options.append(&option_lz_receive(&setup.env, 20, None));
148
+ options.append(&option_lz_receive(&setup.env, 30, None));
149
+
150
+ let params = FeeParams {
151
+ sender: Address::generate(&setup.env),
152
+ dst_eid: 30_000,
153
+ calldata_size: 0,
154
+ options,
155
+ price_feed,
156
+ default_multiplier_bps: 12_000,
157
+ lz_receive_base_gas: 1,
158
+ lz_compose_base_gas: 0,
159
+ floor_margin_usd: 0,
160
+ native_cap: 222_000,
161
+ multiplier_bps: 10_000,
162
+ };
163
+
164
+ // total_gas = base(1) + lzReceive(20 + 30) = 51
165
+ let executor = Address::generate(&setup.env);
166
+ let fee = setup.client.get_fee(&executor, &params);
167
+ assert_eq!(fee, 51);
168
+ }
169
+
170
+ #[test]
171
+ fn test_get_fee_multiple_native_drop_accumulates_value() {
172
+ let setup = TestSetup::new();
173
+ let price_feed = setup.env.register(MockPriceFeedEchoGas, ());
174
+
175
+ let receiver1 = bytes32(&setup.env, 0xAA);
176
+ let receiver2 = bytes32(&setup.env, 0xBB);
177
+ let mut options = Bytes::new(&setup.env);
178
+ options.append(&option_lz_receive(&setup.env, 10, None));
179
+ options.append(&option_native_drop(&setup.env, 5, &receiver1));
180
+ options.append(&option_native_drop(&setup.env, 7, &receiver2));
181
+
182
+ let params = FeeParams {
183
+ sender: Address::generate(&setup.env),
184
+ dst_eid: 30_000,
185
+ calldata_size: 0,
186
+ options,
187
+ price_feed,
188
+ default_multiplier_bps: 12_000,
189
+ lz_receive_base_gas: 1,
190
+ lz_compose_base_gas: 0,
191
+ floor_margin_usd: 0,
192
+ native_cap: 222_000,
193
+ multiplier_bps: 10_000,
194
+ };
195
+
196
+ // total_gas = 1 + 10 = 11
197
+ // total_value = 5 + 7 = 12
198
+ // fee = 11 + 12 = 23
199
+ let executor = Address::generate(&setup.env);
200
+ let fee = setup.client.get_fee(&executor, &params);
201
+ assert_eq!(fee, 23);
202
+ }
203
+
204
+ #[test]
205
+ fn test_get_fee_multiple_lz_compose_accumulates_gas_and_value() {
206
+ let setup = TestSetup::new();
207
+ let price_feed = setup.env.register(MockPriceFeedEchoGas, ());
208
+
209
+ let mut options = Bytes::new(&setup.env);
210
+ options.append(&option_lz_receive(&setup.env, 10, None));
211
+ options.append(&option_lz_compose(&setup.env, 0, 20, Some(3)));
212
+ options.append(&option_lz_compose(&setup.env, 1, 30, None));
213
+
214
+ let params = FeeParams {
215
+ sender: Address::generate(&setup.env),
216
+ dst_eid: 30_000,
217
+ calldata_size: 0,
218
+ options,
219
+ price_feed,
220
+ default_multiplier_bps: 12_000,
221
+ lz_receive_base_gas: 1,
222
+ lz_compose_base_gas: 15,
223
+ floor_margin_usd: 0,
224
+ native_cap: 222_000,
225
+ multiplier_bps: 10_000,
226
+ };
227
+
228
+ // total_gas = base(1) + lzReceive(10) + lzCompose(20+30) + compose_base(15*2) = 91
229
+ // total_value = 3
230
+ // fee = 91 + 3 = 94
231
+ let executor = Address::generate(&setup.env);
232
+ let fee = setup.client.get_fee(&executor, &params);
233
+ assert_eq!(fee, 94);
234
+ }
235
+
236
+ #[test]
237
+ fn test_get_fee_ordered_execution_adds_overhead() {
238
+ let setup = TestSetup::new();
239
+ let price_feed = setup.env.register(MockPriceFeedEchoGas, ());
240
+
241
+ let mut options = Bytes::new(&setup.env);
242
+ // lz_receive_gas must be non-zero (use larger gas to show ordered overhead)
243
+ options.append(&option_lz_receive(&setup.env, 9_000, None));
244
+ options.append(&option_ordered_execution(&setup.env));
245
+
246
+ let params = FeeParams {
247
+ sender: Address::generate(&setup.env),
248
+ dst_eid: 30_000,
249
+ calldata_size: 0,
250
+ options,
251
+ price_feed,
252
+ default_multiplier_bps: 12_000,
253
+ lz_receive_base_gas: 1_000,
254
+ lz_compose_base_gas: 0,
255
+ floor_margin_usd: 0,
256
+ native_cap: 222_000,
257
+ multiplier_bps: 10_000,
258
+ };
259
+
260
+ // total_gas = base(1_000) + lzReceive(9_000) = 10_000, with ordered: (10_000 * 102) / 100 = 10_200
261
+ let executor = Address::generate(&setup.env);
262
+ let fee = setup.client.get_fee(&executor, &params);
263
+ assert_eq!(fee, 10_200);
264
+ }
265
+
266
+ #[test]
267
+ fn test_get_fee_computes_gas_and_value_components() {
268
+ let setup = TestSetup::new();
269
+ let price_feed = setup.env.register(MockPriceFeedEchoGas, ());
270
+
271
+ // Build options:
272
+ // - lzReceive gas=9_000, value=0
273
+ // - nativeDrop amount=700
274
+ // - lzCompose index=0 gas=3_000 value=500
275
+ // - ordered execution enabled
276
+ let receiver = bytes32(&setup.env, 0xAB);
277
+ let mut options = Bytes::new(&setup.env);
278
+ options.append(&option_lz_receive(&setup.env, 9_000, None));
279
+ options.append(&option_native_drop(&setup.env, 700, &receiver));
280
+ options.append(&option_lz_compose(&setup.env, 0, 3_000, Some(500)));
281
+ options.append(&option_ordered_execution(&setup.env));
282
+
283
+ let params = FeeParams {
284
+ sender: Address::generate(&setup.env),
285
+ dst_eid: 30_000, // v2
286
+ calldata_size: 0,
287
+ options,
288
+ price_feed,
289
+ default_multiplier_bps: 12_000,
290
+ lz_receive_base_gas: 1_000,
291
+ lz_compose_base_gas: 2_000,
292
+ floor_margin_usd: 0,
293
+ native_cap: 222_000,
294
+ multiplier_bps: 10_000,
295
+ };
296
+
297
+ // total_value = nativeDrop(700) + lzCompose.value(500) = 1_200
298
+ // total_gas before ordered = base_receive(1_000) + lzReceive(9_000) + lzCompose(3_000) + compose_base(2_000*1) = 15_000
299
+ // ordered overhead: (15_000 * 102) / 100 = 15_300
300
+ // MockPriceFeedEchoGas returns total_gas_fee = gas (15_300), ratio=1, denom=1
301
+ // multiplier=1.0x, so gas fee stays 15_300; value fee = 1_200
302
+ // total = 16_500
303
+ let executor = Address::generate(&setup.env);
304
+ let fee = setup.client.get_fee(&executor, &params);
305
+ assert_eq!(fee, 16_500);
306
+ }
307
+
308
+ #[test]
309
+ fn test_get_fee_uses_dst_multiplier_when_nonzero() {
310
+ let setup = TestSetup::new();
311
+ let price_feed = setup.env.register(MockPriceFeedEchoGas, ());
312
+
313
+ let mut options = Bytes::new(&setup.env);
314
+ // lz_receive_gas must be non-zero (use larger gas to show multiplier effect)
315
+ options.append(&option_lz_receive(&setup.env, 9_000, None));
316
+
317
+ let params = FeeParams {
318
+ sender: Address::generate(&setup.env),
319
+ dst_eid: 30_000,
320
+ calldata_size: 0,
321
+ options,
322
+ price_feed,
323
+ default_multiplier_bps: 12_000,
324
+ lz_receive_base_gas: 1_000,
325
+ lz_compose_base_gas: 0,
326
+ floor_margin_usd: 0,
327
+ native_cap: 222_000,
328
+ multiplier_bps: 15_000, // 1.5x
329
+ };
330
+
331
+ // total_gas = base(1_000) + lzReceive(9_000) = 10_000, multiplier = 1.5x => (10_000 * 15000) / 10000 = 15_000
332
+ let executor = Address::generate(&setup.env);
333
+ let fee = setup.client.get_fee(&executor, &params);
334
+ assert_eq!(fee, 15_000);
335
+ }
336
+
337
+ #[test]
338
+ fn test_get_fee_uses_default_multiplier_when_multiplier_bps_is_zero() {
339
+ let setup = TestSetup::new();
340
+ let price_feed = setup.env.register(MockPriceFeedEchoGas, ());
341
+
342
+ let mut options = Bytes::new(&setup.env);
343
+ // lz_receive_gas must be non-zero (use larger gas to show multiplier effect)
344
+ options.append(&option_lz_receive(&setup.env, 9_000, None));
345
+
346
+ let params = FeeParams {
347
+ sender: Address::generate(&setup.env),
348
+ dst_eid: 30_000,
349
+ calldata_size: 0,
350
+ options,
351
+ price_feed,
352
+ default_multiplier_bps: 12_000, // 1.2x
353
+ lz_receive_base_gas: 1_000,
354
+ lz_compose_base_gas: 0,
355
+ floor_margin_usd: 0,
356
+ native_cap: 222_000,
357
+ multiplier_bps: 0, // Use default
358
+ };
359
+
360
+ // total_gas = base(1_000) + lzReceive(9_000) = 10_000, multiplier = 1.2x => (10_000 * 12000) / 10000 = 12_000
361
+ let executor = Address::generate(&setup.env);
362
+ let fee = setup.client.get_fee(&executor, &params);
363
+ assert_eq!(fee, 12_000);
364
+ }
365
+
366
+ #[test]
367
+ fn test_get_fee_converts_and_multiplies_native_value() {
368
+ let setup = TestSetup::new();
369
+ let price_feed = setup.env.register(MockPriceFeedWithRatio, ());
370
+
371
+ let receiver = bytes32(&setup.env, 0xAB);
372
+ let mut options = Bytes::new(&setup.env);
373
+ // lz_receive_gas must be non-zero
374
+ options.append(&option_lz_receive(&setup.env, 1, None));
375
+ options.append(&option_native_drop(&setup.env, 10, &receiver));
376
+
377
+ let params = FeeParams {
378
+ sender: Address::generate(&setup.env),
379
+ dst_eid: 30_000,
380
+ calldata_size: 0,
381
+ options,
382
+ price_feed,
383
+ default_multiplier_bps: 12_000,
384
+ lz_receive_base_gas: 1,
385
+ lz_compose_base_gas: 0,
386
+ floor_margin_usd: 0,
387
+ native_cap: 222_000,
388
+ multiplier_bps: 10_000, // 1.0x
389
+ };
390
+
391
+ // total_gas = base(1) + lzReceive(1) = 2, gas_fee = 2
392
+ // total_value = 10, ratio=2e10, denom=1e10 => converted = (10 * 2e10) / 1e10 = 20
393
+ // value_fee = (20 * 10000) / 10000 = 20
394
+ // total = 2 (gas) + 20 (value) = 22
395
+ let executor = Address::generate(&setup.env);
396
+ let fee = setup.client.get_fee(&executor, &params);
397
+ assert_eq!(fee, 22);
398
+ }
399
+
400
+ #[test]
401
+ fn test_get_fee_applies_floor_margin_when_gas_fee_is_low() {
402
+ let setup = TestSetup::new();
403
+ let price_feed = setup.env.register(MockPriceFeedWithRatio, ());
404
+
405
+ let mut options = Bytes::new(&setup.env);
406
+ // lz_receive_gas must be non-zero
407
+ options.append(&option_lz_receive(&setup.env, 1, None));
408
+
409
+ let params = FeeParams {
410
+ sender: Address::generate(&setup.env),
411
+ dst_eid: 30_000,
412
+ calldata_size: 0,
413
+ options,
414
+ price_feed,
415
+ default_multiplier_bps: 12_000,
416
+ lz_receive_base_gas: 1,
417
+ lz_compose_base_gas: 0,
418
+ floor_margin_usd: 30_000_000_000, // 3e10
419
+ native_cap: 222_000,
420
+ multiplier_bps: 10_000,
421
+ };
422
+
423
+ // total_gas = 2, gas_fee = 2
424
+ // margin_usd=3e10, native_price_usd=2000e10 => margin_in_native = (3e10 * 10^7) / 2000e10 = 15000
425
+ // fee_with_margin = 2 + 15000 = 15002, fee_with_multiplier = 2
426
+ let executor = Address::generate(&setup.env);
427
+ let fee = setup.client.get_fee(&executor, &params);
428
+ assert_eq!(fee, 15002);
429
+ }
430
+
431
+ #[test]
432
+ fn test_get_fee_multiplier_wins_over_floor_margin() {
433
+ let setup = TestSetup::new();
434
+ let price_feed = setup.env.register(MockPriceFeedWithRatio, ());
435
+
436
+ let mut options = Bytes::new(&setup.env);
437
+ // lz_receive_gas must be non-zero
438
+ options.append(&option_lz_receive(&setup.env, 100_000, None));
439
+
440
+ let params = FeeParams {
441
+ sender: Address::generate(&setup.env),
442
+ dst_eid: 30_000,
443
+ calldata_size: 0,
444
+ options,
445
+ price_feed,
446
+ default_multiplier_bps: 12_000,
447
+ lz_receive_base_gas: 1,
448
+ lz_compose_base_gas: 0,
449
+ floor_margin_usd: 30_000_000_000, // 3e10
450
+ native_cap: 222_000,
451
+ multiplier_bps: 15_000,
452
+ };
453
+
454
+ // total_gas = 100,001, gas_fee = 100,001
455
+ // margin_usd=3e10, native_price_usd=2000e10 => margin_in_native = 15000
456
+ // fee_with_margin = 100,001 + 15000 = 115,001
457
+ // fee_with_multiplier = (100,001 * 15000) / 10000 = 150,001
458
+ let executor = Address::generate(&setup.env);
459
+ let fee = setup.client.get_fee(&executor, &params);
460
+ assert_eq!(fee, 150_001);
461
+ }
462
+
463
+ #[test]
464
+ fn test_get_fee_errors_eid_not_supported_when_base_gas_zero() {
465
+ let setup = TestSetup::new();
466
+
467
+ let params = FeeParams {
468
+ sender: Address::generate(&setup.env),
469
+ dst_eid: 30_000,
470
+ calldata_size: 0,
471
+ options: Bytes::new(&setup.env),
472
+ price_feed: Address::generate(&setup.env),
473
+ default_multiplier_bps: 12_000,
474
+ lz_receive_base_gas: 0,
475
+ lz_compose_base_gas: 0,
476
+ floor_margin_usd: 0,
477
+ native_cap: 0,
478
+ multiplier_bps: 0,
479
+ };
480
+
481
+ let executor = Address::generate(&setup.env);
482
+ assert_eq!(
483
+ setup.client.try_get_fee(&executor, &params).unwrap_err().unwrap(),
484
+ ExecutorFeeLibError::EidNotSupported.into()
485
+ );
486
+ }
487
+
488
+ #[test]
489
+ fn test_get_fee_errors_no_options() {
490
+ let setup = TestSetup::new();
491
+
492
+ let params = FeeParams {
493
+ sender: Address::generate(&setup.env),
494
+ dst_eid: 30_000,
495
+ calldata_size: 0,
496
+ options: Bytes::new(&setup.env),
497
+ price_feed: Address::generate(&setup.env), // not called (fails during option parsing)
498
+ default_multiplier_bps: 12_000,
499
+ lz_receive_base_gas: 1,
500
+ lz_compose_base_gas: 0,
501
+ floor_margin_usd: 0,
502
+ native_cap: 1_000,
503
+ multiplier_bps: 10_000,
504
+ };
505
+
506
+ let executor = Address::generate(&setup.env);
507
+ assert_eq!(
508
+ setup.client.try_get_fee(&executor, &params).unwrap_err().unwrap(),
509
+ ExecutorFeeLibError::NoOptions.into()
510
+ );
511
+ }
512
+
513
+ #[test]
514
+ fn test_get_fee_errors_invalid_fee_when_price_feed_returns_negative_total_gas_fee() {
515
+ let setup = TestSetup::new();
516
+ let price_feed = setup.env.register(MockPriceFeedNegativeFee, ());
517
+
518
+ let mut options = Bytes::new(&setup.env);
519
+ options.append(&option_lz_receive(&setup.env, 1, None));
520
+
521
+ let params = FeeParams {
522
+ sender: Address::generate(&setup.env),
523
+ dst_eid: 30_000,
524
+ calldata_size: 0,
525
+ options,
526
+ price_feed,
527
+ default_multiplier_bps: 12_000,
528
+ lz_receive_base_gas: 1,
529
+ lz_compose_base_gas: 0,
530
+ floor_margin_usd: 0,
531
+ native_cap: 0,
532
+ multiplier_bps: 10_000,
533
+ };
534
+
535
+ let executor = Address::generate(&setup.env);
536
+ assert_eq!(
537
+ setup.client.try_get_fee(&executor, &params).unwrap_err().unwrap(),
538
+ ExecutorFeeLibError::InvalidFee.into()
539
+ );
540
+ }
541
+
542
+ // internal helpers
543
+
544
+ #[test]
545
+ fn test_is_v1_eid() {
546
+ let env = Env::default();
547
+
548
+ // V1: eid < 30000
549
+ assert_eq!(ExecutorFeeLib::is_v1_eid_for_test(&env, 0), true);
550
+ assert_eq!(ExecutorFeeLib::is_v1_eid_for_test(&env, 1), true);
551
+ assert_eq!(ExecutorFeeLib::is_v1_eid_for_test(&env, 29_999), true);
552
+
553
+ // V2: eid >= 30000
554
+ assert_eq!(ExecutorFeeLib::is_v1_eid_for_test(&env, 30_000), false);
555
+ assert_eq!(ExecutorFeeLib::is_v1_eid_for_test(&env, 30_001), false);
556
+ }
557
+
558
+ #[test]
559
+ fn test_safe_u128_to_i128() {
560
+ let env = Env::default();
561
+
562
+ assert_eq!(ExecutorFeeLib::safe_u128_to_i128_for_test(&env, 0), 0);
563
+ assert_eq!(ExecutorFeeLib::safe_u128_to_i128_for_test(&env, 123), 123);
564
+ assert_eq!(ExecutorFeeLib::safe_u128_to_i128_for_test(&env, i128::MAX as u128), i128::MAX);
565
+ }
566
+
567
+ #[test]
568
+ #[should_panic(expected = "Error(Contract, #9)")] // ExecutorFeeLibError::Overflow
569
+ fn test_safe_u128_to_i128_overflow() {
570
+ let env = Env::default();
571
+ // Value exceeds i128::MAX, should panic with Overflow error
572
+ let _ = ExecutorFeeLib::safe_u128_to_i128_for_test(&env, (i128::MAX as u128) + 1);
573
+ }
574
+
575
+ #[test]
576
+ fn test_get_effective_multiplier_bps() {
577
+ let env = Env::default();
578
+
579
+ // When multiplier_bps == 0, use default
580
+ let params = FeeParams {
581
+ sender: Address::generate(&env),
582
+ dst_eid: 1,
583
+ calldata_size: 1,
584
+ options: Bytes::new(&env),
585
+ price_feed: Address::generate(&env),
586
+ default_multiplier_bps: 12_000,
587
+ lz_receive_base_gas: 1,
588
+ lz_compose_base_gas: 0,
589
+ floor_margin_usd: 0,
590
+ native_cap: 0,
591
+ multiplier_bps: 0,
592
+ };
593
+ assert_eq!(ExecutorFeeLib::get_effective_multiplier_bps_for_test(&env, &params), 12_000);
594
+
595
+ // When multiplier_bps != 0, use it instead of default
596
+ let params = FeeParams { multiplier_bps: 15_000, ..params };
597
+ assert_eq!(ExecutorFeeLib::get_effective_multiplier_bps_for_test(&env, &params), 15_000);
598
+ }
599
+
600
+ #[test]
601
+ fn test_apply_premium_to_gas_multiplier_only() {
602
+ let env = Env::default();
603
+
604
+ // When native_price_usd == 0, only apply fee_with_multiplier
605
+ let fee = ExecutorFeeLib::apply_premium_to_gas_for_test(&env, 100, 12_000, 10, 0);
606
+ assert_eq!(fee, 120); // (100 * 12000) / 10000 = 120
607
+
608
+ // When margin_usd == 0, only apply fee_with_multiplier
609
+ let fee = ExecutorFeeLib::apply_premium_to_gas_for_test(&env, 100, 12_000, 0, 20_000_000_000_000);
610
+ assert_eq!(fee, 120);
611
+ }
612
+
613
+ #[test]
614
+ fn test_apply_premium_to_gas_margin_wins() {
615
+ let env = Env::default();
616
+
617
+ // margin_usd=3e10, native_price_usd=2000e10
618
+ // margin_in_native = (3e10 * 10^7) / 2000e10 = 15000
619
+ // fee_with_margin = 15000 + 100 = 15100
620
+ // fee_with_multiplier = (100 * 10000) / 10000 = 100
621
+ // max(15100, 100) = 15100
622
+ let fee = ExecutorFeeLib::apply_premium_to_gas_for_test(&env, 100, 10_000, 30_000_000_000, 20_000_000_000_000);
623
+ assert_eq!(fee, 15100);
624
+ }
625
+
626
+ #[test]
627
+ fn test_apply_premium_to_gas_multiplier_wins() {
628
+ let env = Env::default();
629
+
630
+ // margin_usd=1e9, native_price_usd=2000e10 => margin_in_native = (1e9 * 10^7) / 2000e10 = 500
631
+ // fee_with_margin = 500 + 100 = 600
632
+ // fee_with_multiplier = (100 * 15000) / 10000 = 150
633
+ // In this case margin wins if we use fee=100.
634
+ // Let's use fee=2000.
635
+ // fee=2000, multiplier=1.5x => 3000
636
+ // fee_with_margin = 500 + 2000 = 2500
637
+ // max(2500, 3000) = 3000
638
+ let fee = ExecutorFeeLib::apply_premium_to_gas_for_test(&env, 2000, 15_000, 1_000_000_000, 20_000_000_000_000);
639
+ assert_eq!(fee, 3000);
640
+ }
641
+
642
+ #[test]
643
+ fn test_convert_and_apply_premium_to_value() {
644
+ let env = Env::default();
645
+
646
+ // When value == 0, return 0 immediately
647
+ let fee =
648
+ ExecutorFeeLib::convert_and_apply_premium_to_value_for_test(&env, 0, 10_000_000_000, 10_000_000_000, 12_000);
649
+ assert_eq!(fee, 0);
650
+
651
+ // Normal case: value=10, ratio=2:1, multiplier=1.2x
652
+ // converted = (10 * 2e10) / 1e10 = 20
653
+ // fee = (20 * 12000) / 10000 = 24
654
+ let fee =
655
+ ExecutorFeeLib::convert_and_apply_premium_to_value_for_test(&env, 10, 20_000_000_000, 10_000_000_000, 12_000);
656
+ assert_eq!(fee, 24);
657
+ }
658
+
659
+ #[test]
660
+ fn test_decode_executor_options() {
661
+ let env = Env::default();
662
+
663
+ // Only lzReceive
664
+ let mut options = Bytes::new(&env);
665
+ options.append(&option_lz_receive(&env, 50, None));
666
+ let (value, gas) = ExecutorFeeLib::decode_executor_options_for_test(&env, &options, 30_000, 1, 20, 222_000);
667
+ assert_eq!(value, 0);
668
+ assert_eq!(gas, 51); // base(1) + lzReceive(50)
669
+
670
+ // lzReceive with value
671
+ let mut options = Bytes::new(&env);
672
+ options.append(&option_lz_receive(&env, 50, Some(25)));
673
+ let (value, gas) = ExecutorFeeLib::decode_executor_options_for_test(&env, &options, 30_000, 1, 20, 222_000);
674
+ assert_eq!(value, 25);
675
+ assert_eq!(gas, 51);
676
+
677
+ // With nativeDrop
678
+ let receiver = bytes32(&env, 0xAB);
679
+ let mut options = Bytes::new(&env);
680
+ options.append(&option_lz_receive(&env, 10, None));
681
+ options.append(&option_native_drop(&env, 30, &receiver));
682
+ let (value, gas) = ExecutorFeeLib::decode_executor_options_for_test(&env, &options, 30_000, 1, 20, 222_000);
683
+ assert_eq!(value, 30);
684
+ assert_eq!(gas, 11);
685
+
686
+ // With lzCompose (gas + value + compose_base)
687
+ let mut options = Bytes::new(&env);
688
+ options.append(&option_lz_receive(&env, 10, None));
689
+ options.append(&option_lz_compose(&env, 0, 30, Some(5)));
690
+ let (value, gas) = ExecutorFeeLib::decode_executor_options_for_test(&env, &options, 30_000, 1, 20, 222_000);
691
+ assert_eq!(value, 5);
692
+ assert_eq!(gas, 61); // base(1) + lzReceive(10) + lzCompose(30) + compose_base(20*1)
693
+
694
+ // With ordered execution overhead (lz_receive_gas must be non-zero)
695
+ let mut options = Bytes::new(&env);
696
+ options.append(&option_lz_receive(&env, 9_000, None));
697
+ options.append(&option_ordered_execution(&env));
698
+ let (value, gas) = ExecutorFeeLib::decode_executor_options_for_test(&env, &options, 30_000, 1_000, 0, 222_000);
699
+ assert_eq!(value, 0);
700
+ assert_eq!(gas, 10_200); // ((1_000 + 9_000) * 102) / 100 = 10_200
701
+ }