Lock-voting (#1254)

* Optionally long lock voting

* Lock voting.

* Fix tests

* Comment update

* Comments

* Minor grumbles

* Docs.
This commit is contained in:
Gav Wood
2018-12-13 18:58:50 +01:00
committed by Robert Habermeier
parent 025bcdda0f
commit 27f69def9a
9 changed files with 267 additions and 100 deletions
@@ -114,7 +114,6 @@ impl<Xt> traits::Extrinsic for ExtrinsicWrapper<Xt> {
}
}
#[cfg(feature = "std")]
impl<Xt: Encode> serde::Serialize for ExtrinsicWrapper<Xt>
{
fn serialize<S>(&self, seq: S) -> Result<S::Ok, S::Error> where S: ::serde::Serializer {
+2
View File
@@ -92,6 +92,7 @@ fn staging_testnet_config_genesis() -> GenesisConfig {
voting_period: 5 * MINUTES, // 3 days to discuss & vote on an active referendum
minimum_deposit: 50 * DOLLARS, // 12000 as the minimum deposit for a referendum
public_delay: 0,
max_lock_periods: 6,
}),
council_seats: Some(CouncilSeatsConfig {
active_council: vec![],
@@ -213,6 +214,7 @@ pub fn testnet_genesis(
voting_period: 18,
minimum_deposit: 10,
public_delay: 0,
max_lock_periods: 6,
}),
council_seats: Some(CouncilSeatsConfig {
active_council: endowed_accounts.iter()
+5 -5
View File
@@ -311,9 +311,9 @@ mod tests {
1,
GENESIS_HASH.into(),
if support_changes_trie {
hex!("dd0ab9b04b84b2a0704a6c38968d97a41e693345a9019dae880a80cf8176676c").into()
hex!("1a7758d96d7353732f3054a3dacb18f04f42fc48f6706378d6f7be744c6022f1").into()
} else {
hex!("f5d85f3baebdd85aff8acfdeab0c6e00322d8b64b10a277a0231f4ca10dca7ee").into()
hex!("1cf270c8a484df4931af562f7afdc9f44d99ae1bd35fe30fbd2cf3c1be2e933b").into()
},
if support_changes_trie {
vec![changes_trie_log(
@@ -339,7 +339,7 @@ mod tests {
construct_block(
2,
block1(false).1,
hex!("807f9952d1962fcc62e8ee58fb4a3b5dc0247f314e9920a25354c1a1915e6941").into(),
hex!("a208e27269f8a17e7f7cf9513396d3579066df10a853e030345847ec96593c2e").into(),
vec![ // session changes here, so we add a grandpa change signal log.
Log::from(::grandpa::RawLog::AuthoritiesChangeSignal(0, vec![
(Keyring::One.to_raw_public().into(), 1),
@@ -368,7 +368,7 @@ mod tests {
construct_block(
1,
GENESIS_HASH.into(),
hex!("5cc895b7c1d5fa824a2b6aefdcf34088156b3762509050bc384853106d48c7e4").into(),
hex!("a506a69fefa4dc1be6838b68dc6e5799bd5fec545ef890cadac20edc0254d37a").into(),
vec![],
vec![
CheckedExtrinsic {
@@ -658,7 +658,7 @@ mod tests {
let b = construct_block(
1,
GENESIS_HASH.into(),
hex!("06d7654fe2799df26ef3ae932b04d17c163b5a89e6e5bc4ad514acced17bf6c4").into(),
hex!("3af4e1ba0769122b1e92b138fecf7ce8bb2fe4f2a65fba3b423f87942f1ba8c8").into(),
vec![],
vec![
CheckedExtrinsic {
+1
View File
@@ -128,6 +128,7 @@ mod tests {
voting_period: 3,
minimum_deposit: 1,
public_delay: 0,
max_lock_periods: 6,
}.build_storage().unwrap().0);
t.extend(seats::GenesisConfig::<Test> {
candidacy_bond: 9,
+7 -7
View File
@@ -236,7 +236,7 @@ mod tests {
use ::tests::*;
use ::tests::{Call, Origin};
use srml_support::Hashable;
use democracy::VoteThreshold;
use democracy::{ReferendumInfo, VoteThreshold};
#[test]
fn basic_environment_works() {
@@ -272,7 +272,7 @@ mod tests {
System::set_block_number(1);
let proposal = set_balance_proposal(42);
assert_ok!(Democracy::internal_start_referendum(proposal.clone(), VoteThreshold::SuperMajorityApprove, 0), 0);
assert_eq!(Democracy::active_referendums(), vec![(0, 4, proposal, VoteThreshold::SuperMajorityApprove, 0)]);
assert_eq!(Democracy::active_referendums(), vec![(0, ReferendumInfo::new(4, proposal, VoteThreshold::SuperMajorityApprove, 0))]);
let cancellation = cancel_referendum_proposal(0);
let hash = cancellation.blake2_256().into();
@@ -305,7 +305,7 @@ mod tests {
System::set_block_number(2);
assert_ok!(CouncilVoting::end_block(System::block_number()));
assert_eq!(Democracy::active_referendums(), vec![(0, 4, proposal, VoteThreshold::SuperMajorityApprove, 0)]);
assert_eq!(Democracy::active_referendums(), vec![(0, ReferendumInfo::new(4, proposal, VoteThreshold::SuperMajorityApprove, 0))]);
});
}
@@ -324,7 +324,7 @@ mod tests {
System::set_block_number(2);
assert_ok!(CouncilVoting::end_block(System::block_number()));
assert_eq!(Democracy::active_referendums(), vec![(0, 4, proposal, VoteThreshold::SuperMajorityApprove, 0)]);
assert_eq!(Democracy::active_referendums(), vec![(0, ReferendumInfo::new(4, proposal, VoteThreshold::SuperMajorityApprove, 0))]);
});
}
@@ -388,7 +388,7 @@ mod tests {
System::set_block_number(4);
assert_ok!(CouncilVoting::end_block(System::block_number()));
assert_eq!(CouncilVoting::proposals().len(), 0);
assert_eq!(Democracy::active_referendums(), vec![(0, 7, set_balance_proposal(42), VoteThreshold::SimpleMajority, 0)]);
assert_eq!(Democracy::active_referendums(), vec![(0, ReferendumInfo::new(7, set_balance_proposal(42), VoteThreshold::SimpleMajority, 0))]);
});
}
@@ -453,7 +453,7 @@ mod tests {
System::set_block_number(2);
assert_ok!(CouncilVoting::end_block(System::block_number()));
assert_eq!(CouncilVoting::proposals().len(), 0);
assert_eq!(Democracy::active_referendums(), vec![(0, 5, proposal, VoteThreshold::SuperMajorityAgainst, 0)]);
assert_eq!(Democracy::active_referendums(), vec![(0, ReferendumInfo::new(5, proposal, VoteThreshold::SuperMajorityAgainst, 0))]);
});
}
@@ -471,7 +471,7 @@ mod tests {
System::set_block_number(2);
assert_ok!(CouncilVoting::end_block(System::block_number()));
assert_eq!(CouncilVoting::proposals().len(), 0);
assert_eq!(Democracy::active_referendums(), vec![(0, 5, proposal, VoteThreshold::SimpleMajority, 0)]);
assert_eq!(Democracy::active_referendums(), vec![(0, ReferendumInfo::new(5, proposal, VoteThreshold::SimpleMajority, 0))]);
});
}
+244 -81
View File
@@ -49,6 +49,35 @@ pub use vote_threshold::{Approved, VoteThreshold};
pub type PropIndex = u32;
/// A referendum index.
pub type ReferendumIndex = u32;
/// A number of lock periods.
pub type LockPeriods = i8;
/// A number of lock periods, plus a vote, one way or the other.
#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Default)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct Vote(i8);
impl Vote {
/// Create a new instance.
pub fn new(aye: bool, multiplier: LockPeriods) -> Self {
let m = multiplier.max(1) - 1;
Vote(if aye {
-1 - m
} else {
m
})
}
/// Is this an aye vote?
pub fn is_aye(self) -> bool {
self.0 < 0
}
/// The strength (measured in lock periods).
pub fn multiplier(self) -> LockPeriods {
1 + if self.0 < 0 { -(self.0 + 1) } else { self.0 }
}
}
pub trait Trait: balances::Trait + Sized {
type Proposal: Parameter + Dispatchable<Origin=Self::Origin> + IsSubType<Module<Self>>;
@@ -94,18 +123,19 @@ decl_module! {
<DepositOf<T>>::insert(proposal, deposit);
}
/// Vote in a referendum. If `approve_proposal` is true, the vote is to enact the proposal;
/// false would be a vote to keep the status quo.
fn vote(origin, ref_index: Compact<ReferendumIndex>, approve_proposal: bool) {
/// Vote in a referendum. If `vote.is_aye()`, the vote is to enact the proposal;
/// otherwise it is a vote to keep the status quo.
fn vote(origin, ref_index: Compact<ReferendumIndex>, vote: Vote) {
let who = ensure_signed(origin)?;
let ref_index = ref_index.into();
ensure!(vote.multiplier() <= Self::max_lock_periods(), "vote has too great a strength");
ensure!(Self::is_active_referendum(ref_index), "vote given for invalid referendum.");
ensure!(!<balances::Module<T>>::total_balance(&who).is_zero(),
"transactor must have balance to signal approval.");
if !<VoteOf<T>>::exists(&(ref_index, who.clone())) {
<VotersFor<T>>::mutate(ref_index, |voters| voters.push(who.clone()));
}
<VoteOf<T>>::insert(&(ref_index, who), approve_proposal);
<VoteOf<T>>::insert(&(ref_index, who), vote);
}
/// Start a referendum.
@@ -152,6 +182,13 @@ pub struct ReferendumInfo<BlockNumber: Parameter, Proposal: Parameter> {
delay: BlockNumber,
}
impl<BlockNumber: Parameter, Proposal: Parameter> ReferendumInfo<BlockNumber, Proposal> {
/// Create a new instance.
pub fn new(end: BlockNumber, proposal: Proposal, threshold: VoteThreshold, delay: BlockNumber) -> Self {
ReferendumInfo { end, proposal, threshold, delay }
}
}
decl_storage! {
trait Store for Module<T: Trait> as Democracy {
@@ -167,6 +204,8 @@ decl_storage! {
pub MinimumDeposit get(minimum_deposit) config(): T::Balance;
/// The delay before enactment for all public referenda.
pub PublicDelay get(public_delay) config(): T::BlockNumber;
/// The maximum number of additional lock periods a voter may offer to strengthen their vote. Multiples of `PublicDelay`.
pub MaxLockPeriods get(max_lock_periods) config(): LockPeriods;
/// How often (in blocks) to check for new votes.
pub VotingPeriod get(voting_period) config(): T::BlockNumber = T::BlockNumber::sa(1000);
@@ -186,8 +225,10 @@ decl_storage! {
/// Get the voters for the current proposal.
pub VotersFor get(voters_for): map ReferendumIndex => Vec<T::AccountId>;
/// Get the vote, if Some, of `who`.
pub VoteOf get(vote_of): map (ReferendumIndex, T::AccountId) => Option<bool>;
/// Get the vote in a given referendum of a particular voter. The result is meaningful only if `voters_for` includes the
/// voter when called with the referendum (you'll get the default `Vote` value otherwise). If you don't want to check
/// `voters_for`, then you can also check for simple existence with `VoteOf::exists` first.
pub VoteOf get(vote_of): map (ReferendumIndex, T::AccountId) => Vote;
}
}
@@ -218,30 +259,38 @@ impl<T: Trait> Module<T> {
}
/// Get all referendums currently active.
pub fn active_referendums() -> Vec<(ReferendumIndex, T::BlockNumber, T::Proposal, VoteThreshold, T::BlockNumber)> {
pub fn active_referendums() -> Vec<(ReferendumIndex, ReferendumInfo<T::BlockNumber, T::Proposal>)> {
let next = Self::next_tally();
let last = Self::referendum_count();
(next..last).into_iter()
.filter_map(|i| Self::referendum_info(i).map(|ReferendumInfo{ end, proposal, threshold, delay }| (i, end, proposal, threshold, delay)))
.filter_map(|i| Self::referendum_info(i).map(|info| (i, info)))
.collect()
}
/// Get all referendums ready for tally at block `n`.
pub fn maturing_referendums_at(n: T::BlockNumber) -> Vec<(ReferendumIndex, T::BlockNumber, T::Proposal, VoteThreshold, T::BlockNumber)> {
pub fn maturing_referendums_at(n: T::BlockNumber) -> Vec<(ReferendumIndex, ReferendumInfo<T::BlockNumber, T::Proposal>)> {
let next = Self::next_tally();
let last = Self::referendum_count();
(next..last).into_iter()
.filter_map(|i| Self::referendum_info(i).map(|ReferendumInfo{ end, proposal, threshold, delay }| (i, end, proposal, threshold, delay)))
.take_while(|&(_, block_number, _, _, _)| block_number == n)
.filter_map(|i| Self::referendum_info(i).map(|info| (i, info)))
.take_while(|&(_, ref info)| info.end == n)
.collect()
}
/// Get the voters for the current proposal.
pub fn tally(ref_index: ReferendumIndex) -> (T::Balance, T::Balance) {
pub fn tally(ref_index: ReferendumIndex) -> (T::Balance, T::Balance, T::Balance) {
Self::voters_for(ref_index).iter()
.map(|a| (<balances::Module<T>>::total_balance(a), Self::vote_of((ref_index, a.clone())).unwrap_or(false)/*defensive only: all items come from `voters`; for an item to be in `voters` there must be a vote registered; qed*/))
.map(|(bal, vote)| if vote { (bal, Zero::zero()) } else { (Zero::zero(), bal) })
.fold((Zero::zero(), Zero::zero()), |(a, b), (c, d)| (a + c, b + d))
.map(|voter| (
<balances::Module<T>>::total_balance(voter),
Self::vote_of((ref_index, voter.clone())),
))
.map(|(bal, vote)|
if vote.is_aye() {
(bal * T::Balance::sa(vote.multiplier() as u64), Zero::zero(), bal)
} else {
(Zero::zero(), bal * T::Balance::sa(vote.multiplier() as u64), bal)
}
).fold((Zero::zero(), Zero::zero(), Zero::zero()), |(a, b, c), (d, e, f)| (a + d, b + e, c + f))
}
// Exposed mutables.
@@ -292,51 +341,75 @@ impl<T: Trait> Module<T> {
Self::deposit_event(RawEvent::Executed(index, ok));
}
fn launch_next(now: T::BlockNumber) -> Result {
let mut public_props = Self::public_props();
if let Some((winner_index, _)) = public_props.iter()
.enumerate()
.max_by_key(|x| Self::locked_for((x.1).0).unwrap_or_else(Zero::zero)/*defensive only: All current public proposals have an amount locked*/)
{
let (prop_index, proposal, _) = public_props.swap_remove(winner_index);
<PublicProps<T>>::put(public_props);
if let Some((deposit, depositors)) = <DepositOf<T>>::take(prop_index) {//: (T::Balance, Vec<T::AccountId>) =
// refund depositors
for d in &depositors {
<balances::Module<T>>::unreserve(d, deposit);
}
Self::deposit_event(RawEvent::Tabled(prop_index, deposit, depositors));
Self::inject_referendum(now + Self::voting_period(), proposal, VoteThreshold::SuperMajorityApprove, Self::public_delay())?;
}
}
Ok(())
}
fn bake_referendum(now: T::BlockNumber, index: ReferendumIndex, info: ReferendumInfo<T::BlockNumber, T::Proposal>) -> Result {
let (approve, against, capital) = Self::tally(index);
let total_issuance = <balances::Module<T>>::total_issuance();
let approved = info.threshold.approved(approve, against, capital, total_issuance);
let lock_period = Self::public_delay();
// Logic defined in https://www.slideshare.net/gavofyork/governance-in-polkadot-poc3
// Essentially, we extend the lock-period of the coins behind the winning votes to be the
// vote strength times the public delay period from now.
for (a, vote) in Self::voters_for(index).into_iter()
.map(|a| (a.clone(), Self::vote_of((index, a))))
// ^^^ defensive only: all items come from `voters`; for an item to be in `voters` there must be a vote registered; qed
.filter(|&(_, vote)| vote.is_aye() == approved) // Just the winning coins
{
// now plus: the base lock period multiplied by the number of periods this voter offered to
// lock should they win...
let locked_until = now + lock_period * T::BlockNumber::sa((vote.multiplier()) as u64);
// ...extend their bondage until at least then.
<Bondage<T>>::mutate(a, |b| if *b < locked_until { *b = locked_until });
}
Self::clear_referendum(index);
if approved {
Self::deposit_event(RawEvent::Passed(index));
if info.delay.is_zero() {
Self::enact_proposal(info.proposal, index);
} else {
<DispatchQueue<T>>::mutate(now + info.delay, |q| q.push(Some((info.proposal, index))));
}
} else {
Self::deposit_event(RawEvent::NotPassed(index));
}
<NextTally<T>>::put(index + 1);
Ok(())
}
/// Current era is ending; we should finish up any proposals.
fn end_block(now: T::BlockNumber) -> Result {
// pick out another public referendum if it's time.
if (now % Self::launch_period()).is_zero() {
let mut public_props = Self::public_props();
if let Some((winner_index, _)) = public_props.iter()
.enumerate()
.max_by_key(|x| Self::locked_for((x.1).0).unwrap_or_else(Zero::zero)/*defensive only: All current public proposals have an amount locked*/)
{
let (prop_index, proposal, _) = public_props.swap_remove(winner_index);
<PublicProps<T>>::put(public_props);
if let Some((deposit, depositors)) = <DepositOf<T>>::take(prop_index) {//: (T::Balance, Vec<T::AccountId>) =
// refund depositors
for d in &depositors {
<balances::Module<T>>::unreserve(d, deposit);
}
Self::deposit_event(RawEvent::Tabled(prop_index, deposit, depositors));
Self::inject_referendum(now + Self::voting_period(), proposal, VoteThreshold::SuperMajorityApprove, Self::public_delay())?;
}
}
Self::launch_next(now.clone())?;
}
// tally up votes for any expiring referenda.
for (index, _, proposal, threshold, delay) in Self::maturing_referendums_at(now) {
let (approve, against) = Self::tally(index);
let total_issuance = <balances::Module<T>>::total_issuance();
let approved = threshold.approved(approve, against, total_issuance);
Self::voters_for(index).into_iter()
.filter(|a| (Self::vote_of((index, a.clone())).unwrap_or(false)/*defensive only: all items come from `voters`; for an item to be in `voters` there must be a vote registered; qed*/ == approved))
.for_each(|a| <Bondage<T>>::mutate(a, |b| if *b < now + delay { *b = now + delay }));
Self::clear_referendum(index);
if approved {
Self::deposit_event(RawEvent::Passed(index));
if delay.is_zero() {
Self::enact_proposal(proposal, index);
} else {
<DispatchQueue<T>>::mutate(now + delay, |q| q.push(Some((proposal, index))));
}
} else {
Self::deposit_event(RawEvent::NotPassed(index));
}
<NextTally<T>>::put(index + 1);
for (index, info) in Self::maturing_referendums_at(now).into_iter() {
Self::bake_referendum(now.clone(), index, info)?;
}
for (proposal, index) in <DispatchQueue<T>>::take(now).into_iter().filter_map(|x| x) {
@@ -371,6 +444,9 @@ mod tests {
use primitives::traits::{BlakeTwo256};
use primitives::testing::{Digest, DigestItem, Header};
const AYE: Vote = Vote(-1);
const NAY: Vote = Vote(0);
impl_outer_origin! {
pub enum Origin for Test {}
}
@@ -410,6 +486,10 @@ mod tests {
}
fn new_test_ext() -> runtime_io::TestExternalities<Blake2Hasher> {
new_test_ext_with_public_delay(0)
}
fn new_test_ext_with_public_delay(public_delay: u64) -> runtime_io::TestExternalities<Blake2Hasher> {
let mut t = system::GenesisConfig::<Test>::default().build_storage().unwrap().0;
t.extend(balances::GenesisConfig::<Test>{
balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)],
@@ -424,7 +504,8 @@ mod tests {
launch_period: 1,
voting_period: 1,
minimum_deposit: 1,
public_delay: 0,
public_delay,
max_lock_periods: 6,
}.build_storage().unwrap().0);
runtime_io::TestExternalities::new(t)
}
@@ -442,6 +523,34 @@ mod tests {
assert_eq!(Democracy::referendum_count(), 0);
assert_eq!(Balances::free_balance(&42), 0);
assert_eq!(Balances::total_issuance(), 210);
assert_eq!(Democracy::public_delay(), 0);
assert_eq!(Democracy::max_lock_periods(), 6);
});
}
#[test]
fn vote_should_work() {
assert_eq!(Vote::new(true, 0).multiplier(), 1);
assert_eq!(Vote::new(true, 1).multiplier(), 1);
assert_eq!(Vote::new(true, 2).multiplier(), 2);
assert_eq!(Vote::new(true, 0).is_aye(), true);
assert_eq!(Vote::new(true, 1).is_aye(), true);
assert_eq!(Vote::new(true, 2).is_aye(), true);
assert_eq!(Vote::new(false, 0).multiplier(), 1);
assert_eq!(Vote::new(false, 1).multiplier(), 1);
assert_eq!(Vote::new(false, 2).multiplier(), 2);
assert_eq!(Vote::new(false, 0).is_aye(), false);
assert_eq!(Vote::new(false, 1).is_aye(), false);
assert_eq!(Vote::new(false, 2).is_aye(), false);
}
#[test]
fn invalid_vote_strength_should_not_work() {
with_externalities(&mut new_test_ext(), || {
System::set_block_number(1);
let r = Democracy::inject_referendum(1, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0).unwrap();
assert_noop!(Democracy::vote(Origin::signed(1), r.into(), Vote::new(true, 7)), "vote has too great a strength");
assert_noop!(Democracy::vote(Origin::signed(1), r.into(), Vote::new(false, 7)), "vote has too great a strength");
});
}
@@ -475,12 +584,12 @@ mod tests {
System::set_block_number(2);
let r = 0;
assert_ok!(Democracy::vote(Origin::signed(1), r.into(), true));
assert_ok!(Democracy::vote(Origin::signed(1), r.into(), AYE));
assert_eq!(Democracy::referendum_count(), 1);
assert_eq!(Democracy::voters_for(r), vec![1]);
assert_eq!(Democracy::vote_of((r, 1)), Some(true));
assert_eq!(Democracy::tally(r), (10, 0));
assert_eq!(Democracy::vote_of((r, 1)), AYE);
assert_eq!(Democracy::tally(r), (10, 0, 10));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
@@ -554,17 +663,17 @@ mod tests {
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
System::set_block_number(1);
assert_ok!(Democracy::vote(Origin::signed(1), 0.into(), true));
assert_ok!(Democracy::vote(Origin::signed(1), 0.into(), AYE));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
assert_eq!(Balances::free_balance(&42), 4);
System::set_block_number(2);
assert_ok!(Democracy::vote(Origin::signed(1), 1.into(), true));
assert_ok!(Democracy::vote(Origin::signed(1), 1.into(), AYE));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
assert_eq!(Balances::free_balance(&42), 3);
System::set_block_number(3);
assert_ok!(Democracy::vote(Origin::signed(1), 2.into(), true));
assert_ok!(Democracy::vote(Origin::signed(1), 2.into(), AYE));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
});
}
@@ -574,11 +683,11 @@ mod tests {
with_externalities(&mut new_test_ext(), || {
System::set_block_number(1);
let r = Democracy::inject_referendum(1, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0).unwrap();
assert_ok!(Democracy::vote(Origin::signed(1), r.into(), true));
assert_ok!(Democracy::vote(Origin::signed(1), r.into(), AYE));
assert_eq!(Democracy::voters_for(r), vec![1]);
assert_eq!(Democracy::vote_of((r, 1)), Some(true));
assert_eq!(Democracy::tally(r), (10, 0));
assert_eq!(Democracy::vote_of((r, 1)), AYE);
assert_eq!(Democracy::tally(r), (10, 0, 10));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
@@ -591,7 +700,7 @@ mod tests {
with_externalities(&mut new_test_ext(), || {
System::set_block_number(1);
let r = Democracy::inject_referendum(1, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0).unwrap();
assert_ok!(Democracy::vote(Origin::signed(1), r.into(), true));
assert_ok!(Democracy::vote(Origin::signed(1), r.into(), AYE));
assert_ok!(Democracy::cancel_referendum(r.into()));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
@@ -605,11 +714,11 @@ mod tests {
with_externalities(&mut new_test_ext(), || {
System::set_block_number(1);
let r = Democracy::inject_referendum(1, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0).unwrap();
assert_ok!(Democracy::vote(Origin::signed(1), r.into(), false));
assert_ok!(Democracy::vote(Origin::signed(1), r.into(), NAY));
assert_eq!(Democracy::voters_for(r), vec![1]);
assert_eq!(Democracy::vote_of((r, 1)), Some(false));
assert_eq!(Democracy::tally(r), (0, 10));
assert_eq!(Democracy::vote_of((r, 1)), NAY);
assert_eq!(Democracy::tally(r), (0, 10, 10));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
@@ -622,14 +731,14 @@ mod tests {
with_externalities(&mut new_test_ext(), || {
System::set_block_number(1);
let r = Democracy::inject_referendum(1, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0).unwrap();
assert_ok!(Democracy::vote(Origin::signed(1), r.into(), true));
assert_ok!(Democracy::vote(Origin::signed(2), r.into(), false));
assert_ok!(Democracy::vote(Origin::signed(3), r.into(), false));
assert_ok!(Democracy::vote(Origin::signed(4), r.into(), true));
assert_ok!(Democracy::vote(Origin::signed(5), r.into(), false));
assert_ok!(Democracy::vote(Origin::signed(6), r.into(), true));
assert_ok!(Democracy::vote(Origin::signed(1), r.into(), AYE));
assert_ok!(Democracy::vote(Origin::signed(2), r.into(), NAY));
assert_ok!(Democracy::vote(Origin::signed(3), r.into(), NAY));
assert_ok!(Democracy::vote(Origin::signed(4), r.into(), AYE));
assert_ok!(Democracy::vote(Origin::signed(5), r.into(), NAY));
assert_ok!(Democracy::vote(Origin::signed(6), r.into(), AYE));
assert_eq!(Democracy::tally(r), (110, 100));
assert_eq!(Democracy::tally(r), (110, 100, 210));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
@@ -637,15 +746,69 @@ mod tests {
});
}
#[test]
fn delayed_enactment_should_work() {
with_externalities(&mut new_test_ext(), || {
System::set_block_number(1);
let r = Democracy::inject_referendum(1, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 1).unwrap();
assert_ok!(Democracy::vote(Origin::signed(1), r.into(), AYE));
assert_ok!(Democracy::vote(Origin::signed(2), r.into(), AYE));
assert_ok!(Democracy::vote(Origin::signed(3), r.into(), AYE));
assert_ok!(Democracy::vote(Origin::signed(4), r.into(), AYE));
assert_ok!(Democracy::vote(Origin::signed(5), r.into(), AYE));
assert_ok!(Democracy::vote(Origin::signed(6), r.into(), AYE));
assert_eq!(Democracy::tally(r), (210, 0, 210));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
assert_eq!(Balances::free_balance(&42), 0);
System::set_block_number(2);
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
assert_eq!(Balances::free_balance(&42), 2);
});
}
#[test]
fn lock_voting_should_work() {
with_externalities(&mut new_test_ext_with_public_delay(1), || {
System::set_block_number(1);
let r = Democracy::inject_referendum(1, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0).unwrap();
assert_ok!(Democracy::vote(Origin::signed(1), r.into(), Vote::new(false, 6)));
assert_ok!(Democracy::vote(Origin::signed(2), r.into(), Vote::new(true, 5)));
assert_ok!(Democracy::vote(Origin::signed(3), r.into(), Vote::new(true, 4)));
assert_ok!(Democracy::vote(Origin::signed(4), r.into(), Vote::new(true, 3)));
assert_ok!(Democracy::vote(Origin::signed(5), r.into(), Vote::new(true, 2)));
assert_ok!(Democracy::vote(Origin::signed(6), r.into(), Vote::new(false, 1)));
assert_eq!(Democracy::tally(r), (440, 120, 210));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
assert_eq!(Democracy::bondage(1), 0);
assert_eq!(Democracy::bondage(2), 6);
assert_eq!(Democracy::bondage(3), 5);
assert_eq!(Democracy::bondage(4), 4);
assert_eq!(Democracy::bondage(5), 3);
assert_eq!(Democracy::bondage(6), 0);
System::set_block_number(2);
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
assert_eq!(Balances::free_balance(&42), 2);
});
}
#[test]
fn controversial_low_turnout_voting_should_work() {
with_externalities(&mut new_test_ext(), || {
System::set_block_number(1);
let r = Democracy::inject_referendum(1, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0).unwrap();
assert_ok!(Democracy::vote(Origin::signed(5), r.into(), false));
assert_ok!(Democracy::vote(Origin::signed(6), r.into(), true));
assert_ok!(Democracy::vote(Origin::signed(5), r.into(), NAY));
assert_ok!(Democracy::vote(Origin::signed(6), r.into(), AYE));
assert_eq!(Democracy::tally(r), (60, 50));
assert_eq!(Democracy::tally(r), (60, 50, 110));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
@@ -661,11 +824,11 @@ mod tests {
System::set_block_number(1);
let r = Democracy::inject_referendum(1, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0).unwrap();
assert_ok!(Democracy::vote(Origin::signed(4), r.into(), true));
assert_ok!(Democracy::vote(Origin::signed(5), r.into(), false));
assert_ok!(Democracy::vote(Origin::signed(6), r.into(), true));
assert_ok!(Democracy::vote(Origin::signed(4), r.into(), AYE));
assert_ok!(Democracy::vote(Origin::signed(5), r.into(), NAY));
assert_ok!(Democracy::vote(Origin::signed(6), r.into(), AYE));
assert_eq!(Democracy::tally(r), (100, 50));
assert_eq!(Democracy::tally(r), (100, 50, 150));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
@@ -35,7 +35,7 @@ pub trait Approved<Balance> {
/// Given `approve` votes for and `against` votes against from a total electorate size of
/// `electorate` (`electorate - (approve + against)` are abstainers), then returns true if the
/// overall outcome is in favour of approval.
fn approved(&self, approve: Balance, against: Balance, electorate: Balance) -> bool;
fn approved(&self, approve: Balance, against: Balance, voters: Balance, electorate: Balance) -> bool;
}
/// Return `true` iff `n1 / d1 < n2 / d2`. `d1` and `d2` may not be zero.
@@ -68,10 +68,12 @@ fn compare_rationals<T: Zero + Mul<T, Output = T> + Div<T, Output = T> + Rem<T,
impl<Balance: IntegerSquareRoot + Zero + Ord + Add<Balance, Output = Balance> + Mul<Balance, Output = Balance> + Div<Balance, Output = Balance> + Rem<Balance, Output = Balance> + Copy> Approved<Balance> for VoteThreshold {
/// Given `approve` votes for and `against` votes against from a total electorate size of
/// `electorate` (`electorate - (approve + against)` are abstainers), then returns true if the
/// `electorate` of whom `voters` voted (`electorate - voters` are abstainers) then returns true if the
/// overall outcome is in favour of approval.
fn approved(&self, approve: Balance, against: Balance, electorate: Balance) -> bool {
let voters = approve + against;
///
/// We assume each *voter* may cast more than one *vote*, hence `voters` is not necessarily equal to
/// `approve + against`.
fn approved(&self, approve: Balance, against: Balance, voters: Balance, electorate: Balance) -> bool {
let sqrt_voters = voters.integer_sqrt();
let sqrt_electorate = electorate.integer_sqrt();
if sqrt_voters.is_zero() { return false; }
@@ -91,7 +93,7 @@ mod tests {
#[test]
fn should_work() {
assert_eq!(VoteThreshold::SuperMajorityApprove.approved(60, 50, 210), false);
assert_eq!(VoteThreshold::SuperMajorityApprove.approved(100, 50, 210), true);
assert_eq!(VoteThreshold::SuperMajorityApprove.approved(60, 50, 110, 210), false);
assert_eq!(VoteThreshold::SuperMajorityApprove.approved(100, 50, 150, 210), true);
}
}