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 is_active: bool,
177 last_promotion: BlockNumber,
179 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 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
194
195 #[pallet::pallet]
196 #[pallet::storage_version(STORAGE_VERSION)]
197 pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
198
199 #[pallet::config]
200 pub trait Config<I: 'static = ()>: frame_system::Config {
201 type WeightInfo: WeightInfo;
203
204 #[allow(deprecated)]
206 type RuntimeEvent: From<Event<Self, I>>
207 + IsType<<Self as frame_system::Config>::RuntimeEvent>;
208
209 type Members: RankedMembers<
211 AccountId = <Self as frame_system::Config>::AccountId,
212 Rank = u16,
213 >;
214
215 type Balance: BalanceTrait;
217
218 type ParamsOrigin: EnsureOrigin<Self::RuntimeOrigin>;
220
221 type InductOrigin: EnsureOrigin<Self::RuntimeOrigin>;
227
228 type ApproveOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = RankOf<Self, I>>;
231
232 type PromoteOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = RankOf<Self, I>>;
235
236 type FastPromoteOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = RankOf<Self, I>>;
239
240 #[pallet::constant]
242 type EvidenceSize: Get<u32>;
243
244 #[pallet::constant]
248 type MaxRank: Get<u16>;
249 }
250
251 pub type ParamsOf<T, I> = ParamsType<
252 <T as Config<I>>::Balance,
253 BlockNumberFor<T>,
254 ConvertU16ToU32<<T as Config<I>>::MaxRank>,
255 >;
256 pub type PartialParamsOf<T, I> = ParamsType<
257 Option<<T as Config<I>>::Balance>,
258 Option<BlockNumberFor<T>>,
259 ConvertU16ToU32<<T as Config<I>>::MaxRank>,
260 >;
261 pub type MemberStatusOf<T> = MemberStatus<BlockNumberFor<T>>;
262 pub type RankOf<T, I> = <<T as Config<I>>::Members as RankedMembers>::Rank;
263
264 #[pallet::storage]
266 pub type Params<T: Config<I>, I: 'static = ()> = StorageValue<_, ParamsOf<T, I>, ValueQuery>;
267
268 #[pallet::storage]
270 pub type Member<T: Config<I>, I: 'static = ()> =
271 StorageMap<_, Twox64Concat, T::AccountId, MemberStatusOf<T>, OptionQuery>;
272
273 #[pallet::storage]
275 pub type MemberEvidence<T: Config<I>, I: 'static = ()> =
276 StorageMap<_, Twox64Concat, T::AccountId, (Wish, Evidence<T, I>), OptionQuery>;
277
278 #[pallet::event]
279 #[pallet::generate_deposit(pub(super) fn deposit_event)]
280 pub enum Event<T: Config<I>, I: 'static = ()> {
281 ParamsChanged { params: ParamsOf<T, I> },
283 ActiveChanged { who: T::AccountId, is_active: bool },
285 Inducted { who: T::AccountId },
287 Offboarded { who: T::AccountId },
290 Promoted { who: T::AccountId, to_rank: RankOf<T, I> },
292 Demoted { who: T::AccountId, to_rank: RankOf<T, I> },
294 Proven { who: T::AccountId, at_rank: RankOf<T, I> },
296 Requested { who: T::AccountId, wish: Wish },
298 EvidenceJudged {
301 who: T::AccountId,
303 wish: Wish,
305 evidence: Evidence<T, I>,
307 old_rank: u16,
309 new_rank: Option<u16>,
311 },
312 Imported { who: T::AccountId, rank: RankOf<T, I> },
314 Swapped { who: T::AccountId, new_who: T::AccountId },
316 }
317
318 #[pallet::error]
319 pub enum Error<T, I = ()> {
320 Unranked,
322 Ranked,
324 UnexpectedRank,
327 InvalidRank,
329 NoPermission,
331 NothingDoing,
333 AlreadyInducted,
336 NotTracked,
338 TooSoon,
340 }
341
342 #[pallet::call]
343 impl<T: Config<I>, I: 'static> Pallet<T, I> {
344 #[pallet::weight(T::WeightInfo::bump_offboard().max(T::WeightInfo::bump_demote()))]
352 #[pallet::call_index(0)]
353 pub fn bump(origin: OriginFor<T>, who: T::AccountId) -> DispatchResultWithPostInfo {
354 ensure_signed(origin)?;
355 let mut member = Member::<T, I>::get(&who).ok_or(Error::<T, I>::NotTracked)?;
356 let rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::Unranked)?;
357
358 let params = Params::<T, I>::get();
359 let demotion_period = if rank == 0 {
360 params.offboard_timeout
361 } else {
362 let rank_index = Self::rank_to_index(rank).ok_or(Error::<T, I>::InvalidRank)?;
363 params.demotion_period[rank_index]
364 };
365
366 if demotion_period.is_zero() {
367 return Err(Error::<T, I>::NothingDoing.into())
368 }
369
370 let demotion_block = member.last_proof.saturating_add(demotion_period);
371
372 let now = frame_system::Pallet::<T>::block_number();
374 if now >= demotion_block {
375 T::Members::demote(&who)?;
376 let maybe_to_rank = T::Members::rank_of(&who);
377 Self::dispose_evidence(who.clone(), rank, maybe_to_rank);
378 let event = if let Some(to_rank) = maybe_to_rank {
379 member.last_proof = now;
380 Member::<T, I>::insert(&who, &member);
381 Event::<T, I>::Demoted { who, to_rank }
382 } else {
383 Member::<T, I>::remove(&who);
384 Event::<T, I>::Offboarded { who }
385 };
386 Self::deposit_event(event);
387 return Ok(Pays::No.into())
388 }
389
390 Err(Error::<T, I>::NothingDoing.into())
391 }
392
393 #[pallet::weight(T::WeightInfo::set_params())]
398 #[pallet::call_index(1)]
399 pub fn set_params(origin: OriginFor<T>, params: Box<ParamsOf<T, I>>) -> DispatchResult {
400 T::ParamsOrigin::ensure_origin_or_root(origin)?;
401
402 Params::<T, I>::put(params.as_ref());
403 Self::deposit_event(Event::<T, I>::ParamsChanged { params: *params });
404
405 Ok(())
406 }
407
408 #[pallet::weight(T::WeightInfo::set_active())]
413 #[pallet::call_index(2)]
414 pub fn set_active(origin: OriginFor<T>, is_active: bool) -> DispatchResult {
415 let who = ensure_signed(origin)?;
416 ensure!(
417 T::Members::rank_of(&who).map_or(false, |r| !r.is_zero()),
418 Error::<T, I>::Unranked
419 );
420 let mut member = Member::<T, I>::get(&who).ok_or(Error::<T, I>::NotTracked)?;
421 member.is_active = is_active;
422 Member::<T, I>::insert(&who, &member);
423 Self::deposit_event(Event::<T, I>::ActiveChanged { who, is_active });
424 Ok(())
425 }
426
427 #[pallet::weight(T::WeightInfo::approve())]
437 #[pallet::call_index(3)]
438 pub fn approve(
439 origin: OriginFor<T>,
440 who: T::AccountId,
441 at_rank: RankOf<T, I>,
442 ) -> DispatchResult {
443 match T::ApproveOrigin::try_origin(origin) {
444 Ok(allow_rank) => ensure!(allow_rank >= at_rank, Error::<T, I>::NoPermission),
445 Err(origin) => ensure_root(origin)?,
446 }
447 ensure!(at_rank > 0, Error::<T, I>::InvalidRank);
448 let rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::Unranked)?;
449 ensure!(rank == at_rank, Error::<T, I>::UnexpectedRank);
450 let mut member = Member::<T, I>::get(&who).ok_or(Error::<T, I>::NotTracked)?;
451
452 member.last_proof = frame_system::Pallet::<T>::block_number();
453 Member::<T, I>::insert(&who, &member);
454
455 Self::dispose_evidence(who.clone(), at_rank, Some(at_rank));
456 Self::deposit_event(Event::<T, I>::Proven { who, at_rank });
457
458 Ok(())
459 }
460
461 #[pallet::weight(T::WeightInfo::induct())]
466 #[pallet::call_index(4)]
467 pub fn induct(origin: OriginFor<T>, who: T::AccountId) -> DispatchResult {
468 match T::InductOrigin::try_origin(origin) {
469 Ok(_) => {},
470 Err(origin) => ensure_root(origin)?,
471 }
472 ensure!(!Member::<T, I>::contains_key(&who), Error::<T, I>::AlreadyInducted);
473 ensure!(T::Members::rank_of(&who).is_none(), Error::<T, I>::Ranked);
474
475 T::Members::induct(&who)?;
476 let now = frame_system::Pallet::<T>::block_number();
477 Member::<T, I>::insert(
478 &who,
479 MemberStatus { is_active: true, last_promotion: now, last_proof: now },
480 );
481 Self::deposit_event(Event::<T, I>::Inducted { who });
482 Ok(())
483 }
484
485 #[pallet::weight(T::WeightInfo::promote())]
492 #[pallet::call_index(5)]
493 pub fn promote(
494 origin: OriginFor<T>,
495 who: T::AccountId,
496 to_rank: RankOf<T, I>,
497 ) -> DispatchResult {
498 match T::PromoteOrigin::try_origin(origin) {
499 Ok(allow_rank) => ensure!(allow_rank >= to_rank, Error::<T, I>::NoPermission),
500 Err(origin) => ensure_root(origin)?,
501 }
502 let rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::Unranked)?;
503 ensure!(
504 rank.checked_add(1).map_or(false, |i| i == to_rank),
505 Error::<T, I>::UnexpectedRank
506 );
507
508 let mut member = Member::<T, I>::get(&who).ok_or(Error::<T, I>::NotTracked)?;
509 let now = frame_system::Pallet::<T>::block_number();
510
511 let params = Params::<T, I>::get();
512 let rank_index = Self::rank_to_index(to_rank).ok_or(Error::<T, I>::InvalidRank)?;
513 let min_period = params.min_promotion_period[rank_index];
514 ensure!(
516 member.last_promotion.saturating_add(min_period) <= now,
517 Error::<T, I>::TooSoon,
518 );
519
520 T::Members::promote(&who)?;
521 member.last_promotion = now;
522 member.last_proof = now;
523 Member::<T, I>::insert(&who, &member);
524 Self::dispose_evidence(who.clone(), rank, Some(to_rank));
525
526 Self::deposit_event(Event::<T, I>::Promoted { who, to_rank });
527
528 Ok(())
529 }
530
531 #[pallet::weight(T::WeightInfo::promote_fast(*to_rank as u32))]
537 #[pallet::call_index(10)]
538 pub fn promote_fast(
539 origin: OriginFor<T>,
540 who: T::AccountId,
541 to_rank: RankOf<T, I>,
542 ) -> DispatchResult {
543 match T::FastPromoteOrigin::try_origin(origin) {
544 Ok(allow_rank) => ensure!(allow_rank >= to_rank, Error::<T, I>::NoPermission),
545 Err(origin) => ensure_root(origin)?,
546 }
547 ensure!(to_rank <= T::MaxRank::get(), Error::<T, I>::InvalidRank);
548 let curr_rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::Unranked)?;
549 ensure!(to_rank > curr_rank, Error::<T, I>::UnexpectedRank);
550
551 let mut member = Member::<T, I>::get(&who).ok_or(Error::<T, I>::NotTracked)?;
552 let now = frame_system::Pallet::<T>::block_number();
553 member.last_promotion = now;
554 member.last_proof = now;
555
556 for rank in (curr_rank + 1)..=to_rank {
557 T::Members::promote(&who)?;
558
559 Member::<T, I>::insert(&who, &member);
561
562 Self::dispose_evidence(who.clone(), rank.saturating_sub(1), Some(rank));
563 Self::deposit_event(Event::<T, I>::Promoted { who: who.clone(), to_rank: rank });
564 }
565
566 Ok(())
567 }
568
569 #[pallet::weight(T::WeightInfo::offboard())]
575 #[pallet::call_index(6)]
576 pub fn offboard(origin: OriginFor<T>, who: T::AccountId) -> DispatchResultWithPostInfo {
577 ensure_signed(origin)?;
578 ensure!(T::Members::rank_of(&who).is_none(), Error::<T, I>::Ranked);
579 ensure!(Member::<T, I>::contains_key(&who), Error::<T, I>::NotTracked);
580 Member::<T, I>::remove(&who);
581 MemberEvidence::<T, I>::remove(&who);
582 Self::deposit_event(Event::<T, I>::Offboarded { who });
583 Ok(Pays::No.into())
584 }
585
586 #[pallet::weight(T::WeightInfo::submit_evidence())]
597 #[pallet::call_index(7)]
598 pub fn submit_evidence(
599 origin: OriginFor<T>,
600 wish: Wish,
601 evidence: Evidence<T, I>,
602 ) -> DispatchResultWithPostInfo {
603 let who = ensure_signed(origin)?;
604 ensure!(Member::<T, I>::contains_key(&who), Error::<T, I>::NotTracked);
605 let replaced = MemberEvidence::<T, I>::contains_key(&who);
606 MemberEvidence::<T, I>::insert(&who, (wish, evidence));
607 Self::deposit_event(Event::<T, I>::Requested { who, wish });
608 Ok(if replaced { Pays::Yes } else { Pays::No }.into())
609 }
610
611 #[pallet::weight(T::WeightInfo::import())]
619 #[pallet::call_index(8)]
620 #[deprecated = "Use `import_member` instead"]
621 #[allow(deprecated)] pub fn import(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
623 let who = ensure_signed(origin)?;
624 Self::do_import(who)?;
625
626 Ok(Pays::No.into()) }
628
629 #[pallet::weight(T::WeightInfo::set_partial_params())]
640 #[pallet::call_index(11)]
641 pub fn import_member(
642 origin: OriginFor<T>,
643 who: T::AccountId,
644 ) -> DispatchResultWithPostInfo {
645 ensure_signed(origin)?;
646 Self::do_import(who)?;
647
648 Ok(Pays::No.into()) }
650
651 #[pallet::weight(T::WeightInfo::set_partial_params())]
659 #[pallet::call_index(9)]
660 pub fn set_partial_params(
661 origin: OriginFor<T>,
662 partial_params: Box<PartialParamsOf<T, I>>,
663 ) -> DispatchResult {
664 T::ParamsOrigin::ensure_origin_or_root(origin)?;
665 let params = Params::<T, I>::mutate(|p| {
666 Self::set_partial_params_slice(&mut p.active_salary, partial_params.active_salary);
667 Self::set_partial_params_slice(
668 &mut p.passive_salary,
669 partial_params.passive_salary,
670 );
671 Self::set_partial_params_slice(
672 &mut p.demotion_period,
673 partial_params.demotion_period,
674 );
675 Self::set_partial_params_slice(
676 &mut p.min_promotion_period,
677 partial_params.min_promotion_period,
678 );
679 if let Some(new_offboard_timeout) = partial_params.offboard_timeout {
680 p.offboard_timeout = new_offboard_timeout;
681 }
682 p.clone()
683 });
684 Self::deposit_event(Event::<T, I>::ParamsChanged { params });
685 Ok(())
686 }
687 }
688
689 impl<T: Config<I>, I: 'static> Pallet<T, I> {
690 pub(crate) fn set_partial_params_slice<S>(
694 base_slice: &mut BoundedVec<S, ConvertU16ToU32<T::MaxRank>>,
695 new_slice: BoundedVec<Option<S>, ConvertU16ToU32<T::MaxRank>>,
696 ) {
697 for (base_element, new_element) in base_slice.iter_mut().zip(new_slice) {
698 if let Some(element) = new_element {
699 *base_element = element;
700 }
701 }
702 }
703
704 pub(crate) fn do_import(who: T::AccountId) -> DispatchResult {
708 ensure!(!Member::<T, I>::contains_key(&who), Error::<T, I>::AlreadyInducted);
709 let rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::Unranked)?;
710
711 let now = frame_system::Pallet::<T>::block_number();
712 Member::<T, I>::insert(
713 &who,
714 MemberStatus { is_active: true, last_promotion: 0u32.into(), last_proof: now },
715 );
716 Self::deposit_event(Event::<T, I>::Imported { who, rank });
717
718 Ok(())
719 }
720
721 pub(crate) fn rank_to_index(rank: RankOf<T, I>) -> Option<usize> {
726 if rank == 0 || rank > T::MaxRank::get() {
727 None
728 } else {
729 Some((rank - 1) as usize)
730 }
731 }
732
733 fn dispose_evidence(who: T::AccountId, old_rank: u16, new_rank: Option<u16>) {
734 if let Some((wish, evidence)) = MemberEvidence::<T, I>::take(&who) {
735 let e = Event::<T, I>::EvidenceJudged { who, wish, evidence, old_rank, new_rank };
736 Self::deposit_event(e);
737 }
738 }
739 }
740
741 impl<T: Config<I>, I: 'static> GetSalary<RankOf<T, I>, T::AccountId, T::Balance> for Pallet<T, I> {
742 fn get_salary(rank: RankOf<T, I>, who: &T::AccountId) -> T::Balance {
743 let index = match Self::rank_to_index(rank) {
744 Some(i) => i,
745 None => return Zero::zero(),
746 };
747 let member = match Member::<T, I>::get(who) {
748 Some(m) => m,
749 None => return Zero::zero(),
750 };
751 let params = Params::<T, I>::get();
752 let salary =
753 if member.is_active { params.active_salary } else { params.passive_salary };
754 salary[index]
755 }
756 }
757}
758
759pub struct EnsureInducted<T, I, const MIN_RANK: u16>(PhantomData<(T, I)>);
762impl<T: Config<I>, I: 'static, const MIN_RANK: u16> EnsureOrigin<T::RuntimeOrigin>
763 for EnsureInducted<T, I, MIN_RANK>
764{
765 type Success = T::AccountId;
766
767 fn try_origin(o: T::RuntimeOrigin) -> Result<Self::Success, T::RuntimeOrigin> {
768 let who = <frame_system::EnsureSigned<_> as EnsureOrigin<_>>::try_origin(o)?;
769 match T::Members::rank_of(&who) {
770 Some(rank) if rank >= MIN_RANK && Member::<T, I>::contains_key(&who) => Ok(who),
771 _ => Err(frame_system::RawOrigin::Signed(who).into()),
772 }
773 }
774
775 #[cfg(feature = "runtime-benchmarks")]
776 fn try_successful_origin() -> Result<T::RuntimeOrigin, ()> {
777 let who = frame_benchmarking::account::<T::AccountId>("successful_origin", 0, 0);
778 if T::Members::rank_of(&who).is_none() {
779 T::Members::induct(&who).map_err(|_| ())?;
780 }
781 for _ in 0..MIN_RANK {
782 if T::Members::rank_of(&who).ok_or(())? < MIN_RANK {
783 T::Members::promote(&who).map_err(|_| ())?;
784 }
785 }
786 Ok(frame_system::RawOrigin::Signed(who).into())
787 }
788}
789
790impl_ensure_origin_with_arg_ignoring_arg! {
791 impl< { T: Config<I>, I: 'static, const MIN_RANK: u16, A } >
792 EnsureOriginWithArg<T::RuntimeOrigin, A> for EnsureInducted<T, I, MIN_RANK>
793 {}
794}
795
796impl<T: Config<I>, I: 'static> RankedMembersSwapHandler<T::AccountId, u16> for Pallet<T, I> {
797 fn swapped(old: &T::AccountId, new: &T::AccountId, _rank: u16) {
798 if old == new {
799 defensive!("Should not try to swap with self");
800 return
801 }
802 if !Member::<T, I>::contains_key(old) {
803 defensive!("Should not try to swap non-member");
804 return
805 }
806 if Member::<T, I>::contains_key(new) {
807 defensive!("Should not try to overwrite existing member");
808 return
809 }
810
811 if let Some(member) = Member::<T, I>::take(old) {
812 Member::<T, I>::insert(new, member);
813 }
814 if let Some(we) = MemberEvidence::<T, I>::take(old) {
815 MemberEvidence::<T, I>::insert(new, we);
816 }
817
818 Self::deposit_event(Event::<T, I>::Swapped { who: old.clone(), new_who: new.clone() });
819 }
820}
821
822#[cfg(feature = "runtime-benchmarks")]
823impl<T: Config<I>, I: 'static>
824 pallet_ranked_collective::BenchmarkSetup<<T as frame_system::Config>::AccountId> for Pallet<T, I>
825{
826 fn ensure_member(who: &<T as frame_system::Config>::AccountId) {
827 #[allow(deprecated)]
828 Self::import(frame_system::RawOrigin::Signed(who.clone()).into()).unwrap();
829 }
830}