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 price_q64_64.checked_sub(spread).ok_or(ARITHMETIC_OVERFLOW)
52 } else {
53 price_q64_64.checked_add(spread).ok_or(ARITHMETIC_OVERFLOW)
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 let total_liquidity_per_m: u64 = liquidity_per_m.iter().map(|&x| x as u64).sum();
120 if total_liquidity_per_m > PER_M_DENOMINATOR as u64 {
121 return Err(INVALID_ORACLE_DATA);
122 }
123
124 let mut book = SingleSideLiquidity::new();
125
126 for (i, liquidity_share) in liquidity_per_m.iter().enumerate() {
127 if *liquidity_share == 0 {
128 continue;
129 }
130
131 let price = spacing.price(price, i, quote_type.a_to_b())?;
132
133 let liquidity: u64 = u128::from(total_liquidity)
134 .checked_mul(*liquidity_share as u128)
135 .ok_or(ARITHMETIC_OVERFLOW)?
136 .checked_div(PER_M_DENOMINATOR as u128)
137 .ok_or(ARITHMETIC_OVERFLOW)?
138 .try_into()
139 .map_err(|_| AMOUNT_EXCEEDS_MAX_U64)?;
140
141 book.push((price, liquidity));
142 }
143
144 Ok(book)
145}
146
147pub(crate) fn new_book_liquidity(
148 quote: &Quote,
149 price: u128,
150 spacing: BookSpacingType,
151 bid_liquidity_per_m: &[u32],
152 ask_liquidity_per_m: &[u32],
153 reserves_a: u64,
154 reserves_b: u64,
155) -> Result<OracleData, CoreError> {
156 let mut bid_liquidity = [0u128; BOOK_LIQUIDITY_LEVELS];
157 let mut ask_liquidity = [0u128; BOOK_LIQUIDITY_LEVELS];
158
159 for (i, liquidity_per_m) in bid_liquidity_per_m.iter().enumerate() {
160 bid_liquidity[i] = u128::from(*liquidity_per_m)
161 .checked_mul(reserves_b as u128)
162 .ok_or(ARITHMETIC_OVERFLOW)?
163 .checked_div(PER_M_DENOMINATOR as u128)
164 .ok_or(ARITHMETIC_OVERFLOW)?;
165 }
166
167 for (i, liquidity_per_m) in ask_liquidity_per_m.iter().enumerate() {
168 ask_liquidity[i] = u128::from(*liquidity_per_m)
169 .checked_mul(reserves_a as u128)
170 .ok_or(ARITHMETIC_OVERFLOW)?
171 .checked_div(PER_M_DENOMINATOR as u128)
172 .ok_or(ARITHMETIC_OVERFLOW)?;
173 }
174
175 let new_reserves_a = if quote.quote_type.output_is_token_a() {
176 reserves_a
177 .checked_sub(quote.amount_out)
178 .ok_or(ARITHMETIC_OVERFLOW)?
179 } else {
180 reserves_a
181 .checked_add(quote.amount_in)
182 .ok_or(ARITHMETIC_OVERFLOW)?
183 };
184
185 let new_reserves_b = if quote.quote_type.output_is_token_b() {
186 reserves_b
187 .checked_sub(quote.amount_out)
188 .ok_or(ARITHMETIC_OVERFLOW)?
189 } else {
190 reserves_b
191 .checked_add(quote.amount_in)
192 .ok_or(ARITHMETIC_OVERFLOW)?
193 };
194
195 let mut remaining_consumed = quote.amount_out as u128;
196
197 for i in 0..BOOK_LIQUIDITY_LEVELS {
199 let current_level = if quote.quote_type.a_to_b() {
200 &mut bid_liquidity[i]
201 } else {
202 &mut ask_liquidity[i]
203 };
204 let current_liquidity = *current_level;
205 *current_level = current_liquidity.saturating_sub(remaining_consumed);
206 remaining_consumed = remaining_consumed.saturating_sub(current_liquidity);
207 if remaining_consumed == 0 {
208 break;
209 }
210 }
211
212 let best_other_level = if quote.quote_type.a_to_b() {
214 let best_index = ask_liquidity
215 .iter_mut()
216 .position(|x| *x > 0)
217 .unwrap_or(BOOK_LIQUIDITY_LEVELS - 1);
218 &mut ask_liquidity[best_index]
219 } else {
220 let best_index = bid_liquidity
221 .iter_mut()
222 .position(|x| *x > 0)
223 .unwrap_or(BOOK_LIQUIDITY_LEVELS - 1);
224 &mut bid_liquidity[best_index]
225 };
226 *best_other_level = best_other_level
227 .checked_add(quote.amount_in as u128)
228 .ok_or(ARITHMETIC_OVERFLOW)?;
229
230 let mut new_bid_liquidity_per_m = [0u32; BOOK_LIQUIDITY_LEVELS];
233 let mut new_ask_liquidity_per_m = [0u32; BOOK_LIQUIDITY_LEVELS];
234
235 for i in 0..BOOK_LIQUIDITY_LEVELS {
237 if new_reserves_b > 0 {
238 new_bid_liquidity_per_m[i] = bid_liquidity[i]
239 .checked_mul(PER_M_DENOMINATOR as u128)
240 .ok_or(ARITHMETIC_OVERFLOW)?
241 .checked_div(new_reserves_b as u128)
242 .ok_or(ARITHMETIC_OVERFLOW)?
243 .try_into()
244 .map_err(|_| AMOUNT_EXCEEDS_MAX_U32)?;
245 }
246 if new_reserves_a > 0 {
247 new_ask_liquidity_per_m[i] = ask_liquidity[i]
248 .checked_mul(PER_M_DENOMINATOR as u128)
249 .ok_or(ARITHMETIC_OVERFLOW)?
250 .checked_div(new_reserves_a as u128)
251 .ok_or(ARITHMETIC_OVERFLOW)?
252 .try_into()
253 .map_err(|_| AMOUNT_EXCEEDS_MAX_U32)?;
254 }
255 }
256
257 let total_bid_per_m = new_bid_liquidity_per_m.iter().sum::<u32>();
260 let total_ask_per_m = new_ask_liquidity_per_m.iter().sum::<u32>();
261
262 let missing_bid_per_m = PER_M_DENOMINATOR as u32 - total_bid_per_m;
263 let missing_ask_per_m = PER_M_DENOMINATOR as u32 - total_ask_per_m;
264
265 let worst_bid_index = new_bid_liquidity_per_m
266 .iter()
267 .enumerate()
268 .rev()
269 .find(|(_, x)| **x > 0)
270 .map(|(i, _)| i)
271 .unwrap_or(BOOK_LIQUIDITY_LEVELS - 1);
272 let worst_ask_index = new_ask_liquidity_per_m
273 .iter()
274 .enumerate()
275 .rev()
276 .find(|(_, x)| **x > 0)
277 .map(|(i, _)| i)
278 .unwrap_or(BOOK_LIQUIDITY_LEVELS - 1);
279
280 new_bid_liquidity_per_m[worst_bid_index] = new_bid_liquidity_per_m[worst_bid_index]
281 .checked_add(missing_bid_per_m)
282 .ok_or(ARITHMETIC_OVERFLOW)?;
283 new_ask_liquidity_per_m[worst_ask_index] = new_ask_liquidity_per_m[worst_ask_index]
284 .checked_add(missing_ask_per_m)
285 .ok_or(ARITHMETIC_OVERFLOW)?;
286
287 Ok(OracleData::OrderBook {
288 price_q64_64: price,
289 spacing,
290 bid_liquidity_per_m: new_bid_liquidity_per_m,
291 ask_liquidity_per_m: new_ask_liquidity_per_m,
292 })
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298 use rstest::rstest;
299
300 #[rstest]
301 #[case(11111, true, Ok(vec![1000, 989, 978, 967, 956]))]
302 #[case(11111, false, Ok(vec![1000, 1012, 1023, 1034, 1045]))]
303 #[case(22222, true, Ok(vec![1000, 978, 956, 934, 912]))]
304 #[case(22222, false, Ok(vec![1000, 1023, 1045, 1067, 1089]))]
305 #[case(0, true, Ok(vec![1000, 1000, 1000, 1000, 1000]))]
306 #[case(0, false, Ok(vec![1000, 1000, 1000, 1000, 1000]))]
307 fn test_linear_spacing(
308 #[case] spacing_per_m: u16,
309 #[case] a_to_b: bool,
310 #[case] expected: Result<Vec<u128>, CoreError>,
311 ) {
312 let spacing = BookSpacingType::Linear(spacing_per_m);
313 let prices = (0..5)
314 .map(|i| spacing.price(1000, i, a_to_b))
315 .collect::<Result<Vec<u128>, CoreError>>();
316 assert_eq!(prices, expected);
317 }
318
319 #[rstest]
320 #[case(10000, true, Ok(vec![1000, 989, 980, 969, 960]))]
321 #[case(10000, false, Ok(vec![1000, 1009, 1020, 1029, 1040]))]
322 #[case(20000, true, Ok(vec![1000, 979, 960, 940, 922]))]
323 #[case(20000, false, Ok(vec![1000, 1019, 1040, 1060, 1082]))]
324 #[case(0, true, Ok(vec![1000, 1000, 1000, 1000, 1000]))]
325 #[case(0, false, Ok(vec![1000, 1000, 1000, 1000, 1000]))]
326 fn test_exponential_spacing(
327 #[case] spacing_per_m: u16,
328 #[case] a_to_b: bool,
329 #[case] expected: Result<Vec<u128>, CoreError>,
330 ) {
331 let spacing = BookSpacingType::Exponential(spacing_per_m);
332 let prices = (0..5)
333 .map(|i| spacing.price(1000, i, a_to_b))
334 .collect::<Result<Vec<u128>, CoreError>>();
335 assert_eq!(prices, expected);
336 }
337
338 #[rstest]
339 #[case(QuoteType::TokenAExactIn, Ok(vec![400, 1000, 600]))]
340 #[case(QuoteType::TokenBExactIn, Ok(vec![300, 500, 200]))]
341 #[case(QuoteType::TokenAExactOut, Ok(vec![300, 500, 200]))]
342 #[case(QuoteType::TokenBExactOut, Ok(vec![400, 1000, 600]))]
343 fn test_book_liquidity(
344 #[case] quote_type: QuoteType,
345 #[case] expected: Result<Vec<u64>, CoreError>,
346 ) {
347 let liquidity = book_liquidity(
348 quote_type,
349 1000,
350 BookSpacingType::Linear(0),
351 &[200_000, 0, 500_000, 300_000],
352 &[300_000, 0, 500_000, 200_000],
353 1000,
354 2000,
355 )
356 .map(|liquidity| liquidity.as_slice().iter().map(|x| x.1).collect());
357 assert_eq!(liquidity, expected);
358 }
359
360 #[rstest]
361 #[case(QuoteType::TokenAExactIn, 100, 50, vec![0, 157894, 0, 526315, 315791], vec![0, 363636, 0, 454545, 181819])]
362 #[case(QuoteType::TokenAExactIn, 50, 100, vec![0, 111111, 0, 555555, 333334], vec![0, 333333, 0, 476190, 190477])]
363 #[case(QuoteType::TokenBExactIn, 100, 50, vec![0, 272727, 0, 454545, 272728], vec![0, 263157, 0, 526315, 210528])]
364 #[case(QuoteType::TokenBExactIn, 50, 100, vec![0, 238095, 0, 476190, 285715], vec![0, 222222, 0, 555555, 222223])]
365 #[case(QuoteType::TokenAExactIn, 500, 400, vec![0, 0, 0, 500000, 500000], vec![0, 533333, 0, 333333, 133334])]
366 #[case(QuoteType::TokenAExactIn, 400, 500, vec![0, 0, 0, 400000, 600000], vec![0, 500000, 0, 357142, 142858])]
367 #[case(QuoteType::TokenBExactIn, 500, 400, vec![0, 466666, 0, 333333, 200001], vec![0, 0, 0, 666666, 333334])]
368 #[case(QuoteType::TokenBExactIn, 400, 500, vec![0, 428571, 0, 357142, 214287], vec![0, 0, 0, 600000, 400000])]
369 #[case(QuoteType::TokenAExactIn, 0, 0, vec![0, 200_000, 0, 500_000, 300_000], vec![0, 300_000, 0, 500_000, 200_000])]
370 #[case(QuoteType::TokenBExactIn, 0, 0, vec![0, 200_000, 0, 500_000, 300_000], vec![0, 300_000, 0, 500_000, 200_000])]
371 fn test_new_book_liquidity(
372 #[case] quote_type: QuoteType,
373 #[case] amount_in: u64,
374 #[case] amount_out: u64,
375 #[case] expected_bid_liquidity_per_m: Vec<u32>,
376 #[case] expected_ask_liquidity_per_m: Vec<u32>,
377 ) {
378 let price = 1 << 64;
379 let spacing = BookSpacingType::Linear(0);
380 let liquidity = new_book_liquidity(
381 &Quote {
382 amount_in,
383 amount_out,
384 quote_type,
385 },
386 price,
387 spacing,
388 &[0, 200_000, 0, 500_000, 300_000],
389 &[0, 300_000, 0, 500_000, 200_000],
390 1000,
391 1000,
392 )
393 .unwrap();
394
395 let mut bid_liquidity_per_m = [0u32; 32];
396 let mut ask_liquidity_per_m = [0u32; 32];
397
398 bid_liquidity_per_m[..expected_bid_liquidity_per_m.len()]
399 .copy_from_slice(&expected_bid_liquidity_per_m);
400 ask_liquidity_per_m[..expected_ask_liquidity_per_m.len()]
401 .copy_from_slice(&expected_ask_liquidity_per_m);
402
403 let expected = OracleData::OrderBook {
404 price_q64_64: 1 << 64,
405 spacing: BookSpacingType::Linear(0),
406 bid_liquidity_per_m,
407 ask_liquidity_per_m,
408 };
409 assert_eq!(liquidity, expected);
410 }
411
412 #[rstest]
413 #[case(100, 50, vec![0, 157894, 0, 526315, 315791])]
414 #[case(50, 100, vec![0, 111111, 0, 555555, 333334])]
415 fn test_new_book_liquidity_no_a_liquidity(
416 #[case] amount_in: u64,
417 #[case] amount_out: u64,
418 #[case] expected_bid_liquidity_per_m: Vec<u32>,
419 ) {
420 let price = 1 << 64;
421 let spacing = BookSpacingType::Linear(0);
422 let liquidity = new_book_liquidity(
423 &Quote {
424 amount_in,
425 amount_out,
426 quote_type: QuoteType::TokenAExactIn,
427 },
428 price,
429 spacing,
430 &[0, 200_000, 0, 500_000, 300_000],
431 &[0, 0, 0, 0, 0],
432 0,
433 1000,
434 )
435 .unwrap();
436
437 let mut bid_liquidity_per_m = [0u32; 32];
438 let mut ask_liquidity_per_m = [0u32; 32];
439
440 bid_liquidity_per_m[..expected_bid_liquidity_per_m.len()]
441 .copy_from_slice(&expected_bid_liquidity_per_m);
442 ask_liquidity_per_m[BOOK_LIQUIDITY_LEVELS - 1] = PER_M_DENOMINATOR as u32;
443
444 let expected = OracleData::OrderBook {
445 price_q64_64: 1 << 64,
446 spacing: BookSpacingType::Linear(0),
447 bid_liquidity_per_m,
448 ask_liquidity_per_m,
449 };
450 assert_eq!(liquidity, expected);
451 }
452
453 #[rstest]
454 #[case(100, 50, vec![0, 263157, 0, 526315, 210528])]
455 #[case(50, 100, vec![0, 222222, 0, 555555, 222223])]
456 fn test_new_book_liquidity_no_b_liquidity(
457 #[case] amount_in: u64,
458 #[case] amount_out: u64,
459 #[case] expected_ask_liquidity_per_m: Vec<u32>,
460 ) {
461 let price = 1 << 64;
462 let spacing = BookSpacingType::Linear(0);
463 let liquidity = new_book_liquidity(
464 &Quote {
465 amount_in,
466 amount_out,
467 quote_type: QuoteType::TokenBExactIn,
468 },
469 price,
470 spacing,
471 &[0, 0, 0, 0, 0],
472 &[0, 300_000, 0, 500_000, 200_000],
473 1000,
474 0,
475 )
476 .unwrap();
477
478 let mut bid_liquidity_per_m = [0u32; 32];
479 let mut ask_liquidity_per_m = [0u32; 32];
480
481 bid_liquidity_per_m[BOOK_LIQUIDITY_LEVELS - 1] = PER_M_DENOMINATOR as u32;
482 ask_liquidity_per_m[..expected_ask_liquidity_per_m.len()]
483 .copy_from_slice(&expected_ask_liquidity_per_m);
484
485 let expected = OracleData::OrderBook {
486 price_q64_64: 1 << 64,
487 spacing: BookSpacingType::Linear(0),
488 bid_liquidity_per_m,
489 ask_liquidity_per_m,
490 };
491 assert_eq!(liquidity, expected);
492 }
493}