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
//! Ranked collective system.
19
//!
20
//! This is a membership pallet providing a `Tally` implementation ready for use with polling
21
//! systems such as the Referenda pallet. Members each have a rank, with zero being the lowest.
22
//! There is no complexity limitation on either the number of members at a rank or the number of
23
//! ranks in the system thus allowing potentially public membership. A member of at least a given
24
//! rank can be selected at random in O(1) time, allowing for various games to be constructed using
25
//! this as a primitive. Members may only be promoted and demoted by one rank at a time, however
26
//! all operations (save one) are O(1) in complexity. The only operation which is not O(1) is the
27
//! `remove_member` since they must be removed from all ranks from the present down to zero.
28
//!
29
//! Different ranks have different voting power, and are able to vote in different polls. In general
30
//! rank privileges are cumulative. Higher ranks are able to vote in any polls open to lower ranks.
31
//! Similarly, higher ranks always have at least as much voting power in any given poll as lower
32
//! ranks.
33
//!
34
//! Two `Config` trait items control these "rank privileges": `MinRankOfClass` and `VoteWeight`.
35
//! The first controls which ranks are allowed to vote on a particular class of poll. The second
36
//! controls the weight of a vote given the voter's rank compared to the minimum rank of the poll.
37
//!
38
//! An origin control, `EnsureRank`, ensures that the origin is a member of the collective of at
39
//! least a particular rank.
40

            
41
#![cfg_attr(not(feature = "std"), no_std)]
42

            
43
extern crate alloc;
44

            
45
use codec::{Decode, Encode, MaxEncodedLen};
46
use core::marker::PhantomData;
47
use scale_info::TypeInfo;
48
use sp_arithmetic::traits::Saturating;
49
use sp_runtime::{
50
	traits::{Convert, StaticLookup},
51
	ArithmeticError::Overflow,
52
	DispatchError, Perbill, RuntimeDebug,
53
};
54

            
55
use frame_support::{
56
	dispatch::{DispatchResultWithPostInfo, PostDispatchInfo},
57
	ensure, impl_ensure_origin_with_arg_ignoring_arg,
58
	traits::{
59
		EnsureOrigin, EnsureOriginWithArg, PollStatus, Polling, RankedMembers,
60
		RankedMembersSwapHandler, VoteTally,
61
	},
62
	CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
63
};
64

            
65
#[cfg(test)]
66
mod tests;
67

            
68
#[cfg(feature = "runtime-benchmarks")]
69
mod benchmarking;
70
pub mod weights;
71

            
72
pub use pallet::*;
73
pub use weights::WeightInfo;
74

            
75
/// A number of members.
76
pub type MemberIndex = u32;
77

            
78
/// Member rank.
79
pub type Rank = u16;
80

            
81
/// Votes.
82
pub type Votes = u32;
83

            
84
/// Aggregated votes for an ongoing poll by members of the ranked collective.
85
#[derive(
86
	CloneNoBound,
87
	PartialEqNoBound,
88
	EqNoBound,
89
	RuntimeDebugNoBound,
90
	TypeInfo,
91
	Encode,
92
	Decode,
93
	MaxEncodedLen,
94
)]
95
#[scale_info(skip_type_params(T, I, M))]
96
#[codec(mel_bound())]
97
pub struct Tally<T, I, M: GetMaxVoters> {
98
	bare_ayes: MemberIndex,
99
	ayes: Votes,
100
	nays: Votes,
101
	dummy: PhantomData<(T, I, M)>,
102
}
103

            
104
impl<T: Config<I>, I: 'static, M: GetMaxVoters> Tally<T, I, M> {
105
	pub fn from_parts(bare_ayes: MemberIndex, ayes: Votes, nays: Votes) -> Self {
106
		Tally { bare_ayes, ayes, nays, dummy: PhantomData }
107
	}
108
}
109

            
110
// Use (non-rank-weighted) ayes for calculating support.
111
// Allow only promotion/demotion by one rank only.
112
// Allow removal of member with rank zero only.
113
// This keeps everything O(1) while still allowing arbitrary number of ranks.
114

            
115
// All functions of VoteTally now include the class as a param.
116

            
117
pub type TallyOf<T, I = ()> = Tally<T, I, Pallet<T, I>>;
118
pub type PollIndexOf<T, I = ()> = <<T as Config<I>>::Polls as Polling<TallyOf<T, I>>>::Index;
119
pub type ClassOf<T, I = ()> = <<T as Config<I>>::Polls as Polling<TallyOf<T, I>>>::Class;
120
type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
121

            
122
impl<T: Config<I>, I: 'static, M: GetMaxVoters<Class = ClassOf<T, I>>>
123
	VoteTally<Votes, ClassOf<T, I>> for Tally<T, I, M>
124
{
125
	fn new(_: ClassOf<T, I>) -> Self {
126
		Self { bare_ayes: 0, ayes: 0, nays: 0, dummy: PhantomData }
127
	}
128
	fn ayes(&self, _: ClassOf<T, I>) -> Votes {
129
		self.bare_ayes
130
	}
131
	fn support(&self, class: ClassOf<T, I>) -> Perbill {
132
		Perbill::from_rational(self.bare_ayes, M::get_max_voters(class))
133
	}
134
	fn approval(&self, _: ClassOf<T, I>) -> Perbill {
135
		Perbill::from_rational(self.ayes, 1.max(self.ayes + self.nays))
136
	}
137
	#[cfg(feature = "runtime-benchmarks")]
138
	fn unanimity(class: ClassOf<T, I>) -> Self {
139
		Self {
140
			bare_ayes: M::get_max_voters(class.clone()),
141
			ayes: M::get_max_voters(class),
142
			nays: 0,
143
			dummy: PhantomData,
144
		}
145
	}
146
	#[cfg(feature = "runtime-benchmarks")]
147
	fn rejection(class: ClassOf<T, I>) -> Self {
148
		Self { bare_ayes: 0, ayes: 0, nays: M::get_max_voters(class), dummy: PhantomData }
149
	}
150
	#[cfg(feature = "runtime-benchmarks")]
151
	fn from_requirements(support: Perbill, approval: Perbill, class: ClassOf<T, I>) -> Self {
152
		let c = M::get_max_voters(class);
153
		let ayes = support * c;
154
		let nays = ((ayes as u64) * 1_000_000_000u64 / approval.deconstruct() as u64) as u32 - ayes;
155
		Self { bare_ayes: ayes, ayes, nays, dummy: PhantomData }
156
	}
157

            
158
	#[cfg(feature = "runtime-benchmarks")]
159
	fn setup(class: ClassOf<T, I>, granularity: Perbill) {
160
		if M::get_max_voters(class.clone()) == 0 {
161
			let max_voters = granularity.saturating_reciprocal_mul(1u32);
162
			for i in 0..max_voters {
163
				let who: T::AccountId =
164
					frame_benchmarking::account("ranked_collective_benchmarking", i, 0);
165
				crate::Pallet::<T, I>::do_add_member_to_rank(
166
					who,
167
					T::MinRankOfClass::convert(class.clone()),
168
					true,
169
				)
170
				.expect("could not add members for benchmarks");
171
			}
172
			assert_eq!(M::get_max_voters(class), max_voters);
173
		}
174
	}
175
}
176

            
177
/// Record needed for every member.
178
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
179
pub struct MemberRecord {
180
	/// The rank of the member.
181
	rank: Rank,
182
}
183

            
184
impl MemberRecord {
185
	// Constructs a new instance of [`MemberRecord`].
186
	pub fn new(rank: Rank) -> Self {
187
		Self { rank }
188
	}
189
}
190

            
191
/// Record needed for every vote.
192
#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
193
pub enum VoteRecord {
194
	/// Vote was an aye with given vote weight.
195
	Aye(Votes),
196
	/// Vote was a nay with given vote weight.
197
	Nay(Votes),
198
}
199

            
200
impl From<(bool, Votes)> for VoteRecord {
201
	fn from((aye, votes): (bool, Votes)) -> Self {
202
		match aye {
203
			true => VoteRecord::Aye(votes),
204
			false => VoteRecord::Nay(votes),
205
		}
206
	}
207
}
208

            
209
/// Vote-weight scheme where all voters get one vote regardless of rank.
210
pub struct Unit;
211
impl Convert<Rank, Votes> for Unit {
212
	fn convert(_: Rank) -> Votes {
213
		1
214
	}
215
}
216

            
217
/// Vote-weight scheme where all voters get one vote plus an additional vote for every excess rank
218
/// they have. I.e.:
219
///
220
/// - Each member with an excess rank of 0 gets 1 vote;
221
/// - ...with an excess rank of 1 gets 2 votes;
222
/// - ...with an excess rank of 2 gets 3 votes;
223
/// - ...with an excess rank of 3 gets 4 votes;
224
/// - ...with an excess rank of 4 gets 5 votes.
225
pub struct Linear;
226
impl Convert<Rank, Votes> for Linear {
227
	fn convert(r: Rank) -> Votes {
228
		(r + 1) as Votes
229
	}
230
}
231

            
232
/// Vote-weight scheme where all voters get one vote plus additional votes for every excess rank
233
/// they have incrementing by one vote for each excess rank. I.e.:
234
///
235
/// - Each member with an excess rank of 0 gets 1 vote;
236
/// - ...with an excess rank of 1 gets 3 votes;
237
/// - ...with an excess rank of 2 gets 6 votes;
238
/// - ...with an excess rank of 3 gets 10 votes;
239
/// - ...with an excess rank of 4 gets 15 votes.
240
pub struct Geometric;
241
impl Convert<Rank, Votes> for Geometric {
242
	fn convert(r: Rank) -> Votes {
243
		let v = (r + 1) as Votes;
244
		v * (v + 1) / 2
245
	}
246
}
247

            
248
/// Trait for getting the maximum number of voters for a given poll class.
249
pub trait GetMaxVoters {
250
	/// Poll class type.
251
	type Class;
252
	/// Return the maximum number of voters for the poll class `c`.
253
	fn get_max_voters(c: Self::Class) -> MemberIndex;
254
}
255
impl<T: Config<I>, I: 'static> GetMaxVoters for Pallet<T, I> {
256
	type Class = ClassOf<T, I>;
257
	fn get_max_voters(c: Self::Class) -> MemberIndex {
258
		MemberCount::<T, I>::get(T::MinRankOfClass::convert(c))
259
	}
260
}
261

            
262
/// Guard to ensure that the given origin is a member of the collective. The rank of the member is
263
/// the `Success` value.
264
pub struct EnsureRanked<T, I, const MIN_RANK: u16>(PhantomData<(T, I)>);
265
impl<T: Config<I>, I: 'static, const MIN_RANK: u16> EnsureOrigin<T::RuntimeOrigin>
266
	for EnsureRanked<T, I, MIN_RANK>
267
{
268
	type Success = Rank;
269

            
270
	fn try_origin(o: T::RuntimeOrigin) -> Result<Self::Success, T::RuntimeOrigin> {
271
		let who = <frame_system::EnsureSigned<_> as EnsureOrigin<_>>::try_origin(o)?;
272
		match Members::<T, I>::get(&who) {
273
			Some(MemberRecord { rank, .. }) if rank >= MIN_RANK => Ok(rank),
274
			_ => Err(frame_system::RawOrigin::Signed(who).into()),
275
		}
276
	}
277

            
278
	#[cfg(feature = "runtime-benchmarks")]
279
	fn try_successful_origin() -> Result<T::RuntimeOrigin, ()> {
280
		<EnsureRankedMember<T, I, MIN_RANK> as EnsureOrigin<_>>::try_successful_origin()
281
	}
282
}
283

            
284
impl_ensure_origin_with_arg_ignoring_arg! {
285
	impl<{ T: Config<I>, I: 'static, const MIN_RANK: u16, A }>
286
		EnsureOriginWithArg<T::RuntimeOrigin, A> for EnsureRanked<T, I, MIN_RANK>
287
	{}
288
}
289

            
290
/// Guard to ensure that the given origin is a member of the collective. The rank of the member is
291
/// the `Success` value.
292
pub struct EnsureOfRank<T, I>(PhantomData<(T, I)>);
293
impl<T: Config<I>, I: 'static> EnsureOriginWithArg<T::RuntimeOrigin, Rank> for EnsureOfRank<T, I> {
294
	type Success = (T::AccountId, Rank);
295

            
296
	fn try_origin(o: T::RuntimeOrigin, min_rank: &Rank) -> Result<Self::Success, T::RuntimeOrigin> {
297
		let who = <frame_system::EnsureSigned<_> as EnsureOrigin<_>>::try_origin(o)?;
298
		match Members::<T, I>::get(&who) {
299
			Some(MemberRecord { rank, .. }) if rank >= *min_rank => Ok((who, rank)),
300
			_ => Err(frame_system::RawOrigin::Signed(who).into()),
301
		}
302
	}
303

            
304
	#[cfg(feature = "runtime-benchmarks")]
305
	fn try_successful_origin(min_rank: &Rank) -> Result<T::RuntimeOrigin, ()> {
306
		let who = frame_benchmarking::account::<T::AccountId>("successful_origin", 0, 0);
307
		crate::Pallet::<T, I>::do_add_member_to_rank(who.clone(), *min_rank, true)
308
			.expect("Could not add members for benchmarks");
309
		Ok(frame_system::RawOrigin::Signed(who).into())
310
	}
311
}
312

            
313
/// Guard to ensure that the given origin is a member of the collective. The account ID of the
314
/// member is the `Success` value.
315
pub struct EnsureMember<T, I, const MIN_RANK: u16>(PhantomData<(T, I)>);
316
impl<T: Config<I>, I: 'static, const MIN_RANK: u16> EnsureOrigin<T::RuntimeOrigin>
317
	for EnsureMember<T, I, MIN_RANK>
318
{
319
	type Success = T::AccountId;
320

            
321
423
	fn try_origin(o: T::RuntimeOrigin) -> Result<Self::Success, T::RuntimeOrigin> {
322
423
		let who = <frame_system::EnsureSigned<_> as EnsureOrigin<_>>::try_origin(o)?;
323
423
		match Members::<T, I>::get(&who) {
324
			Some(MemberRecord { rank, .. }) if rank >= MIN_RANK => Ok(who),
325
423
			_ => Err(frame_system::RawOrigin::Signed(who).into()),
326
		}
327
423
	}
328

            
329
	#[cfg(feature = "runtime-benchmarks")]
330
	fn try_successful_origin() -> Result<T::RuntimeOrigin, ()> {
331
		<EnsureRankedMember<T, I, MIN_RANK> as EnsureOrigin<_>>::try_successful_origin()
332
	}
333
}
334

            
335
impl_ensure_origin_with_arg_ignoring_arg! {
336
	impl<{ T: Config<I>, I: 'static, const MIN_RANK: u16, A }>
337
		EnsureOriginWithArg<T::RuntimeOrigin, A> for EnsureMember<T, I, MIN_RANK>
338
	{}
339
}
340

            
341
/// Guard to ensure that the given origin is a member of the collective. The pair of both the
342
/// account ID and the rank of the member is the `Success` value.
343
pub struct EnsureRankedMember<T, I, const MIN_RANK: u16>(PhantomData<(T, I)>);
344
impl<T: Config<I>, I: 'static, const MIN_RANK: u16> EnsureOrigin<T::RuntimeOrigin>
345
	for EnsureRankedMember<T, I, MIN_RANK>
346
{
347
	type Success = (T::AccountId, Rank);
348

            
349
	fn try_origin(o: T::RuntimeOrigin) -> Result<Self::Success, T::RuntimeOrigin> {
350
		let who = <frame_system::EnsureSigned<_> as EnsureOrigin<_>>::try_origin(o)?;
351
		match Members::<T, I>::get(&who) {
352
			Some(MemberRecord { rank, .. }) if rank >= MIN_RANK => Ok((who, rank)),
353
			_ => Err(frame_system::RawOrigin::Signed(who).into()),
354
		}
355
	}
356

            
357
	#[cfg(feature = "runtime-benchmarks")]
358
	fn try_successful_origin() -> Result<T::RuntimeOrigin, ()> {
359
		let who = frame_benchmarking::account::<T::AccountId>("successful_origin", 0, 0);
360
		crate::Pallet::<T, I>::do_add_member_to_rank(who.clone(), MIN_RANK, true)
361
			.expect("Could not add members for benchmarks");
362
		Ok(frame_system::RawOrigin::Signed(who).into())
363
	}
364
}
365

            
366
impl_ensure_origin_with_arg_ignoring_arg! {
367
	impl<{ T: Config<I>, I: 'static, const MIN_RANK: u16, A }>
368
		EnsureOriginWithArg<T::RuntimeOrigin, A> for EnsureRankedMember<T, I, MIN_RANK>
369
	{}
370
}
371

            
372
/// Helper functions to setup benchmarking.
373
#[impl_trait_for_tuples::impl_for_tuples(8)]
374
pub trait BenchmarkSetup<AccountId> {
375
	/// Ensure that this member is registered correctly.
376
	fn ensure_member(acc: &AccountId);
377
}
378

            
379
1980
#[frame_support::pallet]
380
pub mod pallet {
381
	use super::*;
382
	use frame_support::{pallet_prelude::*, storage::KeyLenOf};
383
	use frame_system::pallet_prelude::*;
384
	use sp_runtime::traits::MaybeConvert;
385

            
386
1698
	#[pallet::pallet]
387
	pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
388

            
389
	#[pallet::config]
390
	pub trait Config<I: 'static = ()>: frame_system::Config {
391
		/// Weight information for extrinsics in this pallet.
392
		type WeightInfo: WeightInfo;
393

            
394
		/// The runtime event type.
395
		type RuntimeEvent: From<Event<Self, I>>
396
			+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
397

            
398
		/// The origin required to add a member.
399
		type AddOrigin: EnsureOrigin<Self::RuntimeOrigin>;
400

            
401
		/// The origin required to remove a member.
402
		///
403
		/// The success value indicates the maximum rank *from which* the removal may be.
404
		type RemoveOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Rank>;
405

            
406
		/// The origin required to promote a member. The success value indicates the
407
		/// maximum rank *to which* the promotion may be.
408
		type PromoteOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Rank>;
409

            
410
		/// The origin required to demote a member. The success value indicates the
411
		/// maximum rank *from which* the demotion may be.
412
		type DemoteOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Rank>;
413

            
414
		/// The origin that can swap the account of a member.
415
		type ExchangeOrigin: EnsureOrigin<Self::RuntimeOrigin>;
416

            
417
		/// The polling system used for our voting.
418
		type Polls: Polling<TallyOf<Self, I>, Votes = Votes, Moment = BlockNumberFor<Self>>;
419

            
420
		/// Convert the tally class into the minimum rank required to vote on the poll. If
421
		/// `Polls::Class` is the same type as `Rank`, then `Identity` can be used here to mean
422
		/// "a rank of at least the poll class".
423
		type MinRankOfClass: Convert<ClassOf<Self, I>, Rank>;
424

            
425
		/// An external handler that will be notified when two members are swapped.
426
		type MemberSwappedHandler: RankedMembersSwapHandler<
427
			<Pallet<Self, I> as RankedMembers>::AccountId,
428
			<Pallet<Self, I> as RankedMembers>::Rank,
429
		>;
430

            
431
		/// Convert a rank_delta into a number of votes the rank gets.
432
		///
433
		/// Rank_delta is defined as the number of ranks above the minimum required to take part
434
		/// in the poll.
435
		type VoteWeight: Convert<Rank, Votes>;
436

            
437
		/// The maximum number of members for a given rank in the collective.
438
		///
439
		/// The member at rank `x` contributes to the count at rank `x` and all ranks below it.
440
		/// Therefore, the limit `m` at rank `x` sets the maximum total member count for rank `x`
441
		/// and all ranks above.
442
		/// The `None` indicates no member count limit for the given rank.
443
		type MaxMemberCount: MaybeConvert<Rank, MemberIndex>;
444

            
445
		/// Setup a member for benchmarking.
446
		#[cfg(feature = "runtime-benchmarks")]
447
		type BenchmarkSetup: BenchmarkSetup<Self::AccountId>;
448
	}
449

            
450
	/// The number of members in the collective who have at least the rank according to the index
451
	/// of the vec.
452
160911
	#[pallet::storage]
453
	pub type MemberCount<T: Config<I>, I: 'static = ()> =
454
		StorageMap<_, Twox64Concat, Rank, MemberIndex, ValueQuery>;
455

            
456
	/// The current members of the collective.
457
215004
	#[pallet::storage]
458
	pub type Members<T: Config<I>, I: 'static = ()> =
459
		StorageMap<_, Twox64Concat, T::AccountId, MemberRecord>;
460

            
461
	/// The index of each ranks's member into the group of members who have at least that rank.
462
107274
	#[pallet::storage]
463
	pub type IdToIndex<T: Config<I>, I: 'static = ()> =
464
		StorageDoubleMap<_, Twox64Concat, Rank, Twox64Concat, T::AccountId, MemberIndex>;
465

            
466
	/// The members in the collective by index. All indices in the range `0..MemberCount` will
467
	/// return `Some`, however a member's index is not guaranteed to remain unchanged over time.
468
	#[pallet::storage]
469
	pub type IndexToId<T: Config<I>, I: 'static = ()> =
470
		StorageDoubleMap<_, Twox64Concat, Rank, Twox64Concat, MemberIndex, T::AccountId>;
471

            
472
	/// Votes on a given proposal, if it is ongoing.
473
72
	#[pallet::storage]
474
	pub type Voting<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
475
		_,
476
		Blake2_128Concat,
477
		PollIndexOf<T, I>,
478
		Twox64Concat,
479
		T::AccountId,
480
		VoteRecord,
481
	>;
482

            
483
72
	#[pallet::storage]
484
	pub type VotingCleanup<T: Config<I>, I: 'static = ()> =
485
		StorageMap<_, Blake2_128Concat, PollIndexOf<T, I>, BoundedVec<u8, KeyLenOf<Voting<T, I>>>>;
486

            
487
	#[pallet::event]
488
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
489
	pub enum Event<T: Config<I>, I: 'static = ()> {
490
		/// A member `who` has been added.
491
		MemberAdded { who: T::AccountId },
492
		/// The member `who`se rank has been changed to the given `rank`.
493
		RankChanged { who: T::AccountId, rank: Rank },
494
		/// The member `who` of given `rank` has been removed from the collective.
495
		MemberRemoved { who: T::AccountId, rank: Rank },
496
		/// The member `who` has voted for the `poll` with the given `vote` leading to an updated
497
		/// `tally`.
498
		Voted { who: T::AccountId, poll: PollIndexOf<T, I>, vote: VoteRecord, tally: TallyOf<T, I> },
499
		/// The member `who` had their `AccountId` changed to `new_who`.
500
		MemberExchanged { who: T::AccountId, new_who: T::AccountId },
501
	}
502

            
503
66
	#[pallet::error]
504
	pub enum Error<T, I = ()> {
505
		/// Account is already a member.
506
		AlreadyMember,
507
		/// Account is not a member.
508
		NotMember,
509
		/// The given poll index is unknown or has closed.
510
		NotPolling,
511
		/// The given poll is still ongoing.
512
		Ongoing,
513
		/// There are no further records to be removed.
514
		NoneRemaining,
515
		/// Unexpected error in state.
516
		Corruption,
517
		/// The member's rank is too low to vote.
518
		RankTooLow,
519
		/// The information provided is incorrect.
520
		InvalidWitness,
521
		/// The origin is not sufficiently privileged to do the operation.
522
		NoPermission,
523
		/// The new member to exchange is the same as the old member
524
		SameMember,
525
		/// The max member count for the rank has been reached.
526
		TooManyMembers,
527
	}
528

            
529
13212
	#[pallet::call]
530
	impl<T: Config<I>, I: 'static> Pallet<T, I> {
531
		/// Introduce a new member.
532
		///
533
		/// - `origin`: Must be the `AddOrigin`.
534
		/// - `who`: Account of non-member which will become a member.
535
		///
536
		/// Weight: `O(1)`
537
		#[pallet::call_index(0)]
538
		#[pallet::weight(T::WeightInfo::add_member())]
539
771
		pub fn add_member(origin: OriginFor<T>, who: AccountIdLookupOf<T>) -> DispatchResult {
540
771
			T::AddOrigin::ensure_origin(origin)?;
541
			let who = T::Lookup::lookup(who)?;
542
			Self::do_add_member(who, true)
543
		}
544

            
545
		/// Increment the rank of an existing member by one.
546
		///
547
		/// - `origin`: Must be the `PromoteOrigin`.
548
		/// - `who`: Account of existing member.
549
		///
550
		/// Weight: `O(1)`
551
		#[pallet::call_index(1)]
552
		#[pallet::weight(T::WeightInfo::promote_member(0))]
553
87
		pub fn promote_member(origin: OriginFor<T>, who: AccountIdLookupOf<T>) -> DispatchResult {
554
87
			let max_rank = T::PromoteOrigin::ensure_origin(origin)?;
555
			let who = T::Lookup::lookup(who)?;
556
			Self::do_promote_member(who, Some(max_rank), true)
557
		}
558

            
559
		/// Decrement the rank of an existing member by one. If the member is already at rank zero,
560
		/// then they are removed entirely.
561
		///
562
		/// - `origin`: Must be the `DemoteOrigin`.
563
		/// - `who`: Account of existing member of rank greater than zero.
564
		///
565
		/// Weight: `O(1)`, less if the member's index is highest in its rank.
566
		#[pallet::call_index(2)]
567
		#[pallet::weight(T::WeightInfo::demote_member(0))]
568
801
		pub fn demote_member(origin: OriginFor<T>, who: AccountIdLookupOf<T>) -> DispatchResult {
569
801
			let max_rank = T::DemoteOrigin::ensure_origin(origin)?;
570
			let who = T::Lookup::lookup(who)?;
571
			Self::do_demote_member(who, Some(max_rank))
572
		}
573

            
574
		/// Remove the member entirely.
575
		///
576
		/// - `origin`: Must be the `RemoveOrigin`.
577
		/// - `who`: Account of existing member of rank greater than zero.
578
		/// - `min_rank`: The rank of the member or greater.
579
		///
580
		/// Weight: `O(min_rank)`.
581
		#[pallet::call_index(3)]
582
		#[pallet::weight(T::WeightInfo::remove_member(*min_rank as u32))]
583
		pub fn remove_member(
584
			origin: OriginFor<T>,
585
			who: AccountIdLookupOf<T>,
586
			min_rank: Rank,
587
90
		) -> DispatchResultWithPostInfo {
588
90
			let max_rank = T::RemoveOrigin::ensure_origin(origin)?;
589
			let who = T::Lookup::lookup(who)?;
590
			let MemberRecord { rank, .. } = Self::ensure_member(&who)?;
591
			ensure!(min_rank >= rank, Error::<T, I>::InvalidWitness);
592
			ensure!(max_rank >= rank, Error::<T, I>::NoPermission);
593

            
594
			Self::do_remove_member_from_rank(&who, rank)?;
595
			Self::deposit_event(Event::MemberRemoved { who, rank });
596
			Ok(PostDispatchInfo {
597
				actual_weight: Some(T::WeightInfo::remove_member(rank as u32)),
598
				pays_fee: Pays::Yes,
599
			})
600
		}
601

            
602
		/// Add an aye or nay vote for the sender to the given proposal.
603
		///
604
		/// - `origin`: Must be `Signed` by a member account.
605
		/// - `poll`: Index of a poll which is ongoing.
606
		/// - `aye`: `true` if the vote is to approve the proposal, `false` otherwise.
607
		///
608
		/// Transaction fees are be waived if the member is voting on any particular proposal
609
		/// for the first time and the call is successful. Subsequent vote changes will charge a
610
		/// fee.
611
		///
612
		/// Weight: `O(1)`, less if there was no previous vote on the poll by the member.
613
		#[pallet::call_index(4)]
614
		#[pallet::weight(T::WeightInfo::vote())]
615
		pub fn vote(
616
			origin: OriginFor<T>,
617
			poll: PollIndexOf<T, I>,
618
			aye: bool,
619
33
		) -> DispatchResultWithPostInfo {
620
33
			let who = ensure_signed(origin)?;
621
33
			let record = Self::ensure_member(&who)?;
622
			use VoteRecord::*;
623
			let mut pays = Pays::Yes;
624

            
625
			let (tally, vote) = T::Polls::try_access_poll(
626
				poll,
627
				|mut status| -> Result<(TallyOf<T, I>, VoteRecord), DispatchError> {
628
					match status {
629
						PollStatus::None | PollStatus::Completed(..) =>
630
							Err(Error::<T, I>::NotPolling)?,
631
						PollStatus::Ongoing(ref mut tally, class) => {
632
							match Voting::<T, I>::get(&poll, &who) {
633
								Some(Aye(votes)) => {
634
									tally.bare_ayes.saturating_dec();
635
									tally.ayes.saturating_reduce(votes);
636
								},
637
								Some(Nay(votes)) => tally.nays.saturating_reduce(votes),
638
								None => pays = Pays::No,
639
							}
640
							let min_rank = T::MinRankOfClass::convert(class);
641
							let votes = Self::rank_to_votes(record.rank, min_rank)?;
642
							let vote = VoteRecord::from((aye, votes));
643
							match aye {
644
								true => {
645
									tally.bare_ayes.saturating_inc();
646
									tally.ayes.saturating_accrue(votes);
647
								},
648
								false => tally.nays.saturating_accrue(votes),
649
							}
650
							Voting::<T, I>::insert(&poll, &who, &vote);
651
							Ok((tally.clone(), vote))
652
						},
653
					}
654
				},
655
			)?;
656
			Self::deposit_event(Event::Voted { who, poll, vote, tally });
657
			Ok(pays.into())
658
		}
659

            
660
		/// Remove votes from the given poll. It must have ended.
661
		///
662
		/// - `origin`: Must be `Signed` by any account.
663
		/// - `poll_index`: Index of a poll which is completed and for which votes continue to
664
		///   exist.
665
		/// - `max`: Maximum number of vote items from remove in this call.
666
		///
667
		/// Transaction fees are waived if the operation is successful.
668
		///
669
		/// Weight `O(max)` (less if there are fewer items to remove than `max`).
670
		#[pallet::call_index(5)]
671
		#[pallet::weight(T::WeightInfo::cleanup_poll(*max))]
672
		pub fn cleanup_poll(
673
			origin: OriginFor<T>,
674
			poll_index: PollIndexOf<T, I>,
675
			max: u32,
676
72
		) -> DispatchResultWithPostInfo {
677
72
			ensure_signed(origin)?;
678
72
			ensure!(T::Polls::as_ongoing(poll_index).is_none(), Error::<T, I>::Ongoing);
679

            
680
72
			let r = Voting::<T, I>::clear_prefix(
681
72
				poll_index,
682
72
				max,
683
72
				VotingCleanup::<T, I>::take(poll_index).as_ref().map(|c| &c[..]),
684
72
			);
685
72
			if r.unique == 0 {
686
				// return Err(Error::<T, I>::NoneRemaining)
687
72
				return Ok(Pays::Yes.into())
688
			}
689
			if let Some(cursor) = r.maybe_cursor {
690
				VotingCleanup::<T, I>::insert(poll_index, BoundedVec::truncate_from(cursor));
691
			}
692
			Ok(PostDispatchInfo {
693
				actual_weight: Some(T::WeightInfo::cleanup_poll(r.unique)),
694
				pays_fee: Pays::No,
695
			})
696
		}
697

            
698
		/// Exchanges a member with a new account and the same existing rank.
699
		///
700
		/// - `origin`: Must be the `ExchangeOrigin`.
701
		/// - `who`: Account of existing member of rank greater than zero to be exchanged.
702
		/// - `new_who`: New Account of existing member of rank greater than zero to exchanged to.
703
		#[pallet::call_index(6)]
704
		#[pallet::weight(T::WeightInfo::exchange_member())]
705
		pub fn exchange_member(
706
			origin: OriginFor<T>,
707
			who: AccountIdLookupOf<T>,
708
			new_who: AccountIdLookupOf<T>,
709
117
		) -> DispatchResult {
710
117
			T::ExchangeOrigin::ensure_origin(origin)?;
711
			let who = T::Lookup::lookup(who)?;
712
			let new_who = T::Lookup::lookup(new_who)?;
713

            
714
			ensure!(who != new_who, Error::<T, I>::SameMember);
715

            
716
			let MemberRecord { rank, .. } = Self::ensure_member(&who)?;
717

            
718
			Self::do_remove_member_from_rank(&who, rank)?;
719
			Self::do_add_member_to_rank(new_who.clone(), rank, false)?;
720

            
721
			Self::deposit_event(Event::MemberExchanged {
722
				who: who.clone(),
723
				new_who: new_who.clone(),
724
			});
725
			T::MemberSwappedHandler::swapped(&who, &new_who, rank);
726

            
727
			Ok(())
728
		}
729
	}
730

            
731
554367
	#[pallet::hooks]
732
	impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
733
		#[cfg(feature = "try-runtime")]
734
53637
		fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
735
53637
			Self::do_try_state()
736
53637
		}
737
	}
738

            
739
	impl<T: Config<I>, I: 'static> Pallet<T, I> {
740
33
		fn ensure_member(who: &T::AccountId) -> Result<MemberRecord, DispatchError> {
741
33
			Members::<T, I>::get(who).ok_or(Error::<T, I>::NotMember.into())
742
33
		}
743

            
744
		fn rank_to_votes(rank: Rank, min: Rank) -> Result<Votes, DispatchError> {
745
			let excess = rank.checked_sub(min).ok_or(Error::<T, I>::RankTooLow)?;
746
			Ok(T::VoteWeight::convert(excess))
747
		}
748

            
749
		fn remove_from_rank(who: &T::AccountId, rank: Rank) -> DispatchResult {
750
			MemberCount::<T, I>::try_mutate(rank, |last_index| {
751
				last_index.saturating_dec();
752
				let index = IdToIndex::<T, I>::get(rank, &who).ok_or(Error::<T, I>::Corruption)?;
753
				if index != *last_index {
754
					let last = IndexToId::<T, I>::get(rank, *last_index)
755
						.ok_or(Error::<T, I>::Corruption)?;
756
					IdToIndex::<T, I>::insert(rank, &last, index);
757
					IndexToId::<T, I>::insert(rank, index, &last);
758
				}
759

            
760
				IdToIndex::<T, I>::remove(rank, who);
761
				IndexToId::<T, I>::remove(rank, last_index);
762

            
763
				Ok(())
764
			})
765
		}
766

            
767
		/// Adds a member into the ranked collective at level 0.
768
		///
769
		/// No origin checks are executed.
770
		pub fn do_add_member(who: T::AccountId, emit_event: bool) -> DispatchResult {
771
			ensure!(!Members::<T, I>::contains_key(&who), Error::<T, I>::AlreadyMember);
772
			let index = MemberCount::<T, I>::get(0);
773
			let count = index.checked_add(1).ok_or(Overflow)?;
774
			if let Some(max) = T::MaxMemberCount::maybe_convert(0) {
775
				ensure!(count <= max, Error::<T, I>::TooManyMembers);
776
			}
777

            
778
			Members::<T, I>::insert(&who, MemberRecord { rank: 0 });
779
			IdToIndex::<T, I>::insert(0, &who, index);
780
			IndexToId::<T, I>::insert(0, index, &who);
781
			MemberCount::<T, I>::insert(0, count);
782
			if emit_event {
783
				Self::deposit_event(Event::MemberAdded { who });
784
			}
785
			Ok(())
786
		}
787

            
788
		/// Promotes a member in the ranked collective into the next higher rank.
789
		///
790
		/// A `maybe_max_rank` may be provided to check that the member does not get promoted beyond
791
		/// a certain rank. Is `None` is provided, then the rank will be incremented without checks.
792
		pub fn do_promote_member(
793
			who: T::AccountId,
794
			maybe_max_rank: Option<Rank>,
795
			emit_event: bool,
796
		) -> DispatchResult {
797
			let record = Self::ensure_member(&who)?;
798
			let rank = record.rank.checked_add(1).ok_or(Overflow)?;
799
			if let Some(max_rank) = maybe_max_rank {
800
				ensure!(max_rank >= rank, Error::<T, I>::NoPermission);
801
			}
802
			let index = MemberCount::<T, I>::get(rank);
803
			let count = index.checked_add(1).ok_or(Overflow)?;
804
			if let Some(max) = T::MaxMemberCount::maybe_convert(rank) {
805
				ensure!(count <= max, Error::<T, I>::TooManyMembers);
806
			}
807

            
808
			MemberCount::<T, I>::insert(rank, index.checked_add(1).ok_or(Overflow)?);
809
			IdToIndex::<T, I>::insert(rank, &who, index);
810
			IndexToId::<T, I>::insert(rank, index, &who);
811
			Members::<T, I>::insert(&who, MemberRecord { rank });
812
			if emit_event {
813
				Self::deposit_event(Event::RankChanged { who, rank });
814
			}
815
			Ok(())
816
		}
817

            
818
		/// Demotes a member in the ranked collective into the next lower rank.
819
		///
820
		/// A `maybe_max_rank` may be provided to check that the member does not get demoted from
821
		/// a certain rank. Is `None` is provided, then the rank will be decremented without checks.
822
		fn do_demote_member(who: T::AccountId, maybe_max_rank: Option<Rank>) -> DispatchResult {
823
			let mut record = Self::ensure_member(&who)?;
824
			let rank = record.rank;
825
			if let Some(max_rank) = maybe_max_rank {
826
				ensure!(max_rank >= rank, Error::<T, I>::NoPermission);
827
			}
828

            
829
			Self::remove_from_rank(&who, rank)?;
830
			let maybe_rank = rank.checked_sub(1);
831
			match maybe_rank {
832
				None => {
833
					Members::<T, I>::remove(&who);
834
					Self::deposit_event(Event::MemberRemoved { who, rank: 0 });
835
				},
836
				Some(rank) => {
837
					record.rank = rank;
838
					Members::<T, I>::insert(&who, &record);
839
					Self::deposit_event(Event::RankChanged { who, rank });
840
				},
841
			}
842
			Ok(())
843
		}
844

            
845
		/// Add a member to the rank collective, and continue to promote them until a certain rank
846
		/// is reached.
847
		pub fn do_add_member_to_rank(
848
			who: T::AccountId,
849
			rank: Rank,
850
			emit_event: bool,
851
		) -> DispatchResult {
852
			Self::do_add_member(who.clone(), emit_event)?;
853
			for _ in 0..rank {
854
				Self::do_promote_member(who.clone(), None, emit_event)?;
855
			}
856
			Ok(())
857
		}
858

            
859
		/// Determine the rank of the account behind the `Signed` origin `o`, `None` if the account
860
		/// is unknown to this collective or `o` is not `Signed`.
861
		pub fn as_rank(
862
			o: &<T::RuntimeOrigin as frame_support::traits::OriginTrait>::PalletsOrigin,
863
		) -> Option<u16> {
864
			use frame_support::traits::CallerTrait;
865
			o.as_signed().and_then(Self::rank_of)
866
		}
867

            
868
		/// Removes a member from the rank collective
869
		pub fn do_remove_member_from_rank(who: &T::AccountId, rank: Rank) -> DispatchResult {
870
			for r in 0..=rank {
871
				Self::remove_from_rank(&who, r)?;
872
			}
873
			Members::<T, I>::remove(&who);
874
			Ok(())
875
		}
876
	}
877

            
878
	#[cfg(any(feature = "try-runtime", test))]
879
	impl<T: Config<I>, I: 'static> Pallet<T, I> {
880
		/// Ensure the correctness of the state of this pallet.
881
53637
		pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
882
53637
			Self::try_state_members()?;
883
53637
			Self::try_state_index()?;
884

            
885
53637
			Ok(())
886
53637
		}
887

            
888
		/// ### Invariants of Member storage items
889
		///
890
		/// Total number of [`Members`] in storage should be >= [`MemberIndex`] of a [`Rank`] in
891
		///    [`MemberCount`].
892
		/// [`Rank`] in Members should be in [`MemberCount`]
893
		/// [`Sum`] of [`MemberCount`] index should be the same as the sum of all the index attained
894
		/// for rank possessed by [`Members`]
895
53637
		fn try_state_members() -> Result<(), sp_runtime::TryRuntimeError> {
896
53637
			MemberCount::<T, I>::iter().try_for_each(|(_, member_index)| -> DispatchResult {
897
				let total_members = Members::<T, I>::iter().count();
898
				ensure!(
899
				total_members as u32 >= member_index,
900
				"Total count of `Members` should be greater than or equal to the number of `MemberIndex` of a particular `Rank` in `MemberCount`."
901
				);
902

            
903
				Ok(())
904
53637
			})?;
905

            
906
53637
			let mut sum_of_member_rank_indexes = 0;
907
53637
			Members::<T, I>::iter().try_for_each(|(_, member_record)| -> DispatchResult {
908
				ensure!(
909
					Self::is_rank_in_member_count(member_record.rank.into()),
910
					"`Rank` in Members should be in `MemberCount`"
911
				);
912

            
913
				sum_of_member_rank_indexes += Self::determine_index_of_a_rank(member_record.rank);
914

            
915
				Ok(())
916
53637
			})?;
917

            
918
53637
			let sum_of_all_member_count_indexes =
919
53637
				MemberCount::<T, I>::iter_values().fold(0, |sum, index| sum + index);
920
53637
			ensure!(
921
53637
					sum_of_all_member_count_indexes == sum_of_member_rank_indexes as u32,
922
					"Sum of `MemberCount` index should be the same as the sum of all the index attained for rank possessed by `Members`"
923
				);
924
53637
			Ok(())
925
53637
		}
926

            
927
		/// ### Invariants of Index storage items
928
		/// [`Member`] in storage of [`IdToIndex`] should be the same as [`Member`] in [`IndexToId`]
929
		/// [`Rank`] in [`IdToIndex`] should be the same as the the [`Rank`] in  [`IndexToId`]
930
		/// [`Rank`] of the member [`who`] in [`IdToIndex`] should be the same as the [`Rank`] of
931
		/// the member [`who`] in [`Members`]
932
53637
		fn try_state_index() -> Result<(), sp_runtime::TryRuntimeError> {
933
53637
			IdToIndex::<T, I>::iter().try_for_each(
934
53637
				|(rank, who, member_index)| -> DispatchResult {
935
					let who_from_index = IndexToId::<T, I>::get(rank, member_index).unwrap();
936
					ensure!(
937
				who == who_from_index,
938
				"`Member` in storage of `IdToIndex` should be the same as `Member` in `IndexToId`."
939
				);
940

            
941
					ensure!(
942
						Self::is_rank_in_index_to_id_storage(rank.into()),
943
						"`Rank` in `IdToIndex` should be the same as the `Rank` in `IndexToId`"
944
					);
945
					Ok(())
946
53637
				},
947
53637
			)?;
948

            
949
53637
			Members::<T, I>::iter().try_for_each(|(who, member_record)| -> DispatchResult {
950
				ensure!(
951
						Self::is_who_rank_in_id_to_index_storage(who, member_record.rank),
952
						"`Rank` of the member `who` in `IdToIndex` should be the same as the `Rank` of the member `who` in `Members`"
953
					);
954

            
955
				Ok(())
956
53637
			})?;
957

            
958
53637
			Ok(())
959
53637
		}
960

            
961
		/// Checks if a rank is part of the `MemberCount`
962
		fn is_rank_in_member_count(rank: u32) -> bool {
963
			for (r, _) in MemberCount::<T, I>::iter() {
964
				if r as u32 == rank {
965
					return true;
966
				}
967
			}
968

            
969
			return false;
970
		}
971

            
972
		/// Checks if a rank is the same as the rank `IndexToId`
973
		fn is_rank_in_index_to_id_storage(rank: u32) -> bool {
974
			for (r, _, _) in IndexToId::<T, I>::iter() {
975
				if r as u32 == rank {
976
					return true;
977
				}
978
			}
979

            
980
			return false;
981
		}
982

            
983
		/// Checks if a member(who) rank is the same as the rank of a member(who) in `IdToIndex`
984
		fn is_who_rank_in_id_to_index_storage(who: T::AccountId, rank: u16) -> bool {
985
			for (rank_, who_, _) in IdToIndex::<T, I>::iter() {
986
				if who == who_ && rank == rank_ {
987
					return true;
988
				}
989
			}
990

            
991
			return false;
992
		}
993

            
994
		/// Determines the total index for a rank
995
		fn determine_index_of_a_rank(rank: u16) -> u16 {
996
			let mut sum = 0;
997
			for _ in 0..rank + 1 {
998
				sum += 1;
999
			}
			sum
		}
	}
	impl<T: Config<I>, I: 'static> RankedMembers for Pallet<T, I> {
		type AccountId = T::AccountId;
		type Rank = Rank;
		fn min_rank() -> Self::Rank {
			0
		}
		fn rank_of(who: &Self::AccountId) -> Option<Self::Rank> {
			Some(Self::ensure_member(&who).ok()?.rank)
		}
		fn induct(who: &Self::AccountId) -> DispatchResult {
			Self::do_add_member(who.clone(), true)
		}
		fn promote(who: &Self::AccountId) -> DispatchResult {
			Self::do_promote_member(who.clone(), None, true)
		}
		fn demote(who: &Self::AccountId) -> DispatchResult {
			Self::do_demote_member(who.clone(), None)
		}
	}
}