pallet_staking_async_points_converter/
lib.rs1#![cfg_attr(not(feature = "std"), no_std)]
38
39extern crate alloc;
40
41pub use pallet::*;
42
43#[cfg(test)]
44mod mock;
45
46#[cfg(test)]
47mod tests;
48
49#[cfg(feature = "runtime-benchmarks")]
50mod benchmarking;
51pub mod weights;
52
53#[frame_support::pallet]
54pub mod pallet {
55 pub use crate::weights::WeightInfo;
56 use alloc::vec::Vec;
57 use frame_support::{pallet_prelude::*, DefaultNoBound};
58 use frame_system::pallet_prelude::*;
59 use sp_runtime::traits::{MaybeSerializeDeserialize, Saturating};
60 use sp_staking::{RewardPoints, ServiceKind};
61
62 #[pallet::pallet]
63 pub struct Pallet<T>(_);
64
65 #[pallet::config]
67 pub trait Config: frame_system::Config<RuntimeEvent: From<Event<Self>>> {
68 type Source: Parameter + MaxEncodedLen + Ord + Copy + MaybeSerializeDeserialize;
70
71 type ReportOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::Source>;
73
74 type PointsOrigin: EnsureOrigin<Self::RuntimeOrigin>;
76
77 type RewardPoints: RewardPoints<Self::AccountId>;
79
80 #[pallet::constant]
85 type MaxPointsPerService: Get<u32>;
86
87 #[pallet::constant]
90 type MaxBlocksPerEntry: Get<u32>;
91
92 type WeightInfo: WeightInfo;
94 }
95
96 #[pallet::storage]
102 pub type PointsPerService<T: Config> =
103 StorageDoubleMap<_, Twox64Concat, T::Source, Twox64Concat, ServiceKind, u32, ValueQuery>;
104
105 #[pallet::genesis_config]
106 #[derive(DefaultNoBound)]
107 pub struct GenesisConfig<T: Config> {
108 pub points_per_service: Vec<(T::Source, ServiceKind, u32)>,
110 }
111
112 #[pallet::genesis_build]
113 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
114 fn build(&self) {
115 for (source, service, points) in
116 self.points_per_service.iter().filter(|(_, _, points)| *points > 0)
117 {
118 assert!(
119 *service != ServiceKind::RelayConsensus,
120 "the genesis config weights points that come from the relay chain"
121 );
122 assert!(
123 *points <= T::MaxPointsPerService::get(),
124 "points per service in the genesis config exceed `MaxPointsPerService`"
125 );
126 PointsPerService::<T>::insert(source, service, points);
127 }
128 }
129 }
130
131 #[pallet::event]
132 #[pallet::generate_deposit(pub(super) fn deposit_event)]
133 pub enum Event<T: Config> {
134 PointsPerServiceSet { source: T::Source, service: ServiceKind, points: u32 },
136 BlocksCredited { source: T::Source, blocks: u32 },
139 BlocksIgnored { source: T::Source, blocks: u32 },
141 }
142
143 #[pallet::error]
144 pub enum Error<T> {
145 PointsPerServiceTooHigh,
147 RelayConsensusNotWeighted,
155 }
156
157 #[pallet::hooks]
158 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
159 fn integrity_test() {
160 assert!(T::MaxPointsPerService::get() > 0, "`MaxPointsPerService` must not be zero");
161 assert!(T::MaxBlocksPerEntry::get() > 0, "`MaxBlocksPerEntry` must not be zero");
162 }
163 }
164
165 #[pallet::call]
166 impl<T: Config> Pallet<T> {
167 #[pallet::call_index(0)]
173 #[pallet::weight(T::WeightInfo::set_points_per_service())]
174 pub fn set_points_per_service(
175 origin: OriginFor<T>,
176 source: T::Source,
177 service: ServiceKind,
178 points: u32,
179 ) -> DispatchResult {
180 T::PointsOrigin::ensure_origin(origin)?;
181 ensure!(service != ServiceKind::RelayConsensus, Error::<T>::RelayConsensusNotWeighted);
182 ensure!(points <= T::MaxPointsPerService::get(), Error::<T>::PointsPerServiceTooHigh);
183 match points {
184 0 => PointsPerService::<T>::remove(source, service),
185 points => PointsPerService::<T>::insert(source, service, points),
186 }
187 Self::deposit_event(Event::PointsPerServiceSet { source, service, points });
188 Ok(())
189 }
190
191 #[pallet::call_index(1)]
195 #[pallet::weight(Pallet::<T>::report_authored_blocks_weight(blocks.len() as u32))]
196 pub fn report_authored_blocks(
197 origin: OriginFor<T>,
198 blocks: Vec<(T::AccountId, u32)>,
199 ) -> DispatchResultWithPostInfo {
200 let source = T::ReportOrigin::ensure_origin(origin)?;
201 let credited = Self::credit_authored_blocks(source, blocks);
202 Ok(Some(Self::report_authored_blocks_weight(credited)).into())
203 }
204 }
205
206 impl<T: Config> Pallet<T> {
207 pub fn credit_authored_blocks(
214 source: T::Source,
215 blocks: impl IntoIterator<Item = (T::AccountId, u32)>,
216 ) -> u32 {
217 let points_per_service = PointsPerService::<T>::get(source, ServiceKind::Collation)
218 .min(T::MaxPointsPerService::get());
219 let mut blocks = blocks.into_iter().peekable();
220 if blocks.peek().is_none() {
221 return 0;
222 }
223 if points_per_service == 0 {
224 let blocks = blocks.fold(0u32, |total, (_, blocks)| total.saturating_add(blocks));
225 Self::deposit_event(Event::BlocksIgnored { source, blocks });
226 return 0;
227 }
228 let max_blocks = T::MaxBlocksPerEntry::get();
229 let mut credited_blocks = 0u32;
230 let points: Vec<_> = blocks
231 .map(|(who, blocks)| {
232 let blocks = blocks.min(max_blocks);
233 credited_blocks.saturating_accrue(blocks);
234 (who, blocks.saturating_mul(points_per_service))
235 })
236 .collect();
237 let entries = points.len() as u32;
238 T::RewardPoints::reward(ServiceKind::Collation, points);
239 Self::deposit_event(Event::BlocksCredited { source, blocks: credited_blocks });
240 entries
241 }
242
243 pub fn report_authored_blocks_weight(credited: u32) -> Weight {
246 let reward = match credited {
247 0 => Weight::zero(),
248 credited => T::RewardPoints::weight(credited),
249 };
250 T::WeightInfo::report_authored_blocks().saturating_add(reward)
251 }
252 }
253}