1#![cfg_attr(not(feature = "std"), no_std)]
61
62extern crate alloc;
63
64use alloc::boxed::Box;
65use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
66use core::{fmt::Debug, marker::PhantomData};
67use scale_info::TypeInfo;
68use sp_arithmetic::traits::{Saturating, Zero};
69
70use frame_support::{
71 defensive,
72 dispatch::DispatchResultWithPostInfo,
73 ensure, impl_ensure_origin_with_arg_ignoring_arg,
74 traits::{
75 tokens::Balance as BalanceTrait, EnsureOrigin, EnsureOriginWithArg, Get, RankedMembers,
76 RankedMembersSwapHandler,
77 },
78 BoundedVec, CloneNoBound, DebugNoBound, EqNoBound, PartialEqNoBound,
79};
80
81#[cfg(test)]
82mod tests;
83
84#[cfg(feature = "runtime-benchmarks")]
85mod benchmarking;
86pub mod migration;
87pub mod weights;
88
89pub use pallet::*;
90pub use weights::*;
91
92#[derive(
94 Encode,
95 Decode,
96 DecodeWithMemTracking,
97 Eq,
98 PartialEq,
99 Copy,
100 Clone,
101 TypeInfo,
102 MaxEncodedLen,
103 Debug,
104)]
105pub enum Wish {
106 Retention,
108 Promotion,
110}
111
112pub type Evidence<T, I> = BoundedVec<u8, <T as Config<I>>::EvidenceSize>;
117
118#[derive(
120 Encode,
121 Decode,
122 DecodeWithMemTracking,
123 CloneNoBound,
124 EqNoBound,
125 PartialEqNoBound,
126 DebugNoBound,
127 TypeInfo,
128 MaxEncodedLen,
129)]
130#[scale_info(skip_type_params(Ranks))]
131pub struct ParamsType<
132 Balance: Clone + Eq + PartialEq + Debug,
133 BlockNumber: Clone + Eq + PartialEq + Debug,
134 Ranks: Get<u32>,
135> {
136 pub active_salary: BoundedVec<Balance, Ranks>,
138 pub passive_salary: BoundedVec<Balance, Ranks>,
140 pub demotion_period: BoundedVec<BlockNumber, Ranks>,
142 pub min_promotion_period: BoundedVec<BlockNumber, Ranks>,
144 pub offboard_timeout: BlockNumber,
146}
147
148impl<
149 Balance: Default + Copy + Eq + Debug,
150 BlockNumber: Default + Copy + Eq + Debug,
151 Ranks: Get<u32>,
152 > Default for ParamsType<Balance, BlockNumber, Ranks>
153{
154 fn default() -> Self {
155 Self {
156 active_salary: Default::default(),
157 passive_salary: Default::default(),
158 demotion_period: Default::default(),
159 min_promotion_period: Default::default(),
160 offboard_timeout: BlockNumber::default(),
161 }
162 }
163}
164
165pub struct ConvertU16ToU32<Inner>(PhantomData<Inner>);
166impl<Inner: Get<u16>> Get<u32> for ConvertU16ToU32<Inner> {
167 fn get() -> u32 {
168 Inner::get() as u32
169 }
170}
171
172#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, Debug)]
174pub struct MemberStatus<BlockNumber> {
175 pub is_active: bool,
177 pub last_promotion: BlockNumber,
179 pub last_proof: BlockNumber,
181}
182
183#[frame_support::pallet]
184pub mod pallet {
185 use super::*;
186 use frame_support::{
187 dispatch::Pays,
188 pallet_prelude::*,
189 traits::{tokens::GetSalary, EnsureOrigin},
190 };
191 use frame_system::{ensure_root, pallet_prelude::*};
192 use sp_runtime::traits::BlockNumberProvider;
193 const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
195
196 #[pallet::pallet]
197 #[pallet::storage_version(STORAGE_VERSION)]
198 pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
199
200 #[pallet::config]
201 pub trait Config<I: 'static = ()>: frame_system::Config {
202 type WeightInfo: WeightInfo;
204
205 #[allow(deprecated)]
207 type RuntimeEvent: From<Event<Self, I>>
208 + IsType<<Self as frame_system::Config>::RuntimeEvent>;
209
210 type Members: RankedMembers<
212 AccountId = <Self as frame_system::Config>::AccountId,
213 Rank = u16,
214 >;
215
216 type Balance: BalanceTrait;
218
219 type ParamsOrigin: EnsureOrigin<Self::RuntimeOrigin>;
221
222 type InductOrigin: EnsureOrigin<Self::RuntimeOrigin>;
228
229 type ApproveOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = RankOf<Self, I>>;
232
233 type PromoteOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = RankOf<Self, I>>;
236
237 type FastPromoteOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = RankOf<Self, I>>;
240
241 #[pallet::constant]
243 type EvidenceSize: Get<u32>;
244
245 #[pallet::constant]
249 type MaxRank: Get<u16>;
250
251 type BlockNumberProvider: BlockNumberProvider;
256 }
257
258 pub type BlockNumberFor<T, I = ()> =
259 <<T as Config<I>>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
260 pub type ParamsOf<T, I> = ParamsType<
261 <T as Config<I>>::Balance,
262 BlockNumberFor<T, I>,
263 ConvertU16ToU32<<T as Config<I>>::MaxRank>,
264 >;
265 pub type PartialParamsOf<T, I> = ParamsType<
266 Option<<T as Config<I>>::Balance>,
267 Option<BlockNumberFor<T, I>>,
268 ConvertU16ToU32<<T as Config<I>>::MaxRank>,
269 >;
270 pub type MemberStatusOf<T, I> = MemberStatus<BlockNumberFor<T, I>>;
271 pub type RankOf<T, I> = <<T as Config<I>>::Members as RankedMembers>::Rank;
272
273 #[pallet::storage]
275 pub type Params<T: Config<I>, I: 'static = ()> = StorageValue<_, ParamsOf<T, I>, ValueQuery>;
276
277 #[pallet::storage]
279 pub type Member<T: Config<I>, I: 'static = ()> =
280 StorageMap<_, Twox64Concat, T::AccountId, MemberStatusOf<T, I>, OptionQuery>;
281
282 #[pallet::storage]
284 pub type MemberEvidence<T: Config<I>, I: 'static = ()> =
285 StorageMap<_, Twox64Concat, T::AccountId, (Wish, Evidence<T, I>), OptionQuery>;
286
287 #[pallet::event]
288 #[pallet::generate_deposit(pub(super) fn deposit_event)]
289 pub enum Event<T: Config<I>, I: 'static = ()> {
290 ParamsChanged { params: ParamsOf<T, I> },
292 ActiveChanged { who: T::AccountId, is_active: bool },
294 Inducted { who: T::AccountId },
296 Offboarded { who: T::AccountId },
299 Promoted { who: T::AccountId, to_rank: RankOf<T, I> },
301 Demoted { who: T::AccountId, to_rank: RankOf<T, I> },
303 Proven { who: T::AccountId, at_rank: RankOf<T, I> },
305 Requested { who: T::AccountId, wish: Wish },
307 EvidenceJudged {
310 who: T::AccountId,
312 wish: Wish,
314 evidence: Evidence<T, I>,
316 old_rank: u16,
318 new_rank: Option<u16>,
320 },
321 Imported { who: T::AccountId, rank: RankOf<T, I> },
323 Swapped { who: T::AccountId, new_who: T::AccountId },
325 }
326
327 #[pallet::error]
328 pub enum Error<T, I = ()> {
329 Unranked,
331 Ranked,
333 UnexpectedRank,
336 InvalidRank,
338 NoPermission,
340 NothingDoing,
342 AlreadyInducted,
345 NotTracked,
347 TooSoon,
349 }
350
351 #[pallet::call]
352 impl<T: Config<I>, I: 'static> Pallet<T, I> {
353 #[pallet::weight(T::WeightInfo::bump_offboard().max(T::WeightInfo::bump_demote()))]
361 #[pallet::call_index(0)]
362 pub fn bump(origin: OriginFor<T>, who: T::AccountId) -> DispatchResultWithPostInfo {
363 ensure_signed(origin)?;
364 let mut member = Member::<T, I>::get(&who).ok_or(Error::<T, I>::NotTracked)?;
365 let rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::Unranked)?;
366
367 let params = Params::<T, I>::get();
368 let demotion_period = if rank == 0 {
369 params.offboard_timeout
370 } else {
371 let rank_index = Self::rank_to_index(rank).ok_or(Error::<T, I>::InvalidRank)?;
372 *params.demotion_period.get(rank_index).ok_or(Error::<T, I>::InvalidRank)?
373 };
374
375 if demotion_period.is_zero() {
376 return Err(Error::<T, I>::NothingDoing.into());
377 }
378
379 let demotion_block = member.last_proof.saturating_add(demotion_period);
380
381 let now = T::BlockNumberProvider::current_block_number();
383 if now >= demotion_block {
384 T::Members::demote(&who)?;
385 let maybe_to_rank = T::Members::rank_of(&who);
386 Self::dispose_evidence(who.clone(), rank, maybe_to_rank);
387 let event = if let Some(to_rank) = maybe_to_rank {
388 member.last_proof = now;
389 Member::<T, I>::insert(&who, &member);
390 Event::<T, I>::Demoted { who, to_rank }
391 } else {
392 Member::<T, I>::remove(&who);
393 Event::<T, I>::Offboarded { who }
394 };
395 Self::deposit_event(event);
396 return Ok(Pays::No.into());
397 }
398
399 Err(Error::<T, I>::NothingDoing.into())
400 }
401
402 #[pallet::weight(T::WeightInfo::set_params())]
407 #[pallet::call_index(1)]
408 pub fn set_params(origin: OriginFor<T>, params: Box<ParamsOf<T, I>>) -> DispatchResult {
409 T::ParamsOrigin::ensure_origin_or_root(origin)?;
410
411 Params::<T, I>::put(params.as_ref());
412 Self::deposit_event(Event::<T, I>::ParamsChanged { params: *params });
413
414 Ok(())
415 }
416
417 #[pallet::weight(T::WeightInfo::set_active())]
422 #[pallet::call_index(2)]
423 pub fn set_active(origin: OriginFor<T>, is_active: bool) -> DispatchResult {
424 let who = ensure_signed(origin)?;
425 ensure!(
426 T::Members::rank_of(&who).map_or(false, |r| !r.is_zero()),
427 Error::<T, I>::Unranked
428 );
429 let mut member = Member::<T, I>::get(&who).ok_or(Error::<T, I>::NotTracked)?;
430 member.is_active = is_active;
431 Member::<T, I>::insert(&who, &member);
432 Self::deposit_event(Event::<T, I>::ActiveChanged { who, is_active });
433 Ok(())
434 }
435
436 #[pallet::weight(T::WeightInfo::approve())]
446 #[pallet::call_index(3)]
447 pub fn approve(
448 origin: OriginFor<T>,
449 who: T::AccountId,
450 at_rank: RankOf<T, I>,
451 ) -> DispatchResult {
452 match T::ApproveOrigin::try_origin(origin) {
453 Ok(allow_rank) => ensure!(allow_rank >= at_rank, Error::<T, I>::NoPermission),
454 Err(origin) => ensure_root(origin)?,
455 }
456 ensure!(at_rank > 0, Error::<T, I>::InvalidRank);
457 let rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::Unranked)?;
458 ensure!(rank == at_rank, Error::<T, I>::UnexpectedRank);
459 let mut member = Member::<T, I>::get(&who).ok_or(Error::<T, I>::NotTracked)?;
460
461 member.last_proof = T::BlockNumberProvider::current_block_number();
462 Member::<T, I>::insert(&who, &member);
463
464 Self::dispose_evidence(who.clone(), at_rank, Some(at_rank));
465 Self::deposit_event(Event::<T, I>::Proven { who, at_rank });
466
467 Ok(())
468 }
469
470 #[pallet::weight(T::WeightInfo::induct())]
475 #[pallet::call_index(4)]
476 pub fn induct(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult {
477 match T::InductOrigin::try_origin(origin) {
478 Ok(_) => {},
479 Err(origin) => ensure_root(origin)?,
480 }
481 ensure!(!Member::<T, I>::contains_key(&who), Error::<T, I>::AlreadyInducted);
482 ensure!(T::Members::rank_of(&who).is_none(), Error::<T, I>::Ranked);
483
484 T::Members::induct(&who)?;
485 let now = T::BlockNumberProvider::current_block_number();
486 Member::<T, I>::insert(
487 &who,
488 MemberStatus { is_active: true, last_promotion: now, last_proof: now },
489 );
490 Self::deposit_event(Event::<T, I>::Inducted { who });
491 Ok(())
492 }
493
494 #[pallet::weight(T::WeightInfo::promote())]
501 #[pallet::call_index(5)]
502 pub fn promote(
503 origin: OriginFor<T>,
504 who: T::AccountId,
505 to_rank: RankOf<T, I>,
506 ) -> DispatchResult {
507 match T::PromoteOrigin::try_origin(origin) {
508 Ok(allow_rank) => ensure!(allow_rank >= to_rank, Error::<T, I>::NoPermission),
509 Err(origin) => ensure_root(origin)?,
510 }
511 let rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::Unranked)?;
512 ensure!(
513 rank.checked_add(1).map_or(false, |i| i == to_rank),
514 Error::<T, I>::UnexpectedRank
515 );
516
517 let mut member = Member::<T, I>::get(&who).ok_or(Error::<T, I>::NotTracked)?;
518 let now = T::BlockNumberProvider::current_block_number();
519
520 let params = Params::<T, I>::get();
521 let rank_index = Self::rank_to_index(to_rank).ok_or(Error::<T, I>::InvalidRank)?;
522 let min_period =
523 *params.min_promotion_period.get(rank_index).ok_or(Error::<T, I>::InvalidRank)?;
524 ensure!(
526 member.last_promotion.saturating_add(min_period) <= now,
527 Error::<T, I>::TooSoon,
528 );
529
530 T::Members::promote(&who)?;
531 member.last_promotion = now;
532 member.last_proof = now;
533 Member::<T, I>::insert(&who, &member);
534 Self::dispose_evidence(who.clone(), rank, Some(to_rank));
535
536 Self::deposit_event(Event::<T, I>::Promoted { who, to_rank });
537
538 Ok(())
539 }
540
541 #[pallet::weight(T::WeightInfo::promote_fast(*to_rank as u32))]
547 #[pallet::call_index(10)]
548 pub fn promote_fast(
549 origin: OriginFor<T>,
550 who: T::AccountId,
551 to_rank: RankOf<T, I>,
552 ) -> DispatchResult {
553 match T::FastPromoteOrigin::try_origin(origin) {
554 Ok(allow_rank) => ensure!(allow_rank >= to_rank, Error::<T, I>::NoPermission),
555 Err(origin) => ensure_root(origin)?,
556 }
557 ensure!(to_rank <= T::MaxRank::get(), Error::<T, I>::InvalidRank);
558 let curr_rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::Unranked)?;
559 ensure!(to_rank > curr_rank, Error::<T, I>::UnexpectedRank);
560
561 let mut member = Member::<T, I>::get(&who).ok_or(Error::<T, I>::NotTracked)?;
562 let now = T::BlockNumberProvider::current_block_number();
563 member.last_promotion = now;
564 member.last_proof = now;
565
566 for rank in (curr_rank + 1)..=to_rank {
567 T::Members::promote(&who)?;
568
569 Member::<T, I>::insert(&who, &member);
571
572 Self::dispose_evidence(who.clone(), rank.saturating_sub(1), Some(rank));
573 Self::deposit_event(Event::<T, I>::Promoted { who: who.clone(), to_rank: rank });
574 }
575
576 Ok(())
577 }
578
579 #[pallet::weight(T::WeightInfo::offboard())]
585 #[pallet::call_index(6)]
586 pub fn offboard(origin: OriginFor<T>, who: T::AccountId) -> DispatchResultWithPostInfo {
587 ensure_signed(origin)?;
588 ensure!(T::Members::rank_of(&who).is_none(), Error::<T, I>::Ranked);
589 ensure!(Member::<T, I>::contains_key(&who), Error::<T, I>::NotTracked);
590 Member::<T, I>::remove(&who);
591 MemberEvidence::<T, I>::remove(&who);
592 Self::deposit_event(Event::<T, I>::Offboarded { who });
593 Ok(Pays::No.into())
594 }
595
596 #[pallet::weight(T::WeightInfo::submit_evidence())]
607 #[pallet::call_index(7)]
608 pub fn submit_evidence(
609 origin: OriginFor<T>,
610 wish: Wish,
611 evidence: Evidence<T, I>,
612 ) -> DispatchResultWithPostInfo {
613 let who = ensure_signed(origin)?;
614 ensure!(Member::<T, I>::contains_key(&who), Error::<T, I>::NotTracked);
615 let replaced = MemberEvidence::<T, I>::contains_key(&who);
616 MemberEvidence::<T, I>::insert(&who, (wish, evidence));
617 Self::deposit_event(Event::<T, I>::Requested { who, wish });
618 Ok(if replaced { Pays::Yes } else { Pays::No }.into())
619 }
620
621 #[pallet::weight(T::WeightInfo::import())]
629 #[pallet::call_index(8)]
630 #[deprecated = "Use `import_member` instead"]
631 #[allow(deprecated)] pub fn import(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
633 let who = ensure_signed(origin)?;
634 Self::do_import(who)?;
635
636 Ok(Pays::No.into()) }
638
639 #[pallet::weight(T::WeightInfo::set_partial_params())]
650 #[pallet::call_index(11)]
651 pub fn import_member(
652 origin: OriginFor<T>,
653 who: T::AccountId,
654 ) -> DispatchResultWithPostInfo {
655 ensure_signed(origin)?;
656 Self::do_import(who)?;
657
658 Ok(Pays::No.into()) }
660
661 #[pallet::weight(T::WeightInfo::set_partial_params())]
669 #[pallet::call_index(9)]
670 pub fn set_partial_params(
671 origin: OriginFor<T>,
672 partial_params: Box<PartialParamsOf<T, I>>,
673 ) -> DispatchResult {
674 T::ParamsOrigin::ensure_origin_or_root(origin)?;
675 let params = Params::<T, I>::mutate(|p| {
676 Self::set_partial_params_slice(&mut p.active_salary, partial_params.active_salary);
677 Self::set_partial_params_slice(
678 &mut p.passive_salary,
679 partial_params.passive_salary,
680 );
681 Self::set_partial_params_slice(
682 &mut p.demotion_period,
683 partial_params.demotion_period,
684 );
685 Self::set_partial_params_slice(
686 &mut p.min_promotion_period,
687 partial_params.min_promotion_period,
688 );
689 if let Some(new_offboard_timeout) = partial_params.offboard_timeout {
690 p.offboard_timeout = new_offboard_timeout;
691 }
692 p.clone()
693 });
694 Self::deposit_event(Event::<T, I>::ParamsChanged { params });
695 Ok(())
696 }
697 }
698
699 impl<T: Config<I>, I: 'static> Pallet<T, I> {
700 pub(crate) fn set_partial_params_slice<S>(
704 base_slice: &mut BoundedVec<S, ConvertU16ToU32<T::MaxRank>>,
705 new_slice: BoundedVec<Option<S>, ConvertU16ToU32<T::MaxRank>>,
706 ) {
707 for (base_element, new_element) in base_slice.iter_mut().zip(new_slice) {
708 if let Some(element) = new_element {
709 *base_element = element;
710 }
711 }
712 }
713
714 pub(crate) fn do_import(who: T::AccountId) -> DispatchResult {
718 ensure!(!Member::<T, I>::contains_key(&who), Error::<T, I>::AlreadyInducted);
719 let rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::Unranked)?;
720
721 let now = T::BlockNumberProvider::current_block_number();
722 Member::<T, I>::insert(
723 &who,
724 MemberStatus { is_active: true, last_promotion: 0u32.into(), last_proof: now },
725 );
726 Self::deposit_event(Event::<T, I>::Imported { who, rank });
727
728 Ok(())
729 }
730
731 pub(crate) fn rank_to_index(rank: RankOf<T, I>) -> Option<usize> {
736 if rank == 0 || rank > T::MaxRank::get() {
737 None
738 } else {
739 Some((rank - 1) as usize)
740 }
741 }
742
743 fn dispose_evidence(who: T::AccountId, old_rank: u16, new_rank: Option<u16>) {
744 if let Some((wish, evidence)) = MemberEvidence::<T, I>::take(&who) {
745 let e = Event::<T, I>::EvidenceJudged { who, wish, evidence, old_rank, new_rank };
746 Self::deposit_event(e);
747 }
748 }
749 }
750
751 impl<T: Config<I>, I: 'static> GetSalary<RankOf<T, I>, T::AccountId, T::Balance> for Pallet<T, I> {
752 fn get_salary(rank: RankOf<T, I>, who: &T::AccountId) -> T::Balance {
753 let index = match Self::rank_to_index(rank) {
754 Some(i) => i,
755 None => return Zero::zero(),
756 };
757 let member = match Member::<T, I>::get(who) {
758 Some(m) => m,
759 None => return Zero::zero(),
760 };
761 let params = Params::<T, I>::get();
762 let salary =
763 if member.is_active { params.active_salary } else { params.passive_salary };
764 salary.get(index).copied().unwrap_or_default()
768 }
769 }
770}
771
772pub struct EnsureInducted<T, I, const MIN_RANK: u16>(PhantomData<(T, I)>);
775impl<T: Config<I>, I: 'static, const MIN_RANK: u16> EnsureOrigin<T::RuntimeOrigin>
776 for EnsureInducted<T, I, MIN_RANK>
777{
778 type Success = T::AccountId;
779
780 fn try_origin(o: T::RuntimeOrigin) -> Result<Self::Success, T::RuntimeOrigin> {
781 let who = <frame_system::EnsureSigned<_> as EnsureOrigin<_>>::try_origin(o)?;
782 match T::Members::rank_of(&who) {
783 Some(rank) if rank >= MIN_RANK && Member::<T, I>::contains_key(&who) => Ok(who),
784 _ => Err(frame_system::RawOrigin::Signed(who).into()),
785 }
786 }
787
788 #[cfg(feature = "runtime-benchmarks")]
789 fn try_successful_origin() -> Result<T::RuntimeOrigin, ()> {
790 let who = frame_benchmarking::account::<T::AccountId>("successful_origin", 0, 0);
791 if T::Members::rank_of(&who).is_none() {
792 T::Members::induct(&who).map_err(|_| ())?;
793 }
794 for _ in 0..MIN_RANK {
795 if T::Members::rank_of(&who).ok_or(())? < MIN_RANK {
796 T::Members::promote(&who).map_err(|_| ())?;
797 }
798 }
799 Ok(frame_system::RawOrigin::Signed(who).into())
800 }
801}
802
803impl_ensure_origin_with_arg_ignoring_arg! {
804 impl< { T: Config<I>, I: 'static, const MIN_RANK: u16, A } >
805 EnsureOriginWithArg<T::RuntimeOrigin, A> for EnsureInducted<T, I, MIN_RANK>
806 {}
807}
808
809impl<T: Config<I>, I: 'static> RankedMembersSwapHandler<T::AccountId, u16> for Pallet<T, I> {
810 fn swapped(old: &T::AccountId, new: &T::AccountId, _rank: u16) {
811 if old == new {
812 defensive!("Should not try to swap with self");
813 return;
814 }
815 if !Member::<T, I>::contains_key(old) {
816 defensive!("Should not try to swap non-member");
817 return;
818 }
819 if Member::<T, I>::contains_key(new) {
820 defensive!("Should not try to overwrite existing member");
821 return;
822 }
823
824 if let Some(member) = Member::<T, I>::take(old) {
825 Member::<T, I>::insert(new, member);
826 }
827 if let Some(we) = MemberEvidence::<T, I>::take(old) {
828 MemberEvidence::<T, I>::insert(new, we);
829 }
830
831 Self::deposit_event(Event::<T, I>::Swapped { who: old.clone(), new_who: new.clone() });
832 }
833}
834
835#[cfg(feature = "runtime-benchmarks")]
836impl<T: Config<I>, I: 'static>
837 pallet_ranked_collective::BenchmarkSetup<<T as frame_system::Config>::AccountId> for Pallet<T, I>
838{
839 fn ensure_member(who: &<T as frame_system::Config>::AccountId) {
840 #[allow(deprecated)]
841 Self::import(frame_system::RawOrigin::Signed(who.clone()).into()).unwrap();
842 }
843}