diff --git a/src/chain.rs b/src/chain.rs index 7f8b0ce..1d862b8 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -1,6 +1,6 @@ use crate::NamedChain; use alloc::string::String; -use core::{fmt, str::FromStr}; +use core::{fmt, str::FromStr, time::Duration}; #[cfg(feature = "arbitrary")] use proptest::{ @@ -35,7 +35,7 @@ impl fmt::Debug for Chain { impl Default for Chain { #[inline] fn default() -> Self { - Self::from_named(NamedChain::Mainnet) + Self::from_named(NamedChain::default()) } } @@ -75,6 +75,7 @@ impl TryFrom for NamedChain { impl FromStr for Chain { type Err = core::num::ParseIntError; + #[inline] fn from_str(s: &str) -> Result { if let Ok(chain) = NamedChain::from_str(s) { Ok(Self::from_named(chain)) @@ -85,6 +86,7 @@ impl FromStr for Chain { } impl fmt::Display for Chain { + #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.kind() { ChainKind::Named(chain) => chain.fmt(f), @@ -286,19 +288,81 @@ impl Chain { ChainKind::Id(id) => id, } } +} + +/// Methods delegated to `NamedChain`. Note that [`ChainKind::Id`] won't be converted because it was +/// already done at construction. +impl Chain { + /// Returns the chain's average blocktime, if applicable. + /// + /// See [`NamedChain::average_blocktime_hint`] for more info. + pub const fn average_blocktime_hint(self) -> Option { + match self.kind() { + ChainKind::Named(named) => named.average_blocktime_hint(), + ChainKind::Id(_) => None, + } + } + + /// Returns whether the chain implements EIP-1559 (with the type 2 EIP-2718 transaction type). + /// + /// See [`NamedChain::is_legacy`] for more info. + pub const fn is_legacy(self) -> bool { + match self.kind() { + ChainKind::Named(named) => named.is_legacy(), + ChainKind::Id(_) => false, + } + } + + /// Returns whether the chain supports the `PUSH0` opcode or not. + /// + /// See [`NamedChain::supports_push0`] for more info. + pub const fn supports_push0(self) -> bool { + match self.kind() { + ChainKind::Named(named) => named.supports_push0(), + ChainKind::Id(_) => false, + } + } + + /// Returns the chain's blockchain explorer and its API (Etherscan and Etherscan-like) URLs. + /// + /// See [`NamedChain::etherscan_urls`] for more info. + pub const fn etherscan_urls(self) -> Option<(&'static str, &'static str)> { + match self.kind() { + ChainKind::Named(named) => named.etherscan_urls(), + ChainKind::Id(_) => None, + } + } + + /// Returns the chain's blockchain explorer's API key environment variable's default name. + /// + /// See [`NamedChain::etherscan_api_key_name`] for more info. + pub const fn etherscan_api_key_name(self) -> Option<&'static str> { + match self.kind() { + ChainKind::Named(named) => named.etherscan_api_key_name(), + ChainKind::Id(_) => None, + } + } + + /// Returns the chain's blockchain explorer's API key, from the environment variable with the + /// name specified in [`etherscan_api_key_name`](NamedChain::etherscan_api_key_name). + /// + /// See [`NamedChain::etherscan_api_key`] for more info. + #[cfg(feature = "std")] + pub fn etherscan_api_key(self) -> Option { + match self.kind() { + ChainKind::Named(named) => named.etherscan_api_key(), + ChainKind::Id(_) => None, + } + } /// Returns the address of the public DNS node list for the given chain. /// - /// See also + /// See [`NamedChain::public_dns_network_protocol`] for more info. pub fn public_dns_network_protocol(self) -> Option { - use NamedChain as C; - const DNS_PREFIX: &str = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@"; - - let named: NamedChain = self.try_into().ok()?; - if matches!(named, C::Mainnet | C::Goerli | C::Sepolia | C::Ropsten | C::Rinkeby) { - return Some(format!("{DNS_PREFIX}all.{}.ethdisco.net", named.as_ref().to_lowercase())); + match self.kind() { + ChainKind::Named(named) => named.public_dns_network_protocol(), + ChainKind::Id(_) => None, } - None } } @@ -362,11 +426,4 @@ mod tests { let chain = Chain::from_id(1234); assert_eq!(chain.length(), 3); } - - #[test] - fn test_dns_network() { - let s = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@all.mainnet.ethdisco.net"; - let chain: Chain = NamedChain::Mainnet.into(); - assert_eq!(s, chain.public_dns_network_protocol().unwrap().as_str()); - } } diff --git a/src/named.rs b/src/named.rs index 6ff20f2..f5cda73 100644 --- a/src/named.rs +++ b/src/named.rs @@ -1,3 +1,4 @@ +use alloc::string::String; use core::{fmt, time::Duration}; use num_enum::TryFromPrimitiveError; @@ -159,17 +160,34 @@ pub enum NamedChain { // This must be implemented manually so we avoid a conflict with `TryFromPrimitive` where it treats // the `#[default]` attribute as its own `#[num_enum(default)]` impl Default for NamedChain { + #[inline] fn default() -> Self { Self::Mainnet } } +macro_rules! impl_into_numeric { + ($($t:ty)+) => {$( + impl From for $t { + #[inline] + fn from(chain: NamedChain) -> Self { + chain as $t + } + } + )+}; +} + +impl_into_numeric!(u64 i64 u128 i128); +#[cfg(target_pointer_width = "64")] +impl_into_numeric!(usize isize); + macro_rules! impl_try_from_numeric { ($($native:ty)+) => { $( impl TryFrom<$native> for NamedChain { type Error = TryFromPrimitiveError; + #[inline] fn try_from(value: $native) -> Result { (value as u64).try_into() } @@ -178,17 +196,11 @@ macro_rules! impl_try_from_numeric { }; } -impl From for u64 { - fn from(chain: NamedChain) -> Self { - chain as u64 - } -} - -impl_try_from_numeric!(u8 u16 u32 usize); +impl_try_from_numeric!(u8 i8 u16 i16 u32 i32 usize isize); impl fmt::Display for NamedChain { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.pad(self.as_ref()) + f.pad(self.as_str()) } } @@ -226,6 +238,12 @@ impl alloy_rlp::Decodable for NamedChain { #[allow(clippy::match_like_matches_macro)] #[deny(unreachable_patterns, unused_variables)] impl NamedChain { + /// Returns the string representation of the chain. + #[inline] + pub fn as_str(&self) -> &str { + self.as_ref() + } + /// Returns the chain's average blocktime, if applicable. /// /// It can be beneficial to know the average blocktime to adjust the polling of an HTTP provider @@ -244,7 +262,7 @@ impl NamedChain { /// assert_eq!(NamedChain::Mainnet.average_blocktime_hint(), Some(Duration::from_millis(12_000)),); /// assert_eq!(NamedChain::Optimism.average_blocktime_hint(), Some(Duration::from_millis(2_000)),); /// ``` - pub const fn average_blocktime_hint(&self) -> Option { + pub const fn average_blocktime_hint(self) -> Option { use NamedChain::*; let ms = match self { @@ -286,7 +304,7 @@ impl NamedChain { /// assert!(!NamedChain::Mainnet.is_legacy()); /// assert!(NamedChain::Celo.is_legacy()); /// ``` - pub const fn is_legacy(&self) -> bool { + pub const fn is_legacy(self) -> bool { use NamedChain::*; match self { @@ -350,7 +368,7 @@ impl NamedChain { /// /// For more information, see EIP-3855: /// `` - pub const fn supports_push0(&self) -> bool { + pub const fn supports_push0(self) -> bool { match self { NamedChain::Mainnet | NamedChain::Goerli @@ -380,7 +398,7 @@ impl NamedChain { /// ); /// assert_eq!(NamedChain::AnvilHardhat.etherscan_urls(), None); /// ``` - pub const fn etherscan_urls(&self) -> Option<(&'static str, &'static str)> { + pub const fn etherscan_urls(self) -> Option<(&'static str, &'static str)> { use NamedChain::*; let urls = match self { @@ -553,7 +571,7 @@ impl NamedChain { /// assert_eq!(NamedChain::Mainnet.etherscan_api_key_name(), Some("ETHERSCAN_API_KEY")); /// assert_eq!(NamedChain::AnvilHardhat.etherscan_api_key_name(), None); /// ``` - pub const fn etherscan_api_key_name(&self) -> Option<&'static str> { + pub const fn etherscan_api_key_name(self) -> Option<&'static str> { use NamedChain::*; let api_key_name = match self { @@ -640,9 +658,31 @@ impl NamedChain { /// assert_eq!(chain.etherscan_api_key().as_deref(), Some("KEY")); /// ``` #[cfg(feature = "std")] - pub fn etherscan_api_key(&self) -> Option { + pub fn etherscan_api_key(self) -> Option { self.etherscan_api_key_name().and_then(|name| std::env::var(name).ok()) } + + /// Returns the address of the public DNS node list for the given chain. + /// + /// See also . + pub fn public_dns_network_protocol(self) -> Option { + const DNS_PREFIX: &str = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@"; + if let Self::Mainnet | Self::Goerli | Self::Sepolia | Self::Ropsten | Self::Rinkeby = self { + // `{DNS_PREFIX}all.{self.lower()}.ethdisco.net` + let mut s = String::with_capacity(DNS_PREFIX.len() + 32); + s.push_str(DNS_PREFIX); + s.push_str("all."); + let chain_str = self.as_ref(); + s.push_str(chain_str); + let l = s.len(); + s[l - chain_str.len()..].make_ascii_lowercase(); + s.push_str(".ethdisco.net"); + + Some(s) + } else { + None + } + } } #[cfg(test)] @@ -730,4 +770,10 @@ mod tests { assert_eq!(chain_serde, chain_string); } } + + #[test] + fn test_dns_network() { + let s = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@all.mainnet.ethdisco.net"; + assert_eq!(NamedChain::Mainnet.public_dns_network_protocol().unwrap(), s); + } }