From 56ff52dd6e196510d8ea6c2835c3a24818d3698b Mon Sep 17 00:00:00 2001
From: DaniPopes <57450786+DaniPopes@users.noreply.github.com>
Date: Thu, 18 May 2023 16:54:57 +0200
Subject: [PATCH 01/30] feat: add more serde utils and impls
---
Cargo.toml | 6 +-
crates/primitives/Cargo.toml | 8 +-
crates/primitives/src/bits/address.rs | 22 +--
crates/primitives/src/bits/fixed.rs | 14 +-
crates/primitives/src/bits/macros.rs | 23 ++-
crates/primitives/src/bits/mod.rs | 3 +-
crates/primitives/src/bits/serde.rs | 39 +++++
crates/primitives/src/bits/serialize.rs | 23 ---
crates/primitives/src/lib.rs | 3 +
crates/primitives/src/serde/jsonu256.rs | 119 +++++++++++++
crates/primitives/src/serde/mod.rs | 56 ++++++
crates/primitives/src/serde/num.rs | 188 +++++++++++++++++++++
crates/primitives/src/serde/storage_key.rs | 28 +++
crates/primitives/src/signed/int.rs | 30 ++--
crates/primitives/src/signed/mod.rs | 22 ++-
crates/primitives/src/signed/serde.rs | 55 ++++++
crates/sol-macro/Cargo.toml | 2 +-
crates/sol-types/Cargo.toml | 2 +-
crates/sol-types/src/eip712.rs | 2 +-
crates/sol-types/src/util.rs | 54 ------
20 files changed, 553 insertions(+), 146 deletions(-)
create mode 100644 crates/primitives/src/bits/serde.rs
delete mode 100644 crates/primitives/src/bits/serialize.rs
create mode 100644 crates/primitives/src/serde/jsonu256.rs
create mode 100644 crates/primitives/src/serde/mod.rs
create mode 100644 crates/primitives/src/serde/num.rs
create mode 100644 crates/primitives/src/serde/storage_key.rs
create mode 100644 crates/primitives/src/signed/serde.rs
diff --git a/Cargo.toml b/Cargo.toml
index 61d0d5fa84..e7b8207c1c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,7 +24,7 @@ ruint-macro = { version = "1.0.2", git = "https://github.com/recmo/uint", defaul
# serde
serde = { version = "1.0", default-features = false }
-serde_json = { version = "1.0", default-features = false }
+serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
# macros
proc-macro2 = "1.0"
@@ -35,9 +35,9 @@ syn = "2.0"
arbitrary = { version = "1.3", default-features = false }
arrayvec = { version = "0.7.2", default-features = false }
bytes = { version = "1.4", default-features = false }
-hex = { package = "const-hex", version = ">=1.3", default-features = false }
+hex = { package = "const-hex", version = ">=1.5", default-features = false, features = ["alloc"] }
hex-literal = "0.4"
-proptest = { version = "1.1", default-features = false }
+proptest = { version = "1.1", default-features = false, features = ["alloc"] }
proptest-derive = "0.3"
thiserror = "1.0"
tiny-keccak = "2.0"
diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml
index 968d5e019b..b67354026c 100644
--- a/crates/primitives/Cargo.toml
+++ b/crates/primitives/Cargo.toml
@@ -4,7 +4,7 @@ version = "0.1.0"
description = "Fundamental ethereum types shared by revm, reth and ethers"
readme = "README.md"
keywords = ["ethereum", "ethers", "revm", "reth"]
-categories = ["cryptography::cryptocurrencies"]
+categories = ["data-structures", "cryptography::cryptocurrencies"]
edition.workspace = true
rust-version.workspace = true
@@ -23,7 +23,7 @@ tiny-keccak = { workspace = true, features = ["keccak"] }
hex.workspace = true
itoa = "1"
-# optional
+# serde
serde = { workspace = true, features = ["derive"], optional = true }
# rlp support
@@ -37,12 +37,14 @@ proptest-derive = { workspace = true, optional = true }
[dev-dependencies]
hex-literal = "0.4"
+serde_json = { workspace = true }
+proptest = { workspace = true }
[features]
default = ["std", "rlp", "serde", "hex/std"]
std = ["serde/std", "ethers-rlp?/std", "bytes?/std", "proptest?/std"]
rlp = ["dep:ethers-rlp", "dep:bytes"]
-serde = ["dep:serde", "ruint/serde"]
+serde = ["dep:serde", "ruint/serde", "hex/serde"]
arbitrary = [
"ruint/arbitrary",
"ruint/proptest",
diff --git a/crates/primitives/src/bits/address.rs b/crates/primitives/src/bits/address.rs
index 40025fe62c..f252877fa7 100644
--- a/crates/primitives/src/bits/address.rs
+++ b/crates/primitives/src/bits/address.rs
@@ -44,12 +44,6 @@ impl Borrow<[u8; 20]> for Address {
}
}
-impl From
for [u8; 20] {
- fn from(addr: Address) -> Self {
- addr.0.into()
- }
-}
-
impl From for FixedBytes<32> {
fn from(addr: Address) -> Self {
let mut buf: FixedBytes<32> = Default::default();
@@ -77,10 +71,11 @@ impl Address {
addr_buf[1] = b'x';
hex::encode_to_slice(self.as_bytes(), &mut addr_buf[2..]).unwrap();
- let hash = match chain_id {
+ let mut storage;
+ let to_hash = match chain_id {
Some(chain_id) => {
// A decimal `u64` string is at most 20 bytes long: round up 20 + 42 to 64.
- let mut prefixed = [0u8; 64];
+ storage = [0u8; 64];
// Format the `chain_id` into a stack-allocated buffer using `itoa`
let mut temp = itoa::Buffer::new();
@@ -89,19 +84,20 @@ impl Address {
debug_assert!(prefix_len <= 20);
let len = prefix_len + 42;
- // SAFETY: prefix_len <= 20; len <= 62; prefixed.len() == 64
+ // SAFETY: prefix_len <= 20; len <= 62; storage.len() == 64
unsafe {
- prefixed
+ storage
.get_unchecked_mut(..prefix_len)
.copy_from_slice(prefix_str.as_bytes());
- prefixed
+ storage
.get_unchecked_mut(prefix_len..len)
.copy_from_slice(addr_buf);
}
- keccak256(&prefixed[..len])
+ &storage[..len]
}
- None => keccak256(&addr_buf[2..]),
+ None => &addr_buf[2..],
};
+ let hash = keccak256(to_hash);
let mut hash_hex = [0u8; 64];
hex::encode_to_slice(hash.as_bytes(), &mut hash_hex).unwrap();
diff --git a/crates/primitives/src/bits/fixed.rs b/crates/primitives/src/bits/fixed.rs
index 0ff2114246..ec944e445f 100644
--- a/crates/primitives/src/bits/fixed.rs
+++ b/crates/primitives/src/bits/fixed.rs
@@ -377,19 +377,13 @@ impl core::str::FromStr for FixedBytes {
impl FixedBytes {
fn fmt_hex(&self, f: &mut fmt::Formatter<'_>, prefix: bool) -> fmt::Result {
- if prefix {
- f.write_str("0x")?;
- }
- let mut buf = hex::Buffer::new();
- f.write_str(buf.format(&self.0))
+ let mut buf = hex::Buffer::::new();
+ f.write_str(&buf.format(&self.0)[(!prefix as usize) * 2..])
}
fn fmt_hex_upper(&self, f: &mut fmt::Formatter<'_>, prefix: bool) -> fmt::Result {
- if prefix {
- f.write_str("0x")?;
- }
- let mut buf = hex::Buffer::new();
- f.write_str(buf.format_upper(&self.0))
+ let mut buf = hex::Buffer::::new();
+ f.write_str(&buf.format_upper(&self.0)[(!prefix as usize) * 2..])
}
}
diff --git a/crates/primitives/src/bits/macros.rs b/crates/primitives/src/bits/macros.rs
index ed10c22556..b1c5e7282e 100644
--- a/crates/primitives/src/bits/macros.rs
+++ b/crates/primitives/src/bits/macros.rs
@@ -66,33 +66,40 @@ macro_rules! wrap_fixed_bytes {
$crate::derive_more::LowerHex,
$crate::derive_more::UpperHex,
)]
- pub struct $name($crate::FixedBytes<$n>);
+ pub struct $name(pub $crate::FixedBytes<$n>);
- impl<'a> From<[u8; $n]> for $name {
+ impl From<[u8; $n]> for $name {
#[inline]
- fn from(bytes: [u8; $n]) -> Self {
- Self(bytes.into())
+ fn from(value: [u8; $n]) -> Self {
+ Self(value.into())
+ }
+ }
+
+ impl From<$name> for [u8; $n] {
+ #[inline]
+ fn from(value: $name) -> Self {
+ value.0.0
}
}
impl<'a> From<&'a [u8; $n]> for $name {
#[inline]
- fn from(bytes: &'a [u8; $n]) -> Self {
- Self(bytes.into())
+ fn from(value: &'a [u8; $n]) -> Self {
+ Self(value.into())
}
}
impl AsRef<[u8]> for $name {
#[inline]
fn as_ref(&self) -> &[u8] {
- self.as_bytes()
+ &self.0.0
}
}
impl AsMut<[u8]> for $name {
#[inline]
fn as_mut(&mut self) -> &mut [u8] {
- self.as_bytes_mut()
+ &mut self.0.0
}
}
diff --git a/crates/primitives/src/bits/mod.rs b/crates/primitives/src/bits/mod.rs
index 7cca3148ab..4549cf1e5a 100644
--- a/crates/primitives/src/bits/mod.rs
+++ b/crates/primitives/src/bits/mod.rs
@@ -6,9 +6,8 @@ pub use fixed::FixedBytes;
mod macros;
-// code stolen from: https://docs.rs/impl-serde/0.4.0/impl_serde/
#[cfg(feature = "serde")]
-mod serialize;
+mod serde;
#[cfg(feature = "rlp")]
mod rlp;
diff --git a/crates/primitives/src/bits/serde.rs b/crates/primitives/src/bits/serde.rs
new file mode 100644
index 0000000000..3d22bc9eea
--- /dev/null
+++ b/crates/primitives/src/bits/serde.rs
@@ -0,0 +1,39 @@
+use super::FixedBytes;
+use alloc::string::String;
+use core::fmt;
+use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
+
+impl Serialize for FixedBytes {
+ fn serialize(&self, serializer: S) -> Result {
+ let mut buf = hex::Buffer::::new();
+ serializer.serialize_str(buf.format(&self.0))
+ }
+}
+
+impl<'de, const N: usize> Deserialize<'de> for FixedBytes {
+ fn deserialize>(deserializer: D) -> Result {
+ deserializer.deserialize_str(FixedBytesVisitor::)
+ }
+}
+
+struct FixedBytesVisitor;
+
+impl de::Visitor<'_> for FixedBytesVisitor {
+ type Value = FixedBytes;
+
+ fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "a {N} byte hex string")
+ }
+
+ fn visit_str(self, v: &str) -> Result {
+ let mut buffer = [0u8; N];
+ match hex::decode_to_slice(v.as_bytes(), &mut buffer) {
+ Ok(()) => Ok(FixedBytes(buffer)),
+ Err(e) => Err(de::Error::custom(e)),
+ }
+ }
+
+ fn visit_string(self, v: String) -> Result {
+ self.visit_str(v.as_str())
+ }
+}
diff --git a/crates/primitives/src/bits/serialize.rs b/crates/primitives/src/bits/serialize.rs
deleted file mode 100644
index e6825c4020..0000000000
--- a/crates/primitives/src/bits/serialize.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-use super::FixedBytes;
-use alloc::string::String;
-use core::result::Result;
-
-impl serde::Serialize for FixedBytes {
- fn serialize(&self, serializer: S) -> Result {
- serializer.collect_str(&format_args!("{}", self))
- }
-}
-
-impl<'de, const N: usize> serde::Deserialize<'de> for FixedBytes {
- fn deserialize>(deserializer: D) -> Result {
- let expected = 2 * N + 2;
- let s = String::deserialize(deserializer)?;
- if s.len() != expected {
- return Err(serde::de::Error::custom(format!(
- "Expected exactly {expected} chars, including a 0x prefix. Got {}",
- s.len()
- )))
- }
- s.parse().map_err(serde::de::Error::custom)
- }
-}
diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs
index e785fb574a..581163a300 100644
--- a/crates/primitives/src/lib.rs
+++ b/crates/primitives/src/lib.rs
@@ -18,6 +18,9 @@ extern crate alloc;
mod bits;
pub use bits::{Address, AddressError, FixedBytes, B256};
+#[cfg(feature = "serde")]
+pub mod serde;
+
mod signed;
pub use signed::{
aliases::{self, I160, I256},
diff --git a/crates/primitives/src/serde/jsonu256.rs b/crates/primitives/src/serde/jsonu256.rs
new file mode 100644
index 0000000000..305b871ace
--- /dev/null
+++ b/crates/primitives/src/serde/jsonu256.rs
@@ -0,0 +1,119 @@
+use crate::U256;
+use alloc::string::String;
+use core::fmt;
+use serde::{
+ de::{Error, Visitor},
+ Deserialize, Deserializer, Serialize, Serializer,
+};
+
+/// Wrapper around primitive U256 type to handle edge cases of json parser
+#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
+pub struct JsonU256(pub U256);
+
+impl From for U256 {
+ fn from(value: JsonU256) -> Self {
+ value.0
+ }
+}
+
+impl From for JsonU256 {
+ fn from(value: U256) -> Self {
+ JsonU256(value)
+ }
+}
+
+impl Serialize for JsonU256 {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: Serializer,
+ {
+ // TODO: Uint serde
+ self.0.serialize(serializer)
+ }
+}
+
+impl<'a> Deserialize<'a> for JsonU256 {
+ fn deserialize(deserializer: D) -> Result
+ where
+ D: Deserializer<'a>,
+ {
+ deserializer.deserialize_any(JsonU256Visitor)
+ }
+}
+
+struct JsonU256Visitor;
+
+impl<'a> Visitor<'a> for JsonU256Visitor {
+ type Value = JsonU256;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(formatter, "a hex encoding or decimal number")
+ }
+
+ fn visit_u64(self, value: u64) -> Result
+ where
+ E: Error,
+ {
+ Ok(JsonU256(U256::from(value)))
+ }
+
+ fn visit_str(self, value: &str) -> Result
+ where
+ E: Error,
+ {
+ if value.is_empty() || value == "0x" {
+ Ok(JsonU256(U256::ZERO))
+ } else {
+ value.parse::().map(JsonU256).map_err(Error::custom)
+ }
+ }
+
+ fn visit_string(self, value: String) -> Result
+ where
+ E: Error,
+ {
+ self.visit_str(value.as_ref())
+ }
+}
+
+/// Supports parsing `U256` numbers as strings via [JsonU256]
+pub fn deserialize_json_u256<'de, D>(deserializer: D) -> Result
+where
+ D: Deserializer<'de>,
+{
+ let num = JsonU256::deserialize(deserializer)?;
+ Ok(num.into())
+}
+
+#[cfg(test)]
+mod test {
+ use super::JsonU256;
+ use crate::U256;
+ use alloc::vec::Vec;
+
+ #[test]
+ fn jsonu256_deserialize() {
+ let deserialized: Vec =
+ serde_json::from_str(r#"["","0", "0x","10",10,"0x10"]"#).unwrap();
+ assert_eq!(
+ deserialized,
+ vec![
+ JsonU256(U256::ZERO),
+ JsonU256(U256::ZERO),
+ JsonU256(U256::ZERO),
+ JsonU256(U256::from(10)),
+ JsonU256(U256::from(10)),
+ JsonU256(U256::from(16)),
+ ]
+ );
+ }
+
+ #[test]
+ #[ignore = "TODO: Uint serde"]
+ fn jsonu256_serialize() {
+ let data = JsonU256(U256::from(16));
+ let serialized = serde_json::to_string(&data).unwrap();
+
+ assert_eq!(serialized, r#""0x10""#);
+ }
+}
diff --git a/crates/primitives/src/serde/mod.rs b/crates/primitives/src/serde/mod.rs
new file mode 100644
index 0000000000..56afa08418
--- /dev/null
+++ b/crates/primitives/src/serde/mod.rs
@@ -0,0 +1,56 @@
+//! Various serde utilities
+
+mod storage_key;
+pub use storage_key::*;
+
+mod jsonu256;
+pub use jsonu256::*;
+
+mod num;
+pub use num::*;
+
+/// serde functions for handling primitive `u64` as [U64](crate::U64)
+pub mod u64_hex {
+ use crate::U64;
+ use serde::{Deserialize, Deserializer, Serialize, Serializer};
+
+ /// Deserializes an `u64` from [U64] accepting a hex quantity string with
+ /// optional 0x prefix
+ pub fn deserialize<'de, D>(deserializer: D) -> Result
+ where
+ D: Deserializer<'de>,
+ {
+ U64::deserialize(deserializer).map(|val| val.into_limbs()[0])
+ }
+
+ /// Serializes u64 as hex string
+ pub fn serialize(value: &u64, s: S) -> Result {
+ // TODO: Uint serde
+ U64::from(*value).serialize(s)
+ }
+}
+
+pub use hex::serde as hex_bytes;
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use serde::{Deserialize, Serialize};
+
+ #[test]
+ #[ignore = "TODO: Uint serde"]
+ fn test_hex_u64() {
+ #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
+ struct Value {
+ #[serde(with = "u64_hex")]
+ inner: u64,
+ }
+
+ let val = Value { inner: 1000 };
+ let s = serde_json::to_string(&val).unwrap();
+ assert_eq!(s, "{\"inner\":\"0x3e8\"}");
+
+ let deserialized: Value = serde_json::from_str(&s).unwrap();
+ assert_eq!(val, deserialized);
+ }
+}
diff --git a/crates/primitives/src/serde/num.rs b/crates/primitives/src/serde/num.rs
new file mode 100644
index 0000000000..81c79dbe52
--- /dev/null
+++ b/crates/primitives/src/serde/num.rs
@@ -0,0 +1,188 @@
+//! Numeric helpers
+
+use crate::U256;
+use alloc::string::String;
+use core::fmt;
+use serde::{
+ de::{self, Visitor},
+ Deserialize, Deserializer,
+};
+
+// TODO: Remove once ruint supports serde with both numbers and strings.
+/// Helper type to parse a numeric value which can be stringified.
+///
+/// Use [`deserialize_stringified_numeric`] and
+/// [`deserialize_stringified_numeric_opt`] instead.
+pub struct StringifiedNumeric(pub U256);
+
+impl<'de> Deserialize<'de> for StringifiedNumeric {
+ fn deserialize>(deserializer: D) -> Result {
+ deserializer.deserialize_any(StringifiedNumericVisitor)
+ }
+}
+
+struct StringifiedNumericVisitor;
+
+impl Visitor<'_> for StringifiedNumericVisitor {
+ type Value = StringifiedNumeric;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("a stringified numeric value")
+ }
+
+ fn visit_u64(self, v: u64) -> Result {
+ Ok(StringifiedNumeric(U256::from(v)))
+ }
+
+ fn visit_u128(self, v: u128) -> Result {
+ Ok(StringifiedNumeric(U256::from(v)))
+ }
+
+ fn visit_str(self, v: &str) -> Result {
+ v.parse()
+ .map(StringifiedNumeric)
+ .map_err(serde::de::Error::custom)
+ }
+
+ fn visit_string(self, v: String) -> Result {
+ self.visit_str(&v)
+ }
+}
+
+/// Supports parsing numbers as strings
+///
+/// See
+pub fn deserialize_stringified_numeric<'de, D>(deserializer: D) -> Result
+where
+ D: Deserializer<'de>,
+{
+ StringifiedNumeric::deserialize(deserializer).map(|x| x.0)
+}
+
+/// Supports parsing numbers as strings
+///
+/// See
+pub fn deserialize_stringified_numeric_opt<'de, D>(
+ deserializer: D,
+) -> Result