Ranked Collective pallet (#11548)

* Ranked Collective pallet

* Fixes

* benchmarks

* Weights

* Allow class voting in rank
Use bare ayes for calculating support.
Allow only promotion/demotion by one rank only.
Allow removal of member with rank zero only.
Use new Tally API

* Index by rank, still O(1).

* Custom vote weights

* Formatting

* Update frame/ranked-collective/src/lib.rs

* Broken :(

* origin guard; cleanup uses new API

* Formatting

* Promote/demote by rank

* Formatting

* Use new API

* Remove code in another PR

* Remove code in another PR

* Formatting

* Remove code in another PR

* Docs

* Docs

* Bump

* Fixes

* Formatting

* Fixes
This commit is contained in:
Gavin Wood
2022-06-01 10:23:47 +01:00
committed by GitHub
parent 8e9639d2ff
commit 5595f10245
22 changed files with 1641 additions and 17 deletions
@@ -0,0 +1,155 @@
// This file is part of Substrate.
// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Staking pallet benchmarking.
use super::*;
#[allow(unused_imports)]
use crate::Pallet as RankedCollective;
use frame_benchmarking::{account, benchmarks_instance_pallet, whitelisted_caller};
use frame_support::{assert_ok, dispatch::UnfilteredDispatchable};
use frame_system::RawOrigin as SystemOrigin;
const SEED: u32 = 0;
fn assert_last_event<T: Config<I>, I: 'static>(generic_event: <T as Config<I>>::Event) {
frame_system::Pallet::<T>::assert_last_event(generic_event.into());
}
fn make_member<T: Config<I>, I: 'static>(rank: Rank) -> T::AccountId {
let who = account::<T::AccountId>("member", MemberCount::<T, I>::get(0), SEED);
assert_ok!(Pallet::<T, I>::add_member(T::PromoteOrigin::successful_origin(), who.clone()));
for _ in 0..rank {
assert_ok!(Pallet::<T, I>::promote_member(
T::PromoteOrigin::successful_origin(),
who.clone()
));
}
who
}
benchmarks_instance_pallet! {
add_member {
let who = account::<T::AccountId>("member", 0, SEED);
let origin = T::PromoteOrigin::successful_origin();
let call = Call::<T, I>::add_member { who: who.clone() };
}: { call.dispatch_bypass_filter(origin)? }
verify {
assert_eq!(MemberCount::<T, I>::get(0), 1);
assert_last_event::<T, I>(Event::MemberAdded { who }.into());
}
remove_member {
let r in 0 .. 10;
let rank = r as u16;
let first = make_member::<T, I>(rank);
let who = make_member::<T, I>(rank);
let last = make_member::<T, I>(rank);
let last_index = (0..=rank).map(|r| IdToIndex::<T, I>::get(r, &last).unwrap()).collect::<Vec<_>>();
let origin = T::DemoteOrigin::successful_origin();
let call = Call::<T, I>::remove_member { who: who.clone(), min_rank: rank };
}: { call.dispatch_bypass_filter(origin)? }
verify {
for r in 0..=rank {
assert_eq!(MemberCount::<T, I>::get(r), 2);
assert_ne!(last_index[r as usize], IdToIndex::<T, I>::get(r, &last).unwrap());
}
assert_last_event::<T, I>(Event::MemberRemoved { who, rank }.into());
}
promote_member {
let r in 0 .. 10;
let rank = r as u16;
let who = make_member::<T, I>(rank);
let origin = T::PromoteOrigin::successful_origin();
let call = Call::<T, I>::promote_member { who: who.clone() };
}: { call.dispatch_bypass_filter(origin)? }
verify {
assert_eq!(Members::<T, I>::get(&who).unwrap().rank, rank + 1);
assert_last_event::<T, I>(Event::RankChanged { who, rank: rank + 1 }.into());
}
demote_member {
let r in 0 .. 10;
let rank = r as u16;
let first = make_member::<T, I>(rank);
let who = make_member::<T, I>(rank);
let last = make_member::<T, I>(rank);
let last_index = IdToIndex::<T, I>::get(rank, &last).unwrap();
let origin = T::DemoteOrigin::successful_origin();
let call = Call::<T, I>::demote_member { who: who.clone() };
}: { call.dispatch_bypass_filter(origin)? }
verify {
assert_eq!(Members::<T, I>::get(&who).map(|x| x.rank), rank.checked_sub(1));
assert_eq!(MemberCount::<T, I>::get(rank), 2);
assert_ne!(last_index, IdToIndex::<T, I>::get(rank, &last).unwrap());
assert_last_event::<T, I>(match rank {
0 => Event::MemberRemoved { who, rank: 0 },
r => Event::RankChanged { who, rank: r - 1 },
}.into());
}
vote {
let caller: T::AccountId = whitelisted_caller();
assert_ok!(Pallet::<T, I>::add_member(T::PromoteOrigin::successful_origin(), caller.clone()));
// Create a poll
let class = T::Polls::classes().into_iter().next().unwrap();
let rank = T::MinRankOfClass::convert(class.clone());
for _ in 0..rank {
assert_ok!(Pallet::<T, I>::promote_member(
T::PromoteOrigin::successful_origin(),
caller.clone()
));
}
let poll = T::Polls::create_ongoing(class).expect("Must always be able to create a poll for rank 0");
// Vote once.
assert_ok!(Pallet::<T, I>::vote(SystemOrigin::Signed(caller.clone()).into(), poll, true));
}: _(SystemOrigin::Signed(caller.clone()), poll, false)
verify {
let tally = Tally::from_parts(0, 0, 1);
let ev = Event::Voted { who: caller, poll, vote: VoteRecord::Nay(1), tally };
assert_last_event::<T, I>(ev.into());
}
cleanup_poll {
let n in 1 .. 100;
// Create a poll
let class = T::Polls::classes().into_iter().next().unwrap();
let rank = T::MinRankOfClass::convert(class.clone());
let poll = T::Polls::create_ongoing(class).expect("Must always be able to create a poll");
// Vote in the poll by each of `n` members
for i in 0..n {
let who = make_member::<T, I>(rank);
assert_ok!(Pallet::<T, I>::vote(SystemOrigin::Signed(who).into(), poll, true));
}
// End the poll.
T::Polls::end_ongoing(poll, false).expect("Must always be able to end a poll");
assert_eq!(Voting::<T, I>::iter_prefix(poll).count(), n as usize);
}: _(SystemOrigin::Signed(whitelisted_caller()), poll, n)
verify {
assert_eq!(Voting::<T, I>::iter().count(), 0);
}
impl_benchmark_test_suite!(RankedCollective, crate::tests::new_test_ext(), crate::tests::Test);
}
@@ -0,0 +1,630 @@
// This file is part of Substrate.
// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Ranked collective system.
//!
//! This is a membership pallet providing a `Tally` implementation ready for use with polling
//! systems such as the Referenda pallet. Members each have a rank, with zero being the lowest.
//! There is no complexity limitation on either the number of members at a rank or the number of
//! ranks in the system thus allowing potentially public membership. A member of at least a given
//! rank can be selected at random in O(1) time, allowing for various games to constructed using
//! this as a primitive. Members may only be promoted and demoted by one rank at a time, however
//! all operations (save one) are O(1) in complexity. The only operation which is not O(1) is the
//! `remove_member` since they must be removed from all ranks from the present down to zero.
//!
//! Different ranks have different voting power, and are able to vote in different polls. In general
//! rank privileges are cumulative. Higher ranks are able to vote in any polls open to lower ranks.
//! Similarly, higher ranks always have at least as much voting power in any given poll as lower
//! ranks.
//!
//! Two `Config` trait items control these "rank privileges": `MinRankOfClass` and `VoteWeight`.
//! The first controls which ranks are allowed to vote on a particular class of poll. The second
//! controls the weight of a vote given the voters rank compared to the minimum rank of the poll.
//!
//! An origin control, `EnsureRank`, ensures that the origin is a member of the collective of at
//! least a particular rank.
#![cfg_attr(not(feature = "std"), no_std)]
#![recursion_limit = "128"]
use scale_info::TypeInfo;
use sp_arithmetic::traits::Saturating;
use sp_runtime::{traits::Convert, ArithmeticError::Overflow, Perbill, RuntimeDebug};
use sp_std::{marker::PhantomData, prelude::*};
use frame_support::{
codec::{Decode, Encode, MaxEncodedLen},
dispatch::{DispatchError, DispatchResultWithPostInfo},
ensure,
traits::{EnsureOrigin, PollStatus, Polling, VoteTally},
weights::PostDispatchInfo,
CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
};
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
pub use pallet::*;
pub use weights::WeightInfo;
/// A number of members.
pub type MemberIndex = u32;
/// Member rank.
pub type Rank = u16;
/// Votes.
pub type Votes = u32;
/// Aggregated votes for an ongoing poll by members of the ranked collective.
#[derive(
CloneNoBound,
PartialEqNoBound,
EqNoBound,
RuntimeDebugNoBound,
TypeInfo,
Encode,
Decode,
MaxEncodedLen,
)]
#[scale_info(skip_type_params(M))]
pub struct Tally<M: GetMaxVoters> {
bare_ayes: MemberIndex,
ayes: Votes,
nays: Votes,
dummy: PhantomData<M>,
}
impl<M: GetMaxVoters> Tally<M> {
pub fn from_parts(bare_ayes: MemberIndex, ayes: Votes, nays: Votes) -> Self {
Tally { bare_ayes, ayes, nays, dummy: PhantomData }
}
}
// Use (non-rank-weighted) ayes for calculating support.
// Allow only promotion/demotion by one rank only.
// Allow removal of member with rank zero only.
// This keeps everything O(1) while still allowing arbitrary number of ranks.
// All functions of VoteTally now include the class as a param.
pub type TallyOf<T, I = ()> = Tally<Pallet<T, I>>;
pub type PollIndexOf<T, I = ()> = <<T as Config<I>>::Polls as Polling<TallyOf<T, I>>>::Index;
impl<M: GetMaxVoters> VoteTally<Votes, Rank> for Tally<M> {
fn new(_: Rank) -> Self {
Self { bare_ayes: 0, ayes: 0, nays: 0, dummy: PhantomData }
}
fn ayes(&self, _: Rank) -> Votes {
self.bare_ayes
}
fn support(&self, class: Rank) -> Perbill {
Perbill::from_rational(self.bare_ayes, M::get_max_voters(class))
}
fn approval(&self, _: Rank) -> Perbill {
Perbill::from_rational(self.ayes, 1.max(self.ayes + self.nays))
}
#[cfg(feature = "runtime-benchmarks")]
fn unanimity(class: Rank) -> Self {
Self {
bare_ayes: M::get_max_voters(class),
ayes: M::get_max_voters(class),
nays: 0,
dummy: PhantomData,
}
}
#[cfg(feature = "runtime-benchmarks")]
fn rejection(class: Rank) -> Self {
Self { bare_ayes: 0, ayes: 0, nays: M::get_max_voters(class), dummy: PhantomData }
}
#[cfg(feature = "runtime-benchmarks")]
fn from_requirements(support: Perbill, approval: Perbill, class: Rank) -> Self {
let c = M::get_max_voters(class);
let ayes = support * c;
let nays = ((ayes as u64) * 1_000_000_000u64 / approval.deconstruct() as u64) as u32 - ayes;
Self { bare_ayes: ayes, ayes, nays, dummy: PhantomData }
}
}
/// Record needed for every member.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct MemberRecord {
/// The rank of the member.
rank: Rank,
}
/// Record needed for every vote.
#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum VoteRecord {
/// Vote was an aye with given vote weight.
Aye(Votes),
/// Vote was a nay with given vote weight.
Nay(Votes),
}
impl From<(bool, Votes)> for VoteRecord {
fn from((aye, votes): (bool, Votes)) -> Self {
match aye {
true => VoteRecord::Aye(votes),
false => VoteRecord::Nay(votes),
}
}
}
/// Vote-weight scheme where all voters get one vote regardless of rank.
pub struct Unit;
impl Convert<Rank, Votes> for Unit {
fn convert(_: Rank) -> Votes {
1
}
}
/// Vote-weight scheme where all voters get one vote plus an additional vote for every excess rank
/// they have. I.e.:
///
/// - Each member with no excess rank gets 1 vote;
/// - ...with an excess rank of 1 gets 2 votes;
/// - ...with an excess rank of 2 gets 2 votes;
/// - ...with an excess rank of 3 gets 3 votes;
/// - ...with an excess rank of 4 gets 4 votes.
pub struct Linear;
impl Convert<Rank, Votes> for Linear {
fn convert(r: Rank) -> Votes {
(r + 1) as Votes
}
}
/// Vote-weight scheme where all voters get one vote plus additional votes for every excess rank
/// they have incrementing by one vote for each excess rank. I.e.:
///
/// - Each member with no excess rank gets 1 vote;
/// - ...with an excess rank of 1 gets 2 votes;
/// - ...with an excess rank of 2 gets 3 votes;
/// - ...with an excess rank of 3 gets 6 votes;
/// - ...with an excess rank of 4 gets 10 votes.
pub struct Geometric;
impl Convert<Rank, Votes> for Geometric {
fn convert(r: Rank) -> Votes {
let v = (r + 1) as Votes;
v * (v + 1) / 2
}
}
/// Trait for getting the maximum number of voters for a given rank.
pub trait GetMaxVoters {
/// Return the maximum number of voters for the rank `r`.
fn get_max_voters(r: Rank) -> MemberIndex;
}
impl<T: Config<I>, I: 'static> GetMaxVoters for Pallet<T, I> {
fn get_max_voters(r: Rank) -> MemberIndex {
MemberCount::<T, I>::get(r)
}
}
/// Guard to ensure that the given origin is a member of the collective. The rank of the member is
/// the `Success` value.
pub struct EnsureRanked<T, I, const MIN_RANK: u16>(PhantomData<(T, I)>);
impl<T: Config<I>, I: 'static, const MIN_RANK: u16> EnsureOrigin<T::Origin>
for EnsureRanked<T, I, MIN_RANK>
{
type Success = Rank;
fn try_origin(o: T::Origin) -> Result<Self::Success, T::Origin> {
let who = frame_system::EnsureSigned::try_origin(o)?;
match Members::<T, I>::get(&who) {
Some(MemberRecord { rank, .. }) if rank >= MIN_RANK => Ok(rank),
_ => Err(frame_system::RawOrigin::Signed(who).into()),
}
}
#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin() -> Result<T::Origin, ()> {
let who = IndexToId::<T, I>::get(MIN_RANK, 0).ok_or(())?;
Ok(frame_system::RawOrigin::Signed(who).into())
}
}
/// Guard to ensure that the given origin is a member of the collective. The account ID of the
/// member is the `Success` value.
pub struct EnsureMember<T, I, const MIN_RANK: u16>(PhantomData<(T, I)>);
impl<T: Config<I>, I: 'static, const MIN_RANK: u16> EnsureOrigin<T::Origin>
for EnsureMember<T, I, MIN_RANK>
{
type Success = T::AccountId;
fn try_origin(o: T::Origin) -> Result<Self::Success, T::Origin> {
let who = frame_system::EnsureSigned::try_origin(o)?;
match Members::<T, I>::get(&who) {
Some(MemberRecord { rank, .. }) if rank >= MIN_RANK => Ok(who),
_ => Err(frame_system::RawOrigin::Signed(who).into()),
}
}
#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin() -> Result<T::Origin, ()> {
let who = IndexToId::<T, I>::get(MIN_RANK, 0).ok_or(())?;
Ok(frame_system::RawOrigin::Signed(who).into())
}
}
/// Guard to ensure that the given origin is a member of the collective. The pair of including both
/// the account ID and the rank of the member is the `Success` value.
pub struct EnsureRankedMember<T, I, const MIN_RANK: u16>(PhantomData<(T, I)>);
impl<T: Config<I>, I: 'static, const MIN_RANK: u16> EnsureOrigin<T::Origin>
for EnsureRankedMember<T, I, MIN_RANK>
{
type Success = (T::AccountId, Rank);
fn try_origin(o: T::Origin) -> Result<Self::Success, T::Origin> {
let who = frame_system::EnsureSigned::try_origin(o)?;
match Members::<T, I>::get(&who) {
Some(MemberRecord { rank, .. }) if rank >= MIN_RANK => Ok((who, rank)),
_ => Err(frame_system::RawOrigin::Signed(who).into()),
}
}
#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin() -> Result<T::Origin, ()> {
let who = IndexToId::<T, I>::get(MIN_RANK, 0).ok_or(())?;
Ok(frame_system::RawOrigin::Signed(who).into())
}
}
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{pallet_prelude::*, storage::KeyLenOf};
use frame_system::pallet_prelude::*;
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::without_storage_info]
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
#[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config {
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
/// The outer event type.
type Event: From<Event<Self, I>> + IsType<<Self as frame_system::Config>::Event>;
/// The origin required to add or promote a mmember. The success value indicates the
/// maximum rank *to which* the promotion may be.
type PromoteOrigin: EnsureOrigin<Self::Origin, Success = Rank>;
/// The origin required to demote or remove a member. The success value indicates the
/// maximum rank *from which* the demotion/removal may be.
type DemoteOrigin: EnsureOrigin<Self::Origin, Success = Rank>;
/// The polling system used for our voting.
type Polls: Polling<TallyOf<Self, I>, Votes = Votes, Moment = Self::BlockNumber>;
/// Convert the tally class into the minimum rank required to vote on the poll. If
/// `Polls::Class` is the same type as `Rank`, then `Identity` can be used here to mean
/// "a rank of at least the poll class".
type MinRankOfClass: Convert<<Self::Polls as Polling<TallyOf<Self, I>>>::Class, Rank>;
/// Convert a rank_delta into a number of votes the rank gets.
///
/// Rank_delta is defined as the number of ranks above the minimum required to take part
/// in the poll.
type VoteWeight: Convert<Rank, Votes>;
}
/// The number of members in the collective who have at least the rank according to the index
/// of the vec.
#[pallet::storage]
pub type MemberCount<T: Config<I>, I: 'static = ()> =
StorageMap<_, Twox64Concat, Rank, MemberIndex, ValueQuery>;
/// The current members of the collective.
#[pallet::storage]
pub type Members<T: Config<I>, I: 'static = ()> =
StorageMap<_, Twox64Concat, T::AccountId, MemberRecord>;
/// The index of each ranks's member into the group of members who have at least that rank.
#[pallet::storage]
pub type IdToIndex<T: Config<I>, I: 'static = ()> =
StorageDoubleMap<_, Twox64Concat, Rank, Twox64Concat, T::AccountId, MemberIndex>;
/// The members in the collective by index. All indices in the range `0..MemberCount` will
/// return `Some`, however a member's index is not guaranteed to remain unchanged over time.
#[pallet::storage]
pub type IndexToId<T: Config<I>, I: 'static = ()> =
StorageDoubleMap<_, Twox64Concat, Rank, Twox64Concat, MemberIndex, T::AccountId>;
/// Votes on a given proposal, if it is ongoing.
#[pallet::storage]
pub type Voting<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
_,
Blake2_128Concat,
PollIndexOf<T, I>,
Twox64Concat,
T::AccountId,
VoteRecord,
>;
#[pallet::storage]
pub type VotingCleanup<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, PollIndexOf<T, I>, BoundedVec<u8, KeyLenOf<Voting<T, I>>>>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config<I>, I: 'static = ()> {
/// A member `who` has been added.
MemberAdded { who: T::AccountId },
/// The member `who`'s rank has been changed to the given `rank`.
RankChanged { who: T::AccountId, rank: Rank },
/// The member `who` of given `rank` has been removed from the collective.
MemberRemoved { who: T::AccountId, rank: Rank },
/// The member `who` has voted for the `poll` with the given `vote` leading to an updated
/// `tally`.
Voted { who: T::AccountId, poll: PollIndexOf<T, I>, vote: VoteRecord, tally: TallyOf<T, I> },
}
#[pallet::error]
pub enum Error<T, I = ()> {
/// Account is already a member.
AlreadyMember,
/// Account is not a member.
NotMember,
/// The given poll index is unknown or has closed.
NotPolling,
/// The given poll is still ongoing.
Ongoing,
/// There are no further records to be removed.
NoneRemaining,
/// Unexpected error in state.
Corruption,
/// The member's rank is too low to vote.
RankTooLow,
/// The information provided is incorrect.
InvalidWitness,
/// The origin is not sufficiently privileged to do the operation.
NoPermission,
}
#[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Introduce a new member.
///
/// - `origin`: Must be the `AdminOrigin`.
/// - `who`: Account of non-member which will become a member.
/// - `rank`: The rank to give the new member.
///
/// Weight: `O(1)`
#[pallet::weight(T::WeightInfo::add_member())]
pub fn add_member(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult {
let _ = T::PromoteOrigin::ensure_origin(origin)?;
ensure!(!Members::<T, I>::contains_key(&who), Error::<T, I>::AlreadyMember);
let index = MemberCount::<T, I>::get(0);
let count = index.checked_add(1).ok_or(Overflow)?;
Members::<T, I>::insert(&who, MemberRecord { rank: 0 });
IdToIndex::<T, I>::insert(0, &who, index);
IndexToId::<T, I>::insert(0, index, &who);
MemberCount::<T, I>::insert(0, count);
Self::deposit_event(Event::MemberAdded { who });
Ok(())
}
/// Increment the rank of an existing member by one.
///
/// - `origin`: Must be the `AdminOrigin`.
/// - `who`: Account of existing member.
///
/// Weight: `O(1)`
#[pallet::weight(T::WeightInfo::promote_member(0))]
pub fn promote_member(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult {
let max_rank = T::PromoteOrigin::ensure_origin(origin)?;
let record = Self::ensure_member(&who)?;
let rank = record.rank.checked_add(1).ok_or(Overflow)?;
ensure!(max_rank >= rank, Error::<T, I>::NoPermission);
let index = MemberCount::<T, I>::get(rank);
MemberCount::<T, I>::insert(rank, index.checked_add(1).ok_or(Overflow)?);
IdToIndex::<T, I>::insert(rank, &who, index);
IndexToId::<T, I>::insert(rank, index, &who);
Members::<T, I>::insert(&who, MemberRecord { rank, ..record });
Self::deposit_event(Event::RankChanged { who, rank });
Ok(())
}
/// Decrement the rank of an existing member by one. If the member is already at rank zero,
/// then they are removed entirely.
///
/// - `origin`: Must be the `AdminOrigin`.
/// - `who`: Account of existing member of rank greater than zero.
///
/// Weight: `O(1)`, less if the member's index is highest in its rank.
#[pallet::weight(T::WeightInfo::demote_member(0))]
pub fn demote_member(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult {
let max_rank = T::DemoteOrigin::ensure_origin(origin)?;
let mut record = Self::ensure_member(&who)?;
let rank = record.rank;
ensure!(max_rank >= rank, Error::<T, I>::NoPermission);
Self::remove_from_rank(&who, rank)?;
let maybe_rank = rank.checked_sub(1);
match maybe_rank {
None => {
Members::<T, I>::remove(&who);
Self::deposit_event(Event::MemberRemoved { who, rank: 0 });
},
Some(rank) => {
record.rank = rank;
Members::<T, I>::insert(&who, &record);
Self::deposit_event(Event::RankChanged { who, rank });
},
}
Ok(())
}
/// Remove the member entirely.
///
/// - `origin`: Must be the `AdminOrigin`.
/// - `who`: Account of existing member of rank greater than zero.
/// - `min_rank`: The rank of the member or greater.
///
/// Weight: `O(min_rank)`.
#[pallet::weight(T::WeightInfo::remove_member(*min_rank as u32))]
pub fn remove_member(
origin: OriginFor<T>,
who: T::AccountId,
min_rank: Rank,
) -> DispatchResultWithPostInfo {
let max_rank = T::DemoteOrigin::ensure_origin(origin)?;
let MemberRecord { rank, .. } = Self::ensure_member(&who)?;
ensure!(min_rank >= rank, Error::<T, I>::InvalidWitness);
ensure!(max_rank >= rank, Error::<T, I>::NoPermission);
for r in 0..=rank {
Self::remove_from_rank(&who, r)?;
}
Members::<T, I>::remove(&who);
Self::deposit_event(Event::MemberRemoved { who, rank });
Ok(PostDispatchInfo {
actual_weight: Some(T::WeightInfo::remove_member(rank as u32)),
pays_fee: Pays::Yes,
})
}
/// Add an aye or nay vote for the sender to the given proposal.
///
/// - `origin`: Must be `Signed` by a member account.
/// - `poll`: Index of a poll which is ongoing.
/// - `aye`: `true` if the vote is to approve the proposal, `false` otherwise.
///
/// Transaction fees are be waived if the member is voting on any particular proposal
/// for the first time and the call is successful. Subsequent vote changes will charge a
/// fee.
///
/// Weight: `O(1)`, less if there was no previous vote on the poll by the member.
#[pallet::weight(T::WeightInfo::vote())]
pub fn vote(
origin: OriginFor<T>,
poll: PollIndexOf<T, I>,
aye: bool,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
let record = Self::ensure_member(&who)?;
use VoteRecord::*;
let mut pays = Pays::Yes;
let (tally, vote) = T::Polls::try_access_poll(
poll,
|mut status| -> Result<(TallyOf<T, I>, VoteRecord), DispatchError> {
match status {
PollStatus::None | PollStatus::Completed(..) =>
Err(Error::<T, I>::NotPolling)?,
PollStatus::Ongoing(ref mut tally, class) => {
match Voting::<T, I>::get(&poll, &who) {
Some(Aye(votes)) => {
tally.bare_ayes.saturating_dec();
tally.ayes.saturating_reduce(votes);
},
Some(Nay(votes)) => tally.nays.saturating_reduce(votes),
None => pays = Pays::No,
}
let min_rank = T::MinRankOfClass::convert(class);
let votes = Self::rank_to_votes(record.rank, min_rank)?;
let vote = VoteRecord::from((aye, votes));
match aye {
true => {
tally.bare_ayes.saturating_inc();
tally.ayes.saturating_accrue(votes);
},
false => tally.nays.saturating_accrue(votes),
}
Voting::<T, I>::insert(&poll, &who, &vote);
Ok((tally.clone(), vote))
},
}
},
)?;
Self::deposit_event(Event::Voted { who, poll, vote, tally });
Ok(pays.into())
}
/// Remove votes from the given poll. It must have ended.
///
/// - `origin`: Must be `Signed` by any account.
/// - `poll_index`: Index of a poll which is completed and for which votes continue to
/// exist.
/// - `max`: Maximum number of vote items from remove in this call.
///
/// Transaction fees are waived if the operation is successful.
///
/// Weight `O(max)` (less if there are fewer items to remove than `max`).
#[pallet::weight(T::WeightInfo::cleanup_poll(*max))]
pub fn cleanup_poll(
origin: OriginFor<T>,
poll_index: PollIndexOf<T, I>,
max: u32,
) -> DispatchResultWithPostInfo {
ensure_signed(origin)?;
ensure!(T::Polls::as_ongoing(poll_index).is_none(), Error::<T, I>::Ongoing);
let r = Voting::<T, I>::clear_prefix(
poll_index,
max,
VotingCleanup::<T, I>::take(poll_index).as_ref().map(|c| &c[..]),
);
if r.unique == 0 {
// return Err(Error::<T, I>::NoneRemaining)
return Ok(Pays::Yes.into())
}
if let Some(cursor) = r.maybe_cursor {
VotingCleanup::<T, I>::insert(poll_index, BoundedVec::truncate_from(cursor));
}
Ok(PostDispatchInfo {
actual_weight: Some(T::WeightInfo::cleanup_poll(r.unique)),
pays_fee: Pays::No,
})
}
}
impl<T: Config<I>, I: 'static> Pallet<T, I> {
fn ensure_member(who: &T::AccountId) -> Result<MemberRecord, DispatchError> {
Members::<T, I>::get(who).ok_or(Error::<T, I>::NotMember.into())
}
fn rank_to_votes(rank: Rank, min: Rank) -> Result<Votes, DispatchError> {
let excess = rank.checked_sub(min).ok_or(Error::<T, I>::RankTooLow)?;
Ok(T::VoteWeight::convert(excess))
}
fn remove_from_rank(who: &T::AccountId, rank: Rank) -> DispatchResult {
let last_index = MemberCount::<T, I>::get(rank).saturating_sub(1);
let index = IdToIndex::<T, I>::get(rank, &who).ok_or(Error::<T, I>::Corruption)?;
if index != last_index {
let last =
IndexToId::<T, I>::get(rank, last_index).ok_or(Error::<T, I>::Corruption)?;
IdToIndex::<T, I>::insert(rank, &last, index);
IndexToId::<T, I>::insert(rank, index, &last);
}
MemberCount::<T, I>::mutate(rank, |r| r.saturating_dec());
Ok(())
}
}
}
@@ -0,0 +1,457 @@
// This file is part of Substrate.
// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! The crate's tests.
use std::collections::BTreeMap;
use frame_support::{
assert_noop, assert_ok,
error::BadOrigin,
parameter_types,
traits::{ConstU16, ConstU32, ConstU64, EitherOf, Everything, MapSuccess, Polling, ReduceBy},
};
use sp_core::H256;
use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, Identity, IdentityLookup},
};
use super::*;
use crate as pallet_ranked_collective;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;
frame_support::construct_runtime!(
pub enum Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
Club: pallet_ranked_collective::{Pallet, Call, Storage, Event<T>},
}
);
parameter_types! {
pub BlockWeights: frame_system::limits::BlockWeights =
frame_system::limits::BlockWeights::simple_max(1_000_000);
}
impl frame_system::Config for Test {
type BaseCallFilter = Everything;
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type Origin = Origin;
type Index = u64;
type BlockNumber = u64;
type Call = Call;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = Event;
type BlockHashCount = ConstU64<250>;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = ConstU32<16>;
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum TestPollState {
Ongoing(TallyOf<Test>, Rank),
Completed(u64, bool),
}
use TestPollState::*;
parameter_types! {
pub static Polls: BTreeMap<u8, TestPollState> = vec![
(1, Completed(1, true)),
(2, Completed(2, false)),
(3, Ongoing(Tally::from_parts(0, 0, 0), 1)),
].into_iter().collect();
}
pub struct TestPolls;
impl Polling<TallyOf<Test>> for TestPolls {
type Index = u8;
type Votes = Votes;
type Moment = u64;
type Class = Rank;
fn classes() -> Vec<Self::Class> {
vec![0, 1, 2]
}
fn as_ongoing(index: u8) -> Option<(TallyOf<Test>, Self::Class)> {
Polls::get().remove(&index).and_then(|x| {
if let TestPollState::Ongoing(t, c) = x {
Some((t, c))
} else {
None
}
})
}
fn access_poll<R>(
index: Self::Index,
f: impl FnOnce(PollStatus<&mut TallyOf<Test>, Self::Moment, Self::Class>) -> R,
) -> R {
let mut polls = Polls::get();
let entry = polls.get_mut(&index);
let r = match entry {
Some(Ongoing(ref mut tally_mut_ref, class)) =>
f(PollStatus::Ongoing(tally_mut_ref, *class)),
Some(Completed(when, succeeded)) => f(PollStatus::Completed(*when, *succeeded)),
None => f(PollStatus::None),
};
Polls::set(polls);
r
}
fn try_access_poll<R>(
index: Self::Index,
f: impl FnOnce(
PollStatus<&mut TallyOf<Test>, Self::Moment, Self::Class>,
) -> Result<R, DispatchError>,
) -> Result<R, DispatchError> {
let mut polls = Polls::get();
let entry = polls.get_mut(&index);
let r = match entry {
Some(Ongoing(ref mut tally_mut_ref, class)) =>
f(PollStatus::Ongoing(tally_mut_ref, *class)),
Some(Completed(when, succeeded)) => f(PollStatus::Completed(*when, *succeeded)),
None => f(PollStatus::None),
}?;
Polls::set(polls);
Ok(r)
}
#[cfg(feature = "runtime-benchmarks")]
fn create_ongoing(class: Self::Class) -> Result<Self::Index, ()> {
let mut polls = Polls::get();
let i = polls.keys().rev().next().map_or(0, |x| x + 1);
polls.insert(i, Ongoing(Tally::new(class), class));
Polls::set(polls);
Ok(i)
}
#[cfg(feature = "runtime-benchmarks")]
fn end_ongoing(index: Self::Index, approved: bool) -> Result<(), ()> {
let mut polls = Polls::get();
match polls.get(&index) {
Some(Ongoing(..)) => {},
_ => return Err(()),
}
let now = frame_system::Pallet::<Test>::block_number();
polls.insert(index, Completed(now, approved));
Polls::set(polls);
Ok(())
}
}
impl Config for Test {
type WeightInfo = ();
type Event = Event;
type PromoteOrigin = EitherOf<
// Root can promote arbitrarily.
frame_system::EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>,
// Members can promote up to the rank of 2 below them.
MapSuccess<EnsureRanked<Test, (), 2>, ReduceBy<ConstU16<2>>>,
>;
type DemoteOrigin = EitherOf<
// Root can demote arbitrarily.
frame_system::EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>,
// Members can demote up to the rank of 3 below them.
MapSuccess<EnsureRanked<Test, (), 3>, ReduceBy<ConstU16<3>>>,
>;
type Polls = TestPolls;
type MinRankOfClass = Identity;
type VoteWeight = Geometric;
}
pub fn new_test_ext() -> sp_io::TestExternalities {
let t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
fn next_block() {
System::set_block_number(System::block_number() + 1);
}
fn member_count(r: Rank) -> MemberIndex {
MemberCount::<Test>::get(r)
}
#[allow(dead_code)]
fn run_to(n: u64) {
while System::block_number() < n {
next_block();
}
}
fn tally(index: u8) -> TallyOf<Test> {
<TestPolls as Polling<TallyOf<Test>>>::as_ongoing(index).expect("No poll").0
}
#[test]
#[ignore]
#[should_panic(expected = "No poll")]
fn unknown_poll_should_panic() {
let _ = tally(0);
}
#[test]
#[ignore]
#[should_panic(expected = "No poll")]
fn completed_poll_should_panic() {
let _ = tally(1);
}
#[test]
fn basic_stuff() {
new_test_ext().execute_with(|| {
assert_eq!(tally(3), Tally::from_parts(0, 0, 0));
});
}
#[test]
fn member_lifecycle_works() {
new_test_ext().execute_with(|| {
assert_ok!(Club::add_member(Origin::root(), 1));
assert_ok!(Club::promote_member(Origin::root(), 1));
assert_ok!(Club::demote_member(Origin::root(), 1));
assert_ok!(Club::demote_member(Origin::root(), 1));
assert_eq!(member_count(0), 0);
assert_eq!(member_count(1), 0);
});
}
#[test]
fn add_remove_works() {
new_test_ext().execute_with(|| {
assert_noop!(Club::add_member(Origin::signed(1), 1), DispatchError::BadOrigin);
assert_ok!(Club::add_member(Origin::root(), 1));
assert_eq!(member_count(0), 1);
assert_ok!(Club::demote_member(Origin::root(), 1));
assert_eq!(member_count(0), 0);
assert_ok!(Club::add_member(Origin::root(), 1));
assert_eq!(member_count(0), 1);
assert_ok!(Club::add_member(Origin::root(), 2));
assert_eq!(member_count(0), 2);
assert_ok!(Club::add_member(Origin::root(), 3));
assert_eq!(member_count(0), 3);
assert_ok!(Club::demote_member(Origin::root(), 3));
assert_eq!(member_count(0), 2);
assert_ok!(Club::demote_member(Origin::root(), 1));
assert_eq!(member_count(0), 1);
assert_ok!(Club::demote_member(Origin::root(), 2));
assert_eq!(member_count(0), 0);
});
}
#[test]
fn promote_demote_works() {
new_test_ext().execute_with(|| {
assert_noop!(Club::add_member(Origin::signed(1), 1), DispatchError::BadOrigin);
assert_ok!(Club::add_member(Origin::root(), 1));
assert_eq!(member_count(0), 1);
assert_eq!(member_count(1), 0);
assert_ok!(Club::add_member(Origin::root(), 2));
assert_eq!(member_count(0), 2);
assert_eq!(member_count(1), 0);
assert_ok!(Club::promote_member(Origin::root(), 1));
assert_eq!(member_count(0), 2);
assert_eq!(member_count(1), 1);
assert_ok!(Club::promote_member(Origin::root(), 2));
assert_eq!(member_count(0), 2);
assert_eq!(member_count(1), 2);
assert_ok!(Club::demote_member(Origin::root(), 1));
assert_eq!(member_count(0), 2);
assert_eq!(member_count(1), 1);
assert_noop!(Club::demote_member(Origin::signed(1), 1), DispatchError::BadOrigin);
assert_ok!(Club::demote_member(Origin::root(), 1));
assert_eq!(member_count(0), 1);
assert_eq!(member_count(1), 1);
});
}
#[test]
fn promote_demote_by_rank_works() {
new_test_ext().execute_with(|| {
assert_ok!(Club::add_member(Origin::root(), 1));
for _ in 0..7 {
assert_ok!(Club::promote_member(Origin::root(), 1));
}
// #1 can add #2 and promote to rank 1
assert_ok!(Club::add_member(Origin::signed(1), 2));
assert_ok!(Club::promote_member(Origin::signed(1), 2));
// #2 as rank 1 cannot do anything privileged
assert_noop!(Club::add_member(Origin::signed(2), 3), BadOrigin);
assert_ok!(Club::promote_member(Origin::signed(1), 2));
// #2 as rank 2 can add #3.
assert_ok!(Club::add_member(Origin::signed(2), 3));
// #2 as rank 2 cannot promote #3 to rank 1
assert_noop!(Club::promote_member(Origin::signed(2), 3), Error::<Test>::NoPermission);
// #1 as rank 7 can promote #2 only up to rank 5 and once there cannot demote them.
assert_ok!(Club::promote_member(Origin::signed(1), 2));
assert_ok!(Club::promote_member(Origin::signed(1), 2));
assert_ok!(Club::promote_member(Origin::signed(1), 2));
assert_noop!(Club::promote_member(Origin::signed(1), 2), Error::<Test>::NoPermission);
assert_noop!(Club::demote_member(Origin::signed(1), 2), Error::<Test>::NoPermission);
// #2 as rank 5 can promote #3 only up to rank 3 and once there cannot demote them.
assert_ok!(Club::promote_member(Origin::signed(2), 3));
assert_ok!(Club::promote_member(Origin::signed(2), 3));
assert_ok!(Club::promote_member(Origin::signed(2), 3));
assert_noop!(Club::promote_member(Origin::signed(2), 3), Error::<Test>::NoPermission);
assert_noop!(Club::demote_member(Origin::signed(2), 3), Error::<Test>::NoPermission);
// #2 can add #4 & #5 as rank 0 and #6 & #7 as rank 1.
assert_ok!(Club::add_member(Origin::signed(2), 4));
assert_ok!(Club::add_member(Origin::signed(2), 5));
assert_ok!(Club::add_member(Origin::signed(2), 6));
assert_ok!(Club::promote_member(Origin::signed(2), 6));
assert_ok!(Club::add_member(Origin::signed(2), 7));
assert_ok!(Club::promote_member(Origin::signed(2), 7));
// #3 as rank 3 can demote/remove #4 & #5 but not #6 & #7
assert_ok!(Club::demote_member(Origin::signed(3), 4));
assert_ok!(Club::remove_member(Origin::signed(3), 5, 0));
assert_noop!(Club::demote_member(Origin::signed(3), 6), Error::<Test>::NoPermission);
assert_noop!(Club::remove_member(Origin::signed(3), 7, 1), Error::<Test>::NoPermission);
// #2 as rank 5 can demote/remove #6 & #7
assert_ok!(Club::demote_member(Origin::signed(2), 6));
assert_ok!(Club::remove_member(Origin::signed(2), 7, 1));
});
}
#[test]
fn voting_works() {
new_test_ext().execute_with(|| {
assert_ok!(Club::add_member(Origin::root(), 0));
assert_ok!(Club::add_member(Origin::root(), 1));
assert_ok!(Club::promote_member(Origin::root(), 1));
assert_ok!(Club::add_member(Origin::root(), 2));
assert_ok!(Club::promote_member(Origin::root(), 2));
assert_ok!(Club::promote_member(Origin::root(), 2));
assert_ok!(Club::add_member(Origin::root(), 3));
assert_ok!(Club::promote_member(Origin::root(), 3));
assert_ok!(Club::promote_member(Origin::root(), 3));
assert_ok!(Club::promote_member(Origin::root(), 3));
assert_noop!(Club::vote(Origin::signed(0), 3, true), Error::<Test>::RankTooLow);
assert_eq!(tally(3), Tally::from_parts(0, 0, 0));
assert_ok!(Club::vote(Origin::signed(1), 3, true));
assert_eq!(tally(3), Tally::from_parts(1, 1, 0));
assert_ok!(Club::vote(Origin::signed(1), 3, false));
assert_eq!(tally(3), Tally::from_parts(0, 0, 1));
assert_ok!(Club::vote(Origin::signed(2), 3, true));
assert_eq!(tally(3), Tally::from_parts(1, 3, 1));
assert_ok!(Club::vote(Origin::signed(2), 3, false));
assert_eq!(tally(3), Tally::from_parts(0, 0, 4));
assert_ok!(Club::vote(Origin::signed(3), 3, true));
assert_eq!(tally(3), Tally::from_parts(1, 6, 4));
assert_ok!(Club::vote(Origin::signed(3), 3, false));
assert_eq!(tally(3), Tally::from_parts(0, 0, 10));
});
}
#[test]
fn cleanup_works() {
new_test_ext().execute_with(|| {
assert_ok!(Club::add_member(Origin::root(), 1));
assert_ok!(Club::promote_member(Origin::root(), 1));
assert_ok!(Club::add_member(Origin::root(), 2));
assert_ok!(Club::promote_member(Origin::root(), 2));
assert_ok!(Club::add_member(Origin::root(), 3));
assert_ok!(Club::promote_member(Origin::root(), 3));
assert_ok!(Club::vote(Origin::signed(1), 3, true));
assert_ok!(Club::vote(Origin::signed(2), 3, false));
assert_ok!(Club::vote(Origin::signed(3), 3, true));
assert_noop!(Club::cleanup_poll(Origin::signed(4), 3, 10), Error::<Test>::Ongoing);
Polls::set(
vec![(1, Completed(1, true)), (2, Completed(2, false)), (3, Completed(3, true))]
.into_iter()
.collect(),
);
assert_ok!(Club::cleanup_poll(Origin::signed(4), 3, 10));
// NOTE: This will fail until #10016 is merged.
// assert_noop!(Club::cleanup_poll(Origin::signed(4), 3, 10), Error::<Test>::NoneRemaining);
});
}
#[test]
fn ensure_ranked_works() {
new_test_ext().execute_with(|| {
assert_ok!(Club::add_member(Origin::root(), 1));
assert_ok!(Club::promote_member(Origin::root(), 1));
assert_ok!(Club::add_member(Origin::root(), 2));
assert_ok!(Club::promote_member(Origin::root(), 2));
assert_ok!(Club::promote_member(Origin::root(), 2));
assert_ok!(Club::add_member(Origin::root(), 3));
assert_ok!(Club::promote_member(Origin::root(), 3));
assert_ok!(Club::promote_member(Origin::root(), 3));
assert_ok!(Club::promote_member(Origin::root(), 3));
use frame_support::traits::OriginTrait;
type Rank1 = EnsureRanked<Test, (), 1>;
type Rank2 = EnsureRanked<Test, (), 2>;
type Rank3 = EnsureRanked<Test, (), 3>;
type Rank4 = EnsureRanked<Test, (), 4>;
assert_eq!(Rank1::try_origin(Origin::signed(1)).unwrap(), 1);
assert_eq!(Rank1::try_origin(Origin::signed(2)).unwrap(), 2);
assert_eq!(Rank1::try_origin(Origin::signed(3)).unwrap(), 3);
assert_eq!(Rank2::try_origin(Origin::signed(1)).unwrap_err().as_signed().unwrap(), 1);
assert_eq!(Rank2::try_origin(Origin::signed(2)).unwrap(), 2);
assert_eq!(Rank2::try_origin(Origin::signed(3)).unwrap(), 3);
assert_eq!(Rank3::try_origin(Origin::signed(1)).unwrap_err().as_signed().unwrap(), 1);
assert_eq!(Rank3::try_origin(Origin::signed(2)).unwrap_err().as_signed().unwrap(), 2);
assert_eq!(Rank3::try_origin(Origin::signed(3)).unwrap(), 3);
assert_eq!(Rank4::try_origin(Origin::signed(1)).unwrap_err().as_signed().unwrap(), 1);
assert_eq!(Rank4::try_origin(Origin::signed(2)).unwrap_err().as_signed().unwrap(), 2);
assert_eq!(Rank4::try_origin(Origin::signed(3)).unwrap_err().as_signed().unwrap(), 3);
});
}
@@ -0,0 +1,187 @@
// This file is part of Substrate.
// Copyright (C) 2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Autogenerated weights for pallet_ranked_collective
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2022-05-19, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
// Executed Command:
// /Users/gav/Core/substrate/target/release/substrate
// benchmark
// pallet
// --pallet
// pallet-ranked-collective
// --extrinsic=*
// --chain=dev
// --steps=50
// --repeat=20
// --output=../../../frame/ranked-collective/src/weights.rs
// --template=../../../.maintain/frame-weight-template.hbs
// --header=../../../HEADER-APACHE2
// --record-proof
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use sp_std::marker::PhantomData;
/// Weight functions needed for pallet_ranked_collective.
pub trait WeightInfo {
fn add_member() -> Weight;
fn remove_member(r: u32, ) -> Weight;
fn promote_member(r: u32, ) -> Weight;
fn demote_member(r: u32, ) -> Weight;
fn vote() -> Weight;
fn cleanup_poll(n: u32, ) -> Weight;
}
/// Weights for pallet_ranked_collective using the Substrate node and recommended hardware.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: RankedCollective Members (r:1 w:1)
// Storage: RankedCollective MemberCount (r:1 w:1)
// Storage: RankedCollective IndexToId (r:0 w:1)
// Storage: RankedCollective IdToIndex (r:0 w:1)
fn add_member() -> Weight {
(11_000_000 as Weight)
.saturating_add(T::DbWeight::get().reads(2 as Weight))
.saturating_add(T::DbWeight::get().writes(4 as Weight))
}
// Storage: RankedCollective Members (r:1 w:1)
// Storage: RankedCollective MemberCount (r:1 w:1)
// Storage: RankedCollective IdToIndex (r:1 w:1)
// Storage: RankedCollective IndexToId (r:1 w:1)
fn remove_member(r: u32, ) -> Weight {
(16_855_000 as Weight)
// Standard Error: 27_000
.saturating_add((8_107_000 as Weight).saturating_mul(r as Weight))
.saturating_add(T::DbWeight::get().reads(4 as Weight))
.saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(r as Weight)))
.saturating_add(T::DbWeight::get().writes(4 as Weight))
.saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(r as Weight)))
}
// Storage: RankedCollective Members (r:1 w:1)
// Storage: RankedCollective MemberCount (r:1 w:1)
// Storage: RankedCollective IndexToId (r:0 w:1)
// Storage: RankedCollective IdToIndex (r:0 w:1)
fn promote_member(r: u32, ) -> Weight {
(11_936_000 as Weight)
// Standard Error: 3_000
.saturating_add((9_000 as Weight).saturating_mul(r as Weight))
.saturating_add(T::DbWeight::get().reads(2 as Weight))
.saturating_add(T::DbWeight::get().writes(4 as Weight))
}
// Storage: RankedCollective Members (r:1 w:1)
// Storage: RankedCollective MemberCount (r:1 w:1)
// Storage: RankedCollective IdToIndex (r:1 w:1)
// Storage: RankedCollective IndexToId (r:1 w:1)
fn demote_member(r: u32, ) -> Weight {
(17_582_000 as Weight)
// Standard Error: 14_000
.saturating_add((142_000 as Weight).saturating_mul(r as Weight))
.saturating_add(T::DbWeight::get().reads(4 as Weight))
.saturating_add(T::DbWeight::get().writes(4 as Weight))
}
// Storage: RankedCollective Members (r:1 w:0)
// Storage: RankedPolls ReferendumInfoFor (r:1 w:1)
// Storage: RankedCollective Voting (r:1 w:1)
// Storage: Scheduler Agenda (r:2 w:2)
fn vote() -> Weight {
(22_000_000 as Weight)
.saturating_add(T::DbWeight::get().reads(5 as Weight))
.saturating_add(T::DbWeight::get().writes(4 as Weight))
}
// Storage: RankedPolls ReferendumInfoFor (r:1 w:0)
// Storage: RankedCollective Voting (r:0 w:1)
fn cleanup_poll(n: u32, ) -> Weight {
(6_188_000 as Weight)
// Standard Error: 1_000
.saturating_add((867_000 as Weight).saturating_mul(n as Weight))
.saturating_add(T::DbWeight::get().reads(1 as Weight))
.saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(n as Weight)))
}
}
// For backwards compatibility and tests
impl WeightInfo for () {
// Storage: RankedCollective Members (r:1 w:1)
// Storage: RankedCollective MemberCount (r:1 w:1)
// Storage: RankedCollective IndexToId (r:0 w:1)
// Storage: RankedCollective IdToIndex (r:0 w:1)
fn add_member() -> Weight {
(11_000_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
}
// Storage: RankedCollective Members (r:1 w:1)
// Storage: RankedCollective MemberCount (r:1 w:1)
// Storage: RankedCollective IdToIndex (r:1 w:1)
// Storage: RankedCollective IndexToId (r:1 w:1)
fn remove_member(r: u32, ) -> Weight {
(16_855_000 as Weight)
// Standard Error: 27_000
.saturating_add((8_107_000 as Weight).saturating_mul(r as Weight))
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
.saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(r as Weight)))
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
.saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(r as Weight)))
}
// Storage: RankedCollective Members (r:1 w:1)
// Storage: RankedCollective MemberCount (r:1 w:1)
// Storage: RankedCollective IndexToId (r:0 w:1)
// Storage: RankedCollective IdToIndex (r:0 w:1)
fn promote_member(r: u32, ) -> Weight {
(11_936_000 as Weight)
// Standard Error: 3_000
.saturating_add((9_000 as Weight).saturating_mul(r as Weight))
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
}
// Storage: RankedCollective Members (r:1 w:1)
// Storage: RankedCollective MemberCount (r:1 w:1)
// Storage: RankedCollective IdToIndex (r:1 w:1)
// Storage: RankedCollective IndexToId (r:1 w:1)
fn demote_member(r: u32, ) -> Weight {
(17_582_000 as Weight)
// Standard Error: 14_000
.saturating_add((142_000 as Weight).saturating_mul(r as Weight))
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
}
// Storage: RankedCollective Members (r:1 w:0)
// Storage: RankedPolls ReferendumInfoFor (r:1 w:1)
// Storage: RankedCollective Voting (r:1 w:1)
// Storage: Scheduler Agenda (r:2 w:2)
fn vote() -> Weight {
(22_000_000 as Weight)
.saturating_add(RocksDbWeight::get().reads(5 as Weight))
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
}
// Storage: RankedPolls ReferendumInfoFor (r:1 w:0)
// Storage: RankedCollective Voting (r:0 w:1)
fn cleanup_poll(n: u32, ) -> Weight {
(6_188_000 as Weight)
// Standard Error: 1_000
.saturating_add((867_000 as Weight).saturating_mul(n as Weight))
.saturating_add(RocksDbWeight::get().reads(1 as Weight))
.saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(n as Weight)))
}
}