1use std::str::FromStr;
2
3use semver::{Version, VersionReq};
4use serde::{Deserialize, Serialize};
5use serde_with::{serde_as, DisplayFromStr};
6use validator::Validate;
7
8use hopr_chain_types::ContractAddresses;
9use hopr_primitive_types::primitives::Address;
10
11use crate::errors::HoprChainError;
12
13#[derive(Debug, Copy, Clone, Deserialize, Serialize, Eq, PartialEq, strum::Display, strum::EnumString)]
15#[serde(rename_all(deserialize = "lowercase"))]
16#[strum(serialize_all = "lowercase")]
17pub enum EnvironmentType {
18 Production,
19 Staging,
20 Development,
21 Local,
22}
23
24#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
27#[serde(deny_unknown_fields)]
28pub struct ChainOptions {
29 pub description: String,
30 pub chain_id: u32,
32 pub live: bool,
33 pub default_provider: String,
35 pub etherscan_api_url: Option<String>,
37 pub max_fee_per_gas: String,
39 pub max_priority_fee_per_gas: String,
41 pub native_token_name: String,
42 pub hopr_token_name: String,
43 pub block_time: u64,
45 pub max_rpc_requests_per_sec: Option<u32>,
47 pub tags: Option<Vec<String>>,
48}
49
50#[serde_as]
53#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
54#[serde(deny_unknown_fields)]
55pub struct Network {
56 pub chain: String,
58 #[serde_as(as = "DisplayFromStr")]
59 pub environment_type: EnvironmentType,
60 pub version_range: String,
62 pub indexer_start_block_number: u32,
64 pub tags: Vec<String>,
65 pub addresses: Addresses,
67 pub confirmations: u32,
69 pub tx_polling_interval: u64,
71 pub max_block_range: u64,
73}
74
75#[serde_as]
76#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
77#[serde(deny_unknown_fields)]
78pub struct Addresses {
79 #[serde_as(as = "DisplayFromStr")]
81 pub network_registry: Address,
82 #[serde_as(as = "DisplayFromStr")]
85 pub network_registry_proxy: Address,
86 #[serde_as(as = "DisplayFromStr")]
88 pub channels: Address,
89 #[serde_as(as = "DisplayFromStr")]
91 pub token: Address,
92 #[serde_as(as = "DisplayFromStr")]
94 pub module_implementation: Address,
95 #[serde_as(as = "DisplayFromStr")]
97 pub node_safe_registry: Address,
98 #[serde_as(as = "DisplayFromStr")]
100 pub ticket_price_oracle: Address,
101 #[serde_as(as = "DisplayFromStr")]
103 pub winning_probability_oracle: Address,
104 #[serde_as(as = "DisplayFromStr")]
106 pub announcements: Address,
107 #[serde_as(as = "DisplayFromStr")]
109 pub node_stake_v2_factory: Address,
110}
111
112#[serde_as]
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct ChainNetworkConfig {
115 pub id: String,
117 pub chain: ChainOptions,
118 pub environment_type: EnvironmentType,
119 pub channel_contract_deploy_block: u32,
120 #[serde_as(as = "DisplayFromStr")]
122 pub network_registry: Address,
123 #[serde_as(as = "DisplayFromStr")]
126 pub network_registry_proxy: Address,
127 #[serde_as(as = "DisplayFromStr")]
129 pub channels: Address,
130 #[serde_as(as = "DisplayFromStr")]
132 pub token: Address,
133 #[serde_as(as = "DisplayFromStr")]
135 pub module_implementation: Address,
136 #[serde_as(as = "DisplayFromStr")]
138 pub node_safe_registry: Address,
139 #[serde_as(as = "DisplayFromStr")]
141 pub ticket_price_oracle: Address,
142 #[serde_as(as = "DisplayFromStr")]
144 pub winning_probability_oracle: Address,
145 #[serde_as(as = "DisplayFromStr")]
147 pub announcements: Address,
148 #[serde_as(as = "DisplayFromStr")]
150 pub node_stake_v2_factory: Address,
151 pub confirmations: u32,
153 pub tx_polling_interval: u64,
155 pub max_block_range: u64,
157 pub max_requests_per_sec: Option<u32>,
159}
160
161fn satisfies(version: &str, allowed_versions: &str) -> crate::errors::Result<bool> {
163 let allowed_versions = VersionReq::parse(allowed_versions)
164 .map_err(|e| HoprChainError::Configuration(format!("failed to deserialize allowed version string: {}", e)))?;
165
166 let version = Version::from_str(version).map_err(|e| {
167 HoprChainError::Configuration(format!("failed to deserialize current lib version string: {}", e))
168 })?;
169
170 Ok(allowed_versions.matches(&version))
171}
172
173impl ChainNetworkConfig {
174 pub fn new(
176 id: &str,
177 version: &str,
178 maybe_custom_provider: Option<&str>,
179 max_rpc_requests_per_sec: Option<u32>,
180 protocol_config: &mut ProtocolsConfig,
181 ) -> Result<Self, String> {
182 let network = protocol_config
183 .networks
184 .get_mut(id)
185 .ok_or(format!("Could not find network {id} in protocol config"))?;
186
187 let chain = protocol_config
188 .chains
189 .get_mut(&network.chain)
190 .ok_or(format!("Invalid chain {} for network {id}", network.chain))?;
191
192 if let Some(custom_provider) = maybe_custom_provider {
193 chain.default_provider = custom_provider.into();
194 }
195
196 match satisfies(version, network.version_range.as_str()) {
197 Ok(true) => Ok(ChainNetworkConfig {
198 announcements: network.addresses.announcements.to_owned(),
199 chain: chain.to_owned(),
200 channel_contract_deploy_block: network.indexer_start_block_number,
201 channels: network.addresses.channels.to_owned(),
202 confirmations: network.confirmations,
203 environment_type: network.environment_type,
204 id: network.chain.to_owned(),
205 module_implementation: network.addresses.module_implementation.to_owned(),
206 network_registry: network.addresses.network_registry.to_owned(),
207 network_registry_proxy: network.addresses.network_registry_proxy.to_owned(),
208 node_safe_registry: network.addresses.node_safe_registry.to_owned(),
209 node_stake_v2_factory: network.addresses.node_stake_v2_factory.to_owned(),
210 ticket_price_oracle: network.addresses.ticket_price_oracle.to_owned(),
211 winning_probability_oracle: network.addresses.winning_probability_oracle.to_owned(),
212 token: network.addresses.token.to_owned(),
213 tx_polling_interval: network.tx_polling_interval,
214 max_block_range: network.max_block_range,
215 max_requests_per_sec: max_rpc_requests_per_sec.or(chain.max_rpc_requests_per_sec),
216 }),
217 Ok(false) => Err(format!(
218 "network {id} is not supported, supported networks {:?}",
219 protocol_config.supported_networks(version).join(", ")
220 )),
221 Err(e) => Err(e.to_string()),
222 }
223 }
224}
225
226impl From<&ChainNetworkConfig> for ContractAddresses {
227 fn from(network: &ChainNetworkConfig) -> Self {
228 Self {
229 token: network.token,
230 channels: network.channels,
231 announcements: network.announcements,
232 network_registry: network.network_registry,
233 network_registry_proxy: network.network_registry_proxy,
234 safe_registry: network.node_safe_registry,
235 price_oracle: network.ticket_price_oracle,
236 win_prob_oracle: network.winning_probability_oracle,
237 stake_factory: network.node_stake_v2_factory,
238 module_implementation: network.module_implementation,
239 }
240 }
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
246#[serde(deny_unknown_fields)]
247pub struct ProtocolsConfig {
248 pub networks: std::collections::BTreeMap<String, Network>,
249 pub chains: std::collections::BTreeMap<String, ChainOptions>,
250}
251
252impl Default for ProtocolsConfig {
253 fn default() -> Self {
254 Self::from_str(include_str!("../../../hopr/hopr-lib/data/protocol-config.json"))
255 .expect("bundled protocol config should be always valid")
256 }
257}
258
259impl FromStr for ProtocolsConfig {
260 type Err = String;
261
262 fn from_str(data: &str) -> Result<Self, Self::Err> {
264 serde_json::from_str::<ProtocolsConfig>(data).map_err(|e| e.to_string())
265 }
266}
267
268impl std::cmp::PartialEq for ProtocolsConfig {
269 fn eq(&self, other: &Self) -> bool {
270 Vec::from_iter(self.networks.clone()) == Vec::from_iter(other.networks.clone())
271 && Vec::from_iter(self.chains.clone()) == Vec::from_iter(self.chains.clone())
272 }
273}
274
275impl ProtocolsConfig {
276 pub fn supported_networks(&self, version: &str) -> Vec<String> {
279 let mut allowed = vec![];
280
281 for (name, env) in self.networks.iter() {
282 let range = env.version_range.to_owned();
283
284 if let Ok(true) = satisfies(version, range.as_str()) {
285 allowed.push(name.clone())
286 }
287 }
288
289 allowed
290 }
291}
292#[cfg(test)]
293mod test {
294 use super::*;
295
296 #[test]
297 fn test_default_protocol_config_can_be_deserialized() {
298 let _ = ProtocolsConfig::default();
299 }
300
301 #[test]
302 fn test_version_is_satisfied_should_work_on_ranges() {
303 let actual = satisfies("1.90.0", ">=1.89, <1.93");
304 assert!(actual.is_ok());
305 assert!(actual.expect("should be contained"))
306 }
307
308 #[test]
309 fn test_version_is_satisfied_should_work_for_glob() {
310 let actual = satisfies("1.2.3", "*");
311 assert!(actual.is_ok());
312 assert!(actual.expect("should be contained"))
313 }
314}