1use ruc::*;
2
3use std::cmp;
4
5use hotmint_types::crypto::PublicKey;
6use hotmint_types::validator::{ValidatorId, ValidatorSet};
7use hotmint_types::validator_update::ValidatorUpdate;
8
9use crate::rewards;
10use crate::store::StakingStore;
11use crate::types::{
12 SlashReason, SlashResult, StakeEntry, StakingConfig, UnbondingEntry, ValidatorState,
13};
14
15pub struct StakingManager<S: StakingStore> {
21 store: S,
22 config: StakingConfig,
23}
24
25impl<S: StakingStore> StakingManager<S> {
26 pub fn new(store: S, config: StakingConfig) -> Self {
27 Self { store, config }
28 }
29
30 pub fn config(&self) -> &StakingConfig {
31 &self.config
32 }
33
34 pub fn store(&self) -> &S {
35 &self.store
36 }
37
38 pub fn store_mut(&mut self) -> &mut S {
39 &mut self.store
40 }
41
42 pub fn register_validator(
46 &mut self,
47 id: ValidatorId,
48 pubkey: PublicKey,
49 self_stake: u64,
50 ) -> Result<()> {
51 if self.store.get_validator(id).is_some() {
52 return Err(eg!("validator {} already registered", id));
53 }
54 if self_stake < self.config.min_self_stake {
55 return Err(eg!(
56 "self-stake {} below minimum {}",
57 self_stake,
58 self.config.min_self_stake
59 ));
60 }
61
62 let state = ValidatorState {
63 id,
64 public_key: pubkey,
65 self_stake,
66 delegated_stake: 0,
67 score: self.config.initial_score,
68 jailed: false,
69 jail_until_height: 0,
70 };
71 self.store.set_validator(id, state);
72 Ok(())
73 }
74
75 pub fn unregister_validator(&mut self, id: ValidatorId) -> Result<u64> {
78 let state = self
79 .store
80 .get_validator(id)
81 .ok_or_else(|| eg!("validator {} not found", id))?;
82 if state.jailed {
83 return Err(eg!("cannot unregister validator {} while jailed", id));
84 }
85 let total = state.total_stake();
86 let stakers = self.store.stakers_of(id);
88 for (addr, _) in stakers {
89 self.store.remove_stake(&addr, id);
90 }
91 self.store.remove_validator(id);
92 Ok(total)
93 }
94
95 pub fn delegate(&mut self, staker: &[u8], validator: ValidatorId, amount: u64) -> Result<()> {
99 if amount == 0 {
100 return Err(eg!("cannot delegate zero amount"));
101 }
102 let mut vs = self
103 .store
104 .get_validator(validator)
105 .ok_or_else(|| eg!("validator {} not found", validator))?;
106
107 vs.delegated_stake = vs
108 .delegated_stake
109 .checked_add(amount)
110 .ok_or_else(|| eg!("delegated stake overflow for validator {}", validator))?;
111 self.store.set_validator(validator, vs);
112
113 let mut entry = self
114 .store
115 .get_stake(staker, validator)
116 .unwrap_or(StakeEntry { amount: 0 });
117 entry.amount = entry
118 .amount
119 .checked_add(amount)
120 .ok_or_else(|| eg!("stake entry overflow"))?;
121 self.store.set_stake(staker, validator, entry);
122 Ok(())
123 }
124
125 pub fn undelegate(
131 &mut self,
132 staker: &[u8],
133 validator: ValidatorId,
134 amount: u64,
135 current_height: u64,
136 ) -> Result<()> {
137 if amount == 0 {
138 return Err(eg!("cannot undelegate zero amount"));
139 }
140 let mut vs = self
141 .store
142 .get_validator(validator)
143 .ok_or_else(|| eg!("validator {} not found", validator))?;
144 let mut entry = self
145 .store
146 .get_stake(staker, validator)
147 .ok_or_else(|| eg!("no stake from staker to validator {}", validator))?;
148
149 if entry.amount < amount {
150 return Err(eg!(
151 "insufficient delegation: have {}, requested {}",
152 entry.amount,
153 amount
154 ));
155 }
156
157 entry.amount -= amount;
158 vs.delegated_stake = vs.delegated_stake.saturating_sub(amount);
159
160 if entry.amount == 0 {
161 self.store.remove_stake(staker, validator);
162 } else {
163 self.store.set_stake(staker, validator, entry);
164 }
165 self.store.set_validator(validator, vs);
166
167 let completion_height = current_height.saturating_add(self.config.unbonding_period);
169 self.store.push_unbonding(UnbondingEntry {
170 staker: staker.to_vec(),
171 validator,
172 amount,
173 completion_height,
174 });
175
176 Ok(())
177 }
178
179 pub fn process_unbonding(&mut self, current_height: u64) -> Vec<UnbondingEntry> {
184 self.store.drain_mature_unbondings(current_height)
185 }
186
187 pub fn slash(
194 &mut self,
195 id: ValidatorId,
196 reason: SlashReason,
197 current_height: u64,
198 ) -> Result<SlashResult> {
199 let mut vs = self
200 .store
201 .get_validator(id)
202 .ok_or_else(|| eg!("validator {} not found", id))?;
203
204 if vs.jailed {
205 return Err(eg!(
206 "validator {} already jailed, refusing double slash",
207 id
208 ));
209 }
210
211 let rate = match reason {
212 SlashReason::DoubleSign => self.config.slash_rate_double_sign,
213 SlashReason::Downtime => self.config.slash_rate_downtime,
214 };
215
216 let self_slash = (vs.self_stake as u128 * rate as u128 / 10_000) as u64;
217 let del_slash = (vs.delegated_stake as u128 * rate as u128 / 10_000) as u64;
218
219 vs.self_stake = vs.self_stake.saturating_sub(self_slash);
220 vs.delegated_stake = vs.delegated_stake.saturating_sub(del_slash);
221 vs.jailed = true;
222 vs.jail_until_height = current_height.saturating_add(self.config.jail_duration);
223 vs.score = vs.score.saturating_sub(self.config.max_score / 10);
224
225 if del_slash > 0 {
229 let stakers = self.store.stakers_of(id);
230 let total_del: u64 = stakers.iter().map(|(_, e)| e.amount).sum();
231 if total_del > 0 {
232 let count = stakers.len();
233 let mut slashed_so_far = 0u64;
234 for (i, (addr, mut entry)) in stakers.into_iter().enumerate() {
235 let staker_slash = if i == count - 1 {
236 del_slash.saturating_sub(slashed_so_far)
238 } else {
239 (entry.amount as u128 * del_slash as u128 / total_del as u128) as u64
240 };
241 slashed_so_far = slashed_so_far.saturating_add(staker_slash);
242 entry.amount = entry.amount.saturating_sub(staker_slash);
243 if entry.amount == 0 {
244 self.store.remove_stake(&addr, id);
245 } else {
246 self.store.set_stake(&addr, id, entry);
247 }
248 }
249 }
250 }
251
252 self.store.set_validator(id, vs);
253
254 let unbondings = self.store.all_unbondings();
256 let mut unbonding_slashed = 0u64;
257 let updated: Vec<UnbondingEntry> = unbondings
258 .into_iter()
259 .map(|mut e| {
260 if e.validator == id && e.amount > 0 {
261 let ub_slash = (e.amount as u128 * rate as u128 / 10_000) as u64;
262 e.amount = e.amount.saturating_sub(ub_slash);
263 unbonding_slashed = unbonding_slashed.saturating_add(ub_slash);
264 }
265 e
266 })
267 .filter(|e| e.amount > 0)
268 .collect();
269 self.store.replace_unbondings(updated);
270
271 Ok(SlashResult {
272 self_slashed: self_slash,
273 delegated_slashed: del_slash.saturating_add(unbonding_slashed),
274 jailed: true,
275 })
276 }
277
278 pub fn unjail(&mut self, id: ValidatorId, current_height: u64) -> Result<()> {
280 let mut vs = self
281 .store
282 .get_validator(id)
283 .ok_or_else(|| eg!("validator {} not found", id))?;
284 if !vs.jailed {
285 return Err(eg!("validator {} is not jailed", id));
286 }
287 if current_height < vs.jail_until_height {
288 return Err(eg!(
289 "validator {} jailed until height {}, current {}",
290 id,
291 vs.jail_until_height,
292 current_height
293 ));
294 }
295 vs.jailed = false;
296 vs.jail_until_height = 0;
297 self.store.set_validator(id, vs);
298 Ok(())
299 }
300
301 pub fn increment_score(&mut self, id: ValidatorId, delta: u32) {
305 if let Some(mut vs) = self.store.get_validator(id) {
306 vs.score = vs.score.saturating_add(delta).min(self.config.max_score);
307 self.store.set_validator(id, vs);
308 }
309 }
310
311 pub fn decrement_score(&mut self, id: ValidatorId, delta: u32) {
313 if let Some(mut vs) = self.store.get_validator(id) {
314 vs.score = vs.score.saturating_sub(delta);
315 self.store.set_validator(id, vs);
316 }
317 }
318
319 pub fn reward_proposer(&mut self, proposer: ValidatorId) -> Result<u64> {
324 let reward = rewards::proposer_reward(&self.config);
325 if reward == 0 {
326 return Ok(0);
327 }
328 let mut vs = self
329 .store
330 .get_validator(proposer)
331 .ok_or_else(|| eg!("proposer {} not found", proposer))?;
332 vs.self_stake = vs
333 .self_stake
334 .checked_add(reward)
335 .ok_or_else(|| eg!("stake overflow on reward"))?;
336 self.store.set_validator(proposer, vs);
337 Ok(reward)
338 }
339
340 pub fn get_validator(&self, id: ValidatorId) -> Option<ValidatorState> {
343 self.store.get_validator(id)
344 }
345
346 pub fn voting_power(&self, id: ValidatorId) -> u64 {
347 self.store
348 .get_validator(id)
349 .map(|vs| vs.voting_power())
350 .unwrap_or(0)
351 }
352
353 pub fn total_staked(&self) -> u64 {
354 self.store
355 .all_validator_ids()
356 .into_iter()
357 .filter_map(|id| self.store.get_validator(id))
358 .map(|vs| vs.total_stake())
359 .fold(0u64, u64::saturating_add)
360 }
361
362 pub fn formal_validator_list(&self) -> Vec<ValidatorState> {
365 let mut active: Vec<ValidatorState> = self
366 .store
367 .all_validator_ids()
368 .into_iter()
369 .filter_map(|id| self.store.get_validator(id))
370 .filter(|vs| !vs.jailed && vs.self_stake >= self.config.min_self_stake)
371 .collect();
372 active.sort_by_key(|v| cmp::Reverse(v.voting_power()));
373 active.truncate(self.config.max_validators);
374 active
375 }
376
377 pub fn compute_validator_updates(&self, current_set: &ValidatorSet) -> Vec<ValidatorUpdate> {
385 let formal = self.formal_validator_list();
386 let mut updates = Vec::new();
387
388 for vs in &formal {
390 let new_power = vs.voting_power();
391 let current_power = current_set.power_of(vs.id);
392 if new_power != current_power {
393 updates.push(ValidatorUpdate {
394 id: vs.id,
395 public_key: vs.public_key.clone(),
396 power: new_power,
397 });
398 }
399 }
400
401 for vi in current_set.validators() {
403 if !formal.iter().any(|f| f.id == vi.id) {
404 updates.push(ValidatorUpdate {
405 id: vi.id,
406 public_key: vi.public_key.clone(),
407 power: 0,
408 });
409 }
410 }
411
412 updates
413 }
414}