1#![recursion_limit = "1024"]
102#![cfg_attr(not(feature = "std"), no_std)]
103extern crate alloc;
104use alloc::{boxed::Box, vec, vec::Vec};
105
106#[cfg(any(feature = "try-runtime", test))]
107use frame::traits::fungible::hold::Inspect as _;
108use frame::{
109 prelude::*,
110 traits::{
111 fungible::{hold::Balanced, Credit, Inspect, MutateHold},
112 Consideration, Footprint, OnUnbalanced, OriginTrait,
113 },
114};
115use types::{Bitfield, IdentifiedConsideration};
116
117pub use pallet::*;
118pub use weights::WeightInfo;
119
120#[cfg(feature = "runtime-benchmarks")]
121mod benchmarking;
122pub mod migrations;
123#[cfg(test)]
124mod mock;
125#[cfg(test)]
126mod tests;
127pub mod types;
128pub mod weights;
129
130pub const MAX_GROUPS_PER_ACCOUNT: u32 = 10;
132
133pub type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
134pub type BalanceOf<T> = <<T as Config>::Currency as Inspect<AccountIdFor<T>>>::Balance;
135pub type CreditOf<T> = Credit<AccountIdFor<T>, <T as Config>::Currency>;
136pub type ProvidedBlockNumberOf<T> =
138 <<T as Config>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
139
140pub type FriendsOf<T> =
142 BoundedVec<<T as frame_system::Config>::AccountId, <T as Config>::MaxFriendsPerConfig>;
143pub type HashOf<T> = <T as frame_system::Config>::Hash;
144
145#[derive(
147 Clone,
148 Eq,
149 PartialEq,
150 Encode,
151 Decode,
152 Default,
153 Debug,
154 TypeInfo,
155 MaxEncodedLen,
156 DecodeWithMemTracking,
157)]
158pub struct FriendGroup<ProvidedBlockNumber, AccountId, Friends> {
159 pub friends: Friends,
161
162 pub friends_needed: u32,
164
165 pub inheritor: AccountId,
167
168 pub inheritance_delay: ProvidedBlockNumber,
172
173 pub inheritance_priority: InheritancePriority,
181
182 pub cancel_delay: ProvidedBlockNumber,
189}
190
191pub type FriendGroupIndex = u32;
193
194pub type InheritancePriority = u32;
198
199pub type FriendGroupOf<T> = FriendGroup<ProvidedBlockNumberOf<T>, AccountIdFor<T>, FriendsOf<T>>;
201
202pub type FriendGroupsOf<T> = BoundedVec<FriendGroupOf<T>, ConstU32<MAX_GROUPS_PER_ACCOUNT>>;
204
205pub type ApprovalBitfield<MaxFriends> = Bitfield<MaxFriends>;
207
208pub type ApprovalBitfieldOf<T> = ApprovalBitfield<<T as Config>::MaxFriendsPerConfig>;
210
211#[derive(
213 Clone,
214 Eq,
215 PartialEq,
216 Encode,
217 Decode,
218 Default,
219 Debug,
220 TypeInfo,
221 MaxEncodedLen,
222 DecodeWithMemTracking,
223)]
224pub struct Attempt<ProvidedBlockNumber, ApprovalBitfield, AccountId> {
225 pub friend_group_index: FriendGroupIndex,
229
230 pub initiator: AccountId,
232
233 pub init_block: ProvidedBlockNumber,
237
238 pub last_approval_block: ProvidedBlockNumber,
242
243 pub approvals: ApprovalBitfield,
248}
249
250pub type AttemptOf<T> = Attempt<ProvidedBlockNumberOf<T>, ApprovalBitfieldOf<T>, AccountIdFor<T>>;
252
253pub type AttemptTicketOf<T> =
255 IdentifiedConsideration<AccountIdFor<T>, Footprint, <T as Config>::AttemptConsideration>;
256
257pub type InheritorTicketOf<T> =
259 IdentifiedConsideration<AccountIdFor<T>, Footprint, <T as Config>::InheritorConsideration>;
260
261pub type SecurityDepositOf<T> = BalanceOf<T>;
263
264#[frame::pallet]
265pub mod pallet {
266 use super::*;
267
268 #[pallet::pallet]
269 #[pallet::storage_version(migrations::STORAGE_VERSION)]
270 pub struct Pallet<T>(_);
271
272 #[pallet::config]
273 pub trait Config: frame_system::Config {
274 type RuntimeCall: Parameter
276 + Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
277 + GetDispatchInfo
278 + From<frame_system::Call<Self>>
279 + IsSubType<Call<Self>>
280 + IsType<<Self as frame_system::Config>::RuntimeCall>;
281
282 type RuntimeHoldReason: Parameter
284 + Member
285 + MaxEncodedLen
286 + Copy
287 + VariantCount
288 + From<HoldReason>;
289
290 type BlockNumberProvider: BlockNumberProvider;
313
314 #[cfg(not(feature = "runtime-benchmarks"))]
316 type Currency: MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>
317 + Balanced<Self::AccountId>;
318 #[cfg(feature = "runtime-benchmarks")]
319 type Currency: MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>
320 + Balanced<Self::AccountId>
321 + frame::traits::fungible::Mutate<Self::AccountId>;
322
323 type FriendGroupsConsideration: Consideration<Self::AccountId, Footprint>;
325
326 type AttemptConsideration: Consideration<Self::AccountId, Footprint>;
328
329 type InheritorConsideration: Consideration<Self::AccountId, Footprint>;
331
332 #[pallet::constant]
334 type SecurityDeposit: Get<BalanceOf<Self>>;
335
336 type Slash: OnUnbalanced<CreditOf<Self>>;
341
342 #[pallet::constant]
346 type MaxFriendsPerConfig: Get<u32>;
347
348 type WeightInfo: WeightInfo;
350 }
351
352 #[pallet::storage]
356 pub type FriendGroups<T: Config> = StorageMap<
357 _,
358 Blake2_128Concat,
359 T::AccountId,
360 (FriendGroupsOf<T>, T::FriendGroupsConsideration),
361 >;
362
363 #[pallet::storage]
365 pub type Attempt<T: Config> = StorageDoubleMap<
366 _,
367 Blake2_128Concat,
368 T::AccountId,
369 Blake2_128Concat,
370 FriendGroupIndex,
371 (AttemptOf<T>, AttemptTicketOf<T>, SecurityDepositOf<T>),
372 >;
373
374 #[pallet::storage]
380 pub type Inheritor<T: Config> = StorageMap<
381 _,
382 Blake2_128Concat,
383 T::AccountId,
384 (InheritancePriority, T::AccountId, InheritorTicketOf<T>),
385 >;
386
387 #[pallet::composite_enum]
388 pub enum HoldReason {
389 #[codec(index = 0)]
391 FriendGroupsStorage,
392
393 #[codec(index = 1)]
395 AttemptStorage,
396
397 #[codec(index = 2)]
399 InheritorStorage,
400
401 #[codec(index = 3)]
403 SecurityDeposit,
404 }
405
406 #[pallet::event]
407 #[pallet::generate_deposit(pub(super) fn deposit_event)]
408 pub enum Event<T: Config> {
409 AttemptApproved {
411 lost: T::AccountId,
412 friend_group_index: FriendGroupIndex,
413 friend: T::AccountId,
414 },
415 AttemptCanceled {
417 lost: T::AccountId,
418 friend_group_index: FriendGroupIndex,
419 canceler: T::AccountId,
420 },
421 AttemptInitiated {
423 lost: T::AccountId,
424 friend_group_index: FriendGroupIndex,
425 initiator: T::AccountId,
426 },
427 AttemptFinished {
429 lost: T::AccountId,
430 friend_group_index: FriendGroupIndex,
431 inheritor: T::AccountId,
432 previous_inheritor: Option<T::AccountId>,
433 },
434 AttemptDiscarded {
440 lost: T::AccountId,
441 friend_group_index: FriendGroupIndex,
442 existing_inheritor: T::AccountId,
443 },
444 AttemptSlashed { lost: T::AccountId, friend_group_index: FriendGroupIndex },
448 FriendGroupsChanged { lost: T::AccountId },
450 InheritorRevoked { lost: T::AccountId },
452 RecoveredAccountControlled {
456 recovered: T::AccountId,
457 inheritor: T::AccountId,
458 call_hash: HashOf<T>,
459 call_result: DispatchResult,
460 },
461 }
462
463 #[pallet::error]
464 pub enum Error<T> {
465 AlreadyApproved,
467 AlreadyInitiated,
469 AlreadyVoted,
471 HasOngoingAttempts,
473 LostAccountInFriendGroup,
475 HigherPriorityRecovered,
477 NoCancelDelay,
479 NoFriendGroups,
481 NoFriends,
483 NoInheritor,
485 NotApproved,
487 NotAttempt,
489 NotCanceller,
491 NotFriend,
493 NotFriendGroup,
495 NotInheritor,
497 NotYetCancelable,
499 NotYetInheritable,
501 TooManyFriendGroups,
503 TooManyFriendsNeeded,
505 NoFriendsNeeded,
507 FriendsNotSortedOrUnique,
509 DuplicateFriendGroups,
511 }
512
513 #[pallet::view_functions]
514 impl<T: Config> Pallet<T> {
515 pub fn provided_block_number() -> ProvidedBlockNumberOf<T> {
517 T::BlockNumberProvider::current_block_number()
518 }
519
520 pub fn friend_groups(lost: T::AccountId) -> Vec<FriendGroupOf<T>> {
522 FriendGroups::<T>::get(lost).map(|(g, _t)| g.into_inner()).unwrap_or_default()
523 }
524
525 pub fn attempts(lost: T::AccountId) -> Vec<(FriendGroupOf<T>, AttemptOf<T>)> {
527 Attempt::<T>::iter_prefix(&lost)
528 .filter_map(|(friend_group_index, (attempt, _ticket, _deposit))| {
529 let friend_group = Self::friend_group_of(&lost, friend_group_index).ok()?;
530 Some((friend_group, attempt))
531 })
532 .collect()
533 }
534
535 pub fn inheritor(lost: T::AccountId) -> Option<T::AccountId> {
537 Inheritor::<T>::get(lost).map(|(_, inheritor, _)| inheritor)
538 }
539
540 pub fn inheritance(heir: T::AccountId) -> Vec<T::AccountId> {
542 let mut inheritance = Vec::new();
543
544 for (recovered, (_, inheritor, _)) in Inheritor::<T>::iter() {
545 if inheritor != heir {
546 continue;
547 }
548 let Err(pos) = inheritance.binary_search(&recovered) else { continue };
549
550 inheritance.insert(pos, recovered);
551 }
552
553 inheritance
554 }
555 }
556
557 #[pallet::call]
558 impl<T: Config> Pallet<T> {
559 #[pallet::call_index(0)]
565 #[pallet::weight({
566 let di = call.get_dispatch_info();
567 (T::WeightInfo::control_inherited_account().saturating_add(di.call_weight), di.class)
568 })]
569 pub fn control_inherited_account(
570 origin: OriginFor<T>,
571 recovered: AccountIdLookupOf<T>,
572 call: Box<<T as Config>::RuntimeCall>,
573 ) -> DispatchResult {
574 let maybe_inheritor = ensure_signed(origin)?;
575 let recovered = T::Lookup::lookup(recovered)?;
576
577 let inheritor = Inheritor::<T>::get(&recovered)
578 .map(|(_, inheritor, _ticket)| inheritor)
579 .ok_or(Error::<T>::NoInheritor)?;
580 ensure!(maybe_inheritor == inheritor, Error::<T>::NotInheritor);
581
582 let mut origin: T::RuntimeOrigin =
583 frame_system::RawOrigin::Signed(recovered.clone()).into();
584 origin.add_filter(|c: &<T as frame_system::Config>::RuntimeCall| {
586 let c = <T as Config>::RuntimeCall::from_ref(c);
587 c.is_sub_type().is_none()
588 });
589
590 let call_hash = call.using_encoded(&T::Hashing::hash);
591 let call_result = call.dispatch(origin).map(|_| ()).map_err(|r| r.error);
592
593 Self::deposit_event(Event::<T>::RecoveredAccountControlled {
594 recovered,
595 inheritor,
596 call_hash,
597 call_result,
598 });
599
600 Ok(())
603 }
604
605 #[pallet::call_index(1)]
610 #[pallet::weight(T::WeightInfo::revoke_inheritor())]
611 pub fn revoke_inheritor(origin: OriginFor<T>) -> DispatchResult {
612 let lost = ensure_signed(origin)?;
613
614 let (_priority, _inheritor, ticket) =
615 Inheritor::<T>::take(&lost).ok_or(Error::<T>::NoInheritor)?;
616
617 let _: Result<(), DispatchError> = ticket.try_drop().defensive();
618
619 Self::deposit_event(Event::<T>::InheritorRevoked { lost });
620
621 Ok(())
622 }
623
624 #[pallet::call_index(2)]
633 #[pallet::weight(T::WeightInfo::set_friend_groups())]
634 pub fn set_friend_groups(
635 origin: OriginFor<T>,
636 friend_groups: Vec<FriendGroupOf<T>>,
637 ) -> DispatchResult {
638 let lost = ensure_signed(origin)?;
639
640 if Attempt::<T>::iter_prefix(&lost).next().is_some() {
641 return Err(Error::<T>::HasOngoingAttempts.into());
642 }
643
644 let (old_friend_groups, old_ticket) = match FriendGroups::<T>::get(&lost) {
645 Some((g, t)) => (g, Some(t)),
646 None => Default::default(),
647 };
648
649 let new_friend_groups = Self::bound_friend_groups(&lost, friend_groups)?;
650
651 if new_friend_groups.is_empty() {
653 if let Some(old_ticket) = old_ticket {
654 old_ticket.drop(&lost)?;
655 }
656 FriendGroups::<T>::remove(&lost);
657 if !old_friend_groups.is_empty() {
658 Self::deposit_event(Event::<T>::FriendGroupsChanged { lost });
659 }
660 return Ok(());
661 }
662
663 let new_footprint = Self::friend_group_footprint(&new_friend_groups);
664 let new_ticket = if let Some(old_ticket) = old_ticket {
665 old_ticket.update(&lost, new_footprint)?
666 } else {
667 T::FriendGroupsConsideration::new(&lost, new_footprint)?
668 };
669 FriendGroups::<T>::insert(&lost, (&new_friend_groups, &new_ticket));
670
671 if new_friend_groups != old_friend_groups {
672 Self::deposit_event(Event::<T>::FriendGroupsChanged { lost });
673 }
674
675 Ok(())
676 }
677
678 #[pallet::call_index(3)]
688 #[pallet::weight(T::WeightInfo::initiate_attempt())]
689 pub fn initiate_attempt(
690 origin: OriginFor<T>,
691 lost: AccountIdLookupOf<T>,
692 friend_group_index: FriendGroupIndex,
693 ) -> DispatchResult {
694 let initiator = ensure_signed(origin)?;
695 let lost = T::Lookup::lookup(lost)?;
696
697 if Self::attempt_of(&lost, friend_group_index).is_ok() {
698 return Err(Error::<T>::AlreadyInitiated.into());
699 }
700
701 let friend_group = Self::friend_group_of(&lost, friend_group_index)?;
702 let initiator_index = friend_group
703 .friends
704 .iter()
705 .position(|f| f == &initiator)
706 .ok_or(Error::<T>::NotFriend)?;
707
708 if let Some((inheritance_priority, _, _)) = Inheritor::<T>::get(&lost) {
709 ensure!(
710 friend_group.inheritance_priority < inheritance_priority,
711 Error::<T>::HigherPriorityRecovered
712 );
713 }
714
715 let approvals = ApprovalBitfield::default()
717 .with_bits([initiator_index])
718 .defensive_proof("initiator_index < friends.len() <= MaxFriendsPerConfig; qed")
719 .unwrap_or_default();
720
721 let now = T::BlockNumberProvider::current_block_number();
722 let attempt = AttemptOf::<T> {
723 friend_group_index,
724 initiator: initiator.clone(),
725 init_block: now,
726 last_approval_block: now,
727 approvals,
728 };
729
730 let deposit = T::SecurityDeposit::get();
731 let () = T::Currency::hold(&HoldReason::SecurityDeposit.into(), &initiator, deposit)?;
732
733 let ticket = AttemptTicketOf::<T>::new(&initiator, Self::attempt_footprint())?;
734 Attempt::<T>::insert(&lost, friend_group_index, (&attempt, &ticket, &deposit));
735
736 Self::deposit_event(Event::<T>::AttemptInitiated {
737 lost: lost.clone(),
738 friend_group_index,
739 initiator: initiator.clone(),
740 });
741 Self::deposit_event(Event::<T>::AttemptApproved {
742 lost,
743 friend_group_index,
744 friend: initiator,
745 });
746
747 Ok(())
748 }
749
750 #[pallet::call_index(4)]
756 #[pallet::weight(T::WeightInfo::approve_attempt())]
757 pub fn approve_attempt(
758 origin: OriginFor<T>,
759 lost: AccountIdLookupOf<T>,
760 friend_group_index: FriendGroupIndex,
761 ) -> DispatchResult {
762 let friend = ensure_signed(origin)?;
763 let lost = T::Lookup::lookup(lost)?;
764 let now = T::BlockNumberProvider::current_block_number();
765
766 let (mut attempt, ticket, deposit) = Self::attempt_of(&lost, friend_group_index)?;
767 let friend_group = Self::friend_group_of(&lost, friend_group_index).defensive()?;
768
769 let friend_index = friend_group
770 .friends
771 .iter()
772 .position(|f| f == &friend)
773 .ok_or(Error::<T>::NotFriend)?;
774
775 let friends_voted = attempt.approvals.count_ones();
776 ensure!(friends_voted < friend_group.friends_needed, Error::<T>::AlreadyApproved);
777 attempt.last_approval_block = now;
778
779 attempt
780 .approvals
781 .set_if_not_set(friend_index)
782 .map_err(|_| Error::<T>::AlreadyVoted)?;
783
784 Attempt::<T>::insert(&lost, friend_group_index, (&attempt, &ticket, &deposit));
786
787 Self::deposit_event(Event::<T>::AttemptApproved { lost, friend_group_index, friend });
788
789 Ok(())
790 }
791
792 #[pallet::call_index(5)]
796 #[pallet::weight(T::WeightInfo::finish_attempt())]
797 pub fn finish_attempt(
798 origin: OriginFor<T>,
799 lost: AccountIdLookupOf<T>,
800 friend_group_index: FriendGroupIndex,
801 ) -> DispatchResult {
802 let caller = ensure_signed(origin)?;
803 let lost = T::Lookup::lookup(lost)?;
804 let now = T::BlockNumberProvider::current_block_number();
805
806 let (attempt, attempts_ticket, deposit) =
807 Attempt::<T>::take(&lost, &friend_group_index).ok_or(Error::<T>::NotAttempt)?;
808
809 let _: Result<(), DispatchError> = attempts_ticket.try_drop().defensive();
811 let _: Result<BalanceOf<T>, DispatchError> = T::Currency::release(
812 &HoldReason::SecurityDeposit.into(),
813 &attempt.initiator,
814 deposit,
815 Precision::BestEffort,
816 )
817 .defensive();
818
819 let friend_group = Self::friend_group_of(&lost, friend_group_index).defensive()?;
820
821 let approvals = attempt.approvals.count_ones();
823 ensure!(
824 approvals >= friend_group.friends_needed,
826 Error::<T>::NotApproved
827 );
828
829 let inheritable_at = attempt
830 .init_block
831 .checked_add(&friend_group.inheritance_delay)
832 .ok_or(ArithmeticError::Overflow)?;
833 ensure!(now >= inheritable_at, Error::<T>::NotYetInheritable);
834 let inheritor = friend_group.inheritor;
838 let inheritance_priority = friend_group.inheritance_priority;
839
840 match Inheritor::<T>::get(&lost) {
841 None => {
842 let ticket = Self::inheritor_ticket(&caller)?;
843 Inheritor::<T>::insert(&lost, (inheritance_priority, &inheritor, ticket));
844 Self::deposit_event(Event::<T>::AttemptFinished {
845 lost,
846 friend_group_index,
847 inheritor,
848 previous_inheritor: None,
849 });
850 },
851 Some((old_priority, old_inheritor, ticket))
853 if inheritance_priority < old_priority =>
854 {
855 let ticket = ticket.update(&caller, Self::inheritor_footprint())?;
856 Inheritor::<T>::insert(&lost, (inheritance_priority, &inheritor, ticket));
857 Self::deposit_event(Event::<T>::AttemptFinished {
858 lost,
859 friend_group_index,
860 inheritor,
861 previous_inheritor: Some(old_inheritor),
862 });
863 },
864 Some((_, existing_inheritor, _)) => {
865 Self::deposit_event(Event::<T>::AttemptDiscarded {
868 lost,
869 friend_group_index,
870 existing_inheritor,
871 });
872 },
873 };
874
875 Ok(())
876 }
877
878 #[pallet::call_index(6)]
884 #[pallet::weight(T::WeightInfo::cancel_attempt())]
885 pub fn cancel_attempt(
886 origin: OriginFor<T>,
887 lost: AccountIdLookupOf<T>,
888 friend_group_index: FriendGroupIndex,
889 ) -> DispatchResult {
890 let canceler = ensure_signed(origin)?;
891 let lost = T::Lookup::lookup(lost)?;
892 let now = T::BlockNumberProvider::current_block_number();
893
894 let (attempt, ticket, deposit) =
895 Attempt::<T>::take(&lost, &friend_group_index).ok_or(Error::<T>::NotAttempt)?;
896
897 ensure!(canceler == attempt.initiator || canceler == lost, Error::<T>::NotCanceller);
898
899 let _ignored = ticket.try_drop().defensive();
901 let _: Result<BalanceOf<T>, DispatchError> = T::Currency::release(
902 &HoldReason::SecurityDeposit.into(),
903 &attempt.initiator,
904 deposit,
905 Precision::BestEffort,
906 )
907 .defensive();
908
909 let friend_group = Self::friend_group_of(&lost, friend_group_index).defensive()?;
910
911 if canceler != lost {
912 let cancelable_at = attempt
913 .last_approval_block
914 .checked_add(&friend_group.cancel_delay)
915 .ok_or(ArithmeticError::Overflow)?;
916 ensure!(now >= cancelable_at, Error::<T>::NotYetCancelable);
917 }
918 Self::deposit_event(Event::<T>::AttemptCanceled { lost, friend_group_index, canceler });
923
924 Ok(())
925 }
926
927 #[pallet::call_index(7)]
929 #[pallet::weight(T::WeightInfo::slash_attempt())]
930 pub fn slash_attempt(
931 origin: OriginFor<T>,
932 friend_group_index: FriendGroupIndex,
933 ) -> DispatchResult {
934 let lost = ensure_signed(origin)?;
935
936 let (attempt, ticket, deposit) =
937 Attempt::<T>::take(&lost, &friend_group_index).ok_or(Error::<T>::NotAttempt)?;
938
939 let _: Result<(), DispatchError> = ticket.try_drop().defensive();
940 Self::handle_slash(&attempt.initiator, deposit);
941
942 Self::deposit_event(Event::<T>::AttemptSlashed { lost, friend_group_index });
943
944 Ok(())
945 }
946 }
947
948 #[pallet::hooks]
949 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
950 fn integrity_test() {
951 assert!(
952 T::MaxFriendsPerConfig::get() > 0,
953 "MaxFriendsPerConfig must be greater than 0"
954 );
955
956 let bitfield = ApprovalBitfieldOf::<T>::default();
957 assert!(bitfield.0.len() >= 1, "Default works");
958 }
959
960 #[cfg(feature = "try-runtime")]
961 fn try_state(
962 _n: BlockNumberFor<T>,
963 ) -> Result<(), frame::deps::sp_runtime::TryRuntimeError> {
964 Pallet::<T>::do_try_state()
965 }
966 }
967}
968
969impl<T: Config> Pallet<T> {
970 pub fn friend_group_footprint(friend_groups: &FriendGroupsOf<T>) -> Footprint {
971 if friend_groups.is_empty() {
972 defensive!("Do not call with empty friend groups");
973 }
974
975 Footprint::from_encodable(friend_groups)
976 }
977
978 pub fn attempt_footprint() -> Footprint {
979 Footprint::from_mel::<AttemptOf<T>>()
980 }
981
982 pub fn inheritor_footprint() -> Footprint {
983 Footprint::from_mel::<(InheritancePriority, T::AccountId)>()
984 }
985
986 pub fn inheritor_ticket(who: &T::AccountId) -> Result<InheritorTicketOf<T>, DispatchError> {
987 InheritorTicketOf::<T>::new(&who, Self::inheritor_footprint())
988 }
989
990 pub fn friend_group_of(
991 lost: &T::AccountId,
992 friend_group_index: FriendGroupIndex,
993 ) -> Result<FriendGroupOf<T>, Error<T>> {
994 let friend_groups = match FriendGroups::<T>::get(lost) {
995 Some((g, _t)) => g,
996 None => return Err(Error::<T>::NoFriendGroups),
997 };
998 friend_groups
999 .get(friend_group_index as usize)
1000 .cloned()
1001 .ok_or(Error::<T>::NotFriendGroup)
1002 }
1003
1004 pub fn attempt_of(
1005 lost: &T::AccountId,
1006 friend_group_index: FriendGroupIndex,
1007 ) -> Result<(AttemptOf<T>, AttemptTicketOf<T>, SecurityDepositOf<T>), Error<T>> {
1008 pallet::Attempt::<T>::get(lost, friend_group_index).ok_or(Error::<T>::NotAttempt)
1009 }
1010
1011 pub fn bound_friend_groups(
1013 lost: &T::AccountId,
1014 mut friend_groups: Vec<FriendGroupOf<T>>,
1015 ) -> Result<FriendGroupsOf<T>, Error<T>> {
1016 for friend_group in &mut friend_groups {
1017 ensure!(!friend_group.friends.is_empty(), Error::<T>::NoFriends);
1018 ensure!(!friend_group.friends.contains(&lost), Error::<T>::LostAccountInFriendGroup);
1020 ensure!(
1021 friend_group.friends.windows(2).all(|w| w[0] < w[1]),
1022 Error::<T>::FriendsNotSortedOrUnique
1023 );
1024 ensure!(
1025 friend_group.friends_needed as usize <= friend_group.friends.len(),
1026 Error::<T>::TooManyFriendsNeeded
1027 );
1028 ensure!(friend_group.friends_needed > 0, Error::<T>::NoFriendsNeeded);
1029 ensure!(!friend_group.cancel_delay.is_zero(), Error::<T>::NoCancelDelay);
1031 }
1032
1033 for (i, group_a) in friend_groups.iter().enumerate() {
1034 for group_b in friend_groups.iter().skip(i + 1) {
1035 ensure!(group_a.friends != group_b.friends, Error::<T>::DuplicateFriendGroups);
1036 }
1037 }
1038
1039 friend_groups.try_into().map_err(|_| Error::<T>::TooManyFriendGroups)
1040 }
1041
1042 fn handle_slash(who: &T::AccountId, amount: SecurityDepositOf<T>) {
1044 let (credit, missing) =
1045 T::Currency::slash(&HoldReason::SecurityDeposit.into(), who, amount);
1046 if !missing.is_zero() {
1047 defensive!("could not slash full security deposit");
1048 }
1049 T::Slash::on_unbalanced(credit);
1050 }
1051
1052 #[cfg(any(feature = "try-runtime", test))]
1066 pub(crate) fn do_try_state() -> Result<(), frame::deps::sp_runtime::TryRuntimeError> {
1067 for (lost, friend_group_index, (attempt, ticket, _deposit)) in pallet::Attempt::<T>::iter()
1068 {
1069 let friend_groups_entry = FriendGroups::<T>::get(&lost)
1071 .ok_or("Attempt without matching FriendGroups entry")?;
1072 let friend_groups = friend_groups_entry.0;
1073
1074 let friend_group = friend_groups
1076 .get(friend_group_index as usize)
1077 .ok_or("Attempt friend_group_index out of range")?;
1078
1079 let friends_len = friend_group.friends.len();
1080
1081 if attempt.approvals.iter_ones().any(|index| index >= friends_len) {
1083 return Err("Attempt approvals has a bit set past friends.len()".into());
1084 }
1085
1086 let count = attempt.approvals.count_ones();
1088 if count > friend_group.friends_needed {
1089 return Err("Attempt approvals count exceeds friends_needed".into());
1090 }
1091
1092 if attempt.init_block > attempt.last_approval_block {
1094 return Err("Attempt init_block is later than last_approval_block".into());
1095 }
1096
1097 if ticket.ticket.is_some() &&
1099 T::Currency::balance_on_hold(
1100 &HoldReason::AttemptStorage.into(),
1101 &ticket.depositor,
1102 )
1103 .is_zero()
1104 {
1105 return Err(
1106 "Attempt ticket exists but depositor has zero AttemptStorage hold".into()
1107 );
1108 }
1109 if T::Currency::balance_on_hold(&HoldReason::SecurityDeposit.into(), &attempt.initiator)
1111 .is_zero()
1112 {
1113 return Err("Attempt has zero SecurityDeposit hold on initiator".into());
1114 }
1115 }
1116
1117 for (lost, (friend_groups, _consideration)) in FriendGroups::<T>::iter() {
1118 if !friend_groups.is_empty() &&
1121 T::Currency::balance_on_hold(&HoldReason::FriendGroupsStorage.into(), &lost)
1122 .is_zero()
1123 {
1124 return Err("FriendGroups entry has zero FriendGroupsStorage hold on lost".into());
1125 }
1126 }
1127
1128 for (lost, (_priority, inheritor, ticket)) in Inheritor::<T>::iter() {
1129 if inheritor == lost {
1131 return Err("Inheritor entry has inheritor == lost".into());
1132 }
1133
1134 if ticket.ticket.is_some() &&
1136 T::Currency::balance_on_hold(
1137 &HoldReason::InheritorStorage.into(),
1138 &ticket.depositor,
1139 )
1140 .is_zero()
1141 {
1142 return Err(
1143 "Inheritor ticket exists but depositor has zero InheritorStorage hold".into()
1144 );
1145 }
1146 }
1147
1148 Ok(())
1149 }
1150}