Skip to main content

df_hyperliquid_core/client/evm/abi/
text.rs

1//! Tokens as text: a typed value read into its kind and range-checked, and a
2//! value shown back the way a person reads it.
3
4use crate::client::evm::abi::kind::split;
5use crate::client::evm::abi::token::negated;
6use crate::client::evm::abi::{AbiError, Kind, Token, WORD};
7use crate::client::evm::u256::U256;
8use crate::utils::hex;
9
10impl Token {
11    /// A typed value read into this kind: decimal for integers, `0x` hex for
12    /// addresses and bytes, `true` or `false`, and `[a, b]` or `(a, b)` for
13    /// the containers. `name` is what a refusal calls the field.
14    pub fn parse(kind: &Kind, name: &str, text: &str) -> Result<Token, AbiError> {
15        let text = text.trim();
16        let refused = || AbiError::Value { name: name.to_owned(), wants: kind.wants(), got: text.to_owned() };
17        Ok(match kind {
18            Kind::Uint(bits) => {
19                let value = U256::from_decimal(text).map_err(|_| refused())?;
20                if value.bits() > u32::from(*bits) {
21                    return Err(refused());
22                }
23                Token::Uint(value)
24            }
25            Kind::Int(bits) => {
26                let (negative, digits) = match text.strip_prefix('-') {
27                    Some(rest) => (true, rest),
28                    None => (false, text),
29                };
30                let magnitude = U256::from_decimal(digits).map_err(|_| refused())?;
31                // One bit is the sign: a magnitude below 2^(bits-1), or exactly
32                // that when it is negative.
33                let room = u32::from(*bits) - 1;
34                let edge = magnitude.bits() == room + 1 && magnitude == power_of_two(room);
35                if magnitude.bits() > room && !(negative && edge) {
36                    return Err(refused());
37                }
38                Token::Int(match negative {
39                    true => negated(magnitude),
40                    false => magnitude,
41                })
42            }
43            Kind::Address => Token::Address(text.parse().map_err(|_| refused())?),
44            Kind::Bool => match text.to_ascii_lowercase().as_str() {
45                "true" => Token::Bool(true),
46                "false" => Token::Bool(false),
47                _ => return Err(refused()),
48            },
49            Kind::String => Token::String(text.to_owned()),
50            Kind::Bytes => Token::Bytes(hex_of(text).ok_or_else(refused)?),
51            Kind::Fixed(size) => Token::Fixed(hex_of(text).filter(|bytes| bytes.len() == *size).ok_or_else(refused)?),
52            Kind::Array(inner) | Kind::Sized(inner, _) => {
53                let inside = text.strip_prefix('[').and_then(|rest| rest.strip_suffix(']')).ok_or_else(refused)?;
54                let items: Vec<&str> = match inside.trim().is_empty() {
55                    true => Vec::new(),
56                    false => split(inside),
57                };
58                if let Kind::Sized(_, count) = kind
59                    && items.len() != *count
60                {
61                    return Err(refused());
62                }
63                Token::Array(items.iter().map(|item| Token::parse(inner, name, item)).collect::<Result<_, _>>()?)
64            }
65            Kind::Tuple(params) => {
66                let inside = text.strip_prefix('(').and_then(|rest| rest.strip_suffix(')')).ok_or_else(refused)?;
67                let items = split(inside);
68                if items.len() != params.len() {
69                    return Err(refused());
70                }
71                Token::Tuple(
72                    params
73                        .iter()
74                        .zip(items)
75                        .map(|(param, item)| Token::parse(&param.kind, param.name.as_deref().unwrap_or(name), item))
76                        .collect::<Result<_, _>>()?,
77                )
78            }
79        })
80    }
81
82    /// The value as a person reads it: decimal integers, `0x` hex, and the
83    /// containers in brackets, a tuple's fields named where `kind` names them.
84    #[must_use]
85    pub fn show(&self, kind: &Kind) -> String {
86        match (self, kind) {
87            (Token::Uint(value), _) => value.to_string(),
88            (Token::Int(value), _) => signed(*value),
89            (Token::Address(address), _) => address.to_hex(),
90            (Token::Bool(value), _) => value.to_string(),
91            (Token::String(text), _) => text.clone(),
92            (Token::Bytes(bytes) | Token::Fixed(bytes), _) => hex::encode_prefixed(bytes),
93            (Token::Array(items), Kind::Array(inner) | Kind::Sized(inner, _)) => {
94                format!("[{}]", items.iter().map(|item| item.show(inner)).collect::<Vec<_>>().join(", "))
95            }
96            (Token::Tuple(items), Kind::Tuple(params)) => format!(
97                "{{{}}}",
98                items
99                    .iter()
100                    .zip(params)
101                    .map(|(item, param)| match &param.name {
102                        Some(name) => format!("{name}: {}", item.show(&param.kind)),
103                        None => item.show(&param.kind),
104                    })
105                    .collect::<Vec<_>>()
106                    .join(", ")
107            ),
108            (Token::Array(items) | Token::Tuple(items), _) => {
109                format!("[{}]", items.iter().map(|item| item.show(kind)).collect::<Vec<_>>().join(", "))
110            }
111        }
112    }
113
114    /// A short name of the value, for a refusal.
115    pub(super) fn describe(&self) -> String {
116        match self {
117            Token::Uint(value) => format!("the number {value}"),
118            Token::Int(value) => format!("the number {}", signed(*value)),
119            Token::Address(address) => format!("the address {address}"),
120            Token::Bool(value) => format!("{value}"),
121            Token::String(_) => "a string".to_owned(),
122            Token::Bytes(bytes) => format!("{} bytes", bytes.len()),
123            Token::Fixed(bytes) => format!("{} fixed bytes", bytes.len()),
124            Token::Array(items) => format!("a list of {}", items.len()),
125            Token::Tuple(items) => format!("a tuple of {}", items.len()),
126        }
127    }
128}
129
130fn power_of_two(exponent: u32) -> U256 {
131    let mut word = [0u8; WORD];
132    let at = WORD - 1 - (exponent / 8) as usize;
133    word[at] = 1 << (exponent % 8);
134    U256::from_be_bytes(word)
135}
136
137/// A two's complement word in decimal, with its sign.
138fn signed(value: U256) -> String {
139    match value.bits() == 256 {
140        true => format!("-{}", negated(value)),
141        false => value.to_string(),
142    }
143}
144
145fn hex_of(text: &str) -> Option<Vec<u8>> {
146    text.strip_prefix("0x").or_else(|| text.strip_prefix("0X"))?;
147    hex::decode(text).ok()
148}
149
150#[cfg(test)]
151#[path = "tests/text.rs"]
152mod tests;