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

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 +1 -1
@@ -0,0 +1,272 @@
1
+ use ethnum::{I256, U256};
2
+ use super::full_math;
3
+ use super::unsafe_math;
4
+
5
+ const RESOLUTION: u32 = 96;
6
+ const Q96: U256 = U256::from_words(0, 1u128 << 96);
7
+ // 2^160 - 1: hi_128 = 0xFFFFFFFF, lo_128 = u128::MAX
8
+ const MAX_UINT160: U256 = U256::from_words(0xFFFFFFFF, u128::MAX);
9
+
10
+ /// Truncate to uint160 range.
11
+ fn as_uint160(val: U256) -> U256 {
12
+ val & MAX_UINT160
13
+ }
14
+
15
+ pub fn get_next_sqrt_price_from_amount0_rounding_up(
16
+ sqrt_p_x96: U256,
17
+ liquidity: U256,
18
+ amount: U256,
19
+ add: bool,
20
+ ) -> U256 {
21
+ if amount == U256::ZERO {
22
+ return sqrt_p_x96;
23
+ }
24
+ let numerator1 = liquidity << RESOLUTION;
25
+
26
+ let product = amount * sqrt_p_x96;
27
+ if add {
28
+ if product / amount == sqrt_p_x96 {
29
+ let denominator = numerator1 + product;
30
+ if denominator >= numerator1 {
31
+ return as_uint160(full_math::mul_div_rounding_up(
32
+ numerator1,
33
+ sqrt_p_x96,
34
+ denominator,
35
+ ));
36
+ }
37
+ }
38
+ as_uint160(unsafe_math::div_rounding_up(
39
+ numerator1,
40
+ numerator1 / sqrt_p_x96 + amount,
41
+ ))
42
+ } else {
43
+ assert!(
44
+ product / amount == sqrt_p_x96 && numerator1 > product,
45
+ "product / amount == sqrt_p_x96 && numerator1 > product"
46
+ );
47
+ let denominator = numerator1 - product;
48
+ as_uint160(full_math::mul_div_rounding_up(
49
+ numerator1,
50
+ sqrt_p_x96,
51
+ denominator,
52
+ ))
53
+ }
54
+ }
55
+
56
+ pub fn get_next_sqrt_price_from_amount1_rounding_down(
57
+ sqrt_p_x96: U256,
58
+ liquidity: U256,
59
+ amount: U256,
60
+ add: bool,
61
+ ) -> U256 {
62
+ if add {
63
+ let quotient = if amount <= MAX_UINT160 {
64
+ (amount << RESOLUTION) / liquidity
65
+ } else {
66
+ full_math::mul_div(amount, Q96, liquidity)
67
+ };
68
+ as_uint160(sqrt_p_x96 + quotient)
69
+ } else {
70
+ let quotient = if amount <= MAX_UINT160 {
71
+ unsafe_math::div_rounding_up(amount << RESOLUTION, liquidity)
72
+ } else {
73
+ full_math::mul_div_rounding_up(amount, Q96, liquidity)
74
+ };
75
+ assert!(sqrt_p_x96 > quotient, "sqrt_p_x96 > quotient");
76
+ as_uint160(sqrt_p_x96 - quotient)
77
+ }
78
+ }
79
+
80
+ pub fn get_next_sqrt_price_from_input(
81
+ sqrt_p_x96: U256,
82
+ liquidity: U256,
83
+ amount_in: U256,
84
+ zero_for_one: bool,
85
+ ) -> U256 {
86
+ assert!(sqrt_p_x96 > U256::ZERO, "sqrt_p_x96 > 0");
87
+ assert!(liquidity > U256::ZERO, "liquidity > 0");
88
+
89
+ if zero_for_one {
90
+ get_next_sqrt_price_from_amount0_rounding_up(sqrt_p_x96, liquidity, amount_in, true)
91
+ } else {
92
+ get_next_sqrt_price_from_amount1_rounding_down(sqrt_p_x96, liquidity, amount_in, true)
93
+ }
94
+ }
95
+
96
+ pub fn get_next_sqrt_price_from_output(
97
+ sqrt_p_x96: U256,
98
+ liquidity: U256,
99
+ amount_out: U256,
100
+ zero_for_one: bool,
101
+ ) -> U256 {
102
+ assert!(sqrt_p_x96 > U256::ZERO, "sqrt_p_x96 > 0");
103
+ assert!(liquidity > U256::ZERO, "liquidity > 0");
104
+
105
+ if zero_for_one {
106
+ get_next_sqrt_price_from_amount1_rounding_down(sqrt_p_x96, liquidity, amount_out, false)
107
+ } else {
108
+ get_next_sqrt_price_from_amount0_rounding_up(sqrt_p_x96, liquidity, amount_out, false)
109
+ }
110
+ }
111
+
112
+ pub fn get_amount0_delta(
113
+ sqrt_ratio_a_x96: U256,
114
+ sqrt_ratio_b_x96: U256,
115
+ liquidity: U256,
116
+ round_up: bool,
117
+ ) -> U256 {
118
+ let (sqrt_ratio_a_x96, sqrt_ratio_b_x96) = if sqrt_ratio_a_x96 > sqrt_ratio_b_x96 {
119
+ (sqrt_ratio_b_x96, sqrt_ratio_a_x96)
120
+ } else {
121
+ (sqrt_ratio_a_x96, sqrt_ratio_b_x96)
122
+ };
123
+
124
+ let numerator1 = liquidity << RESOLUTION;
125
+ let numerator2 = sqrt_ratio_b_x96 - sqrt_ratio_a_x96;
126
+
127
+ assert!(sqrt_ratio_a_x96 > U256::ZERO, "sqrt_ratio_a_x96 > 0");
128
+
129
+ if round_up {
130
+ unsafe_math::div_rounding_up(
131
+ full_math::mul_div_rounding_up(numerator1, numerator2, sqrt_ratio_b_x96),
132
+ sqrt_ratio_a_x96,
133
+ )
134
+ } else {
135
+ full_math::mul_div(numerator1, numerator2, sqrt_ratio_b_x96) / sqrt_ratio_a_x96
136
+ }
137
+ }
138
+
139
+ pub fn get_amount1_delta(
140
+ sqrt_ratio_a_x96: U256,
141
+ sqrt_ratio_b_x96: U256,
142
+ liquidity: U256,
143
+ round_up: bool,
144
+ ) -> U256 {
145
+ let (sqrt_ratio_a_x96, sqrt_ratio_b_x96) = if sqrt_ratio_a_x96 > sqrt_ratio_b_x96 {
146
+ (sqrt_ratio_b_x96, sqrt_ratio_a_x96)
147
+ } else {
148
+ (sqrt_ratio_a_x96, sqrt_ratio_b_x96)
149
+ };
150
+
151
+ if round_up {
152
+ full_math::mul_div_rounding_up(
153
+ liquidity,
154
+ sqrt_ratio_b_x96 - sqrt_ratio_a_x96,
155
+ Q96,
156
+ )
157
+ } else {
158
+ full_math::mul_div(liquidity, sqrt_ratio_b_x96 - sqrt_ratio_a_x96, Q96)
159
+ }
160
+ }
161
+
162
+ /// Signed version: _getAmount0DeltaO with signed liquidity.
163
+ /// Equivalent to TS SqrtPriceMath._getAmount0DeltaO.
164
+ pub fn get_amount0_delta_signed(
165
+ sqrt_ratio_a_x96: U256,
166
+ sqrt_ratio_b_x96: U256,
167
+ liquidity: I256,
168
+ ) -> I256 {
169
+ let mask_128 = (U256::ONE << 128) - U256::ONE;
170
+ if liquidity < I256::ZERO {
171
+ // BigInt.asUintN(128, -liquidity)
172
+ let abs_liq = (-liquidity).as_u256() & mask_128;
173
+ let delta = get_amount0_delta(sqrt_ratio_a_x96, sqrt_ratio_b_x96, abs_liq, false);
174
+ // -BigInt.asIntN(256, delta)
175
+ -(delta.as_i256())
176
+ } else {
177
+ // BigInt.asUintN(128, liquidity)
178
+ let liq_u = liquidity.as_u256() & mask_128;
179
+ let delta = get_amount0_delta(sqrt_ratio_a_x96, sqrt_ratio_b_x96, liq_u, true);
180
+ // BigInt.asIntN(256, delta)
181
+ delta.as_i256()
182
+ }
183
+ }
184
+
185
+ /// Signed version: _getAmount1DeltaO with signed liquidity.
186
+ /// Equivalent to TS SqrtPriceMath._getAmount1DeltaO.
187
+ pub fn get_amount1_delta_signed(
188
+ sqrt_ratio_a_x96: U256,
189
+ sqrt_ratio_b_x96: U256,
190
+ liquidity: I256,
191
+ ) -> I256 {
192
+ let mask_128 = (U256::ONE << 128) - U256::ONE;
193
+ if liquidity < I256::ZERO {
194
+ let abs_liq = (-liquidity).as_u256() & mask_128;
195
+ let delta = get_amount1_delta(sqrt_ratio_a_x96, sqrt_ratio_b_x96, abs_liq, false);
196
+ -(delta.as_i256())
197
+ } else {
198
+ let liq_u = liquidity.as_u256() & mask_128;
199
+ let delta = get_amount1_delta(sqrt_ratio_a_x96, sqrt_ratio_b_x96, liq_u, true);
200
+ delta.as_i256()
201
+ }
202
+ }
203
+
204
+ #[cfg(test)]
205
+ mod tests {
206
+ use super::*;
207
+
208
+ #[test]
209
+ fn test_get_amount0_delta_basic() {
210
+ let sqrt_a = U256::from(79228162514264337593543950336u128); // Q96 * 1
211
+ let sqrt_b = U256::from(158456325028528675187087900672u128); // Q96 * 2
212
+ let liquidity = U256::from(1_000_000u64);
213
+ let result = get_amount0_delta(sqrt_a, sqrt_b, liquidity, true);
214
+ assert!(result > U256::ZERO);
215
+ }
216
+
217
+ #[test]
218
+ fn test_get_amount1_delta_basic() {
219
+ let sqrt_a = U256::from(79228162514264337593543950336u128); // Q96 * 1
220
+ let sqrt_b = U256::from(158456325028528675187087900672u128); // Q96 * 2
221
+ let liquidity = U256::from(1_000_000u64);
222
+ let result = get_amount1_delta(sqrt_a, sqrt_b, liquidity, true);
223
+ assert!(result > U256::ZERO);
224
+ }
225
+
226
+ #[test]
227
+ fn test_get_next_sqrt_price_from_input_zero_for_one() {
228
+ let sqrt_p = U256::from(79228162514264337593543950336u128);
229
+ let liquidity = U256::from(1_000_000_000_000u64);
230
+ let amount = U256::from(1_000_000u64);
231
+ let result = get_next_sqrt_price_from_input(sqrt_p, liquidity, amount, true);
232
+ assert!(result > U256::ZERO);
233
+ assert!(result <= sqrt_p);
234
+ }
235
+
236
+ #[test]
237
+ fn test_get_next_sqrt_price_from_input_one_for_zero() {
238
+ let sqrt_p = U256::from(79228162514264337593543950336u128);
239
+ let liquidity = U256::from(1_000_000_000_000u64);
240
+ let amount = U256::from(1_000_000u64);
241
+ let result = get_next_sqrt_price_from_input(sqrt_p, liquidity, amount, false);
242
+ assert!(result >= sqrt_p);
243
+ }
244
+
245
+ #[test]
246
+ fn test_get_amount0_delta_symmetric() {
247
+ let sqrt_a = U256::from(79228162514264337593543950336u128);
248
+ let sqrt_b = U256::from(158456325028528675187087900672u128);
249
+ let liquidity = U256::from(1_000_000u64);
250
+ let r1 = get_amount0_delta(sqrt_a, sqrt_b, liquidity, true);
251
+ let r2 = get_amount0_delta(sqrt_b, sqrt_a, liquidity, true);
252
+ assert_eq!(r1, r2);
253
+ }
254
+
255
+ #[test]
256
+ fn test_get_amount0_delta_signed_positive() {
257
+ let sqrt_a = U256::from(79228162514264337593543950336u128);
258
+ let sqrt_b = U256::from(158456325028528675187087900672u128);
259
+ let liquidity = I256::from(1_000_000i64);
260
+ let result = get_amount0_delta_signed(sqrt_a, sqrt_b, liquidity);
261
+ assert!(result > I256::ZERO);
262
+ }
263
+
264
+ #[test]
265
+ fn test_get_amount0_delta_signed_negative() {
266
+ let sqrt_a = U256::from(79228162514264337593543950336u128);
267
+ let sqrt_b = U256::from(158456325028528675187087900672u128);
268
+ let liquidity = I256::from(-1_000_000i64);
269
+ let result = get_amount0_delta_signed(sqrt_a, sqrt_b, liquidity);
270
+ assert!(result < I256::ZERO);
271
+ }
272
+ }
@@ -0,0 +1,306 @@
1
+ use ethnum::{I256, U256};
2
+ use super::full_math;
3
+ use super::sqrt_price_math;
4
+
5
+ /// Result of a single swap step computation.
6
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
7
+ pub struct SwapStepResult {
8
+ pub sqrt_ratio_next_x96: U256,
9
+ pub amount_in: U256,
10
+ pub amount_out: U256,
11
+ pub fee_amount: U256,
12
+ }
13
+
14
+ /// BI_POWS[6] = 1_000_000
15
+ const ONE_MILLION: U256 = U256::new(1_000_000u128);
16
+
17
+ /// Computes the result of swapping some amount in, or amount out, given the parameters of the swap.
18
+ ///
19
+ /// The fee, `fee_pips`, is in hundredths of a bip (i.e. 1e-6).
20
+ ///
21
+ /// `amount_remaining` is treated as an I256: positive means exact-input, negative means exact-output.
22
+ pub fn compute_swap_step(
23
+ sqrt_ratio_current_x96: U256,
24
+ sqrt_ratio_target_x96: U256,
25
+ liquidity: U256,
26
+ amount_remaining: I256,
27
+ fee_pips: U256,
28
+ ) -> SwapStepResult {
29
+ let zero_for_one = sqrt_ratio_current_x96 >= sqrt_ratio_target_x96;
30
+ let exact_in = amount_remaining >= I256::ZERO;
31
+
32
+ let sqrt_ratio_next_x96;
33
+ let mut amount_in;
34
+ let mut amount_out;
35
+ let fee_amount;
36
+
37
+ if exact_in {
38
+ // BigInt.asUintN(256, amountRemaining) -- amountRemaining is non-negative here so it's identity
39
+ let amount_remaining_u = amount_remaining.as_u256();
40
+ let amount_remaining_less_fee =
41
+ full_math::mul_div(amount_remaining_u, ONE_MILLION - fee_pips, ONE_MILLION);
42
+
43
+ amount_in = if zero_for_one {
44
+ sqrt_price_math::get_amount0_delta(
45
+ sqrt_ratio_target_x96,
46
+ sqrt_ratio_current_x96,
47
+ liquidity,
48
+ true,
49
+ )
50
+ } else {
51
+ sqrt_price_math::get_amount1_delta(
52
+ sqrt_ratio_current_x96,
53
+ sqrt_ratio_target_x96,
54
+ liquidity,
55
+ true,
56
+ )
57
+ };
58
+
59
+ if amount_remaining_less_fee >= amount_in {
60
+ sqrt_ratio_next_x96 = sqrt_ratio_target_x96;
61
+ } else {
62
+ sqrt_ratio_next_x96 = sqrt_price_math::get_next_sqrt_price_from_input(
63
+ sqrt_ratio_current_x96,
64
+ liquidity,
65
+ amount_remaining_less_fee,
66
+ zero_for_one,
67
+ );
68
+ }
69
+ } else {
70
+ // BigInt.asUintN(256, -amountRemaining) -- negate signed, interpret as unsigned
71
+ let neg_amount = (-amount_remaining).as_u256();
72
+
73
+ amount_out = if zero_for_one {
74
+ sqrt_price_math::get_amount1_delta(
75
+ sqrt_ratio_target_x96,
76
+ sqrt_ratio_current_x96,
77
+ liquidity,
78
+ false,
79
+ )
80
+ } else {
81
+ sqrt_price_math::get_amount0_delta(
82
+ sqrt_ratio_current_x96,
83
+ sqrt_ratio_target_x96,
84
+ liquidity,
85
+ false,
86
+ )
87
+ };
88
+
89
+ if neg_amount >= amount_out {
90
+ sqrt_ratio_next_x96 = sqrt_ratio_target_x96;
91
+ } else {
92
+ sqrt_ratio_next_x96 = sqrt_price_math::get_next_sqrt_price_from_output(
93
+ sqrt_ratio_current_x96,
94
+ liquidity,
95
+ neg_amount,
96
+ zero_for_one,
97
+ );
98
+ }
99
+
100
+ // Initialize amount_in to 0; it will be set below
101
+ amount_in = U256::ZERO;
102
+ }
103
+
104
+ // Re-initialize for the second half of the function
105
+ // We need to track amount_out properly for the !exact_in case
106
+ // The TS code re-computes both amount_in and amount_out based on `max` flag
107
+ let max = sqrt_ratio_target_x96 == sqrt_ratio_next_x96;
108
+
109
+ if exact_in {
110
+ // amount_out was not set in exact_in path above, initialize to 0
111
+ amount_out = U256::ZERO;
112
+ } else {
113
+ amount_out = if zero_for_one {
114
+ sqrt_price_math::get_amount1_delta(
115
+ sqrt_ratio_target_x96,
116
+ sqrt_ratio_current_x96,
117
+ liquidity,
118
+ false,
119
+ )
120
+ } else {
121
+ sqrt_price_math::get_amount0_delta(
122
+ sqrt_ratio_current_x96,
123
+ sqrt_ratio_target_x96,
124
+ liquidity,
125
+ false,
126
+ )
127
+ };
128
+ }
129
+
130
+ if zero_for_one {
131
+ if !(max && exact_in) {
132
+ amount_in = sqrt_price_math::get_amount0_delta(
133
+ sqrt_ratio_next_x96,
134
+ sqrt_ratio_current_x96,
135
+ liquidity,
136
+ true,
137
+ );
138
+ }
139
+ if !(max && !exact_in) {
140
+ amount_out = sqrt_price_math::get_amount1_delta(
141
+ sqrt_ratio_next_x96,
142
+ sqrt_ratio_current_x96,
143
+ liquidity,
144
+ false,
145
+ );
146
+ }
147
+ } else {
148
+ if !(max && exact_in) {
149
+ amount_in = sqrt_price_math::get_amount1_delta(
150
+ sqrt_ratio_current_x96,
151
+ sqrt_ratio_next_x96,
152
+ liquidity,
153
+ true,
154
+ );
155
+ }
156
+ if !(max && !exact_in) {
157
+ amount_out = sqrt_price_math::get_amount0_delta(
158
+ sqrt_ratio_current_x96,
159
+ sqrt_ratio_next_x96,
160
+ liquidity,
161
+ false,
162
+ );
163
+ }
164
+ }
165
+
166
+ // Cap the output amount to not exceed the remaining output amount
167
+ if !exact_in {
168
+ let neg_amount = (-amount_remaining).as_u256();
169
+ if amount_out > neg_amount {
170
+ amount_out = neg_amount;
171
+ }
172
+ }
173
+
174
+ if exact_in && sqrt_ratio_next_x96 != sqrt_ratio_target_x96 {
175
+ // We didn't reach the target, so take the remainder of the maximum input as fee
176
+ fee_amount = amount_remaining.as_u256() - amount_in;
177
+ } else {
178
+ fee_amount =
179
+ full_math::mul_div_rounding_up(amount_in, fee_pips, ONE_MILLION - fee_pips);
180
+ }
181
+
182
+ SwapStepResult {
183
+ sqrt_ratio_next_x96,
184
+ amount_in,
185
+ amount_out,
186
+ fee_amount,
187
+ }
188
+ }
189
+
190
+ #[cfg(test)]
191
+ mod tests {
192
+ use super::*;
193
+
194
+ // Q96 = 2^96
195
+ const Q96: U256 = U256::from_words(0, 1u128 << 96);
196
+
197
+ #[test]
198
+ fn test_exact_in_zero_for_one() {
199
+ // price moves from 2.0 toward 1.0
200
+ let sqrt_current = Q96 * U256::from(2u64); // sqrt(4) * Q96
201
+ let sqrt_target = Q96; // sqrt(1) * Q96
202
+ let liquidity = U256::from(1_000_000_000_000u128);
203
+ let amount_remaining = I256::from(1_000_000i64);
204
+ let fee_pips = U256::from(3000u64); // 0.3%
205
+
206
+ let result = compute_swap_step(
207
+ sqrt_current,
208
+ sqrt_target,
209
+ liquidity,
210
+ amount_remaining,
211
+ fee_pips,
212
+ );
213
+
214
+ assert!(result.sqrt_ratio_next_x96 > U256::ZERO);
215
+ assert!(result.sqrt_ratio_next_x96 <= sqrt_current);
216
+ assert!(result.amount_in > U256::ZERO);
217
+ assert!(result.amount_out > U256::ZERO);
218
+ // amount_in + fee_amount should not exceed amount_remaining
219
+ assert!(result.amount_in + result.fee_amount <= amount_remaining.as_u256());
220
+ }
221
+
222
+ #[test]
223
+ fn test_exact_out_zero_for_one() {
224
+ let sqrt_current = Q96 * U256::from(2u64);
225
+ let sqrt_target = Q96;
226
+ let liquidity = U256::from(1_000_000_000_000u128);
227
+ let amount_remaining = I256::from(-500_000i64); // exact output
228
+ let fee_pips = U256::from(3000u64);
229
+
230
+ let result = compute_swap_step(
231
+ sqrt_current,
232
+ sqrt_target,
233
+ liquidity,
234
+ amount_remaining,
235
+ fee_pips,
236
+ );
237
+
238
+ assert!(result.sqrt_ratio_next_x96 > U256::ZERO);
239
+ assert!(result.amount_in > U256::ZERO);
240
+ assert!(result.amount_out > U256::ZERO);
241
+ // amount_out should not exceed requested
242
+ assert!(result.amount_out <= U256::from(500_000u64));
243
+ }
244
+
245
+ #[test]
246
+ fn test_exact_in_one_for_zero() {
247
+ let sqrt_current = Q96;
248
+ let sqrt_target = Q96 * U256::from(2u64);
249
+ let liquidity = U256::from(1_000_000_000_000u128);
250
+ let amount_remaining = I256::from(1_000_000i64);
251
+ let fee_pips = U256::from(3000u64);
252
+
253
+ let result = compute_swap_step(
254
+ sqrt_current,
255
+ sqrt_target,
256
+ liquidity,
257
+ amount_remaining,
258
+ fee_pips,
259
+ );
260
+
261
+ assert!(result.sqrt_ratio_next_x96 >= sqrt_current);
262
+ assert!(result.amount_in > U256::ZERO);
263
+ assert!(result.amount_out > U256::ZERO);
264
+ }
265
+
266
+ #[test]
267
+ fn test_fee_amount_when_target_not_reached() {
268
+ // Very small liquidity so we definitely reach the target
269
+ let sqrt_current = Q96 * U256::from(2u64);
270
+ let sqrt_target = Q96;
271
+ let liquidity = U256::from(100u64); // very small liquidity
272
+ let amount_remaining = I256::from(1_000_000_000i64); // large amount
273
+ let fee_pips = U256::from(3000u64);
274
+
275
+ let result = compute_swap_step(
276
+ sqrt_current,
277
+ sqrt_target,
278
+ liquidity,
279
+ amount_remaining,
280
+ fee_pips,
281
+ );
282
+
283
+ // Should reach the target price
284
+ assert_eq!(result.sqrt_ratio_next_x96, sqrt_target);
285
+ }
286
+
287
+ #[test]
288
+ fn test_zero_fee() {
289
+ let sqrt_current = Q96 * U256::from(2u64);
290
+ let sqrt_target = Q96;
291
+ let liquidity = U256::from(1_000_000_000_000u128);
292
+ let amount_remaining = I256::from(1_000_000i64);
293
+ let fee_pips = U256::ZERO;
294
+
295
+ let result = compute_swap_step(
296
+ sqrt_current,
297
+ sqrt_target,
298
+ liquidity,
299
+ amount_remaining,
300
+ fee_pips,
301
+ );
302
+
303
+ // With zero fee, fee_amount should be 0
304
+ assert_eq!(result.fee_amount, U256::ZERO);
305
+ }
306
+ }