#![allow(clippy::missing_errors_doc)]
use ruint::aliases::U256;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _};
fn parse_radix_and_digits(input: &str) -> Result<(u64, &str), String> {
let s = input.trim();
if s.is_empty() {
return Err("empty numeric string".to_string());
}
if let Some(rest) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
if rest.is_empty() {
return Err("missing digits after 0x prefix".to_string());
}
Ok((16, rest))
} else {
Ok((10, s))
}
}
pub mod hex_u256 {
use super::*;
pub fn serialize<S>(value: &U256, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!("{value:#x}"))
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<U256, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let (radix, digits) = parse_radix_and_digits(&s).map_err(D::Error::custom)?;
U256::from_str_radix(digits, radix)
.map_err(|e| D::Error::custom(format!("invalid numeric U256: {e}")))
}
}
pub mod hex_u256_opt {
use super::*;
pub fn serialize<S>(value: &Option<U256>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(v) => serializer.serialize_some(&format!("{v:#x}")),
None => serializer.serialize_none(),
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<U256>, D::Error>
where
D: Deserializer<'de>,
{
let opt: Option<String> = Option::deserialize(deserializer)?;
match opt {
Some(s) => {
let (radix, digits) = parse_radix_and_digits(&s).map_err(D::Error::custom)?;
let v = U256::from_str_radix(digits, radix)
.map_err(|e| D::Error::custom(format!("invalid numeric U256: {e}")))?;
Ok(Some(v))
}
None => Ok(None),
}
}
}
pub mod hex_u256_vec {
use super::*;
pub fn serialize<S>(values: &[U256], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let hex_strings: Vec<String> = values.iter().map(|v| format!("{v:#x}")).collect();
hex_strings.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<U256>, D::Error>
where
D: Deserializer<'de>,
{
let strings: Vec<String> = Vec::deserialize(deserializer)?;
strings
.into_iter()
.map(|s| {
let (radix, digits) = parse_radix_and_digits(&s).map_err(D::Error::custom)?;
U256::from_str_radix(digits, radix)
.map_err(|e| D::Error::custom(format!("invalid numeric U256: {e}")))
})
.collect()
}
}
pub mod hex_u256_opt_vec {
use super::*;
pub fn serialize<S>(values: &[Option<U256>], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let hex_strings: Vec<Option<String>> = values
.iter()
.map(|value| value.as_ref().map(|v| format!("{v:#x}")))
.collect();
hex_strings.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Option<U256>>, D::Error>
where
D: Deserializer<'de>,
{
let strings: Vec<Option<String>> = Vec::deserialize(deserializer)?;
strings
.into_iter()
.map(|value| match value {
Some(s) => {
let (radix, digits) = parse_radix_and_digits(&s).map_err(D::Error::custom)?;
U256::from_str_radix(digits, radix)
.map(Some)
.map_err(|e| D::Error::custom(format!("invalid numeric U256: {e}")))
}
None => Ok(None),
})
.collect()
}
}
pub mod hex_u64 {
use super::*;
pub fn serialize<S>(value: &u64, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!("{value:#x}"))
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let (radix, digits) = parse_radix_and_digits(&s).map_err(D::Error::custom)?;
u64::from_str_radix(digits, radix as u32)
.map_err(|e| D::Error::custom(format!("invalid numeric u64: {e}")))
}
}
pub mod hex_u32 {
use super::*;
pub fn serialize<S>(value: &u32, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&format!("{value:#x}"))
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<u32, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let (radix, digits) = parse_radix_and_digits(&s).map_err(D::Error::custom)?;
u32::from_str_radix(digits, radix as u32)
.map_err(|e| D::Error::custom(format!("invalid numeric u32: {e}")))
}
}
pub mod hex_u32_opt {
use super::*;
pub fn serialize<S>(value: &Option<u32>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(v) => serializer.serialize_some(&format!("{v:#x}")),
None => serializer.serialize_none(),
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<u32>, D::Error>
where
D: Deserializer<'de>,
{
let opt: Option<String> = Option::deserialize(deserializer)?;
match opt {
Some(s) => {
let (radix, digits) = parse_radix_and_digits(&s).map_err(D::Error::custom)?;
let v = u32::from_str_radix(digits, radix as u32)
.map_err(|e| D::Error::custom(format!("invalid numeric u32: {e}")))?;
Ok(Some(v))
}
None => Ok(None),
}
}
}
pub mod hex_signature {
use alloy_primitives::Signature;
use serde::{Deserialize, Deserializer, Serializer, de::Error as _};
use std::str::FromStr;
pub fn serialize<S>(sig: &Signature, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&sig.to_string())
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Signature, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Signature::from_str(&s).map_err(D::Error::custom)
}
}
pub mod hex_bytes_opt {
use serde::{Deserialize, Deserializer, Serializer, de::Error as _};
pub fn serialize<S>(v: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match v {
Some(v) => {
if serializer.is_human_readable() {
serializer.serialize_some(&format!("0x{}", hex::encode(v)))
} else {
serializer.serialize_some(v)
}
}
None => serializer.serialize_none(),
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
let s = Option::<String>::deserialize(deserializer)?;
s.map(|s| {
let s = s
.strip_prefix("0x")
.or_else(|| s.strip_prefix("0X"))
.unwrap_or(&s);
hex::decode(s).map_err(D::Error::custom)
})
.transpose()
} else {
Option::<Vec<u8>>::deserialize(deserializer)
}
}
}
pub fn deserialize_present<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
T::deserialize(deserializer).map(Some)
}
pub mod cbor {
use std::fmt;
use serde::{
Deserializer,
de::{Error as _, Visitor},
};
pub fn deserialize_byte_string<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Vec<u8>, D::Error> {
struct ByteStringVisitor;
impl Visitor<'_> for ByteStringVisitor {
type Value = Vec<u8>;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a CBOR byte string")
}
fn visit_bytes<E>(self, bytes: &[u8]) -> Result<Vec<u8>, E> {
Ok(bytes.to_vec())
}
fn visit_byte_buf<E>(self, bytes: Vec<u8>) -> Result<Vec<u8>, E> {
Ok(bytes)
}
}
deserializer.deserialize_any(ByteStringVisitor)
}
pub fn deserialize_byte_array<'de, D: Deserializer<'de>, const N: usize>(
deserializer: D,
) -> Result<[u8; N], D::Error> {
deserialize_byte_string(deserializer)?
.try_into()
.map_err(|bytes: Vec<u8>| {
D::Error::custom(format!(
"expected a CBOR byte string of {N} bytes, got {}",
bytes.len()
))
})
}
pub fn deserialize_unsigned<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: TryFrom<u64>,
{
struct UnsignedVisitor;
impl Visitor<'_> for UnsignedVisitor {
type Value = u64;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a CBOR unsigned integer")
}
fn visit_u64<E>(self, value: u64) -> Result<u64, E> {
Ok(value)
}
}
let value = deserializer.deserialize_any(UnsignedVisitor)?;
T::try_from(value).map_err(|_| D::Error::custom("unsigned integer exceeds the field width"))
}
}
#[cfg(test)]
mod tests {
use ciborium::Value;
use serde::{Deserialize, Serialize};
use test_case::test_case;
use super::*;
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Test {
#[serde(with = "hex_u256")]
u256_val: U256,
#[serde(with = "hex_u64")]
u64_val: u64,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct TestOptVec {
#[serde(with = "hex_u256_opt_vec")]
values: Vec<Option<U256>>,
}
#[test]
fn test_hex_roundtrip() {
let original = Test {
u256_val: U256::from(0xdead_beef_u64),
u64_val: 42,
};
let json = serde_json::to_string(&original).unwrap();
assert!(json.contains("0xdeadbeef"));
assert!(json.contains("0x2a"));
let parsed: Test = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, original);
}
#[test]
fn test_deserialize_decimal_without_prefix() {
let parsed: Test = serde_json::from_str(r#"{"u256_val":"42","u64_val":"42"}"#).unwrap();
assert_eq!(parsed.u256_val, U256::from(42));
assert_eq!(parsed.u64_val, 42);
}
#[test]
fn test_deserialize_hex_with_prefix() {
let parsed: Test = serde_json::from_str(r#"{"u256_val":"0x2a","u64_val":"0x2a"}"#).unwrap();
assert_eq!(parsed.u256_val, U256::from(42));
assert_eq!(parsed.u64_val, 42);
}
#[test]
fn test_unprefixed_hex_like_value_is_rejected() {
let err = serde_json::from_str::<Test>(r#"{"u256_val":"ff","u64_val":"255"}"#).unwrap_err();
assert!(err.to_string().contains("invalid numeric U256"));
}
#[test]
fn test_u256_opt_vec_roundtrip() {
let original = TestOptVec {
values: vec![Some(U256::from(1)), None, Some(U256::from(255))],
};
let json = serde_json::to_string(&original).unwrap();
assert_eq!(json, r#"{"values":["0x1",null,"0xff"]}"#);
let parsed: TestOptVec = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, original);
}
#[test]
fn test_u256_opt_vec_deserialize_decimal_and_null() {
let parsed: TestOptVec = serde_json::from_str(r#"{"values":["42",null,"0x2a"]}"#).unwrap();
assert_eq!(
parsed.values,
vec![Some(U256::from(42)), None, Some(U256::from(42))]
);
}
#[test]
fn test_hex_signature_roundtrip() {
use alloy_primitives::Signature;
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct S {
#[serde(with = "hex_signature")]
sig: Signature,
}
let sig = Signature::new(U256::from(1), U256::from(2), false);
let s = S { sig };
let json = serde_json::to_string(&s).unwrap();
let value: serde_json::Value = serde_json::from_str(&json).unwrap();
let hex_str = value["sig"].as_str().expect("signature should be a string");
assert!(hex_str.starts_with("0x"), "should be 0x-prefixed");
assert_eq!(hex_str.len(), 132, "0x + 130 hex chars (65 bytes)");
let parsed: S = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, s);
}
fn from_cbor<T: serde::de::DeserializeOwned>(
value: &Value,
) -> Result<T, ciborium::de::Error<std::io::Error>> {
let mut encoded = Vec::new();
ciborium::into_writer(value, &mut encoded).unwrap();
ciborium::from_reader(encoded.as_slice())
}
#[derive(Debug, Deserialize)]
struct ByteString(#[serde(deserialize_with = "cbor::deserialize_byte_string")] Vec<u8>);
#[derive(Debug, Deserialize)]
struct ByteArray(#[serde(deserialize_with = "cbor::deserialize_byte_array")] [u8; 2]);
#[derive(Debug, Deserialize)]
struct Unsigned(#[serde(deserialize_with = "cbor::deserialize_unsigned")] u8);
#[test]
fn cbor_byte_strings_accept_a_byte_string() {
let bytes = Value::Bytes(vec![1, 2]);
assert_eq!(from_cbor::<ByteString>(&bytes).unwrap().0, [1, 2]);
assert_eq!(from_cbor::<ByteArray>(&bytes).unwrap().0, [1, 2]);
}
#[test_case(Value::Array(vec![1.into(), 2.into()]); "array of integers")]
#[test_case(Value::Tag(64, Box::new(Value::Bytes(vec![1, 2]))); "tagged byte string")]
#[test_case(Value::Text("0102".into()); "text")]
#[test_case(Value::Null; "null")]
fn cbor_byte_strings_reject_other_data_items(invalid: Value) {
assert!(from_cbor::<ByteString>(&invalid).is_err());
assert!(from_cbor::<ByteArray>(&invalid).is_err());
}
#[test]
fn cbor_byte_array_rejects_another_length() {
let error = from_cbor::<ByteArray>(&Value::Bytes(vec![1, 2, 3])).unwrap_err();
assert!(error.to_string().contains("of 2 bytes, got 3"), "{error}");
}
#[test]
fn cbor_unsigned_accepts_an_unsigned_integer_that_fits() {
let value = Value::Integer(255.into());
assert_eq!(from_cbor::<Unsigned>(&value).unwrap().0, 255);
}
#[test_case(Value::Integer(256.into()); "too wide")]
#[test_case(Value::Integer((-1).into()); "negative")]
#[test_case(Value::Float(1.0); "float")]
#[test_case(Value::Text("1".into()); "text")]
#[test_case(Value::Tag(0, Box::new(Value::Integer(1.into()))); "tagged")]
#[test_case(Value::Tag(2, Box::new(Value::Bytes(vec![1]))); "bignum")]
fn cbor_unsigned_rejects_other_data_items(invalid: Value) {
assert!(from_cbor::<Unsigned>(&invalid).is_err());
}
}