diff --git a/substrate/core/sr-primitives/src/testing.rs b/substrate/core/sr-primitives/src/testing.rs index 31be26d3a9..acbcd9241d 100644 --- a/substrate/core/sr-primitives/src/testing.rs +++ b/substrate/core/sr-primitives/src/testing.rs @@ -114,7 +114,6 @@ impl traits::Extrinsic for ExtrinsicWrapper { } } -#[cfg(feature = "std")] impl serde::Serialize for ExtrinsicWrapper { fn serialize(&self, seq: S) -> Result where S: ::serde::Serializer { diff --git a/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm b/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm index 9f80da0d3c..dc2007fe7e 100644 Binary files a/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm and b/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm differ diff --git a/substrate/node/cli/src/chain_spec.rs b/substrate/node/cli/src/chain_spec.rs index 3abf1796bd..92653e057c 100644 --- a/substrate/node/cli/src/chain_spec.rs +++ b/substrate/node/cli/src/chain_spec.rs @@ -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() diff --git a/substrate/node/executor/src/lib.rs b/substrate/node/executor/src/lib.rs index d743303fef..632230133a 100644 --- a/substrate/node/executor/src/lib.rs +++ b/substrate/node/executor/src/lib.rs @@ -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 { diff --git a/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm b/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm index 5d81e65191..364dae5bd0 100644 Binary files a/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm and b/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm differ diff --git a/substrate/srml/council/src/lib.rs b/substrate/srml/council/src/lib.rs index c6d1ec7a25..9d50445891 100644 --- a/substrate/srml/council/src/lib.rs +++ b/substrate/srml/council/src/lib.rs @@ -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:: { candidacy_bond: 9, diff --git a/substrate/srml/council/src/voting.rs b/substrate/srml/council/src/voting.rs index b8dbfbb71b..9536fe5d35 100644 --- a/substrate/srml/council/src/voting.rs +++ b/substrate/srml/council/src/voting.rs @@ -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))]); }); } diff --git a/substrate/srml/democracy/src/lib.rs b/substrate/srml/democracy/src/lib.rs index 7f551a71a3..2bf9e21ae7 100644 --- a/substrate/srml/democracy/src/lib.rs +++ b/substrate/srml/democracy/src/lib.rs @@ -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 + IsSubType>; @@ -94,18 +123,19 @@ decl_module! { >::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, 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, 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!(!>::total_balance(&who).is_zero(), "transactor must have balance to signal approval."); if !>::exists(&(ref_index, who.clone())) { >::mutate(ref_index, |voters| voters.push(who.clone())); } - >::insert(&(ref_index, who), approve_proposal); + >::insert(&(ref_index, who), vote); } /// Start a referendum. @@ -152,6 +182,13 @@ pub struct ReferendumInfo { delay: BlockNumber, } +impl ReferendumInfo { + /// 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 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; - /// Get the vote, if Some, of `who`. - pub VoteOf get(vote_of): map (ReferendumIndex, T::AccountId) => Option; + /// 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 Module { } /// 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)> { 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)> { 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| (>::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| ( + >::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 Module { 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); + >::put(public_props); + + if let Some((deposit, depositors)) = >::take(prop_index) {//: (T::Balance, Vec) = + // refund depositors + for d in &depositors { + >::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) -> Result { + let (approve, against, capital) = Self::tally(index); + let total_issuance = >::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. + >::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 { + >::mutate(now + info.delay, |q| q.push(Some((info.proposal, index)))); + } + } else { + Self::deposit_event(RawEvent::NotPassed(index)); + } + >::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); - >::put(public_props); - - if let Some((deposit, depositors)) = >::take(prop_index) {//: (T::Balance, Vec) = - // refund depositors - for d in &depositors { - >::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 = >::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| >::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 { - >::mutate(now + delay, |q| q.push(Some((proposal, index)))); - } - } else { - Self::deposit_event(RawEvent::NotPassed(index)); - } - >::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 >::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 { + new_test_ext_with_public_delay(0) + } + + fn new_test_ext_with_public_delay(public_delay: u64) -> runtime_io::TestExternalities { let mut t = system::GenesisConfig::::default().build_storage().unwrap().0; t.extend(balances::GenesisConfig::{ 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(())); diff --git a/substrate/srml/democracy/src/vote_threshold.rs b/substrate/srml/democracy/src/vote_threshold.rs index 2882a984d5..ab5e1ddee7 100644 --- a/substrate/srml/democracy/src/vote_threshold.rs +++ b/substrate/srml/democracy/src/vote_threshold.rs @@ -35,7 +35,7 @@ pub trait Approved { /// 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 + Div + Rem + Mul + Div + Rem + Copy> Approved 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); } }