mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-30 22:27:56 +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 }
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
const PREFIX: &[u8] = b"SS58PRE";
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
|
||||
@@ -58,7 +58,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
spec_name: create_runtime_str!("node"),
|
||||
impl_name: create_runtime_str!("substrate-node"),
|
||||
authoring_version: 10,
|
||||
spec_version: 50,
|
||||
spec_version: 51,
|
||||
impl_version: 51,
|
||||
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
|
||||
/// 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 candidates = Self::candidates();
|
||||
Self::do_set_approvals(who, votes, index)
|
||||
}
|
||||
|
||||
ensure!(!Self::presentation_active(), "no approval changes during presentation period");
|
||||
ensure!(index == Self::vote_index(), "incorrect vote index");
|
||||
ensure!(!candidates.len().is_zero(), "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>>::put({
|
||||
let mut v = Self::voters();
|
||||
v.push(who.clone());
|
||||
v
|
||||
});
|
||||
}
|
||||
<LastActiveOf<T>>::insert(&who, index);
|
||||
<ApprovalsOf<T>>::insert(&who, votes);
|
||||
/// Set candidate approvals from a proxy. Approval slots stay valid as long as candidates in those slots
|
||||
/// are registered.
|
||||
fn proxy_set_approvals(origin, votes: Vec<bool>, #[compact] index: VoteIndex) -> Result {
|
||||
let who = <democracy::Module<T>>::proxy(ensure_signed(origin)?).ok_or("not a proxy")?;
|
||||
Self::do_set_approvals(who, votes, index)
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
// 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.
|
||||
fn start_tally() {
|
||||
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]
|
||||
fn setting_any_approval_vote_count_without_any_candidate_count_should_not_work() {
|
||||
with_externalities(&mut new_test_ext(false), || {
|
||||
|
||||
@@ -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