referrerpolicy=no-referrer-when-downgrade

pallet_balances/
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//! Balances pallet benchmarking.
19
20#![cfg(feature = "runtime-benchmarks")]
21
22use super::*;
23use crate::Pallet as Balances;
24
25use frame_benchmarking::v2::*;
26use frame_system::RawOrigin;
27use sp_runtime::traits::Bounded;
28use types::ExtraFlags;
29
30const SEED: u32 = 0;
31// existential deposit multiplier
32const ED_MULTIPLIER: u32 = 10;
33
34fn minimum_balance<T: Config<I>, I: 'static>() -> T::Balance {
35	if cfg!(feature = "insecure_zero_ed") {
36		100u32.into()
37	} else {
38		T::ExistentialDeposit::get()
39	}
40}
41
42#[instance_benchmarks]
43mod benchmarks {
44	use super::*;
45
46	// Benchmark `transfer` extrinsic with the worst possible conditions:
47	// * Transfer will kill the sender account.
48	// * Transfer will create the recipient account.
49	#[benchmark]
50	fn transfer_allow_death() {
51		let existential_deposit: T::Balance = minimum_balance::<T, I>();
52		let caller = whitelisted_caller();
53
54		// Give some multiple of the existential deposit
55		let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()).max(1u32.into());
56		let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&caller, balance);
57
58		// Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account,
59		// and reap this user.
60		let recipient: T::AccountId = account("recipient", 0, SEED);
61		let recipient_lookup = T::Lookup::unlookup(recipient.clone());
62		let transfer_amount =
63			existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into();
64
65		#[extrinsic_call]
66		_(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount);
67
68		if cfg!(feature = "insecure_zero_ed") {
69			assert_eq!(Balances::<T, I>::free_balance(&caller), balance - transfer_amount);
70		} else {
71			assert_eq!(Balances::<T, I>::free_balance(&caller), Zero::zero());
72		}
73
74		assert_eq!(Balances::<T, I>::free_balance(&recipient), transfer_amount);
75	}
76
77	// Benchmark `transfer` with the best possible condition:
78	// * Both accounts exist and will continue to exist.
79	#[benchmark(extra)]
80	fn transfer_best_case() {
81		let caller = whitelisted_caller();
82		let recipient: T::AccountId = account("recipient", 0, SEED);
83		let recipient_lookup = T::Lookup::unlookup(recipient.clone());
84
85		// Give the sender account max funds for transfer (their account will never reasonably be
86		// killed).
87		let _ =
88			<Balances<T, I> as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value());
89
90		// Give the recipient account existential deposit (thus their account already exists).
91		let existential_deposit: T::Balance = minimum_balance::<T, I>();
92		let _ =
93			<Balances<T, I> as Currency<_>>::make_free_balance_be(&recipient, existential_deposit);
94		let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
95
96		#[extrinsic_call]
97		transfer_allow_death(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount);
98
99		assert!(!Balances::<T, I>::free_balance(&caller).is_zero());
100		assert!(!Balances::<T, I>::free_balance(&recipient).is_zero());
101	}
102
103	// Benchmark `transfer_keep_alive` with the worst possible condition:
104	// * The recipient account is created.
105	#[benchmark]
106	fn transfer_keep_alive() {
107		let caller = whitelisted_caller();
108		let recipient: T::AccountId = account("recipient", 0, SEED);
109		let recipient_lookup = T::Lookup::unlookup(recipient.clone());
110
111		// Give the sender account max funds, thus a transfer will not kill account.
112		let _ =
113			<Balances<T, I> as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value());
114		let existential_deposit: T::Balance = minimum_balance::<T, I>();
115		let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
116
117		#[extrinsic_call]
118		_(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount);
119
120		assert!(!Balances::<T, I>::free_balance(&caller).is_zero());
121		assert_eq!(Balances::<T, I>::free_balance(&recipient), transfer_amount);
122	}
123
124	// Benchmark `force_set_balance` coming from ROOT account. This always creates an account.
125	#[benchmark]
126	fn force_set_balance_creating() {
127		let user: T::AccountId = account("user", 0, SEED);
128		let user_lookup = T::Lookup::unlookup(user.clone());
129
130		let existential_deposit: T::Balance = minimum_balance::<T, I>();
131		let balance_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
132
133		#[extrinsic_call]
134		force_set_balance(RawOrigin::Root, user_lookup, balance_amount);
135
136		assert_eq!(Balances::<T, I>::free_balance(&user), balance_amount);
137	}
138
139	// Benchmark `force_set_balance` coming from ROOT account. This always kills an account.
140	#[benchmark]
141	fn force_set_balance_killing() {
142		let user: T::AccountId = account("user", 0, SEED);
143		let user_lookup = T::Lookup::unlookup(user.clone());
144
145		// Give the user some initial balance.
146		let existential_deposit: T::Balance = minimum_balance::<T, I>();
147		let balance_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
148		let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&user, balance_amount);
149
150		#[extrinsic_call]
151		force_set_balance(RawOrigin::Root, user_lookup, Zero::zero());
152
153		assert!(Balances::<T, I>::free_balance(&user).is_zero());
154	}
155
156	// Benchmark `force_transfer` extrinsic with the worst possible conditions:
157	// * Transfer will kill the sender account.
158	// * Transfer will create the recipient account.
159	#[benchmark]
160	fn force_transfer() {
161		let existential_deposit: T::Balance = minimum_balance::<T, I>();
162		let source: T::AccountId = account("source", 0, SEED);
163		let source_lookup = T::Lookup::unlookup(source.clone());
164
165		// Give some multiple of the existential deposit
166		let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
167		let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&source, balance);
168
169		// Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account,
170		// and reap this user.
171		let recipient: T::AccountId = account("recipient", 0, SEED);
172		let recipient_lookup = T::Lookup::unlookup(recipient.clone());
173		let transfer_amount =
174			existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into();
175
176		#[extrinsic_call]
177		_(RawOrigin::Root, source_lookup, recipient_lookup, transfer_amount);
178
179		if cfg!(feature = "insecure_zero_ed") {
180			assert_eq!(Balances::<T, I>::free_balance(&source), balance - transfer_amount);
181		} else {
182			assert_eq!(Balances::<T, I>::free_balance(&source), Zero::zero());
183		}
184
185		assert_eq!(Balances::<T, I>::free_balance(&recipient), transfer_amount);
186	}
187
188	// This benchmark performs the same operation as `transfer` in the worst case scenario,
189	// but additionally introduces many new users into the storage, increasing the the merkle
190	// trie and PoV size.
191	#[benchmark(extra)]
192	fn transfer_increasing_users(u: Linear<0, 1_000>) {
193		// 1_000 is not very much, but this upper bound can be controlled by the CLI.
194		let existential_deposit: T::Balance = minimum_balance::<T, I>();
195		let caller = whitelisted_caller();
196
197		// Give some multiple of the existential deposit
198		let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
199		let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&caller, balance);
200
201		// Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account,
202		// and reap this user.
203		let recipient: T::AccountId = account("recipient", 0, SEED);
204		let recipient_lookup = T::Lookup::unlookup(recipient.clone());
205		let transfer_amount =
206			existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into();
207
208		// Create a bunch of users in storage.
209		for i in 0..u {
210			// The `account` function uses `blake2_256` to generate unique accounts, so these
211			// should be quite random and evenly distributed in the trie.
212			let new_user: T::AccountId = account("new_user", i, SEED);
213			let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&new_user, balance);
214		}
215
216		#[extrinsic_call]
217		transfer_allow_death(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount);
218
219		if cfg!(feature = "insecure_zero_ed") {
220			assert_eq!(Balances::<T, I>::free_balance(&caller), balance - transfer_amount);
221		} else {
222			assert_eq!(Balances::<T, I>::free_balance(&caller), Zero::zero());
223		}
224
225		assert_eq!(Balances::<T, I>::free_balance(&recipient), transfer_amount);
226	}
227
228	// Benchmark `transfer_all` with the worst possible condition:
229	// * The recipient account is created
230	// * The sender is killed
231	#[benchmark]
232	fn transfer_all() {
233		let caller = whitelisted_caller();
234		let recipient: T::AccountId = account("recipient", 0, SEED);
235		let recipient_lookup = T::Lookup::unlookup(recipient.clone());
236
237		// Give some multiple of the existential deposit
238		let existential_deposit: T::Balance = minimum_balance::<T, I>();
239		let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
240		let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&caller, balance);
241
242		#[extrinsic_call]
243		_(RawOrigin::Signed(caller.clone()), recipient_lookup, false);
244
245		assert!(Balances::<T, I>::free_balance(&caller).is_zero());
246		assert_eq!(Balances::<T, I>::free_balance(&recipient), balance);
247	}
248
249	#[benchmark]
250	fn force_unreserve() -> Result<(), BenchmarkError> {
251		let user: T::AccountId = account("user", 0, SEED);
252		let user_lookup = T::Lookup::unlookup(user.clone());
253
254		// Give some multiple of the existential deposit
255		let ed = minimum_balance::<T, I>();
256		let balance = ed + ed;
257		let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&user, balance);
258
259		// Reserve the balance
260		<Balances<T, I> as ReservableCurrency<_>>::reserve(&user, ed)?;
261		assert_eq!(Balances::<T, I>::reserved_balance(&user), ed);
262		assert_eq!(Balances::<T, I>::free_balance(&user), ed);
263
264		#[extrinsic_call]
265		_(RawOrigin::Root, user_lookup, balance);
266
267		assert!(Balances::<T, I>::reserved_balance(&user).is_zero());
268		assert_eq!(Balances::<T, I>::free_balance(&user), ed + ed);
269
270		Ok(())
271	}
272
273	#[benchmark]
274	fn upgrade_accounts(u: Linear<1, 1_000>) {
275		let caller: T::AccountId = whitelisted_caller();
276		let who = (0..u)
277			.into_iter()
278			.map(|i| -> T::AccountId {
279				let user = account("old_user", i, SEED);
280				let account = AccountData {
281					free: minimum_balance::<T, I>(),
282					reserved: minimum_balance::<T, I>(),
283					frozen: Zero::zero(),
284					flags: ExtraFlags::old_logic(),
285				};
286				frame_system::Pallet::<T>::inc_providers(&user);
287				assert!(T::AccountStore::try_mutate_exists(&user, |a| -> DispatchResult {
288					*a = Some(account);
289					Ok(())
290				})
291				.is_ok());
292				assert!(!Balances::<T, I>::account(&user).flags.is_new_logic());
293				assert_eq!(frame_system::Pallet::<T>::providers(&user), 1);
294				assert_eq!(frame_system::Pallet::<T>::consumers(&user), 0);
295				user
296			})
297			.collect();
298
299		#[extrinsic_call]
300		_(RawOrigin::Signed(caller.clone()), who);
301
302		for i in 0..u {
303			let user: T::AccountId = account("old_user", i, SEED);
304			assert!(Balances::<T, I>::account(&user).flags.is_new_logic());
305			assert_eq!(frame_system::Pallet::<T>::providers(&user), 1);
306			assert_eq!(frame_system::Pallet::<T>::consumers(&user), 1);
307		}
308	}
309
310	#[benchmark]
311	fn force_adjust_total_issuance() {
312		let ti = TotalIssuance::<T, I>::get();
313		let delta = 123u32.into();
314
315		#[extrinsic_call]
316		_(RawOrigin::Root, AdjustmentDirection::Increase, delta);
317
318		assert_eq!(TotalIssuance::<T, I>::get(), ti + delta);
319	}
320
321	/// Benchmark `burn` extrinsic with the worst possible condition - burn kills the account.
322	#[benchmark]
323	fn burn_allow_death() {
324		let existential_deposit: T::Balance = minimum_balance::<T, I>();
325		let caller = whitelisted_caller();
326
327		// Give some multiple of the existential deposit
328		let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
329		let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&caller, balance);
330
331		// Burn enough to kill the account.
332		let burn_amount = balance - existential_deposit + 1u32.into();
333
334		#[extrinsic_call]
335		burn(RawOrigin::Signed(caller.clone()), burn_amount, false);
336
337		if cfg!(feature = "insecure_zero_ed") {
338			assert_eq!(Balances::<T, I>::free_balance(&caller), balance - burn_amount);
339		} else {
340			assert_eq!(Balances::<T, I>::free_balance(&caller), Zero::zero());
341		}
342	}
343
344	// Benchmark `burn` extrinsic with the case where account is kept alive.
345	#[benchmark]
346	fn burn_keep_alive() {
347		let existential_deposit: T::Balance = minimum_balance::<T, I>();
348		let caller = whitelisted_caller();
349
350		// Give some multiple of the existential deposit
351		let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
352		let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&caller, balance);
353
354		// Burn minimum possible amount which should not kill the account.
355		let burn_amount = 1u32.into();
356
357		#[extrinsic_call]
358		burn(RawOrigin::Signed(caller.clone()), burn_amount, true);
359
360		assert_eq!(Balances::<T, I>::free_balance(&caller), balance - burn_amount);
361	}
362
363	impl_benchmark_test_suite! {
364		Balances,
365		crate::tests::ExtBuilder::default().build(),
366		crate::tests::Test,
367	}
368}