diff --git a/frame/conviction-voting/src/conviction.rs b/frame/conviction-voting/src/conviction.rs index 129f2771124b5..1feff35b19fcc 100644 --- a/frame/conviction-voting/src/conviction.rs +++ b/frame/conviction-voting/src/conviction.rs @@ -17,14 +17,14 @@ //! The conviction datatype. -use crate::types::Delegations; use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{ traits::{Bounded, CheckedDiv, CheckedMul, Zero}, RuntimeDebug, }; -use sp_std::{convert::TryFrom, result::Result}; + +use crate::types::Delegations; /// A value denoting the strength of conviction of a vote. #[derive( diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index 8e7e0d91b1cf4..af91e99fb3796 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -28,6 +28,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::{ + dispatch::{DispatchError, DispatchResult}, ensure, traits::{ fungible, Currency, Get, LockIdentifier, LockableCurrency, PollStatus, Polling, @@ -36,7 +37,7 @@ use frame_support::{ }; use sp_runtime::{ traits::{AtLeast32BitUnsigned, Saturating, Zero}, - ArithmeticError, DispatchError, DispatchResult, Perbill, + ArithmeticError, Perbill, }; use sp_std::prelude::*; @@ -44,11 +45,14 @@ mod conviction; mod types; mod vote; pub mod weights; -pub use conviction::Conviction; -pub use pallet::*; -pub use types::{Delegations, Tally, UnvoteScope}; -pub use vote::{AccountVote, Casting, Delegating, Vote, Voting}; -pub use weights::WeightInfo; + +pub use self::{ + conviction::Conviction, + pallet::*, + types::{Delegations, Tally, UnvoteScope}, + vote::{AccountVote, Casting, Delegating, Vote, Voting}, + weights::WeightInfo, +}; #[cfg(test)] mod tests; @@ -58,44 +62,43 @@ pub mod benchmarking; const CONVICTION_VOTING_ID: LockIdentifier = *b"pyconvot"; -type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; -type VotingOf = Voting< - BalanceOf, +type BalanceOf = + <>::Currency as Currency<::AccountId>>::Balance; +type VotingOf = Voting< + BalanceOf, ::AccountId, ::BlockNumber, - PollIndexOf, - ::MaxVotes, + PollIndexOf, + >::MaxVotes, >; #[allow(dead_code)] -type DelegatingOf = Delegating< - BalanceOf, +type DelegatingOf = Delegating< + BalanceOf, ::AccountId, ::BlockNumber, >; -pub type TallyOf = Tally, ::MaxTurnout>; -pub type VotesOf = BalanceOf; -type PollIndexOf = <::Polls as Polling>>::Index; +pub type TallyOf = Tally, >::MaxTurnout>; +pub type VotesOf = BalanceOf; +type PollIndexOf = <>::Polls as Polling>>::Index; #[cfg(feature = "runtime-benchmarks")] -type IndexOf = <::Polls as Polling>>::Index; -type ClassOf = <::Polls as Polling>>::Class; +type IndexOf = <>::Polls as Polling>>::Index; +type ClassOf = <>::Polls as Polling>>::Class; #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; - use sp_runtime::DispatchResult; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] #[pallet::without_storage_info] - pub struct Pallet(_); + pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config + Sized { + pub trait Config: frame_system::Config + Sized { // System level stuff. - type Event: From> + IsType<::Event>; + type Event: From> + IsType<::Event>; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; /// Currency type with which voting happens. @@ -104,12 +107,16 @@ pub mod pallet { + fungible::Inspect; /// The implementation of the logic which conducts polls. - type Polls: Polling, Votes = BalanceOf, Moment = Self::BlockNumber>; + type Polls: Polling< + TallyOf, + Votes = BalanceOf, + Moment = Self::BlockNumber, + >; /// The maximum amount of tokens which may be used for voting. May just be /// `Currency::total_issuance`, but you might want to reduce this in order to account for /// funds in the system which are unable to vote (e.g. parachain auction deposits). - type MaxTurnout: Get>; + type MaxTurnout: Get>; /// The maximum number of concurrent votes an account may have. /// @@ -129,13 +136,13 @@ pub mod pallet { /// All voting for a particular voter in a particular voting class. We store the balance for the /// number of votes that we have recorded. #[pallet::storage] - pub type VotingFor = StorageDoubleMap< + pub type VotingFor, I: 'static = ()> = StorageDoubleMap< _, Twox64Concat, T::AccountId, Twox64Concat, - ClassOf, - VotingOf, + ClassOf, + VotingOf, ValueQuery, >; @@ -143,12 +150,17 @@ pub mod pallet { /// require. The actual amount locked on behalf of this pallet should always be the maximum of /// this list. #[pallet::storage] - pub type ClassLocksFor = - StorageMap<_, Twox64Concat, T::AccountId, Vec<(ClassOf, BalanceOf)>, ValueQuery>; + pub type ClassLocksFor, I: 'static = ()> = StorageMap< + _, + Twox64Concat, + T::AccountId, + Vec<(ClassOf, BalanceOf)>, + ValueQuery, + >; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { + pub enum Event, I: 'static = ()> { /// An account has delegated their vote to another account. \[who, target\] Delegated(T::AccountId, T::AccountId), /// An \[account\] has cancelled a previous delegation operation. @@ -156,7 +168,7 @@ pub mod pallet { } #[pallet::error] - pub enum Error { + pub enum Error { /// Poll is not ongoing. NotOngoing, /// The given account did not vote on the poll. @@ -185,7 +197,7 @@ pub mod pallet { } #[pallet::call] - impl Pallet { + impl, I: 'static> Pallet { /// Vote in a poll. If `vote.is_aye()`, the vote is to enact the proposal; /// otherwise it is a vote to keep the status quo. /// @@ -198,8 +210,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::vote_new().max(T::WeightInfo::vote_existing()))] pub fn vote( origin: OriginFor, - #[pallet::compact] poll_index: PollIndexOf, - vote: AccountVote>, + #[pallet::compact] poll_index: PollIndexOf, + vote: AccountVote>, ) -> DispatchResult { let who = ensure_signed(origin)?; Self::try_vote(&who, poll_index, vote) @@ -233,10 +245,10 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::delegate(T::MaxVotes::get()))] pub fn delegate( origin: OriginFor, - class: ClassOf, + class: ClassOf, to: T::AccountId, conviction: Conviction, - balance: BalanceOf, + balance: BalanceOf, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let votes = Self::try_delegate(who, class, to, conviction, balance)?; @@ -261,7 +273,10 @@ pub mod pallet { // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure // because a valid delegation cover decoding a direct voting with max votes. #[pallet::weight(T::WeightInfo::undelegate(T::MaxVotes::get().into()))] - pub fn undelegate(origin: OriginFor, class: ClassOf) -> DispatchResultWithPostInfo { + pub fn undelegate( + origin: OriginFor, + class: ClassOf, + ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let votes = Self::try_undelegate(who, class)?; Ok(Some(T::WeightInfo::undelegate(votes)).into()) @@ -279,7 +294,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::unlock())] pub fn unlock( origin: OriginFor, - class: ClassOf, + class: ClassOf, target: T::AccountId, ) -> DispatchResult { ensure_signed(origin)?; @@ -319,8 +334,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::remove_vote())] pub fn remove_vote( origin: OriginFor, - class: Option>, - index: PollIndexOf, + class: Option>, + index: PollIndexOf, ) -> DispatchResult { let who = ensure_signed(origin)?; Self::try_remove_vote(&who, index, class, UnvoteScope::Any) @@ -346,8 +361,8 @@ pub mod pallet { pub fn remove_other_vote( origin: OriginFor, target: T::AccountId, - class: ClassOf, - index: PollIndexOf, + class: ClassOf, + index: PollIndexOf, ) -> DispatchResult { let who = ensure_signed(origin)?; let scope = if target == who { UnvoteScope::Any } else { UnvoteScope::OnlyExpired }; @@ -357,17 +372,17 @@ pub mod pallet { } } -impl Pallet { +impl, I: 'static> Pallet { /// Actually enact a vote, if legit. fn try_vote( who: &T::AccountId, - poll_index: PollIndexOf, - vote: AccountVote>, + poll_index: PollIndexOf, + vote: AccountVote>, ) -> DispatchResult { - ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::InsufficientFunds); + ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::InsufficientFunds); T::Polls::try_access_poll(poll_index, |poll_status| { - let (tally, class) = poll_status.ensure_ongoing().ok_or(Error::::NotOngoing)?; - VotingFor::::try_mutate(who, &class, |voting| { + let (tally, class) = poll_status.ensure_ongoing().ok_or(Error::::NotOngoing)?; + VotingFor::::try_mutate(who, &class, |voting| { if let Voting::Casting(Casting { ref mut votes, delegations, .. }) = voting { match votes.binary_search_by_key(&poll_index, |i| i.0) { Ok(i) => { @@ -381,7 +396,7 @@ impl Pallet { Err(i) => { votes .try_insert(i, (poll_index, vote)) - .map_err(|()| Error::::MaxVotesReached)?; + .map_err(|()| Error::::MaxVotesReached)?; }, } // Shouldn't be possible to fail, but we handle it gracefully. @@ -390,7 +405,7 @@ impl Pallet { tally.increase(approve, *delegations); } } else { - return Err(Error::::AlreadyDelegating.into()) + return Err(Error::::AlreadyDelegating.into()) } // Extend the lock to `balance` (rather than setting it) since we don't know what // other votes are in place. @@ -408,23 +423,23 @@ impl Pallet { /// This will generally be combined with a call to `unlock`. fn try_remove_vote( who: &T::AccountId, - poll_index: PollIndexOf, - class_hint: Option>, + poll_index: PollIndexOf, + class_hint: Option>, scope: UnvoteScope, ) -> DispatchResult { let class = class_hint .or_else(|| Some(T::Polls::as_ongoing(poll_index)?.1)) - .ok_or(Error::::ClassNeeded)?; - VotingFor::::try_mutate(who, class, |voting| { + .ok_or(Error::::ClassNeeded)?; + VotingFor::::try_mutate(who, class, |voting| { if let Voting::Casting(Casting { ref mut votes, delegations, ref mut prior }) = voting { let i = votes .binary_search_by_key(&poll_index, |i| i.0) - .map_err(|_| Error::::NotVoter)?; + .map_err(|_| Error::::NotVoter)?; let v = votes.remove(i); T::Polls::try_access_poll(poll_index, |poll_status| match poll_status { PollStatus::Ongoing(tally, _) => { - ensure!(matches!(scope, UnvoteScope::Any), Error::::NoPermission); + ensure!(matches!(scope, UnvoteScope::Any), Error::::NoPermission); // Shouldn't be possible to fail, but we handle it gracefully. tally.remove(v.1).ok_or(ArithmeticError::Underflow)?; if let Some(approve) = v.1.as_standard() { @@ -441,7 +456,7 @@ impl Pallet { if now < unlock_at { ensure!( matches!(scope, UnvoteScope::Any), - Error::::NoPermissionYet + Error::::NoPermissionYet ); prior.accumulate(unlock_at, balance) } @@ -459,10 +474,10 @@ impl Pallet { /// Return the number of votes for `who` fn increase_upstream_delegation( who: &T::AccountId, - class: &ClassOf, - amount: Delegations>, + class: &ClassOf, + amount: Delegations>, ) -> u32 { - VotingFor::::mutate(who, class, |voting| match voting { + VotingFor::::mutate(who, class, |voting| match voting { Voting::Delegating(Delegating { delegations, .. }) => { // We don't support second level delegating, so we don't need to do anything more. *delegations = delegations.saturating_add(amount); @@ -487,10 +502,10 @@ impl Pallet { /// Return the number of votes for `who` fn reduce_upstream_delegation( who: &T::AccountId, - class: &ClassOf, - amount: Delegations>, + class: &ClassOf, + amount: Delegations>, ) -> u32 { - VotingFor::::mutate(who, class, |voting| match voting { + VotingFor::::mutate(who, class, |voting| match voting { Voting::Delegating(Delegating { delegations, .. }) => { // We don't support second level delegating, so we don't need to do anything more. *delegations = delegations.saturating_sub(amount); @@ -517,16 +532,16 @@ impl Pallet { /// Return the upstream number of votes. fn try_delegate( who: T::AccountId, - class: ClassOf, + class: ClassOf, target: T::AccountId, conviction: Conviction, - balance: BalanceOf, + balance: BalanceOf, ) -> Result { - ensure!(who != target, Error::::Nonsense); - T::Polls::classes().binary_search(&class).map_err(|_| Error::::BadClass)?; - ensure!(balance <= T::Currency::free_balance(&who), Error::::InsufficientFunds); + ensure!(who != target, Error::::Nonsense); + T::Polls::classes().binary_search(&class).map_err(|_| Error::::BadClass)?; + ensure!(balance <= T::Currency::free_balance(&who), Error::::InsufficientFunds); let votes = - VotingFor::::try_mutate(&who, &class, |voting| -> Result { + VotingFor::::try_mutate(&who, &class, |voting| -> Result { let old = sp_std::mem::replace( voting, Voting::Delegating(Delegating { @@ -538,10 +553,10 @@ impl Pallet { }), ); match old { - Voting::Delegating(Delegating { .. }) => Err(Error::::AlreadyDelegating)?, + Voting::Delegating(Delegating { .. }) => Err(Error::::AlreadyDelegating)?, Voting::Casting(Casting { votes, delegations, prior }) => { // here we just ensure that we're currently idling with no votes recorded. - ensure!(votes.is_empty(), Error::::AlreadyVoting); + ensure!(votes.is_empty(), Error::::AlreadyVoting); voting.set_common(delegations, prior); }, } @@ -553,16 +568,16 @@ impl Pallet { Self::extend_lock(&who, &class, balance); Ok(votes) })?; - Self::deposit_event(Event::::Delegated(who, target)); + Self::deposit_event(Event::::Delegated(who, target)); Ok(votes) } /// Attempt to end the current delegation. /// /// Return the number of votes of upstream. - fn try_undelegate(who: T::AccountId, class: ClassOf) -> Result { + fn try_undelegate(who: T::AccountId, class: ClassOf) -> Result { let votes = - VotingFor::::try_mutate(&who, &class, |voting| -> Result { + VotingFor::::try_mutate(&who, &class, |voting| -> Result { match sp_std::mem::replace(voting, Voting::default()) { Voting::Delegating(Delegating { balance, @@ -589,29 +604,31 @@ impl Pallet { Ok(votes) }, - Voting::Casting(_) => Err(Error::::NotDelegating.into()), + Voting::Casting(_) => Err(Error::::NotDelegating.into()), } })?; - Self::deposit_event(Event::::Undelegated(who)); + Self::deposit_event(Event::::Undelegated(who)); Ok(votes) } - fn extend_lock(who: &T::AccountId, class: &ClassOf, amount: BalanceOf) { - ClassLocksFor::::mutate(who, |locks| match locks.iter().position(|x| &x.0 == class) { - Some(i) => locks[i].1 = locks[i].1.max(amount), - None => locks.push((class.clone(), amount)), + fn extend_lock(who: &T::AccountId, class: &ClassOf, amount: BalanceOf) { + ClassLocksFor::::mutate(who, |locks| { + match locks.iter().position(|x| &x.0 == class) { + Some(i) => locks[i].1 = locks[i].1.max(amount), + None => locks.push((class.clone(), amount)), + } }); T::Currency::extend_lock(CONVICTION_VOTING_ID, who, amount, WithdrawReasons::TRANSFER); } /// Rejig the lock on an account. It will never get more stringent (since that would indicate /// a security hole) but may be reduced from what they are currently. - fn update_lock(class: &ClassOf, who: &T::AccountId) { - let class_lock_needed = VotingFor::::mutate(who, class, |voting| { + fn update_lock(class: &ClassOf, who: &T::AccountId) { + let class_lock_needed = VotingFor::::mutate(who, class, |voting| { voting.rejig(frame_system::Pallet::::block_number()); voting.locked_balance() }); - let lock_needed = ClassLocksFor::::mutate(who, |locks| { + let lock_needed = ClassLocksFor::::mutate(who, |locks| { locks.retain(|x| &x.0 != class); if !class_lock_needed.is_zero() { locks.push((class.clone(), class_lock_needed)); diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index cedb23b02a8db..6a8bad5d8944e 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -19,8 +19,6 @@ use std::collections::BTreeMap; -use super::*; -use crate as pallet_conviction_voting; use frame_support::{ assert_noop, assert_ok, parameter_types, traits::{ConstU32, ConstU64, Contains, Polling}, @@ -31,6 +29,9 @@ use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, }; +use super::*; +use crate as pallet_conviction_voting; + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; diff --git a/frame/conviction-voting/src/types.rs b/frame/conviction-voting/src/types.rs index 2ad1a164dd143..e2b5844ddd5df 100644 --- a/frame/conviction-voting/src/types.rs +++ b/frame/conviction-voting/src/types.rs @@ -19,8 +19,6 @@ use sp_std::marker::PhantomData; -use super::*; -use crate::{AccountVote, Conviction, Vote}; use codec::{Codec, Decode, Encode, MaxEncodedLen}; use frame_support::{ traits::VoteTally, CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, @@ -32,6 +30,9 @@ use sp_runtime::{ RuntimeDebug, }; +use super::*; +use crate::{AccountVote, Conviction, Vote}; + /// Info regarding an ongoing referendum. #[derive( CloneNoBound,