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