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 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::pallet]
71 pub struct Pallet<T>(_);
72
73 #[pallet::config]
74 pub trait Config: frame_system::Config {
75 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
77
78 type Currency: Currency<Self::AccountId>;
79
80 #[pallet::constant]
82 type AvgBlockspaceUsageNumBlocks: Get<u32>;
83
84 #[pallet::constant]
86 type TransactionByteFee: Get<BalanceOf<Self>>;
87
88 #[pallet::constant]
90 type MaxRewardPoints: Get<u32>;
91
92 #[pallet::constant]
94 type ProposerTaxOnVotes: Get<(u32, u32)>;
95
96 type RewardsEnabled: subspace_runtime_primitives::RewardsEnabled;
98
99 type FindBlockRewardAddress: FindBlockRewardAddress<Self::AccountId>;
101
102 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 pub remaining_issuance: BalanceOf<T>,
118 pub proposer_subsidy_points:
120 BoundedVec<RewardPoint<BlockNumberFor<T>, BalanceOf<T>>, T::MaxRewardPoints>,
121 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 #[pallet::storage]
158 pub(crate) type AvgBlockspaceUsage<T> = StorageValue<_, u32, ValueQuery>;
159
160 #[pallet::storage]
162 pub type RewardsEnabled<T> = StorageValue<_, bool, ValueQuery>;
163
164 #[pallet::storage]
166 pub type RemainingIssuance<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;
167
168 #[pallet::storage]
170 pub type ProposerSubsidyPoints<T: Config> = StorageValue<
171 _,
172 BoundedVec<RewardPoint<BlockNumberFor<T>, BalanceOf<T>>, T::MaxRewardPoints>,
173 ValueQuery,
174 >;
175
176 #[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::event]
186 #[pallet::generate_deposit(pub (super) fn deposit_event)]
187 pub enum Event<T: Config> {
188 BlockReward {
190 block_author: T::AccountId,
191 reward: BalanceOf<T>,
192 },
193 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 #[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 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 let maybe_block_author = T::FindBlockRewardAddress::find_block_reward_address();
270 if maybe_block_author.is_some() {
271 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 }
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 let proposer_tax = vote_reward / T::ProposerTaxOnVotes::get().1.into()
288 * T::ProposerTaxOnVotes::get().0.into();
289 let vote_reward = vote_reward - proposer_tax;
291
292 for voter in voters {
293 let mut reward = vote_reward.min(new_remaining_issuance);
295 new_remaining_issuance -= reward;
296 let proposer_reward = proposer_tax.min(new_remaining_issuance);
298 new_remaining_issuance -= proposer_reward;
299 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 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 let multiplier = (2, u64::from(avg_blockspace_usage_num_blocks) + 1);
346
347 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 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 .array_windows::<2>()
390 .find(|&[from, to]| block_height >= from.block && block_height < to.block)
391 .map(|&[from, to]| {
392 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 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}