diff --git a/CHANGELOG.md b/CHANGELOG.md index fffa6f22..46ff1884 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Unreleased +- Remove rust_decimals dependency for ethers-core +- Add support for numbers greater than 2^96 for `ethers_core::utils::parse_units` [#1822](https://github.com/gakonst/ethers-rs/issues/1822) - Add comment about safety of u8 -> u64 cast in `ethers_core::types::Signature` - Stop defaulting to the `"latest"` block in `eth_estimateGas` params [#1657](https://github.com/gakonst/ethers-rs/pull/1657) - Fix geth trace types for debug_traceTransaction rpc diff --git a/Cargo.lock b/Cargo.lock index 3b76b1a4..3db56c4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1316,7 +1316,6 @@ dependencies = [ "rand 0.8.5", "rlp", "rlp-derive", - "rust_decimal", "serde", "serde_json", "strum", @@ -3280,17 +3279,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "rust_decimal" -version = "1.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" -dependencies = [ - "arrayvec 0.7.2", - "num-traits", - "serde", -] - [[package]] name = "rustc-hex" version = "2.1.0" diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml index 281bc00c..da1eda32 100644 --- a/ethers-core/Cargo.toml +++ b/ethers-core/Cargo.toml @@ -43,7 +43,6 @@ cargo_metadata = { version = "0.15.0", optional = true } convert_case = { version = "0.6.0", optional = true } syn = { version = "1.0.103", optional = true } proc-macro2 = { version = "1.0.47", optional = true } -rust_decimal = { version = "1.26.1", features = ["maths"] } [dev-dependencies] serde_json = { version = "1.0.64", default-features = false } diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs index 5e3b1c2d..add0f031 100644 --- a/ethers-core/src/utils/mod.rs +++ b/ethers-core/src/utils/mod.rs @@ -56,8 +56,8 @@ pub enum ConversionError { InvalidFloat(#[from] std::num::ParseFloatError), #[error(transparent)] FromDecStrError(#[from] FromDecStrErr), - #[error(transparent)] - DecimalError(#[from] rust_decimal::Error), + #[error("Overflow parsing string")] + ParseOverflow, } /// 1 Ether = 1e18 Wei == 0x0de0b6b3a7640000 Wei @@ -155,19 +155,27 @@ where S: ToString, K: TryInto + Copy, { - use rust_decimal::{Decimal, MathematicalOps}; + let exponent: u32 = units.try_into()?.as_num(); + let mut amount_str = amount.to_string().replace("_", ""); + let dec_len = if let Some(di) = amount_str.find('.') { + amount_str.remove(di); + amount_str[di..].len() as u32 + } else { + 0 + }; - let num: Decimal = amount.to_string().parse()?; - let exponent = units.try_into()?.as_num(); - - let multiplier = Decimal::TEN - .checked_powu(exponent.into()) - .ok_or(rust_decimal::Error::ExceedsMaximumPossibleValue)?; - - let val = - num.checked_mul(multiplier).ok_or(rust_decimal::Error::ExceedsMaximumPossibleValue)?; - let u256_n: U256 = U256::from_dec_str(&val.round().to_string())?; - Ok(u256_n) + if dec_len > exponent { + // Truncate the decimal part if it is longer than the exponent + let amount_str = &amount_str[..(amount_str.len() - (dec_len - exponent) as usize)]; + let a_uint = U256::from_dec_str(amount_str)?; + Ok(a_uint) + } else { + let mut a_uint = U256::from_dec_str(&amount_str)?; + a_uint *= U256::from(10) + .checked_pow(U256::from(exponent - dec_len)) + .ok_or(ConversionError::ParseOverflow)?; + Ok(a_uint) + } } /// The address for an Ethereum contract is deterministically computed from the /// address of its creator (sender) and how many transactions the creator has @@ -474,6 +482,22 @@ mod tests { let val = parse_units("2.3", "ether").unwrap(); assert_eq!(val, U256::from_dec_str("2300000000000000000").unwrap()); + + assert_eq!(parse_units(".2", 2).unwrap(), U256::from(20), "leading dot"); + assert_eq!(parse_units("333.21", 2).unwrap(), U256::from(33321), "trailing dot"); + assert_eq!( + parse_units("98766", 16).unwrap(), + U256::from_dec_str("987660000000000000000").unwrap(), + "no dot" + ); + assert_eq!(parse_units("3_3_0", 3).unwrap(), U256::from(330000), "underscore"); + assert_eq!(parse_units("330", 0).unwrap(), U256::from(330), "zero decimals"); + assert_eq!(parse_units(".1234", 3).unwrap(), U256::from(123), "truncate too many decimals"); + assert_eq!(parse_units("1", 80).is_err(), true, "overflow"); + assert_eq!(parse_units("1", -1).is_err(), true, "neg units"); + let two_e30 = U256::from(2) * U256([0x4674edea40000000, 0xc9f2c9cd0, 0x0, 0x0]); + assert_eq!(parse_units("2", 30).unwrap(), two_e30, "2e30"); + assert_eq!(parse_units(".33_319_2", 0).unwrap(), U256::zero(), "mix"); } #[test]