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::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
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)]
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::pallet]
69 pub struct Pallet<T>(_);
70
71 #[pallet::config]
72 pub trait Config: frame_system::Config {
73 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
75
76 type Currency: Currency<Self::AccountId>;
77
78 #[pallet::constant]
80 type AvgBlockspaceUsageNumBlocks: Get<u32>;
81
82 #[pallet::constant]
84 type TransactionByteFee: Get<BalanceOf<Self>>;
85
86 #[pallet::constant]
88 type MaxRewardPoints: Get<u32>;
89
90 #[pallet::constant]
92 type ProposerTaxOnVotes: Get<(u32, u32)>;
93
94 type RewardsEnabled: subspace_runtime_primitives::RewardsEnabled;
96
97 type FindBlockRewardAddress: FindBlockRewardAddress<Self::AccountId>;
99
100 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 pub remaining_issuance: BalanceOf<T>,
116 pub proposer_subsidy_points:
118 BoundedVec<RewardPoint<BlockNumberFor<T>, BalanceOf<T>>, T::MaxRewardPoints>,
119 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 #[pallet::storage]
156 pub(crate) type AvgBlockspaceUsage<T> = StorageValue<_, u32, ValueQuery>;
157
158 #[pallet::storage]
160 pub type RewardsEnabled<T> = StorageValue<_, bool, ValueQuery>;
161
162 #[pallet::storage]
164 pub type RemainingIssuance<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;
165
166 #[pallet::storage]
168 pub type ProposerSubsidyPoints<T: Config> = StorageValue<
169 _,
170 BoundedVec<RewardPoint<BlockNumberFor<T>, BalanceOf<T>>, T::MaxRewardPoints>,
171 ValueQuery,
172 >;
173
174 #[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::event]
184 #[pallet::generate_deposit(pub (super) fn deposit_event)]
185 pub enum Event<T: Config> {
186 BlockReward {
188 block_author: T::AccountId,
189 reward: BalanceOf<T>,
190 },
191 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 #[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 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 let maybe_block_author = T::FindBlockRewardAddress::find_block_reward_address();
268 if maybe_block_author.is_some() {
269 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 }
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 let proposer_tax = vote_reward / T::ProposerTaxOnVotes::get().1.into()
286 * T::ProposerTaxOnVotes::get().0.into();
287 let vote_reward = vote_reward - proposer_tax;
289
290 for voter in voters {
291 let mut reward = vote_reward.min(new_remaining_issuance);
293 new_remaining_issuance -= reward;
294 let proposer_reward = proposer_tax.min(new_remaining_issuance);
296 new_remaining_issuance -= proposer_reward;
297 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 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 let multiplier = (2, u64::from(avg_blockspace_usage_num_blocks) + 1);
344
345 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 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 .array_windows::<2>()
388 .find(|&[from, to]| block_height >= from.block && block_height < to.block)
389 .map(|&[from, to]| {
390 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 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}