From b713986d190589dd46ed8bb3c8e0c87bc62b6bc4 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 11 Jan 2023 19:08:34 +0100 Subject: [PATCH 1/4] perf(utils): avoid unnecessary allocations --- ethers-core/src/utils/hash.rs | 42 ++++++++++++++------------ ethers-core/src/utils/mod.rs | 55 +++++++++++++++++++---------------- 2 files changed, 54 insertions(+), 43 deletions(-) diff --git a/ethers-core/src/utils/hash.rs b/ethers-core/src/utils/hash.rs index 2ddb4c43e..185525904 100644 --- a/ethers-core/src/utils/hash.rs +++ b/ethers-core/src/utils/hash.rs @@ -1,36 +1,42 @@ -//! Various utilities for manipulating Ethereum related dat +//! Various utilities for manipulating Ethereum related data. + use ethabi::ethereum_types::H256; use tiny_keccak::{Hasher, Keccak}; -const PREFIX: &str = "\x19Ethereum Signed Message:\n"; - -/// Hash a message according to EIP-191. +/// Hash a message according to [EIP-191] (version `0x01`). +/// +/// The final message is a UTF-8 string, encoded as follows: +/// `"\x19Ethereum Signed Message:\n" + message.length + message` +/// +/// This message is then hashed using [Keccak-256](keccak256). /// -/// The data is a UTF-8 encoded string and will enveloped as follows: -/// `"\x19Ethereum Signed Message:\n" + message.length + message` and hashed -/// using keccak256. -pub fn hash_message(message: S) -> H256 -where - S: AsRef<[u8]>, -{ +/// [EIP-191]: https://eips.ethereum.org/EIPS/eip-191 +pub fn hash_message>(message: T) -> H256 { + const PREFIX: &str = "\x19Ethereum Signed Message:\n"; + let message = message.as_ref(); + let len = message.len(); + let len_string = len.to_string(); - let mut eth_message = format!("{PREFIX}{}", message.len()).into_bytes(); + let mut eth_message = Vec::with_capacity(PREFIX.len() + len_string.len() + len); + eth_message.extend_from_slice(PREFIX.as_bytes()); + eth_message.extend_from_slice(len_string.as_bytes()); eth_message.extend_from_slice(message); - keccak256(ð_message).into() + H256(keccak256(ð_message)) } /// Compute the Keccak-256 hash of input bytes. +/// +/// Note that strings are interpreted as UTF-8 bytes, // TODO: Add Solidity Keccak256 packing support -pub fn keccak256(bytes: S) -> [u8; 32] -where - S: AsRef<[u8]>, -{ +pub fn keccak256>(bytes: T) -> [u8; 32] { let mut output = [0u8; 32]; + let mut hasher = Keccak::v256(); hasher.update(bytes.as_ref()); hasher.finalize(&mut output); + output } @@ -53,7 +59,7 @@ pub fn id>(signature: S) -> [u8; 4] { /// /// If the type returns an error during serialization. pub fn serialize(t: &T) -> serde_json::Value { - serde_json::to_value(t).expect("Types never fail to serialize.") + serde_json::to_value(t).expect("Failed to serialize value") } #[cfg(test)] diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs index ff5ca22ae..ed90ba920 100644 --- a/ethers-core/src/utils/mod.rs +++ b/ethers-core/src/utils/mod.rs @@ -36,7 +36,7 @@ pub use rlp; /// Re-export hex pub use hex; -use crate::types::{Address, Bytes, ParseI256Error, I256, U256}; +use crate::types::{Address, ParseI256Error, H160, I256, U256}; use elliptic_curve::sec1::ToEncodedPoint; use ethabi::ethereum_types::FromDecStrErr; use k256::{ecdsa::SigningKey, PublicKey as K256PublicKey}; @@ -212,10 +212,7 @@ where /// assert_eq!(eth, parse_ether(1usize).unwrap()); /// assert_eq!(eth, parse_ether("1").unwrap()); /// ``` -pub fn parse_ether(eth: S) -> Result -where - S: ToString, -{ +pub fn parse_ether(eth: S) -> Result { Ok(parse_units(eth, "ether")?.into()) } @@ -299,7 +296,7 @@ pub fn get_contract_address(sender: impl Into
, nonce: impl Into) let mut bytes = [0u8; 20]; bytes.copy_from_slice(&hash[12..]); - Address::from(bytes) + H160(bytes) } /// Returns the CREATE2 address of a smart contract as specified in @@ -308,10 +305,11 @@ pub fn get_contract_address(sender: impl Into
, nonce: impl Into) /// keccak256( 0xff ++ senderAddress ++ salt ++ keccak256(init_code))[12..] pub fn get_create2_address( from: impl Into
, - salt: impl Into, - init_code: impl Into, + salt: impl AsRef<[u8]>, + init_code: impl AsRef<[u8]>, ) -> Address { - get_create2_address_from_hash(from, salt, keccak256(init_code.into().as_ref()).to_vec()) + let init_code_hash = keccak256(init_code.as_ref()); + get_create2_address_from_hash(from, salt, init_code_hash) } /// Returns the CREATE2 address of a smart contract as specified in @@ -332,9 +330,7 @@ pub fn get_create2_address( /// utils::{get_create2_address_from_hash, keccak256}, /// }; /// -/// let UNISWAP_V3_POOL_INIT_CODE_HASH = Bytes::from( -/// hex::decode("e34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54").unwrap(), -/// ); +/// let init_code_hash = hex::decode("e34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54").unwrap(); /// let factory: Address = "0x1F98431c8aD98523631AE4a59f267346ea31F984" /// .parse() /// .unwrap(); @@ -344,19 +340,18 @@ pub fn get_create2_address( /// let token1: Address = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" /// .parse() /// .unwrap(); -/// let fee = 500; +/// let fee = U256::from(500_u64); /// /// // abi.encode(token0 as address, token1 as address, fee as uint256) /// let input = abi::encode(&vec![ /// Token::Address(token0), /// Token::Address(token1), -/// Token::Uint(U256::from(fee)), +/// Token::Uint(fee), /// ]); /// /// // keccak256(abi.encode(token0, token1, fee)) /// let salt = keccak256(&input); -/// let pool_address = -/// get_create2_address_from_hash(factory, salt.to_vec(), UNISWAP_V3_POOL_INIT_CODE_HASH); +/// let pool_address = get_create2_address_from_hash(factory, salt, init_code_hash); /// /// assert_eq!( /// pool_address, @@ -367,18 +362,24 @@ pub fn get_create2_address( /// ``` pub fn get_create2_address_from_hash( from: impl Into
, - salt: impl Into, - init_code_hash: impl Into, + salt: impl AsRef<[u8]>, + init_code_hash: impl AsRef<[u8]>, ) -> Address { - let bytes = - [&[0xff], from.into().as_bytes(), salt.into().as_ref(), init_code_hash.into().as_ref()] - .concat(); + let from = from.into(); + let salt = salt.as_ref(); + let init_code_hash = init_code_hash.as_ref(); + + let mut bytes = Vec::with_capacity(1 + 20 + salt.len() + init_code_hash.len()); + bytes.push(0xff); + bytes.extend_from_slice(from.as_bytes()); + bytes.extend_from_slice(salt); + bytes.extend_from_slice(init_code_hash); let hash = keccak256(bytes); let mut bytes = [0u8; 20]; bytes.copy_from_slice(&hash[12..]); - Address::from(bytes) + H160(bytes) } /// Converts a K256 SigningKey to an Ethereum Address @@ -388,11 +389,15 @@ pub fn secret_key_to_address(secret_key: &SigningKey) -> Address { let public_key = public_key.as_bytes(); debug_assert_eq!(public_key[0], 0x04); let hash = keccak256(&public_key[1..]); - Address::from_slice(&hash[12..]) + + let mut bytes = [0u8; 20]; + bytes.copy_from_slice(&hash[12..]); + H160(bytes) } -/// Converts an Ethereum address to the checksum encoding -/// Ref: +/// Encodes an Ethereum address to its [EIP-55] checksum. +/// +/// [EIP-55]: https://eips.ethereum.org/EIPS/eip-55 pub fn to_checksum(addr: &Address, chain_id: Option) -> String { let prefixed_addr = match chain_id { Some(chain_id) => format!("{chain_id}0x{addr:x}"), From 433924b51c8b9086db41fab8da6b890a624b65b1 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 11 Jan 2023 19:14:02 +0100 Subject: [PATCH 2/4] docs: add more to_checksum information --- ethers-core/src/utils/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs index ed90ba920..85a0ed705 100644 --- a/ethers-core/src/utils/mod.rs +++ b/ethers-core/src/utils/mod.rs @@ -397,7 +397,12 @@ pub fn secret_key_to_address(secret_key: &SigningKey) -> Address { /// Encodes an Ethereum address to its [EIP-55] checksum. /// +/// You can optionally specify an [EIP-155 chain ID] to encode the address using the [EIP-1191] +/// extension. +/// /// [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 to_checksum(addr: &Address, chain_id: Option) -> String { let prefixed_addr = match chain_id { Some(chain_id) => format!("{chain_id}0x{addr:x}"), From 10f42585383d071b1de0900268e72c5d0af1f4cb Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 11 Jan 2023 19:22:48 +0100 Subject: [PATCH 3/4] docs --- CHANGELOG.md | 1 + ethers-core/src/utils/mod.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 608599c7c..7751252b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Unreleased +- Avoid unnecessary allocations in `utils` [#2046](https://github.com/gakonst/ethers-rs/pull/2046) - Add abigen support for hardhat generated bytecode json format [#2012](https://github.com/gakonst/ethers-rs/pull/2012) - Fix typo in `RwClient` docs for `write_client` method. - Add support for Geth `debug_traceCall` [#1949](https://github.com/gakonst/ethers-rs/pull/1949) diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs index 85a0ed705..98c416365 100644 --- a/ethers-core/src/utils/mod.rs +++ b/ethers-core/src/utils/mod.rs @@ -343,7 +343,7 @@ pub fn get_create2_address( /// let fee = U256::from(500_u64); /// /// // abi.encode(token0 as address, token1 as address, fee as uint256) -/// let input = abi::encode(&vec![ +/// let input = abi::encode(&[ /// Token::Address(token0), /// Token::Address(token1), /// Token::Uint(fee), From cd6600e04a137a50ed20c969c1be05327370e5d8 Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Wed, 11 Jan 2023 19:34:21 +0100 Subject: [PATCH 4/4] use address --- ethers-core/src/utils/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs index 98c416365..9528fa011 100644 --- a/ethers-core/src/utils/mod.rs +++ b/ethers-core/src/utils/mod.rs @@ -36,7 +36,7 @@ pub use rlp; /// Re-export hex pub use hex; -use crate::types::{Address, ParseI256Error, H160, I256, U256}; +use crate::types::{Address, ParseI256Error, I256, U256}; use elliptic_curve::sec1::ToEncodedPoint; use ethabi::ethereum_types::FromDecStrErr; use k256::{ecdsa::SigningKey, PublicKey as K256PublicKey}; @@ -296,7 +296,7 @@ pub fn get_contract_address(sender: impl Into
, nonce: impl Into) let mut bytes = [0u8; 20]; bytes.copy_from_slice(&hash[12..]); - H160(bytes) + Address::from(bytes) } /// Returns the CREATE2 address of a smart contract as specified in @@ -379,7 +379,7 @@ pub fn get_create2_address_from_hash( let mut bytes = [0u8; 20]; bytes.copy_from_slice(&hash[12..]); - H160(bytes) + Address::from(bytes) } /// Converts a K256 SigningKey to an Ethereum Address @@ -392,7 +392,7 @@ pub fn secret_key_to_address(secret_key: &SigningKey) -> Address { let mut bytes = [0u8; 20]; bytes.copy_from_slice(&hash[12..]); - H160(bytes) + Address::from(bytes) } /// Encodes an Ethereum address to its [EIP-55] checksum.