Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

perf(utils): avoid unnecessary allocations #2046

Merged
merged 4 commits into from
Jan 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
42 changes: 24 additions & 18 deletions ethers-core/src/utils/hash.rs
Original file line number Diff line number Diff line change
@@ -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<S>(message: S) -> H256
where
S: AsRef<[u8]>,
{
/// [EIP-191]: https://eips.ethereum.org/EIPS/eip-191
pub fn hash_message<T: AsRef<[u8]>>(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(&eth_message).into()
H256(keccak256(&eth_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<S>(bytes: S) -> [u8; 32]
where
S: AsRef<[u8]>,
{
pub fn keccak256<T: AsRef<[u8]>>(bytes: T) -> [u8; 32] {
let mut output = [0u8; 32];

let mut hasher = Keccak::v256();
hasher.update(bytes.as_ref());
hasher.finalize(&mut output);

output
}

Expand All @@ -53,7 +59,7 @@ pub fn id<S: AsRef<str>>(signature: S) -> [u8; 4] {
///
/// If the type returns an error during serialization.
pub fn serialize<T: serde::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)]
Expand Down
58 changes: 34 additions & 24 deletions ethers-core/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, I256, U256};
use elliptic_curve::sec1::ToEncodedPoint;
use ethabi::ethereum_types::FromDecStrErr;
use k256::{ecdsa::SigningKey, PublicKey as K256PublicKey};
Expand Down Expand Up @@ -212,10 +212,7 @@ where
/// assert_eq!(eth, parse_ether(1usize).unwrap());
/// assert_eq!(eth, parse_ether("1").unwrap());
/// ```
pub fn parse_ether<S>(eth: S) -> Result<U256, ConversionError>
where
S: ToString,
{
pub fn parse_ether<S: ToString>(eth: S) -> Result<U256, ConversionError> {
Ok(parse_units(eth, "ether")?.into())
}

Expand Down Expand Up @@ -308,10 +305,11 @@ pub fn get_contract_address(sender: impl Into<Address>, nonce: impl Into<U256>)
/// keccak256( 0xff ++ senderAddress ++ salt ++ keccak256(init_code))[12..]
pub fn get_create2_address(
from: impl Into<Address>,
salt: impl Into<Bytes>,
init_code: impl Into<Bytes>,
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
Expand All @@ -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();
Expand All @@ -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![
/// let input = abi::encode(&[
/// 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);
DaniPopes marked this conversation as resolved.
Show resolved Hide resolved
///
/// assert_eq!(
/// pool_address,
Expand All @@ -367,12 +362,18 @@ pub fn get_create2_address(
/// ```
pub fn get_create2_address_from_hash(
from: impl Into<Address>,
salt: impl Into<Bytes>,
init_code_hash: impl Into<Bytes>,
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);

Expand All @@ -388,11 +389,20 @@ 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..]);
Address::from(bytes)
}

/// Converts an Ethereum address to the checksum encoding
/// Ref: <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md>
/// 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<u8>) -> String {
let prefixed_addr = match chain_id {
Some(chain_id) => format!("{chain_id}0x{addr:x}"),
Expand Down