openzeppelin_relayer/models/network/evm/
network.rs1use crate::config::GasPriceCacheConfig;
2use crate::constants::{
3 ARBITRUM_BASED_TAG, LACKS_MEMPOOL_TAGS, OPTIMISM_BASED_TAG, OPTIMISM_TAG, POLYGON_ZKEVM_TAG,
4 ROLLUP_TAG,
5};
6use crate::models::{NetworkConfigData, NetworkRepoModel, RepositoryError, RpcConfig};
7use std::time::Duration;
8
9#[derive(Clone, PartialEq, Eq, Hash, Debug)]
10pub struct EvmNetwork {
11 pub network: String,
14 pub rpc_urls: Vec<RpcConfig>,
16 pub explorer_urls: Option<Vec<String>>,
18 pub average_blocktime_ms: u64,
20 pub is_testnet: bool,
22 pub tags: Vec<String>,
24 pub chain_id: u64,
26 pub required_confirmations: u64,
28 pub features: Vec<String>,
30 pub symbol: String,
32 pub gas_price_cache: Option<GasPriceCacheConfig>,
34}
35
36impl TryFrom<NetworkRepoModel> for EvmNetwork {
37 type Error = RepositoryError;
38
39 fn try_from(network_repo: NetworkRepoModel) -> Result<Self, Self::Error> {
47 match &network_repo.config {
48 NetworkConfigData::Evm(evm_config) => {
49 let common = &evm_config.common;
50
51 let chain_id = evm_config.chain_id.ok_or_else(|| {
52 RepositoryError::InvalidData(format!(
53 "EVM network '{}' has no chain_id",
54 network_repo.name
55 ))
56 })?;
57
58 let required_confirmations =
59 evm_config.required_confirmations.ok_or_else(|| {
60 RepositoryError::InvalidData(format!(
61 "EVM network '{}' has no required_confirmations",
62 network_repo.name
63 ))
64 })?;
65
66 let symbol = evm_config.symbol.clone().ok_or_else(|| {
67 RepositoryError::InvalidData(format!(
68 "EVM network '{}' has no symbol",
69 network_repo.name
70 ))
71 })?;
72
73 let average_blocktime_ms = common.average_blocktime_ms.ok_or_else(|| {
74 RepositoryError::InvalidData(format!(
75 "EVM network '{}' has no average_blocktime_ms",
76 network_repo.name
77 ))
78 })?;
79
80 Ok(EvmNetwork {
81 network: common.network.clone(),
82 rpc_urls: common.rpc_urls.clone().unwrap_or_default(),
83 explorer_urls: common.explorer_urls.clone(),
84 average_blocktime_ms,
85 is_testnet: common.is_testnet.unwrap_or(false),
86 tags: common.tags.clone().unwrap_or_default(),
87 chain_id,
88 required_confirmations,
89 features: evm_config.features.clone().unwrap_or_default(),
90 symbol,
91 gas_price_cache: evm_config.gas_price_cache.clone(),
92 })
93 }
94 _ => Err(RepositoryError::InvalidData(format!(
95 "Network '{}' is not an EVM network",
96 network_repo.name
97 ))),
98 }
99 }
100}
101
102impl EvmNetwork {
103 pub fn is_optimism(&self) -> bool {
104 self.tags
105 .iter()
106 .any(|t| t == OPTIMISM_BASED_TAG || t == OPTIMISM_TAG)
107 }
108
109 pub fn is_rollup(&self) -> bool {
110 self.tags.iter().any(|t| t == ROLLUP_TAG)
111 }
112
113 pub fn lacks_mempool(&self) -> bool {
122 self.tags
123 .iter()
124 .any(|t| LACKS_MEMPOOL_TAGS.contains(&t.as_str()))
125 }
126
127 pub fn is_arbitrum(&self) -> bool {
128 self.tags.iter().any(|t| t == ARBITRUM_BASED_TAG)
129 }
130
131 pub fn is_polygon_zkevm(&self) -> bool {
132 self.tags.iter().any(|t| t == POLYGON_ZKEVM_TAG)
133 }
134
135 pub fn is_testnet(&self) -> bool {
136 self.is_testnet
137 }
138
139 pub fn required_confirmations(&self) -> u64 {
141 self.required_confirmations
142 }
143
144 pub fn id(&self) -> u64 {
145 self.chain_id
146 }
147
148 pub fn average_blocktime(&self) -> Option<Duration> {
149 Some(Duration::from_millis(self.average_blocktime_ms))
150 }
151
152 pub fn is_legacy(&self) -> bool {
153 !self.features.contains(&"eip1559".to_string())
154 }
155
156 pub fn explorer_urls(&self) -> Option<&[String]> {
157 self.explorer_urls.as_deref()
158 }
159
160 pub fn public_rpc_urls(&self) -> Option<&[RpcConfig]> {
161 if self.rpc_urls.is_empty() {
162 None
163 } else {
164 Some(&self.rpc_urls)
165 }
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172 use crate::config::{EvmNetworkConfig, NetworkConfigCommon};
173 use crate::constants::{NO_MEMPOOL_TAG, OPTIMISM_TAG};
174 use crate::models::{NetworkConfigData, NetworkRepoModel, NetworkType};
175
176 fn create_test_evm_network_with_tags(tags: Vec<&str>) -> EvmNetwork {
177 use crate::models::RpcConfig;
178 EvmNetwork {
179 network: "test-network".to_string(),
180 rpc_urls: vec![RpcConfig::new("https://rpc.example.com".to_string())],
181 explorer_urls: None,
182 average_blocktime_ms: 12000,
183 is_testnet: false,
184 tags: tags.into_iter().map(|s| s.to_string()).collect(),
185 chain_id: 1,
186 required_confirmations: 1,
187 features: vec!["eip1559".to_string()],
188 symbol: "ETH".to_string(),
189 gas_price_cache: None,
190 }
191 }
192
193 #[test]
194 fn test_is_optimism_with_optimism_tag() {
195 let network = create_test_evm_network_with_tags(vec![OPTIMISM_BASED_TAG, ROLLUP_TAG]);
196 assert!(network.is_optimism());
197 }
198
199 #[test]
200 fn test_is_optimism_without_optimism_tag() {
201 let network = create_test_evm_network_with_tags(vec![ROLLUP_TAG, "mainnet"]);
202 assert!(!network.is_optimism());
203 }
204
205 #[test]
206 fn test_is_optimism_with_deprecated_optimism_tag() {
207 let network = create_test_evm_network_with_tags(vec![OPTIMISM_TAG, ROLLUP_TAG]);
208 assert!(network.is_optimism());
209 }
210
211 #[test]
212 fn test_lacks_mempool_with_deprecated_optimism_tag() {
213 let network = create_test_evm_network_with_tags(vec![OPTIMISM_TAG, ROLLUP_TAG]);
214 assert!(network.lacks_mempool());
215 }
216
217 #[test]
218 fn test_is_rollup_with_rollup_tag() {
219 let network = create_test_evm_network_with_tags(vec![ROLLUP_TAG, NO_MEMPOOL_TAG]);
220 assert!(network.is_rollup());
221 }
222
223 #[test]
224 fn test_is_rollup_without_rollup_tag() {
225 let network = create_test_evm_network_with_tags(vec!["mainnet", "ethereum"]);
226 assert!(!network.is_rollup());
227 }
228
229 #[test]
230 fn test_lacks_mempool_with_no_mempool_tag() {
231 let network = create_test_evm_network_with_tags(vec![ROLLUP_TAG, NO_MEMPOOL_TAG]);
232 assert!(network.lacks_mempool());
233 }
234
235 #[test]
236 fn test_lacks_mempool_without_no_mempool_tag() {
237 let network = create_test_evm_network_with_tags(vec![ROLLUP_TAG]);
238 assert!(!network.lacks_mempool());
239 }
240
241 #[test]
242 fn test_arbitrum_like_network() {
243 let network = create_test_evm_network_with_tags(vec![ROLLUP_TAG, ARBITRUM_BASED_TAG]);
244 assert!(network.is_rollup());
245 assert!(network.is_arbitrum());
246 assert!(network.lacks_mempool());
247 assert!(!network.is_optimism());
248 }
249
250 #[test]
251 fn test_optimism_like_network() {
252 let network = create_test_evm_network_with_tags(vec![ROLLUP_TAG, OPTIMISM_BASED_TAG]);
253 assert!(network.is_rollup());
254 assert!(network.is_optimism());
255 assert!(network.lacks_mempool());
256 }
257
258 #[test]
259 fn test_polygon_zkevm_network() {
260 let network = create_test_evm_network_with_tags(vec![ROLLUP_TAG, POLYGON_ZKEVM_TAG]);
261 assert!(network.is_rollup());
262 assert!(network.is_polygon_zkevm());
263 assert!(!network.lacks_mempool());
264 assert!(!network.is_optimism());
265 assert!(!network.is_arbitrum());
266 }
267
268 #[test]
269 fn test_ethereum_mainnet_like_network() {
270 let network = create_test_evm_network_with_tags(vec!["mainnet", "ethereum"]);
271 assert!(!network.is_rollup());
272 assert!(!network.is_optimism());
273 assert!(!network.lacks_mempool());
274 }
275
276 #[test]
277 fn test_empty_tags() {
278 let network = create_test_evm_network_with_tags(vec![]);
279 assert!(!network.is_rollup());
280 assert!(!network.is_optimism());
281 assert!(!network.lacks_mempool());
282 }
283
284 #[test]
285 fn test_try_from_with_tags() {
286 use crate::models::RpcConfig;
287 let config = EvmNetworkConfig {
288 common: NetworkConfigCommon {
289 network: "test-network".to_string(),
290 from: None,
291 rpc_urls: Some(vec![RpcConfig::new("https://rpc.example.com".to_string())]),
292 explorer_urls: None,
293 average_blocktime_ms: Some(12000),
294 is_testnet: Some(false),
295 tags: Some(vec![ROLLUP_TAG.to_string(), OPTIMISM_BASED_TAG.to_string()]),
296 },
297 chain_id: Some(10),
298 required_confirmations: Some(1),
299 features: Some(vec!["eip1559".to_string()]),
300 symbol: Some("ETH".to_string()),
301 gas_price_cache: None,
302 };
303
304 let repo_model = NetworkRepoModel {
305 id: "evm:test-network".to_string(),
306 name: "test-network".to_string(),
307 network_type: NetworkType::Evm,
308 config: NetworkConfigData::Evm(config),
309 };
310
311 let network = EvmNetwork::try_from(repo_model).unwrap();
312 assert!(network.is_optimism());
313 assert!(network.is_rollup());
314 assert!(network.lacks_mempool());
315 }
316}