pallet_chain_manager/
offence.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// ``````````````````````````````````` OFFENCES ``````````````````````````````````
14// ===============================================================================
15
16//! Implements [`OnOffenceHandler`] for [`Pallet`].
17//!
18//! Maps offenders to authors and schedules penalties via [`PenalizeAuthors`],
19//! delegating execution to the author role manager [`Config::RoleAdapter`].
20
21// ===============================================================================
22// ``````````````````````````````````` IMPORTS ```````````````````````````````````
23// ===============================================================================
24
25// --- Local crate imports ---
26use crate::{types::*, weights::*, Config, Internals, Pallet};
27
28// --- FRAME Suite ---
29use frame_suite::PenalizeAuthors;
30
31// --- Substrate primitives ---
32use sp_runtime::{Vec, Weight};
33
34// --- Substrate staking ---
35use sp_staking::offence::{OffenceDetails, OnOffenceHandler};
36
37// ===============================================================================
38// ``````````````````````````````` OFFENCE-HANDLER ```````````````````````````````
39// ===============================================================================
40
41/// Integration with Substrate's offence reporting system.
42///
43/// This implementation bridges Substrate's session-level offence detection
44/// with the pallet's **author-level penalty scheduling** via the
45/// [`PenalizeAuthors`] interface.
46///
47/// Offences reported by the session pallet are translated into
48/// author identifiers and penalty inputs, which are then **scheduled**
49/// for enforcement by the pallet's penalty subsystem.
50impl<T: Config> OnOffenceHandler<OffenceReporter<T>, Offender<T>, Weight> for Pallet<T>
51where
52    AuthorOf<T>: From<<T as pallet_session::Config>::ValidatorId>,
53{
54    /// Handles reported offences for the current session.
55    ///
56    /// ## Workflow
57    /// - Convert session-level offenders into local author identifiers.
58    /// - Map each offence to its corresponding penalty fraction.
59    /// - Schedule penalties via the pallet's penalty subsystem
60    ///   [`PenalizeAuthors::penalize_authors`].
61    ///
62    /// ## Semantics
63    /// - Penalties are **scheduled**, not enforced immediately.
64    /// - Final enforcement timing and transformation (e.g. scaling, caps,
65    ///   aggregation) are governed by the configured penalty plugin model
66    ///   [`Config::PenaltyModel`].
67    /// - This handler performs **no additional offence validation** beyond
68    ///   the guarantees already provided by Substrate.
69    fn on_offence(
70        offenders: &[OffenceDetails<OffenceReporter<T>, Offender<T>>],
71        slash_fractions: &[PenaltyRatio],
72        _session: SessionIndex,
73    ) -> Weight {
74        // Prepare a list of (author, penalty) pairs.
75        let mut offenders_list: Vec<(AuthorOf<T>, PenaltyOf<T>)> = Vec::new();
76        
77        // Convert each reported offender into a local author identifier
78        // and associate it with the corresponding penalty fraction.
79        for (i, offender) in offenders.iter().enumerate() {
80            let details = offender;
81            let slash_fraction = slash_fractions[i];
82
83            let offender_account: AuthorOf<T> = details.offender.0.clone().into();
84
85            offenders_list.push((offender_account, slash_fraction));
86        }
87
88        // Schedule penalties for all offending authors via the pallet's
89        // penalty subsystem.
90        <Internals<T> as PenalizeAuthors<AuthorOf<T>, PenaltyOf<T>>>::penalize_authors(
91            offenders_list,
92        );
93
94        let offenders_count = offenders.len() as u32;
95        <T as Config>::WeightInfo::on_offence(offenders_count)
96    }
97}
98
99// ===============================================================================
100// ```````````````````````````````` OFFENCE TESTS ````````````````````````````````
101// ===============================================================================
102
103#[cfg(test)]
104mod tests {
105
106    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
107    // ```````````````````````````````````` IMPORTS ``````````````````````````````````
108    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
109
110    // --- Local crate imports ---
111    use crate::mock::*;
112
113    // --- FRAME Suite ---
114    use frame_suite::roles::*;
115
116    // --- FRAME Support ---
117    use frame_support::traits::tokens::Fortitude;
118
119    // --- Substrate staking ---
120    use sp_staking::offence::{OffenceDetails, OnOffenceHandler};
121
122    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
123    // ``````````````````````````````` OFFENCE HANDLER ```````````````````````````````
124    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
125
126    #[test]
127    fn on_offence_success() {
128        chain_manager_test_ext().execute_with(|| {
129            set_user_balance_and_hold(ALICE, 250, 250).unwrap();
130            set_user_balance_and_hold(BOB, 250, 250).unwrap();
131            set_user_balance_and_hold(MIKE, 250, 250).unwrap();
132            set_user_balance_and_hold(CHARLIE, 250, 250).unwrap();
133
134            RoleAdapter::enroll(&ALICE, 200, Fortitude::Force).unwrap();
135            RoleAdapter::enroll(&BOB, 150, Fortitude::Force).unwrap();
136
137            System::set_block_number(16);
138
139            let offenders = [OffenceDetails {
140                offender: (ALICE, ALICE),
141                reporters: vec![CHARLIE, MIKE],
142            }];
143            let slash_fraction = [PenaltyRatio::from_percent(5)];
144
145            Pallet::on_offence(&offenders, &slash_fraction, 1);
146
147            // Penalty immediately scheduled for Alice
148            let penalties_of_alice = RoleAdapter::get_penalties_of(&ALICE).unwrap();
149
150            let expected_penalties_of_alice = vec![(20, PenaltyRatio::from_percent(5))];
151            assert_eq!(penalties_of_alice, expected_penalties_of_alice);
152        })
153    }
154}