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 }
671
672 #[deprecated(note = "use `Event` instead")]
674 pub type RawEvent<T, I = ()> = Event<T, I>;
675
676 #[pallet::storage]
678 pub type Parameters<T: Config<I>, I: 'static = ()> =
679 StorageValue<_, GroupParamsFor<T, I>, OptionQuery>;
680
681 #[pallet::storage]
683 pub type Pot<T: Config<I>, I: 'static = ()> = StorageValue<_, BalanceOf<T, I>, ValueQuery>;
684
685 #[pallet::storage]
687 pub type Founder<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId>;
688
689 #[pallet::storage]
691 pub type Head<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId>;
692
693 #[pallet::storage]
696 pub type Rules<T: Config<I>, I: 'static = ()> = StorageValue<_, T::Hash>;
697
698 #[pallet::storage]
700 pub type Members<T: Config<I>, I: 'static = ()> =
701 StorageMap<_, Twox64Concat, T::AccountId, MemberRecord, OptionQuery>;
702
703 #[pallet::storage]
705 pub type Payouts<T: Config<I>, I: 'static = ()> =
706 StorageMap<_, Twox64Concat, T::AccountId, PayoutRecordFor<T, I>, ValueQuery>;
707
708 #[pallet::storage]
710 pub type MemberCount<T: Config<I>, I: 'static = ()> = StorageValue<_, u32, ValueQuery>;
711
712 #[pallet::storage]
715 pub type MemberByIndex<T: Config<I>, I: 'static = ()> =
716 StorageMap<_, Twox64Concat, u32, T::AccountId, OptionQuery>;
717
718 #[pallet::storage]
720 pub type SuspendedMembers<T: Config<I>, I: 'static = ()> =
721 StorageMap<_, Twox64Concat, T::AccountId, MemberRecord, OptionQuery>;
722
723 #[pallet::storage]
725 pub type RoundCount<T: Config<I>, I: 'static = ()> = StorageValue<_, RoundIndex, ValueQuery>;
726
727 #[pallet::storage]
729 pub type Bids<T: Config<I>, I: 'static = ()> =
730 StorageValue<_, BoundedVec<Bid<T::AccountId, BalanceOf<T, I>>, T::MaxBids>, ValueQuery>;
731
732 #[pallet::storage]
733 pub type Candidates<T: Config<I>, I: 'static = ()> = StorageMap<
734 _,
735 Blake2_128Concat,
736 T::AccountId,
737 Candidacy<T::AccountId, BalanceOf<T, I>>,
738 OptionQuery,
739 >;
740
741 #[pallet::storage]
743 pub type Skeptic<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId, OptionQuery>;
744
745 #[pallet::storage]
747 pub type Votes<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
748 _,
749 Twox64Concat,
750 T::AccountId,
751 Twox64Concat,
752 T::AccountId,
753 Vote,
754 OptionQuery,
755 >;
756
757 #[pallet::storage]
759 pub type VoteClearCursor<T: Config<I>, I: 'static = ()> =
760 StorageMap<_, Twox64Concat, T::AccountId, BoundedVec<u8, KeyLenOf<Votes<T, I>>>>;
761
762 #[pallet::storage]
766 pub type NextHead<T: Config<I>, I: 'static = ()> =
767 StorageValue<_, IntakeRecordFor<T, I>, OptionQuery>;
768
769 #[pallet::storage]
771 pub type ChallengeRoundCount<T: Config<I>, I: 'static = ()> =
772 StorageValue<_, RoundIndex, ValueQuery>;
773
774 #[pallet::storage]
776 pub type Defending<T: Config<I>, I: 'static = ()> =
777 StorageValue<_, (T::AccountId, T::AccountId, Tally)>;
778
779 #[pallet::storage]
781 pub type DefenderVotes<T: Config<I>, I: 'static = ()> =
782 StorageDoubleMap<_, Twox64Concat, RoundIndex, Twox64Concat, T::AccountId, Vote>;
783
784 #[pallet::storage]
786 pub type NextIntakeAt<T: Config<I>, I: 'static = ()> = StorageValue<_, BlockNumberFor<T, I>>;
787
788 #[pallet::storage]
790 pub type NextChallengeAt<T: Config<I>, I: 'static = ()> = StorageValue<_, BlockNumberFor<T, I>>;
791
792 #[pallet::hooks]
793 impl<T: Config<I>, I: 'static> Hooks<SystemBlockNumberFor<T>> for Pallet<T, I> {
794 fn on_initialize(_n: SystemBlockNumberFor<T>) -> Weight {
795 let mut weight = Weight::zero();
796 let weights = T::BlockWeights::get();
797 let now = T::BlockNumberProvider::current_block_number();
798
799 let phrase = b"society_rotation";
800 let (seed, _) = T::Randomness::random(phrase);
804 let seed = <[u8; 32]>::decode(&mut TrailingZeroInput::new(seed.as_ref()))
806 .expect("input is padded with zeroes; qed");
807 let mut rng = ChaChaRng::from_seed(seed);
808
809 let is_intake_moment = match Self::period() {
811 Period::Intake { .. } => true,
812 _ => false,
813 };
814 if is_intake_moment {
815 Self::rotate_intake(&mut rng);
816 weight.saturating_accrue(weights.max_block / 20);
817 Self::set_next_intake_at();
818 }
819
820 if now >= Self::next_challenge_at() {
822 Self::rotate_challenge(&mut rng);
823 weight.saturating_accrue(weights.max_block / 20);
824 Self::set_next_challenge_at();
825 }
826
827 weight
828 }
829 }
830
831 #[pallet::genesis_config]
832 #[derive(frame_support::DefaultNoBound)]
833 pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
834 pub pot: BalanceOf<T, I>,
835 }
836
837 #[pallet::genesis_build]
838 impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
839 fn build(&self) {
840 Pot::<T, I>::put(self.pot);
841 }
842 }
843
844 #[pallet::call]
845 impl<T: Config<I>, I: 'static> Pallet<T, I> {
846 #[pallet::call_index(0)]
856 #[pallet::weight(T::WeightInfo::bid())]
857 pub fn bid(origin: OriginFor<T>, value: BalanceOf<T, I>) -> DispatchResult {
858 let who = ensure_signed(origin)?;
859
860 let mut bids = Bids::<T, I>::get();
861 ensure!(!Self::has_bid(&bids, &who), Error::<T, I>::AlreadyBid);
862 ensure!(!Candidates::<T, I>::contains_key(&who), Error::<T, I>::AlreadyCandidate);
863 ensure!(!Members::<T, I>::contains_key(&who), Error::<T, I>::AlreadyMember);
864 ensure!(!SuspendedMembers::<T, I>::contains_key(&who), Error::<T, I>::Suspended);
865
866 let params = Parameters::<T, I>::get().ok_or(Error::<T, I>::NotGroup)?;
867 let deposit = params.candidate_deposit;
868 T::Currency::reserve(&who, deposit)?;
870 Self::insert_bid(&mut bids, &who, value, BidKind::Deposit(deposit));
871
872 Bids::<T, I>::put(bids);
873 Self::deposit_event(Event::<T, I>::Bid { candidate_id: who, offer: value });
874 Ok(())
875 }
876
877 #[pallet::call_index(1)]
885 #[pallet::weight(T::WeightInfo::unbid())]
886 pub fn unbid(origin: OriginFor<T>) -> DispatchResult {
887 let who = ensure_signed(origin)?;
888
889 let mut bids = Bids::<T, I>::get();
890 let pos = bids.iter().position(|bid| bid.who == who).ok_or(Error::<T, I>::NotBidder)?;
891 Self::clean_bid(&bids.remove(pos));
892 Bids::<T, I>::put(bids);
893 Self::deposit_event(Event::<T, I>::Unbid { candidate: who });
894 Ok(())
895 }
896
897 #[pallet::call_index(2)]
915 #[pallet::weight(T::WeightInfo::vouch())]
916 pub fn vouch(
917 origin: OriginFor<T>,
918 who: AccountIdLookupOf<T>,
919 value: BalanceOf<T, I>,
920 tip: BalanceOf<T, I>,
921 ) -> DispatchResult {
922 let voucher = ensure_signed(origin)?;
923 let who = T::Lookup::lookup(who)?;
924
925 let mut bids = Bids::<T, I>::get();
927 ensure!(!Self::has_bid(&bids, &who), Error::<T, I>::AlreadyBid);
928
929 ensure!(!Candidates::<T, I>::contains_key(&who), Error::<T, I>::AlreadyCandidate);
931 ensure!(!Members::<T, I>::contains_key(&who), Error::<T, I>::AlreadyMember);
932 ensure!(!SuspendedMembers::<T, I>::contains_key(&who), Error::<T, I>::Suspended);
933
934 let mut record = Members::<T, I>::get(&voucher).ok_or(Error::<T, I>::NotMember)?;
936 ensure!(record.vouching.is_none(), Error::<T, I>::AlreadyVouching);
937
938 record.vouching = Some(VouchingStatus::Vouching);
940 Self::insert_bid(&mut bids, &who, value, BidKind::Vouch(voucher.clone(), tip));
942
943 Members::<T, I>::insert(&voucher, &record);
945 Bids::<T, I>::put(bids);
946 Self::deposit_event(Event::<T, I>::Vouch {
947 candidate_id: who,
948 offer: value,
949 vouching: voucher,
950 });
951 Ok(())
952 }
953
954 #[pallet::call_index(3)]
962 #[pallet::weight(T::WeightInfo::unvouch())]
963 pub fn unvouch(origin: OriginFor<T>) -> DispatchResult {
964 let voucher = ensure_signed(origin)?;
965
966 let mut bids = Bids::<T, I>::get();
967 let pos = bids
968 .iter()
969 .position(|bid| bid.kind.is_vouch(&voucher))
970 .ok_or(Error::<T, I>::NotVouchingOnBidder)?;
971 let bid = bids.remove(pos);
972 Self::clean_bid(&bid);
973
974 Bids::<T, I>::put(bids);
975 Self::deposit_event(Event::<T, I>::Unvouch { candidate: bid.who });
976 Ok(())
977 }
978
979 #[pallet::call_index(4)]
988 #[pallet::weight(T::WeightInfo::vote())]
989 pub fn vote(
990 origin: OriginFor<T>,
991 candidate: AccountIdLookupOf<T>,
992 approve: bool,
993 ) -> DispatchResultWithPostInfo {
994 let voter = ensure_signed(origin)?;
995 let candidate = T::Lookup::lookup(candidate)?;
996
997 let mut candidacy =
998 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
999 let record = Members::<T, I>::get(&voter).ok_or(Error::<T, I>::NotMember)?;
1000
1001 let first_time = Votes::<T, I>::mutate(&candidate, &voter, |v| {
1002 let first_time = v.is_none();
1003 *v = Some(Self::do_vote(*v, approve, record.rank, &mut candidacy.tally));
1004 first_time
1005 });
1006
1007 Candidates::<T, I>::insert(&candidate, &candidacy);
1008 Self::deposit_event(Event::<T, I>::Vote { candidate, voter, vote: approve });
1009 Ok(if first_time { Pays::No } else { Pays::Yes }.into())
1010 }
1011
1012 #[pallet::call_index(5)]
1020 #[pallet::weight(T::WeightInfo::defender_vote())]
1021 pub fn defender_vote(origin: OriginFor<T>, approve: bool) -> DispatchResultWithPostInfo {
1022 let voter = ensure_signed(origin)?;
1023
1024 let mut defending = Defending::<T, I>::get().ok_or(Error::<T, I>::NoDefender)?;
1025 let record = Members::<T, I>::get(&voter).ok_or(Error::<T, I>::NotMember)?;
1026
1027 let round = ChallengeRoundCount::<T, I>::get();
1028 let first_time = DefenderVotes::<T, I>::mutate(round, &voter, |v| {
1029 let first_time = v.is_none();
1030 *v = Some(Self::do_vote(*v, approve, record.rank, &mut defending.2));
1031 first_time
1032 });
1033
1034 Defending::<T, I>::put(defending);
1035 Self::deposit_event(Event::<T, I>::DefenderVote { voter, vote: approve });
1036 Ok(if first_time { Pays::No } else { Pays::Yes }.into())
1037 }
1038
1039 #[pallet::call_index(6)]
1050 #[pallet::weight(T::WeightInfo::payout())]
1051 pub fn payout(origin: OriginFor<T>) -> DispatchResult {
1052 let who = ensure_signed(origin)?;
1053 ensure!(
1054 Members::<T, I>::get(&who).ok_or(Error::<T, I>::NotMember)?.rank == 0,
1055 Error::<T, I>::NoPayout
1056 );
1057 let mut record = Payouts::<T, I>::get(&who);
1058 let block_number = T::BlockNumberProvider::current_block_number();
1059 if let Some((when, amount)) = record.payouts.first() {
1060 if when <= &block_number {
1061 record.paid = record.paid.checked_add(amount).ok_or(Overflow)?;
1062 T::Currency::transfer(&Self::payouts(), &who, *amount, AllowDeath)?;
1063 record.payouts.remove(0);
1064 Payouts::<T, I>::insert(&who, record);
1065 return Ok(())
1066 }
1067 }
1068 Err(Error::<T, I>::NoPayout)?
1069 }
1070
1071 #[pallet::call_index(7)]
1074 #[pallet::weight(T::WeightInfo::waive_repay())]
1075 pub fn waive_repay(origin: OriginFor<T>, amount: BalanceOf<T, I>) -> DispatchResult {
1076 let who = ensure_signed(origin)?;
1077 let mut record = Members::<T, I>::get(&who).ok_or(Error::<T, I>::NotMember)?;
1078 let mut payout_record = Payouts::<T, I>::get(&who);
1079 ensure!(record.rank == 0, Error::<T, I>::AlreadyElevated);
1080 ensure!(amount >= payout_record.paid, Error::<T, I>::InsufficientFunds);
1081
1082 T::Currency::transfer(&who, &Self::account_id(), payout_record.paid, AllowDeath)?;
1083 payout_record.paid = Zero::zero();
1084 payout_record.payouts.clear();
1085 record.rank = 1;
1086 Members::<T, I>::insert(&who, record);
1087 Payouts::<T, I>::insert(&who, payout_record);
1088 Self::deposit_event(Event::<T, I>::Elevated { member: who, rank: 1 });
1089
1090 Ok(())
1091 }
1092
1093 #[pallet::call_index(8)]
1111 #[pallet::weight(T::WeightInfo::found_society())]
1112 pub fn found_society(
1113 origin: OriginFor<T>,
1114 founder: AccountIdLookupOf<T>,
1115 max_members: u32,
1116 max_intake: u32,
1117 max_strikes: u32,
1118 candidate_deposit: BalanceOf<T, I>,
1119 rules: Vec<u8>,
1120 ) -> DispatchResult {
1121 T::FounderSetOrigin::ensure_origin(origin)?;
1122 let founder = T::Lookup::lookup(founder)?;
1123 ensure!(!Head::<T, I>::exists(), Error::<T, I>::AlreadyFounded);
1124 ensure!(max_members > 1, Error::<T, I>::MaxMembers);
1125 let params = GroupParams { max_members, max_intake, max_strikes, candidate_deposit };
1127 Parameters::<T, I>::put(params);
1128 Self::insert_member(&founder, 1)?;
1129 Head::<T, I>::put(&founder);
1130 Founder::<T, I>::put(&founder);
1131 Rules::<T, I>::put(T::Hashing::hash(&rules));
1132 Self::deposit_event(Event::<T, I>::Founded { founder });
1133 Ok(())
1134 }
1135
1136 #[pallet::call_index(9)]
1142 #[pallet::weight(T::WeightInfo::dissolve())]
1143 pub fn dissolve(origin: OriginFor<T>) -> DispatchResult {
1144 let founder = ensure_signed(origin)?;
1145 ensure!(Founder::<T, I>::get().as_ref() == Some(&founder), Error::<T, I>::NotFounder);
1146 ensure!(MemberCount::<T, I>::get() == 1, Error::<T, I>::NotHead);
1147
1148 let _ = Members::<T, I>::clear(u32::MAX, None);
1149 MemberCount::<T, I>::kill();
1150 let _ = MemberByIndex::<T, I>::clear(u32::MAX, None);
1151 let _ = SuspendedMembers::<T, I>::clear(u32::MAX, None);
1152 let _ = Payouts::<T, I>::clear(u32::MAX, None);
1153 let _ = Votes::<T, I>::clear(u32::MAX, None);
1154 let _ = VoteClearCursor::<T, I>::clear(u32::MAX, None);
1155 Head::<T, I>::kill();
1156 NextHead::<T, I>::kill();
1157 Founder::<T, I>::kill();
1158 Rules::<T, I>::kill();
1159 Parameters::<T, I>::kill();
1160 Pot::<T, I>::kill();
1161 RoundCount::<T, I>::kill();
1162 Bids::<T, I>::kill();
1163 Skeptic::<T, I>::kill();
1164 ChallengeRoundCount::<T, I>::kill();
1165 Defending::<T, I>::kill();
1166 let _ = DefenderVotes::<T, I>::clear(u32::MAX, None);
1167 let _ = Candidates::<T, I>::clear(u32::MAX, None);
1168 Self::deposit_event(Event::<T, I>::Unfounded { founder });
1169 Ok(())
1170 }
1171
1172 #[pallet::call_index(10)]
1187 #[pallet::weight(T::WeightInfo::judge_suspended_member())]
1188 pub fn judge_suspended_member(
1189 origin: OriginFor<T>,
1190 who: AccountIdLookupOf<T>,
1191 forgive: bool,
1192 ) -> DispatchResultWithPostInfo {
1193 ensure!(
1194 Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1195 Error::<T, I>::NotFounder
1196 );
1197 let who = T::Lookup::lookup(who)?;
1198 let record = SuspendedMembers::<T, I>::get(&who).ok_or(Error::<T, I>::NotSuspended)?;
1199 if forgive {
1200 Self::reinstate_member(&who, record.rank)?;
1202 } else {
1203 let payout_record = Payouts::<T, I>::take(&who);
1204 let total = payout_record
1205 .payouts
1206 .into_iter()
1207 .map(|x| x.1)
1208 .fold(Zero::zero(), |acc: BalanceOf<T, I>, x| acc.saturating_add(x));
1209 Self::unreserve_payout(total);
1210 }
1211 SuspendedMembers::<T, I>::remove(&who);
1212 Self::deposit_event(Event::<T, I>::SuspendedMemberJudgement { who, judged: forgive });
1213 Ok(Pays::No.into())
1214 }
1215
1216 #[pallet::call_index(11)]
1229 #[pallet::weight(T::WeightInfo::set_parameters())]
1230 pub fn set_parameters(
1231 origin: OriginFor<T>,
1232 max_members: u32,
1233 max_intake: u32,
1234 max_strikes: u32,
1235 candidate_deposit: BalanceOf<T, I>,
1236 ) -> DispatchResult {
1237 ensure!(
1238 Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1239 Error::<T, I>::NotFounder
1240 );
1241 ensure!(max_members >= MemberCount::<T, I>::get(), Error::<T, I>::MaxMembers);
1242 let params = GroupParams { max_members, max_intake, max_strikes, candidate_deposit };
1243 Parameters::<T, I>::put(¶ms);
1244 Self::deposit_event(Event::<T, I>::NewParams { params });
1245 Ok(())
1246 }
1247
1248 #[pallet::call_index(12)]
1251 #[pallet::weight(T::WeightInfo::punish_skeptic())]
1252 pub fn punish_skeptic(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1253 let candidate = ensure_signed(origin)?;
1254 let mut candidacy =
1255 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1256 ensure!(!candidacy.skeptic_struck, Error::<T, I>::AlreadyPunished);
1257 ensure!(!Self::in_progress(candidacy.round), Error::<T, I>::InProgress);
1258 let punished = Self::check_skeptic(&candidate, &mut candidacy);
1259 Candidates::<T, I>::insert(&candidate, candidacy);
1260 Ok(if punished { Pays::No } else { Pays::Yes }.into())
1261 }
1262
1263 #[pallet::call_index(13)]
1266 #[pallet::weight(T::WeightInfo::claim_membership())]
1267 pub fn claim_membership(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1268 let candidate = ensure_signed(origin)?;
1269 let candidacy =
1270 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1271 ensure!(candidacy.tally.clear_approval(), Error::<T, I>::NotApproved);
1272 ensure!(!Self::in_progress(candidacy.round), Error::<T, I>::InProgress);
1273 Self::induct_member(candidate, candidacy, 0)?;
1274 Ok(Pays::No.into())
1275 }
1276
1277 #[pallet::call_index(14)]
1281 #[pallet::weight(T::WeightInfo::bestow_membership())]
1282 pub fn bestow_membership(
1283 origin: OriginFor<T>,
1284 candidate: T::AccountId,
1285 ) -> DispatchResultWithPostInfo {
1286 ensure!(
1287 Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1288 Error::<T, I>::NotFounder
1289 );
1290 let candidacy =
1291 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1292 ensure!(!candidacy.tally.clear_rejection(), Error::<T, I>::Rejected);
1293 ensure!(!Self::in_progress(candidacy.round), Error::<T, I>::InProgress);
1294 Self::induct_member(candidate, candidacy, 0)?;
1295 Ok(Pays::No.into())
1296 }
1297
1298 #[pallet::call_index(15)]
1304 #[pallet::weight(T::WeightInfo::kick_candidate())]
1305 pub fn kick_candidate(
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 mut candidacy =
1314 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1315 ensure!(!Self::in_progress(candidacy.round), Error::<T, I>::InProgress);
1316 ensure!(!candidacy.tally.clear_approval(), Error::<T, I>::Approved);
1317 Self::check_skeptic(&candidate, &mut candidacy);
1318 Self::reject_candidate(&candidate, &candidacy.kind);
1319 Candidates::<T, I>::remove(&candidate);
1320 Ok(Pays::No.into())
1321 }
1322
1323 #[pallet::call_index(16)]
1327 #[pallet::weight(T::WeightInfo::resign_candidacy())]
1328 pub fn resign_candidacy(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1329 let candidate = ensure_signed(origin)?;
1330 let mut candidacy =
1331 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1332 if !Self::in_progress(candidacy.round) {
1333 Self::check_skeptic(&candidate, &mut candidacy);
1334 }
1335 Self::reject_candidate(&candidate, &candidacy.kind);
1336 Candidates::<T, I>::remove(&candidate);
1337 Ok(Pays::No.into())
1338 }
1339
1340 #[pallet::call_index(17)]
1346 #[pallet::weight(T::WeightInfo::drop_candidate())]
1347 pub fn drop_candidate(
1348 origin: OriginFor<T>,
1349 candidate: T::AccountId,
1350 ) -> DispatchResultWithPostInfo {
1351 ensure_signed(origin)?;
1352 let candidacy =
1353 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1354 ensure!(candidacy.tally.clear_rejection(), Error::<T, I>::NotRejected);
1355 ensure!(RoundCount::<T, I>::get() > candidacy.round + 1, Error::<T, I>::TooEarly);
1356 Self::reject_candidate(&candidate, &candidacy.kind);
1357 Candidates::<T, I>::remove(&candidate);
1358 Ok(Pays::No.into())
1359 }
1360
1361 #[pallet::call_index(18)]
1365 #[pallet::weight(T::WeightInfo::cleanup_candidacy())]
1366 pub fn cleanup_candidacy(
1367 origin: OriginFor<T>,
1368 candidate: T::AccountId,
1369 max: u32,
1370 ) -> DispatchResultWithPostInfo {
1371 ensure_signed(origin)?;
1372 ensure!(!Candidates::<T, I>::contains_key(&candidate), Error::<T, I>::InProgress);
1373 let maybe_cursor = VoteClearCursor::<T, I>::get(&candidate);
1374 let r =
1375 Votes::<T, I>::clear_prefix(&candidate, max, maybe_cursor.as_ref().map(|x| &x[..]));
1376 if let Some(cursor) = r.maybe_cursor {
1377 VoteClearCursor::<T, I>::insert(&candidate, BoundedVec::truncate_from(cursor));
1378 }
1379 Ok(if r.loops == 0 { Pays::Yes } else { Pays::No }.into())
1380 }
1381
1382 #[pallet::call_index(19)]
1386 #[pallet::weight(T::WeightInfo::cleanup_challenge())]
1387 pub fn cleanup_challenge(
1388 origin: OriginFor<T>,
1389 challenge_round: RoundIndex,
1390 max: u32,
1391 ) -> DispatchResultWithPostInfo {
1392 ensure_signed(origin)?;
1393 ensure!(
1394 challenge_round < ChallengeRoundCount::<T, I>::get(),
1395 Error::<T, I>::InProgress
1396 );
1397 let _ = DefenderVotes::<T, I>::clear_prefix(challenge_round, max, None);
1398 Ok(Pays::No.into())
1402 }
1403
1404 #[pallet::call_index(20)]
1412 #[pallet::weight(T::WeightInfo::poke_deposit())]
1413 pub fn poke_deposit(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1414 let who = ensure_signed(origin)?;
1415
1416 let mut bids = Bids::<T, I>::get();
1418 let bid = bids.iter_mut().find(|bid| bid.who == who).ok_or(Error::<T, I>::NotBidder)?;
1419
1420 let old_deposit = match &bid.kind {
1422 BidKind::Deposit(amount) => *amount,
1423 _ => return Err(Error::<T, I>::NoDeposit.into()),
1424 };
1425
1426 let params = Parameters::<T, I>::get().ok_or(Error::<T, I>::NotGroup)?;
1427 let new_deposit = params.candidate_deposit;
1428
1429 if old_deposit == new_deposit {
1430 return Ok(Pays::Yes.into());
1431 }
1432
1433 if new_deposit > old_deposit {
1434 let extra = new_deposit.saturating_sub(old_deposit);
1436 T::Currency::reserve(&who, extra)?;
1437 } else {
1438 let excess = old_deposit.saturating_sub(new_deposit);
1440 let remaining_unreserved = T::Currency::unreserve(&who, excess);
1441 if !remaining_unreserved.is_zero() {
1442 defensive!(
1443 "Failed to unreserve for full amount for bid (Requested, Actual)",
1444 (excess, excess.saturating_sub(remaining_unreserved))
1445 );
1446 }
1447 }
1448
1449 bid.kind = BidKind::Deposit(new_deposit);
1450 Bids::<T, I>::put(bids);
1451
1452 Self::deposit_event(Event::<T, I>::DepositPoked {
1453 who: who.clone(),
1454 old_deposit,
1455 new_deposit,
1456 });
1457
1458 Ok(Pays::No.into())
1459 }
1460 }
1461}
1462
1463pub struct EnsureFounder<T>(core::marker::PhantomData<T>);
1465impl<T: Config> EnsureOrigin<<T as frame_system::Config>::RuntimeOrigin> for EnsureFounder<T> {
1466 type Success = T::AccountId;
1467 fn try_origin(o: T::RuntimeOrigin) -> Result<Self::Success, T::RuntimeOrigin> {
1468 match (o.as_signer(), Founder::<T>::get()) {
1469 (Some(who), Some(f)) if *who == f => Ok(f),
1470 _ => Err(o),
1471 }
1472 }
1473
1474 #[cfg(feature = "runtime-benchmarks")]
1475 fn try_successful_origin() -> Result<T::RuntimeOrigin, ()> {
1476 let founder = Founder::<T>::get().ok_or(())?;
1477 Ok(T::RuntimeOrigin::from(frame_system::RawOrigin::Signed(founder)))
1478 }
1479}
1480
1481impl_ensure_origin_with_arg_ignoring_arg! {
1482 impl<{ T: Config, A }>
1483 EnsureOriginWithArg<T::RuntimeOrigin, A> for EnsureFounder<T>
1484 {}
1485}
1486
1487#[derive(Debug, PartialEq, Eq)]
1488pub enum Period<BlockNumber> {
1489 Voting { elapsed: BlockNumber, more: BlockNumber },
1490 Claim { elapsed: BlockNumber, more: BlockNumber },
1491 Intake { elapsed: BlockNumber },
1492}
1493
1494impl<T: Config<I>, I: 'static> Pallet<T, I> {
1495 fn period() -> Period<BlockNumberFor<T, I>> {
1497 let claim_period = T::ClaimPeriod::get();
1498 let voting_period = T::VotingPeriod::get();
1499 let rotation_period = voting_period + claim_period;
1500 let now = T::BlockNumberProvider::current_block_number();
1501 let phase = now % rotation_period;
1502 if now >= Self::next_intake_at() {
1503 Period::Intake { elapsed: now - Self::next_intake_at() }
1504 } else if phase < voting_period {
1505 Period::Voting { elapsed: phase, more: voting_period - phase }
1506 } else {
1507 Period::Claim { elapsed: phase - voting_period, more: rotation_period - phase }
1508 }
1509 }
1510
1511 pub fn next_intake_at() -> BlockNumberFor<T, I> {
1515 match NextIntakeAt::<T, I>::get() {
1516 Some(next) => next,
1517 None => {
1518 let now = T::BlockNumberProvider::current_block_number();
1520 let prev_block = now.saturating_sub(BlockNumberFor::<T, I>::one());
1521 let rotation_period = T::VotingPeriod::get().saturating_add(T::ClaimPeriod::get());
1522 let elapsed = prev_block % rotation_period;
1523 let next_intake_at = prev_block + (rotation_period - elapsed);
1524 NextIntakeAt::<T, I>::put(next_intake_at);
1525 next_intake_at
1526 },
1527 }
1528 }
1529
1530 fn set_next_intake_at() {
1534 let prev_next_intake_at = Self::next_intake_at();
1535 let next_intake_at = prev_next_intake_at
1536 .saturating_add(T::VotingPeriod::get().saturating_add(T::ClaimPeriod::get()));
1537 NextIntakeAt::<T, I>::put(next_intake_at);
1538 }
1539
1540 pub fn next_challenge_at() -> BlockNumberFor<T, I> {
1544 match NextChallengeAt::<T, I>::get() {
1545 Some(next) => next,
1546 None => {
1547 let now = T::BlockNumberProvider::current_block_number();
1549 let prev_block = now.saturating_sub(BlockNumberFor::<T, I>::one());
1550 let challenge_period = T::ChallengePeriod::get();
1551 let elapsed = prev_block % challenge_period;
1552 let next_challenge_at = prev_block + (challenge_period - elapsed);
1553 NextChallengeAt::<T, I>::put(next_challenge_at);
1554 next_challenge_at
1555 },
1556 }
1557 }
1558
1559 fn set_next_challenge_at() {
1563 let prev_next_challenge_at = Self::next_challenge_at();
1564 let next_challenge_at = prev_next_challenge_at.saturating_add(T::ChallengePeriod::get());
1565 NextChallengeAt::<T, I>::put(next_challenge_at);
1566 }
1567
1568 fn in_progress(target_round: RoundIndex) -> bool {
1570 let round = RoundCount::<T, I>::get();
1571 target_round == round && matches!(Self::period(), Period::Voting { .. })
1572 }
1573
1574 fn do_vote(maybe_old: Option<Vote>, approve: bool, rank: Rank, tally: &mut Tally) -> Vote {
1576 match maybe_old {
1577 Some(Vote { approve: true, weight }) => tally.approvals.saturating_reduce(weight),
1578 Some(Vote { approve: false, weight }) => tally.rejections.saturating_reduce(weight),
1579 _ => {},
1580 }
1581 let weight_root = rank + 1;
1582 let weight = weight_root * weight_root;
1583 match approve {
1584 true => tally.approvals.saturating_accrue(weight),
1585 false => tally.rejections.saturating_accrue(weight),
1586 }
1587 Vote { approve, weight }
1588 }
1589
1590 fn check_skeptic(
1592 candidate: &T::AccountId,
1593 candidacy: &mut Candidacy<T::AccountId, BalanceOf<T, I>>,
1594 ) -> bool {
1595 if RoundCount::<T, I>::get() != candidacy.round || candidacy.skeptic_struck {
1596 return false
1597 }
1598 let skeptic = match Skeptic::<T, I>::get() {
1600 Some(s) => s,
1601 None => return false,
1602 };
1603 let maybe_vote = Votes::<T, I>::get(&candidate, &skeptic);
1604 let approved = candidacy.tally.clear_approval();
1605 let rejected = candidacy.tally.clear_rejection();
1606 match (maybe_vote, approved, rejected) {
1607 (None, _, _) |
1608 (Some(Vote { approve: true, .. }), false, true) |
1609 (Some(Vote { approve: false, .. }), true, false) => {
1610 if Self::strike_member(&skeptic).is_ok() {
1612 candidacy.skeptic_struck = true;
1613 true
1614 } else {
1615 false
1616 }
1617 },
1618 _ => false,
1619 }
1620 }
1621
1622 fn rotate_challenge(rng: &mut impl RngCore) {
1624 let mut next_defender = None;
1625 let mut round = ChallengeRoundCount::<T, I>::get();
1626
1627 if let Some((defender, skeptic, tally)) = Defending::<T, I>::get() {
1629 if !tally.more_approvals() {
1631 let _ = Self::suspend_member(&defender);
1634 }
1635
1636 let skeptic_vote = DefenderVotes::<T, I>::get(round, &skeptic);
1638 match (skeptic_vote, tally.more_approvals(), tally.more_rejections()) {
1639 (None, _, _) |
1640 (Some(Vote { approve: true, .. }), false, true) |
1641 (Some(Vote { approve: false, .. }), true, false) => {
1642 let _ = Self::strike_member(&skeptic);
1644 let founder = Founder::<T, I>::get();
1645 let head = Head::<T, I>::get();
1646 if Some(&skeptic) != founder.as_ref() && Some(&skeptic) != head.as_ref() {
1647 next_defender = Some(skeptic);
1648 }
1649 },
1650 _ => {},
1651 }
1652 round.saturating_inc();
1653 ChallengeRoundCount::<T, I>::put(round);
1654 }
1655
1656 if MemberCount::<T, I>::get() > 2 {
1659 let defender = next_defender
1660 .or_else(|| Self::pick_defendant(rng))
1661 .expect("exited if members empty; qed");
1662 let skeptic =
1663 Self::pick_member_except(rng, &defender).expect("exited if members empty; qed");
1664 Self::deposit_event(Event::<T, I>::Challenged { member: defender.clone() });
1665 Defending::<T, I>::put((defender, skeptic, Tally::default()));
1666 } else {
1667 Defending::<T, I>::kill();
1668 }
1669 }
1670
1671 fn rotate_intake(rng: &mut impl RngCore) {
1678 let member_count = MemberCount::<T, I>::get();
1680 if member_count < 1 {
1681 return
1682 }
1683 let maybe_head = NextHead::<T, I>::take();
1684 if let Some(head) = maybe_head {
1685 Head::<T, I>::put(&head.who);
1686 }
1687
1688 let mut pot = Pot::<T, I>::get();
1691 let unaccounted = T::Currency::free_balance(&Self::account_id()).saturating_sub(pot);
1692 pot.saturating_accrue(T::PeriodSpend::get().min(unaccounted / 2u8.into()));
1693 Pot::<T, I>::put(&pot);
1694
1695 let mut round_count = RoundCount::<T, I>::get();
1697 round_count.saturating_inc();
1698 let candidate_count = Self::select_new_candidates(round_count, member_count, pot);
1699 if candidate_count > 0 {
1700 let skeptic = Self::pick_member(rng).expect("exited if members empty; qed");
1702 Skeptic::<T, I>::put(skeptic);
1703 }
1704 RoundCount::<T, I>::put(round_count);
1705 }
1706
1707 pub fn select_new_candidates(
1715 round: RoundIndex,
1716 member_count: u32,
1717 pot: BalanceOf<T, I>,
1718 ) -> u32 {
1719 let mut bids = Bids::<T, I>::get();
1721 let params = match Parameters::<T, I>::get() {
1722 Some(params) => params,
1723 None => return 0,
1724 };
1725 let max_selections: u32 = params
1726 .max_intake
1727 .min(params.max_members.saturating_sub(member_count))
1728 .min(bids.len() as u32);
1729
1730 let mut selections = 0;
1731 let mut total_cost: BalanceOf<T, I> = Zero::zero();
1733
1734 bids.retain(|bid| {
1735 total_cost.saturating_accrue(bid.value);
1737 let accept = selections < max_selections &&
1738 (!bid.value.is_zero() || selections == 0) &&
1739 total_cost <= pot;
1740 if accept {
1741 let candidacy = Candidacy {
1742 round,
1743 kind: bid.kind.clone(),
1744 bid: bid.value,
1745 tally: Default::default(),
1746 skeptic_struck: false,
1747 };
1748 Candidates::<T, I>::insert(&bid.who, candidacy);
1749 selections.saturating_inc();
1750 }
1751 !accept
1752 });
1753
1754 Bids::<T, I>::put(&bids);
1756 selections
1757 }
1758
1759 fn insert_bid(
1762 bids: &mut BoundedVec<Bid<T::AccountId, BalanceOf<T, I>>, T::MaxBids>,
1763 who: &T::AccountId,
1764 value: BalanceOf<T, I>,
1765 bid_kind: BidKind<T::AccountId, BalanceOf<T, I>>,
1766 ) {
1767 let pos = bids.iter().position(|bid| bid.value > value).unwrap_or(bids.len());
1768 let r = bids.force_insert_keep_left(pos, Bid { value, who: who.clone(), kind: bid_kind });
1769 let maybe_discarded = match r {
1770 Ok(x) => x,
1771 Err(x) => Some(x),
1772 };
1773 if let Some(discarded) = maybe_discarded {
1774 Self::clean_bid(&discarded);
1775 Self::deposit_event(Event::<T, I>::AutoUnbid { candidate: discarded.who });
1776 }
1777 }
1778
1779 fn clean_bid(bid: &Bid<T::AccountId, BalanceOf<T, I>>) {
1787 match &bid.kind {
1788 BidKind::Deposit(deposit) => {
1789 let err_amount = T::Currency::unreserve(&bid.who, *deposit);
1790 debug_assert!(err_amount.is_zero());
1791 },
1792 BidKind::Vouch(voucher, _) => {
1793 Members::<T, I>::mutate_extant(voucher, |record| record.vouching = None);
1794 },
1795 }
1796 }
1797
1798 fn reject_candidate(who: &T::AccountId, kind: &BidKind<T::AccountId, BalanceOf<T, I>>) {
1806 match kind {
1807 BidKind::Deposit(deposit) => {
1808 let pot = Self::account_id();
1809 let free = BalanceStatus::Free;
1810 let r = T::Currency::repatriate_reserved(&who, &pot, *deposit, free);
1811 debug_assert!(r.is_ok());
1812 },
1813 BidKind::Vouch(voucher, _) => {
1814 Members::<T, I>::mutate_extant(voucher, |record| {
1815 record.vouching = Some(VouchingStatus::Banned)
1816 });
1817 },
1818 }
1819 }
1820
1821 fn has_bid(bids: &Vec<Bid<T::AccountId, BalanceOf<T, I>>>, who: &T::AccountId) -> bool {
1823 bids.iter().any(|bid| bid.who == *who)
1825 }
1826
1827 fn insert_member(who: &T::AccountId, rank: Rank) -> DispatchResult {
1837 let params = Parameters::<T, I>::get().ok_or(Error::<T, I>::NotGroup)?;
1838 ensure!(MemberCount::<T, I>::get() < params.max_members, Error::<T, I>::MaxMembers);
1839 let index = MemberCount::<T, I>::mutate(|i| {
1840 i.saturating_accrue(1);
1841 *i - 1
1842 });
1843 let record = MemberRecord { rank, strikes: 0, vouching: None, index };
1844 Members::<T, I>::insert(who, record);
1845 MemberByIndex::<T, I>::insert(index, who);
1846 Ok(())
1847 }
1848
1849 fn reinstate_member(who: &T::AccountId, rank: Rank) -> DispatchResult {
1856 Self::insert_member(who, rank)
1857 }
1858
1859 fn add_new_member(who: &T::AccountId, rank: Rank) -> DispatchResult {
1862 Self::insert_member(who, rank)
1863 }
1864
1865 fn induct_member(
1867 candidate: T::AccountId,
1868 mut candidacy: Candidacy<T::AccountId, BalanceOf<T, I>>,
1869 rank: Rank,
1870 ) -> DispatchResult {
1871 Self::add_new_member(&candidate, rank)?;
1872 Self::check_skeptic(&candidate, &mut candidacy);
1873
1874 let next_head = NextHead::<T, I>::get()
1875 .filter(|old| {
1876 old.round > candidacy.round ||
1877 old.round == candidacy.round && old.bid < candidacy.bid
1878 })
1879 .unwrap_or_else(|| IntakeRecord {
1880 who: candidate.clone(),
1881 bid: candidacy.bid,
1882 round: candidacy.round,
1883 });
1884 NextHead::<T, I>::put(next_head);
1885
1886 let now = T::BlockNumberProvider::current_block_number();
1887 let maturity = now + Self::lock_duration(MemberCount::<T, I>::get());
1888 Self::reward_bidder(&candidate, candidacy.bid, candidacy.kind, maturity);
1889
1890 Candidates::<T, I>::remove(&candidate);
1891 Ok(())
1892 }
1893
1894 fn strike_member(who: &T::AccountId) -> DispatchResult {
1895 let mut record = Members::<T, I>::get(who).ok_or(Error::<T, I>::NotMember)?;
1896 record.strikes.saturating_inc();
1897 Members::<T, I>::insert(who, &record);
1898 if record.strikes >= T::GraceStrikes::get() {
1902 let total_payout = Payouts::<T, I>::get(who)
1904 .payouts
1905 .iter()
1906 .fold(BalanceOf::<T, I>::zero(), |acc, x| acc.saturating_add(x.1));
1907 Self::slash_payout(who, total_payout / 2u32.into());
1908 }
1909
1910 let params = Parameters::<T, I>::get().ok_or(Error::<T, I>::NotGroup)?;
1911 if record.strikes >= params.max_strikes {
1912 let _ = Self::suspend_member(who);
1914 }
1915 Ok(())
1916 }
1917
1918 pub fn remove_member(m: &T::AccountId) -> Result<MemberRecord, DispatchError> {
1929 ensure!(Head::<T, I>::get().as_ref() != Some(m), Error::<T, I>::Head);
1930 ensure!(Founder::<T, I>::get().as_ref() != Some(m), Error::<T, I>::Founder);
1931 if let Some(mut record) = Members::<T, I>::get(m) {
1932 let index = record.index;
1933 let last_index = MemberCount::<T, I>::mutate(|i| {
1934 i.saturating_reduce(1);
1935 *i
1936 });
1937 if index != last_index {
1938 if let Some(other) = MemberByIndex::<T, I>::get(last_index) {
1941 MemberByIndex::<T, I>::insert(index, &other);
1942 Members::<T, I>::mutate(other, |m_r| {
1943 if let Some(r) = m_r {
1944 r.index = index
1945 }
1946 });
1947 } else {
1948 debug_assert!(false, "ERROR: No member at the last index position?");
1949 }
1950 }
1951
1952 MemberByIndex::<T, I>::remove(last_index);
1953 Members::<T, I>::remove(m);
1954 if record.vouching.take() == Some(VouchingStatus::Vouching) {
1956 Bids::<T, I>::mutate(|bids|
1959 if let Some(pos) = bids.iter().position(|b| b.kind.is_vouch(&m)) {
1961 let vouched = bids.remove(pos).who;
1963 Self::deposit_event(Event::<T, I>::Unvouch { candidate: vouched });
1964 }
1965 );
1966 }
1967 Ok(record)
1968 } else {
1969 Err(Error::<T, I>::NotMember.into())
1970 }
1971 }
1972
1973 fn suspend_member(who: &T::AccountId) -> DispatchResult {
1979 let record = Self::remove_member(&who)?;
1980 SuspendedMembers::<T, I>::insert(who, record);
1981 Self::deposit_event(Event::<T, I>::MemberSuspended { member: who.clone() });
1982 Ok(())
1983 }
1984
1985 fn pick_member(rng: &mut impl RngCore) -> Option<T::AccountId> {
1989 let member_count = MemberCount::<T, I>::get();
1990 if member_count == 0 {
1991 return None
1992 }
1993 let random_index = rng.next_u32() % member_count;
1994 MemberByIndex::<T, I>::get(random_index)
1995 }
1996
1997 fn pick_member_except(
2002 rng: &mut impl RngCore,
2003 exception: &T::AccountId,
2004 ) -> Option<T::AccountId> {
2005 let member_count = MemberCount::<T, I>::get();
2006 if member_count <= 1 {
2007 return None
2008 }
2009 let random_index = rng.next_u32() % (member_count - 1);
2010 let pick = MemberByIndex::<T, I>::get(random_index);
2011 if pick.as_ref() == Some(exception) {
2012 MemberByIndex::<T, I>::get(member_count - 1)
2013 } else {
2014 pick
2015 }
2016 }
2017
2018 fn pick_defendant(rng: &mut impl RngCore) -> Option<T::AccountId> {
2023 let member_count = MemberCount::<T, I>::get();
2024 if member_count <= 2 {
2025 return None
2026 }
2027 let head = Head::<T, I>::get();
2031 let pickable_count = member_count - if head.is_some() { 2 } else { 1 };
2032 let random_index = rng.next_u32() % pickable_count + 1;
2033 let pick = MemberByIndex::<T, I>::get(random_index);
2034 if pick == head && head.is_some() {
2035 MemberByIndex::<T, I>::get(member_count - 1)
2038 } else {
2039 pick
2040 }
2041 }
2042
2043 fn reward_bidder(
2045 candidate: &T::AccountId,
2046 value: BalanceOf<T, I>,
2047 kind: BidKind<T::AccountId, BalanceOf<T, I>>,
2048 maturity: BlockNumberFor<T, I>,
2049 ) {
2050 let value = match kind {
2051 BidKind::Deposit(deposit) => {
2052 let err_amount = T::Currency::unreserve(candidate, deposit);
2055 debug_assert!(err_amount.is_zero());
2056 value
2057 },
2058 BidKind::Vouch(voucher, tip) => {
2059 if let Some(mut record) = Members::<T, I>::get(&voucher) {
2062 if let Some(VouchingStatus::Vouching) = record.vouching {
2063 record.vouching = None;
2066 Self::bump_payout(&voucher, maturity, tip.min(value));
2067 Members::<T, I>::insert(&voucher, record);
2068 value.saturating_sub(tip)
2069 } else {
2070 value
2071 }
2072 } else {
2073 value
2074 }
2075 },
2076 };
2077
2078 Self::bump_payout(candidate, maturity, value);
2079 }
2080
2081 fn bump_payout(who: &T::AccountId, when: BlockNumberFor<T, I>, value: BalanceOf<T, I>) {
2086 if value.is_zero() {
2087 return
2088 }
2089 if let Some(MemberRecord { rank: 0, .. }) = Members::<T, I>::get(who) {
2090 Payouts::<T, I>::mutate(who, |record| {
2091 match record.payouts.binary_search_by_key(&when, |x| x.0) {
2093 Ok(index) => record.payouts[index].1.saturating_accrue(value),
2094 Err(index) => {
2095 let _ = record.payouts.try_insert(index, (when, value));
2097 },
2098 }
2099 });
2100 Self::reserve_payout(value);
2101 }
2102 }
2103
2104 fn slash_payout(who: &T::AccountId, value: BalanceOf<T, I>) -> BalanceOf<T, I> {
2106 let mut record = Payouts::<T, I>::get(who);
2107 let mut rest = value;
2108 while !record.payouts.is_empty() {
2109 if let Some(new_rest) = rest.checked_sub(&record.payouts[0].1) {
2110 rest = new_rest;
2112 record.payouts.remove(0);
2113 } else {
2114 record.payouts[0].1.saturating_reduce(rest);
2116 rest = Zero::zero();
2117 break
2118 }
2119 }
2120 Payouts::<T, I>::insert(who, record);
2121 value - rest
2122 }
2123
2124 fn reserve_payout(amount: BalanceOf<T, I>) {
2127 Pot::<T, I>::mutate(|pot| pot.saturating_reduce(amount));
2129
2130 let res = T::Currency::transfer(&Self::account_id(), &Self::payouts(), amount, AllowDeath);
2133 debug_assert!(res.is_ok());
2134 }
2135
2136 fn unreserve_payout(amount: BalanceOf<T, I>) {
2139 Pot::<T, I>::mutate(|pot| pot.saturating_accrue(amount));
2141
2142 let res = T::Currency::transfer(&Self::payouts(), &Self::account_id(), amount, AllowDeath);
2145 debug_assert!(res.is_ok());
2146 }
2147
2148 pub fn account_id() -> T::AccountId {
2153 T::PalletId::get().into_account_truncating()
2154 }
2155
2156 pub fn payouts() -> T::AccountId {
2161 T::PalletId::get().into_sub_account_truncating(b"payouts")
2162 }
2163
2164 fn lock_duration(x: u32) -> BlockNumberFor<T, I> {
2169 let lock_pc = 100 - 50_000 / (x + 500);
2170 Percent::from_percent(lock_pc as u8) * T::MaxLockDuration::get()
2171 }
2172}
2173
2174impl<T: Config<I>, I: 'static> OnUnbalanced<NegativeImbalanceOf<T, I>> for Pallet<T, I> {
2175 fn on_nonzero_unbalanced(amount: NegativeImbalanceOf<T, I>) {
2176 let numeric_amount = amount.peek();
2177
2178 let _ = T::Currency::resolve_creating(&Self::account_id(), amount);
2180
2181 Self::deposit_event(Event::<T, I>::Deposit { value: numeric_amount });
2182 }
2183}