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:
Oliver Tale-Yazdi
2024-04-17 18:52:00 +03:00
committed by GitHub
parent bfbf7f5d6f
commit 7a2c9d4a9a
36 changed files with 1800 additions and 1601 deletions
@@ -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",
]
+2 -1
View File
@@ -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",
]
+2 -1
View File
@@ -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;
+54
View File
@@ -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
+1 -1
View File
@@ -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]
+1
View File
@@ -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 }
+2 -1
View File
@@ -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",
]
+2 -2
View File
@@ -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;
+2 -3
View File
@@ -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",
]
+4 -1
View File
@@ -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())
}
+7 -145
View File
@@ -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;
+2 -2
View File
@@ -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);
}
+7 -213
View File
@@ -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;
+50
View File
@@ -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;
}
}
+4 -31
View File
@@ -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"]
+1 -1
View File
@@ -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 }
+2 -2
View File
@@ -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"]