[go: up one dir, main page]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: delegate Chain methods to NamedChain #6

Merged
merged 1 commit into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}