rewards.rs
1 // Copyright (c) 2025 ADnet Contributors 2 // This file is part of the AlphaVM library. 3 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at: 7 8 // http://www.apache.org/licenses/LICENSE-2.0 9 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 //! # Validator & Prover Reward Distribution (F-V07 to F-V09) 17 //! 18 //! Implements the reward distribution mechanism for validators and provers. 19 //! 20 //! ## Epoch Timing 21 //! - Epochs are 24 hours long 22 //! - Validator shuffling occurs every 7 epochs 23 //! - Rewards are distributed at the end of each epoch 24 //! 25 //! ## Reward Splits (Governance-Adjustable) 26 //! - **40%** to Validators 27 //! - **60%** to Provers 28 //! 29 //! ## Validator Rewards 30 //! Active validators (40 max) receive 4× the share of standby validators (300 max). 31 //! 32 //! Formula: 33 //! ```text 34 //! Total Portions = (Active_Count × 4) + Standby_Count 35 //! Standby Share = Validator_Pool / Total_Portions 36 //! Active Share = Standby_Share × 4 37 //! ``` 38 //! 39 //! ## Prover Rewards 40 //! Provers receive rewards pro-rata based on the number of valid proofs 41 //! submitted during the epoch. 42 //! 43 //! ## Performance & Ejection 44 //! There is no performance-based reward scaling. Validators either: 45 //! - Remain in the active/standby set and receive full rewards, OR 46 //! - Are ejected for poor performance (handled by ValidatorPool, not here) 47 48 use crate::{ 49 console::{prelude::*, types::Field}, 50 validator::{VALIDATOR_ALPHA_STAKE_MIN, ValidatorPool}, 51 }; 52 53 use std::collections::HashMap; 54 55 // ============================================================================ 56 // Constants (Governance-Adjustable) 57 // ============================================================================ 58 59 /// Validator share of epoch fees in basis points (40% = 4000 bps) 60 pub const VALIDATOR_SHARE_BPS: u64 = 4000; 61 62 /// Prover share of epoch fees in basis points (60% = 6000 bps) 63 pub const PROVER_SHARE_BPS: u64 = 6000; 64 65 /// Active validator reward multiplier (4× standby) 66 pub const ACTIVE_VALIDATOR_MULTIPLIER: u64 = 4; 67 68 /// Epoch duration in seconds (24 hours) 69 pub const EPOCH_DURATION_SECS: u64 = 24 * 60 * 60; 70 71 /// Validator shuffling period in epochs (7 epochs = 1 week) 72 pub const VALIDATOR_SHUFFLE_EPOCHS: u64 = 7; 73 74 // ============================================================================ 75 // Reward Entry 76 // ============================================================================ 77 78 /// A reward entry for a validator 79 #[derive(Clone, Debug)] 80 pub struct ValidatorRewardEntry<N: Network> { 81 /// Validator ID 82 validator_id: Field<N>, 83 /// Epoch this reward is for 84 epoch: u64, 85 /// Reward amount 86 amount: u64, 87 /// Whether validator was active (true) or standby (false) 88 is_active: bool, 89 /// Whether reward has been distributed 90 distributed: bool, 91 } 92 93 impl<N: Network> ValidatorRewardEntry<N> { 94 /// Create a new reward entry 95 pub fn new(validator_id: Field<N>, epoch: u64, amount: u64, is_active: bool) -> Self { 96 Self { validator_id, epoch, amount, is_active, distributed: false } 97 } 98 99 pub fn validator_id(&self) -> &Field<N> { 100 &self.validator_id 101 } 102 103 pub fn epoch(&self) -> u64 { 104 self.epoch 105 } 106 107 pub fn amount(&self) -> u64 { 108 self.amount 109 } 110 111 pub fn is_active(&self) -> bool { 112 self.is_active 113 } 114 115 pub fn is_distributed(&self) -> bool { 116 self.distributed 117 } 118 119 pub fn mark_distributed(&mut self) { 120 self.distributed = true; 121 } 122 } 123 124 /// A reward entry for a prover 125 #[derive(Clone, Debug)] 126 pub struct ProverRewardEntry<N: Network> { 127 /// Prover ID 128 prover_id: Field<N>, 129 /// Epoch this reward is for 130 epoch: u64, 131 /// Reward amount 132 amount: u64, 133 /// Number of proofs submitted 134 proof_count: u64, 135 /// Whether reward has been distributed 136 distributed: bool, 137 } 138 139 impl<N: Network> ProverRewardEntry<N> { 140 /// Create a new prover reward entry 141 pub fn new(prover_id: Field<N>, epoch: u64, amount: u64, proof_count: u64) -> Self { 142 Self { prover_id, epoch, amount, proof_count, distributed: false } 143 } 144 145 pub fn prover_id(&self) -> &Field<N> { 146 &self.prover_id 147 } 148 149 pub fn epoch(&self) -> u64 { 150 self.epoch 151 } 152 153 pub fn amount(&self) -> u64 { 154 self.amount 155 } 156 157 pub fn proof_count(&self) -> u64 { 158 self.proof_count 159 } 160 161 pub fn is_distributed(&self) -> bool { 162 self.distributed 163 } 164 165 pub fn mark_distributed(&mut self) { 166 self.distributed = true; 167 } 168 } 169 170 // ============================================================================ 171 // Prover Registry 172 // ============================================================================ 173 174 /// Tracks prover activity and proof counts per epoch 175 #[derive(Clone, Debug, Default)] 176 pub struct ProverRegistry<N: Network> { 177 /// Proof counts by prover for current epoch 178 current_epoch_proofs: HashMap<Field<N>, u64>, 179 /// Current epoch number 180 current_epoch: u64, 181 /// Total proofs in current epoch 182 total_proofs_current_epoch: u64, 183 } 184 185 impl<N: Network> ProverRegistry<N> { 186 /// Create a new prover registry 187 pub fn new() -> Self { 188 Self { current_epoch_proofs: HashMap::new(), current_epoch: 0, total_proofs_current_epoch: 0 } 189 } 190 191 /// Record a proof submission 192 pub fn record_proof(&mut self, prover_id: Field<N>) { 193 *self.current_epoch_proofs.entry(prover_id).or_insert(0) += 1; 194 self.total_proofs_current_epoch += 1; 195 } 196 197 /// Record multiple proofs 198 pub fn record_proofs(&mut self, prover_id: Field<N>, count: u64) { 199 *self.current_epoch_proofs.entry(prover_id).or_insert(0) += count; 200 self.total_proofs_current_epoch += count; 201 } 202 203 /// Get proof count for a prover in current epoch 204 pub fn get_proof_count(&self, prover_id: &Field<N>) -> u64 { 205 self.current_epoch_proofs.get(prover_id).copied().unwrap_or(0) 206 } 207 208 /// Get total proofs in current epoch 209 pub fn total_proofs(&self) -> u64 { 210 self.total_proofs_current_epoch 211 } 212 213 /// Get all provers with their proof counts 214 pub fn get_all_provers(&self) -> Vec<(&Field<N>, u64)> { 215 self.current_epoch_proofs.iter().map(|(id, &count)| (id, count)).collect() 216 } 217 218 /// Reset for new epoch 219 pub fn advance_epoch(&mut self, new_epoch: u64) { 220 self.current_epoch_proofs.clear(); 221 self.total_proofs_current_epoch = 0; 222 self.current_epoch = new_epoch; 223 } 224 225 /// Get current epoch 226 pub fn current_epoch(&self) -> u64 { 227 self.current_epoch 228 } 229 } 230 231 // ============================================================================ 232 // Reward Pool 233 // ============================================================================ 234 235 /// The reward pool for validator and prover distribution 236 #[derive(Clone, Debug)] 237 pub struct RewardPool<N: Network> { 238 /// Total available rewards for current epoch 239 available_rewards: u64, 240 /// Pending validator rewards 241 pending_validator_rewards: HashMap<Field<N>, Vec<ValidatorRewardEntry<N>>>, 242 /// Pending prover rewards 243 pending_prover_rewards: HashMap<Field<N>, Vec<ProverRewardEntry<N>>>, 244 /// Total distributed to validators all time 245 total_distributed_validators: u64, 246 /// Total distributed to provers all time 247 total_distributed_provers: u64, 248 /// Current epoch 249 current_epoch: u64, 250 } 251 252 impl<N: Network> Default for RewardPool<N> { 253 fn default() -> Self { 254 Self::new() 255 } 256 } 257 258 impl<N: Network> RewardPool<N> { 259 /// Create a new reward pool 260 pub fn new() -> Self { 261 Self { 262 available_rewards: 0, 263 pending_validator_rewards: HashMap::new(), 264 pending_prover_rewards: HashMap::new(), 265 total_distributed_validators: 0, 266 total_distributed_provers: 0, 267 current_epoch: 0, 268 } 269 } 270 271 /// Add rewards to the pool (called when fees are collected) 272 pub fn add_rewards(&mut self, amount: u64) { 273 self.available_rewards = self.available_rewards.saturating_add(amount); 274 } 275 276 /// Get available rewards 277 pub fn available_rewards(&self) -> u64 { 278 self.available_rewards 279 } 280 281 /// Calculate and allocate epoch rewards for validators and provers 282 /// 283 /// This is called at the end of each epoch. 284 /// 285 /// Returns (validator_entries, prover_entries) 286 pub fn calculate_epoch_rewards( 287 &mut self, 288 validator_pool: &ValidatorPool<N>, 289 prover_registry: &ProverRegistry<N>, 290 epoch: u64, 291 ) -> (Vec<ValidatorRewardEntry<N>>, Vec<ProverRewardEntry<N>>) { 292 self.current_epoch = epoch; 293 294 let total_fees = self.available_rewards; 295 if total_fees == 0 { 296 return (Vec::new(), Vec::new()); 297 } 298 299 // Split between validators (40%) and provers (60%) 300 let validator_pool_amount = (total_fees * VALIDATOR_SHARE_BPS) / 10000; 301 let prover_pool_amount = (total_fees * PROVER_SHARE_BPS) / 10000; 302 303 // Calculate validator rewards 304 let validator_entries = self.calculate_validator_rewards(validator_pool, validator_pool_amount, epoch); 305 306 // Calculate prover rewards 307 let prover_entries = self.calculate_prover_rewards(prover_registry, prover_pool_amount, epoch); 308 309 // Deduct from available 310 let total_allocated: u64 = validator_entries.iter().map(|e| e.amount()).sum::<u64>() 311 + prover_entries.iter().map(|e| e.amount()).sum::<u64>(); 312 self.available_rewards = self.available_rewards.saturating_sub(total_allocated); 313 314 (validator_entries, prover_entries) 315 } 316 317 /// Calculate validator rewards using the portion formula 318 /// 319 /// Active validators get 4 portions, standby validators get 1 portion. 320 /// Total Portions = (Active_Count × 4) + Standby_Count 321 fn calculate_validator_rewards( 322 &mut self, 323 validator_pool: &ValidatorPool<N>, 324 pool_amount: u64, 325 epoch: u64, 326 ) -> Vec<ValidatorRewardEntry<N>> { 327 let active_validators = validator_pool.active_validators(); 328 let standby_validators = validator_pool.standby_validators(); 329 330 let active_count = active_validators.len() as u64; 331 let standby_count = standby_validators.len() as u64; 332 333 if active_count == 0 && standby_count == 0 { 334 return Vec::new(); 335 } 336 337 // Calculate total portions 338 // Active get 4 portions each, standby get 1 portion each 339 let total_portions = (active_count * ACTIVE_VALIDATOR_MULTIPLIER) + standby_count; 340 341 if total_portions == 0 { 342 return Vec::new(); 343 } 344 345 // Calculate per-portion amount 346 let per_portion = pool_amount / total_portions; 347 348 let mut entries = Vec::new(); 349 350 // Allocate to active validators (4 portions each) 351 let active_reward = per_portion * ACTIVE_VALIDATOR_MULTIPLIER; 352 for validator in active_validators { 353 let entry = ValidatorRewardEntry::new(validator.id().clone(), epoch, active_reward, true); 354 355 self.pending_validator_rewards.entry(validator.id().clone()).or_default().push(entry.clone()); 356 357 entries.push(entry); 358 } 359 360 // Allocate to standby validators (1 portion each) 361 for validator in standby_validators { 362 let entry = ValidatorRewardEntry::new(validator.id().clone(), epoch, per_portion, false); 363 364 self.pending_validator_rewards.entry(validator.id().clone()).or_default().push(entry.clone()); 365 366 entries.push(entry); 367 } 368 369 entries 370 } 371 372 /// Calculate prover rewards pro-rata by proof count 373 fn calculate_prover_rewards( 374 &mut self, 375 prover_registry: &ProverRegistry<N>, 376 pool_amount: u64, 377 epoch: u64, 378 ) -> Vec<ProverRewardEntry<N>> { 379 let total_proofs = prover_registry.total_proofs(); 380 381 if total_proofs == 0 { 382 return Vec::new(); 383 } 384 385 let mut entries = Vec::new(); 386 387 for (prover_id, proof_count) in prover_registry.get_all_provers() { 388 // Pro-rata: (prover_proofs / total_proofs) * pool_amount 389 let reward = (pool_amount as u128 * proof_count as u128 / total_proofs as u128) as u64; 390 391 if reward > 0 { 392 let entry = ProverRewardEntry::new(prover_id.clone(), epoch, reward, proof_count); 393 394 self.pending_prover_rewards.entry(prover_id.clone()).or_default().push(entry.clone()); 395 396 entries.push(entry); 397 } 398 } 399 400 entries 401 } 402 403 /// Distribute pending validator rewards 404 pub fn distribute_validator_rewards(&mut self, validator_pool: &mut ValidatorPool<N>) -> Result<u64> { 405 let mut total_distributed = 0u64; 406 407 let validator_ids: Vec<Field<N>> = self.pending_validator_rewards.keys().cloned().collect(); 408 409 for id in validator_ids { 410 if let Some(entries) = self.pending_validator_rewards.get_mut(&id) { 411 for entry in entries.iter_mut() { 412 if !entry.is_distributed() { 413 // Add to validator's earned ALPHA 414 if let Some(validator) = validator_pool.get_mut(&id) { 415 validator.add_earned_alpha(entry.amount(), self.current_epoch); 416 entry.mark_distributed(); 417 total_distributed += entry.amount(); 418 } 419 } 420 } 421 } 422 } 423 424 self.total_distributed_validators += total_distributed; 425 Ok(total_distributed) 426 } 427 428 /// Get pending rewards for a validator 429 pub fn pending_validator_rewards(&self, validator_id: &Field<N>) -> u64 { 430 self.pending_validator_rewards 431 .get(validator_id) 432 .map(|entries| entries.iter().filter(|e| !e.is_distributed()).map(|e| e.amount()).sum()) 433 .unwrap_or(0) 434 } 435 436 /// Get pending rewards for a prover 437 pub fn pending_prover_rewards(&self, prover_id: &Field<N>) -> u64 { 438 self.pending_prover_rewards 439 .get(prover_id) 440 .map(|entries| entries.iter().filter(|e| !e.is_distributed()).map(|e| e.amount()).sum()) 441 .unwrap_or(0) 442 } 443 444 /// Get total distributed to validators 445 pub fn total_distributed_validators(&self) -> u64 { 446 self.total_distributed_validators 447 } 448 449 /// Get total distributed to provers 450 pub fn total_distributed_provers(&self) -> u64 { 451 self.total_distributed_provers 452 } 453 454 /// Cleanup old entries (older than keep_epochs) 455 pub fn cleanup_old_entries(&mut self, keep_epochs: u64) { 456 let cutoff = self.current_epoch.saturating_sub(keep_epochs); 457 458 for entries in self.pending_validator_rewards.values_mut() { 459 entries.retain(|e| e.epoch() > cutoff || !e.is_distributed()); 460 } 461 self.pending_validator_rewards.retain(|_, entries| !entries.is_empty()); 462 463 for entries in self.pending_prover_rewards.values_mut() { 464 entries.retain(|e| e.epoch() > cutoff || !e.is_distributed()); 465 } 466 self.pending_prover_rewards.retain(|_, entries| !entries.is_empty()); 467 } 468 } 469 470 // ============================================================================ 471 // Earn-In Tracker 472 // ============================================================================ 473 474 /// Tracks earn-in progress for validators joining with 0 ALPHA stake 475 #[derive(Clone, Debug)] 476 pub struct EarnInTracker<N: Network> { 477 /// Validators currently in earn-in mode 478 earn_in_validators: HashMap<Field<N>, EarnInProgress>, 479 /// Validators that have graduated 480 graduated_validators: Vec<Field<N>>, 481 } 482 483 /// Progress of an earn-in validator 484 #[derive(Clone, Debug)] 485 pub struct EarnInProgress { 486 /// Epoch when earn-in started 487 start_epoch: u64, 488 /// Total ALPHA earned so far 489 total_earned: u64, 490 /// Target amount to graduate 491 target_amount: u64, 492 } 493 494 impl EarnInProgress { 495 pub fn new(start_epoch: u64) -> Self { 496 Self { start_epoch, total_earned: 0, target_amount: VALIDATOR_ALPHA_STAKE_MIN } 497 } 498 499 pub fn update(&mut self, earned: u64) { 500 self.total_earned = self.total_earned.saturating_add(earned); 501 } 502 503 pub fn is_graduated(&self) -> bool { 504 self.total_earned >= self.target_amount 505 } 506 507 pub fn progress_pct(&self) -> u8 { 508 ((self.total_earned as u128 * 100) / (self.target_amount as u128)).min(100) as u8 509 } 510 511 pub fn total_earned(&self) -> u64 { 512 self.total_earned 513 } 514 } 515 516 impl<N: Network> Default for EarnInTracker<N> { 517 fn default() -> Self { 518 Self::new() 519 } 520 } 521 522 impl<N: Network> EarnInTracker<N> { 523 pub fn new() -> Self { 524 Self { earn_in_validators: HashMap::new(), graduated_validators: Vec::new() } 525 } 526 527 pub fn register(&mut self, validator_id: Field<N>, start_epoch: u64) { 528 if !self.earn_in_validators.contains_key(&validator_id) { 529 self.earn_in_validators.insert(validator_id, EarnInProgress::new(start_epoch)); 530 } 531 } 532 533 pub fn update_progress(&mut self, validator_id: &Field<N>, earned: u64) -> Option<bool> { 534 if let Some(progress) = self.earn_in_validators.get_mut(validator_id) { 535 progress.update(earned); 536 537 if progress.is_graduated() { 538 self.graduated_validators.push(validator_id.clone()); 539 self.earn_in_validators.remove(validator_id); 540 return Some(true); 541 } 542 return Some(false); 543 } 544 None 545 } 546 547 pub fn get_progress(&self, validator_id: &Field<N>) -> Option<&EarnInProgress> { 548 self.earn_in_validators.get(validator_id) 549 } 550 551 pub fn is_earn_in(&self, validator_id: &Field<N>) -> bool { 552 self.earn_in_validators.contains_key(validator_id) 553 } 554 555 pub fn take_graduated(&mut self) -> Vec<Field<N>> { 556 std::mem::take(&mut self.graduated_validators) 557 } 558 559 pub fn earn_in_count(&self) -> usize { 560 self.earn_in_validators.len() 561 } 562 } 563 564 // ============================================================================ 565 // Reward Manager 566 // ============================================================================ 567 568 /// Central manager for validator and prover rewards 569 #[derive(Clone, Debug)] 570 pub struct RewardManager<N: Network> { 571 /// Reward pool 572 pool: RewardPool<N>, 573 /// Prover registry 574 prover_registry: ProverRegistry<N>, 575 /// Earn-in tracker 576 earn_in: EarnInTracker<N>, 577 /// Last processed epoch 578 last_processed_epoch: u64, 579 } 580 581 impl<N: Network> Default for RewardManager<N> { 582 fn default() -> Self { 583 Self::new() 584 } 585 } 586 587 impl<N: Network> RewardManager<N> { 588 pub fn new() -> Self { 589 Self { 590 pool: RewardPool::new(), 591 prover_registry: ProverRegistry::new(), 592 earn_in: EarnInTracker::new(), 593 last_processed_epoch: 0, 594 } 595 } 596 597 pub fn pool(&self) -> &RewardPool<N> { 598 &self.pool 599 } 600 601 pub fn pool_mut(&mut self) -> &mut RewardPool<N> { 602 &mut self.pool 603 } 604 605 pub fn prover_registry(&self) -> &ProverRegistry<N> { 606 &self.prover_registry 607 } 608 609 pub fn prover_registry_mut(&mut self) -> &mut ProverRegistry<N> { 610 &mut self.prover_registry 611 } 612 613 pub fn earn_in(&self) -> &EarnInTracker<N> { 614 &self.earn_in 615 } 616 617 /// Add rewards to the pool 618 pub fn add_rewards(&mut self, amount: u64) { 619 self.pool.add_rewards(amount); 620 } 621 622 /// Record a proof submission 623 pub fn record_proof(&mut self, prover_id: Field<N>) { 624 self.prover_registry.record_proof(prover_id); 625 } 626 627 /// Record multiple proofs 628 pub fn record_proofs(&mut self, prover_id: Field<N>, count: u64) { 629 self.prover_registry.record_proofs(prover_id, count); 630 } 631 632 /// Register an earn-in validator 633 pub fn register_earn_in(&mut self, validator_id: Field<N>, current_epoch: u64) { 634 self.earn_in.register(validator_id, current_epoch); 635 } 636 637 /// Process epoch end: calculate and distribute rewards 638 #[allow(clippy::type_complexity)] 639 pub fn process_epoch( 640 &mut self, 641 validator_pool: &mut ValidatorPool<N>, 642 epoch: u64, 643 auto_distribute: bool, 644 ) -> Result<(Vec<ValidatorRewardEntry<N>>, Vec<ProverRewardEntry<N>>)> { 645 // Calculate rewards 646 let (validator_entries, prover_entries) = 647 self.pool.calculate_epoch_rewards(validator_pool, &self.prover_registry, epoch); 648 649 // Update earn-in progress 650 for entry in &validator_entries { 651 self.earn_in.update_progress(entry.validator_id(), entry.amount()); 652 } 653 654 // Auto-distribute if requested 655 if auto_distribute { 656 self.pool.distribute_validator_rewards(validator_pool)?; 657 } 658 659 // Advance prover registry to next epoch 660 self.prover_registry.advance_epoch(epoch + 1); 661 662 self.last_processed_epoch = epoch; 663 664 Ok((validator_entries, prover_entries)) 665 } 666 667 /// Get graduated validators 668 pub fn take_graduated(&mut self) -> Vec<Field<N>> { 669 self.earn_in.take_graduated() 670 } 671 672 pub fn last_processed_epoch(&self) -> u64 { 673 self.last_processed_epoch 674 } 675 } 676 677 // ============================================================================ 678 // Tests 679 // ============================================================================ 680 681 #[cfg(test)] 682 mod tests { 683 use super::*; 684 use crate::{ 685 console::{network::MainnetV0, types::Group}, 686 validator::Validator, 687 }; 688 use alphavm_ledger_governor::bls::BlsPublicKey; 689 use core::ops::Add; 690 691 type CurrentNetwork = MainnetV0; 692 693 fn create_test_key(id: u64) -> BlsPublicKey<CurrentNetwork> { 694 let generator = Group::<CurrentNetwork>::generator(); 695 let mut point = generator.clone(); 696 for _ in 0..id { 697 point = point.add(&generator); 698 } 699 BlsPublicKey::new(point) 700 } 701 702 #[test] 703 fn test_validator_prover_split() { 704 // 40% validators, 60% provers 705 assert_eq!(VALIDATOR_SHARE_BPS + PROVER_SHARE_BPS, 10000); 706 assert_eq!(VALIDATOR_SHARE_BPS, 4000); 707 assert_eq!(PROVER_SHARE_BPS, 6000); 708 } 709 710 #[test] 711 fn test_epoch_duration() { 712 // Epoch = 24 hours 713 assert_eq!(EPOCH_DURATION_SECS, 24 * 60 * 60); 714 assert_eq!(EPOCH_DURATION_SECS, 86400); 715 716 // Shuffle every 7 epochs = 1 week 717 assert_eq!(VALIDATOR_SHUFFLE_EPOCHS, 7); 718 } 719 720 #[test] 721 fn test_active_multiplier() { 722 // Active validators get 4x standby 723 assert_eq!(ACTIVE_VALIDATOR_MULTIPLIER, 4); 724 } 725 726 #[test] 727 fn test_portion_formula() { 728 // Example: 40 active, 100 standby 729 let active = 40u64; 730 let standby = 100u64; 731 let total_portions = (active * ACTIVE_VALIDATOR_MULTIPLIER) + standby; 732 assert_eq!(total_portions, 260); // (40 * 4) + 100 = 260 733 734 // With 26,000 in validator pool: 735 let pool = 26000u64; 736 let per_portion = pool / total_portions; 737 assert_eq!(per_portion, 100); 738 739 let active_reward = per_portion * ACTIVE_VALIDATOR_MULTIPLIER; 740 let standby_reward = per_portion; 741 742 assert_eq!(active_reward, 400); // 4 portions 743 assert_eq!(standby_reward, 100); // 1 portion 744 } 745 746 #[test] 747 fn test_prover_registry() { 748 let mut registry = ProverRegistry::<CurrentNetwork>::new(); 749 750 registry.record_proof(Field::from_u64(1)); 751 registry.record_proof(Field::from_u64(1)); 752 registry.record_proof(Field::from_u64(2)); 753 754 assert_eq!(registry.get_proof_count(&Field::from_u64(1)), 2); 755 assert_eq!(registry.get_proof_count(&Field::from_u64(2)), 1); 756 assert_eq!(registry.total_proofs(), 3); 757 758 // Advance epoch clears 759 registry.advance_epoch(1); 760 assert_eq!(registry.total_proofs(), 0); 761 } 762 763 #[test] 764 fn test_prover_rewards_pro_rata() { 765 let mut pool = RewardPool::<CurrentNetwork>::new(); 766 let mut prover_registry = ProverRegistry::<CurrentNetwork>::new(); 767 768 // Prover 1: 75 proofs, Prover 2: 25 proofs 769 prover_registry.record_proofs(Field::from_u64(1), 75); 770 prover_registry.record_proofs(Field::from_u64(2), 25); 771 772 // 1000 in prover pool 773 let entries = pool.calculate_prover_rewards(&prover_registry, 1000, 0); 774 775 assert_eq!(entries.len(), 2); 776 777 let p1 = entries.iter().find(|e| e.prover_id() == &Field::from_u64(1)).unwrap(); 778 let p2 = entries.iter().find(|e| e.prover_id() == &Field::from_u64(2)).unwrap(); 779 780 // 75% and 25% 781 assert_eq!(p1.amount(), 750); 782 assert_eq!(p2.amount(), 250); 783 } 784 785 #[test] 786 fn test_validator_active_vs_standby_rewards() { 787 use crate::validator::VALIDATOR_WARMUP_EPOCHS; 788 789 let mut pool = RewardPool::<CurrentNetwork>::new(); 790 let mut validator_pool = ValidatorPool::<CurrentNetwork>::new_with_seed(0, 12345); 791 792 // Register 2 validators (will both be active since < 40) 793 for i in 1..=2 { 794 let validator = 795 Validator::new(Field::from_u64(i), create_test_key(i), Field::from_u64(100 + i), 100_000, 10_000, 0) 796 .unwrap(); 797 validator_pool.register(validator).unwrap(); 798 } 799 800 validator_pool.advance_epoch(VALIDATOR_WARMUP_EPOCHS).unwrap(); 801 802 // Both active, 0 standby 803 // Total portions = (2 * 4) + 0 = 8 804 // 8000 in validator pool -> 1000 per portion -> 4000 per active 805 let entries = pool.calculate_validator_rewards(&validator_pool, 8000, 0); 806 807 assert_eq!(entries.len(), 2); 808 for entry in &entries { 809 assert!(entry.is_active()); 810 assert_eq!(entry.amount(), 4000); // 4 portions each 811 } 812 } 813 814 #[test] 815 fn test_full_epoch_distribution() { 816 use crate::validator::VALIDATOR_WARMUP_EPOCHS; 817 818 let mut manager = RewardManager::<CurrentNetwork>::new(); 819 let mut validator_pool = ValidatorPool::<CurrentNetwork>::new_with_seed(0, 12345); 820 821 // Register validator 822 let validator = 823 Validator::new(Field::from_u64(1), create_test_key(1), Field::from_u64(100), 100_000, 10_000, 0).unwrap(); 824 validator_pool.register(validator).unwrap(); 825 validator_pool.advance_epoch(VALIDATOR_WARMUP_EPOCHS).unwrap(); 826 827 // Record proofs 828 manager.record_proofs(Field::from_u64(10), 100); 829 830 // Add 10,000 in fees 831 manager.add_rewards(10_000); 832 833 // Process epoch with auto-distribute 834 let (val_entries, prover_entries) = 835 manager.process_epoch(&mut validator_pool, VALIDATOR_WARMUP_EPOCHS, true).unwrap(); 836 837 // Validators get 40% = 4000 838 // 1 active validator, 0 standby -> total portions = 4 839 // 4000 / 4 = 1000 per portion, active gets 4 = 4000 840 assert_eq!(val_entries.len(), 1); 841 assert_eq!(val_entries[0].amount(), 4000); 842 843 // Provers get 60% = 6000 844 assert_eq!(prover_entries.len(), 1); 845 assert_eq!(prover_entries[0].amount(), 6000); 846 847 // Validator should have earned ALPHA 848 let validator = validator_pool.get(&Field::from_u64(1)).unwrap(); 849 assert_eq!(validator.stake().alpha_earned(), 4000); 850 } 851 852 #[test] 853 fn test_earn_in_tracker() { 854 let mut tracker = EarnInTracker::<CurrentNetwork>::new(); 855 856 tracker.register(Field::from_u64(1), 0); 857 assert!(tracker.is_earn_in(&Field::from_u64(1))); 858 859 // Update progress 860 tracker.update_progress(&Field::from_u64(1), 25_000); 861 let progress = tracker.get_progress(&Field::from_u64(1)).unwrap(); 862 assert_eq!(progress.total_earned(), 25_000); 863 assert_eq!(progress.progress_pct(), 25); 864 865 // Graduate at threshold 866 tracker.update_progress(&Field::from_u64(1), 75_000); 867 assert!(!tracker.is_earn_in(&Field::from_u64(1))); 868 869 let graduated = tracker.take_graduated(); 870 assert_eq!(graduated.len(), 1); 871 } 872 }