frame_suite/commitment.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 SUITE ``````````````````````````````
14// ===============================================================================
15
16//! A composable framework for expressing financial intent as **immutable commitments**.
17//!
18//! ## Overview
19//!
20//! This module defines a comprehensive set of traits for building financial
21//! commitment systems that are **semantic, composable, transparent, and consistent**.
22//!
23//! At its core, a commitment represents **bonding value to a specific digest under a reason**.
24//!
25//! Instead of treating value as passive balance, this framework models value as
26//! something actively **bound with purpose and context**.
27//!
28//! Every commitment binds an asset to:
29//! - a **Reason** - why the value is committed
30//! - a **Digest** - the exact terms or context the value is bonded to
31//!
32//! Together, they form a **verifiable, immutable agreement**.
33//!
34//! Commitments can carry meaning through variants (e.g., long/short,
35//! for/against, positive/negative), support grouping via indexes and pools,
36//! and allow real-time inspection of state.
37//!
38//! The **reason provider (runtime logic)** governs how committed value evolves:
39//! it can update digest-level values, effectively managing all bonded funds
40//! under that reason. Individual commitments are **resolved lazily**, meaning
41//! adjustments are realized only when commitments are settled.
42//!
43//! ## Mental Model
44//!
45//! A commitment can be understood as:
46//!
47//! "Bond this value, for this purpose, to this exact context."
48//!
49//! - Reason provides intent (staking, escrow, trade, vote)
50//! - Digest defines the binding target (hash, identifier, agreement)
51//! - Asset represents the value being bonded
52//!
53//! This abstraction unifies patterns across financial systems such as
54//! staking, escrow, betting, trading, and governance.
55//!
56//! ## Trait Architecture
57//!
58//! The framework is layered:
59//!
60//! ### Foundation
61//! - [`Commitment`] - atomic bonding and lifecycle of commitments
62//! - [`InspectAsset`] - available funds introspection
63//! - [`DigestModel`] - digest classification
64//!
65//! ### Grouping
66//! - [`CommitIndex`] - a single commitment distributed across multiple digests
67//! under a reason, as if placed individually
68//!
69//! - [`CommitPool`] - manager-controlled allocation of bonded funds across
70//! multiple digests under a reason, without custodial ownership
71//!
72//! ### Semantics
73//! - [`CommitVariant`] - directional meaning (long/short, for/against)
74//! - [`IndexVariant`] - variants applied to indexed commitments
75//! - [`PoolVariant`] - variants applied to pooled commitments
76//!
77//! These traits compose into a layered system:
78//! - Base layer: [`Commitment`], [`InspectAsset`], [`DigestModel`]
79//! - Grouping layer: [`CommitIndex`], [`CommitPool`]
80//! - Variant layer: [`CommitVariant`], [`IndexVariant`], [`PoolVariant`]
81//!
82//! ## Core Properties
83//!
84//! - **Atomic** - bonded commitments cannot be partially altered
85//! - **Immutable** - [`Commitment::Digest`] and reason define permanent binding
86//! - **Value-bound** - always tied to quantifiable assets
87//! - **Composable** - higher-level abstractions reuse the same rules
88//! - **Inspectable** - real-time bonded state is always queryable
89//!
90//! ## Why This Exists
91//!
92//! Many financial systems repeatedly implement the same primitives:
93//! - locking or bonding funds
94//! - enforcing rules
95//! - tracking intent
96//! - preventing duplication
97//!
98//! This framework provides a unified abstraction to express all of them
99//! consistently and safely.
100//!
101//! ## Invariants
102//!
103//! - One commitment per `(proprietor, reason)`
104//! - Commitments are immutable (value may only increase via
105//! [`Commitment::raise_commit`])
106//! - [`Commitment::Digest`] defines the binding target and scope
107//! - State reflects current values via [`Commitment::get_digest_value`],
108//! not historical deposits
109//!
110//! ## Use Cases
111//!
112//! This framework is applicable across domains such as:
113//!
114//! - Financial systems (bets, trades, hedges)
115//! - Prediction markets (affirmative/negative stakes)
116//! - Voting protocols (for/against commitments)
117//! - Portfolio management (indexed and pooled commitments)
118//! - Escrow and contract systems
119//!
120//! ## Summary
121//!
122//! **Commitment = bonding value to a digest under a reason**
123//!
124//! This module transforms raw assets into structured, meaningful agreements
125//! that can be composed into complex financial systems.
126
127// ===============================================================================
128// ``````````````````````````````````` IMPORTS ```````````````````````````````````
129// ===============================================================================
130
131// --- Core ---
132use core::cmp::Ordering;
133
134// --- Local crate imports ---
135use crate::{
136 base::{
137 Asset, Countable, Delimited, Elastic, Keyed, Percentage, RuntimeEnum, RuntimeError,
138 Storable,
139 },
140 misc::{Directive, Extent},
141};
142
143// --- FRAME Support ----
144use frame_support::ensure;
145
146// --- Substrate primitives ---
147use sp_runtime::{
148 traits::{Saturating, Zero},
149 DispatchError, DispatchResult, Vec,
150};
151
152// ===============================================================================
153// ```````````````````````````````` INSPECT ASSET ````````````````````````````````
154// ===============================================================================
155
156/// A trait for inspecting the total available funds of an proprietor.
157///
158/// This allows querying all funds that are available to a proprietor,
159/// including liquid balances and any amounts held for specific purposes
160/// that the system considers available.
161///
162/// In this trait's context, "available funds" means those balances
163/// that can be used for operations in context.
164///
165/// Generics:
166/// - **Proprietor** - the entity that owns the asset and can make commitments.
167pub trait InspectAsset<Proprietor> {
168 /// Representing the Quantifiable Asset Type.
169 type Asset: Asset;
170
171 /// Retrieves the total available funds for a given proprietor.
172 ///
173 /// This must include all balances that can be utilized for the
174 /// implementation purposes.
175 ///
176 /// ## Returns
177 /// - [`Self::Asset`] containing the total available funds for the proprietor
178 fn available_funds(who: &Proprietor) -> Self::Asset;
179}
180
181// ===============================================================================
182// ````````````````````````````````` DIGEST MODEL ````````````````````````````````
183// ===============================================================================
184
185/// A trait for determining the model variant of a digest.
186///
187/// In this system, a "digest" represents a compact identifier for
188/// a commitment or resource.
189///
190/// Since all digests share a common base type, this trait provides a
191/// way to classify or wrap them into distinct model variants
192/// (e.g., Direct, Index, Pools) to improve clarity and enforce type safety.
193pub trait DigestModel<Proprietor>: Commitment<Proprietor> {
194 /// Wraps [`Commitment::Digest`] to reduce ambiguity.
195 type Model: Delimited + RuntimeEnum + Storable;
196
197 /// Determines the appropriate model variant for the given digest and reason.
198 ///
199 /// ## Returns
200 /// - `Ok(Model)` if the digest is recognized.
201 /// - `Err(DispatchError)` if the digest cannot be determined.
202 fn determine_digest(
203 digest: &Self::Digest,
204 reason: &Self::Reason,
205 ) -> Result<Self::Model, DispatchError>;
206}
207
208// ===============================================================================
209// `````````````````````````````` COMMITMENT ERRORS ``````````````````````````````
210// ===============================================================================
211
212/// Commitment-related error type used in trait defaults.
213pub enum CommitError {
214 /// Insufficient funds to perform the requested
215 /// commitment operation.
216 InsufficientFunds,
217
218 /// A commitment already exists for the given proprietor
219 /// and commitment reason.
220 CommitAlreadyExists,
221
222 /// Minting is not permitted under the current constraints
223 /// or exceeds allowed limits.
224 MintingOffLimits,
225
226 /// Reaping (burning) is not permitted under the current
227 /// constraints or exceeds allowed limits.
228 ReapingOffLimits,
229
230 /// Placing a new commitment is not permitted under the current constraints
231 /// or exceeds allowed limits.
232 PlacingOffLimits,
233
234 /// Increasing (raising) an existing commitment is not permitted under the
235 /// current constraints or exceeds allowed limits.
236 RaisingOffLimits,
237}
238
239/// A trait for mapping **domain-level Commitment errors** into
240/// **caller- or pallet-specific error types**.
241///
242/// This trait acts as a bridge between the generic, FRAME-agnostic
243/// [`CommitError`] enum and the concrete error type expected by the
244/// execution context.
245pub trait CommitErrorHandler {
246 /// Concrete error type produced by the handler.
247 ///
248 /// Implements conversion to [`DispatchError`].
249 type Error: RuntimeError;
250
251 /// Converts a generic [`CommitError`] into the handler's
252 /// concrete error type which implements `Into<DispatchError>`.
253 ///
254 /// This function centralizes error translation logic and ensures
255 /// that all balance-related failures are surfaced consistently
256 /// according to the caller's error domain.
257 fn from_commit_error(e: CommitError) -> Self::Error;
258}
259
260// ===============================================================================
261// `````````````````````````````````` COMMITMENT `````````````````````````````````
262// ===============================================================================
263
264/// Represents an **atomic, immutable financial agreement** between parties.
265///
266/// It is anchored in:
267/// - a **Reason** (category/purpose),
268/// - a **Digest** (commitment context),
269/// - and the act of committing a value.
270///
271/// ## Key Concepts
272///
273/// - **Commitment**: Locking or assigning an asset under agreed terms.
274/// - **Reason**: Purpose/category (e.g., staking, escrow, collateral,
275/// investment, betting).
276/// - **Digest**: Immutable reference to the commitment's context or terms
277/// (often a cryptographic hash or a contextual identifier).
278/// - **Proprietor**: Entity responsible for holding/committing the asset.
279/// - **Asset**: Value being committed.
280///
281/// ## Why Commitment?
282///
283/// Many financial systems - staking, escrow, lending, betting, auctions - share
284/// a common pattern: one party proposes terms (digest), another commits a value
285/// under those terms, and both agree on the purpose (reason).
286///
287/// Without a unifying abstraction, each system must reimplement logic for:
288/// - Locking assets
289/// - Ensuring immutability of terms
290/// - Preventing double commitments
291/// - Categorizing commitments by purpose
292///
293/// The `Commitment` trait standardizes this pattern, enabling:
294/// - **Consistency**: Same behavior across different financial constructs.
295/// - **Composability**: Higher-level constructs (pools, indexes, markets) reuse
296/// the same rules.
297/// - **Auditability**: Commitments can be inspected and verified.
298///
299/// ## Properties
300///
301/// - **Atomic**: Commitments cannot be partially altered.
302/// - **Immutable**: Digest + Reason permanently define the commitment.
303/// - **Category-driven**: Reason defines interpretation.
304/// - **Value-based**: Always tied to a quantifiable asset.
305///
306/// ## Real-World Analogies
307///
308/// | Use Case | Reason | Digest | Commitment |
309/// |----------------|--------------------|---------------------|---------------|
310/// | Stake | `"staking"` | Conditions hash | Staked amount |
311/// | Escrow | `"escrow"` | Contract terms hash | Locked funds |
312/// | Collateral | `"loan collateral"`| Loan agreement hash | Pledged asset |
313/// | Betting | `"bet"` | Bet terms | Wagered asset |
314/// | Market position| `"market trade"` | Order terms | Locked funds |
315/// | Auction | `"auction bid"` | Bid terms | Bid amount |
316///
317///
318/// ## Examples
319///
320/// - **Staking**: Alice stakes 100 tokens for reason `"staking"` with digest `"ant_pool_hash"`.
321/// - Proprietor: Alice
322/// - Reason: `"staking"`
323/// - Digest: `"ant_pool_hash"`
324/// - Asset: 100 tokens.
325///
326/// - **Betting**: Bob places a bet of 50 tokens for reason `"bet_games"` with digest
327/// `"nfl_betting"`.
328/// - Locks Bob's stake until event outcome.
329///
330/// - **Escrow**: Carol commits 1000 tokens into escrow for reason `"escrows"` with
331/// digest `"freelance"`.
332/// - Funds locked until conditions are fulfilled.
333///
334/// - **Market Order**: Dave places a buy order worth 500 tokens for reason
335/// `"market_orders"` with digest `"cryptocurrencies"`.
336/// - Funds locked until order executes or cancels.
337///
338/// ## Invariants
339///
340/// - Proprietor can commit to **only one digest per reason**.
341/// - Commitments are **atomic** and **immutable**.
342/// - Commitment value may increase via [`Commitment::raise_commit`] but cannot
343/// be decreased arbitrarily.
344/// - Commitment values are dynamic, reflecting real-time state.
345/// - Digest values may be updated by the reason provider via
346/// [`Commitment::set_digest_value`] and must propagate to all related commitments.
347///
348/// **Summary:**
349/// Commitment = locked asset,
350/// Reason = purpose/category,
351/// Digest = agreement terms hash,
352/// Act of commitment = immutable financial agreement.
353///
354/// Generics:
355/// - **Proprietor** - the entity that owns the asset and can make commitments.
356pub trait Commitment<Proprietor>: InspectAsset<Proprietor> + CommitErrorHandler {
357 /// The source used to generate the digest.
358 type DigestSource: Elastic;
359
360 /// The immutable substance proposed by the first party - the "sealed deed content".
361 /// It represents the details of the deed or offer that expects a commitment.
362 ///
363 /// Digest is the core of a commitment: it proves what the commitment is tied to,
364 /// and ensures it is fixed and immutable.
365 type Digest: Keyed;
366
367 /// The category or type of the commitment.
368 /// Provides meaning to the digest by placing it in context.
369 type Reason: Elastic + Storable + RuntimeEnum;
370
371 /// Represents the qualitative configuration of a commit operation.
372 ///
373 /// Encodes behavioral characteristics such as precision and fortitude,
374 /// influencing how commitments are evaluated and executed. (e.g. exact
375 /// vs approximate, polite vs forceful execution).
376 ///
377 /// Default is provided if in case no preference is provided.
378 type Intent: Delimited + Directive + Default;
379
380 /// Provides optional advisory bounds for commitment and digest operations.
381 ///
382 /// These limits act as an additional validation layer on top of core
383 /// invariants, helping prevent premature or extreme values without
384 /// affecting correctness.
385 ///
386 /// Uses [`Extent`] to express minimum, maximum, or optimal values.
387 type Limits: Extent<Scalar = Self::Asset>;
388
389 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
390 // ``````````````````````````````````` CHECKERS ``````````````````````````````````
391 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
392
393 /// Checks whether a commitment exists for the given proprietor and reason.
394 ///
395 /// ### Returns
396 /// - `Ok(())` if the commitment exists
397 /// - `Err(DispatchError)` if no commitment is found
398 fn commit_exists(who: &Proprietor, reason: &Self::Reason) -> DispatchResult;
399
400 /// Checks whether a specific digest exists for the given reason.
401 ///
402 /// ### Returns
403 /// - `Ok(())` if the digest exists
404 /// - `Err(DispatchError)` if the digest is not found
405 fn digest_exists(reason: &Self::Reason, digest: &Self::Digest) -> DispatchResult;
406
407 /// Validates whether a new commitment can be placed.
408 ///
409 /// Ensures that no existing commitment exists for the given reason (enforcing
410 /// the "one digest per reason" invariant) and that the proprietor has sufficient
411 /// available funds to cover the commitment value.
412 ///
413 /// ## Returns
414 /// - `Ok(())` if validation succeeds
415 /// - `Err(DispatchError)` if a commitment already exists or insufficient funds are available
416 fn can_place_commit(
417 who: &Proprietor,
418 reason: &Self::Reason,
419 digest: &Self::Digest,
420 value: Self::Asset,
421 qualifier: &Self::Intent,
422 ) -> DispatchResult {
423 ensure!(
424 Self::commit_exists(who, reason).is_err(),
425 Self::from_commit_error(CommitError::CommitAlreadyExists)
426 );
427 let max = Self::available_funds(who);
428 ensure!(
429 max >= value,
430 Self::from_commit_error(CommitError::InsufficientFunds)
431 );
432 let limits = Self::place_commit_limits(who, reason, digest, qualifier)?;
433 ensure!(
434 limits.contains(value),
435 Self::from_commit_error(CommitError::PlacingOffLimits)
436 );
437 Ok(())
438 }
439
440 /// Validates whether an existing commitment can be increased.
441 ///
442 /// Ensures that a commitment for the given reason already exists and that
443 /// the proprietor has sufficient available funds to increase the commitment
444 /// by the specified value.
445 ///
446 /// ## Returns
447 /// - `Ok(())` if validation succeeds
448 /// - `Err(DispatchError)` if any of the condition fails.
449 fn can_raise_commit(
450 who: &Proprietor,
451 reason: &Self::Reason,
452 value: Self::Asset,
453 qualifier: &Self::Intent,
454 ) -> DispatchResult {
455 Self::commit_exists(who, reason)?;
456 let max = Self::available_funds(who);
457 ensure!(
458 max >= value,
459 Self::from_commit_error(CommitError::InsufficientFunds)
460 );
461 let limits = Self::raise_commit_limits(who, reason, qualifier)?;
462 ensure!(
463 limits.contains(value),
464 Self::from_commit_error(CommitError::RaisingOffLimits)
465 );
466 Ok(())
467 }
468
469 /// Validates whether an existing commitment can be resolved.
470 ///
471 /// Ensures that a commitment for the given reason exists before
472 /// allowing resolution/closing.
473 ///
474 /// ## Returns
475 /// - `Ok(())` if validation succeeds
476 /// - `Err(DispatchError)` if no such commitment exists
477 fn can_resolve_commit(who: &Proprietor, reason: &Self::Reason) -> DispatchResult {
478 Self::commit_exists(who, reason)?;
479 Ok(())
480 }
481
482 /// Validates whether a digest's value can be set or updated.
483 ///
484 /// Ensures that the specified digest exists under the given reason,
485 /// and that the intended value update respects advisory limits
486 /// (mint or reap depending on direction).
487 ///
488 /// The `qualifier` may influence how limits are interpreted.
489 ///
490 /// ## Returns
491 /// - `Ok(())` if validation succeeds
492 /// - `Err(DispatchError)` if the digest does not exist or limits are violated
493 fn can_set_digest_value(
494 reason: &Self::Reason,
495 digest: &Self::Digest,
496 value: Self::Asset,
497 qualifier: &Self::Intent,
498 ) -> DispatchResult {
499 // Get current value
500 let current = Self::get_digest_value(reason, digest)?;
501 match current.cmp(&value) {
502 Ordering::Less => {
503 // Mint path (increase)
504 let limits = Self::digest_mint_limits(digest, reason, qualifier)?;
505 let mintable = value.saturating_sub(current);
506 ensure!(
507 limits.contains(mintable),
508 Self::from_commit_error(CommitError::MintingOffLimits)
509 )
510 }
511 Ordering::Greater => {
512 // Reap path (decrease)
513 let limits = Self::digest_reap_limits(digest, reason, qualifier)?;
514 let reapable = current.saturating_sub(value);
515 ensure!(
516 limits.contains(reapable),
517 Self::from_commit_error(CommitError::ReapingOffLimits)
518 )
519 }
520 Ordering::Equal => {
521 // No-op, always valid
522 }
523 }
524 Ok(())
525 }
526
527 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
528 // ``````````````````````````````````` GETTERS ```````````````````````````````````
529 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
530
531 /// Retrieves the digest tied to a proprietor's commitment for the given reason.
532 ///
533 /// Since the system enforces only one digest per reason, its expected to be
534 /// only a single digest or none.
535 ///
536 /// ### Returns
537 /// - `Ok(Digest)` containing the commitment's digest
538 /// - `Err(DispatchError)` if no commitment exists for the reason
539 fn get_commit_digest(
540 who: &Proprietor,
541 reason: &Self::Reason,
542 ) -> Result<Self::Digest, DispatchError>;
543
544 /// Retrieves the total aggregated value for all commitments under the given reason.
545 ///
546 /// ### Returns
547 /// - `Self::Asset` containing the total value across all commitments for the reason
548 fn get_total_value(reason: &Self::Reason) -> Self::Asset;
549
550 /// Retrieves the current real-time value of a proprietor's commitment for the given reason.
551 ///
552 /// Returns the live committed value at the moment of calling, reflecting any
553 /// changes to the underlying digest value since the commitment was initially
554 /// placed. This is not a historical value but the actual current state, as
555 /// digest values can be updated via [`set_digest_value`](Self::set_digest_value).
556 ///
557 /// ### Returns
558 /// - `Ok(Asset)` containing the current commitment value
559 /// - `Err(DispatchError)` if the commitment does not exist
560 fn get_commit_value(
561 who: &Proprietor,
562 reason: &Self::Reason,
563 ) -> Result<Self::Asset, DispatchError>;
564
565 /// Retrieves the current aggregated value for the given digest under the specified reason.
566 ///
567 /// Returns the real-time sum of all commitments tied to the digest.
568 /// Useful for evaluating the full scope of commitments to a specific digest.
569 ///
570 /// ### Returns
571 /// - `Ok(Asset)` containing the aggregated digest value
572 /// - `Err(DispatchError)` if the digest does not exist or lookup fails
573 fn get_digest_value(
574 reason: &Self::Reason,
575 digest: &Self::Digest,
576 ) -> Result<Self::Asset, DispatchError>;
577
578 /// Provides advisory bounds for increasing (minting) a digest's value.
579 ///
580 /// Acts as an optional guard to prevent abrupt or excessive growth,
581 /// complementing core digest update logic.
582 fn digest_mint_limits(
583 _digest: &Self::Digest,
584 _reason: &Self::Reason,
585 _qualifier: &Self::Intent,
586 ) -> Result<Self::Limits, DispatchError> {
587 // By default, this returns a baseline limit,
588 // representing a static or fallback bound when no dynamic limits are applied.
589 //
590 // Implementors may override this to provide context-specific limits.
591 Ok(Self::Limits::none())
592 }
593
594 /// Provides advisory bounds for placing a new commitment.
595 ///
596 /// Acts as an optional pre-validation layer to prevent premature or
597 /// undesirable commits by constraining value ranges.
598 fn place_commit_limits(
599 _who: &Proprietor,
600 _reason: &Self::Reason,
601 _digest: &Self::Digest,
602 _qualifier: &Self::Intent,
603 ) -> Result<Self::Limits, DispatchError> {
604 // By default, this returns an unbounded extent,
605 // meaning no additional constraints are applied unless overridden.
606 //
607 // Implementors may override this to provide context-specific limits.
608 Ok(Self::Limits::none())
609 }
610
611 /// Provides advisory bounds for increasing an existing commitment.
612 ///
613 /// Acts as an optional guard to prevent excessive or premature raises,
614 /// complementing core checks like existence and available funds.
615 fn raise_commit_limits(
616 _who: &Proprietor,
617 _reason: &Self::Reason,
618 _qualifier: &Self::Intent,
619 ) -> Result<Self::Limits, DispatchError> {
620 // By default, this returns an unbounded extent,
621 // meaning no additional constraints are applied unless overridden.
622 //
623 // Implementors may override this to provide context-specific limits.
624 Ok(Self::Limits::none())
625 }
626
627 /// Provides advisory bounds for decreasing (reaping) a digest's value.
628 ///
629 /// Acts as an optional guard to prevent aggressive or premature reductions,
630 /// complementing core invariants that ensure correctness.
631 fn digest_reap_limits(
632 _digest: &Self::Digest,
633 _reason: &Self::Reason,
634 _qualifier: &Self::Intent,
635 ) -> Result<Self::Limits, DispatchError> {
636 // By default, this returns a baseline limit,
637 // representing a static or fallback bound when no dynamic limits are applied.
638 // Implementors may override this to provide context-specific limits.
639 Ok(Self::Limits::none())
640 }
641
642 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
643 // ````````````````````````````````` CONSTRUCTORS ````````````````````````````````
644 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
645
646 /// Generates a unique digest from the provided source.
647 ///
648 /// Creates an immutable identifier that represents the commitment's terms,
649 /// ensuring collision-free digest generation across the system.
650 ///
651 /// ## Returns
652 /// - `Ok(Digest)` containing the generated unique digest
653 /// - `Err(DispatchError)` if digest generation fails
654 fn gen_digest(via: &Self::DigestSource) -> Result<Self::Digest, DispatchError>;
655
656 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
657 // ``````````````````````````````````` MUTATORS ``````````````````````````````````
658 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
659
660 /// Places an immutable commitment for the specified digest and reason.
661 ///
662 /// Locks the specified value under the given digest and reason, creating
663 /// an atomic commitment that cannot be reduced or partially withdrawn.
664 ///
665 /// The qualifier parameter enforces exactness or best-effort semantics,
666 /// while also specifying whether the system must force achieving the
667 /// commitment conditions.
668 ///
669 /// ## Returns
670 /// - `Ok(Asset)` containing the actual value committed to the digest
671 /// - `Err(DispatchError)` if placement fails due to insufficient funds or
672 /// validation errors
673 fn place_commit(
674 who: &Proprietor,
675 reason: &Self::Reason,
676 digest: &Self::Digest,
677 value: Self::Asset,
678 qualifier: &Self::Intent,
679 ) -> Result<Self::Asset, DispatchError>;
680
681 /// Resolves and releases the commitment tied to the specified reason.
682 ///
683 /// Finalizes the commitment for the given proprietor and reason, releasing
684 /// the locked value.
685 ///
686 /// Proprietors are enforced to commit to a **single digest per reason**,
687 /// ensuring unambiguous resolution. Higher-level traits such as [`CommitIndex`]
688 /// or [`CommitPool`] can be built to allow indexed or pooled commitments on top of
689 /// this invariant.
690 ///
691 /// The returned value may differ from the original deposit depending on the reason's
692 /// context and any adjustments made during the commitment's lifetime possibly
693 /// via [`Commitment::set_digest_value`].
694 ///
695 /// ## Returns
696 /// - `Ok(Asset)` containing the fully resolved commitment value
697 /// - `Err(DispatchError)` if the commitment does not exist or resolution fails
698 fn resolve_commit(
699 who: &Proprietor,
700 reason: &Self::Reason,
701 ) -> Result<Self::Asset, DispatchError>;
702
703 /// Increases the value of an existing commitment.
704 ///
705 /// Adds the specified value to an existing commitment for the given reason.
706 /// This operation can only be performed if a commitment already exists.
707 ///
708 /// The qualifier parameter control how the increase is applied.
709 ///
710 /// ## Returns
711 /// - `Ok(Asset)` containing the increment actually added (not the total value)
712 /// - `Err(DispatchError)` if the commitment does not exist or if the increase fails
713 fn raise_commit(
714 who: &Proprietor,
715 reason: &Self::Reason,
716 value: Self::Asset,
717 qualifier: &Self::Intent,
718 ) -> Result<Self::Asset, DispatchError>;
719
720 /// Sets or updates the aggregated value of the specified digest.
721 ///
722 /// Allows the reason provider (implementer) to adjust the digest's value,
723 /// which automatically propagates to all individual commitments tied to that
724 /// digest.
725 ///
726 /// The `qualifier` defines how the update is applied (e.g., exact vs best-effort,
727 /// forceful vs relaxed), and determines the execution semantics for the direction
728 /// of change (increase or decrease relative to the current value).
729 ///
730 /// ### Returns
731 /// - `Ok(Asset)` containing the actual value applied to the digest
732 /// - `Err(DispatchError)` if the digest does not exist or update fails
733 fn set_digest_value(
734 reason: &Self::Reason,
735 digest: &Self::Digest,
736 value: Self::Asset,
737 qualifier: &Self::Intent,
738 ) -> Result<Self::Asset, DispatchError>;
739
740 /// Removes a digest if it has no associated commitments.
741 ///
742 /// Permanently deletes the specified digest from the system if its aggregated
743 /// value is zero, freeing resources and cleaning up unused entries.
744 /// This is a maintenance operation and it should ensures no commitments exist
745 /// for the digest before reaping.
746 ///
747 /// ### Returns
748 /// - `Ok(())` if the digest is successfully removed
749 /// - `Err(DispatchError)` if the digest does not exist or still has active commitments
750 fn reap_digest(digest: &Self::Digest, reason: &Self::Reason) -> DispatchResult;
751
752 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
753 // ```````````````````````````````````` HOOKS ````````````````````````````````````
754 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
755
756 /// Hook called after a commitment is successfully placed.
757 ///
758 /// Provides an extension point for triggering side-effects such as events,
759 /// logging, external state updates, or notifications when a new commitment
760 /// is established.
761 ///
762 /// Default implementation is a no-op.
763 fn on_commit_place(
764 _who: &Proprietor,
765 _reason: &Self::Reason,
766 _digest: &Self::Digest,
767 _value: Self::Asset,
768 ) {
769 }
770
771 /// Hook called after a commitment is successfully raised (increased).
772 ///
773 /// Provides an extension point for triggering side-effects such as recalculations,
774 /// notifications, event emissions, or external state synchronization when an
775 /// existing commitment's value is increased.
776 ///
777 /// Default implementation is a no-op.
778 fn on_commit_raise(
779 _who: &Proprietor,
780 _reason: &Self::Reason,
781 _digest: &Self::Digest,
782 _value: Self::Asset,
783 ) {
784 }
785
786 /// Hook called after a commitment is successfully resolved.
787 ///
788 /// Provides an extension point for performing cleanup, distributing rewards or
789 /// penalties, audit logging, or triggering any finalization logic when a
790 /// commitment is released.
791 ///
792 /// Default implementation is a no-op.
793 fn on_commit_resolve(
794 _who: &Proprietor,
795 _reason: &Self::Reason,
796 _digest: &Self::Digest,
797 _value: Self::Asset,
798 ) {
799 }
800
801 /// Hook called after a digest's value is updated.
802 ///
803 /// Provides an extension point for triggering recalculation of dependent
804 /// commitments, propagating changes to related state, or emitting events
805 /// when a digest's aggregated value changes.
806 ///
807 /// Default implementation is a no-op.
808 fn on_digest_update(_digest: &Self::Digest, _reason: &Self::Reason, _value: Self::Asset) {}
809
810 /// Hook called after a digest is reaped (removed).
811 ///
812 /// Provides an extension point for cleanup tasks, logging, freeing
813 /// related resources, or triggering external notifications when a digest
814 /// is permanently removed from the system.
815 ///
816 /// `dust` represents any residual value that was unclaimable and
817 /// effectively considered dead.
818 ///
819 /// Default implementation is a no-op.
820 fn on_reap_digest(_digest: &Self::Digest, _reason: &Self::Reason, _dust: Self::Asset) {}
821}
822
823// ===============================================================================
824// ````````````````````````````````` COMMIT INDEX ````````````````````````````````
825// ===============================================================================
826
827/// `CommitIndex` extends [`Commitment`] by providing a **higher-level financial abstraction**
828/// that groups multiple commitments under a single "index".
829///
830/// This enables proprietors to manage related commitments collectively,
831/// while preserving the **atomicity** and **immutability** guarantees of `Commitment`.
832///
833/// ## Use Cases
834///
835/// - Portfolio management: Aggregate commitments.
836/// - Betting pools: Collective tracking and settlement.
837/// - Multi-asset contracts: Manage grouped assets with shares.
838/// - Escrow pools: Efficient tracking of grouped escrows.
839/// - Market positions: Aggregate for easier tracking.
840///
841/// ## Why `CommitIndex`?
842///
843/// The base `Commitment` trait enforces:
844/// > "One reason -> one digest".
845///
846/// This is restrictive when:
847/// - A proprietor needs to manage **multiple related commitments under one reason**.
848/// - Aggregation of commitments is required.
849/// - Ownership of grouped commitments must be tracked proportionally.
850/// - Trustless, composable structures are desired.
851///
852/// `CommitIndex` solves this by introducing **indexes**:
853/// - An **index digest** references multiple committed digests (entries).
854/// - Each entry remains an independent `Commitment`.
855/// - The index enables aggregation, share-tracking, and collective management
856/// without breaking core commitment rules.
857///
858/// ## Core Principles
859///
860/// 1. **Wrapper over commitments**
861/// - Index digest groups multiple committed digests.
862/// - Entries retain individual commitment properties.
863///
864/// 2. **Management layer**
865/// - Tracks shares and values for each entry.
866/// - Aggregates entry values into a single index value.
867///
868/// 3. **Integrity**
869/// - Entries remain independent commitments.
870/// - Index creation does not alter entry commitments.
871///
872/// 4. **Trustless design**
873/// - Anyone can interact with indexes without requiring creator consent,
874/// provided the share structure allows it.
875///
876/// ## Examples
877///
878/// ### Example 1: Portfolio Staking
879///
880/// Alice stakes multiple digests under a single reason to manage risk:
881///
882/// | Reason | Digest | Value |
883/// |------------|---------------|-------|
884/// | "staking" | "digest_a123" | 100 |
885/// | "staking" | "digest_b456" | 200 |
886/// | "staking" | "digest_c789" | 300 |
887///
888/// Each row is an independent commitment.
889/// Alice creates a `CommitIndex`:
890/// - Reason = `"staking"`
891/// - Digest = `"index_digest_xyz"`
892/// - Entries = `[digest_a123, digest_b456, digest_c789]`
893/// - Shares = `[1, 2, 3]`
894///
895/// Total value = `600`, with proportional share tracking.
896///
897/// ### Example 2: Betting Pool
898///
899/// Bettors commit value for different bets under the same reason:
900///
901/// | Reason | Digest | Value |
902/// |----------------|------------------|-------|
903/// | "betting_pool" | "digest_bet101" | 50 |
904/// | "betting_pool" | "digest_bet102" | 80 |
905///
906/// CommitIndex:
907/// - Reason = `"betting_pool"`
908/// - Digest = `"index_digest_betting"`
909/// - Entries = `[digest_bet101, digest_bet102]`
910/// - Shares = `[2, 3]`
911///
912/// Total value = `130`, with proportional share tracking.
913///
914/// ### Example 3: Market Position Index
915///
916/// Dave aggregates multiple market positions:
917///
918/// | Reason | Digest | Value |
919/// |-------------------|----------------|-------|
920/// | "market_positions"| "digest_order1"| 500 |
921/// | "market_positions"| "digest_order2"| 300 |
922///
923/// CommitIndex:
924/// - Reason = `"market_positions"`
925/// - Digest = `"index_digest_market"`
926/// - Entries = `[digest_order1, digest_order2]`
927/// - Shares = `[5, 3]`
928///
929/// Aggregates positions without altering atomic commitments.
930///
931/// ### Example 4: Escrow Pool
932///
933/// Multiple escrows under one reason:
934///
935/// | Reason | Digest | Value |
936/// |--------------|-----------------|-------|
937/// | "escrow_pool"| "digest_escrow1"| 1000 |
938/// | "escrow_pool"| "digest_escrow2"| 1500 |
939///
940/// CommitIndex:
941/// - Reason = `"escrow_pool"`
942/// - Digest = `"index_digest_escrow"`
943/// - Entries = `[digest_escrow1, digest_escrow2]`
944/// - Shares = `[1, 1.5]`
945///
946/// Tracks total escrow commitments (`2500`) and proportional ownership.
947///
948/// ## Invariants
949///
950/// - An index digest is a managed wrapper over multiple committed digests.
951/// - Each entry is an independent `Commitment`.
952/// - Index creation preserves atomicity, immutability, and reason-digest invariants.
953/// - Shares define proportional ownership of aggregated commitments.
954/// - Must maintain the base-invariant "One Reason -> One Digest"
955///
956/// Generics:
957/// - **Proprietor** - the entity that owns the asset and can make commitments.
958pub trait CommitIndex<Proprietor>: Commitment<Proprietor> {
959 /// The type representing an index.
960 /// This could be a struct containing entries and shares.
961 type Index: Elastic + Storable;
962
963 /// The type representing shares for an entry.
964 ///
965 /// Should be a simple unsigned numeric type.
966 type Shares: Countable;
967
968 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
969 // ``````````````````````````````````` CHECKERS ``````````````````````````````````
970 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
971
972 /// Checks whether an index exists for the given reason and index digest.
973 ///
974 /// ## Returns
975 /// - `Ok(())` if the index exists
976 /// - `Err(DispatchError)` if the index is not found
977 fn index_exists(reason: &Self::Reason, index_of: &Self::Digest) -> DispatchResult;
978
979 /// Checks whether a specific entry exists within the given index.
980 ///
981 /// Verifies that the specified entry digest is part of the index's
982 /// entry list under the given reason.
983 ///
984 /// ## Returns
985 /// - `Ok(())` if the entry exists within the index
986 /// - `Err(DispatchError)` if the entry is not found in the index
987 fn entry_exists(
988 reason: &Self::Reason,
989 index_of: &Self::Digest,
990 entry_of: &Self::Digest,
991 ) -> DispatchResult;
992
993 /// Checks whether any index exists for the given reason.
994 ///
995 /// Verifies that at least one index has been created under the
996 /// specified reason across all proprietors.
997 ///
998 /// ## Returns
999 /// - `Ok(())` if at least one index exists for the reason
1000 /// - `Err(DispatchError)` if no indexes are found for the reason
1001 fn has_index(reason: &Self::Reason) -> DispatchResult;
1002
1003 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1004 // ``````````````````````````````````` GETTERS ```````````````````````````````````
1005 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1006
1007 /// Retrieves the complete index structure for the given reason and index digest.
1008 ///
1009 /// Returns the full index object containing all entries, shares, and
1010 /// associated metadata for inspection or processing.
1011 ///
1012 /// ## Returns
1013 /// - `Ok(Index)` containing the complete index structure i.e.,
1014 /// [`CommitIndex::Index`]
1015 /// - `Err(DispatchError)` if the index does not exist
1016 fn get_index(
1017 reason: &Self::Reason,
1018 index_of: &Self::Digest,
1019 ) -> Result<Self::Index, DispatchError>;
1020
1021 /// Retrieves the real-time aggregated value of the specified index.
1022 ///
1023 /// Computes the total value by summing the current values of all entry digests
1024 /// under the index. Since the supertrait [`Commitment`] allows digest values
1025 /// to be updated via [`Commitment::set_digest_value`], this reflects the
1026 /// **live state** rather than historical deposits.
1027 ///
1028 /// Ensures each entry's digest value is current before aggregation.
1029 ///
1030 /// ## Returns
1031 /// - `Ok(Asset)` containing the total aggregated value of the index
1032 /// - `Err(DispatchError)` if the index does not exist or value computation fails
1033 fn get_index_value(
1034 reason: &Self::Reason,
1035 index_of: &Self::Digest,
1036 ) -> Result<Self::Asset, DispatchError> {
1037 Self::index_exists(reason, index_of)?;
1038 let entries_values = Self::get_entries_value(reason, index_of)?;
1039 let mut value = Self::Asset::zero();
1040 for (_, val) in entries_values {
1041 value = value.saturating_add(val);
1042 }
1043 Ok(value)
1044 }
1045
1046 /// Retrieves the shares of each entry in the given index.
1047 ///
1048 /// Returns a list of entry digests paired with their corresponding shares,
1049 /// representing each entry's proportional weight within the index.
1050 ///
1051 /// ## Returns
1052 /// - `Ok(Vec<(Self::Digest, Self::Shares)>)` containing entry-share pairs
1053 /// - `Err(DispatchError)` if the index does not exist or retrieval fails
1054 fn get_entries_shares(
1055 reason: &Self::Reason,
1056 index_of: &Self::Digest,
1057 ) -> Result<Vec<(Self::Digest, Self::Shares)>, DispatchError>;
1058
1059 /// Retrieves the real-time values of all entries within the specified index.
1060 ///
1061 /// Each entry's value is fetched individually and reflects any changes since
1062 /// the commitment was created, as the supertrait [`Commitment`] allows digest
1063 /// values to be updated via [`Commitment::set_digest_value`].
1064 ///
1065 /// Returns a list of entry digests paired with their current committed values.
1066 ///
1067 /// ## Returns
1068 /// - `Ok(Vec<(Self::Digest, Self::Asset)>)` containing entry-value pairs
1069 /// - `Err(DispatchError)` if the index does not exist or value retrieval fails
1070 fn get_entries_value(
1071 reason: &Self::Reason,
1072 index_of: &Self::Digest,
1073 ) -> Result<Vec<(Self::Digest, Self::Asset)>, DispatchError> {
1074 Self::index_exists(reason, index_of)?;
1075 let entries = Self::get_entries_shares(reason, index_of)?;
1076 let mut vec = Vec::new();
1077 for (entry_of, _) in entries {
1078 let value = Self::get_entry_value(reason, index_of, &entry_of)?;
1079 vec.push((entry_of, value))
1080 }
1081 Ok(vec)
1082 }
1083
1084 /// Retrieves the real-time committed value of a specific entry within an index.
1085 ///
1086 /// Returns the current value for the given entry digest under the specified
1087 /// reason and index. Since the supertrait [`Commitment`] allows digest values
1088 /// to be updated via [`Commitment::set_digest_value`], this reflects the
1089 /// **live state** rather than the original deposit.
1090 ///
1091 /// ## Returns
1092 /// - `Ok(Asset)` containing the entry's current committed value
1093 /// - `Err(DispatchError)` if the index or entry does not exist
1094 fn get_entry_value(
1095 reason: &Self::Reason,
1096 index_of: &Self::Digest,
1097 entry_of: &Self::Digest,
1098 ) -> Result<Self::Asset, DispatchError>;
1099
1100 /// Retrieves the real-time values of a proprietor's commitments to all entries
1101 /// within the specified index.
1102 ///
1103 /// Each entry's value is computed individually for the given proprietor and
1104 /// reflects any changes since commitment, as the supertrait [`Commitment`]
1105 /// allows digest values to be updated via [`Commitment::set_digest_value`].
1106 ///
1107 /// Returns a list of entry digests paired with their current committed values
1108 /// for the specified proprietor.
1109 ///
1110 /// ## Returns
1111 /// - `Ok(Vec<(Self::Digest, Self::Asset)>)` containing entry-value pairs for the proprietor
1112 /// - `Err(DispatchError)` if the index does not exist or value retrieval fails
1113 fn get_entries_value_for(
1114 who: &Proprietor,
1115 reason: &Self::Reason,
1116 index_of: &Self::Digest,
1117 ) -> Result<Vec<(Self::Digest, Self::Asset)>, DispatchError> {
1118 Self::index_exists(reason, index_of)?;
1119 let entries = Self::get_entries_shares(reason, index_of)?;
1120 let mut vec = Vec::new();
1121 for (entry_of, _) in entries {
1122 let value = Self::get_entry_value_for(who, reason, index_of, &entry_of)?;
1123 vec.push((entry_of, value))
1124 }
1125 Ok(vec)
1126 }
1127
1128 /// Retrieves the real-time value of a proprietor's commitment to a specific
1129 /// entry within an index.
1130 ///
1131 /// Returns the live committed value for the given proprietor and entry digest.
1132 /// Since the supertrait [`Commitment`] allows digest values to be updated via
1133 /// [`Commitment::set_digest_value`], this reflects the **current state** rather
1134 /// than the deposited total.
1135 ///
1136 /// ## Returns
1137 /// - `Ok(Asset)` containing the proprietor's current commitment to the entry
1138 /// - `Err(DispatchError)` if the index or entry does not exist
1139 fn get_entry_value_for(
1140 who: &Proprietor,
1141 reason: &Self::Reason,
1142 index_of: &Self::Digest,
1143 entry_of: &Self::Digest,
1144 ) -> Result<Self::Asset, DispatchError>;
1145
1146 /// Retrieves the real-time total value of a proprietor's commitment to an index.
1147 ///
1148 /// Aggregates the live committed values of all entry digests within the index
1149 /// for the given proprietor. Since the supertrait [`Commitment`] allows digest
1150 /// values to be updated via [`Commitment::set_digest_value`], this reflects
1151 /// the **current total** rather than historical deposits.
1152 ///
1153 /// ## Returns
1154 /// - `Ok(Asset)` containing the proprietor's total commitment to the index
1155 /// - `Err(DispatchError)` if the index does not exist or computation fails
1156 fn get_index_value_for(
1157 who: &Proprietor,
1158 reason: &Self::Reason,
1159 index_of: &Self::Digest,
1160 ) -> Result<Self::Asset, DispatchError> {
1161 Self::index_exists(reason, index_of)?;
1162 let mut value = Self::Asset::zero();
1163 let entries = Self::get_entries_value_for(who, reason, index_of)?;
1164 for (_, val) in entries {
1165 value = value.saturating_add(val)
1166 }
1167 Ok(value)
1168 }
1169
1170 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1171 // ````````````````````````````````` CONSTRUCTORS ````````````````````````````````
1172 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1173
1174 /// Generates a unique digest for the given index object.
1175 ///
1176 /// Creates a distinct identifier derived from the proprietor, reason, and
1177 /// index structure (including entries and shares). The generated digest
1178 /// must be collision-resistant and unique across all indexes of reason.
1179 ///
1180 /// ### Returns
1181 /// - `Ok(Digest)` containing the generated unique index digest
1182 /// - `Err(DispatchError)` if digest generation fails due to invalid inputs
1183 fn gen_index_digest(
1184 from: &Proprietor,
1185 reason: &Self::Reason,
1186 index: &Self::Index,
1187 ) -> Result<Self::Digest, DispatchError>;
1188
1189 /// Prepares an index object from the provided entry data.
1190 ///
1191 /// Constructs a valid index structure from a list of `(Digest, Shares)` pairs,
1192 /// where each pair represents:
1193 /// - **Digest**: The unique identifier of an entry within the index
1194 /// - **Shares**: The proportional weight or ownership of that entry
1195 ///
1196 /// This method:
1197 /// - Validates entry data for consistency and integrity
1198 /// - Ensures no duplicate digests exist
1199 /// - Rejects entries with nil/empty digests or zero shares
1200 /// - Creates an immutable, atomic index object
1201 ///
1202 /// ## Returns
1203 /// - `Ok(Index)` containing the prepared index structure
1204 /// - `Err(DispatchError)` if preparation fails due to invalid data or validation errors
1205 fn prepare_index(
1206 who: &Proprietor,
1207 reason: &Self::Reason,
1208 entries: &[(Self::Digest, Self::Shares)],
1209 ) -> Result<Self::Index, DispatchError>;
1210
1211 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1212 // ``````````````````````````````````` MUTATORS ``````````````````````````````````
1213 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1214
1215 /// Binds the prepared index to the specified digest under the given reason.
1216 ///
1217 /// Associates the provided digest with the index structure, making it
1218 /// queryable and usable within the commitment system. The index object
1219 /// should be safely prepared via [`CommitIndex::prepare_index`] before
1220 /// calling this method.
1221 ///
1222 /// This operation ensures:
1223 /// - The digest uniquely identifies the index
1224 /// - All entries and shares are preserved with integrity
1225 /// - The index conforms to [`CommitIndex`] invariants
1226 ///
1227 /// ## Returns
1228 /// - `Ok(())` if the index is successfully set
1229 /// - `Err(DispatchError)` if the operation fails due to invalid data or conflicts
1230 fn set_index(
1231 who: &Proprietor,
1232 reason: &Self::Reason,
1233 index: &Self::Index,
1234 digest: &Self::Digest,
1235 ) -> DispatchResult;
1236
1237 /// Updates the shares of a single entry within an existing index.
1238 ///
1239 /// Since indexes are immutable once created, this method internally:
1240 /// 1. Prepares a new index with the updated share for the specified entry
1241 /// 2. Generates a new digest for the modified index
1242 /// 3. Returns the new index digest
1243 ///
1244 /// Since no explicit entry-removal methods are provided, this method may be
1245 /// used to remove an entry when its share is set to zero.
1246 ///
1247 /// By invariant, commitment indexes must not contain entries with zero shares.
1248 ///
1249 /// For updating multiple entries, use [`CommitIndex::prepare_index`] and
1250 /// [`CommitIndex::set_index`] to create a completely new index structure.
1251 ///
1252 /// ## Returns
1253 /// - `Ok(Digest)` containing the new index digest after share update
1254 /// - `Err(DispatchError)` if the index or entry does not exist, or update fails
1255 fn set_entry_shares(
1256 who: &Proprietor,
1257 reason: &Self::Reason,
1258 index_of: &Self::Digest,
1259 entry_of: &Self::Digest,
1260 shares: Self::Shares,
1261 ) -> Result<Self::Digest, DispatchError>;
1262
1263 /// Removes an index if it contains no active commitments.
1264 ///
1265 /// Permanently deletes the specified index digest under the given reason,
1266 /// freeing associated resources. Indexes can only be reaped when they have
1267 /// no committed entries or remaining balances.
1268 ///
1269 /// ## Returns
1270 /// - `Ok(())` if the index is successfully removed
1271 /// - `Err(DispatchError)` if the index does not exist or contains active commitments
1272 fn reap_index(reason: &Self::Reason, index_of: &Self::Digest) -> DispatchResult;
1273
1274 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1275 // ```````````````````````````````````` HOOKS ````````````````````````````````````
1276 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1277
1278 /// Hook called after an index is created and its digest is set by a proprietor.
1279 ///
1280 /// Provides an extension point for triggering side-effects such as events,
1281 /// logging, recalculations, or external state updates when a new index is
1282 /// established.
1283 ///
1284 /// Default implementation is a no-op.
1285 fn on_set_index(
1286 _who: &Proprietor,
1287 _index_of: &Self::Digest,
1288 _reason: &Self::Reason,
1289 _index: &Self::Index,
1290 ) {
1291 }
1292
1293 /// Hook called after an index is reaped (removed).
1294 ///
1295 /// Provides an extension point for cleanup tasks, logging, freeing
1296 /// related resources, or triggering external notifications when an index
1297 /// is permanently removed from the system.
1298 ///
1299 /// `dust` represents any residual value that was unclaimable and
1300 /// effectively considered dead.
1301 ///
1302 /// Default implementation is a no-op.
1303 fn on_reap_index(_index_of: &Self::Digest, _reason: &Self::Reason, _dust: Self::Asset) {}
1304}
1305
1306// ===============================================================================
1307// ````````````````````````````````` COMMIT POOL `````````````````````````````````
1308// ===============================================================================
1309
1310/// `CommitPool` extends [`Commitment`] and [`CommitIndex`] to provide a **managed,
1311/// dynamic commitment abstraction**.
1312///
1313/// It enables a trusted manager to actively manage where deposited assets are committed
1314/// - effectively controlling how and where value is allocated across different digests
1315/// within a pool.
1316///
1317/// While `CommitIndex` aggregates multiple commitments under a single digest,
1318/// `CommitPool` introduces **live management**:
1319/// - Proprietors deposit assets into a managed pool.
1320/// - The pool manager determines how these assets are allocated across underlying
1321/// commitments to optimize yield, diversify risk, or execute specific strategies.
1322/// - Proprietors trust the manager to act within agreed rules and immutable commission terms.
1323///
1324/// In short, Pool digests are stable identities; index digests are content hashes.
1325///
1326/// ## Purpose
1327///
1328/// Pools are useful for:
1329/// - Managed staking or investment
1330/// - Delegated liquidity provision
1331/// - Escrow pools with oversight
1332/// - Collective asset management with dynamic strategy
1333///
1334/// They preserve the atomicity and immutability of individual commitments while enabling
1335/// flexible allocation.
1336///
1337/// ## Core Principles
1338///
1339/// 1. **Managed aggregation**
1340/// - Pools group commitments under a single digest for a given reason.
1341/// - Slots remain immutable commitments.
1342///
1343/// 2. **Dynamic shares**
1344/// - Managers adjust shares to change exposure or strategy.
1345/// - Share changes require releasing and recovering the pool.
1346///
1347/// 3. **Semi-trusted management**
1348/// - Managers cannot withdraw deposits directly.
1349/// - Operations are restricted by pool rules and fixed commission rates.
1350///
1351/// 4. **Immutable commission**
1352/// - Commission rates are fixed at pool creation for transparency and trust.
1353///
1354/// ## Examples
1355///
1356/// ### Example 1: Managed Staking Pool
1357///
1358/// Manager Dave creates a managed pool:
1359/// - Reason = `"managed_staking"`
1360/// - Digest = `"dave_pool"`
1361/// - Entries = `[digest_a123, digest_b456, digest_c789]`
1362/// - Shares = `[1, 2, 3]`
1363/// - Commission = `2%`
1364///
1365/// Alice, Bob, and Carol deposit tokens into a staking pool managed by Dave:
1366///
1367/// | Proprietor | Reason | Digest | Value |
1368/// |------------|-----------|---------------|-------|
1369/// | Alice | staking | dave_pool | 100 |
1370/// | Bob | staking | dave_pool | 200 |
1371/// | Carol | staking | dave_pool | 300 |
1372///
1373/// The pool holds a total value of `600`, managed dynamically without altering deposits.
1374///
1375/// Dave can rebalance the pool or add new staking positions (new entry digests)
1376/// as opportunities arise, while depositors retain proportional ownership.
1377///
1378/// This makes `CommitPool` distinct from `CommitIndex`,
1379/// which has **static entries and immutable share structures**.
1380///
1381/// #### Dynamic Reallocation and Expansion
1382///
1383/// After creation, the manager can **update both shares and entries**:
1384///
1385/// - Before:
1386/// - Entries = `[digest_a123, digest_b456, digest_c789]`
1387/// - Shares = `[1, 2, 3]`
1388///
1389/// - After:
1390/// - Entries = `[digest_a123, digest_b456, digest_c789, digest_d999]`
1391/// - Shares = `[2, 1, 4, 2]`
1392///
1393/// This allows the pool to grow by adding new slots (digests)
1394/// and to rebalance proportional exposure.
1395/// Proprietors can view current allocations at any time,
1396/// but allocations and entries may change as part of active management.
1397///
1398/// ### Example 2: Managed Liquidity Pool
1399///
1400/// Manager creates:
1401/// - Reason = `"liquidity"`
1402/// - Digest = `"pool_digest_lp"`
1403/// - Entries = `[digest_lp_001, digest_lp_002]`
1404/// - Shares = `[5, 7]`
1405/// - Commission = `1%`
1406///
1407/// The pool manager may later add new liquidity positions (e.g., `digest_lp_003`)
1408/// or adjust existing shares to respond to market demand.
1409/// Depositors continue to share in the aggregated performance of the evolving pool.
1410///
1411/// ### Example 3: Escrow Pool with Commission
1412///
1413/// Carol and Bob deposit into a managed escrow pool:
1414///
1415/// | Proprietor | Reason | Digest | Value |
1416/// |------------|---------------|---------------------|-------|
1417/// | Carol | escrow | pool_digest_escrow | 1000 |
1418/// | Bob | escrow | pool_digest_escrow | 1500 |
1419///
1420/// Manager creates:
1421/// - Reason = `"escrow"`
1422/// - Digest = `"pool_digest_escrow"`
1423/// - Shares = `[1, 1.5]`
1424/// - Commission = `0.5%`
1425///
1426/// The manager can later add new escrow digests or rebalance shares
1427/// to adjust allocations, while commission is paid automatically upon resolution.
1428///
1429/// ### Example 4: Delegated Investment Pool
1430///
1431/// Investors deposit:
1432///
1433/// | Proprietor | Reason | Digest | Value |
1434/// |------------|------------------|----------------|-------|
1435/// | A | investment | inv_pool | 1000 |
1436/// | B | investment | inv_pool | 2000 |
1437///
1438/// Manager creates:
1439/// - Reason = `"investment"`
1440/// - Digest = `"inv_pool"`
1441/// - Shares = `[1, 2]`
1442/// - Commission = `1.5%`
1443///
1444/// After creation:
1445/// - The manager may add new investment digests or update existing ones.
1446/// - Proprietors can see current allocations, but these can change over time.
1447///
1448/// ## Invariants
1449///
1450/// - A pool digest wraps multiple committed entries.
1451/// - Pools are created from indexes.
1452/// - Mutations require full release and recovering.
1453/// - Managers cannot withdraw deposits, only adjust shares.
1454/// - Commission rate is fixed at creation.
1455/// - Commission is paid upon commitment resolution.
1456/// - Share adjustments must preserve proportional commitments.
1457///
1458/// ## Summary
1459///
1460/// `CommitPool` enables managed aggregation of commitments with dynamic share adjustment,
1461/// preserving safety, immutability, and fixed commission rates.
1462///
1463/// Ideal for managed staking, liquidity provision, delegated investment, escrow pools,
1464/// and collective asset management where live management is required.
1465///
1466/// Generics:
1467/// - **Proprietor** - the entity that owns the asset and can make commitments.
1468pub trait CommitPool<Proprietor>: Commitment<Proprietor> + CommitIndex<Proprietor> {
1469 /// The type representing a managed pool.
1470 ///
1471 /// Typically a struct that includes:
1472 /// - The pool's manager (of type `Proprietor`)
1473 /// - A list of slot digests and their shares
1474 /// - The commission rate
1475 /// - Metadata
1476 type Pool: Elastic + Storable;
1477
1478 /// The type representing a pool's commission.
1479 ///
1480 /// Can be a numeric percentage, ratio, or more complex type
1481 /// encoding the manager's commission or performance fee model.
1482 type Commission: Percentage;
1483
1484 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1485 // ``````````````````````````````````` CHECKERS ``````````````````````````````````
1486 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1487
1488 /// Checks whether a pool exists for the given reason and pool digest.
1489 ///
1490 /// ## Returns
1491 /// - `Ok(())` if the pool exists
1492 /// - `Err(DispatchError)` if the pool is not found
1493 fn pool_exists(reason: &Self::Reason, pool_of: &Self::Digest) -> DispatchResult;
1494
1495 /// Checks whether a specific slot exists within the given pool.
1496 ///
1497 /// Verifies that the specified slot digest is part of the pool's
1498 /// slot list under the given reason.
1499 ///
1500 /// ## Returns
1501 /// - `Ok(())` if the slot exists within the pool
1502 /// - `Err(DispatchError)` if the slot is not found in the pool
1503 fn slot_exists(
1504 reason: &Self::Reason,
1505 pool_of: &Self::Digest,
1506 slot_of: &Self::Digest,
1507 ) -> DispatchResult;
1508
1509 /// Checks whether any pool exists for the given reason.
1510 ///
1511 /// Verifies that at least one pool has been created under the
1512 /// specified reason across all managers.
1513 ///
1514 /// ## Returns
1515 /// - `Ok(())` if at least one pool exists for the reason
1516 /// - `Err(DispatchError)` if no pools are found for the reason
1517 fn has_pool(reason: &Self::Reason) -> DispatchResult;
1518
1519 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1520 // ``````````````````````````````````` GETTERS ```````````````````````````````````
1521 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1522
1523 /// Retrieves the manager (controller) of the specified pool.
1524 ///
1525 /// Returns the proprietor who is responsible for managing the pool's
1526 /// allocations, slot configurations, and commission distribution.
1527 ///
1528 /// ## Returns
1529 /// - `Ok(Proprietor)` containing the pool's manager
1530 /// - `Err(DispatchError)` if the pool does not exist
1531 fn get_manager(
1532 reason: &Self::Reason,
1533 pool_of: &Self::Digest,
1534 ) -> Result<Proprietor, DispatchError>;
1535
1536 /// Retrieves the commission rate associated with the specified pool.
1537 ///
1538 /// Returns the fixed commission structure that determines what portion
1539 /// of the pool's yield or value the manager is entitled to receive.
1540 ///
1541 /// ## Returns
1542 /// - `Ok(Commission)` containing the pool's commission structure
1543 /// - `Err(DispatchError)` if the pool does not exist
1544 fn get_commission(
1545 reason: &Self::Reason,
1546 pool_of: &Self::Digest,
1547 ) -> Result<Self::Commission, DispatchError>;
1548
1549 /// Retrieves the complete pool structure for the given reason and pool digest.
1550 ///
1551 /// Returns the full pool object containing all slots, shares, manager identity,
1552 /// commission details, and associated metadata for inspection or processing.
1553 ///
1554 /// ## Returns
1555 /// - `Ok(Pool)` containing the complete pool structure
1556 /// - `Err(DispatchError)` if the pool does not exist
1557 fn get_pool(reason: &Self::Reason, pool_of: &Self::Digest)
1558 -> Result<Self::Pool, DispatchError>;
1559
1560 /// Retrieves the real-time aggregated value of the specified pool.
1561 ///
1562 /// Computes the total value by summing the current values of all slot digests
1563 /// under the pool. Since the supertrait [`Commitment`] allows digest values
1564 /// to be updated via [`Commitment::set_digest_value`], this reflects the
1565 /// **live state** rather than historical deposits.
1566 ///
1567 /// The aggregated value represents the total exposure of all depositors
1568 /// across all slots managed by the pool.
1569 ///
1570 /// ## Returns
1571 /// - `Ok(Asset)` containing the total aggregated value of the pool
1572 /// - `Err(DispatchError)` if the pool does not exist or value computation fails
1573 fn get_pool_value(
1574 reason: &Self::Reason,
1575 pool_of: &Self::Digest,
1576 ) -> Result<Self::Asset, DispatchError> {
1577 Self::pool_exists(reason, pool_of)?;
1578 let slots_values = Self::get_slots_value(reason, pool_of)?;
1579 let mut value: <Self as InspectAsset<Proprietor>>::Asset = Self::Asset::zero();
1580 for (_, val) in slots_values {
1581 value = value.saturating_add(val);
1582 }
1583 Ok(value)
1584 }
1585
1586 /// Retrieves the shares of all slots within the specified pool.
1587 ///
1588 /// Returns a list of slot digests paired with their corresponding shares,
1589 /// representing each slot's proportional weight within the pool's
1590 /// managed allocation strategy.
1591 ///
1592 /// ## Returns
1593 /// - `Ok(Vec<(Self::Digest, Self::Shares)>)` containing slot-share pairs
1594 /// - `Err(DispatchError)` if the pool does not exist or retrieval fails
1595 fn get_slots_shares(
1596 reason: &Self::Reason,
1597 pool_of: &Self::Digest,
1598 ) -> Result<Vec<(Self::Digest, Self::Shares)>, DispatchError>;
1599
1600 /// Retrieves the real-time values of all slots within the specified pool.
1601 ///
1602 /// Each slot's value is fetched individually and reflects any changes since
1603 /// the commitment was created, as the supertrait [`Commitment`] allows digest
1604 /// values to be updated via [`Commitment::set_digest_value`].
1605 ///
1606 /// Returns a list of slot digests paired with their current committed values,
1607 /// representing the live state of the pool's allocation.
1608 ///
1609 /// ## Returns
1610 /// - `Ok(Vec<(Self::Digest, Self::Asset)>)` containing slot-value pairs
1611 /// - `Err(DispatchError)` if the pool does not exist or value retrieval fails
1612 fn get_slots_value(
1613 reason: &Self::Reason,
1614 pool_of: &Self::Digest,
1615 ) -> Result<Vec<(Self::Digest, Self::Asset)>, DispatchError> {
1616 Self::pool_exists(reason, pool_of)?;
1617 let slots = Self::get_slots_shares(reason, pool_of)?;
1618 let mut vec = Vec::new();
1619 for (slot_of, _) in slots {
1620 let value = Self::get_slot_value(reason, pool_of, &slot_of)?;
1621 vec.push((slot_of, value))
1622 }
1623 Ok(vec)
1624 }
1625
1626 /// Retrieves the real-time committed value of a specific slot within a pool.
1627 ///
1628 /// Returns the current value for the given slot digest under the specified
1629 /// reason and pool. Since the supertrait [`Commitment`] allows digest values
1630 /// to be updated via [`Commitment::set_digest_value`], this reflects the
1631 /// **live state** rather than the original allocation.
1632 ///
1633 /// If no funds are available in a valid slot, it is expected to return zero.
1634 ///
1635 /// ## Returns
1636 /// - `Ok(Asset)` containing the slot's current committed value
1637 /// - `Err(DispatchError)` if the pool or slot does not exist
1638 fn get_slot_value(
1639 reason: &Self::Reason,
1640 pool_of: &Self::Digest,
1641 slot_of: &Self::Digest,
1642 ) -> Result<Self::Asset, DispatchError>;
1643
1644 /// Retrieves the real-time values of a proprietor's commitments to all slots
1645 /// within the specified pool.
1646 ///
1647 /// Each slot's value is computed individually for the given proprietor and
1648 /// reflects any changes since commitment, as the supertrait [`Commitment`]
1649 /// allows digest values to be updated via [`Commitment::set_digest_value`].
1650 ///
1651 /// Returns a list of slot digests paired with their current committed values
1652 /// for the specified proprietor, showing their proportional exposure across
1653 /// the pool's managed allocation.
1654 ///
1655 /// ### Returns
1656 /// - `Ok(Vec<(Self::Digest, Self::Asset)>)` containing slot-value pairs for the proprietor
1657 /// - `Err(DispatchError)` if the pool does not exist or value retrieval fails
1658 fn get_slots_value_for(
1659 who: &Proprietor,
1660 reason: &Self::Reason,
1661 pool_of: &Self::Digest,
1662 ) -> Result<Vec<(Self::Digest, Self::Asset)>, DispatchError> {
1663 Self::pool_exists(reason, pool_of)?;
1664 let slots = Self::get_slots_shares(reason, pool_of)?;
1665 let mut vec = Vec::new();
1666 for (slot_of, _) in slots {
1667 let value = Self::get_slot_value_for(who, reason, pool_of, &slot_of)?;
1668 vec.push((slot_of, value))
1669 }
1670 Ok(vec)
1671 }
1672
1673 /// Retrieves the real-time value of a proprietor's commitment to a specific
1674 /// slot within a pool.
1675 ///
1676 /// Returns the live committed value for the given proprietor and slot digest.
1677 /// Since the supertrait [`Commitment`] allows digest values to be updated via
1678 /// [`Commitment::set_digest_value`], this reflects the **current state** rather
1679 /// than the deposited total.
1680 ///
1681 /// ### Returns
1682 /// - `Ok(Asset)` containing the proprietor's current commitment to the slot
1683 /// - `Err(DispatchError)` if the pool or slot does not exist
1684 fn get_slot_value_for(
1685 who: &Proprietor,
1686 reason: &Self::Reason,
1687 pool_of: &Self::Digest,
1688 slot_of: &Self::Digest,
1689 ) -> Result<Self::Asset, DispatchError>;
1690
1691 /// Retrieves the real-time total value of a proprietor's commitment to a pool.
1692 ///
1693 /// Aggregates the live committed values of all slot digests within the pool
1694 /// for the given proprietor. Since the supertrait [`Commitment`] allows digest
1695 /// values to be updated via [`Commitment::set_digest_value`], this reflects
1696 /// the **current total** rather than historical deposits.
1697 ///
1698 /// Represents the proprietor's total exposure to the pool's managed allocation
1699 /// strategy.
1700 ///
1701 /// ### Returns
1702 /// - `Ok(Asset)` containing the proprietor's total commitment to the pool
1703 /// - `Err(DispatchError)` if the pool does not exist or computation fails
1704 fn get_pool_value_for(
1705 who: &Proprietor,
1706 reason: &Self::Reason,
1707 pool_of: &Self::Digest,
1708 ) -> Result<Self::Asset, DispatchError> {
1709 Self::pool_exists(reason, pool_of)?;
1710 let mut value = Self::Asset::zero();
1711 let slots = Self::get_slots_value_for(who, reason, pool_of)?;
1712 for (_, val) in slots {
1713 value = value.saturating_add(val)
1714 }
1715 Ok(value)
1716 }
1717
1718 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1719 // ````````````````````````````````` CONSTRUCTORS ````````````````````````````````
1720 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1721
1722 /// Generates a unique digest that serves as the identifier for a pool configuration.
1723 ///
1724 /// Creates a collision-resistant identifier derived from:
1725 /// - The manager's identity
1726 /// - The reason (category/purpose)
1727 /// - The underlying index digest
1728 /// - The commission structure
1729 ///
1730 /// The generated digest acts as a **globally unique identifier** across all pools,
1731 /// ensuring that even similar configurations or identical indexes result in
1732 /// distinct pool identities when managed by different entities or with different
1733 /// commission rates.
1734 ///
1735 /// ## Returns
1736 /// - `Ok(Digest)` containing the generated unique pool digest
1737 /// - `Err(DispatchError)` if digest generation fails due to invalid inputs
1738 fn gen_pool_digest(
1739 manager: &Proprietor,
1740 reason: &Self::Reason,
1741 index_of: &Self::Digest,
1742 commission: Self::Commission,
1743 ) -> Result<Self::Digest, DispatchError>;
1744
1745 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1746 // ``````````````````````````````````` MUTATORS ``````````````````````````````````
1747 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1748
1749 /// Creates a managed pool from an existing index with a fixed commission rate.
1750 ///
1751 /// Links the pool to an existing index structure under the specified reason
1752 /// and pool digest, establishing a managed aggregation layer with dynamic
1753 /// allocation capabilities.
1754 ///
1755 /// This method:
1756 /// - Validates that the base index exists and is accessible
1757 /// - Ensures the pool digest is unique and available
1758 /// - Records the manager (who) and immutable commission rate
1759 /// - Enables trustless participation for depositors who rely on the index structure
1760 ///
1761 /// **Pools can be created from any valid index**, regardless of who created it,
1762 /// enabling permissionless pool creation while maintaining security.
1763 ///
1764 /// Once created, the pool introduces **managed control and commission logic** over
1765 /// the linked index, allowing the manager to dynamically update slots, adjust slot shares
1766 /// and allocations.
1767 ///
1768 /// ## Returns
1769 /// - `Ok(())` if the pool is successfully created
1770 /// - `Err(DispatchError)` if the index does not exist, pool digest conflicts, or validation fails
1771 fn set_pool(
1772 who: &Proprietor,
1773 reason: &Self::Reason,
1774 pool_of: &Self::Digest,
1775 index_of: &Self::Digest,
1776 commission: Self::Commission,
1777 ) -> DispatchResult;
1778
1779 /// Assigns or updates the manager of an existing pool.
1780 ///
1781 /// Transfers management control of the pool to a new proprietor, who then
1782 /// assumes responsibility for slot configuration, share adjustments, and commission control.
1783 ///
1784 /// This operation preserves all existing commitments, shares, and commission
1785 /// structures while changing only the entity authorized to manage the pool.
1786 ///
1787 /// ### Returns
1788 /// - `Ok(())` if the manager is successfully updated
1789 /// - `Err(DispatchError)` if the pool does not exist or authorization fails
1790 fn set_pool_manager(
1791 reason: &Self::Reason,
1792 pool_of: &Self::Digest,
1793 manager: &Proprietor,
1794 ) -> DispatchResult;
1795
1796 /// Updates the shares of a specific slot within an existing pool.
1797 ///
1798 /// Allows the pool manager to dynamically reallocate exposure between slots
1799 /// without recreating the entire pool structure. This enables responsive
1800 /// strategy adjustments based on market conditions, performance metrics,
1801 /// or risk management requirements.
1802 ///
1803 /// The operation preserves all existing commitments while adjusting the
1804 /// proportional weight of the specified slot within the pool's allocation.
1805 ///
1806 /// ### Returns
1807 /// - `Ok(())` if the slot shares are successfully updated
1808 /// - `Err(DispatchError)` if the pool or slot does not exist, or authorization fails
1809 fn set_slot_shares(
1810 who: &Proprietor,
1811 reason: &Self::Reason,
1812 pool_of: &Self::Digest,
1813 slot_of: &Self::Digest,
1814 shares: Self::Shares,
1815 ) -> DispatchResult;
1816
1817 /// Creates a new pool digest from an index with a specified commission rate.
1818 ///
1819 /// Generates a unique pool identifier by combining the index structure with
1820 /// a commission rate, enabling multiple pools with different commission
1821 /// structures to be created from the same underlying index.
1822 ///
1823 /// Since commission rates are **immutable** after pool creation to protect
1824 /// depositors, this method allows participants to create or select pools
1825 /// with their preferred commission terms without altering existing pools.
1826 ///
1827 /// The generated digest serves as the unique identifier for the new pool
1828 /// configuration and can be used for subsequent pool operations.
1829 ///
1830 /// ## Returns
1831 /// - `Ok(Digest)` containing the newly generated pool digest
1832 /// - `Err(DispatchError)` if pool creation fails due to invalid parameters or conflicts
1833 fn set_commission(
1834 who: &Proprietor,
1835 reason: &Self::Reason,
1836 index_of: &Self::Digest,
1837 commission: Self::Commission,
1838 ) -> Result<Self::Digest, DispatchError> {
1839 let pool_of = Self::gen_pool_digest(who, reason, index_of, commission)?;
1840 Self::set_pool(who, reason, &pool_of, index_of, commission)?;
1841 Ok(pool_of)
1842 }
1843
1844 /// Removes a pool if it contains no active commitments.
1845 ///
1846 /// Permanently deletes the specified pool digest under the given reason,
1847 /// freeing associated resources and references. Pools can only be reaped when
1848 /// **no proprietors have active commitments** to any of its slots.
1849 ///
1850 /// Attempting to reap a non-empty pool must return an error.
1851 ///
1852 /// ## Returns
1853 /// - `Ok(())` if the pool is successfully removed
1854 /// - `Err(DispatchError)` if the pool does not exist or contains active commitments
1855 fn reap_pool(reason: &Self::Reason, pool_of: &Self::Digest) -> DispatchResult;
1856
1857 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1858 // ```````````````````````````````````` HOOKS ````````````````````````````````````
1859 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1860
1861 /// Hook called after a pool is successfully created by a proprietor (assumed to be
1862 /// the current pool-manager).
1863 ///
1864 /// Provides an extension point for triggering side-effects such as events,
1865 /// logging, recalculations, or external state updates when a pool is
1866 /// established or modified.
1867 ///
1868 /// Default implementation is a no-op.
1869 fn on_set_pool(
1870 _who: &Proprietor,
1871 _pool_of: &Self::Digest,
1872 _reason: &Self::Reason,
1873 _pool: &Self::Pool,
1874 ) {
1875 }
1876
1877 /// Hook called after slot shares are updated within a pool.
1878 ///
1879 /// Provides an extension point for triggering recalculations, propagating
1880 /// changes to related state, or emitting events when a slot's shares are
1881 /// adjusted as part of the pool's dynamic allocation strategy.
1882 ///
1883 /// Default implementation is a no-op.
1884 fn on_set_slot_shares(
1885 _pool_of: &Self::Digest,
1886 _reason: &Self::Reason,
1887 _slot_of: &Self::Digest,
1888 _shares: Self::Shares,
1889 ) {
1890 }
1891
1892 /// Hook called after a pool's manager is changed.
1893 ///
1894 /// Provides an extension point for triggering governance events, audit logging,
1895 /// or external notifications when management control of a pool is transferred
1896 /// to a new proprietor.
1897 ///
1898 /// Default implementation is a no-op.
1899 fn on_set_manager(_pool_of: &Self::Digest, _reason: &Self::Reason, _manager: &Proprietor) {}
1900
1901 /// Hook called after a pool is reaped (removed).
1902 ///
1903 /// Provides an extension point for cleanup tasks, audit logging, freeing
1904 /// related resources, or triggering external notifications when a pool
1905 /// is permanently removed from the system.
1906 ///
1907 /// `dust` represents any residual value that was unclaimable and
1908 /// effectively considered dead.
1909 ///
1910 /// Default implementation is a no-op.
1911 fn on_reap_pool(_pool_of: &Self::Digest, _reason: &Self::Reason, _dust: Self::Asset) {}
1912}
1913
1914// ===============================================================================
1915// ``````````````````````````````` COMMIT VARIANT ````````````````````````````````
1916// ===============================================================================
1917
1918/// `CommitVariant` extends [`Commitment`] by introducing **variant-based commitments** -
1919/// allowing each commitment to represent a specific *position* or *type*,
1920/// such as positive/negative, long/short, or other directional states.
1921///
1922/// This abstraction is useful in financial systems where commitments
1923/// can have opposing or complementary meanings (e.g., bets, trades, or hedges),
1924/// and where such **variants** must be consistently classified and tracked
1925/// under the same digest or reason.
1926///
1927/// ## Purpose
1928///
1929/// In standard [`Commitment`], each digest represents a single locked or committed value.
1930/// However, many financial instruments carry a **semantic variant**, such as:
1931/// - Positive / Negative exposure
1932/// - Long / Short position
1933/// - Buy / Sell order
1934/// - For / Against vote
1935///
1936/// `CommitVariant` provides an abstract way to encode and manage such distinctions
1937/// without altering the base commitment model.
1938///
1939/// ## Core Principles
1940///
1941/// 1. **Variant as classification**
1942/// - Each commitment may declare a variant indicating its nature or direction.
1943/// - Variants are accounted for directly under the commitment's digest.
1944///
1945/// 2. **Digest-level association**
1946/// - A commitment's digest uniquely identifies both its value and variant.
1947/// - Variants must be recorded or derivable from the digest state.
1948///
1949/// 3. **Financial semantics**
1950/// - Designed for use in scenarios like betting, derivatives, prediction markets,
1951/// and financial trilemmas where commitments can oppose or complement each other.
1952///
1953/// ## Example Use Cases
1954///
1955/// - **Betting systems**: Represent "for" and "against" commitments.
1956/// - **Trading platforms**: Track "long" and "short" positions.
1957/// - **Hedging mechanisms**: Manage opposing commitments under one reason.
1958/// - **Voting protocols**: Represent affirmative and negative stake commitments.
1959///
1960/// ## Summary
1961///
1962/// `CommitVariant` offers an abstract and extensible layer over [`Commitment`],
1963/// enabling richer semantics and directionality (positive/negative or other variants)
1964/// to be embedded into financial commitment logic in a consistent, trustless manner.
1965///
1966/// Generics:
1967/// - **Proprietor** - the entity that owns the asset and can make commitments.
1968pub trait CommitVariant<Proprietor>: Commitment<Proprietor> {
1969 /// The type representing a commitment's position or variant.
1970 type Position: RuntimeEnum + Delimited;
1971
1972 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1973 // ``````````````````````````````````` CHECKERS ``````````````````````````````````
1974 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1975
1976 /// Validates whether a specific digest variant's value can be set or updated.
1977 ///
1978 /// Ensures that:
1979 /// - The variant is initialized (has a balance entry)
1980 /// - The intended update respects mint/reap advisory limits
1981 ///
1982 /// ## Returns
1983 /// - `Ok(())` if validation succeeds
1984 /// - `Err(DispatchError)` if the digest/variant is invalid or limits are violated
1985 fn can_set_digest_variant_value(
1986 reason: &Self::Reason,
1987 digest: &Self::Digest,
1988 value: Self::Asset,
1989 variant: &Self::Position,
1990 qualifier: &Self::Intent,
1991 ) -> DispatchResult {
1992 let current = Self::get_digest_variant_value(reason, digest, variant)?;
1993 match current.cmp(&value) {
1994 Ordering::Less => {
1995 // Mint path (increase)
1996 let limits = Self::digest_mint_limits(digest, reason, qualifier)?;
1997 let mintable = value.saturating_sub(current);
1998 ensure!(
1999 limits.contains(mintable),
2000 Self::from_commit_error(CommitError::MintingOffLimits)
2001 )
2002 }
2003 Ordering::Greater => {
2004 // Reap path (decrease)
2005 let limits = Self::digest_reap_limits(digest, reason, qualifier)?;
2006 let reapable = current.saturating_sub(value);
2007 ensure!(
2008 limits.contains(reapable),
2009 Self::from_commit_error(CommitError::ReapingOffLimits)
2010 )
2011 }
2012 Ordering::Equal => {
2013 // No-op
2014 }
2015 }
2016
2017 Ok(())
2018 }
2019
2020 /// Validates whether a new commitment can be placed for a specific variant.
2021 ///
2022 /// Ensures that no existing commitment exists for the given reason (enforcing
2023 /// the "one digest per reason" invariant) and that the proprietor has sufficient
2024 /// available funds to cover the commitment value.
2025 ///
2026 /// Additionally validates that the value satisfies variant-specific placement
2027 /// limits derived from the lazy balance model.
2028 ///
2029 /// ## Returns
2030 /// - `Ok(())` if validation succeeds
2031 /// - `Err(DispatchError)` if a commitment already exists, insufficient funds are available,
2032 /// or the value violates variant-specific limits
2033 fn can_place_commit_of_variant(
2034 who: &Proprietor,
2035 reason: &Self::Reason,
2036 digest: &Self::Digest,
2037 variant: &Self::Position,
2038 value: Self::Asset,
2039 qualifier: &Self::Intent,
2040 ) -> DispatchResult {
2041 ensure!(
2042 Self::commit_exists(who, reason).is_err(),
2043 Self::from_commit_error(CommitError::CommitAlreadyExists)
2044 );
2045 let max = Self::available_funds(who);
2046 ensure!(
2047 max >= value,
2048 Self::from_commit_error(CommitError::InsufficientFunds)
2049 );
2050 let limits = Self::place_commit_limits_of_variant(who, reason, digest, variant, qualifier)?;
2051 ensure!(
2052 limits.contains(value),
2053 Self::from_commit_error(CommitError::PlacingOffLimits)
2054 );
2055
2056 Ok(())
2057 }
2058
2059 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2060 // ``````````````````````````````````` GETTERS ```````````````````````````````````
2061 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2062
2063 /// Retrieves the position/variant of a proprietor's commitment for
2064 /// the given reason.
2065 ///
2066 /// Returns the active variant under which the proprietor's commitment
2067 /// is currently classified. Since each proprietor can have only **one
2068 /// commitment per reason** (base trait invariant), they also have only
2069 /// **one active variant per reason**.
2070 ///
2071 /// ## Returns
2072 /// - `Ok(Position)` containing the commitment's current variant
2073 /// - `Err(DispatchError)` if no commitment exists for the reason
2074 fn get_commit_variant(
2075 who: &Proprietor,
2076 reason: &Self::Reason,
2077 ) -> Result<Self::Position, DispatchError>;
2078
2079 /// Retrieves the aggregated value for a specific digest and variant combination.
2080 ///
2081 /// Returns the real-time sum of all commitments to the given digest that are
2082 /// classified under the specified variant. This enables querying variant-specific
2083 /// exposure across all proprietors.
2084 ///
2085 /// When both [`Commitment`] and `CommitVariant` are implemented:
2086 /// - [`Commitment::get_digest_value`] returns the aggregate across **all variants**
2087 /// - This method returns the aggregate for **a specific variant**
2088 ///
2089 /// ## Returns
2090 /// - `Ok(Asset)` containing the variant-specific aggregated value
2091 /// - `Err(DispatchError)` if the digest or variant does not exist
2092 fn get_digest_variant_value(
2093 reason: &Self::Reason,
2094 digest: &Self::Digest,
2095 variant: &Self::Position,
2096 ) -> Result<Self::Asset, DispatchError>;
2097
2098 /// Provides advisory bounds for increasing (minting) a digest's
2099 /// specific variant-balance.
2100 ///
2101 /// Acts as an optional guard to prevent abrupt or excessive growth,
2102 /// complementing core digest update logic.
2103 fn digest_mint_limits_of_variant(
2104 _digest: &Self::Digest,
2105 _reason: &Self::Reason,
2106 _variant: &Self::Position,
2107 _qualifier: &Self::Intent,
2108 ) -> Result<Self::Limits, DispatchError> {
2109 // By default, this returns a baseline limit,
2110 // representing a static or fallback bound when no dynamic limits are applied.
2111 //
2112 // Implementors may override this to provide context-specific limits.
2113 Ok(Self::Limits::none())
2114 }
2115
2116 /// Provides advisory bounds for decreasing (reaping) a digest's
2117 /// specific variant-balance.
2118 ///
2119 /// Acts as an optional guard to prevent aggressive or premature reductions,
2120 /// complementing core invariants that ensure correctness.
2121 fn digest_reap_limits_of_variant(
2122 _digest: &Self::Digest,
2123 _reason: &Self::Reason,
2124 _variant: &Self::Position,
2125 _qualifier: &Self::Intent,
2126 ) -> Result<Self::Limits, DispatchError> {
2127 // By default, this returns a baseline limit,
2128 // representing a static or fallback bound when no dynamic limits are applied.
2129 // Implementors may override this to provide context-specific limits.
2130 Ok(Self::Limits::none())
2131 }
2132
2133 /// Provides advisory bounds for placing a new commitment
2134 /// under a given variant.
2135 ///
2136 /// Acts as an optional pre-validation layer to prevent premature or
2137 /// undesirable commits by constraining value ranges.
2138 fn place_commit_limits_of_variant(
2139 _who: &Proprietor,
2140 _reason: &Self::Reason,
2141 _digest: &Self::Digest,
2142 _variant: &Self::Position,
2143 _qualifier: &Self::Intent,
2144 ) -> Result<Self::Limits, DispatchError> {
2145 // By default, this returns an unbounded extent,
2146 // meaning no additional constraints are applied unless overridden.
2147 //
2148 // Implementors may override this to provide context-specific limits.
2149 Ok(Self::Limits::none())
2150 }
2151
2152 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2153 // ``````````````````````````````````` MUTATORS ``````````````````````````````````
2154 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2155
2156 /// Sets or updates the aggregated value for a specific digest and variant.
2157 ///
2158 /// Updates the value associated with a particular variant of a digest,
2159 /// propagating changes to all commitments tied to that digest-variant pair.
2160 ///
2161 /// When both [`Commitment`] and [`CommitVariant`] are utilized in a system:
2162 /// - [`Commitment::set_digest_value`] may set the **default variant**.
2163 /// - This method updates **the specified variant**.
2164 ///
2165 /// The `qualifier` defines how the update is applied (e.g., exact vs best-effort,
2166 /// forceful vs bounded), and may influence the final value that is actually set.
2167 ///
2168 /// ## Returns
2169 /// - `Ok(Asset)` containing the actual value applied to the specified digest-variant
2170 /// - `Err(DispatchError)` if the digest, variant, or reason is invalid, or update fails
2171 fn set_digest_variant_value(
2172 reason: &Self::Reason,
2173 digest: &Self::Digest,
2174 value: Self::Asset,
2175 variant: &Self::Position,
2176 qualifier: &Self::Intent,
2177 ) -> Result<Self::Asset, DispatchError>;
2178
2179 /// Changes the variant of an existing commitment.
2180 ///
2181 /// Transitions a commitment from its current variant to a new variant while
2182 /// preserving the committed value. Since commitments are immutable, this
2183 /// operation uses the **resolve-and-replace pattern**.
2184 ///
2185 /// Ensures that variant changes maintain consistency with the underlying
2186 /// commitment state and respect the semantics of the commitment system.
2187 ///
2188 /// In case if the variant already exists, the function safely returns.
2189 ///
2190 /// ## Returns
2191 /// - `Ok(())` if the variant is successfully changed
2192 /// - `Err(DispatchError)` if:
2193 /// - No commitment exists for the reason
2194 /// - New variant matches current variant
2195 /// - Commitment resolution or placement fails
2196 fn set_commit_variant(
2197 who: &Proprietor,
2198 reason: &Self::Reason,
2199 variant: &Self::Position,
2200 qualifier: &Self::Intent,
2201 ) -> DispatchResult {
2202 Self::commit_exists(who, reason)?;
2203 let current_variant = Self::get_commit_variant(who, reason)?;
2204 if current_variant == *variant {
2205 return Ok(());
2206 }
2207 let digest = Self::get_commit_digest(who, reason)?;
2208 let re_deposit = Self::resolve_commit(who, reason)?;
2209 Self::place_commit_of_variant(who, reason, &digest, re_deposit, variant, qualifier)?;
2210 Ok(())
2211 }
2212
2213 /// Places a commitment under a specific variant.
2214 ///
2215 /// Creates a new commitment with explicit variant classification, enabling
2216 /// directional or positional semantics from the moment of placement.
2217 ///
2218 /// This extends [`Commitment::place_commit`] by adding variant specification.
2219 /// When both traits are implemented:
2220 /// - [`Commitment::place_commit`] may use a **default variant**
2221 /// - This method allows **explicit variant selection**
2222 ///
2223 /// The method must:
2224 /// - Record the commitment under the specified variant
2225 /// - Associate the variant with the digest at the commitment level
2226 /// - Respect qualifier parameters
2227 /// - Return the actual committed value after any adjustments
2228 ///
2229 /// ## Returns
2230 /// - `Ok(Asset)` containing the actual value committed under the variant
2231 /// - `Err(DispatchError)` if placement fails.
2232 fn place_commit_of_variant(
2233 who: &Proprietor,
2234 reason: &Self::Reason,
2235 digest: &Self::Digest,
2236 value: Self::Asset,
2237 variant: &Self::Position,
2238 qualifier: &Self::Intent,
2239 ) -> Result<Self::Asset, DispatchError>;
2240
2241 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2242 // ```````````````````````````````````` HOOKS ````````````````````````````````````
2243 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2244
2245 /// Hook called after placing a commitment under a specific variant.
2246 ///
2247 /// Provides an extension point for triggering custom side-effects when a new
2248 /// variant-based commitment is placed.
2249 ///
2250 /// Default implementation is a no-op.
2251 fn on_place_commit_on_variant(
2252 _who: &Proprietor,
2253 _reason: &Self::Reason,
2254 _digest: &Self::Digest,
2255 _value: Self::Asset,
2256 _variant: &Self::Position,
2257 ) {
2258 }
2259
2260 /// Hook called after a commitment's variant is changed.
2261 ///
2262 /// Provides an extension point for responding to variant transitions.
2263 /// Receives both the digest and value to enable comprehensive
2264 /// state management and analytics.
2265 ///
2266 /// Default implementation is a no-op.
2267 fn on_set_commit_variant(
2268 _who: &Proprietor,
2269 _reason: &Self::Reason,
2270 _digest: &Self::Digest,
2271 _value: Self::Asset,
2272 _variant: &Self::Position,
2273 ) {
2274 }
2275
2276 /// Hook called after a digest's variant value is updated.
2277 ///
2278 /// Provides an extension point for reacting to variant-specific value changes.
2279 ///
2280 /// Default implementation is a no-op.
2281 fn on_set_digest_variant(
2282 _digest: &Self::Digest,
2283 _reason: &Self::Reason,
2284 _value: Self::Asset,
2285 _variant: &Self::Position,
2286 ) {
2287 }
2288}
2289
2290// ===============================================================================
2291// ``````````````````````````````` INDEX VARIANT `````````````````````````````````
2292// ===============================================================================
2293
2294/// A trait for managing and querying **variants** of index entries and its commitments.
2295///
2296/// Extends [`CommitVariant`] and [`CommitIndex`], allowing indexed commitments
2297/// to carry additional positional/variant metadata.
2298///
2299/// This trait enables tracking, setting, and preparing variant entries in an index context.
2300///
2301/// Generics:
2302/// - **Proprietor** - the entity that owns the asset and can make commitments.
2303pub trait IndexVariant<Proprietor>: CommitVariant<Proprietor> + CommitIndex<Proprietor> {
2304 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2305 // ````````````````````````````````` CONSTRUCTORS ````````````````````````````````
2306 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2307
2308 /// Prepares an index from entry data with explicit variant classification.
2309 ///
2310 /// Constructs a variant-aware index structure from a list of `(Digest, Shares, Position)` tuples,
2311 /// where each tuple represents:
2312 /// - **Digest**: The unique identifier of an entry within the index
2313 /// - **Shares**: The proportional weight or ownership of that entry
2314 /// - **Position**: The variant classification for that entry
2315 ///
2316 /// This extends [`CommitIndex::prepare_index`] by adding variant specification for each entry.
2317 /// When both traits are implemented:
2318 /// - [`CommitIndex::prepare_index`] may assign a **default variant** to all entries
2319 /// - This method allows **explicit variant specification** per entry
2320 ///
2321 /// ## Returns
2322 /// - `Ok(Index)` containing the prepared variant-aware index structure
2323 /// - `Err(DispatchError)` if preparation fails
2324 fn prepare_index_of_variants(
2325 who: &Proprietor,
2326 reason: &Self::Reason,
2327 entries: Vec<(Self::Digest, Self::Shares, Self::Position)>,
2328 ) -> Result<Self::Index, DispatchError>;
2329
2330 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2331 // ``````````````````````````````````` GETTERS ```````````````````````````````````
2332 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2333
2334 /// Retrieves the variant associated with a specific entry within an index.
2335 ///
2336 /// Returns the position classification for the given entry digest under the
2337 /// specified reason and index digest. This enables querying the directional
2338 /// or positional semantics of individual entries within a mixed-variant index.
2339 ///
2340 /// ## Returns
2341 /// - `Ok(Position)` containing the entry's variant
2342 /// - `Err(DispatchError)` if the index or entry does not exist
2343 fn get_entry_variant(
2344 reason: &Self::Reason,
2345 index_of: &Self::Digest,
2346 entry_of: &Self::Digest,
2347 ) -> Result<Self::Position, DispatchError>;
2348
2349 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2350 // ``````````````````````````````````` MUTATORS ``````````````````````````````````
2351 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2352
2353 /// Associates or updates a variant for a specific entry within an index.
2354 ///
2355 /// Records a position (variant) for an entry digest within the context of a given
2356 /// index digest and reason. If shares are provided, they are updated alongside the variant.
2357 ///
2358 /// Since indexes are **immutable** once created, this method internally
2359 /// generates a new digest for the modified index and returns the new index digest.
2360 ///
2361 /// For updating multiple entry variants simultaneously, use
2362 /// [`IndexVariant::prepare_index_of_variants`] to construct a completely new index structure.
2363 ///
2364 /// This behavior partly mirrors [`CommitIndex::set_index`], and can be
2365 /// considered a higher-level wrapper around index reconstruction and binding.
2366 ///
2367 /// As such, it is assumed that any side-effects or lifecycle handling are
2368 /// delegated through the underlying [`CommitIndex::set_index`] call, and
2369 /// therefore this method does **not** introduce a separate hook.
2370 ///
2371 /// ## Returns
2372 /// - `Ok(Digest)` containing the new index digest after variant update
2373 /// - `Err(DispatchError)` if the index or entry does not exist, or update fails
2374 fn set_entry_of_variant(
2375 who: &Proprietor,
2376 reason: &Self::Reason,
2377 index_of: &Self::Digest,
2378 entry_of: &Self::Digest,
2379 variant: Self::Position,
2380 shares: Option<Self::Shares>,
2381 ) -> Result<Self::Digest, DispatchError>;
2382}
2383
2384// ===============================================================================
2385// ```````````````````````````````` POOL VARIANT `````````````````````````````````
2386// ===============================================================================
2387
2388/// A trait for managing and querying **variants** of pool slots and their commitments.
2389///
2390/// Extends [`CommitPool`] to allow pool commitments to carry additional positional
2391/// or variant metadata.
2392///
2393/// Provides the ability to retrieve, set, and react to variant changes within a pool context.
2394///
2395/// Generics:
2396/// - **Proprietor** - the entity that owns the asset and can make commitments.
2397pub trait PoolVariant<Proprietor>:
2398 CommitVariant<Proprietor> + CommitPool<Proprietor> + CommitIndex<Proprietor>
2399{
2400 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2401 // ``````````````````````````````````` GETTERS ```````````````````````````````````
2402 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2403
2404 /// Retrieves the variant (position) associated with a specific slot within a pool.
2405 ///
2406 /// Returns the position classification for the given slot digest under the
2407 /// specified reason and pool digest. This enables querying the directional
2408 /// or positional semantics of individual slots within a managed variant-aware pool.
2409 ///
2410 /// ## Returns
2411 /// - `Ok(Position)` containing the slot's variant
2412 /// - `Err(DispatchError)` if the pool or slot does not exist
2413 fn get_slot_variant(
2414 reason: &Self::Reason,
2415 pool_of: &Self::Digest,
2416 slot_of: &Self::Digest,
2417 ) -> Result<Self::Position, DispatchError>;
2418
2419 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2420 // ``````````````````````````````````` MUTATORS ``````````````````````````````````
2421 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2422
2423 /// Associates or updates a variant for a specific slot within a pool.
2424 ///
2425 /// Records a position (variant) for a slot digest within the context of a given
2426 /// pool digest and reason. If shares are provided, they are updated alongside the variant.
2427 ///
2428 /// Since pools are **mutable** (unlike indexes), this method directly updates
2429 /// the slot's variant without creating a new pool digest.
2430 ///
2431 /// This is a **managed operation** - typically restricted to the pool manager
2432 /// or authorized entities, ensuring controlled strategy execution while
2433 /// protecting depositor interests.
2434 ///
2435 /// ## Returns
2436 /// - `Ok(())` if the variant is successfully updated
2437 /// - `Err(DispatchError)` if fails
2438 fn set_slot_of_variant(
2439 who: &Proprietor,
2440 reason: &Self::Reason,
2441 pool_of: &Self::Digest,
2442 slot_of: &Self::Digest,
2443 variant: Self::Position,
2444 shares: Option<Self::Shares>,
2445 ) -> DispatchResult;
2446
2447 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2448 // ```````````````````````````````````` HOOKS ````````````````````````````````````
2449 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2450
2451 /// Hook called after a slot variant is updated within a pool.
2452 ///
2453 /// Provides an extension point for triggering side-effects when a slot's
2454 /// variant is associated or changed within a managed pool.
2455 ///
2456 /// Receives both the slot digest and optional shares to enable comprehensive
2457 /// state management and analytics when managed slot configurations change.
2458 ///
2459 /// Default implementation is a no-op.
2460 fn on_set_slot_of_variant(
2461 _pool_of: &Self::Digest,
2462 _reason: &Self::Reason,
2463 _slot_of: &Self::Digest,
2464 _shares: Option<Self::Shares>,
2465 _variant: &Self::Position,
2466 ) {
2467 }
2468}