pallet_authors/
lib.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// ``````````````````````````````` PALLET AUTHORS ````````````````````````````````
14// ===============================================================================
15
16//! The **Authors pallet** implements a economically-backed **role system**
17//! for managing **block authors* (validators) as first-class on-chain actors.
18//!
19//! This pallet provides a concrete, implementation of the
20//! generic role abstractions, enabling authors to be enrolled, funded, elected,
21//! rewarded, penalized, and governed in a deterministic and auditable manner.
22//!
23//! - [`Config`] - Runtime configuration
24//! - [`Call`] - Dispatchable extrinsics
25//! - [`Pallet`] - External Usage (Trait Impls)
26//! - [`FlatElection`] - External Usage (Resolved Plugin)
27//! - [`FairElection`]- External Usage (Resolved Plugin)
28//!
29//! ## Overview
30//!
31//! An **Author** represents an actor who:
32//! - locks **self-collateral** as a security commitment (enrollment),
33//! - progresses through a **probationary lifecycle** before permanence.
34//! - may receive **external backing** from third parties (funding),
35//! - participates in **elections** and other duties,
36//! - accrues **rewards and penalties over time**, and
37//!
38//! The pallet models authors as **economic and temporal subjects**, not merely
39//! accounts.
40//!
41//! ## Architectural Role
42//!
43//! The pallet provides a **composable role layer** that integrates:
44//! - lifecycle management (enrollment, status, resignation),
45//! - economic backing (collateral and external funding),
46//! - reward and penalty scheduling,
47//! - probation and risk handling,
48//! - activity-aware participation controls.
49//!
50//! This allows the Authors pallet to act as a **pluggable role provider**
51//! for other runtime modules without exposing low-level storage details.
52//!
53//! ## Funding Models
54//!
55//! Authors may receive economic support through multiple funding paths:
56//!
57//! - **Self-collateral** provided during enrollment.
58//! - **Direct backing** from individual backers.
59//! - **Aggregated backing** via indexes or pools (when enabled).
60//!
61//! All funding is mediated through a shared commitment abstraction, ensuring
62//! consistent locking, accounting, and release semantics across the runtime.
63//!
64//! ## Election Models
65//!
66//! The pallet supports **pluggable election strategies** via
67//! [`elections`](frame_suite::elections) traits, selectable at runtime-composition:
68//!
69//! - **Flat elections** aggregate all backing (including self-collateral)
70//!   into a single influence metric per author.
71//! - **Fair elections** preserve individual backer contributions and
72//!   explicitly including self-collateral as a self-backing.
73//!
74//! Election logic is intentionally externalized using plugin-based models via [`Config`],
75//! allowing governance to evolve influence and election logic without modifying pallet code.
76//!
77//! ## Temporal Semantics
78//!
79//! Rewards and penalties are:
80//! - **scheduled**, not immediate,
81//! - applied deterministically at block boundaries,
82//! - buffered to prevent manipulation and race conditions.
83//!
84//! The pallet processes all pending compensations at the **start of each block**,
85//! guaranteeing consistent state for subsequent extrinsics and elections.
86//!
87//! ## Governance & Safety
88//!
89//! Governance or Sudo may:
90//! - adjust collateral and funding thresholds,
91//! - tune probation and risk parameters,
92//! - control election bounds and strictness,
93//! - override configuration via root-only calls.
94//!
95//! Storage is intentionally **append-only** for critical mappings,
96//! prioritizing auditability and long-term safety over aggressive cleanup.
97//!
98//! ## Intended Use
99//!
100//! While named "Authors", this pallet is designed to be reusable for any
101//! economically-backed role such as:
102//! - block producers,
103//! - content curators,
104//! - oracle operators,
105//! - council members,
106//! - or DAO representatives.
107//!
108//! It serves both as a reusable production pallet and as a reference
109//! implementation of advanced role abstractions.
110//!
111//! ## Development Feature Gate
112//!
113//! This pallet includes a `dev` feature gate for development and testing.
114//!
115//! Core functionality is exposed via public APIs for RPC and UI usage.
116//! The `dev` feature provides thin wrapper extrinsics and extended
117//! events for direct inspection.
118//!
119//! This feature must be disabled in production runtimes due to additional debugging overhead.
120
121#![cfg_attr(not(feature = "std"), no_std)]
122
123// ===============================================================================
124// `````````````````````````````````` MODULES ````````````````````````````````````
125// ===============================================================================
126
127#[cfg(feature = "runtime-benchmarks")]
128mod benchmarking;
129#[cfg(test)]
130mod mock;
131#[cfg(test)]
132mod tests;
133mod roles;
134mod election;
135pub mod types;
136pub mod weights;
137
138// ===============================================================================
139// `````````````````````````````` PALLET MODULE ``````````````````````````````````
140// ===============================================================================
141
142pub use pallet::*;
143
144#[frame_support::pallet]
145pub mod pallet {
146
147    // ===============================================================================
148    // ````````````````````````````````` IMPORTS `````````````````````````````````````
149    // ===============================================================================
150
151    // --- Local crate imports ---
152    use crate::{types::*, weights::*};
153
154    // --- FRAME Suite ---
155    use frame_suite::{base::*, commitment::*, plugin_types, roles::*};
156
157    // --- Core / Std ---
158    use core::{fmt::Debug, marker::PhantomData};
159
160    // --- FRAME Support ---
161    use frame_support::{
162        dispatch::DispatchResult,
163        ensure,
164        pallet_prelude::*,
165        traits::{
166            fungible::{Inspect, Mutate, UnbalancedHold},
167            tokens::{Balance, Fortitude, Precision},
168            VariantCount,
169        },
170    };
171
172    // --- FRAME System ---
173    use frame_system::{
174        ensure_root, ensure_signed,
175        pallet_prelude::{BlockNumberFor, OriginFor},
176    };
177
178    // --- Substrate primitives ---
179    use sp_runtime::{traits::Bounded, DispatchError, Saturating, Vec};
180
181    // ===============================================================================
182    // `````````````````````````````` PALLET MARKER ``````````````````````````````````
183    // ===============================================================================
184
185    /// Primary Marker type for the **Authors pallet**.
186    ///
187    /// This pallet provides implementations for traits from
188    /// [`roles`](frame_suite::roles) and [`elections`](frame_suite::elections)
189    ///
190    /// [`Pallet`] implements the core role-system traits:
191    ///
192    /// - [`RoleManager`](frame_suite::roles::RoleManager)
193    /// - [`FundRoles`]
194    /// - [`CompensateRoles`]
195    /// - [`RoleProbation`]
196    /// - [`RoleManager`](frame_suite::roles::RoleManager)
197    ///
198    /// and **pluggable** election runner traits
199    ///
200    /// - [`InspectWeight`](frame_suite::elections::InspectWeight)
201    /// - [`ElectionManager`](frame_suite::elections::ElectionManager)
202    /// - [`Influence`](frame_suite::elections::Influence)
203    #[pallet::pallet]
204    pub struct Pallet<T>(_);
205
206    // ===============================================================================
207    // `````````````````````````````` CONFIG TRAIT ```````````````````````````````````
208    // ===============================================================================
209
210    /// Configuration trait for the Authors pallet.
211    ///
212    /// This trait defines the types, constants, and dependencies
213    /// that the runtime must provide for this pallet to function.
214    #[pallet::config]
215    pub trait Config: frame_system::Config {
216        // --- Runtime Anchors ---
217
218        /// The overarching event type for this pallet.
219        ///
220        /// Allows the Authors pallet to emit events into the runtime event system,
221        /// e.g., when authors are rewarded, penalized, or change status.
222        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
223
224        /// Type representing reason for freezing assets.
225        ///
226        /// Converts from the pallet-level `FreezeReason` enum and is used by
227        /// the `CommitmentAdapter` to distinguish between different frozen asset categories.
228        type AssetFreeze: From<FreezeReason> + RuntimeEnum + Delimited + Copy + VariantCount;
229
230        // --- Scalars ---
231
232        /// Represents the **computed influence** of an entity (author, account, etc.).
233        ///
234        /// Influence is derived from a raw backing asset (stake, tokens, or other measure)
235        /// and is used as the primary metric for calculating an author's influence over others.
236        ///
237        /// Must implement a unsigned numeric type and support conversion from the raw fungible
238        /// asset.
239        type Influence: Balance + From<AuthorAsset<Self>>;
240
241        // --- Pallet Adapters ---
242
243        /// The **asset type** used for distributing rewards and penalties.
244        ///
245        /// This represents a fungible unit (e.g., a token balance) over which
246        /// the pallet performs **reward**, **penalty**, and **funding** operations.
247        ///
248        /// ## Requirements
249        /// - Must implement [`Inspect`], enabling the pallet to query and verify
250        ///   account balances or holdings of authors.
251        ///
252        /// ## Example
253        /// ```ignore
254        /// type Asset = pallet_balances::Pallet<T>;
255        /// ```
256        type Asset: Inspect<
257                Author<Self>,
258                // Ensures that the pallet's `Asset` type aligns with the assets
259                // used by commitment system.
260                //
261                // Guarantees that rewards, penalties, and funding operations
262                // use a **consistent asset type**.
263                Balance = <Self::CommitmentAdapter as InspectAsset<Author<Self>>>::Asset,
264            > + Mutate<Author<Self>>
265            + UnbalancedHold<Author<Self>>;
266
267        /// Commitment trait adapter for managing authors' external fundings,
268        /// assets and digests.
269        ///
270        /// This type implements multiple traits:
271        /// - [`Commitment`] to track asset commitments per author.
272        /// - [`CommitIndex`] for index-based funders.
273        /// - [`CommitPool`] for pool-backed funding.
274        /// - [`InspectAsset`] to query asset balances or holds.
275        ///
276        /// Provides the **core mechanism for holding and tracking author funds**.
277        type CommitmentAdapter: Commitment<
278                Author<Self>,
279                Reason = Self::AssetFreeze,
280                DigestSource = Author<Self>,
281                Digest: Ord,
282            > + CommitIndex<Author<Self>>
283            + CommitPool<Author<Self>>
284            + InspectAsset<Author<Self>>;
285
286        /// Activity provider for authors, defining hooks or queries related to
287        /// an author's participation and behavior within the runtime.
288        ///
289        /// This type allows the **Authors pallet to expose author activity to
290        /// other pallets** that rely on author role participation.
291        ///
292        /// In essence, it provides a standardized, pluggable interface for
293        /// **cross-pallet activity tracking**.
294        type ActivityProvider: RoleActivity<Author<Self>, BlockNumberFor<Self>>;
295
296        // --- Plugins ---
297
298        // Plugin for computing **influence values** from raw backing assets.
299        //
300        // The computation logic is fully **pluggable** and **runtime-configurable**,
301        // allowing influence policies to evolve without modifying pallet logic.
302        plugin_types!(
303            // Raw backing asset used as input for influence computation.
304            //
305            // Typically represents stake, funds, votes, or other measurable support
306            // associated with an entity.
307            //
308            // The interpretation of this value is entirely model-dependent.
309            input: AuthorAsset<Self>,
310
311            // Computed influence value derived from the raw backing asset.
312            //
313            // This singular metric represents the effective weight or power of an
314            // entity in influence-based decision systems.
315            //
316            // Influence values are comparable and suitable for ordering, weighting,
317            // or normalization, as required by downstream logic.
318            output: Self::Influence,
319
320            /// **Influence computation plugin model**.
321            ///
322            /// Influence is a derived, comparable value used in influence-based systems
323            /// such as elections, ranking, scoring, or governance decisions.
324            ///
325            /// Encapsulates the logic that transforms a raw asset value into an
326            /// influence metric.
327            ///
328            /// Conceptually performs:
329            ///
330            /// `AuthorAsset -> Influence`
331            ///
332            /// The specific transformation semantics are entirely defined by the
333            /// selected model.
334            ///
335            /// Designed to be selectable using template plugin models in
336            /// [`frame_plugins::influence`] or custom model defining
337            /// macros via [`frame_suite::plugins`].
338            model: InfluenceModel,
339
340            /// Plugin model **context** for influence computation.
341            ///
342            /// Supplies parameters that configure how the [`Self::InfluenceModel`]
343            /// behaves at runtime.
344            ///
345            /// This context enables dynamic tuning of influence policies via
346            /// governance or configuration, without modifying the model implementation
347            /// or pallet logic.
348            ///
349            /// Must match the context type expected by the selected
350            /// [`Self::InfluenceModel`].
351            context: InfluenceContext,
352        );
353
354        // Plugin for computing **flat elections** using influence-based metrics.
355        //
356        // This plugin performs election computation by evaluating candidates
357        // (authors) solely on their computed influence values.
358        //
359        // The election logic is fully **pluggable** and **runtime-configurable**,
360        // allowing different influence-based election policies to be applied
361        // without modifying pallet logic.
362        plugin_types!(
363            // Input collection for flat election computation.
364            //
365            // Represents a set of candidates paired with their computed
366            // influence values.
367            //
368            // Each candidate is associated with exactly one influence metric,
369            // typically derived via the influence computation plugin.
370            input: ElectViaInfluence<Self>,
371
372            // Output collection of elected candidates.
373            //
374            // May represent a single elected author or multiple elected authors,
375            // depending on the configured election model.
376            output: ElectedAuthors<Self>,
377
378            /// **Flat election plugin model**.
379            ///
380            /// Encapsulates the election logic that operates on influence values
381            /// to determine the final set of elected candidates.
382            ///
383            /// Conceptually performs:
384            ///
385            /// `[(Author, Influence)] -> [Author]`
386            ///
387            /// The model is expected to return elected candidates in **priority
388            /// order**, as the runtime may truncate the result using
389            /// [`MaxElected`] or [`ForceMaxElected`].
390            ///
391            /// Designed to be selectable using template plugin models in
392            /// [`frame_plugins::elections::flat`] or custom model defining
393            /// macros via [`frame_suite::plugins`].
394            model: FlatElectionModel,
395
396            /// Plugin model **context** for flat election computation.
397            ///
398            /// Supplies parameters that configure how the
399            /// [`Self::FlatElectionModel`] behaves at runtime.
400            ///
401            /// Enables dynamic tuning of election policies without modifying
402            /// the model implementation or pallet logic.
403            ///
404            /// Must match the context type expected by the selected
405            /// [`Self::FlatElectionModel`].
406            context: FlatElectionContext,
407        );
408
409        // Plugin for computing **fair elections** using backing-based metrics.
410        //
411        // This plugin performs election computation by evaluating candidates
412        // (authors) based on their individual backing contributions.
413        //
414        // Each backing relationship is preserved and considered independently,
415        // ensuring that election outcomes reflect the structure and distribution
416        // of external support.
417        //
418        // The election logic is fully **pluggable** and **runtime-configurable**.
419        plugin_types!(
420            // Input collection for fair election computation.
421            //
422            // Represents a set of candidates paired with their backing
423            // contributions, where each candidate may be associated with
424            // multiple backers and corresponding weights.
425            input: ElectViaBacking<Self>,
426
427            // Output collection of elected candidates.
428            //
429            // May represent a single elected author or multiple elected authors,
430            // depending on the configured election model.
431            output: ElectedAuthors<Self>,
432
433            /// **Fair election plugin model**.
434            ///
435            /// Encapsulates the election logic that operates on individual
436            /// backing contributions to determine the final set of elected
437            /// candidates.
438            ///
439            /// Conceptually performs:
440            ///
441            /// `[(Author, [(Backer, Weight)])] -> [Author]`
442            ///
443            /// The model is expected to return elected candidates in **priority
444            /// order**, as the runtime may truncate the result using
445            /// [`MaxElected`] or [`ForceMaxElected`].
446            ///
447            /// Designed to be selectable using template plugin models in
448            /// [`frame_plugins::elections::fair`] or custom model defining
449            /// macros via [`frame_suite::plugins`].
450            model: FairElectionModel,
451
452            /// Plugin model **context** for fair election computation.
453            ///
454            /// Supplies parameters that configure how the
455            /// [`Self::FairElectionModel`] behaves at runtime.
456            ///
457            /// Enables dynamic tuning of election policies without modifying
458            /// the model implementation or pallet logic.
459            ///
460            /// Must match the context type expected by the selected
461            /// [`Self::FairElectionModel`].
462            context: FairElectionContext,
463        );
464
465        // --- Weights ---
466
467        /// Weight information for extrinsics in this pallet.
468        type WeightInfo: WeightInfo;
469
470        // --- Constants ---
471
472        #[pallet::constant]
473        type EmitEvents: Get<bool> + Clone + Debug;
474    }
475
476    // ===============================================================================
477    // ``````````````````````````````` COMPOSITE ENUMS ```````````````````````````````
478    // ===============================================================================
479
480    /// Reasons for which an author's assets may be temporarily frozen in the runtime.
481    ///
482    /// The `FreezeReason` enum is used to **categorize and isolate different types of
483    /// asset holds**, allowing the runtime to manage multiple constraints independently.
484    #[pallet::composite_enum]
485    pub enum FreezeReason {
486        /// Assets reserved due to **external author funding**.
487        ///
488        /// These are funds provided by backers or commitment systems, held to ensure
489        /// accountability, prevent double-spending, and enforce proper reward/penalty mechanics.
490        AuthorFunding,
491
492        /// Assets reserved as **author collateral**.
493        ///
494        /// Collateral represents the skin-in-the-game requirement for authors, ensuring
495        /// they have a stake in maintaining protocol security and correct behavior.
496        AuthorCollateral,
497    }
498
499    // ===============================================================================
500    // ``````````````````````````````` GENESIS CONFIG ````````````````````````````````
501    // ===============================================================================
502
503    /// Genesis configuration for the **Authors pallet**.
504    ///
505    /// Provides the **initial runtime parameters** governing author lifecycle,
506    /// funding, and reward/penalty mechanics at chain genesis.
507    ///
508    /// These values define the **baseline operational rules** before any on-chain
509    /// authors are enrolled or any activity occurs.
510    #[pallet::genesis_config]
511    pub struct GenesisConfig<T: Config> {
512        /// Minimum collateral an author must hold to participate.
513        /// Ensures skin-in-the-game and commitment to the network.
514        /// Must not be set to zero, to avoid no-commitment failures.
515        pub min_collateral: AuthorAsset<T>,
516
517        /// Maximum allowed exposure of a funding/backing operation instance.
518        /// Limits systemic risk from overly funded or leveraged participants.
519        ///
520        /// This is ambiguous in `pool` and `index` contexts as it only represents how
521        /// much the backer is willing to commit at a transaction, not specific to what scheme.
522        pub max_exposure: AuthorAsset<T>,
523
524        /// Minimum funding required of a funding/backing operation instance.
525        /// Guarantees sufficient community support before an author can participate.
526        /// Must not be set to zero, to avoid zero-commitment failures.
527        ///
528        /// This is ambiguous in `pool` and `index` contexts as it only represents how
529        /// much the backer is willing to commit at a transaction, not specific to what scheme.
530        pub min_fund: AuthorAsset<T>,
531
532        /// Number of blocks newly enrolled or demoted authors must spend in probation
533        /// before achieving permanent status.
534        /// Enforces behavioral observation and prevents immediate promotion.
535        pub probation_period: BlockNumberFor<T>,
536
537        /// Number of blocks to reduce an author's risk period when positive behavior is observed.
538        /// Facilitates promotion and rewards accountability.
539        pub reduce_probation_by: BlockNumberFor<T>,
540
541        /// Number of blocks to extend an author's risk period when unsafe behavior is detected.
542        /// Enforces accountability and ensures that authors under observation remain under supervision.
543        pub increase_probation_by: BlockNumberFor<T>,
544
545        /// Number of blocks to delay reward finalization.
546        /// Ensures orderly processing and temporal separation of reward events.
547        pub rewards_buffer: BlockNumberFor<T>,
548
549        /// Number of blocks to delay penalty finalization.
550        /// Allows authors to react or remediate before penalties are enforced.
551        pub penalties_buffer: BlockNumberFor<T>,
552
553        /// Maximum number of authors that can be elected in a single election round.
554        /// If `force_max_elected` is true, the elected list will be truncated to this limit.
555        pub max_elected: u32,
556
557        /// Minimum number of authors required to complete a valid election round.
558        /// Ensures that elections maintain a minimum quorum of participants.
559        pub min_elected: u32,
560
561        /// Whether to strictly enforce the `max_elected` limit.
562        /// - `true`: Forcefully truncate elected authors to `max_elected`.
563        /// - `false`: Attempt to store all elected candidates.
564        pub force_max_elected: bool,
565    }
566
567    impl<T: Config> Default for GenesisConfig<T> {
568        fn default() -> Self {
569            Self {
570                min_collateral: 1u32.into(),
571                max_exposure: Bounded::max_value(),
572                min_fund: 1u32.into(),
573                probation_period: 10u32.into(),
574                reduce_probation_by: 1u32.into(),
575                increase_probation_by: 1u32.into(),
576                rewards_buffer: 2u32.into(),
577                penalties_buffer: 4u32.into(),
578                max_elected: 100u32.into(),
579                min_elected: 10u32.into(),
580                force_max_elected: false,
581            }
582        }
583    }
584
585    #[pallet::genesis_build]
586    impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
587        fn build(&self) {
588            assert!(
589                !self.min_collateral.is_zero(),
590                "GenesisConfig error: min_collateral must be greater than zero"
591            );
592            assert!(
593                !self.min_fund.is_zero(),
594                "GenesisConfig error: min_fund must be greater than zero"
595            );
596            assert!(
597                !self.min_elected.is_zero(),
598                "GenesisConfig error: min_elected must be greater than zero"
599            );
600            assert!(
601                !self.max_elected.is_zero(),
602                "GenesisConfig error: max_elected must be greater than zero"
603            );
604            assert!(
605                self.min_elected <= self.max_elected,
606                "GenesisConfig error: min_elected cannot be greater than max_elected"
607            );
608            assert!(
609                self.min_fund <= self.max_exposure,
610                "GenesisConfig error: min_fund cannot be greater than max_exposure"
611            );
612            MinCollateral::<T>::put(&self.min_collateral);
613            MaxExposure::<T>::put(&self.max_exposure);
614            MinFund::<T>::put(&self.min_fund.max(One::one()));
615            ProbationPeriod::<T>::put(&self.probation_period);
616            ReduceProbationBy::<T>::put(&self.reduce_probation_by);
617            IncreaseProbationBy::<T>::put(&self.increase_probation_by);
618            RewardsBuffer::<T>::put(&self.rewards_buffer);
619            PenaltiesBuffer::<T>::put(&self.penalties_buffer);
620            RewardsUntil::<T>::put(BlockNumberFor::<T>::zero());
621            PenaltiesUntil::<T>::put(BlockNumberFor::<T>::zero());
622            MaxElected::<T>::put(&self.max_elected);
623            MinElected::<T>::put(&self.min_elected.max(One::one()));
624            ForceMaxElected::<T>::put(&self.force_max_elected);
625            RecentElectedOn::<T>::put(BlockNumberFor::<T>::zero());
626        }
627    }
628
629    // ===============================================================================
630    // ```````````````````````````````` STORAGE TYPES ````````````````````````````````
631    // ===============================================================================
632
633    /// Maps each [`Author`] account to its on-chain metadata ([`AuthorInfo`]).
634    ///
635    /// This storage is the **primary record** for all authors, tracking their:
636    /// - lifecycle status (Active, Probation, Resigned),
637    /// - risk period and timestamps,
638    /// - funding constraints (`min_fund`, `max_fund`).
639    ///
640    /// Keys are **insert-mutate-only** and **MUST NOT be removed**.
641    ///
642    /// Indexes and pools may retain digests indefinitely, and re-enrollment of
643    /// resigned authors may utilize their previous commitment-digest (existing meta).
644    ///
645    /// By keeping the resigned authors meta-data alive funders can draw from resigned
646    /// authors.
647    ///
648    /// In Future, if casual resignations are penalized via assigning new commitment-
649    /// digests, then this storage can be mutated again without reuse of digest, but removable
650    /// clearly requires withdrawal, and usage in indexes and pools must not create inconsistent
651    /// state.
652    ///
653    /// Used for all author-related operations.
654    #[pallet::storage]
655    pub type AuthorsMap<T: Config> =
656        StorageMap<_, Blake2_128Concat, Author<T>, AuthorInfo<T>, OptionQuery>;
657
658    /// Maps each [`AuthorDigest`] to its corresponding [`Author`] account.
659    ///
660    /// Provides **digest-to-account resolution**, allowing higher-level logic
661    /// to look up authors from commitment-based identifiers.
662    ///
663    /// Keys are **insert-only** and **MUST NOT be removed or mutated**.
664    ///
665    /// Indexes and pools may retain digests indefinitely, and authors must remain
666    /// discoverable for any referenced digest.
667    ///
668    /// Authors may resign and later re-enroll with the same digest. Hence stale
669    /// digests must remain valid to allow withdrawal of funds or soft-support commitments.
670    ///
671    /// Safe removal would require proving that no index or pool retains the digest,
672    /// which is effectively impossible to guarantee within the subsystem.
673    ///
674    /// In Future, regardless even if casual resignations are penalized via assigning
675    /// new commitment-digests, this doesn't need to be removed, unless the gurantees withhold,
676    /// which generally cannot.
677    ///
678    /// Useful for funding references, reward/penalty scheduling, and auditability.
679    #[pallet::storage]
680    pub type AuthorsDigest<T: Config> =
681        StorageMap<_, Blake2_128Concat, AuthorDigest<T>, Author<T>, OptionQuery>;
682
683    /// Stores the **funder details** for each ([`Author`], [`Backer`]) pair.
684    ///
685    /// Supports multi-source funding for authors:
686    /// - direct account backing
687    /// - index-based commitments
688    /// - pooled commitments
689    ///
690    /// Enables the runtime to query, update, or audit all funders for an author.
691    #[pallet::storage]
692    pub type AuthorFunders<T: Config> = StorageNMap<
693        _,
694        (
695            NMapKey<Blake2_128Concat, Author<T>>,
696            NMapKey<Blake2_128Concat, Backer<T>>,
697        ),
698        Funder<T>,
699        OptionQuery,
700    >;
701
702    /// Tracks the latest block number until which **author rewards** are scheduled.
703    ///
704    /// Enables **efficient scanning** for pending rewards by limiting iteration
705    /// to blocks up to `RewardsUntil`.
706    ///
707    /// Must not be updated by governance manually, as its a runtime inferred value
708    /// and not a genesis configurable value.
709    #[pallet::storage]
710    pub type RewardsUntil<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
711
712    /// Tracks the latest block number until which **author penalties** are scheduled.
713    ///
714    /// Ensures efficient access to pending penalties without scanning irrelevant blocks.
715    ///
716    /// Must not be updated by governance manually, as its a runtime inferred value
717    /// and not a genesis configurable value.
718    #[pallet::storage]
719    pub type PenaltiesUntil<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
720
721    /// Buffer period in blocks before a reward can be applied.
722    ///
723    /// Used to **defer rewards** and ensure orderly enforcement, preventing immediate manipulation.
724    #[pallet::storage]
725    pub type RewardsBuffer<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
726
727    /// Buffer period in blocks before a penalty can be applied.
728    ///
729    /// Provides temporal separation for enforcement and ensures deterministic scheduling of penalties.
730    #[pallet::storage]
731    pub type PenaltiesBuffer<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
732
733    /// Stores **pending rewards** for authors by ([`BlockNumberFor`], [`Author`]) key.
734    ///
735    /// - Enables deferred reward application at the correct block.
736    /// - Supports retrieval of total reward for an author or for a specific timestamp.
737    /// - Value type is [`AuthorAsset`], representing the reward amount.
738    #[pallet::storage]
739    pub type AuthorRewards<T: Config> = StorageNMap<
740        _,
741        (
742            NMapKey<Blake2_128Concat, BlockNumberFor<T>>,
743            NMapKey<Blake2_128Concat, Author<T>>,
744        ),
745        AuthorAsset<T>,
746        OptionQuery,
747    >;
748
749    /// Stores **pending penalties** for authors by ([`BlockNumberFor`], [`Author`]) key.
750    ///
751    /// - Allows deferred penalty enforcement at the correct block.
752    /// - Supports retrieval of total penalty-percentage applied on an author's
753    /// risk-profile for a specific timestamp.
754    /// - Value type is [`Ratio`], representing the penalty factor applied on the
755    /// author's total hold.
756    #[pallet::storage]
757    pub type AuthorPenalties<T: Config> = StorageNMap<
758        _,
759        (
760            NMapKey<Blake2_128Concat, BlockNumberFor<T>>,
761            NMapKey<Blake2_128Concat, Author<T>>,
762        ),
763        Ratio<T>,
764        OptionQuery,
765    >;
766
767    /// Global Minimum collateral required for any author.
768    ///
769    /// Must not be set to zero, else collateral query functions will fail, as there
770    /// will be no actual commitment.
771    ///
772    /// Ensures authors maintain sufficient **stake or backing** for network security.
773    #[pallet::storage]
774    pub type MinCollateral<T: Config> = StorageValue<_, AuthorAsset<T>, ValueQuery>;
775
776    /// Global Maximum exposure allowed globally per funding operation.
777    ///
778    /// Prevents a single funding-instance from **over-concentrating funding** or
779    /// creating systemic risk.
780    ///
781    /// This is ambiguous in `pool` and `index` contexts as it only represents how
782    /// much the backer is willing to commit at a transaction, not specific to what scheme.
783    #[pallet::storage]
784    pub type MaxExposure<T: Config> = StorageValue<_, AuthorAsset<T>, ValueQuery>;
785
786    /// Global Minimum funding required globally per funding operation.
787    ///
788    /// Ensures authors meet **base economic requirements** for participation.
789    ///
790    /// This is ambiguous in `pool` and `index` contexts as it only represents how
791    /// much the backer is willing to commit at a transaction, not specific to what scheme.
792    #[pallet::storage]
793    pub type MinFund<T: Config> = StorageValue<_, AuthorAsset<T>, ValueQuery>;
794
795    /// Number of blocks representing the **probation period** for newly enrolled or demoted authors.
796    ///
797    /// This period enforces a **mandatory observation window** during which an author:
798    /// - Cannot be promoted to permanent/active status.
799    /// - Must demonstrate acceptable behavior to secure their role.
800    ///
801    /// Additionally, if an author is deemed unsafe until a timestamp overlapping this period,
802    /// they are **required to remain or be moved back to probation**, ensuring the network
803    /// only promotes trusted participants.
804    #[pallet::storage]
805    pub type ProbationPeriod<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
806
807    /// Number of blocks by which an author's risk period is **reduced** upon good behavior.
808    ///
809    /// Supports gradual restoration of permanence or reduced probation impact.
810    #[pallet::storage]
811    pub type ReduceProbationBy<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
812
813    /// Number of blocks by which an author's risk period is **increased** upon misbehavior.
814    ///
815    /// Enables dynamic punishment while maintaining predictable enforcement windows.
816    #[pallet::storage]
817    pub type IncreaseProbationBy<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
818
819    /// Persistent record of **elected authors per block**, representing the outcome
820    /// of each finalized election round.
821    ///
822    /// This is a two-dimensional mapping:
823    /// - The block number in which the election was conducted.
824    /// - The author (candidate) elected in that block.
825    /// - Unit value (only existence matters; no extra metadata stored).
826    ///
827    /// Because this is a `StorageNMap`, callers can **query or iterate over all
828    /// authors elected in a given block** by iterating over the entries that share
829    /// the same block number key.
830    ///
831    /// This design allows efficient historical lookups and auditability
832    /// over election rounds without overwriting previous results.
833    ///
834    /// ### Notes
835    /// - This storage is **append-only** - each election round adds entries under
836    ///   a new block key.
837    /// - Multiple authors may be associated with the same block if the election
838    ///   yields more than one winner.
839    /// - Overwriting does not occur; historical elections remain queryable
840    ///   indefinitely.
841    #[pallet::storage]
842    pub type Elected<T: Config> = StorageNMap<
843        _,
844        (
845            NMapKey<Blake2_128Concat, BlockNumberFor<T>>,
846            NMapKey<Blake2_128Concat, Author<T>>,
847        ),
848        (),
849        OptionQuery,
850    >;
851
852    /// Tracks the **most recent block number** in which an election was conducted
853    /// and successfully stored in [`Elected`].
854    ///
855    /// Acts as a quick reference for identifying the **latest election round**
856    /// without scanning all historical data.
857    ///
858    /// ### Usage
859    /// - Updated every time a new election result is finalized and stored.
860    /// - Useful for logic that depends on the recency or periodicity of elections.
861    ///
862    /// ### Notes
863    /// - Always holds a valid block number (`ValueQuery`).
864    /// - Can be used to compute time intervals between election rounds.
865    #[pallet::storage]
866    pub type RecentElectedOn<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
867
868    /// The **upper bound** on the number of authors that can be elected in a single election round.
869    ///
870    /// ## Behavior
871    /// - Acts as a *hard cap* on election size.
872    /// - Used by both [`Config::FlatElectionModel`] and [`Config::FairElectionModel`] to truncate or validate
873    ///   the elected authors list.
874    /// - When [`ForceMaxElected`] is set to `true`, any excess elected candidates beyond this value
875    ///   are **automatically truncated** before being persisted.
876    ///
877    /// ## Example
878    /// If `MaxElected = 50`, the election will never store more than 50 authors,
879    /// even if the algorithm produces 80 winners.
880    #[pallet::storage]
881    pub type MaxElected<T: Config> = StorageValue<_, u32, ValueQuery>;
882
883    /// The **minimum required number of authors** that must be elected for an election
884    /// to be considered valid.
885    ///
886    /// ## Behavior
887    /// - Prevents premature elections with insufficient candidates.
888    /// - If the elected count falls below this threshold,  
889    ///   the election process fails with an error.
890    ///
891    /// ## Example
892    /// If `MinElected = 10` but only 6 valid authors are produced,  
893    /// the election aborts rather than storing incomplete results.
894    #[pallet::storage]
895    pub type MinElected<T: Config> = StorageValue<_, u32, ValueQuery>;
896
897    /// A **strictness flag** controlling how the system enforces the [`MaxElected`] limit.
898    ///
899    /// ## Behavior
900    /// - When `true`:  
901    ///   The system **forcefully truncates** the elected list to `MaxElected`
902    ///   before persisting.
903    /// - When `false`:  
904    ///   The system performs a **safe bounded check**; it **fails** (returns error) if
905    ///   any bounds are enforced by the underlying storage instead of truncating
906    ///   automatically i.e., a [`WeakBoundedVec`].
907    ///
908    /// ## Purpose
909    /// This provides governance-level control over whether elections prioritize
910    /// **strict deterministic enforcement (force)** or **safe bounded validation (fair)**.
911    ///
912    /// ## Example
913    /// - `ForceMaxElected = true` -> Automatically keeps top N candidates.  
914    /// - `ForceMaxElected = false` -> Requires manual handling of overflows before storage.
915    #[pallet::storage]
916    pub type ForceMaxElected<T: Config> = StorageValue<_, bool, ValueQuery>;
917
918    // ===============================================================================
919    // ```````````````````````````````````` ERROR ````````````````````````````````````
920    // ===============================================================================
921
922    #[pallet::error]
923    pub enum Error<T> {
924        /// The specified author is not found in the system.
925        AuthorNotFound,
926
927        /// Lesser than minimum collateral provided for author role enrollment.
928        InadequateCollateral,
929
930        /// Lesser than minimum requirement funds provided for enroll/funding operation.
931        InadequateFunds,
932
933        /// Redundant enrollment for an already existing author.
934        AlreadyEnrolled,
935
936        /// Author is actively participating in their assigned/enrolled duties.
937        ///
938        /// Either internally or externally by other pallets.
939        AuthorIsActive,
940
941        /// The specified author is undergoing probation period.
942        AuthorInProbation,
943
944        /// Author has resigned and must undergo a new enrollment to rejoin.
945        AuthorResigned,
946
947        /// Author has pending penalties that must finalize before the operation can proceed.
948        AuthorHasPenalties,
949
950        /// Once an author is active after probation, cannot undergo probation again.
951        AuthorActivated,
952
953        /// Digest cannot be generated for author for external funding (commitment).
954        CannotGenerateCommitDigest,
955
956        /// Author has pending rewards that must finalize before the operation can proceed.
957        AuthorHasRewards,
958
959        /// The requested external backing/fund does not exist.
960        FundDoesNotExist,
961
962        /// The author's funding digest/hash is not found in the system.
963        AuthorDigestNotFound,
964
965        /// The backer funded another author's digest, not the specified author.
966        ///
967        /// May correspond to an index or pool digest.
968        FundedToAnotherDigest,
969
970        /// Author's funding digest is not available in the index entries.
971        AuthorNotInIndex,
972
973        /// Author's funding digest is not available in the pool's slots.
974        AuthorNotInPool,
975
976        /// Minimum fund not attained, either locally (author-defined) or globally.
977        BelowMinimumFund,
978
979        /// Funds exceed maximum exposable amount, either locally (author-defined) or globally.
980        AboveMaximumExposure,
981
982        /// Author is attempting to fund on top of collateral, which is not allowed.
983        CannotFundOnCollateral,
984
985        /// The requested reward for author is not found in pending rewards.
986        RewardNotFound,
987
988        /// The requested penalty for author is not found in pending penalties.
989        PenaltyNotFound,
990
991        /// The total hold of an author, including all funds and collateral, overflowed.
992        ///
993        /// If the commitment asset is non-issuance based, update the scalar type to maximum precision.
994        /// If issuance-based, an internal audit into the asset is expected.
995        AuthorTotalHoldExhausted,
996
997        /// The requested timestamp doesn't contain any pending rewards.
998        ContainsNoRewards,
999
1000        /// The requested timestamp doesn't contain any pending penalties.
1001        ContainsNoPenalties,
1002
1003        /// The requested timestamp has finalized all obligations, such as pending rewards or penalties.
1004        FinalizedObligations,
1005
1006        /// Author is under-collateralized due to increased system requirements to continue operations.
1007        AuthorNeedsMoreCollateral,
1008
1009        /// The given penalty factor is zero and cannot be applied for penalization of authors.
1010        ZeroPenaltyFound,
1011
1012        /// The author is deemed risky (unsafe); operation cannot proceed.
1013        ///
1014        /// Not a permanent condition: can be resolved with positive activities.
1015        AuthorIsUnsafe,
1016
1017        /// Already resigned author attempting resignation again.
1018        RedundantResignation,
1019
1020        /// The provided candidate set was **too small** to begin an election.
1021        ///
1022        /// This occurs when the number of candidates passed to the election process
1023        /// is less than the configured minimum.
1024        InadequateCandidatesToElect,
1025
1026        /// No elected authors were found in storage or the election produced zero winners.
1027        ///
1028        /// This may occur in two cases:
1029        /// - When attempting to **reveal** or **access** election results but no election
1030        ///   data has been recorded for the current or recent round.
1031        /// - When an election process completed but resulted in **zero elected candidates**,
1032        ///   indicating either a lack of eligible participants or a model configuration issue.
1033        NoElectsFound,
1034
1035        /// The given author is not elected in the recent election.
1036        AuthorNotElected,
1037
1038        /// The number of elected authors **did not reach the configured minimum**.
1039        ///
1040        /// Triggered during the election storage phase when the computed
1041        /// results contain fewer authors than required.
1042        MinElectedNotReached,
1043
1044        /// The author's accumulated risk has **not crossed the revocation limit**.
1045        ///
1046        /// This error is returned when an operation requires the author to have
1047        /// exceeded the allowed risk window, but the author's `risk_until`
1048        /// is still less than or equal to:
1049        ///
1050        /// `current_block + ProbationPeriod`.
1051        ///
1052        /// In this state, the author remains valid and **cannot yet be revoked**.
1053        RiskWithinThreshold,
1054
1055        /// The provided author does not match the expected author for the operation.
1056        ///
1057        /// This error is returned when an operation references an author that is
1058        /// inconsistent with the stored author context or resolved digest.
1059        AuthorMismatch,
1060
1061        /// The caller is not the current manager of the specified funding pool.
1062        InvalidPoolManager,
1063
1064        /// The minimum collateral value cannot be zero.
1065        ///
1066        /// This error is returned when attempting to set the minimum required
1067        /// collateral to `0`. The minimum collateral must be greater than zero.
1068        MinCollateralZero,
1069
1070        /// A minimum configuration value exceeds its corresponding maximum value.
1071        ///
1072        /// This error is returned when a parameter that represents a lower bound
1073        /// (e.g., `MinElected`, `MinFund`) is set greater than its associated
1074        /// upper bound (e.g., `MaxElected`, `MaxExposure`).
1075        ///
1076        /// Ensures logical consistency between related configuration limits.
1077        MinGreaterThanMax,
1078
1079        /// A configuration parameter that must be strictly greater than zero
1080        /// was provided as zero.
1081        ///
1082        /// This error is returned when attempting to set a non-zero-required
1083        /// global parameter (such as `MinCollateral`, `MinFund`,
1084        /// `MinElected`, or `MaxElected`) to `0`.
1085        ///
1086        /// Prevents invalid economic or election configuration.
1087        NonZeroConfigRequired,
1088
1089        /// Initiating a fund (backing) has exceeded allowed funding limits either
1090        /// globally or locally by the receiving model (author/index/pool) or
1091        /// low-level balances.
1092        FundingOffLimits,
1093    }
1094
1095    // ===============================================================================
1096    // ```````````````````````````````````` EVENTS ```````````````````````````````````
1097    // ===============================================================================
1098
1099    #[pallet::event]
1100    #[pallet::generate_deposit(pub(super) fn deposit_event)]
1101    pub enum Event<T: Config> {
1102        /// Emitted when an account successfully enrolls, by enlisting itself as
1103        /// an author by locking the required collateral for the role.
1104        AuthorEnlisted {
1105            author: Author<T>,
1106            collateral: AuthorAsset<T>,
1107        },
1108
1109        /// Emitted when an author voluntarily resigns from the role,
1110        /// regains their collateral and exits active participation.
1111        AuthorResigned {
1112            author: Author<T>,
1113            released: AuthorAsset<T>,
1114        },
1115
1116        /// Emitted when an author's total collateral is incremented i.e.,
1117        /// additional collateral being added.
1118        AuthorCollateralRaised {
1119            author: Author<T>,
1120            raised: AuthorAsset<T>,
1121        },
1122
1123        /// Emitted when the total collateral of an author is queried.
1124        AuthorTotalCollateral {
1125            author: Author<T>,
1126            collateral: AuthorAsset<T>,
1127        },
1128
1129        /// Emitted when a backer funds to an author via any funding mechanism
1130        /// such as direct, indexed and pooled.
1131        AuthorFunded {
1132            author: Author<T>,
1133            backer: Backer<T>,
1134            amount: AuthorAsset<T>,
1135        },
1136
1137        /// Emitted when a backer queries their fund that is commited to
1138        /// a author directly.
1139        InspectAuthorFund {
1140            author: Author<T>,
1141            backer: Backer<T>,
1142            amount: AuthorAsset<T>,
1143        },
1144
1145        /// Emitted when a backer queries their fund that is commited to an
1146        /// author through Direct, Index or Pool based funding mechanism.
1147        InspectFund {
1148            author: Author<T>,
1149            funder: Funder<T>,
1150            amount: AuthorAsset<T>,
1151        },
1152
1153        /// Emitted when a backer funds authors through an index-based
1154        /// funding mechanism.
1155        IndexFunded {
1156            index: IndexDigest<T>,
1157            backer: Backer<T>,
1158            amount: AuthorAsset<T>,
1159        },
1160
1161        /// Emitted when a backer queries their fund that is commited to an
1162        /// index-based funding mechanism.
1163        InspectIndexFund {
1164            index: IndexDigest<T>,
1165            backer: Backer<T>,
1166            amount: AuthorAsset<T>,
1167        },
1168
1169        /// Emitted when a backer funds authors through an pool-based
1170        /// funding mechanism.
1171        PoolFunded {
1172            pool: PoolDigest<T>,
1173            backer: Backer<T>,
1174            amount: AuthorAsset<T>,
1175        },
1176
1177        /// Emitted when a backer queries their fund that is commited to an
1178        /// pool-based funding mechanism.
1179        InspectPoolFund {
1180            pool: PoolDigest<T>,
1181            backer: Backer<T>,
1182            amount: AuthorAsset<T>,
1183        },
1184
1185        /// Emitted when previously committed funds are successfully released
1186        /// from an author back to the backer.
1187        AuthorDrawn {
1188            author: Author<T>,
1189            backer: Backer<T>,
1190            amount: AuthorAsset<T>,
1191        },
1192
1193        /// Emitted when previously committed funds are successfully released
1194        /// from an index back to the backer.
1195        IndexDrawn {
1196            index: IndexDigest<T>,
1197            backer: Backer<T>,
1198            amount: AuthorAsset<T>,
1199        },
1200
1201        /// Emitted when previously committed funds are successfully released
1202        /// from an pool back to the backer.
1203        PoolDrawn {
1204            pool: PoolDigest<T>,
1205            backer: Backer<T>,
1206            amount: AuthorAsset<T>,
1207        },
1208
1209        /// Emitted when scheduled rewards are queried for an author, including one or more
1210        /// future block numbers at which the rewards will be applied.
1211        ScheduledRewards {
1212            author: Author<T>,
1213            rewards: Vec<(BlockNumberFor<T>, AuthorAsset<T>)>,
1214        },
1215
1216        /// Emitted when scheduled penalties are queried for an author, including one or more
1217        /// future block numbers at which the penalties will be enforced.
1218        ScheduledPenalties {
1219            author: Author<T>,
1220            penalties: Vec<(BlockNumberFor<T>, Ratio<T>)>,
1221        },
1222
1223        /// Emitted when a reward is applied or scheduled to an author at a specific block.
1224        AuthorRewardScheduled {
1225            author: Author<T>,
1226            amount: AuthorAsset<T>,
1227            at: BlockNumberFor<T>,
1228        },
1229
1230        /// Emitted when a penalty is applied or scheduled to an author at a specific block.
1231        AuthorPenaltyScheduled {
1232            author: Author<T>,
1233            factor: Ratio<T>,
1234            at: BlockNumberFor<T>,
1235        },
1236
1237        /// Emitted when an author's lifecycle status changes, including transitions
1238        /// between probation, active, or other defined states.
1239        AuthorStatus {
1240            author: Author<T>,
1241            status: AuthorStatus,
1242        },
1243
1244        /// Emitted when an author's status is at risk until a specified block,
1245        /// due to negative behavior, affecting either permanence or probation state.
1246        AuthorAtRisk {
1247            author: Author<T>,
1248            status: AuthorStatus,
1249            until: BlockNumberFor<T>,
1250        },
1251
1252        /// Emitted when a previously scheduled penalty for an author
1253        /// is forgiven.
1254        AuthorPenaltyForgiven { author: Author<T>, factor: Ratio<T> },
1255
1256        /// Emitted when a previously scheduled reward for an author
1257        /// is reclaimed.
1258        AuthorRewardReclaimed {
1259            author: Author<T>,
1260            amount: AuthorAsset<T>,
1261        },
1262
1263        /// Emitted when the held (locked) balance of an author is updated or queried.
1264        AuthorTotalHold {
1265            author: Author<T>,
1266            value: AuthorAsset<T>,
1267        },
1268
1269        /// Emitted when an election preparation completes successfully and elected
1270        /// authors are stored for runtime-usage.
1271        ElectionPrepared { elects: Vec<Author<T>> },
1272
1273        /// Emitted when an election preparation fails to complete successfully.
1274        ElectionFailed { error: DispatchError },
1275
1276        /// Emitted when a new funding index is created and registered.
1277        IndexCreated {
1278            index: IndexDigest<T>,
1279            #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
1280            entries: Vec<(IndexDigest<T>, Shares<T>)>,
1281        },
1282
1283        /// Emitted when a new funding pool is created with a specified commission
1284        /// and an assigned pool manager.
1285        PoolCreated {
1286            pool: PoolDigest<T>,
1287            commission: Commission<T>,
1288            manager: T::AccountId,
1289            #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
1290            slots: Vec<(PoolDigest<T>, Shares<T>)>,
1291        },
1292
1293        /// Emitted when management ownership of a funding pool is transferred.
1294        PoolManager {
1295            digest: PoolDigest<T>,
1296            manager: T::AccountId,
1297        },
1298
1299        /// Emitted when a slot share weight within a pool are updated.
1300        PoolSlotShare {
1301            pool: PoolDigest<T>,
1302            slots: (PoolDigest<T>, Shares<T>),
1303        },
1304
1305        /// A genesis config parameter was updated forcefully.
1306        GenesisConfigUpdated(ForceGenesisConfig<T>),
1307    }
1308
1309    // ===============================================================================
1310    // ```````````````````````````````````` HOOKS ````````````````````````````````````
1311    // ===============================================================================
1312
1313    #[pallet::hooks]
1314    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
1315        /// Apply scheduled author rewards and penalties at the **start of each block**.
1316        ///
1317        /// - Rewards and penalties must be processed **before any new extrinsics or elections** in the current block
1318        ///   to ensure that subsequent computations are accurate.
1319        /// - By processing at the start, we **disallow querying or relying on the current block's rewards/penalties**
1320        ///   until the next block, ensuring deterministic behavior and avoiding double-counting or race conditions.
1321        /// - Rewards/penalties are applied **once per block at the beginning**.
1322        /// - This ensures deterministic, predictable state transitions, and makes querying the pallet
1323        ///   consistent (no author sees their reward/penalty applied mid-block).
1324        fn on_initialize(block: BlockNumberFor<T>) -> Weight {
1325            // Process all scheduled author rewards for this block:
1326            let reward_iter: Vec<_> = AuthorRewards::<T>::iter_prefix((block,)).collect();
1327            let reward_count = reward_iter.len() as u32;
1328            // Fetch each reward, add it to the author's hold, and trigger `on_set_hold`.
1329            for (author, reward) in reward_iter {
1330                if let Ok(hold) = Self::get_hold(&author) {
1331                    let value = hold.saturating_add(reward);
1332                    if Self::set_hold(&author, value, Precision::BestEffort, Fortitude::Polite)
1333                        .is_ok()
1334                    {
1335                        Self::on_set_hold(&author, value);
1336                    }
1337                }
1338                // Remove the applied reward to prevent double processing.
1339                AuthorRewards::<T>::remove((block, author));
1340            }
1341
1342            // Process all scheduled author penalties for this block:
1343            // Remove the applied penalty to maintain idempotency.
1344            let penalty_iter: Vec<_> = AuthorPenalties::<T>::iter_prefix((block,)).collect();
1345            let penalty_count = penalty_iter.len() as u32;
1346            for (author, penalty_percent) in penalty_iter {
1347                if let Ok(hold) = Self::get_hold(&author) {
1348                    // Calculate penalty as a fraction of the current hold, apply it, and trigger `on_set_hold`.
1349                    let penalty = penalty_percent.mul_floor(hold);
1350                    let value = hold.saturating_sub(penalty);
1351                    if Self::set_hold(&author, value, Precision::Exact, Fortitude::Force).is_ok() {
1352                        Self::on_set_hold(&author, value);
1353                    }
1354                }
1355                // Remove the applied penalty to maintain idempotency.
1356                AuthorPenalties::<T>::remove((block, author));
1357            }
1358
1359            T::WeightInfo::on_initialize_rewards_penalties(reward_count, penalty_count)
1360        }
1361    }
1362
1363    // ===============================================================================
1364    // ````````````````````````````````` EXTRINSICS ``````````````````````````````````
1365    // ===============================================================================
1366
1367    #[pallet::call]
1368    impl<T: Config> Pallet<T> {
1369        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1370        // ```````````````````````````````` DISPATCHABLES ````````````````````````````````
1371        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1372
1373        /// Enlist the caller as an **author** by locking the required collateral.
1374        ///
1375        /// Establishes the caller as an author by placing collateral under the
1376        /// author role commitment.
1377        ///
1378        /// ## Requirements
1379        /// - Provided collateral must be at least [`MinCollateral`].
1380        /// - Fails if the caller is already enrolled as an author.
1381        ///
1382        /// ## Behavior
1383        /// - Locks the required collateral and associates it with the author role.
1384        /// - Ensures at least the minimum collateral is maintained.
1385        ///
1386        /// ## Execution Controls
1387        /// - `fortitude` defines how the collateral is sourced:
1388        ///   - [`FortitudeWrapper::Force`]: Uses the caller's **liquid balance** to place
1389        ///     the collateral.
1390        ///   - [`FortitudeWrapper::Polite`]: Uses funds already deposited into the
1391        ///     commitment reserve (if [`Config::CommitmentAdapter`] == `pallet_commitment`).
1392        ///
1393        /// **Emits:** [`Event::AuthorEnlisted`]
1394        #[pallet::call_index(0)]
1395        #[pallet::weight(T::WeightInfo::enlist())]
1396        pub fn enlist(
1397            origin: OriginFor<T>,
1398            collateral: AuthorAsset<T>,
1399            fortitude: FortitudeWrapper,
1400        ) -> DispatchResult {
1401            let caller = ensure_signed(origin)?;
1402            ensure!(
1403                collateral >= MinCollateral::<T>::get(),
1404                Error::<T>::InadequateCollateral
1405            );
1406
1407            let fortitude = fortitude.into();
1408            let collateral =
1409                <Pallet<T> as RoleManager<Author<T>>>::enroll(&caller, collateral, fortitude)?;
1410            if !T::EmitEvents::get() {
1411                Self::deposit_event(Event::<T>::AuthorEnlisted {
1412                    author: caller,
1413                    collateral,
1414                });
1415            }
1416            Ok(())
1417        }
1418
1419        /// Provide **economic backing** support to an author using a supported funding model.
1420        ///
1421        /// This extrinsic allows the caller to economically support an author by
1422        /// locking assets under the [`FreezeReason::AuthorFunding`] commitment domain.
1423        /// Funding directly affects an author's eligibility, exposure, and election weight.
1424        ///
1425        /// ## Funding Models
1426        /// - **Direct:** Funds are committed explicitly to a single author.
1427        /// - **Index:** Route funds through an index resolving to multiple author digests.
1428        /// - **Pool:** Commit funds to a managed pool of authors with commission-based withdrawals.
1429        ///
1430        /// ## Guarantees
1431        /// - Funds are **fully locked** and cannot be double-used elsewhere.
1432        /// - Author collateral is **never mixed** with external funding.
1433        /// - [`MinFund`] and [`MaxExposure`] are strictly enforced.
1434        /// - Local minimum and maximum limits enforced by authors and underlying
1435        ///   systems are considered.
1436        /// - Funding is rejected if the resolved author or digest is invalid.
1437        ///
1438        /// ## Execution Controls
1439        /// - `precision` defines how strictly the requested amount must be satisfied:
1440        ///   - [`Precision::Exact`]: Requires full amount commitment.
1441        ///   - [`Precision::BestEffort`]: Allows partial fulfillment where supported.
1442        /// - `force` defines how funds are sourced:
1443        ///   - [`Fortitude::Force`]: Uses the caller's **liquid balance**, enforcing
1444        ///     the commitment directly.
1445        ///   - [`Fortitude::Polite`]: Uses funds already deposited in the commitment
1446        ///     reserve (if [`Config::CommitmentAdapter`] == `pallet_commitment`).
1447        ///
1448        /// **Emits** via internal hook:
1449        ///     - [`Event::AuthorFunded`] if direct author backing
1450        ///     - [`Event::IndexFunded`] multiple authors via an index
1451        ///     - [`Event::PoolFunded`] multiple authors via a managed pool
1452        #[pallet::call_index(1)]
1453        #[pallet::weight(
1454        T::WeightInfo::direct_fund()
1455            .max(T::WeightInfo::index_fund())
1456            .max(T::WeightInfo::pool_fund())
1457        )]
1458        pub fn back(
1459            origin: OriginFor<T>,
1460            via: FundingTarget<T>,
1461            value: AuthorAsset<T>,
1462            fortitude: FortitudeWrapper,
1463            precision: PrecisionWrapper,
1464        ) -> DispatchResult {
1465            let caller = ensure_signed(origin)?;
1466
1467            let (to, funder) = match via {
1468                FundingTarget::Direct(author) => {
1469                    let funder = Funder::Direct(caller);
1470                    (author, funder)
1471                }
1472                FundingTarget::Index(index_digest) => {
1473                    let reason = &FreezeReason::AuthorFunding.into();
1474                    let index_entry_shares =
1475                        T::CommitmentAdapter::get_entries_shares(reason, &index_digest)?;
1476                    let entry_digest = &index_entry_shares[0].0;
1477                    // A neccessary value required which is the runtime can get instead of asking
1478                    // the caller
1479                    let to = AuthorsDigest::<T>::get(&entry_digest)
1480                        .ok_or(Error::<T>::AuthorDigestNotFound)?;
1481                    let index_funder = Funder::Index {
1482                        digest: index_digest,
1483                        backer: caller,
1484                    };
1485                    (to, index_funder)
1486                }
1487                FundingTarget::Pool(pool_digest) => {
1488                    let reason = &FreezeReason::AuthorFunding.into();
1489                    let pool_entry_shares =
1490                        T::CommitmentAdapter::get_slots_shares(reason, &pool_digest)?;
1491                    let slot_digest = &pool_entry_shares[0].0;
1492                    let to = AuthorsDigest::<T>::get(&slot_digest)
1493                        .ok_or(Error::<T>::AuthorDigestNotFound)?;
1494                    let pool_funder = Funder::Pool {
1495                        digest: pool_digest,
1496                        backer: caller,
1497                    };
1498                    (to, pool_funder)
1499                }
1500            };
1501            let precision: Precision = precision.into();
1502            let fortitude: Fortitude = fortitude.into();
1503            let actual = <Pallet<T> as FundRoles<Author<T>>>::fund(
1504                &to, &funder, value, precision, fortitude,
1505            )?;
1506            if !T::EmitEvents::get() {
1507                match funder {
1508                    Funder::Direct(backer) => {
1509                        Self::deposit_event(Event::<T>::AuthorFunded {
1510                            author: to,
1511                            backer,
1512                            amount: actual,
1513                        });
1514                    }
1515                    Funder::Index { digest, backer } => {
1516                        Self::deposit_event(Event::<T>::IndexFunded {
1517                            index: digest,
1518                            backer,
1519                            amount: actual,
1520                        });
1521                    }
1522                    Funder::Pool { digest, backer } => {
1523                        Self::deposit_event(Event::<T>::PoolFunded {
1524                            pool: digest,
1525                            backer,
1526                            amount: actual,
1527                        });
1528                    }
1529                }
1530            }
1531            Ok(())
1532        }
1533
1534        /// Increase the caller's **collateral** by locking additional assets.
1535        ///
1536        /// Adds to the existing collateral, strengthening the author's position
1537        /// and ensuring compliance with evolving system requirements such
1538        /// as [`MinCollateral`].
1539        ///
1540        /// ## Behavior
1541        /// - Collateral is added on top of existing locked collateral.
1542        ///
1543        /// ## Execution Controls
1544        /// - `fortitude` defines how the collateral is sourced:
1545        ///   - [`FortitudeWrapper::Force`]: Uses the caller's **liquid balance** to place
1546        ///     the additional collateral.
1547        ///   - [`FortitudeWrapper::Polite`]: Uses funds already deposited into the
1548        ///     commitment reserve (if [`Config::CommitmentAdapter`] == `pallet_commitment`).
1549        ///
1550        /// **Emits:** [`Event::AuthorCollateralRaised`]
1551        #[pallet::call_index(2)]
1552        #[pallet::weight(T::WeightInfo::refill())]
1553        pub fn refill(
1554            origin: OriginFor<T>,
1555            collateral: AuthorAsset<T>,
1556            fortitude: FortitudeWrapper,
1557        ) -> DispatchResult {
1558            let caller = ensure_signed(origin)?;
1559            let fortitude = fortitude.into();
1560            let raised = <Pallet<T> as RoleManager<Author<T>>>::add_collateral(
1561                &caller, collateral, fortitude,
1562            )?;
1563            if !T::EmitEvents::get() {
1564                Self::deposit_event(Event::<T>::AuthorCollateralRaised {
1565                    author: caller,
1566                    raised,
1567                });
1568            }
1569            Ok(())
1570        }
1571
1572        /// Confirm the caller as an **active author** after completing probation.
1573        ///
1574        /// Transitions the author from probation to active status once all
1575        /// probation conditions are satisfied.
1576        ///
1577        /// ## Requirements
1578        /// - Caller must be under probation.
1579        /// - Probation conditions must be fulfilled.
1580        ///
1581        /// ## Notes
1582        /// - Authors are responsible for completing their probation requirements.
1583        /// - Activation enables full participation in author duties.
1584        ///
1585        /// **Emits:** [`Event::AuthorStatus`] via internal hook
1586        #[pallet::call_index(3)]
1587        #[pallet::weight(T::WeightInfo::confirm())]
1588        pub fn confirm(origin: OriginFor<T>) -> DispatchResult {
1589            let caller = ensure_signed(origin)?;
1590            <Pallet<T> as RoleManager<Author<T>>>::set_status(&caller, AuthorStatus::Active)?;
1591            if !T::EmitEvents::get() {
1592                Self::deposit_event(Event::AuthorStatus {
1593                    author: caller,
1594                    status: AuthorStatus::Active,
1595                });
1596            }
1597            Ok(())
1598        }
1599
1600        /// Exit an existing **backing position** towards an author.
1601        ///
1602        /// Releases the caller's committed funds (including any applicable rewards
1603        /// or penalties) from a direct, index, or pool-based backing, once the
1604        /// position is eligible for exit.
1605        ///
1606        /// ## Behavior
1607        /// - Exits only the caller's backing position.
1608        /// - Author collateral remains **unaffected**.
1609        /// - Other backers' commitments remain intact.
1610        ///
1611        /// ## Validation
1612        /// - Ensures the backing position exists and is withdrawable.
1613        /// - Resolves the target author from index or pool digests.
1614        ///
1615        /// **Emits:**
1616        /// - [`Event::AuthorDrawn`] for direct backing
1617        /// - [`Event::IndexDrawn`] for index-based backing
1618        /// - [`Event::PoolDrawn`] for pool-based backing
1619        #[pallet::call_index(4)]
1620        #[pallet::weight(
1621        T::WeightInfo::release_direct_fund()
1622            .max(T::WeightInfo::release_index_fund())
1623            .max(T::WeightInfo::release_pool_fund())
1624        )]
1625        pub fn exit(origin: OriginFor<T>, from: FundingTarget<T>) -> DispatchResult {
1626            let caller = ensure_signed(origin)?;
1627            let (from, funder) = match from {
1628                FundingTarget::Direct(author) => {
1629                    let funder = Funder::Direct(caller);
1630                    (author, funder)
1631                }
1632                FundingTarget::Index(index_digest) => {
1633                    let reason = &FreezeReason::AuthorFunding.into();
1634                    let index_entry_shares =
1635                        T::CommitmentAdapter::get_entries_shares(reason, &index_digest)?;
1636                    let entry_digest = &index_entry_shares[0].0;
1637                    let from = AuthorsDigest::<T>::get(&entry_digest)
1638                        .ok_or(Error::<T>::AuthorDigestNotFound)?;
1639                    let index_funder = Funder::Index {
1640                        digest: index_digest,
1641                        backer: caller,
1642                    };
1643                    (from, index_funder)
1644                }
1645                FundingTarget::Pool(pool_digest) => {
1646                    let reason = &FreezeReason::AuthorFunding.into();
1647                    let pool_entry_shares =
1648                        T::CommitmentAdapter::get_slots_shares(reason, &pool_digest)?;
1649                    let slot_digest = &pool_entry_shares[0].0;
1650                    let from = AuthorsDigest::<T>::get(&slot_digest)
1651                        .ok_or(Error::<T>::AuthorDigestNotFound)?;
1652                    let pool_funder = Funder::Pool {
1653                        digest: pool_digest,
1654                        backer: caller,
1655                    };
1656                    (from, pool_funder)
1657                }
1658            };
1659            let draw_value = <Pallet<T> as FundRoles<Author<T>>>::draw(&from, &funder)?;
1660            if !T::EmitEvents::get() {
1661                match funder {
1662                    Funder::Direct(backer) => {
1663                        Self::deposit_event(Event::<T>::AuthorDrawn {
1664                            author: from,
1665                            backer,
1666                            amount: draw_value,
1667                        });
1668                    }
1669                    Funder::Index { digest, backer } => {
1670                        Self::deposit_event(Event::<T>::IndexDrawn {
1671                            index: digest,
1672                            backer,
1673                            amount: draw_value,
1674                        });
1675                    }
1676                    Funder::Pool { digest, backer } => {
1677                        Self::deposit_event(Event::<T>::PoolDrawn {
1678                            pool: digest,
1679                            backer,
1680                            amount: draw_value,
1681                        });
1682                    }
1683                }
1684            }
1685            Ok(())
1686        }
1687
1688        /// Resign from the **author role** and exit active participation.
1689        ///
1690        /// Releases the caller's collateral while retaining any external backing
1691        /// relationships until funders explicitly exit their positions.
1692        ///
1693        /// ## Behavior
1694        /// - Removes the caller from active author participation.
1695        /// - Releases all locked collateral back to the caller.
1696        /// - External funders/backers must withdraw separately via [`Pallet::exit`].
1697        /// - The author's digest is reaped only if no active backing remains.
1698        ///
1699        /// **Emits:** [`Event::AuthorResigned`]
1700        #[pallet::call_index(5)]
1701        #[pallet::weight(T::WeightInfo::demit())]
1702        pub fn demit(origin: OriginFor<T>) -> DispatchResult {
1703            let caller = ensure_signed(origin)?;
1704            let released = <Pallet<T> as RoleManager<Author<T>>>::resign(&caller)?;
1705            if !T::EmitEvents::get() {
1706                Self::deposit_event(Event::<T>::AuthorResigned {
1707                    author: caller,
1708                    released,
1709                });
1710            }
1711            Ok(())
1712        }
1713
1714        /// Create a new **funding index** over a set of authors.
1715        ///
1716        /// An index represents a weighted collection of author commitment digests,
1717        /// enabling aggregated funding and proportional exposure.
1718        ///
1719        /// - Index entries resolve exclusively to author collateral digests.
1720        /// - Share weights are deterministic and auditable.
1721        /// - The caller becomes the index owner.
1722        ///
1723        /// **Emits:** [`Event::IndexCreated`] via deposit event
1724        #[pallet::call_index(6)]
1725        #[pallet::weight(T::WeightInfo::create_index())]
1726        pub fn create_index(
1727            origin: OriginFor<T>,
1728            entries: Vec<(Author<T>, Shares<T>)>,
1729        ) -> DispatchResult {
1730            let caller = ensure_signed(origin)?;
1731            let mut digest_entries = Vec::new();
1732            for (author, shares) in entries {
1733                let author_reason = FreezeReason::AuthorCollateral.into();
1734                let author_digest =
1735                    <T as Config>::CommitmentAdapter::get_commit_digest(&author, &author_reason)?;
1736                digest_entries.push((author_digest, shares));
1737            }
1738            let funding_reason = FreezeReason::AuthorFunding.into();
1739            let index_info = <T as Config>::CommitmentAdapter::prepare_index(
1740                &caller,
1741                &funding_reason,
1742                &digest_entries,
1743            )?;
1744            let index_digest = <T as Config>::CommitmentAdapter::gen_index_digest(
1745                &caller,
1746                &funding_reason,
1747                &index_info,
1748            )?;
1749            <T as Config>::CommitmentAdapter::set_index(
1750                &caller,
1751                &funding_reason,
1752                &index_info,
1753                &index_digest,
1754            )?;
1755
1756            #[cfg(not(any(feature = "dev", feature = "runtime-benchmarks")))]
1757            {
1758                Self::deposit_event(Event::<T>::IndexCreated {
1759                    index: index_digest,
1760                });
1761            }
1762
1763            #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
1764            {
1765                let entries = <T as Config>::CommitmentAdapter::get_entries_shares(
1766                    &funding_reason,
1767                    &index_digest,
1768                )?;
1769                Self::deposit_event(Event::<T>::IndexCreated {
1770                    index: index_digest,
1771                    entries: entries,
1772                });
1773            }
1774            Ok(())
1775        }
1776
1777        /// Create a new **funding pool** backed by an existing index.
1778        ///
1779        /// Pools enable managed aggregation of funds with an explicit commission
1780        /// applied to rewards earned via the underlying index.
1781        ///
1782        /// - Pool configuration is uniquely identified by its digest.
1783        /// - Commission is fixed at creation time, changing commission after
1784        /// creates a new pool with same slots and updated commission.
1785        /// - The caller becomes the initial pool manager.
1786        ///
1787        /// **Emits:** [`Event::PoolCreated`] via deposit event
1788        #[pallet::call_index(7)]
1789        #[pallet::weight(T::WeightInfo::create_pool())]
1790        pub fn create_pool(
1791            origin: OriginFor<T>,
1792            index: IndexDigest<T>,
1793            commission: Commission<T>,
1794        ) -> DispatchResult {
1795            let caller = ensure_signed(origin)?;
1796            let funding_reason = FreezeReason::AuthorFunding.into();
1797            let pool_digest = <T as Config>::CommitmentAdapter::gen_pool_digest(
1798                &caller,
1799                &funding_reason,
1800                &index,
1801                commission,
1802            )?;
1803            <T as Config>::CommitmentAdapter::set_pool(
1804                &caller,
1805                &funding_reason,
1806                &pool_digest,
1807                &index,
1808                commission,
1809            )?;
1810
1811            #[cfg(not(any(feature = "dev", feature = "runtime-benchmarks")))]
1812            {
1813                Self::deposit_event(Event::<T>::PoolCreated {
1814                    pool: pool_digest,
1815                    commission: commission,
1816                    manager: caller,
1817                });
1818            }
1819
1820            #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
1821            {
1822                let slots = <T as Config>::CommitmentAdapter::get_slots_shares(
1823                    &funding_reason,
1824                    &pool_digest,
1825                )?;
1826                Self::deposit_event(Event::<T>::PoolCreated {
1827                    pool: pool_digest,
1828                    commission: commission,
1829                    manager: caller,
1830                    slots: slots,
1831                });
1832            }
1833            Ok(())
1834        }
1835
1836        /// Transfer **management ownership** of a funding pool.
1837        ///
1838        /// Updates the pool manager without affecting custody of funds.
1839        /// The pool remains **non-custodial**, and all underlying funds,
1840        /// slots, and backing relationships remain unchanged.
1841        ///
1842        /// ## Requirements
1843        /// - Callable only by the current pool manager.
1844        ///
1845        /// ## Behavior
1846        /// - Transfers only management control.
1847        /// - Does not transfer ownership of any pooled funds.
1848        ///
1849        /// **Emits:** [`Event::PoolManager`]
1850        #[pallet::call_index(8)]
1851        #[pallet::weight(T::WeightInfo::transfer_pool())]
1852        pub fn transfer_pool(
1853            origin: OriginFor<T>,
1854            pool: PoolDigest<T>,
1855            to: T::AccountId,
1856        ) -> DispatchResult {
1857            let caller = ensure_signed(origin)?;
1858            let funding_reason = FreezeReason::AuthorFunding.into();
1859            let current_manager =
1860                <T as Config>::CommitmentAdapter::get_manager(&funding_reason, &pool)?;
1861            ensure!(current_manager == caller, Error::<T>::InvalidPoolManager);
1862            <T as Config>::CommitmentAdapter::set_pool_manager(&funding_reason, &pool, &to)?;
1863            Self::deposit_event(Event::<T>::PoolManager {
1864                digest: pool,
1865                manager: to,
1866            });
1867            Ok(())
1868        }
1869
1870        /// Update the commission for a funding pool by creating a new pool instance.
1871        ///
1872        /// This operation does **not mutate the existing pool**. Instead, it creates
1873        /// a new pool derived from the given index with the updated commission,
1874        /// assigning the caller as the new pool manager.
1875        ///
1876        /// ## Behavior
1877        /// - A new pool is created with the specified commission.
1878        /// - The caller becomes the manager of the new pool.
1879        /// - The new pool starts with **no funds or backing**.
1880        /// - The original pool remains unchanged.
1881        ///
1882        /// ## Notes
1883        /// - Pools are **non-custodial**, and funds are not transferred during this operation.
1884        /// - This effectively creates a fresh pool configuration with updated parameters.
1885        ///
1886        /// **Emits:** [`Event::PoolCreated`]
1887        #[pallet::call_index(9)]
1888        #[pallet::weight(T::WeightInfo::update_commission())]
1889        pub fn update_commission(
1890            origin: OriginFor<T>,
1891            index: IndexDigest<T>,
1892            commission: Commission<T>,
1893        ) -> DispatchResult {
1894            let caller = ensure_signed(origin)?;
1895            let funding_reason = FreezeReason::AuthorFunding.into();
1896            let pool_digest = <T as Config>::CommitmentAdapter::set_commission(
1897                &caller,
1898                &funding_reason,
1899                &index,
1900                commission,
1901            )?;
1902
1903            #[cfg(not(any(feature = "dev", feature = "runtime-benchmarks")))]
1904            {
1905                Self::deposit_event(Event::<T>::PoolCreated {
1906                    pool: pool_digest,
1907                    commission: commission,
1908                    manager: caller,
1909                });
1910            }
1911
1912            #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
1913            {
1914                let slots = <T as Config>::CommitmentAdapter::get_slots_shares(
1915                    &funding_reason,
1916                    &pool_digest,
1917                )?;
1918                Self::deposit_event(Event::<T>::PoolCreated {
1919                    pool: pool_digest,
1920                    commission: commission,
1921                    manager: caller,
1922                    slots: slots,
1923                });
1924            }
1925            Ok(())
1926        }
1927
1928        /// Derive a **new index** with updated share weight for a specific entry.
1929        ///
1930        /// This operation does **not mutate** the existing index. Instead, it creates
1931        /// a new index configuration where the specified `entry` is assigned the
1932        /// given `shares`, while all other entries remain unchanged.
1933        ///
1934        /// ## Behavior
1935        /// - Produces a new index digest reflecting the updated share distribution.
1936        /// - The original index remains immutable and unchanged.
1937        ///
1938        /// ## Notes
1939        /// - Indexes are **immutable** once created.
1940        /// - Any modification to entry shares results in a **new index instance**.
1941        ///
1942        /// **Emits:** [`Event::IndexCreated`]
1943        #[pallet::call_index(10)]
1944        #[pallet::weight(T::WeightInfo::update_entry_shares())]
1945        pub fn update_entry_shares(
1946            origin: OriginFor<T>,
1947            index: IndexDigest<T>,
1948            entry: IndexDigest<T>,
1949            shares: Shares<T>,
1950        ) -> DispatchResult {
1951            let caller = ensure_signed(origin)?;
1952            let funding_reason = FreezeReason::AuthorFunding.into();
1953            let new_index = <T as Config>::CommitmentAdapter::set_entry_shares(
1954                &caller,
1955                &funding_reason,
1956                &index,
1957                &entry,
1958                shares,
1959            )?;
1960
1961            #[cfg(not(any(feature = "dev", feature = "runtime-benchmarks")))]
1962            {
1963                Self::deposit_event(Event::<T>::IndexCreated { index: new_index });
1964            }
1965
1966            #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
1967            {
1968                let entries = <T as Config>::CommitmentAdapter::get_entries_shares(
1969                    &funding_reason,
1970                    &new_index,
1971                )?;
1972                Self::deposit_event(Event::<T>::IndexCreated {
1973                    index: new_index,
1974                    entries: entries,
1975                });
1976            }
1977            Ok(())
1978        }
1979
1980        /// Update the share weight of a **slot** within an existing pool.
1981        ///
1982        /// Modifies the share allocation of the specified `slot` in the given `pool`
1983        /// without altering the pool's identity or existing commitments.
1984        ///
1985        /// ## Requirements
1986        /// - Caller must be the current pool manager.
1987        /// - The specified pool and slot must be valid.
1988        ///
1989        /// ## Behavior
1990        /// - Updates the slot's share weight **in place**.
1991        /// - Preserves all existing funds, commitments, and pool configuration.
1992        ///
1993        /// ## Notes
1994        /// - Unlike indexes, pools are **mutable** and support in-place updates.
1995        ///
1996        /// **Emits:** [`Event::PoolSlotShare`]
1997        #[pallet::call_index(11)]
1998        #[pallet::weight(T::WeightInfo::update_slot_shares())]
1999        pub fn update_slot_shares(
2000            origin: OriginFor<T>,
2001            pool: PoolDigest<T>,
2002            slot: PoolDigest<T>,
2003            shares: Shares<T>,
2004        ) -> DispatchResult {
2005            let caller = ensure_signed(origin)?;
2006            let funding_reason = FreezeReason::AuthorFunding.into();
2007            let current_manager =
2008                <T as Config>::CommitmentAdapter::get_manager(&funding_reason, &pool)?;
2009            ensure!(current_manager == caller, Error::<T>::InvalidPoolManager);
2010            <T as Config>::CommitmentAdapter::set_slot_shares(
2011                &caller,
2012                &funding_reason,
2013                &pool,
2014                &slot,
2015                shares,
2016            )?;
2017            Self::deposit_event(Event::<T>::PoolSlotShare {
2018                pool,
2019                slots: (slot, shares),
2020            });
2021            Ok(())
2022        }
2023
2024        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2025        // ````````````````````````````````` INSPECTORS ``````````````````````````````````
2026        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2027
2028        /// Inspect and emit the caller's current **total collateral**.
2029        ///
2030        /// Provides an up-to-date view of the caller's collateral, since on-chain
2031        /// storage may reflect only a lazy or stale balance representation.
2032        ///
2033        /// ## Behavior
2034        /// - Emits the latest computed collateral value.
2035        /// - Performs no state mutation.
2036        ///
2037        /// **Emits:** [`Event::AuthorTotalCollateral`]
2038        #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
2039        #[pallet::call_index(12)]
2040        #[pallet::weight(T::WeightInfo::my_collateral())]
2041        pub fn my_collateral(origin: OriginFor<T>) -> DispatchResult {
2042            let caller = ensure_signed(origin)?;
2043            <Pallet<T> as RoleManager<Author<T>>>::role_exists(&caller)?;
2044            let collateral = <Pallet<T> as RoleManager<Author<T>>>::get_collateral(&caller)?;
2045            Self::deposit_event(Event::<T>::AuthorTotalCollateral {
2046                author: caller,
2047                collateral,
2048            });
2049            Ok(())
2050        }
2051
2052        /// Inspect the caller's **total committed funds** under a specific funding target.
2053        ///
2054        /// Provides a read-only view of the caller's currently locked funds
2055        /// across supported funding models.
2056        ///
2057        /// ## Behavior
2058        /// - **Direct:** Emits funds committed to a single author.
2059        /// - **Index:** Emits total funds committed via an index.
2060        /// - **Pool:** Emits total funds committed via a pool.
2061        ///
2062        /// ## Validation
2063        /// - Ensures the provided index or pool digest matches the caller's
2064        ///   active commitment.
2065        /// - Fails if the digest is invalid or belongs to another commitment.
2066        ///
2067        /// **Emits:**
2068        /// - [`Event::InspectAuthorFund`] for direct funding
2069        /// - [`Event::InspectIndexFund`] for index-based funding
2070        /// - [`Event::InspectPoolFund`] for pool-based funding
2071        #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
2072        #[pallet::call_index(13)]
2073        #[pallet::weight(
2074        T::WeightInfo::check_direct_fund()
2075            .max(T::WeightInfo::check_index_fund())
2076            .max(T::WeightInfo::check_pool_fund())
2077        )]
2078        pub fn my_fund(origin: OriginFor<T>, from: FundingTarget<T>) -> DispatchResult {
2079            let caller = ensure_signed(origin)?;
2080            match from {
2081                FundingTarget::Direct(author) => {
2082                    let funder = Funder::<T>::Direct(caller.clone());
2083                    let amount = <Pallet<T> as FundRoles<Author<T>>>::get_fund(&author, &funder)?;
2084                    Self::deposit_event(Event::<T>::InspectAuthorFund {
2085                        author,
2086                        backer: caller,
2087                        amount,
2088                    });
2089                }
2090                FundingTarget::Index(index_digest) => {
2091                    let reason = &FreezeReason::AuthorFunding.into();
2092                    let actual_digest = T::CommitmentAdapter::get_commit_digest(&caller, reason)?;
2093                    T::CommitmentAdapter::index_exists(reason, &actual_digest)?;
2094                    ensure!(
2095                        index_digest == actual_digest,
2096                        Error::<T>::FundedToAnotherDigest
2097                    );
2098                    let index_fund = T::CommitmentAdapter::get_commit_value(&caller, reason)?;
2099                    Self::deposit_event(Event::<T>::InspectIndexFund {
2100                        index: index_digest,
2101                        backer: caller,
2102                        amount: index_fund,
2103                    });
2104                }
2105                FundingTarget::Pool(pool_digest) => {
2106                    let reason = &FreezeReason::AuthorFunding.into();
2107                    let actual_digest = T::CommitmentAdapter::get_commit_digest(&caller, reason)?;
2108                    T::CommitmentAdapter::pool_exists(reason, &actual_digest)?;
2109                    ensure!(
2110                        pool_digest == actual_digest,
2111                        Error::<T>::FundedToAnotherDigest
2112                    );
2113                    let pool_fund = T::CommitmentAdapter::get_commit_value(&caller, reason)?;
2114                    Self::deposit_event(Event::<T>::InspectPoolFund {
2115                        pool: pool_digest,
2116                        backer: caller,
2117                        amount: pool_fund,
2118                    });
2119                }
2120            };
2121            Ok(())
2122        }
2123
2124        /// Inspect the caller's committed funds **towards a specific author**.
2125        ///
2126        /// Resolves the caller's funding relationship with the given author
2127        /// across the specified funding target.
2128        ///
2129        /// ## Behavior
2130        /// - Supports **Direct**, **Index**, and **Pool** funding models.
2131        /// - Emits the amount currently committed by the caller to the specified
2132        /// author.
2133        ///
2134        /// **Emits:** [`Event::InspectFund`]
2135        #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
2136        #[pallet::call_index(14)]
2137        #[pallet::weight(T::WeightInfo::check_index_fund_towards()
2138            .max(T::WeightInfo::check_pool_fund_towards())
2139            .max(T::WeightInfo::check_direct_fund())
2140        )]
2141        pub fn my_author_fund(
2142            origin: OriginFor<T>,
2143            author: Author<T>,
2144            from: FundingTarget<T>,
2145        ) -> DispatchResult {
2146            let caller = ensure_signed(origin)?;
2147            let funder = match from {
2148                FundingTarget::Direct(_author) => {
2149                    let funder = Funder::<T>::Direct(caller);
2150                    funder
2151                }
2152                FundingTarget::Index(index_digest) => {
2153                    let funder = Funder::<T>::Index {
2154                        digest: index_digest,
2155                        backer: caller,
2156                    };
2157                    funder
2158                }
2159                FundingTarget::Pool(pool_digest) => {
2160                    let funder = Funder::<T>::Pool {
2161                        digest: pool_digest,
2162                        backer: caller,
2163                    };
2164                    funder
2165                }
2166            };
2167            let amount = <Pallet<T> as FundRoles<Author<T>>>::get_fund(&author, &funder)?;
2168            Self::deposit_event(Event::<T>::InspectFund {
2169                author,
2170                funder,
2171                amount,
2172            });
2173            Ok(())
2174        }
2175
2176        /// Shed (expose) all **scheduled rewards** for the caller i.e., author.
2177        ///
2178        /// Retrieves and emits the rewards currently scheduled for the caller
2179        /// without modifying state.
2180        ///
2181        /// **Emits:** [`Event::ScheduledRewards`] via internal hook
2182        #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
2183        #[pallet::call_index(15)]
2184        #[pallet::weight(T::WeightInfo::shed_rewards())]
2185        pub fn shed_rewards(origin: OriginFor<T>) -> DispatchResult {
2186            let caller = ensure_signed(origin)?;
2187            Self::has_reward(&caller)?;
2188            let shed_rewards = Self::get_rewards_of(&caller)?;
2189            Self::deposit_event(Event::<T>::ScheduledRewards {
2190                author: caller,
2191                rewards: shed_rewards,
2192            });
2193            Ok(())
2194        }
2195
2196        /// Shed (expose) all **scheduled penalties** for the caller.
2197        ///
2198        /// Retrieves and emits the penalties currently scheduled for the caller
2199        /// without modifying state.
2200        ///
2201        /// ## Notes
2202        /// - Each penalty factor represents a **ratio applied to the author's
2203        ///   total funds**, including both collateral and external backing.
2204        ///
2205        /// **Emits:** [`Event::ScheduledPenalties`]
2206        #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
2207        #[pallet::call_index(16)]
2208        #[pallet::weight(T::WeightInfo::shed_penalties())]
2209        pub fn shed_penalties(origin: OriginFor<T>) -> DispatchResult {
2210            let caller = ensure_signed(origin)?;
2211            Self::has_penalty(&caller)?;
2212            let shed_penalties = Self::get_penalties_of(&caller)?;
2213            Self::deposit_event(Event::<T>::ScheduledPenalties {
2214                author: caller,
2215                penalties: shed_penalties,
2216            });
2217            Ok(())
2218        }
2219
2220        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2221        // ``````````````````````````````` ROOT PRIVILEGED ```````````````````````````````
2222        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2223
2224        /// Force-update a selected genesis configuration parameter.
2225        ///
2226        /// **Origin:** Root only.
2227        ///
2228        /// This extrinsic allows privileged modification of runtime parameters
2229        /// that were originally defined at genesis.
2230        ///
2231        /// - `ProbationPeriod` - Updates the number of blocks authors must remain in probation.
2232        /// - `ReduceProbationBy` - Updates how much probation is reduced on good behavior.
2233        /// - `IncreaseProbationBy` - Updates how much probation is increased on misbehavior.
2234        /// - `RewardsBuffer` - Updates the delay (in blocks) before rewards are finalized.
2235        /// - `PenaltiesBuffer` - Updates the delay (in blocks) before penalties are enforced.
2236        /// - `MaxElected` - Updates the maximum number of authors that can be elected.
2237        /// - `MinElected` - Updates the minimum number of authors required for a valid election.
2238        /// - `EnforceMaxElected` - Toggles strict enforcement of the `MaxElected` limit.
2239        /// - `MinFund` - Updates the minimum funding required per backing operation.
2240        /// - `MaxExposure` - Updates the maximum allowed exposure per funding operation.
2241        /// - `MinCollateral` - Updates the minimum collateral required for authors.
2242        ///
2243        /// The call enforces consistency constraints where applicable:
2244        /// - Values that must be non-zero will fail with [`Error::NonZeroConfigRequired`].
2245        /// - Fails with [`Error::MinGreaterThanMax`] if:
2246        ///   - `MinElected > MaxElected`, or
2247        ///   - `MinFund > MaxExposure`, or
2248        ///   - `MaxElected < MinElected`, or
2249        ///   - `MaxExposure < MinFund`.
2250        ///
2251        /// This call directly overwrites storage and emits an event containing the
2252        /// updated configuration variant.
2253        #[pallet::call_index(17)]
2254        #[pallet::weight(
2255        T::WeightInfo::force_probation_period()
2256            .max(T::WeightInfo::force_enforce_max_elected())
2257            .max(T::WeightInfo::force_increase_probation_by())
2258            .max(T::WeightInfo::force_max_elected())
2259            .max(T::WeightInfo::force_max_exposure())
2260            .max(T::WeightInfo::force_min_collateral())
2261            .max(T::WeightInfo::force_min_elected())
2262            .max(T::WeightInfo::force_min_fund())
2263            .max(T::WeightInfo::force_penalties_buffer())
2264            .max(T::WeightInfo::force_reduce_probation_by())
2265            .max(T::WeightInfo::force_rewards_buffer())
2266        )]
2267        pub fn force_genesis_config(
2268            origin: OriginFor<T>,
2269            field: ForceGenesisConfig<T>,
2270        ) -> DispatchResult {
2271            ensure_root(origin)?;
2272            match field {
2273                ForceGenesisConfig::ProbationPeriod(block) => ProbationPeriod::<T>::put(block),
2274                ForceGenesisConfig::ReduceProbationBy(block) => ReduceProbationBy::<T>::put(block),
2275                ForceGenesisConfig::IncreaseProbationBy(block) => {
2276                    IncreaseProbationBy::<T>::put(block)
2277                }
2278                ForceGenesisConfig::RewardsBuffer(block) => RewardsBuffer::<T>::put(block),
2279                ForceGenesisConfig::PenaltiesBuffer(block) => PenaltiesBuffer::<T>::put(block),
2280                ForceGenesisConfig::MaxElected(max_elected) => {
2281                    ensure!(!max_elected.is_zero(), Error::<T>::NonZeroConfigRequired);
2282                    ensure!(
2283                        max_elected >= MinElected::<T>::get(),
2284                        Error::<T>::MinGreaterThanMax
2285                    );
2286                    MaxElected::<T>::put(max_elected);
2287                }
2288                ForceGenesisConfig::MinElected(min_elected) => {
2289                    ensure!(!min_elected.is_zero(), Error::<T>::NonZeroConfigRequired);
2290                    ensure!(
2291                        min_elected <= MaxElected::<T>::get(),
2292                        Error::<T>::MinGreaterThanMax
2293                    );
2294                    MinElected::<T>::put(min_elected);
2295                }
2296                ForceGenesisConfig::EnforceMaxElected(bool) => ForceMaxElected::<T>::put(bool),
2297                ForceGenesisConfig::MinFund(asset) => {
2298                    ensure!(!asset.is_zero(), Error::<T>::NonZeroConfigRequired);
2299                    ensure!(
2300                        asset <= MaxExposure::<T>::get(),
2301                        Error::<T>::MinGreaterThanMax
2302                    );
2303                    MinFund::<T>::put(asset);
2304                }
2305                ForceGenesisConfig::MaxExposure(asset) => {
2306                    ensure!(asset >= MinFund::<T>::get(), Error::<T>::MinGreaterThanMax);
2307                    MaxExposure::<T>::put(asset);
2308                }
2309                ForceGenesisConfig::MinCollateral(asset) => {
2310                    ensure!(!asset.is_zero(), Error::<T>::NonZeroConfigRequired);
2311                    MinCollateral::<T>::put(asset);
2312                }
2313            }
2314            Self::deposit_event(Event::GenesisConfigUpdated(field));
2315            Ok(())
2316        }
2317    }
2318
2319    // ===============================================================================
2320    // ````````````````````````````````` PUBLIC APIS `````````````````````````````````
2321    // ===============================================================================
2322
2323    /// Public read-only functions for inspecting author collateral and funding state.
2324    ///
2325    /// This interface exposes non-mutating functions that allow external consumers
2326    /// (e.g. off-chain clients, RPC layers, and other pallets) to inspect the
2327    /// economic state of authors and funding relationships.
2328    impl<T: Config> Pallet<T> {
2329        /// Return the total **locked collateral** of an author.
2330        ///  - `who` must be an enrolled author.
2331        pub fn fetch_collateral(who: Author<T>) -> Result<AuthorAsset<T>, DispatchError> {
2332            <Pallet<T> as RoleManager<Author<T>>>::role_exists(&who)?;
2333            let total_collateral = <Pallet<T> as RoleManager<Author<T>>>::get_collateral(&who)?;
2334            Ok(total_collateral)
2335        }
2336
2337        /// Fetch the caller's **total committed funding** under a specific funding model.
2338        ///
2339        /// - The caller must have an active commitment under the specified target.
2340        /// - The provided digest must match the caller's active commitment.
2341        pub fn inspect_fund(
2342            caller: T::AccountId,
2343            from: FundingTarget<T>,
2344        ) -> Result<AuthorAsset<T>, DispatchError> {
2345            match from {
2346                FundingTarget::Direct(author) => {
2347                    let funder = Funder::<T>::Direct(caller);
2348                    let fund = <Pallet<T> as FundRoles<Author<T>>>::get_fund(&author, &funder)?;
2349                    return Ok(fund);
2350                }
2351                FundingTarget::Index(index_digest) => {
2352                    let reason = &FreezeReason::AuthorFunding.into();
2353                    let actual_digest = T::CommitmentAdapter::get_commit_digest(&caller, reason)?;
2354                    T::CommitmentAdapter::index_exists(reason, &actual_digest)?;
2355                    ensure!(
2356                        index_digest == actual_digest,
2357                        Error::<T>::FundedToAnotherDigest
2358                    );
2359                    let index_fund = T::CommitmentAdapter::get_commit_value(&caller, reason)?;
2360                    return Ok(index_fund);
2361                }
2362                FundingTarget::Pool(pool_digest) => {
2363                    let reason = &FreezeReason::AuthorFunding.into();
2364                    let actual_digest = T::CommitmentAdapter::get_commit_digest(&caller, reason)?;
2365                    T::CommitmentAdapter::pool_exists(reason, &actual_digest)?;
2366                    ensure!(
2367                        pool_digest == actual_digest,
2368                        Error::<T>::FundedToAnotherDigest
2369                    );
2370                    let pool_fund = T::CommitmentAdapter::get_commit_value(&caller, reason)?;
2371                    return Ok(pool_fund);
2372                }
2373            };
2374        }
2375
2376        /// Fetch the caller's **committed funding towards a specific author**.
2377        ///
2378        /// - The caller must have an active funding relationship with the author
2379        ///   under the specified funding target.
2380        pub fn inspect_author_fund(
2381            caller: T::AccountId,
2382            author: Author<T>,
2383            from: FundingTarget<T>,
2384        ) -> Result<AuthorAsset<T>, DispatchError> {
2385            let funder = match from {
2386                FundingTarget::Direct(_author) => {
2387                    let funder = Funder::<T>::Direct(caller);
2388                    funder
2389                }
2390                FundingTarget::Index(index_digest) => {
2391                    let funder = Funder::<T>::Index {
2392                        digest: index_digest,
2393                        backer: caller,
2394                    };
2395                    funder
2396                }
2397                FundingTarget::Pool(pool_digest) => {
2398                    let funder = Funder::<T>::Pool {
2399                        digest: pool_digest,
2400                        backer: caller,
2401                    };
2402                    funder
2403                }
2404            };
2405            let fund = <Pallet<T> as FundRoles<Author<T>>>::get_fund(&author, &funder)?;
2406            Ok(fund)
2407        }
2408    }
2409
2410    // ===============================================================================
2411    // `````````````````````````` REUSABLE ELECTION MODELS ```````````````````````````
2412    // ===============================================================================
2413
2414    /// A **FlatElection** represents an election model in which an author's
2415    /// **entire backing position** is **flattened** into a single aggregated
2416    /// influence value.
2417    ///
2418    /// In this model, all sources of support associated with an author - including:
2419    /// - **self-collateral**, and
2420    /// - **external backing** (direct backers, unmanaged indexes, or managed pools)
2421    /// are first **combined into one total backing value**.
2422    ///
2423    /// This flattened value is then passed through the **influence computation plugin**
2424    /// to derive a single scalar influence metric for the author.
2425    ///
2426    /// The resulting influence values are used to compare authors **uniformly**,
2427    /// independent of how their backing is structured or distributed.
2428    ///
2429    /// ## Characteristics
2430    /// - Aggregates **all sources of support** (self + external) into one value.
2431    /// - Produces **exactly one influence metric per author**.
2432    /// - Discards backer-level structure and granularity.
2433    /// - Enables simple, deterministic, influence-based comparison.
2434    ///
2435    /// ## Election Flow
2436    /// 1. Collect all backing associated with each author (self + external).
2437    /// 2. Flatten the backing into a single aggregate value.
2438    /// 3. Compute influence using the [`Config::InfluenceModel`] plugin.
2439    /// 4. Rank or select authors using the [`Config::FlatElectionModel`] plugin.
2440    ///
2441    /// ## Typical Use-Case
2442    /// - Selecting authors or producers based purely on **total effective influence**,
2443    ///   where the origin or distribution of support is intentionally ignored.
2444    ///
2445    /// ## Summary
2446    /// `FlatElection` **flattens the backing graph**:
2447    /// all forms of support are reduced to a single comparable influence metric.
2448    ///
2449    /// The model is simple, deterministic, and influence-driven, but it
2450    /// **does not preserve fairness across individual backers**.
2451    pub struct FlatElection<T: Config>(PhantomData<T>);
2452
2453    /// A **FairElection** represents an election model that preserves the
2454    /// **structure and fairness of backing** by retaining each supporter's
2455    /// individual contribution weight.
2456    ///
2457    /// Unlike [`FlatElection`], this model **does not flatten backing**.
2458    /// Instead, every supporter - including the author themselves - is treated
2459    /// as an individual backer with a distinct weight.
2460    ///
2461    /// Self-collateral is therefore considered **self-backing**, contributing
2462    /// to the author's support while still remaining structurally independent
2463    /// from other backers.
2464    ///
2465    /// Both:
2466    /// - **self-backing** (author's own collateral), and
2467    /// - **external backing** (direct backers, unmanaged indexes, or managed pools)
2468    /// are preserved as **separate weighted relationships** rather than being
2469    /// aggregated into a single scalar.
2470    ///
2471    /// Each backing weight is mapped **individually** to the author they support,
2472    /// and the election outcome is computed from these unaggregated
2473    /// backer-author relationships.
2474    ///
2475    /// ## Characteristics
2476    /// - Treats **self-collateral as self-backing** (not flattened).
2477    /// - Preserves **individual backer weights** without aggregation.
2478    /// - Maintains structural fairness across all supporters.
2479    /// - Reflects both commitment (self-backing) and community support.
2480    /// - Emphasizes proportional and decentralized representation.
2481    ///
2482    /// ## Election Flow
2483    /// 1. Gather all backers for each author, including self-backing.
2484    /// 2. Map each backer's weight individually to their chosen author.
2485    /// 3. Provide these mappings to the [`Config::FairElectionModel`] plugin.
2486    /// 4. Compute outcomes that reflect **proportional and distributed support**.
2487    ///
2488    /// ## Typical Use-Case
2489    /// - Selecting representatives, validators, or leaders where
2490    ///   **fair proportional influence** from all supporters - including the
2491    ///   author's own stake - should be preserved without flattening.
2492    ///
2493    /// ## Summary
2494    /// `FairElection` **preserves the backing structure**:
2495    /// every supporter (self or external) is represented individually, and each
2496    /// weight contributes proportionally without being merged into a single value.
2497    ///
2498    /// The model prioritizes **fair representation and structural integrity of support**.
2499    pub struct FairElection<T: Config>(PhantomData<T>);
2500}
2501
2502// ===============================================================================
2503// `````````````````````````````````` API TESTS ``````````````````````````````````
2504// ===============================================================================
2505
2506#[cfg(test)]
2507mod ext_tests {
2508
2509    // ===============================================================================
2510    // ``````````````````````````````````` IMPORTS ```````````````````````````````````
2511    // ===============================================================================
2512
2513    // --- Local crate imports ---
2514    use crate::{
2515        mock::authors_test_ext,
2516        mock::*,
2517        types::{
2518            AuthorStatus, ForceGenesisConfig, FortitudeWrapper, Funder, FundingTarget,
2519            PrecisionWrapper,
2520        },
2521        FreezeReason,
2522    };
2523
2524    // --- FRAME Suite ---
2525    use frame_suite::{
2526        commitment::*,
2527        roles::{CompensateRoles, FundRoles, RoleManager, RoleProbation},
2528    };
2529
2530    // --- FRAME Support ---
2531    use frame_support::{
2532        assert_err, assert_ok,
2533        traits::{
2534            tokens::{Fortitude, Precision},
2535            Hooks,
2536        },
2537    };
2538
2539    // --- Substrate primitives ---
2540    use sp_runtime::{DispatchError, Perbill};
2541
2542    // ===============================================================================
2543    // ``````````````````````````````````` HELPERS ```````````````````````````````````
2544    // ===============================================================================
2545
2546    // Finds latest IndexCreated event and returns its digest
2547    fn assert_index_created_and_get_digest() -> IndexDigest {
2548        System::events()
2549            .iter()
2550            .rev()
2551            .find_map(|record| {
2552                if let RuntimeEvent::Authors(Event::IndexCreated { index, .. }) = &record.event {
2553                    Some(index.clone())
2554                } else {
2555                    None
2556                }
2557            })
2558            .expect("IndexCreated event not emitted")
2559    }
2560
2561    // Finds latest PoolCreated event and returns its digest
2562    fn assert_pool_created_and_get_digest() -> PoolDigest {
2563        System::events()
2564            .iter()
2565            .rev()
2566            .find_map(|record| {
2567                if let RuntimeEvent::Authors(Event::PoolCreated { pool, .. }) = &record.event {
2568                    Some(pool.clone())
2569                } else {
2570                    None
2571                }
2572            })
2573            .expect("PoolCreated event not emitted")
2574    }
2575
2576    // ===============================================================================
2577    // ```````````````````````````````````` HOOKS ````````````````````````````````````
2578    // ===============================================================================
2579
2580    #[test]
2581    fn on_initialize_succes() {
2582        authors_test_ext().execute_with(|| {
2583            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
2584            initiate_key_and_set_balance_and_hold(&&CHARLIE, INITIAL_BALANCE, STANDARD_HOLD)
2585                .unwrap();
2586
2587            System::set_block_number(6);
2588            Pallet::enroll(&ALICE, 250, Fortitude::Force).unwrap();
2589            Pallet::fund(
2590                &ALICE,
2591                &Funder::Direct(CHARLIE),
2592                125,
2593                Precision::Exact,
2594                Fortitude::Force,
2595            )
2596            .unwrap();
2597
2598            System::set_block_number(16);
2599            Pallet::set_permanence(&ALICE).unwrap();
2600
2601            let author_collateral = Pallet::get_collateral(&ALICE).unwrap();
2602            assert_eq!(author_collateral, 250);
2603            let author_backing = Pallet::get_fund(&ALICE, &Funder::Direct(CHARLIE)).unwrap();
2604            assert_eq!(author_backing, 125);
2605            let hold = Pallet::get_hold(&ALICE).unwrap();
2606            assert_eq!(hold, 375);
2607
2608            System::set_block_number(20);
2609            let reward = 20;
2610            Pallet::reward(&ALICE, reward, Precision::Exact).unwrap();
2611
2612            assert_ok!(Pallet::has_reward(&ALICE));
2613            System::set_block_number(22);
2614            Pallet::on_initialize(22);
2615
2616            let hold = Pallet::get_hold(&ALICE).unwrap();
2617            assert_eq!(hold, 395); // reward is applied to the hold (375 + 20) -> 395
2618
2619            // The reward split proportionally between the author's own collateral and the external backing.
2620            let author_collateral = Pallet::get_collateral(&ALICE).unwrap();
2621            assert_eq!(author_collateral, 263);
2622            let author_backing = Pallet::get_fund(&ALICE, &Funder::Direct(CHARLIE)).unwrap();
2623            assert_eq!(author_backing, 132);
2624
2625            assert_err!(Pallet::has_reward(&ALICE), Error::RewardNotFound);
2626
2627            System::set_block_number(24);
2628            let penalty = Perbill::from_percent(10);
2629            Pallet::penalize(&ALICE, penalty).unwrap();
2630
2631            assert_ok!(Pallet::has_penalty(&ALICE));
2632            System::set_block_number(28);
2633            Pallet::on_initialize(28);
2634
2635            let hold = Pallet::get_hold(&ALICE).unwrap();
2636            assert_eq!(hold, 356); // 10% penalty is applied to the hold (395 - 39) -> 356
2637
2638            // The penalty split proportionally between the author's own
2639            // collateral and the external backing.
2640            let author_collateral = Pallet::get_collateral(&ALICE).unwrap();
2641            // Catering to one unit rounding loss
2642            assert!(author_collateral == 237 || author_collateral == 236);
2643            let author_backing = Pallet::get_fund(&ALICE, &Funder::Direct(CHARLIE)).unwrap();
2644            assert!(author_backing == 119 || author_backing == 118);
2645
2646            assert_err!(Pallet::has_penalty(&ALICE), Error::PenaltyNotFound);
2647        })
2648    }
2649
2650    // ===============================================================================
2651    // ````````````````````````````````` EXTRINSICS ``````````````````````````````````
2652    // ===============================================================================
2653
2654    #[test]
2655    fn enroll_author_success() {
2656        authors_test_ext().execute_with(|| {
2657            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
2658
2659            assert_err!(Pallet::role_exists(&ALICE), Error::AuthorNotFound);
2660            System::set_block_number(10);
2661            assert_ok!(Pallet::enlist(
2662                RuntimeOrigin::signed(ALICE),
2663                100,
2664                FortitudeWrapper::Force
2665            ));
2666
2667            assert_ok!(Pallet::role_exists(&ALICE));
2668            let meta = Pallet::get_meta(&ALICE).unwrap();
2669            assert_eq!(meta.since, 10);
2670            assert_eq!(meta.status, AuthorStatus::Probation);
2671
2672            System::assert_last_event(
2673                Event::AuthorEnlisted {
2674                    author: ALICE,
2675                    collateral: 100,
2676                }
2677                .into(),
2678            );
2679        })
2680    }
2681
2682    #[test]
2683    fn enroll_author_inadequate_collateral() {
2684        authors_test_ext().execute_with(|| {
2685            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
2686
2687            assert_err!(Pallet::role_exists(&ALICE), Error::AuthorNotFound);
2688            System::set_block_number(4);
2689            assert_err!(
2690                Pallet::enlist(RuntimeOrigin::signed(ALICE), 15, FortitudeWrapper::Force),
2691                Error::InadequateCollateral
2692            );
2693        })
2694    }
2695
2696    #[test]
2697    fn enroll_author_bad_origin() {
2698        authors_test_ext().execute_with(|| {
2699            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD)
2700                .unwrap();
2701
2702            assert_err!(
2703                Pallet::role_exists(&ALICE),
2704                Error::AuthorNotFound
2705            );
2706            System::set_block_number(4);
2707            assert_err!{Pallet::enlist(RuntimeOrigin::root(), 100, FortitudeWrapper::Force), DispatchError::BadOrigin};
2708        })
2709    }
2710
2711    #[test]
2712    fn resign_author_success() {
2713        authors_test_ext().execute_with(|| {
2714            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
2715
2716            System::set_block_number(10);
2717            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
2718
2719            System::set_block_number(20);
2720            Pallet::set_status(&ALICE, AuthorStatus::Active).unwrap();
2721
2722            System::set_block_number(25);
2723            assert_ok!(Pallet::demit(RuntimeOrigin::signed(ALICE)));
2724
2725            System::assert_last_event(
2726                Event::AuthorResigned {
2727                    author: ALICE,
2728                    released: 100,
2729                }
2730                .into(),
2731            );
2732        })
2733    }
2734
2735    #[test]
2736    fn resign_author_err_author_in_probation() {
2737        authors_test_ext().execute_with(|| {
2738            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
2739
2740            System::set_block_number(10);
2741            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
2742
2743            System::set_block_number(15);
2744            assert_err!(
2745                Pallet::demit(RuntimeOrigin::signed(ALICE)),
2746                Error::AuthorInProbation
2747            );
2748        })
2749    }
2750
2751    #[test]
2752    fn resign_author_err_author_has_penalties() {
2753        authors_test_ext().execute_with(|| {
2754            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
2755
2756            System::set_block_number(10);
2757            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
2758
2759            System::set_block_number(20);
2760            Pallet::set_status(&ALICE, AuthorStatus::Active).unwrap();
2761
2762            System::set_block_number(25);
2763            Pallet::penalize(&ALICE, Perbill::from_percent(5)).unwrap();
2764
2765            System::set_block_number(26);
2766            assert_err!(
2767                Pallet::demit(RuntimeOrigin::signed(ALICE)),
2768                Error::AuthorHasPenalties
2769            );
2770        })
2771    }
2772
2773    #[test]
2774    fn resign_author_err_bad_origin() {
2775        authors_test_ext().execute_with(|| {
2776            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
2777
2778            System::set_block_number(10);
2779            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
2780
2781            System::set_block_number(20);
2782            Pallet::set_status(&ALICE, AuthorStatus::Active).unwrap();
2783
2784            System::set_block_number(25);
2785            assert_err!(
2786                Pallet::demit(RuntimeOrigin::root()),
2787                DispatchError::BadOrigin
2788            );
2789        })
2790    }
2791
2792    #[test]
2793    fn raise_collateral_success() {
2794        authors_test_ext().execute_with(|| {
2795            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
2796
2797            System::set_block_number(10);
2798            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
2799
2800            let current_collateral = Pallet::get_collateral(&ALICE).unwrap();
2801            assert_eq!(current_collateral, 100);
2802
2803            System::set_block_number(15);
2804            Pallet::refill(RuntimeOrigin::signed(ALICE), 50, FortitudeWrapper::Force).unwrap();
2805
2806            let current_collateral = Pallet::get_collateral(&ALICE).unwrap();
2807            assert_eq!(current_collateral, 150);
2808
2809            System::assert_last_event(
2810                Event::AuthorCollateralRaised {
2811                    author: ALICE,
2812                    raised: 50,
2813                }
2814                .into(),
2815            );
2816        })
2817    }
2818
2819    #[test]
2820    fn raise_collateral_err_bad_origin() {
2821        authors_test_ext().execute_with(|| {
2822            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
2823
2824            System::set_block_number(10);
2825            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
2826
2827            System::set_block_number(15);
2828            assert_err!(
2829                Pallet::refill(RuntimeOrigin::root(), 50, FortitudeWrapper::Force),
2830                DispatchError::BadOrigin
2831            );
2832        })
2833    }
2834
2835    #[cfg(feature = "dev")]
2836    #[test]
2837    fn check_collateral_success() {
2838        authors_test_ext().execute_with(|| {
2839            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
2840
2841            System::set_block_number(10);
2842            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
2843
2844            assert_ok!(Pallet::my_collateral(RuntimeOrigin::signed(ALICE)));
2845
2846            System::assert_last_event(
2847                Event::AuthorTotalCollateral {
2848                    author: ALICE,
2849                    collateral: 100,
2850                }
2851                .into(),
2852            );
2853        })
2854    }
2855
2856    #[cfg(feature = "dev")]
2857    #[test]
2858    fn check_collateral_err_author_not_found() {
2859        authors_test_ext().execute_with(|| {
2860            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
2861
2862            System::set_block_number(10);
2863            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
2864
2865            assert_err!(
2866                Pallet::my_collateral(RuntimeOrigin::signed(BOB)),
2867                Error::AuthorNotFound
2868            );
2869        })
2870    }
2871
2872    #[cfg(feature = "dev")]
2873    #[test]
2874    fn check_collateral_err_bad_origin() {
2875        authors_test_ext().execute_with(|| {
2876            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
2877
2878            System::set_block_number(10);
2879            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
2880
2881            assert_err!(
2882                Pallet::my_collateral(RuntimeOrigin::root()),
2883                DispatchError::BadOrigin
2884            );
2885        })
2886    }
2887
2888    #[test]
2889    fn fund_author_direct_success() {
2890        authors_test_ext().execute_with(|| {
2891            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
2892            initiate_key_and_set_balance_and_hold(&CHARLIE, INITIAL_BALANCE, STANDARD_HOLD)
2893                .unwrap();
2894
2895            System::set_block_number(10);
2896            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
2897
2898            System::set_block_number(20);
2899            assert_ok!(Pallet::back(
2900                RuntimeOrigin::signed(CHARLIE),
2901                FundingTarget::Direct(ALICE),
2902                100,
2903                FortitudeWrapper::Force,
2904                PrecisionWrapper::Exact
2905            ));
2906
2907            let current_hold = Pallet::get_hold(&ALICE).unwrap();
2908            assert_eq!(current_hold, 200);
2909            let backed_value = Pallet::backed_value(&ALICE).unwrap();
2910            assert_eq!(backed_value, 100);
2911            let backers_of = Pallet::backers_of(&ALICE).unwrap();
2912            assert_eq!(backers_of, vec![(Funder::Direct(CHARLIE), 100)]);
2913
2914            System::assert_last_event(
2915                Event::AuthorFunded {
2916                    author: ALICE,
2917                    backer: CHARLIE,
2918                    amount: 100,
2919                }
2920                .into(),
2921            );
2922        })
2923    }
2924
2925    #[test]
2926    fn fund_author_direct_err_bad_origin() {
2927        authors_test_ext().execute_with(|| {
2928            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
2929            initiate_key_and_set_balance_and_hold(&CHARLIE, INITIAL_BALANCE, STANDARD_HOLD)
2930                .unwrap();
2931
2932            System::set_block_number(10);
2933            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
2934
2935            System::set_block_number(20);
2936            assert_err!(
2937                Pallet::back(
2938                    RuntimeOrigin::root(),
2939                    FundingTarget::Direct(ALICE),
2940                    100,
2941                    FortitudeWrapper::Force,
2942                    PrecisionWrapper::Exact
2943                ),
2944                DispatchError::BadOrigin
2945            );
2946        })
2947    }
2948
2949    #[test]
2950    fn fund_author_direct_err_below_minimum_fund() {
2951        authors_test_ext().execute_with(|| {
2952            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
2953            initiate_key_and_set_balance_and_hold(&CHARLIE, INITIAL_BALANCE, STANDARD_HOLD)
2954                .unwrap();
2955
2956            System::set_block_number(10);
2957            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
2958
2959            System::set_block_number(20);
2960            assert_err!(
2961                Pallet::back(
2962                    RuntimeOrigin::signed(CHARLIE),
2963                    FundingTarget::Direct(ALICE),
2964                    15,
2965                    FortitudeWrapper::Force,
2966                    PrecisionWrapper::Exact
2967                ),
2968                Error::BelowMinimumFund
2969            );
2970        })
2971    }
2972
2973    #[test]
2974    fn fund_author_index_success() {
2975        authors_test_ext().execute_with(|| {
2976            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2977            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
2978            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
2979            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
2980            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
2981
2982            System::set_block_number(6);
2983            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
2984
2985            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
2986
2987            Pallet::fund(
2988                &ALICE,
2989                &Funder::Direct(CHARLIE),
2990                STANDARD_VALUE,
2991                Precision::Exact,
2992                Fortitude::Force,
2993            )
2994            .unwrap();
2995
2996            Pallet::fund(
2997                &BOB,
2998                &Funder::Direct(ALAN),
2999                LARGE_VALUE,
3000                Precision::Exact,
3001                Fortitude::Force,
3002            )
3003            .unwrap();
3004
3005            let alice_digest = gen_author_digest(&ALICE).unwrap();
3006            let bob_digest = gen_author_digest(&BOB).unwrap();
3007            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
3008
3009            let alice_current_hold = Pallet::get_hold(&ALICE).unwrap();
3010            assert_eq!(alice_current_hold, 100);
3011            let bob_current_hold = Pallet::get_hold(&BOB).unwrap();
3012            assert_eq!(bob_current_hold, 150);
3013
3014            prepare_and_initiate_index(MIKE, FUNDING.into(), &entries, INDEX_DIGEST).unwrap();
3015
3016            let by = Funder::Index {
3017                digest: INDEX_DIGEST,
3018                backer: MIKE,
3019            };
3020
3021            assert_ok!(Pallet::back(
3022                RuntimeOrigin::signed(MIKE),
3023                FundingTarget::Index(INDEX_DIGEST),
3024                100,
3025                FortitudeWrapper::Force,
3026                PrecisionWrapper::Exact
3027            ));
3028
3029            let backers_of_alice = Pallet::backers_of(&ALICE).unwrap();
3030            let expected_backers_of_alice = vec![(by.clone(), 60), (Funder::Direct(CHARLIE), 50)];
3031            assert_eq!(backers_of_alice, expected_backers_of_alice);
3032
3033            let backers_of_bob = Pallet::backers_of(&BOB).unwrap();
3034            let expected_backers_of_bob = vec![(by.clone(), 40), (Funder::Direct(ALAN), 100)];
3035            assert_eq!(backers_of_bob, expected_backers_of_bob);
3036
3037            let alice_current_hold = Pallet::get_hold(&ALICE).unwrap();
3038            assert_eq!(alice_current_hold, 160);
3039            let bob_current_hold = Pallet::get_hold(&BOB).unwrap();
3040            assert_eq!(bob_current_hold, 190);
3041
3042            System::assert_last_event(
3043                Event::IndexFunded {
3044                    index: INDEX_DIGEST,
3045                    backer: MIKE,
3046                    amount: 100,
3047                }
3048                .into(),
3049            );
3050        })
3051    }
3052
3053    #[test]
3054    fn fund_author_pool_success() {
3055        authors_test_ext().execute_with(|| {
3056            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3057            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3058            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3059            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
3060            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
3061
3062            System::set_block_number(6);
3063            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3064
3065            System::set_block_number(8);
3066            Pallet::fund(
3067                &ALICE,
3068                &Funder::Direct(CHARLIE),
3069                STANDARD_VALUE,
3070                Precision::Exact,
3071                Fortitude::Force,
3072            )
3073            .unwrap();
3074
3075            System::set_block_number(12);
3076            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
3077
3078            System::set_block_number(15);
3079            Pallet::fund(
3080                &BOB,
3081                &Funder::Direct(ALAN),
3082                LARGE_VALUE,
3083                Precision::Exact,
3084                Fortitude::Force,
3085            )
3086            .unwrap();
3087
3088            let total_backing = Pallet::total_backing();
3089            assert_eq!(total_backing, 150);
3090            let alice_backed_value = Pallet::backed_value(&ALICE).unwrap();
3091            assert_eq!(alice_backed_value, 50);
3092            let bob_backed_value = Pallet::backed_value(&BOB).unwrap();
3093            assert_eq!(bob_backed_value, 100);
3094
3095            let alice_digest = gen_author_digest(&ALICE).unwrap();
3096            let bob_digest = gen_author_digest(&BOB).unwrap();
3097            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
3098
3099            let alice_current_hold = Pallet::get_hold(&ALICE).unwrap();
3100            assert_eq!(alice_current_hold, 100);
3101            let bob_current_hold = Pallet::get_hold(&BOB).unwrap();
3102            assert_eq!(bob_current_hold, 150);
3103
3104            prepare_and_initiate_pool(
3105                ALAN,
3106                FUNDING.into(),
3107                &entries,
3108                INDEX_DIGEST,
3109                POOL_DIGEST,
3110                Perbill::from_percent(5),
3111            )
3112            .unwrap();
3113
3114            let by = Funder::Pool {
3115                digest: POOL_DIGEST,
3116                backer: MIKE,
3117            };
3118
3119            assert_ok!(Pallet::back(
3120                RuntimeOrigin::signed(MIKE),
3121                FundingTarget::Pool(POOL_DIGEST),
3122                100,
3123                FortitudeWrapper::Force,
3124                PrecisionWrapper::Exact
3125            ));
3126
3127            let total_backing = Pallet::total_backing();
3128            assert_eq!(total_backing, 250);
3129            let alice_backed_value = Pallet::backed_value(&ALICE).unwrap();
3130            assert_eq!(alice_backed_value, 110); // 50 (existing) + 60 (through index as ALICE share is 60 )
3131            let bob_backed_value = Pallet::backed_value(&BOB).unwrap();
3132            assert_eq!(bob_backed_value, 140); // 100 (existing) + 40 (through index as BOB share is 40 )
3133
3134            let author_funders = AuthorFunders::get((ALICE, MIKE)).unwrap();
3135            assert_eq!(author_funders, by);
3136
3137            let alice_current_hold = Pallet::get_hold(&ALICE).unwrap();
3138            assert_eq!(alice_current_hold, 160);
3139            let bob_current_hold = Pallet::get_hold(&BOB).unwrap();
3140            assert_eq!(bob_current_hold, 190);
3141
3142            System::assert_last_event(
3143                Event::PoolFunded {
3144                    pool: POOL_DIGEST,
3145                    backer: MIKE,
3146                    amount: 100,
3147                }
3148                .into(),
3149            );
3150        })
3151    }
3152
3153    #[test]
3154    fn release_fund_direct_success() {
3155        authors_test_ext().execute_with(|| {
3156            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
3157            initiate_key_and_set_balance_and_hold(&CHARLIE, INITIAL_BALANCE, STANDARD_HOLD)
3158                .unwrap();
3159
3160            System::set_block_number(10);
3161            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
3162
3163            System::set_block_number(20);
3164            assert_ok!(Pallet::back(
3165                RuntimeOrigin::signed(CHARLIE),
3166                FundingTarget::Direct(ALICE),
3167                100,
3168                FortitudeWrapper::Force,
3169                PrecisionWrapper::Exact
3170            ));
3171
3172            let current_hold = Pallet::get_hold(&ALICE).unwrap();
3173            assert_eq!(current_hold, 200);
3174            let backed_value = Pallet::backed_value(&ALICE).unwrap();
3175            assert_eq!(backed_value, 100);
3176            let backers_of = Pallet::backers_of(&ALICE).unwrap();
3177            assert_eq!(backers_of, vec![(Funder::Direct(CHARLIE), 100)]);
3178
3179            let charlie_balance = get_user_balance(&CHARLIE);
3180            assert_eq!(charlie_balance, 100);
3181
3182            System::set_block_number(25);
3183            assert_ok!(Pallet::exit(
3184                RuntimeOrigin::signed(CHARLIE),
3185                FundingTarget::Direct(ALICE)
3186            ));
3187
3188            let current_hold = Pallet::get_hold(&ALICE).unwrap();
3189            assert_eq!(current_hold, 100);
3190
3191            let backed_value = Pallet::backed_value(&ALICE).unwrap();
3192            assert_eq!(backed_value, 0);
3193
3194            let backers_of = Pallet::backers_of(&ALICE).unwrap();
3195            assert_eq!(backers_of, vec![]);
3196
3197            let charlie_balance = get_user_balance(&CHARLIE);
3198            assert_eq!(charlie_balance, 200);
3199
3200            System::assert_last_event(
3201                Event::AuthorDrawn {
3202                    author: ALICE,
3203                    backer: CHARLIE,
3204                    amount: 100,
3205                }
3206                .into(),
3207            );
3208        })
3209    }
3210
3211    #[test]
3212    fn release_fund_direct_err_bad_origin() {
3213        authors_test_ext().execute_with(|| {
3214            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
3215            initiate_key_and_set_balance_and_hold(&CHARLIE, INITIAL_BALANCE, STANDARD_HOLD)
3216                .unwrap();
3217
3218            System::set_block_number(10);
3219            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
3220
3221            System::set_block_number(20);
3222            assert_ok!(Pallet::back(
3223                RuntimeOrigin::signed(CHARLIE),
3224                FundingTarget::Direct(ALICE),
3225                100,
3226                FortitudeWrapper::Force,
3227                PrecisionWrapper::Exact
3228            ));
3229
3230            let current_hold = Pallet::get_hold(&ALICE).unwrap();
3231            assert_eq!(current_hold, 200);
3232            let backed_value = Pallet::backed_value(&ALICE).unwrap();
3233            assert_eq!(backed_value, 100);
3234            let backers_of = Pallet::backers_of(&ALICE).unwrap();
3235            assert_eq!(backers_of, vec![(Funder::Direct(CHARLIE), 100)]);
3236
3237            let charlie_balance = get_user_balance(&CHARLIE);
3238            assert_eq!(charlie_balance, 100);
3239
3240            System::set_block_number(25);
3241            assert_err!(
3242                Pallet::exit(RuntimeOrigin::root(), FundingTarget::Direct(ALICE)),
3243                DispatchError::BadOrigin
3244            );
3245        })
3246    }
3247
3248    #[test]
3249    fn release_fund_index_success() {
3250        authors_test_ext().execute_with(|| {
3251            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3252            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3253            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3254            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
3255            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
3256
3257            System::set_block_number(6);
3258            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3259
3260            System::set_block_number(8);
3261            Pallet::fund(
3262                &ALICE,
3263                &Funder::Direct(CHARLIE),
3264                STANDARD_VALUE,
3265                Precision::Exact,
3266                Fortitude::Force,
3267            )
3268            .unwrap();
3269
3270            System::set_block_number(12);
3271            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
3272
3273            System::set_block_number(15);
3274            Pallet::fund(
3275                &BOB,
3276                &Funder::Direct(ALAN),
3277                LARGE_VALUE,
3278                Precision::Exact,
3279                Fortitude::Force,
3280            )
3281            .unwrap();
3282
3283            let alice_digest = gen_author_digest(&ALICE).unwrap();
3284            let bob_digest = gen_author_digest(&BOB).unwrap();
3285            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
3286
3287            prepare_and_initiate_index(MIKE, FUNDING.into(), &entries, INDEX_DIGEST).unwrap();
3288
3289            let by = Funder::Index {
3290                digest: INDEX_DIGEST,
3291                backer: MIKE,
3292            };
3293
3294            Pallet::fund(&ALICE, &by, LARGE_VALUE, Precision::Exact, Fortitude::Force).unwrap();
3295
3296            let total_backing = Pallet::total_backing();
3297            assert_eq!(total_backing, 250);
3298            let alice_backed_value = Pallet::backed_value(&ALICE).unwrap();
3299            assert_eq!(alice_backed_value, 110); // 50 (existing) + 60 (through index as ALICE share is 60 )
3300            let bob_backed_value = Pallet::backed_value(&BOB).unwrap();
3301            assert_eq!(bob_backed_value, 140); // 100 (existing) + 40 (through index as BOB share is 40 )
3302
3303            let author_funders = AuthorFunders::get((ALICE, MIKE)).unwrap();
3304            assert_eq!(author_funders, by);
3305
3306            assert_ok!(Pallet::exit(
3307                RuntimeOrigin::signed(MIKE),
3308                FundingTarget::Index(INDEX_DIGEST)
3309            ));
3310
3311            let total_backing = Pallet::total_backing();
3312            assert_eq!(total_backing, 150);
3313            let alice_backed_value = Pallet::backed_value(&ALICE).unwrap();
3314            assert_eq!(alice_backed_value, 50); // 50 (existing) - 60 (through index as ALICE share is 60 )
3315            let bob_backed_value = Pallet::backed_value(&BOB).unwrap();
3316            assert_eq!(bob_backed_value, 100); // 100 (existing) - 40 (through index as BOB share is 40 )
3317
3318            assert!(!AuthorFunders::contains_key((ALICE, MIKE)));
3319
3320            let mike_balance = get_user_balance(&MIKE);
3321            assert_eq!(mike_balance, 200);
3322        })
3323    }
3324
3325    #[test]
3326    fn release_fund_pool_success() {
3327        authors_test_ext().execute_with(|| {
3328            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3329            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3330            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3331            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
3332            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
3333
3334            System::set_block_number(6);
3335            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3336
3337            System::set_block_number(8);
3338            Pallet::fund(
3339                &ALICE,
3340                &Funder::Direct(CHARLIE),
3341                STANDARD_VALUE,
3342                Precision::Exact,
3343                Fortitude::Force,
3344            )
3345            .unwrap();
3346
3347            System::set_block_number(12);
3348            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
3349
3350            System::set_block_number(15);
3351            Pallet::fund(
3352                &BOB,
3353                &Funder::Direct(ALAN),
3354                LARGE_VALUE,
3355                Precision::Exact,
3356                Fortitude::Force,
3357            )
3358            .unwrap();
3359
3360            let alice_digest = gen_author_digest(&ALICE).unwrap();
3361            let bob_digest = gen_author_digest(&BOB).unwrap();
3362            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
3363
3364            prepare_and_initiate_pool(
3365                ALAN,
3366                FUNDING.into(),
3367                &entries,
3368                INDEX_DIGEST,
3369                POOL_DIGEST,
3370                Perbill::from_percent(5),
3371            )
3372            .unwrap();
3373
3374            let by = Funder::Pool {
3375                digest: POOL_DIGEST,
3376                backer: MIKE,
3377            };
3378
3379            Pallet::fund(&ALICE, &by, LARGE_VALUE, Precision::Exact, Fortitude::Force).unwrap();
3380
3381            let total_backing = Pallet::total_backing();
3382            assert_eq!(total_backing, 250);
3383            let alice_backed_value = Pallet::backed_value(&ALICE).unwrap();
3384            assert_eq!(alice_backed_value, 110); // 50 (existing) + 60 (through pool as ALICE share is 60 )
3385            let bob_backed_value = Pallet::backed_value(&BOB).unwrap();
3386            assert_eq!(bob_backed_value, 140); // 100 (existing) + 40 (through pool as BOB share is 40 )
3387
3388            let author_funders = AuthorFunders::get((ALICE, MIKE)).unwrap();
3389            assert_eq!(author_funders, by);
3390            let author_funders = AuthorFunders::get((BOB, MIKE)).unwrap();
3391            assert_eq!(author_funders, by);
3392
3393            assert_ok!(Pallet::exit(
3394                RuntimeOrigin::signed(MIKE),
3395                FundingTarget::Pool(POOL_DIGEST)
3396            ));
3397
3398            let total_backing = Pallet::total_backing();
3399            assert_eq!(total_backing, 150);
3400            let alice_backed_value = Pallet::backed_value(&ALICE).unwrap();
3401            assert_eq!(alice_backed_value, 50); // 50 (existing) - 60 (through index as ALICE share is 60 )
3402            let bob_backed_value = Pallet::backed_value(&BOB).unwrap();
3403            assert_eq!(bob_backed_value, 100); // 100 (existing) - 40 (through index as BOB share is 40 )
3404
3405            assert!(!AuthorFunders::contains_key((ALICE, MIKE)));
3406
3407            let mike_balance = get_user_balance(&MIKE);
3408            assert_eq!(mike_balance, 195); // 100 (existing) + 100 (backed) - 5 (commission)
3409            let alan_balance = get_user_balance(&ALAN);
3410            assert_eq!(alan_balance, 105); // 100 (existing) + 5 (commission)
3411        })
3412    }
3413
3414    #[cfg(feature = "dev")]
3415    #[test]
3416    fn check_fund_direct_success() {
3417        authors_test_ext().execute_with(|| {
3418            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
3419            initiate_key_and_set_balance_and_hold(&CHARLIE, INITIAL_BALANCE, STANDARD_HOLD)
3420                .unwrap();
3421
3422            System::set_block_number(10);
3423            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
3424
3425            System::set_block_number(20);
3426            assert_ok!(Pallet::back(
3427                RuntimeOrigin::signed(CHARLIE),
3428                FundingTarget::Direct(ALICE),
3429                100,
3430                FortitudeWrapper::Force,
3431                PrecisionWrapper::Exact
3432            ));
3433
3434            let current_hold = Pallet::get_hold(&ALICE).unwrap();
3435            assert_eq!(current_hold, 200);
3436            let backed_value = Pallet::backed_value(&ALICE).unwrap();
3437            assert_eq!(backed_value, 100);
3438            let backers_of = Pallet::backers_of(&ALICE).unwrap();
3439            assert_eq!(backers_of, vec![(Funder::Direct(CHARLIE), 100)]);
3440
3441            System::set_block_number(25);
3442            assert_ok!(Pallet::my_fund(
3443                RuntimeOrigin::signed(CHARLIE),
3444                FundingTarget::Direct(ALICE)
3445            ));
3446
3447            System::assert_last_event(
3448                Event::InspectAuthorFund {
3449                    author: ALICE,
3450                    backer: CHARLIE,
3451                    amount: 100,
3452                }
3453                .into(),
3454            );
3455        })
3456    }
3457
3458    #[cfg(feature = "dev")]
3459    #[test]
3460    fn check_fund_towards_index_success() {
3461        authors_test_ext().execute_with(|| {
3462            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3463            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3464            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3465            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
3466            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
3467            initiate_key_and_set_balance_and_hold(&NIX, LARGE_VALUE, LARGE_VALUE).unwrap();
3468
3469            System::set_block_number(6);
3470            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3471
3472            System::set_block_number(8);
3473            Pallet::fund(
3474                &ALICE,
3475                &Funder::Direct(CHARLIE),
3476                STANDARD_VALUE,
3477                Precision::Exact,
3478                Fortitude::Force,
3479            )
3480            .unwrap();
3481
3482            System::set_block_number(12);
3483            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
3484
3485            System::set_block_number(15);
3486            Pallet::fund(
3487                &BOB,
3488                &Funder::Direct(ALAN),
3489                LARGE_VALUE,
3490                Precision::Exact,
3491                Fortitude::Force,
3492            )
3493            .unwrap();
3494
3495            let alice_digest = gen_author_digest(&ALICE).unwrap();
3496            let bob_digest = gen_author_digest(&BOB).unwrap();
3497            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
3498
3499            prepare_and_initiate_index(MIKE, FUNDING.into(), &entries, INDEX_DIGEST).unwrap();
3500
3501            let by_mike = Funder::Index {
3502                digest: INDEX_DIGEST,
3503                backer: MIKE,
3504            };
3505
3506            Pallet::fund(
3507                &ALICE,
3508                &by_mike,
3509                LARGE_VALUE,
3510                Precision::Exact,
3511                Fortitude::Force,
3512            )
3513            .unwrap();
3514
3515            let by_nix = Funder::Index {
3516                digest: INDEX_DIGEST,
3517                backer: NIX,
3518            };
3519
3520            Pallet::fund(
3521                &ALICE,
3522                &by_nix,
3523                STANDARD_VALUE,
3524                Precision::Exact,
3525                Fortitude::Force,
3526            )
3527            .unwrap();
3528
3529            System::set_block_number(25);
3530            assert_ok!(Pallet::my_author_fund(
3531                RuntimeOrigin::signed(MIKE),
3532                ALICE,
3533                FundingTarget::Index(INDEX_DIGEST)
3534            ));
3535
3536            System::assert_last_event(
3537                Event::InspectFund {
3538                    author: ALICE,
3539                    funder: by_mike.clone(),
3540                    amount: 60,
3541                }
3542                .into(),
3543            );
3544
3545            assert_ok!(Pallet::my_author_fund(
3546                RuntimeOrigin::signed(MIKE),
3547                BOB,
3548                FundingTarget::Index(INDEX_DIGEST)
3549            ));
3550
3551            System::assert_last_event(
3552                Event::InspectFund {
3553                    author: BOB,
3554                    funder: by_mike,
3555                    amount: 40,
3556                }
3557                .into(),
3558            );
3559
3560            System::set_block_number(26);
3561            assert_ok!(Pallet::my_author_fund(
3562                RuntimeOrigin::signed(NIX),
3563                ALICE,
3564                FundingTarget::Index(INDEX_DIGEST)
3565            ));
3566
3567            System::assert_last_event(
3568                Event::InspectFund {
3569                    author: ALICE,
3570                    funder: by_nix.clone(),
3571                    amount: 30,
3572                }
3573                .into(),
3574            );
3575
3576            assert_ok!(Pallet::my_author_fund(
3577                RuntimeOrigin::signed(NIX),
3578                BOB,
3579                FundingTarget::Index(INDEX_DIGEST)
3580            ));
3581
3582            System::assert_last_event(
3583                Event::InspectFund {
3584                    author: BOB,
3585                    funder: by_nix,
3586                    amount: 20,
3587                }
3588                .into(),
3589            );
3590        })
3591    }
3592
3593    #[cfg(feature = "dev")]
3594    #[test]
3595    fn check_fund_index_success() {
3596        authors_test_ext().execute_with(|| {
3597            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3598            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3599            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3600            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
3601            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
3602            initiate_key_and_set_balance_and_hold(&NIX, LARGE_VALUE, LARGE_VALUE).unwrap();
3603
3604            System::set_block_number(6);
3605            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3606
3607            System::set_block_number(8);
3608            Pallet::fund(
3609                &ALICE,
3610                &Funder::Direct(CHARLIE),
3611                STANDARD_VALUE,
3612                Precision::Exact,
3613                Fortitude::Force,
3614            )
3615            .unwrap();
3616
3617            System::set_block_number(12);
3618            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
3619
3620            System::set_block_number(15);
3621            Pallet::fund(
3622                &BOB,
3623                &Funder::Direct(ALAN),
3624                LARGE_VALUE,
3625                Precision::Exact,
3626                Fortitude::Force,
3627            )
3628            .unwrap();
3629
3630            let alice_digest = gen_author_digest(&ALICE).unwrap();
3631            let bob_digest = gen_author_digest(&BOB).unwrap();
3632            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
3633
3634            prepare_and_initiate_index(MIKE, FUNDING.into(), &entries, INDEX_DIGEST).unwrap();
3635
3636            let by_mike = Funder::Index {
3637                digest: INDEX_DIGEST,
3638                backer: MIKE,
3639            };
3640
3641            Pallet::fund(
3642                &ALICE,
3643                &by_mike,
3644                LARGE_VALUE,
3645                Precision::Exact,
3646                Fortitude::Force,
3647            )
3648            .unwrap();
3649
3650            let by_nix = Funder::Index {
3651                digest: INDEX_DIGEST,
3652                backer: NIX,
3653            };
3654
3655            Pallet::fund(
3656                &ALICE,
3657                &by_nix,
3658                STANDARD_VALUE,
3659                Precision::Exact,
3660                Fortitude::Force,
3661            )
3662            .unwrap();
3663
3664            System::set_block_number(25);
3665            assert_ok!(Pallet::my_fund(
3666                RuntimeOrigin::signed(MIKE),
3667                FundingTarget::Index(INDEX_DIGEST)
3668            ));
3669
3670            System::assert_last_event(
3671                Event::InspectIndexFund {
3672                    index: INDEX_DIGEST,
3673                    backer: MIKE,
3674                    amount: 100,
3675                }
3676                .into(),
3677            );
3678
3679            System::set_block_number(26);
3680            assert_ok!(Pallet::my_fund(
3681                RuntimeOrigin::signed(NIX),
3682                FundingTarget::Index(INDEX_DIGEST)
3683            ));
3684
3685            System::assert_last_event(
3686                Event::InspectIndexFund {
3687                    index: INDEX_DIGEST,
3688                    backer: NIX,
3689                    amount: 50,
3690                }
3691                .into(),
3692            );
3693        })
3694    }
3695
3696    #[cfg(feature = "dev")]
3697    #[test]
3698    fn check_fund_towards_pool_success() {
3699        authors_test_ext().execute_with(|| {
3700            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3701            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3702            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3703            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
3704            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
3705            initiate_key_and_set_balance_and_hold(&NIX, LARGE_VALUE, LARGE_VALUE).unwrap();
3706
3707            System::set_block_number(6);
3708            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3709
3710            System::set_block_number(8);
3711            Pallet::fund(
3712                &ALICE,
3713                &Funder::Direct(CHARLIE),
3714                STANDARD_VALUE,
3715                Precision::Exact,
3716                Fortitude::Force,
3717            )
3718            .unwrap();
3719
3720            System::set_block_number(12);
3721            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
3722
3723            System::set_block_number(15);
3724            Pallet::fund(
3725                &BOB,
3726                &Funder::Direct(ALAN),
3727                LARGE_VALUE,
3728                Precision::Exact,
3729                Fortitude::Force,
3730            )
3731            .unwrap();
3732
3733            let alice_digest = gen_author_digest(&ALICE).unwrap();
3734            let bob_digest = gen_author_digest(&BOB).unwrap();
3735            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
3736
3737            prepare_and_initiate_pool(
3738                ALAN,
3739                FUNDING.into(),
3740                &entries,
3741                INDEX_DIGEST,
3742                POOL_DIGEST,
3743                Perbill::from_percent(5),
3744            )
3745            .unwrap();
3746
3747            let by_mike = Funder::Pool {
3748                digest: POOL_DIGEST,
3749                backer: MIKE,
3750            };
3751
3752            Pallet::fund(
3753                &ALICE,
3754                &by_mike,
3755                LARGE_VALUE,
3756                Precision::Exact,
3757                Fortitude::Force,
3758            )
3759            .unwrap();
3760
3761            let by_nix = Funder::Pool {
3762                digest: POOL_DIGEST,
3763                backer: NIX,
3764            };
3765
3766            Pallet::fund(
3767                &ALICE,
3768                &by_nix,
3769                STANDARD_VALUE,
3770                Precision::Exact,
3771                Fortitude::Force,
3772            )
3773            .unwrap();
3774
3775            System::set_block_number(25);
3776            assert_ok!(Pallet::my_author_fund(
3777                RuntimeOrigin::signed(MIKE),
3778                ALICE,
3779                FundingTarget::Pool(POOL_DIGEST)
3780            ));
3781
3782            System::assert_last_event(
3783                Event::InspectFund {
3784                    author: ALICE,
3785                    funder: by_mike.clone(),
3786                    amount: 60,
3787                }
3788                .into(),
3789            );
3790
3791            assert_ok!(Pallet::my_author_fund(
3792                RuntimeOrigin::signed(MIKE),
3793                BOB,
3794                FundingTarget::Pool(POOL_DIGEST)
3795            ));
3796
3797            System::assert_last_event(
3798                Event::InspectFund {
3799                    author: BOB,
3800                    funder: by_mike,
3801                    amount: 40,
3802                }
3803                .into(),
3804            );
3805
3806            System::set_block_number(26);
3807            assert_ok!(Pallet::my_author_fund(
3808                RuntimeOrigin::signed(NIX),
3809                ALICE,
3810                FundingTarget::Pool(POOL_DIGEST)
3811            ));
3812
3813            System::assert_last_event(
3814                Event::InspectFund {
3815                    author: ALICE,
3816                    funder: by_nix.clone(),
3817                    amount: 30,
3818                }
3819                .into(),
3820            );
3821
3822            assert_ok!(Pallet::my_author_fund(
3823                RuntimeOrigin::signed(NIX),
3824                BOB,
3825                FundingTarget::Pool(POOL_DIGEST)
3826            ));
3827
3828            System::assert_last_event(
3829                Event::InspectFund {
3830                    author: BOB,
3831                    funder: by_nix,
3832                    amount: 20,
3833                }
3834                .into(),
3835            );
3836        })
3837    }
3838
3839    #[cfg(feature = "dev")]
3840    #[test]
3841    fn check_fund_pool_success() {
3842        authors_test_ext().execute_with(|| {
3843            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3844            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3845            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3846            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
3847            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
3848            initiate_key_and_set_balance_and_hold(&NIX, LARGE_VALUE, LARGE_VALUE).unwrap();
3849
3850            System::set_block_number(6);
3851            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3852
3853            System::set_block_number(8);
3854            Pallet::fund(
3855                &ALICE,
3856                &Funder::Direct(CHARLIE),
3857                STANDARD_VALUE,
3858                Precision::Exact,
3859                Fortitude::Force,
3860            )
3861            .unwrap();
3862
3863            System::set_block_number(12);
3864            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
3865
3866            System::set_block_number(15);
3867            Pallet::fund(
3868                &BOB,
3869                &Funder::Direct(ALAN),
3870                LARGE_VALUE,
3871                Precision::Exact,
3872                Fortitude::Force,
3873            )
3874            .unwrap();
3875
3876            let alice_digest = gen_author_digest(&ALICE).unwrap();
3877            let bob_digest = gen_author_digest(&BOB).unwrap();
3878            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
3879
3880            prepare_and_initiate_pool(
3881                ALAN,
3882                FUNDING.into(),
3883                &entries,
3884                INDEX_DIGEST,
3885                POOL_DIGEST,
3886                Perbill::from_percent(5),
3887            )
3888            .unwrap();
3889
3890            let by_mike = Funder::Pool {
3891                digest: POOL_DIGEST,
3892                backer: MIKE,
3893            };
3894
3895            Pallet::fund(
3896                &ALICE,
3897                &by_mike,
3898                LARGE_VALUE,
3899                Precision::Exact,
3900                Fortitude::Force,
3901            )
3902            .unwrap();
3903
3904            let by_nix = Funder::Pool {
3905                digest: POOL_DIGEST,
3906                backer: NIX,
3907            };
3908
3909            Pallet::fund(
3910                &ALICE,
3911                &by_nix,
3912                STANDARD_VALUE,
3913                Precision::Exact,
3914                Fortitude::Force,
3915            )
3916            .unwrap();
3917
3918            System::set_block_number(25);
3919            assert_ok!(Pallet::my_fund(
3920                RuntimeOrigin::signed(MIKE),
3921                FundingTarget::Pool(POOL_DIGEST)
3922            ));
3923
3924            System::assert_last_event(
3925                Event::InspectPoolFund {
3926                    pool: POOL_DIGEST,
3927                    backer: MIKE,
3928                    amount: 100,
3929                }
3930                .into(),
3931            );
3932
3933            System::set_block_number(26);
3934            assert_ok!(Pallet::my_fund(
3935                RuntimeOrigin::signed(NIX),
3936                FundingTarget::Pool(POOL_DIGEST)
3937            ));
3938
3939            System::assert_last_event(
3940                Event::InspectPoolFund {
3941                    pool: POOL_DIGEST,
3942                    backer: NIX,
3943                    amount: 50,
3944                }
3945                .into(),
3946            );
3947        })
3948    }
3949
3950    #[cfg(feature = "dev")]
3951    #[test]
3952    fn upcoming_rewards_success() {
3953        authors_test_ext().execute_with(|| {
3954            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
3955
3956            System::set_block_number(10);
3957            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
3958
3959            System::set_block_number(12);
3960            let reward_a = 20;
3961            Pallet::reward(&ALICE, reward_a, Precision::Exact).unwrap();
3962
3963            System::set_block_number(13);
3964            let reward_b = 15;
3965            Pallet::reward(&ALICE, reward_b, Precision::Exact).unwrap();
3966
3967            assert_ok!(Pallet::shed_rewards(RuntimeOrigin::signed(ALICE)));
3968
3969            let expected_rewards = vec![(14, 20), (15, 15)];
3970            System::assert_last_event(
3971                Event::ScheduledRewards {
3972                    author: ALICE,
3973                    rewards: expected_rewards,
3974                }
3975                .into(),
3976            );
3977        })
3978    }
3979
3980    #[cfg(feature = "dev")]
3981    #[test]
3982    fn upcoming_rewards_err_reward_not_found() {
3983        authors_test_ext().execute_with(|| {
3984            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
3985
3986            System::set_block_number(10);
3987            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
3988
3989            assert_err!(
3990                Pallet::shed_rewards(RuntimeOrigin::signed(ALICE)),
3991                Error::RewardNotFound
3992            );
3993        })
3994    }
3995
3996    #[cfg(feature = "dev")]
3997    #[test]
3998    fn upcoming_penalties_success() {
3999        authors_test_ext().execute_with(|| {
4000            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
4001
4002            System::set_block_number(10);
4003            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
4004
4005            System::set_block_number(12);
4006            let penalty_a = Perbill::from_percent(10);
4007            Pallet::penalize(&ALICE, penalty_a).unwrap();
4008
4009            System::set_block_number(14);
4010            let penalty_b = Perbill::from_percent(5);
4011            Pallet::penalize(&ALICE, penalty_b).unwrap();
4012
4013            assert_ok!(Pallet::shed_penalties(RuntimeOrigin::signed(ALICE)));
4014
4015            let expected_penalties = vec![(16, penalty_a), (18, penalty_b)];
4016            System::assert_last_event(
4017                Event::ScheduledPenalties {
4018                    author: ALICE,
4019                    penalties: expected_penalties,
4020                }
4021                .into(),
4022            );
4023        })
4024    }
4025
4026    #[cfg(feature = "dev")]
4027    #[test]
4028    fn upcoming_penalties_err_penalty_not_found() {
4029        authors_test_ext().execute_with(|| {
4030            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
4031
4032            System::set_block_number(10);
4033            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
4034
4035            assert_err!(
4036                Pallet::shed_penalties(RuntimeOrigin::signed(ALICE)),
4037                Error::PenaltyNotFound
4038            );
4039        })
4040    }
4041
4042    #[test]
4043    fn end_probation_success() {
4044        authors_test_ext().execute_with(|| {
4045            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
4046
4047            System::set_block_number(10);
4048            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
4049
4050            let current_status = Pallet::get_status(&ALICE).unwrap();
4051            assert_eq!(current_status, AuthorStatus::Probation);
4052
4053            System::set_block_number(20);
4054            assert_ok!(Pallet::confirm(RuntimeOrigin::signed(ALICE)));
4055
4056            let current_status = Pallet::get_status(&ALICE).unwrap();
4057            assert_eq!(current_status, AuthorStatus::Active);
4058
4059            System::assert_last_event(
4060                Event::AuthorStatus {
4061                    author: ALICE,
4062                    status: AuthorStatus::Active,
4063                }
4064                .into(),
4065            );
4066        })
4067    }
4068
4069    #[test]
4070    fn end_probation_err_author_in_probation() {
4071        authors_test_ext().execute_with(|| {
4072            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
4073
4074            System::set_block_number(10);
4075            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
4076
4077            let current_status = Pallet::get_status(&ALICE).unwrap();
4078            assert_eq!(current_status, AuthorStatus::Probation);
4079
4080            System::set_block_number(15);
4081            assert_err!(
4082                Pallet::confirm(RuntimeOrigin::signed(ALICE)),
4083                Error::AuthorInProbation
4084            );
4085        })
4086    }
4087
4088    #[test]
4089    fn end_probation_err_author_is_unsafe() {
4090        authors_test_ext().execute_with(|| {
4091            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
4092
4093            System::set_block_number(10);
4094            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
4095
4096            let current_status = Pallet::get_status(&ALICE).unwrap();
4097            assert_eq!(current_status, AuthorStatus::Probation);
4098
4099            System::set_block_number(20);
4100
4101            Pallet::penalize(&ALICE, Perbill::from_percent(5)).unwrap();
4102
4103            assert_err!(
4104                Pallet::confirm(RuntimeOrigin::signed(ALICE)),
4105                Error::AuthorIsUnsafe
4106            );
4107        })
4108    }
4109
4110    #[test]
4111    fn end_probation_err_bad_origin() {
4112        authors_test_ext().execute_with(|| {
4113            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
4114
4115            System::set_block_number(10);
4116            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
4117
4118            let current_status = Pallet::get_status(&ALICE).unwrap();
4119            assert_eq!(current_status, AuthorStatus::Probation);
4120
4121            System::set_block_number(20);
4122            assert_err!(
4123                Pallet::confirm(RuntimeOrigin::root()),
4124                DispatchError::BadOrigin
4125            );
4126        })
4127    }
4128
4129    #[test]
4130    fn force_genesis_config_probation_period_success() {
4131        authors_test_ext().execute_with(|| {
4132            System::set_block_number(10);
4133            let before_probation_period = ProbationPeriod::get();
4134            assert_eq!(before_probation_period, 10);
4135            let new_probation_period = 15;
4136            assert_ok!(Pallet::force_genesis_config(
4137                RuntimeOrigin::root(),
4138                ForceGenesisConfig::ProbationPeriod(new_probation_period)
4139            ));
4140            let after_probation_period = ProbationPeriod::get();
4141            assert_eq!(after_probation_period, 15);
4142            System::assert_last_event(
4143                Event::GenesisConfigUpdated(ForceGenesisConfig::ProbationPeriod(
4144                    new_probation_period,
4145                ))
4146                .into(),
4147            );
4148        })
4149    }
4150
4151    #[test]
4152    fn force_genesis_config_probation_period_err_bad_origin() {
4153        authors_test_ext().execute_with(|| {
4154            let before_probation_period = ProbationPeriod::get();
4155            assert_eq!(before_probation_period, 10);
4156            let new_probation_period = 15;
4157            assert_err!(
4158                Pallet::force_genesis_config(
4159                    RuntimeOrigin::signed(ALICE),
4160                    ForceGenesisConfig::ProbationPeriod(new_probation_period)
4161                ),
4162                DispatchError::BadOrigin
4163            );
4164        })
4165    }
4166
4167    #[test]
4168    fn force_genesis_config_reduce_probation_by_success() {
4169        authors_test_ext().execute_with(|| {
4170            System::set_block_number(10);
4171            let before_reduce_probation_by = ReduceProbationBy::get();
4172            assert_eq!(before_reduce_probation_by, 1);
4173            let new_reduce_probation_by = 2;
4174            assert_ok!(Pallet::force_genesis_config(
4175                RuntimeOrigin::root(),
4176                ForceGenesisConfig::ReduceProbationBy(new_reduce_probation_by)
4177            ));
4178            let after_reduce_probation_by = ReduceProbationBy::get();
4179            assert_eq!(after_reduce_probation_by, 2);
4180            System::assert_last_event(
4181                Event::GenesisConfigUpdated(ForceGenesisConfig::ReduceProbationBy(
4182                    new_reduce_probation_by,
4183                ))
4184                .into(),
4185            );
4186        })
4187    }
4188
4189    #[test]
4190    fn force_genesis_config_reduce_probation_by_err_bad_origin() {
4191        authors_test_ext().execute_with(|| {
4192            let before_reduce_probation_by = ReduceProbationBy::get();
4193            assert_eq!(before_reduce_probation_by, 1);
4194            let new_reduce_probation_by = 2;
4195            assert_err!(
4196                Pallet::force_genesis_config(
4197                    RuntimeOrigin::signed(ALICE),
4198                    ForceGenesisConfig::ReduceProbationBy(new_reduce_probation_by)
4199                ),
4200                DispatchError::BadOrigin
4201            );
4202        })
4203    }
4204
4205    #[test]
4206    fn force_genesis_config_increase_probation_by_success() {
4207        authors_test_ext().execute_with(|| {
4208            System::set_block_number(10);
4209            let before_increase_probation_by = IncreaseProbationBy::get();
4210            assert_eq!(before_increase_probation_by, 1);
4211            let new_increase_probation_by = 2;
4212            assert_ok!(Pallet::force_genesis_config(
4213                RuntimeOrigin::root(),
4214                ForceGenesisConfig::IncreaseProbationBy(new_increase_probation_by)
4215            ));
4216            let after_increase_probation_by = IncreaseProbationBy::get();
4217            assert_eq!(after_increase_probation_by, 2);
4218            System::assert_last_event(
4219                Event::GenesisConfigUpdated(ForceGenesisConfig::IncreaseProbationBy(
4220                    new_increase_probation_by,
4221                ))
4222                .into(),
4223            );
4224        })
4225    }
4226
4227    #[test]
4228    fn force_genesis_config_increase_probation_by_err_bad_origin() {
4229        authors_test_ext().execute_with(|| {
4230            let before_increase_probation_by = IncreaseProbationBy::get();
4231            assert_eq!(before_increase_probation_by, 1);
4232            let new_increase_probation_by = 2;
4233            assert_err!(
4234                Pallet::force_genesis_config(
4235                    RuntimeOrigin::signed(ALICE),
4236                    ForceGenesisConfig::IncreaseProbationBy(new_increase_probation_by)
4237                ),
4238                DispatchError::BadOrigin
4239            );
4240        })
4241    }
4242
4243    #[test]
4244    fn force_genesis_config_rewards_buffer_success() {
4245        authors_test_ext().execute_with(|| {
4246            System::set_block_number(10);
4247            let before_rewards_buffer = RewardsBuffer::get();
4248            assert_eq!(before_rewards_buffer, 2);
4249            let new_rewards_buffer = 4;
4250            assert_ok!(Pallet::force_genesis_config(
4251                RuntimeOrigin::root(),
4252                ForceGenesisConfig::RewardsBuffer(new_rewards_buffer)
4253            ));
4254            let after_rewards_buffer = RewardsBuffer::get();
4255            assert_eq!(after_rewards_buffer, 4);
4256            System::assert_last_event(
4257                Event::GenesisConfigUpdated(ForceGenesisConfig::RewardsBuffer(new_rewards_buffer))
4258                    .into(),
4259            );
4260        })
4261    }
4262
4263    #[test]
4264    fn force_genesis_config_rewards_buffer_err_bad_origin() {
4265        authors_test_ext().execute_with(|| {
4266            let before_rewards_buffer = RewardsBuffer::get();
4267            assert_eq!(before_rewards_buffer, 2);
4268            let new_rewards_buffer = 4;
4269            assert_err!(
4270                Pallet::force_genesis_config(
4271                    RuntimeOrigin::signed(ALICE),
4272                    ForceGenesisConfig::RewardsBuffer(new_rewards_buffer)
4273                ),
4274                DispatchError::BadOrigin
4275            );
4276        })
4277    }
4278
4279    #[test]
4280    fn force_genesis_config_penalties_buffer_success() {
4281        authors_test_ext().execute_with(|| {
4282            System::set_block_number(10);
4283            let before_penalties_buffer = PenaltiesBuffer::get();
4284            assert_eq!(before_penalties_buffer, 4);
4285            let new_penalties_buffer = 5;
4286            assert_ok!(Pallet::force_genesis_config(
4287                RuntimeOrigin::root(),
4288                ForceGenesisConfig::PenaltiesBuffer(new_penalties_buffer)
4289            ));
4290            let after_penalties_buffer = PenaltiesBuffer::get();
4291            assert_eq!(after_penalties_buffer, 5);
4292            System::assert_last_event(
4293                Event::GenesisConfigUpdated(ForceGenesisConfig::PenaltiesBuffer(
4294                    new_penalties_buffer,
4295                ))
4296                .into(),
4297            );
4298        })
4299    }
4300
4301    #[test]
4302    fn force_genesis_config_penalties_buffer_err_bad_origin() {
4303        authors_test_ext().execute_with(|| {
4304            let before_penalties_buffer = PenaltiesBuffer::get();
4305            assert_eq!(before_penalties_buffer, 4);
4306            let new_penalties_buffer = 5;
4307            assert_err!(
4308                Pallet::force_genesis_config(
4309                    RuntimeOrigin::signed(ALICE),
4310                    ForceGenesisConfig::PenaltiesBuffer(new_penalties_buffer)
4311                ),
4312                DispatchError::BadOrigin
4313            );
4314        })
4315    }
4316
4317    #[test]
4318    fn force_genesis_config_max_elected_success() {
4319        authors_test_ext().execute_with(|| {
4320            System::set_block_number(6);
4321            let before_max_elected = MaxElected::get();
4322            assert_eq!(before_max_elected, 100);
4323            let new_max_elected = 75;
4324            assert_ok!(Pallet::force_genesis_config(
4325                RuntimeOrigin::root(),
4326                ForceGenesisConfig::MaxElected(new_max_elected)
4327            ));
4328            let after_max_elected = MaxElected::get();
4329            assert_eq!(after_max_elected, 75);
4330            System::assert_last_event(
4331                Event::GenesisConfigUpdated(ForceGenesisConfig::MaxElected(new_max_elected)).into(),
4332            );
4333        })
4334    }
4335
4336    #[test]
4337    fn force_genesis_config_max_elected_err_bad_origin() {
4338        authors_test_ext().execute_with(|| {
4339            let before_max_elected = MaxElected::get();
4340            assert_eq!(before_max_elected, 100);
4341            let new_max_elected = 75;
4342            assert_err!(
4343                Pallet::force_genesis_config(
4344                    RuntimeOrigin::signed(ALICE),
4345                    ForceGenesisConfig::MaxElected(new_max_elected)
4346                ),
4347                DispatchError::BadOrigin
4348            );
4349        })
4350    }
4351
4352    #[test]
4353    fn force_genesis_config_min_elected_success() {
4354        authors_test_ext().execute_with(|| {
4355            System::set_block_number(10);
4356            let before_min_elected = MinElected::get();
4357            assert_eq!(before_min_elected, 6);
4358            let new_min_elected = 15;
4359            assert_ok!(Pallet::force_genesis_config(
4360                RuntimeOrigin::root(),
4361                ForceGenesisConfig::MinElected(new_min_elected)
4362            ));
4363            let after_min_elected = MinElected::get();
4364            assert_eq!(after_min_elected, 15);
4365            System::assert_last_event(
4366                Event::GenesisConfigUpdated(ForceGenesisConfig::MinElected(new_min_elected)).into(),
4367            );
4368        })
4369    }
4370
4371    #[test]
4372    fn force_genesis_config_min_elected_err_bad_origin() {
4373        authors_test_ext().execute_with(|| {
4374            let before_min_elected = MinElected::get();
4375            assert_eq!(before_min_elected, 6);
4376            let new_min_elected = 15;
4377            assert_err!(
4378                Pallet::force_genesis_config(
4379                    RuntimeOrigin::signed(ALICE),
4380                    ForceGenesisConfig::MinElected(new_min_elected)
4381                ),
4382                DispatchError::BadOrigin
4383            );
4384        })
4385    }
4386
4387    #[test]
4388    fn force_genesis_config_enforce_max_elected_success() {
4389        authors_test_ext().execute_with(|| {
4390            System::set_block_number(10);
4391            let before_enforce_max_elected = ForceMaxElected::get();
4392            assert_eq!(before_enforce_max_elected, false);
4393            let new_enforce_max_elected = true;
4394            assert_ok!(Pallet::force_genesis_config(
4395                RuntimeOrigin::root(),
4396                ForceGenesisConfig::EnforceMaxElected(new_enforce_max_elected)
4397            ));
4398            let after_enforce_max_elected = ForceMaxElected::get();
4399            assert_eq!(after_enforce_max_elected, true);
4400            System::assert_last_event(
4401                Event::GenesisConfigUpdated(ForceGenesisConfig::EnforceMaxElected(
4402                    new_enforce_max_elected,
4403                ))
4404                .into(),
4405            );
4406        })
4407    }
4408
4409    #[test]
4410    fn force_genesis_config_enforce_max_elected_err_bad_origin() {
4411        authors_test_ext().execute_with(|| {
4412            let before_enforce_max_elected = ForceMaxElected::get();
4413            assert_eq!(before_enforce_max_elected, false);
4414            let new_enforce_max_elected = true;
4415            assert_err!(
4416                Pallet::force_genesis_config(
4417                    RuntimeOrigin::signed(ALICE),
4418                    ForceGenesisConfig::EnforceMaxElected(new_enforce_max_elected)
4419                ),
4420                DispatchError::BadOrigin
4421            );
4422        })
4423    }
4424
4425    #[test]
4426    fn force_genesis_config_min_fund_success() {
4427        authors_test_ext().execute_with(|| {
4428            System::set_block_number(10);
4429            let before_min_fund = MinFund::get();
4430            assert_eq!(before_min_fund, 25);
4431            let new_min_fund = 50;
4432            assert_ok!(Pallet::force_genesis_config(
4433                RuntimeOrigin::root(),
4434                ForceGenesisConfig::MinFund(new_min_fund)
4435            ));
4436            let after_min_fund = MinFund::get();
4437            assert_eq!(after_min_fund, 50);
4438            System::assert_last_event(
4439                Event::GenesisConfigUpdated(ForceGenesisConfig::MinFund(new_min_fund)).into(),
4440            );
4441        })
4442    }
4443
4444    #[test]
4445    fn force_genesis_config_min_fund_err_bad_origin() {
4446        authors_test_ext().execute_with(|| {
4447            let before_min_fund = MinFund::get();
4448            assert_eq!(before_min_fund, 25);
4449            let new_min_fund = 50;
4450            assert_err!(
4451                Pallet::force_genesis_config(
4452                    RuntimeOrigin::signed(ALICE),
4453                    ForceGenesisConfig::MinFund(new_min_fund)
4454                ),
4455                DispatchError::BadOrigin
4456            );
4457        })
4458    }
4459
4460    #[test]
4461    fn force_genesis_config_max_exposure_success() {
4462        authors_test_ext().execute_with(|| {
4463            System::set_block_number(10);
4464            let before_max_exposure = MaxExposure::get();
4465            assert_eq!(before_max_exposure, 1000);
4466            let new_max_exposure = u64::MAX;
4467            assert_ok!(Pallet::force_genesis_config(
4468                RuntimeOrigin::root(),
4469                ForceGenesisConfig::MaxExposure(new_max_exposure)
4470            ));
4471            let after_max_exposure = MaxExposure::get();
4472            assert_eq!(after_max_exposure, u64::MAX);
4473            System::assert_last_event(
4474                Event::GenesisConfigUpdated(ForceGenesisConfig::MaxExposure(new_max_exposure))
4475                    .into(),
4476            );
4477        })
4478    }
4479
4480    #[test]
4481    fn force_genesis_config_max_exposure_err_bad_origin() {
4482        authors_test_ext().execute_with(|| {
4483            let before_max_exposure = MaxExposure::get();
4484            assert_eq!(before_max_exposure, 1000);
4485            let new_max_exposure = u64::MAX;
4486            assert_err!(
4487                Pallet::force_genesis_config(
4488                    RuntimeOrigin::signed(ALICE),
4489                    ForceGenesisConfig::MaxExposure(new_max_exposure)
4490                ),
4491                DispatchError::BadOrigin
4492            );
4493        })
4494    }
4495
4496    #[test]
4497    fn force_genesis_config_min_collateral_success() {
4498        authors_test_ext().execute_with(|| {
4499            System::set_block_number(10);
4500            let before_min_collateral = MinCollateral::get();
4501            assert_eq!(before_min_collateral, 50);
4502            let new_min_collateral = 100;
4503            assert_ok!(Pallet::force_genesis_config(
4504                RuntimeOrigin::root(),
4505                ForceGenesisConfig::MinCollateral(new_min_collateral)
4506            ));
4507            let after_min_collateral = MinCollateral::get();
4508            assert_eq!(after_min_collateral, 100);
4509            System::assert_last_event(
4510                Event::GenesisConfigUpdated(ForceGenesisConfig::MinCollateral(new_min_collateral))
4511                    .into(),
4512            );
4513        })
4514    }
4515
4516    #[test]
4517    fn force_genesis_config_min_collateral_err_bad_origin() {
4518        authors_test_ext().execute_with(|| {
4519            let before_min_collateral = MinCollateral::get();
4520            assert_eq!(before_min_collateral, 50);
4521            let new_min_collateral = 100;
4522            assert_err!(
4523                Pallet::force_genesis_config(
4524                    RuntimeOrigin::signed(ALICE),
4525                    ForceGenesisConfig::MinCollateral(new_min_collateral)
4526                ),
4527                DispatchError::BadOrigin
4528            );
4529        })
4530    }
4531
4532    #[test]
4533    fn force_genesis_config_enforces_invariants() {
4534        authors_test_ext().execute_with(|| {
4535            assert_err!(
4536                Pallet::force_genesis_config(
4537                    RuntimeOrigin::root(),
4538                    ForceGenesisConfig::MinCollateral(0)
4539                ),
4540                Error::NonZeroConfigRequired
4541            );
4542
4543            assert_err!(
4544                Pallet::force_genesis_config(
4545                    RuntimeOrigin::root(),
4546                    ForceGenesisConfig::MinElected(0)
4547                ),
4548                Error::NonZeroConfigRequired
4549            );
4550
4551            assert_err!(
4552                Pallet::force_genesis_config(
4553                    RuntimeOrigin::root(),
4554                    ForceGenesisConfig::MaxElected(0)
4555                ),
4556                Error::NonZeroConfigRequired
4557            );
4558
4559            assert_err!(
4560                Pallet::force_genesis_config(RuntimeOrigin::root(), ForceGenesisConfig::MinFund(0)),
4561                Error::NonZeroConfigRequired
4562            );
4563
4564            let max_exposure = MaxExposure::get();
4565            let min_fund = MinFund::get();
4566            assert_eq!(max_exposure, 1000);
4567            assert!(min_fund <= max_exposure);
4568            assert_err!(
4569                Pallet::force_genesis_config(
4570                    RuntimeOrigin::root(),
4571                    ForceGenesisConfig::MinFund(1001)
4572                ),
4573                Error::MinGreaterThanMax
4574            );
4575            assert_eq!(min_fund, 25);
4576            assert_err!(
4577                Pallet::force_genesis_config(
4578                    RuntimeOrigin::root(),
4579                    ForceGenesisConfig::MaxExposure(24)
4580                ),
4581                Error::MinGreaterThanMax
4582            );
4583
4584            let min_elected = MinElected::get();
4585            let max_elected = MaxElected::get();
4586            assert!(min_elected <= max_elected);
4587            assert_eq!(max_elected, 100);
4588            assert_err!(
4589                Pallet::force_genesis_config(
4590                    RuntimeOrigin::root(),
4591                    ForceGenesisConfig::MinElected(101)
4592                ),
4593                Error::MinGreaterThanMax
4594            );
4595            assert_eq!(min_elected, 6);
4596            assert_err!(
4597                Pallet::force_genesis_config(
4598                    RuntimeOrigin::root(),
4599                    ForceGenesisConfig::MaxElected(5)
4600                ),
4601                Error::MinGreaterThanMax
4602            );
4603        })
4604    }
4605
4606    #[test]
4607    fn create_index_success() {
4608        authors_test_ext().execute_with(|| {
4609            initiate_key_and_set_balance_and_hold(&ALICE, 500, 500).unwrap();
4610            initiate_key_and_set_balance_and_hold(&BOB, 500, 500).unwrap();
4611            initiate_key_and_set_balance_and_hold(&CHARLIE, 500, 500).unwrap();
4612            initiate_key_and_set_balance_and_hold(&MIKE, 500, 500).unwrap();
4613            initiate_key_and_set_balance_and_hold(&ALAN, 500, 500).unwrap();
4614            initiate_key_and_set_balance_and_hold(&NIX, 500, 500).unwrap();
4615
4616            Pallet::enroll(&ALICE, 250, Fortitude::Force).unwrap();
4617            Pallet::enroll(&BOB, 300, Fortitude::Force).unwrap();
4618
4619            Pallet::fund(
4620                &ALICE,
4621                &Funder::Direct(ALAN),
4622                100,
4623                Precision::Exact,
4624                Fortitude::Force,
4625            )
4626            .unwrap();
4627
4628            Pallet::fund(
4629                &BOB,
4630                &Funder::Direct(CHARLIE),
4631                200,
4632                Precision::Exact,
4633                Fortitude::Force,
4634            )
4635            .unwrap();
4636
4637            let alice_current_hold = Pallet::get_hold(&ALICE).unwrap();
4638            assert_eq!(alice_current_hold, 350); // collateral + backing
4639            let bob_current_hold = Pallet::get_hold(&BOB).unwrap();
4640            assert_eq!(bob_current_hold, 500); // collateral + backing
4641
4642            let entries = vec![(ALICE, 100), (BOB, 100)];
4643
4644            System::set_block_number(10);
4645            assert_ok!(Pallet::create_index(
4646                RuntimeOrigin::signed(MIKE),
4647                entries.clone()
4648            ));
4649
4650            let index_digest = assert_index_created_and_get_digest();
4651
4652            let index_reason = FreezeReason::AuthorFunding.into();
4653            assert_ok!(CommitAdapter::index_exists(&index_reason, &index_digest));
4654
4655            let author_reason = FreezeReason::AuthorCollateral.into();
4656            let alice_digest = CommitAdapter::get_commit_digest(&ALICE, &author_reason).unwrap();
4657            let bob_digest = CommitAdapter::get_commit_digest(&BOB, &author_reason).unwrap();
4658
4659            let mut actual_entries_and_shares =
4660                CommitAdapter::get_entries_shares(&index_reason, &index_digest).unwrap();
4661            let mut expected_entries_and_shares =
4662                vec![(alice_digest.clone(), 100), (bob_digest.clone(), 100)];
4663            actual_entries_and_shares.sort();
4664            expected_entries_and_shares.sort();
4665            assert_eq!(
4666                actual_entries_and_shares,
4667                expected_entries_and_shares
4668            );
4669
4670            let mut actual_entries_and_values =
4671                CommitAdapter::get_entries_value(&index_reason, &index_digest).unwrap();
4672            let mut expected_entries_and_values =
4673                vec![(alice_digest.clone(), 0), (bob_digest.clone(), 0)];
4674            actual_entries_and_values.sort();
4675            expected_entries_and_values.sort();
4676            assert_eq!(
4677                actual_entries_and_values,
4678                expected_entries_and_values
4679            );
4680
4681            let index_info = CommitAdapter::get_index(&index_reason, &index_digest).unwrap();
4682            assert_eq!(index_info.capital(), 200);
4683            assert_eq!(index_info.principal(), 0);
4684
4685            let fund_by = Funder::Index {
4686                digest: index_digest.clone(),
4687                backer: NIX,
4688            };
4689            Pallet::fund(&ALICE, &fund_by, 100, Precision::Exact, Fortitude::Force).unwrap();
4690
4691            let mut actual_entries_and_values =
4692                CommitAdapter::get_entries_value(&index_reason, &index_digest).unwrap();
4693            let mut expected_entries_and_values = vec![(alice_digest, 50), (bob_digest, 50)];
4694            actual_entries_and_values.sort();
4695            expected_entries_and_values.sort();
4696            assert_eq!(
4697                actual_entries_and_values,
4698                expected_entries_and_values
4699            );
4700
4701            let index_info = CommitAdapter::get_index(&index_reason, &index_digest).unwrap();
4702            assert_eq!(index_info.principal(), 100);
4703
4704            let alice_current_hold = Pallet::get_hold(&ALICE).unwrap();
4705            assert_eq!(alice_current_hold, 400); // collateral + backing + index fund
4706            let bob_current_hold = Pallet::get_hold(&BOB).unwrap();
4707            assert_eq!(bob_current_hold, 550); // collateral + backing + index fund
4708        })
4709    }
4710
4711    #[test]
4712    fn crate_pool_success() {
4713        authors_test_ext().execute_with(|| {
4714            initiate_key_and_set_balance_and_hold(&ALICE, 500, 500).unwrap();
4715            initiate_key_and_set_balance_and_hold(&BOB, 500, 500).unwrap();
4716            initiate_key_and_set_balance_and_hold(&CHARLIE, 500, 500).unwrap();
4717            initiate_key_and_set_balance_and_hold(&MIKE, 500, 500).unwrap();
4718            initiate_key_and_set_balance_and_hold(&ALAN, 500, 500).unwrap();
4719            initiate_key_and_set_balance_and_hold(&NIX, 500, 500).unwrap();
4720
4721            Pallet::enroll(&ALICE, 250, Fortitude::Force).unwrap();
4722            Pallet::enroll(&BOB, 300, Fortitude::Force).unwrap();
4723
4724            Pallet::fund(
4725                &ALICE,
4726                &Funder::Direct(ALAN),
4727                100,
4728                Precision::Exact,
4729                Fortitude::Force,
4730            )
4731            .unwrap();
4732
4733            Pallet::fund(
4734                &BOB,
4735                &Funder::Direct(CHARLIE),
4736                200,
4737                Precision::Exact,
4738                Fortitude::Force,
4739            )
4740            .unwrap();
4741
4742            let alice_current_hold = Pallet::get_hold(&ALICE).unwrap();
4743            assert_eq!(alice_current_hold, 350); // collateral + backing
4744            let bob_current_hold = Pallet::get_hold(&BOB).unwrap();
4745            assert_eq!(bob_current_hold, 500); // collateral + backing
4746
4747            let entries = vec![(ALICE, 100), (BOB, 100)];
4748
4749            System::set_block_number(10);
4750            assert_ok!(Pallet::create_index(
4751                RuntimeOrigin::signed(MIKE),
4752                entries.clone()
4753            ));
4754
4755            let index_digest = assert_index_created_and_get_digest();
4756
4757            let pool_reason = FreezeReason::AuthorFunding.into();
4758
4759            System::set_block_number(15);
4760            let commission = Perbill::from_percent(10);
4761            assert_ok!(Pallet::create_pool(
4762                RuntimeOrigin::signed(MIKE),
4763                index_digest.clone(),
4764                commission
4765            ));
4766
4767            let pool_digest = assert_pool_created_and_get_digest();
4768
4769            assert_ok!(CommitAdapter::pool_exists(&pool_reason, &pool_digest));
4770
4771            let pool_info = CommitAdapter::get_pool(&pool_reason, &pool_digest).unwrap();
4772            assert_eq!(pool_info.capital(), 200);
4773
4774            let author_reason = FreezeReason::AuthorCollateral.into();
4775            let alice_digest = CommitAdapter::get_commit_digest(&ALICE, &author_reason).unwrap();
4776            let bob_digest = CommitAdapter::get_commit_digest(&BOB, &author_reason).unwrap();
4777
4778            let mut actual_slots_and_shares =
4779                CommitAdapter::get_slots_shares(&pool_reason, &pool_digest).unwrap();
4780            let mut expected_entries_and_shares =
4781                vec![(alice_digest.clone(), 100), (bob_digest.clone(), 100)];
4782            actual_slots_and_shares.sort();
4783            expected_entries_and_shares.sort();
4784            assert_eq!(
4785                actual_slots_and_shares,
4786                expected_entries_and_shares
4787            );
4788
4789            let mut actual_entries_and_values =
4790                CommitAdapter::get_slots_value(&pool_reason, &pool_digest).unwrap();
4791            let mut expected_entries_and_values =
4792                vec![(alice_digest.clone(), 0), (bob_digest.clone(), 0)];
4793            actual_entries_and_values.sort();
4794            expected_entries_and_values.sort();
4795            assert_eq!(
4796                actual_entries_and_values,
4797                expected_entries_and_values
4798            );
4799        })
4800    }
4801
4802    #[test]
4803    fn transfer_pool_ownership_success() {
4804        authors_test_ext().execute_with(|| {
4805            initiate_key_and_set_balance_and_hold(&ALICE, 500, 500).unwrap();
4806            initiate_key_and_set_balance_and_hold(&BOB, 500, 500).unwrap();
4807            initiate_key_and_set_balance_and_hold(&CHARLIE, 500, 500).unwrap();
4808            initiate_key_and_set_balance_and_hold(&MIKE, 500, 500).unwrap();
4809            initiate_key_and_set_balance_and_hold(&ALAN, 500, 500).unwrap();
4810            initiate_key_and_set_balance_and_hold(&NIX, 500, 500).unwrap();
4811
4812            Pallet::enroll(&ALICE, 250, Fortitude::Force).unwrap();
4813            Pallet::enroll(&BOB, 300, Fortitude::Force).unwrap();
4814
4815            Pallet::fund(
4816                &ALICE,
4817                &Funder::Direct(ALAN),
4818                100,
4819                Precision::Exact,
4820                Fortitude::Force,
4821            )
4822            .unwrap();
4823
4824            Pallet::fund(
4825                &BOB,
4826                &Funder::Direct(CHARLIE),
4827                200,
4828                Precision::Exact,
4829                Fortitude::Force,
4830            )
4831            .unwrap();
4832
4833            let entries = vec![(ALICE, 100), (BOB, 100)];
4834
4835            System::set_block_number(10);
4836            assert_ok!(Pallet::create_index(
4837                RuntimeOrigin::signed(MIKE),
4838                entries.clone()
4839            ));
4840
4841            let index_digest = assert_index_created_and_get_digest();
4842
4843            let pool_reason = FreezeReason::AuthorFunding.into();
4844
4845            System::set_block_number(15);
4846            let commission = Perbill::from_percent(10);
4847            assert_ok!(Pallet::create_pool(
4848                RuntimeOrigin::signed(MIKE),
4849                index_digest.clone(),
4850                commission
4851            ));
4852
4853            let pool_digest = assert_pool_created_and_get_digest();
4854
4855            assert_ok!(CommitAdapter::pool_exists(&pool_reason, &pool_digest));
4856
4857            let current_manager = CommitAdapter::get_manager(&pool_reason, &pool_digest).unwrap();
4858            assert_eq!(current_manager, MIKE);
4859
4860            Pallet::transfer_pool(RuntimeOrigin::signed(MIKE), pool_digest.clone(), NIX).unwrap();
4861
4862            System::assert_last_event(
4863                Event::PoolManager {
4864                    digest: pool_digest.clone(),
4865                    manager: NIX,
4866                }
4867                .into(),
4868            );
4869
4870            let current_manager = CommitAdapter::get_manager(&pool_reason, &pool_digest).unwrap();
4871            assert_eq!(current_manager, NIX);
4872        })
4873    }
4874
4875    #[test]
4876    fn transfer_pool_ownership_err_invalid_pool_manager() {
4877        authors_test_ext().execute_with(|| {
4878            initiate_key_and_set_balance_and_hold(&ALICE, 500, 500).unwrap();
4879            initiate_key_and_set_balance_and_hold(&BOB, 500, 500).unwrap();
4880            initiate_key_and_set_balance_and_hold(&CHARLIE, 500, 500).unwrap();
4881            initiate_key_and_set_balance_and_hold(&MIKE, 500, 500).unwrap();
4882            initiate_key_and_set_balance_and_hold(&ALAN, 500, 500).unwrap();
4883            initiate_key_and_set_balance_and_hold(&NIX, 500, 500).unwrap();
4884
4885            Pallet::enroll(&ALICE, 250, Fortitude::Force).unwrap();
4886            Pallet::enroll(&BOB, 300, Fortitude::Force).unwrap();
4887
4888            Pallet::fund(
4889                &ALICE,
4890                &Funder::Direct(ALAN),
4891                100,
4892                Precision::Exact,
4893                Fortitude::Force,
4894            )
4895            .unwrap();
4896
4897            Pallet::fund(
4898                &BOB,
4899                &Funder::Direct(CHARLIE),
4900                200,
4901                Precision::Exact,
4902                Fortitude::Force,
4903            )
4904            .unwrap();
4905
4906            let entries = vec![(ALICE, 100), (BOB, 100)];
4907
4908            System::set_block_number(10);
4909            assert_ok!(Pallet::create_index(
4910                RuntimeOrigin::signed(MIKE),
4911                entries.clone()
4912            ));
4913
4914            let index_digest = assert_index_created_and_get_digest();
4915
4916            let pool_reason = FreezeReason::AuthorFunding.into();
4917
4918            System::set_block_number(15);
4919            let commission = Perbill::from_percent(10);
4920            assert_ok!(Pallet::create_pool(
4921                RuntimeOrigin::signed(MIKE),
4922                index_digest.clone(),
4923                commission
4924            ));
4925
4926            let pool_digest = assert_pool_created_and_get_digest();
4927
4928            assert_ok!(CommitAdapter::pool_exists(&pool_reason, &pool_digest));
4929
4930            let current_manager = CommitAdapter::get_manager(&pool_reason, &pool_digest).unwrap();
4931            assert_eq!(current_manager, MIKE);
4932
4933            assert_err!(
4934                Pallet::transfer_pool(RuntimeOrigin::signed(ALICE), pool_digest.clone(), NIX,),
4935                Error::InvalidPoolManager
4936            );
4937        })
4938    }
4939
4940    #[test]
4941    fn update_commission_success() {
4942        authors_test_ext().execute_with(|| {
4943            initiate_key_and_set_balance_and_hold(&ALICE, 500, 500).unwrap();
4944            initiate_key_and_set_balance_and_hold(&BOB, 500, 500).unwrap();
4945            initiate_key_and_set_balance_and_hold(&CHARLIE, 500, 500).unwrap();
4946            initiate_key_and_set_balance_and_hold(&MIKE, 500, 500).unwrap();
4947            initiate_key_and_set_balance_and_hold(&ALAN, 500, 500).unwrap();
4948            initiate_key_and_set_balance_and_hold(&NIX, 500, 500).unwrap();
4949
4950            Pallet::enroll(&ALICE, 250, Fortitude::Force).unwrap();
4951            Pallet::enroll(&BOB, 300, Fortitude::Force).unwrap();
4952
4953            Pallet::fund(
4954                &ALICE,
4955                &Funder::Direct(ALAN),
4956                100,
4957                Precision::Exact,
4958                Fortitude::Force,
4959            )
4960            .unwrap();
4961
4962            Pallet::fund(
4963                &BOB,
4964                &Funder::Direct(CHARLIE),
4965                200,
4966                Precision::Exact,
4967                Fortitude::Force,
4968            )
4969            .unwrap();
4970
4971            let entries = vec![(ALICE, 100), (BOB, 100)];
4972
4973            System::set_block_number(10);
4974            assert_ok!(Pallet::create_index(
4975                RuntimeOrigin::signed(MIKE),
4976                entries.clone()
4977            ));
4978
4979            let index_digest = assert_index_created_and_get_digest();
4980
4981            let pool_reason = FreezeReason::AuthorFunding.into();
4982
4983            System::set_block_number(15);
4984            let commission = Perbill::from_percent(10);
4985            assert_ok!(Pallet::create_pool(
4986                RuntimeOrigin::signed(MIKE),
4987                index_digest.clone(),
4988                commission
4989            ));
4990
4991            let pool_digest = assert_pool_created_and_get_digest();
4992
4993            assert_ok!(CommitAdapter::pool_exists(&pool_reason, &pool_digest));
4994
4995            let actual_commison =
4996                CommitAdapter::get_commission(&pool_reason, &pool_digest).unwrap();
4997            assert_eq!(actual_commison, commission);
4998
4999            let new_commission = Perbill::from_percent(5);
5000            System::set_block_number(20);
5001            Pallet::update_commission(RuntimeOrigin::signed(MIKE), index_digest, new_commission)
5002                .unwrap();
5003
5004            let new_pool_digest = assert_pool_created_and_get_digest();
5005
5006            #[cfg(feature = "dev")]
5007            {
5008                let slots = CommitAdapter::get_slots_shares(&pool_reason, &new_pool_digest).unwrap();
5009                System::assert_last_event(
5010                    Event::PoolCreated {
5011                        pool: new_pool_digest.clone(),
5012                        manager: MIKE,
5013                        commission: new_commission,
5014                        #[cfg(feature = "dev")]
5015                        slots: slots,
5016                    }
5017                    .into(),
5018                );
5019            }
5020
5021            #[cfg(not(feature = "dev"))]
5022            {
5023                System::assert_last_event(
5024                    Event::PoolCreated {
5025                        pool: new_pool_digest.clone(),
5026                        manager: MIKE,
5027                        commission: new_commission,
5028                    }
5029                    .into(),
5030                );
5031            }
5032
5033            let actual_commison =
5034                CommitAdapter::get_commission(&pool_reason, &new_pool_digest).unwrap();
5035            assert_eq!(actual_commison, new_commission);
5036        })
5037    }
5038
5039    #[test]
5040    fn update_entry_shares_success() {
5041        authors_test_ext().execute_with(|| {
5042            initiate_key_and_set_balance_and_hold(&ALICE, 500, 500).unwrap();
5043            initiate_key_and_set_balance_and_hold(&BOB, 500, 500).unwrap();
5044            initiate_key_and_set_balance_and_hold(&CHARLIE, 500, 500).unwrap();
5045            initiate_key_and_set_balance_and_hold(&MIKE, 500, 500).unwrap();
5046            initiate_key_and_set_balance_and_hold(&ALAN, 500, 500).unwrap();
5047            initiate_key_and_set_balance_and_hold(&NIX, 500, 500).unwrap();
5048
5049            Pallet::enroll(&ALICE, 250, Fortitude::Force).unwrap();
5050            Pallet::enroll(&BOB, 300, Fortitude::Force).unwrap();
5051
5052            Pallet::fund(
5053                &ALICE,
5054                &Funder::Direct(ALAN),
5055                100,
5056                Precision::Exact,
5057                Fortitude::Force,
5058            )
5059            .unwrap();
5060
5061            Pallet::fund(
5062                &BOB,
5063                &Funder::Direct(CHARLIE),
5064                200,
5065                Precision::Exact,
5066                Fortitude::Force,
5067            )
5068            .unwrap();
5069
5070            let entries = vec![(ALICE, 100), (BOB, 100)];
5071
5072            System::set_block_number(10);
5073            assert_ok!(Pallet::create_index(
5074                RuntimeOrigin::signed(MIKE),
5075                entries.clone()
5076            ));
5077
5078            let index_digest = assert_index_created_and_get_digest();
5079
5080            let index_reason = FreezeReason::AuthorFunding.into();
5081            assert_ok!(CommitAdapter::index_exists(&index_reason, &index_digest));
5082
5083            let author_reason = FreezeReason::AuthorCollateral.into();
5084            let alice_digest = CommitAdapter::get_commit_digest(&ALICE, &author_reason).unwrap();
5085            let bob_digest = CommitAdapter::get_commit_digest(&BOB, &author_reason).unwrap();
5086
5087            let index_info = CommitAdapter::get_index(&index_reason, &index_digest).unwrap();
5088            assert_eq!(index_info.capital(), 200);
5089            let mut actual_entries_and_shares =
5090                CommitAdapter::get_entries_shares(&index_reason, &index_digest).unwrap();
5091            let mut expected_entries_and_shares =
5092                vec![(alice_digest.clone(), 100), (bob_digest.clone(), 100)];
5093            actual_entries_and_shares.sort();
5094            expected_entries_and_shares.sort();
5095            assert_eq!(
5096                actual_entries_and_shares,
5097                expected_entries_and_shares
5098            );
5099
5100            let new_alice_shares = 25;
5101            assert_ok!(Pallet::update_entry_shares(
5102                RuntimeOrigin::signed(MIKE),
5103                index_digest,
5104                alice_digest.clone(),
5105                new_alice_shares,
5106            ));
5107
5108            let new_index_digest = assert_index_created_and_get_digest();
5109
5110            let index_info = CommitAdapter::get_index(&index_reason, &new_index_digest).unwrap();
5111            assert_eq!(index_info.capital(), 125);
5112            let mut actual_entries_and_shares =
5113                CommitAdapter::get_entries_shares(&index_reason, &new_index_digest).unwrap();
5114            let mut expected_entries_and_shares =
5115                vec![(alice_digest.clone(), 25), (bob_digest.clone(), 100)];
5116            actual_entries_and_shares.sort();
5117            expected_entries_and_shares.sort();
5118            assert_eq!(
5119                actual_entries_and_shares,
5120                expected_entries_and_shares
5121            );  
5122
5123            let new_bob_shares = 75;
5124            assert_ok!(Pallet::update_entry_shares(
5125                RuntimeOrigin::signed(MIKE),
5126                new_index_digest.clone(),
5127                bob_digest.clone(),
5128                new_bob_shares,
5129            ));
5130
5131            let new_index_digest = assert_index_created_and_get_digest();
5132
5133            let index_info = CommitAdapter::get_index(&index_reason, &new_index_digest).unwrap();
5134            assert_eq!(index_info.capital(), 100);
5135            let mut actual_entries_and_shares =
5136                CommitAdapter::get_entries_shares(&index_reason, &new_index_digest).unwrap();
5137            let mut expected_entries_and_shares =
5138                vec![(alice_digest.clone(), 25), (bob_digest.clone(), 75)];
5139            actual_entries_and_shares.sort();
5140            expected_entries_and_shares.sort();
5141            assert_eq!(
5142                actual_entries_and_shares,
5143                expected_entries_and_shares
5144            );
5145        })
5146    }
5147
5148    #[test]
5149    fn update_slot_shares_success() {
5150        authors_test_ext().execute_with(|| {
5151            initiate_key_and_set_balance_and_hold(&ALICE, 500, 500).unwrap();
5152            initiate_key_and_set_balance_and_hold(&BOB, 500, 500).unwrap();
5153            initiate_key_and_set_balance_and_hold(&CHARLIE, 500, 500).unwrap();
5154            initiate_key_and_set_balance_and_hold(&MIKE, 500, 500).unwrap();
5155            initiate_key_and_set_balance_and_hold(&ALAN, 500, 500).unwrap();
5156            initiate_key_and_set_balance_and_hold(&NIX, 500, 500).unwrap();
5157
5158            Pallet::enroll(&ALICE, 250, Fortitude::Force).unwrap();
5159            Pallet::enroll(&BOB, 300, Fortitude::Force).unwrap();
5160
5161            Pallet::fund(
5162                &ALICE,
5163                &Funder::Direct(ALAN),
5164                100,
5165                Precision::Exact,
5166                Fortitude::Force,
5167            )
5168            .unwrap();
5169
5170            Pallet::fund(
5171                &BOB,
5172                &Funder::Direct(CHARLIE),
5173                200,
5174                Precision::Exact,
5175                Fortitude::Force,
5176            )
5177            .unwrap();
5178
5179            let entries = vec![(ALICE, 100), (BOB, 100)];
5180
5181            System::set_block_number(10);
5182            assert_ok!(Pallet::create_index(
5183                RuntimeOrigin::signed(MIKE),
5184                entries.clone()
5185            ));
5186
5187            let index_digest = assert_index_created_and_get_digest();
5188
5189            let pool_reason = FreezeReason::AuthorFunding.into();
5190
5191            System::set_block_number(15);
5192            let commission = Perbill::from_percent(10);
5193            assert_ok!(Pallet::create_pool(
5194                RuntimeOrigin::signed(MIKE),
5195                index_digest.clone(),
5196                commission
5197            ));
5198
5199            let pool_digest = assert_pool_created_and_get_digest();
5200
5201            assert_ok!(CommitAdapter::pool_exists(&pool_reason, &pool_digest));
5202
5203            let author_reason = FreezeReason::AuthorCollateral.into();
5204            let alice_digest = CommitAdapter::get_commit_digest(&ALICE, &author_reason).unwrap();
5205            let bob_digest = CommitAdapter::get_commit_digest(&BOB, &author_reason).unwrap();
5206
5207            let pool_info = CommitAdapter::get_pool(&pool_reason, &pool_digest).unwrap();
5208            assert_eq!(pool_info.capital(), 200);
5209            let mut actual_slots_and_shares =
5210                CommitAdapter::get_slots_shares(&pool_reason, &pool_digest).unwrap();
5211            let mut expected_entries_and_shares =
5212                vec![(alice_digest.clone(), 100), (bob_digest.clone(), 100)];
5213            actual_slots_and_shares.sort();
5214            expected_entries_and_shares.sort();
5215            assert_eq!(
5216                actual_slots_and_shares,
5217                expected_entries_and_shares
5218            );
5219
5220            let new_alice_shares = 40;
5221            assert_ok!(Pallet::update_slot_shares(
5222                RuntimeOrigin::signed(MIKE),
5223                pool_digest.clone(),
5224                alice_digest.clone(),
5225                new_alice_shares
5226            ));
5227
5228            let pool_info = CommitAdapter::get_pool(&pool_reason, &pool_digest).unwrap();
5229            assert_eq!(pool_info.capital(), 140);
5230            let mut actual_slots_and_shares =
5231                CommitAdapter::get_slots_shares(&pool_reason, &pool_digest).unwrap();
5232            let mut expected_slots_and_shares =
5233                vec![(alice_digest.clone(), 40), (bob_digest.clone(), 100)];
5234            actual_slots_and_shares.sort();
5235            expected_slots_and_shares.sort();
5236            assert_eq!(
5237                actual_slots_and_shares,
5238                expected_slots_and_shares
5239            );
5240
5241            System::assert_last_event(
5242                Event::PoolSlotShare {
5243                    pool: pool_digest.clone(),
5244                    slots: (alice_digest.clone(), new_alice_shares),
5245                }
5246                .into(),
5247            );
5248
5249            let new_bob_shares = 60;
5250            assert_ok!(Pallet::update_slot_shares(
5251                RuntimeOrigin::signed(MIKE),
5252                pool_digest.clone(),
5253                bob_digest.clone(),
5254                new_bob_shares
5255            ));
5256
5257            let pool_info = CommitAdapter::get_pool(&pool_reason, &pool_digest).unwrap();
5258            assert_eq!(pool_info.capital(), 100);
5259            let mut actual_slots_and_shares =
5260                CommitAdapter::get_slots_shares(&pool_reason, &pool_digest).unwrap();
5261            let mut expected_slots_and_shares =
5262                vec![(alice_digest.clone(), 40), (bob_digest.clone(), 60)];
5263            actual_slots_and_shares.sort();
5264            expected_slots_and_shares.sort();
5265            assert_eq!(
5266                actual_slots_and_shares,
5267                expected_slots_and_shares
5268            );
5269
5270            System::assert_last_event(
5271                Event::PoolSlotShare {
5272                    pool: pool_digest,
5273                    slots: (bob_digest, new_bob_shares),
5274                }
5275                .into(),
5276            );
5277        })
5278    }
5279
5280    #[test]
5281    fn update_slot_shares_err_invalid_pool_manager() {
5282        authors_test_ext().execute_with(|| {
5283            initiate_key_and_set_balance_and_hold(&ALICE, 500, 500).unwrap();
5284            initiate_key_and_set_balance_and_hold(&BOB, 500, 500).unwrap();
5285            initiate_key_and_set_balance_and_hold(&CHARLIE, 500, 500).unwrap();
5286            initiate_key_and_set_balance_and_hold(&MIKE, 500, 500).unwrap();
5287            initiate_key_and_set_balance_and_hold(&ALAN, 500, 500).unwrap();
5288            initiate_key_and_set_balance_and_hold(&NIX, 500, 500).unwrap();
5289
5290            Pallet::enroll(&ALICE, 250, Fortitude::Force).unwrap();
5291            Pallet::enroll(&BOB, 300, Fortitude::Force).unwrap();
5292
5293            Pallet::fund(
5294                &ALICE,
5295                &Funder::Direct(ALAN),
5296                100,
5297                Precision::Exact,
5298                Fortitude::Force,
5299            )
5300            .unwrap();
5301
5302            Pallet::fund(
5303                &BOB,
5304                &Funder::Direct(CHARLIE),
5305                200,
5306                Precision::Exact,
5307                Fortitude::Force,
5308            )
5309            .unwrap();
5310
5311            let entries = vec![(ALICE, 100), (BOB, 100)];
5312
5313            System::set_block_number(10);
5314            assert_ok!(Pallet::create_index(
5315                RuntimeOrigin::signed(MIKE),
5316                entries.clone()
5317            ));
5318
5319            let index_digest = assert_index_created_and_get_digest();
5320
5321            let pool_reason = FreezeReason::AuthorFunding.into();
5322
5323            System::set_block_number(15);
5324            let commission = Perbill::from_percent(10);
5325            assert_ok!(Pallet::create_pool(
5326                RuntimeOrigin::signed(MIKE),
5327                index_digest.clone(),
5328                commission
5329            ));
5330
5331            let pool_digest = assert_pool_created_and_get_digest();
5332
5333            assert_ok!(CommitAdapter::pool_exists(&pool_reason, &pool_digest));
5334
5335            let author_reason = FreezeReason::AuthorCollateral.into();
5336            let alice_digest = CommitAdapter::get_commit_digest(&ALICE, &author_reason).unwrap();
5337            let bob_digest = CommitAdapter::get_commit_digest(&BOB, &author_reason).unwrap();
5338
5339            let pool_info = CommitAdapter::get_pool(&pool_reason, &pool_digest).unwrap();
5340            assert_eq!(pool_info.capital(), 200);
5341            let mut actual_slots_and_shares =
5342                CommitAdapter::get_slots_shares(&pool_reason, &pool_digest).unwrap();
5343            let mut expected_entries_and_shares =
5344                vec![(alice_digest.clone(), 100), (bob_digest.clone(), 100)];
5345            actual_slots_and_shares.sort();
5346            expected_entries_and_shares.sort();               
5347            assert_eq!(
5348                actual_slots_and_shares,
5349                expected_entries_and_shares
5350            );
5351
5352            let new_alice_shares = 40;
5353            assert_err!(
5354                Pallet::update_slot_shares(
5355                    RuntimeOrigin::signed(CHARLIE),
5356                    pool_digest.clone(),
5357                    alice_digest.clone(),
5358                    new_alice_shares
5359                ),
5360                Error::InvalidPoolManager
5361            );
5362        })
5363    }
5364
5365    #[cfg(feature = "dev")]
5366    #[test]
5367    fn check_direct_funds_towards_author_success() {
5368        authors_test_ext().execute_with(|| {
5369            initiate_key_and_set_balance_and_hold(&ALICE, 500, 500).unwrap();
5370            initiate_key_and_set_balance_and_hold(&BOB, 500, 500).unwrap();
5371
5372            Pallet::enroll(&ALICE, 350, Fortitude::Force).unwrap();
5373
5374            Pallet::fund(
5375                &ALICE,
5376                &Funder::Direct(BOB),
5377                250,
5378                Precision::Exact,
5379                Fortitude::Force,
5380            )
5381            .unwrap();
5382
5383            System::set_block_number(10);
5384            Pallet::my_author_fund(
5385                RuntimeOrigin::signed(BOB),
5386                ALICE,
5387                FundingTarget::Direct(ALICE),
5388            )
5389            .unwrap();
5390
5391            System::assert_last_event(
5392                Event::InspectFund {
5393                    author: ALICE,
5394                    funder: Funder::Direct(BOB),
5395                    amount: 250,
5396                }
5397                .into(),
5398            );
5399        })
5400    }
5401
5402    // ===============================================================================
5403    // ````````````````````````````````` PUBLIC APIS `````````````````````````````````
5404    // ===============================================================================
5405
5406    #[test]
5407    fn fetch_collateral_success() {
5408        authors_test_ext().execute_with(|| {
5409            initiate_key_and_set_balance_and_hold(&ALICE, 500, 500).unwrap();
5410            initiate_key_and_set_balance_and_hold(&BOB, 500, 500).unwrap();
5411
5412            Pallet::enroll(&ALICE, 135, Fortitude::Force).unwrap();
5413            Pallet::enroll(&BOB, 425, Fortitude::Force).unwrap();
5414
5415            let alice_collateral = Pallet::fetch_collateral(ALICE).unwrap();
5416            let bob_collateral = Pallet::fetch_collateral(BOB).unwrap();
5417
5418            assert_eq!(alice_collateral, 135);
5419            assert_eq!(bob_collateral, 425);
5420        })
5421    }
5422
5423    #[test]
5424    fn inspect_fund_direct_success() {
5425        authors_test_ext().execute_with(|| {
5426            initiate_key_and_set_balance_and_hold(&ALICE, INITIAL_BALANCE, STANDARD_HOLD).unwrap();
5427            initiate_key_and_set_balance_and_hold(&CHARLIE, INITIAL_BALANCE, STANDARD_HOLD)
5428                .unwrap();
5429
5430            System::set_block_number(10);
5431            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
5432
5433            System::set_block_number(20);
5434            assert_ok!(Pallet::back(
5435                RuntimeOrigin::signed(CHARLIE),
5436                FundingTarget::Direct(ALICE),
5437                100,
5438                FortitudeWrapper::Force,
5439                PrecisionWrapper::Exact
5440            ));
5441
5442            let current_hold = Pallet::get_hold(&ALICE).unwrap();
5443            assert_eq!(current_hold, 200);
5444            let backed_value = Pallet::backed_value(&ALICE).unwrap();
5445            assert_eq!(backed_value, 100);
5446            let backers_of = Pallet::backers_of(&ALICE).unwrap();
5447            assert_eq!(backers_of, vec![(Funder::Direct(CHARLIE), 100)]);
5448
5449            System::set_block_number(25);
5450            let fund = Pallet::inspect_fund(CHARLIE, FundingTarget::Direct(ALICE)).unwrap();
5451            assert_eq!(fund, 100);
5452        })
5453    }
5454
5455    #[test]
5456    fn inspect_fund_towards_index_success() {
5457        authors_test_ext().execute_with(|| {
5458            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5459            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
5460            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
5461            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
5462            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
5463            initiate_key_and_set_balance_and_hold(&NIX, LARGE_VALUE, LARGE_VALUE).unwrap();
5464
5465            System::set_block_number(6);
5466            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5467
5468            System::set_block_number(8);
5469            Pallet::fund(
5470                &ALICE,
5471                &Funder::Direct(CHARLIE),
5472                STANDARD_VALUE,
5473                Precision::Exact,
5474                Fortitude::Force,
5475            )
5476            .unwrap();
5477
5478            System::set_block_number(12);
5479            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
5480
5481            System::set_block_number(15);
5482            Pallet::fund(
5483                &BOB,
5484                &Funder::Direct(ALAN),
5485                LARGE_VALUE,
5486                Precision::Exact,
5487                Fortitude::Force,
5488            )
5489            .unwrap();
5490
5491            let alice_digest = gen_author_digest(&ALICE).unwrap();
5492            let bob_digest = gen_author_digest(&BOB).unwrap();
5493            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
5494
5495            prepare_and_initiate_index(MIKE, FUNDING.into(), &entries, INDEX_DIGEST).unwrap();
5496
5497            let by_mike = Funder::Index {
5498                digest: INDEX_DIGEST,
5499                backer: MIKE,
5500            };
5501
5502            Pallet::fund(
5503                &ALICE,
5504                &by_mike,
5505                LARGE_VALUE,
5506                Precision::Exact,
5507                Fortitude::Force,
5508            )
5509            .unwrap();
5510
5511            let by_nix = Funder::Index {
5512                digest: INDEX_DIGEST,
5513                backer: NIX,
5514            };
5515
5516            Pallet::fund(
5517                &ALICE,
5518                &by_nix,
5519                STANDARD_VALUE,
5520                Precision::Exact,
5521                Fortitude::Force,
5522            )
5523            .unwrap();
5524
5525            System::set_block_number(25);
5526            let mike_alice_fund =
5527                Pallet::inspect_author_fund(MIKE, ALICE, FundingTarget::Index(INDEX_DIGEST))
5528                    .unwrap();
5529            assert_eq!(mike_alice_fund, 60);
5530
5531            let mike_bob_fund =
5532                Pallet::inspect_author_fund(MIKE, BOB, FundingTarget::Index(INDEX_DIGEST)).unwrap();
5533            assert_eq!(mike_bob_fund, 40);
5534
5535            let nix_alice_fund =
5536                Pallet::inspect_author_fund(NIX, ALICE, FundingTarget::Index(INDEX_DIGEST))
5537                    .unwrap();
5538            assert_eq!(nix_alice_fund, 30);
5539
5540            let nix_bob_fund =
5541                Pallet::inspect_author_fund(NIX, BOB, FundingTarget::Index(INDEX_DIGEST)).unwrap();
5542            assert_eq!(nix_bob_fund, 20);
5543        })
5544    }
5545
5546    #[test]
5547    fn inspect_fund_index_success() {
5548        authors_test_ext().execute_with(|| {
5549            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5550            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
5551            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
5552            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
5553            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
5554            initiate_key_and_set_balance_and_hold(&NIX, LARGE_VALUE, LARGE_VALUE).unwrap();
5555
5556            System::set_block_number(6);
5557            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5558
5559            System::set_block_number(8);
5560            Pallet::fund(
5561                &ALICE,
5562                &Funder::Direct(CHARLIE),
5563                STANDARD_VALUE,
5564                Precision::Exact,
5565                Fortitude::Force,
5566            )
5567            .unwrap();
5568
5569            System::set_block_number(12);
5570            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
5571
5572            System::set_block_number(15);
5573            Pallet::fund(
5574                &BOB,
5575                &Funder::Direct(ALAN),
5576                LARGE_VALUE,
5577                Precision::Exact,
5578                Fortitude::Force,
5579            )
5580            .unwrap();
5581
5582            let alice_digest = gen_author_digest(&ALICE).unwrap();
5583            let bob_digest = gen_author_digest(&BOB).unwrap();
5584            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
5585
5586            prepare_and_initiate_index(MIKE, FUNDING.into(), &entries, INDEX_DIGEST).unwrap();
5587
5588            let by_mike = Funder::Index {
5589                digest: INDEX_DIGEST,
5590                backer: MIKE,
5591            };
5592
5593            Pallet::fund(
5594                &ALICE,
5595                &by_mike,
5596                LARGE_VALUE,
5597                Precision::Exact,
5598                Fortitude::Force,
5599            )
5600            .unwrap();
5601
5602            let by_nix = Funder::Index {
5603                digest: INDEX_DIGEST,
5604                backer: NIX,
5605            };
5606
5607            Pallet::fund(
5608                &ALICE,
5609                &by_nix,
5610                STANDARD_VALUE,
5611                Precision::Exact,
5612                Fortitude::Force,
5613            )
5614            .unwrap();
5615
5616            let mike_index_fund =
5617                Pallet::inspect_fund(MIKE, FundingTarget::Index(INDEX_DIGEST)).unwrap();
5618            assert_eq!(mike_index_fund, 100);
5619
5620            let nix_index_fund =
5621                Pallet::inspect_fund(NIX, FundingTarget::Index(INDEX_DIGEST)).unwrap();
5622            assert_eq!(nix_index_fund, 50);
5623        })
5624    }
5625
5626    #[test]
5627    fn inspect_fund_towards_pool_success() {
5628        authors_test_ext().execute_with(|| {
5629            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5630            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
5631            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
5632            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
5633            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
5634            initiate_key_and_set_balance_and_hold(&NIX, LARGE_VALUE, LARGE_VALUE).unwrap();
5635
5636            System::set_block_number(6);
5637            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5638
5639            System::set_block_number(8);
5640            Pallet::fund(
5641                &ALICE,
5642                &Funder::Direct(CHARLIE),
5643                STANDARD_VALUE,
5644                Precision::Exact,
5645                Fortitude::Force,
5646            )
5647            .unwrap();
5648
5649            System::set_block_number(12);
5650            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
5651
5652            System::set_block_number(15);
5653            Pallet::fund(
5654                &BOB,
5655                &Funder::Direct(ALAN),
5656                LARGE_VALUE,
5657                Precision::Exact,
5658                Fortitude::Force,
5659            )
5660            .unwrap();
5661
5662            let alice_digest = gen_author_digest(&ALICE).unwrap();
5663            let bob_digest = gen_author_digest(&BOB).unwrap();
5664            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
5665
5666            prepare_and_initiate_pool(
5667                ALAN,
5668                FUNDING.into(),
5669                &entries,
5670                INDEX_DIGEST,
5671                POOL_DIGEST,
5672                Perbill::from_percent(5),
5673            )
5674            .unwrap();
5675
5676            let by_mike = Funder::Pool {
5677                digest: POOL_DIGEST,
5678                backer: MIKE,
5679            };
5680
5681            Pallet::fund(
5682                &ALICE,
5683                &by_mike,
5684                LARGE_VALUE,
5685                Precision::Exact,
5686                Fortitude::Force,
5687            )
5688            .unwrap();
5689
5690            let by_nix = Funder::Pool {
5691                digest: POOL_DIGEST,
5692                backer: NIX,
5693            };
5694
5695            Pallet::fund(
5696                &ALICE,
5697                &by_nix,
5698                STANDARD_VALUE,
5699                Precision::Exact,
5700                Fortitude::Force,
5701            )
5702            .unwrap();
5703
5704            let mike_alice_fund =
5705                Pallet::inspect_author_fund(MIKE, ALICE, FundingTarget::Pool(POOL_DIGEST)).unwrap();
5706            assert_eq!(mike_alice_fund, 60);
5707
5708            let mike_bob_fund =
5709                Pallet::inspect_author_fund(MIKE, BOB, FundingTarget::Pool(POOL_DIGEST)).unwrap();
5710            assert_eq!(mike_bob_fund, 40);
5711
5712            let nix_alice_fund =
5713                Pallet::inspect_author_fund(NIX, ALICE, FundingTarget::Pool(POOL_DIGEST)).unwrap();
5714            assert_eq!(nix_alice_fund, 30);
5715
5716            let nix_bob_fund =
5717                Pallet::inspect_author_fund(NIX, BOB, FundingTarget::Pool(POOL_DIGEST)).unwrap();
5718
5719            assert_eq!(nix_bob_fund, 20);
5720        })
5721    }
5722
5723    #[test]
5724    fn inspect_fund_pool_success() {
5725        authors_test_ext().execute_with(|| {
5726            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5727            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
5728            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
5729            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
5730            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
5731            initiate_key_and_set_balance_and_hold(&NIX, LARGE_VALUE, LARGE_VALUE).unwrap();
5732
5733            System::set_block_number(6);
5734            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5735
5736            System::set_block_number(8);
5737            Pallet::fund(
5738                &ALICE,
5739                &Funder::Direct(CHARLIE),
5740                STANDARD_VALUE,
5741                Precision::Exact,
5742                Fortitude::Force,
5743            )
5744            .unwrap();
5745
5746            System::set_block_number(12);
5747            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
5748
5749            System::set_block_number(15);
5750            Pallet::fund(
5751                &BOB,
5752                &Funder::Direct(ALAN),
5753                LARGE_VALUE,
5754                Precision::Exact,
5755                Fortitude::Force,
5756            )
5757            .unwrap();
5758
5759            let alice_digest = gen_author_digest(&ALICE).unwrap();
5760            let bob_digest = gen_author_digest(&BOB).unwrap();
5761            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
5762
5763            prepare_and_initiate_pool(
5764                ALAN,
5765                FUNDING.into(),
5766                &entries,
5767                INDEX_DIGEST,
5768                POOL_DIGEST,
5769                Perbill::from_percent(5),
5770            )
5771            .unwrap();
5772
5773            let by_mike = Funder::Pool {
5774                digest: POOL_DIGEST,
5775                backer: MIKE,
5776            };
5777
5778            Pallet::fund(
5779                &ALICE,
5780                &by_mike,
5781                LARGE_VALUE,
5782                Precision::Exact,
5783                Fortitude::Force,
5784            )
5785            .unwrap();
5786
5787            let by_nix = Funder::Pool {
5788                digest: POOL_DIGEST,
5789                backer: NIX,
5790            };
5791
5792            Pallet::fund(
5793                &ALICE,
5794                &by_nix,
5795                STANDARD_VALUE,
5796                Precision::Exact,
5797                Fortitude::Force,
5798            )
5799            .unwrap();
5800
5801            let mike_fund = Pallet::inspect_fund(MIKE, FundingTarget::Pool(POOL_DIGEST)).unwrap();
5802            assert_eq!(mike_fund, 100);
5803
5804            let nix_fund = Pallet::inspect_fund(NIX, FundingTarget::Pool(POOL_DIGEST)).unwrap();
5805            assert_eq!(nix_fund, 50);
5806        })
5807    }
5808}