1#![recursion_limit = "1024"]
102#![cfg_attr(not(feature = "std"), no_std)]
103extern crate alloc;
104use alloc::{boxed::Box, vec, vec::Vec};
105
106use frame::{
107 prelude::*,
108 traits::{
109 fungible::{hold::Balanced, Credit, Inspect, MutateHold},
110 Consideration, Footprint, OnUnbalanced, OriginTrait,
111 },
112};
113use types::{Bitfield, IdentifiedConsideration};
114
115pub use pallet::*;
116pub use weights::WeightInfo;
117
118#[cfg(feature = "runtime-benchmarks")]
119mod benchmarking;
120pub mod migrations;
121#[cfg(test)]
122mod mock;
123#[cfg(test)]
124mod tests;
125pub mod types;
126pub mod weights;
127
128pub const MAX_GROUPS_PER_ACCOUNT: u32 = 10;
130
131pub type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
132pub type BalanceOf<T> = <<T as Config>::Currency as Inspect<AccountIdFor<T>>>::Balance;
133pub type CreditOf<T> = Credit<AccountIdFor<T>, <T as Config>::Currency>;
134pub type ProvidedBlockNumberOf<T> =
136 <<T as Config>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
137
138pub type FriendsOf<T> =
140 BoundedVec<<T as frame_system::Config>::AccountId, <T as Config>::MaxFriendsPerConfig>;
141pub type HashOf<T> = <T as frame_system::Config>::Hash;
142
143#[derive(
145 Clone,
146 Eq,
147 PartialEq,
148 Encode,
149 Decode,
150 Default,
151 Debug,
152 TypeInfo,
153 MaxEncodedLen,
154 DecodeWithMemTracking,
155)]
156pub struct FriendGroup<ProvidedBlockNumber, AccountId, Friends> {
157 pub friends: Friends,
159
160 pub friends_needed: u32,
162
163 pub inheritor: AccountId,
165
166 pub inheritance_delay: ProvidedBlockNumber,
170
171 pub inheritance_priority: InheritancePriority,
179
180 pub cancel_delay: ProvidedBlockNumber,
187}
188
189pub type FriendGroupIndex = u32;
191
192pub type InheritancePriority = u32;
196
197pub type FriendGroupOf<T> = FriendGroup<ProvidedBlockNumberOf<T>, AccountIdFor<T>, FriendsOf<T>>;
199
200pub type FriendGroupsOf<T> = BoundedVec<FriendGroupOf<T>, ConstU32<MAX_GROUPS_PER_ACCOUNT>>;
202
203pub type ApprovalBitfield<MaxFriends> = Bitfield<MaxFriends>;
205
206pub type ApprovalBitfieldOf<T> = ApprovalBitfield<<T as Config>::MaxFriendsPerConfig>;
208
209#[derive(
211 Clone,
212 Eq,
213 PartialEq,
214 Encode,
215 Decode,
216 Default,
217 Debug,
218 TypeInfo,
219 MaxEncodedLen,
220 DecodeWithMemTracking,
221)]
222pub struct Attempt<ProvidedBlockNumber, ApprovalBitfield, AccountId> {
223 pub friend_group_index: FriendGroupIndex,
227
228 pub initiator: AccountId,
230
231 pub init_block: ProvidedBlockNumber,
235
236 pub last_approval_block: ProvidedBlockNumber,
240
241 pub approvals: ApprovalBitfield,
246}
247
248pub type AttemptOf<T> = Attempt<ProvidedBlockNumberOf<T>, ApprovalBitfieldOf<T>, AccountIdFor<T>>;
250
251pub type AttemptTicketOf<T> =
253 IdentifiedConsideration<AccountIdFor<T>, Footprint, <T as Config>::AttemptConsideration>;
254
255pub type InheritorTicketOf<T> =
257 IdentifiedConsideration<AccountIdFor<T>, Footprint, <T as Config>::InheritorConsideration>;
258
259pub type SecurityDepositOf<T> = BalanceOf<T>;
261
262#[frame::pallet]
263pub mod pallet {
264 use super::*;
265
266 #[pallet::pallet]
267 #[pallet::storage_version(migrations::STORAGE_VERSION)]
268 pub struct Pallet<T>(_);
269
270 #[pallet::config]
271 pub trait Config: frame_system::Config {
272 type RuntimeCall: Parameter
274 + Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
275 + GetDispatchInfo
276 + From<frame_system::Call<Self>>
277 + IsSubType<Call<Self>>
278 + IsType<<Self as frame_system::Config>::RuntimeCall>;
279
280 type RuntimeHoldReason: Parameter
282 + Member
283 + MaxEncodedLen
284 + Copy
285 + VariantCount
286 + From<HoldReason>;
287
288 type BlockNumberProvider: BlockNumberProvider;
311
312 #[cfg(not(feature = "runtime-benchmarks"))]
314 type Currency: MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>
315 + Balanced<Self::AccountId>;
316 #[cfg(feature = "runtime-benchmarks")]
317 type Currency: MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>
318 + Balanced<Self::AccountId>
319 + frame::traits::fungible::Mutate<Self::AccountId>;
320
321 type FriendGroupsConsideration: Consideration<Self::AccountId, Footprint>;
323
324 type AttemptConsideration: Consideration<Self::AccountId, Footprint>;
326
327 type InheritorConsideration: Consideration<Self::AccountId, Footprint>;
329
330 #[pallet::constant]
332 type SecurityDeposit: Get<BalanceOf<Self>>;
333
334 type Slash: OnUnbalanced<CreditOf<Self>>;
339
340 #[pallet::constant]
344 type MaxFriendsPerConfig: Get<u32>;
345
346 type WeightInfo: WeightInfo;
348 }
349
350 #[pallet::storage]
354 pub type FriendGroups<T: Config> = StorageMap<
355 _,
356 Blake2_128Concat,
357 T::AccountId,
358 (FriendGroupsOf<T>, T::FriendGroupsConsideration),
359 >;
360
361 #[pallet::storage]
363 pub type Attempt<T: Config> = StorageDoubleMap<
364 _,
365 Blake2_128Concat,
366 T::AccountId,
367 Blake2_128Concat,
368 FriendGroupIndex,
369 (AttemptOf<T>, AttemptTicketOf<T>, SecurityDepositOf<T>),
370 >;
371
372 #[pallet::storage]
378 pub type Inheritor<T: Config> = StorageMap<
379 _,
380 Blake2_128Concat,
381 T::AccountId,
382 (InheritancePriority, T::AccountId, InheritorTicketOf<T>),
383 >;
384
385 #[pallet::composite_enum]
386 pub enum HoldReason {
387 #[codec(index = 0)]
389 FriendGroupsStorage,
390
391 #[codec(index = 1)]
393 AttemptStorage,
394
395 #[codec(index = 2)]
397 InheritorStorage,
398
399 #[codec(index = 3)]
401 SecurityDeposit,
402 }
403
404 #[pallet::event]
405 #[pallet::generate_deposit(pub(super) fn deposit_event)]
406 pub enum Event<T: Config> {
407 AttemptApproved {
409 lost: T::AccountId,
410 friend_group_index: FriendGroupIndex,
411 friend: T::AccountId,
412 },
413 AttemptCanceled {
415 lost: T::AccountId,
416 friend_group_index: FriendGroupIndex,
417 canceler: T::AccountId,
418 },
419 AttemptInitiated {
421 lost: T::AccountId,
422 friend_group_index: FriendGroupIndex,
423 initiator: T::AccountId,
424 },
425 AttemptFinished {
427 lost: T::AccountId,
428 friend_group_index: FriendGroupIndex,
429 inheritor: T::AccountId,
430 previous_inheritor: Option<T::AccountId>,
431 },
432 AttemptDiscarded {
438 lost: T::AccountId,
439 friend_group_index: FriendGroupIndex,
440 existing_inheritor: T::AccountId,
441 },
442 AttemptSlashed { lost: T::AccountId, friend_group_index: FriendGroupIndex },
446 FriendGroupsChanged { lost: T::AccountId },
448 InheritorRevoked { lost: T::AccountId },
450 RecoveredAccountControlled {
454 recovered: T::AccountId,
455 inheritor: T::AccountId,
456 call_hash: HashOf<T>,
457 call_result: DispatchResult,
458 },
459 }
460
461 #[pallet::error]
462 pub enum Error<T> {
463 AlreadyApproved,
465 AlreadyInitiated,
467 AlreadyVoted,
469 HasOngoingAttempts,
471 LostAccountInFriendGroup,
473 HigherPriorityRecovered,
475 NoCancelDelay,
477 NoFriendGroups,
479 NoFriends,
481 NoInheritor,
483 NotApproved,
485 NotAttempt,
487 NotCanceller,
489 NotFriend,
491 NotFriendGroup,
493 NotInheritor,
495 NotYetCancelable,
497 NotYetInheritable,
499 TooManyFriendGroups,
501 TooManyFriendsNeeded,
503 NoFriendsNeeded,
505 FriendsNotSortedOrUnique,
507 DuplicateFriendGroups,
509 }
510
511 #[pallet::view_functions]
512 impl<T: Config> Pallet<T> {
513 pub fn provided_block_number() -> ProvidedBlockNumberOf<T> {
515 T::BlockNumberProvider::current_block_number()
516 }
517
518 pub fn friend_groups(lost: T::AccountId) -> Vec<FriendGroupOf<T>> {
520 FriendGroups::<T>::get(lost).map(|(g, _t)| g.into_inner()).unwrap_or_default()
521 }
522
523 pub fn attempts(lost: T::AccountId) -> Vec<(FriendGroupOf<T>, AttemptOf<T>)> {
525 Attempt::<T>::iter_prefix(&lost)
526 .filter_map(|(friend_group_index, (attempt, _ticket, _deposit))| {
527 let friend_group = Self::friend_group_of(&lost, friend_group_index).ok()?;
528 Some((friend_group, attempt))
529 })
530 .collect()
531 }
532
533 pub fn inheritor(lost: T::AccountId) -> Option<T::AccountId> {
535 Inheritor::<T>::get(lost).map(|(_, inheritor, _)| inheritor)
536 }
537
538 pub fn inheritance(heir: T::AccountId) -> Vec<T::AccountId> {
540 let mut inheritance = Vec::new();
541
542 for (recovered, (_, inheritor, _)) in Inheritor::<T>::iter() {
543 if inheritor != heir {
544 continue;
545 }
546 let Err(pos) = inheritance.binary_search(&recovered) else { continue };
547
548 inheritance.insert(pos, recovered);
549 }
550
551 inheritance
552 }
553 }
554
555 #[pallet::call]
556 impl<T: Config> Pallet<T> {
557 #[pallet::call_index(0)]
563 #[pallet::weight({
564 let di = call.get_dispatch_info();
565 (T::WeightInfo::control_inherited_account().saturating_add(di.call_weight), di.class)
566 })]
567 pub fn control_inherited_account(
568 origin: OriginFor<T>,
569 recovered: AccountIdLookupOf<T>,
570 call: Box<<T as Config>::RuntimeCall>,
571 ) -> DispatchResult {
572 let maybe_inheritor = ensure_signed(origin)?;
573 let recovered = T::Lookup::lookup(recovered)?;
574
575 let inheritor = Inheritor::<T>::get(&recovered)
576 .map(|(_, inheritor, _ticket)| inheritor)
577 .ok_or(Error::<T>::NoInheritor)?;
578 ensure!(maybe_inheritor == inheritor, Error::<T>::NotInheritor);
579
580 let mut origin: T::RuntimeOrigin =
581 frame_system::RawOrigin::Signed(recovered.clone()).into();
582 origin.add_filter(|c: &<T as frame_system::Config>::RuntimeCall| {
584 let c = <T as Config>::RuntimeCall::from_ref(c);
585 c.is_sub_type().is_none()
586 });
587
588 let call_hash = call.using_encoded(&T::Hashing::hash);
589 let call_result = call.dispatch(origin).map(|_| ()).map_err(|r| r.error);
590
591 Self::deposit_event(Event::<T>::RecoveredAccountControlled {
592 recovered,
593 inheritor,
594 call_hash,
595 call_result,
596 });
597
598 Ok(())
601 }
602
603 #[pallet::call_index(1)]
608 #[pallet::weight(T::WeightInfo::revoke_inheritor())]
609 pub fn revoke_inheritor(origin: OriginFor<T>) -> DispatchResult {
610 let lost = ensure_signed(origin)?;
611
612 let (_priority, _inheritor, ticket) =
613 Inheritor::<T>::take(&lost).ok_or(Error::<T>::NoInheritor)?;
614
615 let _: Result<(), DispatchError> = ticket.try_drop().defensive();
616
617 Self::deposit_event(Event::<T>::InheritorRevoked { lost });
618
619 Ok(())
620 }
621
622 #[pallet::call_index(2)]
631 #[pallet::weight(T::WeightInfo::set_friend_groups())]
632 pub fn set_friend_groups(
633 origin: OriginFor<T>,
634 friend_groups: Vec<FriendGroupOf<T>>,
635 ) -> DispatchResult {
636 let lost = ensure_signed(origin)?;
637
638 if Attempt::<T>::iter_prefix(&lost).next().is_some() {
639 return Err(Error::<T>::HasOngoingAttempts.into());
640 }
641
642 let (old_friend_groups, old_ticket) = match FriendGroups::<T>::get(&lost) {
643 Some((g, t)) => (g, Some(t)),
644 None => Default::default(),
645 };
646
647 let new_friend_groups = Self::bound_friend_groups(&lost, friend_groups)?;
648
649 if new_friend_groups.is_empty() {
651 if let Some(old_ticket) = old_ticket {
652 old_ticket.drop(&lost)?;
653 }
654 FriendGroups::<T>::remove(&lost);
655 if !old_friend_groups.is_empty() {
656 Self::deposit_event(Event::<T>::FriendGroupsChanged { lost });
657 }
658 return Ok(());
659 }
660
661 let new_footprint = Self::friend_group_footprint(&new_friend_groups);
662 let new_ticket = if let Some(old_ticket) = old_ticket {
663 old_ticket.update(&lost, new_footprint)?
664 } else {
665 T::FriendGroupsConsideration::new(&lost, new_footprint)?
666 };
667 FriendGroups::<T>::insert(&lost, (&new_friend_groups, &new_ticket));
668
669 if new_friend_groups != old_friend_groups {
670 Self::deposit_event(Event::<T>::FriendGroupsChanged { lost });
671 }
672
673 Ok(())
674 }
675
676 #[pallet::call_index(3)]
686 #[pallet::weight(T::WeightInfo::initiate_attempt())]
687 pub fn initiate_attempt(
688 origin: OriginFor<T>,
689 lost: AccountIdLookupOf<T>,
690 friend_group_index: FriendGroupIndex,
691 ) -> DispatchResult {
692 let initiator = ensure_signed(origin)?;
693 let lost = T::Lookup::lookup(lost)?;
694
695 if Self::attempt_of(&lost, friend_group_index).is_ok() {
696 return Err(Error::<T>::AlreadyInitiated.into());
697 }
698
699 let friend_group = Self::friend_group_of(&lost, friend_group_index)?;
700 let initiator_index = friend_group
701 .friends
702 .iter()
703 .position(|f| f == &initiator)
704 .ok_or(Error::<T>::NotFriend)?;
705
706 if let Some((inheritance_priority, _, _)) = Inheritor::<T>::get(&lost) {
707 ensure!(
708 friend_group.inheritance_priority < inheritance_priority,
709 Error::<T>::HigherPriorityRecovered
710 );
711 }
712
713 let approvals = ApprovalBitfield::default()
715 .with_bits([initiator_index])
716 .defensive_proof("initiator_index < friends.len() <= MaxFriendsPerConfig; qed")
717 .unwrap_or_default();
718
719 let now = T::BlockNumberProvider::current_block_number();
720 let attempt = AttemptOf::<T> {
721 friend_group_index,
722 initiator: initiator.clone(),
723 init_block: now,
724 last_approval_block: now,
725 approvals,
726 };
727
728 let deposit = T::SecurityDeposit::get();
729 let () = T::Currency::hold(&HoldReason::SecurityDeposit.into(), &initiator, deposit)?;
730
731 let ticket = AttemptTicketOf::<T>::new(&initiator, Self::attempt_footprint())?;
732 Attempt::<T>::insert(&lost, friend_group_index, (&attempt, &ticket, &deposit));
733
734 Self::deposit_event(Event::<T>::AttemptInitiated {
735 lost: lost.clone(),
736 friend_group_index,
737 initiator: initiator.clone(),
738 });
739 Self::deposit_event(Event::<T>::AttemptApproved {
740 lost,
741 friend_group_index,
742 friend: initiator,
743 });
744
745 Ok(())
746 }
747
748 #[pallet::call_index(4)]
754 #[pallet::weight(T::WeightInfo::approve_attempt())]
755 pub fn approve_attempt(
756 origin: OriginFor<T>,
757 lost: AccountIdLookupOf<T>,
758 friend_group_index: FriendGroupIndex,
759 ) -> DispatchResult {
760 let friend = ensure_signed(origin)?;
761 let lost = T::Lookup::lookup(lost)?;
762 let now = T::BlockNumberProvider::current_block_number();
763
764 let (mut attempt, ticket, deposit) = Self::attempt_of(&lost, friend_group_index)?;
765 let friend_group = Self::friend_group_of(&lost, friend_group_index).defensive()?;
766
767 let friend_index = friend_group
768 .friends
769 .iter()
770 .position(|f| f == &friend)
771 .ok_or(Error::<T>::NotFriend)?;
772
773 let friends_voted = attempt.approvals.count_ones();
774 ensure!(friends_voted < friend_group.friends_needed, Error::<T>::AlreadyApproved);
775 attempt.last_approval_block = now;
776
777 attempt
778 .approvals
779 .set_if_not_set(friend_index)
780 .map_err(|_| Error::<T>::AlreadyVoted)?;
781
782 Attempt::<T>::insert(&lost, friend_group_index, (&attempt, &ticket, &deposit));
784
785 Self::deposit_event(Event::<T>::AttemptApproved { lost, friend_group_index, friend });
786
787 Ok(())
788 }
789
790 #[pallet::call_index(5)]
794 #[pallet::weight(T::WeightInfo::finish_attempt())]
795 pub fn finish_attempt(
796 origin: OriginFor<T>,
797 lost: AccountIdLookupOf<T>,
798 friend_group_index: FriendGroupIndex,
799 ) -> DispatchResult {
800 let caller = ensure_signed(origin)?;
801 let lost = T::Lookup::lookup(lost)?;
802 let now = T::BlockNumberProvider::current_block_number();
803
804 let (attempt, attempts_ticket, deposit) =
805 Attempt::<T>::take(&lost, &friend_group_index).ok_or(Error::<T>::NotAttempt)?;
806
807 let _: Result<(), DispatchError> = attempts_ticket.try_drop().defensive();
809 let _: Result<BalanceOf<T>, DispatchError> = T::Currency::release(
810 &HoldReason::SecurityDeposit.into(),
811 &attempt.initiator,
812 deposit,
813 Precision::BestEffort,
814 )
815 .defensive();
816
817 let friend_group = Self::friend_group_of(&lost, friend_group_index).defensive()?;
818
819 let approvals = attempt.approvals.count_ones();
821 ensure!(
822 approvals >= friend_group.friends_needed,
824 Error::<T>::NotApproved
825 );
826
827 let inheritable_at = attempt
828 .init_block
829 .checked_add(&friend_group.inheritance_delay)
830 .ok_or(ArithmeticError::Overflow)?;
831 ensure!(now >= inheritable_at, Error::<T>::NotYetInheritable);
832 let inheritor = friend_group.inheritor;
836 let inheritance_priority = friend_group.inheritance_priority;
837
838 match Inheritor::<T>::get(&lost) {
839 None => {
840 let ticket = Self::inheritor_ticket(&caller)?;
841 Inheritor::<T>::insert(&lost, (inheritance_priority, &inheritor, ticket));
842 Self::deposit_event(Event::<T>::AttemptFinished {
843 lost,
844 friend_group_index,
845 inheritor,
846 previous_inheritor: None,
847 });
848 },
849 Some((old_priority, old_inheritor, ticket))
851 if inheritance_priority < old_priority =>
852 {
853 let ticket = ticket.update(&caller, Self::inheritor_footprint())?;
854 Inheritor::<T>::insert(&lost, (inheritance_priority, &inheritor, ticket));
855 Self::deposit_event(Event::<T>::AttemptFinished {
856 lost,
857 friend_group_index,
858 inheritor,
859 previous_inheritor: Some(old_inheritor),
860 });
861 },
862 Some((_, existing_inheritor, _)) => {
863 Self::deposit_event(Event::<T>::AttemptDiscarded {
866 lost,
867 friend_group_index,
868 existing_inheritor,
869 });
870 },
871 };
872
873 Ok(())
874 }
875
876 #[pallet::call_index(6)]
882 #[pallet::weight(T::WeightInfo::cancel_attempt())]
883 pub fn cancel_attempt(
884 origin: OriginFor<T>,
885 lost: AccountIdLookupOf<T>,
886 friend_group_index: FriendGroupIndex,
887 ) -> DispatchResult {
888 let canceler = ensure_signed(origin)?;
889 let lost = T::Lookup::lookup(lost)?;
890 let now = T::BlockNumberProvider::current_block_number();
891
892 let (attempt, ticket, deposit) =
893 Attempt::<T>::take(&lost, &friend_group_index).ok_or(Error::<T>::NotAttempt)?;
894
895 ensure!(canceler == attempt.initiator || canceler == lost, Error::<T>::NotCanceller);
896
897 let _ignored = ticket.try_drop().defensive();
899 let _: Result<BalanceOf<T>, DispatchError> = T::Currency::release(
900 &HoldReason::SecurityDeposit.into(),
901 &attempt.initiator,
902 deposit,
903 Precision::BestEffort,
904 )
905 .defensive();
906
907 let friend_group = Self::friend_group_of(&lost, friend_group_index).defensive()?;
908
909 if canceler != lost {
910 let cancelable_at = attempt
911 .last_approval_block
912 .checked_add(&friend_group.cancel_delay)
913 .ok_or(ArithmeticError::Overflow)?;
914 ensure!(now >= cancelable_at, Error::<T>::NotYetCancelable);
915 }
916 Self::deposit_event(Event::<T>::AttemptCanceled { lost, friend_group_index, canceler });
921
922 Ok(())
923 }
924
925 #[pallet::call_index(7)]
927 #[pallet::weight(T::WeightInfo::slash_attempt())]
928 pub fn slash_attempt(
929 origin: OriginFor<T>,
930 friend_group_index: FriendGroupIndex,
931 ) -> DispatchResult {
932 let lost = ensure_signed(origin)?;
933
934 let (attempt, ticket, deposit) =
935 Attempt::<T>::take(&lost, &friend_group_index).ok_or(Error::<T>::NotAttempt)?;
936
937 let _: Result<(), DispatchError> = ticket.try_drop().defensive();
938 Self::handle_slash(&attempt.initiator, deposit);
939
940 Self::deposit_event(Event::<T>::AttemptSlashed { lost, friend_group_index });
941
942 Ok(())
943 }
944 }
945
946 #[pallet::hooks]
947 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
948 fn integrity_test() {
949 assert!(
950 T::MaxFriendsPerConfig::get() > 0,
951 "MaxFriendsPerConfig must be greater than 0"
952 );
953
954 let bitfield = ApprovalBitfieldOf::<T>::default();
955 assert!(bitfield.0.len() >= 1, "Default works");
956 }
957 }
958}
959
960impl<T: Config> Pallet<T> {
961 pub fn friend_group_footprint(friend_groups: &FriendGroupsOf<T>) -> Footprint {
962 if friend_groups.is_empty() {
963 defensive!("Do not call with empty friend groups");
964 }
965
966 Footprint::from_encodable(friend_groups)
967 }
968
969 pub fn attempt_footprint() -> Footprint {
970 Footprint::from_mel::<AttemptOf<T>>()
971 }
972
973 pub fn inheritor_footprint() -> Footprint {
974 Footprint::from_mel::<(InheritancePriority, T::AccountId)>()
975 }
976
977 pub fn inheritor_ticket(who: &T::AccountId) -> Result<InheritorTicketOf<T>, DispatchError> {
978 InheritorTicketOf::<T>::new(&who, Self::inheritor_footprint())
979 }
980
981 pub fn friend_group_of(
982 lost: &T::AccountId,
983 friend_group_index: FriendGroupIndex,
984 ) -> Result<FriendGroupOf<T>, Error<T>> {
985 let friend_groups = match FriendGroups::<T>::get(lost) {
986 Some((g, _t)) => g,
987 None => return Err(Error::<T>::NoFriendGroups),
988 };
989 friend_groups
990 .get(friend_group_index as usize)
991 .cloned()
992 .ok_or(Error::<T>::NotFriendGroup)
993 }
994
995 pub fn attempt_of(
996 lost: &T::AccountId,
997 friend_group_index: FriendGroupIndex,
998 ) -> Result<(AttemptOf<T>, AttemptTicketOf<T>, SecurityDepositOf<T>), Error<T>> {
999 pallet::Attempt::<T>::get(lost, friend_group_index).ok_or(Error::<T>::NotAttempt)
1000 }
1001
1002 pub fn bound_friend_groups(
1004 lost: &T::AccountId,
1005 mut friend_groups: Vec<FriendGroupOf<T>>,
1006 ) -> Result<FriendGroupsOf<T>, Error<T>> {
1007 for friend_group in &mut friend_groups {
1008 ensure!(!friend_group.friends.is_empty(), Error::<T>::NoFriends);
1009 ensure!(!friend_group.friends.contains(&lost), Error::<T>::LostAccountInFriendGroup);
1011 ensure!(
1012 friend_group.friends.windows(2).all(|w| w[0] < w[1]),
1013 Error::<T>::FriendsNotSortedOrUnique
1014 );
1015 ensure!(
1016 friend_group.friends_needed as usize <= friend_group.friends.len(),
1017 Error::<T>::TooManyFriendsNeeded
1018 );
1019 ensure!(friend_group.friends_needed > 0, Error::<T>::NoFriendsNeeded);
1020 ensure!(!friend_group.cancel_delay.is_zero(), Error::<T>::NoCancelDelay);
1022 }
1023
1024 for (i, group_a) in friend_groups.iter().enumerate() {
1025 for group_b in friend_groups.iter().skip(i + 1) {
1026 ensure!(group_a.friends != group_b.friends, Error::<T>::DuplicateFriendGroups);
1027 }
1028 }
1029
1030 friend_groups.try_into().map_err(|_| Error::<T>::TooManyFriendGroups)
1031 }
1032
1033 fn handle_slash(who: &T::AccountId, amount: SecurityDepositOf<T>) {
1035 let (credit, missing) =
1036 T::Currency::slash(&HoldReason::SecurityDeposit.into(), who, amount);
1037 if !missing.is_zero() {
1038 defensive!("could not slash full security deposit");
1039 }
1040 T::Slash::on_unbalanced(credit);
1041 }
1042}