diff --git a/frame/scored-pool/src/lib.rs b/frame/scored-pool/src/lib.rs index da26872a0071a..2279bdfbfc5fe 100644 --- a/frame/scored-pool/src/lib.rs +++ b/frame/scored-pool/src/lib.rs @@ -15,9 +15,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! # Scored Pool Module +//! # Scored Pool Pallet //! -//! The module maintains a scored membership pool. Each entity in the +//! The pallet maintains a scored membership pool. Each entity in the //! pool can be attributed a `Score`. From this pool a set `Members` //! is constructed. This set contains the `MemberCount` highest //! scoring entities. Unscored entities are never part of `Members`. @@ -39,7 +39,7 @@ //! //! - [`Config`] //! - [`Call`] -//! - [`Module`] +//! - [`Pallet`] //! //! ## Interface //! @@ -66,7 +66,7 @@ //! pub fn candidate(origin) -> dispatch::DispatchResult { //! let who = ensure_signed(origin)?; //! -//! let _ = >::submit_candidacy( +//! let _ = >::submit_candidacy( //! T::Origin::from(Some(who.clone()).into()) //! ); //! Ok(()) @@ -79,7 +79,7 @@ //! //! ## Dependencies //! -//! This module depends on the [System module](../frame_system/index.html). +//! This pallet depends on the [System pallet](../frame_system/index.html). // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] @@ -96,12 +96,11 @@ use sp_std::{ prelude::*, }; use frame_support::{ - decl_module, decl_storage, decl_event, ensure, decl_error, - traits::{EnsureOrigin, ChangeMembers, InitializeMembers, Currency, Get, ReservableCurrency}, - weights::Weight, + ensure, + traits::{ChangeMembers, InitializeMembers, Currency, Get, ReservableCurrency}, }; -use frame_system::{ensure_root, ensure_signed}; -use sp_runtime::traits::{AtLeast32Bit, MaybeSerializeDeserialize, Zero, StaticLookup}; +use sp_runtime::traits::{AtLeast32Bit, Zero, StaticLookup}; +pub use pallet::*; type BalanceOf = <>::Currency as Currency<::AccountId>>::Balance; type PoolT = Vec<(::AccountId, Option<>::Score>)>; @@ -116,96 +115,60 @@ enum ChangeReceiver { MembershipChanged, } -pub trait Config: frame_system::Config { - /// The currency used for deposits. - type Currency: Currency + ReservableCurrency; - - /// The score attributed to a member or candidate. - type Score: - AtLeast32Bit + Clone + Copy + Default + FullCodec + MaybeSerializeDeserialize + Debug; - - /// The overarching event type. - type Event: From> + Into<::Event>; - - // The deposit which is reserved from candidates if they want to - // start a candidacy. The deposit gets returned when the candidacy is - // withdrawn or when the candidate is kicked. - type CandidateDeposit: Get>; - - /// Every `Period` blocks the `Members` are filled with the highest scoring - /// members in the `Pool`. - type Period: Get; - - /// The receiver of the signal for when the membership has been initialized. - /// This happens pre-genesis and will usually be the same as `MembershipChanged`. - /// If you need to do something different on initialization, then you can change - /// this accordingly. - type MembershipInitialized: InitializeMembers; - - /// The receiver of the signal for when the members have changed. - type MembershipChanged: ChangeMembers; - - /// Allows a configurable origin type to set a score to a candidate in the pool. - type ScoreOrigin: EnsureOrigin; - - /// Required origin for removing a member (though can always be Root). - /// Configurable origin which enables removing an entity. If the entity - /// is part of the `Members` it is immediately replaced by the next - /// highest scoring candidate, if available. - type KickOrigin: EnsureOrigin; -} - -decl_storage! { - trait Store for Module, I: Instance=DefaultInstance> as ScoredPool { - /// The current pool of candidates, stored as an ordered Vec - /// (ordered descending by score, `None` last, highest first). - Pool get(fn pool) config(): PoolT; - - /// A Map of the candidates. The information in this Map is redundant - /// to the information in the `Pool`. But the Map enables us to easily - /// check if a candidate is already in the pool, without having to - /// iterate over the entire pool (the `Pool` is not sorted by - /// `T::AccountId`, but by `T::Score` instead). - CandidateExists get(fn candidate_exists): map hasher(twox_64_concat) T::AccountId => bool; - - /// The current membership, stored as an ordered Vec. - Members get(fn members): Vec; - - /// Size of the `Members` set. - MemberCount get(fn member_count) config(): u32; - } - add_extra_genesis { - config(members): Vec; - config(phantom): sp_std::marker::PhantomData; - build(|config| { - let mut pool = config.pool.clone(); - - // reserve balance for each candidate in the pool. - // panicking here is ok, since this just happens one time, pre-genesis. - pool - .iter() - .for_each(|(who, _)| { - T::Currency::reserve(&who, T::CandidateDeposit::get()) - .expect("balance too low to create candidacy"); - >::insert(who, true); - }); - - // Sorts the `Pool` by score in a descending order. Entities which - // have a score of `None` are sorted to the beginning of the vec. - pool.sort_by_key(|(_, maybe_score)| - Reverse(maybe_score.unwrap_or_default()) - ); - - >::put(&pool); - >::refresh_members(pool, ChangeReceiver::MembershipInitialized); - }) +#[frame_support::pallet] +pub mod pallet { + use frame_support::{pallet_prelude::*, traits::EnsureOrigin, weights::Weight}; + use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; + use sp_runtime::traits::MaybeSerializeDeserialize; + use super::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The currency used for deposits. + type Currency: Currency + ReservableCurrency; + + /// The score attributed to a member or candidate. + type Score: + AtLeast32Bit + Clone + Copy + Default + FullCodec + MaybeSerializeDeserialize + Debug; + + /// The overarching event type. + type Event: From> + IsType<::Event>; + + // The deposit which is reserved from candidates if they want to + // start a candidacy. The deposit gets returned when the candidacy is + // withdrawn or when the candidate is kicked. + type CandidateDeposit: Get>; + + /// Every `Period` blocks the `Members` are filled with the highest scoring + /// members in the `Pool`. + type Period: Get; + + /// The receiver of the signal for when the membership has been initialized. + /// This happens pre-genesis and will usually be the same as `MembershipChanged`. + /// If you need to do something different on initialization, then you can change + /// this accordingly. + type MembershipInitialized: InitializeMembers; + + /// The receiver of the signal for when the members have changed. + type MembershipChanged: ChangeMembers; + + /// Allows a configurable origin type to set a score to a candidate in the pool. + type ScoreOrigin: EnsureOrigin; + + /// Required origin for removing a member (though can always be Root). + /// Configurable origin which enables removing an entity. If the entity + /// is part of the `Members` it is immediately replaced by the next + /// highest scoring candidate, if available. + type KickOrigin: EnsureOrigin; } -} -decl_event!( - pub enum Event where - ::AccountId, - { + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { /// The given member was removed. See the transaction for who. MemberRemoved, /// An entity has issued a candidacy. See the transaction for who. @@ -218,14 +181,11 @@ decl_event!( /// A score was attributed to the candidate. /// See the transaction for who. CandidateScored, - /// Phantom member, never used. - Dummy(sp_std::marker::PhantomData<(AccountId, I)>), } -); -decl_error! { - /// Error for the scored-pool module. - pub enum Error for Module, I: Instance> { + /// Error for the scored-pool pallet. + #[pallet::error] + pub enum Error { /// Already a member. AlreadyInPool, /// Index out of bounds. @@ -233,27 +193,95 @@ decl_error! { /// Index does not match requested account. WrongAccountIndex, } -} -decl_module! { - pub struct Module, I: Instance=DefaultInstance> - for enum Call - where origin: T::Origin - { - type Error = Error; + /// The current pool of candidates, stored as an ordered Vec + /// (ordered descending by score, `None` last, highest first). + #[pallet::storage] + #[pallet::getter(fn pool)] + pub(crate) type Pool, I: 'static = ()> = StorageValue<_, PoolT, ValueQuery>; + + /// A Map of the candidates. The information in this Map is redundant + /// to the information in the `Pool`. But the Map enables us to easily + /// check if a candidate is already in the pool, without having to + /// iterate over the entire pool (the `Pool` is not sorted by + /// `T::AccountId`, but by `T::Score` instead). + #[pallet::storage] + #[pallet::getter(fn candidate_exists)] + pub(crate) type CandidateExists, I: 'static = ()> = StorageMap< + _, + Twox64Concat, T::AccountId, + bool, + ValueQuery, + >; + + /// The current membership, stored as an ordered Vec. + #[pallet::storage] + #[pallet::getter(fn members)] + pub(crate) type Members, I: 'static = ()> = StorageValue<_, Vec, ValueQuery>; + + /// Size of the `Members` set. + #[pallet::storage] + #[pallet::getter(fn member_count)] + pub(crate) type MemberCount = StorageValue<_, u32, ValueQuery>; + + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()> { + pub pool: PoolT, + pub member_count: u32, + } + + #[cfg(feature = "std")] + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + Self { + pool: Default::default(), + member_count: Default::default(), + } + } + } + + #[pallet::genesis_build] + impl, I: 'static> GenesisBuild for GenesisConfig { + fn build(&self) { + let mut pool = self.pool.clone(); + + // reserve balance for each candidate in the pool. + // panicking here is ok, since this just happens one time, pre-genesis. + pool + .iter() + .for_each(|(who, _)| { + T::Currency::reserve(&who, T::CandidateDeposit::get()) + .expect("balance too low to create candidacy"); + >::insert(who, true); + }); + + // Sorts the `Pool` by score in a descending order. Entities which + // have a score of `None` are sorted to the beginning of the vec. + pool.sort_by_key(|(_, maybe_score)| + Reverse(maybe_score.unwrap_or_default()) + ); - fn deposit_event() = default; + >::put(self.member_count); + >::put(&pool); + >::refresh_members(pool, ChangeReceiver::MembershipInitialized); + } + } + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet { /// Every `Period` blocks the `Members` set is refreshed from the /// highest scoring members in the pool. fn on_initialize(n: T::BlockNumber) -> Weight { if n % T::Period::get() == Zero::zero() { let pool = >::get(); - >::refresh_members(pool, ChangeReceiver::MembershipChanged); + >::refresh_members(pool, ChangeReceiver::MembershipChanged); } 0 } + } + #[pallet::call] + impl, I: 'static> Pallet { /// Add `origin` to the pool of candidates. /// /// This results in `CandidateDeposit` being reserved from @@ -265,8 +293,8 @@ decl_module! { /// /// The `index` parameter of this function must be set to /// the index of the transactor in the `Pool`. - #[weight = 0] - pub fn submit_candidacy(origin) { + #[pallet::weight(0)] + pub fn submit_candidacy(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; ensure!(!>::contains_key(&who), Error::::AlreadyInPool); @@ -279,7 +307,8 @@ decl_module! { >::insert(&who, true); - Self::deposit_event(RawEvent::CandidateAdded); + Self::deposit_event(Event::::CandidateAdded); + Ok(()) } /// An entity withdraws candidacy and gets its deposit back. @@ -292,18 +321,19 @@ decl_module! { /// /// The `index` parameter of this function must be set to /// the index of the transactor in the `Pool`. - #[weight = 0] + #[pallet::weight(0)] pub fn withdraw_candidacy( - origin, + origin: OriginFor, index: u32 - ) { + ) -> DispatchResult { let who = ensure_signed(origin)?; let pool = >::get(); Self::ensure_index(&pool, &who, index)?; Self::remove_member(pool, who, index)?; - Self::deposit_event(RawEvent::CandidateWithdrew); + Self::deposit_event(Event::::CandidateWithdrew); + Ok(()) } /// Kick a member `who` from the set. @@ -312,12 +342,12 @@ decl_module! { /// /// The `index` parameter of this function must be set to /// the index of `dest` in the `Pool`. - #[weight = 0] + #[pallet::weight(0)] pub fn kick( - origin, + origin: OriginFor, dest: ::Source, index: u32 - ) { + ) -> DispatchResult { T::KickOrigin::ensure_origin(origin)?; let who = T::Lookup::lookup(dest)?; @@ -326,7 +356,8 @@ decl_module! { Self::ensure_index(&pool, &who, index)?; Self::remove_member(pool, who, index)?; - Self::deposit_event(RawEvent::CandidateKicked); + Self::deposit_event(Event::::CandidateKicked); + Ok(()) } /// Score a member `who` with `score`. @@ -335,13 +366,13 @@ decl_module! { /// /// The `index` parameter of this function must be set to /// the index of the `dest` in the `Pool`. - #[weight = 0] + #[pallet::weight(0)] pub fn score( - origin, + origin: OriginFor, dest: ::Source, index: u32, score: T::Score - ) { + ) -> DispatchResult { T::ScoreOrigin::ensure_origin(origin)?; let who = T::Lookup::lookup(dest)?; @@ -365,7 +396,8 @@ decl_module! { pool.insert(location, item); >::put(&pool); - Self::deposit_event(RawEvent::CandidateScored); + Self::deposit_event(Event::::CandidateScored); + Ok(()) } /// Dispatchable call to change `MemberCount`. @@ -374,15 +406,16 @@ decl_module! { /// (this happens each `Period`). /// /// May only be called from root. - #[weight = 0] - pub fn change_member_count(origin, count: u32) { + #[pallet::weight(0)] + pub fn change_member_count(origin: OriginFor, count: u32) -> DispatchResult { ensure_root(origin)?; - >::put(&count); + MemberCount::::put(&count); + Ok(()) } } } -impl, I: Instance> Module { +impl, I: 'static> Pallet { /// Fetches the `MemberCount` highest scoring members from /// `Pool` and puts them into `Members`. @@ -393,7 +426,7 @@ impl, I: Instance> Module { pool: PoolT, notify: ChangeReceiver ) { - let count = >::get(); + let count = MemberCount::::get(); let mut new_members: Vec = pool .into_iter() @@ -426,7 +459,7 @@ impl, I: Instance> Module { remove: T::AccountId, index: u32 ) -> Result<(), Error> { - // all callers of this function in this module also check + // all callers of this function in this pallet also check // the index for validity before calling this function. // nevertheless we check again here, to assert that there was // no mistake when invoking this sensible function. @@ -445,7 +478,7 @@ impl, I: Instance> Module { T::Currency::unreserve(&remove, T::CandidateDeposit::get()); - Self::deposit_event(RawEvent::MemberRemoved); + Self::deposit_event(Event::::MemberRemoved); Ok(()) } diff --git a/frame/scored-pool/src/mock.rs b/frame/scored-pool/src/mock.rs index 1da665f43eaef..8f7acd32007e7 100644 --- a/frame/scored-pool/src/mock.rs +++ b/frame/scored-pool/src/mock.rs @@ -21,7 +21,7 @@ use super::*; use crate as pallet_scored_pool; use std::cell::RefCell; -use frame_support::{parameter_types, ord_parameter_types}; +use frame_support::{parameter_types, ord_parameter_types, traits::GenesisBuild}; use sp_core::H256; use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, testing::Header, @@ -160,7 +160,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { /// Fetch an entity from the pool, if existent. pub fn fetch_from_pool(who: u64) -> Option<(u64, Option)> { - >::pool() + >::pool() .into_iter() .find(|item| item.0 == who) } @@ -168,7 +168,7 @@ pub fn fetch_from_pool(who: u64) -> Option<(u64, Option)> { /// Find an entity in the pool. /// Returns its position in the `Pool` vec, if existent. pub fn find_in_pool(who: u64) -> Option { - >::pool() + >::pool() .into_iter() .position(|item| item.0 == who) } diff --git a/frame/scored-pool/src/tests.rs b/frame/scored-pool/src/tests.rs index e24ee91164973..4a3b8384b744f 100644 --- a/frame/scored-pool/src/tests.rs +++ b/frame/scored-pool/src/tests.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Tests for the module. +//! Tests for the pallet. use super::*; use mock::*; @@ -23,7 +23,7 @@ use mock::*; use frame_support::{assert_ok, assert_noop, traits::OnInitialize}; use sp_runtime::traits::BadOrigin; -type ScoredPool = Module; +type ScoredPool = Pallet; type System = frame_system::Pallet; type Balances = pallet_balances::Pallet;