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