Skip to main content

df_hyperliquid_core/utils/
json.rs

1//! JSON that keeps what a signature depends on: keys in the order they were
2//! written and numbers as the digits they were written with. An L1 action is
3//! hashed as msgpack, which keeps key order, where `serde_json` sorts keys.
4
5use std::fmt::Write as _;
6
7use crate::utils::sign::msgpack::{Map, Value};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum Json {
11    Null,
12    Bool(bool),
13    /// The number exactly as written: no float ever holds it.
14    Number(String),
15    String(String),
16    Array(Vec<Json>),
17    /// Keys in the order they were written.
18    Object(Vec<(String, Json)>),
19}
20
21/// Where the text stopped being JSON, counted from one as an editor counts.
22#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
23#[error("line {line}, column {column}: {message}")]
24pub struct BadJson {
25    pub line: usize,
26    pub column: usize,
27    pub message: String,
28}
29
30/// Why a JSON value has no msgpack form the venue would hash the same way.
31#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
32pub enum Unpackable {
33    #[error("{0} is not a whole number; the venue hashes decimals as strings, so write it in quotes")]
34    Fraction(String),
35    #[error("{0} does not fit in 64 bits")]
36    TooWide(String),
37    #[error("an action is an object, and this is {0}")]
38    NotAnObject(&'static str),
39}
40
41impl Json {
42    pub fn parse(text: &str) -> Result<Json, BadJson> {
43        let mut reader = Reader { text: text.as_bytes(), at: 0, source: text };
44        reader.space();
45        let value = reader.value(0)?;
46        reader.space();
47        if reader.at < reader.text.len() {
48            return Err(reader.fail("more after the value ends"));
49        }
50        Ok(value)
51    }
52
53    #[must_use]
54    pub fn get(&self, key: &str) -> Option<&Json> {
55        match self {
56            Json::Object(entries) => entries.iter().find(|(name, _)| name == key).map(|(_, value)| value),
57            _ => None,
58        }
59    }
60
61    #[must_use]
62    pub fn as_str(&self) -> Option<&str> {
63        match self {
64            Json::String(text) => Some(text),
65            _ => None,
66        }
67    }
68
69    /// `true` or `false`; `None` for anything else, a `"true"` string too.
70    #[must_use]
71    pub const fn as_bool(&self) -> Option<bool> {
72        match self {
73            Json::Bool(value) => Some(*value),
74            _ => None,
75        }
76    }
77
78    /// A whole non-negative number, as the venue writes counts and ids.
79    #[must_use]
80    pub fn as_u64(&self) -> Option<u64> {
81        match self {
82            Json::Number(digits) => digits.parse().ok(),
83            _ => None,
84        }
85    }
86
87    #[must_use]
88    pub fn as_array(&self) -> Option<&[Json]> {
89        match self {
90            Json::Array(items) => Some(items),
91            _ => None,
92        }
93    }
94
95    #[must_use]
96    pub fn as_object(&self) -> Option<&[(String, Json)]> {
97        match self {
98            Json::Object(entries) => Some(entries),
99            _ => None,
100        }
101    }
102
103    #[must_use]
104    pub const fn kind(&self) -> &'static str {
105        match self {
106            Json::Null => "null",
107            Json::Bool(_) => "a boolean",
108            Json::Number(_) => "a number",
109            Json::String(_) => "a string",
110            Json::Array(_) => "an array",
111            Json::Object(_) => "an object",
112        }
113    }
114
115    /// One line, no spaces: what `JSON.stringify` writes, byte for byte.
116    #[must_use]
117    pub fn compact(&self) -> String {
118        let mut out = String::new();
119        self.write(&mut out, None, 0);
120        out
121    }
122
123    /// Two spaces a level, for showing a body to a person.
124    #[must_use]
125    pub fn pretty(&self) -> String {
126        let mut out = String::new();
127        self.write(&mut out, Some(2), 0);
128        out
129    }
130
131    fn write(&self, out: &mut String, indent: Option<usize>, depth: usize) {
132        let open = |out: &mut String, depth: usize| {
133            if let Some(step) = indent {
134                out.push('\n');
135                out.push_str(&" ".repeat(step * depth));
136            }
137        };
138        match self {
139            Json::Null => out.push_str("null"),
140            Json::Bool(value) => out.push_str(if *value { "true" } else { "false" }),
141            Json::Number(digits) => out.push_str(digits),
142            Json::String(text) => quote(text, out),
143            Json::Array(items) if items.is_empty() => out.push_str("[]"),
144            Json::Array(items) => {
145                out.push('[');
146                for (at, item) in items.iter().enumerate() {
147                    if at > 0 {
148                        out.push(',');
149                    }
150                    open(out, depth + 1);
151                    item.write(out, indent, depth + 1);
152                }
153                open(out, depth);
154                out.push(']');
155            }
156            Json::Object(entries) if entries.is_empty() => out.push_str("{}"),
157            Json::Object(entries) => {
158                out.push('{');
159                for (at, (key, value)) in entries.iter().enumerate() {
160                    if at > 0 {
161                        out.push(',');
162                    }
163                    open(out, depth + 1);
164                    quote(key, out);
165                    out.push(':');
166                    if indent.is_some() {
167                        out.push(' ');
168                    }
169                    value.write(out, indent, depth + 1);
170                }
171                open(out, depth);
172                out.push('}');
173            }
174        }
175    }
176
177    /// The msgpack value this hashes as, keys in the order they were written.
178    pub fn to_msgpack(&self) -> Result<Value, Unpackable> {
179        Ok(match self {
180            Json::Null => Value::Nil,
181            Json::Bool(value) => Value::Bool(*value),
182            Json::Number(digits) => whole(digits)?,
183            Json::String(text) => Value::from(text.clone()),
184            Json::Array(items) => Value::Array(items.iter().map(Json::to_msgpack).collect::<Result<_, _>>()?),
185            Json::Object(entries) => Value::Map(map_of(entries)?),
186        })
187    }
188
189    /// The action map an L1 signature is over. Only an object is one.
190    pub fn to_action(&self) -> Result<Map, Unpackable> {
191        match self {
192            Json::Object(entries) => map_of(entries),
193            other => Err(Unpackable::NotAnObject(other.kind())),
194        }
195    }
196}
197
198fn map_of(entries: &[(String, Json)]) -> Result<Map, Unpackable> {
199    let mut map = Map::new();
200    for (key, value) in entries {
201        map = map.put(key.clone(), value.to_msgpack()?);
202    }
203    Ok(map)
204}
205
206/// An integer as msgpack encodes it: unsigned when it can be, as the venue's
207/// own clients do. A fraction or an exponent would be a float, and a float is
208/// never what an action means.
209fn whole(digits: &str) -> Result<Value, Unpackable> {
210    if digits.contains(['.', 'e', 'E']) {
211        return Err(Unpackable::Fraction(digits.to_owned()));
212    }
213    match digits.strip_prefix('-') {
214        Some(_) => digits.parse::<i64>().map(Value::Int).map_err(|_| Unpackable::TooWide(digits.to_owned())),
215        None => digits.parse::<u64>().map(Value::Uint).map_err(|_| Unpackable::TooWide(digits.to_owned())),
216    }
217}
218
219fn quote(text: &str, out: &mut String) {
220    out.push('"');
221    for letter in text.chars() {
222        match letter {
223            '"' => out.push_str("\\\""),
224            '\\' => out.push_str("\\\\"),
225            '\n' => out.push_str("\\n"),
226            '\r' => out.push_str("\\r"),
227            '\t' => out.push_str("\\t"),
228            '\u{08}' => out.push_str("\\b"),
229            '\u{0c}' => out.push_str("\\f"),
230            control if u32::from(control) < 0x20 => {
231                let _ = write!(out, "\\u{:04x}", u32::from(control));
232            }
233            other => out.push(other),
234        }
235    }
236    out.push('"');
237}
238
239/// Nesting deeper than this is refused rather than followed off the stack.
240const DEEPEST: usize = 128;
241
242struct Reader<'a> {
243    text: &'a [u8],
244    at: usize,
245    source: &'a str,
246}
247
248impl Reader<'_> {
249    fn fail(&self, message: &str) -> BadJson {
250        let before = self.source.get(..self.at).unwrap_or(self.source);
251        let line = before.matches('\n').count() + 1;
252        let column = before.rsplit('\n').next().map_or(0, |tail| tail.chars().count()) + 1;
253        BadJson { line, column, message: message.to_owned() }
254    }
255
256    fn peek(&self) -> Option<u8> {
257        self.text.get(self.at).copied()
258    }
259
260    fn space(&mut self) {
261        while matches!(self.peek(), Some(b' ' | b'\n' | b'\r' | b'\t')) {
262            self.at += 1;
263        }
264    }
265
266    fn expect(&mut self, word: &str) -> Result<(), BadJson> {
267        match self.text[self.at..].starts_with(word.as_bytes()) {
268            true => {
269                self.at += word.len();
270                Ok(())
271            }
272            false => Err(self.fail(&format!("expected {word}"))),
273        }
274    }
275
276    fn value(&mut self, depth: usize) -> Result<Json, BadJson> {
277        if depth > DEEPEST {
278            return Err(self.fail("nested too deep"));
279        }
280        match self.peek() {
281            None => Err(self.fail("the text ends where a value should be")),
282            Some(b'n') => self.expect("null").map(|()| Json::Null),
283            Some(b't') => self.expect("true").map(|()| Json::Bool(true)),
284            Some(b'f') => self.expect("false").map(|()| Json::Bool(false)),
285            Some(b'"') => self.string().map(Json::String),
286            Some(b'[') => self.array(depth),
287            Some(b'{') => self.object(depth),
288            Some(b'-' | b'0'..=b'9') => self.number(),
289            Some(_) => Err(self.fail("not the start of a value")),
290        }
291    }
292
293    fn array(&mut self, depth: usize) -> Result<Json, BadJson> {
294        self.at += 1;
295        let mut items = Vec::new();
296        self.space();
297        if self.peek() == Some(b']') {
298            self.at += 1;
299            return Ok(Json::Array(items));
300        }
301        loop {
302            self.space();
303            items.push(self.value(depth + 1)?);
304            self.space();
305            match self.peek() {
306                Some(b',') => self.at += 1,
307                Some(b']') => {
308                    self.at += 1;
309                    return Ok(Json::Array(items));
310                }
311                _ => return Err(self.fail("expected , or ] in an array")),
312            }
313        }
314    }
315
316    fn object(&mut self, depth: usize) -> Result<Json, BadJson> {
317        self.at += 1;
318        let mut entries: Vec<(String, Json)> = Vec::new();
319        self.space();
320        if self.peek() == Some(b'}') {
321            self.at += 1;
322            return Ok(Json::Object(entries));
323        }
324        loop {
325            self.space();
326            if self.peek() != Some(b'"') {
327                return Err(self.fail("expected a key in quotes"));
328            }
329            let key = self.string()?;
330            if entries.iter().any(|(seen, _)| *seen == key) {
331                return Err(self.fail(&format!("the key {key:?} is written twice")));
332            }
333            self.space();
334            if self.peek() != Some(b':') {
335                return Err(self.fail("expected : after a key"));
336            }
337            self.at += 1;
338            self.space();
339            let value = self.value(depth + 1)?;
340            entries.push((key, value));
341            self.space();
342            match self.peek() {
343                Some(b',') => self.at += 1,
344                Some(b'}') => {
345                    self.at += 1;
346                    return Ok(Json::Object(entries));
347                }
348                _ => return Err(self.fail("expected , or } in an object")),
349            }
350        }
351    }
352
353    fn number(&mut self) -> Result<Json, BadJson> {
354        let start = self.at;
355        if self.peek() == Some(b'-') {
356            self.at += 1;
357        }
358        let digits = |reader: &mut Self| {
359            let from = reader.at;
360            while matches!(reader.peek(), Some(b'0'..=b'9')) {
361                reader.at += 1;
362            }
363            reader.at - from
364        };
365        let whole = self.at;
366        if digits(self) == 0 {
367            return Err(self.fail("a number needs digits"));
368        }
369        if self.text[whole] == b'0' && self.at - whole > 1 {
370            return Err(self.fail("a number does not start with 0"));
371        }
372        if self.peek() == Some(b'.') {
373            self.at += 1;
374            if digits(self) == 0 {
375                return Err(self.fail("digits must follow the point"));
376            }
377        }
378        if matches!(self.peek(), Some(b'e' | b'E')) {
379            self.at += 1;
380            if matches!(self.peek(), Some(b'+' | b'-')) {
381                self.at += 1;
382            }
383            if digits(self) == 0 {
384                return Err(self.fail("digits must follow the exponent"));
385            }
386        }
387        Ok(Json::Number(self.source[start..self.at].to_owned()))
388    }
389
390    fn string(&mut self) -> Result<String, BadJson> {
391        self.at += 1;
392        let mut out = String::new();
393        loop {
394            let start = self.at;
395            while !matches!(self.peek(), None | Some(b'"' | b'\\')) {
396                if self.text[self.at] < 0x20 {
397                    return Err(self.fail("a raw control character inside a string"));
398                }
399                self.at += 1;
400            }
401            out.push_str(&self.source[start..self.at]);
402            match self.peek() {
403                None => return Err(self.fail("a string that never closes")),
404                Some(b'"') => {
405                    self.at += 1;
406                    return Ok(out);
407                }
408                _ => {
409                    self.at += 1;
410                    let escaped = self.peek().ok_or_else(|| self.fail("a string that never closes"))?;
411                    self.at += 1;
412                    match escaped {
413                        b'"' => out.push('"'),
414                        b'\\' => out.push('\\'),
415                        b'/' => out.push('/'),
416                        b'b' => out.push('\u{08}'),
417                        b'f' => out.push('\u{0c}'),
418                        b'n' => out.push('\n'),
419                        b'r' => out.push('\r'),
420                        b't' => out.push('\t'),
421                        b'u' => out.push(self.unicode()?),
422                        _ => return Err(self.fail("not an escape JSON knows")),
423                    }
424                }
425            }
426        }
427    }
428
429    fn unicode(&mut self) -> Result<char, BadJson> {
430        let first = self.four()?;
431        if (0xd800..0xdc00).contains(&first) {
432            if !self.text[self.at..].starts_with(b"\\u") {
433                return Err(self.fail("half of a surrogate pair"));
434            }
435            self.at += 2;
436            let second = self.four()?;
437            if !(0xdc00..0xe000).contains(&second) {
438                return Err(self.fail("half of a surrogate pair"));
439            }
440            let joined = 0x10000 + ((first - 0xd800) << 10) + (second - 0xdc00);
441            return char::from_u32(joined).ok_or_else(|| self.fail("not a character"));
442        }
443        char::from_u32(first).ok_or_else(|| self.fail("half of a surrogate pair"))
444    }
445
446    fn four(&mut self) -> Result<u32, BadJson> {
447        let digits = self.source.get(self.at..self.at + 4).ok_or_else(|| self.fail("\\u needs four hex digits"))?;
448        let value = u32::from_str_radix(digits, 16).map_err(|_| self.fail("\\u needs four hex digits"))?;
449        self.at += 4;
450        Ok(value)
451    }
452}
453
454#[cfg(test)]
455#[path = "tests/json.rs"]
456mod tests;