From 0ec5472c0d6fe4886d52c6b0093f04eca08a1126 Mon Sep 17 00:00:00 2001
From: DaniPopes <57450786+DaniPopes@users.noreply.github.com>
Date: Tue, 14 Nov 2023 14:06:15 +0100
Subject: [PATCH] feat: port `ethers_core::types::Chain`
---
.github/workflows/ci.yml | 121 +++++++
.github/workflows/deps.yml | 18 +
.gitignore | 3 +
Cargo.toml | 21 +-
LICENSE-APACHE | 176 ++++++++++
LICENSE-MIT | 23 ++
README.md | 42 +++
clippy.toml | 1 +
deny.toml | 54 +++
rustfmt.toml | 13 +
src/chain.rs | 704 +++++++++++++++++++++++++++++++++++++
src/lib.rs | 34 +-
12 files changed, 1195 insertions(+), 15 deletions(-)
create mode 100644 .github/workflows/ci.yml
create mode 100644 .github/workflows/deps.yml
create mode 100644 LICENSE-APACHE
create mode 100644 LICENSE-MIT
create mode 100644 README.md
create mode 100644 clippy.toml
create mode 100644 deny.toml
create mode 100644 rustfmt.toml
create mode 100644 src/chain.rs
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..00a3de7
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,121 @@
+name: CI
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ test:
+ name: test ${{ matrix.rust }} ${{ matrix.flags }}
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ strategy:
+ fail-fast: false
+ matrix:
+ rust: ["stable", "beta", "nightly", "1.65"] # MSRV
+ flags: ["--no-default-features", "", "--all-features"]
+ exclude:
+ # Skip because some features have highest MSRV.
+ - rust: "1.65" # MSRV
+ flags: "--all-features"
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@master
+ with:
+ toolchain: ${{ matrix.rust }}
+ - uses: Swatinem/rust-cache@v2
+ with:
+ cache-on-failure: true
+ # Only run tests on latest stable and above
+ - name: build
+ if: ${{ matrix.rust == '1.65' }} # MSRV
+ run: cargo build --workspace ${{ matrix.flags }}
+ - name: test
+ if: ${{ matrix.rust != '1.65' }} # MSRV
+ run: cargo test --workspace ${{ matrix.flags }}
+
+ miri:
+ name: miri ${{ matrix.flags }}
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ strategy:
+ fail-fast: false
+ matrix:
+ flags: ["--no-default-features", "", "--all-features"]
+ env:
+ MIRIFLAGS: -Zmiri-strict-provenance
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@miri
+ - uses: Swatinem/rust-cache@v2
+ with:
+ cache-on-failure: true
+ - run: cargo miri setup ${{ matrix.flags }}
+ - run: cargo miri test ${{ matrix.flags }}
+
+ wasm:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@stable
+ with:
+ targets: wasm32-unknown-unknown
+ - uses: Swatinem/rust-cache@v2
+ with:
+ cache-on-failure: true
+ - name: check
+ run: cargo check --workspace --target wasm32-unknown-unknown
+
+ feature-checks:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@stable
+ - uses: taiki-e/install-action@cargo-hack
+ - uses: Swatinem/rust-cache@v2
+ with:
+ cache-on-failure: true
+ - name: cargo hack
+ run: cargo hack check --feature-powerset --depth 2
+
+ clippy:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@clippy
+ - uses: Swatinem/rust-cache@v2
+ with:
+ cache-on-failure: true
+ - run: cargo clippy --workspace --all-targets --all-features
+ env:
+ RUSTFLAGS: -Dwarnings
+
+ docs:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@nightly
+ - uses: Swatinem/rust-cache@v2
+ with:
+ cache-on-failure: true
+ - run: cargo doc --workspace --all-features --no-deps --document-private-items
+ env:
+ RUSTDOCFLAGS: "--cfg docsrs -D warnings"
+
+ fmt:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - uses: actions/checkout@v3
+ - uses: dtolnay/rust-toolchain@nightly
+ with:
+ components: rustfmt
+ - run: cargo fmt --all --check
diff --git a/.github/workflows/deps.yml b/.github/workflows/deps.yml
new file mode 100644
index 0000000..0b6448d
--- /dev/null
+++ b/.github/workflows/deps.yml
@@ -0,0 +1,18 @@
+name: deps
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+ schedule: [cron: "00 00 * * *"]
+
+jobs:
+ cargo-deny:
+ name: cargo deny check
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: EmbarkStudios/cargo-deny-action@v1
+ with:
+ command: check all
diff --git a/.gitignore b/.gitignore
index ea8c4bf..975b01a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,4 @@
/target
+/Cargo.lock
+.vscode
+.idea
diff --git a/Cargo.toml b/Cargo.toml
index bf66244..06dc5c4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,8 +1,23 @@
[package]
-name = "chains"
+name = "alloy-chains"
+description = "Canonical type definitions for EIP-155 chains"
version = "0.1.0"
edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+rust-version = "1.65"
+keywords = ["ethers", "primitives", "ethereum", "revm", "reth"]
+categories = ["no-std", "data-structures", "cryptography::cryptocurrencies"]
+homepage = "https://github.com/alloy-rs/chains"
+repository = "https://github.com/alloy-rs/chains"
[dependencies]
+num_enum = { version = "0.7", default-features = false }
+serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
+strum = { version = "0.25", default-features = false, features = ["derive"] }
+
+[dev-dependencies]
+serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
+
+[features]
+default = ["std"]
+std = ["strum/std", "serde?/std"]
+serde = ["dep:serde"]
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
new file mode 100644
index 0000000..1b5ec8b
--- /dev/null
+++ b/LICENSE-APACHE
@@ -0,0 +1,176 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
diff --git a/LICENSE-MIT b/LICENSE-MIT
new file mode 100644
index 0000000..31aa793
--- /dev/null
+++ b/LICENSE-MIT
@@ -0,0 +1,23 @@
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..532322f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,42 @@
+# alloy-chains
+
+Canonical type definitions for EIP-155 chains.
+
+## Supported Rust Versions
+
+
+
+Alloy will keep a rolling MSRV (minimum supported rust version) policy of **at
+least** 6 months. When increasing the MSRV, the new Rust version must have been
+released at least six months ago. The current MSRV is 1.65.0.
+
+Note that the MSRV is not increased automatically, and only as part of a minor
+release.
+
+## Note on `no_std`
+
+All crates in this workspace should support `no_std` environments, with the
+`alloc` crate. If you find a crate that does not support `no_std`, please
+[open an issue].
+
+[open an issue]: https://github.com/alloy-rs/chains/issues/new/choose
+
+#### License
+
+
+Licensed under either of Apache License, Version
+2.0 or MIT license at your option.
+
+
+
+
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in these crates by you, as defined in the Apache-2.0 license,
+shall be dual licensed as above, without any additional terms or conditions.
+
diff --git a/clippy.toml b/clippy.toml
new file mode 100644
index 0000000..0437112
--- /dev/null
+++ b/clippy.toml
@@ -0,0 +1 @@
+msrv = "1.65"
diff --git a/deny.toml b/deny.toml
new file mode 100644
index 0000000..8d7bf3e
--- /dev/null
+++ b/deny.toml
@@ -0,0 +1,54 @@
+[advisories]
+vulnerability = "deny"
+unmaintained = "warn"
+unsound = "warn"
+yanked = "warn"
+notice = "warn"
+
+[bans]
+multiple-versions = "warn"
+wildcards = "deny"
+highlight = "all"
+
+[licenses]
+unlicensed = "deny"
+confidence-threshold = 0.9
+# copyleft = "deny"
+
+allow = [
+ "MIT",
+ "MIT-0",
+ "Apache-2.0",
+ "Apache-2.0 WITH LLVM-exception",
+ "BSD-2-Clause",
+ "BSD-3-Clause",
+ "ISC",
+ "Unicode-DFS-2016",
+ "Unlicense",
+ "MPL-2.0",
+ # https://github.com/briansmith/ring/issues/902
+ "LicenseRef-ring",
+ # https://github.com/briansmith/webpki/issues/148
+ "LicenseRef-webpki",
+]
+
+exceptions = [
+ # CC0 is a permissive license but somewhat unclear status for source code
+ # so we prefer to not have dependencies using it
+ # https://tldrlegal.com/license/creative-commons-cc0-1.0-universal
+ { allow = ["CC0-1.0"], name = "tiny-keccak" },
+]
+
+[[licenses.clarify]]
+name = "ring"
+expression = "LicenseRef-ring"
+license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }]
+
+[[licenses.clarify]]
+name = "webpki"
+expression = "LicenseRef-webpki"
+license-files = [{ path = "LICENSE", hash = 0x001c7e6c }]
+
+[sources]
+unknown-registry = "deny"
+unknown-git = "deny"
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 0000000..20d1810
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1,13 @@
+reorder_imports = true
+use_field_init_shorthand = true
+use_small_heuristics = "Max"
+merge_derives = false
+
+# Nightly
+max_width = 100
+comment_width = 100
+imports_granularity = "Crate"
+wrap_comments = true
+format_code_in_doc_comments = true
+doc_comment_code_block_width = 100
+format_macro_matchers = true
diff --git a/src/chain.rs b/src/chain.rs
new file mode 100644
index 0000000..545f38e
--- /dev/null
+++ b/src/chain.rs
@@ -0,0 +1,704 @@
+use core::{fmt, time::Duration};
+use num_enum::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 (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): `#[cfg_attr(feature = "serde", 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`.
+// This means that the Chain type is not "round-trippable", because the Serialize and Deserialize
+// implementations do not use the same case style.
+
+/// An Ethereum EIP-155 chain.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[derive(strum::AsRefStr)] // AsRef, fmt::Display and serde::Serialize
+#[derive(strum::EnumVariantNames)] // Chain::VARIANTS
+#[derive(strum::EnumString)] // FromStr, TryFrom<&str>
+#[derive(strum::EnumIter)] // Chain::iter
+#[derive(strum::EnumCount)] // Chain::COUNT
+#[derive(num_enum::TryFromPrimitive)] // TryFrom
+#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
+#[strum(serialize_all = "kebab-case")]
+#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
+#[repr(u64)]
+#[allow(missing_docs)]
+pub enum Chain {
+ #[strum(to_string = "mainnet", serialize = "ethlive")]
+ #[cfg_attr(feature = "serde", serde(alias = "ethlive"))]
+ Mainnet = 1,
+ Morden = 2,
+ Ropsten = 3,
+ Rinkeby = 4,
+ Goerli = 5,
+ Kovan = 42,
+ Holesky = 17000,
+ Sepolia = 11155111,
+
+ Optimism = 10,
+ OptimismKovan = 69,
+ OptimismGoerli = 420,
+ OptimismSepolia = 11155420,
+
+ Arbitrum = 42161,
+ ArbitrumTestnet = 421611,
+ ArbitrumGoerli = 421613,
+ ArbitrumSepolia = 421614,
+ ArbitrumNova = 42170,
+
+ Cronos = 25,
+ CronosTestnet = 338,
+
+ Rsk = 30,
+
+ #[strum(to_string = "bsc", serialize = "binance-smart-chain")]
+ #[cfg_attr(feature = "serde", serde(alias = "bsc"))]
+ BinanceSmartChain = 56,
+ #[strum(to_string = "bsc-testnet", serialize = "binance-smart-chain-testnet")]
+ #[cfg_attr(feature = "serde", serde(alias = "bsc_testnet"))]
+ BinanceSmartChainTestnet = 97,
+
+ Poa = 99,
+ Sokol = 77,
+
+ Scroll = 534352,
+ ScrollAlphaTestnet = 534353,
+
+ Metis = 1088,
+
+ #[strum(to_string = "xdai", serialize = "gnosis", serialize = "gnosis-chain")]
+ #[cfg_attr(feature = "serde", serde(alias = "xdai", alias = "gnosis", alias = "gnosis_chain"))]
+ Gnosis = 100,
+
+ Polygon = 137,
+ #[strum(to_string = "mumbai", serialize = "polygon-mumbai")]
+ #[cfg_attr(feature = "serde", serde(alias = "mumbai"))]
+ PolygonMumbai = 80001,
+ #[strum(serialize = "polygon-zkevm", serialize = "zkevm")]
+ #[cfg_attr(feature = "serde", serde(alias = "zkevm", alias = "polygon_zkevm"))]
+ PolygonZkEvm = 1101,
+ #[strum(serialize = "polygon-zkevm-testnet", serialize = "zkevm-testnet")]
+ #[cfg_attr(feature = "serde", serde(alias = "zkevm_testnet", alias = "polygon_zkevm_testnet"))]
+ PolygonZkEvmTestnet = 1442,
+
+ Fantom = 250,
+ FantomTestnet = 4002,
+
+ Moonbeam = 1284,
+ MoonbeamDev = 1281,
+
+ Moonriver = 1285,
+
+ Moonbase = 1287,
+
+ Dev = 1337,
+ #[strum(to_string = "anvil-hardhat", serialize = "anvil", serialize = "hardhat")]
+ #[cfg_attr(feature = "serde", serde(alias = "anvil", alias = "hardhat"))]
+ AnvilHardhat = 31337,
+
+ Evmos = 9001,
+ EvmosTestnet = 9000,
+
+ Chiado = 10200,
+
+ Oasis = 26863,
+
+ Emerald = 42262,
+ EmeraldTestnet = 42261,
+
+ FilecoinMainnet = 314,
+ FilecoinCalibrationTestnet = 314159,
+
+ Avalanche = 43114,
+ #[strum(to_string = "fuji", serialize = "avalanche-fuji")]
+ #[cfg_attr(feature = "serde", serde(alias = "fuji"))]
+ AvalancheFuji = 43113,
+
+ Celo = 42220,
+ CeloAlfajores = 44787,
+ CeloBaklava = 62320,
+
+ Aurora = 1313161554,
+ AuroraTestnet = 1313161555,
+
+ Canto = 7700,
+ CantoTestnet = 740,
+
+ Boba = 288,
+
+ Base = 8453,
+ BaseGoerli = 84531,
+
+ Linea = 59144,
+ LineaTestnet = 59140,
+
+ #[strum(to_string = "zksync")]
+ #[cfg_attr(feature = "serde", serde(alias = "zksync"))]
+ ZkSync = 324,
+ #[strum(to_string = "zksync-testnet")]
+ #[cfg_attr(feature = "serde", serde(alias = "zksync_testnet"))]
+ ZkSyncTestnet = 280,
+
+ #[strum(to_string = "mantle")]
+ #[cfg_attr(feature = "serde", serde(alias = "mantle"))]
+ Mantle = 5000,
+ #[strum(to_string = "mantle-testnet")]
+ #[cfg_attr(feature = "serde", serde(alias = "mantle_testnet"))]
+ MantleTestnet = 5001,
+}
+
+// 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 Chain {
+ fn default() -> Self {
+ Self::Mainnet
+ }
+}
+
+macro_rules! impl_try_from_numeric {
+ ($($native:ty)+) => {
+ $(
+ impl TryFrom<$native> for Chain {
+ type Error = TryFromPrimitiveError;
+
+ fn try_from(value: $native) -> Result {
+ (value as u64).try_into()
+ }
+ }
+ )+
+ };
+}
+
+impl From for u64 {
+ fn from(chain: Chain) -> Self {
+ chain as u64
+ }
+}
+
+impl_try_from_numeric!(u8 u16 u32 usize);
+
+impl fmt::Display for Chain {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.pad(self.as_ref())
+ }
+}
+
+#[cfg(feature = "serde")]
+impl serde::Serialize for Chain {
+ fn serialize(&self, s: S) -> Result {
+ s.serialize_str(self.as_ref())
+ }
+}
+
+// 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)]
+#[deny(unreachable_patterns, unused_variables)]
+impl Chain {
+ /// 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
+ /// for example.
+ ///
+ /// **Note:** this is not an accurate average, but is rather a sensible default derived from
+ /// blocktime charts such as [Etherscan's](https://etherscan.com/chart/blocktime)
+ /// or [Polygonscan's](https://polygonscan.com/chart/blocktime).
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use alloy_chains::Chain;
+ /// use std::time::Duration;
+ ///
+ /// assert_eq!(Chain::Mainnet.average_blocktime_hint(), Some(Duration::from_millis(12_000)),);
+ /// assert_eq!(Chain::Optimism.average_blocktime_hint(), Some(Duration::from_millis(2_000)),);
+ /// ```
+ pub const fn average_blocktime_hint(&self) -> Option {
+ use Chain::*;
+
+ let ms = match self {
+ Mainnet => 12_000,
+ Arbitrum | ArbitrumTestnet | ArbitrumGoerli | ArbitrumSepolia | ArbitrumNova => 1_300,
+ Optimism | OptimismGoerli | OptimismSepolia => 2_000,
+ Polygon | PolygonMumbai => 2_100,
+ Moonbeam | Moonriver => 12_500,
+ BinanceSmartChain | BinanceSmartChainTestnet => 3_000,
+ Avalanche | AvalancheFuji => 2_000,
+ Fantom | FantomTestnet => 1_200,
+ Cronos | CronosTestnet | Canto | CantoTestnet => 5_700,
+ Evmos | EvmosTestnet => 1_900,
+ Aurora | AuroraTestnet => 1_100,
+ Oasis => 5_500,
+ Emerald => 6_000,
+ Dev | AnvilHardhat => 200,
+ Celo | CeloAlfajores | CeloBaklava => 5_000,
+ FilecoinCalibrationTestnet | FilecoinMainnet => 30_000,
+ Scroll | ScrollAlphaTestnet => 3_000,
+ Gnosis | Chiado => 5_000,
+ // Explicitly exhaustive. See NB above.
+ Morden | Ropsten | Rinkeby | Goerli | Kovan | Sepolia | Holesky | Moonbase
+ | MoonbeamDev | OptimismKovan | Poa | Sokol | Rsk | EmeraldTestnet | Boba | Base
+ | BaseGoerli | ZkSync | ZkSyncTestnet | PolygonZkEvm | PolygonZkEvmTestnet | Metis
+ | Linea | LineaTestnet | Mantle | MantleTestnet => return None,
+ };
+
+ Some(Duration::from_millis(ms))
+ }
+
+ /// Returns whether the chain implements EIP-1559 (with the type 2 EIP-2718 transaction type).
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use alloy_chains::Chain;
+ ///
+ /// assert!(!Chain::Mainnet.is_legacy());
+ /// assert!(Chain::Celo.is_legacy());
+ /// ```
+ pub const fn is_legacy(&self) -> bool {
+ use Chain::*;
+
+ match self {
+ // Known legacy chains / non EIP-1559 compliant
+ OptimismKovan
+ | Fantom
+ | FantomTestnet
+ | BinanceSmartChain
+ | BinanceSmartChainTestnet
+ | ArbitrumTestnet
+ | Rsk
+ | Oasis
+ | Emerald
+ | EmeraldTestnet
+ | Celo
+ | CeloAlfajores
+ | CeloBaklava
+ | Boba
+ | ZkSync
+ | ZkSyncTestnet
+ | Mantle
+ | MantleTestnet
+ | PolygonZkEvm
+ | PolygonZkEvmTestnet
+ | Scroll => true,
+
+ // Known EIP-1559 chains
+ Mainnet
+ | Goerli
+ | Sepolia
+ | Holesky
+ | Base
+ | BaseGoerli
+ | Optimism
+ | OptimismGoerli
+ | OptimismSepolia
+ | Polygon
+ | PolygonMumbai
+ | Avalanche
+ | AvalancheFuji
+ | Arbitrum
+ | ArbitrumGoerli
+ | ArbitrumSepolia
+ | ArbitrumNova
+ | FilecoinMainnet
+ | Linea
+ | LineaTestnet
+ | FilecoinCalibrationTestnet
+ | Gnosis
+ | Chiado => false,
+
+ // Unknown / not applicable, default to false for backwards compatibility
+ Dev | AnvilHardhat | Morden | Ropsten | Rinkeby | Cronos | CronosTestnet | Kovan
+ | Sokol | Poa | Moonbeam | MoonbeamDev | Moonriver | Moonbase | Evmos
+ | EvmosTestnet | Aurora | AuroraTestnet | Canto | CantoTestnet | ScrollAlphaTestnet
+ | Metis => false,
+ }
+ }
+
+ /// Returns whether the chain supports the `PUSH0` opcode or not.
+ ///
+ /// For more information, see EIP-3855:
+ /// ``
+ pub const fn supports_push0(&self) -> bool {
+ match self {
+ Chain::Mainnet | Chain::Goerli | Chain::Sepolia | Chain::Gnosis | Chain::Chiado => true,
+ _ => false,
+ }
+ }
+
+ /// Returns the chain's blockchain explorer and its API (Etherscan and Etherscan-like) URLs.
+ ///
+ /// Returns `(API_URL, BASE_URL)`
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use alloy_chains::Chain;
+ ///
+ /// assert_eq!(
+ /// Chain::Mainnet.etherscan_urls(),
+ /// Some(("https://api.etherscan.io/api", "https://etherscan.io"))
+ /// );
+ /// assert_eq!(
+ /// Chain::Avalanche.etherscan_urls(),
+ /// Some(("https://api.snowtrace.io/api", "https://snowtrace.io"))
+ /// );
+ /// assert_eq!(Chain::AnvilHardhat.etherscan_urls(), None);
+ /// ```
+ pub const fn etherscan_urls(&self) -> Option<(&'static str, &'static str)> {
+ use Chain::*;
+
+ let urls = match self {
+ Mainnet => ("https://api.etherscan.io/api", "https://etherscan.io"),
+ Ropsten => ("https://api-ropsten.etherscan.io/api", "https://ropsten.etherscan.io"),
+ Kovan => ("https://api-kovan.etherscan.io/api", "https://kovan.etherscan.io"),
+ Rinkeby => ("https://api-rinkeby.etherscan.io/api", "https://rinkeby.etherscan.io"),
+ Goerli => ("https://api-goerli.etherscan.io/api", "https://goerli.etherscan.io"),
+ Sepolia => ("https://api-sepolia.etherscan.io/api", "https://sepolia.etherscan.io"),
+ Holesky => ("https://api-holesky.etherscan.io/api", "https://holesky.etherscan.io"),
+
+ Polygon => ("https://api.polygonscan.com/api", "https://polygonscan.com"),
+ PolygonMumbai => {
+ ("https://api-testnet.polygonscan.com/api", "https://mumbai.polygonscan.com")
+ }
+
+ PolygonZkEvm => {
+ ("https://api-zkevm.polygonscan.com/api", "https://zkevm.polygonscan.com")
+ }
+ PolygonZkEvmTestnet => (
+ "https://api-testnet-zkevm.polygonscan.com/api",
+ "https://testnet-zkevm.polygonscan.com",
+ ),
+
+ Avalanche => ("https://api.snowtrace.io/api", "https://snowtrace.io"),
+ AvalancheFuji => {
+ ("https://api-testnet.snowtrace.io/api", "https://testnet.snowtrace.io")
+ }
+
+ Optimism => {
+ ("https://api-optimistic.etherscan.io/api", "https://optimistic.etherscan.io")
+ }
+ OptimismGoerli => (
+ "https://api-goerli-optimistic.etherscan.io/api",
+ "https://goerli-optimism.etherscan.io",
+ ),
+ OptimismKovan => (
+ "https://api-kovan-optimistic.etherscan.io/api",
+ "https://kovan-optimistic.etherscan.io",
+ ),
+ OptimismSepolia => (
+ "https://api-sepolia-optimistic.etherscan.io/api",
+ "https://sepolia-optimism.etherscan.io",
+ ),
+
+ Fantom => ("https://api.ftmscan.com/api", "https://ftmscan.com"),
+ FantomTestnet => ("https://api-testnet.ftmscan.com/api", "https://testnet.ftmscan.com"),
+
+ BinanceSmartChain => ("https://api.bscscan.com/api", "https://bscscan.com"),
+ BinanceSmartChainTestnet => {
+ ("https://api-testnet.bscscan.com/api", "https://testnet.bscscan.com")
+ }
+
+ Arbitrum => ("https://api.arbiscan.io/api", "https://arbiscan.io"),
+ ArbitrumTestnet => {
+ ("https://api-testnet.arbiscan.io/api", "https://testnet.arbiscan.io")
+ }
+ ArbitrumGoerli => ("https://api-goerli.arbiscan.io/api", "https://goerli.arbiscan.io"),
+ ArbitrumSepolia => {
+ ("https://api-sepolia.arbiscan.io/api", "https://sepolia.arbiscan.io")
+ }
+ ArbitrumNova => ("https://api-nova.arbiscan.io/api", "https://nova.arbiscan.io/"),
+
+ Cronos => ("https://api.cronoscan.com/api", "https://cronoscan.com"),
+ CronosTestnet => {
+ ("https://api-testnet.cronoscan.com/api", "https://testnet.cronoscan.com")
+ }
+
+ Moonbeam => ("https://api-moonbeam.moonscan.io/api", "https://moonbeam.moonscan.io/"),
+ Moonbase => ("https://api-moonbase.moonscan.io/api", "https://moonbase.moonscan.io/"),
+ Moonriver => ("https://api-moonriver.moonscan.io/api", "https://moonriver.moonscan.io"),
+
+ Gnosis => ("https://api.gnosisscan.io/api", "https://gnosisscan.io"),
+
+ Scroll => ("https://api.scrollscan.com", "https://scrollscan.com"),
+ ScrollAlphaTestnet => {
+ ("https://blockscout.scroll.io/api", "https://blockscout.scroll.io/")
+ }
+
+ Metis => {
+ ("https://andromeda-explorer.metis.io/api", "https://andromeda-explorer.metis.io/")
+ }
+
+ Chiado => {
+ ("https://blockscout.chiadochain.net/api", "https://blockscout.chiadochain.net")
+ }
+
+ FilecoinCalibrationTestnet => (
+ "https://api.calibration.node.glif.io/rpc/v1",
+ "https://calibration.filfox.info/en",
+ ),
+
+ Sokol => ("https://blockscout.com/poa/sokol/api", "https://blockscout.com/poa/sokol"),
+
+ Poa => ("https://blockscout.com/poa/core/api", "https://blockscout.com/poa/core"),
+
+ Rsk => ("https://blockscout.com/rsk/mainnet/api", "https://blockscout.com/rsk/mainnet"),
+
+ Oasis => ("https://scan.oasischain.io/api", "https://scan.oasischain.io/"),
+
+ Emerald => {
+ ("https://explorer.emerald.oasis.dev/api", "https://explorer.emerald.oasis.dev/")
+ }
+ EmeraldTestnet => (
+ "https://testnet.explorer.emerald.oasis.dev/api",
+ "https://testnet.explorer.emerald.oasis.dev/",
+ ),
+
+ Aurora => ("https://api.aurorascan.dev/api", "https://aurorascan.dev"),
+ AuroraTestnet => {
+ ("https://testnet.aurorascan.dev/api", "https://testnet.aurorascan.dev")
+ }
+
+ Evmos => ("https://evm.evmos.org/api", "https://evm.evmos.org/"),
+ EvmosTestnet => ("https://evm.evmos.dev/api", "https://evm.evmos.dev/"),
+
+ Celo => ("https://explorer.celo.org/mainnet/api", "https://explorer.celo.org/mainnet"),
+ CeloAlfajores => {
+ ("https://explorer.celo.org/alfajores/api", "https://explorer.celo.org/alfajores")
+ }
+ CeloBaklava => {
+ ("https://explorer.celo.org/baklava/api", "https://explorer.celo.org/baklava")
+ }
+
+ Canto => ("https://evm.explorer.canto.io/api", "https://evm.explorer.canto.io/"),
+ CantoTestnet => (
+ "https://testnet-explorer.canto.neobase.one/api",
+ "https://testnet-explorer.canto.neobase.one/",
+ ),
+
+ Boba => ("https://api.bobascan.com/api", "https://bobascan.com"),
+
+ Base => ("https://api.basescan.org/api", "https://basescan.org"),
+
+ BaseGoerli => ("https://api-goerli.basescan.org/api", "https://goerli.basescan.org"),
+
+ ZkSync => {
+ ("https://zksync2-mainnet-explorer.zksync.io/", "https://explorer.zksync.io/")
+ }
+ ZkSyncTestnet => (
+ "https://zksync2-testnet-explorer.zksync.dev/",
+ "https://goerli.explorer.zksync.io/",
+ ),
+ Linea => ("https://api.lineascan.build/api", "https://lineascan.build/"),
+ LineaTestnet => {
+ ("https://explorer.goerli.linea.build/api", "https://explorer.goerli.linea.build/")
+ }
+ Mantle => ("https://explorer.mantle.xyz/api", "https://explorer.mantle.xyz"),
+ MantleTestnet => {
+ ("https://explorer.testnet.mantle.xyz/api", "https://explorer.testnet.mantle.xyz")
+ }
+
+ AnvilHardhat | Dev | Morden | MoonbeamDev | FilecoinMainnet => {
+ // this is explicitly exhaustive so we don't forget to add new urls when adding a
+ // new chain
+ return None;
+ }
+ };
+
+ Some(urls)
+ }
+
+ /// Returns the chain's blockchain explorer's API key environment variable's default name.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use alloy_chains::Chain;
+ ///
+ /// assert_eq!(Chain::Mainnet.etherscan_api_key_name(), Some("ETHERSCAN_API_KEY"));
+ /// assert_eq!(Chain::AnvilHardhat.etherscan_api_key_name(), None);
+ /// ```
+ pub const fn etherscan_api_key_name(&self) -> Option<&'static str> {
+ use Chain::*;
+
+ let api_key_name = match self {
+ Mainnet
+ | Morden
+ | Ropsten
+ | Kovan
+ | Rinkeby
+ | Goerli
+ | Holesky
+ | Optimism
+ | OptimismGoerli
+ | OptimismKovan
+ | OptimismSepolia
+ | BinanceSmartChain
+ | BinanceSmartChainTestnet
+ | Arbitrum
+ | ArbitrumTestnet
+ | ArbitrumGoerli
+ | ArbitrumSepolia
+ | ArbitrumNova
+ | Cronos
+ | CronosTestnet
+ | Aurora
+ | AuroraTestnet
+ | Celo
+ | CeloAlfajores
+ | CeloBaklava
+ | Base
+ | Linea
+ | Mantle
+ | MantleTestnet
+ | BaseGoerli
+ | Gnosis
+ | Scroll => "ETHERSCAN_API_KEY",
+
+ Avalanche | AvalancheFuji => "SNOWTRACE_API_KEY",
+
+ Polygon | PolygonMumbai | PolygonZkEvm | PolygonZkEvmTestnet => "POLYGONSCAN_API_KEY",
+
+ Fantom | FantomTestnet => "FTMSCAN_API_KEY",
+
+ Moonbeam | Moonbase | MoonbeamDev | Moonriver => "MOONSCAN_API_KEY",
+
+ Canto | CantoTestnet => "BLOCKSCOUT_API_KEY",
+
+ Boba => "BOBASCAN_API_KEY",
+
+ // Explicitly exhaustive. See NB above.
+ ScrollAlphaTestnet
+ | Metis
+ | Chiado
+ | Sepolia
+ | Rsk
+ | Sokol
+ | Poa
+ | Oasis
+ | Emerald
+ | EmeraldTestnet
+ | Evmos
+ | EvmosTestnet
+ | AnvilHardhat
+ | Dev
+ | ZkSync
+ | ZkSyncTestnet
+ | FilecoinMainnet
+ | LineaTestnet
+ | FilecoinCalibrationTestnet => return None,
+ };
+
+ Some(api_key_name)
+ }
+
+ /// Returns the chain's blockchain explorer's API key, from the environment variable with the
+ /// name specified in [`etherscan_api_key_name`](Chain::etherscan_api_key_name).
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use alloy_chains::Chain;
+ ///
+ /// let chain = Chain::Mainnet;
+ /// std::env::set_var(chain.etherscan_api_key_name().unwrap(), "KEY");
+ /// assert_eq!(chain.etherscan_api_key().as_deref(), Some("KEY"));
+ /// ```
+ #[cfg(feature = "std")]
+ pub fn etherscan_api_key(&self) -> Option {
+ self.etherscan_api_key_name().and_then(|name| std::env::var(name).ok())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use alloc::string::ToString;
+ use strum::{EnumCount, IntoEnumIterator};
+
+ #[test]
+ #[cfg(feature = "serde")]
+ fn default() {
+ assert_eq!(serde_json::to_string(&Chain::default()).unwrap(), "\"mainnet\"");
+ }
+
+ #[test]
+ 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());
+ #[cfg(feature = "serde")]
+ assert_eq!(serde_json::to_string(&chain).unwrap(), format!("\"{chain_string}\""));
+
+ assert_eq!(chain_string.parse::().unwrap(), chain);
+ }
+ }
+
+ #[test]
+ #[cfg(feature = "serde")]
+ 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])] = &[
+ (Mainnet, &["ethlive"]),
+ (BinanceSmartChain, &["bsc", "binance-smart-chain"]),
+ (BinanceSmartChainTestnet, &["bsc-testnet", "binance-smart-chain-testnet"]),
+ (Gnosis, &["gnosis", "gnosis-chain"]),
+ (PolygonMumbai, &["mumbai"]),
+ (PolygonZkEvm, &["zkevm", "polygon-zkevm"]),
+ (PolygonZkEvmTestnet, &["zkevm-testnet", "polygon-zkevm-testnet"]),
+ (AnvilHardhat, &["anvil", "hardhat"]),
+ (AvalancheFuji, &["fuji"]),
+ (ZkSync, &["zksync"]),
+ (Mantle, &["mantle"]),
+ (MantleTestnet, &["mantle-testnet"]),
+ ];
+
+ for &(chain, aliases) in ALIASES {
+ for &alias in aliases {
+ assert_eq!(alias.parse::().unwrap(), chain);
+
+ #[cfg(feature = "serde")]
+ {
+ let s = alias.to_string().replace('-', "_");
+ assert_eq!(serde_json::from_str::(&format!("\"{s}\"")).unwrap(), chain);
+ }
+ }
+ }
+ }
+
+ #[test]
+ #[cfg(feature = "serde")]
+ fn serde_to_string_match() {
+ for chain in Chain::iter() {
+ let chain_serde = serde_json::to_string(&chain).unwrap();
+ let chain_string = format!("\"{chain}\"");
+ assert_eq!(chain_serde, chain_string);
+ }
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 7d12d9a..9973455 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,14 +1,24 @@
-pub fn add(left: usize, right: usize) -> usize {
- left + right
-}
+#![doc = include_str!("../README.md")]
+#![doc(
+ html_logo_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/alloy.jpg",
+ html_favicon_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/favicon.ico"
+)]
+#![warn(
+ missing_copy_implementations,
+ missing_debug_implementations,
+ missing_docs,
+ unreachable_pub,
+ clippy::missing_const_for_fn,
+ rustdoc::all
+)]
+#![cfg_attr(not(test), warn(unused_crate_dependencies))]
+#![deny(unused_must_use, rust_2018_idioms)]
+#![cfg_attr(not(feature = "std"), no_std)]
+#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
-#[cfg(test)]
-mod tests {
- use super::*;
+#[allow(unused_imports, unused_extern_crates)]
+#[macro_use]
+extern crate alloc;
- #[test]
- fn it_works() {
- let result = add(2, 2);
- assert_eq!(result, 4);
- }
-}
+mod chain;
+pub use chain::Chain;