1#![cfg_attr(not(feature = "std"), no_std)]
85
86#[cfg(test)]
87mod mock;
88#[cfg(test)]
89mod tests;
90
91#[cfg(feature = "runtime-benchmarks")]
92mod benchmarking;
93pub mod migration;
94mod types;
95pub mod weights;
96
97extern crate alloc;
98
99use alloc::{boxed::Box, vec, vec::Vec};
100use codec::{Decode, Encode, MaxEncodedLen};
101use frame_support::pallet_prelude::*;
102use frame_system::pallet_prelude::*;
103use sp_runtime::{
104 traits::{Dispatchable, Saturating, StaticLookup, Zero},
105 DispatchError, RuntimeDebug,
106};
107
108use frame_support::{
109 dispatch::{DispatchResult, DispatchResultWithPostInfo, GetDispatchInfo, PostDispatchInfo},
110 ensure,
111 traits::{
112 ChangeMembers, Currency, Get, InitializeMembers, IsSubType, OnUnbalanced,
113 ReservableCurrency,
114 },
115 weights::Weight,
116};
117use scale_info::TypeInfo;
118
119pub use pallet::*;
120pub use types::*;
121pub use weights::*;
122
123pub const LOG_TARGET: &str = "runtime::alliance";
125
126pub type ProposalIndex = u32;
128
129type UrlOf<T, I> = BoundedVec<u8, <T as pallet::Config<I>>::MaxWebsiteUrlLength>;
130
131type BalanceOf<T, I> =
132 <<T as Config<I>>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
133type NegativeImbalanceOf<T, I> = <<T as Config<I>>::Currency as Currency<
134 <T as frame_system::Config>::AccountId,
135>>::NegativeImbalance;
136
137pub trait IdentityVerifier<AccountId> {
139 fn has_required_identities(who: &AccountId) -> bool;
142
143 fn has_good_judgement(who: &AccountId) -> bool;
145
146 fn super_account_id(who: &AccountId) -> Option<AccountId>;
149}
150
151impl<AccountId> IdentityVerifier<AccountId> for () {
153 fn has_required_identities(_who: &AccountId) -> bool {
154 true
155 }
156
157 fn has_good_judgement(_who: &AccountId) -> bool {
158 true
159 }
160
161 fn super_account_id(_who: &AccountId) -> Option<AccountId> {
162 None
163 }
164}
165
166pub trait ProposalProvider<AccountId, Hash, Proposal> {
168 fn propose_proposal(
171 who: AccountId,
172 threshold: u32,
173 proposal: Box<Proposal>,
174 length_bound: u32,
175 ) -> Result<(u32, u32), DispatchError>;
176
177 fn vote_proposal(
180 who: AccountId,
181 proposal: Hash,
182 index: ProposalIndex,
183 approve: bool,
184 ) -> Result<bool, DispatchError>;
185
186 fn close_proposal(
188 proposal_hash: Hash,
189 index: ProposalIndex,
190 proposal_weight_bound: Weight,
191 length_bound: u32,
192 ) -> DispatchResultWithPostInfo;
193
194 fn proposal_of(proposal_hash: Hash) -> Option<Proposal>;
196}
197
198#[derive(Copy, Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]
200pub enum MemberRole {
201 Fellow,
202 Ally,
203 Retiring,
204}
205
206#[derive(
208 Clone,
209 PartialEq,
210 Eq,
211 RuntimeDebug,
212 Encode,
213 Decode,
214 DecodeWithMemTracking,
215 TypeInfo,
216 MaxEncodedLen,
217)]
218pub enum UnscrupulousItem<AccountId, Url> {
219 AccountId(AccountId),
220 Website(Url),
221}
222
223type UnscrupulousItemOf<T, I> =
224 UnscrupulousItem<<T as frame_system::Config>::AccountId, UrlOf<T, I>>;
225
226type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
227
228#[frame_support::pallet]
229pub mod pallet {
230 use super::*;
231
232 #[pallet::pallet]
233 #[pallet::storage_version(migration::STORAGE_VERSION)]
234 pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
235
236 #[pallet::config]
237 pub trait Config<I: 'static = ()>: frame_system::Config {
238 #[allow(deprecated)]
240 type RuntimeEvent: From<Event<Self, I>>
241 + IsType<<Self as frame_system::Config>::RuntimeEvent>;
242
243 type Proposal: Parameter
245 + Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
246 + From<frame_system::Call<Self>>
247 + From<Call<Self, I>>
248 + GetDispatchInfo
249 + IsSubType<Call<Self, I>>
250 + IsType<<Self as frame_system::Config>::RuntimeCall>;
251
252 type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
254
255 type MembershipManager: EnsureOrigin<Self::RuntimeOrigin>;
257
258 type AnnouncementOrigin: EnsureOrigin<Self::RuntimeOrigin>;
260
261 type Currency: ReservableCurrency<Self::AccountId>;
263
264 type Slashed: OnUnbalanced<NegativeImbalanceOf<Self, I>>;
266
267 type InitializeMembers: InitializeMembers<Self::AccountId>;
269
270 type MembershipChanged: ChangeMembers<Self::AccountId>;
272
273 type IdentityVerifier: IdentityVerifier<Self::AccountId>;
275
276 type ProposalProvider: ProposalProvider<Self::AccountId, Self::Hash, Self::Proposal>;
278
279 type MaxProposals: Get<ProposalIndex>;
281
282 type MaxFellows: Get<u32>;
288
289 type MaxAllies: Get<u32>;
295
296 #[pallet::constant]
298 type MaxUnscrupulousItems: Get<u32>;
299
300 #[pallet::constant]
302 type MaxWebsiteUrlLength: Get<u32>;
303
304 #[pallet::constant]
306 type AllyDeposit: Get<BalanceOf<Self, I>>;
307
308 #[pallet::constant]
310 type MaxAnnouncementsCount: Get<u32>;
311
312 #[pallet::constant]
314 type MaxMembersCount: Get<u32>;
315
316 type WeightInfo: WeightInfo;
318
319 type RetirementPeriod: Get<BlockNumberFor<Self>>;
322 }
323
324 #[pallet::error]
325 pub enum Error<T, I = ()> {
326 AllianceNotYetInitialized,
328 AllianceAlreadyInitialized,
330 AlreadyMember,
332 NotMember,
334 NotAlly,
336 NoVotingRights,
338 AlreadyElevated,
340 AlreadyUnscrupulous,
342 AccountNonGrata,
345 NotListedAsUnscrupulous,
347 TooManyUnscrupulousItems,
349 TooLongWebsiteUrl,
351 InsufficientFunds,
353 WithoutRequiredIdentityFields,
355 WithoutGoodIdentityJudgement,
357 MissingProposalHash,
359 MissingAnnouncement,
361 TooManyMembers,
363 TooManyAnnouncements,
365 BadWitness,
367 AlreadyRetiring,
369 RetirementNoticeNotGiven,
371 RetirementPeriodNotPassed,
373 FellowsMissing,
375 }
376
377 #[pallet::event]
378 #[pallet::generate_deposit(pub(super) fn deposit_event)]
379 pub enum Event<T: Config<I>, I: 'static = ()> {
380 NewRuleSet { rule: Cid },
382 Announced { announcement: Cid },
384 AnnouncementRemoved { announcement: Cid },
386 MembersInitialized { fellows: Vec<T::AccountId>, allies: Vec<T::AccountId> },
388 NewAllyJoined {
390 ally: T::AccountId,
391 nominator: Option<T::AccountId>,
392 reserved: Option<BalanceOf<T, I>>,
393 },
394 AllyElevated { ally: T::AccountId },
396 MemberRetirementPeriodStarted { member: T::AccountId },
398 MemberRetired { member: T::AccountId, unreserved: Option<BalanceOf<T, I>> },
400 MemberKicked { member: T::AccountId, slashed: Option<BalanceOf<T, I>> },
402 UnscrupulousItemAdded { items: Vec<UnscrupulousItemOf<T, I>> },
404 UnscrupulousItemRemoved { items: Vec<UnscrupulousItemOf<T, I>> },
406 AllianceDisbanded { fellow_members: u32, ally_members: u32, unreserved: u32 },
408 FellowAbdicated { fellow: T::AccountId },
410 }
411
412 #[pallet::genesis_config]
413 #[derive(frame_support::DefaultNoBound)]
414 pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
415 pub fellows: Vec<T::AccountId>,
416 pub allies: Vec<T::AccountId>,
417 #[serde(skip)]
418 pub phantom: PhantomData<(T, I)>,
419 }
420
421 #[pallet::genesis_build]
422 impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
423 fn build(&self) {
424 for m in self.fellows.iter().chain(self.allies.iter()) {
425 assert!(Pallet::<T, I>::has_identity(m).is_ok(), "Member does not set identity!");
426 }
427
428 if !self.fellows.is_empty() {
429 assert!(
430 !Pallet::<T, I>::has_member(MemberRole::Fellow),
431 "Fellows are already initialized!"
432 );
433 let members: BoundedVec<T::AccountId, T::MaxMembersCount> =
434 self.fellows.clone().try_into().expect("Too many genesis fellows");
435 Members::<T, I>::insert(MemberRole::Fellow, members);
436 }
437 if !self.allies.is_empty() {
438 assert!(
439 !Pallet::<T, I>::has_member(MemberRole::Ally),
440 "Allies are already initialized!"
441 );
442 assert!(
443 !self.fellows.is_empty(),
444 "Fellows must be provided to initialize the Alliance"
445 );
446 let members: BoundedVec<T::AccountId, T::MaxMembersCount> =
447 self.allies.clone().try_into().expect("Too many genesis allies");
448 Members::<T, I>::insert(MemberRole::Ally, members);
449 }
450
451 T::InitializeMembers::initialize_members(self.fellows.as_slice())
452 }
453 }
454
455 #[pallet::storage]
458 pub type Rule<T: Config<I>, I: 'static = ()> = StorageValue<_, Cid, OptionQuery>;
459
460 #[pallet::storage]
462 pub type Announcements<T: Config<I>, I: 'static = ()> =
463 StorageValue<_, BoundedVec<Cid, T::MaxAnnouncementsCount>, ValueQuery>;
464
465 #[pallet::storage]
467 pub type DepositOf<T: Config<I>, I: 'static = ()> =
468 StorageMap<_, Blake2_128Concat, T::AccountId, BalanceOf<T, I>, OptionQuery>;
469
470 #[pallet::storage]
472 pub type Members<T: Config<I>, I: 'static = ()> = StorageMap<
473 _,
474 Twox64Concat,
475 MemberRole,
476 BoundedVec<T::AccountId, T::MaxMembersCount>,
477 ValueQuery,
478 >;
479
480 #[pallet::storage]
483 pub type RetiringMembers<T: Config<I>, I: 'static = ()> =
484 StorageMap<_, Blake2_128Concat, T::AccountId, BlockNumberFor<T>, OptionQuery>;
485
486 #[pallet::storage]
489 pub type UnscrupulousAccounts<T: Config<I>, I: 'static = ()> =
490 StorageValue<_, BoundedVec<T::AccountId, T::MaxUnscrupulousItems>, ValueQuery>;
491
492 #[pallet::storage]
494 pub type UnscrupulousWebsites<T: Config<I>, I: 'static = ()> =
495 StorageValue<_, BoundedVec<UrlOf<T, I>, T::MaxUnscrupulousItems>, ValueQuery>;
496
497 #[pallet::call(weight(<T as Config<I>>::WeightInfo))]
498 impl<T: Config<I>, I: 'static> Pallet<T, I> {
499 #[pallet::call_index(0)]
503 #[pallet::weight(T::WeightInfo::propose_proposed(
504 *length_bound, T::MaxFellows::get(), T::MaxProposals::get(), ))]
508 pub fn propose(
509 origin: OriginFor<T>,
510 #[pallet::compact] threshold: u32,
511 proposal: Box<<T as Config<I>>::Proposal>,
512 #[pallet::compact] length_bound: u32,
513 ) -> DispatchResult {
514 let proposer = ensure_signed(origin)?;
515 ensure!(Self::has_voting_rights(&proposer), Error::<T, I>::NoVotingRights);
516
517 T::ProposalProvider::propose_proposal(proposer, threshold, proposal, length_bound)?;
518 Ok(())
519 }
520
521 #[pallet::call_index(1)]
525 #[pallet::weight(T::WeightInfo::vote(T::MaxFellows::get()))]
526 pub fn vote(
527 origin: OriginFor<T>,
528 proposal: T::Hash,
529 #[pallet::compact] index: ProposalIndex,
530 approve: bool,
531 ) -> DispatchResult {
532 let who = ensure_signed(origin)?;
533 ensure!(Self::has_voting_rights(&who), Error::<T, I>::NoVotingRights);
534
535 T::ProposalProvider::vote_proposal(who, proposal, index, approve)?;
536 Ok(())
537 }
538
539 #[pallet::call_index(3)]
547 #[pallet::weight(T::WeightInfo::init_members(
548 fellows.len() as u32,
549 allies.len() as u32,
550 ))]
551 pub fn init_members(
552 origin: OriginFor<T>,
553 fellows: Vec<T::AccountId>,
554 allies: Vec<T::AccountId>,
555 ) -> DispatchResult {
556 ensure_root(origin)?;
557
558 ensure!(!fellows.is_empty(), Error::<T, I>::FellowsMissing);
559 ensure!(!Self::is_initialized(), Error::<T, I>::AllianceAlreadyInitialized);
560
561 let mut fellows: BoundedVec<T::AccountId, T::MaxMembersCount> =
562 fellows.try_into().map_err(|_| Error::<T, I>::TooManyMembers)?;
563 let mut allies: BoundedVec<T::AccountId, T::MaxMembersCount> =
564 allies.try_into().map_err(|_| Error::<T, I>::TooManyMembers)?;
565
566 for member in fellows.iter().chain(allies.iter()) {
567 Self::has_identity(member)?;
568 }
569
570 fellows.sort();
571 Members::<T, I>::insert(&MemberRole::Fellow, fellows.clone());
572 allies.sort();
573 Members::<T, I>::insert(&MemberRole::Ally, allies.clone());
574
575 let mut voteable_members = fellows.clone();
576 voteable_members.sort();
577
578 T::InitializeMembers::initialize_members(&voteable_members);
579
580 log::debug!(
581 target: LOG_TARGET,
582 "Initialize alliance fellows: {:?}, allies: {:?}",
583 fellows,
584 allies
585 );
586
587 Self::deposit_event(Event::MembersInitialized {
588 fellows: fellows.into(),
589 allies: allies.into(),
590 });
591 Ok(())
592 }
593
594 #[pallet::call_index(4)]
598 #[pallet::weight(T::WeightInfo::disband(
599 witness.fellow_members,
600 witness.ally_members,
601 witness.fellow_members.saturating_add(witness.ally_members),
602 ))]
603 pub fn disband(
604 origin: OriginFor<T>,
605 witness: DisbandWitness,
606 ) -> DispatchResultWithPostInfo {
607 ensure_root(origin)?;
608
609 ensure!(!witness.is_zero(), Error::<T, I>::BadWitness);
610 ensure!(
611 Self::voting_members_count() <= witness.fellow_members,
612 Error::<T, I>::BadWitness
613 );
614 ensure!(Self::ally_members_count() <= witness.ally_members, Error::<T, I>::BadWitness);
615 ensure!(Self::is_initialized(), Error::<T, I>::AllianceNotYetInitialized);
616
617 let voting_members = Self::voting_members();
618 T::MembershipChanged::change_members_sorted(&[], &voting_members, &[]);
619
620 let ally_members = Self::members_of(MemberRole::Ally);
621 let mut unreserve_count: u32 = 0;
622 for member in voting_members.iter().chain(ally_members.iter()) {
623 if let Some(deposit) = DepositOf::<T, I>::take(&member) {
624 let err_amount = T::Currency::unreserve(&member, deposit);
625 debug_assert!(err_amount.is_zero());
626 unreserve_count += 1;
627 }
628 }
629
630 Members::<T, I>::remove(&MemberRole::Fellow);
631 Members::<T, I>::remove(&MemberRole::Ally);
632
633 Self::deposit_event(Event::AllianceDisbanded {
634 fellow_members: voting_members.len() as u32,
635 ally_members: ally_members.len() as u32,
636 unreserved: unreserve_count,
637 });
638
639 Ok(Some(T::WeightInfo::disband(
640 voting_members.len() as u32,
641 ally_members.len() as u32,
642 unreserve_count,
643 ))
644 .into())
645 }
646
647 #[pallet::call_index(5)]
649 pub fn set_rule(origin: OriginFor<T>, rule: Cid) -> DispatchResult {
650 T::AdminOrigin::ensure_origin(origin)?;
651
652 Rule::<T, I>::put(&rule);
653
654 Self::deposit_event(Event::NewRuleSet { rule });
655 Ok(())
656 }
657
658 #[pallet::call_index(6)]
660 pub fn announce(origin: OriginFor<T>, announcement: Cid) -> DispatchResult {
661 T::AnnouncementOrigin::ensure_origin(origin)?;
662
663 let mut announcements = <Announcements<T, I>>::get();
664 announcements
665 .try_push(announcement.clone())
666 .map_err(|_| Error::<T, I>::TooManyAnnouncements)?;
667 <Announcements<T, I>>::put(announcements);
668
669 Self::deposit_event(Event::Announced { announcement });
670 Ok(())
671 }
672
673 #[pallet::call_index(7)]
675 pub fn remove_announcement(origin: OriginFor<T>, announcement: Cid) -> DispatchResult {
676 T::AnnouncementOrigin::ensure_origin(origin)?;
677
678 let mut announcements = <Announcements<T, I>>::get();
679 let pos = announcements
680 .binary_search(&announcement)
681 .ok()
682 .ok_or(Error::<T, I>::MissingAnnouncement)?;
683 announcements.remove(pos);
684 <Announcements<T, I>>::put(announcements);
685
686 Self::deposit_event(Event::AnnouncementRemoved { announcement });
687 Ok(())
688 }
689
690 #[pallet::call_index(8)]
692 pub fn join_alliance(origin: OriginFor<T>) -> DispatchResult {
693 let who = ensure_signed(origin)?;
694
695 ensure!(Self::is_initialized(), Error::<T, I>::AllianceNotYetInitialized);
703
704 ensure!(!Self::is_unscrupulous_account(&who), Error::<T, I>::AccountNonGrata);
706 ensure!(!Self::is_member(&who), Error::<T, I>::AlreadyMember);
707 Self::has_identity(&who)?;
710
711 let deposit = T::AllyDeposit::get();
712 T::Currency::reserve(&who, deposit).map_err(|_| Error::<T, I>::InsufficientFunds)?;
713 <DepositOf<T, I>>::insert(&who, deposit);
714
715 Self::add_member(&who, MemberRole::Ally)?;
716
717 Self::deposit_event(Event::NewAllyJoined {
718 ally: who,
719 nominator: None,
720 reserved: Some(deposit),
721 });
722 Ok(())
723 }
724
725 #[pallet::call_index(9)]
728 pub fn nominate_ally(origin: OriginFor<T>, who: AccountIdLookupOf<T>) -> DispatchResult {
729 let nominator = ensure_signed(origin)?;
730 ensure!(Self::has_voting_rights(&nominator), Error::<T, I>::NoVotingRights);
731 let who = T::Lookup::lookup(who)?;
732
733 ensure!(!Self::is_unscrupulous_account(&who), Error::<T, I>::AccountNonGrata);
735 ensure!(!Self::is_member(&who), Error::<T, I>::AlreadyMember);
736 Self::has_identity(&who)?;
739
740 Self::add_member(&who, MemberRole::Ally)?;
741
742 Self::deposit_event(Event::NewAllyJoined {
743 ally: who,
744 nominator: Some(nominator),
745 reserved: None,
746 });
747 Ok(())
748 }
749
750 #[pallet::call_index(10)]
752 pub fn elevate_ally(origin: OriginFor<T>, ally: AccountIdLookupOf<T>) -> DispatchResult {
753 T::MembershipManager::ensure_origin(origin)?;
754 let ally = T::Lookup::lookup(ally)?;
755 ensure!(Self::is_ally(&ally), Error::<T, I>::NotAlly);
756 ensure!(!Self::has_voting_rights(&ally), Error::<T, I>::AlreadyElevated);
757
758 Self::remove_member(&ally, MemberRole::Ally)?;
759 Self::add_member(&ally, MemberRole::Fellow)?;
760
761 Self::deposit_event(Event::AllyElevated { ally });
762 Ok(())
763 }
764
765 #[pallet::call_index(11)]
768 pub fn give_retirement_notice(origin: OriginFor<T>) -> DispatchResult {
769 let who = ensure_signed(origin)?;
770 let role = Self::member_role_of(&who).ok_or(Error::<T, I>::NotMember)?;
771 ensure!(role.ne(&MemberRole::Retiring), Error::<T, I>::AlreadyRetiring);
772
773 Self::remove_member(&who, role)?;
774 Self::add_member(&who, MemberRole::Retiring)?;
775 <RetiringMembers<T, I>>::insert(
776 &who,
777 frame_system::Pallet::<T>::block_number()
778 .saturating_add(T::RetirementPeriod::get()),
779 );
780
781 Self::deposit_event(Event::MemberRetirementPeriodStarted { member: who });
782 Ok(())
783 }
784
785 #[pallet::call_index(12)]
790 pub fn retire(origin: OriginFor<T>) -> DispatchResult {
791 let who = ensure_signed(origin)?;
792 let retirement_period_end = RetiringMembers::<T, I>::get(&who)
793 .ok_or(Error::<T, I>::RetirementNoticeNotGiven)?;
794 ensure!(
795 frame_system::Pallet::<T>::block_number() >= retirement_period_end,
796 Error::<T, I>::RetirementPeriodNotPassed
797 );
798
799 Self::remove_member(&who, MemberRole::Retiring)?;
800 <RetiringMembers<T, I>>::remove(&who);
801 let deposit = DepositOf::<T, I>::take(&who);
802 if let Some(deposit) = deposit {
803 let err_amount = T::Currency::unreserve(&who, deposit);
804 debug_assert!(err_amount.is_zero());
805 }
806 Self::deposit_event(Event::MemberRetired { member: who, unreserved: deposit });
807 Ok(())
808 }
809
810 #[pallet::call_index(13)]
812 pub fn kick_member(origin: OriginFor<T>, who: AccountIdLookupOf<T>) -> DispatchResult {
813 T::MembershipManager::ensure_origin(origin)?;
814 let member = T::Lookup::lookup(who)?;
815
816 let role = Self::member_role_of(&member).ok_or(Error::<T, I>::NotMember)?;
817 Self::remove_member(&member, role)?;
818 let deposit = DepositOf::<T, I>::take(member.clone());
819 if let Some(deposit) = deposit {
820 T::Slashed::on_unbalanced(T::Currency::slash_reserved(&member, deposit).0);
821 }
822
823 Self::deposit_event(Event::MemberKicked { member, slashed: deposit });
824 Ok(())
825 }
826
827 #[pallet::call_index(14)]
829 #[pallet::weight(T::WeightInfo::add_unscrupulous_items(items.len() as u32, T::MaxWebsiteUrlLength::get()))]
830 pub fn add_unscrupulous_items(
831 origin: OriginFor<T>,
832 items: Vec<UnscrupulousItemOf<T, I>>,
833 ) -> DispatchResult {
834 T::AnnouncementOrigin::ensure_origin(origin)?;
835
836 let mut accounts = vec![];
837 let mut webs = vec![];
838 for info in items.iter() {
839 ensure!(!Self::is_unscrupulous(info), Error::<T, I>::AlreadyUnscrupulous);
840 match info {
841 UnscrupulousItem::AccountId(who) => accounts.push(who.clone()),
842 UnscrupulousItem::Website(url) => {
843 ensure!(
844 url.len() as u32 <= T::MaxWebsiteUrlLength::get(),
845 Error::<T, I>::TooLongWebsiteUrl
846 );
847 webs.push(url.clone());
848 },
849 }
850 }
851
852 Self::do_add_unscrupulous_items(&mut accounts, &mut webs)?;
853 Self::deposit_event(Event::UnscrupulousItemAdded { items });
854 Ok(())
855 }
856
857 #[pallet::call_index(15)]
859 #[pallet::weight(<T as Config<I>>::WeightInfo::remove_unscrupulous_items(
860 items.len() as u32, T::MaxWebsiteUrlLength::get()
861 ))]
862 pub fn remove_unscrupulous_items(
863 origin: OriginFor<T>,
864 items: Vec<UnscrupulousItemOf<T, I>>,
865 ) -> DispatchResult {
866 T::AnnouncementOrigin::ensure_origin(origin)?;
867 let mut accounts = vec![];
868 let mut webs = vec![];
869 for info in items.iter() {
870 ensure!(Self::is_unscrupulous(info), Error::<T, I>::NotListedAsUnscrupulous);
871 match info {
872 UnscrupulousItem::AccountId(who) => accounts.push(who.clone()),
873 UnscrupulousItem::Website(url) => webs.push(url.clone()),
874 }
875 }
876 Self::do_remove_unscrupulous_items(&mut accounts, &mut webs)?;
877 Self::deposit_event(Event::UnscrupulousItemRemoved { items });
878 Ok(())
879 }
880
881 #[pallet::call_index(16)]
885 #[pallet::weight({
886 let b = *length_bound;
887 let m = T::MaxFellows::get();
888 let p1 = *proposal_weight_bound;
889 let p2 = T::MaxProposals::get();
890 T::WeightInfo::close_early_approved(b, m, p2)
891 .max(T::WeightInfo::close_early_disapproved(m, p2))
892 .max(T::WeightInfo::close_approved(b, m, p2))
893 .max(T::WeightInfo::close_disapproved(m, p2))
894 .saturating_add(p1)
895 })]
896 pub fn close(
897 origin: OriginFor<T>,
898 proposal_hash: T::Hash,
899 #[pallet::compact] index: ProposalIndex,
900 proposal_weight_bound: Weight,
901 #[pallet::compact] length_bound: u32,
902 ) -> DispatchResultWithPostInfo {
903 let who = ensure_signed(origin)?;
904 ensure!(Self::has_voting_rights(&who), Error::<T, I>::NoVotingRights);
905
906 Self::do_close(proposal_hash, index, proposal_weight_bound, length_bound)
907 }
908
909 #[pallet::call_index(17)]
913 pub fn abdicate_fellow_status(origin: OriginFor<T>) -> DispatchResult {
914 let who = ensure_signed(origin)?;
915 let role = Self::member_role_of(&who).ok_or(Error::<T, I>::NotMember)?;
916 ensure!(Self::has_voting_rights(&who), Error::<T, I>::NoVotingRights);
918
919 Self::remove_member(&who, role)?;
920 Self::add_member(&who, MemberRole::Ally)?;
921
922 Self::deposit_event(Event::FellowAbdicated { fellow: who });
923 Ok(())
924 }
925 }
926}
927
928impl<T: Config<I>, I: 'static> Pallet<T, I> {
929 fn is_initialized() -> bool {
931 Self::has_member(MemberRole::Fellow) || Self::has_member(MemberRole::Ally)
932 }
933
934 fn has_member(role: MemberRole) -> bool {
936 Members::<T, I>::decode_len(role).unwrap_or_default() > 0
937 }
938
939 fn member_role_of(who: &T::AccountId) -> Option<MemberRole> {
941 Members::<T, I>::iter()
942 .find_map(|(r, members)| if members.contains(who) { Some(r) } else { None })
943 }
944
945 pub fn is_member(who: &T::AccountId) -> bool {
947 Self::member_role_of(who).is_some()
948 }
949
950 pub fn is_member_of(who: &T::AccountId, role: MemberRole) -> bool {
952 Members::<T, I>::get(role).contains(&who)
953 }
954
955 fn is_ally(who: &T::AccountId) -> bool {
957 Self::is_member_of(who, MemberRole::Ally)
958 }
959
960 fn has_voting_rights(who: &T::AccountId) -> bool {
962 Self::is_member_of(who, MemberRole::Fellow)
963 }
964
965 fn ally_members_count() -> u32 {
967 Members::<T, I>::decode_len(MemberRole::Ally).unwrap_or(0) as u32
968 }
969
970 fn voting_members_count() -> u32 {
972 Members::<T, I>::decode_len(MemberRole::Fellow).unwrap_or(0) as u32
973 }
974
975 fn members_of(role: MemberRole) -> Vec<T::AccountId> {
977 Members::<T, I>::get(role).into_inner()
978 }
979
980 fn voting_members() -> Vec<T::AccountId> {
982 Self::members_of(MemberRole::Fellow)
983 }
984
985 fn add_member(who: &T::AccountId, role: MemberRole) -> DispatchResult {
987 <Members<T, I>>::try_mutate(role, |members| -> DispatchResult {
988 let pos = members.binary_search(who).err().ok_or(Error::<T, I>::AlreadyMember)?;
989 members
990 .try_insert(pos, who.clone())
991 .map_err(|_| Error::<T, I>::TooManyMembers)?;
992 Ok(())
993 })?;
994
995 if role == MemberRole::Fellow {
996 let members = Self::voting_members();
997 T::MembershipChanged::change_members_sorted(&[who.clone()], &[], &members[..]);
998 }
999 Ok(())
1000 }
1001
1002 fn remove_member(who: &T::AccountId, role: MemberRole) -> DispatchResult {
1004 <Members<T, I>>::try_mutate(role, |members| -> DispatchResult {
1005 let pos = members.binary_search(who).ok().ok_or(Error::<T, I>::NotMember)?;
1006 members.remove(pos);
1007 Ok(())
1008 })?;
1009
1010 if role == MemberRole::Fellow {
1011 let members = Self::voting_members();
1012 T::MembershipChanged::change_members_sorted(&[], &[who.clone()], &members[..]);
1013 }
1014 Ok(())
1015 }
1016
1017 fn is_unscrupulous(info: &UnscrupulousItemOf<T, I>) -> bool {
1019 match info {
1020 UnscrupulousItem::Website(url) => <UnscrupulousWebsites<T, I>>::get().contains(url),
1021 UnscrupulousItem::AccountId(who) => <UnscrupulousAccounts<T, I>>::get().contains(who),
1022 }
1023 }
1024
1025 fn is_unscrupulous_account(who: &T::AccountId) -> bool {
1027 <UnscrupulousAccounts<T, I>>::get().contains(who)
1028 }
1029
1030 fn do_add_unscrupulous_items(
1032 new_accounts: &mut Vec<T::AccountId>,
1033 new_webs: &mut Vec<UrlOf<T, I>>,
1034 ) -> DispatchResult {
1035 if !new_accounts.is_empty() {
1036 <UnscrupulousAccounts<T, I>>::try_mutate(|accounts| -> DispatchResult {
1037 accounts
1038 .try_append(new_accounts)
1039 .map_err(|_| Error::<T, I>::TooManyUnscrupulousItems)?;
1040 accounts.sort();
1041
1042 Ok(())
1043 })?;
1044 }
1045 if !new_webs.is_empty() {
1046 <UnscrupulousWebsites<T, I>>::try_mutate(|webs| -> DispatchResult {
1047 webs.try_append(new_webs).map_err(|_| Error::<T, I>::TooManyUnscrupulousItems)?;
1048 webs.sort();
1049
1050 Ok(())
1051 })?;
1052 }
1053
1054 Ok(())
1055 }
1056
1057 fn do_remove_unscrupulous_items(
1059 out_accounts: &mut Vec<T::AccountId>,
1060 out_webs: &mut Vec<UrlOf<T, I>>,
1061 ) -> DispatchResult {
1062 if !out_accounts.is_empty() {
1063 <UnscrupulousAccounts<T, I>>::try_mutate(|accounts| -> DispatchResult {
1064 for who in out_accounts.iter() {
1065 let pos = accounts
1066 .binary_search(who)
1067 .ok()
1068 .ok_or(Error::<T, I>::NotListedAsUnscrupulous)?;
1069 accounts.remove(pos);
1070 }
1071 Ok(())
1072 })?;
1073 }
1074 if !out_webs.is_empty() {
1075 <UnscrupulousWebsites<T, I>>::try_mutate(|webs| -> DispatchResult {
1076 for web in out_webs.iter() {
1077 let pos = webs
1078 .binary_search(web)
1079 .ok()
1080 .ok_or(Error::<T, I>::NotListedAsUnscrupulous)?;
1081 webs.remove(pos);
1082 }
1083 Ok(())
1084 })?;
1085 }
1086 Ok(())
1087 }
1088
1089 fn has_identity(who: &T::AccountId) -> DispatchResult {
1090 let judgement = |who: &T::AccountId| -> DispatchResult {
1091 ensure!(
1092 T::IdentityVerifier::has_required_identities(who),
1093 Error::<T, I>::WithoutRequiredIdentityFields
1094 );
1095 ensure!(
1096 T::IdentityVerifier::has_good_judgement(who),
1097 Error::<T, I>::WithoutGoodIdentityJudgement
1098 );
1099 Ok(())
1100 };
1101
1102 let res = judgement(who);
1103 if res.is_err() {
1104 if let Some(parent) = T::IdentityVerifier::super_account_id(who) {
1105 return judgement(&parent)
1106 }
1107 }
1108 res
1109 }
1110
1111 fn do_close(
1112 proposal_hash: T::Hash,
1113 index: ProposalIndex,
1114 proposal_weight_bound: Weight,
1115 length_bound: u32,
1116 ) -> DispatchResultWithPostInfo {
1117 let info = T::ProposalProvider::close_proposal(
1118 proposal_hash,
1119 index,
1120 proposal_weight_bound,
1121 length_bound,
1122 )?;
1123 Ok(info.into())
1124 }
1125}