pallet_authors/election.rs
1// SPDX-License-Identifier: MPL-2.0
2//
3// Part of Auguth Labs open-source softwares.
4// Built for the Substrate framework.
5//
6// This Source Code Form is subject to the terms of the Mozilla Public
7// License, v. 2.0. If a copy of the MPL was not distributed with this
8// file, You can obtain one at https://mozilla.org/MPL/2.0/.
9//
10// Copyright (c) 2026 Auguth Labs (OPC) Pvt Ltd, India
11
12// ===============================================================================
13// `````````````````````````````` AUTHORS ELECTIONS ``````````````````````````````
14// ===============================================================================
15
16//! Provides **concrete election implementations** for author
17//! selection using **[`plugin`](frame_suite::plugins)-based election traits**.
18//!
19//! It binds the generic [`election`](frame_suite::elections) abstractions
20//! defined in to pallet-specific storage, configuration, and runtime models.
21//!
22//! The module implements two distinct election strategies:
23//!
24//! ## Flat Election
25//!
26//! - Aggregates all economic exposure of an author (self-collateral and
27//! third-party backing) into a **single influence value**.
28//! - Influence is computed via a runtime-configured [`Influence`] plugin model.
29//! - Each author contributes exactly one comparable weight into the election.
30//!
31//! This model favors **total economic commitment**, regardless of its source.
32//!
33//! ## Fair Election
34//!
35//! - Preserves **individual backing contributions** from external funders.
36//! - Explicitly may include candidate's self-collateral from election weight as
37//! one of the backers.
38//! - Each backer contributes a distinct weight entry for the author.
39//!
40//! This model favors **distributed support** and discourages dominance through
41//! self-backed influence.
42//!
43//! ## Architecture
44//!
45//! Both election modes:
46//!
47//! - Implement [`InspectWeight`] to expose candidate weights in a
48//! model-appropriate form.
49//! - Implement [`ElectionManager`] to:
50//! - prepare election inputs,
51//! - invoke plugin-based election models,
52//! - enforce governance constraints (minimum / maximum elected),
53//! - persist election results,
54//! - and emit lifecycle events.
55//!
56//! All election computation logic is delegated to **runtime-configured plugins**.
57//! This ensures that:
58//!
59//! - election algorithms can evolve without pallet code changes,
60//! - multiple election strategies can coexist safely,
61//! - and governance retains control over election semantics.
62//!
63//! ## Storage Semantics
64//!
65//! - Election results are stored per block and keyed by the most recent
66//! election round.
67//! - Historical elections remain immutable.
68//! - Removal operations only affect the latest election state.
69//!
70//! ## Design Guarantees
71//!
72//! - No election logic is hardcoded in this module.
73//! - Influence and weight calculations are fully externalized.
74//! - Strong type safety is preserved across all election paths.
75//!
76//! This module serves as the **bridge between abstract election traits
77//! and pallet-level author governance**.
78
79// ===============================================================================
80// ``````````````````````````````````` IMPORTS ```````````````````````````````````
81// ===============================================================================
82
83// --- Local crate imports ---
84use crate::{
85 types::{
86 Author, AuthorAsset, BackingElectionWeight, ElectViaBacking, ElectViaInfluence,
87 ElectedAuthors,
88 },
89 Config, Elected, Error, Event, FairElection, FlatElection, ForceMaxElected, MaxElected,
90 MinElected, Pallet, RecentElectedOn,
91};
92
93// --- FRAME Suite ---
94use frame_suite::{
95 elections::{ElectionManager, Influence, InspectWeight},
96 roles::{CompensateRoles, FundRoles},
97};
98
99// --- Substrate primitives ---
100use sp_core::Get;
101use sp_runtime::{traits::Zero, DispatchError, DispatchResult, Vec};
102
103// --- Substrate std (no_std helpers) ---
104use sp_std::vec;
105
106// ===============================================================================
107// ```````````````````````````````` FLAT-ELECTION ````````````````````````````````
108// ===============================================================================
109
110/// Implementation of the [`Influence`] trait for [`FlatElection`].
111///
112/// This binds the [`FlatElection`] election system to a **concrete
113/// influence computation** using [`Config::InfluenceModel`]
114///
115/// ## Influence Input
116///
117/// - [`AuthorAsset`]: The raw input type used to compute influence.
118/// - Typically represents an aggregated backing asset associated
119/// with the author ([`Author`]).
120impl<T: Config> Influence<AuthorAsset<T>> for FlatElection<T> {
121 /// The resulting influence type, as defined in the
122 /// runtime configuration.
123 type Influence = T::Influence;
124
125 /// The plugin context used for influence computation, providing
126 /// runtime parameters, thresholds, or local configuration needed
127 /// by the plugin model.
128 type InfluenceContext = T::InfluenceContext;
129
130 /// The plugin model used to perform the computation, implementing
131 /// the logic to convert [`AuthorAsset`] into `Self::Influence`.
132 type InfluenceModel = T::InfluenceModel;
133}
134
135/// Implementation of the [`InspectWeight`] trait for [`FlatElection`].
136///
137/// This provides a way to **inspect the computed weight** of an [`Author`]
138/// in terms of influence, leveraging the generic influence computation
139/// defined in the runtime.
140///
141/// ## Author Weight
142///
143/// - Returns the weight of a author as a `Vec<Influence>`.
144/// - Although `Influence` is a singular value, it is wrapped in a vector to
145/// satisfy the generic input requirements of [`ElectionManager`] for swappable
146/// [`FairElection`] and [`FlatElection`].
147/// - Each element (typically only one) represents a computed influence derived
148/// from the author's backing asset.
149impl<T: Config> InspectWeight<Author<T>, Vec<T::Influence>> for FlatElection<T> {
150 /// Returns the influence weight of an author wrapped in a vector.
151 ///
152 /// ## Behavior
153 /// 1. Fetches the total backing asset of the author using [`CompensateRoles::get_hold`].
154 /// 2. Computes the author's influence using the [`Influence`] implementation for [`FlatElection`].
155 /// 3. Wraps the result in a `Vec` and returns it.
156 ///
157 /// ## Errors
158 /// - Returns a [`DispatchError`] if fails.
159 fn weight_of(who: &Author<T>) -> Result<Vec<T::Influence>, DispatchError> {
160 // Fetch the backing asset (total hold) of the author (includes collateral + funding)
161 let hold = Pallet::<T>::get_hold(who)?;
162 // Compute influence from the asset
163 let influence = <Self as Influence<AuthorAsset<T>>>::influence(hold);
164 // return as a vector (`ElectionManager` trait's input param compatible)
165 Ok(vec![influence])
166 }
167}
168
169/// Implementation of the [`ElectionManager`] trait for [`FlatElection`].
170///
171/// This binds the [`FlatElection`] system to a **concrete election computation**
172/// using influence-based metrics, leveraging runtime-configured plugin models and contexts.
173///
174/// - Election weights are computed from [`Influence`] values for [`Author`].
175/// - Input type to the election plugin is [`ElectViaInfluence`] (candidates with their
176/// backing influences).
177/// - Output type is [`ElectedAuthors`] (a collection of elected candidates).
178impl<T: Config> ElectionManager<Author<T>> for FlatElection<T> {
179 /// Election weight type: corresponds to the singular influence of an author.
180 type ElectionWeight = T::Influence;
181
182 /// Collection type holding weights for an author.
183 ///
184 /// Although it is a vector, it typically contains only a single influence value.
185 type ElectionWeightOf = Vec<Self::ElectionWeight>;
186
187 /// Input type for election computation: authors paired with influence weights.
188 type Params = ElectViaInfluence<T>;
189
190 /// Output type representing elected authors.
191 type Elected = ElectedAuthors<T>;
192
193 /// Plugin context providing runtime configuration for the election model.
194 type ElectionContext = T::FlatElectionContext;
195
196 /// Plugin model implementing the election algorithm.
197 type ElectionModel = T::FlatElectionModel;
198
199 /// Retrieve the currently elected candidates.
200 ///
201 /// Fetches the authors elected in the **most recent election round**,
202 /// using [`RecentElectedOn`] to determine the latest block where
203 /// results were stored.
204 ///
205 /// ## Returns
206 /// - `Ok(Elected)` - A collection of elected authors.
207 /// - `None` - if none are elected
208 fn reveal() -> Option<Self::Elected> {
209 // Retrieve the most recent election block number.
210 let block = RecentElectedOn::<T>::get();
211
212 // Iterate over all elected authors stored under that block.
213 let iter = Elected::<T>::iter_prefix((block,));
214
215 // Prepare a collection for the converted elected authors.
216 let mut elects_converted: Self::Elected = Default::default();
217
218 // Collect all elected authors from storage.
219 for (author, _) in iter {
220 elects_converted.push(author.into());
221 }
222
223 // Return an error if no elected candidates were found.
224 if elects_converted.is_empty() {
225 return None;
226 }
227
228 Some(elects_converted)
229 }
230
231 /// Optimistically removes a candidate from the **most recent elected pool**.
232 ///
233 /// Directly updates storage by deleting the `(block, author)` entry
234 /// from [`Elected`] for the latest election block.
235 ///
236 /// Does not retroactively remove authors from historical elections.
237 fn remove(who: &Author<T>) {
238 let block = RecentElectedOn::<T>::get();
239 Elected::<T>::remove((block, who));
240 }
241
242 /// Search if the given candidate is an elected in the recent election.
243 ///
244 /// `DispatchError` otherwise
245 fn is_candidate(who: &Author<T>) -> DispatchResult {
246 let all = Self::reveal();
247 if let Some(elects) = all {
248 for elect in elects {
249 if elect == *who {
250 return Ok(());
251 }
252 }
253 }
254 Err(Error::<T>::AuthorNotElected.into())
255 }
256
257 /// Persist the election results into storage.
258 ///
259 /// Stores the newly elected authors under the current block number,
260 /// updating [`Elected`] and [`RecentElectedOn`].
261 ///
262 /// ## Behavior
263 /// - Ensures the number of elected candidates meets the minimum requirement.
264 /// - Optionally truncates to the maximum limit if [`ForceMaxElected`] is enabled.
265 /// - Each elected author is stored under `(current_block, author)`.
266 ///
267 /// ## Errors
268 /// - Returns a [`DispatchError`] if fails.
269 fn store(elects: &Self::Elected) -> DispatchResult {
270 let min_elect = MinElected::<T>::get();
271 debug_assert!(
272 !min_elect.is_zero(),
273 "`MinElected` must be greater than zero"
274 );
275 debug_assert!(
276 min_elect <= MaxElected::<T>::get(),
277 "`MinElected` must be lesser than or equal to `MaxElected`"
278 );
279 // Enforce the minimum elected candidate constraint.
280 if elects.len() < (min_elect as usize) {
281 return Err(Error::<T>::MinElectedNotReached.into());
282 }
283
284 // Get the current block number to use as the election key.
285 let block = frame_system::Pallet::<T>::block_number();
286
287 // Handle the maximum elected constraint.
288 match ForceMaxElected::<T>::get() {
289 // If forced, truncate to the configured maximum.
290 true => {
291 let max = MaxElected::<T>::get();
292 debug_assert!(
293 max >= MinElected::<T>::get(),
294 "`MaxElected` must be greater than or equal to `MinElected`"
295 );
296 let mut final_result = elects.clone();
297 final_result.truncate(max as usize);
298 for author in final_result {
299 Elected::<T>::insert((block, author), ());
300 }
301 }
302 // Otherwise, store all elected authors directly.
303 false => {
304 for author in elects.iter() {
305 Elected::<T>::insert((block, author), ());
306 }
307 }
308 }
309
310 // Record the block number of this election round.
311 RecentElectedOn::<T>::put(block);
312
313 Ok(())
314 }
315
316 /// Check if the election can be prepared with the given authors as candidates.
317 ///
318 /// Ensures that the provided candidate set meets the configured minimum
319 /// ([`MinElected`]) before initiating election computation.
320 fn can_prepare(from: &Self::Params) -> DispatchResult {
321 let min_elect = MinElected::<T>::get();
322 debug_assert!(
323 !min_elect.is_zero(),
324 "`MinElected` must be greater than zero"
325 );
326 debug_assert!(
327 min_elect <= MaxElected::<T>::get(),
328 "`MinElected` must be lesser than or equal to `MaxElected`"
329 );
330 if from.len() < min_elect as usize {
331 return Err(Error::<T>::InadequateCandidatesToElect.into());
332 }
333 Ok(())
334 }
335
336 /// Hook invoked after a successful election process.
337 fn on_prepare_success(elects: &Self::Elected) {
338 if T::EmitEvents::get() {
339 Pallet::<T>::deposit_event(Event::<T>::ElectionPrepared {
340 elects: elects.clone(),
341 });
342 }
343 }
344
345 /// Hook invoked when an election process fails.
346 fn on_prepare_fail(error: DispatchError) {
347 if T::EmitEvents::get() {
348 Pallet::<T>::deposit_event(Event::<T>::ElectionFailed { error });
349 }
350 }
351}
352
353// ===============================================================================
354// ```````````````````````````````` FAIR-ELECTION ````````````````````````````````
355// ===============================================================================
356
357/// Implementation of the [`InspectWeight`] trait for [`FairElection`].
358///
359/// This provides a way to **inspect the backing weights** of an [`Author`]
360/// in terms of their individual contributions from backers, leveraging the stored
361/// backing information in the pallet.
362///
363/// ## Author Weight
364///
365/// - Returns the weight of an author as a `Vec<BackingElectionWeight>`.
366/// Each element represents the backing contribution of an individual backer i.e.,
367/// an external funder.
368/// - Wrapping the contributions in a vector satisfies the generic input requirements
369/// of [`ElectionManager`] for both [`FairElection`] and [`FlatElection`].
370impl<T: Config> InspectWeight<Author<T>, Vec<BackingElectionWeight<T>>> for FairElection<T> {
371 /// Returns the backing weights of an author wrapped in a vector.
372 ///
373 /// ## Behavior
374 /// 1. Fetches the list of backers for the author using [`FundRoles::backers_of`].
375 /// 2. Returns the backers' contributions as a vector.
376 ///
377 /// ## Errors
378 /// - Returns a [`DispatchError`] if fails.
379 fn weight_of(who: &Author<T>) -> Result<Vec<BackingElectionWeight<T>>, DispatchError> {
380 // Fetch the list of backers for the author
381 let backers = Pallet::<T>::backers_of(who)?;
382 Ok(backers)
383 }
384}
385
386/// Implementation of the [`ElectionManager`] trait for [`FairElection`].
387///
388/// This binds the [`FairElection`] system to a **concrete election computation**
389/// using backing-based metrics, leveraging runtime-configured plugin models and contexts.
390///
391/// - Election weights are computed from [`BackingElectionWeight`] values for [`Author`].
392/// - Input type to the election plugin is [`ElectViaBacking`] (candidates with their
393/// backing contributions).
394/// - Output type is [`ElectedAuthors`] (a collection of elected candidates).
395impl<T: Config> ElectionManager<Author<T>> for FairElection<T> {
396 /// Election weight type: corresponds to a singular backing contribution to an author.
397 type ElectionWeight = BackingElectionWeight<T>;
398
399 /// Collection type holding all weights (backing contributions) of an author.
400 ///
401 /// Each author may have multiple backers; this vector represents all backing weights.
402 type ElectionWeightOf = Vec<Self::ElectionWeight>;
403
404 /// Input type for election computation: authors paired with their backing weights.
405 type Params = ElectViaBacking<T>;
406
407 /// Output type representing elected authors.
408 type Elected = ElectedAuthors<T>;
409
410 /// Plugin context providing runtime configuration for the election model.
411 type ElectionContext = T::FairElectionContext;
412
413 /// Plugin model implementing the election algorithm.
414 type ElectionModel = T::FairElectionModel;
415
416 //----- Redudant method implementations similar to `FlatElection` ---------
417
418 /// Retrieve the currently elected candidates.
419 ///
420 /// Fetches the authors elected in the **most recent election round**,
421 /// using [`RecentElectedOn`] to determine the latest block where
422 /// results were stored.
423 ///
424 /// ## Returns
425 /// - `Ok(Elected)` - A collection of elected authors.
426 /// - `None` otherwise.
427 fn reveal() -> Option<Self::Elected> {
428 // Retrieve the most recent election block number.
429 let block = RecentElectedOn::<T>::get();
430
431 // Iterate over all elected authors stored under that block.
432 let iter = Elected::<T>::iter_prefix((block,));
433
434 // Prepare a collection for the converted elected authors.
435 let mut elects_converted: Self::Elected = Default::default();
436
437 // Collect all elected authors from storage.
438 for (author, _) in iter {
439 elects_converted.push(author.into());
440 }
441
442 // Return an error if no elected candidates were found.
443 if elects_converted.is_empty() {
444 return None;
445 }
446
447 Some(elects_converted)
448 }
449
450 /// Optimistically remove a candidate from the **most recent elected pool**.
451 ///
452 /// Directly updates storage by deleting the `(block, author)` entry
453 /// from [`Elected`] for the latest election block.
454 ///
455 /// Does not retroactively remove authors from historical elections.
456 fn remove(who: &Author<T>) {
457 let block = RecentElectedOn::<T>::get();
458 Elected::<T>::remove((block, who));
459 }
460
461 /// Search if the given candidate is an elected in the recent election.
462 ///
463 /// `DispatchError` otherwise
464 fn is_candidate(who: &Author<T>) -> DispatchResult {
465 let all = Self::reveal();
466 if let Some(elects) = all {
467 for elect in elects {
468 if elect == *who {
469 return Ok(());
470 }
471 }
472 }
473 Err(Error::<T>::AuthorNotElected.into())
474 }
475
476 /// Persist the election results into storage.
477 ///
478 /// Stores the newly elected authors under the current block number,
479 /// updating [`Elected`] and [`RecentElectedOn`].
480 ///
481 /// ## Behavior
482 /// - Ensures the number of elected candidates meets the minimum requirement.
483 /// - Optionally truncates to the maximum limit if [`ForceMaxElected`] is enabled.
484 /// - Each elected author is stored under `(current_block, author)`.
485 ///
486 /// ## Errors
487 /// - Returns a [`DispatchError`] if fails.
488 fn store(elects: &Self::Elected) -> DispatchResult {
489 let min_elect = MinElected::<T>::get();
490 debug_assert!(
491 !min_elect.is_zero(),
492 "`MinElected` must be greater than zero"
493 );
494 debug_assert!(
495 min_elect <= MaxElected::<T>::get(),
496 "`MinElected` must be lesser than or equal to `MaxElected`"
497 );
498 // Enforce the minimum elected candidate constraint.
499 if elects.len() < (min_elect as usize) {
500 return Err(Error::<T>::MinElectedNotReached.into());
501 }
502
503 // Get the current block number to use as the election key.
504 let block = frame_system::Pallet::<T>::block_number();
505
506 // Handle the maximum elected constraint.
507 match ForceMaxElected::<T>::get() {
508 // If forced, truncate to the configured maximum.
509 true => {
510 let max = MaxElected::<T>::get();
511 debug_assert!(
512 max >= MinElected::<T>::get(),
513 "`MaxElected` must be greater than or equal to `MinElected`"
514 );
515 let mut final_result = elects.clone();
516 final_result.truncate(max as usize);
517 for author in final_result {
518 Elected::<T>::insert((block, author), ());
519 }
520 }
521 // Otherwise, store all elected authors directly.
522 false => {
523 for author in elects.iter() {
524 Elected::<T>::insert((block, author), ());
525 }
526 }
527 }
528
529 // Record the block number of this election round.
530 RecentElectedOn::<T>::put(block);
531
532 Ok(())
533 }
534
535 /// Check if the election can be prepared with the given authors as candidates.
536 ///
537 /// Ensures that the provided candidate set meets the configured minimum
538 /// (`MinElected`) before initiating election computation.
539 fn can_prepare(from: &Self::Params) -> DispatchResult {
540 let min_elect = MinElected::<T>::get();
541 debug_assert!(
542 !min_elect.is_zero(),
543 "`MinElected` must be greater than zero"
544 );
545 debug_assert!(
546 min_elect <= MaxElected::<T>::get(),
547 "`MinElected` must be lesser than or equal to `MaxElected`"
548 );
549 if from.len() < min_elect as usize {
550 return Err(Error::<T>::InadequateCandidatesToElect.into());
551 }
552 Ok(())
553 }
554
555 /// Hook invoked after a successful election process.
556 fn on_prepare_success(elects: &Self::Elected) {
557 if T::EmitEvents::get() {
558 Pallet::<T>::deposit_event(Event::<T>::ElectionPrepared {
559 elects: elects.clone(),
560 });
561 }
562 }
563 /// Hook invoked when an election process fails.
564 fn on_prepare_fail(error: DispatchError) {
565 if T::EmitEvents::get() {
566 Pallet::<T>::deposit_event(Event::<T>::ElectionFailed { error });
567 }
568 }
569}
570
571// ===============================================================================
572// `````````````````````````````````` UNIT TESTS `````````````````````````````````
573// ===============================================================================
574#[cfg(test)]
575mod tests {
576
577 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
578 // ``````````````````````````````````` IMPORTS ```````````````````````````````````
579 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
580
581 // --- Local crate imports ---
582 use crate::types::Funder;
583 use crate::{mock::*, Elected, RecentElectedOn};
584
585 // --- FRAME Suite ---
586 use frame_suite::{roles::*, ElectionManager, InspectWeight};
587
588 use frame_support::{assert_err, assert_ok};
589 // --- FRAME Support ---
590 use frame_support::traits::tokens::{Fortitude, Precision};
591 use sp_runtime::AccountId32;
592
593 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
594 // ```````````````````````````````` FLAT-ELECTION ````````````````````````````````
595 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
596
597 #[test]
598 fn weight_of_success_for_flat_election() {
599 authors_test_ext().execute_with(|| {
600 initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
601 initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
602 initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
603 initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
604 System::set_block_number(6);
605 // ALICE enrolls with a collateral of 100 units
606 Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
607
608 // BOB backed ALICE with 50 units
609 Pallet::fund(
610 &ALICE,
611 &Funder::Direct(BOB),
612 STANDARD_VALUE,
613 Precision::BestEffort,
614 Fortitude::Force,
615 )
616 .unwrap();
617
618 // CHARLIE backed ALICE with 100 units
619 Pallet::fund(
620 &ALICE,
621 &Funder::Direct(CHARLIE),
622 LARGE_VALUE,
623 Precision::BestEffort,
624 Fortitude::Force,
625 )
626 .unwrap();
627
628 // MIKE backed ALICE with 25 units
629 Pallet::fund(
630 &ALICE,
631 &Funder::Direct(MIKE),
632 SMALL_VALUE,
633 Precision::BestEffort,
634 Fortitude::Force,
635 )
636 .unwrap();
637
638 let influence =
639 <FlatElection as InspectWeight<AccountId32, Vec<u64>>>::weight_of(&ALICE).unwrap();
640
641 assert_eq!(influence, vec![225]);
642 })
643 }
644
645 #[test]
646 fn reveal_success_for_flat_election() {
647 authors_test_ext().execute_with(|| {
648 System::set_block_number(10);
649 RecentElectedOn::<Test>::put(10);
650 Elected::<Test>::insert((10, ALICE), ());
651 Elected::<Test>::insert((10, BOB), ());
652 Elected::<Test>::insert((10, MIKE), ());
653 Elected::<Test>::insert((10, NIX), ());
654 Elected::<Test>::insert((10, ALAN), ());
655 Elected::<Test>::insert((10, AMY), ());
656
657 let mut actual_elected =
658 <FlatElection as ElectionManager<AccountId32>>::reveal().unwrap();
659
660 let mut expected_elected = vec![ALICE, BOB, MIKE, NIX, ALAN, AMY];
661 actual_elected.sort();
662 expected_elected.sort();
663 assert_eq!(actual_elected, expected_elected);
664 })
665 }
666
667 #[test]
668 fn reveal_returns_none_for_flat_election() {
669 authors_test_ext().execute_with(|| {
670 System::set_block_number(450);
671 RecentElectedOn::<Test>::put(450);
672 Elected::<Test>::insert((450, ALICE), ());
673 Elected::<Test>::insert((450, BOB), ());
674 Elected::<Test>::insert((450, MIKE), ());
675 Elected::<Test>::insert((450, NIX), ());
676 Elected::<Test>::insert((450, ALAN), ());
677 Elected::<Test>::insert((450, AMY), ());
678
679 System::set_block_number(900);
680 RecentElectedOn::<Test>::put(900);
681 assert!(<FlatElection as ElectionManager<AccountId32>>::reveal().is_none());
682 })
683 }
684
685 #[test]
686 fn remove_success_for_flat_election() {
687 authors_test_ext().execute_with(|| {
688 System::set_block_number(10);
689 RecentElectedOn::<Test>::put(10);
690 Elected::<Test>::insert((10, ALICE), ());
691 Elected::<Test>::insert((10, BOB), ());
692 Elected::<Test>::insert((10, MIKE), ());
693 Elected::<Test>::insert((10, NIX), ());
694 Elected::<Test>::insert((10, ALAN), ());
695 Elected::<Test>::insert((10, AMY), ());
696
697 let mut actual_elected =
698 <FlatElection as ElectionManager<AccountId32>>::reveal().unwrap();
699 let mut expected_elected = vec![ALICE, BOB, MIKE, NIX, ALAN, AMY];
700 actual_elected.sort();
701 expected_elected.sort();
702 assert_eq!(actual_elected, expected_elected);
703
704 <FlatElection as ElectionManager<AccountId32>>::remove(&NIX);
705
706 let mut actual_elected =
707 <FlatElection as ElectionManager<AccountId32>>::reveal().unwrap();
708 let mut expected_elected = vec![ALICE, BOB, MIKE, ALAN, AMY];
709 actual_elected.sort();
710 expected_elected.sort();
711 assert_eq!(actual_elected, expected_elected);
712
713 <FlatElection as ElectionManager<AccountId32>>::remove(&AMY);
714
715 let mut actual_elected =
716 <FlatElection as ElectionManager<AccountId32>>::reveal().unwrap();
717 let mut expected_elected = vec![ALICE, BOB, MIKE, ALAN];
718 actual_elected.sort();
719 expected_elected.sort();
720 assert_eq!(actual_elected, expected_elected);
721 })
722 }
723
724 #[test]
725 fn is_candidate_success_for_flat_election() {
726 authors_test_ext().execute_with(|| {
727 System::set_block_number(10);
728 RecentElectedOn::<Test>::put(10);
729 Elected::<Test>::insert((10, ALICE), ());
730 Elected::<Test>::insert((10, BOB), ());
731 Elected::<Test>::insert((10, MIKE), ());
732 Elected::<Test>::insert((10, ALAN), ());
733 Elected::<Test>::insert((10, AMY), ());
734
735 assert_ok!(<FlatElection as ElectionManager<AccountId32>>::is_candidate(&ALICE));
736 assert_err!(
737 <FlatElection as ElectionManager<AccountId32>>::is_candidate(&NIX),
738 Error::AuthorNotElected
739 );
740 })
741 }
742
743 #[test]
744 fn store_success_for_flat_election() {
745 authors_test_ext().execute_with(|| {
746 System::set_block_number(25);
747 let mut elects = vec![ALICE, ALAN, NIX, AMY, MIKE, BOB];
748 assert!(<FlatElection as ElectionManager<AccountId32>>::reveal().is_none());
749 assert_ok!(<FlatElection as ElectionManager<AccountId32>>::store(
750 &elects
751 ));
752
753 let elected = <FlatElection as ElectionManager<AccountId32>>::reveal();
754 assert!(elected.is_some());
755 let mut elected = elected.unwrap();
756 elects.sort();
757 elected.sort();
758 assert_eq!(elects, elected);
759 assert_eq!(RecentElectedOn::<Test>::get(), 25);
760 })
761 }
762
763 #[test]
764 fn store_err_min_elected_not_reached_for_flat_election() {
765 authors_test_ext().execute_with(|| {
766 System::set_block_number(10);
767 let elects = vec![ALICE, ALAN, NIX, AMY, MIKE];
768 // Since, min_elected is set to 6
769 assert_err!(
770 <FlatElection as ElectionManager<AccountId32>>::store(&elects),
771 Error::MinElectedNotReached
772 );
773 })
774 }
775
776 #[test]
777 fn on_prepare_success_emits_event_for_flat_election() {
778 authors_test_ext().execute_with(|| {
779 System::set_block_number(10);
780 let elects = vec![ALICE, ALAN, NIX, AMY, MIKE, BOB];
781 <FlatElection as ElectionManager<AccountId32>>::on_prepare_success(&elects);
782
783 System::assert_last_event(Event::ElectionPrepared { elects }.into());
784 })
785 }
786
787 #[test]
788 fn on_prepare_fail_emits_event_for_flat_election() {
789 authors_test_ext().execute_with(|| {
790 System::set_block_number(10);
791 let error = Error::MinElectedNotReached.into();
792 <FlatElection as ElectionManager<AccountId32>>::on_prepare_fail(error);
793
794 System::assert_last_event(Event::ElectionFailed { error: error }.into());
795 })
796 }
797
798 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
799 // ```````````````````````````````` FAIR-ELECTION ````````````````````````````````
800 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
801
802 #[test]
803 fn weight_of_success_for_fair_election() {
804 authors_test_ext().execute_with(|| {
805 initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
806 initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
807 initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
808 initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
809 initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
810
811 Pallet::enroll(&ALICE, LARGE_VALUE, Fortitude::Force).unwrap();
812
813 Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
814
815 Pallet::fund(
816 &ALICE,
817 &Funder::Direct(CHARLIE),
818 STANDARD_VALUE,
819 Precision::BestEffort,
820 Fortitude::Force,
821 )
822 .unwrap();
823
824 Pallet::fund(
825 &BOB,
826 &Funder::Direct(MIKE),
827 LARGE_VALUE,
828 Precision::BestEffort,
829 Fortitude::Force,
830 )
831 .unwrap();
832
833 Pallet::fund(
834 &ALICE,
835 &Funder::Direct(ALAN),
836 SMALL_VALUE,
837 Precision::BestEffort,
838 Fortitude::Force,
839 )
840 .unwrap();
841
842 let alice_weight = FairElection::weight_of(&ALICE).unwrap();
843
844 let bob_weight = FairElection::weight_of(&BOB).unwrap();
845
846 let expected_alice_weight =
847 vec![(Funder::Direct(ALAN), 25), (Funder::Direct(CHARLIE), 50)];
848 let expected_bob_weight = vec![(Funder::Direct(MIKE), 100)];
849
850 assert_eq!(alice_weight, expected_alice_weight);
851 assert_eq!(bob_weight, expected_bob_weight);
852 })
853 }
854
855 #[test]
856 fn reveal_success_for_fair_election() {
857 authors_test_ext().execute_with(|| {
858 System::set_block_number(10);
859 RecentElectedOn::<Test>::put(10);
860 Elected::<Test>::insert((10, ALICE), ());
861 Elected::<Test>::insert((10, BOB), ());
862 Elected::<Test>::insert((10, MIKE), ());
863 Elected::<Test>::insert((10, NIX), ());
864 Elected::<Test>::insert((10, ALAN), ());
865 Elected::<Test>::insert((10, AMY), ());
866
867 let mut actual_elected =
868 <FairElection as ElectionManager<AccountId32>>::reveal().unwrap();
869
870 let mut expected_elected = vec![ALICE, BOB, MIKE, NIX, ALAN, AMY];
871 actual_elected.sort();
872 expected_elected.sort();
873 assert_eq!(actual_elected, expected_elected);
874 })
875 }
876
877 #[test]
878 fn reveal_returns_none_for_fair_election() {
879 authors_test_ext().execute_with(|| {
880 System::set_block_number(450);
881 RecentElectedOn::<Test>::put(450);
882 Elected::<Test>::insert((450, ALICE), ());
883 Elected::<Test>::insert((450, BOB), ());
884 Elected::<Test>::insert((450, MIKE), ());
885 Elected::<Test>::insert((450, NIX), ());
886 Elected::<Test>::insert((450, ALAN), ());
887 Elected::<Test>::insert((450, AMY), ());
888
889 System::set_block_number(900);
890 RecentElectedOn::<Test>::put(900);
891 assert!(<FairElection as ElectionManager<AccountId32>>::reveal().is_none());
892 })
893 }
894
895 #[test]
896 fn remove_success_for_fair_election() {
897 authors_test_ext().execute_with(|| {
898 System::set_block_number(10);
899 RecentElectedOn::<Test>::put(10);
900 Elected::<Test>::insert((10, ALICE), ());
901 Elected::<Test>::insert((10, BOB), ());
902 Elected::<Test>::insert((10, MIKE), ());
903 Elected::<Test>::insert((10, NIX), ());
904 Elected::<Test>::insert((10, ALAN), ());
905 Elected::<Test>::insert((10, AMY), ());
906
907 let mut actual_elected =
908 <FairElection as ElectionManager<AccountId32>>::reveal().unwrap();
909 let mut expected_elected = vec![ALICE, BOB, MIKE, NIX, ALAN, AMY];
910 actual_elected.sort();
911 expected_elected.sort();
912 assert_eq!(actual_elected, expected_elected);
913
914 <FairElection as ElectionManager<AccountId32>>::remove(&NIX);
915
916 let mut actual_elected =
917 <FairElection as ElectionManager<AccountId32>>::reveal().unwrap();
918 let mut expected_elected = vec![ALICE, BOB, MIKE, ALAN, AMY];
919 actual_elected.sort();
920 expected_elected.sort();
921 assert_eq!(actual_elected, expected_elected);
922
923 <FairElection as ElectionManager<AccountId32>>::remove(&AMY);
924
925 let mut actual_elected =
926 <FairElection as ElectionManager<AccountId32>>::reveal().unwrap();
927 let mut expected_elected = vec![ALICE, BOB, MIKE, ALAN];
928 actual_elected.sort();
929 expected_elected.sort();
930 assert_eq!(actual_elected, expected_elected);
931 })
932 }
933
934 #[test]
935 fn is_candidate_success_for_fair_election() {
936 authors_test_ext().execute_with(|| {
937 System::set_block_number(10);
938 RecentElectedOn::<Test>::put(10);
939 Elected::<Test>::insert((10, ALICE), ());
940 Elected::<Test>::insert((10, BOB), ());
941 Elected::<Test>::insert((10, MIKE), ());
942 Elected::<Test>::insert((10, ALAN), ());
943 Elected::<Test>::insert((10, AMY), ());
944
945 assert_ok!(<FairElection as ElectionManager<AccountId32>>::is_candidate(&ALICE));
946 assert_err!(
947 <FairElection as ElectionManager<AccountId32>>::is_candidate(&NIX),
948 Error::AuthorNotElected
949 );
950 })
951 }
952
953 #[test]
954 fn store_success_for_fair_election() {
955 authors_test_ext().execute_with(|| {
956 System::set_block_number(25);
957 let mut elects = vec![ALICE, ALAN, NIX, AMY, MIKE, BOB];
958 assert!(<FairElection as ElectionManager<AccountId32>>::reveal().is_none());
959 assert_ok!(<FairElection as ElectionManager<AccountId32>>::store(
960 &elects
961 ));
962
963 let elected = <FairElection as ElectionManager<AccountId32>>::reveal();
964 assert!(elected.is_some());
965 let mut elected = elected.unwrap();
966 elects.sort();
967 elected.sort();
968 assert_eq!(elects, elected);
969 assert_eq!(RecentElectedOn::<Test>::get(), 25);
970 })
971 }
972
973 #[test]
974 fn store_err_min_elected_not_reached_for_fair_election() {
975 authors_test_ext().execute_with(|| {
976 System::set_block_number(10);
977 let elects = vec![ALICE, ALAN, NIX, AMY, MIKE];
978 // Since, min_elected is set to 6
979 assert_err!(
980 <FairElection as ElectionManager<AccountId32>>::store(&elects),
981 Error::MinElectedNotReached
982 );
983 })
984 }
985
986 #[test]
987 fn on_prepare_success_emits_event_for_fair_election() {
988 authors_test_ext().execute_with(|| {
989 System::set_block_number(10);
990 let elects = vec![ALICE, ALAN, NIX, AMY, MIKE, BOB];
991 <FairElection as ElectionManager<AccountId32>>::on_prepare_success(&elects);
992
993 System::assert_last_event(Event::ElectionPrepared { elects }.into());
994 })
995 }
996
997 #[test]
998 fn on_prepare_fail_emits_event_for_fair_election() {
999 authors_test_ext().execute_with(|| {
1000 System::set_block_number(10);
1001 let error = Error::MinElectedNotReached.into();
1002 <FairElection as ElectionManager<AccountId32>>::on_prepare_fail(error);
1003
1004 System::assert_last_event(Event::ElectionFailed { error: error }.into());
1005 })
1006 }
1007}