mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 21:11:07 +00:00
Introduce default-setting prime for collective (#5137)
* Introduce default-setting prime for collective * Docs. * Elections phragmen supports prime * Fix * Membership supports prime * Fix * Update frame/collective/src/lib.rs Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com> Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
@@ -68,6 +68,7 @@ use impls::{CurrencyToVoteHandler, Author, LinearWeightToFee, TargetedFeeAdjustm
|
||||
/// Constant values used within the runtime.
|
||||
pub mod constants;
|
||||
use constants::{time::*, currency::*};
|
||||
use frame_system::Trait;
|
||||
|
||||
// Make the WASM binary available.
|
||||
#[cfg(feature = "std")]
|
||||
@@ -330,11 +331,16 @@ impl pallet_democracy::Trait for Runtime {
|
||||
type Slash = Treasury;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const CouncilMotionDuration: BlockNumber = 5 * DAYS;
|
||||
}
|
||||
|
||||
type CouncilCollective = pallet_collective::Instance1;
|
||||
impl pallet_collective::Trait<CouncilCollective> for Runtime {
|
||||
type Origin = Origin;
|
||||
type Proposal = Call;
|
||||
type Event = Event;
|
||||
type MotionDuration = CouncilMotionDuration;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
@@ -360,11 +366,16 @@ impl pallet_elections_phragmen::Trait for Runtime {
|
||||
type TermDuration = TermDuration;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const TechnicalMotionDuration: BlockNumber = 5 * DAYS;
|
||||
}
|
||||
|
||||
type TechnicalCollective = pallet_collective::Instance2;
|
||||
impl pallet_collective::Trait<TechnicalCollective> for Runtime {
|
||||
type Origin = Origin;
|
||||
type Proposal = Call;
|
||||
type Event = Event;
|
||||
type MotionDuration = TechnicalMotionDuration;
|
||||
}
|
||||
|
||||
impl pallet_membership::Trait<pallet_membership::Instance1> for Runtime {
|
||||
@@ -373,6 +384,7 @@ impl pallet_membership::Trait<pallet_membership::Instance1> for Runtime {
|
||||
type RemoveOrigin = pallet_collective::EnsureProportionMoreThan<_1, _2, AccountId, CouncilCollective>;
|
||||
type SwapOrigin = pallet_collective::EnsureProportionMoreThan<_1, _2, AccountId, CouncilCollective>;
|
||||
type ResetOrigin = pallet_collective::EnsureProportionMoreThan<_1, _2, AccountId, CouncilCollective>;
|
||||
type PrimeOrigin = pallet_collective::EnsureProportionMoreThan<_1, _2, AccountId, CouncilCollective>;
|
||||
type MembershipInitialized = TechnicalCommittee;
|
||||
type MembershipChanged = TechnicalCommittee;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,20 @@
|
||||
//! through dispatched calls from one of two specialized origins.
|
||||
//!
|
||||
//! The membership can be provided in one of two ways: either directly, using the Root-dispatchable
|
||||
//! function `set_members`, or indirectly, through implementing the `ChangeMembers`
|
||||
//! function `set_members`, or indirectly, through implementing the `ChangeMembers`.
|
||||
//!
|
||||
//! A "prime" member may be set allowing their vote to act as the default vote in case of any
|
||||
//! abstentions after the voting period.
|
||||
//!
|
||||
//! Voting happens through motions comprising a proposal (i.e. a curried dispatchable) plus a
|
||||
//! number of approvals required for it to pass and be called. Motions are open for members to
|
||||
//! vote on for a minimum period given by `MotionDuration`. As soon as the needed number of
|
||||
//! approvals is given, the motion is closed and executed. If the number of approvals is not reached
|
||||
//! during the voting period, then `close` may be called by any account in order to force the end
|
||||
//! the motion explicitly. If a prime member is defined then their vote is used in place of any
|
||||
//! abstentions and the proposal is executed if there are enough approvals counting the new votes.
|
||||
//!
|
||||
//! If there are not, or if no prime is set, then the motion is dropped without being executed.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![recursion_limit="128"]
|
||||
@@ -30,7 +43,7 @@ use sp_runtime::traits::{Hash, EnsureOrigin};
|
||||
use frame_support::weights::SimpleDispatchInfo;
|
||||
use frame_support::{
|
||||
dispatch::{Dispatchable, Parameter}, codec::{Encode, Decode},
|
||||
traits::{ChangeMembers, InitializeMembers}, decl_module, decl_event,
|
||||
traits::{Get, ChangeMembers, InitializeMembers}, decl_module, decl_event,
|
||||
decl_storage, decl_error, ensure,
|
||||
};
|
||||
use frame_system::{self as system, ensure_signed, ensure_root};
|
||||
@@ -53,6 +66,9 @@ pub trait Trait<I=DefaultInstance>: frame_system::Trait {
|
||||
|
||||
/// The outer event type.
|
||||
type Event: From<Event<Self, I>> + Into<<Self as frame_system::Trait>::Event>;
|
||||
|
||||
/// The time-out for council motions.
|
||||
type MotionDuration: Get<Self::BlockNumber>;
|
||||
}
|
||||
|
||||
/// Origin for the collective module.
|
||||
@@ -71,7 +87,7 @@ pub type Origin<T, I=DefaultInstance> = RawOrigin<<T as frame_system::Trait>::Ac
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)]
|
||||
/// Info for keeping track of a motion being voted on.
|
||||
pub struct Votes<AccountId> {
|
||||
pub struct Votes<AccountId, BlockNumber> {
|
||||
/// The proposal's unique index.
|
||||
index: ProposalIndex,
|
||||
/// The number of approval votes that are needed to pass the motion.
|
||||
@@ -80,6 +96,8 @@ pub struct Votes<AccountId> {
|
||||
ayes: Vec<AccountId>,
|
||||
/// The current set of voters that rejected it.
|
||||
nays: Vec<AccountId>,
|
||||
/// The hard end time of this vote.
|
||||
end: BlockNumber,
|
||||
}
|
||||
|
||||
decl_storage! {
|
||||
@@ -91,11 +109,14 @@ decl_storage! {
|
||||
map hasher(blake2_256) T::Hash => Option<<T as Trait<I>>::Proposal>;
|
||||
/// Votes on a given proposal, if it is ongoing.
|
||||
pub Voting get(fn voting):
|
||||
map hasher(blake2_256) T::Hash => Option<Votes<T::AccountId>>;
|
||||
map hasher(blake2_256) T::Hash => Option<Votes<T::AccountId, T::BlockNumber>>;
|
||||
/// Proposals so far.
|
||||
pub ProposalCount get(fn proposal_count): u32;
|
||||
/// The current members of the collective. This is stored sorted (just by value).
|
||||
pub Members get(fn members): Vec<T::AccountId>;
|
||||
/// The member who provides the default vote for any other members that do not vote before
|
||||
/// the timeout. If None, then no member has that privilege.
|
||||
pub Prime get(fn prime): Option<T::AccountId>;
|
||||
}
|
||||
add_extra_genesis {
|
||||
config(phantom): sp_std::marker::PhantomData<I>;
|
||||
@@ -123,6 +144,8 @@ decl_event! {
|
||||
Executed(Hash, bool),
|
||||
/// A single member did some action; `bool` is true if returned without error.
|
||||
MemberExecuted(Hash, bool),
|
||||
/// A proposal was closed after its duration was up.
|
||||
Closed(Hash, MemberCount, MemberCount),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +163,8 @@ decl_error! {
|
||||
DuplicateVote,
|
||||
/// Members are already initialized!
|
||||
AlreadyInitialized,
|
||||
/// The close call is made too early, before the end of the voting.
|
||||
TooEarly,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,19 +177,21 @@ decl_module! {
|
||||
|
||||
fn deposit_event() = default;
|
||||
|
||||
/// Set the collective's membership manually to `new_members`. Be nice to the chain and
|
||||
/// provide it pre-sorted.
|
||||
/// Set the collective's membership.
|
||||
///
|
||||
/// - `new_members`: The new member list. Be nice to the chain and
|
||||
// provide it sorted.
|
||||
/// - `prime`: The prime member whose vote sets the default.
|
||||
///
|
||||
/// Requires root origin.
|
||||
#[weight = SimpleDispatchInfo::FixedOperational(100_000)]
|
||||
fn set_members(origin, new_members: Vec<T::AccountId>) {
|
||||
fn set_members(origin, new_members: Vec<T::AccountId>, prime: Option<T::AccountId>) {
|
||||
ensure_root(origin)?;
|
||||
let mut new_members = new_members;
|
||||
new_members.sort();
|
||||
<Members<T, I>>::mutate(|m| {
|
||||
<Self as ChangeMembers<T::AccountId>>::set_members_sorted(&new_members[..], m);
|
||||
*m = new_members;
|
||||
});
|
||||
let old = Members::<T, I>::get();
|
||||
<Self as ChangeMembers<T::AccountId>>::set_members_sorted(&new_members[..], &old);
|
||||
Prime::<T, I>::set(prime);
|
||||
}
|
||||
|
||||
/// Dispatch a proposal from a member using the `Member` origin.
|
||||
@@ -202,7 +229,8 @@ decl_module! {
|
||||
<ProposalCount<I>>::mutate(|i| *i += 1);
|
||||
<Proposals<T, I>>::mutate(|proposals| proposals.push(proposal_hash));
|
||||
<ProposalOf<T, I>>::insert(proposal_hash, *proposal);
|
||||
let votes = Votes { index, threshold, ayes: vec![who.clone()], nays: vec![] };
|
||||
let end = system::Module::<T>::block_number() + T::MotionDuration::get();
|
||||
let votes = Votes { index, threshold, ayes: vec![who.clone()], nays: vec![], end };
|
||||
<Voting<T, I>>::insert(proposal_hash, votes);
|
||||
|
||||
Self::deposit_event(RawEvent::Proposed(who, index, proposal_hash, threshold));
|
||||
@@ -249,32 +277,55 @@ decl_module! {
|
||||
Self::deposit_event(RawEvent::Voted(who, proposal, approve, yes_votes, no_votes));
|
||||
|
||||
let seats = Self::members().len() as MemberCount;
|
||||
|
||||
let approved = yes_votes >= voting.threshold;
|
||||
let disapproved = seats.saturating_sub(no_votes) < voting.threshold;
|
||||
if approved || disapproved {
|
||||
if approved {
|
||||
Self::deposit_event(RawEvent::Approved(proposal));
|
||||
|
||||
// execute motion, assuming it exists.
|
||||
if let Some(p) = <ProposalOf<T, I>>::take(&proposal) {
|
||||
let origin = RawOrigin::Members(voting.threshold, seats).into();
|
||||
let ok = p.dispatch(origin).is_ok();
|
||||
Self::deposit_event(RawEvent::Executed(proposal, ok));
|
||||
}
|
||||
} else {
|
||||
// disapproved
|
||||
<ProposalOf<T, I>>::remove(&proposal);
|
||||
Self::deposit_event(RawEvent::Disapproved(proposal));
|
||||
}
|
||||
|
||||
// remove vote
|
||||
<Voting<T, I>>::remove(&proposal);
|
||||
<Proposals<T, I>>::mutate(|proposals| proposals.retain(|h| h != &proposal));
|
||||
Self::finalize_proposal(approved, seats, voting, proposal);
|
||||
} else {
|
||||
// update voting
|
||||
<Voting<T, I>>::insert(&proposal, voting);
|
||||
Voting::<T, I>::insert(&proposal, voting);
|
||||
}
|
||||
}
|
||||
|
||||
/// May be called by any signed account after the voting duration has ended in order to
|
||||
/// finish voting and close the proposal.
|
||||
///
|
||||
/// Abstentions are counted as rejections unless there is a prime member set and the prime
|
||||
/// member cast an approval.
|
||||
///
|
||||
/// - the weight of `proposal` preimage.
|
||||
/// - up to three events deposited.
|
||||
/// - one read, two removals, one mutation. (plus three static reads.)
|
||||
/// - computation and i/o `O(P + L + M)` where:
|
||||
/// - `M` is number of members,
|
||||
/// - `P` is number of active proposals,
|
||||
/// - `L` is the encoded length of `proposal` preimage.
|
||||
#[weight = SimpleDispatchInfo::FixedOperational(200_000)]
|
||||
fn close(origin, proposal: T::Hash, #[compact] index: ProposalIndex) {
|
||||
let _ = ensure_signed(origin)?;
|
||||
|
||||
let voting = Self::voting(&proposal).ok_or(Error::<T, I>::ProposalMissing)?;
|
||||
ensure!(voting.index == index, Error::<T, I>::WrongIndex);
|
||||
ensure!(system::Module::<T>::block_number() >= voting.end, Error::<T, I>::TooEarly);
|
||||
|
||||
// default to true only if there's a prime and they voted in favour.
|
||||
let default = Self::prime().map_or(
|
||||
false,
|
||||
|who| voting.ayes.iter().any(|a| a == &who),
|
||||
);
|
||||
|
||||
let mut no_votes = voting.nays.len() as MemberCount;
|
||||
let mut yes_votes = voting.ayes.len() as MemberCount;
|
||||
let seats = Self::members().len() as MemberCount;
|
||||
let abstentions = seats - (yes_votes + no_votes);
|
||||
match default {
|
||||
true => yes_votes += abstentions,
|
||||
false => no_votes += abstentions,
|
||||
}
|
||||
|
||||
Self::deposit_event(RawEvent::Closed(proposal, yes_votes, no_votes));
|
||||
Self::finalize_proposal(yes_votes >= voting.threshold, seats, voting, proposal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,10 +333,54 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
|
||||
pub fn is_member(who: &T::AccountId) -> bool {
|
||||
Self::members().contains(who)
|
||||
}
|
||||
|
||||
/// Weight:
|
||||
/// If `approved`:
|
||||
/// - the weight of `proposal` preimage.
|
||||
/// - two events deposited.
|
||||
/// - two removals, one mutation.
|
||||
/// - computation and i/o `O(P + L)` where:
|
||||
/// - `P` is number of active proposals,
|
||||
/// - `L` is the encoded length of `proposal` preimage.
|
||||
///
|
||||
/// If not `approved`:
|
||||
/// - one event deposited.
|
||||
/// Two removals, one mutation.
|
||||
/// Computation and i/o `O(P)` where:
|
||||
/// - `P` is number of active proposals
|
||||
fn finalize_proposal(
|
||||
approved: bool,
|
||||
seats: MemberCount,
|
||||
voting: Votes<T::AccountId, T::BlockNumber>,
|
||||
proposal: T::Hash,
|
||||
) {
|
||||
if approved {
|
||||
Self::deposit_event(RawEvent::Approved(proposal));
|
||||
|
||||
// execute motion, assuming it exists.
|
||||
if let Some(p) = ProposalOf::<T, I>::take(&proposal) {
|
||||
let origin = RawOrigin::Members(voting.threshold, seats).into();
|
||||
let ok = p.dispatch(origin).is_ok();
|
||||
Self::deposit_event(RawEvent::Executed(proposal, ok));
|
||||
}
|
||||
} else {
|
||||
// disapproved
|
||||
ProposalOf::<T, I>::remove(&proposal);
|
||||
Self::deposit_event(RawEvent::Disapproved(proposal));
|
||||
}
|
||||
|
||||
// remove vote
|
||||
Voting::<T, I>::remove(&proposal);
|
||||
Proposals::<T, I>::mutate(|proposals| proposals.retain(|h| h != &proposal));
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait<I>, I: Instance> ChangeMembers<T::AccountId> for Module<T, I> {
|
||||
fn change_members_sorted(_incoming: &[T::AccountId], outgoing: &[T::AccountId], new: &[T::AccountId]) {
|
||||
fn change_members_sorted(
|
||||
_incoming: &[T::AccountId],
|
||||
outgoing: &[T::AccountId],
|
||||
new: &[T::AccountId],
|
||||
) {
|
||||
// remove accounts from all current voting in motions.
|
||||
let mut outgoing = outgoing.to_vec();
|
||||
outgoing.sort_unstable();
|
||||
@@ -302,7 +397,12 @@ impl<T: Trait<I>, I: Instance> ChangeMembers<T::AccountId> for Module<T, I> {
|
||||
}
|
||||
);
|
||||
}
|
||||
<Members<T, I>>::put(new);
|
||||
Members::<T, I>::put(new);
|
||||
Prime::<T, I>::kill();
|
||||
}
|
||||
|
||||
fn set_prime(prime: Option<T::AccountId>) {
|
||||
Prime::<T, I>::set(prime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,6 +515,7 @@ mod tests {
|
||||
pub const MaximumBlockWeight: Weight = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
pub const AvailableBlockRatio: Perbill = Perbill::one();
|
||||
pub const MotionDuration: u64 = 3;
|
||||
}
|
||||
impl frame_system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
@@ -441,11 +542,13 @@ mod tests {
|
||||
type Origin = Origin;
|
||||
type Proposal = Call;
|
||||
type Event = Event;
|
||||
type MotionDuration = MotionDuration;
|
||||
}
|
||||
impl Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Proposal = Call;
|
||||
type Event = Event;
|
||||
type MotionDuration = MotionDuration;
|
||||
}
|
||||
|
||||
pub type Block = sp_runtime::generic::Block<Header, UncheckedExtrinsic>;
|
||||
@@ -486,22 +589,101 @@ mod tests {
|
||||
Call::System(frame_system::Call::remark(value.encode()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn close_works() {
|
||||
make_ext().execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
let proposal = make_proposal(42);
|
||||
let hash = BlakeTwo256::hash_of(&proposal);
|
||||
|
||||
assert_ok!(Collective::propose(Origin::signed(1), 3, Box::new(proposal.clone())));
|
||||
assert_ok!(Collective::vote(Origin::signed(2), hash.clone(), 0, true));
|
||||
|
||||
System::set_block_number(3);
|
||||
assert_noop!(
|
||||
Collective::close(Origin::signed(4), hash.clone(), 0),
|
||||
Error::<Test, Instance1>::TooEarly
|
||||
);
|
||||
|
||||
System::set_block_number(4);
|
||||
assert_ok!(Collective::close(Origin::signed(4), hash.clone(), 0));
|
||||
|
||||
let record = |event| EventRecord { phase: Phase::Finalization, event, topics: vec![] };
|
||||
assert_eq!(System::events(), vec![
|
||||
record(Event::collective_Instance1(RawEvent::Proposed(1, 0, hash.clone(), 3))),
|
||||
record(Event::collective_Instance1(RawEvent::Voted(2, hash.clone(), true, 2, 0))),
|
||||
record(Event::collective_Instance1(RawEvent::Closed(hash.clone(), 2, 1))),
|
||||
record(Event::collective_Instance1(RawEvent::Disapproved(hash.clone())))
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn close_with_prime_works() {
|
||||
make_ext().execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
let proposal = make_proposal(42);
|
||||
let hash = BlakeTwo256::hash_of(&proposal);
|
||||
assert_ok!(Collective::set_members(Origin::ROOT, vec![1, 2, 3], Some(3)));
|
||||
|
||||
assert_ok!(Collective::propose(Origin::signed(1), 3, Box::new(proposal.clone())));
|
||||
assert_ok!(Collective::vote(Origin::signed(2), hash.clone(), 0, true));
|
||||
|
||||
System::set_block_number(4);
|
||||
assert_ok!(Collective::close(Origin::signed(4), hash.clone(), 0));
|
||||
|
||||
let record = |event| EventRecord { phase: Phase::Finalization, event, topics: vec![] };
|
||||
assert_eq!(System::events(), vec![
|
||||
record(Event::collective_Instance1(RawEvent::Proposed(1, 0, hash.clone(), 3))),
|
||||
record(Event::collective_Instance1(RawEvent::Voted(2, hash.clone(), true, 2, 0))),
|
||||
record(Event::collective_Instance1(RawEvent::Closed(hash.clone(), 2, 1))),
|
||||
record(Event::collective_Instance1(RawEvent::Disapproved(hash.clone())))
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn close_with_voting_prime_works() {
|
||||
make_ext().execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
let proposal = make_proposal(42);
|
||||
let hash = BlakeTwo256::hash_of(&proposal);
|
||||
assert_ok!(Collective::set_members(Origin::ROOT, vec![1, 2, 3], Some(1)));
|
||||
|
||||
assert_ok!(Collective::propose(Origin::signed(1), 3, Box::new(proposal.clone())));
|
||||
assert_ok!(Collective::vote(Origin::signed(2), hash.clone(), 0, true));
|
||||
|
||||
System::set_block_number(4);
|
||||
assert_ok!(Collective::close(Origin::signed(4), hash.clone(), 0));
|
||||
|
||||
let record = |event| EventRecord { phase: Phase::Finalization, event, topics: vec![] };
|
||||
assert_eq!(System::events(), vec![
|
||||
record(Event::collective_Instance1(RawEvent::Proposed(1, 0, hash.clone(), 3))),
|
||||
record(Event::collective_Instance1(RawEvent::Voted(2, hash.clone(), true, 2, 0))),
|
||||
record(Event::collective_Instance1(RawEvent::Closed(hash.clone(), 3, 0))),
|
||||
record(Event::collective_Instance1(RawEvent::Approved(hash.clone()))),
|
||||
record(Event::collective_Instance1(RawEvent::Executed(hash.clone(), false)))
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removal_of_old_voters_votes_works() {
|
||||
make_ext().execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
let proposal = make_proposal(42);
|
||||
let hash = BlakeTwo256::hash_of(&proposal);
|
||||
let end = 4;
|
||||
assert_ok!(Collective::propose(Origin::signed(1), 3, Box::new(proposal.clone())));
|
||||
assert_ok!(Collective::vote(Origin::signed(2), hash.clone(), 0, true));
|
||||
assert_eq!(
|
||||
Collective::voting(&hash),
|
||||
Some(Votes { index: 0, threshold: 3, ayes: vec![1, 2], nays: vec![] })
|
||||
Some(Votes { index: 0, threshold: 3, ayes: vec![1, 2], nays: vec![], end })
|
||||
);
|
||||
Collective::change_members_sorted(&[4], &[1], &[2, 3, 4]);
|
||||
assert_eq!(
|
||||
Collective::voting(&hash),
|
||||
Some(Votes { index: 0, threshold: 3, ayes: vec![2], nays: vec![] })
|
||||
Some(Votes { index: 0, threshold: 3, ayes: vec![2], nays: vec![], end })
|
||||
);
|
||||
|
||||
let proposal = make_proposal(69);
|
||||
@@ -510,12 +692,12 @@ mod tests {
|
||||
assert_ok!(Collective::vote(Origin::signed(3), hash.clone(), 1, false));
|
||||
assert_eq!(
|
||||
Collective::voting(&hash),
|
||||
Some(Votes { index: 1, threshold: 2, ayes: vec![2], nays: vec![3] })
|
||||
Some(Votes { index: 1, threshold: 2, ayes: vec![2], nays: vec![3], end })
|
||||
);
|
||||
Collective::change_members_sorted(&[], &[3], &[2, 4]);
|
||||
assert_eq!(
|
||||
Collective::voting(&hash),
|
||||
Some(Votes { index: 1, threshold: 2, ayes: vec![2], nays: vec![] })
|
||||
Some(Votes { index: 1, threshold: 2, ayes: vec![2], nays: vec![], end })
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -526,16 +708,17 @@ mod tests {
|
||||
System::set_block_number(1);
|
||||
let proposal = make_proposal(42);
|
||||
let hash = BlakeTwo256::hash_of(&proposal);
|
||||
let end = 4;
|
||||
assert_ok!(Collective::propose(Origin::signed(1), 3, Box::new(proposal.clone())));
|
||||
assert_ok!(Collective::vote(Origin::signed(2), hash.clone(), 0, true));
|
||||
assert_eq!(
|
||||
Collective::voting(&hash),
|
||||
Some(Votes { index: 0, threshold: 3, ayes: vec![1, 2], nays: vec![] })
|
||||
Some(Votes { index: 0, threshold: 3, ayes: vec![1, 2], nays: vec![], end })
|
||||
);
|
||||
assert_ok!(Collective::set_members(Origin::ROOT, vec![2, 3, 4]));
|
||||
assert_ok!(Collective::set_members(Origin::ROOT, vec![2, 3, 4], None));
|
||||
assert_eq!(
|
||||
Collective::voting(&hash),
|
||||
Some(Votes { index: 0, threshold: 3, ayes: vec![2], nays: vec![] })
|
||||
Some(Votes { index: 0, threshold: 3, ayes: vec![2], nays: vec![], end })
|
||||
);
|
||||
|
||||
let proposal = make_proposal(69);
|
||||
@@ -544,12 +727,12 @@ mod tests {
|
||||
assert_ok!(Collective::vote(Origin::signed(3), hash.clone(), 1, false));
|
||||
assert_eq!(
|
||||
Collective::voting(&hash),
|
||||
Some(Votes { index: 1, threshold: 2, ayes: vec![2], nays: vec![3] })
|
||||
Some(Votes { index: 1, threshold: 2, ayes: vec![2], nays: vec![3], end })
|
||||
);
|
||||
assert_ok!(Collective::set_members(Origin::ROOT, vec![2, 4]));
|
||||
assert_ok!(Collective::set_members(Origin::ROOT, vec![2, 4], None));
|
||||
assert_eq!(
|
||||
Collective::voting(&hash),
|
||||
Some(Votes { index: 1, threshold: 2, ayes: vec![2], nays: vec![] })
|
||||
Some(Votes { index: 1, threshold: 2, ayes: vec![2], nays: vec![], end })
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -560,12 +743,13 @@ mod tests {
|
||||
System::set_block_number(1);
|
||||
let proposal = make_proposal(42);
|
||||
let hash = proposal.blake2_256().into();
|
||||
let end = 4;
|
||||
assert_ok!(Collective::propose(Origin::signed(1), 3, Box::new(proposal.clone())));
|
||||
assert_eq!(Collective::proposals(), vec![hash]);
|
||||
assert_eq!(Collective::proposal_of(&hash), Some(proposal));
|
||||
assert_eq!(
|
||||
Collective::voting(&hash),
|
||||
Some(Votes { index: 0, threshold: 3, ayes: vec![1], nays: vec![] })
|
||||
Some(Votes { index: 0, threshold: 3, ayes: vec![1], nays: vec![], end })
|
||||
);
|
||||
|
||||
assert_eq!(System::events(), vec![
|
||||
@@ -629,10 +813,11 @@ mod tests {
|
||||
System::set_block_number(1);
|
||||
let proposal = make_proposal(42);
|
||||
let hash: H256 = proposal.blake2_256().into();
|
||||
let end = 4;
|
||||
assert_ok!(Collective::propose(Origin::signed(1), 2, Box::new(proposal.clone())));
|
||||
assert_eq!(
|
||||
Collective::voting(&hash),
|
||||
Some(Votes { index: 0, threshold: 2, ayes: vec![1], nays: vec![] })
|
||||
Some(Votes { index: 0, threshold: 2, ayes: vec![1], nays: vec![], end })
|
||||
);
|
||||
assert_noop!(
|
||||
Collective::vote(Origin::signed(1), hash.clone(), 0, true),
|
||||
@@ -641,7 +826,7 @@ mod tests {
|
||||
assert_ok!(Collective::vote(Origin::signed(1), hash.clone(), 0, false));
|
||||
assert_eq!(
|
||||
Collective::voting(&hash),
|
||||
Some(Votes { index: 0, threshold: 2, ayes: vec![], nays: vec![1] })
|
||||
Some(Votes { index: 0, threshold: 2, ayes: vec![], nays: vec![1], end })
|
||||
);
|
||||
assert_noop!(
|
||||
Collective::vote(Origin::signed(1), hash.clone(), 0, false),
|
||||
|
||||
@@ -690,6 +690,7 @@ impl<T: Trait> Module<T> {
|
||||
// split new set into winners and runners up.
|
||||
let split_point = desired_seats.min(new_set_with_stake.len());
|
||||
let mut new_members = (&new_set_with_stake[..split_point]).to_vec();
|
||||
let most_popular = new_members.first().map(|x| x.0.clone());
|
||||
|
||||
// save the runners up as-is. They are sorted based on desirability.
|
||||
// sort and save the members.
|
||||
@@ -722,6 +723,7 @@ impl<T: Trait> Module<T> {
|
||||
&outgoing.clone(),
|
||||
&new_members_ids,
|
||||
);
|
||||
T::ChangeMembers::set_prime(most_popular);
|
||||
|
||||
// outgoing candidates lose their bond.
|
||||
let mut to_burn_bond = outgoing.to_vec();
|
||||
@@ -864,6 +866,7 @@ mod tests {
|
||||
|
||||
thread_local! {
|
||||
pub static MEMBERS: RefCell<Vec<u64>> = RefCell::new(vec![]);
|
||||
pub static PRIME: RefCell<Option<u64>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
pub struct TestChangeMembers;
|
||||
@@ -898,6 +901,11 @@ mod tests {
|
||||
assert_eq!(old_plus_incoming, new_plus_outgoing);
|
||||
|
||||
MEMBERS.with(|m| *m.borrow_mut() = new.to_vec());
|
||||
PRIME.with(|p| *p.borrow_mut() = None);
|
||||
}
|
||||
|
||||
fn set_prime(who: Option<u64>) {
|
||||
PRIME.with(|p| *p.borrow_mut() = who);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1250,6 +1258,30 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prime_works() {
|
||||
ExtBuilder::default().build().execute_with(|| {
|
||||
assert_ok!(Elections::submit_candidacy(Origin::signed(3)));
|
||||
assert_ok!(Elections::submit_candidacy(Origin::signed(4)));
|
||||
assert_ok!(Elections::submit_candidacy(Origin::signed(5)));
|
||||
|
||||
assert_ok!(Elections::vote(Origin::signed(1), vec![4, 3], 10));
|
||||
assert_ok!(Elections::vote(Origin::signed(2), vec![4], 20));
|
||||
assert_ok!(Elections::vote(Origin::signed(3), vec![3], 30));
|
||||
assert_ok!(Elections::vote(Origin::signed(4), vec![4], 40));
|
||||
assert_ok!(Elections::vote(Origin::signed(5), vec![5], 50));
|
||||
|
||||
System::set_block_number(5);
|
||||
assert_ok!(Elections::end_block(System::block_number()));
|
||||
|
||||
assert_eq!(Elections::members_ids(), vec![4, 5]);
|
||||
assert_eq!(Elections::candidates(), vec![]);
|
||||
|
||||
assert_ok!(Elections::vote(Origin::signed(3), vec![4, 5], 10));
|
||||
assert_eq!(PRIME.with(|p| *p.borrow()), Some(4));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cannot_vote_for_more_than_candidates() {
|
||||
ExtBuilder::default().build().execute_with(|| {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
//! # Membership Module
|
||||
//!
|
||||
//! Allows control of membership of a set of `AccountId`s, useful for managing membership of of a
|
||||
//! collective.
|
||||
//! collective. A prime member may be set.
|
||||
|
||||
// Ensure we're `no_std` when compiling for Wasm.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
@@ -47,6 +47,9 @@ pub trait Trait<I=DefaultInstance>: frame_system::Trait {
|
||||
/// Required origin for resetting membership.
|
||||
type ResetOrigin: EnsureOrigin<Self::Origin>;
|
||||
|
||||
/// Required origin for setting or resetting the prime member.
|
||||
type PrimeOrigin: EnsureOrigin<Self::Origin>;
|
||||
|
||||
/// The receiver of the signal for when the membership has been initialized. This happens pre-
|
||||
/// genesis and will usually be the same as `MembershipChanged`. If you need to do something
|
||||
/// different on initialization, then you can change this accordingly.
|
||||
@@ -60,6 +63,9 @@ decl_storage! {
|
||||
trait Store for Module<T: Trait<I>, I: Instance=DefaultInstance> as Membership {
|
||||
/// The current membership, stored as an ordered Vec.
|
||||
Members get(fn members): Vec<T::AccountId>;
|
||||
|
||||
/// The current prime member, if one exists.
|
||||
Prime get(fn prime): Option<T::AccountId>;
|
||||
}
|
||||
add_extra_genesis {
|
||||
config(members): Vec<T::AccountId>;
|
||||
@@ -144,6 +150,7 @@ decl_module! {
|
||||
<Members<T, I>>::put(&members);
|
||||
|
||||
T::MembershipChanged::change_members_sorted(&[], &[who], &members[..]);
|
||||
Self::rejig_prime(&members);
|
||||
|
||||
Self::deposit_event(RawEvent::MemberRemoved);
|
||||
}
|
||||
@@ -151,6 +158,8 @@ decl_module! {
|
||||
/// Swap out one member `remove` for another `add`.
|
||||
///
|
||||
/// May only be called from `SwapOrigin` or root.
|
||||
///
|
||||
/// Prime membership is *not* passed from `remove` to `add`, if extant.
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(50_000)]
|
||||
fn swap_member(origin, remove: T::AccountId, add: T::AccountId) {
|
||||
T::SwapOrigin::try_origin(origin)
|
||||
@@ -171,6 +180,7 @@ decl_module! {
|
||||
&[remove],
|
||||
&members[..],
|
||||
);
|
||||
Self::rejig_prime(&members);
|
||||
|
||||
Self::deposit_event(RawEvent::MembersSwapped);
|
||||
}
|
||||
@@ -189,15 +199,19 @@ decl_module! {
|
||||
members.sort();
|
||||
<Members<T, I>>::mutate(|m| {
|
||||
T::MembershipChanged::set_members_sorted(&members[..], m);
|
||||
Self::rejig_prime(&members);
|
||||
*m = members;
|
||||
});
|
||||
|
||||
|
||||
Self::deposit_event(RawEvent::MembersReset);
|
||||
}
|
||||
|
||||
/// Swap out the sending member for some other key `new`.
|
||||
///
|
||||
/// May only be called from `Signed` origin of a current member.
|
||||
///
|
||||
/// Prime membership is passed from the origin account to `new`, if extant.
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(50_000)]
|
||||
fn change_key(origin, new: T::AccountId) {
|
||||
let remove = ensure_signed(origin)?;
|
||||
@@ -211,14 +225,51 @@ decl_module! {
|
||||
<Members<T, I>>::put(&members);
|
||||
|
||||
T::MembershipChanged::change_members_sorted(
|
||||
&[new],
|
||||
&[remove],
|
||||
&[new.clone()],
|
||||
&[remove.clone()],
|
||||
&members[..],
|
||||
);
|
||||
|
||||
if Prime::<T, I>::get() == Some(remove) {
|
||||
Prime::<T, I>::put(&new);
|
||||
T::MembershipChanged::set_prime(Some(new));
|
||||
}
|
||||
}
|
||||
|
||||
Self::deposit_event(RawEvent::KeyChanged);
|
||||
}
|
||||
|
||||
/// Set the prime member. Must be a current member.
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(50_000)]
|
||||
fn set_prime(origin, who: T::AccountId) {
|
||||
T::PrimeOrigin::try_origin(origin)
|
||||
.map(|_| ())
|
||||
.or_else(ensure_root)?;
|
||||
Self::members().binary_search(&who).ok().ok_or(Error::<T, I>::NotMember)?;
|
||||
Prime::<T, I>::put(&who);
|
||||
T::MembershipChanged::set_prime(Some(who));
|
||||
}
|
||||
|
||||
/// Remove the prime member if it exists.
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(50_000)]
|
||||
fn clear_prime(origin) {
|
||||
T::PrimeOrigin::try_origin(origin)
|
||||
.map(|_| ())
|
||||
.or_else(ensure_root)?;
|
||||
Prime::<T, I>::kill();
|
||||
T::MembershipChanged::set_prime(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait<I>, I: Instance> Module<T, I> {
|
||||
fn rejig_prime(members: &[T::AccountId]) {
|
||||
if let Some(prime) = Prime::<T, I>::get() {
|
||||
match members.binary_search(&prime) {
|
||||
Ok(_) => T::MembershipChanged::set_prime(Some(prime)),
|
||||
Err(_) => Prime::<T, I>::kill(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,6 +334,7 @@ mod tests {
|
||||
|
||||
thread_local! {
|
||||
static MEMBERS: RefCell<Vec<u64>> = RefCell::new(vec![]);
|
||||
static PRIME: RefCell<Option<u64>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
pub struct TestChangeMembers;
|
||||
@@ -297,6 +349,10 @@ mod tests {
|
||||
assert_eq!(old_plus_incoming, new_plus_outgoing);
|
||||
|
||||
MEMBERS.with(|m| *m.borrow_mut() = new.to_vec());
|
||||
PRIME.with(|p| *p.borrow_mut() = None);
|
||||
}
|
||||
fn set_prime(who: Option<u64>) {
|
||||
PRIME.with(|p| *p.borrow_mut() = who);
|
||||
}
|
||||
}
|
||||
impl InitializeMembers<u64> for TestChangeMembers {
|
||||
@@ -311,6 +367,7 @@ mod tests {
|
||||
type RemoveOrigin = EnsureSignedBy<Two, u64>;
|
||||
type SwapOrigin = EnsureSignedBy<Three, u64>;
|
||||
type ResetOrigin = EnsureSignedBy<Four, u64>;
|
||||
type PrimeOrigin = EnsureSignedBy<Five, u64>;
|
||||
type MembershipInitialized = TestChangeMembers;
|
||||
type MembershipChanged = TestChangeMembers;
|
||||
}
|
||||
@@ -337,6 +394,21 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prime_member_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_noop!(Membership::set_prime(Origin::signed(4), 20), BadOrigin);
|
||||
assert_noop!(Membership::set_prime(Origin::signed(5), 15), Error::<Test, _>::NotMember);
|
||||
assert_ok!(Membership::set_prime(Origin::signed(5), 20));
|
||||
assert_eq!(Membership::prime(), Some(20));
|
||||
assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime());
|
||||
|
||||
assert_ok!(Membership::clear_prime(Origin::signed(5)));
|
||||
assert_eq!(Membership::prime(), None);
|
||||
assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_member_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
@@ -353,9 +425,12 @@ mod tests {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_noop!(Membership::remove_member(Origin::signed(5), 20), BadOrigin);
|
||||
assert_noop!(Membership::remove_member(Origin::signed(2), 15), Error::<Test, _>::NotMember);
|
||||
assert_ok!(Membership::set_prime(Origin::signed(5), 20));
|
||||
assert_ok!(Membership::remove_member(Origin::signed(2), 20));
|
||||
assert_eq!(Membership::members(), vec![10, 30]);
|
||||
assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members());
|
||||
assert_eq!(Membership::prime(), None);
|
||||
assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -365,11 +440,19 @@ mod tests {
|
||||
assert_noop!(Membership::swap_member(Origin::signed(5), 10, 25), BadOrigin);
|
||||
assert_noop!(Membership::swap_member(Origin::signed(3), 15, 25), Error::<Test, _>::NotMember);
|
||||
assert_noop!(Membership::swap_member(Origin::signed(3), 10, 30), Error::<Test, _>::AlreadyMember);
|
||||
|
||||
assert_ok!(Membership::set_prime(Origin::signed(5), 20));
|
||||
assert_ok!(Membership::swap_member(Origin::signed(3), 20, 20));
|
||||
assert_eq!(Membership::members(), vec![10, 20, 30]);
|
||||
assert_eq!(Membership::prime(), Some(20));
|
||||
assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime());
|
||||
|
||||
assert_ok!(Membership::set_prime(Origin::signed(5), 10));
|
||||
assert_ok!(Membership::swap_member(Origin::signed(3), 10, 25));
|
||||
assert_eq!(Membership::members(), vec![20, 25, 30]);
|
||||
assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members());
|
||||
assert_eq!(Membership::prime(), None);
|
||||
assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -385,11 +468,14 @@ mod tests {
|
||||
#[test]
|
||||
fn change_key_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Membership::set_prime(Origin::signed(5), 10));
|
||||
assert_noop!(Membership::change_key(Origin::signed(3), 25), Error::<Test, _>::NotMember);
|
||||
assert_noop!(Membership::change_key(Origin::signed(10), 20), Error::<Test, _>::AlreadyMember);
|
||||
assert_ok!(Membership::change_key(Origin::signed(10), 40));
|
||||
assert_eq!(Membership::members(), vec![20, 30, 40]);
|
||||
assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members());
|
||||
assert_eq!(Membership::prime(), Some(40));
|
||||
assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -405,10 +491,20 @@ mod tests {
|
||||
#[test]
|
||||
fn reset_members_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Membership::set_prime(Origin::signed(5), 20));
|
||||
assert_noop!(Membership::reset_members(Origin::signed(1), vec![20, 40, 30]), BadOrigin);
|
||||
|
||||
assert_ok!(Membership::reset_members(Origin::signed(4), vec![20, 40, 30]));
|
||||
assert_eq!(Membership::members(), vec![20, 30, 40]);
|
||||
assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members());
|
||||
assert_eq!(Membership::prime(), Some(20));
|
||||
assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime());
|
||||
|
||||
assert_ok!(Membership::reset_members(Origin::signed(4), vec![10, 40, 30]));
|
||||
assert_eq!(Membership::members(), vec![10, 30, 40]);
|
||||
assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members());
|
||||
assert_eq!(Membership::prime(), None);
|
||||
assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,14 @@ impl<T: FullCodec, G: StorageValue<T>> storage::StorageValue<T> for G {
|
||||
unhashed::put(&Self::storage_value_final_key(), &val)
|
||||
}
|
||||
|
||||
fn set(maybe_val: Self::Query) {
|
||||
if let Some(val) = G::from_query_to_optional_value(maybe_val) {
|
||||
unhashed::put(&Self::storage_value_final_key(), &val)
|
||||
} else {
|
||||
unhashed::kill(&Self::storage_value_final_key())
|
||||
}
|
||||
}
|
||||
|
||||
fn kill() {
|
||||
unhashed::kill(&Self::storage_value_final_key())
|
||||
}
|
||||
|
||||
@@ -73,6 +73,10 @@ pub trait StorageValue<T: FullCodec> {
|
||||
/// Store a value under this key into the provided storage instance.
|
||||
fn put<Arg: EncodeLike<T>>(val: Arg);
|
||||
|
||||
/// Store a value under this key into the provided storage instance; this uses the query
|
||||
/// type rather than the underlying value.
|
||||
fn set(val: Self::Query);
|
||||
|
||||
/// Mutate the value
|
||||
fn mutate<R, F: FnOnce(&mut Self::Query) -> R>(f: F) -> R;
|
||||
|
||||
|
||||
@@ -808,6 +808,8 @@ impl WithdrawReasons {
|
||||
pub trait ChangeMembers<AccountId: Clone + Ord> {
|
||||
/// A number of members `incoming` just joined the set and replaced some `outgoing` ones. The
|
||||
/// new set is given by `new`, and need not be sorted.
|
||||
///
|
||||
/// This resets any previous value of prime.
|
||||
fn change_members(incoming: &[AccountId], outgoing: &[AccountId], mut new: Vec<AccountId>) {
|
||||
new.sort_unstable();
|
||||
Self::change_members_sorted(incoming, outgoing, &new[..]);
|
||||
@@ -817,6 +819,8 @@ pub trait ChangeMembers<AccountId: Clone + Ord> {
|
||||
/// new set is thus given by `sorted_new` and **must be sorted**.
|
||||
///
|
||||
/// NOTE: This is the only function that needs to be implemented in `ChangeMembers`.
|
||||
///
|
||||
/// This resets any previous value of prime.
|
||||
fn change_members_sorted(
|
||||
incoming: &[AccountId],
|
||||
outgoing: &[AccountId],
|
||||
@@ -825,6 +829,8 @@ pub trait ChangeMembers<AccountId: Clone + Ord> {
|
||||
|
||||
/// Set the new members; they **must already be sorted**. This will compute the diff and use it to
|
||||
/// call `change_members_sorted`.
|
||||
///
|
||||
/// This resets any previous value of prime.
|
||||
fn set_members_sorted(new_members: &[AccountId], old_members: &[AccountId]) {
|
||||
let (incoming, outgoing) = Self::compute_members_diff(new_members, old_members);
|
||||
Self::change_members_sorted(&incoming[..], &outgoing[..], &new_members);
|
||||
@@ -865,14 +871,20 @@ pub trait ChangeMembers<AccountId: Clone + Ord> {
|
||||
}
|
||||
(incoming, outgoing)
|
||||
}
|
||||
|
||||
/// Set the prime member.
|
||||
fn set_prime(_prime: Option<AccountId>) {}
|
||||
}
|
||||
|
||||
impl<T: Clone + Ord> ChangeMembers<T> for () {
|
||||
fn change_members(_: &[T], _: &[T], _: Vec<T>) {}
|
||||
fn change_members_sorted(_: &[T], _: &[T], _: &[T]) {}
|
||||
fn set_members_sorted(_: &[T], _: &[T]) {}
|
||||
fn set_prime(_: Option<T>) {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Trait for type that can handle the initialization of account IDs at genesis.
|
||||
pub trait InitializeMembers<AccountId> {
|
||||
/// Initialize the members to the given `members`.
|
||||
|
||||
Reference in New Issue
Block a user