Skip to main content

nym_mixnet_contract_common/
helpers.rs

1// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::error::MixnetContractError;
5use crate::mixnode::PendingMixNodeChanges;
6use crate::nym_node::NodeOwnershipResponse;
7use crate::{
8    EpochEventId, EpochId, Interval, IntervalEventId, MixNodeBond, MixNodeDetails, NodeId,
9    NodeRewarding, NymNodeBond, NymNodeDetails, PendingNodeChanges, QueryMsg,
10};
11use cosmwasm_std::{
12    Addr, Binary, Coin, CustomQuery, Decimal, QuerierWrapper, StdError, StdResult, Uint128,
13    from_json,
14};
15use cw_storage_plus::{Key, Namespace, Path, PrimaryKey};
16use nym_contracts_common::IdentityKeyRef;
17use serde::de::DeserializeOwned;
18use std::ops::Deref;
19
20pub trait MixnetContractQuerier {
21    #[allow(dead_code)]
22    fn query_mixnet_contract<T: DeserializeOwned>(
23        &self,
24        address: impl Into<String>,
25        msg: &QueryMsg,
26    ) -> StdResult<T>;
27
28    fn query_mixnet_contract_storage(
29        &self,
30        address: impl Into<String>,
31        key: impl Into<Binary>,
32    ) -> StdResult<Option<Vec<u8>>>;
33
34    fn query_mixnet_contract_storage_value<T: DeserializeOwned>(
35        &self,
36        address: impl Into<String>,
37        key: impl Into<Binary>,
38    ) -> StdResult<Option<T>> {
39        match self.query_mixnet_contract_storage(address, key)? {
40            None => Ok(None),
41            Some(value) => Ok(Some(from_json(&value)?)),
42        }
43    }
44
45    fn query_current_mixnet_interval(&self, address: impl Into<String>) -> StdResult<Interval> {
46        self.query_mixnet_contract_storage_value(address, b"ci")?
47            .ok_or(StdError::not_found(
48                "unable to retrieve interval information from the mixnet contract storage",
49            ))
50    }
51
52    fn query_current_absolute_mixnet_epoch_id(
53        &self,
54        address: impl Into<String>,
55    ) -> StdResult<EpochId> {
56        self.query_current_mixnet_interval(address)
57            .map(|interval| interval.current_epoch_absolute_id())
58    }
59
60    fn check_node_existence(&self, address: impl Into<String>, node_id: NodeId) -> StdResult<bool> {
61        let mixnet_contract_address = address.into();
62
63        if let Some(nym_node) = self.query_nymnode_bond(mixnet_contract_address.clone(), node_id)? {
64            return Ok(!nym_node.is_unbonding);
65        }
66
67        Ok(false)
68    }
69
70    fn query_nymnode_bond(
71        &self,
72        address: impl Into<String>,
73        node_id: NodeId,
74    ) -> StdResult<Option<NymNodeBond>> {
75        // construct proper map key
76        let pk_namespace = "nn";
77        let path: Path<NymNodeBond> = Path::new(
78            Namespace::from_static_str(pk_namespace).as_slice(),
79            &node_id.key().iter().map(Key::as_ref).collect::<Vec<_>>(),
80        );
81        let storage_key = path.deref();
82
83        self.query_mixnet_contract_storage_value(address, storage_key)
84    }
85
86    fn query_nymnode_ownership(
87        &self,
88        address: impl Into<String>,
89        owner: &Addr,
90    ) -> StdResult<Option<NymNodeBond>> {
91        let resp: NodeOwnershipResponse = self.query_mixnet_contract(
92            address,
93            &QueryMsg::GetOwnedNymNode {
94                address: owner.to_string(),
95            },
96        )?;
97        Ok(resp.details.map(|d| d.bond_information))
98    }
99}
100
101impl<C> MixnetContractQuerier for QuerierWrapper<'_, C>
102where
103    C: CustomQuery,
104{
105    fn query_mixnet_contract<T: DeserializeOwned>(
106        &self,
107        address: impl Into<String>,
108        msg: &QueryMsg,
109    ) -> StdResult<T> {
110        self.query_wasm_smart(address, msg)
111    }
112
113    fn query_mixnet_contract_storage(
114        &self,
115        address: impl Into<String>,
116        key: impl Into<Binary>,
117    ) -> StdResult<Option<Vec<u8>>> {
118        self.query_wasm_raw(address, key)
119    }
120}
121
122#[track_caller]
123pub fn compare_decimals(a: Decimal, b: Decimal, epsilon: Option<Decimal>) {
124    let epsilon = epsilon.unwrap_or_else(|| Decimal::from_ratio(1u128, 100_000_000u128));
125    if a > b {
126        assert!(a - b < epsilon, "{a} != {b}, delta: {}", a - b)
127    } else {
128        assert!(b - a < epsilon, "{a} != {b}, delta: {}", b - a)
129    }
130}
131
132pub fn into_base_decimal(val: impl Into<Uint128>) -> StdResult<Decimal> {
133    val.into_base_decimal()
134}
135
136pub trait IntoBaseDecimal {
137    fn into_base_decimal(self) -> StdResult<Decimal>;
138}
139
140impl<T> IntoBaseDecimal for T
141where
142    T: Into<Uint128>,
143{
144    fn into_base_decimal(self) -> StdResult<Decimal> {
145        let atomics = self.into();
146        Decimal::from_atomics(atomics, 0).map_err(|_| {
147            StdError::generic_err(format!(
148                "Decimal range exceeded for {atomics} with 0 decimal places."
149            ))
150        })
151    }
152}
153
154pub trait NodeDetails {
155    type Bond: NodeBond;
156    type PendingChanges: PendingChanges;
157
158    fn split(self) -> (Self::Bond, NodeRewarding, Self::PendingChanges);
159    fn rewarding_info(&self) -> &NodeRewarding;
160    fn bond_info(&self) -> &Self::Bond;
161    fn pending_changes(&self) -> &Self::PendingChanges;
162}
163
164pub trait NodeBond {
165    fn node_id(&self) -> NodeId;
166
167    fn is_unbonding(&self) -> bool;
168
169    fn identity(&self) -> IdentityKeyRef<'_>;
170
171    fn original_pledge(&self) -> &Coin;
172
173    fn ensure_bonded(&self) -> Result<(), MixnetContractError> {
174        if self.is_unbonding() {
175            return Err(MixnetContractError::NodeIsUnbonding {
176                node_id: self.node_id(),
177            });
178        }
179        Ok(())
180    }
181}
182
183pub trait PendingChanges {
184    fn pending_pledge_changes(&self) -> Option<EpochEventId>;
185
186    fn pending_cost_params_changes(&self) -> Option<IntervalEventId>;
187
188    fn ensure_no_pending_pledge_changes(&self) -> Result<(), MixnetContractError> {
189        if let Some(pending_event_id) = self.pending_pledge_changes() {
190            return Err(MixnetContractError::PendingPledgeChange { pending_event_id });
191        }
192        Ok(())
193    }
194
195    fn ensure_no_pending_params_changes(&self) -> Result<(), MixnetContractError> {
196        if let Some(pending_event_id) = self.pending_cost_params_changes() {
197            return Err(MixnetContractError::PendingParamsChange { pending_event_id });
198        }
199        Ok(())
200    }
201}
202
203impl NodeDetails for MixNodeDetails {
204    type Bond = MixNodeBond;
205    type PendingChanges = PendingMixNodeChanges;
206
207    fn split(self) -> (Self::Bond, NodeRewarding, Self::PendingChanges) {
208        (
209            self.bond_information,
210            self.rewarding_details,
211            self.pending_changes,
212        )
213    }
214
215    fn rewarding_info(&self) -> &NodeRewarding {
216        &self.rewarding_details
217    }
218
219    fn bond_info(&self) -> &Self::Bond {
220        &self.bond_information
221    }
222
223    fn pending_changes(&self) -> &Self::PendingChanges {
224        &self.pending_changes
225    }
226}
227
228impl NodeBond for MixNodeBond {
229    fn node_id(&self) -> NodeId {
230        self.mix_id
231    }
232
233    fn is_unbonding(&self) -> bool {
234        self.is_unbonding
235    }
236
237    fn identity(&self) -> IdentityKeyRef<'_> {
238        self.identity()
239    }
240
241    fn original_pledge(&self) -> &Coin {
242        self.original_pledge()
243    }
244}
245
246impl PendingChanges for PendingMixNodeChanges {
247    fn pending_pledge_changes(&self) -> Option<EpochEventId> {
248        self.pledge_change
249    }
250
251    fn pending_cost_params_changes(&self) -> Option<IntervalEventId> {
252        self.cost_params_change
253    }
254}
255
256impl NodeDetails for NymNodeDetails {
257    type Bond = NymNodeBond;
258    type PendingChanges = PendingNodeChanges;
259
260    fn split(self) -> (Self::Bond, NodeRewarding, Self::PendingChanges) {
261        (
262            self.bond_information,
263            self.rewarding_details,
264            self.pending_changes,
265        )
266    }
267
268    fn rewarding_info(&self) -> &NodeRewarding {
269        &self.rewarding_details
270    }
271
272    fn bond_info(&self) -> &Self::Bond {
273        &self.bond_information
274    }
275
276    fn pending_changes(&self) -> &Self::PendingChanges {
277        &self.pending_changes
278    }
279}
280
281impl NodeBond for NymNodeBond {
282    fn node_id(&self) -> NodeId {
283        self.node_id
284    }
285
286    fn is_unbonding(&self) -> bool {
287        self.is_unbonding
288    }
289
290    fn identity(&self) -> IdentityKeyRef<'_> {
291        self.identity()
292    }
293
294    fn original_pledge(&self) -> &Coin {
295        &self.original_pledge
296    }
297}
298
299impl PendingChanges for PendingNodeChanges {
300    fn pending_pledge_changes(&self) -> Option<EpochEventId> {
301        self.pledge_change
302    }
303
304    fn pending_cost_params_changes(&self) -> Option<IntervalEventId> {
305        self.cost_params_change
306    }
307}