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 let best_index = ask_liquidity
214 .iter_mut()
215 .position(|x| *x > 0)
216 .unwrap_or(BOOK_LIQUIDITY_LEVELS - 1);
217 &mut ask_liquidity[best_index]
218 } else {
219 let best_index = bid_liquidity
220 .iter_mut()
221 .position(|x| *x > 0)
222 .unwrap_or(BOOK_LIQUIDITY_LEVELS - 1);
223 &mut bid_liquidity[best_index]
224 };
225 *best_other_level = best_other_level
226 .checked_add(quote.amount_in as u128)
227 .ok_or(ARITHMETIC_OVERFLOW)?;
228
229 let mut new_bid_liquidity_per_m = [0u32; BOOK_LIQUIDITY_LEVELS];
232 let mut new_ask_liquidity_per_m = [0u32; BOOK_LIQUIDITY_LEVELS];
233
234 for i in 0..BOOK_LIQUIDITY_LEVELS {
236 if new_reserves_b > 0 {
237 new_bid_liquidity_per_m[i] = bid_liquidity[i]
238 .checked_mul(PER_M_DENOMINATOR as u128)
239 .ok_or(ARITHMETIC_OVERFLOW)?
240 .checked_div(new_reserves_b as u128)
241 .ok_or(ARITHMETIC_OVERFLOW)?
242 .try_into()
243 .map_err(|_| AMOUNT_EXCEEDS_MAX_U32)?;
244 }
245 if new_reserves_a > 0 {
246 new_ask_liquidity_per_m[i] = ask_liquidity[i]
247 .checked_mul(PER_M_DENOMINATOR as u128)
248 .ok_or(ARITHMETIC_OVERFLOW)?
249 .checked_div(new_reserves_a as u128)
250 .ok_or(ARITHMETIC_OVERFLOW)?
251 .try_into()
252 .map_err(|_| AMOUNT_EXCEEDS_MAX_U32)?;
253 }
254 }
255
256 let total_bid_per_m = new_bid_liquidity_per_m.iter().sum::<u32>();
259 let total_ask_per_m = new_ask_liquidity_per_m.iter().sum::<u32>();
260
261 let missing_bid_per_m = PER_M_DENOMINATOR as u32 - total_bid_per_m;
262 let missing_ask_per_m = PER_M_DENOMINATOR as u32 - total_ask_per_m;
263
264 let worst_bid_index = new_bid_liquidity_per_m
265 .iter()
266 .enumerate()
267 .rev()
268 .find(|(_, x)| **x > 0)
269 .map(|(i, _)| i)
270 .unwrap_or(BOOK_LIQUIDITY_LEVELS - 1);
271 let worst_ask_index = new_ask_liquidity_per_m
272 .iter()
273 .enumerate()
274 .rev()
275 .find(|(_, x)| **x > 0)
276 .map(|(i, _)| i)
277 .unwrap_or(BOOK_LIQUIDITY_LEVELS - 1);
278
279 new_bid_liquidity_per_m[worst_bid_index] = new_bid_liquidity_per_m[worst_bid_index]
280 .checked_add(missing_bid_per_m)
281 .ok_or(ARITHMETIC_OVERFLOW)?;
282 new_ask_liquidity_per_m[worst_ask_index] = new_ask_liquidity_per_m[worst_ask_index]
283 .checked_add(missing_ask_per_m)
284 .ok_or(ARITHMETIC_OVERFLOW)?;
285
286 Ok(OracleData::OrderBook {
287 price_q64_64: price,
288 spacing,
289 bid_liquidity_per_m: new_bid_liquidity_per_m,
290 ask_liquidity_per_m: new_ask_liquidity_per_m,
291 })
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297 use rstest::rstest;
298
299 #[rstest]
300 #[case(11111, true, Ok(vec![1000, 989, 978, 967, 956]))]
301 #[case(11111, false, Ok(vec![1000, 1012, 1023, 1034, 1045]))]
302 #[case(22222, true, Ok(vec![1000, 978, 956, 934, 912]))]
303 #[case(22222, false, Ok(vec![1000, 1023, 1045, 1067, 1089]))]
304 #[case(0, true, Ok(vec![1000, 1000, 1000, 1000, 1000]))]
305 #[case(0, false, Ok(vec![1000, 1000, 1000, 1000, 1000]))]
306 fn test_linear_spacing(
307 #[case] spacing_per_m: u16,
308 #[case] a_to_b: bool,
309 #[case] expected: Result<Vec<u128>, CoreError>,
310 ) {
311 let spacing = BookSpacingType::Linear(spacing_per_m);
312 let prices = (0..5)
313 .map(|i| spacing.price(1000, i, a_to_b))
314 .collect::<Result<Vec<u128>, CoreError>>();
315 assert_eq!(prices, expected);
316 }
317
318 #[rstest]
319 #[case(10000, true, Ok(vec![1000, 989, 980, 969, 960]))]
320 #[case(10000, false, Ok(vec![1000, 1009, 1020, 1029, 1040]))]
321 #[case(20000, true, Ok(vec![1000, 979, 960, 940, 922]))]
322 #[case(20000, false, Ok(vec![1000, 1019, 1040, 1060, 1082]))]
323 #[case(0, true, Ok(vec![1000, 1000, 1000, 1000, 1000]))]
324 #[case(0, false, Ok(vec![1000, 1000, 1000, 1000, 1000]))]
325 fn test_exponential_spacing(
326 #[case] spacing_per_m: u16,
327 #[case] a_to_b: bool,
328 #[case] expected: Result<Vec<u128>, CoreError>,
329 ) {
330 let spacing = BookSpacingType::Exponential(spacing_per_m);
331 let prices = (0..5)
332 .map(|i| spacing.price(1000, i, a_to_b))
333 .collect::<Result<Vec<u128>, CoreError>>();
334 assert_eq!(prices, expected);
335 }
336
337 #[rstest]
338 #[case(QuoteType::TokenAExactIn, Ok(vec![400, 1000, 600]))]
339 #[case(QuoteType::TokenBExactIn, Ok(vec![300, 500, 200]))]
340 #[case(QuoteType::TokenAExactOut, Ok(vec![300, 500, 200]))]
341 #[case(QuoteType::TokenBExactOut, Ok(vec![400, 1000, 600]))]
342 fn test_book_liquidity(
343 #[case] quote_type: QuoteType,
344 #[case] expected: Result<Vec<u64>, CoreError>,
345 ) {
346 let liquidity = book_liquidity(
347 quote_type,
348 1000,
349 BookSpacingType::Linear(0),
350 &[200_000, 0, 500_000, 300_000],
351 &[300_000, 0, 500_000, 200_000],
352 1000,
353 2000,
354 )
355 .map(|liquidity| liquidity.as_slice().iter().map(|x| x.1).collect());
356 assert_eq!(liquidity, expected);
357 }
358
359 #[rstest]
360 #[case(QuoteType::TokenAExactIn, 100, 50, vec![0, 157894, 0, 526315, 315791], vec![0, 363636, 0, 454545, 181819])]
361 #[case(QuoteType::TokenAExactIn, 50, 100, vec![0, 111111, 0, 555555, 333334], vec![0, 333333, 0, 476190, 190477])]
362 #[case(QuoteType::TokenBExactIn, 100, 50, vec![0, 272727, 0, 454545, 272728], vec![0, 263157, 0, 526315, 210528])]
363 #[case(QuoteType::TokenBExactIn, 50, 100, vec![0, 238095, 0, 476190, 285715], vec![0, 222222, 0, 555555, 222223])]
364 #[case(QuoteType::TokenAExactIn, 500, 400, vec![0, 0, 0, 500000, 500000], vec![0, 533333, 0, 333333, 133334])]
365 #[case(QuoteType::TokenAExactIn, 400, 500, vec![0, 0, 0, 400000, 600000], vec![0, 500000, 0, 357142, 142858])]
366 #[case(QuoteType::TokenBExactIn, 500, 400, vec![0, 466666, 0, 333333, 200001], vec![0, 0, 0, 666666, 333334])]
367 #[case(QuoteType::TokenBExactIn, 400, 500, vec![0, 428571, 0, 357142, 214287], vec![0, 0, 0, 600000, 400000])]
368 #[case(QuoteType::TokenAExactIn, 0, 0, vec![0, 200_000, 0, 500_000, 300_000], vec![0, 300_000, 0, 500_000, 200_000])]
369 #[case(QuoteType::TokenBExactIn, 0, 0, vec![0, 200_000, 0, 500_000, 300_000], vec![0, 300_000, 0, 500_000, 200_000])]
370 fn test_new_book_liquidity(
371 #[case] quote_type: QuoteType,
372 #[case] amount_in: u64,
373 #[case] amount_out: u64,
374 #[case] expected_bid_liquidity_per_m: Vec<u32>,
375 #[case] expected_ask_liquidity_per_m: Vec<u32>,
376 ) {
377 let price = 1 << 64;
378 let spacing = BookSpacingType::Linear(0);
379 let liquidity = new_book_liquidity(
380 &Quote {
381 amount_in,
382 amount_out,
383 quote_type,
384 },
385 price,
386 spacing,
387 &[0, 200_000, 0, 500_000, 300_000],
388 &[0, 300_000, 0, 500_000, 200_000],
389 1000,
390 1000,
391 )
392 .unwrap();
393
394 let mut bid_liquidity_per_m = [0u32; 32];
395 let mut ask_liquidity_per_m = [0u32; 32];
396
397 bid_liquidity_per_m[..expected_bid_liquidity_per_m.len()]
398 .copy_from_slice(&expected_bid_liquidity_per_m);
399 ask_liquidity_per_m[..expected_ask_liquidity_per_m.len()]
400 .copy_from_slice(&expected_ask_liquidity_per_m);
401
402 let expected = OracleData::OrderBook {
403 price_q64_64: 1 << 64,
404 spacing: BookSpacingType::Linear(0),
405 bid_liquidity_per_m,
406 ask_liquidity_per_m,
407 };
408 assert_eq!(liquidity, expected);
409 }
410
411 #[rstest]
412 #[case(100, 50, vec![0, 157894, 0, 526315, 315791])]
413 #[case(50, 100, vec![0, 111111, 0, 555555, 333334])]
414 fn test_new_book_liquidity_no_a_liquidity(
415 #[case] amount_in: u64,
416 #[case] amount_out: u64,
417 #[case] expected_bid_liquidity_per_m: Vec<u32>,
418 ) {
419 let price = 1 << 64;
420 let spacing = BookSpacingType::Linear(0);
421 let liquidity = new_book_liquidity(
422 &Quote {
423 amount_in,
424 amount_out,
425 quote_type: QuoteType::TokenAExactIn,
426 },
427 price,
428 spacing,
429 &[0, 200_000, 0, 500_000, 300_000],
430 &[0, 0, 0, 0, 0],
431 0,
432 1000,
433 )
434 .unwrap();
435
436 let mut bid_liquidity_per_m = [0u32; 32];
437 let mut ask_liquidity_per_m = [0u32; 32];
438
439 bid_liquidity_per_m[..expected_bid_liquidity_per_m.len()]
440 .copy_from_slice(&expected_bid_liquidity_per_m);
441 ask_liquidity_per_m[BOOK_LIQUIDITY_LEVELS - 1] = PER_M_DENOMINATOR as u32;
442
443 let expected = OracleData::OrderBook {
444 price_q64_64: 1 << 64,
445 spacing: BookSpacingType::Linear(0),
446 bid_liquidity_per_m,
447 ask_liquidity_per_m,
448 };
449 assert_eq!(liquidity, expected);
450 }
451
452 #[rstest]
453 #[case(100, 50, vec![0, 263157, 0, 526315, 210528])]
454 #[case(50, 100, vec![0, 222222, 0, 555555, 222223])]
455 fn test_new_book_liquidity_no_b_liquidity(
456 #[case] amount_in: u64,
457 #[case] amount_out: u64,
458 #[case] expected_ask_liquidity_per_m: Vec<u32>,
459 ) {
460 let price = 1 << 64;
461 let spacing = BookSpacingType::Linear(0);
462 let liquidity = new_book_liquidity(
463 &Quote {
464 amount_in,
465 amount_out,
466 quote_type: QuoteType::TokenBExactIn,
467 },
468 price,
469 spacing,
470 &[0, 0, 0, 0, 0],
471 &[0, 300_000, 0, 500_000, 200_000],
472 1000,
473 0,
474 )
475 .unwrap();
476
477 let mut bid_liquidity_per_m = [0u32; 32];
478 let mut ask_liquidity_per_m = [0u32; 32];
479
480 bid_liquidity_per_m[BOOK_LIQUIDITY_LEVELS - 1] = PER_M_DENOMINATOR as u32;
481 ask_liquidity_per_m[..expected_ask_liquidity_per_m.len()]
482 .copy_from_slice(&expected_ask_liquidity_per_m);
483
484 let expected = OracleData::OrderBook {
485 price_q64_64: 1 << 64,
486 spacing: BookSpacingType::Linear(0),
487 bid_liquidity_per_m,
488 ask_liquidity_per_m,
489 };
490 assert_eq!(liquidity, expected);
491 }
492}