mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
Fix nostd build of several crates (#4060)
Preparation for https://github.com/paritytech/polkadot-sdk/pull/3935 Changes: - Add some `default-features = false` for the case that a crate and that dependency both support nostd builds. - Shuffle files around of some benchmarking-only crates. These conditionally disabled the `cfg_attr` for nostd and pulled in libstd. Example [here](https://github.com/ggwpez/zepter/pull/95). The actual logic is moved into a `inner.rs` to preserve nostd capability of the crate in case the benchmarking feature is disabled. - Add some `use sp_std::vec` where needed. - Remove some `optional = true` in cases where it was not optional. - Removed one superfluous `cfg_attr(not(feature = "std"), no_std..`. All in all this should be logical no-op. --------- Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
This commit is contained in:
committed by
GitHub
parent
bfbf7f5d6f
commit
7a2c9d4a9a
@@ -0,0 +1,42 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Benchmarking setup for pallet-session.
|
||||
|
||||
use sp_std::{prelude::*, vec};
|
||||
|
||||
use frame_benchmarking::{benchmarks, whitelisted_caller};
|
||||
use frame_system::RawOrigin;
|
||||
use pallet_session::*;
|
||||
use parity_scale_codec::Decode;
|
||||
pub struct Pallet<T: Config>(pallet_session::Pallet<T>);
|
||||
pub trait Config: pallet_session::Config {}
|
||||
|
||||
benchmarks! {
|
||||
set_keys {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
frame_system::Pallet::<T>::inc_providers(&caller);
|
||||
let keys = T::Keys::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()).unwrap();
|
||||
let proof: Vec<u8> = vec![0,1,2,3];
|
||||
}: _(RawOrigin::Signed(caller), keys, proof)
|
||||
|
||||
purge_keys {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
frame_system::Pallet::<T>::inc_providers(&caller);
|
||||
let keys = T::Keys::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()).unwrap();
|
||||
let proof: Vec<u8> = vec![0,1,2,3];
|
||||
let _t = pallet_session::Pallet::<T>::set_keys(RawOrigin::Signed(caller.clone()).into(), keys, proof);
|
||||
}: _(RawOrigin::Signed(caller))
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@@ -13,31 +15,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Benchmarking setup for pallet-session
|
||||
//! Benchmarks for the Session Pallet.
|
||||
// This is separated into its own crate due to cyclic dependency issues.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
use sp_std::{prelude::*, vec};
|
||||
|
||||
use frame_benchmarking::{benchmarks, whitelisted_caller};
|
||||
use frame_system::RawOrigin;
|
||||
use pallet_session::*;
|
||||
use parity_scale_codec::Decode;
|
||||
pub struct Pallet<T: Config>(pallet_session::Pallet<T>);
|
||||
pub trait Config: pallet_session::Config {}
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub mod inner;
|
||||
|
||||
benchmarks! {
|
||||
set_keys {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
frame_system::Pallet::<T>::inc_providers(&caller);
|
||||
let keys = T::Keys::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()).unwrap();
|
||||
let proof: Vec<u8> = vec![0,1,2,3];
|
||||
}: _(RawOrigin::Signed(caller), keys, proof)
|
||||
|
||||
purge_keys {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
frame_system::Pallet::<T>::inc_providers(&caller);
|
||||
let keys = T::Keys::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()).unwrap();
|
||||
let proof: Vec<u8> = vec![0,1,2,3];
|
||||
let _t = pallet_session::Pallet::<T>::set_keys(RawOrigin::Signed(caller.clone()).into(), keys, proof);
|
||||
}: _(RawOrigin::Signed(caller))
|
||||
}
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub use inner::*;
|
||||
|
||||
@@ -37,7 +37,7 @@ pallet-nfts = { path = "../../../../../substrate/frame/nfts", default-features =
|
||||
pallet-nfts-runtime-api = { path = "../../../../../substrate/frame/nfts/runtime-api", default-features = false }
|
||||
pallet-proxy = { path = "../../../../../substrate/frame/proxy", default-features = false }
|
||||
pallet-session = { path = "../../../../../substrate/frame/session", default-features = false }
|
||||
pallet-state-trie-migration = { path = "../../../../../substrate/frame/state-trie-migration", default-features = false, optional = true }
|
||||
pallet-state-trie-migration = { path = "../../../../../substrate/frame/state-trie-migration", default-features = false }
|
||||
pallet-timestamp = { path = "../../../../../substrate/frame/timestamp", default-features = false }
|
||||
pallet-transaction-payment = { path = "../../../../../substrate/frame/transaction-payment", default-features = false }
|
||||
pallet-transaction-payment-rpc-runtime-api = { path = "../../../../../substrate/frame/transaction-payment/rpc/runtime-api", default-features = false }
|
||||
@@ -102,14 +102,6 @@ substrate-wasm-builder = { path = "../../../../../substrate/utils/wasm-builder",
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
# When enabled the `state_version` is set to `1`.
|
||||
# This means that the chain will start using the new state format. The migration is lazy, so
|
||||
# it requires to write a storage value to use the new state format. To migrate all the other
|
||||
# storage values that aren't touched the state migration pallet is added as well.
|
||||
# This pallet will migrate the entire state, controlled through some account.
|
||||
#
|
||||
# This feature should be removed when the main-net will be migrated.
|
||||
state-trie-version-1 = ["pallet-state-trie-migration"]
|
||||
runtime-benchmarks = [
|
||||
"assets-common/runtime-benchmarks",
|
||||
"cumulus-pallet-parachain-system/runtime-benchmarks",
|
||||
|
||||
@@ -107,7 +107,6 @@ impl_opaque_keys! {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "state-trie-version-1")]
|
||||
#[sp_version::runtime_version]
|
||||
pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
spec_name: create_runtime_str!("statemine"),
|
||||
@@ -120,19 +119,6 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
state_version: 1,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "state-trie-version-1"))]
|
||||
#[sp_version::runtime_version]
|
||||
pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
spec_name: create_runtime_str!("statemine"),
|
||||
impl_name: create_runtime_str!("statemine"),
|
||||
authoring_version: 1,
|
||||
spec_version: 1_010_000,
|
||||
impl_version: 0,
|
||||
apis: RUNTIME_API_VERSIONS,
|
||||
transaction_version: 14,
|
||||
state_version: 0,
|
||||
};
|
||||
|
||||
/// The version information used to identify this runtime when compiled natively.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn native_version() -> NativeVersion {
|
||||
@@ -953,7 +939,6 @@ construct_runtime!(
|
||||
PoolAssets: pallet_assets::<Instance3> = 55,
|
||||
AssetConversion: pallet_asset_conversion = 56,
|
||||
|
||||
#[cfg(feature = "state-trie-version-1")]
|
||||
StateTrieMigration: pallet_state_trie_migration = 70,
|
||||
|
||||
// TODO: the pallet instance should be removed once all pools have migrated
|
||||
@@ -1695,7 +1680,6 @@ cumulus_pallet_parachain_system::register_validate_block! {
|
||||
BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::<Runtime, Executive>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "state-trie-version-1")]
|
||||
parameter_types! {
|
||||
// The deposit configuration for the singed migration. Specially if you want to allow any signed account to do the migration (see `SignedFilter`, these deposits should be high)
|
||||
pub const MigrationSignedDepositPerItem: Balance = CENTS;
|
||||
@@ -1703,7 +1687,6 @@ parameter_types! {
|
||||
pub const MigrationMaxKeyLen: u32 = 512;
|
||||
}
|
||||
|
||||
#[cfg(feature = "state-trie-version-1")]
|
||||
impl pallet_state_trie_migration::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Currency = Balances;
|
||||
@@ -1721,13 +1704,11 @@ impl pallet_state_trie_migration::Config for Runtime {
|
||||
type MaxKeyLen = MigrationMaxKeyLen;
|
||||
}
|
||||
|
||||
#[cfg(feature = "state-trie-version-1")]
|
||||
frame_support::ord_parameter_types! {
|
||||
pub const MigController: AccountId = AccountId::from(hex_literal::hex!("8458ed39dc4b6f6c7255f7bc42be50c2967db126357c999d44e12ca7ac80dc52"));
|
||||
pub const RootMigController: AccountId = AccountId::from(hex_literal::hex!("8458ed39dc4b6f6c7255f7bc42be50c2967db126357c999d44e12ca7ac80dc52"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "state-trie-version-1")]
|
||||
#[test]
|
||||
fn ensure_key_ss58() {
|
||||
use frame_support::traits::SortedMembers;
|
||||
|
||||
@@ -22,8 +22,8 @@ frame-system-rpc-runtime-api = { path = "../../../../../substrate/frame/system/r
|
||||
frame-system-benchmarking = { path = "../../../../../substrate/frame/system/benchmarking", default-features = false, optional = true }
|
||||
frame-try-runtime = { path = "../../../../../substrate/frame/try-runtime", default-features = false, optional = true }
|
||||
pallet-aura = { path = "../../../../../substrate/frame/aura", default-features = false }
|
||||
pallet-glutton = { path = "../../../../../substrate/frame/glutton", default-features = false, optional = true }
|
||||
pallet-sudo = { path = "../../../../../substrate/frame/sudo", default-features = false, optional = true }
|
||||
pallet-glutton = { path = "../../../../../substrate/frame/glutton", default-features = false }
|
||||
pallet-sudo = { path = "../../../../../substrate/frame/sudo", default-features = false }
|
||||
pallet-timestamp = { path = "../../../../../substrate/frame/timestamp", default-features = false }
|
||||
sp-api = { path = "../../../../../substrate/primitives/api", default-features = false }
|
||||
sp-block-builder = { path = "../../../../../substrate/primitives/block-builder", default-features = false }
|
||||
|
||||
@@ -17,8 +17,8 @@ scale-info = { version = "2.11.1", default-features = false, features = ["derive
|
||||
# Substrate
|
||||
sp-core = { path = "../../../substrate/primitives/core", default-features = false }
|
||||
sp-inherents = { path = "../../../substrate/primitives/inherents", default-features = false }
|
||||
sp-runtime = { path = "../../../substrate/primitives/runtime", optional = true }
|
||||
sp-state-machine = { path = "../../../substrate/primitives/state-machine", optional = true }
|
||||
sp-runtime = { path = "../../../substrate/primitives/runtime", optional = true, default-features = false }
|
||||
sp-state-machine = { path = "../../../substrate/primitives/state-machine", optional = true, default-features = false }
|
||||
sp-std = { path = "../../../substrate/primitives/std", default-features = false }
|
||||
sp-trie = { path = "../../../substrate/primitives/trie", default-features = false }
|
||||
|
||||
@@ -34,6 +34,8 @@ std = [
|
||||
"scale-info/std",
|
||||
"sp-core/std",
|
||||
"sp-inherents/std",
|
||||
"sp-runtime?/std",
|
||||
"sp-state-machine?/std",
|
||||
"sp-std/std",
|
||||
"sp-trie/std",
|
||||
]
|
||||
|
||||
@@ -26,7 +26,7 @@ sp-arithmetic = { path = "../../substrate/primitives/arithmetic", default-featur
|
||||
sp-authority-discovery = { path = "../../substrate/primitives/authority-discovery", default-features = false, features = ["serde"] }
|
||||
sp-consensus-slots = { path = "../../substrate/primitives/consensus/slots", default-features = false, features = ["serde"] }
|
||||
sp-io = { path = "../../substrate/primitives/io", default-features = false }
|
||||
sp-keystore = { path = "../../substrate/primitives/keystore", optional = true }
|
||||
sp-keystore = { path = "../../substrate/primitives/keystore", optional = true, default-features = false }
|
||||
sp-staking = { path = "../../substrate/primitives/staking", default-features = false, features = ["serde"] }
|
||||
sp-std = { package = "sp-std", path = "../../substrate/primitives/std", default-features = false }
|
||||
|
||||
@@ -53,6 +53,7 @@ std = [
|
||||
"sp-consensus-slots/std",
|
||||
"sp-io/std",
|
||||
"sp-keystore",
|
||||
"sp-keystore?/std",
|
||||
"sp-staking/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
|
||||
@@ -28,7 +28,7 @@ sp-runtime = { path = "../../../substrate/primitives/runtime", default-features
|
||||
sp-session = { path = "../../../substrate/primitives/session", default-features = false }
|
||||
sp-staking = { path = "../../../substrate/primitives/staking", default-features = false, features = ["serde"] }
|
||||
sp-core = { path = "../../../substrate/primitives/core", default-features = false, features = ["serde"] }
|
||||
sp-keystore = { path = "../../../substrate/primitives/keystore", optional = true }
|
||||
sp-keystore = { path = "../../../substrate/primitives/keystore", optional = true, default-features = false }
|
||||
sp-application-crypto = { path = "../../../substrate/primitives/application-crypto", default-features = false, optional = true }
|
||||
sp-tracing = { path = "../../../substrate/primitives/tracing", default-features = false, optional = true }
|
||||
sp-arithmetic = { path = "../../../substrate/primitives/arithmetic", default-features = false }
|
||||
@@ -108,6 +108,7 @@ std = [
|
||||
"sp-core/std",
|
||||
"sp-io/std",
|
||||
"sp-keystore",
|
||||
"sp-keystore?/std",
|
||||
"sp-runtime/std",
|
||||
"sp-session/std",
|
||||
"sp-staking/std",
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![cfg(test)]
|
||||
|
||||
use codec::Encode;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
title: "Fix nostd build of several crates"
|
||||
|
||||
doc:
|
||||
- audience: Runtime Dev
|
||||
description: |
|
||||
Fixes feature and dependency configuration of several crate. This should allow for better no-std build capabilities.
|
||||
|
||||
crates:
|
||||
- name: cumulus-pallet-session-benchmarking
|
||||
bump: patch
|
||||
- name: asset-hub-rococo-runtime
|
||||
bump: patch
|
||||
- name: glutton-westend-runtime
|
||||
bump: patch
|
||||
- name: cumulus-primitives-parachain-inherent
|
||||
bump: patch
|
||||
- name: polkadot-primitives
|
||||
bump: patch
|
||||
- name: polkadot-runtime-parachains
|
||||
bump: patch
|
||||
- name: xcm-executor-integration-tests
|
||||
bump: patch
|
||||
- name: pallet-atomic-swap
|
||||
bump: patch
|
||||
- name: pallet-election-provider-support-benchmarking
|
||||
bump: patch
|
||||
- name: pallet-dev-mode
|
||||
bump: patch
|
||||
- name: pallet-example-offchain-worker
|
||||
bump: patch
|
||||
- name: pallet-indices
|
||||
bump: patch
|
||||
- name: pallet-nomination-pools
|
||||
bump: patch
|
||||
- name: pallet-nomination-pools-benchmarking
|
||||
bump: patch
|
||||
- name: pallet-offences-benchmarking
|
||||
bump: patch
|
||||
- name: pallet-root-offences
|
||||
bump: patch
|
||||
- name: pallet-session-benchmarking
|
||||
bump: patch
|
||||
- name: frame-system-benchmarking
|
||||
bump: patch
|
||||
- name: sp-consensus-babe
|
||||
bump: patch
|
||||
- name: sp-consensus-babe
|
||||
bump: patch
|
||||
- name: sp-core
|
||||
bump: patch
|
||||
- name: sp-session
|
||||
bump: patch
|
||||
- name: sp-transaction-storage-proof
|
||||
bump: patch
|
||||
@@ -6,7 +6,7 @@ edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
homepage = "paritytech.github.io"
|
||||
repository.workspace = true
|
||||
description = "The single package to get you started with building frame pallets and runtimes"
|
||||
description = "Experimental: The single package to get you started with building frame pallets and runtimes"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -58,6 +58,7 @@ use frame_system::pallet_prelude::BlockNumberFor;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_io::hashing::blake2_256;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
/// Pending atomic swap operation.
|
||||
#[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Election provider support pallet benchmarking.
|
||||
//! This is separated into its own crate to avoid bloating the size of the runtime.
|
||||
|
||||
use codec::Decode;
|
||||
use frame_benchmarking::v1::benchmarks;
|
||||
use frame_election_provider_support::{NposSolver, PhragMMS, SequentialPhragmen};
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
pub struct Pallet<T: Config>(frame_system::Pallet<T>);
|
||||
pub trait Config: frame_system::Config {}
|
||||
|
||||
const VOTERS: [u32; 2] = [1_000, 2_000];
|
||||
const TARGETS: [u32; 2] = [500, 1_000];
|
||||
const VOTES_PER_VOTER: [u32; 2] = [5, 16];
|
||||
|
||||
const SEED: u32 = 999;
|
||||
fn set_up_voters_targets<AccountId: Decode + Clone>(
|
||||
voters_len: u32,
|
||||
targets_len: u32,
|
||||
degree: usize,
|
||||
) -> (Vec<(AccountId, u64, impl IntoIterator<Item = AccountId>)>, Vec<AccountId>) {
|
||||
// fill targets.
|
||||
let mut targets = (0..targets_len)
|
||||
.map(|i| frame_benchmarking::account::<AccountId>("Target", i, SEED))
|
||||
.collect::<Vec<_>>();
|
||||
assert!(targets.len() > degree, "we should always have enough voters to fill");
|
||||
targets.truncate(degree);
|
||||
|
||||
// fill voters.
|
||||
let voters = (0..voters_len)
|
||||
.map(|i| {
|
||||
let voter = frame_benchmarking::account::<AccountId>("Voter", i, SEED);
|
||||
(voter, 1_000, targets.clone())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
(voters, targets)
|
||||
}
|
||||
|
||||
benchmarks! {
|
||||
phragmen {
|
||||
// number of votes in snapshot.
|
||||
let v in (VOTERS[0]) .. VOTERS[1];
|
||||
// number of targets in snapshot.
|
||||
let t in (TARGETS[0]) .. TARGETS[1];
|
||||
// number of votes per voter (ie the degree).
|
||||
let d in (VOTES_PER_VOTER[0]) .. VOTES_PER_VOTER[1];
|
||||
|
||||
let (voters, targets) = set_up_voters_targets::<T::AccountId>(v, t, d as usize);
|
||||
}: {
|
||||
assert!(
|
||||
SequentialPhragmen::<T::AccountId, sp_runtime::Perbill>
|
||||
::solve(d as usize, targets, voters).is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
phragmms {
|
||||
// number of votes in snapshot.
|
||||
let v in (VOTERS[0]) .. VOTERS[1];
|
||||
// number of targets in snapshot.
|
||||
let t in (TARGETS[0]) .. TARGETS[1];
|
||||
// number of votes per voter (ie the degree).
|
||||
let d in (VOTES_PER_VOTER[0]) .. VOTES_PER_VOTER[1];
|
||||
|
||||
let (voters, targets) = set_up_voters_targets::<T::AccountId>(v, t, d as usize);
|
||||
}: {
|
||||
assert!(
|
||||
PhragMMS::<T::AccountId, sp_runtime::Perbill>
|
||||
::solve(d as usize, targets, voters).is_ok()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -16,77 +16,11 @@
|
||||
// limitations under the License.
|
||||
|
||||
//! Election provider support pallet benchmarking.
|
||||
//! This is separated into its own crate to avoid bloating the size of the runtime.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use codec::Decode;
|
||||
use frame_benchmarking::v1::benchmarks;
|
||||
use frame_election_provider_support::{NposSolver, PhragMMS, SequentialPhragmen};
|
||||
use sp_std::vec::Vec;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub mod inner;
|
||||
|
||||
pub struct Pallet<T: Config>(frame_system::Pallet<T>);
|
||||
pub trait Config: frame_system::Config {}
|
||||
|
||||
const VOTERS: [u32; 2] = [1_000, 2_000];
|
||||
const TARGETS: [u32; 2] = [500, 1_000];
|
||||
const VOTES_PER_VOTER: [u32; 2] = [5, 16];
|
||||
|
||||
const SEED: u32 = 999;
|
||||
fn set_up_voters_targets<AccountId: Decode + Clone>(
|
||||
voters_len: u32,
|
||||
targets_len: u32,
|
||||
degree: usize,
|
||||
) -> (Vec<(AccountId, u64, impl IntoIterator<Item = AccountId>)>, Vec<AccountId>) {
|
||||
// fill targets.
|
||||
let mut targets = (0..targets_len)
|
||||
.map(|i| frame_benchmarking::account::<AccountId>("Target", i, SEED))
|
||||
.collect::<Vec<_>>();
|
||||
assert!(targets.len() > degree, "we should always have enough voters to fill");
|
||||
targets.truncate(degree);
|
||||
|
||||
// fill voters.
|
||||
let voters = (0..voters_len)
|
||||
.map(|i| {
|
||||
let voter = frame_benchmarking::account::<AccountId>("Voter", i, SEED);
|
||||
(voter, 1_000, targets.clone())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
(voters, targets)
|
||||
}
|
||||
|
||||
benchmarks! {
|
||||
phragmen {
|
||||
// number of votes in snapshot.
|
||||
let v in (VOTERS[0]) .. VOTERS[1];
|
||||
// number of targets in snapshot.
|
||||
let t in (TARGETS[0]) .. TARGETS[1];
|
||||
// number of votes per voter (ie the degree).
|
||||
let d in (VOTES_PER_VOTER[0]) .. VOTES_PER_VOTER[1];
|
||||
|
||||
let (voters, targets) = set_up_voters_targets::<T::AccountId>(v, t, d as usize);
|
||||
}: {
|
||||
assert!(
|
||||
SequentialPhragmen::<T::AccountId, sp_runtime::Perbill>
|
||||
::solve(d as usize, targets, voters).is_ok()
|
||||
);
|
||||
}
|
||||
|
||||
phragmms {
|
||||
// number of votes in snapshot.
|
||||
let v in (VOTERS[0]) .. VOTERS[1];
|
||||
// number of targets in snapshot.
|
||||
let t in (TARGETS[0]) .. TARGETS[1];
|
||||
// number of votes per voter (ie the degree).
|
||||
let d in (VOTES_PER_VOTER[0]) .. VOTES_PER_VOTER[1];
|
||||
|
||||
let (voters, targets) = set_up_voters_targets::<T::AccountId>(v, t, d as usize);
|
||||
}: {
|
||||
assert!(
|
||||
PhragMMS::<T::AccountId, sp_runtime::Perbill>
|
||||
::solve(d as usize, targets, voters).is_ok()
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub use inner::*;
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
use frame_support::dispatch::DispatchResult;
|
||||
use frame_system::ensure_signed;
|
||||
use sp_std::{vec, vec::Vec};
|
||||
|
||||
// Re-export pallet items so that they can be accessed from the crate namespace.
|
||||
pub use pallet::*;
|
||||
|
||||
@@ -24,7 +24,7 @@ frame-support = { path = "../../support", default-features = false }
|
||||
frame-system = { path = "../../system", default-features = false }
|
||||
sp-core = { path = "../../../primitives/core", default-features = false }
|
||||
sp-io = { path = "../../../primitives/io", default-features = false }
|
||||
sp-keystore = { path = "../../../primitives/keystore", optional = true }
|
||||
sp-keystore = { path = "../../../primitives/keystore", optional = true, default-features = false }
|
||||
sp-runtime = { path = "../../../primitives/runtime", default-features = false }
|
||||
sp-std = { path = "../../../primitives/std", default-features = false }
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ frame-support = { path = "../support", default-features = false }
|
||||
frame-system = { path = "../system", default-features = false }
|
||||
sp-core = { path = "../../primitives/core", default-features = false }
|
||||
sp-io = { path = "../../primitives/io", default-features = false }
|
||||
sp-keyring = { path = "../../primitives/keyring", optional = true }
|
||||
sp-keyring = { path = "../../primitives/keyring", optional = true, default-features = false }
|
||||
sp-runtime = { path = "../../primitives/runtime", default-features = false }
|
||||
sp-std = { path = "../../primitives/std", default-features = false }
|
||||
|
||||
@@ -42,6 +42,7 @@ std = [
|
||||
"sp-core/std",
|
||||
"sp-io/std",
|
||||
"sp-keyring",
|
||||
"sp-keyring?/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
|
||||
@@ -34,8 +34,8 @@ sp-io = { path = "../../primitives/io", default-features = false }
|
||||
log = { workspace = true }
|
||||
|
||||
# Optional: use for testing and/or fuzzing
|
||||
pallet-balances = { path = "../balances", optional = true }
|
||||
sp-tracing = { path = "../../primitives/tracing", optional = true }
|
||||
pallet-balances = { path = "../balances", optional = true, default-features = false }
|
||||
sp-tracing = { path = "../../primitives/tracing", optional = true, default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
pallet-balances = { path = "../balances" }
|
||||
|
||||
@@ -0,0 +1,846 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Benchmarks for the nomination pools coupled with the staking and bags list pallets.
|
||||
|
||||
use frame_benchmarking::v1::{account, whitelist_account};
|
||||
use frame_election_provider_support::SortedListProvider;
|
||||
use frame_support::{
|
||||
assert_ok, ensure,
|
||||
traits::{
|
||||
fungible::{Inspect, Mutate, Unbalanced},
|
||||
Get,
|
||||
},
|
||||
};
|
||||
use frame_system::RawOrigin as RuntimeOrigin;
|
||||
use pallet_nomination_pools::{
|
||||
BalanceOf, BondExtra, BondedPoolInner, BondedPools, ClaimPermission, ClaimPermissions,
|
||||
Commission, CommissionChangeRate, CommissionClaimPermission, ConfigOp, GlobalMaxCommission,
|
||||
MaxPoolMembers, MaxPoolMembersPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond,
|
||||
Pallet as Pools, PoolMembers, PoolRoles, PoolState, RewardPools, SubPoolsStorage,
|
||||
};
|
||||
use pallet_staking::MaxNominationsOf;
|
||||
use sp_runtime::{
|
||||
traits::{Bounded, StaticLookup, Zero},
|
||||
Perbill,
|
||||
};
|
||||
use sp_staking::{EraIndex, StakingInterface};
|
||||
use sp_std::{vec, vec::Vec};
|
||||
// `frame_benchmarking::benchmarks!` macro needs this
|
||||
use pallet_nomination_pools::Call;
|
||||
|
||||
type CurrencyOf<T> = <T as pallet_nomination_pools::Config>::Currency;
|
||||
|
||||
const USER_SEED: u32 = 0;
|
||||
const MAX_SPANS: u32 = 100;
|
||||
|
||||
pub(crate) type VoterBagsListInstance = pallet_bags_list::Instance1;
|
||||
pub trait Config:
|
||||
pallet_nomination_pools::Config
|
||||
+ pallet_staking::Config
|
||||
+ pallet_bags_list::Config<VoterBagsListInstance>
|
||||
{
|
||||
}
|
||||
|
||||
pub struct Pallet<T: Config>(Pools<T>);
|
||||
|
||||
fn create_funded_user_with_balance<T: pallet_nomination_pools::Config>(
|
||||
string: &'static str,
|
||||
n: u32,
|
||||
balance: BalanceOf<T>,
|
||||
) -> T::AccountId {
|
||||
let user = account(string, n, USER_SEED);
|
||||
T::Currency::set_balance(&user, balance);
|
||||
user
|
||||
}
|
||||
|
||||
// Create a bonded pool account, bonding `balance` and giving the account `balance * 2` free
|
||||
// balance.
|
||||
fn create_pool_account<T: pallet_nomination_pools::Config>(
|
||||
n: u32,
|
||||
balance: BalanceOf<T>,
|
||||
commission: Option<Perbill>,
|
||||
) -> (T::AccountId, T::AccountId) {
|
||||
let ed = CurrencyOf::<T>::minimum_balance();
|
||||
let pool_creator: T::AccountId =
|
||||
create_funded_user_with_balance::<T>("pool_creator", n, ed + balance * 2u32.into());
|
||||
let pool_creator_lookup = T::Lookup::unlookup(pool_creator.clone());
|
||||
|
||||
Pools::<T>::create(
|
||||
RuntimeOrigin::Signed(pool_creator.clone()).into(),
|
||||
balance,
|
||||
pool_creator_lookup.clone(),
|
||||
pool_creator_lookup.clone(),
|
||||
pool_creator_lookup,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if let Some(c) = commission {
|
||||
let pool_id = pallet_nomination_pools::LastPoolId::<T>::get();
|
||||
Pools::<T>::set_commission(
|
||||
RuntimeOrigin::Signed(pool_creator.clone()).into(),
|
||||
pool_id,
|
||||
Some((c, pool_creator.clone())),
|
||||
)
|
||||
.expect("pool just created, commission can be set by root; qed");
|
||||
}
|
||||
|
||||
let pool_account = pallet_nomination_pools::BondedPools::<T>::iter()
|
||||
.find(|(_, bonded_pool)| bonded_pool.roles.depositor == pool_creator)
|
||||
.map(|(pool_id, _)| Pools::<T>::create_bonded_account(pool_id))
|
||||
.expect("pool_creator created a pool above");
|
||||
|
||||
(pool_creator, pool_account)
|
||||
}
|
||||
|
||||
fn vote_to_balance<T: pallet_nomination_pools::Config>(
|
||||
vote: u64,
|
||||
) -> Result<BalanceOf<T>, &'static str> {
|
||||
vote.try_into().map_err(|_| "could not convert u64 to Balance")
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
struct ListScenario<T: pallet_nomination_pools::Config> {
|
||||
/// Stash/Controller that is expected to be moved.
|
||||
origin1: T::AccountId,
|
||||
creator1: T::AccountId,
|
||||
dest_weight: BalanceOf<T>,
|
||||
origin1_member: Option<T::AccountId>,
|
||||
}
|
||||
|
||||
impl<T: Config> ListScenario<T> {
|
||||
/// An expensive scenario for bags-list implementation:
|
||||
///
|
||||
/// - the node to be updated (r) is the head of a bag that has at least one other node. The bag
|
||||
/// itself will need to be read and written to update its head. The node pointed to by r.next
|
||||
/// will need to be read and written as it will need to have its prev pointer updated. Note
|
||||
/// that there are two other worst case scenarios for bag removal: 1) the node is a tail and
|
||||
/// 2) the node is a middle node with prev and next; all scenarios end up with the same number
|
||||
/// of storage reads and writes.
|
||||
///
|
||||
/// - the destination bag has at least one node, which will need its next pointer updated.
|
||||
pub(crate) fn new(
|
||||
origin_weight: BalanceOf<T>,
|
||||
is_increase: bool,
|
||||
) -> Result<Self, &'static str> {
|
||||
ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0");
|
||||
|
||||
ensure!(
|
||||
pallet_nomination_pools::MaxPools::<T>::get().unwrap_or(0) >= 3,
|
||||
"must allow at least three pools for benchmarks"
|
||||
);
|
||||
|
||||
// Burn the entire issuance.
|
||||
CurrencyOf::<T>::set_total_issuance(Zero::zero());
|
||||
|
||||
// Create accounts with the origin weight
|
||||
let (pool_creator1, pool_origin1) =
|
||||
create_pool_account::<T>(USER_SEED + 1, origin_weight, Some(Perbill::from_percent(50)));
|
||||
|
||||
T::Staking::nominate(
|
||||
&pool_origin1,
|
||||
// NOTE: these don't really need to be validators.
|
||||
vec![account("random_validator", 0, USER_SEED)],
|
||||
)?;
|
||||
|
||||
let (_, pool_origin2) =
|
||||
create_pool_account::<T>(USER_SEED + 2, origin_weight, Some(Perbill::from_percent(50)));
|
||||
|
||||
T::Staking::nominate(
|
||||
&pool_origin2,
|
||||
vec![account("random_validator", 0, USER_SEED)].clone(),
|
||||
)?;
|
||||
|
||||
// Find a destination weight that will trigger the worst case scenario
|
||||
let dest_weight_as_vote = <T as pallet_staking::Config>::VoterList::score_update_worst_case(
|
||||
&pool_origin1,
|
||||
is_increase,
|
||||
);
|
||||
|
||||
let dest_weight: BalanceOf<T> =
|
||||
dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?;
|
||||
|
||||
// Create an account with the worst case destination weight
|
||||
let (_, pool_dest1) =
|
||||
create_pool_account::<T>(USER_SEED + 3, dest_weight, Some(Perbill::from_percent(50)));
|
||||
|
||||
T::Staking::nominate(&pool_dest1, vec![account("random_validator", 0, USER_SEED)])?;
|
||||
|
||||
let weight_of = pallet_staking::Pallet::<T>::weight_of_fn();
|
||||
assert_eq!(vote_to_balance::<T>(weight_of(&pool_origin1)).unwrap(), origin_weight);
|
||||
assert_eq!(vote_to_balance::<T>(weight_of(&pool_origin2)).unwrap(), origin_weight);
|
||||
assert_eq!(vote_to_balance::<T>(weight_of(&pool_dest1)).unwrap(), dest_weight);
|
||||
|
||||
Ok(ListScenario {
|
||||
origin1: pool_origin1,
|
||||
creator1: pool_creator1,
|
||||
dest_weight,
|
||||
origin1_member: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn add_joiner(mut self, amount: BalanceOf<T>) -> Self {
|
||||
let amount = MinJoinBond::<T>::get()
|
||||
.max(CurrencyOf::<T>::minimum_balance())
|
||||
// Max `amount` with minimum thresholds for account balance and joining a pool
|
||||
// to ensure 1) the user can be created and 2) can join the pool
|
||||
.max(amount);
|
||||
|
||||
let joiner: T::AccountId = account("joiner", USER_SEED, 0);
|
||||
self.origin1_member = Some(joiner.clone());
|
||||
CurrencyOf::<T>::set_balance(&joiner, amount * 2u32.into());
|
||||
|
||||
let original_bonded = T::Staking::active_stake(&self.origin1).unwrap();
|
||||
|
||||
// Unbond `amount` from the underlying pool account so when the member joins
|
||||
// we will maintain `current_bonded`.
|
||||
T::Staking::unbond(&self.origin1, amount).expect("the pool was created in `Self::new`.");
|
||||
|
||||
// Account pool points for the unbonded balance.
|
||||
BondedPools::<T>::mutate(&1, |maybe_pool| {
|
||||
maybe_pool.as_mut().map(|pool| pool.points -= amount)
|
||||
});
|
||||
|
||||
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), amount, 1).unwrap();
|
||||
|
||||
// check that the vote weight is still the same as the original bonded
|
||||
let weight_of = pallet_staking::Pallet::<T>::weight_of_fn();
|
||||
assert_eq!(vote_to_balance::<T>(weight_of(&self.origin1)).unwrap(), original_bonded);
|
||||
|
||||
// check the member was added correctly
|
||||
let member = PoolMembers::<T>::get(&joiner).unwrap();
|
||||
assert_eq!(member.points, amount);
|
||||
assert_eq!(member.pool_id, 1);
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
frame_benchmarking::benchmarks! {
|
||||
join {
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
|
||||
// setup the worst case list scenario.
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&scenario.origin1).unwrap(),
|
||||
origin_weight
|
||||
);
|
||||
|
||||
let max_additional = scenario.dest_weight - origin_weight;
|
||||
let joiner_free = CurrencyOf::<T>::minimum_balance() + max_additional;
|
||||
|
||||
let joiner: T::AccountId
|
||||
= create_funded_user_with_balance::<T>("joiner", 0, joiner_free);
|
||||
|
||||
whitelist_account!(joiner);
|
||||
}: _(RuntimeOrigin::Signed(joiner.clone()), max_additional, 1)
|
||||
verify {
|
||||
assert_eq!(CurrencyOf::<T>::balance(&joiner), joiner_free - max_additional);
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&scenario.origin1).unwrap(),
|
||||
scenario.dest_weight
|
||||
);
|
||||
}
|
||||
|
||||
bond_extra_transfer {
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
let extra = scenario.dest_weight - origin_weight;
|
||||
|
||||
// creator of the src pool will bond-extra, bumping itself to dest bag.
|
||||
|
||||
}: bond_extra(RuntimeOrigin::Signed(scenario.creator1.clone()), BondExtra::FreeBalance(extra))
|
||||
verify {
|
||||
assert!(
|
||||
T::Staking::active_stake(&scenario.origin1).unwrap() >=
|
||||
scenario.dest_weight
|
||||
);
|
||||
}
|
||||
|
||||
bond_extra_other {
|
||||
let claimer: T::AccountId = account("claimer", USER_SEED + 4, 0);
|
||||
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
let extra = (scenario.dest_weight - origin_weight).max(CurrencyOf::<T>::minimum_balance());
|
||||
|
||||
// set claim preferences to `PermissionlessAll` to any account to bond extra on member's behalf.
|
||||
let _ = Pools::<T>::set_claim_permission(RuntimeOrigin::Signed(scenario.creator1.clone()).into(), ClaimPermission::PermissionlessAll);
|
||||
|
||||
// transfer exactly `extra` to the depositor of the src pool (1),
|
||||
let reward_account1 = Pools::<T>::create_reward_account(1);
|
||||
assert!(extra >= CurrencyOf::<T>::minimum_balance());
|
||||
let _ = CurrencyOf::<T>::mint_into(&reward_account1, extra);
|
||||
|
||||
}: _(RuntimeOrigin::Signed(claimer), T::Lookup::unlookup(scenario.creator1.clone()), BondExtra::Rewards)
|
||||
verify {
|
||||
// commission of 50% deducted here.
|
||||
assert!(
|
||||
T::Staking::active_stake(&scenario.origin1).unwrap() >=
|
||||
scenario.dest_weight / 2u32.into()
|
||||
);
|
||||
}
|
||||
|
||||
claim_payout {
|
||||
let claimer: T::AccountId = account("claimer", USER_SEED + 4, 0);
|
||||
let commission = Perbill::from_percent(50);
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let ed = CurrencyOf::<T>::minimum_balance();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, origin_weight, Some(commission));
|
||||
let reward_account = Pools::<T>::create_reward_account(1);
|
||||
|
||||
// Send funds to the reward account of the pool
|
||||
CurrencyOf::<T>::set_balance(&reward_account, ed + origin_weight);
|
||||
|
||||
// set claim preferences to `PermissionlessAll` so any account can claim rewards on member's
|
||||
// behalf.
|
||||
let _ = Pools::<T>::set_claim_permission(RuntimeOrigin::Signed(depositor.clone()).into(), ClaimPermission::PermissionlessAll);
|
||||
|
||||
// Sanity check
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&depositor),
|
||||
origin_weight
|
||||
);
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:claim_payout_other(RuntimeOrigin::Signed(claimer), depositor.clone())
|
||||
verify {
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&depositor),
|
||||
origin_weight + commission * origin_weight
|
||||
);
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&reward_account),
|
||||
ed + commission * origin_weight
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unbond {
|
||||
// The weight the nominator will start at. The value used here is expected to be
|
||||
// significantly higher than the first position in a list (e.g. the first bag threshold).
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 200u32.into();
|
||||
let scenario = ListScenario::<T>::new(origin_weight, false)?;
|
||||
let amount = origin_weight - scenario.dest_weight;
|
||||
|
||||
let scenario = scenario.add_joiner(amount);
|
||||
let member_id = scenario.origin1_member.unwrap().clone();
|
||||
let member_id_lookup = T::Lookup::unlookup(member_id.clone());
|
||||
let all_points = PoolMembers::<T>::get(&member_id).unwrap().points;
|
||||
whitelist_account!(member_id);
|
||||
}: _(RuntimeOrigin::Signed(member_id.clone()), member_id_lookup, all_points)
|
||||
verify {
|
||||
let bonded_after = T::Staking::active_stake(&scenario.origin1).unwrap();
|
||||
// We at least went down to the destination bag
|
||||
assert!(bonded_after <= scenario.dest_weight);
|
||||
let member = PoolMembers::<T>::get(
|
||||
&member_id
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
member.unbonding_eras.keys().cloned().collect::<Vec<_>>(),
|
||||
vec![0 + T::Staking::bonding_duration()]
|
||||
);
|
||||
assert_eq!(
|
||||
member.unbonding_eras.values().cloned().collect::<Vec<_>>(),
|
||||
vec![all_points]
|
||||
);
|
||||
}
|
||||
|
||||
pool_withdraw_unbonded {
|
||||
let s in 0 .. MAX_SPANS;
|
||||
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
|
||||
// Add a new member
|
||||
let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
|
||||
let joiner = create_funded_user_with_balance::<T>("joiner", 0, min_join_bond * 2u32.into());
|
||||
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1)
|
||||
.unwrap();
|
||||
|
||||
// Sanity check join worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
min_create_bond + min_join_bond
|
||||
);
|
||||
assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
|
||||
|
||||
// Unbond the new member
|
||||
Pools::<T>::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone()).unwrap();
|
||||
|
||||
// Sanity check that unbond worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
min_create_bond
|
||||
);
|
||||
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
|
||||
// Set the current era
|
||||
pallet_staking::CurrentEra::<T>::put(EraIndex::max_value());
|
||||
|
||||
// Add `s` count of slashing spans to storage.
|
||||
pallet_staking::benchmarking::add_slashing_spans::<T>(&pool_account, s);
|
||||
whitelist_account!(pool_account);
|
||||
}: _(RuntimeOrigin::Signed(pool_account.clone()), 1, s)
|
||||
verify {
|
||||
// The joiners funds didn't change
|
||||
assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
|
||||
// The unlocking chunk was removed
|
||||
assert_eq!(pallet_staking::Ledger::<T>::get(pool_account).unwrap().unlocking.len(), 0);
|
||||
}
|
||||
|
||||
withdraw_unbonded_update {
|
||||
let s in 0 .. MAX_SPANS;
|
||||
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
|
||||
// Add a new member
|
||||
let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
|
||||
let joiner = create_funded_user_with_balance::<T>("joiner", 0, min_join_bond * 2u32.into());
|
||||
let joiner_lookup = T::Lookup::unlookup(joiner.clone());
|
||||
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1)
|
||||
.unwrap();
|
||||
|
||||
// Sanity check join worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
min_create_bond + min_join_bond
|
||||
);
|
||||
assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
|
||||
|
||||
// Unbond the new member
|
||||
pallet_staking::CurrentEra::<T>::put(0);
|
||||
Pools::<T>::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone()).unwrap();
|
||||
|
||||
// Sanity check that unbond worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
min_create_bond
|
||||
);
|
||||
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
|
||||
|
||||
// Set the current era to ensure we can withdraw unbonded funds
|
||||
pallet_staking::CurrentEra::<T>::put(EraIndex::max_value());
|
||||
|
||||
pallet_staking::benchmarking::add_slashing_spans::<T>(&pool_account, s);
|
||||
whitelist_account!(joiner);
|
||||
}: withdraw_unbonded(RuntimeOrigin::Signed(joiner.clone()), joiner_lookup, s)
|
||||
verify {
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&joiner), min_join_bond * 2u32.into()
|
||||
);
|
||||
// The unlocking chunk was removed
|
||||
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 0);
|
||||
}
|
||||
|
||||
withdraw_unbonded_kill {
|
||||
let s in 0 .. MAX_SPANS;
|
||||
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
let depositor_lookup = T::Lookup::unlookup(depositor.clone());
|
||||
|
||||
// We set the pool to the destroying state so the depositor can leave
|
||||
BondedPools::<T>::try_mutate(&1, |maybe_bonded_pool| {
|
||||
maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| {
|
||||
bonded_pool.state = PoolState::Destroying;
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Unbond the creator
|
||||
pallet_staking::CurrentEra::<T>::put(0);
|
||||
// Simulate some rewards so we can check if the rewards storage is cleaned up. We check this
|
||||
// here to ensure the complete flow for destroying a pool works - the reward pool account
|
||||
// should never exist by time the depositor withdraws so we test that it gets cleaned
|
||||
// up when unbonding.
|
||||
let reward_account = Pools::<T>::create_reward_account(1);
|
||||
assert!(frame_system::Account::<T>::contains_key(&reward_account));
|
||||
Pools::<T>::fully_unbond(RuntimeOrigin::Signed(depositor.clone()).into(), depositor.clone()).unwrap();
|
||||
|
||||
// Sanity check that unbond worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
Zero::zero()
|
||||
);
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&pool_account),
|
||||
min_create_bond
|
||||
);
|
||||
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
|
||||
|
||||
// Set the current era to ensure we can withdraw unbonded funds
|
||||
pallet_staking::CurrentEra::<T>::put(EraIndex::max_value());
|
||||
|
||||
// Some last checks that storage items we expect to get cleaned up are present
|
||||
assert!(pallet_staking::Ledger::<T>::contains_key(&pool_account));
|
||||
assert!(BondedPools::<T>::contains_key(&1));
|
||||
assert!(SubPoolsStorage::<T>::contains_key(&1));
|
||||
assert!(RewardPools::<T>::contains_key(&1));
|
||||
assert!(PoolMembers::<T>::contains_key(&depositor));
|
||||
assert!(frame_system::Account::<T>::contains_key(&reward_account));
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}: withdraw_unbonded(RuntimeOrigin::Signed(depositor.clone()), depositor_lookup, s)
|
||||
verify {
|
||||
// Pool removal worked
|
||||
assert!(!pallet_staking::Ledger::<T>::contains_key(&pool_account));
|
||||
assert!(!BondedPools::<T>::contains_key(&1));
|
||||
assert!(!SubPoolsStorage::<T>::contains_key(&1));
|
||||
assert!(!RewardPools::<T>::contains_key(&1));
|
||||
assert!(!PoolMembers::<T>::contains_key(&depositor));
|
||||
assert!(!frame_system::Account::<T>::contains_key(&pool_account));
|
||||
assert!(!frame_system::Account::<T>::contains_key(&reward_account));
|
||||
|
||||
// Funds where transferred back correctly
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&depositor),
|
||||
// gets bond back + rewards collecting when unbonding
|
||||
min_create_bond * 2u32.into() + CurrencyOf::<T>::minimum_balance()
|
||||
);
|
||||
}
|
||||
|
||||
create {
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let depositor: T::AccountId = account("depositor", USER_SEED, 0);
|
||||
let depositor_lookup = T::Lookup::unlookup(depositor.clone());
|
||||
|
||||
// Give the depositor some balance to bond
|
||||
CurrencyOf::<T>::set_balance(&depositor, min_create_bond * 2u32.into());
|
||||
|
||||
// Make sure no Pools exist at a pre-condition for our verify checks
|
||||
assert_eq!(RewardPools::<T>::count(), 0);
|
||||
assert_eq!(BondedPools::<T>::count(), 0);
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}: _(
|
||||
RuntimeOrigin::Signed(depositor.clone()),
|
||||
min_create_bond,
|
||||
depositor_lookup.clone(),
|
||||
depositor_lookup.clone(),
|
||||
depositor_lookup
|
||||
)
|
||||
verify {
|
||||
assert_eq!(RewardPools::<T>::count(), 1);
|
||||
assert_eq!(BondedPools::<T>::count(), 1);
|
||||
let (_, new_pool) = BondedPools::<T>::iter().next().unwrap();
|
||||
assert_eq!(
|
||||
new_pool,
|
||||
BondedPoolInner {
|
||||
commission: Commission::default(),
|
||||
member_counter: 1,
|
||||
points: min_create_bond,
|
||||
roles: PoolRoles {
|
||||
depositor: depositor.clone(),
|
||||
root: Some(depositor.clone()),
|
||||
nominator: Some(depositor.clone()),
|
||||
bouncer: Some(depositor.clone()),
|
||||
},
|
||||
state: PoolState::Open,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&Pools::<T>::create_bonded_account(1)),
|
||||
Ok(min_create_bond)
|
||||
);
|
||||
}
|
||||
|
||||
nominate {
|
||||
let n in 1 .. MaxNominationsOf::<T>::get();
|
||||
|
||||
// Create a pool
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
|
||||
// Create some accounts to nominate. For the sake of benchmarking they don't need to be
|
||||
// actual validators
|
||||
let validators: Vec<_> = (0..n)
|
||||
.map(|i| account("stash", USER_SEED, i))
|
||||
.collect();
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1, validators)
|
||||
verify {
|
||||
assert_eq!(RewardPools::<T>::count(), 1);
|
||||
assert_eq!(BondedPools::<T>::count(), 1);
|
||||
let (_, new_pool) = BondedPools::<T>::iter().next().unwrap();
|
||||
assert_eq!(
|
||||
new_pool,
|
||||
BondedPoolInner {
|
||||
commission: Commission::default(),
|
||||
member_counter: 1,
|
||||
points: min_create_bond,
|
||||
roles: PoolRoles {
|
||||
depositor: depositor.clone(),
|
||||
root: Some(depositor.clone()),
|
||||
nominator: Some(depositor.clone()),
|
||||
bouncer: Some(depositor.clone()),
|
||||
},
|
||||
state: PoolState::Open,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&Pools::<T>::create_bonded_account(1)),
|
||||
Ok(min_create_bond)
|
||||
);
|
||||
}
|
||||
|
||||
set_state {
|
||||
// Create a pool
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
BondedPools::<T>::mutate(&1, |maybe_pool| {
|
||||
// Force the pool into an invalid state
|
||||
maybe_pool.as_mut().map(|pool| pool.points = min_create_bond * 10u32.into());
|
||||
});
|
||||
|
||||
let caller = account("caller", 0, USER_SEED);
|
||||
whitelist_account!(caller);
|
||||
}:_(RuntimeOrigin::Signed(caller), 1, PoolState::Destroying)
|
||||
verify {
|
||||
assert_eq!(BondedPools::<T>::get(1).unwrap().state, PoolState::Destroying);
|
||||
}
|
||||
|
||||
set_metadata {
|
||||
let n in 1 .. <T as pallet_nomination_pools::Config>::MaxMetadataLen::get();
|
||||
|
||||
// Create a pool
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
|
||||
// Create metadata of the max possible size
|
||||
let metadata: Vec<u8> = (0..n).map(|_| 42).collect();
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:_(RuntimeOrigin::Signed(depositor), 1, metadata.clone())
|
||||
verify {
|
||||
assert_eq!(Metadata::<T>::get(&1), metadata);
|
||||
}
|
||||
|
||||
set_configs {
|
||||
}:_(
|
||||
RuntimeOrigin::Root,
|
||||
ConfigOp::Set(BalanceOf::<T>::max_value()),
|
||||
ConfigOp::Set(BalanceOf::<T>::max_value()),
|
||||
ConfigOp::Set(u32::MAX),
|
||||
ConfigOp::Set(u32::MAX),
|
||||
ConfigOp::Set(u32::MAX),
|
||||
ConfigOp::Set(Perbill::max_value())
|
||||
) verify {
|
||||
assert_eq!(MinJoinBond::<T>::get(), BalanceOf::<T>::max_value());
|
||||
assert_eq!(MinCreateBond::<T>::get(), BalanceOf::<T>::max_value());
|
||||
assert_eq!(MaxPools::<T>::get(), Some(u32::MAX));
|
||||
assert_eq!(MaxPoolMembers::<T>::get(), Some(u32::MAX));
|
||||
assert_eq!(MaxPoolMembersPerPool::<T>::get(), Some(u32::MAX));
|
||||
assert_eq!(GlobalMaxCommission::<T>::get(), Some(Perbill::max_value()));
|
||||
}
|
||||
|
||||
update_roles {
|
||||
let first_id = pallet_nomination_pools::LastPoolId::<T>::get() + 1;
|
||||
let (root, _) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
let random: T::AccountId = account("but is anything really random in computers..?", 0, USER_SEED);
|
||||
}:_(
|
||||
RuntimeOrigin::Signed(root.clone()),
|
||||
first_id,
|
||||
ConfigOp::Set(random.clone()),
|
||||
ConfigOp::Set(random.clone()),
|
||||
ConfigOp::Set(random.clone())
|
||||
) verify {
|
||||
assert_eq!(
|
||||
pallet_nomination_pools::BondedPools::<T>::get(first_id).unwrap().roles,
|
||||
pallet_nomination_pools::PoolRoles {
|
||||
depositor: root,
|
||||
nominator: Some(random.clone()),
|
||||
bouncer: Some(random.clone()),
|
||||
root: Some(random),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
chill {
|
||||
// Create a pool
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
|
||||
// Nominate with the pool.
|
||||
let validators: Vec<_> = (0..MaxNominationsOf::<T>::get())
|
||||
.map(|i| account("stash", USER_SEED, i))
|
||||
.collect();
|
||||
|
||||
assert_ok!(T::Staking::nominate(&pool_account, validators));
|
||||
assert!(T::Staking::nominations(&Pools::<T>::create_bonded_account(1)).is_some());
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1)
|
||||
verify {
|
||||
assert!(T::Staking::nominations(&Pools::<T>::create_bonded_account(1)).is_none());
|
||||
}
|
||||
|
||||
set_commission {
|
||||
// Create a pool - do not set a commission yet.
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
// set a max commission
|
||||
Pools::<T>::set_commission_max(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into(), Perbill::from_percent(50)).unwrap();
|
||||
// set a change rate
|
||||
Pools::<T>::set_commission_change_rate(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into(), CommissionChangeRate {
|
||||
max_increase: Perbill::from_percent(20),
|
||||
min_delay: 0u32.into(),
|
||||
}).unwrap();
|
||||
// set a claim permission to an account.
|
||||
Pools::<T>::set_commission_claim_permission(
|
||||
RuntimeOrigin::Signed(depositor.clone()).into(),
|
||||
1u32.into(),
|
||||
Some(CommissionClaimPermission::Account(depositor.clone()))
|
||||
).unwrap();
|
||||
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Some((Perbill::from_percent(20), depositor.clone())))
|
||||
verify {
|
||||
assert_eq!(BondedPools::<T>::get(1).unwrap().commission, Commission {
|
||||
current: Some((Perbill::from_percent(20), depositor.clone())),
|
||||
max: Some(Perbill::from_percent(50)),
|
||||
change_rate: Some(CommissionChangeRate {
|
||||
max_increase: Perbill::from_percent(20),
|
||||
min_delay: 0u32.into()
|
||||
}),
|
||||
throttle_from: Some(1u32.into()),
|
||||
claim_permission: Some(CommissionClaimPermission::Account(depositor)),
|
||||
});
|
||||
}
|
||||
|
||||
set_commission_max {
|
||||
// Create a pool, setting a commission that will update when max commission is set.
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), Some(Perbill::from_percent(50)));
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Perbill::from_percent(50))
|
||||
verify {
|
||||
assert_eq!(
|
||||
BondedPools::<T>::get(1).unwrap().commission, Commission {
|
||||
current: Some((Perbill::from_percent(50), depositor)),
|
||||
max: Some(Perbill::from_percent(50)),
|
||||
change_rate: None,
|
||||
throttle_from: Some(0u32.into()),
|
||||
claim_permission: None,
|
||||
});
|
||||
}
|
||||
|
||||
set_commission_change_rate {
|
||||
// Create a pool
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), CommissionChangeRate {
|
||||
max_increase: Perbill::from_percent(50),
|
||||
min_delay: 1000u32.into(),
|
||||
})
|
||||
verify {
|
||||
assert_eq!(
|
||||
BondedPools::<T>::get(1).unwrap().commission, Commission {
|
||||
current: None,
|
||||
max: None,
|
||||
change_rate: Some(CommissionChangeRate {
|
||||
max_increase: Perbill::from_percent(50),
|
||||
min_delay: 1000u32.into(),
|
||||
}),
|
||||
throttle_from: Some(1_u32.into()),
|
||||
claim_permission: None,
|
||||
});
|
||||
}
|
||||
|
||||
set_commission_claim_permission {
|
||||
// Create a pool.
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Some(CommissionClaimPermission::Account(depositor.clone())))
|
||||
verify {
|
||||
assert_eq!(
|
||||
BondedPools::<T>::get(1).unwrap().commission, Commission {
|
||||
current: None,
|
||||
max: None,
|
||||
change_rate: None,
|
||||
throttle_from: None,
|
||||
claim_permission: Some(CommissionClaimPermission::Account(depositor)),
|
||||
});
|
||||
}
|
||||
|
||||
set_claim_permission {
|
||||
// Create a pool
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
|
||||
// Join pool
|
||||
let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
|
||||
let joiner = create_funded_user_with_balance::<T>("joiner", 0, min_join_bond * 4u32.into());
|
||||
let joiner_lookup = T::Lookup::unlookup(joiner.clone());
|
||||
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1)
|
||||
.unwrap();
|
||||
|
||||
// Sanity check join worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
min_create_bond + min_join_bond
|
||||
);
|
||||
}:_(RuntimeOrigin::Signed(joiner.clone()), ClaimPermission::Permissioned)
|
||||
verify {
|
||||
assert_eq!(ClaimPermissions::<T>::get(joiner), ClaimPermission::Permissioned);
|
||||
}
|
||||
|
||||
claim_commission {
|
||||
let claimer: T::AccountId = account("claimer_member", USER_SEED + 4, 0);
|
||||
let commission = Perbill::from_percent(50);
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let ed = CurrencyOf::<T>::minimum_balance();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, origin_weight, Some(commission));
|
||||
let reward_account = Pools::<T>::create_reward_account(1);
|
||||
CurrencyOf::<T>::set_balance(&reward_account, ed + origin_weight);
|
||||
|
||||
// member claims a payout to make some commission available.
|
||||
let _ = Pools::<T>::claim_payout(RuntimeOrigin::Signed(claimer.clone()).into());
|
||||
// set a claim permission to an account.
|
||||
let _ = Pools::<T>::set_commission_claim_permission(
|
||||
RuntimeOrigin::Signed(depositor.clone()).into(),
|
||||
1u32.into(),
|
||||
Some(CommissionClaimPermission::Account(claimer))
|
||||
);
|
||||
whitelist_account!(depositor);
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into())
|
||||
verify {
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&depositor),
|
||||
origin_weight + commission * origin_weight
|
||||
);
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&reward_account),
|
||||
ed + commission * origin_weight
|
||||
);
|
||||
}
|
||||
|
||||
adjust_pool_deposit {
|
||||
// Create a pool
|
||||
let (depositor, _) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
|
||||
// Remove ed freeze to create a scenario where the ed deposit needs to be adjusted.
|
||||
let _ = Pools::<T>::unfreeze_pool_deposit(&Pools::<T>::create_reward_account(1));
|
||||
assert!(&Pools::<T>::check_ed_imbalance().is_err());
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:_(RuntimeOrigin::Signed(depositor), 1)
|
||||
verify {
|
||||
assert!(&Pools::<T>::check_ed_imbalance().is_ok());
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
Pallet,
|
||||
crate::mock::new_test_ext(),
|
||||
crate::mock::Runtime
|
||||
);
|
||||
}
|
||||
@@ -17,836 +17,13 @@
|
||||
|
||||
//! Benchmarks for the nomination pools coupled with the staking and bags list pallets.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub mod inner;
|
||||
|
||||
use frame_benchmarking::v1::{account, whitelist_account};
|
||||
use frame_election_provider_support::SortedListProvider;
|
||||
use frame_support::{
|
||||
assert_ok, ensure,
|
||||
traits::{
|
||||
fungible::{Inspect, Mutate, Unbalanced},
|
||||
Get,
|
||||
},
|
||||
};
|
||||
use frame_system::RawOrigin as RuntimeOrigin;
|
||||
use pallet_nomination_pools::{
|
||||
BalanceOf, BondExtra, BondedPoolInner, BondedPools, ClaimPermission, ClaimPermissions,
|
||||
Commission, CommissionChangeRate, CommissionClaimPermission, ConfigOp, GlobalMaxCommission,
|
||||
MaxPoolMembers, MaxPoolMembersPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond,
|
||||
Pallet as Pools, PoolMembers, PoolRoles, PoolState, RewardPools, SubPoolsStorage,
|
||||
};
|
||||
use pallet_staking::MaxNominationsOf;
|
||||
use sp_runtime::{
|
||||
traits::{Bounded, StaticLookup, Zero},
|
||||
Perbill,
|
||||
};
|
||||
use sp_staking::{EraIndex, StakingInterface};
|
||||
use sp_std::{vec, vec::Vec};
|
||||
// `frame_benchmarking::benchmarks!` macro needs this
|
||||
use pallet_nomination_pools::Call;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub use inner::*;
|
||||
|
||||
type CurrencyOf<T> = <T as pallet_nomination_pools::Config>::Currency;
|
||||
|
||||
const USER_SEED: u32 = 0;
|
||||
const MAX_SPANS: u32 = 100;
|
||||
|
||||
type VoterBagsListInstance = pallet_bags_list::Instance1;
|
||||
pub trait Config:
|
||||
pallet_nomination_pools::Config
|
||||
+ pallet_staking::Config
|
||||
+ pallet_bags_list::Config<VoterBagsListInstance>
|
||||
{
|
||||
}
|
||||
|
||||
pub struct Pallet<T: Config>(Pools<T>);
|
||||
|
||||
fn create_funded_user_with_balance<T: pallet_nomination_pools::Config>(
|
||||
string: &'static str,
|
||||
n: u32,
|
||||
balance: BalanceOf<T>,
|
||||
) -> T::AccountId {
|
||||
let user = account(string, n, USER_SEED);
|
||||
T::Currency::set_balance(&user, balance);
|
||||
user
|
||||
}
|
||||
|
||||
// Create a bonded pool account, bonding `balance` and giving the account `balance * 2` free
|
||||
// balance.
|
||||
fn create_pool_account<T: pallet_nomination_pools::Config>(
|
||||
n: u32,
|
||||
balance: BalanceOf<T>,
|
||||
commission: Option<Perbill>,
|
||||
) -> (T::AccountId, T::AccountId) {
|
||||
let ed = CurrencyOf::<T>::minimum_balance();
|
||||
let pool_creator: T::AccountId =
|
||||
create_funded_user_with_balance::<T>("pool_creator", n, ed + balance * 2u32.into());
|
||||
let pool_creator_lookup = T::Lookup::unlookup(pool_creator.clone());
|
||||
|
||||
Pools::<T>::create(
|
||||
RuntimeOrigin::Signed(pool_creator.clone()).into(),
|
||||
balance,
|
||||
pool_creator_lookup.clone(),
|
||||
pool_creator_lookup.clone(),
|
||||
pool_creator_lookup,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if let Some(c) = commission {
|
||||
let pool_id = pallet_nomination_pools::LastPoolId::<T>::get();
|
||||
Pools::<T>::set_commission(
|
||||
RuntimeOrigin::Signed(pool_creator.clone()).into(),
|
||||
pool_id,
|
||||
Some((c, pool_creator.clone())),
|
||||
)
|
||||
.expect("pool just created, commission can be set by root; qed");
|
||||
}
|
||||
|
||||
let pool_account = pallet_nomination_pools::BondedPools::<T>::iter()
|
||||
.find(|(_, bonded_pool)| bonded_pool.roles.depositor == pool_creator)
|
||||
.map(|(pool_id, _)| Pools::<T>::create_bonded_account(pool_id))
|
||||
.expect("pool_creator created a pool above");
|
||||
|
||||
(pool_creator, pool_account)
|
||||
}
|
||||
|
||||
fn vote_to_balance<T: pallet_nomination_pools::Config>(
|
||||
vote: u64,
|
||||
) -> Result<BalanceOf<T>, &'static str> {
|
||||
vote.try_into().map_err(|_| "could not convert u64 to Balance")
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
struct ListScenario<T: pallet_nomination_pools::Config> {
|
||||
/// Stash/Controller that is expected to be moved.
|
||||
origin1: T::AccountId,
|
||||
creator1: T::AccountId,
|
||||
dest_weight: BalanceOf<T>,
|
||||
origin1_member: Option<T::AccountId>,
|
||||
}
|
||||
|
||||
impl<T: Config> ListScenario<T> {
|
||||
/// An expensive scenario for bags-list implementation:
|
||||
///
|
||||
/// - the node to be updated (r) is the head of a bag that has at least one other node. The bag
|
||||
/// itself will need to be read and written to update its head. The node pointed to by r.next
|
||||
/// will need to be read and written as it will need to have its prev pointer updated. Note
|
||||
/// that there are two other worst case scenarios for bag removal: 1) the node is a tail and
|
||||
/// 2) the node is a middle node with prev and next; all scenarios end up with the same number
|
||||
/// of storage reads and writes.
|
||||
///
|
||||
/// - the destination bag has at least one node, which will need its next pointer updated.
|
||||
pub(crate) fn new(
|
||||
origin_weight: BalanceOf<T>,
|
||||
is_increase: bool,
|
||||
) -> Result<Self, &'static str> {
|
||||
ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0");
|
||||
|
||||
ensure!(
|
||||
pallet_nomination_pools::MaxPools::<T>::get().unwrap_or(0) >= 3,
|
||||
"must allow at least three pools for benchmarks"
|
||||
);
|
||||
|
||||
// Burn the entire issuance.
|
||||
CurrencyOf::<T>::set_total_issuance(Zero::zero());
|
||||
|
||||
// Create accounts with the origin weight
|
||||
let (pool_creator1, pool_origin1) =
|
||||
create_pool_account::<T>(USER_SEED + 1, origin_weight, Some(Perbill::from_percent(50)));
|
||||
|
||||
T::Staking::nominate(
|
||||
&pool_origin1,
|
||||
// NOTE: these don't really need to be validators.
|
||||
vec![account("random_validator", 0, USER_SEED)],
|
||||
)?;
|
||||
|
||||
let (_, pool_origin2) =
|
||||
create_pool_account::<T>(USER_SEED + 2, origin_weight, Some(Perbill::from_percent(50)));
|
||||
|
||||
T::Staking::nominate(
|
||||
&pool_origin2,
|
||||
vec![account("random_validator", 0, USER_SEED)].clone(),
|
||||
)?;
|
||||
|
||||
// Find a destination weight that will trigger the worst case scenario
|
||||
let dest_weight_as_vote = <T as pallet_staking::Config>::VoterList::score_update_worst_case(
|
||||
&pool_origin1,
|
||||
is_increase,
|
||||
);
|
||||
|
||||
let dest_weight: BalanceOf<T> =
|
||||
dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?;
|
||||
|
||||
// Create an account with the worst case destination weight
|
||||
let (_, pool_dest1) =
|
||||
create_pool_account::<T>(USER_SEED + 3, dest_weight, Some(Perbill::from_percent(50)));
|
||||
|
||||
T::Staking::nominate(&pool_dest1, vec![account("random_validator", 0, USER_SEED)])?;
|
||||
|
||||
let weight_of = pallet_staking::Pallet::<T>::weight_of_fn();
|
||||
assert_eq!(vote_to_balance::<T>(weight_of(&pool_origin1)).unwrap(), origin_weight);
|
||||
assert_eq!(vote_to_balance::<T>(weight_of(&pool_origin2)).unwrap(), origin_weight);
|
||||
assert_eq!(vote_to_balance::<T>(weight_of(&pool_dest1)).unwrap(), dest_weight);
|
||||
|
||||
Ok(ListScenario {
|
||||
origin1: pool_origin1,
|
||||
creator1: pool_creator1,
|
||||
dest_weight,
|
||||
origin1_member: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn add_joiner(mut self, amount: BalanceOf<T>) -> Self {
|
||||
let amount = MinJoinBond::<T>::get()
|
||||
.max(CurrencyOf::<T>::minimum_balance())
|
||||
// Max `amount` with minimum thresholds for account balance and joining a pool
|
||||
// to ensure 1) the user can be created and 2) can join the pool
|
||||
.max(amount);
|
||||
|
||||
let joiner: T::AccountId = account("joiner", USER_SEED, 0);
|
||||
self.origin1_member = Some(joiner.clone());
|
||||
CurrencyOf::<T>::set_balance(&joiner, amount * 2u32.into());
|
||||
|
||||
let original_bonded = T::Staking::active_stake(&self.origin1).unwrap();
|
||||
|
||||
// Unbond `amount` from the underlying pool account so when the member joins
|
||||
// we will maintain `current_bonded`.
|
||||
T::Staking::unbond(&self.origin1, amount).expect("the pool was created in `Self::new`.");
|
||||
|
||||
// Account pool points for the unbonded balance.
|
||||
BondedPools::<T>::mutate(&1, |maybe_pool| {
|
||||
maybe_pool.as_mut().map(|pool| pool.points -= amount)
|
||||
});
|
||||
|
||||
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), amount, 1).unwrap();
|
||||
|
||||
// check that the vote weight is still the same as the original bonded
|
||||
let weight_of = pallet_staking::Pallet::<T>::weight_of_fn();
|
||||
assert_eq!(vote_to_balance::<T>(weight_of(&self.origin1)).unwrap(), original_bonded);
|
||||
|
||||
// check the member was added correctly
|
||||
let member = PoolMembers::<T>::get(&joiner).unwrap();
|
||||
assert_eq!(member.points, amount);
|
||||
assert_eq!(member.pool_id, 1);
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
frame_benchmarking::benchmarks! {
|
||||
join {
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
|
||||
// setup the worst case list scenario.
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&scenario.origin1).unwrap(),
|
||||
origin_weight
|
||||
);
|
||||
|
||||
let max_additional = scenario.dest_weight - origin_weight;
|
||||
let joiner_free = CurrencyOf::<T>::minimum_balance() + max_additional;
|
||||
|
||||
let joiner: T::AccountId
|
||||
= create_funded_user_with_balance::<T>("joiner", 0, joiner_free);
|
||||
|
||||
whitelist_account!(joiner);
|
||||
}: _(RuntimeOrigin::Signed(joiner.clone()), max_additional, 1)
|
||||
verify {
|
||||
assert_eq!(CurrencyOf::<T>::balance(&joiner), joiner_free - max_additional);
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&scenario.origin1).unwrap(),
|
||||
scenario.dest_weight
|
||||
);
|
||||
}
|
||||
|
||||
bond_extra_transfer {
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
let extra = scenario.dest_weight - origin_weight;
|
||||
|
||||
// creator of the src pool will bond-extra, bumping itself to dest bag.
|
||||
|
||||
}: bond_extra(RuntimeOrigin::Signed(scenario.creator1.clone()), BondExtra::FreeBalance(extra))
|
||||
verify {
|
||||
assert!(
|
||||
T::Staking::active_stake(&scenario.origin1).unwrap() >=
|
||||
scenario.dest_weight
|
||||
);
|
||||
}
|
||||
|
||||
bond_extra_other {
|
||||
let claimer: T::AccountId = account("claimer", USER_SEED + 4, 0);
|
||||
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
let extra = (scenario.dest_weight - origin_weight).max(CurrencyOf::<T>::minimum_balance());
|
||||
|
||||
// set claim preferences to `PermissionlessAll` to any account to bond extra on member's behalf.
|
||||
let _ = Pools::<T>::set_claim_permission(RuntimeOrigin::Signed(scenario.creator1.clone()).into(), ClaimPermission::PermissionlessAll);
|
||||
|
||||
// transfer exactly `extra` to the depositor of the src pool (1),
|
||||
let reward_account1 = Pools::<T>::create_reward_account(1);
|
||||
assert!(extra >= CurrencyOf::<T>::minimum_balance());
|
||||
let _ = CurrencyOf::<T>::mint_into(&reward_account1, extra);
|
||||
|
||||
}: _(RuntimeOrigin::Signed(claimer), T::Lookup::unlookup(scenario.creator1.clone()), BondExtra::Rewards)
|
||||
verify {
|
||||
// commission of 50% deducted here.
|
||||
assert!(
|
||||
T::Staking::active_stake(&scenario.origin1).unwrap() >=
|
||||
scenario.dest_weight / 2u32.into()
|
||||
);
|
||||
}
|
||||
|
||||
claim_payout {
|
||||
let claimer: T::AccountId = account("claimer", USER_SEED + 4, 0);
|
||||
let commission = Perbill::from_percent(50);
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let ed = CurrencyOf::<T>::minimum_balance();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, origin_weight, Some(commission));
|
||||
let reward_account = Pools::<T>::create_reward_account(1);
|
||||
|
||||
// Send funds to the reward account of the pool
|
||||
CurrencyOf::<T>::set_balance(&reward_account, ed + origin_weight);
|
||||
|
||||
// set claim preferences to `PermissionlessAll` so any account can claim rewards on member's
|
||||
// behalf.
|
||||
let _ = Pools::<T>::set_claim_permission(RuntimeOrigin::Signed(depositor.clone()).into(), ClaimPermission::PermissionlessAll);
|
||||
|
||||
// Sanity check
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&depositor),
|
||||
origin_weight
|
||||
);
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:claim_payout_other(RuntimeOrigin::Signed(claimer), depositor.clone())
|
||||
verify {
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&depositor),
|
||||
origin_weight + commission * origin_weight
|
||||
);
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&reward_account),
|
||||
ed + commission * origin_weight
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
unbond {
|
||||
// The weight the nominator will start at. The value used here is expected to be
|
||||
// significantly higher than the first position in a list (e.g. the first bag threshold).
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 200u32.into();
|
||||
let scenario = ListScenario::<T>::new(origin_weight, false)?;
|
||||
let amount = origin_weight - scenario.dest_weight;
|
||||
|
||||
let scenario = scenario.add_joiner(amount);
|
||||
let member_id = scenario.origin1_member.unwrap().clone();
|
||||
let member_id_lookup = T::Lookup::unlookup(member_id.clone());
|
||||
let all_points = PoolMembers::<T>::get(&member_id).unwrap().points;
|
||||
whitelist_account!(member_id);
|
||||
}: _(RuntimeOrigin::Signed(member_id.clone()), member_id_lookup, all_points)
|
||||
verify {
|
||||
let bonded_after = T::Staking::active_stake(&scenario.origin1).unwrap();
|
||||
// We at least went down to the destination bag
|
||||
assert!(bonded_after <= scenario.dest_weight);
|
||||
let member = PoolMembers::<T>::get(
|
||||
&member_id
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
member.unbonding_eras.keys().cloned().collect::<Vec<_>>(),
|
||||
vec![0 + T::Staking::bonding_duration()]
|
||||
);
|
||||
assert_eq!(
|
||||
member.unbonding_eras.values().cloned().collect::<Vec<_>>(),
|
||||
vec![all_points]
|
||||
);
|
||||
}
|
||||
|
||||
pool_withdraw_unbonded {
|
||||
let s in 0 .. MAX_SPANS;
|
||||
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
|
||||
// Add a new member
|
||||
let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
|
||||
let joiner = create_funded_user_with_balance::<T>("joiner", 0, min_join_bond * 2u32.into());
|
||||
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1)
|
||||
.unwrap();
|
||||
|
||||
// Sanity check join worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
min_create_bond + min_join_bond
|
||||
);
|
||||
assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
|
||||
|
||||
// Unbond the new member
|
||||
Pools::<T>::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone()).unwrap();
|
||||
|
||||
// Sanity check that unbond worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
min_create_bond
|
||||
);
|
||||
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
|
||||
// Set the current era
|
||||
pallet_staking::CurrentEra::<T>::put(EraIndex::max_value());
|
||||
|
||||
// Add `s` count of slashing spans to storage.
|
||||
pallet_staking::benchmarking::add_slashing_spans::<T>(&pool_account, s);
|
||||
whitelist_account!(pool_account);
|
||||
}: _(RuntimeOrigin::Signed(pool_account.clone()), 1, s)
|
||||
verify {
|
||||
// The joiners funds didn't change
|
||||
assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
|
||||
// The unlocking chunk was removed
|
||||
assert_eq!(pallet_staking::Ledger::<T>::get(pool_account).unwrap().unlocking.len(), 0);
|
||||
}
|
||||
|
||||
withdraw_unbonded_update {
|
||||
let s in 0 .. MAX_SPANS;
|
||||
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
|
||||
// Add a new member
|
||||
let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
|
||||
let joiner = create_funded_user_with_balance::<T>("joiner", 0, min_join_bond * 2u32.into());
|
||||
let joiner_lookup = T::Lookup::unlookup(joiner.clone());
|
||||
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1)
|
||||
.unwrap();
|
||||
|
||||
// Sanity check join worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
min_create_bond + min_join_bond
|
||||
);
|
||||
assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
|
||||
|
||||
// Unbond the new member
|
||||
pallet_staking::CurrentEra::<T>::put(0);
|
||||
Pools::<T>::fully_unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner.clone()).unwrap();
|
||||
|
||||
// Sanity check that unbond worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
min_create_bond
|
||||
);
|
||||
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
|
||||
|
||||
// Set the current era to ensure we can withdraw unbonded funds
|
||||
pallet_staking::CurrentEra::<T>::put(EraIndex::max_value());
|
||||
|
||||
pallet_staking::benchmarking::add_slashing_spans::<T>(&pool_account, s);
|
||||
whitelist_account!(joiner);
|
||||
}: withdraw_unbonded(RuntimeOrigin::Signed(joiner.clone()), joiner_lookup, s)
|
||||
verify {
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&joiner), min_join_bond * 2u32.into()
|
||||
);
|
||||
// The unlocking chunk was removed
|
||||
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 0);
|
||||
}
|
||||
|
||||
withdraw_unbonded_kill {
|
||||
let s in 0 .. MAX_SPANS;
|
||||
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
let depositor_lookup = T::Lookup::unlookup(depositor.clone());
|
||||
|
||||
// We set the pool to the destroying state so the depositor can leave
|
||||
BondedPools::<T>::try_mutate(&1, |maybe_bonded_pool| {
|
||||
maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| {
|
||||
bonded_pool.state = PoolState::Destroying;
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Unbond the creator
|
||||
pallet_staking::CurrentEra::<T>::put(0);
|
||||
// Simulate some rewards so we can check if the rewards storage is cleaned up. We check this
|
||||
// here to ensure the complete flow for destroying a pool works - the reward pool account
|
||||
// should never exist by time the depositor withdraws so we test that it gets cleaned
|
||||
// up when unbonding.
|
||||
let reward_account = Pools::<T>::create_reward_account(1);
|
||||
assert!(frame_system::Account::<T>::contains_key(&reward_account));
|
||||
Pools::<T>::fully_unbond(RuntimeOrigin::Signed(depositor.clone()).into(), depositor.clone()).unwrap();
|
||||
|
||||
// Sanity check that unbond worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
Zero::zero()
|
||||
);
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&pool_account),
|
||||
min_create_bond
|
||||
);
|
||||
assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
|
||||
|
||||
// Set the current era to ensure we can withdraw unbonded funds
|
||||
pallet_staking::CurrentEra::<T>::put(EraIndex::max_value());
|
||||
|
||||
// Some last checks that storage items we expect to get cleaned up are present
|
||||
assert!(pallet_staking::Ledger::<T>::contains_key(&pool_account));
|
||||
assert!(BondedPools::<T>::contains_key(&1));
|
||||
assert!(SubPoolsStorage::<T>::contains_key(&1));
|
||||
assert!(RewardPools::<T>::contains_key(&1));
|
||||
assert!(PoolMembers::<T>::contains_key(&depositor));
|
||||
assert!(frame_system::Account::<T>::contains_key(&reward_account));
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}: withdraw_unbonded(RuntimeOrigin::Signed(depositor.clone()), depositor_lookup, s)
|
||||
verify {
|
||||
// Pool removal worked
|
||||
assert!(!pallet_staking::Ledger::<T>::contains_key(&pool_account));
|
||||
assert!(!BondedPools::<T>::contains_key(&1));
|
||||
assert!(!SubPoolsStorage::<T>::contains_key(&1));
|
||||
assert!(!RewardPools::<T>::contains_key(&1));
|
||||
assert!(!PoolMembers::<T>::contains_key(&depositor));
|
||||
assert!(!frame_system::Account::<T>::contains_key(&pool_account));
|
||||
assert!(!frame_system::Account::<T>::contains_key(&reward_account));
|
||||
|
||||
// Funds where transferred back correctly
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&depositor),
|
||||
// gets bond back + rewards collecting when unbonding
|
||||
min_create_bond * 2u32.into() + CurrencyOf::<T>::minimum_balance()
|
||||
);
|
||||
}
|
||||
|
||||
create {
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let depositor: T::AccountId = account("depositor", USER_SEED, 0);
|
||||
let depositor_lookup = T::Lookup::unlookup(depositor.clone());
|
||||
|
||||
// Give the depositor some balance to bond
|
||||
CurrencyOf::<T>::set_balance(&depositor, min_create_bond * 2u32.into());
|
||||
|
||||
// Make sure no Pools exist at a pre-condition for our verify checks
|
||||
assert_eq!(RewardPools::<T>::count(), 0);
|
||||
assert_eq!(BondedPools::<T>::count(), 0);
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}: _(
|
||||
RuntimeOrigin::Signed(depositor.clone()),
|
||||
min_create_bond,
|
||||
depositor_lookup.clone(),
|
||||
depositor_lookup.clone(),
|
||||
depositor_lookup
|
||||
)
|
||||
verify {
|
||||
assert_eq!(RewardPools::<T>::count(), 1);
|
||||
assert_eq!(BondedPools::<T>::count(), 1);
|
||||
let (_, new_pool) = BondedPools::<T>::iter().next().unwrap();
|
||||
assert_eq!(
|
||||
new_pool,
|
||||
BondedPoolInner {
|
||||
commission: Commission::default(),
|
||||
member_counter: 1,
|
||||
points: min_create_bond,
|
||||
roles: PoolRoles {
|
||||
depositor: depositor.clone(),
|
||||
root: Some(depositor.clone()),
|
||||
nominator: Some(depositor.clone()),
|
||||
bouncer: Some(depositor.clone()),
|
||||
},
|
||||
state: PoolState::Open,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&Pools::<T>::create_bonded_account(1)),
|
||||
Ok(min_create_bond)
|
||||
);
|
||||
}
|
||||
|
||||
nominate {
|
||||
let n in 1 .. MaxNominationsOf::<T>::get();
|
||||
|
||||
// Create a pool
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
|
||||
// Create some accounts to nominate. For the sake of benchmarking they don't need to be
|
||||
// actual validators
|
||||
let validators: Vec<_> = (0..n)
|
||||
.map(|i| account("stash", USER_SEED, i))
|
||||
.collect();
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1, validators)
|
||||
verify {
|
||||
assert_eq!(RewardPools::<T>::count(), 1);
|
||||
assert_eq!(BondedPools::<T>::count(), 1);
|
||||
let (_, new_pool) = BondedPools::<T>::iter().next().unwrap();
|
||||
assert_eq!(
|
||||
new_pool,
|
||||
BondedPoolInner {
|
||||
commission: Commission::default(),
|
||||
member_counter: 1,
|
||||
points: min_create_bond,
|
||||
roles: PoolRoles {
|
||||
depositor: depositor.clone(),
|
||||
root: Some(depositor.clone()),
|
||||
nominator: Some(depositor.clone()),
|
||||
bouncer: Some(depositor.clone()),
|
||||
},
|
||||
state: PoolState::Open,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&Pools::<T>::create_bonded_account(1)),
|
||||
Ok(min_create_bond)
|
||||
);
|
||||
}
|
||||
|
||||
set_state {
|
||||
// Create a pool
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
BondedPools::<T>::mutate(&1, |maybe_pool| {
|
||||
// Force the pool into an invalid state
|
||||
maybe_pool.as_mut().map(|pool| pool.points = min_create_bond * 10u32.into());
|
||||
});
|
||||
|
||||
let caller = account("caller", 0, USER_SEED);
|
||||
whitelist_account!(caller);
|
||||
}:_(RuntimeOrigin::Signed(caller), 1, PoolState::Destroying)
|
||||
verify {
|
||||
assert_eq!(BondedPools::<T>::get(1).unwrap().state, PoolState::Destroying);
|
||||
}
|
||||
|
||||
set_metadata {
|
||||
let n in 1 .. <T as pallet_nomination_pools::Config>::MaxMetadataLen::get();
|
||||
|
||||
// Create a pool
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
|
||||
// Create metadata of the max possible size
|
||||
let metadata: Vec<u8> = (0..n).map(|_| 42).collect();
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:_(RuntimeOrigin::Signed(depositor), 1, metadata.clone())
|
||||
verify {
|
||||
assert_eq!(Metadata::<T>::get(&1), metadata);
|
||||
}
|
||||
|
||||
set_configs {
|
||||
}:_(
|
||||
RuntimeOrigin::Root,
|
||||
ConfigOp::Set(BalanceOf::<T>::max_value()),
|
||||
ConfigOp::Set(BalanceOf::<T>::max_value()),
|
||||
ConfigOp::Set(u32::MAX),
|
||||
ConfigOp::Set(u32::MAX),
|
||||
ConfigOp::Set(u32::MAX),
|
||||
ConfigOp::Set(Perbill::max_value())
|
||||
) verify {
|
||||
assert_eq!(MinJoinBond::<T>::get(), BalanceOf::<T>::max_value());
|
||||
assert_eq!(MinCreateBond::<T>::get(), BalanceOf::<T>::max_value());
|
||||
assert_eq!(MaxPools::<T>::get(), Some(u32::MAX));
|
||||
assert_eq!(MaxPoolMembers::<T>::get(), Some(u32::MAX));
|
||||
assert_eq!(MaxPoolMembersPerPool::<T>::get(), Some(u32::MAX));
|
||||
assert_eq!(GlobalMaxCommission::<T>::get(), Some(Perbill::max_value()));
|
||||
}
|
||||
|
||||
update_roles {
|
||||
let first_id = pallet_nomination_pools::LastPoolId::<T>::get() + 1;
|
||||
let (root, _) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
let random: T::AccountId = account("but is anything really random in computers..?", 0, USER_SEED);
|
||||
}:_(
|
||||
RuntimeOrigin::Signed(root.clone()),
|
||||
first_id,
|
||||
ConfigOp::Set(random.clone()),
|
||||
ConfigOp::Set(random.clone()),
|
||||
ConfigOp::Set(random.clone())
|
||||
) verify {
|
||||
assert_eq!(
|
||||
pallet_nomination_pools::BondedPools::<T>::get(first_id).unwrap().roles,
|
||||
pallet_nomination_pools::PoolRoles {
|
||||
depositor: root,
|
||||
nominator: Some(random.clone()),
|
||||
bouncer: Some(random.clone()),
|
||||
root: Some(random),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
chill {
|
||||
// Create a pool
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
|
||||
// Nominate with the pool.
|
||||
let validators: Vec<_> = (0..MaxNominationsOf::<T>::get())
|
||||
.map(|i| account("stash", USER_SEED, i))
|
||||
.collect();
|
||||
|
||||
assert_ok!(T::Staking::nominate(&pool_account, validators));
|
||||
assert!(T::Staking::nominations(&Pools::<T>::create_bonded_account(1)).is_some());
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1)
|
||||
verify {
|
||||
assert!(T::Staking::nominations(&Pools::<T>::create_bonded_account(1)).is_none());
|
||||
}
|
||||
|
||||
set_commission {
|
||||
// Create a pool - do not set a commission yet.
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
// set a max commission
|
||||
Pools::<T>::set_commission_max(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into(), Perbill::from_percent(50)).unwrap();
|
||||
// set a change rate
|
||||
Pools::<T>::set_commission_change_rate(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into(), CommissionChangeRate {
|
||||
max_increase: Perbill::from_percent(20),
|
||||
min_delay: 0u32.into(),
|
||||
}).unwrap();
|
||||
// set a claim permission to an account.
|
||||
Pools::<T>::set_commission_claim_permission(
|
||||
RuntimeOrigin::Signed(depositor.clone()).into(),
|
||||
1u32.into(),
|
||||
Some(CommissionClaimPermission::Account(depositor.clone()))
|
||||
).unwrap();
|
||||
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Some((Perbill::from_percent(20), depositor.clone())))
|
||||
verify {
|
||||
assert_eq!(BondedPools::<T>::get(1).unwrap().commission, Commission {
|
||||
current: Some((Perbill::from_percent(20), depositor.clone())),
|
||||
max: Some(Perbill::from_percent(50)),
|
||||
change_rate: Some(CommissionChangeRate {
|
||||
max_increase: Perbill::from_percent(20),
|
||||
min_delay: 0u32.into()
|
||||
}),
|
||||
throttle_from: Some(1u32.into()),
|
||||
claim_permission: Some(CommissionClaimPermission::Account(depositor)),
|
||||
});
|
||||
}
|
||||
|
||||
set_commission_max {
|
||||
// Create a pool, setting a commission that will update when max commission is set.
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), Some(Perbill::from_percent(50)));
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Perbill::from_percent(50))
|
||||
verify {
|
||||
assert_eq!(
|
||||
BondedPools::<T>::get(1).unwrap().commission, Commission {
|
||||
current: Some((Perbill::from_percent(50), depositor)),
|
||||
max: Some(Perbill::from_percent(50)),
|
||||
change_rate: None,
|
||||
throttle_from: Some(0u32.into()),
|
||||
claim_permission: None,
|
||||
});
|
||||
}
|
||||
|
||||
set_commission_change_rate {
|
||||
// Create a pool
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), CommissionChangeRate {
|
||||
max_increase: Perbill::from_percent(50),
|
||||
min_delay: 1000u32.into(),
|
||||
})
|
||||
verify {
|
||||
assert_eq!(
|
||||
BondedPools::<T>::get(1).unwrap().commission, Commission {
|
||||
current: None,
|
||||
max: None,
|
||||
change_rate: Some(CommissionChangeRate {
|
||||
max_increase: Perbill::from_percent(50),
|
||||
min_delay: 1000u32.into(),
|
||||
}),
|
||||
throttle_from: Some(1_u32.into()),
|
||||
claim_permission: None,
|
||||
});
|
||||
}
|
||||
|
||||
set_commission_claim_permission {
|
||||
// Create a pool.
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Some(CommissionClaimPermission::Account(depositor.clone())))
|
||||
verify {
|
||||
assert_eq!(
|
||||
BondedPools::<T>::get(1).unwrap().commission, Commission {
|
||||
current: None,
|
||||
max: None,
|
||||
change_rate: None,
|
||||
throttle_from: None,
|
||||
claim_permission: Some(CommissionClaimPermission::Account(depositor)),
|
||||
});
|
||||
}
|
||||
|
||||
set_claim_permission {
|
||||
// Create a pool
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond, None);
|
||||
|
||||
// Join pool
|
||||
let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
|
||||
let joiner = create_funded_user_with_balance::<T>("joiner", 0, min_join_bond * 4u32.into());
|
||||
let joiner_lookup = T::Lookup::unlookup(joiner.clone());
|
||||
Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1)
|
||||
.unwrap();
|
||||
|
||||
// Sanity check join worked
|
||||
assert_eq!(
|
||||
T::Staking::active_stake(&pool_account).unwrap(),
|
||||
min_create_bond + min_join_bond
|
||||
);
|
||||
}:_(RuntimeOrigin::Signed(joiner.clone()), ClaimPermission::Permissioned)
|
||||
verify {
|
||||
assert_eq!(ClaimPermissions::<T>::get(joiner), ClaimPermission::Permissioned);
|
||||
}
|
||||
|
||||
claim_commission {
|
||||
let claimer: T::AccountId = account("claimer_member", USER_SEED + 4, 0);
|
||||
let commission = Perbill::from_percent(50);
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let ed = CurrencyOf::<T>::minimum_balance();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, origin_weight, Some(commission));
|
||||
let reward_account = Pools::<T>::create_reward_account(1);
|
||||
CurrencyOf::<T>::set_balance(&reward_account, ed + origin_weight);
|
||||
|
||||
// member claims a payout to make some commission available.
|
||||
let _ = Pools::<T>::claim_payout(RuntimeOrigin::Signed(claimer.clone()).into());
|
||||
// set a claim permission to an account.
|
||||
let _ = Pools::<T>::set_commission_claim_permission(
|
||||
RuntimeOrigin::Signed(depositor.clone()).into(),
|
||||
1u32.into(),
|
||||
Some(CommissionClaimPermission::Account(claimer))
|
||||
);
|
||||
whitelist_account!(depositor);
|
||||
}:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into())
|
||||
verify {
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&depositor),
|
||||
origin_weight + commission * origin_weight
|
||||
);
|
||||
assert_eq!(
|
||||
CurrencyOf::<T>::balance(&reward_account),
|
||||
ed + commission * origin_weight
|
||||
);
|
||||
}
|
||||
|
||||
adjust_pool_deposit {
|
||||
// Create a pool
|
||||
let (depositor, _) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
|
||||
|
||||
// Remove ed freeze to create a scenario where the ed deposit needs to be adjusted.
|
||||
let _ = Pools::<T>::unfreeze_pool_deposit(&Pools::<T>::create_reward_account(1));
|
||||
assert!(&Pools::<T>::check_ed_imbalance().is_err());
|
||||
|
||||
whitelist_account!(depositor);
|
||||
}:_(RuntimeOrigin::Signed(depositor), 1)
|
||||
verify {
|
||||
assert!(&Pools::<T>::check_ed_imbalance().is_ok());
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(
|
||||
Pallet,
|
||||
crate::mock::new_test_ext(),
|
||||
crate::mock::Runtime
|
||||
);
|
||||
}
|
||||
#[cfg(all(feature = "runtime-benchmarks", test))]
|
||||
pub(crate) mod mock;
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Offences pallet benchmarking.
|
||||
|
||||
use sp_std::{prelude::*, vec};
|
||||
|
||||
use frame_benchmarking::v1::{account, benchmarks};
|
||||
use frame_support::traits::{Currency, Get};
|
||||
use frame_system::{Config as SystemConfig, Pallet as System, RawOrigin};
|
||||
|
||||
use sp_runtime::{
|
||||
traits::{Convert, Saturating, StaticLookup},
|
||||
Perbill,
|
||||
};
|
||||
use sp_staking::offence::ReportOffence;
|
||||
|
||||
use pallet_babe::EquivocationOffence as BabeEquivocationOffence;
|
||||
use pallet_balances::Config as BalancesConfig;
|
||||
use pallet_grandpa::{
|
||||
EquivocationOffence as GrandpaEquivocationOffence, TimeSlot as GrandpaTimeSlot,
|
||||
};
|
||||
use pallet_offences::{Config as OffencesConfig, Pallet as Offences};
|
||||
use pallet_session::{
|
||||
historical::{Config as HistoricalConfig, IdentificationTuple},
|
||||
Config as SessionConfig, Pallet as Session, SessionManager,
|
||||
};
|
||||
use pallet_staking::{
|
||||
Config as StakingConfig, Exposure, IndividualExposure, MaxNominationsOf, Pallet as Staking,
|
||||
RewardDestination, ValidatorPrefs,
|
||||
};
|
||||
|
||||
const SEED: u32 = 0;
|
||||
|
||||
const MAX_NOMINATORS: u32 = 100;
|
||||
|
||||
pub struct Pallet<T: Config>(Offences<T>);
|
||||
|
||||
pub trait Config:
|
||||
SessionConfig
|
||||
+ StakingConfig
|
||||
+ OffencesConfig
|
||||
+ HistoricalConfig
|
||||
+ BalancesConfig
|
||||
+ IdTupleConvert<Self>
|
||||
{
|
||||
}
|
||||
|
||||
/// A helper trait to make sure we can convert `IdentificationTuple` coming from historical
|
||||
/// and the one required by offences.
|
||||
pub trait IdTupleConvert<T: HistoricalConfig + OffencesConfig> {
|
||||
/// Convert identification tuple from `historical` trait to the one expected by `offences`.
|
||||
fn convert(id: IdentificationTuple<T>) -> <T as OffencesConfig>::IdentificationTuple;
|
||||
}
|
||||
|
||||
impl<T: HistoricalConfig + OffencesConfig> IdTupleConvert<T> for T
|
||||
where
|
||||
<T as OffencesConfig>::IdentificationTuple: From<IdentificationTuple<T>>,
|
||||
{
|
||||
fn convert(id: IdentificationTuple<T>) -> <T as OffencesConfig>::IdentificationTuple {
|
||||
id.into()
|
||||
}
|
||||
}
|
||||
|
||||
type LookupSourceOf<T> = <<T as SystemConfig>::Lookup as StaticLookup>::Source;
|
||||
type BalanceOf<T> =
|
||||
<<T as StakingConfig>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
|
||||
|
||||
struct Offender<T: Config> {
|
||||
pub controller: T::AccountId,
|
||||
#[allow(dead_code)]
|
||||
pub stash: T::AccountId,
|
||||
#[allow(dead_code)]
|
||||
pub nominator_stashes: Vec<T::AccountId>,
|
||||
}
|
||||
|
||||
fn bond_amount<T: Config>() -> BalanceOf<T> {
|
||||
T::Currency::minimum_balance().saturating_mul(10_000u32.into())
|
||||
}
|
||||
|
||||
fn create_offender<T: Config>(n: u32, nominators: u32) -> Result<Offender<T>, &'static str> {
|
||||
let stash: T::AccountId = account("stash", n, SEED);
|
||||
let stash_lookup: LookupSourceOf<T> = T::Lookup::unlookup(stash.clone());
|
||||
let reward_destination = RewardDestination::Staked;
|
||||
let amount = bond_amount::<T>();
|
||||
// add twice as much balance to prevent the account from being killed.
|
||||
let free_amount = amount.saturating_mul(2u32.into());
|
||||
T::Currency::make_free_balance_be(&stash, free_amount);
|
||||
Staking::<T>::bond(
|
||||
RawOrigin::Signed(stash.clone()).into(),
|
||||
amount,
|
||||
reward_destination.clone(),
|
||||
)?;
|
||||
|
||||
let validator_prefs =
|
||||
ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() };
|
||||
Staking::<T>::validate(RawOrigin::Signed(stash.clone()).into(), validator_prefs)?;
|
||||
|
||||
let mut individual_exposures = vec![];
|
||||
let mut nominator_stashes = vec![];
|
||||
// Create n nominators
|
||||
for i in 0..nominators {
|
||||
let nominator_stash: T::AccountId =
|
||||
account("nominator stash", n * MAX_NOMINATORS + i, SEED);
|
||||
T::Currency::make_free_balance_be(&nominator_stash, free_amount);
|
||||
|
||||
Staking::<T>::bond(
|
||||
RawOrigin::Signed(nominator_stash.clone()).into(),
|
||||
amount,
|
||||
reward_destination.clone(),
|
||||
)?;
|
||||
|
||||
let selected_validators: Vec<LookupSourceOf<T>> = vec![stash_lookup.clone()];
|
||||
Staking::<T>::nominate(
|
||||
RawOrigin::Signed(nominator_stash.clone()).into(),
|
||||
selected_validators,
|
||||
)?;
|
||||
|
||||
individual_exposures
|
||||
.push(IndividualExposure { who: nominator_stash.clone(), value: amount });
|
||||
nominator_stashes.push(nominator_stash.clone());
|
||||
}
|
||||
|
||||
let exposure = Exposure { total: amount * n.into(), own: amount, others: individual_exposures };
|
||||
let current_era = 0u32;
|
||||
Staking::<T>::add_era_stakers(current_era, stash.clone(), exposure);
|
||||
|
||||
Ok(Offender { controller: stash.clone(), stash, nominator_stashes })
|
||||
}
|
||||
|
||||
fn make_offenders<T: Config>(
|
||||
num_offenders: u32,
|
||||
num_nominators: u32,
|
||||
) -> Result<(Vec<IdentificationTuple<T>>, Vec<Offender<T>>), &'static str> {
|
||||
Staking::<T>::new_session(0);
|
||||
|
||||
let mut offenders = vec![];
|
||||
for i in 0..num_offenders {
|
||||
let offender = create_offender::<T>(i + 1, num_nominators)?;
|
||||
offenders.push(offender);
|
||||
}
|
||||
|
||||
Staking::<T>::start_session(0);
|
||||
|
||||
let id_tuples = offenders
|
||||
.iter()
|
||||
.map(|offender| {
|
||||
<T as SessionConfig>::ValidatorIdOf::convert(offender.controller.clone())
|
||||
.expect("failed to get validator id from account id")
|
||||
})
|
||||
.map(|validator_id| {
|
||||
<T as HistoricalConfig>::FullIdentificationOf::convert(validator_id.clone())
|
||||
.map(|full_id| (validator_id, full_id))
|
||||
.expect("failed to convert validator id to full identification")
|
||||
})
|
||||
.collect::<Vec<IdentificationTuple<T>>>();
|
||||
Ok((id_tuples, offenders))
|
||||
}
|
||||
|
||||
benchmarks! {
|
||||
report_offence_grandpa {
|
||||
let n in 0 .. MAX_NOMINATORS.min(MaxNominationsOf::<T>::get());
|
||||
|
||||
// for grandpa equivocation reports the number of reporters
|
||||
// and offenders is always 1
|
||||
let reporters = vec![account("reporter", 1, SEED)];
|
||||
|
||||
// make sure reporters actually get rewarded
|
||||
Staking::<T>::set_slash_reward_fraction(Perbill::one());
|
||||
|
||||
let (mut offenders, raw_offenders) = make_offenders::<T>(1, n)?;
|
||||
let validator_set_count = Session::<T>::validators().len() as u32;
|
||||
|
||||
let offence = GrandpaEquivocationOffence {
|
||||
time_slot: GrandpaTimeSlot { set_id: 0, round: 0 },
|
||||
session_index: 0,
|
||||
validator_set_count,
|
||||
offender: T::convert(offenders.pop().unwrap()),
|
||||
};
|
||||
assert_eq!(System::<T>::event_count(), 0);
|
||||
}: {
|
||||
let _ = Offences::<T>::report_offence(reporters, offence);
|
||||
}
|
||||
verify {
|
||||
// make sure that all slashes have been applied
|
||||
#[cfg(test)]
|
||||
assert_eq!(
|
||||
System::<T>::event_count(), 0
|
||||
+ 1 // offence
|
||||
+ 3 // reporter (reward + endowment)
|
||||
+ 1 // offenders reported
|
||||
+ 3 // offenders slashed
|
||||
+ 1 // offenders chilled
|
||||
+ 3 * n // nominators slashed
|
||||
);
|
||||
}
|
||||
|
||||
report_offence_babe {
|
||||
let n in 0 .. MAX_NOMINATORS.min(MaxNominationsOf::<T>::get());
|
||||
|
||||
// for babe equivocation reports the number of reporters
|
||||
// and offenders is always 1
|
||||
let reporters = vec![account("reporter", 1, SEED)];
|
||||
|
||||
// make sure reporters actually get rewarded
|
||||
Staking::<T>::set_slash_reward_fraction(Perbill::one());
|
||||
|
||||
let (mut offenders, raw_offenders) = make_offenders::<T>(1, n)?;
|
||||
let validator_set_count = Session::<T>::validators().len() as u32;
|
||||
|
||||
let offence = BabeEquivocationOffence {
|
||||
slot: 0u64.into(),
|
||||
session_index: 0,
|
||||
validator_set_count,
|
||||
offender: T::convert(offenders.pop().unwrap()),
|
||||
};
|
||||
assert_eq!(System::<T>::event_count(), 0);
|
||||
}: {
|
||||
let _ = Offences::<T>::report_offence(reporters, offence);
|
||||
}
|
||||
verify {
|
||||
// make sure that all slashes have been applied
|
||||
#[cfg(test)]
|
||||
assert_eq!(
|
||||
System::<T>::event_count(), 0
|
||||
+ 1 // offence
|
||||
+ 3 // reporter (reward + endowment)
|
||||
+ 1 // offenders reported
|
||||
+ 3 // offenders slashed
|
||||
+ 1 // offenders chilled
|
||||
+ 3 * n // nominators slashed
|
||||
);
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
|
||||
}
|
||||
@@ -17,239 +17,13 @@
|
||||
|
||||
//! Offences pallet benchmarking.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
mod mock;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub mod inner;
|
||||
|
||||
use sp_std::{prelude::*, vec};
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub use inner::*;
|
||||
|
||||
use frame_benchmarking::v1::{account, benchmarks};
|
||||
use frame_support::traits::{Currency, Get};
|
||||
use frame_system::{Config as SystemConfig, Pallet as System, RawOrigin};
|
||||
|
||||
use sp_runtime::{
|
||||
traits::{Convert, Saturating, StaticLookup},
|
||||
Perbill,
|
||||
};
|
||||
use sp_staking::offence::ReportOffence;
|
||||
|
||||
use pallet_babe::EquivocationOffence as BabeEquivocationOffence;
|
||||
use pallet_balances::Config as BalancesConfig;
|
||||
use pallet_grandpa::{
|
||||
EquivocationOffence as GrandpaEquivocationOffence, TimeSlot as GrandpaTimeSlot,
|
||||
};
|
||||
use pallet_offences::{Config as OffencesConfig, Pallet as Offences};
|
||||
use pallet_session::{
|
||||
historical::{Config as HistoricalConfig, IdentificationTuple},
|
||||
Config as SessionConfig, Pallet as Session, SessionManager,
|
||||
};
|
||||
use pallet_staking::{
|
||||
Config as StakingConfig, Exposure, IndividualExposure, MaxNominationsOf, Pallet as Staking,
|
||||
RewardDestination, ValidatorPrefs,
|
||||
};
|
||||
|
||||
const SEED: u32 = 0;
|
||||
|
||||
const MAX_NOMINATORS: u32 = 100;
|
||||
|
||||
pub struct Pallet<T: Config>(Offences<T>);
|
||||
|
||||
pub trait Config:
|
||||
SessionConfig
|
||||
+ StakingConfig
|
||||
+ OffencesConfig
|
||||
+ HistoricalConfig
|
||||
+ BalancesConfig
|
||||
+ IdTupleConvert<Self>
|
||||
{
|
||||
}
|
||||
|
||||
/// A helper trait to make sure we can convert `IdentificationTuple` coming from historical
|
||||
/// and the one required by offences.
|
||||
pub trait IdTupleConvert<T: HistoricalConfig + OffencesConfig> {
|
||||
/// Convert identification tuple from `historical` trait to the one expected by `offences`.
|
||||
fn convert(id: IdentificationTuple<T>) -> <T as OffencesConfig>::IdentificationTuple;
|
||||
}
|
||||
|
||||
impl<T: HistoricalConfig + OffencesConfig> IdTupleConvert<T> for T
|
||||
where
|
||||
<T as OffencesConfig>::IdentificationTuple: From<IdentificationTuple<T>>,
|
||||
{
|
||||
fn convert(id: IdentificationTuple<T>) -> <T as OffencesConfig>::IdentificationTuple {
|
||||
id.into()
|
||||
}
|
||||
}
|
||||
|
||||
type LookupSourceOf<T> = <<T as SystemConfig>::Lookup as StaticLookup>::Source;
|
||||
type BalanceOf<T> =
|
||||
<<T as StakingConfig>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
|
||||
|
||||
struct Offender<T: Config> {
|
||||
pub controller: T::AccountId,
|
||||
#[allow(dead_code)]
|
||||
pub stash: T::AccountId,
|
||||
#[allow(dead_code)]
|
||||
pub nominator_stashes: Vec<T::AccountId>,
|
||||
}
|
||||
|
||||
fn bond_amount<T: Config>() -> BalanceOf<T> {
|
||||
T::Currency::minimum_balance().saturating_mul(10_000u32.into())
|
||||
}
|
||||
|
||||
fn create_offender<T: Config>(n: u32, nominators: u32) -> Result<Offender<T>, &'static str> {
|
||||
let stash: T::AccountId = account("stash", n, SEED);
|
||||
let stash_lookup: LookupSourceOf<T> = T::Lookup::unlookup(stash.clone());
|
||||
let reward_destination = RewardDestination::Staked;
|
||||
let amount = bond_amount::<T>();
|
||||
// add twice as much balance to prevent the account from being killed.
|
||||
let free_amount = amount.saturating_mul(2u32.into());
|
||||
T::Currency::make_free_balance_be(&stash, free_amount);
|
||||
Staking::<T>::bond(
|
||||
RawOrigin::Signed(stash.clone()).into(),
|
||||
amount,
|
||||
reward_destination.clone(),
|
||||
)?;
|
||||
|
||||
let validator_prefs =
|
||||
ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() };
|
||||
Staking::<T>::validate(RawOrigin::Signed(stash.clone()).into(), validator_prefs)?;
|
||||
|
||||
let mut individual_exposures = vec![];
|
||||
let mut nominator_stashes = vec![];
|
||||
// Create n nominators
|
||||
for i in 0..nominators {
|
||||
let nominator_stash: T::AccountId =
|
||||
account("nominator stash", n * MAX_NOMINATORS + i, SEED);
|
||||
T::Currency::make_free_balance_be(&nominator_stash, free_amount);
|
||||
|
||||
Staking::<T>::bond(
|
||||
RawOrigin::Signed(nominator_stash.clone()).into(),
|
||||
amount,
|
||||
reward_destination.clone(),
|
||||
)?;
|
||||
|
||||
let selected_validators: Vec<LookupSourceOf<T>> = vec![stash_lookup.clone()];
|
||||
Staking::<T>::nominate(
|
||||
RawOrigin::Signed(nominator_stash.clone()).into(),
|
||||
selected_validators,
|
||||
)?;
|
||||
|
||||
individual_exposures
|
||||
.push(IndividualExposure { who: nominator_stash.clone(), value: amount });
|
||||
nominator_stashes.push(nominator_stash.clone());
|
||||
}
|
||||
|
||||
let exposure = Exposure { total: amount * n.into(), own: amount, others: individual_exposures };
|
||||
let current_era = 0u32;
|
||||
Staking::<T>::add_era_stakers(current_era, stash.clone(), exposure);
|
||||
|
||||
Ok(Offender { controller: stash.clone(), stash, nominator_stashes })
|
||||
}
|
||||
|
||||
fn make_offenders<T: Config>(
|
||||
num_offenders: u32,
|
||||
num_nominators: u32,
|
||||
) -> Result<(Vec<IdentificationTuple<T>>, Vec<Offender<T>>), &'static str> {
|
||||
Staking::<T>::new_session(0);
|
||||
|
||||
let mut offenders = vec![];
|
||||
for i in 0..num_offenders {
|
||||
let offender = create_offender::<T>(i + 1, num_nominators)?;
|
||||
offenders.push(offender);
|
||||
}
|
||||
|
||||
Staking::<T>::start_session(0);
|
||||
|
||||
let id_tuples = offenders
|
||||
.iter()
|
||||
.map(|offender| {
|
||||
<T as SessionConfig>::ValidatorIdOf::convert(offender.controller.clone())
|
||||
.expect("failed to get validator id from account id")
|
||||
})
|
||||
.map(|validator_id| {
|
||||
<T as HistoricalConfig>::FullIdentificationOf::convert(validator_id.clone())
|
||||
.map(|full_id| (validator_id, full_id))
|
||||
.expect("failed to convert validator id to full identification")
|
||||
})
|
||||
.collect::<Vec<IdentificationTuple<T>>>();
|
||||
Ok((id_tuples, offenders))
|
||||
}
|
||||
|
||||
benchmarks! {
|
||||
report_offence_grandpa {
|
||||
let n in 0 .. MAX_NOMINATORS.min(MaxNominationsOf::<T>::get());
|
||||
|
||||
// for grandpa equivocation reports the number of reporters
|
||||
// and offenders is always 1
|
||||
let reporters = vec![account("reporter", 1, SEED)];
|
||||
|
||||
// make sure reporters actually get rewarded
|
||||
Staking::<T>::set_slash_reward_fraction(Perbill::one());
|
||||
|
||||
let (mut offenders, raw_offenders) = make_offenders::<T>(1, n)?;
|
||||
let validator_set_count = Session::<T>::validators().len() as u32;
|
||||
|
||||
let offence = GrandpaEquivocationOffence {
|
||||
time_slot: GrandpaTimeSlot { set_id: 0, round: 0 },
|
||||
session_index: 0,
|
||||
validator_set_count,
|
||||
offender: T::convert(offenders.pop().unwrap()),
|
||||
};
|
||||
assert_eq!(System::<T>::event_count(), 0);
|
||||
}: {
|
||||
let _ = Offences::<T>::report_offence(reporters, offence);
|
||||
}
|
||||
verify {
|
||||
// make sure that all slashes have been applied
|
||||
#[cfg(test)]
|
||||
assert_eq!(
|
||||
System::<T>::event_count(), 0
|
||||
+ 1 // offence
|
||||
+ 3 // reporter (reward + endowment)
|
||||
+ 1 // offenders reported
|
||||
+ 3 // offenders slashed
|
||||
+ 1 // offenders chilled
|
||||
+ 3 * n // nominators slashed
|
||||
);
|
||||
}
|
||||
|
||||
report_offence_babe {
|
||||
let n in 0 .. MAX_NOMINATORS.min(MaxNominationsOf::<T>::get());
|
||||
|
||||
// for babe equivocation reports the number of reporters
|
||||
// and offenders is always 1
|
||||
let reporters = vec![account("reporter", 1, SEED)];
|
||||
|
||||
// make sure reporters actually get rewarded
|
||||
Staking::<T>::set_slash_reward_fraction(Perbill::one());
|
||||
|
||||
let (mut offenders, raw_offenders) = make_offenders::<T>(1, n)?;
|
||||
let validator_set_count = Session::<T>::validators().len() as u32;
|
||||
|
||||
let offence = BabeEquivocationOffence {
|
||||
slot: 0u64.into(),
|
||||
session_index: 0,
|
||||
validator_set_count,
|
||||
offender: T::convert(offenders.pop().unwrap()),
|
||||
};
|
||||
assert_eq!(System::<T>::event_count(), 0);
|
||||
}: {
|
||||
let _ = Offences::<T>::report_offence(reporters, offence);
|
||||
}
|
||||
verify {
|
||||
// make sure that all slashes have been applied
|
||||
#[cfg(test)]
|
||||
assert_eq!(
|
||||
System::<T>::event_count(), 0
|
||||
+ 1 // offence
|
||||
+ 3 // reporter (reward + endowment)
|
||||
+ 1 // offenders reported
|
||||
+ 3 // offenders slashed
|
||||
+ 1 // offenders chilled
|
||||
+ 3 * n // nominators slashed
|
||||
);
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
|
||||
}
|
||||
#[cfg(all(feature = "runtime-benchmarks", test))]
|
||||
pub(crate) mod mock;
|
||||
|
||||
@@ -17,9 +17,6 @@
|
||||
|
||||
//! Mock file for offences benchmarking.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
use frame_election_provider_support::{
|
||||
bounds::{ElectionBounds, ElectionBoundsBuilder},
|
||||
onchain, SequentialPhragmen,
|
||||
@@ -33,7 +30,7 @@ use pallet_session::historical as pallet_session_historical;
|
||||
use sp_runtime::{
|
||||
testing::{Header, UintAuthorityId},
|
||||
traits::IdentityLookup,
|
||||
BuildStorage,
|
||||
BuildStorage, Perbill,
|
||||
};
|
||||
|
||||
type AccountId = u64;
|
||||
|
||||
@@ -24,7 +24,7 @@ pallet-staking = { path = "../staking", default-features = false }
|
||||
|
||||
frame-support = { path = "../support", default-features = false }
|
||||
frame-system = { path = "../system", default-features = false }
|
||||
sp-runtime = { path = "../../primitives/runtime" }
|
||||
sp-runtime = { path = "../../primitives/runtime", default-features = false }
|
||||
sp-staking = { path = "../../primitives/staking", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -34,7 +34,7 @@ pallet-staking-reward-curve = { path = "../staking/reward-curve" }
|
||||
|
||||
sp-core = { path = "../../primitives/core" }
|
||||
sp-io = { path = "../../primitives/io", default-features = false }
|
||||
sp-std = { path = "../../primitives/std", default-features = false }
|
||||
sp-std = { path = "../../primitives/std" }
|
||||
|
||||
frame-election-provider-support = { path = "../election-provider-support" }
|
||||
|
||||
@@ -74,5 +74,4 @@ std = [
|
||||
"sp-io/std",
|
||||
"sp-runtime/std",
|
||||
"sp-staking/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
|
||||
@@ -27,6 +27,9 @@ mod mock;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use pallet_session::historical::IdentificationTuple;
|
||||
use pallet_staking::{BalanceOf, Exposure, ExposureOf, Pallet as Staking};
|
||||
use sp_runtime::Perbill;
|
||||
@@ -112,7 +115,7 @@ pub mod pallet {
|
||||
.into_iter()
|
||||
.map(|(o, _)| OffenceDetails::<T> {
|
||||
offender: (o.clone(), Staking::<T>::eras_stakers(now, &o)),
|
||||
reporters: vec![],
|
||||
reporters: Default::default(),
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Benchmarks for the Session Pallet.
|
||||
// This is separated into its own crate due to cyclic dependency issues.
|
||||
|
||||
use sp_runtime::traits::{One, StaticLookup, TrailingZeroInput};
|
||||
use sp_std::{prelude::*, vec};
|
||||
|
||||
use codec::Decode;
|
||||
use frame_benchmarking::v1::benchmarks;
|
||||
use frame_support::traits::{Get, KeyOwnerProofSystem, OnInitialize};
|
||||
use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin};
|
||||
use pallet_session::{historical::Pallet as Historical, Pallet as Session, *};
|
||||
use pallet_staking::{
|
||||
benchmarking::create_validator_with_nominators, testing_utils::create_validators,
|
||||
MaxNominationsOf, RewardDestination,
|
||||
};
|
||||
|
||||
const MAX_VALIDATORS: u32 = 1000;
|
||||
|
||||
pub struct Pallet<T: Config>(pallet_session::Pallet<T>);
|
||||
pub trait Config:
|
||||
pallet_session::Config + pallet_session::historical::Config + pallet_staking::Config
|
||||
{
|
||||
}
|
||||
|
||||
impl<T: Config> OnInitialize<BlockNumberFor<T>> for Pallet<T> {
|
||||
fn on_initialize(n: BlockNumberFor<T>) -> frame_support::weights::Weight {
|
||||
pallet_session::Pallet::<T>::on_initialize(n)
|
||||
}
|
||||
}
|
||||
|
||||
benchmarks! {
|
||||
set_keys {
|
||||
let n = MaxNominationsOf::<T>::get();
|
||||
let (v_stash, _) = create_validator_with_nominators::<T>(
|
||||
n,
|
||||
MaxNominationsOf::<T>::get(),
|
||||
false,
|
||||
true,
|
||||
RewardDestination::Staked,
|
||||
)?;
|
||||
let v_controller = pallet_staking::Pallet::<T>::bonded(&v_stash).ok_or("not stash")?;
|
||||
|
||||
let keys = T::Keys::decode(&mut TrailingZeroInput::zeroes()).unwrap();
|
||||
let proof: Vec<u8> = vec![0,1,2,3];
|
||||
// Whitelist controller account from further DB operations.
|
||||
let v_controller_key = frame_system::Account::<T>::hashed_key_for(&v_controller);
|
||||
frame_benchmarking::benchmarking::add_to_whitelist(v_controller_key.into());
|
||||
}: _(RawOrigin::Signed(v_controller), keys, proof)
|
||||
|
||||
purge_keys {
|
||||
let n = MaxNominationsOf::<T>::get();
|
||||
let (v_stash, _) = create_validator_with_nominators::<T>(
|
||||
n,
|
||||
MaxNominationsOf::<T>::get(),
|
||||
false,
|
||||
true,
|
||||
RewardDestination::Staked,
|
||||
)?;
|
||||
let v_controller = pallet_staking::Pallet::<T>::bonded(&v_stash).ok_or("not stash")?;
|
||||
let keys = T::Keys::decode(&mut TrailingZeroInput::zeroes()).unwrap();
|
||||
let proof: Vec<u8> = vec![0,1,2,3];
|
||||
Session::<T>::set_keys(RawOrigin::Signed(v_controller.clone()).into(), keys, proof)?;
|
||||
// Whitelist controller account from further DB operations.
|
||||
let v_controller_key = frame_system::Account::<T>::hashed_key_for(&v_controller);
|
||||
frame_benchmarking::benchmarking::add_to_whitelist(v_controller_key.into());
|
||||
}: _(RawOrigin::Signed(v_controller))
|
||||
|
||||
#[extra]
|
||||
check_membership_proof_current_session {
|
||||
let n in 2 .. MAX_VALIDATORS as u32;
|
||||
|
||||
let (key, key_owner_proof1) = check_membership_proof_setup::<T>(n);
|
||||
let key_owner_proof2 = key_owner_proof1.clone();
|
||||
}: {
|
||||
Historical::<T>::check_proof(key, key_owner_proof1);
|
||||
}
|
||||
verify {
|
||||
assert!(Historical::<T>::check_proof(key, key_owner_proof2).is_some());
|
||||
}
|
||||
|
||||
#[extra]
|
||||
check_membership_proof_historical_session {
|
||||
let n in 2 .. MAX_VALIDATORS as u32;
|
||||
|
||||
let (key, key_owner_proof1) = check_membership_proof_setup::<T>(n);
|
||||
|
||||
// skip to the next session so that the session is historical
|
||||
// and the membership merkle proof must be checked.
|
||||
Session::<T>::rotate_session();
|
||||
|
||||
let key_owner_proof2 = key_owner_proof1.clone();
|
||||
}: {
|
||||
Historical::<T>::check_proof(key, key_owner_proof1);
|
||||
}
|
||||
verify {
|
||||
assert!(Historical::<T>::check_proof(key, key_owner_proof2).is_some());
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test, extra = false);
|
||||
}
|
||||
|
||||
/// Sets up the benchmark for checking a membership proof. It creates the given
|
||||
/// number of validators, sets random session keys and then creates a membership
|
||||
/// proof for the first authority and returns its key and the proof.
|
||||
fn check_membership_proof_setup<T: Config>(
|
||||
n: u32,
|
||||
) -> ((sp_runtime::KeyTypeId, &'static [u8; 32]), sp_session::MembershipProof) {
|
||||
pallet_staking::ValidatorCount::<T>::put(n);
|
||||
|
||||
// create validators and set random session keys
|
||||
for (n, who) in create_validators::<T>(n, 1000).unwrap().into_iter().enumerate() {
|
||||
use rand::{RngCore, SeedableRng};
|
||||
|
||||
let validator = T::Lookup::lookup(who).unwrap();
|
||||
let controller = pallet_staking::Pallet::<T>::bonded(&validator).unwrap();
|
||||
|
||||
let keys = {
|
||||
let mut keys = [0u8; 128];
|
||||
|
||||
// we keep the keys for the first validator as 0x00000...
|
||||
if n > 0 {
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(n as u64);
|
||||
rng.fill_bytes(&mut keys);
|
||||
}
|
||||
|
||||
keys
|
||||
};
|
||||
|
||||
let keys: T::Keys = Decode::decode(&mut &keys[..]).unwrap();
|
||||
let proof: Vec<u8> = vec![];
|
||||
|
||||
Session::<T>::set_keys(RawOrigin::Signed(controller).into(), keys, proof).unwrap();
|
||||
}
|
||||
|
||||
Pallet::<T>::on_initialize(frame_system::pallet_prelude::BlockNumberFor::<T>::one());
|
||||
|
||||
// skip sessions until the new validator set is enacted
|
||||
while Session::<T>::validators().len() < n as usize {
|
||||
Session::<T>::rotate_session();
|
||||
}
|
||||
|
||||
let key = (sp_runtime::KeyTypeId(*b"babe"), &[0u8; 32]);
|
||||
|
||||
(key, Historical::<T>::prove(key).unwrap())
|
||||
}
|
||||
@@ -15,153 +15,15 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Benchmarks for the Session Pallet.
|
||||
// This is separated into its own crate due to cyclic dependency issues.
|
||||
//! Offences pallet benchmarking.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
mod mock;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub mod inner;
|
||||
|
||||
use sp_runtime::traits::{One, StaticLookup, TrailingZeroInput};
|
||||
use sp_std::{prelude::*, vec};
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub use inner::*;
|
||||
|
||||
use codec::Decode;
|
||||
use frame_benchmarking::v1::benchmarks;
|
||||
use frame_support::traits::{Get, KeyOwnerProofSystem, OnInitialize};
|
||||
use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin};
|
||||
use pallet_session::{historical::Pallet as Historical, Pallet as Session, *};
|
||||
use pallet_staking::{
|
||||
benchmarking::create_validator_with_nominators, testing_utils::create_validators,
|
||||
MaxNominationsOf, RewardDestination,
|
||||
};
|
||||
|
||||
const MAX_VALIDATORS: u32 = 1000;
|
||||
|
||||
pub struct Pallet<T: Config>(pallet_session::Pallet<T>);
|
||||
pub trait Config:
|
||||
pallet_session::Config + pallet_session::historical::Config + pallet_staking::Config
|
||||
{
|
||||
}
|
||||
|
||||
impl<T: Config> OnInitialize<BlockNumberFor<T>> for Pallet<T> {
|
||||
fn on_initialize(n: BlockNumberFor<T>) -> frame_support::weights::Weight {
|
||||
pallet_session::Pallet::<T>::on_initialize(n)
|
||||
}
|
||||
}
|
||||
|
||||
benchmarks! {
|
||||
set_keys {
|
||||
let n = MaxNominationsOf::<T>::get();
|
||||
let (v_stash, _) = create_validator_with_nominators::<T>(
|
||||
n,
|
||||
MaxNominationsOf::<T>::get(),
|
||||
false,
|
||||
true,
|
||||
RewardDestination::Staked,
|
||||
)?;
|
||||
let v_controller = pallet_staking::Pallet::<T>::bonded(&v_stash).ok_or("not stash")?;
|
||||
|
||||
let keys = T::Keys::decode(&mut TrailingZeroInput::zeroes()).unwrap();
|
||||
let proof: Vec<u8> = vec![0,1,2,3];
|
||||
// Whitelist controller account from further DB operations.
|
||||
let v_controller_key = frame_system::Account::<T>::hashed_key_for(&v_controller);
|
||||
frame_benchmarking::benchmarking::add_to_whitelist(v_controller_key.into());
|
||||
}: _(RawOrigin::Signed(v_controller), keys, proof)
|
||||
|
||||
purge_keys {
|
||||
let n = MaxNominationsOf::<T>::get();
|
||||
let (v_stash, _) = create_validator_with_nominators::<T>(
|
||||
n,
|
||||
MaxNominationsOf::<T>::get(),
|
||||
false,
|
||||
true,
|
||||
RewardDestination::Staked,
|
||||
)?;
|
||||
let v_controller = pallet_staking::Pallet::<T>::bonded(&v_stash).ok_or("not stash")?;
|
||||
let keys = T::Keys::decode(&mut TrailingZeroInput::zeroes()).unwrap();
|
||||
let proof: Vec<u8> = vec![0,1,2,3];
|
||||
Session::<T>::set_keys(RawOrigin::Signed(v_controller.clone()).into(), keys, proof)?;
|
||||
// Whitelist controller account from further DB operations.
|
||||
let v_controller_key = frame_system::Account::<T>::hashed_key_for(&v_controller);
|
||||
frame_benchmarking::benchmarking::add_to_whitelist(v_controller_key.into());
|
||||
}: _(RawOrigin::Signed(v_controller))
|
||||
|
||||
#[extra]
|
||||
check_membership_proof_current_session {
|
||||
let n in 2 .. MAX_VALIDATORS as u32;
|
||||
|
||||
let (key, key_owner_proof1) = check_membership_proof_setup::<T>(n);
|
||||
let key_owner_proof2 = key_owner_proof1.clone();
|
||||
}: {
|
||||
Historical::<T>::check_proof(key, key_owner_proof1);
|
||||
}
|
||||
verify {
|
||||
assert!(Historical::<T>::check_proof(key, key_owner_proof2).is_some());
|
||||
}
|
||||
|
||||
#[extra]
|
||||
check_membership_proof_historical_session {
|
||||
let n in 2 .. MAX_VALIDATORS as u32;
|
||||
|
||||
let (key, key_owner_proof1) = check_membership_proof_setup::<T>(n);
|
||||
|
||||
// skip to the next session so that the session is historical
|
||||
// and the membership merkle proof must be checked.
|
||||
Session::<T>::rotate_session();
|
||||
|
||||
let key_owner_proof2 = key_owner_proof1.clone();
|
||||
}: {
|
||||
Historical::<T>::check_proof(key, key_owner_proof1);
|
||||
}
|
||||
verify {
|
||||
assert!(Historical::<T>::check_proof(key, key_owner_proof2).is_some());
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test, extra = false);
|
||||
}
|
||||
|
||||
/// Sets up the benchmark for checking a membership proof. It creates the given
|
||||
/// number of validators, sets random session keys and then creates a membership
|
||||
/// proof for the first authority and returns its key and the proof.
|
||||
fn check_membership_proof_setup<T: Config>(
|
||||
n: u32,
|
||||
) -> ((sp_runtime::KeyTypeId, &'static [u8; 32]), sp_session::MembershipProof) {
|
||||
pallet_staking::ValidatorCount::<T>::put(n);
|
||||
|
||||
// create validators and set random session keys
|
||||
for (n, who) in create_validators::<T>(n, 1000).unwrap().into_iter().enumerate() {
|
||||
use rand::{RngCore, SeedableRng};
|
||||
|
||||
let validator = T::Lookup::lookup(who).unwrap();
|
||||
let controller = pallet_staking::Pallet::<T>::bonded(&validator).unwrap();
|
||||
|
||||
let keys = {
|
||||
let mut keys = [0u8; 128];
|
||||
|
||||
// we keep the keys for the first validator as 0x00000...
|
||||
if n > 0 {
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(n as u64);
|
||||
rng.fill_bytes(&mut keys);
|
||||
}
|
||||
|
||||
keys
|
||||
};
|
||||
|
||||
let keys: T::Keys = Decode::decode(&mut &keys[..]).unwrap();
|
||||
let proof: Vec<u8> = vec![];
|
||||
|
||||
Session::<T>::set_keys(RawOrigin::Signed(controller).into(), keys, proof).unwrap();
|
||||
}
|
||||
|
||||
Pallet::<T>::on_initialize(frame_system::pallet_prelude::BlockNumberFor::<T>::one());
|
||||
|
||||
// skip sessions until the new validator set is enacted
|
||||
while Session::<T>::validators().len() < n as usize {
|
||||
Session::<T>::rotate_session();
|
||||
}
|
||||
|
||||
let key = (sp_runtime::KeyTypeId(*b"babe"), &[0u8; 32]);
|
||||
|
||||
(key, Historical::<T>::prove(key).unwrap())
|
||||
}
|
||||
#[cfg(all(feature = "runtime-benchmarks", test))]
|
||||
pub(crate) mod mock;
|
||||
|
||||
@@ -34,9 +34,9 @@
|
||||
//!
|
||||
//! See [`polkadot_sdk::frame`](../polkadot_sdk_docs/polkadot_sdk/frame_runtime/index.html).
|
||||
//!
|
||||
//! ## Warning: Experimental
|
||||
//! ## WARNING: Experimental
|
||||
//!
|
||||
//! This crate and all of its content is experimental, and should not yet be used in production.
|
||||
//! **This crate and all of its content is experimental, and should not yet be used in production.**
|
||||
//!
|
||||
//! ## Underlying dependencies
|
||||
//!
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Frame System benchmarks.
|
||||
|
||||
use codec::Encode;
|
||||
use frame_benchmarking::v2::*;
|
||||
use frame_support::{dispatch::DispatchClass, storage, traits::Get};
|
||||
use frame_system::{Call, Pallet as System, RawOrigin};
|
||||
use sp_core::storage::well_known_keys;
|
||||
use sp_runtime::traits::Hash;
|
||||
use sp_std::{prelude::*, vec};
|
||||
|
||||
pub struct Pallet<T: Config>(System<T>);
|
||||
pub trait Config: frame_system::Config {
|
||||
/// Adds ability to the Runtime to test against their sample code.
|
||||
///
|
||||
/// Default is `../res/kitchensink_runtime.compact.compressed.wasm`.
|
||||
fn prepare_set_code_data() -> Vec<u8> {
|
||||
include_bytes!("../res/kitchensink_runtime.compact.compressed.wasm").to_vec()
|
||||
}
|
||||
|
||||
/// Adds ability to the Runtime to prepare/initialize before running benchmark `set_code`.
|
||||
fn setup_set_code_requirements(_code: &Vec<u8>) -> Result<(), BenchmarkError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds ability to the Runtime to do custom validation after benchmark.
|
||||
///
|
||||
/// Default is checking for `CodeUpdated` event .
|
||||
fn verify_set_code() {
|
||||
System::<Self>::assert_last_event(frame_system::Event::<Self>::CodeUpdated.into());
|
||||
}
|
||||
}
|
||||
|
||||
#[benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn remark(
|
||||
b: Linear<0, { *T::BlockLength::get().max.get(DispatchClass::Normal) as u32 }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let remark_message = vec![1; b as usize];
|
||||
let caller = whitelisted_caller();
|
||||
|
||||
#[extrinsic_call]
|
||||
remark(RawOrigin::Signed(caller), remark_message);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn remark_with_event(
|
||||
b: Linear<0, { *T::BlockLength::get().max.get(DispatchClass::Normal) as u32 }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let remark_message = vec![1; b as usize];
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let hash = T::Hashing::hash(&remark_message[..]);
|
||||
|
||||
#[extrinsic_call]
|
||||
remark_with_event(RawOrigin::Signed(caller.clone()), remark_message);
|
||||
|
||||
System::<T>::assert_last_event(
|
||||
frame_system::Event::<T>::Remarked { sender: caller, hash }.into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn set_heap_pages() -> Result<(), BenchmarkError> {
|
||||
#[extrinsic_call]
|
||||
set_heap_pages(RawOrigin::Root, Default::default());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn set_code() -> Result<(), BenchmarkError> {
|
||||
let runtime_blob = T::prepare_set_code_data();
|
||||
T::setup_set_code_requirements(&runtime_blob)?;
|
||||
|
||||
#[extrinsic_call]
|
||||
set_code(RawOrigin::Root, runtime_blob);
|
||||
|
||||
T::verify_set_code();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark(extra)]
|
||||
fn set_code_without_checks() -> Result<(), BenchmarkError> {
|
||||
// Assume Wasm ~4MB
|
||||
let code = vec![1; 4_000_000 as usize];
|
||||
T::setup_set_code_requirements(&code)?;
|
||||
|
||||
#[block]
|
||||
{
|
||||
System::<T>::set_code_without_checks(RawOrigin::Root.into(), code)?;
|
||||
}
|
||||
|
||||
let current_code =
|
||||
storage::unhashed::get_raw(well_known_keys::CODE).ok_or("Code not stored.")?;
|
||||
assert_eq!(current_code.len(), 4_000_000 as usize);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark(skip_meta)]
|
||||
fn set_storage(i: Linear<0, { 1_000 }>) -> Result<(), BenchmarkError> {
|
||||
// Set up i items to add
|
||||
let mut items = Vec::new();
|
||||
for j in 0..i {
|
||||
let hash = (i, j).using_encoded(T::Hashing::hash).as_ref().to_vec();
|
||||
items.push((hash.clone(), hash.clone()));
|
||||
}
|
||||
|
||||
let items_to_verify = items.clone();
|
||||
|
||||
#[extrinsic_call]
|
||||
set_storage(RawOrigin::Root, items);
|
||||
|
||||
// Verify that they're actually in the storage.
|
||||
for (item, _) in items_to_verify {
|
||||
let value = storage::unhashed::get_raw(&item).ok_or("No value stored")?;
|
||||
assert_eq!(value, *item);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark(skip_meta)]
|
||||
fn kill_storage(i: Linear<0, { 1_000 }>) -> Result<(), BenchmarkError> {
|
||||
// Add i items to storage
|
||||
let mut items = Vec::with_capacity(i as usize);
|
||||
for j in 0..i {
|
||||
let hash = (i, j).using_encoded(T::Hashing::hash).as_ref().to_vec();
|
||||
storage::unhashed::put_raw(&hash, &hash);
|
||||
items.push(hash);
|
||||
}
|
||||
|
||||
// Verify that they're actually in the storage.
|
||||
for item in &items {
|
||||
let value = storage::unhashed::get_raw(item).ok_or("No value stored")?;
|
||||
assert_eq!(value, *item);
|
||||
}
|
||||
|
||||
let items_to_verify = items.clone();
|
||||
|
||||
#[extrinsic_call]
|
||||
kill_storage(RawOrigin::Root, items);
|
||||
|
||||
// Verify that they're not in the storage anymore.
|
||||
for item in items_to_verify {
|
||||
assert!(storage::unhashed::get_raw(&item).is_none());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark(skip_meta)]
|
||||
fn kill_prefix(p: Linear<0, { 1_000 }>) -> Result<(), BenchmarkError> {
|
||||
let prefix = p.using_encoded(T::Hashing::hash).as_ref().to_vec();
|
||||
let mut items = Vec::with_capacity(p as usize);
|
||||
// add p items that share a prefix
|
||||
for i in 0..p {
|
||||
let hash = (p, i).using_encoded(T::Hashing::hash).as_ref().to_vec();
|
||||
let key = [&prefix[..], &hash[..]].concat();
|
||||
storage::unhashed::put_raw(&key, &key);
|
||||
items.push(key);
|
||||
}
|
||||
|
||||
// Verify that they're actually in the storage.
|
||||
for item in &items {
|
||||
let value = storage::unhashed::get_raw(item).ok_or("No value stored")?;
|
||||
assert_eq!(value, *item);
|
||||
}
|
||||
|
||||
#[extrinsic_call]
|
||||
kill_prefix(RawOrigin::Root, prefix, p);
|
||||
|
||||
// Verify that they're not in the storage anymore.
|
||||
for item in items {
|
||||
assert!(storage::unhashed::get_raw(&item).is_none());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn authorize_upgrade() -> Result<(), BenchmarkError> {
|
||||
let runtime_blob = T::prepare_set_code_data();
|
||||
T::setup_set_code_requirements(&runtime_blob)?;
|
||||
let hash = T::Hashing::hash(&runtime_blob);
|
||||
|
||||
#[extrinsic_call]
|
||||
authorize_upgrade(RawOrigin::Root, hash);
|
||||
|
||||
assert!(System::<T>::authorized_upgrade().is_some());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn apply_authorized_upgrade() -> Result<(), BenchmarkError> {
|
||||
let runtime_blob = T::prepare_set_code_data();
|
||||
T::setup_set_code_requirements(&runtime_blob)?;
|
||||
let hash = T::Hashing::hash(&runtime_blob);
|
||||
// Will be heavier when it needs to do verification (i.e. don't use `...without_checks`).
|
||||
System::<T>::authorize_upgrade(RawOrigin::Root.into(), hash)?;
|
||||
|
||||
#[extrinsic_call]
|
||||
apply_authorized_upgrade(RawOrigin::Root, runtime_blob);
|
||||
|
||||
// Can't check for `CodeUpdated` in parachain upgrades. Just check that the authorization is
|
||||
// gone.
|
||||
assert!(System::<T>::authorized_upgrade().is_none());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
|
||||
}
|
||||
@@ -15,221 +15,15 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Benchmarks for Utility Pallet
|
||||
//! Frame System benchmarks.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use codec::Encode;
|
||||
use frame_benchmarking::v2::*;
|
||||
use frame_support::{dispatch::DispatchClass, storage, traits::Get};
|
||||
use frame_system::{Call, Pallet as System, RawOrigin};
|
||||
use sp_core::storage::well_known_keys;
|
||||
use sp_runtime::traits::Hash;
|
||||
use sp_std::{prelude::*, vec};
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub mod inner;
|
||||
|
||||
mod mock;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub use inner::*;
|
||||
|
||||
pub struct Pallet<T: Config>(System<T>);
|
||||
pub trait Config: frame_system::Config {
|
||||
/// Adds ability to the Runtime to test against their sample code.
|
||||
///
|
||||
/// Default is `../res/kitchensink_runtime.compact.compressed.wasm`.
|
||||
fn prepare_set_code_data() -> Vec<u8> {
|
||||
include_bytes!("../res/kitchensink_runtime.compact.compressed.wasm").to_vec()
|
||||
}
|
||||
|
||||
/// Adds ability to the Runtime to prepare/initialize before running benchmark `set_code`.
|
||||
fn setup_set_code_requirements(_code: &Vec<u8>) -> Result<(), BenchmarkError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds ability to the Runtime to do custom validation after benchmark.
|
||||
///
|
||||
/// Default is checking for `CodeUpdated` event .
|
||||
fn verify_set_code() {
|
||||
System::<Self>::assert_last_event(frame_system::Event::<Self>::CodeUpdated.into());
|
||||
}
|
||||
}
|
||||
|
||||
#[benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn remark(
|
||||
b: Linear<0, { *T::BlockLength::get().max.get(DispatchClass::Normal) as u32 }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let remark_message = vec![1; b as usize];
|
||||
let caller = whitelisted_caller();
|
||||
|
||||
#[extrinsic_call]
|
||||
remark(RawOrigin::Signed(caller), remark_message);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn remark_with_event(
|
||||
b: Linear<0, { *T::BlockLength::get().max.get(DispatchClass::Normal) as u32 }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let remark_message = vec![1; b as usize];
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let hash = T::Hashing::hash(&remark_message[..]);
|
||||
|
||||
#[extrinsic_call]
|
||||
remark_with_event(RawOrigin::Signed(caller.clone()), remark_message);
|
||||
|
||||
System::<T>::assert_last_event(
|
||||
frame_system::Event::<T>::Remarked { sender: caller, hash }.into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn set_heap_pages() -> Result<(), BenchmarkError> {
|
||||
#[extrinsic_call]
|
||||
set_heap_pages(RawOrigin::Root, Default::default());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn set_code() -> Result<(), BenchmarkError> {
|
||||
let runtime_blob = T::prepare_set_code_data();
|
||||
T::setup_set_code_requirements(&runtime_blob)?;
|
||||
|
||||
#[extrinsic_call]
|
||||
set_code(RawOrigin::Root, runtime_blob);
|
||||
|
||||
T::verify_set_code();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark(extra)]
|
||||
fn set_code_without_checks() -> Result<(), BenchmarkError> {
|
||||
// Assume Wasm ~4MB
|
||||
let code = vec![1; 4_000_000 as usize];
|
||||
T::setup_set_code_requirements(&code)?;
|
||||
|
||||
#[block]
|
||||
{
|
||||
System::<T>::set_code_without_checks(RawOrigin::Root.into(), code)?;
|
||||
}
|
||||
|
||||
let current_code =
|
||||
storage::unhashed::get_raw(well_known_keys::CODE).ok_or("Code not stored.")?;
|
||||
assert_eq!(current_code.len(), 4_000_000 as usize);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark(skip_meta)]
|
||||
fn set_storage(i: Linear<0, { 1_000 }>) -> Result<(), BenchmarkError> {
|
||||
// Set up i items to add
|
||||
let mut items = Vec::new();
|
||||
for j in 0..i {
|
||||
let hash = (i, j).using_encoded(T::Hashing::hash).as_ref().to_vec();
|
||||
items.push((hash.clone(), hash.clone()));
|
||||
}
|
||||
|
||||
let items_to_verify = items.clone();
|
||||
|
||||
#[extrinsic_call]
|
||||
set_storage(RawOrigin::Root, items);
|
||||
|
||||
// Verify that they're actually in the storage.
|
||||
for (item, _) in items_to_verify {
|
||||
let value = storage::unhashed::get_raw(&item).ok_or("No value stored")?;
|
||||
assert_eq!(value, *item);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark(skip_meta)]
|
||||
fn kill_storage(i: Linear<0, { 1_000 }>) -> Result<(), BenchmarkError> {
|
||||
// Add i items to storage
|
||||
let mut items = Vec::with_capacity(i as usize);
|
||||
for j in 0..i {
|
||||
let hash = (i, j).using_encoded(T::Hashing::hash).as_ref().to_vec();
|
||||
storage::unhashed::put_raw(&hash, &hash);
|
||||
items.push(hash);
|
||||
}
|
||||
|
||||
// Verify that they're actually in the storage.
|
||||
for item in &items {
|
||||
let value = storage::unhashed::get_raw(item).ok_or("No value stored")?;
|
||||
assert_eq!(value, *item);
|
||||
}
|
||||
|
||||
let items_to_verify = items.clone();
|
||||
|
||||
#[extrinsic_call]
|
||||
kill_storage(RawOrigin::Root, items);
|
||||
|
||||
// Verify that they're not in the storage anymore.
|
||||
for item in items_to_verify {
|
||||
assert!(storage::unhashed::get_raw(&item).is_none());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark(skip_meta)]
|
||||
fn kill_prefix(p: Linear<0, { 1_000 }>) -> Result<(), BenchmarkError> {
|
||||
let prefix = p.using_encoded(T::Hashing::hash).as_ref().to_vec();
|
||||
let mut items = Vec::with_capacity(p as usize);
|
||||
// add p items that share a prefix
|
||||
for i in 0..p {
|
||||
let hash = (p, i).using_encoded(T::Hashing::hash).as_ref().to_vec();
|
||||
let key = [&prefix[..], &hash[..]].concat();
|
||||
storage::unhashed::put_raw(&key, &key);
|
||||
items.push(key);
|
||||
}
|
||||
|
||||
// Verify that they're actually in the storage.
|
||||
for item in &items {
|
||||
let value = storage::unhashed::get_raw(item).ok_or("No value stored")?;
|
||||
assert_eq!(value, *item);
|
||||
}
|
||||
|
||||
#[extrinsic_call]
|
||||
kill_prefix(RawOrigin::Root, prefix, p);
|
||||
|
||||
// Verify that they're not in the storage anymore.
|
||||
for item in items {
|
||||
assert!(storage::unhashed::get_raw(&item).is_none());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn authorize_upgrade() -> Result<(), BenchmarkError> {
|
||||
let runtime_blob = T::prepare_set_code_data();
|
||||
T::setup_set_code_requirements(&runtime_blob)?;
|
||||
let hash = T::Hashing::hash(&runtime_blob);
|
||||
|
||||
#[extrinsic_call]
|
||||
authorize_upgrade(RawOrigin::Root, hash);
|
||||
|
||||
assert!(System::<T>::authorized_upgrade().is_some());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn apply_authorized_upgrade() -> Result<(), BenchmarkError> {
|
||||
let runtime_blob = T::prepare_set_code_data();
|
||||
T::setup_set_code_requirements(&runtime_blob)?;
|
||||
let hash = T::Hashing::hash(&runtime_blob);
|
||||
// Will be heavier when it needs to do verification (i.e. don't use `...without_checks`).
|
||||
System::<T>::authorize_upgrade(RawOrigin::Root.into(), hash)?;
|
||||
|
||||
#[extrinsic_call]
|
||||
apply_authorized_upgrade(RawOrigin::Root, runtime_blob);
|
||||
|
||||
// Can't check for `CodeUpdated` in parachain upgrades. Just check that the authorization is
|
||||
// gone.
|
||||
assert!(System::<T>::authorized_upgrade().is_none());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
|
||||
}
|
||||
#[cfg(all(feature = "runtime-benchmarks", test))]
|
||||
pub(crate) mod mock;
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Supporting types for try-runtime, testing and dry-running commands.
|
||||
|
||||
pub use frame_support::traits::{TryStateSelect, UpgradeCheckSelect};
|
||||
use frame_support::weights::Weight;
|
||||
|
||||
sp_api::decl_runtime_apis! {
|
||||
/// Runtime api for testing the execution of a runtime upgrade.
|
||||
pub trait TryRuntime {
|
||||
/// dry-run runtime upgrades, returning the total weight consumed.
|
||||
///
|
||||
/// This should do EXACTLY the same operations as the runtime would have done in the case of
|
||||
/// a runtime upgrade (e.g. pallet ordering must be the same)
|
||||
///
|
||||
/// Returns the consumed weight of the migration in case of a successful one, combined with
|
||||
/// the total allowed block weight of the runtime.
|
||||
///
|
||||
/// If `checks` is `true`, `pre_migrate` and `post_migrate` of each migration and
|
||||
/// `try_state` of all pallets will be executed. Else, no. If checks are executed, the PoV
|
||||
/// tracking is likely inaccurate.
|
||||
fn on_runtime_upgrade(checks: UpgradeCheckSelect) -> (Weight, Weight);
|
||||
|
||||
/// Execute the given block, but optionally disable state-root and signature checks.
|
||||
///
|
||||
/// Optionally, a number of `try_state` hooks can also be executed after the block
|
||||
/// execution.
|
||||
fn execute_block(
|
||||
block: Block,
|
||||
state_root_check: bool,
|
||||
signature_check: bool,
|
||||
try_state: TryStateSelect,
|
||||
) -> Weight;
|
||||
}
|
||||
}
|
||||
@@ -18,36 +18,9 @@
|
||||
//! Supporting types for try-runtime, testing and dry-running commands.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![cfg(feature = "try-runtime")]
|
||||
|
||||
pub use frame_support::traits::{TryStateSelect, UpgradeCheckSelect};
|
||||
use frame_support::weights::Weight;
|
||||
#[cfg(feature = "try-runtime")]
|
||||
pub mod inner;
|
||||
|
||||
sp_api::decl_runtime_apis! {
|
||||
/// Runtime api for testing the execution of a runtime upgrade.
|
||||
pub trait TryRuntime {
|
||||
/// dry-run runtime upgrades, returning the total weight consumed.
|
||||
///
|
||||
/// This should do EXACTLY the same operations as the runtime would have done in the case of
|
||||
/// a runtime upgrade (e.g. pallet ordering must be the same)
|
||||
///
|
||||
/// Returns the consumed weight of the migration in case of a successful one, combined with
|
||||
/// the total allowed block weight of the runtime.
|
||||
///
|
||||
/// If `checks` is `true`, `pre_migrate` and `post_migrate` of each migration and
|
||||
/// `try_state` of all pallets will be executed. Else, no. If checks are executed, the PoV
|
||||
/// tracking is likely inaccurate.
|
||||
fn on_runtime_upgrade(checks: UpgradeCheckSelect) -> (Weight, Weight);
|
||||
|
||||
/// Execute the given block, but optionally disable state-root and signature checks.
|
||||
///
|
||||
/// Optionally, a number of `try_state` hooks can also be executed after the block
|
||||
/// execution.
|
||||
fn execute_block(
|
||||
block: Block,
|
||||
state_root_check: bool,
|
||||
signature_check: bool,
|
||||
try_state: TryStateSelect,
|
||||
) -> Weight;
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "try-runtime")]
|
||||
pub use inner::*;
|
||||
|
||||
@@ -26,7 +26,7 @@ sp-consensus-slots = { path = "../slots", default-features = false }
|
||||
sp-core = { path = "../../core", default-features = false }
|
||||
sp-inherents = { path = "../../inherents", default-features = false }
|
||||
sp-runtime = { path = "../../runtime", default-features = false }
|
||||
sp-timestamp = { path = "../../timestamp", optional = true }
|
||||
sp-timestamp = { path = "../../timestamp", optional = true, default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
|
||||
@@ -37,7 +37,7 @@ ss58-registry = { version = "1.34.0", default-features = false }
|
||||
sp-std = { path = "../std", default-features = false }
|
||||
sp-debug-derive = { path = "../debug-derive", default-features = false }
|
||||
sp-storage = { path = "../storage", default-features = false }
|
||||
sp-externalities = { path = "../externalities", optional = true }
|
||||
sp-externalities = { path = "../externalities", optional = true, default-features = false }
|
||||
futures = { version = "0.3.30", optional = true }
|
||||
dyn-clonable = { version = "0.9.0", optional = true }
|
||||
thiserror = { optional = true, workspace = true }
|
||||
|
||||
@@ -20,9 +20,9 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features =
|
||||
scale-info = { version = "2.11.1", default-features = false, features = ["derive"] }
|
||||
sp-api = { path = "../api", default-features = false }
|
||||
sp-core = { path = "../core", default-features = false }
|
||||
sp-runtime = { path = "../runtime", optional = true }
|
||||
sp-runtime = { path = "../runtime", optional = true, default-features = false }
|
||||
sp-staking = { path = "../staking", default-features = false }
|
||||
sp-keystore = { path = "../keystore", optional = true }
|
||||
sp-keystore = { path = "../keystore", optional = true, default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
|
||||
@@ -19,10 +19,10 @@ targets = ["x86_64-unknown-linux-gnu"]
|
||||
async-trait = { version = "0.1.79", optional = true }
|
||||
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] }
|
||||
scale-info = { version = "2.11.1", default-features = false, features = ["derive"] }
|
||||
sp-core = { path = "../core", optional = true }
|
||||
sp-core = { path = "../core", optional = true, default-features = false }
|
||||
sp-inherents = { path = "../inherents", default-features = false }
|
||||
sp-runtime = { path = "../runtime", default-features = false }
|
||||
sp-trie = { path = "../trie", optional = true }
|
||||
sp-trie = { path = "../trie", optional = true, default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
|
||||
Reference in New Issue
Block a user