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}