use crate::NamedChain;
use strum::IntoEnumIterator;
#[allow(unused_imports)]
use alloc::{
collections::BTreeMap,
string::{String, ToString},
};
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct Chains {
pub chains: BTreeMap<u64, Chain>,
}
impl Default for Chains {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl Chains {
#[inline]
pub fn empty() -> Self {
Self { chains: Default::default() }
}
pub fn new() -> Self {
Self { chains: NamedChain::iter().map(|c| (c as u64, Chain::new(c))).collect() }
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct Chain {
pub internal_id: String,
pub name: String,
pub average_blocktime_hint: Option<u64>,
pub is_legacy: bool,
pub supports_shanghai: bool,
pub is_testnet: bool,
pub native_currency_symbol: Option<String>,
pub etherscan_api_url: Option<String>,
pub etherscan_base_url: Option<String>,
pub etherscan_api_key_name: Option<String>,
}
impl Chain {
pub fn new(c: NamedChain) -> Self {
let (etherscan_api_url, etherscan_base_url) = match c.etherscan_urls() {
Some((a, b)) => (Some(a), Some(b)),
None => (None, None),
};
Self {
internal_id: format!("{c:?}"),
name: c.to_string(),
average_blocktime_hint: c
.average_blocktime_hint()
.map(|d| d.as_millis().try_into().unwrap_or(u64::MAX)),
is_legacy: c.is_legacy(),
supports_shanghai: c.supports_shanghai(),
is_testnet: c.is_testnet(),
native_currency_symbol: c.native_currency_symbol().map(Into::into),
etherscan_api_url: etherscan_api_url.map(Into::into),
etherscan_base_url: etherscan_base_url.map(Into::into),
etherscan_api_key_name: c.etherscan_api_key_name().map(Into::into),
}
}
}
#[cfg(all(test, feature = "std", feature = "serde", feature = "schema"))]
mod tests {
use super::*;
use std::{fs, path::Path};
const JSON_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/chains.json");
const SCHEMA_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/chains.schema.json");
fn json_chains() -> String {
serde_json::to_string_pretty(&Chains::new()).unwrap()
}
fn json_schema() -> String {
serde_json::to_string_pretty(&schemars::schema_for!(Chains)).unwrap()
}
#[test]
#[cfg_attr(miri, ignore = "no fs")]
fn spec_up_to_date() {
ensure_file_contents(Path::new(JSON_PATH), &json_chains());
}
#[test]
#[cfg_attr(miri, ignore = "no fs")]
fn schema_up_to_date() {
ensure_file_contents(Path::new(SCHEMA_PATH), &json_schema());
}
fn ensure_file_contents(file: &Path, contents: &str) {
if let Ok(old_contents) = fs::read_to_string(file) {
if normalize_newlines(&old_contents) == normalize_newlines(contents) {
return;
}
}
eprintln!("\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n", file.display());
if std::env::var("CI").is_ok() {
eprintln!(
" NOTE: run `cargo test --all-features` locally and commit the updated files\n"
);
}
if let Some(parent) = file.parent() {
let _ = fs::create_dir_all(parent);
}
fs::write(file, contents).unwrap();
panic!("some file was not up to date and has been updated, simply re-run the tests");
}
fn normalize_newlines(s: &str) -> String {
s.replace("\r\n", "\n")
}
}