use std::{fmt, str::FromStr};
use crate::client::evm::u256::U256;
use crate::utils::hex;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum BadQuantity {
#[error("`{0}` is not a hex quantity; expected 0x and hex digits")]
NotHex(String),
#[error("`{0}` is not a whole decimal number")]
NotDecimal(String),
#[error("`{0}` does not fit in 256 bits")]
TooLarge(String),
}
const TEN_19: u64 = 10_000_000_000_000_000_000;
impl U256 {
pub fn from_quantity(text: &str) -> Result<U256, BadQuantity> {
let bad = || BadQuantity::NotHex(text.to_owned());
let digits = text.strip_prefix("0x").or_else(|| text.strip_prefix("0X")).ok_or_else(bad)?;
if !digits.bytes().all(|byte| byte.is_ascii_hexdigit()) {
return Err(bad());
}
let digits = digits.trim_start_matches('0');
if digits.len() > 64 {
return Err(BadQuantity::TooLarge(text.to_owned()));
}
let bytes: [u8; 32] = hex::decode_array(&format!("{digits:0>64}")).map_err(|_| bad())?;
Ok(U256::from_be_bytes(bytes))
}
#[must_use]
pub fn to_quantity(self) -> String {
let digits = hex::encode(&self.to_be_bytes());
let trimmed = digits.trim_start_matches('0');
match trimmed.is_empty() {
true => "0x0".to_owned(),
false => format!("0x{trimmed}"),
}
}
pub fn from_decimal(text: &str) -> Result<U256, BadQuantity> {
if text.is_empty() || !text.bytes().all(|byte| byte.is_ascii_digit()) {
return Err(BadQuantity::NotDecimal(text.to_owned()));
}
let ten = U256::from_u64(10);
text.bytes().try_fold(U256::ZERO, |value, digit| {
value
.checked_mul(ten)
.and_then(|value| value.checked_add(U256::from_u64(u64::from(digit - b'0'))))
.ok_or_else(|| BadQuantity::TooLarge(text.to_owned()))
})
}
}
impl fmt::Display for U256 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut chunks = Vec::new();
let mut left = *self;
while let Some((quotient, chunk)) = left.div_rem_u64(TEN_19).filter(|_| !left.is_zero()) {
chunks.push(chunk);
left = quotient;
}
let Some((first, rest)) = chunks.split_last() else {
return f.write_str("0");
};
write!(f, "{first}")?;
for chunk in rest.iter().rev() {
write!(f, "{chunk:019}")?;
}
Ok(())
}
}
impl fmt::Debug for U256 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "U256({self})")
}
}
impl FromStr for U256 {
type Err = BadQuantity;
fn from_str(text: &str) -> Result<U256, BadQuantity> {
let text = text.trim();
match text.starts_with("0x") || text.starts_with("0X") {
true => U256::from_quantity(text),
false => U256::from_decimal(text),
}
}
}
impl Serialize for U256 {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.to_quantity())
}
}
impl<'de> Deserialize<'de> for U256 {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<U256, D::Error> {
let text = <std::borrow::Cow<'de, str>>::deserialize(deserializer)?;
U256::from_quantity(&text).map_err(de::Error::custom)
}
}
#[cfg(test)]
#[path = "tests/text.rs"]
mod tests;