Skip to main content

riptide_amm_math/oracle/
amm.rs

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; // ~1e-13
14const MAX_SQRT_PRICE: u128 = 60257519765924248467716150; // ~2e13
15
16#[cfg(feature = "wasm")]
17use riptide_amm_macros::wasm_expose;
18
19const MAX_POSITIONS: usize = 16;
20
21// Minimum sqrt price range width to ensure virtual reserves are non-zero.
22// Derived from: L * (upper - lower) >> 64 >= 1, where L = PER_M_DENOMINATOR (1_000_000).
23// So (upper - lower) >= ceil(2^64 / 1_000_000) = 18_446_744_073_710.
24const MIN_SQRT_PRICE_RANGE: u128 = 18_446_744_073_710;
25
26// A liquidity position here works slightly differently than a regular AMM.
27// Since the liquidity is discretized into 32 levels, we need to calculate the price for each level.
28// A normal AMM would have a continuous price function across the range.
29// A position here is set up in such a way that the first price is always
30// the mid price (sans spread) and the last price is always the outer edge of the position's price
31// range.
32
33#[derive(Debug, Clone, Copy, Eq, PartialEq)]
34#[cfg_attr(true, derive(BorshDeserialize, BorshSerialize))]
35#[cfg_attr(feature = "wasm", wasm_expose)]
36pub enum LiquidityType {
37    // CP shouldn't be used since it is wildly inaccurate if spread across 32 liquidity levels
38    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
113// AMM oracle: current price P is implied by reserves and positions (no stored price).
114// We solve for P using the side being built only: bid => Y(s) = reserves_b, ask => X(s) =
115// reserves_a. Then L_total is scaled so that virtual reserve matches the reserve for that side.
116
117pub(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    // Calculate the price for each liquidity level
200    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 each position, mark the bin as contributing whenever there is
204        // ANY non-zero overlap between the bin's sqrt range and the position's
205        // range — not only when the bin is fully contained. A partial overlap
206        // should still receive its proportional share of the position's
207        // reserves; treating it as zero would dilute liquidity at every bin
208        // that straddles a position boundary (see audit P2-L-02).
209        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    // Add the amounts to the liquidity levels proportional to overlap width.
221    // Two-pass approach to avoid storing a separate overlap_widths array on the stack.
222    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        // Pass 1: compute total overlap width
232        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        // Pass 2: distribute proportionally
251        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    // Normalize the total liquidity to reserves
271    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    // If every bin floored to zero, there is nothing to distribute the
278    // residual onto — an entry-only loop would spin forever. Skip the
279    // normalization in that case; the unallocated residual is at most
280    // a few units (bounded by per-bin floor errors).
281    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    // Calculate the virtual amounts for each position
366    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    // Calculate the actual amounts for each position
384    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    // Normalize sum(actual_a_amounts) to reserves_a so that we're using all of the reserves
394    let total_actual_amount = actual_amounts.iter().sum::<u64>();
395    let mut remaining = reserves.saturating_sub(total_actual_amount);
396    // Same guard as in `amm_liquidity`: if every position floored to zero
397    // the inner check would never fire and the outer loop would spin
398    // forever. Skip the normalization; the residual is bounded.
399    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 one reserve is zero, the sqrt price is the highest or lowest price.
442    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    // Binary search for P where virtual_b(P)/virtual_a(P) = reserves_b/reserves_a.
449    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
553// Take the first price in the bin and apply the spread. If we wanted to be more true to an actual
554// AMM, we would take the geometric mean. The problem if we do that is that the first bin's price
555// would not be equal to the mid price meaning you would get an implicit spread which becomes larger
556// the larger the lp range.
557fn 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    // Only the low 64 bits were discarded by the right shift, so the remainder of the
586    // division by 2^64 is bits[0..64]. Masking with u128::MAX previously bled the low
587    // half of the quotient into the remainder check and forced a spurious round-up
588    // whenever bits[64..128] of the product were set (e.g. sqrt_price = 2^44).
589    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    // Q64.64 squaring is exact when the sqrt price is a pure power of two whose square
736    // is at least 2^64 (e.g. sqrt_price = 2^44 -> product = 2^88, quotient = 2^24,
737    // remainder = 0). The previous remainder mask (`u128::MAX`) bled the low 64 bits of
738    // the quotient back into the remainder check and forced a spurious +1 round-up on
739    // increasing bins. With i=0 the sqrt-price delta is zero, so the result reduces to
740    // the squared start_sqrt_price; combined with spread = PER_M_DENOMINATOR the second
741    // division is also exact, leaving any +1 here attributable solely to the masking bug.
742    #[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, // increasing direction so the round-up branch is reachable
754            PER_M_DENOMINATOR,    // spread of 1.0 -> second division is exact
755        );
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        // Prices starts ~ 0.99 and ends ~ 9e-14
773        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        // Prices starts ~ 0.99 and ends ~ 0.2475
828        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        // Prices starts ~ 1.02 and ends ~ 1e13
880        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        // Prices starts ~ 1.02 and ends ~ 4.08
935        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        // Range width = ~1e-6 in sqrt-price space, too narrow to produce non-zero virtual reserves
976        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        // Range width exactly at MIN_SQRT_PRICE_RANGE should pass Position::new validation
993        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    // Regression for [P1-L-09] — ensure tiny reserves do not trigger an
1002    // infinite loop in the reserve-normalization stage. With reserves much
1003    // smaller than the total virtual amounts, `position_reserves` and the
1004    // distribution loop in `amm_liquidity` could previously have all entries
1005    // floor to zero, leaving a non-zero `remaining` with no bin to seed.
1006    //
1007    // The function may legitimately return Err for degenerate inputs (e.g.
1008    // single-side empty); what matters is that the call terminates instead
1009    // of looping forever. Test harness timeout will catch any regression.
1010    #[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                // Discard the result — pass only requires termination.
1024                let _ = amm_liquidity(lp, 0, 0, qt, ra, rb);
1025            }
1026        }
1027    }
1028
1029    #[test]
1030    fn test_below_min_range_rejected() {
1031        // Range width below MIN_SQRT_PRICE_RANGE should be rejected
1032        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    // Regression for [P2-L-02] — bins that only partially overlap a position's
1041    // sqrt range must still receive a proportional share of that position's
1042    // reserves. We verify the property indirectly: the total liquidity
1043    // distributed across the 32 bins must equal the available reserves on
1044    // each side. If any partial-overlap bin were dropped (the previous
1045    // contained-only check), this sum would fall short of `reserves`.
1046    #[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    /// Reproduces the partial-overlap coverage gap.
1067    ///
1068    /// `bin_position_overlap` is the source of truth for "does this bin
1069    /// receive any of this position's reserves?" — it returns positive width
1070    /// whenever the bin and the position have ANY overlap (including the
1071    /// boundary-straddling partial-overlap case).
1072    ///
1073    /// The fix replaces the coverage bitmap predicate with the same
1074    /// semantics. Every bin that `bin_position_overlap` reports a positive
1075    /// width for must now be marked as covered. The OLD full-containment
1076    /// predicate would return `false` here for those partial-overlap bins,
1077    /// causing under-allocation downstream.
1078    #[test]
1079    fn coverage_predicate_includes_partial_overlap_bins() {
1080        // Bin grid: 32 bins spanning [mid, end] with bin width = MIN_SQRT_PRICE_RANGE.
1081        // Position upper sits HALF a bin past bin index 5, so bin 5 straddles
1082        // the boundary (partial overlap), bins 0..=4 are fully contained, and
1083        // bins 6..=31 are entirely outside the position. Position lower sits
1084        // below mid so the lower edge does not constrain bin coverage.
1085        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            // OLD predicate (buggy) — full containment.
1109            let old_predicate = position.lower_sqrt_price <= lo && hi <= position.upper_sqrt_price;
1110            // NEW predicate — positive overlap.
1111            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            // bin_position_overlap is the source of truth.
1115            let overlap_width = bin_position_overlap(i, mid, end, &position).unwrap();
1116
1117            // Invariant 1: new predicate matches `bin_position_overlap > 0`.
1118            assert_eq!(
1119                new_predicate,
1120                overlap_width > 0,
1121                "bin {i}: new predicate must agree with bin_position_overlap"
1122            );
1123
1124            // Invariant 2: full containment is a STRICT subset of positive overlap.
1125            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        // Sanity: the constructed scenario actually exercises all three regimes.
1142        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}