pallet_root_offences/
lib.rs1#![cfg_attr(not(feature = "std"), no_std)]
24
25#[cfg(test)]
26mod mock;
27#[cfg(test)]
28mod tests;
29
30extern crate alloc;
31
32use alloc::vec::Vec;
33pub use pallet::*;
34use pallet_session::historical::IdentificationTuple;
35use sp_runtime::{traits::Convert, Perbill};
36use sp_staking::offence::OnOffenceHandler;
37
38#[frame_support::pallet]
39pub mod pallet {
40 use super::*;
41 use frame_support::pallet_prelude::*;
42 use frame_system::pallet_prelude::*;
43 use sp_staking::SessionIndex;
44
45 #[pallet::config]
46 pub trait Config:
47 frame_system::Config
48 + pallet_staking::Config
49 + pallet_session::Config<ValidatorId = <Self as frame_system::Config>::AccountId>
50 + pallet_session::historical::Config
51 {
52 #[allow(deprecated)]
53 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
54 type OffenceHandler: OnOffenceHandler<Self::AccountId, IdentificationTuple<Self>, Weight>;
56 }
57
58 #[pallet::pallet]
59 pub struct Pallet<T>(_);
60
61 #[pallet::event]
62 #[pallet::generate_deposit(pub(super) fn deposit_event)]
63 pub enum Event<T: Config> {
64 OffenceCreated { offenders: Vec<(T::AccountId, Perbill)> },
66 }
67
68 #[pallet::error]
69 pub enum Error<T> {
70 FailedToGetActiveEra,
72 }
73
74 type OffenceDetails<T> = sp_staking::offence::OffenceDetails<
75 <T as frame_system::Config>::AccountId,
76 IdentificationTuple<T>,
77 >;
78
79 #[pallet::call]
80 impl<T: Config> Pallet<T> {
81 #[pallet::call_index(0)]
86 #[pallet::weight(T::DbWeight::get().reads(2))]
87 pub fn create_offence(
88 origin: OriginFor<T>,
89 offenders: Vec<(T::AccountId, Perbill)>,
90 maybe_identifications: Option<Vec<T::FullIdentification>>,
91 maybe_session_index: Option<SessionIndex>,
92 ) -> DispatchResult {
93 ensure_root(origin)?;
94
95 ensure!(
96 maybe_identifications.as_ref().map_or(true, |ids| ids.len() == offenders.len()),
97 "InvalidIdentificationLength"
98 );
99
100 let identifications =
101 maybe_identifications.ok_or("Unreachable-NoIdentification").or_else(|_| {
102 offenders
103 .iter()
104 .map(|(who, _)| {
105 T::FullIdentificationOf::convert(who.clone())
106 .ok_or("failed to call FullIdentificationOf")
107 })
108 .collect::<Result<Vec<_>, _>>()
109 })?;
110
111 let slash_fraction =
112 offenders.clone().into_iter().map(|(_, fraction)| fraction).collect::<Vec<_>>();
113 let offence_details = Self::get_offence_details(offenders.clone(), identifications)?;
114
115 Self::submit_offence(&offence_details, &slash_fraction, maybe_session_index);
116 Self::deposit_event(Event::OffenceCreated { offenders });
117 Ok(())
118 }
119 }
120
121 impl<T: Config> Pallet<T> {
122 fn get_offence_details(
124 offenders: Vec<(T::AccountId, Perbill)>,
125 identifications: Vec<T::FullIdentification>,
126 ) -> Result<Vec<OffenceDetails<T>>, DispatchError> {
127 Ok(offenders
128 .clone()
129 .into_iter()
130 .zip(identifications.into_iter())
131 .map(|((o, _), i)| OffenceDetails::<T> {
132 offender: (o.clone(), i),
133 reporters: Default::default(),
134 })
135 .collect())
136 }
137
138 fn submit_offence(
140 offenders: &[OffenceDetails<T>],
141 slash_fraction: &[Perbill],
142 maybe_session_index: Option<SessionIndex>,
143 ) {
144 let session_index = maybe_session_index.unwrap_or_else(|| {
145 <pallet_session::Pallet<T> as frame_support::traits::ValidatorSet<
146 T::AccountId,
147 >>::session_index()
148 });
149 T::OffenceHandler::on_offence(&offenders, &slash_fraction, session_index);
150 }
151 }
152}