1use std::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
10use crate::errors::HoprChainError;
11
12#[derive(Debug, Copy, Clone, Deserialize, Serialize, Eq, PartialEq, strum::Display, strum::EnumString)]
14#[serde(rename_all(deserialize = "lowercase"))]
15#[strum(serialize_all = "lowercase")]
16pub enum EnvironmentType {
17 Production,
18 Staging,
19 Development,
20 Local,
21}
22
23#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
26#[serde(deny_unknown_fields)]
27pub struct ChainOptions {
28 pub description: String,
29 pub chain_id: u32,
31 pub live: bool,
32 pub default_provider: String,
34 pub etherscan_api_url: Option<String>,
36 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)
167 .map_err(|e| HoprChainError::Configuration(format!("failed to deserialize current lib version string: {e}")))?;
168
169 Ok(allowed_versions.matches(&version))
170}
171
172impl ChainNetworkConfig {
173 pub fn new(
175 id: &str,
176 version: &str,
177 maybe_custom_provider: Option<&str>,
178 max_rpc_requests_per_sec: Option<u32>,
179 protocol_config: &mut ProtocolsConfig,
180 ) -> Result<Self, String> {
181 let network = protocol_config
182 .networks
183 .get_mut(id)
184 .ok_or(format!("Could not find network {id} in protocol config"))?;
185
186 let chain = protocol_config
187 .chains
188 .get_mut(&network.chain)
189 .ok_or(format!("Invalid chain {} for network {id}", network.chain))?;
190
191 if let Some(custom_provider) = maybe_custom_provider {
192 chain.default_provider = custom_provider.into();
193 }
194
195 match satisfies(version, network.version_range.as_str()) {
196 Ok(true) => Ok(ChainNetworkConfig {
197 announcements: network.addresses.announcements.to_owned(),
198 chain: chain.to_owned(),
199 channel_contract_deploy_block: network.indexer_start_block_number,
200 channels: network.addresses.channels.to_owned(),
201 confirmations: network.confirmations,
202 environment_type: network.environment_type,
203 id: network.chain.to_owned(),
204 module_implementation: network.addresses.module_implementation.to_owned(),
205 network_registry: network.addresses.network_registry.to_owned(),
206 network_registry_proxy: network.addresses.network_registry_proxy.to_owned(),
207 node_safe_registry: network.addresses.node_safe_registry.to_owned(),
208 node_stake_v2_factory: network.addresses.node_stake_v2_factory.to_owned(),
209 ticket_price_oracle: network.addresses.ticket_price_oracle.to_owned(),
210 winning_probability_oracle: network.addresses.winning_probability_oracle.to_owned(),
211 token: network.addresses.token.to_owned(),
212 tx_polling_interval: network.tx_polling_interval,
213 max_block_range: network.max_block_range,
214 max_requests_per_sec: max_rpc_requests_per_sec.or(chain.max_rpc_requests_per_sec),
215 }),
216 Ok(false) => Err(format!(
217 "network {id} is not supported, supported networks {:?}",
218 protocol_config.supported_networks(version).join(", ")
219 )),
220 Err(e) => Err(e.to_string()),
221 }
222 }
223}
224
225impl From<&ChainNetworkConfig> for ContractAddresses {
226 fn from(network: &ChainNetworkConfig) -> Self {
227 Self {
228 token: network.token,
229 channels: network.channels,
230 announcements: network.announcements,
231 network_registry: network.network_registry,
232 network_registry_proxy: network.network_registry_proxy,
233 safe_registry: network.node_safe_registry,
234 price_oracle: network.ticket_price_oracle,
235 win_prob_oracle: network.winning_probability_oracle,
236 stake_factory: network.node_stake_v2_factory,
237 module_implementation: network.module_implementation,
238 }
239 }
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
245#[serde(deny_unknown_fields)]
246pub struct ProtocolsConfig {
247 pub networks: std::collections::BTreeMap<String, Network>,
248 pub chains: std::collections::BTreeMap<String, ChainOptions>,
249}
250
251impl Default for ProtocolsConfig {
252 fn default() -> Self {
253 Self::from_str(include_str!("../../../hopr/hopr-lib/data/protocol-config.json"))
254 .expect("bundled protocol config should be always valid")
255 }
256}
257
258impl FromStr for ProtocolsConfig {
259 type Err = String;
260
261 fn from_str(data: &str) -> Result<Self, Self::Err> {
263 serde_json::from_str::<ProtocolsConfig>(data).map_err(|e| e.to_string())
264 }
265}
266
267impl std::cmp::PartialEq for ProtocolsConfig {
268 fn eq(&self, other: &Self) -> bool {
269 Vec::from_iter(self.networks.clone()) == Vec::from_iter(other.networks.clone())
270 && Vec::from_iter(self.chains.clone()) == Vec::from_iter(self.chains.clone())
271 }
272}
273
274impl ProtocolsConfig {
275 pub fn supported_networks(&self, version: &str) -> Vec<String> {
278 let mut allowed = vec![];
279
280 for (name, env) in self.networks.iter() {
281 let range = env.version_range.to_owned();
282
283 if let Ok(true) = satisfies(version, range.as_str()) {
284 allowed.push(name.clone())
285 }
286 }
287
288 allowed
289 }
290}
291#[cfg(test)]
292mod test {
293 use super::*;
294
295 #[test]
296 fn test_default_protocol_config_can_be_deserialized() {
297 let _ = ProtocolsConfig::default();
298 }
299
300 #[test]
301 fn test_version_is_satisfied_should_work_on_ranges() {
302 let actual = satisfies("1.90.0", ">=1.89, <1.93");
303 assert!(actual.is_ok());
304 assert!(actual.expect("should be contained"))
305 }
306
307 #[test]
308 fn test_version_is_satisfied_should_work_for_glob() {
309 let actual = satisfies("1.2.3", "*");
310 assert!(actual.is_ok());
311 assert!(actual.expect("should be contained"))
312 }
313}