frame_suite/roles.rs
1// SPDX-License-Identifier: MPL-2.0
2//
3// Part of Auguth Labs open-source softwares.
4// Built for the Substrate framework.
5//
6// This Source Code Form is subject to the terms of the Mozilla Public
7// License, v. 2.0. If a copy of the MPL was not distributed with this
8// file, You can obtain one at https://mozilla.org/MPL/2.0/.
9//
10// Copyright (c) 2026 Auguth Labs (OPC) Pvt Ltd, India
11
12// ===============================================================================
13// ```````````````````````````` ROLE MANAGEMENT SUITE ````````````````````````````
14// ===============================================================================
15
16//! Defines a **unified abstraction layer** for managing *role-based logic*
17//! within a runtime system.
18//!
19//! It provides **trait-based contracts** that describe the lifecycle, funding,
20//! and economic behaviors of entities that assume specific roles, such as:
21//!
22//! - Validators and collators
23//! - Governance council members
24//! - Oracle operators or data feeders
25//! - Curators, auditors, or relayers
26//!
27//! ## Overview
28//!
29//! Each trait in this module represents a **composable building block** for defining
30//! how a role behaves in a decentralized system. These abstractions are designed to be
31//! generic, interoperable, and extensible across multiple pallets or runtime modules.
32//!
33//! ### Currently Included Traits
34//!
35//! - [`RoleManager`] - Core lifecycle and collateral management for role-bearing entities.
36//! - [`FundRoles`] - Extends `RoleManager` with *backing* and *funding* capabilities.
37//! - [`CompensateRoles`] - Extends `RoleManager` with *reward* and *penalty* mechanics.
38//! - [`RoleProbation`] - Extends `RoleManager` with **probation** and **permanence** privileges.
39//!
40//! ### Future Extensions
41//!
42//! These traits will compose together to form a **role framework** enabling complex
43//! behaviors (like staking, governance, delegation, conflict resolutions, etc)
44//! to be implemented consistently across different runtime modules.
45//!
46//! ## Design Philosophy
47//!
48//! - **Composability:** Each role trait defines minimal, orthogonal functionality that
49//! can be combined with others to form rich behavior.
50//! - **Abstraction over implementation:** These traits are *interfaces*, not storage-bound
51//! logic. Concrete modules implement them.
52//! - **Interoperability:** Enables higher-level systems (e.g. governance, incentives,
53//! or auditing) to operate generically across role types.
54//! - **Auditability:** Built-in temporal tracking (timestamps, holds, etc.) ensures
55//! transparent lifecycle and accounting for each role.
56//!
57//! This approach allows shared governance, staking, and incentive systems to operate on
58//! **generic roles** without being tightly coupled to any single pallet or implementation.
59
60// ===============================================================================
61// ``````````````````````````````````` IMPORTS ```````````````````````````````````
62// ===============================================================================
63
64// --- Local crate imports ---
65use crate::{Asset, Delimited, Percentage, RuntimeEnum, Time};
66
67// --- FRAME Support ---
68use frame_support::traits::{
69 tokens::{Fortitude, Precision},
70 VariantCount,
71};
72
73// --- Substrate primitives ---
74use sp_runtime::{DispatchError, DispatchResult, Vec};
75
76// ===============================================================================
77// ```````````````````````````````` ROLE MANAGER `````````````````````````````````
78// ===============================================================================
79
80/// A **universal abstraction** for managing *roles* within a runtime context.
81///
82/// This trait defines the full lifecycle of a role-bearing entity - from enrollment
83/// and collateral management to status transitions and resignation.
84///
85/// It is intended to be the foundational interface for any module that wishes to
86/// assign and track *roles* such as:
87///
88/// - **Validators** - entities providing consensus participation and staking collateral.
89/// - **Council Members / Governance Actors** - accounts participating in decision-making.
90/// - **Oracle Operators** - off-chain data feeders bonded with collateral.
91/// - **Bounty Curators / Auditors / Relayers** - specialized economic actors.
92///
93/// By providing a unified API for checking role existence, managing enrollment conditions,
94/// and handling collateral and lifecycle events, this trait enables **role composability**
95/// and modular runtime design.
96///
97/// ## Type Parameters
98///
99/// - `Candidate`: The identifier type representing an account or entity that can assume a role.
100/// Typically this is an `AccountId`, but it can also be a multi-entity struct or hash ID.
101///
102/// ## Invariants
103///
104/// - Collateral associated with a `Candidate` **must remain locked** while the
105/// role is active.
106/// - Status transitions must be **atomic and consistent** - i.e.,
107/// once [`Self::set_status`] succeeds, [`Self::get_status`] should immediately
108/// reflect the new state.
109///
110/// ## Example Implementations
111///
112/// ### Example 1: Validator Role
113///
114/// A validator role might involve participants who are responsible for block production.
115/// In this context:
116/// - `Status` could include `Pending`, `Active`, `Slashed`, or `Resigned`.
117/// - Candidates are required to provide a minimum collateral to enroll.
118/// - The system checks whether a candidate is already enrolled before allowing enrollment.
119/// - The trait implementation manages enrollment, resignation, collateral tracking, and
120/// status updates.
121///
122/// ### Example 2: Governance Council Member
123///
124/// A council member role represents participants in a governance body:
125/// - `Status` could include `Candidate`, `Active`, `Expelled`, or `Retired`.
126/// - Enrollment may not require collateral but must still enforce eligibility rules.
127/// - Resignation or removal is validated against membership in the council.
128/// - The trait implementation allows tracking of active members, status changes, and
129/// role lifecycle events.
130///
131/// These examples illustrate how the `RoleManager` trait can be applied to **different
132/// kinds of roles**, demonstrating the flexibility of the trait without tying it to a
133/// specific runtime storage or pallet.
134///
135/// ## Usage
136///
137/// - This trait is **not tied to a specific module/pallet**. It can be implemented by multiple
138/// role-managing pallets (e.g. `pallet_validators`, `pallet_council`, etc.).
139/// - Generic logic (e.g. in governance or reputation systems) can rely on `RoleManager`
140/// to interact with roles abstractly without hardcoding pallet internals.
141pub trait RoleManager<Candidate> {
142 /// Represents the discrete state (status) of the role (e.g. Active, Pending, Resigned, etc).
143 /// Must implement [`VariantCount`] for easy introspection of status variants.
144 type Status: RuntimeEnum + Delimited + VariantCount;
145
146 /// Represents the metadata of the candidate.
147 ///
148 /// Could include valuable information pertaining to the role-activity
149 /// of the candidate.
150 type Meta: Delimited;
151
152 /// Represents the collateral or balance type associated with the role.
153 type Asset: Asset;
154
155 /// Represents a timestamp or block number used to track temporal data
156 /// like enrollment or status changes.
157 type TimeStamp: Time;
158
159 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
160 // ``````````````````````````````````` CHECKERS ``````````````````````````````````
161 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
162
163 /// Checks whether a given `Candidate` currently has a registered role
164 /// in the system.
165 ///
166 /// Returns `Ok(())` if the role exists, otherwise `DispatchError`.
167 fn role_exists(who: &Candidate) -> DispatchResult;
168
169 /// Validates whether the `Candidate` is eligible to enroll for a role,
170 /// given a specific collateral amount.
171 ///
172 /// This function only performs validation and **not mutates state**.
173 ///
174 /// Returns `Ok(())` if the candidate can enroll, or a `DispatchError` if not.
175 fn can_enroll(who: &Candidate, collateral: Self::Asset) -> DispatchResult;
176
177 /// Verifies whether the `Candidate` can safely resign their role.
178 ///
179 /// This typically ensures there are no pending obligations or locked collateral.
180 fn can_resign(who: &Candidate) -> DispatchResult;
181
182 /// Returns `Ok(())` if the `Candidate` is considered not *defaulted* i.e., available.
183 ///
184 /// A **defaulted** state represents a breach of expected behavior such as
185 /// under-collateralization, missed performance targets, or protocol violations.
186 ///
187 /// Depending on implementation, a defaulted candidate may be:
188 /// - **Temporarily suspended** - can later recover or be reinstated once obligations are met.
189 /// - **Permanently defaulted** - considered resigned, fired, or barred from reactivation.
190 fn is_available(who: &Candidate) -> DispatchResult;
191
192 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
193 // ``````````````````````````````````` GETTERS ```````````````````````````````````
194 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
195
196 /// Retrieves the meta-data of the `Candidate`.
197 ///
198 /// DispatchError otherwise.
199 fn get_meta(who: &Candidate) -> Result<Self::Meta, DispatchError>;
200
201 /// Retrieves the amount of collateral currently locked by the `Candidate`.
202 ///
203 /// Returns a [`Result`] containing the collateral value or a [`DispatchError`].
204 fn get_collateral(who: &Candidate) -> Result<Self::Asset, DispatchError>;
205
206 /// Retrieves the real-time amount of collateral currently locked by all
207 /// the `Candidate`s.
208 fn total_collateral() -> Self::Asset;
209
210 /// Returns the timestamp (or block number) when the `Candidate` enrolled
211 /// in the role.
212 fn enroll_since(who: &Candidate) -> Result<Self::TimeStamp, DispatchError>;
213
214 /// Returns the current `Status` of the `Candidate`.
215 fn get_status(who: &Candidate) -> Result<Self::Status, DispatchError>;
216
217 /// Returns the timestamp of the last status change for the `Candidate`.
218 fn status_since(who: &Candidate) -> Result<Self::TimeStamp, DispatchError>;
219
220 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
221 // ``````````````````````````````````` MUTATORS ``````````````````````````````````
222 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
223
224 /// Updates the role status of the `Candidate` to a new `Status` value.
225 fn set_status(who: &Candidate, status: Self::Status) -> DispatchResult;
226
227 /// Enrolls a new `Candidate` with the specified collateral amount.
228 ///
229 /// Should handle collateral reservation and emit necessary side effects.
230 ///
231 /// The `force` parameter determines the privilege with which the operation is conducted.
232 ///
233 /// Returns the actual amount of collateral accepted or a `DispatchError`.
234 fn enroll(
235 who: &Candidate,
236 collateral: Self::Asset,
237 force: Fortitude,
238 ) -> Result<Self::Asset, DispatchError>;
239
240 /// Resigns the `Candidate` from their role and releases any associated collateral.
241 ///
242 /// Should reverse the effects of `enroll`.
243 ///
244 /// Returns the released collateral amount.
245 fn resign(who: &Candidate) -> Result<Self::Asset, DispatchError>;
246
247 /// Increases a `Candidate`'s collateral. Required if the implementation enforces
248 /// variable collateral.
249 ///
250 /// The `force` parameter determines the privilege with which the operation is conducted.
251 ///
252 /// Returns the actual amount of collateral added or a `DispatchError`.
253 fn add_collateral(
254 who: &Candidate,
255 collateral: Self::Asset,
256 force: Fortitude,
257 ) -> Result<Self::Asset, DispatchError>;
258
259 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
260 // ```````````````````````````````````` HOOKS ````````````````````````````````````
261 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
262
263 /// Hook triggered after a successful enrollment
264 /// along with its collateral.
265 ///
266 /// Used for post-processing actions such as emitting events or
267 /// initializing role data.
268 ///
269 /// Default is no-op.
270 fn on_enroll(_who: &Candidate, _collateral: Self::Asset) {}
271
272 /// Hook triggered after a role resignation along with
273 /// regained (unlocked) collateral.
274 ///
275 /// Typically used to perform cleanup or emit resignation events.
276 ///
277 /// Default is no-op.
278 fn on_resign(_who: &Candidate, _released: Self::Asset) {}
279
280 /// Hook triggered when a `Candidate`'s status is mutated.
281 ///
282 /// Allows implementing pallets to react to state transitions (e.g. Active -> Inactive).
283 ///
284 /// Default is no-op.
285 fn on_status_update(_who: &Candidate, _status: &Self::Status) {}
286
287 /// Hook triggered when the collateral value of a role is incremented.
288 ///
289 /// Can be used to adjust dependent metrics or notify external systems.
290 ///
291 /// Default is no-op.
292 fn on_add_collateral(_who: &Candidate, _raised: Self::Asset) {}
293}
294
295// ===============================================================================
296// ````````````````````````````````` FUND ROLES ``````````````````````````````````
297// ===============================================================================
298
299/// Extends [`RoleManager`] to introduce **funding and backing mechanics**
300/// for role-based systems.
301///
302/// This trait defines a common interface for *roles that require financial
303/// support or collateralization from external entities* (called **backers**).
304/// It models relationships where one or more backers provide funds to a candidate
305/// in exchange for shared exposure, yield, or delegated influence.
306///
307/// Typical applications include:
308///
309/// - **Validator nomination systems** - nominators/delegators (backers) stake
310/// funds behind validators (candidates).
311/// - **DAO or governance roles** - community members back representatives or
312/// proposal leaders.
313/// - **DeFi-style credit systems** - where backers fund loan candidates, or
314/// insurance underwriters provide coverage.
315/// - **Service networks** - e.g., oracle or relayer pools where reputation and
316/// collateral are pooled.
317///
318/// ## Type Parameters
319/// - `Candidate`: The entity or account being backed.
320///
321/// ## Invariants
322///
323/// - Each backer must fund with at least [`Self::min_fund`] units of asset.
324/// - When a `Candidate` is **not available** (See [`RoleManager::is_available`]),
325/// backers may withdraw all or some of their stake (context-basis).
326/// - [`Self::fund`] and [`Self::draw`] operations must maintain **consistent
327/// accounting symmetry** between [`Self::backers_of`] and [`Self::backed_for`].
328///
329/// ## Example Implementations
330///
331/// ### Example 1: Nominated Validator System
332///
333/// In a nominated staking system:
334/// - Candidates are validators who receive backing from multiple nominators (backers).
335/// - Each backer must meet a minimum funding requirement when supporting a candidate.
336/// - The total funding for a candidate cannot exceed a maximum exposure limit.
337/// - Funding increases the candidate's active collateral, while drawing allows backers
338/// to withdraw.
339/// - This example shows how the trait enforces eligibility, updates backing relationships,
340/// and maintains real-time funded values.
341///
342/// ### Example 2: Governance Role Backing
343///
344/// In a governance system where community members can financially support representatives:
345/// - Candidates are council members or proposal leads.
346/// - Backers provide funds to indicate confidence and to incentivize participation.
347/// - The trait ensures backers cannot overexpose themselves or fund below minimum thresholds.
348/// - Candidates may become defaulted if they violate rules or fail to perform, triggering
349/// potential fund loss.
350/// - Rewards, penalties, or collateral adjustments can be layered on top, creating a
351/// dynamic funding ecosystem.
352///
353/// These examples illustrate how `FundRoles` can handle **different backing scenarios**,
354/// from staking and validator support to governance funding, while maintaining **real-time
355/// tracking** of funds and enforcing role-related invariants.
356///
357/// ## Usage
358///
359/// Implement this trait for any role that allows external capital participation or support.
360/// Higher-level logic such as slashing, reward distribution, or governance power weighting
361/// can be layered on top by combining [`CompensateRoles`] or custom traits.
362pub trait FundRoles<Candidate>: RoleManager<Candidate> {
363 /// Type representing the entity providing funding or backing support.
364 type Backer: Delimited;
365
366 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
367 // ``````````````````````````````````` CHECKERS ``````````````````````````````````
368 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
369
370 /// Checks whether the `Candidate` currently holds any funds or active backing relationships.
371 ///
372 /// Returns `Ok(())` if funds exist, otherwise a `DispatchError`.
373 fn has_funds(who: &Candidate) -> DispatchResult;
374
375 /// Validates whether a `Backer` can fund a `Candidate` with the specified amount.
376 ///
377 /// This performs **only validation**, not mutation.
378 ///
379 /// The `precision` parameter defines proportional allocation or best behavior.
380 /// The `force` parameter defines enforcement principle of this funding operation.
381 ///
382 /// Should verify minimum and maximum exposure constraints possibly
383 /// via [`Self::min_fund`] and [`Self::max_exposure`].
384 fn can_fund(
385 by: &Self::Backer,
386 to: &Candidate,
387 value: Self::Asset,
388 precision: Precision,
389 force: Fortitude,
390 ) -> DispatchResult;
391
392 /// Validates whether a `Backer` can draw its funds backed to a `Candidate`.
393 ///
394 /// This performs **only validation**, not mutation.
395 fn can_draw(by: &Self::Backer, from: &Candidate) -> DispatchResult;
396
397 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
398 // ``````````````````````````````````` GETTERS ```````````````````````````````````
399 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
400
401 /// Returns the **maximum exposure** (total backable amount) allowed for this `Candidate`.
402 ///
403 /// This acts as an upper cap on all active fundings combined.
404 ///
405 /// The `precision` and `force` parameters simulate a funding attempt under
406 /// the given directive, determining the effective exposure limits.
407 fn max_exposure(
408 from: &Self::Backer,
409 towards: &Candidate,
410 precision: Precision,
411 force: Fortitude,
412 ) -> Result<Self::Asset, DispatchError>;
413
414 /// Returns the **minimum funding amount** required for a `Backer` to participate.
415 ///
416 /// The `precision` and `force` parameters simulate a funding attempt under
417 /// the given directive, determining the effective minimum requirement.
418 fn min_fund(
419 from: &Self::Backer,
420 towards: &Candidate,
421 precision: Precision,
422 force: Fortitude,
423 ) -> Result<Self::Asset, DispatchError>;
424
425 /// Returns the real-time total **value of funds currently backing** the given `Candidate`.
426 ///
427 /// This reflects the **latest on-chain state**, meaning the value may vary over time
428 /// due to dynamic adjustments such as:
429 /// - Collateral revaluation or slashing
430 /// - Partial withdrawals or new backings
431 /// - Protocol-driven rewards or penalties
432 ///
433 /// Implementations should compute or retrieve this value *at the moment of call*,
434 /// ensuring it reflects the candidate's **live backing exposure**.
435 fn backed_value(who: &Candidate) -> Result<Self::Asset, DispatchError>;
436
437 /// Returns all `Backer`s currently funding the specified `Candidate`,
438 /// along with each backer's **real-time effective contribution**.
439 ///
440 /// Note that returned contribution values are **not static** - they represent the
441 /// candidate's current funding snapshot and may change due to slashing,
442 /// rebalancing, or collateral mutation.
443 fn backers_of(who: &Candidate) -> Result<Vec<(Self::Backer, Self::Asset)>, DispatchError>;
444
445 /// Returns all `Candidate`s that the given `Backer` is funding,
446 /// along with each **real-time funded amount**.
447 ///
448 /// The returned data represents the backer's **current live exposure**, not a
449 /// historical record. Implementations should resolve these dynamically from
450 /// the latest runtime state.
451 fn backed_for(by: &Self::Backer) -> Result<Vec<(Candidate, Self::Asset)>, DispatchError>;
452
453 /// Retrieves the real-time amount of funds currently funded for all the `Candidate`s.
454 fn total_backing() -> Self::Asset;
455
456 /// Retrieves the **current effective funded amount** that `by` has provided to `who`.
457 ///
458 /// This is a *real-time query* that reflects the live value of the fund relationship,
459 /// factoring in any protocol-driven adjustments (e.g., penalties, rewards,
460 /// partial draws, or slashes).
461 fn get_fund(who: &Candidate, by: &Self::Backer) -> Result<Self::Asset, DispatchError>;
462
463 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
464 // ``````````````````````````````````` MUTATORS ``````````````````````````````````
465 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
466
467 /// Performs the **funding operation**, locking the given value of asset
468 /// from the `Backer` in support of the `Candidate`.
469 ///
470 /// The `precision` parameter defines proportional allocation or best behavior.
471 /// The `force` parameter defines enforcement principle of this funding operation.
472 ///
473 /// Returns the actual funded amount.
474 fn fund(
475 to: &Candidate,
476 by: &Self::Backer,
477 value: Self::Asset,
478 precision: Precision,
479 force: Fortitude,
480 ) -> Result<Self::Asset, DispatchError>;
481
482 /// Allows the `Backer` to **draw back** (withdraw) their previously funded assets
483 /// from a `Candidate`.
484 ///
485 /// Returns the amount successfully withdrawn.
486 fn draw(from: &Candidate, by: &Self::Backer) -> Result<Self::Asset, DispatchError>;
487
488 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
489 // ```````````````````````````````````` HOOKS ````````````````````````````````````
490 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
491
492 /// Hook triggered when a funding action is completed.
493 ///
494 /// Can be used to emit events, update metrics, or notify off-chain logic.
495 ///
496 /// Default is no-op.
497 fn on_funded(_who: &Candidate, _by: &Self::Backer, _value: Self::Asset) {}
498
499 /// Hook triggered when funds are drawn or withdrawn.
500 ///
501 /// Use this for cleanup or balance reallocation tasks.
502 ///
503 /// Default is no-op.
504 fn on_drawn(_who: &Candidate, _by: &Self::Backer, _value: Self::Asset) {}
505}
506
507// ===============================================================================
508// `````````````````````````````` COMPENSATE ROLES ```````````````````````````````
509// ===============================================================================
510
511/// Extends [`RoleManager`] to introduce **reward and penalty mechanics**
512/// for role-based systems.
513///
514/// This trait manages economic incentives and behavioral enforcement for candidates,
515/// tracking rewards, penalties, and temporary holdings. Typical applications include
516/// (but not constrained to):
517///
518/// - **Validators** - rewards for block production, penalties for missed duties.
519/// - **Council members** - incentives for active participation, slashing for misconduct.
520/// - **Oracle operators** - rewards for accurate reporting, penalties for stale or
521/// incorrect data.
522///
523/// ## Concepts
524///
525/// - **Hold**: The total reservation of a candidate's assets, includes rewards and other
526/// backings (collaterals, fundings, etc).
527/// - **Reward**: An addition to a candidate's assets for performing expected duties.
528/// - **Penalty**: A fractional deduction (ratio) applied to assets for misbehavior or
529/// non-performance.
530/// - **Forgive**: Reverses penalties, partially or fully, for rehabilitation or
531/// governance action.
532/// - **Reclaim**: Withdraw previously rewarded assets for redistribution or correction.
533///
534/// All returned values (assets, rewards, penalties) are **real-time**, reflecting the
535/// candidate's current effective state.
536///
537/// ## Example Implementations
538///
539/// ### Example 1: Validator Rewards and Penalties
540///
541/// In a blockchain validator system:
542/// - Validators earn rewards for producing blocks, finalizing chains, or performing
543/// other expected duties.
544/// - Misbehavior, missed blocks, or downtime results in penalties, which reduce the
545/// validator's hold.
546/// - Rewards and penalties are recorded with timestamps, allowing auditability and
547/// historical tracking.
548/// - The system may forgive temporary penalties or reclaim rewards in case of protocol
549/// corrections.
550/// - Real-time queries (get_rewards_of, get_penalties_of) reflect the current effective
551/// state of the validator.
552///
553/// ### Example 2: Council Member Performance Incentives
554///
555/// In a governance council:
556/// - Council members receive rewards for attending sessions, voting, or completing
557/// assigned tasks.
558/// - Failure to participate or violating rules triggers penalties, possibly reducing
559/// influence or rewards.
560/// - Holds represent assets reserved for potential penalties or pending rewards.
561/// - Forgiveness mechanisms allow partial reversal of penalties for rehabilitation or
562/// governance decisions.
563/// - Reclaiming rewards may occur if a proposal is invalidated or performance is reversed.
564/// - Real-time computations ensure all incentives and penalties are up-to-date when queried.
565///
566/// These examples illustrate how `CompensateRoles` can manage **dynamic reward and penalty
567/// systems** across different roles, ensuring fairness, accountability, and flexibility in
568/// role-based operations.
569///
570/// ## Invariants
571///
572/// - Total penalties cannot exceed the candidate's current hold or collateral.
573/// - Implementations should track timestamps accurately to maintain auditability.
574pub trait CompensateRoles<Candidate>: RoleManager<Candidate> {
575 /// The ratio type used to represent **penalty factors** applied to a candidate.
576 ///
577 /// This type defines how much of a candidate's assets should be deducted
578 /// during a penalty event, expressed as a fractional percentage.
579 ///
580 /// - Values should be within `[0, 1]` (0% to 100%)
581 /// - Example: `0.05` -> 5% penalty, `0.25` -> 25% penalty
582 type Ratio: Percentage;
583
584 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
585 // ``````````````````````````````````` CHECKERS ``````````````````````````````````
586 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
587
588 /// Checks whether the candidate has any pending rewards.
589 fn has_reward(who: &Candidate) -> DispatchResult;
590
591 /// Checks whether the candidate has any pending penalties.
592 fn has_penalty(who: &Candidate) -> DispatchResult;
593
594 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
595 // ``````````````````````````````````` GETTERS ```````````````````````````````````
596 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
597
598 /// Returns the current **hold amount** of a candidate.
599 ///
600 /// Holds are total reserved assets (may include rewards) that may be used
601 /// for penalties or temporary constraints.
602 fn get_hold(who: &Candidate) -> Result<Self::Asset, DispatchError>;
603
604 /// Returns all pending rewards for a candidate along with their timestamp of enforcement.
605 ///
606 /// The list reflects **real-time pending rewards**, not historical logs.
607 fn get_rewards_of(
608 who: &Candidate,
609 ) -> Result<Vec<(Self::TimeStamp, Self::Asset)>, DispatchError>;
610
611 /// Returns all rewards of candidates issued at a specific timestamp.
612 ///
613 /// - This is a **snapshot query** for the given timestamp; it does **not mutate state**.
614 /// - Returned values may reflect the **rewards actually enforced or pending** based on
615 /// the given timestamp, which may be used for auditing, reporting, or batch processing.
616 fn get_rewards_on(
617 time_stamp: Self::TimeStamp,
618 ) -> Result<Vec<(Candidate, Self::Asset)>, DispatchError>;
619
620 /// Returns all pending penalties for a candidate along with their timestamp of enforcement.
621 ///
622 /// The list reflects **real-time pending penalties**, not historical logs.
623 fn get_penalties_of(
624 who: &Candidate,
625 ) -> Result<Vec<(Self::TimeStamp, Self::Ratio)>, DispatchError>;
626
627 /// Returns all penalties of candidates issued at a specific timestamp.
628 ///
629 /// - This is a **snapshot query** for the given timestamp; it does **not mutate state**.
630 /// - Returned values may reflect the **penalties actually enforced or pending** based on
631 /// the given timestamp, which may be used for auditing, reporting, or batch processing.
632 fn get_penalties_on(
633 time_stamp: Self::TimeStamp,
634 ) -> Result<Vec<(Candidate, Self::Ratio)>, DispatchError>;
635
636 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
637 // ``````````````````````````````````` MUTATORS ``````````````````````````````````
638 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
639
640 /// Updates the hold of a candidate to the specified amount.
641 ///
642 /// This method is utilized to reward or penalize a `Candidate`'s hold which
643 /// reflects on its individual backings.
644 fn set_hold(
645 who: &Candidate,
646 value: Self::Asset,
647 precision: Precision,
648 force: Fortitude,
649 ) -> DispatchResult;
650
651 /// Issues a reward to a candidate, marks it as pending and returning the timestamp.
652 ///
653 /// Once the reward is enforced during the timestamp, it is applied to the
654 /// `Candidate`'s hold.
655 ///
656 /// This is to ensure rewards reversal (regaining) if applied wrongly.
657 fn reward(
658 who: &Candidate,
659 value: Self::Asset,
660 precision: Precision,
661 ) -> Result<Self::TimeStamp, DispatchError>;
662
663 /// Applies a penalty (fractional [`Ratio`](Self::Ratio)) to a candidate, marks it as pending
664 /// and returning the timestamp.
665 ///
666 /// Once the penalty is enforced during the timestamp, it is applied to the
667 /// `Candidate`'s hold.
668 ///
669 /// This is to ensure penalty reversal (forgiving) if applied wrongly.
670 fn penalize(who: &Candidate, factor: Self::Ratio) -> Result<Self::TimeStamp, DispatchError>;
671
672 /// Forgives a pending penalty, returning its factor.
673 ///
674 /// Cannot be utilized for enforced penalties, only pending ones.
675 ///
676 /// `from` specifies the timestamp of the penalty to forgive.
677 fn forgive(who: &Candidate, from: Self::TimeStamp) -> Result<Self::Ratio, DispatchError>;
678
679 /// Reclaims a pending reward from a candidate, returning its reward value.
680 ///
681 /// Cannot be utilized for enforced rewards, only pending ones.
682 ///
683 /// `from` specifies the timestamp of the reward to reclaim.
684 fn reclaim(who: &Candidate, from: Self::TimeStamp) -> Result<Self::Asset, DispatchError>;
685
686 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
687 // ```````````````````````````````````` HOOKS ````````````````````````````````````
688 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
689
690 /// Hook triggered when a reward is issued.
691 ///
692 /// Default is no-op.
693 fn on_reward(_who: &Candidate, _amount: Self::Asset, _at: Self::TimeStamp) {}
694
695 /// Hook triggered when a penalty is applied.
696 ///
697 /// Default is no-op.
698 fn on_penalize(_who: &Candidate, _factor: Self::Ratio, _at: Self::TimeStamp) {}
699
700 /// Hook triggered when a penalty is forgiven.
701 ///
702 /// Default is no-op.
703 fn on_forgive(_who: &Candidate, _factor: Self::Ratio) {}
704
705 /// Hook triggered when a reward is reclaimed.
706 ///
707 /// Default is no-op.
708 fn on_reclaim(_who: &Candidate, _amount: Self::Asset) {}
709
710 /// Hook triggered when an author's total hold is updated.
711 ///
712 /// Signals a mutation maybe due to rewards/penalties or internal
713 /// changes enforced (finalized)
714 ///
715 /// Default is no-op.
716 fn on_set_hold(_who: &Candidate, _value: Self::Asset) {}
717}
718
719// ===============================================================================
720// ``````````````````````````````` ROLE PROBATION ````````````````````````````````
721// ===============================================================================
722
723/// Extends [`RoleManager`] to introduce **probation and permanent status mechanics**
724/// for role-based systems.
725///
726/// This trait manages the probation lifecycle of candidates, tracking their risk,
727/// confirmation, and eligibility for permanent status. Typical applications include:
728/// - **Employees** - probation periods before permanent employment.
729/// - **Validators or council members** - temporary risk periods before confirmed full status.
730/// - **Accounts or participants** - temporary monitoring before achieving full privileges.
731///
732/// ## Concepts
733///
734/// - **Probation / Risk**: Candidate is under evaluation; actions or failures
735/// may have consequences.
736/// - **Permanent / Confirmation**: Candidate has successfully passed evaluation;
737/// fully confirmed.
738/// - **Secure**: Temporarily avoids risk without confirming permanent status.
739///
740/// ## Probation Flow
741/// - A new enrolled candidate starts in **probation** status.
742/// - Upon negative performance, they risk losing permanence.
743/// - Upon meeting required criteria, they are **confirmed permanent**.
744/// - If a permanent candidate violates policies, permanent status may be revoked.
745///
746/// ## Invariants
747/// - Candidates should not simultaneously be in probation and permanent status.
748pub trait RoleProbation<Candidate>: RoleManager<Candidate> {
749
750 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
751 // ``````````````````````````````````` CHECKERS ``````````````````````````````````
752 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
753
754 /// Checks if the candidate is currently under probation.
755 ///
756 /// Returns `DispatchError` if the query fails.
757 fn is_on_probation(who: &Candidate) -> DispatchResult;
758
759 /// Checks if the candidate has secured permanent / confirmed status.
760 ///
761 /// Returns `DispatchError` if the query fails.
762 fn is_permanent(who: &Candidate) -> DispatchResult;
763
764 /// Checks if the candidate is eligible to become permanent.
765 ///
766 /// Returns `DispatchError` if the candidate is not eligible.
767 fn can_be_permanent(who: &Candidate) -> DispatchResult;
768
769 /// Checks if the candidate's permanent status can be revoked,
770 /// returning them to probation.
771 ///
772 /// Returns `DispatchError` if the candidate is not eligible.
773 fn can_revoke_permanence(who: &Candidate) -> DispatchResult;
774
775 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
776 // ``````````````````````````````````` MUTATORS ``````````````````````````````````
777 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
778
779 /// Places the candidate's probation at risk (risking permanence).
780 ///
781 /// Returns `DispatchError` if the candidate is not in probation.
782 fn risk_probation(who: &Candidate) -> DispatchResult;
783
784 /// Places the candidate's permanence at risk (risking probation).
785 ///
786 /// Returns `DispatchError` if the candidate is not in permanence.
787 fn risk_permanence(who: &Candidate) -> DispatchResult;
788
789 /// Marks the candidate as positively progressing toward permanent status.
790 ///
791 /// This indicates that the candidate has demonstrated sufficient performance
792 /// or compliance during probation, making them eligible to become permanent
793 /// once probation concludes.
794 ///
795 /// Returns `DispatchError` if the candidate is not in probation.
796 fn secure_permanence(who: &Candidate) -> DispatchResult;
797
798 /// Marks the candidate as secured / confirmed / permanent.
799 ///
800 /// Returns the new status on success.
801 /// Returns `DispatchError` if the operation fails.
802 fn set_permanence(who: &Candidate) -> Result<Self::Status, DispatchError>;
803
804 /// Revokes permanent status and places the candidate back under probation.
805 ///
806 /// Returns the new status on success.
807 /// Returns `DispatchError` if the operation fails.
808 fn revoke_permanence(who: &Candidate) -> Result<Self::Status, DispatchError>;
809
810 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
811 // ```````````````````````````````````` HOOKS ````````````````````````````````````
812 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
813
814 /// Hook triggered after the candidate under probation
815 /// risks their permanence.
816 ///
817 /// Default implementation is a no-op.
818 fn on_risk_probation(_who: &Candidate) {}
819
820 /// Hook triggered after the permanent candidate
821 /// risks their permanence.
822 ///
823 /// Default implementation is a no-op.
824 fn on_risk_permanence(_who: &Candidate) {}
825
826 /// Hook triggered after a candidate secures being
827 /// permanently promoted soon without negative impacts.
828 ///
829 /// Default implementation is a no-op.
830 fn on_secure_permanence(_who: &Candidate) {}
831
832 /// Hook triggered after a candidate gets permanent status.
833 ///
834 /// Default implementation is a no-op.
835 fn on_set_permance(_who: &Candidate) {}
836
837 /// Hook triggered after a candidate's permanence is
838 /// revoked, placing them back under probation.
839 ///
840 /// Default implementation is a no-op.
841 fn on_revoke_permanence(_who: &Candidate) {}
842}
843
844// ===============================================================================
845// ```````````````````````````````` ROLE ACTIVITY ````````````````````````````````
846// ===============================================================================
847
848/// A lightweight abstraction for determining whether a role-bearing `Candidate` is
849/// currently **idle** or **actively performing duties**.
850///
851/// This trait models **real-time operational activity**, independent of role
852/// lifecycle, status, or eligibility. It is intended to compose with role
853/// management, funding, and compensation logic.
854///
855/// ## Semantics
856///
857/// The core API uses an **inverted pattern which allows activity to be represented
858/// with structured context rather than a lossy boolean.
859///
860/// The associated `Activity` type must represent **non-fatal engagement**
861/// and be convertible into [`DispatchError`].
862pub trait RoleActivity<Candidate, TimeStamp> {
863 /// Describes the duty currently being performed by the candidate.
864 ///
865 /// Returned when the candidate is active. When converted into
866 /// [`DispatchError`], it must clearly indicate **why the operation is blocked**
867 /// and **what must occur to withdraw from or complete the ongoing activity**.
868 ///
869 /// This type represents non-fatal operational engagement only.
870 type Activity: RuntimeEnum + Delimited + Into<DispatchError>;
871
872 /// Returns `Ok(())` if the candidate is currently **idle** (not performing duties).
873 ///
874 /// Returns `Err(Activity)` if the candidate is **active**, where `Activity`
875 /// describes the duty being performed.
876 ///
877 /// This method must not mutate state and should reflect real-time activity.
878 fn is_idle(who: &Candidate) -> Result<(), Self::Activity>;
879
880 /// Returns `Ok(Activity)` if the candidate is **active**, where `Activity`
881 /// describes the duty being performed.
882 ///
883 /// Returns `Err(())` if the candidate is **idle**.
884 fn is_active(who: &Candidate) -> Result<Self::Activity, ()> {
885 match Self::is_idle(who) {
886 Ok(_) => Err(()),
887 Err(a) => Ok(a),
888 }
889 }
890}