pallet_authors/
roles.rs

1// SPDX-License-Identifier: MPL-2.0
2//
3// Part of Auguth Labs open-source softwares.
4// Built for the Substrate framework.
5//
6// This Source Code Form is subject to the terms of the Mozilla Public
7// License, v. 2.0. If a copy of the MPL was not distributed with this
8// file, You can obtain one at https://mozilla.org/MPL/2.0/.
9//
10// Copyright (c) 2026 Auguth Labs (OPC) Pvt Ltd, India
11
12// ===============================================================================
13// ``````````````````````````` AUTHORS ROLE MANAGEMENT ```````````````````````````
14// ===============================================================================
15
16//! Provides the **concrete runtime implementation** of the Author subsystem,
17//! translating abstract [`role traits`](frame_suite::roles) into operational
18//! logic governing authors' funding, rewards/penalties, and lifecycle
19//! within the runtime.
20//!
21//! ## Purpose
22//!
23//! Authors are key participants whose behavior, backing, and status directly affect
24//! **network security and correctness**. While the role traits define *what* is required
25//! of authors, this module defines *how* those requirements are enforced in a
26//! **safe, predictable, and auditable** manner.
27//!
28//! ## Role Implementations
29//!
30//! ### 1. Funding ([`FundRoles<Author>`])
31//!
32//! - Enables authors to operate with external economic backing, ensuring
33//!   **skin-in-the-game** and accountability.
34//! - Protects backers by enforcing correct fund allocation via digests and
35//!   commitment checks ([`Commitment`]).
36//! - Supports multiple funding models (direct, index-based, pooled) while
37//!   maintaining a **uniform, auditable interface** through the
38//! [`Config::CommitmentAdapter`].
39//!
40//! ### 2. Compensation ([`CompensateRoles<Author>`])
41//!
42//! - Aligns author incentives through rewards and penalties.
43//! - Enforces **temporal separation**: obligations are scheduled for future blocks
44//!   to ensure deterministic execution and prevent immediate exploitation.
45//! - Preserves **hold consistency**, ensuring collateral and external funds are always
46//!   accurately reflected in an author's total hold.
47//!
48//! ### 3. Probation & Permanence ([`RoleProbation<Author>`])
49//!
50//! - Prevents immediate permanence by enforcing a **probation window** for behavioral
51//!   observation.
52//! - Allows authors to be marked **temporarily unsafe** without irreversible removal,
53//!   enabling adaptive risk management.
54//! - Ensures clear promotion and revocation rules, preventing bypass of safety
55//!   invariants or accountability mechanisms.
56//!
57//! By concretely implementing these role traits, this module transforms abstract role
58//! definitions into **runtime-safe, economically secure, and auditable author governance**.
59
60// ===============================================================================
61// ``````````````````````````````````` IMPORTS ```````````````````````````````````
62// ===============================================================================
63
64// --- Local crate imports ---
65use crate::types::*;
66use crate::*;
67
68// --- FRAME Suite ---
69use frame_suite::{commitment::*, roles::*, Directive, Extent};
70
71// --- FRAME Support ---
72use frame_support::{
73    dispatch::DispatchResult,
74    ensure,
75    traits::tokens::{Fortitude, Precision},
76};
77
78// --- FRAME System ---
79use frame_system::pallet_prelude::*;
80
81// --- Substrate primitives ---
82use sp_core::Get;
83use sp_runtime::{
84    traits::{Bounded, CheckedAdd, Saturating, Zero},
85    DispatchError, PerThing, Perbill, Vec,
86};
87
88// ===============================================================================
89// ```````````````````````````````` ROLE MANAGER `````````````````````````````````
90// ===============================================================================
91
92/// Implements the [`RoleManager`] trait for the **Author subsystem**
93///
94/// Defines how authors behave as *role-bearing entities* within
95/// the runtime.  
96impl<T: Config> RoleManager<Author<T>> for Pallet<T> {
97    /// The possible states of an `Author` role.
98    ///
99    /// Variants:
100    /// - `Active`      : Author is actively participating.
101    /// - `Probation`   : Author is under review or subject to restrictions.
102    /// - `Resigned`    : Author voluntarily left the role.
103    ///
104    /// Note: There is no explicit suspension; penalties and probation are
105    /// applied to enforce decentralization.
106    type Status = AuthorStatus;
107
108    /// The meta-information of an `Author` role.
109    type Meta = AuthorInfo<T>;
110
111    /// The type representing the collateral or hold of an `Author` role.
112    type Asset = AuthorAsset<T>;
113
114    /// Timestamp type used for enrollment or status tracking.
115    type TimeStamp = BlockNumberFor<T>;
116
117    /// Checks whether the given `Author` exists in the system.
118    ///
119    /// Returns:
120    /// - `Ok(())` if the author exists.
121    /// - `Err(DispatchError)` otherwise.
122    fn role_exists(who: &Author<T>) -> DispatchResult {
123        ensure!(
124            AuthorsMap::<T>::contains_key(who),
125            Error::<T>::AuthorNotFound
126        );
127        Ok(())
128    }
129
130    /// Retrieves the meta-data of the given `Author` if available.
131    ///
132    /// Returns:
133    /// - `Ok(Meta)` if the author exists.
134    /// - `Err(DispatchError)` otherwise.
135    fn get_meta(who: &Author<T>) -> Result<Self::Meta, DispatchError> {
136        let info = AuthorsMap::<T>::get(who).ok_or(Error::<T>::AuthorNotFound)?;
137        Ok(info)
138    }
139
140    /// Retrieves the amount of collateral currently locked by an `Author` during
141    /// enrollment.
142    ///
143    /// This ensures real-time accuracy, reflecting any updates to the collateral.
144    ///
145    /// - Does not check author validaity, since commitment call reflects
146    /// if the pallet-gated collateral reason is funded by the given author
147    /// - Invariant: [`FreezeReason::AuthorCollateral`] must only be utilized by this pallet
148    /// - Invariant: Ensures the collateral must be non-zero, or else most of the functions will
149    /// fail.
150    ///
151    /// Returns the collateral value or a `DispatchError` otherwise.
152    fn get_collateral(who: &Author<T>) -> Result<Self::Asset, DispatchError> {
153        let reason = &FreezeReason::AuthorCollateral.into();
154        let value = T::CommitmentAdapter::get_commit_value(who, reason)?;
155        Ok(value)
156    }
157
158    /// Retrieves the amount of collateral currently locked by all `Author`s during
159    /// enrollment.
160    ///
161    /// This ensures real-time accuracy, reflecting any updates to any collaterals.
162    fn total_collateral() -> Self::Asset {
163        let reason = &FreezeReason::AuthorCollateral.into();
164        T::CommitmentAdapter::get_total_value(reason)
165    }
166
167    /// Returns the block number when the `Author` enrolled in the role.
168    ///
169    /// DispatchError otherwise
170    fn enroll_since(who: &Author<T>) -> Result<Self::TimeStamp, DispatchError> {
171        let info = Self::get_meta(who)?;
172        Ok(info.since)
173    }
174
175    /// Retrieves the current status of the given `Author`.
176    ///
177    /// Status can be one of:
178    /// - `Active`
179    /// - `Probation`
180    /// - `Resigned`
181    ///
182    /// DispatchError otherwise
183    fn get_status(who: &Author<T>) -> Result<Self::Status, DispatchError> {
184        let info = Self::get_meta(who)?;
185        Ok(info.status)
186    }
187
188    /// Returns the timestamp (block number) when the author's current status was last updated.
189    ///
190    /// This can be used to track how long an author has been in a specific state
191    /// (e.g., probation, active, resigned) and enforce time-based rules.
192    ///
193    /// DispatchError otherwise.
194    fn status_since(who: &Author<T>) -> Result<Self::TimeStamp, DispatchError> {
195        let info = Self::get_meta(who)?;
196        return Ok(info.status_since);
197    }
198
199    /// Updates the status of an author in a **safe, controlled way**.
200    ///
201    /// It doesn't mutate status directly, but enforces validations to proceed.
202    ///
203    /// DispatchError otherwise.
204    fn set_status(who: &Author<T>, status: Self::Status) -> DispatchResult {
205        let info = Self::get_meta(who)?;
206        let current_status = info.status;
207        match current_status {
208            // Current status: Active
209            AuthorStatus::Active => match status {
210                // No-op if status unchanged
211                AuthorStatus::Active => {}
212                // Try sending active author to probation
213                AuthorStatus::Probation => {
214                    Self::revoke_permanence(who)?;
215                }
216                // Trigger full resignation workflow
217                AuthorStatus::Resigned => {
218                    // But cannot return the regained asset
219                    // Ensure `resign` doesn't use `set_status`
220                    // to avoid indefinite recursion
221                    Self::resign(who)?;
222                }
223            },
224            // Current status: Probation
225            AuthorStatus::Probation => match status {
226                // No-op if unchanged
227                AuthorStatus::Probation => {}
228                // Cannot resign during probation
229                AuthorStatus::Resigned => return Err(Error::<T>::AuthorInProbation.into()),
230                AuthorStatus::Active => {
231                    Self::set_permanence(who)?;
232                }
233            },
234            // Current status: Resigned
235            AuthorStatus::Resigned => match status {
236                AuthorStatus::Active | AuthorStatus::Probation => {
237                    // Resigned authors cannot be reactivated via `set_status` directly,
238                    // only via `enroll` it can be done
239                    return Err(Error::<T>::AuthorResigned.into());
240                }
241                // No-op if unchanged
242                AuthorStatus::Resigned => {}
243            },
244        }
245        Self::on_status_update(who, &status);
246        Ok(())
247    }
248
249    /// Validates whether an `Author` can enroll with the given collateral.
250    ///
251    /// Checks include:
252    /// - If the status is `Resigned`, enrollment is allowed (re-entry).
253    /// - Ensures the provided collateral meets the minimum requirement.
254    ///
255    /// Returns:
256    /// - `Ok(())` if all checks pass.
257    /// - `Err(DispatchError) otheriwse.
258    fn can_enroll(who: &Author<T>, collateral: Self::Asset) -> DispatchResult {
259        // In case of re-enrollment by resigned authors
260        if Self::role_exists(who).is_ok() {
261            let status = Self::get_status(who);
262            debug_assert!(
263                status.is_ok(),
264                "author {:?} role-exists but status unavailable",
265                who
266            );
267            match status? {
268                AuthorStatus::Resigned => {
269                    // Resigned must not have any penalties (obligations)
270                    debug_assert!(
271                        Self::has_penalty(who).is_err(),
272                        "author {:?} resigned with penalty and attempting re-enrollment",
273                        who
274                    );
275
276                    // In this case, the author has regained his collateral
277                    // irrespective of rewards,
278                    // hence he cannot claim it but if there are funders
279                    // they can claim it.
280                    if Self::has_reward(who).is_ok() {
281                        return Err(Error::<T>::AuthorHasRewards.into());
282                    }
283                }
284                AuthorStatus::Active | AuthorStatus::Probation => {
285                    return Err(Error::<T>::AlreadyEnrolled.into())
286                }
287            }
288        }
289        let min_collateral = MinCollateral::<T>::get();
290        debug_assert!(
291            !min_collateral.is_zero(),
292            "`MinCollateral` must be greater than zero"
293        );
294        let available = T::CommitmentAdapter::available_funds(who);
295        // Ensure collateral funds are available
296        ensure!(!(available < collateral), Error::<T>::InadequateFunds);
297        // Ensure minimum collateral requirement is met
298        ensure!(
299            !(collateral < min_collateral),
300            Error::<T>::InadequateCollateral
301        );
302        Ok(())
303    }
304
305    /// Enrolls a new author with the specified collateral and the operation's
306    /// priviledge via `force`.
307    ///
308    /// Steps performed:
309    /// - Ensure the author is eligible and the collateral meets minimum requirements.
310    /// - Generate a unique digest/hash for this author's funding commitment.
311    /// - Lock the collateral using the commitment adapter.
312    /// - Register the author in storage and maintain lookup maps.
313    ///
314    /// For Resigned Authors Enrollment, their commitment-digest is reused.
315    ///
316    /// ## `force` Semantics
317    /// - [`Fortitude::Polite`]: Uses funds from the **commitment reserve**.
318    /// - [`Fortitude::Force`]: Uses funds from the user's **liquid balance**.
319    ///
320    /// Prefer `Polite` when collateral is pre-reserved; otherwise use `Force`.
321    ///
322    /// This operation will **never kill an account**, as guaranteed by the
323    /// commitment system.
324    ///
325    /// ## Errors
326    /// - Returns the actual amount of collateral successfully reserved.
327    /// - Returns a `DispatchError` if fails.
328    fn enroll(
329        who: &Author<T>,
330        collateral: Self::Asset,
331        force: Fortitude,
332    ) -> Result<Self::Asset, DispatchError> {
333        //  Validate enrollment eligibility,
334        // also checks for resigned authors
335        Self::can_enroll(who, collateral)?;
336
337        // Safe enrollment for resigned authors
338        let (meta, digest) = match Self::role_exists(who).is_ok() {
339            true => {
340                let meta = Self::get_meta(who);
341                debug_assert!(
342                    meta.is_ok(),
343                    "author {:?} role-exists but meta unavailable",
344                    who
345                );
346                let meta = meta?;
347                debug_assert!(
348                    AuthorStatus::Resigned == meta.status,
349                    "re-enroll tried for non-resigned author {:?}",
350                    who
351                );
352                let info = AuthorInfo::<T>::re_enroll(&meta);
353                // This may change if new feature of disincentivizing casual resignations
354                // are introduced
355                debug_assert!(
356                    meta.digest == info.digest,
357                    "resigned author {:?} re-enroll tried with new commit digest",
358                    who
359                );
360                let digest = meta.digest;
361                (info, digest)
362            }
363            false => {
364                // Generate a unique digest for this author's collateral commitment
365                let digest = T::CommitmentAdapter::gen_digest(who)
366                    .map_err(|_| Error::<T>::CannotGenerateCommitDigest)?;
367                let info = AuthorInfo::<T>::new(digest.clone());
368                (info, digest)
369            }
370        };
371
372        let reason = &FreezeReason::AuthorCollateral.into();
373
374        let limits = T::CommitmentAdapter::place_commit_limits(
375            who,
376            reason,
377            &digest,
378            &Directive::new(
379                Precision::Exact, // Enforce exact collateral placement
380                force,
381            ),
382        )?;
383
384        let actual = match limits.contains(collateral) {
385            true => {
386                // Place the collateral in the commitment system
387                T::CommitmentAdapter::place_commit(
388                    who,
389                    reason,
390                    &digest,
391                    collateral,
392                    &Directive::new(
393                        Precision::Exact, // Enforce exact collateral placement
394                        force,
395                    ),
396                )?
397            }
398            false => {
399                // Place the minimum-collateral in the commitment system
400                T::CommitmentAdapter::place_commit(
401                    who,
402                    reason,
403                    &digest,
404                    MinCollateral::<T>::get(), // Enforce minimum collateral placement
405                    &Directive::new(Precision::Exact, force),
406                )?
407            }
408        };
409
410        // Register the author in pallet storage
411        AuthorsMap::<T>::insert(who, &meta);
412
413        AuthorsDigest::<T>::insert(&digest, who);
414
415        Self::on_enroll(who, collateral);
416
417        // Return the amount of collateral actually reserved
418        Ok(actual)
419    }
420
421    /// Validates whether an `Author` can safely resign from the role.
422    ///
423    /// Checks include:
424    /// - Author in `Probation` cannot resign (must resolve probation first).
425    /// - Ensures there are no pending penalties.
426    /// - Ensures the author is not currently active in duties (cannot resign while active).
427    ///
428    /// Pending rewards are ignored, as its voluntary for author to resign before
429    /// receiving the rewards, whereas the backers are unaffected for receiving.
430    ///
431    /// Returns:
432    /// - `Ok(())` if all conditions are met.
433    /// - `Err(DispatchError) otherwise.
434    fn can_resign(who: &Author<T>) -> DispatchResult {
435        let status = Self::get_status(who)?;
436        // Find Non-Resignable Statuses
437        match status {
438            AuthorStatus::Probation => return Err(Error::<T>::AuthorInProbation.into()),
439            AuthorStatus::Resigned => return Err(Error::<T>::RedundantResignation.into()),
440            AuthorStatus::Active => {}
441        }
442        // Check for any pending penalties
443        if Self::has_penalty(who).is_ok() {
444            return Err(Error::<T>::AuthorHasPenalties.into());
445        }
446        // Ensure author is currently idle (not active)
447        if let Err(a) = T::ActivityProvider::is_idle(who) {
448            return Err(a.into());
449        };
450
451        Ok(())
452    }
453
454    /// Resigns an author, releasing collateral and updating status.
455    ///
456    /// Marks author's status as `Resigned` (so funders may withdraw their funds later).
457    ///
458    /// If an author's metadata is ever reaped, a *separate, safety-checked procedure*
459    /// MUST ensure that **all funders have fully withdrawn their commitments**.
460    /// Only once this invariant holds it is safe to issue a new digest and purge
461    /// the old entry from [`AuthorsDigest`] during re-enrollment.
462    ///
463    /// **This function does not perform those checks and MUST NOT be used for that purpose.**
464    ///
465    /// Returns the refunded collateral of the author. DispatchError otherwise.
466    fn resign(who: &Author<T>) -> Result<Self::Asset, DispatchError> {
467        // Ensure author can resign
468        Self::can_resign(who)?;
469
470        // Does not reaps the maps as its duty should live elsewhere for safety
471        AuthorsMap::<T>::mutate(who, |author| -> DispatchResult {
472            let info = author.as_mut();
473            debug_assert!(
474                info.is_some(),
475                "author {:?} can-resign without its author-info",
476                who
477            );
478            let info = info.ok_or(Error::<T>::AuthorNotFound)?;
479            let status = &mut info.status;
480            *status = AuthorStatus::Resigned;
481            Ok(())
482        })?;
483
484        // Only withdraw the collateral for the author
485        // Funders may withdraw at their own convenience
486        let reason = &FreezeReason::AuthorCollateral.into();
487
488        // Release the collateral the author provided.
489        let refund = T::CommitmentAdapter::resolve_commit(who, reason)?;
490
491        Self::on_resign(who, refund);
492        Ok(refund)
493    }
494
495    /// Increases the collateral for an Author with the specified collateral
496    /// and the operation's priviledge (`force`).
497    ///
498    /// If the existing collateral (before raising/adding) is lesser than the system enforced
499    /// minimum, this function uses [`Precision::Exact`] else [`Precision::BestEffort`].
500    ///
501    /// `force` determines the source of funds:
502    /// - [`Fortitude::Polite`]: commitment reserve
503    /// - [`Fortitude::Force`]: liquid balance
504    ///
505    /// Returns the actually raised collateral (not full collateral). DispatchError otherwise.
506    fn add_collateral(
507        who: &Author<T>,
508        collateral: Self::Asset,
509        force: Fortitude,
510    ) -> Result<Self::Asset, DispatchError> {
511        let exist_collateral = Self::get_collateral(who)?;
512        let reason = &FreezeReason::AuthorCollateral.into();
513        let minimum = MinCollateral::<T>::get();
514        debug_assert!(
515            !minimum.is_zero(),
516            "`MinCollateral` must be greater than zero"
517        );
518        let raised = match exist_collateral < minimum {
519            true => T::CommitmentAdapter::raise_commit(
520                who,
521                reason,
522                collateral,
523                &Directive::new(Precision::Exact, force),
524            )?,
525            false => T::CommitmentAdapter::raise_commit(
526                who,
527                reason,
528                collateral,
529                &Directive::new(Precision::BestEffort, force),
530            )?,
531        };
532        Self::on_add_collateral(who, raised);
533        Ok(raised)
534    }
535
536    /// Checks if the author is not defaulted (available).
537    ///
538    /// - Active or Probation authors are not considered defaulted (returns error).
539    /// - Resigned authors are treated as defaulted.
540    /// - Lesser Collateral will result in author being defaulted.
541    fn is_available(who: &Author<T>) -> DispatchResult {
542        let status = Self::get_status(who)?;
543        if status == AuthorStatus::Resigned {
544            return Err(Error::<T>::AuthorResigned.into());
545        }
546        let collateral = Self::get_collateral(who);
547        debug_assert!(
548            collateral.is_ok(),
549            "author {:?} with status exist without a collateral",
550            who
551        );
552        let min_collateral = MinCollateral::<T>::get();
553        debug_assert!(
554            !min_collateral.is_zero(),
555            "`MinCollateral` must be greater than zero"
556        );
557        if collateral? < min_collateral {
558            return Err(Error::<T>::AuthorNeedsMoreCollateral.into());
559        }
560        Ok(())
561    }
562
563    /// Hook invoked after an author is successfully enrolled.
564    ///
565    /// Emits [`Event::AuthorEnlisted`] if [`Config::EmitEvents`] is `true`.
566    fn on_enroll(who: &Author<T>, collateral: Self::Asset) {
567        if T::EmitEvents::get() {
568            Self::deposit_event(Event::<T>::AuthorEnlisted {
569                author: who.clone(),
570                collateral,
571            });
572        }
573    }
574
575    /// Hook invoked when an author resignation is processed.
576    ///
577    /// Emits [`Event::AuthorResigned`] if [`Config::EmitEvents`] is `true`.
578    fn on_resign(who: &Author<T>, released: Self::Asset) {
579        if T::EmitEvents::get() {
580            Self::deposit_event(Event::<T>::AuthorResigned {
581                author: who.clone(),
582                released,
583            });
584        }
585    }
586
587    /// Hook invoked after an author's collateral balance is incremented.
588    ///
589    /// Emits [`Event::AuthorCollateralRaised`] if [`Config::EmitEvents`] is `true`.
590    fn on_add_collateral(who: &Author<T>, raised: Self::Asset) {
591        if T::EmitEvents::get() {
592            Self::deposit_event(Event::<T>::AuthorCollateralRaised {
593                author: who.clone(),
594                raised,
595            });
596        }
597    }
598
599    /// Hook invoked after an author's status is mutated or updated.
600    ///
601    /// Emits [`Event::AuthorStatus`] if [`Config::EmitEvents`] is `true`.
602    fn on_status_update(who: &Author<T>, status: &Self::Status) {
603        if T::EmitEvents::get() {
604            Self::deposit_event(Event::<T>::AuthorStatus {
605                author: who.clone(),
606                status: status.clone(),
607            });
608        }
609    }
610}
611
612// ===============================================================================
613// ````````````````````````````````` FUND ROLES ``````````````````````````````````
614// ===============================================================================
615
616/// Implements the [`FundRoles`] trait for the **Author subsystem**
617///
618/// Defines how authors can be externally backed by external collaterals
619/// within the runtime.  
620impl<T: Config> FundRoles<Author<T>> for Pallet<T> {
621    /// Represents the entity providing funding to an author.
622    ///
623    /// Can be a direct account, an index, or a managed pool.
624    ///
625    /// Indirect backers such as index or pools must have a direct account
626    /// willing to back (fund) it.
627    type Backer = Funder<T>;
628
629    /// Checks if the author has any active backers/funds.
630    ///
631    /// Returns a DispatchError if no funders exist.
632    fn has_funds(who: &Author<T>) -> DispatchResult {
633        let Some(_) = AuthorFunders::<T>::iter_prefix((who,)).next() else {
634            return Err(Error::<T>::FundDoesNotExist.into());
635        };
636        Ok(())
637    }
638
639    /// Returns the **maximum exposure** allowed for an [`Author`] from a `Backer`,
640    /// under the directive of the attempted funding.
641    ///
642    /// For [`Funder::Direct`] backers, limits are derived from:
643    /// - global constraint ([`MaxExposure`]),
644    /// - author-specific constraint ([`AuthorInfo`]'s `max_fund`),
645    /// - and underlying commitment limits via [`CommitmentAdapter`][Config::CommitmentAdapter],
646    ///   depending on whether the fund is new or being raised.
647    ///
648    /// For index and pool funders:
649    /// - only global and author-specific constraints are applied.
650    ///
651    /// The `precision` and `force` parameters simulate the directive of
652    /// the funding attempt.
653    fn max_exposure(
654        by: &Self::Backer,
655        to: &Author<T>,
656        precision: Precision,
657        force: Fortitude,
658    ) -> Result<Self::Asset, DispatchError> {
659        let info = Self::get_meta(to)?;
660        let global = MaxExposure::<T>::get();
661
662        debug_assert!(
663            global >= MinFund::<T>::get(),
664            "`MaxExposure` must be greater than or equal to `MinFund`"
665        );
666
667        // Local (author-specific) constraint
668        let local = info.max_fund.unwrap_or(global);
669
670        let base_max = local.min(global);
671
672        let Funder::Direct(funder) = by else {
673            return Ok(base_max);
674        };
675
676        // ---- Commitment-aware limits ----
677
678        let reason = &FreezeReason::AuthorFunding.into();
679        let directive = &Directive::new(precision, force);
680
681        let author_digest = &info.digest;
682
683        let commit_exists = T::CommitmentAdapter::commit_exists(funder, reason).is_ok();
684
685        let limits = match commit_exists {
686            true => {
687                let exist_digest = T::CommitmentAdapter::get_commit_digest(funder, reason)?;
688                ensure!(
689                    exist_digest == *author_digest,
690                    Error::<T>::FundedToAnotherDigest
691                );
692                T::CommitmentAdapter::raise_commit_limits(funder, reason, directive)?
693            }
694            false => {
695                T::CommitmentAdapter::place_commit_limits(funder, reason, author_digest, directive)?
696            }
697        };
698
699        let commit_max = limits.maximum().unwrap_or(Bounded::max_value());
700
701        // Final max = min(local/global, commitment)
702        Ok(base_max.min(commit_max))
703    }
704
705    /// Returns the **minimum funding amount** required for a `Backer` to fund
706    /// an [`Author`], under the directive of the attempted funding.
707    ///
708    /// For [`Funder::Direct`] backers, limits are derived from:
709    /// - global constraint ([`MinFund`]),
710    /// - author-specific constraint ([`AuthorInfo`]'s `min_fund`),
711    /// - and underlying commitment limits via [`CommitmentAdapter`](Config::CommitmentAdapter),
712    ///   depending on whether the fund is new or being raised.
713    ///
714    /// For index and pool funders:
715    /// - only global and author-specific constraints are applied.
716    ///
717    /// The `precision` and `force` parameters simulate the directive of
718    /// the funding attempt.
719    fn min_fund(
720        by: &Self::Backer,
721        to: &Author<T>,
722        precision: Precision,
723        force: Fortitude,
724    ) -> Result<Self::Asset, DispatchError> {
725        let info = Self::get_meta(to)?;
726        let global = MinFund::<T>::get();
727        debug_assert!(!global.is_zero(), "`MinFund` must be greater than zero");
728        debug_assert!(
729            global <= MaxExposure::<T>::get(),
730            "`MinFund` must be smaller than or equal to `MaxExposure`"
731        );
732
733        // Local (author-specific) constraint
734        let local = info.min_fund.unwrap_or(global);
735
736        let base_min = local.max(global);
737
738        let Funder::Direct(funder) = by else {
739            return Ok(base_min);
740        };
741
742        // ---- Commitment-aware limits ----
743
744        let reason = &FreezeReason::AuthorFunding.into();
745        let directive = &Directive::new(precision, force);
746
747        let author_digest = &info.digest;
748
749        let commit_exists = T::CommitmentAdapter::commit_exists(funder, reason).is_ok();
750
751        let limits = match commit_exists {
752            true => {
753                let exist_digest = T::CommitmentAdapter::get_commit_digest(funder, reason)?;
754                ensure!(
755                    exist_digest == *author_digest,
756                    Error::<T>::FundedToAnotherDigest
757                );
758                T::CommitmentAdapter::raise_commit_limits(funder, reason, directive)?
759            }
760            false => {
761                T::CommitmentAdapter::place_commit_limits(funder, reason, author_digest, directive)?
762            }
763        };
764
765        let commit_min = limits.minimum().unwrap_or(Zero::zero());
766
767        // Final min = max(local/global, commitment)
768        Ok(base_min.max(commit_min))
769    }
770
771    /// Total real-time funds currently backing the author (excluding the author's own collateral).
772    ///
773    /// Only includes finalized commitments; pending rewards or penalties are ignored.
774    fn backed_value(who: &Author<T>) -> Result<Self::Asset, DispatchError> {
775        let info = Self::get_meta(who)?;
776        let reason = &FreezeReason::AuthorFunding.into();
777        let value = T::CommitmentAdapter::get_digest_value(reason, &info.digest)?;
778        Ok(value)
779    }
780
781    /// Total real-time funds currently backing **all the authors** (excluding all authors own collaterals).
782    ///
783    /// Only includes finalized commitments; pending rewards or penalties are ignored.
784    fn total_backing() -> Self::Asset {
785        let reason = &FreezeReason::AuthorFunding.into();
786        T::CommitmentAdapter::get_total_value(reason)
787    }
788
789    /// Validates whether a backer can fund a given author.
790    ///
791    /// Returns `Ok(())` if the backer is eligible to fund, or a `DispatchError` otherwise.
792    fn can_fund(
793        by: &Self::Backer,
794        to: &Author<T>,
795        value: Self::Asset,
796        precision: Precision,
797        force: Fortitude,
798    ) -> DispatchResult {
799        // Ensure author is available (not defaulted)
800        Self::is_available(to)?;
801
802        let info = Self::get_meta(to);
803        debug_assert!(
804            info.is_ok(),
805            "author {:?} is-available without its meta",
806            to
807        );
808        let info = info?;
809        let author_digest = &info.digest;
810        let reason = &FreezeReason::AuthorFunding.into();
811
812        let (funder, towards) = match by {
813            Funder::Direct(backer) => {
814                // Fund value range check within limits defined by author/global/commitment
815                ensure!(
816                    value >= Self::min_fund(by, to, precision, force)?,
817                    Error::<T>::BelowMinimumFund
818                );
819                ensure!(
820                    value <= Self::max_exposure(by, to, precision, force)?,
821                    Error::<T>::AboveMaximumExposure
822                );
823                (backer, author_digest)
824            }
825            Funder::Index { digest, backer } => {
826                // Fund value range check within limits defined by global
827                ensure!(value >= MinFund::<T>::get(), Error::<T>::BelowMinimumFund);
828                ensure!(
829                    value <= MaxExposure::<T>::get(),
830                    Error::<T>::AboveMaximumExposure
831                );
832                // Check if the author's digest available in the index entries
833                T::CommitmentAdapter::entry_exists(reason, digest, author_digest)?;
834                (backer, digest)
835            }
836            Funder::Pool { digest, backer } => {
837                // Fund value range check within limits defined by global
838                ensure!(value >= MinFund::<T>::get(), Error::<T>::BelowMinimumFund);
839                ensure!(
840                    value <= MaxExposure::<T>::get(),
841                    Error::<T>::AboveMaximumExposure
842                );
843                // Check if the author's digest available in the pool slots
844                T::CommitmentAdapter::slot_exists(reason, digest, author_digest)?;
845                (backer, digest)
846            }
847        };
848
849        // In case if its not the first funding commitment for the funder (signing backer)
850        if T::CommitmentAdapter::commit_exists(funder, reason).is_ok() {
851            let exist_digest = T::CommitmentAdapter::get_commit_digest(funder, reason)?;
852            ensure!(exist_digest == *towards, Error::<T>::FundedToAnotherDigest);
853        }
854        Ok(())
855    }
856
857    /// Funds an author on behalf of a backer.
858    ///
859    /// This function either places a new fund for an author or increases an existing fund
860    /// if the author has already been funded by the same backer.
861    ///
862    /// The backer [`Self::Backer`] is of type [`Funder`] in itself explains to
863    /// whom its funding, and via what.
864    ///
865    /// This function asks a suitable author `to` diligently even for index and pool backers.
866    /// It is never unused as we can do additional invariant checks.
867    ///
868    /// Hence indexes and pool backers should ensure that the author given is indeed true
869    /// in its context of valdidation i.e., author available in respective entires or slots.
870    ///
871    /// In case of backers being index or pools, the returned amount reflects the
872    /// total funding to it (may not be only for the given author)
873    ///
874    /// ## Returns
875    /// - `Ok(Asset)`: The amount successfully funded.
876    /// - `Err(DispatchError)` otherwise.
877    fn fund(
878        to: &Author<T>,
879        by: &Self::Backer,
880        value: Self::Asset,
881        precision: Precision,
882        force: Fortitude,
883    ) -> Result<Self::Asset, DispatchError> {
884        // Validate that the backer can fund the author with the specified value
885        Self::can_fund(by, to, value, precision, force)?;
886
887        // Reason used for freezing/funding the commitment
888        let reason = &FreezeReason::AuthorFunding.into();
889
890        let info = Self::get_meta(to);
891        debug_assert!(
892            info.is_ok(),
893            "backer can-fund but given author's {:?} meta not available",
894            to
895        );
896        let info = info?;
897        let author_digest = &info.digest;
898
899        // Determine funder and target digest based on the backer type
900        let (funder, towards) = match by {
901            Funder::Direct(backer) => (backer, author_digest),
902            Funder::Index { digest, backer } => (backer, digest),
903            Funder::Pool { digest, backer } => (backer, digest),
904        };
905
906        // If a commitment already exists, raise it; otherwise, place a new commitment
907        let actual;
908        match T::CommitmentAdapter::commit_exists(funder, reason) {
909            Ok(_) => {
910                actual = T::CommitmentAdapter::raise_commit(
911                    funder,
912                    reason,
913                    value,
914                    &Directive::new(precision, force),
915                )?;
916            }
917            Err(_) => {
918                actual = T::CommitmentAdapter::place_commit(
919                    funder,
920                    reason,
921                    towards,
922                    value,
923                    &Directive::new(precision, force),
924                )?;
925            }
926        }
927
928        // Update the funders of author
929        // In case of index backer or a pool, all the funded authors must get reflected on their recent funding
930        match by {
931            Funder::Direct(_) => {
932                AuthorFunders::<T>::insert((to, funder), &by);
933            }
934            Funder::Index { digest, backer: _ } => {
935                let entries = T::CommitmentAdapter::get_entries_shares(reason, digest)?;
936                for (entry, _) in entries {
937                    let author = AuthorsDigest::<T>::get(&entry);
938                    let author = author.ok_or(Error::<T>::AuthorDigestNotFound)?;
939                    AuthorFunders::<T>::insert((&author, funder), by);
940                }
941            }
942            Funder::Pool { digest, backer: _ } => {
943                let slots = T::CommitmentAdapter::get_slots_shares(reason, digest)?;
944                for (slot, _) in slots {
945                    let author = AuthorsDigest::<T>::get(&slot);
946                    let author = author.ok_or(Error::<T>::AuthorDigestNotFound)?;
947                    AuthorFunders::<T>::insert((&author, funder), by);
948                }
949            }
950        }
951        Self::on_funded(to, by, actual);
952        Ok(actual)
953    }
954
955    /// Validates whether a backer can withdraw their existing fund from a given author.
956    ///
957    /// The backer [`Self::Backer`] is of type [`Funder`] in itself explains to
958    /// whom its funded, and via what.
959    ///
960    /// This function asks a suitable author `from` diligently even for index and pool backers.
961    /// It is never unused as we can do additional invariant checks.
962    ///
963    /// Hence indexes and pool backers should ensure that the author given is indeed true
964    /// in its context of valdidation i.e., author available in respective entires or slots.
965    ///
966    /// Returns `Ok(())` if the backer is eligible to withdraw the fund, or a `DispatchError` otherwise.
967    fn can_draw(by: &Self::Backer, from: &Author<T>) -> DispatchResult {
968        let info = Self::get_meta(from)?;
969        let author_digest = &info.digest;
970        let reason = &FreezeReason::AuthorFunding.into();
971
972        let (funder, towards) = match by {
973            Funder::Direct(backer) => (backer, author_digest),
974            Funder::Index { digest, backer } => {
975                // Check if the author's digest available in the index entries
976                T::CommitmentAdapter::entry_exists(reason, digest, author_digest)?;
977                (backer, digest)
978            }
979            Funder::Pool { digest, backer } => {
980                // Check if the author's digest available in the pool slots
981                T::CommitmentAdapter::slot_exists(reason, digest, author_digest)?;
982                (backer, digest)
983            }
984        };
985        // Ensure the funder already has funded (not simply trying to draw funds)
986        T::CommitmentAdapter::commit_exists(funder, reason)?;
987        let exist_digest = T::CommitmentAdapter::get_commit_digest(funder, reason)?;
988        // Ensure if the funder funded to the given author only
989        ensure!(exist_digest == *towards, Error::<T>::FundedToAnotherDigest);
990        Ok(())
991    }
992
993    /// Withdraws funds for a given author on behalf of a backer.
994    ///
995    /// This function allows a backer to "draw" or withdraw funds that were committed
996    /// to an author. Depending on the type of backer, the withdrawal behaves slightly differently:
997    ///
998    /// - **Direct Backer:** Withdraws the funds directly committed by the backer.
999    /// - **Index Backer:** Withdraws the total funds of the specified index, assuming the author is part of it.
1000    /// - **Pool Backer:** Withdraws the total funds of the pool, assuming the author is part of it.
1001    ///
1002    /// Returns the withdrawn amount on success, or a `DispatchError` if validation fails.
1003    fn draw(from: &Author<T>, by: &Self::Backer) -> Result<Self::Asset, DispatchError> {
1004        // Validate that the backer can draw funds for the author
1005        Self::can_draw(by, from)?;
1006
1007        // Define the reason for freezing funds during the withdrawal
1008        let reason = &FreezeReason::AuthorFunding.into();
1009
1010        // Identify the actual backer from the `Funder` enum
1011        let funder = match by {
1012            Funder::Direct(backer)
1013            | Funder::Index { digest: _, backer }
1014            | Funder::Pool { digest: _, backer } => backer,
1015        };
1016
1017        // Resolve the commitment and return the withdrawn funds
1018        let actual = T::CommitmentAdapter::resolve_commit(funder, reason)?;
1019
1020        // Update the backers in the authors meta-data
1021        // In case of index backer or a pool, all the funded authors must get reflected on their recent withdrawal
1022        match by {
1023            Funder::Direct(_) => {
1024                AuthorFunders::<T>::remove((from, funder));
1025            }
1026            Funder::Index { digest, backer: _ } => {
1027                let entries = T::CommitmentAdapter::get_entries_shares(reason, digest)?;
1028                for (entry, _) in entries {
1029                    let author = AuthorsDigest::<T>::get(&entry);
1030                    let author = author.ok_or(Error::<T>::AuthorDigestNotFound)?;
1031                    AuthorFunders::<T>::remove((author, funder));
1032                }
1033            }
1034            Funder::Pool { digest, backer: _ } => {
1035                let slots = T::CommitmentAdapter::get_slots_shares(reason, digest)?;
1036                for (slot, _) in slots {
1037                    let author = AuthorsDigest::<T>::get(&slot);
1038                    let author = author.ok_or(Error::<T>::AuthorDigestNotFound)?;
1039                    AuthorFunders::<T>::remove((author, funder));
1040                }
1041            }
1042        }
1043        Self::on_drawn(from, by, actual);
1044        Ok(actual)
1045    }
1046
1047    /// Returns all backers currently funding the given author along with their real-time contributions.
1048    ///
1049    /// This excludes the author's own collateral as only external backers are returned.
1050    ///
1051    /// This function iterates over each registered funder for the author and retrieves their committed value:
1052    /// - **Direct:** Returns the committed amount.
1053    /// - **Index:** Fetches the value of the author's (entry's) digest within the index's digest's entries.
1054    /// - **Pool:** Fetches the value of the author's (slot's) digest within the pool's digest's slots.
1055    ///
1056    /// Returns a vector of `(Backer, Asset)` tuples or a `DispatchError` if any validation fails.
1057    fn backers_of(who: &Author<T>) -> Result<Vec<(Self::Backer, Self::Asset)>, DispatchError> {
1058        let info = Self::get_meta(who)?;
1059        let mut result: Vec<(Self::Backer, Self::Asset)> = Default::default();
1060        let reason = &FreezeReason::AuthorFunding.into();
1061        let iter = AuthorFunders::<T>::iter_prefix((who,));
1062        for (_, funder) in iter {
1063            match &funder {
1064                Funder::Direct(direct) => {
1065                    let value = T::CommitmentAdapter::get_commit_value(direct, reason)?;
1066                    result.push((funder, value));
1067                }
1068                Funder::Index { digest, backer } => {
1069                    // Retrieve the backers's contribution from the index digest for the author (entry)
1070                    let value = T::CommitmentAdapter::get_entry_value_for(
1071                        backer,
1072                        reason,
1073                        digest,
1074                        &info.digest,
1075                    )?;
1076                    result.push((funder, value));
1077                }
1078                Funder::Pool { digest, backer } => {
1079                    // Retrieve the backers's contribution from the pool digest for the author (slot)
1080                    let value = T::CommitmentAdapter::get_slot_value_for(
1081                        backer,
1082                        reason,
1083                        digest,
1084                        &info.digest,
1085                    )?;
1086                    result.push((funder, value));
1087                }
1088            }
1089        }
1090
1091        Ok(result)
1092    }
1093
1094    /// Returns all authors currently funded by the given backer as external funding along
1095    /// with their real-time contributions.
1096    ///
1097    /// Behavior varies by backer type:
1098    /// - **Direct:** Expected to fund a single author; retrieves the commit digest and value.
1099    /// - **Index:** Can fund multiple authors; retrieves all index entries as digests and
1100    /// values for the backing account.
1101    /// - **Pool:** Can fund multiple authors; retrieves all pool slots as digests and values
1102    /// for the backing account.
1103    ///
1104    /// After retrieving digests and values, this function resolves each digest to the
1105    /// corresponding author and ensures that the author exists.
1106    ///
1107    /// Returns a vector of `(Author, Asset)` tuples, or a `DispatchError` if validation fails.
1108    fn backed_for(by: &Self::Backer) -> Result<Vec<(Author<T>, Self::Asset)>, DispatchError> {
1109        let mut result: Vec<(Author<T>, Self::Asset)> = Default::default();
1110        let mut pre_return: Vec<(AuthorDigest<T>, Self::Asset)> = Default::default();
1111        let reason = &FreezeReason::AuthorFunding.into();
1112        match by {
1113            Funder::Direct(funder) => {
1114                // Direct commit; expected to have only a single author
1115                let to = T::CommitmentAdapter::get_commit_digest(funder, reason)?;
1116                let value = T::CommitmentAdapter::get_commit_value(funder, reason)?;
1117                pre_return.push((to, value))
1118            }
1119            Funder::Index { digest, backer } => {
1120                // Retrieve all entries (author digests and values) in the index for the backer.
1121                pre_return = T::CommitmentAdapter::get_entries_value_for(backer, reason, digest)?;
1122            }
1123            Funder::Pool { digest, backer } => {
1124                // Retrieve all slots (author digests and values) in the pool for the backer.
1125                pre_return = T::CommitmentAdapter::get_slots_value_for(backer, reason, digest)?;
1126            }
1127        }
1128
1129        // Resolve each digest to the actual author and push into the result
1130        for (digest, value) in pre_return {
1131            let author = AuthorsDigest::<T>::get(&digest);
1132            let author = author.ok_or(Error::<T>::AuthorDigestNotFound)?;
1133            result.push((author, value))
1134        }
1135
1136        Ok(result)
1137    }
1138
1139    /// Returns the real-time contribution a specific backer has funded to the given author.
1140    ///
1141    /// Behavior varies by backer type:
1142    /// - **Direct:** Expects a single author; verifies that the digest maps to the given author.
1143    /// - **Index:** Returns the value of the author's entry in the index for the backer account.
1144    /// - **Pool:** Returns the value of the author's slot in the pool for the backer account.
1145    ///
1146    /// Returns the funded `Asset` or a `DispatchError` if validation fails.
1147    fn get_fund(who: &Author<T>, by: &Self::Backer) -> Result<Self::Asset, DispatchError> {
1148        let info = Self::get_meta(who)?;
1149        let reason = &FreezeReason::AuthorFunding.into();
1150
1151        match by {
1152            Funder::Direct(direct) => {
1153                // Ensure the direct funder's commit digest corresponds to this author
1154                let digest = T::CommitmentAdapter::get_commit_digest(direct, reason)?;
1155                let is_author =
1156                    AuthorsDigest::<T>::get(digest).ok_or(Error::<T>::AuthorDigestNotFound)?;
1157                ensure!(is_author == *who, Error::<T>::FundedToAnotherDigest,);
1158                T::CommitmentAdapter::get_commit_value(direct, reason)
1159            }
1160            Funder::Index { digest, backer } => {
1161                // Get value of this author's entry in the index for the backer.
1162                T::CommitmentAdapter::get_entry_value_for(backer, reason, digest, &info.digest)
1163            }
1164            Funder::Pool { digest, backer } => {
1165                // Get value of this author's slot in the pool for the backer.
1166                T::CommitmentAdapter::get_slot_value_for(backer, reason, digest, &info.digest)
1167            }
1168        }
1169    }
1170
1171    /// Hook invoked after a backer withdraws previously committed funds
1172    /// from an author, via direct, index, or pool commitments.
1173    ///
1174    /// For index or pool backers, the emitted amount represents the
1175    /// aggregated withdrawal applied across all associated authors.
1176    ///
1177    /// Emits any one of event if [`Config::EmitEvents`] is `true`.
1178    ///     - Direct Author: [`Event::AuthorDrawn`]
1179    ///     - Index: [`Event::IndexDrawn`]
1180    ///     - Pool: [`Event::PoolDrawn`]
1181    fn on_drawn(who: &Author<T>, by: &Self::Backer, amount: Self::Asset) {
1182        if T::EmitEvents::get() {
1183            match by {
1184                Funder::Direct(backer) => {
1185                    Self::deposit_event(Event::<T>::AuthorDrawn {
1186                        author: who.clone(),
1187                        backer: backer.clone(),
1188                        amount,
1189                    });
1190                }
1191                Funder::Index { digest, backer } => {
1192                    Self::deposit_event(Event::<T>::IndexDrawn {
1193                        index: digest.clone(),
1194                        backer: backer.clone(),
1195                        amount,
1196                    });
1197                }
1198                Funder::Pool { digest, backer } => {
1199                    Self::deposit_event(Event::<T>::PoolDrawn {
1200                        pool: digest.clone(),
1201                        backer: backer.clone(),
1202                        amount,
1203                    });
1204                }
1205            }
1206        }
1207    }
1208
1209    /// Hook invoked after an author is successfully funded by a backer.
1210    ///
1211    /// For index or pool backers, the emitted amount represents the
1212    /// aggregated deposit distributed across all associated authors.
1213    ///
1214    /// Emits any one of event if [`Config::EmitEvents`] is `true`.
1215    ///     - Direct Author: [`Event::AuthorFunded`]
1216    ///     - Index: [`Event::IndexFunded`]
1217    ///     - Pool: [`Event::PoolFunded`]
1218    fn on_funded(who: &Author<T>, by: &Self::Backer, amount: Self::Asset) {
1219        if T::EmitEvents::get() {
1220            match by {
1221                Funder::Direct(backer) => {
1222                    Self::deposit_event(Event::<T>::AuthorFunded {
1223                        author: who.clone(),
1224                        backer: backer.clone(),
1225                        amount,
1226                    });
1227                }
1228                Funder::Index { digest, backer } => {
1229                    Self::deposit_event(Event::<T>::IndexFunded {
1230                        index: digest.clone(),
1231                        backer: backer.clone(),
1232                        amount,
1233                    });
1234                }
1235                Funder::Pool { digest, backer } => {
1236                    Self::deposit_event(Event::<T>::PoolFunded {
1237                        pool: digest.clone(),
1238                        backer: backer.clone(),
1239                        amount,
1240                    });
1241                }
1242            }
1243        }
1244    }
1245}
1246
1247// ===============================================================================
1248// `````````````````````````````` COMPENSATE ROLES ```````````````````````````````
1249// ===============================================================================
1250
1251/// Implements the [`CompensateRoles`] trait for the **Author subsystem**
1252///
1253/// Defines how authors can be rewarded/slashed along with its backers
1254/// within the runtime.  
1255impl<T: Config> CompensateRoles<Author<T>> for Pallet<T> {
1256    /// The penalty ratio type.
1257    ///
1258    /// Uses [`Perbill`], a fixed-point representation
1259    /// with 1 billion precision.
1260    ///
1261    /// ## Example
1262    /// - `Perbill::from_percent(5)`  -> 5% penalty
1263    /// - `Perbill::from_parts(500_000_000)` -> 50% penalty
1264    type Ratio = Perbill;
1265
1266    /// Checks whether the given `Author` currently has any pending rewards.
1267    ///
1268    /// - This function **only performs a read check** - it does not mutate state.
1269    /// - The lookup range ensures that pending rewards are checked from the *next block onward*,
1270    ///   accounting for rewards that may already be queued but not yet enforced.
1271    ///
1272    /// DispatchError otherwise.
1273    fn has_reward(who: &Author<T>) -> DispatchResult {
1274        // Early return if author is invalid
1275        Self::role_exists(who)?;
1276
1277        // Since rewards are enforced via `on_initialize`, we skip the current block
1278        let mut start_block = frame_system::Pallet::<T>::block_number().saturating_add(1u32.into());
1279
1280        // The upper bound for reward scanning - no rewards exist beyond this block.
1281        let last_reward_block = RewardsUntil::<T>::get();
1282
1283        // Iterate through blocks up to the last known reward block.
1284        while start_block <= last_reward_block {
1285            // If a reward entry exists for this author at this block, report success.
1286            if AuthorRewards::<T>::contains_key((start_block, who)) {
1287                return Ok(());
1288            }
1289            // Advance to the next block.
1290            start_block = start_block.saturating_add(1u32.into())
1291        }
1292
1293        // No pending rewards found within the valid range.
1294        Err(Error::<T>::RewardNotFound.into())
1295    }
1296
1297    /// Checks whether the given `Author` currently has any pending penalties.
1298    ///
1299    /// - This function **only performs a read check** - it does not mutate state.
1300    /// - The lookup range ensures that pending penalties are checked from the *next block onward*,
1301    ///   accounting for penalries that may already be queued but not yet enforced.
1302    ///
1303    /// DispatchError otherwise.
1304    fn has_penalty(who: &Author<T>) -> DispatchResult {
1305        // Early return if author is invalid
1306        Self::role_exists(who)?;
1307
1308        // Since penalties are enforced via `on_initialize`, we skip the current block
1309        let mut start_block = frame_system::Pallet::<T>::block_number().saturating_add(1u32.into());
1310
1311        // The upper bound for penalty scanning - no penalties exist beyond this block.
1312        let last_penalty_block = PenaltiesUntil::<T>::get();
1313
1314        // Iterate through blocks up to the last known penalty block.
1315        while start_block <= last_penalty_block {
1316            if AuthorPenalties::<T>::contains_key((start_block, who)) {
1317                return Ok(());
1318            }
1319            start_block = start_block.saturating_add(1u32.into())
1320        }
1321        // No pending rewards found within the valid range.
1322        Err(Error::<T>::PenaltyNotFound.into())
1323    }
1324
1325    /// Retrieves all **pending rewards** for a given author.
1326    ///
1327    /// Rewards are finalized over time via periodic enforcement,
1328    /// so the current block is **skipped** since it would have been finalized
1329    ///
1330    /// ## Returns
1331    /// - `Ok(Vec<(TimeStamp, Asset)>)` - a list of `(block_number, reward_value)` tuples
1332    ///   for each reward found.  
1333    /// - `Err(DispatchError)` - otherwise.
1334    fn get_rewards_of(
1335        who: &Author<T>,
1336    ) -> Result<Vec<(Self::TimeStamp, Self::Asset)>, DispatchError> {
1337        // Early return if author is invalid
1338        Self::role_exists(who)?;
1339
1340        // Accumulator for rewards
1341        let mut result: Vec<(Self::TimeStamp, Self::Asset)> = Default::default();
1342
1343        // Since rewards are enforced via `on_initialize`, we skip the current block
1344        let mut start_block = frame_system::Pallet::<T>::block_number().saturating_add(1u32.into());
1345
1346        // The upper bound for reward scanning - no rewards exist beyond this block.
1347        let last_reward_block = RewardsUntil::<T>::get();
1348
1349        // Iterate through blocks up to the last known reward block.
1350        while start_block <= last_reward_block {
1351            if let Some(value) = AuthorRewards::<T>::get((start_block, who)) {
1352                // Reward found hence accumulate
1353                result.push((start_block, value))
1354            }
1355            start_block = start_block.saturating_add(1u32.into())
1356        }
1357        Ok(result)
1358    }
1359
1360    /// Retrieves all **pending penalities** for a given author.
1361    ///
1362    /// Penalties are finalized over time via periodic enforcement,
1363    /// so the current block is **skipped** since it would have been finalized
1364    ///
1365    /// ## Returns
1366    /// - `Ok(Vec<(TimeStamp, Ratio)>)` - a list of `(block_number, factor)` tuples
1367    ///   for each penalty found.  
1368    /// - `Err(DispatchError)` - otherwise.
1369    fn get_penalties_of(
1370        who: &Author<T>,
1371    ) -> Result<Vec<(Self::TimeStamp, Self::Ratio)>, DispatchError> {
1372        // Early return if author is invalid
1373        Self::role_exists(who)?;
1374
1375        // Accumulator for penalties
1376        let mut result: Vec<(Self::TimeStamp, Self::Ratio)> = Default::default();
1377
1378        // Since penalties are enforced via `on_initialize`, we skip the current block
1379        let mut start_block = frame_system::Pallet::<T>::block_number().saturating_add(1u32.into());
1380
1381        // The upper bound for penalty scanning - no penalties exist beyond this block.
1382        let last_penalty_block = PenaltiesUntil::<T>::get();
1383
1384        // Iterate through blocks up to the last known penalty block.
1385        while start_block <= last_penalty_block {
1386            if let Some(factor) = AuthorPenalties::<T>::get((start_block, who)) {
1387                result.push((start_block, factor))
1388            }
1389            start_block = start_block.saturating_add(1u32.into())
1390        }
1391        Ok(result)
1392    }
1393
1394    /// Retrieves the current **hold amount** for the specified `Author`.
1395    ///
1396    /// - This function is **read-only** and does not modify any runtime state.
1397    /// - The returned hold includes all **live reserved assets** for the author:
1398    ///   funding, collateral, and enforced rewards/penalties.
1399    ///
1400    /// DispatchError otherwise
1401    fn get_hold(who: &Author<T>) -> Result<Self::Asset, DispatchError> {
1402        let info = Self::get_meta(who)?;
1403
1404        // Freeze reason for external author fundings.
1405        let funding_reason = &FreezeReason::AuthorFunding.into();
1406        let funding = T::CommitmentAdapter::get_digest_value(funding_reason, &info.digest)?;
1407        // Freeze reason for author collateral.
1408        let collateral_reason = &FreezeReason::AuthorCollateral.into();
1409        let collateral = T::CommitmentAdapter::get_digest_value(collateral_reason, &info.digest)?;
1410
1411        // Compute total hold; fail if overflow occurs.
1412        let hold = funding.checked_add(&collateral);
1413
1414        debug_assert!(
1415            hold.is_some(),
1416            "exhausted the asset type's max bound value by the author {:?}
1417            via funding {:?} + collateral {:?}, if non-issuance asset ignore 
1418            this, else requires strict action",
1419            who,
1420            funding,
1421            collateral
1422        );
1423
1424        let hold = hold.ok_or(Error::<T>::AuthorTotalHoldExhausted)?;
1425
1426        Ok(hold)
1427    }
1428
1429    /// Retrieves all **pending rewards** for a specific timestamp across all authors.
1430    ///
1431    /// This function performs a reverse lookup of rewards scheduled for enforcement
1432    /// at the given `time_stamp`.
1433    ///
1434    /// Rewards at or before the current finalized block cannot be queried, as
1435    /// they are already settled and no longer represent pending obligations.
1436    ///
1437    /// ## Returns
1438    /// - `Ok(Vec<(Author<T>, Asset)>)` - a list of `(author, reward_value)` tuples
1439    ///   representing pending rewards for the specified timestamp.  
1440    /// - `Err(DispatchError)` otherwise.
1441    fn get_rewards_on(
1442        time_stamp: Self::TimeStamp,
1443    ) -> Result<Vec<(Author<T>, Self::Asset)>, DispatchError> {
1444        // Current or previous blocks rewards are finalized, hence cannot derive
1445        if time_stamp <= frame_system::Pallet::<T>::block_number() {
1446            return Err(Error::<T>::FinalizedObligations.into());
1447        }
1448
1449        let mut result: Vec<(Author<T>, Self::Asset)> = Default::default();
1450        let iter = AuthorRewards::<T>::iter_prefix((time_stamp,));
1451        // Iterate through all pending rewards of particular timestamp
1452        for (author, reward) in iter {
1453            // Accumulate pending rewards
1454            result.push((author, reward))
1455        }
1456        Ok(result)
1457    }
1458
1459    /// Retrieves all **pending penalties** for a specific timestamp across all authors.
1460    ///
1461    /// This function performs a reverse lookup of penalties scheduled for enforcement
1462    /// at the given `time_stamp`.
1463    ///
1464    /// Penalties at or before the current finalized block cannot be queried, as
1465    /// they are already settled and no longer represent pending obligations.
1466    ///
1467    /// ## Returns
1468    /// - `Ok(Vec<(Author<T>, Ratio)>)` - a list of `(author, factor)` tuples
1469    ///   representing pending penalties for the specified timestamp.  
1470    /// - `Err(DispatchError)` otherwise.
1471    fn get_penalties_on(
1472        time_stamp: Self::TimeStamp,
1473    ) -> Result<Vec<(Author<T>, Self::Ratio)>, DispatchError> {
1474        // Current or previous blocks penalties are finalized, hence cannot derive
1475        if time_stamp <= frame_system::Pallet::<T>::block_number() {
1476            return Err(Error::<T>::FinalizedObligations.into());
1477        }
1478        let mut result: Vec<(Author<T>, Self::Ratio)> = Default::default();
1479        let iter = AuthorPenalties::<T>::iter_prefix((time_stamp,));
1480        // Iterate through all pending penalties of particular timestamp
1481        for (author, factor) in iter {
1482            result.push((author, factor))
1483        }
1484        Ok(result)
1485    }
1486
1487    /// Updates the **total hold amount** of an author by proportionally redistributing
1488    /// the specified value across all of its components.
1489    ///
1490    /// - A hold represents an aggregated value of all **live reserved assets** for the author:
1491    ///   funding, collateral, and enforced rewards/penalties.
1492    ///
1493    /// This function recalculates and updates these underlying components based on the
1494    /// new total hold value provided.
1495    ///
1496    /// ## Returns
1497    /// - `Ok(())` - if the total hold was successfully recalculated and updated.  
1498    /// - `Err(DispatchError)` - otherwise.
1499    fn set_hold(
1500        who: &Author<T>,
1501        value: Self::Asset,
1502        precision: Precision,
1503        force: Fortitude,
1504    ) -> DispatchResult {
1505        let info = Self::get_meta(who)?;
1506
1507        // Freeze reason for external author fundings.
1508        let funding_reason = &FreezeReason::AuthorFunding.into();
1509        let funding = T::CommitmentAdapter::get_digest_value(funding_reason, &info.digest)?;
1510
1511        // Freeze reason for author collateral.
1512        let collateral_reason = &FreezeReason::AuthorCollateral.into();
1513        let collateral = T::CommitmentAdapter::get_digest_value(collateral_reason, &info.digest)?;
1514
1515        // Compute total hold; fail if overflow occurs.
1516        let hold = funding.checked_add(&collateral);
1517
1518        debug_assert!(
1519            hold.is_some(),
1520            "exhausted the asset type's max bound value by the author {:?}
1521            via funding {:?} + collateral {:?}, if non-issuance asset ignore 
1522            this, else requires strict action",
1523            who,
1524            funding,
1525            collateral
1526        );
1527
1528        let hold = hold.ok_or(Error::<T>::AuthorTotalHoldExhausted)?;
1529
1530        let funding_ratio = <Self::Ratio as PerThing>::from_rational(funding, hold);
1531
1532        // We take ceil instead of floor since external fundings are increasable unlike collateral
1533        // hence it holds more accountability due to its mutable influence.
1534        let funding_value = funding_ratio.mul_ceil(value);
1535        let collateral_value = value.saturating_sub(funding_value);
1536
1537        // Set both holds i.e., commitment reasons an author is subjected to.
1538        let qualifier = <<T::CommitmentAdapter as Commitment<Author<T>>>::Intent as Directive>::new(
1539            precision, force,
1540        );
1541        T::CommitmentAdapter::set_digest_value(
1542            funding_reason,
1543            &info.digest,
1544            funding_value,
1545            &qualifier.clone(),
1546        )?;
1547        T::CommitmentAdapter::set_digest_value(
1548            collateral_reason,
1549            &info.digest,
1550            collateral_value,
1551            &qualifier,
1552        )?;
1553        Self::on_set_hold(who, value);
1554        Ok(())
1555    }
1556
1557    /// Applies a **penalty** to a given author, scheduled for enforcement at a future block.
1558    ///
1559    /// A penalty represents a negative adjustment to the author's hold.
1560    ///
1561    /// This function registers a proportional penalty (as a [`PerThing`] factor)
1562    /// against all of author's commitments.
1563    ///
1564    /// Each penalty is deferred for a specified *buffer period* to allow orderly
1565    /// finalization and to ensure temporal separation of distinct penalty events.
1566    ///
1567    /// Applies risk to the author's permanence before applying the penalty.
1568    ///
1569    /// Additionally tries to revoke permanence for permenant authors if possible.
1570    ///
1571    /// ## Returns
1572    /// - `Ok(TimeStamp)` - the block number at which the penalty is scheduled to finalize.  
1573    /// - `Err(DispatchError)` - otherwise.
1574    fn penalize(who: &Author<T>, factor: Self::Ratio) -> Result<Self::TimeStamp, DispatchError> {
1575        // Reject zero penalties as invalid
1576        if factor.is_zero() {
1577            return Err(Error::<T>::ZeroPenaltyFound.into());
1578        }
1579
1580        let status = Self::get_status(who)?;
1581
1582        match status {
1583            // Active authors risk permanence
1584            AuthorStatus::Active => {
1585                let result = Self::risk_permanence(who);
1586                debug_assert!(
1587                    result.is_ok(),
1588                    "author {:?} active status available but cannot risk their permanance",
1589                    who
1590                );
1591                result?;
1592                if Self::can_revoke_permanence(who).is_ok() {
1593                    Self::revoke_permanence(who)?;
1594                }
1595            }
1596            // Probation authors risk probation
1597            AuthorStatus::Probation => {
1598                let result = Self::risk_probation(who);
1599                debug_assert!(
1600                    result.is_ok(),
1601                    "author {:?} probation status available but cannot risk their probation",
1602                    who
1603                );
1604                result?
1605            }
1606            // Cannot penalize resigned authors
1607            AuthorStatus::Resigned => {
1608                return Err(Error::<T>::AuthorResigned.into());
1609            }
1610        }
1611
1612        // Compute initial target block for penalty enforcement using buffer
1613        let mut block =
1614            frame_system::Pallet::<T>::block_number().saturating_add(PenaltiesBuffer::<T>::get());
1615
1616        // Ensure penalty is scheduled at a unique (block, author) slot
1617        // Loop until an empty slot is found for the author
1618        loop {
1619            if !AuthorPenalties::<T>::contains_key((block, who)) {
1620                AuthorPenalties::<T>::insert((block, who), &factor);
1621                break;
1622            }
1623            // If slot occupied, move to the next block
1624            block = block.saturating_add(1u32.into());
1625        }
1626
1627        // Update system-wide latest penalty timestamp if this penalty is further in the future
1628        if block > PenaltiesUntil::<T>::get() {
1629            PenaltiesUntil::<T>::put(block);
1630        }
1631
1632        Self::on_penalize(who, factor, block);
1633        // Return the scheduled block number for this penalty
1634        Ok(block)
1635    }
1636
1637    /// Removes a **pending penalty** for a given author, effectively forgiving it
1638    /// **at a particular timestamp**.
1639    ///
1640    /// It allows the system to revoke a scheduled penalty that has not yet been finalized,
1641    /// identified by the specific timestamp (`from`) at which the penalty was originally set
1642    /// for enforcement.
1643    ///
1644    /// - Forgiveness cannot apply to penalties at or before the finalized block height.  
1645    /// - Author permanence is re-secured upon successful forgiveness.  
1646    ///
1647    /// ## Returns
1648    /// - `Ok(Ratio)` - the penalty factor that was successfully forgiven.  
1649    /// - `Err(DispatchError)` - otherwise
1650    fn forgive(who: &Author<T>, from: Self::TimeStamp) -> Result<Self::Ratio, DispatchError> {
1651        let status = Self::get_status(who)?;
1652
1653        // Cannot forgive penalties that are already finalized (current or past blocks)
1654        if from <= frame_system::Pallet::<T>::block_number() {
1655            return Err(Error::<T>::FinalizedObligations.into());
1656        }
1657
1658        if status == AuthorStatus::Resigned {
1659            return Err(Error::<T>::AuthorResigned.into());
1660        }
1661
1662        // Retrieve the penalty factor for the specified timestamp
1663        let factor = AuthorPenalties::<T>::get((from, who)).ok_or(Error::<T>::PenaltyNotFound)?;
1664        // Remove the penalty since it is forgiven
1665        AuthorPenalties::<T>::remove((from, who));
1666        // Secure the author's permanence after forgiveness
1667        let result = Self::secure_permanence(who);
1668        debug_assert!(
1669            result.is_ok(),
1670            "author {:?} is-available (not resigned) but cannot secure permanance",
1671            who
1672        );
1673        result?;
1674
1675        Self::on_forgive(who, factor);
1676        // Return the forgiven penalty factor
1677        Ok(factor)
1678    }
1679
1680    /// Schedules a **reward** for a given author at a future block.
1681    ///
1682    /// A reward represents a positive adjustment to the author's hold.
1683    ///
1684    /// Each reward is deferred for a specified *buffer period* to allow orderly
1685    /// finalization and to ensure temporal separation of distinct penalty events.
1686    ///
1687    /// Ensures the author's permanence is secured before applying the reward.
1688    ///
1689    /// ## Returns
1690    /// - `Ok(TimeStamp)` - the block number at which the reward is scheduled.  
1691    /// - `Err(DispatchError)` - otherwise.
1692    /// - `Ok(TimeStamp)` - the block number at which the reward is scheduled.  
1693    /// - `Err(DispatchError)` - otherwise.
1694    fn reward(
1695        who: &Author<T>,
1696        value: Self::Asset,
1697        _precision: Precision,
1698    ) -> Result<Self::TimeStamp, DispatchError> {
1699        let status = Self::get_status(who)?;
1700
1701        // Only Active or Probation authors can receive rewards
1702        // Resigned authors cannot be rewarded
1703        match status {
1704            AuthorStatus::Active | AuthorStatus::Probation => {
1705                // Secure the author's permanence before rewarding
1706                let result = Self::secure_permanence(who);
1707                debug_assert!(
1708                    result.is_ok(),
1709                    "author {:?} is-available (not resigned) but cannot secure permanance",
1710                    who
1711                );
1712                result?;
1713            }
1714            AuthorStatus::Resigned => return Err(Error::<T>::AuthorResigned.into()),
1715        }
1716
1717        // Compute initial target block for reward scheduling using buffer
1718        let mut block =
1719            frame_system::Pallet::<T>::block_number().saturating_add(RewardsBuffer::<T>::get());
1720
1721        // Ensure reward is scheduled at a unique (block, author) slot
1722        // Loop until an empty slot is found for the author's digest
1723        loop {
1724            if !AuthorRewards::<T>::contains_key((block, who)) {
1725                AuthorRewards::<T>::insert((block, who), &value);
1726                break;
1727            }
1728            // If slot occupied, move to the next block
1729            block = block.saturating_add(1u32.into());
1730        }
1731
1732        // Update system-wide latest reward timestamp if this reward is further in the future
1733        if block > RewardsUntil::<T>::get() {
1734            RewardsUntil::<T>::put(block);
1735        }
1736
1737        Self::on_reward(who, value, block);
1738        // Return the scheduled block number for this reward
1739        Ok(block)
1740    }
1741
1742    /// Removes a **pending reward** for a given author, effectively regaining it
1743    /// **from a particular timestamp** scheduled.
1744    ///
1745    /// It allows the system to revoke a scheduled reward that has not yet been finalized,
1746    /// identified by the specific timestamp (`from`) at which the reward was originally set
1747    /// for enforcement.
1748    ///
1749    /// Regaining cannot apply to rewards at or before the finalized block height.  
1750    ///
1751    /// ## Returns
1752    /// - `Ok(Asset)` - the total reward that was successfully regained.  
1753    /// - `Err(DispatchError)` - otherwise
1754    fn reclaim(who: &Author<T>, from: Self::TimeStamp) -> Result<Self::Asset, DispatchError> {
1755        Self::role_exists(who)?;
1756
1757        // Cannot reclaim rewards that are already finalized (current or past blocks)
1758        if from <= frame_system::Pallet::<T>::block_number() {
1759            return Err(Error::<T>::FinalizedObligations.into());
1760        }
1761
1762        // Retrieve the reward value for the specified timestamp
1763        let value = AuthorRewards::<T>::get((from, who)).ok_or(Error::<T>::RewardNotFound)?;
1764        // Remove the reward since it is being reclaimed
1765        AuthorRewards::<T>::remove((from, who));
1766
1767        Self::on_reclaim(who, value);
1768        // Return the reclaimed reward value
1769        Ok(value)
1770    }
1771
1772    /// Hook invoked when reward is scheduled or applied to an author.
1773    ///
1774    /// Emits [`Event::AuthorRewardScheduled`] if [`Config::EmitEvents`] is `true`.
1775    fn on_reward(who: &Author<T>, amount: Self::Asset, at: Self::TimeStamp) {
1776        if T::EmitEvents::get() {
1777            Self::deposit_event(Event::<T>::AuthorRewardScheduled {
1778                author: who.clone(),
1779                amount: amount,
1780                at: at,
1781            });
1782        }
1783    }
1784
1785    /// Hook invoked when an author's scheduled rewards are reclaimed.
1786    ///
1787    /// Emits [`Event::AuthorRewardReclaimed`] if [`Config::EmitEvents`] is `true`.
1788    fn on_reclaim(who: &Author<T>, amount: Self::Asset) {
1789        if T::EmitEvents::get() {
1790            Self::deposit_event(Event::<T>::AuthorRewardReclaimed {
1791                author: who.clone(),
1792                amount,
1793            });
1794        }
1795    }
1796
1797    /// Hook invoked when an author's hold balance is updated.
1798    ///
1799    /// Emits [`Event::AuthorTotalHold`] if [`Config::EmitEvents`] is `true`.
1800    fn on_set_hold(who: &Author<T>, value: Self::Asset) {
1801        if T::EmitEvents::get() {
1802            Self::deposit_event(Event::<T>::AuthorTotalHold {
1803                author: who.clone(),
1804                value: value,
1805            });
1806        }
1807    }
1808
1809    /// Hook invoked when an author's scheduled penalty is forgiven.
1810    ///
1811    /// Emits [`Event::AuthorPenaltyForgiven`] if [`Config::EmitEvents`] is `true`.
1812    fn on_forgive(who: &Author<T>, factor: Self::Ratio) {
1813        if T::EmitEvents::get() {
1814            Self::deposit_event(Event::<T>::AuthorPenaltyForgiven {
1815                author: who.clone(),
1816                factor,
1817            });
1818        }
1819    }
1820
1821    /// Hook invoked when penality is scheduled or applied to an author.
1822    ///
1823    /// Emits [`Event::AuthorPenaltyScheduled`] if [`Config::EmitEvents`] is `true`.
1824    fn on_penalize(who: &Author<T>, factor: Self::Ratio, at: Self::TimeStamp) {
1825        if T::EmitEvents::get() {
1826            Self::deposit_event(Event::<T>::AuthorPenaltyScheduled {
1827                author: who.clone(),
1828                factor,
1829                at: at,
1830            });
1831        }
1832    }
1833}
1834
1835// ===============================================================================
1836// ``````````````````````````````` ROLE PROBATION ````````````````````````````````
1837// ===============================================================================
1838
1839/// Implements the [`RoleProbation`] trait for the **Author subsystem**
1840///
1841/// Defines how authors can be switched between probation and permenance
1842/// with certain invariants enforced for good behavior.
1843impl<T: Config> RoleProbation<Author<T>> for Pallet<T> {
1844    /// Checks if the author is currently under probation.
1845    ///
1846    /// Returns `Ok(())` if the author is in `Probation` status.
1847    /// DispatchError of current status of author otherwise.
1848    fn is_on_probation(who: &Author<T>) -> DispatchResult {
1849        let status = Self::get_status(who)?;
1850        match status {
1851            AuthorStatus::Active => Err(Error::<T>::AuthorIsActive.into()),
1852            AuthorStatus::Probation => Ok(()),
1853            AuthorStatus::Resigned => Err(Error::<T>::AuthorResigned.into()),
1854        }
1855    }
1856
1857    /// Checks if the author has secured permanent (active) status.
1858    ///
1859    /// Returns `Ok(())` if the author is `Active` (permanent).
1860    /// DispatchError of current status of author otherwise.
1861    fn is_permanent(who: &Author<T>) -> DispatchResult {
1862        let status = Self::get_status(who)?;
1863        match status {
1864            AuthorStatus::Active => Ok(()),
1865            AuthorStatus::Probation => Err(Error::<T>::AuthorInProbation.into()),
1866            AuthorStatus::Resigned => Err(Error::<T>::AuthorResigned.into()),
1867        }
1868    }
1869
1870    /// Checks if the given author is eligible to become permanent.
1871    ///
1872    /// Evaluates risk status and requires author in probation.
1873    ///
1874    /// - Returns `Ok(())` if the author can be promoted to permanent status.
1875    /// - Returns `Err(DispatchError)` otherwise.
1876    fn can_be_permanent(who: &Author<T>) -> DispatchResult {
1877        let info = Self::get_meta(who)?;
1878        let status = &info.status;
1879
1880        // Only authors in Probation can be evaluated for permanence
1881        match status {
1882            // Active authors cannot be made permanent
1883            AuthorStatus::Active => return Err(Error::<T>::AuthorIsActive.into()),
1884            // Resigned authors cannot be made permanent
1885            AuthorStatus::Resigned => return Err(Error::<T>::AuthorResigned.into()),
1886            // Probation authors are eligible for further checks
1887            AuthorStatus::Probation => {}
1888        }
1889
1890        let current_block = frame_system::Pallet::<T>::block_number();
1891        let status_since = info.status_since;
1892
1893        // Check if the probation period has elapsed
1894        if status_since.saturating_add(ProbationPeriod::<T>::get()) > current_block {
1895            // Author is still within probation period
1896            return Err(Error::<T>::AuthorInProbation.into());
1897        }
1898
1899        // Check if the author is currently under risk evaluation
1900        let risk_until = info.risk_until;
1901        if risk_until > current_block {
1902            return Err(Error::<T>::AuthorIsUnsafe.into());
1903        }
1904        Ok(())
1905    }
1906
1907    /// Promotes an author to permanent/active status.
1908    ///
1909    /// Returns `Ok(AuthorStatus::Active)` on success or `Err(DispatchError)` otherwise.
1910    fn set_permanence(who: &Author<T>) -> Result<Self::Status, DispatchError> {
1911        // Ensure the author is eligible to become permanent
1912        Self::can_be_permanent(who)?;
1913
1914        let active = AuthorStatus::Active;
1915
1916        AuthorsMap::<T>::mutate(who, |author| -> DispatchResult {
1917            let info = author.as_mut();
1918            debug_assert!(
1919                info.is_some(),
1920                "author {:?} can-be-permanent but cannot mutate status",
1921                who
1922            );
1923            let info = info.ok_or(Error::<T>::AuthorNotFound)?;
1924            let status = &mut info.status;
1925
1926            // Set author status to Active
1927            *status = active.clone();
1928
1929            Ok(())
1930        })?;
1931        Self::on_set_permance(who);
1932        Ok(active)
1933    }
1934
1935    /// Checks if the given author is eligible to be placed back under probation.
1936    ///
1937    /// Passes if indication of significant risk on active authors.
1938    ///
1939    /// - Returns `Ok(())` if the author can be probated.
1940    /// - Returns `Err(DispatchError)` if the author is already in probation, resigned, or cannot be probated.
1941    fn can_revoke_permanence(who: &Author<T>) -> DispatchResult {
1942        let meta = Self::get_meta(who)?;
1943        let status = meta.status;
1944        let risk_until = meta.risk_until;
1945        let current_block = frame_system::Pallet::<T>::block_number();
1946
1947        // Only Active authors permanence can be revoked
1948        match status {
1949            // Already in probation
1950            AuthorStatus::Probation => return Err(Error::<T>::AuthorInProbation.into()),
1951            // Resigned authors cannot be probated
1952            AuthorStatus::Resigned => return Err(Error::<T>::AuthorResigned.into()),
1953            // Active authors permanence may be revoked
1954            AuthorStatus::Active => {}
1955        }
1956
1957        if risk_until <= current_block.saturating_add(ProbationPeriod::<T>::get()) {
1958            return Err(Error::<T>::RiskWithinThreshold.into());
1959        }
1960        Ok(())
1961    }
1962
1963    /// Revokes an author's permanent/active status and places them back under probation.
1964    ///
1965    /// Returns `Ok(AuthorStatus::Probation)` on success or `Err(DispatchError)` otherwise.
1966    fn revoke_permanence(who: &Author<T>) -> Result<Self::Status, DispatchError> {
1967        // Ensure the author is eligible to be moved back to probation
1968        Self::can_revoke_permanence(who)?;
1969
1970        let probation = AuthorStatus::Probation;
1971        let current_block = frame_system::Pallet::<T>::block_number();
1972
1973        AuthorsMap::<T>::mutate(who, |author| -> DispatchResult {
1974            let info = author.as_mut();
1975            debug_assert!(
1976                info.is_some(),
1977                "author {:?} can-revoke-permanence but cannot mutate status",
1978                who
1979            );
1980            let info = info.ok_or(Error::<T>::AuthorNotFound)?;
1981            let status = &mut info.status;
1982            let status_since = &mut info.status_since;
1983            // Set author status to Probation
1984            *status = probation.clone();
1985            // Update timestamp of status update
1986            *status_since = current_block;
1987
1988            Ok(())
1989        })?;
1990        Self::on_revoke_permanence(who);
1991        Ok(probation)
1992    }
1993
1994    /// Marks a probationary author as at risk, extending their risk magnitude.
1995    ///
1996    /// - Returns `Ok(())` on success or `Err(DispatchError)` otherwise.
1997    fn risk_probation(who: &Author<T>) -> DispatchResult {
1998        AuthorsMap::<T>::mutate(who, |author| -> DispatchResult {
1999            let info = author.as_mut().ok_or(Error::<T>::AuthorNotFound)?;
2000
2001            let status = &mut info.status;
2002
2003            // Only authors currently in Probation can be placed at risk
2004            match status {
2005                // Active authors cannot be risked for probation
2006                AuthorStatus::Active => return Err(Error::<T>::AuthorIsActive.into()),
2007                // Resigned authors cannot be risked
2008                AuthorStatus::Resigned => return Err(Error::<T>::AuthorResigned.into()),
2009                // Probation authors are eligible
2010                AuthorStatus::Probation => {}
2011            }
2012
2013            let current_block = frame_system::Pallet::<T>::block_number();
2014            let risk_until = &mut info.risk_until;
2015
2016            // If risk has expired, reset from current block otherwise extend from existing risk_until
2017            if *risk_until < current_block {
2018                *risk_until = current_block.saturating_add(IncreaseProbationBy::<T>::get());
2019                return Ok(());
2020            }
2021            *risk_until = risk_until.saturating_add(IncreaseProbationBy::<T>::get());
2022
2023            Ok(())
2024        })?;
2025        Self::on_risk_probation(who);
2026        Ok(())
2027    }
2028
2029    /// Marks a permanent/active author as at risk, potentially impacting their permanence.
2030    ///
2031    /// - Returns `Ok(())` on success or `Err(DispatchError)` otherwise.
2032    fn risk_permanence(who: &Author<T>) -> DispatchResult {
2033        AuthorsMap::<T>::mutate(who, |author| -> DispatchResult {
2034            // Fetch author metadata; fail early if not found
2035            let info = author.as_mut().ok_or(Error::<T>::AuthorNotFound)?;
2036
2037            let status = &mut info.status;
2038
2039            // Only Active authors can have their permanence risked
2040            match status {
2041                // Active authors are eligible
2042                AuthorStatus::Active => {}
2043                // Resigned authors cannot be risked
2044                AuthorStatus::Resigned => return Err(Error::<T>::AuthorResigned.into()),
2045                // Probation authors cannot be risked here
2046                AuthorStatus::Probation => return Err(Error::<T>::AuthorInProbation.into()),
2047            }
2048
2049            let current_block = frame_system::Pallet::<T>::block_number();
2050            let risk_until = &mut info.risk_until;
2051
2052            // If risk has expired, reset from current block otherwise extend from existing risk_until
2053            if *risk_until < current_block {
2054                *risk_until = current_block.saturating_add(IncreaseProbationBy::<T>::get());
2055                return Ok(());
2056            }
2057            *risk_until = risk_until.saturating_add(IncreaseProbationBy::<T>::get());
2058            Ok(())
2059        })?;
2060        Self::on_risk_permanence(who);
2061        Ok(())
2062    }
2063
2064    /// Reduces the risk period for an author, securing their permanence.
2065    ///
2066    /// - Returns `Ok(())` on success or `Err(DispatchError)` otherwise.
2067    fn secure_permanence(who: &Author<T>) -> DispatchResult {
2068        AuthorsMap::<T>::mutate(who, |author| -> DispatchResult {
2069            let info = author.as_mut().ok_or(Error::<T>::AuthorNotFound)?;
2070
2071            let status = &mut info.status;
2072
2073            // Only Active or Probation authors can have their permanence secured
2074            match status {
2075                // Active authors can reduce risking to probation
2076                AuthorStatus::Active => {}
2077                // Resigned authors cannot be modified
2078                AuthorStatus::Resigned => return Err(Error::<T>::AuthorResigned.into()),
2079                // Probation authors can reduce risking negative performance
2080                AuthorStatus::Probation => {}
2081            }
2082            let risk_until = &mut info.risk_until;
2083            let current_block = frame_system::Pallet::<T>::block_number();
2084
2085            // Only reduce risk if it has considerable magnitude, else do nothing
2086            if *risk_until > current_block {
2087                *risk_until = risk_until.saturating_sub(ReduceProbationBy::<T>::get());
2088                return Ok(());
2089            }
2090            Ok(())
2091        })?;
2092        Self::on_secure_permanence(who);
2093        Ok(())
2094    }
2095
2096    /// Hook invoked after an author is promoted to permanent (Active) status.
2097    ///
2098    /// Emits [`Event::AuthorStatus`] if [`Config::EmitEvents`] is `true`.
2099    fn on_set_permance(who: &Author<T>) {
2100        if T::EmitEvents::get() {
2101            Self::deposit_event(Event::AuthorStatus {
2102                author: who.clone(),
2103                status: AuthorStatus::Active,
2104            });
2105        }
2106    }
2107
2108    /// Hook invoked after an author's permanence is revoked.
2109    ///
2110    /// Emits [`Event::AuthorStatus`] if [`Config::EmitEvents`] is `true`.
2111    fn on_revoke_permanence(who: &Author<T>) {
2112        if T::EmitEvents::get() {
2113            Self::deposit_event(Event::AuthorStatus {
2114                author: who.clone(),
2115                status: AuthorStatus::Probation,
2116            });
2117        }
2118    }
2119
2120    /// Hook invoked when risk is applied to an author increasing their
2121    /// risk towards disinheriting permanace.
2122    ///
2123    /// Emits [`Event::AuthorAtRisk`] if [`Config::EmitEvents`] is `true`.
2124    fn on_risk_permanence(who: &Author<T>) {
2125        if T::EmitEvents::get() {
2126            let Ok(meta) = Self::get_meta(who) else {
2127                return;
2128            };
2129            Self::deposit_event(Event::<T>::AuthorAtRisk {
2130                author: who.clone(),
2131                status: AuthorStatus::Active,
2132                until: meta.risk_until,
2133            });
2134        }
2135    }
2136
2137    /// Hook invoked when risk is applied to an author increasing their
2138    /// risk to inherit permanace.
2139    ///
2140    /// Emits [`Event::AuthorAtRisk`] if [`Config::EmitEvents`] is `true`.
2141    fn on_risk_probation(who: &Author<T>) {
2142        if T::EmitEvents::get() {
2143            let Ok(meta) = Self::get_meta(who) else {
2144                return;
2145            };
2146            Self::deposit_event(Event::<T>::AuthorAtRisk {
2147                author: who.clone(),
2148                status: AuthorStatus::Probation,
2149                until: meta.risk_until,
2150            });
2151        }
2152    }
2153
2154    /// Hook invoked when risk is reduced to an author increasing their
2155    /// oppurtunity to inherit permanace.
2156    ///
2157    /// Emits [`Event::AuthorAtRisk`] if [`Config::EmitEvents`] is `true`.
2158    fn on_secure_permanence(who: &Author<T>) {
2159        if T::EmitEvents::get() {
2160            let Ok(meta) = Self::get_meta(who) else {
2161                return;
2162            };
2163            Self::deposit_event(Event::<T>::AuthorAtRisk {
2164                author: who.clone(),
2165                status: AuthorStatus::Probation,
2166                until: meta.risk_until,
2167            });
2168        }
2169    }
2170}
2171
2172// ===============================================================================
2173// `````````````````````````````````` UNIT TESTS `````````````````````````````````
2174// ===============================================================================
2175
2176#[cfg(test)]
2177mod tests {
2178    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2179    // ``````````````````````````````````` IMPORTS ```````````````````````````````````
2180    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2181
2182    // --- Local crate imports ---
2183    use crate::mock::*;
2184    use crate::{
2185        types::{AuthorStatus, Funder},
2186        Event::*,
2187    };
2188
2189    // --- FRAME Suite ---
2190    use frame_suite::{commitment::*, roles::*};
2191
2192    // --- FRAME Support ---
2193    use frame_support::{
2194        assert_err, assert_ok,
2195        traits::{
2196            fungible::InspectFreeze,
2197            tokens::{Fortitude, Precision},
2198        },
2199    };
2200
2201    // --- Substrate primitives ---
2202    use sp_runtime::{DispatchError, Perbill};
2203
2204    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2205    // ```````````````````````````````` ROLE MANAGER `````````````````````````````````
2206    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2207
2208    #[test]
2209    fn enroll_success() {
2210        authors_test_ext().execute_with(|| {
2211            System::set_block_number(10);
2212            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2213            assert_err!(Pallet::role_exists(&ALICE), Error::AuthorNotFound);
2214            // enroll author
2215            let collateral_locked = Pallet::enroll(&ALICE, LARGE_VALUE, Fortitude::Force).unwrap();
2216            assert_ok!(CommitAdapter::commit_exists(&ALICE, &COLLATERAL.into()));
2217            assert_eq!(
2218                AuthorAsset::balance_frozen(&COLLATERAL.into(), &ALICE),
2219                collateral_locked
2220            );
2221            // author enrolled
2222            assert_ok!(Pallet::role_exists(&ALICE));
2223            let author_digest = gen_author_digest(&ALICE).unwrap();
2224            assert_eq!(AuthorsDigest::get(author_digest), Some(ALICE));
2225            System::assert_last_event(Event::AuthorEnlisted { author: ALICE, collateral: collateral_locked }.into());
2226        })
2227    }
2228
2229    #[test]
2230    fn role_exists_success() {
2231        authors_test_ext().execute_with(|| {
2232            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2233            Pallet::enroll(&ALICE, LARGE_VALUE, Fortitude::Force).unwrap();
2234            assert_ok!(Pallet::role_exists(&ALICE));
2235        })
2236    }
2237
2238    #[test]
2239    fn role_exists_err_author_not_found() {
2240        authors_test_ext().execute_with(|| {
2241            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2242            Pallet::enroll(&ALICE, LARGE_VALUE, Fortitude::Force).unwrap();
2243            // BOB is not enrolled
2244            assert_err!(Pallet::role_exists(&BOB), Error::AuthorNotFound);
2245        })
2246    }
2247
2248    #[test]
2249    fn get_meta_success() {
2250        authors_test_ext().execute_with(|| {
2251            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2252            System::set_block_number(6);
2253            Pallet::enroll(&ALICE, LARGE_VALUE, Fortitude::Force).unwrap();
2254            let author_digest = gen_author_digest(&ALICE).unwrap();
2255            let meta = Pallet::get_meta(&ALICE).unwrap();
2256            assert_eq!(meta.digest, author_digest);
2257            assert_eq!(meta.since, 6);
2258            assert_eq!(meta.status, AuthorStatus::Probation);
2259            assert_eq!(meta.status_since, 6);
2260            assert_eq!(meta.risk_until, 6);
2261            assert_eq!(meta.max_fund, None);
2262            assert_eq!(meta.min_fund, None);
2263        })
2264    }
2265
2266    #[test]
2267    fn get_meta_err_author_not_found() {
2268        authors_test_ext().execute_with(|| {
2269            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2270            System::set_block_number(6);
2271            Pallet::enroll(&ALICE, LARGE_VALUE, Fortitude::Force).unwrap();
2272            // BOB is not enrolled
2273            assert_err!(Pallet::get_meta(&BOB), Error::AuthorNotFound);
2274        })
2275    }
2276
2277    #[test]
2278    fn can_enroll_success() {
2279        authors_test_ext().execute_with(|| {
2280            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2281            assert_ok!(Pallet::can_enroll(&ALICE, LARGE_VALUE));
2282        })
2283    }
2284
2285    #[test]
2286    fn can_enroll_err_already_enrolled() {
2287        authors_test_ext().execute_with(|| {
2288            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2289            System::set_block_number(6);
2290            Pallet::enroll(&ALICE, LARGE_VALUE, Fortitude::Force).unwrap();
2291            assert_err!(
2292                Pallet::can_enroll(&ALICE, LARGE_VALUE),
2293                Error::AlreadyEnrolled
2294            );
2295        })
2296    }
2297
2298    #[test]
2299    fn can_enroll_err_inadequate_collateral() {
2300        authors_test_ext().execute_with(|| {
2301            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2302            assert_err!(
2303                Pallet::can_enroll(&ALICE, SMALL_VALUE),
2304                Error::InadequateCollateral
2305            );
2306        })
2307    }
2308
2309    #[test]
2310    fn can_enroll_err_inadequate_funds() {
2311        authors_test_ext().execute_with(|| {
2312            initiate_key_and_set_balance_and_hold(&ALICE, MIN_VALUE, MIN_VALUE).unwrap();
2313            assert_err!(
2314                Pallet::can_enroll(&ALICE, LARGE_VALUE),
2315                Error::InadequateFunds
2316            );
2317        })
2318    }
2319
2320    #[should_panic]
2321    #[test]
2322    fn can_enroll_panic_author_resigned_with_penalty() {
2323        authors_test_ext().execute_with(|| {
2324            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2325
2326            System::set_block_number(6);
2327            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
2328
2329            System::set_block_number(10);
2330            Pallet::penalize(&ALICE, Perbill::from_percent(5)).unwrap();
2331
2332            AuthorsMap::mutate(ALICE, |author| {
2333                let info = author.as_mut().unwrap();
2334                let status = &mut info.status;
2335                *status = AuthorStatus::Resigned
2336            });
2337
2338            // should, panic
2339            Pallet::can_enroll(&ALICE, STANDARD_VALUE).unwrap();
2340        })
2341    }
2342
2343    #[test]
2344    fn can_enroll_err_author_has_rewards() {
2345        authors_test_ext().execute_with(|| {
2346            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2347
2348            System::set_block_number(6);
2349            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
2350
2351            System::set_block_number(10);
2352            Pallet::reward(&ALICE, 5, Precision::BestEffort).unwrap();
2353
2354            AuthorsMap::mutate(ALICE, |author| {
2355                let info = author.as_mut().unwrap();
2356                let status = &mut info.status;
2357                *status = AuthorStatus::Resigned
2358            });
2359
2360            // since, rewards are scheduled
2361            assert_err!(
2362                Pallet::can_enroll(&ALICE, STANDARD_VALUE),
2363                Error::AuthorHasRewards
2364            );
2365        })
2366    }
2367
2368    #[test]
2369    fn can_resign_success() {
2370        authors_test_ext().execute_with(|| {
2371            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2372            System::set_block_number(6);
2373            Pallet::enroll(&ALICE, LARGE_VALUE, Fortitude::Force).unwrap();
2374
2375            AuthorsMap::mutate(ALICE, |author| {
2376                let info = author.as_mut().unwrap();
2377                let status = &mut info.status;
2378                *status = AuthorStatus::Active
2379            });
2380
2381            // Set author as in-active
2382            set_activity_state(false);
2383
2384            assert_ok! {
2385                Pallet::can_resign(&ALICE)
2386            };
2387        })
2388    }
2389
2390    #[test]
2391    fn can_resign_err_author_in_probation() {
2392        authors_test_ext().execute_with(|| {
2393            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2394            System::set_block_number(6);
2395            Pallet::enroll(&ALICE, LARGE_VALUE, Fortitude::Force).unwrap();
2396
2397            assert_err! {
2398                Pallet::can_resign(
2399                &ALICE,
2400            ), Error::AuthorInProbation
2401            };
2402        })
2403    }
2404
2405    #[test]
2406    fn can_resign_err_redundant_resignation() {
2407        authors_test_ext().execute_with(|| {
2408            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2409            System::set_block_number(6);
2410            Pallet::enroll(&ALICE, LARGE_VALUE, Fortitude::Force).unwrap();
2411
2412            AuthorsMap::mutate(ALICE, |author| {
2413                let info = author.as_mut().unwrap();
2414                let status = &mut info.status;
2415                *status = AuthorStatus::Resigned
2416            });
2417
2418            assert_err! {
2419                Pallet::can_resign(&ALICE),
2420                Error::RedundantResignation
2421            };
2422        })
2423    }
2424
2425    #[test]
2426    fn can_resign_err_author_has_penalties() {
2427        authors_test_ext().execute_with(|| {
2428            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2429
2430            System::set_block_number(6);
2431            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
2432
2433            System::set_block_number(10);
2434            Pallet::penalize(&ALICE, Perbill::from_percent(5)).unwrap();
2435
2436            AuthorsMap::mutate(ALICE, |author| {
2437                let info = author.as_mut().unwrap();
2438                let status = &mut info.status;
2439                *status = AuthorStatus::Active
2440            });
2441
2442            set_activity_state(false);
2443            // since, penalties are scheduled
2444            assert_err!(Pallet::can_resign(&ALICE), Error::AuthorHasPenalties);
2445        })
2446    }
2447
2448    #[test]
2449    fn can_resign_err_author_is_active() {
2450        authors_test_ext().execute_with(|| {
2451            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2452            System::set_block_number(6);
2453            Pallet::enroll(&ALICE, LARGE_VALUE, Fortitude::Force).unwrap();
2454
2455            AuthorsMap::mutate(ALICE, |author| {
2456                let info = author.as_mut().unwrap();
2457                let status = &mut info.status;
2458                *status = AuthorStatus::Active
2459            });
2460
2461            // Set author as active
2462            set_activity_state(true);
2463
2464            assert_err! {
2465                Pallet::can_resign(&ALICE),
2466                DispatchError::Other("AuthorIsActive")
2467            };
2468        })
2469    }
2470
2471    #[test]
2472    fn get_collateral_success() {
2473        authors_test_ext().execute_with(|| {
2474            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2475            System::set_block_number(6);
2476            Pallet::enroll(&ALICE, LARGE_VALUE, Fortitude::Force).unwrap();
2477
2478            let actual_collateral = Pallet::get_collateral(&ALICE).unwrap();
2479
2480            assert_eq!(actual_collateral, LARGE_VALUE);
2481        })
2482    }
2483
2484    #[test]
2485    fn total_collateral_success() {
2486        authors_test_ext().execute_with(|| {
2487            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2488            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
2489            System::set_block_number(6);
2490            Pallet::enroll(&ALICE, LARGE_VALUE, Fortitude::Force).unwrap();
2491
2492            let total_collateral = Pallet::total_collateral();
2493            assert_eq!(total_collateral, 100); // collateral of ALICE
2494
2495            System::set_block_number(10);
2496            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
2497
2498            let total_collateral = Pallet::total_collateral();
2499            assert_eq!(total_collateral, 150); // collateral of ALICE + BOB
2500        })
2501    }
2502
2503    #[test]
2504    fn enroll_since_success() {
2505        authors_test_ext().execute_with(|| {
2506            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2507            System::set_block_number(6);
2508            Pallet::enroll(&ALICE, LARGE_VALUE, Fortitude::Force).unwrap();
2509
2510            assert_eq!(Pallet::enroll_since(&ALICE), Ok(6));
2511        })
2512    }
2513
2514    #[test]
2515    fn get_status_success() {
2516        authors_test_ext().execute_with(|| {
2517            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2518            System::set_block_number(6);
2519            Pallet::enroll(&ALICE, LARGE_VALUE, Fortitude::Force).unwrap();
2520
2521            assert_eq!(Pallet::get_status(&ALICE), Ok(AuthorStatus::Probation));
2522        })
2523    }
2524
2525    #[test]
2526    fn status_since_success() {
2527        authors_test_ext().execute_with(|| {
2528            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2529            System::set_block_number(6);
2530            Pallet::enroll(&ALICE, LARGE_VALUE, Fortitude::Force).unwrap();
2531
2532            assert_eq!(Pallet::status_since(&ALICE), Ok(6));
2533        })
2534    }
2535
2536    #[test]
2537    fn set_status_success_from_probation() {
2538        authors_test_ext().execute_with(|| {
2539            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2540            System::set_block_number(6);
2541            Pallet::enroll(&ALICE, LARGE_VALUE, Fortitude::Force).unwrap();
2542
2543            let current_status = Pallet::get_status(&ALICE).unwrap();
2544
2545            assert_eq!(current_status, AuthorStatus::Probation);
2546
2547            // updating the status of author from AuthorStatus::Probation -> AuthorStatus::Probation is no-op
2548            assert_ok! {
2549                    Pallet::set_status(
2550                    &ALICE,
2551                    AuthorStatus::Probation
2552            )};
2553
2554            // updating the status of author from AuthorStatus::Probation -> AuthorStatus::Resigned
2555            // will cause error as the author still under probation period
2556            assert_err! {
2557                Pallet::set_status(
2558                    &ALICE,
2559                    AuthorStatus::Resigned
2560                ),
2561                Error::AuthorInProbation
2562            };
2563
2564            System::set_block_number(14);
2565            // updating the status of author from AuthorStatus::Probation -> AuthorStatus::Active
2566            // will cause error as block number still not exceeds the probation period
2567            assert_err! {
2568                Pallet::set_status(
2569                &ALICE,
2570                AuthorStatus::Active
2571            ), Error::AuthorInProbation
2572            };
2573
2574            System::set_block_number(20);
2575            // update the status of author: AuthorStatus::Probation -> AuthorStatus::Active
2576            assert_ok!(Pallet::set_status(&ALICE, AuthorStatus::Active));
2577
2578            let current_status = Pallet::get_status(&ALICE).unwrap();
2579
2580            assert_eq!(current_status, AuthorStatus::Active);
2581
2582            System::assert_last_event(Event::AuthorStatus {
2583                 author: ALICE, 
2584                 status: AuthorStatus::Active 
2585                }
2586                .into()
2587            );
2588        })
2589    }
2590
2591    #[test]
2592    fn set_status_success_from_active() {
2593        authors_test_ext().execute_with(|| {
2594            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2595            System::set_block_number(6);
2596            Pallet::enroll(&ALICE, LARGE_VALUE, Fortitude::Force).unwrap();
2597
2598            System::set_block_number(20);
2599            // update the status of author: AuthorStatus::Probation -> AuthorStatus::Active
2600            assert_ok!(Pallet::set_status(&ALICE, AuthorStatus::Active));
2601
2602            let current_status = Pallet::get_status(&ALICE).unwrap();
2603            assert_eq!(current_status, AuthorStatus::Active);
2604
2605            AuthorsMap::mutate(ALICE, |author| {
2606                let info = author.as_mut().unwrap();
2607                let risk = &mut info.risk_until;
2608                *risk = 32
2609            });
2610            // since the risk_until is high, update the status of author
2611            // AuthorStatus::Active -> AuthorStatus::Probation
2612            assert_ok!(Pallet::set_status(&ALICE, AuthorStatus::Probation));
2613
2614            System::set_block_number(42);
2615            // after the probation period, update the status of author
2616            // AuthorStatus::Probation -> AuthorStatus::Active
2617            assert_ok!(Pallet::set_status(&ALICE, AuthorStatus::Active));
2618            let current_status = Pallet::get_status(&ALICE).unwrap();
2619            assert_eq!(current_status, AuthorStatus::Active);
2620
2621            System::set_block_number(45);
2622            // update the status of author
2623            // AuthorStatus::Active -> AuthorStatus::Resign
2624            assert_ok!(Pallet::set_status(&ALICE, AuthorStatus::Resigned));
2625            let current_status = Pallet::get_status(&ALICE).unwrap();
2626            assert_eq!(current_status, AuthorStatus::Resigned);
2627        })
2628    }
2629
2630    #[test]
2631    fn set_status_success_from_resigned() {
2632        authors_test_ext().execute_with(|| {
2633            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2634            System::set_block_number(6);
2635            Pallet::enroll(&ALICE, LARGE_VALUE, Fortitude::Force).unwrap();
2636
2637            AuthorsMap::mutate(ALICE, |author| {
2638                let info = author.as_mut().unwrap();
2639                let status = &mut info.status;
2640                *status = AuthorStatus::Resigned
2641            });
2642
2643            let current_status = Pallet::get_status(&ALICE).unwrap();
2644
2645            assert_eq!(current_status, AuthorStatus::Resigned);
2646
2647            // updating the status of author from AuthorStatus::Resigned -> AuthorStatus::Probation
2648            // will cause error as resigned authors can be reactivated only through enrollment
2649            assert_err! {
2650                Pallet::set_status(
2651                &ALICE,
2652                AuthorStatus::Probation
2653            ), Error::AuthorResigned
2654            };
2655
2656            // updating the status of author from AuthorStatus::Resigned -> AuthorStatus::Active
2657            // also will cause error as resigned authors can be reactivated only through enrollment
2658            assert_err! {
2659                Pallet::set_status(
2660                &ALICE,
2661                AuthorStatus::Active
2662            ), Error::AuthorResigned
2663            };
2664
2665            // updating the status of author from AuthorStatus::Resigned -> AuthorStatus::Active is no-op
2666            assert_ok! {
2667                    Pallet::set_status(
2668                    &ALICE,
2669                    AuthorStatus::Resigned
2670            )};
2671        })
2672    }
2673
2674    #[test]
2675    fn resign_success() {
2676        authors_test_ext().execute_with(|| {
2677            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2678            System::set_block_number(6);
2679            Pallet::enroll(&ALICE, LARGE_VALUE, Fortitude::Force).unwrap();
2680
2681            AuthorsMap::mutate(ALICE, |author| {
2682                let info = author.as_mut().unwrap();
2683                let status = &mut info.status;
2684                *status = AuthorStatus::Active
2685            });
2686
2687            let current_collateral = Pallet::get_collateral(&ALICE).unwrap();
2688            assert_eq!(current_collateral, LARGE_VALUE);
2689
2690            let author_bal = get_user_balance(&ALICE);
2691            assert_eq!(author_bal, 100);
2692
2693            // Set author as in-active
2694            set_activity_state(false);
2695            assert_ok!(Pallet::resign(&ALICE));
2696
2697            assert_eq!(Pallet::get_status(&ALICE), Ok(AuthorStatus::Resigned));
2698
2699            let author_bal = get_user_balance(&ALICE);
2700            assert_eq!(author_bal, 200); // existing balance + collateral
2701            System::assert_last_event(Event::AuthorResigned { author: ALICE, released: 100 }.into());
2702        })
2703    }
2704
2705    #[test]
2706    fn add_collateral_success() {
2707        authors_test_ext().execute_with(|| {
2708            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2709            System::set_block_number(6);
2710            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
2711            let current_collateral = Pallet::get_collateral(&ALICE).unwrap();
2712            assert_eq!(current_collateral, 50);
2713
2714            assert_ok!(Pallet::add_collateral(
2715                &ALICE,
2716                STANDARD_VALUE,
2717                Fortitude::Force
2718            ));
2719            // collateral increaced by 50
2720            let current_collateral = Pallet::get_collateral(&ALICE).unwrap();
2721            assert_eq!(current_collateral, 100);
2722            System::assert_last_event(Event::AuthorCollateralRaised { author: ALICE, raised: STANDARD_VALUE }.into());
2723        })
2724    }
2725
2726    #[test]
2727    fn is_available_success() {
2728        authors_test_ext().execute_with(|| {
2729            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2730            System::set_block_number(6);
2731            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
2732
2733            assert_ok!(Pallet::is_available(&ALICE));
2734        })
2735    }
2736
2737    #[test]
2738    fn is_available_err_author_needs_more_collateral() {
2739        authors_test_ext().execute_with(|| {
2740            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2741            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
2742
2743            System::set_block_number(6);
2744            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
2745            Pallet::fund(
2746                &ALICE,
2747                &Funder::Direct(BOB),
2748                25,
2749                Precision::BestEffort,
2750                Fortitude::Force,
2751            )
2752            .unwrap();
2753
2754            Pallet::set_hold(&ALICE, 15, Precision::Exact, Fortitude::Force).unwrap();
2755
2756            assert_err!(
2757                Pallet::is_available(&ALICE),
2758                Error::AuthorNeedsMoreCollateral
2759            );
2760        })
2761    }
2762
2763    #[test]
2764    fn is_available_err_author_resigned() {
2765        authors_test_ext().execute_with(|| {
2766            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2767            System::set_block_number(6);
2768            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
2769
2770            AuthorsMap::mutate(ALICE, |author| {
2771                let info = author.as_mut().unwrap();
2772                let status = &mut info.status;
2773                *status = AuthorStatus::Resigned
2774            });
2775
2776            assert_err!(Pallet::is_available(&ALICE), Error::AuthorResigned);
2777        })
2778    }
2779
2780    #[test]
2781    fn on_enroll_emit_event_success() {
2782        authors_test_ext().execute_with(|| {
2783            System::set_block_number(10);
2784            let collateral = LARGE_VALUE;
2785            Pallet::on_enroll(&ALICE, collateral);
2786
2787            System::assert_last_event(
2788                Event::AuthorEnlisted {
2789                    author: ALICE,
2790                    collateral,
2791                }
2792                .into(),
2793            );
2794        })
2795    }
2796
2797    #[test]
2798    fn on_resign_emit_event_suucess() {
2799        authors_test_ext().execute_with(|| {
2800            System::set_block_number(10);
2801            let released = LARGE_VALUE;
2802            Pallet::on_resign(&ALICE, released);
2803
2804            System::assert_last_event(
2805                Event::AuthorResigned {
2806                    author: ALICE,
2807                    released,
2808                }
2809                .into(),
2810            );
2811        })
2812    }
2813
2814    #[test]
2815    fn on_add_collateral_emit_event_suucess() {
2816        authors_test_ext().execute_with(|| {
2817            System::set_block_number(10);
2818            let raised = LARGE_VALUE;
2819            Pallet::on_add_collateral(&ALICE, raised);
2820
2821            System::assert_last_event(
2822                Event::AuthorCollateralRaised {
2823                    author: ALICE,
2824                    raised,
2825                }
2826                .into(),
2827            );
2828        })
2829    }
2830
2831    #[test]
2832    fn on_status_update_emit_event_suucess() {
2833        authors_test_ext().execute_with(|| {
2834            System::set_block_number(10);
2835            let status = AuthorStatus::Active;
2836            Pallet::on_status_update(&ALICE, &status);
2837
2838            System::assert_last_event(
2839                Event::AuthorStatus {
2840                    author: ALICE,
2841                    status,
2842                }
2843                .into(),
2844            );
2845        })
2846    }
2847
2848    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2849    // ````````````````````````````````` FUND ROLES ``````````````````````````````````
2850    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2851
2852    #[test]
2853    fn has_funds_success() {
2854        authors_test_ext().execute_with(|| {
2855            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2856            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
2857            System::set_block_number(6);
2858            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
2859
2860            Pallet::fund(
2861                &ALICE,
2862                &Funder::Direct(BOB),
2863                STANDARD_VALUE,
2864                Precision::BestEffort,
2865                Fortitude::Force,
2866            )
2867            .unwrap();
2868
2869            assert_ok!(Pallet::has_funds(&ALICE));
2870        })
2871    }
2872
2873    #[test]
2874    fn has_funds_err_fund_does_not_exist() {
2875        authors_test_ext().execute_with(|| {
2876            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2877            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
2878            System::set_block_number(6);
2879            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
2880
2881            assert_err!(Pallet::has_funds(&ALICE), Error::FundDoesNotExist);
2882        })
2883    }
2884
2885    #[test]
2886    fn can_fund_success() {
2887        authors_test_ext().execute_with(|| {
2888            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2889            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
2890            System::set_block_number(6);
2891            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
2892
2893            assert_ok!(Pallet::can_fund(
2894                &Funder::Direct(BOB),
2895                &ALICE,
2896                STANDARD_VALUE,
2897                Precision::Exact,
2898                Fortitude::Force
2899            ));
2900        })
2901    }
2902
2903    #[test]
2904    fn can_fund_err_below_minimum_fund() {
2905        authors_test_ext().execute_with(|| {
2906            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2907            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
2908            System::set_block_number(6);
2909            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
2910
2911            assert_err!(
2912                Pallet::can_fund(
2913                    &Funder::Direct(BOB),
2914                    &ALICE,
2915                    MIN_VALUE,
2916                    Precision::Exact,
2917                    Fortitude::Force
2918                ),
2919                Error::BelowMinimumFund
2920            );
2921        })
2922    }
2923
2924    #[test]
2925    fn can_fund_err_above_maximum_exposure() {
2926        authors_test_ext().execute_with(|| {
2927            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2928            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
2929            System::set_block_number(6);
2930            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
2931
2932            assert_err!(
2933                Pallet::can_fund(
2934                    &Funder::Direct(BOB),
2935                    &ALICE,
2936                    1100, // 1100 which is higher than the
2937                    // max_exposure which is set to be 1000 in mock.rs
2938                    Precision::Exact,
2939                    Fortitude::Force
2940                ),
2941                Error::AboveMaximumExposure
2942            );
2943        })
2944    }
2945
2946    #[test]
2947    fn can_fund_err_fund_to_another_digest() {
2948        authors_test_ext().execute_with(|| {
2949            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2950            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
2951            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
2952            System::set_block_number(6);
2953            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
2954            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
2955            Pallet::fund(
2956                &ALICE,
2957                &Funder::Direct(CHARLIE),
2958                SMALL_VALUE,
2959                Precision::BestEffort,
2960                Fortitude::Force,
2961            )
2962            .unwrap();
2963
2964            assert_err!(
2965                Pallet::can_fund(
2966                    &Funder::Direct(CHARLIE),
2967                    &BOB,
2968                    LARGE_VALUE,
2969                    Precision::Exact,
2970                    Fortitude::Force
2971                ),
2972                Error::FundedToAnotherDigest
2973            );
2974        })
2975    }
2976
2977    #[test]
2978    fn can_draw_success() {
2979        authors_test_ext().execute_with(|| {
2980            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
2981            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
2982            System::set_block_number(6);
2983            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
2984
2985            Pallet::fund(
2986                &ALICE,
2987                &Funder::Direct(BOB),
2988                SMALL_VALUE,
2989                Precision::BestEffort,
2990                Fortitude::Force,
2991            )
2992            .unwrap();
2993
2994            assert_ok!(Pallet::can_draw(&Funder::Direct(BOB), &ALICE,));
2995        })
2996    }
2997
2998    #[test]
2999    fn can_draw_err_fund_to_another_digest() {
3000        authors_test_ext().execute_with(|| {
3001            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3002            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3003            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3004            System::set_block_number(6);
3005            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3006            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
3007            Pallet::fund(
3008                &ALICE,
3009                &Funder::Direct(CHARLIE),
3010                SMALL_VALUE,
3011                Precision::BestEffort,
3012                Fortitude::Force,
3013            )
3014            .unwrap();
3015
3016            assert_err!(
3017                Pallet::can_draw(&Funder::Direct(CHARLIE), &BOB),
3018                Error::FundedToAnotherDigest
3019            );
3020        })
3021    }
3022
3023    #[test]
3024    fn max_exposure_success() {
3025        authors_test_ext().execute_with(|| {
3026            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3027            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3028            System::set_block_number(6);
3029            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3030
3031            let max_exposure = Pallet::max_exposure(
3032                &Funder::Direct(BOB),
3033                &ALICE,
3034                Precision::Exact,
3035                Fortitude::Force,
3036            )
3037            .unwrap();
3038            assert_eq!(MaxExposure::get(), max_exposure);
3039        })
3040    }
3041
3042    #[test]
3043    fn min_fund_success() {
3044        authors_test_ext().execute_with(|| {
3045            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3046            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3047            System::set_block_number(6);
3048            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3049
3050            let min_fund = Pallet::min_fund(
3051                &Funder::Direct(BOB),
3052                &ALICE,
3053                Precision::Exact,
3054                Fortitude::Force,
3055            )
3056            .unwrap();
3057            assert_eq!(MinFund::get(), min_fund);
3058        })
3059    }
3060
3061    #[test]
3062    fn backed_value_success() {
3063        authors_test_ext().execute_with(|| {
3064            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3065            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3066            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3067            System::set_block_number(6);
3068            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3069
3070            // BOB backed ALICE with 50 units
3071            Pallet::fund(
3072                &ALICE,
3073                &Funder::Direct(BOB),
3074                STANDARD_VALUE,
3075                Precision::BestEffort,
3076                Fortitude::Force,
3077            )
3078            .unwrap();
3079
3080            let current_backed_val = Pallet::backed_value(&ALICE).unwrap();
3081            assert_eq!(current_backed_val, 50);
3082
3083            // CHARLIE backed ALICE with 25 units
3084            Pallet::fund(
3085                &ALICE,
3086                &Funder::Direct(CHARLIE),
3087                SMALL_VALUE,
3088                Precision::BestEffort,
3089                Fortitude::Force,
3090            )
3091            .unwrap();
3092
3093            let current_backed_val = Pallet::backed_value(&ALICE).unwrap();
3094            assert_eq!(current_backed_val, 75); // BOB + CHARLIE = 50 + 25 -> 75
3095        })
3096    }
3097
3098    #[test]
3099    fn total_backing_sucess() {
3100        authors_test_ext().execute_with(|| {
3101            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3102            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3103            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3104            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
3105            System::set_block_number(6);
3106            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3107
3108            System::set_block_number(10);
3109            Pallet::enroll(&MIKE, STANDARD_VALUE, Fortitude::Force).unwrap();
3110
3111            // BOB backed ALICE with 50 units
3112            Pallet::fund(
3113                &ALICE,
3114                &Funder::Direct(BOB),
3115                STANDARD_VALUE,
3116                Precision::BestEffort,
3117                Fortitude::Force,
3118            )
3119            .unwrap();
3120
3121            let current_total_backed_val = Pallet::total_backing();
3122            assert_eq!(current_total_backed_val, 50);
3123
3124            // CHARLIE backed MIKE with 100 units
3125            Pallet::fund(
3126                &MIKE,
3127                &Funder::Direct(CHARLIE),
3128                LARGE_VALUE,
3129                Precision::BestEffort,
3130                Fortitude::Force,
3131            )
3132            .unwrap();
3133
3134            // BOB increase the backing by 25 units
3135            Pallet::fund(
3136                &ALICE,
3137                &Funder::Direct(BOB),
3138                SMALL_VALUE,
3139                Precision::BestEffort,
3140                Fortitude::Force,
3141            )
3142            .unwrap();
3143
3144            let current_total_backed_val = Pallet::total_backing();
3145            assert_eq!(current_total_backed_val, 175); // ALICE + MIKE = (50 + 25) + 100  -> 175
3146        })
3147    }
3148
3149    #[test]
3150    fn backers_of_success_for_direct() {
3151        authors_test_ext().execute_with(|| {
3152            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3153            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3154            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3155            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
3156            System::set_block_number(6);
3157            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3158
3159            System::set_block_number(10);
3160            Pallet::enroll(&MIKE, STANDARD_VALUE, Fortitude::Force).unwrap();
3161
3162            // BOB backed ALICE with 50 units
3163            Pallet::fund(
3164                &ALICE,
3165                &Funder::Direct(BOB),
3166                STANDARD_VALUE,
3167                Precision::BestEffort,
3168                Fortitude::Force,
3169            )
3170            .unwrap();
3171
3172            // CHARLIE backed ALICE with 100 units
3173            Pallet::fund(
3174                &ALICE,
3175                &Funder::Direct(CHARLIE),
3176                LARGE_VALUE,
3177                Precision::BestEffort,
3178                Fortitude::Force,
3179            )
3180            .unwrap();
3181
3182            // MIKE backed ALICE with 25 units
3183            Pallet::fund(
3184                &ALICE,
3185                &Funder::Direct(MIKE),
3186                SMALL_VALUE,
3187                Precision::BestEffort,
3188                Fortitude::Force,
3189            )
3190            .unwrap();
3191
3192            let actual_backers_of = Pallet::backers_of(&ALICE).unwrap();
3193            let expected_backers_of = vec![
3194                (Funder::Direct(BOB), 50),
3195                (Funder::Direct(MIKE), 25),
3196                (Funder::Direct(CHARLIE), 100),
3197            ];
3198            assert_eq!(actual_backers_of, expected_backers_of);
3199        })
3200    }
3201
3202    #[test]
3203    fn backers_of_success_for_index() {
3204        authors_test_ext().execute_with(|| {
3205            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3206            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3207            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3208            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
3209            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
3210
3211            System::set_block_number(6);
3212            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3213
3214            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
3215
3216            Pallet::fund(
3217                &ALICE,
3218                &Funder::Direct(CHARLIE),
3219                STANDARD_VALUE,
3220                Precision::Exact,
3221                Fortitude::Force,
3222            )
3223            .unwrap();
3224
3225            Pallet::fund(
3226                &BOB,
3227                &Funder::Direct(ALAN),
3228                LARGE_VALUE,
3229                Precision::Exact,
3230                Fortitude::Force,
3231            )
3232            .unwrap();
3233
3234            let alice_digest = gen_author_digest(&ALICE).unwrap();
3235            let bob_digest = gen_author_digest(&BOB).unwrap();
3236            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
3237
3238            prepare_and_initiate_index(MIKE, FUNDING.into(), &entries, INDEX_DIGEST).unwrap();
3239
3240            let by = Funder::Index {
3241                digest: INDEX_DIGEST,
3242                backer: MIKE,
3243            };
3244            Pallet::fund(&ALICE, &by, LARGE_VALUE, Precision::Exact, Fortitude::Force).unwrap();
3245
3246            let backers_of_alice = Pallet::backers_of(&ALICE).unwrap();
3247            let expected_backers_of_alice = vec![(by.clone(), 60), (Funder::Direct(CHARLIE), 50)];
3248            assert_eq!(backers_of_alice, expected_backers_of_alice);
3249
3250            let backers_of_bob = Pallet::backers_of(&BOB).unwrap();
3251            let expected_backers_of_bob = vec![(by, 40), (Funder::Direct(ALAN), 100)];
3252            assert_eq!(backers_of_bob, expected_backers_of_bob);
3253        })
3254    }
3255
3256    #[test]
3257    fn backers_of_success_for_pool() {
3258        authors_test_ext().execute_with(|| {
3259            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3260            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3261            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3262            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
3263            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
3264
3265            System::set_block_number(6);
3266            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3267
3268            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
3269
3270            Pallet::fund(
3271                &ALICE,
3272                &Funder::Direct(CHARLIE),
3273                STANDARD_VALUE,
3274                Precision::Exact,
3275                Fortitude::Force,
3276            )
3277            .unwrap();
3278
3279            Pallet::fund(
3280                &BOB,
3281                &Funder::Direct(ALAN),
3282                LARGE_VALUE,
3283                Precision::Exact,
3284                Fortitude::Force,
3285            )
3286            .unwrap();
3287
3288            let alice_digest = gen_author_digest(&ALICE).unwrap();
3289            let bob_digest = gen_author_digest(&BOB).unwrap();
3290            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
3291
3292            prepare_and_initiate_pool(
3293                MIKE,
3294                FUNDING.into(),
3295                &entries,
3296                INDEX_DIGEST,
3297                POOL_DIGEST,
3298                Perbill::from_percent(5),
3299            )
3300            .unwrap();
3301
3302            let by = Funder::Pool {
3303                digest: POOL_DIGEST,
3304                backer: MIKE,
3305            };
3306
3307            Pallet::fund(&ALICE, &by, LARGE_VALUE, Precision::Exact, Fortitude::Force).unwrap();
3308
3309            let backers_of_alice = Pallet::backers_of(&ALICE).unwrap();
3310            let expected_backers_of_alice = vec![(by.clone(), 60), (Funder::Direct(CHARLIE), 50)];
3311            assert_eq!(backers_of_alice, expected_backers_of_alice);
3312
3313            let backers_of_bob = Pallet::backers_of(&BOB).unwrap();
3314            let expected_backers_of_bob = vec![(by, 40), (Funder::Direct(ALAN), 100)];
3315            assert_eq!(backers_of_bob, expected_backers_of_bob);
3316        })
3317    }
3318
3319    #[test]
3320    fn backed_for_success_for_direct() {
3321        authors_test_ext().execute_with(|| {
3322            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3323            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3324            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3325            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
3326            System::set_block_number(6);
3327            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3328
3329            System::set_block_number(10);
3330            Pallet::enroll(&MIKE, STANDARD_VALUE, Fortitude::Force).unwrap();
3331
3332            // BOB backed ALICE with 50 units
3333            Pallet::fund(
3334                &ALICE,
3335                &Funder::Direct(BOB),
3336                STANDARD_VALUE,
3337                Precision::BestEffort,
3338                Fortitude::Force,
3339            )
3340            .unwrap();
3341
3342            let current_total_backed_val = Pallet::total_backing();
3343            assert_eq!(current_total_backed_val, 50);
3344
3345            // CHARLIE backed MIKE with 100 units
3346            Pallet::fund(
3347                &MIKE,
3348                &Funder::Direct(CHARLIE),
3349                LARGE_VALUE,
3350                Precision::BestEffort,
3351                Fortitude::Force,
3352            )
3353            .unwrap();
3354
3355            let bob_backed_for = Pallet::backed_for(&Funder::Direct(BOB)).unwrap();
3356            let expected_author = vec![(ALICE, 50)];
3357            assert_eq!(bob_backed_for, expected_author);
3358
3359            let charlie_backed_for = Pallet::backed_for(&Funder::Direct(CHARLIE)).unwrap();
3360            let expected_author = vec![(MIKE, 100)];
3361            assert_eq!(charlie_backed_for, expected_author);
3362        })
3363    }
3364
3365    #[test]
3366    fn backed_for_success_for_index() {
3367        authors_test_ext().execute_with(|| {
3368            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3369            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3370            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3371            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
3372            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
3373
3374            System::set_block_number(6);
3375            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3376
3377            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
3378
3379            Pallet::fund(
3380                &ALICE,
3381                &Funder::Direct(CHARLIE),
3382                STANDARD_VALUE,
3383                Precision::Exact,
3384                Fortitude::Force,
3385            )
3386            .unwrap();
3387
3388            Pallet::fund(
3389                &BOB,
3390                &Funder::Direct(ALAN),
3391                LARGE_VALUE,
3392                Precision::Exact,
3393                Fortitude::Force,
3394            )
3395            .unwrap();
3396
3397            let alice_digest = gen_author_digest(&ALICE).unwrap();
3398            let bob_digest = gen_author_digest(&BOB).unwrap();
3399            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
3400
3401            prepare_and_initiate_index(MIKE, FUNDING.into(), &entries, INDEX_DIGEST).unwrap();
3402
3403            let by = Funder::Index {
3404                digest: INDEX_DIGEST,
3405                backer: MIKE,
3406            };
3407            Pallet::fund(&ALICE, &by, LARGE_VALUE, Precision::Exact, Fortitude::Force).unwrap();
3408
3409            let backed_for = Pallet::backed_for(&by).unwrap();
3410            let expected_backed_for = vec![(ALICE, 60), (BOB, 40)];
3411            assert_eq!(backed_for, expected_backed_for);
3412        })
3413    }
3414
3415    #[test]
3416    fn backed_for_success_for_pool() {
3417        authors_test_ext().execute_with(|| {
3418            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3419            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3420            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3421            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
3422            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
3423
3424            System::set_block_number(6);
3425            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3426
3427            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
3428
3429            Pallet::fund(
3430                &ALICE,
3431                &Funder::Direct(CHARLIE),
3432                STANDARD_VALUE,
3433                Precision::Exact,
3434                Fortitude::Force,
3435            )
3436            .unwrap();
3437
3438            Pallet::fund(
3439                &BOB,
3440                &Funder::Direct(ALAN),
3441                LARGE_VALUE,
3442                Precision::Exact,
3443                Fortitude::Force,
3444            )
3445            .unwrap();
3446
3447            let alice_digest = gen_author_digest(&ALICE).unwrap();
3448            let bob_digest = gen_author_digest(&BOB).unwrap();
3449            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
3450
3451            prepare_and_initiate_pool(
3452                MIKE,
3453                FUNDING.into(),
3454                &entries,
3455                INDEX_DIGEST,
3456                POOL_DIGEST,
3457                Perbill::from_percent(5),
3458            )
3459            .unwrap();
3460
3461            let by = Funder::Pool {
3462                digest: POOL_DIGEST,
3463                backer: MIKE,
3464            };
3465            Pallet::fund(&ALICE, &by, LARGE_VALUE, Precision::Exact, Fortitude::Force).unwrap();
3466
3467            let backed_for = Pallet::backed_for(&by).unwrap();
3468            let expected_backed_for = vec![(ALICE, 60), (BOB, 40)];
3469            assert_eq!(backed_for, expected_backed_for);
3470        })
3471    }
3472
3473    #[test]
3474    fn get_fund_success_for_direct() {
3475        authors_test_ext().execute_with(|| {
3476            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3477            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3478            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3479            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
3480            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
3481            System::set_block_number(6);
3482            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3483
3484            System::set_block_number(10);
3485            Pallet::enroll(&MIKE, STANDARD_VALUE, Fortitude::Force).unwrap();
3486
3487            // BOB backed ALICE with 50 units
3488            Pallet::fund(
3489                &ALICE,
3490                &Funder::Direct(BOB),
3491                STANDARD_VALUE,
3492                Precision::BestEffort,
3493                Fortitude::Force,
3494            )
3495            .unwrap();
3496
3497            let current_total_backed_val = Pallet::total_backing();
3498            assert_eq!(current_total_backed_val, 50);
3499
3500            // CHARLIE backed MIKE with 100 units
3501            Pallet::fund(
3502                &MIKE,
3503                &Funder::Direct(CHARLIE),
3504                LARGE_VALUE,
3505                Precision::BestEffort,
3506                Fortitude::Force,
3507            )
3508            .unwrap();
3509
3510            // BOB increase the backing by 25 units
3511            Pallet::fund(
3512                &ALICE,
3513                &Funder::Direct(BOB),
3514                SMALL_VALUE,
3515                Precision::BestEffort,
3516                Fortitude::Force,
3517            )
3518            .unwrap();
3519
3520            let bobs_fund_to_alice = Pallet::get_fund(&ALICE, &Funder::Direct(BOB)).unwrap();
3521            assert_eq!(bobs_fund_to_alice, 75); // 50 + 25
3522
3523            let charlies_fund_to_mike = Pallet::get_fund(&MIKE, &Funder::Direct(CHARLIE)).unwrap();
3524            assert_eq!(charlies_fund_to_mike, 100);
3525        })
3526    }
3527
3528    #[test]
3529    fn get_fund_err_author_not_found() {
3530        authors_test_ext().execute_with(|| {
3531            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3532            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3533            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
3534
3535            System::set_block_number(6);
3536            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3537
3538            System::set_block_number(10);
3539            Pallet::enroll(&ALAN, STANDARD_VALUE, Fortitude::Force).unwrap();
3540
3541            Pallet::fund(
3542                &ALICE,
3543                &Funder::Direct(BOB),
3544                LARGE_VALUE,
3545                Precision::BestEffort,
3546                Fortitude::Force,
3547            )
3548            .unwrap();
3549
3550            assert_err!(
3551                Pallet::get_fund(&ALAN, &Funder::Direct(BOB)),
3552                Error::FundedToAnotherDigest
3553            );
3554        })
3555    }
3556
3557    #[test]
3558    fn get_fund_success_for_index() {
3559        authors_test_ext().execute_with(|| {
3560            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3561            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3562            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3563            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
3564            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
3565            System::set_block_number(6);
3566            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3567
3568            System::set_block_number(10);
3569            Pallet::enroll(&MIKE, STANDARD_VALUE, Fortitude::Force).unwrap();
3570
3571            // BOB backed ALICE with 50 units
3572            Pallet::fund(
3573                &ALICE,
3574                &Funder::Direct(BOB),
3575                STANDARD_VALUE,
3576                Precision::BestEffort,
3577                Fortitude::Force,
3578            )
3579            .unwrap();
3580
3581            let current_total_backed_val = Pallet::total_backing();
3582            assert_eq!(current_total_backed_val, 50);
3583
3584            // CHARLIE backed MIKE with 100 units
3585            Pallet::fund(
3586                &MIKE,
3587                &Funder::Direct(CHARLIE),
3588                LARGE_VALUE,
3589                Precision::BestEffort,
3590                Fortitude::Force,
3591            )
3592            .unwrap();
3593
3594            let alice_digest = gen_author_digest(&ALICE).unwrap();
3595            let mike_digest = gen_author_digest(&MIKE).unwrap();
3596            let entries = vec![(alice_digest.clone(), 60), (mike_digest.clone(), 40)];
3597
3598            // ALAN initates a index with both authors as an entry and funds the index
3599            prepare_and_initiate_index(ALAN, FUNDING.into(), &entries, INDEX_DIGEST).unwrap();
3600
3601            let by = Funder::Index {
3602                digest: INDEX_DIGEST,
3603                backer: ALAN,
3604            };
3605            Pallet::fund(&ALICE, &by, LARGE_VALUE, Precision::Exact, Fortitude::Force).unwrap();
3606
3607            let bobs_fund_to_index_alice = Pallet::get_fund(&ALICE, &by).unwrap();
3608            assert_eq!(bobs_fund_to_index_alice, 60);
3609            let bobs_fund_to_index_bob = Pallet::get_fund(&MIKE, &by).unwrap();
3610            assert_eq!(bobs_fund_to_index_bob, 40);
3611        })
3612    }
3613
3614    #[test]
3615    fn get_fund_success_for_pool() {
3616        authors_test_ext().execute_with(|| {
3617            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3618            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3619            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3620            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
3621            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
3622
3623            System::set_block_number(6);
3624            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3625
3626            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
3627
3628            Pallet::fund(
3629                &ALICE,
3630                &Funder::Direct(CHARLIE),
3631                STANDARD_VALUE,
3632                Precision::Exact,
3633                Fortitude::Force,
3634            )
3635            .unwrap();
3636
3637            Pallet::fund(
3638                &BOB,
3639                &Funder::Direct(ALAN),
3640                LARGE_VALUE,
3641                Precision::Exact,
3642                Fortitude::Force,
3643            )
3644            .unwrap();
3645
3646            let alice_digest = gen_author_digest(&ALICE).unwrap();
3647            let bob_digest = gen_author_digest(&BOB).unwrap();
3648            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
3649
3650            prepare_and_initiate_pool(
3651                MIKE,
3652                FUNDING.into(),
3653                &entries,
3654                INDEX_DIGEST,
3655                POOL_DIGEST,
3656                Perbill::from_percent(5),
3657            )
3658            .unwrap();
3659
3660            let by = Funder::Pool {
3661                digest: POOL_DIGEST,
3662                backer: MIKE,
3663            };
3664
3665            Pallet::fund(&ALICE, &by, LARGE_VALUE, Precision::Exact, Fortitude::Force).unwrap();
3666
3667            let bobs_fund_to_index_alice = Pallet::get_fund(&ALICE, &by).unwrap();
3668            assert_eq!(bobs_fund_to_index_alice, 60);
3669            let bobs_fund_to_index_bob = Pallet::get_fund(&BOB, &by).unwrap();
3670            assert_eq!(bobs_fund_to_index_bob, 40);
3671        })
3672    }
3673
3674    #[test]
3675    fn fund_success() {
3676        authors_test_ext().execute_with(|| {
3677            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3678            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3679            System::set_block_number(6);
3680            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3681
3682            // BOB backed ALICE with 50 units
3683            assert_ok!(Pallet::fund(
3684                &ALICE,
3685                &Funder::Direct(BOB),
3686                STANDARD_VALUE,
3687                Precision::BestEffort,
3688                Fortitude::Force,
3689            ));
3690
3691            let funds_backed = Pallet::get_fund(&ALICE, &Funder::Direct(BOB)).unwrap();
3692            assert_eq!(funds_backed, STANDARD_VALUE);
3693
3694            // Raise backing by 25 units
3695            assert_ok!(Pallet::fund(
3696                &ALICE,
3697                &Funder::Direct(BOB),
3698                SMALL_VALUE,
3699                Precision::BestEffort,
3700                Fortitude::Force,
3701            ));
3702
3703            let funds_backed = Pallet::get_fund(&ALICE, &Funder::Direct(BOB)).unwrap();
3704            assert_eq!(funds_backed, 75); // 50 + 25
3705
3706            let author_funders = AuthorFunders::get((ALICE, BOB)).unwrap();
3707            assert_eq!(author_funders, Funder::Direct(BOB));
3708            System::assert_last_event(Event::AuthorFunded { author: ALICE, backer: BOB, amount: SMALL_VALUE }.into());
3709        })
3710    }
3711
3712    #[test]
3713    fn fund_success_for_index() {
3714        authors_test_ext().execute_with(|| {
3715            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3716            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3717            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3718            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
3719            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
3720
3721            System::set_block_number(6);
3722            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3723
3724            System::set_block_number(8);
3725            Pallet::fund(
3726                &ALICE,
3727                &Funder::Direct(CHARLIE),
3728                STANDARD_VALUE,
3729                Precision::Exact,
3730                Fortitude::Force,
3731            )
3732            .unwrap();
3733
3734            System::set_block_number(12);
3735            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
3736
3737            System::set_block_number(15);
3738            Pallet::fund(
3739                &BOB,
3740                &Funder::Direct(ALAN),
3741                LARGE_VALUE,
3742                Precision::Exact,
3743                Fortitude::Force,
3744            )
3745            .unwrap();
3746
3747            let total_backing = Pallet::total_backing();
3748            assert_eq!(total_backing, 150);
3749            let alice_backed_value = Pallet::backed_value(&ALICE).unwrap();
3750            assert_eq!(alice_backed_value, 50);
3751            let bob_backed_value = Pallet::backed_value(&BOB).unwrap();
3752            assert_eq!(bob_backed_value, 100);
3753
3754            let alice_digest = gen_author_digest(&ALICE).unwrap();
3755            let bob_digest = gen_author_digest(&BOB).unwrap();
3756            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
3757
3758            let alice_current_hold = Pallet::get_hold(&ALICE).unwrap();
3759            assert_eq!(alice_current_hold, 100);
3760            let bob_current_hold = Pallet::get_hold(&BOB).unwrap();
3761            assert_eq!(bob_current_hold, 150);
3762
3763            prepare_and_initiate_index(MIKE, FUNDING.into(), &entries, INDEX_DIGEST).unwrap();
3764
3765            let by = Funder::Index {
3766                digest: INDEX_DIGEST,
3767                backer: MIKE,
3768            };
3769
3770            assert_ok!(Pallet::fund(
3771                &ALICE,
3772                &by,
3773                LARGE_VALUE,
3774                Precision::Exact,
3775                Fortitude::Force,
3776            ));
3777
3778            let total_backing = Pallet::total_backing();
3779            assert_eq!(total_backing, 250);
3780            let alice_backed_value = Pallet::backed_value(&ALICE).unwrap();
3781            assert_eq!(alice_backed_value, 110); // 50 (existing) + 60 (through index as ALICE share is 60 )
3782            let bob_backed_value = Pallet::backed_value(&BOB).unwrap();
3783            assert_eq!(bob_backed_value, 140); // 100 (existing) + 40 (through index as BOB share is 40 )
3784
3785            let author_funders = AuthorFunders::get((ALICE, MIKE)).unwrap();
3786            assert_eq!(author_funders, by);
3787
3788            let alice_current_hold = Pallet::get_hold(&ALICE).unwrap();
3789            assert_eq!(alice_current_hold, 160);
3790            let bob_current_hold = Pallet::get_hold(&BOB).unwrap();
3791            assert_eq!(bob_current_hold, 190);
3792            System::assert_last_event(Event::IndexFunded { index: INDEX_DIGEST, backer: MIKE, amount: LARGE_VALUE }.into());
3793        })
3794    }
3795
3796    #[test]
3797    fn fund_success_for_pool() {
3798        authors_test_ext().execute_with(|| {
3799            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3800            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3801            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3802            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
3803            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
3804
3805            System::set_block_number(6);
3806            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3807
3808            System::set_block_number(8);
3809            Pallet::fund(
3810                &ALICE,
3811                &Funder::Direct(CHARLIE),
3812                STANDARD_VALUE,
3813                Precision::Exact,
3814                Fortitude::Force,
3815            )
3816            .unwrap();
3817
3818            System::set_block_number(12);
3819            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
3820
3821            System::set_block_number(15);
3822            Pallet::fund(
3823                &BOB,
3824                &Funder::Direct(ALAN),
3825                LARGE_VALUE,
3826                Precision::Exact,
3827                Fortitude::Force,
3828            )
3829            .unwrap();
3830
3831            let total_backing = Pallet::total_backing();
3832            assert_eq!(total_backing, 150);
3833            let alice_backed_value = Pallet::backed_value(&ALICE).unwrap();
3834            assert_eq!(alice_backed_value, 50);
3835            let bob_backed_value = Pallet::backed_value(&BOB).unwrap();
3836            assert_eq!(bob_backed_value, 100);
3837
3838            let alice_digest = gen_author_digest(&ALICE).unwrap();
3839            let bob_digest = gen_author_digest(&BOB).unwrap();
3840            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
3841
3842            let alice_current_hold = Pallet::get_hold(&ALICE).unwrap();
3843            assert_eq!(alice_current_hold, 100);
3844            let bob_current_hold = Pallet::get_hold(&BOB).unwrap();
3845            assert_eq!(bob_current_hold, 150);
3846
3847            prepare_and_initiate_pool(
3848                ALAN,
3849                FUNDING.into(),
3850                &entries,
3851                INDEX_DIGEST,
3852                POOL_DIGEST,
3853                Perbill::from_percent(5),
3854            )
3855            .unwrap();
3856
3857            let by = Funder::Pool {
3858                digest: POOL_DIGEST,
3859                backer: MIKE,
3860            };
3861
3862            assert_ok!(Pallet::fund(
3863                &ALICE,
3864                &by,
3865                LARGE_VALUE,
3866                Precision::Exact,
3867                Fortitude::Force,
3868            ));
3869
3870            let total_backing = Pallet::total_backing();
3871            assert_eq!(total_backing, 250);
3872            let alice_backed_value = Pallet::backed_value(&ALICE).unwrap();
3873            assert_eq!(alice_backed_value, 110); // 50 (existing) + 60 (through index as ALICE share is 60 )
3874            let bob_backed_value = Pallet::backed_value(&BOB).unwrap();
3875            assert_eq!(bob_backed_value, 140); // 100 (existing) + 40 (through index as BOB share is 40 )
3876
3877            let author_funders = AuthorFunders::get((ALICE, MIKE)).unwrap();
3878            assert_eq!(author_funders, by);
3879
3880            let alice_current_hold = Pallet::get_hold(&ALICE).unwrap();
3881            assert_eq!(alice_current_hold, 160);
3882            let bob_current_hold = Pallet::get_hold(&BOB).unwrap();
3883            assert_eq!(bob_current_hold, 190);
3884            System::assert_last_event(Event::PoolFunded { pool: POOL_DIGEST, backer: MIKE, amount: LARGE_VALUE }.into());
3885        })
3886    }
3887
3888    #[test]
3889    fn draw_success_for_direct() {
3890        authors_test_ext().execute_with(|| {
3891            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3892            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3893            System::set_block_number(6);
3894            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3895
3896            // BOB backed ALICE with 50 units
3897            assert_ok!(Pallet::fund(
3898                &ALICE,
3899                &Funder::Direct(BOB),
3900                STANDARD_VALUE,
3901                Precision::BestEffort,
3902                Fortitude::Force,
3903            ));
3904
3905            let current_backed_value = Pallet::backed_value(&ALICE).unwrap();
3906            assert_eq!(current_backed_value, STANDARD_VALUE);
3907
3908            let alice_backers = Pallet::backers_of(&ALICE).unwrap();
3909            let expected_backers = vec![(Funder::Direct(BOB), STANDARD_VALUE)];
3910            assert_eq!(alice_backers, expected_backers);
3911
3912            // withdraw the backed funds
3913            assert_ok!(Pallet::draw(&ALICE, &Funder::Direct(BOB)));
3914
3915            let current_backed_value = Pallet::backed_value(&ALICE).unwrap();
3916            assert_eq!(current_backed_value, 0);
3917
3918            let alice_backers = Pallet::backers_of(&ALICE).unwrap();
3919            let expected_backers = vec![];
3920            assert_eq!(alice_backers, expected_backers);
3921            assert!(!AuthorFunders::contains_key((ALICE, BOB)));
3922            System::assert_last_event(Event::AuthorDrawn { author: ALICE, backer: BOB, amount: STANDARD_VALUE }.into());
3923        })
3924    }
3925
3926    #[test]
3927    fn draw_success_for_index() {
3928        authors_test_ext().execute_with(|| {
3929            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
3930            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
3931            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
3932            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
3933            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
3934
3935            System::set_block_number(6);
3936            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
3937
3938            System::set_block_number(8);
3939            Pallet::fund(
3940                &ALICE,
3941                &Funder::Direct(CHARLIE),
3942                STANDARD_VALUE,
3943                Precision::Exact,
3944                Fortitude::Force,
3945            )
3946            .unwrap();
3947
3948            System::set_block_number(12);
3949            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
3950
3951            System::set_block_number(15);
3952            Pallet::fund(
3953                &BOB,
3954                &Funder::Direct(ALAN),
3955                LARGE_VALUE,
3956                Precision::Exact,
3957                Fortitude::Force,
3958            )
3959            .unwrap();
3960
3961            let alice_digest = gen_author_digest(&ALICE).unwrap();
3962            let bob_digest = gen_author_digest(&BOB).unwrap();
3963            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
3964
3965            prepare_and_initiate_index(MIKE, FUNDING.into(), &entries, INDEX_DIGEST).unwrap();
3966
3967            let by = Funder::Index {
3968                digest: INDEX_DIGEST,
3969                backer: MIKE,
3970            };
3971
3972            Pallet::fund(&ALICE, &by, LARGE_VALUE, Precision::Exact, Fortitude::Force).unwrap();
3973
3974            let total_backing = Pallet::total_backing();
3975            assert_eq!(total_backing, 250);
3976            let alice_backed_value = Pallet::backed_value(&ALICE).unwrap();
3977            assert_eq!(alice_backed_value, 110); // 50 (existing) + 60 (through index as ALICE share is 60 )
3978            let bob_backed_value = Pallet::backed_value(&BOB).unwrap();
3979            assert_eq!(bob_backed_value, 140); // 100 (existing) + 40 (through index as BOB share is 40 )
3980
3981            let author_funders = AuthorFunders::get((ALICE, MIKE)).unwrap();
3982            assert_eq!(author_funders, by);
3983
3984            assert_ok!(Pallet::draw(&ALICE, &by));
3985
3986            let total_backing = Pallet::total_backing();
3987            assert_eq!(total_backing, 150);
3988            let alice_backed_value = Pallet::backed_value(&ALICE).unwrap();
3989            assert_eq!(alice_backed_value, 50); // 50 (existing) - 60 (through index as ALICE share is 60 )
3990            let bob_backed_value = Pallet::backed_value(&BOB).unwrap();
3991            assert_eq!(bob_backed_value, 100); // 100 (existing) - 40 (through index as BOB share is 40 )
3992
3993            assert!(!AuthorFunders::contains_key((ALICE, MIKE)));
3994
3995            let mike_balance = get_user_balance(&MIKE);
3996            assert_eq!(mike_balance, 200);
3997            System::assert_last_event(Event::IndexDrawn { index: INDEX_DIGEST, backer: MIKE, amount: LARGE_VALUE }.into());
3998        })
3999    }
4000
4001    #[test]
4002    fn draw_success_for_pool() {
4003        authors_test_ext().execute_with(|| {
4004            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4005            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
4006            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
4007            initiate_key_and_set_balance_and_hold(&MIKE, LARGE_VALUE, LARGE_VALUE).unwrap();
4008            initiate_key_and_set_balance_and_hold(&ALAN, LARGE_VALUE, LARGE_VALUE).unwrap();
4009
4010            System::set_block_number(6);
4011            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4012
4013            System::set_block_number(8);
4014            Pallet::fund(
4015                &ALICE,
4016                &Funder::Direct(CHARLIE),
4017                STANDARD_VALUE,
4018                Precision::Exact,
4019                Fortitude::Force,
4020            )
4021            .unwrap();
4022
4023            System::set_block_number(12);
4024            Pallet::enroll(&BOB, STANDARD_VALUE, Fortitude::Force).unwrap();
4025
4026            System::set_block_number(15);
4027            Pallet::fund(
4028                &BOB,
4029                &Funder::Direct(ALAN),
4030                LARGE_VALUE,
4031                Precision::Exact,
4032                Fortitude::Force,
4033            )
4034            .unwrap();
4035
4036            let alice_digest = gen_author_digest(&ALICE).unwrap();
4037            let bob_digest = gen_author_digest(&BOB).unwrap();
4038            let entries = vec![(alice_digest.clone(), 60), (bob_digest.clone(), 40)];
4039
4040            prepare_and_initiate_pool(
4041                ALAN,
4042                FUNDING.into(),
4043                &entries,
4044                INDEX_DIGEST,
4045                POOL_DIGEST,
4046                Perbill::from_percent(5),
4047            )
4048            .unwrap();
4049
4050            let by = Funder::Pool {
4051                digest: POOL_DIGEST,
4052                backer: MIKE,
4053            };
4054
4055            Pallet::fund(&ALICE, &by, LARGE_VALUE, Precision::Exact, Fortitude::Force).unwrap();
4056
4057            let total_backing = Pallet::total_backing();
4058            assert_eq!(total_backing, 250);
4059            let alice_backed_value = Pallet::backed_value(&ALICE).unwrap();
4060            assert_eq!(alice_backed_value, 110); // 50 (existing) + 60 (through pool as ALICE share is 60 )
4061            let bob_backed_value = Pallet::backed_value(&BOB).unwrap();
4062            assert_eq!(bob_backed_value, 140); // 100 (existing) + 40 (through pool as BOB share is 40 )
4063
4064            let author_funders = AuthorFunders::get((ALICE, MIKE)).unwrap();
4065            assert_eq!(author_funders, by);
4066            let author_funders = AuthorFunders::get((BOB, MIKE)).unwrap();
4067            assert_eq!(author_funders, by);
4068
4069            assert_ok!(Pallet::draw(&ALICE, &by));
4070
4071            let total_backing = Pallet::total_backing();
4072            assert_eq!(total_backing, 150);
4073            let alice_backed_value = Pallet::backed_value(&ALICE).unwrap();
4074            assert_eq!(alice_backed_value, 50); // 50 (existing) - 60 (through index as ALICE share is 60 )
4075            let bob_backed_value = Pallet::backed_value(&BOB).unwrap();
4076            assert_eq!(bob_backed_value, 100); // 100 (existing) - 40 (through index as BOB share is 40 )
4077
4078            assert!(!AuthorFunders::contains_key((ALICE, MIKE)));
4079
4080            let mike_balance = get_user_balance(&MIKE);
4081            assert_eq!(mike_balance, 195); // 100 (existing) + 100 (backed) - 5 (commission)
4082            let alan_balance = get_user_balance(&ALAN);
4083            assert_eq!(alan_balance, 105); // 100 (existing) + 5 (commission)
4084            System::assert_last_event(Event::PoolDrawn { pool: POOL_DIGEST, backer: MIKE, amount: 95 }.into());
4085        })
4086    }
4087
4088    #[test]
4089    fn on_drawn_direct_success() {
4090        authors_test_ext().execute_with(|| {
4091            System::set_block_number(10);
4092            let draw_amount = STANDARD_VALUE;
4093            let by = Funder::<Test>::Direct(ALICE);
4094            Pallet::on_drawn(&ALAN, &by, draw_amount);
4095
4096            System::assert_last_event(
4097                Event::AuthorDrawn {
4098                    author: ALAN,
4099                    backer: ALICE,
4100                    amount: draw_amount,
4101                }
4102                .into(),
4103            );
4104        })
4105    }
4106
4107    #[test]
4108    fn on_drawn_index_success() {
4109        authors_test_ext().execute_with(|| {
4110            System::set_block_number(10);
4111            let draw_amount = STANDARD_VALUE;
4112            let by = Funder::<Test>::Index {
4113                digest: INDEX_DIGEST,
4114                backer: ALICE,
4115            };
4116            Pallet::on_drawn(&ALAN, &by, draw_amount);
4117
4118            System::assert_last_event(
4119                Event::IndexDrawn {
4120                    index: INDEX_DIGEST,
4121                    backer: ALICE,
4122                    amount: draw_amount,
4123                }
4124                .into(),
4125            );
4126        })
4127    }
4128
4129    #[test]
4130    fn on_drawn_pool_success() {
4131        authors_test_ext().execute_with(|| {
4132            System::set_block_number(10);
4133            let draw_amount = STANDARD_VALUE;
4134            let by = Funder::<Test>::Pool {
4135                digest: POOL_DIGEST,
4136                backer: ALICE,
4137            };
4138            Pallet::on_drawn(&ALAN, &by, draw_amount);
4139
4140            System::assert_last_event(
4141                Event::PoolDrawn {
4142                    pool: POOL_DIGEST,
4143                    backer: ALICE,
4144                    amount: draw_amount,
4145                }
4146                .into(),
4147            );
4148        })
4149    }
4150
4151    #[test]
4152    fn on_funded_direct_success() {
4153        authors_test_ext().execute_with(|| {
4154            System::set_block_number(10);
4155            let fund_amount = STANDARD_VALUE;
4156            let by = Funder::<Test>::Direct(ALICE);
4157            Pallet::on_funded(&ALAN, &by, fund_amount);
4158
4159            System::assert_last_event(
4160                Event::AuthorFunded {
4161                    author: ALAN,
4162                    backer: ALICE,
4163                    amount: fund_amount,
4164                }
4165                .into(),
4166            );
4167        })
4168    }
4169
4170    #[test]
4171    fn on_funded_index_success() {
4172        authors_test_ext().execute_with(|| {
4173            System::set_block_number(10);
4174            let draw_amount = STANDARD_VALUE;
4175            let by = Funder::<Test>::Index {
4176                digest: INDEX_DIGEST,
4177                backer: ALICE,
4178            };
4179            Pallet::on_funded(&ALAN, &by, draw_amount);
4180
4181            System::assert_last_event(
4182                Event::IndexFunded {
4183                    index: INDEX_DIGEST,
4184                    backer: ALICE,
4185                    amount: draw_amount,
4186                }
4187                .into(),
4188            );
4189        })
4190    }
4191
4192    #[test]
4193    fn on_funded_pool_success() {
4194        authors_test_ext().execute_with(|| {
4195            System::set_block_number(10);
4196            let draw_amount = STANDARD_VALUE;
4197            let by = Funder::<Test>::Pool {
4198                digest: POOL_DIGEST,
4199                backer: ALICE,
4200            };
4201            Pallet::on_funded(&ALAN, &by, draw_amount);
4202
4203            System::assert_last_event(
4204                Event::PoolFunded {
4205                    pool: POOL_DIGEST,
4206                    backer: ALICE,
4207                    amount: draw_amount,
4208                }
4209                .into(),
4210            );
4211        })
4212    }
4213
4214    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4215    // `````````````````````````````` COMPENSATE ROLES ```````````````````````````````
4216    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4217
4218    #[test]
4219    fn reward_success() {
4220        authors_test_ext().execute_with(|| {
4221            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4222
4223            System::set_block_number(6);
4224            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4225
4226            System::set_block_number(10);
4227            let reward_units = 5;
4228            let reward_block = Pallet::reward(&ALICE, reward_units, Precision::BestEffort).unwrap();
4229            // First reward is scheduled at block 12 (current block 10 + buffer 2)
4230            assert_eq!(reward_block, 12);
4231            let reward_scheduled = AuthorRewards::get((12, ALICE)).unwrap();
4232            assert_eq!(reward_scheduled, 5);
4233
4234            // Scheduling a second reward at the same block results in automatic
4235            // collision avoidance: the slot at block 12 is occupied, so the reward
4236            // is deferred to the next available block
4237            let reward_units = 10;
4238            let reward_block = Pallet::reward(&ALICE, reward_units, Precision::BestEffort).unwrap();
4239
4240            // Second reward is scheduled at block 13 due to slot collision
4241            assert_eq!(reward_block, 13);
4242            let reward_scheduled = AuthorRewards::get((13, ALICE)).unwrap();
4243            assert_eq!(reward_scheduled, 10);
4244            System::assert_last_event(Event::AuthorRewardScheduled { author: ALICE, amount: reward_units, at: reward_block }.into());
4245        })
4246    }
4247
4248    #[test]
4249    fn reward_err_author_resigned() {
4250        authors_test_ext().execute_with(|| {
4251            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4252
4253            System::set_block_number(6);
4254            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4255
4256            AuthorsMap::mutate(ALICE, |author| {
4257                let info = author.as_mut().unwrap();
4258                let status = &mut info.status;
4259                *status = AuthorStatus::Resigned;
4260            });
4261
4262            let reward_unit = 5;
4263            assert_err!(
4264                Pallet::reward(&ALICE, reward_unit, Precision::BestEffort,),
4265                Error::AuthorResigned
4266            );
4267        })
4268    }
4269
4270    #[test]
4271    fn reclaim_success() {
4272        authors_test_ext().execute_with(|| {
4273            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4274
4275            System::set_block_number(6);
4276            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4277
4278            System::set_block_number(10);
4279            let reward_units = 5;
4280            let reward_block = Pallet::reward(&ALICE, reward_units, Precision::BestEffort).unwrap();
4281            // First reward is scheduled at block 12 (current block 10 + buffer 2)
4282            assert_eq!(reward_block, 12);
4283            let reward_scheduled = AuthorRewards::get((12, ALICE)).unwrap();
4284            assert_eq!(reward_scheduled, 5);
4285
4286            System::set_block_number(11);
4287            assert_ok!(Pallet::reclaim(&ALICE, 12));
4288            assert!(!AuthorRewards::contains_key((12, ALICE)));
4289            System::assert_last_event(Event::AuthorRewardReclaimed { author: ALICE, amount: reward_units}.into());
4290        })
4291    }
4292
4293    #[test]
4294    fn reclaim_err_finalized_obligations() {
4295        authors_test_ext().execute_with(|| {
4296            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4297
4298            System::set_block_number(6);
4299            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4300
4301            System::set_block_number(10);
4302            let reward_units = 5;
4303            let reward_block = Pallet::reward(&ALICE, reward_units, Precision::BestEffort).unwrap();
4304            assert_eq!(reward_block, 12);
4305
4306            System::set_block_number(12);
4307            assert_err!(Pallet::reclaim(&ALICE, 12), Error::FinalizedObligations);
4308        })
4309    }
4310
4311    #[test]
4312    fn reclaim_err_rewards_not_founds() {
4313        authors_test_ext().execute_with(|| {
4314            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4315
4316            System::set_block_number(6);
4317            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4318
4319            System::set_block_number(10);
4320            assert_err!(Pallet::reclaim(&ALICE, 12), Error::RewardNotFound);
4321        })
4322    }
4323
4324    #[test]
4325    fn penalize_success() {
4326        authors_test_ext().execute_with(|| {
4327            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4328
4329            System::set_block_number(6);
4330            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4331
4332            let meta = Pallet::get_meta(&ALICE).unwrap();
4333            assert_eq!(meta.risk_until, 6);
4334
4335            System::set_block_number(10);
4336            let penalty_block = Pallet::penalize(&ALICE, Perbill::from_percent(5)).unwrap();
4337            // First penalty is scheduled at block 14 (current block 10 + buffer 4)
4338            assert_eq!(penalty_block, 14);
4339            let penalty_scheduled = AuthorPenalties::get((14, ALICE)).unwrap();
4340            assert_eq!(penalty_scheduled, Perbill::from_percent(5));
4341            // Author's risk period was extended by the IncreaseProbationBy value (1 block)
4342            let meta = Pallet::get_meta(&ALICE).unwrap();
4343            assert_eq!(meta.risk_until, 11);
4344
4345            // Scheduling a second penalty at the same block results in automatic
4346            // collision avoidance: the slot at block 14 is occupied, so the penalty
4347            // is deferred to the next available block
4348            let penalty_block = Pallet::penalize(&ALICE, Perbill::from_percent(10)).unwrap();
4349            // Second penalty is scheduled at block 15 due to slot collision
4350            assert_eq!(penalty_block, 15);
4351            let penalty_scheduled = AuthorPenalties::get((15, ALICE)).unwrap();
4352            assert_eq!(penalty_scheduled, Perbill::from_percent(10));
4353            // Risk period is extended again (now 11 + 1 = 12)
4354            let meta = Pallet::get_meta(&ALICE).unwrap();
4355            assert_eq!(meta.risk_until, 12);
4356            System::assert_last_event(Event::AuthorPenaltyScheduled { author: ALICE, factor: penalty_scheduled, at: 15 }.into());
4357        })
4358    }
4359
4360    #[test]
4361    fn penalize_err_zero_penalty_found() {
4362        authors_test_ext().execute_with(|| {
4363            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4364
4365            System::set_block_number(6);
4366            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4367
4368            System::set_block_number(10);
4369            assert_err!(
4370                Pallet::penalize(&ALICE, Perbill::from_percent(0)),
4371                Error::ZeroPenaltyFound
4372            );
4373        })
4374    }
4375
4376    #[test]
4377    fn penalize_err_author_resigned() {
4378        authors_test_ext().execute_with(|| {
4379            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4380
4381            System::set_block_number(6);
4382            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4383
4384            AuthorsMap::mutate(ALICE, |author| {
4385                let info = author.as_mut().unwrap();
4386                let status = &mut info.status;
4387                *status = AuthorStatus::Resigned;
4388            });
4389
4390            System::set_block_number(10);
4391            assert_err!(
4392                Pallet::penalize(&ALICE, Perbill::from_percent(5)),
4393                Error::AuthorResigned
4394            );
4395        })
4396    }
4397
4398    #[test]
4399    fn forgive_success() {
4400        authors_test_ext().execute_with(|| {
4401            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4402
4403            System::set_block_number(6);
4404            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4405
4406            System::set_block_number(10);
4407            let penalty_factor = Perbill::from_percent(5);
4408            let penalty_block = Pallet::penalize(&ALICE, penalty_factor).unwrap();
4409
4410            assert_eq!(penalty_block, 14);
4411            let penalty_scheduled = AuthorPenalties::get((14, ALICE)).unwrap();
4412            assert_eq!(penalty_scheduled, penalty_factor);
4413
4414            let meta = Pallet::get_meta(&ALICE).unwrap();
4415            assert_eq!(meta.risk_until, 11);
4416
4417            assert_ok!(Pallet::forgive(&ALICE, 14));
4418
4419            assert!(!AuthorPenalties::contains_key((14, ALICE)));
4420
4421            let meta = Pallet::get_meta(&ALICE).unwrap();
4422            assert_eq!(meta.risk_until, 10);
4423            System::assert_last_event(Event::AuthorPenaltyForgiven { author: ALICE, factor: penalty_scheduled }.into());
4424        })
4425    }
4426
4427    #[test]
4428    fn forgive_err_finalized_obligations() {
4429        authors_test_ext().execute_with(|| {
4430            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4431
4432            System::set_block_number(6);
4433            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4434
4435            System::set_block_number(10);
4436            let penalty_factor = Perbill::from_percent(5);
4437            let penalty_block = Pallet::penalize(&ALICE, penalty_factor).unwrap();
4438
4439            assert_eq!(penalty_block, 14);
4440            let penalty_scheduled = AuthorPenalties::get((14, ALICE)).unwrap();
4441            assert_eq!(penalty_scheduled, penalty_factor);
4442
4443            System::set_block_number(14);
4444            assert_err!(Pallet::forgive(&ALICE, 14), Error::FinalizedObligations);
4445        })
4446    }
4447
4448    #[test]
4449    fn forgive_err_rewards_not_founds() {
4450        authors_test_ext().execute_with(|| {
4451            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4452
4453            System::set_block_number(6);
4454            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4455
4456            System::set_block_number(10);
4457            assert_err!(Pallet::forgive(&ALICE, 14), Error::PenaltyNotFound);
4458        })
4459    }
4460
4461    #[test]
4462    fn has_reward_success() {
4463        authors_test_ext().execute_with(|| {
4464            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4465
4466            System::set_block_number(6);
4467            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4468
4469            System::set_block_number(10);
4470            let reward_units = 5;
4471            Pallet::reward(&ALICE, reward_units, Precision::BestEffort).unwrap();
4472
4473            assert_ok!(Pallet::has_reward(&ALICE));
4474        })
4475    }
4476
4477    #[test]
4478    fn has_reward_err_reward_not_found() {
4479        authors_test_ext().execute_with(|| {
4480            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4481
4482            System::set_block_number(6);
4483            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4484
4485            assert_err!(Pallet::has_reward(&ALICE), Error::RewardNotFound);
4486        })
4487    }
4488
4489    #[test]
4490    fn has_penalty_success() {
4491        authors_test_ext().execute_with(|| {
4492            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4493
4494            System::set_block_number(6);
4495            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4496
4497            System::set_block_number(10);
4498            Pallet::penalize(&ALICE, Perbill::from_percent(5)).unwrap();
4499
4500            assert_ok!(Pallet::has_penalty(&ALICE));
4501        })
4502    }
4503
4504    #[test]
4505    fn has_penalty_penalty_not_found() {
4506        authors_test_ext().execute_with(|| {
4507            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4508
4509            System::set_block_number(6);
4510            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4511
4512            assert_err!(Pallet::has_penalty(&ALICE), Error::PenaltyNotFound);
4513        })
4514    }
4515
4516    #[test]
4517    fn get_rewards_of_success() {
4518        authors_test_ext().execute_with(|| {
4519            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4520
4521            System::set_block_number(6);
4522            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4523
4524            System::set_block_number(10);
4525            let reward_units = 5;
4526            Pallet::reward(&ALICE, reward_units, Precision::BestEffort).unwrap();
4527
4528            System::set_block_number(11);
4529            let reward_units = 10;
4530            Pallet::reward(&ALICE, reward_units, Precision::BestEffort).unwrap();
4531
4532            let reward_units = 8;
4533            Pallet::reward(&ALICE, reward_units, Precision::BestEffort).unwrap();
4534
4535            let rewards_of = Pallet::get_rewards_of(&ALICE).unwrap();
4536            let expected_rewards_of = vec![(12, 5), (13, 10), (14, 8)];
4537            assert_eq!(rewards_of, expected_rewards_of);
4538        })
4539    }
4540
4541    #[test]
4542    fn get_rewards_of_success_with_empty_vec_when_no_rewards() {
4543        authors_test_ext().execute_with(|| {
4544            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4545
4546            System::set_block_number(6);
4547            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4548
4549            let rewards_of = Pallet::get_rewards_of(&ALICE).unwrap();
4550            assert_eq!(rewards_of, vec![]);
4551        })
4552    }
4553
4554    #[test]
4555    fn get_penalties_of_success() {
4556        authors_test_ext().execute_with(|| {
4557            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4558
4559            System::set_block_number(6);
4560            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4561
4562            System::set_block_number(10);
4563            let penalty_factor = Perbill::from_percent(5);
4564            Pallet::penalize(&ALICE, penalty_factor).unwrap();
4565
4566            System::set_block_number(11);
4567            let penalty_factor = Perbill::from_percent(10);
4568            Pallet::penalize(&ALICE, penalty_factor).unwrap();
4569
4570            System::set_block_number(12);
4571            let penalty_factor = Perbill::from_percent(8);
4572            Pallet::penalize(&ALICE, penalty_factor).unwrap();
4573
4574            let penalties_of = Pallet::get_penalties_of(&ALICE).unwrap();
4575            let expected_penalties_of = vec![
4576                (14, Perbill::from_percent(5)),
4577                (15, Perbill::from_percent(10)),
4578                (16, Perbill::from_percent(8)),
4579            ];
4580            assert_eq!(penalties_of, expected_penalties_of);
4581        })
4582    }
4583
4584    #[test]
4585    fn get_penalties_of_success_with_empty_vec_when_no_penalty() {
4586        authors_test_ext().execute_with(|| {
4587            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4588
4589            System::set_block_number(6);
4590            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4591
4592            let penalties_of = Pallet::get_penalties_of(&ALICE).unwrap();
4593            assert_eq!(penalties_of, vec![]);
4594        })
4595    }
4596
4597    #[test]
4598    fn get_rewards_on_success() {
4599        authors_test_ext().execute_with(|| {
4600            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4601
4602            System::set_block_number(6);
4603            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4604
4605            System::set_block_number(10);
4606            let reward_units = 5;
4607            Pallet::reward(&ALICE, reward_units, Precision::BestEffort).unwrap();
4608
4609            System::set_block_number(11);
4610            let reward_units = 10;
4611            Pallet::reward(&ALICE, reward_units, Precision::BestEffort).unwrap();
4612
4613            let reward_on_12 = Pallet::get_rewards_on(12).unwrap();
4614            let expected_reward_on_12 = vec![(ALICE, 5)];
4615            assert_eq!(reward_on_12, expected_reward_on_12);
4616
4617            let reward_on_13 = Pallet::get_rewards_on(13).unwrap();
4618            let expected_reward_on_13 = vec![(ALICE, 10)];
4619            assert_eq!(reward_on_13, expected_reward_on_13);
4620        })
4621    }
4622
4623    #[test]
4624    fn get_rewards_on_err_finalize_obligations() {
4625        authors_test_ext().execute_with(|| {
4626            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4627
4628            System::set_block_number(6);
4629            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4630
4631            System::set_block_number(10);
4632            let reward_units = 5;
4633            Pallet::reward(&ALICE, reward_units, Precision::BestEffort).unwrap();
4634
4635            System::set_block_number(11);
4636            let reward_units = 10;
4637            Pallet::reward(&ALICE, reward_units, Precision::BestEffort).unwrap();
4638
4639            System::set_block_number(15);
4640            assert_err!(Pallet::get_rewards_on(12), Error::FinalizedObligations);
4641        })
4642    }
4643
4644    #[test]
4645    fn get_rewards_on_err_contains_no_rewards() {
4646        authors_test_ext().execute_with(|| {
4647            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4648
4649            System::set_block_number(6);
4650            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4651
4652            System::set_block_number(10);
4653            let reward_units = 5;
4654            Pallet::reward(&ALICE, reward_units, Precision::BestEffort).unwrap();
4655
4656            let rewards_on = Pallet::get_rewards_on(13).unwrap();
4657            assert_eq!(rewards_on, vec![]);
4658        })
4659    }
4660
4661    #[test]
4662    fn get_penalties_on_success() {
4663        authors_test_ext().execute_with(|| {
4664            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4665
4666            System::set_block_number(6);
4667            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4668
4669            System::set_block_number(10);
4670            let penalty_factor = Perbill::from_percent(5);
4671            Pallet::penalize(&ALICE, penalty_factor).unwrap();
4672
4673            System::set_block_number(11);
4674            let penalty_factor = Perbill::from_percent(10);
4675            Pallet::penalize(&ALICE, penalty_factor).unwrap();
4676
4677            let penalty_on_12 = Pallet::get_penalties_on(14).unwrap();
4678            let expected_penalty_on_12 = vec![(ALICE, Perbill::from_percent(5))];
4679            assert_eq!(penalty_on_12, expected_penalty_on_12);
4680
4681            let penalty_on_13 = Pallet::get_penalties_on(15).unwrap();
4682            let expected_penalty_on_13 = vec![(ALICE, Perbill::from_percent(10))];
4683            assert_eq!(penalty_on_13, expected_penalty_on_13);
4684        })
4685    }
4686
4687    #[test]
4688    fn get_penalties_on_err_finalized_obligations() {
4689        authors_test_ext().execute_with(|| {
4690            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4691
4692            System::set_block_number(6);
4693            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4694
4695            System::set_block_number(10);
4696            let penalty_factor = Perbill::from_percent(5);
4697            Pallet::penalize(&ALICE, penalty_factor).unwrap();
4698
4699            System::set_block_number(11);
4700            let penalty_factor = Perbill::from_percent(10);
4701            Pallet::penalize(&ALICE, penalty_factor).unwrap();
4702
4703            System::set_block_number(15);
4704            assert_err!(Pallet::get_penalties_on(14), Error::FinalizedObligations);
4705        })
4706    }
4707
4708    #[test]
4709    fn get_penalties_on_success_with_empty_vec_when_no_penalty() {
4710        authors_test_ext().execute_with(|| {
4711            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4712
4713            System::set_block_number(6);
4714            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4715
4716            System::set_block_number(10);
4717            let penalty_factor = Perbill::from_percent(5);
4718            Pallet::penalize(&ALICE, penalty_factor).unwrap();
4719
4720            let penalties_on = Pallet::get_penalties_on(15).unwrap();
4721            assert_eq!(penalties_on, vec![]);
4722        })
4723    }
4724
4725    #[test]
4726    fn get_hold_success() {
4727        authors_test_ext().execute_with(|| {
4728            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4729            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
4730            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
4731
4732            System::set_block_number(6);
4733            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4734
4735            Pallet::fund(
4736                &ALICE,
4737                &Funder::Direct(BOB),
4738                LARGE_VALUE,
4739                Precision::BestEffort,
4740                Fortitude::Force,
4741            )
4742            .unwrap();
4743
4744            let actual_hold = Pallet::get_hold(&ALICE).unwrap();
4745            let expected_hold = 150; // 50(collateral) + 100 (funding)
4746            assert_eq!(actual_hold, expected_hold);
4747
4748            Pallet::fund(
4749                &ALICE,
4750                &Funder::Direct(CHARLIE),
4751                SMALL_VALUE,
4752                Precision::BestEffort,
4753                Fortitude::Force,
4754            )
4755            .unwrap();
4756
4757            let actual_hold = Pallet::get_hold(&ALICE).unwrap();
4758            let expected_hold = 175; // 50(collateral) + 100 (funding) + 25 (funding)
4759            assert_eq!(actual_hold, expected_hold);
4760        })
4761    }
4762
4763    #[test]
4764    fn set_hold_success() {
4765        authors_test_ext().execute_with(|| {
4766            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4767            initiate_key_and_set_balance_and_hold(&BOB, LARGE_VALUE, LARGE_VALUE).unwrap();
4768            initiate_key_and_set_balance_and_hold(&CHARLIE, LARGE_VALUE, LARGE_VALUE).unwrap();
4769
4770            System::set_block_number(6);
4771            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4772
4773            Pallet::fund(
4774                &ALICE,
4775                &Funder::Direct(BOB),
4776                LARGE_VALUE,
4777                Precision::BestEffort,
4778                Fortitude::Force,
4779            )
4780            .unwrap();
4781
4782            Pallet::fund(
4783                &ALICE,
4784                &Funder::Direct(CHARLIE),
4785                SMALL_VALUE,
4786                Precision::BestEffort,
4787                Fortitude::Force,
4788            )
4789            .unwrap();
4790
4791            let actual_hold = Pallet::get_hold(&ALICE).unwrap();
4792            let expected_hold = 175; // 50(collateral) + 100 (funding) + 25 (funding)
4793            assert_eq!(actual_hold, expected_hold);
4794
4795            let collateral_value = Pallet::get_collateral(&ALICE).unwrap();
4796            let funding_value = Pallet::total_backing();
4797            assert_eq!(collateral_value, 50);
4798            assert_eq!(funding_value, 125);
4799
4800            assert_ok!(Pallet::set_hold(
4801                &ALICE,
4802                250,
4803                Precision::Exact,
4804                Fortitude::Force
4805            ));
4806
4807            let actual_hold = Pallet::get_hold(&ALICE).unwrap();
4808            let expected_hold = 250;
4809            // hold value updated to set_hold value
4810            assert_eq!(actual_hold, expected_hold);
4811
4812            // collateral and funding value are updated accordingly
4813            let collateral_value = Pallet::get_collateral(&ALICE).unwrap();
4814            let funding_value = Pallet::total_backing();
4815            assert_eq!(collateral_value, 71);
4816            assert_eq!(funding_value, 179);
4817            System::assert_last_event(Event::AuthorTotalHold { author: ALICE, value: 250 }.into());
4818        })
4819    }
4820
4821    #[test]
4822    fn on_reward_event_emmission_success() {
4823        authors_test_ext().execute_with(|| {
4824            System::set_block_number(10);
4825            let at = System::block_number();
4826            let amount = LARGE_VALUE;
4827            Pallet::on_reward(&ALICE, amount, at);
4828
4829            System::assert_last_event(
4830                AuthorRewardScheduled {
4831                    author: ALICE,
4832                    amount,
4833                    at,
4834                }
4835                .into(),
4836            )
4837        })
4838    }
4839
4840    #[test]
4841    fn on_reclaim_event_emmission_success() {
4842        authors_test_ext().execute_with(|| {
4843            System::set_block_number(10);
4844            let amount = LARGE_VALUE;
4845            Pallet::on_reclaim(&ALICE, amount);
4846
4847            System::assert_last_event(
4848                AuthorRewardReclaimed {
4849                    author: ALICE,
4850                    amount,
4851                }
4852                .into(),
4853            )
4854        })
4855    }
4856
4857    #[test]
4858    fn on_set_hold_event_emmission_success() {
4859        authors_test_ext().execute_with(|| {
4860            System::set_block_number(10);
4861            let value = LARGE_VALUE;
4862            Pallet::on_set_hold(&ALICE, value);
4863
4864            System::assert_last_event(
4865                AuthorTotalHold {
4866                    author: ALICE,
4867                    value,
4868                }
4869                .into(),
4870            )
4871        })
4872    }
4873
4874    #[test]
4875    fn on_forgive_event_emmission_success() {
4876        authors_test_ext().execute_with(|| {
4877            System::set_block_number(10);
4878            let factor = Perbill::from_percent(10);
4879            Pallet::on_forgive(&ALICE, factor);
4880
4881            System::assert_last_event(
4882                AuthorPenaltyForgiven {
4883                    author: ALICE,
4884                    factor,
4885                }
4886                .into(),
4887            )
4888        })
4889    }
4890
4891    #[test]
4892    fn on_penalize_event_emmission_success() {
4893        authors_test_ext().execute_with(|| {
4894            System::set_block_number(10);
4895            let at = System::block_number();
4896            let factor = Perbill::from_percent(10);
4897            Pallet::on_penalize(&ALICE, factor, at);
4898
4899            System::assert_last_event(
4900                AuthorPenaltyScheduled {
4901                    author: ALICE,
4902                    factor,
4903                    at,
4904                }
4905                .into(),
4906            )
4907        })
4908    }
4909
4910    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4911    // ``````````````````````````````` ROLE PROBATION ````````````````````````````````
4912    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4913
4914    #[test]
4915    fn is_on_probation_success() {
4916        authors_test_ext().execute_with(|| {
4917            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4918
4919            System::set_block_number(6);
4920            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4921
4922            assert_ok!(Pallet::is_on_probation(&ALICE));
4923        })
4924    }
4925
4926    #[test]
4927    fn is_on_probation_err_author_is_active() {
4928        authors_test_ext().execute_with(|| {
4929            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4930
4931            System::set_block_number(6);
4932            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4933
4934            AuthorsMap::mutate(ALICE, |author| {
4935                let info = author.as_mut().unwrap();
4936                let status = &mut info.status;
4937                *status = AuthorStatus::Active;
4938            });
4939
4940            assert_err!(Pallet::is_on_probation(&ALICE), Error::AuthorIsActive);
4941        })
4942    }
4943
4944    #[test]
4945    fn is_on_probation_err_author_is_resigned() {
4946        authors_test_ext().execute_with(|| {
4947            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4948
4949            System::set_block_number(6);
4950            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4951
4952            AuthorsMap::mutate(ALICE, |author| {
4953                let info = author.as_mut().unwrap();
4954                let status = &mut info.status;
4955                *status = AuthorStatus::Resigned;
4956            });
4957
4958            assert_err!(Pallet::is_on_probation(&ALICE), Error::AuthorResigned);
4959        })
4960    }
4961
4962    #[test]
4963    fn is_permanent_success() {
4964        authors_test_ext().execute_with(|| {
4965            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4966
4967            System::set_block_number(6);
4968            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4969
4970            AuthorsMap::mutate(ALICE, |author| {
4971                let info = author.as_mut().unwrap();
4972                let status = &mut info.status;
4973                *status = AuthorStatus::Active;
4974            });
4975
4976            assert_ok!(Pallet::is_permanent(&ALICE));
4977        })
4978    }
4979
4980    #[test]
4981    fn is_permanent_err_author_in_probation() {
4982        authors_test_ext().execute_with(|| {
4983            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4984
4985            System::set_block_number(6);
4986            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4987
4988            assert_err!(Pallet::is_permanent(&ALICE), Error::AuthorInProbation);
4989        })
4990    }
4991
4992    #[test]
4993    fn is_permanent_err_author_resigned() {
4994        authors_test_ext().execute_with(|| {
4995            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
4996
4997            System::set_block_number(6);
4998            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
4999
5000            AuthorsMap::mutate(ALICE, |author| {
5001                let info = author.as_mut().unwrap();
5002                let status = &mut info.status;
5003                *status = AuthorStatus::Resigned;
5004            });
5005
5006            assert_err!(Pallet::is_permanent(&ALICE), Error::AuthorResigned);
5007        })
5008    }
5009
5010    #[test]
5011    fn can_be_permament_success() {
5012        authors_test_ext().execute_with(|| {
5013            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5014
5015            System::set_block_number(6);
5016            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5017
5018            System::set_block_number(14);
5019            assert_err!(Pallet::can_be_permanent(&ALICE), Error::AuthorInProbation);
5020
5021            System::set_block_number(16);
5022            assert_ok!(Pallet::can_be_permanent(&ALICE));
5023        })
5024    }
5025
5026    #[test]
5027    fn can_be_permament_err_author_is_active() {
5028        authors_test_ext().execute_with(|| {
5029            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5030
5031            System::set_block_number(6);
5032            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5033
5034            AuthorsMap::mutate(ALICE, |author| {
5035                let info = author.as_mut().unwrap();
5036                let status = &mut info.status;
5037                *status = AuthorStatus::Active;
5038            });
5039
5040            System::set_block_number(16);
5041            assert_err!(Pallet::can_be_permanent(&ALICE), Error::AuthorIsActive);
5042        })
5043    }
5044
5045    #[test]
5046    fn can_be_permament_err_author_resigned() {
5047        authors_test_ext().execute_with(|| {
5048            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5049
5050            System::set_block_number(6);
5051            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5052
5053            AuthorsMap::mutate(ALICE, |author| {
5054                let info = author.as_mut().unwrap();
5055                let status = &mut info.status;
5056                *status = AuthorStatus::Resigned;
5057            });
5058
5059            System::set_block_number(16);
5060            assert_err!(Pallet::can_be_permanent(&ALICE), Error::AuthorResigned);
5061        })
5062    }
5063
5064    #[test]
5065    fn can_be_permament_err_author_not_found() {
5066        authors_test_ext().execute_with(|| {
5067            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5068
5069            System::set_block_number(6);
5070            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5071
5072            System::set_block_number(16);
5073            assert_err!(Pallet::can_be_permanent(&BOB), Error::AuthorNotFound);
5074        })
5075    }
5076
5077    #[test]
5078    fn can_be_permament_err_author_is_unsafe() {
5079        authors_test_ext().execute_with(|| {
5080            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5081
5082            System::set_block_number(6);
5083            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5084
5085            AuthorsMap::mutate(ALICE, |author| {
5086                let info = author.as_mut().unwrap();
5087                let risk = &mut info.risk_until;
5088                *risk = 18;
5089            });
5090
5091            System::set_block_number(16);
5092            assert_err!(Pallet::can_be_permanent(&ALICE), Error::AuthorIsUnsafe);
5093        })
5094    }
5095
5096    #[test]
5097    fn risk_probation_success() {
5098        authors_test_ext().execute_with(|| {
5099            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5100
5101            System::set_block_number(6);
5102            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5103
5104            let meta = Pallet::get_meta(&ALICE).unwrap();
5105            assert_eq!(meta.risk_until, 6);
5106
5107            System::set_block_number(10);
5108            Pallet::risk_probation(&ALICE).unwrap();
5109
5110            let meta = Pallet::get_meta(&ALICE).unwrap();
5111            assert_eq!(meta.risk_until, 11);
5112
5113            Pallet::risk_probation(&ALICE).unwrap();
5114
5115            let meta = Pallet::get_meta(&ALICE).unwrap();
5116            assert_eq!(meta.risk_until, 12);
5117
5118            System::set_block_number(15);
5119            Pallet::risk_probation(&ALICE).unwrap();
5120
5121            let meta = Pallet::get_meta(&ALICE).unwrap();
5122            assert_eq!(meta.risk_until, 16);
5123            System::assert_last_event(Event::AuthorAtRisk { author: ALICE, status: AuthorStatus::Probation, until: meta.risk_until }.into());
5124        })
5125    }
5126
5127    #[test]
5128    fn risk_probation_err_author_not_found() {
5129        authors_test_ext().execute_with(|| {
5130            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5131
5132            System::set_block_number(6);
5133            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5134
5135            System::set_block_number(10);
5136            assert_err!(Pallet::risk_probation(&BOB), Error::AuthorNotFound);
5137        })
5138    }
5139
5140    #[test]
5141    fn risk_probation_err_author_is_active() {
5142        authors_test_ext().execute_with(|| {
5143            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5144
5145            System::set_block_number(6);
5146            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5147
5148            AuthorsMap::mutate(ALICE, |author| {
5149                let info = author.as_mut().unwrap();
5150                let status = &mut info.status;
5151                *status = AuthorStatus::Active;
5152            });
5153
5154            System::set_block_number(10);
5155            assert_err!(Pallet::risk_probation(&ALICE), Error::AuthorIsActive);
5156        })
5157    }
5158
5159    #[test]
5160    fn risk_probation_err_author_resigned() {
5161        authors_test_ext().execute_with(|| {
5162            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5163
5164            System::set_block_number(6);
5165            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5166
5167            AuthorsMap::mutate(ALICE, |author| {
5168                let info = author.as_mut().unwrap();
5169                let status = &mut info.status;
5170                *status = AuthorStatus::Resigned;
5171            });
5172
5173            System::set_block_number(10);
5174            assert_err!(Pallet::risk_probation(&ALICE), Error::AuthorResigned);
5175        })
5176    }
5177
5178    #[test]
5179    fn risk_permanence_success() {
5180        authors_test_ext().execute_with(|| {
5181            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5182
5183            System::set_block_number(6);
5184            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5185
5186            let meta = Pallet::get_meta(&ALICE).unwrap();
5187            assert_eq!(meta.risk_until, 6);
5188
5189            AuthorsMap::mutate(ALICE, |author| {
5190                let info = author.as_mut().unwrap();
5191                let status = &mut info.status;
5192                *status = AuthorStatus::Active;
5193            });
5194
5195            System::set_block_number(20);
5196            Pallet::risk_permanence(&ALICE).unwrap();
5197
5198            let meta = Pallet::get_meta(&ALICE).unwrap();
5199            assert_eq!(meta.risk_until, 21);
5200
5201            Pallet::risk_permanence(&ALICE).unwrap();
5202
5203            let meta = Pallet::get_meta(&ALICE).unwrap();
5204            assert_eq!(meta.risk_until, 22);
5205
5206            System::set_block_number(25);
5207            Pallet::risk_permanence(&ALICE).unwrap();
5208
5209            let meta = Pallet::get_meta(&ALICE).unwrap();
5210            assert_eq!(meta.risk_until, 26);
5211            System::assert_last_event(Event::AuthorAtRisk { author: ALICE, status: AuthorStatus::Active, until: meta.risk_until }.into());
5212        })
5213    }
5214
5215    #[test]
5216    fn risk_permanence_success_revoking_permanence() {
5217        authors_test_ext().execute_with(|| {
5218            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5219
5220            System::set_block_number(6);
5221            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5222
5223            let meta = Pallet::get_meta(&ALICE).unwrap();
5224            assert_eq!(meta.risk_until, 6);
5225
5226            AuthorsMap::mutate(ALICE, |author| {
5227                let info = author.as_mut().unwrap();
5228                let status = &mut info.status;
5229                let risk_until = &mut info.risk_until;
5230                *status = AuthorStatus::Active;
5231                *risk_until = 31;
5232            });
5233
5234            let meta = Pallet::get_meta(&ALICE).unwrap();
5235            assert_eq!(meta.status, AuthorStatus::Active);
5236            assert_eq!(meta.risk_until, 31);
5237
5238            System::set_block_number(20);
5239            Pallet::risk_permanence(&ALICE).unwrap();
5240
5241            let meta = Pallet::get_meta(&ALICE).unwrap();
5242            assert_eq!(meta.status, AuthorStatus::Active);
5243            assert_eq!(meta.risk_until, 32);
5244        })
5245    }
5246
5247    #[test]
5248    fn risk_permanence_err_author_in_probation() {
5249        authors_test_ext().execute_with(|| {
5250            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5251
5252            System::set_block_number(6);
5253            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5254
5255            System::set_block_number(10);
5256            assert_err!(Pallet::risk_permanence(&ALICE), Error::AuthorInProbation);
5257        })
5258    }
5259
5260    #[test]
5261    fn risk_permanence_err_author_resigned() {
5262        authors_test_ext().execute_with(|| {
5263            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5264
5265            System::set_block_number(6);
5266            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5267
5268            AuthorsMap::mutate(ALICE, |author| {
5269                let info = author.as_mut().unwrap();
5270                let status = &mut info.status;
5271                *status = AuthorStatus::Resigned;
5272            });
5273
5274            System::set_block_number(35);
5275            assert_err!(Pallet::risk_permanence(&ALICE), Error::AuthorResigned);
5276        })
5277    }
5278
5279    #[test]
5280    fn risk_permanence_err_author_not_found() {
5281        authors_test_ext().execute_with(|| {
5282            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5283
5284            System::set_block_number(6);
5285            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5286
5287            System::set_block_number(10);
5288            assert_err!(Pallet::risk_permanence(&BOB), Error::AuthorNotFound);
5289        })
5290    }
5291
5292    #[test]
5293    fn secure_permanence_success() {
5294        authors_test_ext().execute_with(|| {
5295            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5296
5297            System::set_block_number(6);
5298            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5299
5300            let meta = Pallet::get_meta(&ALICE).unwrap();
5301            assert_eq!(meta.risk_until, 6);
5302
5303            System::set_block_number(10);
5304            Pallet::risk_probation(&ALICE).unwrap();
5305
5306            let meta = Pallet::get_meta(&ALICE).unwrap();
5307            assert_eq!(meta.risk_until, 11);
5308
5309            assert_ok!(Pallet::secure_permanence(&ALICE));
5310            // risk is reduced when author under probation
5311            let meta = Pallet::get_meta(&ALICE).unwrap();
5312            assert_eq!(meta.risk_until, 10);
5313
5314            System::set_block_number(20);
5315            AuthorsMap::mutate(ALICE, |author| {
5316                let info = author.as_mut().unwrap();
5317                let status = &mut info.status;
5318                *status = AuthorStatus::Active;
5319            });
5320
5321            Pallet::risk_permanence(&ALICE).unwrap();
5322
5323            let meta = Pallet::get_meta(&ALICE).unwrap();
5324            assert_eq!(meta.risk_until, 21);
5325
5326            assert_ok!(Pallet::secure_permanence(&ALICE));
5327            // risk is reduced when author is Active
5328            let meta = Pallet::get_meta(&ALICE).unwrap();
5329            assert_eq!(meta.risk_until, 20);
5330            System::assert_last_event(Event::AuthorAtRisk { author: ALICE, status: AuthorStatus::Probation, until: meta.risk_until }.into());
5331        })
5332    }
5333
5334    #[test]
5335    fn secure_permanence_err_author_resigned() {
5336        authors_test_ext().execute_with(|| {
5337            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5338
5339            System::set_block_number(6);
5340            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5341
5342            AuthorsMap::mutate(ALICE, |author| {
5343                let info = author.as_mut().unwrap();
5344                let status = &mut info.status;
5345                *status = AuthorStatus::Resigned;
5346            });
5347
5348            System::set_block_number(35);
5349            assert_err!(Pallet::secure_permanence(&ALICE), Error::AuthorResigned);
5350        })
5351    }
5352
5353    #[test]
5354    fn set_permanence_success() {
5355        authors_test_ext().execute_with(|| {
5356            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5357
5358            System::set_block_number(6);
5359            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5360
5361            let meta = Pallet::get_meta(&ALICE).unwrap();
5362            assert_eq!(meta.status, AuthorStatus::Probation);
5363
5364            System::set_block_number(18);
5365            assert_ok!(Pallet::set_permanence(&ALICE));
5366
5367            let meta = Pallet::get_meta(&ALICE).unwrap();
5368            assert_eq!(meta.status, AuthorStatus::Active);
5369            System::assert_last_event(Event::AuthorStatus { author: ALICE, status: AuthorStatus::Active }.into());
5370        })
5371    }
5372
5373    #[test]
5374    fn set_permanence_author_not_found() {
5375        authors_test_ext().execute_with(|| {
5376            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5377
5378            System::set_block_number(6);
5379            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5380
5381            System::set_block_number(18);
5382            assert_err!(Pallet::set_permanence(&BOB), Error::AuthorNotFound);
5383        })
5384    }
5385
5386    #[test]
5387    fn revoke_permanance_success() {
5388        authors_test_ext().execute_with(|| {
5389            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5390
5391            System::set_block_number(6);
5392            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5393
5394            System::set_block_number(16);
5395            Pallet::set_permanence(&ALICE).unwrap();
5396
5397            let meta = Pallet::get_meta(&ALICE).unwrap();
5398            assert_eq!(meta.status, AuthorStatus::Active);
5399
5400            System::set_block_number(20);
5401            Pallet::risk_permanence(&ALICE).unwrap();
5402            Pallet::risk_permanence(&ALICE).unwrap();
5403            Pallet::risk_permanence(&ALICE).unwrap();
5404            Pallet::risk_permanence(&ALICE).unwrap();
5405            Pallet::risk_permanence(&ALICE).unwrap();
5406            Pallet::risk_permanence(&ALICE).unwrap();
5407            Pallet::risk_permanence(&ALICE).unwrap();
5408            Pallet::risk_permanence(&ALICE).unwrap();
5409            Pallet::risk_permanence(&ALICE).unwrap();
5410            Pallet::risk_permanence(&ALICE).unwrap();
5411            Pallet::risk_permanence(&ALICE).unwrap();
5412
5413            let meta = Pallet::get_meta(&ALICE).unwrap();
5414            assert_eq!(meta.risk_until, 31);
5415
5416            assert_ok!(Pallet::revoke_permanence(&ALICE));
5417
5418            let meta = Pallet::get_meta(&ALICE).unwrap();
5419            assert_eq!(meta.status, AuthorStatus::Probation);
5420            System::assert_last_event(Event::AuthorStatus { author: ALICE, status: AuthorStatus::Probation }.into());
5421        })
5422    }
5423
5424    #[test]
5425    fn revoke_permanance_err_author_not_found() {
5426        authors_test_ext().execute_with(|| {
5427            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5428
5429            System::set_block_number(6);
5430            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5431
5432            System::set_block_number(16);
5433            Pallet::set_permanence(&ALICE).unwrap();
5434
5435            let meta = Pallet::get_meta(&ALICE).unwrap();
5436            assert_eq!(meta.status, AuthorStatus::Active);
5437
5438            assert_err!(Pallet::revoke_permanence(&BOB), Error::AuthorNotFound);
5439        })
5440    }
5441
5442    #[test]
5443    fn can_revoke_permanence_success() {
5444        authors_test_ext().execute_with(|| {
5445            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5446
5447            System::set_block_number(6);
5448            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5449
5450            AuthorsMap::mutate(ALICE, |author| {
5451                let info = author.as_mut().unwrap();
5452                let status = &mut info.status;
5453                let risk_until = &mut info.risk_until;
5454                *status = AuthorStatus::Active;
5455                *risk_until = 31;
5456            });
5457
5458            System::set_block_number(20);
5459            assert_ok!(Pallet::can_revoke_permanence(&ALICE));
5460        })
5461    }
5462
5463    #[test]
5464    fn can_revoke_permanence_err_risk_within_threshold() {
5465        authors_test_ext().execute_with(|| {
5466            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5467
5468            System::set_block_number(6);
5469            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5470
5471            AuthorsMap::mutate(ALICE, |author| {
5472                let info = author.as_mut().unwrap();
5473                let status = &mut info.status;
5474                let risk_until = &mut info.risk_until;
5475                *status = AuthorStatus::Active;
5476                *risk_until = 31;
5477            });
5478
5479            System::set_block_number(21);
5480            assert_err!(
5481                Pallet::can_revoke_permanence(&ALICE),
5482                Error::RiskWithinThreshold
5483            );
5484
5485            System::set_block_number(25);
5486            assert_err!(
5487                Pallet::can_revoke_permanence(&ALICE),
5488                Error::RiskWithinThreshold
5489            );
5490        })
5491    }
5492
5493    #[test]
5494    fn can_revoke_permanence_err_author_resigned() {
5495        authors_test_ext().execute_with(|| {
5496            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5497
5498            System::set_block_number(6);
5499            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5500
5501            AuthorsMap::mutate(ALICE, |author| {
5502                let info = author.as_mut().unwrap();
5503                let status = &mut info.status;
5504                *status = AuthorStatus::Resigned;
5505            });
5506
5507            System::set_block_number(21);
5508            assert_err!(Pallet::can_revoke_permanence(&ALICE), Error::AuthorResigned);
5509        })
5510    }
5511
5512    #[test]
5513    fn can_revoke_permanence_err_author_in_probation() {
5514        authors_test_ext().execute_with(|| {
5515            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, LARGE_VALUE).unwrap();
5516
5517            System::set_block_number(6);
5518            Pallet::enroll(&ALICE, STANDARD_VALUE, Fortitude::Force).unwrap();
5519
5520            AuthorsMap::mutate(ALICE, |author| {
5521                let info = author.as_mut().unwrap();
5522                let status = &mut info.status;
5523                *status = AuthorStatus::Probation;
5524            });
5525
5526            System::set_block_number(21);
5527            assert_err!(
5528                Pallet::can_revoke_permanence(&ALICE),
5529                Error::AuthorInProbation
5530            );
5531        })
5532    }
5533
5534    #[test]
5535    fn on_set_permance_event_emmisison_success() {
5536        authors_test_ext().execute_with(|| {
5537            System::set_block_number(10);
5538            Pallet::on_set_permance(&ALICE);
5539
5540            let status = AuthorStatus::Active;
5541            System::assert_last_event(
5542                Event::AuthorStatus {
5543                    author: ALICE,
5544                    status,
5545                }
5546                .into(),
5547            );
5548        })
5549    }
5550
5551    #[test]
5552    fn on_revoke_permanence_event_emmisison_success() {
5553        authors_test_ext().execute_with(|| {
5554            System::set_block_number(10);
5555            Pallet::on_revoke_permanence(&ALICE);
5556
5557            let status = AuthorStatus::Probation;
5558            System::assert_last_event(
5559                Event::AuthorStatus {
5560                    author: ALICE,
5561                    status,
5562                }
5563                .into(),
5564            );
5565        })
5566    }
5567
5568    #[test]
5569    fn on_risk_permanence_event_emmisison_success() {
5570        authors_test_ext().execute_with(|| {
5571            System::set_block_number(10);
5572            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, STANDARD_HOLD).unwrap();
5573            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
5574
5575            Pallet::on_risk_permanence(&ALICE);
5576
5577            let status = AuthorStatus::Active;
5578            System::assert_last_event(
5579                Event::AuthorAtRisk {
5580                    author: ALICE,
5581                    status,
5582                    until: 10,
5583                }
5584                .into(),
5585            );
5586        })
5587    }
5588
5589    #[test]
5590    fn on_risk_probation_event_emmisison_success() {
5591        authors_test_ext().execute_with(|| {
5592            System::set_block_number(10);
5593            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, STANDARD_HOLD).unwrap();
5594            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
5595
5596            Pallet::on_risk_probation(&ALICE);
5597
5598            let status = AuthorStatus::Probation;
5599            System::assert_last_event(
5600                Event::AuthorAtRisk {
5601                    author: ALICE,
5602                    status,
5603                    until: 10,
5604                }
5605                .into(),
5606            );
5607        })
5608    }
5609
5610    #[test]
5611    fn on_secure_permanence_event_emmisison_success() {
5612        authors_test_ext().execute_with(|| {
5613            System::set_block_number(10);
5614            initiate_key_and_set_balance_and_hold(&ALICE, LARGE_VALUE, STANDARD_HOLD).unwrap();
5615            Pallet::enroll(&ALICE, 100, Fortitude::Force).unwrap();
5616
5617            Pallet::on_secure_permanence(&ALICE);
5618
5619            let status = AuthorStatus::Probation;
5620            System::assert_last_event(
5621                Event::AuthorAtRisk {
5622                    author: ALICE,
5623                    status,
5624                    until: 10,
5625                }
5626                .into(),
5627            );
5628        })
5629    }
5630}