diff --git a/Cargo.lock b/Cargo.lock index 0c82dd5395..d602b65c36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -297,6 +297,7 @@ dependencies = [ "mimalloc", "mockall 0.12.1", "model", + "network", "num", "number", "observe", @@ -3137,6 +3138,16 @@ dependencies = [ "tempfile", ] +[[package]] +name = "network" +version = "0.1.0" +dependencies = [ + "ethcontract", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "nom" version = "7.1.3" @@ -3405,6 +3416,7 @@ dependencies = [ "mockall 0.12.1", "model", "multibase", + "network", "num", "number", "observe", @@ -4475,6 +4487,7 @@ dependencies = [ "maplit", "mockall 0.12.1", "model", + "network", "num", "number", "observe", @@ -4627,6 +4640,7 @@ dependencies = [ "itertools 0.12.1", "mimalloc", "model", + "network", "num", "observe", "prometheus", diff --git a/crates/autopilot/Cargo.toml b/crates/autopilot/Cargo.toml index ffa3a4901a..ad758711c5 100644 --- a/crates/autopilot/Cargo.toml +++ b/crates/autopilot/Cargo.toml @@ -40,6 +40,7 @@ itertools = { workspace = true } maplit = { workspace = true } mimalloc = { workspace = true } model = { path = "../model" } +network = { path = "../network" } num = { workspace = true } number = { path = "../number" } order-validation = { path = "../order-validation" } diff --git a/crates/autopilot/src/domain/settlement/mod.rs b/crates/autopilot/src/domain/settlement/mod.rs index 6199a6e661..ee77be7119 100644 --- a/crates/autopilot/src/domain/settlement/mod.rs +++ b/crates/autopilot/src/domain/settlement/mod.rs @@ -106,11 +106,11 @@ impl Settlement { pub async fn new( settled: Transaction, persistence: &infra::Persistence, - chain: &infra::blockchain::Id, + network: &network::Network, ) -> Result { let auction = persistence.get_auction(settled.auction_id).await?; - if settled.block > auction.block + max_settlement_age(chain) { + if settled.block > auction.block + max_settlement_age(network) { // A settled transaction references a VERY old auction. // // A hacky way to detect processing of production settlements in the staging @@ -138,23 +138,13 @@ impl Settlement { } } -const MAINNET_BLOCK_TIME: u64 = 13_000; // ms -const GNOSIS_BLOCK_TIME: u64 = 5_000; // ms -const SEPOLIA_BLOCK_TIME: u64 = 13_000; // ms -const ARBITRUM_ONE_BLOCK_TIME: u64 = 100; // ms - /// How old (in terms of blocks) a settlement should be, to be considered as a /// settlement from another environment. /// /// Currently set to ~6h -fn max_settlement_age(chain: &infra::blockchain::Id) -> u64 { +fn max_settlement_age(network: &network::Network) -> u64 { const TARGET_AGE: u64 = 6 * 60 * 60 * 1000; // 6h in ms - match chain { - infra::blockchain::Id::Mainnet => TARGET_AGE / MAINNET_BLOCK_TIME, - infra::blockchain::Id::Gnosis => TARGET_AGE / GNOSIS_BLOCK_TIME, - infra::blockchain::Id::Sepolia => TARGET_AGE / SEPOLIA_BLOCK_TIME, - infra::blockchain::Id::ArbitrumOne => TARGET_AGE / ARBITRUM_ONE_BLOCK_TIME, - } + network.number_of_blocks_in(TARGET_AGE).round() as u64 } #[derive(Debug, thiserror::Error)] diff --git a/crates/autopilot/src/domain/settlement/observer.rs b/crates/autopilot/src/domain/settlement/observer.rs index 631539f9ef..87bc46807f 100644 --- a/crates/autopilot/src/domain/settlement/observer.rs +++ b/crates/autopilot/src/domain/settlement/observer.rs @@ -81,7 +81,7 @@ impl Observer { let settlement = match settlement::Settlement::new( transaction, &self.persistence, - self.eth.chain(), + self.eth.network(), ) .await { diff --git a/crates/autopilot/src/infra/blockchain/authenticator.rs b/crates/autopilot/src/infra/blockchain/authenticator.rs index 4ffe0a3caf..ddb621a578 100644 --- a/crates/autopilot/src/infra/blockchain/authenticator.rs +++ b/crates/autopilot/src/infra/blockchain/authenticator.rs @@ -1,10 +1,7 @@ use { crate::{ domain::{self, eth}, - infra::blockchain::{ - self, - contracts::{deployment_address, Contracts}, - }, + infra::blockchain::contracts::{deployment_address, Contracts}, }, ethcontract::{dyns::DynWeb3, GasPrice}, }; @@ -25,13 +22,13 @@ impl Manager { /// Creates an authenticator which can remove solvers from the allow-list pub async fn new( web3: DynWeb3, - chain: blockchain::Id, + network: &network::Network, contracts: Contracts, authenticator_pk: eth::H256, ) -> Self { let authenticator_role = contracts::Roles::at( &web3, - deployment_address(contracts::Roles::raw_contract(), &chain).expect("roles address"), + deployment_address(contracts::Roles::raw_contract(), &network).expect("roles address"), ); Self { diff --git a/crates/autopilot/src/infra/blockchain/contracts.rs b/crates/autopilot/src/infra/blockchain/contracts.rs index 0db3f67a66..36486a4836 100644 --- a/crates/autopilot/src/infra/blockchain/contracts.rs +++ b/crates/autopilot/src/infra/blockchain/contracts.rs @@ -1,8 +1,4 @@ -use { - crate::{domain, infra::blockchain}, - ethcontract::dyns::DynWeb3, - primitive_types::H160, -}; +use {crate::domain, ethcontract::dyns::DynWeb3, primitive_types::H160}; #[derive(Debug, Clone)] pub struct Contracts { @@ -24,10 +20,10 @@ pub struct Addresses { } impl Contracts { - pub async fn new(web3: &DynWeb3, chain: &blockchain::Id, addresses: Addresses) -> Self { + pub async fn new(web3: &DynWeb3, network: &network::Network, addresses: Addresses) -> Self { let address_for = |contract: ðcontract::Contract, address: Option| { address - .or_else(|| deployment_address(contract, chain)) + .or_else(|| deployment_address(contract, network)) .unwrap() }; @@ -104,10 +100,10 @@ impl Contracts { /// there is no known deployment for the contract on that network. pub fn deployment_address( contract: ðcontract::Contract, - chain: &blockchain::Id, + network: &network::Network, ) -> Option { contract .networks - .get(chain.network_id()) + .get(&network.chain_id().to_string()) .map(|network| network.address) } diff --git a/crates/autopilot/src/infra/blockchain/id.rs b/crates/autopilot/src/infra/blockchain/id.rs deleted file mode 100644 index 970c812101..0000000000 --- a/crates/autopilot/src/infra/blockchain/id.rs +++ /dev/null @@ -1,43 +0,0 @@ -use primitive_types::U256; - -/// A supported Ethereum Chain ID. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Id { - Mainnet = 1, - Gnosis = 100, - Sepolia = 11155111, - ArbitrumOne = 42161, -} - -impl Id { - pub fn new(value: U256) -> Result { - // Check to avoid panics for large `U256` values, as there is no checked - // conversion API available and we don't support chains with IDs greater - // than `u64::MAX` anyway. - if value > U256::from(u64::MAX) { - return Err(UnsupportedChain); - } - - match value.as_u64() { - 1 => Ok(Self::Mainnet), - 100 => Ok(Self::Gnosis), - 11155111 => Ok(Self::Sepolia), - 42161 => Ok(Self::ArbitrumOne), - _ => Err(UnsupportedChain), - } - } - - /// Returns the network ID for the chain. - pub fn network_id(self) -> &'static str { - match self { - Id::Mainnet => "1", - Id::Gnosis => "100", - Id::Sepolia => "11155111", - Id::ArbitrumOne => "42161", - } - } -} - -#[derive(Debug, thiserror::Error)] -#[error("unsupported chain")] -pub struct UnsupportedChain; diff --git a/crates/autopilot/src/infra/blockchain/mod.rs b/crates/autopilot/src/infra/blockchain/mod.rs index 42ff639364..06f95c68d4 100644 --- a/crates/autopilot/src/infra/blockchain/mod.rs +++ b/crates/autopilot/src/infra/blockchain/mod.rs @@ -11,14 +11,11 @@ use { pub mod authenticator; pub mod contracts; -pub mod id; - -pub use id::Id; /// An Ethereum RPC connection. pub struct Rpc { web3: DynWeb3, - chain: Id, + network: network::Network, url: Url, } @@ -30,18 +27,19 @@ impl Rpc { ethrpc_args: &shared::ethrpc::Arguments, ) -> Result { let web3 = boundary::web3_client(url, ethrpc_args); - let chain = Id::new(web3.eth().chain_id().await?).map_err(|_| Error::UnsupportedChain)?; + let network = network::Network::try_from(web3.eth().chain_id().await?) + .map_err(|_| Error::UnsupportedChain)?; Ok(Self { web3, - chain, + network, url: url.clone(), }) } /// Returns the chain id for the RPC connection. - pub fn chain(&self) -> Id { - self.chain + pub fn network(&self) -> network::Network { + self.network } /// Returns a reference to the underlying web3 client. @@ -59,7 +57,7 @@ impl Rpc { #[derive(Clone)] pub struct Ethereum { web3: DynWeb3, - chain: Id, + network: network::Network, current_block: CurrentBlockWatcher, contracts: Contracts, } @@ -73,25 +71,25 @@ impl Ethereum { /// any initialization error. pub async fn new( web3: DynWeb3, - chain: Id, + network: &network::Network, url: Url, addresses: contracts::Addresses, poll_interval: Duration, ) -> Self { - let contracts = Contracts::new(&web3, &chain, addresses).await; + let contracts = Contracts::new(&web3, &network, addresses).await; Self { current_block: ethrpc::block_stream::current_block_stream(url, poll_interval) .await .expect("couldn't initialize current block stream"), web3, - chain, + network: network.clone(), contracts, } } - pub fn chain(&self) -> &Id { - &self.chain + pub fn network(&self) -> &network::Network { + &self.network } /// Returns a stream that monitors the block chain to inform about the diff --git a/crates/autopilot/src/run.rs b/crates/autopilot/src/run.rs index c6747413b7..14c214dcf8 100644 --- a/crates/autopilot/src/run.rs +++ b/crates/autopilot/src/run.rs @@ -93,12 +93,12 @@ async fn ethrpc(url: &Url, ethrpc_args: &shared::ethrpc::Arguments) -> infra::bl async fn ethereum( web3: DynWeb3, - chain: infra::blockchain::Id, + network: &network::Network, url: Url, contracts: infra::blockchain::contracts::Addresses, poll_interval: Duration, ) -> infra::Ethereum { - infra::Ethereum::new(web3, chain, url, contracts, poll_interval).await + infra::Ethereum::new(web3, network, url, contracts, poll_interval).await } pub async fn start(args: impl Iterator) { @@ -156,7 +156,7 @@ pub async fn run(args: Arguments) { } let ethrpc = ethrpc(&args.shared.node_url, &args.shared.ethrpc).await; - let chain = ethrpc.chain(); + let chain = ethrpc.network(); let web3 = ethrpc.web3().clone(); let url = ethrpc.url().clone(); let contracts = infra::blockchain::contracts::Addresses { @@ -165,7 +165,7 @@ pub async fn run(args: Arguments) { }; let eth = ethereum( web3.clone(), - chain, + &chain, url, contracts.clone(), args.shared.current_block.block_stream_poll_interval, @@ -197,12 +197,11 @@ pub async fn run(args: Arguments) { other => Some(other.unwrap()), }; - let network_name = shared::network::network_name(chain_id); + let network = network::Network::try_from(chain_id).unwrap(); let signature_validator = signature_validator::validator( &web3, signature_validator::Contracts { - chain_id, settlement: eth.contracts().settlement().address(), vault_relayer, }, @@ -211,7 +210,6 @@ pub async fn run(args: Arguments) { let balance_fetcher = account_balances::cached( &web3, account_balances::Contracts { - chain_id, settlement: eth.contracts().settlement().address(), vault_relayer, vault: vault.as_ref().map(|contract| contract.address()), @@ -230,10 +228,11 @@ pub async fn run(args: Arguments) { .expect("failed to create gas price estimator"), ); - let baseline_sources = args.shared.baseline_sources.clone().unwrap_or_else(|| { - shared::sources::defaults_for_chain(chain_id) - .expect("failed to get default baseline sources") - }); + let baseline_sources = args + .shared + .baseline_sources + .clone() + .unwrap_or_else(|| shared::sources::defaults_for_network(&network)); tracing::info!(?baseline_sources, "using baseline sources"); let univ2_sources = baseline_sources .iter() @@ -261,7 +260,7 @@ pub async fn run(args: Arguments) { let finder = token_owner_finder::init( &args.token_owner_finder, web3.clone(), - chain_id, + &network, &http_factory, &pair_providers, vault.as_ref(), @@ -312,8 +311,7 @@ pub async fn run(args: Arguments) { factory::Network { web3: web3.clone(), simulation_web3, - name: network_name.to_string(), - chain_id, + network, native_token: eth.contracts().weth().address(), settlement: eth.contracts().settlement().address(), authenticator: eth diff --git a/crates/network/Cargo.toml b/crates/network/Cargo.toml new file mode 100644 index 0000000000..5d989b9f91 --- /dev/null +++ b/crates/network/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "network" +version = "0.1.0" +authors = ["Cow Protocol Developers "] +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] +ethcontract = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } +serde_json = { workspace = true } + +[lints] +workspace = true diff --git a/crates/network/LICENSE-APACHE b/crates/network/LICENSE-APACHE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/crates/network/LICENSE-APACHE @@ -0,0 +1,201 @@ + 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/crates/network/LICENSE-MIT b/crates/network/LICENSE-MIT new file mode 100644 index 0000000000..65158b9a9b --- /dev/null +++ b/crates/network/LICENSE-MIT @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2021 Gnosis Ltd +Copyright (c) 2022 Cow Service Lda + +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/crates/network/src/lib.rs b/crates/network/src/lib.rs new file mode 100644 index 0000000000..d3cfa74f7d --- /dev/null +++ b/crates/network/src/lib.rs @@ -0,0 +1,218 @@ +use { + ethcontract::{ + jsonrpc::serde::{de, Deserialize, Deserializer}, + U256, + }, + thiserror::Error, +}; + +/// Represents each available network +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Network { + Mainnet = 1, + Goerli = 5, + Gnosis = 100, + Sepolia = 11155111, + ArbitrumOne = 42161, + Base = 8453, +} + +impl Network { + /// Returns the network's chain ID + pub fn chain_id(&self) -> u64 { + match self { + Self::Mainnet => 1, + Self::Goerli => 5, + Self::Gnosis => 100, + Self::Sepolia => 11155111, + Self::ArbitrumOne => 42161, + Self::Base => 8453, + } + } + + /// Returns the canonical name of the network on CoW Protocol. + pub fn name(&self) -> &'static str { + // You can find a list of available networks by network and chain id here: + // https://chainid.network/chains.json + match &self { + Self::Mainnet => "Ethereum / Mainnet", + Self::Goerli => "Ethereum / Goerli", + Self::Gnosis => "xDAI", + Self::Sepolia => "Ethereum / Sepolia", + Self::ArbitrumOne => "Arbitrum One", + Self::Base => "Base", + } + } + + pub fn default_amount_to_estimate_native_prices_with(&self) -> Option { + match &self { + Self::Mainnet | Self::Goerli | Self::Sepolia | Self::ArbitrumOne | Self::Base => { + Some(10u128.pow(18).into()) + } + Self::Gnosis => Some(10u128.pow(21).into()), + } + } + + /// Returns the block time in milliseconds + pub fn block_time_in_ms(&self) -> u64 { + match self { + Self::Mainnet => 13_000, + Self::Goerli => 13_000, + Self::Gnosis => 5_000, + Self::Sepolia => 13_000, + Self::ArbitrumOne => 250, + Self::Base => 2_000, + } + } + + /// Returns the number of blocks that fits into the given time (in + /// milliseconds) + pub fn number_of_blocks_in(&self, time_in_ms: u64) -> f64 { + time_in_ms as f64 / self.block_time_in_ms() as f64 + } +} + +impl TryFrom for Network { + type Error = Error; + + /// Initializes `Network` from a chain ID, returns error if the chain id is + /// not supported + fn try_from(value: u64) -> Result { + match value { + 1 => Ok(Self::Mainnet), + 5 => Ok(Self::Goerli), + 100 => Ok(Self::Gnosis), + 11155111 => Ok(Self::Sepolia), + 42161 => Ok(Self::ArbitrumOne), + 8453 => Ok(Self::Base), + _ => Err(Error::ChainIdNotSupported(value)), + } + } +} + +impl TryFrom for Network { + type Error = Error; + + /// Initializes `Network` from a chain ID, returns error if the chain id is + /// not supported + fn try_from(value: U256) -> Result { + value.as_u64().try_into() + } +} + +impl<'de> Deserialize<'de> for Network { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct NetworkVisitor; + + impl<'de> de::Visitor<'de> for NetworkVisitor { + type Value = Network; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a u64 or a string") + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + Network::try_from(value).map_err(de::Error::custom) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Network::try_from(value.parse::().map_err(de::Error::custom)?) + .map_err(de::Error::custom) + } + } + + deserializer.deserialize_any(NetworkVisitor) + } +} + +#[derive(Error, Debug)] +pub enum Error { + #[error("chain id not supported: {0}")] + ChainIdNotSupported(u64), +} + +#[cfg(test)] +mod test { + use {super::*, ethcontract::jsonrpc::serde_json}; + + #[test] + fn test_number_of_blocks_in() { + const TARGET_AGE: u64 = 6 * 60 * 60 * 1000; // 6h in ms + + assert_eq!( + Network::Mainnet.number_of_blocks_in(TARGET_AGE).round(), + 1662.0 + ); + assert_eq!( + Network::Sepolia.number_of_blocks_in(TARGET_AGE).round(), + 1662.0 + ); + assert_eq!( + Network::Goerli.number_of_blocks_in(TARGET_AGE).round(), + 1662.0 + ); + assert_eq!( + Network::Gnosis.number_of_blocks_in(TARGET_AGE).round(), + 4320.0 + ); + assert_eq!( + Network::Base.number_of_blocks_in(TARGET_AGE).round(), + 10800.0 + ); + assert_eq!( + Network::ArbitrumOne.number_of_blocks_in(TARGET_AGE).round(), + 86400.0 + ); + } + + #[test] + fn test_deserialize_from_u64() { + // Test valid u64 deserialization + let json_data = "1"; // Should deserialize to Network::Mainnet + let network: Network = serde_json::from_str(json_data).unwrap(); + assert_eq!(network, Network::Mainnet); + + let json_data = "5"; // Should deserialize to Network::Goerli + let network: Network = serde_json::from_str(json_data).unwrap(); + assert_eq!(network, Network::Goerli); + + let json_data = "100"; // Should deserialize to Network::Gnosis + let network: Network = serde_json::from_str(json_data).unwrap(); + assert_eq!(network, Network::Gnosis); + + // Test invalid u64 deserialization (should return an error) + let json_data = "9999999"; // Not a valid Network variant + let result: Result = serde_json::from_str(json_data); + assert!(result.is_err()); + } + + #[test] + fn test_deserialize_from_str() { + // Test valid string deserialization + let json_data = "\"1\""; // Should parse to u64 1 and then to Network::Mainnet + let network: Network = serde_json::from_str(json_data).unwrap(); + assert_eq!(network, Network::Mainnet); + + let json_data = "\"5\""; // Should parse to u64 5 and then to Network::Goerli + let network: Network = serde_json::from_str(json_data).unwrap(); + assert_eq!(network, Network::Goerli); + + let json_data = "\"100\""; // Should parse to u64 100 and then to Network::Gnosis + let network: Network = serde_json::from_str(json_data).unwrap(); + assert_eq!(network, Network::Gnosis); + + // Test invalid string deserialization (should return an error) + let json_data = "\"invalid\""; // Cannot be parsed as u64 + let result: Result = serde_json::from_str(json_data); + assert!(result.is_err()); + } +} diff --git a/crates/orderbook/Cargo.toml b/crates/orderbook/Cargo.toml index 5d89e6cdab..660912ec46 100644 --- a/crates/orderbook/Cargo.toml +++ b/crates/orderbook/Cargo.toml @@ -37,6 +37,7 @@ maplit = { workspace = true } mimalloc = { workspace = true } model = { path = "../model" } multibase = "0.9" +network = { path = "../network" } num = { workspace = true } number = { path = "../number" } order-validation = { path = "../order-validation" } diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 8e87611994..9dcc438f0a 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -30,7 +30,6 @@ use { gas_price::InstrumentedGasEstimator, http_client::HttpClientFactory, metrics::{serve_metrics, DEFAULT_METRICS_PORT}, - network::network_name, order_quoting::{self, OrderQuoter}, order_validation::{OrderValidPeriodConfiguration, OrderValidator}, price_estimation::{ @@ -104,12 +103,11 @@ pub async fn run(args: Arguments) { .expect("load native token contract"), }; - let network_name = network_name(chain_id); + let network = network::Network::try_from(chain_id).unwrap(); let signature_validator = signature_validator::validator( &web3, signature_validator::Contracts { - chain_id, settlement: settlement_contract.address(), vault_relayer, }, @@ -145,7 +143,6 @@ pub async fn run(args: Arguments) { let balance_fetcher = account_balances::fetcher( &web3, account_balances::Contracts { - chain_id, settlement: settlement_contract.address(), vault_relayer, vault: vault.as_ref().map(|contract| contract.address()), @@ -163,9 +160,11 @@ pub async fn run(args: Arguments) { .expect("failed to create gas price estimator"), )); - let baseline_sources = args.shared.baseline_sources.clone().unwrap_or_else(|| { - sources::defaults_for_chain(chain_id).expect("failed to get default baseline sources") - }); + let baseline_sources = args + .shared + .baseline_sources + .clone() + .unwrap_or_else(|| sources::defaults_for_network(&network)); tracing::info!(?baseline_sources, "using baseline sources"); let univ2_sources = baseline_sources .iter() @@ -198,7 +197,7 @@ pub async fn run(args: Arguments) { let finder = token_owner_finder::init( &args.token_owner_finder, web3.clone(), - chain_id, + &network, &http_factory, &pair_providers, vault.as_ref(), @@ -255,8 +254,7 @@ pub async fn run(args: Arguments) { factory::Network { web3: web3.clone(), simulation_web3, - name: network_name.to_string(), - chain_id, + network, native_token: native_token.address(), settlement: settlement_contract.address(), authenticator: settlement_contract diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index 756a6d835e..ab2696dc7e 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -38,6 +38,7 @@ lazy_static = { workspace = true } maplit = { workspace = true } mockall = { workspace = true } model = { path = "../model" } +network = { path = "../network" } num = { workspace = true } number = { path = "../number" } order-validation = { path = "../order-validation" } diff --git a/crates/shared/src/account_balances.rs b/crates/shared/src/account_balances.rs index 93e748d4f1..c66e548e7c 100644 --- a/crates/shared/src/account_balances.rs +++ b/crates/shared/src/account_balances.rs @@ -67,7 +67,6 @@ pub trait BalanceFetching: Send + Sync { /// Contracts required for balance simulation. pub struct Contracts { - pub chain_id: u64, pub settlement: H160, pub vault_relayer: H160, pub vault: Option, diff --git a/crates/shared/src/bad_token/token_owner_finder.rs b/crates/shared/src/bad_token/token_owner_finder.rs index 16ccbb347a..9af9cd7787 100644 --- a/crates/shared/src/bad_token/token_owner_finder.rs +++ b/crates/shared/src/bad_token/token_owner_finder.rs @@ -190,11 +190,14 @@ pub enum TokenOwnerFindingStrategy { impl TokenOwnerFindingStrategy { /// Returns the default set of token owner finding strategies. - pub fn defaults_for_chain(chain_id: u64) -> &'static [Self] { - match chain_id { - 1 => &[Self::Liquidity, Self::Blockscout, Self::Ethplorer], - 100 => &[Self::Liquidity, Self::Blockscout], - _ => &[Self::Liquidity], + pub fn defaults_for_network(network: &network::Network) -> &'static [Self] { + match network { + network::Network::Mainnet => &[Self::Liquidity, Self::Blockscout, Self::Ethplorer], + network::Network::Gnosis => &[Self::Liquidity, Self::Blockscout], + network::Network::Sepolia + | network::Network::Goerli + | network::Network::ArbitrumOne + | network::Network::Base => &[Self::Liquidity], } } } @@ -270,7 +273,7 @@ impl Display for Arguments { pub async fn init( args: &Arguments, web3: Web3, - chain_id: u64, + network: &network::Network, http_factory: &HttpClientFactory, pair_providers: &[PairProvider], vault: Option<&BalancerV2Vault>, @@ -282,7 +285,7 @@ pub async fn init( let finders = args .token_owner_finders .as_deref() - .unwrap_or_else(|| TokenOwnerFindingStrategy::defaults_for_chain(chain_id)); + .unwrap_or_else(|| TokenOwnerFindingStrategy::defaults_for_network(network)); tracing::debug!(?finders, "initializing token owner finders"); let mut proposers = Vec::>::new(); @@ -315,7 +318,7 @@ pub async fn init( if finders.contains(&TokenOwnerFindingStrategy::Blockscout) { let mut blockscout = - BlockscoutTokenOwnerFinder::try_with_network(http_factory.create(), chain_id)?; + BlockscoutTokenOwnerFinder::try_with_network(http_factory.create(), network)?; if let Some(blockscout_config) = &args.blockscout { blockscout.with_base_url(blockscout_config.blockscout_api_url.clone()); blockscout.with_api_key(blockscout_config.blockscout_api_key.clone()); @@ -332,7 +335,7 @@ pub async fn init( args.ethplorer .as_ref() .map(|ethplorer| ethplorer.ethplorer_api_key.clone()), - chain_id, + &network, )?; if let Some(ethplorer_config) = &args.ethplorer { ethplorer.with_base_url(ethplorer_config.ethplorer_api_url.clone()); diff --git a/crates/shared/src/bad_token/token_owner_finder/blockscout.rs b/crates/shared/src/bad_token/token_owner_finder/blockscout.rs index b294826cab..97df7239f8 100644 --- a/crates/shared/src/bad_token/token_owner_finder/blockscout.rs +++ b/crates/shared/src/bad_token/token_owner_finder/blockscout.rs @@ -1,6 +1,6 @@ use { super::TokenOwnerProposing, - anyhow::{bail, Result}, + anyhow::Result, ethcontract::H160, prometheus::IntCounterVec, prometheus_metric_storage::MetricStorage, @@ -17,14 +17,14 @@ pub struct BlockscoutTokenOwnerFinder { } impl BlockscoutTokenOwnerFinder { - pub fn try_with_network(client: Client, network_id: u64) -> Result { - let base_url = match network_id { - 1 => "https://eth.blockscout.com/api", - 5 => "https://eth-goerli.blockscout.com/api", - 100 => "https://blockscout.com/xdai/mainnet/api", - 11155111 => "https://eth-sepolia.blockscout.com/api", - 42161 => "https://arbitrum.blockscout.com/api", - _ => bail!("Unsupported Network"), + pub fn try_with_network(client: Client, network: &network::Network) -> Result { + let base_url = match network { + network::Network::Mainnet => "https://eth.blockscout.com/api", + network::Network::Goerli => "https://eth-goerli.blockscout.com/api", + network::Network::Gnosis => "https://blockscout.com/xdai/mainnet/api", + network::Network::Sepolia => "https://eth-sepolia.blockscout.com/api", + network::Network::ArbitrumOne => "https://arbitrum.blockscout.com/api", + network::Network::Base => "https://base.blockscout.com/api", }; Ok(Self { @@ -138,7 +138,11 @@ mod tests { #[tokio::test] #[ignore] async fn test_blockscout_token_finding_mainnet() { - let finder = BlockscoutTokenOwnerFinder::try_with_network(Client::default(), 1).unwrap(); + let finder = BlockscoutTokenOwnerFinder::try_with_network( + Client::default(), + &network::Network::Mainnet, + ) + .unwrap(); let owners = finder .find_candidate_owners(H160(hex!("1337BedC9D22ecbe766dF105c9623922A27963EC"))) .await; @@ -148,7 +152,11 @@ mod tests { #[tokio::test] #[ignore] async fn test_blockscout_token_finding_xdai() { - let finder = BlockscoutTokenOwnerFinder::try_with_network(Client::default(), 100).unwrap(); + let finder = BlockscoutTokenOwnerFinder::try_with_network( + Client::default(), + &network::Network::Gnosis, + ) + .unwrap(); let owners = finder .find_candidate_owners(H160(hex!("1337BedC9D22ecbe766dF105c9623922A27963EC"))) .await; @@ -158,7 +166,11 @@ mod tests { #[tokio::test] #[ignore] async fn test_blockscout_token_finding_no_owners() { - let finder = BlockscoutTokenOwnerFinder::try_with_network(Client::default(), 100).unwrap(); + let finder = BlockscoutTokenOwnerFinder::try_with_network( + Client::default(), + &network::Network::Gnosis, + ) + .unwrap(); let owners = finder .find_candidate_owners(H160(hex!("000000000000000000000000000000000000def1"))) .await; diff --git a/crates/shared/src/bad_token/token_owner_finder/ethplorer.rs b/crates/shared/src/bad_token/token_owner_finder/ethplorer.rs index 63d6d7c86f..16dfffc739 100644 --- a/crates/shared/src/bad_token/token_owner_finder/ethplorer.rs +++ b/crates/shared/src/bad_token/token_owner_finder/ethplorer.rs @@ -28,9 +28,12 @@ impl EthplorerTokenOwnerFinder { pub fn try_with_network( client: Client, api_key: Option, - chain_id: u64, + network: &network::Network, ) -> Result { - ensure!(chain_id == 1, "Ethplorer API unsupported network"); + ensure!( + *network == network::Network::Mainnet, + "Ethplorer API unsupported network" + ); Ok(Self { client, base: Url::try_from(BASE).unwrap(), @@ -152,8 +155,12 @@ mod tests { #[tokio::test] #[ignore] async fn token_finding_mainnet() { - let finder = - EthplorerTokenOwnerFinder::try_with_network(Client::default(), None, 1).unwrap(); + let finder = EthplorerTokenOwnerFinder::try_with_network( + Client::default(), + None, + &network::Network::Mainnet, + ) + .unwrap(); let owners = finder .find_candidate_owners(H160(hex!("1337BedC9D22ecbe766dF105c9623922A27963EC"))) .await; @@ -163,8 +170,12 @@ mod tests { #[tokio::test] #[ignore] async fn returns_no_owners_on_invalid_token() { - let finder = - EthplorerTokenOwnerFinder::try_with_network(Client::default(), None, 1).unwrap(); + let finder = EthplorerTokenOwnerFinder::try_with_network( + Client::default(), + None, + &network::Network::Gnosis, + ) + .unwrap(); let owners = finder .find_candidate_owners(H160(hex!("000000000000000000000000000000000000def1"))) .await; diff --git a/crates/shared/src/bad_token/trace_call.rs b/crates/shared/src/bad_token/trace_call.rs index 56812bc4bd..742c0886da 100644 --- a/crates/shared/src/bad_token/trace_call.rs +++ b/crates/shared/src/bad_token/trace_call.rs @@ -697,8 +697,11 @@ mod tests { .unwrap(), ), Arc::new( - BlockscoutTokenOwnerFinder::try_with_network(reqwest::Client::new(), 1) - .unwrap(), + BlockscoutTokenOwnerFinder::try_with_network( + reqwest::Client::new(), + &network::Network::Mainnet, + ) + .unwrap(), ), ], }); diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 05fb32902e..9543c870d7 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -26,7 +26,6 @@ pub mod http_solver; pub mod interaction; pub mod maintenance; pub mod metrics; -pub mod network; pub mod order_quoting; pub mod order_validation; pub mod price_estimation; diff --git a/crates/shared/src/network.rs b/crates/shared/src/network.rs deleted file mode 100644 index 6b7eb6f97c..0000000000 --- a/crates/shared/src/network.rs +++ /dev/null @@ -1,15 +0,0 @@ -/// Maps ChainId to the network name. -/// If the output is from a known network, it represents the canonical name of -/// the network on CoW Protocol. -pub fn network_name(chain_id: u64) -> &'static str { - // You can find a list of available networks by network and chain id here: - // https://chainid.network/chains.json - match chain_id { - 1 => "Ethereum / Mainnet", - 5 => "Ethereum / Goerli", - 100 => "xDAI", - 11155111 => "Ethereum / Sepolia", - 42161 => "Arbitrum One", - _ => panic!("Unknown network (chain_id={chain_id})"), - } -} diff --git a/crates/shared/src/price_estimation/factory.rs b/crates/shared/src/price_estimation/factory.rs index 37a2fe5f70..3fb91b440b 100644 --- a/crates/shared/src/price_estimation/factory.rs +++ b/crates/shared/src/price_estimation/factory.rs @@ -56,8 +56,7 @@ struct EstimatorEntry { pub struct Network { pub web3: Web3, pub simulation_web3: Option, - pub name: String, - pub chain_id: u64, + pub network: network::Network, pub native_token: H160, pub settlement: H160, pub authenticator: H160, @@ -102,7 +101,7 @@ impl<'a> PriceEstimatorFactory<'a> { .tenderly .get_api_instance(&components.http_factory, "price_estimation".to_owned()) .unwrap() - .map(|t| TenderlyCodeSimulator::new(t, network.chain_id)); + .map(|t| TenderlyCodeSimulator::new(t, network.network.chain_id())); let simulator: Arc = match tenderly { Some(tenderly) => Arc::new(code_simulation::Web3ThenTenderly::new( @@ -128,7 +127,9 @@ impl<'a> PriceEstimatorFactory<'a> { self.args .amount_to_estimate_prices_with .or_else(|| { - native::default_amount_to_estimate_native_prices_with(self.network.chain_id) + self.network + .network + .default_amount_to_estimate_native_prices_with() }) .context("No amount to estimate prices with set.")?, ) @@ -208,7 +209,7 @@ impl<'a> PriceEstimatorFactory<'a> { self.components.http_factory.create(), self.args.one_inch_url.clone(), self.args.one_inch_api_key.clone(), - self.network.chain_id, + self.network.network.chain_id(), self.network.block_stream.clone(), self.components.tokens.clone(), ), @@ -222,7 +223,7 @@ impl<'a> PriceEstimatorFactory<'a> { self.components.http_factory.create(), self.args.coin_gecko.coin_gecko_url.clone(), self.args.coin_gecko.coin_gecko_api_key.clone(), - self.network.chain_id, + &self.network.network, weth.address(), self.components.tokens.clone(), ) diff --git a/crates/shared/src/price_estimation/native.rs b/crates/shared/src/price_estimation/native.rs index aa7498c84e..dba3c8886b 100644 --- a/crates/shared/src/price_estimation/native.rs +++ b/crates/shared/src/price_estimation/native.rs @@ -17,16 +17,6 @@ pub use self::{coingecko::CoinGecko, oneinch::OneInch}; pub type NativePrice = f64; pub type NativePriceEstimateResult = Result; -pub fn default_amount_to_estimate_native_prices_with(chain_id: u64) -> Option { - match chain_id { - // Mainnet, Göŕli, Sepolia, Arbitrum - 1 | 5 | 11155111 | 42161 => Some(10u128.pow(18).into()), - // Gnosis chain - 100 => Some(10u128.pow(21).into()), - _ => None, - } -} - /// Convert from normalized price to floating point price pub fn from_normalized_price(price: BigDecimal) -> Option { static ONE_E18: Lazy = Lazy::new(|| BigDecimal::try_from(1e18).unwrap()); diff --git a/crates/shared/src/price_estimation/native/coingecko.rs b/crates/shared/src/price_estimation/native/coingecko.rs index aa73f8cc67..78e545d3ff 100644 --- a/crates/shared/src/price_estimation/native/coingecko.rs +++ b/crates/shared/src/price_estimation/native/coingecko.rs @@ -53,7 +53,7 @@ impl CoinGecko { client: Client, base_url: Url, api_key: Option, - chain_id: u64, + network: &network::Network, native_token: H160, token_infos: Arc, ) -> Result { @@ -68,11 +68,14 @@ impl CoinGecko { decimals: denominator_decimals, }; - let chain = match chain_id { - 1 => "ethereum".to_string(), - 100 => "xdai".to_string(), - 42161 => "arbitrum-one".to_string(), - n => anyhow::bail!("unsupported network {n}"), + let chain = match network { + network::Network::Mainnet => "ethereum".to_string(), + network::Network::Gnosis => "xdai".to_string(), + network::Network::ArbitrumOne => "arbitrum-one".to_string(), + network::Network::Base => "base".to_string(), + network::Network::Sepolia | network::Network::Goerli => { + anyhow::bail!("unsupported network {}", network.name()) + } }; Ok(Self { client, @@ -304,32 +307,32 @@ mod tests { client: Client, base_url: Url, api_key: Option, - chain_id: u64, + network: &network::Network, token_infos: Arc, ) -> Result { - let (chain, denominator) = match chain_id { - 1 => ( + let (chain, denominator) = match network { + network::Network::Mainnet => ( "ethereum".to_string(), Denominator { address: addr!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"), decimals: 18, }, ), - 100 => ( + network::Network::Gnosis => ( "xdai".to_string(), Denominator { address: addr!("e91d153e0b41518a2ce8dd3d7944fa863463a97d"), decimals: 18, }, ), - 42161 => ( + network::Network::ArbitrumOne => ( "arbitrum-one".to_string(), Denominator { address: addr!("82af49447d8a07e3bd95bd0d56f35241523fbab1"), decimals: 18, }, ), - n => anyhow::bail!("unsupported network {n}"), + n => anyhow::bail!("unsupported network {}", n.name()), }; Ok(Self { client, @@ -379,7 +382,7 @@ mod tests { Client::default(), Url::parse(BASE_API_URL).unwrap(), None, - 1, + &network::Network::Mainnet, default_token_info_fetcher(), ) .unwrap(); @@ -399,7 +402,7 @@ mod tests { Client::default(), Url::parse(BASE_API_PRO_URL).unwrap(), env::var("COIN_GECKO_API_KEY").ok(), - 100, + &network::Network::Gnosis, default_token_info_fetcher(), ) .unwrap(); @@ -419,7 +422,7 @@ mod tests { Client::default(), Url::parse(BASE_API_PRO_URL).unwrap(), env::var("COIN_GECKO_API_KEY").ok(), - 100, + &network::Network::Gnosis, default_token_info_fetcher(), ) .unwrap(); @@ -445,7 +448,7 @@ mod tests { Client::default(), Url::parse(BASE_API_PRO_URL).unwrap(), env::var("COIN_GECKO_API_KEY").ok(), - 100, + &network::Network::Gnosis, default_token_info_fetcher(), ) .unwrap(); @@ -486,7 +489,7 @@ mod tests { Client::default(), Url::parse(BASE_API_PRO_URL).unwrap(), env::var("COIN_GECKO_API_KEY").ok(), - 100, + &network::Network::Gnosis, Arc::new(mock), ) .unwrap(); @@ -534,7 +537,7 @@ mod tests { Client::default(), Url::parse(BASE_API_PRO_URL).unwrap(), env::var("COIN_GECKO_API_KEY").ok(), - 100, + &network::Network::Gnosis, Arc::new(mock), ) .unwrap(); diff --git a/crates/shared/src/signature_validator.rs b/crates/shared/src/signature_validator.rs index 925963d111..93feddfdf3 100644 --- a/crates/shared/src/signature_validator.rs +++ b/crates/shared/src/signature_validator.rs @@ -60,7 +60,6 @@ pub fn check_erc1271_result(result: Bytes<[u8; 4]>) -> Result<(), SignatureValid /// Contracts required for signature verification simulation. pub struct Contracts { - pub chain_id: u64, pub settlement: H160, pub vault_relayer: H160, } diff --git a/crates/shared/src/sources.rs b/crates/shared/src/sources.rs index 3f9a0c0ba8..1716633e33 100644 --- a/crates/shared/src/sources.rs +++ b/crates/shared/src/sources.rs @@ -9,7 +9,7 @@ pub mod uniswap_v3_pair_provider; use { self::uniswap_v2::pool_fetching::{Pool, PoolFetching}, crate::recent_block_cache::Block, - anyhow::{bail, Result}, + anyhow::Result, model::TokenPair, std::{collections::HashSet, sync::Arc}, }; @@ -29,9 +29,9 @@ pub enum BaselineSource { TestnetUniswapV2, } -pub fn defaults_for_chain(chain_id: u64) -> Result> { - Ok(match chain_id { - 1 => vec![ +pub fn defaults_for_network(network: &network::Network) -> Vec { + match network { + network::Network::Mainnet => vec![ BaselineSource::UniswapV2, BaselineSource::SushiSwap, BaselineSource::Swapr, @@ -39,18 +39,18 @@ pub fn defaults_for_chain(chain_id: u64) -> Result> { BaselineSource::ZeroEx, BaselineSource::UniswapV3, ], - 5 => vec![ + network::Network::Goerli => vec![ BaselineSource::UniswapV2, BaselineSource::SushiSwap, BaselineSource::BalancerV2, ], - 100 => vec![ + network::Network::Gnosis => vec![ BaselineSource::Honeyswap, BaselineSource::SushiSwap, BaselineSource::Baoswap, BaselineSource::Swapr, ], - 42161 => vec![ + network::Network::ArbitrumOne => vec![ BaselineSource::UniswapV2, BaselineSource::SushiSwap, BaselineSource::Swapr, @@ -58,9 +58,16 @@ pub fn defaults_for_chain(chain_id: u64) -> Result> { BaselineSource::ZeroEx, BaselineSource::UniswapV3, ], - 11155111 => vec![BaselineSource::TestnetUniswapV2], - _ => bail!("unsupported chain {:#x}", chain_id), - }) + network::Network::Base => vec![ + BaselineSource::UniswapV2, + BaselineSource::SushiSwap, + BaselineSource::Swapr, + BaselineSource::BalancerV2, + BaselineSource::ZeroEx, + BaselineSource::UniswapV3, + ], + network::Network::Sepolia => vec![BaselineSource::TestnetUniswapV2], + } } pub struct PoolAggregator { diff --git a/crates/solvers/Cargo.toml b/crates/solvers/Cargo.toml index cd9cb3d297..2fa7f5bd97 100644 --- a/crates/solvers/Cargo.toml +++ b/crates/solvers/Cargo.toml @@ -25,6 +25,7 @@ hex = { workspace = true } hyper = { workspace = true } itertools = { workspace = true } mimalloc = { workspace = true } +network = { path = "../network" } num = { workspace = true } prometheus = { workspace = true } prometheus-metric-storage = { workspace = true } diff --git a/crates/solvers/src/domain/eth/chain.rs b/crates/solvers/src/domain/eth/chain.rs deleted file mode 100644 index 9a23ccbcfb..0000000000 --- a/crates/solvers/src/domain/eth/chain.rs +++ /dev/null @@ -1,51 +0,0 @@ -use ethereum_types::U256; - -/// A supported Ethereum Chain ID. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum ChainId { - Mainnet = 1, - Goerli = 5, - Gnosis = 100, - Sepolia = 11155111, - ArbitrumOne = 42161, -} - -impl ChainId { - pub fn new(value: U256) -> Result { - // Check to avoid panics for large `U256` values, as there is no checked - // conversion API available and we don't support chains with IDs greater - // than `u64::MAX` anyway. - if value > U256::from(u64::MAX) { - return Err(UnsupportedChain); - } - - match value.as_u64() { - 1 => Ok(Self::Mainnet), - 5 => Ok(Self::Goerli), - 100 => Ok(Self::Gnosis), - 11155111 => Ok(Self::Sepolia), - 42161 => Ok(Self::ArbitrumOne), - _ => Err(UnsupportedChain), - } - } - - /// Returns the network ID for the chain. - pub fn network_id(self) -> &'static str { - match self { - ChainId::Mainnet => "1", - ChainId::Goerli => "5", - ChainId::Gnosis => "100", - ChainId::Sepolia => "11155111", - ChainId::ArbitrumOne => "42161", - } - } - - /// Returns the chain ID as a numeric value. - pub fn value(self) -> U256 { - U256::from(self as u64) - } -} - -#[derive(Debug, thiserror::Error)] -#[error("unsupported chain")] -pub struct UnsupportedChain; diff --git a/crates/solvers/src/domain/eth/mod.rs b/crates/solvers/src/domain/eth/mod.rs index d5f1b970fd..e469fb36ad 100644 --- a/crates/solvers/src/domain/eth/mod.rs +++ b/crates/solvers/src/domain/eth/mod.rs @@ -1,10 +1,5 @@ +pub use ethereum_types::{H160, H256, U256}; use {crate::util::bytes::Bytes, derive_more::From, web3::types::AccessList}; -mod chain; - -pub use { - self::chain::ChainId, - ethereum_types::{H160, H256, U256}, -}; /// A contract address. #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] diff --git a/crates/solvers/src/infra/config/baseline.rs b/crates/solvers/src/infra/config/baseline.rs index 92c6a98245..edf14646b4 100644 --- a/crates/solvers/src/infra/config/baseline.rs +++ b/crates/solvers/src/infra/config/baseline.rs @@ -18,8 +18,7 @@ use { struct Config { /// Optional chain ID. This is used to automatically determine the address /// of the WETH contract. - #[serde_as(as = "Option")] - chain_id: Option, + chain_id: Option, /// Optional WETH contract address. This can be used to specify a manual /// value **instead** of using the canonical WETH contract for the diff --git a/crates/solvers/src/infra/contracts.rs b/crates/solvers/src/infra/contracts.rs index ea79926f70..51b4593d7a 100644 --- a/crates/solvers/src/infra/contracts.rs +++ b/crates/solvers/src/infra/contracts.rs @@ -9,12 +9,12 @@ pub struct Contracts { } impl Contracts { - pub fn for_chain(chain: eth::ChainId) -> Self { + pub fn for_chain(chain: network::Network) -> Self { let a = |contract: &contracts::ethcontract::Contract| { eth::ContractAddress( contract .networks - .get(chain.network_id()) + .get(&chain.chain_id().to_string()) .expect("contract address for all supported chains") .address, ) diff --git a/crates/solvers/src/util/serialize/chain_id.rs b/crates/solvers/src/util/serialize/chain_id.rs deleted file mode 100644 index e60522be49..0000000000 --- a/crates/solvers/src/util/serialize/chain_id.rs +++ /dev/null @@ -1,16 +0,0 @@ -use { - crate::domain::eth, - serde::{de, Deserializer}, - serde_with::DeserializeAs, -}; - -/// Serialize and deserialize [`eth::ChainId`] values. -#[derive(Debug)] -pub struct ChainId; - -impl<'de> DeserializeAs<'de, eth::ChainId> for ChainId { - fn deserialize_as>(deserializer: D) -> Result { - let value = super::U256::deserialize_as(deserializer)?; - eth::ChainId::new(value).map_err(de::Error::custom) - } -} diff --git a/crates/solvers/src/util/serialize/mod.rs b/crates/solvers/src/util/serialize/mod.rs index d8b9876462..40a9110f94 100644 --- a/crates/solvers/src/util/serialize/mod.rs +++ b/crates/solvers/src/util/serialize/mod.rs @@ -1,5 +1,4 @@ -mod chain_id; mod hex; mod u256; -pub use self::{chain_id::ChainId, u256::U256}; +pub use self::u256::U256;