From 70edd5087770ea7d97c3261921fb75fac8083258 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Thu, 1 Jun 2023 10:21:26 +0200 Subject: [PATCH] feat: primitive utils and improvements (#52) * feat: improve primitives * feat: implement Arbitrary for Bytes and wrapped FixedBytes * feat: improve primitives utils * fix: hex breaking change * fixes * chore: clippy * serde * feat: add more serde utils and impls * ignore test * fix: RLP `Encode` impl for `FixedBytes` and `bool` * proptest * chore: clippy * are you ok proptest? * improve serde impls * fix: Signed `to_{b,l}e_bytes` * fix: alias exports * fix: docs * refactor: improve bloom implementation quality (#59) * refactor: improve bloom implementation quality * chore: fmt * fixes: address PR review comments * chore: move `create{,2}_address*` utils to assoc methods on `Address` * test: `FixedBytes` serde --------- Co-authored-by: James Prestwich --- Cargo.toml | 15 +- crates/dyn-abi/src/eip712/typed_data.rs | 6 +- crates/dyn-abi/src/lib.rs | 8 +- crates/dyn-abi/src/value.rs | 94 +++--- crates/primitives/Cargo.toml | 32 +- crates/primitives/src/{signed => }/aliases.rs | 36 ++- crates/primitives/src/bits/address.rs | 248 ++++++++++++--- crates/primitives/src/bits/bloom.rs | 202 ++++++++++++ crates/primitives/src/bits/fixed.rs | 153 +++++++--- crates/primitives/src/bits/impl_core.rs | 1 + crates/primitives/src/bits/macros.rs | 191 +++++++++--- crates/primitives/src/bits/mod.rs | 21 +- crates/primitives/src/bits/serde.rs | 28 ++ crates/primitives/src/bits/serialize.rs | 23 -- crates/primitives/src/bytes/mod.rs | 270 ++++++++++++++++ crates/primitives/src/bytes/rlp.rs | 18 ++ crates/primitives/src/bytes/serde.rs | 27 ++ crates/primitives/src/lib.rs | 56 ++-- crates/primitives/src/signed/int.rs | 287 ++++++++---------- crates/primitives/src/signed/mod.rs | 19 +- crates/primitives/src/signed/ops.rs | 40 +-- crates/primitives/src/signed/serde.rs | 55 ++++ crates/primitives/src/signed/utils.rs | 5 +- crates/primitives/src/utils.rs | 10 +- crates/sol-macro/Cargo.toml | 2 +- crates/sol-types/Cargo.toml | 2 +- crates/sol-types/src/coder/impl_core.rs | 12 +- crates/sol-types/src/eip712.rs | 12 +- crates/sol-types/src/lib.rs | 27 +- crates/sol-types/src/types/data_type.rs | 6 +- crates/sol-types/src/util.rs | 54 ---- crates/sol-types/tests/doc_structs.rs | 2 +- crates/sol-types/tests/doc_types.rs | 2 +- crates/sol-types/tests/sol.rs | 12 +- 34 files changed, 1422 insertions(+), 554 deletions(-) rename crates/primitives/src/{signed => }/aliases.rs (71%) 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/bits/serde.rs delete mode 100644 crates/primitives/src/bits/serialize.rs create mode 100644 crates/primitives/src/bytes/mod.rs create mode 100644 crates/primitives/src/bytes/rlp.rs create mode 100644 crates/primitives/src/bytes/serde.rs create mode 100644 crates/primitives/src/signed/serde.rs diff --git a/Cargo.toml b/Cargo.toml index 61d0d5fa8..b83f850d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,20 +24,25 @@ 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" quote = "1.0" syn = "2.0" +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.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"] } +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/eip712/typed_data.rs b/crates/dyn-abi/src/eip712/typed_data.rs index 96f118922..099944fec 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": { diff --git a/crates/dyn-abi/src/lib.rs b/crates/dyn-abi/src/lib.rs index 3ac2bcf74..270ca5b84 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 7af45a58b..6da850382 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. @@ -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 { @@ -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/Cargo.toml b/crates/primitives/Cargo.toml index 968d5e019..86240a6be 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 @@ -15,34 +15,38 @@ repository.workspace = true [dependencies] # eth -ruint = { workspace = true, features = ["rlp", "serde"] } +ruint = { workspace = true, features = ["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 +# serde +serde = { workspace = true, 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 +serde_json.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", "ruint/fastrlp"] +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 71% rename from crates/primitives/src/signed/aliases.rs rename to crates/primitives/src/aliases.rs index bb5679501..e077cd557 100644 --- a/crates/primitives/src/signed/aliases.rs +++ b/crates/primitives/src/aliases.rs @@ -1,4 +1,10 @@ -use super::Signed; +//! Type aliases for common primitive types. + +use crate::{Signed, B256}; + +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>; @@ -101,3 +107,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/address.rs b/crates/primitives/src/bits/address.rs index 40025fe62..6c8d3896a 100644 --- a/crates/primitives/src/bits/address.rs +++ b/crates/primitives/src/bits/address.rs @@ -39,26 +39,68 @@ wrap_fixed_bytes!( ); impl Borrow<[u8; 20]> for Address { + #[inline] fn borrow(&self) -> &[u8; 20] { &self.0 } } -impl From
for [u8; 20] { - fn from(addr: Address) -> Self { - addr.0.into() - } -} - 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) + } + + /// 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 @@ -77,10 +119,11 @@ impl Address { addr_buf[1] = b'x'; hex::encode_to_slice(self.as_bytes(), &mut addr_buf[2..]).unwrap(); - let hash = match chain_id { + let mut storage; + let to_hash = match chain_id { Some(chain_id) => { // A decimal `u64` string is at most 20 bytes long: round up 20 + 42 to 64. - let mut prefixed = [0u8; 64]; + storage = [0u8; 64]; // Format the `chain_id` into a stack-allocated buffer using `itoa` let mut temp = itoa::Buffer::new(); @@ -89,19 +132,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(); @@ -131,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() { @@ -262,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/bits/bloom.rs b/crates/primitives/src/bits/bloom.rs new file mode 100644 index 000000000..ff7b44ce9 --- /dev/null +++ b/crates/primitives/src/bits/bloom.rs @@ -0,0 +1,202 @@ +//! Bloom type. +//! +//! Adapted from + +use crate::{keccak256, wrap_fixed_bytes, FixedBytes}; +use core::borrow::{Borrow, BorrowMut}; + +/// 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_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, Clone, Copy)] +pub enum BloomInput<'a> { + /// Raw input to be hashed. + Raw(&'a [u8]), + /// Already hashed input. + Hash(FixedBytes), +} + +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, + } + } +} + +impl From> for Bloom { + fn from(input: BloomInput<'_>) -> Bloom { + let mut bloom = Bloom::default(); + bloom.accrue(input); + bloom + } +} + +wrap_fixed_bytes!(Bloom<256>); + +impl Bloom { + /// 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 fn data_mut(&mut self) -> &mut [u8; BLOOM_SIZE_BYTES] { + &mut self.0 .0 + } + + /// 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) + } + + /// 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)) + } + + /// 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 hash = input.into_hash(); + + let mut ptr = 0; + + for _ in 0..3 { + let mut index = 0_usize; + for _ in 0..ITEM_BYTES { + index = (index << 8) | hash[ptr] as usize; + ptr += 1; + } + index &= MASK; + self.0[BLOOM_SIZE_BYTES - 1 - index / 8] |= 1 << (index % 8); + } + } + + /// Accrues the input into the bloom filter. + 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. + 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_BYTES - 1 - bit / 8] |= 1 << (bit % 8); + } + } +} + +impl Borrow<[u8; 256]> for Bloom { + fn borrow(&self) -> &[u8; 256] { + self.data() + } +} + +impl Borrow<[u8]> for Bloom { + fn borrow(&self) -> &[u8] { + self.data() + } +} + +impl BorrowMut<[u8; 256]> for Bloom { + fn borrow_mut(&mut self) -> &mut [u8; 256] { + self.data_mut() + } +} + +impl BorrowMut<[u8]> for Bloom { + fn borrow_mut(&mut self) -> &mut [u8] { + self.data_mut() + } +} + +#[inline] +const fn log2(x: usize) -> usize { + if x <= 1 { + return 0 + } + + (usize::BITS - x.leading_zeros()) as usize +} + +#[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 ec944e445..65ed2c7a5 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. /// @@ -8,32 +11,19 @@ use derive_more::{AsMut, AsRef, 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( - AsRef, - AsMut, - 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 last byte set to `x`. + #[inline] + pub const fn with_last_byte(x: u8) -> Self { + let mut bytes = [0u8; N]; + bytes[N - 1] = 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,13 +231,12 @@ 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`. - #[inline] pub fn covers(&self, b: &Self) -> bool { &(*b & *self) == b @@ -209,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] { @@ -227,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`. @@ -401,6 +446,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 000000000..805446a29 --- /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 ed10c2255..706560bb4 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,13 +39,13 @@ 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, - $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,83 +54,113 @@ 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>); + 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 } } 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 last byte set to `x`. + #[inline] + 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. + #[inline] + pub fn random() -> Self { + 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 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 { $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] { @@ -171,48 +201,55 @@ 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) + 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)) } } - 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); }; } @@ -221,19 +258,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) } } }; @@ -251,15 +291,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) } } }; @@ -271,3 +315,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/bits/mod.rs b/crates/primitives/src/bits/mod.rs index 7cca3148a..89be3ff5c 100644 --- a/crates/primitives/src/bits/mod.rs +++ b/crates/primitives/src/bits/mod.rs @@ -1,21 +1,34 @@ mod address; pub use address::{Address, AddressError}; +mod bloom; +pub use bloom::{Bloom, BloomInput, BLOOM_BITS_PER_ITEM, BLOOM_SIZE_BITS, BLOOM_SIZE_BYTES}; + mod fixed; 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. +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/bits/serde.rs b/crates/primitives/src/bits/serde.rs new file mode 100644 index 000000000..b51bb9024 --- /dev/null +++ b/crates/primitives/src/bits/serde.rs @@ -0,0 +1,28 @@ +use super::FixedBytes; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +impl Serialize for FixedBytes { + fn serialize(&self, serializer: S) -> Result { + let mut buf = hex::Buffer::::new(); + serializer.serialize_str(buf.format(&self.0)) + } +} + +impl<'de, const N: usize> Deserialize<'de> for FixedBytes { + fn deserialize>(deserializer: D) -> Result { + hex::deserialize::<'de, D, [u8; N]>(deserializer).map(Self) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serde() { + 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, "\"0x000000000123456789abcdef\""); + assert_eq!(serde_json::from_str::>(&ser).unwrap(), bytes); + } +} diff --git a/crates/primitives/src/bits/serialize.rs b/crates/primitives/src/bits/serialize.rs deleted file mode 100644 index e6825c402..000000000 --- a/crates/primitives/src/bits/serialize.rs +++ /dev/null @@ -1,23 +0,0 @@ -use super::FixedBytes; -use alloc::string::String; -use core::result::Result; - -impl serde::Serialize for FixedBytes { - fn serialize(&self, serializer: S) -> Result { - serializer.collect_str(&format_args!("{}", self)) - } -} - -impl<'de, const N: usize> serde::Deserialize<'de> for FixedBytes { - fn deserialize>(deserializer: D) -> Result { - let expected = 2 * N + 2; - let s = String::deserialize(deserializer)?; - if s.len() != expected { - return Err(serde::de::Error::custom(format!( - "Expected exactly {expected} chars, including a 0x prefix. Got {}", - s.len() - ))) - } - s.parse().map_err(serde::de::Error::custom) - } -} diff --git a/crates/primitives/src/bytes/mod.rs b/crates/primitives/src/bytes/mod.rs new file mode 100644 index 000000000..afc26beb4 --- /dev/null +++ b/crates/primitives/src/bytes/mod.rs @@ -0,0 +1,270 @@ +use alloc::{string::String, vec::Vec}; +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)] +pub struct Bytes(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(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::*; + + #[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_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/rlp.rs b/crates/primitives/src/bytes/rlp.rs new file mode 100644 index 000000000..60702bca5 --- /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/bytes/serde.rs b/crates/primitives/src/bytes/serde.rs new file mode 100644 index 000000000..26a87f288 --- /dev/null +++ b/crates/primitives/src/bytes/serde.rs @@ -0,0 +1,27 @@ +use super::Bytes; +use core::result::Result; + +impl serde::Serialize for Bytes { + fn serialize(&self, serializer: S) -> Result { + hex::serialize(self, serializer) + } +} + +impl<'de> serde::Deserialize<'de> for Bytes { + fn deserialize>(deserializer: D) -> Result { + hex::deserialize::<'de, D, alloc::vec::Vec>(deserializer).map(Into::into) + } +} + +#[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); + } +} diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index e785fb574..f5d48d7b3 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, @@ -5,33 +6,54 @@ 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; +// 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, + 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, FixedBytes, B128, B256, B512, B64, + BLOOM_BITS_PER_ITEM, BLOOM_SIZE_BITS, BLOOM_SIZE_BYTES, +}; + +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; +pub mod private { + pub use derive_more; + pub use getrandom; + + #[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; +} diff --git a/crates/primitives/src/signed/int.rs b/crates/primitives/src/signed/int.rs index 1334f784d..afc75b814 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; @@ -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`] @@ -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 @@ -119,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(); @@ -134,36 +142,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. @@ -340,7 +318,7 @@ impl Signed { unsigned_bits + 1 }; - bits as _ + bits as u32 } /// Creates a `Signed` from a sign and an absolute value. Returns the value @@ -423,25 +401,35 @@ impl Signed { (sign, abs) } - /// Convert to a slice in BE format + /// Converts `self` 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() } - /// 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; 32] { + pub fn to_le_bytes(self) -> [u8; BYTES] { self.0.to_le_bytes() } @@ -493,33 +481,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::{I0, I1, I128, I160, I192, I256}, - BigIntConversionError, ParseSignedError, - }; + 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>; @@ -532,17 +503,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 +777,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 +826,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 +859,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 +892,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 +914,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 +952,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 +964,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 +975,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 +987,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 +1024,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 +1036,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 +1062,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 +1088,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 +1096,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 +1144,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 +1213,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 +1245,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 +1281,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() @@ -1375,11 +1325,12 @@ mod tests { } #[test] + #[cfg(feature = "std")] fn division_by_zero() { 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 +1352,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 +1399,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 +1409,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 +1418,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 +1430,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 ); }; @@ -1506,17 +1454,18 @@ mod tests { } #[test] + #[cfg(feature = "std")] fn div_euclid_by_zero() { 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 ); }); @@ -1535,11 +1484,12 @@ mod tests { } #[test] + #[cfg(feature = "std")] fn div_euclid_overflow() { 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()); }; @@ -1552,11 +1502,12 @@ mod tests { } #[test] + #[cfg(feature = "std")] fn mod_by_zero() { 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 +1530,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 +1538,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 +1546,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 +1554,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 +1598,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 505899f1c..fdeff3dc5 100644 --- a/crates/primitives/src/signed/mod.rs +++ b/crates/primitives/src/signed/mod.rs @@ -1,16 +1,12 @@ //! 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}; -/// A simple [`Sign`] enum, for dealing with integer signs. -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,8 +14,13 @@ pub use int::Signed; /// Operation implementations. mod ops; -/// Conversion implementations. -mod conversions; +/// 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; diff --git a/crates/primitives/src/signed/ops.rs b/crates/primitives/src/signed/ops.rs index 4e777b37e..dcdc1ac86 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/serde.rs b/crates/primitives/src/signed/serde.rs new file mode 100644 index 000000000..de412e56f --- /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/primitives/src/signed/utils.rs b/crates/primitives/src/signed/utils.rs index ece8fd5a1..4a64957bb 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 82b092c4a..fd2b32e25 100644 --- a/crates/primitives/src/utils.rs +++ b/crates/primitives/src/utils.rs @@ -2,9 +2,11 @@ use crate::bits::FixedBytes; pub use tiny_keccak::{Hasher, Keccak}; -/// Simple interface to the `keccak256` hash function. -pub fn keccak256(bytes: impl AsRef<[u8]>) -> FixedBytes<32> { - fn internal(bytes: &[u8]) -> FixedBytes<32> { +/// Simple interface to the [`keccak256`] hash function. +/// +/// [`keccak256`]: https://en.wikipedia.org/wiki/SHA-3 +pub fn keccak256>(bytes: T) -> FixedBytes<32> { + fn keccak256(bytes: &[u8]) -> FixedBytes<32> { let mut output = [0u8; 32]; let mut hasher = Keccak::v256(); hasher.update(bytes); @@ -12,5 +14,5 @@ pub fn keccak256(bytes: impl AsRef<[u8]>) -> FixedBytes<32> { output.into() } - internal(bytes.as_ref()) + keccak256(bytes.as_ref()) } diff --git a/crates/sol-macro/Cargo.toml b/crates/sol-macro/Cargo.toml index 4bbfca134..7d2ba5481 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 9c2c5f7b1..6515bfe38 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/coder/impl_core.rs b/crates/sol-types/src/coder/impl_core.rs index ccc99a971..f6ba1fa1e 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/eip712.rs b/crates/sol-types/src/eip712.rs index cb6505551..4de5198de 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, @@ -685,8 +681,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 868d0fe65..9e5d12fa4 100644 --- a/crates/sol-types/src/lib.rs +++ b/crates/sol-types/src/lib.rs @@ -7,21 +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)] -#![doc(test( - no_crate_inject, - attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) -))] - //! Solidity type modeling and ABI coding implementation. //! //! This crate provides tools for expressing Solidity types in Rust, and for @@ -155,10 +140,20 @@ //! 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; -// `unused_crate_dependencies` bug workaround. // This crate is used in tests/compiletest.rs #[cfg(test)] use trybuild as _; diff --git a/crates/sol-types/src/types/data_type.rs b/crates/sol-types/src/types/data_type.rs index 4ae69727d..136d4b713 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>()); } } )+}; @@ -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/src/util.rs b/crates/sol-types/src/util.rs index 5ae711a88..0b8b0a278 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; diff --git a/crates/sol-types/tests/doc_structs.rs b/crates/sol-types/tests/doc_structs.rs index 7a4777c36..5b836b50f 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 e1033fd35..f410ac220 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 5f190bee3..24d485671 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, }, ],