1use crate::error::MixnetContractError;
5use crate::{EpochEventId, EpochId, Gateway, IntervalEventId, MixNode, NodeId, NodeRewarding};
6use cosmwasm_schema::cw_serde;
7use cosmwasm_std::{Addr, Coin, Decimal, StdError, StdResult};
8use cw_storage_plus::{IntKey, Key, KeyDeserialize, PrimaryKey};
9use nym_contracts_common::IdentityKey;
10use std::fmt::{Display, Formatter};
11
12#[cw_serde]
13#[derive(PartialOrd, Copy, Hash, Eq)]
14#[repr(u8)]
15#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
16#[cfg_attr(
17 feature = "generate-ts",
18 ts(export, export_to = "ts-packages/types/src/types/rust/Role.ts")
19)]
20pub enum Role {
21 #[serde(rename = "eg", alias = "entry", alias = "entry_gateway")]
22 EntryGateway = 0,
23
24 #[serde(rename = "l1", alias = "layer1")]
25 Layer1 = 1,
26
27 #[serde(rename = "l2", alias = "layer2")]
28 Layer2 = 2,
29
30 #[serde(rename = "l3", alias = "layer3")]
31 Layer3 = 3,
32
33 #[serde(rename = "xg", alias = "exit", alias = "exit_gateway")]
34 ExitGateway = 4,
35
36 #[serde(rename = "stb", alias = "standby")]
37 Standby = 128,
38}
39
40impl TryFrom<u8> for Role {
41 type Error = MixnetContractError;
42 fn try_from(value: u8) -> Result<Self, Self::Error> {
43 match value {
44 n if n == Role::EntryGateway as u8 => Ok(Role::EntryGateway),
45 n if n == Role::Layer1 as u8 => Ok(Role::Layer1),
46 n if n == Role::Layer2 as u8 => Ok(Role::Layer2),
47 n if n == Role::Layer3 as u8 => Ok(Role::Layer3),
48 n if n == Role::ExitGateway as u8 => Ok(Role::ExitGateway),
49 n if n == Role::Standby as u8 => Ok(Role::Standby),
50 n => Err(MixnetContractError::UnknownRoleRepresentation { got: n }),
51 }
52 }
53}
54
55impl<'a> PrimaryKey<'a> for Role {
56 type Prefix = <u8 as PrimaryKey<'a>>::Prefix;
57 type SubPrefix = <u8 as PrimaryKey<'a>>::SubPrefix;
58 type Suffix = <u8 as PrimaryKey<'a>>::Suffix;
59 type SuperSuffix = <u8 as PrimaryKey<'a>>::SuperSuffix;
60
61 fn key(&self) -> Vec<Key<'_>> {
62 vec![Key::Val8((*self as u8).to_cw_bytes())]
66 }
67
68 fn joined_key(&self) -> Vec<u8> {
69 (*self as u8).joined_key()
70 }
71
72 fn joined_extra_key(&self, key: &[u8]) -> Vec<u8> {
73 (*self as u8).joined_extra_key(key)
74 }
75}
76
77impl KeyDeserialize for Role {
78 type Output = Role;
79
80 const KEY_ELEMS: u16 = 1;
81
82 fn from_vec(value: Vec<u8>) -> StdResult<Self::Output> {
83 let u8_key: <u8 as KeyDeserialize>::Output = <u8 as KeyDeserialize>::from_vec(value)?;
84 Role::try_from(u8_key).map_err(|err| StdError::generic_err(err.to_string()))
85 }
86
87 fn from_slice(value: &[u8]) -> StdResult<Self::Output> {
88 let u8_key: <u8 as KeyDeserialize>::Output = <u8 as KeyDeserialize>::from_slice(value)?;
89 Role::try_from(u8_key).map_err(|err| StdError::generic_err(err.to_string()))
90 }
91}
92
93impl Role {
94 pub fn first() -> Role {
95 Role::ExitGateway
96 }
97
98 pub fn next(&self) -> Option<Self> {
99 match self {
102 Role::ExitGateway => Some(Role::EntryGateway),
103 Role::EntryGateway => Some(Role::Layer1),
104 Role::Layer1 => Some(Role::Layer2),
105 Role::Layer2 => Some(Role::Layer3),
106 Role::Layer3 => Some(Role::Standby),
107 Role::Standby => None,
108 }
109 }
110
111 pub fn is_first(&self) -> bool {
112 self == &Role::first()
113 }
114
115 pub fn is_standby(&self) -> bool {
116 matches!(self, Role::Standby)
117 }
118
119 pub fn is_mixnode(&self) -> bool {
120 matches!(self, Role::Layer1 | Role::Layer2 | Role::Layer3)
121 }
122}
123
124impl Display for Role {
125 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
126 match self {
127 Role::Layer1 => write!(f, "mix layer 1"),
128 Role::Layer2 => write!(f, "mix layer 2"),
129 Role::Layer3 => write!(f, "mix layer 3"),
130 Role::EntryGateway => write!(f, "entry gateway"),
131 Role::ExitGateway => write!(f, "exit gateway"),
132 Role::Standby => write!(f, "standby"),
133 }
134 }
135}
136
137#[cw_serde]
139#[derive(Default, Copy)]
140pub struct RewardedSetMetadata {
141 pub epoch_id: EpochId,
143
144 pub fully_assigned: bool,
146
147 pub entry_gateway_metadata: RoleMetadata,
149
150 pub exit_gateway_metadata: RoleMetadata,
152
153 pub layer1_metadata: RoleMetadata,
155
156 pub layer2_metadata: RoleMetadata,
158
159 pub layer3_metadata: RoleMetadata,
161
162 pub standby_metadata: RoleMetadata,
164}
165
166impl RewardedSetMetadata {
167 pub fn new(epoch_id: EpochId) -> Self {
168 RewardedSetMetadata {
169 epoch_id,
170 fully_assigned: false,
171 entry_gateway_metadata: Default::default(),
172 exit_gateway_metadata: Default::default(),
173 layer1_metadata: Default::default(),
174 layer2_metadata: Default::default(),
175 layer3_metadata: Default::default(),
176 standby_metadata: Default::default(),
177 }
178 }
179
180 pub fn set_role_count(&mut self, role: Role, num_nodes: u32) {
181 match role {
182 Role::EntryGateway => self.entry_gateway_metadata.num_nodes = num_nodes,
183 Role::Layer1 => self.layer1_metadata.num_nodes = num_nodes,
184 Role::Layer2 => self.layer2_metadata.num_nodes = num_nodes,
185 Role::Layer3 => self.layer3_metadata.num_nodes = num_nodes,
186 Role::ExitGateway => self.exit_gateway_metadata.num_nodes = num_nodes,
187 Role::Standby => self.standby_metadata.num_nodes = num_nodes,
188 }
189 }
190
191 pub fn set_highest_id(&mut self, highest_id: NodeId, role: Role) {
192 match role {
193 Role::EntryGateway => self.entry_gateway_metadata.highest_id = highest_id,
194 Role::Layer1 => self.layer1_metadata.highest_id = highest_id,
195 Role::Layer2 => self.layer2_metadata.highest_id = highest_id,
196 Role::Layer3 => self.layer3_metadata.highest_id = highest_id,
197 Role::ExitGateway => self.exit_gateway_metadata.highest_id = highest_id,
198 Role::Standby => self.standby_metadata.highest_id = highest_id,
199 }
200 }
201
202 pub fn highest_rewarded_id(&self) -> NodeId {
203 let mut highest = 0;
204 if self.layer1_metadata.highest_id > highest {
205 highest = self.layer1_metadata.highest_id;
206 }
207 if self.layer2_metadata.highest_id > highest {
208 highest = self.layer2_metadata.highest_id;
209 }
210 if self.layer3_metadata.highest_id > highest {
211 highest = self.layer3_metadata.highest_id;
212 }
213 if self.entry_gateway_metadata.highest_id > highest {
214 highest = self.entry_gateway_metadata.highest_id;
215 }
216 if self.exit_gateway_metadata.highest_id > highest {
217 highest = self.exit_gateway_metadata.highest_id;
218 }
219 if self.standby_metadata.highest_id > highest {
220 highest = self.standby_metadata.highest_id;
221 }
222
223 highest
224 }
225}
226
227#[cw_serde]
229#[derive(Default, Copy)]
230pub struct RoleMetadata {
231 pub highest_id: NodeId,
233
234 pub num_nodes: u32,
236}
237
238#[cw_serde]
240#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
241pub struct NymNodeDetails {
242 pub bond_information: NymNodeBond,
244
245 pub rewarding_details: NodeRewarding,
247
248 pub pending_changes: PendingNodeChanges,
250}
251
252impl NymNodeDetails {
253 pub fn new(
254 bond_information: NymNodeBond,
255 rewarding_details: NodeRewarding,
256 pending_changes: PendingNodeChanges,
257 ) -> Self {
258 NymNodeDetails {
259 bond_information,
260 rewarding_details,
261 pending_changes,
262 }
263 }
264
265 pub fn node_id(&self) -> NodeId {
266 self.bond_information.node_id
267 }
268
269 pub fn is_unbonding(&self) -> bool {
270 self.bond_information.is_unbonding
271 }
272
273 pub fn original_pledge(&self) -> &Coin {
274 &self.bond_information.original_pledge
275 }
276
277 pub fn pending_operator_reward(&self) -> Coin {
278 let pledge = self.original_pledge();
279 self.rewarding_details.pending_operator_reward(pledge)
280 }
281
282 pub fn pending_detailed_operator_reward(&self) -> StdResult<Decimal> {
283 let pledge = self.original_pledge();
284 self.rewarding_details
285 .pending_detailed_operator_reward(pledge)
286 }
287
288 pub fn total_stake(&self) -> Decimal {
289 self.rewarding_details.node_bond()
290 }
291
292 pub fn pending_pledge_change(&self) -> Option<EpochEventId> {
293 self.pending_changes.pledge_change
294 }
295}
296
297#[cw_serde]
298#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
299pub struct NymNodeBond {
300 #[cfg_attr(feature = "utoipa", schema(value_type = u32))]
302 pub node_id: NodeId,
303
304 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
306 pub owner: Addr,
307
308 #[cfg_attr(feature = "utoipa", schema(value_type = crate::CoinSchema))]
311 pub original_pledge: Coin,
312
313 pub bonding_height: u64,
315
316 pub is_unbonding: bool,
319
320 pub node: NymNode,
322}
323
324impl NymNodeBond {
325 pub fn new(
326 node_id: NodeId,
327 owner: Addr,
328 original_pledge: Coin,
329 node: impl Into<NymNode>,
330 bonding_height: u64,
331 ) -> NymNodeBond {
332 Self {
333 node_id,
334 owner,
335 original_pledge,
336 bonding_height,
337 is_unbonding: false,
338 node: node.into(),
339 }
340 }
341
342 pub fn identity(&self) -> &str {
343 &self.node.identity_key
344 }
345
346 pub fn ensure_bonded(&self) -> Result<(), MixnetContractError> {
347 if self.is_unbonding {
348 return Err(MixnetContractError::NodeIsUnbonding {
349 node_id: self.node_id,
350 });
351 }
352 Ok(())
353 }
354}
355
356#[cw_serde]
358#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
359#[cfg_attr(
360 feature = "generate-ts",
361 ts(export, export_to = "ts-packages/types/src/types/rust/NymNode.ts")
362)]
363#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
364pub struct NymNode {
365 pub host: String,
368
369 pub custom_http_port: Option<u16>,
372
373 #[cfg_attr(feature = "utoipa", schema(value_type = String))]
375 pub identity_key: IdentityKey,
376 }
379
380impl NymNode {
381 pub fn naive_ensure_valid_pubkey(&self) -> Result<(), MixnetContractError> {
384 let decoded = bs58::decode(&self.identity_key)
385 .into_vec()
386 .map_err(|_| MixnetContractError::InvalidPubKey)?;
387 if decoded.len() != 32 {
388 return Err(MixnetContractError::InvalidPubKey);
389 }
390 Ok(())
391 }
392
393 pub fn ensure_host_in_range(&self) -> Result<(), MixnetContractError> {
395 if self.host.len() > 255 {
396 return Err(MixnetContractError::HostTooLong);
397 }
398 Ok(())
399 }
400}
401
402impl From<MixNode> for NymNode {
403 fn from(value: MixNode) -> Self {
404 NymNode {
405 host: value.host,
406 custom_http_port: Some(value.http_api_port),
407 identity_key: value.identity_key,
408 }
409 }
410}
411
412impl From<Gateway> for NymNode {
413 fn from(value: Gateway) -> Self {
414 NymNode {
415 host: value.host,
416 custom_http_port: None,
417 identity_key: value.identity_key,
418 }
419 }
420}
421
422#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
423#[cfg_attr(
424 feature = "generate-ts",
425 ts(
426 export,
427 export_to = "ts-packages/types/src/types/rust/NodeConfigUpdate.ts"
428 )
429)]
430#[cw_serde]
431#[derive(Default)]
432pub struct NodeConfigUpdate {
433 pub host: Option<String>,
434 pub custom_http_port: Option<u16>,
436
437 #[serde(default)]
439 pub restore_default_http_port: bool,
440}
441
442#[cw_serde]
443#[derive(Default, Copy)]
444#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
445#[cfg_attr(
446 feature = "generate-ts",
447 ts(
448 export,
449 export_to = "ts-packages/types/src/types/rust/PendingNodeChanges.ts"
450 )
451)]
452#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
453pub struct PendingNodeChanges {
454 #[cfg_attr(feature = "utoipa", schema(value_type = Option<u32>))]
455 pub pledge_change: Option<EpochEventId>,
456 #[cfg_attr(feature = "utoipa", schema(value_type = Option<u32>))]
457 pub cost_params_change: Option<IntervalEventId>,
458}
459
460impl PendingNodeChanges {
461 pub fn new_empty() -> PendingNodeChanges {
462 PendingNodeChanges {
463 pledge_change: None,
464 cost_params_change: None,
465 }
466 }
467
468 pub fn ensure_no_pending_pledge_changes(&self) -> Result<(), MixnetContractError> {
469 if let Some(pending_event_id) = self.pledge_change {
470 return Err(MixnetContractError::PendingPledgeChange { pending_event_id });
471 }
472 Ok(())
473 }
474
475 pub fn ensure_no_pending_params_changes(&self) -> Result<(), MixnetContractError> {
476 if let Some(pending_event_id) = self.cost_params_change {
477 return Err(MixnetContractError::PendingParamsChange { pending_event_id });
478 }
479 Ok(())
480 }
481}
482
483#[cw_serde]
485pub struct UnbondedNymNode {
486 pub identity_key: IdentityKey,
488
489 pub node_id: NodeId,
491
492 pub owner: Addr,
494
495 pub unbonding_height: u64,
497}
498
499#[cw_serde]
501pub struct NodeRewardingDetailsResponse {
502 pub node_id: NodeId,
504
505 pub rewarding_details: Option<NodeRewarding>,
507}
508
509#[cw_serde]
511pub struct NodeOwnershipResponse {
512 pub address: Addr,
514
515 pub details: Option<NymNodeDetails>,
517}
518
519#[cw_serde]
521pub struct NodeDetailsResponse {
522 pub node_id: NodeId,
524
525 pub details: Option<NymNodeDetails>,
527}
528
529#[cw_serde]
531pub struct NodeDetailsByIdentityResponse {
532 pub identity_key: IdentityKey,
534
535 pub details: Option<NymNodeDetails>,
537}
538
539#[cw_serde]
541pub struct StakeSaturationResponse {
542 pub node_id: NodeId,
544
545 pub current_saturation: Option<Decimal>,
548
549 pub uncapped_saturation: Option<Decimal>,
553}
554
555#[cw_serde]
557pub struct PagedUnbondedNymNodesResponse {
558 pub nodes: Vec<UnbondedNymNode>,
560
561 pub start_next_after: Option<NodeId>,
563}
564
565#[cw_serde]
567pub struct UnbondedNodeResponse {
568 pub node_id: NodeId,
570
571 pub details: Option<UnbondedNymNode>,
573}
574
575#[cw_serde]
576pub struct PagedNymNodeBondsResponse {
577 pub nodes: Vec<NymNodeBond>,
579
580 pub start_next_after: Option<NodeId>,
582}
583
584#[cw_serde]
585pub struct PagedNymNodeDetailsResponse {
586 pub nodes: Vec<NymNodeDetails>,
590
591 pub start_next_after: Option<NodeId>,
593}
594
595#[cw_serde]
596pub struct EpochAssignmentResponse {
597 pub epoch_id: EpochId,
599
600 pub nodes: Vec<NodeId>,
601}
602
603#[cw_serde]
604pub struct RolesMetadataResponse {
605 pub metadata: RewardedSetMetadata,
606}