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 Percent, RuntimeDebug,
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, RuntimeDebug, TypeInfo, MaxEncodedLen)]
307pub struct Vote {
308 pub approve: bool,
309 pub weight: u32,
310}
311
312#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
314pub enum Judgement {
315 Rebid,
318 Reject,
320 Approve,
322}
323
324#[derive(
326 Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen,
327)]
328pub struct Payout<Balance, BlockNumber> {
329 pub value: Balance,
331 pub begin: BlockNumber,
333 pub duration: BlockNumber,
335 pub paid: Balance,
337}
338
339#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
341pub enum VouchingStatus {
342 Vouching,
344 Banned,
346}
347
348pub type StrikeCount = u32;
350
351#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
353pub struct Bid<AccountId, Balance> {
354 pub who: AccountId,
356 pub kind: BidKind<AccountId, Balance>,
358 pub value: Balance,
360}
361
362pub type RoundIndex = u32;
364
365pub type Rank = u32;
367
368pub type VoteCount = u32;
370
371#[derive(
373 Default, Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen,
374)]
375pub struct Tally {
376 pub approvals: VoteCount,
378 pub rejections: VoteCount,
380}
381
382impl Tally {
383 fn more_approvals(&self) -> bool {
384 self.approvals > self.rejections
385 }
386
387 fn more_rejections(&self) -> bool {
388 self.rejections > self.approvals
389 }
390
391 fn clear_approval(&self) -> bool {
392 self.approvals >= (2 * self.rejections).max(1)
393 }
394
395 fn clear_rejection(&self) -> bool {
396 self.rejections >= (2 * self.approvals).max(1)
397 }
398}
399
400#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
402pub struct Candidacy<AccountId, Balance> {
403 pub round: RoundIndex,
405 pub kind: BidKind<AccountId, Balance>,
407 pub bid: Balance,
409 pub tally: Tally,
411 pub skeptic_struck: bool,
413}
414
415#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
417pub enum BidKind<AccountId, Balance> {
418 Deposit(Balance),
420 Vouch(AccountId, Balance),
423}
424
425impl<AccountId: PartialEq, Balance> BidKind<AccountId, Balance> {
426 fn is_vouch(&self, v: &AccountId) -> bool {
427 matches!(self, BidKind::Vouch(ref a, _) if a == v)
428 }
429}
430
431pub type PayoutsFor<T, I> =
432 BoundedVec<(BlockNumberFor<T, I>, BalanceOf<T, I>), <T as Config<I>>::MaxPayouts>;
433
434#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
436pub struct MemberRecord {
437 pub rank: Rank,
438 pub strikes: StrikeCount,
439 pub vouching: Option<VouchingStatus>,
440 pub index: u32,
441}
442
443#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, Default, MaxEncodedLen)]
445pub struct PayoutRecord<Balance, PayoutsVec> {
446 pub paid: Balance,
447 pub payouts: PayoutsVec,
448}
449
450pub type PayoutRecordFor<T, I> = PayoutRecord<
451 BalanceOf<T, I>,
452 BoundedVec<(BlockNumberFor<T, I>, BalanceOf<T, I>), <T as Config<I>>::MaxPayouts>,
453>;
454
455#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
457pub struct IntakeRecord<AccountId, Balance> {
458 pub who: AccountId,
459 pub bid: Balance,
460 pub round: RoundIndex,
461}
462
463pub type IntakeRecordFor<T, I> =
464 IntakeRecord<<T as frame_system::Config>::AccountId, BalanceOf<T, I>>;
465
466#[derive(
467 Encode,
468 Decode,
469 DecodeWithMemTracking,
470 Copy,
471 Clone,
472 PartialEq,
473 Eq,
474 RuntimeDebug,
475 TypeInfo,
476 MaxEncodedLen,
477)]
478pub struct GroupParams<Balance> {
479 pub max_members: u32,
480 pub max_intake: u32,
481 pub max_strikes: u32,
482 pub candidate_deposit: Balance,
483}
484
485pub type GroupParamsFor<T, I> = GroupParams<BalanceOf<T, I>>;
486
487pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
488
489#[frame_support::pallet]
490pub mod pallet {
491 use super::*;
492
493 #[pallet::pallet]
494 #[pallet::storage_version(STORAGE_VERSION)]
495 pub struct Pallet<T, I = ()>(_);
496
497 #[pallet::config]
498 pub trait Config<I: 'static = ()>: frame_system::Config {
499 #[allow(deprecated)]
501 type RuntimeEvent: From<Event<Self, I>>
502 + IsType<<Self as frame_system::Config>::RuntimeEvent>;
503
504 #[pallet::constant]
506 type PalletId: Get<PalletId>;
507
508 type Currency: ReservableCurrency<Self::AccountId>;
510
511 type Randomness: Randomness<Self::Hash, BlockNumberFor<Self, I>>;
513
514 #[pallet::constant]
516 type GraceStrikes: Get<u32>;
517
518 #[pallet::constant]
520 type PeriodSpend: Get<BalanceOf<Self, I>>;
521
522 #[pallet::constant]
526 type VotingPeriod: Get<BlockNumberFor<Self, I>>;
527
528 #[pallet::constant]
531 type ClaimPeriod: Get<BlockNumberFor<Self, I>>;
532
533 #[pallet::constant]
535 type MaxLockDuration: Get<BlockNumberFor<Self, I>>;
536
537 type FounderSetOrigin: EnsureOrigin<Self::RuntimeOrigin>;
539
540 #[pallet::constant]
542 type ChallengePeriod: Get<BlockNumberFor<Self, I>>;
543
544 #[pallet::constant]
546 type MaxPayouts: Get<u32>;
547
548 #[pallet::constant]
550 type MaxBids: Get<u32>;
551
552 type WeightInfo: WeightInfo;
554 type BlockNumberProvider: BlockNumberProvider;
556 }
557
558 #[pallet::error]
559 pub enum Error<T, I = ()> {
560 NotMember,
562 AlreadyMember,
564 Suspended,
566 NotSuspended,
568 NoPayout,
570 AlreadyFounded,
572 InsufficientPot,
574 AlreadyVouching,
576 NotVouchingOnBidder,
578 Head,
580 Founder,
582 AlreadyBid,
584 AlreadyCandidate,
586 NotCandidate,
588 MaxMembers,
590 NotFounder,
592 NotHead,
594 NotApproved,
596 NotRejected,
598 Approved,
600 Rejected,
602 InProgress,
604 TooEarly,
606 Voted,
608 Expired,
610 NotBidder,
612 NoDefender,
614 NotGroup,
616 AlreadyElevated,
618 AlreadyPunished,
620 InsufficientFunds,
622 NoVotes,
624 NoDeposit,
626 }
627
628 #[pallet::event]
629 #[pallet::generate_deposit(pub(super) fn deposit_event)]
630 pub enum Event<T: Config<I>, I: 'static = ()> {
631 Founded { founder: T::AccountId },
633 Bid { candidate_id: T::AccountId, offer: BalanceOf<T, I> },
636 Vouch { candidate_id: T::AccountId, offer: BalanceOf<T, I>, vouching: T::AccountId },
639 AutoUnbid { candidate: T::AccountId },
641 Unbid { candidate: T::AccountId },
643 Unvouch { candidate: T::AccountId },
645 Inducted { primary: T::AccountId, candidates: Vec<T::AccountId> },
648 SuspendedMemberJudgement { who: T::AccountId, judged: bool },
650 CandidateSuspended { candidate: T::AccountId },
652 MemberSuspended { member: T::AccountId },
654 Challenged { member: T::AccountId },
656 Vote { candidate: T::AccountId, voter: T::AccountId, vote: bool },
658 DefenderVote { voter: T::AccountId, vote: bool },
660 NewParams { params: GroupParamsFor<T, I> },
662 Unfounded { founder: T::AccountId },
664 Deposit { value: BalanceOf<T, I> },
666 Elevated { member: T::AccountId, rank: Rank },
668 DepositPoked {
670 who: T::AccountId,
671 old_deposit: BalanceOf<T, I>,
672 new_deposit: BalanceOf<T, I>,
673 },
674 }
675
676 #[deprecated(note = "use `Event` instead")]
678 pub type RawEvent<T, I = ()> = Event<T, I>;
679
680 #[pallet::storage]
682 pub type Parameters<T: Config<I>, I: 'static = ()> =
683 StorageValue<_, GroupParamsFor<T, I>, OptionQuery>;
684
685 #[pallet::storage]
687 pub type Pot<T: Config<I>, I: 'static = ()> = StorageValue<_, BalanceOf<T, I>, ValueQuery>;
688
689 #[pallet::storage]
691 pub type Founder<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId>;
692
693 #[pallet::storage]
695 pub type Head<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId>;
696
697 #[pallet::storage]
700 pub type Rules<T: Config<I>, I: 'static = ()> = StorageValue<_, T::Hash>;
701
702 #[pallet::storage]
704 pub type Members<T: Config<I>, I: 'static = ()> =
705 StorageMap<_, Twox64Concat, T::AccountId, MemberRecord, OptionQuery>;
706
707 #[pallet::storage]
709 pub type Payouts<T: Config<I>, I: 'static = ()> =
710 StorageMap<_, Twox64Concat, T::AccountId, PayoutRecordFor<T, I>, ValueQuery>;
711
712 #[pallet::storage]
714 pub type MemberCount<T: Config<I>, I: 'static = ()> = StorageValue<_, u32, ValueQuery>;
715
716 #[pallet::storage]
719 pub type MemberByIndex<T: Config<I>, I: 'static = ()> =
720 StorageMap<_, Twox64Concat, u32, T::AccountId, OptionQuery>;
721
722 #[pallet::storage]
724 pub type SuspendedMembers<T: Config<I>, I: 'static = ()> =
725 StorageMap<_, Twox64Concat, T::AccountId, MemberRecord, OptionQuery>;
726
727 #[pallet::storage]
729 pub type RoundCount<T: Config<I>, I: 'static = ()> = StorageValue<_, RoundIndex, ValueQuery>;
730
731 #[pallet::storage]
733 pub type Bids<T: Config<I>, I: 'static = ()> =
734 StorageValue<_, BoundedVec<Bid<T::AccountId, BalanceOf<T, I>>, T::MaxBids>, ValueQuery>;
735
736 #[pallet::storage]
737 pub type Candidates<T: Config<I>, I: 'static = ()> = StorageMap<
738 _,
739 Blake2_128Concat,
740 T::AccountId,
741 Candidacy<T::AccountId, BalanceOf<T, I>>,
742 OptionQuery,
743 >;
744
745 #[pallet::storage]
747 pub type Skeptic<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId, OptionQuery>;
748
749 #[pallet::storage]
751 pub type Votes<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
752 _,
753 Twox64Concat,
754 T::AccountId,
755 Twox64Concat,
756 T::AccountId,
757 Vote,
758 OptionQuery,
759 >;
760
761 #[pallet::storage]
763 pub type VoteClearCursor<T: Config<I>, I: 'static = ()> =
764 StorageMap<_, Twox64Concat, T::AccountId, BoundedVec<u8, KeyLenOf<Votes<T, I>>>>;
765
766 #[pallet::storage]
770 pub type NextHead<T: Config<I>, I: 'static = ()> =
771 StorageValue<_, IntakeRecordFor<T, I>, OptionQuery>;
772
773 #[pallet::storage]
775 pub type ChallengeRoundCount<T: Config<I>, I: 'static = ()> =
776 StorageValue<_, RoundIndex, ValueQuery>;
777
778 #[pallet::storage]
780 pub type Defending<T: Config<I>, I: 'static = ()> =
781 StorageValue<_, (T::AccountId, T::AccountId, Tally)>;
782
783 #[pallet::storage]
785 pub type DefenderVotes<T: Config<I>, I: 'static = ()> =
786 StorageDoubleMap<_, Twox64Concat, RoundIndex, Twox64Concat, T::AccountId, Vote>;
787
788 #[pallet::storage]
790 pub type NextIntakeAt<T: Config<I>, I: 'static = ()> = StorageValue<_, BlockNumberFor<T, I>>;
791
792 #[pallet::storage]
794 pub type NextChallengeAt<T: Config<I>, I: 'static = ()> = StorageValue<_, BlockNumberFor<T, I>>;
795
796 #[pallet::hooks]
797 impl<T: Config<I>, I: 'static> Hooks<SystemBlockNumberFor<T>> for Pallet<T, I> {
798 fn on_initialize(_n: SystemBlockNumberFor<T>) -> Weight {
799 let mut weight = Weight::zero();
800 let weights = T::BlockWeights::get();
801 let now = T::BlockNumberProvider::current_block_number();
802
803 let phrase = b"society_rotation";
804 let (seed, _) = T::Randomness::random(phrase);
808 let seed = <[u8; 32]>::decode(&mut TrailingZeroInput::new(seed.as_ref()))
810 .expect("input is padded with zeroes; qed");
811 let mut rng = ChaChaRng::from_seed(seed);
812
813 let is_intake_moment = match Self::period() {
815 Period::Intake { .. } => true,
816 _ => false,
817 };
818 if is_intake_moment {
819 Self::rotate_intake(&mut rng);
820 weight.saturating_accrue(weights.max_block / 20);
821 Self::set_next_intake_at();
822 }
823
824 if now >= Self::next_challenge_at() {
826 Self::rotate_challenge(&mut rng);
827 weight.saturating_accrue(weights.max_block / 20);
828 Self::set_next_challenge_at();
829 }
830
831 weight
832 }
833 }
834
835 #[pallet::genesis_config]
836 #[derive(frame_support::DefaultNoBound)]
837 pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
838 pub pot: BalanceOf<T, I>,
839 }
840
841 #[pallet::genesis_build]
842 impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
843 fn build(&self) {
844 Pot::<T, I>::put(self.pot);
845 }
846 }
847
848 #[pallet::call]
849 impl<T: Config<I>, I: 'static> Pallet<T, I> {
850 #[pallet::call_index(0)]
860 #[pallet::weight(T::WeightInfo::bid())]
861 pub fn bid(origin: OriginFor<T>, value: BalanceOf<T, I>) -> DispatchResult {
862 let who = ensure_signed(origin)?;
863
864 let mut bids = Bids::<T, I>::get();
865 ensure!(!Self::has_bid(&bids, &who), Error::<T, I>::AlreadyBid);
866 ensure!(!Candidates::<T, I>::contains_key(&who), Error::<T, I>::AlreadyCandidate);
867 ensure!(!Members::<T, I>::contains_key(&who), Error::<T, I>::AlreadyMember);
868 ensure!(!SuspendedMembers::<T, I>::contains_key(&who), Error::<T, I>::Suspended);
869
870 let params = Parameters::<T, I>::get().ok_or(Error::<T, I>::NotGroup)?;
871 let deposit = params.candidate_deposit;
872 T::Currency::reserve(&who, deposit)?;
874 Self::insert_bid(&mut bids, &who, value, BidKind::Deposit(deposit));
875
876 Bids::<T, I>::put(bids);
877 Self::deposit_event(Event::<T, I>::Bid { candidate_id: who, offer: value });
878 Ok(())
879 }
880
881 #[pallet::call_index(1)]
889 #[pallet::weight(T::WeightInfo::unbid())]
890 pub fn unbid(origin: OriginFor<T>) -> DispatchResult {
891 let who = ensure_signed(origin)?;
892
893 let mut bids = Bids::<T, I>::get();
894 let pos = bids.iter().position(|bid| bid.who == who).ok_or(Error::<T, I>::NotBidder)?;
895 Self::clean_bid(&bids.remove(pos));
896 Bids::<T, I>::put(bids);
897 Self::deposit_event(Event::<T, I>::Unbid { candidate: who });
898 Ok(())
899 }
900
901 #[pallet::call_index(2)]
919 #[pallet::weight(T::WeightInfo::vouch())]
920 pub fn vouch(
921 origin: OriginFor<T>,
922 who: AccountIdLookupOf<T>,
923 value: BalanceOf<T, I>,
924 tip: BalanceOf<T, I>,
925 ) -> DispatchResult {
926 let voucher = ensure_signed(origin)?;
927 let who = T::Lookup::lookup(who)?;
928
929 let mut bids = Bids::<T, I>::get();
931 ensure!(!Self::has_bid(&bids, &who), Error::<T, I>::AlreadyBid);
932
933 ensure!(!Candidates::<T, I>::contains_key(&who), Error::<T, I>::AlreadyCandidate);
935 ensure!(!Members::<T, I>::contains_key(&who), Error::<T, I>::AlreadyMember);
936 ensure!(!SuspendedMembers::<T, I>::contains_key(&who), Error::<T, I>::Suspended);
937
938 let mut record = Members::<T, I>::get(&voucher).ok_or(Error::<T, I>::NotMember)?;
940 ensure!(record.vouching.is_none(), Error::<T, I>::AlreadyVouching);
941
942 record.vouching = Some(VouchingStatus::Vouching);
944 Self::insert_bid(&mut bids, &who, value, BidKind::Vouch(voucher.clone(), tip));
946
947 Members::<T, I>::insert(&voucher, &record);
949 Bids::<T, I>::put(bids);
950 Self::deposit_event(Event::<T, I>::Vouch {
951 candidate_id: who,
952 offer: value,
953 vouching: voucher,
954 });
955 Ok(())
956 }
957
958 #[pallet::call_index(3)]
966 #[pallet::weight(T::WeightInfo::unvouch())]
967 pub fn unvouch(origin: OriginFor<T>) -> DispatchResult {
968 let voucher = ensure_signed(origin)?;
969
970 let mut bids = Bids::<T, I>::get();
971 let pos = bids
972 .iter()
973 .position(|bid| bid.kind.is_vouch(&voucher))
974 .ok_or(Error::<T, I>::NotVouchingOnBidder)?;
975 let bid = bids.remove(pos);
976 Self::clean_bid(&bid);
977
978 Bids::<T, I>::put(bids);
979 Self::deposit_event(Event::<T, I>::Unvouch { candidate: bid.who });
980 Ok(())
981 }
982
983 #[pallet::call_index(4)]
992 #[pallet::weight(T::WeightInfo::vote())]
993 pub fn vote(
994 origin: OriginFor<T>,
995 candidate: AccountIdLookupOf<T>,
996 approve: bool,
997 ) -> DispatchResultWithPostInfo {
998 let voter = ensure_signed(origin)?;
999 let candidate = T::Lookup::lookup(candidate)?;
1000
1001 let mut candidacy =
1002 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1003 let record = Members::<T, I>::get(&voter).ok_or(Error::<T, I>::NotMember)?;
1004
1005 let first_time = Votes::<T, I>::mutate(&candidate, &voter, |v| {
1006 let first_time = v.is_none();
1007 *v = Some(Self::do_vote(*v, approve, record.rank, &mut candidacy.tally));
1008 first_time
1009 });
1010
1011 Candidates::<T, I>::insert(&candidate, &candidacy);
1012 Self::deposit_event(Event::<T, I>::Vote { candidate, voter, vote: approve });
1013 Ok(if first_time { Pays::No } else { Pays::Yes }.into())
1014 }
1015
1016 #[pallet::call_index(5)]
1024 #[pallet::weight(T::WeightInfo::defender_vote())]
1025 pub fn defender_vote(origin: OriginFor<T>, approve: bool) -> DispatchResultWithPostInfo {
1026 let voter = ensure_signed(origin)?;
1027
1028 let mut defending = Defending::<T, I>::get().ok_or(Error::<T, I>::NoDefender)?;
1029 let record = Members::<T, I>::get(&voter).ok_or(Error::<T, I>::NotMember)?;
1030
1031 let round = ChallengeRoundCount::<T, I>::get();
1032 let first_time = DefenderVotes::<T, I>::mutate(round, &voter, |v| {
1033 let first_time = v.is_none();
1034 *v = Some(Self::do_vote(*v, approve, record.rank, &mut defending.2));
1035 first_time
1036 });
1037
1038 Defending::<T, I>::put(defending);
1039 Self::deposit_event(Event::<T, I>::DefenderVote { voter, vote: approve });
1040 Ok(if first_time { Pays::No } else { Pays::Yes }.into())
1041 }
1042
1043 #[pallet::call_index(6)]
1054 #[pallet::weight(T::WeightInfo::payout())]
1055 pub fn payout(origin: OriginFor<T>) -> DispatchResult {
1056 let who = ensure_signed(origin)?;
1057 ensure!(
1058 Members::<T, I>::get(&who).ok_or(Error::<T, I>::NotMember)?.rank == 0,
1059 Error::<T, I>::NoPayout
1060 );
1061 let mut record = Payouts::<T, I>::get(&who);
1062 let block_number = T::BlockNumberProvider::current_block_number();
1063 if let Some((when, amount)) = record.payouts.first() {
1064 if when <= &block_number {
1065 record.paid = record.paid.checked_add(amount).ok_or(Overflow)?;
1066 T::Currency::transfer(&Self::payouts(), &who, *amount, AllowDeath)?;
1067 record.payouts.remove(0);
1068 Payouts::<T, I>::insert(&who, record);
1069 return Ok(())
1070 }
1071 }
1072 Err(Error::<T, I>::NoPayout)?
1073 }
1074
1075 #[pallet::call_index(7)]
1078 #[pallet::weight(T::WeightInfo::waive_repay())]
1079 pub fn waive_repay(origin: OriginFor<T>, amount: BalanceOf<T, I>) -> DispatchResult {
1080 let who = ensure_signed(origin)?;
1081 let mut record = Members::<T, I>::get(&who).ok_or(Error::<T, I>::NotMember)?;
1082 let mut payout_record = Payouts::<T, I>::get(&who);
1083 ensure!(record.rank == 0, Error::<T, I>::AlreadyElevated);
1084 ensure!(amount >= payout_record.paid, Error::<T, I>::InsufficientFunds);
1085
1086 T::Currency::transfer(&who, &Self::account_id(), payout_record.paid, AllowDeath)?;
1087 payout_record.paid = Zero::zero();
1088 payout_record.payouts.clear();
1089 record.rank = 1;
1090 Members::<T, I>::insert(&who, record);
1091 Payouts::<T, I>::insert(&who, payout_record);
1092 Self::deposit_event(Event::<T, I>::Elevated { member: who, rank: 1 });
1093
1094 Ok(())
1095 }
1096
1097 #[pallet::call_index(8)]
1115 #[pallet::weight(T::WeightInfo::found_society())]
1116 pub fn found_society(
1117 origin: OriginFor<T>,
1118 founder: AccountIdLookupOf<T>,
1119 max_members: u32,
1120 max_intake: u32,
1121 max_strikes: u32,
1122 candidate_deposit: BalanceOf<T, I>,
1123 rules: Vec<u8>,
1124 ) -> DispatchResult {
1125 T::FounderSetOrigin::ensure_origin(origin)?;
1126 let founder = T::Lookup::lookup(founder)?;
1127 ensure!(!Head::<T, I>::exists(), Error::<T, I>::AlreadyFounded);
1128 ensure!(max_members > 1, Error::<T, I>::MaxMembers);
1129 let params = GroupParams { max_members, max_intake, max_strikes, candidate_deposit };
1131 Parameters::<T, I>::put(params);
1132 Self::insert_member(&founder, 1)?;
1133 Head::<T, I>::put(&founder);
1134 Founder::<T, I>::put(&founder);
1135 Rules::<T, I>::put(T::Hashing::hash(&rules));
1136 Self::deposit_event(Event::<T, I>::Founded { founder });
1137 Ok(())
1138 }
1139
1140 #[pallet::call_index(9)]
1146 #[pallet::weight(T::WeightInfo::dissolve())]
1147 pub fn dissolve(origin: OriginFor<T>) -> DispatchResult {
1148 let founder = ensure_signed(origin)?;
1149 ensure!(Founder::<T, I>::get().as_ref() == Some(&founder), Error::<T, I>::NotFounder);
1150 ensure!(MemberCount::<T, I>::get() == 1, Error::<T, I>::NotHead);
1151
1152 let _ = Members::<T, I>::clear(u32::MAX, None);
1153 MemberCount::<T, I>::kill();
1154 let _ = MemberByIndex::<T, I>::clear(u32::MAX, None);
1155 let _ = SuspendedMembers::<T, I>::clear(u32::MAX, None);
1156 let _ = Payouts::<T, I>::clear(u32::MAX, None);
1157 let _ = Votes::<T, I>::clear(u32::MAX, None);
1158 let _ = VoteClearCursor::<T, I>::clear(u32::MAX, None);
1159 Head::<T, I>::kill();
1160 NextHead::<T, I>::kill();
1161 Founder::<T, I>::kill();
1162 Rules::<T, I>::kill();
1163 Parameters::<T, I>::kill();
1164 Pot::<T, I>::kill();
1165 RoundCount::<T, I>::kill();
1166 Bids::<T, I>::kill();
1167 Skeptic::<T, I>::kill();
1168 ChallengeRoundCount::<T, I>::kill();
1169 Defending::<T, I>::kill();
1170 let _ = DefenderVotes::<T, I>::clear(u32::MAX, None);
1171 let _ = Candidates::<T, I>::clear(u32::MAX, None);
1172 Self::deposit_event(Event::<T, I>::Unfounded { founder });
1173 Ok(())
1174 }
1175
1176 #[pallet::call_index(10)]
1191 #[pallet::weight(T::WeightInfo::judge_suspended_member())]
1192 pub fn judge_suspended_member(
1193 origin: OriginFor<T>,
1194 who: AccountIdLookupOf<T>,
1195 forgive: bool,
1196 ) -> DispatchResultWithPostInfo {
1197 ensure!(
1198 Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1199 Error::<T, I>::NotFounder
1200 );
1201 let who = T::Lookup::lookup(who)?;
1202 let record = SuspendedMembers::<T, I>::get(&who).ok_or(Error::<T, I>::NotSuspended)?;
1203 if forgive {
1204 Self::reinstate_member(&who, record.rank)?;
1206 } else {
1207 let payout_record = Payouts::<T, I>::take(&who);
1208 let total = payout_record
1209 .payouts
1210 .into_iter()
1211 .map(|x| x.1)
1212 .fold(Zero::zero(), |acc: BalanceOf<T, I>, x| acc.saturating_add(x));
1213 Self::unreserve_payout(total);
1214 }
1215 SuspendedMembers::<T, I>::remove(&who);
1216 Self::deposit_event(Event::<T, I>::SuspendedMemberJudgement { who, judged: forgive });
1217 Ok(Pays::No.into())
1218 }
1219
1220 #[pallet::call_index(11)]
1233 #[pallet::weight(T::WeightInfo::set_parameters())]
1234 pub fn set_parameters(
1235 origin: OriginFor<T>,
1236 max_members: u32,
1237 max_intake: u32,
1238 max_strikes: u32,
1239 candidate_deposit: BalanceOf<T, I>,
1240 ) -> DispatchResult {
1241 ensure!(
1242 Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1243 Error::<T, I>::NotFounder
1244 );
1245 ensure!(max_members >= MemberCount::<T, I>::get(), Error::<T, I>::MaxMembers);
1246 let params = GroupParams { max_members, max_intake, max_strikes, candidate_deposit };
1247 Parameters::<T, I>::put(¶ms);
1248 Self::deposit_event(Event::<T, I>::NewParams { params });
1249 Ok(())
1250 }
1251
1252 #[pallet::call_index(12)]
1255 #[pallet::weight(T::WeightInfo::punish_skeptic())]
1256 pub fn punish_skeptic(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1257 let candidate = ensure_signed(origin)?;
1258 let mut candidacy =
1259 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1260 ensure!(!candidacy.skeptic_struck, Error::<T, I>::AlreadyPunished);
1261 ensure!(!Self::in_progress(candidacy.round), Error::<T, I>::InProgress);
1262 let punished = Self::check_skeptic(&candidate, &mut candidacy);
1263 Candidates::<T, I>::insert(&candidate, candidacy);
1264 Ok(if punished { Pays::No } else { Pays::Yes }.into())
1265 }
1266
1267 #[pallet::call_index(13)]
1270 #[pallet::weight(T::WeightInfo::claim_membership())]
1271 pub fn claim_membership(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1272 let candidate = ensure_signed(origin)?;
1273 let candidacy =
1274 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1275 ensure!(candidacy.tally.clear_approval(), Error::<T, I>::NotApproved);
1276 ensure!(!Self::in_progress(candidacy.round), Error::<T, I>::InProgress);
1277 Self::induct_member(candidate, candidacy, 0)?;
1278 Ok(Pays::No.into())
1279 }
1280
1281 #[pallet::call_index(14)]
1285 #[pallet::weight(T::WeightInfo::bestow_membership())]
1286 pub fn bestow_membership(
1287 origin: OriginFor<T>,
1288 candidate: T::AccountId,
1289 ) -> DispatchResultWithPostInfo {
1290 ensure!(
1291 Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1292 Error::<T, I>::NotFounder
1293 );
1294 let candidacy =
1295 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1296 ensure!(!candidacy.tally.clear_rejection(), Error::<T, I>::Rejected);
1297 ensure!(!Self::in_progress(candidacy.round), Error::<T, I>::InProgress);
1298 Self::induct_member(candidate, candidacy, 0)?;
1299 Ok(Pays::No.into())
1300 }
1301
1302 #[pallet::call_index(15)]
1308 #[pallet::weight(T::WeightInfo::kick_candidate())]
1309 pub fn kick_candidate(
1310 origin: OriginFor<T>,
1311 candidate: T::AccountId,
1312 ) -> DispatchResultWithPostInfo {
1313 ensure!(
1314 Some(ensure_signed(origin)?) == Founder::<T, I>::get(),
1315 Error::<T, I>::NotFounder
1316 );
1317 let mut candidacy =
1318 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1319 ensure!(!Self::in_progress(candidacy.round), Error::<T, I>::InProgress);
1320 ensure!(!candidacy.tally.clear_approval(), Error::<T, I>::Approved);
1321 Self::check_skeptic(&candidate, &mut candidacy);
1322 Self::reject_candidate(&candidate, &candidacy.kind);
1323 Candidates::<T, I>::remove(&candidate);
1324 Ok(Pays::No.into())
1325 }
1326
1327 #[pallet::call_index(16)]
1331 #[pallet::weight(T::WeightInfo::resign_candidacy())]
1332 pub fn resign_candidacy(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1333 let candidate = ensure_signed(origin)?;
1334 let mut candidacy =
1335 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1336 if !Self::in_progress(candidacy.round) {
1337 Self::check_skeptic(&candidate, &mut candidacy);
1338 }
1339 Self::reject_candidate(&candidate, &candidacy.kind);
1340 Candidates::<T, I>::remove(&candidate);
1341 Ok(Pays::No.into())
1342 }
1343
1344 #[pallet::call_index(17)]
1350 #[pallet::weight(T::WeightInfo::drop_candidate())]
1351 pub fn drop_candidate(
1352 origin: OriginFor<T>,
1353 candidate: T::AccountId,
1354 ) -> DispatchResultWithPostInfo {
1355 ensure_signed(origin)?;
1356 let candidacy =
1357 Candidates::<T, I>::get(&candidate).ok_or(Error::<T, I>::NotCandidate)?;
1358 ensure!(candidacy.tally.clear_rejection(), Error::<T, I>::NotRejected);
1359 ensure!(RoundCount::<T, I>::get() > candidacy.round + 1, Error::<T, I>::TooEarly);
1360 Self::reject_candidate(&candidate, &candidacy.kind);
1361 Candidates::<T, I>::remove(&candidate);
1362 Ok(Pays::No.into())
1363 }
1364
1365 #[pallet::call_index(18)]
1369 #[pallet::weight(T::WeightInfo::cleanup_candidacy())]
1370 pub fn cleanup_candidacy(
1371 origin: OriginFor<T>,
1372 candidate: T::AccountId,
1373 max: u32,
1374 ) -> DispatchResultWithPostInfo {
1375 ensure_signed(origin)?;
1376 ensure!(!Candidates::<T, I>::contains_key(&candidate), Error::<T, I>::InProgress);
1377 let maybe_cursor = VoteClearCursor::<T, I>::get(&candidate);
1378 let r =
1379 Votes::<T, I>::clear_prefix(&candidate, max, maybe_cursor.as_ref().map(|x| &x[..]));
1380 if let Some(cursor) = r.maybe_cursor {
1381 VoteClearCursor::<T, I>::insert(&candidate, BoundedVec::truncate_from(cursor));
1382 }
1383 Ok(if r.loops == 0 { Pays::Yes } else { Pays::No }.into())
1384 }
1385
1386 #[pallet::call_index(19)]
1390 #[pallet::weight(T::WeightInfo::cleanup_challenge())]
1391 pub fn cleanup_challenge(
1392 origin: OriginFor<T>,
1393 challenge_round: RoundIndex,
1394 max: u32,
1395 ) -> DispatchResultWithPostInfo {
1396 ensure_signed(origin)?;
1397 ensure!(
1398 challenge_round < ChallengeRoundCount::<T, I>::get(),
1399 Error::<T, I>::InProgress
1400 );
1401 let _ = DefenderVotes::<T, I>::clear_prefix(challenge_round, max, None);
1402 Ok(Pays::No.into())
1406 }
1407
1408 #[pallet::call_index(20)]
1416 #[pallet::weight(T::WeightInfo::poke_deposit())]
1417 pub fn poke_deposit(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
1418 let who = ensure_signed(origin)?;
1419
1420 let mut bids = Bids::<T, I>::get();
1422 let bid = bids.iter_mut().find(|bid| bid.who == who).ok_or(Error::<T, I>::NotBidder)?;
1423
1424 let old_deposit = match &bid.kind {
1426 BidKind::Deposit(amount) => *amount,
1427 _ => return Err(Error::<T, I>::NoDeposit.into()),
1428 };
1429
1430 let params = Parameters::<T, I>::get().ok_or(Error::<T, I>::NotGroup)?;
1431 let new_deposit = params.candidate_deposit;
1432
1433 if old_deposit == new_deposit {
1434 return Ok(Pays::Yes.into());
1435 }
1436
1437 if new_deposit > old_deposit {
1438 let extra = new_deposit.saturating_sub(old_deposit);
1440 T::Currency::reserve(&who, extra)?;
1441 } else {
1442 let excess = old_deposit.saturating_sub(new_deposit);
1444 let remaining_unreserved = T::Currency::unreserve(&who, excess);
1445 if !remaining_unreserved.is_zero() {
1446 defensive!(
1447 "Failed to unreserve for full amount for bid (Requested, Actual)",
1448 (excess, excess.saturating_sub(remaining_unreserved))
1449 );
1450 }
1451 }
1452
1453 bid.kind = BidKind::Deposit(new_deposit);
1454 Bids::<T, I>::put(bids);
1455
1456 Self::deposit_event(Event::<T, I>::DepositPoked {
1457 who: who.clone(),
1458 old_deposit,
1459 new_deposit,
1460 });
1461
1462 Ok(Pays::No.into())
1463 }
1464 }
1465}
1466
1467pub struct EnsureFounder<T>(core::marker::PhantomData<T>);
1469impl<T: Config> EnsureOrigin<<T as frame_system::Config>::RuntimeOrigin> for EnsureFounder<T> {
1470 type Success = T::AccountId;
1471 fn try_origin(o: T::RuntimeOrigin) -> Result<Self::Success, T::RuntimeOrigin> {
1472 match (o.as_signer(), Founder::<T>::get()) {
1473 (Some(who), Some(f)) if *who == f => Ok(f),
1474 _ => Err(o),
1475 }
1476 }
1477
1478 #[cfg(feature = "runtime-benchmarks")]
1479 fn try_successful_origin() -> Result<T::RuntimeOrigin, ()> {
1480 let founder = Founder::<T>::get().ok_or(())?;
1481 Ok(T::RuntimeOrigin::from(frame_system::RawOrigin::Signed(founder)))
1482 }
1483}
1484
1485impl_ensure_origin_with_arg_ignoring_arg! {
1486 impl<{ T: Config, A }>
1487 EnsureOriginWithArg<T::RuntimeOrigin, A> for EnsureFounder<T>
1488 {}
1489}
1490
1491#[derive(Debug, PartialEq, Eq)]
1492pub enum Period<BlockNumber> {
1493 Voting { elapsed: BlockNumber, more: BlockNumber },
1494 Claim { elapsed: BlockNumber, more: BlockNumber },
1495 Intake { elapsed: BlockNumber },
1496}
1497
1498impl<T: Config<I>, I: 'static> Pallet<T, I> {
1499 fn period() -> Period<BlockNumberFor<T, I>> {
1501 let claim_period = T::ClaimPeriod::get();
1502 let voting_period = T::VotingPeriod::get();
1503 let rotation_period = voting_period + claim_period;
1504 let now = T::BlockNumberProvider::current_block_number();
1505 let phase = now % rotation_period;
1506 if now >= Self::next_intake_at() {
1507 Period::Intake { elapsed: now - Self::next_intake_at() }
1508 } else if phase < voting_period {
1509 Period::Voting { elapsed: phase, more: voting_period - phase }
1510 } else {
1511 Period::Claim { elapsed: phase - voting_period, more: rotation_period - phase }
1512 }
1513 }
1514
1515 pub fn next_intake_at() -> BlockNumberFor<T, I> {
1519 match NextIntakeAt::<T, I>::get() {
1520 Some(next) => next,
1521 None => {
1522 let now = T::BlockNumberProvider::current_block_number();
1524 let prev_block = now.saturating_sub(BlockNumberFor::<T, I>::one());
1525 let rotation_period = T::VotingPeriod::get().saturating_add(T::ClaimPeriod::get());
1526 let elapsed = prev_block % rotation_period;
1527 let next_intake_at = prev_block + (rotation_period - elapsed);
1528 NextIntakeAt::<T, I>::put(next_intake_at);
1529 next_intake_at
1530 },
1531 }
1532 }
1533
1534 fn set_next_intake_at() {
1538 let prev_next_intake_at = Self::next_intake_at();
1539 let next_intake_at = prev_next_intake_at
1540 .saturating_add(T::VotingPeriod::get().saturating_add(T::ClaimPeriod::get()));
1541 NextIntakeAt::<T, I>::put(next_intake_at);
1542 }
1543
1544 pub fn next_challenge_at() -> BlockNumberFor<T, I> {
1548 match NextChallengeAt::<T, I>::get() {
1549 Some(next) => next,
1550 None => {
1551 let now = T::BlockNumberProvider::current_block_number();
1553 let prev_block = now.saturating_sub(BlockNumberFor::<T, I>::one());
1554 let challenge_period = T::ChallengePeriod::get();
1555 let elapsed = prev_block % challenge_period;
1556 let next_challenge_at = prev_block + (challenge_period - elapsed);
1557 NextChallengeAt::<T, I>::put(next_challenge_at);
1558 next_challenge_at
1559 },
1560 }
1561 }
1562
1563 fn set_next_challenge_at() {
1567 let prev_next_challenge_at = Self::next_challenge_at();
1568 let next_challenge_at = prev_next_challenge_at.saturating_add(T::ChallengePeriod::get());
1569 NextChallengeAt::<T, I>::put(next_challenge_at);
1570 }
1571
1572 fn in_progress(target_round: RoundIndex) -> bool {
1574 let round = RoundCount::<T, I>::get();
1575 target_round == round && matches!(Self::period(), Period::Voting { .. })
1576 }
1577
1578 fn do_vote(maybe_old: Option<Vote>, approve: bool, rank: Rank, tally: &mut Tally) -> Vote {
1580 match maybe_old {
1581 Some(Vote { approve: true, weight }) => tally.approvals.saturating_reduce(weight),
1582 Some(Vote { approve: false, weight }) => tally.rejections.saturating_reduce(weight),
1583 _ => {},
1584 }
1585 let weight_root = rank + 1;
1586 let weight = weight_root * weight_root;
1587 match approve {
1588 true => tally.approvals.saturating_accrue(weight),
1589 false => tally.rejections.saturating_accrue(weight),
1590 }
1591 Vote { approve, weight }
1592 }
1593
1594 fn check_skeptic(
1596 candidate: &T::AccountId,
1597 candidacy: &mut Candidacy<T::AccountId, BalanceOf<T, I>>,
1598 ) -> bool {
1599 if RoundCount::<T, I>::get() != candidacy.round || candidacy.skeptic_struck {
1600 return false
1601 }
1602 let skeptic = match Skeptic::<T, I>::get() {
1604 Some(s) => s,
1605 None => return false,
1606 };
1607 let maybe_vote = Votes::<T, I>::get(&candidate, &skeptic);
1608 let approved = candidacy.tally.clear_approval();
1609 let rejected = candidacy.tally.clear_rejection();
1610 match (maybe_vote, approved, rejected) {
1611 (None, _, _) |
1612 (Some(Vote { approve: true, .. }), false, true) |
1613 (Some(Vote { approve: false, .. }), true, false) => {
1614 if Self::strike_member(&skeptic).is_ok() {
1616 candidacy.skeptic_struck = true;
1617 true
1618 } else {
1619 false
1620 }
1621 },
1622 _ => false,
1623 }
1624 }
1625
1626 fn rotate_challenge(rng: &mut impl RngCore) {
1628 let mut next_defender = None;
1629 let mut round = ChallengeRoundCount::<T, I>::get();
1630
1631 if let Some((defender, skeptic, tally)) = Defending::<T, I>::get() {
1633 if !tally.more_approvals() {
1635 let _ = Self::suspend_member(&defender);
1638 }
1639
1640 let skeptic_vote = DefenderVotes::<T, I>::get(round, &skeptic);
1642 match (skeptic_vote, tally.more_approvals(), tally.more_rejections()) {
1643 (None, _, _) |
1644 (Some(Vote { approve: true, .. }), false, true) |
1645 (Some(Vote { approve: false, .. }), true, false) => {
1646 let _ = Self::strike_member(&skeptic);
1648 let founder = Founder::<T, I>::get();
1649 let head = Head::<T, I>::get();
1650 if Some(&skeptic) != founder.as_ref() && Some(&skeptic) != head.as_ref() {
1651 next_defender = Some(skeptic);
1652 }
1653 },
1654 _ => {},
1655 }
1656 round.saturating_inc();
1657 ChallengeRoundCount::<T, I>::put(round);
1658 }
1659
1660 if MemberCount::<T, I>::get() > 2 {
1663 let defender = next_defender
1664 .or_else(|| Self::pick_defendant(rng))
1665 .expect("exited if members empty; qed");
1666 let skeptic =
1667 Self::pick_member_except(rng, &defender).expect("exited if members empty; qed");
1668 Self::deposit_event(Event::<T, I>::Challenged { member: defender.clone() });
1669 Defending::<T, I>::put((defender, skeptic, Tally::default()));
1670 } else {
1671 Defending::<T, I>::kill();
1672 }
1673 }
1674
1675 fn rotate_intake(rng: &mut impl RngCore) {
1682 let member_count = MemberCount::<T, I>::get();
1684 if member_count < 1 {
1685 return
1686 }
1687 let maybe_head = NextHead::<T, I>::take();
1688 if let Some(head) = maybe_head {
1689 Head::<T, I>::put(&head.who);
1690 }
1691
1692 let mut pot = Pot::<T, I>::get();
1695 let unaccounted = T::Currency::free_balance(&Self::account_id()).saturating_sub(pot);
1696 pot.saturating_accrue(T::PeriodSpend::get().min(unaccounted / 2u8.into()));
1697 Pot::<T, I>::put(&pot);
1698
1699 let mut round_count = RoundCount::<T, I>::get();
1701 round_count.saturating_inc();
1702 let candidate_count = Self::select_new_candidates(round_count, member_count, pot);
1703 if candidate_count > 0 {
1704 let skeptic = Self::pick_member(rng).expect("exited if members empty; qed");
1706 Skeptic::<T, I>::put(skeptic);
1707 }
1708 RoundCount::<T, I>::put(round_count);
1709 }
1710
1711 pub fn select_new_candidates(
1719 round: RoundIndex,
1720 member_count: u32,
1721 pot: BalanceOf<T, I>,
1722 ) -> u32 {
1723 let mut bids = Bids::<T, I>::get();
1725 let params = match Parameters::<T, I>::get() {
1726 Some(params) => params,
1727 None => return 0,
1728 };
1729 let max_selections: u32 = params
1730 .max_intake
1731 .min(params.max_members.saturating_sub(member_count))
1732 .min(bids.len() as u32);
1733
1734 let mut selections = 0;
1735 let mut total_cost: BalanceOf<T, I> = Zero::zero();
1737
1738 bids.retain(|bid| {
1739 total_cost.saturating_accrue(bid.value);
1741 let accept = selections < max_selections &&
1742 (!bid.value.is_zero() || selections == 0) &&
1743 total_cost <= pot;
1744 if accept {
1745 let candidacy = Candidacy {
1746 round,
1747 kind: bid.kind.clone(),
1748 bid: bid.value,
1749 tally: Default::default(),
1750 skeptic_struck: false,
1751 };
1752 Candidates::<T, I>::insert(&bid.who, candidacy);
1753 selections.saturating_inc();
1754 }
1755 !accept
1756 });
1757
1758 Bids::<T, I>::put(&bids);
1760 selections
1761 }
1762
1763 fn insert_bid(
1766 bids: &mut BoundedVec<Bid<T::AccountId, BalanceOf<T, I>>, T::MaxBids>,
1767 who: &T::AccountId,
1768 value: BalanceOf<T, I>,
1769 bid_kind: BidKind<T::AccountId, BalanceOf<T, I>>,
1770 ) {
1771 let pos = bids.iter().position(|bid| bid.value > value).unwrap_or(bids.len());
1772 let r = bids.force_insert_keep_left(pos, Bid { value, who: who.clone(), kind: bid_kind });
1773 let maybe_discarded = match r {
1774 Ok(x) => x,
1775 Err(x) => Some(x),
1776 };
1777 if let Some(discarded) = maybe_discarded {
1778 Self::clean_bid(&discarded);
1779 Self::deposit_event(Event::<T, I>::AutoUnbid { candidate: discarded.who });
1780 }
1781 }
1782
1783 fn clean_bid(bid: &Bid<T::AccountId, BalanceOf<T, I>>) {
1791 match &bid.kind {
1792 BidKind::Deposit(deposit) => {
1793 let err_amount = T::Currency::unreserve(&bid.who, *deposit);
1794 debug_assert!(err_amount.is_zero());
1795 },
1796 BidKind::Vouch(voucher, _) => {
1797 Members::<T, I>::mutate_extant(voucher, |record| record.vouching = None);
1798 },
1799 }
1800 }
1801
1802 fn reject_candidate(who: &T::AccountId, kind: &BidKind<T::AccountId, BalanceOf<T, I>>) {
1810 match kind {
1811 BidKind::Deposit(deposit) => {
1812 let pot = Self::account_id();
1813 let free = BalanceStatus::Free;
1814 let r = T::Currency::repatriate_reserved(&who, &pot, *deposit, free);
1815 debug_assert!(r.is_ok());
1816 },
1817 BidKind::Vouch(voucher, _) => {
1818 Members::<T, I>::mutate_extant(voucher, |record| {
1819 record.vouching = Some(VouchingStatus::Banned)
1820 });
1821 },
1822 }
1823 }
1824
1825 fn has_bid(bids: &Vec<Bid<T::AccountId, BalanceOf<T, I>>>, who: &T::AccountId) -> bool {
1827 bids.iter().any(|bid| bid.who == *who)
1829 }
1830
1831 fn insert_member(who: &T::AccountId, rank: Rank) -> DispatchResult {
1841 let params = Parameters::<T, I>::get().ok_or(Error::<T, I>::NotGroup)?;
1842 ensure!(MemberCount::<T, I>::get() < params.max_members, Error::<T, I>::MaxMembers);
1843 let index = MemberCount::<T, I>::mutate(|i| {
1844 i.saturating_accrue(1);
1845 *i - 1
1846 });
1847 let record = MemberRecord { rank, strikes: 0, vouching: None, index };
1848 Members::<T, I>::insert(who, record);
1849 MemberByIndex::<T, I>::insert(index, who);
1850 Ok(())
1851 }
1852
1853 fn reinstate_member(who: &T::AccountId, rank: Rank) -> DispatchResult {
1860 Self::insert_member(who, rank)
1861 }
1862
1863 fn add_new_member(who: &T::AccountId, rank: Rank) -> DispatchResult {
1866 Self::insert_member(who, rank)
1867 }
1868
1869 fn induct_member(
1871 candidate: T::AccountId,
1872 mut candidacy: Candidacy<T::AccountId, BalanceOf<T, I>>,
1873 rank: Rank,
1874 ) -> DispatchResult {
1875 Self::add_new_member(&candidate, rank)?;
1876 Self::check_skeptic(&candidate, &mut candidacy);
1877
1878 let next_head = NextHead::<T, I>::get()
1879 .filter(|old| {
1880 old.round > candidacy.round ||
1881 old.round == candidacy.round && old.bid < candidacy.bid
1882 })
1883 .unwrap_or_else(|| IntakeRecord {
1884 who: candidate.clone(),
1885 bid: candidacy.bid,
1886 round: candidacy.round,
1887 });
1888 NextHead::<T, I>::put(next_head);
1889
1890 let now = T::BlockNumberProvider::current_block_number();
1891 let maturity = now + Self::lock_duration(MemberCount::<T, I>::get());
1892 Self::reward_bidder(&candidate, candidacy.bid, candidacy.kind, maturity);
1893
1894 Candidates::<T, I>::remove(&candidate);
1895 Ok(())
1896 }
1897
1898 fn strike_member(who: &T::AccountId) -> DispatchResult {
1899 let mut record = Members::<T, I>::get(who).ok_or(Error::<T, I>::NotMember)?;
1900 record.strikes.saturating_inc();
1901 Members::<T, I>::insert(who, &record);
1902 if record.strikes >= T::GraceStrikes::get() {
1906 let total_payout = Payouts::<T, I>::get(who)
1908 .payouts
1909 .iter()
1910 .fold(BalanceOf::<T, I>::zero(), |acc, x| acc.saturating_add(x.1));
1911 Self::slash_payout(who, total_payout / 2u32.into());
1912 }
1913
1914 let params = Parameters::<T, I>::get().ok_or(Error::<T, I>::NotGroup)?;
1915 if record.strikes >= params.max_strikes {
1916 let _ = Self::suspend_member(who);
1918 }
1919 Ok(())
1920 }
1921
1922 pub fn remove_member(m: &T::AccountId) -> Result<MemberRecord, DispatchError> {
1933 ensure!(Head::<T, I>::get().as_ref() != Some(m), Error::<T, I>::Head);
1934 ensure!(Founder::<T, I>::get().as_ref() != Some(m), Error::<T, I>::Founder);
1935 if let Some(mut record) = Members::<T, I>::get(m) {
1936 let index = record.index;
1937 let last_index = MemberCount::<T, I>::mutate(|i| {
1938 i.saturating_reduce(1);
1939 *i
1940 });
1941 if index != last_index {
1942 if let Some(other) = MemberByIndex::<T, I>::get(last_index) {
1945 MemberByIndex::<T, I>::insert(index, &other);
1946 Members::<T, I>::mutate(other, |m_r| {
1947 if let Some(r) = m_r {
1948 r.index = index
1949 }
1950 });
1951 } else {
1952 debug_assert!(false, "ERROR: No member at the last index position?");
1953 }
1954 }
1955
1956 MemberByIndex::<T, I>::remove(last_index);
1957 Members::<T, I>::remove(m);
1958 if record.vouching.take() == Some(VouchingStatus::Vouching) {
1960 Bids::<T, I>::mutate(|bids|
1963 if let Some(pos) = bids.iter().position(|b| b.kind.is_vouch(&m)) {
1965 let vouched = bids.remove(pos).who;
1967 Self::deposit_event(Event::<T, I>::Unvouch { candidate: vouched });
1968 }
1969 );
1970 }
1971 Ok(record)
1972 } else {
1973 Err(Error::<T, I>::NotMember.into())
1974 }
1975 }
1976
1977 fn suspend_member(who: &T::AccountId) -> DispatchResult {
1983 let record = Self::remove_member(&who)?;
1984 SuspendedMembers::<T, I>::insert(who, record);
1985 Self::deposit_event(Event::<T, I>::MemberSuspended { member: who.clone() });
1986 Ok(())
1987 }
1988
1989 fn pick_member(rng: &mut impl RngCore) -> Option<T::AccountId> {
1993 let member_count = MemberCount::<T, I>::get();
1994 if member_count == 0 {
1995 return None
1996 }
1997 let random_index = rng.next_u32() % member_count;
1998 MemberByIndex::<T, I>::get(random_index)
1999 }
2000
2001 fn pick_member_except(
2006 rng: &mut impl RngCore,
2007 exception: &T::AccountId,
2008 ) -> Option<T::AccountId> {
2009 let member_count = MemberCount::<T, I>::get();
2010 if member_count <= 1 {
2011 return None
2012 }
2013 let random_index = rng.next_u32() % (member_count - 1);
2014 let pick = MemberByIndex::<T, I>::get(random_index);
2015 if pick.as_ref() == Some(exception) {
2016 MemberByIndex::<T, I>::get(member_count - 1)
2017 } else {
2018 pick
2019 }
2020 }
2021
2022 fn pick_defendant(rng: &mut impl RngCore) -> Option<T::AccountId> {
2027 let member_count = MemberCount::<T, I>::get();
2028 if member_count <= 2 {
2029 return None
2030 }
2031 let head = Head::<T, I>::get();
2035 let pickable_count = member_count - if head.is_some() { 2 } else { 1 };
2036 let random_index = rng.next_u32() % pickable_count + 1;
2037 let pick = MemberByIndex::<T, I>::get(random_index);
2038 if pick == head && head.is_some() {
2039 MemberByIndex::<T, I>::get(member_count - 1)
2042 } else {
2043 pick
2044 }
2045 }
2046
2047 fn reward_bidder(
2049 candidate: &T::AccountId,
2050 value: BalanceOf<T, I>,
2051 kind: BidKind<T::AccountId, BalanceOf<T, I>>,
2052 maturity: BlockNumberFor<T, I>,
2053 ) {
2054 let value = match kind {
2055 BidKind::Deposit(deposit) => {
2056 let err_amount = T::Currency::unreserve(candidate, deposit);
2059 debug_assert!(err_amount.is_zero());
2060 value
2061 },
2062 BidKind::Vouch(voucher, tip) => {
2063 if let Some(mut record) = Members::<T, I>::get(&voucher) {
2066 if let Some(VouchingStatus::Vouching) = record.vouching {
2067 record.vouching = None;
2070 Self::bump_payout(&voucher, maturity, tip.min(value));
2071 Members::<T, I>::insert(&voucher, record);
2072 value.saturating_sub(tip)
2073 } else {
2074 value
2075 }
2076 } else {
2077 value
2078 }
2079 },
2080 };
2081
2082 Self::bump_payout(candidate, maturity, value);
2083 }
2084
2085 fn bump_payout(who: &T::AccountId, when: BlockNumberFor<T, I>, value: BalanceOf<T, I>) {
2090 if value.is_zero() {
2091 return
2092 }
2093 if let Some(MemberRecord { rank: 0, .. }) = Members::<T, I>::get(who) {
2094 Payouts::<T, I>::mutate(who, |record| {
2095 match record.payouts.binary_search_by_key(&when, |x| x.0) {
2097 Ok(index) => record.payouts[index].1.saturating_accrue(value),
2098 Err(index) => {
2099 let _ = record.payouts.try_insert(index, (when, value));
2101 },
2102 }
2103 });
2104 Self::reserve_payout(value);
2105 }
2106 }
2107
2108 fn slash_payout(who: &T::AccountId, value: BalanceOf<T, I>) -> BalanceOf<T, I> {
2110 let mut record = Payouts::<T, I>::get(who);
2111 let mut rest = value;
2112 while !record.payouts.is_empty() {
2113 if let Some(new_rest) = rest.checked_sub(&record.payouts[0].1) {
2114 rest = new_rest;
2116 record.payouts.remove(0);
2117 } else {
2118 record.payouts[0].1.saturating_reduce(rest);
2120 rest = Zero::zero();
2121 break
2122 }
2123 }
2124 Payouts::<T, I>::insert(who, record);
2125 value - rest
2126 }
2127
2128 fn reserve_payout(amount: BalanceOf<T, I>) {
2131 Pot::<T, I>::mutate(|pot| pot.saturating_reduce(amount));
2133
2134 let res = T::Currency::transfer(&Self::account_id(), &Self::payouts(), amount, AllowDeath);
2137 debug_assert!(res.is_ok());
2138 }
2139
2140 fn unreserve_payout(amount: BalanceOf<T, I>) {
2143 Pot::<T, I>::mutate(|pot| pot.saturating_accrue(amount));
2145
2146 let res = T::Currency::transfer(&Self::payouts(), &Self::account_id(), amount, AllowDeath);
2149 debug_assert!(res.is_ok());
2150 }
2151
2152 pub fn account_id() -> T::AccountId {
2157 T::PalletId::get().into_account_truncating()
2158 }
2159
2160 pub fn payouts() -> T::AccountId {
2165 T::PalletId::get().into_sub_account_truncating(b"payouts")
2166 }
2167
2168 fn lock_duration(x: u32) -> BlockNumberFor<T, I> {
2173 let lock_pc = 100 - 50_000 / (x + 500);
2174 Percent::from_percent(lock_pc as u8) * T::MaxLockDuration::get()
2175 }
2176}
2177
2178impl<T: Config<I>, I: 'static> OnUnbalanced<NegativeImbalanceOf<T, I>> for Pallet<T, I> {
2179 fn on_nonzero_unbalanced(amount: NegativeImbalanceOf<T, I>) {
2180 let numeric_amount = amount.peek();
2181
2182 let _ = T::Currency::resolve_creating(&Self::account_id(), amount);
2184
2185 Self::deposit_event(Event::<T, I>::Deposit { value: numeric_amount });
2186 }
2187}