referrerpolicy=no-referrer-when-downgrade

pallet_collective/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Collective system: Members of a set of account IDs can make their collective feelings known
19//! through dispatched calls from one of two specialized origins.
20//!
21//! The membership can be provided in one of two ways: either directly, using the Root-dispatchable
22//! function `set_members`, or indirectly, through implementing the `ChangeMembers`.
23//! The pallet assumes that the amount of members stays at or below `MaxMembers` for its weight
24//! calculations, but enforces this neither in `set_members` nor in `change_members_sorted`.
25//!
26//! A "prime" member may be set to help determine the default vote behavior based on chain
27//! config. If `PrimeDefaultVote` is used, the prime vote acts as the default vote in case of any
28//! abstentions after the voting period. If `MoreThanMajorityThenPrimeDefaultVote` is used, then
29//! abstentions will first follow the majority of the collective voting, and then the prime
30//! member.
31//!
32//! Voting happens through motions comprising a proposal (i.e. a curried dispatchable) plus a
33//! number of approvals required for it to pass and be called. Motions are open for members to
34//! vote on for a minimum period given by `MotionDuration`. As soon as the needed number of
35//! approvals is given, the motion is closed and executed. If the number of approvals is not reached
36//! during the voting period, then `close` may be called by any account in order to force the end
37//! the motion explicitly. If a prime member is defined then their vote is used in place of any
38//! abstentions and the proposal is executed if there are enough approvals counting the new votes.
39//!
40//! If there are not, or if no prime is set, then the motion is dropped without being executed.
41
42#![cfg_attr(not(feature = "std"), no_std)]
43
44extern crate alloc;
45
46use alloc::{boxed::Box, vec, vec::Vec};
47use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
48use core::{marker::PhantomData, result};
49use scale_info::TypeInfo;
50use sp_io::storage;
51use sp_runtime::{
52	traits::{Dispatchable, Hash},
53	Debug, DispatchError,
54};
55
56use frame_support::{
57	dispatch::{
58		DispatchResult, DispatchResultWithPostInfo, GetDispatchInfo, Pays, PostDispatchInfo,
59	},
60	ensure,
61	traits::{
62		Backing, ChangeMembers, Consideration, EnsureOrigin, EnsureOriginWithArg, Get, GetBacking,
63		InitializeMembers, MaybeConsideration, OriginTrait, StorageVersion,
64	},
65	weights::Weight,
66};
67
68#[cfg(any(feature = "try-runtime", test))]
69use sp_runtime::TryRuntimeError;
70
71#[cfg(test)]
72mod tests;
73
74#[cfg(feature = "runtime-benchmarks")]
75mod benchmarking;
76pub mod migrations;
77pub mod weights;
78
79pub use pallet::*;
80pub use weights::WeightInfo;
81
82const LOG_TARGET: &str = "runtime::collective";
83
84/// Simple index type for proposal counting.
85pub type ProposalIndex = u32;
86
87/// A number of members.
88///
89/// This also serves as a number of voting members, and since for motions, each member may
90/// vote exactly once, therefore also the number of votes for any given motion.
91pub type MemberCount = u32;
92
93/// Default voting strategy when a member is inactive.
94pub trait DefaultVote {
95	/// Get the default voting strategy, given:
96	///
97	/// - Whether the prime member voted Aye.
98	/// - Raw number of yes votes.
99	/// - Raw number of no votes.
100	/// - Total number of member count.
101	fn default_vote(
102		prime_vote: Option<bool>,
103		yes_votes: MemberCount,
104		no_votes: MemberCount,
105		len: MemberCount,
106	) -> bool;
107}
108
109/// Set the prime member's vote as the default vote.
110pub struct PrimeDefaultVote;
111
112impl DefaultVote for PrimeDefaultVote {
113	fn default_vote(
114		prime_vote: Option<bool>,
115		_yes_votes: MemberCount,
116		_no_votes: MemberCount,
117		_len: MemberCount,
118	) -> bool {
119		prime_vote.unwrap_or(false)
120	}
121}
122
123/// First see if yes vote are over majority of the whole collective. If so, set the default vote
124/// as yes. Otherwise, use the prime member's vote as the default vote.
125pub struct MoreThanMajorityThenPrimeDefaultVote;
126
127impl DefaultVote for MoreThanMajorityThenPrimeDefaultVote {
128	fn default_vote(
129		prime_vote: Option<bool>,
130		yes_votes: MemberCount,
131		_no_votes: MemberCount,
132		len: MemberCount,
133	) -> bool {
134		let more_than_majority = yes_votes * 2 > len;
135		more_than_majority || prime_vote.unwrap_or(false)
136	}
137}
138
139/// Origin for the collective module.
140#[derive(
141	PartialEq, Eq, Clone, Debug, Encode, Decode, DecodeWithMemTracking, TypeInfo, MaxEncodedLen,
142)]
143#[scale_info(skip_type_params(I))]
144#[codec(mel_bound(AccountId: MaxEncodedLen))]
145pub enum RawOrigin<AccountId, I> {
146	/// It has been condoned by a given number of members of the collective from a given total.
147	Members(MemberCount, MemberCount),
148	/// It has been condoned by a single member of the collective.
149	Member(AccountId),
150	/// Dummy to manage the fact we have instancing.
151	_Phantom(PhantomData<I>),
152}
153
154impl<AccountId, I> GetBacking for RawOrigin<AccountId, I> {
155	fn get_backing(&self) -> Option<Backing> {
156		match self {
157			RawOrigin::Members(n, d) => Some(Backing { approvals: *n, eligible: *d }),
158			_ => None,
159		}
160	}
161}
162
163/// Info for keeping track of a motion being voted on.
164#[derive(PartialEq, Eq, Clone, Encode, Decode, Debug, TypeInfo)]
165pub struct Votes<AccountId, BlockNumber> {
166	/// The proposal's unique index.
167	index: ProposalIndex,
168	/// The number of approval votes that are needed to pass the motion.
169	threshold: MemberCount,
170	/// The current set of voters that approved it.
171	ayes: Vec<AccountId>,
172	/// The current set of voters that rejected it.
173	nays: Vec<AccountId>,
174	/// The hard end time of this vote.
175	end: BlockNumber,
176}
177
178/// Types implementing various cost strategies for a given proposal count.
179///
180/// These types implement [Convert](sp_runtime::traits::Convert) trait and can be used with types
181/// like [HoldConsideration](`frame_support::traits::fungible::HoldConsideration`) implementing
182/// [Consideration](`frame_support::traits::Consideration`) trait.
183///
184/// ### Example:
185///
186/// 1. Linear increasing with helper types.
187#[doc = docify::embed!("src/tests.rs", deposit_types_with_linear_work)]
188/// 2. Geometrically increasing with helper types.
189#[doc = docify::embed!("src/tests.rs", deposit_types_with_geometric_work)]
190/// 3. Geometrically increasing with rounding.
191#[doc = docify::embed!("src/tests.rs", deposit_round_with_geometric_work)]
192pub mod deposit {
193	use core::marker::PhantomData;
194	use sp_core::Get;
195	use sp_runtime::{traits::Convert, FixedPointNumber, FixedU128, Saturating};
196
197	/// Constant deposit amount regardless of current proposal count.
198	/// Returns `None` if configured with zero deposit.
199	pub struct Constant<Deposit>(PhantomData<Deposit>);
200	impl<Deposit, Balance> Convert<u32, Balance> for Constant<Deposit>
201	where
202		Deposit: Get<Balance>,
203		Balance: frame_support::traits::tokens::Balance,
204	{
205		fn convert(_: u32) -> Balance {
206			Deposit::get()
207		}
208	}
209
210	/// Linear increasing with some offset.
211	/// f(x) = ax + b, a = `Slope`, x = `proposal_count`, b = `Offset`.
212	pub struct Linear<Slope, Offset>(PhantomData<(Slope, Offset)>);
213	impl<Slope, Offset, Balance> Convert<u32, Balance> for Linear<Slope, Offset>
214	where
215		Slope: Get<u32>,
216		Offset: Get<Balance>,
217		Balance: frame_support::traits::tokens::Balance,
218	{
219		fn convert(proposal_count: u32) -> Balance {
220			let base: Balance = Slope::get().saturating_mul(proposal_count).into();
221			Offset::get().saturating_add(base)
222		}
223	}
224
225	/// Geometrically increasing.
226	/// f(x) = a * r^x, a = `Base`, x = `proposal_count`, r = `Ratio`.
227	pub struct Geometric<Ratio, Base>(PhantomData<(Ratio, Base)>);
228	impl<Ratio, Base, Balance> Convert<u32, Balance> for Geometric<Ratio, Base>
229	where
230		Ratio: Get<FixedU128>,
231		Base: Get<Balance>,
232		Balance: frame_support::traits::tokens::Balance,
233	{
234		fn convert(proposal_count: u32) -> Balance {
235			Ratio::get()
236				.saturating_pow(proposal_count as usize)
237				.saturating_mul_int(Base::get())
238		}
239	}
240
241	/// Rounds a `Deposit` result with `Precision`.
242	/// Particularly useful for types like [`Geometric`] that might produce deposits with high
243	/// precision.
244	pub struct Round<Precision, Deposit>(PhantomData<(Precision, Deposit)>);
245	impl<Precision, Deposit, Balance> Convert<u32, Balance> for Round<Precision, Deposit>
246	where
247		Precision: Get<u32>,
248		Deposit: Convert<u32, Balance>,
249		Balance: frame_support::traits::tokens::Balance,
250	{
251		fn convert(proposal_count: u32) -> Balance {
252			let deposit = Deposit::convert(proposal_count);
253			if !deposit.is_zero() {
254				let factor: Balance =
255					Balance::from(10u32).saturating_pow(Precision::get() as usize);
256				if factor > deposit {
257					deposit
258				} else {
259					(deposit / factor) * factor
260				}
261			} else {
262				deposit
263			}
264		}
265	}
266
267	/// Defines `Period` for supplied `Step` implementing [`Convert`] trait.
268	pub struct Stepped<Period, Step>(PhantomData<(Period, Step)>);
269	impl<Period, Step, Balance> Convert<u32, Balance> for Stepped<Period, Step>
270	where
271		Period: Get<u32>,
272		Step: Convert<u32, Balance>,
273		Balance: frame_support::traits::tokens::Balance,
274	{
275		fn convert(proposal_count: u32) -> Balance {
276			let step_num = proposal_count / Period::get();
277			Step::convert(step_num)
278		}
279	}
280
281	/// Defines `Delay` for supplied `Step` implementing [`Convert`] trait.
282	pub struct Delayed<Delay, Deposit>(PhantomData<(Delay, Deposit)>);
283	impl<Delay, Deposit, Balance> Convert<u32, Balance> for Delayed<Delay, Deposit>
284	where
285		Delay: Get<u32>,
286		Deposit: Convert<u32, Balance>,
287		Balance: frame_support::traits::tokens::Balance,
288	{
289		fn convert(proposal_count: u32) -> Balance {
290			let delay = Delay::get();
291			if delay > proposal_count {
292				return Balance::zero();
293			}
294			let pos = proposal_count.saturating_sub(delay);
295			Deposit::convert(pos)
296		}
297	}
298
299	/// Defines `Ceil` for supplied `Step` implementing [`Convert`] trait.
300	pub struct WithCeil<Ceil, Deposit>(PhantomData<(Ceil, Deposit)>);
301	impl<Ceil, Deposit, Balance> Convert<u32, Balance> for WithCeil<Ceil, Deposit>
302	where
303		Ceil: Get<Balance>,
304		Deposit: Convert<u32, Balance>,
305		Balance: frame_support::traits::tokens::Balance,
306	{
307		fn convert(proposal_count: u32) -> Balance {
308			Deposit::convert(proposal_count).min(Ceil::get())
309		}
310	}
311}
312
313#[frame_support::pallet]
314pub mod pallet {
315	use super::*;
316	use frame_support::pallet_prelude::*;
317	use frame_system::pallet_prelude::*;
318
319	/// The in-code storage version.
320	const STORAGE_VERSION: StorageVersion = StorageVersion::new(4);
321
322	#[pallet::pallet]
323	#[pallet::storage_version(STORAGE_VERSION)]
324	#[pallet::without_storage_info]
325	pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
326
327	#[pallet::config]
328	pub trait Config<I: 'static = ()>: frame_system::Config {
329		/// The runtime origin type.
330		type RuntimeOrigin: From<RawOrigin<Self::AccountId, I>>;
331
332		/// The runtime call dispatch type.
333		type Proposal: Parameter
334			+ Dispatchable<
335				RuntimeOrigin = <Self as Config<I>>::RuntimeOrigin,
336				PostInfo = PostDispatchInfo,
337			> + From<frame_system::Call<Self>>
338			+ GetDispatchInfo;
339
340		/// The runtime event type.
341		#[allow(deprecated)]
342		type RuntimeEvent: From<Event<Self, I>>
343			+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
344
345		/// The time-out for council motions.
346		type MotionDuration: Get<BlockNumberFor<Self>>;
347
348		/// Maximum number of proposals allowed to be active in parallel.
349		type MaxProposals: Get<ProposalIndex>;
350
351		/// The maximum number of members supported by the pallet. Used for weight estimation.
352		///
353		/// NOTE:
354		/// + Benchmarks will need to be re-run and weights adjusted if this changes.
355		/// + This pallet assumes that dependents keep to the limit without enforcing it.
356		type MaxMembers: Get<MemberCount>;
357
358		/// Default vote strategy of this collective.
359		type DefaultVote: DefaultVote;
360
361		/// Weight information for extrinsics in this pallet.
362		type WeightInfo: WeightInfo;
363
364		/// Origin allowed to set collective members
365		type SetMembersOrigin: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin>;
366
367		/// The maximum weight of a dispatch call that can be proposed and executed.
368		#[pallet::constant]
369		type MaxProposalWeight: Get<Weight>;
370
371		/// Origin from which a proposal in any status may be disapproved without associated cost
372		/// for a proposer.
373		type DisapproveOrigin: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin>;
374
375		/// Origin from which any malicious proposal may be killed with associated cost for a
376		/// proposer.
377		///
378		/// The associated cost is set by [`Config::Consideration`] and can be none.
379		type KillOrigin: EnsureOrigin<<Self as frame_system::Config>::RuntimeOrigin>;
380
381		/// Mechanism to assess the necessity of some cost for publishing and storing a proposal.
382		///
383		/// The footprint is defined as `proposal_count`, which reflects the total number of active
384		/// proposals in the system, excluding the one currently being proposed. The cost may vary
385		/// based on this count.
386		///
387		/// Note: If the resulting deposits are excessively high and cause benchmark failures,
388		/// consider using a constant cost (e.g., [`crate::deposit::Constant`]) equal to the minimum
389		/// balance under the `runtime-benchmarks` feature.
390		type Consideration: MaybeConsideration<Self::AccountId, u32>;
391	}
392
393	#[pallet::genesis_config]
394	#[derive(frame_support::DefaultNoBound)]
395	pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
396		#[serde(skip)]
397		pub phantom: PhantomData<I>,
398		pub members: Vec<T::AccountId>,
399	}
400
401	#[pallet::genesis_build]
402	impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
403		fn build(&self) {
404			use alloc::collections::btree_set::BTreeSet;
405			let members_set: BTreeSet<_> = self.members.iter().collect();
406			assert_eq!(
407				members_set.len(),
408				self.members.len(),
409				"Members cannot contain duplicate accounts."
410			);
411			assert!(
412				self.members.len() <= T::MaxMembers::get() as usize,
413				"Members length cannot exceed MaxMembers.",
414			);
415
416			Pallet::<T, I>::initialize_members(&self.members)
417		}
418	}
419
420	/// Origin for the collective pallet.
421	#[pallet::origin]
422	pub type Origin<T, I = ()> = RawOrigin<<T as frame_system::Config>::AccountId, I>;
423
424	/// The hashes of the active proposals.
425	#[pallet::storage]
426	pub type Proposals<T: Config<I>, I: 'static = ()> =
427		StorageValue<_, BoundedVec<T::Hash, T::MaxProposals>, ValueQuery>;
428
429	/// Actual proposal for a given hash, if it's current.
430	#[pallet::storage]
431	pub type ProposalOf<T: Config<I>, I: 'static = ()> =
432		StorageMap<_, Identity, T::Hash, <T as Config<I>>::Proposal, OptionQuery>;
433
434	/// Consideration cost created for publishing and storing a proposal.
435	///
436	/// Determined by [Config::Consideration] and may be not present for certain proposals (e.g. if
437	/// the proposal count at the time of creation was below threshold N).
438	#[pallet::storage]
439	pub type CostOf<T: Config<I>, I: 'static = ()> =
440		StorageMap<_, Identity, T::Hash, (T::AccountId, T::Consideration), OptionQuery>;
441
442	/// Votes on a given proposal, if it is ongoing.
443	#[pallet::storage]
444	pub type Voting<T: Config<I>, I: 'static = ()> =
445		StorageMap<_, Identity, T::Hash, Votes<T::AccountId, BlockNumberFor<T>>, OptionQuery>;
446
447	/// Proposals so far.
448	#[pallet::storage]
449	pub type ProposalCount<T: Config<I>, I: 'static = ()> = StorageValue<_, u32, ValueQuery>;
450
451	/// The current members of the collective. This is stored sorted (just by value).
452	#[pallet::storage]
453	pub type Members<T: Config<I>, I: 'static = ()> =
454		StorageValue<_, Vec<T::AccountId>, ValueQuery>;
455
456	/// The prime member that helps determine the default vote behavior in case of abstentions.
457	#[pallet::storage]
458	pub type Prime<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId, OptionQuery>;
459
460	#[pallet::event]
461	#[pallet::generate_deposit(pub(super) fn deposit_event)]
462	pub enum Event<T: Config<I>, I: 'static = ()> {
463		/// A motion (given hash) has been proposed (by given account) with a threshold (given
464		/// `MemberCount`).
465		Proposed {
466			account: T::AccountId,
467			proposal_index: ProposalIndex,
468			proposal_hash: T::Hash,
469			threshold: MemberCount,
470		},
471		/// A motion (given hash) has been voted on by given account, leaving
472		/// a tally (yes votes and no votes given respectively as `MemberCount`).
473		Voted {
474			account: T::AccountId,
475			proposal_hash: T::Hash,
476			voted: bool,
477			yes: MemberCount,
478			no: MemberCount,
479		},
480		/// A motion was approved by the required threshold.
481		Approved { proposal_hash: T::Hash },
482		/// A motion was not approved by the required threshold.
483		Disapproved { proposal_hash: T::Hash },
484		/// A motion was executed; result will be `Ok` if it returned without error.
485		Executed { proposal_hash: T::Hash, result: DispatchResult },
486		/// A single member did some action; result will be `Ok` if it returned without error.
487		MemberExecuted { proposal_hash: T::Hash, result: DispatchResult },
488		/// A proposal was closed because its threshold was reached or after its duration was up.
489		Closed { proposal_hash: T::Hash, yes: MemberCount, no: MemberCount },
490		/// A proposal was killed.
491		Killed { proposal_hash: T::Hash },
492		/// Some cost for storing a proposal was burned.
493		ProposalCostBurned { proposal_hash: T::Hash, who: T::AccountId },
494		/// Some cost for storing a proposal was released.
495		ProposalCostReleased { proposal_hash: T::Hash, who: T::AccountId },
496	}
497
498	#[pallet::error]
499	pub enum Error<T, I = ()> {
500		/// Account is not a member
501		NotMember,
502		/// Duplicate proposals not allowed
503		DuplicateProposal,
504		/// Proposal must exist
505		ProposalMissing,
506		/// Mismatched index
507		WrongIndex,
508		/// Duplicate vote ignored
509		DuplicateVote,
510		/// Members are already initialized!
511		AlreadyInitialized,
512		/// The close call was made too early, before the end of the voting.
513		TooEarly,
514		/// There can only be a maximum of `MaxProposals` active proposals.
515		TooManyProposals,
516		/// The given weight bound for the proposal was too low.
517		WrongProposalWeight,
518		/// The given length bound for the proposal was too low.
519		WrongProposalLength,
520		/// Prime account is not a member
521		PrimeAccountNotMember,
522		/// Proposal is still active.
523		ProposalActive,
524	}
525
526	#[pallet::hooks]
527	impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
528		#[cfg(feature = "try-runtime")]
529		fn try_state(_n: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
530			Self::do_try_state()
531		}
532	}
533
534	/// A reason for the pallet placing a hold on funds.
535	#[pallet::composite_enum]
536	pub enum HoldReason<I: 'static = ()> {
537		/// Funds are held for submitting and storing a proposal.
538		#[codec(index = 0)]
539		ProposalSubmission,
540	}
541
542	// Note that councillor operations are assigned to the operational class.
543	#[pallet::call]
544	impl<T: Config<I>, I: 'static> Pallet<T, I> {
545		/// Set the collective's membership.
546		///
547		/// - `new_members`: The new member list. Be nice to the chain and provide it sorted.
548		/// - `prime`: The prime member whose vote sets the default.
549		/// - `old_count`: The upper bound for the previous number of members in storage. Used for
550		///   weight estimation.
551		///
552		/// The dispatch of this call must be `SetMembersOrigin`.
553		///
554		/// NOTE: Does not enforce the expected `MaxMembers` limit on the amount of members, but
555		///       the weight estimations rely on it to estimate dispatchable weight.
556		///
557		/// # WARNING:
558		///
559		/// The `pallet-collective` can also be managed by logic outside of the pallet through the
560		/// implementation of the trait [`ChangeMembers`].
561		/// Any call to `set_members` must be careful that the member set doesn't get out of sync
562		/// with other logic managing the member set.
563		///
564		/// ## Complexity:
565		/// - `O(MP + N)` where:
566		///   - `M` old-members-count (code- and governance-bounded)
567		///   - `N` new-members-count (code- and governance-bounded)
568		///   - `P` proposals-count (code-bounded)
569		#[pallet::call_index(0)]
570		#[pallet::weight((
571			T::WeightInfo::set_members(
572				*old_count, // M
573				new_members.len() as u32, // N
574				T::MaxProposals::get() // P
575			),
576			DispatchClass::Operational
577		))]
578		pub fn set_members(
579			origin: OriginFor<T>,
580			new_members: Vec<T::AccountId>,
581			prime: Option<T::AccountId>,
582			old_count: MemberCount,
583		) -> DispatchResultWithPostInfo {
584			T::SetMembersOrigin::ensure_origin(origin)?;
585			if new_members.len() > T::MaxMembers::get() as usize {
586				log::error!(
587					target: LOG_TARGET,
588					"New members count ({}) exceeds maximum amount of members expected ({}).",
589					new_members.len(),
590					T::MaxMembers::get(),
591				);
592			}
593
594			let old = Members::<T, I>::get();
595			if old.len() > old_count as usize {
596				log::warn!(
597					target: LOG_TARGET,
598					"Wrong count used to estimate set_members weight. expected ({}) vs actual ({})",
599					old_count,
600					old.len(),
601				);
602			}
603			if let Some(p) = &prime {
604				ensure!(new_members.contains(p), Error::<T, I>::PrimeAccountNotMember);
605			}
606			let mut new_members = new_members;
607			new_members.sort();
608			<Self as ChangeMembers<T::AccountId>>::set_members_sorted(&new_members, &old);
609			Prime::<T, I>::set(prime);
610
611			Ok(Some(T::WeightInfo::set_members(
612				old.len() as u32,         // M
613				new_members.len() as u32, // N
614				T::MaxProposals::get(),   // P
615			))
616			.into())
617		}
618
619		/// Dispatch a proposal from a member using the `Member` origin.
620		///
621		/// Origin must be a member of the collective.
622		///
623		/// ## Complexity:
624		/// - `O(B + M + P)` where:
625		/// - `B` is `proposal` size in bytes (length-fee-bounded)
626		/// - `M` members-count (code-bounded)
627		/// - `P` complexity of dispatching `proposal`
628		#[pallet::call_index(1)]
629		#[pallet::weight((
630			T::WeightInfo::execute(
631				*length_bound, // B
632				T::MaxMembers::get(), // M
633			).saturating_add(proposal.get_dispatch_info().call_weight), // P
634			DispatchClass::Operational
635		))]
636		pub fn execute(
637			origin: OriginFor<T>,
638			proposal: Box<<T as Config<I>>::Proposal>,
639			#[pallet::compact] length_bound: u32,
640		) -> DispatchResultWithPostInfo {
641			let who = ensure_signed(origin)?;
642			let members = Members::<T, I>::get();
643			ensure!(members.contains(&who), Error::<T, I>::NotMember);
644			let proposal_len = proposal.encoded_size();
645			ensure!(proposal_len <= length_bound as usize, Error::<T, I>::WrongProposalLength);
646
647			let proposal_hash = T::Hashing::hash_of(&proposal);
648			let result = proposal.dispatch(RawOrigin::Member(who).into());
649			Self::deposit_event(Event::MemberExecuted {
650				proposal_hash,
651				result: result.map(|_| ()).map_err(|e| e.error),
652			});
653
654			Ok(get_result_weight(result)
655				.map(|w| {
656					T::WeightInfo::execute(
657						proposal_len as u32,  // B
658						members.len() as u32, // M
659					)
660					.saturating_add(w) // P
661				})
662				.into())
663		}
664
665		/// Add a new proposal to either be voted on or executed directly.
666		///
667		/// Requires the sender to be member.
668		///
669		/// `threshold` determines whether `proposal` is executed directly (`threshold < 2`)
670		/// or put up for voting.
671		///
672		/// ## Complexity
673		/// - `O(B + M + P1)` or `O(B + M + P2)` where:
674		///   - `B` is `proposal` size in bytes (length-fee-bounded)
675		///   - `M` is members-count (code- and governance-bounded)
676		///   - branching is influenced by `threshold` where:
677		///     - `P1` is proposal execution complexity (`threshold < 2`)
678		///     - `P2` is proposals-count (code-bounded) (`threshold >= 2`)
679		#[pallet::call_index(2)]
680		#[pallet::weight((
681			if *threshold < 2 {
682				T::WeightInfo::propose_execute(
683					*length_bound, // B
684					T::MaxMembers::get(), // M
685				).saturating_add(proposal.get_dispatch_info().call_weight) // P1
686			} else {
687				T::WeightInfo::propose_proposed(
688					*length_bound, // B
689					T::MaxMembers::get(), // M
690					T::MaxProposals::get(), // P2
691				)
692			},
693			DispatchClass::Operational
694		))]
695		pub fn propose(
696			origin: OriginFor<T>,
697			#[pallet::compact] threshold: MemberCount,
698			proposal: Box<<T as Config<I>>::Proposal>,
699			#[pallet::compact] length_bound: u32,
700		) -> DispatchResultWithPostInfo {
701			let who = ensure_signed(origin)?;
702			let members = Members::<T, I>::get();
703			ensure!(members.contains(&who), Error::<T, I>::NotMember);
704
705			if threshold < 2 {
706				let (proposal_len, result) = Self::do_propose_execute(proposal, length_bound)?;
707
708				Ok(get_result_weight(result)
709					.map(|w| {
710						T::WeightInfo::propose_execute(
711							proposal_len as u32,  // B
712							members.len() as u32, // M
713						)
714						.saturating_add(w) // P1
715					})
716					.into())
717			} else {
718				let (proposal_len, active_proposals) =
719					Self::do_propose_proposed(who, threshold, proposal, length_bound)?;
720
721				Ok(Some(T::WeightInfo::propose_proposed(
722					proposal_len as u32,  // B
723					members.len() as u32, // M
724					active_proposals,     // P2
725				))
726				.into())
727			}
728		}
729
730		/// Add an aye or nay vote for the sender to the given proposal.
731		///
732		/// Requires the sender to be a member.
733		///
734		/// Transaction fees will be waived if the member is voting on any particular proposal
735		/// for the first time and the call is successful. Subsequent vote changes will charge a
736		/// fee.
737		/// ## Complexity
738		/// - `O(M)` where `M` is members-count (code- and governance-bounded)
739		#[pallet::call_index(3)]
740		#[pallet::weight((T::WeightInfo::vote(T::MaxMembers::get()), DispatchClass::Operational))]
741		pub fn vote(
742			origin: OriginFor<T>,
743			proposal: T::Hash,
744			#[pallet::compact] index: ProposalIndex,
745			approve: bool,
746		) -> DispatchResultWithPostInfo {
747			let who = ensure_signed(origin)?;
748			let members = Members::<T, I>::get();
749			ensure!(members.contains(&who), Error::<T, I>::NotMember);
750
751			// Detects first vote of the member in the motion
752			let is_account_voting_first_time = Self::do_vote(who, proposal, index, approve)?;
753
754			if is_account_voting_first_time {
755				Ok((Some(T::WeightInfo::vote(members.len() as u32)), Pays::No).into())
756			} else {
757				Ok((Some(T::WeightInfo::vote(members.len() as u32)), Pays::Yes).into())
758			}
759		}
760
761		// Index 4 was `close_old_weight`; it was removed due to weights v1 deprecation.
762
763		/// Disapprove a proposal, close, and remove it from the system, regardless of its current
764		/// state.
765		///
766		/// Must be called by the Root origin.
767		///
768		/// Parameters:
769		/// * `proposal_hash`: The hash of the proposal that should be disapproved.
770		///
771		/// ## Complexity
772		/// O(P) where P is the number of max proposals
773		#[pallet::call_index(5)]
774		#[pallet::weight(T::WeightInfo::disapprove_proposal(T::MaxProposals::get()))]
775		pub fn disapprove_proposal(
776			origin: OriginFor<T>,
777			proposal_hash: T::Hash,
778		) -> DispatchResultWithPostInfo {
779			T::DisapproveOrigin::ensure_origin(origin)?;
780			let proposal_count = Self::do_disapprove_proposal(proposal_hash);
781			Ok(Some(T::WeightInfo::disapprove_proposal(proposal_count)).into())
782		}
783
784		/// Close a vote that is either approved, disapproved or whose voting period has ended.
785		///
786		/// May be called by any signed account in order to finish voting and close the proposal.
787		///
788		/// If called before the end of the voting period it will only close the vote if it is
789		/// has enough votes to be approved or disapproved.
790		///
791		/// If called after the end of the voting period abstentions are counted as rejections
792		/// unless there is a prime member set and the prime member cast an approval.
793		///
794		/// If the close operation completes successfully with disapproval, the transaction fee will
795		/// be waived. Otherwise execution of the approved operation will be charged to the caller.
796		///
797		/// + `proposal_weight_bound`: The maximum amount of weight consumed by executing the closed
798		/// proposal.
799		/// + `length_bound`: The upper bound for the length of the proposal in storage. Checked via
800		/// `storage::read` so it is `size_of::<u32>() == 4` larger than the pure length.
801		///
802		/// ## Complexity
803		/// - `O(B + M + P1 + P2)` where:
804		///   - `B` is `proposal` size in bytes (length-fee-bounded)
805		///   - `M` is members-count (code- and governance-bounded)
806		///   - `P1` is the complexity of `proposal` preimage.
807		///   - `P2` is proposal-count (code-bounded)
808		#[pallet::call_index(6)]
809		#[pallet::weight((
810			{
811				let b = *length_bound;
812				let m = T::MaxMembers::get();
813				let p1 = *proposal_weight_bound;
814				let p2 = T::MaxProposals::get();
815				T::WeightInfo::close_early_approved(b, m, p2)
816					.max(T::WeightInfo::close_early_disapproved(m, p2))
817					.max(T::WeightInfo::close_approved(b, m, p2))
818					.max(T::WeightInfo::close_disapproved(m, p2))
819					.saturating_add(p1)
820			},
821			DispatchClass::Operational
822		))]
823		pub fn close(
824			origin: OriginFor<T>,
825			proposal_hash: T::Hash,
826			#[pallet::compact] index: ProposalIndex,
827			proposal_weight_bound: Weight,
828			#[pallet::compact] length_bound: u32,
829		) -> DispatchResultWithPostInfo {
830			ensure_signed(origin)?;
831
832			Self::do_close(proposal_hash, index, proposal_weight_bound, length_bound)
833		}
834
835		/// Disapprove the proposal and burn the cost held for storing this proposal.
836		///
837		/// Parameters:
838		/// - `origin`: must be the `KillOrigin`.
839		/// - `proposal_hash`: The hash of the proposal that should be killed.
840		///
841		/// Emits `Killed` and `ProposalCostBurned` if any cost was held for a given proposal.
842		#[pallet::call_index(7)]
843		#[pallet::weight(T::WeightInfo::kill(1, T::MaxProposals::get()))]
844		pub fn kill(origin: OriginFor<T>, proposal_hash: T::Hash) -> DispatchResultWithPostInfo {
845			T::KillOrigin::ensure_origin(origin)?;
846			ensure!(
847				ProposalOf::<T, I>::get(&proposal_hash).is_some(),
848				Error::<T, I>::ProposalMissing
849			);
850			let burned = if let Some((who, cost)) = <CostOf<T, I>>::take(proposal_hash) {
851				cost.burn(&who);
852				Self::deposit_event(Event::ProposalCostBurned { proposal_hash, who });
853				true
854			} else {
855				false
856			};
857			let proposal_count = Self::remove_proposal(proposal_hash);
858
859			Self::deposit_event(Event::Killed { proposal_hash });
860
861			Ok(Some(T::WeightInfo::kill(burned as u32, proposal_count)).into())
862		}
863
864		/// Release the cost held for storing a proposal once the given proposal is completed.
865		///
866		/// If there is no associated cost for the given proposal, this call will have no effect.
867		///
868		/// Parameters:
869		/// - `origin`: must be `Signed` or `Root`.
870		/// - `proposal_hash`: The hash of the proposal.
871		///
872		/// Emits `ProposalCostReleased` if any cost held for a given proposal.
873		#[pallet::call_index(8)]
874		#[pallet::weight(T::WeightInfo::release_proposal_cost())]
875		pub fn release_proposal_cost(
876			origin: OriginFor<T>,
877			proposal_hash: T::Hash,
878		) -> DispatchResult {
879			ensure_signed_or_root(origin)?;
880			ensure!(
881				ProposalOf::<T, I>::get(&proposal_hash).is_none(),
882				Error::<T, I>::ProposalActive
883			);
884			if let Some((who, cost)) = <CostOf<T, I>>::take(proposal_hash) {
885				cost.drop(&who)?;
886				Self::deposit_event(Event::ProposalCostReleased { proposal_hash, who });
887			}
888
889			Ok(())
890		}
891	}
892}
893
894/// Return the weight of a dispatch call result as an `Option`.
895///
896/// Will return the weight regardless of what the state of the result is.
897fn get_result_weight(result: DispatchResultWithPostInfo) -> Option<Weight> {
898	match result {
899		Ok(post_info) => post_info.actual_weight,
900		Err(err) => err.post_info.actual_weight,
901	}
902}
903
904impl<T: Config<I>, I: 'static> Pallet<T, I> {
905	/// Check whether `who` is a member of the collective.
906	pub fn is_member(who: &T::AccountId) -> bool {
907		// Note: The dispatchables *do not* use this to check membership so make sure
908		// to update those if this is changed.
909		Members::<T, I>::get().contains(who)
910	}
911
912	/// Execute immediately when adding a new proposal.
913	pub fn do_propose_execute(
914		proposal: Box<<T as Config<I>>::Proposal>,
915		length_bound: MemberCount,
916	) -> Result<(u32, DispatchResultWithPostInfo), DispatchError> {
917		let proposal_len = proposal.encoded_size();
918		ensure!(proposal_len <= length_bound as usize, Error::<T, I>::WrongProposalLength);
919		let proposal_weight = proposal.get_dispatch_info().call_weight;
920		ensure!(
921			proposal_weight.all_lte(T::MaxProposalWeight::get()),
922			Error::<T, I>::WrongProposalWeight
923		);
924
925		let proposal_hash = T::Hashing::hash_of(&proposal);
926		ensure!(!<ProposalOf<T, I>>::contains_key(proposal_hash), Error::<T, I>::DuplicateProposal);
927
928		let seats = Members::<T, I>::get().len() as MemberCount;
929		let result = proposal.dispatch(RawOrigin::Members(1, seats).into());
930		Self::deposit_event(Event::Executed {
931			proposal_hash,
932			result: result.map(|_| ()).map_err(|e| e.error),
933		});
934		Ok((proposal_len as u32, result))
935	}
936
937	/// Add a new proposal to be voted.
938	pub fn do_propose_proposed(
939		who: T::AccountId,
940		threshold: MemberCount,
941		proposal: Box<<T as Config<I>>::Proposal>,
942		length_bound: MemberCount,
943	) -> Result<(u32, u32), DispatchError> {
944		let proposal_len = proposal.encoded_size();
945		ensure!(proposal_len <= length_bound as usize, Error::<T, I>::WrongProposalLength);
946		let proposal_weight = proposal.get_dispatch_info().call_weight;
947		ensure!(
948			proposal_weight.all_lte(T::MaxProposalWeight::get()),
949			Error::<T, I>::WrongProposalWeight
950		);
951
952		let proposal_hash = T::Hashing::hash_of(&proposal);
953		ensure!(!<ProposalOf<T, I>>::contains_key(proposal_hash), Error::<T, I>::DuplicateProposal);
954
955		let active_proposals =
956			<Proposals<T, I>>::try_mutate(|proposals| -> Result<usize, DispatchError> {
957				proposals.try_push(proposal_hash).map_err(|_| Error::<T, I>::TooManyProposals)?;
958				Ok(proposals.len())
959			})?;
960
961		let cost = T::Consideration::new(&who, active_proposals as u32 - 1)?;
962		if !cost.is_none() {
963			<CostOf<T, I>>::insert(proposal_hash, (who.clone(), cost));
964		}
965
966		let index = ProposalCount::<T, I>::get();
967
968		<ProposalCount<T, I>>::mutate(|i| *i += 1);
969		<ProposalOf<T, I>>::insert(proposal_hash, proposal);
970		let votes = {
971			let end = frame_system::Pallet::<T>::block_number() + T::MotionDuration::get();
972			Votes { index, threshold, ayes: vec![], nays: vec![], end }
973		};
974		<Voting<T, I>>::insert(proposal_hash, votes);
975
976		Self::deposit_event(Event::Proposed {
977			account: who,
978			proposal_index: index,
979			proposal_hash,
980			threshold,
981		});
982		Ok((proposal_len as u32, active_proposals as u32))
983	}
984
985	/// Add an aye or nay vote for the member to the given proposal, returns true if it's the first
986	/// vote of the member in the motion
987	pub fn do_vote(
988		who: T::AccountId,
989		proposal: T::Hash,
990		index: ProposalIndex,
991		approve: bool,
992	) -> Result<bool, DispatchError> {
993		let mut voting = Voting::<T, I>::get(&proposal).ok_or(Error::<T, I>::ProposalMissing)?;
994		ensure!(voting.index == index, Error::<T, I>::WrongIndex);
995
996		let position_yes = voting.ayes.iter().position(|a| a == &who);
997		let position_no = voting.nays.iter().position(|a| a == &who);
998
999		// Detects first vote of the member in the motion
1000		let is_account_voting_first_time = position_yes.is_none() && position_no.is_none();
1001
1002		if approve {
1003			if position_yes.is_none() {
1004				voting.ayes.push(who.clone());
1005			} else {
1006				return Err(Error::<T, I>::DuplicateVote.into());
1007			}
1008			if let Some(pos) = position_no {
1009				voting.nays.swap_remove(pos);
1010			}
1011		} else {
1012			if position_no.is_none() {
1013				voting.nays.push(who.clone());
1014			} else {
1015				return Err(Error::<T, I>::DuplicateVote.into());
1016			}
1017			if let Some(pos) = position_yes {
1018				voting.ayes.swap_remove(pos);
1019			}
1020		}
1021
1022		let yes_votes = voting.ayes.len() as MemberCount;
1023		let no_votes = voting.nays.len() as MemberCount;
1024		Self::deposit_event(Event::Voted {
1025			account: who,
1026			proposal_hash: proposal,
1027			voted: approve,
1028			yes: yes_votes,
1029			no: no_votes,
1030		});
1031
1032		Voting::<T, I>::insert(&proposal, voting);
1033
1034		Ok(is_account_voting_first_time)
1035	}
1036
1037	/// Close a vote that is either approved, disapproved or whose voting period has ended.
1038	pub fn do_close(
1039		proposal_hash: T::Hash,
1040		index: ProposalIndex,
1041		proposal_weight_bound: Weight,
1042		length_bound: u32,
1043	) -> DispatchResultWithPostInfo {
1044		let voting = Voting::<T, I>::get(&proposal_hash).ok_or(Error::<T, I>::ProposalMissing)?;
1045		ensure!(voting.index == index, Error::<T, I>::WrongIndex);
1046
1047		let mut no_votes = voting.nays.len() as MemberCount;
1048		let mut yes_votes = voting.ayes.len() as MemberCount;
1049		let seats = Members::<T, I>::get().len() as MemberCount;
1050		let approved = yes_votes >= voting.threshold;
1051		let disapproved = seats.saturating_sub(no_votes) < voting.threshold;
1052		// Allow (dis-)approving the proposal as soon as there are enough votes.
1053		if approved {
1054			let (proposal, len) = Self::validate_and_get_proposal(
1055				&proposal_hash,
1056				length_bound,
1057				proposal_weight_bound,
1058			)?;
1059			Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes });
1060			let (proposal_weight, proposal_count) =
1061				Self::do_approve_proposal(seats, yes_votes, proposal_hash, proposal);
1062			return Ok((
1063				Some(
1064					T::WeightInfo::close_early_approved(len as u32, seats, proposal_count)
1065						.saturating_add(proposal_weight),
1066				),
1067				Pays::Yes,
1068			)
1069				.into());
1070		} else if disapproved {
1071			Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes });
1072			let proposal_count = Self::do_disapprove_proposal(proposal_hash);
1073			return Ok((
1074				Some(T::WeightInfo::close_early_disapproved(seats, proposal_count)),
1075				Pays::No,
1076			)
1077				.into());
1078		}
1079
1080		// Only allow actual closing of the proposal after the voting period has ended.
1081		ensure!(frame_system::Pallet::<T>::block_number() >= voting.end, Error::<T, I>::TooEarly);
1082
1083		let prime_vote = Prime::<T, I>::get().map(|who| voting.ayes.iter().any(|a| a == &who));
1084
1085		// default voting strategy.
1086		let default = T::DefaultVote::default_vote(prime_vote, yes_votes, no_votes, seats);
1087
1088		let abstentions = seats - (yes_votes + no_votes);
1089		match default {
1090			true => yes_votes += abstentions,
1091			false => no_votes += abstentions,
1092		}
1093		let approved = yes_votes >= voting.threshold;
1094
1095		if approved {
1096			let (proposal, len) = Self::validate_and_get_proposal(
1097				&proposal_hash,
1098				length_bound,
1099				proposal_weight_bound,
1100			)?;
1101			Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes });
1102			let (proposal_weight, proposal_count) =
1103				Self::do_approve_proposal(seats, yes_votes, proposal_hash, proposal);
1104			Ok((
1105				Some(
1106					T::WeightInfo::close_approved(len as u32, seats, proposal_count)
1107						.saturating_add(proposal_weight),
1108				),
1109				Pays::Yes,
1110			)
1111				.into())
1112		} else {
1113			Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes });
1114			let proposal_count = Self::do_disapprove_proposal(proposal_hash);
1115			Ok((Some(T::WeightInfo::close_disapproved(seats, proposal_count)), Pays::No).into())
1116		}
1117	}
1118
1119	/// Ensure that the right proposal bounds were passed and get the proposal from storage.
1120	///
1121	/// Checks the length in storage via `storage::read` which adds an extra `size_of::<u32>() == 4`
1122	/// to the length.
1123	fn validate_and_get_proposal(
1124		hash: &T::Hash,
1125		length_bound: u32,
1126		weight_bound: Weight,
1127	) -> Result<(<T as Config<I>>::Proposal, usize), DispatchError> {
1128		let key = ProposalOf::<T, I>::hashed_key_for(hash);
1129		// read the length of the proposal storage entry directly
1130		let proposal_len =
1131			storage::read(&key, &mut [0; 0], 0).ok_or(Error::<T, I>::ProposalMissing)?;
1132		ensure!(proposal_len <= length_bound, Error::<T, I>::WrongProposalLength);
1133		let proposal = ProposalOf::<T, I>::get(hash).ok_or(Error::<T, I>::ProposalMissing)?;
1134		let proposal_weight = proposal.get_dispatch_info().call_weight;
1135		ensure!(proposal_weight.all_lte(weight_bound), Error::<T, I>::WrongProposalWeight);
1136		Ok((proposal, proposal_len as usize))
1137	}
1138
1139	/// Weight:
1140	/// If `approved`:
1141	/// - the weight of `proposal` preimage.
1142	/// - two events deposited.
1143	/// - two removals, one mutation.
1144	/// - computation and i/o `O(P + L)` where:
1145	///   - `P` is number of active proposals,
1146	///   - `L` is the encoded length of `proposal` preimage.
1147	///
1148	/// If not `approved`:
1149	/// - one event deposited.
1150	/// Two removals, one mutation.
1151	/// Computation and i/o `O(P)` where:
1152	/// - `P` is number of active proposals
1153	fn do_approve_proposal(
1154		seats: MemberCount,
1155		yes_votes: MemberCount,
1156		proposal_hash: T::Hash,
1157		proposal: <T as Config<I>>::Proposal,
1158	) -> (Weight, u32) {
1159		Self::deposit_event(Event::Approved { proposal_hash });
1160
1161		let dispatch_weight = proposal.get_dispatch_info().call_weight;
1162		let origin = RawOrigin::Members(yes_votes, seats).into();
1163		let result = proposal.dispatch(origin);
1164		Self::deposit_event(Event::Executed {
1165			proposal_hash,
1166			result: result.map(|_| ()).map_err(|e| e.error),
1167		});
1168		// default to the dispatch info weight for safety
1169		let proposal_weight = get_result_weight(result).unwrap_or(dispatch_weight); // P1
1170
1171		let proposal_count = Self::remove_proposal(proposal_hash);
1172		(proposal_weight, proposal_count)
1173	}
1174
1175	/// Removes a proposal from the pallet, and deposit the `Disapproved` event.
1176	pub fn do_disapprove_proposal(proposal_hash: T::Hash) -> u32 {
1177		// disapproved
1178		Self::deposit_event(Event::Disapproved { proposal_hash });
1179		Self::remove_proposal(proposal_hash)
1180	}
1181
1182	// Removes a proposal from the pallet, cleaning up votes and the vector of proposals.
1183	fn remove_proposal(proposal_hash: T::Hash) -> u32 {
1184		// remove proposal and vote
1185		ProposalOf::<T, I>::remove(&proposal_hash);
1186		Voting::<T, I>::remove(&proposal_hash);
1187		let num_proposals = Proposals::<T, I>::mutate(|proposals| {
1188			proposals.retain(|h| h != &proposal_hash);
1189			proposals.len() + 1 // calculate weight based on original length
1190		});
1191		num_proposals as u32
1192	}
1193
1194	/// Ensure the correctness of the state of this pallet.
1195	///
1196	/// The following expectation must always apply.
1197	///
1198	/// ## Expectations:
1199	///
1200	/// Looking at proposals:
1201	///
1202	/// * Each hash of a proposal that is stored inside `Proposals` must have a
1203	/// call mapped to it inside the `ProposalOf` storage map.
1204	/// * `ProposalCount` must always be more or equal to the number of
1205	/// proposals inside the `Proposals` storage value. The reason why
1206	/// `ProposalCount` can be more is because when a proposal is removed the
1207	/// count is not deducted.
1208	/// * Count of `ProposalOf` should match the count of `Proposals`
1209	///
1210	/// Looking at votes:
1211	/// * The sum of aye and nay votes for a proposal can never exceed
1212	///  `MaxMembers`.
1213	/// * The proposal index inside the `Voting` storage map must be unique.
1214	/// * All proposal hashes inside `Voting` must exist in `Proposals`.
1215	///
1216	/// Looking at members:
1217	/// * The members count must never exceed `MaxMembers`.
1218	/// * All the members must be sorted by value.
1219	///
1220	/// Looking at prime account:
1221	/// * The prime account must be a member of the collective.
1222	#[cfg(any(feature = "try-runtime", test))]
1223	fn do_try_state() -> Result<(), TryRuntimeError> {
1224		Proposals::<T, I>::get().into_iter().try_for_each(
1225			|proposal| -> Result<(), TryRuntimeError> {
1226				ensure!(
1227					ProposalOf::<T, I>::get(proposal).is_some(),
1228					"Proposal hash from `Proposals` is not found inside the `ProposalOf` mapping."
1229				);
1230				Ok(())
1231			},
1232		)?;
1233
1234		ensure!(
1235			Proposals::<T, I>::get().into_iter().count() <= ProposalCount::<T, I>::get() as usize,
1236			"The actual number of proposals is greater than `ProposalCount`"
1237		);
1238		ensure!(
1239			Proposals::<T, I>::get().into_iter().count() == <ProposalOf<T, I>>::iter_keys().count(),
1240			"Proposal count inside `Proposals` is not equal to the proposal count in `ProposalOf`"
1241		);
1242
1243		Proposals::<T, I>::get().into_iter().try_for_each(
1244			|proposal| -> Result<(), TryRuntimeError> {
1245				if let Some(votes) = Voting::<T, I>::get(proposal) {
1246					let ayes = votes.ayes.len();
1247					let nays = votes.nays.len();
1248
1249					ensure!(
1250						ayes.saturating_add(nays) <= T::MaxMembers::get() as usize,
1251						"The sum of ayes and nays is greater than `MaxMembers`"
1252					);
1253				}
1254				Ok(())
1255			},
1256		)?;
1257
1258		let mut proposal_indices = vec![];
1259		Proposals::<T, I>::get().into_iter().try_for_each(
1260			|proposal| -> Result<(), TryRuntimeError> {
1261				if let Some(votes) = Voting::<T, I>::get(proposal) {
1262					let proposal_index = votes.index;
1263					ensure!(
1264						!proposal_indices.contains(&proposal_index),
1265						"The proposal index is not unique."
1266					);
1267					proposal_indices.push(proposal_index);
1268				}
1269				Ok(())
1270			},
1271		)?;
1272
1273		<Voting<T, I>>::iter_keys().try_for_each(
1274			|proposal_hash| -> Result<(), TryRuntimeError> {
1275				ensure!(
1276					Proposals::<T, I>::get().contains(&proposal_hash),
1277					"`Proposals` doesn't contain the proposal hash from the `Voting` storage map."
1278				);
1279				Ok(())
1280			},
1281		)?;
1282
1283		ensure!(
1284			Members::<T, I>::get().len() <= T::MaxMembers::get() as usize,
1285			"The member count is greater than `MaxMembers`."
1286		);
1287
1288		ensure!(
1289			Members::<T, I>::get().windows(2).all(|members| members[0] <= members[1]),
1290			"The members are not sorted by value."
1291		);
1292
1293		if let Some(prime) = Prime::<T, I>::get() {
1294			ensure!(Members::<T, I>::get().contains(&prime), "Prime account is not a member.");
1295		}
1296
1297		Ok(())
1298	}
1299}
1300
1301impl<T: Config<I>, I: 'static> ChangeMembers<T::AccountId> for Pallet<T, I> {
1302	/// Update the members of the collective. Votes are updated and the prime is reset.
1303	///
1304	/// NOTE: Does not enforce the expected `MaxMembers` limit on the amount of members, but
1305	///       the weight estimations rely on it to estimate dispatchable weight.
1306	///
1307	/// ## Complexity
1308	/// - `O(MP + N)`
1309	///   - where `M` old-members-count (governance-bounded)
1310	///   - where `N` new-members-count (governance-bounded)
1311	///   - where `P` proposals-count
1312	fn change_members_sorted(
1313		_incoming: &[T::AccountId],
1314		outgoing: &[T::AccountId],
1315		new: &[T::AccountId],
1316	) {
1317		if new.len() > T::MaxMembers::get() as usize {
1318			log::error!(
1319				target: LOG_TARGET,
1320				"New members count ({}) exceeds maximum amount of members expected ({}).",
1321				new.len(),
1322				T::MaxMembers::get(),
1323			);
1324		}
1325		// remove accounts from all current voting in motions.
1326		let mut outgoing = outgoing.to_vec();
1327		outgoing.sort();
1328		for h in Proposals::<T, I>::get().into_iter() {
1329			<Voting<T, I>>::mutate(h, |v| {
1330				if let Some(mut votes) = v.take() {
1331					votes.ayes = votes
1332						.ayes
1333						.into_iter()
1334						.filter(|i| outgoing.binary_search(i).is_err())
1335						.collect();
1336					votes.nays = votes
1337						.nays
1338						.into_iter()
1339						.filter(|i| outgoing.binary_search(i).is_err())
1340						.collect();
1341					*v = Some(votes);
1342				}
1343			});
1344		}
1345		Members::<T, I>::put(new);
1346		Prime::<T, I>::kill();
1347	}
1348
1349	fn set_prime(prime: Option<T::AccountId>) {
1350		Prime::<T, I>::set(prime);
1351	}
1352
1353	fn get_prime() -> Option<T::AccountId> {
1354		Prime::<T, I>::get()
1355	}
1356}
1357
1358impl<T: Config<I>, I: 'static> InitializeMembers<T::AccountId> for Pallet<T, I> {
1359	fn initialize_members(members: &[T::AccountId]) {
1360		if !members.is_empty() {
1361			assert!(<Members<T, I>>::get().is_empty(), "Members are already initialized!");
1362			let mut members = members.to_vec();
1363			members.sort();
1364			<Members<T, I>>::put(members);
1365		}
1366	}
1367}
1368
1369/// Ensure that the origin `o` represents at least `n` members. Returns `Ok` or an `Err`
1370/// otherwise.
1371pub fn ensure_members<OuterOrigin, AccountId, I>(
1372	o: OuterOrigin,
1373	n: MemberCount,
1374) -> result::Result<MemberCount, &'static str>
1375where
1376	OuterOrigin: Into<result::Result<RawOrigin<AccountId, I>, OuterOrigin>>,
1377{
1378	match o.into() {
1379		Ok(RawOrigin::Members(x, _)) if x >= n => Ok(n),
1380		_ => Err("bad origin: expected to be a threshold number of members"),
1381	}
1382}
1383
1384pub struct EnsureMember<AccountId, I: 'static>(PhantomData<(AccountId, I)>);
1385impl<O: OriginTrait + From<RawOrigin<AccountId, I>>, I, AccountId: Decode + Clone> EnsureOrigin<O>
1386	for EnsureMember<AccountId, I>
1387where
1388	for<'a> &'a O::PalletsOrigin: TryInto<&'a RawOrigin<AccountId, I>>,
1389{
1390	type Success = AccountId;
1391	fn try_origin(o: O) -> Result<Self::Success, O> {
1392		match o.caller().try_into() {
1393			Ok(RawOrigin::Member(id)) => return Ok(id.clone()),
1394			_ => (),
1395		}
1396
1397		Err(o)
1398	}
1399
1400	#[cfg(feature = "runtime-benchmarks")]
1401	fn try_successful_origin() -> Result<O, ()> {
1402		let zero_account_id =
1403			AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes())
1404				.expect("infinite length input; no invalid inputs for type; qed");
1405		Ok(O::from(RawOrigin::Member(zero_account_id)))
1406	}
1407}
1408
1409impl<O: OriginTrait + From<RawOrigin<AccountId, I>>, I, AccountId: Decode + Clone, T>
1410	EnsureOriginWithArg<O, T> for EnsureMember<AccountId, I>
1411where
1412	for<'a> &'a O::PalletsOrigin: TryInto<&'a RawOrigin<AccountId, I>>,
1413{
1414	type Success = <Self as EnsureOrigin<O>>::Success;
1415	fn try_origin(o: O, _: &T) -> Result<Self::Success, O> {
1416		<Self as EnsureOrigin<O>>::try_origin(o)
1417	}
1418	#[cfg(feature = "runtime-benchmarks")]
1419	fn try_successful_origin(_: &T) -> Result<O, ()> {
1420		<Self as EnsureOrigin<O>>::try_successful_origin()
1421	}
1422}
1423
1424pub struct EnsureMembers<AccountId, I: 'static, const N: u32>(PhantomData<(AccountId, I)>);
1425impl<O: OriginTrait + From<RawOrigin<AccountId, I>>, AccountId, I, const N: u32> EnsureOrigin<O>
1426	for EnsureMembers<AccountId, I, N>
1427where
1428	for<'a> &'a O::PalletsOrigin: TryInto<&'a RawOrigin<AccountId, I>>,
1429{
1430	type Success = (MemberCount, MemberCount);
1431	fn try_origin(o: O) -> Result<Self::Success, O> {
1432		match o.caller().try_into() {
1433			Ok(RawOrigin::Members(n, m)) if *n >= N => return Ok((*n, *m)),
1434			_ => (),
1435		}
1436
1437		Err(o)
1438	}
1439
1440	#[cfg(feature = "runtime-benchmarks")]
1441	fn try_successful_origin() -> Result<O, ()> {
1442		Ok(O::from(RawOrigin::Members(N, N)))
1443	}
1444}
1445
1446impl<O: OriginTrait + From<RawOrigin<AccountId, I>>, AccountId, I, const N: u32, T>
1447	EnsureOriginWithArg<O, T> for EnsureMembers<AccountId, I, N>
1448where
1449	for<'a> &'a O::PalletsOrigin: TryInto<&'a RawOrigin<AccountId, I>>,
1450{
1451	type Success = <Self as EnsureOrigin<O>>::Success;
1452	fn try_origin(o: O, _: &T) -> Result<Self::Success, O> {
1453		<Self as EnsureOrigin<O>>::try_origin(o)
1454	}
1455	#[cfg(feature = "runtime-benchmarks")]
1456	fn try_successful_origin(_: &T) -> Result<O, ()> {
1457		<Self as EnsureOrigin<O>>::try_successful_origin()
1458	}
1459}
1460
1461pub struct EnsureProportionMoreThan<AccountId, I: 'static, const N: u32, const D: u32>(
1462	PhantomData<(AccountId, I)>,
1463);
1464impl<O: OriginTrait + From<RawOrigin<AccountId, I>>, AccountId, I, const N: u32, const D: u32>
1465	EnsureOrigin<O> for EnsureProportionMoreThan<AccountId, I, N, D>
1466where
1467	for<'a> &'a O::PalletsOrigin: TryInto<&'a RawOrigin<AccountId, I>>,
1468{
1469	type Success = ();
1470	fn try_origin(o: O) -> Result<Self::Success, O> {
1471		match o.caller().try_into() {
1472			Ok(RawOrigin::Members(n, m)) if n * D > N * m => return Ok(()),
1473			_ => (),
1474		}
1475
1476		Err(o)
1477	}
1478
1479	#[cfg(feature = "runtime-benchmarks")]
1480	fn try_successful_origin() -> Result<O, ()> {
1481		Ok(O::from(RawOrigin::Members(1u32, 0u32)))
1482	}
1483}
1484
1485impl<
1486		O: OriginTrait + From<RawOrigin<AccountId, I>>,
1487		AccountId,
1488		I,
1489		const N: u32,
1490		const D: u32,
1491		T,
1492	> EnsureOriginWithArg<O, T> for EnsureProportionMoreThan<AccountId, I, N, D>
1493where
1494	for<'a> &'a O::PalletsOrigin: TryInto<&'a RawOrigin<AccountId, I>>,
1495{
1496	type Success = <Self as EnsureOrigin<O>>::Success;
1497	fn try_origin(o: O, _: &T) -> Result<Self::Success, O> {
1498		<Self as EnsureOrigin<O>>::try_origin(o)
1499	}
1500	#[cfg(feature = "runtime-benchmarks")]
1501	fn try_successful_origin(_: &T) -> Result<O, ()> {
1502		<Self as EnsureOrigin<O>>::try_successful_origin()
1503	}
1504}
1505
1506pub struct EnsureProportionAtLeast<AccountId, I: 'static, const N: u32, const D: u32>(
1507	PhantomData<(AccountId, I)>,
1508);
1509impl<O: OriginTrait + From<RawOrigin<AccountId, I>>, AccountId, I, const N: u32, const D: u32>
1510	EnsureOrigin<O> for EnsureProportionAtLeast<AccountId, I, N, D>
1511where
1512	for<'a> &'a O::PalletsOrigin: TryInto<&'a RawOrigin<AccountId, I>>,
1513{
1514	type Success = ();
1515	fn try_origin(o: O) -> Result<Self::Success, O> {
1516		match o.caller().try_into() {
1517			Ok(RawOrigin::Members(n, m)) if n * D >= N * m => return Ok(()),
1518			_ => (),
1519		};
1520
1521		Err(o)
1522	}
1523
1524	#[cfg(feature = "runtime-benchmarks")]
1525	fn try_successful_origin() -> Result<O, ()> {
1526		Ok(O::from(RawOrigin::Members(0u32, 0u32)))
1527	}
1528}
1529
1530impl<
1531		O: OriginTrait + From<RawOrigin<AccountId, I>>,
1532		AccountId,
1533		I,
1534		const N: u32,
1535		const D: u32,
1536		T,
1537	> EnsureOriginWithArg<O, T> for EnsureProportionAtLeast<AccountId, I, N, D>
1538where
1539	for<'a> &'a O::PalletsOrigin: TryInto<&'a RawOrigin<AccountId, I>>,
1540{
1541	type Success = <Self as EnsureOrigin<O>>::Success;
1542	fn try_origin(o: O, _: &T) -> Result<Self::Success, O> {
1543		<Self as EnsureOrigin<O>>::try_origin(o)
1544	}
1545	#[cfg(feature = "runtime-benchmarks")]
1546	fn try_successful_origin(_: &T) -> Result<O, ()> {
1547		<Self as EnsureOrigin<O>>::try_successful_origin()
1548	}
1549}