df_hyperliquid_core/utils/
hex.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
2pub enum BadHex {
3 #[error("hex must have an even number of digits, found {0}")]
4 OddLength(usize),
5 #[error("expected {expected} bytes of hex, found {found}")]
6 WrongLength { expected: usize, found: usize },
7 #[error("`{0}` is not a hex digit")]
8 NotAHexDigit(char),
9}
10
11#[must_use]
12pub fn encode(bytes: &[u8]) -> String {
13 let mut text = String::with_capacity(bytes.len() * 2);
14 for byte in bytes {
15 text.push(nibble(byte >> 4));
16 text.push(nibble(byte & 0x0f));
17 }
18 text
19}
20
21#[must_use]
22pub fn encode_prefixed(bytes: &[u8]) -> String {
23 let mut text = String::with_capacity(bytes.len() * 2 + 2);
24 text.push_str("0x");
25 text.push_str(&encode(bytes));
26 text
27}
28
29pub fn decode(text: &str) -> Result<Vec<u8>, BadHex> {
31 let body = body_of(text);
32 if !body.len().is_multiple_of(2) {
33 return Err(BadHex::OddLength(body.len()));
34 }
35 let mut bytes = vec![0u8; body.len() / 2];
36 fill(body, &mut bytes)?;
37 Ok(bytes)
38}
39
40pub fn decode_into(text: &str, out: &mut [u8]) -> Result<(), BadHex> {
41 let body = body_of(text);
42 if !body.len().is_multiple_of(2) {
43 return Err(BadHex::OddLength(body.len()));
44 }
45 if body.len() / 2 != out.len() {
46 return Err(BadHex::WrongLength { expected: out.len(), found: body.len() / 2 });
47 }
48 fill(body, out)
49}
50
51pub fn decode_array<const N: usize>(text: &str) -> Result<[u8; N], BadHex> {
52 let mut bytes = [0u8; N];
53 decode_into(text, &mut bytes)?;
54 Ok(bytes)
55}
56
57fn body_of(text: &str) -> &str {
58 let raw = text.trim();
59 raw.strip_prefix("0x").or_else(|| raw.strip_prefix("0X")).unwrap_or(raw)
60}
61
62fn fill(body: &str, out: &mut [u8]) -> Result<(), BadHex> {
63 let digits = body.as_bytes();
64 for &byte in digits {
65 digit(byte)?;
66 }
67 for (index, slot) in out.iter_mut().enumerate() {
68 let high = digit(digits[index * 2])?;
69 let low = digit(digits[index * 2 + 1])?;
70 *slot = (high << 4) | low;
71 }
72 Ok(())
73}
74
75fn digit(byte: u8) -> Result<u8, BadHex> {
76 match byte {
77 b'0'..=b'9' => Ok(byte - b'0'),
78 b'a'..=b'f' => Ok(byte - b'a' + 10),
79 b'A'..=b'F' => Ok(byte - b'A' + 10),
80 _ => Err(BadHex::NotAHexDigit(char::from(byte))),
81 }
82}
83
84const fn nibble(value: u8) -> char {
85 (if value < 10 { b'0' + value } else { b'a' + value - 10 }) as char
86}
87
88#[cfg(test)]
89#[path = "tests/hex.rs"]
90mod tests;