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