openzeppelin_relayer/models/network/evm/
network.rs1use crate::models::{NetworkConfigData, NetworkRepoModel, RepositoryError};
2use std::time::Duration;
3
4#[derive(Clone, PartialEq, Eq, Hash, Debug)]
5pub struct EvmNetwork {
6 pub network: String,
9 pub rpc_urls: Vec<String>,
11 pub explorer_urls: Option<Vec<String>>,
13 pub average_blocktime_ms: u64,
15 pub is_testnet: bool,
17 pub tags: Vec<String>,
19 pub chain_id: u64,
21 pub required_confirmations: u64,
23 pub features: Vec<String>,
25 pub symbol: String,
27}
28
29impl TryFrom<NetworkRepoModel> for EvmNetwork {
30 type Error = RepositoryError;
31
32 fn try_from(network_repo: NetworkRepoModel) -> Result<Self, Self::Error> {
40 match &network_repo.config {
41 NetworkConfigData::Evm(evm_config) => {
42 let common = &evm_config.common;
43
44 let chain_id = evm_config.chain_id.ok_or_else(|| {
45 RepositoryError::InvalidData(format!(
46 "EVM network '{}' has no chain_id",
47 network_repo.name
48 ))
49 })?;
50
51 let required_confirmations =
52 evm_config.required_confirmations.ok_or_else(|| {
53 RepositoryError::InvalidData(format!(
54 "EVM network '{}' has no required_confirmations",
55 network_repo.name
56 ))
57 })?;
58
59 let symbol = evm_config.symbol.clone().ok_or_else(|| {
60 RepositoryError::InvalidData(format!(
61 "EVM network '{}' has no symbol",
62 network_repo.name
63 ))
64 })?;
65
66 let average_blocktime_ms = common.average_blocktime_ms.ok_or_else(|| {
67 RepositoryError::InvalidData(format!(
68 "EVM network '{}' has no average_blocktime_ms",
69 network_repo.name
70 ))
71 })?;
72
73 Ok(EvmNetwork {
74 network: common.network.clone(),
75 rpc_urls: common.rpc_urls.clone().unwrap_or_default(),
76 explorer_urls: common.explorer_urls.clone(),
77 average_blocktime_ms,
78 is_testnet: common.is_testnet.unwrap_or(false),
79 tags: common.tags.clone().unwrap_or_default(),
80 chain_id,
81 required_confirmations,
82 features: evm_config.features.clone().unwrap_or_default(),
83 symbol,
84 })
85 }
86 _ => Err(RepositoryError::InvalidData(format!(
87 "Network '{}' is not an EVM network",
88 network_repo.name
89 ))),
90 }
91 }
92}
93
94impl EvmNetwork {
95 pub fn is_optimism(&self) -> bool {
96 self.tags.contains(&"optimism".to_string())
97 }
98
99 pub fn is_rollup(&self) -> bool {
100 self.tags.contains(&"rollup".to_string())
101 }
102
103 pub fn lacks_mempool(&self) -> bool {
104 self.tags.contains(&"no-mempool".to_string())
105 }
106
107 pub fn is_arbitrum(&self) -> bool {
108 self.tags.contains(&"arbitrum-based".to_string())
109 }
110
111 pub fn is_testnet(&self) -> bool {
112 self.is_testnet
113 }
114
115 pub fn required_confirmations(&self) -> u64 {
117 self.required_confirmations
118 }
119
120 pub fn id(&self) -> u64 {
121 self.chain_id
122 }
123
124 pub fn average_blocktime(&self) -> Option<Duration> {
125 Some(Duration::from_millis(self.average_blocktime_ms))
126 }
127
128 pub fn is_legacy(&self) -> bool {
129 !self.features.contains(&"eip1559".to_string())
130 }
131
132 pub fn explorer_urls(&self) -> Option<&[String]> {
133 self.explorer_urls.as_deref()
134 }
135
136 pub fn public_rpc_urls(&self) -> Option<&[String]> {
137 if self.rpc_urls.is_empty() {
138 None
139 } else {
140 Some(&self.rpc_urls)
141 }
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use crate::config::{EvmNetworkConfig, NetworkConfigCommon};
149 use crate::models::{NetworkConfigData, NetworkRepoModel, NetworkType};
150
151 fn create_test_evm_network_with_tags(tags: Vec<&str>) -> EvmNetwork {
152 EvmNetwork {
153 network: "test-network".to_string(),
154 rpc_urls: vec!["https://rpc.example.com".to_string()],
155 explorer_urls: None,
156 average_blocktime_ms: 12000,
157 is_testnet: false,
158 tags: tags.into_iter().map(|s| s.to_string()).collect(),
159 chain_id: 1,
160 required_confirmations: 1,
161 features: vec!["eip1559".to_string()],
162 symbol: "ETH".to_string(),
163 }
164 }
165
166 #[test]
167 fn test_is_optimism_with_optimism_tag() {
168 let network = create_test_evm_network_with_tags(vec!["optimism", "rollup"]);
169 assert!(network.is_optimism());
170 }
171
172 #[test]
173 fn test_is_optimism_without_optimism_tag() {
174 let network = create_test_evm_network_with_tags(vec!["rollup", "mainnet"]);
175 assert!(!network.is_optimism());
176 }
177
178 #[test]
179 fn test_is_rollup_with_rollup_tag() {
180 let network = create_test_evm_network_with_tags(vec!["rollup", "no-mempool"]);
181 assert!(network.is_rollup());
182 }
183
184 #[test]
185 fn test_is_rollup_without_rollup_tag() {
186 let network = create_test_evm_network_with_tags(vec!["mainnet", "ethereum"]);
187 assert!(!network.is_rollup());
188 }
189
190 #[test]
191 fn test_lacks_mempool_with_no_mempool_tag() {
192 let network = create_test_evm_network_with_tags(vec!["rollup", "no-mempool"]);
193 assert!(network.lacks_mempool());
194 }
195
196 #[test]
197 fn test_lacks_mempool_without_no_mempool_tag() {
198 let network = create_test_evm_network_with_tags(vec!["rollup", "optimism"]);
199 assert!(!network.lacks_mempool());
200 }
201
202 #[test]
203 fn test_arbitrum_like_network() {
204 let network = create_test_evm_network_with_tags(vec!["rollup", "no-mempool"]);
205 assert!(network.is_rollup());
206 assert!(network.lacks_mempool());
207 assert!(!network.is_optimism());
208 }
209
210 #[test]
211 fn test_optimism_like_network() {
212 let network = create_test_evm_network_with_tags(vec!["rollup", "optimism"]);
213 assert!(network.is_rollup());
214 assert!(network.is_optimism());
215 assert!(!network.lacks_mempool());
216 }
217
218 #[test]
219 fn test_ethereum_mainnet_like_network() {
220 let network = create_test_evm_network_with_tags(vec!["mainnet", "ethereum"]);
221 assert!(!network.is_rollup());
222 assert!(!network.is_optimism());
223 assert!(!network.lacks_mempool());
224 }
225
226 #[test]
227 fn test_empty_tags() {
228 let network = create_test_evm_network_with_tags(vec![]);
229 assert!(!network.is_rollup());
230 assert!(!network.is_optimism());
231 assert!(!network.lacks_mempool());
232 }
233
234 #[test]
235 fn test_try_from_with_tags() {
236 let config = EvmNetworkConfig {
237 common: NetworkConfigCommon {
238 network: "test-network".to_string(),
239 from: None,
240 rpc_urls: Some(vec!["https://rpc.example.com".to_string()]),
241 explorer_urls: None,
242 average_blocktime_ms: Some(12000),
243 is_testnet: Some(false),
244 tags: Some(vec!["rollup".to_string(), "optimism".to_string()]),
245 },
246 chain_id: Some(10),
247 required_confirmations: Some(1),
248 features: Some(vec!["eip1559".to_string()]),
249 symbol: Some("ETH".to_string()),
250 };
251
252 let repo_model = NetworkRepoModel {
253 id: "evm:test-network".to_string(),
254 name: "test-network".to_string(),
255 network_type: NetworkType::Evm,
256 config: NetworkConfigData::Evm(config),
257 };
258
259 let network = EvmNetwork::try_from(repo_model).unwrap();
260 assert!(network.is_optimism());
261 assert!(network.is_rollup());
262 assert!(!network.lacks_mempool());
263 }
264}