@paraswap/dex-lib 4.8.36 → 4.8.37-native-dex-math.1

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 (59) hide show
  1. package/build/dex/aave-gsm/config.js +2 -2
  2. package/build/dex/idex.d.ts +1 -1
  3. package/build/dex/pancakeswap-v3/pancakeswap-v3-pool.d.ts +2 -0
  4. package/build/dex/pancakeswap-v3/pancakeswap-v3-pool.js +5 -0
  5. package/build/dex/pancakeswap-v3/pancakeswap-v3-pool.js.map +1 -1
  6. package/build/dex/pancakeswap-v3/pancakeswap-v3.d.ts +6 -3
  7. package/build/dex/pancakeswap-v3/pancakeswap-v3.js +85 -9
  8. package/build/dex/pancakeswap-v3/pancakeswap-v3.js.map +1 -1
  9. package/build/dex/solidly-v3/solidly-v3-pool.d.ts +3 -0
  10. package/build/dex/solidly-v3/solidly-v3-pool.js +8 -0
  11. package/build/dex/solidly-v3/solidly-v3-pool.js.map +1 -1
  12. package/build/dex/solidly-v3/solidly-v3.d.ts +5 -2
  13. package/build/dex/solidly-v3/solidly-v3.js +84 -8
  14. package/build/dex/solidly-v3/solidly-v3.js.map +1 -1
  15. package/build/dex/uniswap-v3/scripts/measure-calc-time.js +222 -110
  16. package/build/dex/uniswap-v3/scripts/measure-calc-time.js.map +1 -1
  17. package/build/dex/uniswap-v3/types.d.ts +1 -0
  18. package/build/dex/uniswap-v3/types.js.map +1 -1
  19. package/build/dex/uniswap-v3/uniswap-v3-pool.d.ts +2 -0
  20. package/build/dex/uniswap-v3/uniswap-v3-pool.js +5 -0
  21. package/build/dex/uniswap-v3/uniswap-v3-pool.js.map +1 -1
  22. package/build/dex/uniswap-v3/uniswap-v3.d.ts +11 -3
  23. package/build/dex/uniswap-v3/uniswap-v3.js +86 -9
  24. package/build/dex/uniswap-v3/uniswap-v3.js.map +1 -1
  25. package/build/dex/uniswap-v4/types.d.ts +1 -0
  26. package/build/dex/uniswap-v4/uniswap-v4-pool-manager.d.ts +2 -0
  27. package/build/dex/uniswap-v4/uniswap-v4-pool-manager.js +3 -0
  28. package/build/dex/uniswap-v4/uniswap-v4-pool-manager.js.map +1 -1
  29. package/build/dex/uniswap-v4/uniswap-v4-pool.d.ts +3 -0
  30. package/build/dex/uniswap-v4/uniswap-v4-pool.js +18 -0
  31. package/build/dex/uniswap-v4/uniswap-v4-pool.js.map +1 -1
  32. package/build/dex/uniswap-v4/uniswap-v4.d.ts +3 -1
  33. package/build/dex/uniswap-v4/uniswap-v4.js +74 -14
  34. package/build/dex/uniswap-v4/uniswap-v4.js.map +1 -1
  35. package/build/pricing-helper.d.ts +1 -1
  36. package/build/pricing-helper.js +2 -2
  37. package/build/pricing-helper.js.map +1 -1
  38. package/native/Cargo.lock +331 -0
  39. package/native/Cargo.toml +22 -0
  40. package/native/build.rs +5 -0
  41. package/native/package-lock.json +32 -0
  42. package/native/package.json +20 -0
  43. package/native/src/config.rs +73 -0
  44. package/native/src/lib.rs +528 -0
  45. package/native/src/math/bit_math.rs +177 -0
  46. package/native/src/math/full_math.rs +217 -0
  47. package/native/src/math/liquidity_math.rs +72 -0
  48. package/native/src/math/mod.rs +10 -0
  49. package/native/src/math/oracle.rs +493 -0
  50. package/native/src/math/sqrt_price_math.rs +272 -0
  51. package/native/src/math/swap_math.rs +306 -0
  52. package/native/src/math/tick.rs +239 -0
  53. package/native/src/math/tick_bitmap.rs +312 -0
  54. package/native/src/math/tick_math.rs +321 -0
  55. package/native/src/math/unsafe_math.rs +67 -0
  56. package/native/src/pool_state.rs +41 -0
  57. package/native/src/query_outputs.rs +379 -0
  58. package/native/src/v4_query_outputs.rs +255 -0
  59. package/package.json +2 -1
@@ -0,0 +1,493 @@
1
+ use ethnum::{I256, U256};
2
+ use std::collections::HashMap;
3
+
4
+ // 2^160 - 1: hi_128 = 0xFFFFFFFF, lo_128 = u128::MAX
5
+ const MASK_160: U256 = U256::from_words(0xFFFF_FFFF, u128::MAX);
6
+
7
+ /// An oracle observation.
8
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
9
+ pub struct OracleObservation {
10
+ pub block_timestamp: U256,
11
+ pub tick_cumulative: I256,
12
+ pub seconds_per_liquidity_cumulative_x128: U256,
13
+ pub initialized: bool,
14
+ }
15
+
16
+ impl Default for OracleObservation {
17
+ fn default() -> Self {
18
+ OracleObservation {
19
+ block_timestamp: U256::ZERO,
20
+ tick_cumulative: I256::ZERO,
21
+ seconds_per_liquidity_cumulative_x128: U256::ZERO,
22
+ initialized: false,
23
+ }
24
+ }
25
+ }
26
+
27
+ /// Observation candidate pair used in binary search.
28
+ pub struct OracleObservationCandidates {
29
+ pub before_or_at: OracleObservation,
30
+ pub at_or_after: OracleObservation,
31
+ }
32
+
33
+ /// Transforms a previous observation into a new one, given the time elapsed and the
34
+ /// current tick and liquidity values.
35
+ ///
36
+ /// The `block_timestamp` parameter corresponds to `state.blockTimestamp` in the TS version.
37
+ /// In the original TS, `transform` receives `state` and uses `state.blockTimestamp` for the
38
+ /// output's blockTimestamp. Here we pass it explicitly as `block_timestamp_state`.
39
+ pub fn transform(
40
+ last: &OracleObservation,
41
+ block_timestamp: U256,
42
+ block_timestamp_state: U256,
43
+ tick: I256,
44
+ liquidity: U256,
45
+ ) -> OracleObservation {
46
+ let delta = block_timestamp - last.block_timestamp;
47
+
48
+ // tickCumulative: last.tickCumulative + BigInt.asIntN(56, tick) * delta
49
+ let tick_i56 = sign_extend_i56(tick);
50
+ let delta_signed = delta.as_i256();
51
+ let tick_cumulative = last.tick_cumulative + tick_i56 * delta_signed;
52
+
53
+ // secondsPerLiquidityCumulativeX128:
54
+ // last.spl + (BigInt.asUintN(160, delta) << 128) / (liquidity > 0 ? liquidity : 1)
55
+ let delta_u160 = delta & MASK_160;
56
+ let numerator = delta_u160 << 128;
57
+ let denominator = if liquidity > U256::ZERO {
58
+ liquidity
59
+ } else {
60
+ U256::ONE
61
+ };
62
+ let seconds_per_liquidity_cumulative_x128 =
63
+ last.seconds_per_liquidity_cumulative_x128 + numerator / denominator;
64
+
65
+ OracleObservation {
66
+ block_timestamp: block_timestamp_state,
67
+ tick_cumulative,
68
+ seconds_per_liquidity_cumulative_x128,
69
+ initialized: true,
70
+ }
71
+ }
72
+
73
+ /// Writes an oracle observation to the array, returning the updated index and cardinality.
74
+ ///
75
+ /// `block_timestamp_state` is the state's block timestamp (used for comparison and as the
76
+ /// new observation's timestamp).
77
+ ///
78
+ /// Returns `(updated_index, updated_cardinality)`.
79
+ pub fn write(
80
+ observations: &mut HashMap<u16, OracleObservation>,
81
+ index: u16,
82
+ block_timestamp: U256,
83
+ block_timestamp_state: U256,
84
+ tick: I256,
85
+ liquidity: U256,
86
+ cardinality: u16,
87
+ cardinality_next: u16,
88
+ ) -> (u16, u16) {
89
+ let last = observations
90
+ .get(&index)
91
+ .copied()
92
+ .expect("last observation must exist");
93
+
94
+ // If the block timestamp hasn't changed, no update needed
95
+ if last.block_timestamp == block_timestamp_state {
96
+ return (index, cardinality);
97
+ }
98
+
99
+ let cardinality_updated = if cardinality_next > cardinality && index == cardinality - 1 {
100
+ cardinality_next
101
+ } else {
102
+ cardinality
103
+ };
104
+
105
+ let index_updated = ((index as u32 + 1) % cardinality_updated as u32) as u16;
106
+
107
+ let new_observation = transform(&last, block_timestamp, block_timestamp_state, tick, liquidity);
108
+ observations.insert(index_updated, new_observation);
109
+
110
+ // In the TS code, if indexUpdated !== index, the old index is deleted
111
+ if index_updated != index {
112
+ observations.remove(&index);
113
+ }
114
+
115
+ (index_updated, cardinality_updated)
116
+ }
117
+
118
+ /// Compares two timestamps with overflow-aware less-than-or-equal.
119
+ ///
120
+ /// This handles the uint32 overflow case: if both timestamps are <= time, compare normally.
121
+ /// Otherwise, adjust the one that hasn't overflowed by adding 2^32.
122
+ pub fn lte(time: U256, a: U256, b: U256) -> bool {
123
+ if a <= time && b <= time {
124
+ return a <= b;
125
+ }
126
+
127
+ let two_pow_32 = U256::ONE << 32;
128
+ let a_adjusted = if a > time { a } else { a + two_pow_32 };
129
+ let b_adjusted = if b > time { b } else { b + two_pow_32 };
130
+ a_adjusted <= b_adjusted
131
+ }
132
+
133
+ /// Binary search for the observations surrounding a target timestamp.
134
+ pub fn binary_search(
135
+ observations: &HashMap<u16, OracleObservation>,
136
+ time: U256,
137
+ target: U256,
138
+ index: u16,
139
+ cardinality: u16,
140
+ ) -> OracleObservationCandidates {
141
+ let mut l = ((index as u32 + 1) % cardinality as u32) as u32;
142
+ let mut r = l + cardinality as u32 - 1;
143
+
144
+ loop {
145
+ let i = (l + r) / 2;
146
+ let idx = (i % cardinality as u32) as u16;
147
+ let before_or_at = observations
148
+ .get(&idx)
149
+ .copied()
150
+ .unwrap_or_default();
151
+
152
+ if !before_or_at.initialized {
153
+ l = i + 1;
154
+ continue;
155
+ }
156
+
157
+ let after_idx = ((i + 1) % cardinality as u32) as u16;
158
+ let at_or_after = observations
159
+ .get(&after_idx)
160
+ .copied()
161
+ .unwrap_or_default();
162
+
163
+ let target_at_or_after = lte(time, before_or_at.block_timestamp, target);
164
+
165
+ if target_at_or_after && lte(time, target, at_or_after.block_timestamp) {
166
+ return OracleObservationCandidates {
167
+ before_or_at,
168
+ at_or_after,
169
+ };
170
+ }
171
+
172
+ if !target_at_or_after {
173
+ r = i - 1;
174
+ } else {
175
+ l = i + 1;
176
+ }
177
+ }
178
+ }
179
+
180
+ /// Returns the observations surrounding a target timestamp.
181
+ ///
182
+ /// `block_timestamp_state` is the state's block timestamp used for transform.
183
+ pub fn get_surrounding_observations(
184
+ observations: &HashMap<u16, OracleObservation>,
185
+ time: U256,
186
+ target: U256,
187
+ block_timestamp_state: U256,
188
+ tick: I256,
189
+ index: u16,
190
+ liquidity: U256,
191
+ cardinality: u16,
192
+ ) -> OracleObservationCandidates {
193
+ let before_or_at = observations
194
+ .get(&index)
195
+ .copied()
196
+ .unwrap_or_default();
197
+
198
+ if lte(time, before_or_at.block_timestamp, target) {
199
+ if before_or_at.block_timestamp == target {
200
+ return OracleObservationCandidates {
201
+ before_or_at,
202
+ at_or_after: before_or_at,
203
+ };
204
+ } else {
205
+ let at_or_after = transform(
206
+ &before_or_at,
207
+ target,
208
+ block_timestamp_state,
209
+ tick,
210
+ liquidity,
211
+ );
212
+ return OracleObservationCandidates {
213
+ before_or_at,
214
+ at_or_after,
215
+ };
216
+ }
217
+ }
218
+
219
+ let oldest_idx = ((index as u32 + 1) % cardinality as u32) as u16;
220
+ let mut before_or_at = observations
221
+ .get(&oldest_idx)
222
+ .copied()
223
+ .unwrap_or_default();
224
+
225
+ if !before_or_at.initialized {
226
+ before_or_at = observations.get(&0u16).copied().unwrap_or_default();
227
+ }
228
+
229
+ assert!(
230
+ lte(time, before_or_at.block_timestamp, target),
231
+ "OLD"
232
+ );
233
+
234
+ binary_search(observations, time, target, index, cardinality)
235
+ }
236
+
237
+ /// Returns the accumulator values as of `secondsAgo` seconds ago from the given time.
238
+ ///
239
+ /// `block_timestamp_state` is the state's block timestamp.
240
+ ///
241
+ /// Returns `(tick_cumulative, seconds_per_liquidity_cumulative_x128)`.
242
+ pub fn observe_single(
243
+ observations: &HashMap<u16, OracleObservation>,
244
+ time: U256,
245
+ seconds_ago: U256,
246
+ block_timestamp_state: U256,
247
+ tick: I256,
248
+ index: u16,
249
+ liquidity: U256,
250
+ cardinality: u16,
251
+ ) -> (I256, U256) {
252
+ if seconds_ago == U256::ZERO {
253
+ let mut last = observations
254
+ .get(&index)
255
+ .copied()
256
+ .unwrap_or_default();
257
+ if last.block_timestamp != time {
258
+ last = transform(&last, time, block_timestamp_state, tick, liquidity);
259
+ }
260
+ return (
261
+ last.tick_cumulative,
262
+ last.seconds_per_liquidity_cumulative_x128,
263
+ );
264
+ }
265
+
266
+ let target = time - seconds_ago;
267
+
268
+ let OracleObservationCandidates {
269
+ before_or_at,
270
+ at_or_after,
271
+ } = get_surrounding_observations(
272
+ observations,
273
+ time,
274
+ target,
275
+ block_timestamp_state,
276
+ tick,
277
+ index,
278
+ liquidity,
279
+ cardinality,
280
+ );
281
+
282
+ if target == before_or_at.block_timestamp {
283
+ return (
284
+ before_or_at.tick_cumulative,
285
+ before_or_at.seconds_per_liquidity_cumulative_x128,
286
+ );
287
+ } else if target == at_or_after.block_timestamp {
288
+ return (
289
+ at_or_after.tick_cumulative,
290
+ at_or_after.seconds_per_liquidity_cumulative_x128,
291
+ );
292
+ } else {
293
+ let observation_time_delta =
294
+ at_or_after.block_timestamp - before_or_at.block_timestamp;
295
+ let target_delta = target - before_or_at.block_timestamp;
296
+ let observation_time_delta_signed = observation_time_delta.as_i256();
297
+ let target_delta_signed = target_delta.as_i256();
298
+
299
+ let tick_cumulative = before_or_at.tick_cumulative
300
+ + ((at_or_after.tick_cumulative - before_or_at.tick_cumulative)
301
+ / observation_time_delta_signed)
302
+ * target_delta_signed;
303
+
304
+ // secondsPerLiquidityCumulativeX128 interpolation:
305
+ // beforeOrAt.spl + BigInt.asUintN(160,
306
+ // (BigInt.asUintN(256, atOrAfter.spl - beforeOrAt.spl) * targetDelta) / observationTimeDelta
307
+ // )
308
+ let spl_diff = at_or_after.seconds_per_liquidity_cumulative_x128
309
+ .wrapping_sub(before_or_at.seconds_per_liquidity_cumulative_x128);
310
+ let spl_interpolated = (spl_diff * target_delta) / observation_time_delta;
311
+ let spl_masked = spl_interpolated & MASK_160;
312
+ let seconds_per_liquidity_cumulative_x128 =
313
+ before_or_at.seconds_per_liquidity_cumulative_x128 + spl_masked;
314
+
315
+ return (tick_cumulative, seconds_per_liquidity_cumulative_x128);
316
+ }
317
+ }
318
+
319
+ /// Sign-extend a value to signed 56-bit (equivalent to BigInt.asIntN(56, x)).
320
+ fn sign_extend_i56(val: I256) -> I256 {
321
+ let mask: I256 = (I256::ONE << 56) - I256::ONE;
322
+ let masked = val & mask;
323
+ if masked & (I256::ONE << 55) != I256::ZERO {
324
+ masked | !mask
325
+ } else {
326
+ masked
327
+ }
328
+ }
329
+
330
+ #[cfg(test)]
331
+ mod tests {
332
+ use super::*;
333
+
334
+ fn make_obs(
335
+ block_timestamp: u64,
336
+ tick_cumulative: i64,
337
+ spl: u64,
338
+ initialized: bool,
339
+ ) -> OracleObservation {
340
+ OracleObservation {
341
+ block_timestamp: U256::from(block_timestamp),
342
+ tick_cumulative: I256::from(tick_cumulative),
343
+ seconds_per_liquidity_cumulative_x128: U256::from(spl),
344
+ initialized,
345
+ }
346
+ }
347
+
348
+ #[test]
349
+ fn test_lte_both_below_time() {
350
+ assert!(lte(U256::from(100u64), U256::from(50u64), U256::from(60u64)));
351
+ assert!(!lte(U256::from(100u64), U256::from(60u64), U256::from(50u64)));
352
+ }
353
+
354
+ #[test]
355
+ fn test_lte_equal() {
356
+ assert!(lte(U256::from(100u64), U256::from(50u64), U256::from(50u64)));
357
+ }
358
+
359
+ #[test]
360
+ fn test_lte_overflow_case() {
361
+ // a is past overflow, b is before overflow
362
+ // time = 10, a = 5 (not overflowed), b = 15 (overflowed past time)
363
+ // a <= time? yes. b <= time? no.
364
+ // a_adjusted = 5 + 2^32, b_adjusted = 15
365
+ // 5 + 2^32 > 15, so a is NOT <= b
366
+ assert!(!lte(U256::from(10u64), U256::from(5u64), U256::from(15u64)));
367
+ }
368
+
369
+ #[test]
370
+ fn test_transform_basic() {
371
+ let last = make_obs(100, 5000, 10000, true);
372
+ let result = transform(
373
+ &last,
374
+ U256::from(110u64), // blockTimestamp
375
+ U256::from(110u64), // state.blockTimestamp
376
+ I256::from(50i64), // tick
377
+ U256::from(1000u64), // liquidity
378
+ );
379
+ assert_eq!(result.block_timestamp, U256::from(110u64));
380
+ // tickCumulative = 5000 + 50 * 10 = 5500
381
+ assert_eq!(result.tick_cumulative, I256::from(5500i64));
382
+ assert!(result.initialized);
383
+ }
384
+
385
+ #[test]
386
+ fn test_transform_zero_liquidity() {
387
+ let last = make_obs(100, 0, 0, true);
388
+ let result = transform(
389
+ &last,
390
+ U256::from(110u64),
391
+ U256::from(110u64),
392
+ I256::from(10i64),
393
+ U256::ZERO, // zero liquidity => denominator becomes 1
394
+ );
395
+ // tickCumulative = 0 + 10 * 10 = 100
396
+ assert_eq!(result.tick_cumulative, I256::from(100i64));
397
+ // secondsPerLiquidity = 0 + (10 << 128) / 1 = 10 << 128
398
+ let expected_spl = U256::from(10u64) << 128;
399
+ assert_eq!(result.seconds_per_liquidity_cumulative_x128, expected_spl);
400
+ }
401
+
402
+ #[test]
403
+ fn test_write_same_timestamp_noop() {
404
+ let mut observations = HashMap::new();
405
+ observations.insert(0u16, make_obs(100, 0, 0, true));
406
+
407
+ let (idx, card) = write(
408
+ &mut observations,
409
+ 0, // index
410
+ U256::from(100u64), // blockTimestamp
411
+ U256::from(100u64), // state.blockTimestamp (same)
412
+ I256::from(10i64),
413
+ U256::from(1000u64),
414
+ 1, // cardinality
415
+ 1, // cardinalityNext
416
+ );
417
+ assert_eq!(idx, 0);
418
+ assert_eq!(card, 1);
419
+ }
420
+
421
+ #[test]
422
+ fn test_write_new_observation() {
423
+ let mut observations = HashMap::new();
424
+ observations.insert(0u16, make_obs(100, 0, 0, true));
425
+
426
+ let (idx, card) = write(
427
+ &mut observations,
428
+ 0,
429
+ U256::from(110u64),
430
+ U256::from(110u64),
431
+ I256::from(10i64),
432
+ U256::from(1000u64),
433
+ 1,
434
+ 2,
435
+ );
436
+ // cardinality should increase because cardinalityNext > cardinality and index == cardinality - 1
437
+ assert_eq!(card, 2);
438
+ assert_eq!(idx, 1);
439
+ assert!(observations.get(&1u16).unwrap().initialized);
440
+ }
441
+
442
+ #[test]
443
+ fn test_observe_single_zero_seconds_ago() {
444
+ let mut observations = HashMap::new();
445
+ observations.insert(0u16, make_obs(100, 5000, 10000, true));
446
+
447
+ let (tick_cum, spl) = observe_single(
448
+ &observations,
449
+ U256::from(100u64),
450
+ U256::ZERO,
451
+ U256::from(100u64),
452
+ I256::from(50i64),
453
+ 0,
454
+ U256::from(1000u64),
455
+ 1,
456
+ );
457
+ // secondsAgo == 0 and last.blockTimestamp == time, so return last values directly
458
+ assert_eq!(tick_cum, I256::from(5000i64));
459
+ assert_eq!(spl, U256::from(10000u64));
460
+ }
461
+
462
+ #[test]
463
+ fn test_observe_single_transforms_when_timestamp_differs() {
464
+ let mut observations = HashMap::new();
465
+ observations.insert(0u16, make_obs(100, 5000, 10000, true));
466
+
467
+ let (tick_cum, _spl) = observe_single(
468
+ &observations,
469
+ U256::from(110u64), // current time
470
+ U256::ZERO, // secondsAgo = 0
471
+ U256::from(110u64),
472
+ I256::from(50i64), // current tick
473
+ 0,
474
+ U256::from(1000u64),
475
+ 1,
476
+ );
477
+ // Should transform: tickCumulative = 5000 + 50 * 10 = 5500
478
+ assert_eq!(tick_cum, I256::from(5500i64));
479
+ }
480
+
481
+ #[test]
482
+ fn test_sign_extend_i56_positive() {
483
+ let val = I256::from(42i64);
484
+ assert_eq!(sign_extend_i56(val), I256::from(42i64));
485
+ }
486
+
487
+ #[test]
488
+ fn test_sign_extend_i56_negative() {
489
+ let val = I256::from(-1i64);
490
+ let result = sign_extend_i56(val);
491
+ assert_eq!(result, I256::from(-1i64));
492
+ }
493
+ }