nym_cli_commands/
utils.rs

1// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4use anyhow::{anyhow, bail};
5use cosmrs::AccountId;
6use cosmwasm_std::{Addr, Coin as CosmWasmCoin, Decimal};
7use log::error;
8use nym_client_core::config::disk_persistence::CommonClientPaths;
9use nym_validator_client::nyxd::Coin;
10use serde::{Deserialize, Serialize};
11use std::error::Error;
12use std::fmt::{Display, Formatter};
13use std::fs;
14use std::path::{Path, PathBuf};
15
16// TODO: perhaps it should be moved to some global common crate?
17pub fn account_id_to_cw_addr(account_id: &AccountId) -> Addr {
18    // the call to unchecked is fine here as we're converting directly from `AccountId`
19    // which must have been a valid bech32 address
20    Addr::unchecked(account_id.as_ref())
21}
22
23pub fn pretty_coin(coin: &Coin) -> String {
24    let amount = Decimal::from_ratio(coin.amount, 1_000_000u128);
25    let denom = if coin.denom.starts_with('u') {
26        &coin.denom[1..]
27    } else {
28        &coin.denom
29    };
30    format!("{amount} {denom}")
31}
32
33pub fn pretty_cosmwasm_coin(coin: &CosmWasmCoin) -> String {
34    let amount = Decimal::from_ratio(coin.amount, 1_000_000u128);
35    let denom = if coin.denom.starts_with('u') {
36        &coin.denom[1..]
37    } else {
38        &coin.denom
39    };
40    format!("{amount} {denom}")
41}
42
43pub fn pretty_decimal_with_denom(value: Decimal, denom: &str) -> String {
44    // TODO: we might have to truncate the value here (that's why I moved it to separate function)
45    format!("{value} {denom}")
46}
47
48pub fn show_error<E>(e: E)
49where
50    E: Display,
51{
52    error!("{e}");
53}
54
55pub fn show_error_passthrough<E>(e: E) -> E
56where
57    E: Error + Display,
58{
59    error!("{e}");
60    e
61}
62
63#[derive(Serialize)]
64pub(crate) struct DataWrapper<T> {
65    data: T,
66}
67
68impl<T> Display for DataWrapper<T>
69where
70    T: Display,
71{
72    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
73        write!(f, "{}", self.data)
74    }
75}
76
77impl<T> DataWrapper<T> {
78    pub(crate) fn new(data: T) -> Self {
79        DataWrapper { data }
80    }
81}
82
83fn find_toml_value<'a>(root: &'a toml::Value, key: &str) -> Option<&'a toml::Value> {
84    if let toml::Value::Table(table) = root {
85        for (k, v) in table {
86            if k == key {
87                return Some(v);
88            }
89            if v.is_table() {
90                if let Some(res) = find_toml_value(v, key) {
91                    return Some(res);
92                }
93            }
94        }
95    }
96    None
97}
98
99#[derive(Deserialize, Debug)]
100#[serde(untagged)]
101pub(crate) enum CommonConfigsWrapper {
102    // native, socks5, NR, etc. clients
103    NymClients(Box<ClientConfigCommonWrapper>),
104
105    // nym-api
106    NymApi(NymApiConfigLight),
107
108    // anything else that might get introduced
109    Unknown(UnknownConfigWrapper),
110}
111
112impl CommonConfigsWrapper {
113    pub(crate) fn try_load<P: AsRef<Path>>(path: P) -> anyhow::Result<CommonConfigsWrapper> {
114        let content = fs::read_to_string(path)?;
115        Ok(toml::from_str(&content)?)
116    }
117
118    pub(crate) fn try_get_id(&self) -> anyhow::Result<&str> {
119        match self {
120            CommonConfigsWrapper::NymClients(cfg) => cfg.try_get_id(),
121            CommonConfigsWrapper::NymApi(cfg) => Ok(&cfg.base.id),
122            CommonConfigsWrapper::Unknown(cfg) => cfg.try_get_id(),
123        }
124    }
125
126    pub(crate) fn try_get_private_id_key(&self) -> anyhow::Result<PathBuf> {
127        match self {
128            CommonConfigsWrapper::NymClients(cfg) => Ok(cfg
129                .storage_paths
130                .inner
131                .keys
132                .private_identity_key_file
133                .clone()),
134            CommonConfigsWrapper::NymApi(_cfg) => {
135                todo!() //SW this will depend on the new network monitor structure. Ping @Drazen
136            }
137            CommonConfigsWrapper::Unknown(cfg) => cfg.try_get_private_id_key(),
138        }
139    }
140
141    pub(crate) fn try_get_credentials_store(&self) -> anyhow::Result<PathBuf> {
142        match self {
143            CommonConfigsWrapper::NymClients(cfg) => {
144                Ok(cfg.storage_paths.inner.credentials_database.clone())
145            }
146            CommonConfigsWrapper::NymApi(cfg) => Ok(cfg
147                .network_monitor
148                .storage_paths
149                .credentials_database_path
150                .clone()),
151            CommonConfigsWrapper::Unknown(cfg) => cfg.try_get_credentials_store(),
152        }
153    }
154}
155
156// ideally we would have just imported the full nym-api config structure, but that'd have been an overkill,
157// because we'd have to import the whole crate
158#[derive(Deserialize, Debug)]
159pub(crate) struct NymApiConfigLight {
160    base: NymApiConfigBaseLight,
161    network_monitor: NymApiConfigNetworkMonitorLight,
162}
163
164#[derive(Deserialize, Debug)]
165struct NymApiConfigBaseLight {
166    id: String,
167}
168
169#[derive(Deserialize, Debug)]
170struct NymApiConfigNetworkMonitorLight {
171    storage_paths: NetworkMonitorPaths,
172}
173
174#[derive(Deserialize, Debug)]
175struct NetworkMonitorPaths {
176    credentials_database_path: PathBuf,
177}
178
179// a hacky way of reading common data from client configs (native, socks5, etc.)
180// it works because all clients follow the same structure for storage paths
181// (or so I thought)
182#[derive(Deserialize, Debug)]
183pub(crate) struct ClientConfigCommonWrapper {
184    storage_paths: StoragePathsWrapper,
185
186    // ... but they have different structure for `nym_client_core::config::Client`
187    // native client has it on the top layer, whilsts socks5 has it under 'core' table
188    #[serde(flatten)]
189    other: toml::Value,
190}
191
192// wrapper to allow for any additional entries besides the common paths, like allow list for NR
193#[derive(Deserialize, Debug)]
194struct StoragePathsWrapper {
195    #[serde(flatten)]
196    inner: CommonClientPaths,
197}
198
199impl ClientConfigCommonWrapper {
200    pub(crate) fn try_get_id(&self) -> anyhow::Result<&str> {
201        let id_val = find_toml_value(&self.other, "id")
202            .ok_or_else(|| anyhow!("no id field present in the config"))?;
203        if let toml::Value::String(id) = id_val {
204            Ok(id)
205        } else {
206            bail!("no id field present in the config")
207        }
208    }
209}
210
211#[derive(Deserialize, Debug)]
212pub(crate) struct UnknownConfigWrapper {
213    #[serde(flatten)]
214    inner: toml::Value,
215}
216
217impl UnknownConfigWrapper {
218    fn find_value(&self, key: &str) -> Option<&toml::Value> {
219        find_toml_value(&self.inner, key)
220    }
221
222    pub(crate) fn try_get_id(&self) -> anyhow::Result<&str> {
223        let id_val = self
224            .find_value("id")
225            .ok_or_else(|| anyhow!("no id field present in the config"))?;
226        if let toml::Value::String(id) = id_val {
227            Ok(id)
228        } else {
229            bail!("no id field present in the config")
230        }
231    }
232
233    pub(crate) fn try_get_credentials_store(&self) -> anyhow::Result<PathBuf> {
234        let id_val = self
235            .find_value("credentials_database_path")
236            .ok_or_else(|| anyhow!("no 'credentials_database_path' field present in the config"))?;
237        if let toml::Value::String(credentials_store) = id_val {
238            Ok(credentials_store.parse()?)
239        } else {
240            bail!("no 'credentials_database_path' field present in the config")
241        }
242    }
243
244    pub(crate) fn try_get_private_id_key(&self) -> anyhow::Result<PathBuf> {
245        let id_val = self
246            .find_value("keys.private_identity_key_file")
247            .ok_or_else(|| {
248                anyhow!("no 'keys.private_identity_key_file' field present in the config")
249            })?;
250        if let toml::Value::String(pub_id_key) = id_val {
251            Ok(pub_id_key.parse()?)
252        } else {
253            bail!("no 'keys.private_identity_key_file' field present in the config")
254        }
255    }
256}