diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 8e68442e3..64328c7bf 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -19,10 +19,6 @@ #[macro_use] extern crate alloc; -// Used in Serde tests. -#[cfg(test)] -use {bincode as _, serde as _, serde_json as _}; - pub mod aliases; #[doc(no_inline)] pub use aliases::{ @@ -48,23 +44,21 @@ pub use log::Log; mod signed; pub use signed::{BigIntConversionError, ParseSignedError, Sign, Signed}; -mod utils; +pub mod utils; pub use utils::{eip191_hash_message, keccak256}; #[doc(no_inline)] -pub use ::bytes; -#[doc(no_inline)] -pub use ::hex; -#[doc(no_inline)] -pub use hex_literal::{self, hex}; -/// Re-export of [`ruint::uint`] for convenience. Note that users of this macro +pub use { + ::hex, + hex_literal::{self, hex}, + ruint::{self, Uint}, + tiny_keccak::{self, Hasher, Keccak}, +}; + +/// Re-export of [`ruint::uint!`] for convenience. Note that users of this macro /// must also add [`ruint`] to their `Cargo.toml` as a dependency. #[doc(inline)] pub use ruint::uint; -#[doc(no_inline)] -pub use ruint::{self, Uint}; -#[doc(no_inline)] -pub use tiny_keccak::{self, Hasher, Keccak}; #[cfg(feature = "serde")] #[doc(no_inline)] diff --git a/crates/primitives/src/signed/int.rs b/crates/primitives/src/signed/int.rs index 0ec9dbded..c75d9068e 100644 --- a/crates/primitives/src/signed/int.rs +++ b/crates/primitives/src/signed/int.rs @@ -65,10 +65,8 @@ impl fmt::Debug for Signed { impl fmt::Display for Signed { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let (sign, abs) = self.into_sign_and_abs(); - // sign must be formatted directly, instead of with `write!` due to the - // `sign_positive` flag sign.fmt(f)?; - write!(f, "{abs}") + abs.fmt(f) } } diff --git a/crates/primitives/src/utils.rs b/crates/primitives/src/utils.rs deleted file mode 100644 index 9c9aea9a5..000000000 --- a/crates/primitives/src/utils.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::{bits::FixedBytes, B256}; -use alloc::{string::ToString, vec::Vec}; -/// The prefix used for hashing messages according to EIP-191. -const EIP191_PREFIX: &str = "\x19Ethereum Signed Message:\n"; - -/// Hash a message according to [EIP-191] (version `0x01`). -/// -/// The final message is a UTF-8 string, encoded as follows: -/// `"\x19Ethereum Signed Message:\n" + message.length + message` -/// -/// This message is then hashed using [Keccak-256](keccak256). -/// -/// [EIP-191]: https://eips.ethereum.org/EIPS/eip-191 -pub fn eip191_hash_message>(message: T) -> B256 { - let message = message.as_ref(); - let len = message.len(); - let len_string = len.to_string(); - - let mut eth_message = Vec::with_capacity(EIP191_PREFIX.len() + len_string.len() + len); - eth_message.extend_from_slice(EIP191_PREFIX.as_bytes()); - eth_message.extend_from_slice(len_string.as_bytes()); - eth_message.extend_from_slice(message); - - keccak256(ð_message) -} - -/// Simple interface to the [`Keccak-256`] hash function. -/// -/// [`Keccak-256`]: https://en.wikipedia.org/wiki/SHA-3 -pub fn keccak256>(bytes: T) -> FixedBytes<32> { - cfg_if::cfg_if! { - if #[cfg(all(feature = "native-keccak", not(feature = "tiny-keccak")))] { - #[link(wasm_import_module = "vm_hooks")] - extern "C" { - /// When targeting VMs with native keccak hooks, the `native-keccak` feature - /// can be enabled to import and use the host environment's implementation - /// of [`keccak256`] in place of [`tiny_keccak`]. This is overridden when - /// the `tiny-keccak` feature is enabled. - /// - /// # Safety - /// - /// The VM accepts the preimage by pointer and length, and writes the - /// 32-byte hash. - /// - `bytes` must point to an input buffer at least `len` long. - /// - `output` must point to a buffer that is at least 32-bytes long. - /// - /// [`keccak256`]: https://en.wikipedia.org/wiki/SHA-3 - /// [`tiny_keccak`]: https://docs.rs/tiny-keccak/latest/tiny_keccak/ - fn native_keccak256(bytes: *const u8, len: usize, output: *mut u8); - } - - /// Calls an external native keccak hook when `native-keccak` is enabled. - /// This is overridden when `tiny-keccak` is enabled. - fn keccak256(bytes: &[u8]) -> FixedBytes<32> { - let mut output = [0; 32]; - // SAFETY: The output is 32-bytes, and the input comes from a slice. - unsafe { native_keccak256(bytes.as_ptr(), bytes.len(), output.as_mut_ptr()) }; - output.into() - } - } else { - use tiny_keccak::{Hasher, Keccak}; - - /// Calls [`tiny-keccak`] when the `tiny-keccak` feature is enabled or - /// when no particular keccak feature flag is specified. - /// - /// [`tiny_keccak`]: https://docs.rs/tiny-keccak/latest/tiny_keccak/ - fn keccak256(bytes: &[u8]) -> FixedBytes<32> { - let mut hasher = Keccak::v256(); - hasher.update(bytes); - let mut output = [0; 32]; - hasher.finalize(&mut output); - output.into() - } - } - } - - keccak256(bytes.as_ref()) -} - -#[cfg(test)] -mod tests { - use super::*; - - // test vector taken from: - // https://web3js.readthedocs.io/en/v1.2.2/web3-eth-accounts.html#hashmessage - #[test] - fn test_hash_message() { - assert_eq!( - eip191_hash_message("Hello World"), - b256!("a1de988600a42c4b4ab089b619297c17d53cffae5d5120d82d8a92d0bb3b78f2") - ); - } -} diff --git a/crates/primitives/src/utils/mod.rs b/crates/primitives/src/utils/mod.rs new file mode 100644 index 000000000..74fd0d3bd --- /dev/null +++ b/crates/primitives/src/utils/mod.rs @@ -0,0 +1,122 @@ +//! Common Ethereum utilities. + +use crate::B256; +use alloc::vec::Vec; +use core::mem::MaybeUninit; + +mod units; +pub use units::{ + format_ether, format_units, parse_ether, parse_units, ParseUnits, Unit, UnitsError, +}; + +#[doc(hidden)] +#[deprecated(since = "0.5.0", note = "use `Unit::ETHER.wei()` instead")] +pub const WEI_IN_ETHER: crate::U256 = Unit::ETHER.wei_const(); + +#[doc(hidden)] +#[deprecated(since = "0.5.0", note = "use `Unit` instead")] +pub type Units = Unit; + +/// The prefix used for hashing messages according to EIP-191. +pub const EIP191_PREFIX: &str = "\x19Ethereum Signed Message:\n"; + +/// Hash a message according to [EIP-191] (version `0x01`). +/// +/// The final message is a UTF-8 string, encoded as follows: +/// `"\x19Ethereum Signed Message:\n" + message.length + message` +/// +/// This message is then hashed using [Keccak-256](keccak256). +/// +/// [EIP-191]: https://eips.ethereum.org/EIPS/eip-191 +pub fn eip191_hash_message>(message: T) -> B256 { + keccak256(eip191_message(message)) +} + +/// Constructs a message according to [EIP-191] (version `0x01`). +/// +/// The final message is a UTF-8 string, encoded as follows: +/// `"\x19Ethereum Signed Message:\n" + message.length + message` +/// +/// [EIP-191]: https://eips.ethereum.org/EIPS/eip-191 +pub fn eip191_message>(message: T) -> Vec { + fn eip191_message(message: &[u8]) -> Vec { + let len = message.len(); + let mut len_string_buffer = itoa::Buffer::new(); + let len_string = len_string_buffer.format(len); + + let mut eth_message = Vec::with_capacity(EIP191_PREFIX.len() + len_string.len() + len); + eth_message.extend_from_slice(EIP191_PREFIX.as_bytes()); + eth_message.extend_from_slice(len_string.as_bytes()); + eth_message.extend_from_slice(message); + eth_message + } + + eip191_message(message.as_ref()) +} + +/// Simple interface to the [`Keccak-256`] hash function. +/// +/// [`Keccak-256`]: https://en.wikipedia.org/wiki/SHA-3 +pub fn keccak256>(bytes: T) -> B256 { + fn keccak256(bytes: &[u8]) -> B256 { + let mut output = MaybeUninit::::uninit(); + + cfg_if::cfg_if! { + if #[cfg(all(feature = "native-keccak", not(feature = "tiny-keccak")))] { + #[link(wasm_import_module = "vm_hooks")] + extern "C" { + /// When targeting VMs with native keccak hooks, the `native-keccak` feature + /// can be enabled to import and use the host environment's implementation + /// of [`keccak256`] in place of [`tiny_keccak`]. This is overridden when + /// the `tiny-keccak` feature is enabled. + /// + /// # Safety + /// + /// The VM accepts the preimage by pointer and length, and writes the + /// 32-byte hash. + /// - `bytes` must point to an input buffer at least `len` long. + /// - `output` must point to a buffer that is at least 32-bytes long. + /// + /// [`keccak256`]: https://en.wikipedia.org/wiki/SHA-3 + /// [`tiny_keccak`]: https://docs.rs/tiny-keccak/latest/tiny_keccak/ + fn native_keccak256(bytes: *const u8, len: usize, output: *mut u8); + } + + // SAFETY: The output is 32-bytes, and the input comes from a slice. + unsafe { native_keccak256(bytes.as_ptr(), bytes.len(), output.as_mut_ptr().cast()) }; + } else { + use tiny_keccak::{Hasher, Keccak}; + + let mut hasher = Keccak::v256(); + hasher.update(bytes); + // SAFETY: Never reads from `output`. + hasher.finalize(unsafe { (*output.as_mut_ptr()).as_mut_slice() }); + } + } + + // SAFETY: Initialized above. + unsafe { output.assume_init() } + } + + keccak256(bytes.as_ref()) +} + +#[cfg(test)] +mod tests { + use super::*; + + // test vector taken from: + // https://web3js.readthedocs.io/en/v1.10.0/web3-eth-accounts.html#hashmessage + #[test] + fn test_hash_message() { + let msg = "Hello World"; + let eip191_msg = eip191_message(msg); + let hash = keccak256(&eip191_msg); + assert_eq!( + eip191_msg, + [EIP191_PREFIX.as_bytes(), msg.len().to_string().as_bytes(), msg.as_bytes()].concat() + ); + assert_eq!(hash, b256!("a1de988600a42c4b4ab089b619297c17d53cffae5d5120d82d8a92d0bb3b78f2")); + assert_eq!(eip191_hash_message(msg), hash); + } +} diff --git a/crates/primitives/src/utils/units.rs b/crates/primitives/src/utils/units.rs new file mode 100644 index 000000000..fe1a51f97 --- /dev/null +++ b/crates/primitives/src/utils/units.rs @@ -0,0 +1,823 @@ +use crate::{ParseSignedError, I256, U256}; +use alloc::string::{String, ToString}; +use core::fmt; + +const MAX_U64_EXPONENT: u8 = 19; + +/// Converts the input to a U256 and converts from Ether to Wei. +/// +/// # Examples +/// +/// ``` +/// use alloy_primitives::{ +/// utils::{parse_ether, Units}, +/// U256, +/// }; +/// +/// let eth = Units::ETHER.wei(); +/// assert_eq!(parse_ether("1").unwrap(), eth); +/// ``` +pub fn parse_ether(eth: &str) -> Result { + ParseUnits::parse_units(eth, Unit::ETHER).map(Into::into) +} + +/// Parses a decimal number and multiplies it with 10^units. +/// +/// # Examples +/// +/// ``` +/// use alloy_primitives::{utils::parse_units, U256}; +/// +/// let amount_in_eth = U256::from_str_radix("15230001000000000000", 10).unwrap(); +/// let amount_in_gwei = U256::from_str_radix("15230001000", 10).unwrap(); +/// let amount_in_wei = U256::from_str_radix("15230001000", 10).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 alloy_primitives::{utils::parse_units, U256}; +/// let amount_in_wei = U256::from_str_radix("15230001000", 10).unwrap(); +/// assert_eq!(amount_in_wei, parse_units("15.230001000000000000", "wei").unwrap().into()); +/// ``` +pub fn parse_units(amount: &str, units: K) -> Result +where + K: TryInto, + UnitsError: From, +{ + ParseUnits::parse_units(amount, units.try_into()?) +} + +/// Formats the given number of Wei as an Ether amount. +/// +/// # Examples +/// +/// ``` +/// use alloy_primitives::{utils::format_ether, U256}; +/// +/// let eth = format_ether(1395633240123456000_u128); +/// assert_eq!(format_ether(1395633240123456000_u128), "1.395633240123456000"); +/// ``` +pub fn format_ether>(amount: T) -> String { + amount.into().format_units(Unit::ETHER) +} + +/// Formats the given number of Wei as the given unit. +/// +/// # Examples +/// +/// ``` +/// use alloy_primitives::{utils::format_units, U256}; +/// +/// let eth = U256::from_str_radix("1395633240123456000", 10).unwrap(); +/// assert_eq!(format_units(eth, "eth").unwrap(), "1.395633240123456000"); +/// +/// assert_eq!(format_units(i64::MIN, "gwei").unwrap(), "-9223372036.854775808"); +/// +/// assert_eq!(format_units(i128::MIN, 36).unwrap(), "-170.141183460469231731687303715884105728"); +/// ``` +pub fn format_units(amount: T, units: K) -> Result +where + T: Into, + K: TryInto, + UnitsError: From, +{ + units.try_into().map(|units| amount.into().format_units(units)).map_err(UnitsError::from) +} + +/// Error type for [`Unit`]-related operations. +#[derive(Debug)] +pub enum UnitsError { + /// The provided units are not recognized. + InvalidUnit(String), + /// Overflow when parsing a signed number. + ParseSigned(ParseSignedError), +} + +#[cfg(feature = "std")] +impl std::error::Error for UnitsError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::InvalidUnit(_) => None, + Self::ParseSigned(e) => Some(e), + } + } +} + +impl fmt::Display for UnitsError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::InvalidUnit(s) => write!(f, "{s:?} is not a valid unit"), + Self::ParseSigned(e) => e.fmt(f), + } + } +} + +impl From for UnitsError { + fn from(value: ruint::ParseError) -> Self { + Self::ParseSigned(value.into()) + } +} + +impl From for UnitsError { + fn from(value: ParseSignedError) -> Self { + Self::ParseSigned(value) + } +} + +/// 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, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum ParseUnits { + /// Unsigned 256-bit integer. + U256(U256), + /// Signed 256-bit integer. + I256(I256), +} + +impl From for U256 { + #[inline] + fn from(value: ParseUnits) -> Self { + value.get_absolute() + } +} + +impl From for I256 { + #[inline] + fn from(value: ParseUnits) -> Self { + value.get_signed() + } +} + +impl fmt::Display for ParseUnits { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ParseUnits::U256(val) => val.fmt(f), + ParseUnits::I256(val) => val.fmt(f), + } + } +} + +macro_rules! impl_from_integers { + ($convert:ident($($t:ty),* $(,)?)) => {$( + impl From<$t> for ParseUnits { + fn from(value: $t) -> Self { + Self::$convert($convert::try_from(value).unwrap()) + } + } + )*} +} + +impl_from_integers!(U256(u8, u16, u32, u64, u128, usize, U256)); +impl_from_integers!(I256(i8, i16, i32, i64, i128, isize, I256)); + +impl ParseUnits { + /// Parses a decimal number and multiplies it with 10^units. + /// + /// See [`parse_units`] for more information. + #[allow(clippy::self_named_constructors)] + pub fn parse_units(amount: &str, unit: Unit) -> Result { + let exponent = unit.get() as usize; + + let mut amount = amount.to_string(); + let negative = amount.starts_with('-'); + let dec_len = if let Some(di) = amount.find('.') { + amount.remove(di); + amount[di..].len() + } else { + 0 + }; + let amount = amount.as_str(); + + if dec_len > exponent { + // Truncate the decimal part if it is longer than the exponent + let amount = &amount[..(amount.len() - (dec_len - exponent))]; + 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 == "-" { + Ok(Self::I256(I256::ZERO)) + } else { + Ok(Self::I256(I256::from_dec_str(amount)?)) + } + } else { + Ok(Self::U256(U256::from_str_radix(amount, 10)?)) + } + } else if negative { + // Edge case: Only a negative sign was given, return 0 as a I256 given the input was + // signed. + if amount == "-" { + Ok(Self::I256(I256::ZERO)) + } else { + let mut n = I256::from_dec_str(amount)?; + n *= I256::try_from(10u8) + .unwrap() + .checked_pow(U256::from(exponent - dec_len)) + .ok_or(UnitsError::ParseSigned(ParseSignedError::IntegerOverflow))?; + Ok(Self::I256(n)) + } + } else { + let mut a_uint = U256::from_str_radix(amount, 10)?; + a_uint *= U256::from(10) + .checked_pow(U256::from(exponent - dec_len)) + .ok_or(UnitsError::ParseSigned(ParseSignedError::IntegerOverflow))?; + Ok(Self::U256(a_uint)) + } + } + + /// Formats the given number of Wei as the given unit. + /// + /// See [`format_units`] for more information. + pub fn format_units(&self, mut unit: Unit) -> String { + // Edge case: If the number is signed and the unit is the largest possible unit, we need to + // subtract 1 from the unit to avoid overflow. + if self.is_signed() && unit == Unit::MAX { + unit = Unit::new(Unit::MAX.get() - 1).unwrap(); + } + let units = unit.get() as usize; + let exp10 = unit.wei(); + + // TODO: `decimals` are formatted twice because U256 does not support alignment + // (`:0>width`). + match *self { + Self::U256(amount) => { + let integer = amount / exp10; + let decimals = (amount % exp10).to_string(); + format!("{integer}.{decimals:0>units$}") + } + Self::I256(amount) => { + let exp10 = I256::from_raw(exp10); + let sign = if amount.is_negative() { "-" } else { "" }; + let integer = (amount / exp10).twos_complement(); + let decimals = ((amount % exp10).twos_complement()).to_string(); + format!("{sign}{integer}.{decimals:0>units$}") + } + } + } + + /// Returns `true` if the number is signed. + #[inline] + pub const fn is_signed(&self) -> bool { + matches!(self, Self::I256(_)) + } + + /// Returns `true` if the number is unsigned. + #[inline] + pub const fn is_unsigned(&self) -> bool { + matches!(self, Self::U256(_)) + } + + /// Returns `true` if the number is negative. + #[inline] + pub const fn is_negative(&self) -> bool { + match self { + Self::U256(_) => false, + Self::I256(n) => n.is_negative(), + } + } + + /// Returns `true` if the number is positive. + #[inline] + pub const fn is_positive(&self) -> bool { + match self { + Self::U256(_) => true, + Self::I256(n) => n.is_positive(), + } + } + + /// Returns `true` if the number is zero. + #[inline] + pub fn is_zero(&self) -> bool { + match self { + Self::U256(n) => n.is_zero(), + Self::I256(n) => n.is_zero(), + } + } + + /// Returns the absolute value of the number. + #[inline] + pub const fn get_absolute(self) -> U256 { + match self { + Self::U256(n) => n, + Self::I256(n) => n.into_raw(), + } + } + + /// Returns the signed value of the number. + #[inline] + pub const fn get_signed(self) -> I256 { + match self { + Self::U256(n) => I256::from_raw(n), + Self::I256(n) => n, + } + } +} + +/// Ethereum unit. Always less than [`77`](Unit::MAX). +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Unit(u8); + +impl fmt::Display for Unit { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.get().fmt(f) + } +} + +impl TryFrom for Unit { + type Error = UnitsError; + + fn try_from(value: u8) -> Result { + Self::new(value).ok_or_else(|| UnitsError::InvalidUnit(value.to_string())) + } +} + +impl TryFrom for Unit { + type Error = UnitsError; + + fn try_from(value: String) -> Result { + value.parse() + } +} + +impl<'a> TryFrom<&'a String> for Unit { + type Error = UnitsError; + + fn try_from(value: &'a String) -> Result { + value.parse() + } +} + +impl TryFrom<&str> for Unit { + type Error = UnitsError; + + fn try_from(value: &str) -> Result { + value.parse() + } +} + +impl core::str::FromStr for Unit { + type Err = UnitsError; + + fn from_str(s: &str) -> Result { + Ok(match s.to_ascii_lowercase().as_str() { + "eth" | "ether" => Self::ETHER, + "pwei" | "milli" | "milliether" | "finney" => Self::PWEI, + "twei" | "micro" | "microether" | "szabo" => Self::TWEI, + "gwei" | "nano" | "nanoether" | "shannon" => Self::GWEI, + "mwei" | "pico" | "picoether" | "lovelace" => Self::MWEI, + "kwei" | "femto" | "femtoether" | "babbage" => Self::KWEI, + "wei" => Self::WEI, + _ => return Err(UnitsError::InvalidUnit(s.to_string())), + }) + } +} + +impl Unit { + /// Wei is equivalent to 1 wei. + pub const WEI: Self = unsafe { Self::new_unchecked(0) }; + #[allow(non_upper_case_globals)] + #[doc(hidden)] + #[deprecated(since = "0.5.0", note = "use `Units::WEI` instead")] + pub const Wei: Self = Self::WEI; + + /// Kwei is equivalent to 1e3 wei. + pub const KWEI: Self = unsafe { Self::new_unchecked(3) }; + #[allow(non_upper_case_globals)] + #[doc(hidden)] + #[deprecated(since = "0.5.0", note = "use `Units::KWEI` instead")] + pub const Kwei: Self = Self::KWEI; + + /// Mwei is equivalent to 1e6 wei. + pub const MWEI: Self = unsafe { Self::new_unchecked(6) }; + #[allow(non_upper_case_globals)] + #[doc(hidden)] + #[deprecated(since = "0.5.0", note = "use `Units::MWEI` instead")] + pub const Mwei: Self = Self::MWEI; + + /// Gwei is equivalent to 1e9 wei. + pub const GWEI: Self = unsafe { Self::new_unchecked(9) }; + #[allow(non_upper_case_globals)] + #[doc(hidden)] + #[deprecated(since = "0.5.0", note = "use `Units::GWEI` instead")] + pub const Gwei: Self = Self::GWEI; + + /// Twei is equivalent to 1e12 wei. + pub const TWEI: Self = unsafe { Self::new_unchecked(12) }; + #[allow(non_upper_case_globals)] + #[doc(hidden)] + #[deprecated(since = "0.5.0", note = "use `Units::TWEI` instead")] + pub const Twei: Self = Self::TWEI; + + /// Pwei is equivalent to 1e15 wei. + pub const PWEI: Self = unsafe { Self::new_unchecked(15) }; + #[allow(non_upper_case_globals)] + #[doc(hidden)] + #[deprecated(since = "0.5.0", note = "use `Units::PWEI` instead")] + pub const Pwei: Self = Self::PWEI; + + /// Ether is equivalent to 1e18 wei. + pub const ETHER: Self = unsafe { Self::new_unchecked(18) }; + #[allow(non_upper_case_globals)] + #[doc(hidden)] + #[deprecated(since = "0.5.0", note = "use `Units::ETHER` instead")] + pub const Ether: Self = Self::ETHER; + + /// The smallest unit. + pub const MIN: Self = Self::WEI; + /// The largest unit. + pub const MAX: Self = unsafe { Self::new_unchecked(77) }; + + /// Creates a new `Units` instance, checking for overflow. + #[inline] + pub const fn new(units: u8) -> Option { + if units <= Self::MAX.get() { + // SAFETY: `units` is contained in the valid range. + Some(unsafe { Self::new_unchecked(units) }) + } else { + None + } + } + + /// Creates a new `Units` instance. + /// + /// # Safety + /// + /// `x` must be less than [`Unit::MAX`]. + #[inline] + pub const unsafe fn new_unchecked(x: u8) -> Self { + Self(x) + } + + /// Returns `10^self`, which is the number of Wei in this unit. + /// + /// # Examples + /// + /// ``` + /// use alloy_primitives::{utils::Units, U256}; + /// + /// assert_eq!(U256::from(1u128), Units::WEI.wei()); + /// assert_eq!(U256::from(1_000u128), Units::KWEI.wei()); + /// assert_eq!(U256::from(1_000_000u128), Units::MWEI.wei()); + /// assert_eq!(U256::from(1_000_000_000u128), Units::GWEI.wei()); + /// assert_eq!(U256::from(1_000_000_000_000u128), Units::TWEI.wei()); + /// assert_eq!(U256::from(1_000_000_000_000_000u128), Units::PWEI.wei()); + /// assert_eq!(U256::from(1_000_000_000_000_000_000u128), Units::ETHER.wei()); + /// ``` + #[inline] + pub fn wei(self) -> U256 { + if self.get() <= MAX_U64_EXPONENT { + self.wei_const() + } else { + U256::from(10u8).pow(U256::from(self.get())) + } + } + + /// Returns `10^self`, which is the number of Wei in this unit. + /// + /// # Panics + /// + /// Panics if `10^self` would overflow a `u64` (`self > 19`). If this can happen, use + /// [`wei`](Self::wei) instead. + #[inline] + pub const fn wei_const(self) -> U256 { + if self.get() > MAX_U64_EXPONENT { + panic!("overflow") + } + U256::from_limbs([10u64.pow(self.get() as u32), 0, 0, 0]) + } + + /// Returns the numeric value of the unit. + #[inline] + pub const fn get(self) -> u8 { + self.0 + } + + #[doc(hidden)] + #[deprecated(since = "0.5.0", note = "use `get` instead")] + pub const fn as_num(&self) -> u8 { + self.get() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn unit_values() { + assert_eq!(Unit::WEI.get(), 0); + assert_eq!(Unit::KWEI.get(), 3); + assert_eq!(Unit::MWEI.get(), 6); + assert_eq!(Unit::GWEI.get(), 9); + assert_eq!(Unit::TWEI.get(), 12); + assert_eq!(Unit::PWEI.get(), 15); + assert_eq!(Unit::ETHER.get(), 18); + assert_eq!(Unit::new(10).unwrap().get(), 10); + assert_eq!(Unit::new(20).unwrap().get(), 20); + } + + #[test] + fn unit_wei() { + let assert = |unit: Unit| { + let wei = unit.wei(); + assert_eq!(wei.to::(), 10u128.pow(unit.get() as u32)); + assert_eq!(wei, U256::from(10u8).pow(U256::from(unit.get()))); + }; + assert(Unit::WEI); + assert(Unit::KWEI); + assert(Unit::MWEI); + assert(Unit::GWEI); + assert(Unit::TWEI); + assert(Unit::PWEI); + assert(Unit::ETHER); + assert(Unit::new(10).unwrap()); + assert(Unit::new(20).unwrap()); + } + + #[test] + fn parse() { + assert_eq!(Unit::try_from("wei").unwrap(), Unit::WEI); + assert_eq!(Unit::try_from("kwei").unwrap(), Unit::KWEI); + assert_eq!(Unit::try_from("mwei").unwrap(), Unit::MWEI); + assert_eq!(Unit::try_from("gwei").unwrap(), Unit::GWEI); + assert_eq!(Unit::try_from("twei").unwrap(), Unit::TWEI); + assert_eq!(Unit::try_from("pwei").unwrap(), Unit::PWEI); + assert_eq!(Unit::try_from("ether").unwrap(), Unit::ETHER); + } + + #[test] + fn wei_in_ether() { + assert_eq!(Unit::ETHER.wei(), U256::from(1e18 as u64)); + } + + #[test] + fn test_format_ether_unsigned() { + let eth = format_ether(Unit::ETHER.wei()); + assert_eq!(eth.parse::().unwrap() as u64, 1); + + let eth = format_ether(1395633240123456000_u128); + assert_eq!(eth.parse::().unwrap(), 1.395633240123456); + + let eth = format_ether(U256::from_str_radix("1395633240123456000", 10).unwrap()); + assert_eq!(eth.parse::().unwrap(), 1.395633240123456); + + let eth = format_ether(U256::from_str_radix("1395633240123456789", 10).unwrap()); + assert_eq!(eth, "1.395633240123456789"); + + let eth = format_ether(U256::from_str_radix("1005633240123456789", 10).unwrap()); + assert_eq!(eth, "1.005633240123456789"); + + let eth = format_ether(u16::MAX); + assert_eq!(eth, "0.000000000000065535"); + + // Note: This covers usize on 32 bit systems. + let eth = format_ether(u32::MAX); + assert_eq!(eth, "0.000000004294967295"); + + // Note: This covers usize on 64 bit systems. + let eth = format_ether(u64::MAX); + assert_eq!(eth, "18.446744073709551615"); + } + + #[test] + fn test_format_ether_signed() { + let eth = format_ether(I256::from_dec_str("-1395633240123456000").unwrap()); + assert_eq!(eth.parse::().unwrap(), -1.395633240123456); + + let eth = format_ether(I256::from_dec_str("-1395633240123456789").unwrap()); + assert_eq!(eth, "-1.395633240123456789"); + + let eth = format_ether(I256::from_dec_str("1005633240123456789").unwrap()); + assert_eq!(eth, "1.005633240123456789"); + + let eth = format_ether(i8::MIN); + assert_eq!(eth, "-0.000000000000000128"); + + let eth = format_ether(i8::MAX); + assert_eq!(eth, "0.000000000000000127"); + + let eth = format_ether(i16::MIN); + assert_eq!(eth, "-0.000000000000032768"); + + // Note: This covers isize on 32 bit systems. + let eth = format_ether(i32::MIN); + assert_eq!(eth, "-0.000000002147483648"); + + // Note: This covers isize on 64 bit systems. + let eth = format_ether(i64::MIN); + assert_eq!(eth, "-9.223372036854775808"); + } + + #[test] + fn test_format_units_unsigned() { + let gwei_in_ether = format_units(Unit::ETHER.wei(), 9).unwrap(); + assert_eq!(gwei_in_ether.parse::().unwrap() as u64, 1e9 as u64); + + let eth = format_units(Unit::ETHER.wei(), "ether").unwrap(); + assert_eq!(eth.parse::().unwrap() as u64, 1); + + let eth = format_units(1395633240123456000_u128, "ether").unwrap(); + assert_eq!(eth.parse::().unwrap(), 1.395633240123456); + + let eth = format_units(U256::from_str_radix("1395633240123456000", 10).unwrap(), "ether") + .unwrap(); + assert_eq!(eth.parse::().unwrap(), 1.395633240123456); + + let eth = format_units(U256::from_str_radix("1395633240123456789", 10).unwrap(), "ether") + .unwrap(); + assert_eq!(eth, "1.395633240123456789"); + + let eth = format_units(U256::from_str_radix("1005633240123456789", 10).unwrap(), "ether") + .unwrap(); + assert_eq!(eth, "1.005633240123456789"); + + let eth = format_units(u8::MAX, 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"); + + let eth = format_units(U256::MAX, 77).unwrap(); + assert_eq!( + eth, + "1.15792089237316195423570985008687907853269984665640564039457584007913129639935" + ); + + let _err = format_units(U256::MAX, 78).unwrap_err(); + let _err = format_units(U256::MAX, 79).unwrap_err(); + } + + #[test] + fn test_format_units_signed() { + let eth = + format_units(I256::from_dec_str("-1395633240123456000").unwrap(), "ether").unwrap(); + assert_eq!(eth.parse::().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::().unwrap(), -0.0128_f64); + + let eth = format_units(i8::MAX, 4).unwrap(); + assert_eq!(eth, "0.0127"); + assert_eq!(eth.parse::().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"); + + let eth = format_units(I256::MIN, 76).unwrap(); + let min = "-5.7896044618658097711785492504343953926634992332820282019728792003956564819968"; + assert_eq!(eth, min); + // doesn't error + let eth = format_units(I256::MIN, 77).unwrap(); + assert_eq!(eth, min); + + let _err = format_units(I256::MIN, 78).unwrap_err(); + let _err = format_units(I256::MIN, 79).unwrap_err(); + } + + #[test] + fn parse_large_units() { + let decimals = 27u8; + let val = "10.55"; + + let n: U256 = parse_units(val, decimals).unwrap().into(); + assert_eq!(n.to_string(), "10550000000000000000000000000"); + } + + #[test] + fn test_parse_units() { + let gwei: U256 = parse_units("1.5", 9).unwrap().into(); + assert_eq!(gwei, U256::from(15e8 as u64)); + + let token: U256 = parse_units("1163.56926418", 8).unwrap().into(); + assert_eq!(token, U256::from(116356926418u64)); + + let eth_dec_float: U256 = parse_units("1.39563324", "ether").unwrap().into(); + assert_eq!(eth_dec_float, U256::from_str_radix("1395633240000000000", 10).unwrap()); + + let eth_dec_string: U256 = parse_units("1.39563324", "ether").unwrap().into(); + assert_eq!(eth_dec_string, U256::from_str_radix("1395633240000000000", 10).unwrap()); + + let eth: U256 = parse_units("1", "ether").unwrap().into(); + assert_eq!(eth, Unit::ETHER.wei()); + + let val: U256 = parse_units("2.3", "ether").unwrap().into(); + assert_eq!(val, U256::from_str_radix("2300000000000000000", 10).unwrap()); + + 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_str_radix("987660000000000000000", 10).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"); + + let two_e30 = U256::from(2) * U256::from_limbs([0x4674edea40000000, 0xc9f2c9cd0, 0x0, 0x0]); + 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(Unit::ETHER.wei()) * 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::try_from(-20).unwrap(), "leading dot"); + + let n: I256 = parse_units("-333.21", 2).unwrap().into(); + assert_eq!(n, I256::try_from(-33321).unwrap(), "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::try_from(-330000).unwrap(), "underscore"); + + let n: I256 = parse_units("-330", 0).unwrap().into(); + assert_eq!(n, I256::try_from(-330).unwrap(), "zero decimals"); + + let n: I256 = parse_units("-.1234", 3).unwrap().into(); + assert_eq!(n, I256::try_from(-123).unwrap(), "truncate too many decimals"); + + assert!(parse_units("-1", 80).is_err(), "overflow"); + + let two_e30 = I256::try_from(-2).unwrap() + * I256::from_raw(U256::from_limbs([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"); + } +}