1#[cfg(feature = "wasm")]
4use equanetwork_macros::wasm_expose;
5
6use super::PER_M_DENOMINATOR;
7
8use super::{SwapVault, AMOUNT_EXCEEDS_MAX_I32, AMOUNT_EXCEEDS_MAX_U64};
9
10use super::error::{CoreError, ARITHMETIC_OVERFLOW};
11
12#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
14#[cfg_attr(feature = "wasm", wasm_expose)]
15pub enum SkewExponent {
16 #[default]
17 Linear,
18 Quadratic,
19 Cubic,
20}
21
22impl SkewExponent {
23 pub fn value(&self) -> u32 {
24 match self {
25 Self::Linear => 1,
26 Self::Quadratic => 2,
27 Self::Cubic => 3,
28 }
29 }
30}
31
32#[derive(Debug, Clone, Copy, Eq, PartialEq)]
33#[cfg_attr(feature = "wasm", wasm_expose)]
34pub struct SkewVault {
35 pub inventory_limit_upper: u64,
36 pub inventory_limit_lower: u64,
37 pub positive_skew_per_m: u32,
38 pub negative_skew_per_m: u32,
39 pub lower_skew_offset: u64,
40 pub upper_skew_offset: u64,
41 pub skew_exponent: SkewExponent,
42}
43
44impl From<SwapVault> for SkewVault {
45 fn from(swap_vault: SwapVault) -> Self {
46 Self {
47 inventory_limit_upper: swap_vault.inventory_limit_upper,
48 inventory_limit_lower: swap_vault.inventory_limit_lower,
49 positive_skew_per_m: swap_vault.positive_skew_per_m,
50 negative_skew_per_m: swap_vault.negative_skew_per_m,
51 lower_skew_offset: swap_vault.lower_skew_offset,
52 upper_skew_offset: swap_vault.upper_skew_offset,
53 skew_exponent: swap_vault.skew_exponent,
54 }
55 }
56}
57
58#[derive(Debug, Clone, Copy, Eq, PartialEq)]
59#[cfg_attr(feature = "wasm", wasm_expose)]
60pub struct SkewBand {
61 pub index: i8,
62 pub lower_limit: u64,
63 pub upper_limit: u64,
64 pub skew_per_m: i32,
65}
66
67pub const BAND_COUNT: i8 = 32;
68
69#[cfg_attr(feature = "wasm", wasm_expose)]
76pub fn skew_band(balance: u64, increasing: bool, vault: SkewVault) -> Result<SkewBand, CoreError> {
77 let index = band_index(balance, increasing, &vault)?;
78 let intensity = select_intensity(index, increasing, &vault);
79 let (lower_limit, upper_limit) = band_limits(index, &vault)?;
80 let skew_per_m = band_skew(index, intensity, vault.skew_exponent, increasing)?;
81 Ok(SkewBand {
82 index,
83 lower_limit,
84 upper_limit,
85 skew_per_m,
86 })
87}
88
89pub(crate) fn prev_band(
90 current_band: &SkewBand,
91 vault: &SkewVault,
92) -> Result<Option<SkewBand>, CoreError> {
93 let prev_index = current_band.index - 1;
94 if prev_index < -BAND_COUNT {
95 return Ok(None);
96 }
97 let intensity = select_intensity(prev_index, false, vault);
98
99 let (lower_limit, upper_limit) = band_limits(prev_index, vault)?;
100 let skew_per_m = band_skew(prev_index, intensity, vault.skew_exponent, false)?;
101 Ok(Some(SkewBand {
102 index: prev_index,
103 lower_limit,
104 upper_limit,
105 skew_per_m,
106 }))
107}
108
109pub(crate) fn next_band(
110 current_band: &SkewBand,
111 vault: &SkewVault,
112) -> Result<Option<SkewBand>, CoreError> {
113 let next_index = current_band.index + 1;
114 if next_index > BAND_COUNT {
115 return Ok(None);
116 }
117 let intensity = select_intensity(next_index, true, vault);
118
119 let (lower_limit, upper_limit) = band_limits(next_index, vault)?;
120 let skew_per_m = band_skew(next_index, intensity, vault.skew_exponent, true)?;
121 Ok(Some(SkewBand {
122 index: next_index,
123 lower_limit,
124 upper_limit,
125 skew_per_m,
126 }))
127}
128
129fn band_index(balance: u64, increasing: bool, vault: &SkewVault) -> Result<i8, CoreError> {
130 let mut index = closed_band_index(balance, vault)?;
131 let (lower_limit, upper_limit) = band_limits(index, vault)?;
134 if increasing && balance == upper_limit && index < BAND_COUNT {
135 index += 1;
136 } else if !increasing && balance == lower_limit && index > -BAND_COUNT {
137 index -= 1;
138 }
139 Ok(index)
140}
141
142fn closed_band_index(balance: u64, vault: &SkewVault) -> Result<i8, CoreError> {
143 let (diff, pos, sign) = if balance < vault.lower_skew_offset {
144 let diff = vault
145 .lower_skew_offset
146 .checked_sub(vault.inventory_limit_lower)
147 .ok_or(ARITHMETIC_OVERFLOW)?;
148 let pos = vault
149 .lower_skew_offset
150 .checked_sub(balance)
151 .ok_or(ARITHMETIC_OVERFLOW)?;
152 (diff, pos, -1)
153 } else if balance > vault.upper_skew_offset {
154 let diff = vault
155 .inventory_limit_upper
156 .checked_sub(vault.upper_skew_offset)
157 .ok_or(ARITHMETIC_OVERFLOW)?;
158 let pos = balance
159 .checked_sub(vault.upper_skew_offset)
160 .ok_or(ARITHMETIC_OVERFLOW)?;
161 (diff, pos, 1)
162 } else {
163 return Ok(0);
164 };
165
166 let product = pos
167 .checked_mul(BAND_COUNT as u64)
168 .ok_or(ARITHMETIC_OVERFLOW)?;
169 let quotient = product
170 .checked_div(diff.into())
171 .ok_or(ARITHMETIC_OVERFLOW)?;
172 let remainder = product
173 .checked_rem(diff.into())
174 .ok_or(ARITHMETIC_OVERFLOW)?;
175 let raw_index = if remainder > 0 {
176 quotient + 1
177 } else {
178 quotient
179 };
180 let index = raw_index.min(BAND_COUNT as u64) as i8;
181 Ok(sign * index)
182}
183
184fn away_from_dead_zone(index: i8, increasing: bool) -> bool {
185 (index > 0 && increasing) || (index < 0 && !increasing)
186}
187
188fn skew_sign(index: i8, increasing: bool) -> i32 {
189 if index == 0 {
190 0
191 } else if away_from_dead_zone(index, increasing) {
192 1
193 } else {
194 -1
195 }
196}
197
198fn select_intensity(index: i8, increasing: bool, vault: &SkewVault) -> u32 {
199 match skew_sign(index, increasing) {
200 1 => vault.positive_skew_per_m,
201 -1 => vault.negative_skew_per_m,
202 _ => 0,
203 }
204}
205
206fn band_limits(index: i8, vault: &SkewVault) -> Result<(u64, u64), CoreError> {
207 let (start, diff) = if index < 0 {
208 let diff = vault
209 .lower_skew_offset
210 .checked_sub(vault.inventory_limit_lower)
211 .ok_or(ARITHMETIC_OVERFLOW)?;
212 (vault.lower_skew_offset, diff)
213 } else if index > 0 {
214 let diff = vault
215 .inventory_limit_upper
216 .checked_sub(vault.upper_skew_offset)
217 .ok_or(ARITHMETIC_OVERFLOW)?;
218 (vault.upper_skew_offset, diff)
219 } else {
220 return Ok((vault.lower_skew_offset, vault.upper_skew_offset));
221 };
222
223 let abs_index = index.unsigned_abs() as u128;
226 let range = u128::from(diff);
227 let band_count = BAND_COUNT as u128;
228
229 let inner_delta = range
230 .checked_mul(abs_index.saturating_sub(1))
231 .ok_or(ARITHMETIC_OVERFLOW)?
232 .checked_div(band_count)
233 .ok_or(ARITHMETIC_OVERFLOW)?;
234 let outer_delta = range
235 .checked_mul(abs_index)
236 .ok_or(ARITHMETIC_OVERFLOW)?
237 .checked_div(band_count)
238 .ok_or(ARITHMETIC_OVERFLOW)?;
239
240 let (lower_limit, upper_limit) = if index > 0 {
241 let inner_limit: u64 = u128::from(start)
242 .checked_add(inner_delta)
243 .ok_or(ARITHMETIC_OVERFLOW)?
244 .try_into()
245 .map_err(|_| AMOUNT_EXCEEDS_MAX_U64)?;
246 if index == BAND_COUNT {
247 (inner_limit, u64::MAX)
248 } else {
249 let outer_limit: u64 = u128::from(start)
250 .checked_add(outer_delta)
251 .ok_or(ARITHMETIC_OVERFLOW)?
252 .try_into()
253 .map_err(|_| AMOUNT_EXCEEDS_MAX_U64)?;
254 (inner_limit, outer_limit)
255 }
256 } else {
257 let inner_limit: u64 = u128::from(start)
258 .checked_sub(inner_delta)
259 .ok_or(ARITHMETIC_OVERFLOW)?
260 .try_into()
261 .map_err(|_| AMOUNT_EXCEEDS_MAX_U64)?;
262 if index == -BAND_COUNT {
263 (0, inner_limit)
264 } else {
265 let outer_limit: u64 = u128::from(start)
266 .checked_sub(outer_delta)
267 .ok_or(ARITHMETIC_OVERFLOW)?
268 .try_into()
269 .map_err(|_| AMOUNT_EXCEEDS_MAX_U64)?;
270 (outer_limit, inner_limit)
271 }
272 };
273
274 Ok((lower_limit, upper_limit))
275}
276
277fn band_skew(
278 index: i8,
279 intensity: u32,
280 exponent: SkewExponent,
281 increasing: bool,
282) -> Result<i32, CoreError> {
283 let sign = skew_sign(index, increasing);
284 let numerator = u128::from(index.unsigned_abs())
285 .checked_pow(exponent.value())
286 .ok_or(ARITHMETIC_OVERFLOW)?
287 .checked_mul(intensity as u128)
288 .ok_or(ARITHMETIC_OVERFLOW)?;
289 let denominator = (BAND_COUNT as u128)
290 .checked_pow(exponent.value())
291 .ok_or(ARITHMETIC_OVERFLOW)?;
292
293 let quotient = numerator
294 .checked_div(denominator)
295 .ok_or(ARITHMETIC_OVERFLOW)?;
296 let remainder = numerator
297 .checked_rem(denominator)
298 .ok_or(ARITHMETIC_OVERFLOW)?;
299 let abs_result = if remainder > 0 {
300 quotient + 1
301 } else {
302 quotient
303 };
304
305 let result: i32 = abs_result
306 .min(PER_M_DENOMINATOR as u128)
307 .try_into()
308 .map_err(|_| AMOUNT_EXCEEDS_MAX_I32)?;
309 result.checked_mul(sign).ok_or(ARITHMETIC_OVERFLOW)
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315 use rstest::rstest;
316
317 #[rstest]
318 #[case(0, true, 0)]
319 #[case(0, false, 0)]
320 #[case(-1, true, 3_000)]
321 #[case(-1, false, 7_000)]
322 #[case(1, true, 7_000)]
323 #[case(1, false, 3_000)]
324 fn test_select_intensity(#[case] index: i8, #[case] increasing: bool, #[case] expected: u32) {
325 let vault = SkewVault {
326 inventory_limit_upper: 64_000,
327 inventory_limit_lower: 0,
328 positive_skew_per_m: 7_000,
329 negative_skew_per_m: 3_000,
330 lower_skew_offset: 16_000,
331 upper_skew_offset: 48_000,
332 skew_exponent: SkewExponent::Linear,
333 };
334 assert_eq!(select_intensity(index, increasing, &vault), expected);
335 }
336
337 #[rstest]
338 #[case(SkewExponent::Linear, 1)]
339 #[case(SkewExponent::Quadratic, 2)]
340 #[case(SkewExponent::Cubic, 3)]
341 fn test_skew_exponent_value(#[case] exponent: SkewExponent, #[case] expected: u32) {
342 assert_eq!(exponent.value(), expected);
343 }
344
345 #[rstest]
346 #[case(16_000, true, 0)]
347 #[case(16_000, false, -1)]
348 #[case(32_000, true, 0)]
349 #[case(32_000, false, 0)]
350 #[case(48_000, true, 1)]
351 #[case(48_000, false, 0)]
352 #[case(15_999, true, -1)]
353 #[case(15_999, false, -1)]
354 #[case(48_001, true, 1)]
355 #[case(48_001, false, 1)]
356 #[case(15_500, true, -1)]
357 #[case(15_500, false, -2)]
358 #[case(15_499, true, -2)]
359 #[case(15_499, false, -2)]
360 #[case(48_500, true, 2)]
361 #[case(48_500, false, 1)]
362 #[case(48_501, true, 2)]
363 #[case(48_501, false, 2)]
364 #[case(8_000, true, -16)]
365 #[case(8_000, false, -17)]
366 #[case(56_000, true, 17)]
367 #[case(56_000, false, 16)]
368 #[case(0, true, -32)]
369 #[case(0, false, -32)]
370 #[case(64_000, true, 32)]
371 #[case(64_000, false, 32)]
372 fn test_band_index(#[case] balance: u64, #[case] increasing: bool, #[case] expected: i8) {
373 let vault = SkewVault {
374 inventory_limit_upper: 64_000,
375 inventory_limit_lower: 0,
376 positive_skew_per_m: 0,
377 negative_skew_per_m: 0,
378 lower_skew_offset: 16_000,
379 upper_skew_offset: 48_000,
380 skew_exponent: SkewExponent::Linear,
381 };
382 assert_eq!(band_index(balance, increasing, &vault).unwrap(), expected);
383 }
384
385 #[rstest]
386 #[case(1_001, true, 1)]
387 #[case(1_000, true, 1)]
388 #[case(1_000, false, -1)]
389 #[case(999, false, -1)]
390 #[case(2_000, true, 32)]
391 #[case(0, false, -32)]
392 #[case(1_031, true, 2)]
393 #[case(1_031, false, 1)]
394 #[case(1_032, true, 2)]
395 #[case(969, true, -1)]
396 #[case(969, false, -2)]
397 #[case(968, false, -2)]
398 #[case(1_968, true, 32)]
399 #[case(1_969, true, 32)]
400 fn test_band_index_rounding(
401 #[case] balance: u64,
402 #[case] increasing: bool,
403 #[case] expected: i8,
404 ) {
405 let vault = SkewVault {
406 inventory_limit_upper: 2_000,
407 inventory_limit_lower: 0,
408 positive_skew_per_m: 0,
409 negative_skew_per_m: 0,
410 lower_skew_offset: 1_000,
411 upper_skew_offset: 1_000,
412 skew_exponent: SkewExponent::Linear,
413 };
414 assert_eq!(band_index(balance, increasing, &vault).unwrap(), expected);
415 }
416
417 #[rstest]
418 #[case(0, 32_000, SkewExponent::Linear, true, 0)]
419 #[case(0, 32_000, SkewExponent::Quadratic, true, 0)]
420 #[case(1, 32_000, SkewExponent::Linear, true, 1_000)]
421 #[case(-1, 32_000, SkewExponent::Linear, true, -1_000)]
422 #[case(-1, 32_000, SkewExponent::Linear, false, 1_000)]
423 #[case(1, 32_000, SkewExponent::Linear, false, -1_000)]
424 #[case(16, 32_000, SkewExponent::Linear, true, 16_000)]
425 #[case(-16, 32_000, SkewExponent::Linear, true, -16_000)]
426 #[case(-16, 32_000, SkewExponent::Linear, false, 16_000)]
427 #[case(32, 32_000, SkewExponent::Linear, true, 32_000)]
428 #[case(-32, 32_000, SkewExponent::Linear, true, -32_000)]
429 #[case(16, 32_000, SkewExponent::Quadratic, true, 8_000)]
430 #[case(32, 32_000, SkewExponent::Quadratic, true, 32_000)]
431 #[case(32, 32_000, SkewExponent::Cubic, true, 32_000)]
432 #[case(32, 100_000, SkewExponent::Linear, true, 100_000)]
433 #[case(1, 10_000, SkewExponent::Linear, true, 313)]
434 #[case(-1, 10_000, SkewExponent::Linear, true, -313)]
435 #[case(1, 1, SkewExponent::Linear, true, 1)]
436 #[case(1, 10_000, SkewExponent::Quadratic, true, 10)]
437 #[case(1, 32_000, SkewExponent::Cubic, true, 1)]
438 #[case(8, 32_000, SkewExponent::Quadratic, true, 2_000)]
439 fn test_band_skew(
440 #[case] index: i8,
441 #[case] intensity: u32,
442 #[case] exponent: SkewExponent,
443 #[case] increasing: bool,
444 #[case] expected: i32,
445 ) {
446 assert_eq!(
447 band_skew(index, intensity, exponent, increasing).unwrap(),
448 expected
449 );
450 }
451
452 #[rstest]
453 #[case(0, 16_000, 48_000)]
454 #[case(1, 48_000, 48_500)]
455 #[case(-1, 15_500, 16_000)]
456 #[case(16, 55_500, 56_000)]
457 #[case(-16, 8_000, 8_500)]
458 #[case(32, 63_500, u64::MAX)]
459 #[case(-32, 0, 500)]
460 fn test_band_limits(
461 #[case] index: i8,
462 #[case] expected_lower: u64,
463 #[case] expected_upper: u64,
464 ) {
465 let vault = SkewVault {
466 inventory_limit_upper: 64_000,
467 inventory_limit_lower: 0,
468 positive_skew_per_m: 0,
469 negative_skew_per_m: 0,
470 lower_skew_offset: 16_000,
471 upper_skew_offset: 48_000,
472 skew_exponent: SkewExponent::Linear,
473 };
474 assert_eq!(
475 band_limits(index, &vault).unwrap(),
476 (expected_lower, expected_upper)
477 );
478 }
479
480 #[rstest]
481 #[case(1, 1_000, 1_031)]
482 #[case(-1, 969, 1_000)]
483 #[case(32, 1_968, u64::MAX)]
484 #[case(-32, 0, 32)]
485 fn test_band_limits_rounding(
486 #[case] index: i8,
487 #[case] expected_lower: u64,
488 #[case] expected_upper: u64,
489 ) {
490 let vault = SkewVault {
491 inventory_limit_upper: 2_000,
492 inventory_limit_lower: 0,
493 positive_skew_per_m: 0,
494 negative_skew_per_m: 0,
495 lower_skew_offset: 1_000,
496 upper_skew_offset: 1_000,
497 skew_exponent: SkewExponent::Linear,
498 };
499 assert_eq!(
500 band_limits(index, &vault).unwrap(),
501 (expected_lower, expected_upper)
502 );
503 }
504
505 #[rstest]
506 #[case(1)]
507 #[case(-1)]
508 #[case(2)]
509 #[case(-2)]
510 #[case(16)]
511 #[case(-16)]
512 #[case(32)]
513 #[case(-32)]
514 fn test_band_limits_matches_index(#[case] index: i8) {
515 let vault = SkewVault {
516 inventory_limit_upper: 64_000,
517 inventory_limit_lower: 0,
518 positive_skew_per_m: 0,
519 negative_skew_per_m: 0,
520 lower_skew_offset: 16_000,
521 upper_skew_offset: 48_000,
522 skew_exponent: SkewExponent::Linear,
523 };
524 assert_band_limits_match_index(index, &vault);
525 }
526
527 #[rstest]
528 #[case(1)]
529 #[case(-1)]
530 #[case(32)]
531 #[case(-32)]
532 fn test_band_limits_matches_index_rounding(#[case] index: i8) {
533 let vault = SkewVault {
534 inventory_limit_upper: 2_000,
535 inventory_limit_lower: 0,
536 positive_skew_per_m: 0,
537 negative_skew_per_m: 0,
538 lower_skew_offset: 1_000,
539 upper_skew_offset: 1_000,
540 skew_exponent: SkewExponent::Linear,
541 };
542 assert_band_limits_match_index(index, &vault);
543 }
544
545 fn assert_band_limits_match_index(index: i8, vault: &SkewVault) {
546 let (lower, upper) = band_limits(index, vault).unwrap();
547 if index > 0 {
551 let sample_upper = if upper == u64::MAX {
552 vault.inventory_limit_upper
553 } else {
554 upper
555 };
556 assert_eq!(band_index(sample_upper, false, vault).unwrap(), index);
557 if lower < sample_upper {
558 assert_eq!(band_index(lower, true, vault).unwrap(), index);
559 assert_eq!(band_index(lower + 1, true, vault).unwrap(), index);
560 assert_eq!(band_index(lower + 1, false, vault).unwrap(), index);
561 }
562 if sample_upper - lower >= 2 {
563 let mid = lower + (sample_upper - lower) / 2;
564 assert_eq!(band_index(mid, true, vault).unwrap(), index);
565 assert_eq!(band_index(mid, false, vault).unwrap(), index);
566 }
567 } else {
568 assert_eq!(band_index(lower, true, vault).unwrap(), index);
569 if lower < upper {
570 assert_eq!(band_index(upper - 1, true, vault).unwrap(), index);
571 assert_eq!(band_index(upper - 1, false, vault).unwrap(), index);
572 }
573 if upper - lower >= 2 {
574 let mid = lower + (upper - lower) / 2;
575 assert_eq!(band_index(mid, true, vault).unwrap(), index);
576 assert_eq!(band_index(mid, false, vault).unwrap(), index);
577 }
578 }
579 }
580
581 #[rstest]
582 #[case(0, Some((1, 48_000, 48_500, 1_000)))]
583 #[case(-2, Some((-1, 15_500, 16_000, -500)))]
584 #[case(31, Some((32, 63_500, u64::MAX, 32_000)))]
585 #[case(32, None)]
586 fn test_next_band(#[case] current_index: i8, #[case] expected: Option<(i8, u64, u64, i32)>) {
587 let vault = SkewVault {
588 inventory_limit_upper: 64_000,
589 inventory_limit_lower: 0,
590 positive_skew_per_m: 32_000,
591 negative_skew_per_m: 16_000,
592 lower_skew_offset: 16_000,
593 upper_skew_offset: 48_000,
594 skew_exponent: SkewExponent::Linear,
595 };
596 let current = SkewBand {
597 index: current_index,
598 lower_limit: 0,
599 upper_limit: 0,
600 skew_per_m: 0,
601 };
602 let result = next_band(¤t, &vault).unwrap();
603 assert_eq!(
604 result.map(|b| (b.index, b.lower_limit, b.upper_limit, b.skew_per_m)),
605 expected
606 );
607 }
608
609 #[test]
610 fn test_next_band_rounding() {
611 let vault = SkewVault {
612 inventory_limit_upper: 2_000,
613 inventory_limit_lower: 0,
614 positive_skew_per_m: 10_000,
615 negative_skew_per_m: 5_000,
616 lower_skew_offset: 1_000,
617 upper_skew_offset: 1_000,
618 skew_exponent: SkewExponent::Linear,
619 };
620 let current = SkewBand {
621 index: 0,
622 lower_limit: 0,
623 upper_limit: 0,
624 skew_per_m: 0,
625 };
626 let band = next_band(¤t, &vault).unwrap().unwrap();
627 assert_eq!(
628 (
629 band.index,
630 band.lower_limit,
631 band.upper_limit,
632 band.skew_per_m
633 ),
634 (1, 1_000, 1_031, 313)
635 );
636 }
637
638 #[rstest]
639 #[case(0, Some((-1, 15_500, 16_000, 1_000)))]
640 #[case(-2, Some((-3, 14_500, 15_000, 3_000)))]
641 #[case(-31, Some((-32, 0, 500, 32_000)))]
642 #[case(-32, None)]
643 fn test_prev_band(#[case] current_index: i8, #[case] expected: Option<(i8, u64, u64, i32)>) {
644 let vault = SkewVault {
645 inventory_limit_upper: 64_000,
646 inventory_limit_lower: 0,
647 positive_skew_per_m: 32_000,
648 negative_skew_per_m: 16_000,
649 lower_skew_offset: 16_000,
650 upper_skew_offset: 48_000,
651 skew_exponent: SkewExponent::Linear,
652 };
653 let current = SkewBand {
654 index: current_index,
655 lower_limit: 0,
656 upper_limit: 0,
657 skew_per_m: 0,
658 };
659 let result = prev_band(¤t, &vault).unwrap();
660 assert_eq!(
661 result.map(|b| (b.index, b.lower_limit, b.upper_limit, b.skew_per_m)),
662 expected
663 );
664 }
665
666 #[test]
667 fn test_prev_band_rounding() {
668 let vault = SkewVault {
669 inventory_limit_upper: 2_000,
670 inventory_limit_lower: 0,
671 positive_skew_per_m: 10_000,
672 negative_skew_per_m: 5_000,
673 lower_skew_offset: 1_000,
674 upper_skew_offset: 1_000,
675 skew_exponent: SkewExponent::Linear,
676 };
677 let current = SkewBand {
678 index: 0,
679 lower_limit: 0,
680 upper_limit: 0,
681 skew_per_m: 0,
682 };
683 let band = prev_band(¤t, &vault).unwrap().unwrap();
684 assert_eq!(
685 (
686 band.index,
687 band.lower_limit,
688 band.upper_limit,
689 band.skew_per_m
690 ),
691 (-1, 969, 1_000, 313)
692 );
693 }
694
695 #[rstest]
696 #[case(32_000, true, SkewExponent::Linear, 0, 16_000, 48_000, 0)]
697 #[case(32_000, false, SkewExponent::Linear, 0, 16_000, 48_000, 0)]
698 #[case(48_001, true, SkewExponent::Linear, 1, 48_000, 48_500, 1_000)]
699 #[case(48_001, false, SkewExponent::Linear, 1, 48_000, 48_500, -500)]
700 #[case(15_999, true, SkewExponent::Linear, -1, 15_500, 16_000, -500)]
701 #[case(15_999, false, SkewExponent::Linear, -1, 15_500, 16_000, 1_000)]
702 #[case(55_999, true, SkewExponent::Quadratic, 16, 55_500, 56_000, 8_000)]
703 #[case(48_000, true, SkewExponent::Linear, 1, 48_000, 48_500, 1_000)]
705 #[case(48_000, false, SkewExponent::Linear, 0, 16_000, 48_000, 0)]
706 #[case(16_000, false, SkewExponent::Linear, -1, 15_500, 16_000, 1_000)]
707 #[case(16_000, true, SkewExponent::Linear, 0, 16_000, 48_000, 0)]
708 #[case(48_500, true, SkewExponent::Linear, 2, 48_500, 49_000, 2_000)]
709 #[case(15_500, false, SkewExponent::Linear, -2, 15_000, 15_500, 2_000)]
710 fn test_skew_band(
711 #[case] balance: u64,
712 #[case] increasing: bool,
713 #[case] skew_exponent: SkewExponent,
714 #[case] expected_index: i8,
715 #[case] expected_lower: u64,
716 #[case] expected_upper: u64,
717 #[case] expected_skew_per_m: i32,
718 ) {
719 let vault = SkewVault {
720 inventory_limit_upper: 64_000,
721 inventory_limit_lower: 0,
722 positive_skew_per_m: 32_000,
723 negative_skew_per_m: 16_000,
724 lower_skew_offset: 16_000,
725 upper_skew_offset: 48_000,
726 skew_exponent,
727 };
728 let band = skew_band(balance, increasing, vault).unwrap();
729 assert_eq!(band.index, expected_index);
730 assert_eq!(band.lower_limit, expected_lower);
731 assert_eq!(band.upper_limit, expected_upper);
732 assert_eq!(band.skew_per_m, expected_skew_per_m);
733 }
734
735 #[test]
736 fn test_skew_band_rounding() {
737 let vault = SkewVault {
738 inventory_limit_upper: 2_000,
739 inventory_limit_lower: 0,
740 positive_skew_per_m: 10_000,
741 negative_skew_per_m: 5_000,
742 lower_skew_offset: 1_000,
743 upper_skew_offset: 1_000,
744 skew_exponent: SkewExponent::Linear,
745 };
746 let band = skew_band(1_001, true, vault).unwrap();
747 assert_eq!(band.index, 1);
748 assert_eq!(band.lower_limit, 1_000);
749 assert_eq!(band.upper_limit, 1_031);
750 assert_eq!(band.skew_per_m, 313);
751 let edge = skew_band(1_000, true, vault).unwrap();
752 assert_eq!(edge.index, 1);
753 let down = skew_band(1_000, false, vault).unwrap();
754 assert_eq!(down.index, -1);
755 }
756
757 #[rstest]
758 #[case(64_000, 32_000, 16_000, 16_000, 48_000, SkewExponent::Quadratic)]
759 #[case(2_000, 10_000, 5_000, 1_000, 1_000, SkewExponent::Cubic)]
760 fn test_from_swap_vault(
761 #[case] inventory_limit_upper: u64,
762 #[case] positive_skew_per_m: u32,
763 #[case] negative_skew_per_m: u32,
764 #[case] lower_skew_offset: u64,
765 #[case] upper_skew_offset: u64,
766 #[case] skew_exponent: SkewExponent,
767 ) {
768 let swap_vault = SwapVault {
769 swap_fee_per_m: 123,
770 inventory_limit_upper,
771 inventory_limit_lower: 0,
772 max_swap_amount: 999,
773 positive_skew_per_m,
774 negative_skew_per_m,
775 lower_skew_offset,
776 upper_skew_offset,
777 skew_exponent,
778 };
779 assert_eq!(
780 SkewVault::from(swap_vault),
781 SkewVault {
782 inventory_limit_upper,
783 inventory_limit_lower: 0,
784 positive_skew_per_m,
785 negative_skew_per_m,
786 lower_skew_offset,
787 upper_skew_offset,
788 skew_exponent,
789 }
790 );
791 }
792
793 #[rstest]
794 #[case(500, 1_000, 64_000, 1_000, 48_000)]
795 #[case(50_000, 0, 48_000, 16_000, 48_000)]
796 #[case(500, 2_000, 64_000, 1_000, 48_000)]
797 fn test_band_index_overflow(
798 #[case] balance: u64,
799 #[case] inventory_limit_lower: u64,
800 #[case] inventory_limit_upper: u64,
801 #[case] lower_skew_offset: u64,
802 #[case] upper_skew_offset: u64,
803 ) {
804 let vault = SkewVault {
805 inventory_limit_upper,
806 inventory_limit_lower,
807 positive_skew_per_m: 0,
808 negative_skew_per_m: 0,
809 lower_skew_offset,
810 upper_skew_offset,
811 skew_exponent: SkewExponent::Linear,
812 };
813 assert_eq!(band_index(balance, true, &vault), Err(ARITHMETIC_OVERFLOW));
814 assert_eq!(band_index(balance, false, &vault), Err(ARITHMETIC_OVERFLOW));
815 }
816
817 #[test]
818 fn test_band_index_clamps_beyond_limit() {
819 let vault = SkewVault {
821 inventory_limit_upper: 32,
822 inventory_limit_lower: 0,
823 positive_skew_per_m: 0,
824 negative_skew_per_m: 0,
825 lower_skew_offset: 0,
826 upper_skew_offset: 0,
827 skew_exponent: SkewExponent::Linear,
828 };
829 assert_eq!(band_index(1_000, true, &vault).unwrap(), BAND_COUNT);
830 assert_eq!(band_index(1_000, false, &vault).unwrap(), BAND_COUNT);
831 }
832}