1use std::{collections::BTreeMap, str::FromStr};
2
3use hopr_chain_types::ContractAddresses;
4use hopr_primitive_types::primitives::Address;
5use semver::{Version, VersionReq};
6use serde::{Deserialize, Serialize};
7use serde_with::{DisplayFromStr, serde_as};
8use validator::Validate;
9
10pub mod errors;
11pub use errors::{HoprConfigError, Result};
12
13#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
16#[serde(deny_unknown_fields)]
17pub struct ChainOptions {
18 pub description: String,
19 pub chain_id: u32,
21 pub live: bool,
22 pub default_provider: String,
24 pub etherscan_api_url: Option<String>,
26 pub max_fee_per_gas: String,
29 pub max_priority_fee_per_gas: String,
31 pub native_token_name: String,
32 pub hopr_token_name: String,
33 pub block_time: u64,
35 pub max_rpc_requests_per_sec: Option<u32>,
37 pub tags: Option<Vec<String>>,
38}
39
40#[derive(Debug, Copy, Clone, Deserialize, Serialize, Eq, PartialEq, strum::Display, strum::EnumString)]
42#[serde(rename_all(deserialize = "lowercase"))]
43#[strum(serialize_all = "lowercase")]
44pub enum EnvironmentType {
45 Production,
46 Staging,
47 Development,
48 Local,
49}
50
51#[serde_as]
54#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
55#[serde(deny_unknown_fields)]
56pub struct Network {
57 pub chain: String,
59 #[serde_as(as = "DisplayFromStr")]
60 pub environment_type: EnvironmentType,
61 pub version_range: String,
63 pub indexer_start_block_number: u32,
65 pub tags: Vec<String>,
66 pub addresses: Addresses,
68 pub confirmations: u32,
70 pub tx_polling_interval: u64,
72 pub max_block_range: u64,
74}
75
76#[serde_as]
77#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
78#[serde(deny_unknown_fields)]
79pub struct Addresses {
80 #[serde_as(as = "DisplayFromStr")]
82 pub network_registry: Address,
83 #[serde_as(as = "DisplayFromStr")]
86 pub network_registry_proxy: Address,
87 #[serde_as(as = "DisplayFromStr")]
89 pub channels: Address,
90 #[serde_as(as = "DisplayFromStr")]
92 pub token: Address,
93 #[serde_as(as = "DisplayFromStr")]
95 pub module_implementation: Address,
96 #[serde_as(as = "DisplayFromStr")]
98 pub node_safe_registry: Address,
99 #[serde_as(as = "DisplayFromStr")]
101 pub ticket_price_oracle: Address,
102 #[serde_as(as = "DisplayFromStr")]
104 pub winning_probability_oracle: Address,
105 #[serde_as(as = "DisplayFromStr")]
107 pub announcements: Address,
108 #[serde_as(as = "DisplayFromStr")]
110 pub node_stake_v2_factory: Address,
111}
112
113#[serde_as]
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct ChainNetworkConfig {
116 pub id: String,
118 pub chain: ChainOptions,
119 pub environment_type: EnvironmentType,
120 pub channel_contract_deploy_block: u32,
121 #[serde_as(as = "DisplayFromStr")]
123 pub network_registry: Address,
124 #[serde_as(as = "DisplayFromStr")]
127 pub network_registry_proxy: Address,
128 #[serde_as(as = "DisplayFromStr")]
130 pub channels: Address,
131 #[serde_as(as = "DisplayFromStr")]
133 pub token: Address,
134 #[serde_as(as = "DisplayFromStr")]
136 pub module_implementation: Address,
137 #[serde_as(as = "DisplayFromStr")]
139 pub node_safe_registry: Address,
140 #[serde_as(as = "DisplayFromStr")]
142 pub ticket_price_oracle: Address,
143 #[serde_as(as = "DisplayFromStr")]
145 pub winning_probability_oracle: Address,
146 #[serde_as(as = "DisplayFromStr")]
148 pub announcements: Address,
149 #[serde_as(as = "DisplayFromStr")]
151 pub node_stake_v2_factory: Address,
152 pub confirmations: u32,
154 pub tx_polling_interval: u64,
156 pub max_block_range: u64,
158 pub max_requests_per_sec: Option<u32>,
160}
161
162fn satisfies(version: &str, allowed_versions: &str) -> Result<bool> {
164 let allowed_versions = VersionReq::parse(allowed_versions)
165 .map_err(|e| HoprConfigError::Configuration(format!("failed to deserialize allowed version string: {e}")))?;
166
167 let version = Version::from_str(version).map_err(|e| {
168 HoprConfigError::Configuration(format!("failed to deserialize current lib version string: {e}"))
169 })?;
170
171 Ok(allowed_versions.matches(&version))
172}
173
174impl ChainNetworkConfig {
175 pub fn new(
178 id: &str,
179 version: &str,
180 maybe_custom_provider: Option<&str>,
181 max_rpc_requests_per_sec: Option<u32>,
182 protocol_config: &mut ProtocolsConfig,
183 ) -> std::result::Result<Self, String> {
184 let network = protocol_config
185 .networks
186 .get_mut(id)
187 .ok_or(format!("Could not find network {id} in protocol config"))?;
188
189 let chain = protocol_config
190 .chains
191 .get_mut(&network.chain)
192 .ok_or(format!("Invalid chain {} for network {id}", network.chain))?;
193
194 if let Some(custom_provider) = maybe_custom_provider {
195 chain.default_provider = custom_provider.into();
196 }
197
198 match satisfies(version, network.version_range.as_str()) {
199 Ok(true) => Ok(ChainNetworkConfig {
200 announcements: network.addresses.announcements.to_owned(),
201 chain: chain.to_owned(),
202 channel_contract_deploy_block: network.indexer_start_block_number,
203 channels: network.addresses.channels.to_owned(),
204 confirmations: network.confirmations,
205 environment_type: network.environment_type,
206 id: network.chain.to_owned(),
207 module_implementation: network.addresses.module_implementation.to_owned(),
208 network_registry: network.addresses.network_registry.to_owned(),
209 network_registry_proxy: network.addresses.network_registry_proxy.to_owned(),
210 node_safe_registry: network.addresses.node_safe_registry.to_owned(),
211 node_stake_v2_factory: network.addresses.node_stake_v2_factory.to_owned(),
212 ticket_price_oracle: network.addresses.ticket_price_oracle.to_owned(),
213 winning_probability_oracle: network.addresses.winning_probability_oracle.to_owned(),
214 token: network.addresses.token.to_owned(),
215 tx_polling_interval: network.tx_polling_interval,
216 max_block_range: network.max_block_range,
217 max_requests_per_sec: max_rpc_requests_per_sec.or(chain.max_rpc_requests_per_sec),
218 }),
219 Ok(false) => Err(HoprConfigError::UnsupportedNetwork(id.into()).to_string()),
220 Err(e) => Err(e.to_string()),
221 }
222 }
223
224 pub fn new_no_version_check(
226 id: &str,
227 maybe_custom_provider: Option<&str>,
228 max_rpc_requests_per_sec: Option<u32>,
229 protocol_config: &mut ProtocolsConfig,
230 ) -> std::result::Result<Self, String> {
231 let network = protocol_config
232 .networks
233 .get_mut(id)
234 .ok_or(format!("Could not find network {id} in protocol config"))?;
235
236 let chain = protocol_config
237 .chains
238 .get_mut(&network.chain)
239 .ok_or(format!("Invalid chain {} for network {id}", network.chain))?;
240
241 if let Some(custom_provider) = maybe_custom_provider {
242 chain.default_provider = custom_provider.into();
243 }
244
245 Ok(ChainNetworkConfig {
246 announcements: network.addresses.announcements.to_owned(),
247 chain: chain.to_owned(),
248 channel_contract_deploy_block: network.indexer_start_block_number,
249 channels: network.addresses.channels.to_owned(),
250 confirmations: network.confirmations,
251 environment_type: network.environment_type,
252 id: network.chain.to_owned(),
253 module_implementation: network.addresses.module_implementation.to_owned(),
254 network_registry: network.addresses.network_registry.to_owned(),
255 network_registry_proxy: network.addresses.network_registry_proxy.to_owned(),
256 node_safe_registry: network.addresses.node_safe_registry.to_owned(),
257 node_stake_v2_factory: network.addresses.node_stake_v2_factory.to_owned(),
258 ticket_price_oracle: network.addresses.ticket_price_oracle.to_owned(),
259 winning_probability_oracle: network.addresses.winning_probability_oracle.to_owned(),
260 token: network.addresses.token.to_owned(),
261 tx_polling_interval: network.tx_polling_interval,
262 max_block_range: network.max_block_range,
263 max_requests_per_sec: max_rpc_requests_per_sec.or(chain.max_rpc_requests_per_sec),
264 })
265 }
266}
267
268impl From<&ChainNetworkConfig> for ContractAddresses {
269 fn from(network: &ChainNetworkConfig) -> Self {
270 Self {
271 token: network.token,
272 channels: network.channels,
273 announcements: network.announcements,
274 network_registry: network.network_registry,
275 network_registry_proxy: network.network_registry_proxy,
276 safe_registry: network.node_safe_registry,
277 price_oracle: network.ticket_price_oracle,
278 win_prob_oracle: network.winning_probability_oracle,
279 stake_factory: network.node_stake_v2_factory,
280 module_implementation: network.module_implementation,
281 }
282 }
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
288#[serde(deny_unknown_fields)]
289pub struct ProtocolsConfig {
290 pub networks: BTreeMap<String, Network>,
291 pub chains: BTreeMap<String, ChainOptions>,
292}
293
294impl Default for ProtocolsConfig {
295 fn default() -> Self {
296 Self::from_str(include_str!(concat!(env!("OUT_DIR"), "/protocol-config.json")))
297 .expect("bundled protocol config should be always valid")
298 }
299}
300
301impl FromStr for ProtocolsConfig {
302 type Err = HoprConfigError;
303
304 fn from_str(data: &str) -> std::result::Result<Self, Self::Err> {
306 serde_json::from_str::<ProtocolsConfig>(data).map_err(|e| HoprConfigError::Configuration(e.to_string()))
307 }
308}
309
310impl std::cmp::PartialEq for ProtocolsConfig {
311 fn eq(&self, other: &Self) -> bool {
312 Vec::from_iter(self.networks.clone()) == Vec::from_iter(other.networks.clone())
313 && Vec::from_iter(self.chains.clone()) == Vec::from_iter(other.chains.clone())
314 }
315}
316
317impl ProtocolsConfig {
318 pub fn supported_networks(&self, version: &str) -> Vec<String> {
321 let mut allowed = vec![];
322
323 for (name, env) in self.networks.iter() {
324 let range = env.version_range.to_owned();
325
326 if let Ok(true) = satisfies(version, range.as_str()) {
327 allowed.push(name.clone())
328 }
329 }
330
331 allowed
332 }
333}
334#[cfg(test)]
335mod test {
336 use super::*;
337
338 #[test]
339 fn test_default_protocol_config_can_be_deserialized() {
340 let _ = ProtocolsConfig::default();
341 }
342
343 #[test]
344 fn test_version_is_satisfied_should_work_on_ranges() {
345 let actual = satisfies("1.90.0", ">=1.89, <1.93");
346 assert!(actual.is_ok());
347 assert!(actual.expect("should be contained"))
348 }
349
350 #[test]
351 fn test_version_is_satisfied_should_work_for_glob() {
352 let actual = satisfies("1.2.3", "*");
353 assert!(actual.is_ok());
354 assert!(actual.expect("should be contained"))
355 }
356}