Skip to main content

pallet_rewards/
lib.rs

1//! Pallet for issuing rewards to block producers.
2
3#![cfg_attr(not(feature = "std"), no_std)]
4#![forbid(unsafe_code)]
5#![warn(rust_2018_idioms)]
6
7#[cfg(feature = "runtime-benchmarks")]
8mod benchmarking;
9#[cfg(all(feature = "std", test))]
10mod mock;
11#[cfg(all(feature = "std", test))]
12mod tests;
13pub mod weights;
14
15use frame_support::pallet_prelude::*;
16use frame_support::traits::Currency;
17use frame_system::pallet_prelude::*;
18use log::warn;
19pub use pallet::*;
20use serde::{Deserialize, Serialize};
21use sp_core::U256;
22use sp_runtime::Saturating;
23use sp_runtime::traits::{CheckedSub, Zero};
24use subspace_runtime_primitives::{BlockNumber, FindBlockRewardAddress, FindVotingRewardAddresses};
25pub use weights::WeightInfo;
26
27type BalanceOf<T> =
28    <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
29
30/// Hooks to notify when there are any rewards for specific account.
31pub trait OnReward<AccountId, Balance> {
32    fn on_reward(account: AccountId, reward: Balance);
33}
34
35impl<AccountId, Balance> OnReward<AccountId, Balance> for () {
36    fn on_reward(_account: AccountId, _reward: Balance) {}
37}
38
39#[derive(
40    Debug,
41    Default,
42    Copy,
43    Clone,
44    Eq,
45    PartialEq,
46    Encode,
47    Decode,
48    MaxEncodedLen,
49    TypeInfo,
50    Serialize,
51    Deserialize,
52    DecodeWithMemTracking,
53)]
54pub struct RewardPoint<BlockNumber, Balance> {
55    pub block: BlockNumber,
56    pub subsidy: Balance,
57}
58
59#[frame_support::pallet]
60mod pallet {
61    use crate::weights::WeightInfo;
62    use crate::{BalanceOf, OnReward, RewardPoint};
63    use frame_support::pallet_prelude::*;
64    use frame_support::traits::Currency;
65    use frame_system::pallet_prelude::*;
66    use subspace_runtime_primitives::{FindBlockRewardAddress, FindVotingRewardAddresses};
67
68    /// Pallet rewards for issuing rewards to block producers.
69    #[pallet::pallet]
70    pub struct Pallet<T>(_);
71
72    #[pallet::config]
73    pub trait Config: frame_system::Config<RuntimeEvent: From<Event<Self>>> {
74        type Currency: Currency<Self::AccountId>;
75
76        /// Number of blocks over which to compute average blockspace usage
77        #[pallet::constant]
78        type AvgBlockspaceUsageNumBlocks: Get<u32>;
79
80        /// Cost of one byte of blockspace
81        #[pallet::constant]
82        type TransactionByteFee: Get<BalanceOf<Self>>;
83
84        /// Max number of reward points
85        #[pallet::constant]
86        type MaxRewardPoints: Get<u32>;
87
88        /// Tax of the proposer on vote rewards
89        #[pallet::constant]
90        type ProposerTaxOnVotes: Get<(u32, u32)>;
91
92        /// Determine whether rewards are enabled or not
93        type RewardsEnabled: subspace_runtime_primitives::RewardsEnabled;
94
95        /// Reward address of block producer
96        type FindBlockRewardAddress: FindBlockRewardAddress<Self::AccountId>;
97
98        /// Reward addresses of all receivers of voting rewards
99        type FindVotingRewardAddresses: FindVotingRewardAddresses<Self::AccountId>;
100
101        type WeightInfo: WeightInfo;
102
103        type OnReward: OnReward<Self::AccountId, BalanceOf<Self>>;
104    }
105
106    #[pallet::genesis_config]
107    #[derive(Debug)]
108    pub struct GenesisConfig<T>
109    where
110        T: Config,
111    {
112        /// Tokens left to issue to farmers at any given time
113        pub remaining_issuance: BalanceOf<T>,
114        /// Block proposer subsidy parameters
115        pub proposer_subsidy_points:
116            BoundedVec<RewardPoint<BlockNumberFor<T>, BalanceOf<T>>, T::MaxRewardPoints>,
117        /// Voter subsidy parameters
118        pub voter_subsidy_points:
119            BoundedVec<RewardPoint<BlockNumberFor<T>, BalanceOf<T>>, T::MaxRewardPoints>,
120    }
121
122    impl<T> Default for GenesisConfig<T>
123    where
124        T: Config,
125    {
126        #[inline]
127        fn default() -> Self {
128            Self {
129                remaining_issuance: Default::default(),
130                proposer_subsidy_points: Default::default(),
131                voter_subsidy_points: Default::default(),
132            }
133        }
134    }
135
136    #[pallet::genesis_build]
137    impl<T> BuildGenesisConfig for GenesisConfig<T>
138    where
139        T: Config,
140    {
141        fn build(&self) {
142            RemainingIssuance::<T>::put(self.remaining_issuance);
143            if !self.proposer_subsidy_points.is_empty() {
144                ProposerSubsidyPoints::<T>::put(self.proposer_subsidy_points.clone());
145            }
146            if !self.voter_subsidy_points.is_empty() {
147                VoterSubsidyPoints::<T>::put(self.voter_subsidy_points.clone());
148            }
149        }
150    }
151
152    /// Utilization of blockspace (in bytes) by the normal extrinsics used to adjust issuance
153    #[pallet::storage]
154    pub(crate) type AvgBlockspaceUsage<T> = StorageValue<_, u32, ValueQuery>;
155
156    /// Whether rewards are enabled
157    #[pallet::storage]
158    pub type RewardsEnabled<T> = StorageValue<_, bool, ValueQuery>;
159
160    /// Tokens left to issue to farmers at any given time
161    #[pallet::storage]
162    pub type RemainingIssuance<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;
163
164    /// Block proposer subsidy parameters
165    #[pallet::storage]
166    pub type ProposerSubsidyPoints<T: Config> = StorageValue<
167        _,
168        BoundedVec<RewardPoint<BlockNumberFor<T>, BalanceOf<T>>, T::MaxRewardPoints>,
169        ValueQuery,
170    >;
171
172    /// Voter subsidy parameters
173    #[pallet::storage]
174    pub type VoterSubsidyPoints<T: Config> = StorageValue<
175        _,
176        BoundedVec<RewardPoint<BlockNumberFor<T>, BalanceOf<T>>, T::MaxRewardPoints>,
177        ValueQuery,
178    >;
179
180    /// `pallet-rewards` events
181    #[pallet::event]
182    #[pallet::generate_deposit(pub (super) fn deposit_event)]
183    pub enum Event<T: Config> {
184        /// Issued reward for the block author
185        BlockReward {
186            block_author: T::AccountId,
187            reward: BalanceOf<T>,
188        },
189        /// Issued reward for the voter
190        VoteReward {
191            voter: T::AccountId,
192            reward: BalanceOf<T>,
193        },
194    }
195
196    #[pallet::hooks]
197    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
198        fn on_finalize(now: BlockNumberFor<T>) {
199            Self::do_finalize(now);
200        }
201    }
202
203    #[pallet::call]
204    impl<T: Config> Pallet<T> {
205        /// Update dynamic issuance parameters
206        #[pallet::call_index(0)]
207        #[pallet::weight(T::WeightInfo::update_issuance_params(proposer_subsidy_points.len() as u32, voter_subsidy_points.len() as u32))]
208        pub fn update_issuance_params(
209            origin: OriginFor<T>,
210            proposer_subsidy_points: BoundedVec<
211                RewardPoint<BlockNumberFor<T>, BalanceOf<T>>,
212                T::MaxRewardPoints,
213            >,
214            voter_subsidy_points: BoundedVec<
215                RewardPoint<BlockNumberFor<T>, BalanceOf<T>>,
216                T::MaxRewardPoints,
217            >,
218        ) -> DispatchResult {
219            ensure_root(origin)?;
220
221            ProposerSubsidyPoints::<T>::put(proposer_subsidy_points);
222            VoterSubsidyPoints::<T>::put(voter_subsidy_points);
223
224            Ok(())
225        }
226    }
227}
228
229impl<T: Config> Pallet<T> {
230    fn do_finalize(block_number: BlockNumberFor<T>) {
231        if !<T::RewardsEnabled as subspace_runtime_primitives::RewardsEnabled>::rewards_enabled() {
232            return;
233        }
234
235        if !RewardsEnabled::<T>::get() {
236            RewardsEnabled::<T>::put(true);
237
238            // When rewards are enabled for the first time, adjust points to start with current
239            // block number
240            ProposerSubsidyPoints::<T>::mutate(|reward_points| {
241                reward_points.iter_mut().for_each(|point| {
242                    point.block += block_number;
243                });
244            });
245            VoterSubsidyPoints::<T>::mutate(|reward_points| {
246                reward_points.iter_mut().for_each(|point| {
247                    point.block += block_number;
248                });
249            });
250        }
251
252        let avg_blockspace_usage = Self::update_avg_blockspace_usage(
253            frame_system::Pallet::<T>::all_extrinsics_len(),
254            AvgBlockspaceUsage::<T>::get(),
255            T::AvgBlockspaceUsageNumBlocks::get(),
256            frame_system::Pallet::<T>::block_number(),
257        );
258        AvgBlockspaceUsage::<T>::put(avg_blockspace_usage);
259
260        let old_remaining_issuance = RemainingIssuance::<T>::get();
261        let mut new_remaining_issuance = old_remaining_issuance;
262        let mut block_reward = Zero::zero();
263
264        // Block author may equivocate, in which case they'll not be present here
265        let maybe_block_author = T::FindBlockRewardAddress::find_block_reward_address();
266        if maybe_block_author.is_some() {
267            // Can't exceed remaining issuance
268            block_reward = Self::block_reward(
269                &ProposerSubsidyPoints::<T>::get(),
270                block_number,
271                avg_blockspace_usage,
272            )
273            .min(new_remaining_issuance);
274            new_remaining_issuance -= block_reward;
275
276            // Issue reward later once all voters were taxed
277        }
278
279        let voters = T::FindVotingRewardAddresses::find_voting_reward_addresses();
280        if !voters.is_empty() {
281            let vote_reward = Self::vote_reward(&VoterSubsidyPoints::<T>::get(), block_number);
282            // Tax voter
283            let proposer_tax = vote_reward / T::ProposerTaxOnVotes::get().1.into()
284                * T::ProposerTaxOnVotes::get().0.into();
285            // Subtract tax from vote reward
286            let vote_reward = vote_reward - proposer_tax;
287
288            for voter in voters {
289                // Can't exceed remaining issuance
290                let mut reward = vote_reward.min(new_remaining_issuance);
291                new_remaining_issuance -= reward;
292                // Can't exceed remaining issuance
293                let proposer_reward = proposer_tax.min(new_remaining_issuance);
294                new_remaining_issuance -= proposer_reward;
295                // In case block author equivocated, give full reward to voter
296                if maybe_block_author.is_some() {
297                    block_reward += proposer_reward;
298                } else {
299                    reward += proposer_reward;
300                }
301
302                if !reward.is_zero() {
303                    let _imbalance = T::Currency::deposit_creating(&voter, reward);
304                    T::OnReward::on_reward(voter.clone(), reward);
305
306                    Self::deposit_event(Event::VoteReward { voter, reward });
307                }
308            }
309        }
310
311        if let Some(block_author) = maybe_block_author
312            && !block_reward.is_zero()
313        {
314            let _imbalance = T::Currency::deposit_creating(&block_author, block_reward);
315            T::OnReward::on_reward(block_author.clone(), block_reward);
316
317            Self::deposit_event(Event::BlockReward {
318                block_author,
319                reward: block_reward,
320            });
321        }
322
323        if old_remaining_issuance != new_remaining_issuance {
324            RemainingIssuance::<T>::put(new_remaining_issuance);
325        }
326    }
327
328    /// Returns new updated average blockspace usage based on given parameters
329    fn update_avg_blockspace_usage(
330        used_blockspace: u32,
331        old_avg_blockspace_usage: u32,
332        avg_blockspace_usage_num_blocks: u32,
333        block_height: BlockNumberFor<T>,
334    ) -> u32 {
335        if avg_blockspace_usage_num_blocks == 0 {
336            used_blockspace
337        } else if block_height <= avg_blockspace_usage_num_blocks.into() {
338            (old_avg_blockspace_usage + used_blockspace) / 2
339        } else {
340            // Multiplier is `a / b` stored as `(a, b)`
341            let multiplier = (2, u64::from(avg_blockspace_usage_num_blocks) + 1);
342
343            // Equivalent to `multiplier * used_blockspace + (1 - multiplier) * old_avg_blockspace_usage`
344            // using integer math
345            let a = multiplier.0 * u64::from(used_blockspace);
346            let b = (multiplier.1 - multiplier.0) * u64::from(old_avg_blockspace_usage);
347
348            u32::try_from((a + b) / multiplier.1).expect(
349                "Average of blockspace usage can't overflow if individual components do not \
350                overflow; qed",
351            )
352        }
353    }
354
355    fn block_reward(
356        proposer_subsidy_points: &[RewardPoint<BlockNumberFor<T>, BalanceOf<T>>],
357        block_height: BlockNumberFor<T>,
358        avg_blockspace_usage: u32,
359    ) -> BalanceOf<T> {
360        let reference_subsidy =
361            Self::reference_subsidy_for_block(proposer_subsidy_points, block_height);
362        let max_normal_block_length = *T::BlockLength::get().max.get(DispatchClass::Normal);
363        let max_block_fee = BalanceOf::<T>::from(max_normal_block_length)
364            .saturating_mul(T::TransactionByteFee::get());
365        // Reward decrease based on chain utilization
366        let reward_decrease = Self::block_number_to_balance(avg_blockspace_usage)
367            * reference_subsidy.min(max_block_fee)
368            / Self::block_number_to_balance(max_normal_block_length);
369        reference_subsidy.saturating_sub(reward_decrease)
370    }
371
372    fn vote_reward(
373        voter_subsidy_points: &[RewardPoint<BlockNumberFor<T>, BalanceOf<T>>],
374        block_height: BlockNumberFor<T>,
375    ) -> BalanceOf<T> {
376        Self::reference_subsidy_for_block(voter_subsidy_points, block_height)
377    }
378
379    fn reference_subsidy_for_block(
380        points: &[RewardPoint<BlockNumberFor<T>, BalanceOf<T>>],
381        block_height: BlockNumberFor<T>,
382    ) -> BalanceOf<T> {
383        points
384            // Find two points between which current block lies
385            .array_windows::<2>()
386            .find(|&[from, to]| block_height >= from.block && block_height < to.block)
387            .map(|&[from, to]| {
388                // Calculate reference subsidy
389                Some(
390                    from.subsidy
391                        - from.subsidy.checked_sub(&to.subsidy)?
392                            / Self::block_number_to_balance(to.block - from.block)
393                            * Self::block_number_to_balance(block_height - from.block),
394                )
395            })
396            .unwrap_or_else(|| {
397                // If no matching points are found and current block number is beyond last block,
398                // use last point's subsidy
399                points
400                    .last()
401                    .and_then(|point| (block_height >= point.block).then_some(point.subsidy))
402            })
403            .unwrap_or_default()
404    }
405
406    fn block_number_to_balance<N>(n: N) -> BalanceOf<T>
407    where
408        N: Into<BlockNumberFor<T>>,
409    {
410        let n = Into::<BlockNumberFor<T>>::into(n);
411        BalanceOf::<T>::from(
412            BlockNumber::try_from(Into::<U256>::into(n))
413                .expect("Block number fits into block number; qed"),
414        )
415    }
416}