diff --git a/Cargo.lock b/Cargo.lock index f812678753142..e3a72ca23d883 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10171,6 +10171,7 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", + "pallet-nomination-pools", "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", @@ -10844,6 +10845,7 @@ dependencies = [ "frame-system", "pallet-bags-list", "pallet-balances", + "pallet-delegated-staking", "pallet-nomination-pools", "pallet-staking", "pallet-staking-reward-curve", @@ -10884,7 +10886,32 @@ dependencies = [ ] [[package]] -name = "pallet-nomination-pools-test-staking" +name = "pallet-nomination-pools-test-delegate-stake" +version = "1.0.0" +dependencies = [ + "frame-election-provider-support", + "frame-support", + "frame-system", + "log", + "pallet-bags-list", + "pallet-balances", + "pallet-delegated-staking", + "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 14.0.0", + "sp-tracing 16.0.0", +] + +[[package]] +name = "pallet-nomination-pools-test-transfer-stake" version = "1.0.0" dependencies = [ "frame-election-provider-support", @@ -22848,6 +22875,7 @@ dependencies = [ "pallet-beefy-mmr", "pallet-collective", "pallet-conviction-voting", + "pallet-delegated-staking", "pallet-democracy", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", diff --git a/Cargo.toml b/Cargo.toml index 239e1f5de996f..54fa44d6654d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -374,7 +374,8 @@ members = [ "substrate/frame/nomination-pools/benchmarking", "substrate/frame/nomination-pools/fuzzer", "substrate/frame/nomination-pools/runtime-api", - "substrate/frame/nomination-pools/test-staking", + "substrate/frame/nomination-pools/test-delegate-stake", + "substrate/frame/nomination-pools/test-transfer-stake", "substrate/frame/offences", "substrate/frame/offences/benchmarking", "substrate/frame/paged-list", diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index 01e9dd1527f22..56623272be82e 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -83,6 +83,7 @@ pallet-society = { path = "../../../substrate/frame/society", default-features = pallet-staking = { path = "../../../substrate/frame/staking", default-features = false } pallet-staking-reward-curve = { package = "pallet-staking-reward-curve", path = "../../../substrate/frame/staking/reward-curve" } pallet-staking-runtime-api = { path = "../../../substrate/frame/staking/runtime-api", default-features = false } +pallet-delegated-staking = { path = "../../../substrate/frame/delegated-staking", default-features = false } pallet-state-trie-migration = { path = "../../../substrate/frame/state-trie-migration", default-features = false } pallet-sudo = { path = "../../../substrate/frame/sudo", default-features = false } pallet-timestamp = { path = "../../../substrate/frame/timestamp", default-features = false } @@ -161,6 +162,7 @@ std = [ "pallet-beefy/std", "pallet-collective/std", "pallet-conviction-voting/std", + "pallet-delegated-staking/std", "pallet-democracy/std", "pallet-election-provider-multi-phase/std", "pallet-election-provider-support-benchmarking?/std", @@ -244,6 +246,7 @@ runtime-benchmarks = [ "pallet-balances/runtime-benchmarks", "pallet-collective/runtime-benchmarks", "pallet-conviction-voting/runtime-benchmarks", + "pallet-delegated-staking/runtime-benchmarks", "pallet-democracy/runtime-benchmarks", "pallet-election-provider-multi-phase/runtime-benchmarks", "pallet-election-provider-support-benchmarking/runtime-benchmarks", @@ -304,6 +307,7 @@ try-runtime = [ "pallet-beefy/try-runtime", "pallet-collective/try-runtime", "pallet-conviction-voting/try-runtime", + "pallet-delegated-staking/try-runtime", "pallet-democracy/try-runtime", "pallet-election-provider-multi-phase/try-runtime", "pallet-elections-phragmen/try-runtime", diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index cfe0dde0da13d..4bf132d82c963 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -647,7 +647,7 @@ impl pallet_staking::Config for Runtime { type HistoryDepth = frame_support::traits::ConstU32<84>; type MaxControllersInDeprecationBatch = MaxControllersInDeprecationBatch; type BenchmarkingConfig = runtime_common::StakingBenchmarkingConfig; - type EventListeners = NominationPools; + type EventListeners = (NominationPools, DelegatedStaking); type WeightInfo = weights::pallet_staking::WeightInfo; type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; } @@ -1360,7 +1360,8 @@ impl pallet_nomination_pools::Config for Runtime { type RewardCounter = FixedU128; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; - type Staking = Staking; + type StakeAdapter = + pallet_nomination_pools::adapter::DelegateStake; type PostUnbondingPoolsWindow = ConstU32<4>; type MaxMetadataLen = ConstU32<256>; // we use the same number of allowed unlocking chunks as with staking. @@ -1370,6 +1371,21 @@ impl pallet_nomination_pools::Config for Runtime { type AdminOrigin = EitherOf, StakingAdmin>; } +parameter_types! { + pub const DelegatedStakingPalletId: PalletId = PalletId(*b"py/dlstk"); + pub const SlashRewardFraction: Perbill = Perbill::from_percent(1); +} + +impl pallet_delegated_staking::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PalletId = DelegatedStakingPalletId; + type Currency = Balances; + type OnSlash = (); + type SlashRewardFraction = SlashRewardFraction; + type RuntimeHoldReason = RuntimeHoldReason; + type CoreStaking = Staking; +} + impl pallet_root_testing::Config for Runtime { type RuntimeEvent = RuntimeEvent; } @@ -1518,6 +1534,10 @@ mod runtime { #[runtime::pallet_index(37)] pub type Treasury = pallet_treasury; + // Staking extension for delegation + #[runtime::pallet_index(38)] + pub type DelegatedStaking = pallet_delegated_staking; + // Parachains pallets. Start indices at 40 to leave room. #[runtime::pallet_index(41)] pub type ParachainsOrigin = parachains_origin; @@ -1621,11 +1641,12 @@ pub type SignedExtra = ( frame_metadata_hash_extension::CheckMetadataHash, ); -pub struct NominationPoolsMigrationV4OldPallet; -impl Get for NominationPoolsMigrationV4OldPallet { - fn get() -> Perbill { - Perbill::from_percent(100) - } +parameter_types! { + // This is the max pools that will be migrated in the runtime upgrade. Westend has more pools + // than this, but we want to emulate some non migrated pools. In prod runtimes, if weight is not + // a concern, it is recommended to set to (existing pools + 10) to also account for any new + // pools getting created before the migration is actually executed. + pub const MaxPoolsToMigrate: u32 = 250; } /// All migrations that will run on the next runtime upgrade. @@ -1658,7 +1679,15 @@ pub mod migrations { } /// Unreleased migrations. Add new ones here: - pub type Unreleased = (pallet_staking::migrations::v15::MigrateV14ToV15,); + pub type Unreleased = ( + // Migrate NominationPools to `DelegateStake` adapter. This is unversioned upgrade and + // should not be applied yet in Kusama/Polkadot. + pallet_nomination_pools::migration::unversioned::DelegationStakeMigration< + Runtime, + MaxPoolsToMigrate, + >, + pallet_staking::migrations::v15::MigrateV14ToV15, + ); } /// Unchecked extrinsic type as expected by this runtime. diff --git a/polkadot/runtime/westend/src/weights/pallet_nomination_pools.rs b/polkadot/runtime/westend/src/weights/pallet_nomination_pools.rs index 6aa5ddd1ec8fb..35eef199fb7a9 100644 --- a/polkadot/runtime/westend/src/weights/pallet_nomination_pools.rs +++ b/polkadot/runtime/westend/src/weights/pallet_nomination_pools.rs @@ -16,10 +16,10 @@ //! Autogenerated weights for `pallet_nomination_pools` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-11-24, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-04-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-dcu62vjg-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -54,7 +54,7 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`) /// Storage: `NominationPools::BondedPools` (r:1 w:1) /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) - /// Storage: `Staking::Bonded` (r:1 w:0) + /// Storage: `Staking::Bonded` (r:2 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) @@ -62,7 +62,7 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `NominationPools::RewardPools` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) /// Storage: `NominationPools::GlobalMaxCommission` (r:1 w:0) /// Proof: `NominationPools::GlobalMaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:2 w:1) + /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `NominationPools::MaxPoolMembersPerPool` (r:1 w:0) /// Proof: `NominationPools::MaxPoolMembersPerPool` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -70,10 +70,16 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `NominationPools::MaxPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `NominationPools::CounterForPoolMembers` (r:1 w:1) /// Proof: `NominationPools::CounterForPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Delegators` (r:1 w:1) + /// Proof: `DelegatedStaking::Delegators` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Agents` (r:2 w:1) + /// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::CounterForDelegators` (r:1 w:1) + /// Proof: `DelegatedStaking::CounterForDelegators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) @@ -82,13 +88,13 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn join() -> Weight { // Proof Size summary in bytes: - // Measured: `3355` + // Measured: `3606` // Estimated: `8877` - // Minimum execution time: 173_707_000 picoseconds. - Weight::from_parts(179_920_000, 0) + // Minimum execution time: 204_877_000 picoseconds. + Weight::from_parts(210_389_000, 0) .saturating_add(Weight::from_parts(0, 8877)) - .saturating_add(T::DbWeight::get().reads(20)) - .saturating_add(T::DbWeight::get().writes(13)) + .saturating_add(T::DbWeight::get().reads(24)) + .saturating_add(T::DbWeight::get().writes(15)) } /// Storage: `NominationPools::PoolMembers` (r:1 w:1) /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`) @@ -98,16 +104,20 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `NominationPools::RewardPools` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) /// Storage: `NominationPools::GlobalMaxCommission` (r:1 w:0) /// Proof: `NominationPools::GlobalMaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:3 w:2) + /// Storage: `System::Account` (r:2 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Staking::Bonded` (r:1 w:0) + /// Storage: `Staking::Bonded` (r:2 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Delegators` (r:1 w:1) + /// Proof: `DelegatedStaking::Delegators` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Agents` (r:2 w:1) + /// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) @@ -116,13 +126,13 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn bond_extra_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `3365` + // Measured: `3762` // Estimated: `8877` - // Minimum execution time: 174_414_000 picoseconds. - Weight::from_parts(178_068_000, 0) + // Minimum execution time: 203_362_000 picoseconds. + Weight::from_parts(209_899_000, 0) .saturating_add(Weight::from_parts(0, 8877)) - .saturating_add(T::DbWeight::get().reads(17)) - .saturating_add(T::DbWeight::get().writes(13)) + .saturating_add(T::DbWeight::get().reads(20)) + .saturating_add(T::DbWeight::get().writes(14)) } /// Storage: `NominationPools::ClaimPermissions` (r:1 w:0) /// Proof: `NominationPools::ClaimPermissions` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) @@ -134,16 +144,20 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `NominationPools::RewardPools` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) /// Storage: `NominationPools::GlobalMaxCommission` (r:1 w:0) /// Proof: `NominationPools::GlobalMaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:3 w:3) + /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Staking::Bonded` (r:1 w:0) + /// Storage: `Staking::Bonded` (r:2 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Delegators` (r:1 w:1) + /// Proof: `DelegatedStaking::Delegators` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Agents` (r:2 w:1) + /// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:2 w:2) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) @@ -152,13 +166,13 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn bond_extra_other() -> Weight { // Proof Size summary in bytes: - // Measured: `3312` - // Estimated: `8799` - // Minimum execution time: 198_864_000 picoseconds. - Weight::from_parts(203_783_000, 0) - .saturating_add(Weight::from_parts(0, 8799)) - .saturating_add(T::DbWeight::get().reads(17)) - .saturating_add(T::DbWeight::get().writes(13)) + // Measured: `3709` + // Estimated: `6248` + // Minimum execution time: 230_686_000 picoseconds. + Weight::from_parts(237_502_000, 0) + .saturating_add(Weight::from_parts(0, 6248)) + .saturating_add(T::DbWeight::get().reads(20)) + .saturating_add(T::DbWeight::get().writes(14)) } /// Storage: `NominationPools::ClaimPermissions` (r:1 w:0) /// Proof: `NominationPools::ClaimPermissions` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`) @@ -176,8 +190,8 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `1138` // Estimated: `4182` - // Minimum execution time: 70_250_000 picoseconds. - Weight::from_parts(72_231_000, 0) + // Minimum execution time: 70_821_000 picoseconds. + Weight::from_parts(72_356_000, 0) .saturating_add(Weight::from_parts(0, 4182)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -194,7 +208,7 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `NominationPools::GlobalMaxCommission` (r:1 w:0) /// Proof: `NominationPools::GlobalMaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:2 w:1) + /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:0) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -202,10 +216,8 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) /// Storage: `Staking::MinNominatorBond` (r:1 w:0) /// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) @@ -216,13 +228,13 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `NominationPools::CounterForSubPoolsStorage` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn unbond() -> Weight { // Proof Size summary in bytes: - // Measured: `3545` + // Measured: `3341` // Estimated: `8877` - // Minimum execution time: 155_853_000 picoseconds. - Weight::from_parts(161_032_000, 0) + // Minimum execution time: 156_714_000 picoseconds. + Weight::from_parts(158_305_000, 0) .saturating_add(Weight::from_parts(0, 8877)) - .saturating_add(T::DbWeight::get().reads(20)) - .saturating_add(T::DbWeight::get().writes(13)) + .saturating_add(T::DbWeight::get().reads(18)) + .saturating_add(T::DbWeight::get().writes(11)) } /// Storage: `NominationPools::BondedPools` (r:1 w:0) /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) @@ -232,23 +244,25 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:0) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::ReversePoolIdLookup` (r:1 w:0) + /// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `NominationPools::TotalValueLocked` (r:1 w:1) /// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Agents` (r:1 w:1) + /// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 100]`. fn pool_withdraw_unbonded(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1744` - // Estimated: `4764` - // Minimum execution time: 62_933_000 picoseconds. - Weight::from_parts(65_847_171, 0) - .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 1_476 - .saturating_add(Weight::from_parts(59_648, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(7)) + // Measured: `1767` + // Estimated: `4556` + // Minimum execution time: 56_836_000 picoseconds. + Weight::from_parts(59_738_398, 0) + .saturating_add(Weight::from_parts(0, 4556)) + // Standard Error: 1_478 + .saturating_add(Weight::from_parts(60_085, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `NominationPools::PoolMembers` (r:1 w:1) @@ -259,18 +273,24 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) /// Storage: `NominationPools::SubPoolsStorage` (r:1 w:1) /// Proof: `NominationPools::SubPoolsStorage` (`max_values`: None, `max_size`: Some(261), added: 2736, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Agents` (r:1 w:1) + /// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::ReversePoolIdLookup` (r:1 w:0) + /// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `NominationPools::TotalValueLocked` (r:1 w:1) /// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Delegators` (r:1 w:1) + /// Proof: `DelegatedStaking::Delegators` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::CounterForDelegators` (r:1 w:1) + /// Proof: `DelegatedStaking::CounterForDelegators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) /// Storage: `NominationPools::CounterForPoolMembers` (r:1 w:1) /// Proof: `NominationPools::CounterForPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `NominationPools::ClaimPermissions` (r:0 w:1) @@ -278,15 +298,15 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2134` - // Estimated: `4764` - // Minimum execution time: 123_641_000 picoseconds. - Weight::from_parts(127_222_589, 0) - .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 2_493 - .saturating_add(Weight::from_parts(83_361, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(11)) - .saturating_add(T::DbWeight::get().writes(9)) + // Measured: `2405` + // Estimated: `4556` + // Minimum execution time: 136_737_000 picoseconds. + Weight::from_parts(141_757_658, 0) + .saturating_add(Weight::from_parts(0, 4556)) + // Standard Error: 2_609 + .saturating_add(Weight::from_parts(84_538, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(14)) + .saturating_add(T::DbWeight::get().writes(11)) } /// Storage: `NominationPools::PoolMembers` (r:1 w:1) /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`) @@ -296,28 +316,38 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) /// Storage: `NominationPools::SubPoolsStorage` (r:1 w:1) /// Proof: `NominationPools::SubPoolsStorage` (`max_values`: None, `max_size`: Some(261), added: 2736, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Agents` (r:1 w:1) + /// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::SlashingSpans` (r:1 w:0) /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Balances::Locks` (r:2 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:2 w:1) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:2 w:2) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Staking::CounterForVirtualStakers` (r:1 w:1) + /// Proof: `Staking::CounterForVirtualStakers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::Nominators` (r:1 w:0) /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::ReversePoolIdLookup` (r:1 w:1) + /// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `NominationPools::TotalValueLocked` (r:1 w:1) /// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Delegators` (r:1 w:1) + /// Proof: `DelegatedStaking::Delegators` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::CounterForAgents` (r:1 w:1) + /// Proof: `DelegatedStaking::CounterForAgents` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::CounterForDelegators` (r:1 w:1) + /// Proof: `DelegatedStaking::CounterForDelegators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) /// Storage: `NominationPools::CounterForPoolMembers` (r:1 w:1) /// Proof: `NominationPools::CounterForPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `NominationPools::ReversePoolIdLookup` (r:1 w:1) - /// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `NominationPools::CounterForReversePoolIdLookup` (r:1 w:1) /// Proof: `NominationPools::CounterForReversePoolIdLookup` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `NominationPools::RewardPools` (r:1 w:1) @@ -326,6 +356,10 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `NominationPools::CounterForRewardPools` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `NominationPools::CounterForSubPoolsStorage` (r:1 w:1) /// Proof: `NominationPools::CounterForSubPoolsStorage` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Balances::Freezes` (r:1 w:1) + /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:0) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `NominationPools::Metadata` (r:1 w:1) /// Proof: `NominationPools::Metadata` (`max_values`: None, `max_size`: Some(270), added: 2745, mode: `MaxEncodedLen`) /// Storage: `NominationPools::CounterForBondedPools` (r:1 w:1) @@ -337,13 +371,13 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(_s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2453` - // Estimated: `8538` - // Minimum execution time: 219_469_000 picoseconds. - Weight::from_parts(227_526_000, 0) - .saturating_add(Weight::from_parts(0, 8538)) - .saturating_add(T::DbWeight::get().reads(24)) - .saturating_add(T::DbWeight::get().writes(20)) + // Measured: `2809` + // Estimated: `6274` + // Minimum execution time: 241_043_000 picoseconds. + Weight::from_parts(250_578_253, 0) + .saturating_add(Weight::from_parts(0, 6274)) + .saturating_add(T::DbWeight::get().reads(29)) + .saturating_add(T::DbWeight::get().writes(26)) } /// Storage: `NominationPools::LastPoolId` (r:1 w:1) /// Proof: `NominationPools::LastPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -365,16 +399,30 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `NominationPools::MaxPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `NominationPools::CounterForPoolMembers` (r:1 w:1) /// Proof: `NominationPools::CounterForPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:2 w:1) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Agents` (r:2 w:1) + /// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Delegators` (r:2 w:1) + /// Proof: `DelegatedStaking::Delegators` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::CounterForAgents` (r:1 w:1) + /// Proof: `DelegatedStaking::CounterForAgents` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Staking::Bonded` (r:1 w:1) - /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:2 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:2 w:1) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::CounterForDelegators` (r:1 w:1) + /// Proof: `DelegatedStaking::CounterForDelegators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Staking::CounterForVirtualStakers` (r:1 w:1) + /// Proof: `Staking::CounterForVirtualStakers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `NominationPools::TotalValueLocked` (r:1 w:1) /// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `Balances::Freezes` (r:1 w:1) + /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:0) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `NominationPools::RewardPools` (r:1 w:1) /// Proof: `NominationPools::RewardPools` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) /// Storage: `NominationPools::CounterForRewardPools` (r:1 w:1) @@ -391,22 +439,28 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: - // Measured: `1102` - // Estimated: `8538` - // Minimum execution time: 166_466_000 picoseconds. - Weight::from_parts(171_425_000, 0) - .saturating_add(Weight::from_parts(0, 8538)) - .saturating_add(T::DbWeight::get().reads(23)) - .saturating_add(T::DbWeight::get().writes(17)) + // Measured: `1168` + // Estimated: `6196` + // Minimum execution time: 180_902_000 picoseconds. + Weight::from_parts(187_769_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(31)) + .saturating_add(T::DbWeight::get().writes(23)) } /// Storage: `NominationPools::BondedPools` (r:1 w:0) /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::PoolMembers` (r:1 w:0) + /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:0) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::MinNominatorBond` (r:1 w:0) /// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::MinCreateBond` (r:1 w:0) + /// Proof: `NominationPools::MinCreateBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::MinJoinBond` (r:1 w:0) + /// Proof: `NominationPools::MinJoinBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::Nominators` (r:1 w:1) /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) /// Storage: `Staking::MaxNominatorsCount` (r:1 w:0) @@ -426,14 +480,14 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1738` + // Measured: `1921` // Estimated: `4556 + n * (2520 ±0)` - // Minimum execution time: 59_650_000 picoseconds. - Weight::from_parts(60_620_077, 0) + // Minimum execution time: 78_369_000 picoseconds. + Weight::from_parts(79_277_958, 0) .saturating_add(Weight::from_parts(0, 4556)) - // Standard Error: 7_316 - .saturating_add(Weight::from_parts(1_467_406, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(12)) + // Standard Error: 8_343 + .saturating_add(Weight::from_parts(1_493_255, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(15)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(5)) .saturating_add(Weight::from_parts(0, 2520).saturating_mul(n.into())) @@ -446,10 +500,10 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) fn set_state() -> Weight { // Proof Size summary in bytes: - // Measured: `1363` + // Measured: `1406` // Estimated: `4556` - // Minimum execution time: 31_170_000 picoseconds. - Weight::from_parts(32_217_000, 0) + // Minimum execution time: 32_631_000 picoseconds. + Weight::from_parts(33_356_000, 0) .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -465,11 +519,11 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `498` // Estimated: `3735` - // Minimum execution time: 12_603_000 picoseconds. - Weight::from_parts(13_241_702, 0) + // Minimum execution time: 12_514_000 picoseconds. + Weight::from_parts(13_232_732, 0) .saturating_add(Weight::from_parts(0, 3735)) - // Standard Error: 116 - .saturating_add(Weight::from_parts(1_428, 0).saturating_mul(n.into())) + // Standard Error: 150 + .saturating_add(Weight::from_parts(2_371, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -489,8 +543,8 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_608_000 picoseconds. - Weight::from_parts(3_801_000, 0) + // Minimum execution time: 3_107_000 picoseconds. + Weight::from_parts(3_255_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(6)) } @@ -500,18 +554,22 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `498` // Estimated: `3719` - // Minimum execution time: 16_053_000 picoseconds. - Weight::from_parts(16_473_000, 0) + // Minimum execution time: 16_568_000 picoseconds. + Weight::from_parts(17_019_000, 0) .saturating_add(Weight::from_parts(0, 3719)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `NominationPools::BondedPools` (r:1 w:0) /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::PoolMembers` (r:1 w:0) + /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:0) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::MinNominatorBond` (r:1 w:0) + /// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::Nominators` (r:1 w:1) @@ -526,12 +584,12 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill() -> Weight { // Proof Size summary in bytes: - // Measured: `1901` + // Measured: `2138` // Estimated: `4556` - // Minimum execution time: 57_251_000 picoseconds. - Weight::from_parts(59_390_000, 0) + // Minimum execution time: 73_717_000 picoseconds. + Weight::from_parts(77_030_000, 0) .saturating_add(Weight::from_parts(0, 4556)) - .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `NominationPools::BondedPools` (r:1 w:1) @@ -546,8 +604,8 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `770` // Estimated: `3719` - // Minimum execution time: 29_888_000 picoseconds. - Weight::from_parts(31_056_000, 0) + // Minimum execution time: 30_770_000 picoseconds. + Weight::from_parts(31_556_000, 0) .saturating_add(Weight::from_parts(0, 3719)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -560,8 +618,8 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `538` // Estimated: `3719` - // Minimum execution time: 15_769_000 picoseconds. - Weight::from_parts(16_579_000, 0) + // Minimum execution time: 16_257_000 picoseconds. + Weight::from_parts(16_891_000, 0) .saturating_add(Weight::from_parts(0, 3719)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -572,8 +630,8 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `498` // Estimated: `3719` - // Minimum execution time: 15_385_000 picoseconds. - Weight::from_parts(16_402_000, 0) + // Minimum execution time: 16_548_000 picoseconds. + Weight::from_parts(18_252_000, 0) .saturating_add(Weight::from_parts(0, 3719)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -584,8 +642,8 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `498` // Estimated: `3719` - // Minimum execution time: 14_965_000 picoseconds. - Weight::from_parts(15_548_000, 0) + // Minimum execution time: 16_085_000 picoseconds. + Weight::from_parts(17_218_000, 0) .saturating_add(Weight::from_parts(0, 3719)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -598,8 +656,8 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `508` // Estimated: `4182` - // Minimum execution time: 13_549_000 picoseconds. - Weight::from_parts(14_307_000, 0) + // Minimum execution time: 13_648_000 picoseconds. + Weight::from_parts(13_990_000, 0) .saturating_add(Weight::from_parts(0, 4182)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -616,8 +674,8 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `968` // Estimated: `3719` - // Minimum execution time: 60_153_000 picoseconds. - Weight::from_parts(61_369_000, 0) + // Minimum execution time: 60_321_000 picoseconds. + Weight::from_parts(61_512_000, 0) .saturating_add(Weight::from_parts(0, 3719)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -632,12 +690,135 @@ impl pallet_nomination_pools::WeightInfo for WeightInfo /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) fn adjust_pool_deposit() -> Weight { // Proof Size summary in bytes: - // Measured: `867` + // Measured: `876` // Estimated: `4764` - // Minimum execution time: 64_985_000 picoseconds. - Weight::from_parts(66_616_000, 0) + // Minimum execution time: 65_609_000 picoseconds. + Weight::from_parts(67_320_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `NominationPools::PoolMembers` (r:1 w:0) + /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Agents` (r:1 w:1) + /// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Delegators` (r:1 w:1) + /// Proof: `DelegatedStaking::Delegators` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::BondedPools` (r:1 w:0) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:1 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:1 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::SubPoolsStorage` (r:1 w:0) + /// Proof: `NominationPools::SubPoolsStorage` (`max_values`: None, `max_size`: Some(261), added: 2736, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn apply_slash() -> Weight { + // Proof Size summary in bytes: + // Measured: `3328` + // Estimated: `4556` + // Minimum execution time: 99_605_000 picoseconds. + Weight::from_parts(101_986_000, 0) + .saturating_add(Weight::from_parts(0, 4556)) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `NominationPools::PoolMembers` (r:1 w:0) + /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Agents` (r:1 w:0) + /// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Delegators` (r:1 w:0) + /// Proof: `DelegatedStaking::Delegators` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::BondedPools` (r:1 w:0) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:1 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:1 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::SubPoolsStorage` (r:1 w:0) + /// Proof: `NominationPools::SubPoolsStorage` (`max_values`: None, `max_size`: Some(261), added: 2736, mode: `MaxEncodedLen`) + fn apply_slash_fail() -> Weight { + // Proof Size summary in bytes: + // Measured: `3070` + // Estimated: `4556` + // Minimum execution time: 58_103_000 picoseconds. + Weight::from_parts(59_680_000, 0) + .saturating_add(Weight::from_parts(0, 4556)) + .saturating_add(T::DbWeight::get().reads(7)) + } + /// Storage: `Staking::Bonded` (r:1 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Validators` (r:1 w:0) + /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) + /// Storage: `Staking::Nominators` (r:1 w:0) + /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Agents` (r:1 w:1) + /// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Delegators` (r:2 w:1) + /// Proof: `DelegatedStaking::Delegators` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::CounterForAgents` (r:1 w:1) + /// Proof: `DelegatedStaking::CounterForAgents` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:1 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) + /// Storage: `Balances::Freezes` (r:1 w:0) + /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Staking::CounterForVirtualStakers` (r:1 w:1) + /// Proof: `Staking::CounterForVirtualStakers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::CounterForDelegators` (r:1 w:1) + /// Proof: `DelegatedStaking::CounterForDelegators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::Payee` (r:0 w:1) + /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + fn pool_migrate() -> Weight { + // Proof Size summary in bytes: + // Measured: `1359` + // Estimated: `6196` + // Minimum execution time: 144_098_000 picoseconds. + Weight::from_parts(146_590_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(16)) + .saturating_add(T::DbWeight::get().writes(11)) + } + /// Storage: `NominationPools::PoolMembers` (r:1 w:0) + /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::BondedPools` (r:1 w:0) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:2 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:1 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::SubPoolsStorage` (r:1 w:0) + /// Proof: `NominationPools::SubPoolsStorage` (`max_values`: None, `max_size`: Some(261), added: 2736, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::MinJoinBond` (r:1 w:0) + /// Proof: `NominationPools::MinJoinBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Delegators` (r:2 w:2) + /// Proof: `DelegatedStaking::Delegators` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Agents` (r:2 w:0) + /// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:2 w:2) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::CounterForDelegators` (r:1 w:1) + /// Proof: `DelegatedStaking::CounterForDelegators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn migrate_delegation() -> Weight { + // Proof Size summary in bytes: + // Measured: `2275` + // Estimated: `6180` + // Minimum execution time: 148_594_000 picoseconds. + Weight::from_parts(152_119_000, 0) + .saturating_add(Weight::from_parts(0, 6180)) + .saturating_add(T::DbWeight::get().reads(15)) + .saturating_add(T::DbWeight::get().writes(6)) + } } diff --git a/prdoc/pr_3905.prdoc b/prdoc/pr_3905.prdoc new file mode 100644 index 0000000000000..d1c03650c9b23 --- /dev/null +++ b/prdoc/pr_3905.prdoc @@ -0,0 +1,25 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Allows Nomination Pool to use different staking strategies including a new DelegateStake strategy. + +doc: + - audience: Runtime Dev + description: | + This PR introduces a new staking strategy called `DelegateStake`. This strategy allows the nomination pool to + delegate its stake to a validator, that is, funds are locked in user account itself instead of being transferred + to the pool account. Includes migration of pools to this strategy for Westend. + +crates: + - name: pallet-nomination-pools + bump: major + - name: pallet-nomination-pools-benchmarking + bump: major + - name: sp-staking + bump: patch + - name: pallet-staking + bump: patch + - name: pallet-delegated-staking + bump: patch + - name: westend-runtime + bump: major diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 5067085e8edda..617088ffe1ff2 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -910,7 +910,7 @@ impl pallet_nomination_pools::Config for Runtime { type RewardCounter = FixedU128; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; - type Staking = Staking; + type StakeAdapter = pallet_nomination_pools::adapter::TransferStake; type PostUnbondingPoolsWindow = PostUnbondPoolsWindow; type MaxMetadataLen = ConstU32<256>; type MaxUnbonding = ConstU32<8>; diff --git a/substrate/frame/delegated-staking/Cargo.toml b/substrate/frame/delegated-staking/Cargo.toml index 4a48988271101..3b122dc2e26c3 100644 --- a/substrate/frame/delegated-staking/Cargo.toml +++ b/substrate/frame/delegated-staking/Cargo.toml @@ -26,6 +26,7 @@ sp-io = { path = "../../primitives/io" } substrate-test-utils = { path = "../../test-utils" } sp-tracing = { path = "../../primitives/tracing" } pallet-staking = { path = "../staking" } +pallet-nomination-pools = { path = "../nomination-pools" } pallet-balances = { path = "../balances" } pallet-timestamp = { path = "../timestamp" } pallet-staking-reward-curve = { path = "../staking/reward-curve" } @@ -39,6 +40,7 @@ std = [ "frame-support/std", "frame-system/std", "pallet-balances/std", + "pallet-nomination-pools/std", "pallet-staking/std", "pallet-timestamp/std", "scale-info/std", @@ -53,6 +55,7 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + "pallet-nomination-pools/runtime-benchmarks", "pallet-staking/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "sp-runtime/runtime-benchmarks", @@ -63,6 +66,7 @@ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "pallet-balances/try-runtime", + "pallet-nomination-pools/try-runtime", "pallet-staking/try-runtime", "pallet-timestamp/try-runtime", "sp-runtime/try-runtime", diff --git a/substrate/frame/delegated-staking/src/impls.rs b/substrate/frame/delegated-staking/src/impls.rs index b1945b0ce3769..032f612064285 100644 --- a/substrate/frame/delegated-staking/src/impls.rs +++ b/substrate/frame/delegated-staking/src/impls.rs @@ -109,7 +109,6 @@ impl DelegationMigrator for Pallet { reward_account.clone(), ) } - fn migrate_delegation( agent: &Self::AccountId, delegator: &Self::AccountId, @@ -121,6 +120,24 @@ impl DelegationMigrator for Pallet { value, ) } + + /// Only used for testing. + #[cfg(feature = "runtime-benchmarks")] + fn drop_agent(agent: &T::AccountId) { + >::remove(agent); + >::iter() + .filter(|(_, delegation)| delegation.agent == *agent) + .for_each(|(delegator, _)| { + let _ = T::Currency::release_all( + &HoldReason::StakingDelegation.into(), + &delegator, + Precision::BestEffort, + ); + >::remove(&delegator); + }); + + T::CoreStaking::migrate_to_direct_staker(agent); + } } impl OnStakingUpdate> for Pallet { diff --git a/substrate/frame/delegated-staking/src/lib.rs b/substrate/frame/delegated-staking/src/lib.rs index 210f69d9c839d..8581a4a981fe4 100644 --- a/substrate/frame/delegated-staking/src/lib.rs +++ b/substrate/frame/delegated-staking/src/lib.rs @@ -165,7 +165,10 @@ use frame_system::{ensure_signed, pallet_prelude::*, RawOrigin}; pub mod pallet { use super::*; + /// The in-code storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(PhantomData); #[pallet::config] @@ -245,6 +248,8 @@ pub mod pallet { Released { agent: T::AccountId, delegator: T::AccountId, amount: BalanceOf }, /// Funds slashed from a delegator. Slashed { agent: T::AccountId, delegator: T::AccountId, amount: BalanceOf }, + /// Unclaimed delegation funds migrated to delegator. + MigratedDelegation { agent: T::AccountId, delegator: T::AccountId, amount: BalanceOf }, } /// Map of Delegators to their `Delegation`. @@ -371,7 +376,7 @@ pub mod pallet { ensure!(Self::is_agent(&agent), Error::::NotAgent); // and has enough delegated balance to migrate. - let proxy_delegator = Self::sub_account(AccountType::ProxyDelegator, agent); + let proxy_delegator = Self::generate_proxy_delegator(agent); let balance_remaining = Self::held_balance_of(&proxy_delegator); ensure!(balance_remaining >= amount, Error::::NotEnoughFunds); @@ -422,6 +427,12 @@ pub mod pallet { } impl Pallet { + /// Derive an account from the migrating agent account where the unclaimed delegation funds + /// are held. + pub fn generate_proxy_delegator(agent: T::AccountId) -> T::AccountId { + Self::sub_account(AccountType::ProxyDelegator, agent) + } + /// Derive a (keyless) pot account from the given agent account and account type. pub(crate) fn sub_account(account_type: AccountType, agent: T::AccountId) -> T::AccountId { T::PalletId::get().into_sub_account_truncating((account_type, agent.clone())) @@ -464,7 +475,7 @@ impl Pallet { // We create a proxy delegator that will keep all the delegation funds until funds are // transferred to actual delegator. - let proxy_delegator = Self::sub_account(AccountType::ProxyDelegator, who.clone()); + let proxy_delegator = Self::generate_proxy_delegator(who.clone()); // Keep proxy delegator alive until all funds are migrated. frame_system::Pallet::::inc_providers(&proxy_delegator); @@ -646,9 +657,9 @@ impl Pallet { !Self::is_delegator(destination_delegator) && !Self::is_agent(destination_delegator) ); + let agent = source_delegation.agent.clone(); // update delegations - Delegation::::new(&source_delegation.agent, amount) - .update_or_kill(destination_delegator); + Delegation::::new(&agent, amount).update_or_kill(destination_delegator); source_delegation.amount = source_delegation .amount @@ -684,6 +695,12 @@ impl Pallet { // hold the funds again in the new delegator account. T::Currency::hold(&HoldReason::StakingDelegation.into(), destination_delegator, amount)?; + Self::deposit_event(Event::::MigratedDelegation { + agent, + delegator: destination_delegator.clone(), + amount, + }); + Ok(()) } diff --git a/substrate/frame/delegated-staking/src/mock.rs b/substrate/frame/delegated-staking/src/mock.rs index 21a9fe6b22700..b9eaffb970e13 100644 --- a/substrate/frame/delegated-staking/src/mock.rs +++ b/substrate/frame/delegated-staking/src/mock.rs @@ -32,6 +32,8 @@ use frame_election_provider_support::{ }; use frame_support::dispatch::RawOrigin; use pallet_staking::{ActiveEra, ActiveEraInfo, CurrentEra}; +use sp_core::U256; +use sp_runtime::traits::Convert; use sp_staking::{Stake, StakingInterface}; pub type T = Runtime; @@ -129,7 +131,7 @@ impl pallet_staking::Config for Runtime { type NominationsQuota = pallet_staking::FixedNominationsQuota<16>; type MaxUnlockingChunks = ConstU32<10>; type MaxControllersInDeprecationBatch = ConstU32<100>; - type EventListeners = DelegatedStaking; + type EventListeners = (Pools, DelegatedStaking); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; @@ -149,8 +151,39 @@ impl delegated_staking::Config for Runtime { type CoreStaking = Staking; } +pub struct BalanceToU256; +impl Convert for BalanceToU256 { + fn convert(n: Balance) -> U256 { + n.into() + } +} +pub struct U256ToBalance; +impl Convert for U256ToBalance { + fn convert(n: U256) -> Balance { + n.try_into().unwrap() + } +} + parameter_types! { pub static MaxUnbonding: u32 = 8; + pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); +} +impl pallet_nomination_pools::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type Currency = Balances; + type RuntimeFreezeReason = RuntimeFreezeReason; + type RewardCounter = sp_runtime::FixedU128; + type BalanceToU256 = BalanceToU256; + type U256ToBalance = U256ToBalance; + type PostUnbondingPoolsWindow = ConstU32<2>; + type PalletId = PoolsPalletId; + type MaxMetadataLen = ConstU32<256>; + type MaxUnbonding = MaxUnbonding; + type MaxPointsToBalance = frame_support::traits::ConstU8<10>; + type StakeAdapter = + pallet_nomination_pools::adapter::DelegateStake; + type AdminOrigin = frame_system::EnsureRoot; } frame_support::construct_runtime!( @@ -159,6 +192,7 @@ frame_support::construct_runtime!( Timestamp: pallet_timestamp, Balances: pallet_balances, Staking: pallet_staking, + Pools: pallet_nomination_pools, DelegatedStaking: delegated_staking, } ); @@ -297,9 +331,16 @@ pub(crate) fn get_agent(agent: &AccountId) -> Agent { parameter_types! { static ObservedEventsDelegatedStaking: usize = 0; + static ObservedEventsPools: usize = 0; +} + +pub(crate) fn pool_events_since_last_call() -> Vec> { + let events = System::read_events_for_pallet::>(); + let already_seen = ObservedEventsPools::get(); + ObservedEventsPools::set(events.len()); + events.into_iter().skip(already_seen).collect() } -#[allow(unused)] pub(crate) fn events_since_last_call() -> Vec> { let events = System::read_events_for_pallet::>(); let already_seen = ObservedEventsDelegatedStaking::get(); diff --git a/substrate/frame/delegated-staking/src/tests.rs b/substrate/frame/delegated-staking/src/tests.rs index 1f36f655beb86..6b68726b274cb 100644 --- a/substrate/frame/delegated-staking/src/tests.rs +++ b/substrate/frame/delegated-staking/src/tests.rs @@ -20,8 +20,9 @@ use super::*; use crate::mock::*; use frame_support::{assert_noop, assert_ok, traits::fungible::InspectHold}; +use pallet_nomination_pools::{Error as PoolsError, Event as PoolsEvent}; use pallet_staking::Error as StakingError; -use sp_staking::DelegationInterface; +use sp_staking::{DelegationInterface, StakerStatus}; #[test] fn create_an_agent_with_first_delegator() { @@ -623,7 +624,7 @@ mod staking_integration { // to migrate, nominator needs to set an account as a proxy delegator where staked funds // will be moved and delegated back to this old nominator account. This should be funded // with at least ED. - let proxy_delegator = DelegatedStaking::sub_account(AccountType::ProxyDelegator, 200); + let proxy_delegator = DelegatedStaking::generate_proxy_delegator(200); assert_ok!(DelegatedStaking::migrate_to_agent(RawOrigin::Signed(200).into(), 201)); @@ -683,3 +684,501 @@ mod staking_integration { }); } } + +mod pool_integration { + use super::*; + use pallet_nomination_pools::{BondExtra, BondedPools, PoolState}; + + #[test] + fn create_pool_test() { + ExtBuilder::default().build_and_execute(|| { + let creator: AccountId = 100; + fund(&creator, 500); + let delegate_amount = 200; + + // nothing held initially + assert_eq!(DelegatedStaking::held_balance_of(&creator), 0); + + // create pool + assert_ok!(Pools::create( + RawOrigin::Signed(creator).into(), + delegate_amount, + creator, + creator, + creator + )); + + // correct amount is locked in depositor's account. + assert_eq!(DelegatedStaking::held_balance_of(&creator), delegate_amount); + + let pool_account = Pools::generate_bonded_account(1); + let agent = get_agent(&pool_account); + + // verify state + assert_eq!(agent.ledger.effective_balance(), delegate_amount); + assert_eq!(agent.available_to_bond(), 0); + assert_eq!(agent.total_unbonded(), 0); + }); + } + + #[test] + fn join_pool() { + ExtBuilder::default().build_and_execute(|| { + // create a pool + let pool_id = create_pool(100, 200); + // keep track of staked amount. + let mut staked_amount: Balance = 200; + + // fund delegator + let delegator: AccountId = 300; + fund(&delegator, 500); + // nothing held initially + assert_eq!(DelegatedStaking::held_balance_of(&delegator), 0); + + // delegator joins pool + assert_ok!(Pools::join(RawOrigin::Signed(delegator).into(), 100, pool_id)); + staked_amount += 100; + + // correct amount is locked in depositor's account. + assert_eq!(DelegatedStaking::held_balance_of(&delegator), 100); + + // delegator is not actively exposed to core staking. + assert_eq!(Staking::status(&delegator), Err(StakingError::::NotStash.into())); + + let pool_agent = get_agent(&Pools::generate_bonded_account(1)); + // verify state + assert_eq!(pool_agent.ledger.effective_balance(), staked_amount); + assert_eq!(pool_agent.bonded_stake(), staked_amount); + assert_eq!(pool_agent.available_to_bond(), 0); + assert_eq!(pool_agent.total_unbonded(), 0); + + // cannot reap agent in staking. + assert_noop!( + Staking::reap_stash(RuntimeOrigin::signed(100), pool_agent.key, 0), + StakingError::::VirtualStakerNotAllowed + ); + + // let a bunch of delegators join this pool + for i in 301..350 { + fund(&i, 500); + assert_ok!(Pools::join(RawOrigin::Signed(i).into(), 100 + i, pool_id)); + staked_amount += 100 + i; + assert_eq!(DelegatedStaking::held_balance_of(&i), 100 + i); + } + + let pool_agent = pool_agent.refresh().unwrap(); + assert_eq!(pool_agent.ledger.effective_balance(), staked_amount); + assert_eq!(pool_agent.bonded_stake(), staked_amount); + assert_eq!(pool_agent.available_to_bond(), 0); + assert_eq!(pool_agent.total_unbonded(), 0); + }); + } + + #[test] + fn bond_extra_to_pool() { + ExtBuilder::default().build_and_execute(|| { + let pool_id = create_pool(100, 200); + add_delegators_to_pool(pool_id, (300..310).collect(), 100); + let mut staked_amount = 200 + 100 * 10; + assert_eq!(get_pool_agent(pool_id).bonded_stake(), staked_amount); + + // bond extra to pool + for i in 300..310 { + assert_ok!(Pools::bond_extra( + RawOrigin::Signed(i).into(), + BondExtra::FreeBalance(50) + )); + staked_amount += 50; + assert_eq!(get_pool_agent(pool_id).bonded_stake(), staked_amount); + } + }); + } + + #[test] + fn claim_pool_rewards() { + ExtBuilder::default().build_and_execute(|| { + let creator = 100; + let creator_stake = 1000; + let pool_id = create_pool(creator, creator_stake); + add_delegators_to_pool(pool_id, (300..310).collect(), 100); + add_delegators_to_pool(pool_id, (310..320).collect(), 200); + let total_staked = creator_stake + 100 * 10 + 200 * 10; + + // give some rewards + let reward_acc = Pools::generate_reward_account(pool_id); + let reward_amount = 1000; + fund(&reward_acc, reward_amount); + + // claim rewards + for i in 300..320 { + let pre_balance = Balances::free_balance(i); + let delegator_staked_balance = DelegatedStaking::held_balance_of(&i); + // payout reward + assert_ok!(Pools::claim_payout(RawOrigin::Signed(i).into())); + + let reward = Balances::free_balance(i) - pre_balance; + assert_eq!(reward, delegator_staked_balance * reward_amount / total_staked); + } + + // payout creator + let pre_balance = Balances::free_balance(creator); + assert_ok!(Pools::claim_payout(RawOrigin::Signed(creator).into())); + // verify they are paid out correctly + let reward = Balances::free_balance(creator) - pre_balance; + assert_eq!(reward, creator_stake * reward_amount / total_staked); + + // reward account should only have left minimum balance after paying out everyone. + assert_eq!(Balances::free_balance(reward_acc), ExistentialDeposit::get()); + }); + } + + #[test] + fn withdraw_from_pool() { + ExtBuilder::default().build_and_execute(|| { + // initial era + start_era(1); + + let pool_id = create_pool(100, 1000); + let bond_amount = 200; + add_delegators_to_pool(pool_id, (300..310).collect(), bond_amount); + let total_staked = 1000 + bond_amount * 10; + let pool_acc = Pools::generate_bonded_account(pool_id); + + start_era(2); + // nothing to release yet. + assert_noop!( + Pools::withdraw_unbonded(RawOrigin::Signed(301).into(), 301, 0), + PoolsError::::SubPoolsNotFound + ); + + // 301 wants to unbond 50 in era 2, withdrawable in era 5. + assert_ok!(Pools::unbond(RawOrigin::Signed(301).into(), 301, 50)); + + // 302 wants to unbond 100 in era 3, withdrawable in era 6. + start_era(3); + assert_ok!(Pools::unbond(RawOrigin::Signed(302).into(), 302, 100)); + + // 303 wants to unbond 200 in era 4, withdrawable in era 7. + start_era(4); + assert_ok!(Pools::unbond(RawOrigin::Signed(303).into(), 303, 200)); + + // active stake is now reduced.. + let expected_active = total_staked - (50 + 100 + 200); + assert!(eq_stake(pool_acc, total_staked, expected_active)); + + // nothing to withdraw at era 4 + for i in 301..310 { + assert_noop!( + Pools::withdraw_unbonded(RawOrigin::Signed(i).into(), i, 0), + PoolsError::::CannotWithdrawAny + ); + } + + assert!(eq_stake(pool_acc, total_staked, expected_active)); + + start_era(5); + // at era 5, 301 can withdraw. + + System::reset_events(); + let held_301 = DelegatedStaking::held_balance_of(&301); + let free_301 = Balances::free_balance(301); + + assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(301).into(), 301, 0)); + assert_eq!( + events_since_last_call(), + vec![Event::Released { agent: pool_acc, delegator: 301, amount: 50 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Withdrawn { member: 301, pool_id, balance: 50, points: 50 }] + ); + assert_eq!(DelegatedStaking::held_balance_of(&301), held_301 - 50); + assert_eq!(Balances::free_balance(301), free_301 + 50); + + start_era(7); + // era 7 both delegators can withdraw + assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(302).into(), 302, 0)); + assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(303).into(), 303, 0)); + + assert_eq!( + events_since_last_call(), + vec![ + Event::Released { agent: pool_acc, delegator: 302, amount: 100 }, + Event::Released { agent: pool_acc, delegator: 303, amount: 200 }, + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Withdrawn { member: 302, pool_id, balance: 100, points: 100 }, + PoolsEvent::Withdrawn { member: 303, pool_id, balance: 200, points: 200 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 303 }, + ] + ); + + // 303 is killed + assert!(!Delegators::::contains_key(303)); + }); + } + + #[test] + fn pool_withdraw_unbonded() { + ExtBuilder::default().build_and_execute(|| { + // initial era + start_era(1); + let pool_id = create_pool(100, 1000); + add_delegators_to_pool(pool_id, (300..310).collect(), 200); + + start_era(2); + // 1000 tokens to be unbonded in era 5. + for i in 300..310 { + assert_ok!(Pools::unbond(RawOrigin::Signed(i).into(), i, 100)); + } + + start_era(3); + // 500 tokens to be unbonded in era 6. + for i in 300..310 { + assert_ok!(Pools::unbond(RawOrigin::Signed(i).into(), i, 50)); + } + + start_era(5); + // withdraw pool should withdraw 1000 tokens + assert_ok!(Pools::pool_withdraw_unbonded(RawOrigin::Signed(100).into(), pool_id, 0)); + assert_eq!(get_pool_agent(pool_id).total_unbonded(), 1000); + + start_era(6); + // should withdraw 500 more + assert_ok!(Pools::pool_withdraw_unbonded(RawOrigin::Signed(100).into(), pool_id, 0)); + assert_eq!(get_pool_agent(pool_id).total_unbonded(), 1000 + 500); + + start_era(7); + // Nothing to withdraw, still at 1500. + assert_ok!(Pools::pool_withdraw_unbonded(RawOrigin::Signed(100).into(), pool_id, 0)); + assert_eq!(get_pool_agent(pool_id).total_unbonded(), 1500); + }); + } + + #[test] + fn update_nominations() { + ExtBuilder::default().build_and_execute(|| { + start_era(1); + // can't nominate for non-existent pool + assert_noop!( + Pools::nominate(RawOrigin::Signed(100).into(), 1, vec![99]), + PoolsError::::PoolNotFound + ); + + let pool_id = create_pool(100, 1000); + let pool_acc = Pools::generate_bonded_account(pool_id); + assert_ok!(Pools::nominate(RawOrigin::Signed(100).into(), 1, vec![20, 21, 22])); + assert!(Staking::status(&pool_acc) == Ok(StakerStatus::Nominator(vec![20, 21, 22]))); + + start_era(3); + assert_ok!(Pools::nominate(RawOrigin::Signed(100).into(), 1, vec![18, 19, 22])); + assert!(Staking::status(&pool_acc) == Ok(StakerStatus::Nominator(vec![18, 19, 22]))); + }); + } + + #[test] + fn destroy_pool() { + ExtBuilder::default().build_and_execute(|| { + start_era(1); + let creator = 100; + let creator_stake = 1000; + let pool_id = create_pool(creator, creator_stake); + add_delegators_to_pool(pool_id, (300..310).collect(), 200); + + start_era(3); + // lets destroy the pool + assert_ok!(Pools::set_state( + RawOrigin::Signed(creator).into(), + pool_id, + PoolState::Destroying + )); + assert_ok!(Pools::chill(RawOrigin::Signed(creator).into(), pool_id)); + + // unbond all members by the creator/admin + for i in 300..310 { + assert_ok!(Pools::unbond(RawOrigin::Signed(creator).into(), i, 200)); + } + + start_era(6); + // withdraw all members by the creator/admin + for i in 300..310 { + assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(creator).into(), i, 0)); + } + + // unbond creator + assert_ok!(Pools::unbond(RawOrigin::Signed(creator).into(), creator, creator_stake)); + + start_era(9); + System::reset_events(); + // Withdraw self + assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(creator).into(), creator, 0)); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Withdrawn { + member: creator, + pool_id, + balance: creator_stake, + points: creator_stake, + }, + PoolsEvent::MemberRemoved { pool_id, member: creator }, + PoolsEvent::Destroyed { pool_id }, + ] + ); + + // Make sure all data is cleaned up. + assert!(!Agents::::contains_key(Pools::generate_bonded_account(pool_id))); + assert!(!System::account_exists(&Pools::generate_bonded_account(pool_id))); + assert!(!Delegators::::contains_key(creator)); + for i in 300..310 { + assert!(!Delegators::::contains_key(i)); + } + }); + } + + #[test] + fn pool_partially_slashed() { + ExtBuilder::default().build_and_execute(|| { + start_era(1); + let creator = 100; + let creator_stake = 500; + let pool_id = create_pool(creator, creator_stake); + let delegator_stake = 100; + add_delegators_to_pool(pool_id, (300..306).collect(), delegator_stake); + let pool_acc = Pools::generate_bonded_account(pool_id); + + let total_staked = creator_stake + delegator_stake * 6; + assert_eq!(Staking::stake(&pool_acc).unwrap().total, total_staked); + + // lets unbond a delegator each in next eras (2, 3, 4). + start_era(2); + assert_ok!(Pools::unbond(RawOrigin::Signed(300).into(), 300, delegator_stake)); + + start_era(3); + assert_ok!(Pools::unbond(RawOrigin::Signed(301).into(), 301, delegator_stake)); + + start_era(4); + assert_ok!(Pools::unbond(RawOrigin::Signed(302).into(), 302, delegator_stake)); + System::reset_events(); + + // slash the pool at era 3 + assert_eq!( + BondedPools::::get(1).unwrap().points, + creator_stake + delegator_stake * 6 - delegator_stake * 3 + ); + pallet_staking::slashing::do_slash::( + &pool_acc, + 500, + &mut Default::default(), + &mut Default::default(), + 3, + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + // 300 did not get slashed as all as it unbonded in an era before slash. + // 301 got slashed 50% of 100 = 50. + PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 6, balance: 50 }, + // 302 got slashed 50% of 100 = 50. + PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 7, balance: 50 }, + // Rest of the pool slashed 50% of 800 = 400. + PoolsEvent::PoolSlashed { pool_id: 1, balance: 400 }, + ] + ); + + // slash is lazy and balance is still locked in user's accounts. + assert_eq!(DelegatedStaking::held_balance_of(&creator), creator_stake); + for i in 300..306 { + assert_eq!(DelegatedStaking::held_balance_of(&i), delegator_stake); + } + assert_eq!( + get_pool_agent(pool_id).ledger.effective_balance(), + Staking::total_stake(&pool_acc).unwrap() + ); + + // pending slash is book kept. + assert_eq!(get_pool_agent(pool_id).ledger.pending_slash, 500); + + // go in some distant future era. + start_era(10); + System::reset_events(); + + // 300 is not slashed and can withdraw all balance. + assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(300).into(), 300, 1)); + assert_eq!( + events_since_last_call(), + vec![Event::Released { agent: pool_acc, delegator: 300, amount: 100 }] + ); + assert_eq!(get_pool_agent(pool_id).ledger.pending_slash, 500); + + // withdraw the other two delegators (301 and 302) who were unbonding. + for i in 301..=302 { + let pre_balance = Balances::free_balance(i); + let pre_pending_slash = get_pool_agent(pool_id).ledger.pending_slash; + assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(i).into(), i, 0)); + assert_eq!( + events_since_last_call(), + vec![ + Event::Slashed { agent: pool_acc, delegator: i, amount: 50 }, + Event::Released { agent: pool_acc, delegator: i, amount: 50 }, + ] + ); + assert_eq!(get_pool_agent(pool_id).ledger.pending_slash, pre_pending_slash - 50); + assert_eq!(DelegatedStaking::held_balance_of(&i), 0); + assert_eq!(Balances::free_balance(i) - pre_balance, 50); + } + + // let's update all the slash + let slash_reporter = 99; + // give our reporter some balance. + fund(&slash_reporter, 100); + + for i in 303..306 { + let pre_pending_slash = get_pool_agent(pool_id).ledger.pending_slash; + assert_ok!(Pools::apply_slash(RawOrigin::Signed(slash_reporter).into(), i)); + + // each member is slashed 50% of 100 = 50. + assert_eq!(get_pool_agent(pool_id).ledger.pending_slash, pre_pending_slash - 50); + // left with 50. + assert_eq!(DelegatedStaking::held_balance_of(&i), 50); + } + // reporter is paid SlashRewardFraction of the slash, i.e. 10% of 50 = 5 + assert_eq!(Balances::free_balance(slash_reporter), 100 + 5 * 3); + // slash creator + assert_ok!(Pools::apply_slash(RawOrigin::Signed(slash_reporter).into(), creator)); + // all slash should be applied now. + assert_eq!(get_pool_agent(pool_id).ledger.pending_slash, 0); + // for creator, 50% of stake should be slashed (250), 10% of which should go to reporter + // (25). + assert_eq!(Balances::free_balance(slash_reporter), 115 + 25); + }); + } + + fn create_pool(creator: AccountId, amount: Balance) -> u32 { + fund(&creator, amount * 2); + assert_ok!(Pools::create( + RawOrigin::Signed(creator).into(), + amount, + creator, + creator, + creator + )); + + pallet_nomination_pools::LastPoolId::::get() + } + + fn add_delegators_to_pool(pool_id: u32, delegators: Vec, amount: Balance) { + for delegator in delegators { + fund(&delegator, amount * 2); + assert_ok!(Pools::join(RawOrigin::Signed(delegator).into(), amount, pool_id)); + } + } + + fn get_pool_agent(pool_id: u32) -> Agent { + get_agent(&Pools::generate_bonded_account(pool_id)) + } +} diff --git a/substrate/frame/delegated-staking/src/types.rs b/substrate/frame/delegated-staking/src/types.rs index 0bfc23281dfe6..958d81c294aa9 100644 --- a/substrate/frame/delegated-staking/src/types.rs +++ b/substrate/frame/delegated-staking/src/types.rs @@ -279,7 +279,6 @@ impl Agent { /// This is similar to [Self::available_to_bond] except it also includes `unclaimed_withdrawals` /// of `Agent`. #[cfg(test)] - #[allow(unused)] pub(crate) fn total_unbonded(&self) -> BalanceOf { let bonded_stake = self.bonded_stake(); diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs index c00bb66ea1304..2b1f1335c6fe8 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs @@ -322,7 +322,7 @@ fn automatic_unbonding_pools() { let init_free_balance_2 = Balances::free_balance(2); let init_free_balance_3 = Balances::free_balance(3); - let pool_bonded_account = Pools::create_bonded_account(1); + let pool_bonded_account = Pools::generate_bonded_account(1); // creates a pool with 5 bonded, owned by 1. assert_ok!(Pools::create(RuntimeOrigin::signed(1), 5, 1, 1, 1)); diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs index 8f1775a7e5951..a9512bef2d50b 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs @@ -274,7 +274,7 @@ impl pallet_nomination_pools::Config for Runtime { type RewardCounter = sp_runtime::FixedU128; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; - type Staking = Staking; + type StakeAdapter = pallet_nomination_pools::adapter::TransferStake; type PostUnbondingPoolsWindow = ConstU32<2>; type PalletId = PoolsPalletId; type MaxMetadataLen = ConstU32<256>; diff --git a/substrate/frame/nomination-pools/benchmarking/Cargo.toml b/substrate/frame/nomination-pools/benchmarking/Cargo.toml index 3186bce5164cc..3f9463a9c429b 100644 --- a/substrate/frame/nomination-pools/benchmarking/Cargo.toml +++ b/substrate/frame/nomination-pools/benchmarking/Cargo.toml @@ -27,6 +27,7 @@ frame-support = { path = "../../support", default-features = false } frame-system = { path = "../../system", default-features = false } pallet-bags-list = { path = "../../bags-list", default-features = false } pallet-staking = { path = "../../staking", default-features = false } +pallet-delegated-staking = { path = "../../delegated-staking", default-features = false } pallet-nomination-pools = { path = "..", default-features = false } # Substrate Primitives @@ -53,6 +54,7 @@ std = [ "frame-system/std", "pallet-bags-list/std", "pallet-balances/std", + "pallet-delegated-staking/std", "pallet-nomination-pools/std", "pallet-staking/std", "pallet-timestamp/std", @@ -72,6 +74,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "pallet-bags-list/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + "pallet-delegated-staking/runtime-benchmarks", "pallet-nomination-pools/runtime-benchmarks", "pallet-staking/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", diff --git a/substrate/frame/nomination-pools/benchmarking/src/inner.rs b/substrate/frame/nomination-pools/benchmarking/src/inner.rs index 277060e7f640f..43de0fddb8b5f 100644 --- a/substrate/frame/nomination-pools/benchmarking/src/inner.rs +++ b/substrate/frame/nomination-pools/benchmarking/src/inner.rs @@ -23,22 +23,24 @@ use frame_support::{ assert_ok, ensure, traits::{ fungible::{Inspect, Mutate, Unbalanced}, - Get, + tokens::Preservation, + Get, Imbalance, }, }; use frame_system::RawOrigin as RuntimeOrigin; use pallet_nomination_pools::{ + adapter::{StakeStrategy, StakeStrategyType}, 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, + Pallet as Pools, PoolId, PoolMembers, PoolRoles, PoolState, RewardPools, SubPoolsStorage, }; use pallet_staking::MaxNominationsOf; use sp_runtime::{ traits::{Bounded, StaticLookup, Zero}, Perbill, }; -use sp_staking::{EraIndex, StakingInterface}; +use sp_staking::EraIndex; use sp_std::{vec, vec::Vec}; // `frame_benchmarking::benchmarks!` macro needs this use pallet_nomination_pools::Call; @@ -101,18 +103,46 @@ fn create_pool_account( let pool_account = pallet_nomination_pools::BondedPools::::iter() .find(|(_, bonded_pool)| bonded_pool.roles.depositor == pool_creator) - .map(|(pool_id, _)| Pools::::create_bonded_account(pool_id)) + .map(|(pool_id, _)| Pools::::generate_bonded_account(pool_id)) .expect("pool_creator created a pool above"); (pool_creator, pool_account) } +fn migrate_to_transfer_stake(pool_id: PoolId) { + if T::StakeAdapter::strategy_type() == StakeStrategyType::Transfer { + // should already be in the correct strategy + return; + } + let pool_acc = Pools::::generate_bonded_account(pool_id); + // drop the agent and its associated delegators . + T::StakeAdapter::remove_as_agent(&pool_acc); + + // tranfer funds from all members to the pool account. + PoolMembers::::iter() + .filter(|(_, member)| member.pool_id == pool_id) + .for_each(|(member_acc, member)| { + let member_balance = member.total_balance(); + ::Currency::transfer( + &member_acc, + &pool_acc, + member_balance, + Preservation::Preserve, + ) + .expect("member should have enough balance to transfer"); + }); +} + fn vote_to_balance( vote: u64, ) -> Result, &'static str> { vote.try_into().map_err(|_| "could not convert u64 to Balance") } +fn is_transfer_stake_strategy() -> bool { + T::StakeAdapter::strategy_type() == StakeStrategyType::Transfer +} + #[allow(unused)] struct ListScenario { /// Stash/Controller that is expected to be moved. @@ -151,7 +181,7 @@ impl ListScenario { let (pool_creator1, pool_origin1) = create_pool_account::(USER_SEED + 1, origin_weight, Some(Perbill::from_percent(50))); - T::Staking::nominate( + T::StakeAdapter::nominate( &pool_origin1, // NOTE: these don't really need to be validators. vec![account("random_validator", 0, USER_SEED)], @@ -160,7 +190,7 @@ impl ListScenario { let (_, pool_origin2) = create_pool_account::(USER_SEED + 2, origin_weight, Some(Perbill::from_percent(50))); - T::Staking::nominate( + T::StakeAdapter::nominate( &pool_origin2, vec![account("random_validator", 0, USER_SEED)].clone(), )?; @@ -178,7 +208,7 @@ impl ListScenario { let (_, pool_dest1) = create_pool_account::(USER_SEED + 3, dest_weight, Some(Perbill::from_percent(50))); - T::Staking::nominate(&pool_dest1, vec![account("random_validator", 0, USER_SEED)])?; + T::StakeAdapter::nominate(&pool_dest1, vec![account("random_validator", 0, USER_SEED)])?; let weight_of = pallet_staking::Pallet::::weight_of_fn(); assert_eq!(vote_to_balance::(weight_of(&pool_origin1)).unwrap(), origin_weight); @@ -204,11 +234,12 @@ impl ListScenario { self.origin1_member = Some(joiner.clone()); CurrencyOf::::set_balance(&joiner, amount * 2u32.into()); - let original_bonded = T::Staking::active_stake(&self.origin1).unwrap(); + let original_bonded = T::StakeAdapter::active_stake(&self.origin1); // 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`."); + T::StakeAdapter::unbond(&self.origin1, amount) + .expect("the pool was created in `Self::new`."); // Account pool points for the unbonded balance. BondedPools::::mutate(&1, |maybe_pool| { @@ -231,13 +262,20 @@ impl ListScenario { } frame_benchmarking::benchmarks! { + where_clause { + where + T: pallet_staking::Config, + pallet_staking::BalanceOf: From, + BalanceOf: Into, + } + join { let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); // setup the worst case list scenario. let scenario = ListScenario::::new(origin_weight, true)?; assert_eq!( - T::Staking::active_stake(&scenario.origin1).unwrap(), + T::StakeAdapter::active_stake(&scenario.origin1), origin_weight ); @@ -252,7 +290,7 @@ frame_benchmarking::benchmarks! { verify { assert_eq!(CurrencyOf::::balance(&joiner), joiner_free - max_additional); assert_eq!( - T::Staking::active_stake(&scenario.origin1).unwrap(), + T::StakeAdapter::active_stake(&scenario.origin1), scenario.dest_weight ); } @@ -267,7 +305,7 @@ frame_benchmarking::benchmarks! { }: bond_extra(RuntimeOrigin::Signed(scenario.creator1.clone()), BondExtra::FreeBalance(extra)) verify { assert!( - T::Staking::active_stake(&scenario.origin1).unwrap() >= + T::StakeAdapter::active_stake(&scenario.origin1) >= scenario.dest_weight ); } @@ -283,7 +321,7 @@ frame_benchmarking::benchmarks! { let _ = Pools::::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::::create_reward_account(1); + let reward_account1 = Pools::::generate_reward_account(1); assert!(extra >= CurrencyOf::::minimum_balance()); let _ = CurrencyOf::::mint_into(&reward_account1, extra); @@ -291,7 +329,7 @@ frame_benchmarking::benchmarks! { verify { // commission of 50% deducted here. assert!( - T::Staking::active_stake(&scenario.origin1).unwrap() >= + T::StakeAdapter::active_stake(&scenario.origin1) >= scenario.dest_weight / 2u32.into() ); } @@ -302,7 +340,7 @@ frame_benchmarking::benchmarks! { let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); let ed = CurrencyOf::::minimum_balance(); let (depositor, pool_account) = create_pool_account::(0, origin_weight, Some(commission)); - let reward_account = Pools::::create_reward_account(1); + let reward_account = Pools::::generate_reward_account(1); // Send funds to the reward account of the pool CurrencyOf::::set_balance(&reward_account, ed + origin_weight); @@ -345,7 +383,7 @@ frame_benchmarking::benchmarks! { 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(); + let bonded_after = T::StakeAdapter::active_stake(&scenario.origin1); // We at least went down to the destination bag assert!(bonded_after <= scenario.dest_weight); let member = PoolMembers::::get( @@ -354,7 +392,7 @@ frame_benchmarking::benchmarks! { .unwrap(); assert_eq!( member.unbonding_eras.keys().cloned().collect::>(), - vec![0 + T::Staking::bonding_duration()] + vec![0 + T::StakeAdapter::bonding_duration()] ); assert_eq!( member.unbonding_eras.values().cloned().collect::>(), @@ -376,7 +414,7 @@ frame_benchmarking::benchmarks! { // Sanity check join worked assert_eq!( - T::Staking::active_stake(&pool_account).unwrap(), + T::StakeAdapter::active_stake(&pool_account), min_create_bond + min_join_bond ); assert_eq!(CurrencyOf::::balance(&joiner), min_join_bond); @@ -386,7 +424,7 @@ frame_benchmarking::benchmarks! { // Sanity check that unbond worked assert_eq!( - T::Staking::active_stake(&pool_account).unwrap(), + T::StakeAdapter::active_stake(&pool_account), min_create_bond ); assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); @@ -419,7 +457,7 @@ frame_benchmarking::benchmarks! { // Sanity check join worked assert_eq!( - T::Staking::active_stake(&pool_account).unwrap(), + T::StakeAdapter::active_stake(&pool_account), min_create_bond + min_join_bond ); assert_eq!(CurrencyOf::::balance(&joiner), min_join_bond); @@ -430,7 +468,7 @@ frame_benchmarking::benchmarks! { // Sanity check that unbond worked assert_eq!( - T::Staking::active_stake(&pool_account).unwrap(), + T::StakeAdapter::active_stake(&pool_account), min_create_bond ); assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); @@ -470,17 +508,17 @@ frame_benchmarking::benchmarks! { // 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::::create_reward_account(1); + let reward_account = Pools::::generate_reward_account(1); assert!(frame_system::Account::::contains_key(&reward_account)); Pools::::fully_unbond(RuntimeOrigin::Signed(depositor.clone()).into(), depositor.clone()).unwrap(); // Sanity check that unbond worked assert_eq!( - T::Staking::active_stake(&pool_account).unwrap(), + T::StakeAdapter::active_stake(&pool_account), Zero::zero() ); assert_eq!( - CurrencyOf::::balance(&pool_account), + T::StakeAdapter::total_balance(&pool_account), min_create_bond ); assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); @@ -522,8 +560,8 @@ frame_benchmarking::benchmarks! { let depositor_lookup = T::Lookup::unlookup(depositor.clone()); // Give the depositor some balance to bond - CurrencyOf::::set_balance(&depositor, min_create_bond * 2u32.into()); - + // it needs to transfer min balance to reward account as well so give additional min balance. + CurrencyOf::::set_balance(&depositor, min_create_bond + CurrencyOf::::minimum_balance() * 2u32.into()); // Make sure no Pools exist at a pre-condition for our verify checks assert_eq!(RewardPools::::count(), 0); assert_eq!(BondedPools::::count(), 0); @@ -556,8 +594,8 @@ frame_benchmarking::benchmarks! { } ); assert_eq!( - T::Staking::active_stake(&Pools::::create_bonded_account(1)), - Ok(min_create_bond) + T::StakeAdapter::active_stake(&Pools::::generate_bonded_account(1)), + min_create_bond ); } @@ -596,8 +634,8 @@ frame_benchmarking::benchmarks! { } ); assert_eq!( - T::Staking::active_stake(&Pools::::create_bonded_account(1)), - Ok(min_create_bond) + T::StakeAdapter::active_stake(&Pools::::generate_bonded_account(1)), + min_create_bond ); } @@ -681,13 +719,13 @@ frame_benchmarking::benchmarks! { .map(|i| account("stash", USER_SEED, i)) .collect(); - assert_ok!(T::Staking::nominate(&pool_account, validators)); - assert!(T::Staking::nominations(&Pools::::create_bonded_account(1)).is_some()); + assert_ok!(T::StakeAdapter::nominate(&pool_account, validators)); + assert!(T::StakeAdapter::nominations(&Pools::::generate_bonded_account(1)).is_some()); whitelist_account!(depositor); }:_(RuntimeOrigin::Signed(depositor.clone()), 1) verify { - assert!(T::Staking::nominations(&Pools::::create_bonded_account(1)).is_none()); + assert!(T::StakeAdapter::nominations(&Pools::::generate_bonded_account(1)).is_none()); } set_commission { @@ -786,7 +824,7 @@ frame_benchmarking::benchmarks! { // Sanity check join worked assert_eq!( - T::Staking::active_stake(&pool_account).unwrap(), + T::StakeAdapter::active_stake(&pool_account), min_create_bond + min_join_bond ); }:_(RuntimeOrigin::Signed(joiner.clone()), ClaimPermission::Permissioned) @@ -800,7 +838,7 @@ frame_benchmarking::benchmarks! { let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); let ed = CurrencyOf::::minimum_balance(); let (depositor, pool_account) = create_pool_account::(0, origin_weight, Some(commission)); - let reward_account = Pools::::create_reward_account(1); + let reward_account = Pools::::generate_reward_account(1); CurrencyOf::::set_balance(&reward_account, ed + origin_weight); // member claims a payout to make some commission available. @@ -829,7 +867,7 @@ frame_benchmarking::benchmarks! { let (depositor, _) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); // Remove ed freeze to create a scenario where the ed deposit needs to be adjusted. - let _ = Pools::::unfreeze_pool_deposit(&Pools::::create_reward_account(1)); + let _ = Pools::::unfreeze_pool_deposit(&Pools::::generate_reward_account(1)); assert!(&Pools::::check_ed_imbalance().is_err()); whitelist_account!(depositor); @@ -838,6 +876,147 @@ frame_benchmarking::benchmarks! { assert!(&Pools::::check_ed_imbalance().is_ok()); } + apply_slash { + // Note: With older `TransferStake` strategy, slashing is greedy and apply_slash should + // always fail. + + // We want to fill member's unbonding pools. So let's bond with big enough amount. + let deposit_amount = Pools::::depositor_min_bond() * T::MaxUnbonding::get().into() * 4u32.into(); + let (depositor, pool_account) = create_pool_account::(0, deposit_amount, None); + let depositor_lookup = T::Lookup::unlookup(depositor.clone()); + + // verify user balance in the pool. + assert_eq!(PoolMembers::::get(&depositor).unwrap().total_balance(), deposit_amount); + // verify delegated balance. + assert!(is_transfer_stake_strategy::() || T::StakeAdapter::member_delegation_balance(&depositor) == deposit_amount); + + // ugly type conversion between balances of pallet staking and pools (which really are same + // type). Maybe there is a better way? + let slash_amount: u128 = deposit_amount.into()/2; + + // slash pool by half + pallet_staking::slashing::do_slash::( + &pool_account, + slash_amount.into(), + &mut pallet_staking::BalanceOf::::zero(), + &mut pallet_staking::NegativeImbalanceOf::::zero(), + EraIndex::zero() + ); + + // verify user balance is slashed in the pool. + assert_eq!(PoolMembers::::get(&depositor).unwrap().total_balance(), deposit_amount/2u32.into()); + // verify delegated balance are not yet slashed. + assert!(is_transfer_stake_strategy::() || T::StakeAdapter::member_delegation_balance(&depositor) == deposit_amount); + + // Fill member's sub pools for the worst case. + for i in 1..(T::MaxUnbonding::get() + 1) { + pallet_staking::CurrentEra::::put(i); + assert!(Pools::::unbond(RuntimeOrigin::Signed(depositor.clone()).into(), depositor_lookup.clone(), Pools::::depositor_min_bond()).is_ok()); + } + + pallet_staking::CurrentEra::::put(T::MaxUnbonding::get() + 2); + + let slash_reporter = create_funded_user_with_balance::("slasher", 0, CurrencyOf::::minimum_balance()); + whitelist_account!(depositor); + }: + { + let res = Pools::::apply_slash(RuntimeOrigin::Signed(slash_reporter.clone()).into(), depositor_lookup.clone()); + // for transfer stake strategy, apply slash would error, otherwise success. + assert!(is_transfer_stake_strategy::() ^ res.is_ok()); + } + verify { + // verify balances are correct and slash applied. + assert_eq!(PoolMembers::::get(&depositor).unwrap().total_balance(), deposit_amount/2u32.into()); + assert!(is_transfer_stake_strategy::() || T::StakeAdapter::member_delegation_balance(&depositor) == deposit_amount/2u32.into()); + } + + apply_slash_fail { + // Bench the scenario where pool has some unapplied slash but the member does not have any + // slash to be applied. + let deposit_amount = Pools::::depositor_min_bond() * 10u32.into(); + // Create pool. + let (depositor, pool_account) = create_pool_account::(0, deposit_amount, None); + + // slash pool by half + let slash_amount: u128 = deposit_amount.into()/2; + pallet_staking::slashing::do_slash::( + &pool_account, + slash_amount.into(), + &mut pallet_staking::BalanceOf::::zero(), + &mut pallet_staking::NegativeImbalanceOf::::zero(), + EraIndex::zero() + ); + + pallet_staking::CurrentEra::::put(1); + + // new member joins the pool who should not be affected by slash. + let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); + let join_amount = min_join_bond * T::MaxUnbonding::get().into() * 2u32.into(); + let joiner = create_funded_user_with_balance::("joiner", 0, join_amount * 2u32.into()); + let joiner_lookup = T::Lookup::unlookup(joiner.clone()); + assert!(Pools::::join(RuntimeOrigin::Signed(joiner.clone()).into(), join_amount, 1).is_ok()); + + // Fill member's sub pools for the worst case. + for i in 0..T::MaxUnbonding::get() { + pallet_staking::CurrentEra::::put(i + 2); // +2 because we already set the current era to 1. + assert!(Pools::::unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner_lookup.clone(), min_join_bond).is_ok()); + } + + pallet_staking::CurrentEra::::put(T::MaxUnbonding::get() + 3); + whitelist_account!(joiner); + + }: { + // Since the StakeAdapter can be different based on the runtime config, the errors could be different as well. + assert!(Pools::::apply_slash(RuntimeOrigin::Signed(joiner.clone()).into(), joiner_lookup.clone()).is_err()); + } + + + pool_migrate { + // create a pool. + let deposit_amount = Pools::::depositor_min_bond() * 2u32.into(); + let (depositor, pool_account) = create_pool_account::(0, deposit_amount, None); + + // migrate pool to transfer stake. + let _ = migrate_to_transfer_stake::(1); + }: { + // Try migrate to `DelegateStake`. Would succeed only if `DelegateStake` strategy is used. + let res = Pools::::migrate_pool_to_delegate_stake(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into()); + assert!(is_transfer_stake_strategy::() ^ res.is_ok()); + } + verify { + // this queries agent balance if `DelegateStake` strategy. + assert!(T::StakeAdapter::total_balance(&pool_account) == deposit_amount); + } + + migrate_delegation { + // create a pool. + let deposit_amount = Pools::::depositor_min_bond() * 2u32.into(); + let (depositor, pool_account) = create_pool_account::(0, deposit_amount, None); + let depositor_lookup = T::Lookup::unlookup(depositor.clone()); + + // migrate pool to transfer stake. + let _ = migrate_to_transfer_stake::(1); + + // Now migrate pool to delegate stake keeping delegators unmigrated. + let migration_res = Pools::::migrate_pool_to_delegate_stake(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into()); + assert!(is_transfer_stake_strategy::() ^ migration_res.is_ok()); + + // verify balances that we will check again later. + assert!(T::StakeAdapter::member_delegation_balance(&depositor) == Zero::zero()); + assert_eq!(PoolMembers::::get(&depositor).unwrap().total_balance(), deposit_amount); + + whitelist_account!(depositor); + }: { + let res = Pools::::migrate_delegation(RuntimeOrigin::Signed(depositor.clone()).into(), depositor_lookup.clone()); + // for transfer stake strategy, apply slash would error, otherwise success. + assert!(is_transfer_stake_strategy::() ^ res.is_ok()); + } + verify { + // verify balances once more. + assert!(is_transfer_stake_strategy::() || T::StakeAdapter::member_delegation_balance(&depositor) == deposit_amount); + assert_eq!(PoolMembers::::get(&depositor).unwrap().total_balance(), deposit_amount); + } + impl_benchmark_test_suite!( Pallet, crate::mock::new_test_ext(), diff --git a/substrate/frame/nomination-pools/benchmarking/src/lib.rs b/substrate/frame/nomination-pools/benchmarking/src/lib.rs index 45e8f1f27e99a..910cdf2e3dff6 100644 --- a/substrate/frame/nomination-pools/benchmarking/src/lib.rs +++ b/substrate/frame/nomination-pools/benchmarking/src/lib.rs @@ -18,6 +18,7 @@ //! Benchmarks for the nomination pools coupled with the staking and bags list pallets. #![cfg_attr(not(feature = "std"), no_std)] +#![recursion_limit = "256"] #[cfg(feature = "runtime-benchmarks")] pub mod inner; diff --git a/substrate/frame/nomination-pools/benchmarking/src/mock.rs b/substrate/frame/nomination-pools/benchmarking/src/mock.rs index 2752d53a6b9f3..def98b4d2945e 100644 --- a/substrate/frame/nomination-pools/benchmarking/src/mock.rs +++ b/substrate/frame/nomination-pools/benchmarking/src/mock.rs @@ -77,7 +77,7 @@ impl pallet_balances::Config for Runtime { type WeightInfo = (); type FreezeIdentifier = RuntimeFreezeReason; type MaxFreezes = ConstU32<1>; - type RuntimeHoldReason = (); + type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = (); } @@ -120,7 +120,7 @@ impl pallet_staking::Config for Runtime { type MaxControllersInDeprecationBatch = ConstU32<100>; type MaxUnlockingChunks = ConstU32<32>; type HistoryDepth = ConstU32<84>; - type EventListeners = Pools; + type EventListeners = (Pools, DelegatedStaking); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; @@ -166,7 +166,8 @@ impl pallet_nomination_pools::Config for Runtime { type RewardCounter = FixedU128; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; - type Staking = Staking; + type StakeAdapter = + pallet_nomination_pools::adapter::DelegateStake; type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; type MaxMetadataLen = ConstU32<256>; type MaxUnbonding = ConstU32<8>; @@ -175,6 +176,20 @@ impl pallet_nomination_pools::Config for Runtime { type AdminOrigin = frame_system::EnsureRoot; } +parameter_types! { + pub const DelegatedStakingPalletId: PalletId = PalletId(*b"py/dlstk"); + pub const SlashRewardFraction: Perbill = Perbill::from_percent(1); +} +impl pallet_delegated_staking::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PalletId = DelegatedStakingPalletId; + type Currency = Balances; + type OnSlash = (); + type SlashRewardFraction = SlashRewardFraction; + type RuntimeHoldReason = RuntimeHoldReason; + type CoreStaking = Staking; +} + impl crate::Config for Runtime {} type Block = frame_system::mocking::MockBlock; @@ -187,6 +202,7 @@ frame_support::construct_runtime!( Staking: pallet_staking, VoterList: pallet_bags_list::, Pools: pallet_nomination_pools, + DelegatedStaking: pallet_delegated_staking, } ); diff --git a/substrate/frame/nomination-pools/fuzzer/src/call.rs b/substrate/frame/nomination-pools/fuzzer/src/call.rs index 027fb2b69138c..9e10d87da6750 100644 --- a/substrate/frame/nomination-pools/fuzzer/src/call.rs +++ b/substrate/frame/nomination-pools/fuzzer/src/call.rs @@ -306,7 +306,7 @@ fn main() { BondedPools::::iter().for_each(|(id, _)| { let amount = random_ed_multiple(&mut rng); let _ = - Balances::deposit_creating(&Pools::create_reward_account(id), amount); + Balances::deposit_creating(&Pools::generate_reward_account(id), amount); // if we just paid out the reward agent, let's calculate how much we expect // our reward agent to have earned. if reward_agent.pool_id.map_or(false, |mid| mid == id) { diff --git a/substrate/frame/nomination-pools/src/adapter.rs b/substrate/frame/nomination-pools/src/adapter.rs new file mode 100644 index 0000000000000..caf4671191d88 --- /dev/null +++ b/substrate/frame/nomination-pools/src/adapter.rs @@ -0,0 +1,389 @@ +// 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. + +use crate::*; +use sp_staking::{DelegationInterface, DelegationMigrator}; + +/// Types of stake strategies. +/// +/// Useful for determining current staking strategy of a runtime and enforce integrity tests. +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, PartialEq)] +pub enum StakeStrategyType { + /// Member funds are transferred to pool account and staked. + /// + /// This is the older staking strategy used by pools. For a new runtime, it is recommended to + /// use [`StakeStrategyType::Delegate`] strategy instead. + Transfer, + /// Member funds are delegated to pool account and staked. + Delegate, +} + +/// An adapter trait that can support multiple staking strategies. +/// +/// Depending on which staking strategy we want to use, the staking logic can be slightly +/// different. Refer the two possible strategies currently: [`TransferStake`] and +/// [`DelegateStake`] for more detail. +pub trait StakeStrategy { + type Balance: frame_support::traits::tokens::Balance; + type AccountId: Clone + sp_std::fmt::Debug; + type CoreStaking: StakingInterface; + + /// The type of staking strategy of the current adapter. + fn strategy_type() -> StakeStrategyType; + + /// See [`StakingInterface::bonding_duration`]. + fn bonding_duration() -> EraIndex { + Self::CoreStaking::bonding_duration() + } + + /// See [`StakingInterface::current_era`]. + fn current_era() -> EraIndex { + Self::CoreStaking::current_era() + } + + /// See [`StakingInterface::minimum_nominator_bond`]. + fn minimum_nominator_bond() -> Self::Balance { + Self::CoreStaking::minimum_nominator_bond() + } + + /// Balance that can be transferred from pool account to member. + /// + /// This is part of the pool balance that is not actively staked. That is, tokens that are + /// in unbonding period or unbonded. + fn transferable_balance(pool_account: &Self::AccountId) -> Self::Balance; + + /// Total balance of the pool including amount that is actively staked. + fn total_balance(pool_account: &Self::AccountId) -> Self::Balance; + + /// Amount of tokens delegated by the member. + fn member_delegation_balance(member_account: &Self::AccountId) -> Self::Balance; + + /// See [`StakingInterface::active_stake`]. + fn active_stake(pool_account: &Self::AccountId) -> Self::Balance { + Self::CoreStaking::active_stake(pool_account).unwrap_or_default() + } + + /// See [`StakingInterface::total_stake`]. + fn total_stake(pool_account: &Self::AccountId) -> Self::Balance { + Self::CoreStaking::total_stake(pool_account).unwrap_or_default() + } + + /// Which strategy the pool account is using. + /// + /// This can be different from the [`Self::strategy_type`] of the adapter if the pool has not + /// migrated to the new strategy yet. + fn pool_strategy(pool_account: &Self::AccountId) -> StakeStrategyType { + match Self::CoreStaking::is_virtual_staker(pool_account) { + true => StakeStrategyType::Delegate, + false => StakeStrategyType::Transfer, + } + } + + /// See [`StakingInterface::nominate`]. + fn nominate( + pool_account: &Self::AccountId, + validators: Vec, + ) -> DispatchResult { + Self::CoreStaking::nominate(pool_account, validators) + } + + /// See [`StakingInterface::chill`]. + fn chill(pool_account: &Self::AccountId) -> DispatchResult { + Self::CoreStaking::chill(pool_account) + } + + /// Pledge `amount` towards `pool_account` and update the pool bond. Also see + /// [`StakingInterface::bond`]. + fn pledge_bond( + who: &Self::AccountId, + pool_account: &Self::AccountId, + reward_account: &Self::AccountId, + amount: Self::Balance, + bond_type: BondType, + ) -> DispatchResult; + + /// See [`StakingInterface::unbond`]. + fn unbond(pool_account: &Self::AccountId, amount: Self::Balance) -> DispatchResult { + Self::CoreStaking::unbond(pool_account, amount) + } + + /// See [`StakingInterface::withdraw_unbonded`]. + fn withdraw_unbonded( + pool_account: &Self::AccountId, + num_slashing_spans: u32, + ) -> Result { + Self::CoreStaking::withdraw_unbonded(pool_account.clone(), num_slashing_spans) + } + + /// Withdraw funds from pool account to member account. + fn member_withdraw( + who: &Self::AccountId, + pool_account: &Self::AccountId, + amount: Self::Balance, + num_slashing_spans: u32, + ) -> DispatchResult; + + /// Check if there is any pending slash for the pool. + fn has_pending_slash(pool_account: &Self::AccountId) -> bool; + + /// Slash the member account with `amount` against pending slashes for the pool. + fn member_slash( + who: &Self::AccountId, + pool_account: &Self::AccountId, + amount: Self::Balance, + maybe_reporter: Option, + ) -> DispatchResult; + + /// Migrate pool account from being a direct nominator to a delegated agent. + /// + /// This is useful for migrating a pool account from [`StakeStrategyType::Transfer`] to + /// [`StakeStrategyType::Delegate`]. + fn migrate_nominator_to_agent( + pool_account: &Self::AccountId, + reward_account: &Self::AccountId, + ) -> DispatchResult; + + /// Migrate member balance from pool account to member account. + /// + /// This is useful for a pool account that migrated from [`StakeStrategyType::Transfer`] to + /// [`StakeStrategyType::Delegate`]. Its members can then migrate their delegated balance + /// back to their account. + /// + /// Internally, the member funds that are locked in the pool account are transferred back and + /// locked in the member account. + fn migrate_delegation( + pool: &Self::AccountId, + delegator: &Self::AccountId, + value: Self::Balance, + ) -> DispatchResult; + + /// List of validators nominated by the pool account. + #[cfg(feature = "runtime-benchmarks")] + fn nominations(pool_account: &Self::AccountId) -> Option> { + Self::CoreStaking::nominations(pool_account) + } + + /// Remove the pool account as agent. + /// + /// Useful for migrating pool account from a delegated agent to a direct nominator. Only used + /// in tests and benchmarks. + #[cfg(feature = "runtime-benchmarks")] + fn remove_as_agent(_pool: &Self::AccountId) { + // noop by default + } +} + +/// A staking strategy implementation that supports transfer based staking. +/// +/// In order to stake, this adapter transfers the funds from the member/delegator account to the +/// pool account and stakes through the pool account on `Staking`. +/// +/// This is the older Staking strategy used by pools. To switch to the newer [`DelegateStake`] +/// strategy in an existing runtime, storage migration is required. See +/// [`migration::unversioned::DelegationStakeMigration`]. For new runtimes, it is highly recommended +/// to use the [`DelegateStake`] strategy. +pub struct TransferStake(PhantomData<(T, Staking)>); + +impl, AccountId = T::AccountId>> + StakeStrategy for TransferStake +{ + type Balance = BalanceOf; + type AccountId = T::AccountId; + type CoreStaking = Staking; + + fn strategy_type() -> StakeStrategyType { + StakeStrategyType::Transfer + } + + fn transferable_balance(pool_account: &Self::AccountId) -> BalanceOf { + T::Currency::balance(pool_account).saturating_sub(Self::active_stake(pool_account)) + } + + fn total_balance(pool_account: &Self::AccountId) -> BalanceOf { + T::Currency::total_balance(pool_account) + } + + fn member_delegation_balance(_member_account: &T::AccountId) -> Staking::Balance { + // for transfer stake, delegation balance is always zero. + Zero::zero() + } + + fn pledge_bond( + who: &T::AccountId, + pool_account: &Self::AccountId, + reward_account: &Self::AccountId, + amount: BalanceOf, + bond_type: BondType, + ) -> DispatchResult { + match bond_type { + BondType::Create => { + // first bond + T::Currency::transfer(who, pool_account, amount, Preservation::Expendable)?; + Staking::bond(pool_account, amount, &reward_account) + }, + BondType::Extra => { + // additional bond + T::Currency::transfer(who, pool_account, amount, Preservation::Preserve)?; + Staking::bond_extra(pool_account, amount) + }, + } + } + + fn member_withdraw( + who: &T::AccountId, + pool_account: &Self::AccountId, + amount: BalanceOf, + _num_slashing_spans: u32, + ) -> DispatchResult { + T::Currency::transfer(pool_account, &who, amount, Preservation::Expendable)?; + + Ok(()) + } + + fn has_pending_slash(_: &Self::AccountId) -> bool { + // for transfer stake strategy, slashing is greedy and never deferred. + false + } + + fn member_slash( + _who: &T::AccountId, + _pool: &Self::AccountId, + _amount: Staking::Balance, + _maybe_reporter: Option, + ) -> DispatchResult { + Err(Error::::Defensive(DefensiveError::DelegationUnsupported).into()) + } + + fn migrate_nominator_to_agent( + _pool: &Self::AccountId, + _reward_account: &Self::AccountId, + ) -> DispatchResult { + Err(Error::::Defensive(DefensiveError::DelegationUnsupported).into()) + } + + fn migrate_delegation( + _pool: &Self::AccountId, + _delegator: &Self::AccountId, + _value: Self::Balance, + ) -> DispatchResult { + Err(Error::::Defensive(DefensiveError::DelegationUnsupported).into()) + } +} + +/// A staking strategy implementation that supports delegation based staking. +/// +/// In this approach, first the funds are delegated from delegator to the pool account and later +/// staked with `Staking`. The advantage of this approach is that the funds are held in the +/// user account itself and not in the pool account. +/// +/// This is the newer staking strategy used by pools. Once switched to this and migrated, ideally +/// the `TransferStake` strategy should not be used. Or a separate migration would be required for +/// it which is not provided by this pallet. +/// +/// Use [`migration::unversioned::DelegationStakeMigration`] to migrate to this strategy. +pub struct DelegateStake( + PhantomData<(T, Staking, Delegation)>, +); + +impl< + T: Config, + Staking: StakingInterface, AccountId = T::AccountId>, + Delegation: DelegationInterface, AccountId = T::AccountId> + + DelegationMigrator, AccountId = T::AccountId>, + > StakeStrategy for DelegateStake +{ + type Balance = BalanceOf; + type AccountId = T::AccountId; + type CoreStaking = Staking; + + fn strategy_type() -> StakeStrategyType { + StakeStrategyType::Delegate + } + + fn transferable_balance(pool_account: &Self::AccountId) -> BalanceOf { + Delegation::agent_balance(pool_account).saturating_sub(Self::active_stake(pool_account)) + } + + fn total_balance(pool_account: &Self::AccountId) -> BalanceOf { + Delegation::agent_balance(pool_account) + } + + fn member_delegation_balance(member_account: &T::AccountId) -> BalanceOf { + Delegation::delegator_balance(member_account) + } + + fn pledge_bond( + who: &T::AccountId, + pool_account: &Self::AccountId, + reward_account: &Self::AccountId, + amount: BalanceOf, + bond_type: BondType, + ) -> DispatchResult { + match bond_type { + BondType::Create => { + // first delegation + Delegation::delegate(who, pool_account, reward_account, amount) + }, + BondType::Extra => { + // additional delegation + Delegation::delegate_extra(who, pool_account, amount) + }, + } + } + + fn member_withdraw( + who: &T::AccountId, + pool_account: &Self::AccountId, + amount: BalanceOf, + num_slashing_spans: u32, + ) -> DispatchResult { + Delegation::withdraw_delegation(&who, pool_account, amount, num_slashing_spans) + } + + fn has_pending_slash(pool_account: &Self::AccountId) -> bool { + Delegation::has_pending_slash(pool_account) + } + + fn member_slash( + who: &T::AccountId, + pool_account: &Self::AccountId, + amount: BalanceOf, + maybe_reporter: Option, + ) -> DispatchResult { + Delegation::delegator_slash(pool_account, who, amount, maybe_reporter) + } + + fn migrate_nominator_to_agent( + pool: &Self::AccountId, + reward_account: &Self::AccountId, + ) -> DispatchResult { + Delegation::migrate_nominator_to_agent(pool, reward_account) + } + + fn migrate_delegation( + pool: &Self::AccountId, + delegator: &Self::AccountId, + value: Self::Balance, + ) -> DispatchResult { + Delegation::migrate_delegation(pool, delegator, value) + } + + #[cfg(feature = "runtime-benchmarks")] + fn remove_as_agent(pool: &Self::AccountId) { + Delegation::drop_agent(pool) + } +} diff --git a/substrate/frame/nomination-pools/src/lib.rs b/substrate/frame/nomination-pools/src/lib.rs index 95d23f2280a7e..816334c1a0840 100644 --- a/substrate/frame/nomination-pools/src/lib.rs +++ b/substrate/frame/nomination-pools/src/lib.rs @@ -351,6 +351,7 @@ #![cfg_attr(not(feature = "std"), no_std)] +use adapter::StakeStrategy; use codec::Codec; use frame_support::{ defensive, defensive_assert, ensure, @@ -397,6 +398,7 @@ pub mod mock; #[cfg(test)] mod tests; +pub mod adapter; pub mod migration; pub mod weights; @@ -425,11 +427,11 @@ pub enum ConfigOp { } /// The type of bonding that can happen to a pool. -enum BondType { +pub enum BondType { /// Someone is bonding into the pool upon creation. Create, /// Someone is adding more funds later to this pool. - Later, + Extra, } /// How to increase the bond of a member. @@ -549,9 +551,19 @@ impl PoolMember { /// Total balance of the member, both active and unbonding. /// Doesn't mutate state. - #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))] - fn total_balance(&self) -> BalanceOf { - let pool = BondedPool::::get(self.pool_id).unwrap(); + /// + /// Worst case, iterates over [`TotalUnbondingPools`] member unbonding pools to calculate member + /// balance. + pub fn total_balance(&self) -> BalanceOf { + let pool = match BondedPool::::get(self.pool_id) { + Some(pool) => pool, + None => { + // this internal function is always called with a valid pool id. + defensive!("pool should exist; qed"); + return Zero::zero(); + }, + }; + let active_balance = pool.points_to_balance(self.active_points()); let sub_pools = match SubPoolsStorage::::get(self.pool_id) { @@ -973,12 +985,12 @@ impl BondedPool { /// Get the bonded account id of this pool. fn bonded_account(&self) -> T::AccountId { - Pallet::::create_bonded_account(self.id) + Pallet::::generate_bonded_account(self.id) } /// Get the reward account id of this pool. fn reward_account(&self) -> T::AccountId { - Pallet::::create_reward_account(self.id) + Pallet::::generate_reward_account(self.id) } /// Consume self and put into storage. @@ -995,8 +1007,7 @@ impl BondedPool { /// /// This is often used for bonding and issuing new funds into the pool. fn balance_to_point(&self, new_funds: BalanceOf) -> BalanceOf { - let bonded_balance = - T::Staking::active_stake(&self.bonded_account()).unwrap_or(Zero::zero()); + let bonded_balance = T::StakeAdapter::active_stake(&self.bonded_account()); Pallet::::balance_to_point(bonded_balance, self.points, new_funds) } @@ -1004,8 +1015,7 @@ impl BondedPool { /// /// This is often used for unbonding. fn points_to_balance(&self, points: BalanceOf) -> BalanceOf { - let bonded_balance = - T::Staking::active_stake(&self.bonded_account()).unwrap_or(Zero::zero()); + let bonded_balance = T::StakeAdapter::active_stake(&self.bonded_account()); Pallet::::point_to_balance(bonded_balance, self.points, points) } @@ -1052,18 +1062,6 @@ impl BondedPool { self } - /// The pools balance that is transferable provided it is expendable by staking pallet. - fn transferable_balance(&self) -> BalanceOf { - let account = self.bonded_account(); - // Note on why we can't use `Currency::reducible_balance`: Since pooled account has a - // provider (staking pallet), the account can not be set expendable by - // `pallet-nomination-pool`. This means reducible balance always returns balance preserving - // ED in the account. What we want though is transferable balance given the account can be - // dusted. - T::Currency::balance(&account) - .saturating_sub(T::Staking::active_stake(&account).unwrap_or_default()) - } - fn is_root(&self, who: &T::AccountId) -> bool { self.roles.root.as_ref().map_or(false, |root| root == who) } @@ -1127,8 +1125,7 @@ impl BondedPool { fn ok_to_be_open(&self) -> Result<(), DispatchError> { ensure!(!self.is_destroying(), Error::::CanNotChangeState); - let bonded_balance = - T::Staking::active_stake(&self.bonded_account()).unwrap_or(Zero::zero()); + let bonded_balance = T::StakeAdapter::active_stake(&self.bonded_account()); ensure!(!bonded_balance.is_zero(), Error::::OverflowRisk); let points_to_balance_ratio_floor = self @@ -1257,28 +1254,17 @@ impl BondedPool { amount: BalanceOf, ty: BondType, ) -> Result, DispatchError> { - // Cache the value - let bonded_account = self.bonded_account(); - T::Currency::transfer( - who, - &bonded_account, - amount, - match ty { - BondType::Create => Preservation::Expendable, - BondType::Later => Preservation::Preserve, - }, - )?; // We must calculate the points issued *before* we bond who's funds, else points:balance // ratio will be wrong. let points_issued = self.issue(amount); - match ty { - BondType::Create => T::Staking::bond(&bonded_account, amount, &self.reward_account())?, - // The pool should always be created in such a way its in a state to bond extra, but if - // the active balance is slashed below the minimum bonded or the account cannot be - // found, we exit early. - BondType::Later => T::Staking::bond_extra(&bonded_account, amount)?, - } + T::StakeAdapter::pledge_bond( + who, + &self.bonded_account(), + &self.reward_account(), + amount, + ty, + )?; TotalValueLocked::::mutate(|tvl| { tvl.saturating_accrue(amount); }); @@ -1456,7 +1442,7 @@ impl RewardPool { /// This is sum of all the rewards that are claimable by pool members. fn current_balance(id: PoolId) -> BalanceOf { T::Currency::reducible_balance( - &Pallet::::create_reward_account(id), + &Pallet::::generate_reward_account(id), Preservation::Expendable, Fortitude::Polite, ) @@ -1569,7 +1555,7 @@ impl Get for TotalUnbondingPools { // NOTE: this may be dangerous in the scenario bonding_duration gets decreased because // we would no longer be able to decode `BoundedBTreeMap::, // TotalUnbondingPools>`, which uses `TotalUnbondingPools` as the bound - T::Staking::bonding_duration() + T::PostUnbondingPoolsWindow::get() + T::StakeAdapter::bonding_duration() + T::PostUnbondingPoolsWindow::get() } } @@ -1646,7 +1632,9 @@ pub mod pallet { type U256ToBalance: Convert>; /// The interface for nominating. - type Staking: StakingInterface, AccountId = Self::AccountId>; + /// + /// Note: Switching to a new [`StakeStrategy`] might require a migration of the storage. + type StakeAdapter: StakeStrategy>; /// The amount of eras a `SubPools::with_era` pool can exist before it gets merged into the /// `SubPools::no_era` pool. In other words, this is the amount of eras a member will be @@ -1950,6 +1938,16 @@ pub mod pallet { BondExtraRestricted, /// No imbalance in the ED deposit for the pool. NothingToAdjust, + /// No slash pending that can be applied to the member. + NothingToSlash, + /// No delegation to migrate. + NoDelegationToMigrate, + /// The pool has already migrated to enable delegation. + PoolAlreadyMigrated, + /// The pool has not migrated yet to enable delegation. + PoolNotMigrated, + /// This call is not allowed in the current state of the pallet. + NotSupported, } #[derive(Encode, Decode, PartialEq, TypeInfo, PalletError, RuntimeDebug)] @@ -1965,6 +1963,10 @@ pub mod pallet { /// The bonded account should only be killed by the staking system when the depositor is /// withdrawing BondedStashKilledPrematurely, + /// The delegation feature is unsupported. + DelegationUnsupported, + /// Unable to slash to the member of the pool. + SlashNotApplied, } impl From for Error { @@ -2019,7 +2021,7 @@ pub mod pallet { )?; bonded_pool.try_inc_members()?; - let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Later)?; + let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Extra)?; PoolMembers::insert( who.clone(), @@ -2141,12 +2143,12 @@ pub mod pallet { &mut reward_pool, )?; - let current_era = T::Staking::current_era(); - let unbond_era = T::Staking::bonding_duration().saturating_add(current_era); + let current_era = T::StakeAdapter::current_era(); + let unbond_era = T::StakeAdapter::bonding_duration().saturating_add(current_era); // Unbond in the actual underlying nominator. let unbonding_balance = bonded_pool.dissolve(unbonding_points); - T::Staking::unbond(&bonded_pool.bonded_account(), unbonding_balance)?; + T::StakeAdapter::unbond(&bonded_pool.bonded_account(), unbonding_balance)?; // Note that we lazily create the unbonding pools here if they don't already exist let mut sub_pools = SubPoolsStorage::::get(member.pool_id) @@ -2209,7 +2211,7 @@ pub mod pallet { // For now we only allow a pool to withdraw unbonded if its not destroying. If the pool // is destroying then `withdraw_unbonded` can be used. ensure!(pool.state != PoolState::Destroying, Error::::NotDestroying); - T::Staking::withdraw_unbonded(pool.bonded_account(), num_slashing_spans)?; + T::StakeAdapter::withdraw_unbonded(&pool.bonded_account(), num_slashing_spans)?; Ok(()) } @@ -2232,7 +2234,10 @@ pub mod pallet { /// /// # Note /// - /// If the target is the depositor, the pool will be destroyed. + /// - If the target is the depositor, the pool will be destroyed. + /// - If the pool has any pending slash, we also try to slash the member before letting them + /// withdraw. This calculation adds some weight overhead and is only defensive. In reality, + /// pool slashes must have been already applied via permissionless [`Call::apply_slash`]. #[pallet::call_index(5)] #[pallet::weight( T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans) @@ -2246,13 +2251,30 @@ pub mod pallet { let member_account = T::Lookup::lookup(member_account)?; let mut member = PoolMembers::::get(&member_account).ok_or(Error::::PoolMemberNotFound)?; - let current_era = T::Staking::current_era(); + let current_era = T::StakeAdapter::current_era(); let bonded_pool = BondedPool::::get(member.pool_id) .defensive_ok_or::>(DefensiveError::PoolNotFound.into())?; let mut sub_pools = SubPoolsStorage::::get(member.pool_id).ok_or(Error::::SubPoolsNotFound)?; + let slash_weight = + // apply slash if any before withdraw. + match Self::do_apply_slash(&member_account, None) { + Ok(_) => T::WeightInfo::apply_slash(), + Err(e) => { + let no_pending_slash: DispatchResult = Err(Error::::NothingToSlash.into()); + // This is an expected error. We add appropriate fees and continue withdrawal. + if Err(e) == no_pending_slash { + T::WeightInfo::apply_slash_fail() + } else { + // defensive: if we can't apply slash for some reason, we abort. + return Err(Error::::Defensive(DefensiveError::SlashNotApplied).into()); + } + } + + }; + bonded_pool.ok_to_withdraw_unbonded_with(&caller, &member_account)?; let pool_account = bonded_pool.bonded_account(); @@ -2261,9 +2283,11 @@ pub mod pallet { ensure!(!withdrawn_points.is_empty(), Error::::CannotWithdrawAny); // Before calculating the `balance_to_unbond`, we call withdraw unbonded to ensure the - // `transferrable_balance` is correct. - let stash_killed = - T::Staking::withdraw_unbonded(pool_account.clone(), num_slashing_spans)?; + // `transferable_balance` is correct. + let stash_killed = T::StakeAdapter::withdraw_unbonded( + &bonded_pool.bonded_account(), + num_slashing_spans, + )?; // defensive-only: the depositor puts enough funds into the stash so that it will only // be destroyed when they are leaving. @@ -2310,15 +2334,16 @@ pub mod pallet { // don't exist. This check is also defensive in cases where the unbond pool does not // update its balance (e.g. a bug in the slashing hook.) We gracefully proceed in // order to ensure members can leave the pool and it can be destroyed. - .min(bonded_pool.transferable_balance()); + .min(T::StakeAdapter::transferable_balance(&bonded_pool.bonded_account())); - T::Currency::transfer( - &bonded_pool.bonded_account(), + // this can fail if the pool uses `DelegateStake` strategy and the member delegation + // is not claimed yet. See `Call::migrate_delegation()`. + T::StakeAdapter::member_withdraw( &member_account, + &bonded_pool.bonded_account(), balance_to_unbond, - Preservation::Expendable, - ) - .defensive()?; + num_slashing_spans, + )?; Self::deposit_event(Event::::Withdrawn { member: member_account.clone(), @@ -2340,20 +2365,20 @@ pub mod pallet { if member_account == bonded_pool.roles.depositor { Pallet::::dissolve_pool(bonded_pool); - None + Weight::default() } else { bonded_pool.dec_members().put(); SubPoolsStorage::::insert(member.pool_id, sub_pools); - Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) + T::WeightInfo::withdraw_unbonded_update(num_slashing_spans) } } else { // we certainly don't need to delete any pools, because no one is being removed. SubPoolsStorage::::insert(member.pool_id, sub_pools); PoolMembers::::insert(&member_account, member); - Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) + T::WeightInfo::withdraw_unbonded_update(num_slashing_spans) }; - Ok(post_info_weight.into()) + Ok(Some(post_info_weight.saturating_add(slash_weight)).into()) } /// Create a new delegation pool. @@ -2448,7 +2473,7 @@ pub mod pallet { Error::::MinimumBondNotMet ); - T::Staking::nominate(&bonded_pool.bonded_account(), validators) + T::StakeAdapter::nominate(&bonded_pool.bonded_account(), validators) } /// Set a new state for the pool. @@ -2636,12 +2661,12 @@ pub mod pallet { .active_points(); if bonded_pool.points_to_balance(depositor_points) >= - T::Staking::minimum_nominator_bond() + T::StakeAdapter::minimum_nominator_bond() { ensure!(bonded_pool.can_nominate(&who), Error::::NotNominator); } - T::Staking::chill(&bonded_pool.bonded_account()) + T::StakeAdapter::chill(&bonded_pool.bonded_account()) } /// `origin` bonds funds from `extra` for some pool member `member` into their respective @@ -2838,6 +2863,119 @@ pub mod pallet { Ok(()) } + + /// Apply a pending slash on a member. + /// + /// Fails unless [`crate::pallet::Config::StakeAdapter`] is of strategy type: + /// [`adapter::StakeStrategyType::Delegate`]. + /// + /// This call can be dispatched permissionlessly (i.e. by any account). If the member has + /// slash to be applied, caller may be rewarded with the part of the slash. + #[pallet::call_index(23)] + #[pallet::weight(T::WeightInfo::apply_slash())] + pub fn apply_slash( + origin: OriginFor, + member_account: AccountIdLookupOf, + ) -> DispatchResultWithPostInfo { + ensure!( + T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate, + Error::::NotSupported + ); + + let who = ensure_signed(origin)?; + let member_account = T::Lookup::lookup(member_account)?; + Self::do_apply_slash(&member_account, Some(who))?; + + // If successful, refund the fees. + Ok(Pays::No.into()) + } + + /// Migrates delegated funds from the pool account to the `member_account`. + /// + /// Fails unless [`crate::pallet::Config::StakeAdapter`] is of strategy type: + /// [`adapter::StakeStrategyType::Delegate`]. + /// + /// This is a permission-less call and refunds any fee if claim is successful. + /// + /// If the pool has migrated to delegation based staking, the staked tokens of pool members + /// can be moved and held in their own account. See [`adapter::DelegateStake`] + #[pallet::call_index(24)] + #[pallet::weight(T::WeightInfo::migrate_delegation())] + pub fn migrate_delegation( + origin: OriginFor, + member_account: AccountIdLookupOf, + ) -> DispatchResultWithPostInfo { + let _caller = ensure_signed(origin)?; + + ensure!( + T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate, + Error::::NotSupported + ); + + let member_account = T::Lookup::lookup(member_account)?; + let member = + PoolMembers::::get(&member_account).ok_or(Error::::PoolMemberNotFound)?; + + // ensure pool is migrated. + ensure!( + T::StakeAdapter::pool_strategy(&Self::generate_bonded_account(member.pool_id)) == + adapter::StakeStrategyType::Delegate, + Error::::PoolNotMigrated + ); + + let pool_contribution = member.total_balance(); + ensure!(pool_contribution >= MinJoinBond::::get(), Error::::MinimumBondNotMet); + // the member must have some contribution to be migrated. + ensure!(pool_contribution > Zero::zero(), Error::::NoDelegationToMigrate); + + let delegation = T::StakeAdapter::member_delegation_balance(&member_account); + // delegation can be claimed only once. + ensure!(delegation == Zero::zero(), Error::::NoDelegationToMigrate); + + let diff = pool_contribution.defensive_saturating_sub(delegation); + T::StakeAdapter::migrate_delegation( + &Pallet::::generate_bonded_account(member.pool_id), + &member_account, + diff, + )?; + + // if successful, we refund the fee. + Ok(Pays::No.into()) + } + + /// Migrate pool from [`adapter::StakeStrategyType::Transfer`] to + /// [`adapter::StakeStrategyType::Delegate`]. + /// + /// Fails unless [`crate::pallet::Config::StakeAdapter`] is of strategy type: + /// [`adapter::StakeStrategyType::Delegate`]. + /// + /// This call can be dispatched permissionlessly, and refunds any fee if successful. + /// + /// If the pool has already migrated to delegation based staking, this call will fail. + #[pallet::call_index(25)] + #[pallet::weight(T::WeightInfo::pool_migrate())] + pub fn migrate_pool_to_delegate_stake( + origin: OriginFor, + pool_id: PoolId, + ) -> DispatchResultWithPostInfo { + // gate this call to be called only if `DelegateStake` strategy is used. + ensure!( + T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate, + Error::::NotSupported + ); + + let _caller = ensure_signed(origin)?; + // ensure pool exists. + let bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + ensure!( + T::StakeAdapter::pool_strategy(&bonded_pool.bonded_account()) == + adapter::StakeStrategyType::Transfer, + Error::::PoolAlreadyMigrated + ); + + Self::migrate_to_delegate_stake(pool_id)?; + Ok(Pays::No.into()) + } } #[pallet::hooks] @@ -2853,7 +2991,7 @@ pub mod pallet { "Minimum points to balance ratio must be greater than 0" ); assert!( - T::Staking::bonding_duration() < TotalUnbondingPools::::get(), + T::StakeAdapter::bonding_duration() < TotalUnbondingPools::::get(), "There must be more unbonding pools then the bonding duration / so a slash can be applied to relevant unbonding pools. (We assume / the bonding duration > slash deffer duration.", @@ -2871,7 +3009,7 @@ impl Pallet { /// 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. pub fn depositor_min_bond() -> BalanceOf { - T::Staking::minimum_nominator_bond() + T::StakeAdapter::minimum_nominator_bond() .max(MinCreateBond::::get()) .max(MinJoinBond::::get()) .max(T::Currency::minimum_balance()) @@ -2907,7 +3045,7 @@ impl Pallet { "bonded account of dissolving pool should have no consumers" ); defensive_assert!( - T::Staking::total_stake(&bonded_account).unwrap_or_default() == Zero::zero(), + T::StakeAdapter::total_stake(&bonded_pool.bonded_account()) == Zero::zero(), "dissolving pool should not have any stake in the staking pallet" ); @@ -2930,11 +3068,12 @@ impl Pallet { "could not transfer all amount to depositor while dissolving pool" ); defensive_assert!( - T::Currency::total_balance(&bonded_pool.bonded_account()) == Zero::zero(), + T::StakeAdapter::total_balance(&bonded_pool.bonded_account()) == Zero::zero(), "dissolving pool should not have any balance" ); // NOTE: Defensively force set balance to zero. T::Currency::set_balance(&reward_account, Zero::zero()); + // With `DelegateStake` strategy, this won't do anything. T::Currency::set_balance(&bonded_pool.bonded_account(), Zero::zero()); Self::deposit_event(Event::::Destroyed { pool_id: bonded_pool.id }); @@ -2945,12 +3084,19 @@ impl Pallet { } /// Create the main, bonded account of a pool with the given id. - pub fn create_bonded_account(id: PoolId) -> T::AccountId { + pub fn generate_bonded_account(id: PoolId) -> T::AccountId { T::PalletId::get().into_sub_account_truncating((AccountType::Bonded, id)) } + fn migrate_to_delegate_stake(id: PoolId) -> DispatchResult { + T::StakeAdapter::migrate_nominator_to_agent( + &Self::generate_bonded_account(id), + &Self::generate_reward_account(id), + ) + } + /// Create the reward account of a pool with the given id. - pub fn create_reward_account(id: PoolId) -> T::AccountId { + pub fn generate_reward_account(id: PoolId) -> T::AccountId { // NOTE: in order to have a distinction in the test account id type (u128), we put // account_type first so it does not get truncated out. T::PalletId::get().into_sub_account_truncating((AccountType::Reward, id)) @@ -3194,9 +3340,9 @@ impl Pallet { let (points_issued, bonded) = match extra { BondExtra::FreeBalance(amount) => - (bonded_pool.try_bond_funds(&member_account, amount, BondType::Later)?, amount), + (bonded_pool.try_bond_funds(&member_account, amount, BondType::Extra)?, amount), BondExtra::Rewards => - (bonded_pool.try_bond_funds(&member_account, claimed, BondType::Later)?, claimed), + (bonded_pool.try_bond_funds(&member_account, claimed, BondType::Extra)?, claimed), }; bonded_pool.ok_to_be_open()?; @@ -3317,6 +3463,36 @@ impl Pallet { Ok(()) } + /// Slash member against the pending slash for the pool. + fn do_apply_slash( + member_account: &T::AccountId, + reporter: Option, + ) -> DispatchResult { + // calculate points to be slashed. + let member = + PoolMembers::::get(&member_account).ok_or(Error::::PoolMemberNotFound)?; + + let pool_account = Pallet::::generate_bonded_account(member.pool_id); + ensure!(T::StakeAdapter::has_pending_slash(&pool_account), Error::::NothingToSlash); + + let unslashed_balance = T::StakeAdapter::member_delegation_balance(&member_account); + let slashed_balance = member.total_balance(); + defensive_assert!( + unslashed_balance >= slashed_balance, + "unslashed balance should always be greater or equal to the slashed" + ); + + // if nothing to slash, return error. + ensure!(unslashed_balance > slashed_balance, Error::::NothingToSlash); + + T::StakeAdapter::member_slash( + &member_account, + &pool_account, + unslashed_balance.defensive_saturating_sub(slashed_balance), + reporter, + ) + } + /// Apply freeze on reward account to restrict it from going below ED. pub(crate) fn freeze_pool_deposit(reward_acc: &T::AccountId) -> DispatchResult { T::Currency::set_freeze( @@ -3395,7 +3571,7 @@ impl Pallet { ); for id in reward_pools { - let account = Self::create_reward_account(id); + let account = Self::generate_reward_account(id); if T::Currency::reducible_balance(&account, Preservation::Expendable, Fortitude::Polite) < T::Currency::minimum_balance() { @@ -3480,8 +3656,7 @@ impl Pallet { pool is being destroyed and the depositor is the last member", ); - expected_tvl += - T::Staking::total_stake(&bonded_pool.bonded_account()).unwrap_or_default(); + expected_tvl += T::StakeAdapter::total_stake(&bonded_pool.bonded_account()); Ok(()) })?; @@ -3506,19 +3681,28 @@ impl Pallet { } for (pool_id, _pool) in BondedPools::::iter() { - let pool_account = Pallet::::create_bonded_account(pool_id); + let pool_account = Pallet::::generate_bonded_account(pool_id); let subs = SubPoolsStorage::::get(pool_id).unwrap_or_default(); let sum_unbonding_balance = subs.sum_unbonding_balance(); - let bonded_balance = T::Staking::active_stake(&pool_account).unwrap_or_default(); - let total_balance = T::Currency::total_balance(&pool_account); + let bonded_balance = T::StakeAdapter::active_stake(&pool_account); + let total_balance = T::StakeAdapter::total_balance(&pool_account); + + // At the time when StakeAdapter is changed but migration is not yet done, the new + // adapter would return zero balance (as it is not an agent yet). We handle that by + // falling back to reading actual balance of the pool account. + let pool_balance = if total_balance.is_zero() { + T::Currency::total_balance(&pool_account) + } else { + total_balance + }; assert!( - total_balance >= bonded_balance + sum_unbonding_balance, - "faulty pool: {:?} / {:?}, total_balance {:?} >= bonded_balance {:?} + sum_unbonding_balance {:?}", + pool_balance >= bonded_balance + sum_unbonding_balance, + "faulty pool: {:?} / {:?}, pool_balance {:?} >= bonded_balance {:?} + sum_unbonding_balance {:?}", pool_id, _pool, - total_balance, + pool_balance, bonded_balance, sum_unbonding_balance ); @@ -3544,7 +3728,7 @@ impl Pallet { pub fn check_ed_imbalance() -> Result<(), DispatchError> { let mut failed: u32 = 0; BondedPools::::iter_keys().for_each(|id| { - let reward_acc = Self::create_reward_account(id); + let reward_acc = Self::generate_reward_account(id); let frozen_balance = T::Currency::balance_frozen(&FreezeReason::PoolMinBalance.into(), &reward_acc); @@ -3615,7 +3799,7 @@ impl Pallet { pub fn api_balance_to_points(pool_id: PoolId, new_funds: BalanceOf) -> BalanceOf { if let Some(pool) = BondedPool::::get(pool_id) { let bonded_balance = - T::Staking::active_stake(&pool.bonded_account()).unwrap_or(Zero::zero()); + T::StakeAdapter::active_stake(&Self::generate_bonded_account(pool_id)); Pallet::::balance_to_point(bonded_balance, pool.points, new_funds) } else { Zero::zero() diff --git a/substrate/frame/nomination-pools/src/migration.rs b/substrate/frame/nomination-pools/src/migration.rs index 796b310862afc..a3989559dfbb0 100644 --- a/substrate/frame/nomination-pools/src/migration.rs +++ b/substrate/frame/nomination-pools/src/migration.rs @@ -107,6 +107,137 @@ pub mod unversioned { Ok(()) } } + + /// Migrate existing pools from [`adapter::StakeStrategyType::Transfer`] to + /// [`adapter::StakeStrategyType::Delegate`]. + /// + /// Note: This only migrates the pools, the members are not migrated. They can use the + /// permission-less [`Pallet::migrate_delegation()`] to migrate their funds. + /// + /// This migration does not break any existing pool storage item, does not need to happen in any + /// sequence and hence can be applied unversioned on a production runtime. + /// + /// Takes `MaxPools` as type parameter to limit the number of pools that should be migrated in a + /// single block. It should be set such that migration weight does not exceed the block weight + /// limit. If all pools can be safely migrated, it is good to keep this number a little higher + /// than the actual number of pools to handle any extra pools created while the migration is + /// proposed, and before it is executed. + /// + /// If there are pools that fail to migrate or did not fit in the bounds, the remaining pools + /// can be migrated via the permission-less extrinsic [`Call::migrate_pool_to_delegate_stake`]. + pub struct DelegationStakeMigration(sp_std::marker::PhantomData<(T, MaxPools)>); + + impl> OnRuntimeUpgrade for DelegationStakeMigration { + fn on_runtime_upgrade() -> Weight { + let mut count: u32 = 0; + + BondedPools::::iter_keys().take(MaxPools::get() as usize).for_each(|id| { + let pool_acc = Pallet::::generate_bonded_account(id); + + // only migrate if the pool is in Transfer Strategy. + if T::StakeAdapter::pool_strategy(&pool_acc) == adapter::StakeStrategyType::Transfer + { + let _ = Pallet::::migrate_to_delegate_stake(id).map_err(|err| { + log!( + warn, + "failed to migrate pool {:?} to delegate stake strategy with err: {:?}", + id, + err + ) + }); + count.saturating_inc(); + } + }); + + log!(info, "migrated {:?} pools to delegate stake strategy", count); + + // reads: (bonded pool key + current pool strategy) * MaxPools (worst case) + T::DbWeight::get() + .reads_writes(2, 0) + .saturating_mul(MaxPools::get() as u64) + // migration weight: `pool_migrate` weight * count + .saturating_add(T::WeightInfo::pool_migrate().saturating_mul(count.into())) + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + // ensure stake adapter is correct. + ensure!( + T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate, + "Current strategy is not `Delegate" + ); + + if BondedPools::::count() > MaxPools::get() { + // we log a warning if the number of pools exceeds the bound. + log!( + warn, + "Number of pools {} exceeds the maximum bound {}. This would leave some pools unmigrated.", BondedPools::::count(), MaxPools::get() + ); + } + + let mut pool_balances: Vec> = Vec::new(); + BondedPools::::iter_keys().take(MaxPools::get() as usize).for_each(|id| { + let pool_account = Pallet::::generate_bonded_account(id); + let current_strategy = T::StakeAdapter::pool_strategy(&pool_account); + + // we ensure migration is idempotent. + let pool_balance = if current_strategy == adapter::StakeStrategyType::Transfer { + T::Currency::total_balance(&pool_account) + } else { + T::StakeAdapter::total_balance(&pool_account) + }; + + pool_balances.push(pool_balance); + }); + + Ok(pool_balances.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(data: Vec) -> Result<(), TryRuntimeError> { + let expected_pool_balances: Vec> = Decode::decode(&mut &data[..]).unwrap(); + + for (index, id) in + BondedPools::::iter_keys().take(MaxPools::get() as usize).enumerate() + { + let pool_account = Pallet::::generate_bonded_account(id); + if T::StakeAdapter::pool_strategy(&pool_account) == + adapter::StakeStrategyType::Transfer + { + log!(error, "Pool {} failed to migrate", id,); + return Err(TryRuntimeError::Other("Pool failed to migrate")); + } + + let actual_balance = T::StakeAdapter::total_balance(&pool_account); + let expected_balance = expected_pool_balances.get(index).unwrap(); + + if actual_balance != *expected_balance { + log!( + error, + "Pool {} balance mismatch. Expected: {:?}, Actual: {:?}", + id, + expected_balance, + actual_balance + ); + return Err(TryRuntimeError::Other("Pool balance mismatch")); + } + + // account balance should be zero. + let pool_account_balance = T::Currency::total_balance(&pool_account); + if pool_account_balance != Zero::zero() { + log!( + error, + "Pool account balance was expected to be zero. Pool: {}, Balance: {:?}", + id, + pool_account_balance + ); + return Err(TryRuntimeError::Other("Pool account balance not migrated")); + } + } + + Ok(()) + } + } } pub mod v8 { @@ -201,7 +332,7 @@ pub(crate) mod v7 { impl V7BondedPool { #[allow(dead_code)] fn bonded_account(&self) -> T::AccountId { - Pallet::::create_bonded_account(self.id) + Pallet::::generate_bonded_account(self.id) } } @@ -275,7 +406,7 @@ mod v6 { impl MigrateToV6 { fn freeze_ed(pool_id: PoolId) -> Result<(), ()> { - let reward_acc = Pallet::::create_reward_account(pool_id); + let reward_acc = Pallet::::generate_reward_account(pool_id); Pallet::::freeze_pool_deposit(&reward_acc).map_err(|e| { log!(error, "Failed to freeze ED for pool {} with error: {:?}", pool_id, e); () @@ -760,7 +891,7 @@ pub mod v2 { }; let accumulated_reward = RewardPool::::current_balance(id); - let reward_account = Pallet::::create_reward_account(id); + let reward_account = Pallet::::generate_reward_account(id); let mut sum_paid_out = BalanceOf::::zero(); members @@ -882,7 +1013,7 @@ pub mod v2 { // all reward accounts must have more than ED. RewardPools::::iter().try_for_each(|(id, _)| -> Result<(), TryRuntimeError> { ensure!( - >::balance(&Pallet::::create_reward_account(id)) >= + >::balance(&Pallet::::generate_reward_account(id)) >= T::Currency::minimum_balance(), "Reward accounts must have greater balance than ED." ); @@ -1022,11 +1153,8 @@ mod helpers { use super::*; pub(crate) fn calculate_tvl_by_total_stake() -> BalanceOf { - BondedPools::::iter() - .map(|(id, inner)| { - T::Staking::total_stake(&BondedPool { id, inner: inner.clone() }.bonded_account()) - .unwrap_or_default() - }) + BondedPools::::iter_keys() + .map(|id| T::StakeAdapter::total_stake(&Pallet::::generate_bonded_account(id))) .reduce(|acc, total_balance| acc + total_balance) .unwrap_or_default() } diff --git a/substrate/frame/nomination-pools/src/mock.rs b/substrate/frame/nomination-pools/src/mock.rs index e34719a7b80e8..b659c975a8395 100644 --- a/substrate/frame/nomination-pools/src/mock.rs +++ b/substrate/frame/nomination-pools/src/mock.rs @@ -36,12 +36,12 @@ pub type Currency = ::Currency; // Ext builder creates a pool with id 1. pub fn default_bonded_account() -> AccountId { - Pools::create_bonded_account(1) + Pools::generate_bonded_account(1) } // Ext builder creates a pool with id 1. pub fn default_reward_account() -> AccountId { - Pools::create_reward_account(1) + Pools::generate_reward_account(1) } parameter_types! { @@ -71,7 +71,7 @@ impl StakingMock { /// Does not modify any [`SubPools`] of the pool as [`Default::default`] is passed for /// `slashed_unlocking`. pub fn slash_by(pool_id: PoolId, amount: Balance) { - let acc = Pools::create_bonded_account(pool_id); + let acc = Pools::generate_bonded_account(pool_id); let bonded = BondedBalanceMap::get(); let pre_total = bonded.get(&acc).unwrap(); Self::set_bonded_balance(acc, pre_total - amount); @@ -111,6 +111,10 @@ impl sp_staking::StakingInterface for StakingMock { .ok_or(DispatchError::Other("NotStash")) } + fn is_virtual_staker(_who: &Self::AccountId) -> bool { + false + } + fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult { let mut x = BondedBalanceMap::get(); x.get_mut(who).map(|v| *v += extra); @@ -314,7 +318,7 @@ impl pools::Config for Runtime { type RewardCounter = RewardCounter; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; - type Staking = StakingMock; + type StakeAdapter = adapter::TransferStake; type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; type PalletId = PoolsPalletId; type MaxMetadataLen = MaxMetadataLen; diff --git a/substrate/frame/nomination-pools/src/tests.rs b/substrate/frame/nomination-pools/src/tests.rs index 535e7537469ec..8fc339c695bde 100644 --- a/substrate/frame/nomination-pools/src/tests.rs +++ b/substrate/frame/nomination-pools/src/tests.rs @@ -95,8 +95,8 @@ fn test_setup_works() { PoolMember:: { pool_id: last_pool, points: 10, ..Default::default() } ); - let bonded_account = Pools::create_bonded_account(last_pool); - let reward_account = Pools::create_reward_account(last_pool); + let bonded_account = Pools::generate_bonded_account(last_pool); + let reward_account = Pools::generate_reward_account(last_pool); // the bonded_account should be bonded by the depositor's funds. assert_eq!(StakingMock::active_stake(&bonded_account).unwrap(), 10); @@ -728,7 +728,7 @@ mod join { ); // Force the pools bonded balance to 0, simulating a 100% slash - StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 0); + StakingMock::set_bonded_balance(Pools::generate_bonded_account(1), 0); assert_noop!( Pools::join(RuntimeOrigin::signed(11), 420, 1), Error::::OverflowRisk @@ -755,7 +755,7 @@ mod join { <::MaxPointsToBalance as Get>::get().into(); StakingMock::set_bonded_balance( - Pools::create_bonded_account(123), + Pools::generate_bonded_account(123), max_points_to_balance, ); assert_noop!( @@ -764,7 +764,7 @@ mod join { ); StakingMock::set_bonded_balance( - Pools::create_bonded_account(123), + Pools::generate_bonded_account(123), Balance::MAX / max_points_to_balance, ); // Balance needs to be gt Balance::MAX / `MaxPointsToBalance` @@ -773,7 +773,10 @@ mod join { TokenError::FundsUnavailable, ); - StakingMock::set_bonded_balance(Pools::create_bonded_account(1), max_points_to_balance); + StakingMock::set_bonded_balance( + Pools::generate_bonded_account(1), + max_points_to_balance, + ); // Cannot join a pool that isn't open unsafe_set_state(123, PoolState::Blocked); @@ -804,7 +807,7 @@ mod join { #[cfg_attr(not(debug_assertions), should_panic)] fn join_panics_when_reward_pool_not_found() { ExtBuilder::default().build_and_execute(|| { - StakingMock::set_bonded_balance(Pools::create_bonded_account(123), 100); + StakingMock::set_bonded_balance(Pools::generate_bonded_account(123), 100); BondedPool:: { id: 123, inner: BondedPoolInner { @@ -1979,7 +1982,7 @@ mod claim_payout { assert_eq!(member_20.last_recorded_reward_counter, 0.into()); // pre-fund the reward account of pool id 3 with some funds. - Currency::set_balance(&Pools::create_reward_account(3), 10); + Currency::set_balance(&Pools::generate_reward_account(3), 10); // create pool 3 Currency::set_balance(&30, 100); @@ -1988,7 +1991,7 @@ mod claim_payout { // reward counter is still the same. let (member_30, _, reward_pool_30) = Pools::get_member_with_pools(&30).unwrap(); assert_eq!( - Currency::free_balance(&Pools::create_reward_account(3)), + Currency::free_balance(&Pools::generate_reward_account(3)), 10 + Currency::minimum_balance() ); @@ -4631,7 +4634,7 @@ mod withdraw_unbonded { // pool is destroyed. assert!(!Metadata::::contains_key(1)); // ensure the pool account is reaped. - assert!(!frame_system::Account::::contains_key(&Pools::create_bonded_account(1))); + assert!(!frame_system::Account::::contains_key(&Pools::generate_bonded_account(1))); }) } @@ -4639,7 +4642,7 @@ mod withdraw_unbonded { fn destroy_works_with_erroneous_extra_consumer() { ExtBuilder::default().ed(1).build_and_execute(|| { // 10 is the depositor for pool 1, with min join bond 10. - let pool_one = Pools::create_bonded_account(1); + let pool_one = Pools::generate_bonded_account(1); // set pool to destroying. unsafe_set_state(1, PoolState::Destroying); @@ -4690,7 +4693,7 @@ mod create { fn create_works() { ExtBuilder::default().build_and_execute(|| { // next pool id is 2. - let next_pool_stash = Pools::create_bonded_account(2); + let next_pool_stash = Pools::generate_bonded_account(2); let ed = Currency::minimum_balance(); assert_eq!(TotalValueLocked::::get(), 10); @@ -5011,6 +5014,13 @@ mod set_state { // surpassed. Making this pool destroyable by anyone. StakingMock::slash_by(1, 10); + // in mock we are using transfer stake which implies slash is greedy. Extrinsic to + // apply pending slash should fail. + assert_noop!( + Pools::apply_slash(RuntimeOrigin::signed(11), 10), + Error::::NotSupported + ); + // When assert_ok!(Pools::set_state(RuntimeOrigin::signed(11), 1, PoolState::Destroying)); // Then @@ -7473,3 +7483,61 @@ mod chill { }) } } + +// the test mock is using `TransferStake` and so `DelegateStake` is not tested here. Extrinsics +// meant for `DelegateStake` should be gated. +// +// `DelegateStake` tests are in `pallet-nomination-pools-test-delegate-stake`. Since we support both +// strategies currently, we keep these tests as it is but in future we may remove `TransferStake` +// completely. +mod delegate_stake { + use super::*; + #[test] + fn delegation_specific_calls_are_gated() { + ExtBuilder::default().with_check(0).build_and_execute(|| { + // Given + Currency::set_balance(&11, ExistentialDeposit::get() + 2); + assert!(!PoolMembers::::contains_key(11)); + + // When + assert_ok!(Pools::join(RuntimeOrigin::signed(11), 2, 1)); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 11, pool_id: 1, bonded: 2, joined: true }, + ] + ); + + assert_eq!( + PoolMembers::::get(11).unwrap(), + PoolMember:: { pool_id: 1, points: 2, ..Default::default() } + ); + + // ensure pool 1 cannot be migrated. + assert_noop!( + Pools::migrate_pool_to_delegate_stake(RuntimeOrigin::signed(10), 1), + Error::::NotSupported + ); + + // members cannot be migrated either. + assert_noop!( + Pools::migrate_delegation(RuntimeOrigin::signed(10), 11), + Error::::NotSupported + ); + + // Given + // The bonded balance is slashed in half + StakingMock::slash_by(1, 6); + + // since slash is greedy with `TransferStake`, `apply_slash` should not work either. + assert_noop!( + Pools::apply_slash(RuntimeOrigin::signed(10), 11), + Error::::NotSupported + ); + }); + } +} diff --git a/substrate/frame/nomination-pools/src/weights.rs b/substrate/frame/nomination-pools/src/weights.rs index 57ea8dc388f68..21711a499b623 100644 --- a/substrate/frame/nomination-pools/src/weights.rs +++ b/substrate/frame/nomination-pools/src/weights.rs @@ -18,27 +18,25 @@ //! Autogenerated weights for `pallet_nomination_pools` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-04-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-04-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-dcu62vjg-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/substrate-node +// target/production/substrate-node // benchmark // pallet -// --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet_nomination_pools -// --no-storage-info -// --no-median-slopes -// --no-min-squares // --extrinsic=* // --wasm-execution=compiled // --heap-pages=4096 -// --output=./substrate/frame/nomination-pools/src/weights.rs +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_nomination_pools +// --chain=dev // --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/nomination-pools/src/weights.rs // --template=./substrate/.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -73,6 +71,10 @@ pub trait WeightInfo { fn set_claim_permission() -> Weight; fn claim_commission() -> Weight; fn adjust_pool_deposit() -> Weight; + fn apply_slash() -> Weight; + fn apply_slash_fail() -> Weight; + fn pool_migrate() -> Weight; + fn migrate_delegation() -> Weight; } /// Weights for `pallet_nomination_pools` using the Substrate node and recommended hardware. @@ -100,6 +102,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `NominationPools::MaxPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `NominationPools::CounterForPoolMembers` (r:1 w:1) /// Proof: `NominationPools::CounterForPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) @@ -112,11 +116,11 @@ impl WeightInfo for SubstrateWeight { /// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn join() -> Weight { // Proof Size summary in bytes: - // Measured: `3425` + // Measured: `3458` // Estimated: `8877` - // Minimum execution time: 201_783_000 picoseconds. - Weight::from_parts(206_014_000, 8877) - .saturating_add(T::DbWeight::get().reads(20_u64)) + // Minimum execution time: 195_962_000 picoseconds. + Weight::from_parts(201_682_000, 8877) + .saturating_add(T::DbWeight::get().reads(21_u64)) .saturating_add(T::DbWeight::get().writes(13_u64)) } /// Storage: `NominationPools::PoolMembers` (r:1 w:1) @@ -133,6 +137,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) @@ -145,11 +151,11 @@ impl WeightInfo for SubstrateWeight { /// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn bond_extra_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `3435` + // Measured: `3468` // Estimated: `8877` - // Minimum execution time: 204_124_000 picoseconds. - Weight::from_parts(207_910_000, 8877) - .saturating_add(T::DbWeight::get().reads(17_u64)) + // Minimum execution time: 197_466_000 picoseconds. + Weight::from_parts(201_356_000, 8877) + .saturating_add(T::DbWeight::get().reads(18_u64)) .saturating_add(T::DbWeight::get().writes(13_u64)) } /// Storage: `NominationPools::ClaimPermissions` (r:1 w:0) @@ -168,6 +174,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) @@ -180,11 +188,11 @@ impl WeightInfo for SubstrateWeight { /// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn bond_extra_other() -> Weight { // Proof Size summary in bytes: - // Measured: `3500` + // Measured: `3533` // Estimated: `8877` - // Minimum execution time: 240_342_000 picoseconds. - Weight::from_parts(245_735_000, 8877) - .saturating_add(T::DbWeight::get().reads(18_u64)) + // Minimum execution time: 232_623_000 picoseconds. + Weight::from_parts(236_970_000, 8877) + .saturating_add(T::DbWeight::get().reads(19_u64)) .saturating_add(T::DbWeight::get().writes(14_u64)) } /// Storage: `NominationPools::ClaimPermissions` (r:1 w:0) @@ -203,8 +211,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1172` // Estimated: `3719` - // Minimum execution time: 81_054_000 picoseconds. - Weight::from_parts(83_324_000, 3719) + // Minimum execution time: 77_992_000 picoseconds. + Weight::from_parts(79_927_000, 3719) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -228,6 +236,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) /// Storage: `Staking::MinNominatorBond` (r:1 w:0) /// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) @@ -242,11 +252,11 @@ impl WeightInfo for SubstrateWeight { /// Proof: `NominationPools::CounterForSubPoolsStorage` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn unbond() -> Weight { // Proof Size summary in bytes: - // Measured: `3622` + // Measured: `3655` // Estimated: `27847` - // Minimum execution time: 188_835_000 picoseconds. - Weight::from_parts(192_565_000, 27847) - .saturating_add(T::DbWeight::get().reads(20_u64)) + // Minimum execution time: 182_368_000 picoseconds. + Weight::from_parts(185_387_000, 27847) + .saturating_add(T::DbWeight::get().reads(21_u64)) .saturating_add(T::DbWeight::get().writes(13_u64)) } /// Storage: `NominationPools::BondedPools` (r:1 w:0) @@ -257,6 +267,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:0) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) @@ -268,13 +280,13 @@ impl WeightInfo for SubstrateWeight { /// The range of component `s` is `[0, 100]`. fn pool_withdraw_unbonded(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1848` + // Measured: `1881` // Estimated: `4764` - // Minimum execution time: 73_556_000 picoseconds. - Weight::from_parts(76_075_881, 4764) - // Standard Error: 1_419 - .saturating_add(Weight::from_parts(54_476, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(8_u64)) + // Minimum execution time: 72_179_000 picoseconds. + Weight::from_parts(75_031_092, 4764) + // Standard Error: 1_487 + .saturating_add(Weight::from_parts(56_741, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } /// Storage: `NominationPools::PoolMembers` (r:1 w:1) @@ -289,6 +301,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) @@ -306,13 +320,13 @@ impl WeightInfo for SubstrateWeight { /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2238` + // Measured: `2271` // Estimated: `27847` - // Minimum execution time: 144_177_000 picoseconds. - Weight::from_parts(148_686_524, 27847) - // Standard Error: 2_475 - .saturating_add(Weight::from_parts(77_460, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(12_u64)) + // Minimum execution time: 137_277_000 picoseconds. + Weight::from_parts(143_537_793, 27847) + // Standard Error: 3_049 + .saturating_add(Weight::from_parts(71_178, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(13_u64)) .saturating_add(T::DbWeight::get().writes(9_u64)) } /// Storage: `NominationPools::PoolMembers` (r:1 w:1) @@ -329,6 +343,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::SlashingSpans` (r:1 w:0) /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:2 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:2 w:1) @@ -364,14 +380,14 @@ impl WeightInfo for SubstrateWeight { /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2525` + // Measured: `2558` // Estimated: `27847` - // Minimum execution time: 255_957_000 picoseconds. - Weight::from_parts(264_206_788, 27847) - // Standard Error: 4_229 - .saturating_add(Weight::from_parts(3_064, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(24_u64)) - .saturating_add(T::DbWeight::get().writes(20_u64)) + // Minimum execution time: 242_522_000 picoseconds. + Weight::from_parts(250_740_608, 27847) + // Standard Error: 4_517 + .saturating_add(Weight::from_parts(13_231, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(25_u64)) + .saturating_add(T::DbWeight::get().writes(21_u64)) } /// Storage: `NominationPools::LastPoolId` (r:1 w:1) /// Proof: `NominationPools::LastPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -393,12 +409,14 @@ impl WeightInfo for SubstrateWeight { /// Proof: `NominationPools::MaxPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `NominationPools::CounterForPoolMembers` (r:1 w:1) /// Proof: `NominationPools::CounterForPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:2 w:2) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:2 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:2 w:1) @@ -419,11 +437,11 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: - // Measured: `1284` + // Measured: `1317` // Estimated: `8538` - // Minimum execution time: 193_527_000 picoseconds. - Weight::from_parts(197_140_000, 8538) - .saturating_add(T::DbWeight::get().reads(24_u64)) + // Minimum execution time: 182_740_000 picoseconds. + Weight::from_parts(188_820_000, 8538) + .saturating_add(T::DbWeight::get().reads(25_u64)) .saturating_add(T::DbWeight::get().writes(17_u64)) } /// Storage: `NominationPools::BondedPools` (r:1 w:0) @@ -459,12 +477,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1976` + // Measured: `2009` // Estimated: `4556 + n * (2520 ±0)` - // Minimum execution time: 86_054_000 picoseconds. - Weight::from_parts(88_743_932, 4556) - // Standard Error: 12_699 - .saturating_add(Weight::from_parts(1_829_097, 0).saturating_mul(n.into())) + // Minimum execution time: 83_649_000 picoseconds. + Weight::from_parts(85_754_306, 4556) + // Standard Error: 12_757 + .saturating_add(Weight::from_parts(1_616_356, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(15_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(5_u64)) @@ -478,10 +496,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) fn set_state() -> Weight { // Proof Size summary in bytes: - // Measured: `1434` + // Measured: `1467` // Estimated: `4556` - // Minimum execution time: 34_544_000 picoseconds. - Weight::from_parts(35_910_000, 4556) + // Minimum execution time: 34_594_000 picoseconds. + Weight::from_parts(36_173_000, 4556) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -496,10 +514,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `532` // Estimated: `3735` - // Minimum execution time: 14_111_000 picoseconds. - Weight::from_parts(15_204_218, 3735) - // Standard Error: 226 - .saturating_add(Weight::from_parts(1_291, 0).saturating_mul(n.into())) + // Minimum execution time: 13_945_000 picoseconds. + Weight::from_parts(14_764_062, 3735) + // Standard Error: 127 + .saturating_add(Weight::from_parts(1_406, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -519,8 +537,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_524_000 picoseconds. - Weight::from_parts(4_882_000, 0) + // Minimum execution time: 4_523_000 picoseconds. + Weight::from_parts(4_727_000, 0) .saturating_add(T::DbWeight::get().writes(6_u64)) } /// Storage: `NominationPools::BondedPools` (r:1 w:1) @@ -529,8 +547,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `532` // Estimated: `3719` - // Minimum execution time: 17_975_000 picoseconds. - Weight::from_parts(18_549_000, 3719) + // Minimum execution time: 17_124_000 picoseconds. + Weight::from_parts(17_718_000, 3719) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -558,10 +576,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill() -> Weight { // Proof Size summary in bytes: - // Measured: `2143` + // Measured: `2176` // Estimated: `4556` - // Minimum execution time: 81_574_000 picoseconds. - Weight::from_parts(83_519_000, 4556) + // Minimum execution time: 78_293_000 picoseconds. + Weight::from_parts(81_177_000, 4556) .saturating_add(T::DbWeight::get().reads(11_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -577,8 +595,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `804` // Estimated: `3719` - // Minimum execution time: 35_015_000 picoseconds. - Weight::from_parts(36_159_000, 3719) + // Minimum execution time: 33_105_000 picoseconds. + Weight::from_parts(34_106_000, 3719) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -590,8 +608,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `572` // Estimated: `3719` - // Minimum execution time: 17_775_000 picoseconds. - Weight::from_parts(18_358_000, 3719) + // Minimum execution time: 16_710_000 picoseconds. + Weight::from_parts(17_269_000, 3719) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -601,8 +619,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `532` // Estimated: `3719` - // Minimum execution time: 16_997_000 picoseconds. - Weight::from_parts(18_041_000, 3719) + // Minimum execution time: 16_557_000 picoseconds. + Weight::from_parts(17_431_000, 3719) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -612,8 +630,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `532` // Estimated: `3719` - // Minimum execution time: 17_000_000 picoseconds. - Weight::from_parts(17_807_000, 3719) + // Minimum execution time: 16_723_000 picoseconds. + Weight::from_parts(17_155_000, 3719) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -625,8 +643,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `542` // Estimated: `3702` - // Minimum execution time: 14_803_000 picoseconds. - Weight::from_parts(15_401_000, 3702) + // Minimum execution time: 14_667_000 picoseconds. + Weight::from_parts(15_242_000, 3702) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -642,8 +660,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1002` // Estimated: `3719` - // Minimum execution time: 69_759_000 picoseconds. - Weight::from_parts(71_985_000, 3719) + // Minimum execution time: 64_219_000 picoseconds. + Weight::from_parts(66_718_000, 3719) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -659,11 +677,58 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `901` // Estimated: `4764` - // Minimum execution time: 73_829_000 picoseconds. - Weight::from_parts(75_966_000, 4764) + // Minimum execution time: 70_284_000 picoseconds. + Weight::from_parts(71_375_000, 4764) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } + /// Storage: `NominationPools::PoolMembers` (r:1 w:0) + /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(237), added: 2712, mode: `MaxEncodedLen`) + fn apply_slash() -> Weight { + // Proof Size summary in bytes: + // Measured: `694` + // Estimated: `3702` + // Minimum execution time: 13_403_000 picoseconds. + Weight::from_parts(14_064_000, 3702) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: `NominationPools::PoolMembers` (r:1 w:0) + /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(237), added: 2712, mode: `MaxEncodedLen`) + fn apply_slash_fail() -> Weight { + // Proof Size summary in bytes: + // Measured: `732` + // Estimated: `3702` + // Minimum execution time: 14_419_000 picoseconds. + Weight::from_parts(15_004_000, 3702) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + fn pool_migrate() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 759_000 picoseconds. + Weight::from_parts(819_000, 0) + } + /// Storage: `NominationPools::PoolMembers` (r:1 w:0) + /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(237), added: 2712, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::BondedPools` (r:1 w:0) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:1 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:1 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::SubPoolsStorage` (r:1 w:0) + /// Proof: `NominationPools::SubPoolsStorage` (`max_values`: None, `max_size`: Some(24382), added: 26857, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::MinJoinBond` (r:1 w:0) + /// Proof: `NominationPools::MinJoinBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn migrate_delegation() -> Weight { + // Proof Size summary in bytes: + // Measured: `1648` + // Estimated: `27847` + // Minimum execution time: 36_192_000 picoseconds. + Weight::from_parts(37_038_000, 27847) + .saturating_add(T::DbWeight::get().reads(6_u64)) + } } // For backwards compatibility and tests. @@ -690,6 +755,8 @@ impl WeightInfo for () { /// Proof: `NominationPools::MaxPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `NominationPools::CounterForPoolMembers` (r:1 w:1) /// Proof: `NominationPools::CounterForPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) @@ -702,11 +769,11 @@ impl WeightInfo for () { /// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn join() -> Weight { // Proof Size summary in bytes: - // Measured: `3425` + // Measured: `3458` // Estimated: `8877` - // Minimum execution time: 201_783_000 picoseconds. - Weight::from_parts(206_014_000, 8877) - .saturating_add(RocksDbWeight::get().reads(20_u64)) + // Minimum execution time: 195_962_000 picoseconds. + Weight::from_parts(201_682_000, 8877) + .saturating_add(RocksDbWeight::get().reads(21_u64)) .saturating_add(RocksDbWeight::get().writes(13_u64)) } /// Storage: `NominationPools::PoolMembers` (r:1 w:1) @@ -723,6 +790,8 @@ impl WeightInfo for () { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) @@ -735,11 +804,11 @@ impl WeightInfo for () { /// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn bond_extra_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `3435` + // Measured: `3468` // Estimated: `8877` - // Minimum execution time: 204_124_000 picoseconds. - Weight::from_parts(207_910_000, 8877) - .saturating_add(RocksDbWeight::get().reads(17_u64)) + // Minimum execution time: 197_466_000 picoseconds. + Weight::from_parts(201_356_000, 8877) + .saturating_add(RocksDbWeight::get().reads(18_u64)) .saturating_add(RocksDbWeight::get().writes(13_u64)) } /// Storage: `NominationPools::ClaimPermissions` (r:1 w:0) @@ -758,6 +827,8 @@ impl WeightInfo for () { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) @@ -770,11 +841,11 @@ impl WeightInfo for () { /// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn bond_extra_other() -> Weight { // Proof Size summary in bytes: - // Measured: `3500` + // Measured: `3533` // Estimated: `8877` - // Minimum execution time: 240_342_000 picoseconds. - Weight::from_parts(245_735_000, 8877) - .saturating_add(RocksDbWeight::get().reads(18_u64)) + // Minimum execution time: 232_623_000 picoseconds. + Weight::from_parts(236_970_000, 8877) + .saturating_add(RocksDbWeight::get().reads(19_u64)) .saturating_add(RocksDbWeight::get().writes(14_u64)) } /// Storage: `NominationPools::ClaimPermissions` (r:1 w:0) @@ -793,8 +864,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1172` // Estimated: `3719` - // Minimum execution time: 81_054_000 picoseconds. - Weight::from_parts(83_324_000, 3719) + // Minimum execution time: 77_992_000 picoseconds. + Weight::from_parts(79_927_000, 3719) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -818,6 +889,8 @@ impl WeightInfo for () { /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) /// Storage: `Staking::MinNominatorBond` (r:1 w:0) /// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) @@ -832,11 +905,11 @@ impl WeightInfo for () { /// Proof: `NominationPools::CounterForSubPoolsStorage` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn unbond() -> Weight { // Proof Size summary in bytes: - // Measured: `3622` + // Measured: `3655` // Estimated: `27847` - // Minimum execution time: 188_835_000 picoseconds. - Weight::from_parts(192_565_000, 27847) - .saturating_add(RocksDbWeight::get().reads(20_u64)) + // Minimum execution time: 182_368_000 picoseconds. + Weight::from_parts(185_387_000, 27847) + .saturating_add(RocksDbWeight::get().reads(21_u64)) .saturating_add(RocksDbWeight::get().writes(13_u64)) } /// Storage: `NominationPools::BondedPools` (r:1 w:0) @@ -847,6 +920,8 @@ impl WeightInfo for () { /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:0) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) @@ -858,13 +933,13 @@ impl WeightInfo for () { /// The range of component `s` is `[0, 100]`. fn pool_withdraw_unbonded(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1848` + // Measured: `1881` // Estimated: `4764` - // Minimum execution time: 73_556_000 picoseconds. - Weight::from_parts(76_075_881, 4764) - // Standard Error: 1_419 - .saturating_add(Weight::from_parts(54_476, 0).saturating_mul(s.into())) - .saturating_add(RocksDbWeight::get().reads(8_u64)) + // Minimum execution time: 72_179_000 picoseconds. + Weight::from_parts(75_031_092, 4764) + // Standard Error: 1_487 + .saturating_add(Weight::from_parts(56_741, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } /// Storage: `NominationPools::PoolMembers` (r:1 w:1) @@ -879,6 +954,8 @@ impl WeightInfo for () { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) @@ -896,13 +973,13 @@ impl WeightInfo for () { /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2238` + // Measured: `2271` // Estimated: `27847` - // Minimum execution time: 144_177_000 picoseconds. - Weight::from_parts(148_686_524, 27847) - // Standard Error: 2_475 - .saturating_add(Weight::from_parts(77_460, 0).saturating_mul(s.into())) - .saturating_add(RocksDbWeight::get().reads(12_u64)) + // Minimum execution time: 137_277_000 picoseconds. + Weight::from_parts(143_537_793, 27847) + // Standard Error: 3_049 + .saturating_add(Weight::from_parts(71_178, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(13_u64)) .saturating_add(RocksDbWeight::get().writes(9_u64)) } /// Storage: `NominationPools::PoolMembers` (r:1 w:1) @@ -919,6 +996,8 @@ impl WeightInfo for () { /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::SlashingSpans` (r:1 w:0) /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:2 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:2 w:1) @@ -954,14 +1033,14 @@ impl WeightInfo for () { /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2525` + // Measured: `2558` // Estimated: `27847` - // Minimum execution time: 255_957_000 picoseconds. - Weight::from_parts(264_206_788, 27847) - // Standard Error: 4_229 - .saturating_add(Weight::from_parts(3_064, 0).saturating_mul(s.into())) - .saturating_add(RocksDbWeight::get().reads(24_u64)) - .saturating_add(RocksDbWeight::get().writes(20_u64)) + // Minimum execution time: 242_522_000 picoseconds. + Weight::from_parts(250_740_608, 27847) + // Standard Error: 4_517 + .saturating_add(Weight::from_parts(13_231, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(25_u64)) + .saturating_add(RocksDbWeight::get().writes(21_u64)) } /// Storage: `NominationPools::LastPoolId` (r:1 w:1) /// Proof: `NominationPools::LastPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -983,12 +1062,14 @@ impl WeightInfo for () { /// Proof: `NominationPools::MaxPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `NominationPools::CounterForPoolMembers` (r:1 w:1) /// Proof: `NominationPools::CounterForPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:2 w:2) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:2 w:1) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:2 w:1) @@ -1009,11 +1090,11 @@ impl WeightInfo for () { /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn create() -> Weight { // Proof Size summary in bytes: - // Measured: `1284` + // Measured: `1317` // Estimated: `8538` - // Minimum execution time: 193_527_000 picoseconds. - Weight::from_parts(197_140_000, 8538) - .saturating_add(RocksDbWeight::get().reads(24_u64)) + // Minimum execution time: 182_740_000 picoseconds. + Weight::from_parts(188_820_000, 8538) + .saturating_add(RocksDbWeight::get().reads(25_u64)) .saturating_add(RocksDbWeight::get().writes(17_u64)) } /// Storage: `NominationPools::BondedPools` (r:1 w:0) @@ -1049,12 +1130,12 @@ impl WeightInfo for () { /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1976` + // Measured: `2009` // Estimated: `4556 + n * (2520 ±0)` - // Minimum execution time: 86_054_000 picoseconds. - Weight::from_parts(88_743_932, 4556) - // Standard Error: 12_699 - .saturating_add(Weight::from_parts(1_829_097, 0).saturating_mul(n.into())) + // Minimum execution time: 83_649_000 picoseconds. + Weight::from_parts(85_754_306, 4556) + // Standard Error: 12_757 + .saturating_add(Weight::from_parts(1_616_356, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(15_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(5_u64)) @@ -1068,10 +1149,10 @@ impl WeightInfo for () { /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) fn set_state() -> Weight { // Proof Size summary in bytes: - // Measured: `1434` + // Measured: `1467` // Estimated: `4556` - // Minimum execution time: 34_544_000 picoseconds. - Weight::from_parts(35_910_000, 4556) + // Minimum execution time: 34_594_000 picoseconds. + Weight::from_parts(36_173_000, 4556) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1086,10 +1167,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `532` // Estimated: `3735` - // Minimum execution time: 14_111_000 picoseconds. - Weight::from_parts(15_204_218, 3735) - // Standard Error: 226 - .saturating_add(Weight::from_parts(1_291, 0).saturating_mul(n.into())) + // Minimum execution time: 13_945_000 picoseconds. + Weight::from_parts(14_764_062, 3735) + // Standard Error: 127 + .saturating_add(Weight::from_parts(1_406, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1109,8 +1190,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_524_000 picoseconds. - Weight::from_parts(4_882_000, 0) + // Minimum execution time: 4_523_000 picoseconds. + Weight::from_parts(4_727_000, 0) .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `NominationPools::BondedPools` (r:1 w:1) @@ -1119,8 +1200,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `532` // Estimated: `3719` - // Minimum execution time: 17_975_000 picoseconds. - Weight::from_parts(18_549_000, 3719) + // Minimum execution time: 17_124_000 picoseconds. + Weight::from_parts(17_718_000, 3719) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1148,10 +1229,10 @@ impl WeightInfo for () { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill() -> Weight { // Proof Size summary in bytes: - // Measured: `2143` + // Measured: `2176` // Estimated: `4556` - // Minimum execution time: 81_574_000 picoseconds. - Weight::from_parts(83_519_000, 4556) + // Minimum execution time: 78_293_000 picoseconds. + Weight::from_parts(81_177_000, 4556) .saturating_add(RocksDbWeight::get().reads(11_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -1167,8 +1248,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `804` // Estimated: `3719` - // Minimum execution time: 35_015_000 picoseconds. - Weight::from_parts(36_159_000, 3719) + // Minimum execution time: 33_105_000 picoseconds. + Weight::from_parts(34_106_000, 3719) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1180,8 +1261,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `572` // Estimated: `3719` - // Minimum execution time: 17_775_000 picoseconds. - Weight::from_parts(18_358_000, 3719) + // Minimum execution time: 16_710_000 picoseconds. + Weight::from_parts(17_269_000, 3719) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1191,8 +1272,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `532` // Estimated: `3719` - // Minimum execution time: 16_997_000 picoseconds. - Weight::from_parts(18_041_000, 3719) + // Minimum execution time: 16_557_000 picoseconds. + Weight::from_parts(17_431_000, 3719) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1202,8 +1283,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `532` // Estimated: `3719` - // Minimum execution time: 17_000_000 picoseconds. - Weight::from_parts(17_807_000, 3719) + // Minimum execution time: 16_723_000 picoseconds. + Weight::from_parts(17_155_000, 3719) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1215,8 +1296,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `542` // Estimated: `3702` - // Minimum execution time: 14_803_000 picoseconds. - Weight::from_parts(15_401_000, 3702) + // Minimum execution time: 14_667_000 picoseconds. + Weight::from_parts(15_242_000, 3702) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1232,8 +1313,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1002` // Estimated: `3719` - // Minimum execution time: 69_759_000 picoseconds. - Weight::from_parts(71_985_000, 3719) + // Minimum execution time: 64_219_000 picoseconds. + Weight::from_parts(66_718_000, 3719) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1249,9 +1330,56 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `901` // Estimated: `4764` - // Minimum execution time: 73_829_000 picoseconds. - Weight::from_parts(75_966_000, 4764) + // Minimum execution time: 70_284_000 picoseconds. + Weight::from_parts(71_375_000, 4764) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } + /// Storage: `NominationPools::PoolMembers` (r:1 w:0) + /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(237), added: 2712, mode: `MaxEncodedLen`) + fn apply_slash() -> Weight { + // Proof Size summary in bytes: + // Measured: `694` + // Estimated: `3702` + // Minimum execution time: 13_403_000 picoseconds. + Weight::from_parts(14_064_000, 3702) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: `NominationPools::PoolMembers` (r:1 w:0) + /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(237), added: 2712, mode: `MaxEncodedLen`) + fn apply_slash_fail() -> Weight { + // Proof Size summary in bytes: + // Measured: `732` + // Estimated: `3702` + // Minimum execution time: 14_419_000 picoseconds. + Weight::from_parts(15_004_000, 3702) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + fn pool_migrate() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 759_000 picoseconds. + Weight::from_parts(819_000, 0) + } + /// Storage: `NominationPools::PoolMembers` (r:1 w:0) + /// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(237), added: 2712, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::BondedPools` (r:1 w:0) + /// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:1 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:1 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::SubPoolsStorage` (r:1 w:0) + /// Proof: `NominationPools::SubPoolsStorage` (`max_values`: None, `max_size`: Some(24382), added: 26857, mode: `MaxEncodedLen`) + /// Storage: `NominationPools::MinJoinBond` (r:1 w:0) + /// Proof: `NominationPools::MinJoinBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + fn migrate_delegation() -> Weight { + // Proof Size summary in bytes: + // Measured: `1648` + // Estimated: `27847` + // Minimum execution time: 36_192_000 picoseconds. + Weight::from_parts(37_038_000, 27847) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + } } diff --git a/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml b/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml new file mode 100644 index 0000000000000..ea8eb20696931 --- /dev/null +++ b/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "pallet-nomination-pools-test-delegate-stake" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage = "https://substrate.io" +repository.workspace = true +description = "FRAME nomination pools pallet tests with the staking pallet" +publish = false + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dev-dependencies] +codec = { package = "parity-scale-codec", version = "3.6.12", features = ["derive"] } +scale-info = { version = "2.11.1", features = ["derive"] } + +sp-runtime = { path = "../../../primitives/runtime" } +sp-io = { path = "../../../primitives/io" } +sp-std = { path = "../../../primitives/std" } +sp-staking = { path = "../../../primitives/staking" } +sp-core = { path = "../../../primitives/core" } + +frame-system = { path = "../../system" } +frame-support = { path = "../../support" } +frame-election-provider-support = { path = "../../election-provider-support" } + +pallet-timestamp = { path = "../../timestamp" } +pallet-balances = { path = "../../balances" } +pallet-staking = { path = "../../staking" } +pallet-delegated-staking = { path = "../../delegated-staking" } +pallet-bags-list = { path = "../../bags-list" } +pallet-staking-reward-curve = { path = "../../staking/reward-curve" } +pallet-nomination-pools = { path = ".." } + +sp-tracing = { path = "../../../primitives/tracing" } +log = { workspace = true, default-features = true } diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs new file mode 100644 index 0000000000000..d3235760ed23b --- /dev/null +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs @@ -0,0 +1,1158 @@ +// 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. + +#![cfg(test)] + +mod mock; + +use frame_support::{ + assert_noop, assert_ok, + traits::{fungible::InspectHold, Currency}, +}; +use mock::*; +use pallet_nomination_pools::{ + BondExtra, BondedPools, Error as PoolsError, Event as PoolsEvent, LastPoolId, PoolMember, + PoolMembers, PoolState, +}; +use pallet_staking::{ + CurrentEra, Error as StakingError, Event as StakingEvent, Payee, RewardDestination, +}; + +use pallet_delegated_staking::{Error as DelegatedStakingError, Event as DelegatedStakingEvent}; + +use sp_runtime::{bounded_btree_map, traits::Zero}; + +#[test] +fn pool_lifecycle_e2e() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::minimum_balance(), 5); + assert_eq!(Staking::current_era(), None); + + // create the pool, we know this has id 1. + assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10)); + assert_eq!(LastPoolId::::get(), 1); + + // have the pool nominate. + assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3])); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 50 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: 10, pool_id: 1 }, + PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true }, + ] + ); + + // have two members join + assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1)); + assert_ok!(Pools::join(RuntimeOrigin::signed(21), 10, 1)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, + StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, + PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 10, joined: true }, + ] + ); + + // pool goes into destroying + assert_ok!(Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Destroying)); + + // depositor cannot unbond yet. + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(10), 10, 50), + PoolsError::::MinimumBondNotMet, + ); + + // now the members want to unbond. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10)); + + assert_eq!(PoolMembers::::get(20).unwrap().unbonding_eras.len(), 1); + assert_eq!(PoolMembers::::get(20).unwrap().points, 0); + assert_eq!(PoolMembers::::get(21).unwrap().unbonding_eras.len(), 1); + assert_eq!(PoolMembers::::get(21).unwrap().points, 0); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, + PoolsEvent::Unbonded { member: 20, pool_id: 1, points: 10, balance: 10, era: 3 }, + PoolsEvent::Unbonded { member: 21, pool_id: 1, points: 10, balance: 10, era: 3 }, + ] + ); + + // depositor cannot still unbond + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(10), 10, 50), + PoolsError::::MinimumBondNotMet, + ); + + for e in 1..BondingDuration::get() { + CurrentEra::::set(Some(e)); + assert_noop!( + Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0), + PoolsError::::CannotWithdrawAny + ); + } + + // members are now unlocked. + CurrentEra::::set(Some(BondingDuration::get())); + + // depositor cannot still unbond + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(10), 10, 50), + PoolsError::::MinimumBondNotMet, + ); + + // but members can now withdraw. + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0)); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0)); + assert!(PoolMembers::::get(20).is_none()); + assert!(PoolMembers::::get(21).is_none()); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 20 },] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Withdrawn { member: 20, pool_id: 1, points: 10, balance: 10 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 20 }, + PoolsEvent::Withdrawn { member: 21, pool_id: 1, points: 10, balance: 10 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 21 }, + ] + ); + + // as soon as all members have left, the depositor can try to unbond, but since the + // min-nominator intention is set, they must chill first. + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(10), 10, 50), + pallet_staking::Error::::InsufficientBond + ); + + assert_ok!(Pools::chill(RuntimeOrigin::signed(10), 1)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 50)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Chilled { stash: POOL1_BONDED }, + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 50 }, + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 50, balance: 50, era: 6 }] + ); + + // waiting another bonding duration: + CurrentEra::::set(Some(BondingDuration::get() * 2)); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 1)); + + // pools is fully destroyed now. + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 50 },] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Withdrawn { member: 10, pool_id: 1, points: 50, balance: 50 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 10 }, + PoolsEvent::Destroyed { pool_id: 1 } + ] + ); + }) +} + +#[test] +fn pool_chill_e2e() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::minimum_balance(), 5); + assert_eq!(Staking::current_era(), None); + + // create the pool, we know this has id 1. + assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10)); + assert_eq!(LastPoolId::::get(), 1); + + // have the pool nominate. + assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3])); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 50 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: 10, pool_id: 1 }, + PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true }, + ] + ); + + // have two members join + assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1)); + assert_ok!(Pools::join(RuntimeOrigin::signed(21), 10, 1)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, + StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, + PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 10, joined: true }, + ] + ); + + // in case depositor does not have more than `MinNominatorBond` staked, we can end up in + // situation where a member unbonding would cause pool balance to drop below + // `MinNominatorBond` and hence not allowed. This can happen if the `MinNominatorBond` is + // increased after the pool is created. + assert_ok!(Staking::set_staking_configs( + RuntimeOrigin::root(), + pallet_staking::ConfigOp::Set(55), // minimum nominator bond + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + )); + + // members can unbond as long as total stake of the pool is above min nominator bond + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10),); + assert_eq!(PoolMembers::::get(20).unwrap().unbonding_eras.len(), 1); + assert_eq!(PoolMembers::::get(20).unwrap().points, 0); + + // this member cannot unbond since it will cause `pool stake < MinNominatorBond` + assert_noop!( + Pools::unbond(RuntimeOrigin::signed(21), 21, 10), + StakingError::::InsufficientBond, + ); + + // members can call `chill` permissionlessly now + assert_ok!(Pools::chill(RuntimeOrigin::signed(20), 1)); + + // now another member can unbond. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10)); + assert_eq!(PoolMembers::::get(21).unwrap().unbonding_eras.len(), 1); + assert_eq!(PoolMembers::::get(21).unwrap().points, 0); + + // nominator can not resume nomination until depositor have enough stake + assert_noop!( + Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]), + PoolsError::::MinimumBondNotMet, + ); + + // other members joining pool does not affect the depositor's ability to resume nomination + assert_ok!(Pools::join(RuntimeOrigin::signed(22), 10, 1)); + + assert_noop!( + Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]), + PoolsError::::MinimumBondNotMet, + ); + + // depositor can bond extra stake + assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10))); + + // `chill` can not be called permissionlessly anymore + assert_noop!( + Pools::chill(RuntimeOrigin::signed(20), 1), + PoolsError::::NotNominator, + ); + + // now nominator can resume nomination + assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3])); + + // skip to make the unbonding period end. + CurrentEra::::set(Some(BondingDuration::get())); + + // members can now withdraw. + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0)); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, + StakingEvent::Chilled { stash: POOL1_BONDED }, + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, + StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, // other member bonding + StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, // depositor bond extra + StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 20 }, + ] + ); + }) +} + +#[test] +fn pool_slash_e2e() { + new_test_ext().execute_with(|| { + ExistentialDeposit::set(1); + assert_eq!(Balances::minimum_balance(), 1); + assert_eq!(Staking::current_era(), None); + + // create the pool, we know this has id 1. + assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10)); + assert_eq!(LastPoolId::::get(), 1); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: 10, pool_id: 1 }, + PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, + ] + ); + + assert_eq!( + Payee::::get(POOL1_BONDED), + Some(RewardDestination::Account(POOL1_REWARD)) + ); + + // have two members join + assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1)); + assert_ok!(Pools::join(RuntimeOrigin::signed(21), 20, 1)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Bonded { stash: POOL1_BONDED, amount: 20 }, + StakingEvent::Bonded { stash: POOL1_BONDED, amount: 20 } + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 20, joined: true }, + ] + ); + + // now let's progress a bit. + CurrentEra::::set(Some(1)); + + // 20 / 80 of the total funds are unlocked, and safe from any further slash. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 10)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 } + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 4 }, + PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 4 } + ] + ); + + CurrentEra::::set(Some(2)); + + // note: depositor cannot fully unbond at this point. + // these funds will still get slashed. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 10)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, + ] + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 5 }, + PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 5 }, + PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 10, points: 10, era: 5 }, + ] + ); + + // At this point, 20 are safe from slash, 30 are unlocking but vulnerable to slash, and and + // another 30 are active and vulnerable to slash. Let's slash half of them. + pallet_staking::slashing::do_slash::( + &POOL1_BONDED, + 30, + &mut Default::default(), + &mut Default::default(), + 2, // slash era 2, affects chunks at era 5 onwards. + ); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 30 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + // 30 has been slashed to 15 (15 slash) + PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 5, balance: 15 }, + // 30 has been slashed to 15 (15 slash) + PoolsEvent::PoolSlashed { pool_id: 1, balance: 15 } + ] + ); + + CurrentEra::::set(Some(3)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10)); + + assert_eq!( + PoolMembers::::get(21).unwrap(), + PoolMember { + pool_id: 1, + points: 0, + last_recorded_reward_counter: Zero::zero(), + // the 10 points unlocked just now correspond to 5 points in the unbond pool. + unbonding_eras: bounded_btree_map!(5 => 10, 6 => 5) + } + ); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 5 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 5, points: 5, era: 6 }] + ); + + // now we start withdrawing. we do it all at once, at era 6 where 20 and 21 are fully free. + CurrentEra::::set(Some(6)); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0)); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0)); + + assert_eq!( + pool_events_since_last_call(), + vec![ + // 20 had unbonded 10 safely, and 10 got slashed by half. + PoolsEvent::Withdrawn { member: 20, pool_id: 1, balance: 10 + 5, points: 20 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 20 }, + // 21 unbonded all of it after the slash + PoolsEvent::Withdrawn { member: 21, pool_id: 1, balance: 5 + 5, points: 15 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 21 } + ] + ); + assert_eq!( + staking_events_since_last_call(), + // a 10 (un-slashed) + 10/2 (slashed) balance from 10 has also been unlocked + vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 15 + 10 + 15 }] + ); + + // now, finally, we can unbond the depositor further than their current limit. + assert_ok!(Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Destroying)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 20)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, + PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 10, balance: 10, era: 9 } + ] + ); + + CurrentEra::::set(Some(9)); + assert_eq!( + PoolMembers::::get(10).unwrap(), + PoolMember { + pool_id: 1, + points: 0, + last_recorded_reward_counter: Zero::zero(), + unbonding_eras: bounded_btree_map!(4 => 10, 5 => 10, 9 => 10) + } + ); + // withdraw the depositor, they should lose 12 balance in total due to slash. + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 10 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Withdrawn { member: 10, pool_id: 1, balance: 10 + 15, points: 30 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 10 }, + PoolsEvent::Destroyed { pool_id: 1 } + ] + ); + }); +} + +#[test] +fn pool_slash_proportional() { + // a typical example where 3 pool members unbond in era 99, 100, and 101, and a slash that + // happened in era 100 should only affect the latter two. + new_test_ext().execute_with(|| { + ExistentialDeposit::set(1); + BondingDuration::set(28); + assert_eq!(Balances::minimum_balance(), 1); + assert_eq!(Staking::current_era(), None); + + // create the pool, we know this has id 1. + assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10)); + assert_eq!(LastPoolId::::get(), 1); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }] + ); + assert_eq!( + delegated_staking_events_since_last_call(), + vec![DelegatedStakingEvent::Delegated { + agent: POOL1_BONDED, + delegator: 10, + amount: 40 + }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: 10, pool_id: 1 }, + PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, + ] + ); + + // have two members join + let bond = 20; + assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1)); + assert_ok!(Pools::join(RuntimeOrigin::signed(21), bond, 1)); + assert_ok!(Pools::join(RuntimeOrigin::signed(22), bond, 1)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }, + StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }, + StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }, + ] + ); + assert_eq!( + delegated_staking_events_since_last_call(), + vec![ + DelegatedStakingEvent::Delegated { + agent: POOL1_BONDED, + delegator: 20, + amount: bond + }, + DelegatedStakingEvent::Delegated { + agent: POOL1_BONDED, + delegator: 21, + amount: bond + }, + DelegatedStakingEvent::Delegated { + agent: POOL1_BONDED, + delegator: 22, + amount: bond + } + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }, + PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: bond, joined: true }, + PoolsEvent::Bonded { member: 22, pool_id: 1, bonded: bond, joined: true }, + ] + ); + + // now let's progress a lot. + CurrentEra::::set(Some(99)); + + // and unbond + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { + member: 20, + pool_id: 1, + balance: bond, + points: bond, + era: 127 + }] + ); + + CurrentEra::::set(Some(100)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, bond)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { + member: 21, + pool_id: 1, + balance: bond, + points: bond, + era: 128 + }] + ); + + CurrentEra::::set(Some(101)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(22), 22, bond)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { + member: 22, + pool_id: 1, + balance: bond, + points: bond, + era: 129 + }] + ); + + // Apply a slash that happened in era 100. This is typically applied with a delay. + // Of the total 100, 50 is slashed. + assert_eq!(BondedPools::::get(1).unwrap().points, 40); + pallet_staking::slashing::do_slash::( + &POOL1_BONDED, + 50, + &mut Default::default(), + &mut Default::default(), + 100, + ); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 50 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + // This era got slashed 12.5, which rounded up to 13. + PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 128, balance: 7 }, + // This era got slashed 12 instead of 12.5 because an earlier chunk got 0.5 more + // slashed, and 12 is all the remaining slash + PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 129, balance: 8 }, + // Bonded pool got slashed for 25, remaining 15 in it. + PoolsEvent::PoolSlashed { pool_id: 1, balance: 15 } + ] + ); + + // 21's balance in the pool is slashed. + assert_eq!(PoolMembers::::get(21).unwrap().total_balance(), 7); + // But their actual balance is still unslashed. + assert_eq!(Balances::total_balance_on_hold(&21), bond); + // apply slash permissionlessly. + assert_ok!(Pools::apply_slash(RuntimeOrigin::signed(10), 21)); + // member balance is slashed. + assert_eq!(Balances::total_balance_on_hold(&21), 7); + + assert_eq!( + delegated_staking_events_since_last_call(), + vec![DelegatedStakingEvent::Slashed { + agent: POOL1_BONDED, + delegator: 21, + amount: bond - 7 + }] + ); + + // 22 balance isn't slashed yet as well. + assert_eq!(PoolMembers::::get(22).unwrap().total_balance(), 8); + assert_eq!(Balances::total_balance_on_hold(&22), bond); + + // they try to withdraw. This should slash them. + CurrentEra::::set(Some(129)); + let pre_balance = Balances::free_balance(&22); + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(22), 22, 0)); + // all balance should be released. + assert_eq!(Balances::total_balance_on_hold(&22), 0); + assert_eq!(Balances::free_balance(&22), pre_balance + 8); + + assert_eq!( + delegated_staking_events_since_last_call(), + vec![ + DelegatedStakingEvent::Slashed { + agent: POOL1_BONDED, + delegator: 22, + amount: bond - 8 + }, + DelegatedStakingEvent::Released { agent: POOL1_BONDED, delegator: 22, amount: 8 }, + ] + ); + }); +} + +#[test] +fn pool_slash_non_proportional_only_bonded_pool() { + // A typical example where a pool member unbonds in era 99, and they can get away with a slash + // that happened in era 100, as long as the pool has enough active bond to cover the slash. If + // everything else in the slashing/staking system works, this should always be the case. + // Nonetheless, `ledger.slash` has been written such that it will slash greedily from any chunk + // if it runs out of chunks that it thinks should be affected by the slash. + new_test_ext().execute_with(|| { + ExistentialDeposit::set(1); + BondingDuration::set(28); + assert_eq!(Balances::minimum_balance(), 1); + assert_eq!(Staking::current_era(), None); + + // create the pool, we know this has id 1. + assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: 10, pool_id: 1 }, + PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, + ] + ); + + // have two members join + let bond = 20; + assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }] + ); + + // progress and unbond. + CurrentEra::::set(Some(99)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { + member: 20, + pool_id: 1, + balance: bond, + points: bond, + era: 127 + }] + ); + + // slash for 30. This will be deducted only from the bonded pool. + CurrentEra::::set(Some(100)); + assert_eq!(BondedPools::::get(1).unwrap().points, 40); + pallet_staking::slashing::do_slash::( + &POOL1_BONDED, + 30, + &mut Default::default(), + &mut Default::default(), + 100, + ); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 30 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::PoolSlashed { pool_id: 1, balance: 10 }] + ); + }); +} + +#[test] +fn pool_slash_non_proportional_bonded_pool_and_chunks() { + // An uncommon example where even though some funds are unlocked such that they should not be + // affected by a slash, we still slash out of them. This should not happen at all. If a + // nomination has unbonded, from the next era onwards, their exposure will drop, so if an era + // happens in that era, then their share of that slash should naturally be less, such that only + // their active ledger stake is enough to compensate it. + new_test_ext().execute_with(|| { + ExistentialDeposit::set(1); + BondingDuration::set(28); + assert_eq!(Balances::minimum_balance(), 1); + assert_eq!(Staking::current_era(), None); + + // create the pool, we know this has id 1. + assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: 10, pool_id: 1 }, + PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, + ] + ); + + // have two members join + let bond = 20; + assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }] + ); + + // progress and unbond. + CurrentEra::::set(Some(99)); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { + member: 20, + pool_id: 1, + balance: bond, + points: bond, + era: 127 + }] + ); + + // slash 50. This will be deducted only from the bonded pool and one of the unbonding pools. + CurrentEra::::set(Some(100)); + assert_eq!(BondedPools::::get(1).unwrap().points, 40); + pallet_staking::slashing::do_slash::( + &POOL1_BONDED, + 50, + &mut Default::default(), + &mut Default::default(), + 100, + ); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 50 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + // out of 20, 10 was taken. + PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 127, balance: 10 }, + // out of 40, all was taken. + PoolsEvent::PoolSlashed { pool_id: 1, balance: 0 } + ] + ); + }); +} +#[test] +fn pool_migration_e2e() { + new_test_ext().execute_with(|| { + LegacyAdapter::set(true); + assert_eq!(Balances::minimum_balance(), 5); + assert_eq!(Staking::current_era(), None); + + // create the pool with TransferStake strategy. + assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10)); + assert_eq!(LastPoolId::::get(), 1); + + // have the pool nominate. + assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3])); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 50 }] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: 10, pool_id: 1 }, + PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true }, + ] + ); + + // have three members join + let pre_20 = Balances::free_balance(20); + assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1)); + let pre_21 = Balances::free_balance(21); + assert_ok!(Pools::join(RuntimeOrigin::signed(21), 10, 1)); + let pre_22 = Balances::free_balance(22); + assert_ok!(Pools::join(RuntimeOrigin::signed(22), 10, 1)); + + // verify members balance is moved to pool. + assert_eq!(Balances::free_balance(20), pre_20 - 10); + assert_eq!(Balances::free_balance(21), pre_21 - 10); + assert_eq!(Balances::free_balance(22), pre_22 - 10); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, + StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, + StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, + PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 10, joined: true }, + PoolsEvent::Bonded { member: 22, pool_id: 1, bonded: 10, joined: true }, + ] + ); + + CurrentEra::::set(Some(2)); + // 20 is partially unbonding + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5)); + + CurrentEra::::set(Some(3)); + // 21 is fully unbonding + assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 5 }, + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 5, points: 5, era: 5 }, + PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 10, points: 10, era: 6 }, + ] + ); + + // with `TransferStake`, we can't migrate. + assert_noop!( + Pools::migrate_pool_to_delegate_stake(RuntimeOrigin::signed(10), 1), + PoolsError::::NotSupported + ); + + // we reset the adapter to `DelegateStake`. + LegacyAdapter::set(false); + + // cannot migrate the member delegation unless pool is migrated first. + assert_noop!( + Pools::migrate_delegation(RuntimeOrigin::signed(10), 20), + PoolsError::::PoolNotMigrated + ); + + // migrate the pool. + assert_ok!(Pools::migrate_pool_to_delegate_stake(RuntimeOrigin::signed(10), 1)); + + // migrate again does not work. + assert_noop!( + Pools::migrate_pool_to_delegate_stake(RuntimeOrigin::signed(10), 1), + PoolsError::::PoolAlreadyMigrated + ); + + // unclaimed delegations to the pool are stored in this account. + let proxy_delegator_1 = DelegatedStaking::generate_proxy_delegator(POOL1_BONDED); + + assert_eq!( + delegated_staking_events_since_last_call(), + vec![DelegatedStakingEvent::Delegated { + agent: POOL1_BONDED, + delegator: proxy_delegator_1, + amount: 50 + 10 * 3 + }] + ); + + // move to era 5 when 20 can withdraw unbonded funds. + CurrentEra::::set(Some(5)); + // Unbond works even without claiming delegation. Lets unbond 22. + assert_ok!(Pools::unbond(RuntimeOrigin::signed(22), 22, 5)); + + // withdraw fails for 20 before claiming delegation + assert_noop!( + Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 10), + DelegatedStakingError::::NotDelegator + ); + + let pre_claim_balance_20 = Balances::total_balance(&20); + assert_eq!(Balances::total_balance_on_hold(&20), 0); + + // migrate delegation for 20. This is permissionless and can be called by anyone. + assert_ok!(Pools::migrate_delegation(RuntimeOrigin::signed(10), 20)); + + // tokens moved to 20's account and held there. + assert_eq!(Balances::total_balance(&20), pre_claim_balance_20 + 10); + assert_eq!(Balances::total_balance_on_hold(&20), 10); + + // withdraw works now + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 5)); + + // balance unlocked in 20's account + assert_eq!(Balances::total_balance_on_hold(&20), 5); + assert_eq!(Balances::total_balance(&20), pre_claim_balance_20 + 10); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 5 }, + StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 5 } + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Unbonded { member: 22, pool_id: 1, balance: 5, points: 5, era: 8 }, + PoolsEvent::Withdrawn { member: 20, pool_id: 1, balance: 5, points: 5 }, + ] + ); + assert_eq!( + delegated_staking_events_since_last_call(), + vec![ + DelegatedStakingEvent::MigratedDelegation { + agent: POOL1_BONDED, + delegator: 20, + amount: 10 + }, + DelegatedStakingEvent::Released { agent: POOL1_BONDED, delegator: 20, amount: 5 } + ] + ); + + // MIGRATE 21 + let pre_migrate_balance_21 = Balances::total_balance(&21); + assert_eq!(Balances::total_balance_on_hold(&21), 0); + + // migrate delegation for 21. + assert_ok!(Pools::migrate_delegation(RuntimeOrigin::signed(10), 21)); + + // tokens moved to 21's account and held there. + assert_eq!(Balances::total_balance(&21), pre_migrate_balance_21 + 10); + assert_eq!(Balances::total_balance_on_hold(&21), 10); + + // withdraw fails since 21 only unbonds at era 6. + assert_noop!( + Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 10), + PoolsError::::CannotWithdrawAny + ); + + // go to era when 21 can unbond + CurrentEra::::set(Some(6)); + + // withdraw works now + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 10)); + + // all balance unlocked in 21's account + assert_eq!(Balances::total_balance_on_hold(&21), 0); + assert_eq!(Balances::total_balance(&21), pre_migrate_balance_21 + 10); + + // MIGRATE 22 + let pre_migrate_balance_22 = Balances::total_balance(&22); + assert_eq!(Balances::total_balance_on_hold(&22), 0); + + // migrate delegation for 22. + assert_ok!(Pools::migrate_delegation(RuntimeOrigin::signed(10), 22)); + + // tokens moved to 22's account and held there. + assert_eq!(Balances::total_balance(&22), pre_migrate_balance_22 + 10); + assert_eq!(Balances::total_balance_on_hold(&22), 10); + + // withdraw fails since 22 only unbonds at era 8. + assert_noop!( + Pools::withdraw_unbonded(RuntimeOrigin::signed(22), 22, 5), + PoolsError::::CannotWithdrawAny + ); + + // go to era when 22 can unbond + CurrentEra::::set(Some(10)); + + // withdraw works now + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(22), 22, 10)); + + // balance of 5 unlocked in 22's account + assert_eq!(Balances::total_balance_on_hold(&22), 10 - 5); + + // assert events for 21 and 22. + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 10 }, + StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 5 } + ] + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Withdrawn { member: 21, pool_id: 1, balance: 10, points: 10 }, + // 21 was fully unbonding and removed from pool. + PoolsEvent::MemberRemoved { member: 21, pool_id: 1 }, + PoolsEvent::Withdrawn { member: 22, pool_id: 1, balance: 5, points: 5 }, + ] + ); + assert_eq!( + delegated_staking_events_since_last_call(), + vec![ + DelegatedStakingEvent::MigratedDelegation { + agent: POOL1_BONDED, + delegator: 21, + amount: 10 + }, + DelegatedStakingEvent::Released { agent: POOL1_BONDED, delegator: 21, amount: 10 }, + DelegatedStakingEvent::MigratedDelegation { + agent: POOL1_BONDED, + delegator: 22, + amount: 10 + }, + DelegatedStakingEvent::Released { agent: POOL1_BONDED, delegator: 22, amount: 5 } + ] + ); + }) +} diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs new file mode 100644 index 0000000000000..1c0a0166fd9a9 --- /dev/null +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs @@ -0,0 +1,406 @@ +// 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. + +use frame_election_provider_support::VoteWeight; +use frame_support::{ + assert_ok, derive_impl, + pallet_prelude::*, + parameter_types, + traits::{ConstU64, ConstU8}, + PalletId, +}; +use frame_system::EnsureRoot; +use pallet_nomination_pools::{adapter::StakeStrategyType, BondType}; +use sp_runtime::{ + traits::{Convert, IdentityLookup}, + BuildStorage, FixedU128, Perbill, +}; + +type AccountId = u128; +type Nonce = u32; +type BlockNumber = u64; +type Balance = u128; + +pub(crate) type T = Runtime; + +pub(crate) const POOL1_BONDED: AccountId = 20318131474730217858575332831085u128; +pub(crate) const POOL1_REWARD: AccountId = 20397359637244482196168876781421u128; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Nonce = Nonce; + type RuntimeCall = RuntimeCall; + type Hash = sp_core::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + 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 = 5; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type FreezeIdentifier = RuntimeFreezeReason; + type MaxFreezes = ConstU32<1>; + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = (); +} + +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; +} + +impl pallet_staking::Config for Runtime { + type Currency = Balances; + type CurrencyBalance = Balance; + type UnixTime = pallet_timestamp::Pallet; + type CurrencyToVote = (); + type RewardRemainder = (); + type RuntimeEvent = RuntimeEvent; + type Slash = (); + type Reward = (); + type SessionsPerEra = (); + type SlashDeferDuration = (); + type AdminOrigin = frame_system::EnsureRoot; + type BondingDuration = BondingDuration; + type SessionInterface = (); + type EraPayout = pallet_staking::ConvertCurve; + type NextNewSession = (); + type MaxExposurePageSize = ConstU32<64>; + type ElectionProvider = + frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>; + type GenesisElectionProvider = Self::ElectionProvider; + type VoterList = VoterList; + type TargetList = pallet_staking::UseValidatorsMap; + type NominationsQuota = pallet_staking::FixedNominationsQuota<16>; + type MaxUnlockingChunks = ConstU32<32>; + type MaxControllersInDeprecationBatch = ConstU32<100>; + type HistoryDepth = ConstU32<84>; + type EventListeners = (Pools, DelegatedStaking); + type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; + type WeightInfo = (); + type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy; +} + +parameter_types! { + pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; +} + +type VoterBagsListInstance = pallet_bags_list::Instance1; +impl pallet_bags_list::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type BagThresholds = BagThresholds; + type ScoreProvider = Staking; + type Score = VoteWeight; +} + +pub struct BalanceToU256; +impl Convert for BalanceToU256 { + fn convert(n: Balance) -> sp_core::U256 { + n.into() + } +} + +pub struct U256ToBalance; +impl Convert 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 LegacyAdapter: bool = false; +} + +pub struct MockAdapter; +type DelegateStake = + pallet_nomination_pools::adapter::DelegateStake; +type TransferStake = pallet_nomination_pools::adapter::TransferStake; +impl pallet_nomination_pools::adapter::StakeStrategy for MockAdapter { + type Balance = Balance; + type AccountId = AccountId; + type CoreStaking = Staking; + + fn strategy_type() -> StakeStrategyType { + if LegacyAdapter::get() { + return TransferStake::strategy_type() + } + DelegateStake::strategy_type() + } + fn transferable_balance(pool_account: &Self::AccountId) -> Self::Balance { + if LegacyAdapter::get() { + return TransferStake::transferable_balance(pool_account) + } + DelegateStake::transferable_balance(pool_account) + } + + fn total_balance(pool_account: &Self::AccountId) -> Self::Balance { + if LegacyAdapter::get() { + return TransferStake::total_balance(pool_account) + } + DelegateStake::total_balance(pool_account) + } + + fn member_delegation_balance(member_account: &Self::AccountId) -> Self::Balance { + if LegacyAdapter::get() { + return TransferStake::member_delegation_balance(member_account) + } + DelegateStake::member_delegation_balance(member_account) + } + + fn pledge_bond( + who: &Self::AccountId, + pool_account: &Self::AccountId, + reward_account: &Self::AccountId, + amount: Self::Balance, + bond_type: BondType, + ) -> DispatchResult { + if LegacyAdapter::get() { + return TransferStake::pledge_bond(who, pool_account, reward_account, amount, bond_type) + } + DelegateStake::pledge_bond(who, pool_account, reward_account, amount, bond_type) + } + + fn member_withdraw( + who: &Self::AccountId, + pool_account: &Self::AccountId, + amount: Self::Balance, + num_slashing_spans: u32, + ) -> DispatchResult { + if LegacyAdapter::get() { + return TransferStake::member_withdraw(who, pool_account, amount, num_slashing_spans) + } + DelegateStake::member_withdraw(who, pool_account, amount, num_slashing_spans) + } + + fn has_pending_slash(pool_account: &Self::AccountId) -> bool { + if LegacyAdapter::get() { + return TransferStake::has_pending_slash(pool_account) + } + DelegateStake::has_pending_slash(pool_account) + } + + fn member_slash( + who: &Self::AccountId, + pool_account: &Self::AccountId, + amount: Self::Balance, + maybe_reporter: Option, + ) -> DispatchResult { + if LegacyAdapter::get() { + return TransferStake::member_slash(who, pool_account, amount, maybe_reporter) + } + DelegateStake::member_slash(who, pool_account, amount, maybe_reporter) + } + + fn migrate_nominator_to_agent( + agent: &Self::AccountId, + reward_account: &Self::AccountId, + ) -> DispatchResult { + if LegacyAdapter::get() { + return TransferStake::migrate_nominator_to_agent(agent, reward_account) + } + DelegateStake::migrate_nominator_to_agent(agent, reward_account) + } + + fn migrate_delegation( + agent: &Self::AccountId, + delegator: &Self::AccountId, + value: Self::Balance, + ) -> DispatchResult { + if LegacyAdapter::get() { + return TransferStake::migrate_delegation(agent, delegator, value) + } + DelegateStake::migrate_delegation(agent, delegator, value) + } +} +impl pallet_nomination_pools::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type Currency = Balances; + type RuntimeFreezeReason = RuntimeFreezeReason; + type RewardCounter = FixedU128; + type BalanceToU256 = BalanceToU256; + type U256ToBalance = U256ToBalance; + type StakeAdapter = MockAdapter; + type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; + type MaxMetadataLen = ConstU32<256>; + type MaxUnbonding = ConstU32<8>; + type MaxPointsToBalance = ConstU8<10>; + type PalletId = PoolsPalletId; + type AdminOrigin = EnsureRoot; +} + +parameter_types! { + pub const DelegatedStakingPalletId: PalletId = PalletId(*b"py/dlstk"); + pub const SlashRewardFraction: Perbill = Perbill::from_percent(1); +} +impl pallet_delegated_staking::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PalletId = DelegatedStakingPalletId; + type Currency = Balances; + type OnSlash = (); + type SlashRewardFraction = SlashRewardFraction; + type RuntimeHoldReason = RuntimeHoldReason; + type CoreStaking = Staking; +} +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Runtime { + System: frame_system, + Timestamp: pallet_timestamp, + Balances: pallet_balances, + Staking: pallet_staking, + VoterList: pallet_bags_list::, + Pools: pallet_nomination_pools, + DelegatedStaking: pallet_delegated_staking, + } +); + +pub fn new_test_ext() -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let _ = pallet_nomination_pools::GenesisConfig:: { + min_join_bond: 2, + min_create_bond: 2, + max_pools: Some(3), + max_members_per_pool: Some(5), + max_members: Some(3 * 5), + global_max_commission: Some(Perbill::from_percent(90)), + } + .assimilate_storage(&mut storage) + .unwrap(); + + let _ = pallet_balances::GenesisConfig:: { + balances: vec![(10, 100), (20, 100), (21, 100), (22, 100)], + } + .assimilate_storage(&mut storage) + .unwrap(); + + let mut ext = sp_io::TestExternalities::from(storage); + + ext.execute_with(|| { + // for events to be deposited. + frame_system::Pallet::::set_block_number(1); + + // set some limit for nominations. + assert_ok!(Staking::set_staking_configs( + RuntimeOrigin::root(), + pallet_staking::ConfigOp::Set(10), // minimum nominator bond + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + )); + }); + + ext +} + +parameter_types! { + static ObservedEventsPools: usize = 0; + static ObservedEventsStaking: usize = 0; + static ObservedEventsBalances: usize = 0; + static ObservedEventsDelegatedStaking: usize = 0; +} + +pub(crate) fn pool_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::Pools(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = ObservedEventsPools::get(); + ObservedEventsPools::set(events.len()); + events.into_iter().skip(already_seen).collect() +} + +pub(crate) fn staking_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::Staking(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = ObservedEventsStaking::get(); + ObservedEventsStaking::set(events.len()); + events.into_iter().skip(already_seen).collect() +} + +pub(crate) fn delegated_staking_events_since_last_call( +) -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map( + |e| if let RuntimeEvent::DelegatedStaking(inner) = e { Some(inner) } else { None }, + ) + .collect::>(); + let already_seen = ObservedEventsDelegatedStaking::get(); + ObservedEventsDelegatedStaking::set(events.len()); + events.into_iter().skip(already_seen).collect() +} diff --git a/substrate/frame/nomination-pools/test-staking/Cargo.toml b/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml similarity index 96% rename from substrate/frame/nomination-pools/test-staking/Cargo.toml rename to substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml index ada52db6de531..5f9bc9af3a214 100644 --- a/substrate/frame/nomination-pools/test-staking/Cargo.toml +++ b/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "pallet-nomination-pools-test-staking" +name = "pallet-nomination-pools-test-transfer-stake" version = "1.0.0" authors.workspace = true edition.workspace = true diff --git a/substrate/frame/nomination-pools/test-staking/src/lib.rs b/substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs similarity index 100% rename from substrate/frame/nomination-pools/test-staking/src/lib.rs rename to substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs diff --git a/substrate/frame/nomination-pools/test-staking/src/mock.rs b/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs similarity index 98% rename from substrate/frame/nomination-pools/test-staking/src/mock.rs rename to substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs index 93a05ddfae990..0970570453b46 100644 --- a/substrate/frame/nomination-pools/test-staking/src/mock.rs +++ b/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs @@ -180,7 +180,7 @@ impl pallet_nomination_pools::Config for Runtime { type RewardCounter = FixedU128; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; - type Staking = Staking; + type StakeAdapter = pallet_nomination_pools::adapter::TransferStake; type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; type MaxMetadataLen = ConstU32<256>; type MaxUnbonding = ConstU32<8>; diff --git a/substrate/frame/staking/src/ledger.rs b/substrate/frame/staking/src/ledger.rs index 67a86b86226cf..294918376d82c 100644 --- a/substrate/frame/staking/src/ledger.rs +++ b/substrate/frame/staking/src/ledger.rs @@ -35,7 +35,7 @@ use frame_support::{ defensive, ensure, traits::{Defensive, LockableCurrency}, }; -use sp_staking::StakingAccount; +use sp_staking::{StakingAccount, StakingInterface}; use sp_std::prelude::*; use crate::{ diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 4f91fd6dff220..053ecdef2b00b 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -361,7 +361,7 @@ pub type BalanceOf = ::CurrencyBalance; type PositiveImbalanceOf = <::Currency as Currency< ::AccountId, >>::PositiveImbalance; -type NegativeImbalanceOf = <::Currency as Currency< +pub type NegativeImbalanceOf = <::Currency as Currency< ::AccountId, >>::NegativeImbalance; diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 52361c6ccdc13..90374451a3a52 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -1161,11 +1161,6 @@ impl Pallet { ) -> Exposure> { EraInfo::::get_full_exposure(era, account) } - - /// Whether `who` is a virtual staker whose funds are managed by another pallet. - pub(crate) fn is_virtual_staker(who: &T::AccountId) -> bool { - VirtualStakers::::contains_key(who) - } } impl Pallet { @@ -1885,6 +1880,11 @@ impl StakingInterface for Pallet { } } + /// Whether `who` is a virtual staker whose funds are managed by another pallet. + fn is_virtual_staker(who: &T::AccountId) -> bool { + VirtualStakers::::contains_key(who) + } + fn slash_reward_fraction() -> Perbill { SlashRewardFraction::::get() } diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index f82266c03903c..284a801a0f050 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -39,6 +39,7 @@ use sp_runtime::{ use sp_staking::{ EraIndex, Page, SessionIndex, StakingAccount::{self, Controller, Stash}, + StakingInterface, }; use sp_std::prelude::*; diff --git a/substrate/frame/staking/src/slashing.rs b/substrate/frame/staking/src/slashing.rs index f831f625957d4..1fe608cd3358b 100644 --- a/substrate/frame/staking/src/slashing.rs +++ b/substrate/frame/staking/src/slashing.rs @@ -64,7 +64,7 @@ use sp_runtime::{ traits::{Saturating, Zero}, DispatchResult, RuntimeDebug, }; -use sp_staking::EraIndex; +use sp_staking::{EraIndex, StakingInterface}; use sp_std::vec::Vec; /// The proportion of the slashing reward to be paid out on the first slashing detection. diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index c7045508cea3f..28a61cd433139 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -285,6 +285,13 @@ pub trait StakingInterface { Self::status(who).map(|s| matches!(s, StakerStatus::Validator)).unwrap_or(false) } + /// Checks whether the staker is a virtual account. + /// + /// A virtual staker is an account whose locks are not managed by the [`StakingInterface`] + /// implementation but by an external pallet. See [`StakingUnchecked::virtual_bond`] for more + /// details. + fn is_virtual_staker(who: &Self::AccountId) -> bool; + /// Get the nominations of a stash, if they are a nominator, `None` otherwise. fn nominations(who: &Self::AccountId) -> Option> { match Self::status(who) { @@ -573,6 +580,12 @@ pub trait DelegationMigrator { delegator: &Self::AccountId, value: Self::Balance, ) -> DispatchResult; + + /// Drop the `Agent` account and its associated delegators. + /// + /// Also removed from [`StakingUnchecked`] as a Virtual Staker. Useful for testing. + #[cfg(feature = "runtime-benchmarks")] + fn drop_agent(agent: &Self::AccountId); } sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $);