frame_election_provider_support/
onchain.rs1use crate::{
23 bounds::{ElectionBounds, ElectionBoundsBuilder},
24 BoundedSupportsOf, Debug, ElectionDataProvider, ElectionProvider, InstantElectionProvider,
25 NposSolver, PageIndex, VoterOf, WeightInfo,
26};
27use alloc::{collections::btree_map::BTreeMap, vec::Vec};
28use core::marker::PhantomData;
29use frame_support::{dispatch::DispatchClass, traits::Get};
30use frame_system::pallet_prelude::BlockNumberFor;
31use sp_npos_elections::{
32 assignment_ratio_to_staked_normalized, to_supports, ElectionResult, VoteWeight,
33};
34
35#[derive(Eq, PartialEq, Debug, Clone)]
37pub enum Error {
38 NposElections(sp_npos_elections::Error),
40 DataProvider(&'static str),
42 FailedToBound,
44}
45
46impl From<sp_npos_elections::Error> for Error {
47 fn from(e: sp_npos_elections::Error) -> Self {
48 Error::NposElections(e)
49 }
50}
51
52pub struct OnChainExecution<T: Config>(PhantomData<T>);
59
60#[deprecated(note = "use OnChainExecution, which is bounded by default")]
61pub type BoundedExecution<T> = OnChainExecution<T>;
62
63pub trait Config {
65 type Sort: Get<bool>;
70
71 type System: frame_system::Config;
73
74 type Solver: NposSolver<
76 AccountId = <Self::System as frame_system::Config>::AccountId,
77 Error = sp_npos_elections::Error,
78 >;
79
80 type MaxBackersPerWinner: Get<u32>;
85
86 type MaxWinnersPerPage: Get<u32>;
91
92 type DataProvider: ElectionDataProvider<
94 AccountId = <Self::System as frame_system::Config>::AccountId,
95 BlockNumber = frame_system::pallet_prelude::BlockNumberFor<Self::System>,
96 >;
97
98 type WeightInfo: WeightInfo;
100
101 type Bounds: Get<ElectionBounds>;
104}
105
106impl<T: Config> OnChainExecution<T> {
107 fn elect_with_snapshot(
108 voters: Vec<VoterOf<T::DataProvider>>,
109 targets: Vec<<T::System as frame_system::Config>::AccountId>,
110 desired_targets: u32,
111 ) -> Result<BoundedSupportsOf<Self>, Error> {
112 if (desired_targets > T::MaxWinnersPerPage::get()) && !T::Sort::get() {
113 return Err(Error::FailedToBound)
115 }
116
117 let voters_len = voters.len() as u32;
118 let targets_len = targets.len() as u32;
119
120 let stake_map: BTreeMap<_, _> = voters
121 .iter()
122 .map(|(validator, vote_weight, _)| (validator.clone(), *vote_weight))
123 .collect();
124
125 let stake_of = |w: &<T::System as frame_system::Config>::AccountId| -> VoteWeight {
126 stake_map.get(w).cloned().unwrap_or_default()
127 };
128
129 let ElectionResult { winners: _, assignments } =
130 T::Solver::solve(desired_targets as usize, targets, voters).map_err(Error::from)?;
131
132 let staked = assignment_ratio_to_staked_normalized(assignments, &stake_of)?;
133
134 let weight = T::Solver::weight::<T::WeightInfo>(
135 voters_len,
136 targets_len,
137 <T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get(),
138 );
139 frame_system::Pallet::<T::System>::register_extra_weight_unchecked(
140 weight,
141 DispatchClass::Mandatory,
142 );
143
144 let unbounded = to_supports(&staked);
145 let bounded = if T::Sort::get() {
146 let (bounded, _winners_removed, _backers_removed) =
147 BoundedSupportsOf::<Self>::sorted_truncate_from(unbounded);
148 bounded
149 } else {
150 unbounded.try_into().map_err(|_| Error::FailedToBound)?
151 };
152 Ok(bounded)
153 }
154
155 fn elect_with(
156 bounds: ElectionBounds,
157 page: PageIndex,
158 ) -> Result<BoundedSupportsOf<Self>, Error> {
159 let (voters, targets) = T::DataProvider::electing_voters(bounds.voters, page)
160 .and_then(|voters| {
161 Ok((voters, T::DataProvider::electable_targets(bounds.targets, page)?))
162 })
163 .map_err(Error::DataProvider)?;
164 let desired_targets = T::DataProvider::desired_targets().map_err(Error::DataProvider)?;
165 Self::elect_with_snapshot(voters, targets, desired_targets)
166 }
167}
168
169impl<T: Config> InstantElectionProvider for OnChainExecution<T> {
170 fn instant_elect(
171 voters: Vec<VoterOf<T::DataProvider>>,
172 targets: Vec<<T::System as frame_system::Config>::AccountId>,
173 desired_targets: u32,
174 ) -> Result<BoundedSupportsOf<Self>, Self::Error> {
175 Self::elect_with_snapshot(voters, targets, desired_targets)
176 }
177
178 fn bother() -> bool {
179 true
180 }
181}
182
183impl<T: Config> ElectionProvider for OnChainExecution<T> {
184 type AccountId = <T::System as frame_system::Config>::AccountId;
185 type BlockNumber = BlockNumberFor<T::System>;
186 type Error = Error;
187 type MaxWinnersPerPage = T::MaxWinnersPerPage;
188 type MaxBackersPerWinnerFinal = T::MaxWinnersPerPage;
189 type MaxBackersPerWinner = T::MaxBackersPerWinner;
190 type Pages = sp_core::ConstU32<1>;
193 type DataProvider = T::DataProvider;
194
195 fn elect(page: PageIndex) -> Result<BoundedSupportsOf<Self>, Self::Error> {
196 let election_bounds = ElectionBoundsBuilder::from(T::Bounds::get()).build();
197 Self::elect_with(election_bounds, page)
198 }
199
200 fn start() -> Result<(), Self::Error> {
201 Ok(())
203 }
204
205 fn duration() -> Self::BlockNumber {
206 sp_runtime::traits::Zero::zero()
207 }
208
209 fn status() -> Result<bool, ()> {
210 Ok(true)
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217 use crate::{ElectionProvider, PhragMMS, SequentialPhragmen};
218 use frame_support::{assert_noop, derive_impl, parameter_types};
219 use sp_io::TestExternalities;
220 use sp_npos_elections::Support;
221 use sp_runtime::Perbill;
222 type AccountId = u64;
223 type Nonce = u64;
224 type BlockNumber = u64;
225
226 pub type Header = sp_runtime::generic::Header<BlockNumber, sp_runtime::traits::BlakeTwo256>;
227 pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic<AccountId, (), (), ()>;
228 pub type Block = sp_runtime::generic::Block<Header, UncheckedExtrinsic>;
229
230 frame_support::construct_runtime!(
231 pub enum Runtime {
232 System: frame_system,
233 }
234 );
235
236 #[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
237 impl frame_system::Config for Runtime {
238 type SS58Prefix = ();
239 type BaseCallFilter = frame_support::traits::Everything;
240 type RuntimeOrigin = RuntimeOrigin;
241 type Nonce = Nonce;
242 type RuntimeCall = RuntimeCall;
243 type Hash = sp_core::H256;
244 type Hashing = sp_runtime::traits::BlakeTwo256;
245 type AccountId = AccountId;
246 type Lookup = sp_runtime::traits::IdentityLookup<Self::AccountId>;
247 type Block = Block;
248 type RuntimeEvent = ();
249 type BlockHashCount = ();
250 type DbWeight = ();
251 type BlockLength = ();
252 type BlockWeights = ();
253 type Version = ();
254 type PalletInfo = PalletInfo;
255 type AccountData = ();
256 type OnNewAccount = ();
257 type OnKilledAccount = ();
258 type SystemWeightInfo = ();
259 type OnSetCode = ();
260 type MaxConsumers = frame_support::traits::ConstU32<16>;
261 }
262
263 struct PhragmenParams;
264 struct PhragMMSParams;
265
266 parameter_types! {
267 pub static MaxWinnersPerPage: u32 = 10;
268 pub static MaxBackersPerWinner: u32 = 20;
269 pub static DesiredTargets: u32 = 2;
270 pub static Sort: bool = false;
271 pub static Bounds: ElectionBounds = ElectionBoundsBuilder::default().voters_count(600.into()).targets_count(400.into()).build();
272 }
273
274 impl Config for PhragmenParams {
275 type Sort = Sort;
276 type System = Runtime;
277 type Solver = SequentialPhragmen<AccountId, Perbill>;
278 type DataProvider = mock_data_provider::DataProvider;
279 type MaxWinnersPerPage = MaxWinnersPerPage;
280 type MaxBackersPerWinner = MaxBackersPerWinner;
281 type Bounds = Bounds;
282 type WeightInfo = ();
283 }
284
285 impl Config for PhragMMSParams {
286 type Sort = Sort;
287 type System = Runtime;
288 type Solver = PhragMMS<AccountId, Perbill>;
289 type DataProvider = mock_data_provider::DataProvider;
290 type MaxWinnersPerPage = MaxWinnersPerPage;
291 type MaxBackersPerWinner = MaxBackersPerWinner;
292 type WeightInfo = ();
293 type Bounds = Bounds;
294 }
295
296 mod mock_data_provider {
297 use super::*;
298 use crate::{data_provider, DataProviderBounds, PageIndex, VoterOf};
299 use frame_support::traits::ConstU32;
300 use sp_runtime::bounded_vec;
301
302 pub struct DataProvider;
303 impl ElectionDataProvider for DataProvider {
304 type AccountId = AccountId;
305 type BlockNumber = BlockNumber;
306 type MaxVotesPerVoter = ConstU32<2>;
307 fn electing_voters(
308 _: DataProviderBounds,
309 _page: PageIndex,
310 ) -> data_provider::Result<Vec<VoterOf<Self>>> {
311 Ok(vec![
312 (1, 10, bounded_vec![10, 20]),
313 (2, 20, bounded_vec![30, 20]),
314 (3, 30, bounded_vec![10, 30]),
315 ])
316 }
317
318 fn electable_targets(
319 _: DataProviderBounds,
320 _page: PageIndex,
321 ) -> data_provider::Result<Vec<AccountId>> {
322 Ok(vec![10, 20, 30])
323 }
324
325 fn desired_targets() -> data_provider::Result<u32> {
326 Ok(DesiredTargets::get())
327 }
328
329 fn next_election_prediction(_: BlockNumber) -> BlockNumber {
330 0
331 }
332 }
333 }
334
335 #[test]
336 fn onchain_seq_phragmen_works() {
337 TestExternalities::new_empty().execute_with(|| {
338 let expected_supports = vec![
339 (
340 10 as AccountId,
341 Support { total: 25, voters: vec![(1 as AccountId, 10), (3, 15)] },
342 ),
343 (30, Support { total: 35, voters: vec![(2, 20), (3, 15)] }),
344 ]
345 .try_into()
346 .unwrap();
347
348 assert_eq!(
349 <OnChainExecution::<PhragmenParams> as ElectionProvider>::elect(0).unwrap(),
350 expected_supports,
351 );
352 })
353 }
354
355 #[test]
356 fn sorting_false_works() {
357 TestExternalities::new_empty().execute_with(|| {
358 DesiredTargets::set(3);
360 MaxWinnersPerPage::set(2);
361
362 assert_noop!(
363 <OnChainExecution::<PhragmenParams> as ElectionProvider>::elect(0),
364 Error::FailedToBound,
365 );
366 });
367
368 TestExternalities::new_empty().execute_with(|| {
369 MaxBackersPerWinner::set(1);
371
372 assert_noop!(
373 <OnChainExecution::<PhragmenParams> as ElectionProvider>::elect(0),
374 Error::FailedToBound,
375 );
376 });
377 }
378
379 #[test]
380 fn sorting_true_works_winners() {
381 Sort::set(true);
382
383 TestExternalities::new_empty().execute_with(|| {
384 let expected_supports =
385 vec![(30, Support { total: 35, voters: vec![(2, 20), (3, 15)] })]
386 .try_into()
387 .unwrap();
388
389 MaxWinnersPerPage::set(1);
391
392 assert_eq!(
393 <OnChainExecution::<PhragmenParams> as ElectionProvider>::elect(0).unwrap(),
394 expected_supports,
395 );
396 });
397
398 MaxWinnersPerPage::set(10);
399
400 TestExternalities::new_empty().execute_with(|| {
401 let expected_supports = vec![
402 (30, Support { total: 20, voters: vec![(2, 20)] }),
403 (10 as AccountId, Support { total: 15, voters: vec![(3 as AccountId, 15)] }),
404 ]
405 .try_into()
406 .unwrap();
407
408 MaxBackersPerWinner::set(1);
410
411 assert_eq!(
412 <OnChainExecution::<PhragmenParams> as ElectionProvider>::elect(0).unwrap(),
413 expected_supports,
414 );
415 })
416 }
417
418 #[test]
419 fn onchain_phragmms_works() {
420 TestExternalities::new_empty().execute_with(|| {
421 assert_eq!(
422 <OnChainExecution::<PhragMMSParams> as ElectionProvider>::elect(0).unwrap(),
423 vec![
424 (
425 10 as AccountId,
426 Support { total: 25, voters: vec![(1 as AccountId, 10), (3, 15)] }
427 ),
428 (30, Support { total: 35, voters: vec![(2, 20), (3, 15)] })
429 ]
430 .try_into()
431 .unwrap()
432 );
433 })
434 }
435}