@paraswap/dex-lib 4.8.27 → 4.8.28-uni-v3-rust.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 (36) hide show
  1. package/build/dex/idex.d.ts +1 -1
  2. package/build/dex/uniswap-v3/contract-math/native-bridge.d.ts +15 -0
  3. package/build/dex/uniswap-v3/contract-math/native-bridge.js +71 -0
  4. package/build/dex/uniswap-v3/contract-math/native-bridge.js.map +1 -0
  5. package/build/dex/uniswap-v3/scripts/measure-calc-time.js +222 -110
  6. package/build/dex/uniswap-v3/scripts/measure-calc-time.js.map +1 -1
  7. package/build/dex/uniswap-v3/uniswap-v3-pool.d.ts +2 -0
  8. package/build/dex/uniswap-v3/uniswap-v3-pool.js +3 -0
  9. package/build/dex/uniswap-v3/uniswap-v3-pool.js.map +1 -1
  10. package/build/dex/uniswap-v3/uniswap-v3.d.ts +8 -3
  11. package/build/dex/uniswap-v3/uniswap-v3.js +8 -5
  12. package/build/dex/uniswap-v3/uniswap-v3.js.map +1 -1
  13. package/build/pricing-helper.d.ts +1 -1
  14. package/build/pricing-helper.js +2 -2
  15. package/build/pricing-helper.js.map +1 -1
  16. package/native/Cargo.lock +279 -0
  17. package/native/Cargo.toml +21 -0
  18. package/native/build.rs +5 -0
  19. package/native/package-lock.json +32 -0
  20. package/native/package.json +20 -0
  21. package/native/src/config.rs +40 -0
  22. package/native/src/lib.rs +216 -0
  23. package/native/src/math/bit_math.rs +177 -0
  24. package/native/src/math/full_math.rs +217 -0
  25. package/native/src/math/liquidity_math.rs +72 -0
  26. package/native/src/math/mod.rs +10 -0
  27. package/native/src/math/oracle.rs +493 -0
  28. package/native/src/math/sqrt_price_math.rs +272 -0
  29. package/native/src/math/swap_math.rs +306 -0
  30. package/native/src/math/tick.rs +239 -0
  31. package/native/src/math/tick_bitmap.rs +292 -0
  32. package/native/src/math/tick_math.rs +321 -0
  33. package/native/src/math/unsafe_math.rs +67 -0
  34. package/native/src/pool_state.rs +36 -0
  35. package/native/src/query_outputs.rs +379 -0
  36. package/package.json +2 -1
@@ -0,0 +1,379 @@
1
+ use ethnum::{I256, U256};
2
+ use std::collections::HashMap;
3
+
4
+ use crate::math::liquidity_math;
5
+ use crate::math::oracle;
6
+ use crate::math::swap_math;
7
+ use crate::math::tick;
8
+ use crate::math::tick::TickInfo;
9
+ use crate::math::tick_bitmap;
10
+ use crate::math::tick_math;
11
+ use crate::pool_state::PoolState;
12
+
13
+ const MAX_PRICING_COMPUTATION_STEPS_ALLOWED: i32 = 128;
14
+
15
+ /// 0 = SELL, 1 = BUY (matches SwapSide enum in TS)
16
+ pub const SWAP_SIDE_SELL: u8 = 0;
17
+
18
+ pub struct OutputResult {
19
+ pub outputs: Vec<U256>,
20
+ pub tick_counts: Vec<i32>,
21
+ }
22
+
23
+ #[derive(Clone)]
24
+ struct PriceComputationState {
25
+ amount_specified_remaining: I256,
26
+ amount_calculated: I256,
27
+ sqrt_price_x96: U256,
28
+ tick: I256,
29
+ protocol_fee: U256,
30
+ liquidity: U256,
31
+ is_first_cycle_state: bool,
32
+ }
33
+
34
+ #[derive(Clone)]
35
+ struct PriceComputationCache {
36
+ liquidity_start: U256,
37
+ block_timestamp: U256,
38
+ fee_protocol: U256,
39
+ seconds_per_liquidity_cumulative_x128: U256,
40
+ tick_cumulative: I256,
41
+ computed_latest_observation: bool,
42
+ tick_count: i32,
43
+ }
44
+
45
+ struct Slot0Snapshot {
46
+ sqrt_price_x96: U256,
47
+ tick: I256,
48
+ observation_index: u16,
49
+ observation_cardinality: u16,
50
+ }
51
+
52
+ fn price_computation_cycles(
53
+ pool: &PoolState,
54
+ ticks_copy: &mut HashMap<i32, TickInfo>,
55
+ slot0_start: &Slot0Snapshot,
56
+ state: &mut PriceComputationState,
57
+ cache: &mut PriceComputationCache,
58
+ sqrt_price_limit_x96: U256,
59
+ zero_for_one: bool,
60
+ exact_input: bool,
61
+ is_sell: bool,
62
+ ) -> (PriceComputationState, PriceComputationCache) {
63
+ let mut latest_full_cycle_state = state.clone();
64
+
65
+ if cache.tick_count == 0 {
66
+ cache.tick_count = 1;
67
+ }
68
+ let mut latest_full_cycle_cache = cache.clone();
69
+
70
+ let mut last_ticks_copy: Option<(i32, TickInfo)> = None;
71
+
72
+ let mut i: i32 = 0;
73
+ while state.amount_specified_remaining != I256::ZERO
74
+ && state.sqrt_price_x96 != sqrt_price_limit_x96
75
+ {
76
+ if latest_full_cycle_cache.tick_count + i > MAX_PRICING_COMPUTATION_STEPS_ALLOWED {
77
+ state.amount_specified_remaining = I256::ZERO;
78
+ state.amount_calculated = I256::ZERO;
79
+ break;
80
+ }
81
+
82
+ let sqrt_price_start_x96 = state.sqrt_price_x96;
83
+
84
+ // Find next initialized tick — may panic if out of range
85
+ let bitmap_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
86
+ tick_bitmap::next_initialized_tick_within_one_word(
87
+ &pool.tick_bitmap,
88
+ state.tick,
89
+ pool.tick_spacing,
90
+ zero_for_one,
91
+ true, // is_price_query
92
+ )
93
+ }));
94
+
95
+ let (tick_next_raw, initialized) = match bitmap_result {
96
+ Ok(result) => result,
97
+ Err(_) => {
98
+ // Out of range — zero out remaining
99
+ state.amount_specified_remaining = I256::ZERO;
100
+ state.amount_calculated = I256::ZERO;
101
+ break;
102
+ }
103
+ };
104
+
105
+ // Clamp to min/max tick
106
+ let tick_next = if tick_next_raw < tick_math::MIN_TICK {
107
+ tick_math::MIN_TICK
108
+ } else if tick_next_raw > tick_math::MAX_TICK {
109
+ tick_math::MAX_TICK
110
+ } else {
111
+ tick_next_raw
112
+ };
113
+
114
+ let sqrt_price_next_x96 = tick_math::get_sqrt_ratio_at_tick(tick_next);
115
+
116
+ // Determine target price (clamped by limit)
117
+ let sqrt_ratio_target = if zero_for_one {
118
+ if sqrt_price_next_x96 < sqrt_price_limit_x96 {
119
+ sqrt_price_limit_x96
120
+ } else {
121
+ sqrt_price_next_x96
122
+ }
123
+ } else {
124
+ if sqrt_price_next_x96 > sqrt_price_limit_x96 {
125
+ sqrt_price_limit_x96
126
+ } else {
127
+ sqrt_price_next_x96
128
+ }
129
+ };
130
+
131
+ let step_result = swap_math::compute_swap_step(
132
+ state.sqrt_price_x96,
133
+ sqrt_ratio_target,
134
+ state.liquidity,
135
+ state.amount_specified_remaining,
136
+ pool.fee,
137
+ );
138
+
139
+ state.sqrt_price_x96 = step_result.sqrt_ratio_next_x96;
140
+ let amount_in = step_result.amount_in;
141
+ let amount_out = step_result.amount_out;
142
+ let mut fee_amount = step_result.fee_amount;
143
+
144
+ if exact_input {
145
+ state.amount_specified_remaining -=
146
+ amount_in.as_i256() + fee_amount.as_i256();
147
+ state.amount_calculated -= amount_out.as_i256();
148
+ } else {
149
+ state.amount_specified_remaining += amount_out.as_i256();
150
+ state.amount_calculated +=
151
+ amount_in.as_i256() + fee_amount.as_i256();
152
+ }
153
+
154
+ if cache.fee_protocol > U256::ZERO {
155
+ let delta = fee_amount / cache.fee_protocol;
156
+ fee_amount -= delta;
157
+ state.protocol_fee += delta;
158
+ }
159
+
160
+ if state.sqrt_price_x96 == sqrt_price_next_x96 {
161
+ if initialized {
162
+ if !cache.computed_latest_observation {
163
+ let (tc, splc) = oracle::observe_single(
164
+ &pool.observations,
165
+ cache.block_timestamp,
166
+ U256::ZERO,
167
+ pool.block_timestamp,
168
+ slot0_start.tick,
169
+ slot0_start.observation_index,
170
+ cache.liquidity_start,
171
+ slot0_start.observation_cardinality,
172
+ );
173
+ cache.tick_cumulative = tc;
174
+ cache.seconds_per_liquidity_cumulative_x128 = splc;
175
+ cache.computed_latest_observation = true;
176
+ }
177
+
178
+ if state.amount_specified_remaining == I256::ZERO {
179
+ let tick_idx = tick_next.as_i32();
180
+ if let Some(existing) = ticks_copy.get(&tick_idx) {
181
+ last_ticks_copy = Some((tick_idx, existing.clone()));
182
+ }
183
+ }
184
+
185
+ let mut liquidity_net = tick::cross(ticks_copy, tick_next.as_i32());
186
+ if zero_for_one {
187
+ liquidity_net = -liquidity_net;
188
+ }
189
+
190
+ state.liquidity = liquidity_math::add_delta(state.liquidity, liquidity_net);
191
+ }
192
+
193
+ state.tick = if zero_for_one {
194
+ tick_next - I256::ONE
195
+ } else {
196
+ tick_next
197
+ };
198
+ } else if state.sqrt_price_x96 != sqrt_price_start_x96 {
199
+ state.tick = tick_math::get_tick_at_sqrt_ratio(state.sqrt_price_x96);
200
+ }
201
+
202
+ if state.amount_specified_remaining != I256::ZERO {
203
+ latest_full_cycle_state = state.clone();
204
+ latest_full_cycle_cache = cache.clone();
205
+ } else if let Some((idx, tick_info)) = last_ticks_copy.take() {
206
+ ticks_copy.insert(idx, tick_info);
207
+ }
208
+
209
+ i += 1;
210
+ }
211
+
212
+ if i > 1 {
213
+ latest_full_cycle_cache.tick_count += i - 1;
214
+ }
215
+
216
+ if state.amount_specified_remaining != I256::ZERO && !is_sell {
217
+ // BUY side: zero out remaining
218
+ state.amount_specified_remaining = I256::ZERO;
219
+ state.amount_calculated = I256::ZERO;
220
+ }
221
+
222
+ (latest_full_cycle_state, latest_full_cycle_cache)
223
+ }
224
+
225
+ /// Main pricing entry point. Equivalent to UniswapV3Math.queryOutputs() in TS.
226
+ pub fn query_outputs(
227
+ pool: &PoolState,
228
+ amounts: &[U256],
229
+ zero_for_one: bool,
230
+ side: u8,
231
+ ) -> OutputResult {
232
+ let is_sell = side == SWAP_SIDE_SELL;
233
+
234
+ let slot0_start = Slot0Snapshot {
235
+ sqrt_price_x96: pool.sqrt_price_x96,
236
+ tick: pool.tick,
237
+ observation_index: pool.observation_index,
238
+ observation_cardinality: pool.observation_cardinality,
239
+ };
240
+
241
+ let sqrt_price_limit_x96 = if zero_for_one {
242
+ tick_math::MIN_SQRT_RATIO + U256::ONE
243
+ } else {
244
+ tick_math::MAX_SQRT_RATIO - U256::ONE
245
+ };
246
+
247
+ let fee_protocol = pool.variant.fee_protocol(pool.fee_protocol, zero_for_one);
248
+
249
+ let mut cache = PriceComputationCache {
250
+ liquidity_start: pool.liquidity,
251
+ block_timestamp: pool.block_timestamp & U256::from(0xFFFFFFFFu32),
252
+ fee_protocol,
253
+ seconds_per_liquidity_cumulative_x128: U256::ZERO,
254
+ tick_cumulative: I256::ZERO,
255
+ computed_latest_observation: false,
256
+ tick_count: 0,
257
+ };
258
+
259
+ let mut state = PriceComputationState {
260
+ amount_specified_remaining: I256::ZERO,
261
+ amount_calculated: I256::ZERO,
262
+ sqrt_price_x96: slot0_start.sqrt_price_x96,
263
+ tick: slot0_start.tick,
264
+ protocol_fee: U256::ZERO,
265
+ liquidity: cache.liquidity_start,
266
+ is_first_cycle_state: true,
267
+ };
268
+
269
+ // Verify price limit
270
+ if zero_for_one {
271
+ assert!(
272
+ sqrt_price_limit_x96 < slot0_start.sqrt_price_x96
273
+ && sqrt_price_limit_x96 > tick_math::MIN_SQRT_RATIO,
274
+ "SPL"
275
+ );
276
+ } else {
277
+ assert!(
278
+ sqrt_price_limit_x96 > slot0_start.sqrt_price_x96
279
+ && sqrt_price_limit_x96 < tick_math::MAX_SQRT_RATIO,
280
+ "SPL"
281
+ );
282
+ }
283
+
284
+ let mut is_out_of_range = false;
285
+ let mut previous_amount = I256::ZERO;
286
+
287
+ let mut outputs = vec![U256::ZERO; amounts.len()];
288
+ let mut tick_counts = vec![0i32; amounts.len()];
289
+
290
+ // We use a mutable copy of ticks for cross() mutations during pricing
291
+ let mut ticks_copy = pool.ticks.clone();
292
+
293
+ for (i, &amount) in amounts.iter().enumerate() {
294
+ if amount == U256::ZERO {
295
+ outputs[i] = U256::ZERO;
296
+ tick_counts[i] = 0;
297
+ continue;
298
+ }
299
+
300
+ // BigInt.asIntN(256, amount) — reinterpret U256 bits as I256
301
+ let amount_as_i256 = amount.as_i256();
302
+ let amount_specified = if is_sell {
303
+ amount_as_i256
304
+ } else {
305
+ -amount_as_i256
306
+ };
307
+
308
+ if state.is_first_cycle_state {
309
+ state.amount_specified_remaining = amount_specified;
310
+ state.is_first_cycle_state = false;
311
+ } else {
312
+ state.amount_specified_remaining =
313
+ amount_specified - (previous_amount - state.amount_specified_remaining);
314
+ }
315
+
316
+ let exact_input = amount_specified > I256::ZERO;
317
+
318
+ if !is_out_of_range {
319
+ let (latest_full_cycle_state, latest_full_cycle_cache) = price_computation_cycles(
320
+ pool,
321
+ &mut ticks_copy,
322
+ &slot0_start,
323
+ &mut state,
324
+ &mut cache,
325
+ sqrt_price_limit_x96,
326
+ zero_for_one,
327
+ exact_input,
328
+ is_sell,
329
+ );
330
+
331
+ if state.amount_specified_remaining == I256::ZERO
332
+ && state.amount_calculated == I256::ZERO
333
+ {
334
+ is_out_of_range = true;
335
+ outputs[i] = U256::ZERO;
336
+ tick_counts[i] = 0;
337
+ continue;
338
+ }
339
+
340
+ previous_amount = amount_specified;
341
+
342
+ let (amount0, amount1) = if zero_for_one == exact_input {
343
+ (
344
+ amount_specified - state.amount_specified_remaining,
345
+ state.amount_calculated,
346
+ )
347
+ } else {
348
+ (
349
+ state.amount_calculated,
350
+ amount_specified - state.amount_specified_remaining,
351
+ )
352
+ };
353
+
354
+ // Restore state to latest full cycle for next amount
355
+ state = latest_full_cycle_state;
356
+ cache = latest_full_cycle_cache;
357
+
358
+ if is_sell {
359
+ // output = BigInt.asUintN(256, -(zeroForOne ? amount1 : amount0))
360
+ let neg = -(if zero_for_one { amount1 } else { amount0 });
361
+ outputs[i] = neg.as_u256();
362
+ tick_counts[i] = cache.tick_count;
363
+ } else {
364
+ // output = BigInt.asUintN(256, zeroForOne ? amount0 : amount1)
365
+ let val = if zero_for_one { amount0 } else { amount1 };
366
+ outputs[i] = val.as_u256();
367
+ tick_counts[i] = cache.tick_count;
368
+ }
369
+ } else {
370
+ outputs[i] = U256::ZERO;
371
+ tick_counts[i] = 0;
372
+ }
373
+ }
374
+
375
+ OutputResult {
376
+ outputs,
377
+ tick_counts,
378
+ }
379
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paraswap/dex-lib",
3
- "version": "4.8.27",
3
+ "version": "4.8.28-uni-v3-rust.1",
4
4
  "main": "build/index.js",
5
5
  "types": "build/index.d.ts",
6
6
  "repository": "https://github.com/paraswap/paraswap-dex-lib",
@@ -39,6 +39,7 @@
39
39
  "yargs": "^17.0.1"
40
40
  },
41
41
  "scripts": {
42
+ "postinstall": "cd native && npm install && npm run build || true",
42
43
  "init-integration": "ts-node scripts/dex-integration.ts init",
43
44
  "test-integration": "ts-node scripts/dex-integration.ts test",
44
45
  "build": "yarn check:pq && yarn check:es && tsc",