nym_mixnet_contract_common/
helpers.rs1use 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 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}