diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b9fb0f2..d31e8407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ ### Unreleased -- Add `other: OtherFields` to `TransactionReceipt` [#2209[](https://github.com/gakonst/ethers-rs/pull/2209) +- Make `Chain` more round-trip friendly [#2270](https://github.com/gakonst/ethers-rs/pull/2270) +- Add `other: OtherFields` to `TransactionReceipt` [#2209](https://github.com/gakonst/ethers-rs/pull/2209) - Add `Signature::recover_typed_data` [#2120](https://github.com/gakonst/ethers-rs/pull/2120) - Add `abi::encode_packed` [#2104](https://github.com/gakonst/ethers-rs/pull/2104) - Add support for custom JavaScript tracer to `debug_traceCall` and `debug_traceTransaction` [#2064](https://github.com/gakonst/ethers-rs/pull/2064) diff --git a/ethers-core/src/types/chain.rs b/ethers-core/src/types/chain.rs index f5264a21..979b4c98 100644 --- a/ethers-core/src/types/chain.rs +++ b/ethers-core/src/types/chain.rs @@ -16,9 +16,15 @@ pub type ParseChainError = TryFromPrimitiveError; // When adding a new chain: // 1. add new variant to the Chain enum; // 2. add extra information in the last `impl` block (explorer URLs, block time) when applicable; -// 3. (optional) add aliases: `#[strum(serialize = "main", serialize = "alias", ...)]`; -// "main" must be present and will be used in `Display`, `Serialize` and `FromStr`, -// while the aliases will be added only to `FromStr`. +// 3. (optional) add aliases: +// - Strum (in kebab-case): `#[strum(to_string = "
", serialize = "", ...)]` +// `to_string = "
"` must be present and will be used in `Display`, `Serialize` +// and `FromStr`, while `serialize = ""` will be appended to `FromStr`. +// More info: +// - Serde (in snake_case): `#[serde(alias = "", ...)]` +// Aliases are appended to the `Deserialize` implementation. +// More info: +// - Add a test at the bottom of the file // We don't derive Serialize because it is manually implemented using AsRef and it would // break a lot of things since Serialize is `kebab-case` vs Deserialize `snake_case`. @@ -69,19 +75,23 @@ pub enum Chain { Rsk = 30, - #[strum(serialize = "bsc")] + #[strum(to_string = "bsc", serialize = "binance-smart-chain")] + #[serde(alias = "bsc")] BinanceSmartChain = 56, - #[strum(serialize = "bsc-testnet")] + #[strum(to_string = "bsc-testnet", serialize = "binance-smart-chain-testnet")] + #[serde(alias = "bsc_testnet")] BinanceSmartChainTestnet = 97, Poa = 99, Sokol = 77, - #[strum(serialize = "gnosis", serialize = "xdai", serialize = "gnosis-chain")] + #[strum(to_string = "xdai", serialize = "gnosis", serialize = "gnosis-chain")] + #[serde(alias = "xdai", alias = "gnosis", alias = "gnosis_chain")] XDai = 100, Polygon = 137, - #[strum(serialize = "mumbai", serialize = "polygon-mumbai")] + #[strum(to_string = "mumbai", serialize = "polygon-mumbai")] + #[serde(alias = "mumbai")] PolygonMumbai = 80001, Fantom = 250, @@ -94,9 +104,9 @@ pub enum Chain { Moonbase = 1287, - #[strum(serialize = "dev")] Dev = 1337, - #[strum(serialize = "anvil-hardhat", serialize = "anvil", serialize = "hardhat")] + #[strum(to_string = "anvil-hardhat", serialize = "anvil", serialize = "hardhat")] + #[serde(alias = "anvil", alias = "hardhat")] AnvilHardhat = 31337, Evmos = 9001, @@ -113,7 +123,8 @@ pub enum Chain { FilecoinHyperspaceTestnet = 3141, Avalanche = 43114, - #[strum(serialize = "fuji", serialize = "avalanche-fuji")] + #[strum(to_string = "fuji", serialize = "avalanche-fuji")] + #[serde(alias = "fuji")] AvalancheFuji = 43113, Celo = 42220, @@ -213,6 +224,7 @@ impl Serialize for Chain { // NB: all utility functions *should* be explicitly exhaustive (not use `_` matcher) so we don't // forget to update them when adding a new `Chain` variant. +#[allow(clippy::match_like_matches_macro)] impl Chain { /// Returns the chain's average blocktime, if applicable. /// @@ -274,7 +286,6 @@ impl Chain { /// assert!(!Chain::Mainnet.is_legacy()); /// assert!(Chain::Celo.is_legacy()); /// ``` - #[allow(clippy::match_like_matches_macro)] pub const fn is_legacy(&self) -> bool { use Chain::*; @@ -550,12 +561,56 @@ mod tests { use strum::IntoEnumIterator; #[test] - fn test_default_chain() { + fn default() { assert_eq!(serde_json::to_string(&Chain::default()).unwrap(), "\"mainnet\""); } #[test] - fn test_enum_iter() { + fn enum_iter() { assert_eq!(Chain::COUNT, Chain::iter().size_hint().0); } + + #[test] + fn roundtrip_string() { + for chain in Chain::iter() { + let chain_string = chain.to_string(); + assert_eq!(chain_string, format!("{chain}")); + assert_eq!(chain_string.as_str(), chain.as_ref()); + assert_eq!(serde_json::to_string(&chain).unwrap(), format!("\"{chain_string}\"")); + + assert_eq!(chain_string.parse::().unwrap(), chain); + } + } + + #[test] + fn roundtrip_serde() { + for chain in Chain::iter() { + let chain_string = serde_json::to_string(&chain).unwrap(); + let chain_string = chain_string.replace('-', "_"); + assert_eq!(serde_json::from_str::<'_, Chain>(&chain_string).unwrap(), chain); + } + } + + #[test] + fn aliases() { + use Chain::*; + + // kebab-case + const ALIASES: &[(Chain, &[&str])] = &[ + (BinanceSmartChain, &["bsc", "binance-smart-chain"]), + (BinanceSmartChainTestnet, &["bsc-testnet", "binance-smart-chain-testnet"]), + (XDai, &["xdai", "gnosis", "gnosis-chain"]), + (PolygonMumbai, &["mumbai"]), + (AnvilHardhat, &["anvil", "hardhat"]), + (AvalancheFuji, &["fuji"]), + ]; + + for &(chain, aliases) in ALIASES { + for &alias in aliases { + assert_eq!(alias.parse::().unwrap(), chain); + let s = alias.to_string().replace('-', "_"); + assert_eq!(serde_json::from_str::(&format!("\"{s}\"")).unwrap(), chain); + } + } + } }