#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod migration;
mod types;
pub mod weights;
extern crate alloc;
use alloc::{boxed::Box, vec, vec::Vec};
use codec::{Decode, Encode, MaxEncodedLen};
use pezframe_support::pezpallet_prelude::*;
use pezframe_system::pezpallet_prelude::*;
use pezsp_runtime::{
traits::{Dispatchable, Saturating, StaticLookup, Zero},
DispatchError, RuntimeDebug,
};
use pezframe_support::{
dispatch::{DispatchResult, DispatchResultWithPostInfo, GetDispatchInfo, PostDispatchInfo},
ensure,
traits::{
ChangeMembers, Currency, Get, InitializeMembers, IsSubType, OnUnbalanced,
ReservableCurrency,
},
weights::Weight,
};
use scale_info::TypeInfo;
pub use pezpallet::*;
pub use types::*;
pub use weights::*;
pub const LOG_TARGET: &str = "runtime::alliance";
pub type ProposalIndex = u32;
type UrlOf<T, I> = BoundedVec<u8, <T as pezpallet::Config<I>>::MaxWebsiteUrlLength>;
type BalanceOf<T, I> =
<<T as Config<I>>::Currency as Currency<<T as pezframe_system::Config>::AccountId>>::Balance;
type NegativeImbalanceOf<T, I> = <<T as Config<I>>::Currency as Currency<
<T as pezframe_system::Config>::AccountId,
>>::NegativeImbalance;
pub trait IdentityVerifier<AccountId> {
fn has_required_identities(who: &AccountId) -> bool;
fn has_good_judgement(who: &AccountId) -> bool;
fn super_account_id(who: &AccountId) -> Option<AccountId>;
}
impl<AccountId> IdentityVerifier<AccountId> for () {
fn has_required_identities(_who: &AccountId) -> bool {
true
}
fn has_good_judgement(_who: &AccountId) -> bool {
true
}
fn super_account_id(_who: &AccountId) -> Option<AccountId> {
None
}
}
pub trait ProposalProvider<AccountId, Hash, Proposal> {
fn propose_proposal(
who: AccountId,
threshold: u32,
proposal: Box<Proposal>,
length_bound: u32,
) -> Result<(u32, u32), DispatchError>;
fn vote_proposal(
who: AccountId,
proposal: Hash,
index: ProposalIndex,
approve: bool,
) -> Result<bool, DispatchError>;
fn close_proposal(
proposal_hash: Hash,
index: ProposalIndex,
proposal_weight_bound: Weight,
length_bound: u32,
) -> DispatchResultWithPostInfo;
fn proposal_of(proposal_hash: Hash) -> Option<Proposal>;
}
#[derive(Copy, Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]
pub enum MemberRole {
Fellow,
Ally,
Retiring,
}
#[derive(
Clone,
PartialEq,
Eq,
RuntimeDebug,
Encode,
Decode,
DecodeWithMemTracking,
TypeInfo,
MaxEncodedLen,
)]
pub enum UnscrupulousItem<AccountId, Url> {
AccountId(AccountId),
Website(Url),
}
type UnscrupulousItemOf<T, I> =
UnscrupulousItem<<T as pezframe_system::Config>::AccountId, UrlOf<T, I>>;
type AccountIdLookupOf<T> = <<T as pezframe_system::Config>::Lookup as StaticLookup>::Source;
#[pezframe_support::pezpallet]
pub mod pezpallet {
use super::*;
#[pezpallet::pezpallet]
#[pezpallet::storage_version(migration::STORAGE_VERSION)]
pub struct Pezpallet<T, I = ()>(PhantomData<(T, I)>);
#[pezpallet::config]
pub trait Config<I: 'static = ()>: pezframe_system::Config {
#[allow(deprecated)]
type RuntimeEvent: From<Event<Self, I>>
+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
type Proposal: Parameter
+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
+ From<pezframe_system::Call<Self>>
+ From<Call<Self, I>>
+ GetDispatchInfo
+ IsSubType<Call<Self, I>>
+ IsType<<Self as pezframe_system::Config>::RuntimeCall>;
type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type MembershipManager: EnsureOrigin<Self::RuntimeOrigin>;
type AnnouncementOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type Currency: ReservableCurrency<Self::AccountId>;
type Slashed: OnUnbalanced<NegativeImbalanceOf<Self, I>>;
type InitializeMembers: InitializeMembers<Self::AccountId>;
type MembershipChanged: ChangeMembers<Self::AccountId>;
type IdentityVerifier: IdentityVerifier<Self::AccountId>;
type ProposalProvider: ProposalProvider<Self::AccountId, Self::Hash, Self::Proposal>;
type MaxProposals: Get<ProposalIndex>;
type MaxFellows: Get<u32>;
type MaxAllies: Get<u32>;
#[pezpallet::constant]
type MaxUnscrupulousItems: Get<u32>;
#[pezpallet::constant]
type MaxWebsiteUrlLength: Get<u32>;
#[pezpallet::constant]
type AllyDeposit: Get<BalanceOf<Self, I>>;
#[pezpallet::constant]
type MaxAnnouncementsCount: Get<u32>;
#[pezpallet::constant]
type MaxMembersCount: Get<u32>;
type WeightInfo: WeightInfo;
type RetirementPeriod: Get<BlockNumberFor<Self>>;
}
#[pezpallet::error]
pub enum Error<T, I = ()> {
AllianceNotYetInitialized,
AllianceAlreadyInitialized,
AlreadyMember,
NotMember,
NotAlly,
NoVotingRights,
AlreadyElevated,
AlreadyUnscrupulous,
AccountNonGrata,
NotListedAsUnscrupulous,
TooManyUnscrupulousItems,
TooLongWebsiteUrl,
InsufficientFunds,
WithoutRequiredIdentityFields,
WithoutGoodIdentityJudgement,
MissingProposalHash,
MissingAnnouncement,
TooManyMembers,
TooManyAnnouncements,
BadWitness,
AlreadyRetiring,
RetirementNoticeNotGiven,
RetirementPeriodNotPassed,
FellowsMissing,
}
#[pezpallet::event]
#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config<I>, I: 'static = ()> {
NewRuleSet { rule: Cid },
Announced { announcement: Cid },
AnnouncementRemoved { announcement: Cid },
MembersInitialized { fellows: Vec<T::AccountId>, allies: Vec<T::AccountId> },
NewAllyJoined {
ally: T::AccountId,
nominator: Option<T::AccountId>,
reserved: Option<BalanceOf<T, I>>,
},
AllyElevated { ally: T::AccountId },
MemberRetirementPeriodStarted { member: T::AccountId },
MemberRetired { member: T::AccountId, unreserved: Option<BalanceOf<T, I>> },
MemberKicked { member: T::AccountId, slashed: Option<BalanceOf<T, I>> },
UnscrupulousItemAdded { items: Vec<UnscrupulousItemOf<T, I>> },
UnscrupulousItemRemoved { items: Vec<UnscrupulousItemOf<T, I>> },
AllianceDisbanded { fellow_members: u32, ally_members: u32, unreserved: u32 },
FellowAbdicated { fellow: T::AccountId },
}
#[pezpallet::genesis_config]
#[derive(pezframe_support::DefaultNoBound)]
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
pub fellows: Vec<T::AccountId>,
pub allies: Vec<T::AccountId>,
#[serde(skip)]
pub phantom: PhantomData<(T, I)>,
}
#[pezpallet::hooks]
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pezpallet<T, I> {
#[cfg(feature = "try-runtime")]
fn try_state(_n: BlockNumberFor<T>) -> Result<(), pezsp_runtime::TryRuntimeError> {
Self::do_try_state()
}
}
#[pezpallet::genesis_build]
impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
fn build(&self) {
for m in self.fellows.iter().chain(self.allies.iter()) {
assert!(
Pezpallet::<T, I>::has_identity(m).is_ok(),
"Member does not set identity!"
);
}
if !self.fellows.is_empty() {
assert!(
!Pezpallet::<T, I>::has_member(MemberRole::Fellow),
"Fellows are already initialized!"
);
let members: BoundedVec<T::AccountId, T::MaxMembersCount> =
self.fellows.clone().try_into().expect("Too many genesis fellows");
Members::<T, I>::insert(MemberRole::Fellow, members);
}
if !self.allies.is_empty() {
assert!(
!Pezpallet::<T, I>::has_member(MemberRole::Ally),
"Allies are already initialized!"
);
assert!(
!self.fellows.is_empty(),
"Fellows must be provided to initialize the Alliance"
);
let members: BoundedVec<T::AccountId, T::MaxMembersCount> =
self.allies.clone().try_into().expect("Too many genesis allies");
Members::<T, I>::insert(MemberRole::Ally, members);
}
T::InitializeMembers::initialize_members(self.fellows.as_slice())
}
}
#[pezpallet::storage]
pub type Rule<T: Config<I>, I: 'static = ()> = StorageValue<_, Cid, OptionQuery>;
#[pezpallet::storage]
pub type Announcements<T: Config<I>, I: 'static = ()> =
StorageValue<_, BoundedVec<Cid, T::MaxAnnouncementsCount>, ValueQuery>;
#[pezpallet::storage]
pub type DepositOf<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, T::AccountId, BalanceOf<T, I>, OptionQuery>;
#[pezpallet::storage]
pub type Members<T: Config<I>, I: 'static = ()> = StorageMap<
_,
Twox64Concat,
MemberRole,
BoundedVec<T::AccountId, T::MaxMembersCount>,
ValueQuery,
>;
#[pezpallet::storage]
pub type RetiringMembers<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, T::AccountId, BlockNumberFor<T>, OptionQuery>;
#[pezpallet::storage]
pub type UnscrupulousAccounts<T: Config<I>, I: 'static = ()> =
StorageValue<_, BoundedVec<T::AccountId, T::MaxUnscrupulousItems>, ValueQuery>;
#[pezpallet::storage]
pub type UnscrupulousWebsites<T: Config<I>, I: 'static = ()> =
StorageValue<_, BoundedVec<UrlOf<T, I>, T::MaxUnscrupulousItems>, ValueQuery>;
#[pezpallet::call(weight(<T as Config<I>>::WeightInfo))]
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
#[pezpallet::call_index(0)]
#[pezpallet::weight(T::WeightInfo::propose_proposed(
*length_bound, // B
T::MaxFellows::get(), // M
T::MaxProposals::get(), // P2
))]
pub fn propose(
origin: OriginFor<T>,
#[pezpallet::compact] threshold: u32,
proposal: Box<<T as Config<I>>::Proposal>,
#[pezpallet::compact] length_bound: u32,
) -> DispatchResult {
let proposer = ensure_signed(origin)?;
ensure!(Self::has_voting_rights(&proposer), Error::<T, I>::NoVotingRights);
T::ProposalProvider::propose_proposal(proposer, threshold, proposal, length_bound)?;
Ok(())
}
#[pezpallet::call_index(1)]
#[pezpallet::weight(T::WeightInfo::vote(T::MaxFellows::get()))]
pub fn vote(
origin: OriginFor<T>,
proposal: T::Hash,
#[pezpallet::compact] index: ProposalIndex,
approve: bool,
) -> DispatchResult {
let who = ensure_signed(origin)?;
ensure!(Self::has_voting_rights(&who), Error::<T, I>::NoVotingRights);
T::ProposalProvider::vote_proposal(who, proposal, index, approve)?;
Ok(())
}
#[pezpallet::call_index(3)]
#[pezpallet::weight(T::WeightInfo::init_members(
fellows.len() as u32,
allies.len() as u32,
))]
pub fn init_members(
origin: OriginFor<T>,
fellows: Vec<T::AccountId>,
allies: Vec<T::AccountId>,
) -> DispatchResult {
ensure_root(origin)?;
ensure!(!fellows.is_empty(), Error::<T, I>::FellowsMissing);
ensure!(!Self::is_initialized(), Error::<T, I>::AllianceAlreadyInitialized);
let mut fellows: BoundedVec<T::AccountId, T::MaxMembersCount> =
fellows.try_into().map_err(|_| Error::<T, I>::TooManyMembers)?;
let mut allies: BoundedVec<T::AccountId, T::MaxMembersCount> =
allies.try_into().map_err(|_| Error::<T, I>::TooManyMembers)?;
for member in fellows.iter().chain(allies.iter()) {
Self::has_identity(member)?;
}
fellows.sort();
Members::<T, I>::insert(&MemberRole::Fellow, fellows.clone());
allies.sort();
Members::<T, I>::insert(&MemberRole::Ally, allies.clone());
let mut voteable_members = fellows.clone();
voteable_members.sort();
T::InitializeMembers::initialize_members(&voteable_members);
log::debug!(
target: LOG_TARGET,
"Initialize alliance fellows: {:?}, allies: {:?}",
fellows,
allies
);
Self::deposit_event(Event::MembersInitialized {
fellows: fellows.into(),
allies: allies.into(),
});
Ok(())
}
#[pezpallet::call_index(4)]
#[pezpallet::weight(T::WeightInfo::disband(
witness.fellow_members,
witness.ally_members,
witness.fellow_members.saturating_add(witness.ally_members),
))]
pub fn disband(
origin: OriginFor<T>,
witness: DisbandWitness,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
ensure!(!witness.is_zero(), Error::<T, I>::BadWitness);
ensure!(
Self::voting_members_count() <= witness.fellow_members,
Error::<T, I>::BadWitness
);
ensure!(Self::ally_members_count() <= witness.ally_members, Error::<T, I>::BadWitness);
ensure!(Self::is_initialized(), Error::<T, I>::AllianceNotYetInitialized);
let voting_members = Self::voting_members();
T::MembershipChanged::change_members_sorted(&[], &voting_members, &[]);
let ally_members = Self::members_of(MemberRole::Ally);
let mut unreserve_count: u32 = 0;
for member in voting_members.iter().chain(ally_members.iter()) {
if let Some(deposit) = DepositOf::<T, I>::take(&member) {
let err_amount = T::Currency::unreserve(&member, deposit);
debug_assert!(err_amount.is_zero());
unreserve_count += 1;
}
}
Members::<T, I>::remove(&MemberRole::Fellow);
Members::<T, I>::remove(&MemberRole::Ally);
Self::deposit_event(Event::AllianceDisbanded {
fellow_members: voting_members.len() as u32,
ally_members: ally_members.len() as u32,
unreserved: unreserve_count,
});
Ok(Some(T::WeightInfo::disband(
voting_members.len() as u32,
ally_members.len() as u32,
unreserve_count,
))
.into())
}
#[pezpallet::call_index(5)]
pub fn set_rule(origin: OriginFor<T>, rule: Cid) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
Rule::<T, I>::put(&rule);
Self::deposit_event(Event::NewRuleSet { rule });
Ok(())
}
#[pezpallet::call_index(6)]
pub fn announce(origin: OriginFor<T>, announcement: Cid) -> DispatchResult {
T::AnnouncementOrigin::ensure_origin(origin)?;
let mut announcements = <Announcements<T, I>>::get();
announcements
.try_push(announcement.clone())
.map_err(|_| Error::<T, I>::TooManyAnnouncements)?;
<Announcements<T, I>>::put(announcements);
Self::deposit_event(Event::Announced { announcement });
Ok(())
}
#[pezpallet::call_index(7)]
pub fn remove_announcement(origin: OriginFor<T>, announcement: Cid) -> DispatchResult {
T::AnnouncementOrigin::ensure_origin(origin)?;
let mut announcements = <Announcements<T, I>>::get();
let pos = announcements
.binary_search(&announcement)
.ok()
.ok_or(Error::<T, I>::MissingAnnouncement)?;
announcements.remove(pos);
<Announcements<T, I>>::put(announcements);
Self::deposit_event(Event::AnnouncementRemoved { announcement });
Ok(())
}
#[pezpallet::call_index(8)]
pub fn join_alliance(origin: OriginFor<T>) -> DispatchResult {
let who = ensure_signed(origin)?;
ensure!(Self::is_initialized(), Error::<T, I>::AllianceNotYetInitialized);
ensure!(!Self::is_unscrupulous_account(&who), Error::<T, I>::AccountNonGrata);
ensure!(!Self::is_member(&who), Error::<T, I>::AlreadyMember);
Self::has_identity(&who)?;
let deposit = T::AllyDeposit::get();
T::Currency::reserve(&who, deposit).map_err(|_| Error::<T, I>::InsufficientFunds)?;
<DepositOf<T, I>>::insert(&who, deposit);
Self::add_member(&who, MemberRole::Ally)?;
Self::deposit_event(Event::NewAllyJoined {
ally: who,
nominator: None,
reserved: Some(deposit),
});
Ok(())
}
#[pezpallet::call_index(9)]
pub fn nominate_ally(origin: OriginFor<T>, who: AccountIdLookupOf<T>) -> DispatchResult {
let nominator = ensure_signed(origin)?;
ensure!(Self::has_voting_rights(&nominator), Error::<T, I>::NoVotingRights);
let who = T::Lookup::lookup(who)?;
ensure!(!Self::is_unscrupulous_account(&who), Error::<T, I>::AccountNonGrata);
ensure!(!Self::is_member(&who), Error::<T, I>::AlreadyMember);
Self::has_identity(&who)?;
Self::add_member(&who, MemberRole::Ally)?;
Self::deposit_event(Event::NewAllyJoined {
ally: who,
nominator: Some(nominator),
reserved: None,
});
Ok(())
}
#[pezpallet::call_index(10)]
pub fn elevate_ally(origin: OriginFor<T>, ally: AccountIdLookupOf<T>) -> DispatchResult {
T::MembershipManager::ensure_origin(origin)?;
let ally = T::Lookup::lookup(ally)?;
ensure!(Self::is_ally(&ally), Error::<T, I>::NotAlly);
ensure!(!Self::has_voting_rights(&ally), Error::<T, I>::AlreadyElevated);
Self::remove_member(&ally, MemberRole::Ally)?;
Self::add_member(&ally, MemberRole::Fellow)?;
Self::deposit_event(Event::AllyElevated { ally });
Ok(())
}
#[pezpallet::call_index(11)]
pub fn give_retirement_notice(origin: OriginFor<T>) -> DispatchResult {
let who = ensure_signed(origin)?;
let role = Self::member_role_of(&who).ok_or(Error::<T, I>::NotMember)?;
ensure!(role.ne(&MemberRole::Retiring), Error::<T, I>::AlreadyRetiring);
Self::remove_member(&who, role)?;
Self::add_member(&who, MemberRole::Retiring)?;
<RetiringMembers<T, I>>::insert(
&who,
pezframe_system::Pezpallet::<T>::block_number()
.saturating_add(T::RetirementPeriod::get()),
);
Self::deposit_event(Event::MemberRetirementPeriodStarted { member: who });
Ok(())
}
#[pezpallet::call_index(12)]
pub fn retire(origin: OriginFor<T>) -> DispatchResult {
let who = ensure_signed(origin)?;
let retirement_period_end = RetiringMembers::<T, I>::get(&who)
.ok_or(Error::<T, I>::RetirementNoticeNotGiven)?;
ensure!(
pezframe_system::Pezpallet::<T>::block_number() >= retirement_period_end,
Error::<T, I>::RetirementPeriodNotPassed
);
Self::remove_member(&who, MemberRole::Retiring)?;
<RetiringMembers<T, I>>::remove(&who);
let deposit = DepositOf::<T, I>::take(&who);
if let Some(deposit) = deposit {
let err_amount = T::Currency::unreserve(&who, deposit);
debug_assert!(err_amount.is_zero());
}
Self::deposit_event(Event::MemberRetired { member: who, unreserved: deposit });
Ok(())
}
#[pezpallet::call_index(13)]
pub fn kick_member(origin: OriginFor<T>, who: AccountIdLookupOf<T>) -> DispatchResult {
T::MembershipManager::ensure_origin(origin)?;
let member = T::Lookup::lookup(who)?;
let role = Self::member_role_of(&member).ok_or(Error::<T, I>::NotMember)?;
Self::remove_member(&member, role)?;
let deposit = DepositOf::<T, I>::take(member.clone());
if let Some(deposit) = deposit {
T::Slashed::on_unbalanced(T::Currency::slash_reserved(&member, deposit).0);
}
Self::deposit_event(Event::MemberKicked { member, slashed: deposit });
Ok(())
}
#[pezpallet::call_index(14)]
#[pezpallet::weight(T::WeightInfo::add_unscrupulous_items(items.len() as u32, T::MaxWebsiteUrlLength::get()))]
pub fn add_unscrupulous_items(
origin: OriginFor<T>,
items: Vec<UnscrupulousItemOf<T, I>>,
) -> DispatchResult {
T::AnnouncementOrigin::ensure_origin(origin)?;
let mut accounts = vec![];
let mut webs = vec![];
for info in items.iter() {
ensure!(!Self::is_unscrupulous(info), Error::<T, I>::AlreadyUnscrupulous);
match info {
UnscrupulousItem::AccountId(who) => accounts.push(who.clone()),
UnscrupulousItem::Website(url) => {
ensure!(
url.len() as u32 <= T::MaxWebsiteUrlLength::get(),
Error::<T, I>::TooLongWebsiteUrl
);
webs.push(url.clone());
},
}
}
Self::do_add_unscrupulous_items(&mut accounts, &mut webs)?;
Self::deposit_event(Event::UnscrupulousItemAdded { items });
Ok(())
}
#[pezpallet::call_index(15)]
#[pezpallet::weight(<T as Config<I>>::WeightInfo::remove_unscrupulous_items(
items.len() as u32, T::MaxWebsiteUrlLength::get()
))]
pub fn remove_unscrupulous_items(
origin: OriginFor<T>,
items: Vec<UnscrupulousItemOf<T, I>>,
) -> DispatchResult {
T::AnnouncementOrigin::ensure_origin(origin)?;
let mut accounts = vec![];
let mut webs = vec![];
for info in items.iter() {
ensure!(Self::is_unscrupulous(info), Error::<T, I>::NotListedAsUnscrupulous);
match info {
UnscrupulousItem::AccountId(who) => accounts.push(who.clone()),
UnscrupulousItem::Website(url) => webs.push(url.clone()),
}
}
Self::do_remove_unscrupulous_items(&mut accounts, &mut webs)?;
Self::deposit_event(Event::UnscrupulousItemRemoved { items });
Ok(())
}
#[pezpallet::call_index(16)]
#[pezpallet::weight({
let b = *length_bound;
let m = T::MaxFellows::get();
let p1 = *proposal_weight_bound;
let p2 = T::MaxProposals::get();
T::WeightInfo::close_early_approved(b, m, p2)
.max(T::WeightInfo::close_early_disapproved(m, p2))
.max(T::WeightInfo::close_approved(b, m, p2))
.max(T::WeightInfo::close_disapproved(m, p2))
.saturating_add(p1)
})]
pub fn close(
origin: OriginFor<T>,
proposal_hash: T::Hash,
#[pezpallet::compact] index: ProposalIndex,
proposal_weight_bound: Weight,
#[pezpallet::compact] length_bound: u32,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
ensure!(Self::has_voting_rights(&who), Error::<T, I>::NoVotingRights);
Self::do_close(proposal_hash, index, proposal_weight_bound, length_bound)
}
#[pezpallet::call_index(17)]
pub fn abdicate_fellow_status(origin: OriginFor<T>) -> DispatchResult {
let who = ensure_signed(origin)?;
let role = Self::member_role_of(&who).ok_or(Error::<T, I>::NotMember)?;
ensure!(Self::has_voting_rights(&who), Error::<T, I>::NoVotingRights);
Self::remove_member(&who, role)?;
Self::add_member(&who, MemberRole::Ally)?;
Self::deposit_event(Event::FellowAbdicated { fellow: who });
Ok(())
}
}
}
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
fn is_initialized() -> bool {
Self::has_member(MemberRole::Fellow) || Self::has_member(MemberRole::Ally)
}
fn has_member(role: MemberRole) -> bool {
Members::<T, I>::decode_len(role).unwrap_or_default() > 0
}
fn member_role_of(who: &T::AccountId) -> Option<MemberRole> {
Members::<T, I>::iter()
.find_map(|(r, members)| if members.contains(who) { Some(r) } else { None })
}
pub fn is_member(who: &T::AccountId) -> bool {
Self::member_role_of(who).is_some()
}
pub fn is_member_of(who: &T::AccountId, role: MemberRole) -> bool {
Members::<T, I>::get(role).contains(&who)
}
fn is_ally(who: &T::AccountId) -> bool {
Self::is_member_of(who, MemberRole::Ally)
}
fn has_voting_rights(who: &T::AccountId) -> bool {
Self::is_member_of(who, MemberRole::Fellow)
}
fn ally_members_count() -> u32 {
Members::<T, I>::decode_len(MemberRole::Ally).unwrap_or(0) as u32
}
fn voting_members_count() -> u32 {
Members::<T, I>::decode_len(MemberRole::Fellow).unwrap_or(0) as u32
}
fn members_of(role: MemberRole) -> Vec<T::AccountId> {
Members::<T, I>::get(role).into_inner()
}
fn voting_members() -> Vec<T::AccountId> {
Self::members_of(MemberRole::Fellow)
}
fn add_member(who: &T::AccountId, role: MemberRole) -> DispatchResult {
<Members<T, I>>::try_mutate(role, |members| -> DispatchResult {
let pos = members.binary_search(who).err().ok_or(Error::<T, I>::AlreadyMember)?;
members
.try_insert(pos, who.clone())
.map_err(|_| Error::<T, I>::TooManyMembers)?;
Ok(())
})?;
if role == MemberRole::Fellow {
let members = Self::voting_members();
T::MembershipChanged::change_members_sorted(&[who.clone()], &[], &members[..]);
}
Ok(())
}
fn remove_member(who: &T::AccountId, role: MemberRole) -> DispatchResult {
<Members<T, I>>::try_mutate(role, |members| -> DispatchResult {
let pos = members.binary_search(who).ok().ok_or(Error::<T, I>::NotMember)?;
members.remove(pos);
Ok(())
})?;
if role == MemberRole::Fellow {
let members = Self::voting_members();
T::MembershipChanged::change_members_sorted(&[], &[who.clone()], &members[..]);
}
Ok(())
}
fn is_unscrupulous(info: &UnscrupulousItemOf<T, I>) -> bool {
match info {
UnscrupulousItem::Website(url) => <UnscrupulousWebsites<T, I>>::get().contains(url),
UnscrupulousItem::AccountId(who) => <UnscrupulousAccounts<T, I>>::get().contains(who),
}
}
fn is_unscrupulous_account(who: &T::AccountId) -> bool {
<UnscrupulousAccounts<T, I>>::get().contains(who)
}
fn do_add_unscrupulous_items(
new_accounts: &mut Vec<T::AccountId>,
new_webs: &mut Vec<UrlOf<T, I>>,
) -> DispatchResult {
if !new_accounts.is_empty() {
<UnscrupulousAccounts<T, I>>::try_mutate(|accounts| -> DispatchResult {
accounts
.try_append(new_accounts)
.map_err(|_| Error::<T, I>::TooManyUnscrupulousItems)?;
accounts.sort();
Ok(())
})?;
}
if !new_webs.is_empty() {
<UnscrupulousWebsites<T, I>>::try_mutate(|webs| -> DispatchResult {
webs.try_append(new_webs).map_err(|_| Error::<T, I>::TooManyUnscrupulousItems)?;
webs.sort();
Ok(())
})?;
}
Ok(())
}
fn do_remove_unscrupulous_items(
out_accounts: &mut Vec<T::AccountId>,
out_webs: &mut Vec<UrlOf<T, I>>,
) -> DispatchResult {
if !out_accounts.is_empty() {
<UnscrupulousAccounts<T, I>>::try_mutate(|accounts| -> DispatchResult {
for who in out_accounts.iter() {
let pos = accounts
.binary_search(who)
.ok()
.ok_or(Error::<T, I>::NotListedAsUnscrupulous)?;
accounts.remove(pos);
}
Ok(())
})?;
}
if !out_webs.is_empty() {
<UnscrupulousWebsites<T, I>>::try_mutate(|webs| -> DispatchResult {
for web in out_webs.iter() {
let pos = webs
.binary_search(web)
.ok()
.ok_or(Error::<T, I>::NotListedAsUnscrupulous)?;
webs.remove(pos);
}
Ok(())
})?;
}
Ok(())
}
fn has_identity(who: &T::AccountId) -> DispatchResult {
let judgement = |who: &T::AccountId| -> DispatchResult {
ensure!(
T::IdentityVerifier::has_required_identities(who),
Error::<T, I>::WithoutRequiredIdentityFields
);
ensure!(
T::IdentityVerifier::has_good_judgement(who),
Error::<T, I>::WithoutGoodIdentityJudgement
);
Ok(())
};
let res = judgement(who);
if res.is_err() {
if let Some(parent) = T::IdentityVerifier::super_account_id(who) {
return judgement(&parent);
}
}
res
}
fn do_close(
proposal_hash: T::Hash,
index: ProposalIndex,
proposal_weight_bound: Weight,
length_bound: u32,
) -> DispatchResultWithPostInfo {
let info = T::ProposalProvider::close_proposal(
proposal_hash,
index,
proposal_weight_bound,
length_bound,
)?;
Ok(info.into())
}
}
#[cfg(any(feature = "try-runtime", test))]
impl<T: Config<I>, I: 'static> Pezpallet<T, I> {
pub fn do_try_state() -> Result<(), pezsp_runtime::TryRuntimeError> {
Self::try_state_members_are_disjoint()?;
Self::try_state_members_are_sorted()?;
Self::try_state_retiring_members_are_consistent()?;
Self::try_state_deposit_of_is_consistent()?;
Self::try_state_unscrupulous_items_are_sorted()?;
Self::try_state_announcements_are_sorted()?;
Ok(())
}
fn try_state_members_are_disjoint() -> Result<(), pezsp_runtime::TryRuntimeError> {
let fellows = Members::<T, I>::get(MemberRole::Fellow);
let allies = Members::<T, I>::get(MemberRole::Ally);
for fellow in fellows.iter() {
ensure!(allies.binary_search(fellow).is_err(), "Member is both Fellow and Ally");
}
Ok(())
}
fn try_state_members_are_sorted() -> Result<(), pezsp_runtime::TryRuntimeError> {
let roles = [MemberRole::Fellow, MemberRole::Ally, MemberRole::Retiring];
for role in roles.iter() {
let members = Members::<T, I>::get(role);
let mut sorted_members = members.clone();
sorted_members.sort();
ensure!(members == sorted_members, "Members of a role are not sorted");
}
Ok(())
}
fn try_state_retiring_members_are_consistent() -> Result<(), pezsp_runtime::TryRuntimeError> {
let retiring_in_members = Members::<T, I>::get(MemberRole::Retiring);
let retiring_keys_count = RetiringMembers::<T, I>::iter_keys().count();
ensure!(
retiring_in_members.len() == retiring_keys_count,
"Count mismatch between Members<Retiring> and RetiringMembers map"
);
for member in retiring_in_members.iter() {
ensure!(
RetiringMembers::<T, I>::contains_key(member),
"Retiring member not found in RetiringMembers map"
);
}
Ok(())
}
fn try_state_deposit_of_is_consistent() -> Result<(), pezsp_runtime::TryRuntimeError> {
for (who, _) in DepositOf::<T, I>::iter() {
ensure!(Self::is_member(&who), "Account with deposit is not an alliance member");
}
Ok(())
}
fn try_state_unscrupulous_items_are_sorted() -> Result<(), pezsp_runtime::TryRuntimeError> {
let accounts = UnscrupulousAccounts::<T, I>::get();
let mut sorted_accounts = accounts.clone();
sorted_accounts.sort();
ensure!(accounts == sorted_accounts, "UnscrupulousAccounts is not sorted");
let websites = UnscrupulousWebsites::<T, I>::get();
let mut sorted_websites = websites.clone();
sorted_websites.sort();
ensure!(websites == sorted_websites, "UnscrupulousWebsites is not sorted");
Ok(())
}
fn try_state_announcements_are_sorted() -> Result<(), pezsp_runtime::TryRuntimeError> {
let announcements = Announcements::<T, I>::get();
let mut sorted_announcements = announcements.clone();
sorted_announcements.sort();
ensure!(announcements == sorted_announcements, "Announcements is not sorted");
Ok(())
}
}