1use alloc::string::String;
2use core::{cmp::Ordering, fmt, str::FromStr, time::Duration};
3
4pub use crate::generated::named::NamedChain;
5
6impl NamedChain {
7 #[inline]
9 pub fn iter() -> NamedChainIter {
10 NamedChainIter { inner: Self::VARIANTS.iter().copied() }
11 }
12
13 #[inline]
15 pub const fn average_blocktime_hint(self) -> Option<Duration> {
16 let millis = self.average_blocktime_millis();
17 if millis == 0 { None } else { Some(Duration::from_millis(millis as u64)) }
18 }
19
20 #[cfg(feature = "std")]
22 pub fn etherscan_api_key(self) -> Option<String> {
23 self.etherscan_api_key_name().and_then(|name| std::env::var(name).ok())
24 }
25
26 pub fn public_dns_network_protocol(self) -> Option<String> {
28 const DNS_PREFIX: &str = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@";
29 if matches!(
30 self,
31 Self::Mainnet
32 | Self::Goerli
33 | Self::Sepolia
34 | Self::Ropsten
35 | Self::Rinkeby
36 | Self::Holesky
37 | Self::Hoodi
38 ) {
39 let mut s = String::with_capacity(DNS_PREFIX.len() + 32);
40 s.push_str(DNS_PREFIX);
41 s.push_str("all.");
42 let chain_str = self.as_ref();
43 s.push_str(chain_str);
44 let l = s.len();
45 s[l - chain_str.len()..].make_ascii_lowercase();
46 s.push_str(".ethdisco.net");
47 Some(s)
48 } else {
49 None
50 }
51 }
52}
53
54#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
56pub struct ParseNamedChainError;
57
58impl fmt::Display for ParseNamedChainError {
59 #[inline]
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 f.write_str("matching variant not found")
62 }
63}
64
65#[cfg(feature = "std")]
66impl std::error::Error for ParseNamedChainError {}
67
68#[derive(Clone, Debug)]
70pub struct NamedChainIter {
71 inner: core::iter::Copied<core::slice::Iter<'static, NamedChain>>,
72}
73
74impl Default for NamedChainIter {
75 #[inline]
76 fn default() -> Self {
77 NamedChain::iter()
78 }
79}
80
81impl Iterator for NamedChainIter {
82 type Item = NamedChain;
83
84 #[inline]
85 fn next(&mut self) -> Option<Self::Item> {
86 self.inner.next()
87 }
88
89 #[inline]
90 fn size_hint(&self) -> (usize, Option<usize>) {
91 self.inner.size_hint()
92 }
93}
94
95impl DoubleEndedIterator for NamedChainIter {
96 #[inline]
97 fn next_back(&mut self) -> Option<Self::Item> {
98 self.inner.next_back()
99 }
100}
101
102impl ExactSizeIterator for NamedChainIter {}
103impl core::iter::FusedIterator for NamedChainIter {}
104
105impl From<NamedChain> for &'static str {
106 #[inline]
107 fn from(chain: NamedChain) -> Self {
108 (&chain).into()
109 }
110}
111
112impl From<&NamedChain> for &'static str {
113 #[inline]
114 fn from(chain: &NamedChain) -> Self {
115 chain.as_str()
116 }
117}
118
119impl Default for NamedChain {
120 #[inline]
121 fn default() -> Self {
122 Self::Mainnet
123 }
124}
125
126macro_rules! impl_into_numeric {
127 ($($t:ty)+) => {$(
128 impl From<NamedChain> for $t {
129 #[inline]
130 fn from(chain: NamedChain) -> Self {
131 chain as $t
132 }
133 }
134 )+};
135}
136
137impl_into_numeric!(u64 i64 u128 i128);
138#[cfg(target_pointer_width = "64")]
139impl_into_numeric!(usize isize);
140
141impl num_enum::TryFromPrimitive for NamedChain {
142 type Primitive = u64;
143 type Error = num_enum::TryFromPrimitiveError<Self>;
144
145 const NAME: &'static str = "NamedChain";
146
147 fn try_from_primitive(number: Self::Primitive) -> Result<Self, Self::Error> {
148 Self::from_chain_id(number).ok_or_else(|| num_enum::TryFromPrimitiveError::new(number))
149 }
150}
151
152impl TryFrom<u64> for NamedChain {
153 type Error = num_enum::TryFromPrimitiveError<Self>;
154
155 #[inline]
156 fn try_from(value: u64) -> Result<Self, Self::Error> {
157 num_enum::TryFromPrimitive::try_from_primitive(value)
158 }
159}
160
161macro_rules! impl_try_from_numeric {
162 ($($native:ty)+) => {
163 $(
164 impl TryFrom<$native> for NamedChain {
165 type Error = num_enum::TryFromPrimitiveError<NamedChain>;
166
167 #[inline]
168 fn try_from(value: $native) -> Result<Self, Self::Error> {
169 (value as u64).try_into()
170 }
171 }
172 )+
173 };
174}
175
176impl_try_from_numeric!(u8 i8 u16 i16 u32 i32 usize isize);
177
178impl fmt::Display for NamedChain {
179 #[inline]
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 self.as_str().fmt(f)
182 }
183}
184
185impl AsRef<str> for NamedChain {
186 #[inline]
187 fn as_ref(&self) -> &str {
188 self.as_str()
189 }
190}
191
192impl FromStr for NamedChain {
193 type Err = ParseNamedChainError;
194
195 fn from_str(s: &str) -> Result<Self, Self::Err> {
196 NamedChain::from_parse_str(s).ok_or(ParseNamedChainError)
197 }
198}
199
200impl TryFrom<&str> for NamedChain {
201 type Error = ParseNamedChainError;
202
203 #[inline]
204 fn try_from(value: &str) -> Result<Self, Self::Error> {
205 value.parse()
206 }
207}
208
209impl Ord for NamedChain {
210 #[inline]
211 fn cmp(&self, other: &Self) -> Ordering {
212 (*self as u64).cmp(&(*other as u64))
213 }
214}
215
216impl PartialOrd for NamedChain {
217 #[inline]
218 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
219 Some(self.cmp(other))
220 }
221}
222
223impl PartialEq<u64> for NamedChain {
224 #[inline]
225 fn eq(&self, other: &u64) -> bool {
226 (*self as u64) == *other
227 }
228}
229
230impl PartialOrd<u64> for NamedChain {
231 #[inline]
232 fn partial_cmp(&self, other: &u64) -> Option<Ordering> {
233 (*self as u64).partial_cmp(other)
234 }
235}
236
237#[cfg(feature = "serde")]
238impl serde::Serialize for NamedChain {
239 #[inline]
240 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
241 s.serialize_str(self.as_ref())
242 }
243}
244
245#[cfg(feature = "serde")]
246impl<'de> serde::Deserialize<'de> for NamedChain {
247 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
248 struct NamedChainVisitor;
249
250 impl serde::de::Visitor<'_> for NamedChainVisitor {
251 type Value = NamedChain;
252
253 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
254 formatter.write_str("a named chain")
255 }
256
257 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
258 NamedChain::from_serde_str(value).ok_or_else(|| {
259 serde::de::Error::unknown_variant(value, NamedChain::VARIANT_NAMES)
260 })
261 }
262 }
263
264 deserializer.deserialize_str(NamedChainVisitor)
265 }
266}
267
268#[cfg(feature = "rlp")]
269impl alloy_rlp::Encodable for NamedChain {
270 #[inline]
271 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
272 (*self as u64).encode(out)
273 }
274
275 #[inline]
276 fn length(&self) -> usize {
277 (*self as u64).length()
278 }
279}
280
281#[cfg(feature = "rlp")]
282impl alloy_rlp::Decodable for NamedChain {
283 #[inline]
284 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
285 let n = u64::decode(buf)?;
286 Self::try_from(n).map_err(|_| alloy_rlp::Error::Overflow)
287 }
288}
289
290#[cfg(feature = "arbitrary")]
291impl<'a> arbitrary::Arbitrary<'a> for NamedChain {
292 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
293 let idx = u.choose_index(Self::COUNT)?;
294 Ok(Self::VARIANTS[idx])
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301 use crate::generated::named::PARSE_ALIASES;
302 #[cfg(feature = "serde")]
303 use crate::generated::named::SERDE_ALIASES;
304
305 #[allow(unused_imports)]
306 use alloc::string::ToString;
307
308 #[test]
309 #[cfg(feature = "serde")]
310 fn default() {
311 assert_eq!(serde_json::to_string(&NamedChain::default()).unwrap(), "\"mainnet\"");
312 }
313
314 #[test]
315 fn enum_iter() {
316 assert_eq!(NamedChain::COUNT, NamedChain::iter().size_hint().0);
317 assert_eq!(NamedChain::COUNT, NamedChain::VARIANTS.len());
318 assert_eq!(NamedChain::COUNT, NamedChain::VARIANT_NAMES.len());
319 }
320
321 #[test]
322 fn roundtrip_string() {
323 for chain in NamedChain::iter() {
324 let chain_string = chain.to_string();
325 assert_eq!(chain_string, format!("{chain}"));
326 assert_eq!(chain_string.as_str(), chain.as_ref());
327 #[cfg(feature = "serde")]
328 assert_eq!(serde_json::to_string(&chain).unwrap(), format!("\"{chain_string}\""));
329
330 assert_eq!(chain_string.parse::<NamedChain>().unwrap(), chain);
331 }
332 }
333
334 #[test]
335 #[cfg(feature = "serde")]
336 fn roundtrip_serde() {
337 for chain in NamedChain::iter() {
338 let chain_string = serde_json::to_string(&chain).unwrap();
339 let chain_string = chain_string.replace('-', "_");
340 assert_eq!(serde_json::from_str::<'_, NamedChain>(&chain_string).unwrap(), chain);
341 }
342 }
343
344 #[test]
345 #[cfg(feature = "arbitrary")]
346 fn test_arbitrary_named_chain() {
347 use arbitrary::{Arbitrary, Unstructured};
348 let data = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 255];
349 let mut unstructured = Unstructured::new(&data);
350
351 for _ in 0..10 {
352 let _chain = NamedChain::arbitrary(&mut unstructured).unwrap();
353 }
354 }
355
356 #[test]
357 fn aliases() {
358 for &(chain, alias) in PARSE_ALIASES {
359 assert_eq!(alias.parse::<NamedChain>().unwrap(), chain);
360
361 #[cfg(feature = "serde")]
362 assert_eq!(serde_json::from_str::<NamedChain>(&format!("\"{alias}\"")).unwrap(), chain);
363 }
364
365 #[cfg(feature = "serde")]
366 for &(chain, alias) in SERDE_ALIASES {
367 assert_eq!(serde_json::from_str::<NamedChain>(&format!("\"{alias}\"")).unwrap(), chain);
368 }
369 }
370
371 #[test]
372 #[cfg(feature = "serde")]
373 fn serde_to_string_match() {
374 for chain in NamedChain::iter() {
375 let chain_serde = serde_json::to_string(&chain).unwrap();
376 let chain_string = format!("\"{chain}\"");
377 assert_eq!(chain_serde, chain_string);
378 }
379 }
380
381 #[test]
382 fn test_dns_network() {
383 let s = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@all.mainnet.ethdisco.net";
384 assert_eq!(NamedChain::Mainnet.public_dns_network_protocol().unwrap(), s);
385 }
386
387 #[test]
388 fn ensure_no_trailing_etherscan_url_separator() {
389 for chain in NamedChain::iter() {
390 if let Some((api, base)) = chain.etherscan_urls() {
391 assert!(!api.ends_with('/'), "{chain:?} api url has trailing /");
392 assert!(!base.ends_with('/'), "{chain:?} base url has trailing /");
393 }
394 }
395 }
396}