From 79591402e56e92c286bc97f92a7070e61dc49698 Mon Sep 17 00:00:00 2001 From: Mateo Date: Tue, 29 Oct 2024 16:23:12 +0100 Subject: [PATCH 1/8] Unify networks and add Base support --- Cargo.lock | 14 ++ crates/autopilot/Cargo.toml | 1 + crates/autopilot/src/domain/settlement/mod.rs | 18 +- .../src/domain/settlement/observer.rs | 2 +- .../src/infra/blockchain/authenticator.rs | 9 +- .../src/infra/blockchain/contracts.rs | 14 +- crates/autopilot/src/infra/blockchain/id.rs | 43 ---- crates/autopilot/src/infra/blockchain/mod.rs | 26 +-- crates/autopilot/src/run.rs | 26 +-- crates/network/Cargo.toml | 17 ++ crates/network/LICENSE-APACHE | 201 ++++++++++++++++ crates/network/LICENSE-MIT | 22 ++ crates/network/src/lib.rs | 218 ++++++++++++++++++ crates/orderbook/Cargo.toml | 1 + crates/orderbook/src/run.rs | 18 +- crates/shared/Cargo.toml | 1 + crates/shared/src/account_balances.rs | 1 - .../src/bad_token/token_owner_finder.rs | 21 +- .../token_owner_finder/blockscout.rs | 30 ++- .../bad_token/token_owner_finder/ethplorer.rs | 23 +- crates/shared/src/bad_token/trace_call.rs | 7 +- crates/shared/src/lib.rs | 1 - crates/shared/src/network.rs | 15 -- crates/shared/src/price_estimation/factory.rs | 13 +- crates/shared/src/price_estimation/native.rs | 10 - .../src/price_estimation/native/coingecko.rs | 39 ++-- crates/shared/src/signature_validator.rs | 1 - crates/shared/src/sources.rs | 27 ++- crates/solvers/Cargo.toml | 1 + crates/solvers/src/domain/eth/chain.rs | 51 ---- crates/solvers/src/domain/eth/mod.rs | 7 +- crates/solvers/src/infra/config/baseline.rs | 3 +- crates/solvers/src/infra/contracts.rs | 4 +- crates/solvers/src/util/serialize/chain_id.rs | 16 -- crates/solvers/src/util/serialize/mod.rs | 3 +- 35 files changed, 623 insertions(+), 281 deletions(-) delete mode 100644 crates/autopilot/src/infra/blockchain/id.rs create mode 100644 crates/network/Cargo.toml create mode 100644 crates/network/LICENSE-APACHE create mode 100644 crates/network/LICENSE-MIT create mode 100644 crates/network/src/lib.rs delete mode 100644 crates/shared/src/network.rs delete mode 100644 crates/solvers/src/domain/eth/chain.rs delete mode 100644 crates/solvers/src/util/serialize/chain_id.rs 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..4b5c0caea8 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..08d623e609 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, 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..e7a6c4d25a --- /dev/null +++ b/crates/network/Cargo.toml @@ -0,0 +1,17 @@ +[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 } + +[dev-dependencies] +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..1cb51b1681 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::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..34129f12e0 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 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,9 @@ 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::with_network(Client::default(), &network::Network::Mainnet) + .unwrap(); let owners = finder .find_candidate_owners(H160(hex!("1337BedC9D22ecbe766dF105c9623922A27963EC"))) .await; @@ -148,7 +150,9 @@ 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::with_network(Client::default(), &network::Network::Gnosis) + .unwrap(); let owners = finder .find_candidate_owners(H160(hex!("1337BedC9D22ecbe766dF105c9623922A27963EC"))) .await; @@ -158,7 +162,9 @@ 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::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..0f68638b97 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::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; From 5583cf924a12a55b9529f2409e06b8cb7728efa4 Mon Sep 17 00:00:00 2001 From: Mateo Date: Wed, 30 Oct 2024 10:00:16 +0100 Subject: [PATCH 2/8] rework comments --- crates/network/LICENSE-APACHE | 202 +--------------------------------- crates/network/LICENSE-MIT | 23 +--- crates/network/src/lib.rs | 45 ++++---- 3 files changed, 25 insertions(+), 245 deletions(-) mode change 100644 => 120000 crates/network/LICENSE-APACHE mode change 100644 => 120000 crates/network/LICENSE-MIT diff --git a/crates/network/LICENSE-APACHE b/crates/network/LICENSE-APACHE deleted file mode 100644 index 261eeb9e9f..0000000000 --- a/crates/network/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - 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-APACHE b/crates/network/LICENSE-APACHE new file mode 120000 index 0000000000..1cd601d0a3 --- /dev/null +++ b/crates/network/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE \ No newline at end of file diff --git a/crates/network/LICENSE-MIT b/crates/network/LICENSE-MIT deleted file mode 100644 index 65158b9a9b..0000000000 --- a/crates/network/LICENSE-MIT +++ /dev/null @@ -1,22 +0,0 @@ -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/LICENSE-MIT b/crates/network/LICENSE-MIT new file mode 120000 index 0000000000..b2cfbdc7b0 --- /dev/null +++ b/crates/network/LICENSE-MIT @@ -0,0 +1 @@ +../../LICENSE-MIT \ No newline at end of file diff --git a/crates/network/src/lib.rs b/crates/network/src/lib.rs index d3cfa74f7d..0b4935d1e1 100644 --- a/crates/network/src/lib.rs +++ b/crates/network/src/lib.rs @@ -8,6 +8,7 @@ use { /// Represents each available network #[derive(Clone, Copy, Debug, PartialEq)] +#[repr(u64)] pub enum Network { Mainnet = 1, Goerli = 5, @@ -20,14 +21,7 @@ pub enum Network { 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, - } + *self as u64 } /// Returns the canonical name of the network on CoW Protocol. @@ -56,10 +50,10 @@ impl Network { /// 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::Mainnet => 12_000, + Self::Goerli => 12_000, Self::Gnosis => 5_000, - Self::Sepolia => 13_000, + Self::Sepolia => 12_000, Self::ArbitrumOne => 250, Self::Base => 2_000, } @@ -78,15 +72,16 @@ impl TryFrom for Network { /// 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)), - } + let network = match value { + x if x == Self::Mainnet as u64 => Self::Mainnet, + x if x == Self::Goerli as u64 => Self::Goerli, + x if x == Self::Gnosis as u64 => Self::Gnosis, + x if x == Self::Sepolia as u64 => Self::Sepolia, + x if x == Self::ArbitrumOne as u64 => Self::ArbitrumOne, + x if x == Self::Base as u64 => Self::Base, + _ => Err(Error::ChainIdNotSupported)?, + }; + Ok(network) } } @@ -96,6 +91,12 @@ impl TryFrom for Network { /// Initializes `Network` from a chain ID, returns error if the chain id is /// not supported fn try_from(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(Error::ChainIdNotSupported); + } value.as_u64().try_into() } } @@ -136,8 +137,8 @@ impl<'de> Deserialize<'de> for Network { #[derive(Error, Debug)] pub enum Error { - #[error("chain id not supported: {0}")] - ChainIdNotSupported(u64), + #[error("chain id not supported")] + ChainIdNotSupported, } #[cfg(test)] From ccdcea687321edc9cd99b25dcf1563e29d848ccc Mon Sep 17 00:00:00 2001 From: Mateo Date: Wed, 30 Oct 2024 10:14:23 +0100 Subject: [PATCH 3/8] rework comments --- crates/autopilot/src/domain/settlement/mod.rs | 7 ++-- .../src/infra/blockchain/authenticator.rs | 3 +- .../src/infra/blockchain/contracts.rs | 9 ++--- crates/autopilot/src/infra/blockchain/mod.rs | 15 +++++---- crates/autopilot/src/run.rs | 5 +-- crates/network/src/lib.rs | 32 ++++-------------- crates/orderbook/src/run.rs | 3 +- .../src/bad_token/token_owner_finder.rs | 16 ++++----- .../token_owner_finder/blockscout.rs | 24 +++++++------- .../bad_token/token_owner_finder/ethplorer.rs | 23 +++++-------- crates/shared/src/bad_token/trace_call.rs | 3 +- .../src/price_estimation/native/coingecko.rs | 33 ++++++++++--------- crates/shared/src/sources.rs | 15 +++++---- crates/solvers/src/infra/config/baseline.rs | 3 +- crates/solvers/src/infra/contracts.rs | 4 +-- 15 files changed, 88 insertions(+), 107 deletions(-) diff --git a/crates/autopilot/src/domain/settlement/mod.rs b/crates/autopilot/src/domain/settlement/mod.rs index ee77be7119..436f84276c 100644 --- a/crates/autopilot/src/domain/settlement/mod.rs +++ b/crates/autopilot/src/domain/settlement/mod.rs @@ -15,6 +15,7 @@ mod auction; mod observer; mod trade; mod transaction; +use network::Network; pub use {auction::Auction, observer::Observer, trade::Trade, transaction::Transaction}; /// A settled transaction together with the `Auction`, for which it was executed @@ -106,7 +107,7 @@ impl Settlement { pub async fn new( settled: Transaction, persistence: &infra::Persistence, - network: &network::Network, + network: &Network, ) -> Result { let auction = persistence.get_auction(settled.auction_id).await?; @@ -142,9 +143,9 @@ impl Settlement { /// settlement from another environment. /// /// Currently set to ~6h -fn max_settlement_age(network: &network::Network) -> u64 { +fn max_settlement_age(network: &Network) -> u64 { const TARGET_AGE: u64 = 6 * 60 * 60 * 1000; // 6h in ms - network.number_of_blocks_in(TARGET_AGE).round() as u64 + network.blocks_in(TARGET_AGE).round() as u64 } #[derive(Debug, thiserror::Error)] diff --git a/crates/autopilot/src/infra/blockchain/authenticator.rs b/crates/autopilot/src/infra/blockchain/authenticator.rs index 4b5c0caea8..58d070dbff 100644 --- a/crates/autopilot/src/infra/blockchain/authenticator.rs +++ b/crates/autopilot/src/infra/blockchain/authenticator.rs @@ -4,6 +4,7 @@ use { infra::blockchain::contracts::{deployment_address, Contracts}, }, ethcontract::{dyns::DynWeb3, GasPrice}, + network::Network, }; #[allow(dead_code)] @@ -22,7 +23,7 @@ impl Manager { /// Creates an authenticator which can remove solvers from the allow-list pub async fn new( web3: DynWeb3, - network: &network::Network, + network: &Network, contracts: Contracts, authenticator_pk: eth::H256, ) -> Self { diff --git a/crates/autopilot/src/infra/blockchain/contracts.rs b/crates/autopilot/src/infra/blockchain/contracts.rs index 36486a4836..d864f1d73d 100644 --- a/crates/autopilot/src/infra/blockchain/contracts.rs +++ b/crates/autopilot/src/infra/blockchain/contracts.rs @@ -1,4 +1,4 @@ -use {crate::domain, ethcontract::dyns::DynWeb3, primitive_types::H160}; +use {crate::domain, ethcontract::dyns::DynWeb3, network::Network, primitive_types::H160}; #[derive(Debug, Clone)] pub struct Contracts { @@ -20,7 +20,7 @@ pub struct Addresses { } impl Contracts { - pub async fn new(web3: &DynWeb3, network: &network::Network, addresses: Addresses) -> Self { + pub async fn new(web3: &DynWeb3, network: &Network, addresses: Addresses) -> Self { let address_for = |contract: ðcontract::Contract, address: Option| { address .or_else(|| deployment_address(contract, network)) @@ -98,10 +98,7 @@ impl Contracts { /// Returns the address of a contract for the specified network, or `None` if /// there is no known deployment for the contract on that network. -pub fn deployment_address( - contract: ðcontract::Contract, - network: &network::Network, -) -> Option { +pub fn deployment_address(contract: ðcontract::Contract, network: &Network) -> Option { contract .networks .get(&network.chain_id().to_string()) diff --git a/crates/autopilot/src/infra/blockchain/mod.rs b/crates/autopilot/src/infra/blockchain/mod.rs index 08d623e609..226ee38780 100644 --- a/crates/autopilot/src/infra/blockchain/mod.rs +++ b/crates/autopilot/src/infra/blockchain/mod.rs @@ -3,6 +3,7 @@ use { crate::{boundary, domain::eth}, ethcontract::dyns::DynWeb3, ethrpc::block_stream::CurrentBlockWatcher, + network::Network, primitive_types::U256, std::time::Duration, thiserror::Error, @@ -15,7 +16,7 @@ pub mod contracts; /// An Ethereum RPC connection. pub struct Rpc { web3: DynWeb3, - network: network::Network, + network: Network, url: Url, } @@ -27,8 +28,8 @@ impl Rpc { ethrpc_args: &shared::ethrpc::Arguments, ) -> Result { let web3 = boundary::web3_client(url, ethrpc_args); - let network = network::Network::try_from(web3.eth().chain_id().await?) - .map_err(|_| Error::UnsupportedChain)?; + let network = + Network::try_from(web3.eth().chain_id().await?).map_err(|_| Error::UnsupportedChain)?; Ok(Self { web3, @@ -38,7 +39,7 @@ impl Rpc { } /// Returns the chain id for the RPC connection. - pub fn network(&self) -> network::Network { + pub fn network(&self) -> Network { self.network } @@ -57,7 +58,7 @@ impl Rpc { #[derive(Clone)] pub struct Ethereum { web3: DynWeb3, - network: network::Network, + network: Network, current_block: CurrentBlockWatcher, contracts: Contracts, } @@ -71,7 +72,7 @@ impl Ethereum { /// any initialization error. pub async fn new( web3: DynWeb3, - network: &network::Network, + network: &Network, url: Url, addresses: contracts::Addresses, poll_interval: Duration, @@ -88,7 +89,7 @@ impl Ethereum { } } - pub fn network(&self) -> &network::Network { + pub fn network(&self) -> &Network { &self.network } diff --git a/crates/autopilot/src/run.rs b/crates/autopilot/src/run.rs index 14c214dcf8..db39e41734 100644 --- a/crates/autopilot/src/run.rs +++ b/crates/autopilot/src/run.rs @@ -29,6 +29,7 @@ use { ethrpc::block_stream::block_number_to_block_number_hash, futures::StreamExt, model::DomainSeparator, + network::Network, shared::{ account_balances, bad_token::{ @@ -93,7 +94,7 @@ async fn ethrpc(url: &Url, ethrpc_args: &shared::ethrpc::Arguments) -> infra::bl async fn ethereum( web3: DynWeb3, - network: &network::Network, + network: &Network, url: Url, contracts: infra::blockchain::contracts::Addresses, poll_interval: Duration, @@ -197,7 +198,7 @@ pub async fn run(args: Arguments) { other => Some(other.unwrap()), }; - let network = network::Network::try_from(chain_id).unwrap(); + let network = Network::try_from(chain_id).unwrap(); let signature_validator = signature_validator::validator( &web3, diff --git a/crates/network/src/lib.rs b/crates/network/src/lib.rs index 0b4935d1e1..42120a8170 100644 --- a/crates/network/src/lib.rs +++ b/crates/network/src/lib.rs @@ -61,7 +61,7 @@ impl Network { /// 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 { + pub fn blocks_in(&self, time_in_ms: u64) -> f64 { time_in_ms as f64 / self.block_time_in_ms() as f64 } } @@ -149,30 +149,12 @@ mod 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 - ); + assert_eq!(Network::Mainnet.blocks_in(TARGET_AGE).round(), 1662.0); + assert_eq!(Network::Sepolia.blocks_in(TARGET_AGE).round(), 1662.0); + assert_eq!(Network::Goerli.blocks_in(TARGET_AGE).round(), 1662.0); + assert_eq!(Network::Gnosis.blocks_in(TARGET_AGE).round(), 4320.0); + assert_eq!(Network::Base.blocks_in(TARGET_AGE).round(), 10800.0); + assert_eq!(Network::ArbitrumOne.blocks_in(TARGET_AGE).round(), 86400.0); } #[test] diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 9dcc438f0a..ff346ab6be 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -15,6 +15,7 @@ use { ethcontract::errors::DeployError, futures::{FutureExt, StreamExt}, model::{order::BUY_ETH_ADDRESS, DomainSeparator}, + network::Network, order_validation, shared::{ account_balances, @@ -103,7 +104,7 @@ pub async fn run(args: Arguments) { .expect("load native token contract"), }; - let network = network::Network::try_from(chain_id).unwrap(); + let network = Network::try_from(chain_id).unwrap(); let signature_validator = signature_validator::validator( &web3, diff --git a/crates/shared/src/bad_token/token_owner_finder.rs b/crates/shared/src/bad_token/token_owner_finder.rs index 1cb51b1681..b733c3e919 100644 --- a/crates/shared/src/bad_token/token_owner_finder.rs +++ b/crates/shared/src/bad_token/token_owner_finder.rs @@ -34,6 +34,7 @@ use { contracts::{BalancerV2Vault, IUniswapV3Factory, ERC20}, ethcontract::U256, futures::{Stream, StreamExt as _}, + network::Network, primitive_types::H160, rate_limit::Strategy, reqwest::Url, @@ -190,14 +191,13 @@ pub enum TokenOwnerFindingStrategy { impl TokenOwnerFindingStrategy { /// Returns the default set of token owner finding strategies. - pub fn defaults_for_network(network: &network::Network) -> &'static [Self] { + pub fn defaults_for_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], + Network::Mainnet => &[Self::Liquidity, Self::Blockscout, Self::Ethplorer], + Network::Gnosis => &[Self::Liquidity, Self::Blockscout], + Network::Sepolia | Network::Goerli | Network::ArbitrumOne | Network::Base => { + &[Self::Liquidity] + } } } } @@ -273,7 +273,7 @@ impl Display for Arguments { pub async fn init( args: &Arguments, web3: Web3, - network: &network::Network, + network: &Network, http_factory: &HttpClientFactory, pair_providers: &[PairProvider], vault: Option<&BalancerV2Vault>, 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 34129f12e0..d63ec18d16 100644 --- a/crates/shared/src/bad_token/token_owner_finder/blockscout.rs +++ b/crates/shared/src/bad_token/token_owner_finder/blockscout.rs @@ -2,6 +2,7 @@ use { super::TokenOwnerProposing, anyhow::Result, ethcontract::H160, + network::Network, prometheus::IntCounterVec, prometheus_metric_storage::MetricStorage, rate_limit::{back_off, RateLimiter, Strategy}, @@ -17,14 +18,14 @@ pub struct BlockscoutTokenOwnerFinder { } impl BlockscoutTokenOwnerFinder { - pub fn with_network(client: Client, network: &network::Network) -> Result { + pub fn with_network(client: Client, 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", + Network::Mainnet => "https://eth.blockscout.com/api", + Network::Goerli => "https://eth-goerli.blockscout.com/api", + Network::Gnosis => "https://blockscout.com/xdai/mainnet/api", + Network::Sepolia => "https://eth-sepolia.blockscout.com/api", + Network::ArbitrumOne => "https://arbitrum.blockscout.com/api", + Network::Base => "https://base.blockscout.com/api", }; Ok(Self { @@ -139,8 +140,7 @@ mod tests { #[ignore] async fn test_blockscout_token_finding_mainnet() { let finder = - BlockscoutTokenOwnerFinder::with_network(Client::default(), &network::Network::Mainnet) - .unwrap(); + BlockscoutTokenOwnerFinder::with_network(Client::default(), &Network::Mainnet).unwrap(); let owners = finder .find_candidate_owners(H160(hex!("1337BedC9D22ecbe766dF105c9623922A27963EC"))) .await; @@ -151,8 +151,7 @@ mod tests { #[ignore] async fn test_blockscout_token_finding_xdai() { let finder = - BlockscoutTokenOwnerFinder::with_network(Client::default(), &network::Network::Gnosis) - .unwrap(); + BlockscoutTokenOwnerFinder::with_network(Client::default(), &Network::Gnosis).unwrap(); let owners = finder .find_candidate_owners(H160(hex!("1337BedC9D22ecbe766dF105c9623922A27963EC"))) .await; @@ -163,8 +162,7 @@ mod tests { #[ignore] async fn test_blockscout_token_finding_no_owners() { let finder = - BlockscoutTokenOwnerFinder::with_network(Client::default(), &network::Network::Gnosis) - .unwrap(); + BlockscoutTokenOwnerFinder::with_network(Client::default(), &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 16dfffc739..c075c28814 100644 --- a/crates/shared/src/bad_token/token_owner_finder/ethplorer.rs +++ b/crates/shared/src/bad_token/token_owner_finder/ethplorer.rs @@ -2,6 +2,7 @@ use { super::TokenOwnerProposing, anyhow::{ensure, Result}, ethcontract::H160, + network::Network, prometheus::IntCounterVec, prometheus_metric_storage::MetricStorage, rate_limit::{back_off, RateLimiter, Strategy}, @@ -28,10 +29,10 @@ impl EthplorerTokenOwnerFinder { pub fn try_with_network( client: Client, api_key: Option, - network: &network::Network, + network: &Network, ) -> Result { ensure!( - *network == network::Network::Mainnet, + *network == Network::Mainnet, "Ethplorer API unsupported network" ); Ok(Self { @@ -155,12 +156,9 @@ mod tests { #[tokio::test] #[ignore] async fn token_finding_mainnet() { - let finder = EthplorerTokenOwnerFinder::try_with_network( - Client::default(), - None, - &network::Network::Mainnet, - ) - .unwrap(); + let finder = + EthplorerTokenOwnerFinder::try_with_network(Client::default(), None, &Network::Mainnet) + .unwrap(); let owners = finder .find_candidate_owners(H160(hex!("1337BedC9D22ecbe766dF105c9623922A27963EC"))) .await; @@ -170,12 +168,9 @@ mod tests { #[tokio::test] #[ignore] async fn returns_no_owners_on_invalid_token() { - let finder = EthplorerTokenOwnerFinder::try_with_network( - Client::default(), - None, - &network::Network::Gnosis, - ) - .unwrap(); + let finder = + EthplorerTokenOwnerFinder::try_with_network(Client::default(), None, &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 0f68638b97..8082f1f457 100644 --- a/crates/shared/src/bad_token/trace_call.rs +++ b/crates/shared/src/bad_token/trace_call.rs @@ -333,6 +333,7 @@ mod tests { }, contracts::{BalancerV2Vault, IUniswapV3Factory}, hex_literal::hex, + network::Network, std::{env, time::Duration}, web3::types::{ Action, @@ -699,7 +700,7 @@ mod tests { Arc::new( BlockscoutTokenOwnerFinder::with_network( reqwest::Client::new(), - &network::Network::Mainnet, + &Network::Mainnet, ) .unwrap(), ), diff --git a/crates/shared/src/price_estimation/native/coingecko.rs b/crates/shared/src/price_estimation/native/coingecko.rs index 78e545d3ff..b2139122e0 100644 --- a/crates/shared/src/price_estimation/native/coingecko.rs +++ b/crates/shared/src/price_estimation/native/coingecko.rs @@ -6,6 +6,7 @@ use { }, anyhow::{anyhow, Context, Result}, futures::{future::BoxFuture, FutureExt}, + network::Network, primitive_types::H160, reqwest::{Client, StatusCode}, rust_decimal::{prelude::ToPrimitive, Decimal, MathematicalOps}, @@ -53,7 +54,7 @@ impl CoinGecko { client: Client, base_url: Url, api_key: Option, - network: &network::Network, + network: &Network, native_token: H160, token_infos: Arc, ) -> Result { @@ -69,11 +70,11 @@ impl CoinGecko { }; 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 => { + Network::Mainnet => "ethereum".to_string(), + Network::Gnosis => "xdai".to_string(), + Network::ArbitrumOne => "arbitrum-one".to_string(), + Network::Base => "base".to_string(), + Network::Sepolia | Network::Goerli => { anyhow::bail!("unsupported network {}", network.name()) } }; @@ -307,25 +308,25 @@ mod tests { client: Client, base_url: Url, api_key: Option, - network: &network::Network, + network: &Network, token_infos: Arc, ) -> Result { let (chain, denominator) = match network { - network::Network::Mainnet => ( + Network::Mainnet => ( "ethereum".to_string(), Denominator { address: addr!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"), decimals: 18, }, ), - network::Network::Gnosis => ( + Network::Gnosis => ( "xdai".to_string(), Denominator { address: addr!("e91d153e0b41518a2ce8dd3d7944fa863463a97d"), decimals: 18, }, ), - network::Network::ArbitrumOne => ( + Network::ArbitrumOne => ( "arbitrum-one".to_string(), Denominator { address: addr!("82af49447d8a07e3bd95bd0d56f35241523fbab1"), @@ -382,7 +383,7 @@ mod tests { Client::default(), Url::parse(BASE_API_URL).unwrap(), None, - &network::Network::Mainnet, + &Network::Mainnet, default_token_info_fetcher(), ) .unwrap(); @@ -402,7 +403,7 @@ mod tests { Client::default(), Url::parse(BASE_API_PRO_URL).unwrap(), env::var("COIN_GECKO_API_KEY").ok(), - &network::Network::Gnosis, + &Network::Gnosis, default_token_info_fetcher(), ) .unwrap(); @@ -422,7 +423,7 @@ mod tests { Client::default(), Url::parse(BASE_API_PRO_URL).unwrap(), env::var("COIN_GECKO_API_KEY").ok(), - &network::Network::Gnosis, + &Network::Gnosis, default_token_info_fetcher(), ) .unwrap(); @@ -448,7 +449,7 @@ mod tests { Client::default(), Url::parse(BASE_API_PRO_URL).unwrap(), env::var("COIN_GECKO_API_KEY").ok(), - &network::Network::Gnosis, + &Network::Gnosis, default_token_info_fetcher(), ) .unwrap(); @@ -489,7 +490,7 @@ mod tests { Client::default(), Url::parse(BASE_API_PRO_URL).unwrap(), env::var("COIN_GECKO_API_KEY").ok(), - &network::Network::Gnosis, + &Network::Gnosis, Arc::new(mock), ) .unwrap(); @@ -537,7 +538,7 @@ mod tests { Client::default(), Url::parse(BASE_API_PRO_URL).unwrap(), env::var("COIN_GECKO_API_KEY").ok(), - &network::Network::Gnosis, + &Network::Gnosis, Arc::new(mock), ) .unwrap(); diff --git a/crates/shared/src/sources.rs b/crates/shared/src/sources.rs index 1716633e33..6e17ba0f71 100644 --- a/crates/shared/src/sources.rs +++ b/crates/shared/src/sources.rs @@ -11,6 +11,7 @@ use { crate::recent_block_cache::Block, anyhow::Result, model::TokenPair, + network::Network, std::{collections::HashSet, sync::Arc}, }; @@ -29,9 +30,9 @@ pub enum BaselineSource { TestnetUniswapV2, } -pub fn defaults_for_network(network: &network::Network) -> Vec { +pub fn defaults_for_network(network: &Network) -> Vec { match network { - network::Network::Mainnet => vec![ + Network::Mainnet => vec![ BaselineSource::UniswapV2, BaselineSource::SushiSwap, BaselineSource::Swapr, @@ -39,18 +40,18 @@ pub fn defaults_for_network(network: &network::Network) -> Vec { BaselineSource::ZeroEx, BaselineSource::UniswapV3, ], - network::Network::Goerli => vec![ + Network::Goerli => vec![ BaselineSource::UniswapV2, BaselineSource::SushiSwap, BaselineSource::BalancerV2, ], - network::Network::Gnosis => vec![ + Network::Gnosis => vec![ BaselineSource::Honeyswap, BaselineSource::SushiSwap, BaselineSource::Baoswap, BaselineSource::Swapr, ], - network::Network::ArbitrumOne => vec![ + Network::ArbitrumOne => vec![ BaselineSource::UniswapV2, BaselineSource::SushiSwap, BaselineSource::Swapr, @@ -58,7 +59,7 @@ pub fn defaults_for_network(network: &network::Network) -> Vec { BaselineSource::ZeroEx, BaselineSource::UniswapV3, ], - network::Network::Base => vec![ + Network::Base => vec![ BaselineSource::UniswapV2, BaselineSource::SushiSwap, BaselineSource::Swapr, @@ -66,7 +67,7 @@ pub fn defaults_for_network(network: &network::Network) -> Vec { BaselineSource::ZeroEx, BaselineSource::UniswapV3, ], - network::Network::Sepolia => vec![BaselineSource::TestnetUniswapV2], + Network::Sepolia => vec![BaselineSource::TestnetUniswapV2], } } diff --git a/crates/solvers/src/infra/config/baseline.rs b/crates/solvers/src/infra/config/baseline.rs index edf14646b4..a5a22f45c7 100644 --- a/crates/solvers/src/infra/config/baseline.rs +++ b/crates/solvers/src/infra/config/baseline.rs @@ -5,6 +5,7 @@ use { util::serialize, }, ethereum_types::H160, + network::Network, serde::Deserialize, serde_with::serde_as, shared::price_estimation::gas::SETTLEMENT_OVERHEAD, @@ -18,7 +19,7 @@ use { struct Config { /// Optional chain ID. This is used to automatically determine the address /// of the WETH contract. - 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 51b4593d7a..c13a885b62 100644 --- a/crates/solvers/src/infra/contracts.rs +++ b/crates/solvers/src/infra/contracts.rs @@ -1,4 +1,4 @@ -use crate::domain::eth; +use {crate::domain::eth, network::Network}; #[derive(Clone, Debug)] pub struct Contracts { @@ -9,7 +9,7 @@ pub struct Contracts { } impl Contracts { - pub fn for_chain(chain: network::Network) -> Self { + pub fn for_chain(chain: Network) -> Self { let a = |contract: &contracts::ethcontract::Contract| { eth::ContractAddress( contract From f852b45f3130798e53b15b1dbf33497ea9535b28 Mon Sep 17 00:00:00 2001 From: Mateo Date: Wed, 30 Oct 2024 10:29:22 +0100 Subject: [PATCH 4/8] adapt test to latest change --- crates/network/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/network/src/lib.rs b/crates/network/src/lib.rs index 42120a8170..bb13f7c603 100644 --- a/crates/network/src/lib.rs +++ b/crates/network/src/lib.rs @@ -146,12 +146,12 @@ mod test { use {super::*, ethcontract::jsonrpc::serde_json}; #[test] - fn test_number_of_blocks_in() { + fn test_blocks_in() { const TARGET_AGE: u64 = 6 * 60 * 60 * 1000; // 6h in ms - assert_eq!(Network::Mainnet.blocks_in(TARGET_AGE).round(), 1662.0); - assert_eq!(Network::Sepolia.blocks_in(TARGET_AGE).round(), 1662.0); - assert_eq!(Network::Goerli.blocks_in(TARGET_AGE).round(), 1662.0); + assert_eq!(Network::Mainnet.blocks_in(TARGET_AGE).round(), 1800.0); + assert_eq!(Network::Sepolia.blocks_in(TARGET_AGE).round(), 1800.0); + assert_eq!(Network::Goerli.blocks_in(TARGET_AGE).round(), 1800.0); assert_eq!(Network::Gnosis.blocks_in(TARGET_AGE).round(), 4320.0); assert_eq!(Network::Base.blocks_in(TARGET_AGE).round(), 10800.0); assert_eq!(Network::ArbitrumOne.blocks_in(TARGET_AGE).round(), 86400.0); From 51fa5ccec7a392ede0e3520c93cd3182ee520069 Mon Sep 17 00:00:00 2001 From: Mateo Date: Wed, 30 Oct 2024 10:51:12 +0100 Subject: [PATCH 5/8] rework comments --- crates/network/src/lib.rs | 24 ++++++++++--------- crates/shared/src/price_estimation/factory.rs | 8 ++++--- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/crates/network/src/lib.rs b/crates/network/src/lib.rs index bb13f7c603..5b232d50f9 100644 --- a/crates/network/src/lib.rs +++ b/crates/network/src/lib.rs @@ -3,6 +3,7 @@ use { jsonrpc::serde::{de, Deserialize, Deserializer}, U256, }, + std::time::Duration, thiserror::Error, }; @@ -38,31 +39,32 @@ impl Network { } } - pub fn default_amount_to_estimate_native_prices_with(&self) -> Option { + /// The default amount in native tokens atoms to use for price estimation + pub fn default_amount_to_estimate_native_prices_with(&self) -> U256 { match &self { Self::Mainnet | Self::Goerli | Self::Sepolia | Self::ArbitrumOne | Self::Base => { - Some(10u128.pow(18).into()) + 10u128.pow(17).into() } - Self::Gnosis => Some(10u128.pow(21).into()), + Self::Gnosis => 10u128.pow(18).into(), } } /// Returns the block time in milliseconds - pub fn block_time_in_ms(&self) -> u64 { + pub fn block_time_in_ms(&self) -> Duration { match self { - Self::Mainnet => 12_000, - Self::Goerli => 12_000, - Self::Gnosis => 5_000, - Self::Sepolia => 12_000, - Self::ArbitrumOne => 250, - Self::Base => 2_000, + Self::Mainnet => Duration::from_millis(12_000), + Self::Goerli => Duration::from_millis(12_000), + Self::Gnosis => Duration::from_millis(5_000), + Self::Sepolia => Duration::from_millis(12_000), + Self::ArbitrumOne => Duration::from_millis(250), + Self::Base => Duration::from_millis(2_000), } } /// Returns the number of blocks that fits into the given time (in /// milliseconds) pub fn blocks_in(&self, time_in_ms: u64) -> f64 { - time_in_ms as f64 / self.block_time_in_ms() as f64 + time_in_ms as f64 / self.block_time_in_ms().as_millis() as f64 } } diff --git a/crates/shared/src/price_estimation/factory.rs b/crates/shared/src/price_estimation/factory.rs index 3fb91b440b..9e60ba3a4a 100644 --- a/crates/shared/src/price_estimation/factory.rs +++ b/crates/shared/src/price_estimation/factory.rs @@ -127,9 +127,11 @@ impl<'a> PriceEstimatorFactory<'a> { self.args .amount_to_estimate_prices_with .or_else(|| { - self.network - .network - .default_amount_to_estimate_native_prices_with() + Some( + self.network + .network + .default_amount_to_estimate_native_prices_with(), + ) }) .context("No amount to estimate prices with set.")?, ) From 9b2d13e95260fd37948259ed95079281dd4ab074 Mon Sep 17 00:00:00 2001 From: Mateo Date: Wed, 30 Oct 2024 15:15:53 +0100 Subject: [PATCH 6/8] rename --- Cargo.lock | 28 ++++---- crates/autopilot/Cargo.toml | 2 +- crates/autopilot/src/domain/settlement/mod.rs | 10 +-- .../src/domain/settlement/observer.rs | 2 +- .../src/infra/blockchain/authenticator.rs | 6 +- .../src/infra/blockchain/contracts.rs | 14 ++-- crates/autopilot/src/infra/blockchain/mod.rs | 28 ++++---- crates/autopilot/src/run.rs | 16 ++--- crates/{network => chain}/Cargo.toml | 2 +- crates/{network => chain}/LICENSE-APACHE | 0 crates/{network => chain}/LICENSE-MIT | 0 crates/{network => chain}/src/lib.rs | 66 +++++++++---------- crates/orderbook/Cargo.toml | 2 +- crates/orderbook/src/run.rs | 10 +-- crates/shared/Cargo.toml | 2 +- .../src/bad_token/token_owner_finder.rs | 22 +++---- .../token_owner_finder/blockscout.rs | 24 +++---- .../bad_token/token_owner_finder/ethplorer.rs | 10 +-- crates/shared/src/bad_token/trace_call.rs | 4 +- crates/shared/src/price_estimation/factory.rs | 10 +-- .../src/price_estimation/native/coingecko.rs | 40 +++++------ crates/shared/src/sources.rs | 18 ++--- crates/solvers/Cargo.toml | 2 +- crates/solvers/src/infra/config/baseline.rs | 4 +- crates/solvers/src/infra/contracts.rs | 6 +- 25 files changed, 163 insertions(+), 165 deletions(-) rename crates/{network => chain}/Cargo.toml (94%) rename crates/{network => chain}/LICENSE-APACHE (100%) rename crates/{network => chain}/LICENSE-MIT (100%) rename crates/{network => chain}/src/lib.rs (73%) diff --git a/Cargo.lock b/Cargo.lock index d602b65c36..140edab451 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -277,6 +277,7 @@ dependencies = [ "async-trait", "bigdecimal", "bytes-hex", + "chain", "chrono", "clap", "contracts", @@ -297,7 +298,6 @@ dependencies = [ "mimalloc", "mockall 0.12.1", "model", - "network", "num", "number", "observe", @@ -1274,6 +1274,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chain" +version = "0.1.0" +dependencies = [ + "ethcontract", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "chrono" version = "0.4.38" @@ -3138,16 +3148,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "network" -version = "0.1.0" -dependencies = [ - "ethcontract", - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "nom" version = "7.1.3" @@ -3400,6 +3400,7 @@ dependencies = [ "async-trait", "bigdecimal", "cached", + "chain", "chrono", "clap", "contracts", @@ -3416,7 +3417,6 @@ dependencies = [ "mockall 0.12.1", "model", "multibase", - "network", "num", "number", "observe", @@ -4466,6 +4466,7 @@ dependencies = [ "bigdecimal", "bytes-hex", "cached", + "chain", "chrono", "clap", "contracts", @@ -4487,7 +4488,6 @@ dependencies = [ "maplit", "mockall 0.12.1", "model", - "network", "num", "number", "observe", @@ -4626,6 +4626,7 @@ dependencies = [ "anyhow", "axum", "bigdecimal", + "chain", "chrono", "clap", "contracts", @@ -4640,7 +4641,6 @@ dependencies = [ "itertools 0.12.1", "mimalloc", "model", - "network", "num", "observe", "prometheus", diff --git a/crates/autopilot/Cargo.toml b/crates/autopilot/Cargo.toml index ad758711c5..8a28b46688 100644 --- a/crates/autopilot/Cargo.toml +++ b/crates/autopilot/Cargo.toml @@ -20,6 +20,7 @@ bytes-hex = { path = "../bytes-hex" } anyhow = { workspace = true } async-trait = { workspace = true } bigdecimal = { workspace = true } +chain = { path = "../chain" } chrono = { workspace = true } clap = { workspace = true } contracts = { path = "../contracts" } @@ -40,7 +41,6 @@ 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 436f84276c..b910a9ceb5 100644 --- a/crates/autopilot/src/domain/settlement/mod.rs +++ b/crates/autopilot/src/domain/settlement/mod.rs @@ -15,7 +15,7 @@ mod auction; mod observer; mod trade; mod transaction; -use network::Network; +use chain::Chain; pub use {auction::Auction, observer::Observer, trade::Trade, transaction::Transaction}; /// A settled transaction together with the `Auction`, for which it was executed @@ -107,11 +107,11 @@ impl Settlement { pub async fn new( settled: Transaction, persistence: &infra::Persistence, - network: &Network, + chain: &Chain, ) -> Result { let auction = persistence.get_auction(settled.auction_id).await?; - if settled.block > auction.block + max_settlement_age(network) { + if settled.block > auction.block + max_settlement_age(chain) { // A settled transaction references a VERY old auction. // // A hacky way to detect processing of production settlements in the staging @@ -143,9 +143,9 @@ impl Settlement { /// settlement from another environment. /// /// Currently set to ~6h -fn max_settlement_age(network: &Network) -> u64 { +fn max_settlement_age(chain: &Chain) -> u64 { const TARGET_AGE: u64 = 6 * 60 * 60 * 1000; // 6h in ms - network.blocks_in(TARGET_AGE).round() as u64 + chain.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 87bc46807f..631539f9ef 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.network(), + self.eth.chain(), ) .await { diff --git a/crates/autopilot/src/infra/blockchain/authenticator.rs b/crates/autopilot/src/infra/blockchain/authenticator.rs index 58d070dbff..bf0d72faa1 100644 --- a/crates/autopilot/src/infra/blockchain/authenticator.rs +++ b/crates/autopilot/src/infra/blockchain/authenticator.rs @@ -3,8 +3,8 @@ use { domain::{self, eth}, infra::blockchain::contracts::{deployment_address, Contracts}, }, + chain::Chain, ethcontract::{dyns::DynWeb3, GasPrice}, - network::Network, }; #[allow(dead_code)] @@ -23,13 +23,13 @@ impl Manager { /// Creates an authenticator which can remove solvers from the allow-list pub async fn new( web3: DynWeb3, - network: &Network, + chain: &Chain, contracts: Contracts, authenticator_pk: eth::H256, ) -> Self { let authenticator_role = contracts::Roles::at( &web3, - deployment_address(contracts::Roles::raw_contract(), network).expect("roles address"), + deployment_address(contracts::Roles::raw_contract(), chain).expect("roles address"), ); Self { diff --git a/crates/autopilot/src/infra/blockchain/contracts.rs b/crates/autopilot/src/infra/blockchain/contracts.rs index d864f1d73d..d5cccbe0db 100644 --- a/crates/autopilot/src/infra/blockchain/contracts.rs +++ b/crates/autopilot/src/infra/blockchain/contracts.rs @@ -1,4 +1,4 @@ -use {crate::domain, ethcontract::dyns::DynWeb3, network::Network, primitive_types::H160}; +use {crate::domain, chain::Chain, ethcontract::dyns::DynWeb3, primitive_types::H160}; #[derive(Debug, Clone)] pub struct Contracts { @@ -20,10 +20,10 @@ pub struct Addresses { } impl Contracts { - pub async fn new(web3: &DynWeb3, network: &Network, addresses: Addresses) -> Self { + pub async fn new(web3: &DynWeb3, chain: &Chain, addresses: Addresses) -> Self { let address_for = |contract: ðcontract::Contract, address: Option| { address - .or_else(|| deployment_address(contract, network)) + .or_else(|| deployment_address(contract, chain)) .unwrap() }; @@ -96,11 +96,11 @@ impl Contracts { } } -/// Returns the address of a contract for the specified network, or `None` if -/// there is no known deployment for the contract on that network. -pub fn deployment_address(contract: ðcontract::Contract, network: &Network) -> Option { +/// Returns the address of a contract for the specified chain, or `None` if +/// there is no known deployment for the contract on that chain. +pub fn deployment_address(contract: ðcontract::Contract, chain: &Chain) -> Option { contract .networks - .get(&network.chain_id().to_string()) + .get(&chain.id().to_string()) .map(|network| network.address) } diff --git a/crates/autopilot/src/infra/blockchain/mod.rs b/crates/autopilot/src/infra/blockchain/mod.rs index 226ee38780..f030cdf7eb 100644 --- a/crates/autopilot/src/infra/blockchain/mod.rs +++ b/crates/autopilot/src/infra/blockchain/mod.rs @@ -1,9 +1,9 @@ use { self::contracts::Contracts, crate::{boundary, domain::eth}, + chain::Chain, ethcontract::dyns::DynWeb3, ethrpc::block_stream::CurrentBlockWatcher, - network::Network, primitive_types::U256, std::time::Duration, thiserror::Error, @@ -16,7 +16,7 @@ pub mod contracts; /// An Ethereum RPC connection. pub struct Rpc { web3: DynWeb3, - network: Network, + chain: Chain, url: Url, } @@ -28,19 +28,19 @@ impl Rpc { ethrpc_args: &shared::ethrpc::Arguments, ) -> Result { let web3 = boundary::web3_client(url, ethrpc_args); - let network = - Network::try_from(web3.eth().chain_id().await?).map_err(|_| Error::UnsupportedChain)?; + let chain = + Chain::try_from(web3.eth().chain_id().await?).map_err(|_| Error::UnsupportedChain)?; Ok(Self { web3, - network, + chain, url: url.clone(), }) } - /// Returns the chain id for the RPC connection. - pub fn network(&self) -> Network { - self.network + /// Returns the chain for the RPC connection. + pub fn chain(&self) -> Chain { + self.chain } /// Returns a reference to the underlying web3 client. @@ -58,7 +58,7 @@ impl Rpc { #[derive(Clone)] pub struct Ethereum { web3: DynWeb3, - network: Network, + chain: Chain, current_block: CurrentBlockWatcher, contracts: Contracts, } @@ -72,25 +72,25 @@ impl Ethereum { /// any initialization error. pub async fn new( web3: DynWeb3, - network: &Network, + chain: &Chain, url: Url, addresses: contracts::Addresses, poll_interval: Duration, ) -> Self { - let contracts = Contracts::new(&web3, network, addresses).await; + let contracts = Contracts::new(&web3, chain, addresses).await; Self { current_block: ethrpc::block_stream::current_block_stream(url, poll_interval) .await .expect("couldn't initialize current block stream"), web3, - network: *network, + chain: *chain, contracts, } } - pub fn network(&self) -> &Network { - &self.network + pub fn chain(&self) -> &Chain { + &self.chain } /// 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 db39e41734..47ee310714 100644 --- a/crates/autopilot/src/run.rs +++ b/crates/autopilot/src/run.rs @@ -23,13 +23,13 @@ use { shadow, solvable_orders::SolvableOrdersCache, }, + chain::Chain, clap::Parser, contracts::{BalancerV2Vault, IUniswapV3Factory}, ethcontract::{dyns::DynWeb3, errors::DeployError, BlockNumber}, ethrpc::block_stream::block_number_to_block_number_hash, futures::StreamExt, model::DomainSeparator, - network::Network, shared::{ account_balances, bad_token::{ @@ -94,12 +94,12 @@ async fn ethrpc(url: &Url, ethrpc_args: &shared::ethrpc::Arguments) -> infra::bl async fn ethereum( web3: DynWeb3, - network: &Network, + chain: &Chain, url: Url, contracts: infra::blockchain::contracts::Addresses, poll_interval: Duration, ) -> infra::Ethereum { - infra::Ethereum::new(web3, network, url, contracts, poll_interval).await + infra::Ethereum::new(web3, chain, url, contracts, poll_interval).await } pub async fn start(args: impl Iterator) { @@ -157,7 +157,7 @@ pub async fn run(args: Arguments) { } let ethrpc = ethrpc(&args.shared.node_url, &args.shared.ethrpc).await; - let chain = ethrpc.network(); + let chain = ethrpc.chain(); let web3 = ethrpc.web3().clone(); let url = ethrpc.url().clone(); let contracts = infra::blockchain::contracts::Addresses { @@ -198,7 +198,7 @@ pub async fn run(args: Arguments) { other => Some(other.unwrap()), }; - let network = Network::try_from(chain_id).unwrap(); + let chain = Chain::try_from(chain_id).unwrap(); let signature_validator = signature_validator::validator( &web3, @@ -233,7 +233,7 @@ pub async fn run(args: Arguments) { .shared .baseline_sources .clone() - .unwrap_or_else(|| shared::sources::defaults_for_network(&network)); + .unwrap_or_else(|| shared::sources::defaults_for_network(&chain)); tracing::info!(?baseline_sources, "using baseline sources"); let univ2_sources = baseline_sources .iter() @@ -261,7 +261,7 @@ pub async fn run(args: Arguments) { let finder = token_owner_finder::init( &args.token_owner_finder, web3.clone(), - &network, + &chain, &http_factory, &pair_providers, vault.as_ref(), @@ -312,7 +312,7 @@ pub async fn run(args: Arguments) { factory::Network { web3: web3.clone(), simulation_web3, - network, + chain, native_token: eth.contracts().weth().address(), settlement: eth.contracts().settlement().address(), authenticator: eth diff --git a/crates/network/Cargo.toml b/crates/chain/Cargo.toml similarity index 94% rename from crates/network/Cargo.toml rename to crates/chain/Cargo.toml index e7a6c4d25a..34e6d08df9 100644 --- a/crates/network/Cargo.toml +++ b/crates/chain/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "network" +name = "chain" version = "0.1.0" authors = ["Cow Protocol Developers "] edition = "2021" diff --git a/crates/network/LICENSE-APACHE b/crates/chain/LICENSE-APACHE similarity index 100% rename from crates/network/LICENSE-APACHE rename to crates/chain/LICENSE-APACHE diff --git a/crates/network/LICENSE-MIT b/crates/chain/LICENSE-MIT similarity index 100% rename from crates/network/LICENSE-MIT rename to crates/chain/LICENSE-MIT diff --git a/crates/network/src/lib.rs b/crates/chain/src/lib.rs similarity index 73% rename from crates/network/src/lib.rs rename to crates/chain/src/lib.rs index 5b232d50f9..f3dae36b98 100644 --- a/crates/network/src/lib.rs +++ b/crates/chain/src/lib.rs @@ -7,10 +7,10 @@ use { thiserror::Error, }; -/// Represents each available network +/// Represents each available chain #[derive(Clone, Copy, Debug, PartialEq)] #[repr(u64)] -pub enum Network { +pub enum Chain { Mainnet = 1, Goerli = 5, Gnosis = 100, @@ -19,15 +19,15 @@ pub enum Network { Base = 8453, } -impl Network { - /// Returns the network's chain ID - pub fn chain_id(&self) -> u64 { +impl Chain { + /// Returns the chain's chain ID + pub fn id(&self) -> u64 { *self as u64 } - /// Returns the canonical name of the network on CoW Protocol. + /// Returns the canonical name of the chain on CoW Protocol. pub fn name(&self) -> &'static str { - // You can find a list of available networks by network and chain id here: + // You can find a list of available networks by chain and chain id here: // https://chainid.network/chains.json match &self { Self::Mainnet => "Ethereum / Mainnet", @@ -68,7 +68,7 @@ impl Network { } } -impl TryFrom for Network { +impl TryFrom for Chain { type Error = Error; /// Initializes `Network` from a chain ID, returns error if the chain id is @@ -87,7 +87,7 @@ impl TryFrom for Network { } } -impl TryFrom for Network { +impl TryFrom for Chain { type Error = Error; /// Initializes `Network` from a chain ID, returns error if the chain id is @@ -103,7 +103,7 @@ impl TryFrom for Network { } } -impl<'de> Deserialize<'de> for Network { +impl<'de> Deserialize<'de> for Chain { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, @@ -111,7 +111,7 @@ impl<'de> Deserialize<'de> for Network { struct NetworkVisitor; impl<'de> de::Visitor<'de> for NetworkVisitor { - type Value = Network; + type Value = Chain; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("a u64 or a string") @@ -121,14 +121,14 @@ impl<'de> Deserialize<'de> for Network { where E: de::Error, { - Network::try_from(value).map_err(de::Error::custom) + Chain::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)?) + Chain::try_from(value.parse::().map_err(de::Error::custom)?) .map_err(de::Error::custom) } } @@ -151,32 +151,32 @@ mod test { fn test_blocks_in() { const TARGET_AGE: u64 = 6 * 60 * 60 * 1000; // 6h in ms - assert_eq!(Network::Mainnet.blocks_in(TARGET_AGE).round(), 1800.0); - assert_eq!(Network::Sepolia.blocks_in(TARGET_AGE).round(), 1800.0); - assert_eq!(Network::Goerli.blocks_in(TARGET_AGE).round(), 1800.0); - assert_eq!(Network::Gnosis.blocks_in(TARGET_AGE).round(), 4320.0); - assert_eq!(Network::Base.blocks_in(TARGET_AGE).round(), 10800.0); - assert_eq!(Network::ArbitrumOne.blocks_in(TARGET_AGE).round(), 86400.0); + assert_eq!(Chain::Mainnet.blocks_in(TARGET_AGE).round(), 1800.0); + assert_eq!(Chain::Sepolia.blocks_in(TARGET_AGE).round(), 1800.0); + assert_eq!(Chain::Goerli.blocks_in(TARGET_AGE).round(), 1800.0); + assert_eq!(Chain::Gnosis.blocks_in(TARGET_AGE).round(), 4320.0); + assert_eq!(Chain::Base.blocks_in(TARGET_AGE).round(), 10800.0); + assert_eq!(Chain::ArbitrumOne.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 network: Chain = serde_json::from_str(json_data).unwrap(); + assert_eq!(network, Chain::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 network: Chain = serde_json::from_str(json_data).unwrap(); + assert_eq!(network, Chain::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); + let network: Chain = serde_json::from_str(json_data).unwrap(); + assert_eq!(network, Chain::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); + let result: Result = serde_json::from_str(json_data); assert!(result.is_err()); } @@ -184,20 +184,20 @@ mod 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 network: Chain = serde_json::from_str(json_data).unwrap(); + assert_eq!(network, Chain::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 network: Chain = serde_json::from_str(json_data).unwrap(); + assert_eq!(network, Chain::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); + let network: Chain = serde_json::from_str(json_data).unwrap(); + assert_eq!(network, Chain::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); + 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 660912ec46..ddb3b05a7c 100644 --- a/crates/orderbook/Cargo.toml +++ b/crates/orderbook/Cargo.toml @@ -22,6 +22,7 @@ app-data-hash = { path = "../app-data-hash" } async-trait = { workspace = true } bigdecimal = { workspace = true } cached = { workspace = true } +chain = { path = "../chain" } chrono = { workspace = true, features = ["clock"] } clap = { workspace = true } contracts = { path = "../contracts" } @@ -37,7 +38,6 @@ 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 ff346ab6be..6d29719637 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -10,12 +10,12 @@ use { }, anyhow::{anyhow, Context, Result}, app_data::Validator, + chain::Chain, clap::Parser, contracts::{BalancerV2Vault, GPv2Settlement, HooksTrampoline, IUniswapV3Factory, WETH9}, ethcontract::errors::DeployError, futures::{FutureExt, StreamExt}, model::{order::BUY_ETH_ADDRESS, DomainSeparator}, - network::Network, order_validation, shared::{ account_balances, @@ -104,7 +104,7 @@ pub async fn run(args: Arguments) { .expect("load native token contract"), }; - let network = Network::try_from(chain_id).unwrap(); + let chain = Chain::try_from(chain_id).unwrap(); let signature_validator = signature_validator::validator( &web3, @@ -165,7 +165,7 @@ pub async fn run(args: Arguments) { .shared .baseline_sources .clone() - .unwrap_or_else(|| sources::defaults_for_network(&network)); + .unwrap_or_else(|| sources::defaults_for_network(&chain)); tracing::info!(?baseline_sources, "using baseline sources"); let univ2_sources = baseline_sources .iter() @@ -198,7 +198,7 @@ pub async fn run(args: Arguments) { let finder = token_owner_finder::init( &args.token_owner_finder, web3.clone(), - &network, + &chain, &http_factory, &pair_providers, vault.as_ref(), @@ -255,7 +255,7 @@ pub async fn run(args: Arguments) { factory::Network { web3: web3.clone(), simulation_web3, - network, + chain, 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 ab2696dc7e..01dce3e1d9 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -16,6 +16,7 @@ bytes-hex = { path = "../bytes-hex" } async-trait = { workspace = true } bigdecimal = { workspace = true } cached = { workspace = true } +chain = { path = "../chain" } chrono = { workspace = true, features = ["clock"] } clap = { workspace = true } contracts = { path = "../contracts" } @@ -38,7 +39,6 @@ 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/bad_token/token_owner_finder.rs b/crates/shared/src/bad_token/token_owner_finder.rs index b733c3e919..7438959cb0 100644 --- a/crates/shared/src/bad_token/token_owner_finder.rs +++ b/crates/shared/src/bad_token/token_owner_finder.rs @@ -31,10 +31,10 @@ use { sources::uniswap_v2::pair_provider::PairProvider, }, anyhow::{Context, Result}, + chain::Chain, contracts::{BalancerV2Vault, IUniswapV3Factory, ERC20}, ethcontract::U256, futures::{Stream, StreamExt as _}, - network::Network, primitive_types::H160, rate_limit::Strategy, reqwest::Url, @@ -191,13 +191,11 @@ pub enum TokenOwnerFindingStrategy { impl TokenOwnerFindingStrategy { /// Returns the default set of token owner finding strategies. - pub fn defaults_for_network(network: &Network) -> &'static [Self] { - match network { - Network::Mainnet => &[Self::Liquidity, Self::Blockscout, Self::Ethplorer], - Network::Gnosis => &[Self::Liquidity, Self::Blockscout], - Network::Sepolia | Network::Goerli | Network::ArbitrumOne | Network::Base => { - &[Self::Liquidity] - } + pub fn defaults_for_network(chain: &Chain) -> &'static [Self] { + match chain { + Chain::Mainnet => &[Self::Liquidity, Self::Blockscout, Self::Ethplorer], + Chain::Gnosis => &[Self::Liquidity, Self::Blockscout], + Chain::Sepolia | Chain::Goerli | Chain::ArbitrumOne | Chain::Base => &[Self::Liquidity], } } } @@ -273,7 +271,7 @@ impl Display for Arguments { pub async fn init( args: &Arguments, web3: Web3, - network: &Network, + chain: &Chain, http_factory: &HttpClientFactory, pair_providers: &[PairProvider], vault: Option<&BalancerV2Vault>, @@ -285,7 +283,7 @@ pub async fn init( let finders = args .token_owner_finders .as_deref() - .unwrap_or_else(|| TokenOwnerFindingStrategy::defaults_for_network(network)); + .unwrap_or_else(|| TokenOwnerFindingStrategy::defaults_for_network(chain)); tracing::debug!(?finders, "initializing token owner finders"); let mut proposers = Vec::>::new(); @@ -318,7 +316,7 @@ pub async fn init( if finders.contains(&TokenOwnerFindingStrategy::Blockscout) { let mut blockscout = - BlockscoutTokenOwnerFinder::with_network(http_factory.create(), network)?; + BlockscoutTokenOwnerFinder::with_network(http_factory.create(), chain)?; 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()); @@ -335,7 +333,7 @@ pub async fn init( args.ethplorer .as_ref() .map(|ethplorer| ethplorer.ethplorer_api_key.clone()), - network, + chain, )?; 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 d63ec18d16..d8fdf4ddd7 100644 --- a/crates/shared/src/bad_token/token_owner_finder/blockscout.rs +++ b/crates/shared/src/bad_token/token_owner_finder/blockscout.rs @@ -1,8 +1,8 @@ use { super::TokenOwnerProposing, anyhow::Result, + chain::Chain, ethcontract::H160, - network::Network, prometheus::IntCounterVec, prometheus_metric_storage::MetricStorage, rate_limit::{back_off, RateLimiter, Strategy}, @@ -18,14 +18,14 @@ pub struct BlockscoutTokenOwnerFinder { } impl BlockscoutTokenOwnerFinder { - pub fn with_network(client: Client, network: &Network) -> Result { - let base_url = match network { - Network::Mainnet => "https://eth.blockscout.com/api", - Network::Goerli => "https://eth-goerli.blockscout.com/api", - Network::Gnosis => "https://blockscout.com/xdai/mainnet/api", - Network::Sepolia => "https://eth-sepolia.blockscout.com/api", - Network::ArbitrumOne => "https://arbitrum.blockscout.com/api", - Network::Base => "https://base.blockscout.com/api", + pub fn with_network(client: Client, chain: &Chain) -> Result { + let base_url = match chain { + Chain::Mainnet => "https://eth.blockscout.com/api", + Chain::Goerli => "https://eth-goerli.blockscout.com/api", + Chain::Gnosis => "https://blockscout.com/xdai/mainnet/api", + Chain::Sepolia => "https://eth-sepolia.blockscout.com/api", + Chain::ArbitrumOne => "https://arbitrum.blockscout.com/api", + Chain::Base => "https://base.blockscout.com/api", }; Ok(Self { @@ -140,7 +140,7 @@ mod tests { #[ignore] async fn test_blockscout_token_finding_mainnet() { let finder = - BlockscoutTokenOwnerFinder::with_network(Client::default(), &Network::Mainnet).unwrap(); + BlockscoutTokenOwnerFinder::with_network(Client::default(), &Chain::Mainnet).unwrap(); let owners = finder .find_candidate_owners(H160(hex!("1337BedC9D22ecbe766dF105c9623922A27963EC"))) .await; @@ -151,7 +151,7 @@ mod tests { #[ignore] async fn test_blockscout_token_finding_xdai() { let finder = - BlockscoutTokenOwnerFinder::with_network(Client::default(), &Network::Gnosis).unwrap(); + BlockscoutTokenOwnerFinder::with_network(Client::default(), &Chain::Gnosis).unwrap(); let owners = finder .find_candidate_owners(H160(hex!("1337BedC9D22ecbe766dF105c9623922A27963EC"))) .await; @@ -162,7 +162,7 @@ mod tests { #[ignore] async fn test_blockscout_token_finding_no_owners() { let finder = - BlockscoutTokenOwnerFinder::with_network(Client::default(), &Network::Gnosis).unwrap(); + BlockscoutTokenOwnerFinder::with_network(Client::default(), &Chain::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 c075c28814..89355b1310 100644 --- a/crates/shared/src/bad_token/token_owner_finder/ethplorer.rs +++ b/crates/shared/src/bad_token/token_owner_finder/ethplorer.rs @@ -1,8 +1,8 @@ use { super::TokenOwnerProposing, anyhow::{ensure, Result}, + chain::Chain, ethcontract::H160, - network::Network, prometheus::IntCounterVec, prometheus_metric_storage::MetricStorage, rate_limit::{back_off, RateLimiter, Strategy}, @@ -29,10 +29,10 @@ impl EthplorerTokenOwnerFinder { pub fn try_with_network( client: Client, api_key: Option, - network: &Network, + chain: &Chain, ) -> Result { ensure!( - *network == Network::Mainnet, + *chain == Chain::Mainnet, "Ethplorer API unsupported network" ); Ok(Self { @@ -157,7 +157,7 @@ mod tests { #[ignore] async fn token_finding_mainnet() { let finder = - EthplorerTokenOwnerFinder::try_with_network(Client::default(), None, &Network::Mainnet) + EthplorerTokenOwnerFinder::try_with_network(Client::default(), None, &Chain::Mainnet) .unwrap(); let owners = finder .find_candidate_owners(H160(hex!("1337BedC9D22ecbe766dF105c9623922A27963EC"))) @@ -169,7 +169,7 @@ mod tests { #[ignore] async fn returns_no_owners_on_invalid_token() { let finder = - EthplorerTokenOwnerFinder::try_with_network(Client::default(), None, &Network::Gnosis) + EthplorerTokenOwnerFinder::try_with_network(Client::default(), None, &Chain::Gnosis) .unwrap(); let owners = finder .find_candidate_owners(H160(hex!("000000000000000000000000000000000000def1"))) diff --git a/crates/shared/src/bad_token/trace_call.rs b/crates/shared/src/bad_token/trace_call.rs index 8082f1f457..00da8feb3c 100644 --- a/crates/shared/src/bad_token/trace_call.rs +++ b/crates/shared/src/bad_token/trace_call.rs @@ -331,9 +331,9 @@ mod tests { ethrpc::create_env_test_transport, sources::{uniswap_v2, BaselineSource}, }, + chain::Chain, contracts::{BalancerV2Vault, IUniswapV3Factory}, hex_literal::hex, - network::Network, std::{env, time::Duration}, web3::types::{ Action, @@ -700,7 +700,7 @@ mod tests { Arc::new( BlockscoutTokenOwnerFinder::with_network( reqwest::Client::new(), - &Network::Mainnet, + &Chain::Mainnet, ) .unwrap(), ), diff --git a/crates/shared/src/price_estimation/factory.rs b/crates/shared/src/price_estimation/factory.rs index 9e60ba3a4a..03868ff1b1 100644 --- a/crates/shared/src/price_estimation/factory.rs +++ b/crates/shared/src/price_estimation/factory.rs @@ -56,7 +56,7 @@ struct EstimatorEntry { pub struct Network { pub web3: Web3, pub simulation_web3: Option, - pub network: network::Network, + pub chain: chain::Chain, pub native_token: H160, pub settlement: H160, pub authenticator: H160, @@ -101,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.network.chain_id())); + .map(|t| TenderlyCodeSimulator::new(t, network.chain.id())); let simulator: Arc = match tenderly { Some(tenderly) => Arc::new(code_simulation::Web3ThenTenderly::new( @@ -129,7 +129,7 @@ impl<'a> PriceEstimatorFactory<'a> { .or_else(|| { Some( self.network - .network + .chain .default_amount_to_estimate_native_prices_with(), ) }) @@ -211,7 +211,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.network.chain_id(), + self.network.chain.id(), self.network.block_stream.clone(), self.components.tokens.clone(), ), @@ -225,7 +225,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.network, + &self.network.chain, weth.address(), self.components.tokens.clone(), ) diff --git a/crates/shared/src/price_estimation/native/coingecko.rs b/crates/shared/src/price_estimation/native/coingecko.rs index b2139122e0..49d33cf10e 100644 --- a/crates/shared/src/price_estimation/native/coingecko.rs +++ b/crates/shared/src/price_estimation/native/coingecko.rs @@ -5,8 +5,8 @@ use { token_info::{TokenInfo, TokenInfoFetching}, }, anyhow::{anyhow, Context, Result}, + chain::Chain, futures::{future::BoxFuture, FutureExt}, - network::Network, primitive_types::H160, reqwest::{Client, StatusCode}, rust_decimal::{prelude::ToPrimitive, Decimal, MathematicalOps}, @@ -54,7 +54,7 @@ impl CoinGecko { client: Client, base_url: Url, api_key: Option, - network: &Network, + chain: &Chain, native_token: H160, token_infos: Arc, ) -> Result { @@ -69,13 +69,13 @@ impl CoinGecko { decimals: denominator_decimals, }; - let chain = match network { - Network::Mainnet => "ethereum".to_string(), - Network::Gnosis => "xdai".to_string(), - Network::ArbitrumOne => "arbitrum-one".to_string(), - Network::Base => "base".to_string(), - Network::Sepolia | Network::Goerli => { - anyhow::bail!("unsupported network {}", network.name()) + let chain = match chain { + Chain::Mainnet => "ethereum".to_string(), + Chain::Gnosis => "xdai".to_string(), + Chain::ArbitrumOne => "arbitrum-one".to_string(), + Chain::Base => "base".to_string(), + Chain::Sepolia | Chain::Goerli => { + anyhow::bail!("unsupported network {}", chain.name()) } }; Ok(Self { @@ -308,25 +308,25 @@ mod tests { client: Client, base_url: Url, api_key: Option, - network: &Network, + chain: &Chain, token_infos: Arc, ) -> Result { - let (chain, denominator) = match network { - Network::Mainnet => ( + let (chain, denominator) = match chain { + Chain::Mainnet => ( "ethereum".to_string(), Denominator { address: addr!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"), decimals: 18, }, ), - Network::Gnosis => ( + Chain::Gnosis => ( "xdai".to_string(), Denominator { address: addr!("e91d153e0b41518a2ce8dd3d7944fa863463a97d"), decimals: 18, }, ), - Network::ArbitrumOne => ( + Chain::ArbitrumOne => ( "arbitrum-one".to_string(), Denominator { address: addr!("82af49447d8a07e3bd95bd0d56f35241523fbab1"), @@ -383,7 +383,7 @@ mod tests { Client::default(), Url::parse(BASE_API_URL).unwrap(), None, - &Network::Mainnet, + &Chain::Mainnet, default_token_info_fetcher(), ) .unwrap(); @@ -403,7 +403,7 @@ mod tests { Client::default(), Url::parse(BASE_API_PRO_URL).unwrap(), env::var("COIN_GECKO_API_KEY").ok(), - &Network::Gnosis, + &Chain::Gnosis, default_token_info_fetcher(), ) .unwrap(); @@ -423,7 +423,7 @@ mod tests { Client::default(), Url::parse(BASE_API_PRO_URL).unwrap(), env::var("COIN_GECKO_API_KEY").ok(), - &Network::Gnosis, + &Chain::Gnosis, default_token_info_fetcher(), ) .unwrap(); @@ -449,7 +449,7 @@ mod tests { Client::default(), Url::parse(BASE_API_PRO_URL).unwrap(), env::var("COIN_GECKO_API_KEY").ok(), - &Network::Gnosis, + &Chain::Gnosis, default_token_info_fetcher(), ) .unwrap(); @@ -490,7 +490,7 @@ mod tests { Client::default(), Url::parse(BASE_API_PRO_URL).unwrap(), env::var("COIN_GECKO_API_KEY").ok(), - &Network::Gnosis, + &Chain::Gnosis, Arc::new(mock), ) .unwrap(); @@ -538,7 +538,7 @@ mod tests { Client::default(), Url::parse(BASE_API_PRO_URL).unwrap(), env::var("COIN_GECKO_API_KEY").ok(), - &Network::Gnosis, + &Chain::Gnosis, Arc::new(mock), ) .unwrap(); diff --git a/crates/shared/src/sources.rs b/crates/shared/src/sources.rs index 6e17ba0f71..eed9bbc5cc 100644 --- a/crates/shared/src/sources.rs +++ b/crates/shared/src/sources.rs @@ -10,8 +10,8 @@ use { self::uniswap_v2::pool_fetching::{Pool, PoolFetching}, crate::recent_block_cache::Block, anyhow::Result, + chain::Chain, model::TokenPair, - network::Network, std::{collections::HashSet, sync::Arc}, }; @@ -30,9 +30,9 @@ pub enum BaselineSource { TestnetUniswapV2, } -pub fn defaults_for_network(network: &Network) -> Vec { - match network { - Network::Mainnet => vec![ +pub fn defaults_for_network(chain: &Chain) -> Vec { + match chain { + Chain::Mainnet => vec![ BaselineSource::UniswapV2, BaselineSource::SushiSwap, BaselineSource::Swapr, @@ -40,18 +40,18 @@ pub fn defaults_for_network(network: &Network) -> Vec { BaselineSource::ZeroEx, BaselineSource::UniswapV3, ], - Network::Goerli => vec![ + Chain::Goerli => vec![ BaselineSource::UniswapV2, BaselineSource::SushiSwap, BaselineSource::BalancerV2, ], - Network::Gnosis => vec![ + Chain::Gnosis => vec![ BaselineSource::Honeyswap, BaselineSource::SushiSwap, BaselineSource::Baoswap, BaselineSource::Swapr, ], - Network::ArbitrumOne => vec![ + Chain::ArbitrumOne => vec![ BaselineSource::UniswapV2, BaselineSource::SushiSwap, BaselineSource::Swapr, @@ -59,7 +59,7 @@ pub fn defaults_for_network(network: &Network) -> Vec { BaselineSource::ZeroEx, BaselineSource::UniswapV3, ], - Network::Base => vec![ + Chain::Base => vec![ BaselineSource::UniswapV2, BaselineSource::SushiSwap, BaselineSource::Swapr, @@ -67,7 +67,7 @@ pub fn defaults_for_network(network: &Network) -> Vec { BaselineSource::ZeroEx, BaselineSource::UniswapV3, ], - Network::Sepolia => vec![BaselineSource::TestnetUniswapV2], + Chain::Sepolia => vec![BaselineSource::TestnetUniswapV2], } } diff --git a/crates/solvers/Cargo.toml b/crates/solvers/Cargo.toml index 2fa7f5bd97..54eb233926 100644 --- a/crates/solvers/Cargo.toml +++ b/crates/solvers/Cargo.toml @@ -15,6 +15,7 @@ path = "src/main.rs" [dependencies] axum = { workspace = true } bigdecimal = { version = "0.3", features = ["serde"] } +chain = { path = "../chain" } chrono = { workspace = true, features = ["serde"], default-features = false } clap = { workspace = true, features = ["derive", "env"] } derive_more = { workspace = true } @@ -25,7 +26,6 @@ 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/infra/config/baseline.rs b/crates/solvers/src/infra/config/baseline.rs index a5a22f45c7..354d383bff 100644 --- a/crates/solvers/src/infra/config/baseline.rs +++ b/crates/solvers/src/infra/config/baseline.rs @@ -4,8 +4,8 @@ use { infra::{config::unwrap_or_log, contracts}, util::serialize, }, + chain::Chain, ethereum_types::H160, - network::Network, serde::Deserialize, serde_with::serde_as, shared::price_estimation::gas::SETTLEMENT_OVERHEAD, @@ -19,7 +19,7 @@ use { struct Config { /// Optional chain ID. This is used to automatically determine the address /// of the WETH contract. - 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 c13a885b62..d0707d7bf4 100644 --- a/crates/solvers/src/infra/contracts.rs +++ b/crates/solvers/src/infra/contracts.rs @@ -1,4 +1,4 @@ -use {crate::domain::eth, network::Network}; +use {crate::domain::eth, chain::Chain}; #[derive(Clone, Debug)] pub struct Contracts { @@ -9,12 +9,12 @@ pub struct Contracts { } impl Contracts { - pub fn for_chain(chain: Network) -> Self { + pub fn for_chain(chain: Chain) -> Self { let a = |contract: &contracts::ethcontract::Contract| { eth::ContractAddress( contract .networks - .get(&chain.chain_id().to_string()) + .get(&chain.id().to_string()) .expect("contract address for all supported chains") .address, ) From 489fc70f188bf50e0f9e2ca523aa53d1b388d149 Mon Sep 17 00:00:00 2001 From: Mateo Date: Thu, 31 Oct 2024 10:11:04 +0100 Subject: [PATCH 7/8] rework comments --- Cargo.lock | 2 + crates/autopilot/src/run.rs | 2 +- crates/chain/Cargo.toml | 1 + crates/chain/src/lib.rs | 37 ++++++++++++++----- crates/driver/Cargo.toml | 1 + .../src/domain/competition/order/signature.rs | 2 +- crates/driver/src/domain/eth/eip712.rs | 4 +- crates/driver/src/domain/eth/mod.rs | 18 --------- .../driver/src/infra/blockchain/contracts.rs | 4 +- crates/driver/src/infra/blockchain/mod.rs | 13 ++++--- crates/driver/src/infra/config/file/load.rs | 7 +++- crates/driver/src/infra/liquidity/config.rs | 18 ++++----- crates/driver/src/infra/simulator/enso/mod.rs | 4 +- crates/orderbook/src/run.rs | 2 +- crates/shared/src/price_estimation/factory.rs | 2 +- 15 files changed, 62 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 140edab451..24526a3e81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1278,6 +1278,7 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" name = "chain" version = "0.1.0" dependencies = [ + "derive_more", "ethcontract", "serde", "serde_json", @@ -1777,6 +1778,7 @@ dependencies = [ "axum", "bigdecimal", "bytes-hex", + "chain", "chrono", "clap", "contracts", diff --git a/crates/autopilot/src/run.rs b/crates/autopilot/src/run.rs index 47ee310714..6a07d6ac08 100644 --- a/crates/autopilot/src/run.rs +++ b/crates/autopilot/src/run.rs @@ -198,7 +198,7 @@ pub async fn run(args: Arguments) { other => Some(other.unwrap()), }; - let chain = Chain::try_from(chain_id).unwrap(); + let chain = Chain::try_from(chain_id).expect("incorrect chain ID"); let signature_validator = signature_validator::validator( &web3, diff --git a/crates/chain/Cargo.toml b/crates/chain/Cargo.toml index 34e6d08df9..954f1f7b37 100644 --- a/crates/chain/Cargo.toml +++ b/crates/chain/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" license = "MIT OR Apache-2.0" [dependencies] +derive_more = { workspace = true } ethcontract = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } diff --git a/crates/chain/src/lib.rs b/crates/chain/src/lib.rs index f3dae36b98..ab598bee8b 100644 --- a/crates/chain/src/lib.rs +++ b/crates/chain/src/lib.rs @@ -1,4 +1,5 @@ use { + derive_more::{From, Into}, ethcontract::{ jsonrpc::serde::{de, Deserialize, Deserializer}, U256, @@ -21,8 +22,8 @@ pub enum Chain { impl Chain { /// Returns the chain's chain ID - pub fn id(&self) -> u64 { - *self as u64 + pub fn id(&self) -> Id { + Id(*self as u64) } /// Returns the canonical name of the chain on CoW Protocol. @@ -69,7 +70,7 @@ impl Chain { } impl TryFrom for Chain { - type Error = Error; + type Error = ChainIdNotSupported; /// Initializes `Network` from a chain ID, returns error if the chain id is /// not supported @@ -81,14 +82,26 @@ impl TryFrom for Chain { x if x == Self::Sepolia as u64 => Self::Sepolia, x if x == Self::ArbitrumOne as u64 => Self::ArbitrumOne, x if x == Self::Base as u64 => Self::Base, - _ => Err(Error::ChainIdNotSupported)?, + _ => Err(ChainIdNotSupported)?, }; Ok(network) } } +/// Chain ID as defined by EIP-155. +/// +/// https://eips.ethereum.org/EIPS/eip-155 +#[derive(Clone, Copy, Debug, Eq, PartialEq, From, Into)] +pub struct Id(u64); + +impl std::fmt::Display for Id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + impl TryFrom for Chain { - type Error = Error; + type Error = ChainIdNotSupported; /// Initializes `Network` from a chain ID, returns error if the chain id is /// not supported @@ -97,7 +110,7 @@ impl TryFrom for Chain { // conversion API available, and we don't support chains with IDs greater // than `u64::MAX` anyway. if value > U256::from(u64::MAX) { - return Err(Error::ChainIdNotSupported); + return Err(ChainIdNotSupported); } value.as_u64().try_into() } @@ -138,10 +151,8 @@ impl<'de> Deserialize<'de> for Chain { } #[derive(Error, Debug)] -pub enum Error { - #[error("chain id not supported")] - ChainIdNotSupported, -} +#[error("chain id not supported")] +pub struct ChainIdNotSupported; #[cfg(test)] mod test { @@ -200,4 +211,10 @@ mod test { let result: Result = serde_json::from_str(json_data); assert!(result.is_err()); } + + #[test] + fn test_try() { + let id = Id(3); + assert_eq!(id.to_string(), "1".to_string()); + } } diff --git a/crates/driver/Cargo.toml b/crates/driver/Cargo.toml index 254257d1c0..874cc5a008 100644 --- a/crates/driver/Cargo.toml +++ b/crates/driver/Cargo.toml @@ -17,6 +17,7 @@ path = "src/main.rs" [dependencies] app-data = { path = "../app-data" } bytes-hex = { path = "../bytes-hex" } +chain = { path = "../chain" } s3 = { path = "../s3" } async-trait = { workspace = true } axum = { workspace = true } diff --git a/crates/driver/src/domain/competition/order/signature.rs b/crates/driver/src/domain/competition/order/signature.rs index b77b8a3edd..31e4e62c04 100644 --- a/crates/driver/src/domain/competition/order/signature.rs +++ b/crates/driver/src/domain/competition/order/signature.rs @@ -43,7 +43,7 @@ pub enum Scheme { } pub fn domain_separator( - chain_id: eth::ChainId, + chain_id: chain::Id, verifying_contract: eth::ContractAddress, ) -> eth::DomainSeparator { eth::DomainSeparator::new(ð::DomainFields { diff --git a/crates/driver/src/domain/eth/eip712.rs b/crates/driver/src/domain/eth/eip712.rs index 002a1816f7..4a68c6bbcd 100644 --- a/crates/driver/src/domain/eth/eip712.rs +++ b/crates/driver/src/domain/eth/eip712.rs @@ -13,7 +13,7 @@ pub struct DomainFields { pub type_hash: &'static [u8], pub name: &'static [u8], pub version: &'static [u8], - pub chain_id: super::ChainId, + pub chain_id: chain::Id, pub verifying_contract: super::ContractAddress, } @@ -23,7 +23,7 @@ impl DomainSeparator { ethabi::Token::Uint(web3::signing::keccak256(fields.type_hash).into()), ethabi::Token::Uint(web3::signing::keccak256(fields.name).into()), ethabi::Token::Uint(web3::signing::keccak256(fields.version).into()), - ethabi::Token::Uint(ethereum_types::U256::from(fields.chain_id.0)), + ethabi::Token::Uint(ethereum_types::U256::from(u64::from(fields.chain_id))), ethabi::Token::Address(fields.verifying_contract.into()), ]); Self(web3::signing::keccak256(abi_string.as_slice())) diff --git a/crates/driver/src/domain/eth/mod.rs b/crates/driver/src/domain/eth/mod.rs index bdf548d32c..bac4776a42 100644 --- a/crates/driver/src/domain/eth/mod.rs +++ b/crates/driver/src/domain/eth/mod.rs @@ -29,24 +29,6 @@ pub use { /// ERC20 token. pub const ETH_TOKEN: TokenAddress = TokenAddress(ContractAddress(H160([0xee; 20]))); -/// Chain ID as defined by EIP-155. -/// -/// https://eips.ethereum.org/EIPS/eip-155 -#[derive(Clone, Copy, Debug, Eq, PartialEq, Into)] -pub struct ChainId(pub u64); - -impl std::fmt::Display for ChainId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From for ChainId { - fn from(value: U256) -> Self { - Self(value.as_u64()) - } -} - /// An EIP-2930 access list. This type ensures that the addresses and storage /// keys are not repeated, and that the ordering is deterministic. /// diff --git a/crates/driver/src/infra/blockchain/contracts.rs b/crates/driver/src/infra/blockchain/contracts.rs index 8575cd58df..835ff66130 100644 --- a/crates/driver/src/infra/blockchain/contracts.rs +++ b/crates/driver/src/infra/blockchain/contracts.rs @@ -28,7 +28,7 @@ pub struct Addresses { impl Contracts { pub(super) async fn new( web3: &DynWeb3, - chain: eth::ChainId, + chain: chain::Id, addresses: Addresses, block_stream: CurrentBlockWatcher, archive_node_url: Option<&Url>, @@ -130,7 +130,7 @@ pub struct CowAmmConfig { /// there is no known deployment for the contract on that network. pub fn deployment_address( contract: ðcontract::Contract, - network_id: eth::ChainId, + network_id: chain::Id, ) -> Option { Some( contract diff --git a/crates/driver/src/infra/blockchain/mod.rs b/crates/driver/src/infra/blockchain/mod.rs index 9ca350e6a4..9d89606f89 100644 --- a/crates/driver/src/infra/blockchain/mod.rs +++ b/crates/driver/src/infra/blockchain/mod.rs @@ -1,6 +1,7 @@ use { self::contracts::ContractAt, crate::{boundary, domain::eth}, + chain::Chain, ethcontract::dyns::DynWeb3, ethrpc::block_stream::CurrentBlockWatcher, std::{fmt, sync::Arc}, @@ -20,7 +21,7 @@ pub use self::{contracts::Contracts, gas::GasPriceEstimator}; /// An Ethereum RPC connection. pub struct Rpc { web3: DynWeb3, - chain: eth::ChainId, + chain: chain::Id, url: Url, } @@ -29,17 +30,17 @@ impl Rpc { /// at the specifed URL. pub async fn new(url: &url::Url) -> Result { let web3 = boundary::buffered_web3_client(url); - let chain = web3.eth().chain_id().await?.into(); + let chain = Chain::try_from(web3.eth().chain_id().await?).expect("invalid chain ID"); Ok(Self { web3, - chain, + chain: chain.id(), url: url.clone(), }) } /// Returns the chain id for the RPC connection. - pub fn chain(&self) -> eth::ChainId { + pub fn chain(&self) -> chain::Id { self.chain } @@ -57,7 +58,7 @@ pub struct Ethereum { } struct Inner { - chain: eth::ChainId, + chain: chain::Id, contracts: Contracts, gas: Arc, current_block: CurrentBlockWatcher, @@ -104,7 +105,7 @@ impl Ethereum { } } - pub fn network(&self) -> eth::ChainId { + pub fn network(&self) -> chain::Id { self.inner.chain } diff --git a/crates/driver/src/infra/config/file/load.rs b/crates/driver/src/infra/config/file/load.rs index ef338d14f4..7c5628e28e 100644 --- a/crates/driver/src/infra/config/file/load.rs +++ b/crates/driver/src/infra/config/file/load.rs @@ -23,7 +23,7 @@ use { /// # Panics /// /// This method panics if the config is invalid or on I/O errors. -pub async fn load(chain: eth::ChainId, path: &Path) -> infra::Config { +pub async fn load(chain: chain::Id, path: &Path) -> infra::Config { let data = fs::read_to_string(path) .await .unwrap_or_else(|e| panic!("I/O error while reading {path:?}: {e:?}")); @@ -40,7 +40,10 @@ pub async fn load(chain: eth::ChainId, path: &Path) -> infra::Config { }); assert_eq!( - config.chain_id.map(eth::ChainId).unwrap_or(chain), + config + .chain_id + .map(|id| chain::Id::from(id)) + .unwrap_or(chain), chain, "The configured chain ID does not match connected Ethereum node" ); diff --git a/crates/driver/src/infra/liquidity/config.rs b/crates/driver/src/infra/liquidity/config.rs index 1527db6c6d..7d2dbf59c1 100644 --- a/crates/driver/src/infra/liquidity/config.rs +++ b/crates/driver/src/infra/liquidity/config.rs @@ -49,7 +49,7 @@ pub struct UniswapV2 { impl UniswapV2 { /// Returns the liquidity configuration for Uniswap V2. #[allow(clippy::self_named_constructors)] - pub fn uniswap_v2(chain: eth::ChainId) -> Option { + pub fn uniswap_v2(chain: chain::Id) -> Option { Some(Self { router: deployment_address(contracts::UniswapV2Router02::raw_contract(), chain)?, pool_code: hex!("96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f") @@ -59,7 +59,7 @@ impl UniswapV2 { } /// Returns the liquidity configuration for SushiSwap. - pub fn sushi_swap(chain: eth::ChainId) -> Option { + pub fn sushi_swap(chain: chain::Id) -> Option { Some(Self { router: deployment_address(contracts::SushiSwapRouter::raw_contract(), chain)?, pool_code: hex!("e18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303") @@ -69,7 +69,7 @@ impl UniswapV2 { } /// Returns the liquidity configuration for Honeyswap. - pub fn honeyswap(chain: eth::ChainId) -> Option { + pub fn honeyswap(chain: chain::Id) -> Option { Some(Self { router: deployment_address(contracts::HoneyswapRouter::raw_contract(), chain)?, pool_code: hex!("3f88503e8580ab941773b59034fb4b2a63e86dbc031b3633a925533ad3ed2b93") @@ -79,7 +79,7 @@ impl UniswapV2 { } /// Returns the liquidity configuration for Baoswap. - pub fn baoswap(chain: eth::ChainId) -> Option { + pub fn baoswap(chain: chain::Id) -> Option { Some(Self { router: deployment_address(contracts::BaoswapRouter::raw_contract(), chain)?, pool_code: hex!("0bae3ead48c325ce433426d2e8e6b07dac10835baec21e163760682ea3d3520d") @@ -89,7 +89,7 @@ impl UniswapV2 { } /// Returns the liquidity configuration for PancakeSwap. - pub fn pancake_swap(chain: eth::ChainId) -> Option { + pub fn pancake_swap(chain: chain::Id) -> Option { Some(Self { router: deployment_address(contracts::PancakeRouter::raw_contract(), chain)?, pool_code: hex!("57224589c67f3f30a6b0d7a1b54cf3153ab84563bc609ef41dfb34f8b2974d2d") @@ -100,7 +100,7 @@ impl UniswapV2 { /// Returns the liquidity configuration for liquidity sources only used on /// test networks. - pub fn testnet_uniswapv2(chain: eth::ChainId) -> Option { + pub fn testnet_uniswapv2(chain: chain::Id) -> Option { Some(Self { router: deployment_address(contracts::TestnetUniswapV2Router02::raw_contract(), chain)?, pool_code: hex!("0efd7612822d579e24a8851501d8c2ad854264a1050e3dfcee8afcca08f80a86") @@ -126,7 +126,7 @@ pub struct Swapr { impl Swapr { /// Returns the liquidity configuration for Swapr. #[allow(clippy::self_named_constructors)] - pub fn swapr(chain: eth::ChainId) -> Option { + pub fn swapr(chain: chain::Id) -> Option { Some(Self { router: deployment_address(contracts::SwaprRouter::raw_contract(), chain)?, pool_code: hex!("d306a548755b9295ee49cc729e13ca4a45e00199bbd890fa146da43a50571776") @@ -152,7 +152,7 @@ pub struct UniswapV3 { impl UniswapV3 { /// Returns the liquidity configuration for Uniswap V3. #[allow(clippy::self_named_constructors)] - pub fn uniswap_v3(graph_url: &Url, chain: eth::ChainId) -> Option { + pub fn uniswap_v3(graph_url: &Url, chain: chain::Id) -> Option { Some(Self { router: deployment_address(contracts::UniswapV3SwapRouter::raw_contract(), chain)?, max_pools_to_initialize: 100, @@ -196,7 +196,7 @@ pub struct BalancerV2 { impl BalancerV2 { /// Returns the liquidity configuration for Balancer V2. #[allow(clippy::self_named_constructors)] - pub fn balancer_v2(graph_url: &Url, chain: eth::ChainId) -> Option { + pub fn balancer_v2(graph_url: &Url, chain: chain::Id) -> Option { let factory_addresses = |contracts: &[ðcontract::Contract]| -> Vec { contracts diff --git a/crates/driver/src/infra/simulator/enso/mod.rs b/crates/driver/src/infra/simulator/enso/mod.rs index 15a9460884..1a91569846 100644 --- a/crates/driver/src/infra/simulator/enso/mod.rs +++ b/crates/driver/src/infra/simulator/enso/mod.rs @@ -13,7 +13,7 @@ const GAS_LIMIT: u64 = 30_000_000; #[derive(Debug, Clone)] pub(super) struct Enso { url: reqwest::Url, - chain_id: eth::ChainId, + chain_id: chain::Id, current_block: CurrentBlockWatcher, network_block_interval: Option, } @@ -29,7 +29,7 @@ pub struct Config { impl Enso { pub(super) fn new( config: Config, - chain_id: eth::ChainId, + chain_id: chain::Id, current_block: CurrentBlockWatcher, ) -> Self { Self { diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 6d29719637..3d9febb24c 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -104,7 +104,7 @@ pub async fn run(args: Arguments) { .expect("load native token contract"), }; - let chain = Chain::try_from(chain_id).unwrap(); + let chain = Chain::try_from(chain_id).expect("incorrect chain ID"); let signature_validator = signature_validator::validator( &web3, diff --git a/crates/shared/src/price_estimation/factory.rs b/crates/shared/src/price_estimation/factory.rs index 03868ff1b1..6019356cf9 100644 --- a/crates/shared/src/price_estimation/factory.rs +++ b/crates/shared/src/price_estimation/factory.rs @@ -211,7 +211,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.chain.id().into(), self.network.block_stream.clone(), self.components.tokens.clone(), ), From a6f8c610296a55039b94236008593e5c071b2bdd Mon Sep 17 00:00:00 2001 From: Mateo Date: Thu, 31 Oct 2024 10:13:49 +0100 Subject: [PATCH 8/8] nit --- crates/chain/src/lib.rs | 12 ++++++------ crates/driver/src/infra/blockchain/mod.rs | 5 ++--- crates/driver/src/infra/config/file/load.rs | 5 +---- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/crates/chain/src/lib.rs b/crates/chain/src/lib.rs index ab598bee8b..6fdb43931e 100644 --- a/crates/chain/src/lib.rs +++ b/crates/chain/src/lib.rs @@ -94,6 +94,12 @@ impl TryFrom for Chain { #[derive(Clone, Copy, Debug, Eq, PartialEq, From, Into)] pub struct Id(u64); +impl From for Id { + fn from(value: U256) -> Self { + Self(value.as_u64()) + } +} + impl std::fmt::Display for Id { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) @@ -211,10 +217,4 @@ mod test { let result: Result = serde_json::from_str(json_data); assert!(result.is_err()); } - - #[test] - fn test_try() { - let id = Id(3); - assert_eq!(id.to_string(), "1".to_string()); - } } diff --git a/crates/driver/src/infra/blockchain/mod.rs b/crates/driver/src/infra/blockchain/mod.rs index 9d89606f89..9115d203a0 100644 --- a/crates/driver/src/infra/blockchain/mod.rs +++ b/crates/driver/src/infra/blockchain/mod.rs @@ -1,7 +1,6 @@ use { self::contracts::ContractAt, crate::{boundary, domain::eth}, - chain::Chain, ethcontract::dyns::DynWeb3, ethrpc::block_stream::CurrentBlockWatcher, std::{fmt, sync::Arc}, @@ -30,11 +29,11 @@ impl Rpc { /// at the specifed URL. pub async fn new(url: &url::Url) -> Result { let web3 = boundary::buffered_web3_client(url); - let chain = Chain::try_from(web3.eth().chain_id().await?).expect("invalid chain ID"); + let chain = web3.eth().chain_id().await?.into(); Ok(Self { web3, - chain: chain.id(), + chain, url: url.clone(), }) } diff --git a/crates/driver/src/infra/config/file/load.rs b/crates/driver/src/infra/config/file/load.rs index 7c5628e28e..b235106448 100644 --- a/crates/driver/src/infra/config/file/load.rs +++ b/crates/driver/src/infra/config/file/load.rs @@ -40,10 +40,7 @@ pub async fn load(chain: chain::Id, path: &Path) -> infra::Config { }); assert_eq!( - config - .chain_id - .map(|id| chain::Id::from(id)) - .unwrap_or(chain), + config.chain_id.map(chain::Id::from).unwrap_or(chain), chain, "The configured chain ID does not match connected Ethereum node" );