diff --git a/Cargo.lock b/Cargo.lock index 0c82dd5395..24526a3e81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -277,6 +277,7 @@ dependencies = [ "async-trait", "bigdecimal", "bytes-hex", + "chain", "chrono", "clap", "contracts", @@ -1273,6 +1274,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chain" +version = "0.1.0" +dependencies = [ + "derive_more", + "ethcontract", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "chrono" version = "0.4.38" @@ -1766,6 +1778,7 @@ dependencies = [ "axum", "bigdecimal", "bytes-hex", + "chain", "chrono", "clap", "contracts", @@ -3389,6 +3402,7 @@ dependencies = [ "async-trait", "bigdecimal", "cached", + "chain", "chrono", "clap", "contracts", @@ -4454,6 +4468,7 @@ dependencies = [ "bigdecimal", "bytes-hex", "cached", + "chain", "chrono", "clap", "contracts", @@ -4613,6 +4628,7 @@ dependencies = [ "anyhow", "axum", "bigdecimal", + "chain", "chrono", "clap", "contracts", diff --git a/crates/autopilot/Cargo.toml b/crates/autopilot/Cargo.toml index ffa3a4901a..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" } diff --git a/crates/autopilot/src/domain/settlement/mod.rs b/crates/autopilot/src/domain/settlement/mod.rs index 6199a6e661..b910a9ceb5 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 chain::Chain; 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, - chain: &infra::blockchain::Id, + chain: &Chain, ) -> Result { let auction = persistence.get_auction(settled.auction_id).await?; @@ -138,23 +139,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(chain: &Chain) -> 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, - } + chain.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 4ffe0a3caf..bf0d72faa1 100644 --- a/crates/autopilot/src/infra/blockchain/authenticator.rs +++ b/crates/autopilot/src/infra/blockchain/authenticator.rs @@ -1,11 +1,9 @@ use { crate::{ domain::{self, eth}, - infra::blockchain::{ - self, - contracts::{deployment_address, Contracts}, - }, + infra::blockchain::contracts::{deployment_address, Contracts}, }, + chain::Chain, ethcontract::{dyns::DynWeb3, GasPrice}, }; @@ -25,13 +23,13 @@ impl Manager { /// Creates an authenticator which can remove solvers from the allow-list pub async fn new( web3: DynWeb3, - chain: blockchain::Id, + chain: &Chain, 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(), chain).expect("roles address"), ); Self { diff --git a/crates/autopilot/src/infra/blockchain/contracts.rs b/crates/autopilot/src/infra/blockchain/contracts.rs index 0db3f67a66..d5cccbe0db 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, chain::Chain, ethcontract::dyns::DynWeb3, primitive_types::H160}; #[derive(Debug, Clone)] pub struct Contracts { @@ -24,7 +20,7 @@ pub struct Addresses { } impl Contracts { - pub async fn new(web3: &DynWeb3, chain: &blockchain::Id, 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, chain)) @@ -100,14 +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, - chain: &blockchain::Id, -) -> 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(chain.network_id()) + .get(&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..f030cdf7eb 100644 --- a/crates/autopilot/src/infra/blockchain/mod.rs +++ b/crates/autopilot/src/infra/blockchain/mod.rs @@ -1,6 +1,7 @@ use { self::contracts::Contracts, crate::{boundary, domain::eth}, + chain::Chain, ethcontract::dyns::DynWeb3, ethrpc::block_stream::CurrentBlockWatcher, primitive_types::U256, @@ -11,14 +12,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, + chain: Chain, url: Url, } @@ -30,7 +28,8 @@ 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 chain = + Chain::try_from(web3.eth().chain_id().await?).map_err(|_| Error::UnsupportedChain)?; Ok(Self { web3, @@ -39,8 +38,8 @@ impl Rpc { }) } - /// Returns the chain id for the RPC connection. - pub fn chain(&self) -> Id { + /// Returns the chain for the RPC connection. + pub fn chain(&self) -> Chain { self.chain } @@ -59,7 +58,7 @@ impl Rpc { #[derive(Clone)] pub struct Ethereum { web3: DynWeb3, - chain: Id, + chain: Chain, current_block: CurrentBlockWatcher, contracts: Contracts, } @@ -73,24 +72,24 @@ impl Ethereum { /// any initialization error. pub async fn new( web3: DynWeb3, - chain: Id, + chain: &Chain, url: Url, addresses: contracts::Addresses, poll_interval: Duration, ) -> Self { - let contracts = Contracts::new(&web3, &chain, 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, - chain, + chain: *chain, contracts, } } - pub fn chain(&self) -> &Id { + pub fn chain(&self) -> &Chain { &self.chain } diff --git a/crates/autopilot/src/run.rs b/crates/autopilot/src/run.rs index c6747413b7..6a07d6ac08 100644 --- a/crates/autopilot/src/run.rs +++ b/crates/autopilot/src/run.rs @@ -23,6 +23,7 @@ use { shadow, solvable_orders::SolvableOrdersCache, }, + chain::Chain, clap::Parser, contracts::{BalancerV2Vault, IUniswapV3Factory}, ethcontract::{dyns::DynWeb3, errors::DeployError, BlockNumber}, @@ -93,7 +94,7 @@ async fn ethrpc(url: &Url, ethrpc_args: &shared::ethrpc::Arguments) -> infra::bl async fn ethereum( web3: DynWeb3, - chain: infra::blockchain::Id, + chain: &Chain, url: Url, contracts: infra::blockchain::contracts::Addresses, poll_interval: Duration, @@ -165,7 +166,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 +198,11 @@ pub async fn run(args: Arguments) { other => Some(other.unwrap()), }; - let network_name = shared::network::network_name(chain_id); + let chain = Chain::try_from(chain_id).expect("incorrect chain ID"); let signature_validator = signature_validator::validator( &web3, signature_validator::Contracts { - chain_id, settlement: eth.contracts().settlement().address(), vault_relayer, }, @@ -211,7 +211,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 +229,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(&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(), - chain_id, + &chain, &http_factory, &pair_providers, vault.as_ref(), @@ -312,8 +312,7 @@ pub async fn run(args: Arguments) { factory::Network { web3: web3.clone(), simulation_web3, - name: network_name.to_string(), - chain_id, + chain, native_token: eth.contracts().weth().address(), settlement: eth.contracts().settlement().address(), authenticator: eth diff --git a/crates/chain/Cargo.toml b/crates/chain/Cargo.toml new file mode 100644 index 0000000000..954f1f7b37 --- /dev/null +++ b/crates/chain/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "chain" +version = "0.1.0" +authors = ["Cow Protocol Developers "] +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] +derive_more = { workspace = true } +ethcontract = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +serde_json = { workspace = true } + +[lints] +workspace = true diff --git a/crates/chain/LICENSE-APACHE b/crates/chain/LICENSE-APACHE new file mode 120000 index 0000000000..1cd601d0a3 --- /dev/null +++ b/crates/chain/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE \ No newline at end of file diff --git a/crates/chain/LICENSE-MIT b/crates/chain/LICENSE-MIT new file mode 120000 index 0000000000..b2cfbdc7b0 --- /dev/null +++ b/crates/chain/LICENSE-MIT @@ -0,0 +1 @@ +../../LICENSE-MIT \ No newline at end of file diff --git a/crates/chain/src/lib.rs b/crates/chain/src/lib.rs new file mode 100644 index 0000000000..6fdb43931e --- /dev/null +++ b/crates/chain/src/lib.rs @@ -0,0 +1,220 @@ +use { + derive_more::{From, Into}, + ethcontract::{ + jsonrpc::serde::{de, Deserialize, Deserializer}, + U256, + }, + std::time::Duration, + thiserror::Error, +}; + +/// Represents each available chain +#[derive(Clone, Copy, Debug, PartialEq)] +#[repr(u64)] +pub enum Chain { + Mainnet = 1, + Goerli = 5, + Gnosis = 100, + Sepolia = 11155111, + ArbitrumOne = 42161, + Base = 8453, +} + +impl Chain { + /// Returns the chain's chain ID + pub fn id(&self) -> Id { + Id(*self as u64) + } + + /// 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 chain 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", + } + } + + /// 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 => { + 10u128.pow(17).into() + } + Self::Gnosis => 10u128.pow(18).into(), + } + } + + /// Returns the block time in milliseconds + pub fn block_time_in_ms(&self) -> Duration { + match self { + 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_millis() as f64 + } +} + +impl TryFrom for Chain { + type Error = ChainIdNotSupported; + + /// Initializes `Network` from a chain ID, returns error if the chain id is + /// not supported + fn try_from(value: u64) -> Result { + 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(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 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) + } +} + +impl TryFrom for Chain { + type Error = ChainIdNotSupported; + + /// 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(ChainIdNotSupported); + } + value.as_u64().try_into() + } +} + +impl<'de> Deserialize<'de> for Chain { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct NetworkVisitor; + + impl<'de> de::Visitor<'de> for NetworkVisitor { + type Value = Chain; + + 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, + { + Chain::try_from(value).map_err(de::Error::custom) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + Chain::try_from(value.parse::().map_err(de::Error::custom)?) + .map_err(de::Error::custom) + } + } + + deserializer.deserialize_any(NetworkVisitor) + } +} + +#[derive(Error, Debug)] +#[error("chain id not supported")] +pub struct ChainIdNotSupported; + +#[cfg(test)] +mod test { + use {super::*, ethcontract::jsonrpc::serde_json}; + + #[test] + fn test_blocks_in() { + const TARGET_AGE: u64 = 6 * 60 * 60 * 1000; // 6h in ms + + 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: Chain = serde_json::from_str(json_data).unwrap(); + assert_eq!(network, Chain::Mainnet); + + let json_data = "5"; // Should deserialize to 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: 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); + 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: 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: 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: 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); + assert!(result.is_err()); + } +} 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..9115d203a0 100644 --- a/crates/driver/src/infra/blockchain/mod.rs +++ b/crates/driver/src/infra/blockchain/mod.rs @@ -20,7 +20,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, } @@ -39,7 +39,7 @@ impl Rpc { } /// Returns the chain id for the RPC connection. - pub fn chain(&self) -> eth::ChainId { + pub fn chain(&self) -> chain::Id { self.chain } @@ -57,7 +57,7 @@ pub struct Ethereum { } struct Inner { - chain: eth::ChainId, + chain: chain::Id, contracts: Contracts, gas: Arc, current_block: CurrentBlockWatcher, @@ -104,7 +104,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..b235106448 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,7 @@ 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(chain::Id::from).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 72cf1a771e..49fd218ac9 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/Cargo.toml b/crates/orderbook/Cargo.toml index 5d89e6cdab..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" } diff --git a/crates/orderbook/src/run.rs b/crates/orderbook/src/run.rs index 8e87611994..3d9febb24c 100644 --- a/crates/orderbook/src/run.rs +++ b/crates/orderbook/src/run.rs @@ -10,6 +10,7 @@ use { }, anyhow::{anyhow, Context, Result}, app_data::Validator, + chain::Chain, clap::Parser, contracts::{BalancerV2Vault, GPv2Settlement, HooksTrampoline, IUniswapV3Factory, WETH9}, ethcontract::errors::DeployError, @@ -30,7 +31,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 +104,11 @@ pub async fn run(args: Arguments) { .expect("load native token contract"), }; - let network_name = network_name(chain_id); + let chain = Chain::try_from(chain_id).expect("incorrect chain ID"); let signature_validator = signature_validator::validator( &web3, signature_validator::Contracts { - chain_id, settlement: settlement_contract.address(), vault_relayer, }, @@ -145,7 +144,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 +161,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(&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(), - chain_id, + &chain, &http_factory, &pair_providers, vault.as_ref(), @@ -255,8 +255,7 @@ pub async fn run(args: Arguments) { factory::Network { web3: web3.clone(), simulation_web3, - name: network_name.to_string(), - chain_id, + 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 756a6d835e..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" } 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..7438959cb0 100644 --- a/crates/shared/src/bad_token/token_owner_finder.rs +++ b/crates/shared/src/bad_token/token_owner_finder.rs @@ -31,6 +31,7 @@ use { sources::uniswap_v2::pair_provider::PairProvider, }, anyhow::{Context, Result}, + chain::Chain, contracts::{BalancerV2Vault, IUniswapV3Factory, ERC20}, ethcontract::U256, futures::{Stream, StreamExt as _}, @@ -190,11 +191,11 @@ 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(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], } } } @@ -270,7 +271,7 @@ impl Display for Arguments { pub async fn init( args: &Arguments, web3: Web3, - chain_id: u64, + chain: &Chain, http_factory: &HttpClientFactory, pair_providers: &[PairProvider], vault: Option<&BalancerV2Vault>, @@ -282,7 +283,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(chain)); tracing::debug!(?finders, "initializing token owner finders"); let mut proposers = Vec::>::new(); @@ -315,7 +316,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(), 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()); @@ -332,7 +333,7 @@ pub async fn init( args.ethplorer .as_ref() .map(|ethplorer| ethplorer.ethplorer_api_key.clone()), - chain_id, + 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 b294826cab..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,6 +1,7 @@ use { super::TokenOwnerProposing, - anyhow::{bail, Result}, + anyhow::Result, + chain::Chain, ethcontract::H160, prometheus::IntCounterVec, prometheus_metric_storage::MetricStorage, @@ -17,14 +18,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, 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 { @@ -138,7 +139,8 @@ 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(), &Chain::Mainnet).unwrap(); let owners = finder .find_candidate_owners(H160(hex!("1337BedC9D22ecbe766dF105c9623922A27963EC"))) .await; @@ -148,7 +150,8 @@ 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(), &Chain::Gnosis).unwrap(); let owners = finder .find_candidate_owners(H160(hex!("1337BedC9D22ecbe766dF105c9623922A27963EC"))) .await; @@ -158,7 +161,8 @@ 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(), &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 63d6d7c86f..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,6 +1,7 @@ use { super::TokenOwnerProposing, anyhow::{ensure, Result}, + chain::Chain, ethcontract::H160, prometheus::IntCounterVec, prometheus_metric_storage::MetricStorage, @@ -28,9 +29,12 @@ impl EthplorerTokenOwnerFinder { pub fn try_with_network( client: Client, api_key: Option, - chain_id: u64, + chain: &Chain, ) -> Result { - ensure!(chain_id == 1, "Ethplorer API unsupported network"); + ensure!( + *chain == Chain::Mainnet, + "Ethplorer API unsupported network" + ); Ok(Self { client, base: Url::try_from(BASE).unwrap(), @@ -153,7 +157,8 @@ mod tests { #[ignore] async fn token_finding_mainnet() { let finder = - EthplorerTokenOwnerFinder::try_with_network(Client::default(), None, 1).unwrap(); + EthplorerTokenOwnerFinder::try_with_network(Client::default(), None, &Chain::Mainnet) + .unwrap(); let owners = finder .find_candidate_owners(H160(hex!("1337BedC9D22ecbe766dF105c9623922A27963EC"))) .await; @@ -164,7 +169,8 @@ mod tests { #[ignore] async fn returns_no_owners_on_invalid_token() { let finder = - EthplorerTokenOwnerFinder::try_with_network(Client::default(), None, 1).unwrap(); + EthplorerTokenOwnerFinder::try_with_network(Client::default(), None, &Chain::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..00da8feb3c 100644 --- a/crates/shared/src/bad_token/trace_call.rs +++ b/crates/shared/src/bad_token/trace_call.rs @@ -331,6 +331,7 @@ mod tests { ethrpc::create_env_test_transport, sources::{uniswap_v2, BaselineSource}, }, + chain::Chain, contracts::{BalancerV2Vault, IUniswapV3Factory}, hex_literal::hex, std::{env, time::Duration}, @@ -697,8 +698,11 @@ mod tests { .unwrap(), ), Arc::new( - BlockscoutTokenOwnerFinder::try_with_network(reqwest::Client::new(), 1) - .unwrap(), + BlockscoutTokenOwnerFinder::with_network( + reqwest::Client::new(), + &Chain::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..6019356cf9 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 chain: chain::Chain, 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.chain.id())); let simulator: Arc = match tenderly { Some(tenderly) => Arc::new(code_simulation::Web3ThenTenderly::new( @@ -128,7 +127,11 @@ 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) + Some( + self.network + .chain + .default_amount_to_estimate_native_prices_with(), + ) }) .context("No amount to estimate prices with set.")?, ) @@ -208,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(), ), @@ -222,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.chain_id, + &self.network.chain, 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..49d33cf10e 100644 --- a/crates/shared/src/price_estimation/native/coingecko.rs +++ b/crates/shared/src/price_estimation/native/coingecko.rs @@ -5,6 +5,7 @@ use { token_info::{TokenInfo, TokenInfoFetching}, }, anyhow::{anyhow, Context, Result}, + chain::Chain, futures::{future::BoxFuture, FutureExt}, primitive_types::H160, reqwest::{Client, StatusCode}, @@ -53,7 +54,7 @@ impl CoinGecko { client: Client, base_url: Url, api_key: Option, - chain_id: u64, + chain: &Chain, native_token: H160, token_infos: Arc, ) -> Result { @@ -68,11 +69,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 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 { client, @@ -304,32 +308,32 @@ mod tests { client: Client, base_url: Url, api_key: Option, - chain_id: u64, + chain: &Chain, token_infos: Arc, ) -> Result { - let (chain, denominator) = match chain_id { - 1 => ( + let (chain, denominator) = match chain { + Chain::Mainnet => ( "ethereum".to_string(), Denominator { address: addr!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"), decimals: 18, }, ), - 100 => ( + Chain::Gnosis => ( "xdai".to_string(), Denominator { address: addr!("e91d153e0b41518a2ce8dd3d7944fa863463a97d"), decimals: 18, }, ), - 42161 => ( + Chain::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 +383,7 @@ mod tests { Client::default(), Url::parse(BASE_API_URL).unwrap(), None, - 1, + &Chain::Mainnet, default_token_info_fetcher(), ) .unwrap(); @@ -399,7 +403,7 @@ mod tests { Client::default(), Url::parse(BASE_API_PRO_URL).unwrap(), env::var("COIN_GECKO_API_KEY").ok(), - 100, + &Chain::Gnosis, default_token_info_fetcher(), ) .unwrap(); @@ -419,7 +423,7 @@ mod tests { Client::default(), Url::parse(BASE_API_PRO_URL).unwrap(), env::var("COIN_GECKO_API_KEY").ok(), - 100, + &Chain::Gnosis, default_token_info_fetcher(), ) .unwrap(); @@ -445,7 +449,7 @@ mod tests { Client::default(), Url::parse(BASE_API_PRO_URL).unwrap(), env::var("COIN_GECKO_API_KEY").ok(), - 100, + &Chain::Gnosis, default_token_info_fetcher(), ) .unwrap(); @@ -486,7 +490,7 @@ mod tests { Client::default(), Url::parse(BASE_API_PRO_URL).unwrap(), env::var("COIN_GECKO_API_KEY").ok(), - 100, + &Chain::Gnosis, Arc::new(mock), ) .unwrap(); @@ -534,7 +538,7 @@ mod tests { Client::default(), Url::parse(BASE_API_PRO_URL).unwrap(), env::var("COIN_GECKO_API_KEY").ok(), - 100, + &Chain::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..eed9bbc5cc 100644 --- a/crates/shared/src/sources.rs +++ b/crates/shared/src/sources.rs @@ -9,7 +9,8 @@ pub mod uniswap_v3_pair_provider; use { self::uniswap_v2::pool_fetching::{Pool, PoolFetching}, crate::recent_block_cache::Block, - anyhow::{bail, Result}, + anyhow::Result, + chain::Chain, model::TokenPair, std::{collections::HashSet, sync::Arc}, }; @@ -29,9 +30,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(chain: &Chain) -> Vec { + match chain { + Chain::Mainnet => vec![ BaselineSource::UniswapV2, BaselineSource::SushiSwap, BaselineSource::Swapr, @@ -39,18 +40,18 @@ pub fn defaults_for_chain(chain_id: u64) -> Result> { BaselineSource::ZeroEx, BaselineSource::UniswapV3, ], - 5 => vec![ + Chain::Goerli => vec![ BaselineSource::UniswapV2, BaselineSource::SushiSwap, BaselineSource::BalancerV2, ], - 100 => vec![ + Chain::Gnosis => vec![ BaselineSource::Honeyswap, BaselineSource::SushiSwap, BaselineSource::Baoswap, BaselineSource::Swapr, ], - 42161 => vec![ + Chain::ArbitrumOne => vec![ BaselineSource::UniswapV2, BaselineSource::SushiSwap, BaselineSource::Swapr, @@ -58,9 +59,16 @@ pub fn defaults_for_chain(chain_id: u64) -> Result> { BaselineSource::ZeroEx, BaselineSource::UniswapV3, ], - 11155111 => vec![BaselineSource::TestnetUniswapV2], - _ => bail!("unsupported chain {:#x}", chain_id), - }) + Chain::Base => vec![ + BaselineSource::UniswapV2, + BaselineSource::SushiSwap, + BaselineSource::Swapr, + BaselineSource::BalancerV2, + BaselineSource::ZeroEx, + BaselineSource::UniswapV3, + ], + Chain::Sepolia => vec![BaselineSource::TestnetUniswapV2], + } } pub struct PoolAggregator { diff --git a/crates/solvers/Cargo.toml b/crates/solvers/Cargo.toml index cd9cb3d297..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 } 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..354d383bff 100644 --- a/crates/solvers/src/infra/config/baseline.rs +++ b/crates/solvers/src/infra/config/baseline.rs @@ -4,6 +4,7 @@ use { infra::{config::unwrap_or_log, contracts}, util::serialize, }, + chain::Chain, ethereum_types::H160, serde::Deserialize, serde_with::serde_as, @@ -18,8 +19,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..d0707d7bf4 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, chain::Chain}; #[derive(Clone, Debug)] pub struct Contracts { @@ -9,12 +9,12 @@ pub struct Contracts { } impl Contracts { - pub fn for_chain(chain: eth::ChainId) -> Self { + pub fn for_chain(chain: Chain) -> Self { let a = |contract: &contracts::ethcontract::Contract| { eth::ContractAddress( contract .networks - .get(chain.network_id()) + .get(&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;