1use super::super::{
2 error::{
3 CoreError, AMOUNT_EXCEEDS_MAX_U128, AMOUNT_EXCEEDS_MAX_U32, AMOUNT_EXCEEDS_MAX_U64,
4 ARITHMETIC_OVERFLOW, INVALID_ORACLE_DATA,
5 },
6 oracle::{OracleData, PER_M_DENOMINATOR},
7 quote::{Quote, QuoteType},
8};
9
10use borsh::{BorshDeserialize, BorshSerialize};
11
12use ethnum::U256;
13#[cfg(feature = "wasm")]
14use riptide_amm_macros::wasm_expose;
15
16use super::SingleSideLiquidity;
17
18pub(crate) const BOOK_LIQUIDITY_LEVELS: usize = 32;
19
20#[derive(Debug, Clone, Copy, Eq, PartialEq)]
21#[cfg_attr(true, derive(BorshDeserialize, BorshSerialize))]
22#[cfg_attr(feature = "wasm", wasm_expose)]
23pub enum BookSpacingType {
24 Linear(u16),
26 Exponential(u16),
28}
29
30impl BookSpacingType {
31 pub fn price(&self, price_q64_64: u128, i: usize, a_to_b: bool) -> Result<u128, CoreError> {
32 match *self {
33 BookSpacingType::Linear(spacing_per_m) => {
34 let product = price_q64_64
35 .checked_mul(spacing_per_m as u128)
36 .ok_or(ARITHMETIC_OVERFLOW)?
37 .checked_mul(i as u128)
38 .ok_or(ARITHMETIC_OVERFLOW)?;
39 let quotient = product
40 .checked_div(PER_M_DENOMINATOR as u128)
41 .ok_or(ARITHMETIC_OVERFLOW)?;
42 let remainder = product
43 .checked_rem(PER_M_DENOMINATOR as u128)
44 .ok_or(ARITHMETIC_OVERFLOW)?;
45 let spread = if !a_to_b && remainder > 0 {
46 quotient + 1
47 } else {
48 quotient
49 };
50 if a_to_b {
51 Ok(price_q64_64 - spread)
52 } else {
53 Ok(price_q64_64 + spread)
54 }
55 }
56 BookSpacingType::Exponential(spacing_per_m) => {
57 let factor = if a_to_b {
58 PER_M_DENOMINATOR - spacing_per_m as i32
59 } else {
60 PER_M_DENOMINATOR + spacing_per_m as i32
61 };
62 let mut base = u128::from(factor as u32)
63 .checked_shl(64)
64 .ok_or(ARITHMETIC_OVERFLOW)?
65 .checked_div(PER_M_DENOMINATOR as u128)
66 .ok_or(ARITHMETIC_OVERFLOW)?;
67
68 let mut exp = i as u32;
69 let mut result: u128 = price_q64_64;
70
71 while exp > 0 {
72 if exp & 1 == 1 {
73 result = U256::from(result)
74 .checked_mul(U256::from(base))
75 .ok_or(ARITHMETIC_OVERFLOW)?
76 .checked_shr(64)
77 .ok_or(ARITHMETIC_OVERFLOW)?
78 .try_into()
79 .map_err(|_| AMOUNT_EXCEEDS_MAX_U128)?;
80 }
81
82 base = U256::from(base)
83 .checked_mul(U256::from(base))
84 .ok_or(ARITHMETIC_OVERFLOW)?
85 .checked_shr(64)
86 .ok_or(ARITHMETIC_OVERFLOW)?
87 .try_into()
88 .map_err(|_| AMOUNT_EXCEEDS_MAX_U128)?;
89 exp >>= 1;
90 }
91
92 Ok(result)
93 }
94 }
95 }
96}
97
98pub(crate) fn book_liquidity(
99 quote_type: QuoteType,
100 price: u128,
101 spacing: BookSpacingType,
102 bid_liquidity_per_m: &[u32],
103 ask_liquidity_per_m: &[u32],
104 reserves_a: u64,
105 reserves_b: u64,
106) -> Result<SingleSideLiquidity, CoreError> {
107 let liquidity_per_m = if quote_type.a_to_b() {
108 bid_liquidity_per_m
109 } else {
110 ask_liquidity_per_m
111 };
112
113 let total_liquidity = if quote_type.a_to_b() {
114 reserves_b
115 } else {
116 reserves_a
117 };
118
119 if liquidity_per_m.iter().sum::<u32>() > PER_M_DENOMINATOR as u32 {
120 return Err(INVALID_ORACLE_DATA);
121 }
122
123 let mut book = SingleSideLiquidity::new();
124
125 for (i, liquidity_share) in liquidity_per_m.iter().enumerate() {
126 if *liquidity_share == 0 {
127 continue;
128 }
129
130 let price = spacing.price(price, i, quote_type.a_to_b())?;
131
132 let liquidity: u64 = u128::from(total_liquidity)
133 .checked_mul(*liquidity_share as u128)
134 .ok_or(ARITHMETIC_OVERFLOW)?
135 .checked_div(PER_M_DENOMINATOR as u128)
136 .ok_or(ARITHMETIC_OVERFLOW)?
137 .try_into()
138 .map_err(|_| AMOUNT_EXCEEDS_MAX_U64)?;
139
140 book.push((price, liquidity));
141 }
142
143 Ok(book)
144}
145
146pub(crate) fn new_book_liquidity(
147 quote: &Quote,
148 price: u128,
149 spacing: BookSpacingType,
150 bid_liquidity_per_m: &[u32],
151 ask_liquidity_per_m: &[u32],
152 reserves_a: u64,
153 reserves_b: u64,
154) -> Result<OracleData, CoreError> {
155 let mut bid_liquidity = [0u128; BOOK_LIQUIDITY_LEVELS];
156 let mut ask_liquidity = [0u128; BOOK_LIQUIDITY_LEVELS];
157
158 for (i, liquidity_per_m) in bid_liquidity_per_m.iter().enumerate() {
159 bid_liquidity[i] = u128::from(*liquidity_per_m)
160 .checked_mul(reserves_b as u128)
161 .ok_or(ARITHMETIC_OVERFLOW)?
162 .checked_div(PER_M_DENOMINATOR as u128)
163 .ok_or(ARITHMETIC_OVERFLOW)?;
164 }
165
166 for (i, liquidity_per_m) in ask_liquidity_per_m.iter().enumerate() {
167 ask_liquidity[i] = u128::from(*liquidity_per_m)
168 .checked_mul(reserves_a as u128)
169 .ok_or(ARITHMETIC_OVERFLOW)?
170 .checked_div(PER_M_DENOMINATOR as u128)
171 .ok_or(ARITHMETIC_OVERFLOW)?;
172 }
173
174 let new_reserves_a = if quote.quote_type.output_is_token_a() {
175 reserves_a
176 .checked_sub(quote.amount_out)
177 .ok_or(ARITHMETIC_OVERFLOW)?
178 } else {
179 reserves_a
180 .checked_add(quote.amount_in)
181 .ok_or(ARITHMETIC_OVERFLOW)?
182 };
183
184 let new_reserves_b = if quote.quote_type.output_is_token_b() {
185 reserves_b
186 .checked_sub(quote.amount_out)
187 .ok_or(ARITHMETIC_OVERFLOW)?
188 } else {
189 reserves_b
190 .checked_add(quote.amount_in)
191 .ok_or(ARITHMETIC_OVERFLOW)?
192 };
193
194 let mut remaining_consumed = quote.amount_out as u128;
195
196 for i in 0..BOOK_LIQUIDITY_LEVELS {
198 let current_level = if quote.quote_type.a_to_b() {
199 &mut bid_liquidity[i]
200 } else {
201 &mut ask_liquidity[i]
202 };
203 let current_liquidity = *current_level;
204 *current_level = current_liquidity.saturating_sub(remaining_consumed);
205 remaining_consumed = remaining_consumed.saturating_sub(current_liquidity);
206 if remaining_consumed == 0 {
207 break;
208 }
209 }
210
211 let best_other_level = if quote.quote_type.a_to_b() {
213 ask_liquidity
214 .iter_mut()
215 .find(|x| **x > 0)
216 .ok_or(INVALID_ORACLE_DATA)?
217 } else {
218 bid_liquidity
219 .iter_mut()
220 .find(|x| **x > 0)
221 .ok_or(INVALID_ORACLE_DATA)?
222 };
223 *best_other_level = best_other_level
224 .checked_add(quote.amount_in as u128)
225 .ok_or(ARITHMETIC_OVERFLOW)?;
226
227 let mut new_bid_liquidity_per_m = [0u32; BOOK_LIQUIDITY_LEVELS];
230 let mut new_ask_liquidity_per_m = [0u32; BOOK_LIQUIDITY_LEVELS];
231
232 for i in 0..BOOK_LIQUIDITY_LEVELS {
234 if new_reserves_b > 0 {
235 new_bid_liquidity_per_m[i] = bid_liquidity[i]
236 .checked_mul(PER_M_DENOMINATOR as u128)
237 .ok_or(ARITHMETIC_OVERFLOW)?
238 .checked_div(new_reserves_b as u128)
239 .ok_or(ARITHMETIC_OVERFLOW)?
240 .try_into()
241 .map_err(|_| AMOUNT_EXCEEDS_MAX_U32)?;
242 }
243 if new_reserves_a > 0 {
244 new_ask_liquidity_per_m[i] = ask_liquidity[i]
245 .checked_mul(PER_M_DENOMINATOR as u128)
246 .ok_or(ARITHMETIC_OVERFLOW)?
247 .checked_div(new_reserves_a as u128)
248 .ok_or(ARITHMETIC_OVERFLOW)?
249 .try_into()
250 .map_err(|_| AMOUNT_EXCEEDS_MAX_U32)?;
251 }
252 }
253
254 let total_bid_per_m = new_bid_liquidity_per_m.iter().sum::<u32>();
257 let total_ask_per_m = new_ask_liquidity_per_m.iter().sum::<u32>();
258
259 let missing_bid_per_m = PER_M_DENOMINATOR as u32 - total_bid_per_m;
260 let missing_ask_per_m = PER_M_DENOMINATOR as u32 - total_ask_per_m;
261
262 let worst_bid_index = new_bid_liquidity_per_m
263 .iter()
264 .enumerate()
265 .rev()
266 .find(|(_, x)| **x > 0)
267 .map(|(i, _)| i)
268 .unwrap_or(BOOK_LIQUIDITY_LEVELS - 1);
269 let worst_ask_index = new_ask_liquidity_per_m
270 .iter()
271 .enumerate()
272 .rev()
273 .find(|(_, x)| **x > 0)
274 .map(|(i, _)| i)
275 .unwrap_or(BOOK_LIQUIDITY_LEVELS - 1);
276
277 new_bid_liquidity_per_m[worst_bid_index] = new_bid_liquidity_per_m[worst_bid_index]
278 .checked_add(missing_bid_per_m)
279 .ok_or(ARITHMETIC_OVERFLOW)?;
280 new_ask_liquidity_per_m[worst_ask_index] = new_ask_liquidity_per_m[worst_ask_index]
281 .checked_add(missing_ask_per_m)
282 .ok_or(ARITHMETIC_OVERFLOW)?;
283
284 Ok(OracleData::Book {
285 price_q64_64: price,
286 spacing: spacing,
287 bid_liquidity_per_m: new_bid_liquidity_per_m,
288 ask_liquidity_per_m: new_ask_liquidity_per_m,
289 })
290}
291
292#[cfg(all(test, feature = "lib"))]
293mod tests {
294 use super::*;
295 use rstest::rstest;
296
297 #[rstest]
298 #[case(11111, true, Ok(vec![1000, 989, 978, 967, 956]))]
299 #[case(11111, false, Ok(vec![1000, 1012, 1023, 1034, 1045]))]
300 #[case(22222, true, Ok(vec![1000, 978, 956, 934, 912]))]
301 #[case(22222, false, Ok(vec![1000, 1023, 1045, 1067, 1089]))]
302 #[case(0, true, Ok(vec![1000, 1000, 1000, 1000, 1000]))]
303 #[case(0, false, Ok(vec![1000, 1000, 1000, 1000, 1000]))]
304 fn test_linear_spacing(
305 #[case] spacing_per_m: u16,
306 #[case] a_to_b: bool,
307 #[case] expected: Result<Vec<u128>, CoreError>,
308 ) {
309 let spacing = BookSpacingType::Linear(spacing_per_m);
310 let prices = (0..5)
311 .map(|i| spacing.price(1000, i, a_to_b))
312 .collect::<Result<Vec<u128>, CoreError>>();
313 assert_eq!(prices, expected);
314 }
315
316 #[rstest]
317 #[case(10000, true, Ok(vec![1000, 989, 980, 969, 960]))]
318 #[case(10000, false, Ok(vec![1000, 1009, 1020, 1029, 1040]))]
319 #[case(20000, true, Ok(vec![1000, 979, 960, 940, 922]))]
320 #[case(20000, false, Ok(vec![1000, 1019, 1040, 1060, 1082]))]
321 #[case(0, true, Ok(vec![1000, 1000, 1000, 1000, 1000]))]
322 #[case(0, false, Ok(vec![1000, 1000, 1000, 1000, 1000]))]
323 fn test_exponential_spacing(
324 #[case] spacing_per_m: u16,
325 #[case] a_to_b: bool,
326 #[case] expected: Result<Vec<u128>, CoreError>,
327 ) {
328 let spacing = BookSpacingType::Exponential(spacing_per_m);
329 let prices = (0..5)
330 .map(|i| spacing.price(1000, i, a_to_b))
331 .collect::<Result<Vec<u128>, CoreError>>();
332 assert_eq!(prices, expected);
333 }
334
335 #[rstest]
336 #[case(QuoteType::TokenAExactIn, Ok(vec![400, 1000, 600]))]
337 #[case(QuoteType::TokenBExactIn, Ok(vec![300, 500, 200]))]
338 #[case(QuoteType::TokenAExactOut, Ok(vec![300, 500, 200]))]
339 #[case(QuoteType::TokenBExactOut, Ok(vec![400, 1000, 600]))]
340 fn test_book_liquidity(
341 #[case] quote_type: QuoteType,
342 #[case] expected: Result<Vec<u64>, CoreError>,
343 ) {
344 let liquidity = book_liquidity(
345 quote_type,
346 1000,
347 BookSpacingType::Linear(0),
348 &[200_000, 0, 500_000, 300_000],
349 &[300_000, 0, 500_000, 200_000],
350 1000,
351 2000,
352 )
353 .map(|liquidity| liquidity.to_vec().iter().map(|x| x.1).collect());
354 assert_eq!(liquidity, expected);
355 }
356
357 #[rstest]
358 #[case(QuoteType::TokenAExactIn, 100, 50, vec![0, 157894, 0, 526315, 315791], vec![0, 363636, 0, 454545, 181819])]
359 #[case(QuoteType::TokenAExactIn, 50, 100, vec![0, 111111, 0, 555555, 333334], vec![0, 333333, 0, 476190, 190477])]
360 #[case(QuoteType::TokenBExactIn, 100, 50, vec![0, 272727, 0, 454545, 272728], vec![0, 263157, 0, 526315, 210528])]
361 #[case(QuoteType::TokenBExactIn, 50, 100, vec![0, 238095, 0, 476190, 285715], vec![0, 222222, 0, 555555, 222223])]
362 #[case(QuoteType::TokenAExactIn, 500, 400, vec![0, 0, 0, 500000, 500000], vec![0, 533333, 0, 333333, 133334])]
363 #[case(QuoteType::TokenAExactIn, 400, 500, vec![0, 0, 0, 400000, 600000], vec![0, 500000, 0, 357142, 142858])]
364 #[case(QuoteType::TokenBExactIn, 500, 400, vec![0, 466666, 0, 333333, 200001], vec![0, 0, 0, 666666, 333334])]
365 #[case(QuoteType::TokenBExactIn, 400, 500, vec![0, 428571, 0, 357142, 214287], vec![0, 0, 0, 600000, 400000])]
366 #[case(QuoteType::TokenAExactIn, 0, 0, vec![0, 200_000, 0, 500_000, 300_000], vec![0, 300_000, 0, 500_000, 200_000])]
367 #[case(QuoteType::TokenBExactIn, 0, 0, vec![0, 200_000, 0, 500_000, 300_000], vec![0, 300_000, 0, 500_000, 200_000])]
368 fn test_new_book_liquidity(
369 #[case] quote_type: QuoteType,
370 #[case] amount_in: u64,
371 #[case] amount_out: u64,
372 #[case] expected_bid_liquidity_per_m: Vec<u32>,
373 #[case] expected_ask_liquidity_per_m: Vec<u32>,
374 ) {
375 let price = 1 << 64;
376 let spacing = BookSpacingType::Linear(0);
377 let liquidity = new_book_liquidity(
378 &Quote {
379 amount_in,
380 amount_out,
381 quote_type,
382 },
383 price,
384 spacing,
385 &[0, 200_000, 0, 500_000, 300_000],
386 &[0, 300_000, 0, 500_000, 200_000],
387 1000,
388 1000,
389 )
390 .unwrap();
391
392 let mut bid_liquidity_per_m = [0u32; 32];
393 let mut ask_liquidity_per_m = [0u32; 32];
394
395 bid_liquidity_per_m[..expected_bid_liquidity_per_m.len()]
396 .copy_from_slice(&expected_bid_liquidity_per_m);
397 ask_liquidity_per_m[..expected_ask_liquidity_per_m.len()]
398 .copy_from_slice(&expected_ask_liquidity_per_m);
399
400 let expected = OracleData::Book {
401 price_q64_64: 1 << 64,
402 spacing: BookSpacingType::Linear(0),
403 bid_liquidity_per_m,
404 ask_liquidity_per_m,
405 };
406 assert_eq!(liquidity, expected);
407 }
408}