#[cfg(feature = "wasm")]
use equanetwork_macros::wasm_expose;
use super::PER_M_DENOMINATOR;
use super::{SwapVault, AMOUNT_EXCEEDS_MAX_I32, AMOUNT_EXCEEDS_MAX_U64};
use super::error::{CoreError, ARITHMETIC_OVERFLOW};
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "wasm", wasm_expose)]
pub enum SkewExponent {
#[default]
Linear,
Quadratic,
Cubic,
}
impl SkewExponent {
pub fn value(&self) -> u32 {
match self {
Self::Linear => 1,
Self::Quadratic => 2,
Self::Cubic => 3,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "wasm", wasm_expose)]
pub struct SkewVault {
pub inventory_limit_upper: u64,
pub inventory_limit_lower: u64,
pub positive_skew_per_m: u32,
pub negative_skew_per_m: u32,
pub lower_skew_offset: u64,
pub upper_skew_offset: u64,
pub skew_exponent: SkewExponent,
}
impl From<SwapVault> for SkewVault {
fn from(swap_vault: SwapVault) -> Self {
Self {
inventory_limit_upper: swap_vault.inventory_limit_upper,
inventory_limit_lower: swap_vault.inventory_limit_lower,
positive_skew_per_m: swap_vault.positive_skew_per_m,
negative_skew_per_m: swap_vault.negative_skew_per_m,
lower_skew_offset: swap_vault.lower_skew_offset,
upper_skew_offset: swap_vault.upper_skew_offset,
skew_exponent: swap_vault.skew_exponent,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "wasm", wasm_expose)]
pub struct SkewBand {
pub index: i8,
pub lower_limit: u64,
pub upper_limit: u64,
pub skew_per_m: i32,
}
pub const BAND_COUNT: i8 = 32;
#[cfg_attr(feature = "wasm", wasm_expose)]
pub fn skew_band(balance: u64, increasing: bool, vault: SkewVault) -> Result<SkewBand, CoreError> {
let index = band_index(balance, increasing, &vault)?;
let intensity = select_intensity(index, increasing, &vault);
let (lower_limit, upper_limit) = band_limits(index, &vault)?;
let skew_per_m = band_skew(index, intensity, vault.skew_exponent, increasing)?;
Ok(SkewBand {
index,
lower_limit,
upper_limit,
skew_per_m,
})
}
pub(crate) fn prev_band(
current_band: &SkewBand,
vault: &SkewVault,
) -> Result<Option<SkewBand>, CoreError> {
let prev_index = current_band.index - 1;
if prev_index < -BAND_COUNT {
return Ok(None);
}
let intensity = select_intensity(prev_index, false, vault);
let (lower_limit, upper_limit) = band_limits(prev_index, vault)?;
let skew_per_m = band_skew(prev_index, intensity, vault.skew_exponent, false)?;
Ok(Some(SkewBand {
index: prev_index,
lower_limit,
upper_limit,
skew_per_m,
}))
}
pub(crate) fn next_band(
current_band: &SkewBand,
vault: &SkewVault,
) -> Result<Option<SkewBand>, CoreError> {
let next_index = current_band.index + 1;
if next_index > BAND_COUNT {
return Ok(None);
}
let intensity = select_intensity(next_index, true, vault);
let (lower_limit, upper_limit) = band_limits(next_index, vault)?;
let skew_per_m = band_skew(next_index, intensity, vault.skew_exponent, true)?;
Ok(Some(SkewBand {
index: next_index,
lower_limit,
upper_limit,
skew_per_m,
}))
}
fn band_index(balance: u64, increasing: bool, vault: &SkewVault) -> Result<i8, CoreError> {
let mut index = closed_band_index(balance, vault)?;
let (lower_limit, upper_limit) = band_limits(index, vault)?;
if increasing && balance == upper_limit && index < BAND_COUNT {
index += 1;
} else if !increasing && balance == lower_limit && index > -BAND_COUNT {
index -= 1;
}
Ok(index)
}
fn closed_band_index(balance: u64, vault: &SkewVault) -> Result<i8, CoreError> {
let (diff, pos, sign) = if balance < vault.lower_skew_offset {
let diff = vault
.lower_skew_offset
.checked_sub(vault.inventory_limit_lower)
.ok_or(ARITHMETIC_OVERFLOW)?;
let pos = vault
.lower_skew_offset
.checked_sub(balance)
.ok_or(ARITHMETIC_OVERFLOW)?;
(diff, pos, -1)
} else if balance > vault.upper_skew_offset {
let diff = vault
.inventory_limit_upper
.checked_sub(vault.upper_skew_offset)
.ok_or(ARITHMETIC_OVERFLOW)?;
let pos = balance
.checked_sub(vault.upper_skew_offset)
.ok_or(ARITHMETIC_OVERFLOW)?;
(diff, pos, 1)
} else {
return Ok(0);
};
let product = pos
.checked_mul(BAND_COUNT as u64)
.ok_or(ARITHMETIC_OVERFLOW)?;
let quotient = product
.checked_div(diff.into())
.ok_or(ARITHMETIC_OVERFLOW)?;
let remainder = product
.checked_rem(diff.into())
.ok_or(ARITHMETIC_OVERFLOW)?;
let raw_index = if remainder > 0 {
quotient + 1
} else {
quotient
};
let index = raw_index.min(BAND_COUNT as u64) as i8;
Ok(sign * index)
}
fn away_from_dead_zone(index: i8, increasing: bool) -> bool {
(index > 0 && increasing) || (index < 0 && !increasing)
}
fn skew_sign(index: i8, increasing: bool) -> i32 {
if index == 0 {
0
} else if away_from_dead_zone(index, increasing) {
1
} else {
-1
}
}
fn select_intensity(index: i8, increasing: bool, vault: &SkewVault) -> u32 {
match skew_sign(index, increasing) {
1 => vault.positive_skew_per_m,
-1 => vault.negative_skew_per_m,
_ => 0,
}
}
fn band_limits(index: i8, vault: &SkewVault) -> Result<(u64, u64), CoreError> {
let (start, diff) = if index < 0 {
let diff = vault
.lower_skew_offset
.checked_sub(vault.inventory_limit_lower)
.ok_or(ARITHMETIC_OVERFLOW)?;
(vault.lower_skew_offset, diff)
} else if index > 0 {
let diff = vault
.inventory_limit_upper
.checked_sub(vault.upper_skew_offset)
.ok_or(ARITHMETIC_OVERFLOW)?;
(vault.upper_skew_offset, diff)
} else {
return Ok((vault.lower_skew_offset, vault.upper_skew_offset));
};
let abs_index = index.unsigned_abs() as u128;
let range = u128::from(diff);
let band_count = BAND_COUNT as u128;
let inner_delta = range
.checked_mul(abs_index.saturating_sub(1))
.ok_or(ARITHMETIC_OVERFLOW)?
.checked_div(band_count)
.ok_or(ARITHMETIC_OVERFLOW)?;
let outer_delta = range
.checked_mul(abs_index)
.ok_or(ARITHMETIC_OVERFLOW)?
.checked_div(band_count)
.ok_or(ARITHMETIC_OVERFLOW)?;
let (lower_limit, upper_limit) = if index > 0 {
let inner_limit: u64 = u128::from(start)
.checked_add(inner_delta)
.ok_or(ARITHMETIC_OVERFLOW)?
.try_into()
.map_err(|_| AMOUNT_EXCEEDS_MAX_U64)?;
if index == BAND_COUNT {
(inner_limit, u64::MAX)
} else {
let outer_limit: u64 = u128::from(start)
.checked_add(outer_delta)
.ok_or(ARITHMETIC_OVERFLOW)?
.try_into()
.map_err(|_| AMOUNT_EXCEEDS_MAX_U64)?;
(inner_limit, outer_limit)
}
} else {
let inner_limit: u64 = u128::from(start)
.checked_sub(inner_delta)
.ok_or(ARITHMETIC_OVERFLOW)?
.try_into()
.map_err(|_| AMOUNT_EXCEEDS_MAX_U64)?;
if index == -BAND_COUNT {
(0, inner_limit)
} else {
let outer_limit: u64 = u128::from(start)
.checked_sub(outer_delta)
.ok_or(ARITHMETIC_OVERFLOW)?
.try_into()
.map_err(|_| AMOUNT_EXCEEDS_MAX_U64)?;
(outer_limit, inner_limit)
}
};
Ok((lower_limit, upper_limit))
}
fn band_skew(
index: i8,
intensity: u32,
exponent: SkewExponent,
increasing: bool,
) -> Result<i32, CoreError> {
let sign = skew_sign(index, increasing);
let numerator = u128::from(index.unsigned_abs())
.checked_pow(exponent.value())
.ok_or(ARITHMETIC_OVERFLOW)?
.checked_mul(intensity as u128)
.ok_or(ARITHMETIC_OVERFLOW)?;
let denominator = (BAND_COUNT as u128)
.checked_pow(exponent.value())
.ok_or(ARITHMETIC_OVERFLOW)?;
let quotient = numerator
.checked_div(denominator)
.ok_or(ARITHMETIC_OVERFLOW)?;
let remainder = numerator
.checked_rem(denominator)
.ok_or(ARITHMETIC_OVERFLOW)?;
let abs_result = if remainder > 0 {
quotient + 1
} else {
quotient
};
let result: i32 = abs_result
.min(PER_M_DENOMINATOR as u128)
.try_into()
.map_err(|_| AMOUNT_EXCEEDS_MAX_I32)?;
result.checked_mul(sign).ok_or(ARITHMETIC_OVERFLOW)
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
#[case(0, true, 0)]
#[case(0, false, 0)]
#[case(-1, true, 3_000)]
#[case(-1, false, 7_000)]
#[case(1, true, 7_000)]
#[case(1, false, 3_000)]
fn test_select_intensity(#[case] index: i8, #[case] increasing: bool, #[case] expected: u32) {
let vault = SkewVault {
inventory_limit_upper: 64_000,
inventory_limit_lower: 0,
positive_skew_per_m: 7_000,
negative_skew_per_m: 3_000,
lower_skew_offset: 16_000,
upper_skew_offset: 48_000,
skew_exponent: SkewExponent::Linear,
};
assert_eq!(select_intensity(index, increasing, &vault), expected);
}
#[rstest]
#[case(SkewExponent::Linear, 1)]
#[case(SkewExponent::Quadratic, 2)]
#[case(SkewExponent::Cubic, 3)]
fn test_skew_exponent_value(#[case] exponent: SkewExponent, #[case] expected: u32) {
assert_eq!(exponent.value(), expected);
}
#[rstest]
#[case(16_000, true, 0)]
#[case(16_000, false, -1)]
#[case(32_000, true, 0)]
#[case(32_000, false, 0)]
#[case(48_000, true, 1)]
#[case(48_000, false, 0)]
#[case(15_999, true, -1)]
#[case(15_999, false, -1)]
#[case(48_001, true, 1)]
#[case(48_001, false, 1)]
#[case(15_500, true, -1)]
#[case(15_500, false, -2)]
#[case(15_499, true, -2)]
#[case(15_499, false, -2)]
#[case(48_500, true, 2)]
#[case(48_500, false, 1)]
#[case(48_501, true, 2)]
#[case(48_501, false, 2)]
#[case(8_000, true, -16)]
#[case(8_000, false, -17)]
#[case(56_000, true, 17)]
#[case(56_000, false, 16)]
#[case(0, true, -32)]
#[case(0, false, -32)]
#[case(64_000, true, 32)]
#[case(64_000, false, 32)]
fn test_band_index(#[case] balance: u64, #[case] increasing: bool, #[case] expected: i8) {
let vault = SkewVault {
inventory_limit_upper: 64_000,
inventory_limit_lower: 0,
positive_skew_per_m: 0,
negative_skew_per_m: 0,
lower_skew_offset: 16_000,
upper_skew_offset: 48_000,
skew_exponent: SkewExponent::Linear,
};
assert_eq!(band_index(balance, increasing, &vault).unwrap(), expected);
}
#[rstest]
#[case(1_001, true, 1)]
#[case(1_000, true, 1)]
#[case(1_000, false, -1)]
#[case(999, false, -1)]
#[case(2_000, true, 32)]
#[case(0, false, -32)]
#[case(1_031, true, 2)]
#[case(1_031, false, 1)]
#[case(1_032, true, 2)]
#[case(969, true, -1)]
#[case(969, false, -2)]
#[case(968, false, -2)]
#[case(1_968, true, 32)]
#[case(1_969, true, 32)]
fn test_band_index_rounding(
#[case] balance: u64,
#[case] increasing: bool,
#[case] expected: i8,
) {
let vault = SkewVault {
inventory_limit_upper: 2_000,
inventory_limit_lower: 0,
positive_skew_per_m: 0,
negative_skew_per_m: 0,
lower_skew_offset: 1_000,
upper_skew_offset: 1_000,
skew_exponent: SkewExponent::Linear,
};
assert_eq!(band_index(balance, increasing, &vault).unwrap(), expected);
}
#[rstest]
#[case(0, 32_000, SkewExponent::Linear, true, 0)]
#[case(0, 32_000, SkewExponent::Quadratic, true, 0)]
#[case(1, 32_000, SkewExponent::Linear, true, 1_000)]
#[case(-1, 32_000, SkewExponent::Linear, true, -1_000)]
#[case(-1, 32_000, SkewExponent::Linear, false, 1_000)]
#[case(1, 32_000, SkewExponent::Linear, false, -1_000)]
#[case(16, 32_000, SkewExponent::Linear, true, 16_000)]
#[case(-16, 32_000, SkewExponent::Linear, true, -16_000)]
#[case(-16, 32_000, SkewExponent::Linear, false, 16_000)]
#[case(32, 32_000, SkewExponent::Linear, true, 32_000)]
#[case(-32, 32_000, SkewExponent::Linear, true, -32_000)]
#[case(16, 32_000, SkewExponent::Quadratic, true, 8_000)]
#[case(32, 32_000, SkewExponent::Quadratic, true, 32_000)]
#[case(32, 32_000, SkewExponent::Cubic, true, 32_000)]
#[case(32, 100_000, SkewExponent::Linear, true, 100_000)]
#[case(1, 10_000, SkewExponent::Linear, true, 313)]
#[case(-1, 10_000, SkewExponent::Linear, true, -313)]
#[case(1, 1, SkewExponent::Linear, true, 1)]
#[case(1, 10_000, SkewExponent::Quadratic, true, 10)]
#[case(1, 32_000, SkewExponent::Cubic, true, 1)]
#[case(8, 32_000, SkewExponent::Quadratic, true, 2_000)]
fn test_band_skew(
#[case] index: i8,
#[case] intensity: u32,
#[case] exponent: SkewExponent,
#[case] increasing: bool,
#[case] expected: i32,
) {
assert_eq!(
band_skew(index, intensity, exponent, increasing).unwrap(),
expected
);
}
#[rstest]
#[case(0, 16_000, 48_000)]
#[case(1, 48_000, 48_500)]
#[case(-1, 15_500, 16_000)]
#[case(16, 55_500, 56_000)]
#[case(-16, 8_000, 8_500)]
#[case(32, 63_500, u64::MAX)]
#[case(-32, 0, 500)]
fn test_band_limits(
#[case] index: i8,
#[case] expected_lower: u64,
#[case] expected_upper: u64,
) {
let vault = SkewVault {
inventory_limit_upper: 64_000,
inventory_limit_lower: 0,
positive_skew_per_m: 0,
negative_skew_per_m: 0,
lower_skew_offset: 16_000,
upper_skew_offset: 48_000,
skew_exponent: SkewExponent::Linear,
};
assert_eq!(
band_limits(index, &vault).unwrap(),
(expected_lower, expected_upper)
);
}
#[rstest]
#[case(1, 1_000, 1_031)]
#[case(-1, 969, 1_000)]
#[case(32, 1_968, u64::MAX)]
#[case(-32, 0, 32)]
fn test_band_limits_rounding(
#[case] index: i8,
#[case] expected_lower: u64,
#[case] expected_upper: u64,
) {
let vault = SkewVault {
inventory_limit_upper: 2_000,
inventory_limit_lower: 0,
positive_skew_per_m: 0,
negative_skew_per_m: 0,
lower_skew_offset: 1_000,
upper_skew_offset: 1_000,
skew_exponent: SkewExponent::Linear,
};
assert_eq!(
band_limits(index, &vault).unwrap(),
(expected_lower, expected_upper)
);
}
#[rstest]
#[case(1)]
#[case(-1)]
#[case(2)]
#[case(-2)]
#[case(16)]
#[case(-16)]
#[case(32)]
#[case(-32)]
fn test_band_limits_matches_index(#[case] index: i8) {
let vault = SkewVault {
inventory_limit_upper: 64_000,
inventory_limit_lower: 0,
positive_skew_per_m: 0,
negative_skew_per_m: 0,
lower_skew_offset: 16_000,
upper_skew_offset: 48_000,
skew_exponent: SkewExponent::Linear,
};
assert_band_limits_match_index(index, &vault);
}
#[rstest]
#[case(1)]
#[case(-1)]
#[case(32)]
#[case(-32)]
fn test_band_limits_matches_index_rounding(#[case] index: i8) {
let vault = SkewVault {
inventory_limit_upper: 2_000,
inventory_limit_lower: 0,
positive_skew_per_m: 0,
negative_skew_per_m: 0,
lower_skew_offset: 1_000,
upper_skew_offset: 1_000,
skew_exponent: SkewExponent::Linear,
};
assert_band_limits_match_index(index, &vault);
}
fn assert_band_limits_match_index(index: i8, vault: &SkewVault) {
let (lower, upper) = band_limits(index, vault).unwrap();
if index > 0 {
let sample_upper = if upper == u64::MAX {
vault.inventory_limit_upper
} else {
upper
};
assert_eq!(band_index(sample_upper, false, vault).unwrap(), index);
if lower < sample_upper {
assert_eq!(band_index(lower, true, vault).unwrap(), index);
assert_eq!(band_index(lower + 1, true, vault).unwrap(), index);
assert_eq!(band_index(lower + 1, false, vault).unwrap(), index);
}
if sample_upper - lower >= 2 {
let mid = lower + (sample_upper - lower) / 2;
assert_eq!(band_index(mid, true, vault).unwrap(), index);
assert_eq!(band_index(mid, false, vault).unwrap(), index);
}
} else {
assert_eq!(band_index(lower, true, vault).unwrap(), index);
if lower < upper {
assert_eq!(band_index(upper - 1, true, vault).unwrap(), index);
assert_eq!(band_index(upper - 1, false, vault).unwrap(), index);
}
if upper - lower >= 2 {
let mid = lower + (upper - lower) / 2;
assert_eq!(band_index(mid, true, vault).unwrap(), index);
assert_eq!(band_index(mid, false, vault).unwrap(), index);
}
}
}
#[rstest]
#[case(0, Some((1, 48_000, 48_500, 1_000)))]
#[case(-2, Some((-1, 15_500, 16_000, -500)))]
#[case(31, Some((32, 63_500, u64::MAX, 32_000)))]
#[case(32, None)]
fn test_next_band(#[case] current_index: i8, #[case] expected: Option<(i8, u64, u64, i32)>) {
let vault = SkewVault {
inventory_limit_upper: 64_000,
inventory_limit_lower: 0,
positive_skew_per_m: 32_000,
negative_skew_per_m: 16_000,
lower_skew_offset: 16_000,
upper_skew_offset: 48_000,
skew_exponent: SkewExponent::Linear,
};
let current = SkewBand {
index: current_index,
lower_limit: 0,
upper_limit: 0,
skew_per_m: 0,
};
let result = next_band(¤t, &vault).unwrap();
assert_eq!(
result.map(|b| (b.index, b.lower_limit, b.upper_limit, b.skew_per_m)),
expected
);
}
#[test]
fn test_next_band_rounding() {
let vault = SkewVault {
inventory_limit_upper: 2_000,
inventory_limit_lower: 0,
positive_skew_per_m: 10_000,
negative_skew_per_m: 5_000,
lower_skew_offset: 1_000,
upper_skew_offset: 1_000,
skew_exponent: SkewExponent::Linear,
};
let current = SkewBand {
index: 0,
lower_limit: 0,
upper_limit: 0,
skew_per_m: 0,
};
let band = next_band(¤t, &vault).unwrap().unwrap();
assert_eq!(
(
band.index,
band.lower_limit,
band.upper_limit,
band.skew_per_m
),
(1, 1_000, 1_031, 313)
);
}
#[rstest]
#[case(0, Some((-1, 15_500, 16_000, 1_000)))]
#[case(-2, Some((-3, 14_500, 15_000, 3_000)))]
#[case(-31, Some((-32, 0, 500, 32_000)))]
#[case(-32, None)]
fn test_prev_band(#[case] current_index: i8, #[case] expected: Option<(i8, u64, u64, i32)>) {
let vault = SkewVault {
inventory_limit_upper: 64_000,
inventory_limit_lower: 0,
positive_skew_per_m: 32_000,
negative_skew_per_m: 16_000,
lower_skew_offset: 16_000,
upper_skew_offset: 48_000,
skew_exponent: SkewExponent::Linear,
};
let current = SkewBand {
index: current_index,
lower_limit: 0,
upper_limit: 0,
skew_per_m: 0,
};
let result = prev_band(¤t, &vault).unwrap();
assert_eq!(
result.map(|b| (b.index, b.lower_limit, b.upper_limit, b.skew_per_m)),
expected
);
}
#[test]
fn test_prev_band_rounding() {
let vault = SkewVault {
inventory_limit_upper: 2_000,
inventory_limit_lower: 0,
positive_skew_per_m: 10_000,
negative_skew_per_m: 5_000,
lower_skew_offset: 1_000,
upper_skew_offset: 1_000,
skew_exponent: SkewExponent::Linear,
};
let current = SkewBand {
index: 0,
lower_limit: 0,
upper_limit: 0,
skew_per_m: 0,
};
let band = prev_band(¤t, &vault).unwrap().unwrap();
assert_eq!(
(
band.index,
band.lower_limit,
band.upper_limit,
band.skew_per_m
),
(-1, 969, 1_000, 313)
);
}
#[rstest]
#[case(32_000, true, SkewExponent::Linear, 0, 16_000, 48_000, 0)]
#[case(32_000, false, SkewExponent::Linear, 0, 16_000, 48_000, 0)]
#[case(48_001, true, SkewExponent::Linear, 1, 48_000, 48_500, 1_000)]
#[case(48_001, false, SkewExponent::Linear, 1, 48_000, 48_500, -500)]
#[case(15_999, true, SkewExponent::Linear, -1, 15_500, 16_000, -500)]
#[case(15_999, false, SkewExponent::Linear, -1, 15_500, 16_000, 1_000)]
#[case(55_999, true, SkewExponent::Quadratic, 16, 55_500, 56_000, 8_000)]
#[case(48_000, true, SkewExponent::Linear, 1, 48_000, 48_500, 1_000)]
#[case(48_000, false, SkewExponent::Linear, 0, 16_000, 48_000, 0)]
#[case(16_000, false, SkewExponent::Linear, -1, 15_500, 16_000, 1_000)]
#[case(16_000, true, SkewExponent::Linear, 0, 16_000, 48_000, 0)]
#[case(48_500, true, SkewExponent::Linear, 2, 48_500, 49_000, 2_000)]
#[case(15_500, false, SkewExponent::Linear, -2, 15_000, 15_500, 2_000)]
fn test_skew_band(
#[case] balance: u64,
#[case] increasing: bool,
#[case] skew_exponent: SkewExponent,
#[case] expected_index: i8,
#[case] expected_lower: u64,
#[case] expected_upper: u64,
#[case] expected_skew_per_m: i32,
) {
let vault = SkewVault {
inventory_limit_upper: 64_000,
inventory_limit_lower: 0,
positive_skew_per_m: 32_000,
negative_skew_per_m: 16_000,
lower_skew_offset: 16_000,
upper_skew_offset: 48_000,
skew_exponent,
};
let band = skew_band(balance, increasing, vault).unwrap();
assert_eq!(band.index, expected_index);
assert_eq!(band.lower_limit, expected_lower);
assert_eq!(band.upper_limit, expected_upper);
assert_eq!(band.skew_per_m, expected_skew_per_m);
}
#[test]
fn test_skew_band_rounding() {
let vault = SkewVault {
inventory_limit_upper: 2_000,
inventory_limit_lower: 0,
positive_skew_per_m: 10_000,
negative_skew_per_m: 5_000,
lower_skew_offset: 1_000,
upper_skew_offset: 1_000,
skew_exponent: SkewExponent::Linear,
};
let band = skew_band(1_001, true, vault).unwrap();
assert_eq!(band.index, 1);
assert_eq!(band.lower_limit, 1_000);
assert_eq!(band.upper_limit, 1_031);
assert_eq!(band.skew_per_m, 313);
let edge = skew_band(1_000, true, vault).unwrap();
assert_eq!(edge.index, 1);
let down = skew_band(1_000, false, vault).unwrap();
assert_eq!(down.index, -1);
}
#[rstest]
#[case(64_000, 32_000, 16_000, 16_000, 48_000, SkewExponent::Quadratic)]
#[case(2_000, 10_000, 5_000, 1_000, 1_000, SkewExponent::Cubic)]
fn test_from_swap_vault(
#[case] inventory_limit_upper: u64,
#[case] positive_skew_per_m: u32,
#[case] negative_skew_per_m: u32,
#[case] lower_skew_offset: u64,
#[case] upper_skew_offset: u64,
#[case] skew_exponent: SkewExponent,
) {
let swap_vault = SwapVault {
swap_fee_per_m: 123,
inventory_limit_upper,
inventory_limit_lower: 0,
max_swap_amount: 999,
positive_skew_per_m,
negative_skew_per_m,
lower_skew_offset,
upper_skew_offset,
skew_exponent,
};
assert_eq!(
SkewVault::from(swap_vault),
SkewVault {
inventory_limit_upper,
inventory_limit_lower: 0,
positive_skew_per_m,
negative_skew_per_m,
lower_skew_offset,
upper_skew_offset,
skew_exponent,
}
);
}
#[rstest]
#[case(500, 1_000, 64_000, 1_000, 48_000)]
#[case(50_000, 0, 48_000, 16_000, 48_000)]
#[case(500, 2_000, 64_000, 1_000, 48_000)]
fn test_band_index_overflow(
#[case] balance: u64,
#[case] inventory_limit_lower: u64,
#[case] inventory_limit_upper: u64,
#[case] lower_skew_offset: u64,
#[case] upper_skew_offset: u64,
) {
let vault = SkewVault {
inventory_limit_upper,
inventory_limit_lower,
positive_skew_per_m: 0,
negative_skew_per_m: 0,
lower_skew_offset,
upper_skew_offset,
skew_exponent: SkewExponent::Linear,
};
assert_eq!(band_index(balance, true, &vault), Err(ARITHMETIC_OVERFLOW));
assert_eq!(band_index(balance, false, &vault), Err(ARITHMETIC_OVERFLOW));
}
#[test]
fn test_band_index_clamps_beyond_limit() {
let vault = SkewVault {
inventory_limit_upper: 32,
inventory_limit_lower: 0,
positive_skew_per_m: 0,
negative_skew_per_m: 0,
lower_skew_offset: 0,
upper_skew_offset: 0,
skew_exponent: SkewExponent::Linear,
};
assert_eq!(band_index(1_000, true, &vault).unwrap(), BAND_COUNT);
assert_eq!(band_index(1_000, false, &vault).unwrap(), BAND_COUNT);
}
}