#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::type_complexity)]
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
pub use pallet::*;
pub use weights::WeightInfo;
use frame_support::pallet_prelude::Weight;
use frame_support::pallet_prelude::*;
use sp_membership::traits::*;
use sp_membership::MembershipData;
use sp_runtime::traits::Zero;
use sp_std::collections::btree_map::BTreeMap;
use sp_std::prelude::*;
#[cfg(feature = "runtime-benchmarks")]
pub trait SetupBenchmark<IdtyId, AccountId> {
fn force_valid_distance_status(idty_index: &IdtyId);
fn add_cert(_issuer: &IdtyId, _receiver: &IdtyId);
}
#[cfg(feature = "runtime-benchmarks")]
impl<IdtyId, AccountId> SetupBenchmark<IdtyId, AccountId> for () {
fn force_valid_distance_status(_idty_id: &IdtyId) {}
fn add_cert(_issuer: &IdtyId, _receiver: &IdtyId) {}
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub enum MembershipRemovalReason {
Expired,
Revoked,
NotEnoughCerts,
System,
}
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::traits::StorageVersion;
use frame_system::pallet_prelude::*;
use sp_runtime::traits::Convert;
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 CheckMembershipOpAllowed: CheckMembershipOpAllowed<Self::IdtyId>;
type IdtyId: Copy + MaybeSerializeDeserialize + Parameter + Ord;
type IdtyIdOf: Convert<Self::AccountId, Option<Self::IdtyId>>;
type AccountIdOf: Convert<Self::IdtyId, Option<Self::AccountId>>;
#[pallet::constant]
type MembershipPeriod: Get<BlockNumberFor<Self>>;
#[pallet::constant]
type MembershipRenewalPeriod: Get<BlockNumberFor<Self>>;
type OnNewMembership: OnNewMembership<Self::IdtyId>;
type OnRemoveMembership: OnRemoveMembership<Self::IdtyId>;
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type WeightInfo: WeightInfo;
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkSetupHandler: SetupBenchmark<Self::IdtyId, Self::AccountId>;
}
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub memberships: BTreeMap<T::IdtyId, MembershipData<BlockNumberFor<T>>>,
}
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
Self {
memberships: Default::default(),
}
}
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
for (idty_id, membership_data) in &self.memberships {
MembershipsExpireOn::<T>::append(membership_data.expire_on, idty_id);
Membership::<T>::insert(idty_id, membership_data);
}
}
}
#[pallet::storage]
#[pallet::getter(fn membership)]
pub type Membership<T: Config> = CountedStorageMap<
_,
Twox64Concat,
T::IdtyId,
MembershipData<BlockNumberFor<T>>,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn memberships_expire_on)]
pub type MembershipsExpireOn<T: Config> =
StorageMap<_, Twox64Concat, BlockNumberFor<T>, Vec<T::IdtyId>, ValueQuery>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
MembershipAdded {
member: T::IdtyId,
expire_on: BlockNumberFor<T>,
},
MembershipRenewed {
member: T::IdtyId,
expire_on: BlockNumberFor<T>,
},
MembershipRemoved {
member: T::IdtyId,
reason: MembershipRemovalReason,
},
}
#[pallet::error]
pub enum Error<T> {
MembershipNotFound,
AlreadyMember,
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(n: BlockNumberFor<T>) -> Weight {
if n > BlockNumberFor::<T>::zero() {
T::WeightInfo::on_initialize().saturating_add(Self::expire_memberships(n))
} else {
T::WeightInfo::on_initialize()
}
}
}
impl<T: Config> Pallet<T> {
fn unschedule_membership_expiry(idty_id: T::IdtyId, block_number: BlockNumberFor<T>) {
let mut scheduled = MembershipsExpireOn::<T>::get(block_number);
if let Some(pos) = scheduled.iter().position(|x| *x == idty_id) {
scheduled.swap_remove(pos);
MembershipsExpireOn::<T>::set(block_number, scheduled);
}
}
fn insert_membership_and_schedule_expiry(idty_id: T::IdtyId) -> BlockNumberFor<T> {
let block_number = frame_system::pallet::Pallet::<T>::block_number();
let expire_on = block_number + T::MembershipPeriod::get();
Membership::<T>::insert(idty_id, MembershipData { expire_on });
MembershipsExpireOn::<T>::append(expire_on, idty_id);
expire_on
}
pub fn check_add_membership(idty_id: T::IdtyId) -> Result<(), DispatchError> {
ensure!(
Membership::<T>::get(idty_id).is_none(),
Error::<T>::AlreadyMember
);
T::CheckMembershipOpAllowed::check_add_membership(idty_id)?;
Ok(())
}
pub fn check_renew_membership(
idty_id: T::IdtyId,
) -> Result<MembershipData<BlockNumberFor<T>>, DispatchError> {
let membership_data =
Membership::<T>::get(idty_id).ok_or(Error::<T>::MembershipNotFound)?;
T::CheckMembershipOpAllowed::check_renew_membership(idty_id)?;
Ok(membership_data)
}
pub fn try_add_membership(idty_id: T::IdtyId) -> Result<(), DispatchError> {
Self::check_add_membership(idty_id)?;
Self::do_add_membership(idty_id);
Ok(())
}
pub fn try_renew_membership(idty_id: T::IdtyId) -> Result<(), DispatchError> {
let membership_data = Self::check_renew_membership(idty_id)?;
Self::do_renew_membership(idty_id, membership_data);
Ok(())
}
fn do_add_membership(idty_id: T::IdtyId) {
let expire_on = Self::insert_membership_and_schedule_expiry(idty_id);
Self::deposit_event(Event::MembershipAdded {
member: idty_id,
expire_on,
});
T::OnNewMembership::on_created(&idty_id);
}
fn do_renew_membership(
idty_id: T::IdtyId,
membership_data: MembershipData<BlockNumberFor<T>>,
) {
Self::unschedule_membership_expiry(idty_id, membership_data.expire_on);
let expire_on = Self::insert_membership_and_schedule_expiry(idty_id);
Self::deposit_event(Event::MembershipRenewed {
member: idty_id,
expire_on,
});
T::OnNewMembership::on_renewed(&idty_id);
}
pub fn do_remove_membership(idty_id: T::IdtyId, reason: MembershipRemovalReason) -> Weight {
let mut weight = T::DbWeight::get().reads_writes(2, 3);
if let Some(membership_data) = Membership::<T>::take(idty_id) {
Self::unschedule_membership_expiry(idty_id, membership_data.expire_on);
Self::deposit_event(Event::MembershipRemoved {
member: idty_id,
reason,
});
weight += T::OnRemoveMembership::on_removed(&idty_id);
}
weight
}
pub fn expire_memberships(block_number: BlockNumberFor<T>) -> Weight {
let mut expired_idty_count = 0u32;
for idty_id in MembershipsExpireOn::<T>::take(block_number) {
Self::do_remove_membership(idty_id, MembershipRemovalReason::Expired);
expired_idty_count += 1;
}
T::WeightInfo::expire_memberships(expired_idty_count)
}
pub fn is_member(idty_id: &T::IdtyId) -> bool {
Membership::<T>::contains_key(idty_id)
}
}
}
impl<T: Config> sp_runtime::traits::IsMember<T::IdtyId> for Pallet<T> {
fn is_member(idty_id: &T::IdtyId) -> bool {
Self::is_member(idty_id)
}
}
impl<T: Config> MembersCount for Pallet<T> {
fn members_count() -> u32 {
Membership::<T>::count()
}
}