use anchor_lang::prelude::{borsh, AnchorDeserialize, AnchorSerialize};
use anchor_spl::token::Mint;
use fix::prelude::*;
use fix::typenum::Integer;
use serde::{Deserialize, Serialize};
use crate::error::CoreError;
use crate::error::CoreError::{SlippageArithmetic, SlippageExceeded};
use crate::util::normalize_mint_exp;
#[derive(Debug, AnchorSerialize, AnchorDeserialize, Serialize, Deserialize)]
pub struct SlippageConfig {
pub expected_token_out: UFixValue64,
pub slippage_tolerance: UFixValue64,
}
impl SlippageConfig {
#[must_use]
pub fn new<Exp: Integer>(
expected_token_out: UFix64<Exp>,
slippage_tolerance: UFix64<N4>,
) -> SlippageConfig {
SlippageConfig {
expected_token_out: expected_token_out.into(),
slippage_tolerance: slippage_tolerance.into(),
}
}
pub fn expected_token_out<Exp: Integer>(
&self,
) -> Result<UFix64<Exp>, CoreError> {
Ok(self.expected_token_out.try_into()?)
}
pub fn slippage_tolerance(&self) -> Result<UFix64<N4>, CoreError> {
Ok(self.slippage_tolerance.try_into()?)
}
fn tolerable_amount<Exp>(
expected: UFix64<Exp>,
tolerance: UFix64<N4>,
) -> Option<UFix64<Exp>> {
UFix64::<N4>::one()
.checked_sub(&tolerance)
.and_then(|factor| expected.mul_div_floor(factor, UFix64::one()))
}
fn meets_tolerance<Exp>(
expected: UFix64<Exp>,
tolerance: UFix64<N4>,
token_out: UFix64<Exp>,
) -> Option<bool> {
SlippageConfig::tolerable_amount(expected, tolerance)
.map(|t| token_out >= t)
}
fn validate_inner<Exp>(
expected: UFix64<Exp>,
tolerance: UFix64<N4>,
token_out: UFix64<Exp>,
) -> Result<(), CoreError> {
match SlippageConfig::meets_tolerance(expected, tolerance, token_out) {
None => Err(SlippageArithmetic),
Some(true) => Ok(()),
Some(false) => Err(SlippageExceeded),
}
}
pub fn validate_token_out<Exp: Integer>(
&self,
token_out: UFix64<Exp>,
) -> Result<(), CoreError> {
let expected = self.expected_token_out()?;
let tolerance = self.slippage_tolerance()?;
Self::validate_inner(expected, tolerance, token_out)
}
pub fn validate_token_out_normalized(
&self,
mint: &Mint,
token_out: UFix64<N9>,
) -> Result<(), CoreError> {
let expected = normalize_mint_exp(mint, self.expected_token_out.bits)?;
let tolerance = self.slippage_tolerance()?;
Self::validate_inner(expected, tolerance, token_out)
}
}
#[cfg(test)]
mod tests {
use fix::prelude::*;
use crate::error::CoreError::{SlippageArithmetic, SlippageExceeded};
use crate::slippage_config::SlippageConfig;
const ONE_PERCENT: UFix64<N4> = UFix64::constant(100);
#[test]
fn slippage_exact_boundary_pos() {
let config = SlippageConfig::new(UFix64::<N6>::one(), ONE_PERCENT);
let amount = UFix64::<N6>::new(990_000);
assert!(config.validate_token_out(amount).is_ok());
}
#[test]
fn slippage_one_below_boundary_neg() {
let config = SlippageConfig::new(UFix64::<N6>::one(), ONE_PERCENT);
let amount = UFix64::<N6>::new(989_999);
assert_eq!(config.validate_token_out(amount), Err(SlippageExceeded));
}
#[test]
fn slippage_zero_tolerance_exact_pos() {
let config = SlippageConfig::new(UFix64::<N6>::one(), UFix64::zero());
assert!(config.validate_token_out(UFix64::<N6>::one()).is_ok());
}
#[test]
fn slippage_zero_tolerance_below_neg() {
let config = SlippageConfig::new(UFix64::<N6>::one(), UFix64::zero());
let amount = UFix64::<N6>::new(999_999);
assert_eq!(config.validate_token_out(amount), Err(SlippageExceeded));
}
#[test]
fn slippage_favorable_execution_pos() {
let config = SlippageConfig::new(UFix64::<N6>::one(), UFix64::new(50));
let amount = UFix64::<N6>::new(1_500_000);
assert!(config.validate_token_out(amount).is_ok());
}
#[test]
fn slippage_full_tolerance_accepts_zero_pos() {
let config = SlippageConfig::new(UFix64::<N6>::one(), UFix64::<N4>::one());
assert!(config.validate_token_out(UFix64::<N6>::zero()).is_ok());
}
#[test]
fn slippage_tolerance_above_one_neg() {
let config =
SlippageConfig::new(UFix64::<N6>::one(), UFix64::<N4>::new(10_001));
assert_eq!(
config.validate_token_out(UFix64::<N6>::one()),
Err(SlippageArithmetic)
);
}
}