Proxy voting (#2137)

* Proxy voting in democracy

* Proxy voting for council elections

* Bump and build

* Kill proxy when account dead

* Rebuild wasm

* Fix democraxy delegation locking

* Update srml/council/src/seats.rs

Co-Authored-By: gavofyork <github@gavwood.com>

* Update to use mutate
This commit is contained in:
Gav Wood
2019-03-29 16:07:04 +01:00
committed by GitHub
parent 5c9f306eb3
commit 086d55397b
5 changed files with 189 additions and 41 deletions
+120 -17
View File
@@ -24,7 +24,8 @@ use primitives::traits::{Zero, As, Bounded};
use parity_codec::{Encode, Decode};
use srml_support::{StorageValue, StorageMap, Parameter, Dispatchable, IsSubType, EnumerableStorageMap};
use srml_support::{decl_module, decl_storage, decl_event, ensure};
use srml_support::traits::{Currency, ReservableCurrency, LockableCurrency, WithdrawReason, LockIdentifier};
use srml_support::traits::{Currency, ReservableCurrency, LockableCurrency, WithdrawReason, LockIdentifier,
OnFreeBalanceZero};
use srml_support::dispatch::Result;
use system::ensure_signed;
@@ -119,16 +120,16 @@ decl_module! {
/// 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, #[compact] ref_index: ReferendumIndex, vote: Vote) {
fn vote(origin, #[compact] ref_index: ReferendumIndex, vote: Vote) -> Result {
let who = ensure_signed(origin)?;
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!(!T::Currency::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), vote);
Self::do_vote(who, ref_index, vote)
}
/// Vote in a referendum on behalf of a stash. If `vote.is_aye()`, the vote is to enact the proposal;
/// otherwise it is a vote to keep the status quo.
fn proxy_vote(origin, #[compact] ref_index: ReferendumIndex, vote: Vote) -> Result {
let who = Self::proxy(ensure_signed(origin)?).ok_or("not a proxy")?;
Self::do_vote(who, ref_index, vote)
}
/// Start a referendum.
@@ -147,10 +148,9 @@ decl_module! {
}
/// Cancel a proposal queued for enactment.
pub fn cancel_queued(#[compact] when: T::BlockNumber, #[compact] which: u32) -> Result {
pub fn cancel_queued(#[compact] when: T::BlockNumber, #[compact] which: u32) {
let which = which as usize;
<DispatchQueue<T>>::mutate(when, |items| if items.len() > which { items[which] = None });
Ok(())
}
fn on_finalize(n: T::BlockNumber) {
@@ -159,20 +159,46 @@ decl_module! {
}
}
/// Specify a proxy. Called by the stash.
fn set_proxy(origin, proxy: T::AccountId) {
let who = ensure_signed(origin)?;
ensure!(!<Proxy<T>>::exists(&proxy), "already a proxy");
<Proxy<T>>::insert(proxy, who)
}
/// Clear the proxy. Called by the proxy.
fn resign_proxy(origin) {
let who = ensure_signed(origin)?;
<Proxy<T>>::remove(who);
}
/// Clear the proxy. Called by the stash.
fn remove_proxy(origin, proxy: T::AccountId) {
let who = ensure_signed(origin)?;
ensure!(&Self::proxy(&proxy).ok_or("not a proxy")? == &who, "wrong proxy");
<Proxy<T>>::remove(proxy);
}
/// Delegate vote.
pub fn delegate(origin, to: T::AccountId, lock_periods: LockPeriods) -> Result {
pub fn delegate(origin, to: T::AccountId, lock_periods: LockPeriods) {
let who = ensure_signed(origin)?;
<Delegations<T>>::insert(who.clone(), (to.clone(), lock_periods.clone()));
// Currency is locked indefinitely as long as it's delegated.
T::Currency::extend_lock(DEMOCRACY_ID, &who, Bounded::max_value(), T::BlockNumber::max_value(), WithdrawReason::Transfer.into());
Self::deposit_event(RawEvent::Delegated(who, to));
Ok(())
}
/// Undelegate vote.
fn undelegate(origin) -> Result {
fn undelegate(origin) {
let who = ensure_signed(origin)?;
<Delegations<T>>::remove(who.clone());
ensure!(<Delegations<T>>::exists(&who), "not delegated");
let d = <Delegations<T>>::take(&who);
// Indefinite lock is reduced to the maximum voting lock that could be possible.
let lock_period = Self::public_delay();
let now = <system::Module<T>>::block_number();
let locked_until = now + lock_period * T::BlockNumber::sa(d.1 as u64);
T::Currency::set_lock(DEMOCRACY_ID, &who, Bounded::max_value(), locked_until, WithdrawReason::Transfer.into());
Self::deposit_event(RawEvent::Undelegated(who));
Ok(())
}
}
}
@@ -236,6 +262,9 @@ decl_storage! {
/// `voters_for`, then you can also check for simple existence with `VoteOf::exists` first.
pub VoteOf get(vote_of): map (ReferendumIndex, T::AccountId) => Vote;
/// Who is able to vote for whom. Value is the fund-holding account, key is the vote-transaction-sending account.
pub Proxy get(proxy): map T::AccountId => Option<T::AccountId>;
/// Get the account (and lock periods) to which another account is delegating vote.
pub Delegations get(delegations): linked_map T::AccountId => (T::AccountId, LockPeriods);
}
@@ -340,6 +369,11 @@ impl<T: Trait> Module<T> {
// Exposed mutables.
#[cfg(feature = "std")]
pub fn force_proxy(stash: T::AccountId, proxy: T::AccountId) {
<Proxy<T>>::insert(proxy, stash)
}
/// Start a referendum. Can be called directly by the council.
pub fn internal_start_referendum(proposal: T::Proposal, threshold: VoteThreshold, delay: T::BlockNumber) -> result::Result<ReferendumIndex, &'static str> {
<Module<T>>::inject_referendum(<system::Module<T>>::block_number() + <Module<T>>::voting_period(), proposal, threshold, delay)
@@ -353,6 +387,17 @@ impl<T: Trait> Module<T> {
// private.
/// Actually enact a vote, if legit.
fn do_vote(who: T::AccountId, ref_index: ReferendumIndex, vote: Vote) -> Result {
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.");
if !<VoteOf<T>>::exists(&(ref_index, who.clone())) {
<VotersFor<T>>::mutate(ref_index, |voters| voters.push(who.clone()));
}
<VoteOf<T>>::insert(&(ref_index, who), vote);
Ok(())
}
/// Start a referendum
fn inject_referendum(
end: T::BlockNumber,
@@ -464,6 +509,12 @@ impl<T: Trait> Module<T> {
}
}
impl<T: Trait> OnFreeBalanceZero<T::AccountId> for Module<T> {
fn on_free_balance_zero(who: &T::AccountId) {
<Proxy<T>>::remove(who)
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -631,6 +682,58 @@ mod tests {
});
}
#[test]
fn proxy_should_work() {
with_externalities(&mut new_test_ext(), || {
assert_eq!(Democracy::proxy(10), None);
assert_ok!(Democracy::set_proxy(Origin::signed(1), 10));
assert_eq!(Democracy::proxy(10), Some(1));
// Can't set when already set.
assert_noop!(Democracy::set_proxy(Origin::signed(2), 10), "already a proxy");
// But this works because 11 isn't proxying.
assert_ok!(Democracy::set_proxy(Origin::signed(2), 11));
assert_eq!(Democracy::proxy(10), Some(1));
assert_eq!(Democracy::proxy(11), Some(2));
// 2 cannot fire 1's proxy:
assert_noop!(Democracy::remove_proxy(Origin::signed(2), 10), "wrong proxy");
// 1 fires his proxy:
assert_ok!(Democracy::remove_proxy(Origin::signed(1), 10));
assert_eq!(Democracy::proxy(10), None);
assert_eq!(Democracy::proxy(11), Some(2));
// 11 resigns:
assert_ok!(Democracy::resign_proxy(Origin::signed(11)));
assert_eq!(Democracy::proxy(10), None);
assert_eq!(Democracy::proxy(11), None);
});
}
#[test]
fn single_proposal_should_work_with_proxy() {
with_externalities(&mut new_test_ext(), || {
System::set_block_number(1);
assert_ok!(propose_set_balance(1, 2, 1));
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
System::set_block_number(2);
let r = 0;
assert_ok!(Democracy::set_proxy(Origin::signed(1), 10));
assert_ok!(Democracy::proxy_vote(Origin::signed(10), r, AYE));
assert_eq!(Democracy::referendum_count(), 1);
assert_eq!(Democracy::voters_for(r), vec![1]);
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(()));
assert_eq!(Balances::free_balance(&42), 2);
});
}
#[test]
fn single_proposal_should_work_with_delegation() {
with_externalities(&mut new_test_ext(), || {