From 13971fe2a7de14cf9f1be780aad014844ac6d267 Mon Sep 17 00:00:00 2001
From: Shawn Tabrizi <shawntabrizi@gmail.com>
Date: Wed, 12 Feb 2020 12:11:20 +0100
Subject: [PATCH] Benchmark the Balances Pallet (#4879)

* Initial transfer bench

* Add best case

* Transfer keep alive

* Set balance benchmarks

* Bump impl

* Fix text

Co-authored-by: Gavin Wood <github@gavwood.com>
---
 substrate/bin/node/runtime/src/lib.rs        |   1 +
 substrate/frame/balances/src/benchmarking.rs | 322 +++++++++++++++++++
 substrate/frame/balances/src/lib.rs          |   1 +
 3 files changed, 324 insertions(+)
 create mode 100644 substrate/frame/balances/src/benchmarking.rs

diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs
index 209208c58fa..4800c08260d 100644
--- a/substrate/bin/node/runtime/src/lib.rs
+++ b/substrate/bin/node/runtime/src/lib.rs
@@ -812,6 +812,7 @@ impl_runtime_apis! {
 			-> Option<Vec<BenchmarkResults>>
 		{
 			match module.as_slice() {
+				b"pallet-balances" | b"balances" => Balances::run_benchmark(extrinsic, steps, repeat).ok(),
 				b"pallet-identity" | b"identity" => Identity::run_benchmark(extrinsic, steps, repeat).ok(),
 				b"pallet-timestamp" | b"timestamp" => Timestamp::run_benchmark(extrinsic, steps, repeat).ok(),
 				_ => return None,
diff --git a/substrate/frame/balances/src/benchmarking.rs b/substrate/frame/balances/src/benchmarking.rs
new file mode 100644
index 00000000000..f48220c1ba7
--- /dev/null
+++ b/substrate/frame/balances/src/benchmarking.rs
@@ -0,0 +1,322 @@
+// Copyright 2020 Parity Technologies (UK) Ltd.
+// This file is part of Substrate.
+
+// Substrate is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Substrate is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Substrate.  If not, see <http://www.gnu.org/licenses/>.
+
+//! Balances pallet benchmarking.
+
+use super::*;
+
+use frame_system::RawOrigin;
+use sp_io::hashing::blake2_256;
+use sp_runtime::{BenchmarkResults, BenchmarkParameter};
+use sp_runtime::traits::{Bounded, Benchmarking, BenchmarkingSetup, Dispatchable};
+
+use crate::Module as Balances;
+
+// Support Functions
+fn account<T: Trait>(name: &'static str, index: u32) -> T::AccountId {
+	let entropy = (name, index).using_encoded(blake2_256);
+	T::AccountId::decode(&mut &entropy[..]).unwrap_or_default()
+}
+
+// Benchmark `transfer` extrinsic with the worst possible conditions:
+// * Transfer will kill the sender account.
+// * Transfer will create the recipient account.
+struct Transfer;
+impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for Transfer {
+	fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)> {
+		vec![
+			// Existential Deposit Multiplier
+			(BenchmarkParameter::E, 2, 1000),
+			// User Seed
+			(BenchmarkParameter::U, 1, 1000),
+		]
+	}
+
+	fn instance(&self, components: &[(BenchmarkParameter, u32)])
+		-> Result<(crate::Call<T>, RawOrigin<T::AccountId>), &'static str>
+	{
+		// Constants
+		let ed = T::ExistentialDeposit::get();
+		
+		// Select an account
+		let u = components.iter().find(|&c| c.0 == BenchmarkParameter::U).unwrap().1;
+		let user = account::<T>("user", u);
+		let user_origin = RawOrigin::Signed(user.clone());
+
+		// Give some multiple of the existential deposit + creation fee + transfer fee
+		let e = components.iter().find(|&c| c.0 == BenchmarkParameter::E).unwrap().1;
+		let mut balance = ed.saturating_mul(e.into());
+		balance += T::CreationFee::get();
+		let _ = <Balances<T> as Currency<_>>::make_free_balance_be(&user, balance);
+
+		// Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account, and reap this user.
+		let recipient = account::<T>("recipient", u);
+		let recipient_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(recipient);
+		let transfer_amt = ed.saturating_mul((e - 1).into()) + 1.into();
+
+		// Return the `transfer` call
+		Ok((crate::Call::<T>::transfer(recipient_lookup, transfer_amt), user_origin))
+	}
+}
+
+// Benchmark `transfer` with the best possible condition:
+// * Both accounts exist and will continue to exist.
+struct TransferBestCase;
+impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for TransferBestCase {
+	fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)> {
+		vec![
+			// Existential Deposit Multiplier
+			(BenchmarkParameter::E, 2, 1000),
+			// User Seed
+			(BenchmarkParameter::U, 1, 1000),
+		]
+	}
+
+	fn instance(&self, components: &[(BenchmarkParameter, u32)])
+		-> Result<(crate::Call<T>, RawOrigin<T::AccountId>), &'static str>
+	{
+		// Constants
+		let ed = T::ExistentialDeposit::get();
+		
+		// Select a sender
+		let u = components.iter().find(|&c| c.0 == BenchmarkParameter::U).unwrap().1;
+		let user = account::<T>("user", u);
+		let user_origin = RawOrigin::Signed(user.clone());
+
+		// Select a recipient
+		let recipient = account::<T>("recipient", u);
+		let recipient_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(recipient.clone());
+
+		// Get the existential deposit multiplier
+		let e = components.iter().find(|&c| c.0 == BenchmarkParameter::E).unwrap().1;
+
+		// Give the sender account max funds for transfer (their account will never reasonably be killed).
+		let _ = <Balances<T> as Currency<_>>::make_free_balance_be(&user, T::Balance::max_value());
+
+		// Give the recipient account existential deposit (thus their account already exists).
+		let _ = <Balances<T> as Currency<_>>::make_free_balance_be(&recipient, ed);
+
+		// Transfer e * existential deposit.
+		let transfer_amt = ed.saturating_mul(e.into());
+
+		// Return the `transfer` call
+		Ok((crate::Call::<T>::transfer(recipient_lookup, transfer_amt), user_origin))
+	}
+}
+
+// Benchmark `transfer_keep_alive` with the worst possible condition:
+// * The recipient account is created.
+struct TransferKeepAlive;
+impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for TransferKeepAlive {
+	fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)> {
+		vec![
+			// Existential Deposit Multiplier
+			(BenchmarkParameter::E, 2, 1000),
+			// User Seed
+			(BenchmarkParameter::U, 1, 1000),
+		]
+	}
+
+	fn instance(&self, components: &[(BenchmarkParameter, u32)])
+		-> Result<(crate::Call<T>, RawOrigin<T::AccountId>), &'static str>
+	{
+		// Constants
+		let ed = T::ExistentialDeposit::get();
+		
+		// Select a sender
+		let u = components.iter().find(|&c| c.0 == BenchmarkParameter::U).unwrap().1;
+		let user = account::<T>("user", u);
+		let user_origin = RawOrigin::Signed(user.clone());
+
+		// Select a recipient
+		let recipient = account::<T>("recipient", u);
+		let recipient_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(recipient.clone());
+
+		// Get the existential deposit multiplier
+		let e = components.iter().find(|&c| c.0 == BenchmarkParameter::E).unwrap().1;
+
+		// Give the sender account max funds, thus a transfer will not kill account.
+		let _ = <Balances<T> as Currency<_>>::make_free_balance_be(&user, T::Balance::max_value());
+
+		// Transfer e * existential deposit.
+		let transfer_amt = ed.saturating_mul(e.into());
+
+		// Return the `transfer_keep_alive` call
+		Ok((crate::Call::<T>::transfer_keep_alive(recipient_lookup, transfer_amt), user_origin))
+	}
+}
+
+// Benchmark `set_balance` coming from ROOT account. This always creates an account.
+struct SetBalance;
+impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for SetBalance {
+	fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)> {
+		vec![
+			// Existential Deposit Multiplier
+			(BenchmarkParameter::E, 2, 1000),
+			// User Seed
+			(BenchmarkParameter::U, 1, 1000),
+		]
+	}
+
+	fn instance(&self, components: &[(BenchmarkParameter, u32)])
+		-> Result<(crate::Call<T>, RawOrigin<T::AccountId>), &'static str>
+	{
+		// Constants
+		let ed = T::ExistentialDeposit::get();
+		
+		// Select a sender
+		let u = components.iter().find(|&c| c.0 == BenchmarkParameter::U).unwrap().1;
+		let user = account::<T>("user", u);
+		let user_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(user.clone());
+
+		// Get the existential deposit multiplier for free and reserved
+		let e = components.iter().find(|&c| c.0 == BenchmarkParameter::E).unwrap().1;
+		let balance_amt = ed.saturating_mul(e.into());
+
+		// Return the `set_balance` call
+		Ok((crate::Call::<T>::set_balance(user_lookup, balance_amt, balance_amt), RawOrigin::Root))
+	}
+}
+
+// Benchmark `set_balance` coming from ROOT account. This always kills an account.
+struct SetBalanceKilling;
+impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for SetBalanceKilling {
+	fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)> {
+		vec![
+			// Existential Deposit Multiplier
+			(BenchmarkParameter::E, 2, 1000),
+			// User Seed
+			(BenchmarkParameter::U, 1, 1000),
+		]
+	}
+
+	fn instance(&self, components: &[(BenchmarkParameter, u32)])
+		-> Result<(crate::Call<T>, RawOrigin<T::AccountId>), &'static str>
+	{
+		// Constants
+		let ed = T::ExistentialDeposit::get();
+		
+		// Select a sender
+		let u = components.iter().find(|&c| c.0 == BenchmarkParameter::U).unwrap().1;
+		let user = account::<T>("user", u);
+		let user_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(user.clone());
+
+		// Get the existential deposit multiplier for free and reserved
+		let e = components.iter().find(|&c| c.0 == BenchmarkParameter::E).unwrap().1;
+		// Give the user some initial balance
+		let balance_amt = ed.saturating_mul(e.into());
+		let _ = <Balances<T> as Currency<_>>::make_free_balance_be(&user, balance_amt);
+
+		// Return the `set_balance` call that will kill the account
+		Ok((crate::Call::<T>::set_balance(user_lookup, 0.into(), 0.into()), RawOrigin::Root))
+	}
+}
+
+// The list of available benchmarks for this pallet.
+enum SelectedBenchmark {
+	Transfer,
+	TransferBestCase,
+	TransferKeepAlive,
+	SetBalance,
+	SetBalanceKilling,
+}
+
+// Allow us to select a benchmark from the list of available benchmarks.
+impl<T: Trait> BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>> for SelectedBenchmark {
+	fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)> {
+		match self {
+			Self::Transfer => <Transfer as BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>>>::components(&Transfer),
+			Self::TransferBestCase => <TransferBestCase as BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>>>::components(&TransferBestCase),
+			Self::TransferKeepAlive => <TransferKeepAlive as BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>>>::components(&TransferKeepAlive),
+			Self::SetBalance => <SetBalance as BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>>>::components(&SetBalance),
+			Self::SetBalanceKilling => <SetBalanceKilling as BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>>>::components(&SetBalanceKilling),
+		}
+	}
+
+	fn instance(&self, components: &[(BenchmarkParameter, u32)])
+		-> Result<(crate::Call<T>, RawOrigin<T::AccountId>), &'static str>
+	{
+		match self {
+			Self::Transfer => <Transfer as BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>>>::instance(&Transfer, components),
+			Self::TransferBestCase => <TransferBestCase as BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>>>::instance(&TransferBestCase, components),
+			Self::TransferKeepAlive => <TransferKeepAlive as BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>>>::instance(&TransferKeepAlive, components),
+			Self::SetBalance => <SetBalance as BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>>>::instance(&SetBalance, components),
+			Self::SetBalanceKilling => <SetBalanceKilling as BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>>>::instance(&SetBalanceKilling, components),
+		}
+	}
+}
+
+impl<T: Trait> Benchmarking<BenchmarkResults> for Module<T> {
+	fn run_benchmark(extrinsic: Vec<u8>, steps: u32, repeat: u32) -> Result<Vec<BenchmarkResults>, &'static str> {
+		// Map the input to the selected benchmark.
+		let selected_benchmark = match extrinsic.as_slice() {
+			b"transfer" => SelectedBenchmark::Transfer,
+			b"transfer_best_case" => SelectedBenchmark::TransferBestCase,
+			b"transfer_keep_alive" => SelectedBenchmark::TransferKeepAlive,
+			b"set_balance" => SelectedBenchmark::SetBalance,
+			b"set_balance_killing" => SelectedBenchmark::SetBalanceKilling,
+			_ => return Err("Could not find extrinsic."),
+		};
+
+		// Warm up the DB
+		sp_io::benchmarking::commit_db();
+		sp_io::benchmarking::wipe_db();
+
+		let components = <SelectedBenchmark as BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>>>::components(&selected_benchmark);		
+		// results go here
+		let mut results: Vec<BenchmarkResults> = Vec::new();
+		// Select the component we will be benchmarking. Each component will be benchmarked.
+		for (name, low, high) in components.iter() {
+			// Create up to `STEPS` steps for that component between high and low.
+			let step_size = ((high - low) / steps).max(1);
+			let num_of_steps = (high - low) / step_size;
+			for s in 0..num_of_steps {
+				// This is the value we will be testing for component `name`
+				let component_value = low + step_size * s;
+
+				// Select the mid value for all the other components.
+				let c: Vec<(BenchmarkParameter, u32)> = components.iter()
+					.map(|(n, l, h)|
+						(*n, if n == name { component_value } else { (h - l) / 2 + l })
+					).collect();
+
+				// Run the benchmark `repeat` times.
+				for _r in 0..repeat {
+					// Set up the externalities environment for the setup we want to benchmark.
+					let (call, caller) = <SelectedBenchmark as BenchmarkingSetup<T, crate::Call<T>, RawOrigin<T::AccountId>>>::instance(&selected_benchmark, &c)?;
+					// Commit the externalities to the database, flushing the DB cache.
+					// This will enable worst case scenario for reading from the database.
+					sp_io::benchmarking::commit_db();
+					// Run the benchmark.
+					let start = sp_io::benchmarking::current_time();
+					call.dispatch(caller.clone().into())?;
+					let finish = sp_io::benchmarking::current_time();
+					let elapsed = finish - start;
+					sp_std::if_std!{
+						if let RawOrigin::Signed(who) = caller.clone() {
+							let balance = Account::<T>::get(&who).free;
+							println!("Free Balance {:?}", balance);
+						}
+					}
+					results.push((c.clone(), elapsed));
+					// Wipe the DB back to the genesis state.
+					sp_io::benchmarking::wipe_db();
+				}
+			}
+		}
+		return Ok(results);
+	}
+}
diff --git a/substrate/frame/balances/src/lib.rs b/substrate/frame/balances/src/lib.rs
index ac651a5c242..9ddfd81daee 100644
--- a/substrate/frame/balances/src/lib.rs
+++ b/substrate/frame/balances/src/lib.rs
@@ -153,6 +153,7 @@ mod mock;
 #[cfg(test)]
 mod tests;
 mod migration;
+mod benchmarking;
 
 use sp_std::prelude::*;
 use sp_std::{cmp, result, mem, fmt::Debug, ops::BitOr};
-- 
GitLab