council vetoing & tests.

This commit is contained in:
Gav
2018-03-05 15:50:40 +01:00
parent 7d378d3de3
commit 0ab638928d
@@ -33,7 +33,7 @@ pub const PROPOSALS: &[u8] = b"cov:prs"; // Vec<(expiry: BlockNumber, Proposa
pub const PROPOSAL_OF: &[u8] = b"cov:pro"; // ProposalHash -> Proposal pub const PROPOSAL_OF: &[u8] = b"cov:pro"; // ProposalHash -> Proposal
pub const PROPOSAL_VOTERS: &[u8] = b"cov:voters:"; // ProposalHash -> Vec<AccountId> pub const PROPOSAL_VOTERS: &[u8] = b"cov:voters:"; // ProposalHash -> Vec<AccountId>
pub const COUNCIL_VOTE_OF: &[u8] = b"cov:vote:"; // (ProposalHash, AccountId) -> bool pub const COUNCIL_VOTE_OF: &[u8] = b"cov:vote:"; // (ProposalHash, AccountId) -> bool
pub const VETOED_PROPOSAL: &[u8] = b"cov:veto:"; // ProposalHash -> (BlockNumber, Vec<AccountId>) pub const VETOED_PROPOSAL: &[u8] = b"cov:veto:"; // ProposalHash -> (BlockNumber, sorted_vetoers: Vec<AccountId>)
pub fn cooloff_period() -> BlockNumber { pub fn cooloff_period() -> BlockNumber {
storage::get(COOLOFF_PERIOD).expect("all parameters must be defined") storage::get(COOLOFF_PERIOD).expect("all parameters must be defined")
@@ -47,8 +47,22 @@ pub fn proposals() -> Vec<(BlockNumber, ProposalHash)> {
storage::get_or_default(PROPOSALS) storage::get_or_default(PROPOSALS)
} }
pub fn was_vetoed(proposal: &ProposalHash) -> bool { pub fn is_vetoed(proposal: &ProposalHash) -> bool {
storage::exists(&proposal.to_keyed_vec(VETOED_PROPOSAL)) storage::get(&proposal.to_keyed_vec(VETOED_PROPOSAL))
.map(|(expiry, _): (BlockNumber, Vec<AccountId>)| system::block_number() < expiry)
.unwrap_or(false)
}
pub fn veto_of(proposal: &ProposalHash) -> Option<(BlockNumber, Vec<AccountId>)> {
storage::get(&proposal.to_keyed_vec(VETOED_PROPOSAL))
}
fn set_veto_of(proposal: &ProposalHash, expiry: BlockNumber, vetoers: Vec<AccountId>) {
storage::put(&proposal.to_keyed_vec(VETOED_PROPOSAL), &(expiry, vetoers))
}
fn kill_veto_of(proposal: &ProposalHash) {
storage::kill(&proposal.to_keyed_vec(VETOED_PROPOSAL))
} }
pub fn will_still_be_councillor_at<P: AsRef<AccountId>>(who: P, n: BlockNumber) -> bool { pub fn will_still_be_councillor_at<P: AsRef<AccountId>>(who: P, n: BlockNumber) -> bool {
@@ -58,6 +72,11 @@ pub fn will_still_be_councillor_at<P: AsRef<AccountId>>(who: P, n: BlockNumber)
.unwrap_or(false) .unwrap_or(false)
} }
pub fn is_councillor<P: AsRef<AccountId>>(who: P) -> bool {
council::active_council().iter()
.any(|&(ref a, _)| a == who.as_ref())
}
pub fn vote_of<P: AsRef<AccountId>>(who: P, proposal: &ProposalHash) -> Option<bool> { pub fn vote_of<P: AsRef<AccountId>>(who: P, proposal: &ProposalHash) -> Option<bool> {
storage::get(&(*proposal, *who.as_ref()).to_keyed_vec(COUNCIL_VOTE_OF)) storage::get(&(*proposal, *who.as_ref()).to_keyed_vec(COUNCIL_VOTE_OF))
} }
@@ -108,7 +127,8 @@ pub mod public {
assert!(will_still_be_councillor_at(signed, expiry)); assert!(will_still_be_councillor_at(signed, expiry));
let proposal_hash = proposal.blake2_256(); let proposal_hash = proposal.blake2_256();
assert!(!was_vetoed(&proposal_hash));
assert!(!is_vetoed(&proposal_hash));
let mut proposals = proposals(); let mut proposals = proposals();
proposals.push((expiry, proposal_hash)); proposals.push((expiry, proposal_hash));
@@ -129,12 +149,24 @@ pub mod public {
storage::put(&(*proposal, *(signed.as_ref())).to_keyed_vec(COUNCIL_VOTE_OF), &approve); storage::put(&(*proposal, *(signed.as_ref())).to_keyed_vec(COUNCIL_VOTE_OF), &approve);
} }
pub fn veto<P: AsRef<AccountId> + Copy>(signed: P, proposal: &ProposalHash) { pub fn veto<P: AsRef<AccountId> + Copy>(signed: P, proposal_hash: &ProposalHash) {
assert!(is_councillor(signed), "only councillors may veto council proposals");
assert!(storage::exists(&proposal_hash.to_keyed_vec(PROPOSAL_VOTERS)), "proposal must exist to be vetoed");
let mut existing_vetoers = veto_of(&proposal_hash)
.map(|pair| pair.1)
.unwrap_or_else(Vec::new);
let insert_position = existing_vetoers.binary_search(signed.as_ref())
.expect_err("a councillor may not veto a proposal twice");
existing_vetoers.insert(insert_position, *signed.as_ref());
set_veto_of(&proposal_hash, system::block_number() + cooloff_period(), existing_vetoers);
set_proposals(&proposals().into_iter().filter(|&(_, h)| h != *proposal_hash).collect::<Vec<_>>());
storage::kill(&proposal_hash.to_keyed_vec(PROPOSAL_VOTERS));
storage::kill(&proposal_hash.to_keyed_vec(PROPOSAL_OF));
for (c, _) in council::active_council() {
storage::kill(&(*proposal_hash, c).to_keyed_vec(COUNCIL_VOTE_OF));
} }
pub fn repropose<P: AsRef<AccountId> + Copy>(signed: P, proposal: &Proposal) {
} }
} }
@@ -158,19 +190,19 @@ pub mod internal {
pub fn end_block(now: BlockNumber) { pub fn end_block(now: BlockNumber) {
while let Some((proposal, proposal_hash)) = take_proposal_if_expiring_at(now) { while let Some((proposal, proposal_hash)) = take_proposal_if_expiring_at(now) {
let tally = take_tally(&proposal_hash); let tally = take_tally(&proposal_hash);
if let Proposal::DemocracyCancelReferendum(ref_index) = proposal { if let (&Proposal::DemocracyCancelReferendum(ref_index), (_, 0 ,0)) = (&proposal, tally) {
if tally.0 == tally.2 {
democracy::privileged::clear_referendum(ref_index); democracy::privileged::clear_referendum(ref_index);
}
} else { } else {
if tally.0 > tally.1 + tally.2 {
kill_veto_of(&proposal_hash);
match tally { match tally {
(_, 0, 0) => start_referendum(proposal, VoteThreshold::SuperMajorityAgainst), (_, 0, 0) => start_referendum(proposal, VoteThreshold::SuperMajorityAgainst),
(y, n, x) if y > n + x => start_referendum(proposal, VoteThreshold::SimpleMajority), _ => start_referendum(proposal, VoteThreshold::SimpleMajority),
_ => {},
}; };
} }
} }
} }
}
} }
#[cfg(test)] #[cfg(test)]
@@ -222,14 +254,77 @@ mod tests {
assert_eq!(will_still_be_councillor_at(Alice, 1), true); assert_eq!(will_still_be_councillor_at(Alice, 1), true);
assert_eq!(will_still_be_councillor_at(Alice, 10), false); assert_eq!(will_still_be_councillor_at(Alice, 10), false);
assert_eq!(will_still_be_councillor_at(Dave, 10), false); assert_eq!(will_still_be_councillor_at(Dave, 10), false);
assert_eq!(is_councillor(Alice), true);
assert_eq!(is_councillor(Dave), false);
assert_eq!(proposals(), Vec::<(BlockNumber, ProposalHash)>::new()); assert_eq!(proposals(), Vec::<(BlockNumber, ProposalHash)>::new());
assert_eq!(proposal_voters(&ProposalHash::default()), Vec::<AccountId>::new()); assert_eq!(proposal_voters(&ProposalHash::default()), Vec::<AccountId>::new());
assert_eq!(was_vetoed(&ProposalHash::default()), false); assert_eq!(is_vetoed(&ProposalHash::default()), false);
assert_eq!(vote_of(Alice, &ProposalHash::default()), None); assert_eq!(vote_of(Alice, &ProposalHash::default()), None);
assert_eq!(tally(&ProposalHash::default()), (0, 0, 3)); assert_eq!(tally(&ProposalHash::default()), (0, 0, 3));
}); });
} }
#[test]
fn veto_should_work() {
with_externalities(&mut new_test_ext(), || {
with_env(|e| e.block_number = 1);
let proposal = Proposal::StakingSetBondingDuration(42);
let hash = proposal.blake2_256();
public::propose(Alice, &proposal);
public::veto(Bob, &hash);
assert_eq!(proposals().len(), 0);
assert_eq!(democracy::active_referendums().len(), 0);
});
}
#[test]
#[should_panic]
fn double_veto_should_panic() {
with_externalities(&mut new_test_ext(), || {
with_env(|e| e.block_number = 1);
let proposal = Proposal::StakingSetBondingDuration(42);
let hash = proposal.blake2_256();
public::propose(Alice, &proposal);
public::veto(Bob, &hash);
with_env(|e| e.block_number = 3);
public::propose(Alice, &proposal);
public::veto(Bob, &hash);
});
}
#[test]
#[should_panic]
fn retry_in_cooloff_should_panic() {
with_externalities(&mut new_test_ext(), || {
with_env(|e| e.block_number = 1);
let proposal = Proposal::StakingSetBondingDuration(42);
let hash = proposal.blake2_256();
public::propose(Alice, &proposal);
public::veto(Bob, &hash);
with_env(|e| e.block_number = 2);
public::propose(Alice, &proposal);
});
}
#[test]
fn alternative_double_veto_should_work() {
with_externalities(&mut new_test_ext(), || {
with_env(|e| e.block_number = 1);
let proposal = Proposal::StakingSetBondingDuration(42);
let hash = proposal.blake2_256();
public::propose(Alice, &proposal);
public::veto(Bob, &hash);
with_env(|e| e.block_number = 3);
public::propose(Alice, &proposal);
public::veto(Charlie, &hash);
assert_eq!(proposals().len(), 0);
assert_eq!(democracy::active_referendums().len(), 0);
});
}
#[test] #[test]
fn simple_propose_should_work() { fn simple_propose_should_work() {
with_externalities(&mut new_test_ext(), || { with_externalities(&mut new_test_ext(), || {