diff --git a/substrate/core/primitives/src/crypto.rs b/substrate/core/primitives/src/crypto.rs index cbe9fe208d..e0ecd4ce42 100644 --- a/substrate/core/primitives/src/crypto.rs +++ b/substrate/core/primitives/src/crypto.rs @@ -218,6 +218,7 @@ pub trait Derive: Sized { fn derive>(&self, _path: Iter) -> Option { None } } +#[cfg(feature = "std")] const PREFIX: &[u8] = b"SS58PRE"; #[cfg(feature = "std")] diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs index 5f6e3f7415..c6e8397be6 100644 --- a/substrate/node/runtime/src/lib.rs +++ b/substrate/node/runtime/src/lib.rs @@ -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, }; 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 79a0010e94..1514b0e607 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/council/src/seats.rs b/substrate/srml/council/src/seats.rs index f005cc6ecf..08bcd5f5d4 100644 --- a/substrate/srml/council/src/seats.rs +++ b/substrate/srml/council/src/seats.rs @@ -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, #[compact] index: VoteIndex) { + fn set_approvals(origin, votes: Vec, #[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 !>::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())?; - - >::put({ - let mut v = Self::voters(); - v.push(who.clone()); - v - }); - } - >::insert(&who, index); - >::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, #[compact] index: VoteIndex) -> Result { + let who = >::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 Module { >::remove(voter); } + // Actually do the voting. + fn do_set_approvals(who: T::AccountId, votes: Vec, 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 !>::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())?; + + >::mutate(|mut v| v.push(who.clone())); + } + >::insert(&who, index); + >::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), || { diff --git a/substrate/srml/democracy/src/lib.rs b/substrate/srml/democracy/src/lib.rs index 2673e43460..4bd7ad60aa 100644 --- a/substrate/srml/democracy/src/lib.rs +++ b/substrate/srml/democracy/src/lib.rs @@ -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 !>::exists(&(ref_index, who.clone())) { - >::mutate(ref_index, |voters| voters.push(who.clone())); - } - >::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; >::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!(!>::exists(&proxy), "already a proxy"); + >::insert(proxy, who) + } + + /// Clear the proxy. Called by the proxy. + fn resign_proxy(origin) { + let who = ensure_signed(origin)?; + >::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"); + >::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)?; >::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)?; - >::remove(who.clone()); + ensure!(>::exists(&who), "not delegated"); + let d = >::take(&who); + // Indefinite lock is reduced to the maximum voting lock that could be possible. + let lock_period = Self::public_delay(); + let now = >::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; + /// 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 Module { // Exposed mutables. + #[cfg(feature = "std")] + pub fn force_proxy(stash: T::AccountId, proxy: T::AccountId) { + >::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 { >::inject_referendum(>::block_number() + >::voting_period(), proposal, threshold, delay) @@ -353,6 +387,17 @@ impl Module { // 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 !>::exists(&(ref_index, who.clone())) { + >::mutate(ref_index, |voters| voters.push(who.clone())); + } + >::insert(&(ref_index, who), vote); + Ok(()) + } + /// Start a referendum fn inject_referendum( end: T::BlockNumber, @@ -464,6 +509,12 @@ impl Module { } } +impl OnFreeBalanceZero for Module { + fn on_free_balance_zero(who: &T::AccountId) { + >::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(), || {