diff --git a/ethereum-types/Cargo.toml b/ethereum-types/Cargo.toml index e3b413f20..544d6fa69 100644 --- a/ethereum-types/Cargo.toml +++ b/ethereum-types/Cargo.toml @@ -26,3 +26,4 @@ serialize = ["std", "impl-serde", "primitive-types/serde", "ethbloom/serialize"] arbitrary = ["ethbloom/arbitrary", "fixed-hash/arbitrary", "uint-crate/arbitrary"] rlp = ["impl-rlp", "ethbloom/rlp", "primitive-types/rlp"] codec = ["impl-codec", "ethbloom/codec"] +num-traits = ["primitive-types/num-traits"] diff --git a/ethereum-types/src/lib.rs b/ethereum-types/src/lib.rs index d94ae57c0..ce84e3731 100644 --- a/ethereum-types/src/lib.rs +++ b/ethereum-types/src/lib.rs @@ -13,6 +13,8 @@ mod uint; pub use ethbloom::{Bloom, BloomRef, Input as BloomInput}; pub use hash::{BigEndianHash, H128, H160, H256, H264, H32, H512, H520, H64}; +#[cfg(feature = "num-traits")] +pub use primitive_types::{FromStrRadixErr, FromStrRadixErrKind}; pub use uint::{FromDecStrErr, U128, U256, U512, U64}; pub type Address = H160; diff --git a/primitive-types/Cargo.toml b/primitive-types/Cargo.toml index d881bce56..4c691d812 100644 --- a/primitive-types/Cargo.toml +++ b/primitive-types/Cargo.toml @@ -12,6 +12,7 @@ fixed-hash = { version = "0.6", path = "../fixed-hash", default-features = false uint = { version = "0.8.3", path = "../uint", default-features = false } impl-serde = { version = "0.3.1", path = "impls/serde", default-features = false, optional = true } impl-codec = { version = "0.4.1", path = "impls/codec", default-features = false, optional = true } +impl-num-traits = { version = "0.1.0", path = "impls/num-traits", default-features = false, optional = true } impl-rlp = { version = "0.3", path = "impls/rlp", default-features = false, optional = true } scale-info = { version = "0.4", features = ["derive"], default-features = false, optional = true } @@ -26,6 +27,7 @@ codec = ["impl-codec"] rlp = ["impl-rlp"] arbitrary = ["fixed-hash/arbitrary", "uint/arbitrary"] fp-conversion = ["std"] +num-traits = ["impl-num-traits"] [[test]] name = "scale_info" diff --git a/primitive-types/impls/num-traits/Cargo.toml b/primitive-types/impls/num-traits/Cargo.toml new file mode 100644 index 000000000..fed7c898e --- /dev/null +++ b/primitive-types/impls/num-traits/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "impl-num-traits" +version = "0.1.0" +authors = ["Parity Technologies "] +license = "MIT OR Apache-2.0" +homepage = "https://github.com/paritytech/parity-common" +description = "num-traits implementation for uint." +edition = "2018" + +[dependencies] +num-traits = { version = "0.2", default-features = false } +uint = { version = "0.8.5", path = "../../../uint", default-features = false } + +[features] +default = ["std"] +std = ["num-traits/std", "uint/std"] diff --git a/primitive-types/impls/num-traits/src/lib.rs b/primitive-types/impls/num-traits/src/lib.rs new file mode 100644 index 000000000..d5e5e5d8b --- /dev/null +++ b/primitive-types/impls/num-traits/src/lib.rs @@ -0,0 +1,49 @@ +// Copyright 2020 Parity Technologies +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! num-traits support for uint. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[doc(hidden)] +pub use num_traits; + +pub use uint::{FromStrRadixErr, FromStrRadixErrKind}; + +/// Add num-traits support to an integer created by `construct_uint!`. +#[macro_export] +macro_rules! impl_uint_num_traits { + ($name: ident, $len: expr) => { + impl $crate::num_traits::identities::Zero for $name { + #[inline] + fn zero() -> Self { + Self::zero() + } + + #[inline] + fn is_zero(&self) -> bool { + self.is_zero() + } + } + + impl $crate::num_traits::identities::One for $name { + #[inline] + fn one() -> Self { + Self::one() + } + } + + impl $crate::num_traits::Num for $name { + type FromStrRadixErr = $crate::FromStrRadixErr; + + fn from_str_radix(txt: &str, radix: u32) -> Result { + Self::from_str_radix(txt, radix) + } + } + }; +} diff --git a/primitive-types/src/lib.rs b/primitive-types/src/lib.rs index fe1eb4ac7..bf9acbdc3 100644 --- a/primitive-types/src/lib.rs +++ b/primitive-types/src/lib.rs @@ -22,6 +22,7 @@ use fixed_hash::{construct_fixed_hash, impl_fixed_hash_conversions}; #[cfg(feature = "scale-info")] use scale_info::TypeInfo; use uint::{construct_uint, uint_full_mul_reg}; +pub use uint::{FromStrRadixErr, FromStrRadixErrKind}; /// Error type for conversion. #[derive(Debug, PartialEq, Eq)] @@ -68,6 +69,16 @@ construct_fixed_hash! { pub struct H512(64); } +#[cfg(feature = "num-traits")] +mod num_traits { + use super::*; + use impl_num_traits::impl_uint_num_traits; + + impl_uint_num_traits!(U128, 2); + impl_uint_num_traits!(U256, 4); + impl_uint_num_traits!(U512, 8); +} + #[cfg(feature = "impl-serde")] mod serde { use super::*; diff --git a/uint/src/uint.rs b/uint/src/uint.rs index 0d7a0dcaf..e6b99e537 100644 --- a/uint/src/uint.rs +++ b/uint/src/uint.rs @@ -31,6 +31,102 @@ use core::fmt; +/// A list of error categories encountered when parsing numbers. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +#[non_exhaustive] +pub enum FromStrRadixErrKind { + /// A character in the input string is not valid for the given radix. + InvalidCharacter, + + /// The input length is not valid for the given radix. + InvalidLength, + + /// The given radix is not supported. + UnsupportedRadix, +} + +#[derive(Debug)] +enum FromStrRadixErrSrc { + Hex(FromHexError), + Dec(FromDecStrErr), +} + +impl fmt::Display for FromStrRadixErrSrc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + FromStrRadixErrSrc::Dec(d) => write!(f, "{}", d), + FromStrRadixErrSrc::Hex(h) => write!(f, "{}", h), + } + } +} + +/// The error type for parsing numbers from strings. +#[derive(Debug)] +pub struct FromStrRadixErr { + kind: FromStrRadixErrKind, + source: Option, +} + +impl FromStrRadixErr { + #[doc(hidden)] + pub fn unsupported() -> Self { + Self { kind: FromStrRadixErrKind::UnsupportedRadix, source: None } + } + + /// Returns the corresponding `FromStrRadixErrKind` for this error. + pub fn kind(&self) -> FromStrRadixErrKind { + self.kind + } +} + +impl fmt::Display for FromStrRadixErr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(ref src) = self.source { + return write!(f, "{}", src); + } + + match self.kind { + FromStrRadixErrKind::UnsupportedRadix => write!(f, "the given radix is not supported"), + FromStrRadixErrKind::InvalidCharacter => write!(f, "input contains an invalid character"), + FromStrRadixErrKind::InvalidLength => write!(f, "length not supported for radix or type"), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for FromStrRadixErr { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self.source { + Some(FromStrRadixErrSrc::Dec(ref d)) => Some(d), + Some(FromStrRadixErrSrc::Hex(ref h)) => Some(h), + None => None, + } + } +} + +impl From for FromStrRadixErr { + fn from(e: FromDecStrErr) -> Self { + let kind = match e { + FromDecStrErr::InvalidCharacter => FromStrRadixErrKind::InvalidCharacter, + FromDecStrErr::InvalidLength => FromStrRadixErrKind::InvalidLength, + }; + + Self { kind, source: Some(FromStrRadixErrSrc::Dec(e)) } + } +} + +impl From for FromStrRadixErr { + fn from(e: FromHexError) -> Self { + let kind = match e.inner { + hex::FromHexError::InvalidHexCharacter { .. } => FromStrRadixErrKind::InvalidCharacter, + hex::FromHexError::InvalidStringLength => FromStrRadixErrKind::InvalidLength, + hex::FromHexError::OddLength => FromStrRadixErrKind::InvalidLength, + }; + + Self { kind, source: Some(FromStrRadixErrSrc::Hex(e)) } + } +} + /// Conversion from decimal string error #[derive(Debug, PartialEq)] pub enum FromDecStrErr { @@ -493,6 +589,18 @@ macro_rules! construct_uint { /// Maximum value. pub const MAX: $name = $name([u64::max_value(); $n_words]); + /// Converts a string slice in a given base to an integer. Only supports radixes of 10 + /// and 16. + pub fn from_str_radix(txt: &str, radix: u32) -> Result { + let parsed = match radix { + 10 => Self::from_dec_str(txt)?, + 16 => core::str::FromStr::from_str(txt)?, + _ => return Err($crate::FromStrRadixErr::unsupported()), + }; + + Ok(parsed) + } + /// Convert from a decimal string. pub fn from_dec_str(value: &str) -> $crate::core_::result::Result { if !value.bytes().all(|b| b >= 48 && b <= 57) {