Skip to main content

df_hyperliquid_core/utils/dec/
wire.rs

1//! A decimal as the venue writes it: read with an optional sign and at most one point, written back
2//! with no trailing zeros and no exponent, and the same through serde.
3
4use std::{fmt, str::FromStr};
5
6use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
7
8use crate::utils::dec::{Dec, MAX_SCALE};
9
10#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
11pub enum BadDec {
12    #[error("`{0}` is not a decimal number")]
13    NotANumber(String),
14    #[error("`{0}` has more than eighteen decimal places")]
15    TooFine(String),
16    #[error("`{0}` is larger than this build can count")]
17    OutOfRange(String),
18}
19
20impl Dec {
21    /// Reads what the venue writes: an optional sign, digits, and at most one
22    /// point. No exponent, since the venue never sends one.
23    pub fn parse(text: &str) -> Result<Dec, BadDec> {
24        let bad = || BadDec::NotANumber(text.to_owned());
25        let body = text.trim();
26        let (negative, body) = match body.strip_prefix('-') {
27            Some(rest) => (true, rest),
28            None => (false, body.strip_prefix('+').unwrap_or(body)),
29        };
30        let (whole, fraction) = body.split_once('.').unwrap_or((body, ""));
31        if whole.is_empty() && fraction.is_empty() {
32            return Err(bad());
33        }
34        if !whole.bytes().all(|b| b.is_ascii_digit()) || !fraction.bytes().all(|b| b.is_ascii_digit()) {
35            return Err(bad());
36        }
37        let fraction = fraction.trim_end_matches('0');
38        let scale = u32::try_from(fraction.len()).map_err(|_| BadDec::TooFine(text.to_owned()))?;
39        if scale > MAX_SCALE {
40            return Err(BadDec::TooFine(text.to_owned()));
41        }
42        let mut units: i128 = 0;
43        for digit in whole.bytes().chain(fraction.bytes()) {
44            units = units
45                .checked_mul(10)
46                .and_then(|u| u.checked_add(i128::from(digit - b'0')))
47                .ok_or_else(|| BadDec::OutOfRange(text.to_owned()))?;
48        }
49        Ok(Dec { units: if negative { -units } else { units }, scale }.normal())
50    }
51
52    /// The canonical spelling: no trailing zeros, no exponent, `0` for zero.
53    /// This is the string that is hashed, so it must match the venue's clients.
54    #[must_use]
55    pub fn to_wire(self) -> String {
56        self.to_string()
57    }
58}
59
60impl fmt::Display for Dec {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        let normal = self.normal();
63        let sign = if normal.units < 0 { "-" } else { "" };
64        let magnitude = normal.units.unsigned_abs();
65        if normal.scale == 0 {
66            return write!(f, "{sign}{magnitude}");
67        }
68        let step = 10u128.pow(normal.scale);
69        let places = normal.scale as usize;
70        write!(f, "{sign}{}.{:0places$}", magnitude / step, magnitude % step)
71    }
72}
73
74impl FromStr for Dec {
75    type Err = BadDec;
76
77    fn from_str(text: &str) -> Result<Self, Self::Err> {
78        Dec::parse(text)
79    }
80}
81
82impl Serialize for Dec {
83    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
84        serializer.collect_str(self)
85    }
86}
87
88/// From the venue's decimal strings, or a JSON integer. A JSON fraction is
89/// refused: it has already been through a float.
90impl<'de> Deserialize<'de> for Dec {
91    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
92        struct Visitor;
93
94        impl de::Visitor<'_> for Visitor {
95            type Value = Dec;
96
97            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98                f.write_str("a decimal string or an integer")
99            }
100
101            fn visit_str<E: de::Error>(self, text: &str) -> Result<Dec, E> {
102                Dec::parse(text).map_err(E::custom)
103            }
104
105            fn visit_u64<E: de::Error>(self, value: u64) -> Result<Dec, E> {
106                Ok(Dec { units: i128::from(value), scale: 0 })
107            }
108
109            fn visit_i64<E: de::Error>(self, value: i64) -> Result<Dec, E> {
110                Ok(Dec { units: i128::from(value), scale: 0 })
111            }
112        }
113
114        deserializer.deserialize_any(Visitor)
115    }
116}
117
118#[cfg(test)]
119#[path = "tests/wire.rs"]
120mod tests;