From 62dc0d7403b06cfc67e6b11a6ca1f64469541b89 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 30 Apr 2022 12:34:22 +0200 Subject: [PATCH 01/43] New Society --- bin/node/runtime/src/lib.rs | 2 +- frame/society/src/lib.rs | 1575 +++++++++++----------- frame/society/src/mock.rs | 2 +- frame/support/src/storage/bounded_vec.rs | 25 +- frame/support/src/storage/types/map.rs | 16 + frame/system/src/lib.rs | 4 +- primitives/core/src/ecdsa.rs | 8 + primitives/core/src/ed25519.rs | 8 + primitives/core/src/sr25519.rs | 10 +- primitives/core/src/traits.rs | 7 + primitives/runtime/src/lib.rs | 13 +- 11 files changed, 868 insertions(+), 802 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 18ce64c29f8e8..fcdf5fc8cdd01 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1280,7 +1280,7 @@ impl pallet_society::Config for Runtime { type MaxLockDuration = MaxLockDuration; type FounderSetOrigin = pallet_collective::EnsureProportionMoreThan; - type SuspensionJudgementOrigin = pallet_society::EnsureFounder; + type JudgementOrigin = pallet_society::EnsureFounder; type MaxCandidateIntake = MaxCandidateIntake; type ChallengePeriod = ChallengePeriod; } diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 6ad63d2102c53..b18140fc989e8 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -242,6 +242,9 @@ //! * `set_max_membership` - The ROOT origin can update the maximum member count for the society. //! The max membership count must be greater than 1. +// TODO: Sort out all the `limit: None` stuff for remove prefix. +// TODO: Membership subsets: ranks and badges. + // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] @@ -267,10 +270,10 @@ use rand_chacha::{ use scale_info::TypeInfo; use sp_runtime::{ traits::{ - AccountIdConversion, CheckedSub, Hash, IntegerSquareRoot, Saturating, StaticLookup, - TrailingZeroInput, Zero, + AccountIdConversion, CheckedAdd, CheckedSub, Hash, IntegerSquareRoot, Saturating, + StaticLookup, TrailingZeroInput, Zero, }, - Percent, RuntimeDebug, + ArithmeticError::Overflow, Percent, RuntimeDebug, }; use sp_std::prelude::*; @@ -284,7 +287,7 @@ type NegativeImbalanceOf = <>::Currency as Currency< /// A vote by a member on a candidate application. #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub enum Vote { +pub enum OldVote { /// The member has been chosen to be skeptic and has not yet taken any action. Skeptic, /// The member has rejected the candidate's application. @@ -293,6 +296,11 @@ pub enum Vote { Approve, } +pub struct Vote { + approve: bool, + weight: u32, +} + /// A judgement by the suspension judgement origin on a suspended candidate. #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub enum Judgement { @@ -341,6 +349,27 @@ pub struct Bid { value: Balance, } +/// The index of a round of candidates. +pub type RoundIndex = u32; + +/// The rank of a member. +pub type Rank = u32; + +/// A bid for entry into society. +#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct Candidacy { + /// The index of the round where the candidacy began. + round: RoundIndex, + /// The kind of bid placed for this bidder/candidate. See `BidKind`. + kind: BidKind, + /// The reward that the bidder has requested for successfully joining the society. + bid: Balance, + /// The tally of explicit approval votes so far. + approvals: u32, + /// The tally of explicit rejection votes so far. + rejections: u32, +} + /// A vote by a member on a candidate application. #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub enum BidKind { @@ -365,6 +394,41 @@ impl BidKind { } } +pub type PayoutsFor = BoundedVec< + (::BlockNumber, BalanceOf), + >::MaxPayouts, +>; + +/// Information concerning a member. +#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct MemberRecord { + rank: Rank, + strikes: StrikeCount, + vouching: Option, + paid: Balance, + index: u32, + payouts: BoundedVec<(BlockNumber, Balance), MaxPayouts>, +} + +pub type MemberRecordFor = MemberRecord< + BalanceOf, + ::BlockNumber, + >::MaxPayouts, +>; + +/// Record for an individual new member who was elevated from a candidate recently. +#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct IntakeRecord { + who: AccountId, + bid: Balance, + round: RoundIndex, +} + +pub type IntakeRecordFor = IntakeRecord< + ::AccountId, + BalanceOf, +>; + #[frame_support::pallet] pub mod pallet { use super::*; @@ -398,21 +462,29 @@ pub mod pallet { #[pallet::constant] type WrongSideDeduction: 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. + /// The number of times a skeptic may not vote or vote the wrong way for a candidate before + /// they become suspended. #[pallet::constant] type MaxStrikes: Get; + /// The number of times a skeptic may not vote or vote the wrong way for a candidate before + /// they get any payouts slashed in half. + #[pallet::constant] + type GraceStrikes: Get; + /// The amount of incentive paid within each period. Doesn't include VoterTip. #[pallet::constant] type PeriodSpend: Get>; - /// The receiver of the signal for when the members have changed. - type MembershipChanged: ChangeMembers; + /// The number of blocks on which new candidates should be voted on. Together with + /// `ClaimPeriod`, this sums to the number of blocks between candidate intake periods. + #[pallet::constant] + type VotingPeriod: Get; - /// The number of blocks between candidate/membership rotation periods. + /// The number of blocks on which new candidates can claim their membership and be the + /// named head. #[pallet::constant] - type RotationPeriod: Get; + type ClaimPeriod: Get; /// The maximum duration of the payout lock. #[pallet::constant] @@ -422,7 +494,7 @@ pub mod pallet { type FounderSetOrigin: EnsureOrigin; /// The origin that is allowed to make suspension judgements. - type SuspensionJudgementOrigin: EnsureOrigin; + type JudgementOrigin: EnsureOrigin; /// The number of blocks between membership challenges. #[pallet::constant] @@ -431,6 +503,17 @@ pub mod pallet { /// The maximum number of candidates that we accept per round. #[pallet::constant] type MaxCandidateIntake: Get; + + /// The origin that is allowed to set the maximum number of members. + type AdminOrigin: Get; + + /// The maximum number of payouts a member may have waiting unclaimed. + #[pallet::constant] + type MaxPayouts: Get; + + /// The maximum number of bids at once. + #[pallet::constant] + type MaxBids: Get; } #[pallet::error] @@ -471,6 +554,20 @@ pub mod pallet { NotFounder, /// The caller is not the head. NotHead, + /// The membership cannot be claimed as the member does not (yet) have enough votes. + NotApproved, + /// The candidacy cannot be claimed/dropped as the voting is still in progress. + InProgress, + /// The candidacy cannot be dropped as the candidate is approved. + Approved, + /// The candidacy cannot be pruned until a full additional intake period has passed. + TooEarly, + /// The skeptic already voted. + Voted, + /// The skeptic need not vote on candidates from expired rounds. + Expired, + /// User is not a bidder. + NotBidder, } #[pallet::event] @@ -513,100 +610,147 @@ pub mod pallet { Deposit { value: BalanceOf }, } + /// Old name generated by `decl_event`. + #[deprecated(note = "use `Event` instead")] + pub type RawEvent = Event; + + /// The max number of members for the society at one time. + #[pallet::storage] + pub(super) type MaxMembers, I: 'static = ()> = StorageValue<_, u32, ValueQuery>; + + /// Amount of our account balance that is specifically for the next round's bid(s). + #[pallet::storage] + pub type Pot, I: 'static = ()> = StorageValue<_, BalanceOf, ValueQuery>; + /// The first member. #[pallet::storage] - #[pallet::getter(fn founder)] pub type Founder, I: 'static = ()> = StorageValue<_, T::AccountId>; + /// The most primary from the most recently approved members. + #[pallet::storage] + pub type Head, 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. + // TODO: Should be a map with rules for each rank and badge. #[pallet::storage] - #[pallet::getter(fn rules)] pub type Rules, 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, I: 'static = ()> = - StorageValue<_, Vec>>, ValueQuery>; - - /// The set of suspended candidates. - #[pallet::storage] - #[pallet::getter(fn suspended_candidate)] - pub type SuspendedCandidates, I: 'static = ()> = StorageMap< - _, - Twox64Concat, - T::AccountId, - (BalanceOf, BidKind>), - >; - - /// Amount of our account balance that is specifically for the next round's bid(s). + /// The current members and their rank. Doesn't include `SuspendedMembers`. #[pallet::storage] - #[pallet::getter(fn pot)] - pub type Pot, I: 'static = ()> = StorageValue<_, BalanceOf, ValueQuery>; + pub type Membership, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, MemberRecordFor, OptionQuery>; - /// The most primary from the most recently approved members. + /// The number of items in `Membership` currently. (Doesn't include `SuspendedMembers`.) #[pallet::storage] - #[pallet::getter(fn head)] - pub type Head, I: 'static = ()> = StorageValue<_, T::AccountId>; + pub type MemberCount, I: 'static = ()> = StorageValue<_, u32, ValueQuery>; - /// The current set of members, ordered. + /// The current items in `Membership` keyed by their unique index. Keys are densely populated + /// `0..MemberCount` (does not include `MemberCount`). #[pallet::storage] - #[pallet::getter(fn members)] - pub type Members, I: 'static = ()> = - StorageValue<_, Vec, ValueQuery>; + pub type MemberByIndex, I: 'static = ()> = + StorageMap<_, Twox64Concat, u32, T::AccountId, OptionQuery>; /// The set of suspended members. #[pallet::storage] - #[pallet::getter(fn suspended_member)] pub type SuspendedMembers, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, bool, ValueQuery>; + StorageMap<_, Twox64Concat, T::AccountId, MemberRecordFor, OptionQuery>; - /// The current bids, stored ordered by the value of the bid. + /// The number of rounds which have passed. #[pallet::storage] - pub(super) type Bids, I: 'static = ()> = - StorageValue<_, Vec>>, ValueQuery>; + pub type RoundCount, I: 'static = ()> = StorageValue<_, RoundIndex, ValueQuery>; - /// Members currently vouching or banned from vouching again + /// The current bids, stored ordered by the value of the bid. #[pallet::storage] - #[pallet::getter(fn vouching)] - pub(super) type Vouching, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, VouchingStatus>; + pub(super) type Bids, I: 'static = ()> = + StorageValue<_, BoundedVec>, T::MaxBids>, ValueQuery>; - /// Pending payouts; ordered by block number, with the amount that should be paid out. #[pallet::storage] - pub(super) type Payouts, I: 'static = ()> = StorageMap< - _, - Twox64Concat, + pub type Candidate, I: 'static = ()> = StorageMap<_, + Blake2_128Concat, T::AccountId, - Vec<(T::BlockNumber, BalanceOf)>, - ValueQuery, + Candidacy>, + OptionQuery, >; - /// The ongoing number of losing votes cast by the member. + /// The current skeptics. #[pallet::storage] - pub(super) type Strikes, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, StrikeCount, ValueQuery>; + pub type Skeptic, I: 'static = ()> = StorageValue<_, Vec, ValueQuery>; /// Double map from Candidate -> Voter -> (Maybe) Vote. #[pallet::storage] - pub(super) type Votes, I: 'static = ()> = - StorageDoubleMap<_, Twox64Concat, T::AccountId, Twox64Concat, T::AccountId, Vote>; + pub(super) type Votes, I: 'static = ()> = StorageDoubleMap<_, + Twox64Concat, + T::AccountId, + Twox64Concat, + T::AccountId, + Vote, + OptionQuery, + >; + // TODO: Migrate from: + //pub(super) type Votes, I: 'static = ()> = + // StorageDoubleMap<_, Twox64Concat, T::AccountId, Twox64Concat, T::AccountId, OldVote>; + + /// At the end of the claim period, this contains the most recently approved members (along with + /// their bid and round ID) who is from the most recent round with the lowest bid. They will + /// become the new `Head`. + #[pallet::storage] + pub type NextHead, I: 'static = ()> = StorageValue<_, + IntakeRecordFor, + OptionQuery, + >; - /// The defending member currently being challenged. + /// The defending member currently being challenged, along with a running tally of approval and + /// rejection votes. #[pallet::storage] - #[pallet::getter(fn defender)] - pub(super) type Defender, I: 'static = ()> = StorageValue<_, T::AccountId>; + pub(super) type Defending, I: 'static = ()> = StorageValue<_, (T::AccountId, T::AccountId, u32, u32)>; /// Votes for the defender. + // TODO: Migrate (used to be `OldVote`) #[pallet::storage] pub(super) type DefenderVotes, I: 'static = ()> = StorageMap<_, Twox64Concat, T::AccountId, Vote>; - /// The max number of members for the society at one time. + // OLD STUFF + + // Moved to `Member`. + // TODO: Needs refactor, migration into Membership, removal. + /// Members currently vouching or banned from vouching again. #[pallet::storage] - #[pallet::getter(fn max_members)] - pub(super) type MaxMembers, I: 'static = ()> = StorageValue<_, u32, ValueQuery>; + pub(super) type Vouching, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, VouchingStatus>; + + /// The ongoing number of losing votes cast by the member. + // TODO: Needs refactor, migration into Membership, removal. + #[pallet::storage] + pub(super) type Strikes, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, StrikeCount, ValueQuery>; + + /// The current set of members, ordered. + // TODO: Needs refactor, migration into Membership, removal. + #[pallet::storage] + #[pallet::getter(fn members)] + pub type Members, I: 'static = ()> = + StorageValue<_, Vec, ValueQuery>; + + /// The current set of candidates; bidders that are attempting to become members. + // TODO: Needs refactor, migration into Candidate, removal. + #[pallet::storage] + #[pallet::getter(fn candidates)] + pub type Candidates, I: 'static = ()> = + StorageValue<_, Vec>>, ValueQuery>; + + /// The set of suspended candidates. + // TODO: Needs refactor and removal. + // TODO: Ensure that it is empty immediately prior to upgrade. + #[pallet::storage] + #[pallet::getter(fn suspended_candidate)] + pub type SuspendedCandidates, I: 'static = ()> = StorageMap< + _, + Twox64Concat, + T::AccountId, + (BalanceOf, BidKind>), + >; #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { @@ -616,22 +760,28 @@ pub mod pallet { let mut weight = 0; let weights = T::BlockWeights::get(); - // Run a candidate/membership rotation - if (n % T::RotationPeriod::get()).is_zero() { - members = >::get(); - Self::rotate_period(&mut members); + let phrase = b"society_rotation"; + // we'll need a random seed here. + // TODO: deal with randomness freshness + // https://github.com/paritytech/substrate/issues/8312 + let (seed, _) = T::Randomness::random(phrase); + // seed needs to be guaranteed to be 32 bytes. + let seed = <[u8; 32]>::decode(&mut TrailingZeroInput::new(seed.as_ref())) + .expect("input is padded with zeroes; qed"); + let mut rng = ChaChaRng::from_seed(seed); - weight += weights.max_block / 20; + // Run a candidate/membership rotation + match Self::period() { + Period::Voting { elapsed: 0, .. } => { + Self::rotate_intake(&mut rng); + 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 = >::get(); - } - Self::rotate_challenge(&mut members); - + Self::rotate_challenge(&mut rng); weight += weights.max_block / 20; } @@ -685,46 +835,22 @@ pub mod pallet { /// Parameters: /// - `value`: A one time payment the bid would like to receive when joining the society. /// - /// # /// Key: B (len of bids), C (len of candidates), M (len of members), X (balance reserve) - /// - Storage Reads: - /// - One storage read to check for suspended candidate. O(1) - /// - One storage read to check for suspended member. O(1) - /// - One storage read to retrieve all current bids. O(B) - /// - 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) - /// - 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 Pallet Operations: - /// - One balance reserve operation. O(X) - /// - Up to one balance unreserve operation if bids.len() > MAX_BID_COUNT. - /// - Events: - /// - One event for new bid. - /// - Up to one event for AutoUnbid if bid.len() > MAX_BID_COUNT. - /// /// Total Complexity: O(M + B + C + logM + logB + X) - /// # #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn bid(origin: OriginFor, value: BalanceOf) -> DispatchResult { let who = ensure_signed(origin)?; - ensure!(!>::contains_key(&who), Error::::Suspended); - ensure!(!>::contains_key(&who), Error::::Suspended); - let bids = >::get(); - ensure!(!Self::is_bid(&bids, &who), Error::::AlreadyBid); - let candidates = >::get(); - ensure!(!Self::is_candidate(&candidates, &who), Error::::AlreadyCandidate); - let members = >::get(); - ensure!(!Self::is_member(&members, &who), Error::::AlreadyMember); + ensure!(!SuspendedMembers::::contains_key(&who), Error::::Suspended); + let mut bids = Bids::::get(); + ensure!(!Self::has_bid(&bids, &who), Error::::AlreadyBid); + ensure!(!Candidate::::contains_key(&who), Error::::AlreadyCandidate); + ensure!(!Membership::::contains_key(&who), Error::::AlreadyMember); let deposit = T::CandidateDeposit::get(); T::Currency::reserve(&who, deposit)?; - Self::put_bid(bids, &who, value.clone(), BidKind::Deposit(deposit)); + Self::insert_bid(&mut bids, &who, value.clone(), BidKind::Deposit(deposit)); + Bids::::put(bids); Self::deposit_event(Event::::Bid { candidate_id: who, offer: value }); Ok(()) } @@ -737,42 +863,18 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_ and a bidder. /// - /// Parameters: - /// - `pos`: Position in the `Bids` vector of the bid who wants to unbid. - /// - /// # /// Key: B (len of bids), X (balance unreserve) - /// - One storage read and write to retrieve and update the bids. O(B) - /// - Either one unreserve balance action O(X) or one vouching storage removal. O(1) - /// - One event. - /// /// Total Complexity: O(B + X) - /// # #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn unbid(origin: OriginFor, pos: u32) -> DispatchResult { + pub fn unbid(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let pos = pos as usize; - >::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 - // no reason that either should fail. - match b.remove(pos).kind { - BidKind::Deposit(deposit) => { - let err_amount = T::Currency::unreserve(&who, deposit); - debug_assert!(err_amount.is_zero()); - }, - BidKind::Vouch(voucher, _) => { - >::remove(&voucher); - }, - } - Self::deposit_event(Event::::Unbid { candidate: who }); - Ok(()) - } else { - Err(Error::::BadPosition)? - } - }) + let mut bids = Bids::::get(); + let pos = bids.iter().position(|bid| bid.who == who).ok_or(Error::::NotBidder)?; + Self::clean_bid(&bids.remove(pos)); + Bids::::put(bids); + Self::deposit_event(Event::::Unbid { candidate: who }); + Ok(()) } /// As a member, vouch for someone to join society by placing a bid on their behalf. @@ -793,33 +895,8 @@ pub mod pallet { /// - `tip`: Your cut of the total `value` payout when the candidate is inducted into /// the society. Tips larger than `value` will be saturated upon payout. /// - /// # /// Key: B (len of bids), C (len of candidates), M (len of members) - /// - Storage Reads: - /// - One storage read to retrieve all members. O(M) - /// - One storage read to check member is not already vouching. O(1) - /// - One storage read to check for suspended candidate. O(1) - /// - One storage read to check for suspended member. O(1) - /// - One storage read to retrieve all current bids. O(B) - /// - 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) - /// - 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 Pallet Operations: - /// - One balance reserve operation. O(X) - /// - Up to one balance unreserve operation if bids.len() > MAX_BID_COUNT. - /// - Events: - /// - One event for vouch. - /// - Up to one event for AutoUnbid if bid.len() > MAX_BID_COUNT. - /// /// Total Complexity: O(M + B + C + logM + logB + X) - /// # #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn vouch( origin: OriginFor, @@ -829,21 +906,21 @@ pub mod pallet { ) -> DispatchResult { let voucher = ensure_signed(origin)?; // Check user is not suspended. - ensure!(!>::contains_key(&who), Error::::Suspended); - ensure!(!>::contains_key(&who), Error::::Suspended); + ensure!(!SuspendedCandidates::::contains_key(&who), Error::::Suspended); + ensure!(!SuspendedMembers::::contains_key(&who), Error::::Suspended); // Check user is not a bid or candidate. - let bids = >::get(); - ensure!(!Self::is_bid(&bids, &who), Error::::AlreadyBid); - let candidates = >::get(); + let bids = Bids::::get(); + ensure!(!Self::has_bid(&bids, &who), Error::::AlreadyBid); + let candidates = Candidates::::get(); ensure!(!Self::is_candidate(&candidates, &who), Error::::AlreadyCandidate); // Check user is not already a member. - let members = >::get(); + let members = Members::::get(); ensure!(!Self::is_member(&members, &who), Error::::AlreadyMember); // Check sender can vouch. ensure!(Self::is_member(&members, &voucher), Error::::NotMember); - ensure!(!>::contains_key(&voucher), Error::::AlreadyVouching); + ensure!(!Vouching::::contains_key(&voucher), Error::::AlreadyVouching); - >::insert(&voucher, VouchingStatus::Vouching); + Vouching::::insert(&voucher, VouchingStatus::Vouching); Self::put_bid(bids, &who, value.clone(), BidKind::Vouch(voucher.clone(), tip)); Self::deposit_event(Event::::Vouch { candidate_id: who, @@ -861,28 +938,21 @@ pub mod pallet { /// Parameters: /// - `pos`: Position in the `Bids` vector of the bid who should be unvouched. /// - /// # /// Key: B (len of bids) - /// - One storage read O(1) to check the signer is a vouching member. - /// - One storage mutate to retrieve and update the bids. O(B) - /// - One vouching storage removal. O(1) - /// - One event. - /// /// Total Complexity: O(B) - /// # #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn unvouch(origin: OriginFor, pos: u32) -> DispatchResult { let voucher = ensure_signed(origin)?; ensure!( - Self::vouching(&voucher) == Some(VouchingStatus::Vouching), + Vouching::::get(&voucher) == Some(VouchingStatus::Vouching), Error::::NotVouching ); let pos = pos as usize; - >::mutate(|b| { + Bids::::mutate(|b| { if pos < b.len() { b[pos].kind.check_voucher(&voucher)?; - >::remove(&voucher); + Vouching::::remove(&voucher); let who = b.remove(pos).who; Self::deposit_event(Event::::Unvouch { candidate: who }); Ok(()) @@ -901,16 +971,8 @@ pub mod pallet { /// - `approve`: A boolean which says if the candidate should be approved (`true`) or /// rejected (`false`). /// - /// # /// Key: C (len of candidates), M (len of members) - /// - One storage read O(M) and O(log M) search to check user is a member. - /// - One account lookup. - /// - One storage read O(C) and O(C) search to check that user is a candidate. - /// - One storage write to add vote to votes. O(1) - /// - One event. - /// /// Total Complexity: O(M + logM + C) - /// # #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn vote( origin: OriginFor, @@ -919,14 +981,24 @@ pub mod pallet { ) -> DispatchResult { let voter = ensure_signed(origin)?; let candidate = T::Lookup::lookup(candidate)?; - let candidates = >::get(); - ensure!(Self::is_candidate(&candidates, &candidate), Error::::NotCandidate); - let members = >::get(); - ensure!(Self::is_member(&members, &voter), Error::::NotMember); - let vote = if approve { Vote::Approve } else { Vote::Reject }; - >::insert(&candidate, &voter, vote); + let mut candidacy = Candidate::::get(candidate).ok_or(Error::::NotCandidate)?; + let record = Membership::::get(&voter).ok_or(Error::::NotMember)?; + // remove the old vote from the count, if there was one. + match Votes::::get(&candidate, &voter) { + Some(Vote { approve: true, weight }) => candidacy.approves.saturating_reduce(weight), + Some(Vote { approve: false, weight }) => candidacy.rejects.saturating_reduce(weight), + _ => {}, + } + let weight_root = record.rank + 1; + let weight = weight_root * weight_root; + match approve { + true => candidacy.approves.saturating_accrue(1), + false => candidacy.rejects.saturating_accrue(1), + } + Votes::::insert(&candidate, &voter, Vote { approve, weight }); + Candidate::::insert(&candidate, &candidacy); Self::deposit_event(Event::::Vote { candidate, voter, vote: approve }); Ok(()) } @@ -939,22 +1011,17 @@ pub mod pallet { /// - `approve`: A boolean which says if the candidate should be /// approved (`true`) or rejected (`false`). /// - /// # - /// - Key: M (len of members) - /// - One storage read O(M) and O(log M) search to check user is a member. - /// - One storage write to add vote to votes. O(1) - /// - One event. - /// + /// Key: M (len of members) /// Total Complexity: O(M + logM) /// # #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn defender_vote(origin: OriginFor, approve: bool) -> DispatchResult { let voter = ensure_signed(origin)?; - let members = >::get(); + let members = Members::::get(); ensure!(Self::is_member(&members, &voter), Error::::NotMember); let vote = if approve { Vote::Approve } else { Vote::Reject }; - >::insert(&voter, vote); + DefenderVotes::::insert(&voter, vote); Self::deposit_event(Event::::DefenderVote { voter, vote: approve }); Ok(()) @@ -971,39 +1038,39 @@ pub mod pallet { /// The dispatch origin for this call must be _Signed_ and a member with /// payouts remaining. /// - /// # /// Key: M (len of members), P (number of payouts for a particular member) - /// - One storage read O(M) and O(log M) search to check signer is a member. - /// - One storage read O(P) to get all payouts for a member. - /// - One storage read O(1) to get the current block number. - /// - One currency transfer call. O(X) - /// - One storage write or removal to update the member's payouts. O(P) - /// /// Total Complexity: O(M + logM + P + X) - /// # #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; + let mut record = Membership::::get(&who).ok_or(Error::::NotMember)?; - let members = >::get(); - ensure!(Self::is_member(&members, &who), Error::::NotMember); - - let mut payouts = >::get(&who); - if let Some((when, amount)) = payouts.first() { + if let Some((when, amount)) = record.payouts.first() { if when <= &>::block_number() { + record.paid = record.paid.checked_add(amount).ok_or(Overflow)?; T::Currency::transfer(&Self::payouts(), &who, *amount, AllowDeath)?; - payouts.remove(0); - if payouts.is_empty() { - >::remove(&who); - } else { - >::insert(&who, payouts); - } + record.payouts.remove(0); + Membership::::insert(&who, record); return Ok(()) } } Err(Error::::NoPayout)? } + /// Repay the payment previously given to the member with the signed origin, and elevate + /// them from rank 0 to rank 1. + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn repay(origin: OriginFor) -> DispatchResult { + todo!() + } + + /// Remove the payment owed to the member with the signed origin, and elevate them from + /// rank 0 to rank 1. + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn waive(origin: OriginFor) -> DispatchResult { + todo!() + } + /// Found the society. /// /// This is done as a discrete action in order to allow for the @@ -1016,13 +1083,7 @@ pub mod pallet { /// - `max_members` - The initial max number of members for the society. /// - `rules` - The rules of this society concerning membership. /// - /// # - /// - Two storage mutates to set `Head` and `Founder`. O(1) - /// - One storage write to add the first member to society. O(1) - /// - One event. - /// - /// Total Complexity: O(1) - /// # + /// Complexity: O(1) #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn found( origin: OriginFor, @@ -1031,13 +1092,13 @@ pub mod pallet { rules: Vec, ) -> DispatchResult { T::FounderSetOrigin::ensure_origin(origin)?; - ensure!(!>::exists(), Error::::AlreadyFounded); + ensure!(!Head::::exists(), Error::::AlreadyFounded); ensure!(max_members > 1, Error::::MaxMembers); // This should never fail in the context of this function... - >::put(max_members); - Self::add_member(&founder)?; - >::put(&founder); - >::put(&founder); + MaxMembers::::put(max_members); + Self::add_member(&founder, 1, BoundedVec::new())?; + Head::::put(&founder); + Founder::::put(&founder); Rules::::put(T::Hashing::hash(&rules)); Self::deposit_event(Event::::Founded { founder }); Ok(()) @@ -1063,10 +1124,15 @@ pub mod pallet { ensure!(Head::::get() == Some(founder.clone()), Error::::NotHead); Members::::kill(); + MemberCount::::kill(); + MemberByIndex::::remove_all(None); + Membership::::remove_all(None); + Votes::::remove_all(None); Head::::kill(); Founder::::kill(); Rules::::kill(); Candidates::::kill(); + Candidate::::remove_all(None); SuspendedCandidates::::remove_all(None); Self::deposit_event(Event::::Unfounded { founder }); Ok(()) @@ -1080,182 +1146,114 @@ pub mod pallet { /// If a suspended member is rejected, remove all associated storage items, including /// their payouts, and remove any vouched bids they currently have. /// - /// The dispatch origin for this call must be from the _SuspensionJudgementOrigin_. + /// The dispatch origin for this call must be from the _JudgementOrigin_. /// /// 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. /// - /// # /// 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 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. - /// - One storage removal. O(1) - /// - One event for the judgement. - /// /// Total Complexity: O(M + logM + B) - /// # #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn judge_suspended_member( origin: OriginFor, who: T::AccountId, forgive: bool, ) -> DispatchResult { - T::SuspensionJudgementOrigin::ensure_origin(origin)?; - ensure!(>::contains_key(&who), Error::::NotSuspended); + T::JudgementOrigin::ensure_origin(origin)?; + let record = SuspendedMembers::::get(&who).ok_or(Error::::NotSuspended)?; if forgive { // Try to add member back to society. Can fail with `MaxMembers` limit. - Self::add_member(&who)?; + Self::reinstate_member(&who, record.rank, record.payouts)?; } else { - // Cancel a suspended member's membership, remove their payouts. - >::remove(&who); - >::remove(&who); - // Remove their vouching status, potentially unbanning them in the future. - if >::take(&who) == Some(VouchingStatus::Vouching) { - // Try to remove their bid if they are vouching. - // If their vouch is already a candidate, do nothing. - >::mutate(|bids| - // Try to find the matching bid - 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(Event::::Unvouch { candidate: vouched }); - } - ); - } + Self::unreserve_payout(record.payouts.into_iter().map(|x| x.1).sum()); } - >::remove(&who); + SuspendedMembers::::remove(&who); Self::deposit_event(Event::::SuspendedMemberJudgement { who, judged: forgive }); Ok(()) } - /// Allow suspended judgement origin to make judgement on a suspended candidate. - /// - /// If the judgement is `Approve`, we add them to society as a member with the appropriate - /// payment for joining society. - /// - /// If the judgement is `Reject`, we either slash the deposit of the bid, giving it back - /// to the society treasury, or we ban the voucher from vouching again. - /// - /// If the judgement is `Rebid`, we put the candidate back in the bid pool and let them go - /// through the induction process again. - /// - /// The dispatch origin for this call must be from the _SuspensionJudgementOrigin_. - /// - /// Parameters: - /// - `who` - The suspended candidate to be judged. - /// - `judgement` - `Approve`, `Reject`, or `Rebid`. - /// - /// # - /// Key: B (len of bids), M (len of members), X (balance action) - /// - One storage read to check `who` is a suspended candidate. - /// - One storage removal of the suspended candidate. - /// - Approve Logic - /// - One storage read to get the available pot to pay users with. O(1) - /// - One storage write to update the available pot. O(1) - /// - One storage read to get the current block number. O(1) - /// - One storage read to get all members. O(M) - /// - Up to one unreserve currency action. - /// - Up to two new storage writes to payouts. - /// - Up to one storage write with O(log M) binary search to add a member to society. - /// - Reject Logic - /// - Up to one repatriate reserved currency action. O(X) - /// - Up to one storage write to ban the vouching member from vouching again. - /// - Rebid Logic - /// - Storage mutate with O(log B) binary search to place the user back into bids. - /// - Up to one additional event if unvouch takes place. - /// - One storage removal. - /// - One event for the judgement. - /// - /// Total Complexity: O(M + logM + B + X) - /// # - #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn judge_suspended_candidate( - origin: OriginFor, - who: T::AccountId, - judgement: Judgement, - ) -> DispatchResult { - T::SuspensionJudgementOrigin::ensure_origin(origin)?; - if let Some((value, kind)) = >::get(&who) { - match judgement { - Judgement::Approve => { - // Suspension Judgement origin has approved this candidate - // Make sure we can pay them - let pot = Self::pot(); - ensure!(pot >= value, Error::::InsufficientPot); - // Try to add user as a member! Can fail with `MaxMember` limit. - Self::add_member(&who)?; - // Reduce next pot by payout - >::put(pot - value); - // Add payout for new candidate - let maturity = >::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, - ); - debug_assert!(res.is_ok()); - }, - BidKind::Vouch(voucher, _) => { - // Ban the voucher from vouching again - >::insert(&voucher, VouchingStatus::Banned); - }, - } - }, - Judgement::Rebid => { - // Founder has taken no judgement, and candidate is placed back into the - // pool. - let bids = >::get(); - Self::put_bid(bids, &who, value, kind); - }, - } - - // Remove suspended candidate - >::remove(who); - } else { - Err(Error::::NotSuspended)? - } - Ok(()) - } - /// Allows root origin to change the maximum number of members in society. - /// Max membership count must be greater than 1. + /// New max membership count must be no less than the current number of members. /// - /// The dispatch origin for this call must be from _ROOT_. + /// The dispatch origin for this call must be Root. /// /// Parameters: /// - `max` - The maximum number of members for the society. /// - /// # - /// - One storage write to update the max. O(1) - /// - One event. - /// /// Total Complexity: O(1) - /// # #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn set_max_members(origin: OriginFor, max: u32) -> DispatchResult { - ensure_root(origin)?; - ensure!(max > 1, Error::::MaxMembers); + T::AdminOrigin::ensure_origin(origin)?; + ensure!(max >= MemberCount::::get(), Error::::MaxMembers); MaxMembers::::put(max); Self::deposit_event(Event::::NewMaxMembers { max }); Ok(()) } + + /// Transform an approved candidate into a member. Callable only by the + /// the candidate, and only after the period for voting has ended. + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn claim_membership(origin: OriginFor) -> DispatchResult { + let candidate = ensure_signed(origin)?; + let candidacy = Candidate::::get(&candidate).ok_or(Error::::NotCandidate)?; + ensure!(candidacy.approvals > candidacy.rejections, Error::::NotApproved); + ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); + Self::induct_member(candidate, candidacy, 0) + } + + /// Transform an approved candidate into a member. Callable only by the + /// `JudgementOrigin` and only after the period for voting has ended. + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn force_membership(origin: OriginFor, candidate: T::AccountId) -> DispatchResult { + T::JudgementOrigin::ensure_origin(origin)?; + let candidacy = Candidate::::get(&candidate).ok_or(Error::::NotCandidate)?; + ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); + Self::induct_member(candidate, candidacy, 0) + } + + /// Remove the candidate's application from the society. Callable only by the + /// `JudgementOrigin` and the candidate, and only after the period for voting has ended. + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn kick_candidate(origin: OriginFor, candidate: T::AccountId) -> DispatchResult { + T::JudgementOrigin::ensure_origin(origin)?; + let candidacy = Candidate::::get(&candidate).ok_or(Error::::NotCandidate)?; + ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); + Self::check_skeptic(&candidate, &candidacy); + Votes::::remove_prefix(&candidate, None); + Candidate::::remove(&candidate); + Ok(()) + } + + /// Remove the candidate's failed application from the society. Callable only by the + /// the candidate, and only after the period for voting has ended. + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn resign_candidate(origin: OriginFor) -> DispatchResult { + let candidate = ensure_signed(origin)?; + let candidacy = Candidate::::get(&candidate).ok_or(Error::::NotCandidate)?; + ensure!(candidacy.approvals <= candidacy.rejections, Error::::Approved); + ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); + Self::check_skeptic(&candidate, &candidacy); + Votes::::remove_prefix(&candidate, None); + Candidate::::remove(&candidate); + Ok(()) + } + + /// Remove a `candidate`'s failed application from the society. Callable by any + /// signed origin but only after the subsequent period for voting has ended. + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn drop_candidate(origin: OriginFor, candidate: T::AccountId) -> DispatchResult { + ensure_signed(origin)?; + let candidacy = Candidate::::get(&candidate).ok_or(Error::::NotCandidate)?; + ensure!(candidacy.approvals <= candidacy.rejections, Error::::Approved); + ensure!(RoundCount::::get() > candidacy.round + 1, Error::::TooEarly); + Votes::::remove_prefix(&candidate, None); + Candidate::::remove(&candidate); + Ok(()) + } } } @@ -1287,363 +1285,403 @@ fn pick_item<'a, R: RngCore, T>(rng: &mut R, items: &'a [T]) -> Option<&'a T> { } /// Pick a new PRN, in the range [0, `max`] (inclusive). -fn pick_usize(rng: &mut R, max: usize) -> usize { +fn pick_usize(rng: &mut impl RngCore, max: usize) -> usize { (rng.next_u32() % (max as u32 + 1)) as usize } +struct InputFromRng<'a, T>(&'a mut T); +impl<'a, T: RngCore> codec::Input for InputFromRng<'a, T> { + fn remaining_len(&mut self) -> Result, Error> { + return Ok(None) + } + + fn read(&mut self, into: &mut [u8]) -> Result<(), Error> { + self.0.fill_bytes(into); + Ok(()) + } +} + +pub enum Period { + Voting { elapsed: BlockNumber, more: BlockNumber }, + Claim { elapsed: BlockNumber, more: BlockNumber }, +} + impl, I: 'static> Pallet { + /// Get the period we are currently in. + fn period() -> Period { + let claim_period = T::ClaimPeriod::get(); + let voting_period = T::VotingPeriod::get(); + let rotation_period = voting_period + claim_period; + let now = ::block_number(); + let phase = now % rotation_period; + if phase < voting_period { + Period::Voting { elapsed: phase, more: voting_period - phase } + } else { + Period::Claim { elapsed: phase - voting_period, more: rotation_period - phase } + } + } + + /// Returns true if the given `target_round` is still in its initial voting phase. + fn in_progress(target_round: RoundIndex) -> bool { + let round = RoundCount::::get(); + target_round == round && matches!(Self::period(), Period::Voting {..}) + } + + fn check_skeptic(candidate: &T::AccountId, candidacy: &Candidacy>) { + if RoundCount::::get() != candidacy.round { return } + // We expect the skeptic to have voted. + let skeptic = Skeptic::::get(); + let maybe_vote = Votes::::get(&candidate, &skeptic); + let approved = candidacy.approvals > candidacy.rejections; + let rejected = candidacy.rejections > candidacy.approvals; + match (maybe_vote, approved, rejected) { + (None, _, _) | (Some(false), true, false) | (Some(true), false, true) => { + // Can't do much if the punishment doesn't work out. + let _ = Self::strike_member(&skeptic); + }, + _ => {}, + } + } + + /// End the current challenge period and start a new one. + fn rotate_challenge(rng: &mut impl RngCore) { + // TODO. + // End current defender rotation + if let Some((defender, skeptic, approvals, rejections)) = Defending::::get() { + if rejections > approvals { + // Member has failed the challenge + Self::suspend_member(&defender); + } + + // TODO: check skeptic voted and that their vote was with the majority. Slash if not. + + // Clean up all votes. + DefenderVotes::::remove_all(None); + } + + // Avoid challenging if there's only two members since we never challenge the Head or + // the Founder. + if MemberCount::::get() > 2 { + let defender = Self::pick_defendent(&mut rng).expect("exited if members empty; qed"); + let skeptic = Self::pick_member_except(&mut rng, &defender).expect("exited if members empty; qed"); + Self::deposit_event(Event::::Challenged { member: defender.clone() }); + Defending::::put((defender, skeptic, 0, 0)); + } else { + Defending::::kill(); + } + } + + /// End the current intake period and begin a new one. + /// + /// --------------------------------------------- + /// #10 || #11 _ || #12 + /// || Voting | Claiming || + /// --------------------------------------------- + fn rotate_intake(rng: &mut impl RngCore) { + // We assume there's at least one member or this logic won't work. + let member_count = MemberCount::get(); + if member_count < 1 { + return + } + let maybe_head = NextHead::::take(); + if let Some(head) = maybe_head { + Head::::put(head); + } + + // Bump the pot by at most `PeriodSpend`, but less if there's not very much left in our + // account. + let mut pot = Pot::::get(); + let unaccounted = T::Currency::free_balance(&Self::account_id()).saturating_sub(pot); + pot.saturating_accrue(T::PeriodSpend::get().min(unaccounted / 2u8.into())); + Pot::::put(&pot); + + // Bump round and create the new intake. + let mut round_count = RoundCount::::get(); + round_count.saturating_inc(); + let candidate_count = Self::select_new_candidates(round_count, member_count, pot); + if candidate_count > 0 { + // Select a member at random and make them the skeptic for this round. + let skeptic = Self::pick_member(&mut rng).expect("exited if members empty; qed"); + Skeptic::::put(skeptic); + } + RoundCount::::put(round_count); + } + + /// Remove a selection of bidding accounts such that the total bids is no greater than `Pot` and + /// the number of bids would not surpass `MaxMembers` if all were accepted. At most one bid may + /// be zero. + /// + /// Candidates are inserted from each bidder. + /// + /// The number of candidates inserted are returned. + pub fn select_new_candidates( + round: RoundIndex, + member_count: u32, + pot: BalanceOf, + ) -> usize { + let max_members = MaxMembers::::get() as usize; + + // Get the number of left-most bidders whose bids add up to less than `pot`. + let mut bids = Bids::::get(); + let mut max_selections: usize = (T::MaxCandidateIntake::get() as usize) + .min(max_members.saturating_sub(member_count)) + .min(bids.len()); + + let mut selections = 0; + // A running total of the cost to onboard these bids + let mut total_cost: BalanceOf = Zero::zero(); + + bids.retain(|bid| { + // We only accept a zero bid as the first selection. + total_cost.saturating_accrue(bid.value); + let accept = selections < max_selections + && (!bid.value.is_zero() || selections == 0) + && total_cost <= pot; + if accept { + let candidacy = Candidacy { + round, + kind: bid.kind, + bid: bid.value, + approvals: 0, + rejections: 0, + }; + Candidate::::insert(&bid.who, candidacy); + selections.saturating_inc(); + } + !accept + }); + + // No need to reset Bids if we're not taking anything. + Bids::::put(bids); + } + /// 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( - mut bids: Vec>>, + fn insert_bid( + bids: &mut BoundedVec>, T::MaxBids>, who: &T::AccountId, value: BalanceOf, bid_kind: BidKind>, ) { - const MAX_BID_COUNT: usize = 1000; - - match bids.binary_search_by(|bid| bid.value.cmp(&value)) { - // Insert new elements after the existing ones. This ensures new bids - // with the same bid value are further down the list than existing ones. - Ok(pos) => { - let different_bid = bids - .iter() - // Easily extract the index we are on - .enumerate() - // Skip ahead to the suggested position - .skip(pos) - // Keep skipping ahead until the position changes - // Get the element when things changed - .find(|(_, x)| x.value > bids[pos].value); - - // If the element is not at the end of the list, insert the new element - // in the spot. - if let Some((p, _)) = different_bid { - bids.insert(p, Bid { value, who: who.clone(), kind: bid_kind }); - // If the element is at the end of the list, push the element on the end. - } else { - bids.push(Bid { value, who: who.clone(), kind: bid_kind }); - } - }, - Err(pos) => bids.insert(pos, Bid { value, who: who.clone(), kind: bid_kind }), - } - // Keep it reasonably small. - if bids.len() > MAX_BID_COUNT { - let Bid { who: popped, kind, .. } = bids.pop().expect("b.len() > 1000; qed"); - match kind { - BidKind::Deposit(deposit) => { - let err_amount = T::Currency::unreserve(&popped, deposit); - debug_assert!(err_amount.is_zero()); - }, - BidKind::Vouch(voucher, _) => { - >::remove(&voucher); - }, - } - Self::deposit_event(Event::::AutoUnbid { candidate: popped }); + let pos = bids.iter().position(|bid| bid.value > value).unwrap_or(bids.len()); + let r = bids.force_insert_keep_left(pos, Bid { value, who: who.clone(), kind: bid_kind }); + let maybe_discarded = r.err().or_else(|| r.ok()); + if let Some(discarded) = maybe_discarded { + Self::clean_bid(&discarded); + Self::deposit_event(Event::::AutoUnbid { candidate: discarded }); } + } - >::put(bids); + /// 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 + /// no reason that either should fail. + fn clean_bid(bid: &Bid>) { + match &bid.kind { + BidKind::Deposit(deposit) => { + let err_amount = T::Currency::unreserve(&bid.who, *deposit); + debug_assert!(err_amount.is_zero()); + }, + BidKind::Vouch(voucher, _) => { + Vouching::::remove(voucher); + }, + } } - /// Check a user is a bid. - fn is_bid(bids: &Vec>>, who: &T::AccountId) -> bool { + /// Check a user has a bid. + fn has_bid(bids: &Vec>>, who: &T::AccountId) -> bool { // Bids are ordered by `value`, so we cannot binary search for a user. bids.iter().any(|bid| bid.who == *who) } - /// Check a user is a candidate. - fn is_candidate( - candidates: &Vec>>, - who: &T::AccountId, - ) -> bool { - // Looking up a candidate is the same as looking up a bid - Self::is_bid(candidates, who) + /// Add a member to the sorted members list. If the user is already a member, do nothing. + /// Can fail when `MaxMember` limit is reached, but in that case it has no side-effects. + /// + /// Set the `payouts` for the member. NOTE: This *WILL NOT RESERVE THE FUNDS TO MAKE THE + /// PAYOUT*. Only set this to be non-empty if you already have the funds reserved in the Payouts + /// account. + /// + /// NOTE: Generally you should not use this, and instead use `add_new_member` or + /// `reinstate_member`, whose names clearly match the desired intention. + fn insert_member(who: &T::AccountId, rank: Rank, payouts: PayoutsFor) -> DispatchResult { + ensure!(MemberCount::get() < MaxMembers::::get() as usize, Error::::MaxMembers); + let index = MemberCount::::mutate(|i| { i.saturating_accrue(1); *i - 1 }); + let paid = Zero::zero(); + let record = MemberRecord { rank, strikes: 0, vouching: None, paid, index, payouts }; + Membership::::insert(who, record); + MemberByIndex::::insert(index, who); + Ok(()) } - /// Check a user is a member. - fn is_member(members: &Vec, who: &T::AccountId) -> bool { - members.binary_search(who).is_ok() + /// Add a member back to the sorted members list, setting their `rank` and `payouts`. + /// + /// Can fail when `MaxMember` limit is reached, but in that case it has no side-effects. + /// + /// The `payouts` value must be exactly as it was prior to suspension since no further funds + /// will be reserved. + fn reinstate_member(who: &T::AccountId, rank: Rank, payouts: PayoutsFor) -> DispatchResult { + Self::insert_member(who, rank, payouts) } /// Add a member to the sorted members list. If the user is already a member, do nothing. - /// Can fail when `MaxMember` limit is reached, but has no side-effects. - fn add_member(who: &T::AccountId) -> DispatchResult { - let mut members = >::get(); - ensure!(members.len() < MaxMembers::::get() as usize, Error::::MaxMembers); - match members.binary_search(who) { - // Add the new member - Err(i) => { - members.insert(i, who.clone()); - T::MembershipChanged::change_members_sorted(&[who.clone()], &[], &members); - >::put(members); - Ok(()) - }, - // User is already a member, do nothing. - Ok(_) => Ok(()), - } + /// Can fail when `MaxMember` limit is reached, but in that case it has no side-effects. + fn add_new_member(who: &T::AccountId, rank: Rank) -> DispatchResult { + Self::insert_member(who, rank, Default::default()) } - /// Remove a member from the members list, except the Head. - /// - /// NOTE: This does not correctly clean up a member from storage. It simply - /// removes them from the Members storage item. - pub fn remove_member(m: &T::AccountId) -> DispatchResult { - ensure!(Self::head() != Some(m.clone()), Error::::Head); - ensure!(Self::founder() != Some(m.clone()), Error::::Founder); - - let mut members = >::get(); - match members.binary_search(&m) { - Err(_) => Err(Error::::NotMember)?, - Ok(i) => { - members.remove(i); - T::MembershipChanged::change_members_sorted(&[], &[m.clone()], &members[..]); - >::put(members); - Ok(()) - }, + /// Induct a new member into the set. + fn induct_member( + candidate: T::AccountId, + candidacy: Candidacy>, + rank: Rank, + ) -> DispatchResult { + Self::check_skeptic(&candidate, &candidacy); + Self::add_new_member(&candidate, 0)?; + let next_head = NextHead::::get() + .filter(|old| old.round > candidacy.round + || old.round == candidacy.round && old.bid < candidacy.bid + ).unwrap_or_else(|| IntakeRecord { who: candidate.clone(), bid: candidacy.bid, round: candidacy.round }); + NextHead::::put(next_head); + let now = >::block_number(); + let maturity = now + Self::lock_duration(MemberCount::::get()); + Self::reward_bidder(&candidate, candidacy.bid, candidacy.kind, maturity); + Votes::::remove_prefix(&candidate, None); + Candidate::::remove(&candidate); + Ok(()) + } + + fn strike_member(who: &T::AccountId) -> DispatchResult { + let mut record = Membership::::get(who).ok_or(NotMember)?; + record.strikes.saturating_inc(); + if record.strikes >= T::GraceStrikes::get() { + // Too many strikes: slash the payout in half. + let total_payout = record.payouts.iter().map(|x| x.1).sum(); + Self::slash_payout(who, total_payout / 2u32.into()); } + if record.strikes >= T::MaxStrikes::get() { + // Way too many strikes: suspend. + Self::suspend_member(who); + } + Ok(()) } - /// End the current period and begin a new one. - fn rotate_period(members: &mut Vec) { - let phrase = b"society_rotation"; - - let mut pot = >::get(); - - // we'll need a random seed here. - // TODO: deal with randomness freshness - // https://github.com/paritytech/substrate/issues/8312 - let (seed, _) = T::Randomness::random(phrase); - // seed needs to be guaranteed to be 32 bytes. - let seed = <[u8; 32]>::decode(&mut TrailingZeroInput::new(seed.as_ref())) - .expect("input is padded with zeroes; qed"); - let mut rng = ChaChaRng::from_seed(seed); - - // we assume there's at least one member or this logic won't work. - if !members.is_empty() { - let candidates = >::take(); - // NOTE: This may cause member length to surpass `MaxMembers`, but results in no - // consensus critical issues or side-effects. This is auto-correcting as members fall - // out of society. - members.reserve(candidates.len()); - - let maturity = >::block_number() + - Self::lock_duration(members.len() as u32); - - let mut rewardees = Vec::new(); - let mut total_approvals = 0; - let mut total_slash = >::zero(); - let mut total_payouts = >::zero(); - - let accepted = candidates - .into_iter() - .filter_map(|Bid { value, who: candidate, kind }| { - let mut approval_count = 0; - - // Creates a vector of (vote, member) for the given candidate - // and tallies total number of approve votes for that candidate. - let votes = members - .iter() - .filter_map(|m| >::take(&candidate, m).map(|v| (v, m))) - .inspect(|&(v, _)| { - if v == Vote::Approve { - approval_count += 1 - } - }) - .collect::>(); - - // Select one of the votes at random. - // Note that `Vote::Skeptical` and `Vote::Reject` both reject the candidate. - let is_accepted = - pick_item(&mut rng, &votes).map(|x| x.0) == Some(Vote::Approve); - - let matching_vote = if is_accepted { Vote::Approve } else { Vote::Reject }; - - let bad_vote = |m: &T::AccountId| { - // Voter voted wrong way (or was just a lazy skeptic) then reduce their - // payout and increase their strikes. after MaxStrikes then they go into - // suspension. - let amount = Self::slash_payout(m, T::WrongSideDeduction::get()); - - let strikes = >::mutate(m, |s| { - *s += 1; - *s - }); - if strikes >= T::MaxStrikes::get() { - Self::suspend_member(m); - } - amount - }; - - // Collect the voters who had a matching vote. - rewardees.extend( - votes - .into_iter() - .filter_map(|(v, m)| { - if v == matching_vote { - Some(m) - } else { - total_slash += bad_vote(m); - None - } - }) - .cloned(), + /// Remove a member from the members list and return the candidacy. + /// + /// If the member was vouching, then this will be reset. Any bidders that the member was + /// vouching for will be cancelled unless they are already selected as candidates (in which case + /// they will be able to stand). + /// + /// If the member has existing payouts, they will be retained in the resultant `MemberRecord` + /// and the funds will remain reserved. + /// + /// The Head and the Founder may never be removed. + pub fn remove_member(m: &T::AccountId) -> Result { + ensure!(Head::::get().as_ref() != Some(m), Error::::Head); + ensure!(Founder::::get().as_ref() != Some(m), Error::::Founder); + if let Some(record) = Membership::::get(m) { + let index = record.index; + let last_index = MemberCount::::mutate(|i| { i.saturating_reduce(1); *i }); + if index != last_index { + // Move the member with the last index down to the index of the member to be removed. + if let Some(other) = MemberByIndex::::get(last_index) { + MemberByIndex::::insert(index, other); + Membership::::mutate(other, |m_r| + if let Some(r) = m_r { r.index = index } ); - - if is_accepted { - total_approvals += approval_count; - total_payouts += value; - members.push(candidate.clone()); - - Self::pay_accepted_candidate(&candidate, value, kind, maturity); - - // We track here the total_approvals so that every candidate has a unique - // range of numbers from 0 to `total_approvals` with length `approval_count` - // so each candidate is proportionally represented when selecting a - // "primary" below. - Some((candidate, total_approvals, value)) - } else { - // Suspend Candidate - >::insert(&candidate, (value, kind)); - Self::deposit_event(Event::::CandidateSuspended { candidate }); - None - } - }) - .collect::>(); - - // Clean up all votes. - >::remove_all(None); - - // Reward one of the voters who voted the right way. - if !total_slash.is_zero() { - if let Some(winner) = pick_item(&mut rng, &rewardees) { - // If we can't reward them, not much that can be done. - Self::bump_payout(winner, maturity, total_slash); } else { - // Move the slashed amount back from payouts account to local treasury. - let res = T::Currency::transfer( - &Self::payouts(), - &Self::account_id(), - total_slash, - AllowDeath, - ); - debug_assert!(res.is_ok()); + debug_assert!(false, "ERROR: No member at the last index position?"); } } - // Fund the total payouts from the local treasury. - if !total_payouts.is_zero() { - // remove payout from pot and shift needed funds to the payout account. - pot = pot.saturating_sub(total_payouts); - - // this should never fail since we ensure we can afford the payouts in a previous - // block, but there's not much we can do to recover if it fails anyway. - let res = T::Currency::transfer( - &Self::account_id(), - &Self::payouts(), - total_payouts, - AllowDeath, + MemberByIndex::::remove(last_index); + Membership::::remove(m); + // Remove their vouching status, potentially unbanning them in the future. + if record.vouching.take() == Some(VouchingStatus::Vouching) { + // Try to remove their bid if they are vouching. + // If their vouch is already a candidate, do nothing. + Bids::::mutate(|bids| + // Try to find the matching bid + 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(Event::::Unvouch { candidate: vouched }); + } ); - debug_assert!(res.is_ok()); - } - - // if at least one candidate was accepted... - if !accepted.is_empty() { - // select one as primary, randomly chosen from the accepted, weighted by approvals. - // Choose a random number between 0 and `total_approvals` - let primary_point = pick_usize(&mut rng, total_approvals - 1); - // Find the zero bid or the user who falls on that point - let primary = accepted - .iter() - .find(|e| e.2.is_zero() || e.1 > primary_point) - .expect( - "e.1 of final item == total_approvals; \ - worst case find will always return that item; qed", - ) - .0 - .clone(); - - let accounts = accepted.into_iter().map(|x| x.0).collect::>(); - - // Then write everything back out, signal the changed membership and leave an event. - members.sort(); - // NOTE: This may cause member length to surpass `MaxMembers`, but results in no - // consensus critical issues or side-effects. This is auto-correcting as members - // fall out of society. - >::put(&members[..]); - >::put(&primary); - - T::MembershipChanged::change_members_sorted(&accounts, &[], &members); - Self::deposit_event(Event::::Inducted { primary, candidates: accounts }); } - - // Bump the pot by at most PeriodSpend, but less if there's not very much left in our - // account. - let unaccounted = T::Currency::free_balance(&Self::account_id()).saturating_sub(pot); - pot += T::PeriodSpend::get().min(unaccounted / 2u8.into()); - - >::put(&pot); + Ok(record) + } else { + Err(Error::::NotMember.into()) } + } - // Setup the candidates for the new intake - let candidates = Self::take_selected(members.len(), pot); - >::put(&candidates); - - // Select sqrt(n) random members from the society and make them skeptics. - let pick_member = - |_| pick_item(&mut rng, &members[..]).expect("exited if members empty; qed"); - for skeptic in (0..members.len().integer_sqrt()).map(pick_member) { - for Bid { who: c, .. } in candidates.iter() { - >::insert(c, skeptic, Vote::Skeptic); - } + /// Remove a member from the members set and add them to the suspended members. + /// + /// If the member was vouching, then this will be reset. Any bidders that the member was + /// vouching for will be cancelled unless they are already selected as candidates (in which case + /// they will be able to stand). + fn suspend_member(who: &T::AccountId) { + if let Ok(record) = Self::remove_member(&who) { + SuspendedMembers::::insert(who, record); + Self::deposit_event(Event::::MemberSuspended { member: who.clone() }); } } - /// Attempt to slash the payout of some member. Return the total amount that was deducted. - fn slash_payout(who: &T::AccountId, value: BalanceOf) -> BalanceOf { - let mut rest = value; - let mut payouts = >::get(who); - if !payouts.is_empty() { - let mut dropped = 0; - for (_, amount) in payouts.iter_mut() { - if let Some(new_rest) = rest.checked_sub(&amount) { - // not yet totally slashed after this one; drop it completely. - rest = new_rest; - dropped += 1; - } else { - // whole slash is accounted for. - *amount -= rest; - rest = Zero::zero(); - break - } - } - >::insert(who, &payouts[dropped..]); + /// Select a member at random, given the RNG `rng`. + /// + /// If no members exist (or the state is inconsistent), then `None` may be returned. + fn pick_member(rng: &mut impl RngCore) -> Option { + let member_count = MemberCount::get(); + if member_count == 0 { + return None } - value - rest + let random_index = rng.next_u32() % member_count; + MemberByIndex::::get(random_index) } - /// Bump the payout amount of `who`, to be unlocked at the given block number. - fn bump_payout(who: &T::AccountId, when: T::BlockNumber, value: BalanceOf) { - if !value.is_zero() { - >::mutate(who, |payouts| { - match payouts.binary_search_by_key(&when, |x| x.0) { - Ok(index) => payouts[index].1 += value, - Err(index) => payouts.insert(index, (when, value)), - } - }); + /// Select a member at random except `exception`, given the RNG `rng`. + /// + /// If `exception` is the only member (or the state is inconsistent), then `None` may be returned. + fn pick_member_except(rng: &mut impl RngCore, exception: &T::AccountId) -> Option { + let member_count = MemberCount::get(); + if member_count <= 1 { + return None + } + let random_index = rng.next_u32() % (member_count - 1); + let pick = MemberByIndex::::get(random_index); + if pick.as_ref() == Some(exception) { + MemberByIndex::::get(member_count - 1) + } else { + pick } } - /// Suspend a user, removing them from the member list. - fn suspend_member(who: &T::AccountId) { - if Self::remove_member(&who).is_ok() { - >::insert(who, true); - >::remove(who); - Self::deposit_event(Event::::MemberSuspended { member: who.clone() }); + /// Select a member who is able to defend at random, given the RNG `rng`. + /// + /// If only the Founder and Head members exist (or the state is inconsistent), then `None` + /// may be returned. + fn pick_defendent(rng: &mut impl RngCore) -> Option { + let member_count = MemberCount::get(); + if member_count <= 2 { + return None + } + // Founder is always at index 0, so we should never pick that one. + // Head will typically but not always be the highest index. We assume it is for now and + // fix it up later if not. + let head = Head::::get(); + let pickable_count = member_count - if head.is_some() { 2 } else { 1 }; + let random_index = rng.next_u32() % pickable_count + 1; + let pick = MemberByIndex::::get(random_index); + if pick == head && head.is_some() { + // Turns out that head was not the last index since we managed to pick it. Exchange our + // pick for the last index. + MemberByIndex::::get(member_count - 1) + } else { + pick } } /// Pay an accepted candidate their bid value. - fn pay_accepted_candidate( + fn reward_bidder( candidate: &T::AccountId, value: BalanceOf, kind: BidKind>, @@ -1660,7 +1698,7 @@ impl, I: 'static> Pallet { BidKind::Vouch(voucher, tip) => { // Check that the voucher is still vouching, else some other logic may have removed // their status. - if >::take(&voucher) == Some(VouchingStatus::Vouching) { + if Vouching::::take(&voucher) == Some(VouchingStatus::Vouching) { // In the case that a vouched-for bid is accepted we unset the // vouching status and transfer the tip over to the voucher. Self::bump_payout(&voucher, maturity, tip.min(value)); @@ -1674,53 +1712,80 @@ impl, I: 'static> Pallet { Self::bump_payout(candidate, maturity, value); } - /// End the current challenge period and start a new one. - fn rotate_challenge(members: &mut Vec) { - // Assume there are members, else don't run this logic. - if !members.is_empty() { - // End current defender rotation - if let Some(defender) = Self::defender() { - let mut approval_count = 0; - let mut rejection_count = 0; - // Tallies total number of approve and reject votes for the defender. - members.iter().filter_map(|m| >::take(m)).for_each( - |v| match v { - Vote::Approve => approval_count += 1, - _ => rejection_count += 1, - }, - ); - - if approval_count <= rejection_count { - // User has failed the challenge - Self::suspend_member(&defender); - *members = Self::members(); + /// Bump the payout amount of `who`, to be unlocked at the given block number. + /// + /// If it the caller's duty to ensure that `who` is already a member. This does nothing if `who` + /// is not a member or if `value` is zero. + fn bump_payout(who: &T::AccountId, when: T::BlockNumber, value: BalanceOf) { + if !value.is_zero() { + Membership::::mutate(who, |maybe_record| if let Some(record) = maybe_record { + if record.rank == 0 { + // Members of rank 1 never get payouts. + match record.payouts.binary_search_by_key(&when, |x| x.0) { + Ok(index) => record.payouts[index].1.saturating_accrue(value), + Err(index) => record.payouts.insert(index, (when, value)), + } } + }); + Self::reserve_payout(value); + } + } - // Clean up all votes. - >::remove_all(None); - } - - // Avoid challenging if there's only two members since we never challenge the Head or - // the Founder. - if members.len() > 2 { - // Start a new defender rotation - let phrase = b"society_challenge"; - // we'll need a random seed here. - // TODO: deal with randomness freshness - // https://github.com/paritytech/substrate/issues/8312 - let (seed, _) = T::Randomness::random(phrase); - // seed needs to be guaranteed to be 32 bytes. - let seed = <[u8; 32]>::decode(&mut TrailingZeroInput::new(seed.as_ref())) - .expect("input is padded with zeroes; qed"); - let mut rng = ChaChaRng::from_seed(seed); - let chosen = pick_item(&mut rng, &members[1..members.len() - 1]) - .expect("exited if members empty; qed"); - >::put(&chosen); - Self::deposit_event(Event::::Challenged { member: chosen.clone() }); + /// Attempt to slash the payout of some member. Return the total amount that was deducted. + fn slash_payout(who: &T::AccountId, value: BalanceOf) -> BalanceOf { + let mut record = match Membership::::get(who) { + Some(record) => record, + None => return Zero::zero(), + }; + let mut rest = value; + while !record.payouts.is_empty() { + if let Some(new_rest) = rest.checked_sub(&record.payouts[0].1) { + // not yet totally slashed after this one; drop it completely. + rest = new_rest; + record.payouts.remove(0); } else { - >::kill(); + // whole slash is accounted for. + record.payouts[0].1.saturating_reduce(rest); + rest = Zero::zero(); + break } } + Membership::::insert(who, record); + value - rest + } + + /// Transfer some `amount` from the main account into the payouts account and reduce the Pot + /// by this amount. + fn reserve_payout(amount: BalanceOf) { + // Tramsfer payout from the Pot into the payouts account. + Pot::::mutate(|pot| pot.saturating_reduce(amount)); + + // this should never fail since we ensure we can afford the payouts in a previous + // block, but there's not much we can do to recover if it fails anyway. + let res = T::Currency::transfer( + &Self::account_id(), + &Self::payouts(), + amount, + AllowDeath, + ); + debug_assert!(res.is_ok()); + } + + /// Transfer some `amount` from the main account into the payouts account and increase the Pot + /// by this amount. + fn unreserve_payout(amount: BalanceOf) { + // Tramsfer payout from the Pot into the payouts account. + Pot::::mutate(|pot| pot.saturating_accrue(amount)); + + // this should never fail since we ensure we can afford the payouts in a previous + // block, but there's not much we can do to recover if it fails anyway. + let res = T::Currency::transfer( + &Self::payouts(), + &Self::account_id(), + amount, + AllowDeath, + ); + debug_assert!(res.is_ok()); } /// The account ID of the treasury pot. @@ -1747,70 +1812,6 @@ impl, I: 'static> Pallet { let lock_pc = 100 - 50_000 / (x + 500); Percent::from_percent(lock_pc as u8) * T::MaxLockDuration::get() } - - /// Get a selection of bidding accounts such that the total bids is no greater than `Pot` and - /// the number of bids would not surpass `MaxMembers` if all were accepted. - /// - /// May be empty. - pub fn take_selected( - members_len: usize, - pot: BalanceOf, - ) -> Vec>> { - let max_members = MaxMembers::::get() as usize; - let mut max_selections: usize = - (T::MaxCandidateIntake::get() as usize).min(max_members.saturating_sub(members_len)); - - if max_selections > 0 { - // Get the number of left-most bidders whose bids add up to less than `pot`. - let mut bids = >::get(); - - // The list of selected candidates - let mut selected = Vec::new(); - - if bids.len() > 0 { - // Can only select at most the length of bids - max_selections = max_selections.min(bids.len()); - // Number of selected bids so far - let mut count = 0; - // Check if we have already selected a candidate with zero bid - let mut zero_selected = false; - // A running total of the cost to onboard these bids - let mut total_cost: BalanceOf = Zero::zero(); - - bids.retain(|bid| { - if count < max_selections { - // Handle zero bids. We only want one of them. - if bid.value.is_zero() { - // Select only the first zero bid - if !zero_selected { - selected.push(bid.clone()); - zero_selected = true; - count += 1; - return false - } - } else { - total_cost += bid.value; - // Select only as many users as the pot can support. - if total_cost <= pot { - selected.push(bid.clone()); - count += 1; - return false - } - } - } - true - }); - - // No need to reset Bids if we're not taking anything. - if count > 0 { - >::put(bids); - } - } - selected - } else { - vec![] - } - } } impl, I: 'static> OnUnbalanced> for Pallet { diff --git a/frame/society/src/mock.rs b/frame/society/src/mock.rs index 04ea705eed556..3c84a3f66a39e 100644 --- a/frame/society/src/mock.rs +++ b/frame/society/src/mock.rs @@ -109,7 +109,7 @@ impl Config for Test { type RotationPeriod = ConstU64<4>; type MaxLockDuration = ConstU64<100>; type FounderSetOrigin = EnsureSignedBy; - type SuspensionJudgementOrigin = EnsureSignedBy; + type JudgementOrigin = EnsureSignedBy; type ChallengePeriod = ConstU64<8>; type MaxCandidateIntake = ConstU32<10>; type PalletId = SocietyPalletId; diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index 92ca167f98436..5795edde24bc2 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -104,6 +104,11 @@ impl> Decode for BoundedVec { impl> EncodeLike> for BoundedVec {} impl BoundedVec { + /// Create `Self` with no items. + fn new() -> Self { + Self(Vec::new(), Default::default()) + } + /// Create `Self` from `t` without any checks. fn unchecked_from(t: Vec) -> Self { Self(t, Default::default()) @@ -241,23 +246,23 @@ impl> BoundedVec { /// If `Self::bound() < index` or `self.len() < index`, then this is also a no-op. /// /// Returns `Ok(maybe_removed)` if the item was inserted, where `maybe_removed` is - /// `Some(removed)` if an item was removed to make room for the new one. Returns `Err(())` if - /// `element` cannot be inserted. + /// `Some(removed)` if an item was removed to make room for the new one. Returns `Err(element)` + /// if `element` cannot be inserted. pub fn force_insert_keep_right( &mut self, index: usize, mut element: T, - ) -> Result, ()> { + ) -> Result, T> { // Check against panics. if Self::bound() < index || self.len() < index { - Err(()) + Err(element) } else if self.len() < Self::bound() { // Cannot panic since self.len() >= index; self.0.insert(index, element); Ok(None) } else { if index == 0 { - return Err(()) + return Err(element) } sp_std::mem::swap(&mut self[0], &mut element); // `[0..index] cannot panic since self.len() >= index. @@ -275,16 +280,16 @@ impl> BoundedVec { /// If `Self::bound() < index` or `self.len() < index`, then this is also a no-op. /// /// Returns `Ok(maybe_removed)` if the item was inserted, where `maybe_removed` is - /// `Some(removed)` if an item was removed to make room for the new one. Returns `Err(())` if - /// `element` cannot be inserted. - pub fn force_insert_keep_left(&mut self, index: usize, element: T) -> Result, ()> { + /// `Some(removed)` if an item was removed to make room for the new one. Returns `Err(element)` + /// if `element` cannot be inserted. + pub fn force_insert_keep_left(&mut self, index: usize, element: T) -> Result, T> { // Check against panics. if Self::bound() < index || self.len() < index || Self::bound() == 0 { - return Err(()) + return Err(element) } // Noop condition. if Self::bound() == index && self.len() <= Self::bound() { - return Err(()) + return Err(element) } let maybe_removed = if self.is_full() { // defensive-only: since we are at capacity, this is a noop. diff --git a/frame/support/src/storage/types/map.rs b/frame/support/src/storage/types/map.rs index bc32c58da8db6..009a0c9fadb8b 100644 --- a/frame/support/src/storage/types/map.rs +++ b/frame/support/src/storage/types/map.rs @@ -316,6 +316,14 @@ where >::iter_from(starting_raw_key) } + /// Enumerate all elements in the map after a specified `starting_key` in no + /// particular order. + /// + /// If you alter the map while doing this, you'll get undefined results. + pub fn iter_from_key(starting_key: impl EncodeLike) -> crate::storage::PrefixIterator<(Key, Value)> { + Self::iter_from(Self::hashed_key_for(starting_key)) + } + /// Enumerate all keys in the map in no particular order. /// /// If you alter the map while doing this, you'll get undefined results. @@ -331,6 +339,14 @@ where >::iter_keys_from(starting_raw_key) } + /// Enumerate all keys in the map after a specified `starting_key` in no particular + /// order. + /// + /// If you alter the map while doing this, you'll get undefined results. + pub fn iter_keys_from_from(starting_key: impl EncodeLike) -> crate::storage::KeyPrefixIterator { + Self::iter_keys_from(Self::hashed_key_for(starting_key)) + } + /// Remove all elements from the map and iterate through them in no particular order. /// /// If you add elements to the map while doing this, you'll get undefined results. diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 14e67cfcd8156..81924eeb295d5 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -195,6 +195,7 @@ impl, MaxOverflow: Get> ConsumerLimits for (MaxNormal, pub mod pallet { use crate::{self as frame_system, pallet_prelude::*, *}; use frame_support::pallet_prelude::*; + use sp_core::traits::FromEntropy; /// System configuration trait. Implemented by runtime. #[pallet::config] @@ -275,7 +276,8 @@ pub mod pallet { + Debug + MaybeDisplay + Ord - + MaxEncodedLen; + + MaxEncodedLen + + FromEntropy; /// Converting trait to take a source type and convert to `AccountId`. /// diff --git a/primitives/core/src/ecdsa.rs b/primitives/core/src/ecdsa.rs index 6343e3f4dfd0d..ebb70e74e758d 100644 --- a/primitives/core/src/ecdsa.rs +++ b/primitives/core/src/ecdsa.rs @@ -74,6 +74,14 @@ type Seed = [u8; 32]; )] pub struct Public(pub [u8; 33]); +impl FromEntropy for Public { + fn from_entropy(input: &mut codec::Input) -> Result { + let mut result = Self([0u8; 33]); + input.read(&mut result.0[..])?; + Ok(result) + } +} + impl Public { /// A new instance from the given 33-byte `data`. /// diff --git a/primitives/core/src/ed25519.rs b/primitives/core/src/ed25519.rs index 0bde9e2e5303a..bbb1ab74587e5 100644 --- a/primitives/core/src/ed25519.rs +++ b/primitives/core/src/ed25519.rs @@ -88,6 +88,14 @@ impl Clone for Pair { } } +impl FromEntropy for Public { + fn from_entropy(input: &mut codec::Input) -> Result { + let mut result = Self([0u8; 32]); + input.read(&mut result.0[..])?; + Ok(result) + } +} + impl AsRef<[u8; 32]> for Public { fn as_ref(&self) -> &[u8; 32] { &self.0 diff --git a/primitives/core/src/sr25519.rs b/primitives/core/src/sr25519.rs index ef033c2099b5f..4e72f747a3a4e 100644 --- a/primitives/core/src/sr25519.rs +++ b/primitives/core/src/sr25519.rs @@ -42,7 +42,7 @@ use crate::{ ByteArray, CryptoType, CryptoTypeId, CryptoTypePublicPair, Derive, Public as TraitPublic, UncheckedFrom, }, - hash::{H256, H512}, + hash::{H256, H512}, traits::FromEntropy, }; use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -93,6 +93,14 @@ impl Clone for Pair { } } +impl FromEntropy for Public { + fn from_entropy(input: &mut codec::Input) -> Result { + let mut result = Self([0u8; 32]); + input.read(&mut result.0[..])?; + Ok(result) + } +} + impl AsRef<[u8; 32]> for Public { fn as_ref(&self) -> &[u8; 32] { &self.0 diff --git a/primitives/core/src/traits.rs b/primitives/core/src/traits.rs index 80e8963a2909d..9ba35dd402548 100644 --- a/primitives/core/src/traits.rs +++ b/primitives/core/src/traits.rs @@ -278,3 +278,10 @@ impl SpawnEssentialNamed for Box { (**self).spawn_essential(name, group, future) } } + +/// Create random values of `Self` given a stream of entropy. +pub trait FromEntropy { + /// Create a random value of `Self` given a stream of random bytes on `input`. May only fail if + /// `input` has an error. + fn from_entropy(input: &mut codec::Input) -> Result; +} diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index 39e606eb9b5f4..e420a8a42377f 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -49,6 +49,7 @@ use sp_core::{ ecdsa, ed25519, hash::{H256, H512}, sr25519, + traits::FromEntropy, }; use sp_std::prelude::*; @@ -169,7 +170,7 @@ impl From for Justifications { } } -use traits::{Lazy, Verify}; +use traits::{Lazy, Verify, FromEntropy}; use crate::traits::IdentifyAccount; #[cfg(feature = "std")] @@ -302,6 +303,16 @@ pub enum MultiSigner { Ecdsa(ecdsa::Public), } +impl FromEntropy for MultiSigner { + fn from_entropy(input: &mut codec::Input) -> Result { + Ok(match input.read_byte() % 3 { + 0 => Self::Ed25519(FromEntropy::try_from_entropy(input)?), + 1 => Self::Sr25519(FromEntropy::try_from_entropy(input)?), + 2.. => Self::Ecdsa(FromEntropy::try_from_entropy(input)?), + }) + } +} + /// NOTE: This implementations is required by `SimpleAddressDeterminer`, /// we convert the hash into some AccountId, it's fine to use any scheme. impl> crypto::UncheckedFrom for MultiSigner { From 135009ccca8af79c749db7270e51a924ae6c2d4d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 30 Apr 2022 15:32:07 +0200 Subject: [PATCH 02/43] More logic drafting --- frame/society/src/lib.rs | 473 +++++++++++++------------ frame/support/src/storage/mod.rs | 13 + frame/support/src/storage/types/map.rs | 8 + 3 files changed, 264 insertions(+), 230 deletions(-) diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index b18140fc989e8..f526e33e43907 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -355,6 +355,28 @@ pub type RoundIndex = u32; /// The rank of a member. pub type Rank = u32; +/// The number of votes. +pub type Votes = u32; + +/// Tally of votes. +#[derive(Default, Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct Tally { + /// The approval votes. + approvals: Votes, + /// The rejection votes. + rejections: Votes, +} + +impl Tally { + fn more_approvals(&self) -> bool { + self.approvals > self.rejections + } + + fn more_rejections(&self) -> bool { + self.rejections > self.approvals + } +} + /// A bid for entry into society. #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub struct Candidacy { @@ -364,16 +386,14 @@ pub struct Candidacy { kind: BidKind, /// The reward that the bidder has requested for successfully joining the society. bid: Balance, - /// The tally of explicit approval votes so far. - approvals: u32, - /// The tally of explicit rejection votes so far. - rejections: u32, + /// The tally of votes so far. + tally: Tally, } /// A vote by a member on a candidate application. #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub enum BidKind { - /// The CandidateDeposit was paid for this bid. + /// The given deposit was paid for this bid. Deposit(Balance), /// A member vouched for this bid. The account should be reinstated into `Members` once the /// bid is successful (or if it is rescinded prior to launch). @@ -381,16 +401,8 @@ pub enum BidKind { } impl BidKind { - fn check_voucher(&self, v: &AccountId) -> DispatchResult { - if let BidKind::Vouch(ref a, _) = self { - if a == v { - Ok(()) - } else { - Err("incorrect identity")? - } - } else { - Err("not vouched")? - } + fn is_vouch(&self, v: &AccountId) -> DispatchResult { + matches!(self, BidKind::Vouch(ref a, _) if a == v) } } @@ -429,6 +441,16 @@ pub type IntakeRecordFor = IntakeRecord< BalanceOf, >; +#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct GroupParams { + max_members: u32, + max_intake: u32, + max_strikes: u32, + candidate_deposit: Balance, +} + +pub type GroupParamsFor = GroupParams>; + #[frame_support::pallet] pub mod pallet { use super::*; @@ -453,22 +475,7 @@ pub mod pallet { /// Something that provides randomness in the runtime. type Randomness: Randomness; - /// The minimum amount of a deposit required for a bid to be made. - #[pallet::constant] - type CandidateDeposit: Get>; - - /// 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>; - - /// The number of times a skeptic may not vote or vote the wrong way for a candidate before - /// they become suspended. - #[pallet::constant] - type MaxStrikes: Get; - - /// The number of times a skeptic may not vote or vote the wrong way for a candidate before - /// they get any payouts slashed in half. + /// The maximum number of strikes before a member gets funds slashed. #[pallet::constant] type GraceStrikes: Get; @@ -493,20 +500,10 @@ pub mod pallet { /// The origin that is allowed to call `found`. type FounderSetOrigin: EnsureOrigin; - /// The origin that is allowed to make suspension judgements. - type JudgementOrigin: EnsureOrigin; - /// The number of blocks between membership challenges. #[pallet::constant] type ChallengePeriod: Get; - /// The maximum number of candidates that we accept per round. - #[pallet::constant] - type MaxCandidateIntake: Get; - - /// The origin that is allowed to set the maximum number of members. - type AdminOrigin: Get; - /// The maximum number of payouts a member may have waiting unclaimed. #[pallet::constant] type MaxPayouts: Get; @@ -518,8 +515,6 @@ pub mod pallet { #[pallet::error] pub enum Error { - /// An incorrect position was provided. - BadPosition, /// User is not a member. NotMember, /// User is already a member. @@ -568,6 +563,10 @@ pub mod pallet { Expired, /// User is not a bidder. NotBidder, + /// There is no defender currently. + NoDefender, + /// Group doesn't exist. + NotGroup, } #[pallet::event] @@ -602,8 +601,8 @@ pub mod pallet { Vote { candidate: T::AccountId, voter: T::AccountId, vote: bool }, /// A vote has been placed for a defending member DefenderVote { voter: T::AccountId, vote: bool }, - /// A new \[max\] member count has been set - NewMaxMembers { max: u32 }, + /// A new set of \[params\] has been set for the group. + NewParams { params: GroupParamsFor }, /// Society is unfounded. Unfounded { founder: T::AccountId }, /// Some funds were deposited into the society account. @@ -616,7 +615,7 @@ pub mod pallet { /// The max number of members for the society at one time. #[pallet::storage] - pub(super) type MaxMembers, I: 'static = ()> = StorageValue<_, u32, ValueQuery>; + pub(super) type Parameters, I: 'static = ()> = StorageValue<_, GroupParamsFor, OptionQuery>; /// Amount of our account balance that is specifically for the next round's bid(s). #[pallet::storage] @@ -632,14 +631,23 @@ pub mod pallet { /// A hash of the rules of this society concerning membership. Can only be set once and /// only by the founder. - // TODO: Should be a map with rules for each rank and badge. + // TODO: Should be a map with rules for each rank and faction. #[pallet::storage] pub type Rules, I: 'static = ()> = StorageValue<_, T::Hash>; /// The current members and their rank. Doesn't include `SuspendedMembers`. #[pallet::storage] - pub type Membership, I: 'static = ()> = + pub type Members, I: 'static = ()> = StorageMap<_, Twox64Concat, T::AccountId, MemberRecordFor, OptionQuery>; + /* + // TODO: Migrate from: + pub(super) type Vouching, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, VouchingStatus>; + pub(super) type Strikes, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, StrikeCount, ValueQuery>; + pub type Members, I: 'static = ()> = + StorageValue<_, Vec, ValueQuery>; + */ /// The number of items in `Membership` currently. (Doesn't include `SuspendedMembers`.) #[pallet::storage] @@ -665,15 +673,20 @@ pub mod pallet { pub(super) type Bids, I: 'static = ()> = StorageValue<_, BoundedVec>, T::MaxBids>, ValueQuery>; + /* + // TODO: Migrate from: + pub type Candidates, I: 'static = ()> = + StorageValue<_, Vec>>, ValueQuery>; + */ #[pallet::storage] - pub type Candidate, I: 'static = ()> = StorageMap<_, + pub type Candidates, I: 'static = ()> = StorageMap<_, Blake2_128Concat, T::AccountId, Candidacy>, OptionQuery, >; - /// The current skeptics. + /// The current skeptic. #[pallet::storage] pub type Skeptic, I: 'static = ()> = StorageValue<_, Vec, ValueQuery>; @@ -700,57 +713,19 @@ pub mod pallet { OptionQuery, >; - /// The defending member currently being challenged, along with a running tally of approval and - /// rejection votes. + /// The defending member currently being challenged, along with a running tally of votes. #[pallet::storage] - pub(super) type Defending, I: 'static = ()> = StorageValue<_, (T::AccountId, T::AccountId, u32, u32)>; + pub(super) type Defending, I: 'static = ()> = StorageValue<_, (T::AccountId, T::AccountId, Tally)>; /// Votes for the defender. - // TODO: Migrate (used to be `OldVote`) #[pallet::storage] pub(super) type DefenderVotes, I: 'static = ()> = StorageMap<_, Twox64Concat, T::AccountId, Vote>; - - // OLD STUFF - - // Moved to `Member`. - // TODO: Needs refactor, migration into Membership, removal. - /// Members currently vouching or banned from vouching again. - #[pallet::storage] - pub(super) type Vouching, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, VouchingStatus>; - - /// The ongoing number of losing votes cast by the member. - // TODO: Needs refactor, migration into Membership, removal. - #[pallet::storage] - pub(super) type Strikes, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, StrikeCount, ValueQuery>; - - /// The current set of members, ordered. - // TODO: Needs refactor, migration into Membership, removal. - #[pallet::storage] - #[pallet::getter(fn members)] - pub type Members, I: 'static = ()> = - StorageValue<_, Vec, ValueQuery>; - - /// The current set of candidates; bidders that are attempting to become members. - // TODO: Needs refactor, migration into Candidate, removal. - #[pallet::storage] - #[pallet::getter(fn candidates)] - pub type Candidates, I: 'static = ()> = - StorageValue<_, Vec>>, ValueQuery>; - - /// The set of suspended candidates. - // TODO: Needs refactor and removal. - // TODO: Ensure that it is empty immediately prior to upgrade. - #[pallet::storage] - #[pallet::getter(fn suspended_candidate)] - pub type SuspendedCandidates, I: 'static = ()> = StorageMap< - _, - Twox64Concat, - T::AccountId, - (BalanceOf, BidKind>), - >; + /* + // TODO: Migrate from + pub(super) type DefenderVotes, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, OldVote>; + */ #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { @@ -807,6 +782,12 @@ pub mod pallet { } } + mod migrations { + fn to_v2() { + + } + } + #[pallet::genesis_build] impl, I: 'static> GenesisBuild for GenesisConfig { fn build(&self) { @@ -827,7 +808,7 @@ pub mod pallet { impl, I: 'static> Pallet { /// A user outside of the society can make a bid for entry. /// - /// Payment: `CandidateDeposit` will be reserved for making a bid. It is returned + /// Payment: The group's Candidate Deposit will be reserved for making a bid. It is returned /// when the bid becomes a member, or if the bid calls `unbid`. /// /// The dispatch origin for this call must be _Signed_. @@ -840,16 +821,19 @@ pub mod pallet { #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn bid(origin: OriginFor, value: BalanceOf) -> DispatchResult { let who = ensure_signed(origin)?; - ensure!(!SuspendedMembers::::contains_key(&who), Error::::Suspended); + let mut bids = Bids::::get(); ensure!(!Self::has_bid(&bids, &who), Error::::AlreadyBid); - ensure!(!Candidate::::contains_key(&who), Error::::AlreadyCandidate); - ensure!(!Membership::::contains_key(&who), Error::::AlreadyMember); + ensure!(!Candidates::::contains_key(&who), Error::::AlreadyCandidate); + ensure!(!Members::::contains_key(&who), Error::::AlreadyMember); + ensure!(!SuspendedMembers::::contains_key(&who), Error::::Suspended); + + let params = Parameters::::get().ok_or(Error::::NotGroup)?; + let deposit = params.candidate_deposit; + Self::insert_bid(&mut bids, &who, value.clone(), BidKind::Deposit(deposit)); - let deposit = T::CandidateDeposit::get(); T::Currency::reserve(&who, deposit)?; - Self::insert_bid(&mut bids, &who, value.clone(), BidKind::Deposit(deposit)); Bids::::put(bids); Self::deposit_event(Event::::Bid { candidate_id: who, offer: value }); Ok(()) @@ -905,23 +889,28 @@ pub mod pallet { tip: BalanceOf, ) -> DispatchResult { let voucher = ensure_signed(origin)?; - // Check user is not suspended. - ensure!(!SuspendedCandidates::::contains_key(&who), Error::::Suspended); - ensure!(!SuspendedMembers::::contains_key(&who), Error::::Suspended); - // Check user is not a bid or candidate. - let bids = Bids::::get(); + + // Get bids and check user is not bidding. + let mut bids = Bids::::get(); ensure!(!Self::has_bid(&bids, &who), Error::::AlreadyBid); - let candidates = Candidates::::get(); - ensure!(!Self::is_candidate(&candidates, &who), Error::::AlreadyCandidate); - // Check user is not already a member. - let members = Members::::get(); - ensure!(!Self::is_member(&members, &who), Error::::AlreadyMember); + + // Check user is not already a candidate, member or suspended member. + ensure!(!Candidates::::contains_key(&who), Error::::AlreadyCandidate); + ensure!(!Members::::contains_key(&who), Error::::AlreadyMember); + ensure!(!SuspendedMembers::::contains_key(&who), Error::::Suspended); + // Check sender can vouch. - ensure!(Self::is_member(&members, &voucher), Error::::NotMember); - ensure!(!Vouching::::contains_key(&voucher), Error::::AlreadyVouching); + let mut record = Members::::get(voucher).ok_or(Error::::NotMember)?; + ensure!(record.vouching.is_none(), Error::::AlreadyVouching); - Vouching::::insert(&voucher, VouchingStatus::Vouching); - Self::put_bid(bids, &who, value.clone(), BidKind::Vouch(voucher.clone(), tip)); + // Update voucher record. + record.vouching = Some(VouchingStatus::Vouching); + // Update bids + Self::insert_bid(&mut bids, &who, value.clone(), BidKind::Vouch(voucher.clone(), tip)); + + // Write new state. + Members::::insert(&voucher, &record); + Bids::::put(bids); Self::deposit_event(Event::::Vouch { candidate_id: who, offer: value, @@ -941,25 +930,18 @@ pub mod pallet { /// Key: B (len of bids) /// Total Complexity: O(B) #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn unvouch(origin: OriginFor, pos: u32) -> DispatchResult { + pub fn unvouch(origin: OriginFor) -> DispatchResult { let voucher = ensure_signed(origin)?; - ensure!( - Vouching::::get(&voucher) == Some(VouchingStatus::Vouching), - Error::::NotVouching - ); - - let pos = pos as usize; - Bids::::mutate(|b| { - if pos < b.len() { - b[pos].kind.check_voucher(&voucher)?; - Vouching::::remove(&voucher); - let who = b.remove(pos).who; - Self::deposit_event(Event::::Unvouch { candidate: who }); - Ok(()) - } else { - Err(Error::::BadPosition)? - } - }) + + let mut bids = Bids::::get(); + let pos = bids.iter().position(|bid| bid.kind.is_vouch(&voucher)) + .ok_or(Error::::NotVouching)?; + let bid = bids.remove(pos); + Self::clean_bid(&bid); + + Bids::::put(bids); + Self::deposit_event(Event::::Unvouch { candidate: bid.who }); + Ok(()) } /// As a member, vote on a candidate. @@ -982,23 +964,14 @@ pub mod pallet { let voter = ensure_signed(origin)?; let candidate = T::Lookup::lookup(candidate)?; - let mut candidacy = Candidate::::get(candidate).ok_or(Error::::NotCandidate)?; - let record = Membership::::get(&voter).ok_or(Error::::NotMember)?; + let mut candidacy = Candidates::::get(candidate).ok_or(Error::::NotCandidate)?; + let record = Members::::get(&voter).ok_or(Error::::NotMember)?; - // remove the old vote from the count, if there was one. - match Votes::::get(&candidate, &voter) { - Some(Vote { approve: true, weight }) => candidacy.approves.saturating_reduce(weight), - Some(Vote { approve: false, weight }) => candidacy.rejects.saturating_reduce(weight), - _ => {}, - } - let weight_root = record.rank + 1; - let weight = weight_root * weight_root; - match approve { - true => candidacy.approves.saturating_accrue(1), - false => candidacy.rejects.saturating_accrue(1), - } - Votes::::insert(&candidate, &voter, Vote { approve, weight }); - Candidate::::insert(&candidate, &candidacy); + Votes::::mutate(&candidate, &voter, |v| { + v = Some(Self::do_vote(v, approve, record.rank, &mut candidacy.tally)); + }); + + Candidates::::insert(&candidate, &candidacy); Self::deposit_event(Event::::Vote { candidate, voter, vote: approve }); Ok(()) } @@ -1017,12 +990,15 @@ pub mod pallet { #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn defender_vote(origin: OriginFor, approve: bool) -> DispatchResult { let voter = ensure_signed(origin)?; - let members = Members::::get(); - ensure!(Self::is_member(&members, &voter), Error::::NotMember); - let vote = if approve { Vote::Approve } else { Vote::Reject }; - DefenderVotes::::insert(&voter, vote); + let defending = Defending::::get().ok_or(Error::::NoDefender)?; + let record = Members::::get(&voter).ok_or(Error::::NotMember)?; + + DefenderVotes::::mutate(&voter, |v| { + v = Some(Self::do_vote(v, approve, record.rank, &mut defending.2)); + }); + Defending::::put(defending); Self::deposit_event(Event::::DefenderVote { voter, vote: approve }); Ok(()) } @@ -1043,14 +1019,14 @@ pub mod pallet { #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let mut record = Membership::::get(&who).ok_or(Error::::NotMember)?; + let mut record = Members::::get(&who).ok_or(Error::::NotMember)?; if let Some((when, amount)) = record.payouts.first() { if when <= &>::block_number() { record.paid = record.paid.checked_add(amount).ok_or(Overflow)?; T::Currency::transfer(&Self::payouts(), &who, *amount, AllowDeath)?; record.payouts.remove(0); - Membership::::insert(&who, record); + Members::::insert(&who, record); return Ok(()) } } @@ -1081,22 +1057,30 @@ pub mod pallet { /// Parameters: /// - `founder` - The first member and head of the newly founded society. /// - `max_members` - The initial max number of members for the society. + /// - `max_intake` - The maximum number of candidates per intake period. + /// - `max_strikes`: The maximum number of strikes a member may get before they become + /// suspended and may only be reinstated by the founder. + /// - `candidate_deposit`: The deposit required to make a bid for membership of the group. /// - `rules` - The rules of this society concerning membership. /// /// Complexity: O(1) #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn found( + pub fn found_society( origin: OriginFor, founder: T::AccountId, max_members: u32, + max_intake: u32, + max_strikes: u32, + candidate_deposit: BalanceOf, rules: Vec, ) -> DispatchResult { T::FounderSetOrigin::ensure_origin(origin)?; ensure!(!Head::::exists(), Error::::AlreadyFounded); ensure!(max_members > 1, Error::::MaxMembers); // This should never fail in the context of this function... - MaxMembers::::put(max_members); - Self::add_member(&founder, 1, BoundedVec::new())?; + let params = GroupParams { max_members, max_intake, max_strikes, candidate_deposit }; + Parameters::::put(params); + Self::insert_member(&founder, 1, BoundedVec::new())?; Head::::put(&founder); Founder::::put(&founder); Rules::::put(T::Hashing::hash(&rules)); @@ -1104,7 +1088,7 @@ pub mod pallet { Ok(()) } - /// Annul the founding of the society. + /// Dissolve the society and remove all members. /// /// The dispatch origin for this call must be Signed, and the signing account must be both /// the `Founder` and the `Head`. This implies that it may only be done when there is one @@ -1118,22 +1102,20 @@ pub mod pallet { /// Total Complexity: O(1) /// # #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn unfound(origin: OriginFor) -> DispatchResult { + pub fn dissolve(origin: OriginFor) -> DispatchResult { let founder = ensure_signed(origin)?; ensure!(Founder::::get() == Some(founder.clone()), Error::::NotFounder); ensure!(Head::::get() == Some(founder.clone()), Error::::NotHead); - Members::::kill(); + Members::::remove_all(None); MemberCount::::kill(); MemberByIndex::::remove_all(None); - Membership::::remove_all(None); + Members::::remove_all(None); Votes::::remove_all(None); Head::::kill(); Founder::::kill(); Rules::::kill(); - Candidates::::kill(); - Candidate::::remove_all(None); - SuspendedCandidates::::remove_all(None); + Candidates::::remove_all(None); Self::deposit_event(Event::::Unfounded { founder }); Ok(()) } @@ -1146,7 +1128,7 @@ pub mod pallet { /// If a suspended member is rejected, remove all associated storage items, including /// their payouts, and remove any vouched bids they currently have. /// - /// The dispatch origin for this call must be from the _JudgementOrigin_. + /// The dispatch origin for this call must be Signed from the Founder. /// /// Parameters: /// - `who` - The suspended member to be judged. @@ -1161,7 +1143,9 @@ pub mod pallet { who: T::AccountId, forgive: bool, ) -> DispatchResult { - T::JudgementOrigin::ensure_origin(origin)?; + let founder = ensure_signed(origin)?; + ensure!(Founder::::get() == Some(founder.clone()), Error::::NotFounder); + let record = SuspendedMembers::::get(&who).ok_or(Error::::NotSuspended)?; if forgive { @@ -1176,21 +1160,33 @@ pub mod pallet { Ok(()) } - /// Allows root origin to change the maximum number of members in society. - /// New max membership count must be no less than the current number of members. + /// Change the maximum number of members in society and the maximum number of new candidates + /// in a single intake period. /// - /// The dispatch origin for this call must be Root. + /// The dispatch origin for this call must be Signed by the Founder. /// /// Parameters: - /// - `max` - The maximum number of members for the society. + /// - `max_members` - The maximum number of members for the society. This must be no + /// less than the current number of members. + /// - `max_intake` - The maximum number of candidates per intake period. + /// - `max_strikes`: The maximum number of strikes a member may get before they become + /// suspended and may only be reinstated by the founder. + /// - `candidate_deposit`: The deposit required to make a bid for membership of the group. /// /// Total Complexity: O(1) #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn set_max_members(origin: OriginFor, max: u32) -> DispatchResult { - T::AdminOrigin::ensure_origin(origin)?; - ensure!(max >= MemberCount::::get(), Error::::MaxMembers); - MaxMembers::::put(max); - Self::deposit_event(Event::::NewMaxMembers { max }); + pub fn set_parameters( + origin: OriginFor, + max_members: u32, + max_intake: u32, + max_strikes: u32, + candidate_deposit: BalanceOf, + ) -> DispatchResult { + ensure!(ensure_signed(origin)? == Founder::::get(), Error::::NotFounder); + ensure!(max_members >= MemberCount::::get(), Error::::MaxMembers); + let params = GroupParams { max_members, max_intake, max_strikes, candidate_deposit }; + Parameters::::put(¶ms); + Self::deposit_event(Event::::NewParams { params }); Ok(()) } @@ -1199,32 +1195,35 @@ pub mod pallet { #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn claim_membership(origin: OriginFor) -> DispatchResult { let candidate = ensure_signed(origin)?; - let candidacy = Candidate::::get(&candidate).ok_or(Error::::NotCandidate)?; - ensure!(candidacy.approvals > candidacy.rejections, Error::::NotApproved); + let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + ensure!(candidacy.tally.more_approvals(), Error::::NotApproved); ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); Self::induct_member(candidate, candidacy, 0) } - /// Transform an approved candidate into a member. Callable only by the - /// `JudgementOrigin` and only after the period for voting has ended. + /// Transform an approved candidate into a member. Callable only by the Signed origin of the + /// Founder and only after the period for voting has ended. #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn force_membership(origin: OriginFor, candidate: T::AccountId) -> DispatchResult { - T::JudgementOrigin::ensure_origin(origin)?; - let candidacy = Candidate::::get(&candidate).ok_or(Error::::NotCandidate)?; + let founder = ensure_signed(origin)?; + ensure!(Founder::::get() == Some(founder.clone()), Error::::NotFounder); + + let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); Self::induct_member(candidate, candidacy, 0) } - /// Remove the candidate's application from the society. Callable only by the - /// `JudgementOrigin` and the candidate, and only after the period for voting has ended. + /// Remove the candidate's application from the society. Callable only by the Signed origin + /// of the Founder, and only after the period for voting has ended. #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn kick_candidate(origin: OriginFor, candidate: T::AccountId) -> DispatchResult { - T::JudgementOrigin::ensure_origin(origin)?; - let candidacy = Candidate::::get(&candidate).ok_or(Error::::NotCandidate)?; + let founder = ensure_signed(origin)?; + ensure!(Founder::::get() == Some(founder.clone()), Error::::NotFounder); + let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); Self::check_skeptic(&candidate, &candidacy); Votes::::remove_prefix(&candidate, None); - Candidate::::remove(&candidate); + Candidates::::remove(&candidate); Ok(()) } @@ -1233,12 +1232,12 @@ pub mod pallet { #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn resign_candidate(origin: OriginFor) -> DispatchResult { let candidate = ensure_signed(origin)?; - let candidacy = Candidate::::get(&candidate).ok_or(Error::::NotCandidate)?; - ensure!(candidacy.approvals <= candidacy.rejections, Error::::Approved); + let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + ensure!(!candidacy.tally.more_approvals(), Error::::Approved); ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); Self::check_skeptic(&candidate, &candidacy); Votes::::remove_prefix(&candidate, None); - Candidate::::remove(&candidate); + Candidates::::remove(&candidate); Ok(()) } @@ -1247,11 +1246,11 @@ pub mod pallet { #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn drop_candidate(origin: OriginFor, candidate: T::AccountId) -> DispatchResult { ensure_signed(origin)?; - let candidacy = Candidate::::get(&candidate).ok_or(Error::::NotCandidate)?; - ensure!(candidacy.approvals <= candidacy.rejections, Error::::Approved); + let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + ensure!(!candidacy.tally.more_approvals(), Error::::Approved); ensure!(RoundCount::::get() > candidacy.round + 1, Error::::TooEarly); Votes::::remove_prefix(&candidate, None); - Candidate::::remove(&candidate); + Candidates::::remove(&candidate); Ok(()) } } @@ -1275,20 +1274,7 @@ impl EnsureOrigin for EnsureFounder { } } -/// Pick an item at pseudo-random from the slice, given the `rng`. `None` iff the slice is empty. -fn pick_item<'a, R: RngCore, T>(rng: &mut R, items: &'a [T]) -> Option<&'a T> { - if items.is_empty() { - None - } else { - Some(&items[pick_usize(rng, items.len() - 1)]) - } -} - -/// Pick a new PRN, in the range [0, `max`] (inclusive). -fn pick_usize(rng: &mut impl RngCore, max: usize) -> usize { - (rng.next_u32() % (max as u32 + 1)) as usize -} - +// TODO: Move close to `FromEntropy`? struct InputFromRng<'a, T>(&'a mut T); impl<'a, T: RngCore> codec::Input for InputFromRng<'a, T> { fn remaining_len(&mut self) -> Result, Error> { @@ -1327,13 +1313,28 @@ impl, I: 'static> Pallet { target_round == round && matches!(Self::period(), Period::Voting {..}) } + fn do_vote(maybe_old: Option, approve: bool, rank: Rank, tally: &mut Tally) -> Vote { + match maybe_old { + Some(Vote { approve: true, weight }) => tally.approvals.saturating_reduce(weight), + Some(Vote { approve: false, weight }) => tally.rejections.saturating_reduce(weight), + _ => {}, + } + let weight_root = rank + 1; + let weight = weight_root * weight_root; + match approve { + true => tally.approvals.saturating_accrue(1), + false => tally.rejections.saturating_accrue(1), + } + Vote { approve, weight } + } + fn check_skeptic(candidate: &T::AccountId, candidacy: &Candidacy>) { if RoundCount::::get() != candidacy.round { return } // We expect the skeptic to have voted. let skeptic = Skeptic::::get(); let maybe_vote = Votes::::get(&candidate, &skeptic); - let approved = candidacy.approvals > candidacy.rejections; - let rejected = candidacy.rejections > candidacy.approvals; + let approved = candidacy.tally.more_approvals(); + let rejected = candidacy.tally.more_rejections(); match (maybe_vote, approved, rejected) { (None, _, _) | (Some(false), true, false) | (Some(true), false, true) => { // Can't do much if the punishment doesn't work out. @@ -1345,17 +1346,25 @@ impl, I: 'static> Pallet { /// End the current challenge period and start a new one. fn rotate_challenge(rng: &mut impl RngCore) { - // TODO. // End current defender rotation - if let Some((defender, skeptic, approvals, rejections)) = Defending::::get() { - if rejections > approvals { + if let Some((defender, skeptic, tally)) = Defending::::get() { + if tally.more_rejections() { // Member has failed the challenge Self::suspend_member(&defender); } - // TODO: check skeptic voted and that their vote was with the majority. Slash if not. + // Check defender skeptic voted and that their vote was with the majority. + let skeptic_vote = DefenderVotes::::get(&skeptic); + match (skeptic_vote, tally.more_approvals(), tally.more_rejections()) { + (None, _, _) | (Some(true), false, true) | (Some(false), true, false) => { + // Punish skeptic. + let _ = Self::strike_member(&skeptic); + } + } // Clean up all votes. + // NOTE: To do lazy removal of this we'll need to store the challenge round so that + // voting when there's a stale vote does not lead to incorrect arithmetic. DefenderVotes::::remove_all(None); } @@ -1419,11 +1428,11 @@ impl, I: 'static> Pallet { member_count: u32, pot: BalanceOf, ) -> usize { - let max_members = MaxMembers::::get() as usize; + let max_members = Parameters::::get().max_members as usize; // Get the number of left-most bidders whose bids add up to less than `pot`. let mut bids = Bids::::get(); - let mut max_selections: usize = (T::MaxCandidateIntake::get() as usize) + let mut max_selections: usize = (Parameters::::get().max_intake as usize) .min(max_members.saturating_sub(member_count)) .min(bids.len()); @@ -1442,10 +1451,9 @@ impl, I: 'static> Pallet { round, kind: bid.kind, bid: bid.value, - approvals: 0, - rejections: 0, + tally: Default::default(), }; - Candidate::::insert(&bid.who, candidacy); + Candidates::::insert(&bid.who, candidacy); selections.saturating_inc(); } !accept @@ -1476,6 +1484,9 @@ impl, I: 'static> Pallet { /// /// In neither case can we do much if the action isn't completable, but there's /// no reason that either should fail. + /// + /// WARNING: This alters the voucher item of `Members`. You must ensure that you do not + /// accidentally overwrite it with an older value after calling this. fn clean_bid(bid: &Bid>) { match &bid.kind { BidKind::Deposit(deposit) => { @@ -1483,7 +1494,7 @@ impl, I: 'static> Pallet { debug_assert!(err_amount.is_zero()); }, BidKind::Vouch(voucher, _) => { - Vouching::::remove(voucher); + Members::::mutate_extant(voucher, |record| record.vouching = None); }, } } @@ -1504,11 +1515,12 @@ impl, I: 'static> Pallet { /// NOTE: Generally you should not use this, and instead use `add_new_member` or /// `reinstate_member`, whose names clearly match the desired intention. fn insert_member(who: &T::AccountId, rank: Rank, payouts: PayoutsFor) -> DispatchResult { - ensure!(MemberCount::get() < MaxMembers::::get() as usize, Error::::MaxMembers); + let params = Parameters::::get().ok_or(Error::::NotGroup); + ensure!(MemberCount::get() < params.max_members as usize, Error::::MaxMembers); let index = MemberCount::::mutate(|i| { i.saturating_accrue(1); *i - 1 }); let paid = Zero::zero(); let record = MemberRecord { rank, strikes: 0, vouching: None, paid, index, payouts }; - Membership::::insert(who, record); + Members::::insert(who, record); MemberByIndex::::insert(index, who); Ok(()) } @@ -1546,19 +1558,20 @@ impl, I: 'static> Pallet { let maturity = now + Self::lock_duration(MemberCount::::get()); Self::reward_bidder(&candidate, candidacy.bid, candidacy.kind, maturity); Votes::::remove_prefix(&candidate, None); - Candidate::::remove(&candidate); + Candidates::::remove(&candidate); Ok(()) } fn strike_member(who: &T::AccountId) -> DispatchResult { - let mut record = Membership::::get(who).ok_or(NotMember)?; + let mut record = Members::::get(who).ok_or(NotMember)?; record.strikes.saturating_inc(); if record.strikes >= T::GraceStrikes::get() { // Too many strikes: slash the payout in half. let total_payout = record.payouts.iter().map(|x| x.1).sum(); Self::slash_payout(who, total_payout / 2u32.into()); } - if record.strikes >= T::MaxStrikes::get() { + let params = Parameters::::get().ok_or(Error::::NotGroup)?; + if record.strikes >= params.max_strikes { // Way too many strikes: suspend. Self::suspend_member(who); } @@ -1578,14 +1591,14 @@ impl, I: 'static> Pallet { pub fn remove_member(m: &T::AccountId) -> Result { ensure!(Head::::get().as_ref() != Some(m), Error::::Head); ensure!(Founder::::get().as_ref() != Some(m), Error::::Founder); - if let Some(record) = Membership::::get(m) { + if let Some(record) = Members::::get(m) { let index = record.index; let last_index = MemberCount::::mutate(|i| { i.saturating_reduce(1); *i }); if index != last_index { // Move the member with the last index down to the index of the member to be removed. if let Some(other) = MemberByIndex::::get(last_index) { MemberByIndex::::insert(index, other); - Membership::::mutate(other, |m_r| + Members::::mutate(other, |m_r| if let Some(r) = m_r { r.index = index } ); } else { @@ -1594,7 +1607,7 @@ impl, I: 'static> Pallet { } MemberByIndex::::remove(last_index); - Membership::::remove(m); + Members::::remove(m); // Remove their vouching status, potentially unbanning them in the future. if record.vouching.take() == Some(VouchingStatus::Vouching) { // Try to remove their bid if they are vouching. @@ -1718,7 +1731,7 @@ impl, I: 'static> Pallet { /// is not a member or if `value` is zero. fn bump_payout(who: &T::AccountId, when: T::BlockNumber, value: BalanceOf) { if !value.is_zero() { - Membership::::mutate(who, |maybe_record| if let Some(record) = maybe_record { + Members::::mutate(who, |maybe_record| if let Some(record) = maybe_record { if record.rank == 0 { // Members of rank 1 never get payouts. match record.payouts.binary_search_by_key(&when, |x| x.0) { @@ -1733,7 +1746,7 @@ impl, I: 'static> Pallet { /// Attempt to slash the payout of some member. Return the total amount that was deducted. fn slash_payout(who: &T::AccountId, value: BalanceOf) -> BalanceOf { - let mut record = match Membership::::get(who) { + let mut record = match Members::::get(who) { Some(record) => record, None => return Zero::zero(), }; @@ -1750,7 +1763,7 @@ impl, I: 'static> Pallet { break } } - Membership::::insert(who, record); + Members::::insert(who, record); value - rest } diff --git a/frame/support/src/storage/mod.rs b/frame/support/src/storage/mod.rs index 066422ad456aa..3b617d3181fd7 100644 --- a/frame/support/src/storage/mod.rs +++ b/frame/support/src/storage/mod.rs @@ -184,6 +184,19 @@ pub trait StorageMap { f: F, ) -> Result; + /// Mutate the value under a key if the value already exists. Do nothing and return the default + /// value if not. + fn mutate_extant< + KeyArg: EncodeLike, + R: Default, + F: FnOnce(&mut V) -> R, + >(key: KeyArg, f: F) -> R { + Self::mutate_exists(key, |maybe_v| match maybe_v { + Some(ref mut value) => f(value), + None => R::default(), + }) + } + /// Mutate the value under a key. /// /// Deletes the item if mutated to a `None`. diff --git a/frame/support/src/storage/types/map.rs b/frame/support/src/storage/types/map.rs index 009a0c9fadb8b..e786c1fd4357d 100644 --- a/frame/support/src/storage/types/map.rs +++ b/frame/support/src/storage/types/map.rs @@ -165,6 +165,14 @@ where >::try_mutate(key, f) } + /// Mutate the value under a key iff it exists. Do nothing and return the default value if not. + pub fn mutate_extant, R, F: FnOnce(&mut Value) -> R>( + key: KeyArg, + f: F, + ) -> R { + >::mutate_extant(key, f) + } + /// Mutate the value under a key. Deletes the item if mutated to a `None`. pub fn mutate_exists, R, F: FnOnce(&mut Option) -> R>( key: KeyArg, From 01c68345f0b7d35789d1147c4ae08be6448892f3 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 3 May 2022 12:27:29 +0200 Subject: [PATCH 03/43] More work --- frame/society/src/lib.rs | 100 +++++++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 30 deletions(-) diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index f526e33e43907..ce36c799c1477 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -30,7 +30,6 @@ //! At any point, a user in the society can be one of a: //! * Bidder - A user who has submitted intention of joining the society. //! * Candidate - A user who will be voted on to join the society. -//! * Suspended Candidate - A user who failed to win a vote. //! * Member - A user who is a member of the society. //! * Suspended Member - A member of the society who has accumulated too many strikes //! or failed their membership challenge. @@ -413,16 +412,21 @@ pub type PayoutsFor = BoundedVec< /// Information concerning a member. #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub struct MemberRecord { +pub struct MemberRecord { rank: Rank, strikes: StrikeCount, vouching: Option, - paid: Balance, index: u32, +} + +/// Information concerning a member. +#[derive(Default, Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct PayoutRecord { + paid: Balance, payouts: BoundedVec<(BlockNumber, Balance), MaxPayouts>, } -pub type MemberRecordFor = MemberRecord< +pub type PayoutRecordFor = PayoutRecord< BalanceOf, ::BlockNumber, >::MaxPayouts, @@ -451,6 +455,21 @@ pub struct GroupParams { pub type GroupParamsFor = GroupParams>; +pub type FactionIndex = u32; + +pub enum Faction { + Society, + Splinter(FactionIndex), +} + +/// A group is just the rank of a faction. +pub struct Group { + faction: Faction, + rank: Rank, +} + +//TODO: Ranks cannot be indexed linearly. Should just be done as Groups DAG. + #[frame_support::pallet] pub mod pallet { use super::*; @@ -567,6 +586,8 @@ pub mod pallet { NoDefender, /// Group doesn't exist. NotGroup, + /// The member is already elevated to this rank. + AlreadyElevated, } #[pallet::event] @@ -607,6 +628,8 @@ pub mod pallet { Unfounded { founder: T::AccountId }, /// Some funds were deposited into the society account. Deposit { value: BalanceOf }, + /// A \[member\] got elevated to \[rank\]. + Elevated { member: T::AccountId, rank: Rank }, } /// Old name generated by `decl_event`. @@ -622,23 +645,25 @@ pub mod pallet { pub type Pot, I: 'static = ()> = StorageValue<_, BalanceOf, ValueQuery>; /// The first member. + // TODO: Rename to `Faction` as a map on `FactionIndex`. #[pallet::storage] pub type Founder, I: 'static = ()> = StorageValue<_, T::AccountId>; - /// The most primary from the most recently approved members. + /// The most primary from the most recently approved rank 0 members in the society. #[pallet::storage] pub type Head, 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. // TODO: Should be a map with rules for each rank and faction. + // TODO: Rename to `GroupMeta` as a map on `GroupIndex` and include `name: String`. #[pallet::storage] pub type Rules, I: 'static = ()> = StorageValue<_, T::Hash>; /// The current members and their rank. Doesn't include `SuspendedMembers`. #[pallet::storage] pub type Members, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, MemberRecordFor, OptionQuery>; + StorageMap<_, Twox64Concat, T::AccountId, MemberRecord, OptionQuery>; /* // TODO: Migrate from: pub(super) type Vouching, I: 'static = ()> = @@ -649,20 +674,29 @@ pub mod pallet { StorageValue<_, Vec, ValueQuery>; */ - /// The number of items in `Membership` currently. (Doesn't include `SuspendedMembers`.) + /// Information regarding rank-0 payouts, past and future. + #[pallet::storage] + pub type Payouts, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, PayoutRecordFor, ValueQuery>; + /* + // TODO: Migrate from: + pub(super) type Payouts, I: 'static = ()> = ??? + */ + + /// The number of items in `Members` currently. (Doesn't include `SuspendedMembers`.) #[pallet::storage] pub type MemberCount, I: 'static = ()> = StorageValue<_, u32, ValueQuery>; - /// The current items in `Membership` keyed by their unique index. Keys are densely populated + /// The current items in `Members` keyed by their unique index. Keys are densely populated /// `0..MemberCount` (does not include `MemberCount`). #[pallet::storage] pub type MemberByIndex, I: 'static = ()> = StorageMap<_, Twox64Concat, u32, T::AccountId, OptionQuery>; - /// The set of suspended members. + /// The set of suspended members, with their old membership record. #[pallet::storage] pub type SuspendedMembers, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, MemberRecordFor, OptionQuery>; + StorageMap<_, Twox64Concat, T::AccountId, MemberRecord, OptionQuery>; /// The number of rounds which have passed. #[pallet::storage] @@ -1019,7 +1053,7 @@ pub mod pallet { #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let mut record = Members::::get(&who).ok_or(Error::::NotMember)?; + let mut record = Payouts::::get(&who).ok_or(Error::::NotMember)?; if let Some((when, amount)) = record.payouts.first() { if when <= &>::block_number() { @@ -1033,18 +1067,24 @@ pub mod pallet { Err(Error::::NoPayout)? } - /// Repay the payment previously given to the member with the signed origin, and elevate - /// them from rank 0 to rank 1. + /// Repay the payment previously given to the member with the signed origin, remove any + /// pending payments, and elevate them from rank 0 to rank 1. #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn repay(origin: OriginFor) -> DispatchResult { - todo!() - } + pub fn waive_repay(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + let mut record = Members::::get(&who).ok_or(Error::::NotMember)?; + let mut payout_record = Payouts::::get(&who).ok_or(Error::::NotMember)?; + ensure!(record.rank == 0, Error::::AlreadyElevated); - /// Remove the payment owed to the member with the signed origin, and elevate them from - /// rank 0 to rank 1. - #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn waive(origin: OriginFor) -> DispatchResult { - todo!() + T::Currency::transfer(&who, &Self::account_id(), payout_record.paid, AllowDeath)?; + payout_record.payouts.clear(); + record.rank = 1; + Members::::insert(&who, record); + Payouts::::insert(&who, payout_record); + + Self::deposit_event(Event::::Elevated { member: who, rank: 1 }); + + Ok(()) } /// Found the society. @@ -1080,7 +1120,7 @@ pub mod pallet { // This should never fail in the context of this function... let params = GroupParams { max_members, max_intake, max_strikes, candidate_deposit }; Parameters::::put(params); - Self::insert_member(&founder, 1, BoundedVec::new())?; + Self::insert_member(&founder, 1)?; Head::::put(&founder); Founder::::put(&founder); Rules::::put(T::Hashing::hash(&rules)); @@ -1150,9 +1190,10 @@ pub mod pallet { if forgive { // Try to add member back to society. Can fail with `MaxMembers` limit. - Self::reinstate_member(&who, record.rank, record.payouts)?; + Self::reinstate_member(&who, record.rank)?; } else { - Self::unreserve_payout(record.payouts.into_iter().map(|x| x.1).sum()); + let mut payout_record = Payouts::::take(&who); + Self::unreserve_payout(payout_record.payouts.into_iter().map(|x| x.1).sum()); } SuspendedMembers::::remove(&who); @@ -1514,12 +1555,11 @@ impl, I: 'static> Pallet { /// /// NOTE: Generally you should not use this, and instead use `add_new_member` or /// `reinstate_member`, whose names clearly match the desired intention. - fn insert_member(who: &T::AccountId, rank: Rank, payouts: PayoutsFor) -> DispatchResult { + fn insert_member(who: &T::AccountId, rank: Rank) -> DispatchResult { let params = Parameters::::get().ok_or(Error::::NotGroup); ensure!(MemberCount::get() < params.max_members as usize, Error::::MaxMembers); let index = MemberCount::::mutate(|i| { i.saturating_accrue(1); *i - 1 }); - let paid = Zero::zero(); - let record = MemberRecord { rank, strikes: 0, vouching: None, paid, index, payouts }; + let record = MemberRecord { rank, strikes: 0, vouching: None, index }; Members::::insert(who, record); MemberByIndex::::insert(index, who); Ok(()) @@ -1531,14 +1571,14 @@ impl, I: 'static> Pallet { /// /// The `payouts` value must be exactly as it was prior to suspension since no further funds /// will be reserved. - fn reinstate_member(who: &T::AccountId, rank: Rank, payouts: PayoutsFor) -> DispatchResult { - Self::insert_member(who, rank, payouts) + fn reinstate_member(who: &T::AccountId, rank: Rank) -> DispatchResult { + Self::insert_member(who, rank) } /// Add a member to the sorted members list. If the user is already a member, do nothing. /// Can fail when `MaxMember` limit is reached, but in that case it has no side-effects. fn add_new_member(who: &T::AccountId, rank: Rank) -> DispatchResult { - Self::insert_member(who, rank, Default::default()) + Self::insert_member(who, rank) } /// Induct a new member into the set. From a6577e0f22e689a10d556c0ff61adc68b0a91371 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 3 May 2022 15:59:50 +0100 Subject: [PATCH 04/43] Building --- frame/society/src/lib.rs | 220 ++++++++++++----------- frame/support/src/storage/bounded_vec.rs | 7 +- frame/support/src/storage/types/map.rs | 2 +- primitives/core/src/ecdsa.rs | 4 +- primitives/core/src/ed25519.rs | 4 +- primitives/core/src/sr25519.rs | 2 +- primitives/core/src/traits.rs | 4 +- primitives/runtime/src/lib.rs | 12 +- 8 files changed, 135 insertions(+), 120 deletions(-) diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index ce36c799c1477..9d5a143f88380 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -256,7 +256,7 @@ mod tests; use frame_support::{ pallet_prelude::*, traits::{ - BalanceStatus, ChangeMembers, Currency, EnsureOrigin, ExistenceRequirement::AllowDeath, + Currency, EnsureOrigin, ExistenceRequirement::AllowDeath, Imbalance, OnUnbalanced, Randomness, ReservableCurrency, }, PalletId, @@ -269,7 +269,7 @@ use rand_chacha::{ use scale_info::TypeInfo; use sp_runtime::{ traits::{ - AccountIdConversion, CheckedAdd, CheckedSub, Hash, IntegerSquareRoot, Saturating, + AccountIdConversion, CheckedAdd, CheckedSub, Hash, Saturating, StaticLookup, TrailingZeroInput, Zero, }, ArithmeticError::Overflow, Percent, RuntimeDebug, @@ -295,6 +295,7 @@ pub enum OldVote { Approve, } +#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub struct Vote { approve: bool, weight: u32, @@ -355,15 +356,15 @@ pub type RoundIndex = u32; pub type Rank = u32; /// The number of votes. -pub type Votes = u32; +pub type VoteCount = u32; /// Tally of votes. #[derive(Default, Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub struct Tally { /// The approval votes. - approvals: Votes, + approvals: VoteCount, /// The rejection votes. - rejections: Votes, + rejections: VoteCount, } impl Tally { @@ -400,7 +401,7 @@ pub enum BidKind { } impl BidKind { - fn is_vouch(&self, v: &AccountId) -> DispatchResult { + fn is_vouch(&self, v: &AccountId) -> bool { matches!(self, BidKind::Vouch(ref a, _) if a == v) } } @@ -420,16 +421,18 @@ pub struct MemberRecord { } /// Information concerning a member. -#[derive(Default, Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub struct PayoutRecord { +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, Default)] +pub struct PayoutRecord { paid: Balance, - payouts: BoundedVec<(BlockNumber, Balance), MaxPayouts>, + payouts: PayoutsVec, } pub type PayoutRecordFor = PayoutRecord< BalanceOf, - ::BlockNumber, - >::MaxPayouts, + BoundedVec< + (::BlockNumber, BalanceOf), + >::MaxPayouts, + >, >; /// Record for an individual new member who was elevated from a candidate recently. @@ -453,8 +456,8 @@ pub struct GroupParams { candidate_deposit: Balance, } -pub type GroupParamsFor = GroupParams>; - +pub type GroupParamsFor = GroupParams>; +/* pub type FactionIndex = u32; pub enum Faction { @@ -467,8 +470,9 @@ pub struct Group { faction: Faction, rank: Rank, } - -//TODO: Ranks cannot be indexed linearly. Should just be done as Groups DAG. +*/ +//TODO: Ranks cannot be indexed linearly if we want to do insertions into the ranking system. +// Should just be done as Groups DAG. #[frame_support::pallet] pub mod pallet { @@ -623,7 +627,7 @@ pub mod pallet { /// A vote has been placed for a defending member DefenderVote { voter: T::AccountId, vote: bool }, /// A new set of \[params\] has been set for the group. - NewParams { params: GroupParamsFor }, + NewParams { params: GroupParamsFor }, /// Society is unfounded. Unfounded { founder: T::AccountId }, /// Some funds were deposited into the society account. @@ -638,7 +642,7 @@ pub mod pallet { /// The max number of members for the society at one time. #[pallet::storage] - pub(super) type Parameters, I: 'static = ()> = StorageValue<_, GroupParamsFor, OptionQuery>; + pub(super) type Parameters, I: 'static = ()> = StorageValue<_, GroupParamsFor, OptionQuery>; /// Amount of our account balance that is specifically for the next round's bid(s). #[pallet::storage] @@ -722,7 +726,7 @@ pub mod pallet { /// The current skeptic. #[pallet::storage] - pub type Skeptic, I: 'static = ()> = StorageValue<_, Vec, ValueQuery>; + pub type Skeptic, I: 'static = ()> = StorageValue<_, T::AccountId, OptionQuery>; /// Double map from Candidate -> Voter -> (Maybe) Vote. #[pallet::storage] @@ -743,7 +747,7 @@ pub mod pallet { /// become the new `Head`. #[pallet::storage] pub type NextHead, I: 'static = ()> = StorageValue<_, - IntakeRecordFor, + IntakeRecordFor, OptionQuery, >; @@ -764,8 +768,6 @@ pub mod pallet { #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { fn on_initialize(n: T::BlockNumber) -> Weight { - let mut members = vec![]; - let mut weight = 0; let weights = T::BlockWeights::get(); @@ -781,7 +783,7 @@ pub mod pallet { // Run a candidate/membership rotation match Self::period() { - Period::Voting { elapsed: 0, .. } => { + Period::Voting { elapsed, .. } if elapsed.is_zero() => { Self::rotate_intake(&mut rng); weight += weights.max_block / 20; }, @@ -801,8 +803,6 @@ pub mod pallet { #[pallet::genesis_config] pub struct GenesisConfig, I: 'static = ()> { pub pot: BalanceOf, - pub members: Vec, - pub max_members: u32, } #[cfg(feature = "std")] @@ -810,34 +810,25 @@ pub mod pallet { fn default() -> Self { Self { pot: Default::default(), - members: Default::default(), - max_members: Default::default(), } } } - mod migrations { - fn to_v2() { - - } - } - #[pallet::genesis_build] impl, I: 'static> GenesisBuild for GenesisConfig { fn build(&self) { Pot::::put(self.pot); - MaxMembers::::put(self.max_members); - let first_member = self.members.first(); - if let Some(member) = first_member { - Founder::::put(member.clone()); - Head::::put(member.clone()); - }; - let mut m = self.members.clone(); - m.sort(); - Members::::put(m); } } + mod migrations { + /* + fn to_v2() { + + } + */ + } + #[pallet::call] impl, I: 'static> Pallet { /// A user outside of the society can make a bid for entry. @@ -934,7 +925,7 @@ pub mod pallet { ensure!(!SuspendedMembers::::contains_key(&who), Error::::Suspended); // Check sender can vouch. - let mut record = Members::::get(voucher).ok_or(Error::::NotMember)?; + let mut record = Members::::get(&voucher).ok_or(Error::::NotMember)?; ensure!(record.vouching.is_none(), Error::::AlreadyVouching); // Update voucher record. @@ -998,11 +989,11 @@ pub mod pallet { let voter = ensure_signed(origin)?; let candidate = T::Lookup::lookup(candidate)?; - let mut candidacy = Candidates::::get(candidate).ok_or(Error::::NotCandidate)?; + let mut candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; let record = Members::::get(&voter).ok_or(Error::::NotMember)?; Votes::::mutate(&candidate, &voter, |v| { - v = Some(Self::do_vote(v, approve, record.rank, &mut candidacy.tally)); + *v = Some(Self::do_vote(*v, approve, record.rank, &mut candidacy.tally)); }); Candidates::::insert(&candidate, &candidacy); @@ -1025,11 +1016,11 @@ pub mod pallet { pub fn defender_vote(origin: OriginFor, approve: bool) -> DispatchResult { let voter = ensure_signed(origin)?; - let defending = Defending::::get().ok_or(Error::::NoDefender)?; + let mut defending = Defending::::get().ok_or(Error::::NoDefender)?; let record = Members::::get(&voter).ok_or(Error::::NotMember)?; DefenderVotes::::mutate(&voter, |v| { - v = Some(Self::do_vote(v, approve, record.rank, &mut defending.2)); + *v = Some(Self::do_vote(*v, approve, record.rank, &mut defending.2)); }); Defending::::put(defending); @@ -1053,14 +1044,14 @@ pub mod pallet { #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let mut record = Payouts::::get(&who).ok_or(Error::::NotMember)?; + let mut record = Payouts::::get(&who); if let Some((when, amount)) = record.payouts.first() { if when <= &>::block_number() { record.paid = record.paid.checked_add(amount).ok_or(Overflow)?; T::Currency::transfer(&Self::payouts(), &who, *amount, AllowDeath)?; record.payouts.remove(0); - Members::::insert(&who, record); + Payouts::::insert(&who, record); return Ok(()) } } @@ -1073,7 +1064,7 @@ pub mod pallet { pub fn waive_repay(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; let mut record = Members::::get(&who).ok_or(Error::::NotMember)?; - let mut payout_record = Payouts::::get(&who).ok_or(Error::::NotMember)?; + let mut payout_record = Payouts::::get(&who); ensure!(record.rank == 0, Error::::AlreadyElevated); T::Currency::transfer(&who, &Self::account_id(), payout_record.paid, AllowDeath)?; @@ -1192,8 +1183,10 @@ pub mod pallet { // Try to add member back to society. Can fail with `MaxMembers` limit. Self::reinstate_member(&who, record.rank)?; } else { - let mut payout_record = Payouts::::take(&who); - Self::unreserve_payout(payout_record.payouts.into_iter().map(|x| x.1).sum()); + let payout_record = Payouts::::take(&who); + let total = payout_record.payouts.into_iter() + .map(|x| x.1).fold(Zero::zero(), |acc: BalanceOf, x| acc.saturating_add(x)); + Self::unreserve_payout(total); } SuspendedMembers::::remove(&who); @@ -1223,7 +1216,7 @@ pub mod pallet { max_strikes: u32, candidate_deposit: BalanceOf, ) -> DispatchResult { - ensure!(ensure_signed(origin)? == Founder::::get(), Error::::NotFounder); + ensure!(Some(ensure_signed(origin)?) == Founder::::get(), Error::::NotFounder); ensure!(max_members >= MemberCount::::get(), Error::::MaxMembers); let params = GroupParams { max_members, max_intake, max_strikes, candidate_deposit }; Parameters::::put(¶ms); @@ -1299,7 +1292,7 @@ pub mod pallet { /// Simple ensure origin struct to filter for the founder account. pub struct EnsureFounder(sp_std::marker::PhantomData); -impl EnsureOrigin for EnsureFounder { +impl EnsureOrigin<::Origin> for EnsureFounder { type Success = T::AccountId; fn try_origin(o: T::Origin) -> Result { o.into().and_then(|o| match (o, Founder::::get()) { @@ -1318,11 +1311,11 @@ impl EnsureOrigin for EnsureFounder { // TODO: Move close to `FromEntropy`? struct InputFromRng<'a, T>(&'a mut T); impl<'a, T: RngCore> codec::Input for InputFromRng<'a, T> { - fn remaining_len(&mut self) -> Result, Error> { + fn remaining_len(&mut self) -> Result, codec::Error> { return Ok(None) } - fn read(&mut self, into: &mut [u8]) -> Result<(), Error> { + fn read(&mut self, into: &mut [u8]) -> Result<(), codec::Error> { self.0.fill_bytes(into); Ok(()) } @@ -1339,7 +1332,7 @@ impl, I: 'static> Pallet { let claim_period = T::ClaimPeriod::get(); let voting_period = T::VotingPeriod::get(); let rotation_period = voting_period + claim_period; - let now = ::block_number(); + let now = frame_system::Pallet::::block_number(); let phase = now % rotation_period; if phase < voting_period { Period::Voting { elapsed: phase, more: voting_period - phase } @@ -1369,15 +1362,18 @@ impl, I: 'static> Pallet { Vote { approve, weight } } - fn check_skeptic(candidate: &T::AccountId, candidacy: &Candidacy>) { + fn check_skeptic(candidate: &T::AccountId, candidacy: &Candidacy>) { if RoundCount::::get() != candidacy.round { return } // We expect the skeptic to have voted. - let skeptic = Skeptic::::get(); + let skeptic = match Skeptic::::get() { Some(s) => s, None => return }; let maybe_vote = Votes::::get(&candidate, &skeptic); let approved = candidacy.tally.more_approvals(); let rejected = candidacy.tally.more_rejections(); match (maybe_vote, approved, rejected) { - (None, _, _) | (Some(false), true, false) | (Some(true), false, true) => { + (None, _, _) + | (Some(Vote { approve: true, .. }), false, true) + | (Some(Vote { approve: false, .. }), true, false) + => { // Can't do much if the punishment doesn't work out. let _ = Self::strike_member(&skeptic); }, @@ -1397,10 +1393,14 @@ impl, I: 'static> Pallet { // Check defender skeptic voted and that their vote was with the majority. let skeptic_vote = DefenderVotes::::get(&skeptic); match (skeptic_vote, tally.more_approvals(), tally.more_rejections()) { - (None, _, _) | (Some(true), false, true) | (Some(false), true, false) => { + (None, _, _) + | (Some(Vote { approve: true, .. }), false, true) + | (Some(Vote { approve: false, .. }), true, false) + => { // Punish skeptic. let _ = Self::strike_member(&skeptic); } + _ => {} } // Clean up all votes. @@ -1412,10 +1412,10 @@ impl, I: 'static> Pallet { // Avoid challenging if there's only two members since we never challenge the Head or // the Founder. if MemberCount::::get() > 2 { - let defender = Self::pick_defendent(&mut rng).expect("exited if members empty; qed"); - let skeptic = Self::pick_member_except(&mut rng, &defender).expect("exited if members empty; qed"); + let defender = Self::pick_defendent(rng).expect("exited if members empty; qed"); + let skeptic = Self::pick_member_except(rng, &defender).expect("exited if members empty; qed"); Self::deposit_event(Event::::Challenged { member: defender.clone() }); - Defending::::put((defender, skeptic, 0, 0)); + Defending::::put((defender, skeptic, Tally::default())); } else { Defending::::kill(); } @@ -1429,13 +1429,13 @@ impl, I: 'static> Pallet { /// --------------------------------------------- fn rotate_intake(rng: &mut impl RngCore) { // We assume there's at least one member or this logic won't work. - let member_count = MemberCount::get(); + let member_count = MemberCount::::get(); if member_count < 1 { return } let maybe_head = NextHead::::take(); if let Some(head) = maybe_head { - Head::::put(head); + Head::::put(&head.who); } // Bump the pot by at most `PeriodSpend`, but less if there's not very much left in our @@ -1451,7 +1451,7 @@ impl, I: 'static> Pallet { let candidate_count = Self::select_new_candidates(round_count, member_count, pot); if candidate_count > 0 { // Select a member at random and make them the skeptic for this round. - let skeptic = Self::pick_member(&mut rng).expect("exited if members empty; qed"); + let skeptic = Self::pick_member(rng).expect("exited if members empty; qed"); Skeptic::::put(skeptic); } RoundCount::::put(round_count); @@ -1469,13 +1469,15 @@ impl, I: 'static> Pallet { member_count: u32, pot: BalanceOf, ) -> usize { - let max_members = Parameters::::get().max_members as usize; - // Get the number of left-most bidders whose bids add up to less than `pot`. let mut bids = Bids::::get(); - let mut max_selections: usize = (Parameters::::get().max_intake as usize) - .min(max_members.saturating_sub(member_count)) - .min(bids.len()); + let params = match Parameters::::get() { + Some(params) => params, + None => return 0, + }; + let max_selections: u32 = params.max_intake + .min(params.max_members.saturating_sub(member_count)) + .min(bids.len() as u32); let mut selections = 0; // A running total of the cost to onboard these bids @@ -1490,7 +1492,7 @@ impl, I: 'static> Pallet { if accept { let candidacy = Candidacy { round, - kind: bid.kind, + kind: bid.kind.clone(), bid: bid.value, tally: Default::default(), }; @@ -1501,7 +1503,8 @@ impl, I: 'static> Pallet { }); // No need to reset Bids if we're not taking anything. - Bids::::put(bids); + Bids::::put(&bids); + bids.len() } /// Puts a bid into storage ordered by smallest to largest value. @@ -1514,10 +1517,10 @@ impl, I: 'static> Pallet { ) { let pos = bids.iter().position(|bid| bid.value > value).unwrap_or(bids.len()); let r = bids.force_insert_keep_left(pos, Bid { value, who: who.clone(), kind: bid_kind }); - let maybe_discarded = r.err().or_else(|| r.ok()); + let maybe_discarded = match r { Ok(x) => x, Err(x) => Some(x) }; if let Some(discarded) = maybe_discarded { Self::clean_bid(&discarded); - Self::deposit_event(Event::::AutoUnbid { candidate: discarded }); + Self::deposit_event(Event::::AutoUnbid { candidate: discarded.who }); } } @@ -1556,8 +1559,8 @@ impl, I: 'static> Pallet { /// NOTE: Generally you should not use this, and instead use `add_new_member` or /// `reinstate_member`, whose names clearly match the desired intention. fn insert_member(who: &T::AccountId, rank: Rank) -> DispatchResult { - let params = Parameters::::get().ok_or(Error::::NotGroup); - ensure!(MemberCount::get() < params.max_members as usize, Error::::MaxMembers); + let params = Parameters::::get().ok_or(Error::::NotGroup)?; + ensure!(MemberCount::::get() < params.max_members, Error::::MaxMembers); let index = MemberCount::::mutate(|i| { i.saturating_accrue(1); *i - 1 }); let record = MemberRecord { rank, strikes: 0, vouching: None, index }; Members::::insert(who, record); @@ -1584,11 +1587,11 @@ impl, I: 'static> Pallet { /// Induct a new member into the set. fn induct_member( candidate: T::AccountId, - candidacy: Candidacy>, + candidacy: Candidacy>, rank: Rank, ) -> DispatchResult { Self::check_skeptic(&candidate, &candidacy); - Self::add_new_member(&candidate, 0)?; + Self::add_new_member(&candidate, rank)?; let next_head = NextHead::::get() .filter(|old| old.round > candidacy.round || old.round == candidacy.round && old.bid < candidacy.bid @@ -1603,11 +1606,12 @@ impl, I: 'static> Pallet { } fn strike_member(who: &T::AccountId) -> DispatchResult { - let mut record = Members::::get(who).ok_or(NotMember)?; + let mut record = Members::::get(who).ok_or(Error::::NotMember)?; record.strikes.saturating_inc(); if record.strikes >= T::GraceStrikes::get() { // Too many strikes: slash the payout in half. - let total_payout = record.payouts.iter().map(|x| x.1).sum(); + let total_payout = Payouts::::get(who).payouts.iter() + .fold(BalanceOf::::zero(), |acc, x| acc.saturating_add(x.1)); Self::slash_payout(who, total_payout / 2u32.into()); } let params = Parameters::::get().ok_or(Error::::NotGroup)?; @@ -1631,13 +1635,13 @@ impl, I: 'static> Pallet { pub fn remove_member(m: &T::AccountId) -> Result { ensure!(Head::::get().as_ref() != Some(m), Error::::Head); ensure!(Founder::::get().as_ref() != Some(m), Error::::Founder); - if let Some(record) = Members::::get(m) { + if let Some(mut record) = Members::::get(m) { let index = record.index; let last_index = MemberCount::::mutate(|i| { i.saturating_reduce(1); *i }); if index != last_index { // Move the member with the last index down to the index of the member to be removed. if let Some(other) = MemberByIndex::::get(last_index) { - MemberByIndex::::insert(index, other); + MemberByIndex::::insert(index, &other); Members::::mutate(other, |m_r| if let Some(r) = m_r { r.index = index } ); @@ -1654,7 +1658,7 @@ impl, I: 'static> Pallet { // If their vouch is already a candidate, do nothing. Bids::::mutate(|bids| // Try to find the matching bid - if let Some(pos) = bids.iter().position(|b| b.kind.check_voucher(&who).is_ok()) { + if let Some(pos) = bids.iter().position(|b| b.kind.is_vouch(&m)) { // Remove the bid, and emit an event let vouched = bids.remove(pos).who; Self::deposit_event(Event::::Unvouch { candidate: vouched }); @@ -1683,7 +1687,7 @@ impl, I: 'static> Pallet { /// /// If no members exist (or the state is inconsistent), then `None` may be returned. fn pick_member(rng: &mut impl RngCore) -> Option { - let member_count = MemberCount::get(); + let member_count = MemberCount::::get(); if member_count == 0 { return None } @@ -1695,7 +1699,7 @@ impl, I: 'static> Pallet { /// /// If `exception` is the only member (or the state is inconsistent), then `None` may be returned. fn pick_member_except(rng: &mut impl RngCore, exception: &T::AccountId) -> Option { - let member_count = MemberCount::get(); + let member_count = MemberCount::::get(); if member_count <= 1 { return None } @@ -1713,7 +1717,7 @@ impl, I: 'static> Pallet { /// If only the Founder and Head members exist (or the state is inconsistent), then `None` /// may be returned. fn pick_defendent(rng: &mut impl RngCore) -> Option { - let member_count = MemberCount::get(); + let member_count = MemberCount::::get(); if member_count <= 2 { return None } @@ -1751,11 +1755,17 @@ impl, I: 'static> Pallet { BidKind::Vouch(voucher, tip) => { // Check that the voucher is still vouching, else some other logic may have removed // their status. - if Vouching::::take(&voucher) == Some(VouchingStatus::Vouching) { - // In the case that a vouched-for bid is accepted we unset the - // vouching status and transfer the tip over to the voucher. - Self::bump_payout(&voucher, maturity, tip.min(value)); - value.saturating_sub(tip) + if let Some(mut record) = Members::::get(&voucher) { + if let Some(VouchingStatus::Vouching) = record.vouching { + // In the case that a vouched-for bid is accepted we unset the + // vouching status and transfer the tip over to the voucher. + record.vouching = None; + Self::bump_payout(&voucher, maturity, tip.min(value)); + Members::::insert(&voucher, record); + value.saturating_sub(tip) + } else { + value + } } else { value } @@ -1771,25 +1781,25 @@ impl, I: 'static> Pallet { /// is not a member or if `value` is zero. fn bump_payout(who: &T::AccountId, when: T::BlockNumber, value: BalanceOf) { if !value.is_zero() { - Members::::mutate(who, |maybe_record| if let Some(record) = maybe_record { - if record.rank == 0 { + if let Some(MemberRecord { rank: 0, .. }) = Members::::get(who) { + Payouts::::mutate_extant(who, |record| { // Members of rank 1 never get payouts. match record.payouts.binary_search_by_key(&when, |x| x.0) { Ok(index) => record.payouts[index].1.saturating_accrue(value), - Err(index) => record.payouts.insert(index, (when, value)), + Err(index) => { + // If they have too many pending payouts, then we take discard the payment. + let _ = record.payouts.try_insert(index, (when, value)); + }, } - } - }); - Self::reserve_payout(value); + }); + Self::reserve_payout(value); + } } } /// Attempt to slash the payout of some member. Return the total amount that was deducted. fn slash_payout(who: &T::AccountId, value: BalanceOf) -> BalanceOf { - let mut record = match Members::::get(who) { - Some(record) => record, - None => return Zero::zero(), - }; + let mut record = Payouts::::get(who); let mut rest = value; while !record.payouts.is_empty() { if let Some(new_rest) = rest.checked_sub(&record.payouts[0].1) { @@ -1803,13 +1813,13 @@ impl, I: 'static> Pallet { break } } - Members::::insert(who, record); + Payouts::::insert(who, record); value - rest } /// Transfer some `amount` from the main account into the payouts account and reduce the Pot /// by this amount. - fn reserve_payout(amount: BalanceOf) { + fn reserve_payout(amount: BalanceOf) { // Tramsfer payout from the Pot into the payouts account. Pot::::mutate(|pot| pot.saturating_reduce(amount)); @@ -1826,7 +1836,7 @@ impl, I: 'static> Pallet { /// Transfer some `amount` from the main account into the payouts account and increase the Pot /// by this amount. - fn unreserve_payout(amount: BalanceOf) { + fn unreserve_payout(amount: BalanceOf) { // Tramsfer payout from the Pot into the payouts account. Pot::::mutate(|pot| pot.saturating_accrue(amount)); diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index 5795edde24bc2..1f0ac9d019259 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -105,7 +105,7 @@ impl> EncodeLike> for BoundedVec {} impl BoundedVec { /// Create `Self` with no items. - fn new() -> Self { + pub fn new() -> Self { Self(Vec::new(), Default::default()) } @@ -144,6 +144,11 @@ impl BoundedVec { self.0.sort() } + /// Exactly the same semantics as `Vec::clear`. + pub fn clear(&mut self) { + self.0.clear() + } + /// Exactly the same semantics as `Vec::remove`. /// /// # Panics diff --git a/frame/support/src/storage/types/map.rs b/frame/support/src/storage/types/map.rs index e786c1fd4357d..80250dbc60796 100644 --- a/frame/support/src/storage/types/map.rs +++ b/frame/support/src/storage/types/map.rs @@ -166,7 +166,7 @@ where } /// Mutate the value under a key iff it exists. Do nothing and return the default value if not. - pub fn mutate_extant, R, F: FnOnce(&mut Value) -> R>( + pub fn mutate_extant, R: Default, F: FnOnce(&mut Value) -> R>( key: KeyArg, f: F, ) -> R { diff --git a/primitives/core/src/ecdsa.rs b/primitives/core/src/ecdsa.rs index ebb70e74e758d..bf6bf33e9a95c 100644 --- a/primitives/core/src/ecdsa.rs +++ b/primitives/core/src/ecdsa.rs @@ -74,8 +74,8 @@ type Seed = [u8; 32]; )] pub struct Public(pub [u8; 33]); -impl FromEntropy for Public { - fn from_entropy(input: &mut codec::Input) -> Result { +impl crate::traits::FromEntropy for Public { + fn from_entropy(input: &mut impl codec::Input) -> Result { let mut result = Self([0u8; 33]); input.read(&mut result.0[..])?; Ok(result) diff --git a/primitives/core/src/ed25519.rs b/primitives/core/src/ed25519.rs index bbb1ab74587e5..d735d8db826e4 100644 --- a/primitives/core/src/ed25519.rs +++ b/primitives/core/src/ed25519.rs @@ -88,8 +88,8 @@ impl Clone for Pair { } } -impl FromEntropy for Public { - fn from_entropy(input: &mut codec::Input) -> Result { +impl crate::traits::FromEntropy for Public { + fn from_entropy(input: &mut impl codec::Input) -> Result { let mut result = Self([0u8; 32]); input.read(&mut result.0[..])?; Ok(result) diff --git a/primitives/core/src/sr25519.rs b/primitives/core/src/sr25519.rs index 4e72f747a3a4e..251776c5e8118 100644 --- a/primitives/core/src/sr25519.rs +++ b/primitives/core/src/sr25519.rs @@ -94,7 +94,7 @@ impl Clone for Pair { } impl FromEntropy for Public { - fn from_entropy(input: &mut codec::Input) -> Result { + fn from_entropy(input: &mut impl codec::Input) -> Result { let mut result = Self([0u8; 32]); input.read(&mut result.0[..])?; Ok(result) diff --git a/primitives/core/src/traits.rs b/primitives/core/src/traits.rs index 9ba35dd402548..a799e3285fe5d 100644 --- a/primitives/core/src/traits.rs +++ b/primitives/core/src/traits.rs @@ -280,8 +280,8 @@ impl SpawnEssentialNamed for Box { } /// Create random values of `Self` given a stream of entropy. -pub trait FromEntropy { +pub trait FromEntropy: Sized { /// Create a random value of `Self` given a stream of random bytes on `input`. May only fail if /// `input` has an error. - fn from_entropy(input: &mut codec::Input) -> Result; + fn from_entropy(input: &mut impl codec::Input) -> Result; } diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index e420a8a42377f..11af6b37d9a67 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -170,7 +170,7 @@ impl From for Justifications { } } -use traits::{Lazy, Verify, FromEntropy}; +use traits::{Lazy, Verify}; use crate::traits::IdentifyAccount; #[cfg(feature = "std")] @@ -304,11 +304,11 @@ pub enum MultiSigner { } impl FromEntropy for MultiSigner { - fn from_entropy(input: &mut codec::Input) -> Result { - Ok(match input.read_byte() % 3 { - 0 => Self::Ed25519(FromEntropy::try_from_entropy(input)?), - 1 => Self::Sr25519(FromEntropy::try_from_entropy(input)?), - 2.. => Self::Ecdsa(FromEntropy::try_from_entropy(input)?), + fn from_entropy(input: &mut impl codec::Input) -> Result { + Ok(match input.read_byte()? % 3 { + 0 => Self::Ed25519(FromEntropy::from_entropy(input)?), + 1 => Self::Sr25519(FromEntropy::from_entropy(input)?), + 2.. => Self::Ecdsa(FromEntropy::from_entropy(input)?), }) } } From feb7d6b66dafefdd98ecc7aac1cb46b9ebe75185 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 3 May 2022 18:19:36 +0100 Subject: [PATCH 05/43] Some tests --- frame/society/src/lib.rs | 10 +- frame/society/src/mock.rs | 65 +++++------ frame/society/src/tests.rs | 210 +++++++++++++++++++--------------- primitives/core/src/traits.rs | 39 +++++++ 4 files changed, 183 insertions(+), 141 deletions(-) diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 9d5a143f88380..e11d7982bec1d 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -1125,18 +1125,12 @@ pub mod pallet { /// the `Founder` and the `Head`. This implies that it may only be done when there is one /// member. /// - /// # - /// - Two storage reads O(1). - /// - Four storage removals O(1). - /// - One event. - /// /// Total Complexity: O(1) - /// # #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn dissolve(origin: OriginFor) -> DispatchResult { let founder = ensure_signed(origin)?; - ensure!(Founder::::get() == Some(founder.clone()), Error::::NotFounder); - ensure!(Head::::get() == Some(founder.clone()), Error::::NotHead); + ensure!(Founder::::get().as_ref() == Some(&founder), Error::::NotFounder); + ensure!(MemberCount::::get() == 1, Error::::NotHead); Members::::remove_all(None); MemberCount::::kill(); diff --git a/frame/society/src/mock.rs b/frame/society/src/mock.rs index 3c84a3f66a39e..dc41295fe7645 100644 --- a/frame/society/src/mock.rs +++ b/frame/society/src/mock.rs @@ -99,34 +99,30 @@ impl pallet_balances::Config for Test { impl Config for Test { type Event = Event; + type PalletId = SocietyPalletId; type Currency = pallet_balances::Pallet; type Randomness = TestRandomness; - type CandidateDeposit = ConstU64<25>; - type WrongSideDeduction = ConstU64<2>; - type MaxStrikes = ConstU32<2>; + type GraceStrikes = ConstU32<1>; type PeriodSpend = ConstU64<1000>; - type MembershipChanged = (); - type RotationPeriod = ConstU64<4>; + type VotingPeriod = ConstU64<3>; + type ClaimPeriod = ConstU64<1>; type MaxLockDuration = ConstU64<100>; type FounderSetOrigin = EnsureSignedBy; - type JudgementOrigin = EnsureSignedBy; type ChallengePeriod = ConstU64<8>; - type MaxCandidateIntake = ConstU32<10>; - type PalletId = SocietyPalletId; + type MaxPayouts = ConstU32<10>; + type MaxBids = ConstU32<10>; } pub struct EnvBuilder { - members: Vec, balance: u64, balances: Vec<(u128, u64)>, pot: u64, - max_members: u32, + founded: bool, } impl EnvBuilder { pub fn new() -> Self { Self { - members: vec![10], balance: 10_000, balances: vec![ (10, 50), @@ -140,7 +136,7 @@ impl EnvBuilder { (90, 50), ], pot: 0, - max_members: 100, + founded: true, } } @@ -151,38 +147,18 @@ impl EnvBuilder { .assimilate_storage(&mut t) .unwrap(); pallet_society::GenesisConfig:: { - members: self.members, pot: self.pot, - max_members: self.max_members, } .assimilate_storage(&mut t) .unwrap(); let mut ext: sp_io::TestExternalities = t.into(); - ext.execute_with(f) - } - #[allow(dead_code)] - pub fn with_members(mut self, m: Vec) -> Self { - self.members = m; - self + ext.execute_with(|| { + assert!(Society::found_society(Origin::signed(1), 10, 100, 10, 2, 25, b"be cool".to_vec()).is_ok()); + f() + }) } - #[allow(dead_code)] - pub fn with_balances(mut self, b: Vec<(u128, u64)>) -> Self { - self.balances = b; - self - } - #[allow(dead_code)] - pub fn with_pot(mut self, p: u64) -> Self { - self.pot = p; - self - } - #[allow(dead_code)] - pub fn with_balance(mut self, b: u64) -> Self { - self.balance = b; - self - } - #[allow(dead_code)] - pub fn with_max_members(mut self, n: u32) -> Self { - self.max_members = n; + pub fn founded(mut self, f: bool) -> Self { + self.founded = f; self } } @@ -198,7 +174,7 @@ pub fn run_to_block(n: u64) { Society::on_initialize(System::block_number()); } } - +/* /// Creates a bid struct using input parameters. pub fn create_bid( value: Balance, @@ -207,3 +183,14 @@ pub fn create_bid( ) -> Bid { Bid { who, kind, value } } +*/ +/// Creates a candidate struct using input parameters. +pub fn candidacy( + round: RoundIndex, + bid: Balance, + kind: BidKind, + approvals: VoteCount, + rejections: VoteCount, +) -> Candidacy { + Candidacy { round, kind, bid, tally: Tally { approvals, rejections } } +} diff --git a/frame/society/src/tests.rs b/frame/society/src/tests.rs index d394ddc9011b0..55e36cc542c90 100644 --- a/frame/society/src/tests.rs +++ b/frame/society/src/tests.rs @@ -24,34 +24,44 @@ use frame_support::{assert_noop, assert_ok}; use sp_core::blake2_256; use sp_runtime::traits::BadOrigin; +fn members() -> Vec { + let mut r = Members::::iter_keys().collect::>(); + r.sort(); + r +} + +fn candidates() -> Vec<(u128, Candidacy)> { + Candidates::::iter().collect() +} + #[test] fn founding_works() { - EnvBuilder::new().with_max_members(0).with_members(vec![]).execute(|| { + EnvBuilder::new().founded(false).execute(|| { // Not set up initially. - assert_eq!(Society::founder(), None); - assert_eq!(Society::max_members(), 0); - assert_eq!(Society::pot(), 0); + assert_eq!(Founder::::get(), None); + assert_eq!(Parameters::::get(), None); + assert_eq!(Pot::::get(), 0); // Account 1 is set as the founder origin // Account 5 cannot start a society - assert_noop!(Society::found(Origin::signed(5), 20, 100, vec![]), BadOrigin); + assert_noop!(Society::found_society(Origin::signed(5), 20, 100, 10, 2, 25, vec![]), BadOrigin); // Account 1 can start a society, where 10 is the founding member - assert_ok!(Society::found(Origin::signed(1), 10, 100, b"be cool".to_vec())); + assert_ok!(Society::found_society(Origin::signed(1), 10, 100, 10, 2, 25, b"be cool".to_vec())); // Society members only include 10 - assert_eq!(Society::members(), vec![10]); + assert_eq!(members(), vec![10]); // 10 is the head of the society - assert_eq!(Society::head(), Some(10)); + assert_eq!(Head::::get(), Some(10)); // ...and also the founder - assert_eq!(Society::founder(), Some(10)); + assert_eq!(Founder::::get(), Some(10)); // 100 members max - assert_eq!(Society::max_members(), 100); + assert_eq!(Parameters::::get().unwrap().max_members, 100); // rules are correct - assert_eq!(Society::rules(), Some(blake2_256(b"be cool").into())); + assert_eq!(Rules::::get(), Some(blake2_256(b"be cool").into())); // Pot grows after first rotation period run_to_block(4); - assert_eq!(Society::pot(), 1000); + assert_eq!(Pot::::get(), 1000); // Cannot start another society assert_noop!( - Society::found(Origin::signed(1), 20, 100, vec![]), + Society::found_society(Origin::signed(1), 20, 100, 10, 2, 25, vec![]), Error::::AlreadyFounded ); }); @@ -59,24 +69,25 @@ fn founding_works() { #[test] fn unfounding_works() { - EnvBuilder::new().with_max_members(0).with_members(vec![]).execute(|| { + EnvBuilder::new().founded(false).execute(|| { // Account 1 sets the founder... - assert_ok!(Society::found(Origin::signed(1), 10, 100, vec![])); + assert_ok!(Society::found_society(Origin::signed(1), 10, 100, 10, 2, 25, vec![])); // Account 2 cannot unfound it as it's not the founder. - assert_noop!(Society::unfound(Origin::signed(2)), Error::::NotFounder); + assert_noop!(Society::dissolve(Origin::signed(2)), Error::::NotFounder); // Account 10 can, though. - assert_ok!(Society::unfound(Origin::signed(10))); + assert_ok!(Society::dissolve(Origin::signed(10))); // 1 sets the founder to 20 this time - assert_ok!(Society::found(Origin::signed(1), 20, 100, vec![])); + assert_ok!(Society::found_society(Origin::signed(1), 20, 100, 10, 2, 25, vec![])); // Bring in a new member... assert_ok!(Society::bid(Origin::signed(10), 0)); run_to_block(4); assert_ok!(Society::vote(Origin::signed(20), 10, true)); - run_to_block(8); + run_to_block(7); + assert_ok!(Society::claim_membership(Origin::signed(10))); // Unfounding won't work now, even though it's from 20. - assert_noop!(Society::unfound(Origin::signed(20)), Error::::NotHead); + assert_noop!(Society::dissolve(Origin::signed(20)), Error::::NotHead); }); } @@ -91,13 +102,15 @@ fn basic_new_member_works() { // Rotate period every 4 blocks run_to_block(4); // 20 is now a candidate - assert_eq!(Society::candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); + assert_eq!(candidates(), vec![(20, candidacy(1, 0, BidKind::Deposit(25), 0, 0))]); // 10 (a member) can vote for the candidate assert_ok!(Society::vote(Origin::signed(10), 20, true)); // Rotate period every 4 blocks + run_to_block(7); + assert_ok!(Society::claim_membership(Origin::signed(20))); run_to_block(8); // 20 is now a member of the society - assert_eq!(Society::members(), vec![10, 20]); + assert_eq!(members(), vec![10, 20]); // Reserved balance is returned assert_eq!(Balances::free_balance(20), 50); assert_eq!(Balances::reserved_balance(20), 0); @@ -115,55 +128,62 @@ fn bidding_works() { // Rotate period run_to_block(4); // Pot is 1000 after "PeriodSpend" - assert_eq!(Society::pot(), 1000); + assert_eq!(Pot::::get(), 1000); assert_eq!(Balances::free_balance(Society::account_id()), 10_000); // Choose smallest bidding users whose total is less than pot assert_eq!( - Society::candidates(), + candidates(), vec![ - create_bid(300, 30, BidKind::Deposit(25)), - create_bid(400, 40, BidKind::Deposit(25)), + (30, candidacy(1, 300, BidKind::Deposit(25), 0, 0)), + (40, candidacy(1, 400, BidKind::Deposit(25), 0, 0)), ] ); // A member votes for these candidates to join the society assert_ok!(Society::vote(Origin::signed(10), 30, true)); assert_ok!(Society::vote(Origin::signed(10), 40, true)); + run_to_block(7); + assert_ok!(Society::claim_membership(Origin::signed(30))); + assert_ok!(Society::claim_membership(Origin::signed(40))); run_to_block(8); // Candidates become members after a period rotation - assert_eq!(Society::members(), vec![10, 30, 40]); + assert_eq!(members(), vec![10, 30, 40]); // Pot is increased by 1000, but pays out 700 to the members assert_eq!(Balances::free_balance(Society::account_id()), 9_300); - assert_eq!(Society::pot(), 1_300); + assert_eq!(Pot::::get(), 1_300); // Left over from the original bids is 50 who satisfies the condition of bid less than pot. - assert_eq!(Society::candidates(), vec![create_bid(500, 50, BidKind::Deposit(25))]); + assert_eq!(candidates(), vec![(50, candidacy(2, 500, BidKind::Deposit(25), 0, 0))]); // 40, now a member, can vote for 50 assert_ok!(Society::vote(Origin::signed(40), 50, true)); + run_to_block(11); + assert_ok!(Society::claim_membership(Origin::signed(50))); run_to_block(12); // 50 is now a member - assert_eq!(Society::members(), vec![10, 30, 40, 50]); + assert_eq!(members(), vec![10, 30, 40, 50]); // Pot is increased by 1000, and 500 is paid out. Total payout so far is 1200. - assert_eq!(Society::pot(), 1_800); + assert_eq!(Pot::::get(), 1_800); assert_eq!(Balances::free_balance(Society::account_id()), 8_800); // No more candidates satisfy the requirements - assert_eq!(Society::candidates(), vec![]); + assert_eq!(candidates(), vec![]); assert_ok!(Society::defender_vote(Origin::signed(10), true)); // Keep defender around - // Next period + // Next period run_to_block(16); // Same members - assert_eq!(Society::members(), vec![10, 30, 40, 50]); + assert_eq!(members(), vec![10, 30, 40, 50]); // Pot is increased by 1000 again - assert_eq!(Society::pot(), 2_800); + assert_eq!(Pot::::get(), 2_800); // No payouts assert_eq!(Balances::free_balance(Society::account_id()), 8_800); // Candidate 60 now qualifies based on the increased pot size. - assert_eq!(Society::candidates(), vec![create_bid(1900, 60, BidKind::Deposit(25))]); + assert_eq!(candidates(), vec![(60, candidacy(4, 1900, BidKind::Deposit(25), 0, 0))]); // Candidate 60 is voted in. assert_ok!(Society::vote(Origin::signed(50), 60, true)); + run_to_block(19); + assert_ok!(Society::claim_membership(Origin::signed(60))); run_to_block(20); // 60 joins as a member - assert_eq!(Society::members(), vec![10, 30, 40, 50, 60]); + assert_eq!(members(), vec![10, 30, 40, 50, 60]); // Pay them - assert_eq!(Society::pot(), 1_900); + assert_eq!(Pot::::get(), 1_900); assert_eq!(Balances::free_balance(Society::account_id()), 6_900); }); } @@ -186,10 +206,11 @@ fn unbidding_works() { assert_eq!(Balances::reserved_balance(30), 0); // 20 wins candidacy run_to_block(4); - assert_eq!(Society::candidates(), vec![create_bid(1000, 20, BidKind::Deposit(25))]); + assert_eq!(candidates(), vec![create_bid(1000, 20, BidKind::Deposit(25))]); }); } +/* #[test] fn payout_works() { EnvBuilder::new().execute(|| { @@ -214,9 +235,9 @@ fn basic_new_member_skeptic_works() { assert_eq!(Strikes::::get(10), 0); assert_ok!(Society::bid(Origin::signed(20), 0)); run_to_block(4); - assert_eq!(Society::candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); + assert_eq!(candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); run_to_block(8); - assert_eq!(Society::members(), vec![10]); + assert_eq!(members(), vec![10]); assert_eq!(Strikes::::get(10), 1); }); } @@ -232,14 +253,14 @@ fn basic_new_member_reject_works() { assert_eq!(Balances::reserved_balance(20), 25); // Rotation Period run_to_block(4); - assert_eq!(Society::candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); + assert_eq!(candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); // We say no assert_ok!(Society::vote(Origin::signed(10), 20, false)); run_to_block(8); // User is not added as member - assert_eq!(Society::members(), vec![10]); + assert_eq!(members(), vec![10]); // User is suspended - assert_eq!(Society::candidates(), vec![]); + assert_eq!(candidates(), vec![]); assert_eq!(Society::suspended_candidate(20).is_some(), true); }); } @@ -342,14 +363,14 @@ fn suspended_candidate_rejected_works() { assert_eq!(Balances::reserved_balance(20), 25); // Rotation Period run_to_block(4); - assert_eq!(Society::candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); + assert_eq!(candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); // We say no assert_ok!(Society::vote(Origin::signed(10), 20, false)); run_to_block(8); // User is not added as member - assert_eq!(Society::members(), vec![10]); + assert_eq!(members(), vec![10]); // User is suspended - assert_eq!(Society::candidates(), vec![]); + assert_eq!(candidates(), vec![]); assert_eq!(Society::suspended_candidate(20).is_some(), true); // Normal user cannot make judgement on suspended candidate @@ -363,14 +384,14 @@ fn suspended_candidate_rejected_works() { // They are placed back in bid pool, repeat suspension process // Rotation Period run_to_block(12); - assert_eq!(Society::candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); + assert_eq!(candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); // We say no assert_ok!(Society::vote(Origin::signed(10), 20, false)); run_to_block(16); // User is not added as member - assert_eq!(Society::members(), vec![10]); + assert_eq!(members(), vec![10]); // User is suspended - assert_eq!(Society::candidates(), vec![]); + assert_eq!(candidates(), vec![]); assert_eq!(Society::suspended_candidate(20).is_some(), true); // Suspension judgement origin rejects the candidate @@ -381,7 +402,7 @@ fn suspended_candidate_rejected_works() { // Funds are deposited to society account assert_eq!(Balances::free_balance(Society::account_id()), 10025); // Cleaned up - assert_eq!(Society::candidates(), vec![]); + assert_eq!(candidates(), vec![]); assert_eq!(>::get(20), None); }); } @@ -390,7 +411,7 @@ fn suspended_candidate_rejected_works() { fn vouch_works() { EnvBuilder::new().execute(|| { // 10 is the only member - assert_eq!(Society::members(), vec![10]); + assert_eq!(members(), vec![10]); // A non-member cannot vouch assert_noop!(Society::vouch(Origin::signed(1), 20, 1000, 100), Error::::NotMember); // A member can though @@ -405,12 +426,12 @@ fn vouch_works() { assert_eq!(>::get(), vec![create_bid(1000, 20, BidKind::Vouch(10, 100))]); // Vouched user can become candidate run_to_block(4); - assert_eq!(Society::candidates(), vec![create_bid(1000, 20, BidKind::Vouch(10, 100))]); + assert_eq!(candidates(), vec![create_bid(1000, 20, BidKind::Vouch(10, 100))]); // Vote yes assert_ok!(Society::vote(Origin::signed(10), 20, true)); // Vouched user can win run_to_block(8); - assert_eq!(Society::members(), vec![10, 20]); + assert_eq!(members(), vec![10, 20]); // Voucher wins a portion of the payment assert_eq!(>::get(10), vec![(9, 100)]); // Vouched user wins the rest @@ -424,19 +445,19 @@ fn vouch_works() { fn voucher_cannot_win_more_than_bid() { EnvBuilder::new().execute(|| { // 10 is the only member - assert_eq!(Society::members(), vec![10]); + assert_eq!(members(), vec![10]); // 10 vouches, but asks for more than the bid assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 1000)); // Vouching creates the right kind of bid assert_eq!(>::get(), vec![create_bid(100, 20, BidKind::Vouch(10, 1000))]); // Vouched user can become candidate run_to_block(4); - assert_eq!(Society::candidates(), vec![create_bid(100, 20, BidKind::Vouch(10, 1000))]); + assert_eq!(candidates(), vec![create_bid(100, 20, BidKind::Vouch(10, 1000))]); // Vote yes assert_ok!(Society::vote(Origin::signed(10), 20, true)); // Vouched user can win run_to_block(8); - assert_eq!(Society::members(), vec![10, 20]); + assert_eq!(members(), vec![10, 20]); // Voucher wins as much as the bid assert_eq!(>::get(10), vec![(9, 100)]); // Vouched user gets nothing @@ -448,7 +469,7 @@ fn voucher_cannot_win_more_than_bid() { fn unvouch_works() { EnvBuilder::new().execute(|| { // 10 is the only member - assert_eq!(Society::members(), vec![10]); + assert_eq!(members(), vec![10]); // 10 vouches for 20 assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 0)); // 20 has a bid @@ -467,21 +488,21 @@ fn unvouch_works() { // Cannot unvouch after they become candidate assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 0)); run_to_block(4); - assert_eq!(Society::candidates(), vec![create_bid(100, 20, BidKind::Vouch(10, 0))]); + assert_eq!(candidates(), vec![create_bid(100, 20, BidKind::Vouch(10, 0))]); assert_noop!(Society::unvouch(Origin::signed(10), 0), Error::::BadPosition); // 10 is still vouching until candidate is approved or rejected assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); run_to_block(8); // In this case candidate is denied and suspended assert!(Society::suspended_candidate(&20).is_some()); - assert_eq!(Society::members(), vec![10]); + assert_eq!(members(), vec![10]); // User is stuck vouching until judgement origin resolves suspended candidate assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); // Judge denies candidate assert_ok!(Society::judge_suspended_candidate(Origin::signed(2), 20, Judgement::Reject)); // 10 is banned from vouching assert_eq!(>::get(10), Some(VouchingStatus::Banned)); - assert_eq!(Society::members(), vec![10]); + assert_eq!(members(), vec![10]); // 10 cannot vouch again assert_noop!( @@ -497,7 +518,7 @@ fn unvouch_works() { fn unbid_vouch_works() { EnvBuilder::new().execute(|| { // 10 is the only member - assert_eq!(Society::members(), vec![10]); + assert_eq!(members(), vec![10]); // 10 vouches for 20 assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 0)); // 20 has a bid @@ -516,9 +537,9 @@ fn unbid_vouch_works() { fn founder_and_head_cannot_be_removed() { EnvBuilder::new().execute(|| { // 10 is the only member, founder, and head - assert_eq!(Society::members(), vec![10]); - assert_eq!(Society::founder(), Some(10)); - assert_eq!(Society::head(), Some(10)); + assert_eq!(members(), vec![10]); + assert_eq!(Founder::::get(), Some(10)); + assert_eq!(Head::::get(), Some(10)); // 10 can still accumulate strikes assert_ok!(Society::bid(Origin::signed(20), 0)); run_to_block(8); @@ -537,10 +558,10 @@ fn founder_and_head_cannot_be_removed() { assert_ok!(Society::vote(Origin::signed(10), 50, true)); assert_ok!(Society::defender_vote(Origin::signed(10), true)); // Keep defender around run_to_block(32); - assert_eq!(Society::members(), vec![10, 50]); - assert_eq!(Society::head(), Some(50)); + assert_eq!(members(), vec![10, 50]); + assert_eq!(Head::::get(), Some(50)); // Founder is unchanged - assert_eq!(Society::founder(), Some(10)); + assert_eq!(Founder::::get(), Some(10)); // 50 can still accumulate strikes assert_ok!(Society::bid(Origin::signed(60), 0)); @@ -557,9 +578,9 @@ fn founder_and_head_cannot_be_removed() { assert_ok!(Society::vote(Origin::signed(50), 80, true)); assert_ok!(Society::defender_vote(Origin::signed(10), true)); // Keep defender around run_to_block(56); - assert_eq!(Society::members(), vec![10, 50, 80]); - assert_eq!(Society::head(), Some(80)); - assert_eq!(Society::founder(), Some(10)); + assert_eq!(members(), vec![10, 50, 80]); + assert_eq!(Head::::get(), Some(80)); + assert_eq!(Founder::::get(), Some(10)); // 50 can now be suspended for strikes assert_ok!(Society::bid(Origin::signed(90), 0)); @@ -569,7 +590,7 @@ fn founder_and_head_cannot_be_removed() { run_to_block(64); assert_eq!(Strikes::::get(50), 0); assert_eq!(>::get(50), true); - assert_eq!(Society::members(), vec![10, 80]); + assert_eq!(members(), vec![10, 80]); }); } @@ -586,7 +607,7 @@ fn challenges_work() { assert_eq!(>::get(30), None); assert_eq!(>::get(40), None); // Check starting point - assert_eq!(Society::members(), vec![10, 20, 30, 40]); + assert_eq!(members(), vec![10, 20, 30, 40]); assert_eq!(Society::defender(), None); // 20 will be challenged during the challenge rotation run_to_block(8); @@ -595,7 +616,7 @@ fn challenges_work() { assert_ok!(Society::defender_vote(Origin::signed(30), true)); // If no one else votes, nothing happens run_to_block(16); - assert_eq!(Society::members(), vec![10, 20, 30, 40]); + assert_eq!(members(), vec![10, 20, 30, 40]); // New challenge period assert_eq!(Society::defender(), Some(30)); // Non-member cannot challenge @@ -607,7 +628,7 @@ fn challenges_work() { assert_ok!(Society::defender_vote(Origin::signed(40), false)); run_to_block(24); // 20 survives - assert_eq!(Society::members(), vec![10, 20, 30, 40]); + assert_eq!(members(), vec![10, 20, 30, 40]); // Votes are reset assert_eq!(>::get(10), None); assert_eq!(>::get(20), None); @@ -622,7 +643,7 @@ fn challenges_work() { assert_ok!(Society::defender_vote(Origin::signed(40), false)); run_to_block(32); // 20 is suspended - assert_eq!(Society::members(), vec![10, 20, 40]); + assert_eq!(members(), vec![10, 20, 40]); assert_eq!(Society::suspended_member(30), true); // New defender is chosen assert_eq!(Society::defender(), Some(20)); @@ -647,7 +668,7 @@ fn bad_vote_slash_works() { Society::bump_payout(&30, 5, 100); Society::bump_payout(&40, 5, 100); // Check starting point - assert_eq!(Society::members(), vec![10, 20, 30, 40]); + assert_eq!(members(), vec![10, 20, 30, 40]); assert_eq!(>::get(10), vec![(5, 100)]); assert_eq!(>::get(20), vec![(5, 100)]); assert_eq!(>::get(30), vec![(5, 100)]); @@ -727,22 +748,22 @@ fn vouching_handles_removed_member_with_candidate() { assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); // Make that bid a candidate run_to_block(4); - assert_eq!(Society::candidates(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); + assert_eq!(candidates(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); // Suspend that member Society::suspend_member(&20); assert_eq!(>::get(20), true); // Nothing changes yet - assert_eq!(Society::candidates(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); + assert_eq!(candidates(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); // Remove member assert_ok!(Society::judge_suspended_member(Origin::signed(2), 20, false)); // Vouching status is removed, but candidate is still in the queue assert_eq!(>::get(20), None); - assert_eq!(Society::candidates(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); + assert_eq!(candidates(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); // Candidate wins assert_ok!(Society::vote(Origin::signed(10), 30, true)); run_to_block(8); - assert_eq!(Society::members(), vec![10, 30]); + assert_eq!(members(), vec![10, 30]); // Payout does not go to removed member assert_eq!(>::get(20), vec![]); assert_eq!(>::get(30), vec![(9, 1000)]); @@ -769,7 +790,7 @@ fn votes_are_working() { assert_eq!(>::get(50, 10), None); run_to_block(8); // Candidates become members after a period rotation - assert_eq!(Society::members(), vec![10, 30, 40]); + assert_eq!(members(), vec![10, 30, 40]); // Votes are cleaned up assert_eq!(>::get(30, 10), None); assert_eq!(>::get(40, 10), None); @@ -796,17 +817,17 @@ fn max_limits_work() { // Rotate period run_to_block(4); // Max of 10 candidates - assert_eq!(Society::candidates().len(), 10); + assert_eq!(candidates().len(), 10); // Fill up membership, max 100, we will do just 95 for i in 2000..2095 { assert_ok!(Society::add_member(&(i as u128))); } // Remember there was 1 original member, so 96 total - assert_eq!(Society::members().len(), 96); + assert_eq!(members().len(), 96); // Rotate period run_to_block(8); // Only of 4 candidates possible now - assert_eq!(Society::candidates().len(), 4); + assert_eq!(candidates().len(), 4); // Fill up members with suspended candidates from the first rotation for i in 100..104 { assert_ok!(Society::judge_suspended_candidate( @@ -815,12 +836,12 @@ fn max_limits_work() { Judgement::Approve )); } - assert_eq!(Society::members().len(), 100); + assert_eq!(members().len(), 100); // Can't add any more members assert_noop!(Society::add_member(&98), Error::::MaxMembers); // However, a fringe scenario allows for in-progress candidates to increase the membership // pool, but it has no real after-effects. - for i in Society::members().iter() { + for i in members().iter() { assert_ok!(Society::vote(Origin::signed(*i), 110, true)); assert_ok!(Society::vote(Origin::signed(*i), 111, true)); assert_ok!(Society::vote(Origin::signed(*i), 112, true)); @@ -828,15 +849,15 @@ fn max_limits_work() { // Rotate period run_to_block(12); // Members length is over 100, no problem... - assert_eq!(Society::members().len(), 103); + assert_eq!(members().len(), 103); // No candidates because full - assert_eq!(Society::candidates().len(), 0); + assert_eq!(candidates().len(), 0); // Increase member limit assert_ok!(Society::set_max_members(Origin::root(), 200)); // Rotate period run_to_block(16); // Candidates are back! - assert_eq!(Society::candidates().len(), 10); + assert_eq!(candidates().len(), 10); }); } @@ -856,11 +877,11 @@ fn zero_bid_works() { // Rotate period run_to_block(4); // Pot is 1000 after "PeriodSpend" - assert_eq!(Society::pot(), 1000); + assert_eq!(Pot::::get(), 1000); assert_eq!(Balances::free_balance(Society::account_id()), 10_000); // Choose smallest bidding users whose total is less than pot, with only one zero bid. assert_eq!( - Society::candidates(), + candidates(), vec![ create_bid(0, 30, BidKind::Deposit(25)), create_bid(300, 50, BidKind::Deposit(25)), @@ -877,9 +898,9 @@ fn zero_bid_works() { assert_ok!(Society::vote(Origin::signed(10), 60, true)); run_to_block(8); // Candidates become members after a period rotation - assert_eq!(Society::members(), vec![10, 30, 50, 60]); + assert_eq!(members(), vec![10, 30, 50, 60]); // The zero bid is selected as head - assert_eq!(Society::head(), Some(30)); + assert_eq!(Head::::get(), Some(30)); }); } @@ -907,3 +928,4 @@ fn bids_ordered_correctly() { assert_eq!(>::get(), final_list); }); } +*/ \ No newline at end of file diff --git a/primitives/core/src/traits.rs b/primitives/core/src/traits.rs index a799e3285fe5d..fc4bd908874d3 100644 --- a/primitives/core/src/traits.rs +++ b/primitives/core/src/traits.rs @@ -285,3 +285,42 @@ pub trait FromEntropy: Sized { /// `input` has an error. fn from_entropy(input: &mut impl codec::Input) -> Result; } + +impl FromEntropy for bool { + fn from_entropy(input: &mut impl codec::Input) -> Result { + Ok(input.read_byte()? % 2 == 1) + } +} + +macro_rules! impl_from_entropy { + ($type:ty , $( $others:tt )*) => { + impl_from_entropy!($type); + impl_from_entropy!($( $others )*); + }; + ($type:ty) => { + impl FromEntropy for $type { + fn from_entropy(input: &mut impl codec::Input) -> Result { + ::decode(input) + } + } + } +} + +macro_rules! impl_from_entropy_base { + ($type:ty , $( $others:tt )*) => { + impl_from_entropy_base!($type); + impl_from_entropy_base!($( $others )*); + }; + ($type:ty) => { + impl_from_entropy!($type, + [$type; 1], [$type; 2], [$type; 3], [$type; 4], [$type; 5], [$type; 6], [$type; 7], [$type; 8], + [$type; 9], [$type; 10], [$type; 11], [$type; 12], [$type; 13], [$type; 14], [$type; 15], [$type; 16], + [$type; 17], [$type; 18], [$type; 19], [$type; 20], [$type; 21], [$type; 22], [$type; 23], [$type; 24], + [$type; 25], [$type; 26], [$type; 27], [$type; 28], [$type; 29], [$type; 30], [$type; 31], [$type; 32], + [$type; 36], [$type; 40], [$type; 44], [$type; 48], [$type; 56], [$type; 64], [$type; 72], [$type; 80], + [$type; 96], [$type; 112], [$type; 128], [$type; 160], [$type; 192], [$type; 224], [$type; 256] + ); + } +} + +impl_from_entropy_base!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); From f7ce7e39e1e6980f32179c45d7d04e94cb44bfa5 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 4 May 2022 12:15:16 +0100 Subject: [PATCH 06/43] Fixes --- frame/society/src/lib.rs | 32 ++++++------ frame/society/src/mock.rs | 5 +- frame/society/src/tests.rs | 100 ++++++++++++++++++++++++------------- 3 files changed, 85 insertions(+), 52 deletions(-) diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index e11d7982bec1d..b0d1f6d249f05 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -1462,7 +1462,7 @@ impl, I: 'static> Pallet { round: RoundIndex, member_count: u32, pot: BalanceOf, - ) -> usize { + ) -> u32 { // Get the number of left-most bidders whose bids add up to less than `pot`. let mut bids = Bids::::get(); let params = match Parameters::::get() { @@ -1498,7 +1498,7 @@ impl, I: 'static> Pallet { // No need to reset Bids if we're not taking anything. Bids::::put(&bids); - bids.len() + selections } /// Puts a bid into storage ordered by smallest to largest value. @@ -1613,6 +1613,7 @@ impl, I: 'static> Pallet { // Way too many strikes: suspend. Self::suspend_member(who); } + Members::::insert(who, &record); Ok(()) } @@ -1774,20 +1775,19 @@ impl, I: 'static> Pallet { /// If it the caller's duty to ensure that `who` is already a member. This does nothing if `who` /// is not a member or if `value` is zero. fn bump_payout(who: &T::AccountId, when: T::BlockNumber, value: BalanceOf) { - if !value.is_zero() { - if let Some(MemberRecord { rank: 0, .. }) = Members::::get(who) { - Payouts::::mutate_extant(who, |record| { - // Members of rank 1 never get payouts. - match record.payouts.binary_search_by_key(&when, |x| x.0) { - Ok(index) => record.payouts[index].1.saturating_accrue(value), - Err(index) => { - // If they have too many pending payouts, then we take discard the payment. - let _ = record.payouts.try_insert(index, (when, value)); - }, - } - }); - Self::reserve_payout(value); - } + if value.is_zero() { return } + if let Some(MemberRecord { rank: 0, .. }) = Members::::get(who) { + Payouts::::mutate(who, |record| { + // Members of rank 1 never get payouts. + match record.payouts.binary_search_by_key(&when, |x| x.0) { + Ok(index) => record.payouts[index].1.saturating_accrue(value), + Err(index) => { + // If they have too many pending payouts, then we take discard the payment. + let _ = record.payouts.try_insert(index, (when, value)); + }, + } + }); + Self::reserve_payout(value); } } diff --git a/frame/society/src/mock.rs b/frame/society/src/mock.rs index dc41295fe7645..d3b8e5168346d 100644 --- a/frame/society/src/mock.rs +++ b/frame/society/src/mock.rs @@ -153,7 +153,10 @@ impl EnvBuilder { .unwrap(); let mut ext: sp_io::TestExternalities = t.into(); ext.execute_with(|| { - assert!(Society::found_society(Origin::signed(1), 10, 100, 10, 2, 25, b"be cool".to_vec()).is_ok()); + if self.founded { + let r = b"be cool".to_vec(); + assert!(Society::found_society(Origin::signed(1), 10, 100, 10, 2, 25, r).is_ok()); + } f() }) } diff --git a/frame/society/src/tests.rs b/frame/society/src/tests.rs index 55e36cc542c90..c377bbdafb654 100644 --- a/frame/society/src/tests.rs +++ b/frame/society/src/tests.rs @@ -197,20 +197,18 @@ fn unbidding_works() { // Balances are reserved assert_eq!(Balances::free_balance(30), 25); assert_eq!(Balances::reserved_balance(30), 25); - // Must know right position to unbid + cannot unbid someone else - assert_noop!(Society::unbid(Origin::signed(30), 1), Error::::BadPosition); // Can unbid themselves with the right position - assert_ok!(Society::unbid(Origin::signed(30), 0)); + assert_ok!(Society::unbid(Origin::signed(30))); + assert_noop!(Society::unbid(Origin::signed(30)), Error::::NotBidder); // Balance is returned assert_eq!(Balances::free_balance(30), 50); assert_eq!(Balances::reserved_balance(30), 0); // 20 wins candidacy run_to_block(4); - assert_eq!(candidates(), vec![create_bid(1000, 20, BidKind::Deposit(25))]); + assert_eq!(candidates(), vec![(20, candidacy(1, 1000, BidKind::Deposit(25), 0, 0))]); }); } -/* #[test] fn payout_works() { EnvBuilder::new().execute(|| { @@ -219,10 +217,11 @@ fn payout_works() { assert_ok!(Society::bid(Origin::signed(20), 1000)); run_to_block(4); assert_ok!(Society::vote(Origin::signed(10), 20, true)); - run_to_block(8); + run_to_block(7); + assert_ok!(Society::claim_membership(Origin::signed(20))); // payout not ready assert_noop!(Society::payout(Origin::signed(20)), Error::::NoPayout); - run_to_block(9); + run_to_block(8); // payout should be here assert_ok!(Society::payout(Origin::signed(20))); assert_eq!(Balances::free_balance(20), 1050); @@ -230,18 +229,49 @@ fn payout_works() { } #[test] -fn basic_new_member_skeptic_works() { +fn non_voting_skeptic_is_punished() { EnvBuilder::new().execute(|| { - assert_eq!(Strikes::::get(10), 0); + assert_eq!(Members::::get(10).unwrap().strikes, 0); assert_ok!(Society::bid(Origin::signed(20), 0)); run_to_block(4); - assert_eq!(candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); + assert_eq!(candidates(), vec![(20, candidacy(1, 0, BidKind::Deposit(25), 0, 0))]); + run_to_block(7); + assert_noop!(Society::claim_membership(Origin::signed(20)), Error::::NotApproved); + assert_ok!(Society::resign_candidate(Origin::signed(20))); run_to_block(8); assert_eq!(members(), vec![10]); - assert_eq!(Strikes::::get(10), 1); + assert_eq!(Members::::get(10).unwrap().strikes, 1); }); } +#[test] +fn rejecting_skeptic_on_approved_is_punished() { + EnvBuilder::new().execute(|| { + assert_eq!(Members::::get(10).unwrap().strikes, 0); + assert_ok!(Society::bid(Origin::signed(20), 0)); + assert_ok!(Society::bid(Origin::signed(30), 1)); + run_to_block(4); + assert_ok!(Society::bid(Origin::signed(40), 0)); + + assert_ok!(Society::vote(Origin::signed(10), 20, true)); + assert_ok!(Society::vote(Origin::signed(10), 30, true)); + run_to_block(7); + assert_ok!(Society::claim_membership(Origin::signed(20))); + assert_ok!(Society::claim_membership(Origin::signed(30))); + run_to_block(8); + let skeptic = Skeptic::::get().unwrap(); + for &i in &[10, 20, 30][..] { + assert_ok!(Society::vote(Origin::signed(i), 40, i != skeptic)); + } + run_to_block(11); + assert_ok!(Society::claim_membership(Origin::signed(40))); + run_to_block(12); + assert_eq!(members(), vec![10, 20, 30, 40]); + assert_eq!(Members::::get(skeptic).unwrap().strikes, 1); + }); +} + +/* #[test] fn basic_new_member_reject_works() { EnvBuilder::new().execute(|| { @@ -253,7 +283,7 @@ fn basic_new_member_reject_works() { assert_eq!(Balances::reserved_balance(20), 25); // Rotation Period run_to_block(4); - assert_eq!(candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); + assert_eq!(candidates(), vec![(20, candidacy(1, 0, BidKind::Deposit(25), 0, 0))]); // We say no assert_ok!(Society::vote(Origin::signed(10), 20, false)); run_to_block(8); @@ -363,7 +393,7 @@ fn suspended_candidate_rejected_works() { assert_eq!(Balances::reserved_balance(20), 25); // Rotation Period run_to_block(4); - assert_eq!(candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); + assert_eq!(candidates(), vec![(20, candidacy(1, 0, BidKind::Deposit(25), 0, 0))]); // We say no assert_ok!(Society::vote(Origin::signed(10), 20, false)); run_to_block(8); @@ -384,7 +414,7 @@ fn suspended_candidate_rejected_works() { // They are placed back in bid pool, repeat suspension process // Rotation Period run_to_block(12); - assert_eq!(candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); + assert_eq!(candidates(), vec![(20, candidacy(1, 0, BidKind::Deposit(25), 0, 0))]); // We say no assert_ok!(Society::vote(Origin::signed(10), 20, false)); run_to_block(16); @@ -423,10 +453,10 @@ fn vouch_works() { Error::::AlreadyVouching ); // Vouching creates the right kind of bid - assert_eq!(>::get(), vec![create_bid(1000, 20, BidKind::Vouch(10, 100))]); + assert_eq!(>::get(), vec![(20, candidacy(1, 1000, BidKind::Vouch(10,, 0, 0) 100))]); // Vouched user can become candidate run_to_block(4); - assert_eq!(candidates(), vec![create_bid(1000, 20, BidKind::Vouch(10, 100))]); + assert_eq!(candidates(), vec![(20, candidacy(1, 1000, BidKind::Vouch(10,, 0, 0) 100))]); // Vote yes assert_ok!(Society::vote(Origin::signed(10), 20, true)); // Vouched user can win @@ -449,10 +479,10 @@ fn voucher_cannot_win_more_than_bid() { // 10 vouches, but asks for more than the bid assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 1000)); // Vouching creates the right kind of bid - assert_eq!(>::get(), vec![create_bid(100, 20, BidKind::Vouch(10, 1000))]); + assert_eq!(>::get(), vec![(20, candidacy(1, 100, BidKind::Vouch(10,, 0, 0) 1000))]); // Vouched user can become candidate run_to_block(4); - assert_eq!(candidates(), vec![create_bid(100, 20, BidKind::Vouch(10, 1000))]); + assert_eq!(candidates(), vec![(20, candidacy(1, 100, BidKind::Vouch(10,, 0, 0) 1000))]); // Vote yes assert_ok!(Society::vote(Origin::signed(10), 20, true)); // Vouched user can win @@ -473,7 +503,7 @@ fn unvouch_works() { // 10 vouches for 20 assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 0)); // 20 has a bid - assert_eq!(>::get(), vec![create_bid(100, 20, BidKind::Vouch(10, 0))]); + assert_eq!(>::get(), vec![(20, candidacy(1, 100, BidKind::Vouch(10,, 0, 0) 0))]); // 10 is vouched assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); // To unvouch, you must know the right bid position @@ -488,7 +518,7 @@ fn unvouch_works() { // Cannot unvouch after they become candidate assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 0)); run_to_block(4); - assert_eq!(candidates(), vec![create_bid(100, 20, BidKind::Vouch(10, 0))]); + assert_eq!(candidates(), vec![(20, candidacy(1, 100, BidKind::Vouch(10,, 0, 0) 0))]); assert_noop!(Society::unvouch(Origin::signed(10), 0), Error::::BadPosition); // 10 is still vouching until candidate is approved or rejected assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); @@ -522,11 +552,11 @@ fn unbid_vouch_works() { // 10 vouches for 20 assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 0)); // 20 has a bid - assert_eq!(>::get(), vec![create_bid(100, 20, BidKind::Vouch(10, 0))]); + assert_eq!(>::get(), vec![(20, candidacy(1, 100, BidKind::Vouch(10,, 0, 0) 0))]); // 10 is vouched assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); // 20 doesn't want to be a member and can unbid themselves. - assert_ok!(Society::unbid(Origin::signed(20), 0)); + assert_ok!(Society::unbid(Origin::signed(20))); // Everything is cleaned up assert_eq!(>::get(10), None); assert_eq!(>::get(), vec![]); @@ -720,13 +750,13 @@ fn vouching_handles_removed_member_with_bid() { // Have that member vouch for a user assert_ok!(Society::vouch(Origin::signed(20), 30, 1000, 100)); // That user is now a bid and the member is vouching - assert_eq!(>::get(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); + assert_eq!(>::get(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20,, 0, 0) 100))]); assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); // Suspend that member Society::suspend_member(&20); assert_eq!(>::get(20), true); // Nothing changes yet - assert_eq!(>::get(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); + assert_eq!(>::get(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20,, 0, 0) 100))]); assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); // Remove member assert_ok!(Society::judge_suspended_member(Origin::signed(2), 20, false)); @@ -744,22 +774,22 @@ fn vouching_handles_removed_member_with_candidate() { // Have that member vouch for a user assert_ok!(Society::vouch(Origin::signed(20), 30, 1000, 100)); // That user is now a bid and the member is vouching - assert_eq!(>::get(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); + assert_eq!(>::get(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20,, 0, 0) 100))]); assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); // Make that bid a candidate run_to_block(4); - assert_eq!(candidates(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); + assert_eq!(candidates(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20,, 0, 0) 100))]); // Suspend that member Society::suspend_member(&20); assert_eq!(>::get(20), true); // Nothing changes yet - assert_eq!(candidates(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); + assert_eq!(candidates(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20,, 0, 0) 100))]); assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); // Remove member assert_ok!(Society::judge_suspended_member(Origin::signed(2), 20, false)); // Vouching status is removed, but candidate is still in the queue assert_eq!(>::get(20), None); - assert_eq!(candidates(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); + assert_eq!(candidates(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20,, 0, 0) 100))]); // Candidate wins assert_ok!(Society::vote(Origin::signed(10), 30, true)); run_to_block(8); @@ -811,9 +841,9 @@ fn max_limits_work() { // Length is 1000 assert_eq!(bids.len(), 1000); // First bid is smallest number (100) - assert_eq!(bids[0], create_bid(100, 100, BidKind::Deposit(25))); + assert_eq!(bids[0], (100, candidacy(1, 100, BidKind::Deposit(25), 0, 0))); // Last bid is smallest number + 99 (1099) - assert_eq!(bids[999], create_bid(1099, 1099, BidKind::Deposit(25))); + assert_eq!(bids[999], (1099, candidacy(1, 1099, BidKind::Deposit(25), 0, 0))); // Rotate period run_to_block(4); // Max of 10 candidates @@ -883,14 +913,14 @@ fn zero_bid_works() { assert_eq!( candidates(), vec![ - create_bid(0, 30, BidKind::Deposit(25)), - create_bid(300, 50, BidKind::Deposit(25)), - create_bid(400, 60, BidKind::Deposit(25)), + (30, candidacy(1, 0, BidKind::Deposit(25), 0, 0)), + (50, candidacy(1, 300, BidKind::Deposit(25), 0, 0)), + (60, candidacy(1, 400, BidKind::Deposit(25), 0, 0)), ] ); assert_eq!( >::get(), - vec![create_bid(0, 20, BidKind::Deposit(25)), create_bid(0, 40, BidKind::Deposit(25)),] + vec![(20, candidacy(1, 0, BidKind::Deposit(25), 0, 0)), (40, candidacy(1, 0, BidKind::Deposit(25), 0, 0)),] ); // A member votes for these candidates to join the society assert_ok!(Society::vote(Origin::signed(10), 30, true)); @@ -921,7 +951,7 @@ fn bids_ordered_correctly() { for j in 0..5 { for i in 0..5 { - final_list.push(create_bid(j, 100 + (i * 5 + j) as u128, BidKind::Deposit(25))); + final_list.push((100 +candidacy(j1, , (i * 5 + j), 0, 0) as u128, BidKind::Deposit(25))); } } From 841c732f922576ef4f1cc98442a97fb4ec92c84b Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 4 May 2022 15:38:36 +0100 Subject: [PATCH 07/43] Improvements to the voting process --- frame/society/src/lib.rs | 131 ++++++++--- frame/society/src/mock.rs | 2 +- frame/society/src/tests.rs | 461 +++++++++++++++++++++---------------- 3 files changed, 368 insertions(+), 226 deletions(-) diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index b0d1f6d249f05..6b99e31c8bb1b 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -257,7 +257,7 @@ use frame_support::{ pallet_prelude::*, traits::{ Currency, EnsureOrigin, ExistenceRequirement::AllowDeath, - Imbalance, OnUnbalanced, Randomness, ReservableCurrency, + Imbalance, OnUnbalanced, Randomness, ReservableCurrency, BalanceStatus, }, PalletId, }; @@ -375,6 +375,14 @@ impl Tally { fn more_rejections(&self) -> bool { self.rejections > self.approvals } + + fn clear_approval(&self) -> bool { + self.approvals >= (2 * self.rejections).max(1) + } + + fn clear_rejection(&self) -> bool { + self.rejections >= (2 * self.approvals).max(1) + } } /// A bid for entry into society. @@ -388,6 +396,8 @@ pub struct Candidacy { bid: Balance, /// The tally of votes so far. tally: Tally, + /// True if the skeptic was already punished for note voting. + skeptic_struck: bool, } /// A vote by a member on a candidate application. @@ -572,12 +582,16 @@ pub mod pallet { NotFounder, /// The caller is not the head. NotHead, - /// The membership cannot be claimed as the member does not (yet) have enough votes. + /// The membership cannot be claimed as the candidate was not clearly approved. NotApproved, - /// The candidacy cannot be claimed/dropped as the voting is still in progress. - InProgress, - /// The candidacy cannot be dropped as the candidate is approved. + /// The candidate cannot be kicked as the candidate was not clearly rejected. + NotRejected, + /// The candidacy cannot be dropped as the candidate was clearly approved. Approved, + /// The candidacy cannot be bestowed as the candidate was clearly rejected. + Rejected, + /// The candidacy cannot be concluded as the voting is still in progress. + InProgress, /// The candidacy cannot be pruned until a full additional intake period has passed. TooEarly, /// The skeptic already voted. @@ -592,6 +606,8 @@ pub mod pallet { NotGroup, /// The member is already elevated to this rank. AlreadyElevated, + /// The skeptic has already been punished for this offence. + AlreadyPunished, } #[pallet::event] @@ -1044,6 +1060,7 @@ pub mod pallet { #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; + ensure!(Members::::get(&who).ok_or(Error::::NotMember)?.rank == 0, Error::::NoPayout); let mut record = Payouts::::get(&who); if let Some((when, amount)) = record.payouts.first() { @@ -1218,65 +1235,90 @@ pub mod pallet { Ok(()) } + /// Punish the skeptic with a strike if they did not vote on a candidate. Callable by the + /// candidate. + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn punish_skeptic(origin: OriginFor) -> DispatchResult { + let candidate = ensure_signed(origin)?; + let mut candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + ensure!(!candidacy.skeptic_struck, Error::::AlreadyPunished); + ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); + Self::check_skeptic(&candidate, &mut candidacy); + Candidates::::insert(&candidate, candidacy); + Ok(()) + } + /// Transform an approved candidate into a member. Callable only by the /// the candidate, and only after the period for voting has ended. #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn claim_membership(origin: OriginFor) -> DispatchResult { let candidate = ensure_signed(origin)?; let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; - ensure!(candidacy.tally.more_approvals(), Error::::NotApproved); + ensure!(candidacy.tally.clear_approval(), Error::::NotApproved); ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); Self::induct_member(candidate, candidacy, 0) } /// Transform an approved candidate into a member. Callable only by the Signed origin of the - /// Founder and only after the period for voting has ended. + /// Founder, only after the period for voting has ended and only when the candidate is not + /// clearly rejected. #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn force_membership(origin: OriginFor, candidate: T::AccountId) -> DispatchResult { + pub fn bestow_membership(origin: OriginFor, candidate: T::AccountId) -> DispatchResult { let founder = ensure_signed(origin)?; ensure!(Founder::::get() == Some(founder.clone()), Error::::NotFounder); - let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + ensure!(!candidacy.tally.clear_rejection(), Error::::Rejected); ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); Self::induct_member(candidate, candidacy, 0) } /// Remove the candidate's application from the society. Callable only by the Signed origin - /// of the Founder, and only after the period for voting has ended. + /// of the Founder, only after the period for voting has ended, and only when they do not + /// have a clear approval. + /// + /// Any bid deposit is lost and voucher is banned. #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn kick_candidate(origin: OriginFor, candidate: T::AccountId) -> DispatchResult { let founder = ensure_signed(origin)?; ensure!(Founder::::get() == Some(founder.clone()), Error::::NotFounder); - let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + let mut candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); - Self::check_skeptic(&candidate, &candidacy); + ensure!(!candidacy.tally.clear_approval(), Error::::Approved); + Self::check_skeptic(&candidate, &mut candidacy); + Self::reject_candidate(&candidate, &candidacy.kind); Votes::::remove_prefix(&candidate, None); Candidates::::remove(&candidate); Ok(()) } - /// Remove the candidate's failed application from the society. Callable only by the - /// the candidate, and only after the period for voting has ended. + /// Remove the candidate's application from the society. Callable only by the candidate. + /// + /// Any bid deposit is lost and voucher is banned. #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn resign_candidate(origin: OriginFor) -> DispatchResult { + pub fn resign_candidacy(origin: OriginFor) -> DispatchResult { let candidate = ensure_signed(origin)?; - let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; - ensure!(!candidacy.tally.more_approvals(), Error::::Approved); - ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); - Self::check_skeptic(&candidate, &candidacy); + let mut candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + if !Self::in_progress(candidacy.round) { + Self::check_skeptic(&candidate, &mut candidacy); + } + Self::reject_candidate(&candidate, &candidacy.kind); Votes::::remove_prefix(&candidate, None); Candidates::::remove(&candidate); Ok(()) } /// Remove a `candidate`'s failed application from the society. Callable by any - /// signed origin but only after the subsequent period for voting has ended. + /// signed origin but only at the end of the subsequent round and only for + /// a candidate with more rejections than approvals. + /// + /// The bid deposit is lost and the voucher is banned. #[pallet::weight(T::BlockWeights::get().max_block / 10)] pub fn drop_candidate(origin: OriginFor, candidate: T::AccountId) -> DispatchResult { ensure_signed(origin)?; let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; - ensure!(!candidacy.tally.more_approvals(), Error::::Approved); + ensure!(candidacy.tally.clear_rejection(), Error::::NotRejected); ensure!(RoundCount::::get() > candidacy.round + 1, Error::::TooEarly); + Self::reject_candidate(&candidate, &candidacy.kind); Votes::::remove_prefix(&candidate, None); Candidates::::remove(&candidate); Ok(()) @@ -1356,20 +1398,22 @@ impl, I: 'static> Pallet { Vote { approve, weight } } - fn check_skeptic(candidate: &T::AccountId, candidacy: &Candidacy>) { - if RoundCount::::get() != candidacy.round { return } + fn check_skeptic(candidate: &T::AccountId, candidacy: &mut Candidacy>) { + if RoundCount::::get() != candidacy.round || candidacy.skeptic_struck { return } // We expect the skeptic to have voted. let skeptic = match Skeptic::::get() { Some(s) => s, None => return }; let maybe_vote = Votes::::get(&candidate, &skeptic); - let approved = candidacy.tally.more_approvals(); - let rejected = candidacy.tally.more_rejections(); + let approved = candidacy.tally.clear_approval(); + let rejected = candidacy.tally.clear_rejection(); match (maybe_vote, approved, rejected) { (None, _, _) | (Some(Vote { approve: true, .. }), false, true) | (Some(Vote { approve: false, .. }), true, false) => { // Can't do much if the punishment doesn't work out. - let _ = Self::strike_member(&skeptic); + if Self::strike_member(&skeptic).is_ok() { + candidacy.skeptic_struck = true; + } }, _ => {}, } @@ -1489,6 +1533,7 @@ impl, I: 'static> Pallet { kind: bid.kind.clone(), bid: bid.value, tally: Default::default(), + skeptic_struck: false, }; Candidates::::insert(&bid.who, candidacy); selections.saturating_inc(); @@ -1537,6 +1582,26 @@ impl, I: 'static> Pallet { } } + /// Either repatriate the deposit into the Society account or ban the vouching member. + /// + /// In neither case can we do much if the action isn't completable, but there's + /// no reason that either should fail. + /// + /// WARNING: This alters the voucher item of `Members`. You must ensure that you do not + /// accidentally overwrite it with an older value after calling this. + fn reject_candidate(who: &T::AccountId, kind: &BidKind>) { + match kind { + BidKind::Deposit(deposit) => { + let pot = Self::account_id(); + let free = BalanceStatus::Free; + debug_assert!(T::Currency::repatriate_reserved(&who, &pot, *deposit, free).is_ok()); + }, + BidKind::Vouch(voucher, _) => { + Members::::mutate_extant(voucher, |record| record.vouching = Some(VouchingStatus::Banned)); + }, + } + } + /// Check a user has a bid. fn has_bid(bids: &Vec>>, who: &T::AccountId) -> bool { // Bids are ordered by `value`, so we cannot binary search for a user. @@ -1581,10 +1646,10 @@ impl, I: 'static> Pallet { /// Induct a new member into the set. fn induct_member( candidate: T::AccountId, - candidacy: Candidacy>, + mut candidacy: Candidacy>, rank: Rank, ) -> DispatchResult { - Self::check_skeptic(&candidate, &candidacy); + Self::check_skeptic(&candidate, &mut candidacy); Self::add_new_member(&candidate, rank)?; let next_head = NextHead::::get() .filter(|old| old.round > candidacy.round @@ -1602,18 +1667,22 @@ impl, I: 'static> Pallet { fn strike_member(who: &T::AccountId) -> DispatchResult { let mut record = Members::::get(who).ok_or(Error::::NotMember)?; record.strikes.saturating_inc(); + Members::::insert(who, &record); + // ^^^ Keep the member record mutation self-contained as we might be suspending them later + // in this function. + if record.strikes >= T::GraceStrikes::get() { // Too many strikes: slash the payout in half. let total_payout = Payouts::::get(who).payouts.iter() .fold(BalanceOf::::zero(), |acc, x| acc.saturating_add(x.1)); Self::slash_payout(who, total_payout / 2u32.into()); } + let params = Parameters::::get().ok_or(Error::::NotGroup)?; if record.strikes >= params.max_strikes { // Way too many strikes: suspend. Self::suspend_member(who); } - Members::::insert(who, &record); Ok(()) } @@ -1675,6 +1744,8 @@ impl, I: 'static> Pallet { if let Ok(record) = Self::remove_member(&who) { SuspendedMembers::::insert(who, record); Self::deposit_event(Event::::MemberSuspended { member: who.clone() }); + } else { + debug_assert!(false, "Unable to remove member (is it the Head or Founder?)") } } @@ -1772,7 +1843,7 @@ impl, I: 'static> Pallet { /// Bump the payout amount of `who`, to be unlocked at the given block number. /// - /// If it the caller's duty to ensure that `who` is already a member. This does nothing if `who` + /// It is the caller's duty to ensure that `who` is already a member. This does nothing if `who` /// is not a member or if `value` is zero. fn bump_payout(who: &T::AccountId, when: T::BlockNumber, value: BalanceOf) { if value.is_zero() { return } diff --git a/frame/society/src/mock.rs b/frame/society/src/mock.rs index d3b8e5168346d..19f08296baf8a 100644 --- a/frame/society/src/mock.rs +++ b/frame/society/src/mock.rs @@ -195,5 +195,5 @@ pub fn candidacy( approvals: VoteCount, rejections: VoteCount, ) -> Candidacy { - Candidacy { round, kind, bid, tally: Tally { approvals, rejections } } + Candidacy { round, kind, bid, tally: Tally { approvals, rejections }, skeptic_struck: false } } diff --git a/frame/society/src/tests.rs b/frame/society/src/tests.rs index c377bbdafb654..c3c6984851ec5 100644 --- a/frame/society/src/tests.rs +++ b/frame/society/src/tests.rs @@ -24,14 +24,80 @@ use frame_support::{assert_noop, assert_ok}; use sp_core::blake2_256; use sp_runtime::traits::BadOrigin; +fn next_voting() { + if let Period::Voting { more, .. } = Society::period() { + run_to_block(System::block_number() + more); + } +} + +fn conclude_intake(allow_resignation: bool, judge_intake: Option) { + next_voting(); + let round = RoundCount::::get(); + for (who, candidacy) in Candidates::::iter() { + if candidacy.tally.clear_approval() { + assert_ok!(Society::claim_membership(Origin::signed(who))); + assert_noop!(Society::claim_membership(Origin::signed(who)), Error::::NotCandidate); + continue + } + if candidacy.tally.clear_rejection() && allow_resignation { + assert_noop!(Society::claim_membership(Origin::signed(who)), Error::::NotApproved); + assert_ok!(Society::resign_candidacy(Origin::signed(who))); + continue + } + if let (Some(founder), Some(approve)) = (Founder::::get(), judge_intake) { + if !candidacy.tally.clear_approval() && !approve { + // can be rejected by founder + assert_ok!(Society::kick_candidate(Origin::signed(founder), who)); + continue + } + if !candidacy.tally.clear_rejection() && approve { + // can be rejected by founder + assert_ok!(Society::bestow_membership(Origin::signed(founder), who)); + continue + } + } + if candidacy.tally.clear_rejection() && round > candidacy.round + 1 { + assert_noop!(Society::claim_membership(Origin::signed(who)), Error::::NotApproved); + assert_ok!(Society::drop_candidate(Origin::signed(0), who)); + assert_noop!(Society::drop_candidate(Origin::signed(0), who), Error::::NotCandidate); + continue + } + if !candidacy.skeptic_struck { + assert_ok!(Society::punish_skeptic(Origin::signed(who))); + } + } +} + +fn next_intake() { + let claim_period: u64 = ::ClaimPeriod::get(); + match Society::period() { + Period::Voting { more, .. } => run_to_block(System::block_number() + more + claim_period), + Period::Claim { more, .. } => run_to_block(System::block_number() + more), + } +} + +fn place_members(members: impl AsRef<[u128]>) { + for who in members.as_ref() { + assert_ok!(Society::insert_member(who, 0)); + } +} + fn members() -> Vec { let mut r = Members::::iter_keys().collect::>(); r.sort(); r } -fn candidates() -> Vec<(u128, Candidacy)> { - Candidates::::iter().collect() +fn candidacies() -> Vec<(u128, Candidacy)> { + let mut r = Candidates::::iter().collect::>(); + r.sort_by_key(|x| x.0); + r +} + +fn candidates() -> Vec { + let mut r = Candidates::::iter_keys().collect::>(); + r.sort(); + r } #[test] @@ -57,12 +123,12 @@ fn founding_works() { // rules are correct assert_eq!(Rules::::get(), Some(blake2_256(b"be cool").into())); // Pot grows after first rotation period - run_to_block(4); + next_intake(); assert_eq!(Pot::::get(), 1000); // Cannot start another society assert_noop!( Society::found_society(Origin::signed(1), 20, 100, 10, 2, 25, vec![]), - Error::::AlreadyFounded + Error::::AlreadyFounded ); }); } @@ -73,7 +139,7 @@ fn unfounding_works() { // Account 1 sets the founder... assert_ok!(Society::found_society(Origin::signed(1), 10, 100, 10, 2, 25, vec![])); // Account 2 cannot unfound it as it's not the founder. - assert_noop!(Society::dissolve(Origin::signed(2)), Error::::NotFounder); + assert_noop!(Society::dissolve(Origin::signed(2)), Error::::NotFounder); // Account 10 can, though. assert_ok!(Society::dissolve(Origin::signed(10))); @@ -81,13 +147,12 @@ fn unfounding_works() { assert_ok!(Society::found_society(Origin::signed(1), 20, 100, 10, 2, 25, vec![])); // Bring in a new member... assert_ok!(Society::bid(Origin::signed(10), 0)); - run_to_block(4); + next_intake(); assert_ok!(Society::vote(Origin::signed(20), 10, true)); - run_to_block(7); - assert_ok!(Society::claim_membership(Origin::signed(10))); + conclude_intake(true, None); // Unfounding won't work now, even though it's from 20. - assert_noop!(Society::dissolve(Origin::signed(20)), Error::::NotHead); + assert_noop!(Society::dissolve(Origin::signed(20)), Error::::NotHead); }); } @@ -100,15 +165,14 @@ fn basic_new_member_works() { assert_eq!(Balances::free_balance(20), 25); assert_eq!(Balances::reserved_balance(20), 25); // Rotate period every 4 blocks - run_to_block(4); + next_intake(); // 20 is now a candidate - assert_eq!(candidates(), vec![(20, candidacy(1, 0, BidKind::Deposit(25), 0, 0))]); + assert_eq!(candidacies(), vec![(20, candidacy(1, 0, BidKind::Deposit(25), 0, 0))]); // 10 (a member) can vote for the candidate assert_ok!(Society::vote(Origin::signed(10), 20, true)); + conclude_intake(true, None); // Rotate period every 4 blocks - run_to_block(7); - assert_ok!(Society::claim_membership(Origin::signed(20))); - run_to_block(8); + next_intake(); // 20 is now a member of the society assert_eq!(members(), vec![10, 20]); // Reserved balance is returned @@ -126,13 +190,13 @@ fn bidding_works() { assert_ok!(Society::bid(Origin::signed(40), 400)); assert_ok!(Society::bid(Origin::signed(30), 300)); // Rotate period - run_to_block(4); + next_intake(); // Pot is 1000 after "PeriodSpend" assert_eq!(Pot::::get(), 1000); assert_eq!(Balances::free_balance(Society::account_id()), 10_000); // Choose smallest bidding users whose total is less than pot assert_eq!( - candidates(), + candidacies(), vec![ (30, candidacy(1, 300, BidKind::Deposit(25), 0, 0)), (40, candidacy(1, 400, BidKind::Deposit(25), 0, 0)), @@ -141,21 +205,18 @@ fn bidding_works() { // A member votes for these candidates to join the society assert_ok!(Society::vote(Origin::signed(10), 30, true)); assert_ok!(Society::vote(Origin::signed(10), 40, true)); - run_to_block(7); - assert_ok!(Society::claim_membership(Origin::signed(30))); - assert_ok!(Society::claim_membership(Origin::signed(40))); - run_to_block(8); + conclude_intake(true, None); + next_intake(); // Candidates become members after a period rotation assert_eq!(members(), vec![10, 30, 40]); // Pot is increased by 1000, but pays out 700 to the members assert_eq!(Balances::free_balance(Society::account_id()), 9_300); assert_eq!(Pot::::get(), 1_300); // Left over from the original bids is 50 who satisfies the condition of bid less than pot. - assert_eq!(candidates(), vec![(50, candidacy(2, 500, BidKind::Deposit(25), 0, 0))]); + assert_eq!(candidacies(), vec![(50, candidacy(2, 500, BidKind::Deposit(25), 0, 0))]); // 40, now a member, can vote for 50 assert_ok!(Society::vote(Origin::signed(40), 50, true)); - run_to_block(11); - assert_ok!(Society::claim_membership(Origin::signed(50))); + conclude_intake(true, None); run_to_block(12); // 50 is now a member assert_eq!(members(), vec![10, 30, 40, 50]); @@ -163,7 +224,7 @@ fn bidding_works() { assert_eq!(Pot::::get(), 1_800); assert_eq!(Balances::free_balance(Society::account_id()), 8_800); // No more candidates satisfy the requirements - assert_eq!(candidates(), vec![]); + assert_eq!(candidacies(), vec![]); assert_ok!(Society::defender_vote(Origin::signed(10), true)); // Keep defender around // Next period run_to_block(16); @@ -174,11 +235,10 @@ fn bidding_works() { // No payouts assert_eq!(Balances::free_balance(Society::account_id()), 8_800); // Candidate 60 now qualifies based on the increased pot size. - assert_eq!(candidates(), vec![(60, candidacy(4, 1900, BidKind::Deposit(25), 0, 0))]); + assert_eq!(candidacies(), vec![(60, candidacy(4, 1900, BidKind::Deposit(25), 0, 0))]); // Candidate 60 is voted in. assert_ok!(Society::vote(Origin::signed(50), 60, true)); - run_to_block(19); - assert_ok!(Society::claim_membership(Origin::signed(60))); + conclude_intake(true, None); run_to_block(20); // 60 joins as a member assert_eq!(members(), vec![10, 30, 40, 50, 60]); @@ -199,13 +259,13 @@ fn unbidding_works() { assert_eq!(Balances::reserved_balance(30), 25); // Can unbid themselves with the right position assert_ok!(Society::unbid(Origin::signed(30))); - assert_noop!(Society::unbid(Origin::signed(30)), Error::::NotBidder); + assert_noop!(Society::unbid(Origin::signed(30)), Error::::NotBidder); // Balance is returned assert_eq!(Balances::free_balance(30), 50); assert_eq!(Balances::reserved_balance(30), 0); // 20 wins candidacy - run_to_block(4); - assert_eq!(candidates(), vec![(20, candidacy(1, 1000, BidKind::Deposit(25), 0, 0))]); + next_intake(); + assert_eq!(candidacies(), vec![(20, candidacy(1, 1000, BidKind::Deposit(25), 0, 0))]); }); } @@ -215,13 +275,12 @@ fn payout_works() { // Original balance of 50 assert_eq!(Balances::free_balance(20), 50); assert_ok!(Society::bid(Origin::signed(20), 1000)); - run_to_block(4); + next_intake(); assert_ok!(Society::vote(Origin::signed(10), 20, true)); - run_to_block(7); - assert_ok!(Society::claim_membership(Origin::signed(20))); + conclude_intake(true, None); // payout not ready - assert_noop!(Society::payout(Origin::signed(20)), Error::::NoPayout); - run_to_block(8); + assert_noop!(Society::payout(Origin::signed(20)), Error::::NoPayout); + next_intake(); // payout should be here assert_ok!(Society::payout(Origin::signed(20))); assert_eq!(Balances::free_balance(20), 1050); @@ -233,12 +292,10 @@ fn non_voting_skeptic_is_punished() { EnvBuilder::new().execute(|| { assert_eq!(Members::::get(10).unwrap().strikes, 0); assert_ok!(Society::bid(Origin::signed(20), 0)); - run_to_block(4); - assert_eq!(candidates(), vec![(20, candidacy(1, 0, BidKind::Deposit(25), 0, 0))]); - run_to_block(7); - assert_noop!(Society::claim_membership(Origin::signed(20)), Error::::NotApproved); - assert_ok!(Society::resign_candidate(Origin::signed(20))); - run_to_block(8); + next_intake(); + assert_eq!(candidacies(), vec![(20, candidacy(1, 0, BidKind::Deposit(25), 0, 0))]); + conclude_intake(true, None); + next_intake(); assert_eq!(members(), vec![10]); assert_eq!(Members::::get(10).unwrap().strikes, 1); }); @@ -247,31 +304,21 @@ fn non_voting_skeptic_is_punished() { #[test] fn rejecting_skeptic_on_approved_is_punished() { EnvBuilder::new().execute(|| { - assert_eq!(Members::::get(10).unwrap().strikes, 0); - assert_ok!(Society::bid(Origin::signed(20), 0)); - assert_ok!(Society::bid(Origin::signed(30), 1)); - run_to_block(4); + place_members([20, 30]); assert_ok!(Society::bid(Origin::signed(40), 0)); - - assert_ok!(Society::vote(Origin::signed(10), 20, true)); - assert_ok!(Society::vote(Origin::signed(10), 30, true)); - run_to_block(7); - assert_ok!(Society::claim_membership(Origin::signed(20))); - assert_ok!(Society::claim_membership(Origin::signed(30))); - run_to_block(8); + next_intake(); let skeptic = Skeptic::::get().unwrap(); for &i in &[10, 20, 30][..] { assert_ok!(Society::vote(Origin::signed(i), 40, i != skeptic)); } - run_to_block(11); - assert_ok!(Society::claim_membership(Origin::signed(40))); + conclude_intake(true, None); + assert_eq!(Members::::get(10).unwrap().strikes, 0); run_to_block(12); assert_eq!(members(), vec![10, 20, 30, 40]); assert_eq!(Members::::get(skeptic).unwrap().strikes, 1); }); } -/* #[test] fn basic_new_member_reject_works() { EnvBuilder::new().execute(|| { @@ -282,16 +329,17 @@ fn basic_new_member_reject_works() { assert_eq!(Balances::free_balance(20), 25); assert_eq!(Balances::reserved_balance(20), 25); // Rotation Period - run_to_block(4); - assert_eq!(candidates(), vec![(20, candidacy(1, 0, BidKind::Deposit(25), 0, 0))]); + next_intake(); + assert_eq!(candidacies(), vec![(20, candidacy(1, 0, BidKind::Deposit(25), 0, 0))]); // We say no assert_ok!(Society::vote(Origin::signed(10), 20, false)); - run_to_block(8); + conclude_intake(true, None); + next_intake(); // User is not added as member assert_eq!(members(), vec![10]); - // User is suspended - assert_eq!(candidates(), vec![]); - assert_eq!(Society::suspended_candidate(20).is_some(), true); + // User is rejected. + assert_eq!(candidacies(), vec![]); + assert_eq!(Bids::::get(), vec![]); }); } @@ -300,19 +348,20 @@ fn slash_payout_works() { EnvBuilder::new().execute(|| { assert_eq!(Balances::free_balance(20), 50); assert_ok!(Society::bid(Origin::signed(20), 1000)); - run_to_block(4); + next_intake(); assert_ok!(Society::vote(Origin::signed(10), 20, true)); - run_to_block(8); + conclude_intake(true, None); // payout in queue - assert_eq!(Payouts::::get(20), vec![(9, 1000)]); - assert_noop!(Society::payout(Origin::signed(20)), Error::::NoPayout); + assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(8, 1000)].try_into().unwrap() }); + assert_noop!(Society::payout(Origin::signed(20)), Error::::NoPayout); // slash payout assert_eq!(Society::slash_payout(&20, 500), 500); - assert_eq!(Payouts::::get(20), vec![(9, 500)]); - run_to_block(9); + assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(8, 500)].try_into().unwrap() }); + run_to_block(8); // payout should be here, but 500 less assert_ok!(Society::payout(Origin::signed(20))); assert_eq!(Balances::free_balance(20), 550); + assert_eq!(Payouts::::get(20), PayoutRecord { paid: 500, payouts: Default::default() }); }); } @@ -320,19 +369,20 @@ fn slash_payout_works() { fn slash_payout_multi_works() { EnvBuilder::new().execute(|| { assert_eq!(Balances::free_balance(20), 50); + place_members([20]); // create a few payouts Society::bump_payout(&20, 5, 100); Society::bump_payout(&20, 10, 100); Society::bump_payout(&20, 15, 100); Society::bump_payout(&20, 20, 100); // payouts in queue - assert_eq!(Payouts::::get(20), vec![(5, 100), (10, 100), (15, 100), (20, 100)]); + assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(5, 100), (10, 100), (15, 100), (20, 100)].try_into().unwrap() }); // slash payout assert_eq!(Society::slash_payout(&20, 250), 250); - assert_eq!(Payouts::::get(20), vec![(15, 50), (20, 100)]); + assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(15, 50), (20, 100)].try_into().unwrap() }); // slash again assert_eq!(Society::slash_payout(&20, 50), 50); - assert_eq!(Payouts::::get(20), vec![(20, 100)]); + assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(20, 100)].try_into().unwrap() }); }); } @@ -340,132 +390,153 @@ fn slash_payout_multi_works() { fn suspended_member_life_cycle_works() { EnvBuilder::new().execute(|| { // Add 20 to members, who is not the head and can be suspended/removed. - assert_ok!(Society::add_member(&20)); - assert_eq!(>::get(), vec![10, 20]); - assert_eq!(Strikes::::get(20), 0); - assert_eq!(>::get(20), false); + place_members([20]); + assert_eq!(members(), vec![10, 20]); + assert_eq!(Members::::get(20).unwrap().strikes, 0); + assert!(!SuspendedMembers::::contains_key(20)); // Let's suspend account 20 by giving them 2 strikes by not voting assert_ok!(Society::bid(Origin::signed(30), 0)); - run_to_block(8); - assert_eq!(Strikes::::get(20), 1); - assert_ok!(Society::bid(Origin::signed(40), 0)); - run_to_block(16); + assert_ok!(Society::bid(Origin::signed(40), 1)); + next_intake(); + conclude_intake(false, None); + - // Strike 2 is accumulated, and 20 is suspended :( - assert_eq!(>::get(20), true); - assert_eq!(>::get(), vec![10]); + // 2 strikes are accumulated, and 20 is suspended :( + assert!(SuspendedMembers::::contains_key(20)); + assert_eq!(members(), vec![10]); // Suspended members cannot get payout Society::bump_payout(&20, 10, 100); - assert_noop!(Society::payout(Origin::signed(20)), Error::::NotMember); + assert_noop!(Society::payout(Origin::signed(20)), Error::::NotMember); // Normal people cannot make judgement - assert_noop!(Society::judge_suspended_member(Origin::signed(20), 20, true), BadOrigin); + assert_noop!(Society::judge_suspended_member(Origin::signed(20), 20, true), Error::::NotFounder); // Suspension judgment origin can judge thee // Suspension judgement origin forgives the suspended member - assert_ok!(Society::judge_suspended_member(Origin::signed(2), 20, true)); - assert_eq!(>::get(20), false); - assert_eq!(>::get(), vec![10, 20]); + assert_ok!(Society::judge_suspended_member(Origin::signed(10), 20, true)); + assert!(!SuspendedMembers::::contains_key(20)); + assert_eq!(members(), vec![10, 20]); // Let's suspend them again, directly Society::suspend_member(&20); - assert_eq!(>::get(20), true); + assert!(SuspendedMembers::::contains_key(20)); // Suspension judgement origin does not forgive the suspended member - assert_ok!(Society::judge_suspended_member(Origin::signed(2), 20, false)); + assert_ok!(Society::judge_suspended_member(Origin::signed(10), 20, false)); // Cleaned up - assert_eq!(>::get(20), false); - assert_eq!(>::get(), vec![10]); - assert_eq!(>::get(20), vec![]); + assert!(!SuspendedMembers::::contains_key(20)); + assert_eq!(members(), vec![10]); + assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![].try_into().unwrap() }); }); } #[test] fn suspended_candidate_rejected_works() { EnvBuilder::new().execute(|| { - // Starting Balance - assert_eq!(Balances::free_balance(20), 50); - assert_eq!(Balances::free_balance(Society::account_id()), 10000); - // 20 makes a bid - assert_ok!(Society::bid(Origin::signed(20), 0)); - assert_eq!(Balances::free_balance(20), 25); - assert_eq!(Balances::reserved_balance(20), 25); + place_members([20, 30]); + // 40, 50, 60, 70, 80 make bids + for &x in &[40u128, 50, 60, 70] { + assert_ok!(Society::bid(Origin::signed(x), 10)); + assert_eq!(Balances::free_balance(x), 25); + assert_eq!(Balances::reserved_balance(x), 25); + } + // Rotation Period - run_to_block(4); - assert_eq!(candidates(), vec![(20, candidacy(1, 0, BidKind::Deposit(25), 0, 0))]); - // We say no - assert_ok!(Society::vote(Origin::signed(10), 20, false)); - run_to_block(8); - // User is not added as member - assert_eq!(members(), vec![10]); - // User is suspended - assert_eq!(candidates(), vec![]); - assert_eq!(Society::suspended_candidate(20).is_some(), true); + next_intake(); + assert_eq!(candidacies(), vec![ + (40, candidacy(1, 10, BidKind::Deposit(25), 0, 0)), + (50, candidacy(1, 10, BidKind::Deposit(25), 0, 0)), + (60, candidacy(1, 10, BidKind::Deposit(25), 0, 0)), + (70, candidacy(1, 10, BidKind::Deposit(25), 0, 0)), + ]); - // Normal user cannot make judgement on suspended candidate - assert_noop!( - Society::judge_suspended_candidate(Origin::signed(20), 20, Judgement::Approve), - BadOrigin - ); + // Split vote over all. + for &x in &[40, 50, 60, 70] { + assert_ok!(Society::vote(Origin::signed(20), x, false)); + assert_ok!(Society::vote(Origin::signed(30), x, true)); + } - // Suspension judgement origin makes no direct judgement - assert_ok!(Society::judge_suspended_candidate(Origin::signed(2), 20, Judgement::Rebid)); - // They are placed back in bid pool, repeat suspension process - // Rotation Period - run_to_block(12); - assert_eq!(candidates(), vec![(20, candidacy(1, 0, BidKind::Deposit(25), 0, 0))]); - // We say no - assert_ok!(Society::vote(Origin::signed(10), 20, false)); - run_to_block(16); - // User is not added as member - assert_eq!(members(), vec![10]); - // User is suspended - assert_eq!(candidates(), vec![]); - assert_eq!(Society::suspended_candidate(20).is_some(), true); + // Voting continues, as no canidate is clearly accepted yet and the founder chooses not to + // act. + conclude_intake(false, None); + assert_eq!(members(), vec![10, 20, 30]); + assert_eq!(candidates(), vec![40, 50, 60, 70]); - // Suspension judgement origin rejects the candidate - assert_ok!(Society::judge_suspended_candidate(Origin::signed(2), 20, Judgement::Reject)); - // User is slashed - assert_eq!(Balances::free_balance(20), 25); - assert_eq!(Balances::reserved_balance(20), 0); - // Funds are deposited to society account - assert_eq!(Balances::free_balance(Society::account_id()), 10025); - // Cleaned up + // 40 gets approved after founder weighs in giving it a clear approval. + // but the founder's rejection of 60 doesn't do much for now. + assert_ok!(Society::vote(Origin::signed(10), 40, true)); + assert_ok!(Society::vote(Origin::signed(10), 60, false)); + conclude_intake(false, None); + assert_eq!(members(), vec![10, 20, 30, 40]); + assert_eq!(candidates(), vec![50, 60, 70]); + assert_eq!(Balances::free_balance(40), 50); + assert_eq!(Balances::reserved_balance(40), 0); + assert_eq!(Balances::free_balance(Society::account_id()), 9990); + + // Founder manually bestows membership on 50 and and kicks 70. + assert_ok!(Society::bestow_membership(Origin::signed(10), 50)); + assert_eq!(members(), vec![10, 20, 30, 40, 50]); + assert_eq!(candidates(), vec![60, 70]); + assert_eq!(Balances::free_balance(50), 50); + assert_eq!(Balances::reserved_balance(50), 0); + assert_eq!(Balances::free_balance(Society::account_id()), 9980); + + assert_ok!(Society::kick_candidate(Origin::signed(10), 70)); + assert_eq!(members(), vec![10, 20, 30, 40, 50]); + assert_eq!(candidates(), vec![60]); + assert_eq!(Balances::free_balance(70), 25); + assert_eq!(Balances::reserved_balance(70), 0); + assert_eq!(Balances::free_balance(Society::account_id()), 10005); + + // Next round doesn't make much difference. + next_intake(); + conclude_intake(false, None); + assert_eq!(members(), vec![10, 20, 30, 40, 50]); + assert_eq!(candidates(), vec![60]); + assert_eq!(Balances::free_balance(Society::account_id()), 10005); + + // But after two rounds, the clearly rejected 60 gets dropped and slashed. + next_intake(); + conclude_intake(false, None); + assert_eq!(members(), vec![10, 20, 30, 40, 50]); assert_eq!(candidates(), vec![]); - assert_eq!(>::get(20), None); + assert_eq!(Balances::free_balance(60), 25); + assert_eq!(Balances::reserved_balance(60), 0); + assert_eq!(Balances::free_balance(Society::account_id()), 10030); }); } +/* #[test] fn vouch_works() { EnvBuilder::new().execute(|| { // 10 is the only member assert_eq!(members(), vec![10]); // A non-member cannot vouch - assert_noop!(Society::vouch(Origin::signed(1), 20, 1000, 100), Error::::NotMember); + assert_noop!(Society::vouch(Origin::signed(1), 20, 1000, 100), Error::::NotMember); // A member can though assert_ok!(Society::vouch(Origin::signed(10), 20, 1000, 100)); assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); // A member cannot vouch twice at the same time assert_noop!( Society::vouch(Origin::signed(10), 30, 100, 0), - Error::::AlreadyVouching + Error::::AlreadyVouching ); // Vouching creates the right kind of bid assert_eq!(>::get(), vec![(20, candidacy(1, 1000, BidKind::Vouch(10,, 0, 0) 100))]); // Vouched user can become candidate - run_to_block(4); - assert_eq!(candidates(), vec![(20, candidacy(1, 1000, BidKind::Vouch(10,, 0, 0) 100))]); + next_intake(); + assert_eq!(candidacies(), vec![(20, candidacy(1, 1000, BidKind::Vouch(10,, 0, 0) 100))]); // Vote yes assert_ok!(Society::vote(Origin::signed(10), 20, true)); // Vouched user can win - run_to_block(8); + next_intake(); assert_eq!(members(), vec![10, 20]); // Voucher wins a portion of the payment - assert_eq!(>::get(10), vec![(9, 100)]); + assert_eq!(Payouts::::get(10), PayoutRecord { paid: 0, payouts: vec![(9, 100)].try_into().unwrap() }); // Vouched user wins the rest - assert_eq!(>::get(20), vec![(9, 900)]); + assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(9, 900)].try_into().unwrap() }); // 10 is no longer vouching assert_eq!(>::get(10), None); }); @@ -481,17 +552,17 @@ fn voucher_cannot_win_more_than_bid() { // Vouching creates the right kind of bid assert_eq!(>::get(), vec![(20, candidacy(1, 100, BidKind::Vouch(10,, 0, 0) 1000))]); // Vouched user can become candidate - run_to_block(4); - assert_eq!(candidates(), vec![(20, candidacy(1, 100, BidKind::Vouch(10,, 0, 0) 1000))]); + next_intake(); + assert_eq!(candidacies(), vec![(20, candidacy(1, 100, BidKind::Vouch(10,, 0, 0) 1000))]); // Vote yes assert_ok!(Society::vote(Origin::signed(10), 20, true)); // Vouched user can win - run_to_block(8); + next_intake(); assert_eq!(members(), vec![10, 20]); // Voucher wins as much as the bid - assert_eq!(>::get(10), vec![(9, 100)]); + assert_eq!(Payouts::::get(10), PayoutRecord { paid: 0, payouts: vec![(9, 100)].try_into().unwrap() }); // Vouched user gets nothing - assert_eq!(>::get(20), vec![]); + assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![].try_into().unwrap() }); }); } @@ -507,7 +578,7 @@ fn unvouch_works() { // 10 is vouched assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); // To unvouch, you must know the right bid position - assert_noop!(Society::unvouch(Origin::signed(10), 2), Error::::BadPosition); + assert_noop!(Society::unvouch(Origin::signed(10), 2), Error::::BadPosition); // 10 can unvouch with the right position assert_ok!(Society::unvouch(Origin::signed(10), 0)); // 20 no longer has a bid @@ -517,12 +588,12 @@ fn unvouch_works() { // Cannot unvouch after they become candidate assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 0)); - run_to_block(4); - assert_eq!(candidates(), vec![(20, candidacy(1, 100, BidKind::Vouch(10,, 0, 0) 0))]); - assert_noop!(Society::unvouch(Origin::signed(10), 0), Error::::BadPosition); + next_intake(); + assert_eq!(candidacies(), vec![(20, candidacy(1, 100, BidKind::Vouch(10,, 0, 0) 0))]); + assert_noop!(Society::unvouch(Origin::signed(10), 0), Error::::BadPosition); // 10 is still vouching until candidate is approved or rejected assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); - run_to_block(8); + next_intake(); // In this case candidate is denied and suspended assert!(Society::suspended_candidate(&20).is_some()); assert_eq!(members(), vec![10]); @@ -537,10 +608,10 @@ fn unvouch_works() { // 10 cannot vouch again assert_noop!( Society::vouch(Origin::signed(10), 30, 100, 0), - Error::::AlreadyVouching + Error::::AlreadyVouching ); // 10 cannot unvouch either, so they are banned forever. - assert_noop!(Society::unvouch(Origin::signed(10), 0), Error::::NotVouching); + assert_noop!(Society::unvouch(Origin::signed(10), 0), Error::::NotVouching); }); } @@ -572,15 +643,15 @@ fn founder_and_head_cannot_be_removed() { assert_eq!(Head::::get(), Some(10)); // 10 can still accumulate strikes assert_ok!(Society::bid(Origin::signed(20), 0)); - run_to_block(8); - assert_eq!(Strikes::::get(10), 1); + next_intake(); + assert_eq!(Members::::get(10).unwrap().strikes, 1); assert_ok!(Society::bid(Origin::signed(30), 0)); run_to_block(16); - assert_eq!(Strikes::::get(10), 2); + assert_eq!(Members::::get(10).unwrap().strikes, 2); // Awkwardly they can obtain more than MAX_STRIKES... assert_ok!(Society::bid(Origin::signed(40), 0)); run_to_block(24); - assert_eq!(Strikes::::get(10), 3); + assert_eq!(Members::::get(10).unwrap().strikes, 3); // Replace the head assert_ok!(Society::bid(Origin::signed(50), 0)); @@ -596,10 +667,10 @@ fn founder_and_head_cannot_be_removed() { // 50 can still accumulate strikes assert_ok!(Society::bid(Origin::signed(60), 0)); run_to_block(40); - assert_eq!(Strikes::::get(50), 1); + assert_eq!(Members::::get(50).unwrap().strikes, 1); assert_ok!(Society::bid(Origin::signed(70), 0)); run_to_block(48); - assert_eq!(Strikes::::get(50), 2); + assert_eq!(Members::::get(50).unwrap().strikes, 2); // Replace the head assert_ok!(Society::bid(Origin::signed(80), 0)); @@ -618,8 +689,8 @@ fn founder_and_head_cannot_be_removed() { // The candidate is rejected, so voting approve will give a strike assert_ok!(Society::vote(Origin::signed(50), 90, true)); run_to_block(64); - assert_eq!(Strikes::::get(50), 0); - assert_eq!(>::get(50), true); + assert_eq!(Members::::get(50).unwrap().strikes, 0); + assert_eq!(SuspendedMembers::::contains_key(50), true); assert_eq!(members(), vec![10, 80]); }); } @@ -640,7 +711,7 @@ fn challenges_work() { assert_eq!(members(), vec![10, 20, 30, 40]); assert_eq!(Society::defender(), None); // 20 will be challenged during the challenge rotation - run_to_block(8); + next_intake(); assert_eq!(Society::defender(), Some(30)); // They can always free vote for themselves assert_ok!(Society::defender_vote(Origin::signed(30), true)); @@ -650,7 +721,7 @@ fn challenges_work() { // New challenge period assert_eq!(Society::defender(), Some(30)); // Non-member cannot challenge - assert_noop!(Society::defender_vote(Origin::signed(1), true), Error::::NotMember); + assert_noop!(Society::defender_vote(Origin::signed(1), true), Error::::NotMember); // 3 people say accept, 1 reject assert_ok!(Society::defender_vote(Origin::signed(10), true)); assert_ok!(Society::defender_vote(Origin::signed(20), true)); @@ -699,28 +770,28 @@ fn bad_vote_slash_works() { Society::bump_payout(&40, 5, 100); // Check starting point assert_eq!(members(), vec![10, 20, 30, 40]); - assert_eq!(>::get(10), vec![(5, 100)]); - assert_eq!(>::get(20), vec![(5, 100)]); - assert_eq!(>::get(30), vec![(5, 100)]); - assert_eq!(>::get(40), vec![(5, 100)]); + assert_eq!(Payouts::::get(10), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); + assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); + assert_eq!(Payouts::::get(30), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); + assert_eq!(Payouts::::get(40), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); // Create a new bid assert_ok!(Society::bid(Origin::signed(50), 1000)); - run_to_block(4); + next_intake(); assert_ok!(Society::vote(Origin::signed(10), 50, false)); assert_ok!(Society::vote(Origin::signed(20), 50, true)); assert_ok!(Society::vote(Origin::signed(30), 50, false)); assert_ok!(Society::vote(Origin::signed(40), 50, false)); - run_to_block(8); + next_intake(); // Wrong voter gained a strike assert_eq!(>::get(10), 0); assert_eq!(>::get(20), 1); assert_eq!(>::get(30), 0); assert_eq!(>::get(40), 0); // Their payout is slashed, a random person is rewarded - assert_eq!(>::get(10), vec![(5, 100), (9, 2)]); - assert_eq!(>::get(20), vec![(5, 98)]); - assert_eq!(>::get(30), vec![(5, 100)]); - assert_eq!(>::get(40), vec![(5, 100)]); + assert_eq!(Payouts::::get(10), PayoutRecord { paid: 0, payouts: vec![(5, 100), (9, 2)].try_into().unwrap() }); + assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(5, 98)].try_into().unwrap() }); + assert_eq!(Payouts::::get(30), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); + assert_eq!(Payouts::::get(40), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); }); } @@ -729,15 +800,15 @@ fn user_cannot_bid_twice() { EnvBuilder::new().execute(|| { // Cannot bid twice assert_ok!(Society::bid(Origin::signed(20), 100)); - assert_noop!(Society::bid(Origin::signed(20), 100), Error::::AlreadyBid); + assert_noop!(Society::bid(Origin::signed(20), 100), Error::::AlreadyBid); // Cannot bid when vouched assert_ok!(Society::vouch(Origin::signed(10), 30, 100, 100)); - assert_noop!(Society::bid(Origin::signed(30), 100), Error::::AlreadyBid); + assert_noop!(Society::bid(Origin::signed(30), 100), Error::::AlreadyBid); // Cannot vouch when already bid assert_ok!(Society::add_member(&50)); assert_noop!( Society::vouch(Origin::signed(50), 20, 100, 100), - Error::::AlreadyBid + Error::::AlreadyBid ); }); } @@ -754,7 +825,7 @@ fn vouching_handles_removed_member_with_bid() { assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); // Suspend that member Society::suspend_member(&20); - assert_eq!(>::get(20), true); + assert_eq!(SuspendedMembers::::contains_key(20), true); // Nothing changes yet assert_eq!(>::get(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20,, 0, 0) 100))]); assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); @@ -777,26 +848,26 @@ fn vouching_handles_removed_member_with_candidate() { assert_eq!(>::get(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20,, 0, 0) 100))]); assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); // Make that bid a candidate - run_to_block(4); - assert_eq!(candidates(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20,, 0, 0) 100))]); + next_intake(); + assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20,, 0, 0) 100))]); // Suspend that member Society::suspend_member(&20); - assert_eq!(>::get(20), true); + assert_eq!(SuspendedMembers::::contains_key(20), true); // Nothing changes yet - assert_eq!(candidates(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20,, 0, 0) 100))]); + assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20,, 0, 0) 100))]); assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); // Remove member assert_ok!(Society::judge_suspended_member(Origin::signed(2), 20, false)); // Vouching status is removed, but candidate is still in the queue assert_eq!(>::get(20), None); - assert_eq!(candidates(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20,, 0, 0) 100))]); + assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20,, 0, 0) 100))]); // Candidate wins assert_ok!(Society::vote(Origin::signed(10), 30, true)); - run_to_block(8); + next_intake(); assert_eq!(members(), vec![10, 30]); // Payout does not go to removed member - assert_eq!(>::get(20), vec![]); - assert_eq!(>::get(30), vec![(9, 1000)]); + assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![].try_into().unwrap() }); + assert_eq!(Payouts::::get(30), PayoutRecord { paid: 0, payouts: vec![(9, 1000)].try_into().unwrap() }); }); } @@ -808,17 +879,17 @@ fn votes_are_working() { assert_ok!(Society::bid(Origin::signed(40), 400)); assert_ok!(Society::bid(Origin::signed(30), 300)); // Rotate period - run_to_block(4); + next_intake(); // A member votes for these candidates to join the society assert_ok!(Society::vote(Origin::signed(10), 30, true)); assert_ok!(Society::vote(Origin::signed(10), 40, true)); // You cannot vote for a non-candidate - assert_noop!(Society::vote(Origin::signed(10), 50, true), Error::::NotCandidate); + assert_noop!(Society::vote(Origin::signed(10), 50, true), Error::::NotCandidate); // Votes are stored assert_eq!(>::get(30, 10), Some(Vote::Approve)); assert_eq!(>::get(40, 10), Some(Vote::Approve)); assert_eq!(>::get(50, 10), None); - run_to_block(8); + next_intake(); // Candidates become members after a period rotation assert_eq!(members(), vec![10, 30, 40]); // Votes are cleaned up @@ -845,7 +916,7 @@ fn max_limits_work() { // Last bid is smallest number + 99 (1099) assert_eq!(bids[999], (1099, candidacy(1, 1099, BidKind::Deposit(25), 0, 0))); // Rotate period - run_to_block(4); + next_intake(); // Max of 10 candidates assert_eq!(candidates().len(), 10); // Fill up membership, max 100, we will do just 95 @@ -855,7 +926,7 @@ fn max_limits_work() { // Remember there was 1 original member, so 96 total assert_eq!(members().len(), 96); // Rotate period - run_to_block(8); + next_intake(); // Only of 4 candidates possible now assert_eq!(candidates().len(), 4); // Fill up members with suspended candidates from the first rotation @@ -868,7 +939,7 @@ fn max_limits_work() { } assert_eq!(members().len(), 100); // Can't add any more members - assert_noop!(Society::add_member(&98), Error::::MaxMembers); + assert_noop!(Society::add_member(&98), Error::::MaxMembers); // However, a fringe scenario allows for in-progress candidates to increase the membership // pool, but it has no real after-effects. for i in members().iter() { @@ -905,7 +976,7 @@ fn zero_bid_works() { assert_ok!(Society::bid(Origin::signed(40), 0)); // Rotate period - run_to_block(4); + next_intake(); // Pot is 1000 after "PeriodSpend" assert_eq!(Pot::::get(), 1000); assert_eq!(Balances::free_balance(Society::account_id()), 10_000); @@ -926,7 +997,7 @@ fn zero_bid_works() { assert_ok!(Society::vote(Origin::signed(10), 30, true)); assert_ok!(Society::vote(Origin::signed(10), 50, true)); assert_ok!(Society::vote(Origin::signed(10), 60, true)); - run_to_block(8); + next_intake(); // Candidates become members after a period rotation assert_eq!(members(), vec![10, 30, 50, 60]); // The zero bid is selected as head From 8b997cd9c922639d432ea3630d6f8b624eb48709 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 4 May 2022 16:12:59 +0100 Subject: [PATCH 08/43] More tests --- frame/society/src/lib.rs | 4 +- frame/society/src/mock.rs | 8 +- frame/society/src/tests.rs | 153 +++++++++++++++++++++---------------- 3 files changed, 94 insertions(+), 71 deletions(-) diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 6b99e31c8bb1b..c8fde907e8468 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -565,7 +565,7 @@ pub mod pallet { /// Member is already vouching or banned from vouching again. AlreadyVouching, /// Member is not vouching. - NotVouching, + NotVouchingOnBidder, /// Cannot remove the head of the chain. Head, /// Cannot remove the founder. @@ -976,7 +976,7 @@ pub mod pallet { let mut bids = Bids::::get(); let pos = bids.iter().position(|bid| bid.kind.is_vouch(&voucher)) - .ok_or(Error::::NotVouching)?; + .ok_or(Error::::NotVouchingOnBidder)?; let bid = bids.remove(pos); Self::clean_bid(&bid); diff --git a/frame/society/src/mock.rs b/frame/society/src/mock.rs index 19f08296baf8a..4243302efdf62 100644 --- a/frame/society/src/mock.rs +++ b/frame/society/src/mock.rs @@ -177,16 +177,16 @@ pub fn run_to_block(n: u64) { Society::on_initialize(System::block_number()); } } -/* + /// Creates a bid struct using input parameters. -pub fn create_bid( - value: Balance, +pub fn bid( who: AccountId, kind: BidKind, + value: Balance, ) -> Bid { Bid { who, kind, value } } -*/ + /// Creates a candidate struct using input parameters. pub fn candidacy( round: RoundIndex, diff --git a/frame/society/src/tests.rs b/frame/society/src/tests.rs index c3c6984851ec5..c5428f9d484a1 100644 --- a/frame/society/src/tests.rs +++ b/frame/society/src/tests.rs @@ -339,7 +339,7 @@ fn basic_new_member_reject_works() { assert_eq!(members(), vec![10]); // User is rejected. assert_eq!(candidacies(), vec![]); - assert_eq!(Bids::::get(), vec![]); + assert_eq!(Bids::::get().into_inner(), vec![]); }); } @@ -507,9 +507,8 @@ fn suspended_candidate_rejected_works() { }); } -/* #[test] -fn vouch_works() { +fn unpaid_vouch_works() { EnvBuilder::new().execute(|| { // 10 is the only member assert_eq!(members(), vec![10]); @@ -517,52 +516,74 @@ fn vouch_works() { assert_noop!(Society::vouch(Origin::signed(1), 20, 1000, 100), Error::::NotMember); // A member can though assert_ok!(Society::vouch(Origin::signed(10), 20, 1000, 100)); - assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); + assert_eq!(Members::::get(10).unwrap().vouching, Some(VouchingStatus::Vouching)); // A member cannot vouch twice at the same time assert_noop!( Society::vouch(Origin::signed(10), 30, 100, 0), Error::::AlreadyVouching ); // Vouching creates the right kind of bid - assert_eq!(>::get(), vec![(20, candidacy(1, 1000, BidKind::Vouch(10,, 0, 0) 100))]); + assert_eq!(Bids::::get().into_inner(), vec![bid(20, BidKind::Vouch(10, 100), 1000)]); // Vouched user can become candidate next_intake(); - assert_eq!(candidacies(), vec![(20, candidacy(1, 1000, BidKind::Vouch(10,, 0, 0) 100))]); + assert_eq!(candidacies(), vec![(20, candidacy(1, 1000, BidKind::Vouch(10, 100), 0, 0))]); // Vote yes assert_ok!(Society::vote(Origin::signed(10), 20, true)); // Vouched user can win - next_intake(); + conclude_intake(true, None); + assert_eq!(members(), vec![10, 20]); + // Vouched user gets whatever remains after the voucher's reservation. + assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(8, 900)].try_into().unwrap() }); + // 10 is no longer vouching + assert_eq!(Members::::get(10).unwrap().vouching, None); + }); +} + +#[test] +fn paid_vouch_works() { + EnvBuilder::new().execute(|| { + place_members([20]); assert_eq!(members(), vec![10, 20]); + + assert_ok!(Society::vouch(Origin::signed(20), 30, 1000, 100)); + assert_eq!(Members::::get(20).unwrap().vouching, Some(VouchingStatus::Vouching)); + assert_eq!(Bids::::get().into_inner(), vec![bid(30, BidKind::Vouch(20, 100), 1000)]); + + next_intake(); + assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20, 100), 0, 0))]); + assert_ok!(Society::vote(Origin::signed(20), 30, true)); + conclude_intake(true, None); + + assert_eq!(members(), vec![10, 20, 30]); // Voucher wins a portion of the payment - assert_eq!(Payouts::::get(10), PayoutRecord { paid: 0, payouts: vec![(9, 100)].try_into().unwrap() }); + assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(8, 100)].try_into().unwrap() }); // Vouched user wins the rest - assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(9, 900)].try_into().unwrap() }); - // 10 is no longer vouching - assert_eq!(>::get(10), None); + assert_eq!(Payouts::::get(30), PayoutRecord { paid: 0, payouts: vec![(8, 900)].try_into().unwrap() }); + // 20 is no longer vouching + assert_eq!(Members::::get(20).unwrap().vouching, None); }); } #[test] fn voucher_cannot_win_more_than_bid() { EnvBuilder::new().execute(|| { - // 10 is the only member - assert_eq!(members(), vec![10]); - // 10 vouches, but asks for more than the bid - assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 1000)); + place_members([20]); + // 20 vouches, but asks for more than the bid + assert_ok!(Society::vouch(Origin::signed(20), 30, 100, 1000)); // Vouching creates the right kind of bid - assert_eq!(>::get(), vec![(20, candidacy(1, 100, BidKind::Vouch(10,, 0, 0) 1000))]); + assert_eq!(Bids::::get().into_inner(), vec![bid(30, BidKind::Vouch(20, 1000), 100)]); // Vouched user can become candidate next_intake(); - assert_eq!(candidacies(), vec![(20, candidacy(1, 100, BidKind::Vouch(10,, 0, 0) 1000))]); + assert_eq!(candidacies(), vec![(30, candidacy(1, 100, BidKind::Vouch(20, 1000), 0, 0))]); // Vote yes - assert_ok!(Society::vote(Origin::signed(10), 20, true)); + assert_ok!(Society::vote(Origin::signed(20), 30, true)); // Vouched user can win - next_intake(); - assert_eq!(members(), vec![10, 20]); + conclude_intake(true, None); + assert_eq!(members(), vec![10, 20, 30]); // Voucher wins as much as the bid - assert_eq!(Payouts::::get(10), PayoutRecord { paid: 0, payouts: vec![(9, 100)].try_into().unwrap() }); + assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(8, 100)].try_into().unwrap() }); // Vouched user gets nothing - assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![].try_into().unwrap() }); + assert_eq!(Payouts::::get(30), PayoutRecord { paid: 0, payouts: vec![].try_into().unwrap() }); }); } @@ -574,35 +595,36 @@ fn unvouch_works() { // 10 vouches for 20 assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 0)); // 20 has a bid - assert_eq!(>::get(), vec![(20, candidacy(1, 100, BidKind::Vouch(10,, 0, 0) 0))]); + assert_eq!(Bids::::get().into_inner(), vec![bid(20, BidKind::Vouch(10, 0), 100)]); // 10 is vouched - assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); - // To unvouch, you must know the right bid position - assert_noop!(Society::unvouch(Origin::signed(10), 2), Error::::BadPosition); - // 10 can unvouch with the right position - assert_ok!(Society::unvouch(Origin::signed(10), 0)); + assert_eq!(Members::::get(10).unwrap().vouching, Some(VouchingStatus::Vouching)); + // 10 can unvouch + assert_ok!(Society::unvouch(Origin::signed(10))); // 20 no longer has a bid - assert_eq!(>::get(), vec![]); + assert_eq!(Bids::::get().into_inner(), vec![]); // 10 is no longer vouching - assert_eq!(>::get(10), None); + assert_eq!(Members::::get(10).unwrap().vouching, None); // Cannot unvouch after they become candidate assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 0)); next_intake(); - assert_eq!(candidacies(), vec![(20, candidacy(1, 100, BidKind::Vouch(10,, 0, 0) 0))]); - assert_noop!(Society::unvouch(Origin::signed(10), 0), Error::::BadPosition); + assert_eq!(candidacies(), vec![(20, candidacy(1, 100, BidKind::Vouch(10, 0), 0, 0))]); + assert_noop!(Society::unvouch(Origin::signed(10)), Error::::NotVouchingOnBidder); + // 10 is still vouching until candidate is approved or rejected - assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); - next_intake(); - // In this case candidate is denied and suspended - assert!(Society::suspended_candidate(&20).is_some()); - assert_eq!(members(), vec![10]); - // User is stuck vouching until judgement origin resolves suspended candidate - assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); - // Judge denies candidate - assert_ok!(Society::judge_suspended_candidate(Origin::signed(2), 20, Judgement::Reject)); - // 10 is banned from vouching - assert_eq!(>::get(10), Some(VouchingStatus::Banned)); + assert_eq!(Members::::get(10).unwrap().vouching, Some(VouchingStatus::Vouching)); + // Voucher inexplicably votes against their pick. + assert_ok!(Society::vote(Origin::signed(10), 20, false)); + // But their pick doesn't resign (yet). + conclude_intake(false, None); + // Voting still happening and voucher cannot unvouch. + assert_eq!(candidacies(), vec![(20, candidacy(1, 100, BidKind::Vouch(10, 0), 0, 1))]); + assert_eq!(Members::::get(10).unwrap().vouching, Some(VouchingStatus::Vouching)); + + // Candidate gives in and resigns. + conclude_intake(true, None); + // Vouxher (10) is banned from vouching. + assert_eq!(Members::::get(10).unwrap().vouching, Some(VouchingStatus::Banned)); assert_eq!(members(), vec![10]); // 10 cannot vouch again @@ -611,10 +633,11 @@ fn unvouch_works() { Error::::AlreadyVouching ); // 10 cannot unvouch either, so they are banned forever. - assert_noop!(Society::unvouch(Origin::signed(10), 0), Error::::NotVouching); + assert_noop!(Society::unvouch(Origin::signed(10)), Error::::NotVouchingOnBidder); }); } +/* #[test] fn unbid_vouch_works() { EnvBuilder::new().execute(|| { @@ -623,14 +646,14 @@ fn unbid_vouch_works() { // 10 vouches for 20 assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 0)); // 20 has a bid - assert_eq!(>::get(), vec![(20, candidacy(1, 100, BidKind::Vouch(10,, 0, 0) 0))]); + assert_eq!(Bids::::get().into_inner(), vec![bid(20, BidKind::Vouch(10, 0), 100)]); // 10 is vouched - assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); + assert_eq!(Members::::get(10).unwrap().vouching, Some(VouchingStatus::Vouching)); // 20 doesn't want to be a member and can unbid themselves. assert_ok!(Society::unbid(Origin::signed(20))); // Everything is cleaned up - assert_eq!(>::get(10), None); - assert_eq!(>::get(), vec![]); + assert_eq!(Members::::get(10).unwrap().vouching, None); + assert_eq!(Bids::::get().into_inner(), vec![]); }); } @@ -821,19 +844,19 @@ fn vouching_handles_removed_member_with_bid() { // Have that member vouch for a user assert_ok!(Society::vouch(Origin::signed(20), 30, 1000, 100)); // That user is now a bid and the member is vouching - assert_eq!(>::get(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20,, 0, 0) 100))]); - assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); + assert_eq!(Bids::::get().into_inner(), vec![bid(30, BidKind::Vouch(20, 100), 1000)]); + assert_eq!(Members::::get(20).unwrap().vouching, Some(VouchingStatus::Vouching)); // Suspend that member Society::suspend_member(&20); assert_eq!(SuspendedMembers::::contains_key(20), true); // Nothing changes yet - assert_eq!(>::get(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20,, 0, 0) 100))]); - assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); + assert_eq!(Bids::::get().into_inner(), vec![bid(30, BidKind::Vouch(20, 100), 1000)]); + assert_eq!(Members::::get(20).unwrap().vouching, Some(VouchingStatus::Vouching)); // Remove member assert_ok!(Society::judge_suspended_member(Origin::signed(2), 20, false)); // Bid is removed, vouching status is removed - assert_eq!(>::get(), vec![]); - assert_eq!(>::get(20), None); + assert_eq!(Bids::::get().into_inner(), vec![]); + assert_eq!(Members::::get(20).unwrap().vouching, None); }); } @@ -845,29 +868,29 @@ fn vouching_handles_removed_member_with_candidate() { // Have that member vouch for a user assert_ok!(Society::vouch(Origin::signed(20), 30, 1000, 100)); // That user is now a bid and the member is vouching - assert_eq!(>::get(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20,, 0, 0) 100))]); - assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); + assert_eq!(Bids::::get().into_inner(), vec![bid(30, BidKind::Vouch(20, 100), 1000)]); + assert_eq!(Members::::get(20).unwrap().vouching, Some(VouchingStatus::Vouching)); // Make that bid a candidate next_intake(); - assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20,, 0, 0) 100))]); + assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20, 100), 0, 0))]); // Suspend that member Society::suspend_member(&20); assert_eq!(SuspendedMembers::::contains_key(20), true); // Nothing changes yet - assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20,, 0, 0) 100))]); - assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); + assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20, 100), 0, 0))]); + assert_eq!(Members::::get(20).unwrap().vouching, Some(VouchingStatus::Vouching)); // Remove member assert_ok!(Society::judge_suspended_member(Origin::signed(2), 20, false)); // Vouching status is removed, but candidate is still in the queue - assert_eq!(>::get(20), None); - assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20,, 0, 0) 100))]); + assert_eq!(Members::::get(20).unwrap().vouching, None); + assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20, 100), 0, 0))]); // Candidate wins assert_ok!(Society::vote(Origin::signed(10), 30, true)); next_intake(); assert_eq!(members(), vec![10, 30]); // Payout does not go to removed member assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![].try_into().unwrap() }); - assert_eq!(Payouts::::get(30), PayoutRecord { paid: 0, payouts: vec![(9, 1000)].try_into().unwrap() }); + assert_eq!(Payouts::::get(30), PayoutRecord { paid: 0, payouts: vec![(8, 1000)].try_into().unwrap() }); }); } @@ -908,7 +931,7 @@ fn max_limits_work() { let _ = Balances::make_free_balance_be(&(i as u128), 1000); assert_ok!(Society::bid(Origin::signed(i as u128), i)); } - let bids = >::get(); + let bids = Bids::::get(); // Length is 1000 assert_eq!(bids.len(), 1000); // First bid is smallest number (100) @@ -990,7 +1013,7 @@ fn zero_bid_works() { ] ); assert_eq!( - >::get(), + Bids::::get(), vec![(20, candidacy(1, 0, BidKind::Deposit(25), 0, 0)), (40, candidacy(1, 0, BidKind::Deposit(25), 0, 0)),] ); // A member votes for these candidates to join the society @@ -1026,7 +1049,7 @@ fn bids_ordered_correctly() { } } - assert_eq!(>::get(), final_list); + assert_eq!(Bids::::get(), final_list); }); } */ \ No newline at end of file From 3f9fdbd120e829ed4e5cd555dac42ad2c6ce2019 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 4 May 2022 16:37:18 +0100 Subject: [PATCH 09/43] Test number 20 --- frame/society/src/lib.rs | 19 +++++++------- frame/society/src/tests.rs | 51 ++++++++++++++++++++++++-------------- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index c8fde907e8468..0b045e9d7239c 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -1424,8 +1424,9 @@ impl, I: 'static> Pallet { // End current defender rotation if let Some((defender, skeptic, tally)) = Defending::::get() { if tally.more_rejections() { - // Member has failed the challenge - Self::suspend_member(&defender); + // Member has failed the challenge: Suspend them. This will fail if they are Head + // or Founder, in which case we ignore. + let _ = Self::suspend_member(&defender); } // Check defender skeptic voted and that their vote was with the majority. @@ -1681,7 +1682,7 @@ impl, I: 'static> Pallet { let params = Parameters::::get().ok_or(Error::::NotGroup)?; if record.strikes >= params.max_strikes { // Way too many strikes: suspend. - Self::suspend_member(who); + let _ = Self::suspend_member(who); } Ok(()) } @@ -1740,13 +1741,11 @@ impl, I: 'static> Pallet { /// If the member was vouching, then this will be reset. Any bidders that the member was /// vouching for will be cancelled unless they are already selected as candidates (in which case /// they will be able to stand). - fn suspend_member(who: &T::AccountId) { - if let Ok(record) = Self::remove_member(&who) { - SuspendedMembers::::insert(who, record); - Self::deposit_event(Event::::MemberSuspended { member: who.clone() }); - } else { - debug_assert!(false, "Unable to remove member (is it the Head or Founder?)") - } + fn suspend_member(who: &T::AccountId) -> DispatchResult { + let record = Self::remove_member(&who)?; + SuspendedMembers::::insert(who, record); + Self::deposit_event(Event::::MemberSuspended { member: who.clone() }); + Ok(()) } /// Select a member at random, given the RNG `rng`. diff --git a/frame/society/src/tests.rs b/frame/society/src/tests.rs index c5428f9d484a1..494321d51fe06 100644 --- a/frame/society/src/tests.rs +++ b/frame/society/src/tests.rs @@ -420,7 +420,7 @@ fn suspended_member_life_cycle_works() { assert_eq!(members(), vec![10, 20]); // Let's suspend them again, directly - Society::suspend_member(&20); + assert_ok!(Society::suspend_member(&20)); assert!(SuspendedMembers::::contains_key(20)); // Suspension judgement origin does not forgive the suspended member assert_ok!(Society::judge_suspended_member(Origin::signed(10), 20, false)); @@ -637,7 +637,6 @@ fn unvouch_works() { }); } -/* #[test] fn unbid_vouch_works() { EnvBuilder::new().execute(|| { @@ -667,57 +666,73 @@ fn founder_and_head_cannot_be_removed() { // 10 can still accumulate strikes assert_ok!(Society::bid(Origin::signed(20), 0)); next_intake(); + conclude_intake(false, None); assert_eq!(Members::::get(10).unwrap().strikes, 1); assert_ok!(Society::bid(Origin::signed(30), 0)); - run_to_block(16); + next_intake(); + conclude_intake(false, None); assert_eq!(Members::::get(10).unwrap().strikes, 2); // Awkwardly they can obtain more than MAX_STRIKES... assert_ok!(Society::bid(Origin::signed(40), 0)); - run_to_block(24); + next_intake(); + conclude_intake(false, None); assert_eq!(Members::::get(10).unwrap().strikes, 3); // Replace the head assert_ok!(Society::bid(Origin::signed(50), 0)); - run_to_block(28); + next_intake(); assert_ok!(Society::vote(Origin::signed(10), 50, true)); - assert_ok!(Society::defender_vote(Origin::signed(10), true)); // Keep defender around - run_to_block(32); + conclude_intake(false, None); assert_eq!(members(), vec![10, 50]); + assert_eq!(Head::::get(), Some(10)); + next_intake(); assert_eq!(Head::::get(), Some(50)); // Founder is unchanged assert_eq!(Founder::::get(), Some(10)); // 50 can still accumulate strikes assert_ok!(Society::bid(Origin::signed(60), 0)); - run_to_block(40); + next_intake(); + // Force 50 to be Skeptic so it gets a strike. + Skeptic::::put(50); + conclude_intake(false, None); assert_eq!(Members::::get(50).unwrap().strikes, 1); assert_ok!(Society::bid(Origin::signed(70), 0)); - run_to_block(48); + next_intake(); + // Force 50 to be Skeptic so it gets a strike. + Skeptic::::put(50); + conclude_intake(false, None); assert_eq!(Members::::get(50).unwrap().strikes, 2); // Replace the head assert_ok!(Society::bid(Origin::signed(80), 0)); - run_to_block(52); + next_intake(); assert_ok!(Society::vote(Origin::signed(10), 80, true)); assert_ok!(Society::vote(Origin::signed(50), 80, true)); - assert_ok!(Society::defender_vote(Origin::signed(10), true)); // Keep defender around - run_to_block(56); + conclude_intake(false, None); + next_intake(); assert_eq!(members(), vec![10, 50, 80]); assert_eq!(Head::::get(), Some(80)); assert_eq!(Founder::::get(), Some(10)); // 50 can now be suspended for strikes assert_ok!(Society::bid(Origin::signed(90), 0)); - run_to_block(60); - // The candidate is rejected, so voting approve will give a strike - assert_ok!(Society::vote(Origin::signed(50), 90, true)); - run_to_block(64); - assert_eq!(Members::::get(50).unwrap().strikes, 0); - assert_eq!(SuspendedMembers::::contains_key(50), true); + next_intake(); + // Force 50 to be Skeptic and get it a strike. + Skeptic::::put(50); + conclude_intake(false, None); + next_intake(); + assert_eq!(SuspendedMembers::::get(50), Some(MemberRecord { + rank: 0, + strikes: 3, + vouching: None, + index: 1, + })); assert_eq!(members(), vec![10, 80]); }); } +/* #[test] fn challenges_work() { EnvBuilder::new().execute(|| { From 98f40601bc52bdf4e6eff047142d8ba7912ad248 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 4 May 2022 17:51:45 +0100 Subject: [PATCH 10/43] Tests --- frame/society/src/lib.rs | 14 ++- frame/society/src/mock.rs | 6 +- frame/society/src/tests.rs | 227 ++++++++++++++++++++++--------------- 3 files changed, 152 insertions(+), 95 deletions(-) diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 0b045e9d7239c..bc7943708ec32 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -1421,9 +1421,12 @@ impl, I: 'static> Pallet { /// End the current challenge period and start a new one. fn rotate_challenge(rng: &mut impl RngCore) { + let mut next_defender = None; + // End current defender rotation if let Some((defender, skeptic, tally)) = Defending::::get() { - if tally.more_rejections() { + // We require strictly more approvals, since the member should be voting for themselves. + if !tally.more_approvals() { // Member has failed the challenge: Suspend them. This will fail if they are Head // or Founder, in which case we ignore. let _ = Self::suspend_member(&defender); @@ -1436,8 +1439,13 @@ impl, I: 'static> Pallet { | (Some(Vote { approve: true, .. }), false, true) | (Some(Vote { approve: false, .. }), true, false) => { - // Punish skeptic. + // Punish skeptic and challenge them next. let _ = Self::strike_member(&skeptic); + let founder = Founder::::get(); + let head = Head::::get(); + if Some(&skeptic) != founder.as_ref() && Some(&skeptic) != head.as_ref() { + next_defender = Some(skeptic); + } } _ => {} } @@ -1451,7 +1459,7 @@ impl, I: 'static> Pallet { // Avoid challenging if there's only two members since we never challenge the Head or // the Founder. if MemberCount::::get() > 2 { - let defender = Self::pick_defendent(rng).expect("exited if members empty; qed"); + let defender = next_defender.or_else(|| Self::pick_defendent(rng)).expect("exited if members empty; qed"); let skeptic = Self::pick_member_except(rng, &defender).expect("exited if members empty; qed"); Self::deposit_event(Event::::Challenged { member: defender.clone() }); Defending::::put((defender, skeptic, Tally::default())); diff --git a/frame/society/src/mock.rs b/frame/society/src/mock.rs index 4243302efdf62..7734f292593a1 100644 --- a/frame/society/src/mock.rs +++ b/frame/society/src/mock.rs @@ -155,7 +155,7 @@ impl EnvBuilder { ext.execute_with(|| { if self.founded { let r = b"be cool".to_vec(); - assert!(Society::found_society(Origin::signed(1), 10, 100, 10, 2, 25, r).is_ok()); + assert!(Society::found_society(Origin::signed(1), 10, 10, 8, 2, 25, r).is_ok()); } f() }) @@ -164,6 +164,10 @@ impl EnvBuilder { self.founded = f; self } + pub fn with_pot(mut self, p: u64) -> Self { + self.pot = p; + self + } } /// Run until a particular block. diff --git a/frame/society/src/tests.rs b/frame/society/src/tests.rs index 494321d51fe06..8f394092c39e2 100644 --- a/frame/society/src/tests.rs +++ b/frame/society/src/tests.rs @@ -24,6 +24,12 @@ use frame_support::{assert_noop, assert_ok}; use sp_core::blake2_256; use sp_runtime::traits::BadOrigin; +fn next_challenge() { + let challenge_period: u64 = ::ChallengePeriod::get(); + let now = System::block_number(); + run_to_block(now + challenge_period - now % challenge_period); +} + fn next_voting() { if let Period::Voting { more, .. } = Society::period() { run_to_block(System::block_number() + more); @@ -732,65 +738,73 @@ fn founder_and_head_cannot_be_removed() { }); } -/* #[test] fn challenges_work() { EnvBuilder::new().execute(|| { // Add some members - assert_ok!(Society::add_member(&20)); - assert_ok!(Society::add_member(&30)); - assert_ok!(Society::add_member(&40)); + place_members([20, 30, 40]); // Votes are empty - assert_eq!(>::get(10), None); - assert_eq!(>::get(20), None); - assert_eq!(>::get(30), None); - assert_eq!(>::get(40), None); + assert_eq!(DefenderVotes::::get(10), None); + assert_eq!(DefenderVotes::::get(20), None); + assert_eq!(DefenderVotes::::get(30), None); + assert_eq!(DefenderVotes::::get(40), None); // Check starting point assert_eq!(members(), vec![10, 20, 30, 40]); - assert_eq!(Society::defender(), None); - // 20 will be challenged during the challenge rotation - next_intake(); - assert_eq!(Society::defender(), Some(30)); + assert_eq!(Defending::::get(), None); + + // 30 will be challenged during the challenge rotation + next_challenge(); + assert_eq!(Defending::::get().unwrap().0, 30); // They can always free vote for themselves assert_ok!(Society::defender_vote(Origin::signed(30), true)); + // If no one else votes, nothing happens - run_to_block(16); + next_challenge(); assert_eq!(members(), vec![10, 20, 30, 40]); // New challenge period - assert_eq!(Society::defender(), Some(30)); - // Non-member cannot challenge + assert_eq!(Defending::::get().unwrap().0, 30); + // Non-member cannot vote assert_noop!(Society::defender_vote(Origin::signed(1), true), Error::::NotMember); // 3 people say accept, 1 reject assert_ok!(Society::defender_vote(Origin::signed(10), true)); assert_ok!(Society::defender_vote(Origin::signed(20), true)); assert_ok!(Society::defender_vote(Origin::signed(30), true)); assert_ok!(Society::defender_vote(Origin::signed(40), false)); - run_to_block(24); - // 20 survives + + next_challenge(); + // 30 survives assert_eq!(members(), vec![10, 20, 30, 40]); // Votes are reset - assert_eq!(>::get(10), None); - assert_eq!(>::get(20), None); - assert_eq!(>::get(30), None); - assert_eq!(>::get(40), None); + assert_eq!(DefenderVotes::::get(10), None); + assert_eq!(DefenderVotes::::get(20), None); + assert_eq!(DefenderVotes::::get(30), None); + assert_eq!(DefenderVotes::::get(40), None); + // One more time - assert_eq!(Society::defender(), Some(30)); + assert_eq!(Defending::::get().unwrap().0, 30); // 2 people say accept, 2 reject assert_ok!(Society::defender_vote(Origin::signed(10), true)); assert_ok!(Society::defender_vote(Origin::signed(20), true)); assert_ok!(Society::defender_vote(Origin::signed(30), false)); assert_ok!(Society::defender_vote(Origin::signed(40), false)); - run_to_block(32); - // 20 is suspended + next_challenge(); + + // 30 is suspended assert_eq!(members(), vec![10, 20, 40]); - assert_eq!(Society::suspended_member(30), true); + assert_eq!(SuspendedMembers::::get(30), Some(MemberRecord { + rank: 0, + strikes: 0, + vouching: None, + index: 2, + })); + // New defender is chosen - assert_eq!(Society::defender(), Some(20)); + assert_eq!(Defending::::get().unwrap().0, 20); // Votes are reset - assert_eq!(>::get(10), None); - assert_eq!(>::get(20), None); - assert_eq!(>::get(30), None); - assert_eq!(>::get(40), None); + assert_eq!(DefenderVotes::::get(10), None); + assert_eq!(DefenderVotes::::get(20), None); + assert_eq!(DefenderVotes::::get(30), None); + assert_eq!(DefenderVotes::::get(40), None); }); } @@ -798,38 +812,38 @@ fn challenges_work() { fn bad_vote_slash_works() { EnvBuilder::new().execute(|| { // Add some members - assert_ok!(Society::add_member(&20)); - assert_ok!(Society::add_member(&30)); - assert_ok!(Society::add_member(&40)); + place_members([20, 30, 40, 50]); + assert_eq!(members(), vec![10, 20, 30, 40, 50]); // Create some payouts - Society::bump_payout(&10, 5, 100); Society::bump_payout(&20, 5, 100); Society::bump_payout(&30, 5, 100); Society::bump_payout(&40, 5, 100); + Society::bump_payout(&50, 5, 100); // Check starting point - assert_eq!(members(), vec![10, 20, 30, 40]); - assert_eq!(Payouts::::get(10), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); assert_eq!(Payouts::::get(30), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); assert_eq!(Payouts::::get(40), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); + assert_eq!(Payouts::::get(50), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); // Create a new bid - assert_ok!(Society::bid(Origin::signed(50), 1000)); - next_intake(); - assert_ok!(Society::vote(Origin::signed(10), 50, false)); - assert_ok!(Society::vote(Origin::signed(20), 50, true)); - assert_ok!(Society::vote(Origin::signed(30), 50, false)); - assert_ok!(Society::vote(Origin::signed(40), 50, false)); + assert_ok!(Society::bid(Origin::signed(60), 1000)); next_intake(); + // Force 20 to be the skeptic, and make it vote against the settled majority. + Skeptic::::put(20); + assert_ok!(Society::vote(Origin::signed(20), 60, true)); + assert_ok!(Society::vote(Origin::signed(30), 60, false)); + assert_ok!(Society::vote(Origin::signed(40), 60, false)); + assert_ok!(Society::vote(Origin::signed(50), 60, false)); + conclude_intake(false, None); // Wrong voter gained a strike - assert_eq!(>::get(10), 0); - assert_eq!(>::get(20), 1); - assert_eq!(>::get(30), 0); - assert_eq!(>::get(40), 0); + assert_eq!(Members::::get(20).unwrap().strikes, 1); + assert_eq!(Members::::get(30).unwrap().strikes, 0); + assert_eq!(Members::::get(40).unwrap().strikes, 0); + assert_eq!(Members::::get(50).unwrap().strikes, 0); // Their payout is slashed, a random person is rewarded - assert_eq!(Payouts::::get(10), PayoutRecord { paid: 0, payouts: vec![(5, 100), (9, 2)].try_into().unwrap() }); - assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(5, 98)].try_into().unwrap() }); + assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(5, 50)].try_into().unwrap() }); assert_eq!(Payouts::::get(30), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); assert_eq!(Payouts::::get(40), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); + assert_eq!(Payouts::::get(50), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); }); } @@ -843,7 +857,7 @@ fn user_cannot_bid_twice() { assert_ok!(Society::vouch(Origin::signed(10), 30, 100, 100)); assert_noop!(Society::bid(Origin::signed(30), 100), Error::::AlreadyBid); // Cannot vouch when already bid - assert_ok!(Society::add_member(&50)); + place_members([50]); assert_noop!( Society::vouch(Origin::signed(50), 20, 100, 100), Error::::AlreadyBid @@ -855,23 +869,19 @@ fn user_cannot_bid_twice() { fn vouching_handles_removed_member_with_bid() { EnvBuilder::new().execute(|| { // Add a member - assert_ok!(Society::add_member(&20)); + place_members([20]); // Have that member vouch for a user assert_ok!(Society::vouch(Origin::signed(20), 30, 1000, 100)); // That user is now a bid and the member is vouching assert_eq!(Bids::::get().into_inner(), vec![bid(30, BidKind::Vouch(20, 100), 1000)]); assert_eq!(Members::::get(20).unwrap().vouching, Some(VouchingStatus::Vouching)); // Suspend that member - Society::suspend_member(&20); - assert_eq!(SuspendedMembers::::contains_key(20), true); - // Nothing changes yet - assert_eq!(Bids::::get().into_inner(), vec![bid(30, BidKind::Vouch(20, 100), 1000)]); - assert_eq!(Members::::get(20).unwrap().vouching, Some(VouchingStatus::Vouching)); - // Remove member - assert_ok!(Society::judge_suspended_member(Origin::signed(2), 20, false)); + assert_ok!(Society::suspend_member(&20)); // Bid is removed, vouching status is removed + let r = MemberRecord { rank: 0, strikes: 0, vouching: None, index: 1 }; + assert_eq!(SuspendedMembers::::get(20), Some(r)); assert_eq!(Bids::::get().into_inner(), vec![]); - assert_eq!(Members::::get(20).unwrap().vouching, None); + assert_eq!(Members::::get(20), None); }); } @@ -879,29 +889,26 @@ fn vouching_handles_removed_member_with_bid() { fn vouching_handles_removed_member_with_candidate() { EnvBuilder::new().execute(|| { // Add a member - assert_ok!(Society::add_member(&20)); + place_members([20]); // Have that member vouch for a user assert_ok!(Society::vouch(Origin::signed(20), 30, 1000, 100)); // That user is now a bid and the member is vouching assert_eq!(Bids::::get().into_inner(), vec![bid(30, BidKind::Vouch(20, 100), 1000)]); assert_eq!(Members::::get(20).unwrap().vouching, Some(VouchingStatus::Vouching)); + // Make that bid a candidate next_intake(); assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20, 100), 0, 0))]); // Suspend that member - Society::suspend_member(&20); + assert_ok!(Society::suspend_member(&20)); assert_eq!(SuspendedMembers::::contains_key(20), true); - // Nothing changes yet - assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20, 100), 0, 0))]); - assert_eq!(Members::::get(20).unwrap().vouching, Some(VouchingStatus::Vouching)); - // Remove member - assert_ok!(Society::judge_suspended_member(Origin::signed(2), 20, false)); - // Vouching status is removed, but candidate is still in the queue - assert_eq!(Members::::get(20).unwrap().vouching, None); + + // Nothing changes yet in the candidacy, though the member now forgets. assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20, 100), 0, 0))]); + // Candidate wins assert_ok!(Society::vote(Origin::signed(10), 30, true)); - next_intake(); + conclude_intake(false, None); assert_eq!(members(), vec![10, 30]); // Payout does not go to removed member assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![].try_into().unwrap() }); @@ -912,6 +919,7 @@ fn vouching_handles_removed_member_with_candidate() { #[test] fn votes_are_working() { EnvBuilder::new().execute(|| { + place_members([20]); // Users make bids of various amounts assert_ok!(Society::bid(Origin::signed(50), 500)); assert_ok!(Society::bid(Origin::signed(40), 400)); @@ -920,53 +928,90 @@ fn votes_are_working() { next_intake(); // A member votes for these candidates to join the society assert_ok!(Society::vote(Origin::signed(10), 30, true)); + assert_ok!(Society::vote(Origin::signed(20), 30, true)); assert_ok!(Society::vote(Origin::signed(10), 40, true)); // You cannot vote for a non-candidate assert_noop!(Society::vote(Origin::signed(10), 50, true), Error::::NotCandidate); // Votes are stored - assert_eq!(>::get(30, 10), Some(Vote::Approve)); - assert_eq!(>::get(40, 10), Some(Vote::Approve)); + assert_eq!(>::get(30, 10), Some(Vote { approve: true, weight: 4 })); + assert_eq!(>::get(30, 20), Some(Vote { approve: true, weight: 1 })); + assert_eq!(>::get(40, 10), Some(Vote { approve: true, weight: 4 })); assert_eq!(>::get(50, 10), None); - next_intake(); + conclude_intake(false, None); // Candidates become members after a period rotation - assert_eq!(members(), vec![10, 30, 40]); + assert_eq!(members(), vec![10, 20, 30, 40]); // Votes are cleaned up assert_eq!(>::get(30, 10), None); + assert_eq!(>::get(30, 20), None); assert_eq!(>::get(40, 10), None); }); } #[test] -fn max_limits_work() { - EnvBuilder::new().with_pot(100000).execute(|| { +fn max_bids_work() { + EnvBuilder::new().execute(|| { // Max bids is 1000, when extra bids come in, it pops the larger ones off the stack. // Try to put 1010 users into the bid pool - for i in (100..1110).rev() { - // Give them some funds - let _ = Balances::make_free_balance_be(&(i as u128), 1000); - assert_ok!(Society::bid(Origin::signed(i as u128), i)); + for i in (0..=10).rev() { + // Give them some funds and bid + let _ = Balances::make_free_balance_be(&((i + 100) as u128), 1000); + assert_ok!(Society::bid(Origin::signed((i + 100) as u128), i)); } let bids = Bids::::get(); // Length is 1000 - assert_eq!(bids.len(), 1000); + assert_eq!(bids.len(), 10); // First bid is smallest number (100) - assert_eq!(bids[0], (100, candidacy(1, 100, BidKind::Deposit(25), 0, 0))); + assert_eq!(bids[0], bid(100, BidKind::Deposit(25), 0)); // Last bid is smallest number + 99 (1099) - assert_eq!(bids[999], (1099, candidacy(1, 1099, BidKind::Deposit(25), 0, 0))); - // Rotate period + assert_eq!(bids[9], bid(109, BidKind::Deposit(25), 9)); + }); +} + +#[test] +fn candidates_are_limited_by_membership_size() { + EnvBuilder::new().execute(|| { + // Fill up some membership + place_members([1, 2, 3, 4, 5, 6, 7, 8]); + // One place left from 10 + assert_eq!(members().len(), 9); + + assert_ok!(Society::bid(Origin::signed(20), 0)); + assert_ok!(Society::bid(Origin::signed(30), 1)); next_intake(); - // Max of 10 candidates - assert_eq!(candidates().len(), 10); - // Fill up membership, max 100, we will do just 95 - for i in 2000..2095 { - assert_ok!(Society::add_member(&(i as u128))); + assert_eq!(candidates().len(), 1); + }); +} + +#[test] +fn candidates_are_limited_by_maximum() { + EnvBuilder::new().execute(|| { + // Nine places left from 10 + assert_eq!(members().len(), 1); + + // Nine bids + for i in (1..=9).rev() { + // Give them some funds and bid + let _ = Balances::make_free_balance_be(&((i + 100) as u128), 1000); + assert_ok!(Society::bid(Origin::signed((i + 100) as u128), i)); } - // Remember there was 1 original member, so 96 total - assert_eq!(members().len(), 96); - // Rotate period next_intake(); + + // Still only 8 candidates. + assert_eq!(candidates().len(), 8); + }); +} + +/* +#[test] +fn candidates_are_limited_by_maximum() { + EnvBuilder::new().execute(|| { + for i in + assert_ok!(Society::bid(Origin::signed(20), 0)); + assert_ok!(Society::bid(Origin::signed(30), 1)); + + // Rotate period + conclude_intake(); // Only of 4 candidates possible now - assert_eq!(candidates().len(), 4); // Fill up members with suspended candidates from the first rotation for i in 100..104 { assert_ok!(Society::judge_suspended_candidate( From 3f6ba7406fa644da722db9331b7ec8a2bf8d8372 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 4 May 2022 19:16:51 +0100 Subject: [PATCH 11/43] 30 tests --- frame/society/src/lib.rs | 12 +++-- frame/society/src/mock.rs | 84 ++++++++++++++++++++++++++++- frame/society/src/tests.rs | 107 +++++++++---------------------------- 3 files changed, 117 insertions(+), 86 deletions(-) diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index bc7943708ec32..4962df7d8f1f2 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -1658,16 +1658,22 @@ impl, I: 'static> Pallet { mut candidacy: Candidacy>, rank: Rank, ) -> DispatchResult { - Self::check_skeptic(&candidate, &mut candidacy); Self::add_new_member(&candidate, rank)?; + Self::check_skeptic(&candidate, &mut candidacy); + let next_head = NextHead::::get() - .filter(|old| old.round > candidacy.round + .filter(|old| { + old.round > candidacy.round || old.round == candidacy.round && old.bid < candidacy.bid - ).unwrap_or_else(|| IntakeRecord { who: candidate.clone(), bid: candidacy.bid, round: candidacy.round }); + }).unwrap_or_else(|| + IntakeRecord { who: candidate.clone(), bid: candidacy.bid, round: candidacy.round } + ); NextHead::::put(next_head); + let now = >::block_number(); let maturity = now + Self::lock_duration(MemberCount::::get()); Self::reward_bidder(&candidate, candidacy.bid, candidacy.kind, maturity); + Votes::::remove_prefix(&candidate, None); Candidates::::remove(&candidate); Ok(()) diff --git a/frame/society/src/mock.rs b/frame/society/src/mock.rs index 7734f292593a1..57af930114bb4 100644 --- a/frame/society/src/mock.rs +++ b/frame/society/src/mock.rs @@ -21,7 +21,7 @@ use super::*; use crate as pallet_society; use frame_support::{ - ord_parameter_types, parameter_types, + assert_noop, assert_ok, ord_parameter_types, parameter_types, traits::{ConstU32, ConstU64}, }; use frame_support_test::TestRandomness; @@ -201,3 +201,85 @@ pub fn candidacy( ) -> Candidacy { Candidacy { round, kind, bid, tally: Tally { approvals, rejections }, skeptic_struck: false } } + +pub fn next_challenge() { + let challenge_period: u64 = ::ChallengePeriod::get(); + let now = System::block_number(); + run_to_block(now + challenge_period - now % challenge_period); +} + +pub fn next_voting() { + if let Period::Voting { more, .. } = Society::period() { + run_to_block(System::block_number() + more); + } +} + +pub fn conclude_intake(allow_resignation: bool, judge_intake: Option) { + next_voting(); + let round = RoundCount::::get(); + for (who, candidacy) in Candidates::::iter() { + if candidacy.tally.clear_approval() { + assert_ok!(Society::claim_membership(Origin::signed(who))); + assert_noop!(Society::claim_membership(Origin::signed(who)), Error::::NotCandidate); + continue + } + if candidacy.tally.clear_rejection() && allow_resignation { + assert_noop!(Society::claim_membership(Origin::signed(who)), Error::::NotApproved); + assert_ok!(Society::resign_candidacy(Origin::signed(who))); + continue + } + if let (Some(founder), Some(approve)) = (Founder::::get(), judge_intake) { + if !candidacy.tally.clear_approval() && !approve { + // can be rejected by founder + assert_ok!(Society::kick_candidate(Origin::signed(founder), who)); + continue + } + if !candidacy.tally.clear_rejection() && approve { + // can be rejected by founder + assert_ok!(Society::bestow_membership(Origin::signed(founder), who)); + continue + } + } + if candidacy.tally.clear_rejection() && round > candidacy.round + 1 { + assert_noop!(Society::claim_membership(Origin::signed(who)), Error::::NotApproved); + assert_ok!(Society::drop_candidate(Origin::signed(0), who)); + assert_noop!(Society::drop_candidate(Origin::signed(0), who), Error::::NotCandidate); + continue + } + if !candidacy.skeptic_struck { + assert_ok!(Society::punish_skeptic(Origin::signed(who))); + } + } +} + +pub fn next_intake() { + let claim_period: u64 = ::ClaimPeriod::get(); + match Society::period() { + Period::Voting { more, .. } => run_to_block(System::block_number() + more + claim_period), + Period::Claim { more, .. } => run_to_block(System::block_number() + more), + } +} + +pub fn place_members(members: impl AsRef<[u128]>) { + for who in members.as_ref() { + assert_ok!(Society::insert_member(who, 0)); + } +} + +pub fn members() -> Vec { + let mut r = Members::::iter_keys().collect::>(); + r.sort(); + r +} + +pub fn candidacies() -> Vec<(u128, Candidacy)> { + let mut r = Candidates::::iter().collect::>(); + r.sort_by_key(|x| x.0); + r +} + +pub fn candidates() -> Vec { + let mut r = Candidates::::iter_keys().collect::>(); + r.sort(); + r +} diff --git a/frame/society/src/tests.rs b/frame/society/src/tests.rs index 8f394092c39e2..528b737203a67 100644 --- a/frame/society/src/tests.rs +++ b/frame/society/src/tests.rs @@ -24,88 +24,6 @@ use frame_support::{assert_noop, assert_ok}; use sp_core::blake2_256; use sp_runtime::traits::BadOrigin; -fn next_challenge() { - let challenge_period: u64 = ::ChallengePeriod::get(); - let now = System::block_number(); - run_to_block(now + challenge_period - now % challenge_period); -} - -fn next_voting() { - if let Period::Voting { more, .. } = Society::period() { - run_to_block(System::block_number() + more); - } -} - -fn conclude_intake(allow_resignation: bool, judge_intake: Option) { - next_voting(); - let round = RoundCount::::get(); - for (who, candidacy) in Candidates::::iter() { - if candidacy.tally.clear_approval() { - assert_ok!(Society::claim_membership(Origin::signed(who))); - assert_noop!(Society::claim_membership(Origin::signed(who)), Error::::NotCandidate); - continue - } - if candidacy.tally.clear_rejection() && allow_resignation { - assert_noop!(Society::claim_membership(Origin::signed(who)), Error::::NotApproved); - assert_ok!(Society::resign_candidacy(Origin::signed(who))); - continue - } - if let (Some(founder), Some(approve)) = (Founder::::get(), judge_intake) { - if !candidacy.tally.clear_approval() && !approve { - // can be rejected by founder - assert_ok!(Society::kick_candidate(Origin::signed(founder), who)); - continue - } - if !candidacy.tally.clear_rejection() && approve { - // can be rejected by founder - assert_ok!(Society::bestow_membership(Origin::signed(founder), who)); - continue - } - } - if candidacy.tally.clear_rejection() && round > candidacy.round + 1 { - assert_noop!(Society::claim_membership(Origin::signed(who)), Error::::NotApproved); - assert_ok!(Society::drop_candidate(Origin::signed(0), who)); - assert_noop!(Society::drop_candidate(Origin::signed(0), who), Error::::NotCandidate); - continue - } - if !candidacy.skeptic_struck { - assert_ok!(Society::punish_skeptic(Origin::signed(who))); - } - } -} - -fn next_intake() { - let claim_period: u64 = ::ClaimPeriod::get(); - match Society::period() { - Period::Voting { more, .. } => run_to_block(System::block_number() + more + claim_period), - Period::Claim { more, .. } => run_to_block(System::block_number() + more), - } -} - -fn place_members(members: impl AsRef<[u128]>) { - for who in members.as_ref() { - assert_ok!(Society::insert_member(who, 0)); - } -} - -fn members() -> Vec { - let mut r = Members::::iter_keys().collect::>(); - r.sort(); - r -} - -fn candidacies() -> Vec<(u128, Candidacy)> { - let mut r = Candidates::::iter().collect::>(); - r.sort_by_key(|x| x.0); - r -} - -fn candidates() -> Vec { - let mut r = Candidates::::iter_keys().collect::>(); - r.sort(); - r -} - #[test] fn founding_works() { EnvBuilder::new().founded(false).execute(|| { @@ -1001,6 +919,31 @@ fn candidates_are_limited_by_maximum() { }); } +#[test] +fn too_many_candidates_cannot_overflow_membership() { + EnvBuilder::new().execute(|| { + // One place left + place_members([1, 2, 3, 4, 5, 6, 7, 8]); + assert_ok!(Society::bid(Origin::signed(20), 0)); + assert_ok!(Society::bid(Origin::signed(30), 1)); + next_intake(); + // Candidate says a candidate. + next_intake(); + // Another candidate taken. + // Both approved. + assert_ok!(Society::vote(Origin::signed(10), 20, true)); + assert_ok!(Society::vote(Origin::signed(10), 30, true)); + next_voting(); + assert_ok!(Society::claim_membership(Origin::signed(20))); + assert_noop!(Society::claim_membership(Origin::signed(30)), Error::::MaxMembers); + + // Maximum members. + assert_eq!(members().len(), 10); + // Still 1 candidate. + assert_eq!(candidates().len(), 1); + }); +} + /* #[test] fn candidates_are_limited_by_maximum() { From 89c4bf66ad281301c0cc4647081519dada63d419 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 5 May 2022 11:36:53 +0100 Subject: [PATCH 12/43] Another test] --- frame/society/src/tests.rs | 58 ++++++++------------------------------ 1 file changed, 11 insertions(+), 47 deletions(-) diff --git a/frame/society/src/tests.rs b/frame/society/src/tests.rs index 528b737203a67..8de73e0ecb4ab 100644 --- a/frame/society/src/tests.rs +++ b/frame/society/src/tests.rs @@ -941,50 +941,10 @@ fn too_many_candidates_cannot_overflow_membership() { assert_eq!(members().len(), 10); // Still 1 candidate. assert_eq!(candidates().len(), 1); - }); -} - -/* -#[test] -fn candidates_are_limited_by_maximum() { - EnvBuilder::new().execute(|| { - for i in - assert_ok!(Society::bid(Origin::signed(20), 0)); - assert_ok!(Society::bid(Origin::signed(30), 1)); - // Rotate period - conclude_intake(); - // Only of 4 candidates possible now - // Fill up members with suspended candidates from the first rotation - for i in 100..104 { - assert_ok!(Society::judge_suspended_candidate( - Origin::signed(2), - i, - Judgement::Approve - )); - } - assert_eq!(members().len(), 100); - // Can't add any more members - assert_noop!(Society::add_member(&98), Error::::MaxMembers); - // However, a fringe scenario allows for in-progress candidates to increase the membership - // pool, but it has no real after-effects. - for i in members().iter() { - assert_ok!(Society::vote(Origin::signed(*i), 110, true)); - assert_ok!(Society::vote(Origin::signed(*i), 111, true)); - assert_ok!(Society::vote(Origin::signed(*i), 112, true)); - } - // Rotate period - run_to_block(12); - // Members length is over 100, no problem... - assert_eq!(members().len(), 103); - // No candidates because full - assert_eq!(candidates().len(), 0); - // Increase member limit - assert_ok!(Society::set_max_members(Origin::root(), 200)); - // Rotate period - run_to_block(16); - // Candidates are back! - assert_eq!(candidates().len(), 10); + // Increase max-members and the candidate can get in. + assert_ok!(Society::set_parameters(Origin::signed(10), 11, 8, 3, 25)); + assert_ok!(Society::claim_membership(Origin::signed(30))); }); } @@ -1008,7 +968,7 @@ fn zero_bid_works() { assert_eq!(Balances::free_balance(Society::account_id()), 10_000); // Choose smallest bidding users whose total is less than pot, with only one zero bid. assert_eq!( - candidates(), + candidacies(), vec![ (30, candidacy(1, 0, BidKind::Deposit(25), 0, 0)), (50, candidacy(1, 300, BidKind::Deposit(25), 0, 0)), @@ -1017,20 +977,24 @@ fn zero_bid_works() { ); assert_eq!( Bids::::get(), - vec![(20, candidacy(1, 0, BidKind::Deposit(25), 0, 0)), (40, candidacy(1, 0, BidKind::Deposit(25), 0, 0)),] + vec![ + bid(20, BidKind::Deposit(25), 0), + bid(40, BidKind::Deposit(25), 0), + ], ); // A member votes for these candidates to join the society assert_ok!(Society::vote(Origin::signed(10), 30, true)); assert_ok!(Society::vote(Origin::signed(10), 50, true)); assert_ok!(Society::vote(Origin::signed(10), 60, true)); - next_intake(); + conclude_intake(false, None); // Candidates become members after a period rotation assert_eq!(members(), vec![10, 30, 50, 60]); + next_intake(); // The zero bid is selected as head assert_eq!(Head::::get(), Some(30)); }); } - +/* #[test] fn bids_ordered_correctly() { // This tests that bids with the same value are placed in the list ordered From db636beafac63df4d19261d08b43cb8e3e79e119 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 5 May 2022 12:15:35 +0100 Subject: [PATCH 13/43] All tests enabled --- frame/society/src/lib.rs | 4 ++-- frame/society/src/mock.rs | 4 ---- frame/society/src/tests.rs | 13 +++++++------ 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 4962df7d8f1f2..b44f020917cb5 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -871,9 +871,9 @@ pub mod pallet { let params = Parameters::::get().ok_or(Error::::NotGroup)?; let deposit = params.candidate_deposit; - Self::insert_bid(&mut bids, &who, value.clone(), BidKind::Deposit(deposit)); - + // NOTE: Reserve must happen before `insert_bid` since that could end up unreserving. T::Currency::reserve(&who, deposit)?; + Self::insert_bid(&mut bids, &who, value.clone(), BidKind::Deposit(deposit)); Bids::::put(bids); Self::deposit_event(Event::::Bid { candidate_id: who, offer: value }); diff --git a/frame/society/src/mock.rs b/frame/society/src/mock.rs index 57af930114bb4..0d756718ea764 100644 --- a/frame/society/src/mock.rs +++ b/frame/society/src/mock.rs @@ -164,10 +164,6 @@ impl EnvBuilder { self.founded = f; self } - pub fn with_pot(mut self, p: u64) -> Self { - self.pot = p; - self - } } /// Run until a particular block. diff --git a/frame/society/src/tests.rs b/frame/society/src/tests.rs index 8de73e0ecb4ab..5519d5d14284d 100644 --- a/frame/society/src/tests.rs +++ b/frame/society/src/tests.rs @@ -994,7 +994,7 @@ fn zero_bid_works() { assert_eq!(Head::::get(), Some(30)); }); } -/* + #[test] fn bids_ordered_correctly() { // This tests that bids with the same value are placed in the list ordered @@ -1003,8 +1003,9 @@ fn bids_ordered_correctly() { for i in 0..5 { for j in 0..5 { // Give them some funds - let _ = Balances::make_free_balance_be(&(100 + (i * 5 + j) as u128), 1000); - assert_ok!(Society::bid(Origin::signed(100 + (i * 5 + j) as u128), j)); + let who = 100 + (i * 5 + j) as u128; + let _ = Balances::make_free_balance_be(&who, 1000); + assert_ok!(Society::bid(Origin::signed(who), j)); } } @@ -1012,11 +1013,11 @@ fn bids_ordered_correctly() { for j in 0..5 { for i in 0..5 { - final_list.push((100 +candidacy(j1, , (i * 5 + j), 0, 0) as u128, BidKind::Deposit(25))); + final_list.push(bid(100 + (i * 5 + j) as u128, BidKind::Deposit(25), j)); } } - + let max_bids: u32 = ::MaxBids::get(); + final_list.truncate(max_bids as usize); assert_eq!(Bids::::get(), final_list); }); } -*/ \ No newline at end of file From d6abcb98bbb62edcc1110f1272c85b1e7d5abde8 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 5 May 2022 18:46:49 +0100 Subject: [PATCH 14/43] Minor stuff --- Cargo.lock | 2 + frame/society/Cargo.toml | 2 + frame/society/src/lib.rs | 132 ++++++++++++++++++-------------------- frame/society/src/mock.rs | 4 +- 4 files changed, 71 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7a5ef8e09ae7b..654c971f346f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6559,10 +6559,12 @@ dependencies = [ "frame-support", "frame-support-test", "frame-system", + "hex-literal", "pallet-balances", "parity-scale-codec", "rand_chacha 0.2.2", "scale-info", + "sp-arithmetic", "sp-core", "sp-io", "sp-runtime", diff --git a/frame/society/Cargo.toml b/frame/society/Cargo.toml index 1fd9693d0d00f..4f24d026c4234 100644 --- a/frame/society/Cargo.toml +++ b/frame/society/Cargo.toml @@ -17,9 +17,11 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../primitives/arithmetic" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } rand_chacha = { version = "0.2", default-features = false } +hex-literal = "0.3.4" [dev-dependencies] sp-core = { version = "6.0.0", path = "../../primitives/core" } diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index b44f020917cb5..48ed6da71ac70 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -241,9 +241,6 @@ //! * `set_max_membership` - The ROOT origin can update the maximum member count for the society. //! The max membership count must be greater than 1. -// TODO: Sort out all the `limit: None` stuff for remove prefix. -// TODO: Membership subsets: ranks and badges. - // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] @@ -253,6 +250,8 @@ mod mock; #[cfg(test)] mod tests; +pub mod migrations; + use frame_support::{ pallet_prelude::*, traits::{ @@ -284,17 +283,6 @@ type NegativeImbalanceOf = <>::Currency as Currency< ::AccountId, >>::NegativeImbalance; -/// A vote by a member on a candidate application. -#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub enum OldVote { - /// The member has been chosen to be skeptic and has not yet taken any action. - Skeptic, - /// The member has rejected the candidate's application. - Reject, - /// The member approves of the candidate's application. - Approve, -} - #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub struct Vote { approve: bool, @@ -467,22 +455,17 @@ pub struct GroupParams { } pub type GroupParamsFor = GroupParams>; -/* -pub type FactionIndex = u32; - -pub enum Faction { - Society, - Splinter(FactionIndex), -} -/// A group is just the rank of a faction. -pub struct Group { - faction: Faction, - rank: Rank, +/// A vote by a member on a candidate application. +#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub enum OldVote { + /// The member has been chosen to be skeptic and has not yet taken any action. + Skeptic, + /// The member has rejected the candidate's application. + Reject, + /// The member approves of the candidate's application. + Approve, } -*/ -//TODO: Ranks cannot be indexed linearly if we want to do insertions into the ranking system. -// Should just be done as Groups DAG. #[frame_support::pallet] pub mod pallet { @@ -608,6 +591,8 @@ pub mod pallet { AlreadyElevated, /// The skeptic has already been punished for this offence. AlreadyPunished, + /// Funds are insufficient to pay off society debts. + insufficientFunds, } #[pallet::event] @@ -665,7 +650,6 @@ pub mod pallet { pub type Pot, I: 'static = ()> = StorageValue<_, BalanceOf, ValueQuery>; /// The first member. - // TODO: Rename to `Faction` as a map on `FactionIndex`. #[pallet::storage] pub type Founder, I: 'static = ()> = StorageValue<_, T::AccountId>; @@ -675,8 +659,6 @@ pub mod pallet { /// A hash of the rules of this society concerning membership. Can only be set once and /// only by the founder. - // TODO: Should be a map with rules for each rank and faction. - // TODO: Rename to `GroupMeta` as a map on `GroupIndex` and include `name: String`. #[pallet::storage] pub type Rules, I: 'static = ()> = StorageValue<_, T::Hash>; @@ -684,24 +666,11 @@ pub mod pallet { #[pallet::storage] pub type Members, I: 'static = ()> = StorageMap<_, Twox64Concat, T::AccountId, MemberRecord, OptionQuery>; - /* - // TODO: Migrate from: - pub(super) type Vouching, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, VouchingStatus>; - pub(super) type Strikes, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, StrikeCount, ValueQuery>; - pub type Members, I: 'static = ()> = - StorageValue<_, Vec, ValueQuery>; - */ /// Information regarding rank-0 payouts, past and future. #[pallet::storage] pub type Payouts, I: 'static = ()> = StorageMap<_, Twox64Concat, T::AccountId, PayoutRecordFor, ValueQuery>; - /* - // TODO: Migrate from: - pub(super) type Payouts, I: 'static = ()> = ??? - */ /// The number of items in `Members` currently. (Doesn't include `SuspendedMembers`.) #[pallet::storage] @@ -727,11 +696,6 @@ pub mod pallet { pub(super) type Bids, I: 'static = ()> = StorageValue<_, BoundedVec>, T::MaxBids>, ValueQuery>; - /* - // TODO: Migrate from: - pub type Candidates, I: 'static = ()> = - StorageValue<_, Vec>>, ValueQuery>; - */ #[pallet::storage] pub type Candidates, I: 'static = ()> = StorageMap<_, Blake2_128Concat, @@ -754,9 +718,6 @@ pub mod pallet { Vote, OptionQuery, >; - // TODO: Migrate from: - //pub(super) type Votes, I: 'static = ()> = - // StorageDoubleMap<_, Twox64Concat, T::AccountId, Twox64Concat, T::AccountId, OldVote>; /// At the end of the claim period, this contains the most recently approved members (along with /// their bid and round ID) who is from the most recent round with the lowest bid. They will @@ -775,11 +736,54 @@ pub mod pallet { #[pallet::storage] pub(super) type DefenderVotes, I: 'static = ()> = StorageMap<_, Twox64Concat, T::AccountId, Vote>; - /* - // TODO: Migrate from - pub(super) type DefenderVotes, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, OldVote>; - */ + + mod old { + use super::{ + pallet, StorageMap, StorageValue, StorageDoubleMap, Config, BalanceOf, Twox64Concat, + ValueQuery, BidKind, VouchingStatus, StrikeCount, Bid, + }; + + #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] + pub enum Vote { + Skeptic, + Reject, + Approve, + } + + #[pallet::storage] + pub type Bids, I: 'static = ()> = + StorageValue<_, Vec>>, ValueQuery>; + #[pallet::storage] + pub type Candidates, I: 'static = ()> = + StorageValue<_, Vec>>, ValueQuery>; + #[pallet::storage] + pub type Votes, I: 'static = ()> = + StorageDoubleMap<_, Twox64Concat, T::AccountId, Twox64Concat, T::AccountId, Vote>; + #[pallet::storage] + pub type SuspendedCandidates, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, (BalanceOf, BidKind>)>; + #[pallet::storage] + pub type Members, I: 'static = ()> = + StorageValue<_, Vec, ValueQuery>; + #[pallet::storage] + pub type Vouching, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, VouchingStatus>; + #[pallet::storage] + pub type Strikes, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, StrikeCount, ValueQuery>; + #[pallet::storage] + pub type Payouts, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, Vec<(T::BlockNumber, BalanceOf)>, ValueQuery>; + #[pallet::storage] + pub type SuspendedMembers, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, bool, ValueQuery>; + #[pallet::storage] + pub type Defender, I: 'static = ()> = + StorageValue<_, T::AccountId>; + #[pallet::storage] + pub type DefenderVotes, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, Vote>; + } #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { @@ -837,14 +841,6 @@ pub mod pallet { } } - mod migrations { - /* - fn to_v2() { - - } - */ - } - #[pallet::call] impl, I: 'static> Pallet { /// A user outside of the society can make a bid for entry. @@ -1078,18 +1074,19 @@ pub mod pallet { /// Repay the payment previously given to the member with the signed origin, remove any /// pending payments, and elevate them from rank 0 to rank 1. #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn waive_repay(origin: OriginFor) -> DispatchResult { + pub fn waive_repay(origin: OriginFor, amount: BalanceOf) -> DispatchResult { let who = ensure_signed(origin)?; let mut record = Members::::get(&who).ok_or(Error::::NotMember)?; let mut payout_record = Payouts::::get(&who); ensure!(record.rank == 0, Error::::AlreadyElevated); + ensure!(amount >= payout_record.paid, Error::::insufficientFunds); T::Currency::transfer(&who, &Self::account_id(), payout_record.paid, AllowDeath)?; + payout_record.paid = 0; payout_record.payouts.clear(); record.rank = 1; Members::::insert(&who, record); Payouts::::insert(&who, payout_record); - Self::deposit_event(Event::::Elevated { member: who, rank: 1 }); Ok(()) @@ -1344,7 +1341,6 @@ impl EnsureOrigin<::Origin> for EnsureFoun } } -// TODO: Move close to `FromEntropy`? struct InputFromRng<'a, T>(&'a mut T); impl<'a, T: RngCore> codec::Input for InputFromRng<'a, T> { fn remaining_len(&mut self) -> Result, codec::Error> { diff --git a/frame/society/src/mock.rs b/frame/society/src/mock.rs index 0d756718ea764..b2c5f698be1f2 100644 --- a/frame/society/src/mock.rs +++ b/frame/society/src/mock.rs @@ -157,7 +157,9 @@ impl EnvBuilder { let r = b"be cool".to_vec(); assert!(Society::found_society(Origin::signed(1), 10, 10, 8, 2, 25, r).is_ok()); } - f() + let r = f(); + migrations::assert_internal_consistency(); + r }) } pub fn founded(mut self, f: bool) -> Self { From f8360a82bd35fdb7d00ac25e4887ec484effa39e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 10 May 2022 13:47:41 +0200 Subject: [PATCH 15/43] generate_storage_alias: Rewrite as proc macro attribute This rewrites the `generate_storage_alias!` declarative macro as proc-macro attribute. While doing this the name is changed to `storage_alias`. The prefix can now also be the name of a pallet. This makes storage aliases work in migrations for all kind of chains and not just for the ones that use predefined prefixes. --- frame/bags-list/src/list/tests.rs | 8 +- frame/bags-list/src/migrations.rs | 5 +- frame/contracts/src/migration.rs | 52 +- frame/elections-phragmen/src/migrations/v3.rs | 95 ++-- frame/offences/src/migration.rs | 8 +- frame/staking/src/migrations.rs | 28 +- frame/support/procedural/src/lib.rs | 8 + frame/support/procedural/src/storage_alias.rs | 524 ++++++++++++++++++ frame/support/src/lib.rs | 221 ++------ .../support/src/storage/bounded_btree_map.rs | 14 +- .../support/src/storage/bounded_btree_set.rs | 15 +- frame/support/src/storage/bounded_vec.rs | 14 +- .../src/storage/generator/double_map.rs | 6 +- frame/support/src/storage/generator/map.rs | 3 +- frame/support/src/storage/generator/nmap.rs | 16 +- frame/support/src/storage/mod.rs | 18 +- frame/support/src/storage/types/nmap.rs | 8 +- frame/support/src/storage/weak_bounded_vec.rs | 13 +- frame/system/src/migrations/mod.rs | 66 +-- 19 files changed, 762 insertions(+), 360 deletions(-) create mode 100644 frame/support/procedural/src/storage_alias.rs diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs index ff7dd2871c237..66c2ad8ab95b5 100644 --- a/frame/bags-list/src/list/tests.rs +++ b/frame/bags-list/src/list/tests.rs @@ -368,11 +368,9 @@ mod list { assert_eq!(crate::ListNodes::::count(), 4); // we do some wacky stuff here to get access to the counter, since it is (reasonably) // not exposed as mutable in any sense. - frame_support::generate_storage_alias!( - BagsList, - CounterForListNodes - => Value - ); + #[frame_support::storage_alias] + type CounterForListNodes = + Value, u32, frame_support::pallet_prelude::ValueQuery>; CounterForListNodes::mutate(|counter| *counter += 1); assert_eq!(crate::ListNodes::::count(), 5); diff --git a/frame/bags-list/src/migrations.rs b/frame/bags-list/src/migrations.rs index 696733e8c7ba5..42606ff7e635d 100644 --- a/frame/bags-list/src/migrations.rs +++ b/frame/bags-list/src/migrations.rs @@ -30,11 +30,12 @@ impl OnRuntimeUpgrade for CheckCounterPrefix { fn pre_upgrade() -> Result<(), &'static str> { use frame_support::ensure; // The old explicit storage item. - frame_support::generate_storage_alias!(BagsList, CounterForListNodes => Value); + #[frame_support::storage_alias] + type CounterForListNodes = Value, u32>; // ensure that a value exists in the counter struct. ensure!( - crate::ListNodes::::count() == CounterForListNodes::get().unwrap(), + crate::ListNodes::::count() == CounterForListNodes::::get().unwrap(), "wrong list node counter" ); diff --git a/frame/contracts/src/migration.rs b/frame/contracts/src/migration.rs index 035e3b4409cf9..827120a9ab96a 100644 --- a/frame/contracts/src/migration.rs +++ b/frame/contracts/src/migration.rs @@ -18,11 +18,8 @@ use crate::{BalanceOf, CodeHash, Config, Pallet, TrieId, Weight}; use codec::{Decode, Encode}; use frame_support::{ - codec, generate_storage_alias, - pallet_prelude::*, - storage::migration, - traits::{Get, PalletInfoAccess}, - Identity, Twox64Concat, + codec, pallet_prelude::*, storage::migration, storage_alias, traits::Get, Identity, + Twox64Concat, }; use sp_std::{marker::PhantomData, prelude::*}; @@ -119,15 +116,12 @@ mod v5 { trie_id: TrieId, } - generate_storage_alias!( - Contracts, - ContractInfoOf => Map<(Twox64Concat, T::AccountId), ContractInfo> - ); + #[storage_alias] + type ContractInfoOf = + Map, Twox64Concat, ::AccountId, ContractInfo>; - generate_storage_alias!( - Contracts, - DeletionQueue => Value> - ); + #[storage_alias] + type DeletionQueue = Value, Vec>; pub fn migrate() -> Weight { let mut weight: Weight = 0; @@ -204,20 +198,15 @@ mod v6 { type ContractInfo = RawContractInfo, BalanceOf>; - generate_storage_alias!( - Contracts, - ContractInfoOf => Map<(Twox64Concat, T::AccountId), ContractInfo> - ); + #[storage_alias] + type ContractInfoOf = + Map, Twox64Concat, ::AccountId, ContractInfo>; - generate_storage_alias!( - Contracts, - CodeStorage => Map<(Identity, CodeHash), PrefabWasmModule> - ); + #[storage_alias] + type CodeStorage = Map, Identity, CodeHash, PrefabWasmModule>; - generate_storage_alias!( - Contracts, - OwnerInfoOf => Map<(Identity, CodeHash), OwnerInfo> - ); + #[storage_alias] + type OwnerInfoOf = Map, Identity, CodeHash, OwnerInfo>; pub fn migrate() -> Weight { let mut weight: Weight = 0; @@ -261,14 +250,11 @@ mod v7 { use super::*; pub fn migrate() -> Weight { - generate_storage_alias!( - Contracts, - AccountCounter => Value - ); - generate_storage_alias!( - Contracts, - Nonce => Value - ); + #[storage_alias] + type AccountCounter = Value, u64, ValueQuery>; + #[storage_alias] + type Nonce = Value, u64, ValueQuery>; + Nonce::set(AccountCounter::take()); T::DbWeight::get().reads_writes(1, 2) } diff --git a/frame/elections-phragmen/src/migrations/v3.rs b/frame/elections-phragmen/src/migrations/v3.rs index c6a7ce7e7ca1b..815bdf71591ea 100644 --- a/frame/elections-phragmen/src/migrations/v3.rs +++ b/frame/elections-phragmen/src/migrations/v3.rs @@ -17,12 +17,10 @@ //! Migrations to version [`3.0.0`], as denoted by the changelog. +use crate::{Config, Pallet}; use codec::{Decode, Encode, FullCodec}; use frame_support::{ - pallet_prelude::ValueQuery, - traits::{PalletInfoAccess, StorageVersion}, - weights::Weight, - RuntimeDebug, Twox64Concat, + pallet_prelude::ValueQuery, traits::StorageVersion, weights::Weight, RuntimeDebug, Twox64Concat, }; use sp_std::prelude::*; @@ -42,9 +40,6 @@ struct Voter { /// Trait to implement to give information about types used for migration pub trait V2ToV3 { - /// The elections-phragmen pallet. - type Pallet: 'static + PalletInfoAccess; - /// System config account id type AccountId: 'static + FullCodec; @@ -52,30 +47,30 @@ pub trait V2ToV3 { type Balance: 'static + FullCodec + Copy; } -frame_support::generate_storage_alias!( - PhragmenElection, Candidates => Value< - Vec<(T::AccountId, T::Balance)>, - ValueQuery - > -); -frame_support::generate_storage_alias!( - PhragmenElection, Members => Value< - Vec>, - ValueQuery - > -); -frame_support::generate_storage_alias!( - PhragmenElection, RunnersUp => Value< - Vec>, - ValueQuery - > -); -frame_support::generate_storage_alias!( - PhragmenElection, Voting => Map< - (Twox64Concat, T::AccountId), - Voter - > -); +#[frame_support::storage_alias] +type Candidates = + Value, Vec<(::AccountId, ::Balance)>, ValueQuery>; + +#[frame_support::storage_alias] +type Members = Value< + Pallet, + Vec::AccountId, ::Balance>>, + ValueQuery, +>; + +#[frame_support::storage_alias] +type RunnersUp = Value< + Pallet, + Vec::AccountId, ::Balance>>, + ValueQuery, +>; + +#[frame_support::storage_alias] +type Voting = Map< + Pallet, + (Twox64Concat, T::AccountId), + Voter<::AccountId, ::Balance>, +>; /// Apply all of the migrations from 2 to 3. /// @@ -86,7 +81,10 @@ frame_support::generate_storage_alias!( /// /// Be aware that this migration is intended to be used only for the mentioned versions. Use /// with care and run at your own risk. -pub fn apply(old_voter_bond: T::Balance, old_candidacy_bond: T::Balance) -> Weight { +pub fn apply( + old_voter_bond: V::Balance, + old_candidacy_bond: V::Balance, +) -> Weight { let storage_version = StorageVersion::get::(); log::info!( target: "runtime::elections-phragmen", @@ -95,12 +93,12 @@ pub fn apply(old_voter_bond: T::Balance, old_candidacy_bond: T::Balan ); if storage_version <= 2 { - migrate_voters_to_recorded_deposit::(old_voter_bond); - migrate_candidates_to_recorded_deposit::(old_candidacy_bond); - migrate_runners_up_to_recorded_deposit::(old_candidacy_bond); - migrate_members_to_recorded_deposit::(old_candidacy_bond); + migrate_voters_to_recorded_deposit::(old_voter_bond); + migrate_candidates_to_recorded_deposit::(old_candidacy_bond); + migrate_runners_up_to_recorded_deposit::(old_candidacy_bond); + migrate_members_to_recorded_deposit::(old_candidacy_bond); - StorageVersion::new(3).put::(); + StorageVersion::new(3).put::>(); Weight::max_value() } else { @@ -114,21 +112,21 @@ pub fn apply(old_voter_bond: T::Balance, old_candidacy_bond: T::Balan } /// Migrate from the old legacy voting bond (fixed) to the new one (per-vote dynamic). -pub fn migrate_voters_to_recorded_deposit(old_deposit: T::Balance) { - >::translate::<(T::Balance, Vec), _>(|_who, (stake, votes)| { +pub fn migrate_voters_to_recorded_deposit(old_deposit: V::Balance) { + >::translate::<(V::Balance, Vec), _>(|_who, (stake, votes)| { Some(Voter { votes, stake, deposit: old_deposit }) }); log::info!( target: "runtime::elections-phragmen", "migrated {} voter accounts.", - >::iter().count(), + >::iter().count(), ); } /// Migrate all candidates to recorded deposit. -pub fn migrate_candidates_to_recorded_deposit(old_deposit: T::Balance) { - let _ = >::translate::, _>(|maybe_old_candidates| { +pub fn migrate_candidates_to_recorded_deposit(old_deposit: V::Balance) { + let _ = >::translate::, _>(|maybe_old_candidates| { maybe_old_candidates.map(|old_candidates| { log::info!( target: "runtime::elections-phragmen", @@ -141,8 +139,8 @@ pub fn migrate_candidates_to_recorded_deposit(old_deposit: T::Balance } /// Migrate all members to recorded deposit. -pub fn migrate_members_to_recorded_deposit(old_deposit: T::Balance) { - let _ = >::translate::, _>(|maybe_old_members| { +pub fn migrate_members_to_recorded_deposit(old_deposit: V::Balance) { + let _ = >::translate::, _>(|maybe_old_members| { maybe_old_members.map(|old_members| { log::info!( target: "runtime::elections-phragmen", @@ -158,9 +156,9 @@ pub fn migrate_members_to_recorded_deposit(old_deposit: T::Balance) { } /// Migrate all runners-up to recorded deposit. -pub fn migrate_runners_up_to_recorded_deposit(old_deposit: T::Balance) { - let _ = - >::translate::, _>(|maybe_old_runners_up| { +pub fn migrate_runners_up_to_recorded_deposit(old_deposit: V::Balance) { + let _ = >::translate::, _>( + |maybe_old_runners_up| { maybe_old_runners_up.map(|old_runners_up| { log::info!( target: "runtime::elections-phragmen", @@ -172,5 +170,6 @@ pub fn migrate_runners_up_to_recorded_deposit(old_deposit: T::Balance .map(|(who, stake)| SeatHolder { who, stake, deposit: old_deposit }) .collect::>() }) - }); + }, + ); } diff --git a/frame/offences/src/migration.rs b/frame/offences/src/migration.rs index 0d6c98b564cb1..72178fe389568 100644 --- a/frame/offences/src/migration.rs +++ b/frame/offences/src/migration.rs @@ -17,7 +17,7 @@ use super::{Config, OffenceDetails, Perbill, SessionIndex}; use frame_support::{ - generate_storage_alias, pallet_prelude::ValueQuery, traits::Get, weights::Weight, + storage_alias, pallet_prelude::ValueQuery, traits::Get, weights::Weight, }; use sp_staking::offence::{DisableStrategy, OnOffenceHandler}; use sp_std::vec::Vec; @@ -31,10 +31,8 @@ type DeferredOffenceOf = ( // Deferred reports that have been rejected by the offence handler and need to be submitted // at a later time. -generate_storage_alias!( - Offences, - DeferredOffences => Value>, ValueQuery> -); +#[storage_alias] +type DeferredOffences = Value, Vec>, ValueQuery>; pub fn remove_deferred_storage() -> Weight { let mut weight = T::DbWeight::get().reads_writes(1, 1); diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 96c905f4e5942..60666083644b0 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -139,10 +139,12 @@ pub mod v8 { pub mod v7 { use super::*; - use frame_support::generate_storage_alias; + use frame_support::storage_alias; - generate_storage_alias!(Staking, CounterForValidators => Value); - generate_storage_alias!(Staking, CounterForNominators => Value); + #[storage_alias] + type CounterForValidators = Value, u32>; + #[storage_alias] + type CounterForNominators = Value, u32>; pub fn pre_migrate() -> Result<(), &'static str> { assert!( @@ -176,15 +178,21 @@ pub mod v7 { pub mod v6 { use super::*; - use frame_support::{generate_storage_alias, traits::Get, weights::Weight}; + use frame_support::{storage_alias, traits::Get, weights::Weight}; // NOTE: value type doesn't matter, we just set it to () here. - generate_storage_alias!(Staking, SnapshotValidators => Value<()>); - generate_storage_alias!(Staking, SnapshotNominators => Value<()>); - generate_storage_alias!(Staking, QueuedElected => Value<()>); - generate_storage_alias!(Staking, QueuedScore => Value<()>); - generate_storage_alias!(Staking, EraElectionStatus => Value<()>); - generate_storage_alias!(Staking, IsCurrentSessionFinal => Value<()>); + #[storage_alias] + type SnapshotValidators = Value, ()>; + #[storage_alias] + type SnapshotNominators = Value, ()>; + #[storage_alias] + type QueuedElected = Value, ()>; + #[storage_alias] + type QueuedScore = Value, ()>; + #[storage_alias] + type EraElectionStatus = Value, ()>; + #[storage_alias] + type IsCurrentSessionFinal = Value, ()>; /// check to execute prior to migration. pub fn pre_migrate() -> Result<(), &'static str> { diff --git a/frame/support/procedural/src/lib.rs b/frame/support/procedural/src/lib.rs index 92564e94493c1..f4e64cb949aa9 100644 --- a/frame/support/procedural/src/lib.rs +++ b/frame/support/procedural/src/lib.rs @@ -25,6 +25,7 @@ mod crate_version; mod debug_no_bound; mod default_no_bound; mod dummy_part_checker; +mod storage_alias; mod key_prefix; mod match_and_insert; mod pallet; @@ -575,3 +576,10 @@ pub fn derive_pallet_error(input: TokenStream) -> TokenStream { pub fn __create_tt_macro(input: TokenStream) -> TokenStream { tt_macro::create_tt_return_macro(input) } + +#[proc_macro_attribute] +pub fn storage_alias(_: TokenStream, input: TokenStream) -> TokenStream { + storage_alias::storage_alias(input.into()) + .unwrap_or_else(|r| r.into_compile_error()) + .into() +} diff --git a/frame/support/procedural/src/storage_alias.rs b/frame/support/procedural/src/storage_alias.rs new file mode 100644 index 0000000000000..39760661dd911 --- /dev/null +++ b/frame/support/procedural/src/storage_alias.rs @@ -0,0 +1,524 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Implementation of the `storage_alias` attribute macro. + +use frame_support_procedural_tools::generate_crate_access_2018; +use proc_macro2::{Span, TokenStream}; +use quote::{quote, ToTokens}; +use syn::{ + ext::IdentExt, + parenthesized, + parse::{Parse, ParseStream}, + punctuated::Punctuated, + token, Attribute, Error, Ident, Result, Token, Type, TypeParam, Visibility, +}; + +/// Represents a path that only consists of [`Ident`] separated by `::`. +struct SimplePath { + leading_colon: Option, + segments: Punctuated, +} + +impl SimplePath { + /// Returns the [`Ident`] of this path. + /// + /// It only returns `Some(_)` if there is exactly one element and no leading colon. + fn get_ident(&self) -> Option<&Ident> { + if self.segments.len() != 1 || self.leading_colon.is_some() { + None + } else { + self.segments.first() + } + } +} + +impl Parse for SimplePath { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + leading_colon: if input.peek(Token![::]) { Some(input.parse()?) } else { None }, + segments: Punctuated::parse_separated_nonempty_with(input, |p| Ident::parse_any(p))?, + }) + } +} + +impl ToTokens for SimplePath { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.leading_colon.to_tokens(tokens); + self.segments.to_tokens(tokens); + } +} + +/// Represents generics which only support [`TypeParam`] separated by commas. +struct SimpleGenerics { + lt_token: Token![<], + params: Punctuated, + gt_token: Token![>], +} + +impl SimpleGenerics { + /// Returns the generics for types declarations etc. + fn type_generics(&self) -> impl Iterator { + self.params.iter().map(|p| &p.ident) + } + + /// Returns the generics for the `impl` block. + fn impl_generics(&self) -> impl Iterator { + self.params.iter() + } + + /// Returns `true` if all parameters have at least one trait bound. + fn all_have_trait_bounds(&self) -> bool { + self.params + .iter() + .all(|p| p.bounds.iter().any(|b| matches!(b, syn::TypeParamBound::Trait(_)))) + } +} + +impl Parse for SimpleGenerics { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + lt_token: input.parse()?, + params: Punctuated::parse_separated_nonempty(input)?, + gt_token: input.parse()?, + }) + } +} + +impl ToTokens for SimpleGenerics { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.lt_token.to_tokens(tokens); + self.params.to_tokens(tokens); + self.gt_token.to_tokens(tokens); + } +} + +mod storage_types { + syn::custom_keyword!(Value); + syn::custom_keyword!(Map); + syn::custom_keyword!(DoubleMap); + syn::custom_keyword!(NMap); +} + +/// The supported storage types +enum StorageType { + Value { + _kw: storage_types::Value, + _lt_token: Token![<], + prefix: SimplePath, + prefix_generics: Option, + _value_comma: Token![,], + value_ty: Type, + query_type: Option<(Token![,], Type)>, + _trailing_comma: Option, + _gt_token: Token![>], + }, + Map { + _kw: storage_types::Map, + _lt_token: Token![<], + prefix: SimplePath, + prefix_generics: Option, + _hasher_comma: Token![,], + hasher_ty: Type, + _key_comma: Token![,], + key_ty: Type, + _value_comma: Token![,], + value_ty: Type, + query_type: Option<(Token![,], Type)>, + _trailing_comma: Option, + _gt_token: Token![>], + }, + DoubleMap { + _kw: storage_types::DoubleMap, + _lt_token: Token![<], + prefix: SimplePath, + prefix_generics: Option, + _hasher1_comma: Token![,], + hasher1_ty: Type, + _key1_comma: Token![,], + key1_ty: Type, + _hasher2_comma: Token![,], + hasher2_ty: Type, + _key2_comma: Token![,], + key2_ty: Type, + _value_comma: Token![,], + value_ty: Type, + query_type: Option<(Token![,], Type)>, + _trailing_comma: Option, + _gt_token: Token![>], + }, + NMap { + _kw: storage_types::NMap, + _lt_token: Token![<], + prefix: SimplePath, + prefix_generics: Option, + _paren_comma: Token![,], + _paren_token: token::Paren, + key_types: Punctuated, + _value_comma: Token![,], + value_ty: Type, + query_type: Option<(Token![,], Type)>, + _trailing_comma: Option, + _gt_token: Token![>], + }, +} + +impl StorageType { + /// Generate the actual type declaration. + fn generate_type_declaration( + &self, + crate_: &Ident, + storage_instance: &StorageInstance, + storage_name: &Ident, + storage_generics: Option<&SimpleGenerics>, + visibility: &Visibility, + attributes: &[Attribute], + ) -> TokenStream { + let storage_instance = &storage_instance.name; + let type_generics = self + .prefix_generics() + .map(|g| { + let g = g.type_generics(); + quote! { <#( #g ),*> } + }) + .unwrap_or_default(); + let attributes = attributes.iter(); + + match self { + Self::Value { value_ty, query_type, .. } => { + let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); + + quote! { + #( #attributes )* + #visibility type #storage_name #storage_generics = #crate_::storage::types::StorageValue< + #storage_instance #type_generics, + #value_ty + #query_type + >; + } + }, + Self::Map { value_ty, query_type, hasher_ty, key_ty, .. } => { + let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); + + quote! { + #( #attributes )* + #visibility type #storage_name #storage_generics = #crate_::storage::types::StorageMap< + #storage_instance #type_generics, + #hasher_ty, + #key_ty, + #value_ty + #query_type + >; + } + }, + Self::DoubleMap { + value_ty, + query_type, + hasher1_ty, + key1_ty, + hasher2_ty, + key2_ty, + .. + } => { + let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); + + quote! { + #( #attributes )* + #visibility type #storage_name #storage_generics = #crate_::storage::types::StorageDoubleMap< + #storage_instance #type_generics, + #hasher1_ty, + #key1_ty, + #hasher2_ty, + #key2_ty, + #value_ty + #query_type + >; + } + }, + Self::NMap { value_ty, query_type, key_types, .. } => { + let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); + let key_types = key_types.iter(); + + quote! { + #( #attributes )* + #visibility type #storage_name #storage_generics = #crate_::storage::types::StorageNMap< + #storage_instance #type_generics, + ( #( #key_types ),* ), + #value_ty + #query_type + >; + } + }, + } + } + + /// The prefix for this storage type. + fn prefix(&self) -> &SimplePath { + match self { + Self::Value { prefix, .. } | + Self::Map { prefix, .. } | + Self::NMap { prefix, .. } | + Self::DoubleMap { prefix, .. } => prefix, + } + } + + /// The prefix generics for this storage type. + fn prefix_generics(&self) -> Option<&SimpleGenerics> { + match self { + Self::Value { prefix_generics, .. } | + Self::Map { prefix_generics, .. } | + Self::NMap { prefix_generics, .. } | + Self::DoubleMap { prefix_generics, .. } => prefix_generics.as_ref(), + } + } +} + +impl Parse for StorageType { + fn parse(input: ParseStream<'_>) -> Result { + let lookahead = input.lookahead1(); + + let parse_query_type = |input: ParseStream<'_>| -> Result> { + if input.peek(Token![,]) && !input.peek2(Token![>]) { + Ok(Some((input.parse()?, input.parse()?))) + } else { + Ok(None) + } + }; + + let parse_pallet_generics = |input: ParseStream<'_>| -> Result> { + let lookahead = input.lookahead1(); + if lookahead.peek(Token![<]) { + Some(input.parse()).transpose() + } else if lookahead.peek(Token![,]) { + Ok(None) + } else { + Err(lookahead.error()) + } + }; + + if lookahead.peek(storage_types::Value) { + Ok(Self::Value { + _kw: input.parse()?, + _lt_token: input.parse()?, + prefix: input.parse()?, + prefix_generics: parse_pallet_generics(input)?, + _value_comma: input.parse()?, + value_ty: input.parse()?, + query_type: parse_query_type(input)?, + _trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?, + _gt_token: input.parse()?, + }) + } else if lookahead.peek(storage_types::Map) { + Ok(Self::Map { + _kw: input.parse()?, + _lt_token: input.parse()?, + prefix: input.parse()?, + prefix_generics: parse_pallet_generics(input)?, + _hasher_comma: input.parse()?, + hasher_ty: input.parse()?, + _key_comma: input.parse()?, + key_ty: input.parse()?, + _value_comma: input.parse()?, + value_ty: input.parse()?, + query_type: parse_query_type(input)?, + _trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?, + _gt_token: input.parse()?, + }) + } else if lookahead.peek(storage_types::DoubleMap) { + Ok(Self::DoubleMap { + _kw: input.parse()?, + _lt_token: input.parse()?, + prefix: input.parse()?, + prefix_generics: parse_pallet_generics(input)?, + _hasher1_comma: input.parse()?, + hasher1_ty: input.parse()?, + _key1_comma: input.parse()?, + key1_ty: input.parse()?, + _hasher2_comma: input.parse()?, + hasher2_ty: input.parse()?, + _key2_comma: input.parse()?, + key2_ty: input.parse()?, + _value_comma: input.parse()?, + value_ty: input.parse()?, + query_type: parse_query_type(input)?, + _trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?, + _gt_token: input.parse()?, + }) + } else if lookahead.peek(storage_types::NMap) { + let content; + Ok(Self::NMap { + _kw: input.parse()?, + _lt_token: input.parse()?, + prefix: input.parse()?, + prefix_generics: parse_pallet_generics(input)?, + _paren_comma: input.parse()?, + _paren_token: parenthesized!(content in input), + key_types: Punctuated::parse_terminated(&content)?, + _value_comma: input.parse()?, + value_ty: input.parse()?, + query_type: parse_query_type(input)?, + _trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?, + _gt_token: input.parse()?, + }) + } else { + Err(lookahead.error()) + } + } +} + +/// The input expected by this macro. +struct Input { + attributes: Vec, + visibility: Visibility, + _type: Token![type], + storage_name: Ident, + storage_generics: Option, + _equal: Token![=], + storage_type: StorageType, + _semicolon: Token![;], +} + +impl Parse for Input { + fn parse(input: ParseStream<'_>) -> Result { + let attributes = input.call(Attribute::parse_outer)?; + let visibility = input.parse()?; + let _type = input.parse()?; + let storage_name = input.parse()?; + + let lookahead = input.lookahead1(); + let (storage_generics, _equal) = if lookahead.peek(Token![<]) { + (Some(input.parse()?), input.parse()?) + } else if lookahead.peek(Token![=]) { + (None, input.parse()?) + } else { + return Err(lookahead.error()) + }; + + let storage_type = input.parse()?; + + let _semicolon = input.parse()?; + + Ok(Self { + attributes, + visibility, + _type, + storage_name, + storage_generics, + _equal, + storage_type, + _semicolon, + }) + } +} + +/// Implementation of the `storage_alias` attribute macro. +pub fn storage_alias(input: TokenStream) -> Result { + let input = syn::parse2::(input)?; + let crate_ = generate_crate_access_2018("frame-support")?; + + if let Some(ref generics) = input.storage_generics { + if !generics.all_have_trait_bounds() { + return Err(Error::new_spanned( + generics, + "The pallet generics require to be bound by the \ + pallet `Config` trait and optional `Instance` trait.", + )) + } + } + + let storage_instance = generate_storage_instance( + &crate_, + &input.storage_name, + input.storage_type.prefix(), + input.storage_type.prefix_generics(), + &input.visibility, + )?; + + let definition = input.storage_type.generate_type_declaration( + &crate_, + &storage_instance, + &input.storage_name, + input.storage_generics.as_ref(), + &input.visibility, + &input.attributes, + ); + + let storage_instance_code = storage_instance.code; + + Ok(quote! { + #storage_instance_code + + #definition + }) +} + +/// The storage instance to use for the storage alias. +struct StorageInstance { + name: Ident, + code: TokenStream, +} + +/// Generate the [`StorageInstance`] for the storage alias. +fn generate_storage_instance( + crate_: &Ident, + storage_name: &Ident, + prefix: &SimplePath, + prefix_generics: Option<&SimpleGenerics>, + visibility: &Visibility, +) -> Result { + let (pallet_prefix, impl_generics, type_generics) = + if let Some(prefix_generics) = prefix_generics { + let type_generics = prefix_generics.type_generics(); + let type_generics2 = prefix_generics.type_generics(); + let impl_generics = prefix_generics.impl_generics(); + + ( + quote! { + <#prefix < #( #type_generics2 ),* > as #crate_::traits::PalletInfoAccess>::name() + }, + quote!( #( #impl_generics ),* ), + quote!( #( #type_generics ),* ), + ) + } else if let Some(prefix) = prefix.get_ident() { + let prefix_str = prefix.to_string(); + + (quote!(#prefix_str), quote!(), quote!()) + } else { + return Err(Error::new_spanned( + prefix, + "If there are no generics, the prefix is only allowed to be an identifier.", + )) + }; + + let name = Ident::new(&format!("{}_Storage_Instance", storage_name), Span::call_site()); + let storage_name_str = storage_name.to_string(); + + // Implement `StorageInstance` trait. + let code = quote! { + #visibility struct #name< #impl_generics >(#crate_::sp_std::marker::PhantomData<(#type_generics)>); + + impl<#impl_generics> #crate_::traits::StorageInstance for #name< #type_generics > { + fn pallet_prefix() -> &'static str { + #pallet_prefix + } + + const STORAGE_PREFIX: &'static str = #storage_name_str; + } + }; + + Ok(StorageInstance { name, code }) +} diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 3e84c009b5ca6..4cd740c52162a 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -166,178 +166,69 @@ macro_rules! bounded_btree_map { /// Useful for creating a *storage-like* struct for test and migrations. /// /// ``` -/// # use frame_support::generate_storage_alias; +/// # use frame_support::storage_alias; /// use frame_support::codec; /// use frame_support::Twox64Concat; /// // generate a storage value with type u32. -/// generate_storage_alias!(Prefix, StorageName => Value); +/// #[storage_alias] +/// type StorageName = Value; /// /// // generate a double map from `(u32, u32)` (with hashers `Twox64Concat` for each key) /// // to `Vec` -/// generate_storage_alias!( -/// OtherPrefix, OtherStorageName => DoubleMap< -/// (Twox64Concat, u32), -/// (Twox64Concat, u32), -/// Vec -/// > -/// ); +/// #[storage_alias] +/// type OtherStorageName = DoubleMap< +/// OtherPrefix, +/// Twox64Concat, +/// u32, +/// Twox64Concat, +/// u32, +/// Vec, +/// >; /// /// // optionally specify the query type /// use frame_support::pallet_prelude::{ValueQuery, OptionQuery}; -/// generate_storage_alias!(Prefix, ValueName => Value); -/// generate_storage_alias!( -/// Prefix, SomeStorageName => DoubleMap< -/// (Twox64Concat, u32), -/// (Twox64Concat, u32), -/// Vec, -/// ValueQuery -/// > -/// ); +/// #[storage_alias] +/// type ValueName = Value; +/// #[storage_alias] +/// type SomeStorageName = Map< +/// Prefix, +/// Twox64Concat, +/// u32, +/// Vec, +/// ValueQuery, +/// >; /// /// // generate a map from `Config::AccountId` (with hasher `Twox64Concat`) to `Vec` /// trait Config { type AccountId: codec::FullCodec; } -/// generate_storage_alias!( -/// Prefix, GenericStorage => Map<(Twox64Concat, T::AccountId), Vec> -/// ); +/// #[storage_alias] +/// type GenericStorage = Map::AccountId, Vec>; +/// +/// // It also supports NMap +/// use frame_support::storage::types::Key as NMapKey; +/// +/// #[storage_alias] +/// type SomeNMap = NMap, NMapKey), Vec>; +/// +/// // Using pallet name as prefix. +/// // +/// // When the first generic argument is taking generic arguments it is expected to be a pallet. +/// // The prefix will then be the pallet name as configured in the runtime through +/// // `construct_runtime!`. +/// +/// # struct Pallet(std::marker::PhantomData); +/// # impl frame_support::traits::PalletInfoAccess for Pallet { +/// # fn index() -> usize { 0 } +/// # fn name() -> &'static str { "pallet" } +/// # fn module_name() -> &'static str { "module" } +/// # fn crate_version() -> frame_support::traits::CrateVersion { unimplemented!() } +/// # } +/// +/// #[storage_alias] +/// type SomeValue = Value, u64>; +/// /// # fn main() {} /// ``` -#[macro_export] -macro_rules! generate_storage_alias { - // without generic for $name. - ($pallet:ident, $name:ident => Map<($hasher:ty, $key:ty), $value:ty $(, $querytype:ty)?>) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - type $name = $crate::storage::types::StorageMap< - [<$name Instance>], - $hasher, - $key, - $value, - $( $querytype )? - >; - } - }; - ( - $pallet:ident, - $name:ident - => DoubleMap<($hasher1:ty, $key1:ty), ($hasher2:ty, $key2:ty), $value:ty $(, $querytype:ty)?> - ) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - type $name = $crate::storage::types::StorageDoubleMap< - [<$name Instance>], - $hasher1, - $key1, - $hasher2, - $key2, - $value, - $( $querytype )? - >; - } - }; - ( - $pallet:ident, - $name:ident - => NMap, $value:ty $(, $querytype:ty)?> - ) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - type $name = $crate::storage::types::StorageNMap< - [<$name Instance>], - ( - $( $crate::storage::types::Key<$hasher, $key>, )+ - ), - $value, - $( $querytype )? - >; - } - }; - ($pallet:ident, $name:ident => Value<$value:ty $(, $querytype:ty)?>) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - type $name = $crate::storage::types::StorageValue< - [<$name Instance>], - $value, - $( $querytype )? - >; - } - }; - // with generic for $name. - ( - $pallet:ident, - $name:ident<$t:ident : $bounds:tt> - => Map<($hasher:ty, $key:ty), $value:ty $(, $querytype:ty)?> - ) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - #[allow(type_alias_bounds)] - type $name<$t : $bounds> = $crate::storage::types::StorageMap< - [<$name Instance>], - $hasher, - $key, - $value, - $( $querytype )? - >; - } - }; - ( - $pallet:ident, - $name:ident<$t:ident : $bounds:tt> - => DoubleMap<($hasher1:ty, $key1:ty), ($hasher2:ty, $key2:ty), $value:ty $(, $querytype:ty)?> - ) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - #[allow(type_alias_bounds)] - type $name<$t : $bounds> = $crate::storage::types::StorageDoubleMap< - [<$name Instance>], - $hasher1, - $key1, - $hasher2, - $key2, - $value, - $( $querytype )? - >; - } - }; - ( - $pallet:ident, - $name:ident<$t:ident : $bounds:tt> - => NMap<$(($hasher:ty, $key:ty),)+ $value:ty $(, $querytype:ty)?> - ) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - #[allow(type_alias_bounds)] - type $name<$t : $bounds> = $crate::storage::types::StorageNMap< - [<$name Instance>], - ( - $( $crate::storage::types::Key<$hasher, $key>, )+ - ), - $value, - $( $querytype )? - >; - } - }; - ($pallet:ident, $name:ident<$t:ident : $bounds:tt> => Value<$value:ty $(, $querytype:ty)?>) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - #[allow(type_alias_bounds)] - type $name<$t : $bounds> = $crate::storage::types::StorageValue< - [<$name Instance>], - $value, - $( $querytype )? - >; - } - }; - // helper used in all arms. - (@GENERATE_INSTANCE_STRUCT $pallet:ident, $name:ident) => { - $crate::paste::paste! { - struct [<$name Instance>]; - impl $crate::traits::StorageInstance for [<$name Instance>] { - fn pallet_prefix() -> &'static str { stringify!($pallet) } - const STORAGE_PREFIX: &'static str = stringify!($name); - } - } - }; -} +pub use frame_support_procedural::storage_alias; /// Create new implementations of the [`Get`](crate::traits::Get) trait. /// @@ -995,16 +886,20 @@ pub mod tests { } #[test] - fn generate_storage_alias_works() { + fn storage_alias_works() { new_test_ext().execute_with(|| { - generate_storage_alias!( - Test, - GenericData2 => Map<(Blake2_128Concat, T::BlockNumber), T::BlockNumber> - ); + #[crate::storage_alias] + type GenericData2 = + Map::BlockNumber, ::BlockNumber>; assert_eq!(Module::::generic_data2(5), None); GenericData2::::insert(5, 5); assert_eq!(Module::::generic_data2(5), Some(5)); + + /// Some random docs that ensure that docs are accepted + #[crate::storage_alias] + pub type GenericData = + Map::BlockNumber, ::BlockNumber>; }); } diff --git a/frame/support/src/storage/bounded_btree_map.rs b/frame/support/src/storage/bounded_btree_map.rs index 0d589994bcc2c..df7abebe1956b 100644 --- a/frame/support/src/storage/bounded_btree_map.rs +++ b/frame/support/src/storage/bounded_btree_map.rs @@ -348,12 +348,14 @@ pub mod test { use frame_support::traits::ConstU32; use sp_io::TestExternalities; - crate::generate_storage_alias! { Prefix, Foo => Value>> } - crate::generate_storage_alias! { Prefix, FooMap => Map<(Twox128, u32), BoundedBTreeMap>> } - crate::generate_storage_alias! { - Prefix, - FooDoubleMap => DoubleMap<(Twox128, u32), (Twox128, u32), BoundedBTreeMap>> - } + #[crate::storage_alias] + type Foo = Value>>; + + #[crate::storage_alias] + type FooMap = Map>>; + + #[crate::storage_alias] + type FooDoubleMap = DoubleMap>>; fn map_from_keys(keys: &[K]) -> BTreeMap where diff --git a/frame/support/src/storage/bounded_btree_set.rs b/frame/support/src/storage/bounded_btree_set.rs index f38952bf545d9..f674c9b2c0bf3 100644 --- a/frame/support/src/storage/bounded_btree_set.rs +++ b/frame/support/src/storage/bounded_btree_set.rs @@ -322,12 +322,15 @@ pub mod test { use frame_support::traits::ConstU32; use sp_io::TestExternalities; - crate::generate_storage_alias! { Prefix, Foo => Value>> } - crate::generate_storage_alias! { Prefix, FooMap => Map<(Twox128, u32), BoundedBTreeSet>> } - crate::generate_storage_alias! { - Prefix, - FooDoubleMap => DoubleMap<(Twox128, u32), (Twox128, u32), BoundedBTreeSet>> - } + #[crate::storage_alias] + type Foo = Value>>; + + #[crate::storage_alias] + type FooMap = Map>>; + + #[crate::storage_alias] + type FooDoubleMap = + DoubleMap>>; fn set_from_keys(keys: &[T]) -> BTreeSet where diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index 92ca167f98436..2024d99771b3d 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -609,12 +609,14 @@ pub mod test { use crate::{bounded_vec, traits::ConstU32, Twox128}; use sp_io::TestExternalities; - crate::generate_storage_alias! { Prefix, Foo => Value>> } - crate::generate_storage_alias! { Prefix, FooMap => Map<(Twox128, u32), BoundedVec>> } - crate::generate_storage_alias! { - Prefix, - FooDoubleMap => DoubleMap<(Twox128, u32), (Twox128, u32), BoundedVec>> - } + #[crate::storage_alias] + type Foo = Value>>; + + #[crate::storage_alias] + type FooMap = Map>>; + + #[crate::storage_alias] + type FooDoubleMap = DoubleMap>>; #[test] fn slide_works() { diff --git a/frame/support/src/storage/generator/double_map.rs b/frame/support/src/storage/generator/double_map.rs index 16fcaf940c62a..6264738265678 100644 --- a/frame/support/src/storage/generator/double_map.rs +++ b/frame/support/src/storage/generator/double_map.rs @@ -525,10 +525,8 @@ mod test_iterators { fn double_map_iter_from() { sp_io::TestExternalities::default().execute_with(|| { use crate::hash::Identity; - crate::generate_storage_alias!( - MyModule, - MyDoubleMap => DoubleMap<(Identity, u64), (Identity, u64), u64> - ); + #[crate::storage_alias] + type MyDoubleMap = DoubleMap; MyDoubleMap::insert(1, 10, 100); MyDoubleMap::insert(1, 21, 201); diff --git a/frame/support/src/storage/generator/map.rs b/frame/support/src/storage/generator/map.rs index da48952bcba87..f054c5c36b84a 100644 --- a/frame/support/src/storage/generator/map.rs +++ b/frame/support/src/storage/generator/map.rs @@ -384,7 +384,8 @@ mod test_iterators { fn map_iter_from() { sp_io::TestExternalities::default().execute_with(|| { use crate::hash::Identity; - crate::generate_storage_alias!(MyModule, MyMap => Map<(Identity, u64), u64>); + #[crate::storage_alias] + type MyMap = Map; MyMap::insert(1, 10); MyMap::insert(2, 20); diff --git a/frame/support/src/storage/generator/nmap.rs b/frame/support/src/storage/generator/nmap.rs index be085ca2d9db6..ef243cead7580 100755 --- a/frame/support/src/storage/generator/nmap.rs +++ b/frame/support/src/storage/generator/nmap.rs @@ -475,10 +475,12 @@ mod test_iterators { fn n_map_iter_from() { sp_io::TestExternalities::default().execute_with(|| { use crate::{hash::Identity, storage::Key as NMapKey}; - crate::generate_storage_alias!( + #[crate::storage_alias] + type MyNMap = NMap< MyModule, - MyNMap => NMap, u64> - ); + (NMapKey, NMapKey, NMapKey), + u64, + >; MyNMap::insert((1, 1, 1), 11); MyNMap::insert((1, 1, 2), 21); @@ -518,11 +520,9 @@ mod test_iterators { let key_hash = NMap::hashed_key_for((1, 2)); { - crate::generate_storage_alias!(Test, NMap => DoubleMap< - (crate::Blake2_128Concat, u16), - (crate::Twox64Concat, u32), - u64 - >); + #[crate::storage_alias] + type NMap = + DoubleMap; let value = NMap::get(1, 2).unwrap(); assert_eq!(value, 50); diff --git a/frame/support/src/storage/mod.rs b/frame/support/src/storage/mod.rs index 115f179d803a7..d181ba12ca869 100644 --- a/frame/support/src/storage/mod.rs +++ b/frame/support/src/storage/mod.rs @@ -1545,10 +1545,8 @@ mod test { fn prefix_iterator_pagination_works() { TestExternalities::default().execute_with(|| { use crate::{hash::Identity, storage::generator::map::StorageMap}; - crate::generate_storage_alias! { - MyModule, - MyStorageMap => Map<(Identity, u64), u64> - } + #[crate::storage_alias] + type MyStorageMap = Map; MyStorageMap::insert(1, 10); MyStorageMap::insert(2, 20); @@ -1663,12 +1661,12 @@ mod test { }); } - crate::generate_storage_alias! { Prefix, Foo => Value>> } - crate::generate_storage_alias! { Prefix, FooMap => Map<(Twox128, u32), BoundedVec>> } - crate::generate_storage_alias! { - Prefix, - FooDoubleMap => DoubleMap<(Twox128, u32), (Twox128, u32), BoundedVec>> - } + #[crate::storage_alias] + type Foo = Value>>; + #[crate::storage_alias] + type FooMap = Map>>; + #[crate::storage_alias] + type FooDoubleMap = DoubleMap>>; #[test] fn try_append_works() { diff --git a/frame/support/src/storage/types/nmap.rs b/frame/support/src/storage/types/nmap.rs index 16dc30ea03903..349d9eea3bede 100755 --- a/frame/support/src/storage/types/nmap.rs +++ b/frame/support/src/storage/types/nmap.rs @@ -544,7 +544,7 @@ mod test { use crate::{ hash::{StorageHasher as _, *}, metadata::{StorageEntryModifier, StorageHasher}, - storage::types::{Key, ValueQuery}, + storage::types::{Key, Key as NMapKey, ValueQuery}, }; use sp_io::{hashing::twox_128, TestExternalities}; @@ -589,10 +589,8 @@ mod test { assert_eq!(AValueQueryWithAnOnEmpty::get((3,)), 10); { - crate::generate_storage_alias!(test, Foo => NMap< - Key<(Blake2_128Concat, u16)>, - u32 - >); + #[crate::storage_alias] + type Foo = NMap), u32>; assert_eq!(Foo::contains_key((3,)), true); assert_eq!(Foo::get((3,)), Some(10)); diff --git a/frame/support/src/storage/weak_bounded_vec.rs b/frame/support/src/storage/weak_bounded_vec.rs index 21cc487b49082..ddbde380147db 100644 --- a/frame/support/src/storage/weak_bounded_vec.rs +++ b/frame/support/src/storage/weak_bounded_vec.rs @@ -321,12 +321,13 @@ pub mod test { use frame_support::traits::ConstU32; use sp_io::TestExternalities; - crate::generate_storage_alias! { Prefix, Foo => Value>> } - crate::generate_storage_alias! { Prefix, FooMap => Map<(Twox128, u32), WeakBoundedVec>> } - crate::generate_storage_alias! { - Prefix, - FooDoubleMap => DoubleMap<(Twox128, u32), (Twox128, u32), WeakBoundedVec>> - } + #[crate::storage_alias] + type Foo = Value>>; + #[crate::storage_alias] + type FooMap = Map>>; + #[crate::storage_alias] + type FooDoubleMap = + DoubleMap>>; #[test] fn bound_returns_correct_value() { diff --git a/frame/system/src/migrations/mod.rs b/frame/system/src/migrations/mod.rs index 872cf389d246c..7e1e2ab05ffcf 100644 --- a/frame/system/src/migrations/mod.rs +++ b/frame/system/src/migrations/mod.rs @@ -17,6 +17,7 @@ //! Migrate the reference counting state. +use crate::{Config, Pallet}; use codec::{Decode, Encode, FullCodec}; use frame_support::{ pallet_prelude::ValueQuery, traits::PalletInfoAccess, weights::Weight, Blake2_128Concat, @@ -52,43 +53,24 @@ pub trait V2ToV3 { type AccountData: 'static + FullCodec; } -// ### Warning -// -// The call below is only valid because the name System is enforced -// at runtime construction level for the system pallet. -frame_support::generate_storage_alias!( - System, UpgradedToU32RefCount => Value< - bool, - ValueQuery - > -); - -// ### Warning -// -// The call below is only valid because the name System is enforced -// at runtime construction level for the system pallet. -frame_support::generate_storage_alias!( - System, UpgradedToTripleRefCount => Value< - bool, - ValueQuery - > -); - -// ### Warning -// -// The call below is only valid because the name System is enforced -// at runtime construction level for the system pallet. -frame_support::generate_storage_alias!( - System, Account => Map< - (Blake2_128Concat, T::AccountId), - AccountInfo - > -); +#[frame_support::storage_alias] +type UpgradedToU32RefCount = Value, bool, ValueQuery>; + +#[frame_support::storage_alias] +type UpgradedToTripleRefCount = Value, bool, ValueQuery>; + +#[frame_support::storage_alias] +type Account = Map< + Pallet, + Blake2_128Concat, + ::AccountId, + AccountInfo<::Index, ::AccountData>, +>; /// Migrate from unique `u8` reference counting to triple `u32` reference counting. -pub fn migrate_from_single_u8_to_triple_ref_count() -> Weight { +pub fn migrate_from_single_u8_to_triple_ref_count() -> Weight { let mut translated: usize = 0; - >::translate::<(T::Index, u8, T::AccountData), _>(|_key, (nonce, rc, data)| { + >::translate::<(V::Index, u8, V::AccountData), _>(|_key, (nonce, rc, data)| { translated += 1; Some(AccountInfo { nonce, consumers: rc as RefCount, providers: 1, sufficients: 0, data }) }); @@ -97,15 +79,15 @@ pub fn migrate_from_single_u8_to_triple_ref_count() -> Weight { "Applied migration from single u8 to triple reference counting to {:?} elements.", translated ); - ::put(true); - ::put(true); + >::put(true); + >::put(true); Weight::max_value() } /// Migrate from unique `u32` reference counting to triple `u32` reference counting. -pub fn migrate_from_single_to_triple_ref_count() -> Weight { +pub fn migrate_from_single_to_triple_ref_count() -> Weight { let mut translated: usize = 0; - >::translate::<(T::Index, RefCount, T::AccountData), _>( + >::translate::<(V::Index, RefCount, V::AccountData), _>( |_key, (nonce, consumers, data)| { translated += 1; Some(AccountInfo { nonce, consumers, providers: 1, sufficients: 0, data }) @@ -116,14 +98,14 @@ pub fn migrate_from_single_to_triple_ref_count() -> Weight { "Applied migration from single to triple reference counting to {:?} elements.", translated ); - ::put(true); + >::put(true); Weight::max_value() } /// Migrate from dual `u32` reference counting to triple `u32` reference counting. -pub fn migrate_from_dual_to_triple_ref_count() -> Weight { +pub fn migrate_from_dual_to_triple_ref_count() -> Weight { let mut translated: usize = 0; - >::translate::<(T::Index, RefCount, RefCount, T::AccountData), _>( + >::translate::<(V::Index, RefCount, RefCount, V::AccountData), _>( |_key, (nonce, consumers, providers, data)| { translated += 1; Some(AccountInfo { nonce, consumers, providers, sufficients: 0, data }) @@ -134,6 +116,6 @@ pub fn migrate_from_dual_to_triple_ref_count() -> Weight { "Applied migration from dual to triple reference counting to {:?} elements.", translated ); - ::put(true); + >::put(true); Weight::max_value() } From 43cf8c13eb4cf3b16bd1d7feafa219e76c3bf97c Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 10 May 2022 14:00:07 +0100 Subject: [PATCH 16/43] Maintenance operations don't pay fee --- frame/society/src/lib.rs | 41 ++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 48ed6da71ac70..74cea2f3e6e84 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -469,6 +469,8 @@ pub enum OldVote { #[frame_support::pallet] pub mod pallet { + use frame_support::dispatch::PaysFee; + use super::*; #[pallet::pallet] @@ -1004,13 +1006,15 @@ pub mod pallet { let mut candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; let record = Members::::get(&voter).ok_or(Error::::NotMember)?; - Votes::::mutate(&candidate, &voter, |v| { + let first_time = Votes::::mutate(&candidate, &voter, |v| { + let first_time = v.is_none(); *v = Some(Self::do_vote(*v, approve, record.rank, &mut candidacy.tally)); + first_time }); Candidates::::insert(&candidate, &candidacy); Self::deposit_event(Event::::Vote { candidate, voter, vote: approve }); - Ok(()) + Ok(if first_time { Pays::No } else { Pays::Yes }.into()) } /// As a member, vote on the defender. @@ -1031,13 +1035,15 @@ pub mod pallet { let mut defending = Defending::::get().ok_or(Error::::NoDefender)?; let record = Members::::get(&voter).ok_or(Error::::NotMember)?; - DefenderVotes::::mutate(&voter, |v| { + let first_time = DefenderVotes::::mutate(&voter, |v| { + let first_time = v.is_none(); *v = Some(Self::do_vote(*v, approve, record.rank, &mut defending.2)); + first_time }); Defending::::put(defending); Self::deposit_event(Event::::DefenderVote { voter, vote: approve }); - Ok(()) + Ok(if first_time { Pays::No } else { Pays::Yes }.into()) } /// Transfer the first matured payout for the sender and remove it from the records. @@ -1199,7 +1205,7 @@ pub mod pallet { SuspendedMembers::::remove(&who); Self::deposit_event(Event::::SuspendedMemberJudgement { who, judged: forgive }); - Ok(()) + Ok(Pays::No) } /// Change the maximum number of members in society and the maximum number of new candidates @@ -1240,9 +1246,9 @@ pub mod pallet { let mut candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; ensure!(!candidacy.skeptic_struck, Error::::AlreadyPunished); ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); - Self::check_skeptic(&candidate, &mut candidacy); + let punished = Self::check_skeptic(&candidate, &mut candidacy); Candidates::::insert(&candidate, candidacy); - Ok(()) + Ok(if punished { Pays::No } else { Pays::Yes }.into()) } /// Transform an approved candidate into a member. Callable only by the @@ -1253,7 +1259,8 @@ pub mod pallet { let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; ensure!(candidacy.tally.clear_approval(), Error::::NotApproved); ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); - Self::induct_member(candidate, candidacy, 0) + Self::induct_member(candidate, candidacy, 0)?; + Ok(Pays::No.into()) } /// Transform an approved candidate into a member. Callable only by the Signed origin of the @@ -1266,7 +1273,8 @@ pub mod pallet { let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; ensure!(!candidacy.tally.clear_rejection(), Error::::Rejected); ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); - Self::induct_member(candidate, candidacy, 0) + Self::induct_member(candidate, candidacy, 0)?; + Ok(Pays::No.into()) } /// Remove the candidate's application from the society. Callable only by the Signed origin @@ -1285,7 +1293,7 @@ pub mod pallet { Self::reject_candidate(&candidate, &candidacy.kind); Votes::::remove_prefix(&candidate, None); Candidates::::remove(&candidate); - Ok(()) + Ok(Pays::No) } /// Remove the candidate's application from the society. Callable only by the candidate. @@ -1301,7 +1309,7 @@ pub mod pallet { Self::reject_candidate(&candidate, &candidacy.kind); Votes::::remove_prefix(&candidate, None); Candidates::::remove(&candidate); - Ok(()) + Ok(Pays::No) } /// Remove a `candidate`'s failed application from the society. Callable by any @@ -1318,7 +1326,7 @@ pub mod pallet { Self::reject_candidate(&candidate, &candidacy.kind); Votes::::remove_prefix(&candidate, None); Candidates::::remove(&candidate); - Ok(()) + Ok(Pays::No) } } } @@ -1379,6 +1387,7 @@ impl, I: 'static> Pallet { target_round == round && matches!(Self::period(), Period::Voting {..}) } + /// Returns the new vote. fn do_vote(maybe_old: Option, approve: bool, rank: Rank, tally: &mut Tally) -> Vote { match maybe_old { Some(Vote { approve: true, weight }) => tally.approvals.saturating_reduce(weight), @@ -1394,7 +1403,8 @@ impl, I: 'static> Pallet { Vote { approve, weight } } - fn check_skeptic(candidate: &T::AccountId, candidacy: &mut Candidacy>) { + /// Returns `true` if a punishment was given. + fn check_skeptic(candidate: &T::AccountId, candidacy: &mut Candidacy>) -> bool { if RoundCount::::get() != candidacy.round || candidacy.skeptic_struck { return } // We expect the skeptic to have voted. let skeptic = match Skeptic::::get() { Some(s) => s, None => return }; @@ -1409,9 +1419,12 @@ impl, I: 'static> Pallet { // Can't do much if the punishment doesn't work out. if Self::strike_member(&skeptic).is_ok() { candidacy.skeptic_struck = true; + true + } else { + false } }, - _ => {}, + _ => false, } } From b12cbb2cd29c0f0a8b3b19f1ee3df8193fc0d44a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 10 May 2022 17:04:12 +0200 Subject: [PATCH 17/43] Fix compilation and FMT --- frame/offences/src/migration.rs | 4 +--- frame/support/procedural/src/lib.rs | 2 +- frame/support/procedural/src/storage_alias.rs | 22 +++++++++---------- frame/support/src/lib.rs | 8 +++++-- .../support/src/storage/bounded_btree_map.rs | 3 ++- 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/frame/offences/src/migration.rs b/frame/offences/src/migration.rs index 72178fe389568..9b883b2809138 100644 --- a/frame/offences/src/migration.rs +++ b/frame/offences/src/migration.rs @@ -16,9 +16,7 @@ // limitations under the License. use super::{Config, OffenceDetails, Perbill, SessionIndex}; -use frame_support::{ - storage_alias, pallet_prelude::ValueQuery, traits::Get, weights::Weight, -}; +use frame_support::{pallet_prelude::ValueQuery, storage_alias, traits::Get, weights::Weight}; use sp_staking::offence::{DisableStrategy, OnOffenceHandler}; use sp_std::vec::Vec; diff --git a/frame/support/procedural/src/lib.rs b/frame/support/procedural/src/lib.rs index f4e64cb949aa9..f8aaa5fe37749 100644 --- a/frame/support/procedural/src/lib.rs +++ b/frame/support/procedural/src/lib.rs @@ -25,13 +25,13 @@ mod crate_version; mod debug_no_bound; mod default_no_bound; mod dummy_part_checker; -mod storage_alias; mod key_prefix; mod match_and_insert; mod pallet; mod pallet_error; mod partial_eq_no_bound; mod storage; +mod storage_alias; mod transactional; mod tt_macro; diff --git a/frame/support/procedural/src/storage_alias.rs b/frame/support/procedural/src/storage_alias.rs index 39760661dd911..6fe98700385de 100644 --- a/frame/support/procedural/src/storage_alias.rs +++ b/frame/support/procedural/src/storage_alias.rs @@ -302,7 +302,17 @@ impl Parse for StorageType { let parse_pallet_generics = |input: ParseStream<'_>| -> Result> { let lookahead = input.lookahead1(); if lookahead.peek(Token![<]) { - Some(input.parse()).transpose() + let generics = input.parse::()?; + + if generics.all_have_trait_bounds() { + Ok(Some(generics)) + } else { + Err(Error::new_spanned( + generics, + "The pallet generics require to be bound by the \ + pallet `Config` trait and optional `Instance` trait.", + )) + } } else if lookahead.peek(Token![,]) { Ok(None) } else { @@ -430,16 +440,6 @@ pub fn storage_alias(input: TokenStream) -> Result { let input = syn::parse2::(input)?; let crate_ = generate_crate_access_2018("frame-support")?; - if let Some(ref generics) = input.storage_generics { - if !generics.all_have_trait_bounds() { - return Err(Error::new_spanned( - generics, - "The pallet generics require to be bound by the \ - pallet `Config` trait and optional `Instance` trait.", - )) - } - } - let storage_instance = generate_storage_instance( &crate_, &input.storage_name, diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 4cd740c52162a..0453c5339f80d 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -898,8 +898,12 @@ pub mod tests { /// Some random docs that ensure that docs are accepted #[crate::storage_alias] - pub type GenericData = - Map::BlockNumber, ::BlockNumber>; + pub type GenericData = Map< + Test2, + Blake2_128Concat, + ::BlockNumber, + ::BlockNumber, + >; }); } diff --git a/frame/support/src/storage/bounded_btree_map.rs b/frame/support/src/storage/bounded_btree_map.rs index df7abebe1956b..6afc786aeb987 100644 --- a/frame/support/src/storage/bounded_btree_map.rs +++ b/frame/support/src/storage/bounded_btree_map.rs @@ -355,7 +355,8 @@ pub mod test { type FooMap = Map>>; #[crate::storage_alias] - type FooDoubleMap = DoubleMap>>; + type FooDoubleMap = + DoubleMap>>; fn map_from_keys(keys: &[K]) -> BTreeMap where From 249b664bf5d1e6e61dbecf6431f0be08b37afa27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 10 May 2022 17:30:45 +0200 Subject: [PATCH 18/43] Moare fixes --- frame/elections-phragmen/src/migrations/v3.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frame/elections-phragmen/src/migrations/v3.rs b/frame/elections-phragmen/src/migrations/v3.rs index 815bdf71591ea..34b8486183ff0 100644 --- a/frame/elections-phragmen/src/migrations/v3.rs +++ b/frame/elections-phragmen/src/migrations/v3.rs @@ -68,7 +68,8 @@ type RunnersUp = Value< #[frame_support::storage_alias] type Voting = Map< Pallet, - (Twox64Concat, T::AccountId), + Twox64Concat, + ::AccountId, Voter<::AccountId, ::Balance>, >; @@ -85,7 +86,7 @@ pub fn apply( old_voter_bond: V::Balance, old_candidacy_bond: V::Balance, ) -> Weight { - let storage_version = StorageVersion::get::(); + let storage_version = StorageVersion::get::>(); log::info!( target: "runtime::elections-phragmen", "Running migration for elections-phragmen with storage version {:?}", From 676595c012943ec6248a2dd00e876c675193c277 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 11 May 2022 12:16:57 +0100 Subject: [PATCH 19/43] Migrations --- Cargo.lock | 6 +- frame/society/Cargo.toml | 1 - frame/society/src/lib.rs | 86 ++------ frame/society/src/migrations.rs | 253 +++++++++++++++++++++++ frame/support/src/storage/bounded_vec.rs | 6 + 5 files changed, 280 insertions(+), 72 deletions(-) create mode 100644 frame/society/src/migrations.rs diff --git a/Cargo.lock b/Cargo.lock index ca7132e7212c8..78cf095cd0942 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3410,9 +3410,9 @@ checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" [[package]] name = "libgit2-sys" -version = "0.13.3+1.4.2" +version = "0.13.2+1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c24d36c3ac9b9996a2418d6bf428cc0bc5d1a814a84303fc60986088c5ed60de" +checksum = "3a42de9a51a5c12e00fc0e4ca6bc2ea43582fc6418488e8f615e905d886f258b" dependencies = [ "cc", "libc", @@ -11098,7 +11098,7 @@ dependencies = [ "chrono", "lazy_static", "matchers", - "parking_lot 0.9.0", + "parking_lot 0.11.2", "regex", "serde", "serde_json", diff --git a/frame/society/Cargo.toml b/frame/society/Cargo.toml index d6979ecde128e..f02188f46ed76 100644 --- a/frame/society/Cargo.toml +++ b/frame/society/Cargo.toml @@ -21,7 +21,6 @@ sp-std = { version = "4.0.0", default-features = false, path = "../../primitives sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../primitives/arithmetic" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -rand_chacha = { version = "0.2", default-features = false } hex-literal = "0.3.4" [dev-dependencies] diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 74cea2f3e6e84..d20dc81d15def 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -469,8 +469,6 @@ pub enum OldVote { #[frame_support::pallet] pub mod pallet { - use frame_support::dispatch::PaysFee; - use super::*; #[pallet::pallet] @@ -594,7 +592,7 @@ pub mod pallet { /// The skeptic has already been punished for this offence. AlreadyPunished, /// Funds are insufficient to pay off society debts. - insufficientFunds, + InsufficientFunds, } #[pallet::event] @@ -739,54 +737,6 @@ pub mod pallet { pub(super) type DefenderVotes, I: 'static = ()> = StorageMap<_, Twox64Concat, T::AccountId, Vote>; - mod old { - use super::{ - pallet, StorageMap, StorageValue, StorageDoubleMap, Config, BalanceOf, Twox64Concat, - ValueQuery, BidKind, VouchingStatus, StrikeCount, Bid, - }; - - #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] - pub enum Vote { - Skeptic, - Reject, - Approve, - } - - #[pallet::storage] - pub type Bids, I: 'static = ()> = - StorageValue<_, Vec>>, ValueQuery>; - #[pallet::storage] - pub type Candidates, I: 'static = ()> = - StorageValue<_, Vec>>, ValueQuery>; - #[pallet::storage] - pub type Votes, I: 'static = ()> = - StorageDoubleMap<_, Twox64Concat, T::AccountId, Twox64Concat, T::AccountId, Vote>; - #[pallet::storage] - pub type SuspendedCandidates, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, (BalanceOf, BidKind>)>; - #[pallet::storage] - pub type Members, I: 'static = ()> = - StorageValue<_, Vec, ValueQuery>; - #[pallet::storage] - pub type Vouching, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, VouchingStatus>; - #[pallet::storage] - pub type Strikes, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, StrikeCount, ValueQuery>; - #[pallet::storage] - pub type Payouts, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, Vec<(T::BlockNumber, BalanceOf)>, ValueQuery>; - #[pallet::storage] - pub type SuspendedMembers, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, bool, ValueQuery>; - #[pallet::storage] - pub type Defender, I: 'static = ()> = - StorageValue<_, T::AccountId>; - #[pallet::storage] - pub type DefenderVotes, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, Vote>; - } - #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { fn on_initialize(n: T::BlockNumber) -> Weight { @@ -999,7 +949,7 @@ pub mod pallet { origin: OriginFor, candidate: ::Source, approve: bool, - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { let voter = ensure_signed(origin)?; let candidate = T::Lookup::lookup(candidate)?; @@ -1029,7 +979,7 @@ pub mod pallet { /// Total Complexity: O(M + logM) /// # #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn defender_vote(origin: OriginFor, approve: bool) -> DispatchResult { + pub fn defender_vote(origin: OriginFor, approve: bool) -> DispatchResultWithPostInfo { let voter = ensure_signed(origin)?; let mut defending = Defending::::get().ok_or(Error::::NoDefender)?; @@ -1085,10 +1035,10 @@ pub mod pallet { let mut record = Members::::get(&who).ok_or(Error::::NotMember)?; let mut payout_record = Payouts::::get(&who); ensure!(record.rank == 0, Error::::AlreadyElevated); - ensure!(amount >= payout_record.paid, Error::::insufficientFunds); + ensure!(amount >= payout_record.paid, Error::::InsufficientFunds); T::Currency::transfer(&who, &Self::account_id(), payout_record.paid, AllowDeath)?; - payout_record.paid = 0; + payout_record.paid = Zero::zero(); payout_record.payouts.clear(); record.rank = 1; Members::::insert(&who, record); @@ -1187,7 +1137,7 @@ pub mod pallet { origin: OriginFor, who: T::AccountId, forgive: bool, - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { let founder = ensure_signed(origin)?; ensure!(Founder::::get() == Some(founder.clone()), Error::::NotFounder); @@ -1205,7 +1155,7 @@ pub mod pallet { SuspendedMembers::::remove(&who); Self::deposit_event(Event::::SuspendedMemberJudgement { who, judged: forgive }); - Ok(Pays::No) + Ok(Pays::No.into()) } /// Change the maximum number of members in society and the maximum number of new candidates @@ -1241,7 +1191,7 @@ pub mod pallet { /// Punish the skeptic with a strike if they did not vote on a candidate. Callable by the /// candidate. #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn punish_skeptic(origin: OriginFor) -> DispatchResult { + pub fn punish_skeptic(origin: OriginFor) -> DispatchResultWithPostInfo { let candidate = ensure_signed(origin)?; let mut candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; ensure!(!candidacy.skeptic_struck, Error::::AlreadyPunished); @@ -1254,7 +1204,7 @@ pub mod pallet { /// Transform an approved candidate into a member. Callable only by the /// the candidate, and only after the period for voting has ended. #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn claim_membership(origin: OriginFor) -> DispatchResult { + pub fn claim_membership(origin: OriginFor) -> DispatchResultWithPostInfo { let candidate = ensure_signed(origin)?; let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; ensure!(candidacy.tally.clear_approval(), Error::::NotApproved); @@ -1267,7 +1217,7 @@ pub mod pallet { /// Founder, only after the period for voting has ended and only when the candidate is not /// clearly rejected. #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn bestow_membership(origin: OriginFor, candidate: T::AccountId) -> DispatchResult { + pub fn bestow_membership(origin: OriginFor, candidate: T::AccountId) -> DispatchResultWithPostInfo { let founder = ensure_signed(origin)?; ensure!(Founder::::get() == Some(founder.clone()), Error::::NotFounder); let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; @@ -1283,7 +1233,7 @@ pub mod pallet { /// /// Any bid deposit is lost and voucher is banned. #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn kick_candidate(origin: OriginFor, candidate: T::AccountId) -> DispatchResult { + pub fn kick_candidate(origin: OriginFor, candidate: T::AccountId) -> DispatchResultWithPostInfo { let founder = ensure_signed(origin)?; ensure!(Founder::::get() == Some(founder.clone()), Error::::NotFounder); let mut candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; @@ -1293,14 +1243,14 @@ pub mod pallet { Self::reject_candidate(&candidate, &candidacy.kind); Votes::::remove_prefix(&candidate, None); Candidates::::remove(&candidate); - Ok(Pays::No) + Ok(Pays::No.into()) } /// Remove the candidate's application from the society. Callable only by the candidate. /// /// Any bid deposit is lost and voucher is banned. #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn resign_candidacy(origin: OriginFor) -> DispatchResult { + pub fn resign_candidacy(origin: OriginFor) -> DispatchResultWithPostInfo { let candidate = ensure_signed(origin)?; let mut candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; if !Self::in_progress(candidacy.round) { @@ -1309,7 +1259,7 @@ pub mod pallet { Self::reject_candidate(&candidate, &candidacy.kind); Votes::::remove_prefix(&candidate, None); Candidates::::remove(&candidate); - Ok(Pays::No) + Ok(Pays::No.into()) } /// Remove a `candidate`'s failed application from the society. Callable by any @@ -1318,7 +1268,7 @@ pub mod pallet { /// /// The bid deposit is lost and the voucher is banned. #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn drop_candidate(origin: OriginFor, candidate: T::AccountId) -> DispatchResult { + pub fn drop_candidate(origin: OriginFor, candidate: T::AccountId) -> DispatchResultWithPostInfo { ensure_signed(origin)?; let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; ensure!(candidacy.tally.clear_rejection(), Error::::NotRejected); @@ -1326,7 +1276,7 @@ pub mod pallet { Self::reject_candidate(&candidate, &candidacy.kind); Votes::::remove_prefix(&candidate, None); Candidates::::remove(&candidate); - Ok(Pays::No) + Ok(Pays::No.into()) } } } @@ -1405,9 +1355,9 @@ impl, I: 'static> Pallet { /// Returns `true` if a punishment was given. fn check_skeptic(candidate: &T::AccountId, candidacy: &mut Candidacy>) -> bool { - if RoundCount::::get() != candidacy.round || candidacy.skeptic_struck { return } + if RoundCount::::get() != candidacy.round || candidacy.skeptic_struck { return false } // We expect the skeptic to have voted. - let skeptic = match Skeptic::::get() { Some(s) => s, None => return }; + let skeptic = match Skeptic::::get() { Some(s) => s, None => return false }; let maybe_vote = Votes::::get(&candidate, &skeptic); let approved = candidacy.tally.clear_approval(); let rejected = candidacy.tally.clear_rejection(); diff --git a/frame/society/src/migrations.rs b/frame/society/src/migrations.rs new file mode 100644 index 0000000000000..c41cbb24122fe --- /dev/null +++ b/frame/society/src/migrations.rs @@ -0,0 +1,253 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Migrations for Society Pallet + +use super::*; +use frame_support::traits::Instance; + +mod old { + use super::*; + use frame_support::storage_alias; + + /// A vote by a member on a candidate application. + #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] + pub enum Vote { + /// The member has been chosen to be skeptic and has not yet taken any action. + Skeptic, + /// The member has rejected the candidate's application. + Reject, + /// The member approves of the candidate's application. + Approve, + } + + #[storage_alias] pub type Bids = Value< + pallet::Pallet, I: Instance + 'static>, + Vec::AccountId, BalanceOf>>, + ValueQuery, + >; + #[storage_alias] pub type Candidates = Value< + Pallet, I: Instance + 'static>, + Vec::AccountId, BalanceOf>>, + ValueQuery, + >; + #[storage_alias] pub type Votes = DoubleMap< + Pallet, I: Instance + 'static>, + Twox64Concat, ::AccountId, + Twox64Concat, ::AccountId, + Vote, + >; + #[storage_alias] pub type SuspendedCandidates = Map< + Pallet, I: Instance + 'static>, + Twox64Concat, ::AccountId, + (BalanceOf, BidKind<::AccountId, BalanceOf>), + >; + #[storage_alias] pub type Members = Value< + Pallet, I: Instance + 'static>, + Vec<::AccountId>, + ValueQuery, + >; + #[storage_alias] pub type Vouching = Map< + Pallet, I: Instance + 'static>, + Twox64Concat, ::AccountId, + VouchingStatus, + >; + #[storage_alias] pub type Strikes = Map< + Pallet, I: Instance + 'static>, + Twox64Concat, ::AccountId, + StrikeCount, + ValueQuery, + >; + #[storage_alias] pub type Payouts = Map< + Pallet, I: Instance + 'static>, + Twox64Concat, ::AccountId, + Vec<(::BlockNumber, BalanceOf)>, + ValueQuery, + >; + #[storage_alias] pub type SuspendedMembers = Map< + Pallet, I: Instance + 'static>, + Twox64Concat, ::AccountId, + bool, + ValueQuery, + >; + #[storage_alias] pub type Defender = Value< + Pallet, I: Instance + 'static>, + ::AccountId, + >; + #[storage_alias] pub type DefenderVotes = Map< + Pallet, I: Instance + 'static>, + Twox64Concat, ::AccountId, + Vote, + >; +} + +pub fn can_migrate, I: Instance + 'static>() -> bool { + old::Members::::exists() +} + +/// Will panic if there are any inconsistencies in the pallet's state or old keys remaining. +pub fn assert_internal_consistency, I: Instance + 'static>() { + // Check all members are valid data. + let mut members = vec![]; + for m in Members::::iter_keys() { + let r = Members::::get(&m).expect("Member data must be valid"); + members.push((m, r)); + } + assert_eq!(MemberCount::::get(), members.len() as u32); + for (who, record) in members.iter() { + assert_eq!(MemberByIndex::::get(record.index).as_ref(), Some(who)); + } + assert_eq!(Founder::::get().as_ref(), members.first().map(|m| &m.0)); + if let Some(head) = Head::::get() { + assert!(Members::::contains_key(head)); + } + // Check all votes are valid data. + for (k1, k2) in Votes::::iter_keys() { + assert!(Votes::::get(k1, k2).is_some()); + } + // Check all defender votes are valid data. + for k in DefenderVotes::::iter_keys() { + assert!(DefenderVotes::::get(k).is_some()); + } + // Check all candidates are valid data. + for k in Candidates::::iter_keys() { + assert!(Candidates::::get(k).is_some()); + } + // Check all suspended members are valid data. + for m in SuspendedMembers::::iter_keys() { + assert!(SuspendedMembers::::get(m).is_some()); + } + // Check all payouts are valid data. + for p in Payouts::::iter_keys() { + let have_value = Payouts::::contains_key(&p); + let is_non_default_value = Payouts::::get(&p) != Default::default(); + // If we have a value it should not be the default value. + assert!(have_value == is_non_default_value); + } + + // We don't use these - make sure they don't exist. + assert_eq!(old::SuspendedCandidates::::iter().count(), 0); + assert_eq!(old::Strikes::::iter().count(), 0); + assert_eq!(old::Vouching::::iter().count(), 0); + assert!(!old::Defender::::exists()); + assert!(!old::Members::::exists()); +} + +pub fn from_original< + T: Config, + I: Instance + 'static, +>( + past_payouts: &mut [(::AccountId, BalanceOf)] +) { + // First check that this is the original state layout. This is easy since the original layout + // contained the Members value, and this value no longer exists in the new layout. + if !old::Members::::exists() { + // Already migrated or no data to migrate: Bail. + return + } + + // Migrate Bids from old::Bids (just a trunctation). + Bids::::put(BoundedVec::<_, T::MaxBids>::truncate_from(old::Bids::::take())); + + // Initialise round counter. + RoundCount::::put(0); + + // Migrate Candidates from old::Candidates + for Bid { who: candidate, kind, value } in old::Candidates::::take().into_iter() { + let mut tally = Tally::default(); + // Migrate Votes from old::Votes + // No need to drain, since we're overwriting values. + for (voter, vote) in old::Votes::::iter_prefix(&candidate) { + Votes::::insert(&candidate, &voter, Vote { + approve: vote == old::Vote::Approve, + weight: 1, + }); + match vote { + old::Vote::Approve => tally.approvals.saturating_inc(), + old::Vote::Reject => tally.rejections.saturating_inc(), + old::Vote::Skeptic => Skeptic::::put(&voter), + } + } + Candidates::::insert(&candidate, Candidacy { + round: 0, + kind, + tally, + skeptic_struck: false, + bid: value, + }); + } + + // Migrate Members from old::Members old::Strikes old::Vouching + let mut member_count = 0; + for member in old::Members::::take() { + let strikes = old::Strikes::::get(&member); + let vouching = old::Vouching::::get(&member); + Members::::insert(&member, MemberRecord { + index: member_count, + rank: 0, + strikes, + vouching, + }); + MemberByIndex::::insert(member_count, &member); + member_count.saturating_inc(); + } + + // Migrate Payouts from: old::Payouts and raw info (needed since we can't query old chain state). + past_payouts.sort(); + for (who, mut payouts) in old::Payouts::::iter() { + payouts.truncate(T::MaxPayouts::get() as usize); + // ^^ Safe since we already truncated. + let paid = past_payouts + .binary_search_by_key(&&who, |x| &x.0) + .ok() + .map(|p| past_payouts[p].1) + .unwrap_or(Zero::zero()); + match BoundedVec::try_from(payouts) { + Ok(payouts) => Payouts::::insert(who, PayoutRecord { paid, payouts }), + Err(_) => debug_assert!(false, "Truncation of Payouts ineffective??"), + } + } + + // Any suspended candidates remaining are rejected. + old::SuspendedCandidates::::remove_all(None); + + // Any suspended members remaining are given the benefit of the doubt. + old::SuspendedMembers::::remove_all(None); + + // We give the current defender the benefit of the doubt. + old::Defender::::kill(); + old::DefenderVotes::::remove_all(None); +} + +pub fn from_raw_past_payouts< + T: Config, + I: Instance + 'static, +>( + past_payouts_raw: &[(&[u8], u128)] +) -> Vec<(::AccountId, BalanceOf)> { + past_payouts_raw.iter() + .filter_map(|(x, y)| Some(( + Decode::decode(&mut x.as_ref()).ok()?, + (*y).try_into().ok()?, + ))) + .collect() +} + +// use hex_literal::hex; +// let mut past_payouts_raw = vec![ +// (hex!["1234567890123456789012345678901234567890123456789012345678901234"], 0u128), +// ]; diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index 120dd354f78e2..9f87e0918a710 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -233,6 +233,12 @@ impl> BoundedVec { Self::with_bounded_capacity(Self::bound()) } + /// Consume and truncate the vector `v` in order to create a new instance of `Self` from it. + pub fn truncate_from(mut v: Vec) -> Self { + v.truncate(Self::bound()); + Self::unchecked_from(v) + } + /// Get the bound of the type in `usize`. pub fn bound() -> usize { S::get() as usize From 0fe52d88713d28c6e0ca7bb0d264288f3ebe0a68 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 11 May 2022 13:51:37 +0100 Subject: [PATCH 20/43] Fix tests and add migration testing --- frame/society/src/migrations.rs | 37 +++--- frame/society/src/mock.rs | 12 +- frame/society/src/tests.rs | 168 ++++++++++++++++++++++------ frame/support/src/traits/storage.rs | 6 + 4 files changed, 169 insertions(+), 54 deletions(-) diff --git a/frame/society/src/migrations.rs b/frame/society/src/migrations.rs index c41cbb24122fe..b34410f056d2c 100644 --- a/frame/society/src/migrations.rs +++ b/frame/society/src/migrations.rs @@ -20,7 +20,7 @@ use super::*; use frame_support::traits::Instance; -mod old { +pub(crate) mod old { use super::*; use frame_support::storage_alias; @@ -111,7 +111,9 @@ pub fn assert_internal_consistency, I: Instance + 'static>() { for (who, record) in members.iter() { assert_eq!(MemberByIndex::::get(record.index).as_ref(), Some(who)); } - assert_eq!(Founder::::get().as_ref(), members.first().map(|m| &m.0)); + if let Some(founder) = Founder::::get() { + assert_eq!(Members::::get(founder).expect("founder is member").index, 0); + } if let Some(head) = Head::::get() { assert!(Members::::contains_key(head)); } @@ -133,10 +135,9 @@ pub fn assert_internal_consistency, I: Instance + 'static>() { } // Check all payouts are valid data. for p in Payouts::::iter_keys() { - let have_value = Payouts::::contains_key(&p); - let is_non_default_value = Payouts::::get(&p) != Default::default(); - // If we have a value it should not be the default value. - assert!(have_value == is_non_default_value); + let k = Payouts::::hashed_key_for(&p); + let v = frame_support::storage::unhashed::get_raw(&k[..]).expect("value is in map"); + assert!(PayoutRecordFor::::decode(&mut &v[..]).is_ok()); } // We don't use these - make sure they don't exist. @@ -194,17 +195,14 @@ pub fn from_original< // Migrate Members from old::Members old::Strikes old::Vouching let mut member_count = 0; for member in old::Members::::take() { - let strikes = old::Strikes::::get(&member); - let vouching = old::Vouching::::get(&member); - Members::::insert(&member, MemberRecord { - index: member_count, - rank: 0, - strikes, - vouching, - }); + let strikes = old::Strikes::::take(&member); + let vouching = old::Vouching::::take(&member); + let record = MemberRecord { index: member_count, rank: 0, strikes, vouching }; + Members::::insert(&member, record); MemberByIndex::::insert(member_count, &member); member_count.saturating_inc(); } + MemberCount::::put(member_count); // Migrate Payouts from: old::Payouts and raw info (needed since we can't query old chain state). past_payouts.sort(); @@ -222,12 +220,17 @@ pub fn from_original< } } + // Migrate SuspendedMembers from old::SuspendedMembers old::Strikes old::Vouching. + for who in old::SuspendedMembers::::iter_keys() { + let strikes = old::Strikes::::take(&who); + let vouching = old::Vouching::::take(&who); + let record = MemberRecord { index: 0, rank: 0, strikes, vouching }; + SuspendedMembers::::insert(&who, record); + } + // Any suspended candidates remaining are rejected. old::SuspendedCandidates::::remove_all(None); - // Any suspended members remaining are given the benefit of the doubt. - old::SuspendedMembers::::remove_all(None); - // We give the current defender the benefit of the doubt. old::Defender::::kill(); old::DefenderVotes::::remove_all(None); diff --git a/frame/society/src/mock.rs b/frame/society/src/mock.rs index b2c5f698be1f2..10404cbae1e0f 100644 --- a/frame/society/src/mock.rs +++ b/frame/society/src/mock.rs @@ -149,8 +149,8 @@ impl EnvBuilder { pallet_society::GenesisConfig:: { pot: self.pot, } - .assimilate_storage(&mut t) - .unwrap(); + .assimilate_storage(&mut t) + .unwrap(); let mut ext: sp_io::TestExternalities = t.into(); ext.execute_with(|| { if self.founded { @@ -158,7 +158,7 @@ impl EnvBuilder { assert!(Society::found_society(Origin::signed(1), 10, 10, 8, 2, 25, r).is_ok()); } let r = f(); - migrations::assert_internal_consistency(); + migrations::assert_internal_consistency::(); r }) } @@ -270,6 +270,12 @@ pub fn members() -> Vec { r } +pub fn membership() -> Vec<(u128, MemberRecord)> { + let mut r = Members::::iter().collect::>(); + r.sort_by_key(|x| x.0); + r +} + pub fn candidacies() -> Vec<(u128, Candidacy)> { let mut r = Candidates::::iter().collect::>(); r.sort_by_key(|x| x.0); diff --git a/frame/society/src/tests.rs b/frame/society/src/tests.rs index 5519d5d14284d..4c095b75ffaaf 100644 --- a/frame/society/src/tests.rs +++ b/frame/society/src/tests.rs @@ -19,10 +19,110 @@ use super::*; use mock::*; +use migrations::old; use frame_support::{assert_noop, assert_ok}; use sp_core::blake2_256; use sp_runtime::traits::BadOrigin; +use VouchingStatus::*; +use BidKind::*; + +#[test] +fn migration_works() { + EnvBuilder::new().founded(false).execute(|| { + use old::Vote::*; + + // Initialise the old storage items. + Founder::::put(10); + Head::::put(30); + old::Members::::put(vec![10, 20, 30]); + old::Vouching::::insert(30, Vouching); + old::Vouching::::insert(40, Banned); + old::Strikes::::insert(20, 1); + old::Strikes::::insert(30, 2); + old::Strikes::::insert(40, 5); + old::Payouts::::insert(20, vec![(1, 1)]); + old::Payouts::::insert(30, (0..=::MaxPayouts::get()).map(|i| (i as u64, i as u64)).collect::>()); + old::SuspendedMembers::::insert(40, true); + + old::Defender::::put(20); + old::DefenderVotes::::insert(10, Approve); + old::DefenderVotes::::insert(20, Approve); + old::DefenderVotes::::insert(30, Reject); + + old::SuspendedCandidates::::insert(50, (10, Deposit(100))); + + old::Candidates::::put(vec![ + Bid { who: 60, kind: Deposit(100), value: 200 }, + Bid { who: 70, kind: Vouch(30, 30), value: 100 }, + ]); + old::Votes::::insert(60, 10, Approve); + old::Votes::::insert(70, 10, Reject); + old::Votes::::insert(70, 20, Approve); + old::Votes::::insert(70, 30, Approve); + + let bids = (0..=::MaxBids::get()) + .map(|i| Bid { + who: 100u128 + i as u128, + kind: Deposit(20u64 + i as u64), + value: 10u64 + i as u64, + }) + .collect::>(); + old::Bids::::put(bids); + + migrations::from_original::(&mut[][..]); + migrations::assert_internal_consistency::(); + + assert_eq!(membership(), vec![ + (10, MemberRecord { rank: 0, strikes: 0, vouching: None, index: 0 }), + (20, MemberRecord { rank: 0, strikes: 1, vouching: None, index: 1 }), + (30, MemberRecord { rank: 0, strikes: 2, vouching: Some(Vouching), index: 2 }), + ]); + assert_eq!(Payouts::::get(10), PayoutRecord::default()); + let payouts = vec![(1, 1)].try_into().unwrap(); + assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts }); + let payouts = (0..::MaxPayouts::get()) + .map(|i| (i as u64, i as u64)) + .collect::>() + .try_into() + .unwrap(); + assert_eq!(Payouts::::get(30), PayoutRecord { paid: 0, payouts }); + assert_eq!(SuspendedMembers::::iter().collect::>(), vec![ + (40, MemberRecord { rank: 0, strikes: 5, vouching: Some(Banned), index: 0 }), + ]); + let bids: BoundedVec<_, ::MaxBids> = (0..::MaxBids::get()) + .map(|i| Bid { + who: 100u128 + i as u128, + kind: Deposit(20u64 + i as u64), + value: 10u64 + i as u64, + }) + .collect::>() + .try_into() + .unwrap(); + assert_eq!(Bids::::get(), bids); + assert_eq!(RoundCount::::get(), 0); + assert_eq!(candidacies(), vec![ + (60, Candidacy { + round: 0, + kind: Deposit(100), + bid: 200, + tally: Tally { approvals: 1, rejections: 0 }, + skeptic_struck: false, + }), + (70, Candidacy { + round: 0, + kind: Vouch(30, 30), + bid: 100, + tally: Tally { approvals: 2, rejections: 1 }, + skeptic_struck: false, + }), + ]); + assert_eq!(Votes::::get(60, 10), Some(Vote { approve: true, weight: 1})); + assert_eq!(Votes::::get(70, 10), Some(Vote { approve: false, weight: 1})); + assert_eq!(Votes::::get(70, 20), Some(Vote { approve: true, weight: 1})); + assert_eq!(Votes::::get(70, 30), Some(Vote { approve: true, weight: 1})); + }); +} #[test] fn founding_works() { @@ -91,7 +191,7 @@ fn basic_new_member_works() { // Rotate period every 4 blocks next_intake(); // 20 is now a candidate - assert_eq!(candidacies(), vec![(20, candidacy(1, 0, BidKind::Deposit(25), 0, 0))]); + assert_eq!(candidacies(), vec![(20, candidacy(1, 0, Deposit(25), 0, 0))]); // 10 (a member) can vote for the candidate assert_ok!(Society::vote(Origin::signed(10), 20, true)); conclude_intake(true, None); @@ -122,8 +222,8 @@ fn bidding_works() { assert_eq!( candidacies(), vec![ - (30, candidacy(1, 300, BidKind::Deposit(25), 0, 0)), - (40, candidacy(1, 400, BidKind::Deposit(25), 0, 0)), + (30, candidacy(1, 300, Deposit(25), 0, 0)), + (40, candidacy(1, 400, Deposit(25), 0, 0)), ] ); // A member votes for these candidates to join the society @@ -137,7 +237,7 @@ fn bidding_works() { assert_eq!(Balances::free_balance(Society::account_id()), 9_300); assert_eq!(Pot::::get(), 1_300); // Left over from the original bids is 50 who satisfies the condition of bid less than pot. - assert_eq!(candidacies(), vec![(50, candidacy(2, 500, BidKind::Deposit(25), 0, 0))]); + assert_eq!(candidacies(), vec![(50, candidacy(2, 500, Deposit(25), 0, 0))]); // 40, now a member, can vote for 50 assert_ok!(Society::vote(Origin::signed(40), 50, true)); conclude_intake(true, None); @@ -159,7 +259,7 @@ fn bidding_works() { // No payouts assert_eq!(Balances::free_balance(Society::account_id()), 8_800); // Candidate 60 now qualifies based on the increased pot size. - assert_eq!(candidacies(), vec![(60, candidacy(4, 1900, BidKind::Deposit(25), 0, 0))]); + assert_eq!(candidacies(), vec![(60, candidacy(4, 1900, Deposit(25), 0, 0))]); // Candidate 60 is voted in. assert_ok!(Society::vote(Origin::signed(50), 60, true)); conclude_intake(true, None); @@ -189,7 +289,7 @@ fn unbidding_works() { assert_eq!(Balances::reserved_balance(30), 0); // 20 wins candidacy next_intake(); - assert_eq!(candidacies(), vec![(20, candidacy(1, 1000, BidKind::Deposit(25), 0, 0))]); + assert_eq!(candidacies(), vec![(20, candidacy(1, 1000, Deposit(25), 0, 0))]); }); } @@ -217,7 +317,7 @@ fn non_voting_skeptic_is_punished() { assert_eq!(Members::::get(10).unwrap().strikes, 0); assert_ok!(Society::bid(Origin::signed(20), 0)); next_intake(); - assert_eq!(candidacies(), vec![(20, candidacy(1, 0, BidKind::Deposit(25), 0, 0))]); + assert_eq!(candidacies(), vec![(20, candidacy(1, 0, Deposit(25), 0, 0))]); conclude_intake(true, None); next_intake(); assert_eq!(members(), vec![10]); @@ -254,7 +354,7 @@ fn basic_new_member_reject_works() { assert_eq!(Balances::reserved_balance(20), 25); // Rotation Period next_intake(); - assert_eq!(candidacies(), vec![(20, candidacy(1, 0, BidKind::Deposit(25), 0, 0))]); + assert_eq!(candidacies(), vec![(20, candidacy(1, 0, Deposit(25), 0, 0))]); // We say no assert_ok!(Society::vote(Origin::signed(10), 20, false)); conclude_intake(true, None); @@ -369,10 +469,10 @@ fn suspended_candidate_rejected_works() { // Rotation Period next_intake(); assert_eq!(candidacies(), vec![ - (40, candidacy(1, 10, BidKind::Deposit(25), 0, 0)), - (50, candidacy(1, 10, BidKind::Deposit(25), 0, 0)), - (60, candidacy(1, 10, BidKind::Deposit(25), 0, 0)), - (70, candidacy(1, 10, BidKind::Deposit(25), 0, 0)), + (40, candidacy(1, 10, Deposit(25), 0, 0)), + (50, candidacy(1, 10, Deposit(25), 0, 0)), + (60, candidacy(1, 10, Deposit(25), 0, 0)), + (70, candidacy(1, 10, Deposit(25), 0, 0)), ]); // Split vote over all. @@ -447,10 +547,10 @@ fn unpaid_vouch_works() { Error::::AlreadyVouching ); // Vouching creates the right kind of bid - assert_eq!(Bids::::get().into_inner(), vec![bid(20, BidKind::Vouch(10, 100), 1000)]); + assert_eq!(Bids::::get().into_inner(), vec![bid(20, Vouch(10, 100), 1000)]); // Vouched user can become candidate next_intake(); - assert_eq!(candidacies(), vec![(20, candidacy(1, 1000, BidKind::Vouch(10, 100), 0, 0))]); + assert_eq!(candidacies(), vec![(20, candidacy(1, 1000, Vouch(10, 100), 0, 0))]); // Vote yes assert_ok!(Society::vote(Origin::signed(10), 20, true)); // Vouched user can win @@ -471,10 +571,10 @@ fn paid_vouch_works() { assert_ok!(Society::vouch(Origin::signed(20), 30, 1000, 100)); assert_eq!(Members::::get(20).unwrap().vouching, Some(VouchingStatus::Vouching)); - assert_eq!(Bids::::get().into_inner(), vec![bid(30, BidKind::Vouch(20, 100), 1000)]); + assert_eq!(Bids::::get().into_inner(), vec![bid(30, Vouch(20, 100), 1000)]); next_intake(); - assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20, 100), 0, 0))]); + assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, Vouch(20, 100), 0, 0))]); assert_ok!(Society::vote(Origin::signed(20), 30, true)); conclude_intake(true, None); @@ -495,10 +595,10 @@ fn voucher_cannot_win_more_than_bid() { // 20 vouches, but asks for more than the bid assert_ok!(Society::vouch(Origin::signed(20), 30, 100, 1000)); // Vouching creates the right kind of bid - assert_eq!(Bids::::get().into_inner(), vec![bid(30, BidKind::Vouch(20, 1000), 100)]); + assert_eq!(Bids::::get().into_inner(), vec![bid(30, Vouch(20, 1000), 100)]); // Vouched user can become candidate next_intake(); - assert_eq!(candidacies(), vec![(30, candidacy(1, 100, BidKind::Vouch(20, 1000), 0, 0))]); + assert_eq!(candidacies(), vec![(30, candidacy(1, 100, Vouch(20, 1000), 0, 0))]); // Vote yes assert_ok!(Society::vote(Origin::signed(20), 30, true)); // Vouched user can win @@ -519,7 +619,7 @@ fn unvouch_works() { // 10 vouches for 20 assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 0)); // 20 has a bid - assert_eq!(Bids::::get().into_inner(), vec![bid(20, BidKind::Vouch(10, 0), 100)]); + assert_eq!(Bids::::get().into_inner(), vec![bid(20, Vouch(10, 0), 100)]); // 10 is vouched assert_eq!(Members::::get(10).unwrap().vouching, Some(VouchingStatus::Vouching)); // 10 can unvouch @@ -532,7 +632,7 @@ fn unvouch_works() { // Cannot unvouch after they become candidate assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 0)); next_intake(); - assert_eq!(candidacies(), vec![(20, candidacy(1, 100, BidKind::Vouch(10, 0), 0, 0))]); + assert_eq!(candidacies(), vec![(20, candidacy(1, 100, Vouch(10, 0), 0, 0))]); assert_noop!(Society::unvouch(Origin::signed(10)), Error::::NotVouchingOnBidder); // 10 is still vouching until candidate is approved or rejected @@ -542,7 +642,7 @@ fn unvouch_works() { // But their pick doesn't resign (yet). conclude_intake(false, None); // Voting still happening and voucher cannot unvouch. - assert_eq!(candidacies(), vec![(20, candidacy(1, 100, BidKind::Vouch(10, 0), 0, 1))]); + assert_eq!(candidacies(), vec![(20, candidacy(1, 100, Vouch(10, 0), 0, 1))]); assert_eq!(Members::::get(10).unwrap().vouching, Some(VouchingStatus::Vouching)); // Candidate gives in and resigns. @@ -569,7 +669,7 @@ fn unbid_vouch_works() { // 10 vouches for 20 assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 0)); // 20 has a bid - assert_eq!(Bids::::get().into_inner(), vec![bid(20, BidKind::Vouch(10, 0), 100)]); + assert_eq!(Bids::::get().into_inner(), vec![bid(20, Vouch(10, 0), 100)]); // 10 is vouched assert_eq!(Members::::get(10).unwrap().vouching, Some(VouchingStatus::Vouching)); // 20 doesn't want to be a member and can unbid themselves. @@ -791,7 +891,7 @@ fn vouching_handles_removed_member_with_bid() { // Have that member vouch for a user assert_ok!(Society::vouch(Origin::signed(20), 30, 1000, 100)); // That user is now a bid and the member is vouching - assert_eq!(Bids::::get().into_inner(), vec![bid(30, BidKind::Vouch(20, 100), 1000)]); + assert_eq!(Bids::::get().into_inner(), vec![bid(30, Vouch(20, 100), 1000)]); assert_eq!(Members::::get(20).unwrap().vouching, Some(VouchingStatus::Vouching)); // Suspend that member assert_ok!(Society::suspend_member(&20)); @@ -811,18 +911,18 @@ fn vouching_handles_removed_member_with_candidate() { // Have that member vouch for a user assert_ok!(Society::vouch(Origin::signed(20), 30, 1000, 100)); // That user is now a bid and the member is vouching - assert_eq!(Bids::::get().into_inner(), vec![bid(30, BidKind::Vouch(20, 100), 1000)]); + assert_eq!(Bids::::get().into_inner(), vec![bid(30, Vouch(20, 100), 1000)]); assert_eq!(Members::::get(20).unwrap().vouching, Some(VouchingStatus::Vouching)); // Make that bid a candidate next_intake(); - assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20, 100), 0, 0))]); + assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, Vouch(20, 100), 0, 0))]); // Suspend that member assert_ok!(Society::suspend_member(&20)); assert_eq!(SuspendedMembers::::contains_key(20), true); // Nothing changes yet in the candidacy, though the member now forgets. - assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, BidKind::Vouch(20, 100), 0, 0))]); + assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, Vouch(20, 100), 0, 0))]); // Candidate wins assert_ok!(Society::vote(Origin::signed(10), 30, true)); @@ -879,9 +979,9 @@ fn max_bids_work() { // Length is 1000 assert_eq!(bids.len(), 10); // First bid is smallest number (100) - assert_eq!(bids[0], bid(100, BidKind::Deposit(25), 0)); + assert_eq!(bids[0], bid(100, Deposit(25), 0)); // Last bid is smallest number + 99 (1099) - assert_eq!(bids[9], bid(109, BidKind::Deposit(25), 9)); + assert_eq!(bids[9], bid(109, Deposit(25), 9)); }); } @@ -970,16 +1070,16 @@ fn zero_bid_works() { assert_eq!( candidacies(), vec![ - (30, candidacy(1, 0, BidKind::Deposit(25), 0, 0)), - (50, candidacy(1, 300, BidKind::Deposit(25), 0, 0)), - (60, candidacy(1, 400, BidKind::Deposit(25), 0, 0)), + (30, candidacy(1, 0, Deposit(25), 0, 0)), + (50, candidacy(1, 300, Deposit(25), 0, 0)), + (60, candidacy(1, 400, Deposit(25), 0, 0)), ] ); assert_eq!( Bids::::get(), vec![ - bid(20, BidKind::Deposit(25), 0), - bid(40, BidKind::Deposit(25), 0), + bid(20, Deposit(25), 0), + bid(40, Deposit(25), 0), ], ); // A member votes for these candidates to join the society @@ -1013,7 +1113,7 @@ fn bids_ordered_correctly() { for j in 0..5 { for i in 0..5 { - final_list.push(bid(100 + (i * 5 + j) as u128, BidKind::Deposit(25), j)); + final_list.push(bid(100 + (i * 5 + j) as u128, Deposit(25), j)); } } let max_bids: u32 = ::MaxBids::get(); diff --git a/frame/support/src/traits/storage.rs b/frame/support/src/traits/storage.rs index e484140cc2fd9..5bc0b470480e2 100644 --- a/frame/support/src/traits/storage.rs +++ b/frame/support/src/traits/storage.rs @@ -33,6 +33,12 @@ pub trait Instance: 'static { const INDEX: u8; } +// Dummy implementation for `()`. +impl Instance for () { + const PREFIX: &'static str = ""; + const INDEX: u8 = 0; +} + /// An instance of a storage in a pallet. /// /// Define an instance for an individual storage inside a pallet. From 27abee7e109398ac784519d7b36bea8bbb122f82 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 11 May 2022 14:40:11 +0100 Subject: [PATCH 21/43] Introduce lazy-cleanup and avoid unbounded prefix removal --- frame/society/src/lib.rs | 64 +++++++++++++++---- frame/society/src/migrations.rs | 4 +- frame/society/src/tests.rs | 50 +++++++++------ primitives/state-machine/src/ext.rs | 5 +- .../src/overlayed_changes/changeset.rs | 5 +- .../src/overlayed_changes/mod.rs | 4 +- 6 files changed, 90 insertions(+), 42 deletions(-) diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index d20dc81d15def..d806ee6b73308 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -469,7 +469,9 @@ pub enum OldVote { #[frame_support::pallet] pub mod pallet { - use super::*; + use sp_io::KillStorageResult; + +use super::*; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] @@ -593,6 +595,8 @@ pub mod pallet { AlreadyPunished, /// Funds are insufficient to pay off society debts. InsufficientFunds, + /// The candidate/defender has no stale votes to remove. + NoVotes, } #[pallet::event] @@ -728,14 +732,18 @@ pub mod pallet { OptionQuery, >; + /// The number of challenge rounds there have been. Used to identify stale DefenderVotes. + #[pallet::storage] + pub(super) type ChallengeRoundCount, I: 'static = ()> = StorageValue<_, RoundIndex, ValueQuery>; + /// The defending member currently being challenged, along with a running tally of votes. #[pallet::storage] pub(super) type Defending, I: 'static = ()> = StorageValue<_, (T::AccountId, T::AccountId, Tally)>; - /// Votes for the defender. + /// Votes for the defender, keyed by challenge round. #[pallet::storage] pub(super) type DefenderVotes, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, Vote>; + StorageDoubleMap<_, Twox64Concat, RoundIndex, Twox64Concat, T::AccountId, Vote>; #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { @@ -985,7 +993,8 @@ pub mod pallet { let mut defending = Defending::::get().ok_or(Error::::NoDefender)?; let record = Members::::get(&voter).ok_or(Error::::NotMember)?; - let first_time = DefenderVotes::::mutate(&voter, |v| { + let round = ChallengeRoundCount::::get(); + let first_time = DefenderVotes::::mutate(round, &voter, |v| { let first_time = v.is_none(); *v = Some(Self::do_vote(*v, approve, record.rank, &mut defending.2)); first_time @@ -1241,7 +1250,6 @@ pub mod pallet { ensure!(!candidacy.tally.clear_approval(), Error::::Approved); Self::check_skeptic(&candidate, &mut candidacy); Self::reject_candidate(&candidate, &candidacy.kind); - Votes::::remove_prefix(&candidate, None); Candidates::::remove(&candidate); Ok(Pays::No.into()) } @@ -1257,7 +1265,6 @@ pub mod pallet { Self::check_skeptic(&candidate, &mut candidacy); } Self::reject_candidate(&candidate, &candidacy.kind); - Votes::::remove_prefix(&candidate, None); Candidates::::remove(&candidate); Ok(Pays::No.into()) } @@ -1274,10 +1281,42 @@ pub mod pallet { ensure!(candidacy.tally.clear_rejection(), Error::::NotRejected); ensure!(RoundCount::::get() > candidacy.round + 1, Error::::TooEarly); Self::reject_candidate(&candidate, &candidacy.kind); - Votes::::remove_prefix(&candidate, None); Candidates::::remove(&candidate); Ok(Pays::No.into()) } + + /// Remove up to `max` stale votes for the given `candidate`. + /// + /// May be called by any Signed origin, but only after the candidate's candidacy is ended. + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn cleanup_candidacy(origin: OriginFor, candidate: T::AccountId, max: u32) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; + ensure!(!Candidates::::contains_key(&candidate), Error::::InProgress); + match Votes::::remove_prefix(&candidate, Some(max)) { + KillStorageResult::AllRemoved(0) => { + dbg!(Votes::::iter_prefix(&candidate).collect::>()); + Err(Error::::NoVotes.into()) + }, + _ => Ok(Pays::No.into()), + } + } + + /// Remove up to `max` stale votes for the defender in the given `challenge_round`. + /// + /// May be called by any Signed origin, but only after the challenge round is ended. + #[pallet::weight(T::BlockWeights::get().max_block / 10)] + pub fn cleanup_challenge( + origin: OriginFor, + challenge_round: RoundIndex, + max: u32, + ) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; + ensure!(challenge_round < ChallengeRoundCount::::get(), Error::::InProgress); + match DefenderVotes::::remove_prefix(challenge_round, Some(max)) { + KillStorageResult::AllRemoved(0) => Err(Error::::NoVotes.into()), + _ => Ok(Pays::No.into()), + } + } } } @@ -1381,6 +1420,7 @@ impl, I: 'static> Pallet { /// End the current challenge period and start a new one. fn rotate_challenge(rng: &mut impl RngCore) { let mut next_defender = None; + let mut round = ChallengeRoundCount::::get(); // End current defender rotation if let Some((defender, skeptic, tally)) = Defending::::get() { @@ -1392,7 +1432,7 @@ impl, I: 'static> Pallet { } // Check defender skeptic voted and that their vote was with the majority. - let skeptic_vote = DefenderVotes::::get(&skeptic); + let skeptic_vote = DefenderVotes::::get(round, &skeptic); match (skeptic_vote, tally.more_approvals(), tally.more_rejections()) { (None, _, _) | (Some(Vote { approve: true, .. }), false, true) @@ -1408,11 +1448,8 @@ impl, I: 'static> Pallet { } _ => {} } - - // Clean up all votes. - // NOTE: To do lazy removal of this we'll need to store the challenge round so that - // voting when there's a stale vote does not lead to incorrect arithmetic. - DefenderVotes::::remove_all(None); + round.saturating_inc(); + ChallengeRoundCount::::put(round); } // Avoid challenging if there's only two members since we never challenge the Head or @@ -1633,7 +1670,6 @@ impl, I: 'static> Pallet { let maturity = now + Self::lock_duration(MemberCount::::get()); Self::reward_bidder(&candidate, candidacy.bid, candidacy.kind, maturity); - Votes::::remove_prefix(&candidate, None); Candidates::::remove(&candidate); Ok(()) } diff --git a/frame/society/src/migrations.rs b/frame/society/src/migrations.rs index b34410f056d2c..b616de0077b1e 100644 --- a/frame/society/src/migrations.rs +++ b/frame/society/src/migrations.rs @@ -122,8 +122,8 @@ pub fn assert_internal_consistency, I: Instance + 'static>() { assert!(Votes::::get(k1, k2).is_some()); } // Check all defender votes are valid data. - for k in DefenderVotes::::iter_keys() { - assert!(DefenderVotes::::get(k).is_some()); + for (k1, k2) in DefenderVotes::::iter_keys() { + assert!(DefenderVotes::::get(k1, k2).is_some()); } // Check all candidates are valid data. for k in Candidates::::iter_keys() { diff --git a/frame/society/src/tests.rs b/frame/society/src/tests.rs index 4c095b75ffaaf..49ddf4bc264f8 100644 --- a/frame/society/src/tests.rs +++ b/frame/society/src/tests.rs @@ -762,10 +762,10 @@ fn challenges_work() { // Add some members place_members([20, 30, 40]); // Votes are empty - assert_eq!(DefenderVotes::::get(10), None); - assert_eq!(DefenderVotes::::get(20), None); - assert_eq!(DefenderVotes::::get(30), None); - assert_eq!(DefenderVotes::::get(40), None); + assert_eq!(DefenderVotes::::get(0, 10), None); + assert_eq!(DefenderVotes::::get(0, 20), None); + assert_eq!(DefenderVotes::::get(0, 30), None); + assert_eq!(DefenderVotes::::get(0, 40), None); // Check starting point assert_eq!(members(), vec![10, 20, 30, 40]); assert_eq!(Defending::::get(), None); @@ -779,6 +779,8 @@ fn challenges_work() { // If no one else votes, nothing happens next_challenge(); assert_eq!(members(), vec![10, 20, 30, 40]); + // Reset votes for last challenge + assert_ok!(Society::cleanup_challenge(Origin::signed(0), 0, 10)); // New challenge period assert_eq!(Defending::::get().unwrap().0, 30); // Non-member cannot vote @@ -792,11 +794,13 @@ fn challenges_work() { next_challenge(); // 30 survives assert_eq!(members(), vec![10, 20, 30, 40]); + // Reset votes for last challenge + assert_ok!(Society::cleanup_challenge(Origin::signed(0), 1, 10)); // Votes are reset - assert_eq!(DefenderVotes::::get(10), None); - assert_eq!(DefenderVotes::::get(20), None); - assert_eq!(DefenderVotes::::get(30), None); - assert_eq!(DefenderVotes::::get(40), None); + assert_eq!(DefenderVotes::::get(0, 10), None); + assert_eq!(DefenderVotes::::get(0, 20), None); + assert_eq!(DefenderVotes::::get(0, 30), None); + assert_eq!(DefenderVotes::::get(0, 40), None); // One more time assert_eq!(Defending::::get().unwrap().0, 30); @@ -805,8 +809,8 @@ fn challenges_work() { assert_ok!(Society::defender_vote(Origin::signed(20), true)); assert_ok!(Society::defender_vote(Origin::signed(30), false)); assert_ok!(Society::defender_vote(Origin::signed(40), false)); - next_challenge(); + next_challenge(); // 30 is suspended assert_eq!(members(), vec![10, 20, 40]); assert_eq!(SuspendedMembers::::get(30), Some(MemberRecord { @@ -815,14 +819,15 @@ fn challenges_work() { vouching: None, index: 2, })); - + // Reset votes for last challenge + assert_ok!(Society::cleanup_challenge(Origin::signed(0), 2, 10)); // New defender is chosen assert_eq!(Defending::::get().unwrap().0, 20); // Votes are reset - assert_eq!(DefenderVotes::::get(10), None); - assert_eq!(DefenderVotes::::get(20), None); - assert_eq!(DefenderVotes::::get(30), None); - assert_eq!(DefenderVotes::::get(40), None); + assert_eq!(DefenderVotes::::get(0, 10), None); + assert_eq!(DefenderVotes::::get(0, 20), None); + assert_eq!(DefenderVotes::::get(0, 30), None); + assert_eq!(DefenderVotes::::get(0, 40), None); }); } @@ -951,17 +956,20 @@ fn votes_are_working() { // You cannot vote for a non-candidate assert_noop!(Society::vote(Origin::signed(10), 50, true), Error::::NotCandidate); // Votes are stored - assert_eq!(>::get(30, 10), Some(Vote { approve: true, weight: 4 })); - assert_eq!(>::get(30, 20), Some(Vote { approve: true, weight: 1 })); - assert_eq!(>::get(40, 10), Some(Vote { approve: true, weight: 4 })); - assert_eq!(>::get(50, 10), None); + assert_eq!(Votes::::get(30, 10), Some(Vote { approve: true, weight: 4 })); + assert_eq!(Votes::::get(30, 20), Some(Vote { approve: true, weight: 1 })); + assert_eq!(Votes::::get(40, 10), Some(Vote { approve: true, weight: 4 })); + assert_eq!(Votes::::get(50, 10), None); conclude_intake(false, None); + // Cleanup the candidacy + assert_ok!(Society::cleanup_candidacy(Origin::signed(0), 30, 10)); + assert_ok!(Society::cleanup_candidacy(Origin::signed(0), 40, 10)); // Candidates become members after a period rotation assert_eq!(members(), vec![10, 20, 30, 40]); // Votes are cleaned up - assert_eq!(>::get(30, 10), None); - assert_eq!(>::get(30, 20), None); - assert_eq!(>::get(40, 10), None); + assert_eq!(Votes::::get(30, 10), None); + assert_eq!(Votes::::get(30, 20), None); + assert_eq!(Votes::::get(40, 10), None); }); } diff --git a/primitives/state-machine/src/ext.rs b/primitives/state-machine/src/ext.rs index e33569e2a1f67..2d446b9c3b9a0 100644 --- a/primitives/state-machine/src/ext.rs +++ b/primitives/state-machine/src/ext.rs @@ -464,8 +464,9 @@ where } self.mark_dirty(); - self.overlay.clear_prefix(prefix); - self.limit_remove_from_backend(None, Some(prefix), limit) + let overlay_count = self.overlay.clear_prefix(prefix); + let (all, count) = self.limit_remove_from_backend(None, Some(prefix), limit); + (all, count + overlay_count) } fn clear_child_prefix( diff --git a/primitives/state-machine/src/overlayed_changes/changeset.rs b/primitives/state-machine/src/overlayed_changes/changeset.rs index 9e7f6ffeddfd7..c0f9d60f75539 100644 --- a/primitives/state-machine/src/overlayed_changes/changeset.rs +++ b/primitives/state-machine/src/overlayed_changes/changeset.rs @@ -410,10 +410,13 @@ impl OverlayedChangeSet { &mut self, predicate: impl Fn(&[u8], &OverlayedValue) -> bool, at_extrinsic: Option, - ) { + ) -> u32 { + let mut count = 0; for (key, val) in self.changes.iter_mut().filter(|(k, v)| predicate(k, v)) { val.set(None, insert_dirty(&mut self.dirty_keys, key.clone()), at_extrinsic); + count += 1; } + count } /// Get the iterator over all changes that follow the supplied `key`. diff --git a/primitives/state-machine/src/overlayed_changes/mod.rs b/primitives/state-machine/src/overlayed_changes/mod.rs index 2161b343711c9..2bb09e98bff62 100644 --- a/primitives/state-machine/src/overlayed_changes/mod.rs +++ b/primitives/state-machine/src/overlayed_changes/mod.rs @@ -315,8 +315,8 @@ impl OverlayedChanges { /// Removes all key-value pairs which keys share the given prefix. /// /// Can be rolled back or committed when called inside a transaction. - pub(crate) fn clear_prefix(&mut self, prefix: &[u8]) { - self.top.clear_where(|key, _| key.starts_with(prefix), self.extrinsic_index()); + pub(crate) fn clear_prefix(&mut self, prefix: &[u8]) -> u32 { + self.top.clear_where(|key, _| key.starts_with(prefix), self.extrinsic_index()) } /// Removes all key-value pairs which keys share the given prefix. From adccb6674406d6892aff37455b7c949ab1a15c9c Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 18 May 2022 16:16:21 +0200 Subject: [PATCH 22/43] Fixes --- frame/support/src/storage/types/map.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/support/src/storage/types/map.rs b/frame/support/src/storage/types/map.rs index 80250dbc60796..98e34eaf4f3c6 100644 --- a/frame/support/src/storage/types/map.rs +++ b/frame/support/src/storage/types/map.rs @@ -351,7 +351,7 @@ where /// order. /// /// If you alter the map while doing this, you'll get undefined results. - pub fn iter_keys_from_from(starting_key: impl EncodeLike) -> crate::storage::KeyPrefixIterator { + pub fn iter_keys_from_key(starting_key: impl EncodeLike) -> crate::storage::KeyPrefixIterator { Self::iter_keys_from(Self::hashed_key_for(starting_key)) } From aefd6db21a52d15aad55ae074ed32466c52af71d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 1 Jun 2022 10:21:50 +0100 Subject: [PATCH 23/43] Fixes --- frame/society/src/lib.rs | 23 ++++++++++------- frame/society/src/migrations.rs | 44 +++++++++++++++---------------- frame/system/src/lib.rs | 2 +- primitives/core/src/crypto.rs | 46 +++++++++++++++++++++++++++++++++ primitives/core/src/ecdsa.rs | 2 +- primitives/core/src/ed25519.rs | 3 ++- primitives/core/src/sr25519.rs | 4 +-- primitives/core/src/traits.rs | 46 --------------------------------- primitives/runtime/src/lib.rs | 3 +-- 9 files changed, 88 insertions(+), 85 deletions(-) diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index ab1223c4b47a6..987a5bcdf8717 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -469,9 +469,7 @@ pub enum OldVote { #[frame_support::pallet] pub mod pallet { - use sp_io::KillStorageResult; - -use super::*; + use super::*; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] @@ -723,6 +721,14 @@ use super::*; OptionQuery, >; + /// Clear-cursor for Vote, map from Candidate -> (Maybe) Cursor. + #[pallet::storage] + pub type VoteClearCursor, I: 'static = ()> = StorageMap<_, + Twox64Concat, + T::AccountId, + BoundedVec>, + >; + /// At the end of the claim period, this contains the most recently approved members (along with /// their bid and round ID) who is from the most recent round with the lowest bid. They will /// become the new `Head`. @@ -1297,13 +1303,10 @@ use super::*; pub fn cleanup_candidacy(origin: OriginFor, candidate: T::AccountId, max: u32) -> DispatchResultWithPostInfo { ensure_signed(origin)?; ensure!(!Candidates::::contains_key(&candidate), Error::::InProgress); - match Votes::::remove_prefix(&candidate, Some(max)) { - KillStorageResult::AllRemoved(0) => { - dbg!(Votes::::iter_prefix(&candidate).collect::>()); - Err(Error::::NoVotes.into()) - }, - _ => Ok(Pays::No.into()), - } + let maybe_cursor = VoteClearCursor::::get(&candidate); + let r = Votes::::clear_prefix(&candidate, Some(max), maybe_cursor.as_ref().map(|x| &x[..])); + VoteClearCursor::::set(&candidate, r.maybe_cursor.map(BoundedVec::truncate_from)); + Ok(if r.loops == 0 { Pays::Yes } else { Pays::No }.into()) } /// Remove up to `max` stale votes for the defender in the given `challenge_round`. diff --git a/frame/society/src/migrations.rs b/frame/society/src/migrations.rs index b616de0077b1e..eb5a8f2019167 100644 --- a/frame/society/src/migrations.rs +++ b/frame/society/src/migrations.rs @@ -35,61 +35,61 @@ pub(crate) mod old { Approve, } - #[storage_alias] pub type Bids = Value< - pallet::Pallet, I: Instance + 'static>, + #[storage_alias] pub type Bids, I: 'static> = StorageValue< + Pallet, Vec::AccountId, BalanceOf>>, ValueQuery, >; - #[storage_alias] pub type Candidates = Value< - Pallet, I: Instance + 'static>, + #[storage_alias] pub type Candidates, I: 'static> = StorageValue< + Pallet, Vec::AccountId, BalanceOf>>, ValueQuery, >; - #[storage_alias] pub type Votes = DoubleMap< - Pallet, I: Instance + 'static>, + #[storage_alias] pub type Votes, I: 'static> = StorageDoubleMap< + Pallet, Twox64Concat, ::AccountId, Twox64Concat, ::AccountId, Vote, >; - #[storage_alias] pub type SuspendedCandidates = Map< - Pallet, I: Instance + 'static>, + #[storage_alias] pub type SuspendedCandidates, I: 'static> = StorageMap< + Pallet, Twox64Concat, ::AccountId, (BalanceOf, BidKind<::AccountId, BalanceOf>), >; - #[storage_alias] pub type Members = Value< - Pallet, I: Instance + 'static>, + #[storage_alias] pub type Members, I: 'static> = StorageValue< + Pallet, Vec<::AccountId>, ValueQuery, >; - #[storage_alias] pub type Vouching = Map< - Pallet, I: Instance + 'static>, + #[storage_alias] pub type Vouching, I: 'static> = StorageMap< + Pallet, Twox64Concat, ::AccountId, VouchingStatus, >; - #[storage_alias] pub type Strikes = Map< - Pallet, I: Instance + 'static>, + #[storage_alias] pub type Strikes, I: 'static> = StorageMap< + Pallet, Twox64Concat, ::AccountId, StrikeCount, ValueQuery, >; - #[storage_alias] pub type Payouts = Map< - Pallet, I: Instance + 'static>, + #[storage_alias] pub type Payouts, I: 'static> = StorageMap< + Pallet, Twox64Concat, ::AccountId, Vec<(::BlockNumber, BalanceOf)>, ValueQuery, >; - #[storage_alias] pub type SuspendedMembers = Map< - Pallet, I: Instance + 'static>, + #[storage_alias] pub type SuspendedMembers, I: 'static> = StorageMap< + Pallet, Twox64Concat, ::AccountId, bool, ValueQuery, >; - #[storage_alias] pub type Defender = Value< - Pallet, I: Instance + 'static>, + #[storage_alias] pub type Defender, I: 'static> = StorageValue< + Pallet, ::AccountId, >; - #[storage_alias] pub type DefenderVotes = Map< - Pallet, I: Instance + 'static>, + #[storage_alias] pub type DefenderVotes, I: 'static> = StorageMap< + Pallet, Twox64Concat, ::AccountId, Vote, >; diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 38109294ec90d..cbbaee75149e9 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -197,7 +197,7 @@ impl, MaxOverflow: Get> ConsumerLimits for (MaxNormal, pub mod pallet { use crate::{self as frame_system, pallet_prelude::*, *}; use frame_support::pallet_prelude::*; - use sp_core::traits::FromEntropy; + use sp_core::crypto::FromEntropy; /// System configuration trait. Implemented by runtime. #[pallet::config] diff --git a/primitives/core/src/crypto.rs b/primitives/core/src/crypto.rs index 80b44449dbac1..8904d8cc2cc49 100644 --- a/primitives/core/src/crypto.rs +++ b/primitives/core/src/crypto.rs @@ -1123,6 +1123,52 @@ pub mod key_types { pub const DUMMY: KeyTypeId = KeyTypeId(*b"dumy"); } +/// Create random values of `Self` given a stream of entropy. +pub trait FromEntropy: Sized { + /// Create a random value of `Self` given a stream of random bytes on `input`. May only fail if + /// `input` has an error. + fn from_entropy(input: &mut impl codec::Input) -> Result; +} + +impl FromEntropy for bool { + fn from_entropy(input: &mut impl codec::Input) -> Result { + Ok(input.read_byte()? % 2 == 1) + } +} + +macro_rules! impl_from_entropy { + ($type:ty , $( $others:tt )*) => { + impl_from_entropy!($type); + impl_from_entropy!($( $others )*); + }; + ($type:ty) => { + impl FromEntropy for $type { + fn from_entropy(input: &mut impl codec::Input) -> Result { + ::decode(input) + } + } + } +} + +macro_rules! impl_from_entropy_base { + ($type:ty , $( $others:tt )*) => { + impl_from_entropy_base!($type); + impl_from_entropy_base!($( $others )*); + }; + ($type:ty) => { + impl_from_entropy!($type, + [$type; 1], [$type; 2], [$type; 3], [$type; 4], [$type; 5], [$type; 6], [$type; 7], [$type; 8], + [$type; 9], [$type; 10], [$type; 11], [$type; 12], [$type; 13], [$type; 14], [$type; 15], [$type; 16], + [$type; 17], [$type; 18], [$type; 19], [$type; 20], [$type; 21], [$type; 22], [$type; 23], [$type; 24], + [$type; 25], [$type; 26], [$type; 27], [$type; 28], [$type; 29], [$type; 30], [$type; 31], [$type; 32], + [$type; 36], [$type; 40], [$type; 44], [$type; 48], [$type; 56], [$type; 64], [$type; 72], [$type; 80], + [$type; 96], [$type; 112], [$type; 128], [$type; 160], [$type; 192], [$type; 224], [$type; 256] + ); + } +} + +impl_from_entropy_base!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); + #[cfg(test)] mod tests { use super::*; diff --git a/primitives/core/src/ecdsa.rs b/primitives/core/src/ecdsa.rs index e37b024bf4cce..b07df85607bc5 100644 --- a/primitives/core/src/ecdsa.rs +++ b/primitives/core/src/ecdsa.rs @@ -74,7 +74,7 @@ type Seed = [u8; 32]; )] pub struct Public(pub [u8; 33]); -impl crate::traits::FromEntropy for Public { +impl crate::crypto::FromEntropy for Public { fn from_entropy(input: &mut impl codec::Input) -> Result { let mut result = Self([0u8; 33]); input.read(&mut result.0[..])?; diff --git a/primitives/core/src/ed25519.rs b/primitives/core/src/ed25519.rs index 9a34fdea73ebe..5dfb94e1cc876 100644 --- a/primitives/core/src/ed25519.rs +++ b/primitives/core/src/ed25519.rs @@ -33,6 +33,7 @@ use scale_info::TypeInfo; use crate::crypto::Ss58Codec; use crate::crypto::{ CryptoType, CryptoTypeId, CryptoTypePublicPair, Derive, Public as TraitPublic, UncheckedFrom, + FromEntropy, }; #[cfg(feature = "full_crypto")] use crate::crypto::{DeriveJunction, Pair as TraitPair, SecretStringError}; @@ -88,7 +89,7 @@ impl Clone for Pair { } } -impl crate::traits::FromEntropy for Public { +impl FromEntropy for Public { fn from_entropy(input: &mut impl codec::Input) -> Result { let mut result = Self([0u8; 32]); input.read(&mut result.0[..])?; diff --git a/primitives/core/src/sr25519.rs b/primitives/core/src/sr25519.rs index 251776c5e8118..72b1ccc9631a7 100644 --- a/primitives/core/src/sr25519.rs +++ b/primitives/core/src/sr25519.rs @@ -40,9 +40,9 @@ use substrate_bip39::mini_secret_from_entropy; use crate::{ crypto::{ ByteArray, CryptoType, CryptoTypeId, CryptoTypePublicPair, Derive, Public as TraitPublic, - UncheckedFrom, + UncheckedFrom, FromEntropy, }, - hash::{H256, H512}, traits::FromEntropy, + hash::{H256, H512}, }; use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; diff --git a/primitives/core/src/traits.rs b/primitives/core/src/traits.rs index fc4bd908874d3..80e8963a2909d 100644 --- a/primitives/core/src/traits.rs +++ b/primitives/core/src/traits.rs @@ -278,49 +278,3 @@ impl SpawnEssentialNamed for Box { (**self).spawn_essential(name, group, future) } } - -/// Create random values of `Self` given a stream of entropy. -pub trait FromEntropy: Sized { - /// Create a random value of `Self` given a stream of random bytes on `input`. May only fail if - /// `input` has an error. - fn from_entropy(input: &mut impl codec::Input) -> Result; -} - -impl FromEntropy for bool { - fn from_entropy(input: &mut impl codec::Input) -> Result { - Ok(input.read_byte()? % 2 == 1) - } -} - -macro_rules! impl_from_entropy { - ($type:ty , $( $others:tt )*) => { - impl_from_entropy!($type); - impl_from_entropy!($( $others )*); - }; - ($type:ty) => { - impl FromEntropy for $type { - fn from_entropy(input: &mut impl codec::Input) -> Result { - ::decode(input) - } - } - } -} - -macro_rules! impl_from_entropy_base { - ($type:ty , $( $others:tt )*) => { - impl_from_entropy_base!($type); - impl_from_entropy_base!($( $others )*); - }; - ($type:ty) => { - impl_from_entropy!($type, - [$type; 1], [$type; 2], [$type; 3], [$type; 4], [$type; 5], [$type; 6], [$type; 7], [$type; 8], - [$type; 9], [$type; 10], [$type; 11], [$type; 12], [$type; 13], [$type; 14], [$type; 15], [$type; 16], - [$type; 17], [$type; 18], [$type; 19], [$type; 20], [$type; 21], [$type; 22], [$type; 23], [$type; 24], - [$type; 25], [$type; 26], [$type; 27], [$type; 28], [$type; 29], [$type; 30], [$type; 31], [$type; 32], - [$type; 36], [$type; 40], [$type; 44], [$type; 48], [$type; 56], [$type; 64], [$type; 72], [$type; 80], - [$type; 96], [$type; 112], [$type; 128], [$type; 160], [$type; 192], [$type; 224], [$type; 256] - ); - } -} - -impl_from_entropy_base!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index 11af6b37d9a67..22d1dc1e2bbef 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -45,11 +45,10 @@ pub use sp_core::storage::StateVersion; pub use sp_core::storage::{Storage, StorageChild}; use sp_core::{ - crypto::{self, ByteArray}, + crypto::{self, ByteArray, FromEntropy}, ecdsa, ed25519, hash::{H256, H512}, sr25519, - traits::FromEntropy, }; use sp_std::prelude::*; From be940c1742174a15efaf5a1ac0f88b690f173ece Mon Sep 17 00:00:00 2001 From: Artur Gontijo Date: Sat, 3 Dec 2022 13:46:31 -0300 Subject: [PATCH 24/43] [WIP][Society] Adding benchmarking to the v2. (#11776) * [Society] Adding benchmarking to the v2. * [Society] Code review. * [Society] Better code. * Using clear() + clear_prefix() and adding more tests. * Benchmarking again... --- Cargo.lock | 1 + bin/node/cli/src/chain_spec.rs | 10 +- bin/node/runtime/src/lib.rs | 24 +- bin/node/testing/src/genesis.rs | 2 +- frame/society/Cargo.toml | 4 + frame/society/src/benchmarking.rs | 339 ++++++++++++++++++++++ frame/society/src/lib.rs | 112 ++++---- frame/society/src/migrations.rs | 4 +- frame/society/src/mock.rs | 13 +- frame/society/src/tests.rs | 68 +++++ frame/society/src/weights.rs | 454 ++++++++++++++++++++++++++++++ frame/system/src/lib.rs | 4 +- 12 files changed, 955 insertions(+), 80 deletions(-) create mode 100644 frame/society/src/benchmarking.rs create mode 100644 frame/society/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 26f6d41b46326..00fd0ddddb1ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6139,6 +6139,7 @@ dependencies = [ name = "pallet-society" version = "4.0.0-dev" dependencies = [ + "frame-benchmarking", "frame-support", "frame-support-test", "frame-system", diff --git a/bin/node/cli/src/chain_spec.rs b/bin/node/cli/src/chain_spec.rs index 221c229876275..7faecb6158f75 100644 --- a/bin/node/cli/src/chain_spec.rs +++ b/bin/node/cli/src/chain_spec.rs @@ -349,15 +349,7 @@ pub fn testnet_genesis( grandpa: GrandpaConfig { authorities: vec![] }, technical_membership: Default::default(), treasury: Default::default(), - society: SocietyConfig { - members: endowed_accounts - .iter() - .take((num_endowed_accounts + 1) / 2) - .cloned() - .collect(), - pot: 0, - max_members: 999, - }, + society: SocietyConfig { pot: 0 }, vesting: Default::default(), assets: Default::default(), gilt: Default::default(), diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 3c0202d683787..2420e8ac9656f 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1313,14 +1313,14 @@ impl pallet_recovery::Config for Runtime { } parameter_types! { - pub const CandidateDeposit: Balance = 10 * DOLLARS; - pub const WrongSideDeduction: Balance = 2 * DOLLARS; - pub const MaxStrikes: u32 = 10; - pub const RotationPeriod: BlockNumber = 80 * HOURS; + pub const GraceStrikes: u32 = 10; + pub const SocietyVotingPeriod: BlockNumber = 80 * HOURS; + pub const ClaimPeriod: BlockNumber = 80 * HOURS; pub const PeriodSpend: Balance = 500 * DOLLARS; pub const MaxLockDuration: BlockNumber = 36 * 30 * DAYS; pub const ChallengePeriod: BlockNumber = 7 * DAYS; - pub const MaxCandidateIntake: u32 = 10; + pub const MaxPayouts: u32 = 10; + pub const MaxBids: u32 = 10; pub const SocietyPalletId: PalletId = PalletId(*b"py/socie"); } @@ -1329,18 +1329,17 @@ impl pallet_society::Config for Runtime { type PalletId = SocietyPalletId; type Currency = Balances; type Randomness = RandomnessCollectiveFlip; - type CandidateDeposit = CandidateDeposit; - type WrongSideDeduction = WrongSideDeduction; - type MaxStrikes = MaxStrikes; + type GraceStrikes = GraceStrikes; type PeriodSpend = PeriodSpend; - type MembershipChanged = (); - type RotationPeriod = RotationPeriod; + type VotingPeriod = SocietyVotingPeriod; + type ClaimPeriod = ClaimPeriod; type MaxLockDuration = MaxLockDuration; type FounderSetOrigin = pallet_collective::EnsureProportionMoreThan; - type JudgementOrigin = pallet_society::EnsureFounder; - type MaxCandidateIntake = MaxCandidateIntake; type ChallengePeriod = ChallengePeriod; + type MaxPayouts = MaxPayouts; + type MaxBids = MaxBids; + type WeightInfo = pallet_society::weights::SubstrateWeight; } parameter_types! { @@ -1658,6 +1657,7 @@ mod benches { [pallet_remark, Remark] [pallet_scheduler, Scheduler] [pallet_session, SessionBench::] + [pallet_society, Society] [pallet_staking, Staking] [pallet_state_trie_migration, StateTrieMigration] [frame_system, SystemBench::] diff --git a/bin/node/testing/src/genesis.rs b/bin/node/testing/src/genesis.rs index 73e7ea50261d3..8cdf9723afd42 100644 --- a/bin/node/testing/src/genesis.rs +++ b/bin/node/testing/src/genesis.rs @@ -86,7 +86,7 @@ pub fn config_endowed(code: Option<&[u8]>, extra_endowed: Vec) -> Gen elections: Default::default(), sudo: Default::default(), treasury: Default::default(), - society: SocietyConfig { members: vec![alice(), bob()], pot: 0, max_members: 999 }, + society: SocietyConfig { pot: 0 }, vesting: Default::default(), assets: Default::default(), gilt: Default::default(), diff --git a/frame/society/Cargo.toml b/frame/society/Cargo.toml index 2c49b56a0e1cf..0edf3e2bcb51b 100644 --- a/frame/society/Cargo.toml +++ b/frame/society/Cargo.toml @@ -18,7 +18,9 @@ rand_chacha = { version = "0.2", default-features = false } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../primitives/arithmetic" } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } hex-literal = "0.3.4" @@ -39,9 +41,11 @@ std = [ "scale-info/std", "sp-runtime/std", "sp-std/std", + "sp-io/std", ] runtime-benchmarks = [ "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/society/src/benchmarking.rs b/frame/society/src/benchmarking.rs new file mode 100644 index 0000000000000..4cd4abe5952dc --- /dev/null +++ b/frame/society/src/benchmarking.rs @@ -0,0 +1,339 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Society pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; + +use frame_benchmarking::{account, benchmarks_instance_pallet, whitelisted_caller}; +use frame_system::RawOrigin; + +use sp_runtime::traits::Bounded; + +use crate::Pallet as Society; + +fn mock_balance_deposit, I: 'static>() -> BalanceOf { + T::Currency::minimum_balance().saturating_mul(1_000u32.into()) +} + +// Set up Society +fn setup_society, I: 'static>() -> Result { + let origin = T::FounderSetOrigin::successful_origin(); + let founder: T::AccountId = account("founder", 0, 0); + let max_members = 5u32; + let max_intake = 3u32; + let max_strikes = 3u32; + Society::::found_society( + origin, + founder.clone(), + max_members, + max_intake, + max_strikes, + mock_balance_deposit::(), + b"benchmarking-society".to_vec(), + )?; + Ok(founder) +} + +fn add_candidate, I: 'static>( + name: &'static str, + tally: Tally, + skeptic_struck: bool, +) -> T::AccountId { + let candidate: T::AccountId = account(name, 0, 0); + T::Currency::make_free_balance_be(&candidate, BalanceOf::::max_value()); + let candidacy = Candidacy { + round: RoundCount::::get(), + kind: BidKind::Deposit(mock_balance_deposit::()), + bid: 0u32.into(), + tally, + skeptic_struck, + }; + Candidates::::insert(&candidate, &candidacy); + candidate +} + +fn increment_round, I: 'static>() { + let mut round_count = RoundCount::::get(); + round_count.saturating_inc(); + RoundCount::::put(round_count); +} + +benchmarks_instance_pallet! { + bid { + let founder = setup_society::()?; + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + }: _(RawOrigin::Signed(caller.clone()), 10u32.into()) + verify { + let first_bid: Bid> = Bid { + who: caller.clone(), + kind: BidKind::Deposit(mock_balance_deposit::()), + value: 10u32.into(), + }; + assert_eq!(Bids::::get(), vec![first_bid]); + } + + unbid { + let founder = setup_society::()?; + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + let mut bids = Bids::::get(); + Society::::insert_bid(&mut bids, &caller, 10u32.into(), BidKind::Deposit(mock_balance_deposit::())); + Bids::::put(bids); + }: _(RawOrigin::Signed(caller.clone())) + verify { + assert_eq!(Bids::::get(), vec![]); + } + + vouch { + let founder = setup_society::()?; + let caller: T::AccountId = whitelisted_caller(); + let vouched: T::AccountId = account("vouched", 0, 0); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + let _ = Society::::insert_member(&caller, 1u32.into()); + }: _(RawOrigin::Signed(caller.clone()), vouched.clone(), 0u32.into(), 0u32.into()) + verify { + let bids = Bids::::get(); + let vouched_bid: Bid> = Bid { + who: vouched.clone(), + kind: BidKind::Vouch(caller.clone(), 0u32.into()), + value: 0u32.into(), + }; + assert_eq!(bids, vec![vouched_bid]); + } + + unvouch { + let founder = setup_society::()?; + let caller: T::AccountId = whitelisted_caller(); + let vouched: T::AccountId = account("vouched", 0, 0); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + let mut bids = Bids::::get(); + Society::::insert_bid(&mut bids, &caller, 10u32.into(), BidKind::Vouch(caller.clone(), 0u32.into())); + Bids::::put(bids); + }: _(RawOrigin::Signed(caller.clone())) + verify { + assert_eq!(Bids::::get(), vec![]); + } + + vote { + let founder = setup_society::()?; + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + let _ = Society::::insert_member(&caller, 1u32.into()); + let candidate = add_candidate::("candidate", Default::default(), false); + let candidate_lookup: ::Source = T::Lookup::unlookup(candidate.clone()); + }: _(RawOrigin::Signed(caller.clone()), candidate_lookup, true) + verify { + let maybe_vote: Vote = >::get(candidate.clone(), caller).unwrap(); + assert_eq!(maybe_vote.approve, true); + } + + defender_vote { + let founder = setup_society::()?; + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + let _ = Society::::insert_member(&caller, 1u32.into()); + let defender: T::AccountId = account("defender", 0, 0); + Defending::::put((defender, caller.clone(), Tally::default())); + }: _(RawOrigin::Signed(caller.clone()), false) + verify { + let round = RoundCount::::get(); + let skeptic_vote: Vote = DefenderVotes::::get(round, &caller).unwrap(); + assert_eq!(skeptic_vote.approve, false); + } + + payout { + let founder = setup_society::()?; + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, mock_balance_deposit::()); + let _ = Society::::insert_member(&caller, 0u32.into()); + T::Currency::make_free_balance_be(&Society::::payouts(), BalanceOf::::max_value()); + let mut pot = >::get(); + pot = pot.saturating_add(BalanceOf::::max_value()); + Pot::::put(&pot); + Society::::bump_payout(&caller, 0u32.into(), 1u32.into()); + }: _(RawOrigin::Signed(caller.clone())) + verify { + let record = Payouts::::get(caller); + assert!(record.payouts.is_empty()); + } + + waive_repay { + let founder = setup_society::()?; + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + let _ = Society::::insert_member(&caller, 0u32.into()); + T::Currency::make_free_balance_be(&Society::::payouts(), BalanceOf::::max_value()); + Society::::bump_payout(&caller, 0u32.into(), 1u32.into()); + }: _(RawOrigin::Signed(caller.clone()), 1u32.into()) + verify { + let record = Payouts::::get(caller); + assert!(record.payouts.is_empty()); + } + + found_society { + let founder: T::AccountId = whitelisted_caller(); + let can_found = T::FounderSetOrigin::successful_origin(); + }: _(can_found, founder.clone(), 5, 3, 3, mock_balance_deposit::(), b"benchmarking-society".to_vec()) + verify { + assert_eq!(Founder::::get(), Some(founder.clone())); + } + + dissolve { + let founder = setup_society::()?; + let members_and_candidates = vec![("m1", "c1"), ("m2", "c2"), ("m3", "c3"), ("m4", "c4")]; + let members_count = members_and_candidates.clone().len() as u32; + for (m, c) in members_and_candidates { + let member: T::AccountId = account(m, 0, 0); + let _ = Society::::insert_member(&member, 100u32.into()); + let candidate = add_candidate::(c, Tally { approvals: 1u32.into(), rejections: 1u32.into() }, false); + let candidate_lookup: ::Source = T::Lookup::unlookup(candidate); + let _ = Society::::vote(RawOrigin::Signed(member).into(), candidate_lookup, true); + } + // Leaving only Founder member. + MemberCount::::mutate(|i| { i.saturating_reduce(members_count) }); + }: _(RawOrigin::Signed(founder)) + verify { + assert_eq!(Founder::::get(), None); + } + + judge_suspended_member { + let founder = setup_society::()?; + let caller: T::AccountId = whitelisted_caller(); + let _ = Society::::insert_member(&caller, 0u32.into()); + let _ = Society::::suspend_member(&caller); + }: _(RawOrigin::Signed(founder), caller.clone(), false) + verify { + assert_eq!(SuspendedMembers::::contains_key(&caller), false); + } + + set_parameters { + let founder = setup_society::()?; + let max_members = 10u32; + let max_intake = 10u32; + let max_strikes = 10u32; + let candidate_deposit: BalanceOf = 10u32.into(); + let params = GroupParams { max_members, max_intake, max_strikes, candidate_deposit }; + }: _(RawOrigin::Signed(founder), max_members, max_intake, max_strikes, candidate_deposit) + verify { + assert_eq!(Parameters::::get(), Some(params)); + } + + punish_skeptic { + let founder = setup_society::()?; + let candidate = add_candidate::("candidate", Default::default(), false); + let skeptic: T::AccountId = account("skeptic", 0, 0); + let _ = Society::::insert_member(&skeptic, 0u32.into()); + Skeptic::::put(&skeptic); + frame_system::Pallet::::set_block_number(T::VotingPeriod::get() + 1u32.into()); + }: _(RawOrigin::Signed(candidate.clone())) + verify { + let candidacy = Candidates::::get(&candidate).unwrap(); + assert_eq!(candidacy.skeptic_struck, true); + } + + claim_membership { + let founder = setup_society::()?; + let candidate = add_candidate::("candidate", Tally { approvals: 3u32.into(), rejections: 0u32.into() }, false); + increment_round::(); + }: _(RawOrigin::Signed(candidate.clone())) + verify { + assert!(!Candidates::::contains_key(&candidate)); + assert!(Members::::contains_key(&candidate)); + } + + bestow_membership { + let founder = setup_society::()?; + let candidate = add_candidate::("candidate", Tally { approvals: 3u32.into(), rejections: 1u32.into() }, false); + increment_round::(); + }: _(RawOrigin::Signed(founder), candidate.clone()) + verify { + assert!(!Candidates::::contains_key(&candidate)); + assert!(Members::::contains_key(&candidate)); + } + + kick_candidate { + let founder = setup_society::()?; + let candidate = add_candidate::("candidate", Tally { approvals: 1u32.into(), rejections: 1u32.into() }, false); + increment_round::(); + }: _(RawOrigin::Signed(founder), candidate.clone()) + verify { + assert!(!Candidates::::contains_key(&candidate)); + } + + resign_candidacy { + let founder = setup_society::()?; + let candidate = add_candidate::("candidate", Tally { approvals: 0u32.into(), rejections: 0u32.into() }, false); + }: _(RawOrigin::Signed(candidate.clone())) + verify { + assert!(!Candidates::::contains_key(&candidate)); + } + + drop_candidate { + let founder = setup_society::()?; + let candidate = add_candidate::("candidate", Tally { approvals: 0u32.into(), rejections: 3u32.into() }, false); + let caller: T::AccountId = whitelisted_caller(); + let _ = Society::::insert_member(&caller, 0u32.into()); + let mut round_count = RoundCount::::get(); + round_count = round_count.saturating_add(2u32); + RoundCount::::put(round_count); + }: _(RawOrigin::Signed(caller), candidate.clone()) + verify { + assert!(!Candidates::::contains_key(&candidate)); + } + + cleanup_candidacy { + let founder = setup_society::()?; + let candidate = add_candidate::("candidate", Tally { approvals: 0u32.into(), rejections: 0u32.into() }, false); + let member_one: T::AccountId = account("one", 0, 0); + let member_two: T::AccountId = account("two", 0, 0); + let _ = Society::::insert_member(&member_one, 0u32.into()); + let _ = Society::::insert_member(&member_two, 0u32.into()); + let candidate_lookup: ::Source = T::Lookup::unlookup(candidate.clone()); + let _ = Society::::vote(RawOrigin::Signed(member_one.clone()).into(), candidate_lookup.clone(), true); + let _ = Society::::vote(RawOrigin::Signed(member_two.clone()).into(), candidate_lookup, true); + Candidates::::remove(&candidate); + }: _(RawOrigin::Signed(member_one), candidate.clone(), 5) + verify { + assert_eq!(Votes::::get(&candidate, &member_two), None); + } + + cleanup_challenge { + let founder = setup_society::()?; + ChallengeRoundCount::::put(1u32); + let member: T::AccountId = whitelisted_caller(); + let _ = Society::::insert_member(&member, 0u32.into()); + let defender: T::AccountId = account("defender", 0, 0); + Defending::::put((defender.clone(), member.clone(), Tally::default())); + let _ = Society::::defender_vote(RawOrigin::Signed(member.clone()).into(), true); + ChallengeRoundCount::::put(2u32); + let mut challenge_round = ChallengeRoundCount::::get(); + challenge_round = challenge_round.saturating_sub(1u32); + }: _(RawOrigin::Signed(member.clone()), challenge_round, 1u32) + verify { + assert_eq!(DefenderVotes::::get(challenge_round, &defender), None); + } + + impl_benchmark_test_suite!( + Society, + crate::tests_composite::ExtBuilder::default().build(), + crate::tests_composite::Test, + ) +} diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 987a5bcdf8717..2f5b56b757747 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -250,10 +250,16 @@ mod mock; #[cfg(test)] mod tests; +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +pub mod weights; + pub mod migrations; use frame_support::{ pallet_prelude::*, + storage::KeyLenOf, traits::{ Currency, EnsureOrigin, ExistenceRequirement::AllowDeath, Imbalance, OnUnbalanced, Randomness, ReservableCurrency, BalanceStatus, @@ -275,6 +281,8 @@ use sp_runtime::{ }; use sp_std::prelude::*; +pub use weights::WeightInfo; + pub use pallet::*; type BalanceOf = @@ -527,6 +535,9 @@ pub mod pallet { /// The maximum number of bids at once. #[pallet::constant] type MaxBids: Get; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; } #[pallet::error] @@ -723,10 +734,10 @@ pub mod pallet { /// Clear-cursor for Vote, map from Candidate -> (Maybe) Cursor. #[pallet::storage] - pub type VoteClearCursor, I: 'static = ()> = StorageMap<_, + pub(super) type VoteClearCursor, I: 'static = ()> = StorageMap<_, Twox64Concat, T::AccountId, - BoundedVec>, + BoundedVec>> >; /// At the end of the claim period, this contains the most recently approved members (along with @@ -821,7 +832,7 @@ pub mod pallet { /// /// Key: B (len of bids), C (len of candidates), M (len of members), X (balance reserve) /// Total Complexity: O(M + B + C + logM + logB + X) - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::bid())] pub fn bid(origin: OriginFor, value: BalanceOf) -> DispatchResult { let who = ensure_signed(origin)?; @@ -852,7 +863,7 @@ pub mod pallet { /// /// Key: B (len of bids), X (balance unreserve) /// Total Complexity: O(B + X) - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::unbid())] pub fn unbid(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -884,7 +895,7 @@ pub mod pallet { /// /// Key: B (len of bids), C (len of candidates), M (len of members) /// Total Complexity: O(M + B + C + logM + logB + X) - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::vouch())] pub fn vouch( origin: OriginFor, who: T::AccountId, @@ -932,7 +943,7 @@ pub mod pallet { /// /// Key: B (len of bids) /// Total Complexity: O(B) - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::unvouch())] pub fn unvouch(origin: OriginFor) -> DispatchResult { let voucher = ensure_signed(origin)?; @@ -958,7 +969,7 @@ pub mod pallet { /// /// Key: C (len of candidates), M (len of members) /// Total Complexity: O(M + logM + C) - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::vote())] pub fn vote( origin: OriginFor, candidate: ::Source, @@ -992,7 +1003,7 @@ pub mod pallet { /// Key: M (len of members) /// Total Complexity: O(M + logM) /// # - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::defender_vote())] pub fn defender_vote(origin: OriginFor, approve: bool) -> DispatchResultWithPostInfo { let voter = ensure_signed(origin)?; @@ -1024,7 +1035,7 @@ pub mod pallet { /// /// Key: M (len of members), P (number of payouts for a particular member) /// Total Complexity: O(M + logM + P + X) - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::payout())] pub fn payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; ensure!(Members::::get(&who).ok_or(Error::::NotMember)?.rank == 0, Error::::NoPayout); @@ -1044,7 +1055,7 @@ pub mod pallet { /// Repay the payment previously given to the member with the signed origin, remove any /// pending payments, and elevate them from rank 0 to rank 1. - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::waive_repay())] pub fn waive_repay(origin: OriginFor, amount: BalanceOf) -> DispatchResult { let who = ensure_signed(origin)?; let mut record = Members::::get(&who).ok_or(Error::::NotMember)?; @@ -1080,7 +1091,7 @@ pub mod pallet { /// - `rules` - The rules of this society concerning membership. /// /// Complexity: O(1) - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::found_society())] pub fn found_society( origin: OriginFor, founder: T::AccountId, @@ -1111,26 +1122,32 @@ pub mod pallet { /// member. /// /// Total Complexity: O(1) - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::dissolve())] pub fn dissolve(origin: OriginFor) -> DispatchResult { let founder = ensure_signed(origin)?; ensure!(Founder::::get().as_ref() == Some(&founder), Error::::NotFounder); ensure!(MemberCount::::get() == 1, Error::::NotHead); - #[allow(deprecated)] - Members::::remove_all(None); + let _ = Members::::clear(u32::MAX, None); MemberCount::::kill(); - #[allow(deprecated)] - MemberByIndex::::remove_all(None); - #[allow(deprecated)] - Members::::remove_all(None); - #[allow(deprecated)] - Votes::::remove_all(None); + let _ = MemberByIndex::::clear(u32::MAX, None); + let _ = SuspendedMembers::::clear(u32::MAX, None); + let _ = Payouts::::clear(u32::MAX, None); + let _ = Votes::::clear(u32::MAX, None); + let _ = VoteClearCursor::::clear(u32::MAX, None); Head::::kill(); + NextHead::::kill(); Founder::::kill(); Rules::::kill(); - #[allow(deprecated)] - Candidates::::remove_all(None); + Parameters::::kill(); + Pot::::kill(); + RoundCount::::kill(); + Bids::::kill(); + Skeptic::::kill(); + ChallengeRoundCount::::kill(); + Defending::::kill(); + let _ = DefenderVotes::::clear(u32::MAX, None); + let _ = Candidates::::clear(u32::MAX, None); Self::deposit_event(Event::::Unfounded { founder }); Ok(()) } @@ -1152,17 +1169,14 @@ pub mod pallet { /// /// Key: B (len of bids), M (len of members) /// Total Complexity: O(M + logM + B) - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::judge_suspended_member())] pub fn judge_suspended_member( origin: OriginFor, who: T::AccountId, forgive: bool, ) -> DispatchResultWithPostInfo { - let founder = ensure_signed(origin)?; - ensure!(Founder::::get() == Some(founder.clone()), Error::::NotFounder); - + ensure!(Some(ensure_signed(origin)?) == Founder::::get(), Error::::NotFounder); let record = SuspendedMembers::::get(&who).ok_or(Error::::NotSuspended)?; - if forgive { // Try to add member back to society. Can fail with `MaxMembers` limit. Self::reinstate_member(&who, record.rank)?; @@ -1172,7 +1186,6 @@ pub mod pallet { .map(|x| x.1).fold(Zero::zero(), |acc: BalanceOf, x| acc.saturating_add(x)); Self::unreserve_payout(total); } - SuspendedMembers::::remove(&who); Self::deposit_event(Event::::SuspendedMemberJudgement { who, judged: forgive }); Ok(Pays::No.into()) @@ -1192,7 +1205,7 @@ pub mod pallet { /// - `candidate_deposit`: The deposit required to make a bid for membership of the group. /// /// Total Complexity: O(1) - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::set_parameters())] pub fn set_parameters( origin: OriginFor, max_members: u32, @@ -1210,7 +1223,7 @@ pub mod pallet { /// Punish the skeptic with a strike if they did not vote on a candidate. Callable by the /// candidate. - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::punish_skeptic())] pub fn punish_skeptic(origin: OriginFor) -> DispatchResultWithPostInfo { let candidate = ensure_signed(origin)?; let mut candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; @@ -1223,7 +1236,7 @@ pub mod pallet { /// Transform an approved candidate into a member. Callable only by the /// the candidate, and only after the period for voting has ended. - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::claim_membership())] pub fn claim_membership(origin: OriginFor) -> DispatchResultWithPostInfo { let candidate = ensure_signed(origin)?; let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; @@ -1236,10 +1249,9 @@ pub mod pallet { /// Transform an approved candidate into a member. Callable only by the Signed origin of the /// Founder, only after the period for voting has ended and only when the candidate is not /// clearly rejected. - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::bestow_membership())] pub fn bestow_membership(origin: OriginFor, candidate: T::AccountId) -> DispatchResultWithPostInfo { - let founder = ensure_signed(origin)?; - ensure!(Founder::::get() == Some(founder.clone()), Error::::NotFounder); + ensure!(Some(ensure_signed(origin)?) == Founder::::get(), Error::::NotFounder); let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; ensure!(!candidacy.tally.clear_rejection(), Error::::Rejected); ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); @@ -1252,10 +1264,9 @@ pub mod pallet { /// have a clear approval. /// /// Any bid deposit is lost and voucher is banned. - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::kick_candidate())] pub fn kick_candidate(origin: OriginFor, candidate: T::AccountId) -> DispatchResultWithPostInfo { - let founder = ensure_signed(origin)?; - ensure!(Founder::::get() == Some(founder.clone()), Error::::NotFounder); + ensure!(Some(ensure_signed(origin)?) == Founder::::get(), Error::::NotFounder); let mut candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); ensure!(!candidacy.tally.clear_approval(), Error::::Approved); @@ -1268,7 +1279,7 @@ pub mod pallet { /// Remove the candidate's application from the society. Callable only by the candidate. /// /// Any bid deposit is lost and voucher is banned. - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::resign_candidacy())] pub fn resign_candidacy(origin: OriginFor) -> DispatchResultWithPostInfo { let candidate = ensure_signed(origin)?; let mut candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; @@ -1285,7 +1296,7 @@ pub mod pallet { /// a candidate with more rejections than approvals. /// /// The bid deposit is lost and the voucher is banned. - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::drop_candidate())] pub fn drop_candidate(origin: OriginFor, candidate: T::AccountId) -> DispatchResultWithPostInfo { ensure_signed(origin)?; let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; @@ -1299,20 +1310,22 @@ pub mod pallet { /// Remove up to `max` stale votes for the given `candidate`. /// /// May be called by any Signed origin, but only after the candidate's candidacy is ended. - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::cleanup_candidacy())] pub fn cleanup_candidacy(origin: OriginFor, candidate: T::AccountId, max: u32) -> DispatchResultWithPostInfo { ensure_signed(origin)?; ensure!(!Candidates::::contains_key(&candidate), Error::::InProgress); let maybe_cursor = VoteClearCursor::::get(&candidate); - let r = Votes::::clear_prefix(&candidate, Some(max), maybe_cursor.as_ref().map(|x| &x[..])); - VoteClearCursor::::set(&candidate, r.maybe_cursor.map(BoundedVec::truncate_from)); + let r = Votes::::clear_prefix(&candidate, max, maybe_cursor.as_ref().map(|x| &x[..])); + if let Some(cursor) = r.maybe_cursor { + VoteClearCursor::::insert(&candidate, BoundedVec::truncate_from(cursor)); + } Ok(if r.loops == 0 { Pays::Yes } else { Pays::No }.into()) } /// Remove up to `max` stale votes for the defender in the given `challenge_round`. /// /// May be called by any Signed origin, but only after the challenge round is ended. - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::cleanup_challenge())] pub fn cleanup_challenge( origin: OriginFor, challenge_round: RoundIndex, @@ -1320,10 +1333,11 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { ensure_signed(origin)?; ensure!(challenge_round < ChallengeRoundCount::::get(), Error::::InProgress); - match DefenderVotes::::remove_prefix(challenge_round, Some(max)) { - KillStorageResult::AllRemoved(0) => Err(Error::::NoVotes.into()), - _ => Ok(Pays::No.into()), - } + let _ = DefenderVotes::::clear_prefix(challenge_round, max, None); + // clear_prefix() v2 is always returning backend = 0, ignoring it till v3. + // let (_, backend, _, _) = r.deconstruct(); + // if backend == 0 { return Err(Error::::NoVotes.into()); }; + Ok(Pays::No.into()) } } } @@ -1370,7 +1384,7 @@ impl, I: 'static> Pallet { let voting_period = T::VotingPeriod::get(); let rotation_period = voting_period + claim_period; let now = frame_system::Pallet::::block_number(); - let phase = now % rotation_period; + let phase = now % rotation_period.clone(); if phase < voting_period { Period::Voting { elapsed: phase, more: voting_period - phase } } else { @@ -1664,7 +1678,7 @@ impl, I: 'static> Pallet { ) -> DispatchResult { Self::add_new_member(&candidate, rank)?; Self::check_skeptic(&candidate, &mut candidacy); - + let next_head = NextHead::::get() .filter(|old| { old.round > candidacy.round diff --git a/frame/society/src/migrations.rs b/frame/society/src/migrations.rs index eb5a8f2019167..29d4bea461a9b 100644 --- a/frame/society/src/migrations.rs +++ b/frame/society/src/migrations.rs @@ -229,11 +229,11 @@ pub fn from_original< } // Any suspended candidates remaining are rejected. - old::SuspendedCandidates::::remove_all(None); + let _ = old::SuspendedCandidates::::clear(u32::MAX, None); // We give the current defender the benefit of the doubt. old::Defender::::kill(); - old::DefenderVotes::::remove_all(None); + let _ = old::DefenderVotes::::clear(u32::MAX, None); } pub fn from_raw_past_payouts< diff --git a/frame/society/src/mock.rs b/frame/society/src/mock.rs index 10404cbae1e0f..ea7d82f34cfc9 100644 --- a/frame/society/src/mock.rs +++ b/frame/society/src/mock.rs @@ -54,8 +54,12 @@ parameter_types! { } ord_parameter_types! { + pub const ChallengePeriod: u64 = 8; + pub const ClaimPeriod: u64 = 1; pub const FounderSetAccount: u128 = 1; pub const SuspensionJudgementSetAccount: u128 = 2; + pub const MaxPayouts: u32 = 10; + pub const MaxBids: u32 = 10; } impl frame_system::Config for Test { @@ -105,12 +109,13 @@ impl Config for Test { type GraceStrikes = ConstU32<1>; type PeriodSpend = ConstU64<1000>; type VotingPeriod = ConstU64<3>; - type ClaimPeriod = ConstU64<1>; + type ClaimPeriod = ClaimPeriod; type MaxLockDuration = ConstU64<100>; type FounderSetOrigin = EnsureSignedBy; - type ChallengePeriod = ConstU64<8>; - type MaxPayouts = ConstU32<10>; - type MaxBids = ConstU32<10>; + type ChallengePeriod = ChallengePeriod; + type MaxPayouts = MaxPayouts; + type MaxBids = MaxBids; + type WeightInfo = (); } pub struct EnvBuilder { diff --git a/frame/society/src/tests.rs b/frame/society/src/tests.rs index 49ddf4bc264f8..55da8e05d0395 100644 --- a/frame/society/src/tests.rs +++ b/frame/society/src/tests.rs @@ -1129,3 +1129,71 @@ fn bids_ordered_correctly() { assert_eq!(Bids::::get(), final_list); }); } + +#[test] +fn waive_repay_works() { + EnvBuilder::new().execute(|| { + place_members([20, 30]); + Society::bump_payout(&20, 5, 100); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() } + ); + assert_eq!(Members::::get(20).unwrap().rank, 0); + assert_ok!(Society::waive_repay(Origin::signed(20), 100)); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![].try_into().unwrap() } + ); + assert_eq!(Members::::get(10).unwrap().rank, 1); + assert_eq!(Balances::free_balance(20), 50); + }); +} + +#[test] +fn punish_skeptic_works() { + EnvBuilder::new().execute(|| { + place_members([20]); + assert_ok!(Society::bid(Origin::signed(30), 0)); + next_intake(); + // Force 20 to be Skeptic so it gets a strike. + Skeptic::::put(20); + next_voting(); + // 30 decides to punish the skeptic (20). + assert_ok!(Society::punish_skeptic(Origin::signed(30))); + // 20 gets 1 strike. + assert_eq!(Members::::get(20).unwrap().strikes, 1); + let candidacy = Candidates::::get(&30).unwrap(); + // 30 candidacy has changed. + assert_eq!(candidacy.skeptic_struck, true); + }); +} + +#[test] +fn resign_candidacy_works() { + EnvBuilder::new().execute(|| { + assert_ok!(Society::bid(Origin::signed(30), 45)); + next_intake(); + assert_eq!(candidates(), vec![30]); + assert_ok!(Society::resign_candidacy(Origin::signed(30))); + // 30 candidacy has gone. + assert_eq!(candidates(), vec![]); + }); +} + +#[test] +fn drop_candidate_works() { + EnvBuilder::new().execute(|| { + place_members([20, 30]); + assert_ok!(Society::bid(Origin::signed(40), 45)); + next_intake(); + assert_eq!(candidates(), vec![40]); + assert_ok!(Society::vote(Origin::signed(10), 40, false)); + assert_ok!(Society::vote(Origin::signed(20), 40, false)); + assert_ok!(Society::vote(Origin::signed(30), 40, false)); + run_to_block(12); + assert_ok!(Society::drop_candidate(Origin::signed(50), 40)); + // 40 candidacy has gone. + assert_eq!(candidates(), vec![]); + }); +} diff --git a/frame/society/src/weights.rs b/frame/society/src/weights.rs new file mode 100644 index 0000000000000..dc9e0d3471692 --- /dev/null +++ b/frame/society/src/weights.rs @@ -0,0 +1,454 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_society +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-09-13, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_society +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --template=./.maintain/frame-weight-template.hbs +// --output=./frame/society/src/weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_society. +pub trait WeightInfo { + fn bid() -> Weight; + fn unbid() -> Weight; + fn vouch() -> Weight; + fn unvouch() -> Weight; + fn vote() -> Weight; + fn defender_vote() -> Weight; + fn payout() -> Weight; + fn waive_repay() -> Weight; + fn found_society() -> Weight; + fn dissolve() -> Weight; + fn judge_suspended_member() -> Weight; + fn set_parameters() -> Weight; + fn punish_skeptic() -> Weight; + fn claim_membership() -> Weight; + fn bestow_membership() -> Weight; + fn kick_candidate() -> Weight; + fn resign_candidacy() -> Weight; + fn drop_candidate() -> Weight; + fn cleanup_candidacy() -> Weight; + fn cleanup_challenge() -> Weight; +} + +/// Weights for pallet_society using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Society Bids (r:1 w:1) + // Storage: Society Candidates (r:1 w:0) + // Storage: Society Members (r:1 w:0) + // Storage: Society SuspendedMembers (r:1 w:0) + // Storage: Society Parameters (r:1 w:0) + fn bid() -> Weight { + (56_738_000 as Weight) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Society Bids (r:1 w:1) + fn unbid() -> Weight { + (45_204_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Society Bids (r:1 w:1) + // Storage: Society Candidates (r:1 w:0) + // Storage: Society Members (r:2 w:1) + // Storage: Society SuspendedMembers (r:1 w:0) + fn vouch() -> Weight { + (43_566_000 as Weight) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Society Bids (r:1 w:1) + // Storage: Society Members (r:1 w:1) + fn unvouch() -> Weight { + (33_741_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Society Candidates (r:1 w:1) + // Storage: Society Members (r:1 w:0) + // Storage: Society Votes (r:1 w:1) + fn vote() -> Weight { + (44_890_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Society Defending (r:1 w:1) + // Storage: Society Members (r:1 w:0) + // Storage: Society ChallengeRoundCount (r:1 w:0) + // Storage: Society DefenderVotes (r:1 w:1) + fn defender_vote() -> Weight { + (41_752_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Society Members (r:1 w:0) + // Storage: Society Payouts (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn payout() -> Weight { + (58_145_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Society Members (r:1 w:1) + // Storage: Society Payouts (r:1 w:1) + fn waive_repay() -> Weight { + (37_796_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Society Head (r:1 w:1) + // Storage: Society MemberCount (r:1 w:1) + // Storage: Society MemberByIndex (r:0 w:1) + // Storage: Society Founder (r:0 w:1) + // Storage: Society Rules (r:0 w:1) + // Storage: Society Members (r:0 w:1) + // Storage: Society Parameters (r:0 w:1) + fn found_society() -> Weight { + (37_221_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(7 as Weight)) + } + // Storage: Society Founder (r:1 w:1) + // Storage: Society MemberCount (r:1 w:1) + // Storage: Society Head (r:0 w:1) + // Storage: Society Defending (r:0 w:1) + // Storage: Society ChallengeRoundCount (r:0 w:1) + // Storage: Society MemberByIndex (r:0 w:5) + // Storage: Society Skeptic (r:0 w:1) + // Storage: Society Candidates (r:0 w:4) + // Storage: Society Pot (r:0 w:1) + // Storage: Society Rules (r:0 w:1) + // Storage: Society Votes (r:0 w:4) + // Storage: Society Members (r:0 w:5) + // Storage: Society RoundCount (r:0 w:1) + // Storage: Society Bids (r:0 w:1) + // Storage: Society Parameters (r:0 w:1) + // Storage: Society NextHead (r:0 w:1) + fn dissolve() -> Weight { + (105_209_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(30 as Weight)) + } + // Storage: Society Founder (r:1 w:0) + // Storage: Society SuspendedMembers (r:1 w:1) + // Storage: Society Payouts (r:1 w:0) + // Storage: Society Pot (r:1 w:1) + fn judge_suspended_member() -> Weight { + (39_916_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Society Founder (r:1 w:0) + // Storage: Society MemberCount (r:1 w:0) + // Storage: Society Parameters (r:0 w:1) + fn set_parameters() -> Weight { + (28_251_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + // Storage: Society Skeptic (r:1 w:0) + // Storage: Society Votes (r:1 w:0) + // Storage: Society Members (r:1 w:1) + // Storage: Society Parameters (r:1 w:0) + fn punish_skeptic() -> Weight { + (40_041_000 as Weight) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + // Storage: Society Parameters (r:1 w:0) + // Storage: Society MemberCount (r:1 w:1) + // Storage: Society NextHead (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: Society MemberByIndex (r:0 w:1) + // Storage: Society Members (r:0 w:1) + fn claim_membership() -> Weight { + (64_305_000 as Weight) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) + .saturating_add(T::DbWeight::get().writes(6 as Weight)) + } + // Storage: Society Founder (r:1 w:0) + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + // Storage: Society Parameters (r:1 w:0) + // Storage: Society MemberCount (r:1 w:1) + // Storage: Society NextHead (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: Society MemberByIndex (r:0 w:1) + // Storage: Society Members (r:0 w:1) + fn bestow_membership() -> Weight { + (68_334_000 as Weight) + .saturating_add(T::DbWeight::get().reads(7 as Weight)) + .saturating_add(T::DbWeight::get().writes(6 as Weight)) + } + // Storage: Society Founder (r:1 w:0) + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + fn kick_candidate() -> Weight { + (23_574_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + fn resign_candidacy() -> Weight { + (22_284_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + fn drop_candidate() -> Weight { + (32_618_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Society Candidates (r:1 w:0) + // Storage: Society VoteClearCursor (r:1 w:0) + // Storage: Society Votes (r:0 w:2) + fn cleanup_candidacy() -> Weight { + (27_936_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Society ChallengeRoundCount (r:1 w:0) + // Storage: Society DefenderVotes (r:0 w:1) + fn cleanup_challenge() -> Weight { + (18_411_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Society Bids (r:1 w:1) + // Storage: Society Candidates (r:1 w:0) + // Storage: Society Members (r:1 w:0) + // Storage: Society SuspendedMembers (r:1 w:0) + // Storage: Society Parameters (r:1 w:0) + fn bid() -> Weight { + (56_738_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Society Bids (r:1 w:1) + fn unbid() -> Weight { + (45_204_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Society Bids (r:1 w:1) + // Storage: Society Candidates (r:1 w:0) + // Storage: Society Members (r:2 w:1) + // Storage: Society SuspendedMembers (r:1 w:0) + fn vouch() -> Weight { + (43_566_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Society Bids (r:1 w:1) + // Storage: Society Members (r:1 w:1) + fn unvouch() -> Weight { + (33_741_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Society Candidates (r:1 w:1) + // Storage: Society Members (r:1 w:0) + // Storage: Society Votes (r:1 w:1) + fn vote() -> Weight { + (44_890_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Society Defending (r:1 w:1) + // Storage: Society Members (r:1 w:0) + // Storage: Society ChallengeRoundCount (r:1 w:0) + // Storage: Society DefenderVotes (r:1 w:1) + fn defender_vote() -> Weight { + (41_752_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Society Members (r:1 w:0) + // Storage: Society Payouts (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn payout() -> Weight { + (58_145_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Society Members (r:1 w:1) + // Storage: Society Payouts (r:1 w:1) + fn waive_repay() -> Weight { + (37_796_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Society Head (r:1 w:1) + // Storage: Society MemberCount (r:1 w:1) + // Storage: Society MemberByIndex (r:0 w:1) + // Storage: Society Founder (r:0 w:1) + // Storage: Society Rules (r:0 w:1) + // Storage: Society Members (r:0 w:1) + // Storage: Society Parameters (r:0 w:1) + fn found_society() -> Weight { + (37_221_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(7 as Weight)) + } + // Storage: Society Founder (r:1 w:1) + // Storage: Society MemberCount (r:1 w:1) + // Storage: Society Head (r:0 w:1) + // Storage: Society Defending (r:0 w:1) + // Storage: Society ChallengeRoundCount (r:0 w:1) + // Storage: Society MemberByIndex (r:0 w:5) + // Storage: Society Skeptic (r:0 w:1) + // Storage: Society Candidates (r:0 w:4) + // Storage: Society Pot (r:0 w:1) + // Storage: Society Rules (r:0 w:1) + // Storage: Society Votes (r:0 w:4) + // Storage: Society Members (r:0 w:5) + // Storage: Society RoundCount (r:0 w:1) + // Storage: Society Bids (r:0 w:1) + // Storage: Society Parameters (r:0 w:1) + // Storage: Society NextHead (r:0 w:1) + fn dissolve() -> Weight { + (105_209_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(30 as Weight)) + } + // Storage: Society Founder (r:1 w:0) + // Storage: Society SuspendedMembers (r:1 w:1) + // Storage: Society Payouts (r:1 w:0) + // Storage: Society Pot (r:1 w:1) + fn judge_suspended_member() -> Weight { + (39_916_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Society Founder (r:1 w:0) + // Storage: Society MemberCount (r:1 w:0) + // Storage: Society Parameters (r:0 w:1) + fn set_parameters() -> Weight { + (28_251_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + // Storage: Society Skeptic (r:1 w:0) + // Storage: Society Votes (r:1 w:0) + // Storage: Society Members (r:1 w:1) + // Storage: Society Parameters (r:1 w:0) + fn punish_skeptic() -> Weight { + (40_041_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(6 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + // Storage: Society Parameters (r:1 w:0) + // Storage: Society MemberCount (r:1 w:1) + // Storage: Society NextHead (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: Society MemberByIndex (r:0 w:1) + // Storage: Society Members (r:0 w:1) + fn claim_membership() -> Weight { + (64_305_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(6 as Weight)) + .saturating_add(RocksDbWeight::get().writes(6 as Weight)) + } + // Storage: Society Founder (r:1 w:0) + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + // Storage: Society Parameters (r:1 w:0) + // Storage: Society MemberCount (r:1 w:1) + // Storage: Society NextHead (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: Society MemberByIndex (r:0 w:1) + // Storage: Society Members (r:0 w:1) + fn bestow_membership() -> Weight { + (68_334_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(7 as Weight)) + .saturating_add(RocksDbWeight::get().writes(6 as Weight)) + } + // Storage: Society Founder (r:1 w:0) + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + fn kick_candidate() -> Weight { + (23_574_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + fn resign_candidacy() -> Weight { + (22_284_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + fn drop_candidate() -> Weight { + (32_618_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Society Candidates (r:1 w:0) + // Storage: Society VoteClearCursor (r:1 w:0) + // Storage: Society Votes (r:0 w:2) + fn cleanup_candidacy() -> Weight { + (27_936_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Society ChallengeRoundCount (r:1 w:0) + // Storage: Society DefenderVotes (r:0 w:1) + fn cleanup_challenge() -> Weight { + (18_411_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } +} diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index cbbaee75149e9..370a802665918 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -197,7 +197,6 @@ impl, MaxOverflow: Get> ConsumerLimits for (MaxNormal, pub mod pallet { use crate::{self as frame_system, pallet_prelude::*, *}; use frame_support::pallet_prelude::*; - use sp_core::crypto::FromEntropy; /// System configuration trait. Implemented by runtime. #[pallet::config] @@ -278,8 +277,7 @@ pub mod pallet { + Debug + MaybeDisplay + Ord - + MaxEncodedLen - + FromEntropy; + + MaxEncodedLen; /// Converting trait to take a source type and convert to `AccountId`. /// From 3f20f3666e37871a20ad95674461989f5794daba Mon Sep 17 00:00:00 2001 From: Gav Date: Mon, 5 Dec 2022 12:38:21 +0000 Subject: [PATCH 25/43] Fix Cargo --- Cargo.lock | 6 ++++++ frame/society/Cargo.toml | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 594b3083f2a0b..4e5d1d25eab47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2872,6 +2872,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + [[package]] name = "hmac" version = "0.8.1" diff --git a/frame/society/Cargo.toml b/frame/society/Cargo.toml index ee40395c75ac1..6b5eb0fce31b6 100644 --- a/frame/society/Cargo.toml +++ b/frame/society/Cargo.toml @@ -18,8 +18,8 @@ rand_chacha = { version = "0.2", default-features = false } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../primitives/arithmetic" } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } From db9990f3edfb67803f7e29049b5214f21cd44fae Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 7 Dec 2022 16:02:53 +0000 Subject: [PATCH 26/43] Fixes --- frame/society/src/lib.rs | 275 ++++++++++++++++--------- frame/society/src/migrations.rs | 113 +++++----- frame/society/src/mock.rs | 28 ++- frame/society/src/tests.rs | 268 ++++++++++++++++-------- frame/society/src/weights.rs | 160 ++++---------- frame/support/src/storage/mod.rs | 9 +- frame/support/src/storage/types/map.rs | 8 +- primitives/core/src/ed25519.rs | 4 +- primitives/core/src/sr25519.rs | 4 +- 9 files changed, 483 insertions(+), 386 deletions(-) diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 2187759a35ba2..0e848dfa949e7 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -261,8 +261,8 @@ use frame_support::{ pallet_prelude::*, storage::KeyLenOf, traits::{ - Currency, EnsureOrigin, ExistenceRequirement::AllowDeath, - Imbalance, OnUnbalanced, Randomness, ReservableCurrency, BalanceStatus, + BalanceStatus, Currency, EnsureOrigin, ExistenceRequirement::AllowDeath, Imbalance, + OnUnbalanced, Randomness, ReservableCurrency, }, PalletId, }; @@ -274,10 +274,11 @@ use rand_chacha::{ use scale_info::TypeInfo; use sp_runtime::{ traits::{ - AccountIdConversion, CheckedAdd, CheckedSub, Hash, Saturating, - StaticLookup, TrailingZeroInput, Zero, + AccountIdConversion, CheckedAdd, CheckedSub, Hash, Saturating, StaticLookup, + TrailingZeroInput, Zero, }, - ArithmeticError::Overflow, Percent, RuntimeDebug, + ArithmeticError::Overflow, + Percent, RuntimeDebug, }; use sp_std::prelude::*; @@ -450,10 +451,8 @@ pub struct IntakeRecord { round: RoundIndex, } -pub type IntakeRecordFor = IntakeRecord< - ::AccountId, - BalanceOf, ->; +pub type IntakeRecordFor = + IntakeRecord<::AccountId, BalanceOf>; #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub struct GroupParams { @@ -658,7 +657,8 @@ pub mod pallet { /// The max number of members for the society at one time. #[pallet::storage] - pub(super) type Parameters, I: 'static = ()> = StorageValue<_, GroupParamsFor, OptionQuery>; + pub(super) type Parameters, I: 'static = ()> = + StorageValue<_, GroupParamsFor, OptionQuery>; /// Amount of our account balance that is specifically for the next round's bid(s). #[pallet::storage] @@ -712,7 +712,8 @@ pub mod pallet { StorageValue<_, BoundedVec>, T::MaxBids>, ValueQuery>; #[pallet::storage] - pub type Candidates, I: 'static = ()> = StorageMap<_, + pub type Candidates, I: 'static = ()> = StorageMap< + _, Blake2_128Concat, T::AccountId, Candidacy>, @@ -725,7 +726,8 @@ pub mod pallet { /// Double map from Candidate -> Voter -> (Maybe) Vote. #[pallet::storage] - pub(super) type Votes, I: 'static = ()> = StorageDoubleMap<_, + pub(super) type Votes, I: 'static = ()> = StorageDoubleMap< + _, Twox64Concat, T::AccountId, Twox64Concat, @@ -736,28 +738,25 @@ pub mod pallet { /// Clear-cursor for Vote, map from Candidate -> (Maybe) Cursor. #[pallet::storage] - pub(super) type VoteClearCursor, I: 'static = ()> = StorageMap<_, - Twox64Concat, - T::AccountId, - BoundedVec>> - >; + pub(super) type VoteClearCursor, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, BoundedVec>>>; /// At the end of the claim period, this contains the most recently approved members (along with /// their bid and round ID) who is from the most recent round with the lowest bid. They will /// become the new `Head`. #[pallet::storage] - pub type NextHead, I: 'static = ()> = StorageValue<_, - IntakeRecordFor, - OptionQuery, - >; + pub type NextHead, I: 'static = ()> = + StorageValue<_, IntakeRecordFor, OptionQuery>; /// The number of challenge rounds there have been. Used to identify stale DefenderVotes. #[pallet::storage] - pub(super) type ChallengeRoundCount, I: 'static = ()> = StorageValue<_, RoundIndex, ValueQuery>; + pub(super) type ChallengeRoundCount, I: 'static = ()> = + StorageValue<_, RoundIndex, ValueQuery>; /// The defending member currently being challenged, along with a running tally of votes. #[pallet::storage] - pub(super) type Defending, I: 'static = ()> = StorageValue<_, (T::AccountId, T::AccountId, Tally)>; + pub(super) type Defending, I: 'static = ()> = + StorageValue<_, (T::AccountId, T::AccountId, Tally)>; /// Votes for the defender, keyed by challenge round. #[pallet::storage] @@ -767,7 +766,7 @@ pub mod pallet { #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { fn on_initialize(n: T::BlockNumber) -> Weight { - let mut weight = 0; + let mut weight = Weight::zero(); let weights = T::BlockWeights::get(); let phrase = b"society_rotation"; @@ -784,15 +783,15 @@ pub mod pallet { match Self::period() { Period::Voting { elapsed, .. } if elapsed.is_zero() => { Self::rotate_intake(&mut rng); - weight += weights.max_block / 20; + weight.saturating_accrue(weights.max_block / 20); }, - _ => {} + _ => {}, } // Run a challenge rotation if (n % T::ChallengePeriod::get()).is_zero() { Self::rotate_challenge(&mut rng); - weight += weights.max_block / 20; + weight.saturating_accrue(weights.max_block / 20); } weight @@ -807,9 +806,7 @@ pub mod pallet { #[cfg(feature = "std")] impl, I: 'static> Default for GenesisConfig { fn default() -> Self { - Self { - pot: Default::default(), - } + Self { pot: Default::default() } } } @@ -905,6 +902,7 @@ pub mod pallet { tip: BalanceOf, ) -> DispatchResult { let voucher = ensure_signed(origin)?; + let who = T::Lookup::lookup(who)?; // Get bids and check user is not bidding. let mut bids = Bids::::get(); @@ -950,7 +948,9 @@ pub mod pallet { let voucher = ensure_signed(origin)?; let mut bids = Bids::::get(); - let pos = bids.iter().position(|bid| bid.kind.is_vouch(&voucher)) + let pos = bids + .iter() + .position(|bid| bid.kind.is_vouch(&voucher)) .ok_or(Error::::NotVouchingOnBidder)?; let bid = bids.remove(pos); Self::clean_bid(&bid); @@ -980,7 +980,8 @@ pub mod pallet { let voter = ensure_signed(origin)?; let candidate = T::Lookup::lookup(candidate)?; - let mut candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + let mut candidacy = + Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; let record = Members::::get(&voter).ok_or(Error::::NotMember)?; let first_time = Votes::::mutate(&candidate, &voter, |v| { @@ -1040,7 +1041,10 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::payout())] pub fn payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - ensure!(Members::::get(&who).ok_or(Error::::NotMember)?.rank == 0, Error::::NoPayout); + ensure!( + Members::::get(&who).ok_or(Error::::NotMember)?.rank == 0, + Error::::NoPayout + ); let mut record = Payouts::::get(&who); if let Some((when, amount)) = record.payouts.first() { @@ -1104,6 +1108,7 @@ pub mod pallet { rules: Vec, ) -> DispatchResult { T::FounderSetOrigin::ensure_origin(origin)?; + let founder = T::Lookup::lookup(founder)?; ensure!(!Head::::exists(), Error::::AlreadyFounded); ensure!(max_members > 1, Error::::MaxMembers); // This should never fail in the context of this function... @@ -1177,15 +1182,22 @@ pub mod pallet { who: AccountIdLookupOf, forgive: bool, ) -> DispatchResultWithPostInfo { - ensure!(Some(ensure_signed(origin)?) == Founder::::get(), Error::::NotFounder); + ensure!( + Some(ensure_signed(origin)?) == Founder::::get(), + Error::::NotFounder + ); + let who = T::Lookup::lookup(who)?; let record = SuspendedMembers::::get(&who).ok_or(Error::::NotSuspended)?; if forgive { // Try to add member back to society. Can fail with `MaxMembers` limit. Self::reinstate_member(&who, record.rank)?; } else { let payout_record = Payouts::::take(&who); - let total = payout_record.payouts.into_iter() - .map(|x| x.1).fold(Zero::zero(), |acc: BalanceOf, x| acc.saturating_add(x)); + let total = payout_record + .payouts + .into_iter() + .map(|x| x.1) + .fold(Zero::zero(), |acc: BalanceOf, x| acc.saturating_add(x)); Self::unreserve_payout(total); } SuspendedMembers::::remove(&who); @@ -1199,8 +1211,8 @@ pub mod pallet { /// The dispatch origin for this call must be Signed by the Founder. /// /// Parameters: - /// - `max_members` - The maximum number of members for the society. This must be no - /// less than the current number of members. + /// - `max_members` - The maximum number of members for the society. This must be no less + /// than the current number of members. /// - `max_intake` - The maximum number of candidates per intake period. /// - `max_strikes`: The maximum number of strikes a member may get before they become /// suspended and may only be reinstated by the founder. @@ -1215,7 +1227,10 @@ pub mod pallet { max_strikes: u32, candidate_deposit: BalanceOf, ) -> DispatchResult { - ensure!(Some(ensure_signed(origin)?) == Founder::::get(), Error::::NotFounder); + ensure!( + Some(ensure_signed(origin)?) == Founder::::get(), + Error::::NotFounder + ); ensure!(max_members >= MemberCount::::get(), Error::::MaxMembers); let params = GroupParams { max_members, max_intake, max_strikes, candidate_deposit }; Parameters::::put(¶ms); @@ -1228,7 +1243,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::punish_skeptic())] pub fn punish_skeptic(origin: OriginFor) -> DispatchResultWithPostInfo { let candidate = ensure_signed(origin)?; - let mut candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + let mut candidacy = + Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; ensure!(!candidacy.skeptic_struck, Error::::AlreadyPunished); ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); let punished = Self::check_skeptic(&candidate, &mut candidacy); @@ -1241,7 +1257,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::claim_membership())] pub fn claim_membership(origin: OriginFor) -> DispatchResultWithPostInfo { let candidate = ensure_signed(origin)?; - let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + let candidacy = + Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; ensure!(candidacy.tally.clear_approval(), Error::::NotApproved); ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); Self::induct_member(candidate, candidacy, 0)?; @@ -1252,9 +1269,16 @@ pub mod pallet { /// Founder, only after the period for voting has ended and only when the candidate is not /// clearly rejected. #[pallet::weight(T::WeightInfo::bestow_membership())] - pub fn bestow_membership(origin: OriginFor, candidate: T::AccountId) -> DispatchResultWithPostInfo { - ensure!(Some(ensure_signed(origin)?) == Founder::::get(), Error::::NotFounder); - let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + pub fn bestow_membership( + origin: OriginFor, + candidate: T::AccountId, + ) -> DispatchResultWithPostInfo { + ensure!( + Some(ensure_signed(origin)?) == Founder::::get(), + Error::::NotFounder + ); + let candidacy = + Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; ensure!(!candidacy.tally.clear_rejection(), Error::::Rejected); ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); Self::induct_member(candidate, candidacy, 0)?; @@ -1267,9 +1291,16 @@ pub mod pallet { /// /// Any bid deposit is lost and voucher is banned. #[pallet::weight(T::WeightInfo::kick_candidate())] - pub fn kick_candidate(origin: OriginFor, candidate: T::AccountId) -> DispatchResultWithPostInfo { - ensure!(Some(ensure_signed(origin)?) == Founder::::get(), Error::::NotFounder); - let mut candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + pub fn kick_candidate( + origin: OriginFor, + candidate: T::AccountId, + ) -> DispatchResultWithPostInfo { + ensure!( + Some(ensure_signed(origin)?) == Founder::::get(), + Error::::NotFounder + ); + let mut candidacy = + Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); ensure!(!candidacy.tally.clear_approval(), Error::::Approved); Self::check_skeptic(&candidate, &mut candidacy); @@ -1284,7 +1315,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::resign_candidacy())] pub fn resign_candidacy(origin: OriginFor) -> DispatchResultWithPostInfo { let candidate = ensure_signed(origin)?; - let mut candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + let mut candidacy = + Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; if !Self::in_progress(candidacy.round) { Self::check_skeptic(&candidate, &mut candidacy); } @@ -1299,9 +1331,13 @@ pub mod pallet { /// /// The bid deposit is lost and the voucher is banned. #[pallet::weight(T::WeightInfo::drop_candidate())] - pub fn drop_candidate(origin: OriginFor, candidate: T::AccountId) -> DispatchResultWithPostInfo { + pub fn drop_candidate( + origin: OriginFor, + candidate: T::AccountId, + ) -> DispatchResultWithPostInfo { ensure_signed(origin)?; - let candidacy = Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + let candidacy = + Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; ensure!(candidacy.tally.clear_rejection(), Error::::NotRejected); ensure!(RoundCount::::get() > candidacy.round + 1, Error::::TooEarly); Self::reject_candidate(&candidate, &candidacy.kind); @@ -1313,11 +1349,16 @@ pub mod pallet { /// /// May be called by any Signed origin, but only after the candidate's candidacy is ended. #[pallet::weight(T::WeightInfo::cleanup_candidacy())] - pub fn cleanup_candidacy(origin: OriginFor, candidate: T::AccountId, max: u32) -> DispatchResultWithPostInfo { + pub fn cleanup_candidacy( + origin: OriginFor, + candidate: T::AccountId, + max: u32, + ) -> DispatchResultWithPostInfo { ensure_signed(origin)?; ensure!(!Candidates::::contains_key(&candidate), Error::::InProgress); let maybe_cursor = VoteClearCursor::::get(&candidate); - let r = Votes::::clear_prefix(&candidate, max, maybe_cursor.as_ref().map(|x| &x[..])); + let r = + Votes::::clear_prefix(&candidate, max, maybe_cursor.as_ref().map(|x| &x[..])); if let Some(cursor) = r.maybe_cursor { VoteClearCursor::::insert(&candidate, BoundedVec::truncate_from(cursor)); } @@ -1334,7 +1375,10 @@ pub mod pallet { max: u32, ) -> DispatchResultWithPostInfo { ensure_signed(origin)?; - ensure!(challenge_round < ChallengeRoundCount::::get(), Error::::InProgress); + ensure!( + challenge_round < ChallengeRoundCount::::get(), + Error::::InProgress + ); let _ = DefenderVotes::::clear_prefix(challenge_round, max, None); // clear_prefix() v2 is always returning backend = 0, ignoring it till v3. // let (_, backend, _, _) = r.deconstruct(); @@ -1346,7 +1390,7 @@ pub mod pallet { /// Simple ensure origin struct to filter for the founder account. pub struct EnsureFounder(sp_std::marker::PhantomData); -impl EnsureOrigin<::Origin> for EnsureFounder { +impl EnsureOrigin<::RuntimeOrigin> for EnsureFounder { type Success = T::AccountId; fn try_origin(o: T::RuntimeOrigin) -> Result { o.into().and_then(|o| match (o, Founder::::get()) { @@ -1397,7 +1441,7 @@ impl, I: 'static> Pallet { /// Returns true if the given `target_round` is still in its initial voting phase. fn in_progress(target_round: RoundIndex) -> bool { let round = RoundCount::::get(); - target_round == round && matches!(Self::period(), Period::Voting {..}) + target_round == round && matches!(Self::period(), Period::Voting { .. }) } /// Returns the new vote. @@ -1417,18 +1461,25 @@ impl, I: 'static> Pallet { } /// Returns `true` if a punishment was given. - fn check_skeptic(candidate: &T::AccountId, candidacy: &mut Candidacy>) -> bool { - if RoundCount::::get() != candidacy.round || candidacy.skeptic_struck { return false } + fn check_skeptic( + candidate: &T::AccountId, + candidacy: &mut Candidacy>, + ) -> bool { + if RoundCount::::get() != candidacy.round || candidacy.skeptic_struck { + return false + } // We expect the skeptic to have voted. - let skeptic = match Skeptic::::get() { Some(s) => s, None => return false }; + let skeptic = match Skeptic::::get() { + Some(s) => s, + None => return false, + }; let maybe_vote = Votes::::get(&candidate, &skeptic); let approved = candidacy.tally.clear_approval(); let rejected = candidacy.tally.clear_rejection(); match (maybe_vote, approved, rejected) { - (None, _, _) - | (Some(Vote { approve: true, .. }), false, true) - | (Some(Vote { approve: false, .. }), true, false) - => { + (None, _, _) | + (Some(Vote { approve: true, .. }), false, true) | + (Some(Vote { approve: false, .. }), true, false) => { // Can't do much if the punishment doesn't work out. if Self::strike_member(&skeptic).is_ok() { candidacy.skeptic_struck = true; @@ -1458,10 +1509,9 @@ impl, I: 'static> Pallet { // Check defender skeptic voted and that their vote was with the majority. let skeptic_vote = DefenderVotes::::get(round, &skeptic); match (skeptic_vote, tally.more_approvals(), tally.more_rejections()) { - (None, _, _) - | (Some(Vote { approve: true, .. }), false, true) - | (Some(Vote { approve: false, .. }), true, false) - => { + (None, _, _) | + (Some(Vote { approve: true, .. }), false, true) | + (Some(Vote { approve: false, .. }), true, false) => { // Punish skeptic and challenge them next. let _ = Self::strike_member(&skeptic); let founder = Founder::::get(); @@ -1469,8 +1519,8 @@ impl, I: 'static> Pallet { if Some(&skeptic) != founder.as_ref() && Some(&skeptic) != head.as_ref() { next_defender = Some(skeptic); } - } - _ => {} + }, + _ => {}, } round.saturating_inc(); ChallengeRoundCount::::put(round); @@ -1479,8 +1529,11 @@ impl, I: 'static> Pallet { // Avoid challenging if there's only two members since we never challenge the Head or // the Founder. if MemberCount::::get() > 2 { - let defender = next_defender.or_else(|| Self::pick_defendent(rng)).expect("exited if members empty; qed"); - let skeptic = Self::pick_member_except(rng, &defender).expect("exited if members empty; qed"); + let defender = next_defender + .or_else(|| Self::pick_defendent(rng)) + .expect("exited if members empty; qed"); + let skeptic = + Self::pick_member_except(rng, &defender).expect("exited if members empty; qed"); Self::deposit_event(Event::::Challenged { member: defender.clone() }); Defending::::put((defender, skeptic, Tally::default())); } else { @@ -1542,7 +1595,8 @@ impl, I: 'static> Pallet { Some(params) => params, None => return 0, }; - let max_selections: u32 = params.max_intake + let max_selections: u32 = params + .max_intake .min(params.max_members.saturating_sub(member_count)) .min(bids.len() as u32); @@ -1553,9 +1607,9 @@ impl, I: 'static> Pallet { bids.retain(|bid| { // We only accept a zero bid as the first selection. total_cost.saturating_accrue(bid.value); - let accept = selections < max_selections - && (!bid.value.is_zero() || selections == 0) - && total_cost <= pot; + let accept = selections < max_selections && + (!bid.value.is_zero() || selections == 0) && + total_cost <= pot; if accept { let candidacy = Candidacy { round, @@ -1585,7 +1639,10 @@ impl, I: 'static> Pallet { ) { let pos = bids.iter().position(|bid| bid.value > value).unwrap_or(bids.len()); let r = bids.force_insert_keep_left(pos, Bid { value, who: who.clone(), kind: bid_kind }); - let maybe_discarded = match r { Ok(x) => x, Err(x) => Some(x) }; + let maybe_discarded = match r { + Ok(x) => x, + Err(x) => Some(x), + }; if let Some(discarded) = maybe_discarded { Self::clean_bid(&discarded); Self::deposit_event(Event::::AutoUnbid { candidate: discarded.who }); @@ -1626,7 +1683,9 @@ impl, I: 'static> Pallet { debug_assert!(T::Currency::repatriate_reserved(&who, &pot, *deposit, free).is_ok()); }, BidKind::Vouch(voucher, _) => { - Members::::mutate_extant(voucher, |record| record.vouching = Some(VouchingStatus::Banned)); + Members::::mutate_extant(voucher, |record| { + record.vouching = Some(VouchingStatus::Banned) + }); }, } } @@ -1649,7 +1708,10 @@ impl, I: 'static> Pallet { fn insert_member(who: &T::AccountId, rank: Rank) -> DispatchResult { let params = Parameters::::get().ok_or(Error::::NotGroup)?; ensure!(MemberCount::::get() < params.max_members, Error::::MaxMembers); - let index = MemberCount::::mutate(|i| { i.saturating_accrue(1); *i - 1 }); + let index = MemberCount::::mutate(|i| { + i.saturating_accrue(1); + *i - 1 + }); let record = MemberRecord { rank, strikes: 0, vouching: None, index }; Members::::insert(who, record); MemberByIndex::::insert(index, who); @@ -1683,11 +1745,14 @@ impl, I: 'static> Pallet { let next_head = NextHead::::get() .filter(|old| { - old.round > candidacy.round - || old.round == candidacy.round && old.bid < candidacy.bid - }).unwrap_or_else(|| - IntakeRecord { who: candidate.clone(), bid: candidacy.bid, round: candidacy.round } - ); + old.round > candidacy.round || + old.round == candidacy.round && old.bid < candidacy.bid + }) + .unwrap_or_else(|| IntakeRecord { + who: candidate.clone(), + bid: candidacy.bid, + round: candidacy.round, + }); NextHead::::put(next_head); let now = >::block_number(); @@ -1707,7 +1772,9 @@ impl, I: 'static> Pallet { if record.strikes >= T::GraceStrikes::get() { // Too many strikes: slash the payout in half. - let total_payout = Payouts::::get(who).payouts.iter() + let total_payout = Payouts::::get(who) + .payouts + .iter() .fold(BalanceOf::::zero(), |acc, x| acc.saturating_add(x.1)); Self::slash_payout(who, total_payout / 2u32.into()); } @@ -1735,14 +1802,20 @@ impl, I: 'static> Pallet { ensure!(Founder::::get().as_ref() != Some(m), Error::::Founder); if let Some(mut record) = Members::::get(m) { let index = record.index; - let last_index = MemberCount::::mutate(|i| { i.saturating_reduce(1); *i }); + let last_index = MemberCount::::mutate(|i| { + i.saturating_reduce(1); + *i + }); if index != last_index { - // Move the member with the last index down to the index of the member to be removed. + // Move the member with the last index down to the index of the member to be + // removed. if let Some(other) = MemberByIndex::::get(last_index) { MemberByIndex::::insert(index, &other); - Members::::mutate(other, |m_r| - if let Some(r) = m_r { r.index = index } - ); + Members::::mutate(other, |m_r| { + if let Some(r) = m_r { + r.index = index + } + }); } else { debug_assert!(false, "ERROR: No member at the last index position?"); } @@ -1795,8 +1868,12 @@ impl, I: 'static> Pallet { /// Select a member at random except `exception`, given the RNG `rng`. /// - /// If `exception` is the only member (or the state is inconsistent), then `None` may be returned. - fn pick_member_except(rng: &mut impl RngCore, exception: &T::AccountId) -> Option { + /// If `exception` is the only member (or the state is inconsistent), then `None` may be + /// returned. + fn pick_member_except( + rng: &mut impl RngCore, + exception: &T::AccountId, + ) -> Option { let member_count = MemberCount::::get(); if member_count <= 1 { return None @@ -1878,7 +1955,9 @@ impl, I: 'static> Pallet { /// It is the caller's duty to ensure that `who` is already a member. This does nothing if `who` /// is not a member or if `value` is zero. fn bump_payout(who: &T::AccountId, when: T::BlockNumber, value: BalanceOf) { - if value.is_zero() { return } + if value.is_zero() { + return + } if let Some(MemberRecord { rank: 0, .. }) = Members::::get(who) { Payouts::::mutate(who, |record| { // Members of rank 1 never get payouts. @@ -1922,12 +2001,7 @@ impl, I: 'static> Pallet { // this should never fail since we ensure we can afford the payouts in a previous // block, but there's not much we can do to recover if it fails anyway. - let res = T::Currency::transfer( - &Self::account_id(), - &Self::payouts(), - amount, - AllowDeath, - ); + let res = T::Currency::transfer(&Self::account_id(), &Self::payouts(), amount, AllowDeath); debug_assert!(res.is_ok()); } @@ -1939,12 +2013,7 @@ impl, I: 'static> Pallet { // this should never fail since we ensure we can afford the payouts in a previous // block, but there's not much we can do to recover if it fails anyway. - let res = T::Currency::transfer( - &Self::payouts(), - &Self::account_id(), - amount, - AllowDeath, - ); + let res = T::Currency::transfer(&Self::payouts(), &Self::account_id(), amount, AllowDeath); debug_assert!(res.is_ok()); } diff --git a/frame/society/src/migrations.rs b/frame/society/src/migrations.rs index 29d4bea461a9b..8691e4d59b317 100644 --- a/frame/society/src/migrations.rs +++ b/frame/society/src/migrations.rs @@ -35,64 +35,74 @@ pub(crate) mod old { Approve, } - #[storage_alias] pub type Bids, I: 'static> = StorageValue< + #[storage_alias] + pub type Bids, I: 'static> = StorageValue< Pallet, Vec::AccountId, BalanceOf>>, ValueQuery, >; - #[storage_alias] pub type Candidates, I: 'static> = StorageValue< + #[storage_alias] + pub type Candidates, I: 'static> = StorageValue< Pallet, Vec::AccountId, BalanceOf>>, ValueQuery, >; - #[storage_alias] pub type Votes, I: 'static> = StorageDoubleMap< + #[storage_alias] + pub type Votes, I: 'static> = StorageDoubleMap< Pallet, - Twox64Concat, ::AccountId, - Twox64Concat, ::AccountId, + Twox64Concat, + ::AccountId, + Twox64Concat, + ::AccountId, Vote, >; - #[storage_alias] pub type SuspendedCandidates, I: 'static> = StorageMap< + #[storage_alias] + pub type SuspendedCandidates, I: 'static> = StorageMap< Pallet, - Twox64Concat, ::AccountId, + Twox64Concat, + ::AccountId, (BalanceOf, BidKind<::AccountId, BalanceOf>), >; - #[storage_alias] pub type Members, I: 'static> = StorageValue< - Pallet, - Vec<::AccountId>, - ValueQuery, - >; - #[storage_alias] pub type Vouching, I: 'static> = StorageMap< + #[storage_alias] + pub type Members, I: 'static> = + StorageValue, Vec<::AccountId>, ValueQuery>; + #[storage_alias] + pub type Vouching, I: 'static> = StorageMap< Pallet, - Twox64Concat, ::AccountId, + Twox64Concat, + ::AccountId, VouchingStatus, >; - #[storage_alias] pub type Strikes, I: 'static> = StorageMap< + #[storage_alias] + pub type Strikes, I: 'static> = StorageMap< Pallet, - Twox64Concat, ::AccountId, + Twox64Concat, + ::AccountId, StrikeCount, ValueQuery, >; - #[storage_alias] pub type Payouts, I: 'static> = StorageMap< + #[storage_alias] + pub type Payouts, I: 'static> = StorageMap< Pallet, - Twox64Concat, ::AccountId, + Twox64Concat, + ::AccountId, Vec<(::BlockNumber, BalanceOf)>, ValueQuery, >; - #[storage_alias] pub type SuspendedMembers, I: 'static> = StorageMap< + #[storage_alias] + pub type SuspendedMembers, I: 'static> = StorageMap< Pallet, - Twox64Concat, ::AccountId, + Twox64Concat, + ::AccountId, bool, ValueQuery, >; - #[storage_alias] pub type Defender, I: 'static> = StorageValue< - Pallet, - ::AccountId, - >; - #[storage_alias] pub type DefenderVotes, I: 'static> = StorageMap< - Pallet, - Twox64Concat, ::AccountId, - Vote, - >; + #[storage_alias] + pub type Defender, I: 'static> = + StorageValue, ::AccountId>; + #[storage_alias] + pub type DefenderVotes, I: 'static> = + StorageMap, Twox64Concat, ::AccountId, Vote>; } pub fn can_migrate, I: Instance + 'static>() -> bool { @@ -148,11 +158,8 @@ pub fn assert_internal_consistency, I: Instance + 'static>() { assert!(!old::Members::::exists()); } -pub fn from_original< - T: Config, - I: Instance + 'static, ->( - past_payouts: &mut [(::AccountId, BalanceOf)] +pub fn from_original, I: Instance + 'static>( + past_payouts: &mut [(::AccountId, BalanceOf)], ) { // First check that this is the original state layout. This is easy since the original layout // contained the Members value, and this value no longer exists in the new layout. @@ -173,23 +180,21 @@ pub fn from_original< // Migrate Votes from old::Votes // No need to drain, since we're overwriting values. for (voter, vote) in old::Votes::::iter_prefix(&candidate) { - Votes::::insert(&candidate, &voter, Vote { - approve: vote == old::Vote::Approve, - weight: 1, - }); + Votes::::insert( + &candidate, + &voter, + Vote { approve: vote == old::Vote::Approve, weight: 1 }, + ); match vote { old::Vote::Approve => tally.approvals.saturating_inc(), old::Vote::Reject => tally.rejections.saturating_inc(), old::Vote::Skeptic => Skeptic::::put(&voter), } } - Candidates::::insert(&candidate, Candidacy { - round: 0, - kind, - tally, - skeptic_struck: false, - bid: value, - }); + Candidates::::insert( + &candidate, + Candidacy { round: 0, kind, tally, skeptic_struck: false, bid: value }, + ); } // Migrate Members from old::Members old::Strikes old::Vouching @@ -204,7 +209,8 @@ pub fn from_original< } MemberCount::::put(member_count); - // Migrate Payouts from: old::Payouts and raw info (needed since we can't query old chain state). + // Migrate Payouts from: old::Payouts and raw info (needed since we can't query old chain + // state). past_payouts.sort(); for (who, mut payouts) in old::Payouts::::iter() { payouts.truncate(T::MaxPayouts::get() as usize); @@ -236,17 +242,12 @@ pub fn from_original< let _ = old::DefenderVotes::::clear(u32::MAX, None); } -pub fn from_raw_past_payouts< - T: Config, - I: Instance + 'static, ->( - past_payouts_raw: &[(&[u8], u128)] +pub fn from_raw_past_payouts, I: Instance + 'static>( + past_payouts_raw: &[(&[u8], u128)], ) -> Vec<(::AccountId, BalanceOf)> { - past_payouts_raw.iter() - .filter_map(|(x, y)| Some(( - Decode::decode(&mut x.as_ref()).ok()?, - (*y).try_into().ok()?, - ))) + past_payouts_raw + .iter() + .filter_map(|(x, y)| Some((Decode::decode(&mut x.as_ref()).ok()?, (*y).try_into().ok()?))) .collect() } diff --git a/frame/society/src/mock.rs b/frame/society/src/mock.rs index 63e939ef1df6e..e53477754a072 100644 --- a/frame/society/src/mock.rs +++ b/frame/society/src/mock.rs @@ -32,6 +32,8 @@ use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, }; +use RuntimeOrigin as Origin; + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -102,7 +104,7 @@ impl pallet_balances::Config for Test { } impl Config for Test { - type Event = Event; + type RuntimeEvent = RuntimeEvent; type PalletId = SocietyPalletId; type Currency = pallet_balances::Pallet; type Randomness = TestRandomness; @@ -151,9 +153,7 @@ impl EnvBuilder { pallet_balances::GenesisConfig:: { balances: self.balances } .assimilate_storage(&mut t) .unwrap(); - pallet_society::GenesisConfig:: { - pot: self.pot, - } + pallet_society::GenesisConfig:: { pot: self.pot } .assimilate_storage(&mut t) .unwrap(); let mut ext: sp_io::TestExternalities = t.into(); @@ -223,11 +223,17 @@ pub fn conclude_intake(allow_resignation: bool, judge_intake: Option) { for (who, candidacy) in Candidates::::iter() { if candidacy.tally.clear_approval() { assert_ok!(Society::claim_membership(Origin::signed(who))); - assert_noop!(Society::claim_membership(Origin::signed(who)), Error::::NotCandidate); + assert_noop!( + Society::claim_membership(Origin::signed(who)), + Error::::NotCandidate + ); continue } if candidacy.tally.clear_rejection() && allow_resignation { - assert_noop!(Society::claim_membership(Origin::signed(who)), Error::::NotApproved); + assert_noop!( + Society::claim_membership(Origin::signed(who)), + Error::::NotApproved + ); assert_ok!(Society::resign_candidacy(Origin::signed(who))); continue } @@ -244,9 +250,15 @@ pub fn conclude_intake(allow_resignation: bool, judge_intake: Option) { } } if candidacy.tally.clear_rejection() && round > candidacy.round + 1 { - assert_noop!(Society::claim_membership(Origin::signed(who)), Error::::NotApproved); + assert_noop!( + Society::claim_membership(Origin::signed(who)), + Error::::NotApproved + ); assert_ok!(Society::drop_candidate(Origin::signed(0), who)); - assert_noop!(Society::drop_candidate(Origin::signed(0), who), Error::::NotCandidate); + assert_noop!( + Society::drop_candidate(Origin::signed(0), who), + Error::::NotCandidate + ); continue } if !candidacy.skeptic_struck { diff --git a/frame/society/src/tests.rs b/frame/society/src/tests.rs index ac60750c0d7e2..28e9ea028c333 100644 --- a/frame/society/src/tests.rs +++ b/frame/society/src/tests.rs @@ -18,14 +18,16 @@ //! Tests for the module. use super::*; -use mock::*; use migrations::old; +use mock::*; use frame_support::{assert_noop, assert_ok}; use sp_core::blake2_256; use sp_runtime::traits::BadOrigin; -use VouchingStatus::*; use BidKind::*; +use VouchingStatus::*; + +use RuntimeOrigin as Origin; #[test] fn migration_works() { @@ -42,7 +44,12 @@ fn migration_works() { old::Strikes::::insert(30, 2); old::Strikes::::insert(40, 5); old::Payouts::::insert(20, vec![(1, 1)]); - old::Payouts::::insert(30, (0..=::MaxPayouts::get()).map(|i| (i as u64, i as u64)).collect::>()); + old::Payouts::::insert( + 30, + (0..=::MaxPayouts::get()) + .map(|i| (i as u64, i as u64)) + .collect::>(), + ); old::SuspendedMembers::::insert(40, true); old::Defender::::put(20); @@ -70,14 +77,17 @@ fn migration_works() { .collect::>(); old::Bids::::put(bids); - migrations::from_original::(&mut[][..]); + migrations::from_original::(&mut [][..]); migrations::assert_internal_consistency::(); - assert_eq!(membership(), vec![ - (10, MemberRecord { rank: 0, strikes: 0, vouching: None, index: 0 }), - (20, MemberRecord { rank: 0, strikes: 1, vouching: None, index: 1 }), - (30, MemberRecord { rank: 0, strikes: 2, vouching: Some(Vouching), index: 2 }), - ]); + assert_eq!( + membership(), + vec![ + (10, MemberRecord { rank: 0, strikes: 0, vouching: None, index: 0 }), + (20, MemberRecord { rank: 0, strikes: 1, vouching: None, index: 1 }), + (30, MemberRecord { rank: 0, strikes: 2, vouching: Some(Vouching), index: 2 }), + ] + ); assert_eq!(Payouts::::get(10), PayoutRecord::default()); let payouts = vec![(1, 1)].try_into().unwrap(); assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts }); @@ -87,9 +97,10 @@ fn migration_works() { .try_into() .unwrap(); assert_eq!(Payouts::::get(30), PayoutRecord { paid: 0, payouts }); - assert_eq!(SuspendedMembers::::iter().collect::>(), vec![ - (40, MemberRecord { rank: 0, strikes: 5, vouching: Some(Banned), index: 0 }), - ]); + assert_eq!( + SuspendedMembers::::iter().collect::>(), + vec![(40, MemberRecord { rank: 0, strikes: 5, vouching: Some(Banned), index: 0 }),] + ); let bids: BoundedVec<_, ::MaxBids> = (0..::MaxBids::get()) .map(|i| Bid { who: 100u128 + i as u128, @@ -101,26 +112,35 @@ fn migration_works() { .unwrap(); assert_eq!(Bids::::get(), bids); assert_eq!(RoundCount::::get(), 0); - assert_eq!(candidacies(), vec![ - (60, Candidacy { - round: 0, - kind: Deposit(100), - bid: 200, - tally: Tally { approvals: 1, rejections: 0 }, - skeptic_struck: false, - }), - (70, Candidacy { - round: 0, - kind: Vouch(30, 30), - bid: 100, - tally: Tally { approvals: 2, rejections: 1 }, - skeptic_struck: false, - }), - ]); - assert_eq!(Votes::::get(60, 10), Some(Vote { approve: true, weight: 1})); - assert_eq!(Votes::::get(70, 10), Some(Vote { approve: false, weight: 1})); - assert_eq!(Votes::::get(70, 20), Some(Vote { approve: true, weight: 1})); - assert_eq!(Votes::::get(70, 30), Some(Vote { approve: true, weight: 1})); + assert_eq!( + candidacies(), + vec![ + ( + 60, + Candidacy { + round: 0, + kind: Deposit(100), + bid: 200, + tally: Tally { approvals: 1, rejections: 0 }, + skeptic_struck: false, + } + ), + ( + 70, + Candidacy { + round: 0, + kind: Vouch(30, 30), + bid: 100, + tally: Tally { approvals: 2, rejections: 1 }, + skeptic_struck: false, + } + ), + ] + ); + assert_eq!(Votes::::get(60, 10), Some(Vote { approve: true, weight: 1 })); + assert_eq!(Votes::::get(70, 10), Some(Vote { approve: false, weight: 1 })); + assert_eq!(Votes::::get(70, 20), Some(Vote { approve: true, weight: 1 })); + assert_eq!(Votes::::get(70, 30), Some(Vote { approve: true, weight: 1 })); }); } @@ -133,9 +153,20 @@ fn founding_works() { assert_eq!(Pot::::get(), 0); // Account 1 is set as the founder origin // Account 5 cannot start a society - assert_noop!(Society::found_society(Origin::signed(5), 20, 100, 10, 2, 25, vec![]), BadOrigin); + assert_noop!( + Society::found_society(Origin::signed(5), 20, 100, 10, 2, 25, vec![]), + BadOrigin + ); // Account 1 can start a society, where 10 is the founding member - assert_ok!(Society::found_society(Origin::signed(1), 10, 100, 10, 2, 25, b"be cool".to_vec())); + assert_ok!(Society::found_society( + Origin::signed(1), + 10, + 100, + 10, + 2, + 25, + b"be cool".to_vec() + )); // Society members only include 10 assert_eq!(members(), vec![10]); // 10 is the head of the society @@ -250,7 +281,7 @@ fn bidding_works() { // No more candidates satisfy the requirements assert_eq!(candidacies(), vec![]); assert_ok!(Society::defender_vote(Origin::signed(10), true)); // Keep defender around - // Next period + // Next period run_to_block(16); // Same members assert_eq!(members(), vec![10, 30, 40, 50]); @@ -376,16 +407,25 @@ fn slash_payout_works() { assert_ok!(Society::vote(Origin::signed(10), 20, true)); conclude_intake(true, None); // payout in queue - assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(8, 1000)].try_into().unwrap() }); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![(8, 1000)].try_into().unwrap() } + ); assert_noop!(Society::payout(Origin::signed(20)), Error::::NoPayout); // slash payout assert_eq!(Society::slash_payout(&20, 500), 500); - assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(8, 500)].try_into().unwrap() }); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![(8, 500)].try_into().unwrap() } + ); run_to_block(8); // payout should be here, but 500 less assert_ok!(Society::payout(RuntimeOrigin::signed(20))); assert_eq!(Balances::free_balance(20), 550); - assert_eq!(Payouts::::get(20), PayoutRecord { paid: 500, payouts: Default::default() }); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 500, payouts: Default::default() } + ); }); } @@ -400,13 +440,25 @@ fn slash_payout_multi_works() { Society::bump_payout(&20, 15, 100); Society::bump_payout(&20, 20, 100); // payouts in queue - assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(5, 100), (10, 100), (15, 100), (20, 100)].try_into().unwrap() }); + assert_eq!( + Payouts::::get(20), + PayoutRecord { + paid: 0, + payouts: vec![(5, 100), (10, 100), (15, 100), (20, 100)].try_into().unwrap() + } + ); // slash payout assert_eq!(Society::slash_payout(&20, 250), 250); - assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(15, 50), (20, 100)].try_into().unwrap() }); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![(15, 50), (20, 100)].try_into().unwrap() } + ); // slash again assert_eq!(Society::slash_payout(&20, 50), 50); - assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(20, 100)].try_into().unwrap() }); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![(20, 100)].try_into().unwrap() } + ); }); } @@ -425,7 +477,6 @@ fn suspended_member_life_cycle_works() { next_intake(); conclude_intake(false, None); - // 2 strikes are accumulated, and 20 is suspended :( assert!(SuspendedMembers::::contains_key(20)); assert_eq!(members(), vec![10]); @@ -435,7 +486,10 @@ fn suspended_member_life_cycle_works() { assert_noop!(Society::payout(Origin::signed(20)), Error::::NotMember); // Normal people cannot make judgement - assert_noop!(Society::judge_suspended_member(Origin::signed(20), 20, true), Error::::NotFounder); + assert_noop!( + Society::judge_suspended_member(Origin::signed(20), 20, true), + Error::::NotFounder + ); // Suspension judgment origin can judge thee // Suspension judgement origin forgives the suspended member @@ -451,7 +505,10 @@ fn suspended_member_life_cycle_works() { // Cleaned up assert!(!SuspendedMembers::::contains_key(20)); assert_eq!(members(), vec![10]); - assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![].try_into().unwrap() }); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![].try_into().unwrap() } + ); }); } @@ -468,12 +525,15 @@ fn suspended_candidate_rejected_works() { // Rotation Period next_intake(); - assert_eq!(candidacies(), vec![ - (40, candidacy(1, 10, Deposit(25), 0, 0)), - (50, candidacy(1, 10, Deposit(25), 0, 0)), - (60, candidacy(1, 10, Deposit(25), 0, 0)), - (70, candidacy(1, 10, Deposit(25), 0, 0)), - ]); + assert_eq!( + candidacies(), + vec![ + (40, candidacy(1, 10, Deposit(25), 0, 0)), + (50, candidacy(1, 10, Deposit(25), 0, 0)), + (60, candidacy(1, 10, Deposit(25), 0, 0)), + (70, candidacy(1, 10, Deposit(25), 0, 0)), + ] + ); // Split vote over all. for &x in &[40, 50, 60, 70] { @@ -557,7 +617,10 @@ fn unpaid_vouch_works() { conclude_intake(true, None); assert_eq!(members(), vec![10, 20]); // Vouched user gets whatever remains after the voucher's reservation. - assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(8, 900)].try_into().unwrap() }); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![(8, 900)].try_into().unwrap() } + ); // 10 is no longer vouching assert_eq!(Members::::get(10).unwrap().vouching, None); }); @@ -580,9 +643,15 @@ fn paid_vouch_works() { assert_eq!(members(), vec![10, 20, 30]); // Voucher wins a portion of the payment - assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(8, 100)].try_into().unwrap() }); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![(8, 100)].try_into().unwrap() } + ); // Vouched user wins the rest - assert_eq!(Payouts::::get(30), PayoutRecord { paid: 0, payouts: vec![(8, 900)].try_into().unwrap() }); + assert_eq!( + Payouts::::get(30), + PayoutRecord { paid: 0, payouts: vec![(8, 900)].try_into().unwrap() } + ); // 20 is no longer vouching assert_eq!(Members::::get(20).unwrap().vouching, None); }); @@ -605,9 +674,15 @@ fn voucher_cannot_win_more_than_bid() { conclude_intake(true, None); assert_eq!(members(), vec![10, 20, 30]); // Voucher wins as much as the bid - assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(8, 100)].try_into().unwrap() }); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![(8, 100)].try_into().unwrap() } + ); // Vouched user gets nothing - assert_eq!(Payouts::::get(30), PayoutRecord { paid: 0, payouts: vec![].try_into().unwrap() }); + assert_eq!( + Payouts::::get(30), + PayoutRecord { paid: 0, payouts: vec![].try_into().unwrap() } + ); }); } @@ -746,12 +821,10 @@ fn founder_and_head_cannot_be_removed() { Skeptic::::put(50); conclude_intake(false, None); next_intake(); - assert_eq!(SuspendedMembers::::get(50), Some(MemberRecord { - rank: 0, - strikes: 3, - vouching: None, - index: 1, - })); + assert_eq!( + SuspendedMembers::::get(50), + Some(MemberRecord { rank: 0, strikes: 3, vouching: None, index: 1 }) + ); assert_eq!(members(), vec![10, 80]); }); } @@ -813,12 +886,10 @@ fn challenges_work() { next_challenge(); // 30 is suspended assert_eq!(members(), vec![10, 20, 40]); - assert_eq!(SuspendedMembers::::get(30), Some(MemberRecord { - rank: 0, - strikes: 0, - vouching: None, - index: 2, - })); + assert_eq!( + SuspendedMembers::::get(30), + Some(MemberRecord { rank: 0, strikes: 0, vouching: None, index: 2 }) + ); // Reset votes for last challenge assert_ok!(Society::cleanup_challenge(Origin::signed(0), 2, 10)); // New defender is chosen @@ -843,10 +914,22 @@ fn bad_vote_slash_works() { Society::bump_payout(&40, 5, 100); Society::bump_payout(&50, 5, 100); // Check starting point - assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); - assert_eq!(Payouts::::get(30), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); - assert_eq!(Payouts::::get(40), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); - assert_eq!(Payouts::::get(50), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() } + ); + assert_eq!( + Payouts::::get(30), + PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() } + ); + assert_eq!( + Payouts::::get(40), + PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() } + ); + assert_eq!( + Payouts::::get(50), + PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() } + ); // Create a new bid assert_ok!(Society::bid(Origin::signed(60), 1000)); next_intake(); @@ -863,10 +946,22 @@ fn bad_vote_slash_works() { assert_eq!(Members::::get(40).unwrap().strikes, 0); assert_eq!(Members::::get(50).unwrap().strikes, 0); // Their payout is slashed, a random person is rewarded - assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(5, 50)].try_into().unwrap() }); - assert_eq!(Payouts::::get(30), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); - assert_eq!(Payouts::::get(40), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); - assert_eq!(Payouts::::get(50), PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() }); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![(5, 50)].try_into().unwrap() } + ); + assert_eq!( + Payouts::::get(30), + PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() } + ); + assert_eq!( + Payouts::::get(40), + PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() } + ); + assert_eq!( + Payouts::::get(50), + PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() } + ); }); } @@ -881,10 +976,7 @@ fn user_cannot_bid_twice() { assert_noop!(Society::bid(Origin::signed(30), 100), Error::::AlreadyBid); // Cannot vouch when already bid place_members([50]); - assert_noop!( - Society::vouch(Origin::signed(50), 20, 100, 100), - Error::::AlreadyBid - ); + assert_noop!(Society::vouch(Origin::signed(50), 20, 100, 100), Error::::AlreadyBid); }); } @@ -934,8 +1026,14 @@ fn vouching_handles_removed_member_with_candidate() { conclude_intake(false, None); assert_eq!(members(), vec![10, 30]); // Payout does not go to removed member - assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![].try_into().unwrap() }); - assert_eq!(Payouts::::get(30), PayoutRecord { paid: 0, payouts: vec![(8, 1000)].try_into().unwrap() }); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![].try_into().unwrap() } + ); + assert_eq!( + Payouts::::get(30), + PayoutRecord { paid: 0, payouts: vec![(8, 1000)].try_into().unwrap() } + ); }); } @@ -1083,13 +1181,7 @@ fn zero_bid_works() { (60, candidacy(1, 400, Deposit(25), 0, 0)), ] ); - assert_eq!( - Bids::::get(), - vec![ - bid(20, Deposit(25), 0), - bid(40, Deposit(25), 0), - ], - ); + assert_eq!(Bids::::get(), vec![bid(20, Deposit(25), 0), bid(40, Deposit(25), 0),],); // A member votes for these candidates to join the society assert_ok!(Society::vote(Origin::signed(10), 30, true)); assert_ok!(Society::vote(Origin::signed(10), 50, true)); diff --git a/frame/society/src/weights.rs b/frame/society/src/weights.rs index dc9e0d3471692..f72ef1c30f6cb 100644 --- a/frame/society/src/weights.rs +++ b/frame/society/src/weights.rs @@ -75,63 +75,47 @@ impl WeightInfo for SubstrateWeight { // Storage: Society SuspendedMembers (r:1 w:0) // Storage: Society Parameters (r:1 w:0) fn bid() -> Weight { - (56_738_000 as Weight) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + Weight::zero() } // Storage: Society Bids (r:1 w:1) fn unbid() -> Weight { - (45_204_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + Weight::zero() } // Storage: Society Bids (r:1 w:1) // Storage: Society Candidates (r:1 w:0) // Storage: Society Members (r:2 w:1) // Storage: Society SuspendedMembers (r:1 w:0) fn vouch() -> Weight { - (43_566_000 as Weight) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + Weight::zero() } // Storage: Society Bids (r:1 w:1) // Storage: Society Members (r:1 w:1) fn unvouch() -> Weight { - (33_741_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + Weight::zero() } // Storage: Society Candidates (r:1 w:1) // Storage: Society Members (r:1 w:0) // Storage: Society Votes (r:1 w:1) fn vote() -> Weight { - (44_890_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + Weight::zero() } // Storage: Society Defending (r:1 w:1) // Storage: Society Members (r:1 w:0) // Storage: Society ChallengeRoundCount (r:1 w:0) // Storage: Society DefenderVotes (r:1 w:1) fn defender_vote() -> Weight { - (41_752_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + Weight::zero() } // Storage: Society Members (r:1 w:0) // Storage: Society Payouts (r:1 w:1) // Storage: System Account (r:1 w:1) fn payout() -> Weight { - (58_145_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + Weight::zero() } // Storage: Society Members (r:1 w:1) // Storage: Society Payouts (r:1 w:1) fn waive_repay() -> Weight { - (37_796_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + Weight::zero() } // Storage: Society Head (r:1 w:1) // Storage: Society MemberCount (r:1 w:1) @@ -141,9 +125,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Society Members (r:0 w:1) // Storage: Society Parameters (r:0 w:1) fn found_society() -> Weight { - (37_221_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(7 as Weight)) + Weight::zero() } // Storage: Society Founder (r:1 w:1) // Storage: Society MemberCount (r:1 w:1) @@ -162,26 +144,20 @@ impl WeightInfo for SubstrateWeight { // Storage: Society Parameters (r:0 w:1) // Storage: Society NextHead (r:0 w:1) fn dissolve() -> Weight { - (105_209_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(30 as Weight)) + Weight::zero() } // Storage: Society Founder (r:1 w:0) // Storage: Society SuspendedMembers (r:1 w:1) // Storage: Society Payouts (r:1 w:0) // Storage: Society Pot (r:1 w:1) fn judge_suspended_member() -> Weight { - (39_916_000 as Weight) - .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + Weight::zero() } // Storage: Society Founder (r:1 w:0) // Storage: Society MemberCount (r:1 w:0) // Storage: Society Parameters (r:0 w:1) fn set_parameters() -> Weight { - (28_251_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + Weight::zero() } // Storage: Society Candidates (r:1 w:1) // Storage: Society RoundCount (r:1 w:0) @@ -190,9 +166,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Society Members (r:1 w:1) // Storage: Society Parameters (r:1 w:0) fn punish_skeptic() -> Weight { - (40_041_000 as Weight) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + Weight::zero() } // Storage: Society Candidates (r:1 w:1) // Storage: Society RoundCount (r:1 w:0) @@ -203,9 +177,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Society MemberByIndex (r:0 w:1) // Storage: Society Members (r:0 w:1) fn claim_membership() -> Weight { - (64_305_000 as Weight) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) - .saturating_add(T::DbWeight::get().writes(6 as Weight)) + Weight::zero() } // Storage: Society Founder (r:1 w:0) // Storage: Society Candidates (r:1 w:1) @@ -217,46 +189,34 @@ impl WeightInfo for SubstrateWeight { // Storage: Society MemberByIndex (r:0 w:1) // Storage: Society Members (r:0 w:1) fn bestow_membership() -> Weight { - (68_334_000 as Weight) - .saturating_add(T::DbWeight::get().reads(7 as Weight)) - .saturating_add(T::DbWeight::get().writes(6 as Weight)) + Weight::zero() } // Storage: Society Founder (r:1 w:0) // Storage: Society Candidates (r:1 w:1) // Storage: Society RoundCount (r:1 w:0) fn kick_candidate() -> Weight { - (23_574_000 as Weight) - .saturating_add(T::DbWeight::get().reads(3 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + Weight::zero() } // Storage: Society Candidates (r:1 w:1) // Storage: Society RoundCount (r:1 w:0) fn resign_candidacy() -> Weight { - (22_284_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + Weight::zero() } // Storage: Society Candidates (r:1 w:1) // Storage: Society RoundCount (r:1 w:0) fn drop_candidate() -> Weight { - (32_618_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + Weight::zero() } // Storage: Society Candidates (r:1 w:0) // Storage: Society VoteClearCursor (r:1 w:0) // Storage: Society Votes (r:0 w:2) fn cleanup_candidacy() -> Weight { - (27_936_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + Weight::zero() } // Storage: Society ChallengeRoundCount (r:1 w:0) // Storage: Society DefenderVotes (r:0 w:1) fn cleanup_challenge() -> Weight { - (18_411_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + Weight::zero() } } @@ -268,63 +228,47 @@ impl WeightInfo for () { // Storage: Society SuspendedMembers (r:1 w:0) // Storage: Society Parameters (r:1 w:0) fn bid() -> Weight { - (56_738_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + Weight::zero() } // Storage: Society Bids (r:1 w:1) fn unbid() -> Weight { - (45_204_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + Weight::zero() } // Storage: Society Bids (r:1 w:1) // Storage: Society Candidates (r:1 w:0) // Storage: Society Members (r:2 w:1) // Storage: Society SuspendedMembers (r:1 w:0) fn vouch() -> Weight { - (43_566_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + Weight::zero() } // Storage: Society Bids (r:1 w:1) // Storage: Society Members (r:1 w:1) fn unvouch() -> Weight { - (33_741_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + Weight::zero() } // Storage: Society Candidates (r:1 w:1) // Storage: Society Members (r:1 w:0) // Storage: Society Votes (r:1 w:1) fn vote() -> Weight { - (44_890_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + Weight::zero() } // Storage: Society Defending (r:1 w:1) // Storage: Society Members (r:1 w:0) // Storage: Society ChallengeRoundCount (r:1 w:0) // Storage: Society DefenderVotes (r:1 w:1) fn defender_vote() -> Weight { - (41_752_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + Weight::zero() } // Storage: Society Members (r:1 w:0) // Storage: Society Payouts (r:1 w:1) // Storage: System Account (r:1 w:1) fn payout() -> Weight { - (58_145_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + Weight::zero() } // Storage: Society Members (r:1 w:1) // Storage: Society Payouts (r:1 w:1) fn waive_repay() -> Weight { - (37_796_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + Weight::zero() } // Storage: Society Head (r:1 w:1) // Storage: Society MemberCount (r:1 w:1) @@ -334,9 +278,7 @@ impl WeightInfo for () { // Storage: Society Members (r:0 w:1) // Storage: Society Parameters (r:0 w:1) fn found_society() -> Weight { - (37_221_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(7 as Weight)) + Weight::zero() } // Storage: Society Founder (r:1 w:1) // Storage: Society MemberCount (r:1 w:1) @@ -355,26 +297,20 @@ impl WeightInfo for () { // Storage: Society Parameters (r:0 w:1) // Storage: Society NextHead (r:0 w:1) fn dissolve() -> Weight { - (105_209_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(30 as Weight)) + Weight::zero() } // Storage: Society Founder (r:1 w:0) // Storage: Society SuspendedMembers (r:1 w:1) // Storage: Society Payouts (r:1 w:0) // Storage: Society Pot (r:1 w:1) fn judge_suspended_member() -> Weight { - (39_916_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + Weight::zero() } // Storage: Society Founder (r:1 w:0) // Storage: Society MemberCount (r:1 w:0) // Storage: Society Parameters (r:0 w:1) fn set_parameters() -> Weight { - (28_251_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + Weight::zero() } // Storage: Society Candidates (r:1 w:1) // Storage: Society RoundCount (r:1 w:0) @@ -383,9 +319,7 @@ impl WeightInfo for () { // Storage: Society Members (r:1 w:1) // Storage: Society Parameters (r:1 w:0) fn punish_skeptic() -> Weight { - (40_041_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(6 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + Weight::zero() } // Storage: Society Candidates (r:1 w:1) // Storage: Society RoundCount (r:1 w:0) @@ -396,9 +330,7 @@ impl WeightInfo for () { // Storage: Society MemberByIndex (r:0 w:1) // Storage: Society Members (r:0 w:1) fn claim_membership() -> Weight { - (64_305_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(6 as Weight)) - .saturating_add(RocksDbWeight::get().writes(6 as Weight)) + Weight::zero() } // Storage: Society Founder (r:1 w:0) // Storage: Society Candidates (r:1 w:1) @@ -410,45 +342,33 @@ impl WeightInfo for () { // Storage: Society MemberByIndex (r:0 w:1) // Storage: Society Members (r:0 w:1) fn bestow_membership() -> Weight { - (68_334_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(7 as Weight)) - .saturating_add(RocksDbWeight::get().writes(6 as Weight)) + Weight::zero() } // Storage: Society Founder (r:1 w:0) // Storage: Society Candidates (r:1 w:1) // Storage: Society RoundCount (r:1 w:0) fn kick_candidate() -> Weight { - (23_574_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + Weight::zero() } // Storage: Society Candidates (r:1 w:1) // Storage: Society RoundCount (r:1 w:0) fn resign_candidacy() -> Weight { - (22_284_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + Weight::zero() } // Storage: Society Candidates (r:1 w:1) // Storage: Society RoundCount (r:1 w:0) fn drop_candidate() -> Weight { - (32_618_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + Weight::zero() } // Storage: Society Candidates (r:1 w:0) // Storage: Society VoteClearCursor (r:1 w:0) // Storage: Society Votes (r:0 w:2) fn cleanup_candidacy() -> Weight { - (27_936_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + Weight::zero() } // Storage: Society ChallengeRoundCount (r:1 w:0) // Storage: Society DefenderVotes (r:0 w:1) fn cleanup_challenge() -> Weight { - (18_411_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + Weight::zero() } } diff --git a/frame/support/src/storage/mod.rs b/frame/support/src/storage/mod.rs index d80058c2824a3..90249561bd236 100644 --- a/frame/support/src/storage/mod.rs +++ b/frame/support/src/storage/mod.rs @@ -196,11 +196,10 @@ pub trait StorageMap { /// Mutate the value under a key if the value already exists. Do nothing and return the default /// value if not. - fn mutate_extant< - KeyArg: EncodeLike, - R: Default, - F: FnOnce(&mut V) -> R, - >(key: KeyArg, f: F) -> R { + fn mutate_extant, R: Default, F: FnOnce(&mut V) -> R>( + key: KeyArg, + f: F, + ) -> R { Self::mutate_exists(key, |maybe_v| match maybe_v { Some(ref mut value) => f(value), None => R::default(), diff --git a/frame/support/src/storage/types/map.rs b/frame/support/src/storage/types/map.rs index 7655922761051..fa1e92b8019c6 100644 --- a/frame/support/src/storage/types/map.rs +++ b/frame/support/src/storage/types/map.rs @@ -378,7 +378,9 @@ where /// particular order. /// /// If you alter the map while doing this, you'll get undefined results. - pub fn iter_from_key(starting_key: impl EncodeLike) -> crate::storage::PrefixIterator<(Key, Value)> { + pub fn iter_from_key( + starting_key: impl EncodeLike, + ) -> crate::storage::PrefixIterator<(Key, Value)> { Self::iter_from(Self::hashed_key_for(starting_key)) } @@ -401,7 +403,9 @@ where /// order. /// /// If you alter the map while doing this, you'll get undefined results. - pub fn iter_keys_from_key(starting_key: impl EncodeLike) -> crate::storage::KeyPrefixIterator { + pub fn iter_keys_from_key( + starting_key: impl EncodeLike, + ) -> crate::storage::KeyPrefixIterator { Self::iter_keys_from(Self::hashed_key_for(starting_key)) } diff --git a/primitives/core/src/ed25519.rs b/primitives/core/src/ed25519.rs index 27090112ff5e5..9c0bd8c0d4bdf 100644 --- a/primitives/core/src/ed25519.rs +++ b/primitives/core/src/ed25519.rs @@ -32,8 +32,8 @@ use scale_info::TypeInfo; #[cfg(feature = "std")] use crate::crypto::Ss58Codec; use crate::crypto::{ - CryptoType, CryptoTypeId, CryptoTypePublicPair, Derive, Public as TraitPublic, UncheckedFrom, - FromEntropy, + CryptoType, CryptoTypeId, CryptoTypePublicPair, Derive, FromEntropy, Public as TraitPublic, + UncheckedFrom, }; #[cfg(feature = "full_crypto")] use crate::crypto::{DeriveJunction, Pair as TraitPair, SecretStringError}; diff --git a/primitives/core/src/sr25519.rs b/primitives/core/src/sr25519.rs index 79784a79d035c..1cba83c610f40 100644 --- a/primitives/core/src/sr25519.rs +++ b/primitives/core/src/sr25519.rs @@ -39,8 +39,8 @@ use substrate_bip39::mini_secret_from_entropy; use crate::{ crypto::{ - ByteArray, CryptoType, CryptoTypeId, CryptoTypePublicPair, Derive, Public as TraitPublic, - UncheckedFrom, FromEntropy, + ByteArray, CryptoType, CryptoTypeId, CryptoTypePublicPair, Derive, FromEntropy, + Public as TraitPublic, UncheckedFrom, }, hash::{H256, H512}, }; From d9dce35ac7410c5cfab82eba7e91faad32e122da Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 7 Dec 2022 16:26:24 +0000 Subject: [PATCH 27/43] Fixes --- frame/society/Cargo.toml | 6 ++++-- frame/society/src/benchmarking.rs | 18 +++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/frame/society/Cargo.toml b/frame/society/Cargo.toml index 6b5eb0fce31b6..9cb4fd12bcc09 100644 --- a/frame/society/Cargo.toml +++ b/frame/society/Cargo.toml @@ -16,10 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } rand_chacha = { version = "0.2", default-features = false } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } @@ -44,8 +44,10 @@ std = [ "sp-io/std", ] runtime-benchmarks = [ - "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/society/src/benchmarking.rs b/frame/society/src/benchmarking.rs index 4cd4abe5952dc..8ef6d0751977f 100644 --- a/frame/society/src/benchmarking.rs +++ b/frame/society/src/benchmarking.rs @@ -36,12 +36,13 @@ fn mock_balance_deposit, I: 'static>() -> BalanceOf { fn setup_society, I: 'static>() -> Result { let origin = T::FounderSetOrigin::successful_origin(); let founder: T::AccountId = account("founder", 0, 0); + let founder_lookup: ::Source = T::Lookup::unlookup(founder.clone()); let max_members = 5u32; let max_intake = 3u32; let max_strikes = 3u32; Society::::found_society( origin, - founder.clone(), + founder_lookup, max_members, max_intake, max_strikes, @@ -108,7 +109,8 @@ benchmarks_instance_pallet! { let vouched: T::AccountId = account("vouched", 0, 0); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); let _ = Society::::insert_member(&caller, 1u32.into()); - }: _(RawOrigin::Signed(caller.clone()), vouched.clone(), 0u32.into(), 0u32.into()) + let vouched_lookup: ::Source = T::Lookup::unlookup(vouched.clone()); + }: _(RawOrigin::Signed(caller.clone()), vouched_lookup, 0u32.into(), 0u32.into()) verify { let bids = Bids::::get(); let vouched_bid: Bid> = Bid { @@ -191,7 +193,8 @@ benchmarks_instance_pallet! { found_society { let founder: T::AccountId = whitelisted_caller(); let can_found = T::FounderSetOrigin::successful_origin(); - }: _(can_found, founder.clone(), 5, 3, 3, mock_balance_deposit::(), b"benchmarking-society".to_vec()) + let founder_lookup: ::Source = T::Lookup::unlookup(founder.clone()); + }: _(can_found, founder_lookup, 5, 3, 3, mock_balance_deposit::(), b"benchmarking-society".to_vec()) verify { assert_eq!(Founder::::get(), Some(founder.clone())); } @@ -217,9 +220,10 @@ benchmarks_instance_pallet! { judge_suspended_member { let founder = setup_society::()?; let caller: T::AccountId = whitelisted_caller(); + let caller_lookup: ::Source = T::Lookup::unlookup(caller.clone()); let _ = Society::::insert_member(&caller, 0u32.into()); let _ = Society::::suspend_member(&caller); - }: _(RawOrigin::Signed(founder), caller.clone(), false) + }: _(RawOrigin::Signed(founder), caller_lookup, false) verify { assert_eq!(SuspendedMembers::::contains_key(&caller), false); } @@ -333,7 +337,7 @@ benchmarks_instance_pallet! { impl_benchmark_test_suite!( Society, - crate::tests_composite::ExtBuilder::default().build(), - crate::tests_composite::Test, - ) + sp_io::TestExternalities::from(frame_system::GenesisConfig::default().build_storage::().unwrap()), + crate::mock::Test + ); } From 4c60eef62ba0c98b0d3b89002060b387db32be68 Mon Sep 17 00:00:00 2001 From: Gav Date: Thu, 8 Dec 2022 13:36:22 +0000 Subject: [PATCH 28/43] Spelling --- frame/democracy/src/vote.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/democracy/src/vote.rs b/frame/democracy/src/vote.rs index 122f54febd8cf..be3b7c50adacb 100644 --- a/frame/democracy/src/vote.rs +++ b/frame/democracy/src/vote.rs @@ -202,7 +202,7 @@ impl< .rejig(now); } - /// The amount of this account's balance that much currently be locked due to voting. + /// The amount of this account's balance that must currently be locked due to voting. pub fn locked_balance(&self) -> Balance { match self { Voting::Direct { votes, prior, .. } => From 3f119f1b892cf701bf558f3955039fedc426a560 Mon Sep 17 00:00:00 2001 From: Gav Date: Mon, 12 Jun 2023 12:15:55 +0100 Subject: [PATCH 29/43] Fix benchmarks --- Cargo.lock | 4 +- frame/society/src/benchmarking.rs | 61 ++++++++++++++++++++++++------- frame/society/src/lib.rs | 1 + primitives/core/Cargo.toml | 2 +- primitives/core/src/ed25519.rs | 4 +- primitives/core/src/sr25519.rs | 5 ++- 6 files changed, 58 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a0b113dedb8d..a9edfd6f12c7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -979,9 +979,9 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "bounded-collections" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fbd1d11282a1eb134d3c3b7cf8ce213b5161c6e5f73fb1b98618482c606b64" +checksum = "eb5b05133427c07c4776906f673ccf36c21b102c9829c641a5b56bd151d44fd6" dependencies = [ "log", "parity-scale-codec", diff --git a/frame/society/src/benchmarking.rs b/frame/society/src/benchmarking.rs index 8ef6d0751977f..2f1dfe021be1f 100644 --- a/frame/society/src/benchmarking.rs +++ b/frame/society/src/benchmarking.rs @@ -32,9 +32,33 @@ fn mock_balance_deposit, I: 'static>() -> BalanceOf { T::Currency::minimum_balance().saturating_mul(1_000u32.into()) } +fn make_deposit, I: 'static>(who: &T::AccountId) -> BalanceOf { + let amount = mock_balance_deposit::(); + let required = amount.saturating_add(T::Currency::minimum_balance()); + if T::Currency::free_balance(who) < required { + T::Currency::make_free_balance_be(who, required); + } + T::Currency::reserve(who, amount).expect("Pre-funded account; qed"); + amount +} + +fn make_bid, I: 'static>( + who: &T::AccountId, +) -> BidKind> { + BidKind::Deposit(make_deposit::(who)) +} + +fn fund_society, I: 'static>() { + T::Currency::make_free_balance_be( + &Society::::account_id(), + BalanceOf::::max_value(), + ); + Pot::::put(&BalanceOf::::max_value()); +} + // Set up Society fn setup_society, I: 'static>() -> Result { - let origin = T::FounderSetOrigin::successful_origin(); + let origin = T::FounderSetOrigin::try_successful_origin().map_err(|_| "No origin")?; let founder: T::AccountId = account("founder", 0, 0); let founder_lookup: ::Source = T::Lookup::unlookup(founder.clone()); let max_members = 5u32; @@ -49,6 +73,16 @@ fn setup_society, I: 'static>() -> Result(), b"benchmarking-society".to_vec(), )?; + T::Currency::make_free_balance_be( + &Society::::account_id(), + T::Currency::minimum_balance(), + ); + Ok(founder) +} + +fn setup_funded_society, I: 'static>() -> Result { + let founder = setup_society::()?; + fund_society::(); Ok(founder) } @@ -58,10 +92,9 @@ fn add_candidate, I: 'static>( skeptic_struck: bool, ) -> T::AccountId { let candidate: T::AccountId = account(name, 0, 0); - T::Currency::make_free_balance_be(&candidate, BalanceOf::::max_value()); let candidacy = Candidacy { round: RoundCount::::get(), - kind: BidKind::Deposit(mock_balance_deposit::()), + kind: make_bid::(&candidate), bid: 0u32.into(), tally, skeptic_struck, @@ -92,11 +125,12 @@ benchmarks_instance_pallet! { } unbid { + println!("MD: {:?}", mock_balance_deposit::()); let founder = setup_society::()?; let caller: T::AccountId = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); let mut bids = Bids::::get(); - Society::::insert_bid(&mut bids, &caller, 10u32.into(), BidKind::Deposit(mock_balance_deposit::())); + Society::::insert_bid(&mut bids, &caller, 10u32.into(), make_bid::(&caller)); Bids::::put(bids); }: _(RawOrigin::Signed(caller.clone())) verify { @@ -162,14 +196,12 @@ benchmarks_instance_pallet! { } payout { - let founder = setup_society::()?; + let founder = setup_funded_society::()?; + // Payee's account already exists and is a member. let caller: T::AccountId = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, mock_balance_deposit::()); let _ = Society::::insert_member(&caller, 0u32.into()); - T::Currency::make_free_balance_be(&Society::::payouts(), BalanceOf::::max_value()); - let mut pot = >::get(); - pot = pot.saturating_add(BalanceOf::::max_value()); - Pot::::put(&pot); + // Introduce payout. Society::::bump_payout(&caller, 0u32.into(), 1u32.into()); }: _(RawOrigin::Signed(caller.clone())) verify { @@ -178,11 +210,10 @@ benchmarks_instance_pallet! { } waive_repay { - let founder = setup_society::()?; + let founder = setup_funded_society::()?; let caller: T::AccountId = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); let _ = Society::::insert_member(&caller, 0u32.into()); - T::Currency::make_free_balance_be(&Society::::payouts(), BalanceOf::::max_value()); Society::::bump_payout(&caller, 0u32.into(), 1u32.into()); }: _(RawOrigin::Signed(caller.clone()), 1u32.into()) verify { @@ -192,7 +223,7 @@ benchmarks_instance_pallet! { found_society { let founder: T::AccountId = whitelisted_caller(); - let can_found = T::FounderSetOrigin::successful_origin(); + let can_found = T::FounderSetOrigin::try_successful_origin().map_err(|_| "No origin")?; let founder_lookup: ::Source = T::Lookup::unlookup(founder.clone()); }: _(can_found, founder_lookup, 5, 3, 3, mock_balance_deposit::(), b"benchmarking-society".to_vec()) verify { @@ -246,8 +277,10 @@ benchmarks_instance_pallet! { let skeptic: T::AccountId = account("skeptic", 0, 0); let _ = Society::::insert_member(&skeptic, 0u32.into()); Skeptic::::put(&skeptic); - frame_system::Pallet::::set_block_number(T::VotingPeriod::get() + 1u32.into()); - }: _(RawOrigin::Signed(candidate.clone())) + if let Period::Voting { more, .. } = Society::::period() { + frame_system::Pallet::::set_block_number(frame_system::Pallet::::block_number() + more); + } + }: _(RawOrigin::Signed(candidate.clone())) verify { let candidacy = Candidates::::get(&candidate).unwrap(); assert_eq!(candidacy.skeptic_struck, true); diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index a1954758b4d02..427856b8b131e 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -1380,6 +1380,7 @@ pub mod pallet { /// Remove up to `max` stale votes for the defender in the given `challenge_round`. /// /// May be called by any Signed origin, but only after the challenge round is ended. + #[pallet::call_index(19)] #[pallet::weight(T::WeightInfo::cleanup_challenge())] pub fn cleanup_challenge( origin: OriginFor, diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 98df775d289c1..6c043d5a21499 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -17,7 +17,7 @@ codec = { package = "parity-scale-codec", version = "3.2.2", default-features = scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } serde = { version = "1.0.163", optional = true, default-features = false, features = ["derive", "alloc"] } -bounded-collections = { version = "0.1.7", default-features = false } +bounded-collections = { version = "0.1.8", default-features = false } primitive-types = { version = "0.12.0", default-features = false, features = ["codec", "scale-info"] } impl-serde = { version = "0.4.0", default-features = false, optional = true } hash-db = { version = "0.16.0", default-features = false } diff --git a/primitives/core/src/ed25519.rs b/primitives/core/src/ed25519.rs index 87213aca9bf89..ba947990e1c0b 100644 --- a/primitives/core/src/ed25519.rs +++ b/primitives/core/src/ed25519.rs @@ -31,7 +31,9 @@ use scale_info::TypeInfo; #[cfg(feature = "serde")] use crate::crypto::Ss58Codec; -use crate::crypto::{CryptoType, CryptoTypeId, Derive, FromEntropy, Public as TraitPublic, UncheckedFrom}; +use crate::crypto::{ + CryptoType, CryptoTypeId, Derive, FromEntropy, Public as TraitPublic, UncheckedFrom, +}; #[cfg(feature = "full_crypto")] use crate::crypto::{DeriveError, DeriveJunction, Pair as TraitPair, SecretStringError}; #[cfg(feature = "full_crypto")] diff --git a/primitives/core/src/sr25519.rs b/primitives/core/src/sr25519.rs index e1abbb4ce296a..963ca1c3fedc3 100644 --- a/primitives/core/src/sr25519.rs +++ b/primitives/core/src/sr25519.rs @@ -37,7 +37,10 @@ use schnorrkel::{ use sp_std::vec::Vec; use crate::{ - crypto::{ByteArray, CryptoType, CryptoTypeId, Derive, FromEntropy, Public as TraitPublic, UncheckedFrom}, + crypto::{ + ByteArray, CryptoType, CryptoTypeId, Derive, FromEntropy, Public as TraitPublic, + UncheckedFrom, + }, hash::{H256, H512}, }; use codec::{Decode, Encode, MaxEncodedLen}; From 2440062c8923b31ab33ac0c5c649c4ff2ce59859 Mon Sep 17 00:00:00 2001 From: Gav Date: Mon, 12 Jun 2023 12:18:32 +0100 Subject: [PATCH 30/43] Another fix --- frame/society/src/lib.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 6d5b52a82ad9d..c8e87c9fd7229 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -262,13 +262,8 @@ use frame_support::{ pallet_prelude::*, storage::KeyLenOf, traits::{ -<<<<<<< HEAD - BalanceStatus, Currency, EnsureOrigin, ExistenceRequirement::AllowDeath, Imbalance, - OnUnbalanced, Randomness, ReservableCurrency, -======= - BalanceStatus, ChangeMembers, Currency, EnsureOrigin, EnsureOriginWithArg, + BalanceStatus, Currency, EnsureOrigin, EnsureOriginWithArg, ExistenceRequirement::AllowDeath, Imbalance, OnUnbalanced, Randomness, ReservableCurrency, ->>>>>>> origin/master }, PalletId, }; From 7811ac156a02cc8f3e9133c1ab306c9c375e3386 Mon Sep 17 00:00:00 2001 From: Gav Date: Mon, 12 Jun 2023 12:31:03 +0100 Subject: [PATCH 31/43] Remove println --- frame/society/src/benchmarking.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/society/src/benchmarking.rs b/frame/society/src/benchmarking.rs index 2f1dfe021be1f..f690abc5745a3 100644 --- a/frame/society/src/benchmarking.rs +++ b/frame/society/src/benchmarking.rs @@ -125,7 +125,6 @@ benchmarks_instance_pallet! { } unbid { - println!("MD: {:?}", mock_balance_deposit::()); let founder = setup_society::()?; let caller: T::AccountId = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); From 687d91436316d803bb972abcf0dd96800736f11a Mon Sep 17 00:00:00 2001 From: Gav Date: Mon, 12 Jun 2023 18:49:34 +0100 Subject: [PATCH 32/43] Society migration --- Cargo.lock | 1 + frame/society/Cargo.toml | 1 + frame/society/src/benchmarking.rs | 4 +++ frame/society/src/migrations.rs | 43 ++++++++++++++++++++++++++++--- 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a9edfd6f12c7d..a91d643d4564d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7289,6 +7289,7 @@ dependencies = [ "frame-support-test", "frame-system", "hex-literal", + "log", "pallet-balances", "parity-scale-codec", "rand_chacha 0.2.2", diff --git a/frame/society/Cargo.toml b/frame/society/Cargo.toml index 23a8cacc17301..7bd2124f132f9 100644 --- a/frame/society/Cargo.toml +++ b/frame/society/Cargo.toml @@ -14,6 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +log = { version = "0.4.17", default-features = false } rand_chacha = { version = "0.2", default-features = false } scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } diff --git a/frame/society/src/benchmarking.rs b/frame/society/src/benchmarking.rs index f690abc5745a3..a7ad6b4a65864 100644 --- a/frame/society/src/benchmarking.rs +++ b/frame/society/src/benchmarking.rs @@ -77,6 +77,10 @@ fn setup_society, I: 'static>() -> Result::account_id(), T::Currency::minimum_balance(), ); + T::Currency::make_free_balance_be( + &Society::::payouts(), + T::Currency::minimum_balance(), + ); Ok(founder) } diff --git a/frame/society/src/migrations.rs b/frame/society/src/migrations.rs index 8691e4d59b317..f4c52e2b02c47 100644 --- a/frame/society/src/migrations.rs +++ b/frame/society/src/migrations.rs @@ -18,7 +18,41 @@ //! # Migrations for Society Pallet use super::*; -use frame_support::traits::Instance; +use frame_support::traits::{Instance, OnRuntimeUpgrade}; + +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + +/// The log target. +const TARGET: &'static str = "runtime::society::migration"; + +/// This migration moves all the state to v2 of Society. +pub struct MigrateToV2, I: 'static, PastPayouts>(sp_std::marker::PhantomData<(T, I, PastPayouts)>); + +impl< + T: Config, + I: Instance + 'static, + PastPayouts: Get::AccountId, BalanceOf)>> +> OnRuntimeUpgrade for MigrateToV2 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + assert!( + can_migrate(), + "Invalid (perhaps already-migrated) state detected prior to migration" + ); + Ok(Vec::new()) + } + + fn on_runtime_upgrade() -> Weight { + from_original::(&mut PastPayouts::get()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_: Vec) -> Result<(), TryRuntimeError> { + assert_internal_consistency::(); + Ok(()) + } +} pub(crate) mod old { use super::*; @@ -160,12 +194,13 @@ pub fn assert_internal_consistency, I: Instance + 'static>() { pub fn from_original, I: Instance + 'static>( past_payouts: &mut [(::AccountId, BalanceOf)], -) { +) -> Weight { // First check that this is the original state layout. This is easy since the original layout // contained the Members value, and this value no longer exists in the new layout. if !old::Members::::exists() { + log::warn!(target: TARGET, "Skipping MigrateToV2 migration since it appears unapplicable"); // Already migrated or no data to migrate: Bail. - return + return T::DbWeight::get().reads(1) } // Migrate Bids from old::Bids (just a trunctation). @@ -240,6 +275,8 @@ pub fn from_original, I: Instance + 'static>( // We give the current defender the benefit of the doubt. old::Defender::::kill(); let _ = old::DefenderVotes::::clear(u32::MAX, None); + + T::BlockWeights::get().max_block } pub fn from_raw_past_payouts, I: Instance + 'static>( From ab1eafdb1f171e4c39b948304236507ac5c67cd8 Mon Sep 17 00:00:00 2001 From: Gav Date: Tue, 13 Jun 2023 19:16:17 +0100 Subject: [PATCH 33/43] Fixes --- frame/society/src/benchmarking.rs | 5 +---- frame/society/src/lib.rs | 3 ++- frame/society/src/migrations.rs | 18 ++++++++++-------- frame/society/src/tests.rs | 3 +++ 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/frame/society/src/benchmarking.rs b/frame/society/src/benchmarking.rs index a7ad6b4a65864..1d6f44a1064da 100644 --- a/frame/society/src/benchmarking.rs +++ b/frame/society/src/benchmarking.rs @@ -77,10 +77,7 @@ fn setup_society, I: 'static>() -> Result::account_id(), T::Currency::minimum_balance(), ); - T::Currency::make_free_balance_be( - &Society::::payouts(), - T::Currency::minimum_balance(), - ); + T::Currency::make_free_balance_be(&Society::::payouts(), T::Currency::minimum_balance()); Ok(founder) } diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index c8e87c9fd7229..48ab159f7cbf7 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -1700,7 +1700,8 @@ impl, I: 'static> Pallet { BidKind::Deposit(deposit) => { let pot = Self::account_id(); let free = BalanceStatus::Free; - debug_assert!(T::Currency::repatriate_reserved(&who, &pot, *deposit, free).is_ok()); + let r = T::Currency::repatriate_reserved(&who, &pot, *deposit, free); + debug_assert!(r.is_ok()); }, BidKind::Vouch(voucher, _) => { Members::::mutate_extant(voucher, |record| { diff --git a/frame/society/src/migrations.rs b/frame/society/src/migrations.rs index f4c52e2b02c47..0974de9a4a51b 100644 --- a/frame/society/src/migrations.rs +++ b/frame/society/src/migrations.rs @@ -27,13 +27,16 @@ use sp_runtime::TryRuntimeError; const TARGET: &'static str = "runtime::society::migration"; /// This migration moves all the state to v2 of Society. -pub struct MigrateToV2, I: 'static, PastPayouts>(sp_std::marker::PhantomData<(T, I, PastPayouts)>); +pub struct MigrateToV2, I: 'static, PastPayouts>( + sp_std::marker::PhantomData<(T, I, PastPayouts)>, +); impl< - T: Config, - I: Instance + 'static, - PastPayouts: Get::AccountId, BalanceOf)>> -> OnRuntimeUpgrade for MigrateToV2 { + T: Config, + I: Instance + 'static, + PastPayouts: Get::AccountId, BalanceOf)>>, + > OnRuntimeUpgrade for MigrateToV2 +{ #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, TryRuntimeError> { assert!( @@ -280,11 +283,10 @@ pub fn from_original, I: Instance + 'static>( } pub fn from_raw_past_payouts, I: Instance + 'static>( - past_payouts_raw: &[(&[u8], u128)], + past_payouts_raw: impl Iterator, ) -> Vec<(::AccountId, BalanceOf)> { past_payouts_raw - .iter() - .filter_map(|(x, y)| Some((Decode::decode(&mut x.as_ref()).ok()?, (*y).try_into().ok()?))) + .filter_map(|(x, y)| Some((Decode::decode(&mut &x[..]).ok()?, y.try_into().ok()?))) .collect() } diff --git a/frame/society/src/tests.rs b/frame/society/src/tests.rs index 67e9829e8b0d5..4a90ad52112d8 100644 --- a/frame/society/src/tests.rs +++ b/frame/society/src/tests.rs @@ -566,6 +566,9 @@ fn suspended_candidate_rejected_works() { assert_eq!(Balances::reserved_balance(50), 0); assert_eq!(Balances::free_balance(Society::account_id()), 9980); + assert_eq!(Balances::free_balance(70), 25); + assert_eq!(Balances::reserved_balance(70), 25); + assert_ok!(Society::kick_candidate(Origin::signed(10), 70)); assert_eq!(members(), vec![10, 20, 30, 40, 50]); assert_eq!(candidates(), vec![60]); From c01e8bc898600061a92ee2f42e6568e9dde3b2a4 Mon Sep 17 00:00:00 2001 From: Gav Date: Tue, 13 Jun 2023 19:33:43 +0100 Subject: [PATCH 34/43] Docs --- frame/asset-conversion/src/benchmarking.rs | 2 +- frame/core-fellowship/src/benchmarking.rs | 2 +- frame/nft-fractionalization/src/benchmarking.rs | 2 +- frame/salary/src/benchmarking.rs | 2 +- frame/society/src/benchmarking.rs | 2 +- frame/society/src/migrations.rs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frame/asset-conversion/src/benchmarking.rs b/frame/asset-conversion/src/benchmarking.rs index 0563a8543f087..0a212fb5ceeb4 100644 --- a/frame/asset-conversion/src/benchmarking.rs +++ b/frame/asset-conversion/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/core-fellowship/src/benchmarking.rs b/frame/core-fellowship/src/benchmarking.rs index 551ec30c19f01..c49f50d4cc115 100644 --- a/frame/core-fellowship/src/benchmarking.rs +++ b/frame/core-fellowship/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/nft-fractionalization/src/benchmarking.rs b/frame/nft-fractionalization/src/benchmarking.rs index 1600ae78c4bdf..50bb6039eb6ec 100644 --- a/frame/nft-fractionalization/src/benchmarking.rs +++ b/frame/nft-fractionalization/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/salary/src/benchmarking.rs b/frame/salary/src/benchmarking.rs index e19834f64ff73..7528293506aec 100644 --- a/frame/salary/src/benchmarking.rs +++ b/frame/salary/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/society/src/benchmarking.rs b/frame/society/src/benchmarking.rs index 1d6f44a1064da..f9cd378b97d3d 100644 --- a/frame/society/src/benchmarking.rs +++ b/frame/society/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/society/src/migrations.rs b/frame/society/src/migrations.rs index 0974de9a4a51b..e2cb178efcb16 100644 --- a/frame/society/src/migrations.rs +++ b/frame/society/src/migrations.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); From 3d3b43c1090d5054e3a670c8f115041f1d21d489 Mon Sep 17 00:00:00 2001 From: Gav Date: Tue, 13 Jun 2023 19:35:37 +0100 Subject: [PATCH 35/43] Docs --- frame/society/src/weights.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/frame/society/src/weights.rs b/frame/society/src/weights.rs index f72ef1c30f6cb..d113f617c886c 100644 --- a/frame/society/src/weights.rs +++ b/frame/society/src/weights.rs @@ -33,6 +33,7 @@ // --execution=wasm // --wasm-execution=compiled // --template=./.maintain/frame-weight-template.hbs +// --header=./HEADER-APACHE2 // --output=./frame/society/src/weights.rs #![cfg_attr(rustfmt, rustfmt_skip)] From 3b73c57e9c55783502d75b50818991d21d901857 Mon Sep 17 00:00:00 2001 From: Gav Date: Tue, 13 Jun 2023 19:36:22 +0100 Subject: [PATCH 36/43] Remove accidental addition. --- primitives/core/src/bounded/bounded_vec.rs | 1315 -------------------- 1 file changed, 1315 deletions(-) delete mode 100644 primitives/core/src/bounded/bounded_vec.rs diff --git a/primitives/core/src/bounded/bounded_vec.rs b/primitives/core/src/bounded/bounded_vec.rs deleted file mode 100644 index 35fbacc2506b9..0000000000000 --- a/primitives/core/src/bounded/bounded_vec.rs +++ /dev/null @@ -1,1315 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Traits, types and structs to support putting a bounded vector into storage, as a raw value, map -//! or a double map. - -use super::WeakBoundedVec; -use crate::{Get, TryCollect}; -use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; -use core::{ - ops::{Deref, Index, IndexMut, RangeBounds}, - slice::SliceIndex, -}; -#[cfg(feature = "std")] -use serde::{ - de::{Error, SeqAccess, Visitor}, - Deserialize, Deserializer, Serialize, -}; -use sp_std::{marker::PhantomData, prelude::*}; - -/// A bounded vector. -/// -/// It has implementations for efficient append and length decoding, as with a normal `Vec<_>`, once -/// put into storage as a raw value, map or double-map. -/// -/// As the name suggests, the length of the queue is always bounded. All internal operations ensure -/// this bound is respected. -#[cfg_attr(feature = "std", derive(Serialize), serde(transparent))] -#[derive(Encode, scale_info::TypeInfo)] -#[scale_info(skip_type_params(S))] -pub struct BoundedVec( - pub(super) Vec, - #[cfg_attr(feature = "std", serde(skip_serializing))] PhantomData, -); - -/// Create an object through truncation. -pub trait TruncateFrom { - /// Create an object through truncation. - fn truncate_from(unbound: T) -> Self; -} - -#[cfg(feature = "std")] -impl<'de, T, S: Get> Deserialize<'de> for BoundedVec -where - T: Deserialize<'de>, -{ - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct VecVisitor>(PhantomData<(T, S)>); - - impl<'de, T, S: Get> Visitor<'de> for VecVisitor - where - T: Deserialize<'de>, - { - type Value = Vec; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a sequence") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - let size = seq.size_hint().unwrap_or(0); - let max = match usize::try_from(S::get()) { - Ok(n) => n, - Err(_) => return Err(A::Error::custom("can't convert to usize")), - }; - if size > max { - Err(A::Error::custom("out of bounds")) - } else { - let mut values = Vec::with_capacity(size); - - while let Some(value) = seq.next_element()? { - values.push(value); - if values.len() > max { - return Err(A::Error::custom("out of bounds")) - } - } - - Ok(values) - } - } - } - - let visitor: VecVisitor = VecVisitor(PhantomData); - deserializer - .deserialize_seq(visitor) - .map(|v| BoundedVec::::try_from(v).map_err(|_| Error::custom("out of bounds")))? - } -} - -/// A bounded slice. -/// -/// Similar to a `BoundedVec`, but not owned and cannot be decoded. -#[derive(Encode)] -pub struct BoundedSlice<'a, T, S>(pub(super) &'a [T], PhantomData); - -// This can be replaced with -// #[derive(scale_info::TypeInfo)] -// #[scale_info(skip_type_params(S))] -// again once this issue is fixed in the rust compiler: https://github.com/rust-lang/rust/issues/96956 -// Tracking issues: https://github.com/paritytech/substrate/issues/11915 -impl<'a, T, S> scale_info::TypeInfo for BoundedSlice<'a, T, S> -where - &'a [T]: scale_info::TypeInfo + 'static, - PhantomData: scale_info::TypeInfo + 'static, - T: scale_info::TypeInfo + 'static, - S: 'static, -{ - type Identity = Self; - - fn type_info() -> ::scale_info::Type { - scale_info::Type::builder() - .path(scale_info::Path::new("BoundedSlice", "sp_runtime::bounded::bounded_vec")) - .type_params(<[_]>::into_vec(Box::new([ - scale_info::TypeParameter::new( - "T", - core::option::Option::Some(::scale_info::meta_type::()), - ), - scale_info::TypeParameter::new("S", ::core::option::Option::None), - ]))) - .docs(&[ - "A bounded slice.", - "", - "Similar to a `BoundedVec`, but not owned and cannot be decoded.", - ]) - .composite( - scale_info::build::Fields::unnamed() - .field(|f| f.ty::<&'static [T]>().type_name("&'static[T]").docs(&[])) - .field(|f| f.ty::>().type_name("PhantomData").docs(&[])), - ) - } -} - -// `BoundedSlice`s encode to something which will always decode into a `BoundedVec`, -// `WeakBoundedVec`, or a `Vec`. -impl<'a, T: Encode + Decode, S: Get> EncodeLike> for BoundedSlice<'a, T, S> {} -impl<'a, T: Encode + Decode, S: Get> EncodeLike> - for BoundedSlice<'a, T, S> -{ -} -impl<'a, T: Encode + Decode, S: Get> EncodeLike> for BoundedSlice<'a, T, S> {} - -impl<'a, T, BoundSelf, BoundRhs> PartialEq> - for BoundedSlice<'a, T, BoundSelf> -where - T: PartialEq, - BoundSelf: Get, - BoundRhs: Get, -{ - fn eq(&self, other: &BoundedSlice<'a, T, BoundRhs>) -> bool { - self.0 == other.0 - } -} - -impl<'a, T, BoundSelf, BoundRhs> PartialEq> - for BoundedSlice<'a, T, BoundSelf> -where - T: PartialEq, - BoundSelf: Get, - BoundRhs: Get, -{ - fn eq(&self, other: &BoundedVec) -> bool { - self.0 == other.0 - } -} - -impl<'a, T, BoundSelf, BoundRhs> PartialEq> - for BoundedSlice<'a, T, BoundSelf> -where - T: PartialEq, - BoundSelf: Get, - BoundRhs: Get, -{ - fn eq(&self, other: &WeakBoundedVec) -> bool { - self.0 == other.0 - } -} - -impl<'a, T, S: Get> Eq for BoundedSlice<'a, T, S> where T: Eq {} - -impl<'a, T, BoundSelf, BoundRhs> PartialOrd> - for BoundedSlice<'a, T, BoundSelf> -where - T: PartialOrd, - BoundSelf: Get, - BoundRhs: Get, -{ - fn partial_cmp(&self, other: &BoundedSlice<'a, T, BoundRhs>) -> Option { - self.0.partial_cmp(other.0) - } -} - -impl<'a, T, BoundSelf, BoundRhs> PartialOrd> - for BoundedSlice<'a, T, BoundSelf> -where - T: PartialOrd, - BoundSelf: Get, - BoundRhs: Get, -{ - fn partial_cmp(&self, other: &BoundedVec) -> Option { - self.0.partial_cmp(&*other.0) - } -} - -impl<'a, T, BoundSelf, BoundRhs> PartialOrd> - for BoundedSlice<'a, T, BoundSelf> -where - T: PartialOrd, - BoundSelf: Get, - BoundRhs: Get, -{ - fn partial_cmp(&self, other: &WeakBoundedVec) -> Option { - self.0.partial_cmp(&*other.0) - } -} - -impl<'a, T: Ord, Bound: Get> Ord for BoundedSlice<'a, T, Bound> { - fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { - self.0.cmp(&other.0) - } -} - -impl<'a, T, S: Get> TryFrom<&'a [T]> for BoundedSlice<'a, T, S> { - type Error = &'a [T]; - fn try_from(t: &'a [T]) -> Result { - if t.len() <= S::get() as usize { - Ok(BoundedSlice(t, PhantomData)) - } else { - Err(t) - } - } -} - -impl<'a, T, S> From> for &'a [T] { - fn from(t: BoundedSlice<'a, T, S>) -> Self { - t.0 - } -} - -impl<'a, T, S: Get> TruncateFrom<&'a [T]> for BoundedSlice<'a, T, S> { - fn truncate_from(unbound: &'a [T]) -> Self { - BoundedSlice::::truncate_from(unbound) - } -} - -impl<'a, T, S> Clone for BoundedSlice<'a, T, S> { - fn clone(&self) -> Self { - BoundedSlice(self.0, PhantomData) - } -} - -impl<'a, T, S> sp_std::fmt::Debug for BoundedSlice<'a, T, S> -where - &'a [T]: sp_std::fmt::Debug, - S: Get, -{ - fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { - f.debug_tuple("BoundedSlice").field(&self.0).field(&S::get()).finish() - } -} - -// Since a reference `&T` is always `Copy`, so is `BoundedSlice<'a, T, S>`. -impl<'a, T, S> Copy for BoundedSlice<'a, T, S> {} - -// will allow for all immutable operations of `[T]` on `BoundedSlice`. -impl<'a, T, S> Deref for BoundedSlice<'a, T, S> { - type Target = [T]; - - fn deref(&self) -> &Self::Target { - self.0 - } -} - -impl<'a, T, S> sp_std::iter::IntoIterator for BoundedSlice<'a, T, S> { - type Item = &'a T; - type IntoIter = sp_std::slice::Iter<'a, T>; - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - -impl<'a, T, S: Get> BoundedSlice<'a, T, S> { - /// Create an instance from the first elements of the given slice (or all of it if it is smaller - /// than the length bound). - pub fn truncate_from(s: &'a [T]) -> Self { - Self(&s[0..(s.len().min(S::get() as usize))], PhantomData) - } -} - -impl> Decode for BoundedVec { - fn decode(input: &mut I) -> Result { - let inner = Vec::::decode(input)?; - if inner.len() > S::get() as usize { - return Err("BoundedVec exceeds its limit".into()) - } - Ok(Self(inner, PhantomData)) - } - - fn skip(input: &mut I) -> Result<(), codec::Error> { - Vec::::skip(input) - } -} - -// `BoundedVec`s encode to something which will always decode as a `Vec`. -impl> EncodeLike> for BoundedVec {} - -impl BoundedVec { - /// Create `Self` with no items. - pub fn new() -> Self { - Self(Vec::new(), Default::default()) - } - - /// Create `Self` from `t` without any checks. - fn unchecked_from(t: Vec) -> Self { - Self(t, Default::default()) - } - - /// Exactly the same semantics as `Vec::clear`. - pub fn clear(&mut self) { - self.0.clear() - } - - /// Consume self, and return the inner `Vec`. Henceforth, the `Vec<_>` can be altered in an - /// arbitrary way. At some point, if the reverse conversion is required, `TryFrom>` can - /// be used. - /// - /// This is useful for cases if you need access to an internal API of the inner `Vec<_>` which - /// is not provided by the wrapper `BoundedVec`. - pub fn into_inner(self) -> Vec { - self.0 - } - - /// Exactly the same semantics as [`slice::sort_by`]. - /// - /// This is safe since sorting cannot change the number of elements in the vector. - pub fn sort_by(&mut self, compare: F) - where - F: FnMut(&T, &T) -> sp_std::cmp::Ordering, - { - self.0.sort_by(compare) - } - - /// Exactly the same semantics as [`slice::sort_by_key`]. - /// - /// This is safe since sorting cannot change the number of elements in the vector. - pub fn sort_by_key(&mut self, f: F) - where - F: FnMut(&T) -> K, - K: sp_std::cmp::Ord, - { - self.0.sort_by_key(f) - } - - /// Exactly the same semantics as [`slice::sort`]. - /// - /// This is safe since sorting cannot change the number of elements in the vector. - pub fn sort(&mut self) - where - T: sp_std::cmp::Ord, - { - self.0.sort() - } - - /// Exactly the same semantics as `Vec::remove`. - /// - /// # Panics - /// - /// Panics if `index` is out of bounds. - pub fn remove(&mut self, index: usize) -> T { - self.0.remove(index) - } - - /// Exactly the same semantics as `slice::swap_remove`. - /// - /// # Panics - /// - /// Panics if `index` is out of bounds. - pub fn swap_remove(&mut self, index: usize) -> T { - self.0.swap_remove(index) - } - - /// Exactly the same semantics as `Vec::retain`. - pub fn retain bool>(&mut self, f: F) { - self.0.retain(f) - } - - /// Exactly the same semantics as `slice::get_mut`. - pub fn get_mut>( - &mut self, - index: I, - ) -> Option<&mut >::Output> { - self.0.get_mut(index) - } - - /// Exactly the same semantics as `Vec::truncate`. - /// - /// This is safe because `truncate` can never increase the length of the internal vector. - pub fn truncate(&mut self, s: usize) { - self.0.truncate(s); - } - - /// Exactly the same semantics as `Vec::pop`. - /// - /// This is safe since popping can only shrink the inner vector. - pub fn pop(&mut self) -> Option { - self.0.pop() - } - - /// Exactly the same semantics as [`slice::iter_mut`]. - pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, T> { - self.0.iter_mut() - } - - /// Exactly the same semantics as [`slice::last_mut`]. - pub fn last_mut(&mut self) -> Option<&mut T> { - self.0.last_mut() - } - - /// Exact same semantics as [`Vec::drain`]. - pub fn drain(&mut self, range: R) -> sp_std::vec::Drain<'_, T> - where - R: RangeBounds, - { - self.0.drain(range) - } -} - -impl> From> for Vec { - fn from(x: BoundedVec) -> Vec { - x.0 - } -} - -impl> BoundedVec { - /// Pre-allocate `capacity` items in self. - /// - /// If `capacity` is greater than [`Self::bound`], then the minimum of the two is used. - pub fn with_bounded_capacity(capacity: usize) -> Self { - let capacity = capacity.min(Self::bound()); - Self(Vec::with_capacity(capacity), Default::default()) - } - - /// Allocate self with the maximum possible capacity. - pub fn with_max_capacity() -> Self { - Self::with_bounded_capacity(Self::bound()) - } - - /// Consume and truncate the vector `v` in order to create a new instance of `Self` from it. - pub fn truncate_from(mut v: Vec) -> Self { - v.truncate(Self::bound()); - Self::unchecked_from(v) - } - - /// Get the bound of the type in `usize`. - pub fn bound() -> usize { - S::get() as usize - } - - /// Returns true of this collection is full. - pub fn is_full(&self) -> bool { - self.len() >= Self::bound() - } - - /// Forces the insertion of `element` into `self` retaining all items with index at least - /// `index`. - /// - /// If `index == 0` and `self.len() == Self::bound()`, then this is a no-op. - /// - /// If `Self::bound() < index` or `self.len() < index`, then this is also a no-op. - /// - /// Returns `Ok(maybe_removed)` if the item was inserted, where `maybe_removed` is - /// `Some(removed)` if an item was removed to make room for the new one. Returns `Err(())` if - /// `element` cannot be inserted. - pub fn force_insert_keep_right( - &mut self, - index: usize, - mut element: T, - ) -> Result, ()> { - // Check against panics. - if Self::bound() < index || self.len() < index { - Err(()) - } else if self.len() < Self::bound() { - // Cannot panic since self.len() >= index; - self.0.insert(index, element); - Ok(None) - } else { - if index == 0 { - return Err(()) - } - sp_std::mem::swap(&mut self[0], &mut element); - // `[0..index] cannot panic since self.len() >= index. - // `rotate_left(1)` cannot panic because there is at least 1 element. - self[0..index].rotate_left(1); - Ok(Some(element)) - } - } - - /// Forces the insertion of `element` into `self` retaining all items with index at most - /// `index`. - /// - /// If `index == Self::bound()` and `self.len() == Self::bound()`, then this is a no-op. - /// - /// If `Self::bound() < index` or `self.len() < index`, then this is also a no-op. - /// - /// Returns `Ok(maybe_removed)` if the item was inserted, where `maybe_removed` is - /// `Some(removed)` if an item was removed to make room for the new one. Returns `Err(element)` - /// if `element` cannot be inserted. - pub fn force_insert_keep_left(&mut self, index: usize, element: T) -> Result, T> { - // Check against panics. - if Self::bound() < index || self.len() < index || Self::bound() == 0 { - return Err(element) - } - // Noop condition. - if Self::bound() == index && self.len() <= Self::bound() { - return Err(element) - } - let maybe_removed = if self.is_full() { - // defensive-only: since we are at capacity, this is a noop. - self.0.truncate(Self::bound()); - // if we truncate anything, it will be the last one. - self.0.pop() - } else { - None - }; - - // Cannot panic since `self.len() >= index`; - self.0.insert(index, element); - Ok(maybe_removed) - } - - /// Move the position of an item from one location to another in the slice. - /// - /// Except for the item being moved, the order of the slice remains the same. - /// - /// - `index` is the location of the item to be moved. - /// - `insert_position` is the index of the item in the slice which should *immediately follow* - /// the item which is being moved. - /// - /// Returns `true` of the operation was successful, otherwise `false` if a noop. - pub fn slide(&mut self, index: usize, insert_position: usize) -> bool { - // Check against panics. - if self.len() <= index || self.len() < insert_position || index == usize::MAX { - return false - } - // Noop conditions. - if index == insert_position || index + 1 == insert_position { - return false - } - if insert_position < index && index < self.len() { - // --- --- --- === === === === @@@ --- --- --- - // ^-- N ^O^ - // ... - // /-----<<<-----\ - // --- --- --- === === === === @@@ --- --- --- - // >>> >>> >>> >>> - // ... - // --- --- --- @@@ === === === === --- --- --- - // ^N^ - self[insert_position..index + 1].rotate_right(1); - return true - } else if insert_position > 0 && index + 1 < insert_position { - // Note that the apparent asymmetry of these two branches is due to the - // fact that the "new" position is the position to be inserted *before*. - // --- --- --- @@@ === === === === --- --- --- - // ^O^ ^-- N - // ... - // /----->>>-----\ - // --- --- --- @@@ === === === === --- --- --- - // <<< <<< <<< <<< - // ... - // --- --- --- === === === === @@@ --- --- --- - // ^N^ - self[index..insert_position].rotate_left(1); - return true - } - - debug_assert!(false, "all noop conditions should have been covered above"); - false - } - - /// Forces the insertion of `s` into `self` truncating first if necessary. - /// - /// Infallible, but if the bound is zero, then it's a no-op. - pub fn force_push(&mut self, element: T) { - if Self::bound() > 0 { - self.0.truncate(Self::bound() as usize - 1); - self.0.push(element); - } - } - - /// Same as `Vec::resize`, but if `size` is more than [`Self::bound`], then [`Self::bound`] is - /// used. - pub fn bounded_resize(&mut self, size: usize, value: T) - where - T: Clone, - { - let size = size.min(Self::bound()); - self.0.resize(size, value); - } - - /// Exactly the same semantics as [`Vec::extend`], but returns an error and does nothing if the - /// length of the outcome is larger than the bound. - pub fn try_extend( - &mut self, - with: impl IntoIterator + ExactSizeIterator, - ) -> Result<(), ()> { - if with.len().saturating_add(self.len()) <= Self::bound() { - self.0.extend(with); - Ok(()) - } else { - Err(()) - } - } - - /// Exactly the same semantics as [`Vec::append`], but returns an error and does nothing if the - /// length of the outcome is larger than the bound. - pub fn try_append(&mut self, other: &mut Vec) -> Result<(), ()> { - if other.len().saturating_add(self.len()) <= Self::bound() { - self.0.append(other); - Ok(()) - } else { - Err(()) - } - } - - /// Consumes self and mutates self via the given `mutate` function. - /// - /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is - /// returned. - /// - /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> - /// [`Self::try_from`]. - pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut Vec)) -> Option { - mutate(&mut self.0); - (self.0.len() <= Self::bound()).then(move || self) - } - - /// Exactly the same semantics as [`Vec::insert`], but returns an `Err` (and is a noop) if the - /// new length of the vector exceeds `S`. - /// - /// # Panics - /// - /// Panics if `index > len`. - pub fn try_insert(&mut self, index: usize, element: T) -> Result<(), T> { - if self.len() < Self::bound() { - self.0.insert(index, element); - Ok(()) - } else { - Err(element) - } - } - - /// Exactly the same semantics as [`Vec::push`], but returns an `Err` (and is a noop) if the - /// new length of the vector exceeds `S`. - /// - /// # Panics - /// - /// Panics if the new capacity exceeds isize::MAX bytes. - pub fn try_push(&mut self, element: T) -> Result<(), T> { - if self.len() < Self::bound() { - self.0.push(element); - Ok(()) - } else { - Err(element) - } - } -} - -impl BoundedVec { - /// Return a [`BoundedSlice`] with the content and bound of [`Self`]. - pub fn as_bounded_slice(&self) -> BoundedSlice { - BoundedSlice(&self.0[..], PhantomData::default()) - } -} - -impl Default for BoundedVec { - fn default() -> Self { - // the bound cannot be below 0, which is satisfied by an empty vector - Self::unchecked_from(Vec::default()) - } -} - -impl sp_std::fmt::Debug for BoundedVec -where - Vec: sp_std::fmt::Debug, - S: Get, -{ - fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { - f.debug_tuple("BoundedVec").field(&self.0).field(&Self::bound()).finish() - } -} - -impl Clone for BoundedVec -where - T: Clone, -{ - fn clone(&self) -> Self { - // bound is retained - Self::unchecked_from(self.0.clone()) - } -} - -impl> TryFrom> for BoundedVec { - type Error = Vec; - fn try_from(t: Vec) -> Result { - if t.len() <= Self::bound() { - // explicit check just above - Ok(Self::unchecked_from(t)) - } else { - Err(t) - } - } -} - -impl> TruncateFrom> for BoundedVec { - fn truncate_from(unbound: Vec) -> Self { - BoundedVec::::truncate_from(unbound) - } -} - -// It is okay to give a non-mutable reference of the inner vec to anyone. -impl AsRef> for BoundedVec { - fn as_ref(&self) -> &Vec { - &self.0 - } -} - -impl AsRef<[T]> for BoundedVec { - fn as_ref(&self) -> &[T] { - &self.0 - } -} - -impl AsMut<[T]> for BoundedVec { - fn as_mut(&mut self) -> &mut [T] { - &mut self.0 - } -} - -// will allow for all immutable operations of `Vec` on `BoundedVec`. -impl Deref for BoundedVec { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -// Allows for indexing similar to a normal `Vec`. Can panic if out of bound. -impl Index for BoundedVec -where - I: SliceIndex<[T]>, -{ - type Output = I::Output; - - #[inline] - fn index(&self, index: I) -> &Self::Output { - self.0.index(index) - } -} - -impl IndexMut for BoundedVec -where - I: SliceIndex<[T]>, -{ - #[inline] - fn index_mut(&mut self, index: I) -> &mut Self::Output { - self.0.index_mut(index) - } -} - -impl sp_std::iter::IntoIterator for BoundedVec { - type Item = T; - type IntoIter = sp_std::vec::IntoIter; - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl<'a, T, S> sp_std::iter::IntoIterator for &'a BoundedVec { - type Item = &'a T; - type IntoIter = sp_std::slice::Iter<'a, T>; - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - -impl<'a, T, S> sp_std::iter::IntoIterator for &'a mut BoundedVec { - type Item = &'a mut T; - type IntoIter = sp_std::slice::IterMut<'a, T>; - fn into_iter(self) -> Self::IntoIter { - self.0.iter_mut() - } -} - -impl codec::DecodeLength for BoundedVec { - fn len(self_encoded: &[u8]) -> Result { - // `BoundedVec` stored just a `Vec`, thus the length is at the beginning in - // `Compact` form, and same implementation as `Vec` can be used. - as codec::DecodeLength>::len(self_encoded) - } -} - -impl PartialEq> for BoundedVec -where - T: PartialEq, - BoundSelf: Get, - BoundRhs: Get, -{ - fn eq(&self, rhs: &BoundedVec) -> bool { - self.0 == rhs.0 - } -} - -impl PartialEq> for BoundedVec -where - T: PartialEq, - BoundSelf: Get, - BoundRhs: Get, -{ - fn eq(&self, rhs: &WeakBoundedVec) -> bool { - self.0 == rhs.0 - } -} - -impl<'a, T, BoundSelf, BoundRhs> PartialEq> - for BoundedVec -where - T: PartialEq, - BoundSelf: Get, - BoundRhs: Get, -{ - fn eq(&self, rhs: &BoundedSlice<'a, T, BoundRhs>) -> bool { - self.0 == rhs.0 - } -} - -impl<'a, T: PartialEq, S: Get> PartialEq<&'a [T]> for BoundedSlice<'a, T, S> { - fn eq(&self, other: &&'a [T]) -> bool { - &self.0 == other - } -} - -impl> PartialEq> for BoundedVec { - fn eq(&self, other: &Vec) -> bool { - &self.0 == other - } -} - -impl> Eq for BoundedVec where T: Eq {} - -impl PartialOrd> for BoundedVec -where - T: PartialOrd, - BoundSelf: Get, - BoundRhs: Get, -{ - fn partial_cmp(&self, other: &BoundedVec) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl PartialOrd> for BoundedVec -where - T: PartialOrd, - BoundSelf: Get, - BoundRhs: Get, -{ - fn partial_cmp(&self, other: &WeakBoundedVec) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl<'a, T, BoundSelf, BoundRhs> PartialOrd> - for BoundedVec -where - T: PartialOrd, - BoundSelf: Get, - BoundRhs: Get, -{ - fn partial_cmp(&self, other: &BoundedSlice<'a, T, BoundRhs>) -> Option { - (&*self.0).partial_cmp(other.0) - } -} - -impl> Ord for BoundedVec { - fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { - self.0.cmp(&other.0) - } -} - -impl MaxEncodedLen for BoundedVec -where - T: MaxEncodedLen, - S: Get, - BoundedVec: Encode, -{ - fn max_encoded_len() -> usize { - // BoundedVec encodes like Vec which encodes like [T], which is a compact u32 - // plus each item in the slice: - // See: https://docs.substrate.io/reference/scale-codec/ - codec::Compact(S::get()) - .encoded_size() - .saturating_add(Self::bound().saturating_mul(T::max_encoded_len())) - } -} - -impl TryCollect> for I -where - I: ExactSizeIterator + Iterator, - Bound: Get, -{ - type Error = &'static str; - - fn try_collect(self) -> Result, Self::Error> { - if self.len() > Bound::get() as usize { - Err("iterator length too big") - } else { - Ok(BoundedVec::::unchecked_from(self.collect::>())) - } - } -} - -#[cfg(test)] -pub mod test { - use super::*; - use crate::{bounded_vec, ConstU32}; - - #[test] - fn slice_truncate_from_works() { - let bounded = BoundedSlice::>::truncate_from(&[1, 2, 3, 4, 5]); - assert_eq!(bounded.deref(), &[1, 2, 3, 4]); - let bounded = BoundedSlice::>::truncate_from(&[1, 2, 3, 4]); - assert_eq!(bounded.deref(), &[1, 2, 3, 4]); - let bounded = BoundedSlice::>::truncate_from(&[1, 2, 3]); - assert_eq!(bounded.deref(), &[1, 2, 3]); - } - - #[test] - fn slide_works() { - let mut b: BoundedVec> = bounded_vec![0, 1, 2, 3, 4, 5]; - assert!(b.slide(1, 5)); - assert_eq!(*b, vec![0, 2, 3, 4, 1, 5]); - assert!(b.slide(4, 0)); - assert_eq!(*b, vec![1, 0, 2, 3, 4, 5]); - assert!(b.slide(0, 2)); - assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); - assert!(b.slide(1, 6)); - assert_eq!(*b, vec![0, 2, 3, 4, 5, 1]); - assert!(b.slide(0, 6)); - assert_eq!(*b, vec![2, 3, 4, 5, 1, 0]); - assert!(b.slide(5, 0)); - assert_eq!(*b, vec![0, 2, 3, 4, 5, 1]); - assert!(!b.slide(6, 0)); - assert!(!b.slide(7, 0)); - assert_eq!(*b, vec![0, 2, 3, 4, 5, 1]); - - let mut c: BoundedVec> = bounded_vec![0, 1, 2]; - assert!(!c.slide(1, 5)); - assert_eq!(*c, vec![0, 1, 2]); - assert!(!c.slide(4, 0)); - assert_eq!(*c, vec![0, 1, 2]); - assert!(!c.slide(3, 0)); - assert_eq!(*c, vec![0, 1, 2]); - assert!(c.slide(2, 0)); - assert_eq!(*c, vec![2, 0, 1]); - } - - #[test] - fn slide_noops_work() { - let mut b: BoundedVec> = bounded_vec![0, 1, 2, 3, 4, 5]; - assert!(!b.slide(3, 3)); - assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); - assert!(!b.slide(3, 4)); - assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); - } - - #[test] - fn force_insert_keep_left_works() { - let mut b: BoundedVec> = bounded_vec![]; - assert_eq!(b.force_insert_keep_left(1, 10), Err(())); - assert!(b.is_empty()); - - assert_eq!(b.force_insert_keep_left(0, 30), Ok(None)); - assert_eq!(b.force_insert_keep_left(0, 10), Ok(None)); - assert_eq!(b.force_insert_keep_left(1, 20), Ok(None)); - assert_eq!(b.force_insert_keep_left(3, 40), Ok(None)); - assert_eq!(*b, vec![10, 20, 30, 40]); - // at capacity. - assert_eq!(b.force_insert_keep_left(4, 41), Err(())); - assert_eq!(*b, vec![10, 20, 30, 40]); - assert_eq!(b.force_insert_keep_left(3, 31), Ok(Some(40))); - assert_eq!(*b, vec![10, 20, 30, 31]); - assert_eq!(b.force_insert_keep_left(1, 11), Ok(Some(31))); - assert_eq!(*b, vec![10, 11, 20, 30]); - assert_eq!(b.force_insert_keep_left(0, 1), Ok(Some(30))); - assert_eq!(*b, vec![1, 10, 11, 20]); - - let mut z: BoundedVec> = bounded_vec![]; - assert!(z.is_empty()); - assert_eq!(z.force_insert_keep_left(0, 10), Err(())); - assert!(z.is_empty()); - } - - #[test] - fn force_insert_keep_right_works() { - let mut b: BoundedVec> = bounded_vec![]; - assert_eq!(b.force_insert_keep_right(1, 10), Err(())); - assert!(b.is_empty()); - - assert_eq!(b.force_insert_keep_right(0, 30), Ok(None)); - assert_eq!(b.force_insert_keep_right(0, 10), Ok(None)); - assert_eq!(b.force_insert_keep_right(1, 20), Ok(None)); - assert_eq!(b.force_insert_keep_right(3, 40), Ok(None)); - assert_eq!(*b, vec![10, 20, 30, 40]); - - // at capacity. - assert_eq!(b.force_insert_keep_right(0, 0), Err(())); - assert_eq!(*b, vec![10, 20, 30, 40]); - assert_eq!(b.force_insert_keep_right(1, 11), Ok(Some(10))); - assert_eq!(*b, vec![11, 20, 30, 40]); - assert_eq!(b.force_insert_keep_right(3, 31), Ok(Some(11))); - assert_eq!(*b, vec![20, 30, 31, 40]); - assert_eq!(b.force_insert_keep_right(4, 41), Ok(Some(20))); - assert_eq!(*b, vec![30, 31, 40, 41]); - - assert_eq!(b.force_insert_keep_right(5, 69), Err(())); - assert_eq!(*b, vec![30, 31, 40, 41]); - - let mut z: BoundedVec> = bounded_vec![]; - assert!(z.is_empty()); - assert_eq!(z.force_insert_keep_right(0, 10), Err(())); - assert!(z.is_empty()); - } - - #[test] - fn bound_returns_correct_value() { - assert_eq!(BoundedVec::>::bound(), 7); - } - - #[test] - fn try_insert_works() { - let mut bounded: BoundedVec> = bounded_vec![1, 2, 3]; - bounded.try_insert(1, 0).unwrap(); - assert_eq!(*bounded, vec![1, 0, 2, 3]); - - assert!(bounded.try_insert(0, 9).is_err()); - assert_eq!(*bounded, vec![1, 0, 2, 3]); - } - - #[test] - fn constructor_macro_works() { - // With values. Use some brackets to make sure the macro doesn't expand. - let bv: BoundedVec<(u32, u32), ConstU32<3>> = bounded_vec![(1, 2), (1, 2), (1, 2)]; - assert_eq!(bv, vec![(1, 2), (1, 2), (1, 2)]); - - // With repetition. - let bv: BoundedVec<(u32, u32), ConstU32<3>> = bounded_vec![(1, 2); 3]; - assert_eq!(bv, vec![(1, 2), (1, 2), (1, 2)]); - } - - #[test] - #[should_panic(expected = "insertion index (is 9) should be <= len (is 3)")] - fn try_inert_panics_if_oob() { - let mut bounded: BoundedVec> = bounded_vec![1, 2, 3]; - bounded.try_insert(9, 0).unwrap(); - } - - #[test] - fn try_push_works() { - let mut bounded: BoundedVec> = bounded_vec![1, 2, 3]; - bounded.try_push(0).unwrap(); - assert_eq!(*bounded, vec![1, 2, 3, 0]); - - assert!(bounded.try_push(9).is_err()); - } - - #[test] - fn deref_vec_coercion_works() { - let bounded: BoundedVec> = bounded_vec![1, 2, 3]; - // these methods come from deref-ed vec. - assert_eq!(bounded.len(), 3); - assert!(bounded.iter().next().is_some()); - assert!(!bounded.is_empty()); - } - - #[test] - fn deref_slice_coercion_works() { - let bounded = BoundedSlice::>::try_from(&[1, 2, 3][..]).unwrap(); - // these methods come from deref-ed slice. - assert_eq!(bounded.len(), 3); - assert!(bounded.iter().next().is_some()); - assert!(!bounded.is_empty()); - } - - #[test] - fn try_mutate_works() { - let bounded: BoundedVec> = bounded_vec![1, 2, 3, 4, 5, 6]; - let bounded = bounded.try_mutate(|v| v.push(7)).unwrap(); - assert_eq!(bounded.len(), 7); - assert!(bounded.try_mutate(|v| v.push(8)).is_none()); - } - - #[test] - fn slice_indexing_works() { - let bounded: BoundedVec> = bounded_vec![1, 2, 3, 4, 5, 6]; - assert_eq!(&bounded[0..=2], &[1, 2, 3]); - } - - #[test] - fn vec_eq_works() { - let bounded: BoundedVec> = bounded_vec![1, 2, 3, 4, 5, 6]; - assert_eq!(bounded, vec![1, 2, 3, 4, 5, 6]); - } - - #[test] - fn too_big_vec_fail_to_decode() { - let v: Vec = vec![1, 2, 3, 4, 5]; - assert_eq!( - BoundedVec::>::decode(&mut &v.encode()[..]), - Err("BoundedVec exceeds its limit".into()), - ); - } - - #[test] - fn eq_works() { - // of same type - let b1: BoundedVec> = bounded_vec![1, 2, 3]; - let b2: BoundedVec> = bounded_vec![1, 2, 3]; - assert_eq!(b1, b2); - - // of different type, but same value and bound. - crate::parameter_types! { - B1: u32 = 7; - B2: u32 = 7; - } - let b1: BoundedVec = bounded_vec![1, 2, 3]; - let b2: BoundedVec = bounded_vec![1, 2, 3]; - assert_eq!(b1, b2); - } - - #[test] - fn ord_works() { - use std::cmp::Ordering; - let b1: BoundedVec> = bounded_vec![1, 2, 3]; - let b2: BoundedVec> = bounded_vec![1, 3, 2]; - - // ordering for vec is lexicographic. - assert_eq!(b1.cmp(&b2), Ordering::Less); - assert_eq!(b1.cmp(&b2), b1.into_inner().cmp(&b2.into_inner())); - } - - #[test] - fn try_extend_works() { - let mut b: BoundedVec> = bounded_vec![1, 2, 3]; - - assert!(b.try_extend(vec![4].into_iter()).is_ok()); - assert_eq!(*b, vec![1, 2, 3, 4]); - - assert!(b.try_extend(vec![5].into_iter()).is_ok()); - assert_eq!(*b, vec![1, 2, 3, 4, 5]); - - assert!(b.try_extend(vec![6].into_iter()).is_err()); - assert_eq!(*b, vec![1, 2, 3, 4, 5]); - - let mut b: BoundedVec> = bounded_vec![1, 2, 3]; - assert!(b.try_extend(vec![4, 5].into_iter()).is_ok()); - assert_eq!(*b, vec![1, 2, 3, 4, 5]); - - let mut b: BoundedVec> = bounded_vec![1, 2, 3]; - assert!(b.try_extend(vec![4, 5, 6].into_iter()).is_err()); - assert_eq!(*b, vec![1, 2, 3]); - } - - #[test] - fn test_serializer() { - let c: BoundedVec> = bounded_vec![0, 1, 2]; - assert_eq!(serde_json::json!(&c).to_string(), r#"[0,1,2]"#); - } - - #[test] - fn test_deserializer() { - let c: BoundedVec> = serde_json::from_str(r#"[0,1,2]"#).unwrap(); - - assert_eq!(c.len(), 3); - assert_eq!(c[0], 0); - assert_eq!(c[1], 1); - assert_eq!(c[2], 2); - } - - #[test] - fn test_deserializer_failed() { - let c: Result>, serde_json::error::Error> = - serde_json::from_str(r#"[0,1,2,3,4,5]"#); - - match c { - Err(msg) => assert_eq!(msg.to_string(), "out of bounds at line 1 column 11"), - _ => unreachable!("deserializer must raise error"), - } - } - - #[test] - fn bounded_vec_try_from_works() { - assert!(BoundedVec::>::try_from(vec![0]).is_ok()); - assert!(BoundedVec::>::try_from(vec![0, 1]).is_ok()); - assert!(BoundedVec::>::try_from(vec![0, 1, 2]).is_err()); - } - - #[test] - fn bounded_slice_try_from_works() { - assert!(BoundedSlice::>::try_from(&[0][..]).is_ok()); - assert!(BoundedSlice::>::try_from(&[0, 1][..]).is_ok()); - assert!(BoundedSlice::>::try_from(&[0, 1, 2][..]).is_err()); - } - - #[test] - fn can_be_collected() { - let b1: BoundedVec> = bounded_vec![1, 2, 3, 4]; - let b2: BoundedVec> = b1.iter().map(|x| x + 1).try_collect().unwrap(); - assert_eq!(b2, vec![2, 3, 4, 5]); - - // can also be collected into a collection of length 4. - let b2: BoundedVec> = b1.iter().map(|x| x + 1).try_collect().unwrap(); - assert_eq!(b2, vec![2, 3, 4, 5]); - - // can be mutated further into iterators that are `ExactSizedIterator`. - let b2: BoundedVec> = - b1.iter().map(|x| x + 1).rev().try_collect().unwrap(); - assert_eq!(b2, vec![5, 4, 3, 2]); - - let b2: BoundedVec> = - b1.iter().map(|x| x + 1).rev().skip(2).try_collect().unwrap(); - assert_eq!(b2, vec![3, 2]); - let b2: BoundedVec> = - b1.iter().map(|x| x + 1).rev().skip(2).try_collect().unwrap(); - assert_eq!(b2, vec![3, 2]); - - let b2: BoundedVec> = - b1.iter().map(|x| x + 1).rev().take(2).try_collect().unwrap(); - assert_eq!(b2, vec![5, 4]); - let b2: BoundedVec> = - b1.iter().map(|x| x + 1).rev().take(2).try_collect().unwrap(); - assert_eq!(b2, vec![5, 4]); - - // but these worn't work - let b2: Result>, _> = b1.iter().map(|x| x + 1).try_collect(); - assert!(b2.is_err()); - - let b2: Result>, _> = - b1.iter().map(|x| x + 1).rev().take(2).try_collect(); - assert!(b2.is_err()); - } - - #[test] - fn bounded_vec_debug_works() { - let bound = BoundedVec::>::truncate_from(vec![1, 2, 3]); - assert_eq!(format!("{:?}", bound), "BoundedVec([1, 2, 3], 5)"); - } - - #[test] - fn bounded_slice_debug_works() { - let bound = BoundedSlice::>::truncate_from(&[1, 2, 3]); - assert_eq!(format!("{:?}", bound), "BoundedSlice([1, 2, 3], 5)"); - } - - #[test] - fn bounded_vec_sort_by_key_works() { - let mut v: BoundedVec> = bounded_vec![-5, 4, 1, -3, 2]; - // Sort by absolute value. - v.sort_by_key(|k| k.abs()); - assert_eq!(v, vec![1, 2, -3, 4, -5]); - } - - #[test] - fn bounded_vec_truncate_from_works() { - let unbound = vec![1, 2, 3, 4, 5]; - let bound = BoundedVec::>::truncate_from(unbound.clone()); - assert_eq!(bound, vec![1, 2, 3]); - } - - #[test] - fn bounded_slice_truncate_from_works() { - let unbound = [1, 2, 3, 4, 5]; - let bound = BoundedSlice::>::truncate_from(&unbound); - assert_eq!(bound, &[1, 2, 3][..]); - } - - #[test] - fn bounded_slice_partialeq_slice_works() { - let unbound = [1, 2, 3]; - let bound = BoundedSlice::>::truncate_from(&unbound); - - assert_eq!(bound, &unbound[..]); - assert!(bound == &unbound[..]); - } -} From e51eebf57b15953c3142eaf6f5bb18799fd89f8d Mon Sep 17 00:00:00 2001 From: Gav Date: Tue, 13 Jun 2023 20:00:50 +0100 Subject: [PATCH 37/43] Fixes --- primitives/state-machine/src/overlayed_changes/changeset.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/primitives/state-machine/src/overlayed_changes/changeset.rs b/primitives/state-machine/src/overlayed_changes/changeset.rs index 9d8579e7f2876..8f2d02fd6840e 100644 --- a/primitives/state-machine/src/overlayed_changes/changeset.rs +++ b/primitives/state-machine/src/overlayed_changes/changeset.rs @@ -442,7 +442,6 @@ impl OverlayedChangeSet { count += 1; } val.set(None, insert_dirty(&mut self.dirty_keys, key.clone()), at_extrinsic); - count += 1; } count } From 43ea1dbede5a4eb0d90a40ba59daa2023fe58529 Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 14 Jun 2023 09:56:17 +0100 Subject: [PATCH 38/43] Clippy --- frame/society/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 48ab159f7cbf7..828ca59142dbc 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -840,7 +840,7 @@ pub mod pallet { let deposit = params.candidate_deposit; // NOTE: Reserve must happen before `insert_bid` since that could end up unreserving. T::Currency::reserve(&who, deposit)?; - Self::insert_bid(&mut bids, &who, value.clone(), BidKind::Deposit(deposit)); + Self::insert_bid(&mut bids, &who, value, BidKind::Deposit(deposit)); Bids::::put(bids); Self::deposit_event(Event::::Bid { candidate_id: who, offer: value }); @@ -917,7 +917,7 @@ pub mod pallet { // Update voucher record. record.vouching = Some(VouchingStatus::Vouching); // Update bids - Self::insert_bid(&mut bids, &who, value.clone(), BidKind::Vouch(voucher.clone(), tip)); + Self::insert_bid(&mut bids, &who, value, BidKind::Vouch(voucher.clone(), tip)); // Write new state. Members::::insert(&voucher, &record); @@ -1450,7 +1450,7 @@ impl, I: 'static> Pallet { let voting_period = T::VotingPeriod::get(); let rotation_period = voting_period + claim_period; let now = frame_system::Pallet::::block_number(); - let phase = now % rotation_period.clone(); + let phase = now % rotation_period; if phase < voting_period { Period::Voting { elapsed: phase, more: voting_period - phase } } else { From bfed684129bfe86507d86527703cd47a3f66643b Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 14 Jun 2023 10:43:46 +0100 Subject: [PATCH 39/43] Fixes --- frame/society/src/migrations.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/society/src/migrations.rs b/frame/society/src/migrations.rs index e2cb178efcb16..32db4d35e46d9 100644 --- a/frame/society/src/migrations.rs +++ b/frame/society/src/migrations.rs @@ -40,7 +40,7 @@ impl< #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, TryRuntimeError> { assert!( - can_migrate(), + can_migrate::(), "Invalid (perhaps already-migrated) state detected prior to migration" ); Ok(Vec::new()) From ba534a6123a884c2901bf99f5ae9d49a8b92c78e Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 14 Jun 2023 14:15:19 +0100 Subject: [PATCH 40/43] Docs --- frame/society/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 828ca59142dbc..930f29eeaa094 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -1004,7 +1004,6 @@ pub mod pallet { /// /// Key: M (len of members) /// Total Complexity: O(M + logM) - /// # #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::defender_vote())] pub fn defender_vote(origin: OriginFor, approve: bool) -> DispatchResultWithPostInfo { From 1528a58b103bffb14de90d7eb7b055123dfe1052 Mon Sep 17 00:00:00 2001 From: Gav Date: Thu, 15 Jun 2023 11:43:59 +0100 Subject: [PATCH 41/43] Improve try_runtime test --- frame/society/src/lib.rs | 39 --------------------------------- frame/society/src/migrations.rs | 29 ++++++++++++++++++------ 2 files changed, 22 insertions(+), 46 deletions(-) diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 930f29eeaa094..b4b3aecce71c1 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -465,17 +465,6 @@ pub struct GroupParams { pub type GroupParamsFor = GroupParams>; -/// A vote by a member on a candidate application. -#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub enum OldVote { - /// The member has been chosen to be skeptic and has not yet taken any action. - Skeptic, - /// The member has rejected the candidate's application. - Reject, - /// The member approves of the candidate's application. - Approve, -} - #[frame_support::pallet] pub mod pallet { use super::*; @@ -822,9 +811,6 @@ pub mod pallet { /// /// Parameters: /// - `value`: A one time payment the bid would like to receive when joining the society. - /// - /// Key: B (len of bids), C (len of candidates), M (len of members), X (balance reserve) - /// Total Complexity: O(M + B + C + logM + logB + X) #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::bid())] pub fn bid(origin: OriginFor, value: BalanceOf) -> DispatchResult { @@ -854,9 +840,6 @@ pub mod pallet { /// Payment: The bid deposit is unreserved if the user made a bid. /// /// The dispatch origin for this call must be _Signed_ and a bidder. - /// - /// Key: B (len of bids), X (balance unreserve) - /// Total Complexity: O(B + X) #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::unbid())] pub fn unbid(origin: OriginFor) -> DispatchResult { @@ -887,9 +870,6 @@ pub mod pallet { /// a member in the society. /// - `tip`: Your cut of the total `value` payout when the candidate is inducted into /// the society. Tips larger than `value` will be saturated upon payout. - /// - /// Key: B (len of bids), C (len of candidates), M (len of members) - /// Total Complexity: O(M + B + C + logM + logB + X) #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::vouch())] pub fn vouch( @@ -937,9 +917,6 @@ pub mod pallet { /// /// Parameters: /// - `pos`: Position in the `Bids` vector of the bid who should be unvouched. - /// - /// Key: B (len of bids) - /// Total Complexity: O(B) #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::unvouch())] pub fn unvouch(origin: OriginFor) -> DispatchResult { @@ -966,9 +943,6 @@ pub mod pallet { /// - `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`). - /// - /// Key: C (len of candidates), M (len of members) - /// Total Complexity: O(M + logM + C) #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::vote())] pub fn vote( @@ -1001,9 +975,6 @@ pub mod pallet { /// Parameters: /// - `approve`: A boolean which says if the candidate should be /// approved (`true`) or rejected (`false`). - /// - /// Key: M (len of members) - /// Total Complexity: O(M + logM) #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::defender_vote())] pub fn defender_vote(origin: OriginFor, approve: bool) -> DispatchResultWithPostInfo { @@ -1034,9 +1005,6 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_ and a member with /// payouts remaining. - /// - /// Key: M (len of members), P (number of payouts for a particular member) - /// Total Complexity: O(M + logM + P + X) #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::payout())] pub fn payout(origin: OriginFor) -> DispatchResult { @@ -1129,8 +1097,6 @@ pub mod pallet { /// The dispatch origin for this call must be Signed, and the signing account must be both /// the `Founder` and the `Head`. This implies that it may only be done when there is one /// member. - /// - /// Total Complexity: O(1) #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::dissolve())] pub fn dissolve(origin: OriginFor) -> DispatchResult { @@ -1176,9 +1142,6 @@ pub mod pallet { /// - `who` - The suspended member to be judged. /// - `forgive` - A boolean representing whether the suspension judgement origin forgives /// (`true`) or rejects (`false`) a suspended member. - /// - /// Key: B (len of bids), M (len of members) - /// Total Complexity: O(M + logM + B) #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::judge_suspended_member())] pub fn judge_suspended_member( @@ -1221,8 +1184,6 @@ pub mod pallet { /// - `max_strikes`: The maximum number of strikes a member may get before they become /// suspended and may only be reinstated by the founder. /// - `candidate_deposit`: The deposit required to make a bid for membership of the group. - /// - /// Total Complexity: O(1) #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::set_parameters())] pub fn set_parameters( diff --git a/frame/society/src/migrations.rs b/frame/society/src/migrations.rs index 32db4d35e46d9..54a6abac499ae 100644 --- a/frame/society/src/migrations.rs +++ b/frame/society/src/migrations.rs @@ -18,6 +18,7 @@ //! # Migrations for Society Pallet use super::*; +use codec::{Decode, Encode}; use frame_support::traits::{Instance, OnRuntimeUpgrade}; #[cfg(feature = "try-runtime")] @@ -43,7 +44,7 @@ impl< can_migrate::(), "Invalid (perhaps already-migrated) state detected prior to migration" ); - Ok(Vec::new()) + Ok((old::Candidates::::get(), old::Members::::get()).encode()) } fn on_runtime_upgrade() -> Weight { @@ -51,7 +52,26 @@ impl< } #[cfg(feature = "try-runtime")] - fn post_upgrade(_: Vec) -> Result<(), TryRuntimeError> { + fn post_upgrade(data: Vec) -> Result<(), TryRuntimeError> { + let old: ( + Vec::AccountId, BalanceOf>>, + Vec<::AccountId>, + ) = Decode::decode(&mut &data[..]).expect("Bad data"); + let mut old_candidates = + old.0.into_iter().map(|x| (x.who, x.kind, x.value)).collect::>(); + let mut old_members = old.1; + let mut candidates = + Candidates::::iter().map(|(k, v)| (k, v.kind, v.bid)).collect::>(); + let mut members = Members::::iter_keys().collect::>(); + + old_candidates.sort_by_key(|x| x.0.clone()); + candidates.sort_by_key(|x| x.0.clone()); + assert_eq!(candidates, old_candidates); + + members.sort(); + old_members.sort(); + assert_eq!(members, old_members); + assert_internal_consistency::(); Ok(()) } @@ -289,8 +309,3 @@ pub fn from_raw_past_payouts, I: Instance + 'static>( .filter_map(|(x, y)| Some((Decode::decode(&mut &x[..]).ok()?, y.try_into().ok()?))) .collect() } - -// use hex_literal::hex; -// let mut past_payouts_raw = vec![ -// (hex!["1234567890123456789012345678901234567890123456789012345678901234"], 0u128), -// ]; From af65cf78b155d8de2d16e17a316d3d8a4f9c9261 Mon Sep 17 00:00:00 2001 From: Gav Date: Thu, 15 Jun 2023 12:01:04 +0100 Subject: [PATCH 42/43] Use pallet storage version --- frame/society/src/lib.rs | 4 ++++ frame/society/src/migrations.rs | 28 +++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index b4b3aecce71c1..6f42ae00f287d 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -264,6 +264,7 @@ use frame_support::{ traits::{ BalanceStatus, Currency, EnsureOrigin, EnsureOriginWithArg, ExistenceRequirement::AllowDeath, Imbalance, OnUnbalanced, Randomness, ReservableCurrency, + StorageVersion, }, PalletId, }; @@ -469,7 +470,10 @@ pub type GroupParamsFor = GroupParams>; pub mod pallet { use super::*; + const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] #[pallet::without_storage_info] pub struct Pallet(_); diff --git a/frame/society/src/migrations.rs b/frame/society/src/migrations.rs index 54a6abac499ae..bd590f9b18770 100644 --- a/frame/society/src/migrations.rs +++ b/frame/society/src/migrations.rs @@ -40,15 +40,28 @@ impl< { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, TryRuntimeError> { - assert!( - can_migrate::(), - "Invalid (perhaps already-migrated) state detected prior to migration" - ); + ensure!(can_migrate::(), "pallet_society: already upgraded"); + + let current = Pallet::::current_storage_version(); + let onchain = Pallet::::on_chain_storage_version(); + ensure!(onchain == 0 && current == 2, "pallet_society: invalid version"); + Ok((old::Candidates::::get(), old::Members::::get()).encode()) } fn on_runtime_upgrade() -> Weight { - from_original::(&mut PastPayouts::get()) + let current = Pallet::::current_storage_version(); + let onchain = Pallet::::on_chain_storage_version(); + if current == 2 && onchain == 0 { + from_original::(&mut PastPayouts::get()) + } else { + log::info!( + "Running migration with current storage version {:?} / onchain {:?}", + current, + onchain + ); + T::DbWeight::get().reads(1) + } } #[cfg(feature = "try-runtime")] @@ -72,6 +85,11 @@ impl< old_members.sort(); assert_eq!(members, old_members); + ensure!( + Pallet::::on_chain_storage_version() == 2, + "The onchain version must be updated after the migration." + ); + assert_internal_consistency::(); Ok(()) } From 308e6464204d855f18ad0bfd931148cdb2b5dfea Mon Sep 17 00:00:00 2001 From: Gav Date: Thu, 15 Jun 2023 12:05:29 +0100 Subject: [PATCH 43/43] Fix up society cargo toml file --- frame/society/Cargo.toml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frame/society/Cargo.toml b/frame/society/Cargo.toml index 7bd2124f132f9..ec5ab04e35ec4 100644 --- a/frame/society/Cargo.toml +++ b/frame/society/Cargo.toml @@ -13,10 +13,12 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +hex-literal = "0.3.4" log = { version = "0.4.17", default-features = false } rand_chacha = { version = "0.2", default-features = false } scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } + sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } sp-arithmetic = { version = "16.0.0", default-features = false, path = "../../primitives/arithmetic" } @@ -24,7 +26,6 @@ sp-runtime = { version = "24.0.0", default-features = false, path = "../../primi frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -hex-literal = "0.3.4" [dev-dependencies] frame-support-test = { version = "3.0.0", path = "../support/test" } @@ -36,6 +37,7 @@ sp-io = { version = "23.0.0", path = "../../primitives/io" } default = ["std"] std = [ "codec/std", + "frame-benchmarking?/std", "frame-support/std", "frame-system/std", "rand_chacha/std", @@ -45,6 +47,7 @@ std = [ "sp-io/std", ] runtime-benchmarks = [ + "frame-benchmarking", "sp-runtime/runtime-benchmarks", "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks",