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
This commit is contained in:
parent
4bb2636b77
commit
f037fc0243
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -4,6 +4,8 @@
|
||||||
|
|
||||||
### Unreleased
|
### 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
|
- Change types mapping for solidity `bytes` to rust `ethers::core::Bytes` and
|
||||||
solidity `uint8[]` to rust `Vec<u8>`.
|
solidity `uint8[]` to rust `Vec<u8>`.
|
||||||
[613](https://github.com/gakonst/ethers-rs/pull/613)
|
[613](https://github.com/gakonst/ethers-rs/pull/613)
|
||||||
|
@ -26,12 +28,14 @@
|
||||||
|
|
||||||
### Unreleased
|
### Unreleased
|
||||||
|
|
||||||
- Add support for hardhat artifacts [#677](https://github.com/gakonst/ethers-rs/pull/677)
|
- Add support for hardhat artifacts
|
||||||
- Add more utility functions to the `Artifact` trait [#673](https://github.com/gakonst/ethers-rs/pull/673)
|
[#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
|
- Return cached artifacts from project `compile` when the cache only contains
|
||||||
some files
|
some files
|
||||||
- Add support for library linking and make `Bytecode`'s `object` filed an `enum BytecodeObject`
|
- Add support for library linking and make `Bytecode`'s `object` filed an
|
||||||
[#656](https://github.com/gakonst/ethers-rs/pull/656).
|
`enum BytecodeObject` [#656](https://github.com/gakonst/ethers-rs/pull/656).
|
||||||
|
|
||||||
### 0.6.0
|
### 0.6.0
|
||||||
|
|
||||||
|
|
|
@ -26,14 +26,23 @@ pub use rlp;
|
||||||
pub use hex;
|
pub use hex;
|
||||||
|
|
||||||
use crate::types::{Address, Bytes, U256};
|
use crate::types::{Address, Bytes, U256};
|
||||||
|
use ethabi::ethereum_types::FromDecStrErr;
|
||||||
use k256::{ecdsa::SigningKey, EncodedPoint as K256PublicKey};
|
use k256::{ecdsa::SigningKey, EncodedPoint as K256PublicKey};
|
||||||
use std::ops::Neg;
|
use std::{convert::TryInto, ops::Neg};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Error, Debug)]
|
||||||
pub enum FormatBytes32StringError {
|
pub enum ConversionError {
|
||||||
|
#[error("Unknown units: {0}")]
|
||||||
|
UnrecognizedUnits(String),
|
||||||
#[error("bytes32 strings must not exceed 32 bytes in length")]
|
#[error("bytes32 strings must not exceed 32 bytes in length")]
|
||||||
TextTooLong,
|
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
|
/// 1 Ether = 1e18 Wei == 0x0de0b6b3a7640000 Wei
|
||||||
|
@ -65,26 +74,30 @@ pub fn format_ether<T: Into<U256>>(amount: T) -> U256 {
|
||||||
/// ```
|
/// ```
|
||||||
/// use ethers_core::{types::U256, utils::format_units};
|
/// 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);
|
/// 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);
|
/// 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");
|
/// assert_eq!(eth, "1.395633240123456789");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn format_units<T: Into<U256>, K: Into<Units>>(amount: T, units: K) -> String {
|
pub fn format_units<T, K>(amount: T, units: K) -> Result<String, ConversionError>
|
||||||
let units = units.into();
|
where
|
||||||
|
T: Into<U256>,
|
||||||
|
K: TryInto<Units, Error = ConversionError>,
|
||||||
|
{
|
||||||
|
let units = units.try_into()?;
|
||||||
let amount = amount.into();
|
let amount = amount.into();
|
||||||
let amount_decimals = amount % U256::from(10_u128.pow(units.as_num()));
|
let amount_decimals = amount % U256::from(10_u128.pow(units.as_num()));
|
||||||
let amount_integer = 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$}",
|
"{}.{:0width$}",
|
||||||
amount_integer,
|
amount_integer,
|
||||||
amount_decimals.as_u128(),
|
amount_decimals.as_u128(),
|
||||||
width = units.as_num() as usize
|
width = units.as_num() as usize
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the input to a U256 and converts from Ether to Wei.
|
/// Converts the input to a U256 and converts from Ether to Wei.
|
||||||
|
@ -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(1usize).unwrap());
|
||||||
/// assert_eq!(eth, parse_ether("1").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
|
where
|
||||||
S: ToString,
|
S: ToString,
|
||||||
{
|
{
|
||||||
|
@ -122,12 +135,13 @@ where
|
||||||
/// let amount_in_wei = U256::from_dec_str("15230001000").unwrap();
|
/// let amount_in_wei = U256::from_dec_str("15230001000").unwrap();
|
||||||
/// assert_eq!(amount_in_wei, parse_units("15.230001000000000000", "wei").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
|
where
|
||||||
S: ToString,
|
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())?;
|
let u256_n: U256 = U256::from_dec_str(&float_n.to_string())?;
|
||||||
Ok(u256_n)
|
Ok(u256_n)
|
||||||
}
|
}
|
||||||
|
@ -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,
|
/// Returns a bytes32 string representation of text. If the length of text exceeds 32 bytes,
|
||||||
/// an error is returned.
|
/// 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();
|
let str_bytes: &[u8] = text.as_bytes();
|
||||||
if str_bytes.len() > 32 {
|
if str_bytes.len() > 32 {
|
||||||
return Err(FormatBytes32StringError::TextTooLong)
|
return Err(ConversionError::TextTooLong)
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut bytes32: [u8; 32] = [0u8; 32];
|
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.
|
/// 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;
|
let mut length = 0;
|
||||||
while length < 32 && bytes[length] != 0 {
|
while length < 32 && bytes[length] != 0 {
|
||||||
length += 1;
|
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)
|
/// 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]
|
#[test]
|
||||||
fn test_format_units() {
|
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);
|
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);
|
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);
|
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);
|
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");
|
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");
|
assert_eq!(eth, "1.005633240123456789");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -633,7 +650,7 @@ mod tests {
|
||||||
fn bytes32_string_formatting_too_long() {
|
fn bytes32_string_formatting_too_long() {
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
format_bytes32_string("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456").unwrap_err(),
|
format_bytes32_string("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456").unwrap_err(),
|
||||||
FormatBytes32StringError::TextTooLong
|
ConversionError::TextTooLong
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use super::ConversionError;
|
||||||
|
|
||||||
/// Common Ethereum unit types.
|
/// Common Ethereum unit types.
|
||||||
pub enum Units {
|
pub enum Units {
|
||||||
/// Ether corresponds to 1e18 Wei
|
/// Ether corresponds to 1e18 Wei
|
||||||
|
@ -21,31 +23,41 @@ impl Units {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u32> for Units {
|
use std::convert::TryFrom;
|
||||||
fn from(src: u32) -> Self {
|
|
||||||
Units::Other(src)
|
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 {
|
impl TryFrom<i32> for Units {
|
||||||
fn from(src: i32) -> Self {
|
type Error = ConversionError;
|
||||||
Units::Other(src as u32)
|
|
||||||
|
fn try_from(src: i32) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Units::Other(src as u32))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<usize> for Units {
|
impl TryFrom<usize> for Units {
|
||||||
fn from(src: usize) -> Self {
|
type Error = ConversionError;
|
||||||
Units::Other(src as u32)
|
|
||||||
|
fn try_from(src: usize) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Units::Other(src as u32))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&str> for Units {
|
impl std::convert::TryFrom<&str> for Units {
|
||||||
fn from(src: &str) -> Self {
|
type Error = ConversionError;
|
||||||
match src.to_lowercase().as_str() {
|
|
||||||
|
fn try_from(src: &str) -> Result<Self, Self::Error> {
|
||||||
|
Ok(match src.to_lowercase().as_str() {
|
||||||
"ether" => Units::Ether,
|
"ether" => Units::Ether,
|
||||||
"gwei" => Units::Gwei,
|
"gwei" => Units::Gwei,
|
||||||
"wei" => Units::Wei,
|
"wei" => Units::Wei,
|
||||||
_ => panic!("unrecognized units"),
|
_ => return Err(ConversionError::UnrecognizedUnits(src.to_string())),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue