1#![cfg(feature = "runtime-benchmarks")]
19
20use super::*;
21
22use crate::Pallet;
23use alloc::{boxed::Box, vec, vec::Vec};
24use frame::benchmarking::prelude::*;
25
26const SEED: u32 = 0;
27const DEFAULT_DELAY: u32 = 0;
28
29fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
30 frame_system::Pallet::<T>::assert_last_event(generic_event.into());
31}
32
33fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
34 frame_system::Pallet::<T>::assert_has_event(generic_event.into());
35}
36
37fn get_total_deposit<T: Config>(
38 bounded_friends: &FriendsOf<T>,
39) -> Option<<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance>
40{
41 let friend_deposit = T::FriendDepositFactor::get()
42 .checked_mul(&bounded_friends.len().saturated_into())
43 .unwrap();
44
45 T::ConfigDepositBase::get().checked_add(&friend_deposit)
46}
47
48fn generate_friends<T: Config>(num: u32) -> Vec<<T as frame_system::Config>::AccountId> {
49 let mut friends = (0..num).map(|x| account("friend", x, SEED)).collect::<Vec<_>>();
51 friends.sort();
53
54 for friend in 0..friends.len() {
55 T::Currency::make_free_balance_be(
57 &friends.get(friend).unwrap(),
58 BalanceOf::<T>::max_value(),
59 );
60 }
61
62 friends
63}
64
65fn add_caller_and_generate_friends<T: Config>(
66 caller: T::AccountId,
67 num: u32,
68) -> Vec<<T as frame_system::Config>::AccountId> {
69 let mut friends = generate_friends::<T>(num - 1);
71
72 T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
73
74 friends.push(caller);
75
76 friends.sort();
78
79 friends
80}
81
82fn insert_recovery_config_with_max_friends<T: Config>(account: &T::AccountId) {
83 T::Currency::make_free_balance_be(&account, BalanceOf::<T>::max_value());
84
85 let n = T::MaxFriends::get();
86
87 let friends = generate_friends::<T>(n);
88
89 let bounded_friends: FriendsOf<T> = friends.try_into().unwrap();
90
91 let total_deposit = get_total_deposit::<T>(&bounded_friends).unwrap();
93
94 let recovery_config = RecoveryConfig {
95 delay_period: DEFAULT_DELAY.into(),
96 deposit: total_deposit,
97 friends: bounded_friends,
98 threshold: n as u16,
99 };
100
101 T::Currency::reserve(&account, total_deposit).unwrap();
103
104 <Recoverable<T>>::insert(&account, recovery_config);
105}
106
107fn setup_active_recovery_with_max_friends<T: Config>(
108 caller: &T::AccountId,
109 lost_account: &T::AccountId,
110) {
111 insert_recovery_config_with_max_friends::<T>(&lost_account);
112 let n = T::MaxFriends::get();
113 let friends = generate_friends::<T>(n);
114 let bounded_friends: FriendsOf<T> = friends.try_into().unwrap();
115
116 let initial_recovery_deposit = T::RecoveryDeposit::get();
117 T::Currency::reserve(caller, initial_recovery_deposit).unwrap();
118
119 let active_recovery = ActiveRecovery {
120 created: DEFAULT_DELAY.into(),
121 deposit: initial_recovery_deposit,
122 friends: bounded_friends,
123 };
124 <ActiveRecoveries<T>>::insert(lost_account, caller, active_recovery);
125}
126
127#[benchmarks]
128mod benchmarks {
129 use super::*;
130
131 #[benchmark]
132 fn as_recovered() {
133 let caller: T::AccountId = whitelisted_caller();
134 let recovered_account: T::AccountId = account("recovered_account", 0, SEED);
135 let recovered_account_lookup = T::Lookup::unlookup(recovered_account.clone());
136 let call: <T as Config>::RuntimeCall =
137 frame_system::Call::<T>::remark { remark: vec![] }.into();
138
139 Proxy::<T>::insert(&caller, &recovered_account);
140
141 #[extrinsic_call]
142 _(RawOrigin::Signed(caller), recovered_account_lookup, Box::new(call))
143 }
144
145 #[benchmark]
146 fn set_recovered() {
147 let lost: T::AccountId = whitelisted_caller();
148 let lost_lookup = T::Lookup::unlookup(lost.clone());
149 let rescuer: T::AccountId = whitelisted_caller();
150 let rescuer_lookup = T::Lookup::unlookup(rescuer.clone());
151
152 #[extrinsic_call]
153 _(RawOrigin::Root, lost_lookup, rescuer_lookup);
154
155 assert_last_event::<T>(
156 Event::AccountRecovered { lost_account: lost, rescuer_account: rescuer }.into(),
157 );
158 }
159
160 #[benchmark]
161 fn create_recovery(n: Linear<1, { T::MaxFriends::get() }>) {
162 let caller: T::AccountId = whitelisted_caller();
163 T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
164
165 let friends = generate_friends::<T>(n);
167
168 #[extrinsic_call]
169 _(RawOrigin::Signed(caller.clone()), friends, n as u16, DEFAULT_DELAY.into());
170
171 assert_last_event::<T>(Event::RecoveryCreated { account: caller }.into());
172 }
173
174 #[benchmark]
175 fn initiate_recovery() {
176 let caller: T::AccountId = whitelisted_caller();
177 T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
178
179 let lost_account: T::AccountId = account("lost_account", 0, SEED);
180 let lost_account_lookup = T::Lookup::unlookup(lost_account.clone());
181
182 insert_recovery_config_with_max_friends::<T>(&lost_account);
183
184 #[extrinsic_call]
185 _(RawOrigin::Signed(caller.clone()), lost_account_lookup);
186
187 assert_last_event::<T>(
188 Event::RecoveryInitiated { lost_account, rescuer_account: caller }.into(),
189 );
190 }
191
192 #[benchmark]
193 fn vouch_recovery(n: Linear<1, { T::MaxFriends::get() }>) {
194 let caller: T::AccountId = whitelisted_caller();
195 let lost_account: T::AccountId = account("lost_account", 0, SEED);
196 let lost_account_lookup = T::Lookup::unlookup(lost_account.clone());
197 let rescuer_account: T::AccountId = account("rescuer_account", 0, SEED);
198 let rescuer_account_lookup = T::Lookup::unlookup(rescuer_account.clone());
199
200 let friends = add_caller_and_generate_friends::<T>(caller.clone(), n);
202 let bounded_friends: FriendsOf<T> = friends.try_into().unwrap();
203
204 let total_deposit = get_total_deposit::<T>(&bounded_friends).unwrap();
206
207 let recovery_config = RecoveryConfig {
208 delay_period: DEFAULT_DELAY.into(),
209 deposit: total_deposit,
210 friends: bounded_friends.clone(),
211 threshold: n as u16,
212 };
213
214 <Recoverable<T>>::insert(&lost_account, recovery_config.clone());
216
217 T::Currency::reserve(&caller, total_deposit).unwrap();
219
220 let recovery_status = ActiveRecovery {
222 created: DEFAULT_DELAY.into(),
223 deposit: total_deposit,
224 friends: generate_friends::<T>(n - 1).try_into().unwrap(),
225 };
226
227 <ActiveRecoveries<T>>::insert(&lost_account, &rescuer_account, recovery_status);
229
230 #[extrinsic_call]
231 _(RawOrigin::Signed(caller.clone()), lost_account_lookup, rescuer_account_lookup);
232 assert_last_event::<T>(
233 Event::RecoveryVouched { lost_account, rescuer_account, sender: caller }.into(),
234 );
235 }
236
237 #[benchmark]
238 fn claim_recovery(n: Linear<1, { T::MaxFriends::get() }>) {
239 let caller: T::AccountId = whitelisted_caller();
240 let lost_account: T::AccountId = account("lost_account", 0, SEED);
241 let lost_account_lookup = T::Lookup::unlookup(lost_account.clone());
242
243 T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
244
245 let friends = generate_friends::<T>(n);
247 let bounded_friends: FriendsOf<T> = friends.try_into().unwrap();
248
249 let total_deposit = get_total_deposit::<T>(&bounded_friends).unwrap();
251
252 let recovery_config = RecoveryConfig {
253 delay_period: 0u32.into(),
254 deposit: total_deposit,
255 friends: bounded_friends.clone(),
256 threshold: n as u16,
257 };
258
259 <Recoverable<T>>::insert(&lost_account, recovery_config.clone());
261
262 T::Currency::reserve(&caller, total_deposit).unwrap();
264
265 let recovery_status = ActiveRecovery {
267 created: 0u32.into(),
268 deposit: total_deposit,
269 friends: bounded_friends.clone(),
270 };
271
272 <ActiveRecoveries<T>>::insert(&lost_account, &caller, recovery_status);
274
275 #[extrinsic_call]
276 _(RawOrigin::Signed(caller.clone()), lost_account_lookup);
277 assert_last_event::<T>(
278 Event::AccountRecovered { lost_account, rescuer_account: caller }.into(),
279 );
280 }
281
282 #[benchmark]
283 fn close_recovery(n: Linear<1, { T::MaxFriends::get() }>) {
284 let caller: T::AccountId = whitelisted_caller();
285 let rescuer_account: T::AccountId = account("rescuer_account", 0, SEED);
286 let rescuer_account_lookup = T::Lookup::unlookup(rescuer_account.clone());
287
288 T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
289 T::Currency::make_free_balance_be(&rescuer_account, BalanceOf::<T>::max_value());
290
291 let friends = generate_friends::<T>(n);
293 let bounded_friends: FriendsOf<T> = friends.try_into().unwrap();
294
295 let total_deposit = get_total_deposit::<T>(&bounded_friends).unwrap();
297
298 let recovery_config = RecoveryConfig {
299 delay_period: DEFAULT_DELAY.into(),
300 deposit: total_deposit,
301 friends: bounded_friends.clone(),
302 threshold: n as u16,
303 };
304
305 <Recoverable<T>>::insert(&caller, recovery_config.clone());
307
308 T::Currency::reserve(&caller, total_deposit).unwrap();
310
311 let recovery_status = ActiveRecovery {
313 created: DEFAULT_DELAY.into(),
314 deposit: total_deposit,
315 friends: bounded_friends.clone(),
316 };
317
318 <ActiveRecoveries<T>>::insert(&caller, &rescuer_account, recovery_status);
320
321 #[extrinsic_call]
322 _(RawOrigin::Signed(caller.clone()), rescuer_account_lookup);
323 assert_last_event::<T>(
324 Event::RecoveryClosed { lost_account: caller, rescuer_account }.into(),
325 );
326 }
327
328 #[benchmark]
329 fn remove_recovery(n: Linear<1, { T::MaxFriends::get() }>) {
330 let caller: T::AccountId = whitelisted_caller();
331
332 T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
333
334 let friends = generate_friends::<T>(n);
336 let bounded_friends: FriendsOf<T> = friends.try_into().unwrap();
337
338 let total_deposit = get_total_deposit::<T>(&bounded_friends).unwrap();
340
341 let recovery_config = RecoveryConfig {
342 delay_period: DEFAULT_DELAY.into(),
343 deposit: total_deposit,
344 friends: bounded_friends.clone(),
345 threshold: n as u16,
346 };
347
348 <Recoverable<T>>::insert(&caller, recovery_config);
350
351 T::Currency::reserve(&caller, total_deposit).unwrap();
353
354 #[extrinsic_call]
355 _(RawOrigin::Signed(caller.clone()));
356 assert_last_event::<T>(Event::RecoveryRemoved { lost_account: caller }.into());
357 }
358
359 #[benchmark]
360 fn cancel_recovered() -> Result<(), BenchmarkError> {
361 let caller: T::AccountId = whitelisted_caller();
362 let account: T::AccountId = account("account", 0, SEED);
363 let account_lookup = T::Lookup::unlookup(account.clone());
364
365 frame_system::Pallet::<T>::inc_providers(&caller);
366
367 frame_system::Pallet::<T>::inc_consumers(&caller)?;
368
369 Proxy::<T>::insert(&caller, &account);
370
371 #[extrinsic_call]
372 _(RawOrigin::Signed(caller), account_lookup);
373
374 Ok(())
375 }
376
377 #[benchmark]
378 fn poke_deposit(n: Linear<1, { T::MaxFriends::get() }>) -> Result<(), BenchmarkError> {
379 let caller: T::AccountId = whitelisted_caller();
380 let lost_account: T::AccountId = account("lost_account", 0, SEED);
381
382 T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
384
385 insert_recovery_config_with_max_friends::<T>(&caller);
387
388 setup_active_recovery_with_max_friends::<T>(&caller, &lost_account);
390
391 let initial_config = <Recoverable<T>>::get(&caller).unwrap();
393 let initial_config_deposit = initial_config.deposit;
394 let initial_recovery_deposit = T::RecoveryDeposit::get();
395 assert_eq!(
396 T::Currency::reserved_balance(&caller),
397 initial_config_deposit.saturating_add(initial_recovery_deposit)
398 );
399
400 let increased_config_deposit = initial_config_deposit.saturating_add(2u32.into());
402 let increased_recovery_deposit = initial_recovery_deposit.saturating_add(2u32.into());
403
404 <Recoverable<T>>::try_mutate(&caller, |maybe_config| -> Result<(), BenchmarkError> {
405 let config = maybe_config.as_mut().unwrap();
406 T::Currency::reserve(
407 &caller,
408 increased_config_deposit.saturating_sub(initial_config_deposit),
409 )?;
410 config.deposit = increased_config_deposit;
411 Ok(())
412 })
413 .map_err(|_| BenchmarkError::Stop("Failed to mutate storage"))?;
414
415 <ActiveRecoveries<T>>::try_mutate(
416 &lost_account,
417 &caller,
418 |maybe_recovery| -> Result<(), BenchmarkError> {
419 let recovery = maybe_recovery.as_mut().unwrap();
420 T::Currency::reserve(
421 &caller,
422 increased_recovery_deposit.saturating_sub(initial_recovery_deposit),
423 )?;
424 recovery.deposit = increased_recovery_deposit;
425 Ok(())
426 },
427 )
428 .map_err(|_| BenchmarkError::Stop("Failed to mutate storage"))?;
429
430 assert_eq!(
432 T::Currency::reserved_balance(&caller),
433 increased_config_deposit.saturating_add(increased_recovery_deposit)
434 );
435
436 #[extrinsic_call]
437 _(RawOrigin::Signed(caller.clone()), Some(T::Lookup::unlookup(lost_account.clone())));
438
439 assert_eq!(
441 T::Currency::reserved_balance(&caller),
442 initial_config_deposit.saturating_add(initial_recovery_deposit)
443 );
444
445 assert_has_event::<T>(
447 Event::DepositPoked {
448 who: caller.clone(),
449 kind: DepositKind::RecoveryConfig,
450 old_deposit: increased_config_deposit,
451 new_deposit: initial_config_deposit,
452 }
453 .into(),
454 );
455 assert_has_event::<T>(
456 Event::DepositPoked {
457 who: caller,
458 kind: DepositKind::ActiveRecoveryFor(lost_account),
459 old_deposit: increased_recovery_deposit,
460 new_deposit: initial_recovery_deposit,
461 }
462 .into(),
463 );
464
465 Ok(())
466 }
467
468 impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
469}