Skip to main content

df_hyperliquid_core/utils/
address.rs

1use std::{fmt, str::FromStr};
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
4
5/// Twenty bytes. Read in either case, written lowercase, which is the only
6/// form the venue hashes.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
8pub struct Address([u8; 20]);
9
10#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
11#[error("`{0}` is not a 20-byte hex address")]
12pub struct BadAddress(String);
13
14impl Address {
15    pub const ZERO: Address = Address([0u8; 20]);
16
17    #[must_use]
18    pub const fn from_bytes(bytes: [u8; 20]) -> Self {
19        Address(bytes)
20    }
21
22    #[must_use]
23    pub const fn bytes(&self) -> &[u8; 20] {
24        &self.0
25    }
26
27    #[must_use]
28    pub fn is_zero(&self) -> bool {
29        *self == Address::ZERO
30    }
31
32    /// For constants: `0x` and forty hex digits in either case, or `None`.
33    #[must_use]
34    pub const fn parse_const(text: &str) -> Option<Address> {
35        let raw = text.as_bytes();
36        if raw.len() != 42 || raw[0] != b'0' || (raw[1] != b'x' && raw[1] != b'X') {
37            return None;
38        }
39        let mut bytes = [0u8; 20];
40        let mut at = 0;
41        while at < 20 {
42            let (Some(high), Some(low)) = (hex_digit(raw[2 + at * 2]), hex_digit(raw[3 + at * 2])) else {
43                return None;
44            };
45            bytes[at] = (high << 4) | low;
46            at += 1;
47        }
48        Some(Address(bytes))
49    }
50
51    /// The lowercase `0x` form the venue's JSON expects.
52    #[must_use]
53    pub fn to_hex(self) -> String {
54        crate::utils::hex::encode_prefixed(&self.0)
55    }
56}
57
58const fn hex_digit(byte: u8) -> Option<u8> {
59    match byte {
60        b'0'..=b'9' => Some(byte - b'0'),
61        b'a'..=b'f' => Some(byte - b'a' + 10),
62        b'A'..=b'F' => Some(byte - b'A' + 10),
63        _ => None,
64    }
65}
66
67impl FromStr for Address {
68    type Err = BadAddress;
69
70    fn from_str(text: &str) -> Result<Self, Self::Err> {
71        let raw = text.trim();
72        Address::parse_const(raw).ok_or_else(|| BadAddress(raw.to_owned()))
73    }
74}
75
76impl fmt::Display for Address {
77    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78        f.write_str(&self.to_hex())
79    }
80}
81
82impl Serialize for Address {
83    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
84        serializer.collect_str(self)
85    }
86}
87
88impl<'de> Deserialize<'de> for Address {
89    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
90        let text = <std::borrow::Cow<'de, str>>::deserialize(deserializer)?;
91        text.parse().map_err(de::Error::custom)
92    }
93}
94
95#[cfg(test)]
96#[path = "tests/address.rs"]
97mod tests;