slashing.rs
1 // Copyright (c) 2025-2026 ACDC Network 2 // This file is part of the alphaos library. 3 // 4 // Alpha Chain | Delta Chain Protocol 5 // International Monetary Graphite. 6 // 7 // Derived from Aleo (https://aleo.org) and ProvableHQ (https://provable.com). 8 // They built world-class ZK infrastructure. We installed the EASY button. 9 // Their cryptography: elegant. Our modifications: bureaucracy-compatible. 10 // Original brilliance: theirs. Robert's Rules: ours. Bugs: definitely ours. 11 // 12 // Original Aleo/ProvableHQ code subject to Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0 13 // All modifications and new work: CC0 1.0 Universal Public Domain Dedication. 14 // No rights reserved. No permission required. No warranty. No refunds. 15 // 16 // https://creativecommons.org/publicdomain/zero/1.0/ 17 // SPDX-License-Identifier: CC0-1.0 18 19 //! CLP-to-Slashing Integration (A003 Phase 6.4) 20 //! 21 //! This module bridges CLP penalty evaluation with the T006 slashing infrastructure. 22 //! When a validator fails CLP challenges, this module converts the CLP penalty 23 //! into a SlashingEvent that can be recorded and processed by the validator 24 //! performance tracking system. 25 //! 26 //! ## Integration Flow 27 //! 28 //! 1. CLP `PenaltyEvaluator` evaluates epoch and produces `ClpEpochResult` 29 //! 2. `ClpSlashingBridge` converts slashed validators into `ClpSlashingEvent`s 30 //! 3. Events are emitted via `SlashingEventEmitter` trait for downstream processing 31 //! 4. T006 `PerformanceRegistry` records the slashing events 32 33 use super::{ClpConfig, ClpEpochResult, ValidatorAddress}; 34 use serde::{Deserialize, Serialize}; 35 use std::collections::HashMap; 36 use tracing::{info, warn}; 37 38 // ============================================================================ 39 // CLP Slashing Event 40 // ============================================================================ 41 42 /// A CLP-originated slashing event ready for T006 integration. 43 /// 44 /// This is the CLP-specific representation before conversion to the 45 /// generic `SlashingEvent` type in adnet-consensus. 46 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 47 pub struct ClpSlashingEvent { 48 /// Epoch when the slashing occurred. 49 pub epoch: u64, 50 /// Validator being slashed. 51 pub validator: ValidatorAddress, 52 /// Slash amount in basis points. 53 pub slash_bps: u16, 54 /// Calculated slash amount (based on stake). 55 pub slash_amount: u64, 56 /// Whether the validator was ejected. 57 pub ejected: bool, 58 /// CLP-specific metadata. 59 pub metadata: ClpSlashingMetadata, 60 } 61 62 /// Metadata about the CLP failure. 63 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 64 pub struct ClpSlashingMetadata { 65 /// Response rate for the epoch. 66 pub response_rate: u64, // Stored as percentage * 100 (e.g., 8500 = 85.00%) 67 /// Total challenges in the epoch. 68 pub total_challenges: u32, 69 /// Challenges responded to. 70 pub responses: u32, 71 /// Challenges missed. 72 pub missed: u32, 73 /// Consecutive failing epochs. 74 pub consecutive_failures: u32, 75 } 76 77 impl ClpSlashingEvent { 78 /// Create a new CLP slashing event. 79 pub fn new( 80 epoch: u64, 81 validator: ValidatorAddress, 82 slash_bps: u16, 83 slash_amount: u64, 84 ejected: bool, 85 metadata: ClpSlashingMetadata, 86 ) -> Self { 87 Self { epoch, validator, slash_bps, slash_amount, ejected, metadata } 88 } 89 90 /// Check if this is a severe slashing (ejection). 91 pub fn is_severe(&self) -> bool { 92 self.ejected || self.slash_bps >= 100 // 1% or more 93 } 94 } 95 96 // ============================================================================ 97 // Slashing Event Emitter Trait 98 // ============================================================================ 99 100 /// Trait for emitting slashing events to downstream systems. 101 /// 102 /// Implementations can: 103 /// - Record to local performance registry 104 /// - Broadcast to network 105 /// - Log to audit trail 106 /// - Trigger governance notifications 107 pub trait SlashingEventEmitter: Send + Sync { 108 /// Emit a CLP slashing event. 109 fn emit_clp_slashing(&self, event: ClpSlashingEvent); 110 111 /// Emit multiple slashing events (batch). 112 fn emit_clp_slashings(&self, events: Vec<ClpSlashingEvent>) { 113 for event in events { 114 self.emit_clp_slashing(event); 115 } 116 } 117 } 118 119 /// A no-op emitter for testing. 120 pub struct NoOpEmitter; 121 122 impl SlashingEventEmitter for NoOpEmitter { 123 fn emit_clp_slashing(&self, _event: ClpSlashingEvent) {} 124 } 125 126 /// A logging emitter that writes to tracing. 127 pub struct LoggingEmitter; 128 129 impl SlashingEventEmitter for LoggingEmitter { 130 fn emit_clp_slashing(&self, event: ClpSlashingEvent) { 131 if event.ejected { 132 warn!( 133 "CLP EJECTION: validator {} ejected at epoch {} (rate: {:.2}%, consecutive failures: {})", 134 event.validator, 135 event.epoch, 136 event.metadata.response_rate as f64 / 100.0, 137 event.metadata.consecutive_failures 138 ); 139 } else { 140 info!( 141 "CLP SLASH: validator {} slashed {} bps at epoch {} (rate: {:.2}%)", 142 event.validator, 143 event.slash_bps, 144 event.epoch, 145 event.metadata.response_rate as f64 / 100.0 146 ); 147 } 148 } 149 } 150 151 /// A collecting emitter that stores events for later inspection. 152 #[derive(Debug, Default)] 153 pub struct CollectingEmitter { 154 events: std::sync::Mutex<Vec<ClpSlashingEvent>>, 155 } 156 157 impl CollectingEmitter { 158 /// Create a new collecting emitter. 159 pub fn new() -> Self { 160 Self { events: std::sync::Mutex::new(Vec::new()) } 161 } 162 163 /// Get all collected events. 164 pub fn events(&self) -> Vec<ClpSlashingEvent> { 165 self.events.lock().unwrap().clone() 166 } 167 168 /// Clear all collected events. 169 pub fn clear(&self) { 170 self.events.lock().unwrap().clear(); 171 } 172 173 /// Get the count of collected events. 174 pub fn len(&self) -> usize { 175 self.events.lock().unwrap().len() 176 } 177 178 /// Check if no events have been collected. 179 pub fn is_empty(&self) -> bool { 180 self.events.lock().unwrap().is_empty() 181 } 182 } 183 184 impl SlashingEventEmitter for CollectingEmitter { 185 fn emit_clp_slashing(&self, event: ClpSlashingEvent) { 186 self.events.lock().unwrap().push(event); 187 } 188 } 189 190 // ============================================================================ 191 // Helper Functions 192 // ============================================================================ 193 194 /// Calculate the slash amount from stake and basis points (static version). 195 /// 196 /// This is a free function to avoid borrow checker issues in the bridge. 197 fn calculate_slash_amount_static(stake: u64, slash_bps: u16) -> u64 { 198 (stake as u128 * slash_bps as u128 / 10000) as u64 199 } 200 201 // ============================================================================ 202 // CLP Slashing Bridge 203 // ============================================================================ 204 205 /// Bridge that converts CLP epoch results into slashing events. 206 /// 207 /// This component takes the output of CLP penalty evaluation and converts 208 /// it into actionable slashing events for the T006 validator performance system. 209 pub struct ClpSlashingBridge<E: SlashingEventEmitter> { 210 config: ClpConfig, 211 emitter: E, 212 /// Validator stakes (address -> stake amount). 213 stakes: HashMap<ValidatorAddress, u64>, 214 /// Tracks consecutive failures per validator. 215 consecutive_failures: HashMap<ValidatorAddress, u32>, 216 /// Statistics. 217 stats: BridgeStats, 218 } 219 220 /// Statistics for the slashing bridge. 221 #[derive(Debug, Clone, Default)] 222 pub struct BridgeStats { 223 /// Total epochs processed. 224 pub epochs_processed: u64, 225 /// Total slashing events emitted. 226 pub slashings_emitted: u64, 227 /// Total ejections emitted. 228 pub ejections_emitted: u64, 229 /// Total amount slashed (sum of all slash_amount fields). 230 pub total_amount_slashed: u64, 231 } 232 233 impl<E: SlashingEventEmitter> ClpSlashingBridge<E> { 234 /// Create a new slashing bridge. 235 pub fn new(config: ClpConfig, emitter: E) -> Self { 236 Self { 237 config, 238 emitter, 239 stakes: HashMap::new(), 240 consecutive_failures: HashMap::new(), 241 stats: BridgeStats::default(), 242 } 243 } 244 245 /// Register a validator's stake for slash calculation. 246 pub fn register_stake(&mut self, validator: ValidatorAddress, stake: u64) { 247 self.stakes.insert(validator, stake); 248 } 249 250 /// Update stakes in bulk. 251 pub fn update_stakes(&mut self, stakes: HashMap<ValidatorAddress, u64>) { 252 self.stakes = stakes; 253 } 254 255 /// Process a CLP epoch result and emit slashing events. 256 pub fn process_epoch_result( 257 &mut self, 258 result: &ClpEpochResult, 259 response_rates: &HashMap<ValidatorAddress, f64>, 260 clp_statuses: &HashMap<ValidatorAddress, (u32, u32, u32)>, // (total, responses, missed) 261 ) -> Vec<ClpSlashingEvent> { 262 let mut events = Vec::new(); 263 264 // Update consecutive failures for passed validators 265 for validator in &result.passed { 266 self.consecutive_failures.remove(validator); 267 } 268 269 // Process slashed validators 270 for (validator, slash_bps) in &result.slashed { 271 // Calculate slash amount first (only needs stake) 272 let stake = self.stakes.get(validator).copied().unwrap_or(0); 273 let slash_amount = calculate_slash_amount_static(stake, *slash_bps); 274 275 // Get response rate 276 let rate = response_rates.get(validator).copied().unwrap_or(0.0); 277 let rate_pct = (rate * 10000.0) as u64; // Convert to basis points 278 279 // Get CLP status 280 let (total, responses, missed) = clp_statuses.get(validator).copied().unwrap_or((0, 0, 0)); 281 282 // Check if ejected 283 let ejected = result.ejected.contains(validator); 284 285 // Update consecutive failures and get the new value 286 let failures = self.consecutive_failures.entry(validator.clone()).or_insert(0); 287 *failures += 1; 288 let failure_count = *failures; 289 290 let event = ClpSlashingEvent::new( 291 result.epoch, 292 validator.clone(), 293 *slash_bps, 294 slash_amount, 295 ejected, 296 ClpSlashingMetadata { 297 response_rate: rate_pct, 298 total_challenges: total, 299 responses, 300 missed, 301 consecutive_failures: failure_count, 302 }, 303 ); 304 305 events.push(event); 306 } 307 308 // Update stats 309 self.stats.epochs_processed += 1; 310 self.stats.slashings_emitted += events.len() as u64; 311 self.stats.ejections_emitted += result.ejected.len() as u64; 312 self.stats.total_amount_slashed += events.iter().map(|e| e.slash_amount).sum::<u64>(); 313 314 // Emit events 315 self.emitter.emit_clp_slashings(events.clone()); 316 317 info!( 318 "Processed CLP epoch {} results: {} slashed, {} ejected", 319 result.epoch, 320 result.slashed.len(), 321 result.ejected.len() 322 ); 323 324 events 325 } 326 327 /// Calculate the slash amount from stake and basis points. 328 pub fn calculate_slash_amount(&self, stake: u64, slash_bps: u16) -> u64 { 329 calculate_slash_amount_static(stake, slash_bps) 330 } 331 332 /// Get the current configuration. 333 pub fn config(&self) -> &ClpConfig { 334 &self.config 335 } 336 337 /// Get bridge statistics. 338 pub fn stats(&self) -> &BridgeStats { 339 &self.stats 340 } 341 342 /// Get consecutive failures for a validator. 343 pub fn consecutive_failures(&self, validator: &ValidatorAddress) -> u32 { 344 self.consecutive_failures.get(validator).copied().unwrap_or(0) 345 } 346 347 /// Reset consecutive failures for a validator. 348 pub fn reset_failures(&mut self, validator: &ValidatorAddress) { 349 self.consecutive_failures.remove(validator); 350 } 351 } 352 353 // ============================================================================ 354 // Tests 355 // ============================================================================ 356 357 #[cfg(test)] 358 mod tests { 359 use super::*; 360 361 fn make_validator(id: u8) -> ValidatorAddress { 362 let mut addr = [0u8; 32]; 363 addr[0] = id; 364 ValidatorAddress(addr) 365 } 366 367 #[test] 368 fn test_clp_slashing_event_creation() { 369 let validator = make_validator(1); 370 let metadata = ClpSlashingMetadata { 371 response_rate: 8500, // 85% 372 total_challenges: 100, 373 responses: 85, 374 missed: 15, 375 consecutive_failures: 1, 376 }; 377 378 // Use 50 bps (0.5%) which is not severe (below 100 bps threshold) 379 let event = ClpSlashingEvent::new(10, validator.clone(), 50, 500_000, false, metadata); 380 381 assert_eq!(event.epoch, 10); 382 assert_eq!(event.validator, validator); 383 assert_eq!(event.slash_bps, 50); 384 assert_eq!(event.slash_amount, 500_000); 385 assert!(!event.ejected); 386 assert!(!event.is_severe()); 387 } 388 389 #[test] 390 fn test_clp_slashing_event_severe() { 391 let validator = make_validator(1); 392 let metadata = ClpSlashingMetadata { 393 response_rate: 7000, 394 total_challenges: 100, 395 responses: 70, 396 missed: 30, 397 consecutive_failures: 3, 398 }; 399 400 let event = ClpSlashingEvent::new(10, validator, 100, 1_000_000, true, metadata); 401 402 assert!(event.ejected); 403 assert!(event.is_severe()); 404 } 405 406 #[test] 407 fn test_no_op_emitter() { 408 let emitter = NoOpEmitter; 409 let validator = make_validator(1); 410 let metadata = ClpSlashingMetadata { 411 response_rate: 8500, 412 total_challenges: 100, 413 responses: 85, 414 missed: 15, 415 consecutive_failures: 1, 416 }; 417 418 let event = ClpSlashingEvent::new(10, validator, 100, 1_000_000, false, metadata); 419 420 // Should not panic 421 emitter.emit_clp_slashing(event); 422 } 423 424 #[test] 425 fn test_collecting_emitter() { 426 let emitter = CollectingEmitter::new(); 427 let validator = make_validator(1); 428 let metadata = ClpSlashingMetadata { 429 response_rate: 8500, 430 total_challenges: 100, 431 responses: 85, 432 missed: 15, 433 consecutive_failures: 1, 434 }; 435 436 assert!(emitter.is_empty()); 437 438 let event = ClpSlashingEvent::new(10, validator, 100, 1_000_000, false, metadata); 439 emitter.emit_clp_slashing(event); 440 441 assert_eq!(emitter.len(), 1); 442 assert!(!emitter.is_empty()); 443 444 let events = emitter.events(); 445 assert_eq!(events.len(), 1); 446 assert_eq!(events[0].epoch, 10); 447 448 emitter.clear(); 449 assert!(emitter.is_empty()); 450 } 451 452 #[test] 453 fn test_bridge_creation() { 454 let config = ClpConfig::default(); 455 let emitter = CollectingEmitter::new(); 456 let bridge = ClpSlashingBridge::new(config, emitter); 457 458 assert_eq!(bridge.stats().epochs_processed, 0); 459 assert_eq!(bridge.stats().slashings_emitted, 0); 460 } 461 462 #[test] 463 fn test_bridge_register_stake() { 464 let config = ClpConfig::default(); 465 let emitter = CollectingEmitter::new(); 466 let mut bridge = ClpSlashingBridge::new(config, emitter); 467 468 let validator = make_validator(1); 469 bridge.register_stake(validator.clone(), 100_000_000); 470 471 // Stake is registered (internal state) 472 assert_eq!(bridge.stakes.get(&validator), Some(&100_000_000)); 473 } 474 475 #[test] 476 fn test_bridge_process_epoch_result() { 477 let config = ClpConfig::default(); 478 let emitter = CollectingEmitter::new(); 479 let mut bridge = ClpSlashingBridge::new(config, emitter); 480 481 let validator1 = make_validator(1); 482 let validator2 = make_validator(2); 483 let validator3 = make_validator(3); 484 485 // Register stakes 486 bridge.register_stake(validator1.clone(), 100_000_000); 487 bridge.register_stake(validator2.clone(), 200_000_000); 488 bridge.register_stake(validator3.clone(), 150_000_000); 489 490 // Create epoch result 491 let result = ClpEpochResult { 492 epoch: 5, 493 passed: vec![validator1.clone()], 494 warned: vec![], 495 slashed: vec![(validator2.clone(), 50), (validator3.clone(), 100)], 496 ejected: vec![], 497 }; 498 499 // Create response rates 500 let mut response_rates = HashMap::new(); 501 response_rates.insert(validator1.clone(), 0.98); 502 response_rates.insert(validator2.clone(), 0.85); 503 response_rates.insert(validator3.clone(), 0.78); 504 505 // Create CLP statuses 506 let mut clp_statuses = HashMap::new(); 507 clp_statuses.insert(validator1, (100, 98, 2)); 508 clp_statuses.insert(validator2.clone(), (100, 85, 15)); 509 clp_statuses.insert(validator3.clone(), (100, 78, 22)); 510 511 let events = bridge.process_epoch_result(&result, &response_rates, &clp_statuses); 512 513 assert_eq!(events.len(), 2); 514 515 // Check validator2's event 516 let event2 = events.iter().find(|e| e.validator == validator2).unwrap(); 517 assert_eq!(event2.slash_bps, 50); 518 assert_eq!(event2.slash_amount, 1_000_000); // 0.5% of 200M 519 assert!(!event2.ejected); 520 521 // Check validator3's event 522 let event3 = events.iter().find(|e| e.validator == validator3).unwrap(); 523 assert_eq!(event3.slash_bps, 100); 524 assert_eq!(event3.slash_amount, 1_500_000); // 1% of 150M 525 assert!(!event3.ejected); 526 527 // Check stats 528 assert_eq!(bridge.stats().epochs_processed, 1); 529 assert_eq!(bridge.stats().slashings_emitted, 2); 530 } 531 532 #[test] 533 fn test_bridge_process_with_ejection() { 534 let config = ClpConfig::default(); 535 let emitter = CollectingEmitter::new(); 536 let mut bridge = ClpSlashingBridge::new(config, emitter); 537 538 let validator = make_validator(1); 539 bridge.register_stake(validator.clone(), 100_000_000); 540 541 let result = ClpEpochResult { 542 epoch: 5, 543 passed: vec![], 544 warned: vec![], 545 slashed: vec![(validator.clone(), 100)], 546 ejected: vec![validator.clone()], 547 }; 548 549 let mut response_rates = HashMap::new(); 550 response_rates.insert(validator.clone(), 0.70); 551 552 let mut clp_statuses = HashMap::new(); 553 clp_statuses.insert(validator.clone(), (100, 70, 30)); 554 555 let events = bridge.process_epoch_result(&result, &response_rates, &clp_statuses); 556 557 assert_eq!(events.len(), 1); 558 assert!(events[0].ejected); 559 assert!(events[0].is_severe()); 560 assert_eq!(bridge.stats().ejections_emitted, 1); 561 } 562 563 #[test] 564 fn test_bridge_consecutive_failures() { 565 let config = ClpConfig::default(); 566 let emitter = CollectingEmitter::new(); 567 let mut bridge = ClpSlashingBridge::new(config, emitter); 568 569 let validator = make_validator(1); 570 bridge.register_stake(validator.clone(), 100_000_000); 571 572 // Process first failing epoch 573 let result1 = ClpEpochResult { 574 epoch: 1, 575 passed: vec![], 576 warned: vec![], 577 slashed: vec![(validator.clone(), 100)], 578 ejected: vec![], 579 }; 580 581 let mut response_rates = HashMap::new(); 582 response_rates.insert(validator.clone(), 0.80); 583 584 let mut clp_statuses = HashMap::new(); 585 clp_statuses.insert(validator.clone(), (100, 80, 20)); 586 587 bridge.process_epoch_result(&result1, &response_rates, &clp_statuses); 588 assert_eq!(bridge.consecutive_failures(&validator), 1); 589 590 // Process second failing epoch 591 let result2 = ClpEpochResult { 592 epoch: 2, 593 passed: vec![], 594 warned: vec![], 595 slashed: vec![(validator.clone(), 100)], 596 ejected: vec![], 597 }; 598 599 bridge.process_epoch_result(&result2, &response_rates, &clp_statuses); 600 assert_eq!(bridge.consecutive_failures(&validator), 2); 601 602 // Process passing epoch (should reset) 603 let result3 = ClpEpochResult { 604 epoch: 3, 605 passed: vec![validator.clone()], 606 warned: vec![], 607 slashed: vec![], 608 ejected: vec![], 609 }; 610 611 response_rates.insert(validator.clone(), 0.98); 612 bridge.process_epoch_result(&result3, &response_rates, &clp_statuses); 613 assert_eq!(bridge.consecutive_failures(&validator), 0); 614 } 615 616 #[test] 617 fn test_bridge_slash_calculation() { 618 let config = ClpConfig::default(); 619 let emitter = NoOpEmitter; 620 let bridge = ClpSlashingBridge::new(config, emitter); 621 622 // 1% of 100,000,000 623 assert_eq!(bridge.calculate_slash_amount(100_000_000, 100), 1_000_000); 624 625 // 0.5% of 200,000,000 626 assert_eq!(bridge.calculate_slash_amount(200_000_000, 50), 1_000_000); 627 628 // 5% of 50,000,000 629 assert_eq!(bridge.calculate_slash_amount(50_000_000, 500), 2_500_000); 630 } 631 632 #[test] 633 fn test_bridge_update_stakes() { 634 let config = ClpConfig::default(); 635 let emitter = NoOpEmitter; 636 let mut bridge = ClpSlashingBridge::new(config, emitter); 637 638 let validator1 = make_validator(1); 639 let validator2 = make_validator(2); 640 641 let mut stakes = HashMap::new(); 642 stakes.insert(validator1.clone(), 100_000_000); 643 stakes.insert(validator2.clone(), 200_000_000); 644 645 bridge.update_stakes(stakes); 646 647 assert_eq!(bridge.stakes.get(&validator1), Some(&100_000_000)); 648 assert_eq!(bridge.stakes.get(&validator2), Some(&200_000_000)); 649 } 650 651 #[test] 652 fn test_bridge_reset_failures() { 653 let config = ClpConfig::default(); 654 let emitter = NoOpEmitter; 655 let mut bridge = ClpSlashingBridge::new(config, emitter); 656 657 let validator = make_validator(1); 658 bridge.consecutive_failures.insert(validator.clone(), 3); 659 660 assert_eq!(bridge.consecutive_failures(&validator), 3); 661 662 bridge.reset_failures(&validator); 663 assert_eq!(bridge.consecutive_failures(&validator), 0); 664 } 665 666 #[test] 667 fn test_clp_slashing_metadata_serialization() { 668 let metadata = ClpSlashingMetadata { 669 response_rate: 8500, 670 total_challenges: 100, 671 responses: 85, 672 missed: 15, 673 consecutive_failures: 2, 674 }; 675 676 let json = serde_json::to_string(&metadata).unwrap(); 677 let deserialized: ClpSlashingMetadata = serde_json::from_str(&json).unwrap(); 678 679 assert_eq!(deserialized.response_rate, 8500); 680 assert_eq!(deserialized.total_challenges, 100); 681 assert_eq!(deserialized.consecutive_failures, 2); 682 } 683 684 #[test] 685 fn test_clp_slashing_event_serialization() { 686 let validator = make_validator(1); 687 let metadata = ClpSlashingMetadata { 688 response_rate: 8500, 689 total_challenges: 100, 690 responses: 85, 691 missed: 15, 692 consecutive_failures: 1, 693 }; 694 695 let event = ClpSlashingEvent::new(10, validator, 100, 1_000_000, false, metadata); 696 697 let json = serde_json::to_string(&event).unwrap(); 698 let deserialized: ClpSlashingEvent = serde_json::from_str(&json).unwrap(); 699 700 assert_eq!(deserialized.epoch, 10); 701 assert_eq!(deserialized.slash_bps, 100); 702 assert_eq!(deserialized.slash_amount, 1_000_000); 703 } 704 705 #[test] 706 fn test_bridge_stats_default() { 707 let stats = BridgeStats::default(); 708 709 assert_eq!(stats.epochs_processed, 0); 710 assert_eq!(stats.slashings_emitted, 0); 711 assert_eq!(stats.ejections_emitted, 0); 712 assert_eq!(stats.total_amount_slashed, 0); 713 } 714 715 #[test] 716 fn test_emitting_batch() { 717 let emitter = CollectingEmitter::new(); 718 719 let validator1 = make_validator(1); 720 let validator2 = make_validator(2); 721 722 let metadata1 = ClpSlashingMetadata { 723 response_rate: 8500, 724 total_challenges: 100, 725 responses: 85, 726 missed: 15, 727 consecutive_failures: 1, 728 }; 729 730 let metadata2 = ClpSlashingMetadata { 731 response_rate: 7500, 732 total_challenges: 100, 733 responses: 75, 734 missed: 25, 735 consecutive_failures: 2, 736 }; 737 738 let events = vec![ 739 ClpSlashingEvent::new(10, validator1, 50, 500_000, false, metadata1), 740 ClpSlashingEvent::new(10, validator2, 100, 1_000_000, false, metadata2), 741 ]; 742 743 emitter.emit_clp_slashings(events); 744 745 assert_eq!(emitter.len(), 2); 746 } 747 }