referrerpolicy=no-referrer-when-downgrade

pallet_proxy/
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// Benchmarks for Proxy Pallet
19
20#![cfg(feature = "runtime-benchmarks")]
21
22use super::*;
23use crate::Pallet as Proxy;
24use alloc::{boxed::Box, vec};
25use frame::benchmarking::prelude::{
26	account, benchmarks, impl_test_function, whitelisted_caller, BenchmarkError, RawOrigin,
27};
28
29const SEED: u32 = 0;
30
31fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
32	frame_system::Pallet::<T>::assert_last_event(generic_event.into());
33}
34
35fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
36	frame_system::Pallet::<T>::assert_has_event(generic_event.into());
37}
38
39fn add_proxies<T: Config>(n: u32, maybe_who: Option<T::AccountId>) -> Result<(), &'static str> {
40	let caller = maybe_who.unwrap_or_else(whitelisted_caller);
41	T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value() / 2u32.into());
42	for i in 0..n {
43		let real = T::Lookup::unlookup(account("target", i, SEED));
44
45		Proxy::<T>::add_proxy(
46			RawOrigin::Signed(caller.clone()).into(),
47			real,
48			T::ProxyType::default(),
49			BlockNumberFor::<T>::zero(),
50		)?;
51	}
52	Ok(())
53}
54
55fn add_announcements<T: Config>(
56	n: u32,
57	maybe_who: Option<T::AccountId>,
58	maybe_real: Option<T::AccountId>,
59) -> Result<(), &'static str> {
60	let caller = maybe_who.unwrap_or_else(|| account("caller", 0, SEED));
61	let caller_lookup = T::Lookup::unlookup(caller.clone());
62	T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value() / 2u32.into());
63	let real = if let Some(real) = maybe_real {
64		real
65	} else {
66		let real = account("real", 0, SEED);
67		T::Currency::make_free_balance_be(&real, BalanceOf::<T>::max_value() / 2u32.into());
68		Proxy::<T>::add_proxy(
69			RawOrigin::Signed(real.clone()).into(),
70			caller_lookup,
71			T::ProxyType::default(),
72			BlockNumberFor::<T>::zero(),
73		)?;
74		real
75	};
76	let real_lookup = T::Lookup::unlookup(real);
77	for _ in 0..n {
78		Proxy::<T>::announce(
79			RawOrigin::Signed(caller.clone()).into(),
80			real_lookup.clone(),
81			T::CallHasher::hash_of(&("add_announcement", n)),
82		)?;
83	}
84	Ok(())
85}
86
87#[benchmarks]
88mod benchmarks {
89	use super::*;
90
91	#[benchmark]
92	fn proxy(p: Linear<1, { T::MaxProxies::get() - 1 }>) -> Result<(), BenchmarkError> {
93		add_proxies::<T>(p, None)?;
94		// In this case the caller is the "target" proxy
95		let caller: T::AccountId = account("target", p - 1, SEED);
96		T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value() / 2u32.into());
97		// ... and "real" is the traditional caller. This is not a typo.
98		let real: T::AccountId = whitelisted_caller();
99		let real_lookup = T::Lookup::unlookup(real);
100		let call: <T as Config>::RuntimeCall =
101			frame_system::Call::<T>::remark { remark: vec![] }.into();
102
103		#[extrinsic_call]
104		_(RawOrigin::Signed(caller), real_lookup, Some(T::ProxyType::default()), Box::new(call));
105
106		assert_last_event::<T>(Event::ProxyExecuted { result: Ok(()) }.into());
107
108		Ok(())
109	}
110
111	#[benchmark]
112	fn proxy_announced(
113		a: Linear<0, { T::MaxPending::get() - 1 }>,
114		p: Linear<1, { T::MaxProxies::get() - 1 }>,
115	) -> Result<(), BenchmarkError> {
116		add_proxies::<T>(p, None)?;
117		// In this case the caller is the "target" proxy
118		let caller: T::AccountId = account("pure", 0, SEED);
119		let delegate: T::AccountId = account("target", p - 1, SEED);
120		let delegate_lookup = T::Lookup::unlookup(delegate.clone());
121		T::Currency::make_free_balance_be(&delegate, BalanceOf::<T>::max_value() / 2u32.into());
122		// ... and "real" is the traditional caller. This is not a typo.
123		let real: T::AccountId = whitelisted_caller();
124		let real_lookup = T::Lookup::unlookup(real);
125		let call: <T as Config>::RuntimeCall =
126			frame_system::Call::<T>::remark { remark: vec![] }.into();
127		Proxy::<T>::announce(
128			RawOrigin::Signed(delegate.clone()).into(),
129			real_lookup.clone(),
130			T::CallHasher::hash_of(&call),
131		)?;
132		add_announcements::<T>(a, Some(delegate.clone()), None)?;
133
134		#[extrinsic_call]
135		_(
136			RawOrigin::Signed(caller),
137			delegate_lookup,
138			real_lookup,
139			Some(T::ProxyType::default()),
140			Box::new(call),
141		);
142
143		assert_last_event::<T>(Event::ProxyExecuted { result: Ok(()) }.into());
144
145		Ok(())
146	}
147
148	#[benchmark]
149	fn remove_announcement(
150		a: Linear<0, { T::MaxPending::get() - 1 }>,
151		p: Linear<1, { T::MaxProxies::get() - 1 }>,
152	) -> Result<(), BenchmarkError> {
153		add_proxies::<T>(p, None)?;
154		// In this case the caller is the "target" proxy
155		let caller: T::AccountId = account("target", p - 1, SEED);
156		T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value() / 2u32.into());
157		// ... and "real" is the traditional caller. This is not a typo.
158		let real: T::AccountId = whitelisted_caller();
159		let real_lookup = T::Lookup::unlookup(real);
160		let call: <T as Config>::RuntimeCall =
161			frame_system::Call::<T>::remark { remark: vec![] }.into();
162		Proxy::<T>::announce(
163			RawOrigin::Signed(caller.clone()).into(),
164			real_lookup.clone(),
165			T::CallHasher::hash_of(&call),
166		)?;
167		add_announcements::<T>(a, Some(caller.clone()), None)?;
168
169		#[extrinsic_call]
170		_(RawOrigin::Signed(caller.clone()), real_lookup, T::CallHasher::hash_of(&call));
171
172		let (announcements, _) = Announcements::<T>::get(&caller);
173		assert_eq!(announcements.len() as u32, a);
174
175		Ok(())
176	}
177
178	#[benchmark]
179	fn reject_announcement(
180		a: Linear<0, { T::MaxPending::get() - 1 }>,
181		p: Linear<1, { T::MaxProxies::get() - 1 }>,
182	) -> Result<(), BenchmarkError> {
183		add_proxies::<T>(p, None)?;
184		// In this case the caller is the "target" proxy
185		let caller: T::AccountId = account("target", p - 1, SEED);
186		let caller_lookup = T::Lookup::unlookup(caller.clone());
187		T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value() / 2u32.into());
188		// ... and "real" is the traditional caller. This is not a typo.
189		let real: T::AccountId = whitelisted_caller();
190		let real_lookup = T::Lookup::unlookup(real.clone());
191		let call: <T as Config>::RuntimeCall =
192			frame_system::Call::<T>::remark { remark: vec![] }.into();
193		Proxy::<T>::announce(
194			RawOrigin::Signed(caller.clone()).into(),
195			real_lookup,
196			T::CallHasher::hash_of(&call),
197		)?;
198		add_announcements::<T>(a, Some(caller.clone()), None)?;
199
200		#[extrinsic_call]
201		_(RawOrigin::Signed(real), caller_lookup, T::CallHasher::hash_of(&call));
202
203		let (announcements, _) = Announcements::<T>::get(&caller);
204		assert_eq!(announcements.len() as u32, a);
205
206		Ok(())
207	}
208
209	#[benchmark]
210	fn announce(
211		a: Linear<0, { T::MaxPending::get() - 1 }>,
212		p: Linear<1, { T::MaxProxies::get() - 1 }>,
213	) -> Result<(), BenchmarkError> {
214		add_proxies::<T>(p, None)?;
215		// In this case the caller is the "target" proxy
216		let caller: T::AccountId = account("target", p - 1, SEED);
217		T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value() / 2u32.into());
218		// ... and "real" is the traditional caller. This is not a typo.
219		let real: T::AccountId = whitelisted_caller();
220		let real_lookup = T::Lookup::unlookup(real.clone());
221		add_announcements::<T>(a, Some(caller.clone()), None)?;
222		let call: <T as Config>::RuntimeCall =
223			frame_system::Call::<T>::remark { remark: vec![] }.into();
224		let call_hash = T::CallHasher::hash_of(&call);
225
226		#[extrinsic_call]
227		_(RawOrigin::Signed(caller.clone()), real_lookup, call_hash);
228
229		assert_last_event::<T>(Event::Announced { real, proxy: caller, call_hash }.into());
230
231		Ok(())
232	}
233
234	#[benchmark]
235	fn add_proxy(p: Linear<1, { T::MaxProxies::get() - 1 }>) -> Result<(), BenchmarkError> {
236		add_proxies::<T>(p, None)?;
237		let caller: T::AccountId = whitelisted_caller();
238		let real = T::Lookup::unlookup(account("target", T::MaxProxies::get(), SEED));
239
240		#[extrinsic_call]
241		_(
242			RawOrigin::Signed(caller.clone()),
243			real,
244			T::ProxyType::default(),
245			BlockNumberFor::<T>::zero(),
246		);
247
248		let (proxies, _) = Proxies::<T>::get(caller);
249		assert_eq!(proxies.len() as u32, p + 1);
250
251		Ok(())
252	}
253
254	#[benchmark]
255	fn remove_proxy(p: Linear<1, { T::MaxProxies::get() - 1 }>) -> Result<(), BenchmarkError> {
256		add_proxies::<T>(p, None)?;
257		let caller: T::AccountId = whitelisted_caller();
258		let delegate = T::Lookup::unlookup(account("target", 0, SEED));
259
260		#[extrinsic_call]
261		_(
262			RawOrigin::Signed(caller.clone()),
263			delegate,
264			T::ProxyType::default(),
265			BlockNumberFor::<T>::zero(),
266		);
267
268		let (proxies, _) = Proxies::<T>::get(caller);
269		assert_eq!(proxies.len() as u32, p - 1);
270
271		Ok(())
272	}
273
274	#[benchmark]
275	fn remove_proxies(p: Linear<1, { T::MaxProxies::get() - 1 }>) -> Result<(), BenchmarkError> {
276		add_proxies::<T>(p, None)?;
277		let caller: T::AccountId = whitelisted_caller();
278
279		#[extrinsic_call]
280		_(RawOrigin::Signed(caller.clone()));
281
282		let (proxies, _) = Proxies::<T>::get(caller);
283		assert_eq!(proxies.len() as u32, 0);
284
285		Ok(())
286	}
287
288	#[benchmark]
289	fn create_pure(p: Linear<1, { T::MaxProxies::get() - 1 }>) -> Result<(), BenchmarkError> {
290		add_proxies::<T>(p, None)?;
291		let caller: T::AccountId = whitelisted_caller();
292
293		#[extrinsic_call]
294		_(
295			RawOrigin::Signed(caller.clone()),
296			T::ProxyType::default(),
297			BlockNumberFor::<T>::zero(),
298			0,
299		);
300
301		let pure_account = Pallet::<T>::pure_account(&caller, &T::ProxyType::default(), 0, None);
302		assert_last_event::<T>(
303			Event::PureCreated {
304				pure: pure_account,
305				who: caller,
306				proxy_type: T::ProxyType::default(),
307				disambiguation_index: 0,
308			}
309			.into(),
310		);
311
312		Ok(())
313	}
314
315	#[benchmark]
316	fn kill_pure(p: Linear<0, { T::MaxProxies::get() - 2 }>) -> Result<(), BenchmarkError> {
317		let caller: T::AccountId = whitelisted_caller();
318		let caller_lookup = T::Lookup::unlookup(caller.clone());
319		T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
320		Pallet::<T>::create_pure(
321			RawOrigin::Signed(whitelisted_caller()).into(),
322			T::ProxyType::default(),
323			BlockNumberFor::<T>::zero(),
324			0,
325		)?;
326		let height = T::BlockNumberProvider::current_block_number();
327		let ext_index = frame_system::Pallet::<T>::extrinsic_index().unwrap_or(0);
328		let pure_account = Pallet::<T>::pure_account(&caller, &T::ProxyType::default(), 0, None);
329
330		add_proxies::<T>(p, Some(pure_account.clone()))?;
331		ensure!(Proxies::<T>::contains_key(&pure_account), "pure proxy not created");
332
333		#[extrinsic_call]
334		_(
335			RawOrigin::Signed(pure_account.clone()),
336			caller_lookup,
337			T::ProxyType::default(),
338			0,
339			height,
340			ext_index,
341		);
342
343		assert!(!Proxies::<T>::contains_key(&pure_account));
344
345		Ok(())
346	}
347
348	#[benchmark]
349	fn poke_deposit() -> Result<(), BenchmarkError> {
350		// Create accounts using the same pattern as other benchmarks
351		let account_1: T::AccountId = account("account", 1, SEED);
352		let account_2: T::AccountId = account("account", 2, SEED);
353		let account_3: T::AccountId = account("account", 3, SEED);
354
355		// Fund accounts
356		T::Currency::make_free_balance_be(&account_1, BalanceOf::<T>::max_value() / 100u8.into());
357		T::Currency::make_free_balance_be(&account_2, BalanceOf::<T>::max_value() / 100u8.into());
358		T::Currency::make_free_balance_be(&account_3, BalanceOf::<T>::max_value() / 100u8.into());
359
360		// Add proxy relationships
361		Proxy::<T>::add_proxy(
362			RawOrigin::Signed(account_1.clone()).into(),
363			T::Lookup::unlookup(account_2.clone()),
364			T::ProxyType::default(),
365			BlockNumberFor::<T>::zero(),
366		)?;
367		Proxy::<T>::add_proxy(
368			RawOrigin::Signed(account_2.clone()).into(),
369			T::Lookup::unlookup(account_3.clone()),
370			T::ProxyType::default(),
371			BlockNumberFor::<T>::zero(),
372		)?;
373		let (proxies, initial_proxy_deposit) = Proxies::<T>::get(&account_2);
374		assert!(!initial_proxy_deposit.is_zero());
375		assert_eq!(initial_proxy_deposit, T::Currency::reserved_balance(&account_2));
376
377		// Create announcement
378		Proxy::<T>::announce(
379			RawOrigin::Signed(account_2.clone()).into(),
380			T::Lookup::unlookup(account_1.clone()),
381			T::CallHasher::hash_of(&("add_announcement", 1)),
382		)?;
383		let (announcements, initial_announcement_deposit) = Announcements::<T>::get(&account_2);
384		assert!(!initial_announcement_deposit.is_zero());
385		assert_eq!(
386			initial_announcement_deposit.saturating_add(initial_proxy_deposit),
387			T::Currency::reserved_balance(&account_2)
388		);
389
390		// Artificially inflate deposits and reserve the extra amount
391		let extra_proxy_deposit = initial_proxy_deposit; // Double the deposit
392		let extra_announcement_deposit = initial_announcement_deposit; // Double the deposit
393		let total = extra_proxy_deposit.saturating_add(extra_announcement_deposit);
394
395		T::Currency::reserve(&account_2, total)?;
396
397		let initial_reserved = T::Currency::reserved_balance(&account_2);
398		assert_eq!(initial_reserved, total.saturating_add(total)); // Double
399
400		// Update storage with increased deposits
401		Proxies::<T>::insert(
402			&account_2,
403			(proxies, initial_proxy_deposit.saturating_add(extra_proxy_deposit)),
404		);
405		Announcements::<T>::insert(
406			&account_2,
407			(
408				announcements,
409				initial_announcement_deposit.saturating_add(extra_announcement_deposit),
410			),
411		);
412
413		// Verify artificial state
414		let (_, inflated_proxy_deposit) = Proxies::<T>::get(&account_2);
415		let (_, inflated_announcement_deposit) = Announcements::<T>::get(&account_2);
416		assert_eq!(
417			inflated_proxy_deposit,
418			initial_proxy_deposit.saturating_add(extra_proxy_deposit)
419		);
420		assert_eq!(
421			inflated_announcement_deposit,
422			initial_announcement_deposit.saturating_add(extra_announcement_deposit)
423		);
424
425		#[extrinsic_call]
426		_(RawOrigin::Signed(account_2.clone()));
427
428		// Verify results
429		let (_, final_proxy_deposit) = Proxies::<T>::get(&account_2);
430		let (_, final_announcement_deposit) = Announcements::<T>::get(&account_2);
431		assert_eq!(final_proxy_deposit, initial_proxy_deposit);
432		assert_eq!(final_announcement_deposit, initial_announcement_deposit);
433
434		let final_reserved = T::Currency::reserved_balance(&account_2);
435		assert_eq!(final_reserved, initial_reserved.saturating_sub(total));
436
437		// Verify events
438		assert_has_event::<T>(
439			Event::DepositPoked {
440				who: account_2.clone(),
441				kind: DepositKind::Proxies,
442				old_deposit: inflated_proxy_deposit,
443				new_deposit: final_proxy_deposit,
444			}
445			.into(),
446		);
447		assert_last_event::<T>(
448			Event::DepositPoked {
449				who: account_2,
450				kind: DepositKind::Announcements,
451				old_deposit: inflated_announcement_deposit,
452				new_deposit: final_announcement_deposit,
453			}
454			.into(),
455		);
456
457		Ok(())
458	}
459
460	impl_benchmark_test_suite!(Proxy, crate::tests::new_test_ext(), crate::tests::Test);
461}