Skip to main content

solana_epoch_rewards/
lib.rs

1//! A type to hold data for the [`EpochRewards` sysvar][sv].
2//!
3//! [sv]: https://docs.solanalabs.com/runtime/sysvars#epochrewards
4//!
5//! The sysvar ID is declared in [`sysvar`].
6//!
7//! [`sysvar`]: crate::sysvar
8
9#![no_std]
10#![cfg_attr(docsrs, feature(doc_cfg))]
11#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
12
13#[cfg(feature = "sysvar")]
14pub mod sysvar;
15
16#[cfg(feature = "std")]
17extern crate std;
18#[cfg(feature = "serde")]
19use serde_derive::{Deserialize, Serialize};
20use {solana_hash::Hash, solana_sdk_macro::CloneZeroed};
21
22#[repr(C, align(16))]
23#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
24#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
25#[cfg_attr(feature = "wincode", derive(wincode::SchemaWrite, wincode::SchemaRead))]
26#[derive(Debug, PartialEq, Eq, Default, CloneZeroed)]
27pub struct EpochRewards {
28    /// The starting block height of the rewards distribution in the current
29    /// epoch
30    pub distribution_starting_block_height: u64,
31
32    /// Number of partitions in the rewards distribution in the current epoch,
33    /// used to generate an EpochRewardsHasher
34    pub num_partitions: u64,
35
36    /// The blockhash of the parent block of the first block in the epoch, used
37    /// to seed an EpochRewardsHasher
38    pub parent_blockhash: Hash,
39
40    /// The total rewards points calculated for the current epoch, where points
41    /// equals the sum of (delegated stake * credits observed) for all
42    /// delegations
43    pub total_points: u128,
44
45    /// The total rewards calculated for the current epoch. This may be greater
46    /// than the total `distributed_rewards` at the end of the rewards period,
47    /// due to rounding and inability to deliver rewards smaller than 1 lamport.
48    pub total_rewards: u64,
49
50    /// The rewards currently distributed for the current epoch, in lamports
51    pub distributed_rewards: u64,
52
53    /// Whether the rewards period (including calculation and distribution) is
54    /// active
55    ///
56    /// SAFETY: upstream invariant: the sysvar data is created exclusively
57    /// by the Solana runtime and serializes bool as 0x00 or 0x01.
58    pub active: bool,
59}
60
61/// Serialized size of the `EpochRewards` sysvar account.
62pub const SIZE: usize = size_of::<u64>() // distribution_starting_block_height
63    + size_of::<u64>() // num_partitions
64    + size_of::<Hash>() // parent_blockhash
65    + size_of::<u128>() // total_points
66    + size_of::<u64>() // total_rewards
67    + size_of::<u64>() // distributed_rewards
68    + size_of::<bool>(); // active
69const _: () = assert!(SIZE == 81);
70
71impl EpochRewards {
72    pub fn distribute(&mut self, amount: u64) {
73        let new_distributed_rewards = self.distributed_rewards.saturating_add(amount);
74        assert!(new_distributed_rewards <= self.total_rewards);
75        self.distributed_rewards = new_distributed_rewards;
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn test_size_of() {
85        assert_eq!(
86            wincode::serialized_size(&EpochRewards::default()).unwrap() as usize,
87            SIZE,
88        );
89    }
90
91    impl EpochRewards {
92        pub fn new(
93            total_rewards: u64,
94            distributed_rewards: u64,
95            distribution_starting_block_height: u64,
96        ) -> Self {
97            Self {
98                total_rewards,
99                distributed_rewards,
100                distribution_starting_block_height,
101                ..Self::default()
102            }
103        }
104    }
105
106    #[test]
107    fn test_epoch_rewards_new() {
108        let epoch_rewards = EpochRewards::new(100, 0, 64);
109
110        assert_eq!(epoch_rewards.total_rewards, 100);
111        assert_eq!(epoch_rewards.distributed_rewards, 0);
112        assert_eq!(epoch_rewards.distribution_starting_block_height, 64);
113    }
114
115    #[test]
116    fn test_epoch_rewards_distribute() {
117        let mut epoch_rewards = EpochRewards::new(100, 0, 64);
118        epoch_rewards.distribute(100);
119
120        assert_eq!(epoch_rewards.total_rewards, 100);
121        assert_eq!(epoch_rewards.distributed_rewards, 100);
122    }
123
124    #[test]
125    #[should_panic(expected = "new_distributed_rewards <= self.total_rewards")]
126    fn test_epoch_rewards_distribute_panic() {
127        let mut epoch_rewards = EpochRewards::new(100, 0, 64);
128        epoch_rewards.distribute(200);
129    }
130}