diff --git a/Cargo.lock b/Cargo.lock index fb932d40..0023542d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -808,6 +808,7 @@ dependencies = [ "generic-array 0.14.4", "glob", "hex", + "hex-literal", "k256", "once_cell", "rand 0.8.4", @@ -1174,6 +1175,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76505e26b6ca3bbdbbb360b68472abbb80998c5fa5dc43672eca34f28258e138" + [[package]] name = "hidapi" version = "1.2.6" diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml index e6a44593..c65418fa 100644 --- a/ethers-core/Cargo.toml +++ b/ethers-core/Cargo.toml @@ -44,7 +44,7 @@ ethers = { version = "0.4.0", path = "../ethers" } serde_json = { version = "1.0.64", default-features = false } bincode = { version = "1.3.3", default-features = false } once_cell = { version = "1.8.0" } - +hex-literal = "0.3.2" [features] celo = [] # celo support extends the transaction format with extra fields diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs index 8212fd60..5717468a 100644 --- a/ethers-core/src/utils/mod.rs +++ b/ethers-core/src/utils/mod.rs @@ -36,6 +36,13 @@ pub use rlp; use crate::types::{Address, Bytes, U256}; use k256::{ecdsa::SigningKey, EncodedPoint as K256PublicKey}; use std::convert::TryInto; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum FormatBytes32StringError { + #[error("bytes32 strings must not exceed 32 bytes in length")] + TextTooLong, +} /// 1 Ether = 1e18 Wei == 0x0de0b6b3a7640000 Wei pub const WEI_IN_ETHER: U256 = U256([0x0de0b6b3a7640000, 0x0, 0x0, 0x0]); @@ -156,6 +163,30 @@ pub fn to_checksum(addr: &Address, chain_id: Option) -> String { }) } +/// Returns a bytes32 string representation of text. If the length of text exceeds 32 bytes, +/// an error is returned. +pub fn format_bytes32_string(text: &str) -> Result<[u8; 32], FormatBytes32StringError> { + let str_bytes: &[u8] = text.as_bytes(); + if str_bytes.len() > 32 { + return Err(FormatBytes32StringError::TextTooLong); + } + + let mut bytes32: [u8; 32] = [0u8; 32]; + bytes32[..str_bytes.len()].copy_from_slice(str_bytes); + + Ok(bytes32) +} + +/// Returns the decoded string represented by the bytes32 encoded data. +pub fn parse_bytes32_string(bytes: &[u8; 32]) -> Result<&str, std::str::Utf8Error> { + let mut length = 0; + while length < 32 && bytes[length] != 0 { + length += 1; + } + + std::str::from_utf8(&bytes[..length]) +} + /// A bit of hack to find an unused TCP port. /// /// Does not guarantee that the given port is unused after the function exists, just that it was @@ -173,6 +204,7 @@ pub(crate) fn unused_port() -> u16 { #[cfg(test)] mod tests { use super::*; + use hex_literal::hex; #[test] fn wei_in_ether() { @@ -359,4 +391,64 @@ mod tests { assert_eq!(expected, get_create2_address(from, salt, init_code)) } } + + #[test] + fn bytes32_string_parsing() { + let text_bytes_list = vec![ + ( + "", + hex!("0000000000000000000000000000000000000000000000000000000000000000"), + ), + ( + "A", + hex!("4100000000000000000000000000000000000000000000000000000000000000"), + ), + ( + "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345", + hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"), + ), + ( + "!@#$%^&*(),./;'[]", + hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"), + ), + ]; + + for (text, bytes) in text_bytes_list { + assert_eq!(text, parse_bytes32_string(&bytes).unwrap()); + } + } + + #[test] + fn bytes32_string_formatting() { + let text_bytes_list = vec![ + ( + "", + hex!("0000000000000000000000000000000000000000000000000000000000000000"), + ), + ( + "A", + hex!("4100000000000000000000000000000000000000000000000000000000000000"), + ), + ( + "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345", + hex!("4142434445464748494a4b4c4d4e4f505152535455565758595a303132333435"), + ), + ( + "!@#$%^&*(),./;'[]", + hex!("21402324255e262a28292c2e2f3b275b5d000000000000000000000000000000"), + ), + ]; + + for (text, bytes) in text_bytes_list { + assert_eq!(bytes, format_bytes32_string(text).unwrap()); + } + } + + #[test] + fn bytes32_string_formatting_too_long() { + assert!(matches!( + format_bytes32_string("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456").unwrap_err(), + FormatBytes32StringError::TextTooLong + )); + } }