diff --git a/substrate/frame/society/src/lib.rs b/substrate/frame/society/src/lib.rs index c6d63eed20ac05d76079d5347da441e35865c207..83b1c4203722b2d3db41abb69612ba38565126ec 100644 --- a/substrate/frame/society/src/lib.rs +++ b/substrate/frame/society/src/lib.rs @@ -15,14 +15,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! # Society Module +//! # Society Pallet //! //! - [`Config`] //! - [`Call`] //! //! ## Overview //! -//! The Society module is an economic game which incentivizes users to participate +//! The Society pallet is an economic game which incentivizes users to participate //! and maintain a membership society. //! //! ### User Types @@ -77,7 +77,7 @@ //! #### Society Treasury //! //! The membership society is independently funded by a treasury managed by this -//! module. Some subset of this treasury is placed in a Society Pot, which is used +//! pallet. Some subset of this treasury is placed in a Society Pot, which is used //! to determine the number of accepted bids. //! //! #### Rate of Growth @@ -132,7 +132,7 @@ //! the society. A vouching bid can additionally request some portion of that reward as a tip //! to the voucher for vouching for the prospective candidate. //! -//! Every rotation period, Bids are ordered by reward amount, and the module +//! Every rotation period, Bids are ordered by reward amount, and the pallet //! selects as many bids the Society Pot can support for that period. //! //! These selected bids become candidates and move on to the Candidate phase. @@ -251,19 +251,15 @@ mod mock; #[cfg(test)] mod tests; -use codec::{Decode, Encode}; use frame_support::{ - decl_error, decl_event, decl_module, decl_storage, - dispatch::DispatchResult, - ensure, + pallet_prelude::*, traits::{ BalanceStatus, ChangeMembers, Currency, EnsureOrigin, ExistenceRequirement::AllowDeath, - Get, Imbalance, OnUnbalanced, Randomness, ReservableCurrency, + Imbalance, OnUnbalanced, Randomness, ReservableCurrency, }, - weights::Weight, PalletId, }; -use frame_system::{self as system, ensure_root, ensure_signed}; +use frame_system::pallet_prelude::*; use rand_chacha::{ rand_core::{RngCore, SeedableRng}, ChaChaRng, @@ -278,62 +274,14 @@ use sp_runtime::{ }; use sp_std::prelude::*; +pub use pallet::*; + type BalanceOf<T, I> = - <<T as Config<I>>::Currency as Currency<<T as system::Config>::AccountId>>::Balance; -type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency< + <<T as Config<I>>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance; +type NegativeImbalanceOf<T, I> = <<T as Config<I>>::Currency as Currency< <T as frame_system::Config>::AccountId, >>::NegativeImbalance; -/// The module's configuration trait. -pub trait Config<I = DefaultInstance>: system::Config { - /// The overarching event type. - type Event: From<Event<Self, I>> + Into<<Self as system::Config>::Event>; - - /// The societies's module id - type PalletId: Get<PalletId>; - - /// The currency type used for bidding. - type Currency: ReservableCurrency<Self::AccountId>; - - /// Something that provides randomness in the runtime. - type Randomness: Randomness<Self::Hash, Self::BlockNumber>; - - /// The minimum amount of a deposit required for a bid to be made. - type CandidateDeposit: Get<BalanceOf<Self, I>>; - - /// The amount of the unpaid reward that gets deducted in the case that either a skeptic - /// doesn't vote or someone votes in the wrong way. - type WrongSideDeduction: Get<BalanceOf<Self, I>>; - - /// The number of times a member may vote the wrong way (or not at all, when they are a skeptic) - /// before they become suspended. - type MaxStrikes: Get<u32>; - - /// The amount of incentive paid within each period. Doesn't include VoterTip. - type PeriodSpend: Get<BalanceOf<Self, I>>; - - /// The receiver of the signal for when the members have changed. - type MembershipChanged: ChangeMembers<Self::AccountId>; - - /// The number of blocks between candidate/membership rotation periods. - type RotationPeriod: Get<Self::BlockNumber>; - - /// The maximum duration of the payout lock. - type MaxLockDuration: Get<Self::BlockNumber>; - - /// The origin that is allowed to call `found`. - type FounderSetOrigin: EnsureOrigin<Self::Origin>; - - /// The origin that is allowed to make suspension judgements. - type SuspensionJudgementOrigin: EnsureOrigin<Self::Origin>; - - /// The number of blocks between membership challenges. - type ChallengePeriod: Get<Self::BlockNumber>; - - /// The maximum number of candidates that we accept per round. - type MaxCandidateIntake: Get<u32>; -} - /// A vote by a member on a candidate application. #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub enum Vote { @@ -417,108 +365,320 @@ impl<AccountId: PartialEq, Balance> BidKind<AccountId, Balance> { } } -// This module's storage items. -decl_storage! { - trait Store for Module<T: Config<I>, I: Instance=DefaultInstance> as Society { - /// The first member. - pub Founder get(fn founder) build(|config: &GenesisConfig<T, I>| config.members.first().cloned()): - Option<T::AccountId>; +#[frame_support::pallet] +pub mod pallet { + use super::*; - /// A hash of the rules of this society concerning membership. Can only be set once and - /// only by the founder. - pub Rules get(fn rules): Option<T::Hash>; + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet<T, I = ()>(_); - /// The current set of candidates; bidders that are attempting to become members. - pub Candidates get(fn candidates): Vec<Bid<T::AccountId, BalanceOf<T, I>>>; + #[pallet::config] + pub trait Config<I: 'static = ()>: frame_system::Config { + /// The overarching event type. + type Event: From<Event<Self, I>> + IsType<<Self as frame_system::Config>::Event>; - /// The set of suspended candidates. - pub SuspendedCandidates get(fn suspended_candidate): - map hasher(twox_64_concat) T::AccountId - => Option<(BalanceOf<T, I>, BidKind<T::AccountId, BalanceOf<T, I>>)>; + /// The societies's pallet id + #[pallet::constant] + type PalletId: Get<PalletId>; - /// Amount of our account balance that is specifically for the next round's bid(s). - pub Pot get(fn pot) config(): BalanceOf<T, I>; + /// The currency type used for bidding. + type Currency: ReservableCurrency<Self::AccountId>; - /// The most primary from the most recently approved members. - pub Head get(fn head) build(|config: &GenesisConfig<T, I>| config.members.first().cloned()): - Option<T::AccountId>; + /// Something that provides randomness in the runtime. + type Randomness: Randomness<Self::Hash, Self::BlockNumber>; - /// The current set of members, ordered. - pub Members get(fn members) build(|config: &GenesisConfig<T, I>| { - let mut m = config.members.clone(); - m.sort(); - m - }): Vec<T::AccountId>; + /// The minimum amount of a deposit required for a bid to be made. + #[pallet::constant] + type CandidateDeposit: Get<BalanceOf<Self, I>>; + + /// The amount of the unpaid reward that gets deducted in the case that either a skeptic + /// doesn't vote or someone votes in the wrong way. + #[pallet::constant] + type WrongSideDeduction: Get<BalanceOf<Self, I>>; - /// The set of suspended members. - pub SuspendedMembers get(fn suspended_member): map hasher(twox_64_concat) T::AccountId => bool; + /// The number of times a member may vote the wrong way (or not at all, when they are a + /// skeptic) before they become suspended. + #[pallet::constant] + type MaxStrikes: Get<u32>; - /// The current bids, stored ordered by the value of the bid. - Bids: Vec<Bid<T::AccountId, BalanceOf<T, I>>>; + /// The amount of incentive paid within each period. Doesn't include VoterTip. + #[pallet::constant] + type PeriodSpend: Get<BalanceOf<Self, I>>; - /// Members currently vouching or banned from vouching again - Vouching get(fn vouching): map hasher(twox_64_concat) T::AccountId => Option<VouchingStatus>; + /// The receiver of the signal for when the members have changed. + type MembershipChanged: ChangeMembers<Self::AccountId>; - /// Pending payouts; ordered by block number, with the amount that should be paid out. - Payouts: map hasher(twox_64_concat) T::AccountId => Vec<(T::BlockNumber, BalanceOf<T, I>)>; + /// The number of blocks between candidate/membership rotation periods. + #[pallet::constant] + type RotationPeriod: Get<Self::BlockNumber>; - /// The ongoing number of losing votes cast by the member. - Strikes: map hasher(twox_64_concat) T::AccountId => StrikeCount; + /// The maximum duration of the payout lock. + #[pallet::constant] + type MaxLockDuration: Get<Self::BlockNumber>; - /// Double map from Candidate -> Voter -> (Maybe) Vote. - Votes: double_map - hasher(twox_64_concat) T::AccountId, - hasher(twox_64_concat) T::AccountId - => Option<Vote>; + /// The origin that is allowed to call `found`. + type FounderSetOrigin: EnsureOrigin<Self::Origin>; - /// The defending member currently being challenged. - Defender get(fn defender): Option<T::AccountId>; + /// The origin that is allowed to make suspension judgements. + type SuspensionJudgementOrigin: EnsureOrigin<Self::Origin>; - /// Votes for the defender. - DefenderVotes: map hasher(twox_64_concat) T::AccountId => Option<Vote>; + /// The number of blocks between membership challenges. + #[pallet::constant] + type ChallengePeriod: Get<Self::BlockNumber>; - /// The max number of members for the society at one time. - MaxMembers get(fn max_members) config(): u32; + /// The maximum number of candidates that we accept per round. + #[pallet::constant] + type MaxCandidateIntake: Get<u32>; + } + + #[pallet::error] + pub enum Error<T, I = ()> { + /// An incorrect position was provided. + BadPosition, + /// User is not a member. + NotMember, + /// User is already a member. + AlreadyMember, + /// User is suspended. + Suspended, + /// User is not suspended. + NotSuspended, + /// Nothing to payout. + NoPayout, + /// Society already founded. + AlreadyFounded, + /// Not enough in pot to accept candidate. + InsufficientPot, + /// Member is already vouching or banned from vouching again. + AlreadyVouching, + /// Member is not vouching. + NotVouching, + /// Cannot remove the head of the chain. + Head, + /// Cannot remove the founder. + Founder, + /// User has already made a bid. + AlreadyBid, + /// User is already a candidate. + AlreadyCandidate, + /// User is not a candidate. + NotCandidate, + /// Too many members in the society. + MaxMembers, + /// The caller is not the founder. + NotFounder, + /// The caller is not the head. + NotHead, } - add_extra_genesis { - config(members): Vec<T::AccountId>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event<T: Config<I>, I: 'static = ()> { + /// The society is founded by the given identity. \[founder\] + Founded(T::AccountId), + /// A membership bid just happened. The given account is the candidate's ID and their offer + /// is the second. \[candidate_id, offer\] + Bid(T::AccountId, BalanceOf<T, I>), + /// A membership bid just happened by vouching. The given account is the candidate's ID and + /// their offer is the second. The vouching party is the third. \[candidate_id, offer, + /// vouching\] + Vouch(T::AccountId, BalanceOf<T, I>, T::AccountId), + /// A \[candidate\] was dropped (due to an excess of bids in the system). + AutoUnbid(T::AccountId), + /// A \[candidate\] was dropped (by their request). + Unbid(T::AccountId), + /// A \[candidate\] was dropped (by request of who vouched for them). + Unvouch(T::AccountId), + /// A group of candidates have been inducted. The batch's primary is the first value, the + /// batch in full is the second. \[primary, candidates\] + Inducted(T::AccountId, Vec<T::AccountId>), + /// A suspended member has been judged. \[who, judged\] + SuspendedMemberJudgement(T::AccountId, bool), + /// A \[candidate\] has been suspended + CandidateSuspended(T::AccountId), + /// A \[member\] has been suspended + MemberSuspended(T::AccountId), + /// A \[member\] has been challenged + Challenged(T::AccountId), + /// A vote has been placed \[candidate, voter, vote\] + Vote(T::AccountId, T::AccountId, bool), + /// A vote has been placed for a defending member \[voter, vote\] + DefenderVote(T::AccountId, bool), + /// A new \[max\] member count has been set + NewMaxMembers(u32), + /// Society is unfounded. \[founder\] + Unfounded(T::AccountId), + /// Some funds were deposited into the society account. \[value\] + Deposit(BalanceOf<T, I>), } -} -// The module's dispatchable functions. -decl_module! { - /// The module declaration. - pub struct Module<T: Config<I>, I: Instance=DefaultInstance> for enum Call where origin: T::Origin { - type Error = Error<T, I>; - /// The minimum amount of a deposit required for a bid to be made. - const CandidateDeposit: BalanceOf<T, I> = T::CandidateDeposit::get(); + /// Old name generated by `decl_event`. + #[deprecated(note = "use `Event` instead")] + pub type RawEvent<T, I = ()> = Event<T, I>; + + /// The first member. + #[pallet::storage] + #[pallet::getter(fn founder)] + pub type Founder<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId>; + + /// A hash of the rules of this society concerning membership. Can only be set once and + /// only by the founder. + #[pallet::storage] + #[pallet::getter(fn rules)] + pub type Rules<T: Config<I>, I: 'static = ()> = StorageValue<_, T::Hash>; + + /// The current set of candidates; bidders that are attempting to become members. + #[pallet::storage] + #[pallet::getter(fn candidates)] + pub type Candidates<T: Config<I>, I: 'static = ()> = + StorageValue<_, Vec<Bid<T::AccountId, BalanceOf<T, I>>>, ValueQuery>; + + /// The set of suspended candidates. + #[pallet::storage] + #[pallet::getter(fn suspended_candidate)] + pub type SuspendedCandidates<T: Config<I>, I: 'static = ()> = StorageMap< + _, + Twox64Concat, + T::AccountId, + (BalanceOf<T, I>, BidKind<T::AccountId, BalanceOf<T, I>>), + >; + + /// Amount of our account balance that is specifically for the next round's bid(s). + #[pallet::storage] + #[pallet::getter(fn pot)] + pub type Pot<T: Config<I>, I: 'static = ()> = StorageValue<_, BalanceOf<T, I>, ValueQuery>; + + /// The most primary from the most recently approved members. + #[pallet::storage] + #[pallet::getter(fn head)] + pub type Head<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId>; + + /// The current set of members, ordered. + #[pallet::storage] + #[pallet::getter(fn members)] + pub type Members<T: Config<I>, I: 'static = ()> = + StorageValue<_, Vec<T::AccountId>, ValueQuery>; + + /// The set of suspended members. + #[pallet::storage] + #[pallet::getter(fn suspended_member)] + pub type SuspendedMembers<T: Config<I>, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, bool, ValueQuery>; + + /// The current bids, stored ordered by the value of the bid. + #[pallet::storage] + pub(super) type Bids<T: Config<I>, I: 'static = ()> = + StorageValue<_, Vec<Bid<T::AccountId, BalanceOf<T, I>>>, ValueQuery>; + + /// Members currently vouching or banned from vouching again + #[pallet::storage] + #[pallet::getter(fn vouching)] + pub(super) type Vouching<T: Config<I>, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, VouchingStatus>; + + /// Pending payouts; ordered by block number, with the amount that should be paid out. + #[pallet::storage] + pub(super) type Payouts<T: Config<I>, I: 'static = ()> = StorageMap< + _, + Twox64Concat, + T::AccountId, + Vec<(T::BlockNumber, BalanceOf<T, I>)>, + ValueQuery, + >; + + /// The ongoing number of losing votes cast by the member. + #[pallet::storage] + pub(super) type Strikes<T: Config<I>, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, StrikeCount, ValueQuery>; + + /// Double map from Candidate -> Voter -> (Maybe) Vote. + #[pallet::storage] + pub(super) type Votes<T: Config<I>, I: 'static = ()> = + StorageDoubleMap<_, Twox64Concat, T::AccountId, Twox64Concat, T::AccountId, Vote>; + + /// The defending member currently being challenged. + #[pallet::storage] + #[pallet::getter(fn defender)] + pub(super) type Defender<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId>; + + /// Votes for the defender. + #[pallet::storage] + pub(super) type DefenderVotes<T: Config<I>, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, Vote>; + + /// The max number of members for the society at one time. + #[pallet::storage] + #[pallet::getter(fn max_members)] + pub(super) type MaxMembers<T: Config<I>, I: 'static = ()> = StorageValue<_, u32, ValueQuery>; + + #[pallet::hooks] + impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> { + fn on_initialize(n: T::BlockNumber) -> Weight { + let mut members = vec![]; - /// The amount of the unpaid reward that gets deducted in the case that either a skeptic - /// doesn't vote or someone votes in the wrong way. - const WrongSideDeduction: BalanceOf<T, I> = T::WrongSideDeduction::get(); + let mut weight = 0; + let weights = T::BlockWeights::get(); - /// The number of times a member may vote the wrong way (or not at all, when they are a skeptic) - /// before they become suspended. - const MaxStrikes: u32 = T::MaxStrikes::get(); + // Run a candidate/membership rotation + if (n % T::RotationPeriod::get()).is_zero() { + members = <Members<T, I>>::get(); + Self::rotate_period(&mut members); - /// The amount of incentive paid within each period. Doesn't include VoterTip. - const PeriodSpend: BalanceOf<T, I> = T::PeriodSpend::get(); + weight += weights.max_block / 20; + } - /// The number of blocks between candidate/membership rotation periods. - const RotationPeriod: T::BlockNumber = T::RotationPeriod::get(); + // Run a challenge rotation + if (n % T::ChallengePeriod::get()).is_zero() { + // Only read members if not already read. + if members.is_empty() { + members = <Members<T, I>>::get(); + } + Self::rotate_challenge(&mut members); - /// The number of blocks between membership challenges. - const ChallengePeriod: T::BlockNumber = T::ChallengePeriod::get(); + weight += weights.max_block / 20; + } - /// The societies's module id - const PalletId: PalletId = T::PalletId::get(); + weight + } + } + + #[pallet::genesis_config] + pub struct GenesisConfig<T: Config<I>, I: 'static = ()> { + pub pot: BalanceOf<T, I>, + pub members: Vec<T::AccountId>, + pub max_members: u32, + } - /// Maximum candidate intake per round. - const MaxCandidateIntake: u32 = T::MaxCandidateIntake::get(); + #[cfg(feature = "std")] + impl<T: Config<I>, I: 'static> Default for GenesisConfig<T, I> { + fn default() -> Self { + Self { + pot: Default::default(), + members: Default::default(), + max_members: Default::default(), + } + } + } - // Used for handling module events. - fn deposit_event() = default; + #[pallet::genesis_build] + impl<T: Config<I>, I: 'static> GenesisBuild<T, I> for GenesisConfig<T, I> { + fn build(&self) { + Pot::<T, I>::put(self.pot); + MaxMembers::<T, I>::put(self.max_members); + let first_member = self.members.first(); + if let Some(member) = first_member { + Founder::<T, I>::put(member.clone()); + Head::<T, I>::put(member.clone()); + }; + let mut m = self.members.clone(); + m.sort(); + Members::<T, I>::put(m); + } + } + #[pallet::call] + impl<T: Config<I>, I: 'static> Pallet<T, I> { /// A user outside of the society can make a bid for entry. /// /// Payment: `CandidateDeposit` will be reserved for making a bid. It is returned @@ -538,12 +698,13 @@ decl_module! { /// - One storage read to retrieve all current candidates. O(C) /// - One storage read to retrieve all members. O(M) /// - Storage Writes: - /// - One storage mutate to add a new bid to the vector O(B) (TODO: possible optimization w/ read) + /// - One storage mutate to add a new bid to the vector O(B) (TODO: possible optimization + /// w/ read) /// - Up to one storage removal if bid.len() > MAX_BID_COUNT. O(1) /// - Notable Computation: /// - O(B + C + log M) search to check user is not already a part of society. /// - O(log B) search to insert the new bid sorted. - /// - External Module Operations: + /// - External Pallet Operations: /// - One balance reserve operation. O(X) /// - Up to one balance unreserve operation if bids.len() > MAX_BID_COUNT. /// - Events: @@ -552,8 +713,8 @@ decl_module! { /// /// Total Complexity: O(M + B + C + logM + logB + X) /// # </weight> - #[weight = T::BlockWeights::get().max_block / 10] - pub fn bid(origin, value: BalanceOf<T, I>) -> DispatchResult { + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn bid(origin: OriginFor<T>, value: BalanceOf<T, I>) -> DispatchResult { let who = ensure_signed(origin)?; ensure!(!<SuspendedCandidates<T, I>>::contains_key(&who), Error::<T, I>::Suspended); ensure!(!<SuspendedMembers<T, I>>::contains_key(&who), Error::<T, I>::Suspended); @@ -562,13 +723,13 @@ decl_module! { let candidates = <Candidates<T, I>>::get(); ensure!(!Self::is_candidate(&candidates, &who), Error::<T, I>::AlreadyCandidate); let members = <Members<T, I>>::get(); - ensure!(!Self::is_member(&members ,&who), Error::<T, I>::AlreadyMember); + ensure!(!Self::is_member(&members, &who), Error::<T, I>::AlreadyMember); let deposit = T::CandidateDeposit::get(); T::Currency::reserve(&who, deposit)?; Self::put_bid(bids, &who, value.clone(), BidKind::Deposit(deposit)); - Self::deposit_event(RawEvent::Bid(who, value)); + Self::deposit_event(Event::<T, I>::Bid(who, value)); Ok(()) } @@ -591,12 +752,12 @@ decl_module! { /// /// Total Complexity: O(B + X) /// # </weight> - #[weight = T::BlockWeights::get().max_block / 10] - pub fn unbid(origin, pos: u32) -> DispatchResult { + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn unbid(origin: OriginFor<T>, pos: u32) -> DispatchResult { let who = ensure_signed(origin)?; let pos = pos as usize; - <Bids<T, I>>::mutate(|b| + <Bids<T, I>>::mutate(|b| { if pos < b.len() && b[pos].who == who { // Either unreserve the deposit or free up the vouching member. // In neither case can we do much if the action isn't completable, but there's @@ -605,17 +766,17 @@ decl_module! { BidKind::Deposit(deposit) => { let err_amount = T::Currency::unreserve(&who, deposit); debug_assert!(err_amount.is_zero()); - } + }, BidKind::Vouch(voucher, _) => { <Vouching<T, I>>::remove(&voucher); - } + }, } - Self::deposit_event(RawEvent::Unbid(who)); + Self::deposit_event(Event::<T, I>::Unbid(who)); Ok(()) } else { Err(Error::<T, I>::BadPosition)? } - ) + }) } /// As a member, vouch for someone to join society by placing a bid on their behalf. @@ -647,13 +808,14 @@ decl_module! { /// - One storage read to retrieve all current candidates. O(C) /// - Storage Writes: /// - One storage write to insert vouching status to the member. O(1) - /// - One storage mutate to add a new bid to the vector O(B) (TODO: possible optimization w/ read) + /// - One storage mutate to add a new bid to the vector O(B) (TODO: possible optimization + /// w/ read) /// - Up to one storage removal if bid.len() > MAX_BID_COUNT. O(1) /// - Notable Computation: /// - O(log M) search to check sender is a member. /// - O(B + C + log M) search to check user is not already a part of society. /// - O(log B) search to insert the new bid sorted. - /// - External Module Operations: + /// - External Pallet Operations: /// - One balance reserve operation. O(X) /// - Up to one balance unreserve operation if bids.len() > MAX_BID_COUNT. /// - Events: @@ -662,8 +824,13 @@ decl_module! { /// /// Total Complexity: O(M + B + C + logM + logB + X) /// # </weight> - #[weight = T::BlockWeights::get().max_block / 10] - pub fn vouch(origin, who: T::AccountId, value: BalanceOf<T, I>, tip: BalanceOf<T, I>) -> DispatchResult { + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn vouch( + origin: OriginFor<T>, + who: T::AccountId, + value: BalanceOf<T, I>, + tip: BalanceOf<T, I>, + ) -> DispatchResult { let voucher = ensure_signed(origin)?; // Check user is not suspended. ensure!(!<SuspendedCandidates<T, I>>::contains_key(&who), Error::<T, I>::Suspended); @@ -682,7 +849,7 @@ decl_module! { <Vouching<T, I>>::insert(&voucher, VouchingStatus::Vouching); Self::put_bid(bids, &who, value.clone(), BidKind::Vouch(voucher.clone(), tip)); - Self::deposit_event(RawEvent::Vouch(who, value, voucher)); + Self::deposit_event(Event::<T, I>::Vouch(who, value, voucher)); Ok(()) } @@ -703,23 +870,26 @@ decl_module! { /// /// Total Complexity: O(B) /// # </weight> - #[weight = T::BlockWeights::get().max_block / 10] - pub fn unvouch(origin, pos: u32) -> DispatchResult { + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn unvouch(origin: OriginFor<T>, pos: u32) -> DispatchResult { let voucher = ensure_signed(origin)?; - ensure!(Self::vouching(&voucher) == Some(VouchingStatus::Vouching), Error::<T, I>::NotVouching); + ensure!( + Self::vouching(&voucher) == Some(VouchingStatus::Vouching), + Error::<T, I>::NotVouching + ); let pos = pos as usize; - <Bids<T, I>>::mutate(|b| + <Bids<T, I>>::mutate(|b| { if pos < b.len() { b[pos].kind.check_voucher(&voucher)?; <Vouching<T, I>>::remove(&voucher); let who = b.remove(pos).who; - Self::deposit_event(RawEvent::Unvouch(who)); + Self::deposit_event(Event::<T, I>::Unvouch(who)); Ok(()) } else { Err(Error::<T, I>::BadPosition)? } - ) + }) } /// As a member, vote on a candidate. @@ -728,8 +898,8 @@ decl_module! { /// /// Parameters: /// - `candidate`: The candidate that the member would like to bid on. - /// - `approve`: A boolean which says if the candidate should be - /// approved (`true`) or rejected (`false`). + /// - `approve`: A boolean which says if the candidate should be approved (`true`) or + /// rejected (`false`). /// /// # <weight> /// Key: C (len of candidates), M (len of members) @@ -741,8 +911,12 @@ decl_module! { /// /// Total Complexity: O(M + logM + C) /// # </weight> - #[weight = T::BlockWeights::get().max_block / 10] - pub fn vote(origin, candidate: <T::Lookup as StaticLookup>::Source, approve: bool) { + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn vote( + origin: OriginFor<T>, + candidate: <T::Lookup as StaticLookup>::Source, + approve: bool, + ) -> DispatchResult { let voter = ensure_signed(origin)?; let candidate = T::Lookup::lookup(candidate)?; let candidates = <Candidates<T, I>>::get(); @@ -753,7 +927,8 @@ decl_module! { let vote = if approve { Vote::Approve } else { Vote::Reject }; <Votes<T, I>>::insert(&candidate, &voter, vote); - Self::deposit_event(RawEvent::Vote(candidate, voter, approve)); + Self::deposit_event(Event::<T, I>::Vote(candidate, voter, approve)); + Ok(()) } /// As a member, vote on the defender. @@ -772,8 +947,8 @@ decl_module! { /// /// Total Complexity: O(M + logM) /// # </weight> - #[weight = T::BlockWeights::get().max_block / 10] - pub fn defender_vote(origin, approve: bool) { + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn defender_vote(origin: OriginFor<T>, approve: bool) -> DispatchResult { let voter = ensure_signed(origin)?; let members = <Members<T, I>>::get(); ensure!(Self::is_member(&members, &voter), Error::<T, I>::NotMember); @@ -781,12 +956,14 @@ decl_module! { let vote = if approve { Vote::Approve } else { Vote::Reject }; <DefenderVotes<T, I>>::insert(&voter, vote); - Self::deposit_event(RawEvent::DefenderVote(voter, approve)); + Self::deposit_event(Event::<T, I>::DefenderVote(voter, approve)); + Ok(()) } /// Transfer the first matured payout for the sender and remove it from the records. /// - /// NOTE: This extrinsic needs to be called multiple times to claim multiple matured payouts. + /// NOTE: This extrinsic needs to be called multiple times to claim multiple matured + /// payouts. /// /// Payment: The member will receive a payment equal to their first matured /// payout to their free balance. @@ -804,8 +981,8 @@ decl_module! { /// /// Total Complexity: O(M + logM + P + X) /// # </weight> - #[weight = T::BlockWeights::get().max_block / 10] - pub fn payout(origin) { + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn payout(origin: OriginFor<T>) -> DispatchResult { let who = ensure_signed(origin)?; let members = <Members<T, I>>::get(); @@ -813,7 +990,7 @@ decl_module! { let mut payouts = <Payouts<T, I>>::get(&who); if let Some((when, amount)) = payouts.first() { - if when <= &<system::Pallet<T>>::block_number() { + if when <= &<frame_system::Pallet<T>>::block_number() { T::Currency::transfer(&Self::payouts(), &who, *amount, AllowDeath)?; payouts.remove(0); if payouts.is_empty() { @@ -830,7 +1007,7 @@ decl_module! { /// Found the society. /// /// This is done as a discrete action in order to allow for the - /// module to be included into a running chain and can only be done once. + /// pallet to be included into a running chain and can only be done once. /// /// The dispatch origin for this call must be from the _FounderSetOrigin_. /// @@ -846,18 +1023,24 @@ decl_module! { /// /// Total Complexity: O(1) /// # </weight> - #[weight = T::BlockWeights::get().max_block / 10] - fn found(origin, founder: T::AccountId, max_members: u32, rules: Vec<u8>) { + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn found( + origin: OriginFor<T>, + founder: T::AccountId, + max_members: u32, + rules: Vec<u8>, + ) -> DispatchResult { T::FounderSetOrigin::ensure_origin(origin)?; ensure!(!<Head<T, I>>::exists(), Error::<T, I>::AlreadyFounded); ensure!(max_members > 1, Error::<T, I>::MaxMembers); // This should never fail in the context of this function... - <MaxMembers<I>>::put(max_members); + <MaxMembers<T, I>>::put(max_members); Self::add_member(&founder)?; <Head<T, I>>::put(&founder); <Founder<T, I>>::put(&founder); Rules::<T, I>::put(T::Hashing::hash(&rules)); - Self::deposit_event(RawEvent::Founded(founder)); + Self::deposit_event(Event::<T, I>::Founded(founder)); + Ok(()) } /// Annul the founding of the society. @@ -873,8 +1056,8 @@ decl_module! { /// /// Total Complexity: O(1) /// # </weight> - #[weight = T::BlockWeights::get().max_block / 10] - fn unfound(origin) { + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn unfound(origin: OriginFor<T>) -> DispatchResult { let founder = ensure_signed(origin)?; ensure!(Founder::<T, I>::get() == Some(founder.clone()), Error::<T, I>::NotFounder); ensure!(Head::<T, I>::get() == Some(founder.clone()), Error::<T, I>::NotHead); @@ -885,7 +1068,8 @@ decl_module! { Rules::<T, I>::kill(); Candidates::<T, I>::kill(); SuspendedCandidates::<T, I>::remove_all(None); - Self::deposit_event(RawEvent::Unfounded(founder)); + Self::deposit_event(Event::<T, I>::Unfounded(founder)); + Ok(()) } /// Allow suspension judgement origin to make judgement on a suspended member. @@ -900,13 +1084,14 @@ decl_module! { /// /// Parameters: /// - `who` - The suspended member to be judged. - /// - `forgive` - A boolean representing whether the suspension judgement origin - /// forgives (`true`) or rejects (`false`) a suspended member. + /// - `forgive` - A boolean representing whether the suspension judgement origin forgives + /// (`true`) or rejects (`false`) a suspended member. /// /// # <weight> /// Key: B (len of bids), M (len of members) /// - One storage read to check `who` is a suspended member. O(1) - /// - Up to one storage write O(M) with O(log M) binary search to add a member back to society. + /// - Up to one storage write O(M) with O(log M) binary search to add a member back to + /// society. /// - Up to 3 storage removals O(1) to clean up a removed member. /// - Up to one storage write O(B) with O(B) search to remove vouched bid from bids. /// - Up to one additional event if unvouch takes place. @@ -915,8 +1100,12 @@ decl_module! { /// /// Total Complexity: O(M + logM + B) /// # </weight> - #[weight = T::BlockWeights::get().max_block / 10] - fn judge_suspended_member(origin, who: T::AccountId, forgive: bool) { + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn judge_suspended_member( + origin: OriginFor<T>, + who: T::AccountId, + forgive: bool, + ) -> DispatchResult { T::SuspensionJudgementOrigin::ensure_origin(origin)?; ensure!(<SuspendedMembers<T, I>>::contains_key(&who), Error::<T, I>::NotSuspended); @@ -936,14 +1125,15 @@ decl_module! { if let Some(pos) = bids.iter().position(|b| b.kind.check_voucher(&who).is_ok()) { // Remove the bid, and emit an event let vouched = bids.remove(pos).who; - Self::deposit_event(RawEvent::Unvouch(vouched)); + Self::deposit_event(Event::<T, I>::Unvouch(vouched)); } ); } } <SuspendedMembers<T, I>>::remove(&who); - Self::deposit_event(RawEvent::SuspendedMemberJudgement(who, forgive)); + Self::deposit_event(Event::<T, I>::SuspendedMemberJudgement(who, forgive)); + Ok(()) } /// Allow suspended judgement origin to make judgement on a suspended candidate. @@ -986,8 +1176,12 @@ decl_module! { /// /// Total Complexity: O(M + logM + B + X) /// # </weight> - #[weight = T::BlockWeights::get().max_block / 10] - fn judge_suspended_candidate(origin, who: T::AccountId, judgement: Judgement) { + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn judge_suspended_candidate( + origin: OriginFor<T>, + who: T::AccountId, + judgement: Judgement, + ) -> DispatchResult { T::SuspensionJudgementOrigin::ensure_origin(origin)?; if let Some((value, kind)) = <SuspendedCandidates<T, I>>::get(&who) { match judgement { @@ -1001,29 +1195,35 @@ decl_module! { // Reduce next pot by payout <Pot<T, I>>::put(pot - value); // Add payout for new candidate - let maturity = <system::Pallet<T>>::block_number() - + Self::lock_duration(Self::members().len() as u32); + let maturity = <frame_system::Pallet<T>>::block_number() + + Self::lock_duration(Self::members().len() as u32); Self::pay_accepted_candidate(&who, value, kind, maturity); - } + }, Judgement::Reject => { // Founder has rejected this candidate match kind { BidKind::Deposit(deposit) => { // Slash deposit and move it to the society account - let res = T::Currency::repatriate_reserved(&who, &Self::account_id(), deposit, BalanceStatus::Free); + let res = T::Currency::repatriate_reserved( + &who, + &Self::account_id(), + deposit, + BalanceStatus::Free, + ); debug_assert!(res.is_ok()); - } + }, BidKind::Vouch(voucher, _) => { // Ban the voucher from vouching again <Vouching<T, I>>::insert(&voucher, VouchingStatus::Banned); - } + }, } - } + }, Judgement::Rebid => { - // Founder has taken no judgement, and candidate is placed back into the pool. + // Founder has taken no judgement, and candidate is placed back into the + // pool. let bids = <Bids<T, I>>::get(); Self::put_bid(bids, &who, value, kind); - } + }, } // Remove suspended candidate @@ -1031,6 +1231,7 @@ decl_module! { } else { Err(Error::<T, I>::NotSuspended)? } + Ok(()) } /// Allows root origin to change the maximum number of members in society. @@ -1047,137 +1248,24 @@ decl_module! { /// /// Total Complexity: O(1) /// # </weight> - #[weight = T::BlockWeights::get().max_block / 10] - fn set_max_members(origin, max: u32) { + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn set_max_members(origin: OriginFor<T>, max: u32) -> DispatchResult { ensure_root(origin)?; ensure!(max > 1, Error::<T, I>::MaxMembers); - MaxMembers::<I>::put(max); - Self::deposit_event(RawEvent::NewMaxMembers(max)); - } - - fn on_initialize(n: T::BlockNumber) -> Weight { - let mut members = vec![]; - - let mut weight = 0; - let weights = T::BlockWeights::get(); - - // Run a candidate/membership rotation - if (n % T::RotationPeriod::get()).is_zero() { - members = <Members<T, I>>::get(); - Self::rotate_period(&mut members); - - weight += weights.max_block / 20; - } - - // Run a challenge rotation - if (n % T::ChallengePeriod::get()).is_zero() { - // Only read members if not already read. - if members.is_empty() { - members = <Members<T, I>>::get(); - } - Self::rotate_challenge(&mut members); - - weight += weights.max_block / 20; - } - - weight + MaxMembers::<T, I>::put(max); + Self::deposit_event(Event::<T, I>::NewMaxMembers(max)); + Ok(()) } } } -decl_error! { - /// Errors for this module. - pub enum Error for Module<T: Config<I>, I: Instance> { - /// An incorrect position was provided. - BadPosition, - /// User is not a member. - NotMember, - /// User is already a member. - AlreadyMember, - /// User is suspended. - Suspended, - /// User is not suspended. - NotSuspended, - /// Nothing to payout. - NoPayout, - /// Society already founded. - AlreadyFounded, - /// Not enough in pot to accept candidate. - InsufficientPot, - /// Member is already vouching or banned from vouching again. - AlreadyVouching, - /// Member is not vouching. - NotVouching, - /// Cannot remove the head of the chain. - Head, - /// Cannot remove the founder. - Founder, - /// User has already made a bid. - AlreadyBid, - /// User is already a candidate. - AlreadyCandidate, - /// User is not a candidate. - NotCandidate, - /// Too many members in the society. - MaxMembers, - /// The caller is not the founder. - NotFounder, - /// The caller is not the head. - NotHead, - } -} - -decl_event! { - /// Events for this module. - pub enum Event<T, I=DefaultInstance> where - AccountId = <T as system::Config>::AccountId, - Balance = BalanceOf<T, I> - { - /// The society is founded by the given identity. \[founder\] - Founded(AccountId), - /// A membership bid just happened. The given account is the candidate's ID and their offer - /// is the second. \[candidate_id, offer\] - Bid(AccountId, Balance), - /// A membership bid just happened by vouching. The given account is the candidate's ID and - /// their offer is the second. The vouching party is the third. \[candidate_id, offer, vouching\] - Vouch(AccountId, Balance, AccountId), - /// A \[candidate\] was dropped (due to an excess of bids in the system). - AutoUnbid(AccountId), - /// A \[candidate\] was dropped (by their request). - Unbid(AccountId), - /// A \[candidate\] was dropped (by request of who vouched for them). - Unvouch(AccountId), - /// A group of candidates have been inducted. The batch's primary is the first value, the - /// batch in full is the second. \[primary, candidates\] - Inducted(AccountId, Vec<AccountId>), - /// A suspended member has been judged. \[who, judged\] - SuspendedMemberJudgement(AccountId, bool), - /// A \[candidate\] has been suspended - CandidateSuspended(AccountId), - /// A \[member\] has been suspended - MemberSuspended(AccountId), - /// A \[member\] has been challenged - Challenged(AccountId), - /// A vote has been placed \[candidate, voter, vote\] - Vote(AccountId, AccountId, bool), - /// A vote has been placed for a defending member \[voter, vote\] - DefenderVote(AccountId, bool), - /// A new \[max\] member count has been set - NewMaxMembers(u32), - /// Society is unfounded. \[founder\] - Unfounded(AccountId), - /// Some funds were deposited into the society account. \[value\] - Deposit(Balance), - } -} - /// Simple ensure origin struct to filter for the founder account. pub struct EnsureFounder<T>(sp_std::marker::PhantomData<T>); impl<T: Config> EnsureOrigin<T::Origin> for EnsureFounder<T> { type Success = T::AccountId; fn try_origin(o: T::Origin) -> Result<Self::Success, T::Origin> { o.into().and_then(|o| match (o, Founder::<T>::get()) { - (system::RawOrigin::Signed(ref who), Some(ref f)) if who == f => Ok(who.clone()), + (frame_system::RawOrigin::Signed(ref who), Some(ref f)) if who == f => Ok(who.clone()), (r, _) => Err(T::Origin::from(r)), }) } @@ -1185,7 +1273,7 @@ impl<T: Config> EnsureOrigin<T::Origin> for EnsureFounder<T> { #[cfg(feature = "runtime-benchmarks")] fn successful_origin() -> T::Origin { let founder = Founder::<T>::get().expect("society founder should exist"); - T::Origin::from(system::RawOrigin::Signed(founder)) + T::Origin::from(frame_system::RawOrigin::Signed(founder)) } } @@ -1203,7 +1291,7 @@ fn pick_usize<'a, R: RngCore>(rng: &mut R, max: usize) -> usize { (rng.next_u32() % (max as u32 + 1)) as usize } -impl<T: Config<I>, I: Instance> Module<T, I> { +impl<T: Config<I>, I: 'static> Pallet<T, I> { /// Puts a bid into storage ordered by smallest to largest value. /// Allows a maximum of 1000 bids in queue, removing largest value people first. fn put_bid( @@ -1251,7 +1339,7 @@ impl<T: Config<I>, I: Instance> Module<T, I> { <Vouching<T, I>>::remove(&voucher); }, } - Self::deposit_event(RawEvent::AutoUnbid(popped)); + Self::deposit_event(Event::<T, I>::AutoUnbid(popped)); } <Bids<T, I>>::put(bids); @@ -1281,7 +1369,7 @@ impl<T: Config<I>, I: Instance> Module<T, I> { /// Can fail when `MaxMember` limit is reached, but has no side-effects. fn add_member(who: &T::AccountId) -> DispatchResult { let mut members = <Members<T, I>>::get(); - ensure!(members.len() < MaxMembers::<I>::get() as usize, Error::<T, I>::MaxMembers); + ensure!(members.len() < MaxMembers::<T, I>::get() as usize, Error::<T, I>::MaxMembers); match members.binary_search(who) { // Add the new member Err(i) => { @@ -1338,8 +1426,8 @@ impl<T: Config<I>, I: Instance> Module<T, I> { // out of society. members.reserve(candidates.len()); - let maturity = - <system::Pallet<T>>::block_number() + Self::lock_duration(members.len() as u32); + let maturity = <frame_system::Pallet<T>>::block_number() + + Self::lock_duration(members.len() as u32); let mut rewardees = Vec::new(); let mut total_approvals = 0; @@ -1416,7 +1504,7 @@ impl<T: Config<I>, I: Instance> Module<T, I> { } else { // Suspend Candidate <SuspendedCandidates<T, I>>::insert(&candidate, (value, kind)); - Self::deposit_event(RawEvent::CandidateSuspended(candidate)); + Self::deposit_event(Event::<T, I>::CandidateSuspended(candidate)); None } }) @@ -1485,7 +1573,7 @@ impl<T: Config<I>, I: Instance> Module<T, I> { <Head<T, I>>::put(&primary); T::MembershipChanged::change_members_sorted(&accounts, &[], &members); - Self::deposit_event(RawEvent::Inducted(primary, accounts)); + Self::deposit_event(Event::<T, I>::Inducted(primary, accounts)); } // Bump the pot by at most PeriodSpend, but less if there's not very much left in our @@ -1550,7 +1638,7 @@ impl<T: Config<I>, I: Instance> Module<T, I> { if Self::remove_member(&who).is_ok() { <SuspendedMembers<T, I>>::insert(who, true); <Strikes<T, I>>::remove(who); - Self::deposit_event(RawEvent::MemberSuspended(who.clone())); + Self::deposit_event(Event::<T, I>::MemberSuspended(who.clone())); } } @@ -1628,7 +1716,7 @@ impl<T: Config<I>, I: Instance> Module<T, I> { let chosen = pick_item(&mut rng, &members[1..members.len() - 1]) .expect("exited if members empty; qed"); <Defender<T, I>>::put(&chosen); - Self::deposit_event(RawEvent::Challenged(chosen.clone())); + Self::deposit_event(Event::<T, I>::Challenged(chosen.clone())); } else { <Defender<T, I>>::kill(); } @@ -1668,7 +1756,7 @@ impl<T: Config<I>, I: Instance> Module<T, I> { members_len: usize, pot: BalanceOf<T, I>, ) -> Vec<Bid<T::AccountId, BalanceOf<T, I>>> { - let max_members = MaxMembers::<I>::get() as usize; + let max_members = MaxMembers::<T, I>::get() as usize; let mut max_selections: usize = (T::MaxCandidateIntake::get() as usize).min(max_members.saturating_sub(members_len)); @@ -1725,13 +1813,13 @@ impl<T: Config<I>, I: Instance> Module<T, I> { } } -impl<T: Config> OnUnbalanced<NegativeImbalanceOf<T>> for Module<T> { - fn on_nonzero_unbalanced(amount: NegativeImbalanceOf<T>) { +impl<T: Config<I>, I: 'static> OnUnbalanced<NegativeImbalanceOf<T, I>> for Pallet<T, I> { + fn on_nonzero_unbalanced(amount: NegativeImbalanceOf<T, I>) { let numeric_amount = amount.peek(); // Must resolve into existing but better to be safe. let _ = T::Currency::resolve_creating(&Self::account_id(), amount); - Self::deposit_event(RawEvent::Deposit(numeric_amount)); + Self::deposit_event(Event::<T, I>::Deposit(numeric_amount)); } } diff --git a/substrate/frame/society/src/mock.rs b/substrate/frame/society/src/mock.rs index 38c2586323135bf9570032991d4be2512bc5b305..9356c083f2331124faffd69d503100c78ffe1b85 100644 --- a/substrate/frame/society/src/mock.rs +++ b/substrate/frame/society/src/mock.rs @@ -20,10 +20,7 @@ use super::*; use crate as pallet_society; -use frame_support::{ - ord_parameter_types, parameter_types, - traits::{OnFinalize, OnInitialize}, -}; +use frame_support::{ord_parameter_types, parameter_types}; use frame_support_test::TestRandomness; use frame_system::EnsureSignedBy; use sp_core::H256;