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};
7use std::time::Duration;
8
9#[derive(Clone, PartialEq, Eq, Hash, Debug)]
10pub struct EvmNetwork {
11 pub network: String,
14 pub rpc_urls: Vec<String>,
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<&[String]> {
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 EvmNetwork {
178 network: "test-network".to_string(),
179 rpc_urls: vec!["https://rpc.example.com".to_string()],
180 explorer_urls: None,
181 average_blocktime_ms: 12000,
182 is_testnet: false,
183 tags: tags.into_iter().map(|s| s.to_string()).collect(),
184 chain_id: 1,
185 required_confirmations: 1,
186 features: vec!["eip1559".to_string()],
187 symbol: "ETH".to_string(),
188 gas_price_cache: None,
189 }
190 }
191
192 #[test]
193 fn test_is_optimism_with_optimism_tag() {
194 let network = create_test_evm_network_with_tags(vec![OPTIMISM_BASED_TAG, ROLLUP_TAG]);
195 assert!(network.is_optimism());
196 }
197
198 #[test]
199 fn test_is_optimism_without_optimism_tag() {
200 let network = create_test_evm_network_with_tags(vec![ROLLUP_TAG, "mainnet"]);
201 assert!(!network.is_optimism());
202 }
203
204 #[test]
205 fn test_is_optimism_with_deprecated_optimism_tag() {
206 let network = create_test_evm_network_with_tags(vec![OPTIMISM_TAG, ROLLUP_TAG]);
207 assert!(network.is_optimism());
208 }
209
210 #[test]
211 fn test_lacks_mempool_with_deprecated_optimism_tag() {
212 let network = create_test_evm_network_with_tags(vec![OPTIMISM_TAG, ROLLUP_TAG]);
213 assert!(network.lacks_mempool());
214 }
215
216 #[test]
217 fn test_is_rollup_with_rollup_tag() {
218 let network = create_test_evm_network_with_tags(vec![ROLLUP_TAG, NO_MEMPOOL_TAG]);
219 assert!(network.is_rollup());
220 }
221
222 #[test]
223 fn test_is_rollup_without_rollup_tag() {
224 let network = create_test_evm_network_with_tags(vec!["mainnet", "ethereum"]);
225 assert!(!network.is_rollup());
226 }
227
228 #[test]
229 fn test_lacks_mempool_with_no_mempool_tag() {
230 let network = create_test_evm_network_with_tags(vec![ROLLUP_TAG, NO_MEMPOOL_TAG]);
231 assert!(network.lacks_mempool());
232 }
233
234 #[test]
235 fn test_lacks_mempool_without_no_mempool_tag() {
236 let network = create_test_evm_network_with_tags(vec![ROLLUP_TAG]);
237 assert!(!network.lacks_mempool());
238 }
239
240 #[test]
241 fn test_arbitrum_like_network() {
242 let network = create_test_evm_network_with_tags(vec![ROLLUP_TAG, ARBITRUM_BASED_TAG]);
243 assert!(network.is_rollup());
244 assert!(network.is_arbitrum());
245 assert!(network.lacks_mempool());
246 assert!(!network.is_optimism());
247 }
248
249 #[test]
250 fn test_optimism_like_network() {
251 let network = create_test_evm_network_with_tags(vec![ROLLUP_TAG, OPTIMISM_BASED_TAG]);
252 assert!(network.is_rollup());
253 assert!(network.is_optimism());
254 assert!(network.lacks_mempool());
255 }
256
257 #[test]
258 fn test_polygon_zkevm_network() {
259 let network = create_test_evm_network_with_tags(vec![ROLLUP_TAG, POLYGON_ZKEVM_TAG]);
260 assert!(network.is_rollup());
261 assert!(network.is_polygon_zkevm());
262 assert!(!network.lacks_mempool());
263 assert!(!network.is_optimism());
264 assert!(!network.is_arbitrum());
265 }
266
267 #[test]
268 fn test_ethereum_mainnet_like_network() {
269 let network = create_test_evm_network_with_tags(vec!["mainnet", "ethereum"]);
270 assert!(!network.is_rollup());
271 assert!(!network.is_optimism());
272 assert!(!network.lacks_mempool());
273 }
274
275 #[test]
276 fn test_empty_tags() {
277 let network = create_test_evm_network_with_tags(vec![]);
278 assert!(!network.is_rollup());
279 assert!(!network.is_optimism());
280 assert!(!network.lacks_mempool());
281 }
282
283 #[test]
284 fn test_try_from_with_tags() {
285 let config = EvmNetworkConfig {
286 common: NetworkConfigCommon {
287 network: "test-network".to_string(),
288 from: None,
289 rpc_urls: Some(vec!["https://rpc.example.com".to_string()]),
290 explorer_urls: None,
291 average_blocktime_ms: Some(12000),
292 is_testnet: Some(false),
293 tags: Some(vec![ROLLUP_TAG.to_string(), OPTIMISM_BASED_TAG.to_string()]),
294 },
295 chain_id: Some(10),
296 required_confirmations: Some(1),
297 features: Some(vec!["eip1559".to_string()]),
298 symbol: Some("ETH".to_string()),
299 gas_price_cache: None,
300 };
301
302 let repo_model = NetworkRepoModel {
303 id: "evm:test-network".to_string(),
304 name: "test-network".to_string(),
305 network_type: NetworkType::Evm,
306 config: NetworkConfigData::Evm(config),
307 };
308
309 let network = EvmNetwork::try_from(repo_model).unwrap();
310 assert!(network.is_optimism());
311 assert!(network.is_rollup());
312 assert!(network.lacks_mempool());
313 }
314}