From f037fc0243e072a65129272bfed3bd6063855d59 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Tue, 14 Dec 2021 19:32:29 -0700 Subject: [PATCH] fix: do not panic on invalid units conversion (#691) * fix: do not panic on invalid units conversion This is done by switching the From implementations to TryFrom and making the conversion functions return a thiserror Error instead of the previous Boxed error object * chore: update changelog --- CHANGELOG.md | 12 ++++-- ethers-core/src/utils/mod.rs | 67 +++++++++++++++++++++------------- ethers-core/src/utils/units.rs | 40 +++++++++++++------- 3 files changed, 76 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18d65384..5d813f1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Unreleased +- Returns error on invalid type conversion instead of panicking + [691](https://github.com/gakonst/ethers-rs/pull/691/files) - Change types mapping for solidity `bytes` to rust `ethers::core::Bytes` and solidity `uint8[]` to rust `Vec`. [613](https://github.com/gakonst/ethers-rs/pull/613) @@ -26,12 +28,14 @@ ### Unreleased -- Add support for hardhat artifacts [#677](https://github.com/gakonst/ethers-rs/pull/677) -- Add more utility functions to the `Artifact` trait [#673](https://github.com/gakonst/ethers-rs/pull/673) +- Add support for hardhat artifacts + [#677](https://github.com/gakonst/ethers-rs/pull/677) +- Add more utility functions to the `Artifact` trait + [#673](https://github.com/gakonst/ethers-rs/pull/673) - Return cached artifacts from project `compile` when the cache only contains some files -- Add support for library linking and make `Bytecode`'s `object` filed an `enum BytecodeObject` - [#656](https://github.com/gakonst/ethers-rs/pull/656). +- Add support for library linking and make `Bytecode`'s `object` filed an + `enum BytecodeObject` [#656](https://github.com/gakonst/ethers-rs/pull/656). ### 0.6.0 diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs index 64a9c28e..690dc492 100644 --- a/ethers-core/src/utils/mod.rs +++ b/ethers-core/src/utils/mod.rs @@ -26,14 +26,23 @@ pub use rlp; pub use hex; use crate::types::{Address, Bytes, U256}; +use ethabi::ethereum_types::FromDecStrErr; use k256::{ecdsa::SigningKey, EncodedPoint as K256PublicKey}; -use std::ops::Neg; +use std::{convert::TryInto, ops::Neg}; use thiserror::Error; -#[derive(Debug, Error)] -pub enum FormatBytes32StringError { +#[derive(Error, Debug)] +pub enum ConversionError { + #[error("Unknown units: {0}")] + UnrecognizedUnits(String), #[error("bytes32 strings must not exceed 32 bytes in length")] TextTooLong, + #[error(transparent)] + Utf8Error(#[from] std::str::Utf8Error), + #[error(transparent)] + InvalidFloat(#[from] std::num::ParseFloatError), + #[error(transparent)] + FromDecStrError(#[from] FromDecStrErr), } /// 1 Ether = 1e18 Wei == 0x0de0b6b3a7640000 Wei @@ -65,26 +74,30 @@ pub fn format_ether>(amount: T) -> U256 { /// ``` /// use ethers_core::{types::U256, utils::format_units}; /// -/// let eth = format_units(1395633240123456000_u128, "ether"); +/// let eth = format_units(1395633240123456000_u128, "ether").unwrap(); /// assert_eq!(eth.parse::().unwrap(), 1.395633240123456); /// -/// let eth = format_units(U256::from_dec_str("1395633240123456000").unwrap(), "ether"); +/// let eth = format_units(U256::from_dec_str("1395633240123456000").unwrap(), "ether").unwrap(); /// assert_eq!(eth.parse::().unwrap(), 1.395633240123456); /// -/// let eth = format_units(U256::from_dec_str("1395633240123456789").unwrap(), "ether"); +/// let eth = format_units(U256::from_dec_str("1395633240123456789").unwrap(), "ether").unwrap(); /// assert_eq!(eth, "1.395633240123456789"); /// ``` -pub fn format_units, K: Into>(amount: T, units: K) -> String { - let units = units.into(); +pub fn format_units(amount: T, units: K) -> Result +where + T: Into, + K: TryInto, +{ + let units = units.try_into()?; let amount = amount.into(); let amount_decimals = amount % U256::from(10_u128.pow(units.as_num())); let amount_integer = amount / U256::from(10_u128.pow(units.as_num())); - format!( + Ok(format!( "{}.{:0width$}", amount_integer, amount_decimals.as_u128(), width = units.as_num() as usize - ) + )) } /// Converts the input to a U256 and converts from Ether to Wei. @@ -97,7 +110,7 @@ pub fn format_units, K: Into>(amount: T, units: K) -> Strin /// assert_eq!(eth, parse_ether(1usize).unwrap()); /// assert_eq!(eth, parse_ether("1").unwrap()); /// ``` -pub fn parse_ether(eth: S) -> Result> +pub fn parse_ether(eth: S) -> Result where S: ToString, { @@ -122,12 +135,13 @@ where /// let amount_in_wei = U256::from_dec_str("15230001000").unwrap(); /// assert_eq!(amount_in_wei, parse_units("15.230001000000000000", "wei").unwrap()); /// ``` -pub fn parse_units(amount: S, units: K) -> Result> +pub fn parse_units(amount: S, units: K) -> Result where S: ToString, - K: Into, + K: TryInto, { - let float_n: f64 = amount.to_string().parse::()? * 10u64.pow(units.into().as_num()) as f64; + let float_n: f64 = + amount.to_string().parse::()? * 10u64.pow(units.try_into()?.as_num()) as f64; let u256_n: U256 = U256::from_dec_str(&float_n.to_string())?; Ok(u256_n) } @@ -261,10 +275,10 @@ 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> { +pub fn format_bytes32_string(text: &str) -> Result<[u8; 32], ConversionError> { let str_bytes: &[u8] = text.as_bytes(); if str_bytes.len() > 32 { - return Err(FormatBytes32StringError::TextTooLong) + return Err(ConversionError::TextTooLong) } let mut bytes32: [u8; 32] = [0u8; 32]; @@ -274,13 +288,13 @@ pub fn format_bytes32_string(text: &str) -> Result<[u8; 32], FormatBytes32String } /// Returns the decoded string represented by the bytes32 encoded data. -pub fn parse_bytes32_string(bytes: &[u8; 32]) -> Result<&str, std::str::Utf8Error> { +pub fn parse_bytes32_string(bytes: &[u8; 32]) -> Result<&str, ConversionError> { let mut length = 0; while length < 32 && bytes[length] != 0 { length += 1; } - std::str::from_utf8(&bytes[..length]) + Ok(std::str::from_utf8(&bytes[..length])?) } /// The default EIP-1559 fee estimator which is based on the work by [MyCrypto](https://github.com/MyCryptoHQ/MyCrypto/blob/master/src/services/ApiService/Gas/eip1559.ts) @@ -389,22 +403,25 @@ mod tests { #[test] fn test_format_units() { - let gwei_in_ether = format_units(WEI_IN_ETHER, 9); + let gwei_in_ether = format_units(WEI_IN_ETHER, 9).unwrap(); assert_eq!(gwei_in_ether.parse::().unwrap() as u64, 1e9 as u64); - let eth = format_units(WEI_IN_ETHER, "ether"); + let eth = format_units(WEI_IN_ETHER, "ether").unwrap(); assert_eq!(eth.parse::().unwrap() as u64, 1); - let eth = format_units(1395633240123456000_u128, "ether"); + let eth = format_units(1395633240123456000_u128, "ether").unwrap(); assert_eq!(eth.parse::().unwrap(), 1.395633240123456); - let eth = format_units(U256::from_dec_str("1395633240123456000").unwrap(), "ether"); + let eth = + format_units(U256::from_dec_str("1395633240123456000").unwrap(), "ether").unwrap(); assert_eq!(eth.parse::().unwrap(), 1.395633240123456); - let eth = format_units(U256::from_dec_str("1395633240123456789").unwrap(), "ether"); + let eth = + format_units(U256::from_dec_str("1395633240123456789").unwrap(), "ether").unwrap(); assert_eq!(eth, "1.395633240123456789"); - let eth = format_units(U256::from_dec_str("1005633240123456789").unwrap(), "ether"); + let eth = + format_units(U256::from_dec_str("1005633240123456789").unwrap(), "ether").unwrap(); assert_eq!(eth, "1.005633240123456789"); } @@ -633,7 +650,7 @@ mod tests { fn bytes32_string_formatting_too_long() { assert!(matches!( format_bytes32_string("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456").unwrap_err(), - FormatBytes32StringError::TextTooLong + ConversionError::TextTooLong )); } diff --git a/ethers-core/src/utils/units.rs b/ethers-core/src/utils/units.rs index 781a1187..e925fa8d 100644 --- a/ethers-core/src/utils/units.rs +++ b/ethers-core/src/utils/units.rs @@ -1,3 +1,5 @@ +use super::ConversionError; + /// Common Ethereum unit types. pub enum Units { /// Ether corresponds to 1e18 Wei @@ -21,31 +23,41 @@ impl Units { } } -impl From for Units { - fn from(src: u32) -> Self { - Units::Other(src) +use std::convert::TryFrom; + +impl TryFrom for Units { + type Error = ConversionError; + + fn try_from(src: u32) -> Result { + Ok(Units::Other(src)) } } -impl From for Units { - fn from(src: i32) -> Self { - Units::Other(src as u32) +impl TryFrom for Units { + type Error = ConversionError; + + fn try_from(src: i32) -> Result { + Ok(Units::Other(src as u32)) } } -impl From for Units { - fn from(src: usize) -> Self { - Units::Other(src as u32) +impl TryFrom for Units { + type Error = ConversionError; + + fn try_from(src: usize) -> Result { + Ok(Units::Other(src as u32)) } } -impl From<&str> for Units { - fn from(src: &str) -> Self { - match src.to_lowercase().as_str() { +impl std::convert::TryFrom<&str> for Units { + type Error = ConversionError; + + fn try_from(src: &str) -> Result { + Ok(match src.to_lowercase().as_str() { "ether" => Units::Ether, "gwei" => Units::Gwei, "wei" => Units::Wei, - _ => panic!("unrecognized units"), - } + _ => return Err(ConversionError::UnrecognizedUnits(src.to_string())), + }) } }