/ ledger / validator / src / rewards.rs
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  }