#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod migration;
mod types;
pub mod weights;
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use sp_runtime::{
	traits::{Saturating, StaticLookup, Zero},
	RuntimeDebug,
};
use sp_std::{convert::TryInto, prelude::*};
use frame_support::{
	dispatch::{
		DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable, GetDispatchInfo,
		PostDispatchInfo,
	},
	ensure,
	traits::{
		ChangeMembers, Currency, Get, InitializeMembers, IsSubType, OnUnbalanced,
		ReservableCurrency,
	},
	weights::Weight,
};
use pallet_identity::IdentityField;
use scale_info::TypeInfo;
pub use pallet::*;
pub use types::*;
pub use weights::*;
pub const LOG_TARGET: &str = "runtime::alliance";
pub type ProposalIndex = u32;
type UrlOf<T, I> = BoundedVec<u8, <T as pallet::Config<I>>::MaxWebsiteUrlLength>;
type BalanceOf<T, I> =
	<<T as Config<I>>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
type NegativeImbalanceOf<T, I> = <<T as Config<I>>::Currency as Currency<
	<T as frame_system::Config>::AccountId,
>>::NegativeImbalance;
pub trait IdentityVerifier<AccountId> {
	fn has_identity(who: &AccountId, fields: u64) -> bool;
	fn has_good_judgement(who: &AccountId) -> bool;
	fn super_account_id(who: &AccountId) -> Option<AccountId>;
}
impl<AccountId> IdentityVerifier<AccountId> for () {
	fn has_identity(_who: &AccountId, _fields: u64) -> bool {
		true
	}
	fn has_good_judgement(_who: &AccountId) -> bool {
		true
	}
	fn super_account_id(_who: &AccountId) -> Option<AccountId> {
		None
	}
}
pub trait ProposalProvider<AccountId, Hash, Proposal> {
	fn propose_proposal(
		who: AccountId,
		threshold: u32,
		proposal: Box<Proposal>,
		length_bound: u32,
	) -> Result<(u32, u32), DispatchError>;
	fn vote_proposal(
		who: AccountId,
		proposal: Hash,
		index: ProposalIndex,
		approve: bool,
	) -> Result<bool, DispatchError>;
	fn close_proposal(
		proposal_hash: Hash,
		index: ProposalIndex,
		proposal_weight_bound: Weight,
		length_bound: u32,
	) -> DispatchResultWithPostInfo;
	fn proposal_of(proposal_hash: Hash) -> Option<Proposal>;
}
#[derive(Copy, Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]
pub enum MemberRole {
	Fellow,
	Ally,
	Retiring,
}
#[derive(Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]
pub enum UnscrupulousItem<AccountId, Url> {
	AccountId(AccountId),
	Website(Url),
}
type UnscrupulousItemOf<T, I> =
	UnscrupulousItem<<T as frame_system::Config>::AccountId, UrlOf<T, I>>;
type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
#[frame_support::pallet]
pub mod pallet {
	use super::*;
	#[pallet::pallet]
	#[pallet::storage_version(migration::STORAGE_VERSION)]
	pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
	#[pallet::config]
	pub trait Config<I: 'static = ()>: frame_system::Config {
		type RuntimeEvent: From<Event<Self, I>>
			+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
		type Proposal: Parameter
			+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
			+ From<frame_system::Call<Self>>
			+ From<Call<Self, I>>
			+ GetDispatchInfo
			+ IsSubType<Call<Self, I>>
			+ IsType<<Self as frame_system::Config>::RuntimeCall>;
		type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
		type MembershipManager: EnsureOrigin<Self::RuntimeOrigin>;
		type AnnouncementOrigin: EnsureOrigin<Self::RuntimeOrigin>;
		type Currency: ReservableCurrency<Self::AccountId>;
		type Slashed: OnUnbalanced<NegativeImbalanceOf<Self, I>>;
		type InitializeMembers: InitializeMembers<Self::AccountId>;
		type MembershipChanged: ChangeMembers<Self::AccountId>;
		type IdentityVerifier: IdentityVerifier<Self::AccountId>;
		type ProposalProvider: ProposalProvider<Self::AccountId, Self::Hash, Self::Proposal>;
		type MaxProposals: Get<ProposalIndex>;
		type MaxFellows: Get<u32>;
		type MaxAllies: Get<u32>;
		#[pallet::constant]
		type MaxUnscrupulousItems: Get<u32>;
		#[pallet::constant]
		type MaxWebsiteUrlLength: Get<u32>;
		#[pallet::constant]
		type AllyDeposit: Get<BalanceOf<Self, I>>;
		#[pallet::constant]
		type MaxAnnouncementsCount: Get<u32>;
		#[pallet::constant]
		type MaxMembersCount: Get<u32>;
		type WeightInfo: WeightInfo;
		type RetirementPeriod: Get<BlockNumberFor<Self>>;
	}
	#[pallet::error]
	pub enum Error<T, I = ()> {
		AllianceNotYetInitialized,
		AllianceAlreadyInitialized,
		AlreadyMember,
		NotMember,
		NotAlly,
		NoVotingRights,
		AlreadyElevated,
		AlreadyUnscrupulous,
		AccountNonGrata,
		NotListedAsUnscrupulous,
		TooManyUnscrupulousItems,
		TooLongWebsiteUrl,
		InsufficientFunds,
		WithoutIdentityDisplayAndWebsite,
		WithoutGoodIdentityJudgement,
		MissingProposalHash,
		MissingAnnouncement,
		TooManyMembers,
		TooManyAnnouncements,
		BadWitness,
		AlreadyRetiring,
		RetirementNoticeNotGiven,
		RetirementPeriodNotPassed,
		FellowsMissing,
	}
	#[pallet::event]
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
	pub enum Event<T: Config<I>, I: 'static = ()> {
		NewRuleSet { rule: Cid },
		Announced { announcement: Cid },
		AnnouncementRemoved { announcement: Cid },
		MembersInitialized { fellows: Vec<T::AccountId>, allies: Vec<T::AccountId> },
		NewAllyJoined {
			ally: T::AccountId,
			nominator: Option<T::AccountId>,
			reserved: Option<BalanceOf<T, I>>,
		},
		AllyElevated { ally: T::AccountId },
		MemberRetirementPeriodStarted { member: T::AccountId },
		MemberRetired { member: T::AccountId, unreserved: Option<BalanceOf<T, I>> },
		MemberKicked { member: T::AccountId, slashed: Option<BalanceOf<T, I>> },
		UnscrupulousItemAdded { items: Vec<UnscrupulousItemOf<T, I>> },
		UnscrupulousItemRemoved { items: Vec<UnscrupulousItemOf<T, I>> },
		AllianceDisbanded { fellow_members: u32, ally_members: u32, unreserved: u32 },
		FellowAbdicated { fellow: T::AccountId },
	}
	#[pallet::genesis_config]
	#[derive(frame_support::DefaultNoBound)]
	pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
		pub fellows: Vec<T::AccountId>,
		pub allies: Vec<T::AccountId>,
		#[serde(skip)]
		pub phantom: PhantomData<(T, I)>,
	}
	#[pallet::genesis_build]
	impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
		fn build(&self) {
			for m in self.fellows.iter().chain(self.allies.iter()) {
				assert!(Pallet::<T, I>::has_identity(m).is_ok(), "Member does not set identity!");
			}
			if !self.fellows.is_empty() {
				assert!(
					!Pallet::<T, I>::has_member(MemberRole::Fellow),
					"Fellows are already initialized!"
				);
				let members: BoundedVec<T::AccountId, T::MaxMembersCount> =
					self.fellows.clone().try_into().expect("Too many genesis fellows");
				Members::<T, I>::insert(MemberRole::Fellow, members);
			}
			if !self.allies.is_empty() {
				assert!(
					!Pallet::<T, I>::has_member(MemberRole::Ally),
					"Allies are already initialized!"
				);
				assert!(
					!self.fellows.is_empty(),
					"Fellows must be provided to initialize the Alliance"
				);
				let members: BoundedVec<T::AccountId, T::MaxMembersCount> =
					self.allies.clone().try_into().expect("Too many genesis allies");
				Members::<T, I>::insert(MemberRole::Ally, members);
			}
			T::InitializeMembers::initialize_members(self.fellows.as_slice())
		}
	}
	#[pallet::storage]
	#[pallet::getter(fn rule)]
	pub type Rule<T: Config<I>, I: 'static = ()> = StorageValue<_, Cid, OptionQuery>;
	#[pallet::storage]
	#[pallet::getter(fn announcements)]
	pub type Announcements<T: Config<I>, I: 'static = ()> =
		StorageValue<_, BoundedVec<Cid, T::MaxAnnouncementsCount>, ValueQuery>;
	#[pallet::storage]
	#[pallet::getter(fn deposit_of)]
	pub type DepositOf<T: Config<I>, I: 'static = ()> =
		StorageMap<_, Blake2_128Concat, T::AccountId, BalanceOf<T, I>, OptionQuery>;
	#[pallet::storage]
	#[pallet::getter(fn members)]
	pub type Members<T: Config<I>, I: 'static = ()> = StorageMap<
		_,
		Twox64Concat,
		MemberRole,
		BoundedVec<T::AccountId, T::MaxMembersCount>,
		ValueQuery,
	>;
	#[pallet::storage]
	#[pallet::getter(fn retiring_members)]
	pub type RetiringMembers<T: Config<I>, I: 'static = ()> =
		StorageMap<_, Blake2_128Concat, T::AccountId, BlockNumberFor<T>, OptionQuery>;
	#[pallet::storage]
	#[pallet::getter(fn unscrupulous_accounts)]
	pub type UnscrupulousAccounts<T: Config<I>, I: 'static = ()> =
		StorageValue<_, BoundedVec<T::AccountId, T::MaxUnscrupulousItems>, ValueQuery>;
	#[pallet::storage]
	#[pallet::getter(fn unscrupulous_websites)]
	pub type UnscrupulousWebsites<T: Config<I>, I: 'static = ()> =
		StorageValue<_, BoundedVec<UrlOf<T, I>, T::MaxUnscrupulousItems>, ValueQuery>;
	#[pallet::call(weight(<T as Config<I>>::WeightInfo))]
	impl<T: Config<I>, I: 'static> Pallet<T, I> {
		#[pallet::call_index(0)]
		#[pallet::weight(T::WeightInfo::propose_proposed(
			*length_bound, T::MaxFellows::get(), T::MaxProposals::get(), ))]
		pub fn propose(
			origin: OriginFor<T>,
			#[pallet::compact] threshold: u32,
			proposal: Box<<T as Config<I>>::Proposal>,
			#[pallet::compact] length_bound: u32,
		) -> DispatchResult {
			let proposor = ensure_signed(origin)?;
			ensure!(Self::has_voting_rights(&proposor), Error::<T, I>::NoVotingRights);
			T::ProposalProvider::propose_proposal(proposor, threshold, proposal, length_bound)?;
			Ok(())
		}
		#[pallet::call_index(1)]
		#[pallet::weight(T::WeightInfo::vote(T::MaxFellows::get()))]
		pub fn vote(
			origin: OriginFor<T>,
			proposal: T::Hash,
			#[pallet::compact] index: ProposalIndex,
			approve: bool,
		) -> DispatchResult {
			let who = ensure_signed(origin)?;
			ensure!(Self::has_voting_rights(&who), Error::<T, I>::NoVotingRights);
			T::ProposalProvider::vote_proposal(who, proposal, index, approve)?;
			Ok(())
		}
		#[pallet::call_index(3)]
		#[pallet::weight(T::WeightInfo::init_members(
			fellows.len() as u32,
			allies.len() as u32,
		))]
		pub fn init_members(
			origin: OriginFor<T>,
			fellows: Vec<T::AccountId>,
			allies: Vec<T::AccountId>,
		) -> DispatchResult {
			ensure_root(origin)?;
			ensure!(!fellows.is_empty(), Error::<T, I>::FellowsMissing);
			ensure!(!Self::is_initialized(), Error::<T, I>::AllianceAlreadyInitialized);
			let mut fellows: BoundedVec<T::AccountId, T::MaxMembersCount> =
				fellows.try_into().map_err(|_| Error::<T, I>::TooManyMembers)?;
			let mut allies: BoundedVec<T::AccountId, T::MaxMembersCount> =
				allies.try_into().map_err(|_| Error::<T, I>::TooManyMembers)?;
			for member in fellows.iter().chain(allies.iter()) {
				Self::has_identity(member)?;
			}
			fellows.sort();
			Members::<T, I>::insert(&MemberRole::Fellow, fellows.clone());
			allies.sort();
			Members::<T, I>::insert(&MemberRole::Ally, allies.clone());
			let mut voteable_members = fellows.clone();
			voteable_members.sort();
			T::InitializeMembers::initialize_members(&voteable_members);
			log::debug!(
				target: LOG_TARGET,
				"Initialize alliance fellows: {:?}, allies: {:?}",
				fellows,
				allies
			);
			Self::deposit_event(Event::MembersInitialized {
				fellows: fellows.into(),
				allies: allies.into(),
			});
			Ok(())
		}
		#[pallet::call_index(4)]
		#[pallet::weight(T::WeightInfo::disband(
			witness.fellow_members,
			witness.ally_members,
			witness.fellow_members.saturating_add(witness.ally_members),
		))]
		pub fn disband(
			origin: OriginFor<T>,
			witness: DisbandWitness,
		) -> DispatchResultWithPostInfo {
			ensure_root(origin)?;
			ensure!(!witness.is_zero(), Error::<T, I>::BadWitness);
			ensure!(
				Self::voting_members_count() <= witness.fellow_members,
				Error::<T, I>::BadWitness
			);
			ensure!(Self::ally_members_count() <= witness.ally_members, Error::<T, I>::BadWitness);
			ensure!(Self::is_initialized(), Error::<T, I>::AllianceNotYetInitialized);
			let voting_members = Self::voting_members();
			T::MembershipChanged::change_members_sorted(&[], &voting_members, &[]);
			let ally_members = Self::members_of(MemberRole::Ally);
			let mut unreserve_count: u32 = 0;
			for member in voting_members.iter().chain(ally_members.iter()) {
				if let Some(deposit) = DepositOf::<T, I>::take(&member) {
					let err_amount = T::Currency::unreserve(&member, deposit);
					debug_assert!(err_amount.is_zero());
					unreserve_count += 1;
				}
			}
			Members::<T, I>::remove(&MemberRole::Fellow);
			Members::<T, I>::remove(&MemberRole::Ally);
			Self::deposit_event(Event::AllianceDisbanded {
				fellow_members: voting_members.len() as u32,
				ally_members: ally_members.len() as u32,
				unreserved: unreserve_count,
			});
			Ok(Some(T::WeightInfo::disband(
				voting_members.len() as u32,
				ally_members.len() as u32,
				unreserve_count,
			))
			.into())
		}
		#[pallet::call_index(5)]
		pub fn set_rule(origin: OriginFor<T>, rule: Cid) -> DispatchResult {
			T::AdminOrigin::ensure_origin(origin)?;
			Rule::<T, I>::put(&rule);
			Self::deposit_event(Event::NewRuleSet { rule });
			Ok(())
		}
		#[pallet::call_index(6)]
		pub fn announce(origin: OriginFor<T>, announcement: Cid) -> DispatchResult {
			T::AnnouncementOrigin::ensure_origin(origin)?;
			let mut announcements = <Announcements<T, I>>::get();
			announcements
				.try_push(announcement.clone())
				.map_err(|_| Error::<T, I>::TooManyAnnouncements)?;
			<Announcements<T, I>>::put(announcements);
			Self::deposit_event(Event::Announced { announcement });
			Ok(())
		}
		#[pallet::call_index(7)]
		pub fn remove_announcement(origin: OriginFor<T>, announcement: Cid) -> DispatchResult {
			T::AnnouncementOrigin::ensure_origin(origin)?;
			let mut announcements = <Announcements<T, I>>::get();
			let pos = announcements
				.binary_search(&announcement)
				.ok()
				.ok_or(Error::<T, I>::MissingAnnouncement)?;
			announcements.remove(pos);
			<Announcements<T, I>>::put(announcements);
			Self::deposit_event(Event::AnnouncementRemoved { announcement });
			Ok(())
		}
		#[pallet::call_index(8)]
		pub fn join_alliance(origin: OriginFor<T>) -> DispatchResult {
			let who = ensure_signed(origin)?;
			ensure!(Self::is_initialized(), Error::<T, I>::AllianceNotYetInitialized);
			ensure!(!Self::is_unscrupulous_account(&who), Error::<T, I>::AccountNonGrata);
			ensure!(!Self::is_member(&who), Error::<T, I>::AlreadyMember);
			Self::has_identity(&who)?;
			let deposit = T::AllyDeposit::get();
			T::Currency::reserve(&who, deposit).map_err(|_| Error::<T, I>::InsufficientFunds)?;
			<DepositOf<T, I>>::insert(&who, deposit);
			Self::add_member(&who, MemberRole::Ally)?;
			Self::deposit_event(Event::NewAllyJoined {
				ally: who,
				nominator: None,
				reserved: Some(deposit),
			});
			Ok(())
		}
		#[pallet::call_index(9)]
		pub fn nominate_ally(origin: OriginFor<T>, who: AccountIdLookupOf<T>) -> DispatchResult {
			let nominator = ensure_signed(origin)?;
			ensure!(Self::has_voting_rights(&nominator), Error::<T, I>::NoVotingRights);
			let who = T::Lookup::lookup(who)?;
			ensure!(!Self::is_unscrupulous_account(&who), Error::<T, I>::AccountNonGrata);
			ensure!(!Self::is_member(&who), Error::<T, I>::AlreadyMember);
			Self::has_identity(&who)?;
			Self::add_member(&who, MemberRole::Ally)?;
			Self::deposit_event(Event::NewAllyJoined {
				ally: who,
				nominator: Some(nominator),
				reserved: None,
			});
			Ok(())
		}
		#[pallet::call_index(10)]
		pub fn elevate_ally(origin: OriginFor<T>, ally: AccountIdLookupOf<T>) -> DispatchResult {
			T::MembershipManager::ensure_origin(origin)?;
			let ally = T::Lookup::lookup(ally)?;
			ensure!(Self::is_ally(&ally), Error::<T, I>::NotAlly);
			ensure!(!Self::has_voting_rights(&ally), Error::<T, I>::AlreadyElevated);
			Self::remove_member(&ally, MemberRole::Ally)?;
			Self::add_member(&ally, MemberRole::Fellow)?;
			Self::deposit_event(Event::AllyElevated { ally });
			Ok(())
		}
		#[pallet::call_index(11)]
		pub fn give_retirement_notice(origin: OriginFor<T>) -> DispatchResult {
			let who = ensure_signed(origin)?;
			let role = Self::member_role_of(&who).ok_or(Error::<T, I>::NotMember)?;
			ensure!(role.ne(&MemberRole::Retiring), Error::<T, I>::AlreadyRetiring);
			Self::remove_member(&who, role)?;
			Self::add_member(&who, MemberRole::Retiring)?;
			<RetiringMembers<T, I>>::insert(
				&who,
				frame_system::Pallet::<T>::block_number()
					.saturating_add(T::RetirementPeriod::get()),
			);
			Self::deposit_event(Event::MemberRetirementPeriodStarted { member: who });
			Ok(())
		}
		#[pallet::call_index(12)]
		pub fn retire(origin: OriginFor<T>) -> DispatchResult {
			let who = ensure_signed(origin)?;
			let retirement_period_end = RetiringMembers::<T, I>::get(&who)
				.ok_or(Error::<T, I>::RetirementNoticeNotGiven)?;
			ensure!(
				frame_system::Pallet::<T>::block_number() >= retirement_period_end,
				Error::<T, I>::RetirementPeriodNotPassed
			);
			Self::remove_member(&who, MemberRole::Retiring)?;
			<RetiringMembers<T, I>>::remove(&who);
			let deposit = DepositOf::<T, I>::take(&who);
			if let Some(deposit) = deposit {
				let err_amount = T::Currency::unreserve(&who, deposit);
				debug_assert!(err_amount.is_zero());
			}
			Self::deposit_event(Event::MemberRetired { member: who, unreserved: deposit });
			Ok(())
		}
		#[pallet::call_index(13)]
		pub fn kick_member(origin: OriginFor<T>, who: AccountIdLookupOf<T>) -> DispatchResult {
			T::MembershipManager::ensure_origin(origin)?;
			let member = T::Lookup::lookup(who)?;
			let role = Self::member_role_of(&member).ok_or(Error::<T, I>::NotMember)?;
			Self::remove_member(&member, role)?;
			let deposit = DepositOf::<T, I>::take(member.clone());
			if let Some(deposit) = deposit {
				T::Slashed::on_unbalanced(T::Currency::slash_reserved(&member, deposit).0);
			}
			Self::deposit_event(Event::MemberKicked { member, slashed: deposit });
			Ok(())
		}
		#[pallet::call_index(14)]
		#[pallet::weight(T::WeightInfo::add_unscrupulous_items(items.len() as u32, T::MaxWebsiteUrlLength::get()))]
		pub fn add_unscrupulous_items(
			origin: OriginFor<T>,
			items: Vec<UnscrupulousItemOf<T, I>>,
		) -> DispatchResult {
			T::AnnouncementOrigin::ensure_origin(origin)?;
			let mut accounts = vec![];
			let mut webs = vec![];
			for info in items.iter() {
				ensure!(!Self::is_unscrupulous(info), Error::<T, I>::AlreadyUnscrupulous);
				match info {
					UnscrupulousItem::AccountId(who) => accounts.push(who.clone()),
					UnscrupulousItem::Website(url) => {
						ensure!(
							url.len() as u32 <= T::MaxWebsiteUrlLength::get(),
							Error::<T, I>::TooLongWebsiteUrl
						);
						webs.push(url.clone());
					},
				}
			}
			Self::do_add_unscrupulous_items(&mut accounts, &mut webs)?;
			Self::deposit_event(Event::UnscrupulousItemAdded { items });
			Ok(())
		}
		#[pallet::call_index(15)]
		#[pallet::weight(<T as Config<I>>::WeightInfo::remove_unscrupulous_items(
			items.len() as u32, T::MaxWebsiteUrlLength::get()
		))]
		pub fn remove_unscrupulous_items(
			origin: OriginFor<T>,
			items: Vec<UnscrupulousItemOf<T, I>>,
		) -> DispatchResult {
			T::AnnouncementOrigin::ensure_origin(origin)?;
			let mut accounts = vec![];
			let mut webs = vec![];
			for info in items.iter() {
				ensure!(Self::is_unscrupulous(info), Error::<T, I>::NotListedAsUnscrupulous);
				match info {
					UnscrupulousItem::AccountId(who) => accounts.push(who.clone()),
					UnscrupulousItem::Website(url) => webs.push(url.clone()),
				}
			}
			Self::do_remove_unscrupulous_items(&mut accounts, &mut webs)?;
			Self::deposit_event(Event::UnscrupulousItemRemoved { items });
			Ok(())
		}
		#[pallet::call_index(16)]
		#[pallet::weight({
			let b = *length_bound;
			let m = T::MaxFellows::get();
			let p1 = *proposal_weight_bound;
			let p2 = T::MaxProposals::get();
			T::WeightInfo::close_early_approved(b, m, p2)
				.max(T::WeightInfo::close_early_disapproved(m, p2))
				.max(T::WeightInfo::close_approved(b, m, p2))
				.max(T::WeightInfo::close_disapproved(m, p2))
				.saturating_add(p1)
		})]
		pub fn close(
			origin: OriginFor<T>,
			proposal_hash: T::Hash,
			#[pallet::compact] index: ProposalIndex,
			proposal_weight_bound: Weight,
			#[pallet::compact] length_bound: u32,
		) -> DispatchResultWithPostInfo {
			let who = ensure_signed(origin)?;
			ensure!(Self::has_voting_rights(&who), Error::<T, I>::NoVotingRights);
			Self::do_close(proposal_hash, index, proposal_weight_bound, length_bound)
		}
		#[pallet::call_index(17)]
		pub fn abdicate_fellow_status(origin: OriginFor<T>) -> DispatchResult {
			let who = ensure_signed(origin)?;
			let role = Self::member_role_of(&who).ok_or(Error::<T, I>::NotMember)?;
			ensure!(Self::has_voting_rights(&who), Error::<T, I>::NoVotingRights);
			Self::remove_member(&who, role)?;
			Self::add_member(&who, MemberRole::Ally)?;
			Self::deposit_event(Event::FellowAbdicated { fellow: who });
			Ok(())
		}
	}
}
impl<T: Config<I>, I: 'static> Pallet<T, I> {
	fn is_initialized() -> bool {
		Self::has_member(MemberRole::Fellow) || Self::has_member(MemberRole::Ally)
	}
	fn has_member(role: MemberRole) -> bool {
		Members::<T, I>::decode_len(role).unwrap_or_default() > 0
	}
	fn member_role_of(who: &T::AccountId) -> Option<MemberRole> {
		Members::<T, I>::iter()
			.find_map(|(r, members)| if members.contains(who) { Some(r) } else { None })
	}
	pub fn is_member(who: &T::AccountId) -> bool {
		Self::member_role_of(who).is_some()
	}
	pub fn is_member_of(who: &T::AccountId, role: MemberRole) -> bool {
		Members::<T, I>::get(role).contains(&who)
	}
	fn is_ally(who: &T::AccountId) -> bool {
		Self::is_member_of(who, MemberRole::Ally)
	}
	fn has_voting_rights(who: &T::AccountId) -> bool {
		Self::is_member_of(who, MemberRole::Fellow)
	}
	fn ally_members_count() -> u32 {
		Members::<T, I>::decode_len(MemberRole::Ally).unwrap_or(0) as u32
	}
	fn voting_members_count() -> u32 {
		Members::<T, I>::decode_len(MemberRole::Fellow).unwrap_or(0) as u32
	}
	fn members_of(role: MemberRole) -> Vec<T::AccountId> {
		Members::<T, I>::get(role).into_inner()
	}
	fn voting_members() -> Vec<T::AccountId> {
		Self::members_of(MemberRole::Fellow)
	}
	fn add_member(who: &T::AccountId, role: MemberRole) -> DispatchResult {
		<Members<T, I>>::try_mutate(role, |members| -> DispatchResult {
			let pos = members.binary_search(who).err().ok_or(Error::<T, I>::AlreadyMember)?;
			members
				.try_insert(pos, who.clone())
				.map_err(|_| Error::<T, I>::TooManyMembers)?;
			Ok(())
		})?;
		if role == MemberRole::Fellow {
			let members = Self::voting_members();
			T::MembershipChanged::change_members_sorted(&[who.clone()], &[], &members[..]);
		}
		Ok(())
	}
	fn remove_member(who: &T::AccountId, role: MemberRole) -> DispatchResult {
		<Members<T, I>>::try_mutate(role, |members| -> DispatchResult {
			let pos = members.binary_search(who).ok().ok_or(Error::<T, I>::NotMember)?;
			members.remove(pos);
			Ok(())
		})?;
		if role == MemberRole::Fellow {
			let members = Self::voting_members();
			T::MembershipChanged::change_members_sorted(&[], &[who.clone()], &members[..]);
		}
		Ok(())
	}
	fn is_unscrupulous(info: &UnscrupulousItemOf<T, I>) -> bool {
		match info {
			UnscrupulousItem::Website(url) => <UnscrupulousWebsites<T, I>>::get().contains(url),
			UnscrupulousItem::AccountId(who) => <UnscrupulousAccounts<T, I>>::get().contains(who),
		}
	}
	fn is_unscrupulous_account(who: &T::AccountId) -> bool {
		<UnscrupulousAccounts<T, I>>::get().contains(who)
	}
	fn do_add_unscrupulous_items(
		new_accounts: &mut Vec<T::AccountId>,
		new_webs: &mut Vec<UrlOf<T, I>>,
	) -> DispatchResult {
		if !new_accounts.is_empty() {
			<UnscrupulousAccounts<T, I>>::try_mutate(|accounts| -> DispatchResult {
				accounts
					.try_append(new_accounts)
					.map_err(|_| Error::<T, I>::TooManyUnscrupulousItems)?;
				accounts.sort();
				Ok(())
			})?;
		}
		if !new_webs.is_empty() {
			<UnscrupulousWebsites<T, I>>::try_mutate(|webs| -> DispatchResult {
				webs.try_append(new_webs).map_err(|_| Error::<T, I>::TooManyUnscrupulousItems)?;
				webs.sort();
				Ok(())
			})?;
		}
		Ok(())
	}
	fn do_remove_unscrupulous_items(
		out_accounts: &mut Vec<T::AccountId>,
		out_webs: &mut Vec<UrlOf<T, I>>,
	) -> DispatchResult {
		if !out_accounts.is_empty() {
			<UnscrupulousAccounts<T, I>>::try_mutate(|accounts| -> DispatchResult {
				for who in out_accounts.iter() {
					let pos = accounts
						.binary_search(who)
						.ok()
						.ok_or(Error::<T, I>::NotListedAsUnscrupulous)?;
					accounts.remove(pos);
				}
				Ok(())
			})?;
		}
		if !out_webs.is_empty() {
			<UnscrupulousWebsites<T, I>>::try_mutate(|webs| -> DispatchResult {
				for web in out_webs.iter() {
					let pos = webs
						.binary_search(web)
						.ok()
						.ok_or(Error::<T, I>::NotListedAsUnscrupulous)?;
					webs.remove(pos);
				}
				Ok(())
			})?;
		}
		Ok(())
	}
	fn has_identity(who: &T::AccountId) -> DispatchResult {
		const IDENTITY_FIELD_DISPLAY: u64 = IdentityField::Display as u64;
		const IDENTITY_FIELD_WEB: u64 = IdentityField::Web as u64;
		let judgement = |who: &T::AccountId| -> DispatchResult {
			ensure!(
				T::IdentityVerifier::has_identity(who, IDENTITY_FIELD_DISPLAY | IDENTITY_FIELD_WEB),
				Error::<T, I>::WithoutIdentityDisplayAndWebsite
			);
			ensure!(
				T::IdentityVerifier::has_good_judgement(who),
				Error::<T, I>::WithoutGoodIdentityJudgement
			);
			Ok(())
		};
		let res = judgement(who);
		if res.is_err() {
			if let Some(parent) = T::IdentityVerifier::super_account_id(who) {
				return judgement(&parent)
			}
		}
		res
	}
	fn do_close(
		proposal_hash: T::Hash,
		index: ProposalIndex,
		proposal_weight_bound: Weight,
		length_bound: u32,
	) -> DispatchResultWithPostInfo {
		let info = T::ProposalProvider::close_proposal(
			proposal_hash,
			index,
			proposal_weight_bound,
			length_bound,
		)?;
		Ok(info.into())
	}
}