1#![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#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
314pub enum Judgement {
315 Rebid,
318 Reject,
320 Approve,
322}
323
324#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, Default, TypeInfo, MaxEncodedLen)]
326pub struct Payout<Balance, BlockNumber> {
327 pub value: Balance,
329 pub begin: BlockNumber,
331 pub duration: BlockNumber,
333 pub paid: Balance,
335}
336
337#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
339pub enum VouchingStatus {
340 Vouching,
342 Banned,
344}
345
346pub type StrikeCount = u32;
348
349#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
351pub struct Bid<AccountId, Balance> {
352 pub who: AccountId,
354 pub kind: BidKind<AccountId, Balance>,
356 pub value: Balance,
358}
359
360pub type RoundIndex = u32;
362
363pub type Rank = u32;
365
366pub type VoteCount = u32;
368
369#[derive(Default, Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
371pub struct Tally {
372 pub approvals: VoteCount,
374 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#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
398pub struct Candidacy<AccountId, Balance> {
399 pub round: RoundIndex,
401 pub kind: BidKind<AccountId, Balance>,
403 pub bid: Balance,
405 pub tally: Tally,
407 pub skeptic_struck: bool,
409}
410
411#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)]
413pub enum BidKind<AccountId, Balance> {
414 Deposit(Balance),
416 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#[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#[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#[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 #[allow(deprecated)]
497 type RuntimeEvent: From<Event<Self, I>>
498 + IsType<<Self as frame_system::Config>::RuntimeEvent>;
499
500 #[pallet::constant]
502 type PalletId: Get<PalletId>;
503
504 type Currency: ReservableCurrency<Self::AccountId>;
506
507 type Randomness: Randomness<Self::Hash, BlockNumberFor<Self, I>>;
509
510 #[pallet::constant]
512 type GraceStrikes: Get<u32>;
513
514 #[pallet::constant]
516 type PeriodSpend: Get<BalanceOf<Self, I>>;
517
518 #[pallet::constant]
522 type VotingPeriod: Get<BlockNumberFor<Self, I>>;
523
524 #[pallet::constant]
527 type ClaimPeriod: Get<BlockNumberFor<Self, I>>;
528
529 #[pallet::constant]
531 type MaxLockDuration: Get<BlockNumberFor<Self, I>>;
532
533 type FounderSetOrigin: EnsureOrigin<Self::RuntimeOrigin>;
535
536 #[pallet::constant]
538 type ChallengePeriod: Get<BlockNumberFor<Self, I>>;
539
540 #[pallet::constant]
542 type MaxPayouts: Get<u32>;
543
544 #[pallet::constant]
546 type MaxBids: Get<u32>;
547
548 type WeightInfo: WeightInfo;
550 type BlockNumberProvider: BlockNumberProvider;
552 }
553
554 #[pallet::error]
555 pub enum Error<T, I = ()> {
556 NotMember,
558 AlreadyMember,
560 Suspended,
562 NotSuspended,
564 NoPayout,
566 AlreadyFounded,
568 InsufficientPot,
570 AlreadyVouching,
572 NotVouchingOnBidder,
574 Head,
576 Founder,
578 AlreadyBid,
580 AlreadyCandidate,
582 NotCandidate,
584 MaxMembers,
586 NotFounder,
588 NotHead,
590 NotApproved,
592 NotRejected,
594 Approved,
596 Rejected,
598 InProgress,
600 TooEarly,
602 Voted,
604 Expired,
606 NotBidder,
608 NoDefender,
610 NotGroup,
612 AlreadyElevated,
614 AlreadyPunished,
616 InsufficientFunds,
618 NoVotes,
620 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 Founded { founder: T::AccountId },
629 Bid { candidate_id: T::AccountId, offer: BalanceOf<T, I> },
632 Vouch { candidate_id: T::AccountId, offer: BalanceOf<T, I>, vouching: T::AccountId },
635 AutoUnbid { candidate: T::AccountId },
637 Unbid { candidate: T::AccountId },
639 Unvouch { candidate: T::AccountId },
641 Inducted { primary: T::AccountId, candidates: Vec<T::AccountId> },
644 SuspendedMemberJudgement { who: T::AccountId, judged: bool },
646 CandidateSuspended { candidate: T::AccountId },
648 MemberSuspended { member: T::AccountId },
650 Challenged { member: T::AccountId },
652 Vote { candidate: T::AccountId, voter: T::AccountId, vote: bool },
654 DefenderVote { voter: T::AccountId, vote: bool },
656 NewParams { params: GroupParamsFor<T, I> },
658 Unfounded { founder: T::AccountId },
660 Deposit { value: BalanceOf<T, I> },
662 Elevated { member: T::AccountId, rank: Rank },
664 DepositPoked {
666 who: T::AccountId,
667 old_deposit: BalanceOf<T, I>,
668 new_deposit: BalanceOf<T, I>,
669 },
670 MemberKicked { member: T::AccountId },
672 }
673
674 #[pallet::storage]
676 pub type Parameters<T: Config<I>, I: 'static = ()> =
677 StorageValue<_, GroupParamsFor<T, I>, OptionQuery>;
678
679 #[pallet::storage]
681 pub type Pot<T: Config<I>, I: 'static = ()> = StorageValue<_, BalanceOf<T, I>, ValueQuery>;
682
683 #[pallet::storage]
685 pub type Founder<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId>;
686
687 #[pallet::storage]
689 pub type Head<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId>;
690
691 #[pallet::storage]
694 pub type Rules<T: Config<I>, I: 'static = ()> = StorageValue<_, T::Hash>;
695
696 #[pallet::storage]
698 pub type Members<T: Config<I>, I: 'static = ()> =
699 StorageMap<_, Twox64Concat, T::AccountId, MemberRecord, OptionQuery>;
700
701 #[pallet::storage]
703 pub type Payouts<T: Config<I>, I: 'static = ()> =
704 StorageMap<_, Twox64Concat, T::AccountId, PayoutRecordFor<T, I>, ValueQuery>;
705
706 #[pallet::storage]
708 pub type MemberCount<T: Config<I>, I: 'static = ()> = StorageValue<_, u32, ValueQuery>;
709
710 #[pallet::storage]
713 pub type MemberByIndex<T: Config<I>, I: 'static = ()> =
714 StorageMap<_, Twox64Concat, u32, T::AccountId, OptionQuery>;
715
716 #[pallet::storage]
718 pub type SuspendedMembers<T: Config<I>, I: 'static = ()> =
719 StorageMap<_, Twox64Concat, T::AccountId, MemberRecord, OptionQuery>;
720
721 #[pallet::storage]
723 pub type RoundCount<T: Config<I>, I: 'static = ()> = StorageValue<_, RoundIndex, ValueQuery>;
724
725 #[pallet::storage]
727 pub type Bids<T: Config<I>, I: 'static = ()> =
728 StorageValue<_, BoundedVec<Bid<T::AccountId, BalanceOf<T, I>>, T::MaxBids>, ValueQuery>;
729
730 #[pallet::storage]
731 pub type Candidates<T: Config<I>, I: 'static = ()> = StorageMap<
732 _,
733 Blake2_128Concat,
734 T::AccountId,
735 Candidacy<T::AccountId, BalanceOf<T, I>>,
736 OptionQuery,
737 >;
738
739 #[pallet::storage]
741 pub type Skeptic<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId, OptionQuery>;
742
743 #[pallet::storage]
745 pub type Votes<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
746 _,
747 Twox64Concat,
748 T::AccountId,
749 Twox64Concat,
750 T::AccountId,
751 Vote,
752 OptionQuery,
753 >;
754
755 #[pallet::storage]
757 pub type VoteClearCursor<T: Config<I>, I: 'static = ()> =
758 StorageMap<_, Twox64Concat, T::AccountId, BoundedVec<u8, KeyLenOf<Votes<T, I>>>>;
759
760 #[pallet::storage]
764 pub type NextHead<T: Config<I>, I: 'static = ()> =
765 StorageValue<_, IntakeRecordFor<T, I>, OptionQuery>;
766
767 #[pallet::storage]
769 pub type ChallengeRoundCount<T: Config<I>, I: 'static = ()> =
770 StorageValue<_, RoundIndex, ValueQuery>;
771
772 #[pallet::storage]
774 pub type Defending<T: Config<I>, I: 'static = ()> =
775 StorageValue<_, (T::AccountId, T::AccountId, Tally)>;
776
777 #[pallet::storage]
779 pub type DefenderVotes<T: Config<I>, I: 'static = ()> =
780 StorageDoubleMap<_, Twox64Concat, RoundIndex, Twox64Concat, T::AccountId, Vote>;
781
782 #[pallet::storage]
784 pub type NextIntakeAt<T: Config<I>, I: 'static = ()> = StorageValue<_, BlockNumberFor<T, I>>;
785
786 #[pallet::storage]
788 pub type NextChallengeAt<T: Config<I>, I: 'static = ()> = StorageValue<_, BlockNumberFor<T, I>>;
789
790 #[pallet::hooks]
791 impl<T: Config<I>, I: 'static> Hooks<SystemBlockNumberFor<T>> for Pallet<T, I> {
792 fn on_initialize(_n: SystemBlockNumberFor<T>) -> Weight {
793 let mut weight = Weight::zero();
794 let weights = T::BlockWeights::get();
795 let now = T::BlockNumberProvider::current_block_number();
796
797 let phrase = b"society_rotation";
798 let (seed, _) = T::Randomness::random(phrase);
802 let seed = <[u8; 32]>::decode(&mut TrailingZeroInput::new(seed.as_ref()))
804 .expect("input is padded with zeroes; qed");
805 let mut rng = ChaChaRng::from_seed(seed);
806
807 let is_intake_moment = match Self::period() {
809 Period::Intake { .. } => true,
810 _ => false,
811 };
812 if is_intake_moment {
813 Self::rotate_intake(&mut rng);
814 weight.saturating_accrue(weights.max_block / 20);
815 Self::set_next_intake_at();
816 }
817
818 if now >= Self::next_challenge_at() {
820 Self::rotate_challenge(&mut rng);
821 weight.saturating_accrue(weights.max_block / 20);
822 Self::set_next_challenge_at();
823 }
824
825 weight
826 }
827
828 #[cfg(feature = "try-runtime")]
829 fn try_state(_: SystemBlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
830 Self::do_try_state()
831 }
832 }
833
834 #[pallet::genesis_config]
835 #[derive(frame_support::DefaultNoBound)]
836 pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
837 pub pot: BalanceOf<T, I>,
838 }
839
840 #[pallet::genesis_build]
841 impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
842 fn build(&self) {
843 Pot::<T, I>::put(self.pot);
844 }
845 }
846
847 #[pallet::call]
848 impl<T: Config<I>, I: 'static> Pallet<T, I> {
849 #[pallet::call_index(0)]
859 #[pallet::weight(T::WeightInfo::bid())]
860 pub fn bid(origin: OriginFor<T>, value: BalanceOf<T, I>) -> DispatchResult {
861 let who = ensure_signed(origin)?;
862
863 let mut bids = Bids::<T, I>::get();
864 ensure!(!Self::has_bid(&bids, &who), Error::<T, I>::AlreadyBid);
865 ensure!(!Candidates::<T, I>::contains_key(&who), Error::<T, I>::AlreadyCandidate);
866 ensure!(!Members::<T, I>::contains_key(&who), Error::<T, I>::AlreadyMember);
867 ensure!(!SuspendedMembers::<T, I>::contains_key(&who), Error::<T, I>::Suspended);
868
869 let params = Parameters::<T, I>::get().ok_or(Error::<T, I>::NotGroup)?;
870 let deposit = params.candidate_deposit;
871 T::Currency::reserve(&who, deposit)?;
873 Self::insert_bid(&mut bids, &who, value, BidKind::Deposit(deposit));
874
875 Bids::<T, I>::put(bids);
876 Self::deposit_event(Event::<T, I>::Bid { candidate_id: who, offer: value });
877 Ok(())
878 }
879
880 #[pallet::call_index(1)]
888 #[pallet::weight(T::WeightInfo::unbid())]
889 pub fn unbid(origin: OriginFor<T>) -> DispatchResult {
890 let who = ensure_signed(origin)?;
891
892 let mut bids = Bids::<T, I>::get();
893 let pos = bids.iter().position(|bid| bid.who == who).ok_or(Error::<T, I>::NotBidder)?;
894 Self::clean_bid(&bids.remove(pos));
895 Bids::<T, I>::put(bids);
896 Self::deposit_event(Event::<T, I>::Unbid { candidate: who });
897 Ok(())
898 }
899
900 #[pallet::call_index(2)]
918 #[pallet::weight(T::WeightInfo::vouch())]
919 pub fn vouch(
920 origin: OriginFor<T>,
921 who: AccountIdLookupOf<T>,
922 value: BalanceOf<T, I>,
923 tip: BalanceOf<T, I>,
924 ) -> DispatchResult {
925 let voucher = ensure_signed(origin)?;
926 let who = T::Lookup::lookup(who)?;
927
928 let mut bids = Bids::<T, I>::get();
930 ensure!(!Self::has_bid(&bids, &who), Error::<T, I>::AlreadyBid);
931
932 ensure!(!Candidates::<T, I>::contains_key(&who), Error::<T, I>::AlreadyCandidate);
934 ensure!(!Members::<T, I>::contains_key(&who), Error::<T, I>::AlreadyMember);
935 ensure!(!SuspendedMembers::<T, I>::contains_key(&who), Error::<T, I>::Suspended);
936
937 let mut record = Members::<T, I>::get(&voucher).ok_or(Error::<T, I>::NotMember)?;
939 ensure!(record.vouching.is_none(), Error::<T, I>::AlreadyVouching);
940
941 record.vouching = Some(VouchingStatus::Vouching);
943 Self::insert_bid(&mut bids, &who, value, BidKind::Vouch(voucher.clone(), tip));
945
946 Members::<T, I>::insert(&voucher, &record);
948 Bids::<T, I>::put(bids);
949 Self::deposit_event(Event::<T, I>::Vouch {
950 candidate_id: who,
951 offer: value,
952 vouching: voucher,
953 });
954 Ok(())
955 }
956
957 #[pallet::call_index(3)]
965 #[pallet::weight(T::WeightInfo::unvouch())]
966 pub fn unvouch(origin: OriginFor<T>) -> DispatchResult {
967 let voucher = ensure_signed(origin)?;
968
969 let mut bids = Bids::<T, I>::get();
970 let pos = bids
971 .iter()
972 .position(|bid| bid.kind.is_vouch(&voucher))
973 .ok_or(Error::<T, I>::NotVouchingOnBidder)?;
974 let bid = bids.remove(pos);
975 Self::clean_bid(&bid);
976
977 Bids::<T, I>::put(bids);
978 Self::deposit_event(Event::<T, I>::Unvouch { candidate: bid.who });
979 Ok(())
980 }
981
982 #[pallet::call_index(4)]
991 #[pallet::weight(T::WeightInfo::vote())]
992 pub fn vote(
993 origin: OriginFor<T>,
994 candidate: AccountIdLookupOf<T>,
995 approve: bool,
996 ) -> DispatchResultWithPostInfo {
997 let voter = ensure_signed(origin)?;
998 let candidate = T::Lookup::lookup(candidate)?;
999
1000 let mut candidacy =
1001 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1002 let record = Members::<T, I>::get(&voter).ok_or(Error::<T, I>::NotMember)?;
1003
1004 let first_time = Votes::<T, I>::mutate(&candidate, &voter, |v| {
1005 let first_time = v.is_none();
1006 *v = Some(Self::do_vote(*v, approve, record.rank, &mut candidacy.tally));
1007 first_time
1008 });
1009
1010 Candidates::<T, I>::insert(&candidate, &candidacy);
1011 Self::deposit_event(Event::<T, I>::Vote { candidate, voter, vote: approve });
1012 Ok(if first_time { Pays::No } else { Pays::Yes }.into())
1013 }
1014
1015 #[pallet::call_index(5)]
1023 #[pallet::weight(T::WeightInfo::defender_vote())]
1024 pub fn defender_vote(origin: OriginFor<T>, approve: bool) -> DispatchResultWithPostInfo {
1025 let voter = ensure_signed(origin)?;
1026
1027 let mut defending = Defending::<T, I>::get().ok_or(Error::<T, I>::NoDefender)?;
1028 let record = Members::<T, I>::get(&voter).ok_or(Error::<T, I>::NotMember)?;
1029
1030 let round = ChallengeRoundCount::<T, I>::get();
1031 let first_time = DefenderVotes::<T, I>::mutate(round, &voter, |v| {
1032 let first_time = v.is_none();
1033 *v = Some(Self::do_vote(*v, approve, record.rank, &mut defending.2));
1034 first_time
1035 });
1036
1037 Defending::<T, I>::put(defending);
1038 Self::deposit_event(Event::<T, I>::DefenderVote { voter, vote: approve });
1039 Ok(if first_time { Pays::No } else { Pays::Yes }.into())
1040 }
1041
1042 #[pallet::call_index(6)]
1053 #[pallet::weight(T::WeightInfo::payout())]
1054 pub fn payout(origin: OriginFor<T>) -> DispatchResult {
1055 let who = ensure_signed(origin)?;
1056 ensure!(
1057 Members::<T, I>::get(&who).ok_or(Error::<T, I>::NotMember)?.rank == 0,
1058 Error::<T, I>::NoPayout
1059 );
1060 let mut record = Payouts::<T, I>::get(&who);
1061 let block_number = T::BlockNumberProvider::current_block_number();
1062 if let Some((when, amount)) = record.payouts.first() {
1063 if when <= &block_number {
1064 record.paid = record.paid.checked_add(amount).ok_or(Overflow)?;
1065 T::Currency::transfer(&Self::payouts(), &who, *amount, AllowDeath)?;
1066 record.payouts.remove(0);
1067 Payouts::<T, I>::insert(&who, record);
1068 return Ok(());
1069 }
1070 }
1071 Err(Error::<T, I>::NoPayout)?
1072 }
1073
1074 #[pallet::call_index(7)]
1079 #[pallet::weight(T::WeightInfo::waive_repay())]
1080 pub fn waive_repay(origin: OriginFor<T>, amount: BalanceOf<T, I>) -> DispatchResult {
1081 let who = ensure_signed(origin)?;
1082 let mut record = Members::<T, I>::get(&who).ok_or(Error::<T, I>::NotMember)?;
1083 let mut payout_record = Payouts::<T, I>::get(&who);
1084 ensure!(record.rank == 0, Error::<T, I>::AlreadyElevated);
1085 ensure!(amount >= payout_record.paid, Error::<T, I>::InsufficientFunds);
1086
1087 T::Currency::transfer(&who, &Self::account_id(), payout_record.paid, AllowDeath)?;
1088 let total = payout_record
1089 .payouts
1090 .drain(..)
1091 .fold(Zero::zero(), |acc: BalanceOf<T, I>, x| acc.saturating_add(x.1));
1092 Self::unreserve_payout(total);
1093 payout_record.paid = Zero::zero();
1094 record.rank = 1;
1095 Members::<T, I>::insert(&who, record);
1096 Payouts::<T, I>::insert(&who, payout_record);
1097 Self::deposit_event(Event::<T, I>::Elevated { member: who, rank: 1 });
1098
1099 Ok(())
1100 }
1101
1102 #[pallet::call_index(8)]
1120 #[pallet::weight(T::WeightInfo::found_society())]
1121 pub fn found_society(
1122 origin: OriginFor<T>,
1123 founder: AccountIdLookupOf<T>,
1124 max_members: u32,
1125 max_intake: u32,
1126 max_strikes: u32,
1127 candidate_deposit: BalanceOf<T, I>,
1128 rules: Vec<u8>,
1129 ) -> DispatchResult {
1130 T::FounderSetOrigin::ensure_origin(origin)?;
1131 let founder = T::Lookup::lookup(founder)?;
1132 ensure!(!Head::<T, I>::exists(), Error::<T, I>::AlreadyFounded);
1133 ensure!(max_members > 1, Error::<T, I>::MaxMembers);
1134 let params = GroupParams { max_members, max_intake, max_strikes, candidate_deposit };
1136 Parameters::<T, I>::put(params);
1137 Self::insert_member(&founder, 1)?;
1138 Head::<T, I>::put(&founder);
1139 Founder::<T, I>::put(&founder);
1140 Rules::<T, I>::put(T::Hashing::hash(&rules));
1141 Self::deposit_event(Event::<T, I>::Founded { founder });
1142 Ok(())
1143 }
1144
1145 #[pallet::call_index(9)]
1151 #[pallet::weight(T::WeightInfo::dissolve())]
1152 pub fn dissolve(origin: OriginFor<T>) -> DispatchResult {
1153 let founder = ensure_signed(origin)?;
1154 ensure!(Founder::<T, I>::get().as_ref() == Some(&founder), Error::<T, I>::NotFounder);
1155 ensure!(MemberCount::<T, I>::get() == 1, Error::<T, I>::NotHead);
1156
1157 let _ = Members::<T, I>::clear(u32::MAX, None);
1158 MemberCount::<T, I>::kill();
1159 let _ = MemberByIndex::<T, I>::clear(u32::MAX, None);
1160 let _ = SuspendedMembers::<T, I>::clear(u32::MAX, None);
1161 let payouts_account = Self::payouts();
1165 T::Currency::transfer(
1166 &payouts_account,
1167 &Self::account_id(),
1168 T::Currency::free_balance(&payouts_account),
1169 AllowDeath,
1170 )?;
1171 let _ = Payouts::<T, I>::clear(u32::MAX, None);
1172 let _ = Votes::<T, I>::clear(u32::MAX, None);
1173 let _ = VoteClearCursor::<T, I>::clear(u32::MAX, None);
1174 Head::<T, I>::kill();
1175 NextHead::<T, I>::kill();
1176 Founder::<T, I>::kill();
1177 Rules::<T, I>::kill();
1178 Parameters::<T, I>::kill();
1179 Pot::<T, I>::kill();
1180 RoundCount::<T, I>::kill();
1181 Bids::<T, I>::kill();
1182 Skeptic::<T, I>::kill();
1183 ChallengeRoundCount::<T, I>::kill();
1184 Defending::<T, I>::kill();
1185 let _ = DefenderVotes::<T, I>::clear(u32::MAX, None);
1186 let _ = Candidates::<T, I>::clear(u32::MAX, None);
1187 Self::deposit_event(Event::<T, I>::Unfounded { founder });
1188 Ok(())
1189 }
1190
1191 #[pallet::call_index(10)]
1206 #[pallet::weight(T::WeightInfo::judge_suspended_member())]
1207 pub fn judge_suspended_member(
1208 origin: OriginFor<T>,
1209 who: AccountIdLookupOf<T>,
1210 forgive: bool,
1211 ) -> DispatchResultWithPostInfo {
1212 ensure!(
1213 Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1214 Error::<T, I>::NotFounder
1215 );
1216 let who = T::Lookup::lookup(who)?;
1217 let record = SuspendedMembers::<T, I>::get(&who).ok_or(Error::<T, I>::NotSuspended)?;
1218 if forgive {
1219 Self::reinstate_member(&who, record.rank)?;
1221 } else {
1222 let payout_record = Payouts::<T, I>::take(&who);
1223 let total = payout_record
1224 .payouts
1225 .into_iter()
1226 .map(|x| x.1)
1227 .fold(Zero::zero(), |acc: BalanceOf<T, I>, x| acc.saturating_add(x));
1228 Self::unreserve_payout(total);
1229 }
1230 SuspendedMembers::<T, I>::remove(&who);
1231 Self::deposit_event(Event::<T, I>::SuspendedMemberJudgement { who, judged: forgive });
1232 Ok(Pays::No.into())
1233 }
1234
1235 #[pallet::call_index(11)]
1248 #[pallet::weight(T::WeightInfo::set_parameters())]
1249 pub fn set_parameters(
1250 origin: OriginFor<T>,
1251 max_members: u32,
1252 max_intake: u32,
1253 max_strikes: u32,
1254 candidate_deposit: BalanceOf<T, I>,
1255 ) -> DispatchResult {
1256 ensure!(
1257 Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1258 Error::<T, I>::NotFounder
1259 );
1260 ensure!(max_members >= MemberCount::<T, I>::get(), Error::<T, I>::MaxMembers);
1261 let params = GroupParams { max_members, max_intake, max_strikes, candidate_deposit };
1262 Parameters::<T, I>::put(¶ms);
1263 Self::deposit_event(Event::<T, I>::NewParams { params });
1264 Ok(())
1265 }
1266
1267 #[pallet::call_index(12)]
1270 #[pallet::weight(T::WeightInfo::punish_skeptic())]
1271 pub fn punish_skeptic(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1272 let candidate = ensure_signed(origin)?;
1273 let mut candidacy =
1274 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1275 ensure!(!candidacy.skeptic_struck, Error::<T, I>::AlreadyPunished);
1276 ensure!(!Self::in_progress(candidacy.round), Error::<T, I>::InProgress);
1277 let punished = Self::check_skeptic(&candidate, &mut candidacy);
1278 Candidates::<T, I>::insert(&candidate, candidacy);
1279 Ok(if punished { Pays::No } else { Pays::Yes }.into())
1280 }
1281
1282 #[pallet::call_index(13)]
1285 #[pallet::weight(T::WeightInfo::claim_membership())]
1286 pub fn claim_membership(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1287 let candidate = ensure_signed(origin)?;
1288 let candidacy =
1289 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1290 ensure!(candidacy.tally.clear_approval(), Error::<T, I>::NotApproved);
1291 ensure!(!Self::in_progress(candidacy.round), Error::<T, I>::InProgress);
1292 Self::induct_member(candidate, candidacy, 0)?;
1293 Ok(Pays::No.into())
1294 }
1295
1296 #[pallet::call_index(14)]
1300 #[pallet::weight(T::WeightInfo::bestow_membership())]
1301 pub fn bestow_membership(
1302 origin: OriginFor<T>,
1303 candidate: T::AccountId,
1304 ) -> DispatchResultWithPostInfo {
1305 ensure!(
1306 Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1307 Error::<T, I>::NotFounder
1308 );
1309 let candidacy =
1310 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1311 ensure!(!candidacy.tally.clear_rejection(), Error::<T, I>::Rejected);
1312 ensure!(!Self::in_progress(candidacy.round), Error::<T, I>::InProgress);
1313 Self::induct_member(candidate, candidacy, 0)?;
1314 Ok(Pays::No.into())
1315 }
1316
1317 #[pallet::call_index(15)]
1323 #[pallet::weight(T::WeightInfo::kick_candidate())]
1324 pub fn kick_candidate(
1325 origin: OriginFor<T>,
1326 candidate: T::AccountId,
1327 ) -> DispatchResultWithPostInfo {
1328 ensure!(
1329 Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1330 Error::<T, I>::NotFounder
1331 );
1332 let mut candidacy =
1333 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1334 ensure!(!Self::in_progress(candidacy.round), Error::<T, I>::InProgress);
1335 ensure!(!candidacy.tally.clear_approval(), Error::<T, I>::Approved);
1336 Self::check_skeptic(&candidate, &mut candidacy);
1337 Self::reject_candidate(&candidate, &candidacy.kind);
1338 Candidates::<T, I>::remove(&candidate);
1339 Ok(Pays::No.into())
1340 }
1341
1342 #[pallet::call_index(16)]
1346 #[pallet::weight(T::WeightInfo::resign_candidacy())]
1347 pub fn resign_candidacy(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1348 let candidate = ensure_signed(origin)?;
1349 let mut candidacy =
1350 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1351 if !Self::in_progress(candidacy.round) {
1352 Self::check_skeptic(&candidate, &mut candidacy);
1353 }
1354 Self::reject_candidate(&candidate, &candidacy.kind);
1355 Candidates::<T, I>::remove(&candidate);
1356 Ok(Pays::No.into())
1357 }
1358
1359 #[pallet::call_index(17)]
1365 #[pallet::weight(T::WeightInfo::drop_candidate())]
1366 pub fn drop_candidate(
1367 origin: OriginFor<T>,
1368 candidate: T::AccountId,
1369 ) -> DispatchResultWithPostInfo {
1370 ensure_signed(origin)?;
1371 let candidacy =
1372 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1373 ensure!(candidacy.tally.clear_rejection(), Error::<T, I>::NotRejected);
1374 ensure!(RoundCount::<T, I>::get() > candidacy.round + 1, Error::<T, I>::TooEarly);
1375 Self::reject_candidate(&candidate, &candidacy.kind);
1376 Candidates::<T, I>::remove(&candidate);
1377 Ok(Pays::No.into())
1378 }
1379
1380 #[pallet::call_index(18)]
1384 #[pallet::weight(T::WeightInfo::cleanup_candidacy())]
1385 pub fn cleanup_candidacy(
1386 origin: OriginFor<T>,
1387 candidate: T::AccountId,
1388 max: u32,
1389 ) -> DispatchResultWithPostInfo {
1390 ensure_signed(origin)?;
1391 ensure!(!Candidates::<T, I>::contains_key(&candidate), Error::<T, I>::InProgress);
1392 let maybe_cursor = VoteClearCursor::<T, I>::get(&candidate);
1393 let r =
1394 Votes::<T, I>::clear_prefix(&candidate, max, maybe_cursor.as_ref().map(|x| &x[..]));
1395 if let Some(cursor) = r.maybe_cursor {
1396 VoteClearCursor::<T, I>::insert(&candidate, BoundedVec::truncate_from(cursor));
1397 }
1398 Ok(if r.loops == 0 { Pays::Yes } else { Pays::No }.into())
1399 }
1400
1401 #[pallet::call_index(19)]
1405 #[pallet::weight(T::WeightInfo::cleanup_challenge())]
1406 pub fn cleanup_challenge(
1407 origin: OriginFor<T>,
1408 challenge_round: RoundIndex,
1409 max: u32,
1410 ) -> DispatchResultWithPostInfo {
1411 ensure_signed(origin)?;
1412 ensure!(
1413 challenge_round < ChallengeRoundCount::<T, I>::get(),
1414 Error::<T, I>::InProgress
1415 );
1416 let _ = DefenderVotes::<T, I>::clear_prefix(challenge_round, max, None);
1417 Ok(Pays::No.into())
1421 }
1422
1423 #[pallet::call_index(20)]
1431 #[pallet::weight(T::WeightInfo::poke_deposit())]
1432 pub fn poke_deposit(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1433 let who = ensure_signed(origin)?;
1434
1435 let mut bids = Bids::<T, I>::get();
1437 let bid = bids.iter_mut().find(|bid| bid.who == who).ok_or(Error::<T, I>::NotBidder)?;
1438
1439 let old_deposit = match &bid.kind {
1441 BidKind::Deposit(amount) => *amount,
1442 _ => return Err(Error::<T, I>::NoDeposit.into()),
1443 };
1444
1445 let params = Parameters::<T, I>::get().ok_or(Error::<T, I>::NotGroup)?;
1446 let new_deposit = params.candidate_deposit;
1447
1448 if old_deposit == new_deposit {
1449 return Ok(Pays::Yes.into());
1450 }
1451
1452 if new_deposit > old_deposit {
1453 let extra = new_deposit.saturating_sub(old_deposit);
1455 T::Currency::reserve(&who, extra)?;
1456 } else {
1457 let excess = old_deposit.saturating_sub(new_deposit);
1459 let remaining_unreserved = T::Currency::unreserve(&who, excess);
1460 if !remaining_unreserved.is_zero() {
1461 defensive!(
1462 "Failed to unreserve for full amount for bid (Requested, Actual)",
1463 (excess, excess.saturating_sub(remaining_unreserved))
1464 );
1465 }
1466 }
1467
1468 bid.kind = BidKind::Deposit(new_deposit);
1469 Bids::<T, I>::put(bids);
1470
1471 Self::deposit_event(Event::<T, I>::DepositPoked {
1472 who: who.clone(),
1473 old_deposit,
1474 new_deposit,
1475 });
1476
1477 Ok(Pays::No.into())
1478 }
1479
1480 #[pallet::call_index(21)]
1488 #[pallet::weight(T::WeightInfo::kick_member())]
1489 pub fn kick_member(origin: OriginFor<T>, who: AccountIdLookupOf<T>) -> DispatchResult {
1490 ensure!(
1491 Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1492 Error::<T, I>::NotFounder
1493 );
1494 let who = T::Lookup::lookup(who)?;
1495
1496 let _ = Self::remove_member(&who)?;
1497
1498 let payout_record = Payouts::<T, I>::take(&who);
1499 let total = payout_record
1500 .payouts
1501 .into_iter()
1502 .fold(Zero::zero(), |acc: BalanceOf<T, I>, x| acc.saturating_add(x.1));
1503 Self::unreserve_payout(total);
1504
1505 Self::deposit_event(Event::<T, I>::MemberKicked { member: who });
1506 Ok(())
1507 }
1508 }
1509}
1510
1511pub struct EnsureFounder<T>(core::marker::PhantomData<T>);
1513impl<T: Config> EnsureOrigin<<T as frame_system::Config>::RuntimeOrigin> for EnsureFounder<T> {
1514 type Success = T::AccountId;
1515 fn try_origin(o: T::RuntimeOrigin) -> Result<Self::Success, T::RuntimeOrigin> {
1516 match (o.as_signer(), Founder::<T>::get()) {
1517 (Some(who), Some(f)) if *who == f => Ok(f),
1518 _ => Err(o),
1519 }
1520 }
1521
1522 #[cfg(feature = "runtime-benchmarks")]
1523 fn try_successful_origin() -> Result<T::RuntimeOrigin, ()> {
1524 let founder = Founder::<T>::get().ok_or(())?;
1525 Ok(T::RuntimeOrigin::from(frame_system::RawOrigin::Signed(founder)))
1526 }
1527}
1528
1529impl_ensure_origin_with_arg_ignoring_arg! {
1530 impl<{ T: Config, A }>
1531 EnsureOriginWithArg<T::RuntimeOrigin, A> for EnsureFounder<T>
1532 {}
1533}
1534
1535#[derive(Debug, PartialEq, Eq)]
1536pub enum Period<BlockNumber> {
1537 Voting { elapsed: BlockNumber, more: BlockNumber },
1538 Claim { elapsed: BlockNumber, more: BlockNumber },
1539 Intake { elapsed: BlockNumber },
1540}
1541
1542impl<T: Config<I>, I: 'static> Pallet<T, I> {
1543 fn period() -> Period<BlockNumberFor<T, I>> {
1545 let claim_period = T::ClaimPeriod::get();
1546 let voting_period = T::VotingPeriod::get();
1547 let rotation_period = voting_period + claim_period;
1548 let now = T::BlockNumberProvider::current_block_number();
1549 let phase = now % rotation_period;
1550 if now >= Self::next_intake_at() {
1551 Period::Intake { elapsed: now - Self::next_intake_at() }
1552 } else if phase < voting_period {
1553 Period::Voting { elapsed: phase, more: voting_period - phase }
1554 } else {
1555 Period::Claim { elapsed: phase - voting_period, more: rotation_period - phase }
1556 }
1557 }
1558
1559 pub fn next_intake_at() -> BlockNumberFor<T, I> {
1563 match NextIntakeAt::<T, I>::get() {
1564 Some(next) => next,
1565 None => {
1566 let now = T::BlockNumberProvider::current_block_number();
1568 let prev_block = now.saturating_sub(BlockNumberFor::<T, I>::one());
1569 let rotation_period = T::VotingPeriod::get().saturating_add(T::ClaimPeriod::get());
1570 let elapsed = prev_block % rotation_period;
1571 let next_intake_at = prev_block + (rotation_period - elapsed);
1572 NextIntakeAt::<T, I>::put(next_intake_at);
1573 next_intake_at
1574 },
1575 }
1576 }
1577
1578 fn set_next_intake_at() {
1582 let prev_next_intake_at = Self::next_intake_at();
1583 let next_intake_at = prev_next_intake_at
1584 .saturating_add(T::VotingPeriod::get().saturating_add(T::ClaimPeriod::get()));
1585 NextIntakeAt::<T, I>::put(next_intake_at);
1586 }
1587
1588 pub fn next_challenge_at() -> BlockNumberFor<T, I> {
1592 match NextChallengeAt::<T, I>::get() {
1593 Some(next) => next,
1594 None => {
1595 let now = T::BlockNumberProvider::current_block_number();
1597 let prev_block = now.saturating_sub(BlockNumberFor::<T, I>::one());
1598 let challenge_period = T::ChallengePeriod::get();
1599 let elapsed = prev_block % challenge_period;
1600 let next_challenge_at = prev_block + (challenge_period - elapsed);
1601 NextChallengeAt::<T, I>::put(next_challenge_at);
1602 next_challenge_at
1603 },
1604 }
1605 }
1606
1607 fn set_next_challenge_at() {
1611 let prev_next_challenge_at = Self::next_challenge_at();
1612 let next_challenge_at = prev_next_challenge_at.saturating_add(T::ChallengePeriod::get());
1613 NextChallengeAt::<T, I>::put(next_challenge_at);
1614 }
1615
1616 fn in_progress(target_round: RoundIndex) -> bool {
1618 let round = RoundCount::<T, I>::get();
1619 target_round == round && matches!(Self::period(), Period::Voting { .. })
1620 }
1621
1622 fn do_vote(maybe_old: Option<Vote>, approve: bool, rank: Rank, tally: &mut Tally) -> Vote {
1624 match maybe_old {
1625 Some(Vote { approve: true, weight }) => tally.approvals.saturating_reduce(weight),
1626 Some(Vote { approve: false, weight }) => tally.rejections.saturating_reduce(weight),
1627 _ => {},
1628 }
1629 let weight_root = rank + 1;
1630 let weight = weight_root * weight_root;
1631 match approve {
1632 true => tally.approvals.saturating_accrue(weight),
1633 false => tally.rejections.saturating_accrue(weight),
1634 }
1635 Vote { approve, weight }
1636 }
1637
1638 fn check_skeptic(
1640 candidate: &T::AccountId,
1641 candidacy: &mut Candidacy<T::AccountId, BalanceOf<T, I>>,
1642 ) -> bool {
1643 if RoundCount::<T, I>::get() != candidacy.round || candidacy.skeptic_struck {
1644 return false;
1645 }
1646 let skeptic = match Skeptic::<T, I>::get() {
1648 Some(s) => s,
1649 None => return false,
1650 };
1651 let maybe_vote = Votes::<T, I>::get(&candidate, &skeptic);
1652 let approved = candidacy.tally.clear_approval();
1653 let rejected = candidacy.tally.clear_rejection();
1654 match (maybe_vote, approved, rejected) {
1655 (None, _, _) |
1656 (Some(Vote { approve: true, .. }), false, true) |
1657 (Some(Vote { approve: false, .. }), true, false) => {
1658 if Self::strike_member(&skeptic).is_ok() {
1660 candidacy.skeptic_struck = true;
1661 true
1662 } else {
1663 false
1664 }
1665 },
1666 _ => false,
1667 }
1668 }
1669
1670 fn rotate_challenge(rng: &mut impl RngCore) {
1672 let mut next_defender = None;
1673 let mut round = ChallengeRoundCount::<T, I>::get();
1674
1675 if let Some((defender, skeptic, tally)) = Defending::<T, I>::get() {
1677 if !tally.more_approvals() {
1679 let _ = Self::suspend_member(&defender);
1682 }
1683
1684 let skeptic_vote = DefenderVotes::<T, I>::get(round, &skeptic);
1686 match (skeptic_vote, tally.more_approvals(), tally.more_rejections()) {
1687 (None, _, _) |
1688 (Some(Vote { approve: true, .. }), false, true) |
1689 (Some(Vote { approve: false, .. }), true, false) => {
1690 let _ = Self::strike_member(&skeptic);
1692 let founder = Founder::<T, I>::get();
1693 let head = Head::<T, I>::get();
1694 if Some(&skeptic) != founder.as_ref() && Some(&skeptic) != head.as_ref() {
1695 next_defender = Some(skeptic);
1696 }
1697 },
1698 _ => {},
1699 }
1700 round.saturating_inc();
1701 ChallengeRoundCount::<T, I>::put(round);
1702 }
1703
1704 if MemberCount::<T, I>::get() > 2 {
1707 let defender = next_defender
1708 .or_else(|| Self::pick_defendant(rng))
1709 .expect("exited if members empty; qed");
1710 let skeptic =
1711 Self::pick_member_except(rng, &defender).expect("exited if members empty; qed");
1712 Self::deposit_event(Event::<T, I>::Challenged { member: defender.clone() });
1713 Defending::<T, I>::put((defender, skeptic, Tally::default()));
1714 } else {
1715 Defending::<T, I>::kill();
1716 }
1717 }
1718
1719 fn rotate_intake(rng: &mut impl RngCore) {
1726 let member_count = MemberCount::<T, I>::get();
1728 if member_count < 1 {
1729 return;
1730 }
1731 let maybe_head = NextHead::<T, I>::take();
1732 if let Some(head) = maybe_head {
1733 Head::<T, I>::put(&head.who);
1734 }
1735
1736 let mut pot = Pot::<T, I>::get();
1739 let unaccounted = T::Currency::free_balance(&Self::account_id()).saturating_sub(pot);
1740 pot.saturating_accrue(T::PeriodSpend::get().min(unaccounted / 2u8.into()));
1741 Pot::<T, I>::put(&pot);
1742
1743 let mut round_count = RoundCount::<T, I>::get();
1745 round_count.saturating_inc();
1746 let candidate_count = Self::select_new_candidates(round_count, member_count, pot);
1747 if candidate_count > 0 {
1748 let skeptic = Self::pick_member(rng).expect("exited if members empty; qed");
1750 Skeptic::<T, I>::put(skeptic);
1751 }
1752 RoundCount::<T, I>::put(round_count);
1753 }
1754
1755 pub fn select_new_candidates(
1763 round: RoundIndex,
1764 member_count: u32,
1765 pot: BalanceOf<T, I>,
1766 ) -> u32 {
1767 let mut bids = Bids::<T, I>::get();
1769 let params = match Parameters::<T, I>::get() {
1770 Some(params) => params,
1771 None => return 0,
1772 };
1773 let max_selections: u32 = params
1774 .max_intake
1775 .min(params.max_members.saturating_sub(member_count))
1776 .min(bids.len() as u32);
1777
1778 let mut selections = 0;
1779 let mut total_cost: BalanceOf<T, I> = Zero::zero();
1781
1782 bids.retain(|bid| {
1783 total_cost.saturating_accrue(bid.value);
1785 let accept = selections < max_selections &&
1786 (!bid.value.is_zero() || selections == 0) &&
1787 total_cost <= pot;
1788 if accept {
1789 let candidacy = Candidacy {
1790 round,
1791 kind: bid.kind.clone(),
1792 bid: bid.value,
1793 tally: Default::default(),
1794 skeptic_struck: false,
1795 };
1796 Candidates::<T, I>::insert(&bid.who, candidacy);
1797 selections.saturating_inc();
1798 }
1799 !accept
1800 });
1801
1802 Bids::<T, I>::put(&bids);
1804 selections
1805 }
1806
1807 fn insert_bid(
1810 bids: &mut BoundedVec<Bid<T::AccountId, BalanceOf<T, I>>, T::MaxBids>,
1811 who: &T::AccountId,
1812 value: BalanceOf<T, I>,
1813 bid_kind: BidKind<T::AccountId, BalanceOf<T, I>>,
1814 ) {
1815 let pos = bids.iter().position(|bid| bid.value > value).unwrap_or(bids.len());
1816 let r = bids.force_insert_keep_left(pos, Bid { value, who: who.clone(), kind: bid_kind });
1817 let maybe_discarded = match r {
1818 Ok(x) => x,
1819 Err(x) => Some(x),
1820 };
1821 if let Some(discarded) = maybe_discarded {
1822 Self::clean_bid(&discarded);
1823 Self::deposit_event(Event::<T, I>::AutoUnbid { candidate: discarded.who });
1824 }
1825 }
1826
1827 fn clean_bid(bid: &Bid<T::AccountId, BalanceOf<T, I>>) {
1835 match &bid.kind {
1836 BidKind::Deposit(deposit) => {
1837 let err_amount = T::Currency::unreserve(&bid.who, *deposit);
1838 debug_assert!(err_amount.is_zero());
1839 },
1840 BidKind::Vouch(voucher, _) => {
1841 Members::<T, I>::mutate_extant(voucher, |record| record.vouching = None);
1842 },
1843 }
1844 }
1845
1846 fn reject_candidate(who: &T::AccountId, kind: &BidKind<T::AccountId, BalanceOf<T, I>>) {
1854 match kind {
1855 BidKind::Deposit(deposit) => {
1856 let pot = Self::account_id();
1857 let free = BalanceStatus::Free;
1858 let r = T::Currency::repatriate_reserved(&who, &pot, *deposit, free);
1859 debug_assert!(r.is_ok());
1860 },
1861 BidKind::Vouch(voucher, _) => {
1862 Members::<T, I>::mutate_extant(voucher, |record| {
1863 record.vouching = Some(VouchingStatus::Banned)
1864 });
1865 },
1866 }
1867 }
1868
1869 fn has_bid(bids: &Vec<Bid<T::AccountId, BalanceOf<T, I>>>, who: &T::AccountId) -> bool {
1871 bids.iter().any(|bid| bid.who == *who)
1873 }
1874
1875 fn insert_member(who: &T::AccountId, rank: Rank) -> DispatchResult {
1885 let params = Parameters::<T, I>::get().ok_or(Error::<T, I>::NotGroup)?;
1886 ensure!(MemberCount::<T, I>::get() < params.max_members, Error::<T, I>::MaxMembers);
1887 let index = MemberCount::<T, I>::mutate(|i| {
1888 i.saturating_accrue(1);
1889 *i - 1
1890 });
1891 let record = MemberRecord { rank, strikes: 0, vouching: None, index };
1892 Members::<T, I>::insert(who, record);
1893 MemberByIndex::<T, I>::insert(index, who);
1894 Ok(())
1895 }
1896
1897 fn reinstate_member(who: &T::AccountId, rank: Rank) -> DispatchResult {
1904 Self::insert_member(who, rank)
1905 }
1906
1907 fn add_new_member(who: &T::AccountId, rank: Rank) -> DispatchResult {
1910 Self::insert_member(who, rank)
1911 }
1912
1913 fn induct_member(
1915 candidate: T::AccountId,
1916 mut candidacy: Candidacy<T::AccountId, BalanceOf<T, I>>,
1917 rank: Rank,
1918 ) -> DispatchResult {
1919 Self::add_new_member(&candidate, rank)?;
1920 Self::check_skeptic(&candidate, &mut candidacy);
1921
1922 let next_head = NextHead::<T, I>::get()
1923 .filter(|old| {
1924 old.round > candidacy.round ||
1925 old.round == candidacy.round && old.bid < candidacy.bid
1926 })
1927 .unwrap_or_else(|| IntakeRecord {
1928 who: candidate.clone(),
1929 bid: candidacy.bid,
1930 round: candidacy.round,
1931 });
1932 NextHead::<T, I>::put(next_head);
1933
1934 let now = T::BlockNumberProvider::current_block_number();
1935 let maturity = now + Self::lock_duration(MemberCount::<T, I>::get());
1936 Self::reward_bidder(&candidate, candidacy.bid, candidacy.kind, maturity);
1937
1938 Candidates::<T, I>::remove(&candidate);
1939 Ok(())
1940 }
1941
1942 fn strike_member(who: &T::AccountId) -> DispatchResult {
1943 let mut record = Members::<T, I>::get(who).ok_or(Error::<T, I>::NotMember)?;
1944 record.strikes.saturating_inc();
1945 Members::<T, I>::insert(who, &record);
1946 if record.strikes >= T::GraceStrikes::get() {
1950 let total_payout = Payouts::<T, I>::get(who)
1952 .payouts
1953 .iter()
1954 .fold(BalanceOf::<T, I>::zero(), |acc, x| acc.saturating_add(x.1));
1955 Self::slash_payout(who, total_payout / 2u32.into());
1956 }
1957
1958 let params = Parameters::<T, I>::get().ok_or(Error::<T, I>::NotGroup)?;
1959 if record.strikes >= params.max_strikes {
1960 let _ = Self::suspend_member(who);
1962 }
1963 Ok(())
1964 }
1965
1966 pub fn remove_member(m: &T::AccountId) -> Result<MemberRecord, DispatchError> {
1977 ensure!(Head::<T, I>::get().as_ref() != Some(m), Error::<T, I>::Head);
1978 ensure!(Founder::<T, I>::get().as_ref() != Some(m), Error::<T, I>::Founder);
1979 if let Some(mut record) = Members::<T, I>::get(m) {
1980 let index = record.index;
1981 let last_index = MemberCount::<T, I>::mutate(|i| {
1982 i.saturating_reduce(1);
1983 *i
1984 });
1985 if index != last_index {
1986 if let Some(other) = MemberByIndex::<T, I>::get(last_index) {
1989 MemberByIndex::<T, I>::insert(index, &other);
1990 Members::<T, I>::mutate(other, |m_r| {
1991 if let Some(r) = m_r {
1992 r.index = index
1993 }
1994 });
1995 } else {
1996 debug_assert!(false, "ERROR: No member at the last index position?");
1997 }
1998 }
1999
2000 MemberByIndex::<T, I>::remove(last_index);
2001 Members::<T, I>::remove(m);
2002 if record.vouching.take() == Some(VouchingStatus::Vouching) {
2004 Bids::<T, I>::mutate(|bids|
2007 if let Some(pos) = bids.iter().position(|b| b.kind.is_vouch(&m)) {
2009 let vouched = bids.remove(pos).who;
2011 Self::deposit_event(Event::<T, I>::Unvouch { candidate: vouched });
2012 }
2013 );
2014 }
2015 Ok(record)
2016 } else {
2017 Err(Error::<T, I>::NotMember.into())
2018 }
2019 }
2020
2021 fn suspend_member(who: &T::AccountId) -> DispatchResult {
2027 let record = Self::remove_member(&who)?;
2028 SuspendedMembers::<T, I>::insert(who, record);
2029 Self::deposit_event(Event::<T, I>::MemberSuspended { member: who.clone() });
2030 Ok(())
2031 }
2032
2033 fn pick_member(rng: &mut impl RngCore) -> Option<T::AccountId> {
2037 let member_count = MemberCount::<T, I>::get();
2038 if member_count == 0 {
2039 return None;
2040 }
2041 let random_index = rng.next_u32() % member_count;
2042 MemberByIndex::<T, I>::get(random_index)
2043 }
2044
2045 fn pick_member_except(
2050 rng: &mut impl RngCore,
2051 exception: &T::AccountId,
2052 ) -> Option<T::AccountId> {
2053 let member_count = MemberCount::<T, I>::get();
2054 if member_count <= 1 {
2055 return None;
2056 }
2057 let random_index = rng.next_u32() % (member_count - 1);
2058 let pick = MemberByIndex::<T, I>::get(random_index);
2059 if pick.as_ref() == Some(exception) {
2060 MemberByIndex::<T, I>::get(member_count - 1)
2061 } else {
2062 pick
2063 }
2064 }
2065
2066 fn pick_defendant(rng: &mut impl RngCore) -> Option<T::AccountId> {
2071 let member_count = MemberCount::<T, I>::get();
2072 if member_count <= 2 {
2073 return None;
2074 }
2075 let head = Head::<T, I>::get();
2079 let pickable_count = member_count - if head.is_some() { 2 } else { 1 };
2080 let random_index = rng.next_u32() % pickable_count + 1;
2081 let pick = MemberByIndex::<T, I>::get(random_index);
2082 if pick == head && head.is_some() {
2083 MemberByIndex::<T, I>::get(member_count - 1)
2086 } else {
2087 pick
2088 }
2089 }
2090
2091 fn reward_bidder(
2093 candidate: &T::AccountId,
2094 value: BalanceOf<T, I>,
2095 kind: BidKind<T::AccountId, BalanceOf<T, I>>,
2096 maturity: BlockNumberFor<T, I>,
2097 ) {
2098 let value = match kind {
2099 BidKind::Deposit(deposit) => {
2100 let err_amount = T::Currency::unreserve(candidate, deposit);
2103 debug_assert!(err_amount.is_zero());
2104 value
2105 },
2106 BidKind::Vouch(voucher, tip) => {
2107 if let Some(mut record) = Members::<T, I>::get(&voucher) {
2110 if let Some(VouchingStatus::Vouching) = record.vouching {
2111 record.vouching = None;
2114 Self::bump_payout(&voucher, maturity, tip.min(value));
2115 Members::<T, I>::insert(&voucher, record);
2116 value.saturating_sub(tip)
2117 } else {
2118 value
2119 }
2120 } else {
2121 value
2122 }
2123 },
2124 };
2125
2126 Self::bump_payout(candidate, maturity, value);
2127 }
2128
2129 fn bump_payout(who: &T::AccountId, when: BlockNumberFor<T, I>, value: BalanceOf<T, I>) {
2135 if value.is_zero() {
2136 return;
2137 }
2138 if let Some(MemberRecord { rank: 0, .. }) = Members::<T, I>::get(who) {
2139 let recorded = Payouts::<T, I>::mutate(who, |record| {
2140 match record.payouts.binary_search_by_key(&when, |x| x.0) {
2142 Ok(index) => {
2143 record.payouts[index].1.saturating_accrue(value);
2144 true
2145 },
2146 Err(index) => record.payouts.try_insert(index, (when, value)).is_ok(),
2148 }
2149 });
2150 if recorded {
2152 Self::reserve_payout(value);
2153 }
2154 }
2155 }
2156
2157 fn slash_payout(who: &T::AccountId, value: BalanceOf<T, I>) -> BalanceOf<T, I> {
2160 let mut record = Payouts::<T, I>::get(who);
2161 let mut rest = value;
2162 while !record.payouts.is_empty() {
2163 if let Some(new_rest) = rest.checked_sub(&record.payouts[0].1) {
2164 rest = new_rest;
2166 record.payouts.remove(0);
2167 } else {
2168 record.payouts[0].1.saturating_reduce(rest);
2170 rest = Zero::zero();
2171 break;
2172 }
2173 }
2174 Payouts::<T, I>::insert(who, record);
2175 let slashed = value - rest;
2176 Self::unreserve_payout(slashed);
2177 slashed
2178 }
2179
2180 fn reserve_payout(amount: BalanceOf<T, I>) {
2183 Pot::<T, I>::mutate(|pot| pot.saturating_reduce(amount));
2185
2186 let res = T::Currency::transfer(&Self::account_id(), &Self::payouts(), amount, AllowDeath);
2189 debug_assert!(res.is_ok());
2190 }
2191
2192 fn unreserve_payout(amount: BalanceOf<T, I>) {
2195 Pot::<T, I>::mutate(|pot| pot.saturating_accrue(amount));
2197
2198 let res = T::Currency::transfer(&Self::payouts(), &Self::account_id(), amount, AllowDeath);
2201 debug_assert!(res.is_ok());
2202 }
2203
2204 pub fn account_id() -> T::AccountId {
2209 T::PalletId::get().into_account_truncating()
2210 }
2211
2212 pub fn payouts() -> T::AccountId {
2217 T::PalletId::get().into_sub_account_truncating(b"payouts")
2218 }
2219
2220 pub(crate) fn pending_payouts_total() -> BalanceOf<T, I> {
2222 Payouts::<T, I>::iter_values()
2223 .flat_map(|record| record.payouts.into_iter())
2224 .fold(Zero::zero(), |acc: BalanceOf<T, I>, x| acc.saturating_add(x.1))
2225 }
2226
2227 #[cfg(any(feature = "try-runtime", test))]
2233 pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
2234 frame_support::ensure!(
2235 T::Currency::free_balance(&Self::payouts()) == Self::pending_payouts_total(),
2236 "payouts account balance must equal the total of pending payouts",
2237 );
2238 Ok(())
2239 }
2240
2241 fn lock_duration(x: u32) -> BlockNumberFor<T, I> {
2246 let lock_pc = 100 - 50_000 / (x + 500);
2247 Percent::from_percent(lock_pc as u8) * T::MaxLockDuration::get()
2248 }
2249}
2250
2251impl<T: Config<I>, I: 'static> OnUnbalanced<NegativeImbalanceOf<T, I>> for Pallet<T, I> {
2252 fn on_nonzero_unbalanced(amount: NegativeImbalanceOf<T, I>) {
2253 let numeric_amount = amount.peek();
2254
2255 let _ = T::Currency::resolve_creating(&Self::account_id(), amount);
2257
2258 Self::deposit_event(Event::<T, I>::Deposit { value: numeric_amount });
2259 }
2260}