mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 23:31:07 +00:00
Fast Unstake Pallet (#12129)
* add failing test for itamar * an ugly example of fast unstake * Revert "add failing test for itamar" This reverts commit 16c4d8015698a0684c090c54fce8b470a2d2feb2. * fast unstake wip * clean it up a bit * some comments * on_idle logic * fix * comment * new working version, checks all pass, looking good * some notes * add mock boilerplate * more boilerplate * simplify the weight stuff * ExtBuilder for pools * fmt * rm bags-list, simplify setup_works * mock + tests boilerplate * make some benchmarks work * mock boilerplate * tests boilerplate * run_to_block works * add Error enums * add test * note * make UnstakeRequest fields pub * some tests * fix origin * fmt * add fast_unstake_events_since_last_call * text * rewrite some benchmes and fix them -- the outcome is still strange * Fix weights * cleanup * Update frame/election-provider-support/solution-type/src/single_page.rs * fix build * Fix pools tests * iterate teset + mock * test unfinished * cleanup and add some tests * add test successful_multi_queue * comment * rm Head check * add TODO * complete successful_multi_queue * + test early_exit * fix a lot of things above the beautiful atlantic ocean 🌊 * seemingly it is finished now * Fix build * ".git/.scripts/fmt.sh" 1 * Fix slashing amount as well * better docs * abstract types * rm use * import * Update frame/nomination-pools/benchmarking/src/lib.rs Co-authored-by: Nitwit <47109040+nitwit69@users.noreply.github.com> * Update frame/fast-unstake/src/types.rs Co-authored-by: Nitwit <47109040+nitwit69@users.noreply.github.com> * Fix build * fmt * Update frame/fast-unstake/src/lib.rs Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * make bounded * feedback from code review with Ankan * Update frame/fast-unstake/src/lib.rs Co-authored-by: Roman Useinov <roman.useinov@gmail.com> * Update frame/fast-unstake/src/lib.rs Co-authored-by: Roman Useinov <roman.useinov@gmail.com> * Update frame/fast-unstake/src/lib.rs Co-authored-by: Roman Useinov <roman.useinov@gmail.com> * Update frame/fast-unstake/src/lib.rs Co-authored-by: Roman Useinov <roman.useinov@gmail.com> * Update frame/fast-unstake/src/lib.rs Co-authored-by: Roman Useinov <roman.useinov@gmail.com> * Update frame/fast-unstake/src/lib.rs Co-authored-by: Roman Useinov <roman.useinov@gmail.com> * Update frame/fast-unstake/src/lib.rs Co-authored-by: Roman Useinov <roman.useinov@gmail.com> * Update frame/fast-unstake/src/lib.rs Co-authored-by: Roman Useinov <roman.useinov@gmail.com> * Update frame/fast-unstake/src/lib.rs Co-authored-by: Roman Useinov <roman.useinov@gmail.com> * Update frame/fast-unstake/src/mock.rs * update to master * some final review comments * fmt * fix clippy * remove unused * ".git/.scripts/fmt.sh" 1 * make it all build again * fmt * undo fishy change Co-authored-by: Ross Bulat <ross@jkrbinvestments.com> Co-authored-by: command-bot <> Co-authored-by: Nitwit <47109040+nitwit69@users.noreply.github.com> Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> Co-authored-by: Roman Useinov <roman.useinov@gmail.com>
This commit is contained in:
Generated
+26
@@ -3376,6 +3376,7 @@ dependencies = [
|
||||
"pallet-election-provider-multi-phase",
|
||||
"pallet-election-provider-support-benchmarking",
|
||||
"pallet-elections-phragmen",
|
||||
"pallet-fast-unstake",
|
||||
"pallet-gilt",
|
||||
"pallet-grandpa",
|
||||
"pallet-identity",
|
||||
@@ -5718,6 +5719,31 @@ dependencies = [
|
||||
"sp-tasks",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-fast-unstake"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"frame-benchmarking",
|
||||
"frame-election-provider-support",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"log",
|
||||
"pallet-balances",
|
||||
"pallet-nomination-pools",
|
||||
"pallet-staking",
|
||||
"pallet-staking-reward-curve",
|
||||
"pallet-timestamp",
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"sp-core",
|
||||
"sp-io",
|
||||
"sp-runtime",
|
||||
"sp-staking",
|
||||
"sp-std",
|
||||
"sp-tracing",
|
||||
"substrate-test-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-gilt"
|
||||
version = "4.0.0-dev"
|
||||
|
||||
@@ -90,6 +90,7 @@ members = [
|
||||
"frame/contracts/rpc/runtime-api",
|
||||
"frame/conviction-voting",
|
||||
"frame/democracy",
|
||||
"frame/fast-unstake",
|
||||
"frame/try-runtime",
|
||||
"frame/election-provider-multi-phase",
|
||||
"frame/election-provider-support",
|
||||
|
||||
@@ -68,6 +68,7 @@ pallet-democracy = { version = "4.0.0-dev", default-features = false, path = "..
|
||||
pallet-election-provider-multi-phase = { version = "4.0.0-dev", default-features = false, path = "../../../frame/election-provider-multi-phase" }
|
||||
pallet-election-provider-support-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/election-provider-support/benchmarking", optional = true }
|
||||
pallet-elections-phragmen = { version = "5.0.0-dev", default-features = false, path = "../../../frame/elections-phragmen" }
|
||||
pallet-fast-unstake = { version = "4.0.0-dev", default-features = false, path = "../../../frame/fast-unstake" }
|
||||
pallet-gilt = { version = "4.0.0-dev", default-features = false, path = "../../../frame/gilt" }
|
||||
pallet-grandpa = { version = "4.0.0-dev", default-features = false, path = "../../../frame/grandpa" }
|
||||
pallet-im-online = { version = "4.0.0-dev", default-features = false, path = "../../../frame/im-online" }
|
||||
@@ -142,6 +143,7 @@ std = [
|
||||
"pallet-conviction-voting/std",
|
||||
"pallet-democracy/std",
|
||||
"pallet-elections-phragmen/std",
|
||||
"pallet-fast-unstake/std",
|
||||
"frame-executive/std",
|
||||
"pallet-gilt/std",
|
||||
"pallet-grandpa/std",
|
||||
@@ -220,6 +222,7 @@ runtime-benchmarks = [
|
||||
"pallet-election-provider-multi-phase/runtime-benchmarks",
|
||||
"pallet-election-provider-support-benchmarking/runtime-benchmarks",
|
||||
"pallet-elections-phragmen/runtime-benchmarks",
|
||||
"pallet-fast-unstake/runtime-benchmarks",
|
||||
"pallet-gilt/runtime-benchmarks",
|
||||
"pallet-grandpa/runtime-benchmarks",
|
||||
"pallet-identity/runtime-benchmarks",
|
||||
@@ -272,6 +275,7 @@ try-runtime = [
|
||||
"pallet-democracy/try-runtime",
|
||||
"pallet-election-provider-multi-phase/try-runtime",
|
||||
"pallet-elections-phragmen/try-runtime",
|
||||
"pallet-fast-unstake/try-runtime",
|
||||
"pallet-gilt/try-runtime",
|
||||
"pallet-grandpa/try-runtime",
|
||||
"pallet-im-online/try-runtime",
|
||||
|
||||
@@ -579,6 +579,13 @@ impl pallet_staking::Config for Runtime {
|
||||
type BenchmarkingConfig = StakingBenchmarkingConfig;
|
||||
}
|
||||
|
||||
impl pallet_fast_unstake::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type SlashPerEra = ConstU128<{ DOLLARS }>;
|
||||
type ControlOrigin = frame_system::EnsureRoot<AccountId>;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
// phase durations. 1/4 of the last session for each.
|
||||
pub const SignedPhase: u32 = EPOCH_DURATION_IN_BLOCKS / 4;
|
||||
@@ -1655,6 +1662,7 @@ construct_runtime!(
|
||||
NominationPools: pallet_nomination_pools,
|
||||
RankedPolls: pallet_referenda::<Instance2>,
|
||||
RankedCollective: pallet_ranked_collective,
|
||||
FastUnstake: pallet_fast_unstake,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1741,6 +1749,7 @@ mod benches {
|
||||
[pallet_election_provider_multi_phase, ElectionProviderMultiPhase]
|
||||
[pallet_election_provider_support_benchmarking, EPSBench::<Runtime>]
|
||||
[pallet_elections_phragmen, Elections]
|
||||
[pallet_fast_unstake, FastUnstake]
|
||||
[pallet_gilt, Gilt]
|
||||
[pallet_grandpa, Grandpa]
|
||||
[pallet_identity, Identity]
|
||||
|
||||
@@ -318,6 +318,10 @@ impl<T: Config> ElectionProvider for NoFallback<T> {
|
||||
type DataProvider = T::DataProvider;
|
||||
type Error = &'static str;
|
||||
|
||||
fn ongoing() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn elect() -> Result<Supports<T::AccountId>, Self::Error> {
|
||||
// Do nothing, this will enable the emergency phase.
|
||||
Err("NoFallback.")
|
||||
@@ -1598,6 +1602,13 @@ impl<T: Config> ElectionProvider for Pallet<T> {
|
||||
type Error = ElectionError<T>;
|
||||
type DataProvider = T::DataProvider;
|
||||
|
||||
fn ongoing() -> bool {
|
||||
match Self::current_phase() {
|
||||
Phase::Off => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn elect() -> Result<Supports<T::AccountId>, Self::Error> {
|
||||
match Self::do_elect() {
|
||||
Ok(supports) => {
|
||||
|
||||
@@ -303,6 +303,10 @@ impl ElectionProvider for MockFallback {
|
||||
type Error = &'static str;
|
||||
type DataProvider = StakingMock;
|
||||
|
||||
fn ongoing() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn elect() -> Result<Supports<AccountId>, Self::Error> {
|
||||
Self::elect_with_bounds(Bounded::max_value(), Bounded::max_value())
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
//! type BlockNumber = BlockNumber;
|
||||
//! type Error = &'static str;
|
||||
//! type DataProvider = T::DataProvider;
|
||||
//!
|
||||
//! fn ongoing() -> bool { false }
|
||||
//! fn elect() -> Result<Supports<AccountId>, Self::Error> {
|
||||
//! Self::DataProvider::electable_targets(None)
|
||||
//! .map_err(|_| "failed to elect")
|
||||
@@ -370,6 +370,9 @@ pub trait ElectionProvider {
|
||||
BlockNumber = Self::BlockNumber,
|
||||
>;
|
||||
|
||||
/// Indicate if this election provider is currently ongoing an asynchronous election or not.
|
||||
fn ongoing() -> bool;
|
||||
|
||||
/// Elect a new set of winners, without specifying any bounds on the amount of data fetched from
|
||||
/// [`Self::DataProvider`]. An implementation could nonetheless impose its own custom limits.
|
||||
///
|
||||
@@ -420,6 +423,10 @@ where
|
||||
fn elect() -> Result<Supports<AccountId>, Self::Error> {
|
||||
Err("<NoElection as ElectionProvider> cannot do anything.")
|
||||
}
|
||||
|
||||
fn ongoing() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// A utility trait for something to implement `ElectionDataProvider` in a sensible way.
|
||||
|
||||
@@ -138,6 +138,10 @@ impl<T: Config> ElectionProvider for UnboundedExecution<T> {
|
||||
type Error = Error;
|
||||
type DataProvider = T::DataProvider;
|
||||
|
||||
fn ongoing() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn elect() -> Result<Supports<Self::AccountId>, Self::Error> {
|
||||
// This should not be called if not in `std` mode (and therefore neither in genesis nor in
|
||||
// testing)
|
||||
@@ -167,6 +171,10 @@ impl<T: BoundedConfig> ElectionProvider for BoundedExecution<T> {
|
||||
type Error = Error;
|
||||
type DataProvider = T::DataProvider;
|
||||
|
||||
fn ongoing() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn elect() -> Result<Supports<Self::AccountId>, Self::Error> {
|
||||
elect_with::<T>(Some(T::VotersBound::get() as usize), Some(T::TargetsBound::get() as usize))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
[package]
|
||||
name = "pallet-fast-unstake"
|
||||
version = "4.0.0-dev"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2021"
|
||||
license = "Unlicense"
|
||||
homepage = "https://substrate.io"
|
||||
repository = "https://github.com/paritytech/substrate/"
|
||||
description = "FRAME fast unstake pallet"
|
||||
readme = "README.md"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false }
|
||||
log = { version = "0.4.17", default-features = false }
|
||||
scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
|
||||
|
||||
frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" }
|
||||
frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" }
|
||||
|
||||
sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" }
|
||||
sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" }
|
||||
sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" }
|
||||
sp-staking = { default-features = false, path = "../../primitives/staking" }
|
||||
|
||||
pallet-balances = { default-features = false, path = "../balances" }
|
||||
pallet-timestamp = { default-features = false, path = "../timestamp" }
|
||||
pallet-staking = { default-features = false, path = "../staking" }
|
||||
pallet-nomination-pools = { default-features = false, path = "../nomination-pools" }
|
||||
frame-election-provider-support = { default-features = false, path = "../election-provider-support" }
|
||||
|
||||
frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" }
|
||||
|
||||
[dev-dependencies]
|
||||
pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" }
|
||||
sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" }
|
||||
substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" }
|
||||
sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"log/std",
|
||||
"scale-info/std",
|
||||
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
|
||||
"sp-io/std",
|
||||
"sp-staking/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
|
||||
"pallet-staking/std",
|
||||
"pallet-nomination-pools/std",
|
||||
"pallet-balances/std",
|
||||
"pallet-timestamp/std",
|
||||
"frame-election-provider-support/std",
|
||||
|
||||
"frame-benchmarking/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame-benchmarking/runtime-benchmarks",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"pallet-staking/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = ["frame-support/try-runtime"]
|
||||
@@ -0,0 +1,228 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2022 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 for pallet-fast-unstake.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use crate::{types::*, Pallet as FastUnstake, *};
|
||||
use frame_benchmarking::{benchmarks, whitelist_account};
|
||||
use frame_support::{
|
||||
assert_ok,
|
||||
traits::{Currency, EnsureOrigin, Get, Hooks},
|
||||
};
|
||||
use frame_system::RawOrigin;
|
||||
use pallet_nomination_pools::{Pallet as Pools, PoolId};
|
||||
use pallet_staking::Pallet as Staking;
|
||||
use sp_runtime::traits::{StaticLookup, Zero};
|
||||
use sp_staking::EraIndex;
|
||||
use sp_std::prelude::*;
|
||||
|
||||
const USER_SEED: u32 = 0;
|
||||
const DEFAULT_BACKER_PER_VALIDATOR: u32 = 128;
|
||||
const MAX_VALIDATORS: u32 = 128;
|
||||
|
||||
type CurrencyOf<T> = <T as pallet_staking::Config>::Currency;
|
||||
|
||||
fn l<T: Config>(
|
||||
who: T::AccountId,
|
||||
) -> <<T as frame_system::Config>::Lookup as StaticLookup>::Source {
|
||||
T::Lookup::unlookup(who)
|
||||
}
|
||||
|
||||
fn create_unexposed_nominator<T: Config>() -> T::AccountId {
|
||||
let account = frame_benchmarking::account::<T::AccountId>("nominator_42", 0, USER_SEED);
|
||||
fund_and_bond_account::<T>(&account);
|
||||
account
|
||||
}
|
||||
|
||||
fn fund_and_bond_account<T: Config>(account: &T::AccountId) {
|
||||
let stake = CurrencyOf::<T>::minimum_balance() * 100u32.into();
|
||||
CurrencyOf::<T>::make_free_balance_be(&account, stake * 10u32.into());
|
||||
|
||||
let account_lookup = l::<T>(account.clone());
|
||||
// bond and nominate ourselves, this will guarantee that we are not backing anyone.
|
||||
assert_ok!(Staking::<T>::bond(
|
||||
RawOrigin::Signed(account.clone()).into(),
|
||||
account_lookup.clone(),
|
||||
stake,
|
||||
pallet_staking::RewardDestination::Controller,
|
||||
));
|
||||
assert_ok!(Staking::<T>::nominate(
|
||||
RawOrigin::Signed(account.clone()).into(),
|
||||
vec![account_lookup]
|
||||
));
|
||||
}
|
||||
|
||||
pub(crate) fn fast_unstake_events<T: Config>() -> Vec<crate::Event<T>> {
|
||||
frame_system::Pallet::<T>::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| <T as Config>::RuntimeEvent::from(e).try_into().ok())
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn setup_pool<T: Config>() -> PoolId {
|
||||
let depositor = frame_benchmarking::account::<T::AccountId>("depositor_42", 0, USER_SEED);
|
||||
let depositor_lookup = l::<T>(depositor.clone());
|
||||
|
||||
let stake = Pools::<T>::depositor_min_bond();
|
||||
CurrencyOf::<T>::make_free_balance_be(&depositor, stake * 10u32.into());
|
||||
|
||||
Pools::<T>::create(
|
||||
RawOrigin::Signed(depositor.clone()).into(),
|
||||
stake,
|
||||
depositor_lookup.clone(),
|
||||
depositor_lookup.clone(),
|
||||
depositor_lookup,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
pallet_nomination_pools::LastPoolId::<T>::get()
|
||||
}
|
||||
|
||||
fn setup_staking<T: Config>(v: u32, until: EraIndex) {
|
||||
let ed = CurrencyOf::<T>::minimum_balance();
|
||||
|
||||
log!(debug, "registering {} validators and {} eras.", v, until);
|
||||
|
||||
// our validators don't actually need to registered in staking -- just generate `v` random
|
||||
// accounts.
|
||||
let validators = (0..v)
|
||||
.map(|x| frame_benchmarking::account::<T::AccountId>("validator", x, USER_SEED))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for era in 0..=until {
|
||||
let others = (0..DEFAULT_BACKER_PER_VALIDATOR)
|
||||
.map(|s| {
|
||||
let who = frame_benchmarking::account::<T::AccountId>("nominator", era, s);
|
||||
let value = ed;
|
||||
pallet_staking::IndividualExposure { who, value }
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let exposure =
|
||||
pallet_staking::Exposure { total: Default::default(), own: Default::default(), others };
|
||||
validators.iter().for_each(|v| {
|
||||
Staking::<T>::add_era_stakers(era, v.clone(), exposure.clone());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn on_idle_full_block<T: Config>() {
|
||||
let remaining_weight = <T as frame_system::Config>::BlockWeights::get().max_block;
|
||||
FastUnstake::<T>::on_idle(Zero::zero(), remaining_weight);
|
||||
}
|
||||
|
||||
benchmarks! {
|
||||
// on_idle, we we don't check anyone, but fully unbond and move them to another pool.
|
||||
on_idle_unstake {
|
||||
let who = create_unexposed_nominator::<T>();
|
||||
let pool_id = setup_pool::<T>();
|
||||
assert_ok!(FastUnstake::<T>::register_fast_unstake(
|
||||
RawOrigin::Signed(who.clone()).into(),
|
||||
Some(pool_id)
|
||||
));
|
||||
ErasToCheckPerBlock::<T>::put(1);
|
||||
|
||||
// run on_idle once. This will check era 0.
|
||||
assert_eq!(Head::<T>::get(), None);
|
||||
on_idle_full_block::<T>();
|
||||
assert_eq!(
|
||||
Head::<T>::get(),
|
||||
Some(UnstakeRequest { stash: who.clone(), checked: vec![0].try_into().unwrap(), maybe_pool_id: Some(pool_id) })
|
||||
);
|
||||
}
|
||||
: {
|
||||
on_idle_full_block::<T>();
|
||||
}
|
||||
verify {
|
||||
assert!(matches!(
|
||||
fast_unstake_events::<T>().last(),
|
||||
Some(Event::Unstaked { .. })
|
||||
));
|
||||
}
|
||||
|
||||
// on_idle, when we check some number of eras,
|
||||
on_idle_check {
|
||||
// number of eras multiplied by validators in that era.
|
||||
let x in (<T as pallet_staking::Config>::BondingDuration::get() * 1) .. (<T as pallet_staking::Config>::BondingDuration::get() * MAX_VALIDATORS);
|
||||
|
||||
let v = x / <T as pallet_staking::Config>::BondingDuration::get();
|
||||
let u = <T as pallet_staking::Config>::BondingDuration::get();
|
||||
|
||||
ErasToCheckPerBlock::<T>::put(u);
|
||||
pallet_staking::CurrentEra::<T>::put(u);
|
||||
|
||||
// setup staking with v validators and u eras of data (0..=u)
|
||||
setup_staking::<T>(v, u);
|
||||
let who = create_unexposed_nominator::<T>();
|
||||
assert_ok!(FastUnstake::<T>::register_fast_unstake(
|
||||
RawOrigin::Signed(who.clone()).into(),
|
||||
None,
|
||||
));
|
||||
|
||||
// no one is queued thus far.
|
||||
assert_eq!(Head::<T>::get(), None);
|
||||
}
|
||||
: {
|
||||
on_idle_full_block::<T>();
|
||||
}
|
||||
verify {
|
||||
let checked: frame_support::BoundedVec<_, _> = (1..=u).rev().collect::<Vec<EraIndex>>().try_into().unwrap();
|
||||
assert_eq!(
|
||||
Head::<T>::get(),
|
||||
Some(UnstakeRequest { stash: who.clone(), checked, maybe_pool_id: None })
|
||||
);
|
||||
assert!(matches!(
|
||||
fast_unstake_events::<T>().last(),
|
||||
Some(Event::Checking { .. })
|
||||
));
|
||||
}
|
||||
|
||||
register_fast_unstake {
|
||||
let who = create_unexposed_nominator::<T>();
|
||||
whitelist_account!(who);
|
||||
assert_eq!(Queue::<T>::count(), 0);
|
||||
|
||||
}
|
||||
:_(RawOrigin::Signed(who.clone()), None)
|
||||
verify {
|
||||
assert_eq!(Queue::<T>::count(), 1);
|
||||
}
|
||||
|
||||
deregister {
|
||||
let who = create_unexposed_nominator::<T>();
|
||||
assert_ok!(FastUnstake::<T>::register_fast_unstake(
|
||||
RawOrigin::Signed(who.clone()).into(),
|
||||
None
|
||||
));
|
||||
assert_eq!(Queue::<T>::count(), 1);
|
||||
whitelist_account!(who);
|
||||
}
|
||||
:_(RawOrigin::Signed(who.clone()))
|
||||
verify {
|
||||
assert_eq!(Queue::<T>::count(), 0);
|
||||
}
|
||||
|
||||
control {
|
||||
let origin = <T as Config>::ControlOrigin::successful_origin();
|
||||
}
|
||||
: _<T::RuntimeOrigin>(origin, 128)
|
||||
verify {}
|
||||
|
||||
impl_benchmark_test_suite!(Pallet, crate::mock::ExtBuilder::default().build(), crate::mock::Runtime)
|
||||
}
|
||||
@@ -0,0 +1,505 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2022 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.
|
||||
|
||||
//! A pallet that's designed to JUST do the following:
|
||||
//!
|
||||
//! If a nominator is not exposed in any `ErasStakers` (i.e. "has not actively backed any
|
||||
//! validators in the last `BondingDuration` days"), then they can register themselves in this
|
||||
//! pallet, unstake faster than having to wait an entire bonding duration, and potentially move
|
||||
//! into a nomination pool.
|
||||
//!
|
||||
//! Appearing in the exposure of a validator means being exposed equal to that validator from the
|
||||
//! point of view of the staking system. This usually means earning rewards with the validator, and
|
||||
//! also being at the risk of slashing with the validator. This is equivalent to the "Active
|
||||
//! Nominator" role explained in the
|
||||
//! [February Staking Update](https://polkadot.network/blog/staking-update-february-2022/).
|
||||
//!
|
||||
//! This pallet works off the basis of `on_idle`, meaning that it provides no guarantee about when
|
||||
//! it will succeed, if at all. Moreover, the queue implementation is unordered. In case of
|
||||
//! congestion, no FIFO ordering is provided.
|
||||
//!
|
||||
//! Stakers who are certain about NOT being exposed can register themselves with
|
||||
//! [`Call::register_fast_unstake`]. This will chill, and fully unbond the staker, and place them in
|
||||
//! the queue to be checked.
|
||||
//!
|
||||
//! Once queued, but not being actively processed, stakers can withdraw their request via
|
||||
//! [`Call::deregister`].
|
||||
//!
|
||||
//! Once queued, a staker wishing to unbond can perform no further action in pallet-staking. This is
|
||||
//! to prevent them from accidentally exposing themselves behind a validator etc.
|
||||
//!
|
||||
//! Once processed, if successful, no additional fee for the checking process is taken, and the
|
||||
//! staker is instantly unbonded. Optionally, if they have asked to join a pool, their *entire*
|
||||
//! stake is joined into their pool of choice.
|
||||
//!
|
||||
//! If unsuccessful, meaning that the staker was exposed sometime in the last `BondingDuration` eras
|
||||
//! they will end up being slashed for the amount of wasted work they have inflicted on the chian.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
// NOTE: enable benchmarking in tests as well.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
mod benchmarking;
|
||||
mod types;
|
||||
pub mod weights;
|
||||
|
||||
pub const LOG_TARGET: &'static str = "runtime::fast-unstake";
|
||||
|
||||
// syntactic sugar for logging.
|
||||
#[macro_export]
|
||||
macro_rules! log {
|
||||
($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
|
||||
log::$level!(
|
||||
target: crate::LOG_TARGET,
|
||||
concat!("[{:?}] 💨 ", $patter), <frame_system::Pallet<T>>::block_number() $(, $values)*
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use crate::types::*;
|
||||
use frame_election_provider_support::ElectionProvider;
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::{pallet_prelude::*, RawOrigin};
|
||||
use pallet_nomination_pools::PoolId;
|
||||
use pallet_staking::Pallet as Staking;
|
||||
use sp_runtime::{
|
||||
traits::{Saturating, Zero},
|
||||
DispatchResult,
|
||||
};
|
||||
use sp_staking::EraIndex;
|
||||
use sp_std::{prelude::*, vec::Vec};
|
||||
use weights::WeightInfo;
|
||||
|
||||
#[derive(scale_info::TypeInfo, codec::Encode, codec::Decode, codec::MaxEncodedLen)]
|
||||
#[codec(mel_bound(T: Config))]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct MaxChecking<T: Config>(sp_std::marker::PhantomData<T>);
|
||||
impl<T: Config> frame_support::traits::Get<u32> for MaxChecking<T> {
|
||||
fn get() -> u32 {
|
||||
<T as pallet_staking::Config>::BondingDuration::get() + 1
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config:
|
||||
frame_system::Config
|
||||
+ pallet_staking::Config<
|
||||
CurrencyBalance = <Self as pallet_nomination_pools::Config>::CurrencyBalance,
|
||||
> + pallet_nomination_pools::Config
|
||||
{
|
||||
/// The overarching event type.
|
||||
type RuntimeEvent: From<Event<Self>>
|
||||
+ IsType<<Self as frame_system::Config>::RuntimeEvent>
|
||||
+ TryInto<Event<Self>>;
|
||||
|
||||
/// The amount of balance slashed per each era that was wastefully checked.
|
||||
///
|
||||
/// A reasonable value could be `runtime_weight_to_fee(weight_per_era_check)`.
|
||||
type SlashPerEra: Get<BalanceOf<Self>>;
|
||||
|
||||
/// The origin that can control this pallet.
|
||||
type ControlOrigin: frame_support::traits::EnsureOrigin<Self::RuntimeOrigin>;
|
||||
|
||||
/// The weight information of this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
}
|
||||
|
||||
/// The current "head of the queue" being unstaked.
|
||||
#[pallet::storage]
|
||||
pub type Head<T: Config> =
|
||||
StorageValue<_, UnstakeRequest<T::AccountId, MaxChecking<T>>, OptionQuery>;
|
||||
|
||||
/// The map of all accounts wishing to be unstaked.
|
||||
///
|
||||
/// Points the `AccountId` wishing to unstake to the optional `PoolId` they wish to join
|
||||
/// thereafter.
|
||||
#[pallet::storage]
|
||||
pub type Queue<T: Config> = CountedStorageMap<_, Twox64Concat, T::AccountId, Option<PoolId>>;
|
||||
|
||||
/// Number of eras to check per block.
|
||||
///
|
||||
/// If set to 0, this pallet does absolutely nothing.
|
||||
///
|
||||
/// Based on the amount of weight available at `on_idle`, up to this many eras of a single
|
||||
/// nominator might be checked.
|
||||
#[pallet::storage]
|
||||
pub type ErasToCheckPerBlock<T: Config> = StorageValue<_, u32, ValueQuery>;
|
||||
|
||||
/// The events of this pallet.
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
/// A staker was unstaked.
|
||||
Unstaked { stash: T::AccountId, maybe_pool_id: Option<PoolId>, result: DispatchResult },
|
||||
/// A staker was slashed for requesting fast-unstake whilst being exposed.
|
||||
Slashed { stash: T::AccountId, amount: BalanceOf<T> },
|
||||
/// A staker was partially checked for the given eras, but the process did not finish.
|
||||
Checking { stash: T::AccountId, eras: Vec<EraIndex> },
|
||||
/// Some internal error happened while migrating stash. They are removed as head as a
|
||||
/// consequence.
|
||||
Errored { stash: T::AccountId },
|
||||
/// An internal error happened. Operations will be paused now.
|
||||
InternalError,
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub enum Error<T> {
|
||||
/// The provided Controller account was not found.
|
||||
///
|
||||
/// This means that the given account is not bonded.
|
||||
NotController,
|
||||
/// The bonded account has already been queued.
|
||||
AlreadyQueued,
|
||||
/// The bonded account has active unlocking chunks.
|
||||
NotFullyBonded,
|
||||
/// The provided un-staker is not in the `Queue`.
|
||||
NotQueued,
|
||||
/// The provided un-staker is already in Head, and cannot deregister.
|
||||
AlreadyHead,
|
||||
}
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<T::BlockNumber> for Pallet<T> {
|
||||
fn on_idle(_: T::BlockNumber, remaining_weight: Weight) -> Weight {
|
||||
if remaining_weight.any_lt(T::DbWeight::get().reads(2)) {
|
||||
return Weight::from_ref_time(0)
|
||||
}
|
||||
|
||||
Self::do_on_idle(remaining_weight)
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Register oneself for fast-unstake.
|
||||
///
|
||||
/// The dispatch origin of this call must be signed by the controller account, similar to
|
||||
/// `staking::unbond`.
|
||||
///
|
||||
/// The stash associated with the origin must have no ongoing unlocking chunks. If
|
||||
/// successful, this will fully unbond and chill the stash. Then, it will enqueue the stash
|
||||
/// to be checked in further blocks.
|
||||
///
|
||||
/// If by the time this is called, the stash is actually eligible for fast-unstake, then
|
||||
/// they are guaranteed to remain eligible, because the call will chill them as well.
|
||||
///
|
||||
/// If the check works, the entire staking data is removed, i.e. the stash is fully
|
||||
/// unstaked, and they potentially join a pool with their entire bonded stake.
|
||||
///
|
||||
/// If the check fails, the stash remains chilled and waiting for being unbonded as in with
|
||||
/// the normal staking system, but they lose part of their unbonding chunks due to consuming
|
||||
/// the chain's resources.
|
||||
#[pallet::weight(<T as Config>::WeightInfo::register_fast_unstake())]
|
||||
pub fn register_fast_unstake(
|
||||
origin: OriginFor<T>,
|
||||
maybe_pool_id: Option<PoolId>,
|
||||
) -> DispatchResult {
|
||||
let ctrl = ensure_signed(origin)?;
|
||||
|
||||
let ledger =
|
||||
pallet_staking::Ledger::<T>::get(&ctrl).ok_or(Error::<T>::NotController)?;
|
||||
ensure!(!Queue::<T>::contains_key(&ledger.stash), Error::<T>::AlreadyQueued);
|
||||
ensure!(
|
||||
Head::<T>::get().map_or(true, |UnstakeRequest { stash, .. }| stash != ledger.stash),
|
||||
Error::<T>::AlreadyHead
|
||||
);
|
||||
// second part of the && is defensive.
|
||||
ensure!(
|
||||
ledger.active == ledger.total && ledger.unlocking.is_empty(),
|
||||
Error::<T>::NotFullyBonded
|
||||
);
|
||||
|
||||
// chill and fully unstake.
|
||||
Staking::<T>::chill(RawOrigin::Signed(ctrl.clone()).into())?;
|
||||
Staking::<T>::unbond(RawOrigin::Signed(ctrl).into(), ledger.total)?;
|
||||
|
||||
// enqueue them.
|
||||
Queue::<T>::insert(ledger.stash, maybe_pool_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deregister oneself from the fast-unstake (also cancels joining the pool if that was
|
||||
/// supplied on `register_fast_unstake` .
|
||||
///
|
||||
/// This is useful if one is registered, they are still waiting, and they change their mind.
|
||||
///
|
||||
/// Note that the associated stash is still fully unbonded and chilled as a consequence of
|
||||
/// calling `register_fast_unstake`. This should probably be followed by a call to
|
||||
/// `Staking::rebond`.
|
||||
#[pallet::weight(<T as Config>::WeightInfo::deregister())]
|
||||
pub fn deregister(origin: OriginFor<T>) -> DispatchResult {
|
||||
let ctrl = ensure_signed(origin)?;
|
||||
let stash = pallet_staking::Ledger::<T>::get(&ctrl)
|
||||
.map(|l| l.stash)
|
||||
.ok_or(Error::<T>::NotController)?;
|
||||
ensure!(Queue::<T>::contains_key(&stash), Error::<T>::NotQueued);
|
||||
ensure!(
|
||||
Head::<T>::get().map_or(true, |UnstakeRequest { stash, .. }| stash != stash),
|
||||
Error::<T>::AlreadyHead
|
||||
);
|
||||
Queue::<T>::remove(stash);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Control the operation of this pallet.
|
||||
///
|
||||
/// Dispatch origin must be signed by the [`Config::ControlOrigin`].
|
||||
#[pallet::weight(<T as Config>::WeightInfo::control())]
|
||||
pub fn control(origin: OriginFor<T>, unchecked_eras_to_check: EraIndex) -> DispatchResult {
|
||||
let _ = T::ControlOrigin::ensure_origin(origin)?;
|
||||
ErasToCheckPerBlock::<T>::put(unchecked_eras_to_check);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// process up to `remaining_weight`.
|
||||
///
|
||||
/// Returns the actual weight consumed.
|
||||
///
|
||||
/// Written for readability in mind, not efficiency. For example:
|
||||
///
|
||||
/// 1. We assume this is only ever called once per `on_idle`. This is because we know that
|
||||
/// in all use cases, even a single nominator cannot be unbonded in a single call. Multiple
|
||||
/// calls to this function are thus not needed.
|
||||
///
|
||||
/// 2. We will only mark a staker as unstaked if at the beginning of a check cycle, they are
|
||||
/// found out to have no eras to check. At the end of a check cycle, even if they are fully
|
||||
/// checked, we don't finish the process.
|
||||
pub(crate) fn do_on_idle(remaining_weight: Weight) -> Weight {
|
||||
let mut eras_to_check_per_block = ErasToCheckPerBlock::<T>::get();
|
||||
if eras_to_check_per_block.is_zero() {
|
||||
return T::DbWeight::get().reads(1)
|
||||
}
|
||||
|
||||
// NOTE: here we're assuming that the number of validators has only ever increased,
|
||||
// meaning that the number of exposures to check is either this per era, or less.
|
||||
let validator_count = pallet_staking::ValidatorCount::<T>::get();
|
||||
|
||||
// determine the number of eras to check. This is based on both `ErasToCheckPerBlock`
|
||||
// and `remaining_weight` passed on to us from the runtime executive.
|
||||
let max_weight = |v, u| {
|
||||
<T as Config>::WeightInfo::on_idle_check(v * u)
|
||||
.max(<T as Config>::WeightInfo::on_idle_unstake())
|
||||
};
|
||||
while max_weight(validator_count, eras_to_check_per_block).any_gt(remaining_weight) {
|
||||
eras_to_check_per_block.saturating_dec();
|
||||
if eras_to_check_per_block.is_zero() {
|
||||
log!(debug, "early existing because eras_to_check_per_block is zero");
|
||||
return T::DbWeight::get().reads(2)
|
||||
}
|
||||
}
|
||||
|
||||
if <T as pallet_staking::Config>::ElectionProvider::ongoing() {
|
||||
// NOTE: we assume `ongoing` does not consume any weight.
|
||||
// there is an ongoing election -- we better not do anything. Imagine someone is not
|
||||
// exposed anywhere in the last era, and the snapshot for the election is already
|
||||
// taken. In this time period, we don't want to accidentally unstake them.
|
||||
return T::DbWeight::get().reads(2)
|
||||
}
|
||||
|
||||
let UnstakeRequest { stash, mut checked, maybe_pool_id } = match Head::<T>::take()
|
||||
.or_else(|| {
|
||||
// NOTE: there is no order guarantees in `Queue`.
|
||||
Queue::<T>::drain()
|
||||
.map(|(stash, maybe_pool_id)| UnstakeRequest {
|
||||
stash,
|
||||
maybe_pool_id,
|
||||
checked: Default::default(),
|
||||
})
|
||||
.next()
|
||||
}) {
|
||||
None => {
|
||||
// There's no `Head` and nothing in the `Queue`, nothing to do here.
|
||||
return T::DbWeight::get().reads(4)
|
||||
},
|
||||
Some(head) => head,
|
||||
};
|
||||
|
||||
log!(
|
||||
debug,
|
||||
"checking {:?}, eras_to_check_per_block = {:?}, remaining_weight = {:?}",
|
||||
stash,
|
||||
eras_to_check_per_block,
|
||||
remaining_weight
|
||||
);
|
||||
|
||||
// the range that we're allowed to check in this round.
|
||||
let current_era = pallet_staking::CurrentEra::<T>::get().unwrap_or_default();
|
||||
let bonding_duration = <T as pallet_staking::Config>::BondingDuration::get();
|
||||
// prune all the old eras that we don't care about. This will help us keep the bound
|
||||
// of `checked`.
|
||||
checked.retain(|e| *e >= current_era.saturating_sub(bonding_duration));
|
||||
let unchecked_eras_to_check = {
|
||||
// get the last available `bonding_duration` eras up to current era in reverse
|
||||
// order.
|
||||
let total_check_range = (current_era.saturating_sub(bonding_duration)..=
|
||||
current_era)
|
||||
.rev()
|
||||
.collect::<Vec<_>>();
|
||||
debug_assert!(
|
||||
total_check_range.len() <= (bonding_duration + 1) as usize,
|
||||
"{:?}",
|
||||
total_check_range
|
||||
);
|
||||
|
||||
// remove eras that have already been checked, take a maximum of
|
||||
// eras_to_check_per_block.
|
||||
total_check_range
|
||||
.into_iter()
|
||||
.filter(|e| !checked.contains(e))
|
||||
.take(eras_to_check_per_block as usize)
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
log!(
|
||||
debug,
|
||||
"{} eras to check: {:?}",
|
||||
unchecked_eras_to_check.len(),
|
||||
unchecked_eras_to_check
|
||||
);
|
||||
|
||||
if unchecked_eras_to_check.is_empty() {
|
||||
// `stash` is not exposed in any era now -- we can let go of them now.
|
||||
let num_slashing_spans = Staking::<T>::slashing_spans(&stash).iter().count() as u32;
|
||||
|
||||
let ctrl = match pallet_staking::Bonded::<T>::get(&stash) {
|
||||
Some(ctrl) => ctrl,
|
||||
None => {
|
||||
Self::deposit_event(Event::<T>::Errored { stash });
|
||||
return <T as Config>::WeightInfo::on_idle_unstake()
|
||||
},
|
||||
};
|
||||
|
||||
let ledger = match pallet_staking::Ledger::<T>::get(ctrl) {
|
||||
Some(ledger) => ledger,
|
||||
None => {
|
||||
Self::deposit_event(Event::<T>::Errored { stash });
|
||||
return <T as Config>::WeightInfo::on_idle_unstake()
|
||||
},
|
||||
};
|
||||
|
||||
let unstake_result = pallet_staking::Pallet::<T>::force_unstake(
|
||||
RawOrigin::Root.into(),
|
||||
stash.clone(),
|
||||
num_slashing_spans,
|
||||
);
|
||||
|
||||
let pool_stake_result = if let Some(pool_id) = maybe_pool_id {
|
||||
pallet_nomination_pools::Pallet::<T>::join(
|
||||
RawOrigin::Signed(stash.clone()).into(),
|
||||
ledger.total,
|
||||
pool_id,
|
||||
)
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let result = unstake_result.and(pool_stake_result);
|
||||
log!(
|
||||
info,
|
||||
"unstaked {:?}, maybe_pool {:?}, outcome: {:?}",
|
||||
stash,
|
||||
maybe_pool_id,
|
||||
result
|
||||
);
|
||||
|
||||
Self::deposit_event(Event::<T>::Unstaked { stash, maybe_pool_id, result });
|
||||
<T as Config>::WeightInfo::on_idle_unstake()
|
||||
} else {
|
||||
// eras remaining to be checked.
|
||||
let mut eras_checked = 0u32;
|
||||
let is_exposed = unchecked_eras_to_check.iter().any(|e| {
|
||||
eras_checked.saturating_inc();
|
||||
Self::is_exposed_in_era(&stash, e)
|
||||
});
|
||||
|
||||
log!(
|
||||
debug,
|
||||
"checked {:?} eras, exposed? {}, (v: {:?}, u: {:?})",
|
||||
eras_checked,
|
||||
is_exposed,
|
||||
validator_count,
|
||||
unchecked_eras_to_check.len()
|
||||
);
|
||||
|
||||
// NOTE: you can be extremely unlucky and get slashed here: You are not exposed in
|
||||
// the last 28 eras, have registered yourself to be unstaked, midway being checked,
|
||||
// you are exposed.
|
||||
if is_exposed {
|
||||
let amount = T::SlashPerEra::get()
|
||||
.saturating_mul(eras_checked.saturating_add(checked.len() as u32).into());
|
||||
pallet_staking::slashing::do_slash::<T>(
|
||||
&stash,
|
||||
amount,
|
||||
&mut Default::default(),
|
||||
&mut Default::default(),
|
||||
current_era,
|
||||
);
|
||||
log!(info, "slashed {:?} by {:?}", stash, amount);
|
||||
Self::deposit_event(Event::<T>::Slashed { stash, amount });
|
||||
} else {
|
||||
// Not exposed in these eras.
|
||||
match checked.try_extend(unchecked_eras_to_check.clone().into_iter()) {
|
||||
Ok(_) => {
|
||||
Head::<T>::put(UnstakeRequest {
|
||||
stash: stash.clone(),
|
||||
checked,
|
||||
maybe_pool_id,
|
||||
});
|
||||
Self::deposit_event(Event::<T>::Checking {
|
||||
stash,
|
||||
eras: unchecked_eras_to_check,
|
||||
});
|
||||
},
|
||||
Err(_) => {
|
||||
// don't put the head back in -- there is an internal error in the
|
||||
// pallet.
|
||||
frame_support::defensive!("`checked is pruned via retain above`");
|
||||
ErasToCheckPerBlock::<T>::put(0);
|
||||
Self::deposit_event(Event::<T>::InternalError);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
<T as Config>::WeightInfo::on_idle_check(validator_count * eras_checked)
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks whether an account `staker` has been exposed in an era.
|
||||
fn is_exposed_in_era(staker: &T::AccountId, era: &EraIndex) -> bool {
|
||||
pallet_staking::ErasStakers::<T>::iter_prefix(era).any(|(validator, exposures)| {
|
||||
validator == *staker || exposures.others.iter().any(|i| i.who == *staker)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,387 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2022 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.
|
||||
|
||||
use crate::{self as fast_unstake};
|
||||
use frame_support::{
|
||||
assert_ok,
|
||||
pallet_prelude::*,
|
||||
parameter_types,
|
||||
traits::{ConstU64, ConstU8, Currency},
|
||||
weights::constants::WEIGHT_PER_SECOND,
|
||||
PalletId,
|
||||
};
|
||||
use sp_runtime::{
|
||||
traits::{Convert, IdentityLookup},
|
||||
FixedU128,
|
||||
};
|
||||
|
||||
use frame_system::RawOrigin;
|
||||
use pallet_staking::{Exposure, IndividualExposure, StakerStatus};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
pub type AccountId = u128;
|
||||
pub type AccountIndex = u32;
|
||||
pub type BlockNumber = u64;
|
||||
pub type Balance = u128;
|
||||
pub type T = Runtime;
|
||||
|
||||
parameter_types! {
|
||||
pub BlockWeights: frame_system::limits::BlockWeights =
|
||||
frame_system::limits::BlockWeights::simple_max(2u64 * WEIGHT_PER_SECOND);
|
||||
}
|
||||
|
||||
impl frame_system::Config for Runtime {
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type BlockWeights = BlockWeights;
|
||||
type BlockLength = ();
|
||||
type DbWeight = ();
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type Index = AccountIndex;
|
||||
type BlockNumber = BlockNumber;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Hash = sp_core::H256;
|
||||
type Hashing = sp_runtime::traits::BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = sp_runtime::testing::Header;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockHashCount = ();
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = pallet_balances::AccountData<Balance>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
}
|
||||
|
||||
impl pallet_timestamp::Config for Runtime {
|
||||
type Moment = u64;
|
||||
type OnTimestampSet = ();
|
||||
type MinimumPeriod = ConstU64<5>;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static ExistentialDeposit: Balance = 1;
|
||||
}
|
||||
|
||||
impl pallet_balances::Config for Runtime {
|
||||
type MaxLocks = ConstU32<128>;
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type Balance = Balance;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type DustRemoval = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
pallet_staking_reward_curve::build! {
|
||||
const I_NPOS: sp_runtime::curve::PiecewiseLinear<'static> = curve!(
|
||||
min_inflation: 0_025_000,
|
||||
max_inflation: 0_100_000,
|
||||
ideal_stake: 0_500_000,
|
||||
falloff: 0_050_000,
|
||||
max_piece_count: 40,
|
||||
test_precision: 0_005_000,
|
||||
);
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS;
|
||||
pub static BondingDuration: u32 = 3;
|
||||
pub static CurrentEra: u32 = 0;
|
||||
pub static Ongoing: bool = false;
|
||||
}
|
||||
|
||||
pub struct MockElection;
|
||||
impl frame_election_provider_support::ElectionProvider for MockElection {
|
||||
type AccountId = AccountId;
|
||||
type BlockNumber = BlockNumber;
|
||||
type DataProvider = Staking;
|
||||
type Error = ();
|
||||
|
||||
fn ongoing() -> bool {
|
||||
Ongoing::get()
|
||||
}
|
||||
|
||||
fn elect() -> Result<frame_election_provider_support::Supports<AccountId>, Self::Error> {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
impl pallet_staking::Config for Runtime {
|
||||
type MaxNominations = ConstU32<16>;
|
||||
type Currency = Balances;
|
||||
type CurrencyBalance = Balance;
|
||||
type UnixTime = pallet_timestamp::Pallet<Self>;
|
||||
type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote;
|
||||
type RewardRemainder = ();
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Slash = ();
|
||||
type Reward = ();
|
||||
type SessionsPerEra = ();
|
||||
type SlashDeferDuration = ();
|
||||
type SlashCancelOrigin = frame_system::EnsureRoot<Self::AccountId>;
|
||||
type BondingDuration = BondingDuration;
|
||||
type SessionInterface = ();
|
||||
type EraPayout = pallet_staking::ConvertCurve<RewardCurve>;
|
||||
type NextNewSession = ();
|
||||
type HistoryDepth = ConstU32<84>;
|
||||
type MaxNominatorRewardedPerValidator = ConstU32<64>;
|
||||
type OffendingValidatorsThreshold = ();
|
||||
type ElectionProvider = MockElection;
|
||||
type GenesisElectionProvider = Self::ElectionProvider;
|
||||
type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
|
||||
type TargetList = pallet_staking::UseValidatorsMap<Self>;
|
||||
type MaxUnlockingChunks = ConstU32<32>;
|
||||
type OnStakerSlash = Pools;
|
||||
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
pub struct BalanceToU256;
|
||||
impl Convert<Balance, sp_core::U256> for BalanceToU256 {
|
||||
fn convert(n: Balance) -> sp_core::U256 {
|
||||
n.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct U256ToBalance;
|
||||
impl Convert<sp_core::U256, Balance> for U256ToBalance {
|
||||
fn convert(n: sp_core::U256) -> Balance {
|
||||
n.try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const PostUnbondingPoolsWindow: u32 = 10;
|
||||
pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls");
|
||||
pub static MaxMetadataLen: u32 = 10;
|
||||
pub static CheckLevel: u8 = 255;
|
||||
}
|
||||
|
||||
impl pallet_nomination_pools::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
type Currency = Balances;
|
||||
type CurrencyBalance = Balance;
|
||||
type RewardCounter = FixedU128;
|
||||
type BalanceToU256 = BalanceToU256;
|
||||
type U256ToBalance = U256ToBalance;
|
||||
type StakingInterface = Staking;
|
||||
type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow;
|
||||
type MaxMetadataLen = MaxMetadataLen;
|
||||
type MaxUnbonding = ConstU32<8>;
|
||||
type MaxPointsToBalance = ConstU8<10>;
|
||||
type PalletId = PoolsPalletId;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static SlashPerEra: u32 = 100;
|
||||
}
|
||||
|
||||
impl fast_unstake::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type SlashPerEra = SlashPerEra;
|
||||
type ControlOrigin = frame_system::EnsureRoot<Self::AccountId>;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Runtime>;
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Runtime>;
|
||||
frame_support::construct_runtime!(
|
||||
pub enum Runtime where
|
||||
Block = Block,
|
||||
NodeBlock = Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic
|
||||
{
|
||||
System: frame_system,
|
||||
Timestamp: pallet_timestamp,
|
||||
Balances: pallet_balances,
|
||||
Staking: pallet_staking,
|
||||
Pools: pallet_nomination_pools,
|
||||
FastUnstake: fast_unstake,
|
||||
}
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
static FastUnstakeEvents: u32 = 0;
|
||||
}
|
||||
|
||||
pub(crate) fn fast_unstake_events_since_last_call() -> Vec<super::Event<Runtime>> {
|
||||
let events = System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| if let RuntimeEvent::FastUnstake(inner) = e { Some(inner) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
let already_seen = FastUnstakeEvents::get();
|
||||
FastUnstakeEvents::set(events.len() as u32);
|
||||
events.into_iter().skip(already_seen as usize).collect()
|
||||
}
|
||||
|
||||
pub struct ExtBuilder {
|
||||
exposed_nominators: Vec<(AccountId, AccountId, Balance)>,
|
||||
}
|
||||
|
||||
impl Default for ExtBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
exposed_nominators: vec![
|
||||
(1, 2, 100),
|
||||
(3, 4, 100),
|
||||
(5, 6, 100),
|
||||
(7, 8, 100),
|
||||
(9, 10, 100),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const VALIDATORS_PER_ERA: AccountId = 32;
|
||||
pub(crate) const VALIDATOR_PREFIX: AccountId = 100;
|
||||
pub(crate) const NOMINATORS_PER_VALIDATOR_PER_ERA: AccountId = 4;
|
||||
pub(crate) const NOMINATOR_PREFIX: AccountId = 1000;
|
||||
|
||||
impl ExtBuilder {
|
||||
pub(crate) fn register_stakers_for_era(era: u32) {
|
||||
// validators are prefixed with 100 and nominators with 1000 to prevent conflict. Make sure
|
||||
// all the other accounts used in tests are below 100. Also ensure here that we don't
|
||||
// overlap.
|
||||
assert!(VALIDATOR_PREFIX + VALIDATORS_PER_ERA < NOMINATOR_PREFIX);
|
||||
|
||||
(VALIDATOR_PREFIX..VALIDATOR_PREFIX + VALIDATORS_PER_ERA)
|
||||
.map(|v| {
|
||||
// for the sake of sanity, let's register this taker as an actual validator.
|
||||
let others = (NOMINATOR_PREFIX..
|
||||
(NOMINATOR_PREFIX + NOMINATORS_PER_VALIDATOR_PER_ERA))
|
||||
.map(|n| IndividualExposure { who: n, value: 0 as Balance })
|
||||
.collect::<Vec<_>>();
|
||||
(v, Exposure { total: 0, own: 0, others })
|
||||
})
|
||||
.for_each(|(validator, exposure)| {
|
||||
pallet_staking::ErasStakers::<T>::insert(era, validator, exposure);
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn build(self) -> sp_io::TestExternalities {
|
||||
sp_tracing::try_init_simple();
|
||||
let mut storage =
|
||||
frame_system::GenesisConfig::default().build_storage::<Runtime>().unwrap();
|
||||
|
||||
// create one default pool.
|
||||
let _ = pallet_nomination_pools::GenesisConfig::<Runtime> { ..Default::default() }
|
||||
.assimilate_storage(&mut storage);
|
||||
|
||||
let validators_range = VALIDATOR_PREFIX..VALIDATOR_PREFIX + VALIDATORS_PER_ERA;
|
||||
let nominators_range =
|
||||
NOMINATOR_PREFIX..NOMINATOR_PREFIX + NOMINATORS_PER_VALIDATOR_PER_ERA;
|
||||
|
||||
let _ = pallet_balances::GenesisConfig::<Runtime> {
|
||||
balances: self
|
||||
.exposed_nominators
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|(stash, _, balance)| (stash, balance * 2))
|
||||
.chain(
|
||||
self.exposed_nominators
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|(_, ctrl, balance)| (ctrl, balance * 2)),
|
||||
)
|
||||
.chain(validators_range.clone().map(|x| (x, 100)))
|
||||
.chain(nominators_range.clone().map(|x| (x, 100)))
|
||||
.collect::<Vec<_>>(),
|
||||
}
|
||||
.assimilate_storage(&mut storage);
|
||||
|
||||
let _ = pallet_staking::GenesisConfig::<Runtime> {
|
||||
stakers: self
|
||||
.exposed_nominators
|
||||
.into_iter()
|
||||
.map(|(x, y, z)| (x, y, z, pallet_staking::StakerStatus::Nominator(vec![42])))
|
||||
.chain(validators_range.map(|x| (x, x, 100, StakerStatus::Validator)))
|
||||
.chain(nominators_range.map(|x| (x, x, 100, StakerStatus::Nominator(vec![x]))))
|
||||
.collect::<Vec<_>>(),
|
||||
..Default::default()
|
||||
}
|
||||
.assimilate_storage(&mut storage);
|
||||
|
||||
let mut ext = sp_io::TestExternalities::from(storage);
|
||||
|
||||
ext.execute_with(|| {
|
||||
// for events to be deposited.
|
||||
frame_system::Pallet::<Runtime>::set_block_number(1);
|
||||
|
||||
for era in 0..=(BondingDuration::get()) {
|
||||
Self::register_stakers_for_era(era);
|
||||
}
|
||||
|
||||
// because we read this value as a measure of how many validators we have.
|
||||
pallet_staking::ValidatorCount::<Runtime>::put(VALIDATORS_PER_ERA as u32);
|
||||
|
||||
// make a pool
|
||||
let amount_to_bond = Pools::depositor_min_bond();
|
||||
Balances::make_free_balance_be(&10, amount_to_bond * 5);
|
||||
assert_ok!(Pools::create(RawOrigin::Signed(10).into(), amount_to_bond, 900, 901, 902));
|
||||
});
|
||||
ext
|
||||
}
|
||||
|
||||
pub fn build_and_execute(self, test: impl FnOnce() -> ()) {
|
||||
self.build().execute_with(|| {
|
||||
test();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn run_to_block(n: u64, on_idle: bool) {
|
||||
let current_block = System::block_number();
|
||||
assert!(n > current_block);
|
||||
while System::block_number() < n {
|
||||
Balances::on_finalize(System::block_number());
|
||||
Staking::on_finalize(System::block_number());
|
||||
Pools::on_finalize(System::block_number());
|
||||
FastUnstake::on_finalize(System::block_number());
|
||||
|
||||
System::set_block_number(System::block_number() + 1);
|
||||
|
||||
Balances::on_initialize(System::block_number());
|
||||
Staking::on_initialize(System::block_number());
|
||||
Pools::on_initialize(System::block_number());
|
||||
FastUnstake::on_initialize(System::block_number());
|
||||
if on_idle {
|
||||
FastUnstake::on_idle(System::block_number(), BlockWeights::get().max_block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn next_block(on_idle: bool) {
|
||||
let current = System::block_number();
|
||||
run_to_block(current + 1, on_idle);
|
||||
}
|
||||
|
||||
pub fn assert_unstaked(stash: &AccountId) {
|
||||
assert!(!pallet_staking::Bonded::<T>::contains_key(stash));
|
||||
assert!(!pallet_staking::Payee::<T>::contains_key(stash));
|
||||
assert!(!pallet_staking::Validators::<T>::contains_key(stash));
|
||||
assert!(!pallet_staking::Nominators::<T>::contains_key(stash));
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,119 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2022 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.
|
||||
|
||||
//! Types used in the Fast Unstake pallet.
|
||||
|
||||
use crate::*;
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use frame_support::{
|
||||
traits::{Currency, Get, IsSubType},
|
||||
BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
|
||||
};
|
||||
use pallet_nomination_pools::PoolId;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::transaction_validity::{InvalidTransaction, TransactionValidityError};
|
||||
use sp_staking::EraIndex;
|
||||
use sp_std::{fmt::Debug, prelude::*};
|
||||
|
||||
pub type BalanceOf<T> = <<T as pallet_staking::Config>::Currency as Currency<
|
||||
<T as frame_system::Config>::AccountId,
|
||||
>>::Balance;
|
||||
|
||||
/// An unstake request.
|
||||
#[derive(
|
||||
Encode, Decode, EqNoBound, PartialEqNoBound, Clone, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen,
|
||||
)]
|
||||
pub struct UnstakeRequest<AccountId: Eq + PartialEq + Debug, MaxChecked: Get<u32>> {
|
||||
/// Their stash account.
|
||||
pub(crate) stash: AccountId,
|
||||
/// The list of eras for which they have been checked.
|
||||
pub(crate) checked: BoundedVec<EraIndex, MaxChecked>,
|
||||
/// The pool they wish to join, if any.
|
||||
pub(crate) maybe_pool_id: Option<PoolId>,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo, RuntimeDebugNoBound)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct PreventStakingOpsIfUnbonding<T: Config + Send + Sync>(sp_std::marker::PhantomData<T>);
|
||||
|
||||
#[cfg(test)]
|
||||
impl<T: Config + Send + Sync> PreventStakingOpsIfUnbonding<T> {
|
||||
pub fn new() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config + Send + Sync> sp_runtime::traits::SignedExtension
|
||||
for PreventStakingOpsIfUnbonding<T>
|
||||
where
|
||||
<T as frame_system::Config>::RuntimeCall: IsSubType<pallet_staking::Call<T>>,
|
||||
{
|
||||
type AccountId = T::AccountId;
|
||||
type Call = <T as frame_system::Config>::RuntimeCall;
|
||||
type AdditionalSigned = ();
|
||||
type Pre = ();
|
||||
const IDENTIFIER: &'static str = "PreventStakingOpsIfUnbonding";
|
||||
|
||||
fn additional_signed(&self) -> Result<Self::AdditionalSigned, TransactionValidityError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pre_dispatch(
|
||||
self,
|
||||
// NOTE: we want to prevent this stash-controller pair from doing anything in the
|
||||
// staking system as long as they are registered here.
|
||||
stash_or_controller: &Self::AccountId,
|
||||
call: &Self::Call,
|
||||
_info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
|
||||
_len: usize,
|
||||
) -> Result<Self::Pre, TransactionValidityError> {
|
||||
// we don't check this in the tx-pool as it requires a storage read.
|
||||
if <Self::Call as IsSubType<pallet_staking::Call<T>>>::is_sub_type(call).is_some() {
|
||||
let check_stash = |stash: &T::AccountId| {
|
||||
if Queue::<T>::contains_key(&stash) ||
|
||||
Head::<T>::get().map_or(false, |u| &u.stash == stash)
|
||||
{
|
||||
Err(TransactionValidityError::Invalid(InvalidTransaction::Call))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
match (
|
||||
// mapped from controller.
|
||||
pallet_staking::Ledger::<T>::get(&stash_or_controller),
|
||||
// mapped from stash.
|
||||
pallet_staking::Bonded::<T>::get(&stash_or_controller),
|
||||
) {
|
||||
(Some(ledger), None) => {
|
||||
// it is a controller.
|
||||
check_stash(&ledger.stash)
|
||||
},
|
||||
(_, Some(_)) => {
|
||||
// it's a stash.
|
||||
let stash = stash_or_controller;
|
||||
check_stash(stash)
|
||||
},
|
||||
(None, None) => {
|
||||
// They are not a staker -- let them execute.
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2022 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.
|
||||
|
||||
//! Autogenerated weights for pallet_fast_unstake
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
|
||||
//! DATE: 2022-09-07, STEPS: `10`, REPEAT: 1, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! HOSTNAME: `Kians-MacBook-Pro-2.local`, CPU: `<UNKNOWN>`
|
||||
//! EXECUTION: Some(Native), WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024
|
||||
|
||||
// Executed Command:
|
||||
// target/release/substrate
|
||||
// benchmark
|
||||
// pallet
|
||||
// --steps=10
|
||||
// --repeat=1
|
||||
// --pallet=pallet_fast_unstake
|
||||
// --extrinsic=*
|
||||
// --execution=native
|
||||
// --output
|
||||
// weight.rs
|
||||
// --template
|
||||
// ./.maintain/frame-weight-template.hbs
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
|
||||
use sp_std::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for pallet_fast_unstake.
|
||||
pub trait WeightInfo {
|
||||
fn on_idle_unstake() -> Weight;
|
||||
fn on_idle_check(x: u32, ) -> Weight;
|
||||
fn register_fast_unstake() -> Weight;
|
||||
fn deregister() -> Weight;
|
||||
fn control() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for pallet_fast_unstake using the Substrate node and recommended hardware.
|
||||
pub struct SubstrateWeight<T>(PhantomData<T>);
|
||||
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
// Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0)
|
||||
// Storage: Staking ValidatorCount (r:1 w:0)
|
||||
// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0)
|
||||
// Storage: FastUnstake Head (r:1 w:1)
|
||||
// Storage: Staking CurrentEra (r:1 w:0)
|
||||
// Storage: Staking SlashingSpans (r:1 w:0)
|
||||
// Storage: Staking Bonded (r:2 w:1)
|
||||
// Storage: Staking Ledger (r:2 w:2)
|
||||
// Storage: Staking Validators (r:1 w:0)
|
||||
// Storage: Staking Nominators (r:1 w:0)
|
||||
// Storage: System Account (r:3 w:2)
|
||||
// Storage: Balances Locks (r:2 w:2)
|
||||
// Storage: NominationPools MinJoinBond (r:1 w:0)
|
||||
// Storage: NominationPools PoolMembers (r:1 w:1)
|
||||
// Storage: NominationPools BondedPools (r:1 w:1)
|
||||
// Storage: NominationPools RewardPools (r:1 w:1)
|
||||
// Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0)
|
||||
// Storage: NominationPools MaxPoolMembers (r:1 w:0)
|
||||
// Storage: NominationPools CounterForPoolMembers (r:1 w:1)
|
||||
// Storage: BagsList ListNodes (r:1 w:0)
|
||||
// Storage: Staking Payee (r:0 w:1)
|
||||
fn on_idle_unstake() -> Weight {
|
||||
Weight::from_ref_time(102_000_000 as u64)
|
||||
.saturating_add(T::DbWeight::get().reads(25 as u64))
|
||||
.saturating_add(T::DbWeight::get().writes(13 as u64))
|
||||
}
|
||||
// Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0)
|
||||
// Storage: Staking ValidatorCount (r:1 w:0)
|
||||
// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0)
|
||||
// Storage: FastUnstake Head (r:1 w:1)
|
||||
// Storage: FastUnstake Queue (r:2 w:1)
|
||||
// Storage: FastUnstake CounterForQueue (r:1 w:1)
|
||||
// Storage: Staking CurrentEra (r:1 w:0)
|
||||
// Storage: Staking ErasStakers (r:1344 w:0)
|
||||
/// The range of component `x` is `[672, 86016]`.
|
||||
fn on_idle_check(x: u32, ) -> Weight {
|
||||
Weight::from_ref_time(0 as u64)
|
||||
// Standard Error: 244_000
|
||||
.saturating_add(Weight::from_ref_time(13_913_000 as u64).saturating_mul(x as u64))
|
||||
.saturating_add(T::DbWeight::get().reads(585 as u64))
|
||||
.saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(x as u64)))
|
||||
.saturating_add(T::DbWeight::get().writes(3 as u64))
|
||||
}
|
||||
// Storage: Staking Ledger (r:1 w:1)
|
||||
// Storage: Staking Nominators (r:1 w:1)
|
||||
// Storage: FastUnstake Queue (r:1 w:1)
|
||||
// Storage: FastUnstake Head (r:1 w:0)
|
||||
// Storage: Staking Validators (r:1 w:0)
|
||||
// Storage: Staking CounterForNominators (r:1 w:1)
|
||||
// Storage: BagsList ListNodes (r:1 w:1)
|
||||
// Storage: BagsList ListBags (r:1 w:1)
|
||||
// Storage: BagsList CounterForListNodes (r:1 w:1)
|
||||
// Storage: Staking CurrentEra (r:1 w:0)
|
||||
// Storage: Balances Locks (r:1 w:1)
|
||||
// Storage: FastUnstake CounterForQueue (r:1 w:1)
|
||||
fn register_fast_unstake() -> Weight {
|
||||
Weight::from_ref_time(57_000_000 as u64)
|
||||
.saturating_add(T::DbWeight::get().reads(12 as u64))
|
||||
.saturating_add(T::DbWeight::get().writes(9 as u64))
|
||||
}
|
||||
// Storage: Staking Ledger (r:1 w:0)
|
||||
// Storage: FastUnstake Queue (r:1 w:1)
|
||||
// Storage: FastUnstake Head (r:1 w:0)
|
||||
// Storage: FastUnstake CounterForQueue (r:1 w:1)
|
||||
fn deregister() -> Weight {
|
||||
Weight::from_ref_time(15_000_000 as u64)
|
||||
.saturating_add(T::DbWeight::get().reads(4 as u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2 as u64))
|
||||
}
|
||||
// Storage: FastUnstake ErasToCheckPerBlock (r:0 w:1)
|
||||
fn control() -> Weight {
|
||||
Weight::from_ref_time(3_000_000 as u64)
|
||||
.saturating_add(T::DbWeight::get().writes(1 as u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
impl WeightInfo for () {
|
||||
// Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0)
|
||||
// Storage: Staking ValidatorCount (r:1 w:0)
|
||||
// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0)
|
||||
// Storage: FastUnstake Head (r:1 w:1)
|
||||
// Storage: Staking CurrentEra (r:1 w:0)
|
||||
// Storage: Staking SlashingSpans (r:1 w:0)
|
||||
// Storage: Staking Bonded (r:2 w:1)
|
||||
// Storage: Staking Ledger (r:2 w:2)
|
||||
// Storage: Staking Validators (r:1 w:0)
|
||||
// Storage: Staking Nominators (r:1 w:0)
|
||||
// Storage: System Account (r:3 w:2)
|
||||
// Storage: Balances Locks (r:2 w:2)
|
||||
// Storage: NominationPools MinJoinBond (r:1 w:0)
|
||||
// Storage: NominationPools PoolMembers (r:1 w:1)
|
||||
// Storage: NominationPools BondedPools (r:1 w:1)
|
||||
// Storage: NominationPools RewardPools (r:1 w:1)
|
||||
// Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0)
|
||||
// Storage: NominationPools MaxPoolMembers (r:1 w:0)
|
||||
// Storage: NominationPools CounterForPoolMembers (r:1 w:1)
|
||||
// Storage: BagsList ListNodes (r:1 w:0)
|
||||
// Storage: Staking Payee (r:0 w:1)
|
||||
fn on_idle_unstake() -> Weight {
|
||||
Weight::from_ref_time(102_000_000 as u64)
|
||||
.saturating_add(RocksDbWeight::get().reads(25 as u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(13 as u64))
|
||||
}
|
||||
// Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0)
|
||||
// Storage: Staking ValidatorCount (r:1 w:0)
|
||||
// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0)
|
||||
// Storage: FastUnstake Head (r:1 w:1)
|
||||
// Storage: FastUnstake Queue (r:2 w:1)
|
||||
// Storage: FastUnstake CounterForQueue (r:1 w:1)
|
||||
// Storage: Staking CurrentEra (r:1 w:0)
|
||||
// Storage: Staking ErasStakers (r:1344 w:0)
|
||||
/// The range of component `x` is `[672, 86016]`.
|
||||
fn on_idle_check(x: u32, ) -> Weight {
|
||||
Weight::from_ref_time(0 as u64)
|
||||
// Standard Error: 244_000
|
||||
.saturating_add(Weight::from_ref_time(13_913_000 as u64).saturating_mul(x as u64))
|
||||
.saturating_add(RocksDbWeight::get().reads(585 as u64))
|
||||
.saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(x as u64)))
|
||||
.saturating_add(RocksDbWeight::get().writes(3 as u64))
|
||||
}
|
||||
// Storage: Staking Ledger (r:1 w:1)
|
||||
// Storage: Staking Nominators (r:1 w:1)
|
||||
// Storage: FastUnstake Queue (r:1 w:1)
|
||||
// Storage: FastUnstake Head (r:1 w:0)
|
||||
// Storage: Staking Validators (r:1 w:0)
|
||||
// Storage: Staking CounterForNominators (r:1 w:1)
|
||||
// Storage: BagsList ListNodes (r:1 w:1)
|
||||
// Storage: BagsList ListBags (r:1 w:1)
|
||||
// Storage: BagsList CounterForListNodes (r:1 w:1)
|
||||
// Storage: Staking CurrentEra (r:1 w:0)
|
||||
// Storage: Balances Locks (r:1 w:1)
|
||||
// Storage: FastUnstake CounterForQueue (r:1 w:1)
|
||||
fn register_fast_unstake() -> Weight {
|
||||
Weight::from_ref_time(57_000_000 as u64)
|
||||
.saturating_add(RocksDbWeight::get().reads(12 as u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(9 as u64))
|
||||
}
|
||||
// Storage: Staking Ledger (r:1 w:0)
|
||||
// Storage: FastUnstake Queue (r:1 w:1)
|
||||
// Storage: FastUnstake Head (r:1 w:0)
|
||||
// Storage: FastUnstake CounterForQueue (r:1 w:1)
|
||||
fn deregister() -> Weight {
|
||||
Weight::from_ref_time(15_000_000 as u64)
|
||||
.saturating_add(RocksDbWeight::get().reads(4 as u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2 as u64))
|
||||
}
|
||||
// Storage: FastUnstake ErasToCheckPerBlock (r:0 w:1)
|
||||
fn control() -> Weight {
|
||||
Weight::from_ref_time(3_000_000 as u64)
|
||||
.saturating_add(RocksDbWeight::get().writes(1 as u64))
|
||||
}
|
||||
}
|
||||
@@ -52,12 +52,6 @@ pub trait Config:
|
||||
|
||||
pub struct Pallet<T: Config>(Pools<T>);
|
||||
|
||||
fn min_create_bond<T: Config>() -> BalanceOf<T> {
|
||||
MinCreateBond::<T>::get()
|
||||
.max(T::StakingInterface::minimum_bond())
|
||||
.max(CurrencyOf::<T>::minimum_balance())
|
||||
}
|
||||
|
||||
fn create_funded_user_with_balance<T: pallet_nomination_pools::Config>(
|
||||
string: &'static str,
|
||||
n: u32,
|
||||
@@ -220,7 +214,7 @@ impl<T: Config> ListScenario<T> {
|
||||
|
||||
frame_benchmarking::benchmarks! {
|
||||
join {
|
||||
let origin_weight = min_create_bond::<T>() * 2u32.into();
|
||||
let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
|
||||
// setup the worst case list scenario.
|
||||
let scenario = ListScenario::<T>::new(origin_weight, true)?;
|
||||
@@ -246,7 +240,7 @@ frame_benchmarking::benchmarks! {
|
||||
}
|
||||
|
||||
bond_extra_transfer {
|
||||
let origin_weight = min_create_bond::<T>() * 2u32.into();
|
||||
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;
|
||||
|
||||
@@ -261,7 +255,7 @@ frame_benchmarking::benchmarks! {
|
||||
}
|
||||
|
||||
bond_extra_reward {
|
||||
let origin_weight = min_create_bond::<T>() * 2u32.into();
|
||||
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());
|
||||
|
||||
@@ -279,7 +273,7 @@ frame_benchmarking::benchmarks! {
|
||||
}
|
||||
|
||||
claim_payout {
|
||||
let origin_weight = min_create_bond::<T>() * 2u32.into();
|
||||
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);
|
||||
let reward_account = Pools::<T>::create_reward_account(1);
|
||||
@@ -309,7 +303,7 @@ frame_benchmarking::benchmarks! {
|
||||
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 = min_create_bond::<T>() * 200u32.into();
|
||||
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;
|
||||
|
||||
@@ -340,7 +334,7 @@ frame_benchmarking::benchmarks! {
|
||||
pool_withdraw_unbonded {
|
||||
let s in 0 .. MAX_SPANS;
|
||||
|
||||
let min_create_bond = min_create_bond::<T>();
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond);
|
||||
|
||||
// Add a new member
|
||||
@@ -382,7 +376,7 @@ frame_benchmarking::benchmarks! {
|
||||
withdraw_unbonded_update {
|
||||
let s in 0 .. MAX_SPANS;
|
||||
|
||||
let min_create_bond = min_create_bond::<T>();
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond);
|
||||
|
||||
// Add a new member
|
||||
@@ -428,7 +422,7 @@ frame_benchmarking::benchmarks! {
|
||||
withdraw_unbonded_kill {
|
||||
let s in 0 .. MAX_SPANS;
|
||||
|
||||
let min_create_bond = min_create_bond::<T>();
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond);
|
||||
let depositor_lookup = T::Lookup::unlookup(depositor.clone());
|
||||
|
||||
@@ -493,14 +487,14 @@ frame_benchmarking::benchmarks! {
|
||||
}
|
||||
|
||||
create {
|
||||
let min_create_bond = min_create_bond::<T>();
|
||||
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>::make_free_balance_be(&depositor, min_create_bond * 2u32.into());
|
||||
|
||||
// Make sure no pools exist as a pre-condition for our verify checks
|
||||
// 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);
|
||||
|
||||
@@ -540,7 +534,7 @@ frame_benchmarking::benchmarks! {
|
||||
let n in 1 .. T::MaxNominations::get();
|
||||
|
||||
// Create a pool
|
||||
let min_create_bond = min_create_bond::<T>() * 2u32.into();
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond() * 2u32.into();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond);
|
||||
|
||||
// Create some accounts to nominate. For the sake of benchmarking they don't need to be
|
||||
@@ -577,7 +571,7 @@ frame_benchmarking::benchmarks! {
|
||||
|
||||
set_state {
|
||||
// Create a pool
|
||||
let min_create_bond = min_create_bond::<T>();
|
||||
let min_create_bond = Pools::<T>::depositor_min_bond();
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond);
|
||||
BondedPools::<T>::mutate(&1, |maybe_pool| {
|
||||
// Force the pool into an invalid state
|
||||
@@ -595,7 +589,7 @@ frame_benchmarking::benchmarks! {
|
||||
let n in 1 .. <T as pallet_nomination_pools::Config>::MaxMetadataLen::get();
|
||||
|
||||
// Create a pool
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond::<T>() * 2u32.into());
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into());
|
||||
|
||||
// Create metadata of the max possible size
|
||||
let metadata: Vec<u8> = (0..n).map(|_| 42).collect();
|
||||
@@ -624,7 +618,7 @@ frame_benchmarking::benchmarks! {
|
||||
|
||||
update_roles {
|
||||
let first_id = pallet_nomination_pools::LastPoolId::<T>::get() + 1;
|
||||
let (root, _) = create_pool_account::<T>(0, min_create_bond::<T>() * 2u32.into());
|
||||
let (root, _) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into());
|
||||
let random: T::AccountId = account("but is anything really random in computers..?", 0, USER_SEED);
|
||||
}:_(
|
||||
RuntimeOrigin::Signed(root.clone()),
|
||||
@@ -646,7 +640,7 @@ frame_benchmarking::benchmarks! {
|
||||
|
||||
chill {
|
||||
// Create a pool
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, min_create_bond::<T>() * 2u32.into());
|
||||
let (depositor, pool_account) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into());
|
||||
|
||||
// Nominate with the pool.
|
||||
let validators: Vec<_> = (0..T::MaxNominations::get())
|
||||
|
||||
@@ -2184,10 +2184,11 @@ impl<T: Config> Pallet<T> {
|
||||
///
|
||||
/// It is essentially `max { MinNominatorBond, MinCreateBond, MinJoinBond }`, where the former
|
||||
/// is coming from the staking pallet and the latter two are configured in this pallet.
|
||||
fn depositor_min_bond() -> BalanceOf<T> {
|
||||
pub fn depositor_min_bond() -> BalanceOf<T> {
|
||||
T::StakingInterface::minimum_bond()
|
||||
.max(MinCreateBond::<T>::get())
|
||||
.max(MinJoinBond::<T>::get())
|
||||
.max(T::Currency::minimum_balance())
|
||||
}
|
||||
/// Remove everything related to the given bonded pool.
|
||||
///
|
||||
|
||||
@@ -2771,7 +2771,7 @@ mod unbond {
|
||||
|
||||
#[test]
|
||||
fn partial_unbond_era_tracking() {
|
||||
ExtBuilder::default().build_and_execute(|| {
|
||||
ExtBuilder::default().ed(1).build_and_execute(|| {
|
||||
// to make the depositor capable of withdrawing.
|
||||
StakingMinBond::set(1);
|
||||
MinCreateBond::<T>::set(1);
|
||||
|
||||
@@ -563,7 +563,7 @@ impl<T: Config> StakingLedger<T> {
|
||||
///
|
||||
/// This calls `Config::OnStakerSlash::on_slash` with information as to how the slash was
|
||||
/// applied.
|
||||
fn slash(
|
||||
pub fn slash(
|
||||
&mut self,
|
||||
slash_amount: BalanceOf<T>,
|
||||
minimum_balance: BalanceOf<T>,
|
||||
|
||||
@@ -653,10 +653,10 @@ impl<T: Config> Pallet<T> {
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn add_era_stakers(
|
||||
current_era: EraIndex,
|
||||
controller: T::AccountId,
|
||||
stash: T::AccountId,
|
||||
exposure: Exposure<T::AccountId, BalanceOf<T>>,
|
||||
) {
|
||||
<ErasStakers<T>>::insert(¤t_era, &controller, &exposure);
|
||||
<ErasStakers<T>>::insert(¤t_era, &stash, &exposure);
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
@@ -504,6 +504,7 @@ pub mod pallet {
|
||||
|
||||
/// Slashing spans for stash accounts.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn slashing_spans)]
|
||||
#[pallet::unbounded]
|
||||
pub(crate) type SlashingSpans<T: Config> =
|
||||
StorageMap<_, Twox64Concat, T::AccountId, slashing::SlashingSpans>;
|
||||
@@ -656,8 +657,8 @@ pub mod pallet {
|
||||
EraPaid(EraIndex, BalanceOf<T>, BalanceOf<T>),
|
||||
/// The nominator has been rewarded by this amount. \[stash, amount\]
|
||||
Rewarded(T::AccountId, BalanceOf<T>),
|
||||
/// One validator (and its nominators) has been slashed by the given amount.
|
||||
/// \[validator, amount\]
|
||||
/// One staker (and potentially its nominators) has been slashed by the given amount.
|
||||
/// \[staker, amount\]
|
||||
Slashed(T::AccountId, BalanceOf<T>),
|
||||
/// An old slashing report from a prior era was discarded because it could
|
||||
/// not be processed. \[session_index\]
|
||||
|
||||
Reference in New Issue
Block a user