1use borsh::{BorshDeserialize, BorshSerialize};
2
3use ethnum::U256;
4
5use super::{
6 super::error::{
7 CoreError, AMOUNT_EXCEEDS_MAX_U128, AMOUNT_EXCEEDS_MAX_U64, ARITHMETIC_OVERFLOW,
8 INVALID_ORACLE_DATA,
9 },
10 QuoteType, SingleSideLiquidity, LIQUIDITY_LEVELS, PER_M_DENOMINATOR,
11};
12
13const MIN_SQRT_PRICE: u128 = 5647135299341; const MAX_SQRT_PRICE: u128 = 60257519765924248467716150; #[cfg(feature = "wasm")]
17use riptide_amm_macros::wasm_expose;
18
19const MAX_POSITIONS: usize = 16;
20
21const MIN_SQRT_PRICE_RANGE: u128 = 18_446_744_073_710;
25
26#[derive(Debug, Clone, Copy, Eq, PartialEq)]
34#[cfg_attr(true, derive(BorshDeserialize, BorshSerialize))]
35#[cfg_attr(feature = "wasm", wasm_expose)]
36pub enum LiquidityType {
37 ConstantProduct,
39 ConcentratedLiquidity {
40 lower_sqrt_price: u128,
41 upper_sqrt_price: u128,
42 },
43}
44
45impl LiquidityType {
46 fn positions(&self) -> Result<[Position; MAX_POSITIONS], CoreError> {
47 let mut positions = [Position::default(); MAX_POSITIONS];
48 match self {
49 LiquidityType::ConstantProduct => {
50 positions[0] = Position::full_range(PER_M_DENOMINATOR as u32);
51 }
52 LiquidityType::ConcentratedLiquidity {
53 lower_sqrt_price,
54 upper_sqrt_price,
55 } => {
56 positions[0] = Position::new(
57 *lower_sqrt_price,
58 *upper_sqrt_price,
59 PER_M_DENOMINATOR as u32,
60 )?;
61 }
62 };
63 Ok(positions)
64 }
65}
66
67#[derive(Debug, Clone, Copy)]
68struct Position {
69 lower_sqrt_price: u128,
70 upper_sqrt_price: u128,
71 liquidity_share_per_m: u32,
72}
73
74impl Default for Position {
75 fn default() -> Self {
76 Self {
77 lower_sqrt_price: MIN_SQRT_PRICE,
78 upper_sqrt_price: MAX_SQRT_PRICE,
79 liquidity_share_per_m: 0,
80 }
81 }
82}
83
84impl Position {
85 fn full_range(liquidity_share_per_m: u32) -> Self {
86 Self {
87 lower_sqrt_price: MIN_SQRT_PRICE,
88 upper_sqrt_price: MAX_SQRT_PRICE,
89 liquidity_share_per_m,
90 }
91 }
92
93 fn new(
94 lower_sqrt_price: u128,
95 upper_sqrt_price: u128,
96 liquidity_share_per_m: u32,
97 ) -> Result<Self, CoreError> {
98 if lower_sqrt_price > upper_sqrt_price
99 || lower_sqrt_price < MIN_SQRT_PRICE
100 || upper_sqrt_price > MAX_SQRT_PRICE
101 || upper_sqrt_price - lower_sqrt_price < MIN_SQRT_PRICE_RANGE
102 {
103 return Err(INVALID_ORACLE_DATA);
104 }
105 Ok(Self {
106 lower_sqrt_price,
107 upper_sqrt_price,
108 liquidity_share_per_m,
109 })
110 }
111}
112
113pub(crate) fn amm_price(
118 liquidity_type: LiquidityType,
119 reserves_a: u64,
120 reserves_b: u64,
121) -> Result<u128, CoreError> {
122 if reserves_a == 0 && reserves_b == 0 {
123 return Ok(0);
124 }
125
126 let positions = liquidity_type.positions()?;
127 let sqrt_price = implied_sqrt_price(&positions, reserves_a, reserves_b)?;
128
129 U256::from(sqrt_price)
130 .checked_mul(U256::from(sqrt_price))
131 .ok_or(ARITHMETIC_OVERFLOW)?
132 .checked_shr(64)
133 .ok_or(ARITHMETIC_OVERFLOW)?
134 .try_into()
135 .map_err(|_| AMOUNT_EXCEEDS_MAX_U128)
136}
137
138pub(crate) fn amm_liquidity(
139 liquidity_type: LiquidityType,
140 bid_spread_per_m: i32,
141 ask_spread_per_m: i32,
142 quote_type: QuoteType,
143 reserves_a: u64,
144 reserves_b: u64,
145) -> Result<SingleSideLiquidity, CoreError> {
146 if bid_spread_per_m <= -PER_M_DENOMINATOR
147 || bid_spread_per_m >= PER_M_DENOMINATOR
148 || ask_spread_per_m <= -PER_M_DENOMINATOR
149 {
150 return Err(INVALID_ORACLE_DATA);
151 }
152 if reserves_a == 0 && reserves_b == 0 {
153 return Ok(SingleSideLiquidity::new());
154 }
155
156 let positions = liquidity_type.positions()?;
157 let total_liquidity_share_per_m = positions
158 .iter()
159 .map(|p| u128::from(p.liquidity_share_per_m))
160 .sum::<u128>();
161 if total_liquidity_share_per_m == 0 || total_liquidity_share_per_m > PER_M_DENOMINATOR as u128 {
162 return Err(INVALID_ORACLE_DATA);
163 }
164
165 let mid_sqrt_price = implied_sqrt_price(&positions, reserves_a, reserves_b)?;
166
167 let reserves = if quote_type.a_to_b() {
168 reserves_b
169 } else {
170 reserves_a
171 };
172
173 let spread = if quote_type.a_to_b() {
174 PER_M_DENOMINATOR - bid_spread_per_m
175 } else {
176 PER_M_DENOMINATOR + ask_spread_per_m
177 };
178
179 let end_sqrt_price = if quote_type.a_to_b() {
180 positions
181 .iter()
182 .filter(|p| p.liquidity_share_per_m > 0)
183 .map(|p| p.lower_sqrt_price)
184 .min()
185 .unwrap_or(MIN_SQRT_PRICE)
186 } else {
187 positions
188 .iter()
189 .filter(|p| p.liquidity_share_per_m > 0)
190 .map(|p| p.upper_sqrt_price)
191 .max()
192 .unwrap_or(MAX_SQRT_PRICE)
193 };
194
195 let mut liquidity_bins: [(u128, u64); LIQUIDITY_LEVELS] = [(0, 0); LIQUIDITY_LEVELS];
196 let mut position_bin_indexes: [[bool; LIQUIDITY_LEVELS]; MAX_POSITIONS] =
197 [[false; LIQUIDITY_LEVELS]; MAX_POSITIONS];
198
199 for i in 0..LIQUIDITY_LEVELS {
201 let (start_sqrt_price, end_sqrt_price) = bin_boundaries(i, mid_sqrt_price, end_sqrt_price)?;
202
203 for j in 0..MAX_POSITIONS {
210 position_bin_indexes[j][i] =
211 bin_position_overlap(i, mid_sqrt_price, end_sqrt_price, &positions[j])? > 0;
212 }
213
214 liquidity_bins[i].0 = bin_price(i, start_sqrt_price, end_sqrt_price, spread)?;
215 }
216
217 let position_amounts =
218 position_reserves(&positions, mid_sqrt_price, reserves, quote_type.a_to_b())?;
219
220 for i in 0..MAX_POSITIONS {
223 let num_bins = position_bin_indexes[i].iter().filter(|&b| *b).count();
224 if num_bins == 0 {
225 continue;
226 }
227 if positions[i].liquidity_share_per_m == 0 {
228 continue;
229 }
230
231 let mut total_overlap: u128 = 0;
233 for j in 0..LIQUIDITY_LEVELS {
234 if position_bin_indexes[i][j] {
235 total_overlap = total_overlap
236 .checked_add(bin_position_overlap(
237 j,
238 mid_sqrt_price,
239 end_sqrt_price,
240 &positions[i],
241 )?)
242 .ok_or(ARITHMETIC_OVERFLOW)?;
243 }
244 }
245
246 if total_overlap == 0 {
247 continue;
248 }
249
250 for j in 0..LIQUIDITY_LEVELS {
252 if position_bin_indexes[i][j] {
253 let width = bin_position_overlap(j, mid_sqrt_price, end_sqrt_price, &positions[i])?;
254 if width > 0 {
255 let amount = U256::from(position_amounts[i])
256 .checked_mul(U256::from(width))
257 .ok_or(ARITHMETIC_OVERFLOW)?
258 .checked_div(U256::from(total_overlap))
259 .ok_or(ARITHMETIC_OVERFLOW)?;
260 let amount: u64 = amount.try_into().map_err(|_| AMOUNT_EXCEEDS_MAX_U64)?;
261 liquidity_bins[j].1 = liquidity_bins[j]
262 .1
263 .checked_add(amount)
264 .ok_or(ARITHMETIC_OVERFLOW)?;
265 }
266 }
267 }
268 }
269
270 let total_amount = liquidity_bins
272 .iter()
273 .map(|b: &(u128, u64)| b.1)
274 .sum::<u64>();
275
276 let mut remaining = reserves.saturating_sub(total_amount);
277 let any_bin_has_liquidity = liquidity_bins.iter().any(|b| b.1 > 0);
282 if any_bin_has_liquidity {
283 while remaining > 0 {
284 for i in (0..LIQUIDITY_LEVELS).rev() {
285 if remaining == 0 {
286 break;
287 }
288 if liquidity_bins[i].1 > 0 {
289 liquidity_bins[i].1 = liquidity_bins[i]
290 .1
291 .checked_add(1)
292 .ok_or(ARITHMETIC_OVERFLOW)?;
293 remaining -= 1;
294 }
295 }
296 }
297 }
298
299 Ok(SingleSideLiquidity::from_slice(&liquidity_bins))
300}
301
302fn virtual_position_reserves(
303 sqrt_price: u128,
304 position: &Position,
305) -> Result<(u64, u64), CoreError> {
306 let liquidity = U256::from(position.liquidity_share_per_m);
307 let sqrt_price = sqrt_price.clamp(position.lower_sqrt_price, position.upper_sqrt_price);
308
309 let diff_a = U256::from(position.upper_sqrt_price).saturating_sub(U256::from(sqrt_price));
310 let virtual_a = if diff_a == 0 {
311 0u64
312 } else {
313 let numerator = liquidity
314 .checked_mul(diff_a)
315 .ok_or(ARITHMETIC_OVERFLOW)?
316 .checked_shl(64)
317 .ok_or(ARITHMETIC_OVERFLOW)?;
318 let denominator = U256::from(sqrt_price)
319 .checked_mul(U256::from(position.upper_sqrt_price))
320 .ok_or(ARITHMETIC_OVERFLOW)?;
321
322 numerator
323 .checked_div(denominator)
324 .ok_or(ARITHMETIC_OVERFLOW)?
325 .try_into()
326 .map_err(|_| AMOUNT_EXCEEDS_MAX_U64)?
327 };
328
329 let diff_b = U256::from(sqrt_price).saturating_sub(U256::from(position.lower_sqrt_price));
330 let virtual_b = if diff_b == 0 {
331 0u64
332 } else {
333 liquidity
334 .checked_mul(diff_b)
335 .ok_or(ARITHMETIC_OVERFLOW)?
336 .checked_shr(64)
337 .ok_or(ARITHMETIC_OVERFLOW)?
338 .try_into()
339 .map_err(|_| AMOUNT_EXCEEDS_MAX_U64)?
340 };
341
342 Ok((virtual_a, virtual_b))
343}
344
345fn virtual_reserves(positions: &[Position], sqrt_price: u128) -> Result<(u64, u64), CoreError> {
346 let mut virtual_a = 0u64;
347 let mut virtual_b = 0u64;
348 for position in positions {
349 if position.liquidity_share_per_m == 0 {
350 continue;
351 }
352 let (a_amount, b_amount) = virtual_position_reserves(sqrt_price, position)?;
353 virtual_a = virtual_a.checked_add(a_amount).ok_or(ARITHMETIC_OVERFLOW)?;
354 virtual_b = virtual_b.checked_add(b_amount).ok_or(ARITHMETIC_OVERFLOW)?;
355 }
356 Ok((virtual_a, virtual_b))
357}
358
359fn position_reserves(
360 positions: &[Position],
361 sqrt_price: u128,
362 reserves: u64,
363 a_to_b: bool,
364) -> Result<[u64; MAX_POSITIONS], CoreError> {
365 let mut total_virtual_amount: u64 = 0;
367 let mut virtual_amounts: [u64; MAX_POSITIONS] = [0; MAX_POSITIONS];
368 for i in 0..MAX_POSITIONS {
369 let (virtual_a, virtual_b) = virtual_position_reserves(sqrt_price, &positions[i])?;
370 if a_to_b {
371 virtual_amounts[i] = virtual_b;
372 total_virtual_amount = total_virtual_amount
373 .checked_add(virtual_b)
374 .ok_or(ARITHMETIC_OVERFLOW)?;
375 } else {
376 virtual_amounts[i] = virtual_a;
377 total_virtual_amount = total_virtual_amount
378 .checked_add(virtual_a)
379 .ok_or(ARITHMETIC_OVERFLOW)?;
380 }
381 }
382
383 let mut actual_amounts: [u64; MAX_POSITIONS] = [0; MAX_POSITIONS];
385 for i in 0..MAX_POSITIONS {
386 actual_amounts[i] = reserves
387 .checked_mul(virtual_amounts[i])
388 .ok_or(ARITHMETIC_OVERFLOW)?
389 .checked_div(total_virtual_amount)
390 .ok_or(ARITHMETIC_OVERFLOW)?;
391 }
392
393 let total_actual_amount = actual_amounts.iter().sum::<u64>();
395 let mut remaining = reserves.saturating_sub(total_actual_amount);
396 let any_position_has_amount = actual_amounts.iter().any(|&a| a > 0);
400 if any_position_has_amount {
401 while remaining > 0 {
402 for i in 0..MAX_POSITIONS {
403 if remaining == 0 {
404 break;
405 }
406 if actual_amounts[i] > 0 {
407 actual_amounts[i] = actual_amounts[i]
408 .checked_add(1)
409 .ok_or(ARITHMETIC_OVERFLOW)?;
410 remaining -= 1;
411 }
412 }
413 }
414 }
415
416 Ok(actual_amounts)
417}
418
419fn implied_sqrt_price(
420 positions: &[Position],
421 reserves_a: u64,
422 reserves_b: u64,
423) -> Result<u128, CoreError> {
424 if positions.is_empty() {
425 return Err(INVALID_ORACLE_DATA);
426 }
427
428 let mut lowest_sqrt_price = positions
429 .iter()
430 .filter(|p| p.liquidity_share_per_m > 0)
431 .map(|p| p.lower_sqrt_price)
432 .min()
433 .unwrap_or(0);
434 let mut highest_sqrt_price = positions
435 .iter()
436 .filter(|p| p.liquidity_share_per_m > 0)
437 .map(|p| p.upper_sqrt_price)
438 .max()
439 .unwrap_or(u128::MAX);
440
441 if reserves_a == 0 {
443 return Ok(highest_sqrt_price);
444 } else if reserves_b == 0 {
445 return Ok(lowest_sqrt_price);
446 }
447
448 for _ in 0..128 {
450 if highest_sqrt_price <= lowest_sqrt_price + 1 {
451 break;
452 }
453 let diff = highest_sqrt_price.abs_diff(lowest_sqrt_price) >> 1;
454 let mid = lowest_sqrt_price
455 .checked_add(diff)
456 .ok_or(ARITHMETIC_OVERFLOW)?;
457 let (virtual_a, virtual_b) = virtual_reserves(positions, mid)?;
458
459 let lhs = u128::from(virtual_b)
460 .checked_mul(reserves_a as u128)
461 .ok_or(ARITHMETIC_OVERFLOW)?;
462 let rhs = u128::from(virtual_a)
463 .checked_mul(reserves_b as u128)
464 .ok_or(ARITHMETIC_OVERFLOW)?;
465
466 if lhs < rhs {
467 lowest_sqrt_price = mid;
468 } else {
469 highest_sqrt_price = mid;
470 }
471 }
472
473 let diff = highest_sqrt_price.abs_diff(lowest_sqrt_price) >> 1;
474 let sqrt_price = lowest_sqrt_price
475 .checked_add(diff)
476 .ok_or(ARITHMETIC_OVERFLOW)?;
477
478 Ok(sqrt_price)
479}
480
481#[inline(always)]
482fn bin_position_overlap(
483 bin_index: usize,
484 mid_sqrt_price: u128,
485 end_sqrt_price: u128,
486 position: &Position,
487) -> Result<u128, CoreError> {
488 let (bin_start, bin_end) = bin_boundaries(bin_index, mid_sqrt_price, end_sqrt_price)?;
489 let (lo, hi) = if bin_start <= bin_end {
490 (bin_start, bin_end)
491 } else {
492 (bin_end, bin_start)
493 };
494 let overlap_start = lo.max(position.lower_sqrt_price);
495 let overlap_end = hi.min(position.upper_sqrt_price);
496 if overlap_end > overlap_start {
497 Ok(overlap_end - overlap_start)
498 } else {
499 Ok(0)
500 }
501}
502
503fn bin_boundaries(
504 i: usize,
505 sqrt_price: u128,
506 end_sqrt_price: u128,
507) -> Result<(u128, u128), CoreError> {
508 let sqrt_price_diff = end_sqrt_price.abs_diff(sqrt_price);
509
510 let start_sqrt_price_delta = U256::from(sqrt_price_diff)
511 .checked_mul(U256::from(i as u128))
512 .ok_or(ARITHMETIC_OVERFLOW)?
513 .checked_div(U256::from(LIQUIDITY_LEVELS as u128))
514 .ok_or(ARITHMETIC_OVERFLOW)?;
515 let end_sqrt_price_delta = U256::from(sqrt_price_diff)
516 .checked_mul(U256::from(i as u128 + 1))
517 .ok_or(ARITHMETIC_OVERFLOW)?
518 .checked_div(U256::from(LIQUIDITY_LEVELS as u128))
519 .ok_or(ARITHMETIC_OVERFLOW)?;
520
521 let (start_sqrt_price, end_sqrt_price) = if end_sqrt_price > sqrt_price {
522 let start = U256::from(sqrt_price)
523 .checked_add(start_sqrt_price_delta)
524 .ok_or(ARITHMETIC_OVERFLOW)?;
525
526 let end = U256::from(sqrt_price)
527 .checked_add(end_sqrt_price_delta)
528 .ok_or(ARITHMETIC_OVERFLOW)?;
529
530 (start, end)
531 } else {
532 let start = U256::from(sqrt_price)
533 .checked_sub(start_sqrt_price_delta)
534 .ok_or(ARITHMETIC_OVERFLOW)?;
535
536 let end = U256::from(sqrt_price)
537 .checked_sub(end_sqrt_price_delta)
538 .ok_or(ARITHMETIC_OVERFLOW)?;
539
540 (start, end)
541 };
542
543 Ok((
544 start_sqrt_price
545 .try_into()
546 .map_err(|_| AMOUNT_EXCEEDS_MAX_U128)?,
547 end_sqrt_price
548 .try_into()
549 .map_err(|_| AMOUNT_EXCEEDS_MAX_U128)?,
550 ))
551}
552
553fn bin_price(
558 i: usize,
559 start_sqrt_price: u128,
560 end_sqrt_price: u128,
561 spread: i32,
562) -> Result<u128, CoreError> {
563 let sqrt_price_diff = end_sqrt_price.abs_diff(start_sqrt_price);
564 let sqrt_price_delta = U256::from(sqrt_price_diff)
565 .checked_mul(U256::from(i as u128))
566 .ok_or(ARITHMETIC_OVERFLOW)?
567 .checked_div(U256::from(LIQUIDITY_LEVELS as u128 - 1))
568 .ok_or(ARITHMETIC_OVERFLOW)?;
569
570 let sqrt_price = if end_sqrt_price > start_sqrt_price {
571 U256::from(start_sqrt_price)
572 .checked_add(sqrt_price_delta)
573 .ok_or(ARITHMETIC_OVERFLOW)?
574 } else {
575 U256::from(start_sqrt_price)
576 .checked_sub(sqrt_price_delta)
577 .ok_or(ARITHMETIC_OVERFLOW)?
578 };
579
580 let product = sqrt_price
581 .checked_mul(sqrt_price)
582 .ok_or(ARITHMETIC_OVERFLOW)?;
583
584 let quotient = product.checked_shr(64).ok_or(ARITHMETIC_OVERFLOW)?;
585 let remainder = product & U256::from(u64::MAX);
590
591 let base_price: u128 = if end_sqrt_price > start_sqrt_price && remainder > 0 {
592 (quotient + 1)
593 .try_into()
594 .map_err(|_| AMOUNT_EXCEEDS_MAX_U128)?
595 } else {
596 quotient.try_into().map_err(|_| AMOUNT_EXCEEDS_MAX_U128)?
597 };
598
599 let product = U256::from(base_price)
600 .checked_mul(U256::from(spread as u128))
601 .ok_or(ARITHMETIC_OVERFLOW)?;
602 let quotient = product
603 .checked_div(U256::from(PER_M_DENOMINATOR as u128))
604 .ok_or(ARITHMETIC_OVERFLOW)?;
605 let remainder = product
606 .checked_rem(U256::from(PER_M_DENOMINATOR as u128))
607 .ok_or(ARITHMETIC_OVERFLOW)?;
608
609 let price = if end_sqrt_price > start_sqrt_price && remainder > 0 {
610 (quotient + 1)
611 .try_into()
612 .map_err(|_| AMOUNT_EXCEEDS_MAX_U128)?
613 } else {
614 quotient.try_into().map_err(|_| AMOUNT_EXCEEDS_MAX_U128)?
615 };
616
617 Ok(price)
618}
619
620#[cfg(test)]
621mod tests {
622 use super::*;
623 use rstest::rstest;
624
625 #[rstest]
626 #[case(Position::full_range(PER_M_DENOMINATOR as u32), 100, 100, 18446738426575981041)]
627 #[case(Position::full_range(PER_M_DENOMINATOR as u32), 150, 50, 10650233338091508966)]
628 #[case(Position::full_range(PER_M_DENOMINATOR as u32), 50, 150, 31950688720003928217)]
629 #[case(Position::full_range(PER_M_DENOMINATOR as u32), 200, 0, MIN_SQRT_PRICE)]
630 #[case(Position::full_range(PER_M_DENOMINATOR as u32), 0, 200, MAX_SQRT_PRICE)]
631 #[case(Position::new((1 << 64) / 2, (1 << 64) * 2, PER_M_DENOMINATOR as u32).unwrap(), 100, 100, 18446744073709551615)]
632 #[case(Position::new((1 << 64) / 3, (1 << 64) * 2, PER_M_DENOMINATOR as u32).unwrap(), 100, 100, 16973448724429105277)]
633 #[case(Position::new((1 << 64) / 2, (1 << 64) * 3, PER_M_DENOMINATOR as u32).unwrap(), 100, 100, 20047903282541897858)]
634 #[case(Position::new((1 << 64) / 2, (1 << 64) * 2, PER_M_DENOMINATOR as u32).unwrap(), 150, 50, 14159573177026862144)]
635 #[case(Position::new((1 << 64) / 2, (1 << 64) * 2, PER_M_DENOMINATOR as u32).unwrap(), 50, 150, 24031964994045732128)]
636 #[case(Position::new((1 << 64) / 2, (1 << 64) * 2, PER_M_DENOMINATOR as u32).unwrap(), 200, 0, (1 << 64) / 2)]
637 #[case(Position::new((1 << 64) / 2, (1 << 64) * 2, PER_M_DENOMINATOR as u32).unwrap(), 0, 200, (1 << 64) * 2)]
638 fn test_implied_sqrt_price_single_position(
639 #[case] position: Position,
640 #[case] reserves_a: u64,
641 #[case] reserves_b: u64,
642 #[case] expected: u128,
643 ) {
644 let sqrt_price = implied_sqrt_price(&[position], reserves_a, reserves_b).unwrap();
645 assert_eq!(sqrt_price, expected);
646 }
647
648 #[rstest]
649 #[case(100, 100, 18446744073709551615)]
650 #[case(150, 50, 13516113850247725564)]
651 #[case(50, 150, 25176050652658693911)]
652 #[case(200, 0, 6148914691236517205)]
653 #[case(0, 200, 55340232221128654848)]
654 fn test_implied_sqrt_price_multiple_positions(
655 #[case] reserves_a: u64,
656 #[case] reserves_b: u64,
657 #[case] expected: u128,
658 ) {
659 let positions = vec![
660 Position::new((1 << 64) / 2, (1 << 64) * 2, 300_000).unwrap(),
661 Position::new((1 << 64) / 2, (1 << 64) * 3, 200_000).unwrap(),
662 Position::new((1 << 64) / 3, (1 << 64) * 2, 200_000).unwrap(),
663 Position::new((1 << 64) / 3, (1 << 64) * 3, 300_000).unwrap(),
664 ];
665 let sqrt_price = implied_sqrt_price(&positions, reserves_a, reserves_b).unwrap();
666 assert_eq!(sqrt_price, expected);
667 }
668
669 #[rstest]
670 #[case(LiquidityType::ConstantProduct, 100, 100, Ok(18446732779444139232))]
671 #[case(LiquidityType::ConstantProduct, 150, 50, Ok(6148915478122426543))]
672 #[case(LiquidityType::ConstantProduct, 50, 150, Ok(55340200178605227858))]
673 #[case(LiquidityType::ConstantProduct, 200, 0, Ok(1728767))]
674 #[case(
675 LiquidityType::ConstantProduct,
676 0,
677 200,
678 Ok(196835207006294262292126795817913)
679 )]
680 #[case(LiquidityType::ConcentratedLiquidity { lower_sqrt_price: (1 << 64) / 2, upper_sqrt_price: (1 << 64) * 2 }, 100, 100, Ok(18446744073709551614))]
681 #[case(LiquidityType::ConcentratedLiquidity { lower_sqrt_price: (1 << 64) / 2, upper_sqrt_price: (1 << 64) * 2 }, 150, 50, Ok(10868775094100403166))]
682 #[case(LiquidityType::ConcentratedLiquidity { lower_sqrt_price: (1 << 64) / 2, upper_sqrt_price: (1 << 64) * 2 }, 50, 150, Ok(31308253595719773170))]
683 #[case(LiquidityType::ConcentratedLiquidity { lower_sqrt_price: (1 << 64) / 2, upper_sqrt_price: (1 << 64) * 2 }, 200, 0, Ok(4611686018427387904))]
684 #[case(LiquidityType::ConcentratedLiquidity { lower_sqrt_price: (1 << 64) / 2, upper_sqrt_price: (1 << 64) * 2 }, 0, 200, Ok(73786976294838206464))]
685 fn test_amm_price(
686 #[case] liquidity_type: LiquidityType,
687 #[case] reserves_a: u64,
688 #[case] reserves_b: u64,
689 #[case] expected: Result<u128, CoreError>,
690 ) {
691 let price = amm_price(liquidity_type, reserves_a, reserves_b);
692 assert_eq!(price, expected);
693 }
694
695 #[rstest]
696 #[case(0, MIN_SQRT_PRICE, Ok((18446744073709551616, 17870283497879106233)))]
697 #[case(1, MIN_SQRT_PRICE, Ok((17870283497879106233, 17293822922048660849)))]
698 #[case(0, MAX_SQRT_PRICE, Ok((18446744073709551616, 1883065362968454170744257)))]
699 #[case(1, MAX_SQRT_PRICE, Ok((1883065362968454170744257, 3766112279192834631936899)))]
700 fn test_bin_boundaries(
701 #[case] i: usize,
702 #[case] end_sqrt_price: u128,
703 #[case] expected: Result<(u128, u128), CoreError>,
704 ) {
705 let result = bin_boundaries(i, 1 << 64, end_sqrt_price);
706 assert_eq!(result, expected);
707 }
708
709 #[rstest]
710 #[case(0, 1 << 64, (1 << 64) / 2, 999_000, Ok(18428297329635842064))]
711 #[case(31, 1 << 64, (1 << 64) / 2, 999_000, Ok(4607074332408960516))]
712 #[case(0, 1 << 64, (1 << 64) / 2, 998_000, Ok(18409850585562132512))]
713 #[case(31, 1 << 64, (1 << 64) / 2, 998_000, Ok(4602462646390533128))]
714 #[case(0, 1 << 64, (1 << 64) / 2, 1_001_000, Ok(18465190817783261167))]
715 #[case(31, 1 << 64, (1 << 64) / 2, 1_002_000, Ok(4620909390464242679))]
716 #[case(0, 1 << 64, (1 << 64) * 2, 999_000, Ok(18428297329635842065))]
717 #[case(31, 1 << 64, (1 << 64) * 2, 999_000, Ok(73713189318543368258))]
718 #[case(0, 1 << 64, (1 << 64) * 2, 998_000, Ok(18409850585562132513))]
719 #[case(31, 1 << 64, (1 << 64) * 2, 998_000, Ok(73639402342248530052))]
720 #[case(0, 1 << 64, (1 << 64) * 2, 1_001_000, Ok(18465190817783261168))]
721 #[case(31, 1 << 64, (1 << 64) * 2, 1_001_000, Ok(73860763271133044671))]
722 #[case(0, 1 << 64, (1 << 64) * 2, 1_002_000, Ok(18483637561856970720))]
723 #[case(31, 1 << 64, (1 << 64) * 2, 1_002_000, Ok(73934550247427882877))]
724 fn test_bin_price(
725 #[case] i: usize,
726 #[case] start_sqrt_price: u128,
727 #[case] end_sqrt_price: u128,
728 #[case] spread: i32,
729 #[case] expected: Result<u128, CoreError>,
730 ) {
731 let result = bin_price(i, start_sqrt_price, end_sqrt_price, spread);
732 assert_eq!(result, expected);
733 }
734
735 #[rstest]
743 #[case(1u128 << 44, 1 << 24)]
744 #[case(1u128 << 50, 1 << 36)]
745 #[case(1u128 << 60, 1 << 56)]
746 fn test_bin_price_no_spurious_round_up_on_exact_division(
747 #[case] start_sqrt_price: u128,
748 #[case] expected: u128,
749 ) {
750 let result = bin_price(
751 0,
752 start_sqrt_price,
753 start_sqrt_price + 1, PER_M_DENOMINATOR, );
756 assert_eq!(result, Ok(expected));
757 }
758
759 #[test]
760 fn test_amm_liquidity_a_to_b() {
761 let liquidity = amm_liquidity(
762 LiquidityType::ConstantProduct,
763 10000,
764 20000,
765 QuoteType::TokenAExactIn,
766 1000,
767 1000,
768 )
769 .unwrap()
770 .as_slice()
771 .to_vec();
772 let expected = vec![
774 (18262265451649697839, 31),
775 (17103058524375092466, 31),
776 (15981858369775465113, 31),
777 (14898664987850815784, 31),
778 (13853478378601144476, 31),
779 (12846298542026451190, 31),
780 (11877125478126735926, 31),
781 (10945959186901998683, 31),
782 (10052799668352239462, 31),
783 (9197646922477458264, 31),
784 (8380500949277655088, 31),
785 (7601361748752829935, 31),
786 (6860229320902982803, 31),
787 (6157103665728113694, 31),
788 (5491984783228222606, 31),
789 (4864872673403309539, 31),
790 (4275767336253374494, 31),
791 (3724668771778417472, 31),
792 (3211576979978438473, 31),
793 (2736491960853437495, 31),
794 (2299413714403414540, 31),
795 (1900342240628369606, 31),
796 (1539277539528302694, 31),
797 (1216219611103213804, 31),
798 (931168455353102936, 32),
799 (684124072277970090, 32),
800 (475086461877815267, 32),
801 (304055624152638466, 32),
802 (171031559102439686, 32),
803 (76014266727218928, 32),
804 (19003747026976192, 32),
805 (1711479, 32),
806 ];
807
808 assert_eq!(liquidity, expected);
809 }
810
811 #[test]
812 fn test_amm_liquidity_concentrated_a_to_b() {
813 let liquidity = amm_liquidity(
814 LiquidityType::ConcentratedLiquidity {
815 lower_sqrt_price: (1 << 64) / 2,
816 upper_sqrt_price: (1 << 64) * 2,
817 },
818 10000,
819 20000,
820 QuoteType::TokenAExactIn,
821 1000,
822 1000,
823 )
824 .unwrap()
825 .as_slice()
826 .to_vec();
827 let expected = vec![
829 (18262276632972456097, 31),
830 (17677921787536552847, 31),
831 (17103068646904485422, 31),
832 (16537717211076253820, 31),
833 (15981867480051858042, 31),
834 (15435519453831298092, 31),
835 (14898673132414573967, 31),
836 (14371328515801685667, 31),
837 (13853485603992633191, 31),
838 (13345144396987416541, 31),
839 (12846304894786035717, 31),
840 (12356967097388490717, 31),
841 (11877131004794781542, 31),
842 (11406796617004908193, 31),
843 (10945963934018870670, 31),
844 (10494632955836668971, 31),
845 (10052803682458303097, 31),
846 (9620476113883773049, 31),
847 (9197650250113078826, 31),
848 (8784326091146220429, 31),
849 (8380503636983197855, 31),
850 (7986182887624011109, 31),
851 (7601363843068660187, 31),
852 (7226046503317145091, 31),
853 (6860230868369465819, 32),
854 (6503916938225622372, 32),
855 (6157104712885614751, 32),
856 (5819794192349442955, 32),
857 (5491985376617106984, 32),
858 (5173678265688606840, 32),
859 (4864872859563942520, 32),
860 (4565569158243114024, 32),
861 ];
862
863 assert_eq!(liquidity, expected);
864 }
865
866 #[test]
867 fn test_amm_liquidity_b_to_a() {
868 let liquidity = amm_liquidity(
869 LiquidityType::ConstantProduct,
870 10000,
871 20000,
872 QuoteType::TokenBExactIn,
873 1000,
874 1000,
875 )
876 .unwrap()
877 .as_slice()
878 .to_vec();
879 let expected = vec![
881 (18815667435033022018, 31),
882 (208923620106592073258895578064, 31),
883 (835686549707659367878445172487, 31),
884 (1880288788822017551293681805289, 31),
885 (3342730337449666623504606336313, 31),
886 (5223011195590606584511217260829, 31),
887 (7521131363244837434313515223724, 31),
888 (10237090840412359172911501729725, 31),
889 (13370889627093171800305175704025, 31),
890 (16922527723287275316494535211973, 31),
891 (20892005128994669721479581758299, 31),
892 (25279321844215355015260315343002, 31),
893 (30084477868949331197836738545617, 31),
894 (35307473203196598269208849216532, 31),
895 (40948307846957156229376644346290, 31),
896 (47006981800231005078340126514425, 31),
897 (53483495063018144816099299160316, 31),
898 (60377847635318575442654155620167, 31),
899 (67690039517132296958004699118395, 31),
900 (75420070708459309362150933739263, 31),
901 (83567941209299612655092855828431, 31),
902 (92133651019653206836830460871714, 31),
903 (101117200139520091907363752953373, 31),
904 (110518588568900267866692732073410, 31),
905 (120337816307793734714817403390893, 32),
906 (130574883356200492451737762176676, 32),
907 (141229789714120541077453802841767, 32),
908 (152302535381553880591965530545236, 32),
909 (163793120358500510995272951305994, 32),
910 (175701544644960432287376053301180, 32),
911 (188027808240933644468274842334742, 32),
912 (200771911146420147537969331734273, 32),
913 ];
914
915 assert_eq!(liquidity, expected);
916 }
917
918 #[test]
919 fn test_amm_liquidity_concentrated_b_to_a() {
920 let liquidity = amm_liquidity(
921 LiquidityType::ConcentratedLiquidity {
922 lower_sqrt_price: (1 << 64) / 2,
923 upper_sqrt_price: (1 << 64) * 2,
924 },
925 10000,
926 20000,
927 QuoteType::TokenBExactIn,
928 1000,
929 1000,
930 )
931 .unwrap()
932 .as_slice()
933 .to_vec();
934 let expected = vec![
936 (18815678955183742648, 31),
937 (20049172996990793413, 31),
938 (21321825579807591824, 31),
939 (22633636703634137876, 31),
940 (23984606368470431575, 31),
941 (25374734574316472913, 31),
942 (26804021321172261898, 31),
943 (28272466609037798524, 31),
944 (29780070437913082795, 31),
945 (31326832807798114708, 31),
946 (32912753718692894266, 31),
947 (34537833170597421466, 31),
948 (36202071163511696311, 31),
949 (37905467697435718797, 31),
950 (39648022772369488929, 31),
951 (41429736388313006701, 31),
952 (43250608545266272120, 31),
953 (45110639243229285179, 31),
954 (47009828482202045885, 31),
955 (48948176262184554231, 31),
956 (50925682583176810224, 31),
957 (52942347445178813857, 31),
958 (54998170848190565136, 31),
959 (57093152792212064055, 31),
960 (59227293277243310621, 32),
961 (61400592303284304828, 32),
962 (63613049870335046681, 32),
963 (65864665978395536173, 32),
964 (68155440627465773314, 32),
965 (70485373817545758093, 32),
966 (72854465548635490520, 32),
967 (75262715820734970594, 32),
968 ];
969
970 assert_eq!(liquidity, expected);
971 }
972
973 #[test]
974 fn test_narrow_range_rejected() {
975 let result = amm_liquidity(
977 LiquidityType::ConcentratedLiquidity {
978 lower_sqrt_price: 1 << 64,
979 upper_sqrt_price: ((1 << 64) * 1_000_001) / 1_000_000,
980 },
981 0,
982 0,
983 QuoteType::TokenAExactIn,
984 1_000_000,
985 1_000_000,
986 );
987 assert_eq!(result, Err(INVALID_ORACLE_DATA));
988 }
989
990 #[test]
991 fn test_min_valid_range_accepted() {
992 let result = Position::new(
994 1 << 64,
995 (1 << 64) + MIN_SQRT_PRICE_RANGE,
996 PER_M_DENOMINATOR as u32,
997 );
998 assert!(result.is_ok());
999 }
1000
1001 #[test]
1011 fn test_tiny_reserves_do_not_loop_forever() {
1012 let lp = LiquidityType::ConcentratedLiquidity {
1013 lower_sqrt_price: 1 << 64,
1014 upper_sqrt_price: (1 << 64) * 2,
1015 };
1016 for (ra, rb) in [(1u64, 1), (1, 0), (0, 1), (1, 100), (100, 1)] {
1017 for qt in [
1018 QuoteType::TokenAExactIn,
1019 QuoteType::TokenBExactIn,
1020 QuoteType::TokenAExactOut,
1021 QuoteType::TokenBExactOut,
1022 ] {
1023 let _ = amm_liquidity(lp, 0, 0, qt, ra, rb);
1025 }
1026 }
1027 }
1028
1029 #[test]
1030 fn test_below_min_range_rejected() {
1031 let result = Position::new(
1033 1 << 64,
1034 (1 << 64) + MIN_SQRT_PRICE_RANGE - 1,
1035 PER_M_DENOMINATOR as u32,
1036 );
1037 assert!(result.is_err());
1038 }
1039
1040 #[test]
1047 fn test_partial_overlap_bins_preserve_total_liquidity() {
1048 let lp = LiquidityType::ConcentratedLiquidity {
1049 lower_sqrt_price: ((1u128 << 64) as f64 * 0.997f64.sqrt()) as u128,
1050 upper_sqrt_price: ((1u128 << 64) as f64 * 1.003f64.sqrt()) as u128,
1051 };
1052 let reserves_a: u64 = 1_000_000_000_000;
1053 let reserves_b: u64 = 1_000_000_000_000;
1054
1055 let ask =
1056 amm_liquidity(lp, 10, 10, QuoteType::TokenBExactIn, reserves_a, reserves_b).unwrap();
1057 let bid =
1058 amm_liquidity(lp, 10, 10, QuoteType::TokenAExactIn, reserves_a, reserves_b).unwrap();
1059
1060 let total_ask: u64 = ask.as_slice().iter().map(|(_, a)| *a).sum();
1061 let total_bid: u64 = bid.as_slice().iter().map(|(_, a)| *a).sum();
1062 assert_eq!(total_ask, reserves_a, "ask side must distribute all of A");
1063 assert_eq!(total_bid, reserves_b, "bid side must distribute all of B");
1064 }
1065
1066 #[test]
1079 fn coverage_predicate_includes_partial_overlap_bins() {
1080 let mid: u128 = 1u128 << 64;
1086 let bin_width: u128 = MIN_SQRT_PRICE_RANGE;
1087 let half_bin: u128 = bin_width / 2;
1088 let position = Position::new(
1089 mid - bin_width,
1090 mid + 5 * bin_width + half_bin,
1091 PER_M_DENOMINATOR as u32,
1092 )
1093 .unwrap();
1094 let end = mid + bin_width * (LIQUIDITY_LEVELS as u128);
1095
1096 let mut full_containment_count = 0;
1097 let mut partial_overlap_count = 0;
1098 let mut no_overlap_count = 0;
1099
1100 for i in 0..LIQUIDITY_LEVELS {
1101 let (start_s, end_s) = bin_boundaries(i, mid, end).unwrap();
1102 let (lo, hi) = if start_s <= end_s {
1103 (start_s, end_s)
1104 } else {
1105 (end_s, start_s)
1106 };
1107
1108 let old_predicate = position.lower_sqrt_price <= lo && hi <= position.upper_sqrt_price;
1110 let overlap_start = lo.max(position.lower_sqrt_price);
1112 let overlap_end = hi.min(position.upper_sqrt_price);
1113 let new_predicate = overlap_end > overlap_start;
1114 let overlap_width = bin_position_overlap(i, mid, end, &position).unwrap();
1116
1117 assert_eq!(
1119 new_predicate,
1120 overlap_width > 0,
1121 "bin {i}: new predicate must agree with bin_position_overlap"
1122 );
1123
1124 if old_predicate {
1126 assert!(
1127 new_predicate,
1128 "bin {i}: full-containment implies positive overlap"
1129 );
1130 }
1131
1132 if old_predicate {
1133 full_containment_count += 1;
1134 } else if new_predicate {
1135 partial_overlap_count += 1;
1136 } else {
1137 no_overlap_count += 1;
1138 }
1139 }
1140
1141 assert!(
1143 full_containment_count > 0,
1144 "scenario must include fully-contained bins"
1145 );
1146 assert!(
1147 no_overlap_count > 0,
1148 "scenario must include no-overlap bins"
1149 );
1150 assert!(
1151 partial_overlap_count > 0,
1152 "scenario must include at least one partial-overlap bin — \
1153 this is the case the old predicate dropped"
1154 );
1155 }
1156}