From b464e352c8d3e3222ca05c91d87d6bcc03a28d11 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 19 May 2023 23:22:13 +0200 Subject: [PATCH 01/20] feat: improve primitives --- Cargo.toml | 10 +- crates/primitives/Cargo.toml | 28 ++- crates/primitives/src/{signed => }/aliases.rs | 34 ++- crates/primitives/src/bits/bloom.rs | 203 ++++++++++++++++ crates/primitives/src/bits/fixed.rs | 144 +++++++---- crates/primitives/src/bits/impl_core.rs | 1 + crates/primitives/src/bits/macros.rs | 26 +- crates/primitives/src/bits/mod.rs | 14 ++ crates/primitives/src/bytes/mod.rs | 230 ++++++++++++++++++ crates/primitives/src/bytes/rlp.rs | 18 ++ crates/primitives/src/lib.rs | 32 +-- crates/primitives/src/signed/int.rs | 230 +++++++----------- crates/primitives/src/signed/mod.rs | 9 +- crates/primitives/src/signed/ops.rs | 40 +-- crates/primitives/src/signed/utils.rs | 5 +- crates/primitives/src/utils.rs | 167 ++++++++++++- crates/rlp-derive/src/lib.rs | 4 - crates/rlp/Cargo.toml | 1 + crates/rlp/src/encode.rs | 22 ++ crates/rlp/src/lib.rs | 6 +- crates/sol-types/src/coder/impl_core.rs | 12 +- crates/sol-types/src/lib.rs | 4 - crates/sol-types/src/types/data_type.rs | 2 +- crates/sol-types/tests/doc_structs.rs | 2 +- crates/sol-types/tests/doc_types.rs | 2 +- crates/sol-types/tests/sol.rs | 12 +- 26 files changed, 972 insertions(+), 286 deletions(-) rename crates/primitives/src/{signed => }/aliases.rs (74%) create mode 100644 crates/primitives/src/bits/bloom.rs create mode 120000 crates/primitives/src/bits/impl_core.rs create mode 100644 crates/primitives/src/bytes/mod.rs create mode 100644 crates/primitives/src/bytes/rlp.rs diff --git a/Cargo.toml b/Cargo.toml index 61d0d5fa84..28774e46ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,13 +31,17 @@ proc-macro2 = "1.0" quote = "1.0" syn = "2.0" +derive_more = "0.99" +hex-literal = "0.4" +strum = { version = "0.24", features = ["derive"] } +num_enum = "0.6" + # misc 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-literal = "0.4" -proptest = { version = "1.1", default-features = false } +hex = { package = "const-hex", version = ">=1.5", default-features = false, features = ["alloc"] } +proptest = "1.1" 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..0d76ba7305 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -18,31 +18,35 @@ repository.workspace = true ruint = { workspace = true, features = ["rlp", "serde"] } # utility -derive_more = "0.99" -tiny-keccak = { workspace = true, features = ["keccak"] } +bytes.workspace = true +getrandom = "0.2" hex.workspace = true itoa = "1" +tiny-keccak = { workspace = true, features = ["keccak"] } -# optional -serde = { workspace = true, features = ["derive"], optional = true } +# macros +derive_more.workspace = true -# rlp support +# rlp ethers-rlp = { workspace = true, optional = true } -bytes = { workspace = true, optional = true } -# prop tests +# optional +serde = { workspace = true, features = ["derive"], optional = true } + +# arbitrary arbitrary = { workspace = true, features = ["derive"], optional = true } proptest = { workspace = true, optional = true } proptest-derive = { workspace = true, optional = true } [dev-dependencies] -hex-literal = "0.4" +hex-literal.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"] +default = ["std", "rlp", "serde"] +std = ["bytes/std", "hex/std", "ethers-rlp?/std", "proptest?/std", "serde/std"] +rlp = ["dep:ethers-rlp"] +serde = ["dep:serde", "bytes/serde", "hex/serde", "ruint/serde"] arbitrary = [ "ruint/arbitrary", "ruint/proptest", diff --git a/crates/primitives/src/signed/aliases.rs b/crates/primitives/src/aliases.rs similarity index 74% rename from crates/primitives/src/signed/aliases.rs rename to crates/primitives/src/aliases.rs index bb5679501a..adfcc31b09 100644 --- a/crates/primitives/src/signed/aliases.rs +++ b/crates/primitives/src/aliases.rs @@ -1,4 +1,8 @@ -use super::Signed; +//! Type aliases for common primitive types. + +use crate::{Signed, B256}; + +pub use ruint::aliases::*; /// The 0-bit signed integer type, capable of representing 0. pub type I0 = Signed<0, 0>; @@ -101,3 +105,31 @@ pub type I248 = Signed<248, 4>; /// 256-bit signed integer type. pub type I256 = Signed<256, 4>; + +/// A block hash. +pub type BlockHash = B256; + +/// A block number. +pub type BlockNumber = u64; + +/// A transaction hash is a kecack hash of an RLP encoded signed transaction. +pub type TxHash = B256; + +/// The sequence number of all existing transactions. +pub type TxNumber = u64; + +/// The index of transaction in a block. +pub type TxIndex = u64; + +/// Chain identifier type (introduced in EIP-155). +pub type ChainId = u64; + +/// An account storage key. +pub type StorageKey = B256; + +/// An account storage value. +pub type StorageValue = U256; + +/// Solidity contract functions are addressed using the first four byte of the +/// Keccak-256 hash of their signature +pub type Selector = [u8; 4]; diff --git a/crates/primitives/src/bits/bloom.rs b/crates/primitives/src/bits/bloom.rs new file mode 100644 index 0000000000..b215ff8381 --- /dev/null +++ b/crates/primitives/src/bits/bloom.rs @@ -0,0 +1,203 @@ +//! Bloom type. +//! +//! Adapted from + +use crate::{keccak256, FixedBytes}; +use alloc::borrow::Cow; +use core::mem; + +/// Length of bloom filter used for Ethereum. +pub const BLOOM_BITS: u32 = 3; +/// Size of the bloom filter in bytes. +pub const BLOOM_SIZE: usize = 256; + +/// A 256-byte Ethereum bloom filter. +pub type Bloom = FixedBytes<256>; + +/// BloomInput to the [`Bloom::accrue`] method. +#[derive(Debug)] +pub enum BloomInput<'a> { + /// Raw input to be hashed. + Raw(&'a [u8]), + /// Already hashed input. + Hash(&'a [u8; 32]), +} + +impl PartialEq> for Bloom { + fn eq(&self, other: &BloomRef<'_>) -> bool { + self.0 == *other.0 + } +} + +impl From> for Bloom { + fn from(input: BloomInput<'_>) -> Bloom { + let mut bloom = Bloom::default(); + bloom.accrue(input); + bloom + } +} + +impl Bloom { + /// Returns the underlying data. + #[inline] + pub const fn data(&self) -> &[u8; BLOOM_SIZE] { + &self.0 + } + + /// Returns whether the bloom filter contains the given input. + pub fn contains_input(&self, input: BloomInput<'_>) -> bool { + let bloom: Bloom = input.into(); + self.contains_bloom(&bloom) + } + + /// Returns whether the bloom filter contains the given input. + pub fn contains_bloom<'a, B: Into>>(&self, bloom: B) -> bool { + self.contains_bloom_ref(bloom.into()) + } + + fn contains_bloom_ref(&self, bloom: BloomRef<'_>) -> bool { + let self_ref: BloomRef<'_> = self.into(); + self_ref.contains_bloom(bloom) + } + + /// Accrues the input into the bloom filter. + pub fn accrue(&mut self, input: BloomInput<'_>) { + let p = BLOOM_BITS; + + let m = self.0.len(); + let bloom_bits = m * 8; + let mask = bloom_bits - 1; + let bloom_bytes = (log2(bloom_bits) + 7) / 8; + + let hash = match input { + BloomInput::Raw(raw) => Cow::Owned(keccak256(raw).0), + BloomInput::Hash(hash) => Cow::Borrowed(hash), + }; + + // must be a power of 2 + assert_eq!(m & (m - 1), 0); + // out of range + assert!(p * bloom_bytes <= hash.len() as u32); + + let mut ptr = 0; + + for _ in 0..3 { + let mut index = 0_usize; + for _ in 0..bloom_bytes { + index = (index << 8) | hash[ptr] as usize; + ptr += 1; + } + index &= mask; + self.0[m - 1 - index / 8] |= 1 << (index % 8); + } + } + + /// Accrues the input into the bloom filter. + pub fn accrue_bloom<'a, B: Into>>(&mut self, bloom: B) { + let bloom_ref: BloomRef<'_> = bloom.into(); + for i in 0..BLOOM_SIZE { + self.0[i] |= bloom_ref.0[i]; + } + } + + /// See Section 4.3.1 "Transaction Receipt" of the Ethereum Yellow Paper. + pub fn m3_2048(&mut self, x: &[u8]) { + let hash = keccak256(x); + let h: &[u8; 32] = hash.as_ref(); + for i in [0, 2, 4] { + let bit = (h[i + 1] as usize + ((h[i] as usize) << 8)) & 0x7FF; + self.0[BLOOM_SIZE - 1 - bit / 8] |= 1 << (bit % 8); + } + } +} + +/// A reference to a bloom filter. Can be +#[derive(Clone, Copy, Debug)] +pub struct BloomRef<'a>(pub &'a [u8; BLOOM_SIZE]); + +impl<'a> BloomRef<'a> { + /// Returns whether the bloom filter contains the given input. + pub fn contains_bloom<'b, B: Into>>(self, bloom: B) -> bool { + let bloom_ref: BloomRef<'_> = bloom.into(); + self.0.iter().zip(bloom_ref.0).all(|(&a, &b)| (a & b) == b) + } + + /// Returns the underlying data. + #[inline] + pub const fn data(self) -> &'a [u8; BLOOM_SIZE] { + self.0 + } + + /// Returns `true` if bloom only consists only of `0`. + #[inline] + pub fn is_empty(self) -> bool { + self.0.iter().all(|x| *x == 0) + } +} + +impl<'a> From<&'a [u8; BLOOM_SIZE]> for BloomRef<'a> { + fn from(data: &'a [u8; BLOOM_SIZE]) -> Self { + Self(data) + } +} + +impl<'a> From<&'a Bloom> for BloomRef<'a> { + fn from(bloom: &'a Bloom) -> Self { + Self(&bloom.0) + } +} + +#[inline] +fn log2(x: usize) -> u32 { + if x <= 1 { + return 0 + } + + let n = x.leading_zeros(); + mem::size_of::() as u32 * 8 - n +} + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + + #[test] + fn works() { + let bloom: Bloom = hex!( + "00000000000000000000000000000000 + 00000000100000000000000000000000 + 00000000000000000000000000000000 + 00000000000000000000000000000000 + 00000000000000000000000000000000 + 00000000000000000000000000000000 + 00000002020000000000000000000000 + 00000000000000000000000800000000 + 10000000000000000000000000000000 + 00000000000000000000001000000000 + 00000000000000000000000000000000 + 00000000000000000000000000000000 + 00000000000000000000000000000000 + 00000000000000000000000000000000 + 00000000000000000000000000000000 + 00000000000000000000000000000000" + ) + .into(); + let address = hex!("ef2d6d194084c2de36e0dabfce45d046b37d1106"); + let topic = hex!("02c69be41d0b7e40352fc85be1cd65eb03d40ef8427a0ca4596b1ead9a00e9fc"); + + let mut my_bloom = Bloom::default(); + assert!(!my_bloom.contains_input(BloomInput::Raw(&address))); + assert!(!my_bloom.contains_input(BloomInput::Raw(&topic))); + + my_bloom.accrue(BloomInput::Raw(&address)); + assert!(my_bloom.contains_input(BloomInput::Raw(&address))); + assert!(!my_bloom.contains_input(BloomInput::Raw(&topic))); + + my_bloom.accrue(BloomInput::Raw(&topic)); + assert!(my_bloom.contains_input(BloomInput::Raw(&address))); + assert!(my_bloom.contains_input(BloomInput::Raw(&topic))); + + assert_eq!(my_bloom, bloom); + } +} diff --git a/crates/primitives/src/bits/fixed.rs b/crates/primitives/src/bits/fixed.rs index 0ff2114246..be61c0c1e3 100644 --- a/crates/primitives/src/bits/fixed.rs +++ b/crates/primitives/src/bits/fixed.rs @@ -1,5 +1,8 @@ -use core::{fmt, ops, str}; -use derive_more::{AsMut, AsRef, Deref, DerefMut, From, Index, IndexMut}; +use core::{ + borrow::{Borrow, BorrowMut}, + fmt, ops, str, +}; +use derive_more::{Deref, DerefMut, From, Index, IndexMut}; /// A bytearray of fixed length. /// @@ -13,27 +16,14 @@ use derive_more::{AsMut, AsRef, Deref, DerefMut, From, Index, IndexMut}; derive(arbitrary::Arbitrary, proptest_derive::Arbitrary) )] #[derive( - AsRef, - AsMut, - Deref, - DerefMut, - From, - Hash, - Copy, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Index, - IndexMut, + Deref, DerefMut, From, Hash, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Index, IndexMut, )] #[repr(transparent)] pub struct FixedBytes(pub [u8; N]); impl Default for FixedBytes { fn default() -> Self { - FixedBytes([0; N]) + Self::ZERO } } @@ -46,7 +36,7 @@ impl<'a, const N: usize> From<&'a [u8; N]> for FixedBytes { /// The given bytes are interpreted in big endian order. #[inline] fn from(bytes: &'a [u8; N]) -> Self { - FixedBytes(*bytes) + Self(*bytes) } } @@ -59,7 +49,7 @@ impl<'a, const N: usize> From<&'a mut [u8; N]> for FixedBytes { /// The given bytes are interpreted in big endian order. #[inline] fn from(bytes: &'a mut [u8; N]) -> Self { - FixedBytes(*bytes) + Self(*bytes) } } @@ -70,27 +60,90 @@ impl From> for [u8; N] { } } +// Borrow is not implemented for references +macro_rules! borrow_impls { + (impl Borrow<$t:ty> for $($b:ty),+) => {$( + impl Borrow<$t> for $b { + #[inline] + fn borrow(&self) -> &$t { + &self.0 + } + } + )+}; + + (impl BorrowMut<$t:ty> for $($b:ty),+) => {$( + impl BorrowMut<$t> for $b { + #[inline] + fn borrow_mut(&mut self) -> &mut $t { + &mut self.0 + } + } + )+}; +} + +borrow_impls!(impl Borrow<[u8]> for FixedBytes, &mut FixedBytes, &FixedBytes); +borrow_impls!(impl Borrow<[u8; N]> for FixedBytes, &mut FixedBytes, &FixedBytes); +borrow_impls!(impl BorrowMut<[u8]> for FixedBytes, &mut FixedBytes); +borrow_impls!(impl BorrowMut<[u8; N]> for FixedBytes, &mut FixedBytes); + impl AsRef<[u8]> for FixedBytes { #[inline] fn as_ref(&self) -> &[u8] { - self.as_bytes() + &self.0 } } impl AsMut<[u8]> for FixedBytes { #[inline] fn as_mut(&mut self) -> &mut [u8] { - self.as_bytes_mut() + &mut self.0 + } +} + +impl AsRef<[u8; N]> for FixedBytes { + #[inline] + fn as_ref(&self) -> &[u8; N] { + &self.0 + } +} + +impl AsMut<[u8; N]> for FixedBytes { + #[inline] + fn as_mut(&mut self) -> &mut [u8; N] { + &mut self.0 } } impl FixedBytes { /// Array of Zero bytes. - pub const ZERO: FixedBytes = FixedBytes([0u8; N]); + pub const ZERO: Self = Self([0u8; N]); /// Instantiates a new fixed hash from the given bytes array. + #[inline] pub const fn new(bytes: [u8; N]) -> Self { - FixedBytes(bytes) + Self(bytes) + } + + /// Utility function to create a fixed hash with the first byte set to `x`. + #[inline] + pub fn with_first_byte(x: u8) -> Self { + let mut bytes = [0u8; N]; + bytes[0] = x; + Self(bytes) + } + + /// Instantiates a new fixed hash with cryptographically random content. + #[inline] + pub fn random() -> Self { + Self::try_random().unwrap() + } + + /// Instantiates a new fixed hash with cryptographically random content. + pub fn try_random() -> Result { + let mut bytes: [_; N] = super::impl_core::uninit_array(); + getrandom::getrandom_uninit(&mut bytes)?; + // SAFETY: The array is initialized by getrandom_uninit. + Ok(Self(unsafe { super::impl_core::array_assume_init(bytes) })) } /// Concatenate two `FixedBytes`. Due to rust constraints, the user must @@ -99,31 +152,24 @@ impl FixedBytes { self, other: FixedBytes, ) -> FixedBytes { - assert!(N + M == Z, "Output size must be sum of input sizes"); + assert!( + N + M == Z, + "Output size `Z` must equal the sum of the input sizes `M` and `N`" + ); let mut result = [0u8; Z]; - - let i = 0; - loop { + let mut i = 0; + while i < Z { result[i] = if i >= N { other.0[i - N] } else { self.0[i] }; - if i == Z { - break - } + i += 1; } - FixedBytes(result) } /// Returns a new fixed hash where all bits are set to the given byte. #[inline] - pub const fn repeat_byte(byte: u8) -> FixedBytes { - FixedBytes([byte; N]) - } - - /// Returns a new zero-initialized fixed hash. - #[inline] - pub const fn zero() -> FixedBytes { - FixedBytes::repeat_byte(0u8) + pub const fn repeat_byte(byte: u8) -> Self { + Self([byte; N]) } /// Returns the size of this hash in bytes. @@ -165,13 +211,13 @@ impl FixedBytes { /// Returns a constant raw pointer to the value. #[inline] pub const fn as_ptr(&self) -> *const u8 { - self.as_bytes().as_ptr() + self.0.as_ptr() } /// Returns a mutable raw pointer to the value. #[inline] pub fn as_mut_ptr(&mut self) -> *mut u8 { - self.as_bytes_mut().as_mut_ptr() + self.0.as_mut_ptr() } /// Create a new fixed-hash from the given slice `src`. @@ -185,9 +231,9 @@ impl FixedBytes { /// If the length of `src` and the number of bytes in `Self` do not match. #[track_caller] pub fn from_slice(src: &[u8]) -> Self { - let mut ret = Self::zero(); - ret.copy_from_slice(src); - ret + let mut bytes = [0; N]; + bytes.copy_from_slice(src); + Self(bytes) } /// Returns `true` if all bits set in `b` are also set in `self`. @@ -407,6 +453,16 @@ mod tests { )+}; } + #[test] + fn concat_const() { + const A: FixedBytes<2> = FixedBytes(hex!("0123")); + const B: FixedBytes<2> = FixedBytes(hex!("4567")); + const EXPECTED: FixedBytes<4> = FixedBytes(hex!("01234567")); + const ACTUAL: FixedBytes<4> = A.concat_const(B); + + assert_eq!(ACTUAL, EXPECTED); + } + #[test] fn display() { test_fmt! { diff --git a/crates/primitives/src/bits/impl_core.rs b/crates/primitives/src/bits/impl_core.rs new file mode 120000 index 0000000000..805446a299 --- /dev/null +++ b/crates/primitives/src/bits/impl_core.rs @@ -0,0 +1 @@ +../../../sol-types/src/coder/impl_core.rs \ No newline at end of file diff --git a/crates/primitives/src/bits/macros.rs b/crates/primitives/src/bits/macros.rs index ed10c22556..3f7a11499f 100644 --- a/crates/primitives/src/bits/macros.rs +++ b/crates/primitives/src/bits/macros.rs @@ -20,7 +20,7 @@ macro_rules! wrap_fixed_bytes { $(#[$attrs:meta])* $name:ident<$n:literal> ) => { - wrap_fixed_bytes!( + $crate::wrap_fixed_bytes!( name_str: stringify!($name), num_str: stringify!($n), $(#[$attrs])* @@ -39,7 +39,7 @@ macro_rules! wrap_fixed_bytes { #[doc = $sname] #[doc = " and containing "] #[doc = $sn] - #[doc = " bytes"] + #[doc = " bytes."] #[derive( $crate::derive_more::AsRef, $crate::derive_more::AsMut, @@ -97,20 +97,32 @@ macro_rules! wrap_fixed_bytes { } impl $name { + /// Array of Zero bytes. + pub const ZERO: Self = Self($crate::FixedBytes::ZERO); + /// Returns a new fixed hash from the given bytes array. + #[inline] pub const fn new(bytes: [u8; $n]) -> Self { Self($crate::FixedBytes(bytes)) } + + /// Utility function to create a fixed hash with the first byte set to `x`. + #[inline] + pub fn with_first_byte(x: u8) -> Self { + Self($crate::FixedBytes::with_first_byte(x)) + } + + /// Instantiates a new fixed hash with cryptographically random content. + #[inline] + pub fn random() -> Self { + Self($crate::FixedBytes::random()) + } + /// Returns a new fixed hash where all bits are set to the given byte. #[inline] pub const fn repeat_byte(byte: u8) -> Self { Self($crate::FixedBytes::repeat_byte(byte)) } - /// Returns a new zero-initialized fixed hash. - #[inline] - pub const fn zero() -> Self { - Self($crate::FixedBytes::repeat_byte(0u8)) - } /// Returns the size of this hash in bytes. #[inline] pub const fn len_bytes() -> usize { diff --git a/crates/primitives/src/bits/mod.rs b/crates/primitives/src/bits/mod.rs index 7cca3148ab..ec9ecaf257 100644 --- a/crates/primitives/src/bits/mod.rs +++ b/crates/primitives/src/bits/mod.rs @@ -1,6 +1,9 @@ mod address; pub use address::{Address, AddressError}; +mod bloom; +pub use bloom::{Bloom, BloomInput, BloomRef, BLOOM_BITS, BLOOM_SIZE}; + mod fixed; pub use fixed::FixedBytes; @@ -13,9 +16,20 @@ mod serialize; #[cfg(feature = "rlp")] mod rlp; +mod impl_core; + +/// 8-byte fixed array type. +pub type B64 = FixedBytes<8>; + +/// 16-byte fixed array type. +pub type B128 = FixedBytes<16>; + /// 32-byte fixed array type. pub type B256 = FixedBytes<32>; +/// 64-byte fixed array type. +pub type B512 = FixedBytes<64>; + impl From for B256 { #[inline] fn from(value: crate::U256) -> Self { diff --git a/crates/primitives/src/bytes/mod.rs b/crates/primitives/src/bytes/mod.rs new file mode 100644 index 0000000000..6a2cc93b2d --- /dev/null +++ b/crates/primitives/src/bytes/mod.rs @@ -0,0 +1,230 @@ +use alloc::{string::String, vec::Vec}; +use core::{borrow::Borrow, fmt, ops::Deref}; + +#[cfg(feature = "rlp")] +mod rlp; + +/// Wrapper type around Bytes to deserialize/serialize "0x" prefixed ethereum +/// hex strings. +#[derive(Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Bytes(#[cfg_attr(serde, serde(with = "hex"))] pub bytes::Bytes); + +impl fmt::Debug for Bytes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Bytes(")?; + f.write_str(&self.hex_encode())?; + f.write_str(")") + } +} + +impl fmt::Display for Bytes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.hex_encode()) + } +} + +impl fmt::LowerHex for Bytes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.hex_encode()) + } +} + +impl Deref for Bytes { + type Target = [u8]; + + #[inline] + fn deref(&self) -> &[u8] { + self.as_ref() + } +} + +impl AsRef<[u8]> for Bytes { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl Borrow<[u8]> for Bytes { + fn borrow(&self) -> &[u8] { + self.as_ref() + } +} + +impl FromIterator for Bytes { + fn from_iter>(iter: T) -> Self { + Self(iter.into_iter().collect::()) + } +} + +impl<'a> FromIterator<&'a u8> for Bytes { + fn from_iter>(iter: T) -> Self { + Self(iter.into_iter().copied().collect::()) + } +} + +impl IntoIterator for Bytes { + type Item = u8; + type IntoIter = bytes::buf::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a> IntoIterator for &'a Bytes { + type Item = &'a u8; + type IntoIter = core::slice::Iter<'a, u8>; + + fn into_iter(self) -> Self::IntoIter { + self.as_ref().iter() + } +} + +impl From for Bytes { + fn from(src: bytes::Bytes) -> Self { + Self(src) + } +} + +impl From> for Bytes { + fn from(src: Vec) -> Self { + Self(src.into()) + } +} + +impl From<[u8; N]> for Bytes { + fn from(src: [u8; N]) -> Self { + src.to_vec().into() + } +} + +impl<'a, const N: usize> From<&'a [u8; N]> for Bytes { + fn from(src: &'a [u8; N]) -> Self { + src.to_vec().into() + } +} + +impl From<&'static [u8]> for Bytes { + fn from(value: &'static [u8]) -> Self { + Self(value.into()) + } +} + +impl From<&'static str> for Bytes { + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +impl PartialEq<[u8]> for Bytes { + fn eq(&self, other: &[u8]) -> bool { + self[..] == *other + } +} + +impl PartialEq for [u8] { + fn eq(&self, other: &Bytes) -> bool { + *self == other[..] + } +} + +impl PartialEq> for Bytes { + fn eq(&self, other: &Vec) -> bool { + self[..] == other[..] + } +} + +impl PartialEq for Vec { + fn eq(&self, other: &Bytes) -> bool { + *other == *self + } +} + +impl PartialEq for Bytes { + fn eq(&self, other: &bytes::Bytes) -> bool { + other == self.as_ref() + } +} + +impl core::str::FromStr for Bytes { + type Err = hex::FromHexError; + + #[inline] + fn from_str(value: &str) -> Result { + hex::decode(value).map(Into::into) + } +} + +impl Bytes { + /// Creates a new empty `Bytes`. + /// + /// This will not allocate and the returned `Bytes` handle will be empty. + /// + /// # Examples + /// + /// ``` + /// use ethers_primitives::Bytes; + /// + /// let b = Bytes::new(); + /// assert_eq!(&b[..], b""); + /// ``` + #[inline] + pub const fn new() -> Self { + Self(bytes::Bytes::new()) + } + + /// Creates a new `Bytes` from a static slice. + /// + /// The returned `Bytes` will point directly to the static slice. There is + /// no allocating or copying. + /// + /// # Examples + /// + /// ``` + /// use ethers_primitives::Bytes; + /// + /// let b = Bytes::from_static(b"hello"); + /// assert_eq!(&b[..], b"hello"); + /// ``` + #[inline] + pub const fn from_static(bytes: &'static [u8]) -> Self { + Self(bytes::Bytes::from_static(bytes)) + } + + fn hex_encode(&self) -> String { + hex::encode_prefixed(self.0.as_ref()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse() { + assert_eq!( + "1213".parse::().unwrap(), + hex::decode("1213").unwrap() + ); + assert_eq!( + "0x1213".parse::().unwrap(), + hex::decode("0x1213").unwrap() + ); + } + + #[test] + fn hex() { + let b = Bytes::from_static(&[1, 35, 69, 103, 137, 171, 205, 239]); + let expected = "0x0123456789abcdef"; + assert_eq!(format!("{b:x}"), expected); + assert_eq!(format!("{b}"), expected); + } + + #[test] + fn debug() { + let b = Bytes::from(vec![1, 35, 69, 103, 137, 171, 205, 239]); + assert_eq!(format!("{b:?}"), "Bytes(0x0123456789abcdef)"); + assert_eq!(format!("{b:#?}"), "Bytes(0x0123456789abcdef)"); + } +} diff --git a/crates/primitives/src/bytes/rlp.rs b/crates/primitives/src/bytes/rlp.rs new file mode 100644 index 0000000000..60702bca52 --- /dev/null +++ b/crates/primitives/src/bytes/rlp.rs @@ -0,0 +1,18 @@ +use super::Bytes; +use ethers_rlp::{Decodable, Encodable}; + +impl Encodable for Bytes { + fn length(&self) -> usize { + self.0.length() + } + + fn encode(&self, out: &mut dyn bytes::BufMut) { + self.0.encode(out) + } +} + +impl Decodable for Bytes { + fn decode(buf: &mut &[u8]) -> Result { + Ok(Self(bytes::Bytes::decode(buf)?)) + } +} diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index e785fb574a..e61d8c4104 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -5,33 +5,35 @@ clippy::missing_const_for_fn )] #![deny(unused_must_use, rust_2018_idioms)] -#![doc(test( - no_crate_inject, - attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) -))] #![cfg_attr(not(feature = "std"), no_std)] #![doc = include_str!("../README.md")] #[macro_use] extern crate alloc; +pub mod aliases; +pub use aliases::{ + BlockHash, BlockNumber, ChainId, Selector, StorageKey, StorageValue, TxHash, TxIndex, TxNumber, + I128, I16, I256, I32, I64, I8, U128, U16, U256, U32, U512, U64, U8, +}; + mod bits; -pub use bits::{Address, AddressError, FixedBytes, B256}; +pub use bits::{ + Address, AddressError, Bloom, BloomInput, BloomRef, FixedBytes, B128, B256, B512, B64, + BLOOM_BITS, BLOOM_SIZE, +}; + +mod bytes; +pub use self::bytes::Bytes; mod signed; -pub use signed::{ - aliases::{self, I160, I256}, - const_eq, BigIntConversionError, ParseSignedError, Sign, Signed, -}; +pub use signed::{const_eq, BigIntConversionError, ParseSignedError, Sign, Signed}; mod utils; -pub use utils::{keccak256, Hasher, Keccak}; +pub use utils::*; -// ruint reexports -pub use ruint::{ - aliases::{B128 as H128, B64 as H64, U128, U256, U64}, - uint, -}; +pub use ruint::{self, uint, Uint}; +// Not public API. #[doc(hidden)] pub use derive_more; diff --git a/crates/primitives/src/signed/int.rs b/crates/primitives/src/signed/int.rs index 1334f784d0..075735ae8c 100644 --- a/crates/primitives/src/signed/int.rs +++ b/crates/primitives/src/signed/int.rs @@ -54,9 +54,9 @@ use ruint::Uint; /// assert!(e > a); /// /// // We have some useful constants too -/// assert_eq!(I256::zero(), I256::unchecked_from(0)); -/// assert_eq!(I256::one(), I256::unchecked_from(1)); -/// assert_eq!(I256::minus_one(), I256::unchecked_from(-1)); +/// assert_eq!(I256::ZERO, I256::unchecked_from(0)); +/// assert_eq!(I256::ONE, I256::unchecked_from(1)); +/// assert_eq!(I256::MINUS_ONE, I256::unchecked_from(-1)); /// ``` /// /// # Note on [`std::str::FromStr`] @@ -134,36 +134,6 @@ impl Signed { /// Minus one (multiplicative inverse) of this type. pub const MINUS_ONE: Self = Self(Uint::::MAX); - /// Zero (additive iden. - #[inline(always)] - pub const fn zero() -> Self { - Self::ZERO - } - - /// One (multiplicative identity) of this type. - #[inline(always)] - pub const fn one() -> Self { - Self::ONE - } - - /// Minus one (multiplicative inverse) of this type. - #[inline(always)] - pub const fn minus_one() -> Self { - Self::MINUS_ONE - } - - /// The maximum value which can be inhabited by this type. - #[inline(always)] - pub const fn max_value() -> Self { - Self::MAX - } - - /// The minimum value which can be inhabited by this type. - #[inline(always)] - pub const fn min_value() -> Self { - Self::MIN - } - /// Coerces an unsigned integer into a signed one. If the unsigned integer /// is greater than the greater than or equal to `1 << 255`, then the result /// will overflow into a negative value. @@ -511,10 +481,8 @@ impl<'de, const BITS: usize, const LIMBS: usize> serde::Deserialize<'de> for Sig #[cfg(all(test, feature = "std"))] mod tests { use super::*; - use crate::{ - aliases::{I0, I1, I128, I160, I192, I256}, - BigIntConversionError, ParseSignedError, - }; + use crate::{aliases::*, BigIntConversionError, ParseSignedError}; + use alloc::string::ToString; use ruint::{ aliases::{U0, U1, U128, U160, U256}, BaseConvertError, ParseError, @@ -532,17 +500,17 @@ mod tests { fn identities() { macro_rules! test_identities { ($signed:ty, $max:literal, $min:literal) => { - assert_eq!(<$signed>::zero().to_string(), "0"); - assert_eq!(<$signed>::one().to_string(), "1"); - assert_eq!(<$signed>::minus_one().to_string(), "-1"); - assert_eq!(<$signed>::max_value().to_string(), $max); - assert_eq!(<$signed>::min_value().to_string(), $min); + assert_eq!(<$signed>::ZERO.to_string(), "0"); + assert_eq!(<$signed>::ONE.to_string(), "1"); + assert_eq!(<$signed>::MINUS_ONE.to_string(), "-1"); + assert_eq!(<$signed>::MAX.to_string(), $max); + assert_eq!(<$signed>::MIN.to_string(), $min); }; } - assert_eq!(I0::zero().to_string(), "0"); - assert_eq!(I1::zero().to_string(), "0"); - assert_eq!(I1::one().to_string(), "-1"); + assert_eq!(I0::ZERO.to_string(), "0"); + assert_eq!(I1::ZERO.to_string(), "0"); + assert_eq!(I1::ONE.to_string(), "-1"); test_identities!( I96, @@ -806,25 +774,25 @@ mod tests { assert!(!<$i_struct>::MAX.is_negative()); assert!(!<$i_struct>::MAX.is_zero()); - assert_eq!(<$i_struct>::one().sign(), Sign::Positive); - assert!(<$i_struct>::one().is_positive()); - assert!(!<$i_struct>::one().is_negative()); - assert!(!<$i_struct>::one().is_zero()); + assert_eq!(<$i_struct>::ONE.sign(), Sign::Positive); + assert!(<$i_struct>::ONE.is_positive()); + assert!(!<$i_struct>::ONE.is_negative()); + assert!(!<$i_struct>::ONE.is_zero()); assert_eq!(<$i_struct>::MIN.sign(), Sign::Negative); assert!(!<$i_struct>::MIN.is_positive()); assert!(<$i_struct>::MIN.is_negative()); assert!(!<$i_struct>::MIN.is_zero()); - assert_eq!(<$i_struct>::minus_one().sign(), Sign::Negative); - assert!(!<$i_struct>::minus_one().is_positive()); - assert!(<$i_struct>::minus_one().is_negative()); - assert!(!<$i_struct>::minus_one().is_zero()); + assert_eq!(<$i_struct>::MINUS_ONE.sign(), Sign::Negative); + assert!(!<$i_struct>::MINUS_ONE.is_positive()); + assert!(<$i_struct>::MINUS_ONE.is_negative()); + assert!(!<$i_struct>::MINUS_ONE.is_zero()); - assert_eq!(<$i_struct>::zero().sign(), Sign::Positive); - assert!(!<$i_struct>::zero().is_positive()); - assert!(!<$i_struct>::zero().is_negative()); - assert!(<$i_struct>::zero().is_zero()); + assert_eq!(<$i_struct>::ZERO.sign(), Sign::Positive); + assert!(!<$i_struct>::ZERO.is_positive()); + assert!(!<$i_struct>::ZERO.is_negative()); + assert!(<$i_struct>::ZERO.is_zero()); }; } @@ -855,7 +823,7 @@ mod tests { assert_ne!(negative, negative.abs()); assert_eq!(negative.sign(), Sign::Negative); assert_eq!(negative.abs().sign(), Sign::Positive); - assert_eq!(<$i_struct>::zero().abs(), <$i_struct>::zero()); + assert_eq!(<$i_struct>::ZERO.abs(), <$i_struct>::ZERO); assert_eq!(<$i_struct>::MAX.abs(), <$i_struct>::MAX); assert_eq!((-<$i_struct>::MAX).abs(), <$i_struct>::MAX); assert_eq!(<$i_struct>::MIN.checked_abs(), None); @@ -888,7 +856,7 @@ mod tests { assert_eq!(-positive, negative); assert_eq!(-negative, positive); - assert_eq!(-<$i_struct>::zero(), <$i_struct>::zero()); + assert_eq!(-<$i_struct>::ZERO, <$i_struct>::ZERO); assert_eq!(-(-<$i_struct>::MAX), <$i_struct>::MAX); assert_eq!(<$i_struct>::MIN.checked_neg(), None); }; @@ -921,7 +889,7 @@ mod tests { assert_eq!(<$i_struct>::MAX.bits(), <$i_struct>::BITS as u32); assert_eq!(<$i_struct>::MIN.bits(), <$i_struct>::BITS as u32); - assert_eq!(<$i_struct>::zero().bits(), 0); + assert_eq!(<$i_struct>::ZERO.bits(), 0); }; } @@ -943,14 +911,8 @@ mod tests { fn bit_shift() { macro_rules! run_test { ($i_struct:ty, $u_struct:ty) => { - assert_eq!( - <$i_struct>::one() << <$i_struct>::BITS - 1, - <$i_struct>::MIN - ); - assert_eq!( - <$i_struct>::MIN >> <$i_struct>::BITS - 1, - <$i_struct>::one() - ); + assert_eq!(<$i_struct>::ONE << <$i_struct>::BITS - 1, <$i_struct>::MIN); + assert_eq!(<$i_struct>::MIN >> <$i_struct>::BITS - 1, <$i_struct>::ONE); }; } @@ -987,8 +949,8 @@ mod tests { "1011...1111 >> 253 was not 1111...1110" ); - let value = <$i_struct>::minus_one(); - let expected_result = <$i_struct>::minus_one(); + let value = <$i_struct>::MINUS_ONE; + let expected_result = <$i_struct>::MINUS_ONE; assert_eq!( value.asr(250), expected_result, @@ -999,7 +961,7 @@ mod tests { <$u_struct>::from(2u8).pow(<$u_struct>::from(<$i_struct>::BITS - 2)), ) .neg(); - let expected_result = <$i_struct>::minus_one(); + let expected_result = <$i_struct>::MINUS_ONE; assert_eq!( value.asr(<$i_struct>::BITS - 1), expected_result, @@ -1010,7 +972,7 @@ mod tests { <$u_struct>::from(2u8).pow(<$u_struct>::from(<$i_struct>::BITS - 2)), ) .neg(); - let expected_result = <$i_struct>::minus_one(); + let expected_result = <$i_struct>::MINUS_ONE; assert_eq!( value.asr(1024), expected_result, @@ -1022,7 +984,7 @@ mod tests { assert_eq!(value.asr(5), expected_result, "1024 >> 5 was not 32"); let value = <$i_struct>::MAX; - let expected_result = <$i_struct>::zero(); + let expected_result = <$i_struct>::ZERO; assert_eq!( value.asr(255), expected_result, @@ -1059,11 +1021,11 @@ mod tests { fn arithmetic_shift_left() { macro_rules! run_test { ($i_struct:ty, $u_struct:ty) => { - let value = <$i_struct>::minus_one(); + let value = <$i_struct>::MINUS_ONE; let expected_result = Some(value); assert_eq!(value.asl(0), expected_result, "-1 << 0 was not -1"); - let value = <$i_struct>::minus_one(); + let value = <$i_struct>::MINUS_ONE; let expected_result = None; assert_eq!( value.asl(256), @@ -1071,7 +1033,7 @@ mod tests { "-1 << 256 did not overflow (result should be 0000...0000)" ); - let value = <$i_struct>::minus_one(); + let value = <$i_struct>::MINUS_ONE; let expected_result = Some(<$i_struct>::from_raw( <$u_struct>::from(2u8).pow(<$u_struct>::from(<$i_struct>::BITS - 1)), )); @@ -1097,7 +1059,7 @@ mod tests { "1024 << 245 did not overflow (result should be 1000...0000)" ); - let value = <$i_struct>::zero(); + let value = <$i_struct>::ZERO; let expected_result = Some(value); assert_eq!(value.asl(1024), expected_result, "0 << anything was not 0"); }; @@ -1123,7 +1085,7 @@ mod tests { ($i_struct:ty, $u_struct:ty) => { assert_eq!( <$i_struct>::MIN.overflowing_add(<$i_struct>::MIN), - (<$i_struct>::zero(), true) + (<$i_struct>::ZERO, true) ); assert_eq!( <$i_struct>::MAX.overflowing_add(<$i_struct>::MAX), @@ -1131,34 +1093,28 @@ mod tests { ); assert_eq!( - <$i_struct>::MIN.overflowing_add(<$i_struct>::minus_one()), + <$i_struct>::MIN.overflowing_add(<$i_struct>::MINUS_ONE), (<$i_struct>::MAX, true) ); assert_eq!( - <$i_struct>::MAX.overflowing_add(<$i_struct>::one()), + <$i_struct>::MAX.overflowing_add(<$i_struct>::ONE), (<$i_struct>::MIN, true) ); - assert_eq!( - <$i_struct>::MAX + <$i_struct>::MIN, - <$i_struct>::minus_one() - ); + assert_eq!(<$i_struct>::MAX + <$i_struct>::MIN, <$i_struct>::MINUS_ONE); assert_eq!( <$i_struct>::try_from(2).unwrap() + <$i_struct>::try_from(40).unwrap(), <$i_struct>::try_from(42).unwrap() ); - assert_eq!( - <$i_struct>::zero() + <$i_struct>::zero(), - <$i_struct>::zero() - ); + assert_eq!(<$i_struct>::ZERO + <$i_struct>::ZERO, <$i_struct>::ZERO); assert_eq!( <$i_struct>::MAX.saturating_add(<$i_struct>::MAX), <$i_struct>::MAX ); assert_eq!( - <$i_struct>::MIN.saturating_add(<$i_struct>::minus_one()), + <$i_struct>::MIN.saturating_add(<$i_struct>::MINUS_ONE), <$i_struct>::MIN ); }; @@ -1185,44 +1141,41 @@ mod tests { ($i_struct:ty, $u_struct:ty) => { assert_eq!( <$i_struct>::MIN.overflowing_sub(<$i_struct>::MAX), - (<$i_struct>::one(), true) + (<$i_struct>::ONE, true) ); assert_eq!( <$i_struct>::MAX.overflowing_sub(<$i_struct>::MIN), - (<$i_struct>::minus_one(), true) + (<$i_struct>::MINUS_ONE, true) ); assert_eq!( - <$i_struct>::MIN.overflowing_sub(<$i_struct>::one()), + <$i_struct>::MIN.overflowing_sub(<$i_struct>::ONE), (<$i_struct>::MAX, true) ); assert_eq!( - <$i_struct>::MAX.overflowing_sub(<$i_struct>::minus_one()), + <$i_struct>::MAX.overflowing_sub(<$i_struct>::MINUS_ONE), (<$i_struct>::MIN, true) ); assert_eq!( - <$i_struct>::zero().overflowing_sub(<$i_struct>::MIN), + <$i_struct>::ZERO.overflowing_sub(<$i_struct>::MIN), (<$i_struct>::MIN, true) ); - assert_eq!(<$i_struct>::MAX - <$i_struct>::MAX, <$i_struct>::zero()); + assert_eq!(<$i_struct>::MAX - <$i_struct>::MAX, <$i_struct>::ZERO); assert_eq!( <$i_struct>::try_from(2).unwrap() - <$i_struct>::try_from(44).unwrap(), <$i_struct>::try_from(-42).unwrap() ); - assert_eq!( - <$i_struct>::zero() - <$i_struct>::zero(), - <$i_struct>::zero() - ); + assert_eq!(<$i_struct>::ZERO - <$i_struct>::ZERO, <$i_struct>::ZERO); assert_eq!( <$i_struct>::MAX.saturating_sub(<$i_struct>::MIN), <$i_struct>::MAX ); assert_eq!( - <$i_struct>::MIN.saturating_sub(<$i_struct>::one()), + <$i_struct>::MIN.saturating_sub(<$i_struct>::ONE), <$i_struct>::MIN ); }; @@ -1257,7 +1210,7 @@ mod tests { (<$i_struct>::MIN, true) ); - assert_eq!(<$i_struct>::MIN * <$i_struct>::one(), <$i_struct>::MIN); + assert_eq!(<$i_struct>::MIN * <$i_struct>::ONE, <$i_struct>::MIN); assert_eq!( <$i_struct>::try_from(2).unwrap() * <$i_struct>::try_from(-21).unwrap(), <$i_struct>::try_from(-42).unwrap() @@ -1289,16 +1242,10 @@ mod tests { <$i_struct>::MIN ); - assert_eq!( - <$i_struct>::zero() * <$i_struct>::zero(), - <$i_struct>::zero() - ); - assert_eq!( - <$i_struct>::one() * <$i_struct>::zero(), - <$i_struct>::zero() - ); - assert_eq!(<$i_struct>::MAX * <$i_struct>::zero(), <$i_struct>::zero()); - assert_eq!(<$i_struct>::MIN * <$i_struct>::zero(), <$i_struct>::zero()); + assert_eq!(<$i_struct>::ZERO * <$i_struct>::ZERO, <$i_struct>::ZERO); + assert_eq!(<$i_struct>::ONE * <$i_struct>::ZERO, <$i_struct>::ZERO); + assert_eq!(<$i_struct>::MAX * <$i_struct>::ZERO, <$i_struct>::ZERO); + assert_eq!(<$i_struct>::MIN * <$i_struct>::ZERO, <$i_struct>::ZERO); }; } @@ -1331,9 +1278,9 @@ mod tests { <$i_struct>::MIN / <$i_struct>::MAX, <$i_struct>::try_from(-1).unwrap() ); - assert_eq!(<$i_struct>::MAX / <$i_struct>::MIN, <$i_struct>::zero()); + assert_eq!(<$i_struct>::MAX / <$i_struct>::MIN, <$i_struct>::ZERO); - assert_eq!(<$i_struct>::MIN / <$i_struct>::one(), <$i_struct>::MIN); + assert_eq!(<$i_struct>::MIN / <$i_struct>::ONE, <$i_struct>::MIN); assert_eq!( <$i_struct>::try_from(-42).unwrap() / <$i_struct>::try_from(-21).unwrap(), <$i_struct>::try_from(2).unwrap() @@ -1379,7 +1326,7 @@ mod tests { macro_rules! run_test { ($i_struct:ty, $u_struct:ty) => { let err = std::panic::catch_unwind(|| { - let _ = <$i_struct>::one() / <$i_struct>::zero(); + let _ = <$i_struct>::ONE / <$i_struct>::ZERO; }); assert!(err.is_err()); }; @@ -1401,30 +1348,27 @@ mod tests { let a = <$i_struct>::try_from(7).unwrap(); let b = <$i_struct>::try_from(4).unwrap(); - assert_eq!(a.div_euclid(b), <$i_struct>::one()); // 7 >= 4 * 1 - assert_eq!(a.div_euclid(-b), <$i_struct>::minus_one()); // 7 >= -4 * -1 + assert_eq!(a.div_euclid(b), <$i_struct>::ONE); // 7 >= 4 * 1 + assert_eq!(a.div_euclid(-b), <$i_struct>::MINUS_ONE); // 7 >= -4 * -1 assert_eq!((-a).div_euclid(b), -<$i_struct>::try_from(2).unwrap()); // -7 >= 4 * -2 assert_eq!((-a).div_euclid(-b), <$i_struct>::try_from(2).unwrap()); // -7 >= -4 * 2 // Overflowing assert_eq!( - <$i_struct>::MIN.overflowing_div_euclid(<$i_struct>::minus_one()), + <$i_struct>::MIN.overflowing_div_euclid(<$i_struct>::MINUS_ONE), (<$i_struct>::MIN, true) ); // Wrapping assert_eq!( - <$i_struct>::MIN.wrapping_div_euclid(<$i_struct>::minus_one()), + <$i_struct>::MIN.wrapping_div_euclid(<$i_struct>::MINUS_ONE), <$i_struct>::MIN ); // // Checked assert_eq!( - <$i_struct>::MIN.checked_div_euclid(<$i_struct>::minus_one()), - None - ); - assert_eq!( - <$i_struct>::one().checked_div_euclid(<$i_struct>::zero()), + <$i_struct>::MIN.checked_div_euclid(<$i_struct>::MINUS_ONE), None ); + assert_eq!(<$i_struct>::ONE.checked_div_euclid(<$i_struct>::ZERO), None); }; } @@ -1451,9 +1395,9 @@ mod tests { let b = <$i_struct>::try_from(4).unwrap(); assert_eq!(a.rem_euclid(b), <$i_struct>::try_from(3).unwrap()); - assert_eq!((-a).rem_euclid(b), <$i_struct>::one()); + assert_eq!((-a).rem_euclid(b), <$i_struct>::ONE); assert_eq!(a.rem_euclid(-b), <$i_struct>::try_from(3).unwrap()); - assert_eq!((-a).rem_euclid(-b), <$i_struct>::one()); + assert_eq!((-a).rem_euclid(-b), <$i_struct>::ONE); // Overflowing assert_eq!( @@ -1461,8 +1405,8 @@ mod tests { (<$i_struct>::try_from(3).unwrap(), false) ); assert_eq!( - <$i_struct>::min_value().overflowing_rem_euclid(<$i_struct>::minus_one()), - (<$i_struct>::zero(), true) + <$i_struct>::MIN.overflowing_rem_euclid(<$i_struct>::MINUS_ONE), + (<$i_struct>::ZERO, true) ); // Wrapping @@ -1470,11 +1414,11 @@ mod tests { <$i_struct>::try_from(100) .unwrap() .wrapping_rem_euclid(<$i_struct>::try_from(10).unwrap()), - <$i_struct>::zero() + <$i_struct>::ZERO ); assert_eq!( - <$i_struct>::min_value().wrapping_rem_euclid(<$i_struct>::minus_one()), - <$i_struct>::zero() + <$i_struct>::MIN.wrapping_rem_euclid(<$i_struct>::MINUS_ONE), + <$i_struct>::ZERO ); // Checked @@ -1482,9 +1426,9 @@ mod tests { a.checked_rem_euclid(b), Some(<$i_struct>::try_from(3).unwrap()) ); - assert_eq!(a.checked_rem_euclid(<$i_struct>::zero()), None); + assert_eq!(a.checked_rem_euclid(<$i_struct>::ZERO), None); assert_eq!( - <$i_struct>::min_value().checked_rem_euclid(<$i_struct>::minus_one()), + <$i_struct>::MIN.checked_rem_euclid(<$i_struct>::MINUS_ONE), None ); }; @@ -1510,13 +1454,13 @@ mod tests { macro_rules! run_test { ($i_struct:ty, $u_struct:ty) => { let err = std::panic::catch_unwind(|| { - let _ = <$i_struct>::one().div_euclid(<$i_struct>::zero()); + let _ = <$i_struct>::ONE.div_euclid(<$i_struct>::ZERO); }); assert!(err.is_err()); let err = std::panic::catch_unwind(|| { assert_eq!( - <$i_struct>::MIN.div_euclid(<$i_struct>::minus_one()), + <$i_struct>::MIN.div_euclid(<$i_struct>::MINUS_ONE), <$i_struct>::MAX ); }); @@ -1539,7 +1483,7 @@ mod tests { macro_rules! run_test { ($i_struct:ty, $u_struct:ty) => { let err = std::panic::catch_unwind(|| { - let _ = <$i_struct>::MIN.div_euclid(<$i_struct>::minus_one()); + let _ = <$i_struct>::MIN.div_euclid(<$i_struct>::MINUS_ONE); }); assert!(err.is_err()); }; @@ -1556,7 +1500,7 @@ mod tests { macro_rules! run_test { ($i_struct:ty, $u_struct:ty) => { let err = std::panic::catch_unwind(|| { - let _ = <$i_struct>::one() % <$i_struct>::zero(); + let _ = <$i_struct>::ONE % <$i_struct>::ZERO; }); assert!(err.is_err()); }; @@ -1579,7 +1523,7 @@ mod tests { // The only case for overflow. assert_eq!( <$i_struct>::MIN.overflowing_rem(<$i_struct>::try_from(-1).unwrap()), - (<$i_struct>::zero(), true) + (<$i_struct>::ZERO, true) ); assert_eq!( <$i_struct>::try_from(-5).unwrap() % <$i_struct>::try_from(-2).unwrap(), @@ -1587,7 +1531,7 @@ mod tests { ); assert_eq!( <$i_struct>::try_from(5).unwrap() % <$i_struct>::try_from(-2).unwrap(), - <$i_struct>::one() + <$i_struct>::ONE ); assert_eq!( <$i_struct>::try_from(-5).unwrap() % <$i_struct>::try_from(2).unwrap(), @@ -1595,7 +1539,7 @@ mod tests { ); assert_eq!( <$i_struct>::try_from(5).unwrap() % <$i_struct>::try_from(2).unwrap(), - <$i_struct>::one() + <$i_struct>::ONE ); assert_eq!( @@ -1603,8 +1547,8 @@ mod tests { None ); assert_eq!( - <$i_struct>::one().checked_rem(<$i_struct>::one()), - Some(<$i_struct>::zero()) + <$i_struct>::ONE.checked_rem(<$i_struct>::ONE), + Some(<$i_struct>::ZERO) ); }; } @@ -1647,8 +1591,8 @@ mod tests { ); assert_eq!( - <$i_struct>::zero().pow(<$u_struct>::from(42)), - <$i_struct>::zero() + <$i_struct>::ZERO.pow(<$u_struct>::from(42)), + <$i_struct>::ZERO ); assert_eq!(<$i_struct>::exp10(18).to_string(), "1000000000000000000"); }; diff --git a/crates/primitives/src/signed/mod.rs b/crates/primitives/src/signed/mod.rs index 505899f1c0..176a5f6efd 100644 --- a/crates/primitives/src/signed/mod.rs +++ b/crates/primitives/src/signed/mod.rs @@ -1,5 +1,8 @@ //! This module contains a 256-bit signed integer implementation. +/// Conversion implementations. +mod conversions; + /// Error types for signed integers. mod errors; pub use errors::{BigIntConversionError, ParseSignedError}; @@ -8,9 +11,6 @@ pub use errors::{BigIntConversionError, ParseSignedError}; mod sign; pub use sign::Sign; -/// Type aliases for signed integers whose bitsize is divisble by 8. -pub mod aliases; - /// Signed integer type wrapping a [`ruint::Uint`]. mod int; pub use int::Signed; @@ -18,9 +18,6 @@ pub use int::Signed; /// Operation implementations. mod ops; -/// Conversion implementations. -mod conversions; - /// Utility functions used in the signed integer implementation. pub(crate) mod utils; pub use utils::const_eq; diff --git a/crates/primitives/src/signed/ops.rs b/crates/primitives/src/signed/ops.rs index 4e777b37ea..dcdc1ac86f 100644 --- a/crates/primitives/src/signed/ops.rs +++ b/crates/primitives/src/signed/ops.rs @@ -331,7 +331,7 @@ impl Signed { #[inline(always)] #[must_use] pub fn checked_div(self, rhs: Self) -> Option { - if rhs.is_zero() || (self == Self::min_value() && rhs == Self::minus_one()) { + if rhs.is_zero() || (self == Self::MIN && rhs == Self::MINUS_ONE) { None } else { Some(self.overflowing_div(rhs).0) @@ -387,8 +387,8 @@ impl Signed { #[track_caller] #[must_use] pub fn overflowing_rem(self, rhs: Self) -> (Self, bool) { - if self == Self::MIN && rhs == Self::minus_one() { - (Self::zero(), true) + if self == Self::MIN && rhs == Self::MINUS_ONE { + (Self::ZERO, true) } else { let div_res = self / rhs; (self - div_res * rhs, false) @@ -400,7 +400,7 @@ impl Signed { #[inline(always)] #[must_use] pub fn checked_rem(self, rhs: Self) -> Option { - if rhs.is_zero() || (self == Self::MIN && rhs == Self::minus_one()) { + if rhs.is_zero() || (self == Self::MIN && rhs == Self::MINUS_ONE) { None } else { Some(self.overflowing_rem(rhs).0) @@ -445,9 +445,9 @@ impl Signed { let q = self / rhs; if (self % rhs).is_negative() { if rhs.is_positive() { - q - Self::one() + q - Self::ONE } else { - q + Self::one() + q + Self::ONE } } else { q @@ -467,7 +467,7 @@ impl Signed { #[track_caller] #[must_use] pub fn overflowing_div_euclid(self, rhs: Self) -> (Self, bool) { - if self == Self::min_value() && rhs == Self::minus_one() { + if self == Self::MIN && rhs == Self::MINUS_ONE { (self, true) } else { (self.div_euclid(rhs), false) @@ -479,7 +479,7 @@ impl Signed { #[inline(always)] #[must_use] pub fn checked_div_euclid(self, rhs: Self) -> Option { - if rhs.is_zero() || (self == Self::min_value() && rhs == Self::minus_one()) { + if rhs.is_zero() || (self == Self::MIN && rhs == Self::MINUS_ONE) { None } else { Some(self.div_euclid(rhs)) @@ -518,8 +518,8 @@ impl Signed { #[must_use] pub fn rem_euclid(self, rhs: Self) -> Self { let r = self % rhs; - if r < Self::zero() { - if rhs < Self::zero() { + if r < Self::ZERO { + if rhs < Self::ZERO { r - rhs } else { r + rhs @@ -542,8 +542,8 @@ impl Signed { #[track_caller] #[must_use] pub fn overflowing_rem_euclid(self, rhs: Self) -> (Self, bool) { - if self == Self::min_value() && rhs == Self::minus_one() { - (Self::zero(), true) + if self == Self::MIN && rhs == Self::MINUS_ONE { + (Self::ZERO, true) } else { (self.rem_euclid(rhs), false) } @@ -571,7 +571,7 @@ impl Signed { #[inline(always)] #[must_use] pub fn checked_rem_euclid(self, rhs: Self) -> Option { - if rhs.is_zero() || (self == Self::min_value() && rhs == Self::minus_one()) { + if rhs.is_zero() || (self == Self::MIN && rhs == Self::MINUS_ONE) { None } else { Some(self.rem_euclid(rhs)) @@ -630,7 +630,7 @@ impl Signed { #[must_use] pub fn overflowing_pow(self, exp: Uint) -> (Self, bool) { if BITS == 0 { - return (Self::zero(), false) + return (Self::ZERO, false) } let sign = self.pow_sign(exp); @@ -687,7 +687,7 @@ impl Signed { #[must_use] pub fn overflowing_shl(self, rhs: usize) -> (Self, bool) { if rhs >= 256 { - (Self::zero(), true) + (Self::ZERO, true) } else { (Self(self.0 << rhs), false) } @@ -721,7 +721,7 @@ impl Signed { #[must_use] pub fn overflowing_shr(self, rhs: usize) -> (Self, bool) { if rhs >= 256 { - (Self::zero(), true) + (Self::ZERO, true) } else { (Self(self.0 >> rhs), false) } @@ -759,8 +759,8 @@ impl Signed { if rhs >= BITS - 1 { match self.sign() { - Sign::Positive => return Self::zero(), - Sign::Negative => return Self::minus_one(), + Sign::Positive => return Self::ZERO, + Sign::Negative => return Self::MINUS_ONE, } } @@ -1010,7 +1010,7 @@ where { #[track_caller] fn sum>(iter: I) -> Self { - iter.fold(Signed::zero(), |acc, x| acc + x) + iter.fold(Signed::ZERO, |acc, x| acc + x) } } @@ -1020,7 +1020,7 @@ where { #[track_caller] fn product>(iter: I) -> Self { - iter.fold(Signed::one(), |acc, x| acc * x) + iter.fold(Signed::ONE, |acc, x| acc * x) } } diff --git a/crates/primitives/src/signed/utils.rs b/crates/primitives/src/signed/utils.rs index ece8fd5a17..4a64957bb8 100644 --- a/crates/primitives/src/signed/utils.rs +++ b/crates/primitives/src/signed/utils.rs @@ -35,14 +35,11 @@ pub const fn const_eq( let mut i = 0; let llimbs = left.0.as_limbs(); let rlimbs = right.0.as_limbs(); - loop { + while i < LIMBS { if llimbs[i] != rlimbs[i] { return false } i += 1; - if i == LIMBS { - break - } } true } diff --git a/crates/primitives/src/utils.rs b/crates/primitives/src/utils.rs index 82b092c4af..911634af8b 100644 --- a/crates/primitives/src/utils.rs +++ b/crates/primitives/src/utils.rs @@ -1,10 +1,13 @@ -use crate::bits::FixedBytes; +use crate::{bits::FixedBytes, Address}; +use core::borrow::Borrow; pub use tiny_keccak::{Hasher, Keccak}; -/// Simple interface to the `keccak256` hash function. +/// Simple interface to the [`keccak256`] hash function. +/// +/// [`keccak256`]: https://en.wikipedia.org/wiki/SHA-3 pub fn keccak256(bytes: impl AsRef<[u8]>) -> FixedBytes<32> { - fn internal(bytes: &[u8]) -> FixedBytes<32> { + fn keccak256(bytes: &[u8]) -> FixedBytes<32> { let mut output = [0u8; 32]; let mut hasher = Keccak::v256(); hasher.update(bytes); @@ -12,5 +15,161 @@ pub fn keccak256(bytes: impl AsRef<[u8]>) -> FixedBytes<32> { output.into() } - internal(bytes.as_ref()) + keccak256(bytes.as_ref()) +} + +/// Computes the `create` address for the given address and nonce. +/// +/// The address for an Ethereum contract is deterministically computed from the +/// address of its creator (sender) and how many transactions the creator has +/// sent (nonce). The sender and nonce are RLP encoded and then hashed with +/// [`keccak256`]. +#[cfg(feature = "rlp")] +pub fn create_address>(sender: T, nonce: u64) -> Address { + fn create_address(sender: &[u8; 20], nonce: u64) -> Address { + use ethers_rlp::Encodable; + + let mut out = alloc::vec::Vec::with_capacity(64); + let buf = &mut out as &mut dyn bytes::BufMut; + sender.encode(buf); + let _ = nonce; + #[cfg(TODO)] + crate::U256::from(nonce).encode(buf); + let hash = keccak256(&out); + + let mut bytes = [0u8; 20]; + bytes.copy_from_slice(&hash[12..]); + Address::from(bytes) + } + + create_address(sender.borrow(), nonce) +} + +/// Returns the CREATE2 address of a smart contract as specified in +/// [EIP1014](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1014.md). +/// +/// `keccak256( 0xff ++ address ++ salt ++ keccak256(init_code))[12:]` +pub fn create2_address_from_code(address: A, salt: S, init_code: C) -> Address +where + A: Borrow<[u8; 20]>, + S: Borrow<[u8; 32]>, + C: AsRef<[u8]>, +{ + create2_address(address, salt, &keccak256(init_code.as_ref()).0) +} + +/// Returns the CREATE2 address of a smart contract as specified in +/// [EIP1014](https://eips.ethereum.org/EIPS/eip-1014), +/// taking the pre-computed hash of the init code as input. +/// +/// `keccak256( 0xff ++ address ++ salt ++ keccak256(init_code))[12:]` +pub fn create2_address(address: A, salt: S, init_code_hash: H) -> Address +where + // not `AsRef` because `[u8; N]` does not implement `AsRef<[u8; N]>` + A: Borrow<[u8; 20]>, + S: Borrow<[u8; 32]>, + H: Borrow<[u8; 32]>, +{ + fn create2_address(address: &[u8; 20], salt: &[u8; 32], init_code_hash: &[u8; 32]) -> Address { + let mut bytes = [0; 85]; + bytes[0] = 0xff; + bytes[1..21].copy_from_slice(address); + bytes[21..53].copy_from_slice(salt); + bytes[53..85].copy_from_slice(init_code_hash); + let hash = keccak256(bytes); + + let mut bytes = [0u8; 20]; + bytes.copy_from_slice(&hash[12..]); + Address::from(bytes) + } + + create2_address(address.borrow(), salt.borrow(), init_code_hash.borrow()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[ignore = "Uint RLP"] + fn contract_address() { + // http://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed + let from = "6ac7ea33f8831ea9dcc53393aaa88b25a785dbf0" + .parse::
() + .unwrap(); + for (nonce, expected) in [ + "cd234a471b72ba2f1ccf0a70fcaba648a5eecd8d", + "343c43a37d37dff08ae8c4a11544c718abb4fcf8", + "f778b86fa74e846c4f0a1fbd1335fe81c00a0c91", + "fffd933a0bc612844eaf0c6fe3e5b8e9b6c1d19c", + ] + .iter() + .enumerate() + { + let address = create_address(from, nonce as u64); + assert_eq!(address, expected.parse::
().unwrap()); + } + } + + // Test vectors from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1014.md#examples + #[test] + fn test_create2_address() { + for (from, salt, init_code, expected) in &[ + ( + "0000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "00", + "4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38", + ), + ( + "deadbeef00000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "00", + "B928f69Bb1D91Cd65274e3c79d8986362984fDA3", + ), + ( + "deadbeef00000000000000000000000000000000", + "000000000000000000000000feed000000000000000000000000000000000000", + "00", + "D04116cDd17beBE565EB2422F2497E06cC1C9833", + ), + ( + "0000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "deadbeef", + "70f2b2914A2a4b783FaEFb75f459A580616Fcb5e", + ), + ( + "00000000000000000000000000000000deadbeef", + "00000000000000000000000000000000000000000000000000000000cafebabe", + "deadbeef", + "60f3f640a8508fC6a86d45DF051962668E1e8AC7", + ), + ( + "00000000000000000000000000000000deadbeef", + "00000000000000000000000000000000000000000000000000000000cafebabe", + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", + "1d8bfDC5D46DC4f61D6b6115972536eBE6A8854C", + ), + ( + "0000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "", + "E33C0C7F7df4809055C3ebA6c09CFe4BaF1BD9e0", + ), + ] { + let from = from.parse::
().unwrap(); + + let salt = hex::decode(salt).unwrap(); + let salt: [u8; 32] = salt.try_into().unwrap(); + + let init_code = hex::decode(init_code).unwrap(); + let init_code_hash = keccak256(&init_code); + + let expected = expected.parse::
().unwrap(); + + assert_eq!(expected, create2_address(from, salt, init_code_hash)); + assert_eq!(expected, create2_address_from_code(from, salt, init_code)); + } + } } diff --git a/crates/rlp-derive/src/lib.rs b/crates/rlp-derive/src/lib.rs index c54f957342..e9721085a0 100644 --- a/crates/rlp-derive/src/lib.rs +++ b/crates/rlp-derive/src/lib.rs @@ -1,9 +1,5 @@ #![warn(missing_docs, unreachable_pub)] #![deny(unused_must_use, unused_crate_dependencies)] -#![doc(test( - no_crate_inject, - attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) -))] //! Derive macro for `#[derive(RlpEncodable, RlpDecodable)]`. //! diff --git a/crates/rlp/Cargo.toml b/crates/rlp/Cargo.toml index 81dc00dce4..f98c502f1a 100644 --- a/crates/rlp/Cargo.toml +++ b/crates/rlp/Cargo.toml @@ -16,6 +16,7 @@ repository.workspace = true [dependencies] arrayvec.workspace = true bytes.workspace = true + ethers-rlp-derive = { workspace = true, optional = true } [dev-dependencies] diff --git a/crates/rlp/src/encode.rs b/crates/rlp/src/encode.rs index ecf231b5f1..84c21ce6ee 100644 --- a/crates/rlp/src/encode.rs +++ b/crates/rlp/src/encode.rs @@ -62,20 +62,24 @@ pub trait Encodable { } impl<'a, T: ?Sized + Encodable> Encodable for &'a T { + #[inline] fn encode(&self, out: &mut dyn BufMut) { (**self).encode(out) } + #[inline] fn length(&self) -> usize { (**self).length() } } impl<'a, T: ?Sized + Encodable> Encodable for &'a mut T { + #[inline] fn encode(&self, out: &mut dyn BufMut) { (**self).encode(out) } + #[inline] fn length(&self) -> usize { (**self).length() } @@ -103,20 +107,24 @@ impl Encodable for [u8] { } impl Encodable for [u8; N] { + #[inline] fn encode(&self, out: &mut dyn BufMut) { Encodable::encode(&self[..], out) } + #[inline] fn length(&self) -> usize { Encodable::length(&self[..]) } } impl Encodable for str { + #[inline] fn encode(&self, out: &mut dyn BufMut) { Encodable::encode(self.as_bytes(), out) } + #[inline] fn length(&self) -> usize { Encodable::length(self.as_bytes()) } @@ -241,60 +249,72 @@ mod alloc_support { use super::*; impl<'a, T: ?Sized + alloc::borrow::ToOwned + Encodable> Encodable for alloc::borrow::Cow<'a, T> { + #[inline] fn encode(&self, out: &mut dyn BufMut) { (**self).encode(out) } + #[inline] fn length(&self) -> usize { (**self).length() } } impl Encodable for alloc::boxed::Box { + #[inline] fn encode(&self, out: &mut dyn BufMut) { (**self).encode(out) } + #[inline] fn length(&self) -> usize { (**self).length() } } impl Encodable for alloc::rc::Rc { + #[inline] fn encode(&self, out: &mut dyn BufMut) { (**self).encode(out) } + #[inline] fn length(&self) -> usize { (**self).length() } } impl Encodable for alloc::sync::Arc { + #[inline] fn encode(&self, out: &mut dyn BufMut) { (**self).encode(out) } + #[inline] fn length(&self) -> usize { (**self).length() } } impl Encodable for alloc::vec::Vec { + #[inline] fn length(&self) -> usize { list_length(self) } + #[inline] fn encode(&self, out: &mut dyn BufMut) { encode_list(self, out) } } impl Encodable for alloc::string::String { + #[inline] fn encode(&self, out: &mut dyn BufMut) { self.as_bytes().encode(out); } + #[inline] fn length(&self) -> usize { self.as_bytes().length() } @@ -304,10 +324,12 @@ mod alloc_support { macro_rules! slice_impl { ($t:ty) => { impl $crate::Encodable for $t { + #[inline] fn encode(&self, out: &mut dyn BufMut) { Encodable::encode(&self[..], out) } + #[inline] fn length(&self) -> usize { Encodable::length(&self[..]) } diff --git a/crates/rlp/src/lib.rs b/crates/rlp/src/lib.rs index 630054d3db..df3f5133ad 100644 --- a/crates/rlp/src/lib.rs +++ b/crates/rlp/src/lib.rs @@ -1,10 +1,6 @@ #![cfg_attr(not(feature = "std"), no_std)] #![warn(missing_docs, unreachable_pub, unused_crate_dependencies)] #![deny(unused_must_use, rust_2018_idioms)] -#![doc(test( - no_crate_inject, - attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) -))] #![doc = include_str!("../README.md")] // This doctest uses derive and alloc, so it cannot be in the README :( #![cfg_attr( @@ -56,7 +52,7 @@ pub use encode::{ const_add, encode_fixed_size, encode_iter, encode_list, length_of_length, list_length, Encodable, MaxEncodedLen, MaxEncodedLenAssoc, }; -pub use types::*; +pub use types::{Header, EMPTY_LIST_CODE, EMPTY_STRING_CODE}; #[cfg(feature = "derive")] pub use ethers_rlp_derive::{ diff --git a/crates/sol-types/src/coder/impl_core.rs b/crates/sol-types/src/coder/impl_core.rs index ccc99a971d..f6ba1fa1e1 100644 --- a/crates/sol-types/src/coder/impl_core.rs +++ b/crates/sol-types/src/coder/impl_core.rs @@ -1,6 +1,8 @@ //! Modified implementations of unstable libcore functions. -use crate::no_std_prelude::*; +#![allow(dead_code)] + +use alloc::vec::Vec; use core::mem::{self, MaybeUninit}; trait Ext { @@ -71,15 +73,15 @@ unsafe fn slice_assume_init_mut(slice: &mut [MaybeUninit]) -> &mut [T] { } /// [`MaybeUninit::uninit_array`] -#[inline(always)] -fn uninit_array() -> [MaybeUninit; N] { +#[inline] +pub(crate) fn uninit_array() -> [MaybeUninit; N] { // SAFETY: An uninitialized `[MaybeUninit<_>; N]` is valid. unsafe { MaybeUninit::<[MaybeUninit; N]>::uninit().assume_init() } } /// [`MaybeUninit::array_assume_init`] -#[inline(always)] -unsafe fn array_assume_init(array: [MaybeUninit; N]) -> [T; N] { +#[inline] +pub(crate) unsafe fn array_assume_init(array: [MaybeUninit; N]) -> [T; N] { // SAFETY: // * The caller guarantees that all elements of the array are initialized // * `MaybeUninit` and T are guaranteed to have the same layout diff --git a/crates/sol-types/src/lib.rs b/crates/sol-types/src/lib.rs index 2a9f4cf455..90f3016b48 100644 --- a/crates/sol-types/src/lib.rs +++ b/crates/sol-types/src/lib.rs @@ -17,10 +17,6 @@ clippy::missing_const_for_fn )] #![deny(unused_must_use, rust_2018_idioms)] -#![doc(test( - no_crate_inject, - attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) -))] //! Solidity type modeling and ABI coding implementation. //! diff --git a/crates/sol-types/src/types/data_type.rs b/crates/sol-types/src/types/data_type.rs index 4ae69727dd..472d66aced 100644 --- a/crates/sol-types/src/types/data_type.rs +++ b/crates/sol-types/src/types/data_type.rs @@ -781,7 +781,7 @@ impl SolType for () { #[inline] fn eip712_data_word>(_rust: B) -> Word { - Word::zero() + Word::ZERO } #[inline] diff --git a/crates/sol-types/tests/doc_structs.rs b/crates/sol-types/tests/doc_structs.rs index 7a4777c362..5b836b50f2 100644 --- a/crates/sol-types/tests/doc_structs.rs +++ b/crates/sol-types/tests/doc_structs.rs @@ -35,7 +35,7 @@ fn structs() { let _nested = Nested { a: [my_foo.clone(), my_foo.clone()], - b: Address::zero(), + b: Address::ZERO, }; let abi_encoded = Foo::encode(my_foo); diff --git a/crates/sol-types/tests/doc_types.rs b/crates/sol-types/tests/doc_types.rs index e1033fd353..f410ac220a 100644 --- a/crates/sol-types/tests/doc_types.rs +++ b/crates/sol-types/tests/doc_types.rs @@ -19,5 +19,5 @@ fn types() { let _ = ::encode_single(true); let _ = B32::encode_single([0; 32]); let _ = SolArrayOf::::encode_single(vec![true, false]); - let _ = SolTuple::encode_single((Address::zero(), vec![0; 32], "hello".to_string())); + let _ = SolTuple::encode_single((Address::ZERO, vec![0; 32], "hello".to_string())); } diff --git a/crates/sol-types/tests/sol.rs b/crates/sol-types/tests/sol.rs index 5f190bee3a..24d485671e 100644 --- a/crates/sol-types/tests/sol.rs +++ b/crates/sol-types/tests/sol.rs @@ -97,27 +97,27 @@ fn function() { basic: U256::from(1), string_: "Hello World".to_owned(), longBytes: vec![0; 36], - array: vec![Address::zero(), Address::zero(), Address::zero()], + array: vec![Address::ZERO, Address::ZERO, Address::ZERO], fixedArray: [true, false], struct_: customStruct { - a: Address::zero(), + a: Address::ZERO, b: 2, }, structArray: vec![ customStruct { - a: Address::zero(), + a: Address::ZERO, b: 3, }, customStruct { - a: Address::zero(), + a: Address::ZERO, b: 4, }, customStruct { - a: Address::zero(), + a: Address::ZERO, b: 5, }, customStruct { - a: Address::zero(), + a: Address::ZERO, b: 6, }, ], From c154b3b50ca8cd77cd2add46988872e643177cc7 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 20 May 2023 12:04:29 +0200 Subject: [PATCH 02/20] feat: implement Arbitrary for Bytes and wrapped FixedBytes --- crates/primitives/src/bits/fixed.rs | 6 +- crates/primitives/src/bits/macros.rs | 121 ++++++++++++++++++++------- crates/primitives/src/bytes/mod.rs | 38 +++++++++ crates/primitives/src/lib.rs | 17 +++- 4 files changed, 147 insertions(+), 35 deletions(-) diff --git a/crates/primitives/src/bits/fixed.rs b/crates/primitives/src/bits/fixed.rs index be61c0c1e3..918280c0d7 100644 --- a/crates/primitives/src/bits/fixed.rs +++ b/crates/primitives/src/bits/fixed.rs @@ -11,13 +11,13 @@ use derive_more::{Deref, DerefMut, From, Index, IndexMut}; /// byte arrays. Users looking to prevent type-confusion between byte arrays of /// different lengths should use the [`crate::wrap_fixed_bytes`] macro to /// create a new fixed-length byte array type. +#[derive( + Deref, DerefMut, From, Hash, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Index, IndexMut, +)] #[cfg_attr( feature = "arbitrary", derive(arbitrary::Arbitrary, proptest_derive::Arbitrary) )] -#[derive( - Deref, DerefMut, From, Hash, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Index, IndexMut, -)] #[repr(transparent)] pub struct FixedBytes(pub [u8; N]); diff --git a/crates/primitives/src/bits/macros.rs b/crates/primitives/src/bits/macros.rs index 3f7a11499f..9d1002a46d 100644 --- a/crates/primitives/src/bits/macros.rs +++ b/crates/primitives/src/bits/macros.rs @@ -41,11 +41,11 @@ macro_rules! wrap_fixed_bytes { #[doc = $sn] #[doc = " bytes."] #[derive( - $crate::derive_more::AsRef, - $crate::derive_more::AsMut, - $crate::derive_more::Deref, - $crate::derive_more::DerefMut, - $crate::derive_more::From, + $crate::private::derive_more::AsRef, + $crate::private::derive_more::AsMut, + $crate::private::derive_more::Deref, + $crate::private::derive_more::DerefMut, + $crate::private::derive_more::From, Hash, Copy, Clone, @@ -54,17 +54,17 @@ macro_rules! wrap_fixed_bytes { PartialOrd, Ord, Default, - $crate::derive_more::Index, - $crate::derive_more::IndexMut, - $crate::derive_more::BitAnd, - $crate::derive_more::BitOr, - $crate::derive_more::BitXor, - $crate::derive_more::BitAndAssign, - $crate::derive_more::BitOrAssign, - $crate::derive_more::BitXorAssign, - $crate::derive_more::FromStr, - $crate::derive_more::LowerHex, - $crate::derive_more::UpperHex, + $crate::private::derive_more::Index, + $crate::private::derive_more::IndexMut, + $crate::private::derive_more::BitAnd, + $crate::private::derive_more::BitOr, + $crate::private::derive_more::BitXor, + $crate::private::derive_more::BitAndAssign, + $crate::private::derive_more::BitOrAssign, + $crate::private::derive_more::BitXorAssign, + $crate::private::derive_more::FromStr, + $crate::private::derive_more::LowerHex, + $crate::private::derive_more::UpperHex, )] pub struct $name($crate::FixedBytes<$n>); @@ -211,20 +211,21 @@ macro_rules! wrap_fixed_bytes { } } - impl core::fmt::Debug for $name { + impl ::core::fmt::Debug for $name { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - core::fmt::Debug::fmt(&self.0, f) + ::core::fmt::Debug::fmt(&self.0, f) } } - impl core::fmt::Display for $name { + impl ::core::fmt::Display for $name { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - core::fmt::Display::fmt(&self.0, f) + ::core::fmt::Display::fmt(&self.0, f) } } $crate::impl_rlp!($name); $crate::impl_serde!($name); + $crate::impl_arbitrary!($name, $n); }; } @@ -233,19 +234,22 @@ macro_rules! wrap_fixed_bytes { #[cfg(feature = "rlp")] macro_rules! impl_rlp { ($t:ty) => { - impl ethers_rlp::Decodable for $t { - fn decode(buf: &mut &[u8]) -> Result { - ethers_rlp::Decodable::decode(buf).map(Self) + impl $crate::private::ethers_rlp::Decodable for $t { + #[inline] + fn decode(buf: &mut &[u8]) -> Result { + $crate::private::ethers_rlp::Decodable::decode(buf).map(Self) } } - impl ethers_rlp::Encodable for $t { + impl $crate::private::ethers_rlp::Encodable for $t { + #[inline] fn length(&self) -> usize { - self.0.length() + $crate::private::ethers_rlp::Encodable::length(&self.0) } + #[inline] fn encode(&self, out: &mut dyn bytes::BufMut) { - self.0.encode(out) + $crate::private::ethers_rlp::Encodable::encode(&self.0, out) } } }; @@ -263,15 +267,19 @@ macro_rules! impl_rlp { #[cfg(feature = "serde")] macro_rules! impl_serde { ($t:ty) => { - impl serde::Serialize for $t { + impl $crate::private::serde::Serialize for $t { + #[inline] fn serialize(&self, serializer: S) -> Result { - serde::Serialize::serialize(&self.0, serializer) + $crate::private::serde::Serialize::serialize(&self.0, serializer) } } - impl<'de> serde::Deserialize<'de> for $t { - fn deserialize>(deserializer: D) -> Result { - serde::Deserialize::deserialize(deserializer).map(Self) + impl<'de> $crate::private::serde::Deserialize<'de> for $t { + #[inline] + fn deserialize>( + deserializer: D, + ) -> Result { + $crate::private::serde::Deserialize::deserialize(deserializer).map(Self) } } }; @@ -283,3 +291,54 @@ macro_rules! impl_serde { macro_rules! impl_serde { ($t:ty) => {}; } + +#[doc(hidden)] +#[macro_export] +#[cfg(feature = "arbitrary")] +macro_rules! impl_arbitrary { + ($t:ty, $n:literal) => { + impl<'a> $crate::private::arbitrary::Arbitrary<'a> for $t { + #[inline] + fn arbitrary(u: &mut $crate::private::arbitrary::Unstructured<'a>) -> $crate::private::arbitrary::Result { + <$crate::FixedBytes<$n> as $crate::private::arbitrary::Arbitrary>::arbitrary(u).map(Self) + } + + #[inline] + fn arbitrary_take_rest(u: $crate::private::arbitrary::Unstructured<'a>) -> $crate::private::arbitrary::Result { + <$crate::FixedBytes<$n> as $crate::private::arbitrary::Arbitrary>::arbitrary_take_rest(u).map(Self) + } + + #[inline] + fn size_hint(depth: usize) -> (usize, Option) { + <$crate::FixedBytes<$n> as $crate::private::arbitrary::Arbitrary>::size_hint(depth) + } + } + + impl $crate::private::proptest::arbitrary::Arbitrary for $t { + type Parameters = <$crate::FixedBytes<$n> as $crate::private::proptest::arbitrary::Arbitrary>::Parameters; + type Strategy = $crate::private::proptest::strategy::Map< + <$crate::FixedBytes<$n> as $crate::private::proptest::arbitrary::Arbitrary>::Strategy, + fn($crate::FixedBytes<$n>) -> Self, + >; + + #[inline] + fn arbitrary() -> Self::Strategy { + use $crate::private::proptest::strategy::Strategy; + <$crate::FixedBytes<$n> as $crate::private::proptest::arbitrary::Arbitrary>::arbitrary().prop_map(Self) + } + + #[inline] + fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { + use $crate::private::proptest::strategy::Strategy; + <$crate::FixedBytes<$n> as $crate::private::proptest::arbitrary::Arbitrary>::arbitrary_with(args).prop_map(Self) + } + } + }; +} + +#[doc(hidden)] +#[macro_export] +#[cfg(not(feature = "arbitrary"))] +macro_rules! impl_arbitrary { + ($t:ty, $n:literal) => {}; +} diff --git a/crates/primitives/src/bytes/mod.rs b/crates/primitives/src/bytes/mod.rs index 6a2cc93b2d..0b39b78d80 100644 --- a/crates/primitives/src/bytes/mod.rs +++ b/crates/primitives/src/bytes/mod.rs @@ -197,6 +197,44 @@ impl Bytes { } } +#[cfg(feature = "arbitrary")] +impl<'a> arbitrary::Arbitrary<'a> for Bytes { + #[inline] + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + u.arbitrary_iter()? + .collect::>>() + .map(Into::into) + } + + #[inline] + fn arbitrary_take_rest(u: arbitrary::Unstructured<'a>) -> arbitrary::Result { + Ok(Self(u.take_rest().to_vec().into())) + } + + #[inline] + fn size_hint(_depth: usize) -> (usize, Option) { + (0, None) + } +} + +#[cfg(feature = "arbitrary")] +impl proptest::arbitrary::Arbitrary for Bytes { + type Parameters = proptest::arbitrary::ParamsFor>; + type Strategy = proptest::arbitrary::Mapped, Self>; + + #[inline] + fn arbitrary() -> Self::Strategy { + use proptest::strategy::Strategy; + proptest::arbitrary::any::>().prop_map(|vec| Self(vec.into())) + } + + #[inline] + fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { + use proptest::strategy::Strategy; + proptest::arbitrary::any_with::>(args).prop_map(|vec| Self(vec.into())) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index e61d8c4104..3cc58d8626 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -36,4 +36,19 @@ pub use ruint::{self, uint, Uint}; // Not public API. #[doc(hidden)] -pub use derive_more; +pub mod private { + pub use derive_more; + + #[cfg(feature = "rlp")] + pub use ethers_rlp; + + #[cfg(feature = "serde")] + pub use serde; + + #[cfg(feature = "arbitrary")] + pub use arbitrary; + #[cfg(feature = "arbitrary")] + pub use proptest; + #[cfg(feature = "arbitrary")] + pub use proptest_derive; +} From dd6269a3b5f10f85cb5acc271d746c67cfcbdbff Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 20 May 2023 14:48:26 +0200 Subject: [PATCH 03/20] feat: improve primitives utils --- Cargo.toml | 3 ++- crates/dyn-abi/src/lib.rs | 8 ++------ crates/dyn-abi/src/value.rs | 2 +- crates/primitives/src/bits/address.rs | 20 ++++++++++++++++--- crates/primitives/src/lib.rs | 2 +- crates/primitives/src/utils.rs | 28 +++++++++++---------------- crates/rlp-derive/src/lib.rs | 6 +++--- crates/rlp/src/lib.rs | 6 +++--- crates/sol-types/src/eip712.rs | 6 +++--- crates/sol-types/src/lib.rs | 22 ++++++++++----------- 10 files changed, 54 insertions(+), 49 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 28774e46ac..32a1a6b927 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,13 +35,14 @@ derive_more = "0.99" hex-literal = "0.4" strum = { version = "0.24", features = ["derive"] } num_enum = "0.6" +thiserror = "1.0" # misc 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.5", default-features = false, features = ["alloc"] } +once_cell = "1.17" proptest = "1.1" proptest-derive = "0.3" -thiserror = "1.0" tiny-keccak = "2.0" diff --git a/crates/dyn-abi/src/lib.rs b/crates/dyn-abi/src/lib.rs index 3ac2bcf74e..270ca5b844 100644 --- a/crates/dyn-abi/src/lib.rs +++ b/crates/dyn-abi/src/lib.rs @@ -7,7 +7,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#![cfg_attr(not(feature = "std"), no_std)] +#![doc = include_str!("../README.md")] #![warn( missing_docs, unreachable_pub, @@ -17,11 +17,7 @@ clippy::missing_const_for_fn )] #![deny(unused_must_use, rust_2018_idioms)] -#![doc(test( - no_crate_inject, - attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) -))] -#![doc = include_str!("../README.md")] +#![cfg_attr(not(feature = "std"), no_std)] #[macro_use] extern crate alloc; diff --git a/crates/dyn-abi/src/value.rs b/crates/dyn-abi/src/value.rs index 7af45a58b0..c23d61fc2d 100644 --- a/crates/dyn-abi/src/value.rs +++ b/crates/dyn-abi/src/value.rs @@ -66,7 +66,7 @@ impl DynSolValue { /// Fallible cast to a single word. Will succeed for any single-word type. pub fn as_word(&self) -> Option { match self { - Self::Address(a) => Some((*a).into()), + Self::Address(a) => Some(a.into_word()), Self::Bool(b) => Some({ let mut buf = [0u8; 32]; if *b { diff --git a/crates/primitives/src/bits/address.rs b/crates/primitives/src/bits/address.rs index 40025fe62c..f6ee3a0e17 100644 --- a/crates/primitives/src/bits/address.rs +++ b/crates/primitives/src/bits/address.rs @@ -39,6 +39,7 @@ wrap_fixed_bytes!( ); impl Borrow<[u8; 20]> for Address { + #[inline] fn borrow(&self) -> &[u8; 20] { &self.0 } @@ -51,14 +52,27 @@ impl From
for [u8; 20] { } impl From
for FixedBytes<32> { + #[inline] fn from(addr: Address) -> Self { - let mut buf: FixedBytes<32> = Default::default(); - buf[12..].copy_from_slice(addr.as_bytes()); - buf + addr.into_word() } } impl Address { + /// Creates an Ethereum address from an EVM word's upper 20 bytes. + #[inline] + pub fn from_word(hash: FixedBytes<32>) -> Self { + Self(FixedBytes(hash[12..].try_into().unwrap())) + } + + /// Left-pads the address to 32 bytes (EVM word size). + #[inline] + pub fn into_word(self) -> FixedBytes<32> { + let mut buf = [0; 32]; + buf[12..].copy_from_slice(self.as_bytes()); + FixedBytes(buf) + } + /// Encodes an Ethereum address to its [EIP-55] checksum. /// /// You can optionally specify an [EIP-155 chain ID] to encode the address diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 3cc58d8626..777c64c2d4 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] #![warn( missing_docs, unreachable_pub, @@ -6,7 +7,6 @@ )] #![deny(unused_must_use, rust_2018_idioms)] #![cfg_attr(not(feature = "std"), no_std)] -#![doc = include_str!("../README.md")] #[macro_use] extern crate alloc; diff --git a/crates/primitives/src/utils.rs b/crates/primitives/src/utils.rs index 911634af8b..06b8c6172c 100644 --- a/crates/primitives/src/utils.rs +++ b/crates/primitives/src/utils.rs @@ -6,7 +6,7 @@ pub use tiny_keccak::{Hasher, Keccak}; /// Simple interface to the [`keccak256`] hash function. /// /// [`keccak256`]: https://en.wikipedia.org/wiki/SHA-3 -pub fn keccak256(bytes: impl AsRef<[u8]>) -> FixedBytes<32> { +pub fn keccak256>(bytes: T) -> FixedBytes<32> { fn keccak256(bytes: &[u8]) -> FixedBytes<32> { let mut output = [0u8; 32]; let mut hasher = Keccak::v256(); @@ -33,22 +33,19 @@ pub fn create_address>(sender: T, nonce: u64) -> Address { let buf = &mut out as &mut dyn bytes::BufMut; sender.encode(buf); let _ = nonce; - #[cfg(TODO)] + #[cfg(TODO_UINT_RLP)] crate::U256::from(nonce).encode(buf); let hash = keccak256(&out); - - let mut bytes = [0u8; 20]; - bytes.copy_from_slice(&hash[12..]); - Address::from(bytes) + Address::from_word(hash) } create_address(sender.borrow(), nonce) } -/// Returns the CREATE2 address of a smart contract as specified in -/// [EIP1014](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1014.md). +/// Returns the `CREATE2` address of a smart contract as specified in +/// [EIP1014](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1014.md): /// -/// `keccak256( 0xff ++ address ++ salt ++ keccak256(init_code))[12:]` +/// `keccak256(0xff ++ address ++ salt ++ keccak256(init_code))[12:]` pub fn create2_address_from_code(address: A, salt: S, init_code: C) -> Address where A: Borrow<[u8; 20]>, @@ -58,11 +55,11 @@ where create2_address(address, salt, &keccak256(init_code.as_ref()).0) } -/// Returns the CREATE2 address of a smart contract as specified in +/// Returns the `CREATE2` address of a smart contract as specified in /// [EIP1014](https://eips.ethereum.org/EIPS/eip-1014), -/// taking the pre-computed hash of the init code as input. +/// taking the pre-computed hash of the init code as input: /// -/// `keccak256( 0xff ++ address ++ salt ++ keccak256(init_code))[12:]` +/// `keccak256(0xff ++ address ++ salt ++ init_code_hash)[12:]` pub fn create2_address(address: A, salt: S, init_code_hash: H) -> Address where // not `AsRef` because `[u8; N]` does not implement `AsRef<[u8; N]>` @@ -77,10 +74,7 @@ where bytes[21..53].copy_from_slice(salt); bytes[53..85].copy_from_slice(init_code_hash); let hash = keccak256(bytes); - - let mut bytes = [0u8; 20]; - bytes.copy_from_slice(&hash[12..]); - Address::from(bytes) + Address::from_word(hash) } create2_address(address.borrow(), salt.borrow(), init_code_hash.borrow()) @@ -159,7 +153,7 @@ mod tests { ), ] { let from = from.parse::
().unwrap(); - + let salt = hex::decode(salt).unwrap(); let salt: [u8; 32] = salt.try_into().unwrap(); diff --git a/crates/rlp-derive/src/lib.rs b/crates/rlp-derive/src/lib.rs index e9721085a0..74c3de6f61 100644 --- a/crates/rlp-derive/src/lib.rs +++ b/crates/rlp-derive/src/lib.rs @@ -1,6 +1,3 @@ -#![warn(missing_docs, unreachable_pub)] -#![deny(unused_must_use, unused_crate_dependencies)] - //! Derive macro for `#[derive(RlpEncodable, RlpDecodable)]`. //! //! For example of usage see `./tests/rlp.rs`. @@ -11,6 +8,9 @@ //! the field deserialization fails, as we don't serialize field //! names and there is no way to tell if it is present or not. +#![warn(missing_docs, unreachable_pub)] +#![deny(unused_must_use, unused_crate_dependencies)] + extern crate proc_macro; mod de; diff --git a/crates/rlp/src/lib.rs b/crates/rlp/src/lib.rs index df3f5133ad..3ab04c3bcf 100644 --- a/crates/rlp/src/lib.rs +++ b/crates/rlp/src/lib.rs @@ -1,6 +1,3 @@ -#![cfg_attr(not(feature = "std"), no_std)] -#![warn(missing_docs, unreachable_pub, unused_crate_dependencies)] -#![deny(unused_must_use, rust_2018_idioms)] #![doc = include_str!("../README.md")] // This doctest uses derive and alloc, so it cannot be in the README :( #![cfg_attr( @@ -32,6 +29,9 @@ fn main() { ``` "## )] +#![warn(missing_docs, unreachable_pub, unused_crate_dependencies)] +#![deny(unused_must_use, rust_2018_idioms)] +#![cfg_attr(not(feature = "std"), no_std)] #[cfg(feature = "alloc")] extern crate alloc; diff --git a/crates/sol-types/src/eip712.rs b/crates/sol-types/src/eip712.rs index cb65055513..2c7c404866 100644 --- a/crates/sol-types/src/eip712.rs +++ b/crates/sol-types/src/eip712.rs @@ -685,8 +685,8 @@ mod test { const _DOMAIN: Eip712Domain = domain! { name: "abcd", version: "1", - chain_id: U256::from_limbs([0u64, 0, 0, 1]), - verifying_contract: Address::new([0u8;20]), - salt: B256::new([0u8;32]), + chain_id: U256::from_limbs([1, 0, 0, 0]), + verifying_contract: Address::ZERO, + salt: B256::ZERO, }; } diff --git a/crates/sol-types/src/lib.rs b/crates/sol-types/src/lib.rs index 90f3016b48..2b44778b10 100644 --- a/crates/sol-types/src/lib.rs +++ b/crates/sol-types/src/lib.rs @@ -7,17 +7,6 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -#![cfg_attr(not(feature = "std"), no_std)] -#![warn( - missing_docs, - unreachable_pub, - unused_crate_dependencies, - missing_copy_implementations, - missing_debug_implementations, - clippy::missing_const_for_fn -)] -#![deny(unused_must_use, rust_2018_idioms)] - //! Solidity type modeling and ABI coding implementation. //! //! This crate provides tools for expressing Solidity types in Rust, and for @@ -151,6 +140,17 @@ //! recommend users use them wherever possible. We do not recommend that users //! interact with Tokens, except when implementing their own [`SolType`]. +#![warn( + missing_docs, + unreachable_pub, + unused_crate_dependencies, + missing_copy_implementations, + missing_debug_implementations, + clippy::missing_const_for_fn +)] +#![deny(unused_must_use, rust_2018_idioms)] +#![cfg_attr(not(feature = "std"), no_std)] + #[macro_use] extern crate alloc; From 54e76485639077a0ca1a14910f55e309b75f427f Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 19 May 2023 19:21:26 +0200 Subject: [PATCH 04/20] fix: hex breaking change --- crates/primitives/src/bits/fixed.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/crates/primitives/src/bits/fixed.rs b/crates/primitives/src/bits/fixed.rs index 918280c0d7..d59e6afe8c 100644 --- a/crates/primitives/src/bits/fixed.rs +++ b/crates/primitives/src/bits/fixed.rs @@ -423,19 +423,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..]) } } From 169c34e4dd9f54daf9e2ef0363f3031494969a4d Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 20 May 2023 15:20:33 +0200 Subject: [PATCH 05/20] fixes --- crates/primitives/Cargo.toml | 1 - crates/primitives/src/bits/bloom.rs | 2 +- crates/primitives/src/bits/macros.rs | 17 +++++++++++++++++ crates/primitives/src/lib.rs | 1 + crates/primitives/src/utils.rs | 3 ++- 5 files changed, 21 insertions(+), 3 deletions(-) diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 0d76ba7305..825e847d6b 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -40,7 +40,6 @@ proptest-derive = { workspace = true, optional = true } [dev-dependencies] hex-literal.workspace = true -proptest.workspace = true [features] default = ["std", "rlp", "serde"] diff --git a/crates/primitives/src/bits/bloom.rs b/crates/primitives/src/bits/bloom.rs index b215ff8381..6c1d8424bc 100644 --- a/crates/primitives/src/bits/bloom.rs +++ b/crates/primitives/src/bits/bloom.rs @@ -14,7 +14,7 @@ pub const BLOOM_SIZE: usize = 256; /// A 256-byte Ethereum bloom filter. pub type Bloom = FixedBytes<256>; -/// BloomInput to the [`Bloom::accrue`] method. +/// Input to the [`Bloom::accrue`] method. #[derive(Debug)] pub enum BloomInput<'a> { /// Raw input to be hashed. diff --git a/crates/primitives/src/bits/macros.rs b/crates/primitives/src/bits/macros.rs index 9d1002a46d..2d9bafbb15 100644 --- a/crates/primitives/src/bits/macros.rs +++ b/crates/primitives/src/bits/macros.rs @@ -118,31 +118,42 @@ macro_rules! wrap_fixed_bytes { Self($crate::FixedBytes::random()) } + /// Instantiates a new fixed hash with cryptographically random content. + #[inline] + pub fn try_random() -> ::core::result::Result { + $crate::FixedBytes::try_random().map(Self) + } + /// Returns a new fixed hash where all bits are set to the given byte. #[inline] pub const fn repeat_byte(byte: u8) -> Self { Self($crate::FixedBytes::repeat_byte(byte)) } + /// Returns the size of this hash in bytes. #[inline] pub const fn len_bytes() -> usize { $n } + /// Extracts a byte slice containing the entire fixed hash. #[inline] pub const fn as_bytes(&self) -> &[u8] { self.0.as_bytes() } + /// Extracts a mutable byte slice containing the entire fixed hash. #[inline] pub fn as_bytes_mut(&mut self) -> &mut [u8] { self.0.as_bytes_mut() } + /// Extracts a reference to the byte array containing the entire fixed hash. #[inline] pub const fn as_fixed_bytes(&self) -> &[u8; $n] { self.0.as_fixed_bytes() } + /// Extracts a reference to the byte array containing the entire fixed hash. #[inline] pub fn as_fixed_bytes_mut(&mut self) -> &mut [u8; $n] { @@ -183,28 +194,34 @@ macro_rules! wrap_fixed_bytes { pub fn covers(&self, b: &Self) -> bool { &(*b & *self) == b } + /// Returns `true` if no bits are set. #[inline] pub fn is_zero(&self) -> bool { self.as_bytes().iter().all(|&byte| byte == 0u8) } + /// Compile-time equality. NOT constant-time equality. pub const fn const_eq(&self, other: Self) -> bool { self.0.const_eq(other.0) } + /// Returns `true` if no bits are set. #[inline] pub const fn const_is_zero(&self) -> bool { self.0.const_is_zero() } + /// Computes the bitwise AND of two `FixedBytes`. pub const fn bit_and(self, rhs: Self) -> Self { Self(self.0.bit_and(rhs.0)) } + /// Computes the bitwise OR of two `FixedBytes`. pub const fn bit_or(self, rhs: Self) -> Self { Self(self.0.bit_or(rhs.0)) } + /// Computes the bitwise XOR of two `FixedBytes`. pub const fn bit_xor(self, rhs: Self) -> Self { Self(self.0.bit_xor(rhs.0)) diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 777c64c2d4..c528abee7b 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -38,6 +38,7 @@ pub use ruint::{self, uint, Uint}; #[doc(hidden)] pub mod private { pub use derive_more; + pub use getrandom; #[cfg(feature = "rlp")] pub use ethers_rlp; diff --git a/crates/primitives/src/utils.rs b/crates/primitives/src/utils.rs index 06b8c6172c..49b4173ee4 100644 --- a/crates/primitives/src/utils.rs +++ b/crates/primitives/src/utils.rs @@ -86,7 +86,8 @@ mod tests { #[test] #[ignore = "Uint RLP"] - fn contract_address() { + #[cfg(feature = "rlp")] + fn test_create_address() { // http://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed let from = "6ac7ea33f8831ea9dcc53393aaa88b25a785dbf0" .parse::
() From 59368682196027a7c425cca96ac77017a994896c Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 20 May 2023 15:21:34 +0200 Subject: [PATCH 06/20] chore: clippy --- crates/primitives/src/bits/bloom.rs | 2 +- crates/primitives/src/bits/fixed.rs | 2 +- crates/primitives/src/bits/macros.rs | 2 +- crates/primitives/src/utils.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/primitives/src/bits/bloom.rs b/crates/primitives/src/bits/bloom.rs index 6c1d8424bc..c4be171e30 100644 --- a/crates/primitives/src/bits/bloom.rs +++ b/crates/primitives/src/bits/bloom.rs @@ -148,7 +148,7 @@ impl<'a> From<&'a Bloom> for BloomRef<'a> { } #[inline] -fn log2(x: usize) -> u32 { +const fn log2(x: usize) -> u32 { if x <= 1 { return 0 } diff --git a/crates/primitives/src/bits/fixed.rs b/crates/primitives/src/bits/fixed.rs index d59e6afe8c..166b9560a6 100644 --- a/crates/primitives/src/bits/fixed.rs +++ b/crates/primitives/src/bits/fixed.rs @@ -126,7 +126,7 @@ impl FixedBytes { /// Utility function to create a fixed hash with the first byte set to `x`. #[inline] - pub fn with_first_byte(x: u8) -> Self { + pub const fn with_first_byte(x: u8) -> Self { let mut bytes = [0u8; N]; bytes[0] = x; Self(bytes) diff --git a/crates/primitives/src/bits/macros.rs b/crates/primitives/src/bits/macros.rs index 2d9bafbb15..2fa86594e9 100644 --- a/crates/primitives/src/bits/macros.rs +++ b/crates/primitives/src/bits/macros.rs @@ -108,7 +108,7 @@ macro_rules! wrap_fixed_bytes { /// Utility function to create a fixed hash with the first byte set to `x`. #[inline] - pub fn with_first_byte(x: u8) -> Self { + pub const fn with_first_byte(x: u8) -> Self { Self($crate::FixedBytes::with_first_byte(x)) } diff --git a/crates/primitives/src/utils.rs b/crates/primitives/src/utils.rs index 49b4173ee4..79f8745419 100644 --- a/crates/primitives/src/utils.rs +++ b/crates/primitives/src/utils.rs @@ -52,7 +52,7 @@ where S: Borrow<[u8; 32]>, C: AsRef<[u8]>, { - create2_address(address, salt, &keccak256(init_code.as_ref()).0) + create2_address(address, salt, keccak256(init_code.as_ref()).0) } /// Returns the `CREATE2` address of a smart contract as specified in From a9a999581eba77e9b43dc8c8a9094c1768841a47 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 20 May 2023 15:37:51 +0200 Subject: [PATCH 07/20] serde --- Cargo.toml | 2 +- crates/primitives/Cargo.toml | 1 + crates/primitives/src/bits/mod.rs | 7 ++--- .../src/bits/{serialize.rs => serde.rs} | 13 +++++++++ crates/primitives/src/bytes/mod.rs | 8 +++-- crates/primitives/src/bytes/serde.rs | 29 +++++++++++++++++++ 6 files changed, 52 insertions(+), 8 deletions(-) rename crates/primitives/src/bits/{serialize.rs => serde.rs} (70%) create mode 100644 crates/primitives/src/bytes/serde.rs diff --git a/Cargo.toml b/Cargo.toml index 32a1a6b927..b83f850d98 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" diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 825e847d6b..84b08dc7e2 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -40,6 +40,7 @@ proptest-derive = { workspace = true, optional = true } [dev-dependencies] hex-literal.workspace = true +serde_json.workspace = true [features] default = ["std", "rlp", "serde"] diff --git a/crates/primitives/src/bits/mod.rs b/crates/primitives/src/bits/mod.rs index ec9ecaf257..9d1fcd382e 100644 --- a/crates/primitives/src/bits/mod.rs +++ b/crates/primitives/src/bits/mod.rs @@ -9,13 +9,12 @@ pub use fixed::FixedBytes; mod macros; -// code stolen from: https://docs.rs/impl-serde/0.4.0/impl_serde/ -#[cfg(feature = "serde")] -mod serialize; - #[cfg(feature = "rlp")] mod rlp; +#[cfg(feature = "serde")] +mod serde; + mod impl_core; /// 8-byte fixed array type. diff --git a/crates/primitives/src/bits/serialize.rs b/crates/primitives/src/bits/serde.rs similarity index 70% rename from crates/primitives/src/bits/serialize.rs rename to crates/primitives/src/bits/serde.rs index e6825c4020..b67276e65d 100644 --- a/crates/primitives/src/bits/serialize.rs +++ b/crates/primitives/src/bits/serde.rs @@ -21,3 +21,16 @@ impl<'de, const N: usize> serde::Deserialize<'de> for FixedBytes { s.parse().map_err(serde::de::Error::custom) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serde() { + let bytes = FixedBytes([1, 35, 69, 103, 137, 171, 205, 239]); + let ser = serde_json::to_string(&bytes).unwrap(); + assert_eq!(ser, "\"0x0123456789abcdef\""); + assert_eq!(serde_json::from_str::>(&ser).unwrap(), bytes); + } +} diff --git a/crates/primitives/src/bytes/mod.rs b/crates/primitives/src/bytes/mod.rs index 0b39b78d80..afc26beb46 100644 --- a/crates/primitives/src/bytes/mod.rs +++ b/crates/primitives/src/bytes/mod.rs @@ -4,11 +4,13 @@ use core::{borrow::Borrow, fmt, ops::Deref}; #[cfg(feature = "rlp")] mod rlp; +#[cfg(feature = "serde")] +mod serde; + /// Wrapper type around Bytes to deserialize/serialize "0x" prefixed ethereum /// hex strings. #[derive(Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Bytes(#[cfg_attr(serde, serde(with = "hex"))] pub bytes::Bytes); +pub struct Bytes(pub bytes::Bytes); impl fmt::Debug for Bytes { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -261,7 +263,7 @@ mod tests { #[test] fn debug() { - let b = Bytes::from(vec![1, 35, 69, 103, 137, 171, 205, 239]); + let b = Bytes::from_static(&[1, 35, 69, 103, 137, 171, 205, 239]); assert_eq!(format!("{b:?}"), "Bytes(0x0123456789abcdef)"); assert_eq!(format!("{b:#?}"), "Bytes(0x0123456789abcdef)"); } diff --git a/crates/primitives/src/bytes/serde.rs b/crates/primitives/src/bytes/serde.rs new file mode 100644 index 0000000000..cd8d616bc6 --- /dev/null +++ b/crates/primitives/src/bytes/serde.rs @@ -0,0 +1,29 @@ +use super::Bytes; +use alloc::string::String; +use core::result::Result; + +impl serde::Serialize for Bytes { + fn serialize(&self, serializer: S) -> Result { + serializer.collect_str(&format_args!("{}", self)) + } +} + +impl<'de> serde::Deserialize<'de> for Bytes { + fn deserialize>(deserializer: D) -> Result { + let s = String::deserialize(deserializer)?; + s.parse().map_err(serde::de::Error::custom) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serde() { + let bytes = Bytes::from_static(&[1, 35, 69, 103, 137, 171, 205, 239]); + let ser = serde_json::to_string(&bytes).unwrap(); + assert_eq!(ser, "\"0x0123456789abcdef\""); + assert_eq!(serde_json::from_str::(&ser).unwrap(), bytes); + } +} From d169b0781c4c734444e3fa09496caa1b3cfce2eb 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 08/20] feat: add more serde utils and impls --- Cargo.toml | 2 +- crates/primitives/Cargo.toml | 10 ++--- crates/primitives/src/bits/address.rs | 22 +++++------ crates/primitives/src/bits/macros.rs | 23 +++++++---- crates/primitives/src/bits/serde.rs | 44 ++++++++++++++------- crates/primitives/src/signed/int.rs | 29 ++++++-------- crates/primitives/src/signed/mod.rs | 12 ++++-- 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 | 6 +-- crates/sol-types/src/util.rs | 54 -------------------------- 12 files changed, 137 insertions(+), 124 deletions(-) create mode 100644 crates/primitives/src/signed/serde.rs diff --git a/Cargo.toml b/Cargo.toml index b83f850d98..7b599249f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,6 @@ arrayvec = { version = "0.7.2", default-features = false } bytes = { version = "1.4", default-features = false } hex = { package = "const-hex", version = ">=1.5", default-features = false, features = ["alloc"] } once_cell = "1.17" -proptest = "1.1" +proptest = { version = "1.1", default-features = false, features = ["alloc"] } proptest-derive = "0.3" tiny-keccak = "2.0" diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 84b08dc7e2..db0d718f47 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 @@ -30,7 +30,7 @@ derive_more.workspace = true # rlp ethers-rlp = { workspace = true, optional = true } -# optional +# serde serde = { workspace = true, features = ["derive"], optional = true } # arbitrary @@ -43,10 +43,10 @@ hex-literal.workspace = true serde_json.workspace = true [features] -default = ["std", "rlp", "serde"] -std = ["bytes/std", "hex/std", "ethers-rlp?/std", "proptest?/std", "serde/std"] +default = ["std", "rlp", "serde", "hex/std"] +std = ["bytes/std", "serde/std", "ethers-rlp?/std", "proptest?/std"] rlp = ["dep:ethers-rlp"] -serde = ["dep:serde", "bytes/serde", "hex/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 f6ee3a0e17..056a72dac3 100644 --- a/crates/primitives/src/bits/address.rs +++ b/crates/primitives/src/bits/address.rs @@ -45,12 +45,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> { #[inline] fn from(addr: Address) -> Self { @@ -91,10 +85,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(); @@ -103,19 +98,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/macros.rs b/crates/primitives/src/bits/macros.rs index 2fa86594e9..4178a068ce 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::private::derive_more::LowerHex, $crate::private::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/serde.rs b/crates/primitives/src/bits/serde.rs index b67276e65d..c9f1c2c178 100644 --- a/crates/primitives/src/bits/serde.rs +++ b/crates/primitives/src/bits/serde.rs @@ -1,24 +1,40 @@ use super::FixedBytes; use alloc::string::String; -use core::result::Result; +use core::fmt; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; -impl serde::Serialize for FixedBytes { - fn serialize(&self, serializer: S) -> Result { - serializer.collect_str(&format_args!("{}", self)) +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> 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() - ))) +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)), } - s.parse().map_err(serde::de::Error::custom) + } + + fn visit_string(self, v: String) -> Result { + self.visit_str(v.as_str()) } } diff --git a/crates/primitives/src/signed/int.rs b/crates/primitives/src/signed/int.rs index 075735ae8c..1d9d5236ef 100644 --- a/crates/primitives/src/signed/int.rs +++ b/crates/primitives/src/signed/int.rs @@ -1,5 +1,5 @@ use super::{errors, utils::*, Sign}; -use alloc::string::{String, ToString}; +use alloc::string::String; use core::fmt; use ruint::Uint; @@ -72,6 +72,10 @@ use ruint::Uint; /// To prevent this, we strongly recommend always prefixing hex strings with /// `0x` AFTER the sign (if any). #[derive(Clone, Copy, Default, PartialEq, Eq, Hash)] +#[cfg_attr( + feature = "arbitrary", + derive(arbitrary::Arbitrary, proptest_derive::Arbitrary) +)] pub struct Signed(pub(crate) Uint); // formatting @@ -463,31 +467,16 @@ impl Signed { } } -#[cfg(feature = "serde")] -impl serde::Serialize for Signed { - fn serialize(&self, serializer: S) -> Result { - self.to_string().serialize(serializer) - } -} - -#[cfg(feature = "serde")] -impl<'de, const BITS: usize, const LIMBS: usize> serde::Deserialize<'de> for Signed { - fn deserialize>(deserializer: D) -> Result { - let s = String::deserialize(deserializer)?; - s.parse().map_err(serde::de::Error::custom) - } -} - -#[cfg(all(test, feature = "std"))] +#[cfg(test)] mod tests { use super::*; use crate::{aliases::*, BigIntConversionError, ParseSignedError}; use alloc::string::ToString; + use core::ops::Neg; use ruint::{ aliases::{U0, U1, U128, U160, U256}, BaseConvertError, ParseError, }; - use std::ops::Neg; // type U2 = Uint<2, 1>; type I96 = Signed<96, 2>; @@ -1322,6 +1311,7 @@ mod tests { } #[test] + #[cfg(feature = "std")] fn division_by_zero() { macro_rules! run_test { ($i_struct:ty, $u_struct:ty) => { @@ -1450,6 +1440,7 @@ mod tests { } #[test] + #[cfg(feature = "std")] fn div_euclid_by_zero() { macro_rules! run_test { ($i_struct:ty, $u_struct:ty) => { @@ -1479,6 +1470,7 @@ mod tests { } #[test] + #[cfg(feature = "std")] fn div_euclid_overflow() { macro_rules! run_test { ($i_struct:ty, $u_struct:ty) => { @@ -1496,6 +1488,7 @@ mod tests { } #[test] + #[cfg(feature = "std")] fn mod_by_zero() { macro_rules! run_test { ($i_struct:ty, $u_struct:ty) => { diff --git a/crates/primitives/src/signed/mod.rs b/crates/primitives/src/signed/mod.rs index 176a5f6efd..fdeff3dc58 100644 --- a/crates/primitives/src/signed/mod.rs +++ b/crates/primitives/src/signed/mod.rs @@ -7,10 +7,6 @@ mod conversions; mod errors; pub use errors::{BigIntConversionError, ParseSignedError}; -/// A simple [`Sign`] enum, for dealing with integer signs. -mod sign; -pub use sign::Sign; - /// Signed integer type wrapping a [`ruint::Uint`]. mod int; pub use int::Signed; @@ -18,6 +14,14 @@ pub use int::Signed; /// Operation implementations. mod ops; +/// A simple [`Sign`] enum, for dealing with integer signs. +mod sign; +pub use sign::Sign; + +/// Serde support. +#[cfg(feature = "serde")] +mod serde; + /// Utility functions used in the signed integer implementation. pub(crate) mod utils; pub use utils::const_eq; diff --git a/crates/primitives/src/signed/serde.rs b/crates/primitives/src/signed/serde.rs new file mode 100644 index 0000000000..de412e56f9 --- /dev/null +++ b/crates/primitives/src/signed/serde.rs @@ -0,0 +1,55 @@ +use super::Signed; +use alloc::string::String; +use core::fmt; +use serde::{ + de::{self, Visitor}, + Deserialize, Deserializer, Serialize, Serializer, +}; + +impl Serialize for Signed { + fn serialize(&self, serializer: S) -> Result { + serializer.collect_str(self) + } +} + +impl<'de, const BITS: usize, const LIMBS: usize> Deserialize<'de> for Signed { + fn deserialize>(deserializer: D) -> Result { + struct SignedVisitor; + + impl Visitor<'_> for SignedVisitor { + type Value = Signed; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "a {BITS} bit signed integer") + } + + fn visit_u64(self, v: u64) -> Result { + Signed::try_from(v).map_err(de::Error::custom) + } + + fn visit_u128(self, v: u128) -> Result { + Signed::try_from(v).map_err(de::Error::custom) + } + + fn visit_i64(self, v: i64) -> Result { + Signed::try_from(v).map_err(de::Error::custom) + } + + fn visit_i128(self, v: i128) -> Result { + Signed::try_from(v).map_err(de::Error::custom) + } + + fn visit_str(self, v: &str) -> Result { + v.parse().map_err(serde::de::Error::custom) + } + + fn visit_string(self, v: String) -> Result { + self.visit_str(&v) + } + } + + deserializer.deserialize_any(SignedVisitor) + } +} + +// TODO: Tests diff --git a/crates/sol-macro/Cargo.toml b/crates/sol-macro/Cargo.toml index 4bbfca1348..7d2ba54813 100644 --- a/crates/sol-macro/Cargo.toml +++ b/crates/sol-macro/Cargo.toml @@ -3,7 +3,7 @@ name = "ethers-sol-macro" version = "0.1.0" description = "Solidity to Rust proc-macro" readme = "README.md" -keywords = ["ethereum", "abi", "encoding", "EVM", "solidity"] +keywords = ["ethereum", "abi", "encoding", "evm", "solidity"] categories = ["encoding", "cryptography::cryptocurrencies"] edition.workspace = true diff --git a/crates/sol-types/Cargo.toml b/crates/sol-types/Cargo.toml index 9c2c5f7b12..6515bfe384 100644 --- a/crates/sol-types/Cargo.toml +++ b/crates/sol-types/Cargo.toml @@ -3,7 +3,7 @@ name = "ethers-sol-types" version = "0.1.0" description = "Ethereum ABI encoding and decoding, with static typing" readme = "README.md" -keywords = ["ethereum", "abi", "encoding", "EVM", "solidity"] +keywords = ["ethereum", "abi", "encoding", "evm", "solidity"] categories = ["encoding", "cryptography::cryptocurrencies"] edition.workspace = true diff --git a/crates/sol-types/src/eip712.rs b/crates/sol-types/src/eip712.rs index 2c7c404866..4de5198dec 100644 --- a/crates/sol-types/src/eip712.rs +++ b/crates/sol-types/src/eip712.rs @@ -33,11 +33,7 @@ pub struct Eip712Domain { /// not match the currently active chain. #[cfg_attr( feature = "eip712-serde", - serde( - default, - skip_serializing_if = "Option::is_none", - deserialize_with = "crate::util::deserialize_stringified_numeric_opt" - ) + serde(default, skip_serializing_if = "Option::is_none") )] pub chain_id: Option, diff --git a/crates/sol-types/src/util.rs b/crates/sol-types/src/util.rs index 5ae711a887..0b8b0a2786 100644 --- a/crates/sol-types/src/util.rs +++ b/crates/sol-types/src/util.rs @@ -71,60 +71,6 @@ pub(crate) fn check_bool(slice: Word) -> bool { check_zeroes(&slice[..31]) } -#[cfg(feature = "eip712-serde")] -pub(crate) use serde_helper::*; - -#[cfg(feature = "eip712-serde")] -mod serde_helper { - use alloc::string::{String, ToString}; - use ethers_primitives::U256; - use serde::{Deserialize, Deserializer}; - - /// Helper type to parse numeric strings, `u64` and `U256` - #[derive(Deserialize, Debug, Clone)] - #[serde(untagged)] - pub(crate) enum StringifiedNumeric { - Num(u64), - U256(U256), - String(String), - } - - impl TryFrom for U256 { - type Error = String; - - fn try_from(value: StringifiedNumeric) -> Result { - match value { - StringifiedNumeric::Num(n) => Ok(U256::from(n)), - StringifiedNumeric::U256(n) => Ok(n), - // TODO: this is probably unreachable, due to ruint U256 deserializing from a string - StringifiedNumeric::String(s) => { - if let Some(s) = s.strip_prefix("0x") { - U256::from_str_radix(s, 16).map_err(|err| err.to_string()) - } else { - U256::from_str_radix(&s, 10).map_err(|err| err.to_string()) - } - } - } - } - } - - /// Supports parsing numbers as strings - /// - /// See - pub(crate) fn deserialize_stringified_numeric_opt<'de, D>( - deserializer: D, - ) -> Result, D::Error> - where - D: Deserializer<'de>, - { - if let Some(num) = Option::::deserialize(deserializer)? { - num.try_into().map(Some).map_err(serde::de::Error::custom) - } else { - Ok(None) - } - } -} - #[cfg(test)] mod tests { use super::pad_u32; From 215f74162b0e49a0799f2b9fce1ab546f232869f Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Sat, 20 May 2023 15:52:16 +0200 Subject: [PATCH 09/20] ignore test --- crates/dyn-abi/src/eip712/typed_data.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/dyn-abi/src/eip712/typed_data.rs b/crates/dyn-abi/src/eip712/typed_data.rs index 96f1189221..099944fec0 100644 --- a/crates/dyn-abi/src/eip712/typed_data.rs +++ b/crates/dyn-abi/src/eip712/typed_data.rs @@ -88,11 +88,14 @@ pub struct TypedData { /// the signature (e.g. the dapp, protocol, etc. that it's intended for). /// This data is used to construct the domain seperator of the message. pub domain: Eip712Domain, + /// The custom types used by this message. pub resolver: Resolver, - #[serde(rename = "primaryType")] + /// The type of the message. + #[serde(rename = "primaryType")] pub primary_type: String, + /// The message to be signed. pub message: serde_json::Value, } @@ -233,6 +236,7 @@ mod tests { use serde_json::json; #[test] + #[ignore = "Uint Serde"] fn test_full_domain() { let json = json!({ "types": { From 73b3106336adf631fed5b2f990cebef97de928d9 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 22 May 2023 19:54:43 +0200 Subject: [PATCH 10/20] fix: RLP `Encode` impl for `FixedBytes` and `bool` --- crates/primitives/Cargo.toml | 11 ++++++----- crates/primitives/src/bits/fixed.rs | 6 +++--- crates/primitives/src/bits/macros.rs | 6 +++--- crates/primitives/src/bits/rlp.rs | 14 ++++++++++++-- crates/rlp/src/encode.rs | 14 +++++++------- 5 files changed, 31 insertions(+), 20 deletions(-) diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index db0d718f47..a247e91653 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -15,7 +15,7 @@ repository.workspace = true [dependencies] # eth -ruint = { workspace = true, features = ["rlp", "serde"] } +ruint = { workspace = true, features = ["serde"] } # utility bytes.workspace = true @@ -40,13 +40,14 @@ proptest-derive = { workspace = true, optional = true } [dev-dependencies] hex-literal.workspace = true +proptest.workspace = true serde_json.workspace = true [features] -default = ["std", "rlp", "serde", "hex/std"] -std = ["bytes/std", "serde/std", "ethers-rlp?/std", "proptest?/std"] -rlp = ["dep:ethers-rlp"] -serde = ["dep:serde", "ruint/serde", "hex/serde"] +default = ["std", "rlp", "serde"] +std = ["bytes/std", "hex/std", "ethers-rlp?/std", "proptest?/std", "serde/std"] +rlp = ["dep:ethers-rlp", "ruint/fastrlp"] +serde = ["dep:serde", "bytes/serde", "hex/serde", "ruint/serde"] arbitrary = [ "ruint/arbitrary", "ruint/proptest", diff --git a/crates/primitives/src/bits/fixed.rs b/crates/primitives/src/bits/fixed.rs index 166b9560a6..e65d75fda6 100644 --- a/crates/primitives/src/bits/fixed.rs +++ b/crates/primitives/src/bits/fixed.rs @@ -124,11 +124,11 @@ impl FixedBytes { Self(bytes) } - /// Utility function to create a fixed hash with the first byte set to `x`. + /// Utility function to create a fixed hash with the last byte set to `x`. #[inline] - pub const fn with_first_byte(x: u8) -> Self { + pub const fn with_last_byte(x: u8) -> Self { let mut bytes = [0u8; N]; - bytes[0] = x; + bytes[N - 1] = x; Self(bytes) } diff --git a/crates/primitives/src/bits/macros.rs b/crates/primitives/src/bits/macros.rs index 4178a068ce..c84ad0dbd0 100644 --- a/crates/primitives/src/bits/macros.rs +++ b/crates/primitives/src/bits/macros.rs @@ -113,10 +113,10 @@ macro_rules! wrap_fixed_bytes { Self($crate::FixedBytes(bytes)) } - /// Utility function to create a fixed hash with the first byte set to `x`. + /// Utility function to create a fixed hash with the last byte set to `x`. #[inline] - pub const fn with_first_byte(x: u8) -> Self { - Self($crate::FixedBytes::with_first_byte(x)) + pub const fn with_last_byte(x: u8) -> Self { + Self($crate::FixedBytes::with_last_byte(x)) } /// Instantiates a new fixed hash with cryptographically random content. diff --git a/crates/primitives/src/bits/rlp.rs b/crates/primitives/src/bits/rlp.rs index 439a62ccae..35e4197920 100644 --- a/crates/primitives/src/bits/rlp.rs +++ b/crates/primitives/src/bits/rlp.rs @@ -1,5 +1,5 @@ use super::FixedBytes; -use ethers_rlp::{Decodable, Encodable}; +use ethers_rlp::{impl_max_encoded_len, length_of_length, Decodable, Encodable}; impl Decodable for FixedBytes { #[inline] @@ -11,7 +11,7 @@ impl Decodable for FixedBytes { impl Encodable for FixedBytes { #[inline] fn length(&self) -> usize { - N + self.0.length() } #[inline] @@ -19,3 +19,13 @@ impl Encodable for FixedBytes { self.0.encode(out) } } + +// cannot implement this with const generics due to Rust issue #76560: +// https://github.com/rust-lang/rust/issues/76560 +macro_rules! fixed_bytes_max_encoded_len { + ($($sz:literal),+) => {$( + impl_max_encoded_len!(FixedBytes<$sz>, $sz + length_of_length($sz)); + )+}; +} + +fixed_bytes_max_encoded_len!(0, 1, 2, 4, 8, 16, 20, 32, 64, 128, 256, 512, 1024); diff --git a/crates/rlp/src/encode.rs b/crates/rlp/src/encode.rs index 84c21ce6ee..08ad6658ad 100644 --- a/crates/rlp/src/encode.rs +++ b/crates/rlp/src/encode.rs @@ -38,7 +38,7 @@ pub unsafe trait MaxEncodedLenAssoc: Encodable { #[macro_export] macro_rules! impl_max_encoded_len { ($t:ty, $len:expr) => { - unsafe impl $crate::MaxEncodedLen<$len> for $t {} + unsafe impl $crate::MaxEncodedLen<{ $len }> for $t {} unsafe impl $crate::MaxEncodedLenAssoc for $t { const LEN: usize = $len; } @@ -136,13 +136,13 @@ unsafe impl MaxEncodedLenAssoc for [u8; N] { impl Encodable for bool { #[inline] - fn length(&self) -> usize { - 1 + fn encode(&self, out: &mut dyn BufMut) { + (*self as u8).encode(out) } #[inline] - fn encode(&self, out: &mut dyn BufMut) { - out.put_u8(*self as u8) + fn length(&self) -> usize { + (*self as u8).length() } } @@ -200,7 +200,7 @@ max_encoded_len_uint!(u64); encodable_uint!(u128); max_encoded_len_uint!(u128); -impl_max_encoded_len!(bool, { ::LEN }); +impl_max_encoded_len!(bool, ::LEN); #[cfg(feature = "std")] mod std_support { @@ -431,7 +431,7 @@ mod tests { encode_list(t, &mut out1); let v = t.to_vec(); - assert_eq!(out1.len(), Encodable::length(&v)); + assert_eq!(out1.len(), v.length()); let mut out2 = BytesMut::new(); v.encode(&mut out2); From 39e127f898d8b013a9b07cb96b947156846fd1b5 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 22 May 2023 19:58:07 +0200 Subject: [PATCH 11/20] proptest --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7b599249f4..23654c1f5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,6 @@ arrayvec = { version = "0.7.2", default-features = false } bytes = { version = "1.4", default-features = false } hex = { package = "const-hex", version = ">=1.5", default-features = false, features = ["alloc"] } once_cell = "1.17" -proptest = { version = "1.1", default-features = false, features = ["alloc"] } +proptest = { version = "1.1", default-features = false } proptest-derive = "0.3" tiny-keccak = "2.0" From a096c94a258193aaecca1d7c0e55d47909c34395 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 22 May 2023 20:00:18 +0200 Subject: [PATCH 12/20] chore: clippy --- crates/primitives/Cargo.toml | 1 - crates/primitives/src/lib.rs | 4 ++++ crates/primitives/src/signed/int.rs | 2 +- crates/sol-types/src/lib.rs | 1 - 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index a247e91653..144f600675 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -40,7 +40,6 @@ proptest-derive = { workspace = true, optional = true } [dev-dependencies] hex-literal.workspace = true -proptest.workspace = true serde_json.workspace = true [features] diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index c528abee7b..cf33bfed17 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -11,6 +11,10 @@ #[macro_use] extern crate alloc; +// Used in Serde tests. +#[cfg(test)] +use serde_json as _; + pub mod aliases; pub use aliases::{ BlockHash, BlockNumber, ChainId, Selector, StorageKey, StorageValue, TxHash, TxIndex, TxNumber, diff --git a/crates/primitives/src/signed/int.rs b/crates/primitives/src/signed/int.rs index 1d9d5236ef..d153f4d418 100644 --- a/crates/primitives/src/signed/int.rs +++ b/crates/primitives/src/signed/int.rs @@ -314,7 +314,7 @@ impl Signed { unsigned_bits + 1 }; - bits as _ + bits as u32 } /// Creates a `Signed` from a sign and an absolute value. Returns the value diff --git a/crates/sol-types/src/lib.rs b/crates/sol-types/src/lib.rs index 2b44778b10..ddd21e5ef4 100644 --- a/crates/sol-types/src/lib.rs +++ b/crates/sol-types/src/lib.rs @@ -154,7 +154,6 @@ #[macro_use] extern crate alloc; -// `unused_crate_dependencies` bug workaround. // This crate is used in tests/compiletest.rs #[cfg(test)] use trybuild as _; From b0406e38ddc16f4ad8ab4396d03b399e513b581e Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Mon, 22 May 2023 20:02:13 +0200 Subject: [PATCH 13/20] are you ok proptest? --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 23654c1f5f..b83f850d98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,6 @@ arrayvec = { version = "0.7.2", default-features = false } bytes = { version = "1.4", default-features = false } hex = { package = "const-hex", version = ">=1.5", default-features = false, features = ["alloc"] } once_cell = "1.17" -proptest = { version = "1.1", default-features = false } +proptest = "1.1" proptest-derive = "0.3" tiny-keccak = "2.0" From 828399dcc38d01a87acb412b6b93db18fabc75d5 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Tue, 23 May 2023 14:45:15 +0200 Subject: [PATCH 14/20] improve serde impls --- crates/primitives/Cargo.toml | 2 +- crates/primitives/src/bits/serde.rs | 28 ++-------------------------- crates/primitives/src/bytes/serde.rs | 6 ++---- 3 files changed, 5 insertions(+), 31 deletions(-) diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 144f600675..86240a6be1 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -31,7 +31,7 @@ derive_more.workspace = true ethers-rlp = { workspace = true, optional = true } # serde -serde = { workspace = true, features = ["derive"], optional = true } +serde = { workspace = true, optional = true } # arbitrary arbitrary = { workspace = true, features = ["derive"], optional = true } diff --git a/crates/primitives/src/bits/serde.rs b/crates/primitives/src/bits/serde.rs index c9f1c2c178..a889cf77ac 100644 --- a/crates/primitives/src/bits/serde.rs +++ b/crates/primitives/src/bits/serde.rs @@ -1,7 +1,5 @@ use super::FixedBytes; -use alloc::string::String; -use core::fmt; -use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; impl Serialize for FixedBytes { fn serialize(&self, serializer: S) -> Result { @@ -12,29 +10,7 @@ impl Serialize for FixedBytes { 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()) + hex::deserialize::<'de, D, [u8; N]>(deserializer).map(Self) } } diff --git a/crates/primitives/src/bytes/serde.rs b/crates/primitives/src/bytes/serde.rs index cd8d616bc6..26a87f2884 100644 --- a/crates/primitives/src/bytes/serde.rs +++ b/crates/primitives/src/bytes/serde.rs @@ -1,17 +1,15 @@ use super::Bytes; -use alloc::string::String; use core::result::Result; impl serde::Serialize for Bytes { fn serialize(&self, serializer: S) -> Result { - serializer.collect_str(&format_args!("{}", self)) + hex::serialize(self, serializer) } } impl<'de> serde::Deserialize<'de> for Bytes { fn deserialize>(deserializer: D) -> Result { - let s = String::deserialize(deserializer)?; - s.parse().map_err(serde::de::Error::custom) + hex::deserialize::<'de, D, alloc::vec::Vec>(deserializer).map(Into::into) } } From e3535d5799c9e62d7c76615de02ce15ae97ffff2 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 31 May 2023 12:30:01 +0200 Subject: [PATCH 15/20] fix: Signed `to_{b,l}e_bytes` --- crates/dyn-abi/src/value.rs | 92 ++++++++++++++----------- crates/primitives/src/bits/fixed.rs | 5 +- crates/primitives/src/bits/macros.rs | 4 +- crates/primitives/src/signed/int.rs | 17 +++-- crates/sol-types/src/types/data_type.rs | 4 +- 5 files changed, 71 insertions(+), 51 deletions(-) diff --git a/crates/dyn-abi/src/value.rs b/crates/dyn-abi/src/value.rs index c23d61fc2d..6da850382a 100644 --- a/crates/dyn-abi/src/value.rs +++ b/crates/dyn-abi/src/value.rs @@ -1,5 +1,5 @@ use crate::{no_std_prelude::*, Word}; -use ethers_primitives::{aliases::*, Address, I256, U256}; +use ethers_primitives::{Address, I256, U256}; /// This type represents a Solidity value that has been decoded into rust. It /// is broadly similar to `serde_json::Value` in that it is an enum of possible @@ -32,7 +32,7 @@ pub enum DynSolValue { name: String, /// The struct's prop names, in declaration order. prop_names: Vec, - /// A inner types. + /// The inner types. tuple: Vec, }, /// A user-defined value type. @@ -185,12 +185,12 @@ impl DynSolValue { /// Encodes the packed value and appends it to the end of a byte array. pub fn encode_packed_to(&self, buf: &mut Vec) { match self { - DynSolValue::Address(addr) => buf.extend_from_slice(addr.as_bytes()), - DynSolValue::Bool(b) => buf.push(*b as u8), - DynSolValue::Bytes(bytes) => buf.extend_from_slice(bytes), - DynSolValue::FixedBytes(word, size) => buf.extend_from_slice(&word.as_bytes()[..*size]), - DynSolValue::Int(num, size) => { - let mut bytes = num.to_be_bytes(); + Self::Address(addr) => buf.extend_from_slice(addr.as_bytes()), + Self::Bool(b) => buf.push(*b as u8), + Self::Bytes(bytes) => buf.extend_from_slice(bytes), + Self::FixedBytes(word, size) => buf.extend_from_slice(&word.as_bytes()[..*size]), + Self::Int(num, size) => { + let mut bytes = num.to_be_bytes::<32>(); let start = 32 - *size; if num.is_negative() { bytes[start] |= 0x80; @@ -199,17 +199,17 @@ impl DynSolValue { } buf.extend_from_slice(&bytes[start..]) } - DynSolValue::Uint(num, size) => { + Self::Uint(num, size) => { buf.extend_from_slice(&num.to_be_bytes::<32>().as_slice()[(32 - *size)..]) } - DynSolValue::String(s) => buf.extend_from_slice(s.as_bytes()), - DynSolValue::Tuple(inner) - | DynSolValue::Array(inner) - | DynSolValue::FixedArray(inner) - | DynSolValue::CustomStruct { tuple: inner, .. } => { - inner.iter().for_each(|v| v.encode_packed_to(buf)); + Self::String(s) => buf.extend_from_slice(s.as_bytes()), + Self::Tuple(inner) + | Self::Array(inner) + | Self::FixedArray(inner) + | Self::CustomStruct { tuple: inner, .. } => { + inner.iter().for_each(|v| v.encode_packed_to(buf)) } - DynSolValue::CustomValue { inner, .. } => buf.extend_from_slice(inner.as_bytes()), + Self::CustomValue { inner, .. } => buf.extend_from_slice(inner.as_bytes()), } } @@ -222,65 +222,77 @@ impl DynSolValue { } impl From
for DynSolValue { + #[inline] fn from(value: Address) -> Self { Self::Address(value) } } impl From for DynSolValue { + #[inline] fn from(value: bool) -> Self { Self::Bool(value) } } impl From> for DynSolValue { + #[inline] fn from(value: Vec) -> Self { Self::Bytes(value) } } macro_rules! impl_from_int { - ($size:ty) => { - impl From<$size> for DynSolValue { - fn from(value: $size) -> Self { - let bits = <$size>::BITS as usize; - let bytes = bits / 8; + ($($t:ty),+) => {$( + impl From<$t> for DynSolValue { + #[inline] + fn from(value: $t) -> Self { + const BITS: usize = <$t>::BITS as usize; + const BYTES: usize = BITS / 8; + const _: () = assert!(BYTES <= 32); + let mut word = if value.is_negative() { ethers_primitives::B256::repeat_byte(0xff) } else { ethers_primitives::B256::default() }; - word[32 - bytes..].copy_from_slice(&value.to_be_bytes()); + word[32 - BYTES..].copy_from_slice(&value.to_be_bytes()); - Self::Int(I256::from_be_bytes(word.into()), bits) + Self::Int(I256::from_be_bytes(word.0), BITS) } } - }; - ($($size:ty),+) => { - $(impl_from_int!($size);)+ - }; + )+}; } -impl_from_int!( - i8, i16, i32, i64, isize, i128, I24, I40, I48, I56, I72, I80, I88, I96, I104, I112, I120, I128, - I136, I144, I152, I160, I168, I176, I184, I192, I200, I208, I216, I224, I232, I240, I248, I256 -); +impl_from_int!(i8, i16, i32, i64, isize, i128); + +impl From for DynSolValue { + #[inline] + fn from(value: I256) -> Self { + Self::Int(value, 256) + } +} macro_rules! impl_from_uint { - ($size:ty) => { - impl From<$size> for DynSolValue { - fn from(value: $size) -> Self { - Self::Uint(U256::from(value), <$size>::BITS as usize) + ($($t:ty),+) => {$( + impl From<$t> for DynSolValue { + #[inline] + fn from(value: $t) -> Self { + Self::Uint(U256::from(value), <$t>::BITS as usize) } } - }; - ($($size:ty),+) => { - $(impl_from_uint!($size);)+ - }; + )+}; } // TODO: more? -impl_from_uint!(u8, u16, u32, u64, usize, u128, U256); +impl_from_uint!(u8, u16, u32, u64, usize, u128); + +impl From for DynSolValue { + #[inline] + fn from(value: U256) -> Self { + Self::Uint(value, 256) + } +} impl From for DynSolValue { fn from(value: String) -> Self { diff --git a/crates/primitives/src/bits/fixed.rs b/crates/primitives/src/bits/fixed.rs index e65d75fda6..65ed2c7a51 100644 --- a/crates/primitives/src/bits/fixed.rs +++ b/crates/primitives/src/bits/fixed.rs @@ -237,7 +237,6 @@ impl FixedBytes { } /// Returns `true` if all bits set in `b` are also set in `self`. - #[inline] pub fn covers(&self, b: &Self) -> bool { &(*b & *self) == b @@ -255,7 +254,7 @@ impl FixedBytes { /// Compile-time equality. NOT constant-time equality. #[inline] - pub const fn const_eq(&self, other: Self) -> bool { + pub const fn const_eq(&self, other: &Self) -> bool { let mut i = 0; loop { if self.0[i] != other.0[i] { @@ -273,7 +272,7 @@ impl FixedBytes { /// Returns `true` if no bits are set. #[inline] pub const fn const_is_zero(&self) -> bool { - self.const_eq(Self::ZERO) + self.const_eq(&Self::ZERO) } /// Computes the bitwise AND of two `FixedBytes`. diff --git a/crates/primitives/src/bits/macros.rs b/crates/primitives/src/bits/macros.rs index c84ad0dbd0..706560bb45 100644 --- a/crates/primitives/src/bits/macros.rs +++ b/crates/primitives/src/bits/macros.rs @@ -209,8 +209,8 @@ macro_rules! wrap_fixed_bytes { } /// Compile-time equality. NOT constant-time equality. - pub const fn const_eq(&self, other: Self) -> bool { - self.0.const_eq(other.0) + pub const fn const_eq(&self, other: &Self) -> bool { + self.0.const_eq(&other.0) } /// Returns `true` if no bits are set. diff --git a/crates/primitives/src/signed/int.rs b/crates/primitives/src/signed/int.rs index d153f4d418..c5932fa93e 100644 --- a/crates/primitives/src/signed/int.rs +++ b/crates/primitives/src/signed/int.rs @@ -123,6 +123,10 @@ impl Signed { /// Number of bits. pub const BITS: usize = BITS; + /// The size of this integer type in bytes. Note that some bits may be + /// forced zero if BITS is not cleanly divisible by eight. + pub const BYTES: usize = Uint::::BYTES; + /// The minimum value. pub const MIN: Self = min(); @@ -397,14 +401,19 @@ impl Signed { (sign, abs) } - /// Convert to a slice in BE format + /// Converts the [`Int`] to a big-endian byte array of size exactly + /// [`Self::BYTES`]. /// /// # Panics /// - /// If the given slice is not exactly 32 bytes long. + /// Panics if the generic parameter `BYTES` is not exactly [`Self::BYTES`]. + /// Ideally this would be a compile time error, but this is blocked by + /// Rust issue [#60551]. + /// + /// [#60551]: https://github.com/rust-lang/rust/issues/60551 #[inline(always)] #[track_caller] - pub fn to_be_bytes(self) -> [u8; 32] { + pub fn to_be_bytes(self) -> [u8; BYTES] { self.0.to_be_bytes() } @@ -415,7 +424,7 @@ impl Signed { /// If the given slice is not exactly 32 bytes long. #[inline(always)] #[track_caller] - pub fn to_le_bytes(self) -> [u8; 32] { + pub fn to_le_bytes(self) -> [u8; BYTES] { self.0.to_le_bytes() } diff --git a/crates/sol-types/src/types/data_type.rs b/crates/sol-types/src/types/data_type.rs index 472d66aced..136d4b713a 100644 --- a/crates/sol-types/src/types/data_type.rs +++ b/crates/sol-types/src/types/data_type.rs @@ -220,7 +220,7 @@ macro_rules! impl_int_sol_type { #[inline] fn tokenize>(rust: B) -> Self::TokenType { - rust.borrow().to_be_bytes().into() + rust.borrow().to_be_bytes::<32>().into() } #[inline] @@ -230,7 +230,7 @@ macro_rules! impl_int_sol_type { #[inline] fn encode_packed_to>(target: &mut Vec, rust: B) { - target.extend(rust.borrow().to_be_bytes()); + target.extend(rust.borrow().to_be_bytes::<32>()); } } )+}; From ce9d158630f23854e004ddde718ef5768980e6ba Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 31 May 2023 12:30:22 +0200 Subject: [PATCH 16/20] fix: alias exports --- crates/primitives/src/aliases.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/primitives/src/aliases.rs b/crates/primitives/src/aliases.rs index adfcc31b09..e077cd557b 100644 --- a/crates/primitives/src/aliases.rs +++ b/crates/primitives/src/aliases.rs @@ -2,7 +2,9 @@ use crate::{Signed, B256}; -pub use ruint::aliases::*; +pub use ruint::aliases::{ + U0, U1, U1024, U128, U16, U160, U192, U2048, U256, U32, U320, U384, U4096, U448, U512, U64, U8, +}; /// The 0-bit signed integer type, capable of representing 0. pub type I0 = Signed<0, 0>; From 774b116565f59465c077232b7760a1f13e3843fa Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 31 May 2023 19:38:55 +0200 Subject: [PATCH 17/20] fix: docs --- crates/primitives/src/signed/int.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/primitives/src/signed/int.rs b/crates/primitives/src/signed/int.rs index c5932fa93e..afc75b814d 100644 --- a/crates/primitives/src/signed/int.rs +++ b/crates/primitives/src/signed/int.rs @@ -401,7 +401,7 @@ impl Signed { (sign, abs) } - /// Converts the [`Int`] to a big-endian byte array of size exactly + /// Converts `self` to a big-endian byte array of size exactly /// [`Self::BYTES`]. /// /// # Panics @@ -417,11 +417,16 @@ impl Signed { self.0.to_be_bytes() } - /// Convert to a slice in LE format + /// Converts `self` to a little-endian byte array of size exactly + /// [`Self::BYTES`]. /// /// # Panics /// - /// If the given slice is not exactly 32 bytes long. + /// Panics if the generic parameter `BYTES` is not exactly [`Self::BYTES`]. + /// Ideally this would be a compile time error, but this is blocked by + /// Rust issue [#60551]. + /// + /// [#60551]: https://github.com/rust-lang/rust/issues/60551 #[inline(always)] #[track_caller] pub fn to_le_bytes(self) -> [u8; BYTES] { From 3bb96fb51724867849eab367c7c16196b979f2d0 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Wed, 31 May 2023 15:16:29 -0700 Subject: [PATCH 18/20] refactor: improve bloom implementation quality (#59) * refactor: improve bloom implementation quality * chore: fmt * fixes: address PR review comments --- crates/primitives/src/bits/bloom.rs | 155 ++++++++++++++-------------- crates/primitives/src/bits/mod.rs | 2 +- crates/primitives/src/lib.rs | 4 +- 3 files changed, 80 insertions(+), 81 deletions(-) diff --git a/crates/primitives/src/bits/bloom.rs b/crates/primitives/src/bits/bloom.rs index c4be171e30..ff7b44ce91 100644 --- a/crates/primitives/src/bits/bloom.rs +++ b/crates/primitives/src/bits/bloom.rs @@ -2,30 +2,47 @@ //! //! Adapted from -use crate::{keccak256, FixedBytes}; -use alloc::borrow::Cow; -use core::mem; +use crate::{keccak256, wrap_fixed_bytes, FixedBytes}; +use core::borrow::{Borrow, BorrowMut}; -/// Length of bloom filter used for Ethereum. -pub const BLOOM_BITS: u32 = 3; +/// Number of bits to set per input in Ethereum bloom filter. +pub const BLOOM_BITS_PER_ITEM: usize = 3; /// Size of the bloom filter in bytes. -pub const BLOOM_SIZE: usize = 256; - -/// A 256-byte Ethereum bloom filter. -pub type Bloom = FixedBytes<256>; +pub const BLOOM_SIZE_BYTES: usize = 256; +/// Size of the bloom filter in bits +pub const BLOOM_SIZE_BITS: usize = BLOOM_SIZE_BYTES * 8; + +/// Size of the keccak256 hash in bytes, used in accrue +const ITEM_HASH_LEN: usize = 32; +/// Mask, used in accrue +const MASK: usize = BLOOM_SIZE_BITS - 1; +/// Number of bytes per item, used in accrue +const ITEM_BYTES: usize = (log2(BLOOM_SIZE_BITS) + 7) / 8; + +// BLOOM_SIZE_BYTES must be a power of 2 +#[allow(clippy::assertions_on_constants)] +const _: () = assert!(BLOOM_SIZE_BYTES.is_power_of_two()); +// Assertion for accrue. This is preserved from parity code, but I do not +// understand its purpose. +#[allow(clippy::assertions_on_constants)] +const _: () = assert!(BLOOM_BITS_PER_ITEM * ITEM_BYTES <= ITEM_HASH_LEN); /// Input to the [`Bloom::accrue`] method. -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum BloomInput<'a> { /// Raw input to be hashed. Raw(&'a [u8]), /// Already hashed input. - Hash(&'a [u8; 32]), + Hash(FixedBytes), } -impl PartialEq> for Bloom { - fn eq(&self, other: &BloomRef<'_>) -> bool { - self.0 == *other.0 +impl BloomInput<'_> { + /// Consume the input, converting it to the hash. + pub fn into_hash(self) -> FixedBytes { + match self { + BloomInput::Raw(raw) => keccak256(raw), + BloomInput::Hash(hash) => hash, + } } } @@ -37,67 +54,62 @@ impl From> for Bloom { } } +wrap_fixed_bytes!(Bloom<256>); + impl Bloom { - /// Returns the underlying data. + /// Returns a reference to the underlying data. + #[inline] + pub const fn data(&self) -> &[u8; BLOOM_SIZE_BYTES] { + &self.0 .0 + } + + /// Returns a mutable reference to the underlying data. #[inline] - pub const fn data(&self) -> &[u8; BLOOM_SIZE] { - &self.0 + pub fn data_mut(&mut self) -> &mut [u8; BLOOM_SIZE_BYTES] { + &mut self.0 .0 } - /// Returns whether the bloom filter contains the given input. + /// Returns whether the bloom filter contains the given input (allowing for + /// false positives) pub fn contains_input(&self, input: BloomInput<'_>) -> bool { let bloom: Bloom = input.into(); - self.contains_bloom(&bloom) + self.contains(bloom) } - /// Returns whether the bloom filter contains the given input. - pub fn contains_bloom<'a, B: Into>>(&self, bloom: B) -> bool { - self.contains_bloom_ref(bloom.into()) + /// True if this bloom filter is a possible superset of the other bloom + /// filter, admitting false positives. + pub const fn const_contains(self, other: Self) -> bool { + // (self & other) == other + other.0.const_eq(&self.0.bit_and(other.0)) } - fn contains_bloom_ref(&self, bloom: BloomRef<'_>) -> bool { - let self_ref: BloomRef<'_> = self.into(); - self_ref.contains_bloom(bloom) + /// Returns whether the bloom filter is a superset of the given bloom + /// filter (allowing for false positives) + pub fn contains>(&self, other: B) -> bool { + self.const_contains(*(other.borrow())) } /// Accrues the input into the bloom filter. pub fn accrue(&mut self, input: BloomInput<'_>) { - let p = BLOOM_BITS; - - let m = self.0.len(); - let bloom_bits = m * 8; - let mask = bloom_bits - 1; - let bloom_bytes = (log2(bloom_bits) + 7) / 8; - - let hash = match input { - BloomInput::Raw(raw) => Cow::Owned(keccak256(raw).0), - BloomInput::Hash(hash) => Cow::Borrowed(hash), - }; - - // must be a power of 2 - assert_eq!(m & (m - 1), 0); - // out of range - assert!(p * bloom_bytes <= hash.len() as u32); + let hash = input.into_hash(); let mut ptr = 0; for _ in 0..3 { let mut index = 0_usize; - for _ in 0..bloom_bytes { + for _ in 0..ITEM_BYTES { index = (index << 8) | hash[ptr] as usize; ptr += 1; } - index &= mask; - self.0[m - 1 - index / 8] |= 1 << (index % 8); + index &= MASK; + self.0[BLOOM_SIZE_BYTES - 1 - index / 8] |= 1 << (index % 8); } } /// Accrues the input into the bloom filter. - pub fn accrue_bloom<'a, B: Into>>(&mut self, bloom: B) { - let bloom_ref: BloomRef<'_> = bloom.into(); - for i in 0..BLOOM_SIZE { - self.0[i] |= bloom_ref.0[i]; - } + pub fn accrue_bloom>(&mut self, bloom: B) { + let other = bloom.borrow(); + *self |= *other; } /// See Section 4.3.1 "Transaction Receipt" of the Ethereum Yellow Paper. @@ -106,55 +118,42 @@ impl Bloom { let h: &[u8; 32] = hash.as_ref(); for i in [0, 2, 4] { let bit = (h[i + 1] as usize + ((h[i] as usize) << 8)) & 0x7FF; - self.0[BLOOM_SIZE - 1 - bit / 8] |= 1 << (bit % 8); + self.0[BLOOM_SIZE_BYTES - 1 - bit / 8] |= 1 << (bit % 8); } } } -/// A reference to a bloom filter. Can be -#[derive(Clone, Copy, Debug)] -pub struct BloomRef<'a>(pub &'a [u8; BLOOM_SIZE]); - -impl<'a> BloomRef<'a> { - /// Returns whether the bloom filter contains the given input. - pub fn contains_bloom<'b, B: Into>>(self, bloom: B) -> bool { - let bloom_ref: BloomRef<'_> = bloom.into(); - self.0.iter().zip(bloom_ref.0).all(|(&a, &b)| (a & b) == b) - } - - /// Returns the underlying data. - #[inline] - pub const fn data(self) -> &'a [u8; BLOOM_SIZE] { - self.0 +impl Borrow<[u8; 256]> for Bloom { + fn borrow(&self) -> &[u8; 256] { + self.data() } +} - /// Returns `true` if bloom only consists only of `0`. - #[inline] - pub fn is_empty(self) -> bool { - self.0.iter().all(|x| *x == 0) +impl Borrow<[u8]> for Bloom { + fn borrow(&self) -> &[u8] { + self.data() } } -impl<'a> From<&'a [u8; BLOOM_SIZE]> for BloomRef<'a> { - fn from(data: &'a [u8; BLOOM_SIZE]) -> Self { - Self(data) +impl BorrowMut<[u8; 256]> for Bloom { + fn borrow_mut(&mut self) -> &mut [u8; 256] { + self.data_mut() } } -impl<'a> From<&'a Bloom> for BloomRef<'a> { - fn from(bloom: &'a Bloom) -> Self { - Self(&bloom.0) +impl BorrowMut<[u8]> for Bloom { + fn borrow_mut(&mut self) -> &mut [u8] { + self.data_mut() } } #[inline] -const fn log2(x: usize) -> u32 { +const fn log2(x: usize) -> usize { if x <= 1 { return 0 } - let n = x.leading_zeros(); - mem::size_of::() as u32 * 8 - n + (usize::BITS - x.leading_zeros()) as usize } #[cfg(test)] diff --git a/crates/primitives/src/bits/mod.rs b/crates/primitives/src/bits/mod.rs index 9d1fcd382e..89be3ff5c1 100644 --- a/crates/primitives/src/bits/mod.rs +++ b/crates/primitives/src/bits/mod.rs @@ -2,7 +2,7 @@ mod address; pub use address::{Address, AddressError}; mod bloom; -pub use bloom::{Bloom, BloomInput, BloomRef, BLOOM_BITS, BLOOM_SIZE}; +pub use bloom::{Bloom, BloomInput, BLOOM_BITS_PER_ITEM, BLOOM_SIZE_BITS, BLOOM_SIZE_BYTES}; mod fixed; pub use fixed::FixedBytes; diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index cf33bfed17..f5d48d7b34 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -23,8 +23,8 @@ pub use aliases::{ mod bits; pub use bits::{ - Address, AddressError, Bloom, BloomInput, BloomRef, FixedBytes, B128, B256, B512, B64, - BLOOM_BITS, BLOOM_SIZE, + Address, AddressError, Bloom, BloomInput, FixedBytes, B128, B256, B512, B64, + BLOOM_BITS_PER_ITEM, BLOOM_SIZE_BITS, BLOOM_SIZE_BYTES, }; mod bytes; From 0ac5b259e1d71125fe4abdb932abd603f171853a Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 1 Jun 2023 00:26:26 +0200 Subject: [PATCH 19/20] chore: move `create{,2}_address*` utils to assoc methods on `Address` --- crates/primitives/src/bits/address.rs | 206 ++++++++++++++++++++++---- crates/primitives/src/utils.rs | 154 +------------------ 2 files changed, 179 insertions(+), 181 deletions(-) diff --git a/crates/primitives/src/bits/address.rs b/crates/primitives/src/bits/address.rs index 056a72dac3..6c8d3896a8 100644 --- a/crates/primitives/src/bits/address.rs +++ b/crates/primitives/src/bits/address.rs @@ -67,6 +67,40 @@ impl Address { FixedBytes(buf) } + /// Parse an Ethereum address, verifying its [EIP-55] checksum. + /// + /// You can optionally specify an [EIP-155 chain ID] to check the address + /// using [EIP-1191]. + /// + /// # Errors + /// + /// If the provided string does not match the expected checksum. + /// + /// [EIP-55]: https://eips.ethereum.org/EIPS/eip-55 + /// [EIP-155 chain ID]: https://eips.ethereum.org/EIPS/eip-155 + /// [EIP-1191]: https://eips.ethereum.org/EIPS/eip-1191 + pub fn parse_checksummed>( + s: S, + chain_id: Option, + ) -> Result { + fn inner(s: &str, chain_id: Option) -> Result { + if !s.starts_with("0x") { + return Err(AddressError::Hex(hex::FromHexError::InvalidStringLength)) + } + + let address: Address = s.parse()?; + let buf = &mut [0; 42]; + let ss = address.to_checksum_raw(buf, chain_id); + if s == ss { + Ok(address) + } else { + Err(AddressError::InvalidChecksum) + } + } + + inner(s.as_ref(), chain_id) + } + /// Encodes an Ethereum address to its [EIP-55] checksum. /// /// You can optionally specify an [EIP-155 chain ID] to encode the address @@ -141,44 +175,76 @@ impl Address { self.to_checksum_raw(&mut buf, chain_id).to_string() } - /// Parse an Ethereum address, verifying its [EIP-55] checksum. - /// - /// You can optionally specify an [EIP-155 chain ID] to check the address - /// using [EIP-1191]. - /// - /// # Errors + /// Computes the `create` address for the given address and nonce. /// - /// If the provided string does not match the expected checksum. + /// The address for an Ethereum contract is deterministically computed from + /// the address of its creator (sender) and how many transactions the + /// creator has sent (nonce). The sender and nonce are RLP encoded and + /// then hashed with [`keccak256`]. + #[cfg(feature = "rlp")] + pub fn create>(sender: T, nonce: u64) -> Address { + fn create(sender: &[u8; 20], nonce: u64) -> Address { + use ethers_rlp::Encodable; + + let mut out = alloc::vec::Vec::with_capacity(64); + let buf = &mut out as &mut dyn bytes::BufMut; + sender.encode(buf); + let _ = nonce; + #[cfg(TODO_UINT_RLP)] + crate::U256::from(nonce).encode(buf); + let hash = keccak256(&out); + Address::from_word(hash) + } + + create(sender.borrow(), nonce) + } + + /// Returns the `CREATE2` address of a smart contract as specified in + /// [EIP1014](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1014.md): /// - /// [EIP-55]: https://eips.ethereum.org/EIPS/eip-55 - /// [EIP-155 chain ID]: https://eips.ethereum.org/EIPS/eip-155 - /// [EIP-1191]: https://eips.ethereum.org/EIPS/eip-1191 - pub fn parse_checksummed>( - s: S, - chain_id: Option, - ) -> Result { - fn inner(s: &str, chain_id: Option) -> Result { - if !s.starts_with("0x") { - return Err(AddressError::Hex(hex::FromHexError::InvalidStringLength)) - } + /// `keccak256(0xff ++ address ++ salt ++ keccak256(init_code))[12:]` + pub fn create2_from_code(address: A, salt: S, init_code: C) -> Address + where + A: Borrow<[u8; 20]>, + S: Borrow<[u8; 32]>, + C: AsRef<[u8]>, + { + Self::create2(address, salt, keccak256(init_code.as_ref()).0) + } - let address: Address = s.parse()?; - let buf = &mut [0; 42]; - let ss = address.to_checksum_raw(buf, chain_id); - if s == ss { - Ok(address) - } else { - Err(AddressError::InvalidChecksum) - } + /// Returns the `CREATE2` address of a smart contract as specified in + /// [EIP1014](https://eips.ethereum.org/EIPS/eip-1014), + /// taking the pre-computed hash of the init code as input: + /// + /// `keccak256(0xff ++ address ++ salt ++ init_code_hash)[12:]` + pub fn create2(address: A, salt: S, init_code_hash: H) -> Address + where + // not `AsRef` because `[u8; N]` does not implement `AsRef<[u8; N]>` + A: Borrow<[u8; 20]>, + S: Borrow<[u8; 32]>, + H: Borrow<[u8; 32]>, + { + fn create2_address( + address: &[u8; 20], + salt: &[u8; 32], + init_code_hash: &[u8; 32], + ) -> Address { + let mut bytes = [0; 85]; + bytes[0] = 0xff; + bytes[1..21].copy_from_slice(address); + bytes[21..53].copy_from_slice(salt); + bytes[53..85].copy_from_slice(init_code_hash); + let hash = keccak256(bytes); + Address::from_word(hash) } - inner(s.as_ref(), chain_id) + create2_address(address.borrow(), salt.borrow(), init_code_hash.borrow()) } } #[cfg(test)] mod test { - use super::Address; + use super::*; #[test] fn parse() { @@ -272,4 +338,88 @@ mod test { } } } + + #[test] + #[ignore = "Uint RLP"] + #[cfg(feature = "rlp")] + fn create() { + // http://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed + let from = "6ac7ea33f8831ea9dcc53393aaa88b25a785dbf0" + .parse::
() + .unwrap(); + for (nonce, expected) in [ + "cd234a471b72ba2f1ccf0a70fcaba648a5eecd8d", + "343c43a37d37dff08ae8c4a11544c718abb4fcf8", + "f778b86fa74e846c4f0a1fbd1335fe81c00a0c91", + "fffd933a0bc612844eaf0c6fe3e5b8e9b6c1d19c", + ] + .iter() + .enumerate() + { + let address = Address::create(from, nonce as u64); + assert_eq!(address, expected.parse::
().unwrap()); + } + } + + // Test vectors from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1014.md#examples + #[test] + fn eip_1014_create2() { + for (from, salt, init_code, expected) in &[ + ( + "0000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "00", + "4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38", + ), + ( + "deadbeef00000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "00", + "B928f69Bb1D91Cd65274e3c79d8986362984fDA3", + ), + ( + "deadbeef00000000000000000000000000000000", + "000000000000000000000000feed000000000000000000000000000000000000", + "00", + "D04116cDd17beBE565EB2422F2497E06cC1C9833", + ), + ( + "0000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "deadbeef", + "70f2b2914A2a4b783FaEFb75f459A580616Fcb5e", + ), + ( + "00000000000000000000000000000000deadbeef", + "00000000000000000000000000000000000000000000000000000000cafebabe", + "deadbeef", + "60f3f640a8508fC6a86d45DF051962668E1e8AC7", + ), + ( + "00000000000000000000000000000000deadbeef", + "00000000000000000000000000000000000000000000000000000000cafebabe", + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", + "1d8bfDC5D46DC4f61D6b6115972536eBE6A8854C", + ), + ( + "0000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "", + "E33C0C7F7df4809055C3ebA6c09CFe4BaF1BD9e0", + ), + ] { + let from = from.parse::
().unwrap(); + + let salt = hex::decode(salt).unwrap(); + let salt: [u8; 32] = salt.try_into().unwrap(); + + let init_code = hex::decode(init_code).unwrap(); + let init_code_hash = keccak256(&init_code); + + let expected = expected.parse::
().unwrap(); + + assert_eq!(expected, Address::create2(from, salt, init_code_hash)); + assert_eq!(expected, Address::create2_from_code(from, salt, init_code)); + } + } } diff --git a/crates/primitives/src/utils.rs b/crates/primitives/src/utils.rs index 79f8745419..fd2b32e25e 100644 --- a/crates/primitives/src/utils.rs +++ b/crates/primitives/src/utils.rs @@ -1,5 +1,4 @@ -use crate::{bits::FixedBytes, Address}; -use core::borrow::Borrow; +use crate::bits::FixedBytes; pub use tiny_keccak::{Hasher, Keccak}; @@ -17,154 +16,3 @@ pub fn keccak256>(bytes: T) -> FixedBytes<32> { keccak256(bytes.as_ref()) } - -/// Computes the `create` address for the given address and nonce. -/// -/// The address for an Ethereum contract is deterministically computed from the -/// address of its creator (sender) and how many transactions the creator has -/// sent (nonce). The sender and nonce are RLP encoded and then hashed with -/// [`keccak256`]. -#[cfg(feature = "rlp")] -pub fn create_address>(sender: T, nonce: u64) -> Address { - fn create_address(sender: &[u8; 20], nonce: u64) -> Address { - use ethers_rlp::Encodable; - - let mut out = alloc::vec::Vec::with_capacity(64); - let buf = &mut out as &mut dyn bytes::BufMut; - sender.encode(buf); - let _ = nonce; - #[cfg(TODO_UINT_RLP)] - crate::U256::from(nonce).encode(buf); - let hash = keccak256(&out); - Address::from_word(hash) - } - - create_address(sender.borrow(), nonce) -} - -/// Returns the `CREATE2` address of a smart contract as specified in -/// [EIP1014](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1014.md): -/// -/// `keccak256(0xff ++ address ++ salt ++ keccak256(init_code))[12:]` -pub fn create2_address_from_code(address: A, salt: S, init_code: C) -> Address -where - A: Borrow<[u8; 20]>, - S: Borrow<[u8; 32]>, - C: AsRef<[u8]>, -{ - create2_address(address, salt, keccak256(init_code.as_ref()).0) -} - -/// Returns the `CREATE2` address of a smart contract as specified in -/// [EIP1014](https://eips.ethereum.org/EIPS/eip-1014), -/// taking the pre-computed hash of the init code as input: -/// -/// `keccak256(0xff ++ address ++ salt ++ init_code_hash)[12:]` -pub fn create2_address(address: A, salt: S, init_code_hash: H) -> Address -where - // not `AsRef` because `[u8; N]` does not implement `AsRef<[u8; N]>` - A: Borrow<[u8; 20]>, - S: Borrow<[u8; 32]>, - H: Borrow<[u8; 32]>, -{ - fn create2_address(address: &[u8; 20], salt: &[u8; 32], init_code_hash: &[u8; 32]) -> Address { - let mut bytes = [0; 85]; - bytes[0] = 0xff; - bytes[1..21].copy_from_slice(address); - bytes[21..53].copy_from_slice(salt); - bytes[53..85].copy_from_slice(init_code_hash); - let hash = keccak256(bytes); - Address::from_word(hash) - } - - create2_address(address.borrow(), salt.borrow(), init_code_hash.borrow()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - #[ignore = "Uint RLP"] - #[cfg(feature = "rlp")] - fn test_create_address() { - // http://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed - let from = "6ac7ea33f8831ea9dcc53393aaa88b25a785dbf0" - .parse::
() - .unwrap(); - for (nonce, expected) in [ - "cd234a471b72ba2f1ccf0a70fcaba648a5eecd8d", - "343c43a37d37dff08ae8c4a11544c718abb4fcf8", - "f778b86fa74e846c4f0a1fbd1335fe81c00a0c91", - "fffd933a0bc612844eaf0c6fe3e5b8e9b6c1d19c", - ] - .iter() - .enumerate() - { - let address = create_address(from, nonce as u64); - assert_eq!(address, expected.parse::
().unwrap()); - } - } - - // Test vectors from https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1014.md#examples - #[test] - fn test_create2_address() { - for (from, salt, init_code, expected) in &[ - ( - "0000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000000", - "00", - "4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38", - ), - ( - "deadbeef00000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000000", - "00", - "B928f69Bb1D91Cd65274e3c79d8986362984fDA3", - ), - ( - "deadbeef00000000000000000000000000000000", - "000000000000000000000000feed000000000000000000000000000000000000", - "00", - "D04116cDd17beBE565EB2422F2497E06cC1C9833", - ), - ( - "0000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000000", - "deadbeef", - "70f2b2914A2a4b783FaEFb75f459A580616Fcb5e", - ), - ( - "00000000000000000000000000000000deadbeef", - "00000000000000000000000000000000000000000000000000000000cafebabe", - "deadbeef", - "60f3f640a8508fC6a86d45DF051962668E1e8AC7", - ), - ( - "00000000000000000000000000000000deadbeef", - "00000000000000000000000000000000000000000000000000000000cafebabe", - "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", - "1d8bfDC5D46DC4f61D6b6115972536eBE6A8854C", - ), - ( - "0000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000000", - "", - "E33C0C7F7df4809055C3ebA6c09CFe4BaF1BD9e0", - ), - ] { - let from = from.parse::
().unwrap(); - - let salt = hex::decode(salt).unwrap(); - let salt: [u8; 32] = salt.try_into().unwrap(); - - let init_code = hex::decode(init_code).unwrap(); - let init_code_hash = keccak256(&init_code); - - let expected = expected.parse::
().unwrap(); - - assert_eq!(expected, create2_address(from, salt, init_code_hash)); - assert_eq!(expected, create2_address_from_code(from, salt, init_code)); - } - } -} From 5d945a8cb3d255458d88132dc9457ed236ab9bcc Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 1 Jun 2023 00:28:19 +0200 Subject: [PATCH 20/20] test: `FixedBytes` serde --- crates/primitives/src/bits/serde.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/primitives/src/bits/serde.rs b/crates/primitives/src/bits/serde.rs index a889cf77ac..b51bb9024d 100644 --- a/crates/primitives/src/bits/serde.rs +++ b/crates/primitives/src/bits/serde.rs @@ -20,9 +20,9 @@ mod tests { #[test] fn serde() { - let bytes = FixedBytes([1, 35, 69, 103, 137, 171, 205, 239]); + let bytes = FixedBytes([0, 0, 0, 0, 1, 35, 69, 103, 137, 171, 205, 239]); let ser = serde_json::to_string(&bytes).unwrap(); - assert_eq!(ser, "\"0x0123456789abcdef\""); - assert_eq!(serde_json::from_str::>(&ser).unwrap(), bytes); + assert_eq!(ser, "\"0x000000000123456789abcdef\""); + assert_eq!(serde_json::from_str::>(&ser).unwrap(), bytes); } }