referrerpolicy=no-referrer-when-downgrade

pallet_recovery/
benchmarking.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18#![cfg(feature = "runtime-benchmarks")]
19
20extern crate alloc;
21
22use super::*;
23use crate::Pallet;
24use frame::{benchmarking::prelude::*, traits::fungible::Mutate};
25
26const SEED: u32 = 0;
27
28fn assert_last_event<T: Config>(generic_event: crate::Event<T>) {
29	frame_system::Pallet::<T>::assert_last_event(generic_event.into());
30}
31
32fn fund_account<T: Config>(who: &T::AccountId) {
33	let balance = BalanceOf::<T>::max_value() / 100000u32.into();
34	T::Currency::mint_into(who, balance).expect("funding account");
35}
36
37fn generate_friends<T: Config>(seed: u32, num: u32) -> Vec<T::AccountId> {
38	let mut friends = (0..num).map(|x| account("friend", x, seed)).collect::<Vec<_>>();
39	friends.sort();
40
41	for friend in &friends {
42		fund_account::<T>(friend);
43	}
44
45	friends
46}
47
48/// Returns the last friend in the sorted friends list (worst case for O(n) lookups).
49fn last_friend<T: Config>(seed: u32, num: u32) -> T::AccountId {
50	let friends = generate_friends::<T>(seed, num);
51	friends.into_iter().last().expect("at least one friend")
52}
53
54fn create_friend_group<T: Config>(
55	seed: u32,
56	num_friends: u32,
57	threshold: u32,
58	inheritance_priority: InheritancePriority,
59	inheritance_delay: ProvidedBlockNumberOf<T>,
60	cancel_delay: ProvidedBlockNumberOf<T>,
61) -> FriendGroupOf<T> {
62	let friends = generate_friends::<T>(num_friends * seed + 1, num_friends);
63	let inheritor: T::AccountId = account("inheritor", inheritance_priority, SEED);
64	fund_account::<T>(&inheritor);
65
66	FriendGroupOf::<T> {
67		friends: friends.try_into().unwrap(),
68		friends_needed: threshold,
69		inheritor,
70		inheritance_delay,
71		inheritance_priority,
72		cancel_delay,
73	}
74}
75
76fn create_friend_groups<T: Config>(num_friends: u32, seed: u32) -> FriendGroupsOf<T> {
77	let mut friend_groups = Vec::new();
78
79	for i in 0..MAX_GROUPS_PER_ACCOUNT {
80		friend_groups.push(create_friend_group::<T>(
81			seed + i,
82			num_friends,
83			1,
84			0,
85			10u32.into(),
86			10u32.into(),
87		));
88	}
89
90	friend_groups.try_into().unwrap()
91}
92
93fn setup_friend_groups<T: Config>(
94	lost: &T::AccountId,
95	num_friends: u32,
96	seed: u32,
97) -> FriendGroupsOf<T> {
98	let friend_groups = create_friend_groups::<T>(num_friends, seed);
99
100	let footprint = Pallet::<T>::friend_group_footprint(&friend_groups);
101	let ticket = T::FriendGroupsConsideration::new(lost, footprint).unwrap();
102	FriendGroups::<T>::insert(lost, (&friend_groups, ticket));
103
104	friend_groups
105}
106
107#[benchmarks]
108mod benchmarks {
109	use super::*;
110
111	#[benchmark]
112	fn control_inherited_account() {
113		let inheritor: T::AccountId = whitelisted_caller();
114		let recovered: T::AccountId = account("recovered", 0, SEED);
115		let recovered_lookup = T::Lookup::unlookup(recovered.clone());
116
117		fund_account::<T>(&inheritor);
118
119		let ticket = Pallet::<T>::inheritor_ticket(&inheritor).unwrap();
120		Inheritor::<T>::insert(&recovered, (0u32, &inheritor, ticket));
121
122		let call: <T as Config>::RuntimeCall =
123			frame_system::Call::<T>::remark { remark: Vec::new() }.into();
124		let call_hash = call.using_encoded(&T::Hashing::hash);
125
126		#[extrinsic_call]
127		_(RawOrigin::Signed(inheritor.clone()), recovered_lookup, Box::new(call));
128
129		assert_last_event::<T>(
130			Event::<T>::RecoveredAccountControlled {
131				recovered,
132				inheritor,
133				call_hash,
134				call_result: Ok(()),
135			}
136			.into(),
137		);
138	}
139
140	#[benchmark]
141	fn revoke_inheritor() {
142		let lost: T::AccountId = whitelisted_caller();
143		let depositor: T::AccountId = account("depositor", 0, SEED);
144
145		fund_account::<T>(&lost);
146		fund_account::<T>(&depositor);
147
148		let ticket = Pallet::<T>::inheritor_ticket(&depositor).unwrap();
149		Inheritor::<T>::insert(
150			&lost,
151			(0u32, &account::<T::AccountId>("inheritor", 0, SEED), ticket),
152		);
153
154		#[extrinsic_call]
155		_(RawOrigin::Signed(lost.clone()));
156
157		assert_last_event::<T>(Event::<T>::InheritorRevoked { lost }.into());
158	}
159
160	#[benchmark]
161	fn set_friend_groups() {
162		let lost: T::AccountId = whitelisted_caller();
163		fund_account::<T>(&lost);
164
165		let _ = setup_friend_groups::<T>(&lost, T::MaxFriendsPerConfig::get(), 0);
166		let new_friend_groups =
167			create_friend_groups::<T>(T::MaxFriendsPerConfig::get(), 1).into_inner();
168
169		#[extrinsic_call]
170		_(RawOrigin::Signed(lost.clone()), new_friend_groups);
171
172		assert_last_event::<T>(Event::<T>::FriendGroupsChanged { lost }.into());
173	}
174
175	#[benchmark]
176	fn initiate_attempt() {
177		let lost: T::AccountId = whitelisted_caller();
178		let lost_lookup = T::Lookup::unlookup(lost.clone());
179		// Use last friend for worst case O(n) lookup in friends.iter().position().
180		let initiator: T::AccountId = last_friend::<T>(1, T::MaxFriendsPerConfig::get());
181
182		fund_account::<T>(&lost);
183
184		let friend_groups =
185			setup_friend_groups::<T>(&lost, T::MaxFriendsPerConfig::get(), 0).into_inner();
186
187		crate::pallet::Pallet::<T>::set_friend_groups(
188			RawOrigin::Signed(lost.clone()).into(),
189			friend_groups,
190		)
191		.unwrap();
192
193		#[extrinsic_call]
194		_(RawOrigin::Signed(initiator.clone()), lost_lookup, 0);
195
196		assert_last_event::<T>(
197			Event::<T>::AttemptApproved { lost, friend_group_index: 0, friend: initiator }.into(),
198		);
199	}
200
201	#[benchmark]
202	fn approve_attempt() {
203		let lost: T::AccountId = whitelisted_caller();
204		let lost_lookup = T::Lookup::unlookup(lost.clone());
205		// Use last friend for worst case O(n) lookup in friends.iter().position()
206		let friend: T::AccountId = last_friend::<T>(1, T::MaxFriendsPerConfig::get());
207
208		fund_account::<T>(&lost);
209
210		let friend_groups =
211			setup_friend_groups::<T>(&lost, T::MaxFriendsPerConfig::get(), 0).into_inner();
212		crate::pallet::Pallet::<T>::set_friend_groups(
213			RawOrigin::Signed(lost.clone()).into(),
214			friend_groups,
215		)
216		.unwrap();
217		crate::pallet::Pallet::<T>::initiate_attempt(
218			RawOrigin::Signed(friend.clone()).into(),
219			lost_lookup.clone(),
220			0,
221		)
222		.unwrap();
223
224		// Initiation auto-approves the initiator. Reset approvals so this benchmark can still
225		// measure a successful `approve_attempt` call.
226		crate::pallet::Attempt::<T>::mutate(&lost, 0, |maybe_attempt| {
227			let (attempt, _, _) = maybe_attempt.as_mut().expect("attempt exists after initiation");
228			attempt.approvals = ApprovalBitfieldOf::<T>::default();
229		});
230
231		#[extrinsic_call]
232		_(RawOrigin::Signed(friend.clone()), lost_lookup, 0);
233
234		assert_last_event::<T>(
235			Event::<T>::AttemptApproved { lost, friend_group_index: 0, friend }.into(),
236		);
237	}
238
239	#[benchmark]
240	fn finish_attempt() {
241		let lost: T::AccountId = whitelisted_caller();
242		let lost_lookup = T::Lookup::unlookup(lost.clone());
243		// Use last friend for worst case O(n) lookup
244		let initiator: T::AccountId = last_friend::<T>(1, T::MaxFriendsPerConfig::get());
245		let inheritor: T::AccountId = account("inheritor", 0, SEED);
246		let previous_inheritor: T::AccountId = account("old_inheritor", 0, SEED);
247
248		fund_account::<T>(&lost);
249		fund_account::<T>(&previous_inheritor);
250
251		// Set up existing inheritor with higher order (1) to trigger replacement path
252		let ticket = Pallet::<T>::inheritor_ticket(&previous_inheritor).unwrap();
253		Inheritor::<T>::insert(&lost, (1u32, &previous_inheritor, ticket));
254
255		// Friend groups have order 0, which is lower than existing order 1
256		let friend_groups =
257			setup_friend_groups::<T>(&lost, T::MaxFriendsPerConfig::get(), 0).into_inner();
258		crate::pallet::Pallet::<T>::set_friend_groups(
259			RawOrigin::Signed(lost.clone()).into(),
260			friend_groups,
261		)
262		.unwrap();
263		crate::pallet::Pallet::<T>::initiate_attempt(
264			RawOrigin::Signed(initiator.clone()).into(),
265			lost_lookup.clone(),
266			0,
267		)
268		.unwrap();
269		frame_system::Pallet::<T>::set_block_number(100u32.into());
270
271		#[extrinsic_call]
272		_(RawOrigin::Signed(initiator.clone()), lost_lookup, 0);
273
274		assert_last_event::<T>(
275			Event::<T>::AttemptFinished {
276				lost,
277				friend_group_index: 0,
278				inheritor,
279				previous_inheritor: Some(previous_inheritor),
280			}
281			.into(),
282		);
283	}
284
285	#[benchmark]
286	fn cancel_attempt() {
287		let lost: T::AccountId = whitelisted_caller();
288		let lost_lookup = T::Lookup::unlookup(lost.clone());
289		let initiator: T::AccountId = account("friend", 0, 1);
290
291		fund_account::<T>(&lost);
292		fund_account::<T>(&initiator);
293
294		let friend_groups =
295			setup_friend_groups::<T>(&lost, T::MaxFriendsPerConfig::get(), 0).into_inner();
296		crate::pallet::Pallet::<T>::set_friend_groups(
297			RawOrigin::Signed(lost.clone()).into(),
298			friend_groups,
299		)
300		.unwrap();
301		crate::pallet::Pallet::<T>::initiate_attempt(
302			RawOrigin::Signed(initiator.clone()).into(),
303			lost_lookup.clone(),
304			0,
305		)
306		.unwrap();
307		frame_system::Pallet::<T>::set_block_number(100u32.into());
308
309		#[extrinsic_call]
310		_(RawOrigin::Signed(initiator.clone()), lost_lookup, 0);
311
312		assert_last_event::<T>(
313			Event::<T>::AttemptCanceled { lost, friend_group_index: 0, canceler: initiator }.into(),
314		);
315	}
316
317	#[benchmark]
318	fn slash_attempt() {
319		let lost: T::AccountId = whitelisted_caller();
320		let lost_lookup = T::Lookup::unlookup(lost.clone());
321		let initiator: T::AccountId = account("friend", 0, 1);
322
323		fund_account::<T>(&lost);
324		fund_account::<T>(&initiator);
325
326		let friend_groups =
327			setup_friend_groups::<T>(&lost, T::MaxFriendsPerConfig::get(), 0).into_inner();
328		crate::pallet::Pallet::<T>::set_friend_groups(
329			RawOrigin::Signed(lost.clone()).into(),
330			friend_groups,
331		)
332		.unwrap();
333		crate::pallet::Pallet::<T>::initiate_attempt(
334			RawOrigin::Signed(initiator.clone()).into(),
335			lost_lookup.clone(),
336			0,
337		)
338		.unwrap();
339
340		#[extrinsic_call]
341		_(RawOrigin::Signed(lost.clone()), 0);
342		assert_last_event::<T>(Event::<T>::AttemptSlashed { lost, friend_group_index: 0 }.into());
343	}
344
345	impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
346}