// 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 . //! Balances pallet benchmarking. use super::*; use frame_system::RawOrigin; use sp_io::hashing::blake2_256; use frame_benchmarking::{ BenchmarkResults, BenchmarkParameter, Benchmarking, BenchmarkingSetup, benchmarking, }; use sp_runtime::traits::{Bounded, Dispatchable}; use crate::Module as Balances; // Support Functions fn account(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 BenchmarkingSetup, RawOrigin> 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, RawOrigin), &'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::("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 balance = ed.saturating_mul(e.into()); let _ = 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::("recipient", u); let recipient_lookup: ::Source = T::Lookup::unlookup(recipient); let transfer_amt = ed.saturating_mul((e - 1).into()) + 1.into(); // Return the `transfer` call Ok((crate::Call::::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 BenchmarkingSetup, RawOrigin> 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, RawOrigin), &'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::("user", u); let user_origin = RawOrigin::Signed(user.clone()); // Select a recipient let recipient = account::("recipient", u); let recipient_lookup: ::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 _ = as Currency<_>>::make_free_balance_be(&user, T::Balance::max_value()); // Give the recipient account existential deposit (thus their account already exists). let _ = 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::::transfer(recipient_lookup, transfer_amt), user_origin)) } } // Benchmark `transfer_keep_alive` with the worst possible condition: // * The recipient account is created. struct TransferKeepAlive; impl BenchmarkingSetup, RawOrigin> 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, RawOrigin), &'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::("user", u); let user_origin = RawOrigin::Signed(user.clone()); // Select a recipient let recipient = account::("recipient", u); let recipient_lookup: ::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 _ = 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::::transfer_keep_alive(recipient_lookup, transfer_amt), user_origin)) } } // Benchmark `set_balance` coming from ROOT account. This always creates an account. struct SetBalance; impl BenchmarkingSetup, RawOrigin> 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, RawOrigin), &'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::("user", u); let user_lookup: ::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::::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 BenchmarkingSetup, RawOrigin> 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, RawOrigin), &'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::("user", u); let user_lookup: ::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 _ = as Currency<_>>::make_free_balance_be(&user, balance_amt); // Return the `set_balance` call that will kill the account Ok((crate::Call::::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 BenchmarkingSetup, RawOrigin> for SelectedBenchmark { fn components(&self) -> Vec<(BenchmarkParameter, u32, u32)> { match self { Self::Transfer => , RawOrigin>>::components(&Transfer), Self::TransferBestCase => , RawOrigin>>::components(&TransferBestCase), Self::TransferKeepAlive => , RawOrigin>>::components(&TransferKeepAlive), Self::SetBalance => , RawOrigin>>::components(&SetBalance), Self::SetBalanceKilling => , RawOrigin>>::components(&SetBalanceKilling), } } fn instance(&self, components: &[(BenchmarkParameter, u32)]) -> Result<(crate::Call, RawOrigin), &'static str> { match self { Self::Transfer => , RawOrigin>>::instance(&Transfer, components), Self::TransferBestCase => , RawOrigin>>::instance(&TransferBestCase, components), Self::TransferKeepAlive => , RawOrigin>>::instance(&TransferKeepAlive, components), Self::SetBalance => , RawOrigin>>::instance(&SetBalance, components), Self::SetBalanceKilling => , RawOrigin>>::instance(&SetBalanceKilling, components), } } } impl Benchmarking for Module { fn run_benchmark(extrinsic: Vec, steps: u32, repeat: u32) -> Result, &'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 benchmarking::commit_db(); benchmarking::wipe_db(); let components = , RawOrigin>>::components(&selected_benchmark); // results go here let mut results: Vec = 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) = , RawOrigin>>::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. benchmarking::commit_db(); // Run the benchmark. let start = benchmarking::current_time(); call.dispatch(caller.clone().into())?; let finish = benchmarking::current_time(); let elapsed = finish - start; sp_std::if_std!{ if let RawOrigin::Signed(who) = caller.clone() { let balance = Account::::get(&who).free; println!("Free Balance {:?}", balance); } } results.push((c.clone(), elapsed)); // Wipe the DB back to the genesis state. benchmarking::wipe_db(); } } } return Ok(results); } }