delvtech / hyperdrive-rs

Rust SDK for the Hyperdrive AMM.
https://docs.rs/crate/hyperdrive-math/latest
Apache License 2.0
2 stars 0 forks source link

Refactor `fixedpointmath` #186

Closed ryangoree closed 3 months ago

ryangoree commented 3 months ago

Related to: #127

(replaces wip pr: #184)

Description

This refactors the FixedPoint type to be generic with implementations for i128, u128, I256, and U256. For the most part, the library can be used the same, but with a type param, eg:

- fn foo() -> FixedPoint {}
+ fn foo() -> FixedPoint<U256> {}

However, the API has been expanded and there's plenty code reduction we can do in future PRs. It's probably also worth rethinking argument types now that the library can support negative numbers and be bound by different limits.

Expanded API

I wrote doc comments throughout to aid in adoption, but below is a skeleton containing most of the new API to get a quick sense:

pub struct FixedPoint<T: FixedPointValue> {
    raw: T,
    decimals: u8,
}

impl<T: FixedPointValue> FixedPoint<T> {
    pub const MIN: Self;
    pub const MAX: Self;

    // Constructors //
    pub fn new<V: Into<T>>(value: V) -> Self;
    pub fn try_from<V: TryInto<T> + Debug>(value: V) -> Result<Self>;
    pub fn from_sign_and_abs(sign: FixedPointSign, abs: U256) -> Result<Self>;
    pub fn from_dec_str(s: &str) -> Result<Self>;
    pub fn saturate_sign(sign: FixedPointSign) -> Self;
    pub fn zero() -> Self;
    pub fn one(&self) -> Self;

    // Getters //
    pub fn raw(&self) -> T;
    pub fn decimals(&self) -> u8;
    pub fn sign(&self) -> FixedPointSign;

    // Predicates //
    pub fn is_negative(&self) -> bool;
    pub fn is_positive(&self) -> bool;
    pub fn is_zero(&self) -> bool;

    // Conversion to other FixedPoint types //
    pub fn change_type<U: FixedPointValue + TryFrom<T>>(self) -> Result<FixedPoint<U>>;

    // Conversion to unsigned & signed ethers types //
    pub fn to_u256(self) -> Result<U256>;
    pub fn to_i256(self) -> Result<I256>;

    // Conversion to unsigned and signed std types //
    pub fn to_u128(self) -> Result<u128>;
    pub fn to_i128(self) -> Result<i128>;

    // Formatting //
    pub fn to_scaled_string(&self) -> String;

    // Math //
    pub fn abs(&self) -> Self;
    pub fn unsigned_abs(&self) -> FixedPoint<U256>; // `U256` to avoid overflow.
    pub fn abs_diff(&self, other: Self) -> FixedPoint<U256>;
    pub fn mul_div_down(self, other: Self, divisor: Self) -> Self;
    pub fn mul_div_up(self, other: Self, divisor: Self) -> Self;
    pub fn mul_down(self, other: Self) -> Self;
    pub fn mul_up(self, other: Self) -> Self;
    pub fn div_down(self, other: Self) -> Self;
    pub fn div_up(self, other: Self) -> Self;
    pub fn pow(self, y: Self) -> Result<Self>;
}

// Direct conversions between primitive types and FixedPoint for any
// `FixedPointValue` that can be converted to and from the primitive type.
conversion_impls!(i8, u8, i16, u16, i32, u32, i64, u64, isize, usize, [u8; 32], bool);

// Value //

/// A value that can be used to perform fixed-point math.
pub trait FixedPointValue: /* ... */ {
    const MIN: Self;
    const MAX: Self;
    const MAX_DECIMALS: u8 = 18;

    fn is_signed() -> bool;
    fn is_negative(&self) -> bool;
    fn is_positive(&self) -> bool;
    fn is_zero(&self) -> bool;
    fn flip_sign(self) -> Self;
    fn flip_sign_if(self, condition: bool) -> Self;
    fn abs(self) -> Self;
    fn unsigned_abs(self) -> U256;

    pub fn from_u256(u: U256) -> Result<Self>;
    pub fn from_u128(u: u128) -> Result<Self>;

    pub fn to_u256(self) -> Result<U256>;
    pub fn to_u128(self) -> Result<u128>;
}

// Conversions traits //

pub trait Fixed: FixedPointValue {
    fn fixed(self) -> FixedPoint<Self>;
}

// Add `.fixed()` to all types that implement `FixedPointValue`.
impl<T: FixedPointValue> Fixed for T {}

pub trait ToFixed: Sized + Debug {
    fn to_fixed<T: FixedPointValue + From<Self>>(self) -> FixedPoint<T>;
    fn try_to_fixed<T: FixedPointValue + TryFrom<Self>>(self) -> Result<FixedPoint<T>>;
}

// Add `.to_fixed()` & `.try_to_fixed()` to all sized types that implement
// `Debug`.
impl<T: Sized + Debug> ToFixed for T {}

// Macros //

macro_rules! uint256 {}    // -> `U256`
macro_rules! int256 {}     // -> `I256`
macro_rules! fixed {}      // -> `FixedPoint<T>` (where `T` is inferred)
macro_rules! fixed_u256 {} // -> `FixedPoint<U256>`
macro_rules! fixed_i256 {} // -> `FixedPoint<I256>`
macro_rules! fixed_u128 {} // -> `FixedPoint<u128>`
macro_rules! fixed_i128 {} // -> `FixedPoint<i128>`