mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-08 15:58:02 +00:00
Delegation voting (#1827)
* feat: add on-chain delegation voting * fix: add recursion limit to delegated_votes * fix: tests * fix: count votes before limit exceeded
This commit is contained in:
BIN
Binary file not shown.
@@ -58,8 +58,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
spec_name: create_runtime_str!("node"),
|
||||
impl_name: create_runtime_str!("substrate-node"),
|
||||
authoring_version: 10,
|
||||
spec_version: 36,
|
||||
impl_version: 39,
|
||||
spec_version: 37,
|
||||
impl_version: 40,
|
||||
apis: RUNTIME_API_VERSIONS,
|
||||
};
|
||||
|
||||
|
||||
BIN
Binary file not shown.
@@ -22,7 +22,7 @@ use rstd::prelude::*;
|
||||
use rstd::result;
|
||||
use primitives::traits::{Zero, As, Bounded};
|
||||
use parity_codec::{Encode, Decode};
|
||||
use srml_support::{StorageValue, StorageMap, Parameter, Dispatchable, IsSubType};
|
||||
use srml_support::{StorageValue, StorageMap, Parameter, Dispatchable, IsSubType, EnumerableStorageMap};
|
||||
use srml_support::{decl_module, decl_storage, decl_event, ensure};
|
||||
use srml_support::traits::{Currency, LockableCurrency, WithdrawReason, ArithmeticType, LockIdentifier};
|
||||
use srml_support::dispatch::Result;
|
||||
@@ -40,6 +40,8 @@ pub type ReferendumIndex = u32;
|
||||
/// A number of lock periods.
|
||||
pub type LockPeriods = i8;
|
||||
|
||||
const MAX_RECURSION_LIMIT: u32 = 16;
|
||||
|
||||
/// A number of lock periods, plus a vote, one way or the other.
|
||||
#[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Default)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
@@ -156,6 +158,22 @@ decl_module! {
|
||||
runtime_io::print(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Delegate vote.
|
||||
pub fn delegate(origin, to: T::AccountId, lock_periods: LockPeriods) -> Result {
|
||||
let who = ensure_signed(origin)?;
|
||||
<Delegations<T>>::insert(who.clone(), (to.clone(), lock_periods.clone()));
|
||||
Self::deposit_event(RawEvent::Delegated(who, to));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Undelegate vote.
|
||||
fn undelegate(origin) -> Result {
|
||||
let who = ensure_signed(origin)?;
|
||||
<Delegations<T>>::remove(who.clone());
|
||||
Self::deposit_event(RawEvent::Undelegated(who));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,6 +235,9 @@ decl_storage! {
|
||||
/// voter when called with the referendum (you'll get the default `Vote` value otherwise). If you don't want to check
|
||||
/// `voters_for`, then you can also check for simple existence with `VoteOf::exists` first.
|
||||
pub VoteOf get(vote_of): map (ReferendumIndex, T::AccountId) => 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,6 +251,8 @@ decl_event!(
|
||||
NotPassed(ReferendumIndex),
|
||||
Cancelled(ReferendumIndex),
|
||||
Executed(ReferendumIndex, bool),
|
||||
Delegated(AccountId, AccountId),
|
||||
Undelegated(AccountId),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -268,10 +291,9 @@ impl<T: Trait> Module<T> {
|
||||
|
||||
/// Get the voters for the current proposal.
|
||||
pub fn tally(ref_index: ReferendumIndex) -> (BalanceOf<T>, BalanceOf<T>, BalanceOf<T>) {
|
||||
Self::voters_for(ref_index).iter()
|
||||
let (approve, against, capital): (BalanceOf<T>, BalanceOf<T>, BalanceOf<T>) = Self::voters_for(ref_index).iter()
|
||||
.map(|voter| (
|
||||
T::Currency::total_balance(voter),
|
||||
Self::vote_of((ref_index, voter.clone())),
|
||||
T::Currency::total_balance(voter), Self::vote_of((ref_index, voter.clone()))
|
||||
))
|
||||
.map(|(bal, vote)|
|
||||
if vote.is_aye() {
|
||||
@@ -279,7 +301,42 @@ impl<T: Trait> Module<T> {
|
||||
} else {
|
||||
(Zero::zero(), bal * BalanceOf::<T>::sa(vote.multiplier() as u64), bal)
|
||||
}
|
||||
).fold((Zero::zero(), Zero::zero(), Zero::zero()), |(a, b, c), (d, e, f)| (a + d, b + e, c + f))
|
||||
).fold((Zero::zero(), Zero::zero(), Zero::zero()), |(a, b, c), (d, e, f)| (a + d, b + e, c + f));
|
||||
let (del_approve, del_against, del_capital) = Self::tally_delegation(ref_index);
|
||||
(approve + del_approve, against + del_against, capital + del_capital)
|
||||
}
|
||||
|
||||
/// Get the delegated voters for the current proposal.
|
||||
/// I think this goes into a worker once https://github.com/paritytech/substrate/issues/1458 is done.
|
||||
fn tally_delegation(ref_index: ReferendumIndex) -> (BalanceOf<T>, BalanceOf<T>, BalanceOf<T>) {
|
||||
Self::voters_for(ref_index).iter()
|
||||
.fold((Zero::zero(), Zero::zero(), Zero::zero()), |(approve_acc, against_acc, capital_acc), voter| {
|
||||
let vote = Self::vote_of((ref_index, voter.clone()));
|
||||
let (votes, balance) = Self::delegated_votes(ref_index, voter.clone(), vote.multiplier(), MAX_RECURSION_LIMIT);
|
||||
if vote.is_aye() {
|
||||
(approve_acc + votes, against_acc, capital_acc + balance)
|
||||
} else {
|
||||
(approve_acc, against_acc + votes, capital_acc + balance)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn delegated_votes(
|
||||
ref_index: ReferendumIndex,
|
||||
to: T::AccountId,
|
||||
min_lock_periods: LockPeriods,
|
||||
recursion_limit: u32,
|
||||
) -> (BalanceOf<T>, BalanceOf<T>) {
|
||||
if recursion_limit == 0 { return (Zero::zero(), Zero::zero()); }
|
||||
<Delegations<T>>::enumerate()
|
||||
.filter(|(delegator, (delegate, _))| *delegate == to && !<VoteOf<T>>::exists(&(ref_index, delegator.clone())))
|
||||
.fold((Zero::zero(), Zero::zero()), |(votes_acc, balance_acc), (delegator, (_delegate, periods))| {
|
||||
let lock_periods = if min_lock_periods <= periods { min_lock_periods } else { periods };
|
||||
let balance = T::Currency::total_balance(&delegator);
|
||||
let votes = T::Currency::total_balance(&delegator) * BalanceOf::<T>::sa(lock_periods as u64);
|
||||
let (del_votes, del_balance) = Self::delegated_votes(ref_index, delegator, lock_periods, recursion_limit - 1);
|
||||
(votes_acc + votes + del_votes, balance_acc + balance + del_balance)
|
||||
})
|
||||
}
|
||||
|
||||
// Exposed mutables.
|
||||
@@ -564,6 +621,158 @@ mod tests {
|
||||
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(), || {
|
||||
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;
|
||||
|
||||
// Delegate vote.
|
||||
assert_ok!(Democracy::delegate(Origin::signed(2), 1, 100));
|
||||
|
||||
assert_ok!(Democracy::vote(Origin::signed(1), r, AYE));
|
||||
|
||||
assert_eq!(Democracy::referendum_count(), 1);
|
||||
assert_eq!(Democracy::voters_for(r), vec![1]);
|
||||
assert_eq!(Democracy::vote_of((r, 1)), AYE);
|
||||
|
||||
// Delegated vote is counted.
|
||||
assert_eq!(Democracy::tally(r), (30, 0, 30));
|
||||
|
||||
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
|
||||
|
||||
assert_eq!(Balances::free_balance(&42), 2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_proposal_should_work_with_cyclic_delegation() {
|
||||
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;
|
||||
|
||||
// Check behaviour with cycle.
|
||||
assert_ok!(Democracy::delegate(Origin::signed(2), 1, 100));
|
||||
assert_ok!(Democracy::delegate(Origin::signed(3), 2, 100));
|
||||
assert_ok!(Democracy::delegate(Origin::signed(1), 3, 100));
|
||||
|
||||
assert_ok!(Democracy::vote(Origin::signed(1), r, AYE));
|
||||
|
||||
assert_eq!(Democracy::referendum_count(), 1);
|
||||
assert_eq!(Democracy::voters_for(r), vec![1]);
|
||||
|
||||
// Delegated vote is counted.
|
||||
assert_eq!(Democracy::tally(r), (60, 0, 60));
|
||||
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
|
||||
|
||||
assert_eq!(Balances::free_balance(&42), 2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// If transactor already voted, delegated vote is overwriten.
|
||||
fn single_proposal_should_work_with_vote_and_delegation() {
|
||||
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::vote(Origin::signed(1), r, AYE));
|
||||
|
||||
// Vote.
|
||||
assert_ok!(Democracy::vote(Origin::signed(2), r, AYE));
|
||||
|
||||
// Delegate vote.
|
||||
assert_ok!(Democracy::delegate(Origin::signed(2), 1, 100));
|
||||
|
||||
assert_eq!(Democracy::referendum_count(), 1);
|
||||
assert_eq!(Democracy::voters_for(r), vec![1, 2]);
|
||||
assert_eq!(Democracy::vote_of((r, 1)), AYE);
|
||||
|
||||
// Delegated vote is not counted.
|
||||
assert_eq!(Democracy::tally(r), (30, 0, 30));
|
||||
|
||||
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
|
||||
|
||||
assert_eq!(Balances::free_balance(&42), 2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_proposal_should_work_with_undelegation() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
System::set_block_number(1);
|
||||
|
||||
assert_ok!(propose_set_balance(1, 2, 1));
|
||||
|
||||
// Delegate and undelegate vote.
|
||||
assert_ok!(Democracy::delegate(Origin::signed(2), 1, 100));
|
||||
assert_ok!(Democracy::undelegate(Origin::signed(2)));
|
||||
|
||||
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
|
||||
System::set_block_number(2);
|
||||
let r = 0;
|
||||
assert_ok!(Democracy::vote(Origin::signed(1), r, AYE));
|
||||
|
||||
assert_eq!(Democracy::referendum_count(), 1);
|
||||
assert_eq!(Democracy::voters_for(r), vec![1]);
|
||||
assert_eq!(Democracy::vote_of((r, 1)), AYE);
|
||||
|
||||
// Delegated vote is not counted.
|
||||
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]
|
||||
/// If transactor voted, delegated vote is overwriten.
|
||||
fn single_proposal_should_work_with_delegation_and_vote() {
|
||||
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::vote(Origin::signed(1), r, AYE));
|
||||
|
||||
// Delegate vote.
|
||||
assert_ok!(Democracy::delegate(Origin::signed(2), 1, 100));
|
||||
|
||||
// Vote.
|
||||
assert_ok!(Democracy::vote(Origin::signed(2), r, AYE));
|
||||
|
||||
assert_eq!(Democracy::referendum_count(), 1);
|
||||
assert_eq!(Democracy::voters_for(r), vec![1, 2]);
|
||||
assert_eq!(Democracy::vote_of((r, 1)), AYE);
|
||||
|
||||
// Delegated vote is not counted.
|
||||
assert_eq!(Democracy::tally(r), (30, 0, 30));
|
||||
|
||||
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
|
||||
|
||||
@@ -774,6 +983,29 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lock_voting_should_work_with_delegation() {
|
||||
with_externalities(&mut new_test_ext_with_public_delay(1), || {
|
||||
System::set_block_number(1);
|
||||
let r = Democracy::inject_referendum(1, set_balance_proposal(2), VoteThreshold::SuperMajorityApprove, 0).unwrap();
|
||||
assert_ok!(Democracy::vote(Origin::signed(1), r, Vote::new(false, 6)));
|
||||
assert_ok!(Democracy::vote(Origin::signed(2), r, Vote::new(true, 5)));
|
||||
assert_ok!(Democracy::vote(Origin::signed(3), r, Vote::new(true, 4)));
|
||||
assert_ok!(Democracy::vote(Origin::signed(4), r, Vote::new(true, 3)));
|
||||
assert_ok!(Democracy::delegate(Origin::signed(5), 2, 2));
|
||||
assert_ok!(Democracy::vote(Origin::signed(6), r, Vote::new(false, 1)));
|
||||
|
||||
assert_eq!(Democracy::tally(r), (440, 120, 210));
|
||||
|
||||
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
|
||||
|
||||
System::set_block_number(2);
|
||||
assert_eq!(Democracy::end_block(System::block_number()), Ok(()));
|
||||
|
||||
assert_eq!(Balances::free_balance(&42), 2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn controversial_low_turnout_voting_should_work() {
|
||||
with_externalities(&mut new_test_ext(), || {
|
||||
|
||||
Reference in New Issue
Block a user