@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.
- package/build/dex/aave-gsm/config.js +2 -2
- package/build/dex/idex.d.ts +1 -1
- package/build/dex/pancakeswap-v3/pancakeswap-v3-pool.d.ts +2 -0
- package/build/dex/pancakeswap-v3/pancakeswap-v3-pool.js +5 -0
- package/build/dex/pancakeswap-v3/pancakeswap-v3-pool.js.map +1 -1
- package/build/dex/pancakeswap-v3/pancakeswap-v3.d.ts +6 -3
- package/build/dex/pancakeswap-v3/pancakeswap-v3.js +85 -9
- package/build/dex/pancakeswap-v3/pancakeswap-v3.js.map +1 -1
- package/build/dex/solidly-v3/solidly-v3-pool.d.ts +3 -0
- package/build/dex/solidly-v3/solidly-v3-pool.js +8 -0
- package/build/dex/solidly-v3/solidly-v3-pool.js.map +1 -1
- package/build/dex/solidly-v3/solidly-v3.d.ts +5 -2
- package/build/dex/solidly-v3/solidly-v3.js +84 -8
- package/build/dex/solidly-v3/solidly-v3.js.map +1 -1
- 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/types.d.ts +1 -0
- package/build/dex/uniswap-v3/types.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 +5 -0
- package/build/dex/uniswap-v3/uniswap-v3-pool.js.map +1 -1
- package/build/dex/uniswap-v3/uniswap-v3.d.ts +11 -3
- package/build/dex/uniswap-v3/uniswap-v3.js +86 -9
- package/build/dex/uniswap-v3/uniswap-v3.js.map +1 -1
- package/build/dex/uniswap-v4/types.d.ts +1 -0
- package/build/dex/uniswap-v4/uniswap-v4-pool-manager.d.ts +2 -0
- package/build/dex/uniswap-v4/uniswap-v4-pool-manager.js +3 -0
- package/build/dex/uniswap-v4/uniswap-v4-pool-manager.js.map +1 -1
- package/build/dex/uniswap-v4/uniswap-v4-pool.d.ts +3 -0
- package/build/dex/uniswap-v4/uniswap-v4-pool.js +18 -0
- package/build/dex/uniswap-v4/uniswap-v4-pool.js.map +1 -1
- package/build/dex/uniswap-v4/uniswap-v4.d.ts +3 -1
- package/build/dex/uniswap-v4/uniswap-v4.js +74 -14
- package/build/dex/uniswap-v4/uniswap-v4.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 +331 -0
- package/native/Cargo.toml +22 -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 +73 -0
- package/native/src/lib.rs +528 -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 +312 -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 +41 -0
- package/native/src/query_outputs.rs +379 -0
- package/native/src/v4_query_outputs.rs +255 -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 — returns Err if out of bitmap range
|
|
85
|
+
let bitmap_result = tick_bitmap::next_initialized_tick_within_one_word(
|
|
86
|
+
&pool.tick_bitmap,
|
|
87
|
+
state.tick,
|
|
88
|
+
pool.tick_spacing,
|
|
89
|
+
zero_for_one,
|
|
90
|
+
true, // is_price_query
|
|
91
|
+
Some((pool.bitmap_range_lower, pool.bitmap_range_upper)),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
let (tick_next_raw, initialized) = match bitmap_result {
|
|
95
|
+
Ok(result) => result,
|
|
96
|
+
Err(_) => {
|
|
97
|
+
// Out of range — zero out remaining
|
|
98
|
+
state.amount_specified_remaining = I256::ZERO;
|
|
99
|
+
state.amount_calculated = I256::ZERO;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// Clamp to min/max tick
|
|
105
|
+
let tick_next = if tick_next_raw < tick_math::MIN_TICK {
|
|
106
|
+
tick_math::MIN_TICK
|
|
107
|
+
} else if tick_next_raw > tick_math::MAX_TICK {
|
|
108
|
+
tick_math::MAX_TICK
|
|
109
|
+
} else {
|
|
110
|
+
tick_next_raw
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
let sqrt_price_next_x96 = tick_math::get_sqrt_ratio_at_tick(tick_next);
|
|
114
|
+
|
|
115
|
+
// Determine target price (clamped by limit)
|
|
116
|
+
let sqrt_ratio_target = if zero_for_one {
|
|
117
|
+
if sqrt_price_next_x96 < sqrt_price_limit_x96 {
|
|
118
|
+
sqrt_price_limit_x96
|
|
119
|
+
} else {
|
|
120
|
+
sqrt_price_next_x96
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
if sqrt_price_next_x96 > sqrt_price_limit_x96 {
|
|
124
|
+
sqrt_price_limit_x96
|
|
125
|
+
} else {
|
|
126
|
+
sqrt_price_next_x96
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
let step_result = swap_math::compute_swap_step(
|
|
131
|
+
state.sqrt_price_x96,
|
|
132
|
+
sqrt_ratio_target,
|
|
133
|
+
state.liquidity,
|
|
134
|
+
state.amount_specified_remaining,
|
|
135
|
+
pool.fee,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
state.sqrt_price_x96 = step_result.sqrt_ratio_next_x96;
|
|
139
|
+
let amount_in = step_result.amount_in;
|
|
140
|
+
let amount_out = step_result.amount_out;
|
|
141
|
+
let mut fee_amount = step_result.fee_amount;
|
|
142
|
+
|
|
143
|
+
if exact_input {
|
|
144
|
+
state.amount_specified_remaining -=
|
|
145
|
+
amount_in.as_i256() + fee_amount.as_i256();
|
|
146
|
+
state.amount_calculated -= amount_out.as_i256();
|
|
147
|
+
} else {
|
|
148
|
+
state.amount_specified_remaining += amount_out.as_i256();
|
|
149
|
+
state.amount_calculated +=
|
|
150
|
+
amount_in.as_i256() + fee_amount.as_i256();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if cache.fee_protocol > U256::ZERO {
|
|
154
|
+
let delta = pool.variant.protocol_fee_delta(fee_amount, cache.fee_protocol);
|
|
155
|
+
fee_amount -= delta;
|
|
156
|
+
state.protocol_fee += delta;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if state.sqrt_price_x96 == sqrt_price_next_x96 {
|
|
160
|
+
if initialized {
|
|
161
|
+
if pool.variant.has_oracle() && !cache.computed_latest_observation {
|
|
162
|
+
let (tc, splc) = oracle::observe_single(
|
|
163
|
+
&pool.observations,
|
|
164
|
+
cache.block_timestamp,
|
|
165
|
+
U256::ZERO,
|
|
166
|
+
pool.block_timestamp,
|
|
167
|
+
slot0_start.tick,
|
|
168
|
+
slot0_start.observation_index,
|
|
169
|
+
cache.liquidity_start,
|
|
170
|
+
slot0_start.observation_cardinality,
|
|
171
|
+
);
|
|
172
|
+
cache.tick_cumulative = tc;
|
|
173
|
+
cache.seconds_per_liquidity_cumulative_x128 = splc;
|
|
174
|
+
cache.computed_latest_observation = true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if state.amount_specified_remaining == I256::ZERO {
|
|
178
|
+
let tick_idx = tick_next.as_i32();
|
|
179
|
+
if let Some(existing) = ticks_copy.get(&tick_idx) {
|
|
180
|
+
last_ticks_copy = Some((tick_idx, existing.clone()));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let mut liquidity_net = tick::cross(ticks_copy, tick_next.as_i32());
|
|
185
|
+
if zero_for_one {
|
|
186
|
+
liquidity_net = -liquidity_net;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
state.liquidity = liquidity_math::add_delta(state.liquidity, liquidity_net);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
state.tick = if zero_for_one {
|
|
193
|
+
tick_next - I256::ONE
|
|
194
|
+
} else {
|
|
195
|
+
tick_next
|
|
196
|
+
};
|
|
197
|
+
} else if state.sqrt_price_x96 != sqrt_price_start_x96 {
|
|
198
|
+
state.tick = tick_math::get_tick_at_sqrt_ratio(state.sqrt_price_x96);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if state.amount_specified_remaining != I256::ZERO {
|
|
202
|
+
latest_full_cycle_state = state.clone();
|
|
203
|
+
latest_full_cycle_cache = cache.clone();
|
|
204
|
+
} else if let Some((idx, tick_info)) = last_ticks_copy.take() {
|
|
205
|
+
ticks_copy.insert(idx, tick_info);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
i += 1;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if i > 1 {
|
|
212
|
+
latest_full_cycle_cache.tick_count += i - 1;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if state.amount_specified_remaining != I256::ZERO
|
|
216
|
+
&& (!is_sell || pool.variant.zero_remaining_for_sell())
|
|
217
|
+
{
|
|
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
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
use ethnum::{I256, U256};
|
|
2
|
+
use std::collections::HashMap;
|
|
3
|
+
|
|
4
|
+
use crate::math::liquidity_math;
|
|
5
|
+
use crate::math::swap_math;
|
|
6
|
+
use crate::math::tick;
|
|
7
|
+
use crate::math::tick::TickInfo;
|
|
8
|
+
use crate::math::tick_bitmap;
|
|
9
|
+
use crate::math::tick_math;
|
|
10
|
+
|
|
11
|
+
const MAX_PRICING_COMPUTATION_STEPS_ALLOWED: i32 = 64;
|
|
12
|
+
const PIPS_DENOMINATOR: U256 = U256::new(1_000_000);
|
|
13
|
+
|
|
14
|
+
/// V4 pool state — simplified vs V3 (no oracle, different fee model).
|
|
15
|
+
#[derive(Debug, Clone)]
|
|
16
|
+
pub struct V4PoolState {
|
|
17
|
+
pub sqrt_price_x96: U256,
|
|
18
|
+
pub tick: I256,
|
|
19
|
+
pub protocol_fee: U256,
|
|
20
|
+
pub lp_fee: U256,
|
|
21
|
+
pub liquidity: U256,
|
|
22
|
+
pub tick_spacing: I256,
|
|
23
|
+
pub fee_growth_global0_x128: U256,
|
|
24
|
+
pub fee_growth_global1_x128: U256,
|
|
25
|
+
pub tick_bitmap: HashMap<i16, U256>,
|
|
26
|
+
pub ticks: HashMap<i32, TickInfo>,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// --- ProtocolFeeLibrary ---
|
|
30
|
+
|
|
31
|
+
fn get_zero_for_one_fee(protocol_fee: U256) -> U256 {
|
|
32
|
+
protocol_fee % U256::new(4096) // lower 12 bits
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fn get_one_for_zero_fee(protocol_fee: U256) -> U256 {
|
|
36
|
+
protocol_fee >> 12
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fn calculate_swap_fee(protocol_fee: U256, lp_fee: U256) -> U256 {
|
|
40
|
+
// protocolFee + lpFee - (protocolFee * lpFee) / PIPS_DENOMINATOR
|
|
41
|
+
protocol_fee + lp_fee - (protocol_fee * lp_fee) / PIPS_DENOMINATOR
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// --- SwapMath helpers ---
|
|
45
|
+
|
|
46
|
+
fn get_sqrt_price_target(zero_for_one: bool, next_price: U256, limit_price: U256) -> U256 {
|
|
47
|
+
let cond = if zero_for_one {
|
|
48
|
+
next_price < limit_price
|
|
49
|
+
} else {
|
|
50
|
+
next_price > limit_price
|
|
51
|
+
};
|
|
52
|
+
if cond { limit_price } else { next_price }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// V4 _swap — single amount, returns (amount0, amount1) as I256.
|
|
56
|
+
fn swap(
|
|
57
|
+
pool: &V4PoolState,
|
|
58
|
+
zero_for_one: bool,
|
|
59
|
+
amount_specified: I256,
|
|
60
|
+
sqrt_price_limit_x96: U256,
|
|
61
|
+
tick_spacing: I256,
|
|
62
|
+
) -> (I256, I256) {
|
|
63
|
+
let protocol_fee = if zero_for_one {
|
|
64
|
+
get_zero_for_one_fee(pool.protocol_fee)
|
|
65
|
+
} else {
|
|
66
|
+
get_one_for_zero_fee(pool.protocol_fee)
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
let swap_fee = if protocol_fee == U256::ZERO {
|
|
70
|
+
pool.lp_fee
|
|
71
|
+
} else {
|
|
72
|
+
calculate_swap_fee(protocol_fee, pool.lp_fee)
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// MAX_SWAP_FEE check
|
|
76
|
+
if swap_fee >= U256::new(1_000_000) {
|
|
77
|
+
assert!(amount_specified < I256::ZERO, "Invalid fee for exact out");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if amount_specified == I256::ZERO {
|
|
81
|
+
return (I256::ZERO, I256::ZERO);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// SPL checks
|
|
85
|
+
if zero_for_one {
|
|
86
|
+
assert!(sqrt_price_limit_x96 < pool.sqrt_price_x96, "Price limit already exceeded");
|
|
87
|
+
assert!(sqrt_price_limit_x96 > tick_math::MIN_SQRT_RATIO, "Price limit out of bounds");
|
|
88
|
+
} else {
|
|
89
|
+
assert!(sqrt_price_limit_x96 > pool.sqrt_price_x96, "Price limit already exceeded");
|
|
90
|
+
assert!(sqrt_price_limit_x96 < tick_math::MAX_SQRT_RATIO, "Price limit out of bounds");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let mut amount_remaining = amount_specified;
|
|
94
|
+
let mut amount_calculated = I256::ZERO;
|
|
95
|
+
let mut sqrt_price_x96 = pool.sqrt_price_x96;
|
|
96
|
+
let mut current_tick = pool.tick;
|
|
97
|
+
let mut liquidity = pool.liquidity;
|
|
98
|
+
|
|
99
|
+
let mut counter = 0i32;
|
|
100
|
+
while !(amount_remaining == I256::ZERO || sqrt_price_x96 == sqrt_price_limit_x96)
|
|
101
|
+
&& counter <= MAX_PRICING_COMPUTATION_STEPS_ALLOWED
|
|
102
|
+
{
|
|
103
|
+
// Find next tick
|
|
104
|
+
// V4 has no bitmap range check — it reads through empty words naturally.
|
|
105
|
+
let (tick_next_raw, initialized) = tick_bitmap::next_initialized_tick_within_one_word(
|
|
106
|
+
&pool.tick_bitmap,
|
|
107
|
+
current_tick,
|
|
108
|
+
tick_spacing,
|
|
109
|
+
zero_for_one,
|
|
110
|
+
false, // not a bounded price query
|
|
111
|
+
None, // no bitmap range bounds for V4
|
|
112
|
+
).unwrap();
|
|
113
|
+
|
|
114
|
+
let tick_next = if tick_next_raw <= tick_math::MIN_TICK {
|
|
115
|
+
tick_math::MIN_TICK
|
|
116
|
+
} else if tick_next_raw >= tick_math::MAX_TICK {
|
|
117
|
+
tick_math::MAX_TICK
|
|
118
|
+
} else {
|
|
119
|
+
tick_next_raw
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
let sqrt_price_next_x96 = tick_math::get_sqrt_ratio_at_tick(tick_next);
|
|
123
|
+
|
|
124
|
+
let step_start_price = sqrt_price_x96;
|
|
125
|
+
let target = get_sqrt_price_target(zero_for_one, sqrt_price_next_x96, sqrt_price_limit_x96);
|
|
126
|
+
|
|
127
|
+
// V4 uses opposite sign convention: negative = exactIn.
|
|
128
|
+
// V3's compute_swap_step expects positive = exactIn.
|
|
129
|
+
// Negate before calling, results (amountIn/amountOut) stay positive.
|
|
130
|
+
let step = swap_math::compute_swap_step(
|
|
131
|
+
sqrt_price_x96,
|
|
132
|
+
target,
|
|
133
|
+
liquidity,
|
|
134
|
+
-amount_remaining,
|
|
135
|
+
swap_fee,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
sqrt_price_x96 = step.sqrt_ratio_next_x96;
|
|
139
|
+
|
|
140
|
+
// V4 sign convention: amountSpecified > 0 = exactOut, < 0 = exactIn
|
|
141
|
+
if amount_specified > I256::ZERO {
|
|
142
|
+
// exactOut
|
|
143
|
+
amount_remaining -= step.amount_out.as_i256();
|
|
144
|
+
amount_calculated -= step.amount_in.as_i256() + step.fee_amount.as_i256();
|
|
145
|
+
} else {
|
|
146
|
+
// exactIn
|
|
147
|
+
amount_remaining += step.amount_in.as_i256() + step.fee_amount.as_i256();
|
|
148
|
+
amount_calculated += step.amount_out.as_i256();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if sqrt_price_x96 == sqrt_price_next_x96 {
|
|
152
|
+
if initialized {
|
|
153
|
+
let mut liquidity_net = tick::cross(&pool.ticks, tick_next.as_i32());
|
|
154
|
+
if zero_for_one {
|
|
155
|
+
liquidity_net = -liquidity_net;
|
|
156
|
+
}
|
|
157
|
+
liquidity = liquidity_math::add_delta(liquidity, liquidity_net);
|
|
158
|
+
}
|
|
159
|
+
current_tick = if zero_for_one { tick_next - I256::ONE } else { tick_next };
|
|
160
|
+
} else if sqrt_price_x96 != step_start_price {
|
|
161
|
+
current_tick = tick_math::get_tick_at_sqrt_ratio(sqrt_price_x96);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
counter += 1;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if counter >= MAX_PRICING_COMPUTATION_STEPS_ALLOWED {
|
|
168
|
+
return (I256::ZERO, I256::ZERO);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if zero_for_one != (amount_specified < I256::ZERO) {
|
|
172
|
+
(
|
|
173
|
+
amount_calculated,
|
|
174
|
+
amount_specified - amount_remaining,
|
|
175
|
+
)
|
|
176
|
+
} else {
|
|
177
|
+
(
|
|
178
|
+
amount_specified - amount_remaining,
|
|
179
|
+
amount_calculated,
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/// V4 queryOutputs — processes each amount independently.
|
|
185
|
+
/// Returns outputs as U256 (absolute values).
|
|
186
|
+
pub fn query_outputs(
|
|
187
|
+
pool: &V4PoolState,
|
|
188
|
+
tick_spacing: I256,
|
|
189
|
+
amounts: &[U256],
|
|
190
|
+
zero_for_one: bool,
|
|
191
|
+
side: u8, // 0=SELL, 1=BUY
|
|
192
|
+
) -> Vec<U256> {
|
|
193
|
+
let is_sell = side == 0;
|
|
194
|
+
|
|
195
|
+
amounts
|
|
196
|
+
.iter()
|
|
197
|
+
.map(|&amount| {
|
|
198
|
+
if amount == U256::ZERO {
|
|
199
|
+
return U256::ZERO;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let sqrt_price_limit_x96 = if zero_for_one {
|
|
203
|
+
tick_math::MIN_SQRT_RATIO + U256::ONE
|
|
204
|
+
} else {
|
|
205
|
+
tick_math::MAX_SQRT_RATIO - U256::ONE
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
if is_sell {
|
|
209
|
+
let amount_specified = -(amount.as_i256()); // exactIn: negative
|
|
210
|
+
let (amount0, amount1) = swap(
|
|
211
|
+
pool,
|
|
212
|
+
zero_for_one,
|
|
213
|
+
amount_specified,
|
|
214
|
+
sqrt_price_limit_x96,
|
|
215
|
+
tick_spacing,
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
let amount_specified_actual = if zero_for_one == (amount_specified < I256::ZERO) {
|
|
219
|
+
amount0
|
|
220
|
+
} else {
|
|
221
|
+
amount1
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
if amount_specified_actual != amount_specified {
|
|
225
|
+
return U256::ZERO;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
let output = if zero_for_one { amount1 } else { amount0 };
|
|
229
|
+
output.as_u256()
|
|
230
|
+
} else {
|
|
231
|
+
let amount_specified = amount.as_i256(); // exactOut: positive
|
|
232
|
+
let (amount0, amount1) = swap(
|
|
233
|
+
pool,
|
|
234
|
+
zero_for_one,
|
|
235
|
+
amount_specified,
|
|
236
|
+
sqrt_price_limit_x96,
|
|
237
|
+
tick_spacing,
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
let amount_specified_actual = if zero_for_one == (amount_specified < I256::ZERO) {
|
|
241
|
+
amount0
|
|
242
|
+
} else {
|
|
243
|
+
amount1
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
if amount_specified_actual != amount_specified {
|
|
247
|
+
return U256::ZERO;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
let output = if zero_for_one { -amount0 } else { -amount1 };
|
|
251
|
+
output.as_u256()
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
.collect()
|
|
255
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paraswap/dex-lib",
|
|
3
|
-
"version": "4.8.
|
|
3
|
+
"version": "4.8.37-native-dex-math.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",
|