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}