1#![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
30pub 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::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 #[pallet::constant]
78 type AvgBlockspaceUsageNumBlocks: Get<u32>;
79
80 #[pallet::constant]
82 type TransactionByteFee: Get<BalanceOf<Self>>;
83
84 #[pallet::constant]
86 type MaxRewardPoints: Get<u32>;
87
88 #[pallet::constant]
90 type ProposerTaxOnVotes: Get<(u32, u32)>;
91
92 type RewardsEnabled: subspace_runtime_primitives::RewardsEnabled;
94
95 type FindBlockRewardAddress: FindBlockRewardAddress<Self::AccountId>;
97
98 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 pub remaining_issuance: BalanceOf<T>,
114 pub proposer_subsidy_points:
116 BoundedVec<RewardPoint<BlockNumberFor<T>, BalanceOf<T>>, T::MaxRewardPoints>,
117 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 #[pallet::storage]
154 pub(crate) type AvgBlockspaceUsage<T> = StorageValue<_, u32, ValueQuery>;
155
156 #[pallet::storage]
158 pub type RewardsEnabled<T> = StorageValue<_, bool, ValueQuery>;
159
160 #[pallet::storage]
162 pub type RemainingIssuance<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;
163
164 #[pallet::storage]
166 pub type ProposerSubsidyPoints<T: Config> = StorageValue<
167 _,
168 BoundedVec<RewardPoint<BlockNumberFor<T>, BalanceOf<T>>, T::MaxRewardPoints>,
169 ValueQuery,
170 >;
171
172 #[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::event]
182 #[pallet::generate_deposit(pub (super) fn deposit_event)]
183 pub enum Event<T: Config> {
184 BlockReward {
186 block_author: T::AccountId,
187 reward: BalanceOf<T>,
188 },
189 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 #[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 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 let maybe_block_author = T::FindBlockRewardAddress::find_block_reward_address();
266 if maybe_block_author.is_some() {
267 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 }
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 let proposer_tax = vote_reward / T::ProposerTaxOnVotes::get().1.into()
284 * T::ProposerTaxOnVotes::get().0.into();
285 let vote_reward = vote_reward - proposer_tax;
287
288 for voter in voters {
289 let mut reward = vote_reward.min(new_remaining_issuance);
291 new_remaining_issuance -= reward;
292 let proposer_reward = proposer_tax.min(new_remaining_issuance);
294 new_remaining_issuance -= proposer_reward;
295 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 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 let multiplier = (2, u64::from(avg_blockspace_usage_num_blocks) + 1);
342
343 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 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 .array_windows::<2>()
386 .find(|&[from, to]| block_height >= from.block && block_height < to.block)
387 .map(|&[from, to]| {
388 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 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}