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_testnet(&self) -> bool {
108 self.is_testnet
109 }
110
111 pub fn required_confirmations(&self) -> u64 {
113 self.required_confirmations
114 }
115
116 pub fn id(&self) -> u64 {
117 self.chain_id
118 }
119
120 pub fn average_blocktime(&self) -> Option<Duration> {
121 Some(Duration::from_millis(self.average_blocktime_ms))
122 }
123
124 pub fn is_legacy(&self) -> bool {
125 !self.features.contains(&"eip1559".to_string())
126 }
127
128 pub fn explorer_urls(&self) -> Option<&[String]> {
129 self.explorer_urls.as_deref()
130 }
131
132 pub fn public_rpc_urls(&self) -> Option<&[String]> {
133 if self.rpc_urls.is_empty() {
134 None
135 } else {
136 Some(&self.rpc_urls)
137 }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use crate::config::{EvmNetworkConfig, NetworkConfigCommon};
145 use crate::models::{NetworkConfigData, NetworkRepoModel, NetworkType};
146
147 fn create_test_evm_network_with_tags(tags: Vec<&str>) -> EvmNetwork {
148 EvmNetwork {
149 network: "test-network".to_string(),
150 rpc_urls: vec!["https://rpc.example.com".to_string()],
151 explorer_urls: None,
152 average_blocktime_ms: 12000,
153 is_testnet: false,
154 tags: tags.into_iter().map(|s| s.to_string()).collect(),
155 chain_id: 1,
156 required_confirmations: 1,
157 features: vec!["eip1559".to_string()],
158 symbol: "ETH".to_string(),
159 }
160 }
161
162 #[test]
163 fn test_is_optimism_with_optimism_tag() {
164 let network = create_test_evm_network_with_tags(vec!["optimism", "rollup"]);
165 assert!(network.is_optimism());
166 }
167
168 #[test]
169 fn test_is_optimism_without_optimism_tag() {
170 let network = create_test_evm_network_with_tags(vec!["rollup", "mainnet"]);
171 assert!(!network.is_optimism());
172 }
173
174 #[test]
175 fn test_is_rollup_with_rollup_tag() {
176 let network = create_test_evm_network_with_tags(vec!["rollup", "no-mempool"]);
177 assert!(network.is_rollup());
178 }
179
180 #[test]
181 fn test_is_rollup_without_rollup_tag() {
182 let network = create_test_evm_network_with_tags(vec!["mainnet", "ethereum"]);
183 assert!(!network.is_rollup());
184 }
185
186 #[test]
187 fn test_lacks_mempool_with_no_mempool_tag() {
188 let network = create_test_evm_network_with_tags(vec!["rollup", "no-mempool"]);
189 assert!(network.lacks_mempool());
190 }
191
192 #[test]
193 fn test_lacks_mempool_without_no_mempool_tag() {
194 let network = create_test_evm_network_with_tags(vec!["rollup", "optimism"]);
195 assert!(!network.lacks_mempool());
196 }
197
198 #[test]
199 fn test_arbitrum_like_network() {
200 let network = create_test_evm_network_with_tags(vec!["rollup", "no-mempool"]);
201 assert!(network.is_rollup());
202 assert!(network.lacks_mempool());
203 assert!(!network.is_optimism());
204 }
205
206 #[test]
207 fn test_optimism_like_network() {
208 let network = create_test_evm_network_with_tags(vec!["rollup", "optimism"]);
209 assert!(network.is_rollup());
210 assert!(network.is_optimism());
211 assert!(!network.lacks_mempool());
212 }
213
214 #[test]
215 fn test_ethereum_mainnet_like_network() {
216 let network = create_test_evm_network_with_tags(vec!["mainnet", "ethereum"]);
217 assert!(!network.is_rollup());
218 assert!(!network.is_optimism());
219 assert!(!network.lacks_mempool());
220 }
221
222 #[test]
223 fn test_empty_tags() {
224 let network = create_test_evm_network_with_tags(vec![]);
225 assert!(!network.is_rollup());
226 assert!(!network.is_optimism());
227 assert!(!network.lacks_mempool());
228 }
229
230 #[test]
231 fn test_try_from_with_tags() {
232 let config = EvmNetworkConfig {
233 common: NetworkConfigCommon {
234 network: "test-network".to_string(),
235 from: None,
236 rpc_urls: Some(vec!["https://rpc.example.com".to_string()]),
237 explorer_urls: None,
238 average_blocktime_ms: Some(12000),
239 is_testnet: Some(false),
240 tags: Some(vec!["rollup".to_string(), "optimism".to_string()]),
241 },
242 chain_id: Some(10),
243 required_confirmations: Some(1),
244 features: Some(vec!["eip1559".to_string()]),
245 symbol: Some("ETH".to_string()),
246 };
247
248 let repo_model = NetworkRepoModel {
249 id: "evm:test-network".to_string(),
250 name: "test-network".to_string(),
251 network_type: NetworkType::Evm,
252 config: NetworkConfigData::Evm(config),
253 };
254
255 let network = EvmNetwork::try_from(repo_model).unwrap();
256 assert!(network.is_optimism());
257 assert!(network.is_rollup());
258 assert!(!network.lacks_mempool());
259 }
260}