referrerpolicy=no-referrer-when-downgrade

pallet_society/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! # Society Pallet
19//!
20//! - [`Config`]
21//! - [`Call`]
22//!
23//! ## Overview
24//!
25//! The Society pallet is an economic game which incentivizes users to participate
26//! and maintain a membership society.
27//!
28//! ### User Types
29//!
30//! At any point, a user in the society can be one of a:
31//! * Bidder - A user who has submitted intention of joining the society.
32//! * Candidate - A user who will be voted on to join the society.
33//! * Member - A user who is a member of the society.
34//! * Suspended Member - A member of the society who has accumulated too many strikes
35//! or failed their membership challenge.
36//!
37//! Of the non-suspended members, there is always a:
38//! * Head - A member who is exempt from suspension.
39//! * Defender - A member whose membership is under question and voted on again.
40//!
41//! Of the non-suspended members of the society, a random set of them are chosen as
42//! "skeptics". The mechanics of skeptics is explained in the
43//! [member phase](#member-phase) below.
44//!
45//! ### Mechanics
46//!
47//! #### Rewards
48//!
49//! Members are incentivized to participate in the society through rewards paid
50//! by the Society treasury. These payments have a maturity period that the user
51//! must wait before they are able to access the funds.
52//!
53//! #### Punishments
54//!
55//! Members can be punished by slashing the reward payouts that have not been
56//! collected. Additionally, members can accumulate "strikes", and when they
57//! reach a max strike limit, they become suspended.
58//!
59//! #### Skeptics
60//!
61//! During the voting period, a random set of members are selected as "skeptics".
62//! These skeptics are expected to vote on the current candidates. If they do not vote,
63//! their skeptic status is treated as a rejection vote, the member is deemed
64//! "lazy", and are given a strike per missing vote.
65//!
66//! #### Membership Challenges
67//!
68//! Every challenge rotation period, an existing member will be randomly selected
69//! to defend their membership into society. Then, other members can vote whether
70//! this defender should stay in society. A simple majority wins vote will determine
71//! the outcome of the user. Ties are treated as a failure of the challenge, but
72//! assuming no one else votes, the defender always get a free vote on their
73//! own challenge keeping them in the society. The Head member is exempt from the
74//! negative outcome of a membership challenge.
75//!
76//! #### Society Treasury
77//!
78//! The membership society is independently funded by a treasury managed by this
79//! pallet. Some subset of this treasury is placed in a Society Pot, which is used
80//! to determine the number of accepted bids.
81//!
82//! #### Rate of Growth
83//!
84//! The membership society can grow at a rate of 10 accepted candidates per rotation period up
85//! to the max membership threshold. Once this threshold is met, candidate selections
86//! are stalled until there is space for new members to join. This can be resolved by
87//! voting out existing members through the random challenges or by using governance
88//! to increase the maximum membership count.
89//!
90//! ### User Life Cycle
91//!
92//! A user can go through the following phases:
93//!
94//! ```ignore
95//!           +------->  User  <----------+
96//!           |           +               |
97//!           |           |               |
98//! +----------------------------------------------+
99//! |         |           |               |        |
100//! |         |           v               |        |
101//! |         |        Bidder <-----------+        |
102//! |         |           +               |        |
103//! |         |           |               +        |
104//! |         |           v            Suspended   |
105//! |         |       Candidate +----> Candidate   |
106//! |         |           +               +        |
107//! |         |           |               |        |
108//! |         +           |               |        |
109//! |   Suspended +------>|               |        |
110//! |      Member         |               |        |
111//! |         ^           |               |        |
112//! |         |           v               |        |
113//! |         +-------+ Member <----------+        |
114//! |                                              |
115//! |                                              |
116//! +------------------Society---------------------+
117//! ```
118//!
119//! #### Initialization
120//!
121//! The society is initialized with a single member who is automatically chosen as the Head.
122//!
123//! #### Bid Phase
124//!
125//! New users must have a bid to join the society.
126//!
127//! A user can make a bid by reserving a deposit. Alternatively, an already existing member
128//! can create a bid on a user's behalf by "vouching" for them.
129//!
130//! A bid includes reward information that the user would like to receive for joining
131//! the society. A vouching bid can additionally request some portion of that reward as a tip
132//! to the voucher for vouching for the prospective candidate.
133//!
134//! Every rotation period, Bids are ordered by reward amount, and the pallet
135//! selects as many bids the Society Pot can support for that period.
136//!
137//! These selected bids become candidates and move on to the Candidate phase.
138//! Bids that were not selected stay in the bidder pool until they are selected or
139//! a user chooses to "unbid".
140//!
141//! #### Candidate Phase
142//!
143//! Once a bidder becomes a candidate, members vote whether to approve or reject
144//! that candidate into society. This voting process also happens during a rotation period.
145//!
146//! The approval and rejection criteria for candidates are not set on chain,
147//! and may change for different societies.
148//!
149//! At the end of the rotation period, we collect the votes for a candidate
150//! and randomly select a vote as the final outcome.
151//!
152//! ```ignore
153//!  [ a-accept, r-reject, s-skeptic ]
154//! +----------------------------------+
155//! |                                  |
156//! |  Member   |0|1|2|3|4|5|6|7|8|9|  |
157//! |  -----------------------------   |
158//! |  Vote     |a|a|a|r|s|r|a|a|s|a|  |
159//! |  -----------------------------   |
160//! |  Selected | | | |x| | | | | | |  |
161//! |                                  |
162//! +----------------------------------+
163//!
164//! Result: Rejected
165//! ```
166//!
167//! Each member that voted opposite to this randomly selected vote is punished by
168//! slashing their unclaimed payouts and increasing the number of strikes they have.
169//!
170//! These slashed funds are given to a random user who voted the same as the
171//! selected vote as a reward for participating in the vote.
172//!
173//! If the candidate wins the vote, they receive their bid reward as a future payout.
174//! If the bid was placed by a voucher, they will receive their portion of the reward,
175//! before the rest is paid to the winning candidate.
176//!
177//! One winning candidate is selected as the Head of the members. This is randomly
178//! chosen, weighted by the number of approvals the winning candidates accumulated.
179//!
180//! If the candidate loses the vote, they are suspended and it is up to the Suspension
181//! Judgement origin to determine if the candidate should go through the bidding process
182//! again, should be accepted into the membership society, or rejected and their deposit
183//! slashed.
184//!
185//! #### Member Phase
186//!
187//! Once a candidate becomes a member, their role is to participate in society.
188//!
189//! Regular participation involves voting on candidates who want to join the membership
190//! society, and by voting in the right way, a member will accumulate future payouts.
191//! When a payout matures, members are able to claim those payouts.
192//!
193//! Members can also vouch for users to join the society, and request a "tip" from
194//! the fees the new member would collect by joining the society. This vouching
195//! process is useful in situations where a user may not have enough balance to
196//! satisfy the bid deposit. A member can only vouch one user at a time.
197//!
198//! During rotation periods, a random group of members are selected as "skeptics".
199//! These skeptics are expected to vote on the current candidates. If they do not vote,
200//! their skeptic status is treated as a rejection vote, the member is deemed
201//! "lazy", and are given a strike per missing vote.
202//!
203//! There is a challenge period in parallel to the rotation period. During a challenge period,
204//! a random member is selected to defend their membership to the society. Other members
205//! make a traditional majority-wins vote to determine if the member should stay in the society.
206//! Ties are treated as a failure of the challenge.
207//!
208//! If a member accumulates too many strikes or fails their membership challenge,
209//! they will become suspended. While a member is suspended, they are unable to
210//! claim matured payouts. It is up to the Suspension Judgement origin to determine
211//! if the member should re-enter society or be removed from society with all their
212//! future payouts slashed.
213//!
214//! ## Interface
215//!
216//! ### Dispatchable Functions
217//!
218//! #### For General Users
219//!
220//! * `bid` - A user can make a bid to join the membership society by reserving a deposit.
221//! * `unbid` - A user can withdraw their bid for entry, the deposit is returned.
222//!
223//! #### For Members
224//!
225//! * `vouch` - A member can place a bid on behalf of a user to join the membership society.
226//! * `unvouch` - A member can revoke their vouch for a user.
227//! * `vote` - A member can vote to approve or reject a candidate's request to join the society.
228//! * `defender_vote` - A member can vote to approve or reject a defender's continued membership
229//! to the society.
230//! * `payout` - A member can claim their first matured payment.
231//! * `unfound` - Allow the founder to unfound the society when they are the only member.
232//!
233//! #### For Super Users
234//!
235//! * `found` - The founder origin can initiate this society. Useful for bootstrapping the Society
236//! pallet on an already running chain.
237//! * `judge_suspended_member` - The suspension judgement origin is able to make
238//! judgement on a suspended member.
239//! * `judge_suspended_candidate` - The suspension judgement origin is able to
240//! make judgement on a suspended candidate.
241//! * `set_max_membership` - The ROOT origin can update the maximum member count for the society.
242//! The max membership count must be greater than 1.
243
244// Ensure we're `no_std` when compiling for Wasm.
245#![cfg_attr(not(feature = "std"), no_std)]
246
247#[cfg(test)]
248mod mock;
249
250#[cfg(test)]
251mod tests;
252
253#[cfg(feature = "runtime-benchmarks")]
254mod benchmarking;
255
256pub mod weights;
257
258pub mod migrations;
259
260extern crate alloc;
261
262use alloc::vec::Vec;
263use frame_support::{
264	impl_ensure_origin_with_arg_ignoring_arg,
265	pallet_prelude::*,
266	storage::KeyLenOf,
267	traits::{
268		BalanceStatus, Currency, EnsureOrigin, EnsureOriginWithArg,
269		ExistenceRequirement::AllowDeath, Imbalance, OnUnbalanced, Randomness, ReservableCurrency,
270		StorageVersion,
271	},
272	PalletId,
273};
274use frame_system::pallet_prelude::{
275	ensure_signed, BlockNumberFor as SystemBlockNumberFor, OriginFor,
276};
277use rand_chacha::{
278	rand_core::{RngCore, SeedableRng},
279	ChaChaRng,
280};
281use scale_info::TypeInfo;
282use sp_runtime::{
283	traits::{
284		AccountIdConversion, CheckedAdd, CheckedSub, Hash, Saturating, StaticLookup,
285		TrailingZeroInput, Zero,
286	},
287	ArithmeticError::Overflow,
288	Debug, Percent,
289};
290
291pub use weights::WeightInfo;
292
293pub use pallet::*;
294use sp_runtime::traits::BlockNumberProvider;
295
296pub type BlockNumberFor<T, I> =
297	<<T as Config<I>>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
298
299pub type BalanceOf<T, I> =
300	<<T as Config<I>>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
301pub type NegativeImbalanceOf<T, I> = <<T as Config<I>>::Currency as Currency<
302	<T as frame_system::Config>::AccountId,
303>>::NegativeImbalance;
304pub type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
305
306#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
307pub struct Vote {
308	pub approve: bool,
309	pub weight: u32,
310}
311
312/// A judgement by the suspension judgement origin on a suspended candidate.
313#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
314pub enum Judgement {
315	/// The suspension judgement origin takes no direct judgment
316	/// and places the candidate back into the bid pool.
317	Rebid,
318	/// The suspension judgement origin has rejected the candidate's application.
319	Reject,
320	/// The suspension judgement origin approves of the candidate's application.
321	Approve,
322}
323
324/// Details of a payout given as a per-block linear "trickle".
325#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, Default, TypeInfo, MaxEncodedLen)]
326pub struct Payout<Balance, BlockNumber> {
327	/// Total value of the payout.
328	pub value: Balance,
329	/// Block number at which the payout begins.
330	pub begin: BlockNumber,
331	/// Total number of blocks over which the payout is spread.
332	pub duration: BlockNumber,
333	/// Total value paid out so far.
334	pub paid: Balance,
335}
336
337/// Status of a vouching member.
338#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
339pub enum VouchingStatus {
340	/// Member is currently vouching for a user.
341	Vouching,
342	/// Member is banned from vouching for other members.
343	Banned,
344}
345
346/// Number of strikes that a member has against them.
347pub type StrikeCount = u32;
348
349/// A bid for entry into society.
350#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
351pub struct Bid<AccountId, Balance> {
352	/// The bidder/candidate trying to enter society
353	pub who: AccountId,
354	/// The kind of bid placed for this bidder/candidate. See `BidKind`.
355	pub kind: BidKind<AccountId, Balance>,
356	/// The reward that the bidder has requested for successfully joining the society.
357	pub value: Balance,
358}
359
360/// The index of a round of candidates.
361pub type RoundIndex = u32;
362
363/// The rank of a member.
364pub type Rank = u32;
365
366/// The number of votes.
367pub type VoteCount = u32;
368
369/// Tally of votes.
370#[derive(Default, Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
371pub struct Tally {
372	/// The approval votes.
373	pub approvals: VoteCount,
374	/// The rejection votes.
375	pub rejections: VoteCount,
376}
377
378impl Tally {
379	fn more_approvals(&self) -> bool {
380		self.approvals > self.rejections
381	}
382
383	fn more_rejections(&self) -> bool {
384		self.rejections > self.approvals
385	}
386
387	fn clear_approval(&self) -> bool {
388		self.approvals >= (2 * self.rejections).max(1)
389	}
390
391	fn clear_rejection(&self) -> bool {
392		self.rejections >= (2 * self.approvals).max(1)
393	}
394}
395
396/// A bid for entry into society.
397#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
398pub struct Candidacy<AccountId, Balance> {
399	/// The index of the round where the candidacy began.
400	pub round: RoundIndex,
401	/// The kind of bid placed for this bidder/candidate. See `BidKind`.
402	pub kind: BidKind<AccountId, Balance>,
403	/// The reward that the bidder has requested for successfully joining the society.
404	pub bid: Balance,
405	/// The tally of votes so far.
406	pub tally: Tally,
407	/// True if the skeptic was already punished for note voting.
408	pub skeptic_struck: bool,
409}
410
411/// A vote by a member on a candidate application.
412#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
413pub enum BidKind<AccountId, Balance> {
414	/// The given deposit was paid for this bid.
415	Deposit(Balance),
416	/// A member vouched for this bid. The account should be reinstated into `Members` once the
417	/// bid is successful (or if it is rescinded prior to launch).
418	Vouch(AccountId, Balance),
419}
420
421impl<AccountId: PartialEq, Balance> BidKind<AccountId, Balance> {
422	fn is_vouch(&self, v: &AccountId) -> bool {
423		matches!(self, BidKind::Vouch(ref a, _) if a == v)
424	}
425}
426
427pub type PayoutsFor<T, I> =
428	BoundedVec<(BlockNumberFor<T, I>, BalanceOf<T, I>), <T as Config<I>>::MaxPayouts>;
429
430/// Information concerning a member.
431#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
432pub struct MemberRecord {
433	pub rank: Rank,
434	pub strikes: StrikeCount,
435	pub vouching: Option<VouchingStatus>,
436	pub index: u32,
437}
438
439/// Information concerning a member.
440#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, Default, MaxEncodedLen)]
441pub struct PayoutRecord<Balance, PayoutsVec> {
442	pub paid: Balance,
443	pub payouts: PayoutsVec,
444}
445
446pub type PayoutRecordFor<T, I> = PayoutRecord<
447	BalanceOf<T, I>,
448	BoundedVec<(BlockNumberFor<T, I>, BalanceOf<T, I>), <T as Config<I>>::MaxPayouts>,
449>;
450
451/// Record for an individual new member who was elevated from a candidate recently.
452#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
453pub struct IntakeRecord<AccountId, Balance> {
454	pub who: AccountId,
455	pub bid: Balance,
456	pub round: RoundIndex,
457}
458
459pub type IntakeRecordFor<T, I> =
460	IntakeRecord<<T as frame_system::Config>::AccountId, BalanceOf<T, I>>;
461
462#[derive(
463	Encode,
464	Decode,
465	DecodeWithMemTracking,
466	Copy,
467	Clone,
468	PartialEq,
469	Eq,
470	Debug,
471	TypeInfo,
472	MaxEncodedLen,
473)]
474pub struct GroupParams<Balance> {
475	pub max_members: u32,
476	pub max_intake: u32,
477	pub max_strikes: u32,
478	pub candidate_deposit: Balance,
479}
480
481pub type GroupParamsFor<T, I> = GroupParams<BalanceOf<T, I>>;
482
483pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
484
485#[frame_support::pallet]
486pub mod pallet {
487	use super::*;
488
489	#[pallet::pallet]
490	#[pallet::storage_version(STORAGE_VERSION)]
491	pub struct Pallet<T, I = ()>(_);
492
493	#[pallet::config]
494	pub trait Config<I: 'static = ()>: frame_system::Config {
495		/// The overarching event type.
496		#[allow(deprecated)]
497		type RuntimeEvent: From<Event<Self, I>>
498			+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
499
500		/// The societies's pallet id
501		#[pallet::constant]
502		type PalletId: Get<PalletId>;
503
504		/// The currency type used for bidding.
505		type Currency: ReservableCurrency<Self::AccountId>;
506
507		/// Something that provides randomness in the runtime.
508		type Randomness: Randomness<Self::Hash, BlockNumberFor<Self, I>>;
509
510		/// The maximum number of strikes before a member gets funds slashed.
511		#[pallet::constant]
512		type GraceStrikes: Get<u32>;
513
514		/// The amount of incentive paid within each period. Doesn't include VoterTip.
515		#[pallet::constant]
516		type PeriodSpend: Get<BalanceOf<Self, I>>;
517
518		/// The number of [Config::BlockNumberProvider] blocks on which new candidates should be
519		/// voted on. Together with
520		/// `ClaimPeriod`, this sums to the number of blocks between candidate intake periods.
521		#[pallet::constant]
522		type VotingPeriod: Get<BlockNumberFor<Self, I>>;
523
524		/// The number of [Config::BlockNumberProvider] blocks on which new candidates can claim
525		/// their membership and be the named head.
526		#[pallet::constant]
527		type ClaimPeriod: Get<BlockNumberFor<Self, I>>;
528
529		/// The maximum duration of the payout lock.
530		#[pallet::constant]
531		type MaxLockDuration: Get<BlockNumberFor<Self, I>>;
532
533		/// The origin that is allowed to call `found`.
534		type FounderSetOrigin: EnsureOrigin<Self::RuntimeOrigin>;
535
536		/// The number of [Config::BlockNumberProvider] blocks between membership challenges.
537		#[pallet::constant]
538		type ChallengePeriod: Get<BlockNumberFor<Self, I>>;
539
540		/// The maximum number of payouts a member may have waiting unclaimed.
541		#[pallet::constant]
542		type MaxPayouts: Get<u32>;
543
544		/// The maximum number of bids at once.
545		#[pallet::constant]
546		type MaxBids: Get<u32>;
547
548		/// Weight information for extrinsics in this pallet.
549		type WeightInfo: WeightInfo;
550		/// Provider for the block number. Normally this is the `frame_system` pallet.
551		type BlockNumberProvider: BlockNumberProvider;
552	}
553
554	#[pallet::error]
555	pub enum Error<T, I = ()> {
556		/// User is not a member.
557		NotMember,
558		/// User is already a member.
559		AlreadyMember,
560		/// User is suspended.
561		Suspended,
562		/// User is not suspended.
563		NotSuspended,
564		/// Nothing to payout.
565		NoPayout,
566		/// Society already founded.
567		AlreadyFounded,
568		/// Not enough in pot to accept candidate.
569		InsufficientPot,
570		/// Member is already vouching or banned from vouching again.
571		AlreadyVouching,
572		/// Member is not vouching.
573		NotVouchingOnBidder,
574		/// Cannot remove the head of the chain.
575		Head,
576		/// Cannot remove the founder.
577		Founder,
578		/// User has already made a bid.
579		AlreadyBid,
580		/// User is already a candidate.
581		AlreadyCandidate,
582		/// User is not a candidate.
583		NotCandidate,
584		/// Too many members in the society.
585		MaxMembers,
586		/// The caller is not the founder.
587		NotFounder,
588		/// The caller is not the head.
589		NotHead,
590		/// The membership cannot be claimed as the candidate was not clearly approved.
591		NotApproved,
592		/// The candidate cannot be kicked as the candidate was not clearly rejected.
593		NotRejected,
594		/// The candidacy cannot be dropped as the candidate was clearly approved.
595		Approved,
596		/// The candidacy cannot be bestowed as the candidate was clearly rejected.
597		Rejected,
598		/// The candidacy cannot be concluded as the voting is still in progress.
599		InProgress,
600		/// The candidacy cannot be pruned until a full additional intake period has passed.
601		TooEarly,
602		/// The skeptic already voted.
603		Voted,
604		/// The skeptic need not vote on candidates from expired rounds.
605		Expired,
606		/// User is not a bidder.
607		NotBidder,
608		/// There is no defender currently.
609		NoDefender,
610		/// Group doesn't exist.
611		NotGroup,
612		/// The member is already elevated to this rank.
613		AlreadyElevated,
614		/// The skeptic has already been punished for this offence.
615		AlreadyPunished,
616		/// Funds are insufficient to pay off society debts.
617		InsufficientFunds,
618		/// The candidate/defender has no stale votes to remove.
619		NoVotes,
620		/// There is no deposit associated with a bid.
621		NoDeposit,
622	}
623
624	#[pallet::event]
625	#[pallet::generate_deposit(pub(super) fn deposit_event)]
626	pub enum Event<T: Config<I>, I: 'static = ()> {
627		/// The society is founded by the given identity.
628		Founded { founder: T::AccountId },
629		/// A membership bid just happened. The given account is the candidate's ID and their offer
630		/// is the second.
631		Bid { candidate_id: T::AccountId, offer: BalanceOf<T, I> },
632		/// A membership bid just happened by vouching. The given account is the candidate's ID and
633		/// their offer is the second. The vouching party is the third.
634		Vouch { candidate_id: T::AccountId, offer: BalanceOf<T, I>, vouching: T::AccountId },
635		/// A candidate was dropped (due to an excess of bids in the system).
636		AutoUnbid { candidate: T::AccountId },
637		/// A candidate was dropped (by their request).
638		Unbid { candidate: T::AccountId },
639		/// A candidate was dropped (by request of who vouched for them).
640		Unvouch { candidate: T::AccountId },
641		/// A group of candidates have been inducted. The batch's primary is the first value, the
642		/// batch in full is the second.
643		Inducted { primary: T::AccountId, candidates: Vec<T::AccountId> },
644		/// A suspended member has been judged.
645		SuspendedMemberJudgement { who: T::AccountId, judged: bool },
646		/// A candidate has been suspended
647		CandidateSuspended { candidate: T::AccountId },
648		/// A member has been suspended
649		MemberSuspended { member: T::AccountId },
650		/// A member has been challenged
651		Challenged { member: T::AccountId },
652		/// A vote has been placed
653		Vote { candidate: T::AccountId, voter: T::AccountId, vote: bool },
654		/// A vote has been placed for a defending member
655		DefenderVote { voter: T::AccountId, vote: bool },
656		/// A new set of \[params\] has been set for the group.
657		NewParams { params: GroupParamsFor<T, I> },
658		/// Society is unfounded.
659		Unfounded { founder: T::AccountId },
660		/// Some funds were deposited into the society account.
661		Deposit { value: BalanceOf<T, I> },
662		/// A \[member\] got elevated to \[rank\].
663		Elevated { member: T::AccountId, rank: Rank },
664		/// A deposit was poked / adjusted.
665		DepositPoked {
666			who: T::AccountId,
667			old_deposit: BalanceOf<T, I>,
668			new_deposit: BalanceOf<T, I>,
669		},
670		/// A member was kicked by the founder.
671		MemberKicked { member: T::AccountId },
672	}
673
674	/// Old name generated by `decl_event`.
675	#[deprecated(note = "use `Event` instead")]
676	pub type RawEvent<T, I = ()> = Event<T, I>;
677
678	/// The max number of members for the society at one time.
679	#[pallet::storage]
680	pub type Parameters<T: Config<I>, I: 'static = ()> =
681		StorageValue<_, GroupParamsFor<T, I>, OptionQuery>;
682
683	/// Amount of our account balance that is specifically for the next round's bid(s).
684	#[pallet::storage]
685	pub type Pot<T: Config<I>, I: 'static = ()> = StorageValue<_, BalanceOf<T, I>, ValueQuery>;
686
687	/// The first member.
688	#[pallet::storage]
689	pub type Founder<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId>;
690
691	/// The most primary from the most recently approved rank 0 members in the society.
692	#[pallet::storage]
693	pub type Head<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId>;
694
695	/// A hash of the rules of this society concerning membership. Can only be set once and
696	/// only by the founder.
697	#[pallet::storage]
698	pub type Rules<T: Config<I>, I: 'static = ()> = StorageValue<_, T::Hash>;
699
700	/// The current members and their rank. Doesn't include `SuspendedMembers`.
701	#[pallet::storage]
702	pub type Members<T: Config<I>, I: 'static = ()> =
703		StorageMap<_, Twox64Concat, T::AccountId, MemberRecord, OptionQuery>;
704
705	/// Information regarding rank-0 payouts, past and future.
706	#[pallet::storage]
707	pub type Payouts<T: Config<I>, I: 'static = ()> =
708		StorageMap<_, Twox64Concat, T::AccountId, PayoutRecordFor<T, I>, ValueQuery>;
709
710	/// The number of items in `Members` currently. (Doesn't include `SuspendedMembers`.)
711	#[pallet::storage]
712	pub type MemberCount<T: Config<I>, I: 'static = ()> = StorageValue<_, u32, ValueQuery>;
713
714	/// The current items in `Members` keyed by their unique index. Keys are densely populated
715	/// `0..MemberCount` (does not include `MemberCount`).
716	#[pallet::storage]
717	pub type MemberByIndex<T: Config<I>, I: 'static = ()> =
718		StorageMap<_, Twox64Concat, u32, T::AccountId, OptionQuery>;
719
720	/// The set of suspended members, with their old membership record.
721	#[pallet::storage]
722	pub type SuspendedMembers<T: Config<I>, I: 'static = ()> =
723		StorageMap<_, Twox64Concat, T::AccountId, MemberRecord, OptionQuery>;
724
725	/// The number of rounds which have passed.
726	#[pallet::storage]
727	pub type RoundCount<T: Config<I>, I: 'static = ()> = StorageValue<_, RoundIndex, ValueQuery>;
728
729	/// The current bids, stored ordered by the value of the bid.
730	#[pallet::storage]
731	pub type Bids<T: Config<I>, I: 'static = ()> =
732		StorageValue<_, BoundedVec<Bid<T::AccountId, BalanceOf<T, I>>, T::MaxBids>, ValueQuery>;
733
734	#[pallet::storage]
735	pub type Candidates<T: Config<I>, I: 'static = ()> = StorageMap<
736		_,
737		Blake2_128Concat,
738		T::AccountId,
739		Candidacy<T::AccountId, BalanceOf<T, I>>,
740		OptionQuery,
741	>;
742
743	/// The current skeptic.
744	#[pallet::storage]
745	pub type Skeptic<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId, OptionQuery>;
746
747	/// Double map from Candidate -> Voter -> (Maybe) Vote.
748	#[pallet::storage]
749	pub type Votes<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
750		_,
751		Twox64Concat,
752		T::AccountId,
753		Twox64Concat,
754		T::AccountId,
755		Vote,
756		OptionQuery,
757	>;
758
759	/// Clear-cursor for Vote, map from Candidate -> (Maybe) Cursor.
760	#[pallet::storage]
761	pub type VoteClearCursor<T: Config<I>, I: 'static = ()> =
762		StorageMap<_, Twox64Concat, T::AccountId, BoundedVec<u8, KeyLenOf<Votes<T, I>>>>;
763
764	/// At the end of the claim period, this contains the most recently approved members (along with
765	/// their bid and round ID) who is from the most recent round with the lowest bid. They will
766	/// become the new `Head`.
767	#[pallet::storage]
768	pub type NextHead<T: Config<I>, I: 'static = ()> =
769		StorageValue<_, IntakeRecordFor<T, I>, OptionQuery>;
770
771	/// The number of challenge rounds there have been. Used to identify stale DefenderVotes.
772	#[pallet::storage]
773	pub type ChallengeRoundCount<T: Config<I>, I: 'static = ()> =
774		StorageValue<_, RoundIndex, ValueQuery>;
775
776	/// The defending member currently being challenged, along with a running tally of votes.
777	#[pallet::storage]
778	pub type Defending<T: Config<I>, I: 'static = ()> =
779		StorageValue<_, (T::AccountId, T::AccountId, Tally)>;
780
781	/// Votes for the defender, keyed by challenge round.
782	#[pallet::storage]
783	pub type DefenderVotes<T: Config<I>, I: 'static = ()> =
784		StorageDoubleMap<_, Twox64Concat, RoundIndex, Twox64Concat, T::AccountId, Vote>;
785
786	/// Next intake rotation scheduled with [Config::BlockNumberProvider].
787	#[pallet::storage]
788	pub type NextIntakeAt<T: Config<I>, I: 'static = ()> = StorageValue<_, BlockNumberFor<T, I>>;
789
790	/// Next challenge rotation scheduled with [Config::BlockNumberProvider].
791	#[pallet::storage]
792	pub type NextChallengeAt<T: Config<I>, I: 'static = ()> = StorageValue<_, BlockNumberFor<T, I>>;
793
794	#[pallet::hooks]
795	impl<T: Config<I>, I: 'static> Hooks<SystemBlockNumberFor<T>> for Pallet<T, I> {
796		fn on_initialize(_n: SystemBlockNumberFor<T>) -> Weight {
797			let mut weight = Weight::zero();
798			let weights = T::BlockWeights::get();
799			let now = T::BlockNumberProvider::current_block_number();
800
801			let phrase = b"society_rotation";
802			// we'll need a random seed here.
803			// TODO: deal with randomness freshness
804			// https://github.com/paritytech/substrate/issues/8312
805			let (seed, _) = T::Randomness::random(phrase);
806			// seed needs to be guaranteed to be 32 bytes.
807			let seed = <[u8; 32]>::decode(&mut TrailingZeroInput::new(seed.as_ref()))
808				.expect("input is padded with zeroes; qed");
809			let mut rng = ChaChaRng::from_seed(seed);
810
811			// Run a candidate/membership rotation
812			let is_intake_moment = match Self::period() {
813				Period::Intake { .. } => true,
814				_ => false,
815			};
816			if is_intake_moment {
817				Self::rotate_intake(&mut rng);
818				weight.saturating_accrue(weights.max_block / 20);
819				Self::set_next_intake_at();
820			}
821
822			// Run a challenge rotation
823			if now >= Self::next_challenge_at() {
824				Self::rotate_challenge(&mut rng);
825				weight.saturating_accrue(weights.max_block / 20);
826				Self::set_next_challenge_at();
827			}
828
829			weight
830		}
831
832		#[cfg(feature = "try-runtime")]
833		fn try_state(_: SystemBlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
834			Self::do_try_state()
835		}
836	}
837
838	#[pallet::genesis_config]
839	#[derive(frame_support::DefaultNoBound)]
840	pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
841		pub pot: BalanceOf<T, I>,
842	}
843
844	#[pallet::genesis_build]
845	impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
846		fn build(&self) {
847			Pot::<T, I>::put(self.pot);
848		}
849	}
850
851	#[pallet::call]
852	impl<T: Config<I>, I: 'static> Pallet<T, I> {
853		/// A user outside of the society can make a bid for entry.
854		///
855		/// Payment: The group's Candidate Deposit will be reserved for making a bid. It is returned
856		/// when the bid becomes a member, or if the bid calls `unbid`.
857		///
858		/// The dispatch origin for this call must be _Signed_.
859		///
860		/// Parameters:
861		/// - `value`: A one time payment the bid would like to receive when joining the society.
862		#[pallet::call_index(0)]
863		#[pallet::weight(T::WeightInfo::bid())]
864		pub fn bid(origin: OriginFor<T>, value: BalanceOf<T, I>) -> DispatchResult {
865			let who = ensure_signed(origin)?;
866
867			let mut bids = Bids::<T, I>::get();
868			ensure!(!Self::has_bid(&bids, &who), Error::<T, I>::AlreadyBid);
869			ensure!(!Candidates::<T, I>::contains_key(&who), Error::<T, I>::AlreadyCandidate);
870			ensure!(!Members::<T, I>::contains_key(&who), Error::<T, I>::AlreadyMember);
871			ensure!(!SuspendedMembers::<T, I>::contains_key(&who), Error::<T, I>::Suspended);
872
873			let params = Parameters::<T, I>::get().ok_or(Error::<T, I>::NotGroup)?;
874			let deposit = params.candidate_deposit;
875			// NOTE: Reserve must happen before `insert_bid` since that could end up unreserving.
876			T::Currency::reserve(&who, deposit)?;
877			Self::insert_bid(&mut bids, &who, value, BidKind::Deposit(deposit));
878
879			Bids::<T, I>::put(bids);
880			Self::deposit_event(Event::<T, I>::Bid { candidate_id: who, offer: value });
881			Ok(())
882		}
883
884		/// A bidder can remove their bid for entry into society.
885		/// By doing so, they will have their candidate deposit returned or
886		/// they will unvouch their voucher.
887		///
888		/// Payment: The bid deposit is unreserved if the user made a bid.
889		///
890		/// The dispatch origin for this call must be _Signed_ and a bidder.
891		#[pallet::call_index(1)]
892		#[pallet::weight(T::WeightInfo::unbid())]
893		pub fn unbid(origin: OriginFor<T>) -> DispatchResult {
894			let who = ensure_signed(origin)?;
895
896			let mut bids = Bids::<T, I>::get();
897			let pos = bids.iter().position(|bid| bid.who == who).ok_or(Error::<T, I>::NotBidder)?;
898			Self::clean_bid(&bids.remove(pos));
899			Bids::<T, I>::put(bids);
900			Self::deposit_event(Event::<T, I>::Unbid { candidate: who });
901			Ok(())
902		}
903
904		/// As a member, vouch for someone to join society by placing a bid on their behalf.
905		///
906		/// There is no deposit required to vouch for a new bid, but a member can only vouch for
907		/// one bid at a time. If the bid becomes a suspended candidate and ultimately rejected by
908		/// the suspension judgement origin, the member will be banned from vouching again.
909		///
910		/// As a vouching member, you can claim a tip if the candidate is accepted. This tip will
911		/// be paid as a portion of the reward the member will receive for joining the society.
912		///
913		/// The dispatch origin for this call must be _Signed_ and a member.
914		///
915		/// Parameters:
916		/// - `who`: The user who you would like to vouch for.
917		/// - `value`: The total reward to be paid between you and the candidate if they become
918		/// a member in the society.
919		/// - `tip`: Your cut of the total `value` payout when the candidate is inducted into
920		/// the society. Tips larger than `value` will be saturated upon payout.
921		#[pallet::call_index(2)]
922		#[pallet::weight(T::WeightInfo::vouch())]
923		pub fn vouch(
924			origin: OriginFor<T>,
925			who: AccountIdLookupOf<T>,
926			value: BalanceOf<T, I>,
927			tip: BalanceOf<T, I>,
928		) -> DispatchResult {
929			let voucher = ensure_signed(origin)?;
930			let who = T::Lookup::lookup(who)?;
931
932			// Get bids and check user is not bidding.
933			let mut bids = Bids::<T, I>::get();
934			ensure!(!Self::has_bid(&bids, &who), Error::<T, I>::AlreadyBid);
935
936			// Check user is not already a candidate, member or suspended member.
937			ensure!(!Candidates::<T, I>::contains_key(&who), Error::<T, I>::AlreadyCandidate);
938			ensure!(!Members::<T, I>::contains_key(&who), Error::<T, I>::AlreadyMember);
939			ensure!(!SuspendedMembers::<T, I>::contains_key(&who), Error::<T, I>::Suspended);
940
941			// Check sender can vouch.
942			let mut record = Members::<T, I>::get(&voucher).ok_or(Error::<T, I>::NotMember)?;
943			ensure!(record.vouching.is_none(), Error::<T, I>::AlreadyVouching);
944
945			// Update voucher record.
946			record.vouching = Some(VouchingStatus::Vouching);
947			// Update bids
948			Self::insert_bid(&mut bids, &who, value, BidKind::Vouch(voucher.clone(), tip));
949
950			// Write new state.
951			Members::<T, I>::insert(&voucher, &record);
952			Bids::<T, I>::put(bids);
953			Self::deposit_event(Event::<T, I>::Vouch {
954				candidate_id: who,
955				offer: value,
956				vouching: voucher,
957			});
958			Ok(())
959		}
960
961		/// As a vouching member, unvouch a bid. This only works while vouched user is
962		/// only a bidder (and not a candidate).
963		///
964		/// The dispatch origin for this call must be _Signed_ and a vouching member.
965		///
966		/// Parameters:
967		/// - `pos`: Position in the `Bids` vector of the bid who should be unvouched.
968		#[pallet::call_index(3)]
969		#[pallet::weight(T::WeightInfo::unvouch())]
970		pub fn unvouch(origin: OriginFor<T>) -> DispatchResult {
971			let voucher = ensure_signed(origin)?;
972
973			let mut bids = Bids::<T, I>::get();
974			let pos = bids
975				.iter()
976				.position(|bid| bid.kind.is_vouch(&voucher))
977				.ok_or(Error::<T, I>::NotVouchingOnBidder)?;
978			let bid = bids.remove(pos);
979			Self::clean_bid(&bid);
980
981			Bids::<T, I>::put(bids);
982			Self::deposit_event(Event::<T, I>::Unvouch { candidate: bid.who });
983			Ok(())
984		}
985
986		/// As a member, vote on a candidate.
987		///
988		/// The dispatch origin for this call must be _Signed_ and a member.
989		///
990		/// Parameters:
991		/// - `candidate`: The candidate that the member would like to bid on.
992		/// - `approve`: A boolean which says if the candidate should be approved (`true`) or
993		///   rejected (`false`).
994		#[pallet::call_index(4)]
995		#[pallet::weight(T::WeightInfo::vote())]
996		pub fn vote(
997			origin: OriginFor<T>,
998			candidate: AccountIdLookupOf<T>,
999			approve: bool,
1000		) -> DispatchResultWithPostInfo {
1001			let voter = ensure_signed(origin)?;
1002			let candidate = T::Lookup::lookup(candidate)?;
1003
1004			let mut candidacy =
1005				Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1006			let record = Members::<T, I>::get(&voter).ok_or(Error::<T, I>::NotMember)?;
1007
1008			let first_time = Votes::<T, I>::mutate(&candidate, &voter, |v| {
1009				let first_time = v.is_none();
1010				*v = Some(Self::do_vote(*v, approve, record.rank, &mut candidacy.tally));
1011				first_time
1012			});
1013
1014			Candidates::<T, I>::insert(&candidate, &candidacy);
1015			Self::deposit_event(Event::<T, I>::Vote { candidate, voter, vote: approve });
1016			Ok(if first_time { Pays::No } else { Pays::Yes }.into())
1017		}
1018
1019		/// As a member, vote on the defender.
1020		///
1021		/// The dispatch origin for this call must be _Signed_ and a member.
1022		///
1023		/// Parameters:
1024		/// - `approve`: A boolean which says if the candidate should be
1025		/// approved (`true`) or rejected (`false`).
1026		#[pallet::call_index(5)]
1027		#[pallet::weight(T::WeightInfo::defender_vote())]
1028		pub fn defender_vote(origin: OriginFor<T>, approve: bool) -> DispatchResultWithPostInfo {
1029			let voter = ensure_signed(origin)?;
1030
1031			let mut defending = Defending::<T, I>::get().ok_or(Error::<T, I>::NoDefender)?;
1032			let record = Members::<T, I>::get(&voter).ok_or(Error::<T, I>::NotMember)?;
1033
1034			let round = ChallengeRoundCount::<T, I>::get();
1035			let first_time = DefenderVotes::<T, I>::mutate(round, &voter, |v| {
1036				let first_time = v.is_none();
1037				*v = Some(Self::do_vote(*v, approve, record.rank, &mut defending.2));
1038				first_time
1039			});
1040
1041			Defending::<T, I>::put(defending);
1042			Self::deposit_event(Event::<T, I>::DefenderVote { voter, vote: approve });
1043			Ok(if first_time { Pays::No } else { Pays::Yes }.into())
1044		}
1045
1046		/// Transfer the first matured payout for the sender and remove it from the records.
1047		///
1048		/// NOTE: This extrinsic needs to be called multiple times to claim multiple matured
1049		/// payouts.
1050		///
1051		/// Payment: The member will receive a payment equal to their first matured
1052		/// payout to their free balance.
1053		///
1054		/// The dispatch origin for this call must be _Signed_ and a member with
1055		/// payouts remaining.
1056		#[pallet::call_index(6)]
1057		#[pallet::weight(T::WeightInfo::payout())]
1058		pub fn payout(origin: OriginFor<T>) -> DispatchResult {
1059			let who = ensure_signed(origin)?;
1060			ensure!(
1061				Members::<T, I>::get(&who).ok_or(Error::<T, I>::NotMember)?.rank == 0,
1062				Error::<T, I>::NoPayout
1063			);
1064			let mut record = Payouts::<T, I>::get(&who);
1065			let block_number = T::BlockNumberProvider::current_block_number();
1066			if let Some((when, amount)) = record.payouts.first() {
1067				if when <= &block_number {
1068					record.paid = record.paid.checked_add(amount).ok_or(Overflow)?;
1069					T::Currency::transfer(&Self::payouts(), &who, *amount, AllowDeath)?;
1070					record.payouts.remove(0);
1071					Payouts::<T, I>::insert(&who, record);
1072					return Ok(());
1073				}
1074			}
1075			Err(Error::<T, I>::NoPayout)?
1076		}
1077
1078		/// Repay the payment previously given to the member with the signed origin, remove any
1079		/// pending payments, and elevate them from rank 0 to rank 1.
1080		///
1081		/// The funds reserved for the forfeited pending payments are returned to the society pot.
1082		#[pallet::call_index(7)]
1083		#[pallet::weight(T::WeightInfo::waive_repay())]
1084		pub fn waive_repay(origin: OriginFor<T>, amount: BalanceOf<T, I>) -> DispatchResult {
1085			let who = ensure_signed(origin)?;
1086			let mut record = Members::<T, I>::get(&who).ok_or(Error::<T, I>::NotMember)?;
1087			let mut payout_record = Payouts::<T, I>::get(&who);
1088			ensure!(record.rank == 0, Error::<T, I>::AlreadyElevated);
1089			ensure!(amount >= payout_record.paid, Error::<T, I>::InsufficientFunds);
1090
1091			T::Currency::transfer(&who, &Self::account_id(), payout_record.paid, AllowDeath)?;
1092			let total = payout_record
1093				.payouts
1094				.drain(..)
1095				.fold(Zero::zero(), |acc: BalanceOf<T, I>, x| acc.saturating_add(x.1));
1096			Self::unreserve_payout(total);
1097			payout_record.paid = Zero::zero();
1098			record.rank = 1;
1099			Members::<T, I>::insert(&who, record);
1100			Payouts::<T, I>::insert(&who, payout_record);
1101			Self::deposit_event(Event::<T, I>::Elevated { member: who, rank: 1 });
1102
1103			Ok(())
1104		}
1105
1106		/// Found the society.
1107		///
1108		/// This is done as a discrete action in order to allow for the
1109		/// pallet to be included into a running chain and can only be done once.
1110		///
1111		/// The dispatch origin for this call must be from the _FounderSetOrigin_.
1112		///
1113		/// Parameters:
1114		/// - `founder` - The first member and head of the newly founded society.
1115		/// - `max_members` - The initial max number of members for the society.
1116		/// - `max_intake` - The maximum number of candidates per intake period.
1117		/// - `max_strikes`: The maximum number of strikes a member may get before they become
1118		///   suspended and may only be reinstated by the founder.
1119		/// - `candidate_deposit`: The deposit required to make a bid for membership of the group.
1120		/// - `rules` - The rules of this society concerning membership.
1121		///
1122		/// Complexity: O(1)
1123		#[pallet::call_index(8)]
1124		#[pallet::weight(T::WeightInfo::found_society())]
1125		pub fn found_society(
1126			origin: OriginFor<T>,
1127			founder: AccountIdLookupOf<T>,
1128			max_members: u32,
1129			max_intake: u32,
1130			max_strikes: u32,
1131			candidate_deposit: BalanceOf<T, I>,
1132			rules: Vec<u8>,
1133		) -> DispatchResult {
1134			T::FounderSetOrigin::ensure_origin(origin)?;
1135			let founder = T::Lookup::lookup(founder)?;
1136			ensure!(!Head::<T, I>::exists(), Error::<T, I>::AlreadyFounded);
1137			ensure!(max_members > 1, Error::<T, I>::MaxMembers);
1138			// This should never fail in the context of this function...
1139			let params = GroupParams { max_members, max_intake, max_strikes, candidate_deposit };
1140			Parameters::<T, I>::put(params);
1141			Self::insert_member(&founder, 1)?;
1142			Head::<T, I>::put(&founder);
1143			Founder::<T, I>::put(&founder);
1144			Rules::<T, I>::put(T::Hashing::hash(&rules));
1145			Self::deposit_event(Event::<T, I>::Founded { founder });
1146			Ok(())
1147		}
1148
1149		/// Dissolve the society and remove all members.
1150		///
1151		/// The dispatch origin for this call must be Signed, and the signing account must be both
1152		/// the `Founder` and the `Head`. This implies that it may only be done when there is one
1153		/// member.
1154		#[pallet::call_index(9)]
1155		#[pallet::weight(T::WeightInfo::dissolve())]
1156		pub fn dissolve(origin: OriginFor<T>) -> DispatchResult {
1157			let founder = ensure_signed(origin)?;
1158			ensure!(Founder::<T, I>::get().as_ref() == Some(&founder), Error::<T, I>::NotFounder);
1159			ensure!(MemberCount::<T, I>::get() == 1, Error::<T, I>::NotHead);
1160
1161			let _ = Members::<T, I>::clear(u32::MAX, None);
1162			MemberCount::<T, I>::kill();
1163			let _ = MemberByIndex::<T, I>::clear(u32::MAX, None);
1164			let _ = SuspendedMembers::<T, I>::clear(u32::MAX, None);
1165			// Return the funds backing the discarded pending payouts to the society account. On
1166			// failure, abort the dissolution rather than stranding the funds in the payouts
1167			// account with no records left to claim them.
1168			let payouts_account = Self::payouts();
1169			T::Currency::transfer(
1170				&payouts_account,
1171				&Self::account_id(),
1172				T::Currency::free_balance(&payouts_account),
1173				AllowDeath,
1174			)?;
1175			let _ = Payouts::<T, I>::clear(u32::MAX, None);
1176			let _ = Votes::<T, I>::clear(u32::MAX, None);
1177			let _ = VoteClearCursor::<T, I>::clear(u32::MAX, None);
1178			Head::<T, I>::kill();
1179			NextHead::<T, I>::kill();
1180			Founder::<T, I>::kill();
1181			Rules::<T, I>::kill();
1182			Parameters::<T, I>::kill();
1183			Pot::<T, I>::kill();
1184			RoundCount::<T, I>::kill();
1185			Bids::<T, I>::kill();
1186			Skeptic::<T, I>::kill();
1187			ChallengeRoundCount::<T, I>::kill();
1188			Defending::<T, I>::kill();
1189			let _ = DefenderVotes::<T, I>::clear(u32::MAX, None);
1190			let _ = Candidates::<T, I>::clear(u32::MAX, None);
1191			Self::deposit_event(Event::<T, I>::Unfounded { founder });
1192			Ok(())
1193		}
1194
1195		/// Allow suspension judgement origin to make judgement on a suspended member.
1196		///
1197		/// If a suspended member is forgiven, we simply add them back as a member, not affecting
1198		/// any of the existing storage items for that member.
1199		///
1200		/// If a suspended member is rejected, remove all associated storage items, including
1201		/// their payouts, and remove any vouched bids they currently have.
1202		///
1203		/// The dispatch origin for this call must be Signed from the Founder.
1204		///
1205		/// Parameters:
1206		/// - `who` - The suspended member to be judged.
1207		/// - `forgive` - A boolean representing whether the suspension judgement origin forgives
1208		///   (`true`) or rejects (`false`) a suspended member.
1209		#[pallet::call_index(10)]
1210		#[pallet::weight(T::WeightInfo::judge_suspended_member())]
1211		pub fn judge_suspended_member(
1212			origin: OriginFor<T>,
1213			who: AccountIdLookupOf<T>,
1214			forgive: bool,
1215		) -> DispatchResultWithPostInfo {
1216			ensure!(
1217				Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1218				Error::<T, I>::NotFounder
1219			);
1220			let who = T::Lookup::lookup(who)?;
1221			let record = SuspendedMembers::<T, I>::get(&who).ok_or(Error::<T, I>::NotSuspended)?;
1222			if forgive {
1223				// Try to add member back to society. Can fail with `MaxMembers` limit.
1224				Self::reinstate_member(&who, record.rank)?;
1225			} else {
1226				let payout_record = Payouts::<T, I>::take(&who);
1227				let total = payout_record
1228					.payouts
1229					.into_iter()
1230					.map(|x| x.1)
1231					.fold(Zero::zero(), |acc: BalanceOf<T, I>, x| acc.saturating_add(x));
1232				Self::unreserve_payout(total);
1233			}
1234			SuspendedMembers::<T, I>::remove(&who);
1235			Self::deposit_event(Event::<T, I>::SuspendedMemberJudgement { who, judged: forgive });
1236			Ok(Pays::No.into())
1237		}
1238
1239		/// Change the maximum number of members in society and the maximum number of new candidates
1240		/// in a single intake period.
1241		///
1242		/// The dispatch origin for this call must be Signed by the Founder.
1243		///
1244		/// Parameters:
1245		/// - `max_members` - The maximum number of members for the society. This must be no less
1246		///   than the current number of members.
1247		/// - `max_intake` - The maximum number of candidates per intake period.
1248		/// - `max_strikes`: The maximum number of strikes a member may get before they become
1249		///   suspended and may only be reinstated by the founder.
1250		/// - `candidate_deposit`: The deposit required to make a bid for membership of the group.
1251		#[pallet::call_index(11)]
1252		#[pallet::weight(T::WeightInfo::set_parameters())]
1253		pub fn set_parameters(
1254			origin: OriginFor<T>,
1255			max_members: u32,
1256			max_intake: u32,
1257			max_strikes: u32,
1258			candidate_deposit: BalanceOf<T, I>,
1259		) -> DispatchResult {
1260			ensure!(
1261				Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1262				Error::<T, I>::NotFounder
1263			);
1264			ensure!(max_members >= MemberCount::<T, I>::get(), Error::<T, I>::MaxMembers);
1265			let params = GroupParams { max_members, max_intake, max_strikes, candidate_deposit };
1266			Parameters::<T, I>::put(&params);
1267			Self::deposit_event(Event::<T, I>::NewParams { params });
1268			Ok(())
1269		}
1270
1271		/// Punish the skeptic with a strike if they did not vote on a candidate. Callable by the
1272		/// candidate.
1273		#[pallet::call_index(12)]
1274		#[pallet::weight(T::WeightInfo::punish_skeptic())]
1275		pub fn punish_skeptic(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1276			let candidate = ensure_signed(origin)?;
1277			let mut candidacy =
1278				Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1279			ensure!(!candidacy.skeptic_struck, Error::<T, I>::AlreadyPunished);
1280			ensure!(!Self::in_progress(candidacy.round), Error::<T, I>::InProgress);
1281			let punished = Self::check_skeptic(&candidate, &mut candidacy);
1282			Candidates::<T, I>::insert(&candidate, candidacy);
1283			Ok(if punished { Pays::No } else { Pays::Yes }.into())
1284		}
1285
1286		/// Transform an approved candidate into a member. Callable only by the
1287		/// the candidate, and only after the period for voting has ended.
1288		#[pallet::call_index(13)]
1289		#[pallet::weight(T::WeightInfo::claim_membership())]
1290		pub fn claim_membership(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1291			let candidate = ensure_signed(origin)?;
1292			let candidacy =
1293				Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1294			ensure!(candidacy.tally.clear_approval(), Error::<T, I>::NotApproved);
1295			ensure!(!Self::in_progress(candidacy.round), Error::<T, I>::InProgress);
1296			Self::induct_member(candidate, candidacy, 0)?;
1297			Ok(Pays::No.into())
1298		}
1299
1300		/// Transform an approved candidate into a member. Callable only by the Signed origin of the
1301		/// Founder, only after the period for voting has ended and only when the candidate is not
1302		/// clearly rejected.
1303		#[pallet::call_index(14)]
1304		#[pallet::weight(T::WeightInfo::bestow_membership())]
1305		pub fn bestow_membership(
1306			origin: OriginFor<T>,
1307			candidate: T::AccountId,
1308		) -> DispatchResultWithPostInfo {
1309			ensure!(
1310				Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1311				Error::<T, I>::NotFounder
1312			);
1313			let candidacy =
1314				Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1315			ensure!(!candidacy.tally.clear_rejection(), Error::<T, I>::Rejected);
1316			ensure!(!Self::in_progress(candidacy.round), Error::<T, I>::InProgress);
1317			Self::induct_member(candidate, candidacy, 0)?;
1318			Ok(Pays::No.into())
1319		}
1320
1321		/// Remove the candidate's application from the society. Callable only by the Signed origin
1322		/// of the Founder, only after the period for voting has ended, and only when they do not
1323		/// have a clear approval.
1324		///
1325		/// Any bid deposit is lost and voucher is banned.
1326		#[pallet::call_index(15)]
1327		#[pallet::weight(T::WeightInfo::kick_candidate())]
1328		pub fn kick_candidate(
1329			origin: OriginFor<T>,
1330			candidate: T::AccountId,
1331		) -> DispatchResultWithPostInfo {
1332			ensure!(
1333				Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1334				Error::<T, I>::NotFounder
1335			);
1336			let mut candidacy =
1337				Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1338			ensure!(!Self::in_progress(candidacy.round), Error::<T, I>::InProgress);
1339			ensure!(!candidacy.tally.clear_approval(), Error::<T, I>::Approved);
1340			Self::check_skeptic(&candidate, &mut candidacy);
1341			Self::reject_candidate(&candidate, &candidacy.kind);
1342			Candidates::<T, I>::remove(&candidate);
1343			Ok(Pays::No.into())
1344		}
1345
1346		/// Remove the candidate's application from the society. Callable only by the candidate.
1347		///
1348		/// Any bid deposit is lost and voucher is banned.
1349		#[pallet::call_index(16)]
1350		#[pallet::weight(T::WeightInfo::resign_candidacy())]
1351		pub fn resign_candidacy(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1352			let candidate = ensure_signed(origin)?;
1353			let mut candidacy =
1354				Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1355			if !Self::in_progress(candidacy.round) {
1356				Self::check_skeptic(&candidate, &mut candidacy);
1357			}
1358			Self::reject_candidate(&candidate, &candidacy.kind);
1359			Candidates::<T, I>::remove(&candidate);
1360			Ok(Pays::No.into())
1361		}
1362
1363		/// Remove a `candidate`'s failed application from the society. Callable by any
1364		/// signed origin but only at the end of the subsequent round and only for
1365		/// a candidate with more rejections than approvals.
1366		///
1367		/// The bid deposit is lost and the voucher is banned.
1368		#[pallet::call_index(17)]
1369		#[pallet::weight(T::WeightInfo::drop_candidate())]
1370		pub fn drop_candidate(
1371			origin: OriginFor<T>,
1372			candidate: T::AccountId,
1373		) -> DispatchResultWithPostInfo {
1374			ensure_signed(origin)?;
1375			let candidacy =
1376				Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1377			ensure!(candidacy.tally.clear_rejection(), Error::<T, I>::NotRejected);
1378			ensure!(RoundCount::<T, I>::get() > candidacy.round + 1, Error::<T, I>::TooEarly);
1379			Self::reject_candidate(&candidate, &candidacy.kind);
1380			Candidates::<T, I>::remove(&candidate);
1381			Ok(Pays::No.into())
1382		}
1383
1384		/// Remove up to `max` stale votes for the given `candidate`.
1385		///
1386		/// May be called by any Signed origin, but only after the candidate's candidacy is ended.
1387		#[pallet::call_index(18)]
1388		#[pallet::weight(T::WeightInfo::cleanup_candidacy())]
1389		pub fn cleanup_candidacy(
1390			origin: OriginFor<T>,
1391			candidate: T::AccountId,
1392			max: u32,
1393		) -> DispatchResultWithPostInfo {
1394			ensure_signed(origin)?;
1395			ensure!(!Candidates::<T, I>::contains_key(&candidate), Error::<T, I>::InProgress);
1396			let maybe_cursor = VoteClearCursor::<T, I>::get(&candidate);
1397			let r =
1398				Votes::<T, I>::clear_prefix(&candidate, max, maybe_cursor.as_ref().map(|x| &x[..]));
1399			if let Some(cursor) = r.maybe_cursor {
1400				VoteClearCursor::<T, I>::insert(&candidate, BoundedVec::truncate_from(cursor));
1401			}
1402			Ok(if r.loops == 0 { Pays::Yes } else { Pays::No }.into())
1403		}
1404
1405		/// Remove up to `max` stale votes for the defender in the given `challenge_round`.
1406		///
1407		/// May be called by any Signed origin, but only after the challenge round is ended.
1408		#[pallet::call_index(19)]
1409		#[pallet::weight(T::WeightInfo::cleanup_challenge())]
1410		pub fn cleanup_challenge(
1411			origin: OriginFor<T>,
1412			challenge_round: RoundIndex,
1413			max: u32,
1414		) -> DispatchResultWithPostInfo {
1415			ensure_signed(origin)?;
1416			ensure!(
1417				challenge_round < ChallengeRoundCount::<T, I>::get(),
1418				Error::<T, I>::InProgress
1419			);
1420			let _ = DefenderVotes::<T, I>::clear_prefix(challenge_round, max, None);
1421			// clear_prefix() v2 is always returning backend = 0, ignoring it till v3.
1422			// let (_, backend, _, _) = r.deconstruct();
1423			// if backend == 0 { return Err(Error::<T, I>::NoVotes.into()); };
1424			Ok(Pays::No.into())
1425		}
1426
1427		/// Poke the deposit reserved when bidding.
1428		///
1429		/// The dispatch origin for this call must be _Signed_ and must be the bidder.
1430		///
1431		/// The transaction fee is waived if the deposit is changed after poking/reconsideration.
1432		///
1433		/// Emits `DepositPoked` if successful.
1434		#[pallet::call_index(20)]
1435		#[pallet::weight(T::WeightInfo::poke_deposit())]
1436		pub fn poke_deposit(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1437			let who = ensure_signed(origin)?;
1438
1439			// Get current bids and find the bidder's bid
1440			let mut bids = Bids::<T, I>::get();
1441			let bid = bids.iter_mut().find(|bid| bid.who == who).ok_or(Error::<T, I>::NotBidder)?;
1442
1443			// Only handle deposit bids
1444			let old_deposit = match &bid.kind {
1445				BidKind::Deposit(amount) => *amount,
1446				_ => return Err(Error::<T, I>::NoDeposit.into()),
1447			};
1448
1449			let params = Parameters::<T, I>::get().ok_or(Error::<T, I>::NotGroup)?;
1450			let new_deposit = params.candidate_deposit;
1451
1452			if old_deposit == new_deposit {
1453				return Ok(Pays::Yes.into());
1454			}
1455
1456			if new_deposit > old_deposit {
1457				// Need to reserve more
1458				let extra = new_deposit.saturating_sub(old_deposit);
1459				T::Currency::reserve(&who, extra)?;
1460			} else {
1461				// Need to unreserve some
1462				let excess = old_deposit.saturating_sub(new_deposit);
1463				let remaining_unreserved = T::Currency::unreserve(&who, excess);
1464				if !remaining_unreserved.is_zero() {
1465					defensive!(
1466						"Failed to unreserve for full amount for bid (Requested, Actual)",
1467						(excess, excess.saturating_sub(remaining_unreserved))
1468					);
1469				}
1470			}
1471
1472			bid.kind = BidKind::Deposit(new_deposit);
1473			Bids::<T, I>::put(bids);
1474
1475			Self::deposit_event(Event::<T, I>::DepositPoked {
1476				who: who.clone(),
1477				old_deposit,
1478				new_deposit,
1479			});
1480
1481			Ok(Pays::No.into())
1482		}
1483
1484		/// Kick a member from the society. Callable only by the Signed origin of the Founder.
1485		///
1486		/// The member is fully removed (not suspended). All unclaimed payouts are slashed and
1487		/// returned to the society pot.
1488		///
1489		/// Parameters:
1490		/// - `who`: The member to be removed.
1491		#[pallet::call_index(21)]
1492		#[pallet::weight(T::WeightInfo::kick_member())]
1493		pub fn kick_member(origin: OriginFor<T>, who: AccountIdLookupOf<T>) -> DispatchResult {
1494			ensure!(
1495				Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1496				Error::<T, I>::NotFounder
1497			);
1498			let who = T::Lookup::lookup(who)?;
1499
1500			let _ = Self::remove_member(&who)?;
1501
1502			let payout_record = Payouts::<T, I>::take(&who);
1503			let total = payout_record
1504				.payouts
1505				.into_iter()
1506				.fold(Zero::zero(), |acc: BalanceOf<T, I>, x| acc.saturating_add(x.1));
1507			Self::unreserve_payout(total);
1508
1509			Self::deposit_event(Event::<T, I>::MemberKicked { member: who });
1510			Ok(())
1511		}
1512	}
1513}
1514
1515/// Simple ensure origin struct to filter for the founder account.
1516pub struct EnsureFounder<T>(core::marker::PhantomData<T>);
1517impl<T: Config> EnsureOrigin<<T as frame_system::Config>::RuntimeOrigin> for EnsureFounder<T> {
1518	type Success = T::AccountId;
1519	fn try_origin(o: T::RuntimeOrigin) -> Result<Self::Success, T::RuntimeOrigin> {
1520		match (o.as_signer(), Founder::<T>::get()) {
1521			(Some(who), Some(f)) if *who == f => Ok(f),
1522			_ => Err(o),
1523		}
1524	}
1525
1526	#[cfg(feature = "runtime-benchmarks")]
1527	fn try_successful_origin() -> Result<T::RuntimeOrigin, ()> {
1528		let founder = Founder::<T>::get().ok_or(())?;
1529		Ok(T::RuntimeOrigin::from(frame_system::RawOrigin::Signed(founder)))
1530	}
1531}
1532
1533impl_ensure_origin_with_arg_ignoring_arg! {
1534	impl<{ T: Config, A }>
1535		EnsureOriginWithArg<T::RuntimeOrigin, A> for EnsureFounder<T>
1536	{}
1537}
1538
1539#[derive(Debug, PartialEq, Eq)]
1540pub enum Period<BlockNumber> {
1541	Voting { elapsed: BlockNumber, more: BlockNumber },
1542	Claim { elapsed: BlockNumber, more: BlockNumber },
1543	Intake { elapsed: BlockNumber },
1544}
1545
1546impl<T: Config<I>, I: 'static> Pallet<T, I> {
1547	/// Get the period we are currently in.
1548	fn period() -> Period<BlockNumberFor<T, I>> {
1549		let claim_period = T::ClaimPeriod::get();
1550		let voting_period = T::VotingPeriod::get();
1551		let rotation_period = voting_period + claim_period;
1552		let now = T::BlockNumberProvider::current_block_number();
1553		let phase = now % rotation_period;
1554		if now >= Self::next_intake_at() {
1555			Period::Intake { elapsed: now - Self::next_intake_at() }
1556		} else if phase < voting_period {
1557			Period::Voting { elapsed: phase, more: voting_period - phase }
1558		} else {
1559			Period::Claim { elapsed: phase - voting_period, more: rotation_period - phase }
1560		}
1561	}
1562
1563	/// Next intake (candidate/membership) rotation scheduled with [Config::BlockNumberProvider].
1564	///
1565	/// Rounds the previous block number up to the next rotation period (voting + claim periods).
1566	pub fn next_intake_at() -> BlockNumberFor<T, I> {
1567		match NextIntakeAt::<T, I>::get() {
1568			Some(next) => next,
1569			None => {
1570				// executed once.
1571				let now = T::BlockNumberProvider::current_block_number();
1572				let prev_block = now.saturating_sub(BlockNumberFor::<T, I>::one());
1573				let rotation_period = T::VotingPeriod::get().saturating_add(T::ClaimPeriod::get());
1574				let elapsed = prev_block % rotation_period;
1575				let next_intake_at = prev_block + (rotation_period - elapsed);
1576				NextIntakeAt::<T, I>::put(next_intake_at);
1577				next_intake_at
1578			},
1579		}
1580	}
1581
1582	/// Set the next intake (candidate/membership) rotation.
1583	///
1584	/// This supposed to be called once the current intake is executed.
1585	fn set_next_intake_at() {
1586		let prev_next_intake_at = Self::next_intake_at();
1587		let next_intake_at = prev_next_intake_at
1588			.saturating_add(T::VotingPeriod::get().saturating_add(T::ClaimPeriod::get()));
1589		NextIntakeAt::<T, I>::put(next_intake_at);
1590	}
1591
1592	/// Returns the next challenge rotation scheduled with [Config::BlockNumberProvider].
1593	///
1594	/// Rounds the previous block number up to the next multiple of the challenge duration.
1595	pub fn next_challenge_at() -> BlockNumberFor<T, I> {
1596		match NextChallengeAt::<T, I>::get() {
1597			Some(next) => next,
1598			None => {
1599				// executed once.
1600				let now = T::BlockNumberProvider::current_block_number();
1601				let prev_block = now.saturating_sub(BlockNumberFor::<T, I>::one());
1602				let challenge_period = T::ChallengePeriod::get();
1603				let elapsed = prev_block % challenge_period;
1604				let next_challenge_at = prev_block + (challenge_period - elapsed);
1605				NextChallengeAt::<T, I>::put(next_challenge_at);
1606				next_challenge_at
1607			},
1608		}
1609	}
1610
1611	/// Set the next challenge rotation.
1612	///
1613	/// This supposed to be called once the current challenge is executed.
1614	fn set_next_challenge_at() {
1615		let prev_next_challenge_at = Self::next_challenge_at();
1616		let next_challenge_at = prev_next_challenge_at.saturating_add(T::ChallengePeriod::get());
1617		NextChallengeAt::<T, I>::put(next_challenge_at);
1618	}
1619
1620	/// Returns true if the given `target_round` is still in its initial voting phase.
1621	fn in_progress(target_round: RoundIndex) -> bool {
1622		let round = RoundCount::<T, I>::get();
1623		target_round == round && matches!(Self::period(), Period::Voting { .. })
1624	}
1625
1626	/// Returns the new vote.
1627	fn do_vote(maybe_old: Option<Vote>, approve: bool, rank: Rank, tally: &mut Tally) -> Vote {
1628		match maybe_old {
1629			Some(Vote { approve: true, weight }) => tally.approvals.saturating_reduce(weight),
1630			Some(Vote { approve: false, weight }) => tally.rejections.saturating_reduce(weight),
1631			_ => {},
1632		}
1633		let weight_root = rank + 1;
1634		let weight = weight_root * weight_root;
1635		match approve {
1636			true => tally.approvals.saturating_accrue(weight),
1637			false => tally.rejections.saturating_accrue(weight),
1638		}
1639		Vote { approve, weight }
1640	}
1641
1642	/// Returns `true` if a punishment was given.
1643	fn check_skeptic(
1644		candidate: &T::AccountId,
1645		candidacy: &mut Candidacy<T::AccountId, BalanceOf<T, I>>,
1646	) -> bool {
1647		if RoundCount::<T, I>::get() != candidacy.round || candidacy.skeptic_struck {
1648			return false;
1649		}
1650		// We expect the skeptic to have voted.
1651		let skeptic = match Skeptic::<T, I>::get() {
1652			Some(s) => s,
1653			None => return false,
1654		};
1655		let maybe_vote = Votes::<T, I>::get(&candidate, &skeptic);
1656		let approved = candidacy.tally.clear_approval();
1657		let rejected = candidacy.tally.clear_rejection();
1658		match (maybe_vote, approved, rejected) {
1659			(None, _, _) |
1660			(Some(Vote { approve: true, .. }), false, true) |
1661			(Some(Vote { approve: false, .. }), true, false) => {
1662				// Can't do much if the punishment doesn't work out.
1663				if Self::strike_member(&skeptic).is_ok() {
1664					candidacy.skeptic_struck = true;
1665					true
1666				} else {
1667					false
1668				}
1669			},
1670			_ => false,
1671		}
1672	}
1673
1674	/// End the current challenge period and start a new one.
1675	fn rotate_challenge(rng: &mut impl RngCore) {
1676		let mut next_defender = None;
1677		let mut round = ChallengeRoundCount::<T, I>::get();
1678
1679		// End current defender rotation
1680		if let Some((defender, skeptic, tally)) = Defending::<T, I>::get() {
1681			// We require strictly more approvals, since the member should be voting for themselves.
1682			if !tally.more_approvals() {
1683				// Member has failed the challenge: Suspend them. This will fail if they are Head
1684				// or Founder, in which case we ignore.
1685				let _ = Self::suspend_member(&defender);
1686			}
1687
1688			// Check defender skeptic voted and that their vote was with the majority.
1689			let skeptic_vote = DefenderVotes::<T, I>::get(round, &skeptic);
1690			match (skeptic_vote, tally.more_approvals(), tally.more_rejections()) {
1691				(None, _, _) |
1692				(Some(Vote { approve: true, .. }), false, true) |
1693				(Some(Vote { approve: false, .. }), true, false) => {
1694					// Punish skeptic and challenge them next.
1695					let _ = Self::strike_member(&skeptic);
1696					let founder = Founder::<T, I>::get();
1697					let head = Head::<T, I>::get();
1698					if Some(&skeptic) != founder.as_ref() && Some(&skeptic) != head.as_ref() {
1699						next_defender = Some(skeptic);
1700					}
1701				},
1702				_ => {},
1703			}
1704			round.saturating_inc();
1705			ChallengeRoundCount::<T, I>::put(round);
1706		}
1707
1708		// Avoid challenging if there's only two members since we never challenge the Head or
1709		// the Founder.
1710		if MemberCount::<T, I>::get() > 2 {
1711			let defender = next_defender
1712				.or_else(|| Self::pick_defendant(rng))
1713				.expect("exited if members empty; qed");
1714			let skeptic =
1715				Self::pick_member_except(rng, &defender).expect("exited if members empty; qed");
1716			Self::deposit_event(Event::<T, I>::Challenged { member: defender.clone() });
1717			Defending::<T, I>::put((defender, skeptic, Tally::default()));
1718		} else {
1719			Defending::<T, I>::kill();
1720		}
1721	}
1722
1723	/// End the current intake period and begin a new one.
1724	///
1725	/// ---------------------------------------------
1726	///  #10  || #11           _              || #12
1727	///       || Voting        | Claiming     ||
1728	/// ---------------------------------------------
1729	fn rotate_intake(rng: &mut impl RngCore) {
1730		// We assume there's at least one member or this logic won't work.
1731		let member_count = MemberCount::<T, I>::get();
1732		if member_count < 1 {
1733			return;
1734		}
1735		let maybe_head = NextHead::<T, I>::take();
1736		if let Some(head) = maybe_head {
1737			Head::<T, I>::put(&head.who);
1738		}
1739
1740		// Bump the pot by at most `PeriodSpend`, but less if there's not very much left in our
1741		// account.
1742		let mut pot = Pot::<T, I>::get();
1743		let unaccounted = T::Currency::free_balance(&Self::account_id()).saturating_sub(pot);
1744		pot.saturating_accrue(T::PeriodSpend::get().min(unaccounted / 2u8.into()));
1745		Pot::<T, I>::put(&pot);
1746
1747		// Bump round and create the new intake.
1748		let mut round_count = RoundCount::<T, I>::get();
1749		round_count.saturating_inc();
1750		let candidate_count = Self::select_new_candidates(round_count, member_count, pot);
1751		if candidate_count > 0 {
1752			// Select a member at random and make them the skeptic for this round.
1753			let skeptic = Self::pick_member(rng).expect("exited if members empty; qed");
1754			Skeptic::<T, I>::put(skeptic);
1755		}
1756		RoundCount::<T, I>::put(round_count);
1757	}
1758
1759	/// Remove a selection of bidding accounts such that the total bids is no greater than `Pot` and
1760	/// the number of bids would not surpass `MaxMembers` if all were accepted. At most one bid may
1761	/// be zero.
1762	///
1763	/// Candidates are inserted from each bidder.
1764	///
1765	/// The number of candidates inserted are returned.
1766	pub fn select_new_candidates(
1767		round: RoundIndex,
1768		member_count: u32,
1769		pot: BalanceOf<T, I>,
1770	) -> u32 {
1771		// Get the number of left-most bidders whose bids add up to less than `pot`.
1772		let mut bids = Bids::<T, I>::get();
1773		let params = match Parameters::<T, I>::get() {
1774			Some(params) => params,
1775			None => return 0,
1776		};
1777		let max_selections: u32 = params
1778			.max_intake
1779			.min(params.max_members.saturating_sub(member_count))
1780			.min(bids.len() as u32);
1781
1782		let mut selections = 0;
1783		// A running total of the cost to onboard these bids
1784		let mut total_cost: BalanceOf<T, I> = Zero::zero();
1785
1786		bids.retain(|bid| {
1787			// We only accept a zero bid as the first selection.
1788			total_cost.saturating_accrue(bid.value);
1789			let accept = selections < max_selections &&
1790				(!bid.value.is_zero() || selections == 0) &&
1791				total_cost <= pot;
1792			if accept {
1793				let candidacy = Candidacy {
1794					round,
1795					kind: bid.kind.clone(),
1796					bid: bid.value,
1797					tally: Default::default(),
1798					skeptic_struck: false,
1799				};
1800				Candidates::<T, I>::insert(&bid.who, candidacy);
1801				selections.saturating_inc();
1802			}
1803			!accept
1804		});
1805
1806		// No need to reset Bids if we're not taking anything.
1807		Bids::<T, I>::put(&bids);
1808		selections
1809	}
1810
1811	/// Puts a bid into storage ordered by smallest to largest value.
1812	/// Allows a maximum of 1000 bids in queue, removing largest value people first.
1813	fn insert_bid(
1814		bids: &mut BoundedVec<Bid<T::AccountId, BalanceOf<T, I>>, T::MaxBids>,
1815		who: &T::AccountId,
1816		value: BalanceOf<T, I>,
1817		bid_kind: BidKind<T::AccountId, BalanceOf<T, I>>,
1818	) {
1819		let pos = bids.iter().position(|bid| bid.value > value).unwrap_or(bids.len());
1820		let r = bids.force_insert_keep_left(pos, Bid { value, who: who.clone(), kind: bid_kind });
1821		let maybe_discarded = match r {
1822			Ok(x) => x,
1823			Err(x) => Some(x),
1824		};
1825		if let Some(discarded) = maybe_discarded {
1826			Self::clean_bid(&discarded);
1827			Self::deposit_event(Event::<T, I>::AutoUnbid { candidate: discarded.who });
1828		}
1829	}
1830
1831	/// Either unreserve the deposit or free up the vouching member.
1832	///
1833	/// In neither case can we do much if the action isn't completable, but there's
1834	/// no reason that either should fail.
1835	///
1836	/// WARNING: This alters the voucher item of `Members`. You must ensure that you do not
1837	/// accidentally overwrite it with an older value after calling this.
1838	fn clean_bid(bid: &Bid<T::AccountId, BalanceOf<T, I>>) {
1839		match &bid.kind {
1840			BidKind::Deposit(deposit) => {
1841				let err_amount = T::Currency::unreserve(&bid.who, *deposit);
1842				debug_assert!(err_amount.is_zero());
1843			},
1844			BidKind::Vouch(voucher, _) => {
1845				Members::<T, I>::mutate_extant(voucher, |record| record.vouching = None);
1846			},
1847		}
1848	}
1849
1850	/// Either repatriate the deposit into the Society account or ban the vouching member.
1851	///
1852	/// In neither case can we do much if the action isn't completable, but there's
1853	/// no reason that either should fail.
1854	///
1855	/// WARNING: This alters the voucher item of `Members`. You must ensure that you do not
1856	/// accidentally overwrite it with an older value after calling this.
1857	fn reject_candidate(who: &T::AccountId, kind: &BidKind<T::AccountId, BalanceOf<T, I>>) {
1858		match kind {
1859			BidKind::Deposit(deposit) => {
1860				let pot = Self::account_id();
1861				let free = BalanceStatus::Free;
1862				let r = T::Currency::repatriate_reserved(&who, &pot, *deposit, free);
1863				debug_assert!(r.is_ok());
1864			},
1865			BidKind::Vouch(voucher, _) => {
1866				Members::<T, I>::mutate_extant(voucher, |record| {
1867					record.vouching = Some(VouchingStatus::Banned)
1868				});
1869			},
1870		}
1871	}
1872
1873	/// Check a user has a bid.
1874	fn has_bid(bids: &Vec<Bid<T::AccountId, BalanceOf<T, I>>>, who: &T::AccountId) -> bool {
1875		// Bids are ordered by `value`, so we cannot binary search for a user.
1876		bids.iter().any(|bid| bid.who == *who)
1877	}
1878
1879	/// Add a member to the members list. If the user is already a member, do nothing. Can fail when
1880	/// `MaxMember` limit is reached, but in that case it has no side-effects.
1881	///
1882	/// Set the `payouts` for the member. NOTE: This *WILL NOT RESERVE THE FUNDS TO MAKE THE
1883	/// PAYOUT*. Only set this to be non-empty if you already have the funds reserved in the Payouts
1884	/// account.
1885	///
1886	/// NOTE: Generally you should not use this, and instead use `add_new_member` or
1887	/// `reinstate_member`, whose names clearly match the desired intention.
1888	fn insert_member(who: &T::AccountId, rank: Rank) -> DispatchResult {
1889		let params = Parameters::<T, I>::get().ok_or(Error::<T, I>::NotGroup)?;
1890		ensure!(MemberCount::<T, I>::get() < params.max_members, Error::<T, I>::MaxMembers);
1891		let index = MemberCount::<T, I>::mutate(|i| {
1892			i.saturating_accrue(1);
1893			*i - 1
1894		});
1895		let record = MemberRecord { rank, strikes: 0, vouching: None, index };
1896		Members::<T, I>::insert(who, record);
1897		MemberByIndex::<T, I>::insert(index, who);
1898		Ok(())
1899	}
1900
1901	/// Add a member back to the members list, setting their `rank` and `payouts`.
1902	///
1903	/// Can fail when `MaxMember` limit is reached, but in that case it has no side-effects.
1904	///
1905	/// The `payouts` value must be exactly as it was prior to suspension since no further funds
1906	/// will be reserved.
1907	fn reinstate_member(who: &T::AccountId, rank: Rank) -> DispatchResult {
1908		Self::insert_member(who, rank)
1909	}
1910
1911	/// Add a member to the members list. If the user is already a member, do nothing. Can fail when
1912	/// `MaxMember` limit is reached, but in that case it has no side-effects.
1913	fn add_new_member(who: &T::AccountId, rank: Rank) -> DispatchResult {
1914		Self::insert_member(who, rank)
1915	}
1916
1917	/// Induct a new member into the set.
1918	fn induct_member(
1919		candidate: T::AccountId,
1920		mut candidacy: Candidacy<T::AccountId, BalanceOf<T, I>>,
1921		rank: Rank,
1922	) -> DispatchResult {
1923		Self::add_new_member(&candidate, rank)?;
1924		Self::check_skeptic(&candidate, &mut candidacy);
1925
1926		let next_head = NextHead::<T, I>::get()
1927			.filter(|old| {
1928				old.round > candidacy.round ||
1929					old.round == candidacy.round && old.bid < candidacy.bid
1930			})
1931			.unwrap_or_else(|| IntakeRecord {
1932				who: candidate.clone(),
1933				bid: candidacy.bid,
1934				round: candidacy.round,
1935			});
1936		NextHead::<T, I>::put(next_head);
1937
1938		let now = T::BlockNumberProvider::current_block_number();
1939		let maturity = now + Self::lock_duration(MemberCount::<T, I>::get());
1940		Self::reward_bidder(&candidate, candidacy.bid, candidacy.kind, maturity);
1941
1942		Candidates::<T, I>::remove(&candidate);
1943		Ok(())
1944	}
1945
1946	fn strike_member(who: &T::AccountId) -> DispatchResult {
1947		let mut record = Members::<T, I>::get(who).ok_or(Error::<T, I>::NotMember)?;
1948		record.strikes.saturating_inc();
1949		Members::<T, I>::insert(who, &record);
1950		// ^^^ Keep the member record mutation self-contained as we might be suspending them later
1951		// in this function.
1952
1953		if record.strikes >= T::GraceStrikes::get() {
1954			// Too many strikes: slash the payout in half.
1955			let total_payout = Payouts::<T, I>::get(who)
1956				.payouts
1957				.iter()
1958				.fold(BalanceOf::<T, I>::zero(), |acc, x| acc.saturating_add(x.1));
1959			Self::slash_payout(who, total_payout / 2u32.into());
1960		}
1961
1962		let params = Parameters::<T, I>::get().ok_or(Error::<T, I>::NotGroup)?;
1963		if record.strikes >= params.max_strikes {
1964			// Way too many strikes: suspend.
1965			let _ = Self::suspend_member(who);
1966		}
1967		Ok(())
1968	}
1969
1970	/// Remove a member from the members list and return the candidacy.
1971	///
1972	/// If the member was vouching, then this will be reset. Any bidders that the member was
1973	/// vouching for will be cancelled unless they are already selected as candidates (in which case
1974	/// they will be able to stand).
1975	///
1976	/// If the member has existing payouts, they will be retained in the resultant `MemberRecord`
1977	/// and the funds will remain reserved.
1978	///
1979	/// The Head and the Founder may never be removed.
1980	pub fn remove_member(m: &T::AccountId) -> Result<MemberRecord, DispatchError> {
1981		ensure!(Head::<T, I>::get().as_ref() != Some(m), Error::<T, I>::Head);
1982		ensure!(Founder::<T, I>::get().as_ref() != Some(m), Error::<T, I>::Founder);
1983		if let Some(mut record) = Members::<T, I>::get(m) {
1984			let index = record.index;
1985			let last_index = MemberCount::<T, I>::mutate(|i| {
1986				i.saturating_reduce(1);
1987				*i
1988			});
1989			if index != last_index {
1990				// Move the member with the last index down to the index of the member to be
1991				// removed.
1992				if let Some(other) = MemberByIndex::<T, I>::get(last_index) {
1993					MemberByIndex::<T, I>::insert(index, &other);
1994					Members::<T, I>::mutate(other, |m_r| {
1995						if let Some(r) = m_r {
1996							r.index = index
1997						}
1998					});
1999				} else {
2000					debug_assert!(false, "ERROR: No member at the last index position?");
2001				}
2002			}
2003
2004			MemberByIndex::<T, I>::remove(last_index);
2005			Members::<T, I>::remove(m);
2006			// Remove their vouching status, potentially unbanning them in the future.
2007			if record.vouching.take() == Some(VouchingStatus::Vouching) {
2008				// Try to remove their bid if they are vouching.
2009				// If their vouch is already a candidate, do nothing.
2010				Bids::<T, I>::mutate(|bids|
2011					// Try to find the matching bid
2012					if let Some(pos) = bids.iter().position(|b| b.kind.is_vouch(&m)) {
2013						// Remove the bid, and emit an event
2014						let vouched = bids.remove(pos).who;
2015						Self::deposit_event(Event::<T, I>::Unvouch { candidate: vouched });
2016					}
2017				);
2018			}
2019			Ok(record)
2020		} else {
2021			Err(Error::<T, I>::NotMember.into())
2022		}
2023	}
2024
2025	/// Remove a member from the members set and add them to the suspended members.
2026	///
2027	/// If the member was vouching, then this will be reset. Any bidders that the member was
2028	/// vouching for will be cancelled unless they are already selected as candidates (in which case
2029	/// they will be able to stand).
2030	fn suspend_member(who: &T::AccountId) -> DispatchResult {
2031		let record = Self::remove_member(&who)?;
2032		SuspendedMembers::<T, I>::insert(who, record);
2033		Self::deposit_event(Event::<T, I>::MemberSuspended { member: who.clone() });
2034		Ok(())
2035	}
2036
2037	/// Select a member at random, given the RNG `rng`.
2038	///
2039	/// If no members exist (or the state is inconsistent), then `None` may be returned.
2040	fn pick_member(rng: &mut impl RngCore) -> Option<T::AccountId> {
2041		let member_count = MemberCount::<T, I>::get();
2042		if member_count == 0 {
2043			return None;
2044		}
2045		let random_index = rng.next_u32() % member_count;
2046		MemberByIndex::<T, I>::get(random_index)
2047	}
2048
2049	/// Select a member at random except `exception`, given the RNG `rng`.
2050	///
2051	/// If `exception` is the only member (or the state is inconsistent), then `None` may be
2052	/// returned.
2053	fn pick_member_except(
2054		rng: &mut impl RngCore,
2055		exception: &T::AccountId,
2056	) -> Option<T::AccountId> {
2057		let member_count = MemberCount::<T, I>::get();
2058		if member_count <= 1 {
2059			return None;
2060		}
2061		let random_index = rng.next_u32() % (member_count - 1);
2062		let pick = MemberByIndex::<T, I>::get(random_index);
2063		if pick.as_ref() == Some(exception) {
2064			MemberByIndex::<T, I>::get(member_count - 1)
2065		} else {
2066			pick
2067		}
2068	}
2069
2070	/// Select a member who is able to defend at random, given the RNG `rng`.
2071	///
2072	/// If only the Founder and Head members exist (or the state is inconsistent), then `None`
2073	/// may be returned.
2074	fn pick_defendant(rng: &mut impl RngCore) -> Option<T::AccountId> {
2075		let member_count = MemberCount::<T, I>::get();
2076		if member_count <= 2 {
2077			return None;
2078		}
2079		// Founder is always at index 0, so we should never pick that one.
2080		// Head will typically but not always be the highest index. We assume it is for now and
2081		// fix it up later if not.
2082		let head = Head::<T, I>::get();
2083		let pickable_count = member_count - if head.is_some() { 2 } else { 1 };
2084		let random_index = rng.next_u32() % pickable_count + 1;
2085		let pick = MemberByIndex::<T, I>::get(random_index);
2086		if pick == head && head.is_some() {
2087			// Turns out that head was not the last index since we managed to pick it. Exchange our
2088			// pick for the last index.
2089			MemberByIndex::<T, I>::get(member_count - 1)
2090		} else {
2091			pick
2092		}
2093	}
2094
2095	/// Pay an accepted candidate their bid value.
2096	fn reward_bidder(
2097		candidate: &T::AccountId,
2098		value: BalanceOf<T, I>,
2099		kind: BidKind<T::AccountId, BalanceOf<T, I>>,
2100		maturity: BlockNumberFor<T, I>,
2101	) {
2102		let value = match kind {
2103			BidKind::Deposit(deposit) => {
2104				// In the case that a normal deposit bid is accepted we unreserve
2105				// the deposit.
2106				let err_amount = T::Currency::unreserve(candidate, deposit);
2107				debug_assert!(err_amount.is_zero());
2108				value
2109			},
2110			BidKind::Vouch(voucher, tip) => {
2111				// Check that the voucher is still vouching, else some other logic may have removed
2112				// their status.
2113				if let Some(mut record) = Members::<T, I>::get(&voucher) {
2114					if let Some(VouchingStatus::Vouching) = record.vouching {
2115						// In the case that a vouched-for bid is accepted we unset the
2116						// vouching status and transfer the tip over to the voucher.
2117						record.vouching = None;
2118						Self::bump_payout(&voucher, maturity, tip.min(value));
2119						Members::<T, I>::insert(&voucher, record);
2120						value.saturating_sub(tip)
2121					} else {
2122						value
2123					}
2124				} else {
2125					value
2126				}
2127			},
2128		};
2129
2130		Self::bump_payout(candidate, maturity, value);
2131	}
2132
2133	/// Bump the payout amount of `who`, to be unlocked at the given block number.
2134	///
2135	/// It is the caller's duty to ensure that `who` is already a member. This does nothing if `who`
2136	/// is not a member, if `value` is zero or if the payment cannot be recorded because the member
2137	/// already has too many pending payouts.
2138	fn bump_payout(who: &T::AccountId, when: BlockNumberFor<T, I>, value: BalanceOf<T, I>) {
2139		if value.is_zero() {
2140			return;
2141		}
2142		if let Some(MemberRecord { rank: 0, .. }) = Members::<T, I>::get(who) {
2143			let recorded = Payouts::<T, I>::mutate(who, |record| {
2144				// Members of rank 1 never get payouts.
2145				match record.payouts.binary_search_by_key(&when, |x| x.0) {
2146					Ok(index) => {
2147						record.payouts[index].1.saturating_accrue(value);
2148						true
2149					},
2150					// A member with too many pending payouts forfeits the payment.
2151					Err(index) => record.payouts.try_insert(index, (when, value)).is_ok(),
2152				}
2153			});
2154			// Only reserve funds for payments which have been recorded.
2155			if recorded {
2156				Self::reserve_payout(value);
2157			}
2158		}
2159	}
2160
2161	/// Attempt to slash the payout of some member, returning the funds reserved for the deducted
2162	/// amount to the pot. Return the total amount that was deducted.
2163	fn slash_payout(who: &T::AccountId, value: BalanceOf<T, I>) -> BalanceOf<T, I> {
2164		let mut record = Payouts::<T, I>::get(who);
2165		let mut rest = value;
2166		while !record.payouts.is_empty() {
2167			if let Some(new_rest) = rest.checked_sub(&record.payouts[0].1) {
2168				// not yet totally slashed after this one; drop it completely.
2169				rest = new_rest;
2170				record.payouts.remove(0);
2171			} else {
2172				// whole slash is accounted for.
2173				record.payouts[0].1.saturating_reduce(rest);
2174				rest = Zero::zero();
2175				break;
2176			}
2177		}
2178		Payouts::<T, I>::insert(who, record);
2179		let slashed = value - rest;
2180		Self::unreserve_payout(slashed);
2181		slashed
2182	}
2183
2184	/// Transfer some `amount` from the main account into the payouts account and reduce the Pot
2185	/// by this amount.
2186	fn reserve_payout(amount: BalanceOf<T, I>) {
2187		// Transfer payout from the Pot into the payouts account.
2188		Pot::<T, I>::mutate(|pot| pot.saturating_reduce(amount));
2189
2190		// this should never fail since we ensure we can afford the payouts in a previous
2191		// block, but there's not much we can do to recover if it fails anyway.
2192		let res = T::Currency::transfer(&Self::account_id(), &Self::payouts(), amount, AllowDeath);
2193		debug_assert!(res.is_ok());
2194	}
2195
2196	/// Transfer some `amount` from the main account into the payouts account and increase the Pot
2197	/// by this amount.
2198	fn unreserve_payout(amount: BalanceOf<T, I>) {
2199		// Transfer payout from the Pot into the payouts account.
2200		Pot::<T, I>::mutate(|pot| pot.saturating_accrue(amount));
2201
2202		// this should never fail since we ensure we can afford the payouts in a previous
2203		// block, but there's not much we can do to recover if it fails anyway.
2204		let res = T::Currency::transfer(&Self::payouts(), &Self::account_id(), amount, AllowDeath);
2205		debug_assert!(res.is_ok());
2206	}
2207
2208	/// The account ID of the treasury pot.
2209	///
2210	/// This actually does computation. If you need to keep using it, then make sure you cache the
2211	/// value and only call this once.
2212	pub fn account_id() -> T::AccountId {
2213		T::PalletId::get().into_account_truncating()
2214	}
2215
2216	/// The account ID of the payouts pot. This is where payouts are made from.
2217	///
2218	/// This actually does computation. If you need to keep using it, then make sure you cache the
2219	/// value and only call this once.
2220	pub fn payouts() -> T::AccountId {
2221		T::PalletId::get().into_sub_account_truncating(b"payouts")
2222	}
2223
2224	/// The total of all pending payouts recorded in [`Payouts`].
2225	pub(crate) fn pending_payouts_total() -> BalanceOf<T, I> {
2226		Payouts::<T, I>::iter_values()
2227			.flat_map(|record| record.payouts.into_iter())
2228			.fold(Zero::zero(), |acc: BalanceOf<T, I>, x| acc.saturating_add(x.1))
2229	}
2230
2231	/// Ensure the correctness of the state of this pallet.
2232	///
2233	/// The balance of the payouts account must equal the total of all pending payouts recorded in
2234	/// `Payouts`, as funds are moved into the account when a payout is recorded and out of it when
2235	/// a payout is claimed or discarded.
2236	#[cfg(any(feature = "try-runtime", test))]
2237	pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
2238		frame_support::ensure!(
2239			T::Currency::free_balance(&Self::payouts()) == Self::pending_payouts_total(),
2240			"payouts account balance must equal the total of pending payouts",
2241		);
2242		Ok(())
2243	}
2244
2245	/// Return the duration of the lock, in blocks, with the given number of members.
2246	///
2247	/// This is a rather opaque calculation based on the formula here:
2248	/// https://www.desmos.com/calculator/9itkal1tce
2249	fn lock_duration(x: u32) -> BlockNumberFor<T, I> {
2250		let lock_pc = 100 - 50_000 / (x + 500);
2251		Percent::from_percent(lock_pc as u8) * T::MaxLockDuration::get()
2252	}
2253}
2254
2255impl<T: Config<I>, I: 'static> OnUnbalanced<NegativeImbalanceOf<T, I>> for Pallet<T, I> {
2256	fn on_nonzero_unbalanced(amount: NegativeImbalanceOf<T, I>) {
2257		let numeric_amount = amount.peek();
2258
2259		// Must resolve into existing but better to be safe.
2260		let _ = T::Currency::resolve_creating(&Self::account_id(), amount);
2261
2262		Self::deposit_event(Event::<T, I>::Deposit { value: numeric_amount });
2263	}
2264}