df_hyperliquid_core/utils/
address.rs1use std::{fmt, str::FromStr};
2
3use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
4
5#[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 #[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 #[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)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn an_address_reads_back_out_of_its_own_json() {
101 let address: Address = "0xbaff87940cb148c5e2529c0a5944b0a3386fb015".parse().unwrap();
102 let json = serde_json::to_string(&address).unwrap();
103 assert_eq!(json, "\"0xbaff87940cb148c5e2529c0a5944b0a3386fb015\"");
104 let back: Address = serde_json::from_str(&json).unwrap();
105 assert_eq!(back, address);
106 }
107
108 #[test]
109 fn any_case_reads_to_the_same_address_and_writes_lowercase() {
110 let upper: Address = serde_json::from_str("\"0xBAFF87940CB148C5E2529C0A5944B0A3386FB015\"").unwrap();
111 let mixed: Address = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831".parse().unwrap();
112 let lower: Address = serde_json::from_str("\"0xbaff87940cb148c5e2529c0a5944b0a3386fb015\"").unwrap();
113 assert_eq!(upper, lower);
114 assert_eq!(upper.to_string(), "0xbaff87940cb148c5e2529c0a5944b0a3386fb015");
115 assert_eq!(mixed.to_hex(), "0xaf88d065e77c8cc2239327c5edb3a432268e5831");
116 assert_eq!(" 0X14791697260E4c9A71f18484C9f997B308e59325 ".parse::<Address>().unwrap().bytes()[0], 0x14);
117 }
118
119 #[test]
120 fn anything_else_is_refused_rather_than_zeroed() {
121 for bad in [
122 "0xdeadbeef",
123 "baff87940cb148c5e2529c0a5944b0a3386fb015",
124 "0xzzff87940cb148c5e2529c0a5944b0a3386fb015",
125 "0xbaff87940cb148c5e2529c0a5944b0a3386fb0155",
126 "",
127 "0x",
128 ] {
129 assert!(bad.parse::<Address>().is_err(), "{bad} should be refused");
130 }
131 for said in ["\"0x00\"", "\"nonsense\"", "\"\"", "42"] {
132 assert!(serde_json::from_str::<Address>(said).is_err(), "{said}");
133 }
134 }
135}