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, check if the bin is fully contained within the position's range.
204        // This avoids phantom liquidity at boundary bins where the position contributes
205        // zero reserves. The comparison order depends on whether bins ascend or descend.
206        for j in 0..MAX_POSITIONS {
207            position_bin_indexes[j][i] = if start_sqrt_price < end_sqrt_price {
208                positions[j].lower_sqrt_price <= start_sqrt_price
209                    && end_sqrt_price <= positions[j].upper_sqrt_price
210            } else {
211                positions[j].lower_sqrt_price <= end_sqrt_price
212                    && start_sqrt_price <= positions[j].upper_sqrt_price
213            };
214        }
215
216        liquidity_bins[i].0 = bin_price(i, start_sqrt_price, end_sqrt_price, spread)?;
217    }
218
219    let position_amounts =
220        position_reserves(&positions, mid_sqrt_price, reserves, quote_type.a_to_b())?;
221
222    // Add the amounts to the liquidity levels proportional to overlap width.
223    // Two-pass approach to avoid storing a separate overlap_widths array on the stack.
224    for i in 0..MAX_POSITIONS {
225        let num_bins = position_bin_indexes[i].iter().filter(|&b| *b).count();
226        if num_bins == 0 {
227            continue;
228        }
229        if positions[i].liquidity_share_per_m == 0 {
230            continue;
231        }
232
233        // Pass 1: compute total overlap width
234        let mut total_overlap: u128 = 0;
235        for j in 0..LIQUIDITY_LEVELS {
236            if position_bin_indexes[i][j] {
237                total_overlap = total_overlap
238                    .checked_add(bin_position_overlap(
239                        j,
240                        mid_sqrt_price,
241                        end_sqrt_price,
242                        &positions[i],
243                    )?)
244                    .ok_or(ARITHMETIC_OVERFLOW)?;
245            }
246        }
247
248        if total_overlap == 0 {
249            continue;
250        }
251
252        // Pass 2: distribute proportionally
253        for j in 0..LIQUIDITY_LEVELS {
254            if position_bin_indexes[i][j] {
255                let width = bin_position_overlap(j, mid_sqrt_price, end_sqrt_price, &positions[i])?;
256                if width > 0 {
257                    let amount = U256::from(position_amounts[i])
258                        .checked_mul(U256::from(width))
259                        .ok_or(ARITHMETIC_OVERFLOW)?
260                        .checked_div(U256::from(total_overlap))
261                        .ok_or(ARITHMETIC_OVERFLOW)?;
262                    let amount: u64 = amount.try_into().map_err(|_| AMOUNT_EXCEEDS_MAX_U64)?;
263                    liquidity_bins[j].1 = liquidity_bins[j]
264                        .1
265                        .checked_add(amount)
266                        .ok_or(ARITHMETIC_OVERFLOW)?;
267                }
268            }
269        }
270    }
271
272    // Normalize the total liquidity to reserves
273    let total_amount = liquidity_bins
274        .iter()
275        .map(|b: &(u128, u64)| b.1)
276        .sum::<u64>();
277
278    let mut remaining = reserves - total_amount;
279    while remaining > 0 {
280        for i in (0..LIQUIDITY_LEVELS).rev() {
281            if remaining == 0 {
282                break;
283            }
284            if liquidity_bins[i].1 > 0 {
285                liquidity_bins[i].1 = liquidity_bins[i]
286                    .1
287                    .checked_add(1)
288                    .ok_or(ARITHMETIC_OVERFLOW)?;
289                remaining -= 1;
290            }
291        }
292    }
293
294    Ok(SingleSideLiquidity::from_slice(&liquidity_bins))
295}
296
297fn virtual_position_reserves(
298    sqrt_price: u128,
299    position: &Position,
300) -> Result<(u64, u64), CoreError> {
301    let liquidity = U256::from(position.liquidity_share_per_m);
302    let sqrt_price = sqrt_price.clamp(position.lower_sqrt_price, position.upper_sqrt_price);
303
304    let diff_a = U256::from(position.upper_sqrt_price).saturating_sub(U256::from(sqrt_price));
305    let virtual_a = if diff_a == 0 {
306        0u64
307    } else {
308        let numerator = liquidity
309            .checked_mul(diff_a)
310            .ok_or(ARITHMETIC_OVERFLOW)?
311            .checked_shl(64)
312            .ok_or(ARITHMETIC_OVERFLOW)?;
313        let denominator = U256::from(sqrt_price)
314            .checked_mul(U256::from(position.upper_sqrt_price))
315            .ok_or(ARITHMETIC_OVERFLOW)?;
316
317        numerator
318            .checked_div(denominator)
319            .ok_or(ARITHMETIC_OVERFLOW)?
320            .try_into()
321            .map_err(|_| AMOUNT_EXCEEDS_MAX_U64)?
322    };
323
324    let diff_b = U256::from(sqrt_price).saturating_sub(U256::from(position.lower_sqrt_price));
325    let virtual_b = if diff_b == 0 {
326        0u64
327    } else {
328        liquidity
329            .checked_mul(diff_b)
330            .ok_or(ARITHMETIC_OVERFLOW)?
331            .checked_shr(64)
332            .ok_or(ARITHMETIC_OVERFLOW)?
333            .try_into()
334            .map_err(|_| AMOUNT_EXCEEDS_MAX_U64)?
335    };
336
337    Ok((virtual_a, virtual_b))
338}
339
340fn virtual_reserves(positions: &[Position], sqrt_price: u128) -> Result<(u64, u64), CoreError> {
341    let mut virtual_a = 0u64;
342    let mut virtual_b = 0u64;
343    for position in positions {
344        if position.liquidity_share_per_m == 0 {
345            continue;
346        }
347        let (a_amount, b_amount) = virtual_position_reserves(sqrt_price, position)?;
348        virtual_a = virtual_a.checked_add(a_amount).ok_or(ARITHMETIC_OVERFLOW)?;
349        virtual_b = virtual_b.checked_add(b_amount).ok_or(ARITHMETIC_OVERFLOW)?;
350    }
351    Ok((virtual_a, virtual_b))
352}
353
354fn position_reserves(
355    positions: &[Position],
356    sqrt_price: u128,
357    reserves: u64,
358    a_to_b: bool,
359) -> Result<[u64; MAX_POSITIONS], CoreError> {
360    // Calculate the virtual amounts for each position
361    let mut total_virtual_amount: u64 = 0;
362    let mut virtual_amounts: [u64; MAX_POSITIONS] = [0; MAX_POSITIONS];
363    for i in 0..MAX_POSITIONS {
364        let (virtual_a, virtual_b) = virtual_position_reserves(sqrt_price, &positions[i])?;
365        if a_to_b {
366            virtual_amounts[i] = virtual_b;
367            total_virtual_amount = total_virtual_amount
368                .checked_add(virtual_b)
369                .ok_or(ARITHMETIC_OVERFLOW)?;
370        } else {
371            virtual_amounts[i] = virtual_a;
372            total_virtual_amount = total_virtual_amount
373                .checked_add(virtual_a)
374                .ok_or(ARITHMETIC_OVERFLOW)?;
375        }
376    }
377
378    // Calculate the actual amounts for each position
379    let mut actual_amounts: [u64; MAX_POSITIONS] = [0; MAX_POSITIONS];
380    for i in 0..MAX_POSITIONS {
381        actual_amounts[i] = reserves
382            .checked_mul(virtual_amounts[i])
383            .ok_or(ARITHMETIC_OVERFLOW)?
384            .checked_div(total_virtual_amount)
385            .ok_or(ARITHMETIC_OVERFLOW)?;
386    }
387
388    // Normalize sum(actual_a_amounts) to reserves_a so that we're using all of the reserves
389    let total_actual_amount = actual_amounts.iter().sum::<u64>();
390    let mut remaining = reserves - total_actual_amount;
391    while remaining > 0 {
392        for i in 0..MAX_POSITIONS {
393            if remaining == 0 {
394                break;
395            }
396            if actual_amounts[i] > 0 {
397                actual_amounts[i] = actual_amounts[i]
398                    .checked_add(1)
399                    .ok_or(ARITHMETIC_OVERFLOW)?;
400                remaining -= 1;
401            }
402        }
403    }
404
405    Ok(actual_amounts)
406}
407
408fn implied_sqrt_price(
409    positions: &[Position],
410    reserves_a: u64,
411    reserves_b: u64,
412) -> Result<u128, CoreError> {
413    if positions.is_empty() {
414        return Err(INVALID_ORACLE_DATA);
415    }
416
417    let mut lowest_sqrt_price = positions
418        .iter()
419        .filter(|p| p.liquidity_share_per_m > 0)
420        .map(|p| p.lower_sqrt_price)
421        .min()
422        .unwrap_or(0);
423    let mut highest_sqrt_price = positions
424        .iter()
425        .filter(|p| p.liquidity_share_per_m > 0)
426        .map(|p| p.upper_sqrt_price)
427        .max()
428        .unwrap_or(u128::MAX);
429
430    // If one reserve is zero, the sqrt price is the highest or lowest price.
431    if reserves_a == 0 {
432        return Ok(highest_sqrt_price);
433    } else if reserves_b == 0 {
434        return Ok(lowest_sqrt_price);
435    }
436
437    // Binary search for P where virtual_b(P)/virtual_a(P) = reserves_b/reserves_a.
438    for _ in 0..128 {
439        if highest_sqrt_price <= lowest_sqrt_price + 1 {
440            break;
441        }
442        let diff = highest_sqrt_price.abs_diff(lowest_sqrt_price) >> 1;
443        let mid = lowest_sqrt_price
444            .checked_add(diff)
445            .ok_or(ARITHMETIC_OVERFLOW)?;
446        let (virtual_a, virtual_b) = virtual_reserves(positions, mid)?;
447
448        let lhs = u128::from(virtual_b)
449            .checked_mul(reserves_a as u128)
450            .ok_or(ARITHMETIC_OVERFLOW)?;
451        let rhs = u128::from(virtual_a)
452            .checked_mul(reserves_b as u128)
453            .ok_or(ARITHMETIC_OVERFLOW)?;
454
455        if lhs < rhs {
456            lowest_sqrt_price = mid;
457        } else {
458            highest_sqrt_price = mid;
459        }
460    }
461
462    let diff = highest_sqrt_price.abs_diff(lowest_sqrt_price) >> 1;
463    let sqrt_price = lowest_sqrt_price
464        .checked_add(diff)
465        .ok_or(ARITHMETIC_OVERFLOW)?;
466
467    Ok(sqrt_price)
468}
469
470#[inline(always)]
471fn bin_position_overlap(
472    bin_index: usize,
473    mid_sqrt_price: u128,
474    end_sqrt_price: u128,
475    position: &Position,
476) -> Result<u128, CoreError> {
477    let (bin_start, bin_end) = bin_boundaries(bin_index, mid_sqrt_price, end_sqrt_price)?;
478    let (lo, hi) = if bin_start <= bin_end {
479        (bin_start, bin_end)
480    } else {
481        (bin_end, bin_start)
482    };
483    let overlap_start = lo.max(position.lower_sqrt_price);
484    let overlap_end = hi.min(position.upper_sqrt_price);
485    if overlap_end > overlap_start {
486        Ok(overlap_end - overlap_start)
487    } else {
488        Ok(0)
489    }
490}
491
492fn bin_boundaries(
493    i: usize,
494    sqrt_price: u128,
495    end_sqrt_price: u128,
496) -> Result<(u128, u128), CoreError> {
497    let sqrt_price_diff = end_sqrt_price.abs_diff(sqrt_price);
498
499    let start_sqrt_price_delta = U256::from(sqrt_price_diff)
500        .checked_mul(U256::from(i as u128))
501        .ok_or(ARITHMETIC_OVERFLOW)?
502        .checked_div(U256::from(LIQUIDITY_LEVELS as u128))
503        .ok_or(ARITHMETIC_OVERFLOW)?;
504    let end_sqrt_price_delta = U256::from(sqrt_price_diff)
505        .checked_mul(U256::from(i as u128 + 1))
506        .ok_or(ARITHMETIC_OVERFLOW)?
507        .checked_div(U256::from(LIQUIDITY_LEVELS as u128))
508        .ok_or(ARITHMETIC_OVERFLOW)?;
509
510    let (start_sqrt_price, end_sqrt_price) = if end_sqrt_price > sqrt_price {
511        let start = U256::from(sqrt_price)
512            .checked_add(start_sqrt_price_delta)
513            .ok_or(ARITHMETIC_OVERFLOW)?;
514
515        let end = U256::from(sqrt_price)
516            .checked_add(end_sqrt_price_delta)
517            .ok_or(ARITHMETIC_OVERFLOW)?;
518
519        (start, end)
520    } else {
521        let start = U256::from(sqrt_price)
522            .checked_sub(start_sqrt_price_delta)
523            .ok_or(ARITHMETIC_OVERFLOW)?;
524
525        let end = U256::from(sqrt_price)
526            .checked_sub(end_sqrt_price_delta)
527            .ok_or(ARITHMETIC_OVERFLOW)?;
528
529        (start, end)
530    };
531
532    Ok((
533        start_sqrt_price
534            .try_into()
535            .map_err(|_| AMOUNT_EXCEEDS_MAX_U128)?,
536        end_sqrt_price
537            .try_into()
538            .map_err(|_| AMOUNT_EXCEEDS_MAX_U128)?,
539    ))
540}
541
542// Take the first price in the bin and apply the spread. If we wanted to be more true to an actual
543// AMM, we would take the geometric mean. The problem if we do that is that the first bin's price
544// would not be equal to the mid price meaning you would get an implicit spread which becomes larger
545// the larger the lp range.
546fn bin_price(
547    i: usize,
548    start_sqrt_price: u128,
549    end_sqrt_price: u128,
550    spread: i32,
551) -> Result<u128, CoreError> {
552    let sqrt_price_diff = end_sqrt_price.abs_diff(start_sqrt_price);
553    let sqrt_price_delta = U256::from(sqrt_price_diff)
554        .checked_mul(U256::from(i as u128))
555        .ok_or(ARITHMETIC_OVERFLOW)?
556        .checked_div(U256::from(LIQUIDITY_LEVELS as u128 - 1))
557        .ok_or(ARITHMETIC_OVERFLOW)?;
558
559    let sqrt_price = if end_sqrt_price > start_sqrt_price {
560        U256::from(start_sqrt_price)
561            .checked_add(sqrt_price_delta)
562            .ok_or(ARITHMETIC_OVERFLOW)?
563    } else {
564        U256::from(start_sqrt_price)
565            .checked_sub(sqrt_price_delta)
566            .ok_or(ARITHMETIC_OVERFLOW)?
567    };
568
569    let product = sqrt_price
570        .checked_mul(sqrt_price)
571        .ok_or(ARITHMETIC_OVERFLOW)?;
572
573    let quotient = product.checked_shr(64).ok_or(ARITHMETIC_OVERFLOW)?;
574    let remainder = product & u128::MAX;
575
576    let base_price: u128 = if end_sqrt_price > start_sqrt_price && remainder > 0 {
577        (quotient + 1)
578            .try_into()
579            .map_err(|_| AMOUNT_EXCEEDS_MAX_U128)?
580    } else {
581        quotient.try_into().map_err(|_| AMOUNT_EXCEEDS_MAX_U128)?
582    };
583
584    let product = U256::from(base_price)
585        .checked_mul(U256::from(spread as u128))
586        .ok_or(ARITHMETIC_OVERFLOW)?;
587    let quotient = product
588        .checked_div(U256::from(PER_M_DENOMINATOR as u128))
589        .ok_or(ARITHMETIC_OVERFLOW)?;
590    let remainder = product
591        .checked_rem(U256::from(PER_M_DENOMINATOR as u128))
592        .ok_or(ARITHMETIC_OVERFLOW)?;
593
594    let price = if end_sqrt_price > start_sqrt_price && remainder > 0 {
595        (quotient + 1)
596            .try_into()
597            .map_err(|_| AMOUNT_EXCEEDS_MAX_U128)?
598    } else {
599        quotient.try_into().map_err(|_| AMOUNT_EXCEEDS_MAX_U128)?
600    };
601
602    Ok(price)
603}
604
605#[cfg(test)]
606mod tests {
607    use super::*;
608    use rstest::rstest;
609
610    #[rstest]
611    #[case(Position::full_range(PER_M_DENOMINATOR as u32), 100, 100, 18446738426575981041)]
612    #[case(Position::full_range(PER_M_DENOMINATOR as u32), 150, 50, 10650233338091508966)]
613    #[case(Position::full_range(PER_M_DENOMINATOR as u32), 50, 150, 31950688720003928217)]
614    #[case(Position::full_range(PER_M_DENOMINATOR as u32), 200, 0, MIN_SQRT_PRICE)]
615    #[case(Position::full_range(PER_M_DENOMINATOR as u32), 0, 200, MAX_SQRT_PRICE)]
616    #[case(Position::new((1 << 64) / 2, (1 << 64) * 2, PER_M_DENOMINATOR as u32).unwrap(), 100, 100, 18446744073709551615)]
617    #[case(Position::new((1 << 64) / 3, (1 << 64) * 2, PER_M_DENOMINATOR as u32).unwrap(), 100, 100, 16973448724429105277)]
618    #[case(Position::new((1 << 64) / 2, (1 << 64) * 3, PER_M_DENOMINATOR as u32).unwrap(), 100, 100, 20047903282541897858)]
619    #[case(Position::new((1 << 64) / 2, (1 << 64) * 2, PER_M_DENOMINATOR as u32).unwrap(), 150, 50, 14159573177026862144)]
620    #[case(Position::new((1 << 64) / 2, (1 << 64) * 2, PER_M_DENOMINATOR as u32).unwrap(), 50, 150, 24031964994045732128)]
621    #[case(Position::new((1 << 64) / 2, (1 << 64) * 2, PER_M_DENOMINATOR as u32).unwrap(), 200, 0, (1 << 64) / 2)]
622    #[case(Position::new((1 << 64) / 2, (1 << 64) * 2, PER_M_DENOMINATOR as u32).unwrap(), 0, 200, (1 << 64) * 2)]
623    fn test_implied_sqrt_price_single_position(
624        #[case] position: Position,
625        #[case] reserves_a: u64,
626        #[case] reserves_b: u64,
627        #[case] expected: u128,
628    ) {
629        let sqrt_price = implied_sqrt_price(&[position], reserves_a, reserves_b).unwrap();
630        assert_eq!(sqrt_price, expected);
631    }
632
633    #[rstest]
634    #[case(100, 100, 18446744073709551615)]
635    #[case(150, 50, 13516113850247725564)]
636    #[case(50, 150, 25176050652658693911)]
637    #[case(200, 0, 6148914691236517205)]
638    #[case(0, 200, 55340232221128654848)]
639    fn test_implied_sqrt_price_multiple_positions(
640        #[case] reserves_a: u64,
641        #[case] reserves_b: u64,
642        #[case] expected: u128,
643    ) {
644        let positions = vec![
645            Position::new((1 << 64) / 2, (1 << 64) * 2, 300_000).unwrap(),
646            Position::new((1 << 64) / 2, (1 << 64) * 3, 200_000).unwrap(),
647            Position::new((1 << 64) / 3, (1 << 64) * 2, 200_000).unwrap(),
648            Position::new((1 << 64) / 3, (1 << 64) * 3, 300_000).unwrap(),
649        ];
650        let sqrt_price = implied_sqrt_price(&positions, reserves_a, reserves_b).unwrap();
651        assert_eq!(sqrt_price, expected);
652    }
653
654    #[rstest]
655    #[case(LiquidityType::ConstantProduct, 100, 100, Ok(18446732779444139232))]
656    #[case(LiquidityType::ConstantProduct, 150, 50, Ok(6148915478122426543))]
657    #[case(LiquidityType::ConstantProduct, 50, 150, Ok(55340200178605227858))]
658    #[case(LiquidityType::ConstantProduct, 200, 0, Ok(1728767))]
659    #[case(
660        LiquidityType::ConstantProduct,
661        0,
662        200,
663        Ok(196835207006294262292126795817913)
664    )]
665    #[case(LiquidityType::ConcentratedLiquidity { lower_sqrt_price: (1 << 64) / 2, upper_sqrt_price: (1 << 64) * 2 }, 100, 100, Ok(18446744073709551614))]
666    #[case(LiquidityType::ConcentratedLiquidity { lower_sqrt_price: (1 << 64) / 2, upper_sqrt_price: (1 << 64) * 2 }, 150, 50, Ok(10868775094100403166))]
667    #[case(LiquidityType::ConcentratedLiquidity { lower_sqrt_price: (1 << 64) / 2, upper_sqrt_price: (1 << 64) * 2 }, 50, 150, Ok(31308253595719773170))]
668    #[case(LiquidityType::ConcentratedLiquidity { lower_sqrt_price: (1 << 64) / 2, upper_sqrt_price: (1 << 64) * 2 }, 200, 0, Ok(4611686018427387904))]
669    #[case(LiquidityType::ConcentratedLiquidity { lower_sqrt_price: (1 << 64) / 2, upper_sqrt_price: (1 << 64) * 2 }, 0, 200, Ok(73786976294838206464))]
670    fn test_amm_price(
671        #[case] liquidity_type: LiquidityType,
672        #[case] reserves_a: u64,
673        #[case] reserves_b: u64,
674        #[case] expected: Result<u128, CoreError>,
675    ) {
676        let price = amm_price(liquidity_type, reserves_a, reserves_b);
677        assert_eq!(price, expected);
678    }
679
680    #[rstest]
681    #[case(0, MIN_SQRT_PRICE, Ok((18446744073709551616, 17870283497879106233)))]
682    #[case(1, MIN_SQRT_PRICE, Ok((17870283497879106233, 17293822922048660849)))]
683    #[case(0, MAX_SQRT_PRICE, Ok((18446744073709551616, 1883065362968454170744257)))]
684    #[case(1, MAX_SQRT_PRICE, Ok((1883065362968454170744257, 3766112279192834631936899)))]
685    fn test_bin_boundaries(
686        #[case] i: usize,
687        #[case] end_sqrt_price: u128,
688        #[case] expected: Result<(u128, u128), CoreError>,
689    ) {
690        let result = bin_boundaries(i, 1 << 64, end_sqrt_price);
691        assert_eq!(result, expected);
692    }
693
694    #[rstest]
695    #[case(0, 1 << 64, (1 << 64) / 2, 999_000, Ok(18428297329635842064))]
696    #[case(31, 1 << 64, (1 << 64) / 2, 999_000, Ok(4607074332408960516))]
697    #[case(0, 1 << 64, (1 << 64) / 2, 998_000, Ok(18409850585562132512))]
698    #[case(31, 1 << 64, (1 << 64) / 2, 998_000, Ok(4602462646390533128))]
699    #[case(0, 1 << 64, (1 << 64) / 2, 1_001_000, Ok(18465190817783261167))]
700    #[case(31, 1 << 64, (1 << 64) / 2, 1_002_000, Ok(4620909390464242679))]
701    #[case(0, 1 << 64, (1 << 64) * 2, 999_000, Ok(18428297329635842065))]
702    #[case(31, 1 << 64, (1 << 64) * 2, 999_000, Ok(73713189318543368258))]
703    #[case(0, 1 << 64, (1 << 64) * 2, 998_000, Ok(18409850585562132513))]
704    #[case(31, 1 << 64, (1 << 64) * 2, 998_000, Ok(73639402342248530052))]
705    #[case(0, 1 << 64, (1 << 64) * 2, 1_001_000, Ok(18465190817783261168))]
706    #[case(31, 1 << 64, (1 << 64) * 2, 1_001_000, Ok(73860763271133044671))]
707    #[case(0, 1 << 64, (1 << 64) * 2, 1_002_000, Ok(18483637561856970720))]
708    #[case(31, 1 << 64, (1 << 64) * 2, 1_002_000, Ok(73934550247427882877))]
709    fn test_bin_price(
710        #[case] i: usize,
711        #[case] start_sqrt_price: u128,
712        #[case] end_sqrt_price: u128,
713        #[case] spread: i32,
714        #[case] expected: Result<u128, CoreError>,
715    ) {
716        let result = bin_price(i, start_sqrt_price, end_sqrt_price, spread);
717        assert_eq!(result, expected);
718    }
719
720    #[test]
721    fn test_amm_liquidity_a_to_b() {
722        let liquidity = amm_liquidity(
723            LiquidityType::ConstantProduct,
724            10000,
725            20000,
726            QuoteType::TokenAExactIn,
727            1000,
728            1000,
729        )
730        .unwrap()
731        .as_slice()
732        .to_vec();
733        // Prices starts ~ 0.99 and ends ~ 9e-14
734        let expected = vec![
735            (18262265451649697839, 31),
736            (17103058524375092466, 31),
737            (15981858369775465113, 31),
738            (14898664987850815784, 31),
739            (13853478378601144476, 31),
740            (12846298542026451190, 31),
741            (11877125478126735926, 31),
742            (10945959186901998683, 31),
743            (10052799668352239462, 31),
744            (9197646922477458264, 31),
745            (8380500949277655088, 31),
746            (7601361748752829935, 31),
747            (6860229320902982803, 31),
748            (6157103665728113694, 31),
749            (5491984783228222606, 31),
750            (4864872673403309539, 31),
751            (4275767336253374494, 31),
752            (3724668771778417472, 31),
753            (3211576979978438473, 31),
754            (2736491960853437495, 31),
755            (2299413714403414540, 31),
756            (1900342240628369606, 31),
757            (1539277539528302694, 31),
758            (1216219611103213804, 31),
759            (931168455353102936, 32),
760            (684124072277970090, 32),
761            (475086461877815267, 32),
762            (304055624152638466, 32),
763            (171031559102439686, 32),
764            (76014266727218928, 32),
765            (19003747026976192, 32),
766            (1711479, 32),
767        ];
768
769        assert_eq!(liquidity, expected);
770    }
771
772    #[test]
773    fn test_amm_liquidity_concentrated_a_to_b() {
774        let liquidity = amm_liquidity(
775            LiquidityType::ConcentratedLiquidity {
776                lower_sqrt_price: (1 << 64) / 2,
777                upper_sqrt_price: (1 << 64) * 2,
778            },
779            10000,
780            20000,
781            QuoteType::TokenAExactIn,
782            1000,
783            1000,
784        )
785        .unwrap()
786        .as_slice()
787        .to_vec();
788        // Prices starts ~ 0.99 and ends ~ 0.2475
789        let expected = vec![
790            (18262276632972456097, 31),
791            (17677921787536552847, 31),
792            (17103068646904485422, 31),
793            (16537717211076253820, 31),
794            (15981867480051858042, 31),
795            (15435519453831298092, 31),
796            (14898673132414573967, 31),
797            (14371328515801685667, 31),
798            (13853485603992633191, 31),
799            (13345144396987416541, 31),
800            (12846304894786035717, 31),
801            (12356967097388490717, 31),
802            (11877131004794781542, 31),
803            (11406796617004908193, 31),
804            (10945963934018870670, 31),
805            (10494632955836668971, 31),
806            (10052803682458303097, 31),
807            (9620476113883773049, 31),
808            (9197650250113078826, 31),
809            (8784326091146220429, 31),
810            (8380503636983197855, 31),
811            (7986182887624011109, 31),
812            (7601363843068660187, 31),
813            (7226046503317145091, 31),
814            (6860230868369465819, 32),
815            (6503916938225622372, 32),
816            (6157104712885614751, 32),
817            (5819794192349442955, 32),
818            (5491985376617106984, 32),
819            (5173678265688606840, 32),
820            (4864872859563942520, 32),
821            (4565569158243114024, 32),
822        ];
823
824        assert_eq!(liquidity, expected);
825    }
826
827    #[test]
828    fn test_amm_liquidity_b_to_a() {
829        let liquidity = amm_liquidity(
830            LiquidityType::ConstantProduct,
831            10000,
832            20000,
833            QuoteType::TokenBExactIn,
834            1000,
835            1000,
836        )
837        .unwrap()
838        .as_slice()
839        .to_vec();
840        // Prices starts ~ 1.02 and ends ~ 1e13
841        let expected = vec![
842            (18815667435033022018, 31),
843            (208923620106592073258895578064, 31),
844            (835686549707659367878445172487, 31),
845            (1880288788822017551293681805289, 31),
846            (3342730337449666623504606336313, 31),
847            (5223011195590606584511217260829, 31),
848            (7521131363244837434313515223724, 31),
849            (10237090840412359172911501729725, 31),
850            (13370889627093171800305175704025, 31),
851            (16922527723287275316494535211973, 31),
852            (20892005128994669721479581758299, 31),
853            (25279321844215355015260315343002, 31),
854            (30084477868949331197836738545617, 31),
855            (35307473203196598269208849216532, 31),
856            (40948307846957156229376644346290, 31),
857            (47006981800231005078340126514425, 31),
858            (53483495063018144816099299160316, 31),
859            (60377847635318575442654155620167, 31),
860            (67690039517132296958004699118395, 31),
861            (75420070708459309362150933739263, 31),
862            (83567941209299612655092855828431, 31),
863            (92133651019653206836830460871714, 31),
864            (101117200139520091907363752953373, 31),
865            (110518588568900267866692732073410, 31),
866            (120337816307793734714817403390893, 32),
867            (130574883356200492451737762176676, 32),
868            (141229789714120541077453802841767, 32),
869            (152302535381553880591965530545236, 32),
870            (163793120358500510995272951305994, 32),
871            (175701544644960432287376053301180, 32),
872            (188027808240933644468274842334742, 32),
873            (200771911146420147537969331734273, 32),
874        ];
875
876        assert_eq!(liquidity, expected);
877    }
878
879    #[test]
880    fn test_amm_liquidity_concentrated_b_to_a() {
881        let liquidity = amm_liquidity(
882            LiquidityType::ConcentratedLiquidity {
883                lower_sqrt_price: (1 << 64) / 2,
884                upper_sqrt_price: (1 << 64) * 2,
885            },
886            10000,
887            20000,
888            QuoteType::TokenBExactIn,
889            1000,
890            1000,
891        )
892        .unwrap()
893        .as_slice()
894        .to_vec();
895        // Prices starts ~ 1.02 and ends ~ 4.08
896        let expected = vec![
897            (18815678955183742648, 31),
898            (20049172996990793413, 31),
899            (21321825579807591824, 31),
900            (22633636703634137876, 31),
901            (23984606368470431575, 31),
902            (25374734574316472913, 31),
903            (26804021321172261898, 31),
904            (28272466609037798524, 31),
905            (29780070437913082795, 31),
906            (31326832807798114708, 31),
907            (32912753718692894266, 31),
908            (34537833170597421466, 31),
909            (36202071163511696311, 31),
910            (37905467697435718797, 31),
911            (39648022772369488929, 31),
912            (41429736388313006701, 31),
913            (43250608545266272120, 31),
914            (45110639243229285179, 31),
915            (47009828482202045885, 31),
916            (48948176262184554231, 31),
917            (50925682583176810224, 31),
918            (52942347445178813857, 31),
919            (54998170848190565136, 31),
920            (57093152792212064055, 31),
921            (59227293277243310621, 32),
922            (61400592303284304828, 32),
923            (63613049870335046681, 32),
924            (65864665978395536173, 32),
925            (68155440627465773314, 32),
926            (70485373817545758093, 32),
927            (72854465548635490520, 32),
928            (75262715820734970594, 32),
929        ];
930
931        assert_eq!(liquidity, expected);
932    }
933
934    #[test]
935    fn test_narrow_range_rejected() {
936        // Range width = ~1e-6 in sqrt-price space, too narrow to produce non-zero virtual reserves
937        let result = amm_liquidity(
938            LiquidityType::ConcentratedLiquidity {
939                lower_sqrt_price: 1 << 64,
940                upper_sqrt_price: ((1 << 64) * 1_000_001) / 1_000_000,
941            },
942            0,
943            0,
944            QuoteType::TokenAExactIn,
945            1_000_000,
946            1_000_000,
947        );
948        assert_eq!(result, Err(INVALID_ORACLE_DATA));
949    }
950
951    #[test]
952    fn test_min_valid_range_accepted() {
953        // Range width exactly at MIN_SQRT_PRICE_RANGE should pass Position::new validation
954        let result = Position::new(
955            1 << 64,
956            (1 << 64) + MIN_SQRT_PRICE_RANGE,
957            PER_M_DENOMINATOR as u32,
958        );
959        assert!(result.is_ok());
960    }
961
962    #[test]
963    fn test_below_min_range_rejected() {
964        // Range width below MIN_SQRT_PRICE_RANGE should be rejected
965        let result = Position::new(
966            1 << 64,
967            (1 << 64) + MIN_SQRT_PRICE_RANGE - 1,
968            PER_M_DENOMINATOR as u32,
969        );
970        assert!(result.is_err());
971    }
972}