Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: do not panic on invalid units conversion #691

Merged
merged 2 commits into from
Dec 15, 2021
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
12 changes: 8 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>`.
[613](https://github.com/gakonst/ethers-rs/pull/613)
Expand All @@ -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

Expand Down
67 changes: 42 additions & 25 deletions ethers-core/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -65,26 +74,30 @@ pub fn format_ether<T: Into<U256>>(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::<f64>().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::<f64>().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<T: Into<U256>, K: Into<Units>>(amount: T, units: K) -> String {
let units = units.into();
pub fn format_units<T, K>(amount: T, units: K) -> Result<String, ConversionError>
where
T: Into<U256>,
K: TryInto<Units, Error = ConversionError>,
{
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.
Expand All @@ -97,7 +110,7 @@ pub fn format_units<T: Into<U256>, K: Into<Units>>(amount: T, units: K) -> Strin
/// assert_eq!(eth, parse_ether(1usize).unwrap());
/// assert_eq!(eth, parse_ether("1").unwrap());
/// ```
pub fn parse_ether<S>(eth: S) -> Result<U256, Box<dyn std::error::Error>>
pub fn parse_ether<S>(eth: S) -> Result<U256, ConversionError>
where
S: ToString,
{
Expand All @@ -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<K, S>(amount: S, units: K) -> Result<U256, Box<dyn std::error::Error>>
pub fn parse_units<K, S>(amount: S, units: K) -> Result<U256, ConversionError>
where
S: ToString,
K: Into<Units>,
K: TryInto<Units, Error = ConversionError>,
{
let float_n: f64 = amount.to_string().parse::<f64>()? * 10u64.pow(units.into().as_num()) as f64;
let float_n: f64 =
amount.to_string().parse::<f64>()? * 10u64.pow(units.try_into()?.as_num()) as f64;
let u256_n: U256 = U256::from_dec_str(&float_n.to_string())?;
Ok(u256_n)
}
Expand Down Expand Up @@ -261,10 +275,10 @@ pub fn to_checksum(addr: &Address, chain_id: Option<u8>) -> 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];
Expand All @@ -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)
Expand Down Expand Up @@ -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::<f64>().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::<f64>().unwrap() as u64, 1);

let eth = format_units(1395633240123456000_u128, "ether");
let eth = format_units(1395633240123456000_u128, "ether").unwrap();
assert_eq!(eth.parse::<f64>().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::<f64>().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");
}

Expand Down Expand Up @@ -633,7 +650,7 @@ mod tests {
fn bytes32_string_formatting_too_long() {
assert!(matches!(
format_bytes32_string("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456").unwrap_err(),
FormatBytes32StringError::TextTooLong
ConversionError::TextTooLong
));
}

Expand Down
40 changes: 26 additions & 14 deletions ethers-core/src/utils/units.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use super::ConversionError;

/// Common Ethereum unit types.
pub enum Units {
/// Ether corresponds to 1e18 Wei
Expand All @@ -21,31 +23,41 @@ impl Units {
}
}

impl From<u32> for Units {
fn from(src: u32) -> Self {
Units::Other(src)
use std::convert::TryFrom;

impl TryFrom<u32> for Units {
type Error = ConversionError;

fn try_from(src: u32) -> Result<Self, Self::Error> {
Ok(Units::Other(src))
}
}

impl From<i32> for Units {
fn from(src: i32) -> Self {
Units::Other(src as u32)
impl TryFrom<i32> for Units {
type Error = ConversionError;

fn try_from(src: i32) -> Result<Self, Self::Error> {
Ok(Units::Other(src as u32))
}
}

impl From<usize> for Units {
fn from(src: usize) -> Self {
Units::Other(src as u32)
impl TryFrom<usize> for Units {
type Error = ConversionError;

fn try_from(src: usize) -> Result<Self, Self::Error> {
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<Self, Self::Error> {
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())),
})
}
}