I256 parse support (#1863)

* Added support for I256 in format_units. Added two complement support for I256.

* Add I256 support into parse_units

Co-authored-by: Dave Belvedere <dave@protonmail.com>
This commit is contained in:
Dave Belvedere 2022-11-17 10:03:07 +10:00 committed by GitHub
parent 74bf6fbb04
commit a0fb1bf196
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 302 additions and 46 deletions

View File

@ -94,9 +94,11 @@
- [#1632](https://github.com/gakonst/ethers-rs/pull/1632) Re-export `H32` from `ethabi`.
- [#1634](https://github.com/gakonst/ethers-rs/pull/1634) Derive missing `Clone`, `Copy` and `Debug` impls in ethers-etherscan.
- Bytes debug format now displays hex literals [#1658](https://github.com/gakonst/ethers-rs/pull/1658)
- [#1451](https://github.com/gakonst/ethers-rs/issues/1451) Add Arithemtic Shift Left operation for I256
- [#1860](https://github.com/gakonst/ethers-rs/pull/1860)Update I256 type documentation calling out the inconsistency
- [#1451](https://github.com/gakonst/ethers-rs/issues/1451) Add Arithmetic Shift Left operation for I256
- [#1860](https://github.com/gakonst/ethers-rs/pull/1860) Update I256 type documentation calling out the inconsistency
between its right shift operator and standard library numeric types.
- [#842](https://github.com/gakonst/ethers-rs/issues/842) Add support for I256 types in `parse_units` and `format_units`.
Added `twos_complement` function for I256.
## ethers-contract-abigen

View File

@ -4,6 +4,7 @@
use crate::{
abi::{InvalidOutputType, Token, Tokenizable},
types::U256,
utils::ParseUnits,
};
use ethabi::ethereum_types::FromDecStrErr;
use serde::{Deserialize, Serialize};
@ -977,13 +978,21 @@ impl I256 {
} else {
let result = self << shift;
if result.sign() != self.sign() {
// Overflow occured
// Overflow occurred
None
} else {
Some(result)
}
}
}
/// Compute the twos complement of the I256
pub fn twos_complement(self) -> U256 {
match self.sign() {
Sign::Positive => self.into_raw(),
Sign::Negative => twos_complement(self.into_raw()),
}
}
}
macro_rules! impl_from {
@ -1042,6 +1051,15 @@ impl TryFrom<I256> for U256 {
}
}
impl From<ParseUnits> for I256 {
fn from(n: ParseUnits) -> Self {
match n {
ParseUnits::U256(n) => Self::from_raw(n),
ParseUnits::I256(n) => n,
}
}
}
impl str::FromStr for I256 {
type Err = ParseI256Error;
@ -1828,4 +1846,37 @@ mod tests {
assert_eq!(I256::from_token(42i32.into_token()).unwrap(), I256::from(42),);
assert_eq!(I256::from_token(U256::MAX.into_token()).unwrap(), I256::minus_one(),);
}
#[test]
fn twos_complement() {
macro_rules! assert_twos_complement {
($signed:ty, $unsigned:ty) => {
assert_eq!(
I256::from(<$signed>::MAX).twos_complement(),
U256::from(<$signed>::MAX)
);
assert_eq!(
I256::from(<$signed>::MIN).twos_complement(),
U256::from(<$signed>::MIN.unsigned_abs())
);
assert_eq!(I256::from(0 as $signed).twos_complement(), U256::from(0 as $signed));
assert_eq!(
I256::from(<$unsigned>::MAX).twos_complement(),
U256::from(<$unsigned>::MAX)
);
assert_eq!(
I256::from(0 as $unsigned).twos_complement(),
U256::from(0 as $unsigned)
);
};
}
assert_twos_complement!(i8, u8);
assert_twos_complement!(i16, u16);
assert_twos_complement!(i32, u32);
assert_twos_complement!(i64, u64);
assert_twos_complement!(i128, u128);
assert_twos_complement!(isize, usize);
}
}

View File

@ -30,7 +30,7 @@ mod uint8;
pub use uint8::*;
mod i256;
pub use i256::{Sign, I256};
pub use i256::{ParseI256Error, Sign, I256};
mod bytes;
pub use self::bytes::{deserialize_bytes, serialize_bytes, Bytes, ParseBytesError};

View File

@ -31,7 +31,7 @@ pub use rlp;
/// Re-export hex
pub use hex;
use crate::types::{Address, Bytes, I256, U256};
use crate::types::{Address, Bytes, ParseI256Error, I256, U256};
use elliptic_curve::sec1::ToEncodedPoint;
use ethabi::ethereum_types::FromDecStrErr;
use k256::{ecdsa::SigningKey, PublicKey as K256PublicKey};
@ -58,6 +58,8 @@ pub enum ConversionError {
FromDecStrError(#[from] FromDecStrErr),
#[error("Overflow parsing string")]
ParseOverflow,
#[error(transparent)]
ParseI256Error(#[from] ParseI256Error),
}
/// 1 Ether = 1e18 Wei == 0x0de0b6b3a7640000 Wei
@ -76,6 +78,41 @@ pub const EIP1559_FEE_ESTIMATION_PRIORITY_FEE_TRIGGER: u64 = 100_000_000_000;
/// under it.
pub const EIP1559_FEE_ESTIMATION_THRESHOLD_MAX_CHANGE: i64 = 200;
/// This enum holds the numeric types that a possible to be returned by `parse_units` and
/// that are taken by `format_units`.
#[derive(Copy, Clone)]
pub enum ParseUnits {
U256(U256),
I256(I256),
}
impl From<ParseUnits> for U256 {
fn from(n: ParseUnits) -> Self {
match n {
ParseUnits::U256(n) => n,
ParseUnits::I256(n) => n.into_raw(),
}
}
}
macro_rules! construct_format_units_from {
($( $t:ty[$convert:ident] ),*) => {
$(
impl From<$t> for ParseUnits {
fn from(num: $t) -> Self {
Self::$convert(num.into())
}
}
)*
}
}
// Generate the From<T> code for the given numeric types below.
construct_format_units_from! {
u8[U256], u16[U256], u32[U256], u64[U256], u128[U256], U256[U256], usize[U256],
i8[I256], i16[I256], i32[I256], i64[I256], i128[I256], I256[I256], isize[I256]
}
/// Format the output for the user which prefer to see values
/// in ether (instead of wei)
///
@ -97,22 +134,43 @@ pub fn format_ether<T: Into<U256>>(amount: T) -> U256 {
///
/// let eth = format_units(U256::from_dec_str("1395633240123456789").unwrap(), "ether").unwrap();
/// assert_eq!(eth, "1.395633240123456789");
///
/// let eth = format_units(i64::MIN, "gwei").unwrap();
/// assert_eq!(eth, "-9223372036.854775808");
///
/// let eth = format_units(i128::MIN, 36).unwrap();
/// assert_eq!(eth, "-170.141183460469231731687303715884105728");
/// ```
pub fn format_units<T, K>(amount: T, units: K) -> Result<String, ConversionError>
where
T: Into<U256>,
T: Into<ParseUnits>,
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()));
Ok(format!(
"{}.{:0width$}",
amount_integer,
amount_decimals.as_u128(),
width = units.as_num() as usize
))
match amount.into() {
ParseUnits::U256(amount) => {
let amount_decimals = amount % U256::from(10_u128.pow(units.as_num()));
let amount_integer = amount / U256::from(10_u128.pow(units.as_num()));
Ok(format!(
"{}.{:0width$}",
amount_integer,
amount_decimals.as_u128(),
width = units.as_num() as usize
))
}
ParseUnits::I256(amount) => {
let sign = if amount.is_negative() { "-" } else { "" };
let amount_decimals = amount % I256::from(10_u128.pow(units.as_num()));
let amount_integer = amount / I256::from(10_u128.pow(units.as_num()));
Ok(format!(
"{}{}.{:0width$}",
sign,
amount_integer.twos_complement(),
amount_decimals.twos_complement().as_u128(),
width = units.as_num() as usize
))
}
}
}
/// Converts the input to a U256 and converts from Ether to Wei.
@ -129,7 +187,7 @@ pub fn parse_ether<S>(eth: S) -> Result<U256, ConversionError>
where
S: ToString,
{
parse_units(eth, "ether")
Ok(parse_units(eth, "ether")?.into())
}
/// Multiplies the provided amount with 10^{units} provided.
@ -139,24 +197,25 @@ where
/// let amount_in_eth = U256::from_dec_str("15230001000000000000").unwrap();
/// let amount_in_gwei = U256::from_dec_str("15230001000").unwrap();
/// let amount_in_wei = U256::from_dec_str("15230001000").unwrap();
/// assert_eq!(amount_in_eth, parse_units("15.230001000000000000", "ether").unwrap());
/// assert_eq!(amount_in_gwei, parse_units("15.230001000000000000", "gwei").unwrap());
/// assert_eq!(amount_in_wei, parse_units("15230001000", "wei").unwrap());
/// assert_eq!(amount_in_eth, parse_units("15.230001000000000000", "ether").unwrap().into());
/// assert_eq!(amount_in_gwei, parse_units("15.230001000000000000", "gwei").unwrap().into());
/// assert_eq!(amount_in_wei, parse_units("15230001000", "wei").unwrap().into());
/// ```
/// Example of trying to parse decimal WEI, which should fail, as WEI is the smallest
/// ETH denominator. 1 ETH = 10^18 WEI.
/// ```should_panic
/// use ethers_core::{types::U256, utils::parse_units};
/// 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().into());
/// ```
pub fn parse_units<K, S>(amount: S, units: K) -> Result<U256, ConversionError>
pub fn parse_units<K, S>(amount: S, units: K) -> Result<ParseUnits, ConversionError>
where
S: ToString,
K: TryInto<Units, Error = ConversionError> + Copy,
{
let exponent: u32 = units.try_into()?.as_num();
let mut amount_str = amount.to_string().replace('_', "");
let negative = amount_str.chars().next().unwrap_or_default() == '-';
let dec_len = if let Some(di) = amount_str.find('.') {
amount_str.remove(di);
amount_str[di..].len() as u32
@ -167,16 +226,37 @@ where
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)
if negative {
// Edge case: We have removed the entire number and only the negative sign is left.
// Return 0 as a I256 given the input was signed.
if amount_str == "-" {
Ok(ParseUnits::I256(I256::zero()))
} else {
Ok(ParseUnits::I256(I256::from_dec_str(amount_str)?))
}
} else {
Ok(ParseUnits::U256(U256::from_dec_str(amount_str)?))
}
} else if negative {
// Edge case: Only a negative sign was given, return 0 as a I256 given the input was signed.
if amount_str == "-" {
Ok(ParseUnits::I256(I256::zero()))
} else {
let mut n = I256::from_dec_str(&amount_str)?;
n *= I256::from(10)
.checked_pow(exponent - dec_len)
.ok_or(ConversionError::ParseOverflow)?;
Ok(ParseUnits::I256(n))
}
} 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)
Ok(ParseUnits::U256(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
/// sent (nonce). The sender and nonce are RLP encoded and then hashed with Keccak-256.
@ -432,7 +512,7 @@ mod tests {
}
#[test]
fn test_format_units() {
fn test_format_units_unsigned() {
let gwei_in_ether = format_units(WEI_IN_ETHER, 9).unwrap();
assert_eq!(gwei_in_ether.parse::<f64>().unwrap() as u64, 1e9 as u64);
@ -453,51 +533,173 @@ mod tests {
let eth =
format_units(U256::from_dec_str("1005633240123456789").unwrap(), "ether").unwrap();
assert_eq!(eth, "1.005633240123456789");
let eth = format_units(255u8, 4).unwrap();
assert_eq!(eth, "0.0255");
let eth = format_units(u16::MAX, "ether").unwrap();
assert_eq!(eth, "0.000000000000065535");
// Note: This covers usize on 32 bit systems.
let eth = format_units(u32::MAX, 18).unwrap();
assert_eq!(eth, "0.000000004294967295");
// Note: This covers usize on 64 bit systems.
let eth = format_units(u64::MAX, "gwei").unwrap();
assert_eq!(eth, "18446744073.709551615");
let eth = format_units(u128::MAX, 36).unwrap();
assert_eq!(eth, "340.282366920938463463374607431768211455");
}
#[test]
fn test_format_units_signed() {
let eth =
format_units(I256::from_dec_str("-1395633240123456000").unwrap(), "ether").unwrap();
assert_eq!(eth.parse::<f64>().unwrap(), -1.395633240123456);
let eth =
format_units(I256::from_dec_str("-1395633240123456789").unwrap(), "ether").unwrap();
assert_eq!(eth, "-1.395633240123456789");
let eth =
format_units(I256::from_dec_str("1005633240123456789").unwrap(), "ether").unwrap();
assert_eq!(eth, "1.005633240123456789");
let eth = format_units(i8::MIN, 4).unwrap();
assert_eq!(eth, "-0.0128");
assert_eq!(eth.parse::<f64>().unwrap(), -0.0128_f64);
let eth = format_units(i8::MAX, 4).unwrap();
assert_eq!(eth, "0.0127");
assert_eq!(eth.parse::<f64>().unwrap(), 0.0127);
let eth = format_units(i16::MIN, "ether").unwrap();
assert_eq!(eth, "-0.000000000000032768");
// Note: This covers isize on 32 bit systems.
let eth = format_units(i32::MIN, 18).unwrap();
assert_eq!(eth, "-0.000000002147483648");
// Note: This covers isize on 64 bit systems.
let eth = format_units(i64::MIN, "gwei").unwrap();
assert_eq!(eth, "-9223372036.854775808");
let eth = format_units(i128::MIN, 36).unwrap();
assert_eq!(eth, "-170.141183460469231731687303715884105728");
}
#[test]
fn parse_large_units() {
let decimals = 27u32;
let val = "10.55";
let unit = parse_units(val, decimals).unwrap();
assert_eq!(unit.to_string(), "10550000000000000000000000000");
let n: U256 = parse_units(val, decimals).unwrap().into();
assert_eq!(n.to_string(), "10550000000000000000000000000");
}
#[test]
fn test_parse_units() {
let gwei = parse_units(1.5, 9).unwrap();
let gwei: U256 = parse_units(1.5, 9).unwrap().into();
assert_eq!(gwei.as_u64(), 15e8 as u64);
let token = parse_units(1163.56926418, 8).unwrap();
let token: U256 = parse_units(1163.56926418, 8).unwrap().into();
assert_eq!(token.as_u64(), 116356926418);
let eth_dec_float = parse_units(1.39563324, "ether").unwrap();
let eth_dec_float: U256 = parse_units(1.39563324, "ether").unwrap().into();
assert_eq!(eth_dec_float, U256::from_dec_str("1395633240000000000").unwrap());
let eth_dec_string = parse_units("1.39563324", "ether").unwrap();
let eth_dec_string: U256 = parse_units("1.39563324", "ether").unwrap().into();
assert_eq!(eth_dec_string, U256::from_dec_str("1395633240000000000").unwrap());
let eth = parse_units(1, "ether").unwrap();
let eth: U256 = parse_units(1, "ether").unwrap().into();
assert_eq!(eth, WEI_IN_ETHER);
let val = parse_units("2.3", "ether").unwrap();
let val: U256 = parse_units("2.3", "ether").unwrap().into();
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");
let n: U256 = parse_units(".2", 2).unwrap().into();
assert_eq!(n, U256::from(20), "leading dot");
let n: U256 = parse_units("333.21", 2).unwrap().into();
assert_eq!(n, U256::from(33321), "trailing dot");
let n: U256 = parse_units("98766", 16).unwrap().into();
assert_eq!(n, U256::from_dec_str("987660000000000000000").unwrap(), "no dot");
let n: U256 = parse_units("3_3_0", 3).unwrap().into();
assert_eq!(n, U256::from(330000), "underscore");
let n: U256 = parse_units("330", 0).unwrap().into();
assert_eq!(n, U256::from(330), "zero decimals");
let n: U256 = parse_units(".1234", 3).unwrap().into();
assert_eq!(n, U256::from(123), "truncate too many decimals");
assert!(parse_units("1", 80).is_err(), "overflow");
assert!(parse_units("1", -1).is_err(), "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");
let n: U256 = parse_units("2", 30).unwrap().into();
assert_eq!(n, two_e30, "2e30");
let n: U256 = parse_units(".33_319_2", 0).unwrap().into();
assert_eq!(n, U256::zero(), "mix");
let n: U256 = parse_units("", 3).unwrap().into();
assert_eq!(n, U256::zero(), "empty");
}
#[test]
fn test_signed_parse_units() {
let gwei: I256 = parse_units(-1.5, 9).unwrap().into();
assert_eq!(gwei.as_i64(), -15e8 as i64);
let token: I256 = parse_units(-1163.56926418, 8).unwrap().into();
assert_eq!(token.as_i64(), -116356926418);
let eth_dec_float: I256 = parse_units(-1.39563324, "ether").unwrap().into();
assert_eq!(eth_dec_float, I256::from_dec_str("-1395633240000000000").unwrap());
let eth_dec_string: I256 = parse_units("-1.39563324", "ether").unwrap().into();
assert_eq!(eth_dec_string, I256::from_dec_str("-1395633240000000000").unwrap());
let eth: I256 = parse_units(-1, "ether").unwrap().into();
assert_eq!(eth, I256::from_raw(WEI_IN_ETHER) * I256::minus_one());
let val: I256 = parse_units("-2.3", "ether").unwrap().into();
assert_eq!(val, I256::from_dec_str("-2300000000000000000").unwrap());
let n: I256 = parse_units("-.2", 2).unwrap().into();
assert_eq!(n, I256::from(-20), "leading dot");
let n: I256 = parse_units("-333.21", 2).unwrap().into();
assert_eq!(n, I256::from(-33321), "trailing dot");
let n: I256 = parse_units("-98766", 16).unwrap().into();
assert_eq!(n, I256::from_dec_str("-987660000000000000000").unwrap(), "no dot");
let n: I256 = parse_units("-3_3_0", 3).unwrap().into();
assert_eq!(n, I256::from(-330000), "underscore");
let n: I256 = parse_units("-330", 0).unwrap().into();
assert_eq!(n, I256::from(-330), "zero decimals");
let n: I256 = parse_units("-.1234", 3).unwrap().into();
assert_eq!(n, I256::from(-123), "truncate too many decimals");
assert!(parse_units("-1", 80).is_err(), "overflow");
let two_e30 =
I256::from(-2) * I256::from_raw(U256([0x4674edea40000000, 0xc9f2c9cd0, 0x0, 0x0]));
let n: I256 = parse_units("-2", 30).unwrap().into();
assert_eq!(n, two_e30, "-2e30");
let n: I256 = parse_units("-.33_319_2", 0).unwrap().into();
assert_eq!(n, I256::zero(), "mix");
let n: I256 = parse_units("-", 3).unwrap().into();
assert_eq!(n, I256::zero(), "empty");
}
#[test]

View File

@ -97,6 +97,7 @@ async fn pending_txs_with_confirmations_testnet() {
#[cfg(not(feature = "celo"))]
use ethers_core::types::{Address, Eip1559TransactionRequest};
use ethers_core::utils::parse_ether;
// different keys to avoid nonce errors
#[tokio::test]
@ -317,7 +318,7 @@ impl TestWallets {
.nonce(nonce)
.to(addr)
// 0.1 eth per wallet
.value(parse_units("1", 18).unwrap());
.value(parse_ether("1").unwrap());
pending_txs.push(
provider.send_transaction(tx, Some(BlockNumber::Pending.into())).await.unwrap(),
);