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};
69use sp_runtime::RuntimeDebug;
70
71use frame_support::{
72 defensive,
73 dispatch::DispatchResultWithPostInfo,
74 ensure, impl_ensure_origin_with_arg_ignoring_arg,
75 traits::{
76 tokens::Balance as BalanceTrait, EnsureOrigin, EnsureOriginWithArg, Get, RankedMembers,
77 RankedMembersSwapHandler,
78 },
79 BoundedVec, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
80};
81
82#[cfg(test)]
83mod tests;
84
85#[cfg(feature = "runtime-benchmarks")]
86mod benchmarking;
87pub mod migration;
88pub mod weights;
89
90pub use pallet::*;
91pub use weights::*;
92
93#[derive(
95 Encode,
96 Decode,
97 DecodeWithMemTracking,
98 Eq,
99 PartialEq,
100 Copy,
101 Clone,
102 TypeInfo,
103 MaxEncodedLen,
104 RuntimeDebug,
105)]
106pub enum Wish {
107 Retention,
109 Promotion,
111}
112
113pub type Evidence<T, I> = BoundedVec<u8, <T as Config<I>>::EvidenceSize>;
118
119#[derive(
121 Encode,
122 Decode,
123 DecodeWithMemTracking,
124 CloneNoBound,
125 EqNoBound,
126 PartialEqNoBound,
127 RuntimeDebugNoBound,
128 TypeInfo,
129 MaxEncodedLen,
130)]
131#[scale_info(skip_type_params(Ranks))]
132pub struct ParamsType<
133 Balance: Clone + Eq + PartialEq + Debug,
134 BlockNumber: Clone + Eq + PartialEq + Debug,
135 Ranks: Get<u32>,
136> {
137 pub active_salary: BoundedVec<Balance, Ranks>,
139 pub passive_salary: BoundedVec<Balance, Ranks>,
141 pub demotion_period: BoundedVec<BlockNumber, Ranks>,
143 pub min_promotion_period: BoundedVec<BlockNumber, Ranks>,
145 pub offboard_timeout: BlockNumber,
147}
148
149impl<
150 Balance: Default + Copy + Eq + Debug,
151 BlockNumber: Default + Copy + Eq + Debug,
152 Ranks: Get<u32>,
153 > Default for ParamsType<Balance, BlockNumber, Ranks>
154{
155 fn default() -> Self {
156 Self {
157 active_salary: Default::default(),
158 passive_salary: Default::default(),
159 demotion_period: Default::default(),
160 min_promotion_period: Default::default(),
161 offboard_timeout: BlockNumber::default(),
162 }
163 }
164}
165
166pub struct ConvertU16ToU32<Inner>(PhantomData<Inner>);
167impl<Inner: Get<u16>> Get<u32> for ConvertU16ToU32<Inner> {
168 fn get() -> u32 {
169 Inner::get() as u32
170 }
171}
172
173#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)]
175pub struct MemberStatus<BlockNumber> {
176 is_active: bool,
178 last_promotion: BlockNumber,
180 last_proof: BlockNumber,
182}
183
184#[frame_support::pallet]
185pub mod pallet {
186 use super::*;
187 use frame_support::{
188 dispatch::Pays,
189 pallet_prelude::*,
190 traits::{tokens::GetSalary, EnsureOrigin},
191 };
192 use frame_system::{ensure_root, pallet_prelude::*};
193 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
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
252 pub type ParamsOf<T, I> = ParamsType<
253 <T as Config<I>>::Balance,
254 BlockNumberFor<T>,
255 ConvertU16ToU32<<T as Config<I>>::MaxRank>,
256 >;
257 pub type PartialParamsOf<T, I> = ParamsType<
258 Option<<T as Config<I>>::Balance>,
259 Option<BlockNumberFor<T>>,
260 ConvertU16ToU32<<T as Config<I>>::MaxRank>,
261 >;
262 pub type MemberStatusOf<T> = MemberStatus<BlockNumberFor<T>>;
263 pub type RankOf<T, I> = <<T as Config<I>>::Members as RankedMembers>::Rank;
264
265 #[pallet::storage]
267 pub type Params<T: Config<I>, I: 'static = ()> = StorageValue<_, ParamsOf<T, I>, ValueQuery>;
268
269 #[pallet::storage]
271 pub type Member<T: Config<I>, I: 'static = ()> =
272 StorageMap<_, Twox64Concat, T::AccountId, MemberStatusOf<T>, OptionQuery>;
273
274 #[pallet::storage]
276 pub type MemberEvidence<T: Config<I>, I: 'static = ()> =
277 StorageMap<_, Twox64Concat, T::AccountId, (Wish, Evidence<T, I>), OptionQuery>;
278
279 #[pallet::event]
280 #[pallet::generate_deposit(pub(super) fn deposit_event)]
281 pub enum Event<T: Config<I>, I: 'static = ()> {
282 ParamsChanged { params: ParamsOf<T, I> },
284 ActiveChanged { who: T::AccountId, is_active: bool },
286 Inducted { who: T::AccountId },
288 Offboarded { who: T::AccountId },
291 Promoted { who: T::AccountId, to_rank: RankOf<T, I> },
293 Demoted { who: T::AccountId, to_rank: RankOf<T, I> },
295 Proven { who: T::AccountId, at_rank: RankOf<T, I> },
297 Requested { who: T::AccountId, wish: Wish },
299 EvidenceJudged {
302 who: T::AccountId,
304 wish: Wish,
306 evidence: Evidence<T, I>,
308 old_rank: u16,
310 new_rank: Option<u16>,
312 },
313 Imported { who: T::AccountId, rank: RankOf<T, I> },
315 Swapped { who: T::AccountId, new_who: T::AccountId },
317 }
318
319 #[pallet::error]
320 pub enum Error<T, I = ()> {
321 Unranked,
323 Ranked,
325 UnexpectedRank,
328 InvalidRank,
330 NoPermission,
332 NothingDoing,
334 AlreadyInducted,
337 NotTracked,
339 TooSoon,
341 }
342
343 #[pallet::call]
344 impl<T: Config<I>, I: 'static> Pallet<T, I> {
345 #[pallet::weight(T::WeightInfo::bump_offboard().max(T::WeightInfo::bump_demote()))]
353 #[pallet::call_index(0)]
354 pub fn bump(origin: OriginFor<T>, who: T::AccountId) -> DispatchResultWithPostInfo {
355 ensure_signed(origin)?;
356 let mut member = Member::<T, I>::get(&who).ok_or(Error::<T, I>::NotTracked)?;
357 let rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::Unranked)?;
358
359 let params = Params::<T, I>::get();
360 let demotion_period = if rank == 0 {
361 params.offboard_timeout
362 } else {
363 let rank_index = Self::rank_to_index(rank).ok_or(Error::<T, I>::InvalidRank)?;
364 params.demotion_period[rank_index]
365 };
366
367 if demotion_period.is_zero() {
368 return Err(Error::<T, I>::NothingDoing.into())
369 }
370
371 let demotion_block = member.last_proof.saturating_add(demotion_period);
372
373 let now = frame_system::Pallet::<T>::block_number();
375 if now >= demotion_block {
376 T::Members::demote(&who)?;
377 let maybe_to_rank = T::Members::rank_of(&who);
378 Self::dispose_evidence(who.clone(), rank, maybe_to_rank);
379 let event = if let Some(to_rank) = maybe_to_rank {
380 member.last_proof = now;
381 Member::<T, I>::insert(&who, &member);
382 Event::<T, I>::Demoted { who, to_rank }
383 } else {
384 Member::<T, I>::remove(&who);
385 Event::<T, I>::Offboarded { who }
386 };
387 Self::deposit_event(event);
388 return Ok(Pays::No.into())
389 }
390
391 Err(Error::<T, I>::NothingDoing.into())
392 }
393
394 #[pallet::weight(T::WeightInfo::set_params())]
399 #[pallet::call_index(1)]
400 pub fn set_params(origin: OriginFor<T>, params: Box<ParamsOf<T, I>>) -> DispatchResult {
401 T::ParamsOrigin::ensure_origin_or_root(origin)?;
402
403 Params::<T, I>::put(params.as_ref());
404 Self::deposit_event(Event::<T, I>::ParamsChanged { params: *params });
405
406 Ok(())
407 }
408
409 #[pallet::weight(T::WeightInfo::set_active())]
414 #[pallet::call_index(2)]
415 pub fn set_active(origin: OriginFor<T>, is_active: bool) -> DispatchResult {
416 let who = ensure_signed(origin)?;
417 ensure!(
418 T::Members::rank_of(&who).map_or(false, |r| !r.is_zero()),
419 Error::<T, I>::Unranked
420 );
421 let mut member = Member::<T, I>::get(&who).ok_or(Error::<T, I>::NotTracked)?;
422 member.is_active = is_active;
423 Member::<T, I>::insert(&who, &member);
424 Self::deposit_event(Event::<T, I>::ActiveChanged { who, is_active });
425 Ok(())
426 }
427
428 #[pallet::weight(T::WeightInfo::approve())]
438 #[pallet::call_index(3)]
439 pub fn approve(
440 origin: OriginFor<T>,
441 who: T::AccountId,
442 at_rank: RankOf<T, I>,
443 ) -> DispatchResult {
444 match T::ApproveOrigin::try_origin(origin) {
445 Ok(allow_rank) => ensure!(allow_rank >= at_rank, Error::<T, I>::NoPermission),
446 Err(origin) => ensure_root(origin)?,
447 }
448 ensure!(at_rank > 0, Error::<T, I>::InvalidRank);
449 let rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::Unranked)?;
450 ensure!(rank == at_rank, Error::<T, I>::UnexpectedRank);
451 let mut member = Member::<T, I>::get(&who).ok_or(Error::<T, I>::NotTracked)?;
452
453 member.last_proof = frame_system::Pallet::<T>::block_number();
454 Member::<T, I>::insert(&who, &member);
455
456 Self::dispose_evidence(who.clone(), at_rank, Some(at_rank));
457 Self::deposit_event(Event::<T, I>::Proven { who, at_rank });
458
459 Ok(())
460 }
461
462 #[pallet::weight(T::WeightInfo::induct())]
467 #[pallet::call_index(4)]
468 pub fn induct(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult {
469 match T::InductOrigin::try_origin(origin) {
470 Ok(_) => {},
471 Err(origin) => ensure_root(origin)?,
472 }
473 ensure!(!Member::<T, I>::contains_key(&who), Error::<T, I>::AlreadyInducted);
474 ensure!(T::Members::rank_of(&who).is_none(), Error::<T, I>::Ranked);
475
476 T::Members::induct(&who)?;
477 let now = frame_system::Pallet::<T>::block_number();
478 Member::<T, I>::insert(
479 &who,
480 MemberStatus { is_active: true, last_promotion: now, last_proof: now },
481 );
482 Self::deposit_event(Event::<T, I>::Inducted { who });
483 Ok(())
484 }
485
486 #[pallet::weight(T::WeightInfo::promote())]
493 #[pallet::call_index(5)]
494 pub fn promote(
495 origin: OriginFor<T>,
496 who: T::AccountId,
497 to_rank: RankOf<T, I>,
498 ) -> DispatchResult {
499 match T::PromoteOrigin::try_origin(origin) {
500 Ok(allow_rank) => ensure!(allow_rank >= to_rank, Error::<T, I>::NoPermission),
501 Err(origin) => ensure_root(origin)?,
502 }
503 let rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::Unranked)?;
504 ensure!(
505 rank.checked_add(1).map_or(false, |i| i == to_rank),
506 Error::<T, I>::UnexpectedRank
507 );
508
509 let mut member = Member::<T, I>::get(&who).ok_or(Error::<T, I>::NotTracked)?;
510 let now = frame_system::Pallet::<T>::block_number();
511
512 let params = Params::<T, I>::get();
513 let rank_index = Self::rank_to_index(to_rank).ok_or(Error::<T, I>::InvalidRank)?;
514 let min_period = params.min_promotion_period[rank_index];
515 ensure!(
517 member.last_promotion.saturating_add(min_period) <= now,
518 Error::<T, I>::TooSoon,
519 );
520
521 T::Members::promote(&who)?;
522 member.last_promotion = now;
523 member.last_proof = now;
524 Member::<T, I>::insert(&who, &member);
525 Self::dispose_evidence(who.clone(), rank, Some(to_rank));
526
527 Self::deposit_event(Event::<T, I>::Promoted { who, to_rank });
528
529 Ok(())
530 }
531
532 #[pallet::weight(T::WeightInfo::promote_fast(*to_rank as u32))]
538 #[pallet::call_index(10)]
539 pub fn promote_fast(
540 origin: OriginFor<T>,
541 who: T::AccountId,
542 to_rank: RankOf<T, I>,
543 ) -> DispatchResult {
544 match T::FastPromoteOrigin::try_origin(origin) {
545 Ok(allow_rank) => ensure!(allow_rank >= to_rank, Error::<T, I>::NoPermission),
546 Err(origin) => ensure_root(origin)?,
547 }
548 ensure!(to_rank <= T::MaxRank::get(), Error::<T, I>::InvalidRank);
549 let curr_rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::Unranked)?;
550 ensure!(to_rank > curr_rank, Error::<T, I>::UnexpectedRank);
551
552 let mut member = Member::<T, I>::get(&who).ok_or(Error::<T, I>::NotTracked)?;
553 let now = frame_system::Pallet::<T>::block_number();
554 member.last_promotion = now;
555 member.last_proof = now;
556
557 for rank in (curr_rank + 1)..=to_rank {
558 T::Members::promote(&who)?;
559
560 Member::<T, I>::insert(&who, &member);
562
563 Self::dispose_evidence(who.clone(), rank.saturating_sub(1), Some(rank));
564 Self::deposit_event(Event::<T, I>::Promoted { who: who.clone(), to_rank: rank });
565 }
566
567 Ok(())
568 }
569
570 #[pallet::weight(T::WeightInfo::offboard())]
576 #[pallet::call_index(6)]
577 pub fn offboard(origin: OriginFor<T>, who: T::AccountId) -> DispatchResultWithPostInfo {
578 ensure_signed(origin)?;
579 ensure!(T::Members::rank_of(&who).is_none(), Error::<T, I>::Ranked);
580 ensure!(Member::<T, I>::contains_key(&who), Error::<T, I>::NotTracked);
581 Member::<T, I>::remove(&who);
582 MemberEvidence::<T, I>::remove(&who);
583 Self::deposit_event(Event::<T, I>::Offboarded { who });
584 Ok(Pays::No.into())
585 }
586
587 #[pallet::weight(T::WeightInfo::submit_evidence())]
598 #[pallet::call_index(7)]
599 pub fn submit_evidence(
600 origin: OriginFor<T>,
601 wish: Wish,
602 evidence: Evidence<T, I>,
603 ) -> DispatchResultWithPostInfo {
604 let who = ensure_signed(origin)?;
605 ensure!(Member::<T, I>::contains_key(&who), Error::<T, I>::NotTracked);
606 let replaced = MemberEvidence::<T, I>::contains_key(&who);
607 MemberEvidence::<T, I>::insert(&who, (wish, evidence));
608 Self::deposit_event(Event::<T, I>::Requested { who, wish });
609 Ok(if replaced { Pays::Yes } else { Pays::No }.into())
610 }
611
612 #[pallet::weight(T::WeightInfo::import())]
620 #[pallet::call_index(8)]
621 #[deprecated = "Use `import_member` instead"]
622 #[allow(deprecated)] pub fn import(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
624 let who = ensure_signed(origin)?;
625 Self::do_import(who)?;
626
627 Ok(Pays::No.into()) }
629
630 #[pallet::weight(T::WeightInfo::set_partial_params())]
641 #[pallet::call_index(11)]
642 pub fn import_member(
643 origin: OriginFor<T>,
644 who: T::AccountId,
645 ) -> DispatchResultWithPostInfo {
646 ensure_signed(origin)?;
647 Self::do_import(who)?;
648
649 Ok(Pays::No.into()) }
651
652 #[pallet::weight(T::WeightInfo::set_partial_params())]
660 #[pallet::call_index(9)]
661 pub fn set_partial_params(
662 origin: OriginFor<T>,
663 partial_params: Box<PartialParamsOf<T, I>>,
664 ) -> DispatchResult {
665 T::ParamsOrigin::ensure_origin_or_root(origin)?;
666 let params = Params::<T, I>::mutate(|p| {
667 Self::set_partial_params_slice(&mut p.active_salary, partial_params.active_salary);
668 Self::set_partial_params_slice(
669 &mut p.passive_salary,
670 partial_params.passive_salary,
671 );
672 Self::set_partial_params_slice(
673 &mut p.demotion_period,
674 partial_params.demotion_period,
675 );
676 Self::set_partial_params_slice(
677 &mut p.min_promotion_period,
678 partial_params.min_promotion_period,
679 );
680 if let Some(new_offboard_timeout) = partial_params.offboard_timeout {
681 p.offboard_timeout = new_offboard_timeout;
682 }
683 p.clone()
684 });
685 Self::deposit_event(Event::<T, I>::ParamsChanged { params });
686 Ok(())
687 }
688 }
689
690 impl<T: Config<I>, I: 'static> Pallet<T, I> {
691 pub(crate) fn set_partial_params_slice<S>(
695 base_slice: &mut BoundedVec<S, ConvertU16ToU32<T::MaxRank>>,
696 new_slice: BoundedVec<Option<S>, ConvertU16ToU32<T::MaxRank>>,
697 ) {
698 for (base_element, new_element) in base_slice.iter_mut().zip(new_slice) {
699 if let Some(element) = new_element {
700 *base_element = element;
701 }
702 }
703 }
704
705 pub(crate) fn do_import(who: T::AccountId) -> DispatchResult {
709 ensure!(!Member::<T, I>::contains_key(&who), Error::<T, I>::AlreadyInducted);
710 let rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::Unranked)?;
711
712 let now = frame_system::Pallet::<T>::block_number();
713 Member::<T, I>::insert(
714 &who,
715 MemberStatus { is_active: true, last_promotion: 0u32.into(), last_proof: now },
716 );
717 Self::deposit_event(Event::<T, I>::Imported { who, rank });
718
719 Ok(())
720 }
721
722 pub(crate) fn rank_to_index(rank: RankOf<T, I>) -> Option<usize> {
727 if rank == 0 || rank > T::MaxRank::get() {
728 None
729 } else {
730 Some((rank - 1) as usize)
731 }
732 }
733
734 fn dispose_evidence(who: T::AccountId, old_rank: u16, new_rank: Option<u16>) {
735 if let Some((wish, evidence)) = MemberEvidence::<T, I>::take(&who) {
736 let e = Event::<T, I>::EvidenceJudged { who, wish, evidence, old_rank, new_rank };
737 Self::deposit_event(e);
738 }
739 }
740 }
741
742 impl<T: Config<I>, I: 'static> GetSalary<RankOf<T, I>, T::AccountId, T::Balance> for Pallet<T, I> {
743 fn get_salary(rank: RankOf<T, I>, who: &T::AccountId) -> T::Balance {
744 let index = match Self::rank_to_index(rank) {
745 Some(i) => i,
746 None => return Zero::zero(),
747 };
748 let member = match Member::<T, I>::get(who) {
749 Some(m) => m,
750 None => return Zero::zero(),
751 };
752 let params = Params::<T, I>::get();
753 let salary =
754 if member.is_active { params.active_salary } else { params.passive_salary };
755 salary[index]
756 }
757 }
758}
759
760pub struct EnsureInducted<T, I, const MIN_RANK: u16>(PhantomData<(T, I)>);
763impl<T: Config<I>, I: 'static, const MIN_RANK: u16> EnsureOrigin<T::RuntimeOrigin>
764 for EnsureInducted<T, I, MIN_RANK>
765{
766 type Success = T::AccountId;
767
768 fn try_origin(o: T::RuntimeOrigin) -> Result<Self::Success, T::RuntimeOrigin> {
769 let who = <frame_system::EnsureSigned<_> as EnsureOrigin<_>>::try_origin(o)?;
770 match T::Members::rank_of(&who) {
771 Some(rank) if rank >= MIN_RANK && Member::<T, I>::contains_key(&who) => Ok(who),
772 _ => Err(frame_system::RawOrigin::Signed(who).into()),
773 }
774 }
775
776 #[cfg(feature = "runtime-benchmarks")]
777 fn try_successful_origin() -> Result<T::RuntimeOrigin, ()> {
778 let who = frame_benchmarking::account::<T::AccountId>("successful_origin", 0, 0);
779 if T::Members::rank_of(&who).is_none() {
780 T::Members::induct(&who).map_err(|_| ())?;
781 }
782 for _ in 0..MIN_RANK {
783 if T::Members::rank_of(&who).ok_or(())? < MIN_RANK {
784 T::Members::promote(&who).map_err(|_| ())?;
785 }
786 }
787 Ok(frame_system::RawOrigin::Signed(who).into())
788 }
789}
790
791impl_ensure_origin_with_arg_ignoring_arg! {
792 impl< { T: Config<I>, I: 'static, const MIN_RANK: u16, A } >
793 EnsureOriginWithArg<T::RuntimeOrigin, A> for EnsureInducted<T, I, MIN_RANK>
794 {}
795}
796
797impl<T: Config<I>, I: 'static> RankedMembersSwapHandler<T::AccountId, u16> for Pallet<T, I> {
798 fn swapped(old: &T::AccountId, new: &T::AccountId, _rank: u16) {
799 if old == new {
800 defensive!("Should not try to swap with self");
801 return
802 }
803 if !Member::<T, I>::contains_key(old) {
804 defensive!("Should not try to swap non-member");
805 return
806 }
807 if Member::<T, I>::contains_key(new) {
808 defensive!("Should not try to overwrite existing member");
809 return
810 }
811
812 if let Some(member) = Member::<T, I>::take(old) {
813 Member::<T, I>::insert(new, member);
814 }
815 if let Some(we) = MemberEvidence::<T, I>::take(old) {
816 MemberEvidence::<T, I>::insert(new, we);
817 }
818
819 Self::deposit_event(Event::<T, I>::Swapped { who: old.clone(), new_who: new.clone() });
820 }
821}
822
823#[cfg(feature = "runtime-benchmarks")]
824impl<T: Config<I>, I: 'static>
825 pallet_ranked_collective::BenchmarkSetup<<T as frame_system::Config>::AccountId> for Pallet<T, I>
826{
827 fn ensure_member(who: &<T as frame_system::Config>::AccountId) {
828 #[allow(deprecated)]
829 Self::import(frame_system::RawOrigin::Signed(who.clone()).into()).unwrap();
830 }
831}