mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 15:51:12 +00:00
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:
@@ -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(), || {
|
||||
|
||||
Reference in New Issue
Block a user