@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.
- package/build/dex/idex.d.ts +1 -1
- package/build/dex/uniswap-v3/contract-math/native-bridge.d.ts +15 -0
- package/build/dex/uniswap-v3/contract-math/native-bridge.js +71 -0
- package/build/dex/uniswap-v3/contract-math/native-bridge.js.map +1 -0
- package/build/dex/uniswap-v3/scripts/measure-calc-time.js +222 -110
- package/build/dex/uniswap-v3/scripts/measure-calc-time.js.map +1 -1
- package/build/dex/uniswap-v3/uniswap-v3-pool.d.ts +2 -0
- package/build/dex/uniswap-v3/uniswap-v3-pool.js +3 -0
- package/build/dex/uniswap-v3/uniswap-v3-pool.js.map +1 -1
- package/build/dex/uniswap-v3/uniswap-v3.d.ts +8 -3
- package/build/dex/uniswap-v3/uniswap-v3.js +8 -5
- package/build/dex/uniswap-v3/uniswap-v3.js.map +1 -1
- package/build/pricing-helper.d.ts +1 -1
- package/build/pricing-helper.js +2 -2
- package/build/pricing-helper.js.map +1 -1
- package/native/Cargo.lock +279 -0
- package/native/Cargo.toml +21 -0
- package/native/build.rs +5 -0
- package/native/package-lock.json +32 -0
- package/native/package.json +20 -0
- package/native/src/config.rs +40 -0
- package/native/src/lib.rs +216 -0
- package/native/src/math/bit_math.rs +177 -0
- package/native/src/math/full_math.rs +217 -0
- package/native/src/math/liquidity_math.rs +72 -0
- package/native/src/math/mod.rs +10 -0
- package/native/src/math/oracle.rs +493 -0
- package/native/src/math/sqrt_price_math.rs +272 -0
- package/native/src/math/swap_math.rs +306 -0
- package/native/src/math/tick.rs +239 -0
- package/native/src/math/tick_bitmap.rs +292 -0
- package/native/src/math/tick_math.rs +321 -0
- package/native/src/math/unsafe_math.rs +67 -0
- package/native/src/pool_state.rs +36 -0
- package/native/src/query_outputs.rs +379 -0
- 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.
|
|
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",
|