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 Linear(u16),
23 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}