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 #[deprecated(note = "use `Event` instead")]
676 pub type RawEvent<T, I = ()> = Event<T, I>;
677
678 #[pallet::storage]
680 pub type Parameters<T: Config<I>, I: 'static = ()> =
681 StorageValue<_, GroupParamsFor<T, I>, OptionQuery>;
682
683 #[pallet::storage]
685 pub type Pot<T: Config<I>, I: 'static = ()> = StorageValue<_, BalanceOf<T, I>, ValueQuery>;
686
687 #[pallet::storage]
689 pub type Founder<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId>;
690
691 #[pallet::storage]
693 pub type Head<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId>;
694
695 #[pallet::storage]
698 pub type Rules<T: Config<I>, I: 'static = ()> = StorageValue<_, T::Hash>;
699
700 #[pallet::storage]
702 pub type Members<T: Config<I>, I: 'static = ()> =
703 StorageMap<_, Twox64Concat, T::AccountId, MemberRecord, OptionQuery>;
704
705 #[pallet::storage]
707 pub type Payouts<T: Config<I>, I: 'static = ()> =
708 StorageMap<_, Twox64Concat, T::AccountId, PayoutRecordFor<T, I>, ValueQuery>;
709
710 #[pallet::storage]
712 pub type MemberCount<T: Config<I>, I: 'static = ()> = StorageValue<_, u32, ValueQuery>;
713
714 #[pallet::storage]
717 pub type MemberByIndex<T: Config<I>, I: 'static = ()> =
718 StorageMap<_, Twox64Concat, u32, T::AccountId, OptionQuery>;
719
720 #[pallet::storage]
722 pub type SuspendedMembers<T: Config<I>, I: 'static = ()> =
723 StorageMap<_, Twox64Concat, T::AccountId, MemberRecord, OptionQuery>;
724
725 #[pallet::storage]
727 pub type RoundCount<T: Config<I>, I: 'static = ()> = StorageValue<_, RoundIndex, ValueQuery>;
728
729 #[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 #[pallet::storage]
745 pub type Skeptic<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId, OptionQuery>;
746
747 #[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 #[pallet::storage]
761 pub type VoteClearCursor<T: Config<I>, I: 'static = ()> =
762 StorageMap<_, Twox64Concat, T::AccountId, BoundedVec<u8, KeyLenOf<Votes<T, I>>>>;
763
764 #[pallet::storage]
768 pub type NextHead<T: Config<I>, I: 'static = ()> =
769 StorageValue<_, IntakeRecordFor<T, I>, OptionQuery>;
770
771 #[pallet::storage]
773 pub type ChallengeRoundCount<T: Config<I>, I: 'static = ()> =
774 StorageValue<_, RoundIndex, ValueQuery>;
775
776 #[pallet::storage]
778 pub type Defending<T: Config<I>, I: 'static = ()> =
779 StorageValue<_, (T::AccountId, T::AccountId, Tally)>;
780
781 #[pallet::storage]
783 pub type DefenderVotes<T: Config<I>, I: 'static = ()> =
784 StorageDoubleMap<_, Twox64Concat, RoundIndex, Twox64Concat, T::AccountId, Vote>;
785
786 #[pallet::storage]
788 pub type NextIntakeAt<T: Config<I>, I: 'static = ()> = StorageValue<_, BlockNumberFor<T, I>>;
789
790 #[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 let (seed, _) = T::Randomness::random(phrase);
806 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 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 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 #[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 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 #[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 #[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 let mut bids = Bids::<T, I>::get();
934 ensure!(!Self::has_bid(&bids, &who), Error::<T, I>::AlreadyBid);
935
936 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 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 record.vouching = Some(VouchingStatus::Vouching);
947 Self::insert_bid(&mut bids, &who, value, BidKind::Vouch(voucher.clone(), tip));
949
950 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 #[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 #[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 #[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 #[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 #[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 #[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 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 #[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 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 #[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 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 #[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(¶ms);
1267 Self::deposit_event(Event::<T, I>::NewParams { params });
1268 Ok(())
1269 }
1270
1271 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 Ok(Pays::No.into())
1425 }
1426
1427 #[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 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 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 let extra = new_deposit.saturating_sub(old_deposit);
1459 T::Currency::reserve(&who, extra)?;
1460 } else {
1461 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 #[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
1515pub 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 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 pub fn next_intake_at() -> BlockNumberFor<T, I> {
1567 match NextIntakeAt::<T, I>::get() {
1568 Some(next) => next,
1569 None => {
1570 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 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 pub fn next_challenge_at() -> BlockNumberFor<T, I> {
1596 match NextChallengeAt::<T, I>::get() {
1597 Some(next) => next,
1598 None => {
1599 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 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 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 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 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 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 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 fn rotate_challenge(rng: &mut impl RngCore) {
1676 let mut next_defender = None;
1677 let mut round = ChallengeRoundCount::<T, I>::get();
1678
1679 if let Some((defender, skeptic, tally)) = Defending::<T, I>::get() {
1681 if !tally.more_approvals() {
1683 let _ = Self::suspend_member(&defender);
1686 }
1687
1688 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 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 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 fn rotate_intake(rng: &mut impl RngCore) {
1730 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 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 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 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 pub fn select_new_candidates(
1767 round: RoundIndex,
1768 member_count: u32,
1769 pot: BalanceOf<T, I>,
1770 ) -> u32 {
1771 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 let mut total_cost: BalanceOf<T, I> = Zero::zero();
1785
1786 bids.retain(|bid| {
1787 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 Bids::<T, I>::put(&bids);
1808 selections
1809 }
1810
1811 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 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 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 fn has_bid(bids: &Vec<Bid<T::AccountId, BalanceOf<T, I>>>, who: &T::AccountId) -> bool {
1875 bids.iter().any(|bid| bid.who == *who)
1877 }
1878
1879 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 fn reinstate_member(who: &T::AccountId, rank: Rank) -> DispatchResult {
1908 Self::insert_member(who, rank)
1909 }
1910
1911 fn add_new_member(who: &T::AccountId, rank: Rank) -> DispatchResult {
1914 Self::insert_member(who, rank)
1915 }
1916
1917 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 if record.strikes >= T::GraceStrikes::get() {
1954 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 let _ = Self::suspend_member(who);
1966 }
1967 Ok(())
1968 }
1969
1970 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 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 if record.vouching.take() == Some(VouchingStatus::Vouching) {
2008 Bids::<T, I>::mutate(|bids|
2011 if let Some(pos) = bids.iter().position(|b| b.kind.is_vouch(&m)) {
2013 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 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 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 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 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 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 MemberByIndex::<T, I>::get(member_count - 1)
2090 } else {
2091 pick
2092 }
2093 }
2094
2095 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 let err_amount = T::Currency::unreserve(candidate, deposit);
2107 debug_assert!(err_amount.is_zero());
2108 value
2109 },
2110 BidKind::Vouch(voucher, tip) => {
2111 if let Some(mut record) = Members::<T, I>::get(&voucher) {
2114 if let Some(VouchingStatus::Vouching) = record.vouching {
2115 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 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 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 Err(index) => record.payouts.try_insert(index, (when, value)).is_ok(),
2152 }
2153 });
2154 if recorded {
2156 Self::reserve_payout(value);
2157 }
2158 }
2159 }
2160
2161 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 rest = new_rest;
2170 record.payouts.remove(0);
2171 } else {
2172 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 fn reserve_payout(amount: BalanceOf<T, I>) {
2187 Pot::<T, I>::mutate(|pot| pot.saturating_reduce(amount));
2189
2190 let res = T::Currency::transfer(&Self::account_id(), &Self::payouts(), amount, AllowDeath);
2193 debug_assert!(res.is_ok());
2194 }
2195
2196 fn unreserve_payout(amount: BalanceOf<T, I>) {
2199 Pot::<T, I>::mutate(|pot| pot.saturating_accrue(amount));
2201
2202 let res = T::Currency::transfer(&Self::payouts(), &Self::account_id(), amount, AllowDeath);
2205 debug_assert!(res.is_ok());
2206 }
2207
2208 pub fn account_id() -> T::AccountId {
2213 T::PalletId::get().into_account_truncating()
2214 }
2215
2216 pub fn payouts() -> T::AccountId {
2221 T::PalletId::get().into_sub_account_truncating(b"payouts")
2222 }
2223
2224 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 #[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 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 let _ = T::Currency::resolve_creating(&Self::account_id(), amount);
2261
2262 Self::deposit_event(Event::<T, I>::Deposit { value: numeric_amount });
2263 }
2264}