Skip to main content

perpl_sdk/
num.rs

1use alloy::primitives::{I256, U256};
2use fastnum::{
3    bint,
4    decimal::{Context, Decimal, RoundingMode, UnsignedDecimal},
5};
6
7/// Scale of on-chain fee rates: the exchange expresses every fee rate in
8/// hundred-thousandths of the traded amount (`Per100K`, 1 = 0.1bps), exchange
9/// wide and independent of the perpetual contract.
10pub const FEE_SCALE: u8 = 5;
11
12/// Converter for fee rates, see [`FEE_SCALE`].
13pub fn fee_converter() -> Converter { Converter::new(FEE_SCALE) }
14
15/// Fixed-point to decimal converter.
16#[derive(Clone, Copy, Debug, Default)]
17pub struct Converter {
18    decimals: i32,
19}
20
21impl Converter {
22    /// Fixed-point converter for `decimals` decimal places. `pub` to match the
23    /// other public constructors, so callers can build one directly.
24    pub fn new(decimals: u8) -> Self { Self { decimals: decimals as i32 } }
25
26    pub fn decimals(&self) -> u8 { self.decimals as u8 }
27
28    pub fn scale<const N: usize>(&self) -> UnsignedDecimal<N> {
29        UnsignedDecimal::<N>::from_parts(
30            bint::UInt::ONE,
31            self.decimals,
32            Context::default().with_rounding_mode(RoundingMode::Floor),
33        )
34    }
35
36    pub fn from_unsigned<const N: usize>(&self, value: U256) -> UnsignedDecimal<N> {
37        let unscaled = bint::UInt::<N>::from_le_slice(value.as_le_slice())
38            .expect("Converter: U256 -> UInt::<N>");
39        UnsignedDecimal::<N>::from_parts(
40            unscaled,
41            -self.decimals,
42            Context::default().with_rounding_mode(RoundingMode::Floor),
43        )
44    }
45
46    pub fn from_u64<const N: usize>(&self, value: u64) -> UnsignedDecimal<N> {
47        UnsignedDecimal::<N>::from_parts(
48            bint::UInt::from_u64(value),
49            -self.decimals,
50            Context::default().with_rounding_mode(RoundingMode::Floor),
51        )
52    }
53
54    pub fn from_signed<const N: usize>(&self, value: I256) -> Decimal<N> {
55        let unscaled = bint::UInt::<N>::from_le_slice(value.unsigned_abs().as_le_slice())
56            .expect("Converter: abs(I256) -> UInt::<N>");
57        Decimal::<N>::from_parts(
58            unscaled,
59            -self.decimals,
60            match value.sign() {
61                alloy::primitives::Sign::Negative => fastnum::decimal::Sign::Minus,
62                alloy::primitives::Sign::Positive => fastnum::decimal::Sign::Plus,
63            },
64            Context::default().with_rounding_mode(RoundingMode::Floor),
65        )
66    }
67
68    pub fn from_i64<const N: usize>(&self, value: i64) -> Decimal<N> {
69        Decimal::<N>::from_parts(
70            bint::UInt::from_u64(value.unsigned_abs()),
71            -self.decimals,
72            if value < 0 { fastnum::decimal::Sign::Minus } else { fastnum::decimal::Sign::Plus },
73            Context::default().with_rounding_mode(RoundingMode::Floor),
74        )
75    }
76
77    pub fn to_unsigned<const N: usize>(&self, value: UnsignedDecimal<N>) -> U256 {
78        let rescaled = value.rescale(self.decimals as i16);
79        U256::from_le_slice(rescaled.digits().to_radix_le(256).as_slice())
80    }
81
82    pub fn to_signed<const N: usize>(&self, value: Decimal<N>) -> I256 {
83        let rescaled = value.rescale(self.decimals as i16);
84        let mut res = I256::try_from_le_slice(rescaled.digits().to_radix_le(256).as_slice())
85            .unwrap_or_default();
86        if value.is_negative() {
87            res = res.saturating_neg();
88        }
89        res
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use fastnum::{dec256, udec256};
96
97    use super::*;
98
99    #[test]
100    fn test_numeric_converter_from_unsigned() {
101        assert_eq!(Converter::new(0).from_unsigned(U256::from(1234567890)), udec256!(1234567890));
102        assert_eq!(Converter::new(6).from_unsigned(U256::from(1234567890)), udec256!(1234.56789));
103        assert_eq!(
104            Converter::new(12).from_unsigned(U256::from(1234567890)),
105            udec256!(0.00123456789)
106        );
107    }
108
109    #[test]
110    fn test_numeric_converter_from_signed() {
111        assert_eq!(
112            Converter::new(0).from_signed(I256::try_from(1234567890).unwrap()),
113            dec256!(1234567890)
114        );
115        assert_eq!(
116            Converter::new(6).from_signed(I256::try_from(1234567890).unwrap()),
117            dec256!(1234.56789)
118        );
119        assert_eq!(
120            Converter::new(12).from_signed(I256::try_from(1234567890).unwrap()),
121            dec256!(0.00123456789)
122        );
123
124        assert_eq!(
125            Converter::new(0).from_signed(I256::try_from(-1234567890).unwrap()),
126            dec256!(-1234567890)
127        );
128        assert_eq!(
129            Converter::new(6).from_signed(I256::try_from(-1234567890).unwrap()),
130            dec256!(-1234.56789)
131        );
132        assert_eq!(
133            Converter::new(12).from_signed(I256::try_from(-1234567890).unwrap()),
134            dec256!(-0.00123456789)
135        );
136    }
137
138    #[test]
139    fn test_numeric_converter_to_unsigned() {
140        assert_eq!(Converter::new(0).to_unsigned(udec256!(1234567890)), U256::from(1234567890));
141        assert_eq!(Converter::new(6).to_unsigned(udec256!(1234.56789)), U256::from(1234567890));
142        assert_eq!(Converter::new(12).to_unsigned(udec256!(0.00123456789)), U256::from(1234567890));
143    }
144
145    #[test]
146    fn test_numeric_converter_to_signed() {
147        assert_eq!(
148            Converter::new(0).to_signed(dec256!(1234567890)),
149            I256::try_from(1234567890).unwrap(),
150        );
151        assert_eq!(
152            Converter::new(6).to_signed(dec256!(1234.56789)),
153            I256::try_from(1234567890).unwrap(),
154        );
155        assert_eq!(
156            Converter::new(12).to_signed(dec256!(0.00123456789)),
157            I256::try_from(1234567890).unwrap(),
158        );
159
160        assert_eq!(
161            Converter::new(0).to_signed(dec256!(-1234567890)),
162            I256::try_from(-1234567890).unwrap(),
163        );
164        assert_eq!(
165            Converter::new(6).to_signed(dec256!(-1234.56789)),
166            I256::try_from(-1234567890).unwrap(),
167        );
168        assert_eq!(
169            Converter::new(12).to_signed(dec256!(-0.00123456789)),
170            I256::try_from(-1234567890).unwrap(),
171        );
172    }
173}