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