#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
extern crate alloc;
use alloc::vec::Vec;
use pallet_session::historical::IdentificationTuple;
use pallet_staking::{BalanceOf, Exposure, ExposureOf, Pallet as Staking};
use sp_runtime::Perbill;
use sp_staking::offence::OnOffenceHandler;
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::config]
pub trait Config:
frame_system::Config
+ pallet_staking::Config
+ pallet_session::Config<ValidatorId = <Self as frame_system::Config>::AccountId>
+ pallet_session::historical::Config<
FullIdentification = Exposure<
<Self as frame_system::Config>::AccountId,
BalanceOf<Self>,
>,
FullIdentificationOf = ExposureOf<Self>,
>
{
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
OffenceCreated { offenders: Vec<(T::AccountId, Perbill)> },
}
#[pallet::error]
pub enum Error<T> {
FailedToGetActiveEra,
}
type OffenceDetails<T> = sp_staking::offence::OffenceDetails<
<T as frame_system::Config>::AccountId,
IdentificationTuple<T>,
>;
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(T::DbWeight::get().reads(2))]
pub fn create_offence(
origin: OriginFor<T>,
offenders: Vec<(T::AccountId, Perbill)>,
) -> DispatchResult {
ensure_root(origin)?;
let slash_fraction =
offenders.clone().into_iter().map(|(_, fraction)| fraction).collect::<Vec<_>>();
let offence_details = Self::get_offence_details(offenders.clone())?;
Self::submit_offence(&offence_details, &slash_fraction);
Self::deposit_event(Event::OffenceCreated { offenders });
Ok(())
}
}
impl<T: Config> Pallet<T> {
fn get_offence_details(
offenders: Vec<(T::AccountId, Perbill)>,
) -> Result<Vec<OffenceDetails<T>>, DispatchError> {
let now = pallet_staking::ActiveEra::<T>::get()
.map(|e| e.index)
.ok_or(Error::<T>::FailedToGetActiveEra)?;
Ok(offenders
.clone()
.into_iter()
.map(|(o, _)| OffenceDetails::<T> {
offender: (o.clone(), Staking::<T>::eras_stakers(now, &o)),
reporters: Default::default(),
})
.collect())
}
fn submit_offence(offenders: &[OffenceDetails<T>], slash_fraction: &[Perbill]) {
let session_index = <pallet_session::Pallet<T> as frame_support::traits::ValidatorSet<T::AccountId>>::session_index();
<pallet_staking::Pallet<T> as OnOffenceHandler<
T::AccountId,
IdentificationTuple<T>,
Weight,
>>::on_offence(&offenders, &slash_fraction, session_index);
}
}
}