diff --git a/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm b/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm index 0059c300216579fe71a647c516ae913cae408a2e..4e37d4101a2d12f63345fc1ba4b5e05e99dbd912 100644 Binary files a/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm and b/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm differ diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs index ea9f60afd9952a89a4afa0a2e5b8f217825b1644..b1199d2891db12eb78dd60bbc8b36db7300651fe 100644 --- a/substrate/node/runtime/src/lib.rs +++ b/substrate/node/runtime/src/lib.rs @@ -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, }; diff --git a/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm b/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm index f3ccde68d4398f18d7df982f890753e6e9b720d2..d3cd9bbcfb4e7b91cfce9282a20269b74c7ffe75 100644 Binary files a/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm and b/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm differ diff --git a/substrate/srml/democracy/src/lib.rs b/substrate/srml/democracy/src/lib.rs index fb2ee276681d538715de9b1f7ffe954efc6cb7d7..befbb2082dd5d77e6a5761d5cb100888fc9bc5ac 100644 --- a/substrate/srml/democracy/src/lib.rs +++ b/substrate/srml/democracy/src/lib.rs @@ -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(), || {