1#![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
31pub 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::pallet]
70 pub struct Pallet<T>(_);
71
72 #[pallet::config]
73 pub trait Config: frame_system::Config {
74 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
76
77 type Currency: Currency<Self::AccountId>;
78
79 #[pallet::constant]
81 type AvgBlockspaceUsageNumBlocks: Get<u32>;
82
83 #[pallet::constant]
85 type TransactionByteFee: Get<BalanceOf<Self>>;
86
87 #[pallet::constant]
89 type MaxRewardPoints: Get<u32>;
90
91 #[pallet::constant]
93 type ProposerTaxOnVotes: Get<(u32, u32)>;
94
95 type RewardsEnabled: subspace_runtime_primitives::RewardsEnabled;
97
98 type FindBlockRewardAddress: FindBlockRewardAddress<Self::AccountId>;
100
101 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 pub remaining_issuance: BalanceOf<T>,
117 pub proposer_subsidy_points:
119 BoundedVec<RewardPoint<BlockNumberFor<T>, BalanceOf<T>>, T::MaxRewardPoints>,
120 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 #[pallet::storage]
157 pub(crate) type AvgBlockspaceUsage<T> = StorageValue<_, u32, ValueQuery>;
158
159 #[pallet::storage]
161 pub type RewardsEnabled<T> = StorageValue<_, bool, ValueQuery>;
162
163 #[pallet::storage]
165 pub type RemainingIssuance<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;
166
167 #[pallet::storage]
169 pub type ProposerSubsidyPoints<T: Config> = StorageValue<
170 _,
171 BoundedVec<RewardPoint<BlockNumberFor<T>, BalanceOf<T>>, T::MaxRewardPoints>,
172 ValueQuery,
173 >;
174
175 #[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::event]
185 #[pallet::generate_deposit(pub (super) fn deposit_event)]
186 pub enum Event<T: Config> {
187 BlockReward {
189 block_author: T::AccountId,
190 reward: BalanceOf<T>,
191 },
192 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 #[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 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 let maybe_block_author = T::FindBlockRewardAddress::find_block_reward_address();
269 if maybe_block_author.is_some() {
270 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 }
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 let proposer_tax = vote_reward / T::ProposerTaxOnVotes::get().1.into()
287 * T::ProposerTaxOnVotes::get().0.into();
288 let vote_reward = vote_reward - proposer_tax;
290
291 for voter in voters {
292 let mut reward = vote_reward.min(new_remaining_issuance);
294 new_remaining_issuance -= reward;
295 let proposer_reward = proposer_tax.min(new_remaining_issuance);
297 new_remaining_issuance -= proposer_reward;
298 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 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 let multiplier = (2, u64::from(avg_blockspace_usage_num_blocks) + 1);
345
346 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 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 .array_windows::<2>()
389 .find(|&[from, to]| block_height >= from.block && block_height < to.block)
390 .map(|&[from, to]| {
391 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 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}