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
833 #[pallet::genesis_config]
834 #[derive(frame_support::DefaultNoBound)]
835 pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
836 pub pot: BalanceOf<T, I>,
837 }
838
839 #[pallet::genesis_build]
840 impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
841 fn build(&self) {
842 Pot::<T, I>::put(self.pot);
843 }
844 }
845
846 #[pallet::call]
847 impl<T: Config<I>, I: 'static> Pallet<T, I> {
848 #[pallet::call_index(0)]
858 #[pallet::weight(T::WeightInfo::bid())]
859 pub fn bid(origin: OriginFor<T>, value: BalanceOf<T, I>) -> DispatchResult {
860 let who = ensure_signed(origin)?;
861
862 let mut bids = Bids::<T, I>::get();
863 ensure!(!Self::has_bid(&bids, &who), Error::<T, I>::AlreadyBid);
864 ensure!(!Candidates::<T, I>::contains_key(&who), Error::<T, I>::AlreadyCandidate);
865 ensure!(!Members::<T, I>::contains_key(&who), Error::<T, I>::AlreadyMember);
866 ensure!(!SuspendedMembers::<T, I>::contains_key(&who), Error::<T, I>::Suspended);
867
868 let params = Parameters::<T, I>::get().ok_or(Error::<T, I>::NotGroup)?;
869 let deposit = params.candidate_deposit;
870 T::Currency::reserve(&who, deposit)?;
872 Self::insert_bid(&mut bids, &who, value, BidKind::Deposit(deposit));
873
874 Bids::<T, I>::put(bids);
875 Self::deposit_event(Event::<T, I>::Bid { candidate_id: who, offer: value });
876 Ok(())
877 }
878
879 #[pallet::call_index(1)]
887 #[pallet::weight(T::WeightInfo::unbid())]
888 pub fn unbid(origin: OriginFor<T>) -> DispatchResult {
889 let who = ensure_signed(origin)?;
890
891 let mut bids = Bids::<T, I>::get();
892 let pos = bids.iter().position(|bid| bid.who == who).ok_or(Error::<T, I>::NotBidder)?;
893 Self::clean_bid(&bids.remove(pos));
894 Bids::<T, I>::put(bids);
895 Self::deposit_event(Event::<T, I>::Unbid { candidate: who });
896 Ok(())
897 }
898
899 #[pallet::call_index(2)]
917 #[pallet::weight(T::WeightInfo::vouch())]
918 pub fn vouch(
919 origin: OriginFor<T>,
920 who: AccountIdLookupOf<T>,
921 value: BalanceOf<T, I>,
922 tip: BalanceOf<T, I>,
923 ) -> DispatchResult {
924 let voucher = ensure_signed(origin)?;
925 let who = T::Lookup::lookup(who)?;
926
927 let mut bids = Bids::<T, I>::get();
929 ensure!(!Self::has_bid(&bids, &who), Error::<T, I>::AlreadyBid);
930
931 ensure!(!Candidates::<T, I>::contains_key(&who), Error::<T, I>::AlreadyCandidate);
933 ensure!(!Members::<T, I>::contains_key(&who), Error::<T, I>::AlreadyMember);
934 ensure!(!SuspendedMembers::<T, I>::contains_key(&who), Error::<T, I>::Suspended);
935
936 let mut record = Members::<T, I>::get(&voucher).ok_or(Error::<T, I>::NotMember)?;
938 ensure!(record.vouching.is_none(), Error::<T, I>::AlreadyVouching);
939
940 record.vouching = Some(VouchingStatus::Vouching);
942 Self::insert_bid(&mut bids, &who, value, BidKind::Vouch(voucher.clone(), tip));
944
945 Members::<T, I>::insert(&voucher, &record);
947 Bids::<T, I>::put(bids);
948 Self::deposit_event(Event::<T, I>::Vouch {
949 candidate_id: who,
950 offer: value,
951 vouching: voucher,
952 });
953 Ok(())
954 }
955
956 #[pallet::call_index(3)]
964 #[pallet::weight(T::WeightInfo::unvouch())]
965 pub fn unvouch(origin: OriginFor<T>) -> DispatchResult {
966 let voucher = ensure_signed(origin)?;
967
968 let mut bids = Bids::<T, I>::get();
969 let pos = bids
970 .iter()
971 .position(|bid| bid.kind.is_vouch(&voucher))
972 .ok_or(Error::<T, I>::NotVouchingOnBidder)?;
973 let bid = bids.remove(pos);
974 Self::clean_bid(&bid);
975
976 Bids::<T, I>::put(bids);
977 Self::deposit_event(Event::<T, I>::Unvouch { candidate: bid.who });
978 Ok(())
979 }
980
981 #[pallet::call_index(4)]
990 #[pallet::weight(T::WeightInfo::vote())]
991 pub fn vote(
992 origin: OriginFor<T>,
993 candidate: AccountIdLookupOf<T>,
994 approve: bool,
995 ) -> DispatchResultWithPostInfo {
996 let voter = ensure_signed(origin)?;
997 let candidate = T::Lookup::lookup(candidate)?;
998
999 let mut candidacy =
1000 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1001 let record = Members::<T, I>::get(&voter).ok_or(Error::<T, I>::NotMember)?;
1002
1003 let first_time = Votes::<T, I>::mutate(&candidate, &voter, |v| {
1004 let first_time = v.is_none();
1005 *v = Some(Self::do_vote(*v, approve, record.rank, &mut candidacy.tally));
1006 first_time
1007 });
1008
1009 Candidates::<T, I>::insert(&candidate, &candidacy);
1010 Self::deposit_event(Event::<T, I>::Vote { candidate, voter, vote: approve });
1011 Ok(if first_time { Pays::No } else { Pays::Yes }.into())
1012 }
1013
1014 #[pallet::call_index(5)]
1022 #[pallet::weight(T::WeightInfo::defender_vote())]
1023 pub fn defender_vote(origin: OriginFor<T>, approve: bool) -> DispatchResultWithPostInfo {
1024 let voter = ensure_signed(origin)?;
1025
1026 let mut defending = Defending::<T, I>::get().ok_or(Error::<T, I>::NoDefender)?;
1027 let record = Members::<T, I>::get(&voter).ok_or(Error::<T, I>::NotMember)?;
1028
1029 let round = ChallengeRoundCount::<T, I>::get();
1030 let first_time = DefenderVotes::<T, I>::mutate(round, &voter, |v| {
1031 let first_time = v.is_none();
1032 *v = Some(Self::do_vote(*v, approve, record.rank, &mut defending.2));
1033 first_time
1034 });
1035
1036 Defending::<T, I>::put(defending);
1037 Self::deposit_event(Event::<T, I>::DefenderVote { voter, vote: approve });
1038 Ok(if first_time { Pays::No } else { Pays::Yes }.into())
1039 }
1040
1041 #[pallet::call_index(6)]
1052 #[pallet::weight(T::WeightInfo::payout())]
1053 pub fn payout(origin: OriginFor<T>) -> DispatchResult {
1054 let who = ensure_signed(origin)?;
1055 ensure!(
1056 Members::<T, I>::get(&who).ok_or(Error::<T, I>::NotMember)?.rank == 0,
1057 Error::<T, I>::NoPayout
1058 );
1059 let mut record = Payouts::<T, I>::get(&who);
1060 let block_number = T::BlockNumberProvider::current_block_number();
1061 if let Some((when, amount)) = record.payouts.first() {
1062 if when <= &block_number {
1063 record.paid = record.paid.checked_add(amount).ok_or(Overflow)?;
1064 T::Currency::transfer(&Self::payouts(), &who, *amount, AllowDeath)?;
1065 record.payouts.remove(0);
1066 Payouts::<T, I>::insert(&who, record);
1067 return Ok(());
1068 }
1069 }
1070 Err(Error::<T, I>::NoPayout)?
1071 }
1072
1073 #[pallet::call_index(7)]
1076 #[pallet::weight(T::WeightInfo::waive_repay())]
1077 pub fn waive_repay(origin: OriginFor<T>, amount: BalanceOf<T, I>) -> DispatchResult {
1078 let who = ensure_signed(origin)?;
1079 let mut record = Members::<T, I>::get(&who).ok_or(Error::<T, I>::NotMember)?;
1080 let mut payout_record = Payouts::<T, I>::get(&who);
1081 ensure!(record.rank == 0, Error::<T, I>::AlreadyElevated);
1082 ensure!(amount >= payout_record.paid, Error::<T, I>::InsufficientFunds);
1083
1084 T::Currency::transfer(&who, &Self::account_id(), payout_record.paid, AllowDeath)?;
1085 payout_record.paid = Zero::zero();
1086 payout_record.payouts.clear();
1087 record.rank = 1;
1088 Members::<T, I>::insert(&who, record);
1089 Payouts::<T, I>::insert(&who, payout_record);
1090 Self::deposit_event(Event::<T, I>::Elevated { member: who, rank: 1 });
1091
1092 Ok(())
1093 }
1094
1095 #[pallet::call_index(8)]
1113 #[pallet::weight(T::WeightInfo::found_society())]
1114 pub fn found_society(
1115 origin: OriginFor<T>,
1116 founder: AccountIdLookupOf<T>,
1117 max_members: u32,
1118 max_intake: u32,
1119 max_strikes: u32,
1120 candidate_deposit: BalanceOf<T, I>,
1121 rules: Vec<u8>,
1122 ) -> DispatchResult {
1123 T::FounderSetOrigin::ensure_origin(origin)?;
1124 let founder = T::Lookup::lookup(founder)?;
1125 ensure!(!Head::<T, I>::exists(), Error::<T, I>::AlreadyFounded);
1126 ensure!(max_members > 1, Error::<T, I>::MaxMembers);
1127 let params = GroupParams { max_members, max_intake, max_strikes, candidate_deposit };
1129 Parameters::<T, I>::put(params);
1130 Self::insert_member(&founder, 1)?;
1131 Head::<T, I>::put(&founder);
1132 Founder::<T, I>::put(&founder);
1133 Rules::<T, I>::put(T::Hashing::hash(&rules));
1134 Self::deposit_event(Event::<T, I>::Founded { founder });
1135 Ok(())
1136 }
1137
1138 #[pallet::call_index(9)]
1144 #[pallet::weight(T::WeightInfo::dissolve())]
1145 pub fn dissolve(origin: OriginFor<T>) -> DispatchResult {
1146 let founder = ensure_signed(origin)?;
1147 ensure!(Founder::<T, I>::get().as_ref() == Some(&founder), Error::<T, I>::NotFounder);
1148 ensure!(MemberCount::<T, I>::get() == 1, Error::<T, I>::NotHead);
1149
1150 let _ = Members::<T, I>::clear(u32::MAX, None);
1151 MemberCount::<T, I>::kill();
1152 let _ = MemberByIndex::<T, I>::clear(u32::MAX, None);
1153 let _ = SuspendedMembers::<T, I>::clear(u32::MAX, None);
1154 let _ = Payouts::<T, I>::clear(u32::MAX, None);
1155 let _ = Votes::<T, I>::clear(u32::MAX, None);
1156 let _ = VoteClearCursor::<T, I>::clear(u32::MAX, None);
1157 Head::<T, I>::kill();
1158 NextHead::<T, I>::kill();
1159 Founder::<T, I>::kill();
1160 Rules::<T, I>::kill();
1161 Parameters::<T, I>::kill();
1162 Pot::<T, I>::kill();
1163 RoundCount::<T, I>::kill();
1164 Bids::<T, I>::kill();
1165 Skeptic::<T, I>::kill();
1166 ChallengeRoundCount::<T, I>::kill();
1167 Defending::<T, I>::kill();
1168 let _ = DefenderVotes::<T, I>::clear(u32::MAX, None);
1169 let _ = Candidates::<T, I>::clear(u32::MAX, None);
1170 Self::deposit_event(Event::<T, I>::Unfounded { founder });
1171 Ok(())
1172 }
1173
1174 #[pallet::call_index(10)]
1189 #[pallet::weight(T::WeightInfo::judge_suspended_member())]
1190 pub fn judge_suspended_member(
1191 origin: OriginFor<T>,
1192 who: AccountIdLookupOf<T>,
1193 forgive: bool,
1194 ) -> DispatchResultWithPostInfo {
1195 ensure!(
1196 Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1197 Error::<T, I>::NotFounder
1198 );
1199 let who = T::Lookup::lookup(who)?;
1200 let record = SuspendedMembers::<T, I>::get(&who).ok_or(Error::<T, I>::NotSuspended)?;
1201 if forgive {
1202 Self::reinstate_member(&who, record.rank)?;
1204 } else {
1205 let payout_record = Payouts::<T, I>::take(&who);
1206 let total = payout_record
1207 .payouts
1208 .into_iter()
1209 .map(|x| x.1)
1210 .fold(Zero::zero(), |acc: BalanceOf<T, I>, x| acc.saturating_add(x));
1211 Self::unreserve_payout(total);
1212 }
1213 SuspendedMembers::<T, I>::remove(&who);
1214 Self::deposit_event(Event::<T, I>::SuspendedMemberJudgement { who, judged: forgive });
1215 Ok(Pays::No.into())
1216 }
1217
1218 #[pallet::call_index(11)]
1231 #[pallet::weight(T::WeightInfo::set_parameters())]
1232 pub fn set_parameters(
1233 origin: OriginFor<T>,
1234 max_members: u32,
1235 max_intake: u32,
1236 max_strikes: u32,
1237 candidate_deposit: BalanceOf<T, I>,
1238 ) -> DispatchResult {
1239 ensure!(
1240 Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1241 Error::<T, I>::NotFounder
1242 );
1243 ensure!(max_members >= MemberCount::<T, I>::get(), Error::<T, I>::MaxMembers);
1244 let params = GroupParams { max_members, max_intake, max_strikes, candidate_deposit };
1245 Parameters::<T, I>::put(¶ms);
1246 Self::deposit_event(Event::<T, I>::NewParams { params });
1247 Ok(())
1248 }
1249
1250 #[pallet::call_index(12)]
1253 #[pallet::weight(T::WeightInfo::punish_skeptic())]
1254 pub fn punish_skeptic(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1255 let candidate = ensure_signed(origin)?;
1256 let mut candidacy =
1257 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1258 ensure!(!candidacy.skeptic_struck, Error::<T, I>::AlreadyPunished);
1259 ensure!(!Self::in_progress(candidacy.round), Error::<T, I>::InProgress);
1260 let punished = Self::check_skeptic(&candidate, &mut candidacy);
1261 Candidates::<T, I>::insert(&candidate, candidacy);
1262 Ok(if punished { Pays::No } else { Pays::Yes }.into())
1263 }
1264
1265 #[pallet::call_index(13)]
1268 #[pallet::weight(T::WeightInfo::claim_membership())]
1269 pub fn claim_membership(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1270 let candidate = ensure_signed(origin)?;
1271 let candidacy =
1272 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1273 ensure!(candidacy.tally.clear_approval(), Error::<T, I>::NotApproved);
1274 ensure!(!Self::in_progress(candidacy.round), Error::<T, I>::InProgress);
1275 Self::induct_member(candidate, candidacy, 0)?;
1276 Ok(Pays::No.into())
1277 }
1278
1279 #[pallet::call_index(14)]
1283 #[pallet::weight(T::WeightInfo::bestow_membership())]
1284 pub fn bestow_membership(
1285 origin: OriginFor<T>,
1286 candidate: T::AccountId,
1287 ) -> DispatchResultWithPostInfo {
1288 ensure!(
1289 Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1290 Error::<T, I>::NotFounder
1291 );
1292 let candidacy =
1293 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1294 ensure!(!candidacy.tally.clear_rejection(), Error::<T, I>::Rejected);
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(15)]
1306 #[pallet::weight(T::WeightInfo::kick_candidate())]
1307 pub fn kick_candidate(
1308 origin: OriginFor<T>,
1309 candidate: T::AccountId,
1310 ) -> DispatchResultWithPostInfo {
1311 ensure!(
1312 Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1313 Error::<T, I>::NotFounder
1314 );
1315 let mut candidacy =
1316 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1317 ensure!(!Self::in_progress(candidacy.round), Error::<T, I>::InProgress);
1318 ensure!(!candidacy.tally.clear_approval(), Error::<T, I>::Approved);
1319 Self::check_skeptic(&candidate, &mut candidacy);
1320 Self::reject_candidate(&candidate, &candidacy.kind);
1321 Candidates::<T, I>::remove(&candidate);
1322 Ok(Pays::No.into())
1323 }
1324
1325 #[pallet::call_index(16)]
1329 #[pallet::weight(T::WeightInfo::resign_candidacy())]
1330 pub fn resign_candidacy(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1331 let candidate = ensure_signed(origin)?;
1332 let mut candidacy =
1333 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1334 if !Self::in_progress(candidacy.round) {
1335 Self::check_skeptic(&candidate, &mut candidacy);
1336 }
1337 Self::reject_candidate(&candidate, &candidacy.kind);
1338 Candidates::<T, I>::remove(&candidate);
1339 Ok(Pays::No.into())
1340 }
1341
1342 #[pallet::call_index(17)]
1348 #[pallet::weight(T::WeightInfo::drop_candidate())]
1349 pub fn drop_candidate(
1350 origin: OriginFor<T>,
1351 candidate: T::AccountId,
1352 ) -> DispatchResultWithPostInfo {
1353 ensure_signed(origin)?;
1354 let candidacy =
1355 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1356 ensure!(candidacy.tally.clear_rejection(), Error::<T, I>::NotRejected);
1357 ensure!(RoundCount::<T, I>::get() > candidacy.round + 1, Error::<T, I>::TooEarly);
1358 Self::reject_candidate(&candidate, &candidacy.kind);
1359 Candidates::<T, I>::remove(&candidate);
1360 Ok(Pays::No.into())
1361 }
1362
1363 #[pallet::call_index(18)]
1367 #[pallet::weight(T::WeightInfo::cleanup_candidacy())]
1368 pub fn cleanup_candidacy(
1369 origin: OriginFor<T>,
1370 candidate: T::AccountId,
1371 max: u32,
1372 ) -> DispatchResultWithPostInfo {
1373 ensure_signed(origin)?;
1374 ensure!(!Candidates::<T, I>::contains_key(&candidate), Error::<T, I>::InProgress);
1375 let maybe_cursor = VoteClearCursor::<T, I>::get(&candidate);
1376 let r =
1377 Votes::<T, I>::clear_prefix(&candidate, max, maybe_cursor.as_ref().map(|x| &x[..]));
1378 if let Some(cursor) = r.maybe_cursor {
1379 VoteClearCursor::<T, I>::insert(&candidate, BoundedVec::truncate_from(cursor));
1380 }
1381 Ok(if r.loops == 0 { Pays::Yes } else { Pays::No }.into())
1382 }
1383
1384 #[pallet::call_index(19)]
1388 #[pallet::weight(T::WeightInfo::cleanup_challenge())]
1389 pub fn cleanup_challenge(
1390 origin: OriginFor<T>,
1391 challenge_round: RoundIndex,
1392 max: u32,
1393 ) -> DispatchResultWithPostInfo {
1394 ensure_signed(origin)?;
1395 ensure!(
1396 challenge_round < ChallengeRoundCount::<T, I>::get(),
1397 Error::<T, I>::InProgress
1398 );
1399 let _ = DefenderVotes::<T, I>::clear_prefix(challenge_round, max, None);
1400 Ok(Pays::No.into())
1404 }
1405
1406 #[pallet::call_index(20)]
1414 #[pallet::weight(T::WeightInfo::poke_deposit())]
1415 pub fn poke_deposit(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1416 let who = ensure_signed(origin)?;
1417
1418 let mut bids = Bids::<T, I>::get();
1420 let bid = bids.iter_mut().find(|bid| bid.who == who).ok_or(Error::<T, I>::NotBidder)?;
1421
1422 let old_deposit = match &bid.kind {
1424 BidKind::Deposit(amount) => *amount,
1425 _ => return Err(Error::<T, I>::NoDeposit.into()),
1426 };
1427
1428 let params = Parameters::<T, I>::get().ok_or(Error::<T, I>::NotGroup)?;
1429 let new_deposit = params.candidate_deposit;
1430
1431 if old_deposit == new_deposit {
1432 return Ok(Pays::Yes.into());
1433 }
1434
1435 if new_deposit > old_deposit {
1436 let extra = new_deposit.saturating_sub(old_deposit);
1438 T::Currency::reserve(&who, extra)?;
1439 } else {
1440 let excess = old_deposit.saturating_sub(new_deposit);
1442 let remaining_unreserved = T::Currency::unreserve(&who, excess);
1443 if !remaining_unreserved.is_zero() {
1444 defensive!(
1445 "Failed to unreserve for full amount for bid (Requested, Actual)",
1446 (excess, excess.saturating_sub(remaining_unreserved))
1447 );
1448 }
1449 }
1450
1451 bid.kind = BidKind::Deposit(new_deposit);
1452 Bids::<T, I>::put(bids);
1453
1454 Self::deposit_event(Event::<T, I>::DepositPoked {
1455 who: who.clone(),
1456 old_deposit,
1457 new_deposit,
1458 });
1459
1460 Ok(Pays::No.into())
1461 }
1462
1463 #[pallet::call_index(21)]
1471 #[pallet::weight(T::WeightInfo::kick_member())]
1472 pub fn kick_member(origin: OriginFor<T>, who: AccountIdLookupOf<T>) -> DispatchResult {
1473 ensure!(
1474 Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1475 Error::<T, I>::NotFounder
1476 );
1477 let who = T::Lookup::lookup(who)?;
1478
1479 let _ = Self::remove_member(&who)?;
1480
1481 let payout_record = Payouts::<T, I>::take(&who);
1482 let total = payout_record
1483 .payouts
1484 .into_iter()
1485 .fold(Zero::zero(), |acc: BalanceOf<T, I>, x| acc.saturating_add(x.1));
1486 Self::unreserve_payout(total);
1487
1488 Self::deposit_event(Event::<T, I>::MemberKicked { member: who });
1489 Ok(())
1490 }
1491 }
1492}
1493
1494pub struct EnsureFounder<T>(core::marker::PhantomData<T>);
1496impl<T: Config> EnsureOrigin<<T as frame_system::Config>::RuntimeOrigin> for EnsureFounder<T> {
1497 type Success = T::AccountId;
1498 fn try_origin(o: T::RuntimeOrigin) -> Result<Self::Success, T::RuntimeOrigin> {
1499 match (o.as_signer(), Founder::<T>::get()) {
1500 (Some(who), Some(f)) if *who == f => Ok(f),
1501 _ => Err(o),
1502 }
1503 }
1504
1505 #[cfg(feature = "runtime-benchmarks")]
1506 fn try_successful_origin() -> Result<T::RuntimeOrigin, ()> {
1507 let founder = Founder::<T>::get().ok_or(())?;
1508 Ok(T::RuntimeOrigin::from(frame_system::RawOrigin::Signed(founder)))
1509 }
1510}
1511
1512impl_ensure_origin_with_arg_ignoring_arg! {
1513 impl<{ T: Config, A }>
1514 EnsureOriginWithArg<T::RuntimeOrigin, A> for EnsureFounder<T>
1515 {}
1516}
1517
1518#[derive(Debug, PartialEq, Eq)]
1519pub enum Period<BlockNumber> {
1520 Voting { elapsed: BlockNumber, more: BlockNumber },
1521 Claim { elapsed: BlockNumber, more: BlockNumber },
1522 Intake { elapsed: BlockNumber },
1523}
1524
1525impl<T: Config<I>, I: 'static> Pallet<T, I> {
1526 fn period() -> Period<BlockNumberFor<T, I>> {
1528 let claim_period = T::ClaimPeriod::get();
1529 let voting_period = T::VotingPeriod::get();
1530 let rotation_period = voting_period + claim_period;
1531 let now = T::BlockNumberProvider::current_block_number();
1532 let phase = now % rotation_period;
1533 if now >= Self::next_intake_at() {
1534 Period::Intake { elapsed: now - Self::next_intake_at() }
1535 } else if phase < voting_period {
1536 Period::Voting { elapsed: phase, more: voting_period - phase }
1537 } else {
1538 Period::Claim { elapsed: phase - voting_period, more: rotation_period - phase }
1539 }
1540 }
1541
1542 pub fn next_intake_at() -> BlockNumberFor<T, I> {
1546 match NextIntakeAt::<T, I>::get() {
1547 Some(next) => next,
1548 None => {
1549 let now = T::BlockNumberProvider::current_block_number();
1551 let prev_block = now.saturating_sub(BlockNumberFor::<T, I>::one());
1552 let rotation_period = T::VotingPeriod::get().saturating_add(T::ClaimPeriod::get());
1553 let elapsed = prev_block % rotation_period;
1554 let next_intake_at = prev_block + (rotation_period - elapsed);
1555 NextIntakeAt::<T, I>::put(next_intake_at);
1556 next_intake_at
1557 },
1558 }
1559 }
1560
1561 fn set_next_intake_at() {
1565 let prev_next_intake_at = Self::next_intake_at();
1566 let next_intake_at = prev_next_intake_at
1567 .saturating_add(T::VotingPeriod::get().saturating_add(T::ClaimPeriod::get()));
1568 NextIntakeAt::<T, I>::put(next_intake_at);
1569 }
1570
1571 pub fn next_challenge_at() -> BlockNumberFor<T, I> {
1575 match NextChallengeAt::<T, I>::get() {
1576 Some(next) => next,
1577 None => {
1578 let now = T::BlockNumberProvider::current_block_number();
1580 let prev_block = now.saturating_sub(BlockNumberFor::<T, I>::one());
1581 let challenge_period = T::ChallengePeriod::get();
1582 let elapsed = prev_block % challenge_period;
1583 let next_challenge_at = prev_block + (challenge_period - elapsed);
1584 NextChallengeAt::<T, I>::put(next_challenge_at);
1585 next_challenge_at
1586 },
1587 }
1588 }
1589
1590 fn set_next_challenge_at() {
1594 let prev_next_challenge_at = Self::next_challenge_at();
1595 let next_challenge_at = prev_next_challenge_at.saturating_add(T::ChallengePeriod::get());
1596 NextChallengeAt::<T, I>::put(next_challenge_at);
1597 }
1598
1599 fn in_progress(target_round: RoundIndex) -> bool {
1601 let round = RoundCount::<T, I>::get();
1602 target_round == round && matches!(Self::period(), Period::Voting { .. })
1603 }
1604
1605 fn do_vote(maybe_old: Option<Vote>, approve: bool, rank: Rank, tally: &mut Tally) -> Vote {
1607 match maybe_old {
1608 Some(Vote { approve: true, weight }) => tally.approvals.saturating_reduce(weight),
1609 Some(Vote { approve: false, weight }) => tally.rejections.saturating_reduce(weight),
1610 _ => {},
1611 }
1612 let weight_root = rank + 1;
1613 let weight = weight_root * weight_root;
1614 match approve {
1615 true => tally.approvals.saturating_accrue(weight),
1616 false => tally.rejections.saturating_accrue(weight),
1617 }
1618 Vote { approve, weight }
1619 }
1620
1621 fn check_skeptic(
1623 candidate: &T::AccountId,
1624 candidacy: &mut Candidacy<T::AccountId, BalanceOf<T, I>>,
1625 ) -> bool {
1626 if RoundCount::<T, I>::get() != candidacy.round || candidacy.skeptic_struck {
1627 return false;
1628 }
1629 let skeptic = match Skeptic::<T, I>::get() {
1631 Some(s) => s,
1632 None => return false,
1633 };
1634 let maybe_vote = Votes::<T, I>::get(&candidate, &skeptic);
1635 let approved = candidacy.tally.clear_approval();
1636 let rejected = candidacy.tally.clear_rejection();
1637 match (maybe_vote, approved, rejected) {
1638 (None, _, _) |
1639 (Some(Vote { approve: true, .. }), false, true) |
1640 (Some(Vote { approve: false, .. }), true, false) => {
1641 if Self::strike_member(&skeptic).is_ok() {
1643 candidacy.skeptic_struck = true;
1644 true
1645 } else {
1646 false
1647 }
1648 },
1649 _ => false,
1650 }
1651 }
1652
1653 fn rotate_challenge(rng: &mut impl RngCore) {
1655 let mut next_defender = None;
1656 let mut round = ChallengeRoundCount::<T, I>::get();
1657
1658 if let Some((defender, skeptic, tally)) = Defending::<T, I>::get() {
1660 if !tally.more_approvals() {
1662 let _ = Self::suspend_member(&defender);
1665 }
1666
1667 let skeptic_vote = DefenderVotes::<T, I>::get(round, &skeptic);
1669 match (skeptic_vote, tally.more_approvals(), tally.more_rejections()) {
1670 (None, _, _) |
1671 (Some(Vote { approve: true, .. }), false, true) |
1672 (Some(Vote { approve: false, .. }), true, false) => {
1673 let _ = Self::strike_member(&skeptic);
1675 let founder = Founder::<T, I>::get();
1676 let head = Head::<T, I>::get();
1677 if Some(&skeptic) != founder.as_ref() && Some(&skeptic) != head.as_ref() {
1678 next_defender = Some(skeptic);
1679 }
1680 },
1681 _ => {},
1682 }
1683 round.saturating_inc();
1684 ChallengeRoundCount::<T, I>::put(round);
1685 }
1686
1687 if MemberCount::<T, I>::get() > 2 {
1690 let defender = next_defender
1691 .or_else(|| Self::pick_defendant(rng))
1692 .expect("exited if members empty; qed");
1693 let skeptic =
1694 Self::pick_member_except(rng, &defender).expect("exited if members empty; qed");
1695 Self::deposit_event(Event::<T, I>::Challenged { member: defender.clone() });
1696 Defending::<T, I>::put((defender, skeptic, Tally::default()));
1697 } else {
1698 Defending::<T, I>::kill();
1699 }
1700 }
1701
1702 fn rotate_intake(rng: &mut impl RngCore) {
1709 let member_count = MemberCount::<T, I>::get();
1711 if member_count < 1 {
1712 return;
1713 }
1714 let maybe_head = NextHead::<T, I>::take();
1715 if let Some(head) = maybe_head {
1716 Head::<T, I>::put(&head.who);
1717 }
1718
1719 let mut pot = Pot::<T, I>::get();
1722 let unaccounted = T::Currency::free_balance(&Self::account_id()).saturating_sub(pot);
1723 pot.saturating_accrue(T::PeriodSpend::get().min(unaccounted / 2u8.into()));
1724 Pot::<T, I>::put(&pot);
1725
1726 let mut round_count = RoundCount::<T, I>::get();
1728 round_count.saturating_inc();
1729 let candidate_count = Self::select_new_candidates(round_count, member_count, pot);
1730 if candidate_count > 0 {
1731 let skeptic = Self::pick_member(rng).expect("exited if members empty; qed");
1733 Skeptic::<T, I>::put(skeptic);
1734 }
1735 RoundCount::<T, I>::put(round_count);
1736 }
1737
1738 pub fn select_new_candidates(
1746 round: RoundIndex,
1747 member_count: u32,
1748 pot: BalanceOf<T, I>,
1749 ) -> u32 {
1750 let mut bids = Bids::<T, I>::get();
1752 let params = match Parameters::<T, I>::get() {
1753 Some(params) => params,
1754 None => return 0,
1755 };
1756 let max_selections: u32 = params
1757 .max_intake
1758 .min(params.max_members.saturating_sub(member_count))
1759 .min(bids.len() as u32);
1760
1761 let mut selections = 0;
1762 let mut total_cost: BalanceOf<T, I> = Zero::zero();
1764
1765 bids.retain(|bid| {
1766 total_cost.saturating_accrue(bid.value);
1768 let accept = selections < max_selections &&
1769 (!bid.value.is_zero() || selections == 0) &&
1770 total_cost <= pot;
1771 if accept {
1772 let candidacy = Candidacy {
1773 round,
1774 kind: bid.kind.clone(),
1775 bid: bid.value,
1776 tally: Default::default(),
1777 skeptic_struck: false,
1778 };
1779 Candidates::<T, I>::insert(&bid.who, candidacy);
1780 selections.saturating_inc();
1781 }
1782 !accept
1783 });
1784
1785 Bids::<T, I>::put(&bids);
1787 selections
1788 }
1789
1790 fn insert_bid(
1793 bids: &mut BoundedVec<Bid<T::AccountId, BalanceOf<T, I>>, T::MaxBids>,
1794 who: &T::AccountId,
1795 value: BalanceOf<T, I>,
1796 bid_kind: BidKind<T::AccountId, BalanceOf<T, I>>,
1797 ) {
1798 let pos = bids.iter().position(|bid| bid.value > value).unwrap_or(bids.len());
1799 let r = bids.force_insert_keep_left(pos, Bid { value, who: who.clone(), kind: bid_kind });
1800 let maybe_discarded = match r {
1801 Ok(x) => x,
1802 Err(x) => Some(x),
1803 };
1804 if let Some(discarded) = maybe_discarded {
1805 Self::clean_bid(&discarded);
1806 Self::deposit_event(Event::<T, I>::AutoUnbid { candidate: discarded.who });
1807 }
1808 }
1809
1810 fn clean_bid(bid: &Bid<T::AccountId, BalanceOf<T, I>>) {
1818 match &bid.kind {
1819 BidKind::Deposit(deposit) => {
1820 let err_amount = T::Currency::unreserve(&bid.who, *deposit);
1821 debug_assert!(err_amount.is_zero());
1822 },
1823 BidKind::Vouch(voucher, _) => {
1824 Members::<T, I>::mutate_extant(voucher, |record| record.vouching = None);
1825 },
1826 }
1827 }
1828
1829 fn reject_candidate(who: &T::AccountId, kind: &BidKind<T::AccountId, BalanceOf<T, I>>) {
1837 match kind {
1838 BidKind::Deposit(deposit) => {
1839 let pot = Self::account_id();
1840 let free = BalanceStatus::Free;
1841 let r = T::Currency::repatriate_reserved(&who, &pot, *deposit, free);
1842 debug_assert!(r.is_ok());
1843 },
1844 BidKind::Vouch(voucher, _) => {
1845 Members::<T, I>::mutate_extant(voucher, |record| {
1846 record.vouching = Some(VouchingStatus::Banned)
1847 });
1848 },
1849 }
1850 }
1851
1852 fn has_bid(bids: &Vec<Bid<T::AccountId, BalanceOf<T, I>>>, who: &T::AccountId) -> bool {
1854 bids.iter().any(|bid| bid.who == *who)
1856 }
1857
1858 fn insert_member(who: &T::AccountId, rank: Rank) -> DispatchResult {
1868 let params = Parameters::<T, I>::get().ok_or(Error::<T, I>::NotGroup)?;
1869 ensure!(MemberCount::<T, I>::get() < params.max_members, Error::<T, I>::MaxMembers);
1870 let index = MemberCount::<T, I>::mutate(|i| {
1871 i.saturating_accrue(1);
1872 *i - 1
1873 });
1874 let record = MemberRecord { rank, strikes: 0, vouching: None, index };
1875 Members::<T, I>::insert(who, record);
1876 MemberByIndex::<T, I>::insert(index, who);
1877 Ok(())
1878 }
1879
1880 fn reinstate_member(who: &T::AccountId, rank: Rank) -> DispatchResult {
1887 Self::insert_member(who, rank)
1888 }
1889
1890 fn add_new_member(who: &T::AccountId, rank: Rank) -> DispatchResult {
1893 Self::insert_member(who, rank)
1894 }
1895
1896 fn induct_member(
1898 candidate: T::AccountId,
1899 mut candidacy: Candidacy<T::AccountId, BalanceOf<T, I>>,
1900 rank: Rank,
1901 ) -> DispatchResult {
1902 Self::add_new_member(&candidate, rank)?;
1903 Self::check_skeptic(&candidate, &mut candidacy);
1904
1905 let next_head = NextHead::<T, I>::get()
1906 .filter(|old| {
1907 old.round > candidacy.round ||
1908 old.round == candidacy.round && old.bid < candidacy.bid
1909 })
1910 .unwrap_or_else(|| IntakeRecord {
1911 who: candidate.clone(),
1912 bid: candidacy.bid,
1913 round: candidacy.round,
1914 });
1915 NextHead::<T, I>::put(next_head);
1916
1917 let now = T::BlockNumberProvider::current_block_number();
1918 let maturity = now + Self::lock_duration(MemberCount::<T, I>::get());
1919 Self::reward_bidder(&candidate, candidacy.bid, candidacy.kind, maturity);
1920
1921 Candidates::<T, I>::remove(&candidate);
1922 Ok(())
1923 }
1924
1925 fn strike_member(who: &T::AccountId) -> DispatchResult {
1926 let mut record = Members::<T, I>::get(who).ok_or(Error::<T, I>::NotMember)?;
1927 record.strikes.saturating_inc();
1928 Members::<T, I>::insert(who, &record);
1929 if record.strikes >= T::GraceStrikes::get() {
1933 let total_payout = Payouts::<T, I>::get(who)
1935 .payouts
1936 .iter()
1937 .fold(BalanceOf::<T, I>::zero(), |acc, x| acc.saturating_add(x.1));
1938 Self::slash_payout(who, total_payout / 2u32.into());
1939 }
1940
1941 let params = Parameters::<T, I>::get().ok_or(Error::<T, I>::NotGroup)?;
1942 if record.strikes >= params.max_strikes {
1943 let _ = Self::suspend_member(who);
1945 }
1946 Ok(())
1947 }
1948
1949 pub fn remove_member(m: &T::AccountId) -> Result<MemberRecord, DispatchError> {
1960 ensure!(Head::<T, I>::get().as_ref() != Some(m), Error::<T, I>::Head);
1961 ensure!(Founder::<T, I>::get().as_ref() != Some(m), Error::<T, I>::Founder);
1962 if let Some(mut record) = Members::<T, I>::get(m) {
1963 let index = record.index;
1964 let last_index = MemberCount::<T, I>::mutate(|i| {
1965 i.saturating_reduce(1);
1966 *i
1967 });
1968 if index != last_index {
1969 if let Some(other) = MemberByIndex::<T, I>::get(last_index) {
1972 MemberByIndex::<T, I>::insert(index, &other);
1973 Members::<T, I>::mutate(other, |m_r| {
1974 if let Some(r) = m_r {
1975 r.index = index
1976 }
1977 });
1978 } else {
1979 debug_assert!(false, "ERROR: No member at the last index position?");
1980 }
1981 }
1982
1983 MemberByIndex::<T, I>::remove(last_index);
1984 Members::<T, I>::remove(m);
1985 if record.vouching.take() == Some(VouchingStatus::Vouching) {
1987 Bids::<T, I>::mutate(|bids|
1990 if let Some(pos) = bids.iter().position(|b| b.kind.is_vouch(&m)) {
1992 let vouched = bids.remove(pos).who;
1994 Self::deposit_event(Event::<T, I>::Unvouch { candidate: vouched });
1995 }
1996 );
1997 }
1998 Ok(record)
1999 } else {
2000 Err(Error::<T, I>::NotMember.into())
2001 }
2002 }
2003
2004 fn suspend_member(who: &T::AccountId) -> DispatchResult {
2010 let record = Self::remove_member(&who)?;
2011 SuspendedMembers::<T, I>::insert(who, record);
2012 Self::deposit_event(Event::<T, I>::MemberSuspended { member: who.clone() });
2013 Ok(())
2014 }
2015
2016 fn pick_member(rng: &mut impl RngCore) -> Option<T::AccountId> {
2020 let member_count = MemberCount::<T, I>::get();
2021 if member_count == 0 {
2022 return None;
2023 }
2024 let random_index = rng.next_u32() % member_count;
2025 MemberByIndex::<T, I>::get(random_index)
2026 }
2027
2028 fn pick_member_except(
2033 rng: &mut impl RngCore,
2034 exception: &T::AccountId,
2035 ) -> Option<T::AccountId> {
2036 let member_count = MemberCount::<T, I>::get();
2037 if member_count <= 1 {
2038 return None;
2039 }
2040 let random_index = rng.next_u32() % (member_count - 1);
2041 let pick = MemberByIndex::<T, I>::get(random_index);
2042 if pick.as_ref() == Some(exception) {
2043 MemberByIndex::<T, I>::get(member_count - 1)
2044 } else {
2045 pick
2046 }
2047 }
2048
2049 fn pick_defendant(rng: &mut impl RngCore) -> Option<T::AccountId> {
2054 let member_count = MemberCount::<T, I>::get();
2055 if member_count <= 2 {
2056 return None;
2057 }
2058 let head = Head::<T, I>::get();
2062 let pickable_count = member_count - if head.is_some() { 2 } else { 1 };
2063 let random_index = rng.next_u32() % pickable_count + 1;
2064 let pick = MemberByIndex::<T, I>::get(random_index);
2065 if pick == head && head.is_some() {
2066 MemberByIndex::<T, I>::get(member_count - 1)
2069 } else {
2070 pick
2071 }
2072 }
2073
2074 fn reward_bidder(
2076 candidate: &T::AccountId,
2077 value: BalanceOf<T, I>,
2078 kind: BidKind<T::AccountId, BalanceOf<T, I>>,
2079 maturity: BlockNumberFor<T, I>,
2080 ) {
2081 let value = match kind {
2082 BidKind::Deposit(deposit) => {
2083 let err_amount = T::Currency::unreserve(candidate, deposit);
2086 debug_assert!(err_amount.is_zero());
2087 value
2088 },
2089 BidKind::Vouch(voucher, tip) => {
2090 if let Some(mut record) = Members::<T, I>::get(&voucher) {
2093 if let Some(VouchingStatus::Vouching) = record.vouching {
2094 record.vouching = None;
2097 Self::bump_payout(&voucher, maturity, tip.min(value));
2098 Members::<T, I>::insert(&voucher, record);
2099 value.saturating_sub(tip)
2100 } else {
2101 value
2102 }
2103 } else {
2104 value
2105 }
2106 },
2107 };
2108
2109 Self::bump_payout(candidate, maturity, value);
2110 }
2111
2112 fn bump_payout(who: &T::AccountId, when: BlockNumberFor<T, I>, value: BalanceOf<T, I>) {
2117 if value.is_zero() {
2118 return;
2119 }
2120 if let Some(MemberRecord { rank: 0, .. }) = Members::<T, I>::get(who) {
2121 Payouts::<T, I>::mutate(who, |record| {
2122 match record.payouts.binary_search_by_key(&when, |x| x.0) {
2124 Ok(index) => record.payouts[index].1.saturating_accrue(value),
2125 Err(index) => {
2126 let _ = record.payouts.try_insert(index, (when, value));
2128 },
2129 }
2130 });
2131 Self::reserve_payout(value);
2132 }
2133 }
2134
2135 fn slash_payout(who: &T::AccountId, value: BalanceOf<T, I>) -> BalanceOf<T, I> {
2137 let mut record = Payouts::<T, I>::get(who);
2138 let mut rest = value;
2139 while !record.payouts.is_empty() {
2140 if let Some(new_rest) = rest.checked_sub(&record.payouts[0].1) {
2141 rest = new_rest;
2143 record.payouts.remove(0);
2144 } else {
2145 record.payouts[0].1.saturating_reduce(rest);
2147 rest = Zero::zero();
2148 break;
2149 }
2150 }
2151 Payouts::<T, I>::insert(who, record);
2152 value - rest
2153 }
2154
2155 fn reserve_payout(amount: BalanceOf<T, I>) {
2158 Pot::<T, I>::mutate(|pot| pot.saturating_reduce(amount));
2160
2161 let res = T::Currency::transfer(&Self::account_id(), &Self::payouts(), amount, AllowDeath);
2164 debug_assert!(res.is_ok());
2165 }
2166
2167 fn unreserve_payout(amount: BalanceOf<T, I>) {
2170 Pot::<T, I>::mutate(|pot| pot.saturating_accrue(amount));
2172
2173 let res = T::Currency::transfer(&Self::payouts(), &Self::account_id(), amount, AllowDeath);
2176 debug_assert!(res.is_ok());
2177 }
2178
2179 pub fn account_id() -> T::AccountId {
2184 T::PalletId::get().into_account_truncating()
2185 }
2186
2187 pub fn payouts() -> T::AccountId {
2192 T::PalletId::get().into_sub_account_truncating(b"payouts")
2193 }
2194
2195 fn lock_duration(x: u32) -> BlockNumberFor<T, I> {
2200 let lock_pc = 100 - 50_000 / (x + 500);
2201 Percent::from_percent(lock_pc as u8) * T::MaxLockDuration::get()
2202 }
2203}
2204
2205impl<T: Config<I>, I: 'static> OnUnbalanced<NegativeImbalanceOf<T, I>> for Pallet<T, I> {
2206 fn on_nonzero_unbalanced(amount: NegativeImbalanceOf<T, I>) {
2207 let numeric_amount = amount.peek();
2208
2209 let _ = T::Currency::resolve_creating(&Self::account_id(), amount);
2211
2212 Self::deposit_event(Event::<T, I>::Deposit { value: numeric_amount });
2213 }
2214}