pallet_commitment/
types.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// ``````````````````````````````` COMMITMENT TYPES ``````````````````````````````
14// ===============================================================================
15
16//! **Core types and aliases for the Commitment system.**
17//!
18//! This module defines the primary structures and type aliases used by
19//! [`pallet_commitment`](crate). These types are publicly exposed and used across
20//! the pallet's APIs for representing Commitment-related data.
21//!
22//! Trait implementations provided by this crate's [`crate::Pallet`] can use these types
23//! via trait-bound equality constraints to ensure type alignment with this pallet's
24//! concrete implementations if necessary.
25//!
26//! ## Invariants & Access
27//!
28//! All structures in this module encapsulate their fields as private to enforce
29//! invariants during creation, mutation, and access. As a result, interaction with
30//! these types is performed exclusively through inherent methods, which provide
31//! both internal mutation capabilities and safe external (read/query) access.
32//!
33//! ## Example
34//!
35//! ```ignore
36//! mod pallet {
37//!     use pallet_commitment::types::IndexInfo;
38//!
39//!     pub trait Config<I: 'static>: frame_system::Config {
40//!         type CommitmentAdapter: CommitIndex<Index = IndexInfo<Self, I>>;
41//!     }
42//! }
43//! ```
44
45// ===============================================================================
46// ``````````````````````````````````` IMPORTS ```````````````````````````````````
47// ===============================================================================
48
49// --- Local crate imports ---
50use crate::{balance::ProductType, Config, Error};
51
52// --- Core ---
53use core::{fmt::Debug, marker::PhantomData};
54
55// --- Scale-codec crates ---
56use codec::DecodeWithMemTracking;
57use scale_info::{prelude::vec, TypeInfo};
58
59// --- Derive Macros ---
60use derive_more::Constructor;
61
62// --- FRAME Suite ---
63use frame_suite::{assets::*, misc::PositionIndex, plugins::ModelContext};
64
65// --- FRAME Support ---
66use frame_support::{
67    dispatch::DispatchResult,
68    ensure,
69    traits::{
70        fungible::{Inspect, InspectFreeze},
71        tokens::Precision,
72        VariantCountOf,
73    },
74};
75
76// --- FRAME System ---
77use frame_system::pallet;
78
79// --- Substrate primitives ---
80use sp_core::{Decode, Encode, Get, MaxEncodedLen};
81use sp_runtime::{
82    traits::{CheckedAdd, Zero},
83    BoundedVec, DispatchError, RuntimeDebug, Vec, WeakBoundedVec,
84};
85use sp_std::collections::btree_set::BTreeSet;
86
87// ===============================================================================
88// ``````````````````````````````````` ALIASES ```````````````````````````````````
89// ===============================================================================
90
91/// The **primary digest type** used to uniquely identify a commitment entity.
92///
93/// This type is reused across direct, index, and pool commitments.
94pub type Digest<T> = <T as pallet::Config>::AccountId;
95
96/// Represents the **unique identifier** for a direct digest.
97///
98/// A direct digest is neither an index nor a pool (i.e. not an indirect digest).
99pub type DirectDigest<T> = Digest<T>;
100
101/// Represents the **unique identifier** for an index.
102pub type IndexDigest<T> = Digest<T>;
103
104/// Represents the **unique identifier** for a pool.
105pub type PoolDigest<T> = Digest<T>;
106
107/// Represents the **unique identifier** for an entry within an index.
108pub type EntryDigest<T> = Digest<T>;
109
110/// Represents the **unique identifier** for a slot within a pool.
111pub type SlotDigest<T> = Digest<T>;
112
113/// Represents the **source for generating a digest**,
114/// typically the runtime-caller's `AccountId` that seeds it.
115pub type DigestSource<T> = <T as pallet::Config>::AccountId;
116
117/// Represents the **owner of an asset or commitment**.  
118pub type Proprietor<T> = <T as pallet::Config>::AccountId;
119
120/// The fungible **balance type** for assets handled by the pallet.
121///
122/// Derived from the pallet's [`Config::Asset`] type and associated with the [`Proprietor`].
123pub type AssetOf<T, I = ()> = <<T as Config<I>>::Asset as Inspect<Proprietor<T>>>::Balance;
124
125/// Represents a **lazy-evaluated balance** for commitments.  
126///
127/// Doesn't specialize for commit-variants [`Config::Position`] as its implemented at
128/// higher level for commits, digests, indexes, pools, etc individually.
129pub type LazyBalanceOf<T, I = ()> = VirtualBalance<T, I>;
130
131/// Represents a **single commit instance** created by a commit operation.
132///
133/// This is a thin wrapper over [`VirtualReceipt`], capturing a receipt of the
134/// deposit at the time of commitment-similar to a bill that is later required
135/// during withdrawal resolution.
136///
137/// A commitment may accumulate multiple commit instances over time. Each
138/// instance is immutable, with aggregation and evaluation performed at
139/// higher levels.
140pub type CommitInstance<T, I = ()> = VirtualReceipt<T, I>;
141
142/// Combined identifier representing the **reason for freezing or locking a balance**.
143///
144/// Typically derived from the runtime's composite freeze-reason enum associated
145/// with [`Config::Asset`]. It is used to distinguish between different contexts
146/// in which balances are held, such as commitments, freezes, or other locking
147/// mechanisms.
148pub type CommitReason<T, I = ()> = <<T as Config<I>>::Asset as InspectFreeze<Proprietor<T>>>::Id;
149
150/// Alias to the pallet-defined balance execution context.
151///
152/// This represents the **type-level environment** configured by the runtime,
153/// providing all bounds, extensions, and error definitions required to
154/// materialize a lazy balance [`plugin`](frame_suite::plugins) family model.
155pub type BalanceContext<T, I = ()> = <T as Config<I>>::BalanceContext;
156
157/// Concrete [`plugin`](frame_suite::plugins)-model/family context derived
158/// from [`BalanceContext`].
159///
160/// This resolves the **plugin execution context**, supplying runtime-specific
161/// parameters and dependencies required by balance operations.
162pub type BalanceModelContext<T, I = ()> = <BalanceContext<T, I> as ModelContext>::Context;
163
164/// A generic [`virtual`](frame_suite::virtuals) structure. It acts as the
165/// core building block for all lazy balance-related virtual types.
166pub type LazyVirtual<T, A, R, Ti, Ad, I = ()> =
167    ProductType<T, I, BalanceModelContext<T, I>, A, R, Ti, Ad>;
168
169/// Virtual representation of a live lazy-balance.
170///
171/// Backed by the lazy balance model, meaning storage layouts are interpreted
172/// dynamically by the caller rather than the implementor.
173pub type VirtualBalance<T, I = ()> =
174    LazyVirtual<T, BalanceAsset, BalanceRational, BalanceTime, BalanceAddon, I>;
175
176/// Virtual representation of a balance snapshot.
177///
178/// Captures balance state at a specific point in time. Used for historical
179/// views and proportional calculations.
180pub type VirtualSnapShot<T, I = ()> =
181    LazyVirtual<T, SnapShotAsset, SnapShotRational, SnapShotTime, SnapShotAddon, I>;
182
183/// Virtual representation of a receipt (claim).
184///
185/// Represents a deferred claim over balance value in the lazy model:
186/// - created on deposit
187/// - resolved on withdrawal
188///
189/// Its value is computed dynamically based on global balance state.
190pub type VirtualReceipt<T, I = ()> =
191    LazyVirtual<T, ReceiptAsset, ReceiptRational, ReceiptTime, ReceiptAddon, I>;
192
193// ===============================================================================
194// ``````````````````````````` DIGEST BALANCES VECTOR ````````````````````````````
195// ===============================================================================
196
197/// Stores balance information for each variant of a digest.
198///
199/// A digest may have multiple semantic variants (e.g. `Affirmative`, `Contrary`, etc),
200/// each maintaining its own balance. This structure tracks the corresponding
201/// [`LazyBalanceOf`] for every variant.
202///
203/// Internally, it is backed by a [`BoundedVec`] whose length is fixed to the
204/// number of semantic variants defined by [`Config::Position`] via the
205/// `VariantCount` bound. This guarantees a **single, stable slot per variant**.
206///
207/// In some scenarios, a higher-indexed variant may be initialized before its
208/// preceding variants. In such cases, the earlier slots are filled with a default
209/// lazy balance to preserve positional invariants. While eagerly initializing all
210/// slots would also be invariant-safe, it could increase storage usage when many
211/// digests exist or when commitments do not utilize all variants.
212///
213/// To keep storage usage minimal, it is expected that the default variant
214/// (`[`Default`]` for [`Config::Position`]) occupies index `0`, as defined by
215/// [`PositionIndex`].
216#[derive(
217    Encode,
218    Decode,
219    Clone,
220    RuntimeDebug,
221    PartialEq,
222    Eq,
223    MaxEncodedLen,
224    TypeInfo,
225    DecodeWithMemTracking,
226)]
227#[scale_info(skip_type_params(T, I))]
228pub struct DigestInfo<T: Config<I>, I: 'static = ()>(
229    BoundedVec<LazyBalanceOf<T, I>, VariantCountOf<T::Position>>,
230);
231
232// ===============================================================================
233// ``````````````````` DIGEST BALANCES VECTOR INHERENT METHODS ```````````````````
234// ===============================================================================
235
236impl<T: Config<I>, I: 'static> DigestInfo<T, I> {
237    /// Returns the actively funded lazy balances of commitment digests along with
238    /// their corresponding semantic positions (commit variants).
239    ///
240    /// Returns `DispatchError` if lookup or decoding fails.
241    pub fn balances(&self) -> Result<Vec<(T::Position, LazyBalanceOf<T, I>)>, DispatchError> {
242        let bound = &self.0;
243        let mut collect = Vec::new();
244        for (i, balance) in bound.iter().enumerate() {
245            if *balance == Default::default() {
246                continue;
247            }
248            let position = <T::Position as PositionIndex>::position_of(i);
249            debug_assert!(
250                position.is_some(),
251                "commit-variant invalid position found for index {:?}, 
252                an example default of the position type for debugging is {:?}",
253                i,
254                T::Position::default()
255            );
256            let position = position.ok_or(Error::<T, I>::InvalidCommitVariantIndex)?;
257            collect.push((position, balance.clone()));
258        }
259        Ok(collect)
260    }
261
262    pub(crate) fn mut_balance(
263        &mut self,
264        variant: &T::Position,
265    ) -> Option<&mut LazyBalanceOf<T, I>> {
266        // Since we store variant balances as a vector, we need to deterministically
267        // determine an index associated with the given variant
268        let idx = variant.index();
269        self.0.get_mut(idx)
270    }
271
272    pub fn get_balance(&self, variant: &T::Position) -> Option<&LazyBalanceOf<T, I>> {
273        let idx = variant.index();
274        self.0.get(idx)
275    }
276
277    pub fn reveal(&self) -> BoundedVec<LazyBalanceOf<T, I>, VariantCountOf<T::Position>> {
278        self.0.clone()
279    }
280
281    pub(crate) fn init_balance(&mut self, variant: &T::Position) -> Result<(), DispatchError> {
282        let idx = variant.index();
283        // If the variant does not exist, create default variant balances up to the requested index
284        let vec = &mut self.0;
285        for i in 0..=idx {
286            if let None = vec.get(i) {
287                // Push default variant balances for missing variants
288                let result = vec.try_push(Default::default());
289                debug_assert!(
290                    result.is_ok(),
291                    "default commit-variants push results bad, where pushed 
292                    index {:?} is lesser than or equal to expected variant (position) 
293                    {:?} whoose index is {:?}",
294                    i,
295                    variant,
296                    idx
297                );
298                result.map_err(|_| Error::<T, I>::VariantsExhausted)?;
299            }
300        }
301        return Ok(());
302    }
303}
304
305// ===============================================================================
306// ``````````````````````````````` COMMITS VECTOR ````````````````````````````````
307// ===============================================================================
308
309/// Represents a collection of individual commit instances of a proprietor
310/// for a specific digest (direct/index/pool) and commitment reason.
311///
312/// The association with a single **digest** and **reason** is not structurally
313/// enforced at this level; instead, it is guaranteed by higher-level structures
314/// (typically [`CommitInfo`]).
315///
316/// Each commit is stored as a [`CommitInstance`] within a [`WeakBoundedVec`],
317/// bounding the number of commits per `(digest, reason)` pair to
318/// [`Config::MaxCommits`].
319#[derive(Encode, Decode, Clone, RuntimeDebug, MaxEncodedLen, TypeInfo, DecodeWithMemTracking)]
320#[scale_info(skip_type_params(T, I))]
321pub struct Commits<T: Config<I>, I: 'static = ()>(
322    WeakBoundedVec<CommitInstance<T, I>, T::MaxCommits>,
323);
324
325// ===============================================================================
326// ``````````````````````` COMMITS VECTOR INHERENT METHODS ```````````````````````
327// ===============================================================================
328
329impl<T: Config<I>, I: 'static> Commits<T, I> {
330    /// Initializes a new collection of commits with a single [`CommitInstance`],
331    /// typically derived from [`LazyBalance`] deposit operations.
332    ///
333    /// This establishes the initial commit, after which additional commits
334    /// may be appended via [`Commits::add_commit`].
335    pub(crate) fn new(instance: CommitInstance<T, I>) -> Result<Self, DispatchError> {
336        let max = T::MaxCommits::get();
337        ensure!(!max.is_zero(), Error::<T, I>::ZeroMaxCommits);
338        let vec = vec![instance];
339        let commits = WeakBoundedVec::<CommitInstance<T, I>, T::MaxCommits>::try_from(vec);
340        debug_assert!(
341            commits.is_ok(),
342            "single commit-instance vec to weak-vec of 
343            max-commit {} is non-zero failed but shouldn't be",
344            T::MaxCommits::get()
345        );
346        let commits = commits.map_err(|_| Error::<T, I>::CommitConstructionFailed)?;
347        return Ok(Commits(commits));
348    }
349
350    /// Adds a new commitment instance to the existing
351    /// commits collection.
352    ///
353    /// Returns `DispatchError` if the bounded vector capacity
354    /// is exhausted.
355    pub(crate) fn add_commit(
356        &mut self,
357        instance: CommitInstance<T, I>,
358    ) -> Result<(), DispatchError> {
359        debug_assert!(
360            !self.0.is_empty(),
361            "empty commits constructed without a single 
362            commit-instance, attempting to add a new-instance {:?}",
363            instance
364        );
365        ensure!(!self.0.is_empty(), Error::<T, I>::EmptyCommitsNotAllowed);
366        let vec = &mut self.0;
367        vec.try_push(instance)
368            .map_err(|_| Error::<T, I>::MaxCommitsReached)?;
369        Ok(())
370    }
371
372    pub fn commits(&self) -> WeakBoundedVec<CommitInstance<T, I>, T::MaxCommits> {
373        debug_assert!(
374            !self.0.is_empty(),
375            "empty commits constructed without 
376            a single commit-instance"
377        );
378        // no need to ensure for empty commits since this is a query function
379        // which can return empty vector without Result<T, DispatchError>
380        // ensure!(!self.0.is_empty(), Error::<T, I>::EmptyCommitsNotAllowed);
381        self.0.clone()
382    }
383}
384
385// ===============================================================================
386// ``````````````````````````` SINGLE COMMIT META-DATA ```````````````````````````
387// ===============================================================================
388
389/// Represents a commitment associated with a specific **digest** and reason.
390///
391/// This structure tracks commitments at the lowest level. The referenced
392/// `digest` is intentionally unclassified and may correspond to a direct,
393/// index, or pool digest, as those are higher-level abstractions built over
394/// the same commitment model.
395///
396/// Each [`CommitInfo`] aggregates multiple [`CommitInstance`] values produced
397/// over time for the same `(digest, reason)` pair, representing successive
398/// commitments raised by the proprietor.
399#[derive(
400    Encode,
401    Decode,
402    Clone,
403    RuntimeDebug,
404    MaxEncodedLen,
405    TypeInfo,
406    PartialEq,
407    Eq,
408    DecodeWithMemTracking,
409)]
410#[scale_info(skip_type_params(T, I))]
411pub struct CommitInfo<T: Config<I>, I: 'static = ()> {
412    /// The target digest this commitment is associated with.
413    ///
414    /// The digest is intentionally unclassified and may refer to a
415    /// direct, index, or pool digest.
416    digest: Digest<T>,
417
418    /// Collection of commit instances ([`CommitInstance`])
419    /// associated with this `digest`.
420    ///
421    /// This collection is internally mutated via [`Commits::add_commit`]
422    /// whenever new commit instances are appended.
423    commits: Commits<T, I>,
424
425    /// The semantic disposition (variant) of the commitment
426    /// (e.g. `Affirmative`, `Contrary`, etc).
427    ///
428    /// This is semantically meaningful only for direct digests. For index
429    /// and pool digests, this field acts as a structural placeholder, as
430    /// those abstractions manage their own variant information through
431    /// entries and slots respectively.
432    variant: T::Position,
433}
434
435// ===============================================================================
436// `````````````````` SINGLE COMMIT META-DATA INHERENT METHODS ```````````````````
437// ===============================================================================
438
439impl<T: Config<I>, I: 'static> CommitInfo<T, I> {
440    /// Creates a new [`CommitInfo`] with an initial commit instance.
441    ///
442    /// This constructor initializes the internal [`Commits`] collection
443    /// in a controlled manner and establishes the initial commitment
444    /// state for the given digest and reason.
445    pub(crate) fn new(
446        digest: Digest<T>,
447        instance: CommitInstance<T, I>,
448        variant: T::Position,
449    ) -> Result<Self, DispatchError> {
450        let commits = Commits::<T, I>::new(instance)?;
451        let try_position = <T::Position as PositionIndex>::position_of(
452            <T::Position as PositionIndex>::index(&variant),
453        );
454        debug_assert!(
455            try_position.is_some(),
456            "cannot equalize new-commit's given variant {:?} and its derived 
457            positional index (not consistent) when creating new commit-info for 
458            proprietor towards non-classified-digest {:?}",
459            variant,
460            digest
461        );
462        let position = try_position.ok_or(Error::<T, I>::InvalidCommitVariantIndex)?;
463        debug_assert!(
464            position == variant,
465            "new-commit's given variant {:?} and its derived
466            positional index (not consistent) variant is not same, 
467            found {:?} when creating new commit-info for 
468            proprietor towards non-classified-digest {:?}",
469            variant,
470            position,
471            digest
472        );
473        ensure!(
474            position == variant,
475            Error::<T, I>::InvalidCommitVariantIndex
476        );
477        Ok(Self {
478            digest,
479            commits,
480            variant,
481        })
482    }
483
484    /// Returns the individual commit instances of the proprietor.
485    #[inline]
486    pub fn commits(&self) -> WeakBoundedVec<CommitInstance<T, I>, T::MaxCommits> {
487        Commits::<T, I>::commits(&self.commits)
488    }
489
490    /// Returns the digest proprietor committed to.
491    pub fn digest(&self) -> Digest<T> {
492        self.digest.clone()
493    }
494
495    /// Returns the digest's variant proprietor committed to.
496    pub fn variant(&self) -> T::Position {
497        self.variant.clone()
498    }
499
500    /// Adds a new commitment instance to the existing commits
501    /// collection of the proprietor's commit-info for a digest.
502    ///
503    /// Returns `DispatchError` if the bounded vector capacity
504    /// is exhausted.
505    #[inline]
506    pub(crate) fn add_commit(
507        &mut self,
508        instance: CommitInstance<T, I>,
509    ) -> Result<(), DispatchError> {
510        self.commits.add_commit(instance)
511    }
512}
513
514// ===============================================================================
515// ````````````````````````` INDEX SINGLE-ENTRY META-DATA ````````````````````````
516// ===============================================================================
517
518/// Represents a single entry within an index.
519///
520/// Each entry maps a direct digest to a non-zero share allocation and a
521/// semantic variant. This allows index commitments to be proportionally
522/// distributed across multiple underlying digests.
523#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DecodeWithMemTracking)]
524#[scale_info(skip_type_params(T, I))]
525pub struct EntryInfo<T: Config<I>, I: 'static = ()> {
526    /// The direct digest identifying this entry.
527    digest: EntryDigest<T>,
528
529    /// Number of shares (must be non-zero) associated with this entry.
530    shares: T::Shares,
531
532    /// Semantic variant/disposition of this entry (e.g. `Affirmative`, `Contrary`, etc).
533    ///
534    /// Commitments placed through this entry are credited to the corresponding
535    /// variant balance of the underlying direct digest.
536    variant: T::Position,
537}
538
539// ===============================================================================
540// ````````````````````` INDEX SINGLE-ENTRY INHERENT METHODS `````````````````````
541// ===============================================================================
542
543impl<T: Config<I>, I: 'static> EntryInfo<T, I> {
544    /// Creates a new [`EntryInfo`] for an index.
545    ///
546    /// Validates that the provided shares are non-zero and that the variant
547    /// is semantically valid.
548    ///
549    /// Returns `DispatchError` if validation fails.
550    pub fn new(
551        digest: EntryDigest<T>,
552        shares: T::Shares,
553        variant: T::Position,
554    ) -> Result<Self, DispatchError> {
555        ensure!(!shares.is_zero(), Error::<T, I>::ShareCannotBeZero);
556        let try_position = <T::Position as PositionIndex>::position_of(
557            <T::Position as PositionIndex>::index(&variant),
558        );
559        debug_assert!(
560            try_position.is_some(),
561            "cannot equalize new-commit's given variant {:?} and its derived 
562            positional index (not consistent) when creating new entry-info for 
563            entry-digest {:?} of shares {:?}",
564            variant,
565            digest,
566            shares
567        );
568        let position = try_position.ok_or(Error::<T, I>::InvalidCommitVariantIndex)?;
569        debug_assert!(
570            position == variant,
571            "new-commit's given variant {:?} and its derived
572            positional index (not consistent) variant is not same, 
573            found {:?} when creating new entry-info for entry-digest 
574            {:?} of shares {:?}",
575            variant,
576            position,
577            digest,
578            shares
579        );
580        ensure!(
581            position == variant,
582            Error::<T, I>::InvalidCommitVariantIndex
583        );
584        Ok(Self {
585            digest,
586            shares,
587            variant,
588        })
589    }
590
591    /// Return the share value of this entry.
592    ///
593    /// `DispatchError` if inconsistency detected.
594    pub fn shares(&self) -> T::Shares {
595        self.shares
596    }
597
598    /// Returns the direct digest associated with this entry.
599    pub fn digest(&self) -> Digest<T> {
600        self.digest.clone()
601    }
602
603    /// Returns the variant associated with this entry's direct digest.
604    pub fn variant(&self) -> T::Position {
605        self.variant.clone()
606    }
607}
608
609impl<T: Config<I>, I: 'static> Clone for EntryInfo<T, I> {
610    fn clone(&self) -> Self {
611        Self {
612            digest: self.digest.clone(),
613            shares: self.shares,
614            variant: self.variant.clone(),
615        }
616    }
617}
618
619// ===============================================================================
620// ```````````````````````````` INDEX ENTRIES VECTOR `````````````````````````````
621// ===============================================================================
622
623/// Represents a collection of entries within an index.
624///
625/// Backed by a [`WeakBoundedVec`] to enforce an upper bound on the number of
626/// entries (via [`Config::MaxIndexEntries`]) while maintaining efficient,
627/// bounded storage.
628///
629/// This serves as the low-level container for all [`EntryInfo`] items that
630/// constitute an index.
631#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DecodeWithMemTracking)]
632#[scale_info(skip_type_params(T, I))]
633pub struct Entries<T: Config<I>, I: 'static = ()>(
634    WeakBoundedVec<EntryInfo<T, I>, T::MaxIndexEntries>,
635);
636
637// ===============================================================================
638// ```````````````````` INDEX ENTRIES VECTOR INHERENT METHODS ````````````````````
639// ===============================================================================
640
641impl<T: Config<I>, I: 'static> Entries<T, I> {
642    /// Creates a new [`Entries`] collection for an index from a vector of
643    /// validated [`EntryInfo`] items.
644    ///
645    /// Returns `DispatchError` if any inconsistencies detected.
646    pub fn new(entries: Vec<EntryInfo<T, I>>) -> Result<Self, DispatchError> {
647        let max = T::MaxIndexEntries::get();
648        ensure!(!max.is_zero(), Error::<T, I>::TriedCreatingHaltedIndexes);
649        ensure!(!entries.is_empty(), Error::<T, I>::EmptyEntriesNotAllowed);
650        let mut seen = BTreeSet::new();
651        for entry in &entries {
652            ensure!(
653                seen.insert(entry.digest.clone()),
654                Error::<T, I>::DuplicateEntry
655            );
656        }
657        let entries = WeakBoundedVec::<EntryInfo<T, I>, T::MaxIndexEntries>::try_from(entries)
658            .map_err(|_| Error::<T, I>::MaxEntriesReached)?;
659        return Ok(Entries(entries));
660    }
661
662    /// Returns all [`EntryInfo`] items contained in this list as a owned vector.
663    pub fn entries(&self) -> Vec<EntryInfo<T, I>> {
664        let bounded = &self.0;
665        let mut collect = Vec::new();
666        for entry in bounded {
667            collect.push(entry.clone())
668        }
669        debug_assert!(
670            !collect.is_empty(),
671            "empty entries-list initiated which 
672            should not be for indexes"
673        );
674        collect
675    }
676
677    /// Adds a new [`EntryInfo`] to the list of entries.
678    ///
679    /// Returns `DispatchError` if vector bound exhausted
680    /// or duplicate found.
681    pub fn add_entry(&mut self, entry: EntryInfo<T, I>) -> Result<(), DispatchError> {
682        debug_assert!(
683            !self.0.is_empty(),
684            "empty entries constructed without a single 
685            commit-instance, attempting to add a new-entry",
686        );
687        ensure!(!self.0.is_empty(), Error::<T, I>::EmptyEntriesNotAllowed);
688        let vec = &mut self.0;
689        vec.try_push(entry)
690            .map_err(|_| Error::<T, I>::MaxEntriesReached)?;
691        let mut seen = BTreeSet::new();
692        for entry in vec {
693            ensure!(
694                seen.insert(entry.digest.clone()),
695                Error::<T, I>::DuplicateEntry
696            );
697        }
698        Ok(())
699    }
700
701    /// Removes an existing [`EntryInfo`] from the entries-list.
702    ///
703    /// Returns `DispatchError` if entry of digest not found.
704    pub fn remove_entry(&mut self, entry: &EntryDigest<T>) -> Result<(), DispatchError> {
705        debug_assert!(
706            !self.0.is_empty(),
707            "empty entries constructed without a single 
708            commit-instance, attempting to remove an existing-entry {:?}",
709            entry
710        );
711        ensure!(
712            (!self.0.is_empty() && self.0.len() > 1),
713            Error::<T, I>::EmptyEntriesNotAllowed
714        );
715        debug_assert!(
716            self.0.len() > 1,
717            "attempting to remove an existing-entry {:?}, which 
718            will result in zero-length entries",
719            entry
720        );
721        let mut entry_idx = None;
722        for (i, entry_of) in self.0.iter().enumerate() {
723            if entry_of.digest == *entry {
724                entry_idx = Some(i);
725                break;
726            }
727        }
728
729        match entry_idx {
730            Some(idx) => {
731                self.0.remove(idx);
732            }
733            None => {
734                return Err(Error::<T, I>::EntryOfIndexNotFound)?;
735            }
736        }
737        Ok(())
738    }
739}
740
741// ===============================================================================
742// ``````````````````````````````` INDEX META-DATA ```````````````````````````````
743// ===============================================================================
744
745/// Represents an index containing multiple entries.
746///
747/// An `IndexInfo` tracks the overall capital, total balance, and the entries themselves.
748///
749#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DecodeWithMemTracking)]
750#[scale_info(skip_type_params(T, I))]
751pub struct IndexInfo<T: Config<I>, I: 'static = ()> {
752    /// Total asset depositted to this index
753    ///
754    /// This does not qualify as real-time value of the index,
755    /// since entries are finite digests that should be queried
756    /// for such cases.
757    principal: AssetOf<T, I>,
758
759    /// Total shares/capital across all entries in this index
760    capital: T::Shares,
761
762    /// The collection of entries making up this index
763    entries: Entries<T, I>,
764}
765
766// ===============================================================================
767// ``````````````````````` INDEX META-DATA INHERENT METHODS ``````````````````````
768// ===============================================================================
769
770impl<T: Config<I>, I: 'static> IndexInfo<T, I> {
771    /// Creates a new [`IndexInfo`] from a collection of entries.
772    ///
773    /// This function calculates the total capital by summing the shares
774    /// of all entries. It retains only entries of non-zero shares.
775    ///
776    /// Returns `DispatchError` if any of invariant fails
777    /// - Total Capital cannot be zero
778    /// - Entries should not be empty
779    pub(crate) fn new(entries: &mut Entries<T, I>) -> Result<Self, DispatchError> {
780        debug_assert!(!entries.0.is_empty(), "entries is constructed empty");
781        ensure!(!entries.0.is_empty(), Error::<T, I>::EmptyEntriesNotAllowed);
782        let mut total_capital = T::Shares::zero();
783        for entry in &entries.0 {
784            let shares = entry.shares;
785            debug_assert!(
786                !shares.is_zero(),
787                "entry for digest {:?} of variant {:?} share is constructed zero",
788                entry.digest,
789                entry.variant
790            );
791            ensure!(!shares.is_zero(), Error::<T, I>::ShareCannotBeZero);
792            total_capital = total_capital
793                .checked_add(&shares)
794                .ok_or(Error::<T, I>::CapitalOverflowed)?;
795        }
796        debug_assert!(
797            !total_capital.is_zero(),
798            "total capital is zero while its entry shares isn't"
799        );
800        ensure!(!total_capital.is_zero(), Error::<T, I>::CapitalCannotBeZero);
801        Ok(Self {
802            principal: AssetOf::<T, I>::zero(),
803            capital: total_capital,
804            entries: entries.clone(),
805        })
806    }
807
808    /// Returns the index's capital - total shares.
809    pub fn capital(&self) -> T::Shares {
810        let value = self.capital;
811        debug_assert!(!value.is_zero(), "index capital is constructed zero");
812        value
813    }
814
815    /// Returns the index's principal, i.e. the total amount
816    /// deposited by proprietors.
817    pub fn principal(&self) -> AssetOf<T, I> {
818        self.principal
819    }
820
821    /// Returns the index's entries vector list.
822    #[inline]
823    pub fn entries(&self) -> Vec<EntryInfo<T, I>> {
824        Entries::<T, I>::entries(&self.entries)
825    }
826
827    /// Reveal the actual entries [`Entries`]
828    pub fn reveal_entries(&self) -> Entries<T, I> {
829        self.entries.clone()
830    }
831
832    /// Checks if an entry of digest exists in the index.
833    pub fn entry_exists(&self, entry: &EntryDigest<T>) -> DispatchResult {
834        let entries = &self.entries;
835        debug_assert!(!entries.0.is_empty(), "entries is constructed empty");
836        ensure!(!entries.0.is_empty(), Error::<T, I>::EmptyEntriesNotAllowed);
837        let mut idx = None;
838        // Locate the target slot.
839        for (i, entry_of) in entries.0.iter().enumerate() {
840            if entry_of.digest == *entry {
841                idx = Some(i);
842            }
843        }
844
845        // If no matching slot exists, nothing to remove.
846        if let Some(_) = idx {
847            return Ok(());
848        };
849
850        Err(Error::<T, I>::EntryOfIndexNotFound.into())
851    }
852
853    /// Sets the index principal to the provided value, replacing the existing balance.
854    pub(crate) fn set_balance(&mut self, principal: AssetOf<T, I>) {
855        self.principal = principal
856    }
857}
858
859// ===============================================================================
860// ```````````````````````````` SINGLE SLOT META-DATA ````````````````````````````
861// ===============================================================================
862
863/// Represents a slot within a pool, derived from an index entry.
864///
865/// A `SlotInfo` tracks the underlying digest, the allocated shares, the
866/// slot's single [`CommitInstance`] (a collective receipt)
867/// and the variant/disposition. It is primarily used when creating or
868/// managing pools derived from index entries.
869#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DecodeWithMemTracking)]
870#[scale_info(skip_type_params(T, I))]
871pub struct SlotInfo<T: Config<I>, I: 'static = ()> {
872    /// Unique identifier for the slot
873    digest: SlotDigest<T>,
874
875    /// Shares allocated to this slot
876    shares: T::Shares,
877
878    /// Commit-Instance associated with this slot
879    ///
880    /// Since pools collectively manages funds, slots also
881    /// inherit such behaviour. Hence it acts similar to a proprietor
882    /// holding a deposit receipt from digest for their commitment.
883    commit: CommitInstance<T, I>,
884
885    /// Disposition of this slot (e.g., Affirmative, Contrary, Awaiting)
886    variant: T::Position,
887}
888
889// ===============================================================================
890// ``````````````````````` SINGLE SLOT META-DATA INHERENTS ```````````````````````
891// ===============================================================================
892
893impl<T: Config<I>, I: 'static> SlotInfo<T, I> {
894    /// Returns the pool's slot's total shares.
895    pub fn shares(&self) -> T::Shares {
896        self.shares
897    }
898
899    /// Returns the pool's slot's commit-instance
900    /// (as a pseudo-proprietor).
901    pub fn commit(&self) -> CommitInstance<T, I> {
902        self.commit.clone()
903    }
904
905    /// Returns the pool's slot's digest.
906    pub fn digest(&self) -> SlotDigest<T> {
907        self.digest.clone()
908    }
909
910    /// Returns the pool's slot's variant.
911    pub fn variant(&self) -> T::Position {
912        self.variant.clone()
913    }
914
915    /// Updates the [`CommitInstance`] associated with this slot.
916    ///
917    /// This method **replaces the existing commit instance** with the provided one.
918    /// It does not perform any validation, aggregation, or merging of commits,
919    /// the caller is responsible for ensuring correctness and consistency.
920    ///
921    /// ## Parameters
922    /// - `commit`: The new [`CommitInstance`] to associate with this slot.
923    ///
924    /// ## Invariants
925    /// - This operation assumes the slot is already initialized.
926    /// - The provided commit should be semantically valid for this slot's
927    ///   digest, shares, and variant.
928    ///
929    /// ## Note
930    /// This is an internal mutation helper and is not intended to enforce
931    /// higher-level commitment rules.
932    fn set_slot_commit(&mut self, commit: CommitInstance<T, I>) {
933        self.commit = commit
934    }
935}
936
937// ===============================================================================
938// ```````````````````````` ENTRY-TO-SLOT SAFE CONVERSION ````````````````````````
939// ===============================================================================
940
941impl<T: Config<I>, I: 'static> From<EntryInfo<T, I>> for SlotInfo<T, I> {
942    /// Converts an `EntryInfo` into a `SlotInfo`.
943    ///
944    /// This is used when creating a pool from an index, as each index entry
945    /// corresponds to a pool slot. The deposit-receipt is initialized to empty
946    /// as slot doesn't hold a commit currently.
947    ///
948    /// ## Returns
949    /// A `SlotInfo` with the same digest, shares, and variant.
950    fn from(entry: EntryInfo<T, I>) -> Self {
951        Self {
952            digest: entry.digest,
953            shares: entry.shares,
954            commit: Default::default(),
955            variant: entry.variant,
956        }
957    }
958}
959
960// ===============================================================================
961// ````````````````````````````````` SLOTS VECTOR ````````````````````````````````
962// ===============================================================================
963
964/// Represents a collection of slots within a pool.
965///
966/// `Slots` is essentially a bounded vector of [`SlotInfo`] instances, enforcing
967/// a maximum number of slots defined by `MaxIndexEntries`.
968///
969/// This type ensures that pools derived from indexes cannot exceed the maximum
970/// allowed number of slots.
971#[derive(Encode, Decode, MaxEncodedLen, TypeInfo)]
972#[scale_info(skip_type_params(T, I))]
973pub struct Slots<T: Config<I>, I: 'static = ()>(WeakBoundedVec<SlotInfo<T, I>, T::MaxIndexEntries>);
974
975// ===============================================================================
976// ```````````````````````` SLOTS VECTOR INHERENT METHODS ````````````````````````
977// ===============================================================================
978
979impl<T: Config<I>, I: 'static> Slots<T, I> {
980    /// Returns the pools's individual slots as a vector.
981    pub fn slots(&self) -> Vec<SlotInfo<T, I>> {
982        let bounded = &self.0;
983        let mut collect = Vec::new();
984        for slot in bounded {
985            collect.push(slot.clone())
986        }
987        debug_assert!(
988            !collect.is_empty(),
989            "empty slots initiated which should not be for pools"
990        );
991        collect
992    }
993
994    /// Adds a new [`EntryInfo`] to the list of slots, since slots can only
995    /// be derived from entry.
996    ///
997    /// Returns `DispatchError` if vector bound exhausted or duplicate found.
998    fn add_slot(&mut self, entry: EntryInfo<T, I>) -> Result<(), DispatchError> {
999        debug_assert!(
1000            !self.0.is_empty(),
1001            "empty slots constructed without a single 
1002            slot, attempting to add a new-slot via entry",
1003        );
1004        ensure!(!self.0.is_empty(), Error::<T, I>::EmptySlotsNotAllowed);
1005        let vec = &mut self.0;
1006        vec.try_push(entry.into())
1007            .map_err(|_| Error::<T, I>::MaxSlotsReached)?;
1008        let mut seen = BTreeSet::new();
1009        for slot in vec {
1010            ensure!(
1011                seen.insert(slot.digest.clone()),
1012                Error::<T, I>::DuplicateSlot,
1013            );
1014        }
1015        Ok(())
1016    }
1017
1018    /// Removes an existing [`SlotInfo`] from the slots-list.
1019    ///
1020    /// Returns `DispatchError` if slot of digest not found.
1021    fn remove_slot(&mut self, slot: &SlotDigest<T>) -> Result<(), DispatchError> {
1022        debug_assert!(
1023            !self.0.is_empty(),
1024            "empty slots constructed without a single 
1025            slot, attempting to remove an existing-slot {:?}",
1026            slot,
1027        );
1028        ensure!(
1029            (!self.0.is_empty() && self.0.len() > 1),
1030            Error::<T, I>::EmptySlotsNotAllowed
1031        );
1032        debug_assert!(
1033            self.0.len() > 1,
1034            "attempting to remove an existing-slot {:?}, which 
1035            will result in zero-length slots",
1036            slot
1037        );
1038        let mut slot_idx = None;
1039        for (i, slot_of) in self.0.iter().enumerate() {
1040            if slot_of.digest == *slot {
1041                slot_idx = Some(i);
1042                break;
1043            }
1044        }
1045
1046        match slot_idx {
1047            Some(idx) => {
1048                self.0.remove(idx);
1049            }
1050            None => {
1051                return Err(Error::<T, I>::SlotOfPoolNotFound)?;
1052            }
1053        }
1054        Ok(())
1055    }
1056
1057    /// Updates the commit instance of a slot identified by the given `digest`.
1058    ///
1059    /// This function performs a linear search over the internal slots collection
1060    /// to locate a slot whose `digest` matches the provided `digest`.
1061    ///
1062    /// - If a matching slot is found:
1063    ///     - Its associated [`CommitInstance`] is **replaced** with the provided `commit`.
1064    /// - If no matching slot exists:
1065    ///     - Returns [`Error::SlotOfPoolNotFound`].
1066    fn set_slot_commit(
1067        &mut self,
1068        digest: &SlotDigest<T>,
1069        commit: CommitInstance<T, I>,
1070    ) -> Result<(), DispatchError> {
1071        debug_assert!(
1072            !self.0.is_empty(),
1073            "empty slots constructed without a single 
1074            slot {:?}",
1075            self,
1076        );
1077
1078        let mut slot_idx = None;
1079        for (i, slot_of) in self.0.iter().enumerate() {
1080            if slot_of.digest == *digest {
1081                slot_idx = Some(i);
1082                break;
1083            }
1084        }
1085
1086        match slot_idx {
1087            Some(idx) => {
1088                let slot_of = self
1089                    .0
1090                    .get_mut(idx)
1091                    .ok_or(Error::<T, I>::SlotOfPoolNotFound)?;
1092                slot_of.set_slot_commit(commit);
1093            }
1094            None => {
1095                return Err(Error::<T, I>::SlotOfPoolNotFound)?;
1096            }
1097        }
1098        Ok(())
1099    }
1100}
1101
1102// ===============================================================================
1103// `````````````````````` SLOT-TO-ENTRY FALLIBLE CONVERSION ``````````````````````
1104// ===============================================================================
1105
1106impl<T: Config<I>, I: 'static> TryFrom<Entries<T, I>> for Slots<T, I> {
1107    type Error = DispatchError;
1108
1109    /// Converts a collection of `Entries` into `Slots`.
1110    ///
1111    /// Each [`EntryInfo`] is converted into a [`SlotInfo`] using the `From<EntryInfo>` implementation.
1112    ///
1113    /// ## Returns
1114    /// - `Ok(Slots)` if the conversion succeeds within the maximum allowed slots.
1115    /// - `Err(DispatchError)` if the resulting collection exceeds `MaxIndexEntries`.
1116    fn try_from(entries: Entries<T, I>) -> Result<Self, Self::Error> {
1117        let raw_vec: Vec<SlotInfo<T, I>> =
1118            entries.0.into_iter().map(|entry| entry.into()).collect();
1119
1120        let entries = WeakBoundedVec::try_from(raw_vec)
1121            .map(Slots)
1122            .map_err(|_| Error::<T, I>::MaxSlotsReached.into());
1123        debug_assert!(
1124            entries.is_ok(),
1125            "both entries and slots have same upper weak-bound
1126            but slots cannot be tried from entries"
1127        );
1128        entries
1129    }
1130}
1131
1132// ===============================================================================
1133// ```````````````````````````````` POOL META-DATA ```````````````````````````````
1134// ===============================================================================
1135
1136/// Represents a managed pool derived from an index.
1137///
1138/// `PoolInfo` aggregates capital, commission, and the collection of slots,
1139/// each of which represents a portion of the pool linked to underlying entries.
1140/// Unlike indexes, pools are mutable and can have their slot balances adjusted dynamically.
1141///
1142/// Pools are created from an [`IndexInfo`] (or [`Entries`]), allowing the
1143/// shares of each entry to be translated into slots within the pool.
1144///
1145/// Pool's slot shares/variants can then be adjusted over time, while the
1146/// pool's commission and structure remain consistent.
1147///
1148#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DecodeWithMemTracking)]
1149#[scale_info(skip_type_params(T, I))]
1150pub struct PoolInfo<T: Config<I>, I: 'static = ()> {
1151    /// Real-time balance of the pool.
1152    ///
1153    /// Stored as [`LazyBalanceOf`] which allows efficient updates and tracking
1154    /// proprietor depositted balances internally within itself at higher level.
1155    balance_of: LazyBalanceOf<T, I>,
1156
1157    /// Total capital of the pool.
1158    ///
1159    /// Computed as the sum of all shares in the pool's slots.
1160    /// Represents the total weight or stake across all slots.
1161    capital: T::Shares,
1162
1163    /// Commission rate charged by the pool manager.
1164    ///
1165    /// The manager earns this percentage of rewards or profits
1166    /// generated by the pool.
1167    commission: T::Commission,
1168
1169    /// Collection of slots representing the underlying assets
1170    /// or commitments.
1171    ///
1172    /// Each slot corresponds to an entry from the index that formed
1173    /// this pool. Slots track individual shares, its deposit receipt,
1174    /// and disposition [`Disposition`](frame_suite::Disposition).
1175    slots: Slots<T, I>,
1176}
1177
1178// ===============================================================================
1179// ``````````````````````` POOL META-DATA INHERENT METHODS ```````````````````````
1180// ===============================================================================
1181
1182impl<T: Config<I>, I: 'static> PoolInfo<T, I> {
1183    /// Creates a new pool from a list of index entries and a commission rate.
1184    ///
1185    /// - Converts the [`Entries`] into [`Slots`].
1186    /// - Calculates the total capital by summing all entry shares.
1187    /// - Initializes the pool balance to zero.
1188    ///
1189    /// ## Returns
1190    /// - `Ok(PoolInfo)` if successful.
1191    /// - `Err(DispatchError)` otherwise.
1192    pub(crate) fn new(
1193        index_entries: Entries<T, I>,
1194        commission: T::Commission,
1195    ) -> Result<Self, DispatchError> {
1196        let total_capital = index_entries
1197            .0
1198            .iter()
1199            .try_fold(T::Shares::zero(), |acc, slot| {
1200                acc.checked_add(&slot.shares)
1201                    .ok_or(Error::<T, I>::CapitalOverflowed)
1202            })?;
1203
1204        let slots = index_entries.try_into()?;
1205
1206        Ok(Self {
1207            balance_of: Default::default(),
1208            capital: total_capital,
1209            commission,
1210            slots,
1211        })
1212    }
1213
1214    /// Returns the pool's lazy balance.
1215    pub fn balance(&self) -> LazyBalanceOf<T, I> {
1216        self.balance_of.clone()
1217    }
1218
1219    /// Set the pool balance usually only after release.
1220    pub(crate) fn set_balance(&mut self, balance: LazyBalanceOf<T, I>) {
1221        self.balance_of = balance;
1222    }
1223
1224    /// Returns the pool's total capital i.e., total shares of all slots.
1225    pub fn capital(&self) -> T::Shares {
1226        let value = self.capital;
1227        debug_assert!(!value.is_zero(), "index capital is constructed zero");
1228        value
1229    }
1230
1231    /// Returns the pool's commission i.e., manager's share while resolving the pool's commit.
1232    pub fn commission(&self) -> T::Commission {
1233        self.commission
1234    }
1235
1236    /// Returns the pools's individual slots as a vector.
1237    pub fn slots(&self) -> Vec<SlotInfo<T, I>> {
1238        self.slots.slots()
1239    }
1240
1241    /// Resets the pools's top-level lazy balance.
1242    ///
1243    /// This lazy balance acts like a pseudo-direct-digest
1244    /// for proprietors structurally for pool-commitments.
1245    pub(crate) fn balance_reset(&mut self) {
1246        self.balance_of = Default::default();
1247        for slot in &mut self.slots.0 {
1248            slot.commit = Default::default();
1249        }
1250    }
1251
1252    /// Replaces the commit instance of the slot identified by `digest`
1253    /// by delegating to the underlying `slots`.
1254    #[inline]
1255    pub(crate) fn set_slot_commit(
1256        &mut self,
1257        digest: &SlotDigest<T>,
1258        commit: CommitInstance<T, I>,
1259    ) -> Result<(), DispatchError> {
1260        self.slots.set_slot_commit(digest, commit)
1261    }
1262
1263    /// Adds a new [`EntryInfo`] to the list of pool's slots,
1264    /// as slots [`SlotInfo`] can only be derived from an entry.
1265    ///
1266    /// Unlike indexes which are immutable, pools are mutable hence requires
1267    /// slot management via higher-structures.
1268    ///
1269    /// Returns `DispatchError` otherwise.
1270    pub(crate) fn add_slot(&mut self, entry: EntryInfo<T, I>) -> DispatchResult {
1271        self.slots.add_slot(entry)?;
1272        let total_capital = self
1273            .slots
1274            .0
1275            .iter()
1276            .try_fold(T::Shares::zero(), |acc, slot| {
1277                acc.checked_add(&slot.shares)
1278                    .ok_or(Error::<T, I>::CapitalOverflowed)
1279            })?;
1280        self.capital = total_capital;
1281        Ok(())
1282    }
1283
1284    /// Removes an existing slot from the pool.
1285    ///
1286    /// Unlike indexes which are immutable, pools are mutable hence requires
1287    /// slot management via higher-structures.
1288    ///
1289    /// Returns `DispatchError` otherwise.
1290    pub(crate) fn remove_slot(&mut self, slot: &SlotDigest<T>) -> DispatchResult {
1291        self.slots.remove_slot(slot)?;
1292        ensure!(
1293            !self.slots.0.is_empty(),
1294            Error::<T, I>::EmptySlotsNotAllowed
1295        );
1296        let total_capital = self
1297            .slots
1298            .0
1299            .iter()
1300            .try_fold(T::Shares::zero(), |acc, slot| {
1301                acc.checked_add(&slot.shares)
1302                    .ok_or(Error::<T, I>::CapitalOverflowed)
1303            })?;
1304        self.capital = total_capital;
1305        Ok(())
1306    }
1307
1308    /// Checks if a slot of digest exists in the pool.
1309    pub fn slot_exists(&self, slot: &SlotDigest<T>) -> DispatchResult {
1310        let slots = &self.slots;
1311        debug_assert!(!slots.0.is_empty(), "slots are constructed empty");
1312        let mut idx = None;
1313        // Locate the target slot.
1314        for (i, slot_of) in slots.0.iter().enumerate() {
1315            if slot_of.digest == *slot {
1316                idx = Some(i);
1317            }
1318        }
1319
1320        // If no matching slot exists, nothing to remove.
1321        if let Some(_) = idx {
1322            return Ok(());
1323        };
1324
1325        Err(Error::<T, I>::SlotOfPoolNotFound.into())
1326    }
1327}
1328
1329// ===============================================================================
1330// ````````````````````````` KEY-GENERATION SEED STRUCTS `````````````````````````
1331// ===============================================================================
1332
1333/// A composite structure combining a commit reason with an index.
1334///
1335/// This struct is primarily used as a **key generation seed** for creating
1336/// unique digests associated with an index under a specific reason. By combining
1337/// both the `reason` and the [`IndexInfo`] itself, the resulting hash is unique
1338/// and deterministic for the given combination.
1339///
1340/// Used in functions like `gen_index_digest` to ensure that the same index under
1341/// the same reason always produces the same digest, while different reasons or
1342/// different index contents yield different digests.
1343#[derive(
1344    Encode,
1345    Decode,
1346    RuntimeDebug,
1347    MaxEncodedLen,
1348    TypeInfo,
1349    Constructor,
1350    PartialEq,
1351    Eq,
1352    DecodeWithMemTracking,
1353)]
1354#[scale_info(skip_type_params(T, I))]
1355pub struct IndexOfReason<T: Config<I>, I: 'static = ()> {
1356    /// The reason or context under which this index is associated.
1357    pub reason: CommitReason<T, I>,
1358
1359    /// The index information being combined with the reason for key generation.
1360    pub index: IndexInfo<T, I>,
1361}
1362
1363/// A composite structure combining a commit reason with a pool.
1364///
1365/// This struct is used as a **key generation seed** for creating unique digests
1366/// associated with a pool under a specific reason. By combining the `reason` and
1367/// the [`PoolInfo`], the digest is deterministic and unique for the pool's
1368/// composition and context.
1369///
1370/// Primarily utilized in functions like `gen_pool_digest`, allowing consistent
1371/// and collision-resistant digest generation for pools derived from an index.
1372#[derive(
1373    Encode,
1374    Decode,
1375    RuntimeDebug,
1376    MaxEncodedLen,
1377    TypeInfo,
1378    Constructor,
1379    PartialEq,
1380    Eq,
1381    DecodeWithMemTracking,
1382)]
1383#[scale_info(skip_type_params(T, I))]
1384pub struct PoolOfReason<T: Config<I>, I: 'static = ()> {
1385    /// The reason or context under which this pool is associated.
1386    pub reason: CommitReason<T, I>,
1387
1388    /// The pool information being combined with the reason for key generation.
1389    pub pool: PoolInfo<T, I>,
1390}
1391
1392// ===============================================================================
1393// ``````````````````````` IMBALANCE CARRIER (ASSET-DELTA) ```````````````````````
1394// ===============================================================================
1395
1396/// Represents a net change (delta) in assets for a particular operation.
1397///
1398/// This struct is used to track **both deposits and withdrawals** in a single structure,
1399/// which is particularly useful when working with unbalanced fungible traits.
1400///
1401/// Since the pallet manually mints and burns assets to maintain equilibrium,
1402/// both `deposit` and `withdraw` fields are necessary.
1403///
1404/// ## Usage
1405/// - **Withdrawal operations**: Used to record the assets that need to be withdrawn
1406/// from a pool or digest.
1407/// - **Deposit/Recovery**: Tracks any leftover assets that must be deposited back to
1408/// maintain total system balance.
1409/// - **Equilibrium maintenance**: Helps reconcile unbalanced operations by explicitly
1410/// separating minting and burning.
1411#[derive(
1412    Encode,
1413    Decode,
1414    MaxEncodedLen,
1415    RuntimeDebug,
1416    TypeInfo,
1417    PartialEq,
1418    Clone,
1419    Constructor,
1420    Eq,
1421    DecodeWithMemTracking,
1422    Copy,
1423)]
1424#[scale_info(skip_type_params(T, I))]
1425pub(crate) struct AssetDelta<T: Config<I>, I: 'static = ()> {
1426    /// The amount of assets to be taken (decreased or burned) to the system or account.
1427    pub deposit: AssetOf<T, I>,
1428    /// The amount of assets to be given (increased or minted) to the system or account.
1429    pub withdraw: AssetOf<T, I>,
1430}
1431
1432// ===============================================================================
1433// ```````````````````````````` EXTRINSIC PARAMETERS `````````````````````````````
1434// ===============================================================================
1435
1436/// Choose a valid digest model for [`ChooseDigest::digest_model`] safe construction.
1437#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DecodeWithMemTracking, Clone, Debug, Copy)]
1438pub enum ChooseDigest {
1439    Direct,
1440    Index,
1441    Pool,
1442}
1443
1444impl ChooseDigest {
1445    pub fn digest_model<T: Config<I>, I: 'static>(&self, digest: Digest<T>) -> DigestVariant<T, I> {
1446        match self {
1447            ChooseDigest::Direct => DigestVariant::Direct(digest),
1448            ChooseDigest::Index => DigestVariant::Index(digest),
1449            ChooseDigest::Pool => DigestVariant::Pool(digest),
1450        }
1451    }
1452}
1453
1454/// Represents a generic digest variant in the commitment system.
1455///
1456/// Note: Usage of PhantomData variant in runtime will result in `panic!`
1457/// in debug-builds. Use [`ChooseDigest::digest_model`] for safe constructions.
1458///
1459/// This enum distinguishes between the different types of digests that
1460/// can exist in the pallet. It allows functions and events to operate
1461/// generically over digests without losing context about their source or type.
1462///
1463/// ### Usage:
1464/// - Used in events like `CommitPlaced`, `DigestInfo`, etc., to convey
1465///   which type of digest is being referred to.
1466/// - Enables trait and function implementations to handle multiple digest types
1467///   in a type-safe and g||eneric way.
1468#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DecodeWithMemTracking)]
1469#[scale_info(skip_type_params(T, I))]
1470pub enum DigestVariant<T: Config<I>, I: 'static = ()> {
1471    /// A digest that refers directly to a commitment.
1472    Direct(Digest<T>),
1473    /// A digest that represents an index of multiple entries.
1474    Index(Digest<T>),
1475    /// A digest that represents a managed pool of slots.
1476    Pool(Digest<T>),
1477    /// Phantom variant to ensure the instance parameter `I` is used.
1478    /// This variant is never constructed.
1479    #[codec(skip)]
1480    __Ignore(PhantomData<I>),
1481}
1482
1483/// Wrapper type for [`Precision`] used in extrinsics.
1484///
1485/// This enum defines how precisely a funding operation should be executed,
1486/// particularly in scenarios where exact amounts may not be achievable due
1487/// to rounding, liquidity, or distribution constraints.
1488///
1489/// ## Variants
1490/// - `Exact`: Requires the operation to be executed with exact precision.
1491///   Fails if the exact value cannot be honored.
1492/// - `BestEffort`: Allows approximate execution, where the system will
1493///   attempt to fulfill the request as closely as possible.
1494///
1495/// ## Notes
1496/// - This type exists to decouple the extrinsic API from the internal
1497///   [`Precision`] type.
1498/// - It is converted into [`Precision`] internally before execution.
1499#[derive(
1500    Encode,
1501    Decode,
1502    DecodeWithMemTracking,
1503    RuntimeDebug,
1504    Clone,
1505    PartialEq,
1506    Eq,
1507    MaxEncodedLen,
1508    TypeInfo,
1509)]
1510pub enum PrecisionWrapper {
1511    Exact,
1512    BestEffort,
1513}
1514
1515impl From<PrecisionWrapper> for Precision {
1516    fn from(value: PrecisionWrapper) -> Self {
1517        match value {
1518            PrecisionWrapper::Exact => Precision::Exact,
1519            PrecisionWrapper::BestEffort => Precision::BestEffort,
1520        }
1521    }
1522}
1523
1524// ===============================================================================
1525// ````````````````````````````````` DERIVE IMPLS ````````````````````````````````
1526// ===============================================================================
1527
1528impl<T: Config<I>, I: 'static> Default for DigestInfo<T, I> {
1529    /// Creates an empty `DigestInfo` with no variant balances.
1530    /// Variant slots are initialized lazily and filled safely on demand.
1531    fn default() -> Self {
1532        Self(Default::default())
1533    }
1534}
1535
1536impl<T: Config<I>, I: 'static> PartialEq for Commits<T, I> {
1537    fn eq(&self, other: &Self) -> bool {
1538        self.0 == other.0
1539    }
1540}
1541
1542impl<T: Config<I>, I: 'static> Eq for Commits<T, I> {}
1543
1544impl<T: Config<I>, I: 'static> PartialEq for EntryInfo<T, I> {
1545    fn eq(&self, other: &Self) -> bool {
1546        self.digest == other.digest && self.shares == other.shares && self.variant == other.variant
1547    }
1548}
1549
1550impl<T: Config<I>, I: 'static> Eq for EntryInfo<T, I> {}
1551
1552impl<T: Config<I>, I: 'static> core::fmt::Debug for EntryInfo<T, I> {
1553    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1554        f.debug_struct("EntryInfo")
1555            .field("digest", &self.digest)
1556            .field("shares", &self.shares)
1557            .field("variant", &self.variant)
1558            .finish()
1559    }
1560}
1561
1562impl<T: Config<I>, I: 'static> PartialEq for Entries<T, I> {
1563    fn eq(&self, other: &Self) -> bool {
1564        self.0 == other.0
1565    }
1566}
1567
1568impl<T: Config<I>, I: 'static> Eq for Entries<T, I> {}
1569
1570impl<T: Config<I>, I: 'static> core::fmt::Debug for Entries<T, I> {
1571    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1572        f.debug_tuple("Entries").field(&self.0).finish()
1573    }
1574}
1575
1576impl<T: Config<I>, I: 'static> Clone for Entries<T, I> {
1577    fn clone(&self) -> Self {
1578        Self(self.0.clone())
1579    }
1580}
1581
1582impl<T: Config<I>, I: 'static> core::fmt::Debug for IndexInfo<T, I> {
1583    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1584        f.debug_struct("IndexInfo")
1585            .field("principal", &self.principal)
1586            .field("capital", &self.capital)
1587            .field("entries", &self.entries)
1588            .finish()
1589    }
1590}
1591
1592impl<T: Config<I>, I: 'static> PartialEq for IndexInfo<T, I> {
1593    fn eq(&self, other: &Self) -> bool {
1594        self.principal == other.principal
1595            && self.capital == other.capital
1596            && self.entries == other.entries
1597    }
1598}
1599
1600impl<T: Config<I>, I: 'static> Eq for IndexInfo<T, I> {}
1601
1602impl<T: Config<I>, I: 'static> Clone for IndexInfo<T, I> {
1603    fn clone(&self) -> Self {
1604        Self {
1605            principal: self.principal,
1606            capital: self.capital,
1607            entries: self.entries.clone(),
1608        }
1609    }
1610}
1611
1612impl<T: Config<I>, I: 'static> PartialEq for SlotInfo<T, I> {
1613    fn eq(&self, other: &Self) -> bool {
1614        self.digest == other.digest
1615            && self.shares == other.shares
1616            && self.commit == other.commit
1617            && self.variant == other.variant
1618    }
1619}
1620
1621impl<T: Config<I>, I: 'static> Eq for SlotInfo<T, I> {}
1622
1623impl<T: Config<I>, I: 'static> core::fmt::Debug for SlotInfo<T, I> {
1624    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1625        f.debug_struct("SlotInfo")
1626            .field("digest", &self.digest)
1627            .field("shares", &self.shares)
1628            .field("commit", &self.commit)
1629            .field("variant", &self.variant)
1630            .finish()
1631    }
1632}
1633
1634impl<T: Config<I>, I: 'static> Clone for SlotInfo<T, I> {
1635    fn clone(&self) -> Self {
1636        Self {
1637            digest: self.digest.clone(),
1638            shares: self.shares,
1639            commit: self.commit.clone(),
1640            variant: self.variant.clone(),
1641        }
1642    }
1643}
1644
1645impl<T: Config<I>, I: 'static> PartialEq for Slots<T, I> {
1646    fn eq(&self, other: &Self) -> bool {
1647        self.0 == other.0
1648    }
1649}
1650
1651impl<T: Config<I>, I: 'static> core::fmt::Debug for Slots<T, I> {
1652    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1653        f.debug_tuple("Slots").field(&self.0.as_slice()).finish()
1654    }
1655}
1656
1657impl<T: Config<I>, I: 'static> Eq for Slots<T, I> {}
1658
1659impl<T: Config<I>, I: 'static> Clone for Slots<T, I> {
1660    fn clone(&self) -> Self {
1661        Self(self.0.clone())
1662    }
1663}
1664
1665impl<T: Config<I>, I: 'static> PartialEq for PoolInfo<T, I> {
1666    fn eq(&self, other: &Self) -> bool {
1667        self.balance_of == other.balance_of
1668            && self.capital == other.capital
1669            && self.commission == other.commission
1670            && self.slots == other.slots
1671    }
1672}
1673
1674impl<T: Config<I>, I: 'static> Eq for PoolInfo<T, I> {}
1675
1676impl<T: Config<I>, I: 'static> Clone for PoolInfo<T, I> {
1677    fn clone(&self) -> Self {
1678        Self {
1679            balance_of: self.balance_of.clone(),
1680            capital: self.capital,
1681            commission: self.commission,
1682            slots: self.slots.clone(),
1683        }
1684    }
1685}
1686
1687impl<T: Config<I>, I: 'static> core::fmt::Debug for PoolInfo<T, I> {
1688    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1689        f.debug_struct("PoolInfo")
1690            .field("balance_of", &self.balance_of)
1691            .field("capital", &self.capital)
1692            .field("commission", &self.commission)
1693            .field("slots", &self.slots)
1694            .finish()
1695    }
1696}
1697
1698impl<T: Config<I>, I: 'static> Clone for IndexOfReason<T, I> {
1699    fn clone(&self) -> Self {
1700        Self {
1701            reason: self.reason,
1702            index: self.index.clone(),
1703        }
1704    }
1705}
1706
1707impl<T: Config<I>, I: 'static> Clone for PoolOfReason<T, I> {
1708    fn clone(&self) -> Self {
1709        Self {
1710            reason: self.reason,
1711            pool: self.pool.clone(),
1712        }
1713    }
1714}
1715
1716impl<T: Config<I>, I: 'static> Clone for DigestVariant<T, I> {
1717    fn clone(&self) -> Self {
1718        match self {
1719            DigestVariant::Direct(d) => DigestVariant::Direct(d.clone()),
1720            DigestVariant::Index(d) => DigestVariant::Index(d.clone()),
1721            DigestVariant::Pool(d) => DigestVariant::Pool(d.clone()),
1722            DigestVariant::__Ignore(_) => {
1723                debug_assert!(false, "digest variant phantom variant accessed");
1724                DigestVariant::__Ignore(PhantomData)
1725            }
1726        }
1727    }
1728}
1729
1730impl<T: Config<I>, I: 'static> PartialEq for DigestVariant<T, I> {
1731    fn eq(&self, other: &Self) -> bool {
1732        match (self, other) {
1733            (DigestVariant::Direct(a), DigestVariant::Direct(b)) => a == b,
1734            (DigestVariant::Index(a), DigestVariant::Index(b)) => a == b,
1735            (DigestVariant::Pool(a), DigestVariant::Pool(b)) => a == b,
1736            (DigestVariant::__Ignore(_), DigestVariant::__Ignore(_)) => {
1737                debug_assert!(false, "digest variant phantom variant accessed");
1738                true
1739            }
1740            (DigestVariant::__Ignore(_), _) => {
1741                debug_assert!(false, "digest variant phantom variant accessed");
1742                false
1743            }
1744            (_, DigestVariant::__Ignore(_)) => {
1745                debug_assert!(false, "digest variant phantom variant accessed");
1746                false
1747            }
1748            _ => false,
1749        }
1750    }
1751}
1752
1753impl<T: Config<I>, I: 'static> Eq for DigestVariant<T, I> {}
1754
1755impl<T: Config<I>, I: 'static> Debug for DigestVariant<T, I> {
1756    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1757        match self {
1758            DigestVariant::Direct(d) => write!(f, "Direct({:?})", d),
1759            DigestVariant::Index(d) => write!(f, "Index({:?})", d),
1760            DigestVariant::Pool(d) => write!(f, "Pool({:?})", d),
1761            DigestVariant::__Ignore(_) => {
1762                debug_assert!(false, "digest variant phantom variant accessed");
1763                write!(f, "Invalid Digest Variant")
1764            }
1765        }
1766    }
1767}
1768
1769// ===============================================================================
1770// `````````````````````````````````` UNIT TESTS `````````````````````````````````
1771// ===============================================================================
1772
1773#[cfg(test)]
1774mod tests {
1775
1776    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1777    // ``````````````````````````````````` IMPORTS ```````````````````````````````````
1778    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1779
1780    // --- Local crate imports ---
1781    use crate::{
1782        balance::{balance_total, mint},
1783        mock::*,
1784    };
1785
1786    // --- FRAME Suite ---
1787    use frame_suite::{
1788        commitment::*,
1789        misc::{Directive, PositionIndex},
1790    };
1791
1792    // --- FRAME Support ---
1793    use frame_support::{
1794        assert_err, assert_ok,
1795        traits::tokens::{Fortitude, Precision},
1796    };
1797
1798    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1799    // ``````````````````````````````````` DIGEST INFO ```````````````````````````````
1800    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1801
1802    #[test]
1803    fn digest_info_balances() {
1804        commit_test_ext().execute_with(|| {
1805            set_default_user_balance_and_standard_hold(ALICE).unwrap();
1806            set_default_user_balance_and_standard_hold(ALAN).unwrap();
1807            set_default_user_balance_and_standard_hold(BOB).unwrap();
1808
1809            let alice_position = Position::default();
1810            Pallet::place_commit(
1811                &ALICE,
1812                &ESCROW,
1813                &CONTRACT_FREELANCE,
1814                STANDARD_COMMIT,
1815                &Directive::new(Precision::BestEffort, Fortitude::Force),
1816            )
1817            .unwrap();
1818
1819            let alan_position = Position::position_of(1).unwrap();
1820            Pallet::place_commit_of_variant(
1821                &ALAN,
1822                &ESCROW,
1823                &CONTRACT_FREELANCE,
1824                LARGE_COMMIT,
1825                &alan_position,
1826                &Directive::new(Precision::BestEffort, Fortitude::Force),
1827            )
1828            .unwrap();
1829
1830            let bob_position = Position::position_of(2).unwrap();
1831            Pallet::place_commit_of_variant(
1832                &BOB,
1833                &GOVERNANCE,
1834                &PROPOSAL_TREASURY_SPEND,
1835                LARGE_COMMIT,
1836                &bob_position,
1837                &Directive::new(Precision::BestEffort, Fortitude::Force),
1838            )
1839            .unwrap();
1840
1841            let digest_info = DigestMap::get((ESCROW, CONTRACT_FREELANCE)).unwrap();
1842            let balances = digest_info.balances().unwrap();
1843            // ALICE balances at index 0 (Position, LazyBalanceOf)
1844            let alice_balances = balances.get(0).unwrap();
1845            assert_eq!(alice_balances.0, alice_position);
1846            let alice_bal = digest_info.get_balance(&alice_position).unwrap();
1847            assert_eq!(alice_balances.1, *alice_bal);
1848            // ALAN balances at index 1 (Position, LazyBalanceOf)
1849            let alan_balances = balances.get(1).unwrap();
1850            assert_eq!(alan_balances.0, alan_position);
1851            let alan_bal = digest_info.get_balance(&alan_position).unwrap();
1852            assert_eq!(alan_balances.1, *alan_bal);
1853
1854            // BOB's digest info
1855            let digest_info = DigestMap::get((GOVERNANCE, PROPOSAL_TREASURY_SPEND)).unwrap();
1856            let balances = digest_info.balances().unwrap();
1857            // BOB's balances at index 0 (Position, LazyBalanceOf)
1858            let bob_balances = balances.get(0).unwrap();
1859            assert_eq!(bob_balances.0, bob_position);
1860            let bob_bal = digest_info.get_balance(&bob_position).unwrap();
1861            assert_eq!(bob_balances.1, *bob_bal);
1862        })
1863    }
1864
1865    #[test]
1866    fn digest_info_get_balance() {
1867        commit_test_ext().execute_with(|| {
1868            set_default_user_balance_and_standard_hold(ALICE).unwrap();
1869
1870            let alice_position = Position::default();
1871            Pallet::place_commit(
1872                &ALICE,
1873                &ESCROW,
1874                &CONTRACT_FREELANCE,
1875                STANDARD_COMMIT,
1876                &Directive::new(Precision::BestEffort, Fortitude::Force),
1877            )
1878            .unwrap();
1879
1880            let digest_info = DigestMap::get((ESCROW, CONTRACT_FREELANCE)).unwrap();
1881            let alice_balance = digest_info.get_balance(&alice_position).unwrap();
1882
1883            let alice_bal_total =
1884                balance_total(alice_balance, &alice_position, &CONTRACT_FREELANCE).unwrap();
1885            assert_eq!(alice_bal_total, STANDARD_COMMIT);
1886        })
1887    }
1888
1889    #[test]
1890    fn digest_info_mut_balance() {
1891        commit_test_ext().execute_with(|| {
1892            set_default_user_balance_and_standard_hold(ALICE).unwrap();
1893
1894            let alice_position = Position::default();
1895            Pallet::place_commit(
1896                &ALICE,
1897                &ESCROW,
1898                &CONTRACT_FREELANCE,
1899                STANDARD_COMMIT,
1900                &Directive::new(Precision::BestEffort, Fortitude::Force),
1901            )
1902            .unwrap();
1903
1904            let mut digest_info = DigestMap::get((ESCROW, CONTRACT_FREELANCE)).unwrap();
1905            let alice_mut_balance = digest_info.mut_balance(&alice_position).unwrap();
1906
1907            let mint_val = 125;
1908            mint(
1909                alice_mut_balance,
1910                &alice_position,
1911                &CONTRACT_FREELANCE,
1912                &mint_val,
1913                &Directive::new(Precision::Exact, Fortitude::Force),
1914            )
1915            .unwrap();
1916            let alice_bal_total =
1917                balance_total(alice_mut_balance, &alice_position, &CONTRACT_FREELANCE).unwrap();
1918            assert_ne!(alice_bal_total, STANDARD_COMMIT);
1919
1920            assert_eq!(alice_bal_total, STANDARD_COMMIT + mint_val);
1921        })
1922    }
1923
1924    #[test]
1925    fn digest_info_reveal() {
1926        commit_test_ext().execute_with(|| {
1927            set_default_user_balance_and_standard_hold(ALICE).unwrap();
1928            set_default_user_balance_and_standard_hold(ALAN).unwrap();
1929            set_default_user_balance_and_standard_hold(BOB).unwrap();
1930
1931            let alice_position = Position::default();
1932            Pallet::place_commit(
1933                &ALICE,
1934                &ESCROW,
1935                &CONTRACT_FREELANCE,
1936                STANDARD_COMMIT,
1937                &Directive::new(Precision::BestEffort, Fortitude::Force),
1938            )
1939            .unwrap();
1940
1941            let alan_position = Position::position_of(1).unwrap();
1942            Pallet::place_commit_of_variant(
1943                &ALAN,
1944                &ESCROW,
1945                &CONTRACT_FREELANCE,
1946                LARGE_COMMIT,
1947                &alan_position,
1948                &Directive::new(Precision::BestEffort, Fortitude::Force),
1949            )
1950            .unwrap();
1951
1952            let bob_position = Position::position_of(2).unwrap();
1953            Pallet::place_commit_of_variant(
1954                &BOB,
1955                &GOVERNANCE,
1956                &PROPOSAL_TREASURY_SPEND,
1957                LARGE_COMMIT,
1958                &bob_position,
1959                &Directive::new(Precision::BestEffort, Fortitude::Force),
1960            )
1961            .unwrap();
1962
1963            let digest_info = DigestMap::get((ESCROW, CONTRACT_FREELANCE)).unwrap();
1964            let reveal_balances = digest_info.reveal();
1965
1966            assert_eq!(reveal_balances.len(), 2);
1967
1968            let alice_total_bal =
1969                balance_total(&reveal_balances[0], &alice_position, &CONTRACT_FREELANCE).unwrap();
1970            assert_eq!(alice_total_bal, STANDARD_COMMIT,);
1971
1972            let alan_total_bal =
1973                balance_total(&reveal_balances[1], &alan_position, &CONTRACT_FREELANCE).unwrap();
1974            assert_eq!(alan_total_bal, LARGE_COMMIT,);
1975
1976            let digest_info = DigestMap::get((GOVERNANCE, PROPOSAL_TREASURY_SPEND)).unwrap();
1977            let reveal_balances = digest_info.reveal();
1978
1979            assert_eq!(reveal_balances.len(), 3);
1980            assert_eq!(
1981                balance_total(&reveal_balances[0], &bob_position, &PROPOSAL_TREASURY_SPEND),
1982                Ok(0)
1983            );
1984            assert_eq!(
1985                balance_total(&reveal_balances[1], &bob_position, &PROPOSAL_TREASURY_SPEND),
1986                Ok(0)
1987            );
1988
1989            let bob_total_bal =
1990                balance_total(&reveal_balances[2], &bob_position, &PROPOSAL_TREASURY_SPEND)
1991                    .unwrap();
1992            assert_eq!(bob_total_bal, LARGE_COMMIT,);
1993        })
1994    }
1995
1996    #[test]
1997    fn digest_info_init_balance() {
1998        commit_test_ext().execute_with(|| {
1999            let mut digest_info = DigestInfo::default();
2000
2001            let pos0 = Position::position_of(0).unwrap();
2002            let pos1 = Position::position_of(1).unwrap();
2003            let pos2 = Position::position_of(2).unwrap();
2004
2005            // before init -> nothing exists
2006            assert!(digest_info.get_balance(&pos0).is_none());
2007            assert!(digest_info.get_balance(&pos1).is_none());
2008            assert!(digest_info.get_balance(&pos2).is_none());
2009
2010            assert_ok!(digest_info.init_balance(&pos1));
2011
2012            // after init -> all slots up to index must exist
2013            assert!(digest_info.get_balance(&pos0).is_some());
2014            assert!(digest_info.get_balance(&pos1).is_some());
2015            assert!(digest_info.get_balance(&pos2).is_none());
2016
2017            assert_eq!(digest_info.reveal().len(), 2);
2018
2019            assert_ok!(digest_info.init_balance(&pos2));
2020            assert!(digest_info.get_balance(&pos2).is_some());
2021
2022            assert_eq!(digest_info.reveal().len(), 3);
2023        })
2024    }
2025
2026    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2027    // ``````````````````````````````````` COMMITS ```````````````````````````````````
2028    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2029
2030    #[test]
2031    fn commits_new_success() {
2032        commit_test_ext().execute_with(|| {
2033            let commit_ins = CommitInstance::default();
2034
2035            let commits = Commits::new(commit_ins.clone()).unwrap();
2036            assert_eq!(commits.0.len(), 1);
2037
2038            let init_commit = commits.0.get(0).unwrap();
2039            assert_eq!(init_commit.clone(), commit_ins);
2040        })
2041    }
2042
2043    #[test]
2044    fn commits_success() {
2045        commit_test_ext().execute_with(|| {
2046            let derive_bal_a = CommitInstance::default();
2047            let mut commits = Commits::new(derive_bal_a.clone()).unwrap();
2048
2049            let commits_vec = commits.commits();
2050            assert_eq!(commits_vec, vec![derive_bal_a.clone()]);
2051
2052            let derive_bal_b = CommitInstance::default();
2053            let derive_bal_c = CommitInstance::default();
2054            commits.add_commit(derive_bal_b.clone()).unwrap();
2055            commits.add_commit(derive_bal_c.clone()).unwrap();
2056
2057            let commits_vec = commits.commits();
2058            assert_eq!(commits_vec, vec![derive_bal_a, derive_bal_b, derive_bal_c]);
2059        })
2060    }
2061
2062    #[test]
2063    fn add_commit_success() {
2064        commit_test_ext().execute_with(|| {
2065            let derive_bal_a = CommitInstance::default();
2066            let mut commits = Commits::new(derive_bal_a.clone()).unwrap();
2067
2068            let commits_vec = commits.commits();
2069            assert_eq!(commits_vec, vec![derive_bal_a.clone()]);
2070
2071            let derive_bal_b = CommitInstance::default();
2072            let derive_bal_c = CommitInstance::default();
2073            commits.add_commit(derive_bal_b.clone()).unwrap();
2074            commits.add_commit(derive_bal_c.clone()).unwrap();
2075
2076            let commits_vec = commits.commits();
2077            assert_eq!(commits_vec, vec![derive_bal_a, derive_bal_b, derive_bal_c]);
2078
2079            let derive_bal_d = CommitInstance::default();
2080            assert_err!(commits.add_commit(derive_bal_d), Error::MaxCommitsReached);
2081        })
2082    }
2083
2084    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2085    // ``````````````````````````````````` COMMIT INFO ```````````````````````````````````
2086    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2087
2088    #[test]
2089    fn commit_info_new_success() {
2090        commit_test_ext().execute_with(|| {
2091            let instance = CommitInstance::default();
2092            let variant = Position::position_of(0).unwrap();
2093            let commit_info = CommitInfo::new(VALIDATOR_ALPHA, instance.clone(), variant).unwrap();
2094
2095            assert_eq!(commit_info.commits().len(), 1);
2096            assert_eq!(commit_info.commits(), vec![instance]);
2097            assert_eq!(commit_info.digest(), VALIDATOR_ALPHA);
2098            assert_eq!(commit_info.variant(), variant);
2099        })
2100    }
2101
2102    #[test]
2103    fn commit_info_add_commit_success() {
2104        commit_test_ext().execute_with(|| {
2105            let instance_a = CommitInstance::default();
2106            let variant = Position::position_of(0).unwrap();
2107            let mut commit_info =
2108                CommitInfo::new(VALIDATOR_ALPHA, instance_a.clone(), variant).unwrap();
2109
2110            assert_eq!(commit_info.commits().len(), 1);
2111            assert_eq!(commit_info.commits(), vec![instance_a.clone()]);
2112
2113            let instance_b = CommitInstance::default();
2114            let instance_c = CommitInstance::default();
2115            commit_info.add_commit(instance_b.clone()).unwrap();
2116            commit_info.add_commit(instance_c.clone()).unwrap();
2117
2118            assert_eq!(commit_info.commits().len(), 3);
2119            assert_eq!(
2120                commit_info.commits(),
2121                vec![instance_a, instance_b, instance_c]
2122            );
2123        })
2124    }
2125
2126    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2127    // ``````````````````````````````````` ENTRY INFO ```````````````````````````````````
2128    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2129
2130    #[test]
2131    fn entry_info_eq_true() {
2132        commit_test_ext().execute_with(|| {
2133            let variant = Position::position_of(0).unwrap();
2134            let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2135            let entry_info_b = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2136
2137            assert!(entry_info_a.eq(&entry_info_b));
2138        })
2139    }
2140
2141    #[test]
2142    fn entry_info_eq_false() {
2143        commit_test_ext().execute_with(|| {
2144            let variant = Position::position_of(0).unwrap();
2145            let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2146            let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 100, variant).unwrap();
2147
2148            assert!(!entry_info_a.eq(&entry_info_b));
2149        })
2150    }
2151
2152    #[test]
2153    fn entry_info_new_success() {
2154        commit_test_ext().execute_with(|| {
2155            let variant = Position::position_of(0).unwrap();
2156            let entry_info = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2157
2158            assert_eq!(entry_info.digest(), VALIDATOR_ALPHA);
2159            assert_eq!(entry_info.shares, 100);
2160            assert_eq!(entry_info.variant(), variant);
2161        })
2162    }
2163
2164    #[test]
2165    fn entry_info_new_err_share_cannot_be_zero() {
2166        let variant = Position::position_of(0).unwrap();
2167        assert_err!(
2168            EntryInfo::new(VALIDATOR_ALPHA, 0, variant),
2169            Error::ShareCannotBeZero
2170        );
2171    }
2172
2173    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2174    // ``````````````````````````````````` ENTRIES ```````````````````````````````````
2175    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2176
2177    #[test]
2178    fn entries_eq_true() {
2179        let variant = Position::position_of(0).unwrap();
2180        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2181        let entry_info_b = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2182
2183        let entries_a = Entries::new(vec![entry_info_a]).unwrap();
2184        let entries_b = Entries::new(vec![entry_info_b]).unwrap();
2185
2186        assert!(entries_a.eq(&entries_b));
2187    }
2188
2189    #[test]
2190    fn entries_eq_false() {
2191        let variant = Position::position_of(0).unwrap();
2192        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2193        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2194
2195        let entries_a = Entries::new(vec![entry_info_a]).unwrap();
2196        let entries_b = Entries::new(vec![entry_info_b]).unwrap();
2197
2198        assert!(!entries_a.eq(&entries_b));
2199    }
2200
2201    #[test]
2202    fn entries_new_success() {
2203        let variant = Position::position_of(0).unwrap();
2204        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2205        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2206        let entry_info_c = EntryInfo::new(VALIDATOR_GAMMA, 75, variant).unwrap();
2207
2208        let entries = Entries::new(vec![
2209            entry_info_a.clone(),
2210            entry_info_b.clone(),
2211            entry_info_c.clone(),
2212        ])
2213        .unwrap();
2214
2215        assert_eq!(entries.0.get(0), Some(&entry_info_a));
2216        assert_eq!(entries.0.get(1), Some(&entry_info_b));
2217        assert_eq!(entries.0.get(2), Some(&entry_info_c));
2218        assert_eq!(entries.0.get(3), None);
2219    }
2220
2221    #[test]
2222    fn entries_new_err_duplicate_entry() {
2223        let variant = Position::position_of(0).unwrap();
2224        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2225        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2226        let _entry_info_c = EntryInfo::new(VALIDATOR_GAMMA, 75, variant).unwrap();
2227
2228        let entries = Entries::new(vec![
2229            entry_info_a.clone(),
2230            entry_info_b.clone(),
2231            entry_info_b.clone(),
2232        ]);
2233
2234        assert!(entries.is_err());
2235        assert_err!(entries, Error::DuplicateEntry);
2236    }
2237
2238    #[test]
2239    fn entries_new_err_max_entries_reached() {
2240        let variant = Position::position_of(0).unwrap();
2241        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2242        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2243        let entry_info_c = EntryInfo::new(VALIDATOR_GAMMA, 75, variant).unwrap();
2244        let entry_info_d = EntryInfo::new(VALIDATOR_DELTA, 125, variant).unwrap();
2245
2246        let entries = Entries::new(vec![
2247            entry_info_a.clone(),
2248            entry_info_b.clone(),
2249            entry_info_c.clone(),
2250            entry_info_d.clone(),
2251        ]);
2252
2253        assert!(entries.is_err());
2254        assert_err!(entries, Error::MaxEntriesReached);
2255    }
2256
2257    #[test]
2258    fn entries_success() {
2259        let variant = Position::position_of(0).unwrap();
2260        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2261        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2262        let entry_info_c = EntryInfo::new(VALIDATOR_GAMMA, 75, variant).unwrap();
2263
2264        let entries = Entries::new(vec![
2265            entry_info_a.clone(),
2266            entry_info_b.clone(),
2267            entry_info_c.clone(),
2268        ])
2269        .unwrap();
2270
2271        let actual_entries = entries.entries();
2272        let expected_entries = vec![entry_info_a, entry_info_b, entry_info_c];
2273        assert_eq!(actual_entries, expected_entries);
2274    }
2275
2276    #[test]
2277    fn add_entry_success() {
2278        let variant = Position::position_of(0).unwrap();
2279        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2280
2281        let mut entries = Entries::new(vec![entry_info_a.clone()]).unwrap();
2282
2283        let actual_entries = entries.entries();
2284        let expected_entries = vec![entry_info_a.clone()];
2285        assert_eq!(actual_entries, expected_entries);
2286
2287        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2288        let entry_info_c = EntryInfo::new(VALIDATOR_GAMMA, 75, variant).unwrap();
2289
2290        entries.add_entry(entry_info_b.clone()).unwrap();
2291
2292        let actual_entries = entries.entries();
2293        let expected_entries = vec![entry_info_a.clone(), entry_info_b.clone()];
2294        assert_eq!(actual_entries, expected_entries);
2295
2296        entries.add_entry(entry_info_c.clone()).unwrap();
2297
2298        let actual_entries = entries.entries();
2299        let expected_entries = vec![entry_info_a, entry_info_b, entry_info_c];
2300        assert_eq!(actual_entries, expected_entries);
2301    }
2302
2303    #[test]
2304    fn add_entry_err_duplicate_entry() {
2305        let variant = Position::position_of(0).unwrap();
2306        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2307
2308        let mut entries = Entries::new(vec![entry_info_a.clone()]).unwrap();
2309
2310        assert_err!(entries.add_entry(entry_info_a), Error::DuplicateEntry);
2311    }
2312
2313    #[test]
2314    fn add_entry_err_max_entries_reached() {
2315        let variant = Position::position_of(0).unwrap();
2316        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2317        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2318        let entry_info_c = EntryInfo::new(VALIDATOR_GAMMA, 75, variant).unwrap();
2319
2320        let mut entries = Entries::new(vec![
2321            entry_info_a.clone(),
2322            entry_info_b.clone(),
2323            entry_info_c.clone(),
2324        ])
2325        .unwrap();
2326
2327        let entry_info_d = EntryInfo::new(VALIDATOR_DELTA, 125, variant).unwrap();
2328
2329        assert_err!(entries.add_entry(entry_info_d), Error::MaxEntriesReached);
2330    }
2331
2332    #[test]
2333    fn remove_entry_success() {
2334        let variant = Position::position_of(0).unwrap();
2335        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2336        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2337        let entry_info_c = EntryInfo::new(VALIDATOR_GAMMA, 75, variant).unwrap();
2338
2339        let mut entries = Entries::new(vec![
2340            entry_info_a.clone(),
2341            entry_info_b.clone(),
2342            entry_info_c.clone(),
2343        ])
2344        .unwrap();
2345
2346        let actual_entries = entries.entries();
2347        let expected_entries = vec![
2348            entry_info_a.clone(),
2349            entry_info_b.clone(),
2350            entry_info_c.clone(),
2351        ];
2352        assert_eq!(actual_entries, expected_entries);
2353
2354        entries.remove_entry(&VALIDATOR_ALPHA).unwrap();
2355
2356        let actual_entries = entries.entries();
2357        let expected_entries = vec![entry_info_b.clone(), entry_info_c.clone()];
2358        assert_eq!(actual_entries, expected_entries);
2359
2360        entries.remove_entry(&VALIDATOR_GAMMA).unwrap();
2361
2362        let actual_entries = entries.entries();
2363        let expected_entries = vec![entry_info_b.clone()];
2364        assert_eq!(actual_entries, expected_entries);
2365    }
2366
2367    #[test]
2368    fn remove_entry_err_entry_of_index_not_found() {
2369        let variant = Position::position_of(0).unwrap();
2370        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2371        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2372
2373        let mut entries = Entries::new(vec![entry_info_a.clone(), entry_info_b.clone()]).unwrap();
2374
2375        assert_err!(
2376            entries.remove_entry(&VALIDATOR_GAMMA),
2377            Error::EntryOfIndexNotFound
2378        );
2379    }
2380
2381    #[test]
2382    fn remove_entry_err_empty_entries_not_allowed() {
2383        let variant = Position::position_of(0).unwrap();
2384        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2385
2386        let mut entries = Entries::new(vec![entry_info_a.clone()]).unwrap();
2387
2388        assert_err!(
2389            entries.remove_entry(&VALIDATOR_ALPHA),
2390            Error::EmptyEntriesNotAllowed
2391        );
2392    }
2393
2394    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2395    // `````````````````````````````````` INDEX INFO `````````````````````````````````
2396    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2397
2398    #[test]
2399    fn index_info_eq_true() {
2400        let variant = Position::position_of(0).unwrap();
2401        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2402        let entry_info_b = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2403
2404        let mut entries_a = Entries::new(vec![entry_info_a]).unwrap();
2405        let mut entries_b = Entries::new(vec![entry_info_b]).unwrap();
2406
2407        let index_info_a = IndexInfo::new(&mut entries_a).unwrap();
2408        let index_info_b = IndexInfo::new(&mut entries_b).unwrap();
2409
2410        assert!(index_info_a.eq(&index_info_b));
2411    }
2412
2413    #[test]
2414    fn index_info_eq_false() {
2415        let variant = Position::position_of(0).unwrap();
2416        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2417        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2418
2419        let mut entries_a = Entries::new(vec![entry_info_a]).unwrap();
2420        let mut entries_b = Entries::new(vec![entry_info_b]).unwrap();
2421
2422        let index_info_a = IndexInfo::new(&mut entries_a).unwrap();
2423        let index_info_b = IndexInfo::new(&mut entries_b).unwrap();
2424
2425        assert!(!index_info_a.eq(&index_info_b));
2426    }
2427
2428    #[test]
2429    fn index_info_new_success() {
2430        let variant = Position::position_of(0).unwrap();
2431        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2432        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2433
2434        let mut entries = Entries::new(vec![entry_info_a.clone(), entry_info_b.clone()]).unwrap();
2435        let index_info = IndexInfo::new(&mut entries).unwrap();
2436
2437        assert_eq!(index_info.capital(), 150);
2438        assert_eq!(index_info.principal(), 0);
2439        assert_eq!(index_info.entries(), vec![entry_info_a, entry_info_b]);
2440    }
2441
2442    #[test]
2443    fn index_info_new_err_capital_overflowed() {
2444        let variant = Position::position_of(0).unwrap();
2445        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, MAX_SHARES, variant).unwrap();
2446        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2447
2448        let mut entries = Entries::new(vec![entry_info_a.clone(), entry_info_b.clone()]).unwrap();
2449        let index_info = IndexInfo::new(&mut entries);
2450        assert!(index_info.is_err());
2451        assert_err!(index_info, Error::CapitalOverflowed);
2452    }
2453
2454    #[test]
2455    fn index_info_reveal_entries() {
2456        let variant = Position::position_of(0).unwrap();
2457        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2458        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2459
2460        let mut entries = Entries::new(vec![entry_info_a.clone(), entry_info_b.clone()]).unwrap();
2461        let index_info = IndexInfo::new(&mut entries).unwrap();
2462
2463        let reveal_entries = index_info.reveal_entries();
2464        assert_eq!(reveal_entries, entries);
2465    }
2466
2467    #[test]
2468    fn entry_exists_success() {
2469        let variant = Position::position_of(0).unwrap();
2470        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2471        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2472
2473        let mut entries = Entries::new(vec![entry_info_a.clone(), entry_info_b.clone()]).unwrap();
2474        let index_info = IndexInfo::new(&mut entries).unwrap();
2475
2476        assert_ok!(index_info.entry_exists(&VALIDATOR_ALPHA));
2477        assert_ok!(index_info.entry_exists(&VALIDATOR_BETA));
2478    }
2479
2480    #[test]
2481    fn entry_exists_err_entry_index_not_found() {
2482        let variant = Position::position_of(0).unwrap();
2483        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2484        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2485
2486        let mut entries = Entries::new(vec![entry_info_a.clone(), entry_info_b.clone()]).unwrap();
2487        let index_info = IndexInfo::new(&mut entries).unwrap();
2488
2489        assert_err!(
2490            index_info.entry_exists(&VALIDATOR_GAMMA),
2491            Error::EntryOfIndexNotFound
2492        );
2493    }
2494
2495    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2496    // `````````````````````````````````` SLOT INFO ``````````````````````````````````
2497    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2498
2499    #[test]
2500    fn slot_info_eq_true() {
2501        let variant = Position::position_of(0).unwrap();
2502
2503        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2504        let entry_info_b = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2505
2506        let slot_info_a: SlotInfo = entry_info_a.into();
2507        let slot_info_b: SlotInfo = entry_info_b.into();
2508
2509        assert!(slot_info_a.eq(&slot_info_b));
2510    }
2511
2512    #[test]
2513    fn slot_info_eq_false() {
2514        let variant = Position::position_of(0).unwrap();
2515
2516        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2517        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2518
2519        let slot_info_a: SlotInfo = entry_info_a.into();
2520        let slot_info_b: SlotInfo = entry_info_b.into();
2521
2522        assert!(!slot_info_a.eq(&slot_info_b));
2523    }
2524
2525    #[test]
2526    fn slot_info_from_entry_success() {
2527        let variant = Position::position_of(0).unwrap();
2528
2529        let entry = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2530
2531        let slot_info: SlotInfo = entry.clone().into();
2532
2533        assert_eq!(slot_info.digest(), VALIDATOR_ALPHA);
2534        assert_eq!(slot_info.shares(), 100);
2535        assert_eq!(slot_info.variant(), variant);
2536
2537        let default_commit = CommitInstance::default();
2538        assert_eq!(slot_info.commit(), default_commit);
2539    }
2540
2541    #[test]
2542    fn slot_info_set_slot_commit() {
2543        commit_test_ext().execute_with(|| {
2544            set_default_user_balance_and_standard_hold(ALICE).unwrap();
2545            let variant = Position::position_of(0).unwrap();
2546            let entry = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2547            let mut slot_info: SlotInfo = entry.clone().into();
2548
2549            let default_commit = CommitInstance::default();
2550            assert_eq!(slot_info.commit(), default_commit);
2551
2552            let alice_position = Position::position_of(1).unwrap();
2553            Pallet::place_commit_of_variant(
2554                &ALICE,
2555                &GOVERNANCE,
2556                &PROPOSAL_TREASURY_SPEND,
2557                LARGE_COMMIT,
2558                &alice_position,
2559                &Directive::new(Precision::BestEffort, Fortitude::Force),
2560            )
2561            .unwrap();
2562
2563            let commit_info = CommitMap::get((ALICE, GOVERNANCE)).unwrap();
2564            let new_instance = commit_info.commits.0.get(0).unwrap();
2565
2566            slot_info.set_slot_commit(new_instance.clone());
2567            assert_ne!(slot_info.commit(), default_commit);
2568            assert_eq!(slot_info.commit(), new_instance.clone());
2569        })
2570    }
2571
2572    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2573    // ```````````````````````````````````` SLOTS ````````````````````````````````````
2574    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2575
2576    #[test]
2577    fn slots_eq_true() {
2578        let variant = Position::position_of(0).unwrap();
2579
2580        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2581        let entry_info_b = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2582
2583        let entries_a = Entries::new(vec![entry_info_a]).unwrap();
2584        let entries_b = Entries::new(vec![entry_info_b]).unwrap();
2585
2586        let slots_a = Slots::try_from(entries_a).unwrap();
2587        let slots_b = Slots::try_from(entries_b).unwrap();
2588
2589        assert!(slots_a.eq(&slots_b));
2590    }
2591
2592    #[test]
2593    fn slots_eq_false() {
2594        let variant = Position::position_of(0).unwrap();
2595
2596        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2597        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2598
2599        let entries_a = Entries::new(vec![entry_info_a]).unwrap();
2600        let entries_b = Entries::new(vec![entry_info_b]).unwrap();
2601
2602        let slots_a = Slots::try_from(entries_a).unwrap();
2603        let slots_b = Slots::try_from(entries_b).unwrap();
2604
2605        assert!(!slots_a.eq(&slots_b));
2606    }
2607
2608    #[test]
2609    fn slots_success() {
2610        let variant = Position::position_of(0).unwrap();
2611
2612        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2613        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2614        let entry_info_c = EntryInfo::new(VALIDATOR_GAMMA, 75, variant).unwrap();
2615
2616        let entries = Entries::new(vec![
2617            entry_info_a.clone(),
2618            entry_info_b.clone(),
2619            entry_info_c.clone(),
2620        ])
2621        .unwrap();
2622
2623        let slots = Slots::try_from(entries).unwrap();
2624        let slot_info_a: SlotInfo = entry_info_a.into();
2625        let slot_info_b: SlotInfo = entry_info_b.into();
2626        let slot_info_c: SlotInfo = entry_info_c.into();
2627
2628        let actual_slots = slots.slots();
2629        let expected_slots = vec![slot_info_a, slot_info_b, slot_info_c];
2630        assert_eq!(actual_slots, expected_slots);
2631    }
2632
2633    #[test]
2634    fn add_slot_success() {
2635        let variant = Position::position_of(0).unwrap();
2636
2637        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2638        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2639        let entry_info_c = EntryInfo::new(VALIDATOR_GAMMA, 75, variant).unwrap();
2640
2641        let entries = Entries::new(vec![entry_info_a.clone(), entry_info_b.clone()]).unwrap();
2642
2643        let mut slots = Slots::try_from(entries).unwrap();
2644        let slot_info_a: SlotInfo = entry_info_a.into();
2645        let slot_info_b: SlotInfo = entry_info_b.into();
2646
2647        let actual_slots = slots.slots();
2648        let expected_slots = vec![slot_info_a.clone(), slot_info_b.clone()];
2649        assert_eq!(actual_slots, expected_slots);
2650
2651        slots.add_slot(entry_info_c.clone()).unwrap();
2652
2653        let slot_info_c: SlotInfo = entry_info_c.into();
2654
2655        let actual_slots = slots.slots();
2656        let expected_slots = vec![slot_info_a, slot_info_b, slot_info_c];
2657        assert_eq!(actual_slots, expected_slots);
2658    }
2659
2660    #[test]
2661    fn add_slot_err_max_slots_reached() {
2662        let variant = Position::position_of(0).unwrap();
2663
2664        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2665        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2666        let entry_info_c = EntryInfo::new(VALIDATOR_GAMMA, 75, variant).unwrap();
2667        let entry_info_d = EntryInfo::new(VALIDATOR_DELTA, 125, variant).unwrap();
2668
2669        let entries = Entries::new(vec![entry_info_a.clone(), entry_info_b.clone()]).unwrap();
2670
2671        let mut slots = Slots::try_from(entries).unwrap();
2672
2673        slots.add_slot(entry_info_c.clone()).unwrap();
2674
2675        assert_err!(slots.add_slot(entry_info_d), Error::MaxSlotsReached);
2676    }
2677
2678    #[test]
2679    fn add_slot_err_duplicate_slot() {
2680        let variant = Position::position_of(0).unwrap();
2681
2682        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2683        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2684        let _entry_info_c = EntryInfo::new(VALIDATOR_GAMMA, 75, variant).unwrap();
2685
2686        let entries = Entries::new(vec![entry_info_a.clone(), entry_info_b.clone()]).unwrap();
2687
2688        let mut slots = Slots::try_from(entries).unwrap();
2689
2690        assert_err!(slots.add_slot(entry_info_b), Error::DuplicateSlot);
2691    }
2692
2693    #[test]
2694    fn remove_slot_success() {
2695        let variant = Position::position_of(0).unwrap();
2696        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2697        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2698        let entry_info_c = EntryInfo::new(VALIDATOR_GAMMA, 75, variant).unwrap();
2699
2700        let entries = Entries::new(vec![
2701            entry_info_a.clone(),
2702            entry_info_b.clone(),
2703            entry_info_c.clone(),
2704        ])
2705        .unwrap();
2706        let mut slots = Slots::try_from(entries).unwrap();
2707        let slot_info_a: SlotInfo = entry_info_a.into();
2708        let slot_info_b: SlotInfo = entry_info_b.into();
2709        let slot_info_c: SlotInfo = entry_info_c.into();
2710
2711        let actual_slots = slots.slots();
2712        let expected_slots = vec![
2713            slot_info_a.clone(),
2714            slot_info_b.clone(),
2715            slot_info_c.clone(),
2716        ];
2717        assert_eq!(actual_slots, expected_slots);
2718
2719        slots.remove_slot(&VALIDATOR_ALPHA).unwrap();
2720
2721        let actual_slots = slots.slots();
2722        let expected_slots = vec![slot_info_b.clone(), slot_info_c.clone()];
2723        assert_eq!(actual_slots, expected_slots);
2724
2725        slots.remove_slot(&VALIDATOR_GAMMA).unwrap();
2726
2727        let actual_slots = slots.slots();
2728        let expected_slots = vec![slot_info_b.clone()];
2729        assert_eq!(actual_slots, expected_slots);
2730    }
2731
2732    #[test]
2733    fn remove_slot_err_slot_of_pool_not_found() {
2734        let variant = Position::position_of(0).unwrap();
2735        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2736        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2737
2738        let entries = Entries::new(vec![entry_info_a.clone(), entry_info_b.clone()]).unwrap();
2739
2740        let mut slots = Slots::try_from(entries).unwrap();
2741
2742        assert_err!(
2743            slots.remove_slot(&VALIDATOR_GAMMA),
2744            Error::SlotOfPoolNotFound
2745        );
2746    }
2747
2748    #[test]
2749    fn remove_slot_err_empty_slots_not_allowed() {
2750        let variant = Position::position_of(0).unwrap();
2751        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2752
2753        let entries = Entries::new(vec![entry_info_a.clone()]).unwrap();
2754
2755        let mut slots = Slots::try_from(entries).unwrap();
2756
2757        assert_err!(
2758            slots.remove_slot(&VALIDATOR_ALPHA),
2759            Error::EmptySlotsNotAllowed
2760        );
2761    }
2762
2763    #[test]
2764    fn set_slot_commit_success() {
2765        commit_test_ext().execute_with(|| {
2766            set_default_user_balance_and_standard_hold(ALICE).unwrap();
2767            let alice_position = Position::position_of(1).unwrap();
2768            Pallet::place_commit_of_variant(
2769                &ALICE,
2770                &GOVERNANCE,
2771                &PROPOSAL_TREASURY_SPEND,
2772                LARGE_COMMIT,
2773                &alice_position,
2774                &Directive::new(Precision::BestEffort, Fortitude::Force),
2775            )
2776            .unwrap();
2777
2778            let commit_info = CommitMap::get((ALICE, GOVERNANCE)).unwrap();
2779            let new_instance = commit_info.commits.0.get(0).unwrap();
2780
2781            let variant = Position::position_of(0).unwrap();
2782
2783            let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2784            let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2785            let entry_info_c = EntryInfo::new(VALIDATOR_GAMMA, 75, variant).unwrap();
2786
2787            let entries = Entries::new(vec![
2788                entry_info_a.clone(),
2789                entry_info_b.clone(),
2790                entry_info_c.clone(),
2791            ])
2792            .unwrap();
2793
2794            let mut slots = Slots::try_from(entries).unwrap();
2795            let slot_info_a: SlotInfo = entry_info_a.into();
2796            let slot_info_b: SlotInfo = entry_info_b.into();
2797            let slot_info_c: SlotInfo = entry_info_c.into();
2798
2799            let actual_slots = slots.slots();
2800            let expected_slots = vec![
2801                slot_info_a.clone(),
2802                slot_info_b.clone(),
2803                slot_info_c.clone(),
2804            ];
2805            assert_eq!(actual_slots, expected_slots);
2806
2807            slots
2808                .set_slot_commit(&VALIDATOR_BETA, new_instance.clone())
2809                .unwrap();
2810
2811            let actual_slots = slots.slots();
2812            let new_slot_b = &actual_slots[1];
2813            assert_ne!(new_slot_b.clone(), slot_info_b.clone());
2814            let expected_slots = vec![slot_info_a.clone(), new_slot_b.clone(), slot_info_c.clone()];
2815            assert_eq!(actual_slots, expected_slots);
2816        })
2817    }
2818
2819    #[test]
2820    fn set_slot_commit_err_slot_of_pool_not_found() {
2821        commit_test_ext().execute_with(|| {
2822            set_default_user_balance_and_standard_hold(ALICE).unwrap();
2823            let alice_position = Position::position_of(1).unwrap();
2824            Pallet::place_commit_of_variant(
2825                &ALICE,
2826                &GOVERNANCE,
2827                &PROPOSAL_TREASURY_SPEND,
2828                LARGE_COMMIT,
2829                &alice_position,
2830                &Directive::new(Precision::BestEffort, Fortitude::Force),
2831            )
2832            .unwrap();
2833
2834            let commit_info = CommitMap::get((ALICE, GOVERNANCE)).unwrap();
2835            let new_instance = commit_info.commits.0.get(0).unwrap();
2836
2837            let variant = Position::position_of(0).unwrap();
2838
2839            let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2840            let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2841
2842            let entries = Entries::new(vec![entry_info_a.clone(), entry_info_b.clone()]).unwrap();
2843
2844            let mut slots = Slots::try_from(entries).unwrap();
2845            let slot_info_a: SlotInfo = entry_info_a.into();
2846            let slot_info_b: SlotInfo = entry_info_b.into();
2847
2848            let actual_slots = slots.slots();
2849            let expected_slots = vec![slot_info_a.clone(), slot_info_b.clone()];
2850            assert_eq!(actual_slots, expected_slots);
2851
2852            let result = slots.set_slot_commit(&VALIDATOR_GAMMA, new_instance.clone());
2853            assert_err!(result, Error::SlotOfPoolNotFound);
2854        })
2855    }
2856
2857    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2858    // ```````````````````````````````````` POOL INFO ````````````````````````````````````
2859    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2860
2861    #[test]
2862    fn pool_info_eq_true() {
2863        let variant = Position::position_of(0).unwrap();
2864
2865        let entry_info = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2866        let entries = Entries::new(vec![entry_info]).unwrap();
2867
2868        let pool_info_a = PoolInfo::new(entries.clone(), COMMISSION_ZERO).unwrap();
2869        let pool_info_b = PoolInfo::new(entries, COMMISSION_ZERO).unwrap();
2870
2871        assert!(pool_info_a.eq(&pool_info_b));
2872    }
2873
2874    #[test]
2875    fn pool_info_eq_false() {
2876        let variant = Position::position_of(0).unwrap();
2877        let entry_info = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2878        let entries = Entries::new(vec![entry_info]).unwrap();
2879
2880        let pool_info_a = PoolInfo::new(entries.clone(), COMMISSION_ZERO).unwrap();
2881        let pool_info_b = PoolInfo::new(entries, COMMISSION_STANDARD).unwrap();
2882
2883        assert!(!pool_info_a.eq(&pool_info_b));
2884    }
2885
2886    #[test]
2887    fn pool_info_new_success() {
2888        let variant = Position::position_of(0).unwrap();
2889
2890        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2891        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2892
2893        let entries = Entries::new(vec![entry_info_a.clone(), entry_info_b.clone()]).unwrap();
2894
2895        let pool = PoolInfo::new(entries, COMMISSION_STANDARD).unwrap();
2896        let slot_info_a: SlotInfo = entry_info_a.into();
2897        let slot_info_b: SlotInfo = entry_info_b.into();
2898
2899        assert_eq!(pool.balance_of, LazyBalance::default());
2900        assert_eq!(pool.capital(), 150);
2901        assert_eq!(pool.commission, COMMISSION_STANDARD);
2902        assert_eq!(pool.slots().len(), 2);
2903        assert_eq!(pool.slots(), vec![slot_info_a, slot_info_b]);
2904    }
2905
2906    #[test]
2907    fn pool_info_new_err_capital_overflowed() {
2908        let variant = Position::position_of(0).unwrap();
2909
2910        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, MAX_SHARES, variant).unwrap();
2911        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2912
2913        let entries = Entries::new(vec![entry_info_a.clone(), entry_info_b.clone()]).unwrap();
2914
2915        let pool = PoolInfo::new(entries, COMMISSION_STANDARD);
2916        assert!(pool.is_err());
2917        assert_err!(pool, Error::CapitalOverflowed);
2918    }
2919
2920    #[test]
2921    fn pool_info_slots_success() {
2922        let variant = Position::position_of(0).unwrap();
2923
2924        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2925        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 50, variant).unwrap();
2926        let entry_info_c = EntryInfo::new(VALIDATOR_GAMMA, 150, variant).unwrap();
2927
2928        let entries = Entries::new(vec![
2929            entry_info_a.clone(),
2930            entry_info_b.clone(),
2931            entry_info_c.clone(),
2932        ])
2933        .unwrap();
2934
2935        let pool = PoolInfo::new(entries, COMMISSION_STANDARD).unwrap();
2936        let slot_info_a: SlotInfo = entry_info_a.into();
2937        let slot_info_b: SlotInfo = entry_info_b.into();
2938        let slot_info_c: SlotInfo = entry_info_c.into();
2939
2940        let actual_slots = pool.slots();
2941        let expected_slots = vec![slot_info_a, slot_info_b, slot_info_c];
2942        assert_eq!(actual_slots, expected_slots);
2943    }
2944
2945    #[test]
2946    fn pool_info_balance_reset_success() {
2947        let variant = Position::position_of(0).unwrap();
2948        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2949        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 200, variant).unwrap();
2950
2951        let entries = Entries::new(vec![entry_info_a, entry_info_b]).unwrap();
2952        let mut pool = PoolInfo::new(entries, COMMISSION_ZERO).unwrap();
2953
2954        pool.balance_reset();
2955
2956        assert_eq!(pool.balance_of, Default::default());
2957
2958        for slot in pool.slots() {
2959            assert_eq!(slot.commit(), Default::default());
2960        }
2961    }
2962
2963    #[test]
2964    fn pool_info_add_slot_success() {
2965        let variant = Position::position_of(0).unwrap();
2966
2967        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2968        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 100, variant).unwrap();
2969
2970        let entries = Entries::new(vec![entry_info_a.clone(), entry_info_b.clone()]).unwrap();
2971
2972        let mut pool = PoolInfo::new(entries, COMMISSION_ZERO).unwrap();
2973        let slot_info_a: SlotInfo = entry_info_a.into();
2974        let slot_info_b: SlotInfo = entry_info_b.into();
2975
2976        assert_eq!(pool.capital(), 200);
2977        assert_eq!(pool.slots().len(), 2);
2978
2979        let actual_slots = pool.slots();
2980        let expected_slots = vec![slot_info_a.clone(), slot_info_b.clone()];
2981        assert_eq!(actual_slots, expected_slots);
2982
2983        let entry_info_c = EntryInfo::new(VALIDATOR_GAMMA, 50, variant).unwrap();
2984        assert_ok!(pool.add_slot(entry_info_c.clone()));
2985        let slot_info_c: SlotInfo = entry_info_c.into();
2986
2987        assert_eq!(pool.capital(), 250);
2988        assert_eq!(pool.slots().len(), 3);
2989        let actual_slots = pool.slots();
2990        let expected_slots = vec![slot_info_a.clone(), slot_info_b.clone(), slot_info_c];
2991        assert_eq!(actual_slots, expected_slots);
2992    }
2993
2994    #[test]
2995    fn pool_info_add_slot_err_capital_overflowed() {
2996        let variant = Position::position_of(0).unwrap();
2997
2998        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
2999        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 100, variant).unwrap();
3000
3001        let entries = Entries::new(vec![entry_info_a.clone(), entry_info_b.clone()]).unwrap();
3002
3003        let mut pool = PoolInfo::new(entries, COMMISSION_ZERO).unwrap();
3004        let slot_info_a: SlotInfo = entry_info_a.into();
3005        let slot_info_b: SlotInfo = entry_info_b.into();
3006
3007        assert_eq!(pool.capital(), 200);
3008        assert_eq!(pool.slots().len(), 2);
3009
3010        let actual_slots = pool.slots();
3011        let expected_slots = vec![slot_info_a.clone(), slot_info_b.clone()];
3012        assert_eq!(actual_slots, expected_slots);
3013
3014        let entry_info_c = EntryInfo::new(VALIDATOR_GAMMA, MAX_SHARES, variant).unwrap();
3015        assert_err!(pool.add_slot(entry_info_c), Error::CapitalOverflowed);
3016    }
3017
3018    #[test]
3019    fn pool_info_remove_slot_success() {
3020        let variant = Position::position_of(0).unwrap();
3021
3022        let entry_info_a = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
3023        let entry_info_b = EntryInfo::new(VALIDATOR_BETA, 100, variant).unwrap();
3024        let entry_info_c = EntryInfo::new(VALIDATOR_GAMMA, 50, variant).unwrap();
3025
3026        let entries = Entries::new(vec![
3027            entry_info_a.clone(),
3028            entry_info_b.clone(),
3029            entry_info_c.clone(),
3030        ])
3031        .unwrap();
3032
3033        let mut pool = PoolInfo::new(entries, COMMISSION_ZERO).unwrap();
3034        let slot_info_a: SlotInfo = entry_info_a.into();
3035        let slot_info_b: SlotInfo = entry_info_b.into();
3036        let slot_info_c: SlotInfo = entry_info_c.into();
3037
3038        assert_eq!(pool.capital(), 250);
3039        assert_eq!(pool.slots().len(), 3);
3040
3041        let actual_slots = pool.slots();
3042        let expected_slots = vec![
3043            slot_info_a.clone(),
3044            slot_info_b.clone(),
3045            slot_info_c.clone(),
3046        ];
3047        assert_eq!(actual_slots, expected_slots);
3048
3049        pool.remove_slot(&VALIDATOR_ALPHA).unwrap();
3050        let actual_slots = pool.slots();
3051        let expected_slots = vec![slot_info_b.clone(), slot_info_c.clone()];
3052        assert_eq!(actual_slots, expected_slots);
3053
3054        assert_eq!(pool.capital(), 150);
3055        assert_eq!(pool.slots().len(), 2);
3056
3057        pool.remove_slot(&VALIDATOR_GAMMA).unwrap();
3058        let actual_slots = pool.slots();
3059        let expected_slots = vec![slot_info_b.clone()];
3060        assert_eq!(actual_slots, expected_slots);
3061
3062        assert_eq!(pool.capital(), 100);
3063        assert_eq!(pool.slots().len(), 1);
3064    }
3065
3066    #[test]
3067    fn pool_info_slot_exists_success() {
3068        let variant = Position::position_of(0).unwrap();
3069
3070        let entry_info = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
3071        let entries = Entries::new(vec![entry_info]).unwrap();
3072
3073        let pool = PoolInfo::new(entries, COMMISSION_ZERO).unwrap();
3074
3075        assert_ok!(pool.slot_exists(&VALIDATOR_ALPHA));
3076    }
3077
3078    #[test]
3079    fn pool_info_slot_exists_err_not_found() {
3080        let variant = Position::position_of(0).unwrap();
3081
3082        let entry_info = EntryInfo::new(VALIDATOR_ALPHA, 100, variant).unwrap();
3083        let entries = Entries::new(vec![entry_info]).unwrap();
3084
3085        let pool = PoolInfo::new(entries, COMMISSION_ZERO).unwrap();
3086
3087        assert_err!(pool.slot_exists(&VALIDATOR_BETA), Error::SlotOfPoolNotFound);
3088    }
3089}