#![cfg_attr(not(feature = "std"), no_std)]
use codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_io::storage;
use sp_runtime::{
traits::{Dispatchable, Hash},
DispatchError, RuntimeDebug,
};
use sp_std::{marker::PhantomData, prelude::*, result};
use frame_support::{
dispatch::{
DispatchResult, DispatchResultWithPostInfo, GetDispatchInfo, Pays, PostDispatchInfo,
},
ensure, impl_ensure_origin_with_arg_ignoring_arg,
traits::{
Backing, ChangeMembers, EnsureOrigin, EnsureOriginWithArg, Get, GetBacking,
InitializeMembers, StorageVersion,
},
weights::Weight,
};
#[cfg(any(feature = "try-runtime", test))]
use sp_runtime::TryRuntimeError;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod migrations;
pub mod weights;
pub use pallet::*;
pub use weights::WeightInfo;
const LOG_TARGET: &str = "runtime::collective";
pub type ProposalIndex = u32;
pub type MemberCount = u32;
pub trait DefaultVote {
fn default_vote(
prime_vote: Option<bool>,
yes_votes: MemberCount,
no_votes: MemberCount,
len: MemberCount,
) -> bool;
}
pub struct PrimeDefaultVote;
impl DefaultVote for PrimeDefaultVote {
fn default_vote(
prime_vote: Option<bool>,
_yes_votes: MemberCount,
_no_votes: MemberCount,
_len: MemberCount,
) -> bool {
prime_vote.unwrap_or(false)
}
}
pub struct MoreThanMajorityThenPrimeDefaultVote;
impl DefaultVote for MoreThanMajorityThenPrimeDefaultVote {
fn default_vote(
prime_vote: Option<bool>,
yes_votes: MemberCount,
_no_votes: MemberCount,
len: MemberCount,
) -> bool {
let more_than_majority = yes_votes * 2 > len;
more_than_majority || prime_vote.unwrap_or(false)
}
}
#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(I))]
#[codec(mel_bound(AccountId: MaxEncodedLen))]
pub enum RawOrigin<AccountId, I> {
Members(MemberCount, MemberCount),
Member(AccountId),
_Phantom(PhantomData<I>),
}
impl<AccountId, I> GetBacking for RawOrigin<AccountId, I> {
fn get_backing(&self) -> Option<Backing> {
match self {
RawOrigin::Members(n, d) => Some(Backing { approvals: *n, eligible: *d }),
_ => None,
}
}
}
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct Votes<AccountId, BlockNumber> {
index: ProposalIndex,
threshold: MemberCount,
ayes: Vec<AccountId>,
nays: Vec<AccountId>,
end: BlockNumber,
}
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
const STORAGE_VERSION: StorageVersion = StorageVersion::new(4);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info]
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
#[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config {
type RuntimeOrigin: From<RawOrigin<Self::AccountId, I>>;
type Proposal: Parameter
+ Dispatchable<
RuntimeOrigin = <Self as Config<I>>::RuntimeOrigin,
PostInfo = PostDispatchInfo,
> + From<frame_system::Call<Self>>
+ GetDispatchInfo;
type RuntimeEvent: From<Event<Self, I>>
+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
type MotionDuration: Get<BlockNumberFor<Self>>;
type MaxProposals: Get<ProposalIndex>;
type MaxMembers: Get<MemberCount>;
type DefaultVote: DefaultVote;
type WeightInfo: WeightInfo;
type SetMembersOrigin: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin>;
#[pallet::constant]
type MaxProposalWeight: Get<Weight>;
}
#[pallet::genesis_config]
#[derive(frame_support::DefaultNoBound)]
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
#[serde(skip)]
pub phantom: PhantomData<I>,
pub members: Vec<T::AccountId>,
}
#[pallet::genesis_build]
impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
fn build(&self) {
use sp_std::collections::btree_set::BTreeSet;
let members_set: BTreeSet<_> = self.members.iter().collect();
assert_eq!(
members_set.len(),
self.members.len(),
"Members cannot contain duplicate accounts."
);
assert!(
self.members.len() <= T::MaxMembers::get() as usize,
"Members length cannot exceed MaxMembers.",
);
Pallet::<T, I>::initialize_members(&self.members)
}
}
#[pallet::origin]
pub type Origin<T, I = ()> = RawOrigin<<T as frame_system::Config>::AccountId, I>;
#[pallet::storage]
pub type Proposals<T: Config<I>, I: 'static = ()> =
StorageValue<_, BoundedVec<T::Hash, T::MaxProposals>, ValueQuery>;
#[pallet::storage]
pub type ProposalOf<T: Config<I>, I: 'static = ()> =
StorageMap<_, Identity, T::Hash, <T as Config<I>>::Proposal, OptionQuery>;
#[pallet::storage]
pub type Voting<T: Config<I>, I: 'static = ()> =
StorageMap<_, Identity, T::Hash, Votes<T::AccountId, BlockNumberFor<T>>, OptionQuery>;
#[pallet::storage]
pub type ProposalCount<T: Config<I>, I: 'static = ()> = StorageValue<_, u32, ValueQuery>;
#[pallet::storage]
pub type Members<T: Config<I>, I: 'static = ()> =
StorageValue<_, Vec<T::AccountId>, ValueQuery>;
#[pallet::storage]
pub type Prime<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId, OptionQuery>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config<I>, I: 'static = ()> {
Proposed {
account: T::AccountId,
proposal_index: ProposalIndex,
proposal_hash: T::Hash,
threshold: MemberCount,
},
Voted {
account: T::AccountId,
proposal_hash: T::Hash,
voted: bool,
yes: MemberCount,
no: MemberCount,
},
Approved { proposal_hash: T::Hash },
Disapproved { proposal_hash: T::Hash },
Executed { proposal_hash: T::Hash, result: DispatchResult },
MemberExecuted { proposal_hash: T::Hash, result: DispatchResult },
Closed { proposal_hash: T::Hash, yes: MemberCount, no: MemberCount },
}
#[pallet::error]
pub enum Error<T, I = ()> {
NotMember,
DuplicateProposal,
ProposalMissing,
WrongIndex,
DuplicateVote,
AlreadyInitialized,
TooEarly,
TooManyProposals,
WrongProposalWeight,
WrongProposalLength,
PrimeAccountNotMember,
}
#[pallet::hooks]
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
#[cfg(feature = "try-runtime")]
fn try_state(_n: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
Self::do_try_state()
}
}
#[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
#[pallet::call_index(0)]
#[pallet::weight((
T::WeightInfo::set_members(
*old_count, new_members.len() as u32, T::MaxProposals::get() ),
DispatchClass::Operational
))]
pub fn set_members(
origin: OriginFor<T>,
new_members: Vec<T::AccountId>,
prime: Option<T::AccountId>,
old_count: MemberCount,
) -> DispatchResultWithPostInfo {
T::SetMembersOrigin::ensure_origin(origin)?;
if new_members.len() > T::MaxMembers::get() as usize {
log::error!(
target: LOG_TARGET,
"New members count ({}) exceeds maximum amount of members expected ({}).",
new_members.len(),
T::MaxMembers::get(),
);
}
let old = Members::<T, I>::get();
if old.len() > old_count as usize {
log::warn!(
target: LOG_TARGET,
"Wrong count used to estimate set_members weight. expected ({}) vs actual ({})",
old_count,
old.len(),
);
}
if let Some(p) = &prime {
ensure!(new_members.contains(p), Error::<T, I>::PrimeAccountNotMember);
}
let mut new_members = new_members;
new_members.sort();
<Self as ChangeMembers<T::AccountId>>::set_members_sorted(&new_members, &old);
Prime::<T, I>::set(prime);
Ok(Some(T::WeightInfo::set_members(
old.len() as u32, new_members.len() as u32, T::MaxProposals::get(), ))
.into())
}
#[pallet::call_index(1)]
#[pallet::weight((
T::WeightInfo::execute(
*length_bound, T::MaxMembers::get(), ).saturating_add(proposal.get_dispatch_info().weight), DispatchClass::Operational
))]
pub fn execute(
origin: OriginFor<T>,
proposal: Box<<T as Config<I>>::Proposal>,
#[pallet::compact] length_bound: u32,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
let members = Members::<T, I>::get();
ensure!(members.contains(&who), Error::<T, I>::NotMember);
let proposal_len = proposal.encoded_size();
ensure!(proposal_len <= length_bound as usize, Error::<T, I>::WrongProposalLength);
let proposal_hash = T::Hashing::hash_of(&proposal);
let result = proposal.dispatch(RawOrigin::Member(who).into());
Self::deposit_event(Event::MemberExecuted {
proposal_hash,
result: result.map(|_| ()).map_err(|e| e.error),
});
Ok(get_result_weight(result)
.map(|w| {
T::WeightInfo::execute(
proposal_len as u32, members.len() as u32, )
.saturating_add(w) })
.into())
}
#[pallet::call_index(2)]
#[pallet::weight((
if *threshold < 2 {
T::WeightInfo::propose_execute(
*length_bound, T::MaxMembers::get(), ).saturating_add(proposal.get_dispatch_info().weight) } else {
T::WeightInfo::propose_proposed(
*length_bound, T::MaxMembers::get(), T::MaxProposals::get(), )
},
DispatchClass::Operational
))]
pub fn propose(
origin: OriginFor<T>,
#[pallet::compact] threshold: MemberCount,
proposal: Box<<T as Config<I>>::Proposal>,
#[pallet::compact] length_bound: u32,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
let members = Members::<T, I>::get();
ensure!(members.contains(&who), Error::<T, I>::NotMember);
if threshold < 2 {
let (proposal_len, result) = Self::do_propose_execute(proposal, length_bound)?;
Ok(get_result_weight(result)
.map(|w| {
T::WeightInfo::propose_execute(
proposal_len as u32, members.len() as u32, )
.saturating_add(w) })
.into())
} else {
let (proposal_len, active_proposals) =
Self::do_propose_proposed(who, threshold, proposal, length_bound)?;
Ok(Some(T::WeightInfo::propose_proposed(
proposal_len as u32, members.len() as u32, active_proposals, ))
.into())
}
}
#[pallet::call_index(3)]
#[pallet::weight((T::WeightInfo::vote(T::MaxMembers::get()), DispatchClass::Operational))]
pub fn vote(
origin: OriginFor<T>,
proposal: T::Hash,
#[pallet::compact] index: ProposalIndex,
approve: bool,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
let members = Members::<T, I>::get();
ensure!(members.contains(&who), Error::<T, I>::NotMember);
let is_account_voting_first_time = Self::do_vote(who, proposal, index, approve)?;
if is_account_voting_first_time {
Ok((Some(T::WeightInfo::vote(members.len() as u32)), Pays::No).into())
} else {
Ok((Some(T::WeightInfo::vote(members.len() as u32)), Pays::Yes).into())
}
}
#[pallet::call_index(5)]
#[pallet::weight(T::WeightInfo::disapprove_proposal(T::MaxProposals::get()))]
pub fn disapprove_proposal(
origin: OriginFor<T>,
proposal_hash: T::Hash,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
let proposal_count = Self::do_disapprove_proposal(proposal_hash);
Ok(Some(T::WeightInfo::disapprove_proposal(proposal_count)).into())
}
#[pallet::call_index(6)]
#[pallet::weight((
{
let b = *length_bound;
let m = T::MaxMembers::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)
},
DispatchClass::Operational
))]
pub fn close(
origin: OriginFor<T>,
proposal_hash: T::Hash,
#[pallet::compact] index: ProposalIndex,
proposal_weight_bound: Weight,
#[pallet::compact] length_bound: u32,
) -> DispatchResultWithPostInfo {
let _ = ensure_signed(origin)?;
Self::do_close(proposal_hash, index, proposal_weight_bound, length_bound)
}
}
}
fn get_result_weight(result: DispatchResultWithPostInfo) -> Option<Weight> {
match result {
Ok(post_info) => post_info.actual_weight,
Err(err) => err.post_info.actual_weight,
}
}
impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub fn is_member(who: &T::AccountId) -> bool {
Members::<T, I>::get().contains(who)
}
pub fn do_propose_execute(
proposal: Box<<T as Config<I>>::Proposal>,
length_bound: MemberCount,
) -> Result<(u32, DispatchResultWithPostInfo), DispatchError> {
let proposal_len = proposal.encoded_size();
ensure!(proposal_len <= length_bound as usize, Error::<T, I>::WrongProposalLength);
let proposal_weight = proposal.get_dispatch_info().weight;
ensure!(
proposal_weight.all_lte(T::MaxProposalWeight::get()),
Error::<T, I>::WrongProposalWeight
);
let proposal_hash = T::Hashing::hash_of(&proposal);
ensure!(!<ProposalOf<T, I>>::contains_key(proposal_hash), Error::<T, I>::DuplicateProposal);
let seats = Members::<T, I>::get().len() as MemberCount;
let result = proposal.dispatch(RawOrigin::Members(1, seats).into());
Self::deposit_event(Event::Executed {
proposal_hash,
result: result.map(|_| ()).map_err(|e| e.error),
});
Ok((proposal_len as u32, result))
}
pub fn do_propose_proposed(
who: T::AccountId,
threshold: MemberCount,
proposal: Box<<T as Config<I>>::Proposal>,
length_bound: MemberCount,
) -> Result<(u32, u32), DispatchError> {
let proposal_len = proposal.encoded_size();
ensure!(proposal_len <= length_bound as usize, Error::<T, I>::WrongProposalLength);
let proposal_weight = proposal.get_dispatch_info().weight;
ensure!(
proposal_weight.all_lte(T::MaxProposalWeight::get()),
Error::<T, I>::WrongProposalWeight
);
let proposal_hash = T::Hashing::hash_of(&proposal);
ensure!(!<ProposalOf<T, I>>::contains_key(proposal_hash), Error::<T, I>::DuplicateProposal);
let active_proposals =
<Proposals<T, I>>::try_mutate(|proposals| -> Result<usize, DispatchError> {
proposals.try_push(proposal_hash).map_err(|_| Error::<T, I>::TooManyProposals)?;
Ok(proposals.len())
})?;
let index = ProposalCount::<T, I>::get();
<ProposalCount<T, I>>::mutate(|i| *i += 1);
<ProposalOf<T, I>>::insert(proposal_hash, proposal);
let votes = {
let end = frame_system::Pallet::<T>::block_number() + T::MotionDuration::get();
Votes { index, threshold, ayes: vec![], nays: vec![], end }
};
<Voting<T, I>>::insert(proposal_hash, votes);
Self::deposit_event(Event::Proposed {
account: who,
proposal_index: index,
proposal_hash,
threshold,
});
Ok((proposal_len as u32, active_proposals as u32))
}
pub fn do_vote(
who: T::AccountId,
proposal: T::Hash,
index: ProposalIndex,
approve: bool,
) -> Result<bool, DispatchError> {
let mut voting = Voting::<T, I>::get(&proposal).ok_or(Error::<T, I>::ProposalMissing)?;
ensure!(voting.index == index, Error::<T, I>::WrongIndex);
let position_yes = voting.ayes.iter().position(|a| a == &who);
let position_no = voting.nays.iter().position(|a| a == &who);
let is_account_voting_first_time = position_yes.is_none() && position_no.is_none();
if approve {
if position_yes.is_none() {
voting.ayes.push(who.clone());
} else {
return Err(Error::<T, I>::DuplicateVote.into())
}
if let Some(pos) = position_no {
voting.nays.swap_remove(pos);
}
} else {
if position_no.is_none() {
voting.nays.push(who.clone());
} else {
return Err(Error::<T, I>::DuplicateVote.into())
}
if let Some(pos) = position_yes {
voting.ayes.swap_remove(pos);
}
}
let yes_votes = voting.ayes.len() as MemberCount;
let no_votes = voting.nays.len() as MemberCount;
Self::deposit_event(Event::Voted {
account: who,
proposal_hash: proposal,
voted: approve,
yes: yes_votes,
no: no_votes,
});
Voting::<T, I>::insert(&proposal, voting);
Ok(is_account_voting_first_time)
}
pub fn do_close(
proposal_hash: T::Hash,
index: ProposalIndex,
proposal_weight_bound: Weight,
length_bound: u32,
) -> DispatchResultWithPostInfo {
let voting = Voting::<T, I>::get(&proposal_hash).ok_or(Error::<T, I>::ProposalMissing)?;
ensure!(voting.index == index, Error::<T, I>::WrongIndex);
let mut no_votes = voting.nays.len() as MemberCount;
let mut yes_votes = voting.ayes.len() as MemberCount;
let seats = Members::<T, I>::get().len() as MemberCount;
let approved = yes_votes >= voting.threshold;
let disapproved = seats.saturating_sub(no_votes) < voting.threshold;
if approved {
let (proposal, len) = Self::validate_and_get_proposal(
&proposal_hash,
length_bound,
proposal_weight_bound,
)?;
Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes });
let (proposal_weight, proposal_count) =
Self::do_approve_proposal(seats, yes_votes, proposal_hash, proposal);
return Ok((
Some(
T::WeightInfo::close_early_approved(len as u32, seats, proposal_count)
.saturating_add(proposal_weight),
),
Pays::Yes,
)
.into())
} else if disapproved {
Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes });
let proposal_count = Self::do_disapprove_proposal(proposal_hash);
return Ok((
Some(T::WeightInfo::close_early_disapproved(seats, proposal_count)),
Pays::No,
)
.into())
}
ensure!(frame_system::Pallet::<T>::block_number() >= voting.end, Error::<T, I>::TooEarly);
let prime_vote = Prime::<T, I>::get().map(|who| voting.ayes.iter().any(|a| a == &who));
let default = T::DefaultVote::default_vote(prime_vote, yes_votes, no_votes, seats);
let abstentions = seats - (yes_votes + no_votes);
match default {
true => yes_votes += abstentions,
false => no_votes += abstentions,
}
let approved = yes_votes >= voting.threshold;
if approved {
let (proposal, len) = Self::validate_and_get_proposal(
&proposal_hash,
length_bound,
proposal_weight_bound,
)?;
Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes });
let (proposal_weight, proposal_count) =
Self::do_approve_proposal(seats, yes_votes, proposal_hash, proposal);
Ok((
Some(
T::WeightInfo::close_approved(len as u32, seats, proposal_count)
.saturating_add(proposal_weight),
),
Pays::Yes,
)
.into())
} else {
Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes });
let proposal_count = Self::do_disapprove_proposal(proposal_hash);
Ok((Some(T::WeightInfo::close_disapproved(seats, proposal_count)), Pays::No).into())
}
}
fn validate_and_get_proposal(
hash: &T::Hash,
length_bound: u32,
weight_bound: Weight,
) -> Result<(<T as Config<I>>::Proposal, usize), DispatchError> {
let key = ProposalOf::<T, I>::hashed_key_for(hash);
let proposal_len =
storage::read(&key, &mut [0; 0], 0).ok_or(Error::<T, I>::ProposalMissing)?;
ensure!(proposal_len <= length_bound, Error::<T, I>::WrongProposalLength);
let proposal = ProposalOf::<T, I>::get(hash).ok_or(Error::<T, I>::ProposalMissing)?;
let proposal_weight = proposal.get_dispatch_info().weight;
ensure!(proposal_weight.all_lte(weight_bound), Error::<T, I>::WrongProposalWeight);
Ok((proposal, proposal_len as usize))
}
fn do_approve_proposal(
seats: MemberCount,
yes_votes: MemberCount,
proposal_hash: T::Hash,
proposal: <T as Config<I>>::Proposal,
) -> (Weight, u32) {
Self::deposit_event(Event::Approved { proposal_hash });
let dispatch_weight = proposal.get_dispatch_info().weight;
let origin = RawOrigin::Members(yes_votes, seats).into();
let result = proposal.dispatch(origin);
Self::deposit_event(Event::Executed {
proposal_hash,
result: result.map(|_| ()).map_err(|e| e.error),
});
let proposal_weight = get_result_weight(result).unwrap_or(dispatch_weight); let proposal_count = Self::remove_proposal(proposal_hash);
(proposal_weight, proposal_count)
}
pub fn do_disapprove_proposal(proposal_hash: T::Hash) -> u32 {
Self::deposit_event(Event::Disapproved { proposal_hash });
Self::remove_proposal(proposal_hash)
}
fn remove_proposal(proposal_hash: T::Hash) -> u32 {
ProposalOf::<T, I>::remove(&proposal_hash);
Voting::<T, I>::remove(&proposal_hash);
let num_proposals = Proposals::<T, I>::mutate(|proposals| {
proposals.retain(|h| h != &proposal_hash);
proposals.len() + 1 });
num_proposals as u32
}
#[cfg(any(feature = "try-runtime", test))]
fn do_try_state() -> Result<(), TryRuntimeError> {
Proposals::<T, I>::get().into_iter().try_for_each(
|proposal| -> Result<(), TryRuntimeError> {
ensure!(
ProposalOf::<T, I>::get(proposal).is_some(),
"Proposal hash from `Proposals` is not found inside the `ProposalOf` mapping."
);
Ok(())
},
)?;
ensure!(
Proposals::<T, I>::get().into_iter().count() <= ProposalCount::<T, I>::get() as usize,
"The actual number of proposals is greater than `ProposalCount`"
);
ensure!(
Proposals::<T, I>::get().into_iter().count() == <ProposalOf<T, I>>::iter_keys().count(),
"Proposal count inside `Proposals` is not equal to the proposal count in `ProposalOf`"
);
Proposals::<T, I>::get().into_iter().try_for_each(
|proposal| -> Result<(), TryRuntimeError> {
if let Some(votes) = Voting::<T, I>::get(proposal) {
let ayes = votes.ayes.len();
let nays = votes.nays.len();
ensure!(
ayes.saturating_add(nays) <= T::MaxMembers::get() as usize,
"The sum of ayes and nays is greater than `MaxMembers`"
);
}
Ok(())
},
)?;
let mut proposal_indices = vec![];
Proposals::<T, I>::get().into_iter().try_for_each(
|proposal| -> Result<(), TryRuntimeError> {
if let Some(votes) = Voting::<T, I>::get(proposal) {
let proposal_index = votes.index;
ensure!(
!proposal_indices.contains(&proposal_index),
"The proposal index is not unique."
);
proposal_indices.push(proposal_index);
}
Ok(())
},
)?;
<Voting<T, I>>::iter_keys().try_for_each(
|proposal_hash| -> Result<(), TryRuntimeError> {
ensure!(
Proposals::<T, I>::get().contains(&proposal_hash),
"`Proposals` doesn't contain the proposal hash from the `Voting` storage map."
);
Ok(())
},
)?;
ensure!(
Members::<T, I>::get().len() <= T::MaxMembers::get() as usize,
"The member count is greater than `MaxMembers`."
);
ensure!(
Members::<T, I>::get().windows(2).all(|members| members[0] <= members[1]),
"The members are not sorted by value."
);
if let Some(prime) = Prime::<T, I>::get() {
ensure!(Members::<T, I>::get().contains(&prime), "Prime account is not a member.");
}
Ok(())
}
}
impl<T: Config<I>, I: 'static> ChangeMembers<T::AccountId> for Pallet<T, I> {
fn change_members_sorted(
_incoming: &[T::AccountId],
outgoing: &[T::AccountId],
new: &[T::AccountId],
) {
if new.len() > T::MaxMembers::get() as usize {
log::error!(
target: LOG_TARGET,
"New members count ({}) exceeds maximum amount of members expected ({}).",
new.len(),
T::MaxMembers::get(),
);
}
let mut outgoing = outgoing.to_vec();
outgoing.sort();
for h in Proposals::<T, I>::get().into_iter() {
<Voting<T, I>>::mutate(h, |v| {
if let Some(mut votes) = v.take() {
votes.ayes = votes
.ayes
.into_iter()
.filter(|i| outgoing.binary_search(i).is_err())
.collect();
votes.nays = votes
.nays
.into_iter()
.filter(|i| outgoing.binary_search(i).is_err())
.collect();
*v = Some(votes);
}
});
}
Members::<T, I>::put(new);
Prime::<T, I>::kill();
}
fn set_prime(prime: Option<T::AccountId>) {
Prime::<T, I>::set(prime);
}
fn get_prime() -> Option<T::AccountId> {
Prime::<T, I>::get()
}
}
impl<T: Config<I>, I: 'static> InitializeMembers<T::AccountId> for Pallet<T, I> {
fn initialize_members(members: &[T::AccountId]) {
if !members.is_empty() {
assert!(<Members<T, I>>::get().is_empty(), "Members are already initialized!");
let mut members = members.to_vec();
members.sort();
<Members<T, I>>::put(members);
}
}
}
pub fn ensure_members<OuterOrigin, AccountId, I>(
o: OuterOrigin,
n: MemberCount,
) -> result::Result<MemberCount, &'static str>
where
OuterOrigin: Into<result::Result<RawOrigin<AccountId, I>, OuterOrigin>>,
{
match o.into() {
Ok(RawOrigin::Members(x, _)) if x >= n => Ok(n),
_ => Err("bad origin: expected to be a threshold number of members"),
}
}
pub struct EnsureMember<AccountId, I: 'static>(PhantomData<(AccountId, I)>);
impl<
O: Into<Result<RawOrigin<AccountId, I>, O>> + From<RawOrigin<AccountId, I>>,
I,
AccountId: Decode,
> EnsureOrigin<O> for EnsureMember<AccountId, I>
{
type Success = AccountId;
fn try_origin(o: O) -> Result<Self::Success, O> {
o.into().and_then(|o| match o {
RawOrigin::Member(id) => Ok(id),
r => Err(O::from(r)),
})
}
#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin() -> Result<O, ()> {
let zero_account_id =
AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes())
.expect("infinite length input; no invalid inputs for type; qed");
Ok(O::from(RawOrigin::Member(zero_account_id)))
}
}
impl_ensure_origin_with_arg_ignoring_arg! {
impl< { O: .., I: 'static, AccountId: Decode, T } >
EnsureOriginWithArg<O, T> for EnsureMember<AccountId, I>
{}
}
pub struct EnsureMembers<AccountId, I: 'static, const N: u32>(PhantomData<(AccountId, I)>);
impl<
O: Into<Result<RawOrigin<AccountId, I>, O>> + From<RawOrigin<AccountId, I>>,
AccountId,
I,
const N: u32,
> EnsureOrigin<O> for EnsureMembers<AccountId, I, N>
{
type Success = (MemberCount, MemberCount);
fn try_origin(o: O) -> Result<Self::Success, O> {
o.into().and_then(|o| match o {
RawOrigin::Members(n, m) if n >= N => Ok((n, m)),
r => Err(O::from(r)),
})
}
#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin() -> Result<O, ()> {
Ok(O::from(RawOrigin::Members(N, N)))
}
}
impl_ensure_origin_with_arg_ignoring_arg! {
impl< { O: .., I: 'static, const N: u32, AccountId, T } >
EnsureOriginWithArg<O, T> for EnsureMembers<AccountId, I, N>
{}
}
pub struct EnsureProportionMoreThan<AccountId, I: 'static, const N: u32, const D: u32>(
PhantomData<(AccountId, I)>,
);
impl<
O: Into<Result<RawOrigin<AccountId, I>, O>> + From<RawOrigin<AccountId, I>>,
AccountId,
I,
const N: u32,
const D: u32,
> EnsureOrigin<O> for EnsureProportionMoreThan<AccountId, I, N, D>
{
type Success = ();
fn try_origin(o: O) -> Result<Self::Success, O> {
o.into().and_then(|o| match o {
RawOrigin::Members(n, m) if n * D > N * m => Ok(()),
r => Err(O::from(r)),
})
}
#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin() -> Result<O, ()> {
Ok(O::from(RawOrigin::Members(1u32, 0u32)))
}
}
impl_ensure_origin_with_arg_ignoring_arg! {
impl< { O: .., I: 'static, const N: u32, const D: u32, AccountId, T } >
EnsureOriginWithArg<O, T> for EnsureProportionMoreThan<AccountId, I, N, D>
{}
}
pub struct EnsureProportionAtLeast<AccountId, I: 'static, const N: u32, const D: u32>(
PhantomData<(AccountId, I)>,
);
impl<
O: Into<Result<RawOrigin<AccountId, I>, O>> + From<RawOrigin<AccountId, I>>,
AccountId,
I,
const N: u32,
const D: u32,
> EnsureOrigin<O> for EnsureProportionAtLeast<AccountId, I, N, D>
{
type Success = ();
fn try_origin(o: O) -> Result<Self::Success, O> {
o.into().and_then(|o| match o {
RawOrigin::Members(n, m) if n * D >= N * m => Ok(()),
r => Err(O::from(r)),
})
}
#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin() -> Result<O, ()> {
Ok(O::from(RawOrigin::Members(0u32, 0u32)))
}
}
impl_ensure_origin_with_arg_ignoring_arg! {
impl< { O: .., I: 'static, const N: u32, const D: u32, AccountId, T } >
EnsureOriginWithArg<O, T> for EnsureProportionAtLeast<AccountId, I, N, D>
{}
}