Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit caab538

Browse files
sorpaasbkchr
authored andcommitted
pallet-collective: allow customized default vote (#6984)
* collective: add DefaultVote trait * Fix test and node compile * Expose the whole prime_vote * Add test for MoreThanMajorityThenPrimeDefaultVote * Docs fix
1 parent a5977f5 commit caab538

File tree

2 files changed

+111
-11
lines changed

2 files changed

+111
-11
lines changed

bin/node/runtime/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
109109
// and set impl_version to 0. If only runtime
110110
// implementation changes and behavior does not, then leave spec_version as
111111
// is and increment impl_version.
112-
spec_version: 258,
112+
spec_version: 259,
113113
impl_version: 0,
114114
apis: RUNTIME_API_VERSIONS,
115115
transaction_version: 1,
@@ -520,6 +520,7 @@ impl pallet_collective::Trait<CouncilCollective> for Runtime {
520520
type MotionDuration = CouncilMotionDuration;
521521
type MaxProposals = CouncilMaxProposals;
522522
type MaxMembers = CouncilMaxMembers;
523+
type DefaultVote = pallet_collective::PrimeDefaultVote;
523524
type WeightInfo = weights::pallet_collective::WeightInfo;
524525
}
525526

@@ -569,6 +570,7 @@ impl pallet_collective::Trait<TechnicalCollective> for Runtime {
569570
type MotionDuration = TechnicalMotionDuration;
570571
type MaxProposals = TechnicalMaxProposals;
571572
type MaxMembers = TechnicalMaxMembers;
573+
type DefaultVote = pallet_collective::PrimeDefaultVote;
572574
type WeightInfo = weights::pallet_collective::WeightInfo;
573575
}
574576

frame/collective/src/lib.rs

Lines changed: 108 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@
2323
//! The pallet assumes that the amount of members stays at or below `MaxMembers` for its weight
2424
//! calculations, but enforces this neither in `set_members` nor in `change_members_sorted`.
2525
//!
26-
//! A "prime" member may be set allowing their vote to act as the default vote in case of any
27-
//! abstentions after the voting period.
26+
//! A "prime" member may be set to help determine the default vote behavior based on chain
27+
//! config. If `PreimDefaultVote` is used, the prime vote acts as the default vote in case of any
28+
//! abstentions after the voting period. If `MoreThanMajorityThenPrimeDefaultVote` is used, then
29+
//! abstentations will first follow the majority of the collective voting, and then the prime
30+
//! member.
2831
//!
2932
//! Voting happens through motions comprising a proposal (i.e. a curried dispatchable) plus a
3033
//! number of approvals required for it to pass and be called. Motions are open for members to
@@ -71,6 +74,52 @@ pub type ProposalIndex = u32;
7174
/// vote exactly once, therefore also the number of votes for any given motion.
7275
pub type MemberCount = u32;
7376

77+
/// Default voting strategy when a member is inactive.
78+
pub trait DefaultVote {
79+
/// Get the default voting strategy, given:
80+
///
81+
/// - Whether the prime member voted Aye.
82+
/// - Raw number of yes votes.
83+
/// - Raw number of no votes.
84+
/// - Total number of member count.
85+
fn default_vote(
86+
prime_vote: Option<bool>,
87+
yes_votes: MemberCount,
88+
no_votes: MemberCount,
89+
len: MemberCount,
90+
) -> bool;
91+
}
92+
93+
/// Set the prime member's vote as the default vote.
94+
pub struct PrimeDefaultVote;
95+
96+
impl DefaultVote for PrimeDefaultVote {
97+
fn default_vote(
98+
prime_vote: Option<bool>,
99+
_yes_votes: MemberCount,
100+
_no_votes: MemberCount,
101+
_len: MemberCount,
102+
) -> bool {
103+
prime_vote.unwrap_or(false)
104+
}
105+
}
106+
107+
/// First see if yes vote are over majority of the whole collective. If so, set the default vote
108+
/// as yes. Otherwise, use the prime meber's vote as the default vote.
109+
pub struct MoreThanMajorityThenPrimeDefaultVote;
110+
111+
impl DefaultVote for MoreThanMajorityThenPrimeDefaultVote {
112+
fn default_vote(
113+
prime_vote: Option<bool>,
114+
yes_votes: MemberCount,
115+
_no_votes: MemberCount,
116+
len: MemberCount,
117+
) -> bool {
118+
let more_than_majority = yes_votes * 2 > len;
119+
more_than_majority || prime_vote.unwrap_or(false)
120+
}
121+
}
122+
74123
pub trait WeightInfo {
75124
fn set_members(m: u32, n: u32, p: u32, ) -> Weight;
76125
fn execute(b: u32, m: u32, ) -> Weight;
@@ -110,6 +159,9 @@ pub trait Trait<I: Instance=DefaultInstance>: frame_system::Trait {
110159
/// + This pallet assumes that dependents keep to the limit without enforcing it.
111160
type MaxMembers: Get<MemberCount>;
112161

162+
/// Default vote strategy of this collective.
163+
type DefaultVote: DefaultVote;
164+
113165
/// Weight information for extrinsics in this pallet.
114166
type WeightInfo: WeightInfo;
115167
}
@@ -157,8 +209,7 @@ decl_storage! {
157209
pub ProposalCount get(fn proposal_count): u32;
158210
/// The current members of the collective. This is stored sorted (just by value).
159211
pub Members get(fn members): Vec<T::AccountId>;
160-
/// The member who provides the default vote for any other members that do not vote before
161-
/// the timeout. If None, then no member has that privilege.
212+
/// The prime member that helps determine the default vote behavior in case of absentations.
162213
pub Prime get(fn prime): Option<T::AccountId>;
163214
}
164215
add_extra_genesis {
@@ -587,8 +638,10 @@ decl_module! {
587638
// Only allow actual closing of the proposal after the voting period has ended.
588639
ensure!(system::Module::<T>::block_number() >= voting.end, Error::<T, I>::TooEarly);
589640

590-
// default to true only if there's a prime and they voted in favour.
591-
let default = Self::prime().map_or(false, |who| voting.ayes.iter().any(|a| a == &who));
641+
let prime_vote = Self::prime().map(|who| voting.ayes.iter().any(|a| a == &who));
642+
643+
// default voting strategy.
644+
let default = T::DefaultVote::default_vote(prime_vote, yes_votes, no_votes, seats);
592645

593646
let abstentions = seats - (yes_votes + no_votes);
594647
match default {
@@ -945,6 +998,17 @@ mod tests {
945998
type MotionDuration = MotionDuration;
946999
type MaxProposals = MaxProposals;
9471000
type MaxMembers = MaxMembers;
1001+
type DefaultVote = PrimeDefaultVote;
1002+
type WeightInfo = ();
1003+
}
1004+
impl Trait<Instance2> for Test {
1005+
type Origin = Origin;
1006+
type Proposal = Call;
1007+
type Event = Event;
1008+
type MotionDuration = MotionDuration;
1009+
type MaxProposals = MaxProposals;
1010+
type MaxMembers = MaxMembers;
1011+
type DefaultVote = MoreThanMajorityThenPrimeDefaultVote;
9481012
type WeightInfo = ();
9491013
}
9501014
impl Trait for Test {
@@ -954,6 +1018,7 @@ mod tests {
9541018
type MotionDuration = MotionDuration;
9551019
type MaxProposals = MaxProposals;
9561020
type MaxMembers = MaxMembers;
1021+
type DefaultVote = PrimeDefaultVote;
9571022
type WeightInfo = ();
9581023
}
9591024

@@ -968,6 +1033,7 @@ mod tests {
9681033
{
9691034
System: system::{Module, Call, Event<T>},
9701035
Collective: collective::<Instance1>::{Module, Call, Event<T>, Origin<T>, Config<T>},
1036+
CollectiveMajority: collective::<Instance2>::{Module, Call, Event<T>, Origin<T>, Config<T>},
9711037
DefaultCollective: collective::{Module, Call, Event<T>, Origin<T>, Config<T>},
9721038
}
9731039
);
@@ -978,12 +1044,20 @@ mod tests {
9781044
members: vec![1, 2, 3],
9791045
phantom: Default::default(),
9801046
}),
1047+
collective_Instance2: Some(collective::GenesisConfig {
1048+
members: vec![1, 2, 3, 4, 5],
1049+
phantom: Default::default(),
1050+
}),
9811051
collective: None,
9821052
}.build_storage().unwrap().into();
9831053
ext.execute_with(|| System::set_block_number(1));
9841054
ext
9851055
}
9861056

1057+
fn make_proposal(value: u64) -> Call {
1058+
Call::System(frame_system::Call::remark(value.encode()))
1059+
}
1060+
9871061
#[test]
9881062
fn motions_basic_environment_works() {
9891063
new_test_ext().execute_with(|| {
@@ -992,10 +1066,6 @@ mod tests {
9921066
});
9931067
}
9941068

995-
fn make_proposal(value: u64) -> Call {
996-
Call::System(frame_system::Call::remark(value.encode()))
997-
}
998-
9991069
#[test]
10001070
fn close_works() {
10011071
new_test_ext().execute_with(|| {
@@ -1114,6 +1184,34 @@ mod tests {
11141184
});
11151185
}
11161186

1187+
#[test]
1188+
fn close_with_no_prime_but_majority_works() {
1189+
new_test_ext().execute_with(|| {
1190+
let proposal = make_proposal(42);
1191+
let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32);
1192+
let proposal_weight = proposal.get_dispatch_info().weight;
1193+
let hash = BlakeTwo256::hash_of(&proposal);
1194+
assert_ok!(CollectiveMajority::set_members(Origin::root(), vec![1, 2, 3, 4, 5], Some(5), MaxMembers::get()));
1195+
1196+
assert_ok!(CollectiveMajority::propose(Origin::signed(1), 5, Box::new(proposal.clone()), proposal_len));
1197+
assert_ok!(CollectiveMajority::vote(Origin::signed(2), hash.clone(), 0, true));
1198+
assert_ok!(CollectiveMajority::vote(Origin::signed(3), hash.clone(), 0, true));
1199+
1200+
System::set_block_number(4);
1201+
assert_ok!(CollectiveMajority::close(Origin::signed(4), hash.clone(), 0, proposal_weight, proposal_len));
1202+
1203+
let record = |event| EventRecord { phase: Phase::Initialization, event, topics: vec![] };
1204+
assert_eq!(System::events(), vec![
1205+
record(Event::collective_Instance2(RawEvent::Proposed(1, 0, hash.clone(), 5))),
1206+
record(Event::collective_Instance2(RawEvent::Voted(2, hash.clone(), true, 2, 0))),
1207+
record(Event::collective_Instance2(RawEvent::Voted(3, hash.clone(), true, 3, 0))),
1208+
record(Event::collective_Instance2(RawEvent::Closed(hash.clone(), 5, 0))),
1209+
record(Event::collective_Instance2(RawEvent::Approved(hash.clone()))),
1210+
record(Event::collective_Instance2(RawEvent::Executed(hash.clone(), Err(DispatchError::BadOrigin))))
1211+
]);
1212+
});
1213+
}
1214+
11171215
#[test]
11181216
fn removal_of_old_voters_votes_works() {
11191217
new_test_ext().execute_with(|| {

0 commit comments

Comments
 (0)