Skip to main content

riptide_amm/math/oracle/
book.rs

1use super::super::{
2    error::{
3        CoreError, AMOUNT_EXCEEDS_MAX_U128, AMOUNT_EXCEEDS_MAX_U64, ARITHMETIC_OVERFLOW,
4        INVALID_ORACLE_DATA,
5    },
6    oracle::PER_M_DENOMINATOR,
7};
8
9use borsh::{BorshDeserialize, BorshSerialize};
10
11use ethnum::U256;
12#[cfg(feature = "wasm")]
13use riptide_amm_macros_lib::wasm_expose;
14
15use super::SingleSideLiquidity;
16
17#[derive(Debug, Clone, Copy, Eq, PartialEq)]
18#[cfg_attr(true, derive(BorshDeserialize, BorshSerialize))]
19#[cfg_attr(feature = "wasm", wasm_expose)]
20pub enum BookSpacingType {
21    // Price = price_q64_64 ± (X * i * price_q64_64)
22    Linear(u16),
23    // Price = price_q64_64 * (1 ± X)^i
24    Exponential(u16),
25}
26
27impl BookSpacingType {
28    pub fn price(&self, price_q64_64: u128, i: usize, a_to_b: bool) -> Result<u128, CoreError> {
29        match *self {
30            BookSpacingType::Linear(spacing_per_m) => {
31                let product = price_q64_64
32                    .checked_mul(spacing_per_m as u128)
33                    .ok_or(ARITHMETIC_OVERFLOW)?
34                    .checked_mul(i as u128)
35                    .ok_or(ARITHMETIC_OVERFLOW)?;
36                let quotient = product
37                    .checked_div(PER_M_DENOMINATOR as u128)
38                    .ok_or(ARITHMETIC_OVERFLOW)?;
39                let remainder = product
40                    .checked_rem(PER_M_DENOMINATOR as u128)
41                    .ok_or(ARITHMETIC_OVERFLOW)?;
42                let spread = if !a_to_b && remainder > 0 {
43                    quotient + 1
44                } else {
45                    quotient
46                };
47                if a_to_b {
48                    Ok(price_q64_64 - spread)
49                } else {
50                    Ok(price_q64_64 + spread)
51                }
52            }
53            BookSpacingType::Exponential(spacing_per_m) => {
54                let factor = if a_to_b {
55                    PER_M_DENOMINATOR - spacing_per_m as i32
56                } else {
57                    PER_M_DENOMINATOR + spacing_per_m as i32
58                };
59                let mut base = u128::from(factor as u32)
60                    .checked_shl(64)
61                    .ok_or(ARITHMETIC_OVERFLOW)?
62                    .checked_div(PER_M_DENOMINATOR as u128)
63                    .ok_or(ARITHMETIC_OVERFLOW)?;
64
65                let mut exp = i as u32;
66                let mut result: u128 = price_q64_64;
67
68                while exp > 0 {
69                    if exp & 1 == 1 {
70                        result = U256::from(result)
71                            .checked_mul(U256::from(base))
72                            .ok_or(ARITHMETIC_OVERFLOW)?
73                            .checked_shr(64)
74                            .ok_or(ARITHMETIC_OVERFLOW)?
75                            .try_into()
76                            .map_err(|_| AMOUNT_EXCEEDS_MAX_U128)?;
77                    }
78
79                    base = U256::from(base)
80                        .checked_mul(U256::from(base))
81                        .ok_or(ARITHMETIC_OVERFLOW)?
82                        .checked_shr(64)
83                        .ok_or(ARITHMETIC_OVERFLOW)?
84                        .try_into()
85                        .map_err(|_| AMOUNT_EXCEEDS_MAX_U128)?;
86                    exp >>= 1;
87                }
88
89                Ok(result)
90            }
91        }
92    }
93}
94
95pub(crate) fn book_liquidity(
96    amount_is_token_a: bool,
97    amount_is_input: bool,
98    price: u128,
99    spacing: BookSpacingType,
100    bid_liquidity_per_m: &[u32],
101    ask_liquidity_per_m: &[u32],
102    reserves_a: u64,
103    reserves_b: u64,
104) -> Result<SingleSideLiquidity, CoreError> {
105    let a_to_b = amount_is_token_a == amount_is_input;
106
107    let liquidity_per_m = if a_to_b {
108        bid_liquidity_per_m
109    } else {
110        ask_liquidity_per_m
111    };
112
113    let total_liquidity = if a_to_b { reserves_b } else { reserves_a };
114
115    if liquidity_per_m.iter().sum::<u32>() > PER_M_DENOMINATOR as u32 {
116        return Err(INVALID_ORACLE_DATA);
117    }
118
119    let mut book = SingleSideLiquidity::new();
120
121    for (i, liquidity_share) in liquidity_per_m.iter().enumerate() {
122        if *liquidity_share == 0 {
123            continue;
124        }
125
126        let price = spacing.price(price, i, a_to_b)?;
127
128        let liquidity: u64 = u128::from(total_liquidity)
129            .checked_mul(*liquidity_share as u128)
130            .ok_or(ARITHMETIC_OVERFLOW)?
131            .checked_div(PER_M_DENOMINATOR as u128)
132            .ok_or(ARITHMETIC_OVERFLOW)?
133            .try_into()
134            .map_err(|_| AMOUNT_EXCEEDS_MAX_U64)?;
135
136        book.push((price, liquidity));
137    }
138
139    Ok(book)
140}
141
142#[cfg(all(test, feature = "lib"))]
143mod tests {
144    use super::*;
145    use rstest::rstest;
146
147    #[rstest]
148    #[case(11111, true, Ok(vec![1000, 989, 978, 967, 956]))]
149    #[case(11111, false, Ok(vec![1000, 1012, 1023, 1034, 1045]))]
150    #[case(22222, true, Ok(vec![1000, 978, 956, 934, 912]))]
151    #[case(22222, false, Ok(vec![1000, 1023, 1045, 1067, 1089]))]
152    #[case(0, true, Ok(vec![1000, 1000, 1000, 1000, 1000]))]
153    #[case(0, false, Ok(vec![1000, 1000, 1000, 1000, 1000]))]
154    fn test_linear_spacing(
155        #[case] spacing_per_m: u16,
156        #[case] a_to_b: bool,
157        #[case] expected: Result<Vec<u128>, CoreError>,
158    ) {
159        let spacing = BookSpacingType::Linear(spacing_per_m);
160        let prices = (0..5)
161            .map(|i| spacing.price(1000, i, a_to_b))
162            .collect::<Result<Vec<u128>, CoreError>>();
163        assert_eq!(prices, expected);
164    }
165
166    #[rstest]
167    #[case(10000, true, Ok(vec![1000, 989, 980, 969, 960]))]
168    #[case(10000, false, Ok(vec![1000, 1009, 1020, 1029, 1040]))]
169    #[case(20000, true, Ok(vec![1000, 979, 960, 940, 922]))]
170    #[case(20000, false, Ok(vec![1000, 1019, 1040, 1060, 1082]))]
171    #[case(0, true, Ok(vec![1000, 1000, 1000, 1000, 1000]))]
172    #[case(0, false, Ok(vec![1000, 1000, 1000, 1000, 1000]))]
173    fn test_exponential_spacing(
174        #[case] spacing_per_m: u16,
175        #[case] a_to_b: bool,
176        #[case] expected: Result<Vec<u128>, CoreError>,
177    ) {
178        let spacing = BookSpacingType::Exponential(spacing_per_m);
179        let prices = (0..5)
180            .map(|i| spacing.price(1000, i, a_to_b))
181            .collect::<Result<Vec<u128>, CoreError>>();
182        assert_eq!(prices, expected);
183    }
184
185    #[rstest]
186    #[case(true, true, Ok(vec![400, 1000, 600]))]
187    #[case(true, false, Ok(vec![300, 500, 200]))]
188    #[case(false, true, Ok(vec![300, 500, 200]))]
189    #[case(false, false, Ok(vec![400, 1000, 600]))]
190    fn test_linear_book_liquidity(
191        #[case] amount_is_token_a: bool,
192        #[case] amount_is_input: bool,
193        #[case] expected: Result<Vec<u64>, CoreError>,
194    ) {
195        let liquidity = book_liquidity(
196            amount_is_token_a,
197            amount_is_input,
198            1000,
199            BookSpacingType::Linear(0),
200            &[200_000, 0, 500_000, 300_000],
201            &[300_000, 0, 500_000, 200_000],
202            1000,
203            2000,
204        )
205        .map(|liquidity| liquidity.to_vec().iter().map(|x| x.1).collect());
206        assert_eq!(liquidity, expected);
207    }
208}