Skip to content

Commit

Permalink
feat: delegate Chain methods to NamedChain (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
DaniPopes authored Nov 14, 2023
1 parent a63bf79 commit ac705fd
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 31 deletions.
91 changes: 74 additions & 17 deletions src/chain.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand Down Expand Up @@ -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())
}
}

Expand Down Expand Up @@ -75,6 +75,7 @@ impl TryFrom<Chain> for NamedChain {
impl FromStr for Chain {
type Err = core::num::ParseIntError;

#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(chain) = NamedChain::from_str(s) {
Ok(Self::from_named(chain))
Expand All @@ -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),
Expand Down Expand Up @@ -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<Duration> {
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<String> {
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 <https://github.com/ethereum/discv4-dns-lists>
/// See [`NamedChain::public_dns_network_protocol`] for more info.
pub fn public_dns_network_protocol(self) -> Option<String> {
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
}
}

Expand Down Expand Up @@ -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());
}
}
74 changes: 60 additions & 14 deletions src/named.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use alloc::string::String;
use core::{fmt, time::Duration};
use num_enum::TryFromPrimitiveError;

Expand Down Expand Up @@ -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<NamedChain> 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<NamedChain>;

#[inline]
fn try_from(value: $native) -> Result<Self, Self::Error> {
(value as u64).try_into()
}
Expand All @@ -178,17 +196,11 @@ macro_rules! impl_try_from_numeric {
};
}

impl From<NamedChain> 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())
}
}

Expand Down Expand Up @@ -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
Expand All @@ -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<Duration> {
pub const fn average_blocktime_hint(self) -> Option<Duration> {
use NamedChain::*;

let ms = match self {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -350,7 +368,7 @@ impl NamedChain {
///
/// For more information, see EIP-3855:
/// `<https://eips.ethereum.org/EIPS/eip-3855>`
pub const fn supports_push0(&self) -> bool {
pub const fn supports_push0(self) -> bool {
match self {
NamedChain::Mainnet
| NamedChain::Goerli
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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<String> {
pub fn etherscan_api_key(self) -> Option<String> {
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 <https://github.com/ethereum/discv4-dns-lists>.
pub fn public_dns_network_protocol(self) -> Option<String> {
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)]
Expand Down Expand Up @@ -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);
}
}

0 comments on commit ac705fd

Please sign in to comment.