Skip to main content

hotmint_staking/
types.rs

1use serde::{Deserialize, Serialize};
2
3use hotmint_types::crypto::PublicKey;
4use hotmint_types::validator::ValidatorId;
5
6/// Validator state within the staking system.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct ValidatorState {
9    pub id: ValidatorId,
10    pub public_key: PublicKey,
11    /// Self-bonded stake.
12    pub self_stake: u64,
13    /// Total delegated stake from other stakers.
14    pub delegated_stake: u64,
15    /// Reputation score (0 to `config.max_score`).
16    pub score: u32,
17    /// Whether the validator is jailed (temporarily removed from active set).
18    pub jailed: bool,
19    /// Block height until which the validator remains jailed.
20    pub jail_until_height: u64,
21}
22
23impl ValidatorState {
24    /// Total stake = self-bonded + delegated.
25    pub fn total_stake(&self) -> u64 {
26        self.self_stake.saturating_add(self.delegated_stake)
27    }
28
29    /// Voting power: total stake if not jailed, 0 otherwise.
30    pub fn voting_power(&self) -> u64 {
31        if self.jailed { 0 } else { self.total_stake() }
32    }
33}
34
35/// A single delegation entry from a staker to a validator.
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct StakeEntry {
38    pub amount: u64,
39}
40
41/// Reason for slashing a validator.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum SlashReason {
44    /// Equivocation (double-signing).
45    DoubleSign,
46    /// Extended downtime / inactivity.
47    Downtime,
48}
49
50/// Result of a slash operation.
51#[derive(Debug, Clone)]
52pub struct SlashResult {
53    /// Amount slashed from self-stake.
54    pub self_slashed: u64,
55    /// Amount slashed from delegated stakes.
56    pub delegated_slashed: u64,
57    /// Whether the validator was jailed.
58    pub jailed: bool,
59}
60
61/// An entry in the unbonding queue.
62///
63/// When a delegator undelegates, voting power is reduced immediately but
64/// the tokens are locked until `completion_height` to prevent slash evasion.
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct UnbondingEntry {
67    pub staker: Vec<u8>,
68    pub validator: ValidatorId,
69    pub amount: u64,
70    /// Block height at which the unbonding completes.
71    pub completion_height: u64,
72}
73
74/// Staking system configuration.
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct StakingConfig {
77    /// Maximum number of active (non-jailed) validators in the formal set.
78    pub max_validators: usize,
79    /// Minimum self-stake required to register as a validator.
80    pub min_self_stake: u64,
81    /// Slash rate for double-signing (basis points: 500 = 5%).
82    pub slash_rate_double_sign: u32,
83    /// Slash rate for downtime (basis points: 100 = 1%).
84    pub slash_rate_downtime: u32,
85    /// Number of blocks a jailed validator must wait before unjailing.
86    pub jail_duration: u64,
87    /// Initial reputation score for new validators.
88    pub initial_score: u32,
89    /// Maximum reputation score.
90    pub max_score: u32,
91    /// Block reward (added to proposer's self-stake).
92    pub block_reward: u64,
93    /// Unbonding period in blocks. 0 = instant (legacy behavior).
94    pub unbonding_period: u64,
95}
96
97impl StakingConfig {
98    pub fn validate(&self) -> ruc::Result<()> {
99        if self.max_validators == 0 {
100            return Err(ruc::eg!("max_validators must be > 0"));
101        }
102        if self.slash_rate_double_sign > 10_000 {
103            return Err(ruc::eg!(
104                "slash_rate_double_sign must be <= 10000 (basis points)"
105            ));
106        }
107        if self.slash_rate_downtime > 10_000 {
108            return Err(ruc::eg!(
109                "slash_rate_downtime must be <= 10000 (basis points)"
110            ));
111        }
112        if self.initial_score > self.max_score {
113            return Err(ruc::eg!("initial_score must be <= max_score"));
114        }
115        Ok(())
116    }
117}
118
119impl Default for StakingConfig {
120    fn default() -> Self {
121        Self {
122            max_validators: 100,
123            min_self_stake: 1000,
124            slash_rate_double_sign: 500, // 5%
125            slash_rate_downtime: 100,    // 1%
126            jail_duration: 1000,
127            initial_score: 10_000,
128            max_score: 10_000,
129            block_reward: 100,
130            unbonding_period: 1000,
131        }
132    }
133}