mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-14 02:51:08 +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:
@@ -218,6 +218,7 @@ pub trait Derive: Sized {
|
|||||||
fn derive<Iter: Iterator<Item=DeriveJunction>>(&self, _path: Iter) -> Option<Self> { None }
|
fn derive<Iter: Iterator<Item=DeriveJunction>>(&self, _path: Iter) -> Option<Self> { None }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
const PREFIX: &[u8] = b"SS58PRE";
|
const PREFIX: &[u8] = b"SS58PRE";
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
|
|||||||
spec_name: create_runtime_str!("node"),
|
spec_name: create_runtime_str!("node"),
|
||||||
impl_name: create_runtime_str!("substrate-node"),
|
impl_name: create_runtime_str!("substrate-node"),
|
||||||
authoring_version: 10,
|
authoring_version: 10,
|
||||||
spec_version: 50,
|
spec_version: 51,
|
||||||
impl_version: 51,
|
impl_version: 51,
|
||||||
apis: RUNTIME_API_VERSIONS,
|
apis: RUNTIME_API_VERSIONS,
|
||||||
};
|
};
|
||||||
|
|||||||
BIN
Binary file not shown.
@@ -103,31 +103,16 @@ decl_module! {
|
|||||||
|
|
||||||
/// Set candidate approvals. Approval slots stay valid as long as candidates in those slots
|
/// Set candidate approvals. Approval slots stay valid as long as candidates in those slots
|
||||||
/// are registered.
|
/// are registered.
|
||||||
fn set_approvals(origin, votes: Vec<bool>, #[compact] index: VoteIndex) {
|
fn set_approvals(origin, votes: Vec<bool>, #[compact] index: VoteIndex) -> Result {
|
||||||
let who = ensure_signed(origin)?;
|
let who = ensure_signed(origin)?;
|
||||||
let candidates = Self::candidates();
|
Self::do_set_approvals(who, votes, index)
|
||||||
|
}
|
||||||
|
|
||||||
ensure!(!Self::presentation_active(), "no approval changes during presentation period");
|
/// Set candidate approvals from a proxy. Approval slots stay valid as long as candidates in those slots
|
||||||
ensure!(index == Self::vote_index(), "incorrect vote index");
|
/// are registered.
|
||||||
ensure!(!candidates.len().is_zero(), "amount of candidates to receive approval votes should be non-zero");
|
fn proxy_set_approvals(origin, votes: Vec<bool>, #[compact] index: VoteIndex) -> Result {
|
||||||
// Prevent a vote from voters that provide a list of votes that exceeds the candidates length
|
let who = <democracy::Module<T>>::proxy(ensure_signed(origin)?).ok_or("not a proxy")?;
|
||||||
// since otherwise an attacker may be able to submit a very long list of `votes` that far exceeds
|
Self::do_set_approvals(who, votes, index)
|
||||||
// the amount of candidates and waste more computation than a reasonable voting bond would cover.
|
|
||||||
ensure!(candidates.len() >= votes.len(), "amount of candidate approval votes cannot exceed amount of candidates");
|
|
||||||
|
|
||||||
if !<LastActiveOf<T>>::exists(&who) {
|
|
||||||
// not yet a voter - deduct bond.
|
|
||||||
// NOTE: this must be the last potential bailer, since it changes state.
|
|
||||||
T::Currency::reserve(&who, Self::voting_bond())?;
|
|
||||||
|
|
||||||
<Voters<T>>::put({
|
|
||||||
let mut v = Self::voters();
|
|
||||||
v.push(who.clone());
|
|
||||||
v
|
|
||||||
});
|
|
||||||
}
|
|
||||||
<LastActiveOf<T>>::insert(&who, index);
|
|
||||||
<ApprovalsOf<T>>::insert(&who, votes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a voter. For it not to be a bond-consuming no-op, all approved candidate indices
|
/// Remove a voter. For it not to be a bond-consuming no-op, all approved candidate indices
|
||||||
@@ -468,6 +453,31 @@ impl<T: Trait> Module<T> {
|
|||||||
<LastActiveOf<T>>::remove(voter);
|
<LastActiveOf<T>>::remove(voter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Actually do the voting.
|
||||||
|
fn do_set_approvals(who: T::AccountId, votes: Vec<bool>, index: VoteIndex) -> Result {
|
||||||
|
let candidates = Self::candidates();
|
||||||
|
|
||||||
|
ensure!(!Self::presentation_active(), "no approval changes during presentation period");
|
||||||
|
ensure!(index == Self::vote_index(), "incorrect vote index");
|
||||||
|
ensure!(!candidates.is_empty(), "amount of candidates to receive approval votes should be non-zero");
|
||||||
|
// Prevent a vote from voters that provide a list of votes that exceeds the candidates length
|
||||||
|
// since otherwise an attacker may be able to submit a very long list of `votes` that far exceeds
|
||||||
|
// the amount of candidates and waste more computation than a reasonable voting bond would cover.
|
||||||
|
ensure!(candidates.len() >= votes.len(), "amount of candidate approval votes cannot exceed amount of candidates");
|
||||||
|
|
||||||
|
if !<LastActiveOf<T>>::exists(&who) {
|
||||||
|
// not yet a voter - deduct bond.
|
||||||
|
// NOTE: this must be the last potential bailer, since it changes state.
|
||||||
|
T::Currency::reserve(&who, Self::voting_bond())?;
|
||||||
|
|
||||||
|
<Voters<T>>::mutate(|mut v| v.push(who.clone()));
|
||||||
|
}
|
||||||
|
<LastActiveOf<T>>::insert(&who, index);
|
||||||
|
<ApprovalsOf<T>>::insert(&who, votes);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Close the voting, snapshot the staking and the number of seats that are actually up for grabs.
|
/// Close the voting, snapshot the staking and the number of seats that are actually up for grabs.
|
||||||
fn start_tally() {
|
fn start_tally() {
|
||||||
let active_council = Self::active_council();
|
let active_council = Self::active_council();
|
||||||
@@ -741,6 +751,40 @@ mod tests {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn proxy_voting_should_work() {
|
||||||
|
with_externalities(&mut new_test_ext(false), || {
|
||||||
|
System::set_block_number(1);
|
||||||
|
|
||||||
|
assert_ok!(Council::submit_candidacy(Origin::signed(5), 0));
|
||||||
|
|
||||||
|
Democracy::force_proxy(1, 11);
|
||||||
|
Democracy::force_proxy(2, 12);
|
||||||
|
Democracy::force_proxy(3, 13);
|
||||||
|
Democracy::force_proxy(4, 14);
|
||||||
|
|
||||||
|
assert_ok!(Council::proxy_set_approvals(Origin::signed(11), vec![true], 0));
|
||||||
|
assert_ok!(Council::proxy_set_approvals(Origin::signed(14), vec![true], 0));
|
||||||
|
|
||||||
|
assert_eq!(Council::approvals_of(1), vec![true]);
|
||||||
|
assert_eq!(Council::approvals_of(4), vec![true]);
|
||||||
|
assert_eq!(Council::voters(), vec![1, 4]);
|
||||||
|
|
||||||
|
assert_ok!(Council::submit_candidacy(Origin::signed(2), 1));
|
||||||
|
assert_ok!(Council::submit_candidacy(Origin::signed(3), 2));
|
||||||
|
|
||||||
|
assert_ok!(Council::proxy_set_approvals(Origin::signed(12), vec![false, true, true], 0));
|
||||||
|
assert_ok!(Council::proxy_set_approvals(Origin::signed(13), vec![false, true, true], 0));
|
||||||
|
|
||||||
|
assert_eq!(Council::approvals_of(1), vec![true]);
|
||||||
|
assert_eq!(Council::approvals_of(4), vec![true]);
|
||||||
|
assert_eq!(Council::approvals_of(2), vec![false, true, true]);
|
||||||
|
assert_eq!(Council::approvals_of(3), vec![false, true, true]);
|
||||||
|
|
||||||
|
assert_eq!(Council::voters(), vec![1, 4, 2, 3]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn setting_any_approval_vote_count_without_any_candidate_count_should_not_work() {
|
fn setting_any_approval_vote_count_without_any_candidate_count_should_not_work() {
|
||||||
with_externalities(&mut new_test_ext(false), || {
|
with_externalities(&mut new_test_ext(false), || {
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ use primitives::traits::{Zero, As, Bounded};
|
|||||||
use parity_codec::{Encode, Decode};
|
use parity_codec::{Encode, Decode};
|
||||||
use srml_support::{StorageValue, StorageMap, Parameter, Dispatchable, IsSubType, EnumerableStorageMap};
|
use srml_support::{StorageValue, StorageMap, Parameter, Dispatchable, IsSubType, EnumerableStorageMap};
|
||||||
use srml_support::{decl_module, decl_storage, decl_event, ensure};
|
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 srml_support::dispatch::Result;
|
||||||
use system::ensure_signed;
|
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;
|
/// 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.
|
/// 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)?;
|
let who = ensure_signed(origin)?;
|
||||||
ensure!(vote.multiplier() <= Self::max_lock_periods(), "vote has too great a strength");
|
Self::do_vote(who, ref_index, vote)
|
||||||
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.");
|
/// Vote in a referendum on behalf of a stash. If `vote.is_aye()`, the vote is to enact the proposal;
|
||||||
if !<VoteOf<T>>::exists(&(ref_index, who.clone())) {
|
/// otherwise it is a vote to keep the status quo.
|
||||||
<VotersFor<T>>::mutate(ref_index, |voters| voters.push(who.clone()));
|
fn proxy_vote(origin, #[compact] ref_index: ReferendumIndex, vote: Vote) -> Result {
|
||||||
}
|
let who = Self::proxy(ensure_signed(origin)?).ok_or("not a proxy")?;
|
||||||
<VoteOf<T>>::insert(&(ref_index, who), vote);
|
Self::do_vote(who, ref_index, vote)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start a referendum.
|
/// Start a referendum.
|
||||||
@@ -147,10 +148,9 @@ decl_module! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Cancel a proposal queued for enactment.
|
/// 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;
|
let which = which as usize;
|
||||||
<DispatchQueue<T>>::mutate(when, |items| if items.len() > which { items[which] = None });
|
<DispatchQueue<T>>::mutate(when, |items| if items.len() > which { items[which] = None });
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_finalize(n: T::BlockNumber) {
|
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.
|
/// 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)?;
|
let who = ensure_signed(origin)?;
|
||||||
<Delegations<T>>::insert(who.clone(), (to.clone(), lock_periods.clone()));
|
<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));
|
Self::deposit_event(RawEvent::Delegated(who, to));
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Undelegate vote.
|
/// Undelegate vote.
|
||||||
fn undelegate(origin) -> Result {
|
fn undelegate(origin) {
|
||||||
let who = ensure_signed(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));
|
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.
|
/// `voters_for`, then you can also check for simple existence with `VoteOf::exists` first.
|
||||||
pub VoteOf get(vote_of): map (ReferendumIndex, T::AccountId) => Vote;
|
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.
|
/// Get the account (and lock periods) to which another account is delegating vote.
|
||||||
pub Delegations get(delegations): linked_map T::AccountId => (T::AccountId, LockPeriods);
|
pub Delegations get(delegations): linked_map T::AccountId => (T::AccountId, LockPeriods);
|
||||||
}
|
}
|
||||||
@@ -340,6 +369,11 @@ impl<T: Trait> Module<T> {
|
|||||||
|
|
||||||
// Exposed mutables.
|
// 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.
|
/// 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> {
|
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)
|
<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.
|
// 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
|
/// Start a referendum
|
||||||
fn inject_referendum(
|
fn inject_referendum(
|
||||||
end: T::BlockNumber,
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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]
|
#[test]
|
||||||
fn single_proposal_should_work_with_delegation() {
|
fn single_proposal_should_work_with_delegation() {
|
||||||
with_externalities(&mut new_test_ext(), || {
|
with_externalities(&mut new_test_ext(), || {
|
||||||
|
|||||||
Reference in New Issue
Block a user