#![cfg_attr(not(feature = "std"), no_std)]
pub mod migration;
mod mock;
mod tests;
extern crate alloc;
use alloc::vec::Vec;
use codec::Encode;
use core::marker::PhantomData;
use frame_support::weights::Weight;
use sp_runtime::{traits::Hash, Perbill};
use sp_staking::{
offence::{Kind, Offence, OffenceDetails, OffenceError, OnOffenceHandler, ReportOffence},
SessionIndex,
};
pub use pallet::*;
type OpaqueTimeSlot = Vec<u8>;
type ReportIdOf<T> = <T as frame_system::Config>::Hash;
const LOG_TARGET: &str = "runtime::offences";
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type IdentificationTuple: Parameter;
type OnOffenceHandler: OnOffenceHandler<Self::AccountId, Self::IdentificationTuple, Weight>;
}
#[pallet::storage]
pub type Reports<T: Config> = StorageMap<
_,
Twox64Concat,
ReportIdOf<T>,
OffenceDetails<T::AccountId, T::IdentificationTuple>,
>;
#[pallet::storage]
pub type ConcurrentReportsIndex<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
Kind,
Twox64Concat,
OpaqueTimeSlot,
Vec<ReportIdOf<T>>,
ValueQuery,
>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event {
Offence { kind: Kind, timeslot: OpaqueTimeSlot },
}
}
impl<T, O> ReportOffence<T::AccountId, T::IdentificationTuple, O> for Pallet<T>
where
T: Config,
O: Offence<T::IdentificationTuple>,
{
fn report_offence(reporters: Vec<T::AccountId>, offence: O) -> Result<(), OffenceError> {
let offenders = offence.offenders();
let time_slot = offence.time_slot();
let TriageOutcome { concurrent_offenders } =
match Self::triage_offence_report::<O>(reporters, &time_slot, offenders) {
Some(triage) => triage,
None => return Err(OffenceError::DuplicateReport),
};
let offenders_count = concurrent_offenders.len() as u32;
let new_fraction = offence.slash_fraction(offenders_count);
let slash_perbill: Vec<_> = (0..concurrent_offenders.len()).map(|_| new_fraction).collect();
T::OnOffenceHandler::on_offence(
&concurrent_offenders,
&slash_perbill,
offence.session_index(),
);
Self::deposit_event(Event::Offence { kind: O::ID, timeslot: time_slot.encode() });
Ok(())
}
fn is_known_offence(offenders: &[T::IdentificationTuple], time_slot: &O::TimeSlot) -> bool {
let any_unknown = offenders.iter().any(|offender| {
let report_id = Self::report_id::<O>(time_slot, offender);
!<Reports<T>>::contains_key(&report_id)
});
!any_unknown
}
}
impl<T: Config> Pallet<T> {
pub fn reports(
report_id: ReportIdOf<T>,
) -> Option<OffenceDetails<T::AccountId, T::IdentificationTuple>> {
Reports::<T>::get(report_id)
}
fn report_id<O: Offence<T::IdentificationTuple>>(
time_slot: &O::TimeSlot,
offender: &T::IdentificationTuple,
) -> ReportIdOf<T> {
(O::ID, time_slot.encode(), offender).using_encoded(T::Hashing::hash)
}
fn triage_offence_report<O: Offence<T::IdentificationTuple>>(
reporters: Vec<T::AccountId>,
time_slot: &O::TimeSlot,
offenders: Vec<T::IdentificationTuple>,
) -> Option<TriageOutcome<T>> {
let mut storage = ReportIndexStorage::<T, O>::load(time_slot);
let mut any_new = false;
for offender in offenders {
let report_id = Self::report_id::<O>(time_slot, &offender);
if !<Reports<T>>::contains_key(&report_id) {
any_new = true;
<Reports<T>>::insert(
&report_id,
OffenceDetails { offender, reporters: reporters.clone() },
);
storage.insert(report_id);
}
}
if any_new {
let concurrent_offenders = storage
.concurrent_reports
.iter()
.filter_map(<Reports<T>>::get)
.collect::<Vec<_>>();
storage.save();
Some(TriageOutcome { concurrent_offenders })
} else {
None
}
}
}
struct TriageOutcome<T: Config> {
concurrent_offenders: Vec<OffenceDetails<T::AccountId, T::IdentificationTuple>>,
}
#[must_use = "The changes are not saved without called `save`"]
struct ReportIndexStorage<T: Config, O: Offence<T::IdentificationTuple>> {
opaque_time_slot: OpaqueTimeSlot,
concurrent_reports: Vec<ReportIdOf<T>>,
_phantom: PhantomData<O>,
}
impl<T: Config, O: Offence<T::IdentificationTuple>> ReportIndexStorage<T, O> {
fn load(time_slot: &O::TimeSlot) -> Self {
let opaque_time_slot = time_slot.encode();
let concurrent_reports = <ConcurrentReportsIndex<T>>::get(&O::ID, &opaque_time_slot);
Self { opaque_time_slot, concurrent_reports, _phantom: Default::default() }
}
fn insert(&mut self, report_id: ReportIdOf<T>) {
self.concurrent_reports.push(report_id);
}
fn save(self) {
<ConcurrentReportsIndex<T>>::insert(
&O::ID,
&self.opaque_time_slot,
&self.concurrent_reports,
);
}
}