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
+18
View File
@@ -4764,6 +4764,7 @@ dependencies = [
"pallet-preimage",
"pallet-proxy",
"pallet-randomness-collective-flip",
"pallet-ranked-collective",
"pallet-recovery",
"pallet-referenda",
"pallet-remark",
@@ -5988,6 +5989,23 @@ dependencies = [
"sp-std",
]
[[package]]
name = "pallet-ranked-collective"
version = "4.0.0-dev"
dependencies = [
"frame-benchmarking",
"frame-support",
"frame-system",
"log",
"parity-scale-codec",
"scale-info",
"sp-arithmetic",
"sp-core",
"sp-io",
"sp-runtime",
"sp-std",
]
[[package]]
name = "pallet-recovery"
version = "4.0.0-dev"
+1
View File
@@ -115,6 +115,7 @@ members = [
"frame/nomination-pools",
"frame/nomination-pools/benchmarking",
"frame/randomness-collective-flip",
"frame/ranked-collective",
"frame/recovery",
"frame/referenda",
"frame/remark",
+4
View File
@@ -83,6 +83,7 @@ pallet-offences-benchmarking = { version = "4.0.0-dev", path = "../../../frame/o
pallet-preimage = { version = "4.0.0-dev", default-features = false, path = "../../../frame/preimage" }
pallet-proxy = { version = "4.0.0-dev", default-features = false, path = "../../../frame/proxy" }
pallet-randomness-collective-flip = { version = "4.0.0-dev", default-features = false, path = "../../../frame/randomness-collective-flip" }
pallet-ranked-collective = { version = "4.0.0-dev", default-features = false, path = "../../../frame/ranked-collective" }
pallet-recovery = { version = "4.0.0-dev", default-features = false, path = "../../../frame/recovery" }
pallet-referenda = { version = "4.0.0-dev", default-features = false, path = "../../../frame/referenda" }
pallet-remark = { version = "4.0.0-dev", default-features = false, path = "../../../frame/remark" }
@@ -176,6 +177,7 @@ std = [
"pallet-utility/std",
"sp-version/std",
"pallet-society/std",
"pallet-ranked-collective/std",
"pallet-referenda/std",
"pallet-remark/std",
"pallet-recovery/std",
@@ -218,6 +220,7 @@ runtime-benchmarks = [
"pallet-preimage/runtime-benchmarks",
"pallet-proxy/runtime-benchmarks",
"pallet-scheduler/runtime-benchmarks",
"pallet-ranked-collective/runtime-benchmarks",
"pallet-referenda/runtime-benchmarks",
"pallet-recovery/runtime-benchmarks",
"pallet-remark/runtime-benchmarks",
@@ -265,6 +268,7 @@ try-runtime = [
"pallet-offences/try-runtime",
"pallet-preimage/try-runtime",
"pallet-proxy/try-runtime",
"pallet-ranked-collective/try-runtime",
"pallet-randomness-collective-flip/try-runtime",
"pallet-recovery/try-runtime",
"pallet-referenda/try-runtime",
+36 -5
View File
@@ -20,7 +20,7 @@
#![cfg_attr(not(feature = "std"), no_std)]
// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256.
#![recursion_limit = "256"]
#![recursion_limit = "512"]
use codec::{Decode, Encode, MaxEncodedLen};
use frame_election_provider_support::{
@@ -43,7 +43,7 @@ use frame_support::{
};
use frame_system::{
limits::{BlockLength, BlockWeights},
EnsureRoot, EnsureSigned,
EnsureRoot, EnsureRootWithSuccess, EnsureSigned,
};
pub use node_primitives::{AccountId, Signature};
use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Index, Moment};
@@ -780,11 +780,11 @@ parameter_types! {
pub struct TracksInfo;
impl pallet_referenda::TracksInfo<Balance, BlockNumber> for TracksInfo {
type Id = u8;
type Id = u16;
type Origin = <Origin as frame_support::traits::OriginTrait>::PalletsOrigin;
fn tracks() -> &'static [(Self::Id, pallet_referenda::TrackInfo<Balance, BlockNumber>)] {
static DATA: [(u8, pallet_referenda::TrackInfo<Balance, BlockNumber>); 1] = [(
0u8,
static DATA: [(u16, pallet_referenda::TrackInfo<Balance, BlockNumber>); 1] = [(
0u16,
pallet_referenda::TrackInfo {
name: "root",
max_deciding: 1,
@@ -837,6 +837,34 @@ impl pallet_referenda::Config for Runtime {
type Tracks = TracksInfo;
}
impl pallet_referenda::Config<pallet_referenda::Instance2> for Runtime {
type WeightInfo = pallet_referenda::weights::SubstrateWeight<Self>;
type Call = Call;
type Event = Event;
type Scheduler = Scheduler;
type Currency = pallet_balances::Pallet<Self>;
type CancelOrigin = EnsureRoot<AccountId>;
type KillOrigin = EnsureRoot<AccountId>;
type Slash = ();
type Votes = pallet_ranked_collective::Votes;
type Tally = pallet_ranked_collective::TallyOf<Runtime>;
type SubmissionDeposit = SubmissionDeposit;
type MaxQueued = ConstU32<100>;
type UndecidingTimeout = UndecidingTimeout;
type AlarmInterval = AlarmInterval;
type Tracks = TracksInfo;
}
impl pallet_ranked_collective::Config for Runtime {
type WeightInfo = pallet_ranked_collective::weights::SubstrateWeight<Self>;
type Event = Event;
type PromoteOrigin = EnsureRootWithSuccess<AccountId, ConstU16<65535>>;
type DemoteOrigin = EnsureRootWithSuccess<AccountId, ConstU16<65535>>;
type Polls = RankedPolls;
type MinRankOfClass = traits::Identity;
type VoteWeight = pallet_ranked_collective::Geometric;
}
impl pallet_remark::Config for Runtime {
type WeightInfo = pallet_remark::weights::SubstrateWeight<Self>;
type Event = Event;
@@ -1534,6 +1562,8 @@ construct_runtime!(
ConvictionVoting: pallet_conviction_voting,
Whitelist: pallet_whitelist,
NominationPools: pallet_nomination_pools,
RankedPolls: pallet_referenda::<Instance2>,
RankedCollective: pallet_ranked_collective,
}
);
@@ -1622,6 +1652,7 @@ mod benches {
[pallet_offences, OffencesBench::<Runtime>]
[pallet_preimage, Preimage]
[pallet_proxy, Proxy]
[pallet_ranked_collective, RankedCollective]
[pallet_referenda, Referenda]
[pallet_recovery, Recovery]
[pallet_remark, Remark]
@@ -0,0 +1,49 @@
[package]
name = "pallet-ranked-collective"
version = "4.0.0-dev"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "Apache-2.0"
homepage = "https://substrate.io"
repository = "https://github.com/paritytech/substrate/"
description = "Ranked collective system: Members of a set of account IDs can make their collective feelings known through dispatched calls from one of two specialized origins."
readme = "README.md"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] }
log = { version = "0.4.16", default-features = false }
scale-info = { version = "2.0.1", default-features = false, features = ["derive"] }
frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" }
frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" }
frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" }
sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../primitives/arithmetic" }
sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" }
sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" }
sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" }
sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" }
[features]
default = ["std"]
std = [
"codec/std",
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
"log/std",
"scale-info/std",
"sp-arithmetic/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
try-runtime = ["frame-support/try-runtime"]
@@ -0,0 +1,22 @@
# 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.
@@ -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)))
}
}
+1 -1
View File
@@ -150,7 +150,7 @@ pub mod pallet {
/// Handler for the unbalanced reduction when slashing a preimage deposit.
type Slash: OnUnbalanced<NegativeImbalanceOf<Self, I>>;
/// The counting type for votes. Usually just balance.
type Votes: AtLeast32BitUnsigned + Copy + Parameter + Member;
type Votes: AtLeast32BitUnsigned + Copy + Parameter + Member + MaxEncodedLen;
/// The tallying type.
type Tally: VoteTally<Self::Votes, TrackIdOf<Self, I>>
+ Clone
+1 -1
View File
@@ -136,7 +136,7 @@ pub struct TrackInfo<Balance, Moment> {
/// Information on the voting tracks.
pub trait TracksInfo<Balance, Moment> {
/// The identifier for a track.
type Id: Copy + Parameter + Ord + PartialOrd + Send + Sync + 'static;
type Id: Copy + Parameter + Ord + PartialOrd + Send + Sync + 'static + MaxEncodedLen;
/// The origin type from which a track is implied.
type Origin;
@@ -186,9 +186,16 @@ pub fn expand_outer_origin(
#system_path::RawOrigin::Root.into()
}
fn signed(by: <#runtime as #system_path::Config>::AccountId) -> Self {
fn signed(by: Self::AccountId) -> Self {
#system_path::RawOrigin::Signed(by).into()
}
fn as_signed(self) -> Option<Self::AccountId> {
match self.caller {
OriginCaller::system(#system_path::RawOrigin::Signed(by)) => Some(by),
_ => None,
}
}
}
#[derive(
+3
View File
@@ -2748,6 +2748,9 @@ mod tests {
fn signed(_by: <TraitImpl as system::Config>::AccountId) -> Self {
unimplemented!("Not required in tests!")
}
fn as_signed(self) -> Option<Self::AccountId> {
unimplemented!("Not required in tests!")
}
}
impl system::Config for TraitImpl {
@@ -286,6 +286,12 @@ impl<T, S: Get<u32>> BoundedVec<T, S> {
Self::with_bounded_capacity(Self::bound())
}
/// Consume and truncate the vector `v` in order to create a new instance of `Self` from it.
pub fn truncate_from(mut v: Vec<T>) -> Self {
v.truncate(Self::bound());
Self::unchecked_from(v)
}
/// Get the bound of the type in `usize`.
pub fn bound() -> usize {
S::get() as usize
+5 -1
View File
@@ -27,7 +27,7 @@ use crate::{
use codec::{Decode, Encode, EncodeLike, FullCodec, FullEncode};
use sp_core::storage::ChildInfo;
use sp_runtime::generic::{Digest, DigestItem};
use sp_std::prelude::*;
use sp_std::{marker::PhantomData, prelude::*};
pub use self::{
transactional::{
@@ -51,6 +51,10 @@ pub mod types;
pub mod unhashed;
pub mod weak_bounded_vec;
/// Utility type for converting a storage map into a `Get<u32>` impl which returns the maximum
/// key size.
pub struct KeyLenOf<M>(PhantomData<M>);
/// A trait for working with macro-generated storage values under the substrate storage API.
///
/// Details on implementation can be found at [`generator::StorageValue`].
@@ -22,7 +22,7 @@ use crate::{
metadata::{StorageEntryMetadata, StorageEntryType},
storage::{
types::{OptionQuery, QueryKindTrait, StorageEntryMetadataBuilder},
StorageAppend, StorageDecodeLength, StoragePrefixedMap, StorageTryAppend,
KeyLenOf, StorageAppend, StorageDecodeLength, StoragePrefixedMap, StorageTryAppend,
},
traits::{Get, GetDefault, StorageInfo, StorageInstance},
};
@@ -70,6 +70,35 @@ pub struct StorageDoubleMap<
)>,
);
impl<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty, MaxValues> Get<u32>
for KeyLenOf<
StorageDoubleMap<
Prefix,
Hasher1,
Key1,
Hasher2,
Key2,
Value,
QueryKind,
OnEmpty,
MaxValues,
>,
> where
Prefix: StorageInstance,
Hasher1: crate::hash::StorageHasher,
Hasher2: crate::hash::StorageHasher,
Key1: MaxEncodedLen,
Key2: MaxEncodedLen,
{
fn get() -> u32 {
let z = Hasher1::max_len::<Key1>() +
Hasher2::max_len::<Key2>() +
Prefix::pallet_prefix().len() +
Prefix::STORAGE_PREFIX.len();
z as u32
}
}
impl<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty, MaxValues>
crate::storage::generator::StorageDoubleMap<Key1, Key2, Value>
for StorageDoubleMap<Prefix, Hasher1, Key1, Hasher2, Key2, Value, QueryKind, OnEmpty, MaxValues>
@@ -22,7 +22,7 @@ use crate::{
metadata::{StorageEntryMetadata, StorageEntryType},
storage::{
types::{OptionQuery, QueryKindTrait, StorageEntryMetadataBuilder},
StorageAppend, StorageDecodeLength, StoragePrefixedMap, StorageTryAppend,
KeyLenOf, StorageAppend, StorageDecodeLength, StoragePrefixedMap, StorageTryAppend,
},
traits::{Get, GetDefault, StorageInfo, StorageInstance},
};
@@ -53,6 +53,20 @@ pub struct StorageMap<
MaxValues = GetDefault,
>(core::marker::PhantomData<(Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues)>);
impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues> Get<u32>
for KeyLenOf<StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues>>
where
Prefix: StorageInstance,
Hasher: crate::hash::StorageHasher,
Key: FullCodec + MaxEncodedLen,
{
fn get() -> u32 {
let z =
Hasher::max_len::<Key>() + Prefix::pallet_prefix().len() + Prefix::STORAGE_PREFIX.len();
z as u32
}
}
impl<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues>
crate::storage::generator::StorageMap<Key, Value>
for StorageMap<Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues>
+2 -1
View File
@@ -97,7 +97,8 @@ mod dispatch;
pub use dispatch::EnsureOneOf;
pub use dispatch::{
AsEnsureOriginWithArg, DispatchableWithStorageLayer, EitherOf, EitherOfDiverse, EnsureOrigin,
EnsureOriginWithArg, NeverEnsureOrigin, OriginTrait, UnfilteredDispatchable,
EnsureOriginWithArg, MapSuccess, NeverEnsureOrigin, OriginTrait, ReduceBy, TryMapSuccess,
UnfilteredDispatchable,
};
mod voting;
@@ -319,6 +319,9 @@ pub trait OriginTrait: Sized {
/// Create with system signed origin and `frame_system::Config::BaseCallFilter`.
fn signed(by: Self::AccountId) -> Self;
/// Extract the signer from the message if it is a `Signed` origin.
fn as_signed(self) -> Option<Self::AccountId>;
}
#[cfg(test)]
+4 -4
View File
@@ -19,7 +19,7 @@
//! votes.
use crate::dispatch::{DispatchError, Parameter};
use codec::HasCompact;
use codec::{HasCompact, MaxEncodedLen};
use sp_arithmetic::{
traits::{SaturatedConversion, UniqueSaturatedFrom, UniqueSaturatedInto},
Perbill,
@@ -123,9 +123,9 @@ impl<Tally, Moment, Class> PollStatus<Tally, Moment, Class> {
}
pub trait Polling<Tally> {
type Index: Parameter + Member + Ord + PartialOrd + Copy + HasCompact;
type Votes: Parameter + Member + Ord + PartialOrd + Copy + HasCompact;
type Class: Parameter + Member + Ord + PartialOrd;
type Index: Parameter + Member + Ord + PartialOrd + Copy + HasCompact + MaxEncodedLen;
type Votes: Parameter + Member + Ord + PartialOrd + Copy + HasCompact + MaxEncodedLen;
type Class: Parameter + Member + Ord + PartialOrd + MaxEncodedLen;
type Moment;
/// Provides a vec of values that `T` may take.
+4 -1
View File
@@ -491,7 +491,10 @@ impl frame_support::traits::OriginTrait for Origin {
fn root() -> Self {
unimplemented!("Not required in tests!")
}
fn signed(_by: <Runtime as frame_system::Config>::AccountId) -> Self {
fn signed(_by: Self::AccountId) -> Self {
unimplemented!("Not required in tests!")
}
fn as_signed(self) -> Option<Self::AccountId> {
unimplemented!("Not required in tests!")
}
}